@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 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/minimax-m2.5-free` mode with:
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 --model opencode/minimax-m2.5-free`
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 OpenCode Zen MiniMax M2.5 Free, 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
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 opencode/minimax-m2.5-free
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 MiniMax M2.5)
193
+ # Default model (free Nemotron 3 Super)
194
194
  echo "hi" | agent
195
195
 
196
- # Other free models (in order of recommendation)
197
- echo "hi" | agent --model opencode/minimax-m2.5-free
198
- echo "hi" | agent --model opencode/ling-2.6-flash-free
199
- echo "hi" | agent --model opencode/hy3-preview-free
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: opencode/minimax-m2.5-free
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.11",
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.5.1",
54
- "@ai-sdk/amazon-bedrock": "^5.0.102",
55
- "@ai-sdk/anthropic": "^4.0.69",
56
- "@ai-sdk/azure": "^4.0.86",
57
- "@ai-sdk/google": "^4.0.86",
58
- "@ai-sdk/google-vertex": "^5.0.99",
59
- "@ai-sdk/groq": "^4.0.53",
60
- "@ai-sdk/mcp": "^2.0.63",
61
- "@ai-sdk/mistral": "^4.0.55",
62
- "@ai-sdk/openai": "^4.0.82",
63
- "@ai-sdk/openai-compatible": "^3.0.60",
64
- "@ai-sdk/provider": "^4.0.20",
65
- "@ai-sdk/xai": "^5.0.13",
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.31.0",
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.18.33",
74
+ "@opencode-ai/sdk": "^1.19.0",
74
75
  "@openrouter/ai-sdk-provider": "^3.1.0",
75
- "@opentui/core": "0.5.13",
76
- "@opentui/solid": "0.5.13",
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.18.2",
82
- "ai": "^7.0.123",
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": "^1.3.0",
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.12",
93
- "hono-openapi": "^1.3.3",
94
- "ignore": "^7.0.10",
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.8.0",
98
+ "lino-objects-codec": "^0.9.2",
98
99
  "log-lazy": "^1.0.4",
99
100
  "minimatch": "^10.2.6",
100
- "remeda": "^2.50.0",
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.5"
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.11.0",
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.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/link-assistant/agent/issues/322"
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
- * "(big-pickle minimax-m2.5-free nemotron-3-super-free hy3-preview-free ling-2.6-flash-free gpt-5-nano same)"
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
  */
@@ -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: opencode/gpt-5-nano (free, 400K context), which falls back to the --model model when --model points at another provider. Overridden by --compaction-models if both are specified.',
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', {
@@ -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 = 'opencode/minimax-m2.5-free';
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 = 'opencode/gpt-5-nano';
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
- * Default compaction models cascade, ordered from smallest/cheapest context to largest.
33
- * During compaction, the system tries each model in order. If the used context exceeds
34
- * a model's context limit, it skips to the next larger model. If a model's rate limit
35
- * is reached, it also skips to the next model.
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
- * Parsed as links notation references sequence (single anonymous link):
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
- '(big-pickle minimax-m2.5-free nemotron-3-super-free hy3-preview-free ling-2.6-flash-free gpt-5-nano same)';
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(['trinity-large-preview-free']);
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
- const FREE_MODEL_OVERRIDES: Record<
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
- 'minimax-m2.5-free': {
14
- name: 'MiniMax M2.5 Free',
23
+ 'nemotron-3.5-lightning-free': {
24
+ name: 'Nemotron 3.5 Lightning Free',
15
25
  reasoning: true,
16
- release_date: '2026-02-12',
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,
@@ -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 500+ AI models through Kilo Gateway
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 required: run `agent auth login` and select "Kilo Gateway"
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 available after authentication:
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
- // Add Kilo provider for access to 500+ AI models through Kilo Gateway
936
- // Free models available: GLM-5, GLM 4.7, Kimi K2.5, MiniMax M2.1, Giga Potato
937
- // @see https://kilo.ai/docs/gateway
938
- // @see https://github.com/link-assistant/agent/issues/159
939
- database['kilo'] = {
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 (freshProvider?.models[modelID]) {
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
- * Priority for free models:
2087
- * 1. If model is uniquely available in one provider, use that provider
2088
- * 2. If model is available in multiple providers, prioritize based on free model availability:
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
- // Define model-to-provider mappings for free models
2102
- // Models unique to Kilo (GLM models from Z.AI are only free on Kilo)
2103
- const kiloUniqueModels = [
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/glm-5-free" -> { providerID: "kilo", modelID: "glm-5-free" }
2191
- * - "glm-5-free" -> { providerID: "kilo", modelID: "glm-5-free" } (resolved)
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/deepseek-r1-free", no fallback will occur.
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
- 'minimax-m2.5-free': ['opencode', 'kilo'],
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/deepseek-r1-free"), this returns empty array.
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'] ?? candidate['statusCode'] ?? candidate['status'];
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(