@link-assistant/agent 0.16.11 → 0.16.13
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/README.md +6 -6
- package/package.json +1 -1
- package/src/provider/provider.ts +15 -13
- package/src/session/processor.ts +59 -0
- package/src/tool/task.ts +1 -1
package/README.md
CHANGED
|
@@ -20,11 +20,11 @@
|
|
|
20
20
|
|
|
21
21
|
> This is the JavaScript/Bun implementation. See also the [Rust implementation](../rust/README.md).
|
|
22
22
|
|
|
23
|
-
This is an MVP implementation of an OpenCode-compatible CLI agent, focused on maximum efficiency and unrestricted execution. We reproduce OpenCode's `run --format json --model opencode/
|
|
23
|
+
This is an MVP implementation of an OpenCode-compatible CLI agent, focused on maximum efficiency and unrestricted execution. We reproduce OpenCode's `run --format json --model opencode/minimax-m2.5-free` mode with:
|
|
24
24
|
|
|
25
|
-
- ✅ **JSON Input/Output**: Compatible with `opencode run --format json --model opencode/
|
|
25
|
+
- ✅ **JSON Input/Output**: Compatible with `opencode run --format json --model opencode/minimax-m2.5-free`
|
|
26
26
|
- ✅ **Plain Text Input**: Also accepts plain text messages (auto-converted to JSON format)
|
|
27
|
-
- ✅ **Flexible Model Selection**: Defaults to free OpenCode Zen
|
|
27
|
+
- ✅ **Flexible Model Selection**: Defaults to free OpenCode Zen MiniMax M2.5 Free, supports [OpenCode Zen](https://opencode.ai/docs/zen/), [Claude OAuth](../docs/claude-oauth.md), [Groq](../docs/groq.md), and [OpenRouter](../docs/openrouter.md) providers
|
|
28
28
|
- ✅ **No Restrictions**: Fully unrestricted file system and command execution access (no sandbox)
|
|
29
29
|
- ✅ **Minimal Footprint**: Built with Bun.sh for maximum efficiency
|
|
30
30
|
- ✅ **Full Tool Support**: 13 tools including websearch, codesearch, batch - all enabled by default
|
|
@@ -169,7 +169,7 @@ echo '{"message":"hi"}' | agent
|
|
|
169
169
|
**With custom model:**
|
|
170
170
|
|
|
171
171
|
```bash
|
|
172
|
-
echo "hi" | agent --model opencode/
|
|
172
|
+
echo "hi" | agent --model opencode/minimax-m2.5-free
|
|
173
173
|
```
|
|
174
174
|
|
|
175
175
|
### More Examples
|
|
@@ -190,7 +190,7 @@ echo '{"message":"run command","tools":[{"name":"bash","params":{"command":"ls -
|
|
|
190
190
|
**Using different models:**
|
|
191
191
|
|
|
192
192
|
```bash
|
|
193
|
-
# Default model (free
|
|
193
|
+
# Default model (free MiniMax M2.5)
|
|
194
194
|
echo "hi" | agent
|
|
195
195
|
|
|
196
196
|
# Other free models (in order of recommendation)
|
|
@@ -280,7 +280,7 @@ agent [options]
|
|
|
280
280
|
|
|
281
281
|
Options:
|
|
282
282
|
--model Model to use in format providerID/modelID
|
|
283
|
-
Default: opencode/
|
|
283
|
+
Default: opencode/minimax-m2.5-free
|
|
284
284
|
--json-standard JSON output format standard
|
|
285
285
|
Choices: "opencode" (default), "claude" (experimental)
|
|
286
286
|
--use-existing-claude-oauth Use existing Claude OAuth credentials
|
package/package.json
CHANGED
package/src/provider/provider.ts
CHANGED
|
@@ -1201,15 +1201,23 @@ export namespace Provider {
|
|
|
1201
1201
|
sessionID: provider.id,
|
|
1202
1202
|
});
|
|
1203
1203
|
|
|
1204
|
-
// Wrap fetch with verbose HTTP logging
|
|
1205
|
-
//
|
|
1204
|
+
// Wrap fetch with verbose HTTP logging for debugging provider issues.
|
|
1205
|
+
// IMPORTANT: The verbose check is done at call time (not SDK creation time)
|
|
1206
|
+
// because the SDK is cached and Flag.OPENCODE_VERBOSE may change after creation.
|
|
1207
|
+
// When verbose is disabled, the wrapper is a no-op passthrough with negligible overhead.
|
|
1206
1208
|
// See: https://github.com/link-assistant/agent/issues/200
|
|
1207
|
-
|
|
1209
|
+
// See: https://github.com/link-assistant/agent/issues/206
|
|
1210
|
+
{
|
|
1208
1211
|
const innerFetch = options['fetch'];
|
|
1209
1212
|
options['fetch'] = async (
|
|
1210
1213
|
input: RequestInfo | URL,
|
|
1211
1214
|
init?: RequestInit
|
|
1212
1215
|
): Promise<Response> => {
|
|
1216
|
+
// Check verbose flag at call time — not at SDK creation time
|
|
1217
|
+
if (!Flag.OPENCODE_VERBOSE) {
|
|
1218
|
+
return innerFetch(input, init);
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1213
1221
|
const url =
|
|
1214
1222
|
typeof input === 'string'
|
|
1215
1223
|
? input
|
|
@@ -1643,12 +1651,7 @@ export namespace Provider {
|
|
|
1643
1651
|
priority = priority.filter((m) => m !== 'claude-haiku-4.5');
|
|
1644
1652
|
}
|
|
1645
1653
|
if (providerID === 'opencode' || providerID === 'local') {
|
|
1646
|
-
priority = [
|
|
1647
|
-
'kimi-k2.5-free',
|
|
1648
|
-
'minimax-m2.5-free',
|
|
1649
|
-
'gpt-5-nano',
|
|
1650
|
-
'big-pickle',
|
|
1651
|
-
];
|
|
1654
|
+
priority = ['minimax-m2.5-free', 'gpt-5-nano', 'big-pickle'];
|
|
1652
1655
|
}
|
|
1653
1656
|
if (providerID === 'kilo') {
|
|
1654
1657
|
priority = [
|
|
@@ -1676,7 +1679,6 @@ export namespace Provider {
|
|
|
1676
1679
|
|
|
1677
1680
|
const priority = [
|
|
1678
1681
|
'glm-5-free',
|
|
1679
|
-
'kimi-k2.5-free',
|
|
1680
1682
|
'minimax-m2.5-free',
|
|
1681
1683
|
'gpt-5-nano',
|
|
1682
1684
|
'big-pickle',
|
|
@@ -1848,7 +1850,7 @@ export namespace Provider {
|
|
|
1848
1850
|
* Examples:
|
|
1849
1851
|
* - "kilo/glm-5-free" -> { providerID: "kilo", modelID: "glm-5-free" }
|
|
1850
1852
|
* - "glm-5-free" -> { providerID: "kilo", modelID: "glm-5-free" } (resolved)
|
|
1851
|
-
* - "
|
|
1853
|
+
* - "big-pickle" -> { providerID: "opencode", modelID: "big-pickle" } (resolved)
|
|
1852
1854
|
* - "nonexistent-model" -> throws ModelNotFoundError
|
|
1853
1855
|
*
|
|
1854
1856
|
* @param model - Model string with or without provider prefix
|
|
@@ -1908,7 +1910,7 @@ export namespace Provider {
|
|
|
1908
1910
|
* When one provider hits rate limits, the system can try an alternative.
|
|
1909
1911
|
*
|
|
1910
1912
|
* Note: This is only used for models without explicit provider specification.
|
|
1911
|
-
* If user specifies "kilo/
|
|
1913
|
+
* If user specifies "kilo/deepseek-r1-free", no fallback will occur.
|
|
1912
1914
|
*/
|
|
1913
1915
|
const SHARED_FREE_MODELS: Record<string, string[]> = {
|
|
1914
1916
|
// Currently no shared models between OpenCode and Kilo providers.
|
|
@@ -1920,7 +1922,7 @@ export namespace Provider {
|
|
|
1920
1922
|
* This function returns a list of alternative providers that offer the same model.
|
|
1921
1923
|
*
|
|
1922
1924
|
* Note: This only returns alternatives for models without explicit provider specification.
|
|
1923
|
-
* If the original request had an explicit provider (like "kilo/
|
|
1925
|
+
* If the original request had an explicit provider (like "kilo/deepseek-r1-free"), this returns empty array.
|
|
1924
1926
|
*
|
|
1925
1927
|
* @param modelID - The model ID to find alternatives for
|
|
1926
1928
|
* @param failedProviderID - The provider that failed
|
package/src/session/processor.ts
CHANGED
|
@@ -22,6 +22,34 @@ export namespace SessionProcessor {
|
|
|
22
22
|
const DOOM_LOOP_THRESHOLD = 3;
|
|
23
23
|
const log = Log.create({ service: 'session.processor' });
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Detect model-not-supported errors from an API response body.
|
|
27
|
+
*
|
|
28
|
+
* Some providers (e.g., OpenCode, OpenRouter) return HTTP 401 with a body
|
|
29
|
+
* like {"type":"ModelError","message":"Model X not supported"} instead of
|
|
30
|
+
* the semantically correct 400/404. Without this check the error looks like
|
|
31
|
+
* an authentication failure, making diagnostics confusing.
|
|
32
|
+
*
|
|
33
|
+
* See: https://github.com/link-assistant/agent/issues/208
|
|
34
|
+
*/
|
|
35
|
+
export function isModelNotSupportedError(responseBody: string): boolean {
|
|
36
|
+
try {
|
|
37
|
+
const parsed = JSON.parse(responseBody);
|
|
38
|
+
// OpenCode/OpenRouter format: {"type":"error","error":{"type":"ModelError","message":"..."}}
|
|
39
|
+
if (parsed?.error?.type === 'ModelError') return true;
|
|
40
|
+
// Flat format: {"type":"ModelError","message":"..."}
|
|
41
|
+
if (parsed?.type === 'ModelError') return true;
|
|
42
|
+
return false;
|
|
43
|
+
} catch {
|
|
44
|
+
// If response body is not JSON, check for common text patterns
|
|
45
|
+
return (
|
|
46
|
+
responseBody.includes('ModelError') ||
|
|
47
|
+
responseBody.toLowerCase().includes('model not supported') ||
|
|
48
|
+
responseBody.toLowerCase().includes('model not found')
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
25
53
|
export type Info = Awaited<ReturnType<typeof create>>;
|
|
26
54
|
export type Result = Awaited<ReturnType<Info['process']>>;
|
|
27
55
|
|
|
@@ -551,6 +579,37 @@ export namespace SessionProcessor {
|
|
|
551
579
|
|
|
552
580
|
// Clear retry state on non-retryable error
|
|
553
581
|
SessionRetry.clearRetryState(input.sessionID);
|
|
582
|
+
|
|
583
|
+
// Detect model-not-supported errors from provider response body.
|
|
584
|
+
// OpenCode and similar proxies return HTTP 401 with
|
|
585
|
+
// {"type":"ModelError","message":"Model X not supported"}.
|
|
586
|
+
// Without this check the error looks like an auth failure (401),
|
|
587
|
+
// but it's actually a model-availability issue.
|
|
588
|
+
// See: https://github.com/link-assistant/agent/issues/208
|
|
589
|
+
if (
|
|
590
|
+
error?.name === 'APIError' &&
|
|
591
|
+
error.data.statusCode === 401 &&
|
|
592
|
+
error.data.responseBody
|
|
593
|
+
) {
|
|
594
|
+
const isModelError = isModelNotSupportedError(
|
|
595
|
+
error.data.responseBody
|
|
596
|
+
);
|
|
597
|
+
if (isModelError) {
|
|
598
|
+
log.error(() => ({
|
|
599
|
+
message:
|
|
600
|
+
'model not supported by provider — this is NOT an auth error',
|
|
601
|
+
hint: 'The model was found in the local cache but the provider rejected it. The model may have been removed or is temporarily unavailable.',
|
|
602
|
+
providerID: input.providerID,
|
|
603
|
+
modelID: input.model.id,
|
|
604
|
+
statusCode: error.data.statusCode,
|
|
605
|
+
responseBody: error.data.responseBody,
|
|
606
|
+
suggestion:
|
|
607
|
+
'Try a different model or check the provider status. Use --model <provider>/<model-id> to specify an alternative.',
|
|
608
|
+
issue: 'https://github.com/link-assistant/agent/issues/208',
|
|
609
|
+
}));
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
554
613
|
input.assistantMessage.error = error;
|
|
555
614
|
Bus.publish(Session.Event.Error, {
|
|
556
615
|
sessionID: input.assistantMessage.sessionID,
|