Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 22 additions & 0 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -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!"

Expand Down Expand Up @@ -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
Expand Down
6 changes: 5 additions & 1 deletion crates/goose-cli/src/commands/configure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1489,7 +1489,11 @@ pub fn configure_keyring_dialog() -> anyhow::Result<()> {
);
}

let currently_disabled = config.get_param::<String>("GOOSE_DISABLE_KEYRING").is_ok();
let currently_disabled = config
.get_param::<serde_yaml::Value>("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)"
Expand Down
7 changes: 7 additions & 0 deletions crates/goose-server/src/openapi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use goose::agents::extension::ToolInfo;
use goose::agents::ExtensionConfig;
use goose::config::permission::PermissionLevel;
use goose::config::ExtensionEntry;
use goose::config::ProviderEntry;
use goose::conversation::Conversation;
use goose::download_manager::{DownloadProgress, DownloadStatus};
use goose::model::ModelConfig;
Expand Down Expand Up @@ -391,6 +392,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,
Expand Down Expand Up @@ -484,6 +487,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,
Expand Down Expand Up @@ -559,6 +565,7 @@ derive_utoipa!(IconTheme as IconThemeSchema);
DeclarativeProviderConfig,
EnvVarConfig,
ExtensionEntry,
ProviderEntry,
ExtensionConfig,
ConfigKey,
Envs,
Expand Down
49 changes: 49 additions & 0 deletions crates/goose-server/src/routes/config_management.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -915,9 +916,57 @@ 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<Json<GooseConfigSchema>, ErrorResponse> {
let config = Config::global();
let typed = GooseConfigSchema::from_config(config);
Ok(Json(typed))
}

/// 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",
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<GooseConfigUpdate>,
) -> Result<Json<GooseConfigSchema>, ErrorResponse> {
let config = Config::global();
update.apply_to_config(config)?;
let typed = GooseConfigSchema::from_config(config);
Ok(Json(typed))
}

pub fn routes(state: Arc<AppState>) -> 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))
Expand Down
4 changes: 4 additions & 0 deletions crates/goose/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down
Loading
Loading