@sagentlab/navarch-runtime 0.1.30 → 0.1.32

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.
@@ -93,7 +93,13 @@ function attachUsage(result) {
93
93
  if (!parsed)
94
94
  return result;
95
95
  const usage = (0, exit_conditions_cjs_1.extractUsageFromClaudeJson)(parsed);
96
- return { ...result, tokensIn: usage.tokensIn, tokensOut: usage.tokensOut, costUsd: usage.costUsd };
96
+ return {
97
+ ...result,
98
+ tokensIn: usage.tokensIn,
99
+ tokensOut: usage.tokensOut,
100
+ cacheHitTokensIn: usage.cacheHitTokensIn,
101
+ costUsd: usage.costUsd,
102
+ };
97
103
  }
98
104
  async function runOnHost(options, args) {
99
105
  return new Promise((resolve) => {
@@ -156,6 +156,7 @@ function attachUsage(result, model) {
156
156
  // measured zero — leave the fields unset so aggregation skips them.
157
157
  ...(usage.tokensIn !== undefined ? { tokensIn: usage.tokensIn } : {}),
158
158
  ...(usage.tokensOut !== undefined ? { tokensOut: usage.tokensOut } : {}),
159
+ ...(usage.cacheHitTokensIn !== undefined ? { cacheHitTokensIn: usage.cacheHitTokensIn } : {}),
159
160
  ...(costUsd !== undefined ? { costUsd } : {}),
160
161
  ...(reportText !== undefined ? { reportText } : {}),
161
162
  };
@@ -123,6 +123,7 @@ function attachOpenCodeOutput(result) {
123
123
  return result;
124
124
  let tokensIn;
125
125
  let tokensOut;
126
+ let cacheHitTokensIn;
126
127
  let costUsd;
127
128
  let finalMessageId;
128
129
  const textByMessage = new Map();
@@ -144,6 +145,9 @@ function attachOpenCodeOutput(result) {
144
145
  const cost = nonNegativeMetric(part.cost);
145
146
  if (input !== undefined || cacheRead !== undefined || cacheWrite !== undefined) {
146
147
  tokensIn = (tokensIn ?? 0) + (input ?? 0) + (cacheRead ?? 0) + (cacheWrite ?? 0);
148
+ // Cache reads are the "hit" component of tokensIn; cache writes count
149
+ // as misses (AdapterResult.cacheHitTokensIn semantics).
150
+ cacheHitTokensIn = (cacheHitTokensIn ?? 0) + (cacheRead ?? 0);
147
151
  }
148
152
  if (output !== undefined)
149
153
  tokensOut = (tokensOut ?? 0) + output;
@@ -157,6 +161,7 @@ function attachOpenCodeOutput(result) {
157
161
  ...result,
158
162
  ...(tokensIn !== undefined ? { tokensIn } : {}),
159
163
  ...(tokensOut !== undefined ? { tokensOut } : {}),
164
+ ...(cacheHitTokensIn !== undefined ? { cacheHitTokensIn } : {}),
160
165
  ...(costUsd !== undefined ? { costUsd } : {}),
161
166
  ...(reportText ? { reportText } : {}),
162
167
  };
@@ -52,6 +52,8 @@ function parseClaudeJsonResult(stdout) {
52
52
  * no cache breakdown, so the components are folded together here rather
53
53
  * than dropped). Cost prefers `total_cost_usd` (the field name used in
54
54
  * multi-turn/agentic CLI output) and falls back to `cost_usd`.
55
+ * `cacheHitTokensIn` is the cache-read component of that sum -- cache writes
56
+ * count as misses, matching sessions.cache_miss_input_tokens semantics.
55
57
  */
56
58
  function extractUsageFromClaudeJson(parsed) {
57
59
  const usage = parsed.usage ?? {};
@@ -60,7 +62,7 @@ function extractUsageFromClaudeJson(parsed) {
60
62
  (usage.cache_read_input_tokens ?? 0);
61
63
  const tokensOut = usage.output_tokens ?? 0;
62
64
  const costUsd = parsed.total_cost_usd ?? parsed.cost_usd ?? 0;
63
- return { tokensIn, tokensOut, costUsd };
65
+ return { tokensIn, tokensOut, cacheHitTokensIn: usage.cache_read_input_tokens ?? 0, costUsd };
64
66
  }
65
67
  /**
66
68
  * Best-effort line-by-line parse of `codex exec --json` stdout into the
@@ -105,15 +107,21 @@ function parseCodexJsonEvents(stdout) {
105
107
  function extractUsageFromCodexEvents(events) {
106
108
  let turnTokensIn = 0;
107
109
  let turnTokensOut = 0;
110
+ let turnCacheHit = 0;
108
111
  let sawTurnUsage = false;
109
112
  let legacyTokensIn;
110
113
  let legacyTokensOut;
114
+ let legacyCacheHit;
111
115
  let costUsd;
112
116
  for (const event of events) {
113
117
  if (event.type === "turn.completed" && event.usage) {
114
118
  sawTurnUsage = true;
115
119
  turnTokensIn += event.usage.input_tokens ?? 0;
116
120
  turnTokensOut += event.usage.output_tokens ?? 0;
121
+ // cached_input_tokens is a component of input_tokens in the verified
122
+ // stream (see codex-pricing.cts priceUsage) -- same inclusive
123
+ // semantics as AdapterResult.cacheHitTokensIn.
124
+ turnCacheHit += event.usage.cached_input_tokens ?? 0;
117
125
  if (typeof event.usage.total_cost_usd === "number") {
118
126
  costUsd = (costUsd ?? 0) + event.usage.total_cost_usd;
119
127
  }
@@ -124,6 +132,9 @@ function extractUsageFromCodexEvents(events) {
124
132
  ? (nested.input_tokens ?? 0)
125
133
  : (event.msg.input_tokens ?? 0) + (event.msg.cached_input_tokens ?? 0);
126
134
  legacyTokensOut = nested?.output_tokens ?? event.msg.output_tokens ?? 0;
135
+ legacyCacheHit = nested
136
+ ? (nested.cached_input_tokens ?? 0)
137
+ : (event.msg.cached_input_tokens ?? 0);
127
138
  if (typeof nested?.total_cost_usd === "number")
128
139
  costUsd = nested.total_cost_usd;
129
140
  }
@@ -132,6 +143,7 @@ function extractUsageFromCodexEvents(events) {
132
143
  if (total) {
133
144
  legacyTokensIn = total.input_tokens ?? 0;
134
145
  legacyTokensOut = total.output_tokens ?? 0;
146
+ legacyCacheHit = total.cached_input_tokens ?? 0;
135
147
  if (typeof total.total_cost_usd === "number")
136
148
  costUsd = total.total_cost_usd;
137
149
  }
@@ -140,9 +152,24 @@ function extractUsageFromCodexEvents(events) {
140
152
  costUsd = event.msg.total_cost_usd;
141
153
  }
142
154
  }
143
- if (sawTurnUsage)
144
- return { tokensIn: turnTokensIn, tokensOut: turnTokensOut, costUsd };
145
- return { tokensIn: legacyTokensIn, tokensOut: legacyTokensOut, costUsd };
155
+ if (sawTurnUsage) {
156
+ return {
157
+ tokensIn: turnTokensIn,
158
+ tokensOut: turnTokensOut,
159
+ // Clamp like codex-pricing.cts: a malformed event must not report more
160
+ // cache reads than input tokens.
161
+ cacheHitTokensIn: Math.min(turnCacheHit, turnTokensIn),
162
+ costUsd,
163
+ };
164
+ }
165
+ return {
166
+ tokensIn: legacyTokensIn,
167
+ tokensOut: legacyTokensOut,
168
+ ...(legacyTokensIn !== undefined && legacyCacheHit !== undefined
169
+ ? { cacheHitTokensIn: Math.min(legacyCacheHit, legacyTokensIn) }
170
+ : {}),
171
+ costUsd,
172
+ };
146
173
  }
147
174
  /** The last completed agent message, with legacy `msg.agent_message` fallback. */
148
175
  function extractFinalMessageFromCodexEvents(events) {
package/dist/prompt.cjs CHANGED
@@ -36,7 +36,8 @@ function renderPrompt(task, bundle) {
36
36
  if (bundle.depends_on_reports.length > 0) {
37
37
  sections.push("## Prior task reports (depends_on)");
38
38
  for (const report of bundle.depends_on_reports) {
39
- sections.push(`- ${report.task_id}: ${report.report_summary ?? report.summary}`);
39
+ const source = report.github_issue_url ? ` (${report.github_issue_url})` : "";
40
+ sections.push(`- ${report.task_id}${source}: ${report.report_summary ?? report.summary}`);
40
41
  }
41
42
  }
42
43
  sections.push("## Playbook");
package/dist/session.cjs CHANGED
@@ -400,6 +400,7 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
400
400
  ...turnResult,
401
401
  tokensIn: sumReportedUsage(attempts, "tokensIn"),
402
402
  tokensOut: sumReportedUsage(attempts, "tokensOut"),
403
+ cacheHitTokensIn: sumReportedUsage(attempts, "cacheHitTokensIn"),
403
404
  costUsd: sumReportedUsage(attempts, "costUsd"),
404
405
  };
405
406
  const mapping = (0, exit_conditions_cjs_1.mapExitCondition)({
@@ -468,6 +469,7 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
468
469
  ...(result.tokensIn !== undefined ? { tokens_in: result.tokensIn } : {}),
469
470
  ...(result.tokensOut !== undefined ? { tokens_out: result.tokensOut } : {}),
470
471
  ...(result.costUsd !== undefined ? { cost_usd: result.costUsd } : {}),
472
+ ...cacheSplitFields(result.tokensIn, result.cacheHitTokensIn),
471
473
  },
472
474
  transcript_url: transcriptUrl,
473
475
  exit_status: mapping.exitStatus,
@@ -593,6 +595,21 @@ function adapterCommand(config, runtime) {
593
595
  return { bin: config.opencodeBin, extraArgs: config.opencodeExtraArgs };
594
596
  }
595
597
  }
598
+ /**
599
+ * The wire-shape prompt-cache split derived from adapter usage, or {} when no
600
+ * attempt reported a cache breakdown. The control plane persists the split
601
+ * only when hit + miss equals tokens_in (dispatch-service.ts
602
+ * byoCacheSplitOrNull), so the miss half is derived from the same summed
603
+ * total rather than reported independently. An attempt that reported tokens
604
+ * without a breakdown (plain-text fallback) inflates the miss half -- those
605
+ * tokens are "uncached or unknown", never fabricated hits.
606
+ */
607
+ function cacheSplitFields(tokensIn, cacheHitTokensIn) {
608
+ if (tokensIn === undefined || cacheHitTokensIn === undefined)
609
+ return {};
610
+ const hit = Math.min(cacheHitTokensIn, tokensIn);
611
+ return { cache_hit_input_tokens: hit, cache_miss_input_tokens: tokensIn - hit };
612
+ }
596
613
  function sumReportedUsage(attempts, key) {
597
614
  const reported = attempts.flatMap((attempt) => {
598
615
  const value = attempt[key];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sagentlab/navarch-runtime",
3
- "version": "0.1.30",
3
+ "version": "0.1.32",
4
4
  "description": "Navarch machine-side session manager: claims delivery tasks and runs them through Claude Code, Codex, Gemini, or OpenCode.",
5
5
  "type": "commonjs",
6
6
  "license": "MIT",