@link-assistant/agent 0.16.12 → 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 +4 -10
- 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
|
@@ -1651,12 +1651,7 @@ export namespace Provider {
|
|
|
1651
1651
|
priority = priority.filter((m) => m !== 'claude-haiku-4.5');
|
|
1652
1652
|
}
|
|
1653
1653
|
if (providerID === 'opencode' || providerID === 'local') {
|
|
1654
|
-
priority = [
|
|
1655
|
-
'kimi-k2.5-free',
|
|
1656
|
-
'minimax-m2.5-free',
|
|
1657
|
-
'gpt-5-nano',
|
|
1658
|
-
'big-pickle',
|
|
1659
|
-
];
|
|
1654
|
+
priority = ['minimax-m2.5-free', 'gpt-5-nano', 'big-pickle'];
|
|
1660
1655
|
}
|
|
1661
1656
|
if (providerID === 'kilo') {
|
|
1662
1657
|
priority = [
|
|
@@ -1684,7 +1679,6 @@ export namespace Provider {
|
|
|
1684
1679
|
|
|
1685
1680
|
const priority = [
|
|
1686
1681
|
'glm-5-free',
|
|
1687
|
-
'kimi-k2.5-free',
|
|
1688
1682
|
'minimax-m2.5-free',
|
|
1689
1683
|
'gpt-5-nano',
|
|
1690
1684
|
'big-pickle',
|
|
@@ -1856,7 +1850,7 @@ export namespace Provider {
|
|
|
1856
1850
|
* Examples:
|
|
1857
1851
|
* - "kilo/glm-5-free" -> { providerID: "kilo", modelID: "glm-5-free" }
|
|
1858
1852
|
* - "glm-5-free" -> { providerID: "kilo", modelID: "glm-5-free" } (resolved)
|
|
1859
|
-
* - "
|
|
1853
|
+
* - "big-pickle" -> { providerID: "opencode", modelID: "big-pickle" } (resolved)
|
|
1860
1854
|
* - "nonexistent-model" -> throws ModelNotFoundError
|
|
1861
1855
|
*
|
|
1862
1856
|
* @param model - Model string with or without provider prefix
|
|
@@ -1916,7 +1910,7 @@ export namespace Provider {
|
|
|
1916
1910
|
* When one provider hits rate limits, the system can try an alternative.
|
|
1917
1911
|
*
|
|
1918
1912
|
* Note: This is only used for models without explicit provider specification.
|
|
1919
|
-
* If user specifies "kilo/
|
|
1913
|
+
* If user specifies "kilo/deepseek-r1-free", no fallback will occur.
|
|
1920
1914
|
*/
|
|
1921
1915
|
const SHARED_FREE_MODELS: Record<string, string[]> = {
|
|
1922
1916
|
// Currently no shared models between OpenCode and Kilo providers.
|
|
@@ -1928,7 +1922,7 @@ export namespace Provider {
|
|
|
1928
1922
|
* This function returns a list of alternative providers that offer the same model.
|
|
1929
1923
|
*
|
|
1930
1924
|
* Note: This only returns alternatives for models without explicit provider specification.
|
|
1931
|
-
* 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.
|
|
1932
1926
|
*
|
|
1933
1927
|
* @param modelID - The model ID to find alternatives for
|
|
1934
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,
|