@link-assistant/agent 0.16.9 → 0.16.10

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.16.9",
3
+ "version": "0.16.10",
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",
package/src/index.js CHANGED
@@ -54,6 +54,37 @@ try {
54
54
  // Track if any errors occurred during execution
55
55
  let hasError = false;
56
56
 
57
+ // Intercept stderr writes to ensure ALL output is JSON (#200)
58
+ // Bun runtime may print errors in plain text format (e.g., stack traces with source code)
59
+ // This interceptor wraps any non-JSON stderr output in a JSON envelope
60
+ const originalStderrWrite = process.stderr.write.bind(process.stderr);
61
+ process.stderr.write = function (chunk, encoding, callback) {
62
+ const str = typeof chunk === 'string' ? chunk : chunk.toString();
63
+ const trimmed = str.trim();
64
+ if (!trimmed) {
65
+ return originalStderrWrite(chunk, encoding, callback);
66
+ }
67
+
68
+ // Check if it's already valid JSON
69
+ if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
70
+ try {
71
+ JSON.parse(trimmed);
72
+ // Already JSON, pass through
73
+ return originalStderrWrite(chunk, encoding, callback);
74
+ } catch (_e) {
75
+ // Not valid JSON, wrap it
76
+ }
77
+ }
78
+
79
+ // Wrap non-JSON stderr output in JSON envelope
80
+ const wrapped = `${JSON.stringify({
81
+ type: 'error',
82
+ errorType: 'RuntimeError',
83
+ message: trimmed,
84
+ })}\n`;
85
+ return originalStderrWrite(wrapped, encoding, callback);
86
+ };
87
+
57
88
  // Install global error handlers to ensure non-zero exit codes
58
89
  // All output is JSON to ensure machine-parsability (#200)
59
90
  process.on('uncaughtException', (error) => {
@@ -1416,21 +1416,70 @@ export namespace Provider {
1416
1416
  providerID === 'link-assistant' || providerID === 'link-assistant/cache';
1417
1417
 
1418
1418
  // For synthetic providers, we don't need model info from the database
1419
- const info = isSyntheticProvider ? null : provider.info.models[modelID];
1419
+ let info = isSyntheticProvider ? null : provider.info.models[modelID];
1420
1420
  if (!isSyntheticProvider && !info) {
1421
+ // Model not in provider's known list - try refreshing the cache first (#200)
1422
+ // This handles stale bundled data or expired cache (1-hour TTL)
1423
+ log.info(() => ({
1424
+ message: 'model not in catalog - refreshing models cache',
1425
+ providerID,
1426
+ modelID,
1427
+ }));
1428
+ try {
1429
+ await ModelsDev.refresh();
1430
+ const freshDB = await ModelsDev.get();
1431
+ const freshProvider = freshDB[providerID];
1432
+ if (freshProvider?.models[modelID]) {
1433
+ // Model found after refresh - update provider info and use it
1434
+ provider.info.models[modelID] = freshProvider.models[modelID];
1435
+ info = freshProvider.models[modelID];
1436
+ log.info(() => ({
1437
+ message: 'model found after cache refresh',
1438
+ providerID,
1439
+ modelID,
1440
+ }));
1441
+ }
1442
+ } catch (refreshError) {
1443
+ log.warn(() => ({
1444
+ message: 'cache refresh failed',
1445
+ error:
1446
+ refreshError instanceof Error
1447
+ ? refreshError.message
1448
+ : String(refreshError),
1449
+ }));
1450
+ }
1451
+ }
1452
+
1453
+ if (!isSyntheticProvider && !info) {
1454
+ // Still not found after refresh - create fallback info and try anyway
1455
+ // Provider may support unlisted models
1421
1456
  const availableInProvider = Object.keys(provider.info.models).slice(
1422
1457
  0,
1423
1458
  10
1424
1459
  );
1425
- const suggestion = `Model "${modelID}" not found in provider "${providerID}". Available models: ${availableInProvider.join(', ')}${Object.keys(provider.info.models).length > 10 ? ` (and ${Object.keys(provider.info.models).length - 10} more)` : ''}.`;
1426
- log.error(() => ({
1427
- message: 'model not found in provider',
1460
+ log.warn(() => ({
1461
+ message:
1462
+ 'model not in provider catalog after refresh - attempting anyway (may be unlisted)',
1428
1463
  providerID,
1429
1464
  modelID,
1430
1465
  availableModels: availableInProvider,
1431
1466
  totalModels: Object.keys(provider.info.models).length,
1432
1467
  }));
1433
- throw new ModelNotFoundError({ providerID, modelID, suggestion });
1468
+
1469
+ // Create a minimal fallback model info so SDK loading can proceed
1470
+ // Use sensible defaults - the provider will reject if the model truly doesn't exist
1471
+ info = {
1472
+ id: modelID,
1473
+ name: modelID,
1474
+ release_date: '',
1475
+ attachment: false,
1476
+ reasoning: false,
1477
+ temperature: true,
1478
+ tool_call: true,
1479
+ cost: { input: 0, output: 0 },
1480
+ limit: { context: 128000, output: 16384 },
1481
+ options: {},
1482
+ } as ModelsDev.Model;
1434
1483
  }
1435
1484
 
1436
1485
  try {