@link-assistant/agent 0.26.11 → 0.26.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 +10 -13
- package/package.json +32 -30
- package/src/cli/argv.ts +1 -1
- package/src/cli/run-options.js +1 -1
- package/src/config/defaults.ts +8 -25
- package/src/provider/free-tier-error.ts +16 -0
- package/src/provider/kilo.ts +49 -0
- package/src/provider/opencode-zen.ts +21 -42
- package/src/provider/provider.ts +39 -221
- package/src/session/message-v2.ts +31 -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
|
|
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` mode with:
|
|
24
24
|
|
|
25
|
-
- ✅ **JSON Input/Output**: Compatible with `opencode run --format json
|
|
25
|
+
- ✅ **JSON Input/Output**: Compatible with `opencode run --format json`
|
|
26
26
|
- ✅ **Plain Text Input**: Also accepts plain text messages (auto-converted to JSON format)
|
|
27
|
-
- ✅ **Flexible Model Selection**: Defaults to free
|
|
27
|
+
- ✅ **Flexible Model Selection**: Defaults to free Kilo Nemotron 3 Super (Kilo authentication recommended), supports [OpenCode Zen](https://opencode.ai/docs/zen/), [Formal AI](../docs/formal-ai.md), [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
|
|
172
|
+
echo "hi" | agent --model kilo/nemotron-3-super-free
|
|
173
173
|
```
|
|
174
174
|
|
|
175
175
|
### More Examples
|
|
@@ -190,16 +190,13 @@ 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 Nemotron 3 Super)
|
|
194
194
|
echo "hi" | agent
|
|
195
195
|
|
|
196
|
-
#
|
|
197
|
-
echo "hi" | agent --model
|
|
198
|
-
echo "hi" | agent --model
|
|
199
|
-
echo "hi" | agent --model
|
|
200
|
-
echo "hi" | agent --model opencode/nemotron-3-super-free
|
|
201
|
-
echo "hi" | agent --model opencode/gpt-5-nano
|
|
202
|
-
echo "hi" | agent --model opencode/big-pickle
|
|
196
|
+
# Built-in free models (authenticate with agent auth login or KILO_API_KEY)
|
|
197
|
+
echo "hi" | agent --model kilo/nemotron-3-super-free
|
|
198
|
+
echo "hi" | agent --model kilo/nemotron-3-ultra-free
|
|
199
|
+
echo "hi" | agent --model kilo/nemotron-3.5-lightning-free
|
|
203
200
|
|
|
204
201
|
# Formal AI local server
|
|
205
202
|
echo "hi" | agent --model formal-ai
|
|
@@ -288,7 +285,7 @@ agent [options]
|
|
|
288
285
|
|
|
289
286
|
Options:
|
|
290
287
|
--model Model to use in format providerID/modelID
|
|
291
|
-
Default:
|
|
288
|
+
Default: kilo/nemotron-3-super-free
|
|
292
289
|
--json-standard JSON output format standard
|
|
293
290
|
Choices: "opencode" (default), "claude" (experimental)
|
|
294
291
|
--output-format Alias for JSON output format
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@link-assistant/agent",
|
|
3
|
-
"version": "0.26.
|
|
3
|
+
"version": "0.26.13",
|
|
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",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"format": "prettier --write . ../scripts",
|
|
18
18
|
"format:check": "prettier --check . ../scripts",
|
|
19
19
|
"check:file-size": "node ../scripts/check-file-size.mjs",
|
|
20
|
+
"check:model-drift": "bun ../scripts/check-model-drift.mjs",
|
|
20
21
|
"check": "npm run lint && npm run format:check && npm run check:file-size",
|
|
21
22
|
"prepare": "cd .. && husky || true",
|
|
22
23
|
"changeset": "changeset",
|
|
@@ -50,54 +51,54 @@
|
|
|
50
51
|
"LICENSE"
|
|
51
52
|
],
|
|
52
53
|
"dependencies": {
|
|
53
|
-
"@agentclientprotocol/sdk": "^1.
|
|
54
|
-
"@ai-sdk/amazon-bedrock": "^5.0.
|
|
55
|
-
"@ai-sdk/anthropic": "^4.0.
|
|
56
|
-
"@ai-sdk/azure": "^4.0.
|
|
57
|
-
"@ai-sdk/google": "^4.0.
|
|
58
|
-
"@ai-sdk/google-vertex": "^5.0.
|
|
59
|
-
"@ai-sdk/groq": "^4.0.
|
|
60
|
-
"@ai-sdk/mcp": "^2.0.
|
|
61
|
-
"@ai-sdk/mistral": "^4.0.
|
|
62
|
-
"@ai-sdk/openai": "^4.0.
|
|
63
|
-
"@ai-sdk/openai-compatible": "^3.0.
|
|
64
|
-
"@ai-sdk/provider": "^4.0.
|
|
65
|
-
"@ai-sdk/xai": "^5.0.
|
|
54
|
+
"@agentclientprotocol/sdk": "^1.8.0",
|
|
55
|
+
"@ai-sdk/amazon-bedrock": "^5.0.113",
|
|
56
|
+
"@ai-sdk/anthropic": "^4.0.78",
|
|
57
|
+
"@ai-sdk/azure": "^4.0.99",
|
|
58
|
+
"@ai-sdk/google": "^4.0.93",
|
|
59
|
+
"@ai-sdk/google-vertex": "^5.0.109",
|
|
60
|
+
"@ai-sdk/groq": "^4.0.59",
|
|
61
|
+
"@ai-sdk/mcp": "^2.0.73",
|
|
62
|
+
"@ai-sdk/mistral": "^4.0.62",
|
|
63
|
+
"@ai-sdk/openai": "^4.0.91",
|
|
64
|
+
"@ai-sdk/openai-compatible": "^3.0.67",
|
|
65
|
+
"@ai-sdk/provider": "^4.0.26",
|
|
66
|
+
"@ai-sdk/xai": "^5.0.20",
|
|
66
67
|
"@clack/prompts": "^1.8.1",
|
|
67
68
|
"@hono/standard-validator": "^0.4.0",
|
|
68
69
|
"@hono/zod-validator": "^0.9.1",
|
|
69
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
70
|
+
"@modelcontextprotocol/sdk": "^1.32.1",
|
|
70
71
|
"@octokit/graphql": "^9.0.5",
|
|
71
72
|
"@octokit/rest": "^22.0.1",
|
|
72
73
|
"@openauthjs/openauth": "^0.4.3",
|
|
73
|
-
"@opencode-ai/sdk": "^1.
|
|
74
|
+
"@opencode-ai/sdk": "^1.19.0",
|
|
74
75
|
"@openrouter/ai-sdk-provider": "^3.1.0",
|
|
75
|
-
"@opentui/core": "0.
|
|
76
|
-
"@opentui/solid": "0.
|
|
76
|
+
"@opentui/core": "0.6.1",
|
|
77
|
+
"@opentui/solid": "0.6.1",
|
|
77
78
|
"@parcel/watcher": "^2.6.0",
|
|
78
79
|
"@solid-primitives/event-bus": "^1.1.4",
|
|
79
80
|
"@standard-community/standard-json": "^0.3.6",
|
|
80
81
|
"@standard-community/standard-openapi": "^0.2.10",
|
|
81
|
-
"@zip.js/zip.js": "^2.
|
|
82
|
-
"ai": "^7.0.
|
|
82
|
+
"@zip.js/zip.js": "^2.23.0",
|
|
83
|
+
"ai": "^7.0.137",
|
|
83
84
|
"chokidar": "^5.0.0",
|
|
84
85
|
"clipboardy": "^5.3.2",
|
|
85
|
-
"command-stream": "^
|
|
86
|
+
"command-stream": "^2.3.0",
|
|
86
87
|
"decimal.js": "^10.6.0",
|
|
87
88
|
"diff": "^9.0.0",
|
|
88
89
|
"fuzzysort": "^4.0.2",
|
|
89
90
|
"glob": "^13.0.6",
|
|
90
91
|
"gpt-tokenizer": "^4.0.0",
|
|
91
92
|
"gray-matter": "^4.0.3",
|
|
92
|
-
"hono": "^4.13.
|
|
93
|
-
"hono-openapi": "^1.3.
|
|
94
|
-
"ignore": "^7.0.
|
|
93
|
+
"hono": "^4.13.13",
|
|
94
|
+
"hono-openapi": "^1.3.5",
|
|
95
|
+
"ignore": "^7.0.12",
|
|
95
96
|
"jsonc-parser": "^3.3.1",
|
|
96
97
|
"lino-arguments": "^0.3.0",
|
|
97
|
-
"lino-objects-codec": "^0.
|
|
98
|
+
"lino-objects-codec": "^0.9.2",
|
|
98
99
|
"log-lazy": "^1.0.4",
|
|
99
100
|
"minimatch": "^10.2.6",
|
|
100
|
-
"remeda": "^2.
|
|
101
|
+
"remeda": "^2.51.0",
|
|
101
102
|
"solid-js": "1.9.12",
|
|
102
103
|
"strip-ansi": "^7.2.0",
|
|
103
104
|
"test-anywhere": "^0.9.1",
|
|
@@ -109,18 +110,18 @@
|
|
|
109
110
|
"web-tree-sitter": "0.25.10",
|
|
110
111
|
"xdg-basedir": "^5.1.0",
|
|
111
112
|
"yargs": "^18.2.0",
|
|
112
|
-
"zod": "^4.6.
|
|
113
|
+
"zod": "^4.6.6"
|
|
113
114
|
},
|
|
114
115
|
"devDependencies": {
|
|
115
116
|
"@changesets/cli": "^3.0.3",
|
|
116
117
|
"@eslint/js": "^10.0.1",
|
|
117
|
-
"eslint": "^10.
|
|
118
|
+
"eslint": "^10.12.0",
|
|
118
119
|
"eslint-config-prettier": "^10.1.8",
|
|
119
120
|
"eslint-plugin-prettier": "^5.5.6",
|
|
120
121
|
"eslint-plugin-promise": "^7.3.0",
|
|
121
122
|
"husky": "^9.1.7",
|
|
122
123
|
"lint-staged": "^17.6.0",
|
|
123
|
-
"prettier": "^3.9.
|
|
124
|
+
"prettier": "^3.9.10"
|
|
124
125
|
},
|
|
125
126
|
"lint-staged": {
|
|
126
127
|
"*.{js,mjs,cjs}": [
|
|
@@ -140,10 +141,11 @@
|
|
|
140
141
|
"node-pty@1.2.0-beta.15": true
|
|
141
142
|
},
|
|
142
143
|
"overrides": {
|
|
144
|
+
"seroval": "^1.6.9",
|
|
143
145
|
"undici": "^7.16.0"
|
|
144
146
|
},
|
|
145
147
|
"dependencyPins": {
|
|
146
148
|
"solid-js": "https://github.com/anomalyco/opentui/issues/689",
|
|
147
|
-
"web-tree-sitter": "https://github.com/
|
|
149
|
+
"web-tree-sitter": "https://github.com/anomalyco/opentui/issues/1550"
|
|
148
150
|
}
|
|
149
151
|
}
|
package/src/cli/argv.ts
CHANGED
|
@@ -105,7 +105,7 @@ export function getCompactionSafetyMarginFromProcessArgv(): string | null {
|
|
|
105
105
|
/**
|
|
106
106
|
* Extract --compaction-models argument directly from process.argv
|
|
107
107
|
* The value is a links notation references sequence, e.g.:
|
|
108
|
-
* "(
|
|
108
|
+
* "(kilo/nemotron-3-super-free kilo/nemotron-3-ultra-free kilo/nemotron-3.5-lightning-free same)"
|
|
109
109
|
* @returns The compaction models argument from CLI or null if not found
|
|
110
110
|
* @see https://github.com/link-assistant/agent/issues/232
|
|
111
111
|
*/
|
package/src/cli/run-options.js
CHANGED
|
@@ -190,7 +190,7 @@ export function buildRunOptions(yargs, defaultOptions = {}) {
|
|
|
190
190
|
.option('compaction-model', {
|
|
191
191
|
type: 'string',
|
|
192
192
|
description:
|
|
193
|
-
'Model to use for context compaction and session summaries, in format providerID/modelID. Use "same" to use the base model. Default:
|
|
193
|
+
'Model to use for context compaction and session summaries, in format providerID/modelID. Use "same" to use the base model. Default: kilo/nemotron-3-super-free (free, Kilo authentication recommended, 262K context), which falls back to the --model model when --model points at another provider. Overridden by --compaction-models if both are specified.',
|
|
194
194
|
default: defaultCompactionModel,
|
|
195
195
|
})
|
|
196
196
|
.option('compaction-models', {
|
package/src/config/defaults.ts
CHANGED
|
@@ -16,45 +16,28 @@ export interface DefaultConfigOptions {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
/** Default model used when no `--model` CLI argument is provided. */
|
|
19
|
-
export const DEFAULT_MODEL = '
|
|
19
|
+
export const DEFAULT_MODEL = 'kilo/nemotron-3-super-free';
|
|
20
20
|
|
|
21
21
|
/** Env var for overriding the default model in test runs and automation. */
|
|
22
22
|
export const DEFAULT_MODEL_ENV = 'LINK_ASSISTANT_AGENT_DEFAULT_MODEL';
|
|
23
23
|
|
|
24
24
|
/** Default compaction model used when no `--compaction-model` CLI argument is provided. */
|
|
25
|
-
export const DEFAULT_COMPACTION_MODEL = '
|
|
25
|
+
export const DEFAULT_COMPACTION_MODEL = 'kilo/nemotron-3-super-free';
|
|
26
26
|
|
|
27
27
|
/** Env var for overriding the default compaction model in test runs and automation. */
|
|
28
28
|
export const DEFAULT_COMPACTION_MODEL_ENV =
|
|
29
29
|
'LINK_ASSISTANT_AGENT_DEFAULT_COMPACTION_MODEL';
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* The special value "same" means use the same model as `--model`.
|
|
32
|
+
* Free Kilo models ordered by context capacity, followed by the session model.
|
|
33
|
+
* Compaction skips models whose context is too small or which hit a rate limit.
|
|
34
|
+
* Provider prefixes keep default secondary calls away from Zen's restricted tier.
|
|
35
|
+
* The special value "same" uses the model running the session.
|
|
37
36
|
*
|
|
38
|
-
*
|
|
39
|
-
* "(big-pickle minimax-m2.5-free nemotron-3-super-free hy3-preview-free ling-2.6-flash-free gpt-5-nano same)"
|
|
40
|
-
*
|
|
41
|
-
* Context limits (approximate):
|
|
42
|
-
* big-pickle: ~200K
|
|
43
|
-
* minimax-m2.5-free: ~204K
|
|
44
|
-
* nemotron-3-super-free: ~204K
|
|
45
|
-
* hy3-preview-free: ~256K
|
|
46
|
-
* ling-2.6-flash-free: ~262K
|
|
47
|
-
* gpt-5-nano: ~400K
|
|
48
|
-
* same: (base model's context)
|
|
49
|
-
*
|
|
50
|
-
* Note: qwen3.6-plus-free was removed because the free promotion ended in April 2026.
|
|
51
|
-
* Note: minimax-m2.5-free is the default model again as of issue #266.
|
|
52
|
-
* @see https://github.com/link-assistant/agent/issues/266
|
|
53
|
-
* @see https://github.com/link-assistant/agent/issues/242
|
|
54
|
-
* @see https://github.com/link-assistant/agent/issues/232
|
|
37
|
+
* @see https://github.com/link-assistant/agent/issues/327
|
|
55
38
|
*/
|
|
56
39
|
export const DEFAULT_COMPACTION_MODELS =
|
|
57
|
-
'(
|
|
40
|
+
'(kilo/nemotron-3-super-free kilo/nemotron-3-ultra-free kilo/nemotron-3.5-lightning-free same)';
|
|
58
41
|
|
|
59
42
|
/** Env var for overriding the default compaction cascade in test runs and automation. */
|
|
60
43
|
export const DEFAULT_COMPACTION_MODELS_ENV =
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Zen uses this typed error for a permanent client restriction. */
|
|
2
|
+
export function isFreeTierError(
|
|
3
|
+
status: number | undefined,
|
|
4
|
+
body: unknown
|
|
5
|
+
): boolean {
|
|
6
|
+
if (status !== 403) return false;
|
|
7
|
+
try {
|
|
8
|
+
const parsed = typeof body === 'string' ? JSON.parse(body) : body;
|
|
9
|
+
return parsed?.error?.type === 'FreeTierError';
|
|
10
|
+
} catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const FREE_TIER_ERROR_MESSAGE =
|
|
16
|
+
"OpenCode Zen's free tier is limited to OpenCode clients. Use kilo/nemotron-3-super-free (authenticate with agent auth login or KILO_API_KEY), or select a paid model.";
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { ModelsDev } from './models';
|
|
2
|
+
|
|
3
|
+
/** Built-in aliases use exact IDs from https://api.kilo.ai/api/gateway/models. */
|
|
4
|
+
export namespace Kilo {
|
|
5
|
+
export const MODELS_URL = 'https://api.kilo.ai/api/gateway/models';
|
|
6
|
+
|
|
7
|
+
function freeModel(
|
|
8
|
+
id: string,
|
|
9
|
+
name: string,
|
|
10
|
+
context: number,
|
|
11
|
+
output: number
|
|
12
|
+
): ModelsDev.Model {
|
|
13
|
+
return {
|
|
14
|
+
id,
|
|
15
|
+
name,
|
|
16
|
+
release_date: '',
|
|
17
|
+
attachment: false,
|
|
18
|
+
reasoning: true,
|
|
19
|
+
temperature: true,
|
|
20
|
+
tool_call: true,
|
|
21
|
+
cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 },
|
|
22
|
+
limit: { context, output },
|
|
23
|
+
modalities: { input: ['text'], output: ['text'] },
|
|
24
|
+
options: {},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Super's output cap is deliberately conservative for existing integrations.
|
|
29
|
+
export const FREE_MODELS: Record<string, ModelsDev.Model> = {
|
|
30
|
+
'nemotron-3-super-free': freeModel(
|
|
31
|
+
'nvidia/nemotron-3-super-120b-a12b:free',
|
|
32
|
+
'Nemotron 3 Super (Free)',
|
|
33
|
+
262144,
|
|
34
|
+
32768
|
|
35
|
+
),
|
|
36
|
+
'nemotron-3-ultra-free': freeModel(
|
|
37
|
+
'nvidia/nemotron-3-ultra-550b-a55b:free',
|
|
38
|
+
'Nemotron 3 Ultra (Free)',
|
|
39
|
+
1000000,
|
|
40
|
+
65536
|
|
41
|
+
),
|
|
42
|
+
'nemotron-3.5-lightning-free': freeModel(
|
|
43
|
+
'nvidia/nemotron-3.5-lightning:free',
|
|
44
|
+
'Nemotron 3.5 Lightning (Free)',
|
|
45
|
+
1000000,
|
|
46
|
+
65536
|
|
47
|
+
),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -3,50 +3,29 @@ import type { ModelsDev } from './models';
|
|
|
3
3
|
|
|
4
4
|
export namespace OpenCodeZen {
|
|
5
5
|
const log = Log.create({ service: 'opencode-zen' });
|
|
6
|
-
const MODELS_URL = 'https://opencode.ai/zen/v1/models';
|
|
7
|
-
const DEPRECATED_FREE_MODEL_IDS = new Set([
|
|
6
|
+
export const MODELS_URL = 'https://opencode.ai/zen/v1/models';
|
|
7
|
+
export const DEPRECATED_FREE_MODEL_IDS = new Set([
|
|
8
|
+
'trinity-large-preview-free',
|
|
9
|
+
'qwen3.6-plus-free',
|
|
10
|
+
'kimi-k2.5-free',
|
|
11
|
+
'minimax-m2.5-free',
|
|
12
|
+
'ling-2.6-flash-free',
|
|
13
|
+
'hy3-preview-free',
|
|
14
|
+
'nemotron-3-super-free',
|
|
15
|
+
'nemotron-3-ultra-free',
|
|
16
|
+
]);
|
|
8
17
|
|
|
9
|
-
|
|
18
|
+
// Zen's free endpoints are restricted to OpenCode clients, so none is a default.
|
|
19
|
+
export const FREE_MODEL_OVERRIDES: Record<
|
|
10
20
|
string,
|
|
11
21
|
Partial<ModelsDev.Model> & { name: string }
|
|
12
22
|
> = {
|
|
13
|
-
'
|
|
14
|
-
name: '
|
|
23
|
+
'nemotron-3.5-lightning-free': {
|
|
24
|
+
name: 'Nemotron 3.5 Lightning Free',
|
|
15
25
|
reasoning: true,
|
|
16
|
-
|
|
17
|
-
limit: { context: 204800, output: 131072 },
|
|
26
|
+
limit: { context: 1000000, output: 65536 },
|
|
18
27
|
provider: { npm: '@ai-sdk/openai-compatible' },
|
|
19
28
|
},
|
|
20
|
-
'ling-2.6-flash-free': {
|
|
21
|
-
name: 'Ling 2.6 Flash Free',
|
|
22
|
-
reasoning: false,
|
|
23
|
-
release_date: '2026-04-21',
|
|
24
|
-
limit: { context: 262100, output: 32800 },
|
|
25
|
-
provider: { npm: '@ai-sdk/openai-compatible' },
|
|
26
|
-
},
|
|
27
|
-
'hy3-preview-free': {
|
|
28
|
-
name: 'Hy3 Preview Free',
|
|
29
|
-
reasoning: true,
|
|
30
|
-
release_date: '2026-04-20',
|
|
31
|
-
limit: { context: 256000, output: 64000 },
|
|
32
|
-
provider: { npm: '@ai-sdk/openai-compatible' },
|
|
33
|
-
},
|
|
34
|
-
'nemotron-3-super-free': {
|
|
35
|
-
name: 'Nemotron 3 Super Free',
|
|
36
|
-
reasoning: true,
|
|
37
|
-
release_date: '2026-03-11',
|
|
38
|
-
limit: { context: 204800, output: 128000 },
|
|
39
|
-
provider: { npm: '@ai-sdk/openai-compatible' },
|
|
40
|
-
},
|
|
41
|
-
'gpt-5-nano': {
|
|
42
|
-
name: 'GPT-5 Nano',
|
|
43
|
-
attachment: true,
|
|
44
|
-
reasoning: true,
|
|
45
|
-
temperature: false,
|
|
46
|
-
release_date: '2025-08-07',
|
|
47
|
-
limit: { context: 400000, output: 128000 },
|
|
48
|
-
provider: { npm: '@ai-sdk/openai' },
|
|
49
|
-
},
|
|
50
29
|
'big-pickle': {
|
|
51
30
|
name: 'Big Pickle',
|
|
52
31
|
reasoning: true,
|
|
@@ -68,11 +47,7 @@ export namespace OpenCodeZen {
|
|
|
68
47
|
|
|
69
48
|
function isFreeModelID(modelID: string) {
|
|
70
49
|
if (DEPRECATED_FREE_MODEL_IDS.has(modelID)) return false;
|
|
71
|
-
return (
|
|
72
|
-
modelID.endsWith('-free') ||
|
|
73
|
-
modelID === 'gpt-5-nano' ||
|
|
74
|
-
modelID === 'big-pickle'
|
|
75
|
-
);
|
|
50
|
+
return modelID.endsWith('-free') || modelID === 'big-pickle';
|
|
76
51
|
}
|
|
77
52
|
|
|
78
53
|
export async function fetchModelIDs(fetchFn: typeof fetch = fetch) {
|
|
@@ -115,6 +90,10 @@ export namespace OpenCodeZen {
|
|
|
115
90
|
});
|
|
116
91
|
if (!ids.has(modelID)) return undefined;
|
|
117
92
|
|
|
93
|
+
return freeModelInfo(modelID);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function freeModelInfo(modelID: string): ModelsDev.Model {
|
|
118
97
|
const overrides = FREE_MODEL_OVERRIDES[modelID];
|
|
119
98
|
return {
|
|
120
99
|
id: modelID,
|
package/src/provider/provider.ts
CHANGED
|
@@ -20,6 +20,7 @@ import { createCacheModel } from './cache';
|
|
|
20
20
|
import { RetryFetch } from './retry-fetch';
|
|
21
21
|
import { SSEUsageExtractor } from '../util/sse-usage-extractor';
|
|
22
22
|
import { OpenCodeZen } from './opencode-zen';
|
|
23
|
+
import { Kilo } from './kilo';
|
|
23
24
|
|
|
24
25
|
// Direct imports for bundled providers - these are pre-installed to avoid runtime installation hangs
|
|
25
26
|
// @see https://github.com/link-assistant/agent/issues/173
|
|
@@ -445,18 +446,13 @@ export namespace Provider {
|
|
|
445
446
|
};
|
|
446
447
|
},
|
|
447
448
|
/**
|
|
448
|
-
* Kilo provider - access to
|
|
449
|
+
* Kilo provider - access to AI models through Kilo Gateway
|
|
449
450
|
* Uses OpenRouter-compatible API at https://api.kilo.ai/api/openrouter
|
|
450
451
|
*
|
|
451
|
-
* Authentication
|
|
452
|
+
* Authentication recommended: run `agent auth login` and select "Kilo Gateway"
|
|
452
453
|
* For API key authentication, set KILO_API_KEY environment variable
|
|
453
454
|
*
|
|
454
|
-
* Free models
|
|
455
|
-
* - GLM-5 (z-ai/glm-5) - Free limited time, flagship Z.AI model
|
|
456
|
-
* - GLM 4.7 (z-ai/glm-4.7:free) - Free, agent-centric model
|
|
457
|
-
* - Kimi K2.5 (moonshot/kimi-k2.5:free) - Free, agentic capabilities
|
|
458
|
-
* - MiniMax M2.1 (minimax/m2.1:free) - Free, general-purpose
|
|
459
|
-
* - Giga Potato (giga-potato:free) - Free evaluation model
|
|
455
|
+
* Free Nemotron models are listed in Kilo.FREE_MODELS.
|
|
460
456
|
*
|
|
461
457
|
* @see https://kilo.ai/docs/gateway
|
|
462
458
|
* @see https://kilo.ai/docs/advanced-usage/free-and-budget-models
|
|
@@ -932,174 +928,24 @@ export namespace Provider {
|
|
|
932
928
|
models: {}, // Models are dynamically created based on the provider/model syntax
|
|
933
929
|
};
|
|
934
930
|
|
|
935
|
-
//
|
|
936
|
-
//
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
931
|
+
// Remove withdrawn Zen entries even when models.dev has a stale cache.
|
|
932
|
+
// Operator model overrides are applied below and remain authoritative.
|
|
933
|
+
if (database.opencode) {
|
|
934
|
+
for (const id of OpenCodeZen.DEPRECATED_FREE_MODEL_IDS) {
|
|
935
|
+
delete database.opencode.models[id];
|
|
936
|
+
}
|
|
937
|
+
for (const id of Object.keys(OpenCodeZen.FREE_MODEL_OVERRIDES)) {
|
|
938
|
+
database.opencode.models[id] = OpenCodeZen.freeModelInfo(id);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
database.kilo = {
|
|
940
943
|
id: 'kilo',
|
|
941
944
|
name: 'Kilo Gateway',
|
|
942
945
|
npm: '@openrouter/ai-sdk-provider',
|
|
943
946
|
api: 'https://api.kilo.ai/api/openrouter',
|
|
944
947
|
env: ['KILO_API_KEY'],
|
|
945
|
-
models:
|
|
946
|
-
// GLM-5 - Flagship Z.AI model, free tier
|
|
947
|
-
// Kilo API model ID: z-ai/glm-5:free (NOT z-ai/glm-5 which is paid)
|
|
948
|
-
'glm-5-free': {
|
|
949
|
-
id: 'z-ai/glm-5:free',
|
|
950
|
-
name: 'GLM-5 (Free)',
|
|
951
|
-
release_date: '2026-02-11',
|
|
952
|
-
attachment: false,
|
|
953
|
-
reasoning: true,
|
|
954
|
-
temperature: true,
|
|
955
|
-
tool_call: true,
|
|
956
|
-
cost: {
|
|
957
|
-
input: 0,
|
|
958
|
-
output: 0,
|
|
959
|
-
cache_read: 0,
|
|
960
|
-
cache_write: 0,
|
|
961
|
-
},
|
|
962
|
-
limit: {
|
|
963
|
-
context: 202800,
|
|
964
|
-
output: 131072,
|
|
965
|
-
},
|
|
966
|
-
modalities: {
|
|
967
|
-
input: ['text'],
|
|
968
|
-
output: ['text'],
|
|
969
|
-
},
|
|
970
|
-
options: {},
|
|
971
|
-
},
|
|
972
|
-
// GLM 4.5 Air - Free Z.AI model (replaces non-existent glm-4.7:free)
|
|
973
|
-
// Kilo API model ID: z-ai/glm-4.5-air:free
|
|
974
|
-
'glm-4.5-air-free': {
|
|
975
|
-
id: 'z-ai/glm-4.5-air:free',
|
|
976
|
-
name: 'GLM 4.5 Air (Free)',
|
|
977
|
-
release_date: '2026-01-15',
|
|
978
|
-
attachment: false,
|
|
979
|
-
reasoning: true,
|
|
980
|
-
temperature: true,
|
|
981
|
-
tool_call: true,
|
|
982
|
-
cost: {
|
|
983
|
-
input: 0,
|
|
984
|
-
output: 0,
|
|
985
|
-
cache_read: 0,
|
|
986
|
-
cache_write: 0,
|
|
987
|
-
},
|
|
988
|
-
limit: {
|
|
989
|
-
context: 131072,
|
|
990
|
-
output: 96000,
|
|
991
|
-
},
|
|
992
|
-
modalities: {
|
|
993
|
-
input: ['text'],
|
|
994
|
-
output: ['text'],
|
|
995
|
-
},
|
|
996
|
-
options: {},
|
|
997
|
-
},
|
|
998
|
-
// MiniMax M2.5 - General-purpose, free (upgraded from M2.1)
|
|
999
|
-
// Kilo API model ID: minimax/minimax-m2.5:free
|
|
1000
|
-
'minimax-m2.5-free': {
|
|
1001
|
-
id: 'minimax/minimax-m2.5:free',
|
|
1002
|
-
name: 'MiniMax M2.5 (Free)',
|
|
1003
|
-
release_date: '2026-01-01',
|
|
1004
|
-
attachment: false,
|
|
1005
|
-
reasoning: true,
|
|
1006
|
-
temperature: true,
|
|
1007
|
-
tool_call: true,
|
|
1008
|
-
cost: {
|
|
1009
|
-
input: 0,
|
|
1010
|
-
output: 0,
|
|
1011
|
-
cache_read: 0,
|
|
1012
|
-
cache_write: 0,
|
|
1013
|
-
},
|
|
1014
|
-
limit: {
|
|
1015
|
-
context: 204800,
|
|
1016
|
-
output: 131072,
|
|
1017
|
-
},
|
|
1018
|
-
modalities: {
|
|
1019
|
-
input: ['text'],
|
|
1020
|
-
output: ['text'],
|
|
1021
|
-
},
|
|
1022
|
-
options: {},
|
|
1023
|
-
},
|
|
1024
|
-
// Giga Potato - Free evaluation model
|
|
1025
|
-
// Kilo API model ID: giga-potato (no :free suffix)
|
|
1026
|
-
'giga-potato-free': {
|
|
1027
|
-
id: 'giga-potato',
|
|
1028
|
-
name: 'Giga Potato (Free)',
|
|
1029
|
-
release_date: '2026-01-01',
|
|
1030
|
-
attachment: true,
|
|
1031
|
-
reasoning: true,
|
|
1032
|
-
temperature: true,
|
|
1033
|
-
tool_call: true,
|
|
1034
|
-
cost: {
|
|
1035
|
-
input: 0,
|
|
1036
|
-
output: 0,
|
|
1037
|
-
cache_read: 0,
|
|
1038
|
-
cache_write: 0,
|
|
1039
|
-
},
|
|
1040
|
-
limit: {
|
|
1041
|
-
context: 256000,
|
|
1042
|
-
output: 32000,
|
|
1043
|
-
},
|
|
1044
|
-
modalities: {
|
|
1045
|
-
input: ['text', 'image'],
|
|
1046
|
-
output: ['text'],
|
|
1047
|
-
},
|
|
1048
|
-
options: {},
|
|
1049
|
-
},
|
|
1050
|
-
// Trinity Large Preview - Preview model from Arcee AI
|
|
1051
|
-
// Kilo API model ID: arcee-ai/trinity-large-preview:free
|
|
1052
|
-
'trinity-large-preview': {
|
|
1053
|
-
id: 'arcee-ai/trinity-large-preview:free',
|
|
1054
|
-
name: 'Trinity Large Preview (Free)',
|
|
1055
|
-
release_date: '2026-01-01',
|
|
1056
|
-
attachment: false,
|
|
1057
|
-
reasoning: false,
|
|
1058
|
-
temperature: true,
|
|
1059
|
-
tool_call: true,
|
|
1060
|
-
cost: {
|
|
1061
|
-
input: 0,
|
|
1062
|
-
output: 0,
|
|
1063
|
-
cache_read: 0,
|
|
1064
|
-
cache_write: 0,
|
|
1065
|
-
},
|
|
1066
|
-
limit: {
|
|
1067
|
-
context: 131000,
|
|
1068
|
-
output: 65536,
|
|
1069
|
-
},
|
|
1070
|
-
modalities: {
|
|
1071
|
-
input: ['text'],
|
|
1072
|
-
output: ['text'],
|
|
1073
|
-
},
|
|
1074
|
-
options: {},
|
|
1075
|
-
},
|
|
1076
|
-
// DeepSeek R1 - Reasoning model, free
|
|
1077
|
-
// Kilo API model ID: deepseek/deepseek-r1-0528:free
|
|
1078
|
-
'deepseek-r1-free': {
|
|
1079
|
-
id: 'deepseek/deepseek-r1-0528:free',
|
|
1080
|
-
name: 'DeepSeek R1 (Free)',
|
|
1081
|
-
release_date: '2025-05-28',
|
|
1082
|
-
attachment: false,
|
|
1083
|
-
reasoning: true,
|
|
1084
|
-
temperature: true,
|
|
1085
|
-
tool_call: false,
|
|
1086
|
-
cost: {
|
|
1087
|
-
input: 0,
|
|
1088
|
-
output: 0,
|
|
1089
|
-
cache_read: 0,
|
|
1090
|
-
cache_write: 0,
|
|
1091
|
-
},
|
|
1092
|
-
limit: {
|
|
1093
|
-
context: 163840,
|
|
1094
|
-
output: 163840,
|
|
1095
|
-
},
|
|
1096
|
-
modalities: {
|
|
1097
|
-
input: ['text'],
|
|
1098
|
-
output: ['text'],
|
|
1099
|
-
},
|
|
1100
|
-
options: {},
|
|
1101
|
-
},
|
|
1102
|
-
},
|
|
948
|
+
models: structuredClone(Kilo.FREE_MODELS),
|
|
1103
949
|
};
|
|
1104
950
|
|
|
1105
951
|
for (const [providerID, provider] of configProviders) {
|
|
@@ -1794,7 +1640,13 @@ export namespace Provider {
|
|
|
1794
1640
|
await ModelsDev.refresh();
|
|
1795
1641
|
const freshDB = await ModelsDev.get();
|
|
1796
1642
|
const freshProvider = freshDB[providerID];
|
|
1797
|
-
if (
|
|
1643
|
+
if (
|
|
1644
|
+
freshProvider?.models[modelID] &&
|
|
1645
|
+
!(
|
|
1646
|
+
providerID === 'opencode' &&
|
|
1647
|
+
OpenCodeZen.DEPRECATED_FREE_MODEL_IDS.has(modelID)
|
|
1648
|
+
)
|
|
1649
|
+
) {
|
|
1798
1650
|
// Model found after refresh - update provider info and use it
|
|
1799
1651
|
provider.info.models[modelID] = freshProvider.models[modelID];
|
|
1800
1652
|
info = freshProvider.models[modelID];
|
|
@@ -1958,23 +1810,10 @@ export namespace Provider {
|
|
|
1958
1810
|
priority = priority.filter((m) => m !== 'claude-haiku-4.5');
|
|
1959
1811
|
}
|
|
1960
1812
|
if (providerID === 'opencode' || providerID === 'local') {
|
|
1961
|
-
priority =
|
|
1962
|
-
'minimax-m2.5-free',
|
|
1963
|
-
'ling-2.6-flash-free',
|
|
1964
|
-
'hy3-preview-free',
|
|
1965
|
-
'nemotron-3-super-free',
|
|
1966
|
-
'gpt-5-nano',
|
|
1967
|
-
'big-pickle',
|
|
1968
|
-
];
|
|
1813
|
+
priority = Object.keys(OpenCodeZen.FREE_MODEL_OVERRIDES);
|
|
1969
1814
|
}
|
|
1970
1815
|
if (providerID === 'kilo') {
|
|
1971
|
-
priority =
|
|
1972
|
-
'glm-5-free',
|
|
1973
|
-
'glm-4.5-air-free',
|
|
1974
|
-
'minimax-m2.5-free',
|
|
1975
|
-
'deepseek-r1-free',
|
|
1976
|
-
'giga-potato-free',
|
|
1977
|
-
];
|
|
1816
|
+
priority = Object.keys(Kilo.FREE_MODELS);
|
|
1978
1817
|
}
|
|
1979
1818
|
for (const item of priority) {
|
|
1980
1819
|
for (const model of Object.keys(provider.info.models)) {
|
|
@@ -1994,11 +1833,9 @@ export namespace Provider {
|
|
|
1994
1833
|
const priority = [
|
|
1995
1834
|
'big-pickle',
|
|
1996
1835
|
'gpt-5-nano',
|
|
1836
|
+
'nemotron-3.5-lightning-free',
|
|
1837
|
+
'nemotron-3-ultra-free',
|
|
1997
1838
|
'nemotron-3-super-free',
|
|
1998
|
-
'hy3-preview-free',
|
|
1999
|
-
'ling-2.6-flash-free',
|
|
2000
|
-
'minimax-m2.5-free',
|
|
2001
|
-
'glm-5-free',
|
|
2002
1839
|
'gpt-5',
|
|
2003
1840
|
'claude-sonnet-4',
|
|
2004
1841
|
'gemini-3-pro',
|
|
@@ -2083,12 +1920,9 @@ export namespace Provider {
|
|
|
2083
1920
|
* Resolve a short model name (without provider prefix) to the appropriate provider.
|
|
2084
1921
|
* This function finds which provider should handle a model when no explicit provider is specified.
|
|
2085
1922
|
*
|
|
2086
|
-
*
|
|
2087
|
-
*
|
|
2088
|
-
*
|
|
2089
|
-
* - kilo: glm-5-free, glm-4.5-air-free, giga-potato-free, deepseek-r1-free (unique to Kilo)
|
|
2090
|
-
* - opencode: minimax-m2.5-free, ling-2.6-flash-free, hy3-preview-free, big-pickle, gpt-5-nano, nemotron-3-super-free
|
|
2091
|
-
* 3. For shared models, prefer OpenCode first, then fall back to Kilo on rate limit
|
|
1923
|
+
* Built-in Kilo free aliases prefer Kilo, including aliases also served by
|
|
1924
|
+
* Zen, whose free tier is restricted to OpenCode clients. Other short names
|
|
1925
|
+
* resolve against available provider catalogs.
|
|
2092
1926
|
*
|
|
2093
1927
|
* @param modelID - Short model name without provider prefix
|
|
2094
1928
|
* @returns Provider ID that should handle this model, or undefined if not found
|
|
@@ -2098,25 +1932,9 @@ export namespace Provider {
|
|
|
2098
1932
|
): Promise<{ providerID: string; modelID: string } | undefined> {
|
|
2099
1933
|
const s = await state();
|
|
2100
1934
|
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
'glm-5-free',
|
|
2105
|
-
'glm-4.5-air-free',
|
|
2106
|
-
'giga-potato-free',
|
|
2107
|
-
'trinity-large-preview',
|
|
2108
|
-
'deepseek-r1-free',
|
|
2109
|
-
];
|
|
2110
|
-
|
|
2111
|
-
// Check if it's a Kilo-unique model
|
|
2112
|
-
if (kiloUniqueModels.includes(modelID)) {
|
|
2113
|
-
const kiloProvider = s.providers['kilo'];
|
|
2114
|
-
if (kiloProvider && kiloProvider.info.models[modelID]) {
|
|
2115
|
-
registryLog.info(() => ({
|
|
2116
|
-
message: 'resolved short model name to kilo (unique)',
|
|
2117
|
-
name: modelID,
|
|
2118
|
-
candidate: `kilo/${modelID}`,
|
|
2119
|
-
}));
|
|
1935
|
+
if (Object.hasOwn(Kilo.FREE_MODELS, modelID)) {
|
|
1936
|
+
const kiloProvider = s.providers.kilo;
|
|
1937
|
+
if (kiloProvider?.info.models[modelID]) {
|
|
2120
1938
|
return { providerID: 'kilo', modelID };
|
|
2121
1939
|
}
|
|
2122
1940
|
}
|
|
@@ -2187,8 +2005,8 @@ export namespace Provider {
|
|
|
2187
2005
|
* or a clear error explaining why it's not available.
|
|
2188
2006
|
*
|
|
2189
2007
|
* Examples:
|
|
2190
|
-
* - "kilo/
|
|
2191
|
-
* - "
|
|
2008
|
+
* - "kilo/nemotron-3-super-free" -> { providerID: "kilo", modelID: "nemotron-3-super-free" }
|
|
2009
|
+
* - "nemotron-3-super-free" -> { providerID: "kilo", modelID: "nemotron-3-super-free" } (resolved)
|
|
2192
2010
|
* - "big-pickle" -> { providerID: "opencode", modelID: "big-pickle" } (resolved)
|
|
2193
2011
|
* - "nonexistent-model" -> throws ModelNotFoundError
|
|
2194
2012
|
*
|
|
@@ -2249,10 +2067,10 @@ export namespace Provider {
|
|
|
2249
2067
|
* When one provider hits rate limits, the system can try an alternative.
|
|
2250
2068
|
*
|
|
2251
2069
|
* Note: This is only used for models without explicit provider specification.
|
|
2252
|
-
* If user specifies "kilo/
|
|
2070
|
+
* If user specifies "kilo/nemotron-3-super-free", no fallback will occur.
|
|
2253
2071
|
*/
|
|
2254
2072
|
const SHARED_FREE_MODELS: Record<string, string[]> = {
|
|
2255
|
-
'
|
|
2073
|
+
'nemotron-3.5-lightning-free': ['kilo', 'opencode'],
|
|
2256
2074
|
};
|
|
2257
2075
|
|
|
2258
2076
|
/**
|
|
@@ -2260,7 +2078,7 @@ export namespace Provider {
|
|
|
2260
2078
|
* This function returns a list of alternative providers that offer the same model.
|
|
2261
2079
|
*
|
|
2262
2080
|
* Note: This only returns alternatives for models without explicit provider specification.
|
|
2263
|
-
* If the original request had an explicit provider (like "kilo/
|
|
2081
|
+
* If the original request had an explicit provider (like "kilo/nemotron-3-super-free"), this returns empty array.
|
|
2264
2082
|
*
|
|
2265
2083
|
* @param modelID - The model ID to find alternatives for
|
|
2266
2084
|
* @param failedProviderID - The provider that failed
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isFreeTierError,
|
|
3
|
+
FREE_TIER_ERROR_MESSAGE,
|
|
4
|
+
} from '../provider/free-tier-error';
|
|
1
5
|
import z from 'zod';
|
|
2
6
|
import { Bus } from '../bus';
|
|
3
7
|
import { NamedError } from '../util/error';
|
|
@@ -866,7 +870,12 @@ export namespace MessageV2 {
|
|
|
866
870
|
? (root['error'] as Record<string, unknown>)
|
|
867
871
|
: root;
|
|
868
872
|
const rawCode =
|
|
869
|
-
candidate['code'] ??
|
|
873
|
+
candidate['code'] ??
|
|
874
|
+
candidate['statusCode'] ??
|
|
875
|
+
candidate['status'] ??
|
|
876
|
+
root['code'] ??
|
|
877
|
+
root['statusCode'] ??
|
|
878
|
+
root['status'];
|
|
870
879
|
const statusCode =
|
|
871
880
|
typeof rawCode === 'number'
|
|
872
881
|
? rawCode
|
|
@@ -900,6 +909,27 @@ export namespace MessageV2 {
|
|
|
900
909
|
}
|
|
901
910
|
|
|
902
911
|
export function fromError(e: unknown, ctx: { providerID: string }) {
|
|
912
|
+
const providerError = APICallError.isInstance(e)
|
|
913
|
+
? undefined
|
|
914
|
+
: normalizeProviderErrorObject(e);
|
|
915
|
+
const freeTierRefusal =
|
|
916
|
+
ctx.providerID === 'opencode' &&
|
|
917
|
+
(APICallError.isInstance(e)
|
|
918
|
+
? isFreeTierError(e.statusCode, e.responseBody)
|
|
919
|
+
: isFreeTierError(providerError?.statusCode, e));
|
|
920
|
+
if (freeTierRefusal) {
|
|
921
|
+
return new MessageV2.APIError(
|
|
922
|
+
{
|
|
923
|
+
message: FREE_TIER_ERROR_MESSAGE,
|
|
924
|
+
statusCode: 403,
|
|
925
|
+
isRetryable: false,
|
|
926
|
+
responseBody: APICallError.isInstance(e)
|
|
927
|
+
? e.responseBody
|
|
928
|
+
: providerError?.responseBody,
|
|
929
|
+
},
|
|
930
|
+
{ cause: e }
|
|
931
|
+
).toObject();
|
|
932
|
+
}
|
|
903
933
|
switch (true) {
|
|
904
934
|
case e instanceof DOMException && e.name === 'AbortError':
|
|
905
935
|
return new MessageV2.AbortedError(
|