feat: endpoints for config management via goose-server (#1207)
Co-authored-by: Lily Delalande <ldelalande@squareup.com>
This commit is contained in:
@@ -12,7 +12,7 @@ goose = { path = "../goose" }
|
|||||||
mcp-core = { path = "../mcp-core" }
|
mcp-core = { path = "../mcp-core" }
|
||||||
goose-mcp = { path = "../goose-mcp" }
|
goose-mcp = { path = "../goose-mcp" }
|
||||||
mcp-server = { path = "../mcp-server" }
|
mcp-server = { path = "../mcp-server" }
|
||||||
axum = { version = "0.7", features = ["ws"] }
|
axum = { version = "0.7.2", features = ["ws", "macros"] }
|
||||||
tokio = { version = "1.0", features = ["full"] }
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
tower-http = { version = "0.5", features = ["cors"] }
|
tower-http = { version = "0.5", features = ["cors"] }
|
||||||
@@ -31,11 +31,19 @@ thiserror = "1.0"
|
|||||||
clap = { version = "4.4", features = ["derive"] }
|
clap = { version = "4.4", features = ["derive"] }
|
||||||
once_cell = "1.20.2"
|
once_cell = "1.20.2"
|
||||||
etcetera = "0.8.0"
|
etcetera = "0.8.0"
|
||||||
|
serde_yaml = "0.9.34"
|
||||||
|
axum-extra = "0.10.0"
|
||||||
|
utoipa = { version = "4.1", features = ["axum_extras"] }
|
||||||
|
dirs = "6.0.0"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "goosed"
|
name = "goosed"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "generate_schema"
|
||||||
|
path = "src/bin/generate_schema.rs"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tower = "0.5"
|
tower = "0.5"
|
||||||
async-trait = "0.1"
|
async-trait = "0.1"
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
// We'll generate the schema at runtime since we need access to the complete application context
|
||||||
|
fn main() {
|
||||||
|
println!("cargo:rerun-if-changed=src/");
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
use goose_server::openapi;
|
||||||
|
use std::env;
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let schema = openapi::generate_schema();
|
||||||
|
|
||||||
|
// Get the current working directory
|
||||||
|
let current_dir = env::current_dir().unwrap();
|
||||||
|
let output_path = current_dir.join("ui").join("desktop").join("openapi.json");
|
||||||
|
|
||||||
|
// Ensure parent directory exists
|
||||||
|
if let Some(parent) = output_path.parent() {
|
||||||
|
fs::create_dir_all(parent).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::write(&output_path, schema).unwrap();
|
||||||
|
println!(
|
||||||
|
"Successfully generated OpenAPI schema at {}",
|
||||||
|
output_path.display()
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
pub mod openapi;
|
||||||
|
pub mod routes;
|
||||||
|
pub mod state;
|
||||||
|
|
||||||
|
// Re-export commonly used items
|
||||||
|
pub use openapi::*;
|
||||||
|
pub use state::*;
|
||||||
@@ -11,6 +11,7 @@ mod commands;
|
|||||||
mod configuration;
|
mod configuration;
|
||||||
mod error;
|
mod error;
|
||||||
mod logging;
|
mod logging;
|
||||||
|
mod openapi;
|
||||||
mod routes;
|
mod routes;
|
||||||
mod state;
|
mod state;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
use utoipa::OpenApi;
|
||||||
|
|
||||||
|
#[allow(dead_code)] // Used by utoipa for OpenAPI generation
|
||||||
|
#[derive(OpenApi)]
|
||||||
|
#[openapi(
|
||||||
|
paths(
|
||||||
|
super::routes::config_management::upsert_config,
|
||||||
|
super::routes::config_management::remove_config,
|
||||||
|
super::routes::config_management::read_config,
|
||||||
|
super::routes::config_management::add_extension,
|
||||||
|
super::routes::config_management::remove_extension,
|
||||||
|
super::routes::config_management::read_all_config
|
||||||
|
),
|
||||||
|
components(schemas(
|
||||||
|
super::routes::config_management::UpsertConfigQuery,
|
||||||
|
super::routes::config_management::ConfigKeyQuery,
|
||||||
|
super::routes::config_management::ExtensionQuery,
|
||||||
|
super::routes::config_management::ConfigResponse
|
||||||
|
))
|
||||||
|
)]
|
||||||
|
pub struct ApiDoc;
|
||||||
|
|
||||||
|
#[allow(dead_code)] // Used by generate_schema binary
|
||||||
|
pub fn generate_schema() -> String {
|
||||||
|
let api_doc = ApiDoc::openapi();
|
||||||
|
serde_json::to_string_pretty(&api_doc).unwrap()
|
||||||
|
}
|
||||||
@@ -95,6 +95,7 @@ async fn extend_prompt(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[axum::debug_handler]
|
||||||
async fn create_agent(
|
async fn create_agent(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
|
|||||||
@@ -0,0 +1,206 @@
|
|||||||
|
use axum::{
|
||||||
|
extract::State,
|
||||||
|
routing::{delete, get, post},
|
||||||
|
Json, Router,
|
||||||
|
};
|
||||||
|
use goose::config::Config;
|
||||||
|
use http::StatusCode;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_json::Value;
|
||||||
|
use std::{collections::HashMap, sync::Arc};
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
use utoipa::ToSchema;
|
||||||
|
|
||||||
|
use crate::state::AppState;
|
||||||
|
|
||||||
|
#[derive(Deserialize, ToSchema)]
|
||||||
|
pub struct UpsertConfigQuery {
|
||||||
|
pub key: String,
|
||||||
|
pub value: Value,
|
||||||
|
pub is_secret: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, ToSchema)]
|
||||||
|
pub struct ConfigKeyQuery {
|
||||||
|
pub key: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, ToSchema)]
|
||||||
|
pub struct ExtensionQuery {
|
||||||
|
pub name: String,
|
||||||
|
pub config: Value,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, ToSchema)]
|
||||||
|
pub struct ConfigResponse {
|
||||||
|
pub config: HashMap<String, Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
post,
|
||||||
|
path = "/config/upsert",
|
||||||
|
request_body = UpsertConfigQuery,
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "Configuration value upserted successfully", body = String),
|
||||||
|
(status = 500, description = "Internal server error")
|
||||||
|
)
|
||||||
|
)]
|
||||||
|
pub async fn upsert_config(
|
||||||
|
State(_state): State<Arc<Mutex<HashMap<String, Value>>>>,
|
||||||
|
Json(query): Json<UpsertConfigQuery>,
|
||||||
|
) -> Result<Json<Value>, StatusCode> {
|
||||||
|
let config = Config::global();
|
||||||
|
|
||||||
|
let result = if query.is_secret.unwrap_or(false) {
|
||||||
|
config.set_secret(&query.key, query.value)
|
||||||
|
} else {
|
||||||
|
config.set(&query.key, query.value)
|
||||||
|
};
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(_) => Ok(Json(Value::String(format!("Upserted key {}", query.key)))),
|
||||||
|
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
post,
|
||||||
|
path = "/config/remove",
|
||||||
|
request_body = ConfigKeyQuery,
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "Configuration value removed successfully", body = String),
|
||||||
|
(status = 404, description = "Configuration key not found"),
|
||||||
|
(status = 500, description = "Internal server error")
|
||||||
|
)
|
||||||
|
)]
|
||||||
|
pub async fn remove_config(
|
||||||
|
State(_state): State<Arc<Mutex<HashMap<String, Value>>>>,
|
||||||
|
Json(query): Json<ConfigKeyQuery>,
|
||||||
|
) -> Result<Json<String>, StatusCode> {
|
||||||
|
let config = Config::global();
|
||||||
|
|
||||||
|
match config.delete(&query.key) {
|
||||||
|
Ok(_) => Ok(Json(format!("Removed key {}", query.key))),
|
||||||
|
Err(_) => Err(StatusCode::NOT_FOUND),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
get,
|
||||||
|
path = "/config/read",
|
||||||
|
request_body = ConfigKeyQuery,
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "Configuration value retrieved successfully", body = Value),
|
||||||
|
(status = 404, description = "Configuration key not found")
|
||||||
|
)
|
||||||
|
)]
|
||||||
|
pub async fn read_config(
|
||||||
|
State(_state): State<Arc<Mutex<HashMap<String, Value>>>>,
|
||||||
|
Json(query): Json<ConfigKeyQuery>,
|
||||||
|
) -> Result<Json<Value>, StatusCode> {
|
||||||
|
let config = Config::global();
|
||||||
|
|
||||||
|
match config.get::<Value>(&query.key) {
|
||||||
|
Ok(value) => Ok(Json(value)),
|
||||||
|
Err(_) => Err(StatusCode::NOT_FOUND),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
post,
|
||||||
|
path = "/config/extension",
|
||||||
|
request_body = ExtensionQuery,
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "Extension added successfully", body = String),
|
||||||
|
(status = 400, description = "Invalid request"),
|
||||||
|
(status = 500, description = "Internal server error")
|
||||||
|
)
|
||||||
|
)]
|
||||||
|
pub async fn add_extension(
|
||||||
|
State(_state): State<Arc<Mutex<HashMap<String, Value>>>>,
|
||||||
|
Json(extension): Json<ExtensionQuery>,
|
||||||
|
) -> Result<Json<String>, StatusCode> {
|
||||||
|
let config = Config::global();
|
||||||
|
|
||||||
|
// Get current extensions or initialize empty map
|
||||||
|
let mut extensions: HashMap<String, Value> =
|
||||||
|
config.get("extensions").unwrap_or_else(|_| HashMap::new());
|
||||||
|
|
||||||
|
// Add new extension
|
||||||
|
extensions.insert(extension.name.clone(), extension.config);
|
||||||
|
|
||||||
|
// Save updated extensions
|
||||||
|
match config.set(
|
||||||
|
"extensions",
|
||||||
|
Value::Object(extensions.into_iter().collect()),
|
||||||
|
) {
|
||||||
|
Ok(_) => Ok(Json(format!("Added extension {}", extension.name))),
|
||||||
|
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
delete,
|
||||||
|
path = "/config/extension",
|
||||||
|
request_body = ConfigKeyQuery,
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "Extension removed successfully", body = String),
|
||||||
|
(status = 404, description = "Extension not found"),
|
||||||
|
(status = 500, description = "Internal server error")
|
||||||
|
)
|
||||||
|
)]
|
||||||
|
pub async fn remove_extension(
|
||||||
|
State(_state): State<Arc<Mutex<HashMap<String, Value>>>>,
|
||||||
|
Json(query): Json<ConfigKeyQuery>,
|
||||||
|
) -> Result<Json<String>, StatusCode> {
|
||||||
|
let config = Config::global();
|
||||||
|
|
||||||
|
// Get current extensions
|
||||||
|
let mut extensions: HashMap<String, Value> = match config.get("extensions") {
|
||||||
|
Ok(exts) => exts,
|
||||||
|
Err(_) => return Err(StatusCode::NOT_FOUND),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Remove extension if it exists
|
||||||
|
if extensions.remove(&query.key).is_some() {
|
||||||
|
// Save updated extensions
|
||||||
|
match config.set(
|
||||||
|
"extensions",
|
||||||
|
Value::Object(extensions.into_iter().collect()),
|
||||||
|
) {
|
||||||
|
Ok(_) => Ok(Json(format!("Removed extension {}", query.key))),
|
||||||
|
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(StatusCode::NOT_FOUND)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
get,
|
||||||
|
path = "/config",
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "All configuration values retrieved successfully", body = ConfigResponse)
|
||||||
|
)
|
||||||
|
)]
|
||||||
|
pub async fn read_all_config(
|
||||||
|
State(_state): State<Arc<Mutex<HashMap<String, Value>>>>,
|
||||||
|
) -> Result<Json<ConfigResponse>, StatusCode> {
|
||||||
|
let config = Config::global();
|
||||||
|
|
||||||
|
// Load values from config file
|
||||||
|
let values = config.load_values().unwrap_or_default();
|
||||||
|
|
||||||
|
Ok(Json(ConfigResponse { config: values }))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn routes(state: AppState) -> Router {
|
||||||
|
Router::new()
|
||||||
|
.route("/config", get(read_all_config))
|
||||||
|
.route("/config/upsert", post(upsert_config))
|
||||||
|
.route("/config/remove", post(remove_config))
|
||||||
|
.route("/config/read", post(read_config))
|
||||||
|
.route("/config/extension", post(add_extension))
|
||||||
|
.route("/config/extension", delete(remove_extension))
|
||||||
|
.with_state(state.config)
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
// Export route modules
|
// Export route modules
|
||||||
pub mod agent;
|
pub mod agent;
|
||||||
|
pub mod config_management;
|
||||||
pub mod configs;
|
pub mod configs;
|
||||||
pub mod extension;
|
pub mod extension;
|
||||||
pub mod health;
|
pub mod health;
|
||||||
@@ -14,5 +15,6 @@ pub fn configure(state: crate::state::AppState) -> Router {
|
|||||||
.merge(reply::routes(state.clone()))
|
.merge(reply::routes(state.clone()))
|
||||||
.merge(agent::routes(state.clone()))
|
.merge(agent::routes(state.clone()))
|
||||||
.merge(extension::routes(state.clone()))
|
.merge(extension::routes(state.clone()))
|
||||||
.merge(configs::routes(state))
|
.merge(configs::routes(state.clone()))
|
||||||
|
.merge(config_management::routes(state))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -559,6 +559,7 @@ mod tests {
|
|||||||
mod integration_tests {
|
mod integration_tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use axum::{body::Body, http::Request};
|
use axum::{body::Body, http::Request};
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use tower::ServiceExt;
|
use tower::ServiceExt;
|
||||||
@@ -573,6 +574,7 @@ mod tests {
|
|||||||
});
|
});
|
||||||
let agent = AgentFactory::create("reference", mock_provider).unwrap();
|
let agent = AgentFactory::create("reference", mock_provider).unwrap();
|
||||||
let state = AppState {
|
let state = AppState {
|
||||||
|
config: Arc::new(Mutex::new(HashMap::new())), // Add this line
|
||||||
agent: Arc::new(Mutex::new(Some(agent))),
|
agent: Arc::new(Mutex::new(Some(agent))),
|
||||||
secret_key: "test-secret".to_string(),
|
secret_key: "test-secret".to_string(),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use goose::agents::Agent;
|
use goose::agents::Agent;
|
||||||
|
use serde_json::Value;
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
@@ -9,6 +11,7 @@ use tokio::sync::Mutex;
|
|||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
pub agent: Arc<Mutex<Option<Box<dyn Agent>>>>,
|
pub agent: Arc<Mutex<Option<Box<dyn Agent>>>>,
|
||||||
pub secret_key: String,
|
pub secret_key: String,
|
||||||
|
pub config: Arc<Mutex<HashMap<String, Value>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppState {
|
impl AppState {
|
||||||
@@ -16,6 +19,7 @@ impl AppState {
|
|||||||
Ok(Self {
|
Ok(Self {
|
||||||
agent: Arc::new(Mutex::new(None)),
|
agent: Arc::new(Mutex::new(None)),
|
||||||
secret_key,
|
secret_key,
|
||||||
|
config: Arc::new(Mutex::new(HashMap::new())),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,279 @@
|
|||||||
|
{
|
||||||
|
"openapi": "3.0.3",
|
||||||
|
"info": {
|
||||||
|
"title": "goose-server",
|
||||||
|
"description": "An AI agent",
|
||||||
|
"contact": {
|
||||||
|
"name": "Block",
|
||||||
|
"email": "ai-oss-tools@block.xyz"
|
||||||
|
},
|
||||||
|
"license": {
|
||||||
|
"name": "Apache-2.0"
|
||||||
|
},
|
||||||
|
"version": "1.0.4"
|
||||||
|
},
|
||||||
|
"paths": {
|
||||||
|
"/config": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"super::routes::config_management"
|
||||||
|
],
|
||||||
|
"summary": "Read all configuration values",
|
||||||
|
"operationId": "read_all_config",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "All configuration values retrieved successfully",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ConfigResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/config/extension": {
|
||||||
|
"post": {
|
||||||
|
"tags": [
|
||||||
|
"super::routes::config_management"
|
||||||
|
],
|
||||||
|
"summary": "Add an extension configuration",
|
||||||
|
"operationId": "add_extension",
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ExtensionQuery"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Extension added successfully",
|
||||||
|
"content": {
|
||||||
|
"text/plain": {
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Invalid request"
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal server error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"tags": [
|
||||||
|
"super::routes::config_management"
|
||||||
|
],
|
||||||
|
"summary": "Remove an extension configuration",
|
||||||
|
"operationId": "remove_extension",
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ConfigKeyQuery"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Extension removed successfully",
|
||||||
|
"content": {
|
||||||
|
"text/plain": {
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Extension not found"
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal server error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/config/read": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"super::routes::config_management"
|
||||||
|
],
|
||||||
|
"summary": "Read a configuration value",
|
||||||
|
"operationId": "read_config",
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ConfigKeyQuery"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Configuration value retrieved successfully",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Configuration key not found"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/config/remove": {
|
||||||
|
"post": {
|
||||||
|
"tags": [
|
||||||
|
"super::routes::config_management"
|
||||||
|
],
|
||||||
|
"summary": "Remove a configuration value",
|
||||||
|
"operationId": "remove_config",
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ConfigKeyQuery"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Configuration value removed successfully",
|
||||||
|
"content": {
|
||||||
|
"text/plain": {
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Configuration key not found"
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal server error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/config/upsert": {
|
||||||
|
"post": {
|
||||||
|
"tags": [
|
||||||
|
"super::routes::config_management"
|
||||||
|
],
|
||||||
|
"summary": "Upsert a configuration value",
|
||||||
|
"operationId": "upsert_config",
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/UpsertConfigQuery"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Configuration value upserted successfully",
|
||||||
|
"content": {
|
||||||
|
"text/plain": {
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal server error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"ConfigKeyQuery": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"key"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"key": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The configuration key to operate on"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ConfigResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"config"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"config": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "The configuration values",
|
||||||
|
"additionalProperties": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ExtensionQuery": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"name",
|
||||||
|
"config"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"config": {
|
||||||
|
"description": "The configuration for the extension"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The name of the extension"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"UpsertConfigQuery": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"key",
|
||||||
|
"value"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"is_secret": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Whether this configuration value should be treated as a secret",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"key": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The configuration key to upsert"
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"description": "The value to set for the configuration"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -159,7 +159,7 @@ impl Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Load current values from the config file
|
// Load current values from the config file
|
||||||
fn load_values(&self) -> Result<HashMap<String, Value>, ConfigError> {
|
pub fn load_values(&self) -> Result<HashMap<String, Value>, ConfigError> {
|
||||||
if self.config_path.exists() {
|
if self.config_path.exists() {
|
||||||
let file_content = std::fs::read_to_string(&self.config_path)?;
|
let file_content = std::fs::read_to_string(&self.config_path)?;
|
||||||
// Parse YAML into JSON Value for consistent internal representation
|
// Parse YAML into JSON Value for consistent internal representation
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "goose-app",
|
"name": "goose-app",
|
||||||
"version": "1.0.5",
|
"version": "1.0.51",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "goose-app",
|
"name": "goose-app",
|
||||||
"version": "1.0.5",
|
"version": "1.0.51",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/openai": "^0.0.72",
|
"@ai-sdk/openai": "^0.0.72",
|
||||||
|
|||||||
Reference in New Issue
Block a user