@itsaplan/runner 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +117 -5
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -25,11 +25,13 @@ class AnswerStream {
|
|
|
25
25
|
sawPartialText = false;
|
|
26
26
|
sawAnyText = false;
|
|
27
27
|
sending = Promise.resolve();
|
|
28
|
+
usage;
|
|
28
29
|
constructor(format, threadId, runId, send) {
|
|
29
30
|
this.format = format;
|
|
30
31
|
this.threadId = threadId;
|
|
31
32
|
this.runId = runId;
|
|
32
33
|
this.send = send;
|
|
34
|
+
this.usage = new UsageReader(format);
|
|
33
35
|
this.messageId = `msg-${runId}`;
|
|
34
36
|
this.queued.push({ type: "RUN_STARTED", threadId, runId });
|
|
35
37
|
}
|
|
@@ -48,6 +50,9 @@ class AnswerStream {
|
|
|
48
50
|
startedSession() {
|
|
49
51
|
return this.sessionId;
|
|
50
52
|
}
|
|
53
|
+
contextUsage() {
|
|
54
|
+
return this.usage.value();
|
|
55
|
+
}
|
|
51
56
|
async flush() {
|
|
52
57
|
const sending = this.sending.catch(() => {}).then(() => this.sendQueued());
|
|
53
58
|
this.sending = sending.catch(() => {});
|
|
@@ -127,6 +132,7 @@ class AnswerStream {
|
|
|
127
132
|
}
|
|
128
133
|
if (!parsed || typeof parsed !== "object")
|
|
129
134
|
return;
|
|
135
|
+
this.usage.readLine(parsed);
|
|
130
136
|
switch (this.format) {
|
|
131
137
|
case "claude-stream-json":
|
|
132
138
|
this.readClaudeLine(parsed);
|
|
@@ -275,7 +281,9 @@ class AnswerStream {
|
|
|
275
281
|
}
|
|
276
282
|
}
|
|
277
283
|
readPartial(event) {
|
|
278
|
-
if (event
|
|
284
|
+
if (!event)
|
|
285
|
+
return;
|
|
286
|
+
if (event.type !== "content_block_delta")
|
|
279
287
|
return;
|
|
280
288
|
if (event.delta?.type === "text_delta" && event.delta.text) {
|
|
281
289
|
this.sawPartialText = true;
|
|
@@ -313,6 +321,108 @@ class AnswerStream {
|
|
|
313
321
|
});
|
|
314
322
|
}
|
|
315
323
|
}
|
|
324
|
+
|
|
325
|
+
class UsageReader {
|
|
326
|
+
format;
|
|
327
|
+
last = null;
|
|
328
|
+
buffered = "";
|
|
329
|
+
constructor(format) {
|
|
330
|
+
this.format = format;
|
|
331
|
+
}
|
|
332
|
+
write(chunk) {
|
|
333
|
+
if (this.format === "text")
|
|
334
|
+
return;
|
|
335
|
+
this.buffered += chunk;
|
|
336
|
+
const lines = this.buffered.split(`
|
|
337
|
+
`);
|
|
338
|
+
this.buffered = lines.pop() ?? "";
|
|
339
|
+
for (const line of lines)
|
|
340
|
+
this.readText(line);
|
|
341
|
+
}
|
|
342
|
+
end() {
|
|
343
|
+
if (!this.buffered)
|
|
344
|
+
return;
|
|
345
|
+
this.readText(this.buffered);
|
|
346
|
+
this.buffered = "";
|
|
347
|
+
}
|
|
348
|
+
value() {
|
|
349
|
+
if (this.format === "copilot-json")
|
|
350
|
+
return null;
|
|
351
|
+
return this.last ?? undefined;
|
|
352
|
+
}
|
|
353
|
+
readText(line) {
|
|
354
|
+
const trimmed = line.trim();
|
|
355
|
+
if (!trimmed)
|
|
356
|
+
return;
|
|
357
|
+
try {
|
|
358
|
+
const parsed = JSON.parse(trimmed);
|
|
359
|
+
if (parsed && typeof parsed === "object")
|
|
360
|
+
this.readLine(parsed);
|
|
361
|
+
} catch {}
|
|
362
|
+
}
|
|
363
|
+
readLine(parsed) {
|
|
364
|
+
switch (this.format) {
|
|
365
|
+
case "claude-stream-json":
|
|
366
|
+
this.readClaude(parsed);
|
|
367
|
+
return;
|
|
368
|
+
case "codex-jsonl":
|
|
369
|
+
this.readCodex(parsed);
|
|
370
|
+
return;
|
|
371
|
+
case "opencode-json":
|
|
372
|
+
this.readOpencode(parsed);
|
|
373
|
+
return;
|
|
374
|
+
case "antigravity-stream-json":
|
|
375
|
+
this.readAntigravity(parsed);
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
readClaude(message) {
|
|
380
|
+
const event = message.type === "stream_event" ? message.event : undefined;
|
|
381
|
+
if (!event)
|
|
382
|
+
return;
|
|
383
|
+
const usage = event.type === "message_start" ? event.message?.usage : event.usage;
|
|
384
|
+
if (!usage)
|
|
385
|
+
return;
|
|
386
|
+
const read = (usage.input_tokens ?? 0) + (usage.cache_read_input_tokens ?? 0) + (usage.cache_creation_input_tokens ?? 0);
|
|
387
|
+
const written = usage.output_tokens ?? 0;
|
|
388
|
+
if (event.type === "message_start") {
|
|
389
|
+
this.last = { inputTokens: read, outputTokens: written };
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
if (event.type !== "message_delta")
|
|
393
|
+
return;
|
|
394
|
+
this.last = { inputTokens: read || (this.last?.inputTokens ?? 0), outputTokens: written };
|
|
395
|
+
}
|
|
396
|
+
readCodex(message) {
|
|
397
|
+
if (message.type !== "turn.completed" || !message.usage)
|
|
398
|
+
return;
|
|
399
|
+
this.last = {
|
|
400
|
+
inputTokens: message.usage.input_tokens ?? 0,
|
|
401
|
+
outputTokens: message.usage.output_tokens ?? 0
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
readOpencode(message) {
|
|
405
|
+
const tokens = message.part?.type === "step-finish" ? message.part.tokens : undefined;
|
|
406
|
+
if (!tokens)
|
|
407
|
+
return;
|
|
408
|
+
this.last = {
|
|
409
|
+
inputTokens: (tokens.input ?? 0) + (tokens.cache?.read ?? 0) + (tokens.cache?.write ?? 0),
|
|
410
|
+
outputTokens: (tokens.output ?? 0) + (tokens.reasoning ?? 0)
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
readAntigravity(message) {
|
|
414
|
+
const step = message.step_update;
|
|
415
|
+
if (!step)
|
|
416
|
+
return;
|
|
417
|
+
const usage = step.usage ?? message.usage;
|
|
418
|
+
if (!usage)
|
|
419
|
+
return;
|
|
420
|
+
this.last = {
|
|
421
|
+
inputTokens: (usage.input_tokens ?? 0) + (usage.cache_read_tokens ?? 0),
|
|
422
|
+
outputTokens: (usage.output_tokens ?? 0) + (usage.thinking_tokens ?? 0)
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
}
|
|
316
426
|
function tail(text, limit) {
|
|
317
427
|
return text.length <= limit ? text : `…${text.slice(-limit)}`;
|
|
318
428
|
}
|
|
@@ -705,12 +815,12 @@ async function answer(config, client, message, stop) {
|
|
|
705
815
|
return;
|
|
706
816
|
if (outcome.status === "success") {
|
|
707
817
|
await stream.finish(outcome.output);
|
|
708
|
-
await client.chatResult(message.id, { status: "success" });
|
|
818
|
+
await client.chatResult(message.id, { status: "success", usage: stream.contextUsage() });
|
|
709
819
|
return;
|
|
710
820
|
}
|
|
711
821
|
const error = outcome.error ?? "The command failed";
|
|
712
822
|
await stream.fail(error, outcome.output);
|
|
713
|
-
await client.chatResult(message.id, { status: "failed", error });
|
|
823
|
+
await client.chatResult(message.id, { status: "failed", error, usage: stream.contextUsage() });
|
|
714
824
|
}
|
|
715
825
|
|
|
716
826
|
// src/client.ts
|
|
@@ -816,8 +926,10 @@ async function handle(config, client, log2, run) {
|
|
|
816
926
|
const label = run.issueIdentifier ?? `run ${run.id}`;
|
|
817
927
|
log2(`${label}: started (${run.trigger})`);
|
|
818
928
|
try {
|
|
819
|
-
const
|
|
820
|
-
await client.
|
|
929
|
+
const usage = new UsageReader(config.outputFormat);
|
|
930
|
+
const outcome = await withHeartbeat(log2, () => client.heartbeat(run.id), execute(config, taskOf(run), { onData: (chunk) => usage.write(chunk) }));
|
|
931
|
+
usage.end();
|
|
932
|
+
await client.report(run.id, { ...outcome, usage: usage.value() });
|
|
821
933
|
log2(`${label}: ${outcome.status}${outcome.error ? ` — ${outcome.error}` : ""}`);
|
|
822
934
|
} catch (err) {
|
|
823
935
|
const message = err instanceof Error ? err.message : String(err);
|