@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.
- package/CHANGELOG.md +17 -0
- package/README.hi.md +2 -0
- package/README.md +2 -0
- package/README.ru.md +2 -0
- package/README.zh.md +2 -0
- package/package.json +3 -2
- package/src/agentic-cli-freshness.lib.mjs +118 -0
- package/src/agentic-cli-updater.lib.mjs +8 -4
- package/src/docker-sidecar.lib.mjs +17 -1
- package/src/hive-models.lib.mjs +181 -0
- package/src/hive-models.mjs +20 -0
- package/src/locales/en.lino +2 -1
- package/src/locales/hi.lino +2 -1
- package/src/locales/ru.lino +2 -1
- package/src/locales/zh.lino +2 -1
- package/src/model-catalogue-fetch.lib.mjs +333 -0
- package/src/model-catalogue-render.lib.mjs +191 -0
- package/src/model-catalogue-sources.lib.mjs +224 -0
- package/src/model-catalogue.lib.mjs +385 -0
- package/src/models/catalog.mjs +408 -0
- package/src/models/index.mjs +23 -362
- package/src/router-isolation.lib.mjs +103 -19
- package/src/router-routes.lib.mjs +250 -0
- package/src/router-sidecar.lib.mjs +33 -13
- package/src/solve.config.lib.mjs +5 -0
- package/src/solve.escalate.lib.mjs +3 -0
- package/src/solve.mjs +12 -0
- package/src/task.config.lib.mjs +5 -0
- package/src/task.mjs +12 -0
- package/src/telegram-bot.mjs +4 -1
- package/src/telegram-models-command.lib.mjs +157 -0
- package/src/telegram-ui-messages.lib.mjs +1 -1
package/src/models/index.mjs
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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.**
|
|
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
|
-
|
|
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.' +
|
|
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
|
-
|
|
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
|
|
311
|
-
//
|
|
312
|
-
// OPENAI_BASE_URL and needs the
|
|
313
|
-
// buildRouterTaskWiringScript().
|
|
314
|
-
taskEnv.OPENAI_BASE_URL =
|
|
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
|
|
401
|
+
* router's `…/responses`, which the dialect decides the prefix of.
|
|
338
402
|
*/
|
|
339
|
-
export function buildRouterCodexConfig({ baseUrl } = {}) {
|
|
340
|
-
|
|
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.
|
|
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.
|
|
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
|