@link-assistant/hive-mind 2.18.0 → 2.19.0

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.
@@ -23,7 +23,7 @@ if (typeof globalThis.use === 'undefined') {
23
23
  }
24
24
 
25
25
  import { log } from '../lib.mjs';
26
- import { FORMAL_AI_MODEL_ALIAS, FORMAL_AI_PROVIDER_MODEL_ID, isFormalAiModel } from '../formal-ai-model.lib.mjs';
26
+ import { FORMAL_AI_MODEL_ALIAS, isFormalAiModel } from '../formal-ai-model.lib.mjs';
27
27
 
28
28
  const execFileAsync = promisify(execFile);
29
29
 
@@ -34,366 +34,12 @@ const execFileAsync = promisify(execFile);
34
34
  // and its `use-m` bootstrap. Re-exported here so the public surface is unchanged.
35
35
  export { FORMAL_AI_MODEL_ALIAS, FORMAL_AI_PROVIDER_MODEL_ID, isFormalAiModel } from '../formal-ai-model.lib.mjs';
36
36
 
37
- const formalAiNativeModelAliases = {
38
- [FORMAL_AI_MODEL_ALIAS]: FORMAL_AI_MODEL_ALIAS,
39
- [FORMAL_AI_PROVIDER_MODEL_ID]: FORMAL_AI_MODEL_ALIAS,
40
- };
41
-
42
- const formalAiProviderModelAliases = {
43
- [FORMAL_AI_MODEL_ALIAS]: FORMAL_AI_PROVIDER_MODEL_ID,
44
- [FORMAL_AI_PROVIDER_MODEL_ID]: FORMAL_AI_PROVIDER_MODEL_ID,
45
- };
46
- // Claude models (Anthropic API)
47
- // Updated for Opus 4.5/4.6/4.7/4.8/5, Sonnet 4.6/5, and Fable 5 / Mythos 5 support
48
- // (Issue #1221, Issue #1238, Issue #1329, Issue #1433, Issue #1620, Issue #1832, Issue #1875, Issue #2003, Issue #2096)
49
- export const claudeModels = {
50
- ...formalAiNativeModelAliases,
51
- sonnet: 'claude-sonnet-5', // Sonnet 5 (Issue #2003)
52
- opus: 'claude-opus-5', // Opus 5 (default, Issue #2096)
53
- haiku: 'claude-haiku-4-5-20251001', // Haiku 4.5
54
- 'haiku-3-5': 'claude-3-5-haiku-20241022', // Haiku 3.5
55
- 'haiku-3': 'claude-3-haiku-20240307', // Haiku 3
56
- opusplan: 'opusplan', // Special mode: Opus for planning, Sonnet for execution (Issue #1223)
57
- // Claude Fable 5 — Anthropic's most capable widely released (Mythos-class) model, GA 2026-06-09 (Issue #1875)
58
- fable: 'claude-fable-5', // Fable 5 alias
59
- 'fable-5': 'claude-fable-5', // Fable 5 short alias
60
- 'claude-fable-5': 'claude-fable-5', // Fable 5 full ID
61
- // Claude Mythos 5 — shares Fable 5's capabilities without safety classifiers; limited availability (Project Glasswing) (Issue #1875)
62
- 'mythos-5': 'claude-mythos-5', // Mythos 5 short alias
63
- 'claude-mythos-5': 'claude-mythos-5', // Mythos 5 full ID
64
- // Shorter version aliases (Issue #1221, Issue #1329 - PR comment feedback)
65
- 'sonnet-5': 'claude-sonnet-5', // Sonnet 5 short alias (Issue #2003)
66
- 'sonnet-4-6': 'claude-sonnet-4-6', // Sonnet 4.6 short alias (Issue #1329)
67
- 'opus-5': 'claude-opus-5', // Opus 5 short alias (Issue #2096)
68
- 'opus-4-8': 'claude-opus-4-8', // Opus 4.8 short alias (Issue #1832)
69
- 'opus-4-7': 'claude-opus-4-7', // Opus 4.7 short alias (backward compatibility)
70
- 'opus-4-6': 'claude-opus-4-6', // Opus 4.6 short alias (backward compatibility)
71
- 'opus-4-5': 'claude-opus-4-5-20251101', // Opus 4.5 short alias
72
- 'sonnet-4-5': 'claude-sonnet-4-5-20250929', // Sonnet 4.5 short alias (backward compatibility)
73
- 'haiku-4-5': 'claude-haiku-4-5-20251001', // Haiku 4.5 short alias
74
- // Version aliases for backward compatibility (Issue #1221, Issue #1329, Issue #1620, Issue #1832, Issue #2096)
75
- 'claude-opus-5': 'claude-opus-5', // Opus 5 (Issue #2096)
76
- 'claude-opus-4-8': 'claude-opus-4-8', // Opus 4.8 (Issue #1832)
77
- 'claude-opus-4-7': 'claude-opus-4-7', // Opus 4.7 (backward compatibility)
78
- 'claude-sonnet-5': 'claude-sonnet-5', // Sonnet 5 (Issue #2003)
79
- 'claude-sonnet-4-6': 'claude-sonnet-4-6', // Sonnet 4.6 (Issue #1329)
80
- 'claude-opus-4-6': 'claude-opus-4-6', // Opus 4.6 (backward compatibility)
81
- 'claude-opus-4-5': 'claude-opus-4-5-20251101', // Opus 4.5
82
- 'claude-sonnet-4-5': 'claude-sonnet-4-5-20250929', // Sonnet 4.5 (backward compatibility)
83
- 'claude-haiku-4-5': 'claude-haiku-4-5-20251001', // Haiku 4.5
84
- };
85
-
86
- // Agent models (OpenCode API and Kilo Gateway via agent CLI)
87
- // Issue #1300: Updated free models to match agent PR #191
88
- // Issue #1543: Added qwen3.6-plus-free (former default) and nemotron-3-super-free per agent PR #234
89
- // Issue #1563: qwen3.6-plus-free free promotion ended (April 2026), nemotron-3-super-free is now default per agent PR #243
90
- export const agentModels = {
91
- ...formalAiProviderModelAliases,
92
- // OpenCode Zen free models (current)
93
- grok: 'opencode/grok-code',
94
- 'grok-code': 'opencode/grok-code',
95
- 'grok-code-fast-1': 'opencode/grok-code',
96
- 'big-pickle': 'opencode/big-pickle',
97
- 'gpt-5-nano': 'opencode/gpt-5-nano',
98
- 'minimax-m2.5-free': 'opencode/minimax-m2.5-free', // Upgraded from M2.1 (Issue #1391)
99
- 'nemotron-3-super-free': 'opencode/nemotron-3-super-free', // Default: NVIDIA hybrid Mamba-Transformer (Issue #1563)
100
- // Kilo Gateway free models (Issue #1282, updated in #1300)
101
- // Short names for Kilo-exclusive models (Issue #1300)
102
- 'glm-5-free': 'kilo/glm-5-free', // Kilo-exclusive
103
- 'glm-4.5-air-free': 'kilo/glm-4.5-air-free', // Kilo-exclusive: agent-centric model
104
- 'deepseek-r1-free': 'kilo/deepseek-r1-free', // Kilo-exclusive: reasoning model
105
- 'giga-potato-free': 'kilo/giga-potato-free', // Kilo-exclusive
106
- 'trinity-large-preview': 'kilo/trinity-large-preview', // Kilo-exclusive
107
- // Full names with kilo/ prefix
108
- 'kilo/glm-5-free': 'kilo/glm-5-free',
109
- 'kilo/glm-4.5-air-free': 'kilo/glm-4.5-air-free',
110
- 'kilo/minimax-m2.5-free': 'kilo/minimax-m2.5-free', // Also on OpenCode Zen
111
- 'kilo/deepseek-r1-free': 'kilo/deepseek-r1-free',
112
- 'kilo/giga-potato-free': 'kilo/giga-potato-free',
113
- 'kilo/trinity-large-preview': 'kilo/trinity-large-preview',
114
- // Deprecated free models (kept for backward compatibility)
115
- 'qwen3.6-plus-free': 'opencode/qwen3.6-plus-free', // Deprecated: free promotion ended April 2026 (Issue #1563)
116
- 'kimi-k2.5-free': 'opencode/kimi-k2.5-free', // Deprecated: not supported (Issue #1391)
117
- 'glm-4.7-free': 'opencode/glm-4.7-free', // Deprecated: no longer free
118
- 'minimax-m2.1-free': 'opencode/minimax-m2.1-free', // Deprecated: replaced by m2.5
119
- 'kilo/glm-4.7-free': 'kilo/glm-4.7-free', // Deprecated: replaced by glm-4.5-air-free
120
- 'kilo/kimi-k2.5-free': 'kilo/kimi-k2.5-free', // Deprecated: not recommended
121
- 'kilo/minimax-m2.1-free': 'kilo/minimax-m2.1-free', // Deprecated: replaced by m2.5
122
- // Premium models
123
- sonnet: 'anthropic/claude-3-5-sonnet',
124
- haiku: 'anthropic/claude-3-5-haiku',
125
- opus: 'anthropic/claude-3-opus',
126
- 'gemini-3-pro': 'google/gemini-3-pro',
127
- };
128
-
129
- // OpenCode models (OpenCode API)
130
- export const opencodeModels = {
131
- ...formalAiProviderModelAliases,
132
- gpt4: 'openai/gpt-4',
133
- gpt4o: 'openai/gpt-4o',
134
- claude: 'anthropic/claude-3-5-sonnet',
135
- sonnet: 'anthropic/claude-3-5-sonnet',
136
- opus: 'anthropic/claude-3-opus',
137
- gemini: 'google/gemini-pro',
138
- grok: 'opencode/grok-code',
139
- 'grok-code': 'opencode/grok-code',
140
- 'grok-code-fast-1': 'opencode/grok-code',
141
- };
142
-
143
- // Codex models (OpenAI API)
144
- export const codexModels = {
145
- ...formalAiNativeModelAliases,
146
- gpt5: 'gpt-5',
147
- 'gpt-5': 'gpt-5',
148
- 'gpt-5.5': 'gpt-5.5',
149
- 'gpt-5.5-mini': 'gpt-5.5-mini',
150
- 'gpt-5.5-nano': 'gpt-5.5-nano',
151
- 'gpt-5.6-sol': 'gpt-5.6-sol',
152
- 'gpt-5.6-terra': 'gpt-5.6-terra',
153
- 'gpt-5.6-luna': 'gpt-5.6-luna',
154
- 'gpt-5.4': 'gpt-5.4',
155
- 'gpt-5.4-mini': 'gpt-5.4-mini',
156
- 'gpt-5.4-nano': 'gpt-5.4-nano',
157
- 'gpt-5.2': 'gpt-5.2',
158
- 'gpt-5.2-codex': 'gpt-5.2-codex',
159
- 'gpt-5.3-codex': 'gpt-5.3-codex',
160
- 'gpt-5.3-codex-spark': 'gpt-5.3-codex-spark',
161
- 'gpt-5.1-codex-max': 'gpt-5.1-codex-max',
162
- 'openai.gpt-5.5': 'openai.gpt-5.5',
163
- 'openai.gpt-5.4': 'openai.gpt-5.4',
164
- 'openai.gpt-5.6-sol': 'openai.gpt-5.6-sol',
165
- 'openai.gpt-5.6-terra': 'openai.gpt-5.6-terra',
166
- 'openai.gpt-5.6-luna': 'openai.gpt-5.6-luna',
167
- 'codex-auto-review': 'codex-auto-review',
168
- 'o3-mini': 'o3-mini',
169
- gpt4: 'gpt-4',
170
- 'gpt-4': 'gpt-4',
171
- gpt4o: 'gpt-4o',
172
- 'gpt-4o': 'gpt-4o',
173
- };
174
-
175
- const CODEX_GENERATION_ALIAS_PATTERN = /^gpt-(\d+(?:\.\d+)?)-(sol|terra|luna)$/;
176
- const OPENAI_MODEL_PREFIX_PATTERN = /^openai([/.])/;
177
-
178
- /**
179
- * Resolve sol/terra/luna to the newest generation that contains the complete
180
- * alias family. A complete family prevents a partially rolled-out catalog from
181
- * moving only some aliases to a newer generation.
182
- */
183
- export const getLatestCodexGenerationAliases = (models = codexModels) => {
184
- const generations = new Map();
185
-
186
- for (const modelId of Object.values(models)) {
187
- const bareModelId = modelId.replace(OPENAI_MODEL_PREFIX_PATTERN, '');
188
- const match = bareModelId.match(CODEX_GENERATION_ALIAS_PATTERN);
189
- if (!match) continue;
190
-
191
- const [, generation, alias] = match;
192
- if (!generations.has(generation)) generations.set(generation, {});
193
- generations.get(generation)[alias] = bareModelId;
194
- }
195
-
196
- const latestCompleteGeneration = [...generations.entries()].filter(([, aliases]) => ['sol', 'terra', 'luna'].every(alias => aliases[alias])).sort(([left], [right]) => right.localeCompare(left, undefined, { numeric: true }))[0];
197
-
198
- return latestCompleteGeneration?.[1] || {};
199
- };
200
- const getCodexModelVariants = () => {
201
- const bareModels = [...new Set(Object.values(codexModels).map(modelId => modelId.replace(OPENAI_MODEL_PREFIX_PATTERN, '')))];
202
- const aliases = getLatestCodexGenerationAliases();
203
- const variants = { ...codexModels, ...aliases };
204
-
205
- for (const [name, modelId] of Object.entries({ ...Object.fromEntries(bareModels.map(modelId => [modelId, modelId])), ...aliases })) {
206
- variants[`openai/${name}`] = `openai/${modelId}`;
207
- variants[`openai.${name}`] = `openai.${modelId}`;
208
- }
209
-
210
- return variants;
211
- };
212
-
213
- export const CODEX_MODEL_VARIANTS = getCodexModelVariants();
214
-
215
- // Qwen Code models
216
- export const qwenModels = {
217
- ...formalAiNativeModelAliases,
218
- qwen: 'qwen3-coder-plus',
219
- 'qwen-coder': 'qwen3-coder-plus',
220
- qwen3: 'qwen3-coder-plus',
221
- 'qwen3-coder': 'qwen3-coder',
222
- 'qwen3-coder-plus': 'qwen3-coder-plus',
223
- 'qwen3-coder-flash': 'qwen3-coder-flash',
224
- 'qwen3.6-plus': 'qwen3.6-plus',
225
- 'qwen3.6-coder-plus': 'qwen3.6-coder-plus',
226
- };
227
-
228
- // Gemini models (Google Gemini CLI)
229
- // Keep aliases aligned with the Gemini CLI model aliases documented in
230
- // docs/cli/cli-reference.md: auto, pro, flash, and flash-lite.
231
- export const geminiModels = {
232
- ...formalAiNativeModelAliases,
233
- auto: 'auto',
234
- gemini: 'gemini-2.5-flash',
235
- flash: 'gemini-2.5-flash',
236
- '2.5-flash': 'gemini-2.5-flash',
237
- pro: 'gemini-2.5-pro',
238
- '2.5-pro': 'gemini-2.5-pro',
239
- lite: 'gemini-2.5-flash-lite',
240
- '2.5-lite': 'gemini-2.5-flash-lite',
241
- 'flash-lite': 'gemini-2.5-flash-lite',
242
- '3-flash': 'gemini-3-flash-preview',
243
- '3-pro': 'gemini-3-pro-preview',
244
- 'gemini-flash': 'gemini-2.5-flash',
245
- 'gemini-pro': 'gemini-2.5-pro',
246
- 'gemini-2.5-flash': 'gemini-2.5-flash',
247
- 'gemini-2.5-pro': 'gemini-2.5-pro',
248
- 'gemini-2.5-flash-lite': 'gemini-2.5-flash-lite',
249
- 'gemini-3-flash-preview': 'gemini-3-flash-preview',
250
- 'gemini-3-pro-preview': 'gemini-3-pro-preview',
251
- };
252
-
253
- // Default model for each tool (Issue #1473: centralized to avoid scattered hardcoded defaults)
254
- export const defaultModels = {
255
- claude: 'opus', // Issue #2033: Opus is the preferred default for Claude; sonnet remains available explicitly. Opus now maps to Opus 5 (Issue #2096)
256
- agent: 'nemotron-3-super-free', // Issue #1563: changed from qwen3.6-plus-free (free promotion ended) per agent PR #243
257
- opencode: 'grok-code-fast-1',
258
- codex: 'gpt-5.6-sol', // Issue #2027: GPT-5.6 Sol is the released Codex flagship; runtime falls back to gpt-5.5 when Sol is not in the local catalog
259
- qwen: 'qwen3-coder-plus',
260
- gemini: 'flash',
261
- };
262
-
263
- // Models that support 1M token context window via [1m] suffix (Issue #1221, Issue #1238, Issue #1329, Issue #1832)
264
- // See: https://code.claude.com/docs/en/model-config
265
- export const MODELS_SUPPORTING_1M_CONTEXT = [
266
- 'claude-fable-5', // Fable 5 — 1M context by default (Issue #1875)
267
- 'claude-mythos-5', // Mythos 5 — 1M context by default (Issue #1875)
268
- 'fable', // Fable 5 alias (Issue #1875)
269
- 'fable-5', // Fable 5 short alias (Issue #1875)
270
- 'mythos-5', // Mythos 5 short alias (Issue #1875)
271
- 'claude-opus-4-8', // Opus 4.8 (Issue #1832)
272
- 'claude-opus-4-7', // Opus 4.7 (Issue #1620)
273
- 'claude-opus-4-6',
274
- 'claude-opus-4-5-20251101',
275
- 'claude-sonnet-5', // Sonnet 5 — 1M context (Issue #2003)
276
- 'claude-sonnet-4-6', // Sonnet 4.6 (Issue #1329)
277
- 'claude-sonnet-4-5-20250929',
278
- 'claude-sonnet-4-5',
279
- 'claude-opus-5', // Opus 5 — 1M context (Issue #2096)
280
- 'sonnet', // Now maps to Sonnet 5 (Issue #2003)
281
- 'sonnet-5', // Short alias (Issue #2003)
282
- 'sonnet-4-6', // Short alias (Issue #1329)
283
- 'opus', // Now maps to Opus 5 (Issue #2096)
284
- 'opus-5', // Short alias (Issue #2096)
285
- 'opus-4-8', // Short alias (Issue #1832)
286
- 'opus-4-7', // Short alias (Issue #1620)
287
- 'opus-4-6', // Short alias (Issue #1221 - PR comment feedback)
288
- 'opus-4-5', // Short alias (Issue #1238)
289
- 'sonnet-4-5', // Short alias (Issue #1221 - PR comment feedback)
290
- ];
291
-
292
- // Free model to base model mapping for pricing lookup (Issue #1250, Issue #1473)
293
- // Free models like "kimi-k2.5-free" should use pricing from base model "kimi-k2.5"
294
- export const freeToBaseModelMap = {
295
- 'kimi-k2.5-free': 'kimi-k2.5',
296
- 'glm-4.7-free': 'glm-4.7',
297
- 'minimax-m2.1-free': 'minimax-m2.1',
298
- 'minimax-m2.5-free': 'minimax-m2.5',
299
- 'qwen3.6-plus-free': 'qwen3.6-plus', // Issue #1543
300
- 'nemotron-3-super-free': 'nemotron-3-super', // Issue #1543
301
- 'glm-5-free': 'glm-5',
302
- 'glm-4.5-air-free': 'glm-4.5-air',
303
- 'deepseek-r1-free': 'deepseek-r1',
304
- 'giga-potato-free': 'giga-potato',
305
- 'trinity-large-preview-free': 'trinity-large-preview',
306
- };
307
-
308
- // ─── VALIDATION-EXTENDED MODEL MAPS ──────────────────────────────────────────
309
- // These extend the base maps with full model ID identity entries for validation
310
- // (e.g., 'claude-sonnet-4-5-20250929' → 'claude-sonnet-4-5-20250929')
311
- // so that full model IDs are also accepted as valid inputs
312
- export const CLAUDE_MODELS = {
313
- ...claudeModels,
314
- 'claude-fable-5': 'claude-fable-5', // Fable 5 full ID (Issue #1875)
315
- 'claude-mythos-5': 'claude-mythos-5', // Mythos 5 full ID (Issue #1875)
316
- 'claude-opus-5': 'claude-opus-5', // Opus 5 full ID (Issue #2096)
317
- 'claude-opus-4-8': 'claude-opus-4-8', // Opus 4.8 full ID (Issue #1832)
318
- 'claude-opus-4-7': 'claude-opus-4-7', // Opus 4.7 full ID (Issue #1620)
319
- 'claude-sonnet-4-5-20250929': 'claude-sonnet-4-5-20250929',
320
- 'claude-opus-4-5-20251101': 'claude-opus-4-5-20251101',
321
- 'claude-haiku-4-5-20251001': 'claude-haiku-4-5-20251001',
322
- 'claude-3-5-haiku-20241022': 'claude-3-5-haiku-20241022',
323
- 'claude-3-haiku-20240307': 'claude-3-haiku-20240307',
324
- };
325
-
326
- export const OPENCODE_MODELS = {
327
- ...opencodeModels,
328
- 'openai/gpt-4': 'openai/gpt-4',
329
- 'openai/gpt-4o': 'openai/gpt-4o',
330
- 'anthropic/claude-3-5-sonnet': 'anthropic/claude-3-5-sonnet',
331
- 'anthropic/claude-3-opus': 'anthropic/claude-3-opus',
332
- 'google/gemini-pro': 'google/gemini-pro',
333
- 'opencode/grok-code': 'opencode/grok-code',
334
- };
335
-
336
- export const CODEX_MODELS = {
337
- ...CODEX_MODEL_VARIANTS,
338
- 'gpt-5': 'gpt-5',
339
- 'gpt-5.5': 'gpt-5.5',
340
- 'gpt-5.5-mini': 'gpt-5.5-mini',
341
- 'gpt-5.5-nano': 'gpt-5.5-nano',
342
- 'gpt-5.6-sol': 'gpt-5.6-sol',
343
- 'gpt-5.6-terra': 'gpt-5.6-terra',
344
- 'gpt-5.6-luna': 'gpt-5.6-luna',
345
- 'gpt-5.4': 'gpt-5.4',
346
- 'gpt-5.4-mini': 'gpt-5.4-mini',
347
- 'gpt-5.4-nano': 'gpt-5.4-nano',
348
- 'gpt-5.2': 'gpt-5.2',
349
- 'gpt-5.2-codex': 'gpt-5.2-codex',
350
- 'gpt-5.3-codex': 'gpt-5.3-codex',
351
- 'gpt-5.3-codex-spark': 'gpt-5.3-codex-spark',
352
- 'gpt-5.1-codex-max': 'gpt-5.1-codex-max',
353
- 'openai.gpt-5.5': 'openai.gpt-5.5',
354
- 'openai.gpt-5.4': 'openai.gpt-5.4',
355
- 'openai.gpt-5.6-sol': 'openai.gpt-5.6-sol',
356
- 'openai.gpt-5.6-terra': 'openai.gpt-5.6-terra',
357
- 'openai.gpt-5.6-luna': 'openai.gpt-5.6-luna',
358
- 'codex-auto-review': 'codex-auto-review',
359
- 'gpt-4': 'gpt-4',
360
- 'gpt-4o': 'gpt-4o',
361
- };
362
-
363
- export const QWEN_MODELS = {
364
- ...qwenModels,
365
- 'qwen3-coder': 'qwen3-coder',
366
- 'qwen3-coder-plus': 'qwen3-coder-plus',
367
- 'qwen3-coder-flash': 'qwen3-coder-flash',
368
- 'qwen3.6-plus': 'qwen3.6-plus',
369
- 'qwen3.6-coder-plus': 'qwen3.6-coder-plus',
370
- };
371
-
372
- export const GEMINI_MODELS = {
373
- ...geminiModels,
374
- 'gemini-2.5-flash': 'gemini-2.5-flash',
375
- 'gemini-2.5-pro': 'gemini-2.5-pro',
376
- 'gemini-2.5-flash-lite': 'gemini-2.5-flash-lite',
377
- 'gemini-3-flash-preview': 'gemini-3-flash-preview',
378
- 'gemini-3-pro-preview': 'gemini-3-pro-preview',
379
- };
380
-
381
- export const AGENT_MODELS = {
382
- ...agentModels,
383
- 'opencode/grok-code': 'opencode/grok-code',
384
- 'opencode/big-pickle': 'opencode/big-pickle',
385
- 'opencode/gpt-5-nano': 'opencode/gpt-5-nano',
386
- 'opencode/minimax-m2.5-free': 'opencode/minimax-m2.5-free',
387
- 'opencode/nemotron-3-super-free': 'opencode/nemotron-3-super-free', // Issue #1563: now default
388
- 'opencode/qwen3.6-plus-free': 'opencode/qwen3.6-plus-free', // Deprecated: free promotion ended (Issue #1563)
389
- 'opencode/kimi-k2.5-free': 'opencode/kimi-k2.5-free', // Deprecated
390
- 'opencode/glm-4.7-free': 'opencode/glm-4.7-free', // Deprecated
391
- 'opencode/minimax-m2.1-free': 'opencode/minimax-m2.1-free', // Deprecated
392
- 'anthropic/claude-3-5-sonnet': 'anthropic/claude-3-5-sonnet',
393
- 'anthropic/claude-3-5-haiku': 'anthropic/claude-3-5-haiku',
394
- 'anthropic/claude-3-opus': 'anthropic/claude-3-opus',
395
- 'google/gemini-3-pro': 'google/gemini-3-pro',
396
- };
37
+ // The bundled catalogue itself lives in ./catalog.mjs (issue #2202); everything
38
+ // it exports is re-exported here, so `src/models/index.mjs` remains the single
39
+ // import site for callers. The named import is what the functions below read —
40
+ // `export *` re-exports without binding the names locally.
41
+ export * from './catalog.mjs';
42
+ import { AGENT_MODELS, agentModels, CLAUDE_MODELS, claudeModels, CODEX_MODEL_VARIANTS, CODEX_MODELS, defaultModels, GEMINI_MODELS, geminiModels, MODELS_SUPPORTING_1M_CONTEXT, OPENCODE_MODELS, opencodeModels, QWEN_MODELS, qwenModels } from './catalog.mjs';
397
43
 
398
44
  // ─── MODEL MAPPING FUNCTIONS ─────────────────────────────────────────────────
399
45
 
@@ -562,7 +208,7 @@ export const getValidModelsForTool = tool => {
562
208
  export const primaryModelNames = {
563
209
  claude: ['opus', 'sonnet', 'haiku', 'opusplan', 'fable', FORMAL_AI_MODEL_ALIAS],
564
210
  opencode: ['grok', 'gpt4o', FORMAL_AI_MODEL_ALIAS],
565
- codex: ['gpt-5.6-sol', 'gpt-5.5', 'gpt-5.6-terra', 'gpt-5.6-luna', 'gpt-5.4', 'gpt-5.4-mini', 'gpt-5.3-codex-spark', FORMAL_AI_MODEL_ALIAS],
211
+ codex: ['gpt-5.6-sol', 'gpt-6-astra', 'gpt-5.5', 'gpt-5.6-terra', 'gpt-5.6-luna', 'gpt-5.4', 'gpt-5.4-mini', 'gpt-5.3-codex-spark', FORMAL_AI_MODEL_ALIAS],
566
212
  agent: ['nemotron-3-super-free', 'minimax-m2.5-free', 'big-pickle', 'gpt-5-nano', 'glm-5-free', 'deepseek-r1-free', FORMAL_AI_MODEL_ALIAS],
567
213
  qwen: ['qwen3-coder-plus', 'qwen3-coder', 'qwen3-coder-flash', FORMAL_AI_MODEL_ALIAS],
568
214
  gemini: ['flash', 'pro', 'flash-lite', 'auto', FORMAL_AI_MODEL_ALIAS],
@@ -1251,9 +897,16 @@ export const defaultFallbackModels = {
1251
897
  // off to Claude Opus; mirror that documented fallback here (Issue #1875).
1252
898
  // See: https://platform.claude.com/docs/en/about-claude/models/introducing-claude-fable-5-and-claude-mythos-5
1253
899
  'claude-fable-5': 'opus',
900
+ // Claude Fable 5.1 steps down to the previous Fable generation before leaving
901
+ // the Mythos class entirely, so a capacity failure keeps the same model family
902
+ // (Issue #2202).
903
+ 'claude-fable-5-1': 'fable-5',
1254
904
  // Claude Mythos 5 (limited availability) falls back to the generally available
1255
905
  // Mythos-class model, Claude Fable 5 (Issue #1875).
1256
906
  'claude-mythos-5': 'fable',
907
+ // Claude Mythos 5.1 (invite only) falls back to the generally available
908
+ // Mythos-class model, which the `fable` alias now resolves to (Issue #2202).
909
+ 'claude-mythos-5-1': 'fable',
1257
910
  // Claude Opus 5 falls back to the prior Opus generation (Issue #2096).
1258
911
  'claude-opus-5': 'opus-4-8',
1259
912
  'claude-opus-4-8': 'opus-4-7',
@@ -1271,9 +924,17 @@ export const defaultFallbackModels = {
1271
924
  // on. So the flagship chain walks sol -> terra -> gpt-5.5 -> gpt-5.4 -> gpt-5.2
1272
925
  // and never detours through the smaller `luna` tier. The smaller `luna` variant,
1273
926
  // if requested directly, steps down to the previous full generation as well.
927
+ // GPT-6 Astra is preview-gated, so a fallback is the difference between a
928
+ // degraded run and a failed one; it steps down to the GPT-5.6 flagship
929
+ // (Issue #2202).
930
+ 'gpt-6-astra': 'gpt-5.6-sol',
931
+ 'openai.gpt-6-astra': 'openai.gpt-5.6-sol',
1274
932
  'gpt-5.6-sol': 'gpt-5.6-terra',
1275
933
  'gpt-5.6-terra': 'gpt-5.5',
1276
934
  'gpt-5.6-luna': 'gpt-5.5',
935
+ // GPT-5.6 Cyber is gated on the Daybreak program (Issue #2202).
936
+ 'gpt-5.6-cyber': 'gpt-5.6-sol',
937
+ 'openai.gpt-5.6-cyber': 'openai.gpt-5.6-sol',
1277
938
  'openai.gpt-5.6-sol': 'openai.gpt-5.6-terra',
1278
939
  'openai.gpt-5.6-terra': 'openai.gpt-5.5',
1279
940
  'openai.gpt-5.6-luna': 'openai.gpt-5.5',
@@ -17,7 +17,7 @@
17
17
  * isolation hold rather than merely look tidy (all three were measured first —
18
18
  * see `experiments/issue-2164/`):
19
19
  *
20
- * 1. **The router serves TLS on 443.** Router 0.119.0 terminates TLS itself
20
+ * 1. **The router serves TLS on 443.** The router terminates TLS itself
21
21
  * (`TLS_SELF_SIGNED=1`) and prints its CA with `router tls ca`. Plain HTTP
22
22
  * would rule out `gh` entirely, which refuses non-HTTPS hosts.
23
23
  * 2. **GitHub is intercepted by name, not by reconfiguring `gh`.** The
@@ -30,14 +30,21 @@
30
30
  * itself (measured: 502 on every proxied call).
31
31
  * 3. **`github.com` itself stays untouched**, so git is pointed at the router's
32
32
  * smart-HTTP proxy explicitly with `url.<router>/git/.insteadOf`.
33
+ * 4. **Every path is derived from a route dialect, never spelled inline**
34
+ * (issue #2202). Router 1.0.0 moved the whole public surface and removed the
35
+ * old aliases, so the shape depends on which image is running;
36
+ * `router-routes.lib.mjs` holds the two tables and this module asks it.
33
37
  *
34
38
  * This module is deliberately pure: it decides *what a routed task should see*
35
39
  * and nothing else, so the policy is testable without Docker. Container
36
40
  * lifecycle lives in `router-sidecar.lib.mjs`.
37
41
  *
38
42
  * @see https://github.com/link-assistant/hive-mind/issues/2164
43
+ * @see https://github.com/link-assistant/hive-mind/issues/2202
39
44
  */
40
45
 
46
+ import { buildRouterGitUrlPrefix, buildRouterToolServiceUrl, resolveRouterRouteDialect, ROUTER_TOOL_SERVICE } from './router-routes.lib.mjs';
47
+
41
48
  export const ROUTER_SIDECAR_CONTAINER_NAME = 'hive-mind-router';
42
49
  export const ROUTER_SIDECAR_NETWORK_NAME = 'hive-mind-router';
43
50
  // Tasks reach the sidecar by alias, so the endpoint stays stable across restarts.
@@ -52,7 +59,42 @@ export const ROUTER_SIDECAR_LABEL = 'com.link-assistant.hive-mind.router';
52
59
  // 0.110.0 is the floor: it carries the compare-based force-push mediation
53
60
  // (upstream router#273), without which a routed task can still rewrite history.
54
61
  // Override with HIVE_MIND_ROUTER_IMAGE.
55
- export const ROUTER_SIDECAR_IMAGE = 'ghcr.io/link-assistant/router:0.119.0';
62
+ //
63
+ // 0.125.4 is the highest 0.x release, and the pin sits there rather than on
64
+ // 0.119.0 because 0.120.0 is where a routed Codex task stopped being told that
65
+ // new models do not exist: the ChatGPT backend gates them behind a `version`
66
+ // header the router only started sending then, and without it `POST /responses`
67
+ // answers `Model not found` (issue #2202, requirement R1).
68
+ //
69
+ // The pin deliberately stays on 0.x even though upstream is at 1.2.0. Router
70
+ // 1.0.0 moved the GitHub proxy under `/api/services/github/api/v3` and `gh`
71
+ // builds a custom host's REST base as `https://<host>/api/v3/` with no
72
+ // path-prefix setting, so no client-side configuration reaches the new prefix
73
+ // and bumping across 1.0 would silently delete the `gh` mediation this feature
74
+ // shipped (upstream link-assistant/router#415; measured in
75
+ // `docs/case-studies/issue-2202/data/measurements/
76
+ // router-route-comparison-2026-09-04.md`). Every other route is already
77
+ // supported on both dialects, so the day upstream lands a gh-reachable base
78
+ // this becomes a one-line change with the tests already in place.
79
+ export const ROUTER_SIDECAR_IMAGE = 'ghcr.io/link-assistant/router:0.125.4';
80
+
81
+ /**
82
+ * Router image this run should use.
83
+ *
84
+ * Lives here rather than in `router-sidecar.lib.mjs` because the route dialect
85
+ * is derived from it and this module is the leaf both sides import.
86
+ */
87
+ export const resolveRouterSidecarImage = (env = process.env) => String(env?.HIVE_MIND_ROUTER_IMAGE || '').trim() || ROUTER_SIDECAR_IMAGE;
88
+
89
+ /**
90
+ * Route dialect for this run, resolved from the image that will actually be
91
+ * started (or from `HIVE_MIND_ROUTER_ROUTES` when an operator declares it).
92
+ *
93
+ * @returns {{dialect: object, source: 'env'|'image'|'default', error: string|null}}
94
+ */
95
+ export function resolveRouterDialect({ env = process.env } = {}) {
96
+ return resolveRouterRouteDialect({ image: resolveRouterSidecarImage(env), env });
97
+ }
56
98
 
57
99
  /** The one GitHub name the router impersonates; see the module header. */
58
100
  export const ROUTER_GITHUB_API_HOST = 'api.github.com';
@@ -259,12 +301,19 @@ export function resolveRouterGitHubRouting({ env = process.env, external = false
259
301
  *
260
302
  * @returns {Array<[string, string]>}
261
303
  */
262
- export function buildRouterGitConfigEntries({ baseUrl, token, githubMode = 'transparent' } = {}) {
304
+ export function buildRouterGitConfigEntries({ baseUrl, token, githubMode = 'transparent', dialect = null } = {}) {
263
305
  if (!baseUrl || !token || githubMode === 'off') return [];
306
+ const routes = dialect || resolveRouterDialect().dialect;
307
+ const gitPrefix = buildRouterGitUrlPrefix({ baseUrl, dialect: routes });
308
+ if (!gitPrefix) return [];
309
+ // The CA and the header stay scoped to the origin, not to the git prefix:
310
+ // git matches these keys by URL prefix, and the origin covers every path the
311
+ // router serves — including a future one — while the rewrite target must be
312
+ // exact.
264
313
  const routerUrl = `${String(baseUrl).replace(/\/+$/, '')}/`;
265
314
  return [
266
315
  ['credential.helper', ''],
267
- ['url.' + `${routerUrl}git/` + '.insteadOf', 'https://github.com/'],
316
+ ['url.' + gitPrefix + '.insteadOf', 'https://github.com/'],
268
317
  [`http.${routerUrl}.sslCAInfo`, ROUTER_CA_CONTAINER_PATH],
269
318
  [`http.${routerUrl}.extraHeader`, `Authorization: Bearer ${token}`],
270
319
  ];
@@ -287,9 +336,11 @@ export function buildRouterGitConfigEntries({ baseUrl, token, githubMode = 'tran
287
336
  *
288
337
  * @returns {Record<string,string>}
289
338
  */
290
- export function buildRouterTaskEnv({ tool = 'claude', baseUrl, token, githubMode = 'transparent', ghHost = null, homeDir = '/home/box' } = {}) {
339
+ export function buildRouterTaskEnv({ tool = 'claude', baseUrl, token, githubMode = 'transparent', ghHost = null, homeDir = '/home/box', dialect = null } = {}) {
291
340
  if (!baseUrl || !token) return {};
292
341
  const normalizedTool = normalizeTool(tool);
342
+ const routes = dialect || resolveRouterDialect().dialect;
343
+ const serviceUrl = buildRouterToolServiceUrl({ baseUrl, dialect: routes, tool: normalizedTool });
293
344
  const taskEnv = {
294
345
  HIVE_MIND_USE_ROUTER: '1',
295
346
  HIVE_MIND_ROUTER_URL: baseUrl,
@@ -303,15 +354,28 @@ export function buildRouterTaskEnv({ tool = 'claude', baseUrl, token, githubMode
303
354
  GIT_TERMINAL_PROMPT: '0',
304
355
  };
305
356
  if (ANTHROPIC_TOOLS.has(normalizedTool)) {
306
- taskEnv.ANTHROPIC_BASE_URL = baseUrl;
357
+ // serviceUrl is the origin itself on the legacy dialect and
358
+ // <origin>/api/services/anthropic on the canonical one; Claude Code appends
359
+ // /v1/messages to whichever it is given.
360
+ if (serviceUrl) taskEnv.ANTHROPIC_BASE_URL = serviceUrl;
307
361
  taskEnv.ANTHROPIC_AUTH_TOKEN = token;
308
362
  taskEnv.ANTHROPIC_API_KEY = token;
363
+ } else if (normalizedTool === 'gemini') {
364
+ // gemini-cli reads neither of the two variables above. Before issue #2202 it
365
+ // was handed OPENAI_BASE_URL and ignored it, which meant a "routed" gemini
366
+ // task quietly called Google directly — an isolation hole, not a detail.
367
+ // Only the canonical dialect serves Gemini at all, so a legacy pin wires
368
+ // nothing rather than pointing the CLI at a 404.
369
+ if (serviceUrl) {
370
+ taskEnv.GOOGLE_GEMINI_BASE_URL = serviceUrl;
371
+ taskEnv.GEMINI_API_KEY = token;
372
+ }
309
373
  } else {
310
- // codex, opencode, gemini and qwen all speak the OpenAI-compatible surface,
311
- // which the router serves under /v1. Codex additionally ignores
312
- // OPENAI_BASE_URL and needs the generated provider entry written by
313
- // buildRouterTaskWiringScript().
314
- taskEnv.OPENAI_BASE_URL = `${baseUrl}/v1`;
374
+ // codex, opencode and qwen all speak the OpenAI-compatible surface. On the
375
+ // canonical dialect each has its own service prefix, so they are no longer
376
+ // interchangeable. Codex additionally ignores OPENAI_BASE_URL and needs the
377
+ // generated provider entry written by buildRouterTaskWiringScript().
378
+ if (serviceUrl) taskEnv.OPENAI_BASE_URL = serviceUrl;
315
379
  taskEnv.OPENAI_API_KEY = token;
316
380
  if (normalizedTool === 'codex') taskEnv.CODEX_HOME = `${homeDir}/.codex`;
317
381
  }
@@ -334,10 +398,12 @@ export function buildRouterTaskEnv({ tool = 'claude', baseUrl, token, githubMode
334
398
  * Codex 0.147 ignores `OPENAI_BASE_URL` (measured: it kept calling
335
399
  * api.openai.com and returned 401), so the endpoint has to be declared as a
336
400
  * provider in `CODEX_HOME/config.toml`. `wire_api = "responses"` matches the
337
- * router's `/v1/responses`.
401
+ * router's `…/responses`, which the dialect decides the prefix of.
338
402
  */
339
- export function buildRouterCodexConfig({ baseUrl } = {}) {
340
- return `model_provider = "${CODEX_PROVIDER_ID}"\n\n[model_providers.${CODEX_PROVIDER_ID}]\nname = "Hive Mind Router"\nbase_url = "${baseUrl}/v1"\nenv_key = "OPENAI_API_KEY"\nwire_api = "responses"\n`;
403
+ export function buildRouterCodexConfig({ baseUrl, dialect = null } = {}) {
404
+ const routes = dialect || resolveRouterDialect().dialect;
405
+ const codexUrl = buildRouterToolServiceUrl({ baseUrl, dialect: routes, tool: 'codex' }) || `${baseUrl}/v1`;
406
+ return `model_provider = "${CODEX_PROVIDER_ID}"\n\n[model_providers.${CODEX_PROVIDER_ID}]\nname = "Hive Mind Router"\nbase_url = "${codexUrl}"\nenv_key = "OPENAI_API_KEY"\nwire_api = "responses"\n`;
341
407
  }
342
408
 
343
409
  /**
@@ -380,7 +446,7 @@ export function buildRouterProviderArgs({ name, baseUrl, model, models = null, a
380
446
  * the call, GET /v1/models advertises `{"id":"formal-ai","owned_by":
381
447
  * "hive-mind-formal-ai"}` and a chat completion for that id is answered by the
382
448
  * sidecar (HTTP 200), with `"provider":"openai-compatible","model":"formal-ai"`
383
- * recorded in /data/router/audit.jsonl. The pin has since moved to 0.119.0; the
449
+ * recorded in /data/router/audit.jsonl. The pin has since moved to 0.125.4; the
384
450
  * `providers add` surface and the automatic-routing behaviour this relies on
385
451
  * (upstream router#260) are unchanged there, but the probe has not been re-run
386
452
  * against it — re-run it before treating the measurement as current.
@@ -413,7 +479,7 @@ export function buildRouterFormalAiProviderArgs({ baseUrl } = {}) {
413
479
  *
414
480
  * @returns {string} a `sh -c` script
415
481
  */
416
- export function buildRouterTaskWiringScript({ routerIp = null, caCertificate = null, homeDir = '/home/box', tool = 'claude', baseUrl = getInternalRouterBaseUrl(), githubMode = 'transparent' } = {}) {
482
+ export function buildRouterTaskWiringScript({ routerIp = null, caCertificate = null, homeDir = '/home/box', tool = 'claude', baseUrl = getInternalRouterBaseUrl(), githubMode = 'transparent', dialect = null } = {}) {
417
483
  const lines = ['set -e'];
418
484
  if (caCertificate) {
419
485
  lines.push(`cat > ${ROUTER_CA_CONTAINER_PATH} <<'HIVE_MIND_ROUTER_CA_PEM'`, String(caCertificate).trim(), 'HIVE_MIND_ROUTER_CA_PEM', `chmod 0644 ${ROUTER_CA_CONTAINER_PATH}`);
@@ -427,7 +493,7 @@ export function buildRouterTaskWiringScript({ routerIp = null, caCertificate = n
427
493
  }
428
494
  if (normalizeTool(tool) === 'codex') {
429
495
  const codexHome = `${homeDir}/.codex`;
430
- lines.push(`mkdir -p ${codexHome}`, `cat > ${codexHome}/config.toml <<'HIVE_MIND_ROUTER_CODEX_TOML'`, buildRouterCodexConfig({ baseUrl }).trimEnd(), 'HIVE_MIND_ROUTER_CODEX_TOML');
496
+ lines.push(`mkdir -p ${codexHome}`, `cat > ${codexHome}/config.toml <<'HIVE_MIND_ROUTER_CODEX_TOML'`, buildRouterCodexConfig({ baseUrl, dialect }).trimEnd(), 'HIVE_MIND_ROUTER_CODEX_TOML');
431
497
  // The exec runs as root; the task does not, and codex rewrites its own
432
498
  // config. Hand the directory to whoever owns the home directory.
433
499
  lines.push(`owner=$(stat -c '%u:%g' ${homeDir} 2>/dev/null || echo '')`, `if [ -n "$owner" ]; then chown -R "$owner" ${codexHome}; fi`);
@@ -460,8 +526,26 @@ export function getRouterSuppressedCredentialPaths({ tool = 'claude', ghRouted =
460
526
  * Human-readable warnings for the parts of issue #2164 that are not yet covered,
461
527
  * so an experimental run states its own limits instead of implying full coverage.
462
528
  */
463
- export function describeRouterCoverageGaps({ model = null, tool = 'claude', githubMode = 'transparent' } = {}) {
529
+ export function describeRouterCoverageGaps({ model = null, tool = 'claude', githubMode = 'transparent', dialect = null } = {}) {
464
530
  const gaps = [];
531
+ const routes = dialect || resolveRouterDialect().dialect;
532
+ // Measured in experiments/issue-2202/compare-router-routes.sh: on router >= 1.0
533
+ // the GitHub proxy is only mounted under /api/services/github/api/*, and `gh`
534
+ // builds a custom host's REST base as `https://<host>/api/v3/` with no
535
+ // path-prefix setting. Nothing on the client side closes that gap, so a run on
536
+ // the canonical dialect has to say what it lost rather than 404 in the task.
537
+ if (githubMode !== 'off' && routes && routes.ghReachable === false) {
538
+ gaps.push(`This router serves the GitHub proxy under ${routes.github.rest} and ${routes.github.graphql}, but gh has no path-prefix setting and always calls /api/v3/ — so \`gh api\` and GraphQL are NOT mediated on this dialect. git still is. Pin HIVE_MIND_ROUTER_IMAGE to a 0.x router to keep gh routed (upstream link-assistant/router#415).`);
539
+ }
540
+ // buildRouterTaskEnv wires no base URL when the dialect serves nothing for
541
+ // this tool (Gemini on the legacy dialect is the live case). The CLI then
542
+ // keeps its own credential and calls the vendor directly, which is the
543
+ // opposite of what `--use-router` was asked for — so the run has to say it
544
+ // rather than look routed.
545
+ const service = ROUTER_TOOL_SERVICE[normalizeTool(tool)];
546
+ if (!service || routes?.services?.[service] === null || routes?.services?.[service] === undefined) {
547
+ gaps.push(`This router (${routes?.description ?? 'unknown dialect'}) serves no endpoint for '${normalizeTool(tool)}', so its model traffic is NOT routed: the CLI keeps its own credential and calls the vendor directly. Pin HIVE_MIND_ROUTER_IMAGE to a router that serves this tool, or run it without --use-router rather than assuming it is mediated.`);
548
+ }
465
549
  if (githubMode === 'off') {
466
550
  gaps.push('GitHub traffic is NOT routed: the task keeps its own gh credential, and destructive API calls are not mediated. Unset HIVE_MIND_ROUTER_GITHUB, or set HIVE_MIND_ROUTER_GH_HOST for an external router.');
467
551
  }
@@ -471,7 +555,7 @@ export function describeRouterCoverageGaps({ model = null, tool = 'claude', gith
471
555
  // capability the router looked for. Reported as router#272 and fixed upstream
472
556
  // in router#273: from 0.110.0 the router asks GitHub's compare API whether the
473
557
  // proposed tip is ahead of the current one and forwards the packfile only if it
474
- // is, failing closed on any answer it cannot read. The pin is now 0.119.0, so
558
+ // is, failing closed on any answer it cannot read. The pin is now 0.125.4, so
475
559
  // that layer is live and this is no longer warned about.
476
560
  //
477
561
  // What remains uncovered is the other half of R13. The router's built-in