From 2a77a7d7653e579b8b656d082853ee23db9fe62c Mon Sep 17 00:00:00 2001 From: liyang Date: Fri, 27 Mar 2026 17:29:03 +0800 Subject: [PATCH] Fix OpenAI Responses compatible endpoint handling --- .../provider/openai_responses/repository.rs | 41 ++++++++++++++++--- 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/crates/forge_repo/src/provider/openai_responses/repository.rs b/crates/forge_repo/src/provider/openai_responses/repository.rs index 296410abfa..54100589d9 100644 --- a/crates/forge_repo/src/provider/openai_responses/repository.rs +++ b/crates/forge_repo/src/provider/openai_responses/repository.rs @@ -29,8 +29,9 @@ pub(super) struct OpenAIResponsesProvider { impl OpenAIResponsesProvider { /// Creates a new OpenAI Responses provider /// - /// For the Codex provider, the configured URL is used directly as the - /// responses endpoint (e.g., `chatgpt.com/backend-api/codex/responses`). + /// For providers whose configured URL already points at a full Responses + /// endpoint, the configured URL is used directly (for example, + /// `chatgpt.com/backend-api/codex/responses`). /// For all other providers, the path is rewritten to `{host}/v1/responses`. /// /// # Panics @@ -39,10 +40,12 @@ impl OpenAIResponsesProvider { pub fn new(provider: Provider, http: Arc) -> Self { use forge_domain::ProviderId; - if provider.id == ProviderId::CODEX || provider.id == ProviderId::OPENCODE_ZEN { - // Codex and OpenCode Zen use the configured URL directly as the - // responses endpoint (their URLs already contain the full path, - // e.g. `opencode.ai/zen/v1/responses`). + if provider.id == ProviderId::CODEX + || provider.id == ProviderId::OPENCODE_ZEN + || provider.id == ProviderId::OPENAI_RESPONSES_COMPATIBLE + { + // These providers already configure a complete Responses endpoint, + // so preserve the configured path exactly as-is. let responses_url = provider.url.clone(); let api_base = { let mut base = provider.url.clone(); @@ -635,6 +638,32 @@ mod tests { ); } + #[test] + fn test_openai_responses_provider_new_preserves_existing_base_path_for_compatible_provider() { + let provider = Provider { + id: ProviderId::OPENAI_RESPONSES_COMPATIBLE, + provider_type: forge_domain::ProviderType::Llm, + response: Some(ProviderResponse::OpenAIResponses), + url: Url::parse("https://provider.example/custom-prefix/v1/responses").unwrap(), + credential: make_credential(ProviderId::OPENAI_RESPONSES_COMPATIBLE, "test-key"), + custom_headers: None, + auth_methods: vec![forge_domain::AuthMethod::ApiKey], + url_params: vec![], + models: None, + }; + let infra = Arc::new(MockHttpClient { client: reqwest::Client::new() }); + let provider_impl = OpenAIResponsesProvider::::new(provider, infra); + + assert_eq!( + provider_impl.api_base.as_str(), + "https://provider.example/custom-prefix/v1" + ); + assert_eq!( + provider_impl.responses_url.as_str(), + "https://provider.example/custom-prefix/v1/responses" + ); + } + #[test] fn test_openai_responses_provider_new_with_codex_url() { let provider = Provider {