sidekick-shared 0.28.3 → 0.29.1

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 (151) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/README.md +188 -27
  3. package/dist/aggregation/EventAggregator.d.ts +19 -0
  4. package/dist/aggregation/EventAggregator.js +174 -35
  5. package/dist/aggregation/snapshot.d.ts +37 -8
  6. package/dist/aggregation/snapshot.js +31 -9
  7. package/dist/aggregation/types.d.ts +13 -0
  8. package/dist/authStatus.d.ts +54 -0
  9. package/dist/authStatus.js +91 -0
  10. package/dist/browser.d.ts +16 -6
  11. package/dist/browser.js +24 -2
  12. package/dist/codex.d.ts +23 -0
  13. package/dist/codex.js +44 -0
  14. package/dist/codexProfiles.d.ts +1 -0
  15. package/dist/codexProfiles.js +8 -11
  16. package/dist/context/sessionContext.js +4 -0
  17. package/dist/esm/aggregation/EventAggregator.js +174 -35
  18. package/dist/esm/aggregation/snapshot.js +31 -9
  19. package/dist/esm/authStatus.js +87 -0
  20. package/dist/esm/browser.js +5 -2
  21. package/dist/esm/codex.js +18 -0
  22. package/dist/esm/codexProfiles.js +8 -11
  23. package/dist/esm/context/sessionContext.js +4 -0
  24. package/dist/esm/formatters/toolSummary.js +4 -1
  25. package/dist/esm/formatting.js +10 -1
  26. package/dist/esm/index.js +11 -6
  27. package/dist/esm/modelCatalog.js +7 -5
  28. package/dist/esm/modelContext.js +4 -3
  29. package/dist/esm/modelInfo.js +425 -47
  30. package/dist/esm/node.js +9 -3
  31. package/dist/esm/observedContextWindows.js +2 -1
  32. package/dist/esm/ownKey.js +26 -0
  33. package/dist/esm/parsers/codexParser.js +351 -54
  34. package/dist/esm/parsers/codexRolloutOrigin.js +107 -0
  35. package/dist/esm/parsers/openCodeParser.js +85 -22
  36. package/dist/esm/parsers/subagentScanner.js +33 -12
  37. package/dist/esm/parsers/subagentTraceParser.js +97 -37
  38. package/dist/esm/pricing.js +18 -0
  39. package/dist/esm/pricingCatalog.js +17 -9
  40. package/dist/esm/promptHistory.js +66 -4
  41. package/dist/esm/providerFailure.js +124 -3
  42. package/dist/esm/providers/claudeCode.js +10 -2
  43. package/dist/esm/providers/codex.js +84 -62
  44. package/dist/esm/providers/codexDatabase.js +3 -19
  45. package/dist/esm/providers/codexRollouts.js +24 -1
  46. package/dist/esm/providers/openCode.js +9 -17
  47. package/dist/esm/report/htmlReportGenerator.js +42 -5
  48. package/dist/esm/report/transcriptParser.js +13 -11
  49. package/dist/esm/schemas/providerFailure.js +3 -1
  50. package/dist/esm/schemas/sessionEvent.js +35 -14
  51. package/dist/esm/schemas/transcript.js +4 -0
  52. package/dist/esm/sessionPromptHistory.js +2 -2
  53. package/dist/esm/statusline/payload.js +9 -0
  54. package/dist/esm/subagentSpawn.js +163 -0
  55. package/dist/esm/testing/credentialGuard.js +87 -5
  56. package/dist/esm/toolCallOrigins.js +310 -0
  57. package/dist/esm/transcript.js +46 -3
  58. package/dist/esm/turns/assistantTurn.js +3 -2
  59. package/dist/esm/usage/claudeUsageDedupe.js +68 -0
  60. package/dist/esm/usage/usageEvents.js +146 -17
  61. package/dist/esm/usage/usageReports.js +39 -6
  62. package/dist/esm/watchers/eventBridge.js +15 -0
  63. package/dist/esm/watchers/factory.js +5 -3
  64. package/dist/esm/watchers/jsonlTail.js +442 -58
  65. package/dist/esm/watchers/jsonlWatcher.js +51 -4
  66. package/dist/esm/watchers/providerReaderWatcher.js +13 -0
  67. package/dist/esm/zaiQuota.js +2 -1
  68. package/dist/formatters/toolSummary.js +4 -1
  69. package/dist/formatting.d.ts +26 -1
  70. package/dist/formatting.js +11 -1
  71. package/dist/index.d.ts +22 -11
  72. package/dist/index.js +44 -12
  73. package/dist/modelCatalog.js +7 -5
  74. package/dist/modelContext.js +4 -3
  75. package/dist/modelInfo.d.ts +185 -16
  76. package/dist/modelInfo.js +434 -49
  77. package/dist/node.d.ts +16 -5
  78. package/dist/node.js +33 -2
  79. package/dist/observedContextWindows.js +2 -1
  80. package/dist/ownKey.d.ts +16 -0
  81. package/dist/ownKey.js +30 -0
  82. package/dist/parsers/codexParser.d.ts +118 -0
  83. package/dist/parsers/codexParser.js +352 -54
  84. package/dist/parsers/codexRolloutOrigin.d.ts +49 -0
  85. package/dist/parsers/codexRolloutOrigin.js +111 -0
  86. package/dist/parsers/openCodeParser.d.ts +38 -0
  87. package/dist/parsers/openCodeParser.js +88 -22
  88. package/dist/parsers/subagentScanner.d.ts +19 -6
  89. package/dist/parsers/subagentScanner.js +33 -12
  90. package/dist/parsers/subagentTraceParser.d.ts +25 -5
  91. package/dist/parsers/subagentTraceParser.js +97 -37
  92. package/dist/pricing.d.ts +22 -0
  93. package/dist/pricing.js +50 -0
  94. package/dist/pricingCatalog.d.ts +10 -0
  95. package/dist/pricingCatalog.js +16 -8
  96. package/dist/promptHistory.d.ts +65 -4
  97. package/dist/promptHistory.js +68 -5
  98. package/dist/providerFailure.d.ts +44 -0
  99. package/dist/providerFailure.js +126 -4
  100. package/dist/providers/claudeCode.js +9 -1
  101. package/dist/providers/codex.d.ts +3 -22
  102. package/dist/providers/codex.js +85 -64
  103. package/dist/providers/codexDatabase.d.ts +3 -2
  104. package/dist/providers/codexDatabase.js +4 -22
  105. package/dist/providers/codexRollouts.d.ts +22 -0
  106. package/dist/providers/codexRollouts.js +24 -0
  107. package/dist/providers/openCode.js +8 -16
  108. package/dist/providers/types.d.ts +14 -0
  109. package/dist/report/htmlReportGenerator.js +42 -5
  110. package/dist/report/transcriptParser.d.ts +10 -1
  111. package/dist/report/transcriptParser.js +13 -11
  112. package/dist/schemas/index.d.ts +1 -0
  113. package/dist/schemas/providerFailure.d.ts +39 -0
  114. package/dist/schemas/providerFailure.js +2 -0
  115. package/dist/schemas/sessionEvent.d.ts +27 -10
  116. package/dist/schemas/sessionEvent.js +35 -14
  117. package/dist/schemas/transcript.d.ts +2 -0
  118. package/dist/schemas/transcript.js +4 -0
  119. package/dist/sessionPromptHistory.d.ts +7 -1
  120. package/dist/sessionPromptHistory.js +1 -1
  121. package/dist/statusline/payload.d.ts +10 -0
  122. package/dist/statusline/payload.js +14 -0
  123. package/dist/subagentSpawn.d.ts +80 -0
  124. package/dist/subagentSpawn.js +174 -0
  125. package/dist/testing/credentialGuard.d.ts +14 -0
  126. package/dist/testing/credentialGuard.js +87 -5
  127. package/dist/toolCallOrigins.d.ts +70 -0
  128. package/dist/toolCallOrigins.js +346 -0
  129. package/dist/transcript.d.ts +12 -0
  130. package/dist/transcript.js +46 -3
  131. package/dist/turns/assistantTurn.d.ts +1 -0
  132. package/dist/turns/assistantTurn.js +3 -2
  133. package/dist/types/opencode.d.ts +2 -0
  134. package/dist/usage/claudeUsageDedupe.d.ts +31 -0
  135. package/dist/usage/claudeUsageDedupe.js +68 -0
  136. package/dist/usage/usageEvents.d.ts +54 -6
  137. package/dist/usage/usageEvents.js +146 -17
  138. package/dist/usage/usageReports.d.ts +35 -4
  139. package/dist/usage/usageReports.js +39 -6
  140. package/dist/watchers/eventBridge.js +15 -0
  141. package/dist/watchers/factory.d.ts +6 -0
  142. package/dist/watchers/factory.js +5 -3
  143. package/dist/watchers/jsonlTail.d.ts +124 -4
  144. package/dist/watchers/jsonlTail.js +446 -59
  145. package/dist/watchers/jsonlWatcher.d.ts +18 -1
  146. package/dist/watchers/jsonlWatcher.js +50 -3
  147. package/dist/watchers/providerReaderWatcher.d.ts +7 -0
  148. package/dist/watchers/providerReaderWatcher.js +13 -0
  149. package/dist/watchers/types.d.ts +12 -0
  150. package/dist/zaiQuota.js +2 -1
  151. package/package.json +41 -1
@@ -6,10 +6,13 @@
6
6
  * (not just stats) for drill-down trace visualization. Applies noise classification
7
7
  * and tool summary formatting from the formatters module.
8
8
  *
9
- * Three-phase parent-child linking:
10
- * 1. Result-based: match toolUseResult.agentId → parent tool_use_id
11
- * 2. Team summary: parse <teammate-message> XML blocks
12
- * 3. Positional fallback: timestamp ordering + proximity
9
+ * Parent-child linking, exact links first:
10
+ * 1. Meta `parentAgentId` (Claude Code writes it for nested spawns)
11
+ * 2. The child's spawning tool_use id (meta `toolUseId`) found in another
12
+ * trace's `Agent` / `Task` calls
13
+ * Only traces without that spawn metadata (older Claude Code) fall back to:
14
+ * 3. Team summary: parse <teammate-message> XML blocks
15
+ * 4. Positional fallback: timestamp ordering + proximity
13
16
  *
14
17
  * @module parsers/subagentTraceParser
15
18
  */
@@ -55,6 +58,8 @@ const noiseClassifier_1 = require("../formatters/noiseClassifier");
55
58
  const claudeUsageDedupe_1 = require("../usage/claudeUsageDedupe");
56
59
  const subagentUsage_1 = require("../usage/subagentUsage");
57
60
  const subagentScanner_1 = require("./subagentScanner");
61
+ const subagentSpawn_1 = require("../subagentSpawn");
62
+ const ownKey_1 = require("../ownKey");
58
63
  // ── Constants ──
59
64
  const AGENT_FILE_PATTERN = /^agent-(.+)\.jsonl$/;
60
65
  // ── Public API ──
@@ -86,10 +91,8 @@ function scanSubagentTraces(sessionDir, sessionId) {
86
91
  if (trace)
87
92
  traces.push(trace);
88
93
  }
89
- // Link parent-child relationships
90
- linkTraces(traces);
91
- // Return only top-level traces (those without parents)
92
- return traces.filter((t) => !t.parentToolUseId);
94
+ // Link parent-child relationships; returns the traces with no parent agent.
95
+ return linkTraces(traces);
93
96
  }
94
97
  catch {
95
98
  return [];
@@ -169,8 +172,8 @@ function parseAgentTrace(filePath, agentId) {
169
172
  input: block.input || {},
170
173
  timestamp,
171
174
  });
172
- // Extract agent info from Task tool
173
- if (block.name === 'Task' && block.input) {
175
+ // Extract agent info from an Agent/Task spawn
176
+ if ((0, subagentSpawn_1.isSubagentSpawnTool)(block.name) && block.input) {
174
177
  if (block.input.subagent_type && !agentType) {
175
178
  agentType = String(block.input.subagent_type);
176
179
  }
@@ -207,6 +210,14 @@ function parseAgentTrace(filePath, agentId) {
207
210
  agentId,
208
211
  agentType,
209
212
  description,
213
+ filePath,
214
+ ...(meta.toolUseId
215
+ ? { spawnToolUseId: meta.toolUseId, parentToolUseId: meta.toolUseId }
216
+ : {}),
217
+ ...(meta.parentAgentId ? { parentAgentId: meta.parentAgentId } : {}),
218
+ ...(meta.requestShape ? { background: meta.requestShape === 'background' } : {}),
219
+ ...(meta.finished !== undefined ? { finished: meta.finished } : {}),
220
+ ...(meta.stoppedByUser !== undefined ? { stoppedByUser: meta.stoppedByUser } : {}),
210
221
  events,
211
222
  children: [],
212
223
  stats: {
@@ -231,23 +242,61 @@ function parseAgentTrace(filePath, agentId) {
231
242
  }
232
243
  }
233
244
  // ── Parent-Child Linking ──
245
+ /**
246
+ * Nests child traces under their parents and returns the traces with no
247
+ * parent agent. Exact metadata links come first; the heuristics only place
248
+ * traces Claude Code recorded no spawn metadata for.
249
+ */
234
250
  function linkTraces(traces) {
235
251
  const traceById = new Map();
236
252
  for (const trace of traces) {
237
253
  traceById.set(trace.agentId, trace);
238
254
  }
239
- // Phase 1: Result-based linking
240
- // Look for tool_result events that contain agentId references
255
+ const parentOf = new Map();
256
+ const attach = (parent, child) => {
257
+ if (parent === child || parentOf.has(child))
258
+ return false;
259
+ // Never nest an agent under its own descendant (malformed metadata).
260
+ for (let up = parent; up; up = parentOf.get(up)) {
261
+ if (up === child)
262
+ return false;
263
+ }
264
+ parentOf.set(child, parent);
265
+ parent.children.push(child);
266
+ return true;
267
+ };
268
+ // Phase 1: the meta file names the parent agent
269
+ for (const child of traces) {
270
+ const parent = child.parentAgentId ? traceById.get(child.parentAgentId) : undefined;
271
+ if (parent)
272
+ attach(parent, child);
273
+ }
274
+ // Phase 2: the child's spawning call is in another trace
275
+ const spawnOwner = new Map();
241
276
  for (const trace of traces) {
242
- for (const traceEvent of trace.events) {
243
- const evt = traceEvent.event;
244
- if (evt.type === 'tool_use' && evt.tool?.name === 'Task') {
245
- // The tool_use_id from this call should appear as a result in the parent
246
- // but we can link via timestamp proximity to child traces
247
- }
277
+ for (const id of spawnToolUseIdsOf(trace)) {
278
+ if (!spawnOwner.has(id))
279
+ spawnOwner.set(id, trace);
248
280
  }
249
281
  }
250
- // Phase 2: Team summary linking
282
+ for (const child of traces) {
283
+ if (parentOf.has(child) || !child.spawnToolUseId)
284
+ continue;
285
+ const parent = spawnOwner.get(child.spawnToolUseId);
286
+ if (parent && attach(parent, child))
287
+ child.parentAgentId ??= parent.agentId;
288
+ }
289
+ // Heuristic links set parentAgentId and, as before, parentToolUseId to the
290
+ // parent's agent id.
291
+ const unresolved = (trace) => !parentOf.has(trace) && !trace.spawnToolUseId && !trace.parentAgentId;
292
+ const linkHeuristically = (parent, child) => {
293
+ if (!attach(parent, child))
294
+ return false;
295
+ child.parentAgentId = parent.agentId;
296
+ child.parentToolUseId = parent.agentId;
297
+ return true;
298
+ };
299
+ // Phase 3: Team summary linking
251
300
  for (const trace of traces) {
252
301
  for (const traceEvent of trace.events) {
253
302
  const content = traceEvent.event.message?.content;
@@ -255,45 +304,56 @@ function linkTraces(traces) {
255
304
  // Parse teammate agent IDs from XML blocks
256
305
  const matches = content.matchAll(/agent[_-]id['":\s]+([a-f0-9]+)/gi);
257
306
  for (const match of matches) {
258
- const childId = match[1];
259
- const child = traceById.get(childId);
260
- if (child && child !== trace && !child.parentToolUseId) {
261
- child.parentToolUseId = trace.agentId;
262
- trace.children.push(child);
263
- }
307
+ const child = traceById.get(match[1]);
308
+ if (child && unresolved(child))
309
+ linkHeuristically(trace, child);
264
310
  }
265
311
  }
266
312
  }
267
313
  }
268
- // Phase 3: Positional fallback — timestamp proximity
314
+ // Phase 4: Positional fallback — timestamp proximity
269
315
  // Traces without parents that fall within another trace's time window
270
- const parentless = traces.filter((t) => !t.parentToolUseId);
271
- for (const child of parentless) {
316
+ for (const child of traces.filter(unresolved)) {
272
317
  if (!child.stats.startTime)
273
318
  continue;
274
319
  const childStart = child.stats.startTime.getTime();
275
- // Find a trace that was active (Task tool call) right before this child started
320
+ // Find a top-level trace that spawned an agent right before this child started
276
321
  for (const potential of traces) {
277
- if (potential === child)
322
+ if (potential === child || parentOf.has(potential))
278
323
  continue;
279
- if (potential.parentToolUseId)
280
- continue; // Already linked
281
324
  for (const traceEvent of potential.events) {
282
325
  const evt = traceEvent.event;
283
- if (evt.type === 'assistant' && evt.tool?.name === 'Task') {
326
+ if (evt.type === 'assistant' && (0, subagentSpawn_1.isSubagentSpawnTool)(evt.tool?.name)) {
284
327
  const taskTime = new Date(evt.timestamp).getTime();
285
328
  // Within 2 seconds before child start
286
329
  if (taskTime <= childStart && childStart - taskTime < 2000) {
287
- child.parentToolUseId = potential.agentId;
288
- potential.children.push(child);
330
+ linkHeuristically(potential, child);
289
331
  break;
290
332
  }
291
333
  }
292
334
  }
293
- if (child.parentToolUseId)
335
+ if (parentOf.has(child))
294
336
  break;
295
337
  }
296
338
  }
339
+ return traces.filter((trace) => !parentOf.has(trace));
340
+ }
341
+ /** Ids of the `Agent` / `Task` calls a trace made. */
342
+ function spawnToolUseIdsOf(trace) {
343
+ const ids = [];
344
+ for (const traceEvent of trace.events) {
345
+ const content = traceEvent.event.message?.content;
346
+ if (!Array.isArray(content))
347
+ continue;
348
+ for (const block of content) {
349
+ if (block?.type === 'tool_use' &&
350
+ typeof block.id === 'string' &&
351
+ (0, subagentSpawn_1.isSubagentSpawnTool)(block.name)) {
352
+ ids.push(block.id);
353
+ }
354
+ }
355
+ }
356
+ return ids;
297
357
  }
298
358
  // ── Helpers ──
299
359
  function rawToSessionEvent(raw) {
@@ -308,7 +368,7 @@ function rawToSessionEvent(raw) {
308
368
  tool_result: 'tool_result',
309
369
  summary: 'summary',
310
370
  };
311
- const mappedType = typeMap[type];
371
+ const mappedType = (0, ownKey_1.ownValue)(typeMap, type);
312
372
  if (!mappedType)
313
373
  return null;
314
374
  const message = raw.message;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * `sidekick-shared/pricing` — model pricing, model-id identity, usage
3
+ * normalization and cost, cost provenance, cost/token formatting, and text
4
+ * token estimates, as one small pure entry (no filesystem, network, or Zod).
5
+ *
6
+ * For hosts that price usage or format costs without session reading: it
7
+ * loads a handful of modules instead of the full browser surface. LiteLLM
8
+ * catalog hydration stays Node-only in `sidekick-shared/node`
9
+ * (`hydratePricingCatalog`); once hydrated, lookups here see the catalog.
10
+ * Everything here is also exported from the package root and
11
+ * `sidekick-shared/browser`.
12
+ *
13
+ * @module pricing
14
+ */
15
+ export { MODEL_PROVIDER_IDS, canonicalModelId, isMovingModelAlias, parseModelId, compareModelIds, sortModelIds, shortModelName, getModelDisplayInfo, getModelInfo, getModelPricing, resolveModelPricing, registerModelPricing, clearRegisteredModelPricing, getPricingCatalogInfo, calculateCost, calculateCostWithPricing, calculateCostWithProvenance, mergeCostSources, costSourceFromPricingProvenance, mergeCostComponents, formatCost, } from './modelInfo';
16
+ export type { ModelPricing, ModelPricingSource, ModelPricingMatch, ModelPricingProvenance, ResolvedModelPricing, ResolveModelPricingOptions, RegisterModelPricingOptions, PricingCatalogSource, PricingCatalogInfo, CostComponent, CostTokenUsage, ModelProvider, ParsedModelId, ModelInfo, CostSource, CostProvenanceInput, CostWithProvenance, ModelDisplayInfo, FormatCostOptions, } from './modelInfo';
17
+ export { normalizeProviderUsage, extractNormalizedUsage, calculateNormalizedUsageCost, } from './usageNormalization';
18
+ export type { UsageProvider, UsageSemantics, UsageNormalizationProvenance, AnthropicUsageInput, OpenAIUsageInput, SidekickUsageInput, ProviderUsageInput, NormalizedUsage, PricingProvenance, NormalizedUsageCostInput, NormalizedUsageCost, } from './usageNormalization';
19
+ export { formatTokenCount, formatDurationMs } from './formatting';
20
+ export type { FormatTokenCountOptions, FormatDurationMsOptions } from './formatting';
21
+ export { ASCII_CHARS_PER_TOKEN, estimateTextTokens, estimateSerializedTokens, } from './tokenEstimation';
22
+ export type { TokenEstimationMethod, TokenEstimationConfidence, ExactTokenCounterContext, ExactTokenCounter, TokenEstimationOptions, TokenEstimate, } from './tokenEstimation';
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ /**
3
+ * `sidekick-shared/pricing` — model pricing, model-id identity, usage
4
+ * normalization and cost, cost provenance, cost/token formatting, and text
5
+ * token estimates, as one small pure entry (no filesystem, network, or Zod).
6
+ *
7
+ * For hosts that price usage or format costs without session reading: it
8
+ * loads a handful of modules instead of the full browser surface. LiteLLM
9
+ * catalog hydration stays Node-only in `sidekick-shared/node`
10
+ * (`hydratePricingCatalog`); once hydrated, lookups here see the catalog.
11
+ * Everything here is also exported from the package root and
12
+ * `sidekick-shared/browser`.
13
+ *
14
+ * @module pricing
15
+ */
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.estimateSerializedTokens = exports.estimateTextTokens = exports.ASCII_CHARS_PER_TOKEN = exports.formatDurationMs = exports.formatTokenCount = exports.calculateNormalizedUsageCost = exports.extractNormalizedUsage = exports.normalizeProviderUsage = exports.formatCost = exports.mergeCostComponents = exports.costSourceFromPricingProvenance = exports.mergeCostSources = exports.calculateCostWithProvenance = exports.calculateCostWithPricing = exports.calculateCost = exports.getPricingCatalogInfo = exports.clearRegisteredModelPricing = exports.registerModelPricing = exports.resolveModelPricing = exports.getModelPricing = exports.getModelInfo = exports.getModelDisplayInfo = exports.shortModelName = exports.sortModelIds = exports.compareModelIds = exports.parseModelId = exports.isMovingModelAlias = exports.canonicalModelId = exports.MODEL_PROVIDER_IDS = void 0;
18
+ var modelInfo_1 = require("./modelInfo");
19
+ Object.defineProperty(exports, "MODEL_PROVIDER_IDS", { enumerable: true, get: function () { return modelInfo_1.MODEL_PROVIDER_IDS; } });
20
+ Object.defineProperty(exports, "canonicalModelId", { enumerable: true, get: function () { return modelInfo_1.canonicalModelId; } });
21
+ Object.defineProperty(exports, "isMovingModelAlias", { enumerable: true, get: function () { return modelInfo_1.isMovingModelAlias; } });
22
+ Object.defineProperty(exports, "parseModelId", { enumerable: true, get: function () { return modelInfo_1.parseModelId; } });
23
+ Object.defineProperty(exports, "compareModelIds", { enumerable: true, get: function () { return modelInfo_1.compareModelIds; } });
24
+ Object.defineProperty(exports, "sortModelIds", { enumerable: true, get: function () { return modelInfo_1.sortModelIds; } });
25
+ Object.defineProperty(exports, "shortModelName", { enumerable: true, get: function () { return modelInfo_1.shortModelName; } });
26
+ Object.defineProperty(exports, "getModelDisplayInfo", { enumerable: true, get: function () { return modelInfo_1.getModelDisplayInfo; } });
27
+ Object.defineProperty(exports, "getModelInfo", { enumerable: true, get: function () { return modelInfo_1.getModelInfo; } });
28
+ Object.defineProperty(exports, "getModelPricing", { enumerable: true, get: function () { return modelInfo_1.getModelPricing; } });
29
+ Object.defineProperty(exports, "resolveModelPricing", { enumerable: true, get: function () { return modelInfo_1.resolveModelPricing; } });
30
+ Object.defineProperty(exports, "registerModelPricing", { enumerable: true, get: function () { return modelInfo_1.registerModelPricing; } });
31
+ Object.defineProperty(exports, "clearRegisteredModelPricing", { enumerable: true, get: function () { return modelInfo_1.clearRegisteredModelPricing; } });
32
+ Object.defineProperty(exports, "getPricingCatalogInfo", { enumerable: true, get: function () { return modelInfo_1.getPricingCatalogInfo; } });
33
+ Object.defineProperty(exports, "calculateCost", { enumerable: true, get: function () { return modelInfo_1.calculateCost; } });
34
+ Object.defineProperty(exports, "calculateCostWithPricing", { enumerable: true, get: function () { return modelInfo_1.calculateCostWithPricing; } });
35
+ Object.defineProperty(exports, "calculateCostWithProvenance", { enumerable: true, get: function () { return modelInfo_1.calculateCostWithProvenance; } });
36
+ Object.defineProperty(exports, "mergeCostSources", { enumerable: true, get: function () { return modelInfo_1.mergeCostSources; } });
37
+ Object.defineProperty(exports, "costSourceFromPricingProvenance", { enumerable: true, get: function () { return modelInfo_1.costSourceFromPricingProvenance; } });
38
+ Object.defineProperty(exports, "mergeCostComponents", { enumerable: true, get: function () { return modelInfo_1.mergeCostComponents; } });
39
+ Object.defineProperty(exports, "formatCost", { enumerable: true, get: function () { return modelInfo_1.formatCost; } });
40
+ var usageNormalization_1 = require("./usageNormalization");
41
+ Object.defineProperty(exports, "normalizeProviderUsage", { enumerable: true, get: function () { return usageNormalization_1.normalizeProviderUsage; } });
42
+ Object.defineProperty(exports, "extractNormalizedUsage", { enumerable: true, get: function () { return usageNormalization_1.extractNormalizedUsage; } });
43
+ Object.defineProperty(exports, "calculateNormalizedUsageCost", { enumerable: true, get: function () { return usageNormalization_1.calculateNormalizedUsageCost; } });
44
+ var formatting_1 = require("./formatting");
45
+ Object.defineProperty(exports, "formatTokenCount", { enumerable: true, get: function () { return formatting_1.formatTokenCount; } });
46
+ Object.defineProperty(exports, "formatDurationMs", { enumerable: true, get: function () { return formatting_1.formatDurationMs; } });
47
+ var tokenEstimation_1 = require("./tokenEstimation");
48
+ Object.defineProperty(exports, "ASCII_CHARS_PER_TOKEN", { enumerable: true, get: function () { return tokenEstimation_1.ASCII_CHARS_PER_TOKEN; } });
49
+ Object.defineProperty(exports, "estimateTextTokens", { enumerable: true, get: function () { return tokenEstimation_1.estimateTextTokens; } });
50
+ Object.defineProperty(exports, "estimateSerializedTokens", { enumerable: true, get: function () { return tokenEstimation_1.estimateSerializedTokens; } });
@@ -51,6 +51,12 @@ export interface HydrateResult {
51
51
  contextWindowEntries: number;
52
52
  /** ISO timestamp of the fetch that produced the cache. */
53
53
  fetchedAt: string;
54
+ /**
55
+ * `getPricingCatalogInfo().digest` after hydration: a stable fingerprint of
56
+ * every active pricing table, so callers can record which prices produced
57
+ * an estimate. See `getPricingCatalogInfo()` for the source and staleness.
58
+ */
59
+ digest: string;
54
60
  }
55
61
  export declare const LITELLM_CATALOG_URL = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json";
56
62
  /**
@@ -61,6 +67,10 @@ export declare const LITELLM_CATALOG_URL = "https://raw.githubusercontent.com/Be
61
67
  * 2. Otherwise attempt a network fetch; on success, persist + apply.
62
68
  * 3. On any failure, load a stale cache if present; else leave overrides empty.
63
69
  *
70
+ * `getPricingCatalogInfo().source` afterwards reads `litellm` for a fresh fetch
71
+ * or a cache within `ttlMs`, and `stale-cache` for a cache older than `ttlMs`
72
+ * — whether the refresh failed or `offline` skipped it.
73
+ *
64
74
  * Always resolves — never throws. Offline-safe.
65
75
  */
66
76
  export declare function hydratePricingCatalog(options?: HydrateOptions): Promise<HydrateResult>;
@@ -59,6 +59,7 @@ const node_fs_1 = require("node:fs");
59
59
  const path = __importStar(require("node:path"));
60
60
  const modelContext_1 = require("./modelContext");
61
61
  const modelInfo_1 = require("./modelInfo");
62
+ const ownKey_1 = require("./ownKey");
62
63
  const paths_1 = require("./paths");
63
64
  const atomic_1 = require("./writers/atomic");
64
65
  // ── Constants ──
@@ -75,6 +76,10 @@ const DEFAULT_TIMEOUT_MS = 3_000;
75
76
  * 2. Otherwise attempt a network fetch; on success, persist + apply.
76
77
  * 3. On any failure, load a stale cache if present; else leave overrides empty.
77
78
  *
79
+ * `getPricingCatalogInfo().source` afterwards reads `litellm` for a fresh fetch
80
+ * or a cache within `ttlMs`, and `stale-cache` for a cache older than `ttlMs`
81
+ * — whether the refresh failed or `offline` skipped it.
82
+ *
78
83
  * Always resolves — never throws. Offline-safe.
79
84
  */
80
85
  async function hydratePricingCatalog(options = {}) {
@@ -83,8 +88,9 @@ async function hydratePricingCatalog(options = {}) {
83
88
  const now = Date.now();
84
89
  // 1. Fresh cache? (Offline callers take any cache, however old.)
85
90
  const cached = await readCache(cachePath);
86
- if (cached && (offline || now - Date.parse(cached.fetchedAt) < ttlMs)) {
87
- return applyCacheFile(cached, 'cache');
91
+ const cacheFresh = cached !== null && now - Date.parse(cached.fetchedAt) < ttlMs;
92
+ if (cached && (offline || cacheFresh)) {
93
+ return applyCacheFile(cached, 'cache', cacheFresh ? 'litellm' : 'stale-cache');
88
94
  }
89
95
  // 2. Try network.
90
96
  if (fetchImpl && !offline) {
@@ -97,12 +103,12 @@ async function hydratePricingCatalog(options = {}) {
97
103
  contextWindows: normalizeLiteLlmContextWindows(fetched),
98
104
  };
99
105
  await writeCache(cachePath, payload, logger);
100
- return applyCacheFile(payload, 'network');
106
+ return applyCacheFile(payload, 'network', 'litellm');
101
107
  }
102
108
  }
103
109
  // 3. Fall back to stale cache if present.
104
110
  if (cached) {
105
- return applyCacheFile(cached, 'cache');
111
+ return applyCacheFile(cached, 'cache', 'stale-cache');
106
112
  }
107
113
  // 4. Nothing we can do — stay on static tables.
108
114
  return {
@@ -110,6 +116,7 @@ async function hydratePricingCatalog(options = {}) {
110
116
  entries: 0,
111
117
  contextWindowEntries: 0,
112
118
  fetchedAt: new Date(now).toISOString(),
119
+ digest: (0, modelInfo_1.getPricingCatalogInfo)().digest,
113
120
  };
114
121
  }
115
122
  /**
@@ -117,9 +124,9 @@ async function hydratePricingCatalog(options = {}) {
117
124
  * `contextWindows` is absent on caches written before context hydration; those
118
125
  * contribute no overrides rather than clearing whatever is already loaded.
119
126
  */
120
- function applyCacheFile(cache, source) {
127
+ function applyCacheFile(cache, source, basis) {
121
128
  const contextWindows = cache.contextWindows ?? {};
122
- (0, modelInfo_1._setPricingOverrides)(cache.overrides);
129
+ (0, modelInfo_1._setPricingOverrides)(cache.overrides, { source: basis, fetchedAt: cache.fetchedAt });
123
130
  if (Object.keys(contextWindows).length > 0) {
124
131
  (0, modelContext_1._setCatalogContextWindows)(contextWindows);
125
132
  }
@@ -128,6 +135,7 @@ function applyCacheFile(cache, source) {
128
135
  entries: Object.keys(cache.overrides).length,
129
136
  contextWindowEntries: Object.keys(contextWindows).length,
130
137
  fetchedAt: cache.fetchedAt,
138
+ digest: (0, modelInfo_1.getPricingCatalogInfo)().digest,
131
139
  };
132
140
  }
133
141
  /**
@@ -149,7 +157,7 @@ function normalizeLiteLlmCatalog(raw) {
149
157
  continue;
150
158
  // Catalog keys use both `provider/model` and bare `model` forms. Record both
151
159
  // the raw key and the bare-model form so lookups work regardless of caller.
152
- out[key] = pricing;
160
+ (0, ownKey_1.setOwnValue)(out, key, pricing);
153
161
  const bare = stripProviderPrefix(key);
154
162
  if (bare && bare !== key && !out[bare]) {
155
163
  out[bare] = pricing;
@@ -186,7 +194,7 @@ function normalizeLiteLlmContextWindows(raw) {
186
194
  const window = entry.max_input_tokens;
187
195
  if (typeof window !== 'number' || !Number.isFinite(window) || window <= 0)
188
196
  continue;
189
- out[key] = window;
197
+ (0, ownKey_1.setOwnValue)(out, key, window);
190
198
  const bare = stripProviderPrefix(key);
191
199
  if (bare && bare !== key) {
192
200
  const seen = aliasWindows.get(bare) ?? new Set();
@@ -14,6 +14,10 @@
14
14
  * record over `maxRecordBytes` is skipped without being buffered and reported
15
15
  * in `exclusions`, the only way a record is permanently left out.
16
16
  *
17
+ * `includeText: false` returns the same entries, ordinals, timestamps,
18
+ * metadata, exclusions, and cursor without prompt text, for consumers that
19
+ * only count or attribute prompts. Cursors are interchangeable between modes.
20
+ *
17
21
  * A prompt can be written before its answer. The cursor then keeps the byte
18
22
  * offset of that prompt's line and its answer-tracking state (never text), and
19
23
  * the prompt is returned again under the same identity once its first
@@ -34,8 +38,14 @@ export interface PromptHistoryBounds {
34
38
  /** Bytes read across all files in this call. Default 256 MiB. */
35
39
  maxTotalBytes?: number;
36
40
  /**
37
- * Largest single log record that is parsed. Default 16 MiB. A longer record
38
- * is skipped without being buffered and reported in `exclusions`.
41
+ * Largest single log record that is parsed. Default 16 MiB
42
+ * (`PROMPT_HISTORY_ORDINAL_MAX_RECORD_BYTES`). A longer record is skipped
43
+ * without being buffered and reported in `exclusions`.
44
+ *
45
+ * Ordinals depend on this bound: a prompt inside a skipped record is never
46
+ * seen, so later prompts in that file do not count it. Ordinals are only
47
+ * stable across runs and across consumers (for example usage events'
48
+ * `promptOrdinal`) when every reader uses the default.
39
49
  */
40
50
  maxRecordBytes?: number;
41
51
  /** Entries returned by one call. Default 10 000. */
@@ -139,8 +149,10 @@ export interface PromptHistoryEntry {
139
149
  ordinal: number;
140
150
  /** ISO timestamp from the log line; never synthesized. */
141
151
  timestamp: string;
142
- /** Full prompt text, untruncated. */
152
+ /** Full prompt text, untruncated; `''` when the call passed `includeText: false`. */
143
153
  text: string;
154
+ /** Set when `text` was left out by `includeText: false`; the prompt was not empty. */
155
+ textOmitted?: true;
144
156
  /** Realpath of the working directory; always inside an allowed root. */
145
157
  cwd: string;
146
158
  gitBranch?: string;
@@ -170,7 +182,9 @@ export interface PromptHistorySessionRef {
170
182
  /**
171
183
  * A log record permanently left out. Records over `maxRecordBytes` and lines
172
184
  * that are not valid JSON are never parsed, so any prompt inside is lost, and
173
- * the ordinals of later prompts in that file do not count it.
185
+ * the ordinals of later prompts in that file do not count it. Because of
186
+ * that, ordinals are only stable across runs and consumers that all use the
187
+ * default `maxRecordBytes` (`PROMPT_HISTORY_ORDINAL_MAX_RECORD_BYTES`).
174
188
  */
175
189
  export interface PromptHistoryExclusion {
176
190
  provider: PromptHistoryProvider;
@@ -213,6 +227,14 @@ export interface CollectPromptHistoryOptions {
213
227
  cursor?: PromptHistoryCursor;
214
228
  bounds?: PromptHistoryBounds;
215
229
  signal?: AbortSignal;
230
+ /**
231
+ * Return prompt text. Default true. With `false`, entries keep their
232
+ * ordinal, timestamp, cwd, branch, and answer metadata but carry `text: ''`
233
+ * and `textOmitted: true`, and no prompt text is kept past classifying its
234
+ * line. Ordinals, timestamps, exclusions, and the cursor are identical in
235
+ * both modes, so a cursor written in one mode resumes in the other.
236
+ */
237
+ includeText?: boolean;
216
238
  }
217
239
  export interface PromptHistoryResult {
218
240
  /** Ordered by provider, session, then ordinal. */
@@ -235,6 +257,13 @@ export interface PromptHistoryResult {
235
257
  /** Realpaths of the roots scanned, after worktree expansion. */
236
258
  roots?: string[];
237
259
  }
260
+ /**
261
+ * The default `maxRecordBytes` (16 MiB). Prompt ordinals are only comparable
262
+ * between readers that skip the same records, so every reader that publishes
263
+ * or joins on ordinals (prompt history, session prompt history, usage events'
264
+ * `promptOrdinal`) uses this bound.
265
+ */
266
+ export declare const PROMPT_HISTORY_ORDINAL_MAX_RECORD_BYTES: number;
238
267
  /** @internal */
239
268
  export declare const DEFAULT_BOUNDS: Required<PromptHistoryBounds>;
240
269
  /** Test hooks; not part of the public entry points. */
@@ -257,6 +286,17 @@ export type RecordObserver = (record: Record<string, unknown>, info: {
257
286
  ordinal: number;
258
287
  lineOffset: number;
259
288
  }) => void;
289
+ /**
290
+ * Sees every human prompt as it is given its ordinal, before the timestamp and
291
+ * scope checks, so prompts that are not returned are seen too. `timestamp` is
292
+ * undefined when the line has no usable one.
293
+ * @internal
294
+ */
295
+ export type PromptObserver = (prompt: {
296
+ ordinal: number;
297
+ timestamp: string | undefined;
298
+ lineOffset: number;
299
+ }) => void;
260
300
  /** @internal */
261
301
  export interface ScanContext {
262
302
  roots: string[];
@@ -269,6 +309,9 @@ export interface ScanContext {
269
309
  entriesSoFar: () => number;
270
310
  exclude: (exclusion: PromptHistoryExclusion) => void;
271
311
  observe?: RecordObserver;
312
+ onPrompt?: PromptObserver;
313
+ /** Keep prompt text on entries. Default true. */
314
+ includeText?: boolean;
272
315
  }
273
316
  /** @internal */
274
317
  export interface FileBudget {
@@ -293,6 +336,24 @@ export interface FileOutcome {
293
336
  }
294
337
  /** Collect human-typed prompts from Claude Code and Codex logs under the given roots. */
295
338
  export declare function collectPromptHistory(options: CollectPromptHistoryOptions): Promise<PromptHistoryResult>;
339
+ /** One human prompt of a session file: its ordinal and parsed timestamp. @internal */
340
+ export interface PromptOrdinalMark {
341
+ ordinal: number;
342
+ /** Prompt line timestamp, ms since epoch. */
343
+ timestampMs: number;
344
+ }
345
+ /**
346
+ * Every human prompt of one session file with its ordinal and timestamp, read
347
+ * whole in one pass with the default record bound, so the ordinals are the
348
+ * ones `collectPromptHistory()` returns with default bounds. No scope check
349
+ * and no text: prompts are counted wherever their cwd points. Prompts whose
350
+ * line has no usable timestamp still take an ordinal but are left out. A
351
+ * non-interactive Codex session (subagent, fork, unknown source) has none.
352
+ * In file order. Rejects when the file cannot be opened or changes while its
353
+ * head is read.
354
+ * @internal
355
+ */
356
+ export declare function readPromptOrdinalMarks(provider: PromptHistoryProvider, sessionId: string, filePath: string): Promise<PromptOrdinalMark[]>;
296
357
  /** @internal */
297
358
  export declare function emptyStats(): PromptHistoryStats;
298
359
  /**