@alook/daemon 0.1.26 → 0.1.28

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.
package/dist/cli/index.js CHANGED
@@ -16646,6 +16646,7 @@ var communityMachine = sqliteTable("community_machine", {
16646
16646
  arch: text("arch").notNull().default(""),
16647
16647
  osRelease: text("os_release").notNull().default(""),
16648
16648
  daemonVersion: text("daemon_version").notNull().default(""),
16649
+ timeZone: text("time_zone"),
16649
16650
  metadata: text("metadata"),
16650
16651
  availableRuntimes: text("available_runtimes", { mode: "json" }).$type().notNull().default([]),
16651
16652
  status: text("status").notNull().default("offline"),
@@ -17639,6 +17640,7 @@ var HostReadyMessageSchema = exports_external.object({
17639
17640
  arch: exports_external.string().optional(),
17640
17641
  osRelease: exports_external.string().optional(),
17641
17642
  daemonVersion: exports_external.string().optional(),
17643
+ timeZone: exports_external.string().min(1).max(128).optional(),
17642
17644
  providerQuotas: exports_external.array(ProviderQuotaSnapshotSchema).max(2).optional().default([])
17643
17645
  });
17644
17646
  var CommunityDaemonReadySchema = exports_external.object({
@@ -17661,6 +17663,8 @@ var AgentActivityMessageSchema = exports_external.object({
17661
17663
  type: exports_external.literal("agent_activity"),
17662
17664
  agentId: exports_external.string(),
17663
17665
  state: exports_external.enum(["idle", "starting", "running", "stopping"]),
17666
+ usageTimeZone: exports_external.string().min(1).max(128).optional(),
17667
+ usageDay: exports_external.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
17664
17668
  dailyUsage: exports_external.array(DailyUsageSnapshotSchema).max(7).optional(),
17665
17669
  quota: ProviderQuotaSnapshotSchema.optional()
17666
17670
  });
@@ -17926,6 +17930,42 @@ var BotAuditEventAckFrameSchema = exports_external.strictObject({
17926
17930
  type: exports_external.literal("bot_audit_event_ack"),
17927
17931
  eventId: exports_external.string().min(1).max(128)
17928
17932
  });
17933
+ // ../shared/src/utils/day-key.ts
17934
+ function utcDayKey(now) {
17935
+ const d = now instanceof Date ? now : new Date(now);
17936
+ return d.toISOString().slice(0, 10);
17937
+ }
17938
+ function calendarDayKeyDaysAgo(day, days) {
17939
+ const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(day);
17940
+ if (!match)
17941
+ throw new RangeError("invalid calendar day key");
17942
+ const year = Number(match[1]);
17943
+ const month = Number(match[2]);
17944
+ const date5 = Number(match[3]);
17945
+ const parsed = new Date(Date.UTC(year, month - 1, date5));
17946
+ if (parsed.getUTCFullYear() !== year || parsed.getUTCMonth() !== month - 1 || parsed.getUTCDate() !== date5) {
17947
+ throw new RangeError("invalid calendar day key");
17948
+ }
17949
+ parsed.setUTCDate(parsed.getUTCDate() - days);
17950
+ return utcDayKey(parsed);
17951
+ }
17952
+ function dayKeyInTimeZone(now, timeZone) {
17953
+ const date5 = now instanceof Date ? now : new Date(now);
17954
+ const parts = new Intl.DateTimeFormat("en-US", {
17955
+ timeZone,
17956
+ year: "numeric",
17957
+ month: "2-digit",
17958
+ day: "2-digit"
17959
+ }).formatToParts(date5);
17960
+ const values = new Map(parts.map((part) => [part.type, part.value]));
17961
+ const year = values.get("year");
17962
+ const month = values.get("month");
17963
+ const day = values.get("day");
17964
+ if (!year || !month || !day)
17965
+ throw new RangeError("unable to format calendar day key");
17966
+ return `${year}-${month}-${day}`;
17967
+ }
17968
+
17929
17969
  // ../shared/src/db/community-schema.ts
17930
17970
  var exports_community_schema = {};
17931
17971
  __export(exports_community_schema, {
@@ -20522,6 +20562,66 @@ function buildClaudeArgs(config2) {
20522
20562
  return args;
20523
20563
  }
20524
20564
 
20565
+ // agent-driver/dist/internal/token-usage.js
20566
+ function validIdentityPart(value) {
20567
+ return value.trim().length > 0 && Buffer.byteLength(value, "utf8") <= 512;
20568
+ }
20569
+ function identityKey(identity) {
20570
+ if (!validIdentityPart(identity.runtime) || !validIdentityPart(identity.backendSessionId) || !validIdentityPart(identity.providerRecordId))
20571
+ return null;
20572
+ return JSON.stringify([identity.runtime, identity.backendSessionId, identity.providerRecordId]);
20573
+ }
20574
+ function metric(value) {
20575
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : null;
20576
+ }
20577
+ function cacheMetric(read, write) {
20578
+ const present = [read, write].filter((value) => value !== undefined);
20579
+ if (present.length === 0)
20580
+ return null;
20581
+ const metrics = present.map(metric);
20582
+ if (metrics.some((value) => value === null))
20583
+ return null;
20584
+ const total = metrics.reduce((sum, value) => sum + value, 0);
20585
+ return Number.isSafeInteger(total) ? total : null;
20586
+ }
20587
+
20588
+ class SettledUsageProjector {
20589
+ active = new Set;
20590
+ project(record2) {
20591
+ const key = identityKey(record2);
20592
+ if (!key || this.active.has(key))
20593
+ return null;
20594
+ this.active.add(key);
20595
+ const cache = cacheMetric(record2.cacheRead, record2.cacheWrite);
20596
+ const rawInput = metric(record2.input);
20597
+ const input = record2.inputIncludesCache ? rawInput !== null && cache !== null && cache <= rawInput ? rawInput - cache : null : rawInput;
20598
+ const rawOutput = metric(record2.output);
20599
+ const reasoning = metric(record2.reasoning);
20600
+ const output = record2.outputIncludesReasoning ? rawOutput : rawOutput !== null && reasoning !== null && Number.isSafeInteger(rawOutput + reasoning) ? rawOutput + reasoning : null;
20601
+ if (input === null && output === null && cache === null) {
20602
+ this.active.delete(key);
20603
+ return null;
20604
+ }
20605
+ return {
20606
+ kind: "telemetry",
20607
+ name: "token_usage",
20608
+ source: record2.source,
20609
+ usage: { input, output, cache }
20610
+ };
20611
+ }
20612
+ release(identity) {
20613
+ const key = identityKey(identity);
20614
+ if (key)
20615
+ this.active.delete(key);
20616
+ }
20617
+ reset() {
20618
+ this.active.clear();
20619
+ }
20620
+ get activeCount() {
20621
+ return this.active.size;
20622
+ }
20623
+ }
20624
+
20525
20625
  // agent-driver/dist/internal/utils.js
20526
20626
  import { randomUUID as randomUUID3 } from "crypto";
20527
20627
  function jsonRpcRequest(method, params, id) {
@@ -20541,9 +20641,13 @@ var API_ERROR_RE = /API Error:.*(?:Connection error|\b[45]\d{2}\b)/i;
20541
20641
  class ClaudeEventNormalizer {
20542
20642
  turnProtocol;
20543
20643
  currentSession = null;
20644
+ usageProjector = new SettledUsageProjector;
20544
20645
  constructor(turnProtocol) {
20545
20646
  this.turnProtocol = turnProtocol;
20546
20647
  }
20648
+ beginTurn() {
20649
+ this.usageProjector.reset();
20650
+ }
20547
20651
  get currentSessionId() {
20548
20652
  return this.currentSession;
20549
20653
  }
@@ -20635,7 +20739,7 @@ class ClaudeEventNormalizer {
20635
20739
  const turnOwner = rawOwner ? this.turnProtocol?.claimResult(rawOwner) ?? (this.turnProtocol ? null : `claude:${rawOwner}`) : null;
20636
20740
  if (this.turnProtocol && !turnOwner)
20637
20741
  return;
20638
- const usage = this.buildUsageTelemetry(event);
20742
+ const usage = this.buildUsageTelemetry(event, rawOwner);
20639
20743
  if (usage)
20640
20744
  out.push(usage);
20641
20745
  if (event.is_error || event.subtype === "error_during_execution") {
@@ -20650,23 +20754,26 @@ class ClaudeEventNormalizer {
20650
20754
  acceptsTurnWork() {
20651
20755
  return this.turnProtocol?.acceptsTurnWork() ?? true;
20652
20756
  }
20653
- buildUsageTelemetry(event) {
20757
+ buildUsageTelemetry(event, rootRequestId) {
20654
20758
  const u = event?.usage;
20655
20759
  if (!u)
20656
20760
  return null;
20657
- const metric = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : null;
20658
- const cacheParts = [u.cache_read_input_tokens, u.cache_creation_input_tokens].filter((value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0);
20659
- const cache = cacheParts.length > 0 && Number.isSafeInteger(cacheParts.reduce((sum, value) => sum + value, 0)) ? cacheParts.reduce((sum, value) => sum + value, 0) : null;
20660
- return {
20661
- kind: "telemetry",
20662
- name: "token_usage",
20761
+ const backendSessionId = event.session_id ?? this.currentSession;
20762
+ if (typeof backendSessionId !== "string" || !backendSessionId)
20763
+ return null;
20764
+ const providerRecordId = rootRequestId ?? (typeof event.request_id === "string" ? event.request_id : "invocation-result");
20765
+ return this.usageProjector.project({
20766
+ runtime: "claude",
20767
+ backendSessionId,
20768
+ providerRecordId,
20663
20769
  source: "claude_result_usage",
20664
- usage: {
20665
- input: metric(u.input_tokens),
20666
- output: metric(u.output_tokens),
20667
- cache
20668
- }
20669
- };
20770
+ input: u.input_tokens,
20771
+ output: u.output_tokens,
20772
+ cacheRead: u.cache_read_input_tokens,
20773
+ cacheWrite: u.cache_creation_input_tokens,
20774
+ inputIncludesCache: false,
20775
+ outputIncludesReasoning: true
20776
+ });
20670
20777
  }
20671
20778
  }
20672
20779
 
@@ -20869,7 +20976,9 @@ class ClaudeDriver {
20869
20976
  turnProtocol = new ClaudeTurnProtocol;
20870
20977
  eventNormalizer = new ClaudeEventNormalizer(this.turnProtocol);
20871
20978
  beginTurn() {
20872
- return this.turnProtocol.beginTurn();
20979
+ const receipt = this.turnProtocol.beginTurn();
20980
+ this.eventNormalizer.beginTurn();
20981
+ return receipt;
20873
20982
  }
20874
20983
  probe(command) {
20875
20984
  const explicit = command?.trim();
@@ -20923,14 +21032,6 @@ class ClaudeDriver {
20923
21032
  }
20924
21033
 
20925
21034
  // agent-driver/dist/adapters/codex/telemetry.js
20926
- function metric(value) {
20927
- return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : null;
20928
- }
20929
- function nonCachedInput(input, cached2) {
20930
- if (typeof input !== "number" || !Number.isSafeInteger(input) || input < 0 || typeof cached2 !== "number" || !Number.isSafeInteger(cached2) || cached2 < 0 || cached2 > input)
20931
- return null;
20932
- return input - cached2;
20933
- }
20934
21035
  function canonicalId(value, fallback) {
20935
21036
  return typeof value === "string" && value.length > 0 && new TextEncoder().encode(value).length <= 64 ? value : fallback;
20936
21037
  }
@@ -21024,28 +21125,24 @@ function mapCodexQuotaSnapshots(snapshots, sourceEpoch) {
21024
21125
  }
21025
21126
  };
21026
21127
  }
21027
- function mapCodexTelemetry(method, params, sourceEpoch) {
21028
- if (method === "thread/tokenUsage/updated") {
21029
- const u = params?.tokenUsage?.last ?? params?.token_usage?.last;
21030
- if (!u)
21031
- return [];
21032
- const input = u.inputTokens ?? u.input_tokens;
21033
- const cached2 = u.cachedInputTokens ?? u.cached_input_tokens;
21034
- return [{
21035
- kind: "telemetry",
21036
- name: "token_usage",
21037
- source: "codex_thread_token_usage_updated",
21038
- usage: {
21039
- input: nonCachedInput(input, cached2),
21040
- output: metric(u.outputTokens ?? u.output_tokens),
21041
- cache: metric(cached2)
21042
- }
21043
- }];
21044
- }
21045
- if (method === "account/rateLimits/updated") {
21046
- return [mapCodexQuotaSnapshots([params?.rateLimits ?? params ?? {}], sourceEpoch)];
21047
- }
21048
- return [];
21128
+ function mapCodexSettledUsage(params, projector) {
21129
+ const backendSessionId = params?.threadId ?? params?.thread_id;
21130
+ const providerRecordId = params?.responseId ?? params?.response_id;
21131
+ const usage = params?.usage;
21132
+ if (typeof backendSessionId !== "string" || typeof providerRecordId !== "string" || !usage)
21133
+ return null;
21134
+ return projector.project({
21135
+ runtime: "codex",
21136
+ backendSessionId,
21137
+ providerRecordId,
21138
+ source: "codex_raw_response_completed",
21139
+ input: usage.inputTokens ?? usage.input_tokens,
21140
+ output: usage.outputTokens ?? usage.output_tokens,
21141
+ cacheRead: usage.cachedInputTokens ?? usage.cached_input_tokens,
21142
+ cacheWrite: usage.cacheWriteInputTokens ?? usage.cache_write_input_tokens,
21143
+ inputIncludesCache: true,
21144
+ outputIncludesReasoning: true
21145
+ });
21049
21146
  }
21050
21147
 
21051
21148
  // agent-driver/dist/adapters/codex/normalizer.js
@@ -21097,7 +21194,8 @@ class CodexEventNormalizer {
21097
21194
  rateLimitSnapshots = new Map;
21098
21195
  quotaSnapshotInitialized = false;
21099
21196
  quotaSourceGeneration = codexQuotaSourceGeneration;
21100
- pendingTurnUsage = null;
21197
+ usageProjector = new SettledUsageProjector;
21198
+ usageRecordsBySessionAndTurn = new Map;
21101
21199
  threadId = null;
21102
21200
  turnId = null;
21103
21201
  terminalTurn = null;
@@ -21170,7 +21268,8 @@ class CodexEventNormalizer {
21170
21268
  if (threadId !== this.threadId) {
21171
21269
  this.turnId = null;
21172
21270
  this.terminalTurn = null;
21173
- this.pendingTurnUsage = null;
21271
+ this.usageProjector.reset();
21272
+ this.usageRecordsBySessionAndTurn.clear();
21174
21273
  }
21175
21274
  this.threadId = threadId;
21176
21275
  }
@@ -21219,6 +21318,22 @@ class CodexEventNormalizer {
21219
21318
  }
21220
21319
  handleNotification(method, params) {
21221
21320
  const notificationThreadId = typeof params?.threadId === "string" ? params.threadId : null;
21321
+ if (method === "rawResponse/completed")
21322
+ return this.handleSettledUsage(params);
21323
+ if (method === "turn/completed" && notificationThreadId !== null && notificationThreadId !== this.threadId) {
21324
+ const turnId = this.notificationTurnId(params);
21325
+ if (turnId)
21326
+ this.releaseUsageForTurn(notificationThreadId, turnId);
21327
+ return [];
21328
+ }
21329
+ if (method === "item/completed" && notificationThreadId !== null && notificationThreadId !== this.threadId) {
21330
+ const turnId = this.notificationTurnId(params);
21331
+ const itemType = params?.item?.type ?? params?.type;
21332
+ if (turnId && itemType === "contextCompaction") {
21333
+ this.releaseUsageForTurn(notificationThreadId, turnId);
21334
+ }
21335
+ return [];
21336
+ }
21222
21337
  if (this.threadId !== null && notificationThreadId !== null && notificationThreadId !== this.threadId)
21223
21338
  return [];
21224
21339
  if (this.isRootWorkNotification(method) && !this.acceptRootWork(params))
@@ -21231,7 +21346,6 @@ class CodexEventNormalizer {
21231
21346
  return [];
21232
21347
  this.turnId = params.turn.id;
21233
21348
  this.terminalTurn = null;
21234
- this.pendingTurnUsage = null;
21235
21349
  return [
21236
21350
  {
21237
21351
  kind: "turn_owner",
@@ -21247,7 +21361,7 @@ class CodexEventNormalizer {
21247
21361
  case "item/started":
21248
21362
  return this.handleItemStarted(params);
21249
21363
  case "item/completed":
21250
- return this.handleItemCompleted(params);
21364
+ return this.handleItemCompletedAndReleaseUsage(params);
21251
21365
  case "rawResponseItem/completed":
21252
21366
  return [{ kind: "internal_progress", source: "codex_raw_item", itemType: "rawResponseItem" }];
21253
21367
  case "configWarning":
@@ -21260,30 +21374,24 @@ class CodexEventNormalizer {
21260
21374
  case "turn/completed":
21261
21375
  if (!this.acceptRootTerminal(params))
21262
21376
  return [];
21263
- const usage = this.pendingTurnUsage;
21264
- this.pendingTurnUsage = null;
21377
+ this.releaseUsageForTurn(params.threadId, params.turn.id);
21265
21378
  if (params.turn.status === "failed") {
21266
21379
  return [
21267
- ...usage ? [usage] : [],
21268
21380
  { kind: "error", message: "Codex turn failed" },
21269
21381
  { kind: "turn_end", sessionId: this.threadId ?? undefined, turnOwner: this.turnReceipt(params.threadId, params.turn.id) }
21270
21382
  ];
21271
21383
  }
21272
21384
  if (params.turn.status === "interrupted") {
21273
- return [...usage ? [usage] : [], { kind: "error", message: "Codex turn interrupted" }, { kind: "turn_end", sessionId: this.threadId ?? undefined, turnOwner: this.turnReceipt(params.threadId, params.turn.id) }];
21385
+ return [{ kind: "error", message: "Codex turn interrupted" }, { kind: "turn_end", sessionId: this.threadId ?? undefined, turnOwner: this.turnReceipt(params.threadId, params.turn.id) }];
21274
21386
  }
21275
- return [...usage ? [usage] : [], { kind: "turn_end", sessionId: this.threadId ?? undefined, turnOwner: this.turnReceipt(params.threadId, params.turn.id) }];
21387
+ return [{ kind: "turn_end", sessionId: this.threadId ?? undefined, turnOwner: this.turnReceipt(params.threadId, params.turn.id) }];
21276
21388
  case "error":
21277
21389
  if (params?.willRetry === true) {
21278
21390
  return [{ kind: "runtime_recovery", stage: "retrying", source: "codex_stream" }];
21279
21391
  }
21280
21392
  return [{ kind: "error", message: params?.error?.message ?? params?.message ?? "Codex error" }];
21281
- case "thread/tokenUsage/updated": {
21282
- const usage2 = mapCodexTelemetry(method, params, codexQuotaSourceEpoch)[0];
21283
- if (usage2)
21284
- this.pendingTurnUsage = usage2;
21393
+ case "thread/tokenUsage/updated":
21285
21394
  return [];
21286
- }
21287
21395
  case "account/rateLimits/updated":
21288
21396
  return this.mergeQuotaSnapshots(params);
21289
21397
  case "account/updated":
@@ -21294,6 +21402,42 @@ class CodexEventNormalizer {
21294
21402
  return [];
21295
21403
  }
21296
21404
  }
21405
+ handleSettledUsage(params) {
21406
+ const notificationTurnId = this.notificationTurnId(params);
21407
+ if (this.turnId === null && this.terminalTurn?.state === "closed" && notificationTurnId === this.terminalTurn.turnId && params?.threadId === this.terminalTurn.threadId)
21408
+ return [];
21409
+ const backendSessionId = params?.threadId ?? params?.thread_id;
21410
+ const providerRecordId = params?.responseId ?? params?.response_id;
21411
+ if (!notificationTurnId || typeof backendSessionId !== "string" || typeof providerRecordId !== "string")
21412
+ return [];
21413
+ const usage = mapCodexSettledUsage(params, this.usageProjector);
21414
+ if (!usage)
21415
+ return [];
21416
+ const recordsByTurn = this.usageRecordsBySessionAndTurn.get(backendSessionId) ?? new Map;
21417
+ const recordIds = recordsByTurn.get(notificationTurnId) ?? new Set;
21418
+ recordIds.add(providerRecordId);
21419
+ recordsByTurn.set(notificationTurnId, recordIds);
21420
+ this.usageRecordsBySessionAndTurn.set(backendSessionId, recordsByTurn);
21421
+ return [usage];
21422
+ }
21423
+ releaseUsageForTurn(backendSessionId, turnId) {
21424
+ const recordsByTurn = this.usageRecordsBySessionAndTurn.get(backendSessionId);
21425
+ for (const providerRecordId of recordsByTurn?.get(turnId) ?? []) {
21426
+ this.usageProjector.release({ runtime: "codex", backendSessionId, providerRecordId });
21427
+ }
21428
+ recordsByTurn?.delete(turnId);
21429
+ if (recordsByTurn?.size === 0)
21430
+ this.usageRecordsBySessionAndTurn.delete(backendSessionId);
21431
+ }
21432
+ handleItemCompletedAndReleaseUsage(params) {
21433
+ const events = this.handleItemCompleted(params);
21434
+ const turnId = this.notificationTurnId(params);
21435
+ const itemType = params?.item?.type ?? params?.type;
21436
+ if (turnId && itemType === "contextCompaction" && turnId !== this.turnId && typeof params?.threadId === "string") {
21437
+ this.releaseUsageForTurn(params.threadId, turnId);
21438
+ }
21439
+ return events;
21440
+ }
21297
21441
  isRootWorkNotification(method) {
21298
21442
  return method === "item/reasoning/textDelta" || method === "item/reasoning/summaryTextDelta" || method === "item/agentMessage/delta" || method === "item/started" || method === "item/completed" || method === "rawResponseItem/completed";
21299
21443
  }
@@ -22838,6 +22982,7 @@ class OpenCodeServiceLane {
22838
22982
  lastDurableSeq = 0;
22839
22983
  durableSeqById = new Map;
22840
22984
  durableIdBySeq = new Map;
22985
+ usageProjector = new SettledUsageProjector;
22841
22986
  toolNames = new Map;
22842
22987
  handledPermissions = new Set;
22843
22988
  permissionFlights = new Map;
@@ -23378,7 +23523,8 @@ class OpenCodeServiceLane {
23378
23523
  const event = record4(value);
23379
23524
  const durable = record4(event?.durable);
23380
23525
  const data = record4(event?.data);
23381
- if (!event || typeof event.id !== "string" || typeof event.type !== "string" || !durable || durable.aggregateID !== this.sessionId || !Number.isInteger(durable.seq) || Number(durable.seq) < 0 || data?.sessionID !== this.sessionId) {
23526
+ const backendSessionId = this.sessionId;
23527
+ if (!event || !backendSessionId || typeof event.id !== "string" || typeof event.type !== "string" || !durable || durable.aggregateID !== this.sessionId || !Number.isInteger(durable.seq) || Number(durable.seq) < 0 || data?.sessionID !== this.sessionId) {
23382
23528
  throw new OpenCodeProtocolError("OpenCode session stream emitted an invalid durable event");
23383
23529
  }
23384
23530
  const seq = Number(durable.seq);
@@ -23468,21 +23614,27 @@ class OpenCodeServiceLane {
23468
23614
  });
23469
23615
  }
23470
23616
  const tokens = record4(data.tokens);
23471
- if (tokens && data.finish !== "tool-calls") {
23617
+ if (tokens) {
23472
23618
  const cache = record4(tokens.cache);
23473
- const metric2 = (value2) => typeof value2 === "number" && Number.isSafeInteger(value2) && value2 >= 0 ? value2 : null;
23474
- const cacheParts = [cache?.read, cache?.write].filter((value2) => typeof value2 === "number" && Number.isSafeInteger(value2) && value2 >= 0);
23475
- const cacheTotal = cacheParts.reduce((sum, value2) => sum + value2, 0);
23476
- this.events.emit("runtime_event", {
23477
- kind: "telemetry",
23478
- name: "token_usage",
23619
+ const identity = {
23620
+ runtime: "opencode",
23621
+ backendSessionId,
23622
+ providerRecordId: event.id
23623
+ };
23624
+ const usage = this.usageProjector.project({
23625
+ ...identity,
23479
23626
  source: "opencode.v2",
23480
- usage: {
23481
- input: metric2(tokens.input),
23482
- output: metric2(tokens.output),
23483
- cache: cacheParts.length > 0 && Number.isSafeInteger(cacheTotal) ? cacheTotal : null
23484
- }
23627
+ input: tokens.input,
23628
+ output: tokens.output,
23629
+ reasoning: tokens.reasoning,
23630
+ cacheRead: cache?.read,
23631
+ cacheWrite: cache?.write,
23632
+ inputIncludesCache: false,
23633
+ outputIncludesReasoning: false
23485
23634
  });
23635
+ if (usage)
23636
+ this.events.emit("runtime_event", usage);
23637
+ this.usageProjector.release(identity);
23486
23638
  }
23487
23639
  break;
23488
23640
  }
@@ -24201,6 +24353,11 @@ function readPiSdkVersion() {
24201
24353
  } catch {}
24202
24354
  return resolvePiSdkVersionFromPath();
24203
24355
  }
24356
+ function piUsageState(state) {
24357
+ state.usageProjector ??= new SettledUsageProjector;
24358
+ state.pendingUsageRecordIds ??= new Set;
24359
+ return { projector: state.usageProjector, pending: state.pendingUsageRecordIds };
24360
+ }
24204
24361
  function mapPiSdkEvent(event, sessionId, state) {
24205
24362
  if (event?.type === "message_update") {
24206
24363
  const d = event.assistantMessageEvent ?? {};
@@ -24221,6 +24378,35 @@ function mapPiSdkEvent(event, sessionId, state) {
24221
24378
  }
24222
24379
  }
24223
24380
  switch (event?.type) {
24381
+ case "message_end": {
24382
+ const message2 = event.message;
24383
+ if (message2?.role !== "assistant" || !message2.usage)
24384
+ return [];
24385
+ state.usageRecordSequence = (state.usageRecordSequence ?? 0) + 1;
24386
+ const providerRecordId = typeof message2.responseId === "string" && message2.responseId ? message2.responseId : `live:${message2.timestamp ?? "unknown"}:${state.usageRecordSequence}`;
24387
+ const { projector, pending } = piUsageState(state);
24388
+ const identity = { runtime: "pi", backendSessionId: sessionId, providerRecordId };
24389
+ const usage = projector.project({
24390
+ ...identity,
24391
+ source: "pi_message_end",
24392
+ input: message2.usage.input,
24393
+ output: message2.usage.output,
24394
+ cacheRead: message2.usage.cacheRead,
24395
+ cacheWrite: message2.usage.cacheWrite,
24396
+ inputIncludesCache: false,
24397
+ outputIncludesReasoning: true
24398
+ });
24399
+ pending.add(providerRecordId);
24400
+ return usage ? [usage] : [];
24401
+ }
24402
+ case "turn_end": {
24403
+ const { projector, pending } = piUsageState(state);
24404
+ for (const providerRecordId of pending) {
24405
+ projector.release({ runtime: "pi", backendSessionId: sessionId, providerRecordId });
24406
+ }
24407
+ pending.clear();
24408
+ return [];
24409
+ }
24224
24410
  case "auto_retry_start":
24225
24411
  return [{ kind: "runtime_recovery", stage: "retrying", source: "pi_auto_retry" }];
24226
24412
  case "auto_retry_end":
@@ -30499,6 +30685,7 @@ class AgentRouter {
30499
30685
  await this.opts.channel.reportReady(this.buildReady());
30500
30686
  }
30501
30687
  buildReady() {
30688
+ const timeZone = typeof this.opts.timeZone === "function" ? this.opts.timeZone() : this.opts.timeZone;
30502
30689
  return {
30503
30690
  runtimeReport: [...this.runtimes.values()],
30504
30691
  capabilities: [CONTROL_HEARTBEAT_CAPABILITY],
@@ -30508,6 +30695,7 @@ class AgentRouter {
30508
30695
  arch: this.opts.arch,
30509
30696
  osRelease: this.opts.osRelease,
30510
30697
  daemonVersion: this.opts.daemonVersion,
30698
+ timeZone,
30511
30699
  ...this.opts.providerQuotas ? { providerQuotas: this.opts.providerQuotas() } : {}
30512
30700
  };
30513
30701
  }
@@ -32428,15 +32616,12 @@ class DaemonSelfSleepScheduler {
32428
32616
  import { chmod, mkdir, open, readFile as readFile2, rename, rm } from "node:fs/promises";
32429
32617
  import { dirname as dirname6, join as join14 } from "node:path";
32430
32618
  import { randomUUID as randomUUID7 } from "node:crypto";
32431
- function dayKey(at) {
32432
- return at.toISOString().slice(0, 10);
32619
+ function oldestRetainedDay(at, timeZone) {
32620
+ const today = dayKeyInTimeZone(at, timeZone);
32621
+ return calendarDayKeyDaysAgo(today, 8);
32433
32622
  }
32434
- function retainedDays(at) {
32435
- const days = new Set;
32436
- for (let offset = 0;offset < 7; offset += 1) {
32437
- days.add(dayKey(new Date(at.getTime() - offset * 86400000)));
32438
- }
32439
- return days;
32623
+ function oldestVisibleDay(today) {
32624
+ return calendarDayKeyDaysAgo(today, 6);
32440
32625
  }
32441
32626
  function isMetric(value) {
32442
32627
  return value === null || typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
@@ -32481,16 +32666,25 @@ class DailyTokenUsageStore {
32481
32666
  loaded = false;
32482
32667
  data = { version: 1, bots: {} };
32483
32668
  filePath;
32484
- constructor(workingDirectoryBase, now = () => new Date) {
32669
+ resolveTimeZone;
32670
+ constructor(workingDirectoryBase, now = () => new Date, timeZone = () => Intl.DateTimeFormat().resolvedOptions().timeZone) {
32485
32671
  this.now = now;
32672
+ this.resolveTimeZone = typeof timeZone === "string" ? () => timeZone : timeZone;
32673
+ this.timeZone;
32486
32674
  this.filePath = join14(workingDirectoryBase, ".telemetry", "daily-token-usage.json");
32487
32675
  }
32676
+ get timeZone() {
32677
+ const timeZone = this.resolveTimeZone();
32678
+ dayKeyInTimeZone(0, timeZone);
32679
+ return timeZone;
32680
+ }
32488
32681
  record(botId, delta) {
32489
32682
  return this.enqueue(async () => {
32490
32683
  await this.load();
32491
32684
  const at = this.now();
32492
- this.prune(at);
32493
- const day = dayKey(at);
32685
+ const timeZone = this.timeZone;
32686
+ this.prune(at, timeZone);
32687
+ const day = dayKeyInTimeZone(at, timeZone);
32494
32688
  const snapshots = this.data.bots[botId] ?? [];
32495
32689
  const existing = snapshots.find((snapshot) => snapshot.day === day);
32496
32690
  const next = existing ?? emptySnapshot(botId, day);
@@ -32507,13 +32701,27 @@ class DailyTokenUsageStore {
32507
32701
  });
32508
32702
  }
32509
32703
  snapshots(botId) {
32704
+ return this.usageWindow(botId).then((window2) => window2.snapshots);
32705
+ }
32706
+ usageWindow(botId) {
32510
32707
  let result = [];
32708
+ let usageDay = "";
32709
+ let usageTimeZone = "";
32511
32710
  return this.enqueue(async () => {
32512
32711
  await this.load();
32513
- if (this.prune(this.now()))
32712
+ const at = this.now();
32713
+ const timeZone = this.timeZone;
32714
+ usageTimeZone = timeZone;
32715
+ usageDay = dayKeyInTimeZone(at, timeZone);
32716
+ if (this.prune(at, timeZone))
32514
32717
  await this.persist();
32515
- result = (this.data.bots[botId] ?? []).map((snapshot) => structuredClone(snapshot));
32516
- }).then(() => result);
32718
+ const oldestDay = oldestVisibleDay(usageDay);
32719
+ result = (this.data.bots[botId] ?? []).filter((snapshot) => snapshot.day >= oldestDay && snapshot.day <= usageDay).map((snapshot) => structuredClone(snapshot));
32720
+ }).then(() => ({
32721
+ usageDay,
32722
+ usageTimeZone,
32723
+ snapshots: result
32724
+ }));
32517
32725
  }
32518
32726
  enqueue(operation) {
32519
32727
  const result = this.tail.then(operation, operation);
@@ -32558,11 +32766,11 @@ class DailyTokenUsageStore {
32558
32766
  this.data = { version: 1, bots: valid };
32559
32767
  this.loaded = true;
32560
32768
  }
32561
- prune(at) {
32562
- const keep = retainedDays(at);
32769
+ prune(at, timeZone) {
32770
+ const oldestDay = oldestRetainedDay(at, timeZone);
32563
32771
  let changed = false;
32564
32772
  for (const [botId, snapshots] of Object.entries(this.data.bots)) {
32565
- const retained = snapshots.filter((snapshot) => keep.has(snapshot.day)).sort((a, b) => a.day.localeCompare(b.day)).slice(-7);
32773
+ const retained = snapshots.filter((snapshot) => snapshot.day >= oldestDay).sort((a, b) => a.day.localeCompare(b.day));
32566
32774
  if (retained.length !== snapshots.length || retained.some((snapshot, index2) => snapshot !== snapshots[index2]))
32567
32775
  changed = true;
32568
32776
  if (retained.length === 0)
@@ -32606,6 +32814,7 @@ var WARMUP_CEILING_MS = 30000;
32606
32814
  var RUNTIME_RAW_TRACE_MAX_BYTES = 8 * 1024 * 1024;
32607
32815
  var RUNTIME_RAW_TRACE_AGENT_IDS_ENV = "ALOOK_RUNTIME_RAW_TRACE_AGENT_IDS";
32608
32816
  var STATUS_WRITE_INTERVAL_MS = 5000;
32817
+ var TOKEN_USAGE_BACKENDS = new Set(["claude", "codex", "opencode", "pi"]);
32609
32818
  function parseRuntimeRawTraceAgentIds(value) {
32610
32819
  return new Set((value ?? "").split(",").map((agentId) => agentId.trim()).filter((agentId) => agentId.length > 0 && agentId !== "*"));
32611
32820
  }
@@ -32782,10 +32991,14 @@ async function createDaemon(opts) {
32782
32991
  recordProviderQuota("claude", observed);
32783
32992
  }
32784
32993
  const quota = backendId === "claude" || backendId === "codex" ? providerQuotaByBackend.get(backendId) : undefined;
32785
- const dailyUsage = await dailyTokenUsage2.snapshots(info.agentId);
32994
+ const usageWindow = backendId && TOKEN_USAGE_BACKENDS.has(backendId) ? await dailyTokenUsage2.usageWindow(info.agentId) : null;
32786
32995
  return {
32787
32996
  ...info,
32788
- ...dailyUsage.length > 0 ? { dailyUsage } : {},
32997
+ ...usageWindow ? {
32998
+ usageTimeZone: usageWindow.usageTimeZone,
32999
+ usageDay: usageWindow.usageDay,
33000
+ ...usageWindow.snapshots.length > 0 ? { dailyUsage: usageWindow.snapshots } : {}
33001
+ } : {},
32789
33002
  ...quota ? { quota: structuredClone(quota) } : {}
32790
33003
  };
32791
33004
  };
@@ -33197,6 +33410,7 @@ async function createDaemon(opts) {
33197
33410
  arch: opts.arch,
33198
33411
  osRelease: opts.osRelease,
33199
33412
  daemonVersion: opts.daemonVersion,
33413
+ timeZone: () => dailyTokenUsage2.timeZone,
33200
33414
  providerQuotas: providerQuotaSnapshots,
33201
33415
  resyncActivities: async () => {
33202
33416
  const activities = await Promise.all(manager.liveAgentActivities().map((info) => activityPayload(info)));
package/dist/index.js CHANGED
@@ -1125,6 +1125,66 @@ function buildClaudeArgs(config) {
1125
1125
  return args;
1126
1126
  }
1127
1127
 
1128
+ // agent-driver/dist/internal/token-usage.js
1129
+ function validIdentityPart(value) {
1130
+ return value.trim().length > 0 && Buffer.byteLength(value, "utf8") <= 512;
1131
+ }
1132
+ function identityKey(identity) {
1133
+ if (!validIdentityPart(identity.runtime) || !validIdentityPart(identity.backendSessionId) || !validIdentityPart(identity.providerRecordId))
1134
+ return null;
1135
+ return JSON.stringify([identity.runtime, identity.backendSessionId, identity.providerRecordId]);
1136
+ }
1137
+ function metric(value) {
1138
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : null;
1139
+ }
1140
+ function cacheMetric(read, write) {
1141
+ const present = [read, write].filter((value) => value !== undefined);
1142
+ if (present.length === 0)
1143
+ return null;
1144
+ const metrics = present.map(metric);
1145
+ if (metrics.some((value) => value === null))
1146
+ return null;
1147
+ const total = metrics.reduce((sum, value) => sum + value, 0);
1148
+ return Number.isSafeInteger(total) ? total : null;
1149
+ }
1150
+
1151
+ class SettledUsageProjector {
1152
+ active = new Set;
1153
+ project(record) {
1154
+ const key = identityKey(record);
1155
+ if (!key || this.active.has(key))
1156
+ return null;
1157
+ this.active.add(key);
1158
+ const cache = cacheMetric(record.cacheRead, record.cacheWrite);
1159
+ const rawInput = metric(record.input);
1160
+ const input = record.inputIncludesCache ? rawInput !== null && cache !== null && cache <= rawInput ? rawInput - cache : null : rawInput;
1161
+ const rawOutput = metric(record.output);
1162
+ const reasoning = metric(record.reasoning);
1163
+ const output = record.outputIncludesReasoning ? rawOutput : rawOutput !== null && reasoning !== null && Number.isSafeInteger(rawOutput + reasoning) ? rawOutput + reasoning : null;
1164
+ if (input === null && output === null && cache === null) {
1165
+ this.active.delete(key);
1166
+ return null;
1167
+ }
1168
+ return {
1169
+ kind: "telemetry",
1170
+ name: "token_usage",
1171
+ source: record.source,
1172
+ usage: { input, output, cache }
1173
+ };
1174
+ }
1175
+ release(identity) {
1176
+ const key = identityKey(identity);
1177
+ if (key)
1178
+ this.active.delete(key);
1179
+ }
1180
+ reset() {
1181
+ this.active.clear();
1182
+ }
1183
+ get activeCount() {
1184
+ return this.active.size;
1185
+ }
1186
+ }
1187
+
1128
1188
  // agent-driver/dist/internal/utils.js
1129
1189
  import { randomUUID as randomUUID3 } from "crypto";
1130
1190
  function jsonRpcRequest(method, params, id) {
@@ -1144,9 +1204,13 @@ var API_ERROR_RE = /API Error:.*(?:Connection error|\b[45]\d{2}\b)/i;
1144
1204
  class ClaudeEventNormalizer {
1145
1205
  turnProtocol;
1146
1206
  currentSession = null;
1207
+ usageProjector = new SettledUsageProjector;
1147
1208
  constructor(turnProtocol) {
1148
1209
  this.turnProtocol = turnProtocol;
1149
1210
  }
1211
+ beginTurn() {
1212
+ this.usageProjector.reset();
1213
+ }
1150
1214
  get currentSessionId() {
1151
1215
  return this.currentSession;
1152
1216
  }
@@ -1238,7 +1302,7 @@ class ClaudeEventNormalizer {
1238
1302
  const turnOwner = rawOwner ? this.turnProtocol?.claimResult(rawOwner) ?? (this.turnProtocol ? null : `claude:${rawOwner}`) : null;
1239
1303
  if (this.turnProtocol && !turnOwner)
1240
1304
  return;
1241
- const usage = this.buildUsageTelemetry(event);
1305
+ const usage = this.buildUsageTelemetry(event, rawOwner);
1242
1306
  if (usage)
1243
1307
  out.push(usage);
1244
1308
  if (event.is_error || event.subtype === "error_during_execution") {
@@ -1253,23 +1317,26 @@ class ClaudeEventNormalizer {
1253
1317
  acceptsTurnWork() {
1254
1318
  return this.turnProtocol?.acceptsTurnWork() ?? true;
1255
1319
  }
1256
- buildUsageTelemetry(event) {
1320
+ buildUsageTelemetry(event, rootRequestId) {
1257
1321
  const u = event?.usage;
1258
1322
  if (!u)
1259
1323
  return null;
1260
- const metric = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : null;
1261
- const cacheParts = [u.cache_read_input_tokens, u.cache_creation_input_tokens].filter((value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0);
1262
- const cache = cacheParts.length > 0 && Number.isSafeInteger(cacheParts.reduce((sum, value) => sum + value, 0)) ? cacheParts.reduce((sum, value) => sum + value, 0) : null;
1263
- return {
1264
- kind: "telemetry",
1265
- name: "token_usage",
1324
+ const backendSessionId = event.session_id ?? this.currentSession;
1325
+ if (typeof backendSessionId !== "string" || !backendSessionId)
1326
+ return null;
1327
+ const providerRecordId = rootRequestId ?? (typeof event.request_id === "string" ? event.request_id : "invocation-result");
1328
+ return this.usageProjector.project({
1329
+ runtime: "claude",
1330
+ backendSessionId,
1331
+ providerRecordId,
1266
1332
  source: "claude_result_usage",
1267
- usage: {
1268
- input: metric(u.input_tokens),
1269
- output: metric(u.output_tokens),
1270
- cache
1271
- }
1272
- };
1333
+ input: u.input_tokens,
1334
+ output: u.output_tokens,
1335
+ cacheRead: u.cache_read_input_tokens,
1336
+ cacheWrite: u.cache_creation_input_tokens,
1337
+ inputIncludesCache: false,
1338
+ outputIncludesReasoning: true
1339
+ });
1273
1340
  }
1274
1341
  }
1275
1342
 
@@ -1472,7 +1539,9 @@ class ClaudeDriver {
1472
1539
  turnProtocol = new ClaudeTurnProtocol;
1473
1540
  eventNormalizer = new ClaudeEventNormalizer(this.turnProtocol);
1474
1541
  beginTurn() {
1475
- return this.turnProtocol.beginTurn();
1542
+ const receipt = this.turnProtocol.beginTurn();
1543
+ this.eventNormalizer.beginTurn();
1544
+ return receipt;
1476
1545
  }
1477
1546
  probe(command) {
1478
1547
  const explicit = command?.trim();
@@ -1526,14 +1595,6 @@ class ClaudeDriver {
1526
1595
  }
1527
1596
 
1528
1597
  // agent-driver/dist/adapters/codex/telemetry.js
1529
- function metric(value) {
1530
- return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : null;
1531
- }
1532
- function nonCachedInput(input, cached) {
1533
- if (typeof input !== "number" || !Number.isSafeInteger(input) || input < 0 || typeof cached !== "number" || !Number.isSafeInteger(cached) || cached < 0 || cached > input)
1534
- return null;
1535
- return input - cached;
1536
- }
1537
1598
  function canonicalId(value, fallback) {
1538
1599
  return typeof value === "string" && value.length > 0 && new TextEncoder().encode(value).length <= 64 ? value : fallback;
1539
1600
  }
@@ -1627,28 +1688,24 @@ function mapCodexQuotaSnapshots(snapshots, sourceEpoch) {
1627
1688
  }
1628
1689
  };
1629
1690
  }
1630
- function mapCodexTelemetry(method, params, sourceEpoch) {
1631
- if (method === "thread/tokenUsage/updated") {
1632
- const u = params?.tokenUsage?.last ?? params?.token_usage?.last;
1633
- if (!u)
1634
- return [];
1635
- const input = u.inputTokens ?? u.input_tokens;
1636
- const cached = u.cachedInputTokens ?? u.cached_input_tokens;
1637
- return [{
1638
- kind: "telemetry",
1639
- name: "token_usage",
1640
- source: "codex_thread_token_usage_updated",
1641
- usage: {
1642
- input: nonCachedInput(input, cached),
1643
- output: metric(u.outputTokens ?? u.output_tokens),
1644
- cache: metric(cached)
1645
- }
1646
- }];
1647
- }
1648
- if (method === "account/rateLimits/updated") {
1649
- return [mapCodexQuotaSnapshots([params?.rateLimits ?? params ?? {}], sourceEpoch)];
1650
- }
1651
- return [];
1691
+ function mapCodexSettledUsage(params, projector) {
1692
+ const backendSessionId = params?.threadId ?? params?.thread_id;
1693
+ const providerRecordId = params?.responseId ?? params?.response_id;
1694
+ const usage = params?.usage;
1695
+ if (typeof backendSessionId !== "string" || typeof providerRecordId !== "string" || !usage)
1696
+ return null;
1697
+ return projector.project({
1698
+ runtime: "codex",
1699
+ backendSessionId,
1700
+ providerRecordId,
1701
+ source: "codex_raw_response_completed",
1702
+ input: usage.inputTokens ?? usage.input_tokens,
1703
+ output: usage.outputTokens ?? usage.output_tokens,
1704
+ cacheRead: usage.cachedInputTokens ?? usage.cached_input_tokens,
1705
+ cacheWrite: usage.cacheWriteInputTokens ?? usage.cache_write_input_tokens,
1706
+ inputIncludesCache: true,
1707
+ outputIncludesReasoning: true
1708
+ });
1652
1709
  }
1653
1710
 
1654
1711
  // agent-driver/dist/adapters/codex/normalizer.js
@@ -1700,7 +1757,8 @@ class CodexEventNormalizer {
1700
1757
  rateLimitSnapshots = new Map;
1701
1758
  quotaSnapshotInitialized = false;
1702
1759
  quotaSourceGeneration = codexQuotaSourceGeneration;
1703
- pendingTurnUsage = null;
1760
+ usageProjector = new SettledUsageProjector;
1761
+ usageRecordsBySessionAndTurn = new Map;
1704
1762
  threadId = null;
1705
1763
  turnId = null;
1706
1764
  terminalTurn = null;
@@ -1773,7 +1831,8 @@ class CodexEventNormalizer {
1773
1831
  if (threadId !== this.threadId) {
1774
1832
  this.turnId = null;
1775
1833
  this.terminalTurn = null;
1776
- this.pendingTurnUsage = null;
1834
+ this.usageProjector.reset();
1835
+ this.usageRecordsBySessionAndTurn.clear();
1777
1836
  }
1778
1837
  this.threadId = threadId;
1779
1838
  }
@@ -1822,6 +1881,22 @@ class CodexEventNormalizer {
1822
1881
  }
1823
1882
  handleNotification(method, params) {
1824
1883
  const notificationThreadId = typeof params?.threadId === "string" ? params.threadId : null;
1884
+ if (method === "rawResponse/completed")
1885
+ return this.handleSettledUsage(params);
1886
+ if (method === "turn/completed" && notificationThreadId !== null && notificationThreadId !== this.threadId) {
1887
+ const turnId = this.notificationTurnId(params);
1888
+ if (turnId)
1889
+ this.releaseUsageForTurn(notificationThreadId, turnId);
1890
+ return [];
1891
+ }
1892
+ if (method === "item/completed" && notificationThreadId !== null && notificationThreadId !== this.threadId) {
1893
+ const turnId = this.notificationTurnId(params);
1894
+ const itemType = params?.item?.type ?? params?.type;
1895
+ if (turnId && itemType === "contextCompaction") {
1896
+ this.releaseUsageForTurn(notificationThreadId, turnId);
1897
+ }
1898
+ return [];
1899
+ }
1825
1900
  if (this.threadId !== null && notificationThreadId !== null && notificationThreadId !== this.threadId)
1826
1901
  return [];
1827
1902
  if (this.isRootWorkNotification(method) && !this.acceptRootWork(params))
@@ -1834,7 +1909,6 @@ class CodexEventNormalizer {
1834
1909
  return [];
1835
1910
  this.turnId = params.turn.id;
1836
1911
  this.terminalTurn = null;
1837
- this.pendingTurnUsage = null;
1838
1912
  return [
1839
1913
  {
1840
1914
  kind: "turn_owner",
@@ -1850,7 +1924,7 @@ class CodexEventNormalizer {
1850
1924
  case "item/started":
1851
1925
  return this.handleItemStarted(params);
1852
1926
  case "item/completed":
1853
- return this.handleItemCompleted(params);
1927
+ return this.handleItemCompletedAndReleaseUsage(params);
1854
1928
  case "rawResponseItem/completed":
1855
1929
  return [{ kind: "internal_progress", source: "codex_raw_item", itemType: "rawResponseItem" }];
1856
1930
  case "configWarning":
@@ -1863,30 +1937,24 @@ class CodexEventNormalizer {
1863
1937
  case "turn/completed":
1864
1938
  if (!this.acceptRootTerminal(params))
1865
1939
  return [];
1866
- const usage = this.pendingTurnUsage;
1867
- this.pendingTurnUsage = null;
1940
+ this.releaseUsageForTurn(params.threadId, params.turn.id);
1868
1941
  if (params.turn.status === "failed") {
1869
1942
  return [
1870
- ...usage ? [usage] : [],
1871
1943
  { kind: "error", message: "Codex turn failed" },
1872
1944
  { kind: "turn_end", sessionId: this.threadId ?? undefined, turnOwner: this.turnReceipt(params.threadId, params.turn.id) }
1873
1945
  ];
1874
1946
  }
1875
1947
  if (params.turn.status === "interrupted") {
1876
- return [...usage ? [usage] : [], { kind: "error", message: "Codex turn interrupted" }, { kind: "turn_end", sessionId: this.threadId ?? undefined, turnOwner: this.turnReceipt(params.threadId, params.turn.id) }];
1948
+ return [{ kind: "error", message: "Codex turn interrupted" }, { kind: "turn_end", sessionId: this.threadId ?? undefined, turnOwner: this.turnReceipt(params.threadId, params.turn.id) }];
1877
1949
  }
1878
- return [...usage ? [usage] : [], { kind: "turn_end", sessionId: this.threadId ?? undefined, turnOwner: this.turnReceipt(params.threadId, params.turn.id) }];
1950
+ return [{ kind: "turn_end", sessionId: this.threadId ?? undefined, turnOwner: this.turnReceipt(params.threadId, params.turn.id) }];
1879
1951
  case "error":
1880
1952
  if (params?.willRetry === true) {
1881
1953
  return [{ kind: "runtime_recovery", stage: "retrying", source: "codex_stream" }];
1882
1954
  }
1883
1955
  return [{ kind: "error", message: params?.error?.message ?? params?.message ?? "Codex error" }];
1884
- case "thread/tokenUsage/updated": {
1885
- const usage2 = mapCodexTelemetry(method, params, codexQuotaSourceEpoch)[0];
1886
- if (usage2)
1887
- this.pendingTurnUsage = usage2;
1956
+ case "thread/tokenUsage/updated":
1888
1957
  return [];
1889
- }
1890
1958
  case "account/rateLimits/updated":
1891
1959
  return this.mergeQuotaSnapshots(params);
1892
1960
  case "account/updated":
@@ -1897,6 +1965,42 @@ class CodexEventNormalizer {
1897
1965
  return [];
1898
1966
  }
1899
1967
  }
1968
+ handleSettledUsage(params) {
1969
+ const notificationTurnId = this.notificationTurnId(params);
1970
+ if (this.turnId === null && this.terminalTurn?.state === "closed" && notificationTurnId === this.terminalTurn.turnId && params?.threadId === this.terminalTurn.threadId)
1971
+ return [];
1972
+ const backendSessionId = params?.threadId ?? params?.thread_id;
1973
+ const providerRecordId = params?.responseId ?? params?.response_id;
1974
+ if (!notificationTurnId || typeof backendSessionId !== "string" || typeof providerRecordId !== "string")
1975
+ return [];
1976
+ const usage = mapCodexSettledUsage(params, this.usageProjector);
1977
+ if (!usage)
1978
+ return [];
1979
+ const recordsByTurn = this.usageRecordsBySessionAndTurn.get(backendSessionId) ?? new Map;
1980
+ const recordIds = recordsByTurn.get(notificationTurnId) ?? new Set;
1981
+ recordIds.add(providerRecordId);
1982
+ recordsByTurn.set(notificationTurnId, recordIds);
1983
+ this.usageRecordsBySessionAndTurn.set(backendSessionId, recordsByTurn);
1984
+ return [usage];
1985
+ }
1986
+ releaseUsageForTurn(backendSessionId, turnId) {
1987
+ const recordsByTurn = this.usageRecordsBySessionAndTurn.get(backendSessionId);
1988
+ for (const providerRecordId of recordsByTurn?.get(turnId) ?? []) {
1989
+ this.usageProjector.release({ runtime: "codex", backendSessionId, providerRecordId });
1990
+ }
1991
+ recordsByTurn?.delete(turnId);
1992
+ if (recordsByTurn?.size === 0)
1993
+ this.usageRecordsBySessionAndTurn.delete(backendSessionId);
1994
+ }
1995
+ handleItemCompletedAndReleaseUsage(params) {
1996
+ const events = this.handleItemCompleted(params);
1997
+ const turnId = this.notificationTurnId(params);
1998
+ const itemType = params?.item?.type ?? params?.type;
1999
+ if (turnId && itemType === "contextCompaction" && turnId !== this.turnId && typeof params?.threadId === "string") {
2000
+ this.releaseUsageForTurn(params.threadId, turnId);
2001
+ }
2002
+ return events;
2003
+ }
1900
2004
  isRootWorkNotification(method) {
1901
2005
  return method === "item/reasoning/textDelta" || method === "item/reasoning/summaryTextDelta" || method === "item/agentMessage/delta" || method === "item/started" || method === "item/completed" || method === "rawResponseItem/completed";
1902
2006
  }
@@ -3441,6 +3545,7 @@ class OpenCodeServiceLane {
3441
3545
  lastDurableSeq = 0;
3442
3546
  durableSeqById = new Map;
3443
3547
  durableIdBySeq = new Map;
3548
+ usageProjector = new SettledUsageProjector;
3444
3549
  toolNames = new Map;
3445
3550
  handledPermissions = new Set;
3446
3551
  permissionFlights = new Map;
@@ -3981,7 +4086,8 @@ class OpenCodeServiceLane {
3981
4086
  const event = record3(value);
3982
4087
  const durable = record3(event?.durable);
3983
4088
  const data = record3(event?.data);
3984
- if (!event || typeof event.id !== "string" || typeof event.type !== "string" || !durable || durable.aggregateID !== this.sessionId || !Number.isInteger(durable.seq) || Number(durable.seq) < 0 || data?.sessionID !== this.sessionId) {
4089
+ const backendSessionId = this.sessionId;
4090
+ if (!event || !backendSessionId || typeof event.id !== "string" || typeof event.type !== "string" || !durable || durable.aggregateID !== this.sessionId || !Number.isInteger(durable.seq) || Number(durable.seq) < 0 || data?.sessionID !== this.sessionId) {
3985
4091
  throw new OpenCodeProtocolError("OpenCode session stream emitted an invalid durable event");
3986
4092
  }
3987
4093
  const seq = Number(durable.seq);
@@ -4071,21 +4177,27 @@ class OpenCodeServiceLane {
4071
4177
  });
4072
4178
  }
4073
4179
  const tokens = record3(data.tokens);
4074
- if (tokens && data.finish !== "tool-calls") {
4180
+ if (tokens) {
4075
4181
  const cache = record3(tokens.cache);
4076
- const metric2 = (value2) => typeof value2 === "number" && Number.isSafeInteger(value2) && value2 >= 0 ? value2 : null;
4077
- const cacheParts = [cache?.read, cache?.write].filter((value2) => typeof value2 === "number" && Number.isSafeInteger(value2) && value2 >= 0);
4078
- const cacheTotal = cacheParts.reduce((sum, value2) => sum + value2, 0);
4079
- this.events.emit("runtime_event", {
4080
- kind: "telemetry",
4081
- name: "token_usage",
4182
+ const identity = {
4183
+ runtime: "opencode",
4184
+ backendSessionId,
4185
+ providerRecordId: event.id
4186
+ };
4187
+ const usage = this.usageProjector.project({
4188
+ ...identity,
4082
4189
  source: "opencode.v2",
4083
- usage: {
4084
- input: metric2(tokens.input),
4085
- output: metric2(tokens.output),
4086
- cache: cacheParts.length > 0 && Number.isSafeInteger(cacheTotal) ? cacheTotal : null
4087
- }
4190
+ input: tokens.input,
4191
+ output: tokens.output,
4192
+ reasoning: tokens.reasoning,
4193
+ cacheRead: cache?.read,
4194
+ cacheWrite: cache?.write,
4195
+ inputIncludesCache: false,
4196
+ outputIncludesReasoning: false
4088
4197
  });
4198
+ if (usage)
4199
+ this.events.emit("runtime_event", usage);
4200
+ this.usageProjector.release(identity);
4089
4201
  }
4090
4202
  break;
4091
4203
  }
@@ -4804,6 +4916,11 @@ function readPiSdkVersion() {
4804
4916
  } catch {}
4805
4917
  return resolvePiSdkVersionFromPath();
4806
4918
  }
4919
+ function piUsageState(state) {
4920
+ state.usageProjector ??= new SettledUsageProjector;
4921
+ state.pendingUsageRecordIds ??= new Set;
4922
+ return { projector: state.usageProjector, pending: state.pendingUsageRecordIds };
4923
+ }
4807
4924
  function mapPiSdkEvent(event, sessionId, state) {
4808
4925
  if (event?.type === "message_update") {
4809
4926
  const d = event.assistantMessageEvent ?? {};
@@ -4824,6 +4941,35 @@ function mapPiSdkEvent(event, sessionId, state) {
4824
4941
  }
4825
4942
  }
4826
4943
  switch (event?.type) {
4944
+ case "message_end": {
4945
+ const message = event.message;
4946
+ if (message?.role !== "assistant" || !message.usage)
4947
+ return [];
4948
+ state.usageRecordSequence = (state.usageRecordSequence ?? 0) + 1;
4949
+ const providerRecordId = typeof message.responseId === "string" && message.responseId ? message.responseId : `live:${message.timestamp ?? "unknown"}:${state.usageRecordSequence}`;
4950
+ const { projector, pending } = piUsageState(state);
4951
+ const identity = { runtime: "pi", backendSessionId: sessionId, providerRecordId };
4952
+ const usage = projector.project({
4953
+ ...identity,
4954
+ source: "pi_message_end",
4955
+ input: message.usage.input,
4956
+ output: message.usage.output,
4957
+ cacheRead: message.usage.cacheRead,
4958
+ cacheWrite: message.usage.cacheWrite,
4959
+ inputIncludesCache: false,
4960
+ outputIncludesReasoning: true
4961
+ });
4962
+ pending.add(providerRecordId);
4963
+ return usage ? [usage] : [];
4964
+ }
4965
+ case "turn_end": {
4966
+ const { projector, pending } = piUsageState(state);
4967
+ for (const providerRecordId of pending) {
4968
+ projector.release({ runtime: "pi", backendSessionId: sessionId, providerRecordId });
4969
+ }
4970
+ pending.clear();
4971
+ return [];
4972
+ }
4827
4973
  case "auto_retry_start":
4828
4974
  return [{ kind: "runtime_recovery", stage: "retrying", source: "pi_auto_retry" }];
4829
4975
  case "auto_retry_end":
@@ -26874,6 +27020,7 @@ var communityMachine = sqliteTable("community_machine", {
26874
27020
  arch: text("arch").notNull().default(""),
26875
27021
  osRelease: text("os_release").notNull().default(""),
26876
27022
  daemonVersion: text("daemon_version").notNull().default(""),
27023
+ timeZone: text("time_zone"),
26877
27024
  metadata: text("metadata"),
26878
27025
  availableRuntimes: text("available_runtimes", { mode: "json" }).$type().notNull().default([]),
26879
27026
  status: text("status").notNull().default("offline"),
@@ -27225,6 +27372,7 @@ class AgentRouter {
27225
27372
  await this.opts.channel.reportReady(this.buildReady());
27226
27373
  }
27227
27374
  buildReady() {
27375
+ const timeZone = typeof this.opts.timeZone === "function" ? this.opts.timeZone() : this.opts.timeZone;
27228
27376
  return {
27229
27377
  runtimeReport: [...this.runtimes.values()],
27230
27378
  capabilities: [CONTROL_HEARTBEAT_CAPABILITY],
@@ -27234,6 +27382,7 @@ class AgentRouter {
27234
27382
  arch: this.opts.arch,
27235
27383
  osRelease: this.opts.osRelease,
27236
27384
  daemonVersion: this.opts.daemonVersion,
27385
+ timeZone,
27237
27386
  ...this.opts.providerQuotas ? { providerQuotas: this.opts.providerQuotas() } : {}
27238
27387
  };
27239
27388
  }
@@ -28951,6 +29100,7 @@ var HostReadyMessageSchema = exports_external.object({
28951
29100
  arch: exports_external.string().optional(),
28952
29101
  osRelease: exports_external.string().optional(),
28953
29102
  daemonVersion: exports_external.string().optional(),
29103
+ timeZone: exports_external.string().min(1).max(128).optional(),
28954
29104
  providerQuotas: exports_external.array(ProviderQuotaSnapshotSchema).max(2).optional().default([])
28955
29105
  });
28956
29106
  var CommunityDaemonReadySchema = exports_external.object({
@@ -28973,6 +29123,8 @@ var AgentActivityMessageSchema = exports_external.object({
28973
29123
  type: exports_external.literal("agent_activity"),
28974
29124
  agentId: exports_external.string(),
28975
29125
  state: exports_external.enum(["idle", "starting", "running", "stopping"]),
29126
+ usageTimeZone: exports_external.string().min(1).max(128).optional(),
29127
+ usageDay: exports_external.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
28976
29128
  dailyUsage: exports_external.array(DailyUsageSnapshotSchema).max(7).optional(),
28977
29129
  quota: ProviderQuotaSnapshotSchema.optional()
28978
29130
  });
@@ -29238,6 +29390,42 @@ var BotAuditEventAckFrameSchema = exports_external.strictObject({
29238
29390
  type: exports_external.literal("bot_audit_event_ack"),
29239
29391
  eventId: exports_external.string().min(1).max(128)
29240
29392
  });
29393
+ // ../shared/src/utils/day-key.ts
29394
+ function utcDayKey(now) {
29395
+ const d = now instanceof Date ? now : new Date(now);
29396
+ return d.toISOString().slice(0, 10);
29397
+ }
29398
+ function calendarDayKeyDaysAgo(day, days) {
29399
+ const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(day);
29400
+ if (!match)
29401
+ throw new RangeError("invalid calendar day key");
29402
+ const year = Number(match[1]);
29403
+ const month = Number(match[2]);
29404
+ const date5 = Number(match[3]);
29405
+ const parsed = new Date(Date.UTC(year, month - 1, date5));
29406
+ if (parsed.getUTCFullYear() !== year || parsed.getUTCMonth() !== month - 1 || parsed.getUTCDate() !== date5) {
29407
+ throw new RangeError("invalid calendar day key");
29408
+ }
29409
+ parsed.setUTCDate(parsed.getUTCDate() - days);
29410
+ return utcDayKey(parsed);
29411
+ }
29412
+ function dayKeyInTimeZone(now, timeZone) {
29413
+ const date5 = now instanceof Date ? now : new Date(now);
29414
+ const parts = new Intl.DateTimeFormat("en-US", {
29415
+ timeZone,
29416
+ year: "numeric",
29417
+ month: "2-digit",
29418
+ day: "2-digit"
29419
+ }).formatToParts(date5);
29420
+ const values = new Map(parts.map((part) => [part.type, part.value]));
29421
+ const year = values.get("year");
29422
+ const month = values.get("month");
29423
+ const day = values.get("day");
29424
+ if (!year || !month || !day)
29425
+ throw new RangeError("unable to format calendar day key");
29426
+ return `${year}-${month}-${day}`;
29427
+ }
29428
+
29241
29429
  // ../shared/src/db/community-schema.ts
29242
29430
  var exports_community_schema = {};
29243
29431
  __export(exports_community_schema, {
@@ -32726,15 +32914,12 @@ class DaemonSelfSleepScheduler {
32726
32914
  import { chmod, mkdir, open, readFile as readFile2, rename, rm } from "node:fs/promises";
32727
32915
  import { dirname as dirname5, join as join14 } from "node:path";
32728
32916
  import { randomUUID as randomUUID7 } from "node:crypto";
32729
- function dayKey(at) {
32730
- return at.toISOString().slice(0, 10);
32917
+ function oldestRetainedDay(at, timeZone) {
32918
+ const today = dayKeyInTimeZone(at, timeZone);
32919
+ return calendarDayKeyDaysAgo(today, 8);
32731
32920
  }
32732
- function retainedDays(at) {
32733
- const days = new Set;
32734
- for (let offset = 0;offset < 7; offset += 1) {
32735
- days.add(dayKey(new Date(at.getTime() - offset * 86400000)));
32736
- }
32737
- return days;
32921
+ function oldestVisibleDay(today) {
32922
+ return calendarDayKeyDaysAgo(today, 6);
32738
32923
  }
32739
32924
  function isMetric(value) {
32740
32925
  return value === null || typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
@@ -32779,16 +32964,25 @@ class DailyTokenUsageStore {
32779
32964
  loaded = false;
32780
32965
  data = { version: 1, bots: {} };
32781
32966
  filePath;
32782
- constructor(workingDirectoryBase, now = () => new Date) {
32967
+ resolveTimeZone;
32968
+ constructor(workingDirectoryBase, now = () => new Date, timeZone = () => Intl.DateTimeFormat().resolvedOptions().timeZone) {
32783
32969
  this.now = now;
32970
+ this.resolveTimeZone = typeof timeZone === "string" ? () => timeZone : timeZone;
32971
+ this.timeZone;
32784
32972
  this.filePath = join14(workingDirectoryBase, ".telemetry", "daily-token-usage.json");
32785
32973
  }
32974
+ get timeZone() {
32975
+ const timeZone = this.resolveTimeZone();
32976
+ dayKeyInTimeZone(0, timeZone);
32977
+ return timeZone;
32978
+ }
32786
32979
  record(botId, delta) {
32787
32980
  return this.enqueue(async () => {
32788
32981
  await this.load();
32789
32982
  const at = this.now();
32790
- this.prune(at);
32791
- const day = dayKey(at);
32983
+ const timeZone = this.timeZone;
32984
+ this.prune(at, timeZone);
32985
+ const day = dayKeyInTimeZone(at, timeZone);
32792
32986
  const snapshots = this.data.bots[botId] ?? [];
32793
32987
  const existing = snapshots.find((snapshot) => snapshot.day === day);
32794
32988
  const next = existing ?? emptySnapshot(botId, day);
@@ -32805,13 +32999,27 @@ class DailyTokenUsageStore {
32805
32999
  });
32806
33000
  }
32807
33001
  snapshots(botId) {
33002
+ return this.usageWindow(botId).then((window2) => window2.snapshots);
33003
+ }
33004
+ usageWindow(botId) {
32808
33005
  let result = [];
33006
+ let usageDay = "";
33007
+ let usageTimeZone = "";
32809
33008
  return this.enqueue(async () => {
32810
33009
  await this.load();
32811
- if (this.prune(this.now()))
33010
+ const at = this.now();
33011
+ const timeZone = this.timeZone;
33012
+ usageTimeZone = timeZone;
33013
+ usageDay = dayKeyInTimeZone(at, timeZone);
33014
+ if (this.prune(at, timeZone))
32812
33015
  await this.persist();
32813
- result = (this.data.bots[botId] ?? []).map((snapshot) => structuredClone(snapshot));
32814
- }).then(() => result);
33016
+ const oldestDay = oldestVisibleDay(usageDay);
33017
+ result = (this.data.bots[botId] ?? []).filter((snapshot) => snapshot.day >= oldestDay && snapshot.day <= usageDay).map((snapshot) => structuredClone(snapshot));
33018
+ }).then(() => ({
33019
+ usageDay,
33020
+ usageTimeZone,
33021
+ snapshots: result
33022
+ }));
32815
33023
  }
32816
33024
  enqueue(operation) {
32817
33025
  const result = this.tail.then(operation, operation);
@@ -32856,11 +33064,11 @@ class DailyTokenUsageStore {
32856
33064
  this.data = { version: 1, bots: valid };
32857
33065
  this.loaded = true;
32858
33066
  }
32859
- prune(at) {
32860
- const keep = retainedDays(at);
33067
+ prune(at, timeZone) {
33068
+ const oldestDay = oldestRetainedDay(at, timeZone);
32861
33069
  let changed = false;
32862
33070
  for (const [botId, snapshots] of Object.entries(this.data.bots)) {
32863
- const retained = snapshots.filter((snapshot) => keep.has(snapshot.day)).sort((a, b) => a.day.localeCompare(b.day)).slice(-7);
33071
+ const retained = snapshots.filter((snapshot) => snapshot.day >= oldestDay).sort((a, b) => a.day.localeCompare(b.day));
32864
33072
  if (retained.length !== snapshots.length || retained.some((snapshot, index2) => snapshot !== snapshots[index2]))
32865
33073
  changed = true;
32866
33074
  if (retained.length === 0)
@@ -32904,6 +33112,7 @@ var WARMUP_CEILING_MS = 30000;
32904
33112
  var RUNTIME_RAW_TRACE_MAX_BYTES = 8 * 1024 * 1024;
32905
33113
  var RUNTIME_RAW_TRACE_AGENT_IDS_ENV = "ALOOK_RUNTIME_RAW_TRACE_AGENT_IDS";
32906
33114
  var STATUS_WRITE_INTERVAL_MS = 5000;
33115
+ var TOKEN_USAGE_BACKENDS = new Set(["claude", "codex", "opencode", "pi"]);
32907
33116
  function parseRuntimeRawTraceAgentIds(value) {
32908
33117
  return new Set((value ?? "").split(",").map((agentId) => agentId.trim()).filter((agentId) => agentId.length > 0 && agentId !== "*"));
32909
33118
  }
@@ -33080,10 +33289,14 @@ async function createDaemon(opts) {
33080
33289
  recordProviderQuota("claude", observed);
33081
33290
  }
33082
33291
  const quota = backendId === "claude" || backendId === "codex" ? providerQuotaByBackend.get(backendId) : undefined;
33083
- const dailyUsage = await dailyTokenUsage2.snapshots(info.agentId);
33292
+ const usageWindow = backendId && TOKEN_USAGE_BACKENDS.has(backendId) ? await dailyTokenUsage2.usageWindow(info.agentId) : null;
33084
33293
  return {
33085
33294
  ...info,
33086
- ...dailyUsage.length > 0 ? { dailyUsage } : {},
33295
+ ...usageWindow ? {
33296
+ usageTimeZone: usageWindow.usageTimeZone,
33297
+ usageDay: usageWindow.usageDay,
33298
+ ...usageWindow.snapshots.length > 0 ? { dailyUsage: usageWindow.snapshots } : {}
33299
+ } : {},
33087
33300
  ...quota ? { quota: structuredClone(quota) } : {}
33088
33301
  };
33089
33302
  };
@@ -33495,6 +33708,7 @@ async function createDaemon(opts) {
33495
33708
  arch: opts.arch,
33496
33709
  osRelease: opts.osRelease,
33497
33710
  daemonVersion: opts.daemonVersion,
33711
+ timeZone: () => dailyTokenUsage2.timeZone,
33498
33712
  providerQuotas: providerQuotaSnapshots,
33499
33713
  resyncActivities: async () => {
33500
33714
  const activities = await Promise.all(manager.liveAgentActivities().map((info) => activityPayload(info)));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alook/daemon",
3
- "version": "0.1.26",
3
+ "version": "0.1.28",
4
4
  "description": "Alook agent daemon — host-side runtime backend, process manager, credential proxy, and control plane.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://github.com/alookai/alook#readme",