@link-assistant/hive-mind 1.35.9 → 1.35.10
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 +13 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/agent.lib.mjs +7 -47
- package/src/claude.lib.mjs +2 -2
- package/src/claude.prompts.lib.mjs +2 -1
- package/src/github.lib.mjs +1 -1
- package/src/hive.config.lib.mjs +2 -1
- package/src/hive.mjs +1 -1
- package/src/models/index.mjs +871 -0
- package/src/opencode.lib.mjs +4 -15
- package/src/review.mjs +4 -3
- package/src/solve.config.lib.mjs +8 -19
- package/src/solve.mjs +1 -1
- package/src/task.mjs +4 -3
- package/src/telegram-bot.mjs +2 -2
- package/src/model-info.lib.mjs +0 -360
- package/src/model-mapping.lib.mjs +0 -176
- package/src/model-validation.lib.mjs +0 -427
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Unified model mapping module
|
|
5
|
-
* Provides a single source of truth for model name mapping across all tools
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
// Claude models (Anthropic API)
|
|
9
|
-
// Updated for Opus 4.5/4.6 and Sonnet 4.6 support (Issue #1221, Issue #1238, Issue #1329, Issue #1433)
|
|
10
|
-
export const claudeModels = {
|
|
11
|
-
sonnet: 'claude-sonnet-4-6', // Sonnet 4.6 (default, Issue #1329)
|
|
12
|
-
opus: 'claude-opus-4-6', // Opus 4.6 (Issue #1433)
|
|
13
|
-
haiku: 'claude-haiku-4-5-20251001', // Haiku 4.5
|
|
14
|
-
'haiku-3-5': 'claude-3-5-haiku-20241022', // Haiku 3.5
|
|
15
|
-
'haiku-3': 'claude-3-haiku-20240307', // Haiku 3
|
|
16
|
-
// Shorter version aliases (Issue #1221, Issue #1329 - PR comment feedback)
|
|
17
|
-
'sonnet-4-6': 'claude-sonnet-4-6', // Sonnet 4.6 short alias (Issue #1329)
|
|
18
|
-
'opus-4-6': 'claude-opus-4-6', // Opus 4.6 short alias
|
|
19
|
-
'opus-4-5': 'claude-opus-4-5-20251101', // Opus 4.5 short alias
|
|
20
|
-
'sonnet-4-5': 'claude-sonnet-4-5-20250929', // Sonnet 4.5 short alias (backward compatibility)
|
|
21
|
-
'haiku-4-5': 'claude-haiku-4-5-20251001', // Haiku 4.5 short alias
|
|
22
|
-
// Version aliases for backward compatibility (Issue #1221, Issue #1329)
|
|
23
|
-
'claude-sonnet-4-6': 'claude-sonnet-4-6', // Sonnet 4.6 (Issue #1329)
|
|
24
|
-
'claude-opus-4-6': 'claude-opus-4-6', // Opus 4.6
|
|
25
|
-
'claude-opus-4-5': 'claude-opus-4-5-20251101', // Opus 4.5
|
|
26
|
-
'claude-sonnet-4-5': 'claude-sonnet-4-5-20250929', // Sonnet 4.5 (backward compatibility)
|
|
27
|
-
'claude-haiku-4-5': 'claude-haiku-4-5-20251001', // Haiku 4.5
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
// Agent models (OpenCode API and Kilo Gateway via agent CLI)
|
|
31
|
-
// Issue #1300: Updated free models to match agent PR #191
|
|
32
|
-
export const agentModels = {
|
|
33
|
-
// OpenCode Zen free models (current)
|
|
34
|
-
grok: 'opencode/grok-code',
|
|
35
|
-
'grok-code': 'opencode/grok-code',
|
|
36
|
-
'grok-code-fast-1': 'opencode/grok-code',
|
|
37
|
-
'big-pickle': 'opencode/big-pickle',
|
|
38
|
-
'gpt-5-nano': 'opencode/gpt-5-nano',
|
|
39
|
-
'minimax-m2.5-free': 'opencode/minimax-m2.5-free', // New: upgraded from M2.1 (Issue #1391: now default)
|
|
40
|
-
// Kilo Gateway free models (Issue #1282, updated in #1300)
|
|
41
|
-
// Short names for Kilo-exclusive models (Issue #1300)
|
|
42
|
-
'glm-5-free': 'kilo/glm-5-free', // Kilo-exclusive
|
|
43
|
-
'glm-4.5-air-free': 'kilo/glm-4.5-air-free', // Kilo-exclusive: agent-centric model
|
|
44
|
-
'deepseek-r1-free': 'kilo/deepseek-r1-free', // Kilo-exclusive: reasoning model
|
|
45
|
-
'giga-potato-free': 'kilo/giga-potato-free', // Kilo-exclusive
|
|
46
|
-
'trinity-large-preview': 'kilo/trinity-large-preview', // Kilo-exclusive
|
|
47
|
-
// Full names with kilo/ prefix
|
|
48
|
-
'kilo/glm-5-free': 'kilo/glm-5-free',
|
|
49
|
-
'kilo/glm-4.5-air-free': 'kilo/glm-4.5-air-free',
|
|
50
|
-
'kilo/minimax-m2.5-free': 'kilo/minimax-m2.5-free', // Also on OpenCode Zen
|
|
51
|
-
'kilo/deepseek-r1-free': 'kilo/deepseek-r1-free',
|
|
52
|
-
'kilo/giga-potato-free': 'kilo/giga-potato-free',
|
|
53
|
-
'kilo/trinity-large-preview': 'kilo/trinity-large-preview',
|
|
54
|
-
// Deprecated free models (kept for backward compatibility)
|
|
55
|
-
'kimi-k2.5-free': 'opencode/kimi-k2.5-free', // Deprecated: not supported (Issue #1391)
|
|
56
|
-
'glm-4.7-free': 'opencode/glm-4.7-free', // Deprecated: no longer free
|
|
57
|
-
'minimax-m2.1-free': 'opencode/minimax-m2.1-free', // Deprecated: replaced by m2.5
|
|
58
|
-
'kilo/glm-4.7-free': 'kilo/glm-4.7-free', // Deprecated: replaced by glm-4.5-air-free
|
|
59
|
-
'kilo/kimi-k2.5-free': 'kilo/kimi-k2.5-free', // Deprecated: not recommended
|
|
60
|
-
'kilo/minimax-m2.1-free': 'kilo/minimax-m2.1-free', // Deprecated: replaced by m2.5
|
|
61
|
-
// Premium models
|
|
62
|
-
sonnet: 'anthropic/claude-3-5-sonnet',
|
|
63
|
-
haiku: 'anthropic/claude-3-5-haiku',
|
|
64
|
-
opus: 'anthropic/claude-3-opus',
|
|
65
|
-
'gemini-3-pro': 'google/gemini-3-pro',
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
// OpenCode models (OpenCode API)
|
|
69
|
-
export const opencodeModels = {
|
|
70
|
-
gpt4: 'openai/gpt-4',
|
|
71
|
-
gpt4o: 'openai/gpt-4o',
|
|
72
|
-
claude: 'anthropic/claude-3-5-sonnet',
|
|
73
|
-
sonnet: 'anthropic/claude-3-5-sonnet',
|
|
74
|
-
opus: 'anthropic/claude-3-opus',
|
|
75
|
-
gemini: 'google/gemini-pro',
|
|
76
|
-
grok: 'opencode/grok-code',
|
|
77
|
-
'grok-code': 'opencode/grok-code',
|
|
78
|
-
'grok-code-fast-1': 'opencode/grok-code',
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
// Codex models (OpenAI API)
|
|
82
|
-
export const codexModels = {
|
|
83
|
-
gpt5: 'gpt-5',
|
|
84
|
-
'gpt5-codex': 'gpt-5-codex',
|
|
85
|
-
o3: 'o3',
|
|
86
|
-
'o3-mini': 'o3-mini',
|
|
87
|
-
gpt4: 'gpt-4',
|
|
88
|
-
gpt4o: 'gpt-4o',
|
|
89
|
-
claude: 'claude-3-5-sonnet',
|
|
90
|
-
sonnet: 'claude-3-5-sonnet',
|
|
91
|
-
opus: 'claude-3-opus',
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Map model name to full model ID for a specific tool
|
|
96
|
-
* @param {string} tool - The tool name (claude, agent, opencode, codex)
|
|
97
|
-
* @param {string} model - The model name or alias
|
|
98
|
-
* @returns {string} The full model ID
|
|
99
|
-
*/
|
|
100
|
-
export const mapModelForTool = (tool, model) => {
|
|
101
|
-
switch (tool) {
|
|
102
|
-
case 'claude':
|
|
103
|
-
return claudeModels[model] || model;
|
|
104
|
-
case 'agent':
|
|
105
|
-
return agentModels[model] || model;
|
|
106
|
-
case 'opencode':
|
|
107
|
-
return opencodeModels[model] || model;
|
|
108
|
-
case 'codex':
|
|
109
|
-
return codexModels[model] || model;
|
|
110
|
-
default:
|
|
111
|
-
return model;
|
|
112
|
-
}
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Validate if a model is compatible with a tool
|
|
117
|
-
* @param {string} tool - The tool name (claude, agent, opencode, codex)
|
|
118
|
-
* @param {string} model - The model name or alias
|
|
119
|
-
* @returns {boolean} True if the model is compatible with the tool
|
|
120
|
-
*/
|
|
121
|
-
export const isModelCompatibleWithTool = (tool, model) => {
|
|
122
|
-
const mappedModel = mapModelForTool(tool, model);
|
|
123
|
-
|
|
124
|
-
switch (tool) {
|
|
125
|
-
case 'claude':
|
|
126
|
-
// Claude only accepts models in the claude- namespace
|
|
127
|
-
return mappedModel.startsWith('claude-');
|
|
128
|
-
case 'agent':
|
|
129
|
-
// Agent accepts any model with provider prefix (opencode/, anthropic/, etc.)
|
|
130
|
-
// or models in the agentModels list
|
|
131
|
-
return mappedModel.includes('/') || Object.keys(agentModels).includes(model);
|
|
132
|
-
case 'opencode':
|
|
133
|
-
// OpenCode accepts models with provider prefix
|
|
134
|
-
return mappedModel.includes('/') || Object.keys(opencodeModels).includes(model);
|
|
135
|
-
case 'codex':
|
|
136
|
-
// Codex accepts OpenAI and some Claude models
|
|
137
|
-
return Object.keys(codexModels).includes(model) || mappedModel.startsWith('gpt-') || mappedModel.startsWith('o3') || mappedModel.startsWith('claude-');
|
|
138
|
-
default:
|
|
139
|
-
return true;
|
|
140
|
-
}
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Get a list of valid model names for a tool
|
|
145
|
-
* @param {string} tool - The tool name
|
|
146
|
-
* @returns {string[]} Array of valid model names
|
|
147
|
-
*/
|
|
148
|
-
export const getValidModelsForTool = tool => {
|
|
149
|
-
switch (tool) {
|
|
150
|
-
case 'claude':
|
|
151
|
-
return Object.keys(claudeModels);
|
|
152
|
-
case 'agent':
|
|
153
|
-
return Object.keys(agentModels);
|
|
154
|
-
case 'opencode':
|
|
155
|
-
return Object.keys(opencodeModels);
|
|
156
|
-
case 'codex':
|
|
157
|
-
return Object.keys(codexModels);
|
|
158
|
-
default:
|
|
159
|
-
return [];
|
|
160
|
-
}
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Validate tool-model compatibility and throw descriptive error if invalid
|
|
165
|
-
* @param {string} tool - The tool name
|
|
166
|
-
* @param {string} model - The model name
|
|
167
|
-
* @throws {Error} If the model is not compatible with the tool
|
|
168
|
-
*/
|
|
169
|
-
export const validateToolModelCompatibility = (tool, model) => {
|
|
170
|
-
if (!isModelCompatibleWithTool(tool, model)) {
|
|
171
|
-
const validModels = getValidModelsForTool(tool);
|
|
172
|
-
const mappedModel = mapModelForTool(tool, model);
|
|
173
|
-
|
|
174
|
-
throw new Error(`Model '${model}' (mapped to '${mappedModel}') is not compatible with --tool ${tool}.\n` + `Valid models for ${tool}: ${validModels.join(', ')}\n` + 'Hint: Different tools use different model APIs and naming conventions.');
|
|
175
|
-
}
|
|
176
|
-
};
|
|
@@ -1,427 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Model validation library for hive-mind
|
|
3
|
-
// Provides model name validation with exact matching and fuzzy suggestions
|
|
4
|
-
|
|
5
|
-
// Check if use is already defined (when imported from solve.mjs)
|
|
6
|
-
// If not, fetch it (when running standalone)
|
|
7
|
-
if (typeof globalThis.use === 'undefined') {
|
|
8
|
-
globalThis.use = (await eval(await (await fetch('https://unpkg.com/use-m/use.js')).text())).use;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
import { log } from './lib.mjs';
|
|
12
|
-
|
|
13
|
-
// Available models for each tool
|
|
14
|
-
// These are the "known good" model names that we accept
|
|
15
|
-
export const CLAUDE_MODELS = {
|
|
16
|
-
// Short aliases (single word)
|
|
17
|
-
sonnet: 'claude-sonnet-4-6', // Sonnet 4.6 (default, Issue #1329)
|
|
18
|
-
opus: 'claude-opus-4-6', // Opus 4.6 (Issue #1433)
|
|
19
|
-
haiku: 'claude-haiku-4-5-20251001',
|
|
20
|
-
'haiku-3-5': 'claude-3-5-haiku-20241022',
|
|
21
|
-
'haiku-3': 'claude-3-haiku-20240307',
|
|
22
|
-
// Shorter version aliases (Issue #1221, Issue #1329 - PR comment feedback)
|
|
23
|
-
'sonnet-4-6': 'claude-sonnet-4-6', // Sonnet 4.6 short alias (Issue #1329)
|
|
24
|
-
'opus-4-6': 'claude-opus-4-6', // Opus 4.6 short alias
|
|
25
|
-
'opus-4-5': 'claude-opus-4-5-20251101', // Opus 4.5 short alias
|
|
26
|
-
'sonnet-4-5': 'claude-sonnet-4-5-20250929', // Sonnet 4.5 short alias (backward compatibility)
|
|
27
|
-
'haiku-4-5': 'claude-haiku-4-5-20251001', // Haiku 4.5 short alias
|
|
28
|
-
// Sonnet version aliases (Issue #1329)
|
|
29
|
-
'claude-sonnet-4-6': 'claude-sonnet-4-6', // Sonnet 4.6
|
|
30
|
-
'claude-sonnet-4-5': 'claude-sonnet-4-5-20250929', // Sonnet 4.5 (backward compatibility)
|
|
31
|
-
// Opus version aliases (Issue #1221)
|
|
32
|
-
'claude-opus-4-6': 'claude-opus-4-6', // Opus 4.6
|
|
33
|
-
'claude-opus-4-5': 'claude-opus-4-5-20251101', // Opus 4.5
|
|
34
|
-
// Haiku version aliases
|
|
35
|
-
'claude-haiku-4-5': 'claude-haiku-4-5-20251001',
|
|
36
|
-
// Full model IDs (also valid inputs)
|
|
37
|
-
'claude-sonnet-4-5-20250929': 'claude-sonnet-4-5-20250929',
|
|
38
|
-
'claude-opus-4-5-20251101': 'claude-opus-4-5-20251101',
|
|
39
|
-
'claude-haiku-4-5-20251001': 'claude-haiku-4-5-20251001',
|
|
40
|
-
'claude-3-5-haiku-20241022': 'claude-3-5-haiku-20241022',
|
|
41
|
-
'claude-3-haiku-20240307': 'claude-3-haiku-20240307',
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
// Models that support 1M token context window via [1m] suffix (Issue #1221, Issue #1238, Issue #1329)
|
|
45
|
-
// See: https://code.claude.com/docs/en/model-config
|
|
46
|
-
export const MODELS_SUPPORTING_1M_CONTEXT = [
|
|
47
|
-
'claude-opus-4-6',
|
|
48
|
-
'claude-opus-4-5-20251101',
|
|
49
|
-
'claude-sonnet-4-6', // Sonnet 4.6 (Issue #1329)
|
|
50
|
-
'claude-sonnet-4-5-20250929',
|
|
51
|
-
'claude-sonnet-4-5',
|
|
52
|
-
'sonnet', // Now maps to Sonnet 4.6 (Issue #1329)
|
|
53
|
-
'sonnet-4-6', // Short alias (Issue #1329)
|
|
54
|
-
'opus',
|
|
55
|
-
'opus-4-6', // Short alias (Issue #1221 - PR comment feedback)
|
|
56
|
-
'opus-4-5', // Short alias (Issue #1238)
|
|
57
|
-
'sonnet-4-5', // Short alias (Issue #1221 - PR comment feedback)
|
|
58
|
-
];
|
|
59
|
-
|
|
60
|
-
export const OPENCODE_MODELS = {
|
|
61
|
-
gpt4: 'openai/gpt-4',
|
|
62
|
-
gpt4o: 'openai/gpt-4o',
|
|
63
|
-
claude: 'anthropic/claude-3-5-sonnet',
|
|
64
|
-
sonnet: 'anthropic/claude-3-5-sonnet',
|
|
65
|
-
opus: 'anthropic/claude-3-opus',
|
|
66
|
-
gemini: 'google/gemini-pro',
|
|
67
|
-
grok: 'opencode/grok-code',
|
|
68
|
-
'grok-code': 'opencode/grok-code',
|
|
69
|
-
'grok-code-fast-1': 'opencode/grok-code',
|
|
70
|
-
// Full model IDs
|
|
71
|
-
'openai/gpt-4': 'openai/gpt-4',
|
|
72
|
-
'openai/gpt-4o': 'openai/gpt-4o',
|
|
73
|
-
'anthropic/claude-3-5-sonnet': 'anthropic/claude-3-5-sonnet',
|
|
74
|
-
'anthropic/claude-3-opus': 'anthropic/claude-3-opus',
|
|
75
|
-
'google/gemini-pro': 'google/gemini-pro',
|
|
76
|
-
'opencode/grok-code': 'opencode/grok-code',
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
export const CODEX_MODELS = {
|
|
80
|
-
gpt5: 'gpt-5',
|
|
81
|
-
'gpt-5': 'gpt-5',
|
|
82
|
-
'gpt5-codex': 'gpt-5-codex',
|
|
83
|
-
'gpt-5-codex': 'gpt-5-codex',
|
|
84
|
-
o3: 'o3',
|
|
85
|
-
'o3-mini': 'o3-mini',
|
|
86
|
-
gpt4: 'gpt-4',
|
|
87
|
-
gpt4o: 'gpt-4o',
|
|
88
|
-
claude: 'claude-3-5-sonnet',
|
|
89
|
-
sonnet: 'claude-3-5-sonnet',
|
|
90
|
-
opus: 'claude-3-opus',
|
|
91
|
-
// Full model IDs
|
|
92
|
-
'gpt-4': 'gpt-4',
|
|
93
|
-
'gpt-4o': 'gpt-4o',
|
|
94
|
-
'claude-3-5-sonnet': 'claude-3-5-sonnet',
|
|
95
|
-
'claude-3-opus': 'claude-3-opus',
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
export const AGENT_MODELS = {
|
|
99
|
-
// Free models (via OpenCode Zen)
|
|
100
|
-
// Issue #1185: Model IDs must use opencode/ prefix for OpenCode Zen models
|
|
101
|
-
// Issue #1300: Updated free models - minimax-m2.5-free replaces m2.1, glm-4.7-free removed
|
|
102
|
-
grok: 'opencode/grok-code',
|
|
103
|
-
'grok-code': 'opencode/grok-code',
|
|
104
|
-
'grok-code-fast-1': 'opencode/grok-code',
|
|
105
|
-
'big-pickle': 'opencode/big-pickle',
|
|
106
|
-
'gpt-5-nano': 'opencode/gpt-5-nano',
|
|
107
|
-
'minimax-m2.5-free': 'opencode/minimax-m2.5-free', // Upgraded from M2.1 (Issue #1300); default as of Issue #1391
|
|
108
|
-
// Free models (via Kilo Gateway)
|
|
109
|
-
// Issue #1282: Kilo provider adds access to 500+ models including free tier
|
|
110
|
-
// Issue #1300: Updated Kilo free models with new offerings
|
|
111
|
-
// See: https://kilo.ai/docs/advanced-usage/free-and-budget-models
|
|
112
|
-
// Short names for Kilo-exclusive models (Issue #1300)
|
|
113
|
-
'glm-5-free': 'kilo/glm-5-free', // Kilo-exclusive: Z.AI flagship model
|
|
114
|
-
'glm-4.5-air-free': 'kilo/glm-4.5-air-free', // Kilo-exclusive: Z.AI agent-centric model
|
|
115
|
-
'deepseek-r1-free': 'kilo/deepseek-r1-free', // Kilo-exclusive: DeepSeek reasoning model
|
|
116
|
-
'giga-potato-free': 'kilo/giga-potato-free', // Kilo-exclusive: Evaluation model
|
|
117
|
-
'trinity-large-preview': 'kilo/trinity-large-preview', // Kilo-exclusive: Arcee AI preview
|
|
118
|
-
// Full names with kilo/ prefix
|
|
119
|
-
'kilo/glm-5-free': 'kilo/glm-5-free',
|
|
120
|
-
'kilo/glm-4.5-air-free': 'kilo/glm-4.5-air-free',
|
|
121
|
-
'kilo/minimax-m2.5-free': 'kilo/minimax-m2.5-free', // Also on OpenCode Zen
|
|
122
|
-
'kilo/deepseek-r1-free': 'kilo/deepseek-r1-free',
|
|
123
|
-
'kilo/giga-potato-free': 'kilo/giga-potato-free',
|
|
124
|
-
'kilo/trinity-large-preview': 'kilo/trinity-large-preview',
|
|
125
|
-
// Deprecated free models (kept for backward compatibility)
|
|
126
|
-
// These models are no longer the recommended options but may still work
|
|
127
|
-
'kimi-k2.5-free': 'opencode/kimi-k2.5-free', // Deprecated: not supported by OpenCode Zen (Issue #1391)
|
|
128
|
-
'glm-4.7-free': 'opencode/glm-4.7-free', // Deprecated: no longer free on OpenCode Zen
|
|
129
|
-
'minimax-m2.1-free': 'opencode/minimax-m2.1-free', // Deprecated: replaced by m2.5
|
|
130
|
-
'kilo/glm-4.7-free': 'kilo/glm-4.7-free', // Deprecated: replaced by glm-4.5-air-free
|
|
131
|
-
'kilo/kimi-k2.5-free': 'kilo/kimi-k2.5-free', // Deprecated: not recommended
|
|
132
|
-
'kilo/minimax-m2.1-free': 'kilo/minimax-m2.1-free', // Deprecated: replaced by m2.5
|
|
133
|
-
// Premium models (requires OpenCode Zen subscription)
|
|
134
|
-
sonnet: 'anthropic/claude-3-5-sonnet',
|
|
135
|
-
haiku: 'anthropic/claude-3-5-haiku',
|
|
136
|
-
opus: 'anthropic/claude-3-opus',
|
|
137
|
-
'gemini-3-pro': 'google/gemini-3-pro',
|
|
138
|
-
// Full model IDs with provider prefix (OpenCode Zen)
|
|
139
|
-
'opencode/grok-code': 'opencode/grok-code',
|
|
140
|
-
'opencode/big-pickle': 'opencode/big-pickle',
|
|
141
|
-
'opencode/gpt-5-nano': 'opencode/gpt-5-nano',
|
|
142
|
-
'opencode/minimax-m2.5-free': 'opencode/minimax-m2.5-free', // New (Issue #1300)
|
|
143
|
-
// Deprecated OpenCode Zen models (kept for backward compatibility)
|
|
144
|
-
'opencode/kimi-k2.5-free': 'opencode/kimi-k2.5-free', // Deprecated: not supported (Issue #1391)
|
|
145
|
-
'opencode/glm-4.7-free': 'opencode/glm-4.7-free', // Deprecated
|
|
146
|
-
'opencode/minimax-m2.1-free': 'opencode/minimax-m2.1-free', // Deprecated
|
|
147
|
-
// Premium models with provider prefix
|
|
148
|
-
'anthropic/claude-3-5-sonnet': 'anthropic/claude-3-5-sonnet',
|
|
149
|
-
'anthropic/claude-3-5-haiku': 'anthropic/claude-3-5-haiku',
|
|
150
|
-
'anthropic/claude-3-opus': 'anthropic/claude-3-opus',
|
|
151
|
-
'google/gemini-3-pro': 'google/gemini-3-pro',
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Get the model map for a given tool
|
|
156
|
-
* @param {string} tool - The tool name ('claude', 'opencode', 'codex', 'agent')
|
|
157
|
-
* @returns {Object} The model mapping for the tool
|
|
158
|
-
*/
|
|
159
|
-
export const getModelMapForTool = tool => {
|
|
160
|
-
switch (tool) {
|
|
161
|
-
case 'opencode':
|
|
162
|
-
return OPENCODE_MODELS;
|
|
163
|
-
case 'codex':
|
|
164
|
-
return CODEX_MODELS;
|
|
165
|
-
case 'agent':
|
|
166
|
-
return AGENT_MODELS;
|
|
167
|
-
case 'claude':
|
|
168
|
-
default:
|
|
169
|
-
return CLAUDE_MODELS;
|
|
170
|
-
}
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Get the list of available model names for a tool (for display in help/error messages)
|
|
175
|
-
* @param {string} tool - The tool name ('claude', 'opencode', 'codex', 'agent')
|
|
176
|
-
* @returns {string[]} Array of available model short names
|
|
177
|
-
*/
|
|
178
|
-
export const getAvailableModelNames = tool => {
|
|
179
|
-
const modelMap = getModelMapForTool(tool);
|
|
180
|
-
// Get unique short names (aliases) - exclude full model IDs that contain '/' or long claude- prefixed IDs
|
|
181
|
-
const aliases = Object.keys(modelMap).filter(key => {
|
|
182
|
-
// Keep short aliases only - exclude:
|
|
183
|
-
// - Full model IDs with slashes (e.g., 'openai/gpt-4')
|
|
184
|
-
// - Long claude-prefixed model IDs (e.g., 'claude-sonnet-4-5-20250929')
|
|
185
|
-
// - Full gpt- prefixed IDs that are ONLY version numbers (e.g., 'gpt-4', 'gpt-4o', 'gpt-5')
|
|
186
|
-
// But keep descriptive aliases like 'gpt-5-nano', 'gpt-5-codex', 'o3', 'o3-mini', 'gpt5', etc.
|
|
187
|
-
// Issue #1185: Updated regex to not filter out gpt-5-nano (a valid short alias)
|
|
188
|
-
if (key.includes('/')) return false;
|
|
189
|
-
if (key.match(/^claude-.*-\d{8}$/)) return false; // Full claude model IDs with date
|
|
190
|
-
if (key.match(/^gpt-\d+[a-z]?$/)) return false; // Full gpt-N or gpt-No model IDs only (e.g., gpt-4, gpt-4o, gpt-5)
|
|
191
|
-
return true;
|
|
192
|
-
});
|
|
193
|
-
return [...new Set(aliases)];
|
|
194
|
-
};
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Calculate Levenshtein distance between two strings (case-insensitive)
|
|
198
|
-
* @param {string} a - First string
|
|
199
|
-
* @param {string} b - Second string
|
|
200
|
-
* @returns {number} The edit distance between the strings
|
|
201
|
-
*/
|
|
202
|
-
export const levenshteinDistance = (a, b) => {
|
|
203
|
-
const aLower = a.toLowerCase();
|
|
204
|
-
const bLower = b.toLowerCase();
|
|
205
|
-
|
|
206
|
-
if (aLower === bLower) return 0;
|
|
207
|
-
if (aLower.length === 0) return bLower.length;
|
|
208
|
-
if (bLower.length === 0) return aLower.length;
|
|
209
|
-
|
|
210
|
-
const matrix = [];
|
|
211
|
-
|
|
212
|
-
// Initialize first column
|
|
213
|
-
for (let i = 0; i <= bLower.length; i++) {
|
|
214
|
-
matrix[i] = [i];
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// Initialize first row
|
|
218
|
-
for (let j = 0; j <= aLower.length; j++) {
|
|
219
|
-
matrix[0][j] = j;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// Fill in the rest of the matrix
|
|
223
|
-
for (let i = 1; i <= bLower.length; i++) {
|
|
224
|
-
for (let j = 1; j <= aLower.length; j++) {
|
|
225
|
-
if (bLower.charAt(i - 1) === aLower.charAt(j - 1)) {
|
|
226
|
-
matrix[i][j] = matrix[i - 1][j - 1];
|
|
227
|
-
} else {
|
|
228
|
-
matrix[i][j] = Math.min(
|
|
229
|
-
matrix[i - 1][j - 1] + 1, // substitution
|
|
230
|
-
matrix[i][j - 1] + 1, // insertion
|
|
231
|
-
matrix[i - 1][j] + 1 // deletion
|
|
232
|
-
);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
return matrix[bLower.length][aLower.length];
|
|
238
|
-
};
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* Find the closest matching model names using fuzzy matching
|
|
242
|
-
* @param {string} input - The user-provided model name
|
|
243
|
-
* @param {string[]} validModels - Array of valid model names
|
|
244
|
-
* @param {number} maxSuggestions - Maximum number of suggestions to return
|
|
245
|
-
* @param {number} maxDistance - Maximum Levenshtein distance to consider
|
|
246
|
-
* @returns {string[]} Array of suggested model names
|
|
247
|
-
*/
|
|
248
|
-
export const findSimilarModels = (input, validModels, maxSuggestions = 3, maxDistance = 3) => {
|
|
249
|
-
const suggestions = validModels
|
|
250
|
-
.map(model => ({
|
|
251
|
-
model,
|
|
252
|
-
distance: levenshteinDistance(input, model),
|
|
253
|
-
}))
|
|
254
|
-
.filter(({ distance }) => distance <= maxDistance)
|
|
255
|
-
.sort((a, b) => a.distance - b.distance)
|
|
256
|
-
.slice(0, maxSuggestions)
|
|
257
|
-
.map(({ model }) => model);
|
|
258
|
-
|
|
259
|
-
return suggestions;
|
|
260
|
-
};
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* Parse model name to extract base model and optional [1m] suffix
|
|
264
|
-
* @param {string} model - The model name (e.g., "opus[1m]", "claude-opus-4-6[1m]")
|
|
265
|
-
* @returns {{ baseModel: string, has1mSuffix: boolean }}
|
|
266
|
-
*/
|
|
267
|
-
export const parseModelWith1mSuffix = model => {
|
|
268
|
-
if (!model || typeof model !== 'string') {
|
|
269
|
-
return { baseModel: model, has1mSuffix: false };
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// Check for [1m] suffix (case-insensitive)
|
|
273
|
-
const match = model.match(/^(.+?)\[1m\]$/i);
|
|
274
|
-
if (match) {
|
|
275
|
-
return { baseModel: match[1], has1mSuffix: true };
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
return { baseModel: model, has1mSuffix: false };
|
|
279
|
-
};
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* Check if a model supports the [1m] context window
|
|
283
|
-
* @param {string} model - The base model name (without [1m] suffix)
|
|
284
|
-
* @param {string} tool - The tool name
|
|
285
|
-
* @returns {boolean} True if the model supports 1M context
|
|
286
|
-
*/
|
|
287
|
-
export const supports1mContext = (model, tool = 'claude') => {
|
|
288
|
-
if (tool !== 'claude') {
|
|
289
|
-
return false;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
const normalizedModel = model.toLowerCase();
|
|
293
|
-
|
|
294
|
-
// Check if the model or its mapped version supports 1M context
|
|
295
|
-
for (const supportedModel of MODELS_SUPPORTING_1M_CONTEXT) {
|
|
296
|
-
if (supportedModel.toLowerCase() === normalizedModel) {
|
|
297
|
-
return true;
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
// Also check if the mapped model supports 1M context
|
|
302
|
-
const modelMap = getModelMapForTool(tool);
|
|
303
|
-
const matchedKey = Object.keys(modelMap).find(key => key.toLowerCase() === normalizedModel);
|
|
304
|
-
if (matchedKey) {
|
|
305
|
-
const mappedModel = modelMap[matchedKey];
|
|
306
|
-
for (const supportedModel of MODELS_SUPPORTING_1M_CONTEXT) {
|
|
307
|
-
if (supportedModel.toLowerCase() === mappedModel.toLowerCase()) {
|
|
308
|
-
return true;
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
return false;
|
|
314
|
-
};
|
|
315
|
-
|
|
316
|
-
/**
|
|
317
|
-
* Validate a model name against the available models for a tool
|
|
318
|
-
* Supports [1m] suffix for 1 million token context (Issue #1221)
|
|
319
|
-
* @param {string} model - The model name to validate (e.g., "opus", "opus[1m]", "claude-opus-4-6[1m]")
|
|
320
|
-
* @param {string} tool - The tool name ('claude', 'opencode', 'codex')
|
|
321
|
-
* @returns {{ valid: boolean, message?: string, suggestions?: string[], mappedModel?: string, has1mSuffix?: boolean }}
|
|
322
|
-
*/
|
|
323
|
-
export const validateModelName = (model, tool = 'claude') => {
|
|
324
|
-
if (!model || typeof model !== 'string') {
|
|
325
|
-
return {
|
|
326
|
-
valid: false,
|
|
327
|
-
message: 'Model name is required',
|
|
328
|
-
suggestions: [],
|
|
329
|
-
};
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
// Parse [1m] suffix (Issue #1221)
|
|
333
|
-
const { baseModel, has1mSuffix } = parseModelWith1mSuffix(model);
|
|
334
|
-
|
|
335
|
-
const modelMap = getModelMapForTool(tool);
|
|
336
|
-
const availableNames = Object.keys(modelMap);
|
|
337
|
-
|
|
338
|
-
// Case-insensitive exact match
|
|
339
|
-
const normalizedModel = baseModel.toLowerCase();
|
|
340
|
-
const matchedKey = availableNames.find(key => key.toLowerCase() === normalizedModel);
|
|
341
|
-
|
|
342
|
-
if (matchedKey) {
|
|
343
|
-
const mappedModel = modelMap[matchedKey];
|
|
344
|
-
|
|
345
|
-
// If [1m] suffix is present, validate it's supported
|
|
346
|
-
if (has1mSuffix) {
|
|
347
|
-
if (!supports1mContext(baseModel, tool)) {
|
|
348
|
-
const supportedModels = MODELS_SUPPORTING_1M_CONTEXT.filter(m => !m.includes('-')).join(', ');
|
|
349
|
-
return {
|
|
350
|
-
valid: false,
|
|
351
|
-
message: `Model "${baseModel}" does not support [1m] context window.\n Models supporting 1M context: ${supportedModels}`,
|
|
352
|
-
suggestions: [],
|
|
353
|
-
};
|
|
354
|
-
}
|
|
355
|
-
// Return the mapped model with [1m] suffix appended
|
|
356
|
-
return {
|
|
357
|
-
valid: true,
|
|
358
|
-
mappedModel: `${mappedModel}[1m]`,
|
|
359
|
-
has1mSuffix: true,
|
|
360
|
-
};
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
return {
|
|
364
|
-
valid: true,
|
|
365
|
-
mappedModel,
|
|
366
|
-
has1mSuffix: false,
|
|
367
|
-
};
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
// Model not found - provide helpful error with suggestions
|
|
371
|
-
const shortNames = getAvailableModelNames(tool);
|
|
372
|
-
const suggestions = findSimilarModels(baseModel, shortNames);
|
|
373
|
-
|
|
374
|
-
let message = `Unrecognized model: "${model}"`;
|
|
375
|
-
|
|
376
|
-
if (suggestions.length > 0) {
|
|
377
|
-
message += `\n Did you mean: ${suggestions.map(s => `"${s}"`).join(', ')}?`;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
message += `\n Available models for ${tool}: ${shortNames.join(', ')}`;
|
|
381
|
-
|
|
382
|
-
// Add hint about [1m] suffix if available
|
|
383
|
-
if (tool === 'claude') {
|
|
384
|
-
message += `\n Tip: Use [1m] suffix for 1M context (e.g., opus[1m], sonnet[1m])`;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
return {
|
|
388
|
-
valid: false,
|
|
389
|
-
message,
|
|
390
|
-
suggestions,
|
|
391
|
-
};
|
|
392
|
-
};
|
|
393
|
-
|
|
394
|
-
/**
|
|
395
|
-
* Validate model name and exit with error if invalid
|
|
396
|
-
* This is the main entry point for model validation in solve.mjs, hive.mjs, etc.
|
|
397
|
-
* @param {string} model - The model name to validate
|
|
398
|
-
* @param {string} tool - The tool name ('claude', 'opencode', 'codex')
|
|
399
|
-
* @param {Function} exitFn - Function to call for exiting (default: process.exit)
|
|
400
|
-
* @returns {Promise<boolean>} True if valid, exits process if invalid
|
|
401
|
-
*/
|
|
402
|
-
export const validateAndExitOnInvalidModel = async (model, tool = 'claude', exitFn = null) => {
|
|
403
|
-
const result = validateModelName(model, tool);
|
|
404
|
-
|
|
405
|
-
if (!result.valid) {
|
|
406
|
-
await log(`❌ ${result.message}`, { level: 'error' });
|
|
407
|
-
|
|
408
|
-
if (exitFn) {
|
|
409
|
-
await exitFn(1, 'Invalid model name');
|
|
410
|
-
} else {
|
|
411
|
-
process.exit(1);
|
|
412
|
-
}
|
|
413
|
-
return false;
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
return true;
|
|
417
|
-
};
|
|
418
|
-
|
|
419
|
-
/**
|
|
420
|
-
* Format the list of available models for help text
|
|
421
|
-
* @param {string} tool - The tool name
|
|
422
|
-
* @returns {string} Formatted list of available models
|
|
423
|
-
*/
|
|
424
|
-
export const formatAvailableModelsForHelp = (tool = 'claude') => {
|
|
425
|
-
const names = getAvailableModelNames(tool);
|
|
426
|
-
return names.join(', ');
|
|
427
|
-
};
|