@link-assistant/agent 0.8.17 → 0.8.19

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.17",
3
+ "version": "0.8.19",
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",
@@ -71,7 +71,7 @@
71
71
  "@standard-community/standard-json": "^0.3.5",
72
72
  "@standard-community/standard-openapi": "^0.2.9",
73
73
  "@zip.js/zip.js": "^2.8.10",
74
- "ai": "6.0.0-beta.99",
74
+ "ai": "^6.0.1",
75
75
  "chokidar": "^4.0.3",
76
76
  "clipboardy": "^5.0.0",
77
77
  "command-stream": "^0.7.1",
package/src/index.js CHANGED
@@ -68,11 +68,16 @@ process.on('uncaughtException', (error) => {
68
68
 
69
69
  process.on('unhandledRejection', (reason, _promise) => {
70
70
  hasError = true;
71
- outputError({
71
+ const errorOutput = {
72
72
  errorType: 'UnhandledRejection',
73
73
  message: reason?.message || String(reason),
74
74
  stack: reason?.stack,
75
- });
75
+ };
76
+ // If the error has a data property with a suggestion (e.g., ProviderModelNotFoundError), add it as a hint
77
+ if (reason?.data?.suggestion) {
78
+ errorOutput.hint = reason.data.suggestion;
79
+ }
80
+ outputError(errorOutput);
76
81
  process.exit(1);
77
82
  });
78
83
 
@@ -909,7 +909,23 @@ export namespace Provider {
909
909
  log.info(() => ({ message: 'getModel', providerID, modelID }));
910
910
 
911
911
  const provider = s.providers[providerID];
912
- if (!provider) throw new ModelNotFoundError({ providerID, modelID });
912
+ if (!provider) {
913
+ // Check if this model ID might exist in another provider (e.g., OpenRouter)
914
+ // This helps users who use formats like "z-ai/glm-4.7" instead of "openrouter/z-ai/glm-4.7"
915
+ const fullModelKey = `${providerID}/${modelID}`;
916
+ let suggestion: string | undefined;
917
+
918
+ for (const [knownProviderID, knownProvider] of Object.entries(
919
+ s.providers
920
+ )) {
921
+ if (knownProvider.info.models[fullModelKey]) {
922
+ suggestion = `Did you mean: ${knownProviderID}/${fullModelKey}?`;
923
+ break;
924
+ }
925
+ }
926
+
927
+ throw new ModelNotFoundError({ providerID, modelID, suggestion });
928
+ }
913
929
 
914
930
  // For synthetic providers (like link-assistant/echo and link-assistant/cache), skip SDK loading
915
931
  // These providers have a custom getModel function that creates the model directly
@@ -1077,6 +1093,7 @@ export namespace Provider {
1077
1093
  z.object({
1078
1094
  providerID: z.string(),
1079
1095
  modelID: z.string(),
1096
+ suggestion: z.string().optional(),
1080
1097
  })
1081
1098
  );
1082
1099
 
@@ -580,6 +580,27 @@ export namespace Session {
580
580
  metadata: z.custom<ProviderMetadata>().optional(),
581
581
  }),
582
582
  (input) => {
583
+ // Handle undefined or null usage gracefully
584
+ // Some providers (e.g., OpenCode with Kimi K2.5) may return incomplete usage data
585
+ // See: https://github.com/link-assistant/agent/issues/152
586
+ if (!input.usage) {
587
+ log.warn(() => ({
588
+ message: 'getUsage received undefined usage, returning zero values',
589
+ providerMetadata: input.metadata
590
+ ? JSON.stringify(input.metadata)
591
+ : 'none',
592
+ }));
593
+ return {
594
+ cost: 0,
595
+ tokens: {
596
+ input: 0,
597
+ output: 0,
598
+ reasoning: 0,
599
+ cache: { read: 0, write: 0 },
600
+ },
601
+ };
602
+ }
603
+
583
604
  // Log raw usage data in verbose mode for debugging
584
605
  if (Flag.OPENCODE_VERBOSE) {
585
606
  log.debug(() => ({
@@ -219,9 +219,17 @@ export namespace SessionProcessor {
219
219
  break;
220
220
 
221
221
  case 'finish-step':
222
+ // Safely handle missing or undefined usage data
223
+ // Some providers (e.g., OpenCode with Kimi K2.5) may return incomplete usage
224
+ // See: https://github.com/link-assistant/agent/issues/152
225
+ const safeUsage = value.usage ?? {
226
+ inputTokens: 0,
227
+ outputTokens: 0,
228
+ totalTokens: 0,
229
+ };
222
230
  const usage = Session.getUsage({
223
231
  model: input.model,
224
- usage: value.usage,
232
+ usage: safeUsage,
225
233
  metadata: value.providerMetadata,
226
234
  });
227
235
  // Use toFinishReason to safely convert object/string finishReason to string
@@ -322,6 +330,34 @@ export namespace SessionProcessor {
322
330
  }
323
331
  } catch (e) {
324
332
  log.error(() => ({ message: 'process', error: e }));
333
+
334
+ // Check for AI SDK usage-related TypeError (input_tokens undefined)
335
+ // This happens when providers return incomplete usage data
336
+ // See: https://github.com/link-assistant/agent/issues/152
337
+ if (
338
+ e instanceof TypeError &&
339
+ (e.message.includes('input_tokens') ||
340
+ e.message.includes('output_tokens') ||
341
+ e.message.includes("reading 'input_tokens'") ||
342
+ e.message.includes("reading 'output_tokens'"))
343
+ ) {
344
+ log.warn(() => ({
345
+ message:
346
+ 'Provider returned invalid usage data, continuing with zero usage',
347
+ errorMessage: e.message,
348
+ providerID: input.providerID,
349
+ }));
350
+ // Set default token values to prevent crash
351
+ input.assistantMessage.tokens = {
352
+ input: 0,
353
+ output: 0,
354
+ reasoning: 0,
355
+ cache: { read: 0, write: 0 },
356
+ };
357
+ // Continue processing instead of failing
358
+ continue;
359
+ }
360
+
325
361
  const error = MessageV2.fromError(e, {
326
362
  providerID: input.providerID,
327
363
  });