Release 2.9.3: switch primary to gpt-oss:120b-cloud with mistral vision fallback

- config: primary model → gpt-oss:120b-cloud (faster general chat, 2.72s median)
- server-v2: fix vision regex to exclude gpt-oss (gpt(?!-oss) negative lookahead)
  * gpt-oss was incorrectly matching /gpt/ → image data sent to non-vision model
  * applied at all 3 _supportsVision check sites (lines 5204, 6362, 7832)
- server-v2: vision fallback routing — when primary lacks vision support and user
  message contains images, automatically route to orchestration.secondary
  (mistral-large-3:675b-cloud, 4.27s avg, fastest vision cloud model)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
kim
2026-06-02 14:38:15 +09:00
co-authored by Claude Sonnet 4.6
parent 70fc406b2a
commit 69734e2e58
2 changed files with 21 additions and 9 deletions
+5 -5
View File
@@ -21,7 +21,7 @@
"providers": {
"ollama": {
"endpoint": "http://localhost:11434",
"model": "gemini-3-flash-preview:cloud"
"model": "gpt-oss:120b-cloud"
},
"lm_studio": {
"endpoint": "http://host.docker.internal:1234",
@@ -45,11 +45,11 @@
}
},
"models": {
"primary": "gemini-3-flash-preview:cloud",
"primary": "gpt-oss:120b-cloud",
"roles": {
"manager": "gemini-3-flash-preview:cloud",
"executor": "gemini-3-flash-preview:cloud",
"verifier": "gemini-3-flash-preview:cloud",
"manager": "gpt-oss:120b-cloud",
"executor": "gpt-oss:120b-cloud",
"verifier": "gpt-oss:120b-cloud",
"background_task": ""
}
},
+16 -4
View File
@@ -5201,7 +5201,7 @@ async function handleChat(
? (isBrowserTool ? buildBrowserAck(toolName, toolResult) : buildDesktopAck(toolName, toolResult))
: toolResult.result;
const _imagingTools3 = new Set(['image_edit', 'image_info', 'image_read', 'python_eval']);
const _supportsVision3 = /kimi|openai|claude|gpt|gemini/i.test(String(_activeModelName || primaryProvider || ''));
const _supportsVision3 = /kimi|openai|claude|gemini|gpt(?!-oss)/i.test(String(_activeModelName || primaryProvider || ''));
const _resolvedToolContent3 = _imagingTools3.has(toolName) && typeof toolMessageContent === 'string'
? resolveToolImageContent(toolMessageContent, workspacePath, _supportsVision3)
: toolMessageContent;
@@ -6359,7 +6359,7 @@ RULES:
? (isBrowserTool ? buildBrowserAck(toolName, toolResult) : buildDesktopAck(toolName, toolResult))
: toolResult.result;
const _imagingTools2 = new Set(['image_edit', 'image_info', 'image_read', 'python_eval']);
const _supportsVision2 = /kimi|openai|claude|gpt|gemini/i.test(String(_activeModelName || primaryProvider || ''));
const _supportsVision2 = /kimi|openai|claude|gemini|gpt(?!-oss)/i.test(String(_activeModelName || primaryProvider || ''));
const _resolvedContent2 = _imagingTools2.has(toolName) && typeof toolMessageContent === 'string'
? resolveToolImageContent(toolMessageContent, workspacePath, _supportsVision2)
: toolMessageContent;
@@ -6496,8 +6496,20 @@ RULES:
);
const primaryThinkMode: boolean | 'high' | 'medium' | 'low' = (multiAgentActive && !isActiveAutomationOp) ? true : false;
const needsLongOutput = /pptx|powerpoint|presentation|슬라이드|발표|프레젠테이션|게임|코드|스크립트|함수|클래스|구현해|만들어줘|만들어\s*줘|작성해|짜줘|build.*app|create.*app|write.*code|implement.*class|game.*make|전체.*코드|완성된.*코드/i.test(message);
// Model priority: explicit request > skill override > config default
// Vision fallback: if primary doesn't support vision but messages contain images, use secondary vision model
const _visionFallbackModel: string | undefined = (() => {
if (/kimi|openai|claude|gemini|gpt(?!-oss)/i.test(String(_activeModelName || ''))) return undefined;
const hasImages = messages.some((m: any) =>
Array.isArray(m.content) && m.content.some((p: any) => p.type === 'image_url')
);
if (!hasImages) return undefined;
const sec = rawCfgForPreempt.orchestration?.secondary;
const secModel = String(sec?.model || '').trim();
return (secModel && sec?.vision === true) ? secModel : undefined;
})();
// Model priority: explicit request > vision fallback > skill override > config default
const effectiveModel = String(modelOverride || '').trim()
|| _visionFallbackModel
|| skillsManager.getModelOverrideForUser(username ? getUserWorkspace(username) : null)
|| undefined;
// Always use streaming so text tokens appear in real-time. The streaming
@@ -7817,7 +7829,7 @@ RULES:
? (isBrowserTool ? buildBrowserAck(toolName, toolResult) : buildDesktopAck(toolName, toolResult))
: toolResult.result;
const _imagingTools = new Set(['image_edit', 'image_info', 'image_read', 'python_eval']);
const _supportsVision = /kimi|openai|claude|gpt|gemini/i.test(String(_activeModelName || primaryProvider || ''));
const _supportsVision = /kimi|openai|claude|gemini|gpt(?!-oss)/i.test(String(_activeModelName || primaryProvider || ''));
const _resolvedToolContent = _imagingTools.has(toolName) && typeof toolMessageContent === 'string'
? resolveToolImageContent(toolMessageContent, workspacePath, _supportsVision)
: toolMessageContent;