@link-assistant/hive-mind 1.15.2 → 1.16.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 +12 -0
- package/package.json +1 -1
- package/src/claude.lib.mjs +23 -19
- package/src/config.lib.mjs +53 -3
- package/src/model-mapping.lib.mjs +12 -1
- package/src/model-validation.lib.mjs +115 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 1.16.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 5f78253: Add Claude Opus 4.6 model support with [1m] suffix
|
|
8
|
+
- `opus` alias now defaults to `claude-opus-4-6` (latest and most capable Opus model)
|
|
9
|
+
- Added shorter version aliases: `opus-4-6`, `opus-4-5`, `sonnet-4-5`, `haiku-4-5`
|
|
10
|
+
- Added `claude-haiku-4-5` alias for consistency
|
|
11
|
+
- `[1m]` suffix enables 1 million token context window for supported models
|
|
12
|
+
- Opus 4.6 gets 128K max output tokens and 64K thinking budget
|
|
13
|
+
- Backward compatibility: `claude-opus-4-5` maps to `claude-opus-4-5-20251101`
|
|
14
|
+
|
|
3
15
|
## 1.15.2
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/package.json
CHANGED
package/src/claude.lib.mjs
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// Claude CLI-related utility functions
|
|
3
|
-
// If not, fetch it (when running standalone)
|
|
2
|
+
// Claude CLI-related utility functions. Fetch use-m if not available.
|
|
4
3
|
if (typeof globalThis.use === 'undefined') {
|
|
5
4
|
globalThis.use = (await eval(await (await fetch('https://unpkg.com/use-m/use.js')).text())).use;
|
|
6
5
|
}
|
|
@@ -10,14 +9,14 @@ const path = (await use('path')).default;
|
|
|
10
9
|
// Import log from general lib
|
|
11
10
|
import { log } from './lib.mjs';
|
|
12
11
|
import { reportError } from './sentry.lib.mjs';
|
|
13
|
-
import { timeouts, retryLimits, claudeCode, getClaudeEnv, getThinkingLevelToTokens, getTokensToThinkingLevel, supportsThinkingBudget, DEFAULT_MAX_THINKING_BUDGET } from './config.lib.mjs';
|
|
12
|
+
import { timeouts, retryLimits, claudeCode, getClaudeEnv, getThinkingLevelToTokens, getTokensToThinkingLevel, supportsThinkingBudget, DEFAULT_MAX_THINKING_BUDGET, getMaxOutputTokensForModel } from './config.lib.mjs';
|
|
14
13
|
import { detectUsageLimit, formatUsageLimitMessage } from './usage-limit.lib.mjs';
|
|
15
14
|
import { createInteractiveHandler } from './interactive-mode.lib.mjs';
|
|
16
15
|
import { displayBudgetStats } from './claude.budget-stats.lib.mjs';
|
|
17
|
-
// Import Claude command builder for generating resume commands
|
|
18
16
|
import { buildClaudeResumeCommand } from './claude.command-builder.lib.mjs';
|
|
19
|
-
|
|
20
|
-
import {
|
|
17
|
+
import { handleClaudeRuntimeSwitch } from './claude.runtime-switch.lib.mjs'; // see issue #1141
|
|
18
|
+
import { CLAUDE_MODELS as availableModels } from './model-validation.lib.mjs'; // Issue #1221
|
|
19
|
+
export { availableModels }; // Re-export for backward compatibility
|
|
21
20
|
|
|
22
21
|
// Helper to display resume command at end of session
|
|
23
22
|
const showResumeCommand = async (sessionId, tempDir, claudePath, model, log) => {
|
|
@@ -36,16 +35,21 @@ export const formatNumber = num => {
|
|
|
36
35
|
const formattedInteger = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, ' ');
|
|
37
36
|
return decimalPart !== undefined ? `${formattedInteger}.${decimalPart}` : formattedInteger;
|
|
38
37
|
};
|
|
39
|
-
// Available model configurations
|
|
40
|
-
export const availableModels = {
|
|
41
|
-
sonnet: 'claude-sonnet-4-5-20250929', // Sonnet 4.5
|
|
42
|
-
opus: 'claude-opus-4-5-20251101', // Opus 4.5
|
|
43
|
-
haiku: 'claude-haiku-4-5-20251001', // Haiku 4.5
|
|
44
|
-
'haiku-3-5': 'claude-3-5-haiku-20241022', // Haiku 3.5
|
|
45
|
-
'haiku-3': 'claude-3-haiku-20240307', // Haiku 3
|
|
46
|
-
};
|
|
47
38
|
// Model mapping to translate aliases to full model IDs
|
|
39
|
+
// Supports [1m] suffix for 1 million token context (Issue #1221)
|
|
48
40
|
export const mapModelToId = model => {
|
|
41
|
+
if (!model || typeof model !== 'string') {
|
|
42
|
+
return model;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Check for [1m] suffix (case-insensitive)
|
|
46
|
+
const match = model.match(/^(.+?)\[1m\]$/i);
|
|
47
|
+
if (match) {
|
|
48
|
+
const baseModel = match[1];
|
|
49
|
+
const mappedBase = availableModels[baseModel] || baseModel;
|
|
50
|
+
return `${mappedBase}[1m]`;
|
|
51
|
+
}
|
|
52
|
+
|
|
49
53
|
return availableModels[model] || model;
|
|
50
54
|
};
|
|
51
55
|
// Function to validate Claude CLI connection with retry logic
|
|
@@ -218,9 +222,7 @@ export const validateClaudeConnection = async (model = 'haiku-3') => {
|
|
|
218
222
|
// Start the validation with retry logic
|
|
219
223
|
return await attemptValidation();
|
|
220
224
|
};
|
|
221
|
-
|
|
222
|
-
// Re-export it for backwards compatibility
|
|
223
|
-
export { handleClaudeRuntimeSwitch };
|
|
225
|
+
export { handleClaudeRuntimeSwitch }; // Re-export from ./claude.runtime-switch.lib.mjs
|
|
224
226
|
|
|
225
227
|
// Store Claude Code version globally (set during validation)
|
|
226
228
|
let detectedClaudeVersion = null;
|
|
@@ -913,8 +915,10 @@ export const executeClaudeCommand = async params => {
|
|
|
913
915
|
|
|
914
916
|
// Set CLAUDE_CODE_MAX_OUTPUT_TOKENS (see issue #1076), MAX_THINKING_TOKENS (see issue #1146),
|
|
915
917
|
// and MCP timeout configurations (see issue #1066)
|
|
916
|
-
|
|
917
|
-
|
|
918
|
+
// Pass model for model-specific max output tokens (Issue #1221)
|
|
919
|
+
const claudeEnv = getClaudeEnv({ thinkingBudget: resolvedThinkingBudget, model: mappedModel });
|
|
920
|
+
const modelMaxOutputTokens = getMaxOutputTokensForModel(mappedModel);
|
|
921
|
+
if (argv.verbose) await log(`📊 CLAUDE_CODE_MAX_OUTPUT_TOKENS: ${modelMaxOutputTokens}`, { verbose: true });
|
|
918
922
|
if (argv.verbose) await log(`📊 MCP_TIMEOUT: ${claudeCode.mcpTimeout}ms (server startup)`, { verbose: true });
|
|
919
923
|
if (argv.verbose) await log(`📊 MCP_TOOL_TIMEOUT: ${claudeCode.mcpToolTimeout}ms (tool execution)`, { verbose: true });
|
|
920
924
|
if (resolvedThinkingBudget !== undefined) {
|
package/src/config.lib.mjs
CHANGED
|
@@ -91,13 +91,18 @@ export const retryLimits = {
|
|
|
91
91
|
|
|
92
92
|
// Claude Code CLI configurations
|
|
93
93
|
// See: https://github.com/link-assistant/hive-mind/issues/1076
|
|
94
|
-
// Claude models support
|
|
94
|
+
// Claude models support different max output tokens:
|
|
95
|
+
// - Opus 4.6: 128K tokens (Issue #1221)
|
|
96
|
+
// - Sonnet 4.5, Opus 4.5, Haiku 4.5: 64K tokens
|
|
95
97
|
// Setting a higher limit allows Claude to generate longer responses without hitting the limit
|
|
96
98
|
export const claudeCode = {
|
|
97
99
|
// Maximum output tokens for Claude Code CLI responses
|
|
98
100
|
// Default: 64000 (matches Claude Sonnet/Opus/Haiku 4.5 model capabilities)
|
|
99
101
|
// Set via CLAUDE_CODE_MAX_OUTPUT_TOKENS or HIVE_MIND_CLAUDE_CODE_MAX_OUTPUT_TOKENS
|
|
100
102
|
maxOutputTokens: parseIntWithDefault('CLAUDE_CODE_MAX_OUTPUT_TOKENS', parseIntWithDefault('HIVE_MIND_CLAUDE_CODE_MAX_OUTPUT_TOKENS', 64000)),
|
|
103
|
+
// Maximum output tokens for Opus 4.6 (Issue #1221)
|
|
104
|
+
// See: https://platform.claude.com/docs/en/about-claude/models/overview
|
|
105
|
+
maxOutputTokensOpus46: parseIntWithDefault('CLAUDE_CODE_MAX_OUTPUT_TOKENS_OPUS_46', parseIntWithDefault('HIVE_MIND_CLAUDE_CODE_MAX_OUTPUT_TOKENS_OPUS_46', 128000)),
|
|
101
106
|
// MCP (Model Context Protocol) timeout configurations
|
|
102
107
|
// See: https://github.com/link-assistant/hive-mind/issues/1066
|
|
103
108
|
// See: https://code.claude.com/docs/en/settings#environment-variables
|
|
@@ -114,6 +119,47 @@ export const claudeCode = {
|
|
|
114
119
|
// Can be overridden via --max-thinking-budget option
|
|
115
120
|
export const DEFAULT_MAX_THINKING_BUDGET = 31999;
|
|
116
121
|
|
|
122
|
+
// Default max thinking budget for Opus 4.6 (Issue #1221)
|
|
123
|
+
// Opus 4.6 supports higher thinking budgets due to 128K max output tokens
|
|
124
|
+
// Can be overridden via --max-thinking-budget option or HIVE_MIND_MAX_THINKING_BUDGET_OPUS_46
|
|
125
|
+
export const DEFAULT_MAX_THINKING_BUDGET_OPUS_46 = parseIntWithDefault('HIVE_MIND_MAX_THINKING_BUDGET_OPUS_46', 64000);
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Check if a model is Opus 4.6 or later (Issue #1221)
|
|
129
|
+
* @param {string} model - The model name or ID
|
|
130
|
+
* @returns {boolean} True if the model is Opus 4.6 or later
|
|
131
|
+
*/
|
|
132
|
+
export const isOpus46OrLater = model => {
|
|
133
|
+
if (!model) return false;
|
|
134
|
+
const normalizedModel = model.toLowerCase();
|
|
135
|
+
// Check for opus alias (which maps to 4.6) or explicit opus-4-6
|
|
136
|
+
return normalizedModel === 'opus' || normalizedModel.includes('opus-4-6') || normalizedModel.includes('opus-4-7') || normalizedModel.includes('opus-5');
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Get the max output tokens for a specific model (Issue #1221)
|
|
141
|
+
* @param {string} model - The model name or ID
|
|
142
|
+
* @returns {number} The max output tokens for the model
|
|
143
|
+
*/
|
|
144
|
+
export const getMaxOutputTokensForModel = model => {
|
|
145
|
+
if (isOpus46OrLater(model)) {
|
|
146
|
+
return claudeCode.maxOutputTokensOpus46;
|
|
147
|
+
}
|
|
148
|
+
return claudeCode.maxOutputTokens;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Get the default max thinking budget for a specific model (Issue #1221)
|
|
153
|
+
* @param {string} model - The model name or ID
|
|
154
|
+
* @returns {number} The default max thinking budget for the model
|
|
155
|
+
*/
|
|
156
|
+
export const getDefaultMaxThinkingBudgetForModel = model => {
|
|
157
|
+
if (isOpus46OrLater(model)) {
|
|
158
|
+
return DEFAULT_MAX_THINKING_BUDGET_OPUS_46;
|
|
159
|
+
}
|
|
160
|
+
return DEFAULT_MAX_THINKING_BUDGET;
|
|
161
|
+
};
|
|
162
|
+
|
|
117
163
|
/**
|
|
118
164
|
* Get thinking level token values calculated from max budget
|
|
119
165
|
* Values are evenly distributed: off=0, low=max/4, medium=max/2, high=max*3/4, max=max
|
|
@@ -174,10 +220,14 @@ export const supportsThinkingBudget = (version, minVersion = '2.1.12') => {
|
|
|
174
220
|
// Helper function to get Claude CLI environment with CLAUDE_CODE_MAX_OUTPUT_TOKENS set
|
|
175
221
|
// Optionally sets MAX_THINKING_TOKENS when thinkingBudget is provided (see issue #1146)
|
|
176
222
|
// Also sets MCP_TIMEOUT and MCP_TOOL_TIMEOUT for MCP tool execution (see issue #1066)
|
|
223
|
+
// Supports model-specific max output tokens for Opus 4.6 (Issue #1221)
|
|
177
224
|
export const getClaudeEnv = (options = {}) => {
|
|
225
|
+
// Get max output tokens based on model (Issue #1221)
|
|
226
|
+
const maxOutputTokens = options.model ? getMaxOutputTokensForModel(options.model) : claudeCode.maxOutputTokens;
|
|
227
|
+
|
|
178
228
|
const env = {
|
|
179
229
|
...process.env,
|
|
180
|
-
CLAUDE_CODE_MAX_OUTPUT_TOKENS: String(
|
|
230
|
+
CLAUDE_CODE_MAX_OUTPUT_TOKENS: String(maxOutputTokens),
|
|
181
231
|
// MCP timeout configurations to prevent tool calls from hanging indefinitely
|
|
182
232
|
// See: https://github.com/link-assistant/hive-mind/issues/1066
|
|
183
233
|
// MCP_TIMEOUT: Timeout for MCP server startup
|
|
@@ -187,7 +237,7 @@ export const getClaudeEnv = (options = {}) => {
|
|
|
187
237
|
};
|
|
188
238
|
// Set MAX_THINKING_TOKENS if thinkingBudget is provided
|
|
189
239
|
// This controls Claude Code's extended thinking feature (Claude Code >= 2.1.12)
|
|
190
|
-
// Default is 31999, set to 0 to disable thinking
|
|
240
|
+
// Default is 31999 (or 64000 for Opus 4.6), set to 0 to disable thinking
|
|
191
241
|
if (options.thinkingBudget !== undefined) {
|
|
192
242
|
env.MAX_THINKING_TOKENS = String(options.thinkingBudget);
|
|
193
243
|
}
|
|
@@ -6,12 +6,23 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
// Claude models (Anthropic API)
|
|
9
|
+
// Updated for Opus 4.6 support (Issue #1221)
|
|
9
10
|
export const claudeModels = {
|
|
10
11
|
sonnet: 'claude-sonnet-4-5-20250929', // Sonnet 4.5
|
|
11
|
-
opus: 'claude-opus-4-
|
|
12
|
+
opus: 'claude-opus-4-6', // Opus 4.6 (latest)
|
|
12
13
|
haiku: 'claude-haiku-4-5-20251001', // Haiku 4.5
|
|
13
14
|
'haiku-3-5': 'claude-3-5-haiku-20241022', // Haiku 3.5
|
|
14
15
|
'haiku-3': 'claude-3-haiku-20240307', // Haiku 3
|
|
16
|
+
// Shorter version aliases (Issue #1221 - PR comment feedback)
|
|
17
|
+
'opus-4-6': 'claude-opus-4-6', // Opus 4.6 short alias
|
|
18
|
+
'opus-4-5': 'claude-opus-4-5-20251101', // Opus 4.5 short alias
|
|
19
|
+
'sonnet-4-5': 'claude-sonnet-4-5-20250929', // Sonnet 4.5 short alias
|
|
20
|
+
'haiku-4-5': 'claude-haiku-4-5-20251001', // Haiku 4.5 short alias
|
|
21
|
+
// Version aliases for backward compatibility (Issue #1221)
|
|
22
|
+
'claude-opus-4-6': 'claude-opus-4-6', // Opus 4.6
|
|
23
|
+
'claude-opus-4-5': 'claude-opus-4-5-20251101', // Opus 4.5
|
|
24
|
+
'claude-sonnet-4-5': 'claude-sonnet-4-5-20250929', // Sonnet 4.5
|
|
25
|
+
'claude-haiku-4-5': 'claude-haiku-4-5-20251001', // Haiku 4.5
|
|
15
26
|
};
|
|
16
27
|
|
|
17
28
|
// Agent models (OpenCode API via agent CLI)
|
|
@@ -13,12 +13,24 @@ import { log } from './lib.mjs';
|
|
|
13
13
|
// Available models for each tool
|
|
14
14
|
// These are the "known good" model names that we accept
|
|
15
15
|
export const CLAUDE_MODELS = {
|
|
16
|
-
// Short aliases
|
|
16
|
+
// Short aliases (single word)
|
|
17
17
|
sonnet: 'claude-sonnet-4-5-20250929',
|
|
18
|
-
opus: 'claude-opus-4-
|
|
18
|
+
opus: 'claude-opus-4-6', // Updated to Opus 4.6 (Issue #1221)
|
|
19
19
|
haiku: 'claude-haiku-4-5-20251001',
|
|
20
20
|
'haiku-3-5': 'claude-3-5-haiku-20241022',
|
|
21
21
|
'haiku-3': 'claude-3-haiku-20240307',
|
|
22
|
+
// Shorter version aliases (Issue #1221 - PR comment feedback)
|
|
23
|
+
'opus-4-6': 'claude-opus-4-6', // Opus 4.6 short alias
|
|
24
|
+
'opus-4-5': 'claude-opus-4-5-20251101', // Opus 4.5 short alias
|
|
25
|
+
'sonnet-4-5': 'claude-sonnet-4-5-20250929', // Sonnet 4.5 short alias
|
|
26
|
+
'haiku-4-5': 'claude-haiku-4-5-20251001', // Haiku 4.5 short alias
|
|
27
|
+
// Opus version aliases (Issue #1221)
|
|
28
|
+
'claude-opus-4-6': 'claude-opus-4-6', // Latest Opus
|
|
29
|
+
'claude-opus-4-5': 'claude-opus-4-5-20251101', // Backward compatibility alias
|
|
30
|
+
// Sonnet version aliases
|
|
31
|
+
'claude-sonnet-4-5': 'claude-sonnet-4-5-20250929',
|
|
32
|
+
// Haiku version aliases
|
|
33
|
+
'claude-haiku-4-5': 'claude-haiku-4-5-20251001',
|
|
22
34
|
// Full model IDs (also valid inputs)
|
|
23
35
|
'claude-sonnet-4-5-20250929': 'claude-sonnet-4-5-20250929',
|
|
24
36
|
'claude-opus-4-5-20251101': 'claude-opus-4-5-20251101',
|
|
@@ -27,6 +39,18 @@ export const CLAUDE_MODELS = {
|
|
|
27
39
|
'claude-3-haiku-20240307': 'claude-3-haiku-20240307',
|
|
28
40
|
};
|
|
29
41
|
|
|
42
|
+
// Models that support 1M token context window via [1m] suffix (Issue #1221)
|
|
43
|
+
// See: https://code.claude.com/docs/en/model-config
|
|
44
|
+
export const MODELS_SUPPORTING_1M_CONTEXT = [
|
|
45
|
+
'claude-opus-4-6',
|
|
46
|
+
'claude-sonnet-4-5-20250929',
|
|
47
|
+
'claude-sonnet-4-5',
|
|
48
|
+
'sonnet',
|
|
49
|
+
'opus',
|
|
50
|
+
'opus-4-6', // Short alias (Issue #1221 - PR comment feedback)
|
|
51
|
+
'sonnet-4-5', // Short alias (Issue #1221 - PR comment feedback)
|
|
52
|
+
];
|
|
53
|
+
|
|
30
54
|
export const OPENCODE_MODELS = {
|
|
31
55
|
gpt4: 'openai/gpt-4',
|
|
32
56
|
gpt4o: 'openai/gpt-4o',
|
|
@@ -196,11 +220,66 @@ export const findSimilarModels = (input, validModels, maxSuggestions = 3, maxDis
|
|
|
196
220
|
return suggestions;
|
|
197
221
|
};
|
|
198
222
|
|
|
223
|
+
/**
|
|
224
|
+
* Parse model name to extract base model and optional [1m] suffix
|
|
225
|
+
* @param {string} model - The model name (e.g., "opus[1m]", "claude-opus-4-6[1m]")
|
|
226
|
+
* @returns {{ baseModel: string, has1mSuffix: boolean }}
|
|
227
|
+
*/
|
|
228
|
+
export const parseModelWith1mSuffix = model => {
|
|
229
|
+
if (!model || typeof model !== 'string') {
|
|
230
|
+
return { baseModel: model, has1mSuffix: false };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Check for [1m] suffix (case-insensitive)
|
|
234
|
+
const match = model.match(/^(.+?)\[1m\]$/i);
|
|
235
|
+
if (match) {
|
|
236
|
+
return { baseModel: match[1], has1mSuffix: true };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return { baseModel: model, has1mSuffix: false };
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Check if a model supports the [1m] context window
|
|
244
|
+
* @param {string} model - The base model name (without [1m] suffix)
|
|
245
|
+
* @param {string} tool - The tool name
|
|
246
|
+
* @returns {boolean} True if the model supports 1M context
|
|
247
|
+
*/
|
|
248
|
+
export const supports1mContext = (model, tool = 'claude') => {
|
|
249
|
+
if (tool !== 'claude') {
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const normalizedModel = model.toLowerCase();
|
|
254
|
+
|
|
255
|
+
// Check if the model or its mapped version supports 1M context
|
|
256
|
+
for (const supportedModel of MODELS_SUPPORTING_1M_CONTEXT) {
|
|
257
|
+
if (supportedModel.toLowerCase() === normalizedModel) {
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Also check if the mapped model supports 1M context
|
|
263
|
+
const modelMap = getModelMapForTool(tool);
|
|
264
|
+
const matchedKey = Object.keys(modelMap).find(key => key.toLowerCase() === normalizedModel);
|
|
265
|
+
if (matchedKey) {
|
|
266
|
+
const mappedModel = modelMap[matchedKey];
|
|
267
|
+
for (const supportedModel of MODELS_SUPPORTING_1M_CONTEXT) {
|
|
268
|
+
if (supportedModel.toLowerCase() === mappedModel.toLowerCase()) {
|
|
269
|
+
return true;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return false;
|
|
275
|
+
};
|
|
276
|
+
|
|
199
277
|
/**
|
|
200
278
|
* Validate a model name against the available models for a tool
|
|
201
|
-
*
|
|
279
|
+
* Supports [1m] suffix for 1 million token context (Issue #1221)
|
|
280
|
+
* @param {string} model - The model name to validate (e.g., "opus", "opus[1m]", "claude-opus-4-6[1m]")
|
|
202
281
|
* @param {string} tool - The tool name ('claude', 'opencode', 'codex')
|
|
203
|
-
* @returns {{ valid: boolean, message?: string, suggestions?: string[] }}
|
|
282
|
+
* @returns {{ valid: boolean, message?: string, suggestions?: string[], mappedModel?: string, has1mSuffix?: boolean }}
|
|
204
283
|
*/
|
|
205
284
|
export const validateModelName = (model, tool = 'claude') => {
|
|
206
285
|
if (!model || typeof model !== 'string') {
|
|
@@ -211,23 +290,47 @@ export const validateModelName = (model, tool = 'claude') => {
|
|
|
211
290
|
};
|
|
212
291
|
}
|
|
213
292
|
|
|
293
|
+
// Parse [1m] suffix (Issue #1221)
|
|
294
|
+
const { baseModel, has1mSuffix } = parseModelWith1mSuffix(model);
|
|
295
|
+
|
|
214
296
|
const modelMap = getModelMapForTool(tool);
|
|
215
297
|
const availableNames = Object.keys(modelMap);
|
|
216
298
|
|
|
217
299
|
// Case-insensitive exact match
|
|
218
|
-
const normalizedModel =
|
|
300
|
+
const normalizedModel = baseModel.toLowerCase();
|
|
219
301
|
const matchedKey = availableNames.find(key => key.toLowerCase() === normalizedModel);
|
|
220
302
|
|
|
221
303
|
if (matchedKey) {
|
|
304
|
+
const mappedModel = modelMap[matchedKey];
|
|
305
|
+
|
|
306
|
+
// If [1m] suffix is present, validate it's supported
|
|
307
|
+
if (has1mSuffix) {
|
|
308
|
+
if (!supports1mContext(baseModel, tool)) {
|
|
309
|
+
const supportedModels = MODELS_SUPPORTING_1M_CONTEXT.filter(m => !m.includes('-')).join(', ');
|
|
310
|
+
return {
|
|
311
|
+
valid: false,
|
|
312
|
+
message: `Model "${baseModel}" does not support [1m] context window.\n Models supporting 1M context: ${supportedModels}`,
|
|
313
|
+
suggestions: [],
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
// Return the mapped model with [1m] suffix appended
|
|
317
|
+
return {
|
|
318
|
+
valid: true,
|
|
319
|
+
mappedModel: `${mappedModel}[1m]`,
|
|
320
|
+
has1mSuffix: true,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
222
324
|
return {
|
|
223
325
|
valid: true,
|
|
224
|
-
mappedModel
|
|
326
|
+
mappedModel,
|
|
327
|
+
has1mSuffix: false,
|
|
225
328
|
};
|
|
226
329
|
}
|
|
227
330
|
|
|
228
331
|
// Model not found - provide helpful error with suggestions
|
|
229
332
|
const shortNames = getAvailableModelNames(tool);
|
|
230
|
-
const suggestions = findSimilarModels(
|
|
333
|
+
const suggestions = findSimilarModels(baseModel, shortNames);
|
|
231
334
|
|
|
232
335
|
let message = `Unrecognized model: "${model}"`;
|
|
233
336
|
|
|
@@ -237,6 +340,11 @@ export const validateModelName = (model, tool = 'claude') => {
|
|
|
237
340
|
|
|
238
341
|
message += `\n Available models for ${tool}: ${shortNames.join(', ')}`;
|
|
239
342
|
|
|
343
|
+
// Add hint about [1m] suffix if available
|
|
344
|
+
if (tool === 'claude') {
|
|
345
|
+
message += `\n Tip: Use [1m] suffix for 1M context (e.g., opus[1m], sonnet[1m])`;
|
|
346
|
+
}
|
|
347
|
+
|
|
240
348
|
return {
|
|
241
349
|
valid: false,
|
|
242
350
|
message,
|