Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
0ed2765
feat: Add backend auto-detection API endpoint
spencrmartin Oct 30, 2025
456acd9
feat: Integrate frontend with backend auto-detection
spencrmartin Oct 30, 2025
1f3f891
feat: Improve onboarding layout design
spencrmartin Oct 30, 2025
0d33d81
fix: Correct route to configure-providers page
spencrmartin Oct 30, 2025
4d3d73b
docs: Add integration documentation and update API client
spencrmartin Oct 30, 2025
9e1cca0
cleanup: Remove documentation and backup files
spencrmartin Oct 30, 2025
fe869a9
fix: Remove TypeScript any type in ApiKeyTester
spencrmartin Oct 30, 2025
c5cfcc9
fix: Apply Rust formatting (cargo fmt)
spencrmartin Oct 30, 2025
c9f2bee
feat: Improve API key validation with format-specific detection
spencrmartin Oct 30, 2025
efb23bc
fix: Prevent unwanted redirects during API key testing
spencrmartin Oct 30, 2025
77926c2
feat: Add backend support for disabling Ollama fallback
spencrmartin Oct 30, 2025
c8e1177
feat: Integrate parallel provider detection from PR #5147
spencrmartin Oct 30, 2025
e87dd52
feat: Add cloud-only provider detection to prevent Ollama fallback
spencrmartin Oct 30, 2025
4bf7f22
fix: Revert to existing endpoint with frontend Ollama filtering
spencrmartin Oct 30, 2025
fbbccd9
fix: Correct imports for cloud provider detection functions
spencrmartin Oct 30, 2025
397e7e3
fix: Add back missing Ollama rejection in Quick Setup
spencrmartin Oct 30, 2025
4d13e0e
feat: Implement hybrid detection - format detection + parallel fallback
spencrmartin Oct 30, 2025
c8b924c
feat: Add frontend format detection and improved error messages
spencrmartin Oct 30, 2025
fab8230
feat: Complete API key detection with smart provider workarounds
spencrmartin Oct 30, 2025
4341a8f
fix: Resolve React key uniqueness warning in ProgressiveMessageList
spencrmartin Oct 30, 2025
0fdc8cc
feat: Upgrade default model selection to Claude 4 (Opus)
spencrmartin Oct 30, 2025
ca67221
fix: Update to correct Claude Sonnet 4 model name
spencrmartin Oct 30, 2025
fe892bc
feat: Add OpenAI support with GPT-4.1 default model
spencrmartin Oct 30, 2025
9a9549f
integrate querying provider
zanesq Dec 2, 2025
d3173be
Merge branch 'main' of github.com:block/goose into integration/pr-495…
zanesq Dec 2, 2025
8dc17e3
clippy
zanesq Dec 2, 2025
4ed4e3d
cleanup
zanesq Dec 2, 2025
414329c
remove temporal service binary
zanesq Dec 2, 2025
6da97e4
cleanup
zanesq Dec 2, 2025
a682e40
relaxed front end validation
zanesq Dec 2, 2025
5a52644
Merge branch 'main' into zane/onboarding-detect-provider
michaelneale Dec 5, 2025
adc4aea
change to only check provider via api
zanesq Dec 5, 2025
7f2166e
change to only check provider via api and various feedback
zanesq Dec 5, 2025
ec8c8e6
fix onboarding for provider settings page
zanesq Dec 5, 2025
4d6d6a8
add delay before routing to home after success
zanesq Dec 5, 2025
ab6cda4
Update text
zanesq Dec 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions crates/goose-server/src/openapi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,7 @@ derive_utoipa!(Icon as IconSchema);
super::routes::status::diagnostics,
super::routes::mcp_ui_proxy::mcp_ui_proxy,
super::routes::config_management::backup_config,
super::routes::config_management::detect_provider,
super::routes::config_management::recover_config,
super::routes::config_management::validate_config,
super::routes::config_management::init_config,
Expand Down Expand Up @@ -398,6 +399,8 @@ derive_utoipa!(Icon as IconSchema);
components(schemas(
super::routes::config_management::UpsertConfigQuery,
super::routes::config_management::ConfigKeyQuery,
super::routes::config_management::DetectProviderRequest,
super::routes::config_management::DetectProviderResponse,
super::routes::config_management::ConfigResponse,
super::routes::config_management::ProvidersResponse,
super::routes::config_management::ProviderDetails,
Expand Down
36 changes: 35 additions & 1 deletion crates/goose-server/src/routes/config_management.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use goose::config::paths::Paths;
use goose::config::ExtensionEntry;
use goose::config::{Config, ConfigError};
use goose::model::ModelConfig;
use goose::providers::auto_detect::detect_provider_from_api_key;
use goose::providers::base::{ProviderMetadata, ProviderType};
use goose::providers::create_with_default_model;
use goose::providers::pricing::{
Expand Down Expand Up @@ -131,6 +132,16 @@ pub struct SlashCommandsResponse {
pub commands: Vec<SlashCommand>,
}

#[derive(Deserialize, ToSchema)]
pub struct DetectProviderRequest {
pub api_key: String,
}

#[derive(Serialize, ToSchema)]
pub struct DetectProviderResponse {
pub provider_name: String,
pub models: Vec<String>,
}
#[utoipa::path(
post,
path = "/config/upsert",
Expand Down Expand Up @@ -596,6 +607,29 @@ pub async fn upsert_permissions(
Ok(Json("Permissions updated successfully".to_string()))
}

#[utoipa::path(
post,
path = "/config/detect-provider",
request_body = DetectProviderRequest,
responses(
(status = 200, description = "Provider detected successfully", body = DetectProviderResponse),
(status = 404, description = "No matching provider found"),
)
)]
pub async fn detect_provider(
Json(detect_request): Json<DetectProviderRequest>,
) -> Result<Json<DetectProviderResponse>, StatusCode> {
let api_key = detect_request.api_key.trim();

match detect_provider_from_api_key(api_key).await {
Some((provider_name, models)) => Ok(Json(DetectProviderResponse {
provider_name,
models,
})),
None => Err(StatusCode::NOT_FOUND),
}
}

#[utoipa::path(
post,
path = "/config/backup",
Expand Down Expand Up @@ -686,7 +720,6 @@ pub async fn validate_config() -> Result<Json<String>, StatusCode> {
}
}
}

#[utoipa::path(
post,
path = "/config/custom-providers",
Expand Down Expand Up @@ -834,6 +867,7 @@ pub fn routes(state: Arc<AppState>) -> Router {
.route("/config/extensions/{name}", delete(remove_extension))
.route("/config/providers", get(providers))
.route("/config/providers/{name}/models", get(get_provider_models))
.route("/config/detect-provider", post(detect_provider))
.route("/config/slash_commands", get(get_slash_commands))
.route("/config/pricing", post(get_pricing))
.route("/config/init", post(init_config))
Expand Down
51 changes: 51 additions & 0 deletions crates/goose/src/providers/auto_detect.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use crate::model::ModelConfig;

pub async fn detect_provider_from_api_key(api_key: &str) -> Option<(String, Vec<String>)> {
let provider_tests = vec![
("anthropic", "ANTHROPIC_API_KEY"),
("openai", "OPENAI_API_KEY"),
("google", "GOOGLE_API_KEY"),
("groq", "GROQ_API_KEY"),
("xai", "XAI_API_KEY"),
// Ollama and OpenRouter don't validate keys, so they would match any input
];

let tasks: Vec<_> = provider_tests
.into_iter()
.map(|(provider_name, env_key)| {
let api_key = api_key.to_string();
tokio::spawn(async move {
let original_value = std::env::var(env_key).ok();
std::env::set_var(env_key, &api_key);

let result = match crate::providers::create(
provider_name,
ModelConfig::new_or_fail("default"),
)
.await
{
Ok(provider) => match provider.fetch_supported_models().await {
Ok(Some(models)) => Some((provider_name.to_string(), models)),
_ => None,
},
Err(_) => None,
};

match original_value {
Some(val) => std::env::set_var(env_key, val),
None => std::env::remove_var(env_key),
}

result
})
})
.collect();

for task in tasks {
if let Ok(Some(result)) = task.await {
return Some(result);
}
}

None
}
1 change: 1 addition & 0 deletions crates/goose/src/providers/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod anthropic;
pub mod api_client;
pub mod auto_detect;
pub mod azure;
pub mod azureauth;
pub mod base;
Expand Down
62 changes: 62 additions & 0 deletions ui/desktop/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,39 @@
}
}
},
"/config/detect-provider": {
"post": {
"tags": [
"super::routes::config_management"
],
"operationId": "detect_provider",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/DetectProviderRequest"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "Provider detected successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/DetectProviderResponse"
}
}
}
},
"404": {
"description": "No matching provider found"
}
}
}
},
"/config/extensions": {
"get": {
"tags": [
Expand Down Expand Up @@ -2774,6 +2807,35 @@
}
}
},
"DetectProviderRequest": {
"type": "object",
"required": [
"api_key"
],
"properties": {
"api_key": {
"type": "string"
}
}
},
"DetectProviderResponse": {
"type": "object",
"required": [
"provider_name",
"models"
],
"properties": {
"models": {
"type": "array",
"items": {
"type": "string"
}
},
"provider_name": {
"type": "string"
}
}
},
"EditMessageRequest": {
"type": "object",
"required": [
Expand Down
17 changes: 11 additions & 6 deletions ui/desktop/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback, useEffect, useState } from 'react';
import { useEffect, useState } from 'react';
import { IpcRendererEvent } from 'electron';
import {
HashRouter,
Expand Down Expand Up @@ -255,14 +255,19 @@ interface WelcomeRouteProps {

const WelcomeRoute = ({ onSelectProvider }: WelcomeRouteProps) => {
const navigate = useNavigate();
const onClose = useCallback(() => {
onSelectProvider();
navigate('/');
}, [navigate, onSelectProvider]);

return (
<div className="w-screen h-screen bg-background-default">
<ProviderSettings onClose={onClose} isOnboarding={true} />
<ProviderSettings
onClose={() => {
navigate('/', { replace: true });
}}
isOnboarding={true}
onProviderLaunched={() => {
onSelectProvider();
navigate('/', { replace: true });
}}
/>
</div>
);
};
Expand Down
11 changes: 10 additions & 1 deletion ui/desktop/src/api/sdk.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import type { Client, Options as Options2, TDataShape } from './client';
import { client } from './client.gen';
import type { AddExtensionData, AddExtensionErrors, AddExtensionResponses, AgentAddExtensionData, AgentAddExtensionErrors, AgentAddExtensionResponses, AgentRemoveExtensionData, AgentRemoveExtensionErrors, AgentRemoveExtensionResponses, BackupConfigData, BackupConfigErrors, BackupConfigResponses, CheckProviderData, ConfirmToolActionData, ConfirmToolActionErrors, ConfirmToolActionResponses, CreateCustomProviderData, CreateCustomProviderErrors, CreateCustomProviderResponses, CreateRecipeData, CreateRecipeErrors, CreateRecipeResponses, CreateScheduleData, CreateScheduleErrors, CreateScheduleResponses, DecodeRecipeData, DecodeRecipeErrors, DecodeRecipeResponses, DeleteRecipeData, DeleteRecipeErrors, DeleteRecipeResponses, DeleteScheduleData, DeleteScheduleErrors, DeleteScheduleResponses, DeleteSessionData, DeleteSessionErrors, DeleteSessionResponses, DiagnosticsData, DiagnosticsErrors, DiagnosticsResponses, EditMessageData, EditMessageErrors, EditMessageResponses, EncodeRecipeData, EncodeRecipeErrors, EncodeRecipeResponses, ExportSessionData, ExportSessionErrors, ExportSessionResponses, GetCustomProviderData, GetCustomProviderErrors, GetCustomProviderResponses, GetExtensionsData, GetExtensionsErrors, GetExtensionsResponses, GetProviderModelsData, GetProviderModelsErrors, GetProviderModelsResponses, GetSessionData, GetSessionErrors, GetSessionInsightsData, GetSessionInsightsErrors, GetSessionInsightsResponses, GetSessionResponses, GetSlashCommandsData, GetSlashCommandsResponses, GetToolsData, GetToolsErrors, GetToolsResponses, GetTunnelStatusData, GetTunnelStatusResponses, ImportSessionData, ImportSessionErrors, ImportSessionResponses, InitConfigData, InitConfigErrors, InitConfigResponses, InspectRunningJobData, InspectRunningJobErrors, InspectRunningJobResponses, KillRunningJobData, KillRunningJobResponses, ListRecipesData, ListRecipesErrors, ListRecipesResponses, ListSchedulesData, ListSchedulesErrors, ListSchedulesResponses, ListSessionsData, ListSessionsErrors, ListSessionsResponses, McpUiProxyData, McpUiProxyErrors, McpUiProxyResponses, ParseRecipeData, ParseRecipeErrors, ParseRecipeResponses, PauseScheduleData, PauseScheduleErrors, PauseScheduleResponses, ProvidersData, ProvidersResponses, ReadAllConfigData, ReadAllConfigResponses, ReadConfigData, ReadConfigErrors, ReadConfigResponses, RecoverConfigData, RecoverConfigErrors, RecoverConfigResponses, RemoveConfigData, RemoveConfigErrors, RemoveConfigResponses, RemoveCustomProviderData, RemoveCustomProviderErrors, RemoveCustomProviderResponses, RemoveExtensionData, RemoveExtensionErrors, RemoveExtensionResponses, ReplyData, ReplyErrors, ReplyResponses, ResumeAgentData, ResumeAgentErrors, ResumeAgentResponses, RunNowHandlerData, RunNowHandlerErrors, RunNowHandlerResponses, SaveRecipeData, SaveRecipeErrors, SaveRecipeResponses, ScanRecipeData, ScanRecipeResponses, ScheduleRecipeData, ScheduleRecipeErrors, ScheduleRecipeResponses, SessionsHandlerData, SessionsHandlerErrors, SessionsHandlerResponses, SetConfigProviderData, SetRecipeSlashCommandData, SetRecipeSlashCommandErrors, SetRecipeSlashCommandResponses, StartAgentData, StartAgentErrors, StartAgentResponses, StartOpenrouterSetupData, StartOpenrouterSetupResponses, StartTetrateSetupData, StartTetrateSetupResponses, StartTunnelData, StartTunnelErrors, StartTunnelResponses, StatusData, StatusResponses, StopTunnelData, StopTunnelErrors, StopTunnelResponses, UnpauseScheduleData, UnpauseScheduleErrors, UnpauseScheduleResponses, UpdateAgentProviderData, UpdateAgentProviderErrors, UpdateAgentProviderResponses, UpdateCustomProviderData, UpdateCustomProviderErrors, UpdateCustomProviderResponses, UpdateFromSessionData, UpdateFromSessionErrors, UpdateFromSessionResponses, UpdateRouterToolSelectorData, UpdateRouterToolSelectorErrors, UpdateRouterToolSelectorResponses, UpdateScheduleData, UpdateScheduleErrors, UpdateScheduleResponses, UpdateSessionNameData, UpdateSessionNameErrors, UpdateSessionNameResponses, UpdateSessionUserRecipeValuesData, UpdateSessionUserRecipeValuesErrors, UpdateSessionUserRecipeValuesResponses, UpsertConfigData, UpsertConfigErrors, UpsertConfigResponses, UpsertPermissionsData, UpsertPermissionsErrors, UpsertPermissionsResponses, ValidateConfigData, ValidateConfigErrors, ValidateConfigResponses } from './types.gen';
import type { AddExtensionData, AddExtensionErrors, AddExtensionResponses, AgentAddExtensionData, AgentAddExtensionErrors, AgentAddExtensionResponses, AgentRemoveExtensionData, AgentRemoveExtensionErrors, AgentRemoveExtensionResponses, BackupConfigData, BackupConfigErrors, BackupConfigResponses, CheckProviderData, ConfirmToolActionData, ConfirmToolActionErrors, ConfirmToolActionResponses, CreateCustomProviderData, CreateCustomProviderErrors, CreateCustomProviderResponses, CreateRecipeData, CreateRecipeErrors, CreateRecipeResponses, CreateScheduleData, CreateScheduleErrors, CreateScheduleResponses, DecodeRecipeData, DecodeRecipeErrors, DecodeRecipeResponses, DeleteRecipeData, DeleteRecipeErrors, DeleteRecipeResponses, DeleteScheduleData, DeleteScheduleErrors, DeleteScheduleResponses, DeleteSessionData, DeleteSessionErrors, DeleteSessionResponses, DetectProviderData, DetectProviderErrors, DetectProviderResponses, DiagnosticsData, DiagnosticsErrors, DiagnosticsResponses, EditMessageData, EditMessageErrors, EditMessageResponses, EncodeRecipeData, EncodeRecipeErrors, EncodeRecipeResponses, ExportSessionData, ExportSessionErrors, ExportSessionResponses, GetCustomProviderData, GetCustomProviderErrors, GetCustomProviderResponses, GetExtensionsData, GetExtensionsErrors, GetExtensionsResponses, GetProviderModelsData, GetProviderModelsErrors, GetProviderModelsResponses, GetSessionData, GetSessionErrors, GetSessionInsightsData, GetSessionInsightsErrors, GetSessionInsightsResponses, GetSessionResponses, GetSlashCommandsData, GetSlashCommandsResponses, GetToolsData, GetToolsErrors, GetToolsResponses, GetTunnelStatusData, GetTunnelStatusResponses, ImportSessionData, ImportSessionErrors, ImportSessionResponses, InitConfigData, InitConfigErrors, InitConfigResponses, InspectRunningJobData, InspectRunningJobErrors, InspectRunningJobResponses, KillRunningJobData, KillRunningJobResponses, ListRecipesData, ListRecipesErrors, ListRecipesResponses, ListSchedulesData, ListSchedulesErrors, ListSchedulesResponses, ListSessionsData, ListSessionsErrors, ListSessionsResponses, McpUiProxyData, McpUiProxyErrors, McpUiProxyResponses, ParseRecipeData, ParseRecipeErrors, ParseRecipeResponses, PauseScheduleData, PauseScheduleErrors, PauseScheduleResponses, ProvidersData, ProvidersResponses, ReadAllConfigData, ReadAllConfigResponses, ReadConfigData, ReadConfigErrors, ReadConfigResponses, RecoverConfigData, RecoverConfigErrors, RecoverConfigResponses, RemoveConfigData, RemoveConfigErrors, RemoveConfigResponses, RemoveCustomProviderData, RemoveCustomProviderErrors, RemoveCustomProviderResponses, RemoveExtensionData, RemoveExtensionErrors, RemoveExtensionResponses, ReplyData, ReplyErrors, ReplyResponses, ResumeAgentData, ResumeAgentErrors, ResumeAgentResponses, RunNowHandlerData, RunNowHandlerErrors, RunNowHandlerResponses, SaveRecipeData, SaveRecipeErrors, SaveRecipeResponses, ScanRecipeData, ScanRecipeResponses, ScheduleRecipeData, ScheduleRecipeErrors, ScheduleRecipeResponses, SessionsHandlerData, SessionsHandlerErrors, SessionsHandlerResponses, SetConfigProviderData, SetRecipeSlashCommandData, SetRecipeSlashCommandErrors, SetRecipeSlashCommandResponses, StartAgentData, StartAgentErrors, StartAgentResponses, StartOpenrouterSetupData, StartOpenrouterSetupResponses, StartTetrateSetupData, StartTetrateSetupResponses, StartTunnelData, StartTunnelErrors, StartTunnelResponses, StatusData, StatusResponses, StopTunnelData, StopTunnelErrors, StopTunnelResponses, UnpauseScheduleData, UnpauseScheduleErrors, UnpauseScheduleResponses, UpdateAgentProviderData, UpdateAgentProviderErrors, UpdateAgentProviderResponses, UpdateCustomProviderData, UpdateCustomProviderErrors, UpdateCustomProviderResponses, UpdateFromSessionData, UpdateFromSessionErrors, UpdateFromSessionResponses, UpdateRouterToolSelectorData, UpdateRouterToolSelectorErrors, UpdateRouterToolSelectorResponses, UpdateScheduleData, UpdateScheduleErrors, UpdateScheduleResponses, UpdateSessionNameData, UpdateSessionNameErrors, UpdateSessionNameResponses, UpdateSessionUserRecipeValuesData, UpdateSessionUserRecipeValuesErrors, UpdateSessionUserRecipeValuesResponses, UpsertConfigData, UpsertConfigErrors, UpsertConfigResponses, UpsertPermissionsData, UpsertPermissionsErrors, UpsertPermissionsResponses, ValidateConfigData, ValidateConfigErrors, ValidateConfigResponses } from './types.gen';

export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = Options2<TData, ThrowOnError> & {
/**
Expand Down Expand Up @@ -127,6 +127,15 @@ export const updateCustomProvider = <ThrowOnError extends boolean = false>(optio
}
});

export const detectProvider = <ThrowOnError extends boolean = false>(options: Options<DetectProviderData, ThrowOnError>) => (options.client ?? client).post<DetectProviderResponses, DetectProviderErrors, ThrowOnError>({
url: '/config/detect-provider',
...options,
headers: {
'Content-Type': 'application/json',
...options.headers
}
});

export const getExtensions = <ThrowOnError extends boolean = false>(options?: Options<GetExtensionsData, ThrowOnError>) => (options?.client ?? client).get<GetExtensionsResponses, GetExtensionsErrors, ThrowOnError>({ url: '/config/extensions', ...options });

export const addExtension = <ThrowOnError extends boolean = false>(options: Options<AddExtensionData, ThrowOnError>) => (options.client ?? client).post<AddExtensionResponses, AddExtensionErrors, ThrowOnError>({
Expand Down
32 changes: 32 additions & 0 deletions ui/desktop/src/api/types.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,15 @@ export type DeleteRecipeRequest = {
id: string;
};

export type DetectProviderRequest = {
api_key: string;
};

export type DetectProviderResponse = {
models: Array<string>;
provider_name: string;
};

export type EditMessageRequest = {
editType?: EditType;
timestamp: number;
Expand Down Expand Up @@ -1444,6 +1453,29 @@ export type UpdateCustomProviderResponses = {

export type UpdateCustomProviderResponse = UpdateCustomProviderResponses[keyof UpdateCustomProviderResponses];

export type DetectProviderData = {
body: DetectProviderRequest;
path?: never;
query?: never;
url: '/config/detect-provider';
};

export type DetectProviderErrors = {
/**
* No matching provider found
*/
404: unknown;
};

export type DetectProviderResponses = {
/**
* Provider detected successfully
*/
200: DetectProviderResponse;
};

export type DetectProviderResponse2 = DetectProviderResponses[keyof DetectProviderResponses];

export type GetExtensionsData = {
body?: never;
path?: never;
Expand Down
Loading
Loading