From 6ecad74efe90e7738878fd2c3e946b094a93dc39 Mon Sep 17 00:00:00 2001 From: kdletters <61648117+kdletters@users.noreply.github.com> Date: Mon, 5 Oct 2026 18:48:04 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=9D=E7=95=99=20Provider=20proxy=20?= =?UTF-8?q?=E5=BA=95=E5=B1=82=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 传播请求体改写和上游发送失败正文 补充 Provider proxy 错误收口约定 --- .../src/agent/codex_provider_proxy.rs | 49 +++++++++++++------ ...GC命令错误结构化与错误报告口径-2026-10-01.md | 1 + ...€�里程碑】AGC错误具体文本展示-2026-10-04.md | 1 + 3 files changed, 35 insertions(+), 16 deletions(-) diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/codex_provider_proxy.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/codex_provider_proxy.rs index bdc90be02..4d45af75d 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/codex_provider_proxy.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/codex_provider_proxy.rs @@ -147,11 +147,11 @@ fn normalize_codex_provider_upstream(value: &str) -> Result { Ok(value.to_string()) } -fn proxy_error(status: StatusCode, message: &'static str) -> Response { +fn proxy_error(status: StatusCode, message: impl Into) -> Response { Response::builder() .status(status) .header("content-type", "text/plain; charset=utf-8") - .body(Body::from(message)) + .body(message.into()) .unwrap_or_else(|_| Response::new(Body::empty())) } @@ -175,18 +175,19 @@ fn is_codex_account_limit_header(name: &HeaderName) -> bool { name.as_str().to_ascii_lowercase().starts_with("x-codex-") } -fn parallel_direct_request(body: &[u8]) -> Result, &'static str> { - let mut value: serde_json::Value = - serde_json::from_slice(body).map_err(|_| "provider request JSON invalid")?; +fn parallel_direct_request(body: &[u8]) -> Result, String> { + let mut value: serde_json::Value = serde_json::from_slice(body) + .map_err(|error| format!("provider request JSON invalid:{error}"))?; let object = value .as_object_mut() - .ok_or("provider request must be an object")?; + .ok_or_else(|| "provider request must be an object".to_string())?; // SDK 的未知模型回退目录默认关闭并行。Direct 的宿主已负责许可、依赖和冲突, // 因此明确请求标准 Responses 并行能力;不伪造模型名称或工具的只读标记。 object.insert("parallel_tool_calls".into(), serde_json::Value::Bool(true)); - let bytes = serde_json::to_vec(&value).map_err(|_| "provider request JSON invalid")?; + let bytes = serde_json::to_vec(&value) + .map_err(|error| format!("provider request JSON serialization failed:{error}"))?; if bytes.len() > CODEX_PROVIDER_PROXY_MAX_REQUEST_BYTES { - return Err("provider request too large"); + return Err("provider request too large".to_string()); } Ok(bytes) } @@ -233,15 +234,21 @@ async fn proxy_codex_provider_request( let (parts, body) = request.into_parts(); let body = match to_bytes(body, CODEX_PROVIDER_PROXY_MAX_REQUEST_BYTES).await { Ok(body) => body, - Err(_) => return proxy_error(StatusCode::PAYLOAD_TOO_LARGE, "provider request too large"), + Err(error) => { + return proxy_error( + StatusCode::PAYLOAD_TOO_LARGE, + format!("provider request body read failed:{error}"), + ) + } }; let body = if state.parallel_tool_calls { match tokio::task::spawn_blocking(move || parallel_direct_request(&body)).await { Ok(Ok(bytes)) => axum::body::Bytes::from(bytes), - _ => { + Ok(Err(error)) => return proxy_error(StatusCode::BAD_REQUEST, error), + Err(error) => { return proxy_error( - StatusCode::BAD_REQUEST, - "provider request JSON invalid or oversized", + StatusCode::INTERNAL_SERVER_ERROR, + format!("provider request rewrite task failed:{error}"), ) } } @@ -264,10 +271,10 @@ async fn proxy_codex_provider_request( } let upstream_authorization = match format!("Bearer {}", state.upstream_bearer_token).parse() { Ok(value) => value, - Err(_) => { + Err(error) => { return proxy_error( StatusCode::INTERNAL_SERVER_ERROR, - "provider proxy credential invalid", + format!("provider proxy credential invalid:{error}"), ) } }; @@ -281,7 +288,12 @@ async fn proxy_codex_provider_request( .await { Ok(response) => response, - Err(_) => return proxy_error(StatusCode::BAD_GATEWAY, "provider upstream unavailable"), + Err(error) => { + return proxy_error( + StatusCode::BAD_GATEWAY, + format!("provider upstream unavailable:{error}"), + ) + } }; let status = upstream.status(); let upstream_headers = upstream.headers().clone(); @@ -310,7 +322,12 @@ async fn proxy_codex_provider_request( } response .body(Body::from_stream(stream)) - .unwrap_or_else(|_| proxy_error(StatusCode::BAD_GATEWAY, "provider response invalid")) + .unwrap_or_else(|error| { + proxy_error( + StatusCode::BAD_GATEWAY, + format!("provider response invalid:{error}"), + ) + }) } #[cfg(test)] diff --git a/docs/adr/【ADR】AGC命令错误结构化与错误报告口径-2026-10-01.md b/docs/adr/【ADR】AGC命令错误结构化与错误报告口径-2026-10-01.md index 43624394a..42ec51ead 100644 --- a/docs/adr/【ADR】AGC命令错误结构化与错误报告口径-2026-10-01.md +++ b/docs/adr/【ADR】AGC命令错误结构化与错误报告口径-2026-10-01.md @@ -114,6 +114,7 @@ DirectProject 已经解决过同一类问题([`【ADR】DirectProject命令接 - 素材直传、参考图直传、资源编辑上传/轮询、模型目录初始化和 Direct MCP stdio 的 `send`/读写/序列化失败,不得用 `map_err(|_| 固定句)` 丢掉 `reqwest`、serde 或 OS/IPC 正文;固定句只能作为事实前缀,底层原因必须接在后面。 - 插件 stdout RPC 读取线程不得吞掉 `fill_buf`、UTF-8 或大小上限错误后伪装成“插件进程已退出”;读线程通过挂起 RPC 的错误通道传播真实原因。 - Codex CLI/Agent Runner 的 stderr、子任务 join、请求序列化/响应解析和本地资源/字体读取错误保留底层 `std::io`、serde 与 UTF-8 原因;路径安全校验仍单独使用稳定的安全拒绝码。 +- Codex Provider proxy 的请求体读取/JSON 改写、上游请求、响应构造和 SSE 传输错误也必须把 serde、join、reqwest 或流错误写入返回正文,不能只回 `provider upstream unavailable`。 - 浏览器启动/DevTools 握手与 Codex model-catalog 子进程失败保留 stderr、退出状态、解析错误和阶段;稳定机器码仍用于分类,但不能单独成为用户可见正文。 - Node/npm 环境探测和 Web scaffold 构建持续排空并保存有界 stdout/stderr 尾部,失败返回退出状态、超时阶段和脱敏正文;环境预检回执同时下发 `code` 与 `diagnostic`。 - HTTP 409 只有明确包含泥点不足事实时才映射为 `paidCreditsInsufficient`;Claude Code 的普通 409 冲突保留为 `upstreamFailed`。 diff --git a/docs/project-memory/plans/【里程碑】AGC错误具体文本展示-2026-10-04.md b/docs/project-memory/plans/【里程碑】AGC错误具体文本展示-2026-10-04.md index ffde9ee82..daa45726f 100644 --- a/docs/project-memory/plans/【里程碑】AGC错误具体文本展示-2026-10-04.md +++ b/docs/project-memory/plans/【里程碑】AGC错误具体文本展示-2026-10-04.md @@ -17,6 +17,7 @@ DirectProject 回合失败在确认不是客户端内部不可归类故障时, - 素材/参考图/资源编辑上传、资源编辑轮询、模型目录初始化和 Direct MCP stdio 的网络、JSON、写入、读写失败保留底层错误正文;不能由 `map_err(|_| 固定句)` 把它们压成无因的连接或失败提示。 - 插件 stdout RPC 的 `fill_buf`、UTF-8 和响应大小错误通过挂起 RPC 传播,不能在读取线程里吞掉后统一说“插件进程已退出”。 - Codex CLI/Agent Runner 及本地素材/字体读取保留 stderr、join、序列化/解析、UTF-8 和 OS 错误正文;路径边界拒绝仍保留稳定安全码。 +- Codex Provider proxy 的请求体改写、上游发送、响应构造和 SSE 错误保留底层正文,避免代理层再次把具体网络/解析故障压成单一 unavailable。 - Transport / Stream / IPC / host process / memory exhaustion 的回归测试与错误事件证据。 ## 不做