{"record":{"id":"f6f24573f5742aae","repo":"BigPizzaV3/CodexPlusPlus","slug":"responses-api","errorCode":null,"errorMessage":"模型路由目标必须使用 Responses API：{}","messagePattern":"模型路由目标必须使用 Responses API：(.+?)","errorType":"http","errorClass":null,"httpStatus":502,"severity":"error","filePath":"crates/codex-plus-core/src/protocol_proxy.rs","lineNumber":746,"sourceCode":"        .find(|route| route.model.trim() == model)\n    else {\n        return Ok(None);\n    };\n    let target_relay_id = route.target_relay_id.trim();\n    if target_relay_id == source.id {\n        anyhow::bail!(\"模型路由不能指向当前供应商自身：{model}\");\n    }\n    let target = settings\n        .relay_profiles\n        .iter()\n        .find(|profile| profile.id == target_relay_id)\n        .cloned()\n        .with_context(|| format!(\"模型路由目标供应商不存在：{target_relay_id}\"))?;\n    if target.relay_mode == crate::settings::RelayMode::Aggregate {\n        anyhow::bail!(\"模型路由目标不能是聚合供应商：{}\", target.name);\n    }\n    if target.protocol != RelayProtocol::Responses {\n        anyhow::bail!(\"模型路由目标必须使用 Responses API：{}\", target.name);\n    }\n\n    let upstream_model = if route.target_model.trim().is_empty() {\n        model.to_string()\n    } else {\n        route.target_model.trim().to_string()\n    };\n    Ok(Some(ModelRouteSelection {\n        relay: target,\n        source_relay_id: source.id,\n        source_model: model.to_string(),\n        upstream_model,\n    }))\n}\n\npub async fn open_models_proxy_request(\n    original_user_agent: Option<&str>,\n) -> anyhow::Result<UpstreamProxyResponse> {","sourceCodeStart":728,"sourceCodeEnd":764,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/1f431ae49b57b3055e0e6845ba6156c6b4232b4d/crates/codex-plus-core/src/protocol_proxy.rs#L728-L764","documentation":"Thrown by select_model_route in crates/codex-plus-core/src/protocol_proxy.rs when a model route's target relay uses a protocol other than RelayProtocol::Responses. Model routes forward the original Responses-API request body (only rewriting the model name), which requires the target to accept the Responses wire format; a ChatCompletions target would need full protocol conversion that the route path does not perform. The placeholder is the target's display name.","triggerScenarios":"A model_routes entry targeting a relay profile configured with protocol = ChatCompletions; user switched the target provider from Responses to Chat Completions after the route was created.","commonSituations":"Reusing an old route after the target provider migrated to a chat-completions-only upstream; importing route configs paired with the wrong protocol profiles; relay target pointing at a vendor that only supports /v1/chat/completions.","solutions":["Change the target relay profile's protocol to Responses (and make sure its upstream really serves the Responses API)","Or repoint the route at a different relay that already uses the Responses protocol","Remove the route if the model should go through the normal protocol-conversion path of the active relay instead"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"fn route_target_uses_responses(settings: &BackendSettings, target_id: &str) -> bool {\n    settings\n        .relay_profiles\n        .iter()\n        .any(|p| p.id == target_id && p.protocol == RelayProtocol::Responses)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Only offer Responses-protocol profiles as model-route targets","When changing a profile's protocol away from Responses, check it is not referenced by any route first","Document that model routes forward the raw Responses payload without conversion"],"tags":["model-routing","protocol-mismatch","responses-api","configuration"],"backgroundTag":"invalid-routing-config","analyzedSha":"1f431ae49b57b3055e0e6845ba6156c6b4232b4d","analyzedAt":"2026-08-16T20:54:18.598Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}