@yhong91/vibetime 0.1.38 → 0.1.40

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/bin/vibetime.mjs +111 -26
  2. package/package.json +1 -1
package/bin/vibetime.mjs CHANGED
@@ -1928,7 +1928,7 @@ function countTextLines(text) {
1928
1928
  }
1929
1929
 
1930
1930
  // src/lib/constants.ts
1931
- var PACKAGE_VERSION = true ? "0.1.38" : "0.1.1";
1931
+ var PACKAGE_VERSION = true ? "0.1.40" : "0.1.1";
1932
1932
  var DEFAULT_API_URL = "http://121.196.224.82:3001";
1933
1933
  var DEFAULT_BACKFILL_BATCH_SIZE = 50;
1934
1934
  var DEFAULT_BACKFILL_BATCH_BYTES = 800 * 1024;
@@ -4209,7 +4209,7 @@ async function parseCodebuddyTraceFile(filePath, options) {
4209
4209
  return [];
4210
4210
  }
4211
4211
  const sessionMeta = await readCodebuddySessionMeta(trace.workerPid, trace.sessionId, options);
4212
- const sessionId = trace.sessionId || sessionMeta.sessionId || sessionIdFromTracePath(filePath);
4212
+ const sessionId = trace.sessionId || sessionMeta.sessionId || await resolveSessionIdFromSiblingTraces(filePath) || sessionIdFromTracePath(filePath);
4213
4213
  let cwd = sessionMeta.cwd;
4214
4214
  if (!cwd && trace.startedAt) {
4215
4215
  cwd = await cwdFromHistoryFile(trace.startedAt, options);
@@ -4565,46 +4565,71 @@ function normalizeModelCandidate(value) {
4565
4565
  }
4566
4566
  return trimmed;
4567
4567
  }
4568
- function extractUsageFromGenerationSpan(span) {
4569
- const parsed = parseEmbeddedJson(span.toolOutput);
4570
- if (!Array.isArray(parsed) || parsed.length === 0) {
4571
- return void 0;
4572
- }
4573
- const first = parsed[0];
4574
- if (!isPlainObject(first)) {
4575
- return void 0;
4576
- }
4577
- const usage = objectField(first, "usage");
4578
- if (!isPlainObject(usage)) {
4568
+ function finitePositive(value) {
4569
+ if (typeof value !== "number" && typeof value !== "string") {
4579
4570
  return void 0;
4580
4571
  }
4572
+ const parsed = Number(value);
4573
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
4574
+ }
4575
+ function extractUsageFromGenerationSpan(span) {
4576
+ const parsed = parseEmbeddedJson(span.toolOutput);
4577
+ const first = Array.isArray(parsed) ? parsed[0] : void 0;
4578
+ const usage = isPlainObject(first) ? objectField(first, "usage") : {};
4581
4579
  const inputTokens = numberField(usage, "prompt_tokens");
4582
4580
  const outputTokens = numberField(usage, "completion_tokens");
4583
4581
  const totalTokens = numberField(usage, "total_tokens");
4584
4582
  const details = objectField(usage, "prompt_tokens_details");
4585
4583
  const cachedTokens = isPlainObject(details) ? numberField(details, "cached_tokens") : void 0;
4584
+ const otelAttributes = span.attributes || {};
4585
+ const otelInputTokens = finitePositive(otelAttributes["gen_ai.usage.input_tokens"]);
4586
+ const otelOutputTokens = finitePositive(otelAttributes["gen_ai.usage.output_tokens"]);
4587
+ const otelCacheReadTokens = finitePositive(otelAttributes["gen_ai.usage.cache_read_input_tokens"]);
4588
+ const otelCacheCreationTokens = finitePositive(otelAttributes["gen_ai.usage.cache_creation_input_tokens"]);
4589
+ const otelReasoningTokens = finitePositive(otelAttributes["gen_ai.usage.reasoning_output_tokens"]);
4590
+ const tokensInput = otelInputTokens ?? inputTokens;
4591
+ const tokensOutput = otelOutputTokens ?? outputTokens;
4592
+ const tokensCacheReadInput = otelCacheReadTokens ?? cachedTokens;
4593
+ const tokensCachedInput = (tokensCacheReadInput ?? 0) + (otelCacheCreationTokens ?? 0);
4594
+ const tokensTotal = totalTokens || (tokensInput ?? 0) + (tokensOutput ?? 0);
4595
+ if (!tokensInput && !tokensOutput && !tokensCachedInput && !otelReasoningTokens && !tokensTotal) {
4596
+ return void 0;
4597
+ }
4586
4598
  return {
4587
- tokensInput: inputTokens || void 0,
4588
- tokensCachedInput: cachedTokens || void 0,
4589
- tokensOutput: outputTokens || void 0,
4590
- tokensTotal: totalTokens || void 0,
4599
+ tokensInput: tokensInput || void 0,
4600
+ tokensOutput: tokensOutput || void 0,
4601
+ tokensCachedInput: tokensCachedInput || void 0,
4602
+ tokensCacheReadInput: tokensCacheReadInput || void 0,
4603
+ tokensCacheCreationInput: otelCacheCreationTokens,
4604
+ tokensReasoningOutput: otelReasoningTokens,
4605
+ tokensTotal: tokensTotal || void 0,
4591
4606
  modelCalls: 1
4592
4607
  };
4593
4608
  }
4594
4609
  function modelUsageFromTrace(trace, generationIndex, totalGenerations) {
4595
4610
  const info = trace.modelInfo;
4596
- if (!info || !info.totalInputTokens && !info.totalOutputTokens && !trace.totalTokens) {
4611
+ const otelAttributes = trace.attributes || {};
4612
+ const otelInputTokens = finitePositive(otelAttributes["gen_ai.usage.input_tokens"]);
4613
+ const otelOutputTokens = finitePositive(otelAttributes["gen_ai.usage.output_tokens"]);
4614
+ const otelCacheReadTokens = finitePositive(otelAttributes["gen_ai.usage.cache_read_input_tokens"]);
4615
+ const otelCacheCreationTokens = finitePositive(otelAttributes["gen_ai.usage.cache_creation_input_tokens"]);
4616
+ const otelReasoningTokens = finitePositive(otelAttributes["gen_ai.usage.reasoning_output_tokens"]);
4617
+ if (!info && !trace.totalTokens && !otelInputTokens && !otelOutputTokens && !otelCacheReadTokens && !otelCacheCreationTokens && !otelReasoningTokens) {
4597
4618
  return void 0;
4598
4619
  }
4620
+ const inputTokens = otelInputTokens ?? (info?.totalInputTokens || 0);
4621
+ const outputTokens = otelOutputTokens ?? (info?.totalOutputTokens || 0);
4622
+ const cacheReadTokens = otelCacheReadTokens ?? info?.totalCachedTokens;
4623
+ const cachedTokens = (cacheReadTokens ?? 0) + (otelCacheCreationTokens ?? 0);
4624
+ const totalTokens = trace.totalTokens || inputTokens + outputTokens;
4599
4625
  if (generationIndex === 1) {
4600
- const inputTokens = info.totalInputTokens || 0;
4601
- const outputTokens = info.totalOutputTokens || 0;
4602
- const cachedTokens = info.totalCachedTokens || 0;
4603
- const totalTokens = trace.totalTokens || inputTokens + outputTokens;
4604
4626
  return {
4605
4627
  tokensInput: inputTokens || void 0,
4606
4628
  tokensCachedInput: cachedTokens || void 0,
4629
+ tokensCacheReadInput: cacheReadTokens,
4630
+ tokensCacheCreationInput: otelCacheCreationTokens || void 0,
4607
4631
  tokensOutput: outputTokens || void 0,
4632
+ tokensReasoningOutput: otelReasoningTokens || void 0,
4608
4633
  tokensTotal: totalTokens || void 0,
4609
4634
  modelCalls: totalGenerations || 1
4610
4635
  };
@@ -4615,6 +4640,42 @@ function sessionIdFromTracePath(filePath) {
4615
4640
  const match = path9.basename(filePath).match(/trace_([0-9a-f]{32})/);
4616
4641
  return match?.[1] ? `codebuddy_${match[1]}` : `codebuddy_${createStableHash(filePath).slice(0, 24)}`;
4617
4642
  }
4643
+ var siblingSessionIdByDir = /* @__PURE__ */ new Map();
4644
+ async function resolveSessionIdFromSiblingTraces(filePath) {
4645
+ const dir = path9.dirname(filePath);
4646
+ let cached = siblingSessionIdByDir.get(dir);
4647
+ if (!cached) {
4648
+ cached = (async () => {
4649
+ try {
4650
+ const entries = await readdir4(dir);
4651
+ for (const entry of entries) {
4652
+ if (!entry.startsWith("trace_") || !entry.endsWith(".json")) {
4653
+ continue;
4654
+ }
4655
+ const siblingPath = path9.join(dir, entry);
4656
+ if (siblingPath === filePath) {
4657
+ continue;
4658
+ }
4659
+ try {
4660
+ const text = await readFile5(siblingPath, "utf8");
4661
+ const data = JSON.parse(text);
4662
+ if (isPlainObject(data) && isPlainObject(data.trace)) {
4663
+ const sid = stringField(data.trace, "sessionId");
4664
+ if (sid) {
4665
+ return sid;
4666
+ }
4667
+ }
4668
+ } catch {
4669
+ }
4670
+ }
4671
+ } catch {
4672
+ }
4673
+ return void 0;
4674
+ })();
4675
+ siblingSessionIdByDir.set(dir, cached);
4676
+ }
4677
+ return cached;
4678
+ }
4618
4679
  function resolveHome(options) {
4619
4680
  return options.home ? path9.resolve(String(options.home)) : process.env.HOME || __require("node:os").homedir();
4620
4681
  }
@@ -7579,9 +7640,8 @@ function rebuildEventIdentity2(event) {
7579
7640
  }
7580
7641
  };
7581
7642
  }
7582
- async function loadQoderCnModelNames(configDir2) {
7643
+ async function parseModelNamesFromDynamicTexts(dynamicTextsPath) {
7583
7644
  try {
7584
- const dynamicTextsPath = path17.join(configDir2, ".auth", "dynamic-texts.json");
7585
7645
  const content = await readFile10(dynamicTextsPath, "utf8");
7586
7646
  const json = JSON.parse(content);
7587
7647
  const texts = json.texts || {};
@@ -7597,6 +7657,19 @@ async function loadQoderCnModelNames(configDir2) {
7597
7657
  return {};
7598
7658
  }
7599
7659
  }
7660
+ async function loadQoderCnModelNames(configDir2) {
7661
+ const map = await parseModelNamesFromDynamicTexts(path17.join(configDir2, ".auth", "dynamic-texts.json"));
7662
+ const siblingConfigDir = configDir2.replace(/\.qoder-cn$/, ".qoder");
7663
+ if (siblingConfigDir !== configDir2) {
7664
+ const siblingMap = await parseModelNamesFromDynamicTexts(path17.join(siblingConfigDir, ".auth", "dynamic-texts.json"));
7665
+ for (const [key, val] of Object.entries(siblingMap)) {
7666
+ if (!(key in map)) {
7667
+ map[key] = val;
7668
+ }
7669
+ }
7670
+ }
7671
+ return map;
7672
+ }
7600
7673
  async function loadQoderCnSegmentModelCalls(filePath, isSubagentSession, modelMap) {
7601
7674
  const { configDir: configDir2, projectName, sessionId } = parseQoderCnPaths(filePath);
7602
7675
  const segmentsPath = path17.join(configDir2, "logs", "sessions", projectName, sessionId, "segments");
@@ -8382,9 +8455,8 @@ function rebuildEventIdentity3(event) {
8382
8455
  }
8383
8456
  };
8384
8457
  }
8385
- async function loadQoderModelNames(configDir2) {
8458
+ async function parseModelNamesFromDynamicTexts2(dynamicTextsPath) {
8386
8459
  try {
8387
- const dynamicTextsPath = path18.join(configDir2, ".auth", "dynamic-texts.json");
8388
8460
  const content = await readFile11(dynamicTextsPath, "utf8");
8389
8461
  const json = JSON.parse(content);
8390
8462
  const texts = json.texts || {};
@@ -8400,6 +8472,19 @@ async function loadQoderModelNames(configDir2) {
8400
8472
  return {};
8401
8473
  }
8402
8474
  }
8475
+ async function loadQoderModelNames(configDir2) {
8476
+ const map = await parseModelNamesFromDynamicTexts2(path18.join(configDir2, ".auth", "dynamic-texts.json"));
8477
+ const siblingConfigDir = configDir2.replace(/\.qoder$/, ".qoder-cn");
8478
+ if (siblingConfigDir !== configDir2) {
8479
+ const siblingMap = await parseModelNamesFromDynamicTexts2(path18.join(siblingConfigDir, ".auth", "dynamic-texts.json"));
8480
+ for (const [key, val] of Object.entries(siblingMap)) {
8481
+ if (!(key in map)) {
8482
+ map[key] = val;
8483
+ }
8484
+ }
8485
+ }
8486
+ return map;
8487
+ }
8403
8488
  async function loadQoderSegmentModelCalls(filePath, isSubagentSession, modelMap) {
8404
8489
  const { configDir: configDir2, projectName, sessionId } = parseQoderPaths(filePath);
8405
8490
  const segmentsPath = path18.join(configDir2, "logs", "sessions", projectName, sessionId, "segments");
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@yhong91/vibetime",
3
3
  "type": "module",
4
- "version": "0.1.38",
4
+ "version": "0.1.40",
5
5
  "description": "vibetime CLI — install AI-agent hooks (Claude Code, Codex, OpenCode, Pi) and report activity to vibetime.",
6
6
  "license": "MIT",
7
7
  "publishConfig": {