@link-assistant/agent 0.8.6 → 0.8.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/agent",
3
- "version": "0.8.6",
3
+ "version": "0.8.7",
4
4
  "description": "A minimal, public domain AI CLI agent compatible with OpenCode's JSON interface. Bun-only runtime.",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -408,9 +408,18 @@ export namespace Session {
408
408
  }
409
409
 
410
410
  try {
411
- // Handle undefined/null explicitly - Number() would convert these to 0 or NaN
411
+ // Handle undefined/null gracefully by returning 0
412
+ // These are expected for optional fields like cachedInputTokens, reasoningTokens
413
+ // See: https://github.com/link-assistant/agent/issues/127
412
414
  if (value === undefined || value === null) {
413
- throw new Error(`Cannot convert ${value} to number`);
415
+ if (Flag.OPENCODE_VERBOSE) {
416
+ log.debug(() => ({
417
+ message: 'toNumber received undefined/null, returning 0',
418
+ context,
419
+ valueType: typeof value,
420
+ }));
421
+ }
422
+ return 0;
414
423
  }
415
424
 
416
425
  // Handle objects with a 'total' field (e.g., { total: 8707, noCache: 6339, cacheRead: 2368 })
@@ -572,9 +581,28 @@ export namespace Session {
572
581
  const safeNum = (n: number): number =>
573
582
  Number.isNaN(n) || !Number.isFinite(n) ? 0 : n;
574
583
 
575
- const cachedInputTokens = safeNum(
584
+ // Extract top-level cachedInputTokens
585
+ const topLevelCachedInputTokens = safeNum(
576
586
  toNumber(input.usage.cachedInputTokens, 'cachedInputTokens')
577
587
  );
588
+
589
+ // Some providers (e.g., opencode/grok-code) nest cacheRead inside inputTokens object
590
+ // e.g., inputTokens: { total: 12703, noCache: 12511, cacheRead: 192 }
591
+ // See: https://github.com/link-assistant/agent/issues/127
592
+ const inputTokensObj = input.usage.inputTokens;
593
+ const nestedCacheRead =
594
+ typeof inputTokensObj === 'object' && inputTokensObj !== null
595
+ ? safeNum(
596
+ toNumber(
597
+ (inputTokensObj as { cacheRead?: unknown }).cacheRead,
598
+ 'inputTokens.cacheRead'
599
+ )
600
+ )
601
+ : 0;
602
+
603
+ // Use top-level if available, otherwise fall back to nested
604
+ const cachedInputTokens = topLevelCachedInputTokens || nestedCacheRead;
605
+
578
606
  const excludesCachedTokens = !!(
579
607
  input.metadata?.['anthropic'] || input.metadata?.['bedrock']
580
608
  );
@@ -595,12 +623,28 @@ export namespace Session {
595
623
  )
596
624
  );
597
625
 
626
+ // Extract reasoning tokens - some providers nest it inside outputTokens
627
+ // e.g., outputTokens: { total: 562, text: -805, reasoning: 1367 }
628
+ // See: https://github.com/link-assistant/agent/issues/127
629
+ const topLevelReasoningTokens = safeNum(
630
+ toNumber(input.usage?.reasoningTokens, 'reasoningTokens')
631
+ );
632
+ const outputTokensObj = input.usage.outputTokens;
633
+ const nestedReasoning =
634
+ typeof outputTokensObj === 'object' && outputTokensObj !== null
635
+ ? safeNum(
636
+ toNumber(
637
+ (outputTokensObj as { reasoning?: unknown }).reasoning,
638
+ 'outputTokens.reasoning'
639
+ )
640
+ )
641
+ : 0;
642
+ const reasoningTokens = topLevelReasoningTokens || nestedReasoning;
643
+
598
644
  const tokens = {
599
645
  input: Math.max(0, adjustedInputTokens), // Ensure non-negative
600
646
  output: safeNum(toNumber(input.usage.outputTokens, 'outputTokens')),
601
- reasoning: safeNum(
602
- toNumber(input.usage?.reasoningTokens, 'reasoningTokens')
603
- ),
647
+ reasoning: reasoningTokens,
604
648
  cache: {
605
649
  write: cacheWriteTokens,
606
650
  read: cachedInputTokens,