070959e133
landing-page-staging / Deploy landing page to staging (push) Has been skipped
landing-page-ci / Validate landing page (push) Failing after 4s
visual-baseline / Capture visual baselines (push) Has been cancelled
bake-plugin-previews / Bake plugin previews (push) Has been cancelled
64 lines
2.1 KiB
JavaScript
64 lines
2.1 KiB
JavaScript
// OD-faithful cursor-agent renderer.
|
|
//
|
|
// Matches the JSONL shape OD's `json-event-stream.ts:handleCursorEvent`
|
|
// parser accepts:
|
|
// {"type":"system","subtype":"init","model":"..."}
|
|
// {"type":"assistant","message":{
|
|
// "content":[{"type":"text","text":"..."}, ...]},
|
|
// "timestamp_ms": ...}
|
|
// {"type":"result","usage":{"inputTokens","outputTokens","cacheReadTokens","cacheWriteTokens"},
|
|
// "duration_ms": ...}
|
|
//
|
|
// Cursor's parser handles delta-vs-replacement detection itself: when a
|
|
// later text block STARTS WITH the prior accumulated text, the parser
|
|
// strips the prefix and emits only the delta. So we can either emit a
|
|
// single message containing the full text, OR emit progressive chunks
|
|
// where each chunk is the cumulative-so-far. We use the single-message
|
|
// form — simplest, no risk of state desync.
|
|
//
|
|
// Like gemini, the cursor-agent parser does NOT recognize tool events.
|
|
// Tool calls in the recording are ignored; only the final assistant
|
|
// text is rendered.
|
|
|
|
import { writeFile } from 'node:fs/promises';
|
|
|
|
const sleep = ms => new Promise(r => setTimeout(r, ms));
|
|
|
|
export async function renderAsCursorAgent(events, opts = {}) {
|
|
const emit = opts.emit ?? (s => process.stdout.write(s));
|
|
const maxSleep = opts.maxSleepMs ?? 2000;
|
|
const meta = events.find(e => e.type === 'meta');
|
|
|
|
emit(JSON.stringify({
|
|
type: 'system',
|
|
subtype: 'init',
|
|
model: meta?.model ?? 'cursor-default',
|
|
}) + '\n');
|
|
|
|
if (!opts.noDelay) await sleep(Math.min(maxSleep, 200));
|
|
|
|
for (const e of events) {
|
|
if (e.type === 'report') {
|
|
emit(JSON.stringify({
|
|
type: 'assistant',
|
|
message: {
|
|
content: [{ type: 'text', text: e.content }],
|
|
},
|
|
timestamp_ms: Date.now(),
|
|
}) + '\n');
|
|
if (opts.reportFile) await writeFile(opts.reportFile, e.content).catch(() => {});
|
|
}
|
|
}
|
|
|
|
emit(JSON.stringify({
|
|
type: 'result',
|
|
usage: {
|
|
inputTokens: 0,
|
|
outputTokens: meta?.total_tokens ?? 0,
|
|
cacheReadTokens: 0,
|
|
cacheWriteTokens: 0,
|
|
},
|
|
duration_ms: meta?.duration_ms ?? 0,
|
|
}) + '\n');
|
|
}
|