From bc8f180c6dc26c8ee7e57184de6cb3863a3edadf Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Fri, 27 Feb 2026 15:10:06 -0500 Subject: [PATCH] Add base_path field to custom provider config Allow custom providers using the OpenAI engine to explicitly set the API path (e.g. "v1/responses" vs "v1/chat/completions") instead of relying on URL path parsing or the default. This enables pointing a custom provider at an OpenAI-compatible endpoint that serves the Responses API without needing to embed the path in the base_url. The new optional base_path field is threaded through the declarative provider config, CLI, and server routes. When set, it takes priority over both the URL-derived path and the model-name heuristic in should_use_responses_api(). Signed-off-by: Dan Prince --- crates/goose-cli/src/commands/configure.rs | 1 + crates/goose-server/src/routes/config_management.rs | 4 ++++ crates/goose/src/config/declarative_providers.rs | 6 ++++++ crates/goose/src/providers/openai.rs | 12 ++++++++---- ui/desktop/openapi.json | 8 ++++++++ ui/desktop/src/api/types.gen.ts | 2 ++ 6 files changed, 29 insertions(+), 4 deletions(-) diff --git a/crates/goose-cli/src/commands/configure.rs b/crates/goose-cli/src/commands/configure.rs index b906fde6cd3e..8b16cd46fa61 100644 --- a/crates/goose-cli/src/commands/configure.rs +++ b/crates/goose-cli/src/commands/configure.rs @@ -2027,6 +2027,7 @@ fn add_provider() -> anyhow::Result<()> { headers, requires_auth, catalog_provider_id: None, + base_path: None, })?; cliclack::outro(format!("Custom provider added: {}", display_name))?; diff --git a/crates/goose-server/src/routes/config_management.rs b/crates/goose-server/src/routes/config_management.rs index 6063b40e37e8..60cdff93d9a0 100644 --- a/crates/goose-server/src/routes/config_management.rs +++ b/crates/goose-server/src/routes/config_management.rs @@ -100,6 +100,8 @@ pub struct UpdateCustomProviderRequest { pub requires_auth: bool, #[serde(default)] pub catalog_provider_id: Option, + #[serde(default)] + pub base_path: Option, } fn default_requires_auth() -> bool { @@ -655,6 +657,7 @@ pub async fn create_custom_provider( headers: request.headers, requires_auth: request.requires_auth, catalog_provider_id: request.catalog_provider_id, + base_path: request.base_path, }, )?; @@ -726,6 +729,7 @@ pub async fn update_custom_provider( headers: request.headers, requires_auth: request.requires_auth, catalog_provider_id: request.catalog_provider_id, + base_path: request.base_path, }, )?; diff --git a/crates/goose/src/config/declarative_providers.rs b/crates/goose/src/config/declarative_providers.rs index 340a5894454e..7634278deb2c 100644 --- a/crates/goose/src/config/declarative_providers.rs +++ b/crates/goose/src/config/declarative_providers.rs @@ -44,6 +44,8 @@ pub struct DeclarativeProviderConfig { pub requires_auth: bool, #[serde(default)] pub catalog_provider_id: Option, + #[serde(default)] + pub base_path: Option, } fn default_requires_auth() -> bool { @@ -105,6 +107,7 @@ pub struct CreateCustomProviderParams { pub headers: Option>, pub requires_auth: bool, pub catalog_provider_id: Option, + pub base_path: Option, } #[derive(Debug, Clone)] @@ -119,6 +122,7 @@ pub struct UpdateCustomProviderParams { pub headers: Option>, pub requires_auth: bool, pub catalog_provider_id: Option, + pub base_path: Option, } pub fn create_custom_provider( @@ -159,6 +163,7 @@ pub fn create_custom_provider( supports_streaming: params.supports_streaming, requires_auth: params.requires_auth, catalog_provider_id: params.catalog_provider_id, + base_path: params.base_path, }; let custom_providers_dir = custom_providers_dir(); @@ -221,6 +226,7 @@ pub fn update_custom_provider(params: UpdateCustomProviderParams) -> Result<()> supports_streaming: params.supports_streaming, requires_auth: params.requires_auth, catalog_provider_id: params.catalog_provider_id, + base_path: params.base_path, }; let file_path = custom_providers_dir().join(format!("{}.json", updated_config.name)); diff --git a/crates/goose/src/providers/openai.rs b/crates/goose/src/providers/openai.rs index 480fd6cf6008..67a013de2e40 100644 --- a/crates/goose/src/providers/openai.rs +++ b/crates/goose/src/providers/openai.rs @@ -168,11 +168,15 @@ impl OpenAiProvider { } else { format!("{}://{}", url.scheme(), url.host_str().unwrap_or("")) }; - let base_path = url.path().trim_start_matches('/').to_string(); - let base_path = if base_path.is_empty() || base_path == "v1" || base_path == "v1/" { - "v1/chat/completions".to_string() + let base_path = if let Some(ref explicit_path) = config.base_path { + explicit_path.trim_start_matches('/').to_string() } else { - base_path + let url_path = url.path().trim_start_matches('/').to_string(); + if url_path.is_empty() || url_path == "v1" || url_path == "v1/" { + "v1/chat/completions".to_string() + } else { + url_path + } }; let timeout_secs = config.timeout_seconds.unwrap_or(600); diff --git a/ui/desktop/openapi.json b/ui/desktop/openapi.json index 35921d8cc91d..a7954b6ff860 100644 --- a/ui/desktop/openapi.json +++ b/ui/desktop/openapi.json @@ -4177,6 +4177,10 @@ "api_key_env": { "type": "string" }, + "base_path": { + "type": "string", + "nullable": true + }, "base_url": { "type": "string" }, @@ -8159,6 +8163,10 @@ "api_url": { "type": "string" }, + "base_path": { + "type": "string", + "nullable": true + }, "catalog_provider_id": { "type": "string", "nullable": true diff --git a/ui/desktop/src/api/types.gen.ts b/ui/desktop/src/api/types.gen.ts index 2ccd506d8b4e..a61b65691638 100644 --- a/ui/desktop/src/api/types.gen.ts +++ b/ui/desktop/src/api/types.gen.ts @@ -173,6 +173,7 @@ export type CspMetadata = { export type DeclarativeProviderConfig = { api_key_env?: string; + base_path?: string | null; base_url: string; catalog_provider_id?: string | null; description?: string | null; @@ -1477,6 +1478,7 @@ export type UiMetadata = { export type UpdateCustomProviderRequest = { api_key: string; api_url: string; + base_path?: string | null; catalog_provider_id?: string | null; display_name: string; engine: string;