agent-trace 0.2.6 → 0.2.8

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 (2) hide show
  1. package/agent-trace.cjs +155 -14
  2. package/package.json +1 -1
package/agent-trace.cjs CHANGED
@@ -24225,10 +24225,10 @@ function parseReplay(val) {
24225
24225
  var ev=asRec(e);if(!ev)return null;
24226
24226
  var id=readStr(ev,'id'),type=readStr(ev,'type'),ts=readStr(ev,'timestamp');if(!id||!type||!ts)return null;
24227
24227
  var d=asRec(ev.details),tok=asRec(ev.tokens);
24228
- return{id:id,type:type,timestamp:ts,promptId:readStr(ev,'promptId'),status:readStr(ev,'status'),costUsd:readNum(ev,'costUsd'),toolName:d?readStr(d,'toolName'):undefined,toolDurationMs:d?readNum(d,'toolDurationMs'):undefined,inputTokens:tok?readNum(tok,'input'):undefined,outputTokens:tok?readNum(tok,'output'):undefined,details:d};
24228
+ return{id:id,type:type,timestamp:ts,promptId:readStr(ev,'promptId'),status:readStr(ev,'status'),costUsd:readNum(ev,'costUsd'),toolName:d?readStr(d,'toolName'):undefined,toolDurationMs:d?readNum(d,'toolDurationMs'):undefined,inputTokens:tok?readNum(tok,'input'):undefined,outputTokens:tok?readNum(tok,'output'):undefined,cacheReadTokens:tok?readNum(tok,'cacheRead'):undefined,cacheWriteTokens:tok?readNum(tok,'cacheWrite'):undefined,details:d};
24229
24229
  }).filter(Boolean);
24230
24230
  return{sessionId:sid,startedAt:sa,endedAt:readStr(r,'endedAt'),gitBranch:gitBranch,
24231
- metrics:{promptCount:readNum(m,'promptCount')||0,toolCallCount:readNum(m,'toolCallCount')||0,totalCostUsd:readNum(m,'totalCostUsd')||0,totalInputTokens:readNum(m,'totalInputTokens')||0,totalOutputTokens:readNum(m,'totalOutputTokens')||0,linesAdded:readNum(m,'linesAdded')||0,linesRemoved:readNum(m,'linesRemoved')||0,modelsUsed:readArr(m,'modelsUsed').filter(function(x){return typeof x==='string'}),toolsUsed:readArr(m,'toolsUsed').filter(function(x){return typeof x==='string'}),filesTouched:readArr(m,'filesTouched').filter(function(x){return typeof x==='string'})},
24231
+ metrics:{promptCount:readNum(m,'promptCount')||0,toolCallCount:readNum(m,'toolCallCount')||0,totalCostUsd:readNum(m,'totalCostUsd')||0,totalInputTokens:readNum(m,'totalInputTokens')||0,totalOutputTokens:readNum(m,'totalOutputTokens')||0,totalCacheReadTokens:readNum(m,'totalCacheReadTokens')||0,totalCacheWriteTokens:readNum(m,'totalCacheWriteTokens')||0,linesAdded:readNum(m,'linesAdded')||0,linesRemoved:readNum(m,'linesRemoved')||0,modelsUsed:readArr(m,'modelsUsed').filter(function(x){return typeof x==='string'}),toolsUsed:readArr(m,'toolsUsed').filter(function(x){return typeof x==='string'}),filesTouched:readArr(m,'filesTouched').filter(function(x){return typeof x==='string'})},
24232
24232
  commits:commits,pullRequests:prs,timeline:timeline};
24233
24233
  }
24234
24234
 
@@ -24298,7 +24298,7 @@ function buildPromptGroups(timeline, commits) {
24298
24298
  map[ev.promptId].push(ev);
24299
24299
  });
24300
24300
  return order.map(function(pid){
24301
- var evts = map[pid] || [], promptText, responseText, cost=0,tools=0,inTok=0,outTok=0,dur=0;
24301
+ var evts = map[pid] || [], promptText, responseText, cost=0,tools=0,inTok=0,outTok=0,cacheRTok=0,cacheWTok=0,dur=0;
24302
24302
  var filesR={},filesW={},toolEvts=[];
24303
24303
  evts.forEach(function(ev){
24304
24304
  var d = ev.details;
@@ -24306,7 +24306,7 @@ function buildPromptGroups(timeline, commits) {
24306
24306
  if(ev.type==='assistant_response'||ev.type==='api_call'||ev.type==='api_response'){
24307
24307
  if(d){var rt=readStr(d,'responseText')||readStr(d,'lastAssistantMessage');if(rt)responseText=rt;}
24308
24308
  }
24309
- cost+=(ev.costUsd||0);inTok+=(ev.inputTokens||0);outTok+=(ev.outputTokens||0);
24309
+ cost+=(ev.costUsd||0);inTok+=(ev.inputTokens||0);outTok+=(ev.outputTokens||0);cacheRTok+=(ev.cacheReadTokens||0);cacheWTok+=(ev.cacheWriteTokens||0);
24310
24310
  if(ev.toolName||(ev.type==='tool_call'||ev.type==='tool_result')){
24311
24311
  toolEvts.push(ev);tools++;dur+=(ev.toolDurationMs||0);
24312
24312
  var dd=ev.details,rti=dd?(dd.toolInput||dd.tool_input):undefined,inp=asRec(rti),tiStr=typeof rti==='string'?rti:undefined;
@@ -24327,7 +24327,7 @@ function buildPromptGroups(timeline, commits) {
24327
24327
  }
24328
24328
  deduped.push(ev);
24329
24329
  });
24330
- return{promptId:pid,promptText:promptText,responseText:responseText,toolEvents:deduped,commits:byPrompt[pid]||[],totalCostUsd:cost,totalToolCalls:tools,totalInputTokens:inTok,totalOutputTokens:outTok,totalDurationMs:dur,filesRead:Object.keys(filesR),filesWritten:Object.keys(filesW)};
24330
+ return{promptId:pid,promptText:promptText,responseText:responseText,toolEvents:deduped,commits:byPrompt[pid]||[],totalCostUsd:cost,totalToolCalls:tools,totalInputTokens:inTok,totalOutputTokens:outTok,totalCacheReadTokens:cacheRTok,totalCacheWriteTokens:cacheWTok,totalDurationMs:dur,filesRead:Object.keys(filesR),filesWritten:Object.keys(filesW)};
24331
24331
  }).filter(function(g){return g.promptText||g.toolEvents.length>0||g.responseText;});
24332
24332
  }
24333
24333
 
@@ -24450,6 +24450,7 @@ function renderReplay() {
24450
24450
  h += '<div class="tm">';
24451
24451
  h += '<span class="tmi">Cost <span class="badge orange">' + fmt$4(replay.metrics.totalCostUsd) + '</span></span>';
24452
24452
  h += '<span class="tmi">Tokens <span class="badge cyan">' + replay.metrics.totalInputTokens + ' in / ' + replay.metrics.totalOutputTokens + ' out</span></span>';
24453
+ if (replay.metrics.totalCacheReadTokens > 0 || replay.metrics.totalCacheWriteTokens > 0) h += '<span class="tmi">Cache <span class="badge purple">' + replay.metrics.totalCacheReadTokens + ' read / ' + replay.metrics.totalCacheWriteTokens + ' write</span></span>';
24453
24454
  if (replay.metrics.linesAdded > 0 || replay.metrics.linesRemoved > 0) h += '<span class="tmi">Lines <span class="badge green">+' + replay.metrics.linesAdded + '</span> <span class="badge red">-' + replay.metrics.linesRemoved + '</span></span>';
24454
24455
  if (replay.metrics.modelsUsed.length > 0) h += '<span class="tmi">' + esc(replay.metrics.modelsUsed.join(', ')) + '</span>';
24455
24456
  if (replay.metrics.filesTouched.length > 0) h += '<span class="tmi">' + replay.metrics.filesTouched.length + ' files</span>';
@@ -24776,6 +24777,8 @@ async function fetchSessionReplayFromApi(apiBaseUrl, sessionId) {
24776
24777
  totalCostUsd: typeof metrics["totalCostUsd"] === "number" ? metrics["totalCostUsd"] : 0,
24777
24778
  totalInputTokens: typeof metrics["totalInputTokens"] === "number" ? metrics["totalInputTokens"] : 0,
24778
24779
  totalOutputTokens: typeof metrics["totalOutputTokens"] === "number" ? metrics["totalOutputTokens"] : 0,
24780
+ totalCacheReadTokens: typeof metrics["totalCacheReadTokens"] === "number" ? metrics["totalCacheReadTokens"] : 0,
24781
+ totalCacheWriteTokens: typeof metrics["totalCacheWriteTokens"] === "number" ? metrics["totalCacheWriteTokens"] : 0,
24779
24782
  linesAdded: typeof metrics["linesAdded"] === "number" ? metrics["linesAdded"] : 0,
24780
24783
  linesRemoved: typeof metrics["linesRemoved"] === "number" ? metrics["linesRemoved"] : 0,
24781
24784
  modelsUsed,
@@ -25003,6 +25006,8 @@ CREATE TABLE IF NOT EXISTS agent_events (
25003
25006
  cost_usd REAL,
25004
25007
  input_tokens INTEGER,
25005
25008
  output_tokens INTEGER,
25009
+ cache_read_tokens INTEGER,
25010
+ cache_write_tokens INTEGER,
25006
25011
  api_duration_ms REAL,
25007
25012
  lines_added INTEGER,
25008
25013
  lines_removed INTEGER,
@@ -25028,6 +25033,8 @@ CREATE TABLE IF NOT EXISTS session_traces (
25028
25033
  total_cost_usd REAL NOT NULL DEFAULT 0,
25029
25034
  total_input_tokens INTEGER NOT NULL DEFAULT 0,
25030
25035
  total_output_tokens INTEGER NOT NULL DEFAULT 0,
25036
+ total_cache_read_tokens INTEGER NOT NULL DEFAULT 0,
25037
+ total_cache_write_tokens INTEGER NOT NULL DEFAULT 0,
25031
25038
  lines_added INTEGER NOT NULL DEFAULT 0,
25032
25039
  lines_removed INTEGER NOT NULL DEFAULT 0,
25033
25040
  models_used TEXT NOT NULL DEFAULT '[]',
@@ -25102,6 +25109,8 @@ var SqliteClient = class {
25102
25109
  this.db.pragma("synchronous = NORMAL");
25103
25110
  this.migrateDeduplicateEvents();
25104
25111
  this.db.exec(SCHEMA_SQL);
25112
+ this.migrateCacheTokenColumns();
25113
+ this.migrateRebuildBrokenTraces();
25105
25114
  }
25106
25115
  async insertJsonEachRow(request) {
25107
25116
  if (request.rows.length === 0) return;
@@ -25115,10 +25124,11 @@ var SqliteClient = class {
25115
25124
  INSERT INTO session_traces
25116
25125
  (session_id, version, started_at, ended_at, user_id, git_repo, git_branch,
25117
25126
  prompt_count, tool_call_count, api_call_count, total_cost_usd,
25118
- total_input_tokens, total_output_tokens, lines_added, lines_removed,
25127
+ total_input_tokens, total_output_tokens, total_cache_read_tokens, total_cache_write_tokens,
25128
+ lines_added, lines_removed,
25119
25129
  models_used, tools_used, files_touched, commit_count, updated_at)
25120
25130
  VALUES
25121
- (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
25131
+ (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
25122
25132
  ON CONFLICT(session_id) DO UPDATE SET
25123
25133
  version = excluded.version,
25124
25134
  started_at = excluded.started_at,
@@ -25132,6 +25142,8 @@ var SqliteClient = class {
25132
25142
  total_cost_usd = excluded.total_cost_usd,
25133
25143
  total_input_tokens = excluded.total_input_tokens,
25134
25144
  total_output_tokens = excluded.total_output_tokens,
25145
+ total_cache_read_tokens = excluded.total_cache_read_tokens,
25146
+ total_cache_write_tokens = excluded.total_cache_write_tokens,
25135
25147
  lines_added = excluded.lines_added,
25136
25148
  lines_removed = excluded.lines_removed,
25137
25149
  models_used = excluded.models_used,
@@ -25156,6 +25168,8 @@ var SqliteClient = class {
25156
25168
  row.total_cost_usd,
25157
25169
  row.total_input_tokens,
25158
25170
  row.total_output_tokens,
25171
+ row.total_cache_read_tokens,
25172
+ row.total_cache_write_tokens,
25159
25173
  row.lines_added,
25160
25174
  row.lines_removed,
25161
25175
  toJsonArray(row.models_used),
@@ -25265,6 +25279,8 @@ var SqliteClient = class {
25265
25279
  total_cost_usd: raw["total_cost_usd"],
25266
25280
  total_input_tokens: raw["total_input_tokens"],
25267
25281
  total_output_tokens: raw["total_output_tokens"],
25282
+ total_cache_read_tokens: raw["total_cache_read_tokens"] ?? 0,
25283
+ total_cache_write_tokens: raw["total_cache_write_tokens"] ?? 0,
25268
25284
  lines_added: raw["lines_added"],
25269
25285
  lines_removed: raw["lines_removed"],
25270
25286
  models_used: fromJsonArray(raw["models_used"]),
@@ -25278,7 +25294,7 @@ var SqliteClient = class {
25278
25294
  const rows = this.db.prepare(
25279
25295
  `SELECT event_id, event_type, event_timestamp, session_id, prompt_id,
25280
25296
  tool_success, tool_name, tool_duration_ms, model, cost_usd,
25281
- input_tokens, output_tokens, attributes
25297
+ input_tokens, output_tokens, cache_read_tokens, cache_write_tokens, attributes
25282
25298
  FROM agent_events
25283
25299
  WHERE session_id = ?
25284
25300
  ORDER BY event_timestamp ASC
@@ -25297,6 +25313,8 @@ var SqliteClient = class {
25297
25313
  cost_usd: raw["cost_usd"],
25298
25314
  input_tokens: raw["input_tokens"],
25299
25315
  output_tokens: raw["output_tokens"],
25316
+ cache_read_tokens: raw["cache_read_tokens"] ?? null,
25317
+ cache_write_tokens: raw["cache_write_tokens"] ?? null,
25300
25318
  attributes: fromJsonObject(raw["attributes"])
25301
25319
  }));
25302
25320
  }
@@ -25320,6 +25338,31 @@ var SqliteClient = class {
25320
25338
  close() {
25321
25339
  this.db.close();
25322
25340
  }
25341
+ /**
25342
+ * Migration: add cache_read_tokens / cache_write_tokens columns to existing databases.
25343
+ */
25344
+ migrateCacheTokenColumns() {
25345
+ const addColumnIfMissing = (table, column, definition) => {
25346
+ const cols = this.db.prepare(`PRAGMA table_info('${table}')`).all();
25347
+ if (!cols.some((c) => c.name === column)) {
25348
+ this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);
25349
+ }
25350
+ };
25351
+ const eventsExist = this.db.prepare(
25352
+ "SELECT 1 FROM sqlite_master WHERE type='table' AND name='agent_events'"
25353
+ ).get();
25354
+ if (eventsExist !== void 0) {
25355
+ addColumnIfMissing("agent_events", "cache_read_tokens", "INTEGER");
25356
+ addColumnIfMissing("agent_events", "cache_write_tokens", "INTEGER");
25357
+ }
25358
+ const tracesExist = this.db.prepare(
25359
+ "SELECT 1 FROM sqlite_master WHERE type='table' AND name='session_traces'"
25360
+ ).get();
25361
+ if (tracesExist !== void 0) {
25362
+ addColumnIfMissing("session_traces", "total_cache_read_tokens", "INTEGER NOT NULL DEFAULT 0");
25363
+ addColumnIfMissing("session_traces", "total_cache_write_tokens", "INTEGER NOT NULL DEFAULT 0");
25364
+ }
25365
+ }
25323
25366
  /**
25324
25367
  * Migration: deduplicate agent_events rows from older schemas that lacked a UNIQUE constraint.
25325
25368
  * Runs once — if the old table exists without a unique index, it rebuilds it.
@@ -25375,6 +25418,8 @@ var SqliteClient = class {
25375
25418
  cost_usd REAL,
25376
25419
  input_tokens INTEGER,
25377
25420
  output_tokens INTEGER,
25421
+ cache_read_tokens INTEGER,
25422
+ cache_write_tokens INTEGER,
25378
25423
  api_duration_ms REAL,
25379
25424
  lines_added INTEGER,
25380
25425
  lines_removed INTEGER,
@@ -25395,6 +25440,32 @@ var SqliteClient = class {
25395
25440
  console.log(`[agent-trace] migration complete: ${afterCount.c} events after dedup (removed ${total - afterCount.c} duplicates)`);
25396
25441
  this.rebuildSessionTracesFromEvents();
25397
25442
  }
25443
+ /**
25444
+ * Migration v2: fix databases that ran v0.2.6's broken rebuild (models_used='[]' with data present).
25445
+ * Re-runs the rebuild if session_traces exist but have empty models_used while events have model data.
25446
+ */
25447
+ migrateRebuildBrokenTraces() {
25448
+ const tracesExist = this.db.prepare(
25449
+ "SELECT 1 FROM sqlite_master WHERE type='table' AND name='session_traces'"
25450
+ ).get();
25451
+ if (tracesExist === void 0) {
25452
+ return;
25453
+ }
25454
+ const broken = this.db.prepare(`
25455
+ SELECT 1 FROM session_traces st
25456
+ WHERE st.models_used = '[]'
25457
+ AND EXISTS (
25458
+ SELECT 1 FROM agent_events ae
25459
+ WHERE ae.session_id = st.session_id AND ae.model IS NOT NULL
25460
+ )
25461
+ LIMIT 1
25462
+ `).get();
25463
+ if (broken === void 0) {
25464
+ return;
25465
+ }
25466
+ console.log("[agent-trace] migrating: rebuilding session traces with correct models/tools");
25467
+ this.rebuildSessionTracesFromEvents();
25468
+ }
25398
25469
  /**
25399
25470
  * Rebuild session_traces by aggregating deduplicated agent_events.
25400
25471
  * Called after dedup migration so the dashboard has correct metrics immediately.
@@ -25411,7 +25482,8 @@ var SqliteClient = class {
25411
25482
  INSERT OR REPLACE INTO session_traces
25412
25483
  (session_id, version, started_at, ended_at, user_id, git_repo, git_branch,
25413
25484
  prompt_count, tool_call_count, api_call_count, total_cost_usd,
25414
- total_input_tokens, total_output_tokens, lines_added, lines_removed,
25485
+ total_input_tokens, total_output_tokens, total_cache_read_tokens, total_cache_write_tokens,
25486
+ lines_added, lines_removed,
25415
25487
  models_used, tools_used, files_touched, commit_count, updated_at)
25416
25488
  SELECT
25417
25489
  session_id,
@@ -25427,6 +25499,8 @@ var SqliteClient = class {
25427
25499
  COALESCE(SUM(cost_usd), 0),
25428
25500
  COALESCE(SUM(input_tokens), 0),
25429
25501
  COALESCE(SUM(output_tokens), 0),
25502
+ COALESCE(SUM(cache_read_tokens), 0),
25503
+ COALESCE(SUM(cache_write_tokens), 0),
25430
25504
  COALESCE(SUM(lines_added), 0),
25431
25505
  COALESCE(SUM(lines_removed), 0),
25432
25506
  '[]',
@@ -25437,6 +25511,32 @@ var SqliteClient = class {
25437
25511
  FROM agent_events
25438
25512
  GROUP BY session_id
25439
25513
  `);
25514
+ const sessionIds = this.db.prepare(
25515
+ "SELECT DISTINCT session_id FROM agent_events"
25516
+ ).all();
25517
+ const updateArrays = this.db.prepare(
25518
+ "UPDATE session_traces SET models_used = ?, tools_used = ?, files_touched = ? WHERE session_id = ?"
25519
+ );
25520
+ const transaction = this.db.transaction((ids) => {
25521
+ for (const { session_id } of ids) {
25522
+ const models = this.db.prepare(
25523
+ "SELECT DISTINCT model FROM agent_events WHERE session_id = ? AND model IS NOT NULL"
25524
+ ).all(session_id);
25525
+ const tools = this.db.prepare(
25526
+ "SELECT DISTINCT tool_name FROM agent_events WHERE session_id = ? AND tool_name IS NOT NULL"
25527
+ ).all(session_id);
25528
+ const files = this.db.prepare(
25529
+ "SELECT DISTINCT commit_sha FROM agent_events WHERE session_id = ? AND commit_sha IS NOT NULL"
25530
+ ).all(session_id);
25531
+ updateArrays.run(
25532
+ JSON.stringify(models.map((r) => r.model)),
25533
+ JSON.stringify(tools.map((r) => r.tool_name)),
25534
+ JSON.stringify(files.map((r) => r.commit_sha)),
25535
+ session_id
25536
+ );
25537
+ }
25538
+ });
25539
+ transaction(sessionIds);
25440
25540
  const rebuilt = this.db.prepare("SELECT COUNT(*) as c FROM session_traces").get();
25441
25541
  console.log(`[agent-trace] rebuilt ${rebuilt.c} session traces from deduplicated events`);
25442
25542
  }
@@ -25445,9 +25545,10 @@ var SqliteClient = class {
25445
25545
  INSERT OR IGNORE INTO agent_events
25446
25546
  (event_id, event_type, event_timestamp, session_id, prompt_id, user_id, source, agent_type,
25447
25547
  tool_name, tool_success, tool_duration_ms, model, cost_usd, input_tokens, output_tokens,
25548
+ cache_read_tokens, cache_write_tokens,
25448
25549
  api_duration_ms, lines_added, lines_removed, files_changed, commit_sha, attributes)
25449
25550
  VALUES
25450
- (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
25551
+ (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
25451
25552
  `);
25452
25553
  const transaction = this.db.transaction((eventRows) => {
25453
25554
  for (const row of eventRows) {
@@ -25467,6 +25568,8 @@ var SqliteClient = class {
25467
25568
  row.cost_usd,
25468
25569
  row.input_tokens,
25469
25570
  row.output_tokens,
25571
+ row.cache_read_tokens,
25572
+ row.cache_write_tokens,
25470
25573
  row.api_duration_ms,
25471
25574
  row.lines_added,
25472
25575
  row.lines_removed,
@@ -25681,6 +25784,8 @@ function toClickHouseAgentEventRow(event) {
25681
25784
  cost_usd: pickNumber(payload, "cost_usd", "costUsd") ?? null,
25682
25785
  input_tokens: pickNumber(payload, "input_tokens", "inputTokens") ?? null,
25683
25786
  output_tokens: pickNumber(payload, "output_tokens", "outputTokens") ?? null,
25787
+ cache_read_tokens: pickNumber(payload, "cache_read_tokens", "cacheReadTokens") ?? pickNumber(payload, "cache_read_input_tokens", "cacheReadInputTokens") ?? null,
25788
+ cache_write_tokens: pickNumber(payload, "cache_write_tokens", "cacheWriteTokens") ?? pickNumber(payload, "cache_creation_input_tokens", "cacheCreationInputTokens") ?? null,
25684
25789
  api_duration_ms: pickNumber(payload, "api_duration_ms", "apiDurationMs") ?? null,
25685
25790
  lines_added: pickNumber(payload, "lines_added", "linesAdded") ?? null,
25686
25791
  lines_removed: pickNumber(payload, "lines_removed", "linesRemoved") ?? null,
@@ -25770,6 +25875,8 @@ function toClickHouseSessionTraceRow(trace, version, updatedAt) {
25770
25875
  total_cost_usd: Number.isFinite(trace.metrics.totalCostUsd) ? trace.metrics.totalCostUsd : 0,
25771
25876
  total_input_tokens: toNonNegativeInteger(trace.metrics.totalInputTokens),
25772
25877
  total_output_tokens: toNonNegativeInteger(trace.metrics.totalOutputTokens),
25878
+ total_cache_read_tokens: toNonNegativeInteger(trace.metrics.totalCacheReadTokens),
25879
+ total_cache_write_tokens: toNonNegativeInteger(trace.metrics.totalCacheWriteTokens),
25773
25880
  lines_added: Math.trunc(trace.metrics.linesAdded),
25774
25881
  lines_removed: Math.trunc(trace.metrics.linesRemoved),
25775
25882
  models_used: toUniqueStringArray(trace.metrics.modelsUsed),
@@ -25921,6 +26028,8 @@ var SESSION_TRACE_SELECT_COLUMNS = [
25921
26028
  "total_cost_usd",
25922
26029
  "total_input_tokens",
25923
26030
  "total_output_tokens",
26031
+ "total_cache_read_tokens",
26032
+ "total_cache_write_tokens",
25924
26033
  "lines_added",
25925
26034
  "lines_removed",
25926
26035
  "models_used",
@@ -25992,6 +26101,8 @@ function toAgentSessionTraceFromClickHouseRow(row) {
25992
26101
  totalCostUsd: Number.isFinite(row.total_cost_usd) ? row.total_cost_usd : 0,
25993
26102
  totalInputTokens: toNonNegativeInteger3(row.total_input_tokens),
25994
26103
  totalOutputTokens: toNonNegativeInteger3(row.total_output_tokens),
26104
+ totalCacheReadTokens: toNonNegativeInteger3(row.total_cache_read_tokens),
26105
+ totalCacheWriteTokens: toNonNegativeInteger3(row.total_cache_write_tokens),
25995
26106
  linesAdded: Math.trunc(row.lines_added),
25996
26107
  linesRemoved: Math.trunc(row.lines_removed),
25997
26108
  filesTouched: toUniqueStrings(row.files_touched),
@@ -26019,6 +26130,8 @@ var EVENT_SELECT_COLUMNS = [
26019
26130
  "cost_usd",
26020
26131
  "input_tokens",
26021
26132
  "output_tokens",
26133
+ "cache_read_tokens",
26134
+ "cache_write_tokens",
26022
26135
  "attributes"
26023
26136
  ].join(", ");
26024
26137
  function toNumber(value) {
@@ -26067,6 +26180,8 @@ function toTimelineEventFromClickHouseRow(row) {
26067
26180
  const costUsd = toNumber(row.cost_usd);
26068
26181
  const inputTokens = toNumber(row.input_tokens);
26069
26182
  const outputTokens = toNumber(row.output_tokens);
26183
+ const cacheReadTokens = toNumber(row.cache_read_tokens);
26184
+ const cacheWriteTokens = toNumber(row.cache_write_tokens);
26070
26185
  const status2 = readStatus(row);
26071
26186
  const details = {};
26072
26187
  if (row.tool_name !== null) {
@@ -26133,7 +26248,9 @@ function toTimelineEventFromClickHouseRow(row) {
26133
26248
  ...inputTokens !== void 0 && outputTokens !== void 0 ? {
26134
26249
  tokens: {
26135
26250
  input: inputTokens,
26136
- output: outputTokens
26251
+ output: outputTokens,
26252
+ ...cacheReadTokens !== void 0 ? { cacheRead: cacheReadTokens } : {},
26253
+ ...cacheWriteTokens !== void 0 ? { cacheWrite: cacheWriteTokens } : {}
26137
26254
  }
26138
26255
  } : {},
26139
26256
  ...Object.keys(details).length > 0 ? { details } : {}
@@ -28305,6 +28422,8 @@ function toTimelineEvent(envelope) {
28305
28422
  const payload = asRecord5(envelope.payload);
28306
28423
  const inputTokens = readNumber3(payload, ["input_tokens", "inputTokens"]);
28307
28424
  const outputTokens = readNumber3(payload, ["output_tokens", "outputTokens"]);
28425
+ const cacheReadTokens = readNumber3(payload, ["cache_read_tokens", "cacheReadTokens", "cache_read_input_tokens"]);
28426
+ const cacheWriteTokens = readNumber3(payload, ["cache_write_tokens", "cacheWriteTokens", "cache_creation_input_tokens"]);
28308
28427
  const costUsd = computeEventCost(payload);
28309
28428
  const details = buildNormalizedDetails(envelope.payload);
28310
28429
  return {
@@ -28316,7 +28435,9 @@ function toTimelineEvent(envelope) {
28316
28435
  ...inputTokens !== void 0 && outputTokens !== void 0 ? {
28317
28436
  tokens: {
28318
28437
  input: inputTokens,
28319
- output: outputTokens
28438
+ output: outputTokens,
28439
+ ...cacheReadTokens !== void 0 ? { cacheRead: cacheReadTokens } : {},
28440
+ ...cacheWriteTokens !== void 0 ? { cacheWrite: cacheWriteTokens } : {}
28320
28441
  }
28321
28442
  } : {},
28322
28443
  ...details !== void 0 ? { details } : {}
@@ -28349,6 +28470,8 @@ function toBaseTrace(envelope) {
28349
28470
  totalCostUsd: 0,
28350
28471
  totalInputTokens: 0,
28351
28472
  totalOutputTokens: 0,
28473
+ totalCacheReadTokens: 0,
28474
+ totalCacheWriteTokens: 0,
28352
28475
  linesAdded: 0,
28353
28476
  linesRemoved: 0,
28354
28477
  filesTouched: [],
@@ -28390,6 +28513,8 @@ function toUpdatedTrace(existing, envelope) {
28390
28513
  const cost = computeEventCost(payload) ?? 0;
28391
28514
  const inputTokens = readNumber3(payload, ["input_tokens", "inputTokens"]) ?? 0;
28392
28515
  const outputTokens = readNumber3(payload, ["output_tokens", "outputTokens"]) ?? 0;
28516
+ const cacheReadTokens = readNumber3(payload, ["cache_read_tokens", "cacheReadTokens", "cache_read_input_tokens"]) ?? 0;
28517
+ const cacheWriteTokens = readNumber3(payload, ["cache_write_tokens", "cacheWriteTokens", "cache_creation_input_tokens"]) ?? 0;
28393
28518
  const linesAdded = readNumber3(payload, ["lines_added", "linesAdded"]) ?? 0;
28394
28519
  const linesRemoved = readNumber3(payload, ["lines_removed", "linesRemoved"]) ?? 0;
28395
28520
  const model = readString4(payload, ["model"]);
@@ -28443,6 +28568,8 @@ function toUpdatedTrace(existing, envelope) {
28443
28568
  totalCostUsd: Number((existing.metrics.totalCostUsd + cost).toFixed(6)),
28444
28569
  totalInputTokens: existing.metrics.totalInputTokens + inputTokens,
28445
28570
  totalOutputTokens: existing.metrics.totalOutputTokens + outputTokens,
28571
+ totalCacheReadTokens: existing.metrics.totalCacheReadTokens + cacheReadTokens,
28572
+ totalCacheWriteTokens: existing.metrics.totalCacheWriteTokens + cacheWriteTokens,
28446
28573
  linesAdded: existing.metrics.linesAdded + linesAdded,
28447
28574
  linesRemoved: existing.metrics.linesRemoved + linesRemoved,
28448
28575
  filesTouched,
@@ -28489,11 +28616,23 @@ async function close2(server) {
28489
28616
  });
28490
28617
  });
28491
28618
  }
28619
+ function enrichEnvelopeWithCost(event) {
28620
+ const payload = event.payload;
28621
+ if (typeof payload["cost_usd"] === "number" && payload["cost_usd"] > 0) {
28622
+ return event;
28623
+ }
28624
+ const cost = computeEventCost(payload);
28625
+ if (cost === void 0 || cost <= 0) {
28626
+ return event;
28627
+ }
28628
+ return { ...event, payload: { ...payload, cost_usd: cost } };
28629
+ }
28492
28630
  async function projectAndPersistEvent(event, sessionRepository, persistence) {
28493
28631
  const current = sessionRepository.getBySessionId(event.sessionId);
28494
28632
  const projected = projectEnvelopeToTrace(current, event);
28495
28633
  sessionRepository.upsert(projected);
28496
- await persistence.persistAcceptedEvent(event, projected);
28634
+ const enriched = enrichEnvelopeWithCost(event);
28635
+ await persistence.persistAcceptedEvent(enriched, projected);
28497
28636
  }
28498
28637
  function createRuntimeOtelSink(runtime) {
28499
28638
  return runtime.collectorService.otelSink;
@@ -28695,7 +28834,9 @@ function hydrateFromSqlite(runtime, sqlite, limit, eventLimit) {
28695
28834
  const recalculated = calculateCostUsd({
28696
28835
  model,
28697
28836
  inputTokens: hydratedTrace.metrics.totalInputTokens,
28698
- outputTokens: hydratedTrace.metrics.totalOutputTokens
28837
+ outputTokens: hydratedTrace.metrics.totalOutputTokens,
28838
+ cacheReadTokens: hydratedTrace.metrics.totalCacheReadTokens,
28839
+ cacheWriteTokens: hydratedTrace.metrics.totalCacheWriteTokens
28699
28840
  });
28700
28841
  if (recalculated > 0) {
28701
28842
  hydratedTrace = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-trace",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
4
4
  "description": "Self-hosted observability for AI coding agents. One command, zero config.",
5
5
  "license": "Apache-2.0",
6
6
  "bin": {