From 2219d8a347447c0e6ca490c26a2af5c5989d597a Mon Sep 17 00:00:00 2001 From: Will Pfleger Date: Wed, 13 May 2026 19:03:59 -0400 Subject: [PATCH 1/4] feat: add typed config endpoints with dual schema generation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Single GooseConfigSchema struct derives both utoipa::ToSchema (for OpenAPI → TypeScript type safety) and schemars::JsonSchema (for standalone config.schema.json consumed by downstream tools). Adds GET/PATCH /config/typed endpoints alongside existing per-key endpoints. GooseConfigUpdate struct includes secret fields routed through the keyring. Existing UI and endpoints are unchanged. Signed-off-by: Will Pfleger --- crates/goose-server/src/openapi.rs | 5 + .../src/routes/config_management.rs | 37 + crates/goose/Cargo.toml | 4 + crates/goose/config.schema.json | 1430 +++++++++++++++++ crates/goose/src/agents/extension.rs | 5 +- .../goose/src/bin/generate_config_schema.rs | 28 + crates/goose/src/config/extensions.rs | 3 +- crates/goose/src/config/goose_mode.rs | 2 + crates/goose/src/config/mod.rs | 2 + crates/goose/src/config/schema.rs | 1359 ++++++++++++++++ crates/goose/src/slash_commands.rs | 4 +- ui/desktop/openapi.json | 1208 ++++++++++++++ ui/desktop/src/api/client.gen.ts | 2 +- ui/desktop/src/api/client/types.gen.ts | 2 +- ui/desktop/src/api/core/params.gen.ts | 2 +- ui/desktop/src/api/index.ts | 4 +- ui/desktop/src/api/sdk.gen.ts | 13 +- ui/desktop/src/api/types.gen.ts | 322 ++++ 18 files changed, 4422 insertions(+), 10 deletions(-) create mode 100644 crates/goose/config.schema.json create mode 100644 crates/goose/src/bin/generate_config_schema.rs create mode 100644 crates/goose/src/config/schema.rs diff --git a/crates/goose-server/src/openapi.rs b/crates/goose-server/src/openapi.rs index ae33e32b69..5301562678 100644 --- a/crates/goose-server/src/openapi.rs +++ b/crates/goose-server/src/openapi.rs @@ -391,6 +391,8 @@ derive_utoipa!(IconTheme as IconThemeSchema); super::routes::config_management::upsert_config, super::routes::config_management::remove_config, super::routes::config_management::read_config, + super::routes::config_management::read_typed_config, + super::routes::config_management::patch_typed_config, super::routes::config_management::add_extension, super::routes::config_management::remove_extension, super::routes::config_management::get_extensions, @@ -484,6 +486,9 @@ derive_utoipa!(IconTheme as IconThemeSchema); super::routes::config_management::UpsertConfigQuery, super::routes::config_management::ConfigKeyQuery, super::routes::config_management::ConfigResponse, + goose::config::schema::GooseConfigSchema, + goose::config::schema::GooseConfigUpdate, + goose::slash_commands::SlashCommandMapping, super::routes::config_management::ProvidersResponse, super::routes::config_management::ProviderDetails, super::routes::config_management::SlashCommandsResponse, diff --git a/crates/goose-server/src/routes/config_management.rs b/crates/goose-server/src/routes/config_management.rs index 1f93f5e298..7322b170d5 100644 --- a/crates/goose-server/src/routes/config_management.rs +++ b/crates/goose-server/src/routes/config_management.rs @@ -9,6 +9,7 @@ use axum::{ }; use goose::config::declarative_providers::LoadedProvider; use goose::config::paths::Paths; +use goose::config::schema::{GooseConfigSchema, GooseConfigUpdate}; use goose::config::ExtensionEntry; use goose::config::{Config, ConfigError}; use goose::custom_requests::SourceType; @@ -842,9 +843,45 @@ pub async fn configure_provider_oauth( Ok(Json("OAuth configuration completed".to_string())) } +#[utoipa::path( + get, + path = "/config/typed", + responses( + (status = 200, description = "All configuration values (typed)", body = GooseConfigSchema), + (status = 500, description = "Internal server error") + ) +)] +pub async fn read_typed_config() -> Result, ErrorResponse> { + let config = Config::global(); + let typed = GooseConfigSchema::from_config(config); + Ok(Json(typed)) +} + +#[utoipa::path( + patch, + path = "/config/typed", + request_body = GooseConfigUpdate, + responses( + (status = 200, description = "Configuration updated", body = GooseConfigSchema), + (status = 500, description = "Internal server error") + ) +)] +pub async fn patch_typed_config( + Json(update): Json, +) -> Result, ErrorResponse> { + let config = Config::global(); + update.apply_to_config(config)?; + let typed = GooseConfigSchema::from_config(config); + Ok(Json(typed)) +} + pub fn routes(state: Arc) -> Router { Router::new() .route("/config", get(read_all_config)) + .route( + "/config/typed", + get(read_typed_config).patch(patch_typed_config), + ) .route("/config/upsert", post(upsert_config)) .route("/config/remove", post(remove_config)) .route("/config/read", post(read_config)) diff --git a/crates/goose/Cargo.toml b/crates/goose/Cargo.toml index 3b49beced2..d6c55eeb03 100644 --- a/crates/goose/Cargo.toml +++ b/crates/goose/Cargo.toml @@ -259,6 +259,10 @@ path = "src/providers/canonical/build_canonical_models.rs" name = "generate-acp-schema" path = "src/bin/generate_acp_schema.rs" +[[bin]] +name = "generate-config-schema" +path = "src/bin/generate_config_schema.rs" + [package.metadata.cargo-machete] ignored = [ diff --git a/crates/goose/config.schema.json b/crates/goose/config.schema.json new file mode 100644 index 0000000000..7d89411e93 --- /dev/null +++ b/crates/goose/config.schema.json @@ -0,0 +1,1430 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "GooseConfigSchema", + "description": "JSON Schema representation of Goose's config.yaml.\n\nAll keys are optional. Unknown keys are allowed (additionalProperties: true)\nbecause Goose passes undocumented provider-specific keys through as\nenvironment variable overrides.", + "type": "object", + "properties": { + "GOOSE_PROVIDER": { + "type": [ + "string", + "null" + ] + }, + "GOOSE_MODEL": { + "type": [ + "string", + "null" + ] + }, + "GOOSE_MODE": { + "anyOf": [ + { + "$ref": "#/$defs/GooseMode" + }, + { + "type": "null" + } + ] + }, + "GOOSE_MAX_TOKENS": { + "type": [ + "integer", + "null" + ], + "format": "int32" + }, + "GOOSE_CONTEXT_LIMIT": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0 + }, + "GOOSE_INPUT_LIMIT": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0 + }, + "GOOSE_MAX_TURNS": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0 + }, + "GOOSE_MAX_ACTIVE_AGENTS": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0 + }, + "GOOSE_AUTO_COMPACT_THRESHOLD": { + "type": [ + "number", + "null" + ], + "format": "double" + }, + "GOOSE_TOOL_PAIR_SUMMARIZATION": { + "type": [ + "boolean", + "null" + ] + }, + "GOOSE_TOOL_CALL_CUTOFF": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0 + }, + "GOOSE_STREAM_TIMEOUT": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0 + }, + "GOOSE_SEARCH_PATHS": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "GOOSE_DISABLE_SESSION_NAMING": { + "type": [ + "boolean", + "null" + ] + }, + "GOOSE_DISABLE_KEYRING": { + "type": [ + "string", + "null" + ] + }, + "GOOSE_TELEMETRY_ENABLED": { + "type": [ + "boolean", + "null" + ] + }, + "GOOSE_DEFAULT_EXTENSION_TIMEOUT": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0 + }, + "GOOSE_PROMPT_EDITOR": { + "type": [ + "string", + "null" + ] + }, + "GOOSE_PROMPT_EDITOR_ALWAYS": { + "type": [ + "boolean", + "null" + ] + }, + "GOOSE_ALLOWLIST": { + "type": [ + "string", + "null" + ] + }, + "GOOSE_SYSTEM_PROMPT_FILE_PATH": { + "type": [ + "string", + "null" + ] + }, + "GOOSE_DEBUG": { + "type": [ + "boolean", + "null" + ] + }, + "GOOSE_SHOW_FULL_OUTPUT": { + "type": [ + "boolean", + "null" + ] + }, + "GOOSE_STATUS_HOOK": { + "type": [ + "string", + "null" + ] + }, + "GOOSE_LOCAL_ENABLE_THINKING": { + "type": [ + "boolean", + "null" + ] + }, + "GOOSE_DATABRICKS_CLIENT_REQUEST_ID": { + "type": [ + "boolean", + "null" + ] + }, + "CONTEXT_FILE_NAMES": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "EDIT_MODE": { + "type": [ + "string", + "null" + ] + }, + "RANDOM_THINKING_MESSAGES": { + "type": [ + "boolean", + "null" + ] + }, + "CODE_MODE_TOOL_DISCLOSURE": { + "type": [ + "string", + "null" + ] + }, + "GOOSE_CLIENT_CERT_PATH": { + "type": [ + "string", + "null" + ] + }, + "GOOSE_CLIENT_KEY_PATH": { + "type": [ + "string", + "null" + ] + }, + "GOOSE_CA_CERT_PATH": { + "type": [ + "string", + "null" + ] + }, + "GOOSE_PLANNER_PROVIDER": { + "type": [ + "string", + "null" + ] + }, + "GOOSE_PLANNER_MODEL": { + "type": [ + "string", + "null" + ] + }, + "GOOSE_SUBAGENT_PROVIDER": { + "type": [ + "string", + "null" + ] + }, + "GOOSE_SUBAGENT_MODEL": { + "type": [ + "string", + "null" + ] + }, + "GOOSE_SUBAGENT_MAX_TURNS": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0 + }, + "GOOSE_MAX_BACKGROUND_TASKS": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0 + }, + "GOOSE_RECIPE_GITHUB_REPO": { + "type": [ + "string", + "null" + ] + }, + "GOOSE_RECIPE_RETRY_TIMEOUT_SECONDS": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0 + }, + "GOOSE_RECIPE_ON_FAILURE_TIMEOUT_SECONDS": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0 + }, + "GOOSE_CLI_MIN_PRIORITY": { + "type": [ + "number", + "null" + ], + "format": "float" + }, + "GOOSE_CLI_THEME": { + "type": [ + "string", + "null" + ] + }, + "GOOSE_CLI_LIGHT_THEME": { + "type": [ + "string", + "null" + ] + }, + "GOOSE_CLI_DARK_THEME": { + "type": [ + "string", + "null" + ] + }, + "GOOSE_CLI_SHOW_COST": { + "type": [ + "boolean", + "null" + ] + }, + "GOOSE_CLI_SHOW_THINKING": { + "type": [ + "boolean", + "null" + ] + }, + "GOOSE_CLI_NEWLINE_KEY": { + "type": [ + "string", + "null" + ] + }, + "CLAUDE_CODE_COMMAND": { + "type": [ + "string", + "null" + ] + }, + "GEMINI_CLI_COMMAND": { + "type": [ + "string", + "null" + ] + }, + "CURSOR_AGENT_COMMAND": { + "type": [ + "string", + "null" + ] + }, + "CODEX_COMMAND": { + "type": [ + "string", + "null" + ] + }, + "CODEX_REASONING_EFFORT": { + "type": [ + "string", + "null" + ] + }, + "CODEX_ENABLE_SKILLS": { + "type": [ + "string", + "null" + ] + }, + "CODEX_SKIP_GIT_CHECK": { + "type": [ + "string", + "null" + ] + }, + "CHATGPT_CODEX_REASONING_EFFORT": { + "type": [ + "string", + "null" + ] + }, + "CLAUDE_THINKING_TYPE": { + "type": [ + "string", + "null" + ] + }, + "CLAUDE_THINKING_EFFORT": { + "type": [ + "string", + "null" + ] + }, + "CLAUDE_THINKING_BUDGET": { + "type": [ + "integer", + "null" + ], + "format": "int32" + }, + "GEMINI3_THINKING_LEVEL": { + "type": [ + "string", + "null" + ] + }, + "GEMINI25_THINKING_BUDGET": { + "type": [ + "integer", + "null" + ], + "format": "int32" + }, + "SECURITY_PROMPT_ENABLED": { + "type": [ + "boolean", + "null" + ] + }, + "SECURITY_PROMPT_THRESHOLD": { + "type": [ + "number", + "null" + ], + "format": "double" + }, + "SECURITY_PROMPT_CLASSIFIER_ENABLED": { + "type": [ + "boolean", + "null" + ] + }, + "SECURITY_PROMPT_CLASSIFIER_MODEL": { + "type": [ + "string", + "null" + ] + }, + "SECURITY_PROMPT_CLASSIFIER_ENDPOINT": { + "type": [ + "string", + "null" + ] + }, + "SECURITY_COMMAND_CLASSIFIER_ENABLED": { + "type": [ + "boolean", + "null" + ] + }, + "OPENAI_HOST": { + "type": [ + "string", + "null" + ] + }, + "OPENAI_BASE_URL": { + "type": [ + "string", + "null" + ] + }, + "OPENAI_BASE_PATH": { + "type": [ + "string", + "null" + ] + }, + "OPENAI_ORGANIZATION": { + "type": [ + "string", + "null" + ] + }, + "OPENAI_PROJECT": { + "type": [ + "string", + "null" + ] + }, + "OPENAI_TIMEOUT": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0 + }, + "ANTHROPIC_HOST": { + "type": [ + "string", + "null" + ] + }, + "OLLAMA_HOST": { + "type": [ + "string", + "null" + ] + }, + "OLLAMA_TIMEOUT": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0 + }, + "OLLAMA_STREAM_TIMEOUT": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0 + }, + "OLLAMA_STREAM_USAGE": { + "type": [ + "boolean", + "null" + ] + }, + "DATABRICKS_HOST": { + "type": [ + "string", + "null" + ] + }, + "DATABRICKS_MAX_RETRIES": { + "type": [ + "string", + "null" + ] + }, + "DATABRICKS_INITIAL_RETRY_INTERVAL_MS": { + "type": [ + "string", + "null" + ] + }, + "DATABRICKS_BACKOFF_MULTIPLIER": { + "type": [ + "string", + "null" + ] + }, + "DATABRICKS_MAX_RETRY_INTERVAL_MS": { + "type": [ + "string", + "null" + ] + }, + "AZURE_OPENAI_ENDPOINT": { + "type": [ + "string", + "null" + ] + }, + "AZURE_OPENAI_DEPLOYMENT_NAME": { + "type": [ + "string", + "null" + ] + }, + "AZURE_OPENAI_API_VERSION": { + "type": [ + "string", + "null" + ] + }, + "GOOGLE_HOST": { + "type": [ + "string", + "null" + ] + }, + "GCP_PROJECT_ID": { + "type": [ + "string", + "null" + ] + }, + "GCP_LOCATION": { + "type": [ + "string", + "null" + ] + }, + "GCP_MAX_RETRIES": { + "type": [ + "string", + "null" + ] + }, + "GCP_INITIAL_RETRY_INTERVAL_MS": { + "type": [ + "string", + "null" + ] + }, + "GCP_BACKOFF_MULTIPLIER": { + "type": [ + "string", + "null" + ] + }, + "GCP_MAX_RETRY_INTERVAL_MS": { + "type": [ + "string", + "null" + ] + }, + "AWS_REGION": { + "type": [ + "string", + "null" + ] + }, + "AWS_PROFILE": { + "type": [ + "string", + "null" + ] + }, + "BEDROCK_MAX_RETRIES": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0 + }, + "BEDROCK_INITIAL_RETRY_INTERVAL_MS": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0 + }, + "BEDROCK_BACKOFF_MULTIPLIER": { + "type": [ + "number", + "null" + ], + "format": "double" + }, + "BEDROCK_MAX_RETRY_INTERVAL_MS": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0 + }, + "BEDROCK_ENABLE_CACHING": { + "type": [ + "boolean", + "null" + ] + }, + "SAGEMAKER_ENDPOINT_NAME": { + "type": [ + "string", + "null" + ] + }, + "LITELLM_HOST": { + "type": [ + "string", + "null" + ] + }, + "LITELLM_BASE_PATH": { + "type": [ + "string", + "null" + ] + }, + "LITELLM_TIMEOUT": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0 + }, + "SNOWFLAKE_HOST": { + "type": [ + "string", + "null" + ] + }, + "GITHUB_COPILOT_HOST": { + "type": [ + "string", + "null" + ] + }, + "GITHUB_COPILOT_CLIENT_ID": { + "type": [ + "string", + "null" + ] + }, + "GITHUB_COPILOT_TOKEN_URL": { + "type": [ + "string", + "null" + ] + }, + "XAI_HOST": { + "type": [ + "string", + "null" + ] + }, + "OPENROUTER_HOST": { + "type": [ + "string", + "null" + ] + }, + "VENICE_HOST": { + "type": [ + "string", + "null" + ] + }, + "VENICE_BASE_PATH": { + "type": [ + "string", + "null" + ] + }, + "VENICE_MODELS_PATH": { + "type": [ + "string", + "null" + ] + }, + "TETRATE_HOST": { + "type": [ + "string", + "null" + ] + }, + "AVIAN_HOST": { + "type": [ + "string", + "null" + ] + }, + "otel_exporter_otlp_endpoint": { + "type": [ + "string", + "null" + ] + }, + "otel_exporter_otlp_timeout": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0 + }, + "tunnel_auto_start": { + "type": [ + "boolean", + "null" + ] + }, + "extensions": { + "type": [ + "object", + "null" + ], + "additionalProperties": { + "$ref": "#/$defs/ExtensionEntry" + } + }, + "slash_commands": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/$defs/SlashCommandMapping" + } + }, + "experiments": { + "type": [ + "object", + "null" + ], + "additionalProperties": { + "type": "boolean" + } + } + }, + "$defs": { + "GooseMode": { + "type": "string", + "enum": [ + "auto", + "approve", + "smart_approve", + "chat" + ] + }, + "ExtensionEntry": { + "description": "Represents the different types of MCP extensions that can be added to the manager", + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + } + }, + "required": [ + "enabled" + ], + "oneOf": [ + { + "description": "SSE transport is no longer supported - kept only for config file compatibility", + "type": "object", + "properties": { + "name": { + "type": "string", + "default": "" + }, + "description": { + "type": "string", + "default": "" + }, + "uri": { + "type": [ + "string", + "null" + ], + "default": null + }, + "type": { + "type": "string", + "const": "sse" + } + }, + "required": [ + "type" + ] + }, + { + "description": "Standard I/O client with command and arguments", + "type": "object", + "properties": { + "name": { + "description": "The name used to identify this extension", + "type": "string" + }, + "description": { + "type": "string", + "default": "" + }, + "cmd": { + "type": "string" + }, + "args": { + "type": "array", + "items": { + "type": "string" + } + }, + "envs": { + "$ref": "#/$defs/Envs", + "default": {} + }, + "env_keys": { + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, + "timeout": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0 + }, + "bundled": { + "type": [ + "boolean", + "null" + ], + "default": null + }, + "available_tools": { + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, + "type": { + "type": "string", + "const": "stdio" + } + }, + "required": [ + "type", + "name", + "cmd", + "args" + ] + }, + { + "description": "Built-in extension that is part of the bundled goose MCP server", + "type": "object", + "properties": { + "name": { + "description": "The name used to identify this extension", + "type": "string" + }, + "description": { + "type": "string", + "default": "" + }, + "display_name": { + "type": [ + "string", + "null" + ] + }, + "timeout": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0 + }, + "bundled": { + "type": [ + "boolean", + "null" + ], + "default": null + }, + "available_tools": { + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, + "type": { + "type": "string", + "const": "builtin" + } + }, + "required": [ + "type", + "name" + ] + }, + { + "description": "Platform extensions that have direct access to the agent etc and run in the agent process", + "type": "object", + "properties": { + "name": { + "description": "The name used to identify this extension", + "type": "string" + }, + "description": { + "type": "string", + "default": "" + }, + "display_name": { + "type": [ + "string", + "null" + ] + }, + "bundled": { + "type": [ + "boolean", + "null" + ], + "default": null + }, + "available_tools": { + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, + "type": { + "type": "string", + "const": "platform" + } + }, + "required": [ + "type", + "name" + ] + }, + { + "description": "Streamable HTTP client with a URI endpoint using MCP Streamable HTTP specification", + "type": "object", + "properties": { + "name": { + "description": "The name used to identify this extension", + "type": "string" + }, + "description": { + "type": "string", + "default": "" + }, + "uri": { + "type": "string" + }, + "envs": { + "$ref": "#/$defs/Envs", + "default": {} + }, + "env_keys": { + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, + "headers": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "default": {} + }, + "timeout": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0 + }, + "socket": { + "description": "Optional Unix domain socket path for HTTP-over-UDS transport.\nWhen set, the HTTP connection is routed through this socket while\n`uri` is used for the Host header and path.\nUse `@name` for Linux abstract sockets.", + "type": [ + "string", + "null" + ], + "default": null + }, + "bundled": { + "type": [ + "boolean", + "null" + ], + "default": null + }, + "available_tools": { + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, + "type": { + "type": "string", + "const": "streamable_http" + } + }, + "required": [ + "type", + "name", + "uri" + ] + }, + { + "description": "Frontend-provided tools that will be called through the frontend", + "type": "object", + "properties": { + "name": { + "description": "The name used to identify this extension", + "type": "string" + }, + "description": { + "type": "string", + "default": "" + }, + "tools": { + "description": "The tools provided by the frontend", + "type": "array", + "items": { + "$ref": "#/$defs/Tool" + } + }, + "instructions": { + "description": "Instructions for how to use these tools", + "type": [ + "string", + "null" + ] + }, + "bundled": { + "type": [ + "boolean", + "null" + ], + "default": null + }, + "available_tools": { + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, + "type": { + "type": "string", + "const": "frontend" + } + }, + "required": [ + "type", + "name", + "tools" + ] + }, + { + "description": "Inline Python code that will be executed using uvx", + "type": "object", + "properties": { + "name": { + "description": "The name used to identify this extension", + "type": "string" + }, + "description": { + "type": "string", + "default": "" + }, + "code": { + "description": "The Python code to execute", + "type": "string" + }, + "timeout": { + "description": "Timeout in seconds", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0 + }, + "dependencies": { + "description": "Python package dependencies required by this extension", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + }, + "default": null + }, + "available_tools": { + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, + "type": { + "type": "string", + "const": "inline_python" + } + }, + "required": [ + "type", + "name", + "code" + ] + } + ] + }, + "Envs": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "Tool": { + "description": "A tool that can be used by a model.", + "type": "object", + "properties": { + "name": { + "description": "The name of the tool", + "type": "string" + }, + "title": { + "description": "A human-readable title for the tool", + "type": [ + "string", + "null" + ] + }, + "description": { + "description": "A description of what the tool does", + "type": [ + "string", + "null" + ] + }, + "inputSchema": { + "description": "A JSON Schema object defining the expected parameters for the tool", + "type": "object", + "additionalProperties": true + }, + "outputSchema": { + "description": "An optional JSON Schema object defining the structure of the tool's output", + "type": [ + "object", + "null" + ], + "additionalProperties": true + }, + "annotations": { + "description": "Optional additional tool information.", + "anyOf": [ + { + "$ref": "#/$defs/ToolAnnotations" + }, + { + "type": "null" + } + ] + }, + "execution": { + "description": "Execution-related configuration including task support mode.", + "anyOf": [ + { + "$ref": "#/$defs/ToolExecution" + }, + { + "type": "null" + } + ] + }, + "icons": { + "description": "Optional list of icons for the tool", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/$defs/Icon" + } + }, + "_meta": { + "description": "Optional additional metadata for this tool", + "type": [ + "object", + "null" + ], + "additionalProperties": true + } + }, + "required": [ + "name", + "inputSchema" + ] + }, + "ToolAnnotations": { + "description": "Additional properties describing a Tool to clients.\n\nNOTE: all properties in ToolAnnotations are **hints**.\nThey are not guaranteed to provide a faithful description of\ntool behavior (including descriptive properties like `title`).\n\nClients should never make tool use decisions based on ToolAnnotations\nreceived from untrusted servers.", + "type": "object", + "properties": { + "title": { + "description": "A human-readable title for the tool.", + "type": [ + "string", + "null" + ] + }, + "readOnlyHint": { + "description": "If true, the tool does not modify its environment.\n\nDefault: false", + "type": [ + "boolean", + "null" + ] + }, + "destructiveHint": { + "description": "If true, the tool may perform destructive updates to its environment.\nIf false, the tool performs only additive updates.\n\n(This property is meaningful only when `readOnlyHint == false`)\n\nDefault: true\nA human-readable description of the tool's purpose.", + "type": [ + "boolean", + "null" + ] + }, + "idempotentHint": { + "description": "If true, calling the tool repeatedly with the same arguments\nwill have no additional effect on the its environment.\n\n(This property is meaningful only when `readOnlyHint == false`)\n\nDefault: false.", + "type": [ + "boolean", + "null" + ] + }, + "openWorldHint": { + "description": "If true, this tool may interact with an \"open world\" of external\nentities. If false, the tool's domain of interaction is closed.\nFor example, the world of a web search tool is open, whereas that\nof a memory tool is not.\n\nDefault: true", + "type": [ + "boolean", + "null" + ] + } + } + }, + "ToolExecution": { + "description": "Execution-related configuration for a tool.\n\nThis struct contains settings that control how a tool should be executed,\nincluding task support configuration.", + "type": "object", + "properties": { + "taskSupport": { + "description": "Indicates whether this tool supports task-based invocation.\n\nWhen not present or set to `Forbidden`, clients MUST NOT invoke this tool as a task.\nWhen set to `Optional`, clients MAY invoke this tool as a task or normal call.\nWhen set to `Required`, clients MUST invoke this tool as a task.", + "anyOf": [ + { + "$ref": "#/$defs/TaskSupport" + }, + { + "type": "null" + } + ] + } + } + }, + "TaskSupport": { + "description": "Per-tool task support mode as defined in the MCP specification.\n\nThis enum indicates whether a tool supports task-based invocation,\nallowing clients to know how to properly call the tool.\n\nSee [Tool-Level Negotiation](https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks#tool-level-negotiation).", + "oneOf": [ + { + "description": "Clients MUST NOT invoke this tool as a task (default behavior).", + "type": "string", + "const": "forbidden" + }, + { + "description": "Clients MAY invoke this tool as either a task or a normal call.", + "type": "string", + "const": "optional" + }, + { + "description": "Clients MUST invoke this tool as a task.", + "type": "string", + "const": "required" + } + ] + }, + "Icon": { + "description": "A URL pointing to an icon resource or a base64-encoded data URI.\n\nClients that support rendering icons MUST support at least the following MIME types:\n- image/png - PNG images (safe, universal compatibility)\n- image/jpeg (and image/jpg) - JPEG images (safe, universal compatibility)\n\nClients that support rendering icons SHOULD also support:\n- image/svg+xml - SVG images (scalable but requires security precautions)\n- image/webp - WebP images (modern, efficient format)", + "type": "object", + "properties": { + "src": { + "description": "A standard URI pointing to an icon resource", + "type": "string" + }, + "mimeType": { + "description": "Optional override if the server's MIME type is missing or generic", + "type": [ + "string", + "null" + ] + }, + "sizes": { + "description": "Size specification, each string should be in WxH format (e.g., `\\\"48x48\\\"`, `\\\"96x96\\\"`) or `\\\"any\\\"` for scalable formats like SVG", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "theme": { + "description": "Optional specifier for the theme this icon is designed for\nIf not provided, the client should assume the icon can be used with any theme.", + "anyOf": [ + { + "$ref": "#/$defs/IconTheme" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "src" + ] + }, + "IconTheme": { + "description": "Icon themes supported by the MCP specification", + "oneOf": [ + { + "description": "Indicates the icon is designed to be used with a light background", + "type": "string", + "const": "light" + }, + { + "description": "Indicates the icon is designed to be used with a dark background", + "type": "string", + "const": "dark" + } + ] + }, + "SlashCommandMapping": { + "type": "object", + "properties": { + "command": { + "type": "string" + }, + "recipe_path": { + "type": "string" + } + }, + "required": [ + "command", + "recipe_path" + ] + } + } +} diff --git a/crates/goose/src/agents/extension.rs b/crates/goose/src/agents/extension.rs index 51349e0115..f8c3d8223e 100644 --- a/crates/goose/src/agents/extension.rs +++ b/crates/goose/src/agents/extension.rs @@ -7,6 +7,7 @@ use crate::config::Config; use rmcp::model::Tool; use rmcp::service::ClientInitializeError; use rmcp::ServiceError as ClientError; +use schemars::JsonSchema; use serde::Deserializer; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -58,7 +59,7 @@ pub enum ExtensionError { pub type ExtensionResult = Result; -#[derive(Debug, Clone, Deserialize, Serialize, Default, ToSchema, PartialEq)] +#[derive(Debug, Clone, Deserialize, Serialize, Default, ToSchema, PartialEq, JsonSchema)] pub struct Envs { /// A map of environment variables to set, e.g. API_KEY -> some_secret, HOST -> host #[serde(default)] @@ -148,7 +149,7 @@ impl Envs { } /// Represents the different types of MCP extensions that can be added to the manager -#[derive(Debug, Clone, Deserialize, Serialize, ToSchema, PartialEq)] +#[derive(Debug, Clone, Deserialize, Serialize, ToSchema, PartialEq, JsonSchema)] #[serde(tag = "type")] pub enum ExtensionConfig { /// SSE transport is no longer supported - kept only for config file compatibility diff --git a/crates/goose/src/bin/generate_config_schema.rs b/crates/goose/src/bin/generate_config_schema.rs new file mode 100644 index 0000000000..2d3d36523b --- /dev/null +++ b/crates/goose/src/bin/generate_config_schema.rs @@ -0,0 +1,28 @@ +use goose::config::GooseConfigSchema; +use std::env; +use std::fs; +use std::path::PathBuf; + +fn main() { + let schema = schemars::schema_for!(GooseConfigSchema); + let json_str = serde_json::to_string_pretty(&schema).expect("failed to serialize schema"); + + let package_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + let schema_path = PathBuf::from(&package_dir).join("config.schema.json"); + + let check_mode = env::args().any(|arg| arg == "--check"); + + if check_mode { + let existing = fs::read_to_string(&schema_path).unwrap_or_default(); + if existing.trim() != json_str.trim() { + eprintln!( + "Config schema is out of date. Run `cargo run -p goose --bin generate_config_schema` to regenerate." + ); + std::process::exit(1); + } + eprintln!("Config schema is up to date."); + } else { + fs::write(&schema_path, format!("{json_str}\n")).expect("failed to write schema file"); + eprintln!("Generated config schema at {}", schema_path.display()); + } +} diff --git a/crates/goose/src/config/extensions.rs b/crates/goose/src/config/extensions.rs index 460a6e93eb..ccecbe9b8a 100644 --- a/crates/goose/src/config/extensions.rs +++ b/crates/goose/src/config/extensions.rs @@ -2,6 +2,7 @@ use super::base::Config; use crate::agents::extension::PLATFORM_EXTENSIONS; use crate::agents::ExtensionConfig; use indexmap::IndexMap; +use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_yaml::Mapping; use tracing::warn; @@ -13,7 +14,7 @@ pub const DEFAULT_EXTENSION_DESCRIPTION: &str = ""; pub const DEFAULT_DISPLAY_NAME: &str = "Developer"; const EXTENSIONS_CONFIG_KEY: &str = "extensions"; -#[derive(Debug, Deserialize, Serialize, Clone, ToSchema)] +#[derive(Debug, Deserialize, Serialize, Clone, ToSchema, JsonSchema)] pub struct ExtensionEntry { pub enabled: bool, #[serde(flatten)] diff --git a/crates/goose/src/config/goose_mode.rs b/crates/goose/src/config/goose_mode.rs index dbfe8af128..65df7693fd 100644 --- a/crates/goose/src/config/goose_mode.rs +++ b/crates/goose/src/config/goose_mode.rs @@ -1,3 +1,4 @@ +use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use strum::{Display, EnumMessage, EnumString, IntoStaticStr, VariantNames}; use utoipa::ToSchema; @@ -18,6 +19,7 @@ use utoipa::ToSchema; IntoStaticStr, VariantNames, ToSchema, + JsonSchema, )] #[serde(rename_all = "snake_case")] #[strum(serialize_all = "snake_case")] diff --git a/crates/goose/src/config/mod.rs b/crates/goose/src/config/mod.rs index cd731c2ae3..ea3b26c85c 100644 --- a/crates/goose/src/config/mod.rs +++ b/crates/goose/src/config/mod.rs @@ -6,6 +6,8 @@ pub mod goose_mode; mod migrations; pub mod paths; pub mod permission; +pub mod schema; +pub use schema::{GooseConfigSchema, GooseConfigUpdate}; pub mod search_path; pub mod signup_nanogpt; pub mod signup_openrouter; diff --git a/crates/goose/src/config/schema.rs b/crates/goose/src/config/schema.rs new file mode 100644 index 0000000000..64ca0c9c3d --- /dev/null +++ b/crates/goose/src/config/schema.rs @@ -0,0 +1,1359 @@ +use std::collections::HashMap; + +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use utoipa::ToSchema; + +use crate::config::base::{Config, ConfigError}; +use crate::config::extensions::ExtensionEntry; +use crate::config::goose_mode::GooseMode; +use crate::slash_commands::SlashCommandMapping; + +/// JSON Schema representation of Goose's config.yaml. +/// +/// All keys are optional. Unknown keys are allowed (additionalProperties: true) +/// because Goose passes undocumented provider-specific keys through as +/// environment variable overrides. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, ToSchema)] +pub struct GooseConfigSchema { + // === Core Goose Settings === + #[serde(rename = "GOOSE_PROVIDER")] + pub goose_provider: Option, + #[serde(rename = "GOOSE_MODEL")] + pub goose_model: Option, + #[serde(rename = "GOOSE_MODE")] + pub goose_mode: Option, + #[serde(rename = "GOOSE_MAX_TOKENS")] + pub goose_max_tokens: Option, + #[serde(rename = "GOOSE_CONTEXT_LIMIT")] + pub goose_context_limit: Option, + #[serde(rename = "GOOSE_INPUT_LIMIT")] + pub goose_input_limit: Option, + #[serde(rename = "GOOSE_MAX_TURNS")] + pub goose_max_turns: Option, + #[serde(rename = "GOOSE_MAX_ACTIVE_AGENTS")] + pub goose_max_active_agents: Option, + #[serde(rename = "GOOSE_AUTO_COMPACT_THRESHOLD")] + pub goose_auto_compact_threshold: Option, + #[serde(rename = "GOOSE_TOOL_PAIR_SUMMARIZATION")] + pub goose_tool_pair_summarization: Option, + #[serde(rename = "GOOSE_TOOL_CALL_CUTOFF")] + pub goose_tool_call_cutoff: Option, + #[serde(rename = "GOOSE_STREAM_TIMEOUT")] + pub goose_stream_timeout: Option, + #[serde(rename = "GOOSE_SEARCH_PATHS")] + pub goose_search_paths: Option>, + #[serde(rename = "GOOSE_DISABLE_SESSION_NAMING")] + pub goose_disable_session_naming: Option, + #[serde(rename = "GOOSE_DISABLE_KEYRING")] + pub goose_disable_keyring: Option, + #[serde(rename = "GOOSE_TELEMETRY_ENABLED")] + pub goose_telemetry_enabled: Option, + #[serde(rename = "GOOSE_DEFAULT_EXTENSION_TIMEOUT")] + pub goose_default_extension_timeout: Option, + #[serde(rename = "GOOSE_PROMPT_EDITOR")] + pub goose_prompt_editor: Option, + #[serde(rename = "GOOSE_PROMPT_EDITOR_ALWAYS")] + pub goose_prompt_editor_always: Option, + #[serde(rename = "GOOSE_ALLOWLIST")] + pub goose_allowlist: Option, + #[serde(rename = "GOOSE_SYSTEM_PROMPT_FILE_PATH")] + pub goose_system_prompt_file_path: Option, + #[serde(rename = "GOOSE_DEBUG")] + pub goose_debug: Option, + #[serde(rename = "GOOSE_SHOW_FULL_OUTPUT")] + pub goose_show_full_output: Option, + #[serde(rename = "GOOSE_STATUS_HOOK")] + pub goose_status_hook: Option, + #[serde(rename = "GOOSE_LOCAL_ENABLE_THINKING")] + pub goose_local_enable_thinking: Option, + #[serde(rename = "GOOSE_DATABRICKS_CLIENT_REQUEST_ID")] + pub goose_databricks_client_request_id: Option, + #[serde(rename = "CONTEXT_FILE_NAMES")] + pub context_file_names: Option>, + #[serde(rename = "EDIT_MODE")] + pub edit_mode: Option, + #[serde(rename = "RANDOM_THINKING_MESSAGES")] + pub random_thinking_messages: Option, + #[serde(rename = "CODE_MODE_TOOL_DISCLOSURE")] + pub code_mode_tool_disclosure: Option, + + // === mTLS Settings === + #[serde(rename = "GOOSE_CLIENT_CERT_PATH")] + pub goose_client_cert_path: Option, + #[serde(rename = "GOOSE_CLIENT_KEY_PATH")] + pub goose_client_key_path: Option, + #[serde(rename = "GOOSE_CA_CERT_PATH")] + pub goose_ca_cert_path: Option, + + // === Planner & Subagent Settings === + #[serde(rename = "GOOSE_PLANNER_PROVIDER")] + pub goose_planner_provider: Option, + #[serde(rename = "GOOSE_PLANNER_MODEL")] + pub goose_planner_model: Option, + #[serde(rename = "GOOSE_SUBAGENT_PROVIDER")] + pub goose_subagent_provider: Option, + #[serde(rename = "GOOSE_SUBAGENT_MODEL")] + pub goose_subagent_model: Option, + #[serde(rename = "GOOSE_SUBAGENT_MAX_TURNS")] + pub goose_subagent_max_turns: Option, + #[serde(rename = "GOOSE_MAX_BACKGROUND_TASKS")] + pub goose_max_background_tasks: Option, + + // === Recipe Settings === + #[serde(rename = "GOOSE_RECIPE_GITHUB_REPO")] + pub goose_recipe_github_repo: Option, + #[serde(rename = "GOOSE_RECIPE_RETRY_TIMEOUT_SECONDS")] + pub goose_recipe_retry_timeout_seconds: Option, + #[serde(rename = "GOOSE_RECIPE_ON_FAILURE_TIMEOUT_SECONDS")] + pub goose_recipe_on_failure_timeout_seconds: Option, + + // === CLI Settings === + #[serde(rename = "GOOSE_CLI_MIN_PRIORITY")] + pub goose_cli_min_priority: Option, + #[serde(rename = "GOOSE_CLI_THEME")] + pub goose_cli_theme: Option, + #[serde(rename = "GOOSE_CLI_LIGHT_THEME")] + pub goose_cli_light_theme: Option, + #[serde(rename = "GOOSE_CLI_DARK_THEME")] + pub goose_cli_dark_theme: Option, + #[serde(rename = "GOOSE_CLI_SHOW_COST")] + pub goose_cli_show_cost: Option, + #[serde(rename = "GOOSE_CLI_SHOW_THINKING")] + pub goose_cli_show_thinking: Option, + #[serde(rename = "GOOSE_CLI_NEWLINE_KEY")] + pub goose_cli_newline_key: Option, + + // === AI Agent / Thinking Settings === + #[serde(rename = "CLAUDE_CODE_COMMAND")] + pub claude_code_command: Option, + #[serde(rename = "GEMINI_CLI_COMMAND")] + pub gemini_cli_command: Option, + #[serde(rename = "CURSOR_AGENT_COMMAND")] + pub cursor_agent_command: Option, + #[serde(rename = "CODEX_COMMAND")] + pub codex_command: Option, + #[serde(rename = "CODEX_REASONING_EFFORT")] + pub codex_reasoning_effort: Option, + #[serde(rename = "CODEX_ENABLE_SKILLS")] + pub codex_enable_skills: Option, + #[serde(rename = "CODEX_SKIP_GIT_CHECK")] + pub codex_skip_git_check: Option, + #[serde(rename = "CHATGPT_CODEX_REASONING_EFFORT")] + pub chatgpt_codex_reasoning_effort: Option, + #[serde(rename = "CLAUDE_THINKING_TYPE")] + pub claude_thinking_type: Option, + #[serde(rename = "CLAUDE_THINKING_EFFORT")] + pub claude_thinking_effort: Option, + #[serde(rename = "CLAUDE_THINKING_BUDGET")] + pub claude_thinking_budget: Option, + #[serde(rename = "GEMINI3_THINKING_LEVEL")] + pub gemini3_thinking_level: Option, + #[serde(rename = "GEMINI25_THINKING_BUDGET")] + pub gemini25_thinking_budget: Option, + + // === Security Settings === + #[serde(rename = "SECURITY_PROMPT_ENABLED")] + pub security_prompt_enabled: Option, + #[serde(rename = "SECURITY_PROMPT_THRESHOLD")] + pub security_prompt_threshold: Option, + #[serde(rename = "SECURITY_PROMPT_CLASSIFIER_ENABLED")] + pub security_prompt_classifier_enabled: Option, + #[serde(rename = "SECURITY_PROMPT_CLASSIFIER_MODEL")] + pub security_prompt_classifier_model: Option, + #[serde(rename = "SECURITY_PROMPT_CLASSIFIER_ENDPOINT")] + pub security_prompt_classifier_endpoint: Option, + #[serde(rename = "SECURITY_COMMAND_CLASSIFIER_ENABLED")] + pub security_command_classifier_enabled: Option, + + // === Provider Settings === + #[serde(rename = "OPENAI_HOST")] + pub openai_host: Option, + #[serde(rename = "OPENAI_BASE_URL")] + pub openai_base_url: Option, + #[serde(rename = "OPENAI_BASE_PATH")] + pub openai_base_path: Option, + #[serde(rename = "OPENAI_ORGANIZATION")] + pub openai_organization: Option, + #[serde(rename = "OPENAI_PROJECT")] + pub openai_project: Option, + #[serde(rename = "OPENAI_TIMEOUT")] + pub openai_timeout: Option, + #[serde(rename = "ANTHROPIC_HOST")] + pub anthropic_host: Option, + #[serde(rename = "OLLAMA_HOST")] + pub ollama_host: Option, + #[serde(rename = "OLLAMA_TIMEOUT")] + pub ollama_timeout: Option, + #[serde(rename = "OLLAMA_STREAM_TIMEOUT")] + pub ollama_stream_timeout: Option, + #[serde(rename = "OLLAMA_STREAM_USAGE")] + pub ollama_stream_usage: Option, + #[serde(rename = "DATABRICKS_HOST")] + pub databricks_host: Option, + #[serde(rename = "DATABRICKS_MAX_RETRIES")] + pub databricks_max_retries: Option, + #[serde(rename = "DATABRICKS_INITIAL_RETRY_INTERVAL_MS")] + pub databricks_initial_retry_interval_ms: Option, + #[serde(rename = "DATABRICKS_BACKOFF_MULTIPLIER")] + pub databricks_backoff_multiplier: Option, + #[serde(rename = "DATABRICKS_MAX_RETRY_INTERVAL_MS")] + pub databricks_max_retry_interval_ms: Option, + #[serde(rename = "AZURE_OPENAI_ENDPOINT")] + pub azure_openai_endpoint: Option, + #[serde(rename = "AZURE_OPENAI_DEPLOYMENT_NAME")] + pub azure_openai_deployment_name: Option, + #[serde(rename = "AZURE_OPENAI_API_VERSION")] + pub azure_openai_api_version: Option, + #[serde(rename = "GOOGLE_HOST")] + pub google_host: Option, + #[serde(rename = "GCP_PROJECT_ID")] + pub gcp_project_id: Option, + #[serde(rename = "GCP_LOCATION")] + pub gcp_location: Option, + #[serde(rename = "GCP_MAX_RETRIES")] + pub gcp_max_retries: Option, + #[serde(rename = "GCP_INITIAL_RETRY_INTERVAL_MS")] + pub gcp_initial_retry_interval_ms: Option, + #[serde(rename = "GCP_BACKOFF_MULTIPLIER")] + pub gcp_backoff_multiplier: Option, + #[serde(rename = "GCP_MAX_RETRY_INTERVAL_MS")] + pub gcp_max_retry_interval_ms: Option, + #[serde(rename = "AWS_REGION")] + pub aws_region: Option, + #[serde(rename = "AWS_PROFILE")] + pub aws_profile: Option, + #[serde(rename = "BEDROCK_MAX_RETRIES")] + pub bedrock_max_retries: Option, + #[serde(rename = "BEDROCK_INITIAL_RETRY_INTERVAL_MS")] + pub bedrock_initial_retry_interval_ms: Option, + #[serde(rename = "BEDROCK_BACKOFF_MULTIPLIER")] + pub bedrock_backoff_multiplier: Option, + #[serde(rename = "BEDROCK_MAX_RETRY_INTERVAL_MS")] + pub bedrock_max_retry_interval_ms: Option, + #[serde(rename = "BEDROCK_ENABLE_CACHING")] + pub bedrock_enable_caching: Option, + #[serde(rename = "SAGEMAKER_ENDPOINT_NAME")] + pub sagemaker_endpoint_name: Option, + #[serde(rename = "LITELLM_HOST")] + pub litellm_host: Option, + #[serde(rename = "LITELLM_BASE_PATH")] + pub litellm_base_path: Option, + #[serde(rename = "LITELLM_TIMEOUT")] + pub litellm_timeout: Option, + #[serde(rename = "SNOWFLAKE_HOST")] + pub snowflake_host: Option, + #[serde(rename = "GITHUB_COPILOT_HOST")] + pub github_copilot_host: Option, + #[serde(rename = "GITHUB_COPILOT_CLIENT_ID")] + pub github_copilot_client_id: Option, + #[serde(rename = "GITHUB_COPILOT_TOKEN_URL")] + pub github_copilot_token_url: Option, + #[serde(rename = "XAI_HOST")] + pub xai_host: Option, + #[serde(rename = "OPENROUTER_HOST")] + pub openrouter_host: Option, + #[serde(rename = "VENICE_HOST")] + pub venice_host: Option, + #[serde(rename = "VENICE_BASE_PATH")] + pub venice_base_path: Option, + #[serde(rename = "VENICE_MODELS_PATH")] + pub venice_models_path: Option, + #[serde(rename = "TETRATE_HOST")] + pub tetrate_host: Option, + #[serde(rename = "AVIAN_HOST")] + pub avian_host: Option, + + // === Observability Settings (lowercase keys) === + pub otel_exporter_otlp_endpoint: Option, + pub otel_exporter_otlp_timeout: Option, + + // === Tunnel Settings (lowercase keys) === + pub tunnel_auto_start: Option, + + // === Structured Config (lowercase keys) === + pub extensions: Option>, + pub slash_commands: Option>, + pub experiments: Option>, +} + +impl GooseConfigSchema { + /// All user-facing config keys that get `config_value!` typed accessors. + /// Category B keys (extensions, slash_commands, experiments) are in the struct + /// for schema generation but NOT here — they use dedicated module helpers. + pub const ALL_KEYS: &[&str] = &[ + // Core Goose Settings + "GOOSE_PROVIDER", + "GOOSE_MODEL", + "GOOSE_MODE", + "GOOSE_MAX_TOKENS", + "GOOSE_CONTEXT_LIMIT", + "GOOSE_INPUT_LIMIT", + "GOOSE_MAX_TURNS", + "GOOSE_MAX_ACTIVE_AGENTS", + "GOOSE_AUTO_COMPACT_THRESHOLD", + "GOOSE_TOOL_PAIR_SUMMARIZATION", + "GOOSE_TOOL_CALL_CUTOFF", + "GOOSE_STREAM_TIMEOUT", + "GOOSE_SEARCH_PATHS", + "GOOSE_DISABLE_SESSION_NAMING", + "GOOSE_DISABLE_KEYRING", + "GOOSE_TELEMETRY_ENABLED", + "GOOSE_DEFAULT_EXTENSION_TIMEOUT", + "GOOSE_PROMPT_EDITOR", + "GOOSE_PROMPT_EDITOR_ALWAYS", + "GOOSE_ALLOWLIST", + "GOOSE_SYSTEM_PROMPT_FILE_PATH", + "GOOSE_DEBUG", + "GOOSE_SHOW_FULL_OUTPUT", + "GOOSE_STATUS_HOOK", + "GOOSE_LOCAL_ENABLE_THINKING", + "GOOSE_DATABRICKS_CLIENT_REQUEST_ID", + "CONTEXT_FILE_NAMES", + "EDIT_MODE", + "RANDOM_THINKING_MESSAGES", + "CODE_MODE_TOOL_DISCLOSURE", + // mTLS Settings + "GOOSE_CLIENT_CERT_PATH", + "GOOSE_CLIENT_KEY_PATH", + "GOOSE_CA_CERT_PATH", + // Planner & Subagent Settings + "GOOSE_PLANNER_PROVIDER", + "GOOSE_PLANNER_MODEL", + "GOOSE_SUBAGENT_PROVIDER", + "GOOSE_SUBAGENT_MODEL", + "GOOSE_SUBAGENT_MAX_TURNS", + "GOOSE_MAX_BACKGROUND_TASKS", + // Recipe Settings + "GOOSE_RECIPE_GITHUB_REPO", + "GOOSE_RECIPE_RETRY_TIMEOUT_SECONDS", + "GOOSE_RECIPE_ON_FAILURE_TIMEOUT_SECONDS", + // CLI Settings + "GOOSE_CLI_MIN_PRIORITY", + "GOOSE_CLI_THEME", + "GOOSE_CLI_LIGHT_THEME", + "GOOSE_CLI_DARK_THEME", + "GOOSE_CLI_SHOW_COST", + "GOOSE_CLI_SHOW_THINKING", + "GOOSE_CLI_NEWLINE_KEY", + // AI Agent / Thinking Settings + "CLAUDE_CODE_COMMAND", + "GEMINI_CLI_COMMAND", + "CURSOR_AGENT_COMMAND", + "CODEX_COMMAND", + "CODEX_REASONING_EFFORT", + "CODEX_ENABLE_SKILLS", + "CODEX_SKIP_GIT_CHECK", + "CHATGPT_CODEX_REASONING_EFFORT", + "CLAUDE_THINKING_TYPE", + "CLAUDE_THINKING_EFFORT", + "CLAUDE_THINKING_BUDGET", + "GEMINI3_THINKING_LEVEL", + "GEMINI25_THINKING_BUDGET", + // Security Settings + "SECURITY_PROMPT_ENABLED", + "SECURITY_PROMPT_THRESHOLD", + "SECURITY_PROMPT_CLASSIFIER_ENABLED", + "SECURITY_PROMPT_CLASSIFIER_MODEL", + "SECURITY_PROMPT_CLASSIFIER_ENDPOINT", + "SECURITY_COMMAND_CLASSIFIER_ENABLED", + // Provider Settings + "OPENAI_HOST", + "OPENAI_BASE_URL", + "OPENAI_BASE_PATH", + "OPENAI_ORGANIZATION", + "OPENAI_PROJECT", + "OPENAI_TIMEOUT", + "ANTHROPIC_HOST", + "OLLAMA_HOST", + "OLLAMA_TIMEOUT", + "OLLAMA_STREAM_TIMEOUT", + "OLLAMA_STREAM_USAGE", + "DATABRICKS_HOST", + "DATABRICKS_MAX_RETRIES", + "DATABRICKS_INITIAL_RETRY_INTERVAL_MS", + "DATABRICKS_BACKOFF_MULTIPLIER", + "DATABRICKS_MAX_RETRY_INTERVAL_MS", + "AZURE_OPENAI_ENDPOINT", + "AZURE_OPENAI_DEPLOYMENT_NAME", + "AZURE_OPENAI_API_VERSION", + "GOOGLE_HOST", + "GCP_PROJECT_ID", + "GCP_LOCATION", + "GCP_MAX_RETRIES", + "GCP_INITIAL_RETRY_INTERVAL_MS", + "GCP_BACKOFF_MULTIPLIER", + "GCP_MAX_RETRY_INTERVAL_MS", + "AWS_REGION", + "AWS_PROFILE", + "BEDROCK_MAX_RETRIES", + "BEDROCK_INITIAL_RETRY_INTERVAL_MS", + "BEDROCK_BACKOFF_MULTIPLIER", + "BEDROCK_MAX_RETRY_INTERVAL_MS", + "BEDROCK_ENABLE_CACHING", + "SAGEMAKER_ENDPOINT_NAME", + "LITELLM_HOST", + "LITELLM_BASE_PATH", + "LITELLM_TIMEOUT", + "SNOWFLAKE_HOST", + "GITHUB_COPILOT_HOST", + "GITHUB_COPILOT_CLIENT_ID", + "GITHUB_COPILOT_TOKEN_URL", + "XAI_HOST", + "OPENROUTER_HOST", + "VENICE_HOST", + "VENICE_BASE_PATH", + "VENICE_MODELS_PATH", + "TETRATE_HOST", + "AVIAN_HOST", + // Observability Settings + "otel_exporter_otlp_endpoint", + "otel_exporter_otlp_timeout", + // Tunnel Settings + "tunnel_auto_start", + ]; + + pub const fn has_key(key: &str) -> bool { + let key_bytes = key.as_bytes(); + let mut i = 0; + while i < Self::ALL_KEYS.len() { + let candidate = Self::ALL_KEYS[i].as_bytes(); + if candidate.len() == key_bytes.len() { + let mut j = 0; + let mut eq = true; + while j < key_bytes.len() { + if candidate[j] != key_bytes[j] { + eq = false; + break; + } + j += 1; + } + if eq { + return true; + } + } + i += 1; + } + false + } + + pub fn from_config(config: &Config) -> Self { + GooseConfigSchema { + goose_provider: config.get_param("GOOSE_PROVIDER").ok(), + goose_model: config.get_param("GOOSE_MODEL").ok(), + goose_mode: config.get_param("GOOSE_MODE").ok(), + goose_max_tokens: config.get_param("GOOSE_MAX_TOKENS").ok(), + goose_context_limit: config.get_param("GOOSE_CONTEXT_LIMIT").ok(), + goose_input_limit: config.get_param("GOOSE_INPUT_LIMIT").ok(), + goose_max_turns: config.get_param("GOOSE_MAX_TURNS").ok(), + goose_max_active_agents: config.get_param("GOOSE_MAX_ACTIVE_AGENTS").ok(), + goose_auto_compact_threshold: config.get_param("GOOSE_AUTO_COMPACT_THRESHOLD").ok(), + goose_tool_pair_summarization: config.get_param("GOOSE_TOOL_PAIR_SUMMARIZATION").ok(), + goose_tool_call_cutoff: config.get_param("GOOSE_TOOL_CALL_CUTOFF").ok(), + goose_stream_timeout: config.get_param("GOOSE_STREAM_TIMEOUT").ok(), + goose_search_paths: config.get_param("GOOSE_SEARCH_PATHS").ok(), + goose_disable_session_naming: config.get_param("GOOSE_DISABLE_SESSION_NAMING").ok(), + goose_disable_keyring: config.get_param("GOOSE_DISABLE_KEYRING").ok(), + goose_telemetry_enabled: config.get_param("GOOSE_TELEMETRY_ENABLED").ok(), + goose_default_extension_timeout: config + .get_param("GOOSE_DEFAULT_EXTENSION_TIMEOUT") + .ok(), + goose_prompt_editor: config.get_param("GOOSE_PROMPT_EDITOR").ok(), + goose_prompt_editor_always: config.get_param("GOOSE_PROMPT_EDITOR_ALWAYS").ok(), + goose_allowlist: config.get_param("GOOSE_ALLOWLIST").ok(), + goose_system_prompt_file_path: config.get_param("GOOSE_SYSTEM_PROMPT_FILE_PATH").ok(), + goose_debug: config.get_param("GOOSE_DEBUG").ok(), + goose_show_full_output: config.get_param("GOOSE_SHOW_FULL_OUTPUT").ok(), + goose_status_hook: config.get_param("GOOSE_STATUS_HOOK").ok(), + goose_local_enable_thinking: config.get_param("GOOSE_LOCAL_ENABLE_THINKING").ok(), + goose_databricks_client_request_id: config + .get_param("GOOSE_DATABRICKS_CLIENT_REQUEST_ID") + .ok(), + context_file_names: config.get_param("CONTEXT_FILE_NAMES").ok(), + edit_mode: config.get_param("EDIT_MODE").ok(), + random_thinking_messages: config.get_param("RANDOM_THINKING_MESSAGES").ok(), + code_mode_tool_disclosure: config.get_param("CODE_MODE_TOOL_DISCLOSURE").ok(), + goose_client_cert_path: config.get_param("GOOSE_CLIENT_CERT_PATH").ok(), + goose_client_key_path: config.get_param("GOOSE_CLIENT_KEY_PATH").ok(), + goose_ca_cert_path: config.get_param("GOOSE_CA_CERT_PATH").ok(), + goose_planner_provider: config.get_param("GOOSE_PLANNER_PROVIDER").ok(), + goose_planner_model: config.get_param("GOOSE_PLANNER_MODEL").ok(), + goose_subagent_provider: config.get_param("GOOSE_SUBAGENT_PROVIDER").ok(), + goose_subagent_model: config.get_param("GOOSE_SUBAGENT_MODEL").ok(), + goose_subagent_max_turns: config.get_param("GOOSE_SUBAGENT_MAX_TURNS").ok(), + goose_max_background_tasks: config.get_param("GOOSE_MAX_BACKGROUND_TASKS").ok(), + goose_recipe_github_repo: config.get_param("GOOSE_RECIPE_GITHUB_REPO").ok(), + goose_recipe_retry_timeout_seconds: config + .get_param("GOOSE_RECIPE_RETRY_TIMEOUT_SECONDS") + .ok(), + goose_recipe_on_failure_timeout_seconds: config + .get_param("GOOSE_RECIPE_ON_FAILURE_TIMEOUT_SECONDS") + .ok(), + goose_cli_min_priority: config.get_param("GOOSE_CLI_MIN_PRIORITY").ok(), + goose_cli_theme: config.get_param("GOOSE_CLI_THEME").ok(), + goose_cli_light_theme: config.get_param("GOOSE_CLI_LIGHT_THEME").ok(), + goose_cli_dark_theme: config.get_param("GOOSE_CLI_DARK_THEME").ok(), + goose_cli_show_cost: config.get_param("GOOSE_CLI_SHOW_COST").ok(), + goose_cli_show_thinking: config.get_param("GOOSE_CLI_SHOW_THINKING").ok(), + goose_cli_newline_key: config.get_param("GOOSE_CLI_NEWLINE_KEY").ok(), + claude_code_command: config.get_param("CLAUDE_CODE_COMMAND").ok(), + gemini_cli_command: config.get_param("GEMINI_CLI_COMMAND").ok(), + cursor_agent_command: config.get_param("CURSOR_AGENT_COMMAND").ok(), + codex_command: config.get_param("CODEX_COMMAND").ok(), + codex_reasoning_effort: config.get_param("CODEX_REASONING_EFFORT").ok(), + codex_enable_skills: config.get_param("CODEX_ENABLE_SKILLS").ok(), + codex_skip_git_check: config.get_param("CODEX_SKIP_GIT_CHECK").ok(), + chatgpt_codex_reasoning_effort: config.get_param("CHATGPT_CODEX_REASONING_EFFORT").ok(), + claude_thinking_type: config.get_param("CLAUDE_THINKING_TYPE").ok(), + claude_thinking_effort: config.get_param("CLAUDE_THINKING_EFFORT").ok(), + claude_thinking_budget: config.get_param("CLAUDE_THINKING_BUDGET").ok(), + gemini3_thinking_level: config.get_param("GEMINI3_THINKING_LEVEL").ok(), + gemini25_thinking_budget: config.get_param("GEMINI25_THINKING_BUDGET").ok(), + security_prompt_enabled: config.get_param("SECURITY_PROMPT_ENABLED").ok(), + security_prompt_threshold: config.get_param("SECURITY_PROMPT_THRESHOLD").ok(), + security_prompt_classifier_enabled: config + .get_param("SECURITY_PROMPT_CLASSIFIER_ENABLED") + .ok(), + security_prompt_classifier_model: config + .get_param("SECURITY_PROMPT_CLASSIFIER_MODEL") + .ok(), + security_prompt_classifier_endpoint: config + .get_param("SECURITY_PROMPT_CLASSIFIER_ENDPOINT") + .ok(), + security_command_classifier_enabled: config + .get_param("SECURITY_COMMAND_CLASSIFIER_ENABLED") + .ok(), + openai_host: config.get_param("OPENAI_HOST").ok(), + openai_base_url: config.get_param("OPENAI_BASE_URL").ok(), + openai_base_path: config.get_param("OPENAI_BASE_PATH").ok(), + openai_organization: config.get_param("OPENAI_ORGANIZATION").ok(), + openai_project: config.get_param("OPENAI_PROJECT").ok(), + openai_timeout: config.get_param("OPENAI_TIMEOUT").ok(), + anthropic_host: config.get_param("ANTHROPIC_HOST").ok(), + ollama_host: config.get_param("OLLAMA_HOST").ok(), + ollama_timeout: config.get_param("OLLAMA_TIMEOUT").ok(), + ollama_stream_timeout: config.get_param("OLLAMA_STREAM_TIMEOUT").ok(), + ollama_stream_usage: config.get_param("OLLAMA_STREAM_USAGE").ok(), + databricks_host: config.get_param("DATABRICKS_HOST").ok(), + databricks_max_retries: config.get_param("DATABRICKS_MAX_RETRIES").ok(), + databricks_initial_retry_interval_ms: config + .get_param("DATABRICKS_INITIAL_RETRY_INTERVAL_MS") + .ok(), + databricks_backoff_multiplier: config.get_param("DATABRICKS_BACKOFF_MULTIPLIER").ok(), + databricks_max_retry_interval_ms: config + .get_param("DATABRICKS_MAX_RETRY_INTERVAL_MS") + .ok(), + azure_openai_endpoint: config.get_param("AZURE_OPENAI_ENDPOINT").ok(), + azure_openai_deployment_name: config.get_param("AZURE_OPENAI_DEPLOYMENT_NAME").ok(), + azure_openai_api_version: config.get_param("AZURE_OPENAI_API_VERSION").ok(), + google_host: config.get_param("GOOGLE_HOST").ok(), + gcp_project_id: config.get_param("GCP_PROJECT_ID").ok(), + gcp_location: config.get_param("GCP_LOCATION").ok(), + gcp_max_retries: config.get_param("GCP_MAX_RETRIES").ok(), + gcp_initial_retry_interval_ms: config.get_param("GCP_INITIAL_RETRY_INTERVAL_MS").ok(), + gcp_backoff_multiplier: config.get_param("GCP_BACKOFF_MULTIPLIER").ok(), + gcp_max_retry_interval_ms: config.get_param("GCP_MAX_RETRY_INTERVAL_MS").ok(), + aws_region: config.get_param("AWS_REGION").ok(), + aws_profile: config.get_param("AWS_PROFILE").ok(), + bedrock_max_retries: config.get_param("BEDROCK_MAX_RETRIES").ok(), + bedrock_initial_retry_interval_ms: config + .get_param("BEDROCK_INITIAL_RETRY_INTERVAL_MS") + .ok(), + bedrock_backoff_multiplier: config.get_param("BEDROCK_BACKOFF_MULTIPLIER").ok(), + bedrock_max_retry_interval_ms: config.get_param("BEDROCK_MAX_RETRY_INTERVAL_MS").ok(), + bedrock_enable_caching: config.get_param("BEDROCK_ENABLE_CACHING").ok(), + sagemaker_endpoint_name: config.get_param("SAGEMAKER_ENDPOINT_NAME").ok(), + litellm_host: config.get_param("LITELLM_HOST").ok(), + litellm_base_path: config.get_param("LITELLM_BASE_PATH").ok(), + litellm_timeout: config.get_param("LITELLM_TIMEOUT").ok(), + snowflake_host: config.get_param("SNOWFLAKE_HOST").ok(), + github_copilot_host: config.get_param("GITHUB_COPILOT_HOST").ok(), + github_copilot_client_id: config.get_param("GITHUB_COPILOT_CLIENT_ID").ok(), + github_copilot_token_url: config.get_param("GITHUB_COPILOT_TOKEN_URL").ok(), + xai_host: config.get_param("XAI_HOST").ok(), + openrouter_host: config.get_param("OPENROUTER_HOST").ok(), + venice_host: config.get_param("VENICE_HOST").ok(), + venice_base_path: config.get_param("VENICE_BASE_PATH").ok(), + venice_models_path: config.get_param("VENICE_MODELS_PATH").ok(), + tetrate_host: config.get_param("TETRATE_HOST").ok(), + avian_host: config.get_param("AVIAN_HOST").ok(), + otel_exporter_otlp_endpoint: config.get_param("otel_exporter_otlp_endpoint").ok(), + otel_exporter_otlp_timeout: config.get_param("otel_exporter_otlp_timeout").ok(), + tunnel_auto_start: config.get_param("tunnel_auto_start").ok(), + extensions: config.get_param("extensions").ok(), + slash_commands: config.get_param("slash_commands").ok(), + experiments: config.get_param("experiments").ok(), + } + } + + pub fn apply_to_config(&self, config: &Config) -> Result<(), ConfigError> { + let mut updates: Vec<(String, serde_json::Value)> = Vec::new(); + + macro_rules! push_if_some { + ($field:expr, $key:expr) => { + if let Some(ref v) = $field { + if let Ok(json) = serde_json::to_value(v) { + updates.push(($key.to_string(), json)); + } + } + }; + } + + push_if_some!(self.goose_provider, "GOOSE_PROVIDER"); + push_if_some!(self.goose_model, "GOOSE_MODEL"); + push_if_some!(self.goose_mode, "GOOSE_MODE"); + push_if_some!(self.goose_max_tokens, "GOOSE_MAX_TOKENS"); + push_if_some!(self.goose_context_limit, "GOOSE_CONTEXT_LIMIT"); + push_if_some!(self.goose_input_limit, "GOOSE_INPUT_LIMIT"); + push_if_some!(self.goose_max_turns, "GOOSE_MAX_TURNS"); + push_if_some!(self.goose_max_active_agents, "GOOSE_MAX_ACTIVE_AGENTS"); + push_if_some!( + self.goose_auto_compact_threshold, + "GOOSE_AUTO_COMPACT_THRESHOLD" + ); + push_if_some!( + self.goose_tool_pair_summarization, + "GOOSE_TOOL_PAIR_SUMMARIZATION" + ); + push_if_some!(self.goose_tool_call_cutoff, "GOOSE_TOOL_CALL_CUTOFF"); + push_if_some!(self.goose_stream_timeout, "GOOSE_STREAM_TIMEOUT"); + push_if_some!(self.goose_search_paths, "GOOSE_SEARCH_PATHS"); + push_if_some!( + self.goose_disable_session_naming, + "GOOSE_DISABLE_SESSION_NAMING" + ); + push_if_some!(self.goose_disable_keyring, "GOOSE_DISABLE_KEYRING"); + push_if_some!(self.goose_telemetry_enabled, "GOOSE_TELEMETRY_ENABLED"); + push_if_some!( + self.goose_default_extension_timeout, + "GOOSE_DEFAULT_EXTENSION_TIMEOUT" + ); + push_if_some!(self.goose_prompt_editor, "GOOSE_PROMPT_EDITOR"); + push_if_some!( + self.goose_prompt_editor_always, + "GOOSE_PROMPT_EDITOR_ALWAYS" + ); + push_if_some!(self.goose_allowlist, "GOOSE_ALLOWLIST"); + push_if_some!( + self.goose_system_prompt_file_path, + "GOOSE_SYSTEM_PROMPT_FILE_PATH" + ); + push_if_some!(self.goose_debug, "GOOSE_DEBUG"); + push_if_some!(self.goose_show_full_output, "GOOSE_SHOW_FULL_OUTPUT"); + push_if_some!(self.goose_status_hook, "GOOSE_STATUS_HOOK"); + push_if_some!( + self.goose_local_enable_thinking, + "GOOSE_LOCAL_ENABLE_THINKING" + ); + push_if_some!( + self.goose_databricks_client_request_id, + "GOOSE_DATABRICKS_CLIENT_REQUEST_ID" + ); + push_if_some!(self.context_file_names, "CONTEXT_FILE_NAMES"); + push_if_some!(self.edit_mode, "EDIT_MODE"); + push_if_some!(self.random_thinking_messages, "RANDOM_THINKING_MESSAGES"); + push_if_some!(self.code_mode_tool_disclosure, "CODE_MODE_TOOL_DISCLOSURE"); + push_if_some!(self.goose_client_cert_path, "GOOSE_CLIENT_CERT_PATH"); + push_if_some!(self.goose_client_key_path, "GOOSE_CLIENT_KEY_PATH"); + push_if_some!(self.goose_ca_cert_path, "GOOSE_CA_CERT_PATH"); + push_if_some!(self.goose_planner_provider, "GOOSE_PLANNER_PROVIDER"); + push_if_some!(self.goose_planner_model, "GOOSE_PLANNER_MODEL"); + push_if_some!(self.goose_subagent_provider, "GOOSE_SUBAGENT_PROVIDER"); + push_if_some!(self.goose_subagent_model, "GOOSE_SUBAGENT_MODEL"); + push_if_some!(self.goose_subagent_max_turns, "GOOSE_SUBAGENT_MAX_TURNS"); + push_if_some!( + self.goose_max_background_tasks, + "GOOSE_MAX_BACKGROUND_TASKS" + ); + push_if_some!(self.goose_recipe_github_repo, "GOOSE_RECIPE_GITHUB_REPO"); + push_if_some!( + self.goose_recipe_retry_timeout_seconds, + "GOOSE_RECIPE_RETRY_TIMEOUT_SECONDS" + ); + push_if_some!( + self.goose_recipe_on_failure_timeout_seconds, + "GOOSE_RECIPE_ON_FAILURE_TIMEOUT_SECONDS" + ); + push_if_some!(self.goose_cli_min_priority, "GOOSE_CLI_MIN_PRIORITY"); + push_if_some!(self.goose_cli_theme, "GOOSE_CLI_THEME"); + push_if_some!(self.goose_cli_light_theme, "GOOSE_CLI_LIGHT_THEME"); + push_if_some!(self.goose_cli_dark_theme, "GOOSE_CLI_DARK_THEME"); + push_if_some!(self.goose_cli_show_cost, "GOOSE_CLI_SHOW_COST"); + push_if_some!(self.goose_cli_show_thinking, "GOOSE_CLI_SHOW_THINKING"); + push_if_some!(self.goose_cli_newline_key, "GOOSE_CLI_NEWLINE_KEY"); + push_if_some!(self.claude_code_command, "CLAUDE_CODE_COMMAND"); + push_if_some!(self.gemini_cli_command, "GEMINI_CLI_COMMAND"); + push_if_some!(self.cursor_agent_command, "CURSOR_AGENT_COMMAND"); + push_if_some!(self.codex_command, "CODEX_COMMAND"); + push_if_some!(self.codex_reasoning_effort, "CODEX_REASONING_EFFORT"); + push_if_some!(self.codex_enable_skills, "CODEX_ENABLE_SKILLS"); + push_if_some!(self.codex_skip_git_check, "CODEX_SKIP_GIT_CHECK"); + push_if_some!( + self.chatgpt_codex_reasoning_effort, + "CHATGPT_CODEX_REASONING_EFFORT" + ); + push_if_some!(self.claude_thinking_type, "CLAUDE_THINKING_TYPE"); + push_if_some!(self.claude_thinking_effort, "CLAUDE_THINKING_EFFORT"); + push_if_some!(self.claude_thinking_budget, "CLAUDE_THINKING_BUDGET"); + push_if_some!(self.gemini3_thinking_level, "GEMINI3_THINKING_LEVEL"); + push_if_some!(self.gemini25_thinking_budget, "GEMINI25_THINKING_BUDGET"); + push_if_some!(self.security_prompt_enabled, "SECURITY_PROMPT_ENABLED"); + push_if_some!(self.security_prompt_threshold, "SECURITY_PROMPT_THRESHOLD"); + push_if_some!( + self.security_prompt_classifier_enabled, + "SECURITY_PROMPT_CLASSIFIER_ENABLED" + ); + push_if_some!( + self.security_prompt_classifier_model, + "SECURITY_PROMPT_CLASSIFIER_MODEL" + ); + push_if_some!( + self.security_prompt_classifier_endpoint, + "SECURITY_PROMPT_CLASSIFIER_ENDPOINT" + ); + push_if_some!( + self.security_command_classifier_enabled, + "SECURITY_COMMAND_CLASSIFIER_ENABLED" + ); + push_if_some!(self.openai_host, "OPENAI_HOST"); + push_if_some!(self.openai_base_url, "OPENAI_BASE_URL"); + push_if_some!(self.openai_base_path, "OPENAI_BASE_PATH"); + push_if_some!(self.openai_organization, "OPENAI_ORGANIZATION"); + push_if_some!(self.openai_project, "OPENAI_PROJECT"); + push_if_some!(self.openai_timeout, "OPENAI_TIMEOUT"); + push_if_some!(self.anthropic_host, "ANTHROPIC_HOST"); + push_if_some!(self.ollama_host, "OLLAMA_HOST"); + push_if_some!(self.ollama_timeout, "OLLAMA_TIMEOUT"); + push_if_some!(self.ollama_stream_timeout, "OLLAMA_STREAM_TIMEOUT"); + push_if_some!(self.ollama_stream_usage, "OLLAMA_STREAM_USAGE"); + push_if_some!(self.databricks_host, "DATABRICKS_HOST"); + push_if_some!(self.databricks_max_retries, "DATABRICKS_MAX_RETRIES"); + push_if_some!( + self.databricks_initial_retry_interval_ms, + "DATABRICKS_INITIAL_RETRY_INTERVAL_MS" + ); + push_if_some!( + self.databricks_backoff_multiplier, + "DATABRICKS_BACKOFF_MULTIPLIER" + ); + push_if_some!( + self.databricks_max_retry_interval_ms, + "DATABRICKS_MAX_RETRY_INTERVAL_MS" + ); + push_if_some!(self.azure_openai_endpoint, "AZURE_OPENAI_ENDPOINT"); + push_if_some!( + self.azure_openai_deployment_name, + "AZURE_OPENAI_DEPLOYMENT_NAME" + ); + push_if_some!(self.azure_openai_api_version, "AZURE_OPENAI_API_VERSION"); + push_if_some!(self.google_host, "GOOGLE_HOST"); + push_if_some!(self.gcp_project_id, "GCP_PROJECT_ID"); + push_if_some!(self.gcp_location, "GCP_LOCATION"); + push_if_some!(self.gcp_max_retries, "GCP_MAX_RETRIES"); + push_if_some!( + self.gcp_initial_retry_interval_ms, + "GCP_INITIAL_RETRY_INTERVAL_MS" + ); + push_if_some!(self.gcp_backoff_multiplier, "GCP_BACKOFF_MULTIPLIER"); + push_if_some!(self.gcp_max_retry_interval_ms, "GCP_MAX_RETRY_INTERVAL_MS"); + push_if_some!(self.aws_region, "AWS_REGION"); + push_if_some!(self.aws_profile, "AWS_PROFILE"); + push_if_some!(self.bedrock_max_retries, "BEDROCK_MAX_RETRIES"); + push_if_some!( + self.bedrock_initial_retry_interval_ms, + "BEDROCK_INITIAL_RETRY_INTERVAL_MS" + ); + push_if_some!( + self.bedrock_backoff_multiplier, + "BEDROCK_BACKOFF_MULTIPLIER" + ); + push_if_some!( + self.bedrock_max_retry_interval_ms, + "BEDROCK_MAX_RETRY_INTERVAL_MS" + ); + push_if_some!(self.bedrock_enable_caching, "BEDROCK_ENABLE_CACHING"); + push_if_some!(self.sagemaker_endpoint_name, "SAGEMAKER_ENDPOINT_NAME"); + push_if_some!(self.litellm_host, "LITELLM_HOST"); + push_if_some!(self.litellm_base_path, "LITELLM_BASE_PATH"); + push_if_some!(self.litellm_timeout, "LITELLM_TIMEOUT"); + push_if_some!(self.snowflake_host, "SNOWFLAKE_HOST"); + push_if_some!(self.github_copilot_host, "GITHUB_COPILOT_HOST"); + push_if_some!(self.github_copilot_client_id, "GITHUB_COPILOT_CLIENT_ID"); + push_if_some!(self.github_copilot_token_url, "GITHUB_COPILOT_TOKEN_URL"); + push_if_some!(self.xai_host, "XAI_HOST"); + push_if_some!(self.openrouter_host, "OPENROUTER_HOST"); + push_if_some!(self.venice_host, "VENICE_HOST"); + push_if_some!(self.venice_base_path, "VENICE_BASE_PATH"); + push_if_some!(self.venice_models_path, "VENICE_MODELS_PATH"); + push_if_some!(self.tetrate_host, "TETRATE_HOST"); + push_if_some!(self.avian_host, "AVIAN_HOST"); + push_if_some!( + self.otel_exporter_otlp_endpoint, + "otel_exporter_otlp_endpoint" + ); + push_if_some!( + self.otel_exporter_otlp_timeout, + "otel_exporter_otlp_timeout" + ); + push_if_some!(self.tunnel_auto_start, "tunnel_auto_start"); + push_if_some!(self.extensions, "extensions"); + push_if_some!(self.slash_commands, "slash_commands"); + push_if_some!(self.experiments, "experiments"); + + config.set_param_values(&updates) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, ToSchema)] +pub struct GooseConfigUpdate { + // === Core Goose Settings === + #[serde(rename = "GOOSE_PROVIDER")] + pub goose_provider: Option, + #[serde(rename = "GOOSE_MODEL")] + pub goose_model: Option, + #[serde(rename = "GOOSE_MODE")] + pub goose_mode: Option, + #[serde(rename = "GOOSE_MAX_TOKENS")] + pub goose_max_tokens: Option, + #[serde(rename = "GOOSE_CONTEXT_LIMIT")] + pub goose_context_limit: Option, + #[serde(rename = "GOOSE_INPUT_LIMIT")] + pub goose_input_limit: Option, + #[serde(rename = "GOOSE_MAX_TURNS")] + pub goose_max_turns: Option, + #[serde(rename = "GOOSE_MAX_ACTIVE_AGENTS")] + pub goose_max_active_agents: Option, + #[serde(rename = "GOOSE_AUTO_COMPACT_THRESHOLD")] + pub goose_auto_compact_threshold: Option, + #[serde(rename = "GOOSE_TOOL_PAIR_SUMMARIZATION")] + pub goose_tool_pair_summarization: Option, + #[serde(rename = "GOOSE_TOOL_CALL_CUTOFF")] + pub goose_tool_call_cutoff: Option, + #[serde(rename = "GOOSE_STREAM_TIMEOUT")] + pub goose_stream_timeout: Option, + #[serde(rename = "GOOSE_SEARCH_PATHS")] + pub goose_search_paths: Option>, + #[serde(rename = "GOOSE_DISABLE_SESSION_NAMING")] + pub goose_disable_session_naming: Option, + #[serde(rename = "GOOSE_DISABLE_KEYRING")] + pub goose_disable_keyring: Option, + #[serde(rename = "GOOSE_TELEMETRY_ENABLED")] + pub goose_telemetry_enabled: Option, + #[serde(rename = "GOOSE_DEFAULT_EXTENSION_TIMEOUT")] + pub goose_default_extension_timeout: Option, + #[serde(rename = "GOOSE_PROMPT_EDITOR")] + pub goose_prompt_editor: Option, + #[serde(rename = "GOOSE_PROMPT_EDITOR_ALWAYS")] + pub goose_prompt_editor_always: Option, + #[serde(rename = "GOOSE_ALLOWLIST")] + pub goose_allowlist: Option, + #[serde(rename = "GOOSE_SYSTEM_PROMPT_FILE_PATH")] + pub goose_system_prompt_file_path: Option, + #[serde(rename = "GOOSE_DEBUG")] + pub goose_debug: Option, + #[serde(rename = "GOOSE_SHOW_FULL_OUTPUT")] + pub goose_show_full_output: Option, + #[serde(rename = "GOOSE_STATUS_HOOK")] + pub goose_status_hook: Option, + #[serde(rename = "GOOSE_LOCAL_ENABLE_THINKING")] + pub goose_local_enable_thinking: Option, + #[serde(rename = "GOOSE_DATABRICKS_CLIENT_REQUEST_ID")] + pub goose_databricks_client_request_id: Option, + #[serde(rename = "CONTEXT_FILE_NAMES")] + pub context_file_names: Option>, + #[serde(rename = "EDIT_MODE")] + pub edit_mode: Option, + #[serde(rename = "RANDOM_THINKING_MESSAGES")] + pub random_thinking_messages: Option, + #[serde(rename = "CODE_MODE_TOOL_DISCLOSURE")] + pub code_mode_tool_disclosure: Option, + + // === mTLS Settings === + #[serde(rename = "GOOSE_CLIENT_CERT_PATH")] + pub goose_client_cert_path: Option, + #[serde(rename = "GOOSE_CLIENT_KEY_PATH")] + pub goose_client_key_path: Option, + #[serde(rename = "GOOSE_CA_CERT_PATH")] + pub goose_ca_cert_path: Option, + + // === Planner & Subagent Settings === + #[serde(rename = "GOOSE_PLANNER_PROVIDER")] + pub goose_planner_provider: Option, + #[serde(rename = "GOOSE_PLANNER_MODEL")] + pub goose_planner_model: Option, + #[serde(rename = "GOOSE_SUBAGENT_PROVIDER")] + pub goose_subagent_provider: Option, + #[serde(rename = "GOOSE_SUBAGENT_MODEL")] + pub goose_subagent_model: Option, + #[serde(rename = "GOOSE_SUBAGENT_MAX_TURNS")] + pub goose_subagent_max_turns: Option, + #[serde(rename = "GOOSE_MAX_BACKGROUND_TASKS")] + pub goose_max_background_tasks: Option, + + // === Recipe Settings === + #[serde(rename = "GOOSE_RECIPE_GITHUB_REPO")] + pub goose_recipe_github_repo: Option, + #[serde(rename = "GOOSE_RECIPE_RETRY_TIMEOUT_SECONDS")] + pub goose_recipe_retry_timeout_seconds: Option, + #[serde(rename = "GOOSE_RECIPE_ON_FAILURE_TIMEOUT_SECONDS")] + pub goose_recipe_on_failure_timeout_seconds: Option, + + // === CLI Settings === + #[serde(rename = "GOOSE_CLI_MIN_PRIORITY")] + pub goose_cli_min_priority: Option, + #[serde(rename = "GOOSE_CLI_THEME")] + pub goose_cli_theme: Option, + #[serde(rename = "GOOSE_CLI_LIGHT_THEME")] + pub goose_cli_light_theme: Option, + #[serde(rename = "GOOSE_CLI_DARK_THEME")] + pub goose_cli_dark_theme: Option, + #[serde(rename = "GOOSE_CLI_SHOW_COST")] + pub goose_cli_show_cost: Option, + #[serde(rename = "GOOSE_CLI_SHOW_THINKING")] + pub goose_cli_show_thinking: Option, + #[serde(rename = "GOOSE_CLI_NEWLINE_KEY")] + pub goose_cli_newline_key: Option, + + // === AI Agent / Thinking Settings === + #[serde(rename = "CLAUDE_CODE_COMMAND")] + pub claude_code_command: Option, + #[serde(rename = "GEMINI_CLI_COMMAND")] + pub gemini_cli_command: Option, + #[serde(rename = "CURSOR_AGENT_COMMAND")] + pub cursor_agent_command: Option, + #[serde(rename = "CODEX_COMMAND")] + pub codex_command: Option, + #[serde(rename = "CODEX_REASONING_EFFORT")] + pub codex_reasoning_effort: Option, + #[serde(rename = "CODEX_ENABLE_SKILLS")] + pub codex_enable_skills: Option, + #[serde(rename = "CODEX_SKIP_GIT_CHECK")] + pub codex_skip_git_check: Option, + #[serde(rename = "CHATGPT_CODEX_REASONING_EFFORT")] + pub chatgpt_codex_reasoning_effort: Option, + #[serde(rename = "CLAUDE_THINKING_TYPE")] + pub claude_thinking_type: Option, + #[serde(rename = "CLAUDE_THINKING_EFFORT")] + pub claude_thinking_effort: Option, + #[serde(rename = "CLAUDE_THINKING_BUDGET")] + pub claude_thinking_budget: Option, + #[serde(rename = "GEMINI3_THINKING_LEVEL")] + pub gemini3_thinking_level: Option, + #[serde(rename = "GEMINI25_THINKING_BUDGET")] + pub gemini25_thinking_budget: Option, + + // === Security Settings === + #[serde(rename = "SECURITY_PROMPT_ENABLED")] + pub security_prompt_enabled: Option, + #[serde(rename = "SECURITY_PROMPT_THRESHOLD")] + pub security_prompt_threshold: Option, + #[serde(rename = "SECURITY_PROMPT_CLASSIFIER_ENABLED")] + pub security_prompt_classifier_enabled: Option, + #[serde(rename = "SECURITY_PROMPT_CLASSIFIER_MODEL")] + pub security_prompt_classifier_model: Option, + #[serde(rename = "SECURITY_PROMPT_CLASSIFIER_ENDPOINT")] + pub security_prompt_classifier_endpoint: Option, + #[serde(rename = "SECURITY_COMMAND_CLASSIFIER_ENABLED")] + pub security_command_classifier_enabled: Option, + + // === Provider Settings === + #[serde(rename = "OPENAI_HOST")] + pub openai_host: Option, + #[serde(rename = "OPENAI_BASE_URL")] + pub openai_base_url: Option, + #[serde(rename = "OPENAI_BASE_PATH")] + pub openai_base_path: Option, + #[serde(rename = "OPENAI_ORGANIZATION")] + pub openai_organization: Option, + #[serde(rename = "OPENAI_PROJECT")] + pub openai_project: Option, + #[serde(rename = "OPENAI_TIMEOUT")] + pub openai_timeout: Option, + #[serde(rename = "ANTHROPIC_HOST")] + pub anthropic_host: Option, + #[serde(rename = "OLLAMA_HOST")] + pub ollama_host: Option, + #[serde(rename = "OLLAMA_TIMEOUT")] + pub ollama_timeout: Option, + #[serde(rename = "OLLAMA_STREAM_TIMEOUT")] + pub ollama_stream_timeout: Option, + #[serde(rename = "OLLAMA_STREAM_USAGE")] + pub ollama_stream_usage: Option, + #[serde(rename = "DATABRICKS_HOST")] + pub databricks_host: Option, + #[serde(rename = "DATABRICKS_MAX_RETRIES")] + pub databricks_max_retries: Option, + #[serde(rename = "DATABRICKS_INITIAL_RETRY_INTERVAL_MS")] + pub databricks_initial_retry_interval_ms: Option, + #[serde(rename = "DATABRICKS_BACKOFF_MULTIPLIER")] + pub databricks_backoff_multiplier: Option, + #[serde(rename = "DATABRICKS_MAX_RETRY_INTERVAL_MS")] + pub databricks_max_retry_interval_ms: Option, + #[serde(rename = "AZURE_OPENAI_ENDPOINT")] + pub azure_openai_endpoint: Option, + #[serde(rename = "AZURE_OPENAI_DEPLOYMENT_NAME")] + pub azure_openai_deployment_name: Option, + #[serde(rename = "AZURE_OPENAI_API_VERSION")] + pub azure_openai_api_version: Option, + #[serde(rename = "GOOGLE_HOST")] + pub google_host: Option, + #[serde(rename = "GCP_PROJECT_ID")] + pub gcp_project_id: Option, + #[serde(rename = "GCP_LOCATION")] + pub gcp_location: Option, + #[serde(rename = "GCP_MAX_RETRIES")] + pub gcp_max_retries: Option, + #[serde(rename = "GCP_INITIAL_RETRY_INTERVAL_MS")] + pub gcp_initial_retry_interval_ms: Option, + #[serde(rename = "GCP_BACKOFF_MULTIPLIER")] + pub gcp_backoff_multiplier: Option, + #[serde(rename = "GCP_MAX_RETRY_INTERVAL_MS")] + pub gcp_max_retry_interval_ms: Option, + #[serde(rename = "AWS_REGION")] + pub aws_region: Option, + #[serde(rename = "AWS_PROFILE")] + pub aws_profile: Option, + #[serde(rename = "BEDROCK_MAX_RETRIES")] + pub bedrock_max_retries: Option, + #[serde(rename = "BEDROCK_INITIAL_RETRY_INTERVAL_MS")] + pub bedrock_initial_retry_interval_ms: Option, + #[serde(rename = "BEDROCK_BACKOFF_MULTIPLIER")] + pub bedrock_backoff_multiplier: Option, + #[serde(rename = "BEDROCK_MAX_RETRY_INTERVAL_MS")] + pub bedrock_max_retry_interval_ms: Option, + #[serde(rename = "BEDROCK_ENABLE_CACHING")] + pub bedrock_enable_caching: Option, + #[serde(rename = "SAGEMAKER_ENDPOINT_NAME")] + pub sagemaker_endpoint_name: Option, + #[serde(rename = "LITELLM_HOST")] + pub litellm_host: Option, + #[serde(rename = "LITELLM_BASE_PATH")] + pub litellm_base_path: Option, + #[serde(rename = "LITELLM_TIMEOUT")] + pub litellm_timeout: Option, + #[serde(rename = "SNOWFLAKE_HOST")] + pub snowflake_host: Option, + #[serde(rename = "GITHUB_COPILOT_HOST")] + pub github_copilot_host: Option, + #[serde(rename = "GITHUB_COPILOT_CLIENT_ID")] + pub github_copilot_client_id: Option, + #[serde(rename = "GITHUB_COPILOT_TOKEN_URL")] + pub github_copilot_token_url: Option, + #[serde(rename = "XAI_HOST")] + pub xai_host: Option, + #[serde(rename = "OPENROUTER_HOST")] + pub openrouter_host: Option, + #[serde(rename = "VENICE_HOST")] + pub venice_host: Option, + #[serde(rename = "VENICE_BASE_PATH")] + pub venice_base_path: Option, + #[serde(rename = "VENICE_MODELS_PATH")] + pub venice_models_path: Option, + #[serde(rename = "TETRATE_HOST")] + pub tetrate_host: Option, + #[serde(rename = "AVIAN_HOST")] + pub avian_host: Option, + + // === Observability Settings (lowercase keys) === + pub otel_exporter_otlp_endpoint: Option, + pub otel_exporter_otlp_timeout: Option, + + // === Tunnel Settings (lowercase keys) === + pub tunnel_auto_start: Option, + + // === Structured Config (lowercase keys) === + pub extensions: Option>, + pub slash_commands: Option>, + pub experiments: Option>, + + // === Provider API Keys (secrets, stored in keyring) === + #[serde(rename = "OPENAI_API_KEY")] + pub openai_api_key: Option, + #[serde(rename = "ANTHROPIC_API_KEY")] + pub anthropic_api_key: Option, + #[serde(rename = "GOOGLE_API_KEY")] + pub google_api_key: Option, + #[serde(rename = "DATABRICKS_TOKEN")] + pub databricks_token: Option, + #[serde(rename = "AZURE_OPENAI_API_KEY")] + pub azure_openai_api_key: Option, + #[serde(rename = "OPENROUTER_API_KEY")] + pub openrouter_api_key: Option, +} + +impl GooseConfigUpdate { + pub fn apply_to_config(&self, config: &Config) -> Result<(), ConfigError> { + let mut param_updates: Vec<(String, serde_json::Value)> = Vec::new(); + let mut secret_updates: Vec<(String, serde_json::Value)> = Vec::new(); + + macro_rules! push_param { + ($field:expr, $key:expr) => { + if let Some(ref v) = $field { + if let Ok(json) = serde_json::to_value(v) { + param_updates.push(($key.to_string(), json)); + } + } + }; + } + + macro_rules! push_secret { + ($field:expr, $key:expr) => { + if let Some(ref v) = $field { + if let Ok(json) = serde_json::to_value(v) { + secret_updates.push(($key.to_string(), json)); + } + } + }; + } + + push_param!(self.goose_provider, "GOOSE_PROVIDER"); + push_param!(self.goose_model, "GOOSE_MODEL"); + push_param!(self.goose_mode, "GOOSE_MODE"); + push_param!(self.goose_max_tokens, "GOOSE_MAX_TOKENS"); + push_param!(self.goose_context_limit, "GOOSE_CONTEXT_LIMIT"); + push_param!(self.goose_input_limit, "GOOSE_INPUT_LIMIT"); + push_param!(self.goose_max_turns, "GOOSE_MAX_TURNS"); + push_param!(self.goose_max_active_agents, "GOOSE_MAX_ACTIVE_AGENTS"); + push_param!( + self.goose_auto_compact_threshold, + "GOOSE_AUTO_COMPACT_THRESHOLD" + ); + push_param!( + self.goose_tool_pair_summarization, + "GOOSE_TOOL_PAIR_SUMMARIZATION" + ); + push_param!(self.goose_tool_call_cutoff, "GOOSE_TOOL_CALL_CUTOFF"); + push_param!(self.goose_stream_timeout, "GOOSE_STREAM_TIMEOUT"); + push_param!(self.goose_search_paths, "GOOSE_SEARCH_PATHS"); + push_param!( + self.goose_disable_session_naming, + "GOOSE_DISABLE_SESSION_NAMING" + ); + push_param!(self.goose_disable_keyring, "GOOSE_DISABLE_KEYRING"); + push_param!(self.goose_telemetry_enabled, "GOOSE_TELEMETRY_ENABLED"); + push_param!( + self.goose_default_extension_timeout, + "GOOSE_DEFAULT_EXTENSION_TIMEOUT" + ); + push_param!(self.goose_prompt_editor, "GOOSE_PROMPT_EDITOR"); + push_param!( + self.goose_prompt_editor_always, + "GOOSE_PROMPT_EDITOR_ALWAYS" + ); + push_param!(self.goose_allowlist, "GOOSE_ALLOWLIST"); + push_param!( + self.goose_system_prompt_file_path, + "GOOSE_SYSTEM_PROMPT_FILE_PATH" + ); + push_param!(self.goose_debug, "GOOSE_DEBUG"); + push_param!(self.goose_show_full_output, "GOOSE_SHOW_FULL_OUTPUT"); + push_param!(self.goose_status_hook, "GOOSE_STATUS_HOOK"); + push_param!( + self.goose_local_enable_thinking, + "GOOSE_LOCAL_ENABLE_THINKING" + ); + push_param!( + self.goose_databricks_client_request_id, + "GOOSE_DATABRICKS_CLIENT_REQUEST_ID" + ); + push_param!(self.context_file_names, "CONTEXT_FILE_NAMES"); + push_param!(self.edit_mode, "EDIT_MODE"); + push_param!(self.random_thinking_messages, "RANDOM_THINKING_MESSAGES"); + push_param!(self.code_mode_tool_disclosure, "CODE_MODE_TOOL_DISCLOSURE"); + push_param!(self.goose_client_cert_path, "GOOSE_CLIENT_CERT_PATH"); + push_param!(self.goose_client_key_path, "GOOSE_CLIENT_KEY_PATH"); + push_param!(self.goose_ca_cert_path, "GOOSE_CA_CERT_PATH"); + push_param!(self.goose_planner_provider, "GOOSE_PLANNER_PROVIDER"); + push_param!(self.goose_planner_model, "GOOSE_PLANNER_MODEL"); + push_param!(self.goose_subagent_provider, "GOOSE_SUBAGENT_PROVIDER"); + push_param!(self.goose_subagent_model, "GOOSE_SUBAGENT_MODEL"); + push_param!(self.goose_subagent_max_turns, "GOOSE_SUBAGENT_MAX_TURNS"); + push_param!( + self.goose_max_background_tasks, + "GOOSE_MAX_BACKGROUND_TASKS" + ); + push_param!(self.goose_recipe_github_repo, "GOOSE_RECIPE_GITHUB_REPO"); + push_param!( + self.goose_recipe_retry_timeout_seconds, + "GOOSE_RECIPE_RETRY_TIMEOUT_SECONDS" + ); + push_param!( + self.goose_recipe_on_failure_timeout_seconds, + "GOOSE_RECIPE_ON_FAILURE_TIMEOUT_SECONDS" + ); + push_param!(self.goose_cli_min_priority, "GOOSE_CLI_MIN_PRIORITY"); + push_param!(self.goose_cli_theme, "GOOSE_CLI_THEME"); + push_param!(self.goose_cli_light_theme, "GOOSE_CLI_LIGHT_THEME"); + push_param!(self.goose_cli_dark_theme, "GOOSE_CLI_DARK_THEME"); + push_param!(self.goose_cli_show_cost, "GOOSE_CLI_SHOW_COST"); + push_param!(self.goose_cli_show_thinking, "GOOSE_CLI_SHOW_THINKING"); + push_param!(self.goose_cli_newline_key, "GOOSE_CLI_NEWLINE_KEY"); + push_param!(self.claude_code_command, "CLAUDE_CODE_COMMAND"); + push_param!(self.gemini_cli_command, "GEMINI_CLI_COMMAND"); + push_param!(self.cursor_agent_command, "CURSOR_AGENT_COMMAND"); + push_param!(self.codex_command, "CODEX_COMMAND"); + push_param!(self.codex_reasoning_effort, "CODEX_REASONING_EFFORT"); + push_param!(self.codex_enable_skills, "CODEX_ENABLE_SKILLS"); + push_param!(self.codex_skip_git_check, "CODEX_SKIP_GIT_CHECK"); + push_param!( + self.chatgpt_codex_reasoning_effort, + "CHATGPT_CODEX_REASONING_EFFORT" + ); + push_param!(self.claude_thinking_type, "CLAUDE_THINKING_TYPE"); + push_param!(self.claude_thinking_effort, "CLAUDE_THINKING_EFFORT"); + push_param!(self.claude_thinking_budget, "CLAUDE_THINKING_BUDGET"); + push_param!(self.gemini3_thinking_level, "GEMINI3_THINKING_LEVEL"); + push_param!(self.gemini25_thinking_budget, "GEMINI25_THINKING_BUDGET"); + push_param!(self.security_prompt_enabled, "SECURITY_PROMPT_ENABLED"); + push_param!(self.security_prompt_threshold, "SECURITY_PROMPT_THRESHOLD"); + push_param!( + self.security_prompt_classifier_enabled, + "SECURITY_PROMPT_CLASSIFIER_ENABLED" + ); + push_param!( + self.security_prompt_classifier_model, + "SECURITY_PROMPT_CLASSIFIER_MODEL" + ); + push_param!( + self.security_prompt_classifier_endpoint, + "SECURITY_PROMPT_CLASSIFIER_ENDPOINT" + ); + push_param!( + self.security_command_classifier_enabled, + "SECURITY_COMMAND_CLASSIFIER_ENABLED" + ); + push_param!(self.openai_host, "OPENAI_HOST"); + push_param!(self.openai_base_url, "OPENAI_BASE_URL"); + push_param!(self.openai_base_path, "OPENAI_BASE_PATH"); + push_param!(self.openai_organization, "OPENAI_ORGANIZATION"); + push_param!(self.openai_project, "OPENAI_PROJECT"); + push_param!(self.openai_timeout, "OPENAI_TIMEOUT"); + push_param!(self.anthropic_host, "ANTHROPIC_HOST"); + push_param!(self.ollama_host, "OLLAMA_HOST"); + push_param!(self.ollama_timeout, "OLLAMA_TIMEOUT"); + push_param!(self.ollama_stream_timeout, "OLLAMA_STREAM_TIMEOUT"); + push_param!(self.ollama_stream_usage, "OLLAMA_STREAM_USAGE"); + push_param!(self.databricks_host, "DATABRICKS_HOST"); + push_param!(self.databricks_max_retries, "DATABRICKS_MAX_RETRIES"); + push_param!( + self.databricks_initial_retry_interval_ms, + "DATABRICKS_INITIAL_RETRY_INTERVAL_MS" + ); + push_param!( + self.databricks_backoff_multiplier, + "DATABRICKS_BACKOFF_MULTIPLIER" + ); + push_param!( + self.databricks_max_retry_interval_ms, + "DATABRICKS_MAX_RETRY_INTERVAL_MS" + ); + push_param!(self.azure_openai_endpoint, "AZURE_OPENAI_ENDPOINT"); + push_param!( + self.azure_openai_deployment_name, + "AZURE_OPENAI_DEPLOYMENT_NAME" + ); + push_param!(self.azure_openai_api_version, "AZURE_OPENAI_API_VERSION"); + push_param!(self.google_host, "GOOGLE_HOST"); + push_param!(self.gcp_project_id, "GCP_PROJECT_ID"); + push_param!(self.gcp_location, "GCP_LOCATION"); + push_param!(self.gcp_max_retries, "GCP_MAX_RETRIES"); + push_param!( + self.gcp_initial_retry_interval_ms, + "GCP_INITIAL_RETRY_INTERVAL_MS" + ); + push_param!(self.gcp_backoff_multiplier, "GCP_BACKOFF_MULTIPLIER"); + push_param!(self.gcp_max_retry_interval_ms, "GCP_MAX_RETRY_INTERVAL_MS"); + push_param!(self.aws_region, "AWS_REGION"); + push_param!(self.aws_profile, "AWS_PROFILE"); + push_param!(self.bedrock_max_retries, "BEDROCK_MAX_RETRIES"); + push_param!( + self.bedrock_initial_retry_interval_ms, + "BEDROCK_INITIAL_RETRY_INTERVAL_MS" + ); + push_param!( + self.bedrock_backoff_multiplier, + "BEDROCK_BACKOFF_MULTIPLIER" + ); + push_param!( + self.bedrock_max_retry_interval_ms, + "BEDROCK_MAX_RETRY_INTERVAL_MS" + ); + push_param!(self.bedrock_enable_caching, "BEDROCK_ENABLE_CACHING"); + push_param!(self.sagemaker_endpoint_name, "SAGEMAKER_ENDPOINT_NAME"); + push_param!(self.litellm_host, "LITELLM_HOST"); + push_param!(self.litellm_base_path, "LITELLM_BASE_PATH"); + push_param!(self.litellm_timeout, "LITELLM_TIMEOUT"); + push_param!(self.snowflake_host, "SNOWFLAKE_HOST"); + push_param!(self.github_copilot_host, "GITHUB_COPILOT_HOST"); + push_param!(self.github_copilot_client_id, "GITHUB_COPILOT_CLIENT_ID"); + push_param!(self.github_copilot_token_url, "GITHUB_COPILOT_TOKEN_URL"); + push_param!(self.xai_host, "XAI_HOST"); + push_param!(self.openrouter_host, "OPENROUTER_HOST"); + push_param!(self.venice_host, "VENICE_HOST"); + push_param!(self.venice_base_path, "VENICE_BASE_PATH"); + push_param!(self.venice_models_path, "VENICE_MODELS_PATH"); + push_param!(self.tetrate_host, "TETRATE_HOST"); + push_param!(self.avian_host, "AVIAN_HOST"); + push_param!( + self.otel_exporter_otlp_endpoint, + "otel_exporter_otlp_endpoint" + ); + push_param!( + self.otel_exporter_otlp_timeout, + "otel_exporter_otlp_timeout" + ); + push_param!(self.tunnel_auto_start, "tunnel_auto_start"); + push_param!(self.extensions, "extensions"); + push_param!(self.slash_commands, "slash_commands"); + push_param!(self.experiments, "experiments"); + + push_secret!(self.openai_api_key, "OPENAI_API_KEY"); + push_secret!(self.anthropic_api_key, "ANTHROPIC_API_KEY"); + push_secret!(self.google_api_key, "GOOGLE_API_KEY"); + push_secret!(self.databricks_token, "DATABRICKS_TOKEN"); + push_secret!(self.azure_openai_api_key, "AZURE_OPENAI_API_KEY"); + push_secret!(self.openrouter_api_key, "OPENROUTER_API_KEY"); + + config.set_param_values(¶m_updates)?; + config.set_secret_values(&secret_updates) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use schemars::schema_for; + + #[test] + fn all_keys_matches_struct_fields() { + let schema = schema_for!(GooseConfigSchema); + let obj = schema.as_object().expect("schema should be an object"); + let properties = obj + .get("properties") + .and_then(|p| p.as_object()) + .expect("schema should have properties"); + + let schema_keys: std::collections::HashSet<&str> = + properties.keys().map(|k| k.as_str()).collect(); + + for key in GooseConfigSchema::ALL_KEYS { + assert!( + schema_keys.contains(key), + "ALL_KEYS contains '{key}' but GooseConfigSchema has no field with serde(rename = \"{key}\")" + ); + } + + // Category B keys are in the struct but NOT in ALL_KEYS — that's intentional + let category_b = ["extensions", "slash_commands", "experiments"]; + for key in &category_b { + assert!( + schema_keys.contains(key), + "Category B key '{key}' should be in the schema struct for IDE autocomplete" + ); + assert!( + !GooseConfigSchema::has_key(key), + "Category B key '{key}' should NOT be in ALL_KEYS" + ); + } + } +} diff --git a/crates/goose/src/slash_commands.rs b/crates/goose/src/slash_commands.rs index 5e7065db00..bb8678b2c1 100644 --- a/crates/goose/src/slash_commands.rs +++ b/crates/goose/src/slash_commands.rs @@ -1,15 +1,17 @@ use std::path::PathBuf; use anyhow::Result; +use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use tracing::warn; +use utoipa::ToSchema; use crate::config::Config; use crate::recipe::Recipe; const SLASH_COMMANDS_CONFIG_KEY: &str = "slash_commands"; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, ToSchema)] pub struct SlashCommandMapping { pub command: String, pub recipe_path: String, diff --git a/ui/desktop/openapi.json b/ui/desktop/openapi.json index 3f0a16d6d8..1efa668135 100644 --- a/ui/desktop/openapi.json +++ b/ui/desktop/openapi.json @@ -1557,6 +1557,60 @@ } } }, + "/config/typed": { + "get": { + "tags": [ + "super::routes::config_management" + ], + "operationId": "read_typed_config", + "responses": { + "200": { + "description": "All configuration values (typed)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GooseConfigSchema" + } + } + } + }, + "500": { + "description": "Internal server error" + } + } + }, + "patch": { + "tags": [ + "super::routes::config_management" + ], + "operationId": "patch_typed_config", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GooseConfigUpdate" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Configuration updated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GooseConfigSchema" + } + } + } + }, + "500": { + "description": "Internal server error" + } + } + } + }, "/config/upsert": { "post": { "tags": [ @@ -5517,6 +5571,1145 @@ } ] }, + "GooseConfigSchema": { + "type": "object", + "description": "JSON Schema representation of Goose's config.yaml.\n\nAll keys are optional. Unknown keys are allowed (additionalProperties: true)\nbecause Goose passes undocumented provider-specific keys through as\nenvironment variable overrides.", + "properties": { + "ANTHROPIC_HOST": { + "type": "string", + "nullable": true + }, + "AVIAN_HOST": { + "type": "string", + "nullable": true + }, + "AWS_PROFILE": { + "type": "string", + "nullable": true + }, + "AWS_REGION": { + "type": "string", + "nullable": true + }, + "AZURE_OPENAI_API_VERSION": { + "type": "string", + "nullable": true + }, + "AZURE_OPENAI_DEPLOYMENT_NAME": { + "type": "string", + "nullable": true + }, + "AZURE_OPENAI_ENDPOINT": { + "type": "string", + "nullable": true + }, + "BEDROCK_BACKOFF_MULTIPLIER": { + "type": "number", + "format": "double", + "nullable": true + }, + "BEDROCK_ENABLE_CACHING": { + "type": "boolean", + "nullable": true + }, + "BEDROCK_INITIAL_RETRY_INTERVAL_MS": { + "type": "integer", + "format": "int64", + "nullable": true, + "minimum": 0 + }, + "BEDROCK_MAX_RETRIES": { + "type": "integer", + "format": "int64", + "nullable": true, + "minimum": 0 + }, + "BEDROCK_MAX_RETRY_INTERVAL_MS": { + "type": "integer", + "format": "int64", + "nullable": true, + "minimum": 0 + }, + "CHATGPT_CODEX_REASONING_EFFORT": { + "type": "string", + "nullable": true + }, + "CLAUDE_CODE_COMMAND": { + "type": "string", + "nullable": true + }, + "CLAUDE_THINKING_BUDGET": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "CLAUDE_THINKING_EFFORT": { + "type": "string", + "nullable": true + }, + "CLAUDE_THINKING_TYPE": { + "type": "string", + "nullable": true + }, + "CODEX_COMMAND": { + "type": "string", + "nullable": true + }, + "CODEX_ENABLE_SKILLS": { + "type": "string", + "nullable": true + }, + "CODEX_REASONING_EFFORT": { + "type": "string", + "nullable": true + }, + "CODEX_SKIP_GIT_CHECK": { + "type": "string", + "nullable": true + }, + "CODE_MODE_TOOL_DISCLOSURE": { + "type": "string", + "nullable": true + }, + "CONTEXT_FILE_NAMES": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "CURSOR_AGENT_COMMAND": { + "type": "string", + "nullable": true + }, + "DATABRICKS_BACKOFF_MULTIPLIER": { + "type": "string", + "nullable": true + }, + "DATABRICKS_HOST": { + "type": "string", + "nullable": true + }, + "DATABRICKS_INITIAL_RETRY_INTERVAL_MS": { + "type": "string", + "nullable": true + }, + "DATABRICKS_MAX_RETRIES": { + "type": "string", + "nullable": true + }, + "DATABRICKS_MAX_RETRY_INTERVAL_MS": { + "type": "string", + "nullable": true + }, + "EDIT_MODE": { + "type": "string", + "nullable": true + }, + "GCP_BACKOFF_MULTIPLIER": { + "type": "string", + "nullable": true + }, + "GCP_INITIAL_RETRY_INTERVAL_MS": { + "type": "string", + "nullable": true + }, + "GCP_LOCATION": { + "type": "string", + "nullable": true + }, + "GCP_MAX_RETRIES": { + "type": "string", + "nullable": true + }, + "GCP_MAX_RETRY_INTERVAL_MS": { + "type": "string", + "nullable": true + }, + "GCP_PROJECT_ID": { + "type": "string", + "nullable": true + }, + "GEMINI25_THINKING_BUDGET": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "GEMINI3_THINKING_LEVEL": { + "type": "string", + "nullable": true + }, + "GEMINI_CLI_COMMAND": { + "type": "string", + "nullable": true + }, + "GITHUB_COPILOT_CLIENT_ID": { + "type": "string", + "nullable": true + }, + "GITHUB_COPILOT_HOST": { + "type": "string", + "nullable": true + }, + "GITHUB_COPILOT_TOKEN_URL": { + "type": "string", + "nullable": true + }, + "GOOGLE_HOST": { + "type": "string", + "nullable": true + }, + "GOOSE_ALLOWLIST": { + "type": "string", + "nullable": true + }, + "GOOSE_AUTO_COMPACT_THRESHOLD": { + "type": "number", + "format": "double", + "nullable": true + }, + "GOOSE_CA_CERT_PATH": { + "type": "string", + "nullable": true + }, + "GOOSE_CLIENT_CERT_PATH": { + "type": "string", + "nullable": true + }, + "GOOSE_CLIENT_KEY_PATH": { + "type": "string", + "nullable": true + }, + "GOOSE_CLI_DARK_THEME": { + "type": "string", + "nullable": true + }, + "GOOSE_CLI_LIGHT_THEME": { + "type": "string", + "nullable": true + }, + "GOOSE_CLI_MIN_PRIORITY": { + "type": "number", + "format": "float", + "nullable": true + }, + "GOOSE_CLI_NEWLINE_KEY": { + "type": "string", + "nullable": true + }, + "GOOSE_CLI_SHOW_COST": { + "type": "boolean", + "nullable": true + }, + "GOOSE_CLI_SHOW_THINKING": { + "type": "boolean", + "nullable": true + }, + "GOOSE_CLI_THEME": { + "type": "string", + "nullable": true + }, + "GOOSE_CONTEXT_LIMIT": { + "type": "integer", + "format": "int64", + "nullable": true, + "minimum": 0 + }, + "GOOSE_DATABRICKS_CLIENT_REQUEST_ID": { + "type": "boolean", + "nullable": true + }, + "GOOSE_DEBUG": { + "type": "boolean", + "nullable": true + }, + "GOOSE_DEFAULT_EXTENSION_TIMEOUT": { + "type": "integer", + "format": "int64", + "nullable": true, + "minimum": 0 + }, + "GOOSE_DISABLE_KEYRING": { + "type": "string", + "nullable": true + }, + "GOOSE_DISABLE_SESSION_NAMING": { + "type": "boolean", + "nullable": true + }, + "GOOSE_INPUT_LIMIT": { + "type": "integer", + "format": "int64", + "nullable": true, + "minimum": 0 + }, + "GOOSE_LOCAL_ENABLE_THINKING": { + "type": "boolean", + "nullable": true + }, + "GOOSE_MAX_ACTIVE_AGENTS": { + "type": "integer", + "format": "int64", + "nullable": true, + "minimum": 0 + }, + "GOOSE_MAX_BACKGROUND_TASKS": { + "type": "integer", + "format": "int64", + "nullable": true, + "minimum": 0 + }, + "GOOSE_MAX_TOKENS": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "GOOSE_MAX_TURNS": { + "type": "integer", + "format": "int32", + "nullable": true, + "minimum": 0 + }, + "GOOSE_MODE": { + "allOf": [ + { + "$ref": "#/components/schemas/GooseMode" + } + ], + "nullable": true + }, + "GOOSE_MODEL": { + "type": "string", + "nullable": true + }, + "GOOSE_PLANNER_MODEL": { + "type": "string", + "nullable": true + }, + "GOOSE_PLANNER_PROVIDER": { + "type": "string", + "nullable": true + }, + "GOOSE_PROMPT_EDITOR": { + "type": "string", + "nullable": true + }, + "GOOSE_PROMPT_EDITOR_ALWAYS": { + "type": "boolean", + "nullable": true + }, + "GOOSE_PROVIDER": { + "type": "string", + "nullable": true + }, + "GOOSE_RECIPE_GITHUB_REPO": { + "type": "string", + "nullable": true + }, + "GOOSE_RECIPE_ON_FAILURE_TIMEOUT_SECONDS": { + "type": "integer", + "format": "int64", + "nullable": true, + "minimum": 0 + }, + "GOOSE_RECIPE_RETRY_TIMEOUT_SECONDS": { + "type": "integer", + "format": "int64", + "nullable": true, + "minimum": 0 + }, + "GOOSE_SEARCH_PATHS": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "GOOSE_SHOW_FULL_OUTPUT": { + "type": "boolean", + "nullable": true + }, + "GOOSE_STATUS_HOOK": { + "type": "string", + "nullable": true + }, + "GOOSE_STREAM_TIMEOUT": { + "type": "integer", + "format": "int64", + "nullable": true, + "minimum": 0 + }, + "GOOSE_SUBAGENT_MAX_TURNS": { + "type": "integer", + "format": "int64", + "nullable": true, + "minimum": 0 + }, + "GOOSE_SUBAGENT_MODEL": { + "type": "string", + "nullable": true + }, + "GOOSE_SUBAGENT_PROVIDER": { + "type": "string", + "nullable": true + }, + "GOOSE_SYSTEM_PROMPT_FILE_PATH": { + "type": "string", + "nullable": true + }, + "GOOSE_TELEMETRY_ENABLED": { + "type": "boolean", + "nullable": true + }, + "GOOSE_TOOL_CALL_CUTOFF": { + "type": "integer", + "format": "int64", + "nullable": true, + "minimum": 0 + }, + "GOOSE_TOOL_PAIR_SUMMARIZATION": { + "type": "boolean", + "nullable": true + }, + "LITELLM_BASE_PATH": { + "type": "string", + "nullable": true + }, + "LITELLM_HOST": { + "type": "string", + "nullable": true + }, + "LITELLM_TIMEOUT": { + "type": "integer", + "format": "int64", + "nullable": true, + "minimum": 0 + }, + "OLLAMA_HOST": { + "type": "string", + "nullable": true + }, + "OLLAMA_STREAM_TIMEOUT": { + "type": "integer", + "format": "int64", + "nullable": true, + "minimum": 0 + }, + "OLLAMA_STREAM_USAGE": { + "type": "boolean", + "nullable": true + }, + "OLLAMA_TIMEOUT": { + "type": "integer", + "format": "int64", + "nullable": true, + "minimum": 0 + }, + "OPENAI_BASE_PATH": { + "type": "string", + "nullable": true + }, + "OPENAI_BASE_URL": { + "type": "string", + "nullable": true + }, + "OPENAI_HOST": { + "type": "string", + "nullable": true + }, + "OPENAI_ORGANIZATION": { + "type": "string", + "nullable": true + }, + "OPENAI_PROJECT": { + "type": "string", + "nullable": true + }, + "OPENAI_TIMEOUT": { + "type": "integer", + "format": "int64", + "nullable": true, + "minimum": 0 + }, + "OPENROUTER_HOST": { + "type": "string", + "nullable": true + }, + "RANDOM_THINKING_MESSAGES": { + "type": "boolean", + "nullable": true + }, + "SAGEMAKER_ENDPOINT_NAME": { + "type": "string", + "nullable": true + }, + "SECURITY_COMMAND_CLASSIFIER_ENABLED": { + "type": "boolean", + "nullable": true + }, + "SECURITY_PROMPT_CLASSIFIER_ENABLED": { + "type": "boolean", + "nullable": true + }, + "SECURITY_PROMPT_CLASSIFIER_ENDPOINT": { + "type": "string", + "nullable": true + }, + "SECURITY_PROMPT_CLASSIFIER_MODEL": { + "type": "string", + "nullable": true + }, + "SECURITY_PROMPT_ENABLED": { + "type": "boolean", + "nullable": true + }, + "SECURITY_PROMPT_THRESHOLD": { + "type": "number", + "format": "double", + "nullable": true + }, + "SNOWFLAKE_HOST": { + "type": "string", + "nullable": true + }, + "TETRATE_HOST": { + "type": "string", + "nullable": true + }, + "VENICE_BASE_PATH": { + "type": "string", + "nullable": true + }, + "VENICE_HOST": { + "type": "string", + "nullable": true + }, + "VENICE_MODELS_PATH": { + "type": "string", + "nullable": true + }, + "XAI_HOST": { + "type": "string", + "nullable": true + }, + "experiments": { + "type": "object", + "additionalProperties": { + "type": "boolean" + }, + "nullable": true + }, + "extensions": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/ExtensionEntry" + }, + "nullable": true + }, + "otel_exporter_otlp_endpoint": { + "type": "string", + "nullable": true + }, + "otel_exporter_otlp_timeout": { + "type": "integer", + "format": "int64", + "nullable": true, + "minimum": 0 + }, + "slash_commands": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SlashCommandMapping" + }, + "nullable": true + }, + "tunnel_auto_start": { + "type": "boolean", + "nullable": true + } + } + }, + "GooseConfigUpdate": { + "type": "object", + "properties": { + "ANTHROPIC_API_KEY": { + "type": "string", + "nullable": true + }, + "ANTHROPIC_HOST": { + "type": "string", + "nullable": true + }, + "AVIAN_HOST": { + "type": "string", + "nullable": true + }, + "AWS_PROFILE": { + "type": "string", + "nullable": true + }, + "AWS_REGION": { + "type": "string", + "nullable": true + }, + "AZURE_OPENAI_API_KEY": { + "type": "string", + "nullable": true + }, + "AZURE_OPENAI_API_VERSION": { + "type": "string", + "nullable": true + }, + "AZURE_OPENAI_DEPLOYMENT_NAME": { + "type": "string", + "nullable": true + }, + "AZURE_OPENAI_ENDPOINT": { + "type": "string", + "nullable": true + }, + "BEDROCK_BACKOFF_MULTIPLIER": { + "type": "number", + "format": "double", + "nullable": true + }, + "BEDROCK_ENABLE_CACHING": { + "type": "boolean", + "nullable": true + }, + "BEDROCK_INITIAL_RETRY_INTERVAL_MS": { + "type": "integer", + "format": "int64", + "nullable": true, + "minimum": 0 + }, + "BEDROCK_MAX_RETRIES": { + "type": "integer", + "format": "int64", + "nullable": true, + "minimum": 0 + }, + "BEDROCK_MAX_RETRY_INTERVAL_MS": { + "type": "integer", + "format": "int64", + "nullable": true, + "minimum": 0 + }, + "CHATGPT_CODEX_REASONING_EFFORT": { + "type": "string", + "nullable": true + }, + "CLAUDE_CODE_COMMAND": { + "type": "string", + "nullable": true + }, + "CLAUDE_THINKING_BUDGET": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "CLAUDE_THINKING_EFFORT": { + "type": "string", + "nullable": true + }, + "CLAUDE_THINKING_TYPE": { + "type": "string", + "nullable": true + }, + "CODEX_COMMAND": { + "type": "string", + "nullable": true + }, + "CODEX_ENABLE_SKILLS": { + "type": "string", + "nullable": true + }, + "CODEX_REASONING_EFFORT": { + "type": "string", + "nullable": true + }, + "CODEX_SKIP_GIT_CHECK": { + "type": "string", + "nullable": true + }, + "CODE_MODE_TOOL_DISCLOSURE": { + "type": "string", + "nullable": true + }, + "CONTEXT_FILE_NAMES": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "CURSOR_AGENT_COMMAND": { + "type": "string", + "nullable": true + }, + "DATABRICKS_BACKOFF_MULTIPLIER": { + "type": "string", + "nullable": true + }, + "DATABRICKS_HOST": { + "type": "string", + "nullable": true + }, + "DATABRICKS_INITIAL_RETRY_INTERVAL_MS": { + "type": "string", + "nullable": true + }, + "DATABRICKS_MAX_RETRIES": { + "type": "string", + "nullable": true + }, + "DATABRICKS_MAX_RETRY_INTERVAL_MS": { + "type": "string", + "nullable": true + }, + "DATABRICKS_TOKEN": { + "type": "string", + "nullable": true + }, + "EDIT_MODE": { + "type": "string", + "nullable": true + }, + "GCP_BACKOFF_MULTIPLIER": { + "type": "string", + "nullable": true + }, + "GCP_INITIAL_RETRY_INTERVAL_MS": { + "type": "string", + "nullable": true + }, + "GCP_LOCATION": { + "type": "string", + "nullable": true + }, + "GCP_MAX_RETRIES": { + "type": "string", + "nullable": true + }, + "GCP_MAX_RETRY_INTERVAL_MS": { + "type": "string", + "nullable": true + }, + "GCP_PROJECT_ID": { + "type": "string", + "nullable": true + }, + "GEMINI25_THINKING_BUDGET": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "GEMINI3_THINKING_LEVEL": { + "type": "string", + "nullable": true + }, + "GEMINI_CLI_COMMAND": { + "type": "string", + "nullable": true + }, + "GITHUB_COPILOT_CLIENT_ID": { + "type": "string", + "nullable": true + }, + "GITHUB_COPILOT_HOST": { + "type": "string", + "nullable": true + }, + "GITHUB_COPILOT_TOKEN_URL": { + "type": "string", + "nullable": true + }, + "GOOGLE_API_KEY": { + "type": "string", + "nullable": true + }, + "GOOGLE_HOST": { + "type": "string", + "nullable": true + }, + "GOOSE_ALLOWLIST": { + "type": "string", + "nullable": true + }, + "GOOSE_AUTO_COMPACT_THRESHOLD": { + "type": "number", + "format": "double", + "nullable": true + }, + "GOOSE_CA_CERT_PATH": { + "type": "string", + "nullable": true + }, + "GOOSE_CLIENT_CERT_PATH": { + "type": "string", + "nullable": true + }, + "GOOSE_CLIENT_KEY_PATH": { + "type": "string", + "nullable": true + }, + "GOOSE_CLI_DARK_THEME": { + "type": "string", + "nullable": true + }, + "GOOSE_CLI_LIGHT_THEME": { + "type": "string", + "nullable": true + }, + "GOOSE_CLI_MIN_PRIORITY": { + "type": "number", + "format": "float", + "nullable": true + }, + "GOOSE_CLI_NEWLINE_KEY": { + "type": "string", + "nullable": true + }, + "GOOSE_CLI_SHOW_COST": { + "type": "boolean", + "nullable": true + }, + "GOOSE_CLI_SHOW_THINKING": { + "type": "boolean", + "nullable": true + }, + "GOOSE_CLI_THEME": { + "type": "string", + "nullable": true + }, + "GOOSE_CONTEXT_LIMIT": { + "type": "integer", + "format": "int64", + "nullable": true, + "minimum": 0 + }, + "GOOSE_DATABRICKS_CLIENT_REQUEST_ID": { + "type": "boolean", + "nullable": true + }, + "GOOSE_DEBUG": { + "type": "boolean", + "nullable": true + }, + "GOOSE_DEFAULT_EXTENSION_TIMEOUT": { + "type": "integer", + "format": "int64", + "nullable": true, + "minimum": 0 + }, + "GOOSE_DISABLE_KEYRING": { + "type": "string", + "nullable": true + }, + "GOOSE_DISABLE_SESSION_NAMING": { + "type": "boolean", + "nullable": true + }, + "GOOSE_INPUT_LIMIT": { + "type": "integer", + "format": "int64", + "nullable": true, + "minimum": 0 + }, + "GOOSE_LOCAL_ENABLE_THINKING": { + "type": "boolean", + "nullable": true + }, + "GOOSE_MAX_ACTIVE_AGENTS": { + "type": "integer", + "format": "int64", + "nullable": true, + "minimum": 0 + }, + "GOOSE_MAX_BACKGROUND_TASKS": { + "type": "integer", + "format": "int64", + "nullable": true, + "minimum": 0 + }, + "GOOSE_MAX_TOKENS": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "GOOSE_MAX_TURNS": { + "type": "integer", + "format": "int32", + "nullable": true, + "minimum": 0 + }, + "GOOSE_MODE": { + "allOf": [ + { + "$ref": "#/components/schemas/GooseMode" + } + ], + "nullable": true + }, + "GOOSE_MODEL": { + "type": "string", + "nullable": true + }, + "GOOSE_PLANNER_MODEL": { + "type": "string", + "nullable": true + }, + "GOOSE_PLANNER_PROVIDER": { + "type": "string", + "nullable": true + }, + "GOOSE_PROMPT_EDITOR": { + "type": "string", + "nullable": true + }, + "GOOSE_PROMPT_EDITOR_ALWAYS": { + "type": "boolean", + "nullable": true + }, + "GOOSE_PROVIDER": { + "type": "string", + "nullable": true + }, + "GOOSE_RECIPE_GITHUB_REPO": { + "type": "string", + "nullable": true + }, + "GOOSE_RECIPE_ON_FAILURE_TIMEOUT_SECONDS": { + "type": "integer", + "format": "int64", + "nullable": true, + "minimum": 0 + }, + "GOOSE_RECIPE_RETRY_TIMEOUT_SECONDS": { + "type": "integer", + "format": "int64", + "nullable": true, + "minimum": 0 + }, + "GOOSE_SEARCH_PATHS": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "GOOSE_SHOW_FULL_OUTPUT": { + "type": "boolean", + "nullable": true + }, + "GOOSE_STATUS_HOOK": { + "type": "string", + "nullable": true + }, + "GOOSE_STREAM_TIMEOUT": { + "type": "integer", + "format": "int64", + "nullable": true, + "minimum": 0 + }, + "GOOSE_SUBAGENT_MAX_TURNS": { + "type": "integer", + "format": "int64", + "nullable": true, + "minimum": 0 + }, + "GOOSE_SUBAGENT_MODEL": { + "type": "string", + "nullable": true + }, + "GOOSE_SUBAGENT_PROVIDER": { + "type": "string", + "nullable": true + }, + "GOOSE_SYSTEM_PROMPT_FILE_PATH": { + "type": "string", + "nullable": true + }, + "GOOSE_TELEMETRY_ENABLED": { + "type": "boolean", + "nullable": true + }, + "GOOSE_TOOL_CALL_CUTOFF": { + "type": "integer", + "format": "int64", + "nullable": true, + "minimum": 0 + }, + "GOOSE_TOOL_PAIR_SUMMARIZATION": { + "type": "boolean", + "nullable": true + }, + "LITELLM_BASE_PATH": { + "type": "string", + "nullable": true + }, + "LITELLM_HOST": { + "type": "string", + "nullable": true + }, + "LITELLM_TIMEOUT": { + "type": "integer", + "format": "int64", + "nullable": true, + "minimum": 0 + }, + "OLLAMA_HOST": { + "type": "string", + "nullable": true + }, + "OLLAMA_STREAM_TIMEOUT": { + "type": "integer", + "format": "int64", + "nullable": true, + "minimum": 0 + }, + "OLLAMA_STREAM_USAGE": { + "type": "boolean", + "nullable": true + }, + "OLLAMA_TIMEOUT": { + "type": "integer", + "format": "int64", + "nullable": true, + "minimum": 0 + }, + "OPENAI_API_KEY": { + "type": "string", + "nullable": true + }, + "OPENAI_BASE_PATH": { + "type": "string", + "nullable": true + }, + "OPENAI_BASE_URL": { + "type": "string", + "nullable": true + }, + "OPENAI_HOST": { + "type": "string", + "nullable": true + }, + "OPENAI_ORGANIZATION": { + "type": "string", + "nullable": true + }, + "OPENAI_PROJECT": { + "type": "string", + "nullable": true + }, + "OPENAI_TIMEOUT": { + "type": "integer", + "format": "int64", + "nullable": true, + "minimum": 0 + }, + "OPENROUTER_API_KEY": { + "type": "string", + "nullable": true + }, + "OPENROUTER_HOST": { + "type": "string", + "nullable": true + }, + "RANDOM_THINKING_MESSAGES": { + "type": "boolean", + "nullable": true + }, + "SAGEMAKER_ENDPOINT_NAME": { + "type": "string", + "nullable": true + }, + "SECURITY_COMMAND_CLASSIFIER_ENABLED": { + "type": "boolean", + "nullable": true + }, + "SECURITY_PROMPT_CLASSIFIER_ENABLED": { + "type": "boolean", + "nullable": true + }, + "SECURITY_PROMPT_CLASSIFIER_ENDPOINT": { + "type": "string", + "nullable": true + }, + "SECURITY_PROMPT_CLASSIFIER_MODEL": { + "type": "string", + "nullable": true + }, + "SECURITY_PROMPT_ENABLED": { + "type": "boolean", + "nullable": true + }, + "SECURITY_PROMPT_THRESHOLD": { + "type": "number", + "format": "double", + "nullable": true + }, + "SNOWFLAKE_HOST": { + "type": "string", + "nullable": true + }, + "TETRATE_HOST": { + "type": "string", + "nullable": true + }, + "VENICE_BASE_PATH": { + "type": "string", + "nullable": true + }, + "VENICE_HOST": { + "type": "string", + "nullable": true + }, + "VENICE_MODELS_PATH": { + "type": "string", + "nullable": true + }, + "XAI_HOST": { + "type": "string", + "nullable": true + }, + "experiments": { + "type": "object", + "additionalProperties": { + "type": "boolean" + }, + "nullable": true + }, + "extensions": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/ExtensionEntry" + }, + "nullable": true + }, + "otel_exporter_otlp_endpoint": { + "type": "string", + "nullable": true + }, + "otel_exporter_otlp_timeout": { + "type": "integer", + "format": "int64", + "nullable": true, + "minimum": 0 + }, + "slash_commands": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SlashCommandMapping" + }, + "nullable": true + }, + "tunnel_auto_start": { + "type": "boolean", + "nullable": true + } + } + }, "GooseMode": { "type": "string", "enum": [ @@ -8271,6 +9464,21 @@ } } }, + "SlashCommandMapping": { + "type": "object", + "required": [ + "command", + "recipe_path" + ], + "properties": { + "command": { + "type": "string" + }, + "recipe_path": { + "type": "string" + } + } + }, "SlashCommandsResponse": { "type": "object", "required": [ diff --git a/ui/desktop/src/api/client.gen.ts b/ui/desktop/src/api/client.gen.ts index d81ce3f8f7..cab3c70195 100644 --- a/ui/desktop/src/api/client.gen.ts +++ b/ui/desktop/src/api/client.gen.ts @@ -11,6 +11,6 @@ import type { ClientOptions as ClientOptions2 } from './types.gen'; * `setConfig()`. This is useful for example if you're using Next.js * to ensure your client always has the correct values. */ -export type CreateClientConfig = (override?: Config) => Config & T> | Promise & T>>; +export type CreateClientConfig = (override?: Config) => Config & T>; export const client = createClient(createConfig()); diff --git a/ui/desktop/src/api/client/types.gen.ts b/ui/desktop/src/api/client/types.gen.ts index 8c0df2321e..a3f8616511 100644 --- a/ui/desktop/src/api/client/types.gen.ts +++ b/ui/desktop/src/api/client/types.gen.ts @@ -190,7 +190,7 @@ export type Client = CoreClient */ export type CreateClientConfig = ( override?: Config, -) => Config & T> | Promise & T>>; +) => Config & T>; export interface TDataShape { body?: unknown; diff --git a/ui/desktop/src/api/core/params.gen.ts b/ui/desktop/src/api/core/params.gen.ts index 7955601a5c..6099cab1b4 100644 --- a/ui/desktop/src/api/core/params.gen.ts +++ b/ui/desktop/src/api/core/params.gen.ts @@ -96,7 +96,7 @@ interface Params { const stripEmptySlots = (params: Params) => { for (const [slot, value] of Object.entries(params)) { - if (value && typeof value === 'object' && !Array.isArray(value) && !Object.keys(value).length) { + if (value && typeof value === 'object' && !Object.keys(value).length) { delete params[slot as Slot]; } } diff --git a/ui/desktop/src/api/index.ts b/ui/desktop/src/api/index.ts index fd1811a2c9..f00111687d 100644 --- a/ui/desktop/src/api/index.ts +++ b/ui/desktop/src/api/index.ts @@ -1,4 +1,4 @@ // This file is auto-generated by @hey-api/openapi-ts -export { addExtension, agentAddExtension, agentRemoveExtension, callTool, cancelDownload, cancelLocalModelDownload, checkProvider, cleanupProviderCache, configureProviderOauth, confirmToolAction, createCustomProvider, createRecipe, createSchedule, decodeRecipe, deleteLocalModel, deleteModel, deleteRecipe, deleteSchedule, deleteSession, diagnostics, downloadHfModel, downloadModel, encodeRecipe, exportApp, exportSession, forkSession, getCanonicalModelInfo, getCustomProvider, getDictationConfig, getDownloadProgress, getExtensions, getFeatures, getLocalModelDownloadProgress, getModelSettings, getPrompt, getPrompts, getProviderCatalog, getProviderCatalogTemplate, getProviderModels, getRepoFiles, getSession, getSessionExtensions, getSessionInsights, getSlashCommands, getTools, getTunnelStatus, importApp, importSession, importSessionNostr, inspectRunningJob, killRunningJob, listApps, listLocalModels, listModels, listRecipes, listSchedules, listSessions, mcpUiProxy, type Options, parseRecipe, pauseSchedule, providers, readAllConfig, readConfig, readResource, recipeToYaml, removeConfig, removeCustomProvider, removeExtension, reply, resetPrompt, restartAgent, resumeAgent, runNowHandler, savePrompt, saveRecipe, scanRecipe, scheduleRecipe, searchHfModels, searchSessions, sendTelemetryEvent, sessionCancel, sessionEvents, sessionReply, sessionsHandler, setConfigProvider, setRecipeSlashCommand, shareSessionNostr, startAgent, startNanogptSetup, startOpenrouterSetup, startTetrateSetup, startTunnel, status, stopAgent, stopTunnel, syncFeaturedModels, systemInfo, transcribeDictation, unpauseSchedule, updateAgentProvider, updateCustomProvider, updateFromSession, updateModelSettings, updateSchedule, updateSession, updateSessionName, updateSessionUserRecipeValues, updateWorkingDir, upsertConfig, upsertPermissions, validateConfig } from './sdk.gen'; -export type { ActionRequired, ActionRequiredData, AddExtensionData, AddExtensionErrors, AddExtensionRequest, AddExtensionResponse, AddExtensionResponses, AgentAddExtensionData, AgentAddExtensionErrors, AgentAddExtensionResponse, AgentAddExtensionResponses, AgentRemoveExtensionData, AgentRemoveExtensionErrors, AgentRemoveExtensionResponse, AgentRemoveExtensionResponses, Annotations, Author, AuthorRequest, CallToolData, CallToolError, CallToolErrors, CallToolRequest, CallToolResponse, CallToolResponse2, CallToolResponses, CancelDownloadData, CancelDownloadErrors, CancelDownloadResponses, CancelLocalModelDownloadData, CancelLocalModelDownloadErrors, CancelLocalModelDownloadResponses, CancelRequest, ChatRequest, CheckProviderData, CheckProviderRequest, CleanupProviderCacheData, CleanupProviderCacheErrors, CleanupProviderCacheResponse, CleanupProviderCacheResponses, ClientOptions, CommandType, ConfigKey, ConfigKeyQuery, ConfigResponse, ConfigureProviderOauthData, ConfigureProviderOauthErrors, ConfigureProviderOauthResponses, ConfirmToolActionData, ConfirmToolActionErrors, ConfirmToolActionRequest, ConfirmToolActionResponses, Content, ContentBlock, Conversation, CreateCustomProviderData, CreateCustomProviderErrors, CreateCustomProviderResponse, CreateCustomProviderResponse2, CreateCustomProviderResponses, CreateRecipeData, CreateRecipeErrors, CreateRecipeRequest, CreateRecipeResponse, CreateRecipeResponse2, CreateRecipeResponses, CreateScheduleData, CreateScheduleErrors, CreateScheduleRequest, CreateScheduleResponse, CreateScheduleResponses, CspMetadata, DeclarativeProviderConfig, DecodeRecipeData, DecodeRecipeErrors, DecodeRecipeRequest, DecodeRecipeResponse, DecodeRecipeResponse2, DecodeRecipeResponses, DeleteLocalModelData, DeleteLocalModelErrors, DeleteLocalModelResponses, DeleteModelData, DeleteModelErrors, DeleteModelResponses, DeleteRecipeData, DeleteRecipeErrors, DeleteRecipeRequest, DeleteRecipeResponse, DeleteRecipeResponses, DeleteScheduleData, DeleteScheduleErrors, DeleteScheduleResponse, DeleteScheduleResponses, DeleteSessionData, DeleteSessionErrors, DeleteSessionResponses, DiagnosticsData, DiagnosticsErrors, DiagnosticsResponse, DiagnosticsResponses, DictationProvider, DictationProviderStatus, DownloadHfModelData, DownloadHfModelErrors, DownloadHfModelResponse, DownloadHfModelResponses, DownloadModelData, DownloadModelErrors, DownloadModelRequest, DownloadModelResponses, DownloadProgress, DownloadStatus, EmbeddedResource, EncodeRecipeData, EncodeRecipeErrors, EncodeRecipeRequest, EncodeRecipeResponse, EncodeRecipeResponse2, EncodeRecipeResponses, Envs, EnvVarConfig, ErrorResponse, ExportAppData, ExportAppError, ExportAppErrors, ExportAppResponse, ExportAppResponses, ExportSessionData, ExportSessionErrors, ExportSessionResponse, ExportSessionResponses, ExtensionConfig, ExtensionData, ExtensionEntry, ExtensionLoadResult, ExtensionQuery, ExtensionResponse, FeaturesResponse, ForkRequest, ForkResponse, ForkSessionData, ForkSessionErrors, ForkSessionResponse, ForkSessionResponses, FrontendToolRequest, GetCanonicalModelInfoData, GetCanonicalModelInfoResponse, GetCanonicalModelInfoResponses, GetCustomProviderData, GetCustomProviderErrors, GetCustomProviderResponse, GetCustomProviderResponses, GetDictationConfigData, GetDictationConfigResponse, GetDictationConfigResponses, GetDownloadProgressData, GetDownloadProgressErrors, GetDownloadProgressResponse, GetDownloadProgressResponses, GetExtensionsData, GetExtensionsErrors, GetExtensionsResponse, GetExtensionsResponses, GetFeaturesData, GetFeaturesResponse, GetFeaturesResponses, GetLocalModelDownloadProgressData, GetLocalModelDownloadProgressErrors, GetLocalModelDownloadProgressResponse, GetLocalModelDownloadProgressResponses, GetModelSettingsData, GetModelSettingsErrors, GetModelSettingsResponse, GetModelSettingsResponses, GetPromptData, GetPromptErrors, GetPromptResponse, GetPromptResponses, GetPromptsData, GetPromptsResponse, GetPromptsResponses, GetProviderCatalogData, GetProviderCatalogErrors, GetProviderCatalogResponse, GetProviderCatalogResponses, GetProviderCatalogTemplateData, GetProviderCatalogTemplateErrors, GetProviderCatalogTemplateResponse, GetProviderCatalogTemplateResponses, GetProviderModelsData, GetProviderModelsErrors, GetProviderModelsResponse, GetProviderModelsResponses, GetRepoFilesData, GetRepoFilesResponse, GetRepoFilesResponses, GetSessionData, GetSessionErrors, GetSessionExtensionsData, GetSessionExtensionsErrors, GetSessionExtensionsResponse, GetSessionExtensionsResponses, GetSessionInsightsData, GetSessionInsightsErrors, GetSessionInsightsResponse, GetSessionInsightsResponses, GetSessionResponse, GetSessionResponses, GetSlashCommandsData, GetSlashCommandsResponse, GetSlashCommandsResponses, GetToolsData, GetToolsErrors, GetToolsQuery, GetToolsResponse, GetToolsResponses, GetTunnelStatusData, GetTunnelStatusResponse, GetTunnelStatusResponses, GooseApp, GooseMode, HfGgufFile, HfModelInfo, HfQuantVariant, Icon, IconTheme, ImageContent, ImportAppData, ImportAppError, ImportAppErrors, ImportAppRequest, ImportAppResponse, ImportAppResponse2, ImportAppResponses, ImportSessionData, ImportSessionErrors, ImportSessionNostrData, ImportSessionNostrErrors, ImportSessionNostrRequest, ImportSessionNostrResponse, ImportSessionNostrResponses, ImportSessionRequest, ImportSessionResponse, ImportSessionResponses, InspectJobResponse, InspectRunningJobData, InspectRunningJobErrors, InspectRunningJobResponse, InspectRunningJobResponses, JsonObject, KillJobResponse, KillRunningJobData, KillRunningJobResponses, ListAppsData, ListAppsError, ListAppsErrors, ListAppsRequest, ListAppsResponse, ListAppsResponse2, ListAppsResponses, ListLocalModelsData, ListLocalModelsResponse, ListLocalModelsResponses, ListModelsData, ListModelsResponse, ListModelsResponses, ListRecipeResponse, ListRecipesData, ListRecipesErrors, ListRecipesResponse, ListRecipesResponses, ListSchedulesData, ListSchedulesErrors, ListSchedulesResponse, ListSchedulesResponse2, ListSchedulesResponses, ListSessionsData, ListSessionsErrors, ListSessionsResponse, ListSessionsResponses, LoadedProvider, LocalModelResponse, McpAppResource, McpUiProxyData, McpUiProxyErrors, McpUiProxyResponses, Message, MessageContent, MessageEvent, MessageMetadata, ModelCapabilities, ModelConfig, ModelDownloadStatus, ModelInfo, ModelInfoData, ModelInfoQuery, ModelInfoResponse, ModelSettings, ModelTemplate, ParseRecipeData, ParseRecipeError, ParseRecipeErrors, ParseRecipeRequest, ParseRecipeResponse, ParseRecipeResponse2, ParseRecipeResponses, PauseScheduleData, PauseScheduleErrors, PauseScheduleResponse, PauseScheduleResponses, Permission, PermissionLevel, PermissionsMetadata, PrincipalType, PromptContentResponse, PromptsListResponse, ProviderCatalogEntry, ProviderDetails, ProviderEngine, ProviderMetadata, ProvidersData, ProvidersResponse, ProvidersResponse2, ProvidersResponses, ProviderTemplate, ProviderType, RawAudioContent, RawEmbeddedResource, RawImageContent, RawResource, RawTextContent, ReadAllConfigData, ReadAllConfigResponse, ReadAllConfigResponses, ReadConfigData, ReadConfigErrors, ReadConfigResponses, ReadResourceData, ReadResourceErrors, ReadResourceRequest, ReadResourceResponse, ReadResourceResponse2, ReadResourceResponses, Recipe, RecipeManifest, RecipeParameter, RecipeParameterInputType, RecipeParameterRequirement, RecipeToYamlData, RecipeToYamlError, RecipeToYamlErrors, RecipeToYamlRequest, RecipeToYamlResponse, RecipeToYamlResponse2, RecipeToYamlResponses, RedactedThinkingContent, RemoveConfigData, RemoveConfigErrors, RemoveConfigResponse, RemoveConfigResponses, RemoveCustomProviderData, RemoveCustomProviderErrors, RemoveCustomProviderResponse, RemoveCustomProviderResponses, RemoveExtensionData, RemoveExtensionErrors, RemoveExtensionRequest, RemoveExtensionResponse, RemoveExtensionResponses, ReplyData, ReplyErrors, ReplyResponse, ReplyResponses, RepoVariantsResponse, ResetPromptData, ResetPromptErrors, ResetPromptResponse, ResetPromptResponses, ResourceContents, ResourceMetadata, Response, RestartAgentData, RestartAgentErrors, RestartAgentRequest, RestartAgentResponse, RestartAgentResponse2, RestartAgentResponses, ResumeAgentData, ResumeAgentErrors, ResumeAgentRequest, ResumeAgentResponse, ResumeAgentResponse2, ResumeAgentResponses, RetryConfig, Role, RunNowHandlerData, RunNowHandlerErrors, RunNowHandlerResponse, RunNowHandlerResponses, RunNowResponse, SamplingConfig, SavePromptData, SavePromptErrors, SavePromptRequest, SavePromptResponse, SavePromptResponses, SaveRecipeData, SaveRecipeError, SaveRecipeErrors, SaveRecipeRequest, SaveRecipeResponse, SaveRecipeResponse2, SaveRecipeResponses, ScanRecipeData, ScanRecipeRequest, ScanRecipeResponse, ScanRecipeResponse2, ScanRecipeResponses, ScheduledJob, ScheduleRecipeData, ScheduleRecipeErrors, ScheduleRecipeRequest, ScheduleRecipeResponses, SearchHfModelsData, SearchHfModelsErrors, SearchHfModelsResponse, SearchHfModelsResponses, SearchSessionsData, SearchSessionsErrors, SearchSessionsResponse, SearchSessionsResponses, SendTelemetryEventData, SendTelemetryEventResponses, Session, SessionCancelData, SessionCancelResponses, SessionDisplayInfo, SessionEventsData, SessionEventsErrors, SessionEventsResponse, SessionEventsResponses, SessionExtensionsResponse, SessionInsights, SessionListResponse, SessionReplyData, SessionReplyErrors, SessionReplyRequest, SessionReplyResponse, SessionReplyResponse2, SessionReplyResponses, SessionsHandlerData, SessionsHandlerErrors, SessionsHandlerResponse, SessionsHandlerResponses, SessionsQuery, SessionType, SetConfigProviderData, SetProviderRequest, SetRecipeSlashCommandData, SetRecipeSlashCommandErrors, SetRecipeSlashCommandResponses, SetSlashCommandRequest, Settings, SetupResponse, ShareSessionNostrData, ShareSessionNostrErrors, ShareSessionNostrRequest, ShareSessionNostrResponse, ShareSessionNostrResponse2, ShareSessionNostrResponses, SlashCommand, SlashCommandsResponse, StartAgentData, StartAgentError, StartAgentErrors, StartAgentRequest, StartAgentResponse, StartAgentResponses, StartNanogptSetupData, StartNanogptSetupResponse, StartNanogptSetupResponses, StartOpenrouterSetupData, StartOpenrouterSetupResponse, StartOpenrouterSetupResponses, StartTetrateSetupData, StartTetrateSetupResponse, StartTetrateSetupResponses, StartTunnelData, StartTunnelError, StartTunnelErrors, StartTunnelResponse, StartTunnelResponses, StatusData, StatusResponse, StatusResponses, StopAgentData, StopAgentErrors, StopAgentRequest, StopAgentResponse, StopAgentResponses, StopTunnelData, StopTunnelError, StopTunnelErrors, StopTunnelResponses, SubRecipe, SuccessCheck, SyncFeaturedModelsData, SyncFeaturedModelsResponses, SystemInfo, SystemInfoData, SystemInfoResponse, SystemInfoResponses, SystemNotificationContent, SystemNotificationType, TaskSupport, TelemetryEventRequest, Template, TextContent, ThinkingContent, TokenState, Tool, ToolAnnotations, ToolConfirmationRequest, ToolExecution, ToolInfo, ToolPermission, ToolRequest, ToolResponse, TranscribeDictationData, TranscribeDictationErrors, TranscribeDictationResponse, TranscribeDictationResponses, TranscribeRequest, TranscribeResponse, TunnelInfo, TunnelState, UiMetadata, UnpauseScheduleData, UnpauseScheduleErrors, UnpauseScheduleResponse, UnpauseScheduleResponses, UpdateAgentProviderData, UpdateAgentProviderErrors, UpdateAgentProviderResponses, UpdateCustomProviderData, UpdateCustomProviderErrors, UpdateCustomProviderRequest, UpdateCustomProviderResponse, UpdateCustomProviderResponses, UpdateFromSessionData, UpdateFromSessionErrors, UpdateFromSessionRequest, UpdateFromSessionResponses, UpdateModelSettingsData, UpdateModelSettingsErrors, UpdateModelSettingsResponse, UpdateModelSettingsResponses, UpdateProviderRequest, UpdateScheduleData, UpdateScheduleErrors, UpdateScheduleRequest, UpdateScheduleResponse, UpdateScheduleResponses, UpdateSessionData, UpdateSessionErrors, UpdateSessionNameData, UpdateSessionNameErrors, UpdateSessionNameRequest, UpdateSessionNameResponses, UpdateSessionRequest, UpdateSessionResponses, UpdateSessionUserRecipeValuesData, UpdateSessionUserRecipeValuesError, UpdateSessionUserRecipeValuesErrors, UpdateSessionUserRecipeValuesRequest, UpdateSessionUserRecipeValuesResponse, UpdateSessionUserRecipeValuesResponse2, UpdateSessionUserRecipeValuesResponses, UpdateWorkingDirData, UpdateWorkingDirErrors, UpdateWorkingDirRequest, UpdateWorkingDirResponses, UpsertConfigData, UpsertConfigErrors, UpsertConfigQuery, UpsertConfigResponse, UpsertConfigResponses, UpsertPermissionsData, UpsertPermissionsErrors, UpsertPermissionsQuery, UpsertPermissionsResponse, UpsertPermissionsResponses, ValidateConfigData, ValidateConfigErrors, ValidateConfigResponse, ValidateConfigResponses, WhisperModelResponse, WindowProps } from './types.gen'; +export { addExtension, agentAddExtension, agentRemoveExtension, callTool, cancelDownload, cancelLocalModelDownload, checkProvider, cleanupProviderCache, configureProviderOauth, confirmToolAction, createCustomProvider, createRecipe, createSchedule, decodeRecipe, deleteLocalModel, deleteModel, deleteRecipe, deleteSchedule, deleteSession, diagnostics, downloadHfModel, downloadModel, encodeRecipe, exportApp, exportSession, forkSession, getCanonicalModelInfo, getCustomProvider, getDictationConfig, getDownloadProgress, getExtensions, getFeatures, getLocalModelDownloadProgress, getModelSettings, getPrompt, getPrompts, getProviderCatalog, getProviderCatalogTemplate, getProviderModels, getRepoFiles, getSession, getSessionExtensions, getSessionInsights, getSlashCommands, getTools, getTunnelStatus, importApp, importSession, importSessionNostr, inspectRunningJob, killRunningJob, listApps, listLocalModels, listModels, listRecipes, listSchedules, listSessions, mcpUiProxy, type Options, parseRecipe, patchTypedConfig, pauseSchedule, providers, readAllConfig, readConfig, readResource, readTypedConfig, recipeToYaml, removeConfig, removeCustomProvider, removeExtension, reply, resetPrompt, restartAgent, resumeAgent, runNowHandler, savePrompt, saveRecipe, scanRecipe, scheduleRecipe, searchHfModels, searchSessions, sendTelemetryEvent, sessionCancel, sessionEvents, sessionReply, sessionsHandler, setConfigProvider, setRecipeSlashCommand, shareSessionNostr, startAgent, startNanogptSetup, startOpenrouterSetup, startTetrateSetup, startTunnel, status, stopAgent, stopTunnel, syncFeaturedModels, systemInfo, transcribeDictation, unpauseSchedule, updateAgentProvider, updateCustomProvider, updateFromSession, updateModelSettings, updateSchedule, updateSession, updateSessionName, updateSessionUserRecipeValues, updateWorkingDir, upsertConfig, upsertPermissions, validateConfig } from './sdk.gen'; +export type { ActionRequired, ActionRequiredData, AddExtensionData, AddExtensionErrors, AddExtensionRequest, AddExtensionResponse, AddExtensionResponses, AgentAddExtensionData, AgentAddExtensionErrors, AgentAddExtensionResponse, AgentAddExtensionResponses, AgentRemoveExtensionData, AgentRemoveExtensionErrors, AgentRemoveExtensionResponse, AgentRemoveExtensionResponses, Annotations, Author, AuthorRequest, CallToolData, CallToolError, CallToolErrors, CallToolRequest, CallToolResponse, CallToolResponse2, CallToolResponses, CancelDownloadData, CancelDownloadErrors, CancelDownloadResponses, CancelLocalModelDownloadData, CancelLocalModelDownloadErrors, CancelLocalModelDownloadResponses, CancelRequest, ChatRequest, CheckProviderData, CheckProviderRequest, CleanupProviderCacheData, CleanupProviderCacheErrors, CleanupProviderCacheResponse, CleanupProviderCacheResponses, ClientOptions, CommandType, ConfigKey, ConfigKeyQuery, ConfigResponse, ConfigureProviderOauthData, ConfigureProviderOauthErrors, ConfigureProviderOauthResponses, ConfirmToolActionData, ConfirmToolActionErrors, ConfirmToolActionRequest, ConfirmToolActionResponses, Content, ContentBlock, Conversation, CreateCustomProviderData, CreateCustomProviderErrors, CreateCustomProviderResponse, CreateCustomProviderResponse2, CreateCustomProviderResponses, CreateRecipeData, CreateRecipeErrors, CreateRecipeRequest, CreateRecipeResponse, CreateRecipeResponse2, CreateRecipeResponses, CreateScheduleData, CreateScheduleErrors, CreateScheduleRequest, CreateScheduleResponse, CreateScheduleResponses, CspMetadata, DeclarativeProviderConfig, DecodeRecipeData, DecodeRecipeErrors, DecodeRecipeRequest, DecodeRecipeResponse, DecodeRecipeResponse2, DecodeRecipeResponses, DeleteLocalModelData, DeleteLocalModelErrors, DeleteLocalModelResponses, DeleteModelData, DeleteModelErrors, DeleteModelResponses, DeleteRecipeData, DeleteRecipeErrors, DeleteRecipeRequest, DeleteRecipeResponse, DeleteRecipeResponses, DeleteScheduleData, DeleteScheduleErrors, DeleteScheduleResponse, DeleteScheduleResponses, DeleteSessionData, DeleteSessionErrors, DeleteSessionResponses, DiagnosticsData, DiagnosticsErrors, DiagnosticsResponse, DiagnosticsResponses, DictationProvider, DictationProviderStatus, DownloadHfModelData, DownloadHfModelErrors, DownloadHfModelResponse, DownloadHfModelResponses, DownloadModelData, DownloadModelErrors, DownloadModelRequest, DownloadModelResponses, DownloadProgress, DownloadStatus, EmbeddedResource, EncodeRecipeData, EncodeRecipeErrors, EncodeRecipeRequest, EncodeRecipeResponse, EncodeRecipeResponse2, EncodeRecipeResponses, Envs, EnvVarConfig, ErrorResponse, ExportAppData, ExportAppError, ExportAppErrors, ExportAppResponse, ExportAppResponses, ExportSessionData, ExportSessionErrors, ExportSessionResponse, ExportSessionResponses, ExtensionConfig, ExtensionData, ExtensionEntry, ExtensionLoadResult, ExtensionQuery, ExtensionResponse, FeaturesResponse, ForkRequest, ForkResponse, ForkSessionData, ForkSessionErrors, ForkSessionResponse, ForkSessionResponses, FrontendToolRequest, GetCanonicalModelInfoData, GetCanonicalModelInfoResponse, GetCanonicalModelInfoResponses, GetCustomProviderData, GetCustomProviderErrors, GetCustomProviderResponse, GetCustomProviderResponses, GetDictationConfigData, GetDictationConfigResponse, GetDictationConfigResponses, GetDownloadProgressData, GetDownloadProgressErrors, GetDownloadProgressResponse, GetDownloadProgressResponses, GetExtensionsData, GetExtensionsErrors, GetExtensionsResponse, GetExtensionsResponses, GetFeaturesData, GetFeaturesResponse, GetFeaturesResponses, GetLocalModelDownloadProgressData, GetLocalModelDownloadProgressErrors, GetLocalModelDownloadProgressResponse, GetLocalModelDownloadProgressResponses, GetModelSettingsData, GetModelSettingsErrors, GetModelSettingsResponse, GetModelSettingsResponses, GetPromptData, GetPromptErrors, GetPromptResponse, GetPromptResponses, GetPromptsData, GetPromptsResponse, GetPromptsResponses, GetProviderCatalogData, GetProviderCatalogErrors, GetProviderCatalogResponse, GetProviderCatalogResponses, GetProviderCatalogTemplateData, GetProviderCatalogTemplateErrors, GetProviderCatalogTemplateResponse, GetProviderCatalogTemplateResponses, GetProviderModelsData, GetProviderModelsErrors, GetProviderModelsResponse, GetProviderModelsResponses, GetRepoFilesData, GetRepoFilesResponse, GetRepoFilesResponses, GetSessionData, GetSessionErrors, GetSessionExtensionsData, GetSessionExtensionsErrors, GetSessionExtensionsResponse, GetSessionExtensionsResponses, GetSessionInsightsData, GetSessionInsightsErrors, GetSessionInsightsResponse, GetSessionInsightsResponses, GetSessionResponse, GetSessionResponses, GetSlashCommandsData, GetSlashCommandsResponse, GetSlashCommandsResponses, GetToolsData, GetToolsErrors, GetToolsQuery, GetToolsResponse, GetToolsResponses, GetTunnelStatusData, GetTunnelStatusResponse, GetTunnelStatusResponses, GooseApp, GooseConfigSchema, GooseConfigUpdate, GooseMode, HfGgufFile, HfModelInfo, HfQuantVariant, Icon, IconTheme, ImageContent, ImportAppData, ImportAppError, ImportAppErrors, ImportAppRequest, ImportAppResponse, ImportAppResponse2, ImportAppResponses, ImportSessionData, ImportSessionErrors, ImportSessionNostrData, ImportSessionNostrErrors, ImportSessionNostrRequest, ImportSessionNostrResponse, ImportSessionNostrResponses, ImportSessionRequest, ImportSessionResponse, ImportSessionResponses, InspectJobResponse, InspectRunningJobData, InspectRunningJobErrors, InspectRunningJobResponse, InspectRunningJobResponses, JsonObject, KillJobResponse, KillRunningJobData, KillRunningJobResponses, ListAppsData, ListAppsError, ListAppsErrors, ListAppsRequest, ListAppsResponse, ListAppsResponse2, ListAppsResponses, ListLocalModelsData, ListLocalModelsResponse, ListLocalModelsResponses, ListModelsData, ListModelsResponse, ListModelsResponses, ListRecipeResponse, ListRecipesData, ListRecipesErrors, ListRecipesResponse, ListRecipesResponses, ListSchedulesData, ListSchedulesErrors, ListSchedulesResponse, ListSchedulesResponse2, ListSchedulesResponses, ListSessionsData, ListSessionsErrors, ListSessionsResponse, ListSessionsResponses, LoadedProvider, LocalModelResponse, McpAppResource, McpUiProxyData, McpUiProxyErrors, McpUiProxyResponses, Message, MessageContent, MessageEvent, MessageMetadata, ModelCapabilities, ModelConfig, ModelDownloadStatus, ModelInfo, ModelInfoData, ModelInfoQuery, ModelInfoResponse, ModelSettings, ModelTemplate, ParseRecipeData, ParseRecipeError, ParseRecipeErrors, ParseRecipeRequest, ParseRecipeResponse, ParseRecipeResponse2, ParseRecipeResponses, PatchTypedConfigData, PatchTypedConfigErrors, PatchTypedConfigResponse, PatchTypedConfigResponses, PauseScheduleData, PauseScheduleErrors, PauseScheduleResponse, PauseScheduleResponses, Permission, PermissionLevel, PermissionsMetadata, PrincipalType, PromptContentResponse, PromptsListResponse, ProviderCatalogEntry, ProviderDetails, ProviderEngine, ProviderMetadata, ProvidersData, ProvidersResponse, ProvidersResponse2, ProvidersResponses, ProviderTemplate, ProviderType, RawAudioContent, RawEmbeddedResource, RawImageContent, RawResource, RawTextContent, ReadAllConfigData, ReadAllConfigResponse, ReadAllConfigResponses, ReadConfigData, ReadConfigErrors, ReadConfigResponses, ReadResourceData, ReadResourceErrors, ReadResourceRequest, ReadResourceResponse, ReadResourceResponse2, ReadResourceResponses, ReadTypedConfigData, ReadTypedConfigErrors, ReadTypedConfigResponse, ReadTypedConfigResponses, Recipe, RecipeManifest, RecipeParameter, RecipeParameterInputType, RecipeParameterRequirement, RecipeToYamlData, RecipeToYamlError, RecipeToYamlErrors, RecipeToYamlRequest, RecipeToYamlResponse, RecipeToYamlResponse2, RecipeToYamlResponses, RedactedThinkingContent, RemoveConfigData, RemoveConfigErrors, RemoveConfigResponse, RemoveConfigResponses, RemoveCustomProviderData, RemoveCustomProviderErrors, RemoveCustomProviderResponse, RemoveCustomProviderResponses, RemoveExtensionData, RemoveExtensionErrors, RemoveExtensionRequest, RemoveExtensionResponse, RemoveExtensionResponses, ReplyData, ReplyErrors, ReplyResponse, ReplyResponses, RepoVariantsResponse, ResetPromptData, ResetPromptErrors, ResetPromptResponse, ResetPromptResponses, ResourceContents, ResourceMetadata, Response, RestartAgentData, RestartAgentErrors, RestartAgentRequest, RestartAgentResponse, RestartAgentResponse2, RestartAgentResponses, ResumeAgentData, ResumeAgentErrors, ResumeAgentRequest, ResumeAgentResponse, ResumeAgentResponse2, ResumeAgentResponses, RetryConfig, Role, RunNowHandlerData, RunNowHandlerErrors, RunNowHandlerResponse, RunNowHandlerResponses, RunNowResponse, SamplingConfig, SavePromptData, SavePromptErrors, SavePromptRequest, SavePromptResponse, SavePromptResponses, SaveRecipeData, SaveRecipeError, SaveRecipeErrors, SaveRecipeRequest, SaveRecipeResponse, SaveRecipeResponse2, SaveRecipeResponses, ScanRecipeData, ScanRecipeRequest, ScanRecipeResponse, ScanRecipeResponse2, ScanRecipeResponses, ScheduledJob, ScheduleRecipeData, ScheduleRecipeErrors, ScheduleRecipeRequest, ScheduleRecipeResponses, SearchHfModelsData, SearchHfModelsErrors, SearchHfModelsResponse, SearchHfModelsResponses, SearchSessionsData, SearchSessionsErrors, SearchSessionsResponse, SearchSessionsResponses, SendTelemetryEventData, SendTelemetryEventResponses, Session, SessionCancelData, SessionCancelResponses, SessionDisplayInfo, SessionEventsData, SessionEventsErrors, SessionEventsResponse, SessionEventsResponses, SessionExtensionsResponse, SessionInsights, SessionListResponse, SessionReplyData, SessionReplyErrors, SessionReplyRequest, SessionReplyResponse, SessionReplyResponse2, SessionReplyResponses, SessionsHandlerData, SessionsHandlerErrors, SessionsHandlerResponse, SessionsHandlerResponses, SessionsQuery, SessionType, SetConfigProviderData, SetProviderRequest, SetRecipeSlashCommandData, SetRecipeSlashCommandErrors, SetRecipeSlashCommandResponses, SetSlashCommandRequest, Settings, SetupResponse, ShareSessionNostrData, ShareSessionNostrErrors, ShareSessionNostrRequest, ShareSessionNostrResponse, ShareSessionNostrResponse2, ShareSessionNostrResponses, SlashCommand, SlashCommandMapping, SlashCommandsResponse, StartAgentData, StartAgentError, StartAgentErrors, StartAgentRequest, StartAgentResponse, StartAgentResponses, StartNanogptSetupData, StartNanogptSetupResponse, StartNanogptSetupResponses, StartOpenrouterSetupData, StartOpenrouterSetupResponse, StartOpenrouterSetupResponses, StartTetrateSetupData, StartTetrateSetupResponse, StartTetrateSetupResponses, StartTunnelData, StartTunnelError, StartTunnelErrors, StartTunnelResponse, StartTunnelResponses, StatusData, StatusResponse, StatusResponses, StopAgentData, StopAgentErrors, StopAgentRequest, StopAgentResponse, StopAgentResponses, StopTunnelData, StopTunnelError, StopTunnelErrors, StopTunnelResponses, SubRecipe, SuccessCheck, SyncFeaturedModelsData, SyncFeaturedModelsResponses, SystemInfo, SystemInfoData, SystemInfoResponse, SystemInfoResponses, SystemNotificationContent, SystemNotificationType, TaskSupport, TelemetryEventRequest, Template, TextContent, ThinkingContent, TokenState, Tool, ToolAnnotations, ToolConfirmationRequest, ToolExecution, ToolInfo, ToolPermission, ToolRequest, ToolResponse, TranscribeDictationData, TranscribeDictationErrors, TranscribeDictationResponse, TranscribeDictationResponses, TranscribeRequest, TranscribeResponse, TunnelInfo, TunnelState, UiMetadata, UnpauseScheduleData, UnpauseScheduleErrors, UnpauseScheduleResponse, UnpauseScheduleResponses, UpdateAgentProviderData, UpdateAgentProviderErrors, UpdateAgentProviderResponses, UpdateCustomProviderData, UpdateCustomProviderErrors, UpdateCustomProviderRequest, UpdateCustomProviderResponse, UpdateCustomProviderResponses, UpdateFromSessionData, UpdateFromSessionErrors, UpdateFromSessionRequest, UpdateFromSessionResponses, UpdateModelSettingsData, UpdateModelSettingsErrors, UpdateModelSettingsResponse, UpdateModelSettingsResponses, UpdateProviderRequest, UpdateScheduleData, UpdateScheduleErrors, UpdateScheduleRequest, UpdateScheduleResponse, UpdateScheduleResponses, UpdateSessionData, UpdateSessionErrors, UpdateSessionNameData, UpdateSessionNameErrors, UpdateSessionNameRequest, UpdateSessionNameResponses, UpdateSessionRequest, UpdateSessionResponses, UpdateSessionUserRecipeValuesData, UpdateSessionUserRecipeValuesError, UpdateSessionUserRecipeValuesErrors, UpdateSessionUserRecipeValuesRequest, UpdateSessionUserRecipeValuesResponse, UpdateSessionUserRecipeValuesResponse2, UpdateSessionUserRecipeValuesResponses, UpdateWorkingDirData, UpdateWorkingDirErrors, UpdateWorkingDirRequest, UpdateWorkingDirResponses, UpsertConfigData, UpsertConfigErrors, UpsertConfigQuery, UpsertConfigResponse, UpsertConfigResponses, UpsertPermissionsData, UpsertPermissionsErrors, UpsertPermissionsQuery, UpsertPermissionsResponse, UpsertPermissionsResponses, ValidateConfigData, ValidateConfigErrors, ValidateConfigResponse, ValidateConfigResponses, WhisperModelResponse, WindowProps } from './types.gen'; diff --git a/ui/desktop/src/api/sdk.gen.ts b/ui/desktop/src/api/sdk.gen.ts index 2870da539d..8330466964 100644 --- a/ui/desktop/src/api/sdk.gen.ts +++ b/ui/desktop/src/api/sdk.gen.ts @@ -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, CallToolData, CallToolErrors, CallToolResponses, CancelDownloadData, CancelDownloadErrors, CancelDownloadResponses, CancelLocalModelDownloadData, CancelLocalModelDownloadErrors, CancelLocalModelDownloadResponses, CheckProviderData, CleanupProviderCacheData, CleanupProviderCacheErrors, CleanupProviderCacheResponses, ConfigureProviderOauthData, ConfigureProviderOauthErrors, ConfigureProviderOauthResponses, ConfirmToolActionData, ConfirmToolActionErrors, ConfirmToolActionResponses, CreateCustomProviderData, CreateCustomProviderErrors, CreateCustomProviderResponses, CreateRecipeData, CreateRecipeErrors, CreateRecipeResponses, CreateScheduleData, CreateScheduleErrors, CreateScheduleResponses, DecodeRecipeData, DecodeRecipeErrors, DecodeRecipeResponses, DeleteLocalModelData, DeleteLocalModelErrors, DeleteLocalModelResponses, DeleteModelData, DeleteModelErrors, DeleteModelResponses, DeleteRecipeData, DeleteRecipeErrors, DeleteRecipeResponses, DeleteScheduleData, DeleteScheduleErrors, DeleteScheduleResponses, DeleteSessionData, DeleteSessionErrors, DeleteSessionResponses, DiagnosticsData, DiagnosticsErrors, DiagnosticsResponses, DownloadHfModelData, DownloadHfModelErrors, DownloadHfModelResponses, DownloadModelData, DownloadModelErrors, DownloadModelResponses, EncodeRecipeData, EncodeRecipeErrors, EncodeRecipeResponses, ExportAppData, ExportAppErrors, ExportAppResponses, ExportSessionData, ExportSessionErrors, ExportSessionResponses, ForkSessionData, ForkSessionErrors, ForkSessionResponses, GetCanonicalModelInfoData, GetCanonicalModelInfoResponses, GetCustomProviderData, GetCustomProviderErrors, GetCustomProviderResponses, GetDictationConfigData, GetDictationConfigResponses, GetDownloadProgressData, GetDownloadProgressErrors, GetDownloadProgressResponses, GetExtensionsData, GetExtensionsErrors, GetExtensionsResponses, GetFeaturesData, GetFeaturesResponses, GetLocalModelDownloadProgressData, GetLocalModelDownloadProgressErrors, GetLocalModelDownloadProgressResponses, GetModelSettingsData, GetModelSettingsErrors, GetModelSettingsResponses, GetPromptData, GetPromptErrors, GetPromptResponses, GetPromptsData, GetPromptsResponses, GetProviderCatalogData, GetProviderCatalogErrors, GetProviderCatalogResponses, GetProviderCatalogTemplateData, GetProviderCatalogTemplateErrors, GetProviderCatalogTemplateResponses, GetProviderModelsData, GetProviderModelsErrors, GetProviderModelsResponses, GetRepoFilesData, GetRepoFilesResponses, GetSessionData, GetSessionErrors, GetSessionExtensionsData, GetSessionExtensionsErrors, GetSessionExtensionsResponses, GetSessionInsightsData, GetSessionInsightsErrors, GetSessionInsightsResponses, GetSessionResponses, GetSlashCommandsData, GetSlashCommandsResponses, GetToolsData, GetToolsErrors, GetToolsResponses, GetTunnelStatusData, GetTunnelStatusResponses, ImportAppData, ImportAppErrors, ImportAppResponses, ImportSessionData, ImportSessionErrors, ImportSessionNostrData, ImportSessionNostrErrors, ImportSessionNostrResponses, ImportSessionResponses, InspectRunningJobData, InspectRunningJobErrors, InspectRunningJobResponses, KillRunningJobData, KillRunningJobResponses, ListAppsData, ListAppsErrors, ListAppsResponses, ListLocalModelsData, ListLocalModelsResponses, ListModelsData, ListModelsResponses, 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, ReadResourceData, ReadResourceErrors, ReadResourceResponses, RecipeToYamlData, RecipeToYamlErrors, RecipeToYamlResponses, RemoveConfigData, RemoveConfigErrors, RemoveConfigResponses, RemoveCustomProviderData, RemoveCustomProviderErrors, RemoveCustomProviderResponses, RemoveExtensionData, RemoveExtensionErrors, RemoveExtensionResponses, ReplyData, ReplyErrors, ReplyResponses, ResetPromptData, ResetPromptErrors, ResetPromptResponses, RestartAgentData, RestartAgentErrors, RestartAgentResponses, ResumeAgentData, ResumeAgentErrors, ResumeAgentResponses, RunNowHandlerData, RunNowHandlerErrors, RunNowHandlerResponses, SavePromptData, SavePromptErrors, SavePromptResponses, SaveRecipeData, SaveRecipeErrors, SaveRecipeResponses, ScanRecipeData, ScanRecipeResponses, ScheduleRecipeData, ScheduleRecipeErrors, ScheduleRecipeResponses, SearchHfModelsData, SearchHfModelsErrors, SearchHfModelsResponses, SearchSessionsData, SearchSessionsErrors, SearchSessionsResponses, SendTelemetryEventData, SendTelemetryEventResponses, SessionCancelData, SessionCancelResponses, SessionEventsData, SessionEventsErrors, SessionEventsResponses, SessionReplyData, SessionReplyErrors, SessionReplyResponses, SessionsHandlerData, SessionsHandlerErrors, SessionsHandlerResponses, SetConfigProviderData, SetRecipeSlashCommandData, SetRecipeSlashCommandErrors, SetRecipeSlashCommandResponses, ShareSessionNostrData, ShareSessionNostrErrors, ShareSessionNostrResponses, StartAgentData, StartAgentErrors, StartAgentResponses, StartNanogptSetupData, StartNanogptSetupResponses, StartOpenrouterSetupData, StartOpenrouterSetupResponses, StartTetrateSetupData, StartTetrateSetupResponses, StartTunnelData, StartTunnelErrors, StartTunnelResponses, StatusData, StatusResponses, StopAgentData, StopAgentErrors, StopAgentResponses, StopTunnelData, StopTunnelErrors, StopTunnelResponses, SyncFeaturedModelsData, SyncFeaturedModelsResponses, SystemInfoData, SystemInfoResponses, TranscribeDictationData, TranscribeDictationErrors, TranscribeDictationResponses, UnpauseScheduleData, UnpauseScheduleErrors, UnpauseScheduleResponses, UpdateAgentProviderData, UpdateAgentProviderErrors, UpdateAgentProviderResponses, UpdateCustomProviderData, UpdateCustomProviderErrors, UpdateCustomProviderResponses, UpdateFromSessionData, UpdateFromSessionErrors, UpdateFromSessionResponses, UpdateModelSettingsData, UpdateModelSettingsErrors, UpdateModelSettingsResponses, UpdateScheduleData, UpdateScheduleErrors, UpdateScheduleResponses, UpdateSessionData, UpdateSessionErrors, UpdateSessionNameData, UpdateSessionNameErrors, UpdateSessionNameResponses, UpdateSessionResponses, UpdateSessionUserRecipeValuesData, UpdateSessionUserRecipeValuesErrors, UpdateSessionUserRecipeValuesResponses, UpdateWorkingDirData, UpdateWorkingDirErrors, UpdateWorkingDirResponses, UpsertConfigData, UpsertConfigErrors, UpsertConfigResponses, UpsertPermissionsData, UpsertPermissionsErrors, UpsertPermissionsResponses, ValidateConfigData, ValidateConfigErrors, ValidateConfigResponses } from './types.gen'; +import type { AddExtensionData, AddExtensionErrors, AddExtensionResponses, AgentAddExtensionData, AgentAddExtensionErrors, AgentAddExtensionResponses, AgentRemoveExtensionData, AgentRemoveExtensionErrors, AgentRemoveExtensionResponses, CallToolData, CallToolErrors, CallToolResponses, CancelDownloadData, CancelDownloadErrors, CancelDownloadResponses, CancelLocalModelDownloadData, CancelLocalModelDownloadErrors, CancelLocalModelDownloadResponses, CheckProviderData, CleanupProviderCacheData, CleanupProviderCacheErrors, CleanupProviderCacheResponses, ConfigureProviderOauthData, ConfigureProviderOauthErrors, ConfigureProviderOauthResponses, ConfirmToolActionData, ConfirmToolActionErrors, ConfirmToolActionResponses, CreateCustomProviderData, CreateCustomProviderErrors, CreateCustomProviderResponses, CreateRecipeData, CreateRecipeErrors, CreateRecipeResponses, CreateScheduleData, CreateScheduleErrors, CreateScheduleResponses, DecodeRecipeData, DecodeRecipeErrors, DecodeRecipeResponses, DeleteLocalModelData, DeleteLocalModelErrors, DeleteLocalModelResponses, DeleteModelData, DeleteModelErrors, DeleteModelResponses, DeleteRecipeData, DeleteRecipeErrors, DeleteRecipeResponses, DeleteScheduleData, DeleteScheduleErrors, DeleteScheduleResponses, DeleteSessionData, DeleteSessionErrors, DeleteSessionResponses, DiagnosticsData, DiagnosticsErrors, DiagnosticsResponses, DownloadHfModelData, DownloadHfModelErrors, DownloadHfModelResponses, DownloadModelData, DownloadModelErrors, DownloadModelResponses, EncodeRecipeData, EncodeRecipeErrors, EncodeRecipeResponses, ExportAppData, ExportAppErrors, ExportAppResponses, ExportSessionData, ExportSessionErrors, ExportSessionResponses, ForkSessionData, ForkSessionErrors, ForkSessionResponses, GetCanonicalModelInfoData, GetCanonicalModelInfoResponses, GetCustomProviderData, GetCustomProviderErrors, GetCustomProviderResponses, GetDictationConfigData, GetDictationConfigResponses, GetDownloadProgressData, GetDownloadProgressErrors, GetDownloadProgressResponses, GetExtensionsData, GetExtensionsErrors, GetExtensionsResponses, GetFeaturesData, GetFeaturesResponses, GetLocalModelDownloadProgressData, GetLocalModelDownloadProgressErrors, GetLocalModelDownloadProgressResponses, GetModelSettingsData, GetModelSettingsErrors, GetModelSettingsResponses, GetPromptData, GetPromptErrors, GetPromptResponses, GetPromptsData, GetPromptsResponses, GetProviderCatalogData, GetProviderCatalogErrors, GetProviderCatalogResponses, GetProviderCatalogTemplateData, GetProviderCatalogTemplateErrors, GetProviderCatalogTemplateResponses, GetProviderModelsData, GetProviderModelsErrors, GetProviderModelsResponses, GetRepoFilesData, GetRepoFilesResponses, GetSessionData, GetSessionErrors, GetSessionExtensionsData, GetSessionExtensionsErrors, GetSessionExtensionsResponses, GetSessionInsightsData, GetSessionInsightsErrors, GetSessionInsightsResponses, GetSessionResponses, GetSlashCommandsData, GetSlashCommandsResponses, GetToolsData, GetToolsErrors, GetToolsResponses, GetTunnelStatusData, GetTunnelStatusResponses, ImportAppData, ImportAppErrors, ImportAppResponses, ImportSessionData, ImportSessionErrors, ImportSessionNostrData, ImportSessionNostrErrors, ImportSessionNostrResponses, ImportSessionResponses, InspectRunningJobData, InspectRunningJobErrors, InspectRunningJobResponses, KillRunningJobData, KillRunningJobResponses, ListAppsData, ListAppsErrors, ListAppsResponses, ListLocalModelsData, ListLocalModelsResponses, ListModelsData, ListModelsResponses, ListRecipesData, ListRecipesErrors, ListRecipesResponses, ListSchedulesData, ListSchedulesErrors, ListSchedulesResponses, ListSessionsData, ListSessionsErrors, ListSessionsResponses, McpUiProxyData, McpUiProxyErrors, McpUiProxyResponses, ParseRecipeData, ParseRecipeErrors, ParseRecipeResponses, PatchTypedConfigData, PatchTypedConfigErrors, PatchTypedConfigResponses, PauseScheduleData, PauseScheduleErrors, PauseScheduleResponses, ProvidersData, ProvidersResponses, ReadAllConfigData, ReadAllConfigResponses, ReadConfigData, ReadConfigErrors, ReadConfigResponses, ReadResourceData, ReadResourceErrors, ReadResourceResponses, ReadTypedConfigData, ReadTypedConfigErrors, ReadTypedConfigResponses, RecipeToYamlData, RecipeToYamlErrors, RecipeToYamlResponses, RemoveConfigData, RemoveConfigErrors, RemoveConfigResponses, RemoveCustomProviderData, RemoveCustomProviderErrors, RemoveCustomProviderResponses, RemoveExtensionData, RemoveExtensionErrors, RemoveExtensionResponses, ReplyData, ReplyErrors, ReplyResponses, ResetPromptData, ResetPromptErrors, ResetPromptResponses, RestartAgentData, RestartAgentErrors, RestartAgentResponses, ResumeAgentData, ResumeAgentErrors, ResumeAgentResponses, RunNowHandlerData, RunNowHandlerErrors, RunNowHandlerResponses, SavePromptData, SavePromptErrors, SavePromptResponses, SaveRecipeData, SaveRecipeErrors, SaveRecipeResponses, ScanRecipeData, ScanRecipeResponses, ScheduleRecipeData, ScheduleRecipeErrors, ScheduleRecipeResponses, SearchHfModelsData, SearchHfModelsErrors, SearchHfModelsResponses, SearchSessionsData, SearchSessionsErrors, SearchSessionsResponses, SendTelemetryEventData, SendTelemetryEventResponses, SessionCancelData, SessionCancelResponses, SessionEventsData, SessionEventsErrors, SessionEventsResponses, SessionReplyData, SessionReplyErrors, SessionReplyResponses, SessionsHandlerData, SessionsHandlerErrors, SessionsHandlerResponses, SetConfigProviderData, SetRecipeSlashCommandData, SetRecipeSlashCommandErrors, SetRecipeSlashCommandResponses, ShareSessionNostrData, ShareSessionNostrErrors, ShareSessionNostrResponses, StartAgentData, StartAgentErrors, StartAgentResponses, StartNanogptSetupData, StartNanogptSetupResponses, StartOpenrouterSetupData, StartOpenrouterSetupResponses, StartTetrateSetupData, StartTetrateSetupResponses, StartTunnelData, StartTunnelErrors, StartTunnelResponses, StatusData, StatusResponses, StopAgentData, StopAgentErrors, StopAgentResponses, StopTunnelData, StopTunnelErrors, StopTunnelResponses, SyncFeaturedModelsData, SyncFeaturedModelsResponses, SystemInfoData, SystemInfoResponses, TranscribeDictationData, TranscribeDictationErrors, TranscribeDictationResponses, UnpauseScheduleData, UnpauseScheduleErrors, UnpauseScheduleResponses, UpdateAgentProviderData, UpdateAgentProviderErrors, UpdateAgentProviderResponses, UpdateCustomProviderData, UpdateCustomProviderErrors, UpdateCustomProviderResponses, UpdateFromSessionData, UpdateFromSessionErrors, UpdateFromSessionResponses, UpdateModelSettingsData, UpdateModelSettingsErrors, UpdateModelSettingsResponses, UpdateScheduleData, UpdateScheduleErrors, UpdateScheduleResponses, UpdateSessionData, UpdateSessionErrors, UpdateSessionNameData, UpdateSessionNameErrors, UpdateSessionNameResponses, UpdateSessionResponses, UpdateSessionUserRecipeValuesData, UpdateSessionUserRecipeValuesErrors, UpdateSessionUserRecipeValuesResponses, UpdateWorkingDirData, UpdateWorkingDirErrors, UpdateWorkingDirResponses, UpsertConfigData, UpsertConfigErrors, UpsertConfigResponses, UpsertPermissionsData, UpsertPermissionsErrors, UpsertPermissionsResponses, ValidateConfigData, ValidateConfigErrors, ValidateConfigResponses } from './types.gen'; export type Options = Options2 & { /** @@ -270,6 +270,17 @@ export const setConfigProvider = (options: export const getSlashCommands = (options?: Options) => (options?.client ?? client).get({ url: '/config/slash_commands', ...options }); +export const readTypedConfig = (options?: Options) => (options?.client ?? client).get({ url: '/config/typed', ...options }); + +export const patchTypedConfig = (options: Options) => (options.client ?? client).patch({ + url: '/config/typed', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + export const upsertConfig = (options: Options) => (options.client ?? client).post({ url: '/config/upsert', ...options, diff --git a/ui/desktop/src/api/types.gen.ts b/ui/desktop/src/api/types.gen.ts index 5f410551c5..714b55a929 100644 --- a/ui/desktop/src/api/types.gen.ts +++ b/ui/desktop/src/api/types.gen.ts @@ -529,6 +529,277 @@ export type GooseApp = McpAppResource & (WindowProps | null) & { prd?: string | null; }; +/** + * JSON Schema representation of Goose's config.yaml. + * + * All keys are optional. Unknown keys are allowed (additionalProperties: true) + * because Goose passes undocumented provider-specific keys through as + * environment variable overrides. + */ +export type GooseConfigSchema = { + ANTHROPIC_HOST?: string | null; + AVIAN_HOST?: string | null; + AWS_PROFILE?: string | null; + AWS_REGION?: string | null; + AZURE_OPENAI_API_VERSION?: string | null; + AZURE_OPENAI_DEPLOYMENT_NAME?: string | null; + AZURE_OPENAI_ENDPOINT?: string | null; + BEDROCK_BACKOFF_MULTIPLIER?: number | null; + BEDROCK_ENABLE_CACHING?: boolean | null; + BEDROCK_INITIAL_RETRY_INTERVAL_MS?: number | null; + BEDROCK_MAX_RETRIES?: number | null; + BEDROCK_MAX_RETRY_INTERVAL_MS?: number | null; + CHATGPT_CODEX_REASONING_EFFORT?: string | null; + CLAUDE_CODE_COMMAND?: string | null; + CLAUDE_THINKING_BUDGET?: number | null; + CLAUDE_THINKING_EFFORT?: string | null; + CLAUDE_THINKING_TYPE?: string | null; + CODEX_COMMAND?: string | null; + CODEX_ENABLE_SKILLS?: string | null; + CODEX_REASONING_EFFORT?: string | null; + CODEX_SKIP_GIT_CHECK?: string | null; + CODE_MODE_TOOL_DISCLOSURE?: string | null; + CONTEXT_FILE_NAMES?: Array | null; + CURSOR_AGENT_COMMAND?: string | null; + DATABRICKS_BACKOFF_MULTIPLIER?: string | null; + DATABRICKS_HOST?: string | null; + DATABRICKS_INITIAL_RETRY_INTERVAL_MS?: string | null; + DATABRICKS_MAX_RETRIES?: string | null; + DATABRICKS_MAX_RETRY_INTERVAL_MS?: string | null; + EDIT_MODE?: string | null; + GCP_BACKOFF_MULTIPLIER?: string | null; + GCP_INITIAL_RETRY_INTERVAL_MS?: string | null; + GCP_LOCATION?: string | null; + GCP_MAX_RETRIES?: string | null; + GCP_MAX_RETRY_INTERVAL_MS?: string | null; + GCP_PROJECT_ID?: string | null; + GEMINI25_THINKING_BUDGET?: number | null; + GEMINI3_THINKING_LEVEL?: string | null; + GEMINI_CLI_COMMAND?: string | null; + GITHUB_COPILOT_CLIENT_ID?: string | null; + GITHUB_COPILOT_HOST?: string | null; + GITHUB_COPILOT_TOKEN_URL?: string | null; + GOOGLE_HOST?: string | null; + GOOSE_ALLOWLIST?: string | null; + GOOSE_AUTO_COMPACT_THRESHOLD?: number | null; + GOOSE_CA_CERT_PATH?: string | null; + GOOSE_CLIENT_CERT_PATH?: string | null; + GOOSE_CLIENT_KEY_PATH?: string | null; + GOOSE_CLI_DARK_THEME?: string | null; + GOOSE_CLI_LIGHT_THEME?: string | null; + GOOSE_CLI_MIN_PRIORITY?: number | null; + GOOSE_CLI_NEWLINE_KEY?: string | null; + GOOSE_CLI_SHOW_COST?: boolean | null; + GOOSE_CLI_SHOW_THINKING?: boolean | null; + GOOSE_CLI_THEME?: string | null; + GOOSE_CONTEXT_LIMIT?: number | null; + GOOSE_DATABRICKS_CLIENT_REQUEST_ID?: boolean | null; + GOOSE_DEBUG?: boolean | null; + GOOSE_DEFAULT_EXTENSION_TIMEOUT?: number | null; + GOOSE_DISABLE_KEYRING?: string | null; + GOOSE_DISABLE_SESSION_NAMING?: boolean | null; + GOOSE_INPUT_LIMIT?: number | null; + GOOSE_LOCAL_ENABLE_THINKING?: boolean | null; + GOOSE_MAX_ACTIVE_AGENTS?: number | null; + GOOSE_MAX_BACKGROUND_TASKS?: number | null; + GOOSE_MAX_TOKENS?: number | null; + GOOSE_MAX_TURNS?: number | null; + GOOSE_MODE?: GooseMode | null; + GOOSE_MODEL?: string | null; + GOOSE_PLANNER_MODEL?: string | null; + GOOSE_PLANNER_PROVIDER?: string | null; + GOOSE_PROMPT_EDITOR?: string | null; + GOOSE_PROMPT_EDITOR_ALWAYS?: boolean | null; + GOOSE_PROVIDER?: string | null; + GOOSE_RECIPE_GITHUB_REPO?: string | null; + GOOSE_RECIPE_ON_FAILURE_TIMEOUT_SECONDS?: number | null; + GOOSE_RECIPE_RETRY_TIMEOUT_SECONDS?: number | null; + GOOSE_SEARCH_PATHS?: Array | null; + GOOSE_SHOW_FULL_OUTPUT?: boolean | null; + GOOSE_STATUS_HOOK?: string | null; + GOOSE_STREAM_TIMEOUT?: number | null; + GOOSE_SUBAGENT_MAX_TURNS?: number | null; + GOOSE_SUBAGENT_MODEL?: string | null; + GOOSE_SUBAGENT_PROVIDER?: string | null; + GOOSE_SYSTEM_PROMPT_FILE_PATH?: string | null; + GOOSE_TELEMETRY_ENABLED?: boolean | null; + GOOSE_TOOL_CALL_CUTOFF?: number | null; + GOOSE_TOOL_PAIR_SUMMARIZATION?: boolean | null; + LITELLM_BASE_PATH?: string | null; + LITELLM_HOST?: string | null; + LITELLM_TIMEOUT?: number | null; + OLLAMA_HOST?: string | null; + OLLAMA_STREAM_TIMEOUT?: number | null; + OLLAMA_STREAM_USAGE?: boolean | null; + OLLAMA_TIMEOUT?: number | null; + OPENAI_BASE_PATH?: string | null; + OPENAI_BASE_URL?: string | null; + OPENAI_HOST?: string | null; + OPENAI_ORGANIZATION?: string | null; + OPENAI_PROJECT?: string | null; + OPENAI_TIMEOUT?: number | null; + OPENROUTER_HOST?: string | null; + RANDOM_THINKING_MESSAGES?: boolean | null; + SAGEMAKER_ENDPOINT_NAME?: string | null; + SECURITY_COMMAND_CLASSIFIER_ENABLED?: boolean | null; + SECURITY_PROMPT_CLASSIFIER_ENABLED?: boolean | null; + SECURITY_PROMPT_CLASSIFIER_ENDPOINT?: string | null; + SECURITY_PROMPT_CLASSIFIER_MODEL?: string | null; + SECURITY_PROMPT_ENABLED?: boolean | null; + SECURITY_PROMPT_THRESHOLD?: number | null; + SNOWFLAKE_HOST?: string | null; + TETRATE_HOST?: string | null; + VENICE_BASE_PATH?: string | null; + VENICE_HOST?: string | null; + VENICE_MODELS_PATH?: string | null; + XAI_HOST?: string | null; + experiments?: { + [key: string]: boolean; + } | null; + extensions?: { + [key: string]: ExtensionEntry; + } | null; + otel_exporter_otlp_endpoint?: string | null; + otel_exporter_otlp_timeout?: number | null; + slash_commands?: Array | null; + tunnel_auto_start?: boolean | null; +}; + +export type GooseConfigUpdate = { + ANTHROPIC_API_KEY?: string | null; + ANTHROPIC_HOST?: string | null; + AVIAN_HOST?: string | null; + AWS_PROFILE?: string | null; + AWS_REGION?: string | null; + AZURE_OPENAI_API_KEY?: string | null; + AZURE_OPENAI_API_VERSION?: string | null; + AZURE_OPENAI_DEPLOYMENT_NAME?: string | null; + AZURE_OPENAI_ENDPOINT?: string | null; + BEDROCK_BACKOFF_MULTIPLIER?: number | null; + BEDROCK_ENABLE_CACHING?: boolean | null; + BEDROCK_INITIAL_RETRY_INTERVAL_MS?: number | null; + BEDROCK_MAX_RETRIES?: number | null; + BEDROCK_MAX_RETRY_INTERVAL_MS?: number | null; + CHATGPT_CODEX_REASONING_EFFORT?: string | null; + CLAUDE_CODE_COMMAND?: string | null; + CLAUDE_THINKING_BUDGET?: number | null; + CLAUDE_THINKING_EFFORT?: string | null; + CLAUDE_THINKING_TYPE?: string | null; + CODEX_COMMAND?: string | null; + CODEX_ENABLE_SKILLS?: string | null; + CODEX_REASONING_EFFORT?: string | null; + CODEX_SKIP_GIT_CHECK?: string | null; + CODE_MODE_TOOL_DISCLOSURE?: string | null; + CONTEXT_FILE_NAMES?: Array | null; + CURSOR_AGENT_COMMAND?: string | null; + DATABRICKS_BACKOFF_MULTIPLIER?: string | null; + DATABRICKS_HOST?: string | null; + DATABRICKS_INITIAL_RETRY_INTERVAL_MS?: string | null; + DATABRICKS_MAX_RETRIES?: string | null; + DATABRICKS_MAX_RETRY_INTERVAL_MS?: string | null; + DATABRICKS_TOKEN?: string | null; + EDIT_MODE?: string | null; + GCP_BACKOFF_MULTIPLIER?: string | null; + GCP_INITIAL_RETRY_INTERVAL_MS?: string | null; + GCP_LOCATION?: string | null; + GCP_MAX_RETRIES?: string | null; + GCP_MAX_RETRY_INTERVAL_MS?: string | null; + GCP_PROJECT_ID?: string | null; + GEMINI25_THINKING_BUDGET?: number | null; + GEMINI3_THINKING_LEVEL?: string | null; + GEMINI_CLI_COMMAND?: string | null; + GITHUB_COPILOT_CLIENT_ID?: string | null; + GITHUB_COPILOT_HOST?: string | null; + GITHUB_COPILOT_TOKEN_URL?: string | null; + GOOGLE_API_KEY?: string | null; + GOOGLE_HOST?: string | null; + GOOSE_ALLOWLIST?: string | null; + GOOSE_AUTO_COMPACT_THRESHOLD?: number | null; + GOOSE_CA_CERT_PATH?: string | null; + GOOSE_CLIENT_CERT_PATH?: string | null; + GOOSE_CLIENT_KEY_PATH?: string | null; + GOOSE_CLI_DARK_THEME?: string | null; + GOOSE_CLI_LIGHT_THEME?: string | null; + GOOSE_CLI_MIN_PRIORITY?: number | null; + GOOSE_CLI_NEWLINE_KEY?: string | null; + GOOSE_CLI_SHOW_COST?: boolean | null; + GOOSE_CLI_SHOW_THINKING?: boolean | null; + GOOSE_CLI_THEME?: string | null; + GOOSE_CONTEXT_LIMIT?: number | null; + GOOSE_DATABRICKS_CLIENT_REQUEST_ID?: boolean | null; + GOOSE_DEBUG?: boolean | null; + GOOSE_DEFAULT_EXTENSION_TIMEOUT?: number | null; + GOOSE_DISABLE_KEYRING?: string | null; + GOOSE_DISABLE_SESSION_NAMING?: boolean | null; + GOOSE_INPUT_LIMIT?: number | null; + GOOSE_LOCAL_ENABLE_THINKING?: boolean | null; + GOOSE_MAX_ACTIVE_AGENTS?: number | null; + GOOSE_MAX_BACKGROUND_TASKS?: number | null; + GOOSE_MAX_TOKENS?: number | null; + GOOSE_MAX_TURNS?: number | null; + GOOSE_MODE?: GooseMode | null; + GOOSE_MODEL?: string | null; + GOOSE_PLANNER_MODEL?: string | null; + GOOSE_PLANNER_PROVIDER?: string | null; + GOOSE_PROMPT_EDITOR?: string | null; + GOOSE_PROMPT_EDITOR_ALWAYS?: boolean | null; + GOOSE_PROVIDER?: string | null; + GOOSE_RECIPE_GITHUB_REPO?: string | null; + GOOSE_RECIPE_ON_FAILURE_TIMEOUT_SECONDS?: number | null; + GOOSE_RECIPE_RETRY_TIMEOUT_SECONDS?: number | null; + GOOSE_SEARCH_PATHS?: Array | null; + GOOSE_SHOW_FULL_OUTPUT?: boolean | null; + GOOSE_STATUS_HOOK?: string | null; + GOOSE_STREAM_TIMEOUT?: number | null; + GOOSE_SUBAGENT_MAX_TURNS?: number | null; + GOOSE_SUBAGENT_MODEL?: string | null; + GOOSE_SUBAGENT_PROVIDER?: string | null; + GOOSE_SYSTEM_PROMPT_FILE_PATH?: string | null; + GOOSE_TELEMETRY_ENABLED?: boolean | null; + GOOSE_TOOL_CALL_CUTOFF?: number | null; + GOOSE_TOOL_PAIR_SUMMARIZATION?: boolean | null; + LITELLM_BASE_PATH?: string | null; + LITELLM_HOST?: string | null; + LITELLM_TIMEOUT?: number | null; + OLLAMA_HOST?: string | null; + OLLAMA_STREAM_TIMEOUT?: number | null; + OLLAMA_STREAM_USAGE?: boolean | null; + OLLAMA_TIMEOUT?: number | null; + OPENAI_API_KEY?: string | null; + OPENAI_BASE_PATH?: string | null; + OPENAI_BASE_URL?: string | null; + OPENAI_HOST?: string | null; + OPENAI_ORGANIZATION?: string | null; + OPENAI_PROJECT?: string | null; + OPENAI_TIMEOUT?: number | null; + OPENROUTER_API_KEY?: string | null; + OPENROUTER_HOST?: string | null; + RANDOM_THINKING_MESSAGES?: boolean | null; + SAGEMAKER_ENDPOINT_NAME?: string | null; + SECURITY_COMMAND_CLASSIFIER_ENABLED?: boolean | null; + SECURITY_PROMPT_CLASSIFIER_ENABLED?: boolean | null; + SECURITY_PROMPT_CLASSIFIER_ENDPOINT?: string | null; + SECURITY_PROMPT_CLASSIFIER_MODEL?: string | null; + SECURITY_PROMPT_ENABLED?: boolean | null; + SECURITY_PROMPT_THRESHOLD?: number | null; + SNOWFLAKE_HOST?: string | null; + TETRATE_HOST?: string | null; + VENICE_BASE_PATH?: string | null; + VENICE_HOST?: string | null; + VENICE_MODELS_PATH?: string | null; + XAI_HOST?: string | null; + experiments?: { + [key: string]: boolean; + } | null; + extensions?: { + [key: string]: ExtensionEntry; + } | null; + otel_exporter_otlp_endpoint?: string | null; + otel_exporter_otlp_timeout?: number | null; + slash_commands?: Array | null; + tunnel_auto_start?: boolean | null; +}; + export type GooseMode = 'auto' | 'approve' | 'smart_approve' | 'chat'; /** @@ -1389,6 +1660,11 @@ export type SlashCommand = { help: string; }; +export type SlashCommandMapping = { + command: string; + recipe_path: string; +}; + export type SlashCommandsResponse = { commands: Array; }; @@ -2861,6 +3137,52 @@ export type GetSlashCommandsResponses = { export type GetSlashCommandsResponse = GetSlashCommandsResponses[keyof GetSlashCommandsResponses]; +export type ReadTypedConfigData = { + body?: never; + path?: never; + query?: never; + url: '/config/typed'; +}; + +export type ReadTypedConfigErrors = { + /** + * Internal server error + */ + 500: unknown; +}; + +export type ReadTypedConfigResponses = { + /** + * All configuration values (typed) + */ + 200: GooseConfigSchema; +}; + +export type ReadTypedConfigResponse = ReadTypedConfigResponses[keyof ReadTypedConfigResponses]; + +export type PatchTypedConfigData = { + body: GooseConfigUpdate; + path?: never; + query?: never; + url: '/config/typed'; +}; + +export type PatchTypedConfigErrors = { + /** + * Internal server error + */ + 500: unknown; +}; + +export type PatchTypedConfigResponses = { + /** + * Configuration updated + */ + 200: GooseConfigSchema; +}; + +export type PatchTypedConfigResponse = PatchTypedConfigResponses[keyof PatchTypedConfigResponses]; + export type UpsertConfigData = { body: UpsertConfigQuery; path?: never; From b18ca11e063ee3c6a82e271f6460b32f7bedbcaa Mon Sep 17 00:00:00 2001 From: Will Pfleger Date: Wed, 13 May 2026 20:55:58 -0400 Subject: [PATCH 2/4] fix: address crossfire review findings for typed config endpoints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flatten GooseConfigUpdate to embed GooseConfigSchema via #[serde(flatten)], eliminating ~500 lines of duplication and reducing edit sites from 6 to 3 per new config key. Fix GOOSE_DISABLE_KEYRING type (String → bool), add 7 missing provider secret keys, make ALL_KEYS test bidirectional, add config roundtrip test, and log warnings on serialization failures. Signed-off-by: Will Pfleger --- .../src/routes/config_management.rs | 3 + crates/goose/config.schema.json | 2 +- crates/goose/src/config/schema.rs | 586 +++------------- ui/desktop/openapi.json | 642 ++---------------- ui/desktop/src/api/client/types.gen.ts | 2 +- ui/desktop/src/api/core/params.gen.ts | 2 +- ui/desktop/src/api/sdk.gen.ts | 6 + ui/desktop/src/api/types.gen.ts | 144 +--- 8 files changed, 199 insertions(+), 1188 deletions(-) diff --git a/crates/goose-server/src/routes/config_management.rs b/crates/goose-server/src/routes/config_management.rs index 7322b170d5..4cce96882b 100644 --- a/crates/goose-server/src/routes/config_management.rs +++ b/crates/goose-server/src/routes/config_management.rs @@ -857,6 +857,9 @@ pub async fn read_typed_config() -> Result, ErrorRespons Ok(Json(typed)) } +/// Update configuration values. Fields set to null are left unchanged — to delete +/// a key, use `POST /config/remove`. Secret fields (API keys) are stored in the +/// system keyring, not the config file. #[utoipa::path( patch, path = "/config/typed", diff --git a/crates/goose/config.schema.json b/crates/goose/config.schema.json index 7d89411e93..fec6c8b9be 100644 --- a/crates/goose/config.schema.json +++ b/crates/goose/config.schema.json @@ -111,7 +111,7 @@ }, "GOOSE_DISABLE_KEYRING": { "type": [ - "string", + "boolean", "null" ] }, diff --git a/crates/goose/src/config/schema.rs b/crates/goose/src/config/schema.rs index 64ca0c9c3d..a5a694406f 100644 --- a/crates/goose/src/config/schema.rs +++ b/crates/goose/src/config/schema.rs @@ -46,7 +46,7 @@ pub struct GooseConfigSchema { #[serde(rename = "GOOSE_DISABLE_SESSION_NAMING")] pub goose_disable_session_naming: Option, #[serde(rename = "GOOSE_DISABLE_KEYRING")] - pub goose_disable_keyring: Option, + pub goose_disable_keyring: Option, #[serde(rename = "GOOSE_TELEMETRY_ENABLED")] pub goose_telemetry_enabled: Option, #[serde(rename = "GOOSE_DEFAULT_EXTENSION_TIMEOUT")] @@ -271,7 +271,7 @@ pub struct GooseConfigSchema { // === Tunnel Settings (lowercase keys) === pub tunnel_auto_start: Option, - // === Structured Config (lowercase keys) === + // Category B: not in ALL_KEYS — these use dedicated module helpers, not config_value! macro pub extensions: Option>, pub slash_commands: Option>, pub experiments: Option>, @@ -413,6 +413,7 @@ impl GooseConfigSchema { "tunnel_auto_start", ]; + // const fn cannot use == on &str in stable Rust; manual byte comparison required pub const fn has_key(key: &str) -> bool { let key_bytes = key.as_bytes(); let mut i = 0; @@ -592,8 +593,9 @@ impl GooseConfigSchema { macro_rules! push_if_some { ($field:expr, $key:expr) => { if let Some(ref v) = $field { - if let Ok(json) = serde_json::to_value(v) { - updates.push(($key.to_string(), json)); + match serde_json::to_value(v) { + Ok(json) => updates.push(($key.to_string(), json)), + Err(e) => tracing::warn!("Failed to serialize config key {}: {}", $key, e), } } }; @@ -804,267 +806,15 @@ impl GooseConfigSchema { } } +/// Config update payload for `PATCH /config/typed`. +/// +/// Embeds all non-secret fields from [`GooseConfigSchema`] via `#[serde(flatten)]`, +/// plus provider API key fields that route to the system keyring. +/// Fields set to `null` are left unchanged — to delete a key, use `POST /config/remove`. #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, ToSchema)] pub struct GooseConfigUpdate { - // === Core Goose Settings === - #[serde(rename = "GOOSE_PROVIDER")] - pub goose_provider: Option, - #[serde(rename = "GOOSE_MODEL")] - pub goose_model: Option, - #[serde(rename = "GOOSE_MODE")] - pub goose_mode: Option, - #[serde(rename = "GOOSE_MAX_TOKENS")] - pub goose_max_tokens: Option, - #[serde(rename = "GOOSE_CONTEXT_LIMIT")] - pub goose_context_limit: Option, - #[serde(rename = "GOOSE_INPUT_LIMIT")] - pub goose_input_limit: Option, - #[serde(rename = "GOOSE_MAX_TURNS")] - pub goose_max_turns: Option, - #[serde(rename = "GOOSE_MAX_ACTIVE_AGENTS")] - pub goose_max_active_agents: Option, - #[serde(rename = "GOOSE_AUTO_COMPACT_THRESHOLD")] - pub goose_auto_compact_threshold: Option, - #[serde(rename = "GOOSE_TOOL_PAIR_SUMMARIZATION")] - pub goose_tool_pair_summarization: Option, - #[serde(rename = "GOOSE_TOOL_CALL_CUTOFF")] - pub goose_tool_call_cutoff: Option, - #[serde(rename = "GOOSE_STREAM_TIMEOUT")] - pub goose_stream_timeout: Option, - #[serde(rename = "GOOSE_SEARCH_PATHS")] - pub goose_search_paths: Option>, - #[serde(rename = "GOOSE_DISABLE_SESSION_NAMING")] - pub goose_disable_session_naming: Option, - #[serde(rename = "GOOSE_DISABLE_KEYRING")] - pub goose_disable_keyring: Option, - #[serde(rename = "GOOSE_TELEMETRY_ENABLED")] - pub goose_telemetry_enabled: Option, - #[serde(rename = "GOOSE_DEFAULT_EXTENSION_TIMEOUT")] - pub goose_default_extension_timeout: Option, - #[serde(rename = "GOOSE_PROMPT_EDITOR")] - pub goose_prompt_editor: Option, - #[serde(rename = "GOOSE_PROMPT_EDITOR_ALWAYS")] - pub goose_prompt_editor_always: Option, - #[serde(rename = "GOOSE_ALLOWLIST")] - pub goose_allowlist: Option, - #[serde(rename = "GOOSE_SYSTEM_PROMPT_FILE_PATH")] - pub goose_system_prompt_file_path: Option, - #[serde(rename = "GOOSE_DEBUG")] - pub goose_debug: Option, - #[serde(rename = "GOOSE_SHOW_FULL_OUTPUT")] - pub goose_show_full_output: Option, - #[serde(rename = "GOOSE_STATUS_HOOK")] - pub goose_status_hook: Option, - #[serde(rename = "GOOSE_LOCAL_ENABLE_THINKING")] - pub goose_local_enable_thinking: Option, - #[serde(rename = "GOOSE_DATABRICKS_CLIENT_REQUEST_ID")] - pub goose_databricks_client_request_id: Option, - #[serde(rename = "CONTEXT_FILE_NAMES")] - pub context_file_names: Option>, - #[serde(rename = "EDIT_MODE")] - pub edit_mode: Option, - #[serde(rename = "RANDOM_THINKING_MESSAGES")] - pub random_thinking_messages: Option, - #[serde(rename = "CODE_MODE_TOOL_DISCLOSURE")] - pub code_mode_tool_disclosure: Option, - - // === mTLS Settings === - #[serde(rename = "GOOSE_CLIENT_CERT_PATH")] - pub goose_client_cert_path: Option, - #[serde(rename = "GOOSE_CLIENT_KEY_PATH")] - pub goose_client_key_path: Option, - #[serde(rename = "GOOSE_CA_CERT_PATH")] - pub goose_ca_cert_path: Option, - - // === Planner & Subagent Settings === - #[serde(rename = "GOOSE_PLANNER_PROVIDER")] - pub goose_planner_provider: Option, - #[serde(rename = "GOOSE_PLANNER_MODEL")] - pub goose_planner_model: Option, - #[serde(rename = "GOOSE_SUBAGENT_PROVIDER")] - pub goose_subagent_provider: Option, - #[serde(rename = "GOOSE_SUBAGENT_MODEL")] - pub goose_subagent_model: Option, - #[serde(rename = "GOOSE_SUBAGENT_MAX_TURNS")] - pub goose_subagent_max_turns: Option, - #[serde(rename = "GOOSE_MAX_BACKGROUND_TASKS")] - pub goose_max_background_tasks: Option, - - // === Recipe Settings === - #[serde(rename = "GOOSE_RECIPE_GITHUB_REPO")] - pub goose_recipe_github_repo: Option, - #[serde(rename = "GOOSE_RECIPE_RETRY_TIMEOUT_SECONDS")] - pub goose_recipe_retry_timeout_seconds: Option, - #[serde(rename = "GOOSE_RECIPE_ON_FAILURE_TIMEOUT_SECONDS")] - pub goose_recipe_on_failure_timeout_seconds: Option, - - // === CLI Settings === - #[serde(rename = "GOOSE_CLI_MIN_PRIORITY")] - pub goose_cli_min_priority: Option, - #[serde(rename = "GOOSE_CLI_THEME")] - pub goose_cli_theme: Option, - #[serde(rename = "GOOSE_CLI_LIGHT_THEME")] - pub goose_cli_light_theme: Option, - #[serde(rename = "GOOSE_CLI_DARK_THEME")] - pub goose_cli_dark_theme: Option, - #[serde(rename = "GOOSE_CLI_SHOW_COST")] - pub goose_cli_show_cost: Option, - #[serde(rename = "GOOSE_CLI_SHOW_THINKING")] - pub goose_cli_show_thinking: Option, - #[serde(rename = "GOOSE_CLI_NEWLINE_KEY")] - pub goose_cli_newline_key: Option, - - // === AI Agent / Thinking Settings === - #[serde(rename = "CLAUDE_CODE_COMMAND")] - pub claude_code_command: Option, - #[serde(rename = "GEMINI_CLI_COMMAND")] - pub gemini_cli_command: Option, - #[serde(rename = "CURSOR_AGENT_COMMAND")] - pub cursor_agent_command: Option, - #[serde(rename = "CODEX_COMMAND")] - pub codex_command: Option, - #[serde(rename = "CODEX_REASONING_EFFORT")] - pub codex_reasoning_effort: Option, - #[serde(rename = "CODEX_ENABLE_SKILLS")] - pub codex_enable_skills: Option, - #[serde(rename = "CODEX_SKIP_GIT_CHECK")] - pub codex_skip_git_check: Option, - #[serde(rename = "CHATGPT_CODEX_REASONING_EFFORT")] - pub chatgpt_codex_reasoning_effort: Option, - #[serde(rename = "CLAUDE_THINKING_TYPE")] - pub claude_thinking_type: Option, - #[serde(rename = "CLAUDE_THINKING_EFFORT")] - pub claude_thinking_effort: Option, - #[serde(rename = "CLAUDE_THINKING_BUDGET")] - pub claude_thinking_budget: Option, - #[serde(rename = "GEMINI3_THINKING_LEVEL")] - pub gemini3_thinking_level: Option, - #[serde(rename = "GEMINI25_THINKING_BUDGET")] - pub gemini25_thinking_budget: Option, - - // === Security Settings === - #[serde(rename = "SECURITY_PROMPT_ENABLED")] - pub security_prompt_enabled: Option, - #[serde(rename = "SECURITY_PROMPT_THRESHOLD")] - pub security_prompt_threshold: Option, - #[serde(rename = "SECURITY_PROMPT_CLASSIFIER_ENABLED")] - pub security_prompt_classifier_enabled: Option, - #[serde(rename = "SECURITY_PROMPT_CLASSIFIER_MODEL")] - pub security_prompt_classifier_model: Option, - #[serde(rename = "SECURITY_PROMPT_CLASSIFIER_ENDPOINT")] - pub security_prompt_classifier_endpoint: Option, - #[serde(rename = "SECURITY_COMMAND_CLASSIFIER_ENABLED")] - pub security_command_classifier_enabled: Option, - - // === Provider Settings === - #[serde(rename = "OPENAI_HOST")] - pub openai_host: Option, - #[serde(rename = "OPENAI_BASE_URL")] - pub openai_base_url: Option, - #[serde(rename = "OPENAI_BASE_PATH")] - pub openai_base_path: Option, - #[serde(rename = "OPENAI_ORGANIZATION")] - pub openai_organization: Option, - #[serde(rename = "OPENAI_PROJECT")] - pub openai_project: Option, - #[serde(rename = "OPENAI_TIMEOUT")] - pub openai_timeout: Option, - #[serde(rename = "ANTHROPIC_HOST")] - pub anthropic_host: Option, - #[serde(rename = "OLLAMA_HOST")] - pub ollama_host: Option, - #[serde(rename = "OLLAMA_TIMEOUT")] - pub ollama_timeout: Option, - #[serde(rename = "OLLAMA_STREAM_TIMEOUT")] - pub ollama_stream_timeout: Option, - #[serde(rename = "OLLAMA_STREAM_USAGE")] - pub ollama_stream_usage: Option, - #[serde(rename = "DATABRICKS_HOST")] - pub databricks_host: Option, - #[serde(rename = "DATABRICKS_MAX_RETRIES")] - pub databricks_max_retries: Option, - #[serde(rename = "DATABRICKS_INITIAL_RETRY_INTERVAL_MS")] - pub databricks_initial_retry_interval_ms: Option, - #[serde(rename = "DATABRICKS_BACKOFF_MULTIPLIER")] - pub databricks_backoff_multiplier: Option, - #[serde(rename = "DATABRICKS_MAX_RETRY_INTERVAL_MS")] - pub databricks_max_retry_interval_ms: Option, - #[serde(rename = "AZURE_OPENAI_ENDPOINT")] - pub azure_openai_endpoint: Option, - #[serde(rename = "AZURE_OPENAI_DEPLOYMENT_NAME")] - pub azure_openai_deployment_name: Option, - #[serde(rename = "AZURE_OPENAI_API_VERSION")] - pub azure_openai_api_version: Option, - #[serde(rename = "GOOGLE_HOST")] - pub google_host: Option, - #[serde(rename = "GCP_PROJECT_ID")] - pub gcp_project_id: Option, - #[serde(rename = "GCP_LOCATION")] - pub gcp_location: Option, - #[serde(rename = "GCP_MAX_RETRIES")] - pub gcp_max_retries: Option, - #[serde(rename = "GCP_INITIAL_RETRY_INTERVAL_MS")] - pub gcp_initial_retry_interval_ms: Option, - #[serde(rename = "GCP_BACKOFF_MULTIPLIER")] - pub gcp_backoff_multiplier: Option, - #[serde(rename = "GCP_MAX_RETRY_INTERVAL_MS")] - pub gcp_max_retry_interval_ms: Option, - #[serde(rename = "AWS_REGION")] - pub aws_region: Option, - #[serde(rename = "AWS_PROFILE")] - pub aws_profile: Option, - #[serde(rename = "BEDROCK_MAX_RETRIES")] - pub bedrock_max_retries: Option, - #[serde(rename = "BEDROCK_INITIAL_RETRY_INTERVAL_MS")] - pub bedrock_initial_retry_interval_ms: Option, - #[serde(rename = "BEDROCK_BACKOFF_MULTIPLIER")] - pub bedrock_backoff_multiplier: Option, - #[serde(rename = "BEDROCK_MAX_RETRY_INTERVAL_MS")] - pub bedrock_max_retry_interval_ms: Option, - #[serde(rename = "BEDROCK_ENABLE_CACHING")] - pub bedrock_enable_caching: Option, - #[serde(rename = "SAGEMAKER_ENDPOINT_NAME")] - pub sagemaker_endpoint_name: Option, - #[serde(rename = "LITELLM_HOST")] - pub litellm_host: Option, - #[serde(rename = "LITELLM_BASE_PATH")] - pub litellm_base_path: Option, - #[serde(rename = "LITELLM_TIMEOUT")] - pub litellm_timeout: Option, - #[serde(rename = "SNOWFLAKE_HOST")] - pub snowflake_host: Option, - #[serde(rename = "GITHUB_COPILOT_HOST")] - pub github_copilot_host: Option, - #[serde(rename = "GITHUB_COPILOT_CLIENT_ID")] - pub github_copilot_client_id: Option, - #[serde(rename = "GITHUB_COPILOT_TOKEN_URL")] - pub github_copilot_token_url: Option, - #[serde(rename = "XAI_HOST")] - pub xai_host: Option, - #[serde(rename = "OPENROUTER_HOST")] - pub openrouter_host: Option, - #[serde(rename = "VENICE_HOST")] - pub venice_host: Option, - #[serde(rename = "VENICE_BASE_PATH")] - pub venice_base_path: Option, - #[serde(rename = "VENICE_MODELS_PATH")] - pub venice_models_path: Option, - #[serde(rename = "TETRATE_HOST")] - pub tetrate_host: Option, - #[serde(rename = "AVIAN_HOST")] - pub avian_host: Option, - - // === Observability Settings (lowercase keys) === - pub otel_exporter_otlp_endpoint: Option, - pub otel_exporter_otlp_timeout: Option, - - // === Tunnel Settings (lowercase keys) === - pub tunnel_auto_start: Option, - - // === Structured Config (lowercase keys) === - pub extensions: Option>, - pub slash_commands: Option>, - pub experiments: Option>, + #[serde(flatten)] + pub config: GooseConfigSchema, // === Provider API Keys (secrets, stored in keyring) === #[serde(rename = "OPENAI_API_KEY")] @@ -1079,242 +829,53 @@ pub struct GooseConfigUpdate { pub azure_openai_api_key: Option, #[serde(rename = "OPENROUTER_API_KEY")] pub openrouter_api_key: Option, + #[serde(rename = "XAI_API_KEY")] + pub xai_api_key: Option, + #[serde(rename = "AVIAN_API_KEY")] + pub avian_api_key: Option, + #[serde(rename = "VENICE_API_KEY")] + pub venice_api_key: Option, + #[serde(rename = "TETRATE_API_KEY")] + pub tetrate_api_key: Option, + #[serde(rename = "LITELLM_API_KEY")] + pub litellm_api_key: Option, + #[serde(rename = "SNOWFLAKE_TOKEN")] + pub snowflake_token: Option, + #[serde(rename = "GROQ_API_KEY")] + pub groq_api_key: Option, } impl GooseConfigUpdate { pub fn apply_to_config(&self, config: &Config) -> Result<(), ConfigError> { - let mut param_updates: Vec<(String, serde_json::Value)> = Vec::new(); - let mut secret_updates: Vec<(String, serde_json::Value)> = Vec::new(); + self.config.apply_to_config(config)?; - macro_rules! push_param { - ($field:expr, $key:expr) => { - if let Some(ref v) = $field { - if let Ok(json) = serde_json::to_value(v) { - param_updates.push(($key.to_string(), json)); - } - } - }; - } + let mut secret_updates: Vec<(String, serde_json::Value)> = Vec::new(); macro_rules! push_secret { ($field:expr, $key:expr) => { if let Some(ref v) = $field { - if let Ok(json) = serde_json::to_value(v) { - secret_updates.push(($key.to_string(), json)); + match serde_json::to_value(v) { + Ok(json) => secret_updates.push(($key.to_string(), json)), + Err(e) => tracing::warn!("Failed to serialize secret key {}: {}", $key, e), } } }; } - push_param!(self.goose_provider, "GOOSE_PROVIDER"); - push_param!(self.goose_model, "GOOSE_MODEL"); - push_param!(self.goose_mode, "GOOSE_MODE"); - push_param!(self.goose_max_tokens, "GOOSE_MAX_TOKENS"); - push_param!(self.goose_context_limit, "GOOSE_CONTEXT_LIMIT"); - push_param!(self.goose_input_limit, "GOOSE_INPUT_LIMIT"); - push_param!(self.goose_max_turns, "GOOSE_MAX_TURNS"); - push_param!(self.goose_max_active_agents, "GOOSE_MAX_ACTIVE_AGENTS"); - push_param!( - self.goose_auto_compact_threshold, - "GOOSE_AUTO_COMPACT_THRESHOLD" - ); - push_param!( - self.goose_tool_pair_summarization, - "GOOSE_TOOL_PAIR_SUMMARIZATION" - ); - push_param!(self.goose_tool_call_cutoff, "GOOSE_TOOL_CALL_CUTOFF"); - push_param!(self.goose_stream_timeout, "GOOSE_STREAM_TIMEOUT"); - push_param!(self.goose_search_paths, "GOOSE_SEARCH_PATHS"); - push_param!( - self.goose_disable_session_naming, - "GOOSE_DISABLE_SESSION_NAMING" - ); - push_param!(self.goose_disable_keyring, "GOOSE_DISABLE_KEYRING"); - push_param!(self.goose_telemetry_enabled, "GOOSE_TELEMETRY_ENABLED"); - push_param!( - self.goose_default_extension_timeout, - "GOOSE_DEFAULT_EXTENSION_TIMEOUT" - ); - push_param!(self.goose_prompt_editor, "GOOSE_PROMPT_EDITOR"); - push_param!( - self.goose_prompt_editor_always, - "GOOSE_PROMPT_EDITOR_ALWAYS" - ); - push_param!(self.goose_allowlist, "GOOSE_ALLOWLIST"); - push_param!( - self.goose_system_prompt_file_path, - "GOOSE_SYSTEM_PROMPT_FILE_PATH" - ); - push_param!(self.goose_debug, "GOOSE_DEBUG"); - push_param!(self.goose_show_full_output, "GOOSE_SHOW_FULL_OUTPUT"); - push_param!(self.goose_status_hook, "GOOSE_STATUS_HOOK"); - push_param!( - self.goose_local_enable_thinking, - "GOOSE_LOCAL_ENABLE_THINKING" - ); - push_param!( - self.goose_databricks_client_request_id, - "GOOSE_DATABRICKS_CLIENT_REQUEST_ID" - ); - push_param!(self.context_file_names, "CONTEXT_FILE_NAMES"); - push_param!(self.edit_mode, "EDIT_MODE"); - push_param!(self.random_thinking_messages, "RANDOM_THINKING_MESSAGES"); - push_param!(self.code_mode_tool_disclosure, "CODE_MODE_TOOL_DISCLOSURE"); - push_param!(self.goose_client_cert_path, "GOOSE_CLIENT_CERT_PATH"); - push_param!(self.goose_client_key_path, "GOOSE_CLIENT_KEY_PATH"); - push_param!(self.goose_ca_cert_path, "GOOSE_CA_CERT_PATH"); - push_param!(self.goose_planner_provider, "GOOSE_PLANNER_PROVIDER"); - push_param!(self.goose_planner_model, "GOOSE_PLANNER_MODEL"); - push_param!(self.goose_subagent_provider, "GOOSE_SUBAGENT_PROVIDER"); - push_param!(self.goose_subagent_model, "GOOSE_SUBAGENT_MODEL"); - push_param!(self.goose_subagent_max_turns, "GOOSE_SUBAGENT_MAX_TURNS"); - push_param!( - self.goose_max_background_tasks, - "GOOSE_MAX_BACKGROUND_TASKS" - ); - push_param!(self.goose_recipe_github_repo, "GOOSE_RECIPE_GITHUB_REPO"); - push_param!( - self.goose_recipe_retry_timeout_seconds, - "GOOSE_RECIPE_RETRY_TIMEOUT_SECONDS" - ); - push_param!( - self.goose_recipe_on_failure_timeout_seconds, - "GOOSE_RECIPE_ON_FAILURE_TIMEOUT_SECONDS" - ); - push_param!(self.goose_cli_min_priority, "GOOSE_CLI_MIN_PRIORITY"); - push_param!(self.goose_cli_theme, "GOOSE_CLI_THEME"); - push_param!(self.goose_cli_light_theme, "GOOSE_CLI_LIGHT_THEME"); - push_param!(self.goose_cli_dark_theme, "GOOSE_CLI_DARK_THEME"); - push_param!(self.goose_cli_show_cost, "GOOSE_CLI_SHOW_COST"); - push_param!(self.goose_cli_show_thinking, "GOOSE_CLI_SHOW_THINKING"); - push_param!(self.goose_cli_newline_key, "GOOSE_CLI_NEWLINE_KEY"); - push_param!(self.claude_code_command, "CLAUDE_CODE_COMMAND"); - push_param!(self.gemini_cli_command, "GEMINI_CLI_COMMAND"); - push_param!(self.cursor_agent_command, "CURSOR_AGENT_COMMAND"); - push_param!(self.codex_command, "CODEX_COMMAND"); - push_param!(self.codex_reasoning_effort, "CODEX_REASONING_EFFORT"); - push_param!(self.codex_enable_skills, "CODEX_ENABLE_SKILLS"); - push_param!(self.codex_skip_git_check, "CODEX_SKIP_GIT_CHECK"); - push_param!( - self.chatgpt_codex_reasoning_effort, - "CHATGPT_CODEX_REASONING_EFFORT" - ); - push_param!(self.claude_thinking_type, "CLAUDE_THINKING_TYPE"); - push_param!(self.claude_thinking_effort, "CLAUDE_THINKING_EFFORT"); - push_param!(self.claude_thinking_budget, "CLAUDE_THINKING_BUDGET"); - push_param!(self.gemini3_thinking_level, "GEMINI3_THINKING_LEVEL"); - push_param!(self.gemini25_thinking_budget, "GEMINI25_THINKING_BUDGET"); - push_param!(self.security_prompt_enabled, "SECURITY_PROMPT_ENABLED"); - push_param!(self.security_prompt_threshold, "SECURITY_PROMPT_THRESHOLD"); - push_param!( - self.security_prompt_classifier_enabled, - "SECURITY_PROMPT_CLASSIFIER_ENABLED" - ); - push_param!( - self.security_prompt_classifier_model, - "SECURITY_PROMPT_CLASSIFIER_MODEL" - ); - push_param!( - self.security_prompt_classifier_endpoint, - "SECURITY_PROMPT_CLASSIFIER_ENDPOINT" - ); - push_param!( - self.security_command_classifier_enabled, - "SECURITY_COMMAND_CLASSIFIER_ENABLED" - ); - push_param!(self.openai_host, "OPENAI_HOST"); - push_param!(self.openai_base_url, "OPENAI_BASE_URL"); - push_param!(self.openai_base_path, "OPENAI_BASE_PATH"); - push_param!(self.openai_organization, "OPENAI_ORGANIZATION"); - push_param!(self.openai_project, "OPENAI_PROJECT"); - push_param!(self.openai_timeout, "OPENAI_TIMEOUT"); - push_param!(self.anthropic_host, "ANTHROPIC_HOST"); - push_param!(self.ollama_host, "OLLAMA_HOST"); - push_param!(self.ollama_timeout, "OLLAMA_TIMEOUT"); - push_param!(self.ollama_stream_timeout, "OLLAMA_STREAM_TIMEOUT"); - push_param!(self.ollama_stream_usage, "OLLAMA_STREAM_USAGE"); - push_param!(self.databricks_host, "DATABRICKS_HOST"); - push_param!(self.databricks_max_retries, "DATABRICKS_MAX_RETRIES"); - push_param!( - self.databricks_initial_retry_interval_ms, - "DATABRICKS_INITIAL_RETRY_INTERVAL_MS" - ); - push_param!( - self.databricks_backoff_multiplier, - "DATABRICKS_BACKOFF_MULTIPLIER" - ); - push_param!( - self.databricks_max_retry_interval_ms, - "DATABRICKS_MAX_RETRY_INTERVAL_MS" - ); - push_param!(self.azure_openai_endpoint, "AZURE_OPENAI_ENDPOINT"); - push_param!( - self.azure_openai_deployment_name, - "AZURE_OPENAI_DEPLOYMENT_NAME" - ); - push_param!(self.azure_openai_api_version, "AZURE_OPENAI_API_VERSION"); - push_param!(self.google_host, "GOOGLE_HOST"); - push_param!(self.gcp_project_id, "GCP_PROJECT_ID"); - push_param!(self.gcp_location, "GCP_LOCATION"); - push_param!(self.gcp_max_retries, "GCP_MAX_RETRIES"); - push_param!( - self.gcp_initial_retry_interval_ms, - "GCP_INITIAL_RETRY_INTERVAL_MS" - ); - push_param!(self.gcp_backoff_multiplier, "GCP_BACKOFF_MULTIPLIER"); - push_param!(self.gcp_max_retry_interval_ms, "GCP_MAX_RETRY_INTERVAL_MS"); - push_param!(self.aws_region, "AWS_REGION"); - push_param!(self.aws_profile, "AWS_PROFILE"); - push_param!(self.bedrock_max_retries, "BEDROCK_MAX_RETRIES"); - push_param!( - self.bedrock_initial_retry_interval_ms, - "BEDROCK_INITIAL_RETRY_INTERVAL_MS" - ); - push_param!( - self.bedrock_backoff_multiplier, - "BEDROCK_BACKOFF_MULTIPLIER" - ); - push_param!( - self.bedrock_max_retry_interval_ms, - "BEDROCK_MAX_RETRY_INTERVAL_MS" - ); - push_param!(self.bedrock_enable_caching, "BEDROCK_ENABLE_CACHING"); - push_param!(self.sagemaker_endpoint_name, "SAGEMAKER_ENDPOINT_NAME"); - push_param!(self.litellm_host, "LITELLM_HOST"); - push_param!(self.litellm_base_path, "LITELLM_BASE_PATH"); - push_param!(self.litellm_timeout, "LITELLM_TIMEOUT"); - push_param!(self.snowflake_host, "SNOWFLAKE_HOST"); - push_param!(self.github_copilot_host, "GITHUB_COPILOT_HOST"); - push_param!(self.github_copilot_client_id, "GITHUB_COPILOT_CLIENT_ID"); - push_param!(self.github_copilot_token_url, "GITHUB_COPILOT_TOKEN_URL"); - push_param!(self.xai_host, "XAI_HOST"); - push_param!(self.openrouter_host, "OPENROUTER_HOST"); - push_param!(self.venice_host, "VENICE_HOST"); - push_param!(self.venice_base_path, "VENICE_BASE_PATH"); - push_param!(self.venice_models_path, "VENICE_MODELS_PATH"); - push_param!(self.tetrate_host, "TETRATE_HOST"); - push_param!(self.avian_host, "AVIAN_HOST"); - push_param!( - self.otel_exporter_otlp_endpoint, - "otel_exporter_otlp_endpoint" - ); - push_param!( - self.otel_exporter_otlp_timeout, - "otel_exporter_otlp_timeout" - ); - push_param!(self.tunnel_auto_start, "tunnel_auto_start"); - push_param!(self.extensions, "extensions"); - push_param!(self.slash_commands, "slash_commands"); - push_param!(self.experiments, "experiments"); - push_secret!(self.openai_api_key, "OPENAI_API_KEY"); push_secret!(self.anthropic_api_key, "ANTHROPIC_API_KEY"); push_secret!(self.google_api_key, "GOOGLE_API_KEY"); push_secret!(self.databricks_token, "DATABRICKS_TOKEN"); push_secret!(self.azure_openai_api_key, "AZURE_OPENAI_API_KEY"); push_secret!(self.openrouter_api_key, "OPENROUTER_API_KEY"); + push_secret!(self.xai_api_key, "XAI_API_KEY"); + push_secret!(self.avian_api_key, "AVIAN_API_KEY"); + push_secret!(self.venice_api_key, "VENICE_API_KEY"); + push_secret!(self.tetrate_api_key, "TETRATE_API_KEY"); + push_secret!(self.litellm_api_key, "LITELLM_API_KEY"); + push_secret!(self.snowflake_token, "SNOWFLAKE_TOKEN"); + push_secret!(self.groq_api_key, "GROQ_API_KEY"); - config.set_param_values(¶m_updates)?; config.set_secret_values(&secret_updates) } } @@ -1336,6 +897,13 @@ mod tests { let schema_keys: std::collections::HashSet<&str> = properties.keys().map(|k| k.as_str()).collect(); + let category_b: std::collections::HashSet<&str> = + ["extensions", "slash_commands", "experiments"] + .iter() + .copied() + .collect(); + + // Forward: every ALL_KEYS entry must exist in the struct for key in GooseConfigSchema::ALL_KEYS { assert!( schema_keys.contains(key), @@ -1343,8 +911,17 @@ mod tests { ); } + // Reverse: every struct field (except Category B) must be in ALL_KEYS + for key in &schema_keys { + if !category_b.contains(key) { + assert!( + GooseConfigSchema::has_key(key), + "GooseConfigSchema has field '{key}' but it's missing from ALL_KEYS (add it, or mark as Category B)" + ); + } + } + // Category B keys are in the struct but NOT in ALL_KEYS — that's intentional - let category_b = ["extensions", "slash_commands", "experiments"]; for key in &category_b { assert!( schema_keys.contains(key), @@ -1356,4 +933,55 @@ mod tests { ); } } + + #[test] + fn roundtrip_config_values() { + let config_file = tempfile::NamedTempFile::new().unwrap(); + let secrets_file = tempfile::NamedTempFile::new().unwrap(); + let config = + Config::new_with_file_secrets(config_file.path(), secrets_file.path()).unwrap(); + + config + .set_param_values(&[ + ( + "GOOSE_PROVIDER".to_string(), + serde_json::Value::String("anthropic".to_string()), + ), + ( + "GOOSE_MAX_TOKENS".to_string(), + serde_json::Value::Number(4096.into()), + ), + ("GOOSE_DEBUG".to_string(), serde_json::Value::Bool(true)), + ( + "GOOSE_DISABLE_KEYRING".to_string(), + serde_json::Value::Bool(true), + ), + ( + "SECURITY_PROMPT_THRESHOLD".to_string(), + serde_json::json!(0.75), + ), + ]) + .expect("set_param_values should succeed"); + + let typed = GooseConfigSchema::from_config(&config); + assert_eq!(typed.goose_provider.as_deref(), Some("anthropic")); + assert_eq!(typed.goose_max_tokens, Some(4096)); + assert_eq!(typed.goose_debug, Some(true)); + assert_eq!(typed.goose_disable_keyring, Some(true)); + assert_eq!(typed.security_prompt_threshold, Some(0.75)); + + // Roundtrip: apply to a fresh config and verify + let config_file2 = tempfile::NamedTempFile::new().unwrap(); + let secrets_file2 = tempfile::NamedTempFile::new().unwrap(); + let config2 = + Config::new_with_file_secrets(config_file2.path(), secrets_file2.path()).unwrap(); + typed + .apply_to_config(&config2) + .expect("apply_to_config should succeed"); + let typed2 = GooseConfigSchema::from_config(&config2); + assert_eq!(typed2.goose_provider, typed.goose_provider); + assert_eq!(typed2.goose_max_tokens, typed.goose_max_tokens); + assert_eq!(typed2.goose_debug, typed.goose_debug); + assert_eq!(typed2.goose_disable_keyring, typed.goose_disable_keyring); + } } diff --git a/ui/desktop/openapi.json b/ui/desktop/openapi.json index 1efa668135..5496a1b054 100644 --- a/ui/desktop/openapi.json +++ b/ui/desktop/openapi.json @@ -1583,6 +1583,8 @@ "tags": [ "super::routes::config_management" ], + "summary": "Update configuration values. Fields set to null are left unchanged — to delete", + "description": "a key, use `POST /config/remove`. Secret fields (API keys) are stored in the\nsystem keyring, not the config file.", "operationId": "patch_typed_config", "requestBody": { "content": { @@ -5830,7 +5832,7 @@ "minimum": 0 }, "GOOSE_DISABLE_KEYRING": { - "type": "string", + "type": "boolean", "nullable": true }, "GOOSE_DISABLE_SESSION_NAMING": { @@ -6130,585 +6132,69 @@ } }, "GooseConfigUpdate": { - "type": "object", - "properties": { - "ANTHROPIC_API_KEY": { - "type": "string", - "nullable": true - }, - "ANTHROPIC_HOST": { - "type": "string", - "nullable": true - }, - "AVIAN_HOST": { - "type": "string", - "nullable": true - }, - "AWS_PROFILE": { - "type": "string", - "nullable": true - }, - "AWS_REGION": { - "type": "string", - "nullable": true - }, - "AZURE_OPENAI_API_KEY": { - "type": "string", - "nullable": true - }, - "AZURE_OPENAI_API_VERSION": { - "type": "string", - "nullable": true - }, - "AZURE_OPENAI_DEPLOYMENT_NAME": { - "type": "string", - "nullable": true - }, - "AZURE_OPENAI_ENDPOINT": { - "type": "string", - "nullable": true - }, - "BEDROCK_BACKOFF_MULTIPLIER": { - "type": "number", - "format": "double", - "nullable": true - }, - "BEDROCK_ENABLE_CACHING": { - "type": "boolean", - "nullable": true - }, - "BEDROCK_INITIAL_RETRY_INTERVAL_MS": { - "type": "integer", - "format": "int64", - "nullable": true, - "minimum": 0 - }, - "BEDROCK_MAX_RETRIES": { - "type": "integer", - "format": "int64", - "nullable": true, - "minimum": 0 - }, - "BEDROCK_MAX_RETRY_INTERVAL_MS": { - "type": "integer", - "format": "int64", - "nullable": true, - "minimum": 0 - }, - "CHATGPT_CODEX_REASONING_EFFORT": { - "type": "string", - "nullable": true - }, - "CLAUDE_CODE_COMMAND": { - "type": "string", - "nullable": true - }, - "CLAUDE_THINKING_BUDGET": { - "type": "integer", - "format": "int32", - "nullable": true - }, - "CLAUDE_THINKING_EFFORT": { - "type": "string", - "nullable": true - }, - "CLAUDE_THINKING_TYPE": { - "type": "string", - "nullable": true - }, - "CODEX_COMMAND": { - "type": "string", - "nullable": true - }, - "CODEX_ENABLE_SKILLS": { - "type": "string", - "nullable": true - }, - "CODEX_REASONING_EFFORT": { - "type": "string", - "nullable": true - }, - "CODEX_SKIP_GIT_CHECK": { - "type": "string", - "nullable": true - }, - "CODE_MODE_TOOL_DISCLOSURE": { - "type": "string", - "nullable": true - }, - "CONTEXT_FILE_NAMES": { - "type": "array", - "items": { - "type": "string" - }, - "nullable": true - }, - "CURSOR_AGENT_COMMAND": { - "type": "string", - "nullable": true - }, - "DATABRICKS_BACKOFF_MULTIPLIER": { - "type": "string", - "nullable": true - }, - "DATABRICKS_HOST": { - "type": "string", - "nullable": true - }, - "DATABRICKS_INITIAL_RETRY_INTERVAL_MS": { - "type": "string", - "nullable": true - }, - "DATABRICKS_MAX_RETRIES": { - "type": "string", - "nullable": true - }, - "DATABRICKS_MAX_RETRY_INTERVAL_MS": { - "type": "string", - "nullable": true - }, - "DATABRICKS_TOKEN": { - "type": "string", - "nullable": true - }, - "EDIT_MODE": { - "type": "string", - "nullable": true - }, - "GCP_BACKOFF_MULTIPLIER": { - "type": "string", - "nullable": true - }, - "GCP_INITIAL_RETRY_INTERVAL_MS": { - "type": "string", - "nullable": true - }, - "GCP_LOCATION": { - "type": "string", - "nullable": true - }, - "GCP_MAX_RETRIES": { - "type": "string", - "nullable": true - }, - "GCP_MAX_RETRY_INTERVAL_MS": { - "type": "string", - "nullable": true - }, - "GCP_PROJECT_ID": { - "type": "string", - "nullable": true - }, - "GEMINI25_THINKING_BUDGET": { - "type": "integer", - "format": "int32", - "nullable": true - }, - "GEMINI3_THINKING_LEVEL": { - "type": "string", - "nullable": true - }, - "GEMINI_CLI_COMMAND": { - "type": "string", - "nullable": true - }, - "GITHUB_COPILOT_CLIENT_ID": { - "type": "string", - "nullable": true - }, - "GITHUB_COPILOT_HOST": { - "type": "string", - "nullable": true - }, - "GITHUB_COPILOT_TOKEN_URL": { - "type": "string", - "nullable": true - }, - "GOOGLE_API_KEY": { - "type": "string", - "nullable": true - }, - "GOOGLE_HOST": { - "type": "string", - "nullable": true - }, - "GOOSE_ALLOWLIST": { - "type": "string", - "nullable": true - }, - "GOOSE_AUTO_COMPACT_THRESHOLD": { - "type": "number", - "format": "double", - "nullable": true - }, - "GOOSE_CA_CERT_PATH": { - "type": "string", - "nullable": true - }, - "GOOSE_CLIENT_CERT_PATH": { - "type": "string", - "nullable": true - }, - "GOOSE_CLIENT_KEY_PATH": { - "type": "string", - "nullable": true - }, - "GOOSE_CLI_DARK_THEME": { - "type": "string", - "nullable": true - }, - "GOOSE_CLI_LIGHT_THEME": { - "type": "string", - "nullable": true - }, - "GOOSE_CLI_MIN_PRIORITY": { - "type": "number", - "format": "float", - "nullable": true - }, - "GOOSE_CLI_NEWLINE_KEY": { - "type": "string", - "nullable": true - }, - "GOOSE_CLI_SHOW_COST": { - "type": "boolean", - "nullable": true - }, - "GOOSE_CLI_SHOW_THINKING": { - "type": "boolean", - "nullable": true - }, - "GOOSE_CLI_THEME": { - "type": "string", - "nullable": true - }, - "GOOSE_CONTEXT_LIMIT": { - "type": "integer", - "format": "int64", - "nullable": true, - "minimum": 0 - }, - "GOOSE_DATABRICKS_CLIENT_REQUEST_ID": { - "type": "boolean", - "nullable": true - }, - "GOOSE_DEBUG": { - "type": "boolean", - "nullable": true - }, - "GOOSE_DEFAULT_EXTENSION_TIMEOUT": { - "type": "integer", - "format": "int64", - "nullable": true, - "minimum": 0 - }, - "GOOSE_DISABLE_KEYRING": { - "type": "string", - "nullable": true - }, - "GOOSE_DISABLE_SESSION_NAMING": { - "type": "boolean", - "nullable": true - }, - "GOOSE_INPUT_LIMIT": { - "type": "integer", - "format": "int64", - "nullable": true, - "minimum": 0 - }, - "GOOSE_LOCAL_ENABLE_THINKING": { - "type": "boolean", - "nullable": true - }, - "GOOSE_MAX_ACTIVE_AGENTS": { - "type": "integer", - "format": "int64", - "nullable": true, - "minimum": 0 - }, - "GOOSE_MAX_BACKGROUND_TASKS": { - "type": "integer", - "format": "int64", - "nullable": true, - "minimum": 0 - }, - "GOOSE_MAX_TOKENS": { - "type": "integer", - "format": "int32", - "nullable": true - }, - "GOOSE_MAX_TURNS": { - "type": "integer", - "format": "int32", - "nullable": true, - "minimum": 0 - }, - "GOOSE_MODE": { - "allOf": [ - { - "$ref": "#/components/schemas/GooseMode" + "allOf": [ + { + "$ref": "#/components/schemas/GooseConfigSchema" + }, + { + "type": "object", + "properties": { + "ANTHROPIC_API_KEY": { + "type": "string", + "nullable": true + }, + "AVIAN_API_KEY": { + "type": "string", + "nullable": true + }, + "AZURE_OPENAI_API_KEY": { + "type": "string", + "nullable": true + }, + "DATABRICKS_TOKEN": { + "type": "string", + "nullable": true + }, + "GOOGLE_API_KEY": { + "type": "string", + "nullable": true + }, + "GROQ_API_KEY": { + "type": "string", + "nullable": true + }, + "LITELLM_API_KEY": { + "type": "string", + "nullable": true + }, + "OPENAI_API_KEY": { + "type": "string", + "nullable": true + }, + "OPENROUTER_API_KEY": { + "type": "string", + "nullable": true + }, + "SNOWFLAKE_TOKEN": { + "type": "string", + "nullable": true + }, + "TETRATE_API_KEY": { + "type": "string", + "nullable": true + }, + "VENICE_API_KEY": { + "type": "string", + "nullable": true + }, + "XAI_API_KEY": { + "type": "string", + "nullable": true } - ], - "nullable": true - }, - "GOOSE_MODEL": { - "type": "string", - "nullable": true - }, - "GOOSE_PLANNER_MODEL": { - "type": "string", - "nullable": true - }, - "GOOSE_PLANNER_PROVIDER": { - "type": "string", - "nullable": true - }, - "GOOSE_PROMPT_EDITOR": { - "type": "string", - "nullable": true - }, - "GOOSE_PROMPT_EDITOR_ALWAYS": { - "type": "boolean", - "nullable": true - }, - "GOOSE_PROVIDER": { - "type": "string", - "nullable": true - }, - "GOOSE_RECIPE_GITHUB_REPO": { - "type": "string", - "nullable": true - }, - "GOOSE_RECIPE_ON_FAILURE_TIMEOUT_SECONDS": { - "type": "integer", - "format": "int64", - "nullable": true, - "minimum": 0 - }, - "GOOSE_RECIPE_RETRY_TIMEOUT_SECONDS": { - "type": "integer", - "format": "int64", - "nullable": true, - "minimum": 0 - }, - "GOOSE_SEARCH_PATHS": { - "type": "array", - "items": { - "type": "string" - }, - "nullable": true - }, - "GOOSE_SHOW_FULL_OUTPUT": { - "type": "boolean", - "nullable": true - }, - "GOOSE_STATUS_HOOK": { - "type": "string", - "nullable": true - }, - "GOOSE_STREAM_TIMEOUT": { - "type": "integer", - "format": "int64", - "nullable": true, - "minimum": 0 - }, - "GOOSE_SUBAGENT_MAX_TURNS": { - "type": "integer", - "format": "int64", - "nullable": true, - "minimum": 0 - }, - "GOOSE_SUBAGENT_MODEL": { - "type": "string", - "nullable": true - }, - "GOOSE_SUBAGENT_PROVIDER": { - "type": "string", - "nullable": true - }, - "GOOSE_SYSTEM_PROMPT_FILE_PATH": { - "type": "string", - "nullable": true - }, - "GOOSE_TELEMETRY_ENABLED": { - "type": "boolean", - "nullable": true - }, - "GOOSE_TOOL_CALL_CUTOFF": { - "type": "integer", - "format": "int64", - "nullable": true, - "minimum": 0 - }, - "GOOSE_TOOL_PAIR_SUMMARIZATION": { - "type": "boolean", - "nullable": true - }, - "LITELLM_BASE_PATH": { - "type": "string", - "nullable": true - }, - "LITELLM_HOST": { - "type": "string", - "nullable": true - }, - "LITELLM_TIMEOUT": { - "type": "integer", - "format": "int64", - "nullable": true, - "minimum": 0 - }, - "OLLAMA_HOST": { - "type": "string", - "nullable": true - }, - "OLLAMA_STREAM_TIMEOUT": { - "type": "integer", - "format": "int64", - "nullable": true, - "minimum": 0 - }, - "OLLAMA_STREAM_USAGE": { - "type": "boolean", - "nullable": true - }, - "OLLAMA_TIMEOUT": { - "type": "integer", - "format": "int64", - "nullable": true, - "minimum": 0 - }, - "OPENAI_API_KEY": { - "type": "string", - "nullable": true - }, - "OPENAI_BASE_PATH": { - "type": "string", - "nullable": true - }, - "OPENAI_BASE_URL": { - "type": "string", - "nullable": true - }, - "OPENAI_HOST": { - "type": "string", - "nullable": true - }, - "OPENAI_ORGANIZATION": { - "type": "string", - "nullable": true - }, - "OPENAI_PROJECT": { - "type": "string", - "nullable": true - }, - "OPENAI_TIMEOUT": { - "type": "integer", - "format": "int64", - "nullable": true, - "minimum": 0 - }, - "OPENROUTER_API_KEY": { - "type": "string", - "nullable": true - }, - "OPENROUTER_HOST": { - "type": "string", - "nullable": true - }, - "RANDOM_THINKING_MESSAGES": { - "type": "boolean", - "nullable": true - }, - "SAGEMAKER_ENDPOINT_NAME": { - "type": "string", - "nullable": true - }, - "SECURITY_COMMAND_CLASSIFIER_ENABLED": { - "type": "boolean", - "nullable": true - }, - "SECURITY_PROMPT_CLASSIFIER_ENABLED": { - "type": "boolean", - "nullable": true - }, - "SECURITY_PROMPT_CLASSIFIER_ENDPOINT": { - "type": "string", - "nullable": true - }, - "SECURITY_PROMPT_CLASSIFIER_MODEL": { - "type": "string", - "nullable": true - }, - "SECURITY_PROMPT_ENABLED": { - "type": "boolean", - "nullable": true - }, - "SECURITY_PROMPT_THRESHOLD": { - "type": "number", - "format": "double", - "nullable": true - }, - "SNOWFLAKE_HOST": { - "type": "string", - "nullable": true - }, - "TETRATE_HOST": { - "type": "string", - "nullable": true - }, - "VENICE_BASE_PATH": { - "type": "string", - "nullable": true - }, - "VENICE_HOST": { - "type": "string", - "nullable": true - }, - "VENICE_MODELS_PATH": { - "type": "string", - "nullable": true - }, - "XAI_HOST": { - "type": "string", - "nullable": true - }, - "experiments": { - "type": "object", - "additionalProperties": { - "type": "boolean" - }, - "nullable": true - }, - "extensions": { - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/ExtensionEntry" - }, - "nullable": true - }, - "otel_exporter_otlp_endpoint": { - "type": "string", - "nullable": true - }, - "otel_exporter_otlp_timeout": { - "type": "integer", - "format": "int64", - "nullable": true, - "minimum": 0 - }, - "slash_commands": { - "type": "array", - "items": { - "$ref": "#/components/schemas/SlashCommandMapping" - }, - "nullable": true - }, - "tunnel_auto_start": { - "type": "boolean", - "nullable": true + } } - } + ], + "description": "Config update payload for `PATCH /config/typed`.\n\nEmbeds all non-secret fields from [`GooseConfigSchema`] via `#[serde(flatten)]`,\nplus provider API key fields that route to the system keyring.\nFields set to `null` are left unchanged — to delete a key, use `POST /config/remove`." }, "GooseMode": { "type": "string", diff --git a/ui/desktop/src/api/client/types.gen.ts b/ui/desktop/src/api/client/types.gen.ts index a3f8616511..8c0df2321e 100644 --- a/ui/desktop/src/api/client/types.gen.ts +++ b/ui/desktop/src/api/client/types.gen.ts @@ -190,7 +190,7 @@ export type Client = CoreClient */ export type CreateClientConfig = ( override?: Config, -) => Config & T>; +) => Config & T> | Promise & T>>; export interface TDataShape { body?: unknown; diff --git a/ui/desktop/src/api/core/params.gen.ts b/ui/desktop/src/api/core/params.gen.ts index 6099cab1b4..7955601a5c 100644 --- a/ui/desktop/src/api/core/params.gen.ts +++ b/ui/desktop/src/api/core/params.gen.ts @@ -96,7 +96,7 @@ interface Params { const stripEmptySlots = (params: Params) => { for (const [slot, value] of Object.entries(params)) { - if (value && typeof value === 'object' && !Object.keys(value).length) { + if (value && typeof value === 'object' && !Array.isArray(value) && !Object.keys(value).length) { delete params[slot as Slot]; } } diff --git a/ui/desktop/src/api/sdk.gen.ts b/ui/desktop/src/api/sdk.gen.ts index 8330466964..ed9c38baca 100644 --- a/ui/desktop/src/api/sdk.gen.ts +++ b/ui/desktop/src/api/sdk.gen.ts @@ -272,6 +272,12 @@ export const getSlashCommands = (options?: export const readTypedConfig = (options?: Options) => (options?.client ?? client).get({ url: '/config/typed', ...options }); +/** + * Update configuration values. Fields set to null are left unchanged — to delete + * + * a key, use `POST /config/remove`. Secret fields (API keys) are stored in the + * system keyring, not the config file. + */ export const patchTypedConfig = (options: Options) => (options.client ?? client).patch({ url: '/config/typed', ...options, diff --git a/ui/desktop/src/api/types.gen.ts b/ui/desktop/src/api/types.gen.ts index 714b55a929..81dab2be76 100644 --- a/ui/desktop/src/api/types.gen.ts +++ b/ui/desktop/src/api/types.gen.ts @@ -596,7 +596,7 @@ export type GooseConfigSchema = { GOOSE_DATABRICKS_CLIENT_REQUEST_ID?: boolean | null; GOOSE_DEBUG?: boolean | null; GOOSE_DEFAULT_EXTENSION_TIMEOUT?: number | null; - GOOSE_DISABLE_KEYRING?: string | null; + GOOSE_DISABLE_KEYRING?: boolean | null; GOOSE_DISABLE_SESSION_NAMING?: boolean | null; GOOSE_INPUT_LIMIT?: number | null; GOOSE_LOCAL_ENABLE_THINKING?: boolean | null; @@ -665,139 +665,27 @@ export type GooseConfigSchema = { tunnel_auto_start?: boolean | null; }; -export type GooseConfigUpdate = { +/** + * Config update payload for `PATCH /config/typed`. + * + * Embeds all non-secret fields from [`GooseConfigSchema`] via `#[serde(flatten)]`, + * plus provider API key fields that route to the system keyring. + * Fields set to `null` are left unchanged — to delete a key, use `POST /config/remove`. + */ +export type GooseConfigUpdate = GooseConfigSchema & { ANTHROPIC_API_KEY?: string | null; - ANTHROPIC_HOST?: string | null; - AVIAN_HOST?: string | null; - AWS_PROFILE?: string | null; - AWS_REGION?: string | null; + AVIAN_API_KEY?: string | null; AZURE_OPENAI_API_KEY?: string | null; - AZURE_OPENAI_API_VERSION?: string | null; - AZURE_OPENAI_DEPLOYMENT_NAME?: string | null; - AZURE_OPENAI_ENDPOINT?: string | null; - BEDROCK_BACKOFF_MULTIPLIER?: number | null; - BEDROCK_ENABLE_CACHING?: boolean | null; - BEDROCK_INITIAL_RETRY_INTERVAL_MS?: number | null; - BEDROCK_MAX_RETRIES?: number | null; - BEDROCK_MAX_RETRY_INTERVAL_MS?: number | null; - CHATGPT_CODEX_REASONING_EFFORT?: string | null; - CLAUDE_CODE_COMMAND?: string | null; - CLAUDE_THINKING_BUDGET?: number | null; - CLAUDE_THINKING_EFFORT?: string | null; - CLAUDE_THINKING_TYPE?: string | null; - CODEX_COMMAND?: string | null; - CODEX_ENABLE_SKILLS?: string | null; - CODEX_REASONING_EFFORT?: string | null; - CODEX_SKIP_GIT_CHECK?: string | null; - CODE_MODE_TOOL_DISCLOSURE?: string | null; - CONTEXT_FILE_NAMES?: Array | null; - CURSOR_AGENT_COMMAND?: string | null; - DATABRICKS_BACKOFF_MULTIPLIER?: string | null; - DATABRICKS_HOST?: string | null; - DATABRICKS_INITIAL_RETRY_INTERVAL_MS?: string | null; - DATABRICKS_MAX_RETRIES?: string | null; - DATABRICKS_MAX_RETRY_INTERVAL_MS?: string | null; DATABRICKS_TOKEN?: string | null; - EDIT_MODE?: string | null; - GCP_BACKOFF_MULTIPLIER?: string | null; - GCP_INITIAL_RETRY_INTERVAL_MS?: string | null; - GCP_LOCATION?: string | null; - GCP_MAX_RETRIES?: string | null; - GCP_MAX_RETRY_INTERVAL_MS?: string | null; - GCP_PROJECT_ID?: string | null; - GEMINI25_THINKING_BUDGET?: number | null; - GEMINI3_THINKING_LEVEL?: string | null; - GEMINI_CLI_COMMAND?: string | null; - GITHUB_COPILOT_CLIENT_ID?: string | null; - GITHUB_COPILOT_HOST?: string | null; - GITHUB_COPILOT_TOKEN_URL?: string | null; GOOGLE_API_KEY?: string | null; - GOOGLE_HOST?: string | null; - GOOSE_ALLOWLIST?: string | null; - GOOSE_AUTO_COMPACT_THRESHOLD?: number | null; - GOOSE_CA_CERT_PATH?: string | null; - GOOSE_CLIENT_CERT_PATH?: string | null; - GOOSE_CLIENT_KEY_PATH?: string | null; - GOOSE_CLI_DARK_THEME?: string | null; - GOOSE_CLI_LIGHT_THEME?: string | null; - GOOSE_CLI_MIN_PRIORITY?: number | null; - GOOSE_CLI_NEWLINE_KEY?: string | null; - GOOSE_CLI_SHOW_COST?: boolean | null; - GOOSE_CLI_SHOW_THINKING?: boolean | null; - GOOSE_CLI_THEME?: string | null; - GOOSE_CONTEXT_LIMIT?: number | null; - GOOSE_DATABRICKS_CLIENT_REQUEST_ID?: boolean | null; - GOOSE_DEBUG?: boolean | null; - GOOSE_DEFAULT_EXTENSION_TIMEOUT?: number | null; - GOOSE_DISABLE_KEYRING?: string | null; - GOOSE_DISABLE_SESSION_NAMING?: boolean | null; - GOOSE_INPUT_LIMIT?: number | null; - GOOSE_LOCAL_ENABLE_THINKING?: boolean | null; - GOOSE_MAX_ACTIVE_AGENTS?: number | null; - GOOSE_MAX_BACKGROUND_TASKS?: number | null; - GOOSE_MAX_TOKENS?: number | null; - GOOSE_MAX_TURNS?: number | null; - GOOSE_MODE?: GooseMode | null; - GOOSE_MODEL?: string | null; - GOOSE_PLANNER_MODEL?: string | null; - GOOSE_PLANNER_PROVIDER?: string | null; - GOOSE_PROMPT_EDITOR?: string | null; - GOOSE_PROMPT_EDITOR_ALWAYS?: boolean | null; - GOOSE_PROVIDER?: string | null; - GOOSE_RECIPE_GITHUB_REPO?: string | null; - GOOSE_RECIPE_ON_FAILURE_TIMEOUT_SECONDS?: number | null; - GOOSE_RECIPE_RETRY_TIMEOUT_SECONDS?: number | null; - GOOSE_SEARCH_PATHS?: Array | null; - GOOSE_SHOW_FULL_OUTPUT?: boolean | null; - GOOSE_STATUS_HOOK?: string | null; - GOOSE_STREAM_TIMEOUT?: number | null; - GOOSE_SUBAGENT_MAX_TURNS?: number | null; - GOOSE_SUBAGENT_MODEL?: string | null; - GOOSE_SUBAGENT_PROVIDER?: string | null; - GOOSE_SYSTEM_PROMPT_FILE_PATH?: string | null; - GOOSE_TELEMETRY_ENABLED?: boolean | null; - GOOSE_TOOL_CALL_CUTOFF?: number | null; - GOOSE_TOOL_PAIR_SUMMARIZATION?: boolean | null; - LITELLM_BASE_PATH?: string | null; - LITELLM_HOST?: string | null; - LITELLM_TIMEOUT?: number | null; - OLLAMA_HOST?: string | null; - OLLAMA_STREAM_TIMEOUT?: number | null; - OLLAMA_STREAM_USAGE?: boolean | null; - OLLAMA_TIMEOUT?: number | null; + GROQ_API_KEY?: string | null; + LITELLM_API_KEY?: string | null; OPENAI_API_KEY?: string | null; - OPENAI_BASE_PATH?: string | null; - OPENAI_BASE_URL?: string | null; - OPENAI_HOST?: string | null; - OPENAI_ORGANIZATION?: string | null; - OPENAI_PROJECT?: string | null; - OPENAI_TIMEOUT?: number | null; OPENROUTER_API_KEY?: string | null; - OPENROUTER_HOST?: string | null; - RANDOM_THINKING_MESSAGES?: boolean | null; - SAGEMAKER_ENDPOINT_NAME?: string | null; - SECURITY_COMMAND_CLASSIFIER_ENABLED?: boolean | null; - SECURITY_PROMPT_CLASSIFIER_ENABLED?: boolean | null; - SECURITY_PROMPT_CLASSIFIER_ENDPOINT?: string | null; - SECURITY_PROMPT_CLASSIFIER_MODEL?: string | null; - SECURITY_PROMPT_ENABLED?: boolean | null; - SECURITY_PROMPT_THRESHOLD?: number | null; - SNOWFLAKE_HOST?: string | null; - TETRATE_HOST?: string | null; - VENICE_BASE_PATH?: string | null; - VENICE_HOST?: string | null; - VENICE_MODELS_PATH?: string | null; - XAI_HOST?: string | null; - experiments?: { - [key: string]: boolean; - } | null; - extensions?: { - [key: string]: ExtensionEntry; - } | null; - otel_exporter_otlp_endpoint?: string | null; - otel_exporter_otlp_timeout?: number | null; - slash_commands?: Array | null; - tunnel_auto_start?: boolean | null; + SNOWFLAKE_TOKEN?: string | null; + TETRATE_API_KEY?: string | null; + VENICE_API_KEY?: string | null; + XAI_API_KEY?: string | null; }; export type GooseMode = 'auto' | 'approve' | 'smart_approve' | 'chat'; From 036136a959f439937162f37e5b103bfd8d1e8d92 Mon Sep 17 00:00:00 2001 From: Will Pfleger Date: Wed, 13 May 2026 22:49:28 -0400 Subject: [PATCH 3/4] fix: patch client.gen.ts for openapi-ts 0.93.1 codegen compat MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Missed this file in the previous commit — CI uses @hey-api/openapi-ts 0.93.1 (no lockfile, resolves ^0.93.0 to latest) while local has 0.93.0. The CreateClientConfig return type now includes | Promise<...>. Signed-off-by: Will Pfleger --- ui/desktop/src/api/client.gen.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/desktop/src/api/client.gen.ts b/ui/desktop/src/api/client.gen.ts index cab3c70195..d81ce3f8f7 100644 --- a/ui/desktop/src/api/client.gen.ts +++ b/ui/desktop/src/api/client.gen.ts @@ -11,6 +11,6 @@ import type { ClientOptions as ClientOptions2 } from './types.gen'; * `setConfig()`. This is useful for example if you're using Next.js * to ensure your client always has the correct values. */ -export type CreateClientConfig = (override?: Config) => Config & T>; +export type CreateClientConfig = (override?: Config) => Config & T> | Promise & T>>; export const client = createClient(createConfig()); From ea40228713277ffb2d595567a7b9fbd33bb331a8 Mon Sep 17 00:00:00 2001 From: Will Pfleger Date: Thu, 14 May 2026 13:34:42 -0400 Subject: [PATCH 4/4] fix: address round-2 crossfire review findings and pin openapi-ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Round-2 crossfire review (3 Claude agents + Codex + Gemini) surfaced a missing config key, a CLI display bug, incomplete test coverage, and the root cause of the CI codegen mismatch. This commit addresses all actionable findings and eliminates the manual TS codegen patching. Key changes: - Add GOOSE_DISABLE_TOOL_CALL_SUMMARY to GooseConfigSchema - Add NANOGPT_API_KEY and LITELLM_CUSTOM_HEADERS to GooseConfigUpdate - Fix configure.rs keyring status display for boolean GOOSE_DISABLE_KEYRING - Add exhaustive from_config/apply_to_config coverage test - Add CI check-config-schema job + Justfile targets - Fix generate-config-schema error message (underscores → hyphens) - Pin @hey-api/openapi-ts to 0.93.1, matching the lockfile - Update PATCH endpoint docs (sparse semantics, nested replacement, env caution) Signed-off-by: Will Pfleger --- .github/workflows/ci.yml | 5 + Justfile | 22 +++ crates/goose-cli/src/commands/configure.rs | 6 +- .../src/routes/config_management.rs | 15 +- crates/goose/config.schema.json | 8 +- .../goose/src/bin/generate_config_schema.rs | 2 +- crates/goose/src/config/schema.rs | 161 +++++++++++++++++- ui/desktop/openapi.json | 20 ++- ui/desktop/package.json | 2 +- ui/desktop/src/api/sdk.gen.ts | 15 +- ui/desktop/src/api/types.gen.ts | 17 +- ui/pnpm-lock.yaml | 26 +-- 12 files changed, 253 insertions(+), 46 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 21e866c02d..56c5280301 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -194,6 +194,11 @@ jobs: source ./bin/activate-hermit just check-acp-schema + - name: Check Config Schema is Up-to-Date + run: | + source ./bin/activate-hermit + just check-config-schema + desktop-lint: name: Test and Lint Electron Desktop App runs-on: macos-latest diff --git a/Justfile b/Justfile index bb4996370c..5d98408c00 100644 --- a/Justfile +++ b/Justfile @@ -17,6 +17,8 @@ check-everything: cd ui/desktop && pnpm run lint:check @echo " → Validating OpenAPI schema..." ./scripts/check-openapi-schema.sh + @echo " → Validating config schema..." + just check-config-schema @echo "" @echo "✅ All style checks passed!" @@ -200,6 +202,26 @@ generate-openapi: @echo "Generating frontend API..." cd ui/desktop && npx @hey-api/openapi-ts +# Generate config.schema.json from Rust types +generate-config-schema: + @echo "Generating config schema..." + cargo run -p goose --bin generate-config-schema + @echo "Config schema generated: crates/goose/config.schema.json" + +# Check if config.schema.json is up-to-date +check-config-schema: generate-config-schema + #!/usr/bin/env bash + set -e + echo "🔍 Checking config schema is up-to-date..." + if ! git diff --exit-code crates/goose/config.schema.json; then + echo "" + echo "❌ Config schema is out of date!" + echo "" + echo "Run 'just generate-config-schema' locally, then commit the changes." + exit 1 + fi + echo "✅ Config schema is up-to-date" + # Check if generated ACP schema and TypeScript types are up-to-date check-acp-schema: generate-acp-types #!/usr/bin/env bash diff --git a/crates/goose-cli/src/commands/configure.rs b/crates/goose-cli/src/commands/configure.rs index c682724276..d6940692b3 100644 --- a/crates/goose-cli/src/commands/configure.rs +++ b/crates/goose-cli/src/commands/configure.rs @@ -1482,7 +1482,11 @@ pub fn configure_keyring_dialog() -> anyhow::Result<()> { ); } - let currently_disabled = config.get_param::("GOOSE_DISABLE_KEYRING").is_ok(); + let currently_disabled = config + .get_param::("GOOSE_DISABLE_KEYRING") + .is_ok_and(|v| { + v.as_bool().unwrap_or(false) || v.as_str().is_some_and(|s| s == "true" || s == "1") + }); let current_status = if currently_disabled { "Disabled (using file-based storage)" diff --git a/crates/goose-server/src/routes/config_management.rs b/crates/goose-server/src/routes/config_management.rs index 4cce96882b..4c85fa1b92 100644 --- a/crates/goose-server/src/routes/config_management.rs +++ b/crates/goose-server/src/routes/config_management.rs @@ -857,9 +857,18 @@ pub async fn read_typed_config() -> Result, ErrorRespons Ok(Json(typed)) } -/// Update configuration values. Fields set to null are left unchanged — to delete -/// a key, use `POST /config/remove`. Secret fields (API keys) are stored in the -/// system keyring, not the config file. +/// Update configuration values via sparse patch. Only send the fields you want +/// to change — omitted and null fields are both left unchanged (serde cannot +/// distinguish the two). To delete a key, use `POST /config/remove`. +/// +/// Nested objects (`extensions`, `slash_commands`, `experiments`) use whole-value +/// replacement, not deep merge. Secret fields (API keys) are stored in the system +/// keyring, not the config file. +/// +/// **Caution:** `GET /config/typed` returns values merged from env vars, system +/// config, and user config. Sending the full GET response back as a PATCH payload +/// can persist inherited/env values into the user config file. Only send fields +/// the user explicitly changed. #[utoipa::path( patch, path = "/config/typed", diff --git a/crates/goose/config.schema.json b/crates/goose/config.schema.json index fec6c8b9be..e1e142d4ec 100644 --- a/crates/goose/config.schema.json +++ b/crates/goose/config.schema.json @@ -1,7 +1,7 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "GooseConfigSchema", - "description": "JSON Schema representation of Goose's config.yaml.\n\nAll keys are optional. Unknown keys are allowed (additionalProperties: true)\nbecause Goose passes undocumented provider-specific keys through as\nenvironment variable overrides.", + "description": "JSON Schema representation of Goose's config.yaml.\n\nAll fields are optional. The standalone JSON Schema (`config.schema.json`)\nsets `additionalProperties: true` so config.yaml can carry undocumented\nprovider-specific keys as env-var overrides. However, the typed API\nendpoints only persist fields explicitly declared on this struct — unknown\nkeys in a `PATCH /config/typed` payload are silently dropped by serde.", "type": "object", "properties": { "GOOSE_PROVIDER": { @@ -165,6 +165,12 @@ "null" ] }, + "GOOSE_DISABLE_TOOL_CALL_SUMMARY": { + "type": [ + "boolean", + "null" + ] + }, "GOOSE_STATUS_HOOK": { "type": [ "string", diff --git a/crates/goose/src/bin/generate_config_schema.rs b/crates/goose/src/bin/generate_config_schema.rs index 2d3d36523b..d82ae5942a 100644 --- a/crates/goose/src/bin/generate_config_schema.rs +++ b/crates/goose/src/bin/generate_config_schema.rs @@ -16,7 +16,7 @@ fn main() { let existing = fs::read_to_string(&schema_path).unwrap_or_default(); if existing.trim() != json_str.trim() { eprintln!( - "Config schema is out of date. Run `cargo run -p goose --bin generate_config_schema` to regenerate." + "Config schema is out of date. Run `cargo run -p goose --bin generate-config-schema` to regenerate." ); std::process::exit(1); } diff --git a/crates/goose/src/config/schema.rs b/crates/goose/src/config/schema.rs index a5a694406f..92730455c8 100644 --- a/crates/goose/src/config/schema.rs +++ b/crates/goose/src/config/schema.rs @@ -11,9 +11,11 @@ use crate::slash_commands::SlashCommandMapping; /// JSON Schema representation of Goose's config.yaml. /// -/// All keys are optional. Unknown keys are allowed (additionalProperties: true) -/// because Goose passes undocumented provider-specific keys through as -/// environment variable overrides. +/// All fields are optional. The standalone JSON Schema (`config.schema.json`) +/// sets `additionalProperties: true` so config.yaml can carry undocumented +/// provider-specific keys as env-var overrides. However, the typed API +/// endpoints only persist fields explicitly declared on this struct — unknown +/// keys in a `PATCH /config/typed` payload are silently dropped by serde. #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, ToSchema)] pub struct GooseConfigSchema { // === Core Goose Settings === @@ -63,6 +65,8 @@ pub struct GooseConfigSchema { pub goose_debug: Option, #[serde(rename = "GOOSE_SHOW_FULL_OUTPUT")] pub goose_show_full_output: Option, + #[serde(rename = "GOOSE_DISABLE_TOOL_CALL_SUMMARY")] + pub goose_disable_tool_call_summary: Option, #[serde(rename = "GOOSE_STATUS_HOOK")] pub goose_status_hook: Option, #[serde(rename = "GOOSE_LOCAL_ENABLE_THINKING")] @@ -306,6 +310,7 @@ impl GooseConfigSchema { "GOOSE_SYSTEM_PROMPT_FILE_PATH", "GOOSE_DEBUG", "GOOSE_SHOW_FULL_OUTPUT", + "GOOSE_DISABLE_TOOL_CALL_SUMMARY", "GOOSE_STATUS_HOOK", "GOOSE_LOCAL_ENABLE_THINKING", "GOOSE_DATABRICKS_CLIENT_REQUEST_ID", @@ -465,6 +470,9 @@ impl GooseConfigSchema { goose_system_prompt_file_path: config.get_param("GOOSE_SYSTEM_PROMPT_FILE_PATH").ok(), goose_debug: config.get_param("GOOSE_DEBUG").ok(), goose_show_full_output: config.get_param("GOOSE_SHOW_FULL_OUTPUT").ok(), + goose_disable_tool_call_summary: config + .get_param("GOOSE_DISABLE_TOOL_CALL_SUMMARY") + .ok(), goose_status_hook: config.get_param("GOOSE_STATUS_HOOK").ok(), goose_local_enable_thinking: config.get_param("GOOSE_LOCAL_ENABLE_THINKING").ok(), goose_databricks_client_request_id: config @@ -642,6 +650,10 @@ impl GooseConfigSchema { ); push_if_some!(self.goose_debug, "GOOSE_DEBUG"); push_if_some!(self.goose_show_full_output, "GOOSE_SHOW_FULL_OUTPUT"); + push_if_some!( + self.goose_disable_tool_call_summary, + "GOOSE_DISABLE_TOOL_CALL_SUMMARY" + ); push_if_some!(self.goose_status_hook, "GOOSE_STATUS_HOOK"); push_if_some!( self.goose_local_enable_thinking, @@ -810,7 +822,11 @@ impl GooseConfigSchema { /// /// Embeds all non-secret fields from [`GooseConfigSchema`] via `#[serde(flatten)]`, /// plus provider API key fields that route to the system keyring. -/// Fields set to `null` are left unchanged — to delete a key, use `POST /config/remove`. +/// +/// **Sparse patch semantics:** Only send fields you want to change. Fields set to +/// `null` (or omitted) are left unchanged — serde cannot distinguish the two cases. +/// To delete a key, use `POST /config/remove`. Nested objects (`extensions`, +/// `slash_commands`, `experiments`) use whole-value replacement, not deep merge. #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, ToSchema)] pub struct GooseConfigUpdate { #[serde(flatten)] @@ -843,6 +859,10 @@ pub struct GooseConfigUpdate { pub snowflake_token: Option, #[serde(rename = "GROQ_API_KEY")] pub groq_api_key: Option, + #[serde(rename = "NANOGPT_API_KEY")] + pub nanogpt_api_key: Option, + #[serde(rename = "LITELLM_CUSTOM_HEADERS")] + pub litellm_custom_headers: Option, } impl GooseConfigUpdate { @@ -875,6 +895,8 @@ impl GooseConfigUpdate { push_secret!(self.litellm_api_key, "LITELLM_API_KEY"); push_secret!(self.snowflake_token, "SNOWFLAKE_TOKEN"); push_secret!(self.groq_api_key, "GROQ_API_KEY"); + push_secret!(self.nanogpt_api_key, "NANOGPT_API_KEY"); + push_secret!(self.litellm_custom_headers, "LITELLM_CUSTOM_HEADERS"); config.set_secret_values(&secret_updates) } @@ -960,6 +982,19 @@ mod tests { "SECURITY_PROMPT_THRESHOLD".to_string(), serde_json::json!(0.75), ), + ( + "GOOSE_MODE".to_string(), + serde_json::Value::String("auto".to_string()), + ), + ( + "GOOSE_SEARCH_PATHS".to_string(), + serde_json::json!(["/usr/local/bin", "/opt/bin"]), + ), + ( + "GOOSE_CONTEXT_LIMIT".to_string(), + serde_json::Value::Number(128000u64.into()), + ), + ("GOOSE_CLI_MIN_PRIORITY".to_string(), serde_json::json!(0.5)), ]) .expect("set_param_values should succeed"); @@ -969,6 +1004,12 @@ mod tests { assert_eq!(typed.goose_debug, Some(true)); assert_eq!(typed.goose_disable_keyring, Some(true)); assert_eq!(typed.security_prompt_threshold, Some(0.75)); + assert_eq!(typed.goose_mode, Some(GooseMode::Auto)); + assert_eq!( + typed.goose_search_paths, + Some(vec!["/usr/local/bin".to_string(), "/opt/bin".to_string()]) + ); + assert_eq!(typed.goose_context_limit, Some(128000)); // Roundtrip: apply to a fresh config and verify let config_file2 = tempfile::NamedTempFile::new().unwrap(); @@ -983,5 +1024,117 @@ mod tests { assert_eq!(typed2.goose_max_tokens, typed.goose_max_tokens); assert_eq!(typed2.goose_debug, typed.goose_debug); assert_eq!(typed2.goose_disable_keyring, typed.goose_disable_keyring); + assert_eq!(typed2.goose_mode, typed.goose_mode); + assert_eq!(typed2.goose_search_paths, typed.goose_search_paths); + assert_eq!(typed2.goose_context_limit, typed.goose_context_limit); + } + + #[test] + fn from_config_and_apply_cover_all_string_keys() { + let config_file = tempfile::NamedTempFile::new().unwrap(); + let secrets_file = tempfile::NamedTempFile::new().unwrap(); + let config = + Config::new_with_file_secrets(config_file.path(), secrets_file.path()).unwrap(); + + // Non-string keys need type-appropriate values; get_param fails if + // the stored value can't deserialize to the field's Rust type. + // Test string-typed keys exhaustively, and spot-check typed keys + // in the roundtrip test above. + let non_string_keys: std::collections::HashSet<&str> = [ + "GOOSE_MODE", + "GOOSE_MAX_TOKENS", + "GOOSE_CONTEXT_LIMIT", + "GOOSE_INPUT_LIMIT", + "GOOSE_MAX_TURNS", + "GOOSE_MAX_ACTIVE_AGENTS", + "GOOSE_AUTO_COMPACT_THRESHOLD", + "GOOSE_TOOL_PAIR_SUMMARIZATION", + "GOOSE_TOOL_CALL_CUTOFF", + "GOOSE_STREAM_TIMEOUT", + "GOOSE_SEARCH_PATHS", + "GOOSE_DISABLE_SESSION_NAMING", + "GOOSE_DISABLE_KEYRING", + "GOOSE_TELEMETRY_ENABLED", + "GOOSE_DEFAULT_EXTENSION_TIMEOUT", + "GOOSE_PROMPT_EDITOR_ALWAYS", + "GOOSE_DEBUG", + "GOOSE_SHOW_FULL_OUTPUT", + "GOOSE_DISABLE_TOOL_CALL_SUMMARY", + "GOOSE_LOCAL_ENABLE_THINKING", + "GOOSE_DATABRICKS_CLIENT_REQUEST_ID", + "RANDOM_THINKING_MESSAGES", + "GOOSE_SUBAGENT_MAX_TURNS", + "GOOSE_MAX_BACKGROUND_TASKS", + "GOOSE_RECIPE_RETRY_TIMEOUT_SECONDS", + "GOOSE_RECIPE_ON_FAILURE_TIMEOUT_SECONDS", + "GOOSE_CLI_MIN_PRIORITY", + "GOOSE_CLI_SHOW_COST", + "GOOSE_CLI_SHOW_THINKING", + "CLAUDE_THINKING_BUDGET", + "GEMINI25_THINKING_BUDGET", + "SECURITY_PROMPT_ENABLED", + "SECURITY_PROMPT_THRESHOLD", + "SECURITY_PROMPT_CLASSIFIER_ENABLED", + "SECURITY_COMMAND_CLASSIFIER_ENABLED", + "OPENAI_TIMEOUT", + "OLLAMA_TIMEOUT", + "OLLAMA_STREAM_TIMEOUT", + "OLLAMA_STREAM_USAGE", + "BEDROCK_MAX_RETRIES", + "BEDROCK_INITIAL_RETRY_INTERVAL_MS", + "BEDROCK_BACKOFF_MULTIPLIER", + "BEDROCK_MAX_RETRY_INTERVAL_MS", + "BEDROCK_ENABLE_CACHING", + "LITELLM_TIMEOUT", + "otel_exporter_otlp_timeout", + "tunnel_auto_start", + "CONTEXT_FILE_NAMES", + ] + .iter() + .copied() + .collect(); + + let string_keys: Vec<&&str> = GooseConfigSchema::ALL_KEYS + .iter() + .filter(|k| !non_string_keys.contains(**k)) + .collect(); + + let sentinel = serde_json::Value::String("__test_sentinel__".to_string()); + let updates: Vec<(String, serde_json::Value)> = string_keys + .iter() + .map(|k| (k.to_string(), sentinel.clone())) + .collect(); + config + .set_param_values(&updates) + .expect("set_param_values should succeed"); + + let typed = GooseConfigSchema::from_config(&config); + let json = serde_json::to_value(&typed).expect("serialize schema"); + let obj = json.as_object().expect("schema should be object"); + + for key in &string_keys { + assert!( + obj.get(**key).is_some_and(|v| !v.is_null()), + "from_config did not populate field for key '{}' — check the from_config() body", + key + ); + } + + let config_file2 = tempfile::NamedTempFile::new().unwrap(); + let secrets_file2 = tempfile::NamedTempFile::new().unwrap(); + let config2 = + Config::new_with_file_secrets(config_file2.path(), secrets_file2.path()).unwrap(); + typed + .apply_to_config(&config2) + .expect("apply_to_config should succeed"); + + for key in &string_keys { + let val: Result = config2.get_param(key); + assert!( + val.is_ok(), + "apply_to_config did not persist key '{}' — check the apply_to_config() body", + key + ); + } } } diff --git a/ui/desktop/openapi.json b/ui/desktop/openapi.json index 5496a1b054..258f9d9b29 100644 --- a/ui/desktop/openapi.json +++ b/ui/desktop/openapi.json @@ -1583,8 +1583,8 @@ "tags": [ "super::routes::config_management" ], - "summary": "Update configuration values. Fields set to null are left unchanged — to delete", - "description": "a key, use `POST /config/remove`. Secret fields (API keys) are stored in the\nsystem keyring, not the config file.", + "summary": "Update configuration values via sparse patch. Only send the fields you want", + "description": "to change — omitted and null fields are both left unchanged (serde cannot\ndistinguish the two). To delete a key, use `POST /config/remove`.\n\nNested objects (`extensions`, `slash_commands`, `experiments`) use whole-value\nreplacement, not deep merge. Secret fields (API keys) are stored in the system\nkeyring, not the config file.\n\n**Caution:** `GET /config/typed` returns values merged from env vars, system\nconfig, and user config. Sending the full GET response back as a PATCH payload\ncan persist inherited/env values into the user config file. Only send fields\nthe user explicitly changed.", "operationId": "patch_typed_config", "requestBody": { "content": { @@ -5575,7 +5575,7 @@ }, "GooseConfigSchema": { "type": "object", - "description": "JSON Schema representation of Goose's config.yaml.\n\nAll keys are optional. Unknown keys are allowed (additionalProperties: true)\nbecause Goose passes undocumented provider-specific keys through as\nenvironment variable overrides.", + "description": "JSON Schema representation of Goose's config.yaml.\n\nAll fields are optional. The standalone JSON Schema (`config.schema.json`)\nsets `additionalProperties: true` so config.yaml can carry undocumented\nprovider-specific keys as env-var overrides. However, the typed API\nendpoints only persist fields explicitly declared on this struct — unknown\nkeys in a `PATCH /config/typed` payload are silently dropped by serde.", "properties": { "ANTHROPIC_HOST": { "type": "string", @@ -5839,6 +5839,10 @@ "type": "boolean", "nullable": true }, + "GOOSE_DISABLE_TOOL_CALL_SUMMARY": { + "type": "boolean", + "nullable": true + }, "GOOSE_INPUT_LIMIT": { "type": "integer", "format": "int64", @@ -6167,6 +6171,14 @@ "type": "string", "nullable": true }, + "LITELLM_CUSTOM_HEADERS": { + "type": "string", + "nullable": true + }, + "NANOGPT_API_KEY": { + "type": "string", + "nullable": true + }, "OPENAI_API_KEY": { "type": "string", "nullable": true @@ -6194,7 +6206,7 @@ } } ], - "description": "Config update payload for `PATCH /config/typed`.\n\nEmbeds all non-secret fields from [`GooseConfigSchema`] via `#[serde(flatten)]`,\nplus provider API key fields that route to the system keyring.\nFields set to `null` are left unchanged — to delete a key, use `POST /config/remove`." + "description": "Config update payload for `PATCH /config/typed`.\n\nEmbeds all non-secret fields from [`GooseConfigSchema`] via `#[serde(flatten)]`,\nplus provider API key fields that route to the system keyring.\n\n**Sparse patch semantics:** Only send fields you want to change. Fields set to\n`null` (or omitted) are left unchanged — serde cannot distinguish the two cases.\nTo delete a key, use `POST /config/remove`. Nested objects (`extensions`,\n`slash_commands`, `experiments`) use whole-value replacement, not deep merge." }, "GooseMode": { "type": "string", diff --git a/ui/desktop/package.json b/ui/desktop/package.json index 25b83a1cc8..bee272e30b 100644 --- a/ui/desktop/package.json +++ b/ui/desktop/package.json @@ -114,7 +114,7 @@ "@electron/fuses": "^1.8.0", "@eslint/js": "^9.39.2", "@formatjs/cli": "^6.14.0", - "@hey-api/openapi-ts": "^0.93.0", + "@hey-api/openapi-ts": "0.93.1", "@modelcontextprotocol/sdk": "^1.27.0", "@playwright/test": "^1.58.2", "@tailwindcss/line-clamp": "^0.4.4", diff --git a/ui/desktop/src/api/sdk.gen.ts b/ui/desktop/src/api/sdk.gen.ts index ed9c38baca..c6a180512c 100644 --- a/ui/desktop/src/api/sdk.gen.ts +++ b/ui/desktop/src/api/sdk.gen.ts @@ -273,10 +273,19 @@ export const getSlashCommands = (options?: export const readTypedConfig = (options?: Options) => (options?.client ?? client).get({ url: '/config/typed', ...options }); /** - * Update configuration values. Fields set to null are left unchanged — to delete + * Update configuration values via sparse patch. Only send the fields you want * - * a key, use `POST /config/remove`. Secret fields (API keys) are stored in the - * system keyring, not the config file. + * to change — omitted and null fields are both left unchanged (serde cannot + * distinguish the two). To delete a key, use `POST /config/remove`. + * + * Nested objects (`extensions`, `slash_commands`, `experiments`) use whole-value + * replacement, not deep merge. Secret fields (API keys) are stored in the system + * keyring, not the config file. + * + * **Caution:** `GET /config/typed` returns values merged from env vars, system + * config, and user config. Sending the full GET response back as a PATCH payload + * can persist inherited/env values into the user config file. Only send fields + * the user explicitly changed. */ export const patchTypedConfig = (options: Options) => (options.client ?? client).patch({ url: '/config/typed', diff --git a/ui/desktop/src/api/types.gen.ts b/ui/desktop/src/api/types.gen.ts index 81dab2be76..b67039979d 100644 --- a/ui/desktop/src/api/types.gen.ts +++ b/ui/desktop/src/api/types.gen.ts @@ -532,9 +532,11 @@ export type GooseApp = McpAppResource & (WindowProps | null) & { /** * JSON Schema representation of Goose's config.yaml. * - * All keys are optional. Unknown keys are allowed (additionalProperties: true) - * because Goose passes undocumented provider-specific keys through as - * environment variable overrides. + * All fields are optional. The standalone JSON Schema (`config.schema.json`) + * sets `additionalProperties: true` so config.yaml can carry undocumented + * provider-specific keys as env-var overrides. However, the typed API + * endpoints only persist fields explicitly declared on this struct — unknown + * keys in a `PATCH /config/typed` payload are silently dropped by serde. */ export type GooseConfigSchema = { ANTHROPIC_HOST?: string | null; @@ -598,6 +600,7 @@ export type GooseConfigSchema = { GOOSE_DEFAULT_EXTENSION_TIMEOUT?: number | null; GOOSE_DISABLE_KEYRING?: boolean | null; GOOSE_DISABLE_SESSION_NAMING?: boolean | null; + GOOSE_DISABLE_TOOL_CALL_SUMMARY?: boolean | null; GOOSE_INPUT_LIMIT?: number | null; GOOSE_LOCAL_ENABLE_THINKING?: boolean | null; GOOSE_MAX_ACTIVE_AGENTS?: number | null; @@ -670,7 +673,11 @@ export type GooseConfigSchema = { * * Embeds all non-secret fields from [`GooseConfigSchema`] via `#[serde(flatten)]`, * plus provider API key fields that route to the system keyring. - * Fields set to `null` are left unchanged — to delete a key, use `POST /config/remove`. + * + * **Sparse patch semantics:** Only send fields you want to change. Fields set to + * `null` (or omitted) are left unchanged — serde cannot distinguish the two cases. + * To delete a key, use `POST /config/remove`. Nested objects (`extensions`, + * `slash_commands`, `experiments`) use whole-value replacement, not deep merge. */ export type GooseConfigUpdate = GooseConfigSchema & { ANTHROPIC_API_KEY?: string | null; @@ -680,6 +687,8 @@ export type GooseConfigUpdate = GooseConfigSchema & { GOOGLE_API_KEY?: string | null; GROQ_API_KEY?: string | null; LITELLM_API_KEY?: string | null; + LITELLM_CUSTOM_HEADERS?: string | null; + NANOGPT_API_KEY?: string | null; OPENAI_API_KEY?: string | null; OPENROUTER_API_KEY?: string | null; SNOWFLAKE_TOKEN?: string | null; diff --git a/ui/pnpm-lock.yaml b/ui/pnpm-lock.yaml index 609bbab6b4..09088ae1f0 100644 --- a/ui/pnpm-lock.yaml +++ b/ui/pnpm-lock.yaml @@ -221,7 +221,7 @@ importers: specifier: ^6.14.0 version: 6.14.0 '@hey-api/openapi-ts': - specifier: ^0.93.0 + specifier: 0.93.1 version: 0.93.1(magicast@0.5.2)(typescript@5.9.3) '@modelcontextprotocol/sdk': specifier: ^1.27.0 @@ -10306,7 +10306,7 @@ snapshots: '@mcp-ui/client@7.1.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@modelcontextprotocol/ext-apps': 1.2.2(@modelcontextprotocol/sdk@1.27.1(zod@3.25.76))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@3.25.76) - '@modelcontextprotocol/sdk': 1.27.1(zod@4.3.6) + '@modelcontextprotocol/sdk': 1.27.1(zod@3.25.76) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) zod: 3.25.76 @@ -10373,28 +10373,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@modelcontextprotocol/sdk@1.27.1(zod@4.3.6)': - dependencies: - '@hono/node-server': 1.19.11(hono@4.12.8) - ajv: 8.18.0 - ajv-formats: 3.0.1(ajv@8.18.0) - content-type: 1.0.5 - cors: 2.8.6 - cross-spawn: 7.0.6 - eventsource: 3.0.7 - eventsource-parser: 3.0.6 - express: 5.2.1 - express-rate-limit: 8.3.1(express@5.2.1) - hono: 4.12.8 - jose: 6.2.2 - json-schema-typed: 8.0.2 - pkce-challenge: 5.0.1 - raw-body: 3.0.2 - zod: 4.3.6 - zod-to-json-schema: 3.25.1(zod@3.25.76) - transitivePeerDependencies: - - supports-color - '@napi-rs/wasm-runtime@1.1.1': dependencies: '@emnapi/core': 1.9.1