@narumitw/pi-subagents 2.0.2 → 2.0.4

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.
Files changed (62) hide show
  1. package/README.md +253 -160
  2. package/dist/chunks/auto-transport-SY2VHUFH.ts +8 -8
  3. package/dist/chunks/capability-grant-CGEWOEKE.ts +3 -3
  4. package/dist/chunks/{chunk-F5YWVHQX.ts → chunk-2OUVSORM.ts} +32 -32
  5. package/dist/chunks/chunk-434NII74.ts +4 -4
  6. package/dist/chunks/chunk-7QPPBBXZ.ts +3 -3
  7. package/dist/chunks/chunk-AEUYS7JC.ts +2 -2
  8. package/dist/chunks/chunk-BFK2Z4ZF.ts +1 -1
  9. package/dist/chunks/chunk-DQMN4OYM.ts +9 -9
  10. package/dist/chunks/chunk-DSOOH73Y.ts +1 -1
  11. package/dist/chunks/chunk-EFBPISNT.ts +5 -5
  12. package/dist/chunks/chunk-EWWXFU7N.ts +2 -2
  13. package/dist/chunks/chunk-F4L2UVSK.ts +3 -3
  14. package/dist/chunks/chunk-G3RSMSXJ.ts +3 -3
  15. package/dist/chunks/chunk-HA36LPCO.ts +1 -1
  16. package/dist/chunks/chunk-HKC4ES4B.ts +1 -1
  17. package/dist/chunks/chunk-I2FAK44T.ts +1 -1
  18. package/dist/chunks/chunk-NIF42QMF.ts +5 -5
  19. package/dist/chunks/chunk-O4VO6JUJ.ts +3 -3
  20. package/dist/chunks/chunk-ONDTY4EL.ts +1 -1
  21. package/dist/chunks/chunk-OVPHFGKO.ts +1 -1
  22. package/dist/chunks/chunk-PSK43Y6A.ts +2 -2
  23. package/dist/chunks/chunk-QBORTI4W.ts +3 -3
  24. package/dist/chunks/chunk-QDVGH2X7.ts +2 -2
  25. package/dist/chunks/chunk-RHRQMT7Q.ts +7 -7
  26. package/dist/chunks/chunk-RSUXZD6S.ts +1 -1
  27. package/dist/chunks/chunk-RTCVYIZA.ts +1 -1
  28. package/dist/chunks/chunk-SCZ33MYW.ts +4 -4
  29. package/dist/chunks/chunk-SWGQLFSD.ts +1 -1
  30. package/dist/chunks/chunk-UVFVHIOP.ts +12 -12
  31. package/dist/chunks/completion-delivery-YPOWSSV3.ts +6 -6
  32. package/dist/chunks/config-status-J4GPWP46.ts +8 -8
  33. package/dist/chunks/{config-ui-3IBMHTQB.ts → config-ui-7IMI3I4B.ts} +24 -24
  34. package/dist/chunks/consult-NYXVDN5G.ts +24 -24
  35. package/dist/chunks/context-QDKLVQXE.ts +1 -1
  36. package/dist/chunks/{create-stateful-transport-4WKZHRSI.ts → create-stateful-transport-F5OG2LEY.ts} +7 -7
  37. package/dist/chunks/cwd-policy-NB6XZ5GF.ts +3 -3
  38. package/dist/chunks/delegation-contract-WJPT4FVS.ts +2 -2
  39. package/dist/chunks/discovery-K25T3SH4.ts +4 -4
  40. package/dist/chunks/execution-DS2DEIQZ.ts +23 -23
  41. package/dist/chunks/in-process-transport-TEYAWSEC.ts +16 -16
  42. package/dist/chunks/inspect-UOQQ3KNL.ts +14 -14
  43. package/dist/chunks/peer-communication-KL36Z6CO.ts +2 -2
  44. package/dist/chunks/persistence-VSGXAW4P.ts +12 -12
  45. package/dist/chunks/registry-E6XPJB7L.ts +12 -12
  46. package/dist/chunks/retained-semantic-state-7WXSGRJU.ts +3 -3
  47. package/dist/chunks/{rpc-transport-Y6LA5BOP.ts → rpc-transport-ZNNBRUSY.ts} +118 -48
  48. package/dist/chunks/rpc-transport-ZNNBRUSY.ts.map +7 -0
  49. package/dist/chunks/runtime-policy-5YELCOVC.ts +1 -1
  50. package/dist/chunks/semantic-snapshot-WPJ3UAFW.ts +1 -1
  51. package/dist/chunks/spawn-idempotency-QGHO2WKC.ts +1 -1
  52. package/dist/chunks/stateful-lifecycle-JAR6K55H.ts +1 -1
  53. package/dist/chunks/subprocess-transport-C2XVR25R.ts +21 -21
  54. package/dist/chunks/workspace-WDKLN72J.ts +1 -1
  55. package/dist/index.ts +25 -25
  56. package/package.json +5 -5
  57. package/src/rpc-transport.ts +20 -6
  58. package/src/rpc-turn-capture.ts +98 -23
  59. package/dist/chunks/rpc-transport-Y6LA5BOP.ts.map +0 -7
  60. /package/dist/chunks/{chunk-F5YWVHQX.ts.map → chunk-2OUVSORM.ts.map} +0 -0
  61. /package/dist/chunks/{config-ui-3IBMHTQB.ts.map → config-ui-7IMI3I4B.ts.map} +0 -0
  62. /package/dist/chunks/{create-stateful-transport-4WKZHRSI.ts.map → create-stateful-transport-F5OG2LEY.ts.map} +0 -0
@@ -5,7 +5,7 @@ import {
5
5
  assertSubagentDepthAllowed,
6
6
  parsePositiveInteger,
7
7
  resolveDefaultSubagentTimeoutMs
8
- } from "./chunk-RRR66UWR.js";
8
+ } from "./chunk-RRR66UWR.ts";
9
9
  export {
10
10
  FALLBACK_TIMEOUT_MS,
11
11
  assertSubagentDepthAllowed,
@@ -7,7 +7,7 @@ import {
7
7
  captureSemanticSnapshot,
8
8
  evaluateSemanticCompatibility,
9
9
  isSemanticSnapshot
10
- } from "./chunk-BOAXY55Y.js";
10
+ } from "./chunk-BOAXY55Y.ts";
11
11
  export {
12
12
  SEMANTIC_SNAPSHOT_VERSION,
13
13
  captureRepositoryGeneration,
@@ -4,7 +4,7 @@ import {
4
4
  MAX_SPAWN_IDEMPOTENCY_KEY_LENGTH,
5
5
  assertSpawnIdempotencyKey,
6
6
  hashSpawnRequest
7
- } from "./chunk-S2IWVK3J.js";
7
+ } from "./chunk-S2IWVK3J.ts";
8
8
  export {
9
9
  MAX_SPAWN_IDEMPOTENCY_KEY_LENGTH,
10
10
  assertSpawnIdempotencyKey,
@@ -5,7 +5,7 @@ import {
5
5
  cleanupPersistedWorkspaces,
6
6
  disposeStatefulRuntime,
7
7
  waitForOwnedSpawn
8
- } from "./chunk-P7OH4XMF.js";
8
+ } from "./chunk-P7OH4XMF.ts";
9
9
  export {
10
10
  assertCurrentSpawn,
11
11
  cleanupPersistedWorkspaces,
@@ -3,43 +3,43 @@
3
3
  import {
4
4
  buildStatefulTurnPrompt,
5
5
  resolveStatefulTurnTimeout
6
- } from "./chunk-SCZ33MYW.js";
6
+ } from "./chunk-SCZ33MYW.ts";
7
7
  import {
8
8
  runSingleAgent
9
- } from "./chunk-DQMN4OYM.js";
10
- import "./chunk-IGAMVWFN.js";
11
- import "./chunk-47SK2QYC.js";
12
- import "./chunk-HKC4ES4B.js";
13
- import "./chunk-DSOOH73Y.js";
9
+ } from "./chunk-DQMN4OYM.ts";
10
+ import "./chunk-IGAMVWFN.ts";
11
+ import "./chunk-47SK2QYC.ts";
12
+ import "./chunk-HKC4ES4B.ts";
13
+ import "./chunk-DSOOH73Y.ts";
14
14
  import {
15
15
  CHILD_PEER_TOOL_NAMES,
16
16
  childPeerBridgePath,
17
17
  peerBridgeEnvironment
18
- } from "./chunk-EWWXFU7N.js";
18
+ } from "./chunk-EWWXFU7N.ts";
19
19
  import {
20
20
  resolvePiPromptResources
21
- } from "./chunk-G6VZTCFH.js";
22
- import "./chunk-G3RSMSXJ.js";
21
+ } from "./chunk-G6VZTCFH.ts";
22
+ import "./chunk-G3RSMSXJ.ts";
23
23
  import {
24
24
  getResultFinalOutput
25
- } from "./chunk-AEUYS7JC.js";
26
- import "./chunk-RTCVYIZA.js";
27
- import "./chunk-ONDTY4EL.js";
25
+ } from "./chunk-AEUYS7JC.ts";
26
+ import "./chunk-RTCVYIZA.ts";
27
+ import "./chunk-ONDTY4EL.ts";
28
28
  import {
29
29
  discoverAgents
30
- } from "./chunk-PSK43Y6A.js";
31
- import "./chunk-RSUXZD6S.js";
30
+ } from "./chunk-PSK43Y6A.ts";
31
+ import "./chunk-RSUXZD6S.ts";
32
32
  import {
33
33
  resolveSubagentThinkingLevel
34
- } from "./chunk-7QPPBBXZ.js";
34
+ } from "./chunk-7QPPBBXZ.ts";
35
35
  import {
36
36
  readSubagentSettings
37
- } from "./chunk-QBORTI4W.js";
38
- import "./chunk-7OBINKFM.js";
39
- import "./chunk-DPPVEQAM.js";
40
- import "./chunk-I2FAK44T.js";
41
- import "./chunk-H3FP6DLR.js";
42
- import "./chunk-RRR66UWR.js";
37
+ } from "./chunk-QBORTI4W.ts";
38
+ import "./chunk-7OBINKFM.ts";
39
+ import "./chunk-DPPVEQAM.ts";
40
+ import "./chunk-I2FAK44T.ts";
41
+ import "./chunk-H3FP6DLR.ts";
42
+ import "./chunk-RRR66UWR.ts";
43
43
 
44
44
  // src/subprocess-transport.ts
45
45
  function resolveStatefulSubprocessThinkingLevel(agents, record) {
@@ -3,7 +3,7 @@
3
3
  import {
4
4
  WorkspaceManager,
5
5
  assertWorkspaceIsolationReady
6
- } from "./chunk-6KJ34M6S.js";
6
+ } from "./chunk-6KJ34M6S.ts";
7
7
  export {
8
8
  WorkspaceManager,
9
9
  assertWorkspaceIsolationReady
package/dist/index.ts CHANGED
@@ -2,35 +2,35 @@
2
2
  // @ts-nocheck -- generated JavaScript uses a .ts extension for Pi's Jiti loader.
3
3
  import {
4
4
  registerSubagentInspect
5
- } from "./chunks/chunk-F4L2UVSK.js";
5
+ } from "./chunks/chunk-F4L2UVSK.ts";
6
6
  import {
7
7
  SubagentParams,
8
8
  hasUsableAggregator
9
- } from "./chunks/chunk-NIF42QMF.js";
9
+ } from "./chunks/chunk-NIF42QMF.ts";
10
10
  import {
11
11
  registerStatefulSubagents
12
- } from "./chunks/chunk-F5YWVHQX.js";
12
+ } from "./chunks/chunk-2OUVSORM.ts";
13
13
  import {
14
14
  discoverAgentCatalog,
15
15
  formatAgentCatalog,
16
16
  formatUsageStats,
17
17
  registerSubagentConsult
18
- } from "./chunks/chunk-RHRQMT7Q.js";
19
- import "./chunks/chunk-P7OH4XMF.js";
20
- import "./chunks/chunk-N7BLVXKK.js";
18
+ } from "./chunks/chunk-RHRQMT7Q.ts";
19
+ import "./chunks/chunk-P7OH4XMF.ts";
20
+ import "./chunks/chunk-N7BLVXKK.ts";
21
21
  import {
22
22
  getResultFinalOutput,
23
23
  isResultError
24
- } from "./chunks/chunk-AEUYS7JC.js";
25
- import "./chunks/chunk-RTCVYIZA.js";
26
- import "./chunks/chunk-ONDTY4EL.js";
27
- import "./chunks/chunk-O4VO6JUJ.js";
24
+ } from "./chunks/chunk-AEUYS7JC.ts";
25
+ import "./chunks/chunk-RTCVYIZA.ts";
26
+ import "./chunks/chunk-ONDTY4EL.ts";
27
+ import "./chunks/chunk-O4VO6JUJ.ts";
28
28
  import {
29
29
  cachedModuleLoader,
30
30
  throwIfAborted
31
- } from "./chunks/chunk-HOP5FXDT.js";
32
- import "./chunks/chunk-PSK43Y6A.js";
33
- import "./chunks/chunk-RSUXZD6S.js";
31
+ } from "./chunks/chunk-HOP5FXDT.ts";
32
+ import "./chunks/chunk-PSK43Y6A.ts";
33
+ import "./chunks/chunk-RSUXZD6S.ts";
34
34
  import {
35
35
  DEFAULT_CONSULTATION_CWD_POLICY,
36
36
  DEFAULT_CONSULT_RESOURCE_POLICY,
@@ -39,25 +39,25 @@ import {
39
39
  inspectSubagentSettings,
40
40
  readSubagentSettings,
41
41
  resolveBlockingMaxParallelTasks
42
- } from "./chunks/chunk-QBORTI4W.js";
43
- import "./chunks/chunk-7OBINKFM.js";
44
- import "./chunks/chunk-DPPVEQAM.js";
42
+ } from "./chunks/chunk-QBORTI4W.ts";
43
+ import "./chunks/chunk-7OBINKFM.ts";
44
+ import "./chunks/chunk-DPPVEQAM.ts";
45
45
  import {
46
46
  SUBAGENT_COMPLETION_MESSAGE_TYPE,
47
47
  renderCompletionMessage
48
- } from "./chunks/chunk-QDVGH2X7.js";
48
+ } from "./chunks/chunk-QDVGH2X7.ts";
49
49
  import {
50
50
  expansionHint,
51
51
  formatToolActivity,
52
52
  safeBlock,
53
53
  safeLine
54
- } from "./chunks/chunk-BFK2Z4ZF.js";
55
- import "./chunks/chunk-SWGQLFSD.js";
56
- import "./chunks/chunk-I2FAK44T.js";
54
+ } from "./chunks/chunk-BFK2Z4ZF.ts";
55
+ import "./chunks/chunk-SWGQLFSD.ts";
56
+ import "./chunks/chunk-I2FAK44T.ts";
57
57
  import {
58
58
  MAX_BLOCKING_PARALLEL_CONCURRENCY
59
- } from "./chunks/chunk-H3FP6DLR.js";
60
- import "./chunks/chunk-S2IWVK3J.js";
59
+ } from "./chunks/chunk-H3FP6DLR.ts";
60
+ import "./chunks/chunk-S2IWVK3J.ts";
61
61
 
62
62
  // src/subagents-extension.ts
63
63
  import {
@@ -85,10 +85,10 @@ function registerSubagentConfigLifecycle(pi) {
85
85
  }
86
86
  function registerSubagentConfigCommand(pi, runtime, owner = registerSubagentConfigLifecycle(pi), dependencies = {}) {
87
87
  const loadConfigUi = cachedModuleLoader(
88
- dependencies.loadConfigUi ?? (() => import("./chunks/config-ui-3IBMHTQB.js"))
88
+ dependencies.loadConfigUi ?? (() => import("./chunks/config-ui-7IMI3I4B.ts"))
89
89
  );
90
90
  const loadConfigStatus = cachedModuleLoader(
91
- dependencies.loadConfigStatus ?? (() => import("./chunks/config-status-J4GPWP46.js"))
91
+ dependencies.loadConfigStatus ?? (() => import("./chunks/config-status-J4GPWP46.ts"))
92
92
  );
93
93
  pi.registerCommand("subagents", {
94
94
  description: "Manage current-session subagents and user settings",
@@ -832,7 +832,7 @@ ${expansionHint()}`;
832
832
  function subagents_extension_default(pi, dependencies = {}) {
833
833
  pi.registerMessageRenderer(SUBAGENT_COMPLETION_MESSAGE_TYPE, renderCompletionMessage);
834
834
  const loadBlockingExecution = cachedModuleLoader(
835
- dependencies.loadBlockingExecution ?? (() => import("./chunks/execution-DS2DEIQZ.js"))
835
+ dependencies.loadBlockingExecution ?? (() => import("./chunks/execution-DS2DEIQZ.ts"))
836
836
  );
837
837
  const configOwner = registerSubagentConfigLifecycle(pi);
838
838
  const settings = readSubagentSettings();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@narumitw/pi-subagents",
3
- "version": "2.0.2",
3
+ "version": "2.0.4",
4
4
  "description": "Pi extension for delegating work to specialized isolated subagents.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -42,17 +42,17 @@
42
42
  "typebox": "*"
43
43
  },
44
44
  "devDependencies": {
45
- "@biomejs/biome": "2.5.8",
45
+ "@biomejs/biome": "2.5.9",
46
46
  "@earendil-works/pi-agent-core": "0.84.2",
47
47
  "@earendil-works/pi-ai": "0.84.2",
48
48
  "@earendil-works/pi-coding-agent": "0.84.2",
49
49
  "@earendil-works/pi-tui": "0.84.2",
50
- "esbuild": "0.28.1",
51
- "typebox": "1.3.14",
50
+ "esbuild": "0.28.2",
51
+ "typebox": "1.3.16",
52
52
  "typescript": "7.0.2"
53
53
  },
54
54
  "dependencies": {
55
- "@narumitw/pi-tui-kit": "^0.56.0",
55
+ "@narumitw/pi-tui-kit": "^0.56.1",
56
56
  "proper-lockfile": "^4.1.2"
57
57
  },
58
58
  "repository": {
@@ -37,8 +37,10 @@ import {
37
37
  } from "./rpc-transport-metadata.js";
38
38
  import {
39
39
  captureRpcEvent,
40
+ commitRpcInFlightUsage,
40
41
  createRpcTurnCapture,
41
42
  observeRpcBudgetEvent,
43
+ snapshotRpcUsage,
42
44
  } from "./rpc-turn-capture.js";
43
45
  import { terminateProcess } from "./runner.js";
44
46
  import { safeTerminalText } from "./safe-text.js";
@@ -501,11 +503,17 @@ export class RpcTransport implements SubagentTransport {
501
503
  });
502
504
  void settled.catch(() => undefined);
503
505
  const unsubscribeEvent = child.client.onEvent((event) => {
504
- captureRpcEvent(event, capture);
506
+ const usageChanged = captureRpcEvent(event, capture);
505
507
  observeRpcBudgetEvent(event, budgetMonitor);
506
508
  if (!capture.firstActivityAt && isActivityEvent(event)) {
507
509
  capture.firstActivityAt = Date.now();
508
- publish({ phase: "running", timing: { firstActivityAt: capture.firstActivityAt } });
510
+ publish({
511
+ phase: "running",
512
+ timing: { firstActivityAt: capture.firstActivityAt },
513
+ ...(usageChanged ? { usage: snapshotRpcUsage(capture) } : {}),
514
+ });
515
+ } else if (usageChanged) {
516
+ publish({ usage: snapshotRpcUsage(capture) });
509
517
  }
510
518
  const type = eventType(event);
511
519
  if (type === "auto_retry_start") publish({ phase: "retrying" });
@@ -572,7 +580,7 @@ export class RpcTransport implements SubagentTransport {
572
580
  phase: explicitAbort ? "interrupted" : "failed",
573
581
  failurePhase: "running",
574
582
  timing: { settledAt: Date.now() },
575
- usage: capture.usage,
583
+ usage: snapshotRpcUsage(capture),
576
584
  });
577
585
  const checkpointOutput = termination
578
586
  ? formatTimeoutCheckpoint(termination.checkpoint)
@@ -591,7 +599,11 @@ export class RpcTransport implements SubagentTransport {
591
599
  };
592
600
  }
593
601
 
594
- publish({ phase: "finalizing", failurePhase: "running" });
602
+ publish({
603
+ phase: "finalizing",
604
+ failurePhase: "running",
605
+ usage: snapshotRpcUsage(capture),
606
+ });
595
607
  const finalizationStartedAt = Date.now();
596
608
  const summary = await finalizeTimedOutRpcTurn({
597
609
  client: child.client,
@@ -605,6 +617,7 @@ export class RpcTransport implements SubagentTransport {
605
617
  finalizationTimeoutMs: this.options.timeoutFinalizationMs,
606
618
  abortGraceMs: this.options.abortGraceMs ?? ABORT_GRACE_MS,
607
619
  resetCapture() {
620
+ commitRpcInFlightUsage(capture);
608
621
  capture.output = "";
609
622
  capture.partial = "";
610
623
  capture.stopReason = undefined;
@@ -625,7 +638,7 @@ export class RpcTransport implements SubagentTransport {
625
638
  phase: "failed",
626
639
  failurePhase: "running",
627
640
  timing: { settledAt: Date.now() },
628
- usage: capture.usage,
641
+ usage: snapshotRpcUsage(capture),
629
642
  });
630
643
  const checkpointOutput = termination ? formatTimeoutCheckpoint(termination.checkpoint) : "";
631
644
  return {
@@ -651,7 +664,7 @@ export class RpcTransport implements SubagentTransport {
651
664
  timing: { settledAt: Date.now() },
652
665
  provider: capture.provider ?? telemetry.provider,
653
666
  model: capture.model ?? telemetry.model,
654
- usage: capture.usage,
667
+ usage: snapshotRpcUsage(capture),
655
668
  });
656
669
  return {
657
670
  output: output.text,
@@ -686,6 +699,7 @@ export class RpcTransport implements SubagentTransport {
686
699
  phase: signal.aborted ? "interrupted" : "failed",
687
700
  failurePhase: accepted ? "running" : "ready",
688
701
  timing: { settledAt: Date.now() },
702
+ usage: snapshotRpcUsage(capture),
689
703
  });
690
704
  return {
691
705
  output:
@@ -12,6 +12,7 @@ export interface RpcTurnCapture {
12
12
  provider?: string;
13
13
  model?: string;
14
14
  usage: TransportUsage;
15
+ inFlightUsage?: TransportUsage;
15
16
  firstActivityAt?: number;
16
17
  journal: TimeoutProgressJournal;
17
18
  }
@@ -25,8 +26,9 @@ export function createRpcTurnCapture(): RpcTurnCapture {
25
26
  };
26
27
  }
27
28
 
28
- export function captureRpcEvent(event: unknown, capture: RpcTurnCapture): void {
29
- if (!isRecord(event)) return;
29
+ export function captureRpcEvent(event: unknown, capture: RpcTurnCapture): boolean {
30
+ if (!isRecord(event)) return false;
31
+ const previousUsage = snapshotRpcUsage(capture);
30
32
  if (event.type === "tool_execution_start") {
31
33
  capture.journal.recordToolCall(
32
34
  typeof event.toolCallId === "string" ? event.toolCallId : "",
@@ -42,7 +44,12 @@ export function captureRpcEvent(event: unknown, capture: RpcTurnCapture): void {
42
44
  { content: result.content, isError: event.isError },
43
45
  );
44
46
  }
47
+ if (event.type === "message_start" && isRecord(event.message)) {
48
+ if (event.message.role === "assistant") commitRpcInFlightUsage(capture);
49
+ }
45
50
  if (event.type === "message_update") {
51
+ const usage = normalizeUsage(event.usage);
52
+ if (usage) capture.inFlightUsage = usage;
46
53
  const delta = event.assistantMessageEvent;
47
54
  if (isRecord(delta) && delta.type === "text_delta" && typeof delta.delta === "string") {
48
55
  capture.partial = truncateUtf8(
@@ -51,10 +58,28 @@ export function captureRpcEvent(event: unknown, capture: RpcTurnCapture): void {
51
58
  ).text;
52
59
  }
53
60
  }
54
- if (event.type !== "message_end" || !isRecord(event.message)) return;
55
- const candidate = event.message;
56
- if (candidate.role !== "toolResult") journalMessages(capture.journal, [candidate]);
57
- if (candidate.role !== "assistant") return;
61
+ if (event.type === "message_end" && isRecord(event.message)) {
62
+ const candidate = event.message;
63
+ if (candidate.role !== "toolResult") journalMessages(capture.journal, [candidate]);
64
+ if (candidate.role === "assistant") captureAssistantEnd(candidate, capture);
65
+ }
66
+ return !sameUsage(previousUsage, snapshotRpcUsage(capture));
67
+ }
68
+
69
+ export function snapshotRpcUsage(capture: RpcTurnCapture): TransportUsage {
70
+ const usage = { ...capture.usage };
71
+ if (capture.inFlightUsage) addUsage(usage, capture.inFlightUsage);
72
+ return usage;
73
+ }
74
+
75
+ export function commitRpcInFlightUsage(capture: RpcTurnCapture): boolean {
76
+ if (!capture.inFlightUsage) return false;
77
+ addUsage(capture.usage, capture.inFlightUsage);
78
+ capture.inFlightUsage = undefined;
79
+ return true;
80
+ }
81
+
82
+ function captureAssistantEnd(candidate: Record<string, unknown>, capture: RpcTurnCapture): void {
58
83
  capture.output = truncateUtf8(
59
84
  assistantText(candidate.content) || capture.partial,
60
85
  DEFAULT_MAX_OUTPUT_BYTES,
@@ -76,8 +101,11 @@ export function captureRpcEvent(event: unknown, capture: RpcTurnCapture): void {
76
101
  ? candidate.model
77
102
  : undefined;
78
103
  capture.model = responseModel ? boundedPrivateText(responseModel, 256) : undefined;
79
- capture.usage.turns++;
80
- addUsage(capture.usage, candidate.usage);
104
+ const finalUsage = normalizeUsage(candidate.usage);
105
+ const completedUsage = finalUsage ?? capture.inFlightUsage;
106
+ if (completedUsage) addUsage(capture.usage, completedUsage);
107
+ capture.inFlightUsage = undefined;
108
+ capture.usage.turns = addUsageValue(capture.usage.turns, 1);
81
109
  }
82
110
 
83
111
  export function observeRpcBudgetEvent(event: unknown, monitor: TurnBudgetMonitor): void {
@@ -101,29 +129,76 @@ function assistantToolCallCount(content: unknown): number {
101
129
  return content.filter((part) => isRecord(part) && part.type === "toolCall").length;
102
130
  }
103
131
 
104
- function addUsage(target: TransportUsage, value: unknown): void {
105
- if (!isRecord(value)) return;
106
- target.input += safeNonNegative(value.input);
107
- target.output += safeNonNegative(value.output);
108
- target.cacheRead += safeNonNegative(value.cacheRead);
109
- target.cacheWrite += safeNonNegative(value.cacheWrite);
132
+ function normalizeUsage(value: unknown): TransportUsage | undefined {
133
+ if (!isRecord(value)) return undefined;
134
+ const input = safeNonNegative(value.input);
135
+ const output = safeNonNegative(value.output);
136
+ const cacheRead = safeNonNegative(value.cacheRead);
137
+ const cacheWrite = safeNonNegative(value.cacheWrite);
110
138
  const reportedTotal = safeNonNegative(value.totalTokens);
111
- target.totalTokens +=
112
- reportedTotal ||
113
- safeNonNegative(value.input) +
114
- safeNonNegative(value.output) +
115
- safeNonNegative(value.cacheRead) +
116
- safeNonNegative(value.cacheWrite);
117
- if (isRecord(value.cost)) target.cost += safeNonNegative(value.cost.total);
139
+ const cost = isRecord(value.cost) ? safeNonNegative(value.cost.total) : undefined;
140
+ if (
141
+ input === undefined &&
142
+ output === undefined &&
143
+ cacheRead === undefined &&
144
+ cacheWrite === undefined &&
145
+ reportedTotal === undefined &&
146
+ cost === undefined
147
+ ) {
148
+ return undefined;
149
+ }
150
+ const components = [input, output, cacheRead, cacheWrite].reduce<number>(
151
+ (total, current) => addUsageValue(total, current ?? 0),
152
+ 0,
153
+ );
154
+ return {
155
+ input: input ?? 0,
156
+ output: output ?? 0,
157
+ cacheRead: cacheRead ?? 0,
158
+ cacheWrite: cacheWrite ?? 0,
159
+ totalTokens: reportedTotal ?? components,
160
+ cost: cost ?? 0,
161
+ turns: 0,
162
+ };
163
+ }
164
+
165
+ function addUsage(target: TransportUsage, value: TransportUsage): void {
166
+ for (const key of [
167
+ "input",
168
+ "output",
169
+ "cacheRead",
170
+ "cacheWrite",
171
+ "totalTokens",
172
+ "cost",
173
+ "turns",
174
+ ] as const) {
175
+ target[key] = addUsageValue(target[key], value[key]);
176
+ }
118
177
  }
119
178
 
120
- function safeNonNegative(value: unknown): number {
179
+ function addUsageValue(current: number, addition: number): number {
180
+ return Math.min(Number.MAX_SAFE_INTEGER, current + addition);
181
+ }
182
+
183
+ function safeNonNegative(value: unknown): number | undefined {
121
184
  return typeof value === "number" &&
122
185
  Number.isFinite(value) &&
123
186
  value >= 0 &&
124
187
  value <= Number.MAX_SAFE_INTEGER
125
188
  ? value
126
- : 0;
189
+ : undefined;
190
+ }
191
+
192
+ function sameUsage(left: TransportUsage, right: TransportUsage): boolean {
193
+ return (
194
+ left.input === right.input &&
195
+ left.output === right.output &&
196
+ left.cacheRead === right.cacheRead &&
197
+ left.cacheWrite === right.cacheWrite &&
198
+ left.totalTokens === right.totalTokens &&
199
+ left.cost === right.cost &&
200
+ left.turns === right.turns
201
+ );
127
202
  }
128
203
 
129
204
  function assistantText(value: unknown): string {