diff --git a/crates/goose-cli/src/commands/configure.rs b/crates/goose-cli/src/commands/configure.rs index 8b16cd46fa..122b143eb6 100644 --- a/crates/goose-cli/src/commands/configure.rs +++ b/crates/goose-cli/src/commands/configure.rs @@ -2015,6 +2015,17 @@ fn add_provider() -> anyhow::Result<()> { .initial_value(true) .interact()?; + let base_path_input: String = cliclack::input("API base path (optional, press Enter to skip):") + .placeholder("e.g., v1/chat/completions or project_id/v1") + .required(false) + .interact()?; + + let base_path = if base_path_input.trim().is_empty() { + None + } else { + Some(base_path_input) + }; + let headers = collect_custom_headers()?; create_custom_provider(CreateCustomProviderParams { @@ -2027,7 +2038,7 @@ fn add_provider() -> anyhow::Result<()> { headers, requires_auth, catalog_provider_id: None, - base_path: None, + base_path, })?; cliclack::outro(format!("Custom provider added: {}", display_name))?; diff --git a/ui/desktop/src/components/settings/providers/ProviderGrid.tsx b/ui/desktop/src/components/settings/providers/ProviderGrid.tsx index 834d0bfee1..ea4e5f4977 100644 --- a/ui/desktop/src/components/settings/providers/ProviderGrid.tsx +++ b/ui/desktop/src/components/settings/providers/ProviderGrid.tsx @@ -236,6 +236,7 @@ function ProviderCards({ engine: editingProvider.config.engine, display_name: editingProvider.config.display_name, api_url: editingProvider.config.base_url, + base_path: editingProvider.config.base_path ?? undefined, api_key: '', models: editingProvider.config.models.map((m) => m.name), supports_streaming: editingProvider.config.supports_streaming ?? true, diff --git a/ui/desktop/src/components/settings/providers/modal/subcomponents/forms/CustomProviderForm.tsx b/ui/desktop/src/components/settings/providers/modal/subcomponents/forms/CustomProviderForm.tsx index 7a9337654a..eda04e16cd 100644 --- a/ui/desktop/src/components/settings/providers/modal/subcomponents/forms/CustomProviderForm.tsx +++ b/ui/desktop/src/components/settings/providers/modal/subcomponents/forms/CustomProviderForm.tsx @@ -30,6 +30,7 @@ export default function CustomProviderForm({ const [engine, setEngine] = useState('openai_compatible'); const [displayName, setDisplayName] = useState(''); const [apiUrl, setApiUrl] = useState(''); + const [basePath, setBasePath] = useState(''); const [apiKey, setApiKey] = useState(''); const [models, setModels] = useState(''); const [requiresAuth, setRequiresAuth] = useState(false); @@ -59,6 +60,7 @@ export default function CustomProviderForm({ setEngine(engineMap[initialData.engine] || 'openai_compatible'); setDisplayName(initialData.display_name); setApiUrl(initialData.api_url); + setBasePath(initialData.base_path ?? ''); setModels(initialData.models.join(', ')); setSupportsStreaming(initialData.supports_streaming ?? true); setRequiresAuth(initialData.requires_auth ?? true); @@ -81,6 +83,7 @@ export default function CustomProviderForm({ // Prefill fields from template setDisplayName(template.name); setApiUrl(template.api_url); + setBasePath(''); setSupportsStreaming(template.supports_streaming); setRequiresAuth(true); @@ -101,6 +104,7 @@ export default function CustomProviderForm({ setSelectedTemplate(null); setDisplayName(''); setApiUrl(''); + setBasePath(''); setModels(''); setEngine('openai_compatible'); setSupportsStreaming(true); @@ -233,6 +237,7 @@ export default function CustomProviderForm({ requires_auth: requiresAuth, headers: headersObject, catalog_provider_id: selectedTemplate?.id ?? initialData?.catalog_provider_id ?? undefined, + base_path: basePath || undefined, }); }; @@ -438,7 +443,7 @@ export default function CustomProviderForm({ id="api-url" value={apiUrl} onChange={(e) => setApiUrl(e.target.value)} - placeholder="https://api.example.com/v1" + placeholder="https://api.example.com" aria-invalid={!!validationErrors.apiUrl} aria-describedby={validationErrors.apiUrl ? 'api-url-error' : undefined} className={validationErrors.apiUrl ? 'border-red-500' : ''} @@ -451,6 +456,27 @@ export default function CustomProviderForm({ )} + {/* Base Path */} + {isEditable && ( +
+ + setBasePath(e.target.value)} + placeholder="e.g., v1/chat/completions or project_id/v1" + /> +

+ Override the default API path. Leave blank to use the provider's default path. +

+
+ )} + {/* Authentication */}