@link-assistant/agent 0.16.0 → 0.16.2
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 +4 -4
- package/src/session/index.ts +50 -6
- package/src/session/processor.ts +18 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@link-assistant/agent",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.2",
|
|
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",
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"@ai-sdk/openai": "^2.0.89",
|
|
66
66
|
"@ai-sdk/openai-compatible": "^1.0.32",
|
|
67
67
|
"@ai-sdk/xai": "^2.0.33",
|
|
68
|
-
"@openrouter/ai-sdk-provider": "^
|
|
68
|
+
"@openrouter/ai-sdk-provider": "^2.2.3",
|
|
69
69
|
"@clack/prompts": "^0.11.0",
|
|
70
70
|
"@hono/standard-validator": "^0.2.0",
|
|
71
71
|
"@hono/zod-validator": "^0.7.5",
|
|
@@ -74,8 +74,8 @@
|
|
|
74
74
|
"@octokit/rest": "^22.0.1",
|
|
75
75
|
"@openauthjs/openauth": "^0.4.3",
|
|
76
76
|
"@opencode-ai/sdk": "^1.0.78",
|
|
77
|
-
"@opentui/core": "^0.1.
|
|
78
|
-
"@opentui/solid": "^0.1.
|
|
77
|
+
"@opentui/core": "^0.1.79",
|
|
78
|
+
"@opentui/solid": "^0.1.79",
|
|
79
79
|
"@parcel/watcher": "^2.5.1",
|
|
80
80
|
"@solid-primitives/event-bus": "^1.1.2",
|
|
81
81
|
"@standard-community/standard-json": "^0.3.5",
|
package/src/session/index.ts
CHANGED
|
@@ -614,15 +614,59 @@ export namespace Session {
|
|
|
614
614
|
const safeNum = (n: number): number =>
|
|
615
615
|
Number.isNaN(n) || !Number.isFinite(n) ? 0 : n;
|
|
616
616
|
|
|
617
|
+
// Check if standard usage has valid data (inputTokens or outputTokens defined)
|
|
618
|
+
// If not, try to extract from providerMetadata.openrouter.usage
|
|
619
|
+
// This handles cases where OpenRouter-compatible APIs (like Kilo) put usage in metadata
|
|
620
|
+
// See: https://github.com/link-assistant/agent/issues/187
|
|
621
|
+
const openrouterUsage = input.metadata?.['openrouter']?.['usage'] as
|
|
622
|
+
| {
|
|
623
|
+
promptTokens?: number;
|
|
624
|
+
completionTokens?: number;
|
|
625
|
+
totalTokens?: number;
|
|
626
|
+
cost?: number;
|
|
627
|
+
promptTokensDetails?: { cachedTokens?: number };
|
|
628
|
+
completionTokensDetails?: { reasoningTokens?: number };
|
|
629
|
+
costDetails?: { upstreamInferenceCost?: number };
|
|
630
|
+
}
|
|
631
|
+
| undefined;
|
|
632
|
+
|
|
633
|
+
const standardUsageIsEmpty =
|
|
634
|
+
input.usage.inputTokens === undefined &&
|
|
635
|
+
input.usage.outputTokens === undefined;
|
|
636
|
+
|
|
637
|
+
// If standard usage is empty but openrouter metadata has usage, use it as source
|
|
638
|
+
let effectiveUsage = input.usage;
|
|
639
|
+
if (standardUsageIsEmpty && openrouterUsage) {
|
|
640
|
+
if (Flag.OPENCODE_VERBOSE) {
|
|
641
|
+
log.debug(() => ({
|
|
642
|
+
message:
|
|
643
|
+
'Standard usage empty, falling back to openrouter metadata',
|
|
644
|
+
openrouterUsage: JSON.stringify(openrouterUsage),
|
|
645
|
+
}));
|
|
646
|
+
}
|
|
647
|
+
// Create a usage-like object from openrouter metadata
|
|
648
|
+
// The openrouter usage uses camelCase: promptTokens, completionTokens
|
|
649
|
+
effectiveUsage = {
|
|
650
|
+
...input.usage,
|
|
651
|
+
inputTokens: openrouterUsage.promptTokens,
|
|
652
|
+
outputTokens: openrouterUsage.completionTokens,
|
|
653
|
+
totalTokens: openrouterUsage.totalTokens,
|
|
654
|
+
cachedInputTokens:
|
|
655
|
+
openrouterUsage.promptTokensDetails?.cachedTokens ?? 0,
|
|
656
|
+
reasoningTokens:
|
|
657
|
+
openrouterUsage.completionTokensDetails?.reasoningTokens ?? 0,
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
|
|
617
661
|
// Extract top-level cachedInputTokens
|
|
618
662
|
const topLevelCachedInputTokens = safeNum(
|
|
619
|
-
toNumber(
|
|
663
|
+
toNumber(effectiveUsage.cachedInputTokens, 'cachedInputTokens')
|
|
620
664
|
);
|
|
621
665
|
|
|
622
666
|
// Some providers (e.g., opencode/grok-code) nest cacheRead inside inputTokens object
|
|
623
667
|
// e.g., inputTokens: { total: 12703, noCache: 12511, cacheRead: 192 }
|
|
624
668
|
// See: https://github.com/link-assistant/agent/issues/127
|
|
625
|
-
const inputTokensObj =
|
|
669
|
+
const inputTokensObj = effectiveUsage.inputTokens;
|
|
626
670
|
const nestedCacheRead =
|
|
627
671
|
typeof inputTokensObj === 'object' && inputTokensObj !== null
|
|
628
672
|
? safeNum(
|
|
@@ -641,7 +685,7 @@ export namespace Session {
|
|
|
641
685
|
);
|
|
642
686
|
|
|
643
687
|
const rawInputTokens = safeNum(
|
|
644
|
-
toNumber(
|
|
688
|
+
toNumber(effectiveUsage.inputTokens, 'inputTokens')
|
|
645
689
|
);
|
|
646
690
|
const adjustedInputTokens = excludesCachedTokens
|
|
647
691
|
? rawInputTokens
|
|
@@ -660,9 +704,9 @@ export namespace Session {
|
|
|
660
704
|
// e.g., outputTokens: { total: 562, text: -805, reasoning: 1367 }
|
|
661
705
|
// See: https://github.com/link-assistant/agent/issues/127
|
|
662
706
|
const topLevelReasoningTokens = safeNum(
|
|
663
|
-
toNumber(
|
|
707
|
+
toNumber(effectiveUsage?.reasoningTokens, 'reasoningTokens')
|
|
664
708
|
);
|
|
665
|
-
const outputTokensObj =
|
|
709
|
+
const outputTokensObj = effectiveUsage.outputTokens;
|
|
666
710
|
const nestedReasoning =
|
|
667
711
|
typeof outputTokensObj === 'object' && outputTokensObj !== null
|
|
668
712
|
? safeNum(
|
|
@@ -676,7 +720,7 @@ export namespace Session {
|
|
|
676
720
|
|
|
677
721
|
const tokens = {
|
|
678
722
|
input: Math.max(0, adjustedInputTokens), // Ensure non-negative
|
|
679
|
-
output: safeNum(toNumber(
|
|
723
|
+
output: safeNum(toNumber(effectiveUsage.outputTokens, 'outputTokens')),
|
|
680
724
|
reasoning: reasoningTokens,
|
|
681
725
|
cache: {
|
|
682
726
|
write: cacheWriteTokens,
|
package/src/session/processor.ts
CHANGED
|
@@ -256,9 +256,24 @@ export namespace SessionProcessor {
|
|
|
256
256
|
});
|
|
257
257
|
// Use toFinishReason to safely convert object/string finishReason to string
|
|
258
258
|
// See: https://github.com/link-assistant/agent/issues/125
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
259
|
+
// Also check providerMetadata for finish reason if undefined
|
|
260
|
+
// OpenRouter-compatible APIs (like Kilo) may not populate finishReason in standard location
|
|
261
|
+
// See: https://github.com/link-assistant/agent/issues/187
|
|
262
|
+
let rawFinishReason = value.finishReason;
|
|
263
|
+
if (rawFinishReason === undefined) {
|
|
264
|
+
// Try to extract from OpenRouter provider metadata
|
|
265
|
+
// The openrouter metadata may contain finish_reason or other indicators
|
|
266
|
+
const openrouterMeta =
|
|
267
|
+
value.providerMetadata?.['openrouter'];
|
|
268
|
+
if (openrouterMeta) {
|
|
269
|
+
// OpenRouter sometimes includes reason in reasoning_details or annotations
|
|
270
|
+
// For now, if we have usage data, we can assume it completed successfully
|
|
271
|
+
if (openrouterMeta['usage']) {
|
|
272
|
+
rawFinishReason = 'stop';
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
const finishReason = Session.toFinishReason(rawFinishReason);
|
|
262
277
|
input.assistantMessage.finish = finishReason;
|
|
263
278
|
input.assistantMessage.cost += usage.cost;
|
|
264
279
|
input.assistantMessage.tokens = usage.tokens;
|