@roberttlange/agentlens 0.2.2 → 0.3.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.
Files changed (48) hide show
  1. package/dist/browser.js +154 -20
  2. package/dist/browser.js.map +1 -1
  3. package/dist/main.test.js +138 -1
  4. package/dist/main.test.js.map +1 -1
  5. package/node_modules/@agentlens/contracts/dist/index.d.ts +120 -0
  6. package/node_modules/@agentlens/core/dist/__tests__/config.test.js +67 -2
  7. package/node_modules/@agentlens/core/dist/__tests__/config.test.js.map +1 -1
  8. package/node_modules/@agentlens/core/dist/__tests__/index.test.js +590 -2
  9. package/node_modules/@agentlens/core/dist/__tests__/index.test.js.map +1 -1
  10. package/node_modules/@agentlens/core/dist/config.js +95 -5
  11. package/node_modules/@agentlens/core/dist/config.js.map +1 -1
  12. package/node_modules/@agentlens/core/dist/generatedPricing.d.ts +3 -0
  13. package/node_modules/@agentlens/core/dist/generatedPricing.js +131 -0
  14. package/node_modules/@agentlens/core/dist/generatedPricing.js.map +1 -0
  15. package/node_modules/@agentlens/core/dist/metrics.d.ts +13 -0
  16. package/node_modules/@agentlens/core/dist/metrics.js +227 -54
  17. package/node_modules/@agentlens/core/dist/metrics.js.map +1 -1
  18. package/node_modules/@agentlens/core/dist/pricing.d.ts +15 -0
  19. package/node_modules/@agentlens/core/dist/pricing.js +133 -0
  20. package/node_modules/@agentlens/core/dist/pricing.js.map +1 -0
  21. package/node_modules/@agentlens/core/dist/pricing.test.d.ts +1 -0
  22. package/node_modules/@agentlens/core/dist/pricing.test.js +109 -0
  23. package/node_modules/@agentlens/core/dist/pricing.test.js.map +1 -0
  24. package/node_modules/@agentlens/core/dist/sourceProfiles.js +7 -67
  25. package/node_modules/@agentlens/core/dist/sourceProfiles.js.map +1 -1
  26. package/node_modules/@agentlens/core/dist/traceIndex.d.ts +34 -1
  27. package/node_modules/@agentlens/core/dist/traceIndex.js +374 -15
  28. package/node_modules/@agentlens/core/dist/traceIndex.js.map +1 -1
  29. package/node_modules/@agentlens/server/dist/activity-cache.d.ts +32 -0
  30. package/node_modules/@agentlens/server/dist/activity-cache.js +63 -0
  31. package/node_modules/@agentlens/server/dist/activity-cache.js.map +1 -0
  32. package/node_modules/@agentlens/server/dist/activity-cache.test.d.ts +1 -0
  33. package/node_modules/@agentlens/server/dist/activity-cache.test.js +170 -0
  34. package/node_modules/@agentlens/server/dist/activity-cache.test.js.map +1 -0
  35. package/node_modules/@agentlens/server/dist/activity.d.ts +31 -1
  36. package/node_modules/@agentlens/server/dist/activity.js +532 -34
  37. package/node_modules/@agentlens/server/dist/activity.js.map +1 -1
  38. package/node_modules/@agentlens/server/dist/app.d.ts +4 -2
  39. package/node_modules/@agentlens/server/dist/app.js +248 -5
  40. package/node_modules/@agentlens/server/dist/app.js.map +1 -1
  41. package/node_modules/@agentlens/server/dist/app.test.js +670 -9
  42. package/node_modules/@agentlens/server/dist/app.test.js.map +1 -1
  43. package/node_modules/@agentlens/server/dist/web/assets/index-CTFOBaBt.css +1 -0
  44. package/node_modules/@agentlens/server/dist/web/assets/index-CVf00w06.js +52 -0
  45. package/node_modules/@agentlens/server/dist/web/index.html +2 -2
  46. package/package.json +1 -1
  47. package/node_modules/@agentlens/server/dist/web/assets/index-Ci8okH8M.js +0 -52
  48. package/node_modules/@agentlens/server/dist/web/assets/index-Cj3kmsFf.css +0 -1
@@ -0,0 +1,131 @@
1
+ // Generated by scripts/sync-pricing.mjs on 2026-03-08T11:05:25.212Z.
2
+ // Sources: https://platform.claude.com/docs/en/about-claude/pricing, https://platform.claude.com/docs/en/about-claude/models/overview, https://developers.openai.com/api/docs/pricing, https://developers.openai.com/api/docs/models/gpt-5.2, https://developers.openai.com/api/docs/models/gpt-5.2-codex, https://developers.openai.com/api/docs/models/gpt-5.4, https://developers.openai.com/api/docs/models/gpt-5.3-codex
3
+ export const DEFAULT_PRICING_MODEL_RATES = [
4
+ {
5
+ model: "gpt-5.2-codex",
6
+ inputPer1MUsd: 1.75,
7
+ outputPer1MUsd: 14,
8
+ cachedReadPer1MUsd: 0.175,
9
+ cachedCreatePer1MUsd: 0,
10
+ reasoningOutputPer1MUsd: 0,
11
+ contextWindowTokens: 400000,
12
+ },
13
+ {
14
+ model: "gpt-5.3-codex",
15
+ inputPer1MUsd: 1.75,
16
+ outputPer1MUsd: 14,
17
+ cachedReadPer1MUsd: 0.175,
18
+ cachedCreatePer1MUsd: 0,
19
+ reasoningOutputPer1MUsd: 0,
20
+ contextWindowTokens: 400000,
21
+ },
22
+ {
23
+ model: "gpt-5.2",
24
+ inputPer1MUsd: 0.875,
25
+ outputPer1MUsd: 7,
26
+ cachedReadPer1MUsd: 0.0875,
27
+ cachedCreatePer1MUsd: 0,
28
+ reasoningOutputPer1MUsd: 0,
29
+ contextWindowTokens: 400000,
30
+ },
31
+ {
32
+ model: "gpt-5.4",
33
+ inputPer1MUsd: 1.25,
34
+ outputPer1MUsd: 7.5,
35
+ cachedReadPer1MUsd: 0.13,
36
+ cachedCreatePer1MUsd: 0,
37
+ reasoningOutputPer1MUsd: 0,
38
+ longContextThresholdTokens: 272000,
39
+ longContextInputPer1MUsd: 2.5,
40
+ longContextOutputPer1MUsd: 11.25,
41
+ contextWindowTokens: 1050000,
42
+ },
43
+ {
44
+ model: "claude-opus-4.6",
45
+ inputPer1MUsd: 5,
46
+ outputPer1MUsd: 25,
47
+ cachedReadPer1MUsd: 0.5,
48
+ cachedCreatePer1MUsd: 6.25,
49
+ cachedCreate5mPer1MUsd: 6.25,
50
+ cachedCreate1hPer1MUsd: 10,
51
+ reasoningOutputPer1MUsd: 0,
52
+ longContextThresholdTokens: 200000,
53
+ longContextInputPer1MUsd: 10,
54
+ longContextOutputPer1MUsd: 37.5,
55
+ longContextCachedReadPer1MUsd: 1,
56
+ longContextCachedCreatePer1MUsd: 12.5,
57
+ longContextCachedCreate5mPer1MUsd: 12.5,
58
+ longContextCachedCreate1hPer1MUsd: 20,
59
+ contextWindowTokens: 200000,
60
+ },
61
+ {
62
+ model: "claude-opus-4-5-20251101",
63
+ inputPer1MUsd: 5,
64
+ outputPer1MUsd: 25,
65
+ cachedReadPer1MUsd: 0.5,
66
+ cachedCreatePer1MUsd: 6.25,
67
+ cachedCreate5mPer1MUsd: 6.25,
68
+ cachedCreate1hPer1MUsd: 10,
69
+ reasoningOutputPer1MUsd: 0,
70
+ contextWindowTokens: 200000,
71
+ },
72
+ {
73
+ model: "claude-sonnet-4.6",
74
+ inputPer1MUsd: 3,
75
+ outputPer1MUsd: 15,
76
+ cachedReadPer1MUsd: 0.3,
77
+ cachedCreatePer1MUsd: 3.75,
78
+ cachedCreate5mPer1MUsd: 3.75,
79
+ cachedCreate1hPer1MUsd: 6,
80
+ reasoningOutputPer1MUsd: 0,
81
+ longContextThresholdTokens: 200000,
82
+ longContextInputPer1MUsd: 6,
83
+ longContextOutputPer1MUsd: 22.5,
84
+ longContextCachedReadPer1MUsd: 0.6,
85
+ longContextCachedCreatePer1MUsd: 7.5,
86
+ longContextCachedCreate5mPer1MUsd: 7.5,
87
+ longContextCachedCreate1hPer1MUsd: 12,
88
+ contextWindowTokens: 200000,
89
+ },
90
+ {
91
+ model: "claude-sonnet-4-5-20250929",
92
+ inputPer1MUsd: 3,
93
+ outputPer1MUsd: 15,
94
+ cachedReadPer1MUsd: 0.3,
95
+ cachedCreatePer1MUsd: 3.75,
96
+ cachedCreate5mPer1MUsd: 3.75,
97
+ cachedCreate1hPer1MUsd: 6,
98
+ reasoningOutputPer1MUsd: 0,
99
+ longContextThresholdTokens: 200000,
100
+ longContextInputPer1MUsd: 6,
101
+ longContextOutputPer1MUsd: 22.5,
102
+ longContextCachedReadPer1MUsd: 0.6,
103
+ longContextCachedCreatePer1MUsd: 7.5,
104
+ longContextCachedCreate5mPer1MUsd: 7.5,
105
+ longContextCachedCreate1hPer1MUsd: 12,
106
+ contextWindowTokens: 200000,
107
+ },
108
+ {
109
+ model: "claude-haiku-4.5",
110
+ inputPer1MUsd: 1,
111
+ outputPer1MUsd: 5,
112
+ cachedReadPer1MUsd: 0.1,
113
+ cachedCreatePer1MUsd: 1.25,
114
+ cachedCreate5mPer1MUsd: 1.25,
115
+ cachedCreate1hPer1MUsd: 2,
116
+ reasoningOutputPer1MUsd: 0,
117
+ contextWindowTokens: 200000,
118
+ },
119
+ ];
120
+ export const DEFAULT_CONTEXT_WINDOWS = [
121
+ { model: "gpt-5.2-codex", contextWindowTokens: 400000 },
122
+ { model: "gpt-5.3-codex", contextWindowTokens: 400000 },
123
+ { model: "gpt-5.2", contextWindowTokens: 400000 },
124
+ { model: "gpt-5.4", contextWindowTokens: 1050000 },
125
+ { model: "claude-opus-4.6", contextWindowTokens: 200000 },
126
+ { model: "claude-opus-4-5-20251101", contextWindowTokens: 200000 },
127
+ { model: "claude-sonnet-4.6", contextWindowTokens: 200000 },
128
+ { model: "claude-sonnet-4-5-20250929", contextWindowTokens: 200000 },
129
+ { model: "claude-haiku-4.5", contextWindowTokens: 200000 },
130
+ ];
131
+ //# sourceMappingURL=generatedPricing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generatedPricing.js","sourceRoot":"","sources":["../src/generatedPricing.ts"],"names":[],"mappings":"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"}
@@ -1,10 +1,23 @@
1
1
  import type { AgentKind, AppConfig, ModelTokenShare, NormalizedEvent, TokenTotals } from "@agentlens/contracts";
2
+ export interface SessionUsagePoint {
3
+ timestampMs: number;
4
+ agent: AgentKind;
5
+ model: string;
6
+ inputTokens: number;
7
+ cachedReadTokens: number;
8
+ cachedCreateTokens: number;
9
+ outputTokens: number;
10
+ reasoningOutputTokens: number;
11
+ totalTokens: number;
12
+ costUsd: number;
13
+ }
2
14
  interface SessionMetrics {
3
15
  tokenTotals: TokenTotals;
4
16
  modelTokenSharesTop: ModelTokenShare[];
5
17
  modelTokenSharesEstimated: boolean;
6
18
  contextWindowPct: number | null;
7
19
  costEstimateUsd: number | null;
20
+ usagePoints: SessionUsagePoint[];
8
21
  }
9
22
  export declare function deriveSessionMetrics(events: NormalizedEvent[], agent: AgentKind, config: AppConfig): SessionMetrics;
10
23
  export {};
@@ -1,3 +1,4 @@
1
+ import { estimateUsageCost, resolveContextWindowTokens } from "./pricing.js";
1
2
  import { asRecord, asString } from "./utils.js";
2
3
  function toNumber(value) {
3
4
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
@@ -59,6 +60,17 @@ function tokenTotalsFromUsageRecord(usageRecord) {
59
60
  totals.totalTokens = toNumber(usageRecord.total_tokens);
60
61
  return finalizeTokenTotals(totals);
61
62
  }
63
+ function claudeCacheCreationBreakdown(usageRecord) {
64
+ const cacheCreation = asRecord(usageRecord.cache_creation);
65
+ const cachedCreate5mTokens = toNumber(cacheCreation.ephemeral_5m_input_tokens);
66
+ const cachedCreate1hTokens = toNumber(cacheCreation.ephemeral_1h_input_tokens);
67
+ const cachedCreateTokens = Math.max(toNumber(usageRecord.cache_creation_input_tokens), cachedCreate5mTokens + cachedCreate1hTokens);
68
+ return {
69
+ cachedCreateTokens,
70
+ cachedCreate5mTokens,
71
+ cachedCreate1hTokens,
72
+ };
73
+ }
62
74
  function tokenTotalsFromCodexUsageRecord(record) {
63
75
  const totals = emptyTokenTotals();
64
76
  totals.inputTokens = toNumber(record.input_tokens);
@@ -69,6 +81,26 @@ function tokenTotalsFromCodexUsageRecord(record) {
69
81
  totals.totalTokens = toNumber(record.total_tokens);
70
82
  return finalizeTokenTotals(totals);
71
83
  }
84
+ function codexInputIncludesCached(record) {
85
+ const inputTokens = toNumber(record.input_tokens);
86
+ const cachedReadTokens = toNumber(record.cached_input_tokens);
87
+ const outputTokens = toNumber(record.output_tokens);
88
+ const reasoningOutputTokens = toNumber(record.reasoning_output_tokens);
89
+ const totalTokens = toNumber(record.total_tokens);
90
+ if (cachedReadTokens <= 0 || totalTokens <= 0)
91
+ return false;
92
+ return inputTokens + cachedReadTokens + outputTokens + reasoningOutputTokens > totalTokens;
93
+ }
94
+ function codexPromptTokens(record) {
95
+ const inputTokens = toNumber(record.input_tokens);
96
+ const cachedReadTokens = toNumber(record.cached_input_tokens);
97
+ return codexInputIncludesCached(record) ? inputTokens : inputTokens + cachedReadTokens;
98
+ }
99
+ function codexBillableInputTokens(record) {
100
+ const inputTokens = toNumber(record.input_tokens);
101
+ const cachedReadTokens = toNumber(record.cached_input_tokens);
102
+ return codexInputIncludesCached(record) ? Math.max(0, inputTokens - cachedReadTokens) : inputTokens;
103
+ }
72
104
  function tokenTotalsFromOpenCodeTokensRecord(record) {
73
105
  const cache = asRecord(record.cache);
74
106
  const totals = emptyTokenTotals();
@@ -101,20 +133,7 @@ function tokenTotalsFromPiUsageRecord(record) {
101
133
  return finalizeTokenTotals(totals);
102
134
  }
103
135
  function contextWindowResolver(config) {
104
- const byModel = new Map();
105
- for (const entry of config.models.contextWindows) {
106
- const model = entry.model.trim();
107
- if (!model || !Number.isFinite(entry.contextWindowTokens) || entry.contextWindowTokens <= 0)
108
- continue;
109
- byModel.set(model, entry.contextWindowTokens);
110
- }
111
- const fallback = config.models.defaultContextWindowTokens;
112
- return (model) => {
113
- const direct = byModel.get(model);
114
- if (direct)
115
- return direct;
116
- return fallback > 0 ? fallback : 0;
117
- };
136
+ return (model) => resolveContextWindowTokens(model, config);
118
137
  }
119
138
  function buildTopModelShares(modelTokenTotals, topN) {
120
139
  const ranked = [...modelTokenTotals.entries()]
@@ -132,20 +151,20 @@ function buildTopModelShares(modelTokenTotals, topN) {
132
151
  function estimateCost(perModel, costConfig) {
133
152
  if (!costConfig.enabled)
134
153
  return null;
135
- const rateByModel = new Map(costConfig.modelRates.map((rate) => [rate.model, rate]));
136
154
  let total = 0;
137
155
  for (const [model, tokens] of perModel) {
138
- const rate = rateByModel.get(model);
139
- if (!rate) {
140
- if (costConfig.unknownModelPolicy === "n_a")
141
- return null;
142
- continue;
143
- }
144
- total += (tokens.inputTokens / 1_000_000) * rate.inputPer1MUsd;
145
- total += (tokens.cachedReadTokens / 1_000_000) * rate.cachedReadPer1MUsd;
146
- total += (tokens.cachedCreateTokens / 1_000_000) * rate.cachedCreatePer1MUsd;
147
- total += (tokens.outputTokens / 1_000_000) * rate.outputPer1MUsd;
148
- total += (tokens.reasoningOutputTokens / 1_000_000) * rate.reasoningOutputPer1MUsd;
156
+ const cost = estimateUsageCost({
157
+ model,
158
+ promptTokens: tokens.inputTokens + tokens.cachedReadTokens + tokens.cachedCreateTokens,
159
+ inputTokens: tokens.inputTokens,
160
+ cachedReadTokens: tokens.cachedReadTokens,
161
+ cachedCreateTokens: tokens.cachedCreateTokens,
162
+ outputTokens: tokens.outputTokens,
163
+ reasoningOutputTokens: tokens.reasoningOutputTokens,
164
+ }, costConfig);
165
+ if (cost === null)
166
+ return null;
167
+ total += cost;
149
168
  }
150
169
  return Number(total.toFixed(6));
151
170
  }
@@ -158,11 +177,25 @@ function buildClaudeUsageDedupKey(raw, message) {
158
177
  return `message:${messageId}`;
159
178
  return "";
160
179
  }
161
- function codexTokensForCost(totals) {
180
+ function finalizeCostTotal(costKnown, total) {
181
+ if (!costKnown)
182
+ return null;
183
+ return Number(total.toFixed(6));
184
+ }
185
+ function usagePointFromTotals(event, agent, model, totals, costUsd) {
186
+ if (event.timestampMs === null || event.timestampMs <= 0)
187
+ return null;
162
188
  return {
163
- ...totals,
164
- // Codex reports cached tokens as part of input; subtract once for pricing.
165
- inputTokens: Math.max(0, totals.inputTokens - totals.cachedReadTokens - totals.cachedCreateTokens),
189
+ timestampMs: event.timestampMs,
190
+ agent,
191
+ model,
192
+ inputTokens: totals.inputTokens,
193
+ cachedReadTokens: totals.cachedReadTokens,
194
+ cachedCreateTokens: totals.cachedCreateTokens,
195
+ outputTokens: totals.outputTokens,
196
+ reasoningOutputTokens: totals.reasoningOutputTokens,
197
+ totalTokens: totals.totalTokens,
198
+ costUsd: costUsd === null ? 0 : Number(costUsd.toFixed(6)),
166
199
  };
167
200
  }
168
201
  function deriveClaudeMetrics(events, config) {
@@ -170,8 +203,11 @@ function deriveClaudeMetrics(events, config) {
170
203
  const totals = emptyTokenTotals();
171
204
  const modelBreakdown = new Map();
172
205
  let maxContextPct = null;
206
+ let costTotal = 0;
207
+ let costKnown = config.cost.enabled;
173
208
  const seenRows = new Set();
174
209
  const seenUsageKeys = new Set();
210
+ const usagePoints = [];
175
211
  for (const event of events) {
176
212
  const raw = event.raw;
177
213
  if (seenRows.has(raw))
@@ -191,6 +227,7 @@ function deriveClaudeMetrics(events, config) {
191
227
  }
192
228
  const model = asString(message.model) || "<unknown>";
193
229
  const usageTotals = tokenTotalsFromUsageRecord(usage);
230
+ const cacheCreation = claudeCacheCreationBreakdown(usage);
194
231
  addTokenTotals(totals, usageTotals);
195
232
  const existing = modelBreakdown.get(model) ?? emptyTokenTotals();
196
233
  addTokenTotals(existing, usageTotals);
@@ -201,6 +238,26 @@ function deriveClaudeMetrics(events, config) {
201
238
  const pct = (promptTokens / window) * 100;
202
239
  maxContextPct = maxContextPct === null ? pct : Math.max(maxContextPct, pct);
203
240
  }
241
+ const cost = estimateUsageCost({
242
+ model,
243
+ promptTokens,
244
+ inputTokens: usageTotals.inputTokens,
245
+ cachedReadTokens: usageTotals.cachedReadTokens,
246
+ cachedCreateTokens: cacheCreation.cachedCreateTokens,
247
+ cachedCreate5mTokens: cacheCreation.cachedCreate5mTokens,
248
+ cachedCreate1hTokens: cacheCreation.cachedCreate1hTokens,
249
+ outputTokens: usageTotals.outputTokens,
250
+ reasoningOutputTokens: usageTotals.reasoningOutputTokens,
251
+ }, config.cost);
252
+ if (cost === null) {
253
+ costKnown = false;
254
+ }
255
+ else {
256
+ costTotal += cost;
257
+ }
258
+ const usagePoint = usagePointFromTotals(event, "claude", model, usageTotals, cost);
259
+ if (usagePoint)
260
+ usagePoints.push(usagePoint);
204
261
  }
205
262
  for (const [model, modelTotals] of modelBreakdown) {
206
263
  modelBreakdown.set(model, finalizeTokenTotals(modelTotals));
@@ -210,7 +267,8 @@ function deriveClaudeMetrics(events, config) {
210
267
  modelTokenSharesTop: buildTopModelShares(new Map([...modelBreakdown.entries()].map(([model, modelTotals]) => [model, modelTotals.totalTokens])), config.traceInspector.topModelCount),
211
268
  modelTokenSharesEstimated: false,
212
269
  contextWindowPct: maxContextPct,
213
- costEstimateUsd: estimateCost(modelBreakdown, config.cost),
270
+ costEstimateUsd: finalizeCostTotal(costKnown, costTotal),
271
+ usagePoints,
214
272
  };
215
273
  }
216
274
  function deriveCodexMetrics(events, config) {
@@ -221,14 +279,17 @@ function deriveCodexMetrics(events, config) {
221
279
  if (seenRows.has(event.raw))
222
280
  continue;
223
281
  seenRows.add(event.raw);
224
- rows.push(event.raw);
282
+ rows.push({ raw: event.raw, event });
225
283
  }
226
284
  let currentModel = "";
227
285
  let latestTotals = emptyTokenTotals();
228
286
  let prevTotalTokens = null;
229
287
  let maxContextPct = null;
288
+ let costTotal = 0;
289
+ let costKnown = config.cost.enabled;
230
290
  const modelTotalDeltas = new Map();
231
- for (const row of rows) {
291
+ const usagePoints = [];
292
+ for (const { raw: row, event } of rows) {
232
293
  const rowType = asString(row.type).toLowerCase();
233
294
  if (rowType === "turn_context") {
234
295
  const payload = asRecord(row.payload);
@@ -254,37 +315,50 @@ function deriveCodexMetrics(events, config) {
254
315
  prevTotalTokens = latestTotals.totalTokens;
255
316
  const windowFromEvent = toNumber(info.model_context_window);
256
317
  const window = windowFromEvent > 0 ? windowFromEvent : resolveWindow(currentModel);
257
- const promptTokens = toNumber(lastUsage.input_tokens) + toNumber(lastUsage.cached_input_tokens) + toNumber(lastUsage.cache_creation_input_tokens);
258
- const fallbackPromptTokens = promptTokens > 0 ? promptTokens : toNumber(lastUsage.total_tokens);
318
+ const pricedUsage = Object.keys(lastUsage).length > 0 ? lastUsage : totalUsage;
319
+ const promptTokens = codexPromptTokens(pricedUsage);
320
+ const fallbackPromptTokens = promptTokens > 0 ? promptTokens : toNumber(pricedUsage.total_tokens);
259
321
  if (window > 0 && fallbackPromptTokens > 0) {
260
322
  const pct = (fallbackPromptTokens / window) * 100;
261
323
  maxContextPct = maxContextPct === null ? pct : Math.max(maxContextPct, pct);
262
324
  }
325
+ if (currentModel) {
326
+ const cost = estimateUsageCost({
327
+ model: currentModel,
328
+ promptTokens: fallbackPromptTokens,
329
+ inputTokens: codexBillableInputTokens(pricedUsage),
330
+ cachedReadTokens: toNumber(pricedUsage.cached_input_tokens),
331
+ cachedCreateTokens: 0,
332
+ outputTokens: toNumber(pricedUsage.output_tokens),
333
+ reasoningOutputTokens: toNumber(pricedUsage.reasoning_output_tokens),
334
+ }, config.cost);
335
+ if (cost === null) {
336
+ costKnown = false;
337
+ }
338
+ else {
339
+ costTotal += cost;
340
+ }
341
+ const usageTotals = tokenTotalsFromCodexUsageRecord(pricedUsage);
342
+ const usagePoint = usagePointFromTotals(event, "codex", currentModel, usageTotals, cost);
343
+ if (usagePoint)
344
+ usagePoints.push(usagePoint);
345
+ }
346
+ else {
347
+ const usageTotals = tokenTotalsFromCodexUsageRecord(pricedUsage);
348
+ const usagePoint = usagePointFromTotals(event, "codex", "<unknown>", usageTotals, null);
349
+ if (usagePoint)
350
+ usagePoints.push(usagePoint);
351
+ }
263
352
  }
264
353
  const tokenTotals = finalizeTokenTotals(latestTotals);
265
354
  const topShares = buildTopModelShares(modelTotalDeltas, config.traceInspector.topModelCount);
266
- const modelBreakdown = new Map();
267
- const allModelShares = [...modelTotalDeltas.entries()].filter(([, tokens]) => tokens > 0);
268
- const shareTotal = allModelShares.reduce((sum, [, tokens]) => sum + tokens, 0);
269
- if (shareTotal > 0) {
270
- for (const [model, modelTokens] of allModelShares) {
271
- const ratio = modelTokens / shareTotal;
272
- modelBreakdown.set(model, codexTokensForCost({
273
- inputTokens: tokenTotals.inputTokens * ratio,
274
- cachedReadTokens: tokenTotals.cachedReadTokens * ratio,
275
- cachedCreateTokens: tokenTotals.cachedCreateTokens * ratio,
276
- outputTokens: tokenTotals.outputTokens * ratio,
277
- reasoningOutputTokens: tokenTotals.reasoningOutputTokens * ratio,
278
- totalTokens: tokenTotals.totalTokens * ratio,
279
- }));
280
- }
281
- }
282
355
  return {
283
356
  tokenTotals,
284
357
  modelTokenSharesTop: topShares,
285
358
  modelTokenSharesEstimated: topShares.length > 0,
286
359
  contextWindowPct: maxContextPct,
287
- costEstimateUsd: estimateCost(modelBreakdown, config.cost),
360
+ costEstimateUsd: finalizeCostTotal(costKnown, costTotal),
361
+ usagePoints,
288
362
  };
289
363
  }
290
364
  function deriveOpenCodeMetrics(events, config) {
@@ -292,7 +366,10 @@ function deriveOpenCodeMetrics(events, config) {
292
366
  const totals = emptyTokenTotals();
293
367
  const modelBreakdown = new Map();
294
368
  let maxContextPct = null;
369
+ let costTotal = 0;
370
+ let costKnown = config.cost.enabled;
295
371
  const seenAssistantMessageIds = new Set();
372
+ const usagePoints = [];
296
373
  for (const event of events) {
297
374
  const raw = asRecord(event.raw);
298
375
  const nestedMessage = asRecord(raw.message);
@@ -321,6 +398,24 @@ function deriveOpenCodeMetrics(events, config) {
321
398
  const pct = (promptTokens / window) * 100;
322
399
  maxContextPct = maxContextPct === null ? pct : Math.max(maxContextPct, pct);
323
400
  }
401
+ const cost = estimateUsageCost({
402
+ model: modelId || model,
403
+ promptTokens,
404
+ inputTokens: usageTotals.inputTokens,
405
+ cachedReadTokens: usageTotals.cachedReadTokens,
406
+ cachedCreateTokens: usageTotals.cachedCreateTokens,
407
+ outputTokens: usageTotals.outputTokens,
408
+ reasoningOutputTokens: usageTotals.reasoningOutputTokens,
409
+ }, config.cost);
410
+ if (cost === null) {
411
+ costKnown = false;
412
+ }
413
+ else {
414
+ costTotal += cost;
415
+ }
416
+ const usagePoint = usagePointFromTotals(event, "opencode", modelId || model, usageTotals, cost);
417
+ if (usagePoint)
418
+ usagePoints.push(usagePoint);
324
419
  }
325
420
  for (const [model, modelTotals] of modelBreakdown) {
326
421
  modelBreakdown.set(model, finalizeTokenTotals(modelTotals));
@@ -330,7 +425,8 @@ function deriveOpenCodeMetrics(events, config) {
330
425
  modelTokenSharesTop: buildTopModelShares(new Map([...modelBreakdown.entries()].map(([model, modelTotals]) => [model, modelTotals.totalTokens])), config.traceInspector.topModelCount),
331
426
  modelTokenSharesEstimated: false,
332
427
  contextWindowPct: maxContextPct,
333
- costEstimateUsd: estimateCost(modelBreakdown, config.cost),
428
+ costEstimateUsd: finalizeCostTotal(costKnown, costTotal),
429
+ usagePoints,
334
430
  };
335
431
  }
336
432
  function deriveCursorMetrics(events, config) {
@@ -338,6 +434,7 @@ function deriveCursorMetrics(events, config) {
338
434
  const totals = emptyTokenTotals();
339
435
  const modelBreakdown = new Map();
340
436
  let maxContextPct = null;
437
+ const usagePoints = [];
341
438
  const modelFallback = events
342
439
  .map((event) => asString(asRecord(event.raw).model).trim())
343
440
  .find((model) => model.length > 0) ?? "<unknown>";
@@ -363,6 +460,22 @@ function deriveCursorMetrics(events, config) {
363
460
  modelTotals.inputTokens += estimatedTokens;
364
461
  applyPromptWindowEstimate();
365
462
  modelBreakdown.set(model, modelTotals);
463
+ const usageTotals = finalizeTokenTotals({
464
+ ...emptyTokenTotals(),
465
+ inputTokens: estimatedTokens,
466
+ });
467
+ const cost = estimateUsageCost({
468
+ model,
469
+ promptTokens: usageTotals.inputTokens,
470
+ inputTokens: usageTotals.inputTokens,
471
+ cachedReadTokens: 0,
472
+ cachedCreateTokens: 0,
473
+ outputTokens: 0,
474
+ reasoningOutputTokens: 0,
475
+ }, config.cost);
476
+ const usagePoint = usagePointFromTotals(event, "cursor", model, usageTotals, cost);
477
+ if (usagePoint)
478
+ usagePoints.push(usagePoint);
366
479
  continue;
367
480
  }
368
481
  if (event.eventKind === "assistant" || event.eventKind === "tool_use" || event.eventKind === "reasoning") {
@@ -373,6 +486,23 @@ function deriveCursorMetrics(events, config) {
373
486
  modelTotals.reasoningOutputTokens += estimatedTokens;
374
487
  }
375
488
  modelBreakdown.set(model, modelTotals);
489
+ const usageTotals = finalizeTokenTotals({
490
+ ...emptyTokenTotals(),
491
+ outputTokens: estimatedTokens,
492
+ reasoningOutputTokens: event.eventKind === "reasoning" ? estimatedTokens : 0,
493
+ });
494
+ const cost = estimateUsageCost({
495
+ model,
496
+ promptTokens: 0,
497
+ inputTokens: 0,
498
+ cachedReadTokens: 0,
499
+ cachedCreateTokens: 0,
500
+ outputTokens: usageTotals.outputTokens,
501
+ reasoningOutputTokens: usageTotals.reasoningOutputTokens,
502
+ }, config.cost);
503
+ const usagePoint = usagePointFromTotals(event, "cursor", model, usageTotals, cost);
504
+ if (usagePoint)
505
+ usagePoints.push(usagePoint);
376
506
  }
377
507
  }
378
508
  for (const [model, modelTotals] of modelBreakdown) {
@@ -384,6 +514,7 @@ function deriveCursorMetrics(events, config) {
384
514
  modelTokenSharesEstimated: modelBreakdown.size > 0,
385
515
  contextWindowPct: maxContextPct,
386
516
  costEstimateUsd: estimateCost(modelBreakdown, config.cost),
517
+ usagePoints,
387
518
  };
388
519
  }
389
520
  function deriveGeminiMetrics(events, config) {
@@ -393,6 +524,7 @@ function deriveGeminiMetrics(events, config) {
393
524
  let maxContextPct = null;
394
525
  const seenMessageIds = new Set();
395
526
  const seenRows = new Set();
527
+ const usagePoints = [];
396
528
  for (const event of events) {
397
529
  const raw = asRecord(event.raw);
398
530
  if (seenRows.has(raw))
@@ -421,6 +553,18 @@ function deriveGeminiMetrics(events, config) {
421
553
  const pct = (promptTokens / window) * 100;
422
554
  maxContextPct = maxContextPct === null ? pct : Math.max(maxContextPct, pct);
423
555
  }
556
+ const cost = estimateUsageCost({
557
+ model,
558
+ promptTokens,
559
+ inputTokens: usageTotals.inputTokens,
560
+ cachedReadTokens: usageTotals.cachedReadTokens,
561
+ cachedCreateTokens: usageTotals.cachedCreateTokens,
562
+ outputTokens: usageTotals.outputTokens,
563
+ reasoningOutputTokens: usageTotals.reasoningOutputTokens,
564
+ }, config.cost);
565
+ const usagePoint = usagePointFromTotals(event, "gemini", model, usageTotals, cost);
566
+ if (usagePoint)
567
+ usagePoints.push(usagePoint);
424
568
  }
425
569
  for (const [model, modelTotals] of modelBreakdown) {
426
570
  modelBreakdown.set(model, finalizeTokenTotals(modelTotals));
@@ -431,6 +575,7 @@ function deriveGeminiMetrics(events, config) {
431
575
  modelTokenSharesEstimated: false,
432
576
  contextWindowPct: maxContextPct,
433
577
  costEstimateUsd: estimateCost(modelBreakdown, config.cost),
578
+ usagePoints,
434
579
  };
435
580
  }
436
581
  function derivePiMetrics(events, config) {
@@ -441,6 +586,9 @@ function derivePiMetrics(events, config) {
441
586
  const seenRows = new Set();
442
587
  let embeddedCostTotal = 0;
443
588
  let embeddedCostKnown = false;
589
+ let estimatedCostTotal = 0;
590
+ let estimatedCostKnown = config.cost.enabled;
591
+ const usagePoints = [];
444
592
  for (const event of events) {
445
593
  const raw = asRecord(event.raw);
446
594
  if (seenRows.has(raw))
@@ -475,6 +623,27 @@ function derivePiMetrics(events, config) {
475
623
  embeddedCostKnown = true;
476
624
  }
477
625
  }
626
+ const estimatedCost = estimateUsageCost({
627
+ model,
628
+ promptTokens,
629
+ inputTokens: usageTotals.inputTokens,
630
+ cachedReadTokens: usageTotals.cachedReadTokens,
631
+ cachedCreateTokens: usageTotals.cachedCreateTokens,
632
+ outputTokens: usageTotals.outputTokens,
633
+ reasoningOutputTokens: usageTotals.reasoningOutputTokens,
634
+ }, config.cost);
635
+ if (estimatedCost === null) {
636
+ estimatedCostKnown = false;
637
+ }
638
+ else {
639
+ estimatedCostTotal += estimatedCost;
640
+ }
641
+ const preferredCost = Object.prototype.hasOwnProperty.call(cost, "total")
642
+ ? Number(cost.total)
643
+ : estimatedCost;
644
+ const usagePoint = usagePointFromTotals(event, "pi", model, usageTotals, Number.isFinite(preferredCost) ? preferredCost : estimatedCost);
645
+ if (usagePoint)
646
+ usagePoints.push(usagePoint);
478
647
  }
479
648
  for (const [model, modelTotals] of modelBreakdown) {
480
649
  modelBreakdown.set(model, finalizeTokenTotals(modelTotals));
@@ -484,7 +653,10 @@ function derivePiMetrics(events, config) {
484
653
  modelTokenSharesTop: buildTopModelShares(new Map([...modelBreakdown.entries()].map(([model, modelTotals]) => [model, modelTotals.totalTokens])), config.traceInspector.topModelCount),
485
654
  modelTokenSharesEstimated: false,
486
655
  contextWindowPct: maxContextPct,
487
- costEstimateUsd: embeddedCostKnown ? Number(embeddedCostTotal.toFixed(6)) : null,
656
+ costEstimateUsd: embeddedCostKnown
657
+ ? Number(embeddedCostTotal.toFixed(6))
658
+ : finalizeCostTotal(estimatedCostKnown, estimatedCostTotal),
659
+ usagePoints,
488
660
  };
489
661
  }
490
662
  export function deriveSessionMetrics(events, agent, config) {
@@ -506,6 +678,7 @@ export function deriveSessionMetrics(events, agent, config) {
506
678
  modelTokenSharesEstimated: false,
507
679
  contextWindowPct: null,
508
680
  costEstimateUsd: null,
681
+ usagePoints: [],
509
682
  };
510
683
  }
511
684
  //# sourceMappingURL=metrics.js.map