@spexcode/transcript 0.7.0-next.4 → 0.7.0-next.6

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/parsers.d.ts CHANGED
@@ -8,6 +8,7 @@ export type MutableTool = {
8
8
  output?: string;
9
9
  outputLines: number;
10
10
  outputBytes: number;
11
+ outcome?: ToolOutcome;
11
12
  };
12
13
  export type MutableTurn = {
13
14
  id: string | null;
@@ -16,12 +17,14 @@ export type MutableTurn = {
16
17
  text?: string;
17
18
  tools: MutableTool[];
18
19
  };
20
+ export type ToolOutcome = 'failed' | 'rejected';
19
21
  export type ParsedEvent = {
20
22
  at: number | null;
21
23
  turn: MutableTurn | null;
22
24
  toolOutputs?: readonly {
23
25
  id: string;
24
26
  text: string;
27
+ outcome?: ToolOutcome;
25
28
  }[];
26
29
  };
27
30
  export type Parse = (value: unknown) => ParsedEvent | null;
package/dist/parsers.js CHANGED
@@ -82,7 +82,7 @@ export function claudeEvent(value) {
82
82
  const outputs = blocks.flatMap((block) => {
83
83
  const b = object(block);
84
84
  const id = string(b?.tool_use_id);
85
- return b?.type === 'tool_result' && id ? [{ id, text: compact(b?.content ?? '') }] : [];
85
+ return b?.type === 'tool_result' && id ? [{ id, text: compact(b?.content ?? ''), ...(b?.is_error === true ? { outcome: 'failed' } : {}) }] : [];
86
86
  });
87
87
  if (outputs.length)
88
88
  return { at: eventAt, turn: null, toolOutputs: outputs };
@@ -204,9 +204,14 @@ export function codexAppServerEvent(value) {
204
204
  output = item.result ?? item.error;
205
205
  else if (type === 'dynamicToolCall')
206
206
  output = item.contentItems ?? item.output ?? item.error;
207
- return output === undefined || output === null
208
- ? { at: eventAt, turn: null }
209
- : { at: eventAt, turn: null, toolOutputs: [{ id, text: compact(output) }] };
207
+ // the item status is the app-server's own verdict: `failed`, or `declined` when the person refused the call —
208
+ // a declined call has no output, so the empty result is what ends its "running"
209
+ const status = string(item.status);
210
+ const outcome = status === 'failed' ? 'failed' : status === 'declined' ? 'rejected' : undefined;
211
+ if (output === undefined || output === null) {
212
+ return outcome ? { at: eventAt, turn: null, toolOutputs: [{ id, text: '', outcome }] } : { at: eventAt, turn: null };
213
+ }
214
+ return { at: eventAt, turn: null, toolOutputs: [{ id, text: compact(output), ...(outcome ? { outcome } : {}) }] };
210
215
  }
211
216
  // Agent-message deltas are fragments of one native item. The closure remembers only that item's text and
212
217
  // re-emits its native id, allowing IntervalCollector to replace the earlier turn in place.
@@ -268,7 +273,7 @@ export function piEvent(value) {
268
273
  }
269
274
  if (message.role === 'toolResult') {
270
275
  const id = string(message.toolCallId);
271
- return id ? { at: eventAt, turn: null, toolOutputs: [{ id, text: blockText(message.content) ?? compact(message.content ?? '') }] } : null;
276
+ return id ? { at: eventAt, turn: null, toolOutputs: [{ id, text: blockText(message.content) ?? compact(message.content ?? ''), ...(message.isError === true ? { outcome: 'failed' } : {}) }] } : null;
272
277
  }
273
278
  return null;
274
279
  }
@@ -303,7 +308,7 @@ export function geminiEvent(value) {
303
308
  for (const callValue of items(message.toolCalls)) {
304
309
  const call = object(callValue);
305
310
  const id = string(call?.id) ?? `tool-${turn.tools.length}`;
306
- turn.tools.push({ id, name: string(call?.name) ?? 'tool', input: call?.args === undefined ? undefined : compact(call.args), outputLines: 0, outputBytes: 0 });
311
+ turn.tools.push({ id, name: string(call?.name) ?? 'tool', input: call?.args === undefined ? undefined : compact(call.args), outputLines: 0, outputBytes: 0, ...(call?.status === 'error' ? { outcome: 'failed' } : {}) });
307
312
  }
308
313
  if (!turn.text && !turn.tools.length)
309
314
  return null;
@@ -325,7 +330,7 @@ export function openclawEvent(value) {
325
330
  }
326
331
  if (message.role === 'toolResult') {
327
332
  const id = string(message.toolCallId);
328
- return id ? { at: eventAt, turn: null, toolOutputs: [{ id, text: blockText(message.content) ?? compact(message.content ?? '') }] } : null;
333
+ return id ? { at: eventAt, turn: null, toolOutputs: [{ id, text: blockText(message.content) ?? compact(message.content ?? ''), ...(message.isError === true ? { outcome: 'failed' } : {}) }] } : null;
329
334
  }
330
335
  if (message.role === 'assistant') {
331
336
  const turn = { id: idOf(entry) ?? idOf(message), at: eventAt, role: 'assistant', tools: [] };
@@ -414,6 +419,8 @@ export function opencodeEvents(value) {
414
419
  tool.output = output.slice(0, MAX_OUTPUT_BYTES);
415
420
  tool.outputBytes = Buffer.byteLength(output);
416
421
  tool.outputLines = lineCount(output);
422
+ if (status === 'error')
423
+ tool.outcome = 'failed';
417
424
  }
418
425
  turn.tools.push(tool);
419
426
  }
@@ -466,6 +473,8 @@ export class IntervalCollector {
466
473
  tool.outputLines += lineCount(output.text);
467
474
  if (tool.output === undefined)
468
475
  tool.output = '';
476
+ if (output.outcome)
477
+ tool.outcome = output.outcome;
469
478
  const remaining = Math.max(0, MAX_OUTPUT_BYTES - Buffer.byteLength(tool.output));
470
479
  tool.output += output.text.slice(0, remaining);
471
480
  if (bytes > remaining)
package/dist/turns.d.ts CHANGED
@@ -9,6 +9,7 @@ export type TranscriptTool = Readonly<{
9
9
  output?: string;
10
10
  outputLines: number;
11
11
  outputBytes: number;
12
+ outcome?: 'failed' | 'rejected';
12
13
  }>;
13
14
  export type TranscriptTurn = Readonly<{
14
15
  id: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spexcode/transcript",
3
- "version": "0.7.0-next.4",
3
+ "version": "0.7.0-next.6",
4
4
  "type": "module",
5
5
  "description": "Normalized agent transcripts: one parser per harness, a bounded interval reader over a native thread file or an in-memory event stream, and the full/delta frame protocol every transport and renderer share.",
6
6
  "files": [