From bcd117270e09e0815ae90e947cd79d75a0b1fd0e Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Wed, 24 Dec 2025 18:40:44 +0700 Subject: [PATCH] Handle Codex MCP model rejection fallback --- .../server/agent/providers/codex-mcp-agent.ts | 50 ++++++++++++++++--- plan.md | 3 +- 2 files changed, 45 insertions(+), 8 deletions(-) diff --git a/packages/server/src/server/agent/providers/codex-mcp-agent.ts b/packages/server/src/server/agent/providers/codex-mcp-agent.ts index ae201e027..c58e338d7 100644 --- a/packages/server/src/server/agent/providers/codex-mcp-agent.ts +++ b/packages/server/src/server/agent/providers/codex-mcp-agent.ts @@ -228,6 +228,11 @@ function buildCodexMcpConfig( }; } +function isUnsupportedChatGptModelError(error: unknown): boolean { + const message = error instanceof Error ? error.message : String(error); + return message.includes("model is not supported when using Codex with a ChatGPT account"); +} + function extractCommandText(command?: unknown): string | null { if (typeof command === "string") { return command; @@ -295,6 +300,8 @@ class CodexMcpAgentSession implements AgentSession { private currentMode: string; private sessionId: string | null = null; private conversationId: string | null = null; + private runtimeModel: string | null = null; + private modelRejected = false; private pendingLocalId: string | null = null; private persistence: AgentPersistenceHandle | null = null; private cachedRuntimeInfo: AgentRuntimeInfo | null = null; @@ -410,7 +417,7 @@ class CodexMcpAgentSession implements AgentSession { this.cachedRuntimeInfo = { provider: "codex-mcp" as AgentRuntimeInfo["provider"], sessionId: this.sessionId ?? this.pendingLocalId ?? null, - model: this.config.model ?? null, + model: this.runtimeModel ?? this.config.model ?? null, modeId: this.currentMode ?? null, }; @@ -509,7 +516,7 @@ class CodexMcpAgentSession implements AgentSession { const info: AgentRuntimeInfo = { provider: "codex-mcp" as AgentRuntimeInfo["provider"], sessionId: this.sessionId ?? this.pendingLocalId ?? null, - model: this.config.model ?? null, + model: this.runtimeModel ?? this.config.model ?? null, modeId: this.currentMode ?? null, }; this.cachedRuntimeInfo = info; @@ -648,11 +655,25 @@ class CodexMcpAgentSession implements AgentSession { try { if (!this.sessionId) { const config = buildCodexMcpConfig(this.config, prompt, this.currentMode); - response = await this.client.callTool( - { name: "codex", arguments: config }, - undefined, - { signal, timeout: DEFAULT_TIMEOUT_MS } - ); + const attempt = async (arguments_: Record) => + this.client.callTool( + { name: "codex", arguments: arguments_ }, + undefined, + { signal, timeout: DEFAULT_TIMEOUT_MS } + ); + try { + response = await attempt(config); + } catch (error) { + if (config.model && isUnsupportedChatGptModelError(error)) { + const { model: _ignoredModel, ...fallback } = config; + this.modelRejected = true; + this.runtimeModel = null; + this.config.model = undefined; + response = await attempt(fallback); + } else { + throw error; + } + } } else { const conversationId = this.conversationId ?? this.sessionId; response = await this.client.callTool( @@ -683,6 +704,9 @@ class CodexMcpAgentSession implements AgentSession { } this.updateIdentifiersFromResponse(response); + if (this.modelRejected && !this.runtimeModel) { + this.runtimeModel = "default"; + } if (!turnState.sawAssistant) { const text = extractTextContent(response); @@ -778,6 +802,7 @@ class CodexMcpAgentSession implements AgentSession { const meta = record.meta && typeof record.meta === "object" ? (record.meta as Record) : null; const sessionId = meta?.sessionId ?? record.sessionId; const conversationId = meta?.conversationId ?? record.conversationId; + const model = meta?.model ?? record.model; if (typeof sessionId === "string") { this.sessionId = sessionId; this.flushPendingHistory(); @@ -785,6 +810,9 @@ class CodexMcpAgentSession implements AgentSession { if (typeof conversationId === "string") { this.conversationId = conversationId; } + if (typeof model === "string" && model.length > 0) { + this.runtimeModel = model; + } const content = record.content; if (Array.isArray(content)) { @@ -799,6 +827,10 @@ class CodexMcpAgentSession implements AgentSession { if (!this.conversationId && typeof conversationCandidate === "string") { this.conversationId = conversationCandidate; } + const modelCandidate = (item as Record).model; + if (typeof modelCandidate === "string" && modelCandidate.length > 0) { + this.runtimeModel = modelCandidate; + } } } } @@ -815,6 +847,7 @@ class CodexMcpAgentSession implements AgentSession { const record = candidate as Record; const sessionId = record.session_id ?? record.sessionId; const conversationId = record.conversation_id ?? record.conversationId; + const model = record.model; if (!this.sessionId && typeof sessionId === "string") { this.sessionId = sessionId; this.flushPendingHistory(); @@ -822,6 +855,9 @@ class CodexMcpAgentSession implements AgentSession { if (!this.conversationId && typeof conversationId === "string") { this.conversationId = conversationId; } + if (typeof model === "string" && model.length > 0) { + this.runtimeModel = model; + } } } diff --git a/plan.md b/plan.md index 0f8a29e6a..82740eccb 100644 --- a/plan.md +++ b/plan.md @@ -46,7 +46,8 @@ Build a new Codex MCP provider side‑by‑side with the existing Codex SDK prov - Run server typecheck and record failures; add fix tasks for each category. - **Done (2025-12-24 18:35)**: Documented Codex CLI model/permission mismatches in `CODEX_MCP_MISMATCH_REPORT.md`; ran focused Vitest tests (runtime info + permission gating) and captured failures; ran server typecheck and recorded TS2339/TS6133 failures in `codex-mcp-agent.ts`. -- [ ] **Fix**: Handle Codex CLI model availability mismatch (gpt-4.1 rejected for ChatGPT accounts) in Codex MCP tests/provider. +- [x] **Fix**: Handle Codex CLI model availability mismatch (gpt-4.1 rejected for ChatGPT accounts) in Codex MCP tests/provider. + - **Done (2025-12-24 18:40)**: Added model-rejection fallback for ChatGPT accounts, track runtime model from responses, and default to a placeholder when a configured model is rejected. - [ ] **Fix**: Investigate Codex MCP permission elicitation behavior for `approval-policy=on-request` and `untrusted` (no permission_requested events).