From 80bce6ad70b08b8ced2d8f243a27fec3c1978447 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 25 Apr 2026 16:54:15 +0000 Subject: [PATCH 1/5] Support custom Claude model on spawn Co-authored-by: METO --- cli/src/api/apiMachine.ts | 3 ++- cli/src/claude/runClaude.ts | 13 +++++++++++-- cli/src/modules/common/rpcTypes.ts | 1 + cli/src/runner/run.ts | 8 ++++++++ hub/src/sync/rpcGateway.ts | 3 ++- hub/src/sync/syncEngine.ts | 4 +++- hub/src/web/routes/machines.ts | 2 ++ web/src/api/client.ts | 3 ++- .../components/NewSession/ModelSelector.tsx | 12 ++++++++++++ web/src/components/NewSession/index.tsx | 18 +++++++++++++++++- web/src/components/NewSession/types.ts | 1 + web/src/hooks/mutations/useSpawnSession.ts | 2 ++ web/src/lib/locales/en.ts | 2 ++ web/src/lib/locales/zh-CN.ts | 2 ++ 14 files changed, 67 insertions(+), 7 deletions(-) diff --git a/cli/src/api/apiMachine.ts b/cli/src/api/apiMachine.ts index e38d088932..fc303c6330 100644 --- a/cli/src/api/apiMachine.ts +++ b/cli/src/api/apiMachine.ts @@ -101,7 +101,7 @@ export class ApiMachineClient { setRPCHandlers({ spawnSession, stopSession, requestShutdown }: MachineRpcHandlers): void { this.rpcHandlerManager.registerHandler('spawn-happy-session', async (params: any) => { - const { directory, sessionId, resumeSessionId, machineId, approvedNewDirectoryCreation, agent, model, yolo, token, sessionType, worktreeName } = params || {} + const { directory, sessionId, resumeSessionId, machineId, approvedNewDirectoryCreation, agent, model, isCustomModel, yolo, token, sessionType, worktreeName } = params || {} if (!directory) { throw new Error('Directory is required') @@ -115,6 +115,7 @@ export class ApiMachineClient { approvedNewDirectoryCreation, agent, model, + isCustomModel, yolo, token, sessionType, diff --git a/cli/src/claude/runClaude.ts b/cli/src/claude/runClaude.ts index 796365b3ab..3a677d73d4 100644 --- a/cli/src/claude/runClaude.ts +++ b/cli/src/claude/runClaude.ts @@ -18,6 +18,13 @@ import { isModelModeAllowedForFlavor, isPermissionModeAllowedForFlavor } from '@ import { ModelModeSchema, PermissionModeSchema } from '@hapi/protocol/schemas'; import { formatMessageWithAttachments } from '@/utils/attachmentFormatter'; +function resolveInitialModelMode(model?: string): SessionModelMode { + if (model === 'sonnet' || model === 'opus') { + return model; + } + return 'default'; +} + export interface StartOptions { model?: string permissionMode?: PermissionMode @@ -143,7 +150,8 @@ export async function runClaude(options: StartOptions = {}): Promise { // Forward messages to the queue let currentPermissionMode: PermissionMode = options.permissionMode ?? 'default'; - let currentModelMode: SessionModelMode = options.model === 'sonnet' || options.model === 'opus' ? options.model : 'default'; + let currentModelMode: SessionModelMode = resolveInitialModelMode(options.model); + let currentModel = options.model; let currentFallbackModel: string | undefined = undefined; // Track current fallback model let currentCustomSystemPrompt: string | undefined = undefined; // Track current custom system prompt let currentAppendSystemPrompt: string | undefined = undefined; // Track current append system prompt @@ -165,7 +173,7 @@ export async function runClaude(options: StartOptions = {}): Promise { currentPermissionMode = sessionPermissionMode as PermissionMode; } const messagePermissionMode = currentPermissionMode; - const messageModel = currentModelMode === 'default' ? undefined : currentModelMode; + const messageModel = currentModel; logger.debug(`[loop] User message received with permission mode: ${currentPermissionMode}, model: ${currentModelMode}`); // Resolve custom system prompt - use message.meta.customSystemPrompt if provided, otherwise use current @@ -303,6 +311,7 @@ export async function runClaude(options: StartOptions = {}): Promise { if (config.modelMode !== undefined) { const resolvedModelMode = resolveModelMode(config.modelMode); currentModelMode = resolvedModelMode; + currentModel = resolvedModelMode === 'default' ? undefined : resolvedModelMode; } syncSessionModes(); diff --git a/cli/src/modules/common/rpcTypes.ts b/cli/src/modules/common/rpcTypes.ts index 2c6f729419..1f01cbedd3 100644 --- a/cli/src/modules/common/rpcTypes.ts +++ b/cli/src/modules/common/rpcTypes.ts @@ -6,6 +6,7 @@ export interface SpawnSessionOptions { approvedNewDirectoryCreation?: boolean agent?: 'claude' | 'codex' | 'gemini' | 'opencode' model?: string + isCustomModel?: boolean yolo?: boolean token?: string sessionType?: 'simple' | 'worktree' diff --git a/cli/src/runner/run.ts b/cli/src/runner/run.ts index b6494077d8..15640f617d 100644 --- a/cli/src/runner/run.ts +++ b/cli/src/runner/run.ts @@ -181,6 +181,7 @@ export async function startRunner(): Promise { const agent = options.agent ?? 'claude'; const yolo = options.yolo === true; const sessionType = options.sessionType ?? 'simple'; + const isCustomClaudeModel = agent === 'claude' && options.isCustomModel === true && Boolean(options.model); const worktreeName = options.worktreeName; let directoryCreated = false; let spawnDirectory = directory; @@ -322,6 +323,13 @@ export async function startRunner(): Promise { }; } + if (isCustomClaudeModel && options.model) { + extraEnv = { + ...extraEnv, + ANTHROPIC_DEFAULT_HAIKU_MODEL: options.model + }; + } + // Construct arguments for the CLI const agentCommand = agent === 'codex' ? 'codex' diff --git a/hub/src/sync/rpcGateway.ts b/hub/src/sync/rpcGateway.ts index 84a3b05e51..4c3b1960a9 100644 --- a/hub/src/sync/rpcGateway.ts +++ b/hub/src/sync/rpcGateway.ts @@ -108,6 +108,7 @@ export class RpcGateway { directory: string, agent: 'claude' | 'codex' | 'gemini' | 'opencode' = 'claude', model?: string, + isCustomModel?: boolean, yolo?: boolean, sessionType?: 'simple' | 'worktree', worktreeName?: string, @@ -117,7 +118,7 @@ export class RpcGateway { const result = await this.machineRpc( machineId, 'spawn-happy-session', - { type: 'spawn-in-directory', directory, agent, model, yolo, sessionType, worktreeName, resumeSessionId } + { type: 'spawn-in-directory', directory, agent, model, isCustomModel, yolo, sessionType, worktreeName, resumeSessionId } ) if (result && typeof result === 'object') { const obj = result as Record diff --git a/hub/src/sync/syncEngine.ts b/hub/src/sync/syncEngine.ts index 1ab46e65f5..f7068f3e0b 100644 --- a/hub/src/sync/syncEngine.ts +++ b/hub/src/sync/syncEngine.ts @@ -302,12 +302,13 @@ export class SyncEngine { directory: string, agent: 'claude' | 'codex' | 'gemini' | 'opencode' = 'claude', model?: string, + isCustomModel?: boolean, yolo?: boolean, sessionType?: 'simple' | 'worktree', worktreeName?: string, resumeSessionId?: string ): Promise<{ type: 'success'; sessionId: string } | { type: 'error'; message: string }> { - return await this.rpcGateway.spawnSession(machineId, directory, agent, model, yolo, sessionType, worktreeName, resumeSessionId) + return await this.rpcGateway.spawnSession(machineId, directory, agent, model, isCustomModel, yolo, sessionType, worktreeName, resumeSessionId) } async resumeSession(sessionId: string, namespace: string): Promise { @@ -374,6 +375,7 @@ export class SyncEngine { undefined, undefined, undefined, + undefined, resumeToken ) diff --git a/hub/src/web/routes/machines.ts b/hub/src/web/routes/machines.ts index 5749d0b8f1..0d388376a5 100644 --- a/hub/src/web/routes/machines.ts +++ b/hub/src/web/routes/machines.ts @@ -8,6 +8,7 @@ const spawnBodySchema = z.object({ directory: z.string().min(1), agent: z.enum(['claude', 'codex', 'gemini', 'opencode']).optional(), model: z.string().optional(), + isCustomModel: z.boolean().optional(), yolo: z.boolean().optional(), sessionType: z.enum(['simple', 'worktree']).optional(), worktreeName: z.string().optional() @@ -54,6 +55,7 @@ export function createMachinesRoutes(getSyncEngine: () => SyncEngine | null): Ho parsed.data.directory, parsed.data.agent, parsed.data.model, + parsed.data.isCustomModel, parsed.data.yolo, parsed.data.sessionType, parsed.data.worktreeName diff --git a/web/src/api/client.ts b/web/src/api/client.ts index 347e78f184..f843d54fbf 100644 --- a/web/src/api/client.ts +++ b/web/src/api/client.ts @@ -374,13 +374,14 @@ export class ApiClient { directory: string, agent?: 'claude' | 'codex' | 'gemini' | 'opencode', model?: string, + isCustomModel?: boolean, yolo?: boolean, sessionType?: 'simple' | 'worktree', worktreeName?: string ): Promise { return await this.request(`/api/machines/${encodeURIComponent(machineId)}/spawn`, { method: 'POST', - body: JSON.stringify({ directory, agent, model, yolo, sessionType, worktreeName }) + body: JSON.stringify({ directory, agent, model, isCustomModel, yolo, sessionType, worktreeName }) }) } diff --git a/web/src/components/NewSession/ModelSelector.tsx b/web/src/components/NewSession/ModelSelector.tsx index a3ee434bf7..01c6bccd4b 100644 --- a/web/src/components/NewSession/ModelSelector.tsx +++ b/web/src/components/NewSession/ModelSelector.tsx @@ -5,8 +5,10 @@ import { useTranslation } from '@/lib/use-translation' export function ModelSelector(props: { agent: AgentType model: string + customModel: string isDisabled: boolean onModelChange: (value: string) => void + onCustomModelChange: (value: string) => void }) { const { t } = useTranslation() const options = MODEL_OPTIONS[props.agent] @@ -32,6 +34,16 @@ export function ModelSelector(props: { ))} + {props.agent === 'claude' && props.model === 'custom' ? ( + props.onCustomModelChange(e.target.value)} + disabled={props.isDisabled} + placeholder={t('newSession.model.custom.placeholder')} + className="w-full px-3 py-2 text-sm rounded-lg border border-[var(--app-divider)] bg-[var(--app-bg)] text-[var(--app-text)] focus:outline-none focus:ring-2 focus:ring-[var(--app-link)] disabled:opacity-50" + /> + ) : null} ) } diff --git a/web/src/components/NewSession/index.tsx b/web/src/components/NewSession/index.tsx index 08fd815664..95d36ec648 100644 --- a/web/src/components/NewSession/index.tsx +++ b/web/src/components/NewSession/index.tsx @@ -21,6 +21,7 @@ import { } from './preferences' import { SessionTypeSelector } from './SessionTypeSelector' import { YoloToggle } from './YoloToggle' +import { useTranslation } from '@/lib/use-translation' export function NewSession(props: { api: ApiClient @@ -30,6 +31,7 @@ export function NewSession(props: { onCancel: () => void }) { const { haptic } = usePlatform() + const { t } = useTranslation() const { spawnSession, isPending, error: spawnError } = useSpawnSession(props.api) const { sessions } = useSessions(props.api) const isFormDisabled = Boolean(isPending || props.isLoading) @@ -42,6 +44,7 @@ export function NewSession(props: { const [pathExistence, setPathExistence] = useState>({}) const [agent, setAgent] = useState(loadPreferredAgent) const [model, setModel] = useState('auto') + const [customModel, setCustomModel] = useState('') const [yoloMode, setYoloMode] = useState(loadPreferredYoloMode) const [sessionType, setSessionType] = useState('simple') const [worktreeName, setWorktreeName] = useState('') @@ -56,6 +59,7 @@ export function NewSession(props: { useEffect(() => { setModel('auto') + setCustomModel('') }, [agent]) useEffect(() => { @@ -209,12 +213,22 @@ export function NewSession(props: { setError(null) try { - const resolvedModel = model !== 'auto' && agent !== 'opencode' ? model : undefined + const trimmedCustomModel = customModel.trim() + if (agent === 'claude' && model === 'custom' && !trimmedCustomModel) { + setError(t('newSession.model.custom.required')) + return + } + const resolvedModel = agent === 'claude' && model === 'custom' + ? trimmedCustomModel + : model !== 'auto' && agent !== 'opencode' + ? model + : undefined const result = await spawnSession({ machineId, directory: directory.trim(), agent, model: resolvedModel, + isCustomModel: agent === 'claude' && model === 'custom', yolo: yoloMode, sessionType, worktreeName: sessionType === 'worktree' ? (worktreeName.trim() || undefined) : undefined @@ -276,8 +290,10 @@ export function NewSession(props: { Date: Sun, 26 Apr 2026 01:32:35 +0000 Subject: [PATCH 2/5] Infer custom Claude model from model value Co-authored-by: METO --- cli/src/api/apiMachine.ts | 3 +-- cli/src/modules/common/rpcTypes.ts | 1 - cli/src/runner/buildCliArgs.test.ts | 12 +++++++++++- cli/src/runner/run.ts | 13 +++++++++++-- hub/src/sync/rpcGateway.ts | 3 +-- hub/src/sync/syncEngine.ts | 3 --- hub/src/web/routes/machines.ts | 2 -- web/src/api/client.ts | 3 +-- web/src/components/NewSession/index.tsx | 1 - web/src/hooks/mutations/useSpawnSession.ts | 2 -- 10 files changed, 25 insertions(+), 18 deletions(-) diff --git a/cli/src/api/apiMachine.ts b/cli/src/api/apiMachine.ts index 136336b523..da7fab7ad3 100644 --- a/cli/src/api/apiMachine.ts +++ b/cli/src/api/apiMachine.ts @@ -103,7 +103,7 @@ export class ApiMachineClient { setRPCHandlers({ spawnSession, stopSession, requestShutdown }: MachineRpcHandlers): void { this.rpcHandlerManager.registerHandler('spawn-happy-session', async (params: any) => { - const { directory, sessionId, resumeSessionId, machineId, approvedNewDirectoryCreation, agent, model, isCustomModel, effort, modelReasoningEffort, yolo, permissionMode, token, sessionType, worktreeName } = params || {} + const { directory, sessionId, resumeSessionId, machineId, approvedNewDirectoryCreation, agent, model, effort, modelReasoningEffort, yolo, permissionMode, token, sessionType, worktreeName } = params || {} if (!directory) { throw new Error('Directory is required') @@ -117,7 +117,6 @@ export class ApiMachineClient { approvedNewDirectoryCreation, agent, model, - isCustomModel, effort, modelReasoningEffort, yolo, diff --git a/cli/src/modules/common/rpcTypes.ts b/cli/src/modules/common/rpcTypes.ts index d4b9c9676e..6336f57dd8 100644 --- a/cli/src/modules/common/rpcTypes.ts +++ b/cli/src/modules/common/rpcTypes.ts @@ -6,7 +6,6 @@ export interface SpawnSessionOptions { approvedNewDirectoryCreation?: boolean agent?: 'claude' | 'codex' | 'cursor' | 'gemini' | 'opencode' model?: string - isCustomModel?: boolean effort?: string modelReasoningEffort?: string yolo?: boolean diff --git a/cli/src/runner/buildCliArgs.test.ts b/cli/src/runner/buildCliArgs.test.ts index 612e2d1d6f..e1bcc679fa 100644 --- a/cli/src/runner/buildCliArgs.test.ts +++ b/cli/src/runner/buildCliArgs.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest' -import { buildCliArgs } from './run' +import { buildCliArgs, shouldSetClaudeHaikuModelEnv } from './run' describe('buildCliArgs', () => { it('adds --permission-mode for valid permission mode', () => { @@ -61,4 +61,14 @@ describe('buildCliArgs', () => { expect(args).toContain(mode) } }) + + it('detects custom Claude models for Haiku env override', () => { + expect(shouldSetClaudeHaikuModelEnv('claude', 'claude-3-5-haiku-latest')).toBe(true) + expect(shouldSetClaudeHaikuModelEnv('claude', 'opus')).toBe(false) + expect(shouldSetClaudeHaikuModelEnv('claude', 'opus[1m]')).toBe(false) + expect(shouldSetClaudeHaikuModelEnv('claude', 'sonnet')).toBe(false) + expect(shouldSetClaudeHaikuModelEnv('claude', 'sonnet[1m]')).toBe(false) + expect(shouldSetClaudeHaikuModelEnv('codex', 'claude-3-5-haiku-latest')).toBe(false) + expect(shouldSetClaudeHaikuModelEnv('claude')).toBe(false) + }) }) diff --git a/cli/src/runner/run.ts b/cli/src/runner/run.ts index bfdf3cd4e8..284e3b1201 100644 --- a/cli/src/runner/run.ts +++ b/cli/src/runner/run.ts @@ -24,6 +24,16 @@ import { join } from 'path'; import { buildMachineMetadata } from '@/agent/sessionFactory'; import { hashRunnerCliApiToken } from './runnerIdentity'; +const CLAUDE_BUILT_IN_MODEL_ALIASES = new Set(['opus', 'opus[1m]', 'sonnet', 'sonnet[1m]']); + +export function shouldSetClaudeHaikuModelEnv(agent: string, model?: string): boolean { + if (agent !== 'claude') { + return false; + } + const normalizedModel = model?.trim(); + return Boolean(normalizedModel && !CLAUDE_BUILT_IN_MODEL_ALIASES.has(normalizedModel)); +} + export async function startRunner(): Promise { // We don't have cleanup function at the time of server construction // Control flow is: @@ -236,7 +246,6 @@ export async function startRunner(): Promise { const agent = options.agent ?? 'claude'; const yolo = options.yolo === true; const sessionType = options.sessionType ?? 'simple'; - const isCustomClaudeModel = agent === 'claude' && options.isCustomModel === true && Boolean(options.model); const worktreeName = options.worktreeName; let directoryCreated = false; let spawnDirectory = directory; @@ -378,7 +387,7 @@ export async function startRunner(): Promise { }; } - if (isCustomClaudeModel && options.model) { + if (shouldSetClaudeHaikuModelEnv(agent, options.model) && options.model) { extraEnv = { ...extraEnv, ANTHROPIC_DEFAULT_HAIKU_MODEL: options.model diff --git a/hub/src/sync/rpcGateway.ts b/hub/src/sync/rpcGateway.ts index 79c0e04269..dfc78d89e5 100644 --- a/hub/src/sync/rpcGateway.ts +++ b/hub/src/sync/rpcGateway.ts @@ -125,7 +125,6 @@ export class RpcGateway { directory: string, agent: 'claude' | 'codex' | 'cursor' | 'gemini' | 'opencode' = 'claude', model?: string, - isCustomModel?: boolean, modelReasoningEffort?: string, yolo?: boolean, sessionType?: 'simple' | 'worktree', @@ -138,7 +137,7 @@ export class RpcGateway { const result = await this.machineRpc( machineId, 'spawn-happy-session', - { type: 'spawn-in-directory', directory, agent, model, isCustomModel, modelReasoningEffort, yolo, sessionType, worktreeName, resumeSessionId, effort, permissionMode } + { type: 'spawn-in-directory', directory, agent, model, modelReasoningEffort, yolo, sessionType, worktreeName, resumeSessionId, effort, permissionMode } ) if (result && typeof result === 'object') { const obj = result as Record diff --git a/hub/src/sync/syncEngine.ts b/hub/src/sync/syncEngine.ts index 864a624235..284160ba6c 100644 --- a/hub/src/sync/syncEngine.ts +++ b/hub/src/sync/syncEngine.ts @@ -369,7 +369,6 @@ export class SyncEngine { directory: string, agent: 'claude' | 'codex' | 'cursor' | 'gemini' | 'opencode' = 'claude', model?: string, - isCustomModel?: boolean, modelReasoningEffort?: string, yolo?: boolean, sessionType?: 'simple' | 'worktree', @@ -383,7 +382,6 @@ export class SyncEngine { directory, agent, model, - isCustomModel, modelReasoningEffort, yolo, sessionType, @@ -457,7 +455,6 @@ export class SyncEngine { metadata.path, flavor, session.model ?? undefined, - undefined, session.modelReasoningEffort ?? undefined, undefined, undefined, diff --git a/hub/src/web/routes/machines.ts b/hub/src/web/routes/machines.ts index 0826271707..ea6dedf0b5 100644 --- a/hub/src/web/routes/machines.ts +++ b/hub/src/web/routes/machines.ts @@ -8,7 +8,6 @@ const spawnBodySchema = z.object({ directory: z.string().min(1), agent: z.enum(['claude', 'codex', 'cursor', 'gemini', 'opencode']).optional(), model: z.string().optional(), - isCustomModel: z.boolean().optional(), effort: z.string().optional(), modelReasoningEffort: z.string().optional(), yolo: z.boolean().optional(), @@ -57,7 +56,6 @@ export function createMachinesRoutes(getSyncEngine: () => SyncEngine | null): Ho parsed.data.directory, parsed.data.agent, parsed.data.model, - parsed.data.isCustomModel, parsed.data.modelReasoningEffort, parsed.data.yolo, parsed.data.sessionType, diff --git a/web/src/api/client.ts b/web/src/api/client.ts index 60f9f7974c..802528860f 100644 --- a/web/src/api/client.ts +++ b/web/src/api/client.ts @@ -396,7 +396,6 @@ export class ApiClient { directory: string, agent?: 'claude' | 'codex' | 'cursor' | 'gemini' | 'opencode', model?: string, - isCustomModel?: boolean, modelReasoningEffort?: string, yolo?: boolean, sessionType?: 'simple' | 'worktree', @@ -405,7 +404,7 @@ export class ApiClient { ): Promise { return await this.request(`/api/machines/${encodeURIComponent(machineId)}/spawn`, { method: 'POST', - body: JSON.stringify({ directory, agent, model, isCustomModel, modelReasoningEffort, yolo, sessionType, worktreeName, effort }) + body: JSON.stringify({ directory, agent, model, modelReasoningEffort, yolo, sessionType, worktreeName, effort }) }) } diff --git a/web/src/components/NewSession/index.tsx b/web/src/components/NewSession/index.tsx index fd9e0f3184..dfc06d1828 100644 --- a/web/src/components/NewSession/index.tsx +++ b/web/src/components/NewSession/index.tsx @@ -286,7 +286,6 @@ export function NewSession(props: { directory: trimmedDirectory, agent, model: resolvedModel, - isCustomModel: agent === 'claude' && model === 'custom', effort: resolvedEffort, modelReasoningEffort: resolvedModelReasoningEffort, yolo: yoloMode, diff --git a/web/src/hooks/mutations/useSpawnSession.ts b/web/src/hooks/mutations/useSpawnSession.ts index c706d73bb5..37f69f61a1 100644 --- a/web/src/hooks/mutations/useSpawnSession.ts +++ b/web/src/hooks/mutations/useSpawnSession.ts @@ -8,7 +8,6 @@ type SpawnInput = { directory: string agent?: 'claude' | 'codex' | 'cursor' | 'gemini' | 'opencode' model?: string - isCustomModel?: boolean effort?: string modelReasoningEffort?: string yolo?: boolean @@ -33,7 +32,6 @@ export function useSpawnSession(api: ApiClient | null): { input.directory, input.agent, input.model, - input.isCustomModel, input.modelReasoningEffort, input.yolo, input.sessionType, From 45c7fee887144927143fc856b2a233de36410e20 Mon Sep 17 00:00:00 2001 From: metowolf Date: Sun, 26 Apr 2026 16:23:14 +0800 Subject: [PATCH 3/5] fix(web): update custom model placeholder text --- web/src/lib/locales/zh-CN.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/lib/locales/zh-CN.ts b/web/src/lib/locales/zh-CN.ts index a37d48a8b5..4ebfd06228 100644 --- a/web/src/lib/locales/zh-CN.ts +++ b/web/src/lib/locales/zh-CN.ts @@ -107,7 +107,7 @@ export default { 'newSession.model': '模型', 'newSession.effort': '思考强度', 'newSession.model.optional': '可选', - 'newSession.model.custom.placeholder': '输入 Claude 模型名称', + 'newSession.model.custom.placeholder': '输入自定义模型名称', 'newSession.model.custom.required': '请输入自定义模型名称', 'newSession.model.loadFailed': '加载 Codex 模型失败', 'newSession.reasoningEffort': '推理强度', From a70013e7ac27892e091fcb8ad17b5a60bdae88c8 Mon Sep 17 00:00:00 2001 From: metowolf Date: Sun, 26 Apr 2026 16:29:53 +0800 Subject: [PATCH 4/5] fix(test): update model options test for custom model --- web/src/components/NewSession/types.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/web/src/components/NewSession/types.test.ts b/web/src/components/NewSession/types.test.ts index 08591db022..b044d6de91 100644 --- a/web/src/components/NewSession/types.test.ts +++ b/web/src/components/NewSession/types.test.ts @@ -10,6 +10,7 @@ describe('Claude model options', () => { { value: 'opus[1m]', label: 'Opus 1M' }, { value: 'sonnet', label: 'Sonnet' }, { value: 'sonnet[1m]', label: 'Sonnet 1M' }, + { value: 'custom', label: 'Custom' }, ]) }) From a8415b0fb73886f1fd21055781346a9fb3d9603d Mon Sep 17 00:00:00 2001 From: metowolf Date: Tue, 28 Apr 2026 23:35:57 +0800 Subject: [PATCH 5/5] chore: trigger CI