From caf4affdfecedeacd9583cdd9da318fc00ac6e72 Mon Sep 17 00:00:00 2001 From: Tong Chen Date: Fri, 1 May 2026 22:05:35 +0800 Subject: [PATCH] fix: cloud chat /chat startup failures (422 + Azure api_version) (#1605) --- backend/app/agent/agent_model.py | 12 +++- backend/app/agent/factory/mcp.py | 7 ++- backend/app/model/model_platform.py | 19 +++++++ .../BottomBox/ChatInputModelDropdown.tsx | 56 +++++++++---------- src/store/authStore.ts | 34 ++++++++++- vite.config.web.ts | 14 +++++ 6 files changed, 110 insertions(+), 32 deletions(-) diff --git a/backend/app/agent/agent_model.py b/backend/app/agent/agent_model.py index b7078a14..77110216 100644 --- a/backend/app/agent/agent_model.py +++ b/backend/app/agent/agent_model.py @@ -24,7 +24,10 @@ from camel.types import ModelPlatformType from app.agent.listen_chat_agent import ListenChatAgent, logger from app.model.chat import AgentModelConfig, Chat -from app.model.model_platform import patch_bedrock_cloud_config +from app.model.model_platform import ( + patch_azure_cloud_config, + patch_bedrock_cloud_config, +) from app.service.task import ActionCreateAgentData, Agents, get_task_lock from app.utils.event_loop_utils import _schedule_async_task @@ -89,6 +92,13 @@ def agent_model( effective_config["api_url"], extra_params = patch_bedrock_cloud_config( effective_config["api_url"], extra_params ) + # Cloud mode: default api_version for Azure-backed models so AzureOpenAI + # construction does not blow up when the frontend omits extra_params. + if ( + effective_config.get("model_platform") == "azure" + and options.is_cloud() + ): + extra_params = patch_azure_cloud_config(extra_params) init_param_keys = { "api_version", "azure_ad_token", diff --git a/backend/app/agent/factory/mcp.py b/backend/app/agent/factory/mcp.py index f31cefd8..c72abdb0 100644 --- a/backend/app/agent/factory/mcp.py +++ b/backend/app/agent/factory/mcp.py @@ -23,7 +23,10 @@ from app.agent.prompt import MCP_SYS_PROMPT from app.agent.toolkit.mcp_search_toolkit import McpSearchToolkit from app.agent.tools import get_mcp_tools from app.model.chat import Chat -from app.model.model_platform import patch_bedrock_cloud_config +from app.model.model_platform import ( + patch_azure_cloud_config, + patch_bedrock_cloud_config, +) from app.service.task import ActionCreateAgentData, Agents, get_task_lock @@ -86,6 +89,8 @@ async def mcp_agent(options: Chat): api_url, extra_params = patch_bedrock_cloud_config( api_url, extra_params ) + if options.model_platform == "azure" and options.is_cloud(): + extra_params = patch_azure_cloud_config(extra_params) # Build model_config_dict with prompt caching model_config_dict = {} diff --git a/backend/app/model/model_platform.py b/backend/app/model/model_platform.py index 94ca0d91..f57fea87 100644 --- a/backend/app/model/model_platform.py +++ b/backend/app/model/model_platform.py @@ -27,6 +27,11 @@ PLATFORM_ALIAS_MAPPING: Final[dict[str, str]] = { # Bedrock Converse requires a region during model initialization. BEDROCK_CONVERSE_REGION: Final[str] = "us-west-2" +# Azure OpenAI requires an api_version. The cloud proxy accepts any modern +# version; this default keeps cloud-mode requests working when the frontend +# does not surface api_version in extra_params. +AZURE_DEFAULT_API_VERSION: Final[str] = "2024-10-21" + def patch_bedrock_cloud_config( api_url: str, extra_params: dict @@ -43,6 +48,20 @@ def patch_bedrock_cloud_config( return api_url, extra_params +def patch_azure_cloud_config(extra_params: dict) -> dict: + """Default Azure `api_version` for cloud mode. + + The cloud proxy fronts Azure OpenAI but the frontend sends an empty + `extra_params` for cloud, leaving `api_version` unset. Camel's + `AzureOpenAIModel` raises if neither the kwarg nor `AZURE_API_VERSION` + env var is provided — inject a sensible default here so cloud-mode + GPT models (gpt-5.4, gpt-5.5, gpt-5-mini, ...) construct cleanly. + """ + extra_params = dict(extra_params) + extra_params.setdefault("api_version", AZURE_DEFAULT_API_VERSION) + return extra_params + + def normalize_model_platform(platform: str) -> str: """Normalize provider aliases to supported model platform names.""" return PLATFORM_ALIAS_MAPPING.get(platform, platform) diff --git a/src/components/ChatBox/BottomBox/ChatInputModelDropdown.tsx b/src/components/ChatBox/BottomBox/ChatInputModelDropdown.tsx index 707393fb..d2dbc3fe 100644 --- a/src/components/ChatBox/BottomBox/ChatInputModelDropdown.tsx +++ b/src/components/ChatBox/BottomBox/ChatInputModelDropdown.tsx @@ -72,18 +72,16 @@ const cloudModelOptions = [ { id: 'gemini-3.1-pro-preview', name: 'Gemini 3.1 Pro Preview' }, { id: 'gemini-3-pro-preview', name: 'Gemini 3 Pro Preview' }, { id: 'gemini-3-flash-preview', name: 'Gemini 3 Flash Preview' }, - { id: 'gpt-4.1-mini', name: 'GPT-4.1 Mini' }, - { id: 'gpt-4.1', name: 'GPT-4.1' }, - { id: 'gpt-5', name: 'GPT-5' }, - { id: 'gpt-5.1', name: 'GPT-5.1' }, - { id: 'gpt-5.2', name: 'GPT-5.2' }, { id: 'gpt-5.4', name: 'GPT-5.4' }, + { id: 'gpt-5.5', name: 'GPT-5.5' }, { id: 'gpt-5-mini', name: 'GPT-5 Mini' }, { id: 'claude-haiku-4-5', name: 'Claude Haiku 4.5' }, { id: 'claude-sonnet-4-5', name: 'Claude Sonnet 4.5' }, { id: 'claude-sonnet-4-6', name: 'Claude Sonnet 4.6' }, { id: 'claude-opus-4-6', name: 'Claude Opus 4.6' }, - { id: 'minimax_m2_5', name: 'Minimax M2.5' }, + { id: 'claude-opus-4-7', name: 'Claude Opus 4.7' }, + { id: 'deepseek-v4-pro', name: 'DeepSeek V4 Pro' }, + { id: 'minimax_m2_7', name: 'Minimax M2.7' }, ] as const; export interface ChatInputModelDropdownProps { @@ -381,9 +379,9 @@ export function ChatInputModelDropdown({ } )} > - + - + {triggerModelName} @@ -407,19 +405,19 @@ export function ChatInputModelDropdown({ className={cn( modelTriggerShellClass, 'min-w-0 cursor-pointer border-0 text-left', - 'font-semibold justify-between transition-colors', + 'justify-between font-semibold transition-colors', 'hover:bg-ds-bg-neutral-subtle-hover active:bg-ds-bg-neutral-subtle-default', - 'focus-visible:ring-ds-border-neutral-strong-default focus-visible:ring-offset-ds-bg-neutral-default-default focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none', + 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ds-border-neutral-strong-default focus-visible:ring-offset-2 focus-visible:ring-offset-ds-bg-neutral-default-default', 'disabled:pointer-events-none disabled:opacity-50' )} > - + - + {triggerModelName} @@ -446,7 +444,7 @@ export function ChatInputModelDropdown({ }} > { activeSubTriggerRef.current = e.currentTarget; }} @@ -454,10 +452,10 @@ export function ChatInputModelDropdown({ - + {t('setting.eigent-cloud')} @@ -492,16 +490,16 @@ export function ChatInputModelDropdown({ }} > { activeSubTriggerRef.current = e.currentTarget; }} > - + {t('setting.custom-model')} @@ -525,7 +523,7 @@ export function ChatInputModelDropdown({ }} className="flex items-center justify-between" > -
+
{modelImage ? (
-
+
{!isConfigured && ( -
+
)} {isPreferred && ( )} {isConfigured && !isPreferred && ( -
+
)}
@@ -569,16 +567,16 @@ export function ChatInputModelDropdown({ }} > { activeSubTriggerRef.current = e.currentTarget; }} > - + {t('setting.local-model')} @@ -602,7 +600,7 @@ export function ChatInputModelDropdown({ }} className="flex items-center justify-between" > -
+
{modelImage ? (
-
+
{!isConfigured && ( -
+
)} {isPreferred && ( )} {isConfigured && !isPreferred && ( -
+
)}
diff --git a/src/store/authStore.ts b/src/store/authStore.ts index a886c34e..93e44caa 100644 --- a/src/store/authStore.ts +++ b/src/store/authStore.ts @@ -136,6 +136,27 @@ const getRandomDefaultModel = (): CloudModelType => { return models[Math.floor(Math.random() * models.length)]; }; +const SUPPORTED_CLOUD_MODEL_TYPES: ReadonlySet = + new Set([ + 'gemini-3.1-pro-preview', + 'gemini-3-pro-preview', + 'gemini-3-flash-preview', + 'claude-haiku-4-5', + 'claude-sonnet-4-5', + 'claude-sonnet-4-6', + 'claude-opus-4-6', + 'claude-opus-4-7', + 'gpt-5.4', + 'gpt-5.5', + 'gpt-5-mini', + 'deepseek-v4-pro', + 'minimax_m2_7', + ]); + +const isSupportedCloudModelType = (value: unknown): value is CloudModelType => + typeof value === 'string' && + SUPPORTED_CLOUD_MODEL_TYPES.has(value as CloudModelType); + // create store const authStore = create()( persist( @@ -306,7 +327,7 @@ const authStore = create()( }), { name: 'auth-storage', - version: 6, + version: 7, migrate: (persistedState, _version) => { const s = persistedState as | { @@ -314,10 +335,19 @@ const authStore = create()( appearanceMode?: AppearanceMode; customThemeCatalog?: Partial; workspaceMainBackground?: string; + cloud_model_type?: unknown; } | undefined; if (!s) return persistedState as typeof persistedState; + // Drop unsupported cloud model ids so stale values like 'gpt-5.2' + // (removed from the chat input dropdown) don't keep submitting an + // empty model_platform and triggering 422 on /chat. + const sanitizedCloudModelType: CloudModelType = + isSupportedCloudModelType(s.cloud_model_type) + ? s.cloud_model_type + : getRandomDefaultModel(); + const rawWmb = s.workspaceMainBackground; let workspaceMainBackground: WorkspaceMainBackground = 'empty'; if ( @@ -352,6 +382,7 @@ const authStore = create()( appearanceMode: 'light', customThemeCatalog: normalizedCustomCatalog, workspaceMainBackground, + cloud_model_type: sanitizedCloudModelType, }; } return { @@ -360,6 +391,7 @@ const authStore = create()( appearanceMode: normalizedAppearanceMode, customThemeCatalog: normalizedCustomCatalog, workspaceMainBackground, + cloud_model_type: sanitizedCloudModelType, } as typeof persistedState; }, partialize: (state) => ({ diff --git a/vite.config.web.ts b/vite.config.web.ts index 7a3b0c41..a0a883f7 100644 --- a/vite.config.web.ts +++ b/vite.config.web.ts @@ -15,9 +15,20 @@ // Usage: npm run dev:web | npm run build:web import react from '@vitejs/plugin-react'; +import http from 'node:http'; +import https from 'node:https'; import path from 'node:path'; import { defineConfig, loadEnv } from 'vite'; +const proxyHttpsAgent = new https.Agent({ + keepAlive: true, + keepAliveMsecs: 30_000, +}); +const proxyHttpAgent = new http.Agent({ + keepAlive: true, + keepAliveMsecs: 30_000, +}); + export default defineConfig(({ mode }) => { const env = loadEnv(mode, process.cwd(), ''); return { @@ -44,6 +55,9 @@ export default defineConfig(({ mode }) => { '/api': { target: env.VITE_PROXY_URL, changeOrigin: true, + agent: env.VITE_PROXY_URL.startsWith('https') + ? proxyHttpsAgent + : proxyHttpAgent, }, } : undefined,