@link-assistant/agent 0.16.13 → 0.16.16
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 +1 -1
- package/src/cli/defaults.ts +15 -0
- package/src/cli/model-config.js +5 -4
- package/src/index.js +2 -1
- package/src/provider/provider.ts +22 -15
- package/src/session/index.ts +42 -2
- package/src/tool/task.ts +3 -2
package/package.json
CHANGED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default CLI configuration values.
|
|
3
|
+
*
|
|
4
|
+
* Centralizing defaults here ensures all code references the same value (#208).
|
|
5
|
+
* When the default model changes, update this file only.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** Default model used when no `--model` CLI argument is provided. */
|
|
9
|
+
export const DEFAULT_MODEL = 'opencode/minimax-m2.5-free';
|
|
10
|
+
|
|
11
|
+
/** Default provider ID extracted from DEFAULT_MODEL. */
|
|
12
|
+
export const DEFAULT_PROVIDER_ID = DEFAULT_MODEL.split('/')[0];
|
|
13
|
+
|
|
14
|
+
/** Default model ID extracted from DEFAULT_MODEL. */
|
|
15
|
+
export const DEFAULT_MODEL_ID = DEFAULT_MODEL.split('/').slice(1).join('/');
|
package/src/cli/model-config.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getModelFromProcessArgv } from './argv.ts';
|
|
2
2
|
import { Log } from '../util/log.ts';
|
|
3
|
+
import { DEFAULT_PROVIDER_ID, DEFAULT_MODEL_ID } from './defaults.ts';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Parse model config from argv. Supports "provider/model" or short "model" format.
|
|
@@ -16,7 +17,7 @@ export async function parseModelConfig(argv, outputError, outputStatus) {
|
|
|
16
17
|
let modelArg = argv.model;
|
|
17
18
|
|
|
18
19
|
// ALWAYS prefer the CLI value over yargs when available (#196)
|
|
19
|
-
// The yargs default
|
|
20
|
+
// The yargs default (DEFAULT_MODEL) can silently override user's --model argument
|
|
20
21
|
if (cliModelArg) {
|
|
21
22
|
if (cliModelArg !== modelArg) {
|
|
22
23
|
Log.Default.warn(() => ({
|
|
@@ -45,7 +46,7 @@ export async function parseModelConfig(argv, outputError, outputStatus) {
|
|
|
45
46
|
// Do NOT fall back to defaults - if the user provided an invalid format, fail clearly (#196)
|
|
46
47
|
if (!providerID || !modelID) {
|
|
47
48
|
throw new Error(
|
|
48
|
-
`Invalid model format: "${modelArg}". Expected "provider/model" format (e.g., "
|
|
49
|
+
`Invalid model format: "${modelArg}". Expected "provider/model" format (e.g., "${DEFAULT_PROVIDER_ID}/${DEFAULT_MODEL_ID}"). ` +
|
|
49
50
|
`Provider: "${providerID || '(empty)'}", Model: "${modelID || '(empty)'}".`
|
|
50
51
|
);
|
|
51
52
|
}
|
|
@@ -124,9 +125,9 @@ export async function parseModelConfig(argv, outputError, outputStatus) {
|
|
|
124
125
|
// Set environment variable for the provider to use
|
|
125
126
|
process.env.CLAUDE_CODE_OAUTH_TOKEN = creds.accessToken;
|
|
126
127
|
|
|
127
|
-
// If user specified the default model (
|
|
128
|
+
// If user specified the default model (DEFAULT_MODEL), switch to claude-oauth
|
|
128
129
|
// If user explicitly specified kilo or another provider, warn but respect their choice
|
|
129
|
-
if (providerID ===
|
|
130
|
+
if (providerID === DEFAULT_PROVIDER_ID && modelID === DEFAULT_MODEL_ID) {
|
|
130
131
|
providerID = 'claude-oauth';
|
|
131
132
|
modelID = 'claude-sonnet-4-5';
|
|
132
133
|
} else if (!['claude-oauth', 'anthropic'].includes(providerID)) {
|
package/src/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import { Server } from './server/server.ts';
|
|
|
6
6
|
import { Instance } from './project/instance.ts';
|
|
7
7
|
import { Log } from './util/log.ts';
|
|
8
8
|
import { parseModelConfig } from './cli/model-config.js';
|
|
9
|
+
import { DEFAULT_MODEL } from './cli/defaults.ts';
|
|
9
10
|
// Bus is used via createBusEventSubscription in event-handler.js
|
|
10
11
|
import { Session } from './session/index.ts';
|
|
11
12
|
import { SessionPrompt } from './session/prompt.ts';
|
|
@@ -588,7 +589,7 @@ async function main() {
|
|
|
588
589
|
.option('model', {
|
|
589
590
|
type: 'string',
|
|
590
591
|
description: 'Model to use in format providerID/modelID',
|
|
591
|
-
default:
|
|
592
|
+
default: DEFAULT_MODEL,
|
|
592
593
|
})
|
|
593
594
|
.option('json-standard', {
|
|
594
595
|
type: 'string',
|
package/src/provider/provider.ts
CHANGED
|
@@ -1272,22 +1272,26 @@ export namespace Provider {
|
|
|
1272
1272
|
}
|
|
1273
1273
|
}
|
|
1274
1274
|
|
|
1275
|
-
|
|
1276
|
-
|
|
1275
|
+
// Use direct (non-lazy) logging for HTTP request/response to ensure output
|
|
1276
|
+
// is not lost when piped through external process wrappers (e.g., solve.mjs).
|
|
1277
|
+
// The verbose check is already done above, so lazy evaluation is not needed here.
|
|
1278
|
+
// See: https://github.com/link-assistant/agent/issues/211
|
|
1279
|
+
log.info('HTTP request', {
|
|
1277
1280
|
providerID: provider.id,
|
|
1278
1281
|
method,
|
|
1279
1282
|
url,
|
|
1280
1283
|
headers: sanitizedHeaders,
|
|
1281
1284
|
bodyPreview,
|
|
1282
|
-
})
|
|
1285
|
+
});
|
|
1283
1286
|
|
|
1284
1287
|
const startMs = Date.now();
|
|
1285
1288
|
try {
|
|
1286
1289
|
const response = await innerFetch(input, init);
|
|
1287
1290
|
const durationMs = Date.now() - startMs;
|
|
1288
1291
|
|
|
1289
|
-
|
|
1290
|
-
|
|
1292
|
+
// Use direct (non-lazy) logging to ensure HTTP response details are captured
|
|
1293
|
+
// See: https://github.com/link-assistant/agent/issues/211
|
|
1294
|
+
log.info('HTTP response', {
|
|
1291
1295
|
providerID: provider.id,
|
|
1292
1296
|
method,
|
|
1293
1297
|
url,
|
|
@@ -1295,7 +1299,7 @@ export namespace Provider {
|
|
|
1295
1299
|
statusText: response.statusText,
|
|
1296
1300
|
durationMs,
|
|
1297
1301
|
responseHeaders: Object.fromEntries(response.headers.entries()),
|
|
1298
|
-
})
|
|
1302
|
+
});
|
|
1299
1303
|
|
|
1300
1304
|
// Log response body for debugging provider failures
|
|
1301
1305
|
// For streaming responses (SSE/event-stream), tee() the stream so the AI SDK
|
|
@@ -1335,14 +1339,15 @@ export namespace Provider {
|
|
|
1335
1339
|
}
|
|
1336
1340
|
}
|
|
1337
1341
|
}
|
|
1338
|
-
|
|
1339
|
-
|
|
1342
|
+
// Use direct (non-lazy) logging for stream body
|
|
1343
|
+
// See: https://github.com/link-assistant/agent/issues/211
|
|
1344
|
+
log.info('HTTP response body (stream)', {
|
|
1340
1345
|
providerID: provider.id,
|
|
1341
1346
|
url,
|
|
1342
1347
|
bodyPreview: truncated
|
|
1343
1348
|
? bodyPreview + `... [truncated]`
|
|
1344
1349
|
: bodyPreview,
|
|
1345
|
-
})
|
|
1350
|
+
});
|
|
1346
1351
|
} catch {
|
|
1347
1352
|
// Ignore logging errors — do not affect the SDK stream
|
|
1348
1353
|
}
|
|
@@ -1362,12 +1367,13 @@ export namespace Provider {
|
|
|
1362
1367
|
? bodyText.slice(0, responseBodyMaxChars) +
|
|
1363
1368
|
`... [truncated, total ${bodyText.length} chars]`
|
|
1364
1369
|
: bodyText;
|
|
1365
|
-
|
|
1366
|
-
|
|
1370
|
+
// Use direct (non-lazy) logging for non-streaming body
|
|
1371
|
+
// See: https://github.com/link-assistant/agent/issues/211
|
|
1372
|
+
log.info('HTTP response body', {
|
|
1367
1373
|
providerID: provider.id,
|
|
1368
1374
|
url,
|
|
1369
1375
|
bodyPreview,
|
|
1370
|
-
})
|
|
1376
|
+
});
|
|
1371
1377
|
return new Response(bodyText, {
|
|
1372
1378
|
status: response.status,
|
|
1373
1379
|
statusText: response.statusText,
|
|
@@ -1379,8 +1385,9 @@ export namespace Provider {
|
|
|
1379
1385
|
return response;
|
|
1380
1386
|
} catch (error) {
|
|
1381
1387
|
const durationMs = Date.now() - startMs;
|
|
1382
|
-
|
|
1383
|
-
|
|
1388
|
+
// Use direct (non-lazy) logging for error path
|
|
1389
|
+
// See: https://github.com/link-assistant/agent/issues/211
|
|
1390
|
+
log.error('HTTP request failed', {
|
|
1384
1391
|
providerID: provider.id,
|
|
1385
1392
|
method,
|
|
1386
1393
|
url,
|
|
@@ -1389,7 +1396,7 @@ export namespace Provider {
|
|
|
1389
1396
|
error instanceof Error
|
|
1390
1397
|
? { name: error.name, message: error.message }
|
|
1391
1398
|
: String(error),
|
|
1392
|
-
})
|
|
1399
|
+
});
|
|
1393
1400
|
throw error;
|
|
1394
1401
|
}
|
|
1395
1402
|
};
|
package/src/session/index.ts
CHANGED
|
@@ -630,9 +630,14 @@ export namespace Session {
|
|
|
630
630
|
}
|
|
631
631
|
| undefined;
|
|
632
632
|
|
|
633
|
+
// Check if standard usage has valid data (inputTokens or outputTokens defined)
|
|
634
|
+
// Also check for zero-valued tokens (some providers set them to 0 instead of undefined)
|
|
633
635
|
const standardUsageIsEmpty =
|
|
634
|
-
input.usage.inputTokens === undefined &&
|
|
635
|
-
|
|
636
|
+
(input.usage.inputTokens === undefined &&
|
|
637
|
+
input.usage.outputTokens === undefined) ||
|
|
638
|
+
(input.usage.inputTokens === 0 &&
|
|
639
|
+
input.usage.outputTokens === 0 &&
|
|
640
|
+
!input.usage.totalTokens);
|
|
636
641
|
|
|
637
642
|
// If standard usage is empty but openrouter metadata has usage, use it as source
|
|
638
643
|
let effectiveUsage = input.usage;
|
|
@@ -658,6 +663,41 @@ export namespace Session {
|
|
|
658
663
|
};
|
|
659
664
|
}
|
|
660
665
|
|
|
666
|
+
// If still empty, try providerMetadata.anthropic.usage as fallback
|
|
667
|
+
// Some providers (e.g., opencode using @ai-sdk/anthropic) return usage in
|
|
668
|
+
// Anthropic-specific metadata with snake_case keys (input_tokens, output_tokens)
|
|
669
|
+
// while the standard AI SDK usage object remains empty.
|
|
670
|
+
// See: https://github.com/link-assistant/agent/issues/211
|
|
671
|
+
if (standardUsageIsEmpty && !openrouterUsage) {
|
|
672
|
+
const anthropicUsage = input.metadata?.['anthropic']?.['usage'] as
|
|
673
|
+
| {
|
|
674
|
+
input_tokens?: number;
|
|
675
|
+
output_tokens?: number;
|
|
676
|
+
cache_creation_input_tokens?: number;
|
|
677
|
+
cache_read_input_tokens?: number;
|
|
678
|
+
}
|
|
679
|
+
| undefined;
|
|
680
|
+
|
|
681
|
+
if (
|
|
682
|
+
anthropicUsage &&
|
|
683
|
+
(anthropicUsage.input_tokens || anthropicUsage.output_tokens)
|
|
684
|
+
) {
|
|
685
|
+
if (Flag.OPENCODE_VERBOSE) {
|
|
686
|
+
log.debug(() => ({
|
|
687
|
+
message:
|
|
688
|
+
'Standard usage empty, falling back to anthropic provider metadata',
|
|
689
|
+
anthropicUsage: JSON.stringify(anthropicUsage),
|
|
690
|
+
}));
|
|
691
|
+
}
|
|
692
|
+
effectiveUsage = {
|
|
693
|
+
...input.usage,
|
|
694
|
+
inputTokens: anthropicUsage.input_tokens ?? 0,
|
|
695
|
+
outputTokens: anthropicUsage.output_tokens ?? 0,
|
|
696
|
+
cachedInputTokens: anthropicUsage.cache_read_input_tokens ?? 0,
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
|
|
661
701
|
// Extract top-level cachedInputTokens
|
|
662
702
|
const topLevelCachedInputTokens = safeNum(
|
|
663
703
|
toNumber(effectiveUsage.cachedInputTokens, 'cachedInputTokens')
|
package/src/tool/task.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { Agent } from '../agent/agent';
|
|
|
9
9
|
import { SessionPrompt } from '../session/prompt';
|
|
10
10
|
import { iife } from '../util/iife';
|
|
11
11
|
import { defer } from '../util/defer';
|
|
12
|
+
import { DEFAULT_PROVIDER_ID, DEFAULT_MODEL_ID } from '../cli/defaults';
|
|
12
13
|
|
|
13
14
|
export const TaskTool = Tool.define('task', async () => {
|
|
14
15
|
const agents = await Agent.list().then((x) =>
|
|
@@ -99,8 +100,8 @@ export const TaskTool = Tool.define('task', async () => {
|
|
|
99
100
|
|
|
100
101
|
const model = agent.model ??
|
|
101
102
|
parentModel ?? {
|
|
102
|
-
modelID:
|
|
103
|
-
providerID:
|
|
103
|
+
modelID: DEFAULT_MODEL_ID,
|
|
104
|
+
providerID: DEFAULT_PROVIDER_ID,
|
|
104
105
|
};
|
|
105
106
|
|
|
106
107
|
function cancel() {
|