Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
46 changes: 46 additions & 0 deletions crates/goose-sdk-types/src/custom_requests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,52 @@ pub struct PreferencesRemoveRequest {
pub keys: Vec<PreferenceKey>,
}

#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcRequest)]
#[request(method = "_goose/unstable/config/read", response = ConfigReadResponse)]
#[serde(rename_all = "camelCase")]
pub struct ConfigReadRequest {
pub key: String,
#[serde(default)]
pub is_secret: bool,
}

#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcResponse)]
#[serde(rename_all = "camelCase")]
pub struct ConfigReadResponse {
#[serde(default)]
pub value: serde_json::Value,
}

#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcRequest)]
#[request(method = "_goose/unstable/config/upsert", response = EmptyResponse)]
#[serde(rename_all = "camelCase")]
pub struct ConfigUpsertRequest {
pub key: String,
pub value: serde_json::Value,
#[serde(default)]
pub is_secret: bool,
}

#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcRequest)]
#[request(method = "_goose/unstable/config/remove", response = EmptyResponse)]
#[serde(rename_all = "camelCase")]
pub struct ConfigRemoveRequest {
pub key: String,
#[serde(default)]
pub is_secret: bool,
}

#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcRequest)]
#[request(method = "_goose/unstable/config/read-all", response = ConfigReadAllResponse)]
#[serde(rename_all = "camelCase")]
pub struct ConfigReadAllRequest {}

#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcResponse)]
#[serde(rename_all = "camelCase")]
pub struct ConfigReadAllResponse {
pub config: std::collections::HashMap<String, serde_json::Value>,
}

#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub enum PreferenceKey {
Expand Down
20 changes: 20 additions & 0 deletions crates/goose/acp-meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,26 @@
"requestType": "PreferencesRemoveRequest_unstable",
"responseType": "EmptyResponse"
},
{
"method": "_goose/unstable/config/read",
"requestType": "ConfigReadRequest_unstable",
"responseType": "ConfigReadResponse_unstable"
},
{
"method": "_goose/unstable/config/upsert",
"requestType": "ConfigUpsertRequest_unstable",
"responseType": "EmptyResponse"
},
{
"method": "_goose/unstable/config/remove",
"requestType": "ConfigRemoveRequest_unstable",
"responseType": "EmptyResponse"
},
{
"method": "_goose/unstable/config/read-all",
"requestType": "ConfigReadAllRequest_unstable",
"responseType": "ConfigReadAllResponse_unstable"
},
{
"method": "_goose/unstable/defaults/read",
"requestType": "DefaultsReadRequest_unstable",
Expand Down
134 changes: 134 additions & 0 deletions crates/goose/acp-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -2961,6 +2961,88 @@
"x-side": "agent",
"x-method": "_goose/unstable/preferences/remove"
},
"ConfigReadRequest_unstable": {
"type": "object",
"properties": {
"key": {
"type": "string"
},
"isSecret": {
"type": "boolean",
"default": false
}
},
"required": [
"key"
],
"x-side": "agent",
"x-method": "_goose/unstable/config/read"
},
"ConfigReadResponse_unstable": {
"type": "object",
"properties": {
"value": {
"default": null
}
},
"x-side": "agent",
"x-method": "_goose/unstable/config/read"
},
"ConfigUpsertRequest_unstable": {
"type": "object",
"properties": {
"key": {
"type": "string"
},
"value": {},
"isSecret": {
"type": "boolean",
"default": false
}
},
"required": [
"key",
"value"
],
"x-side": "agent",
"x-method": "_goose/unstable/config/upsert"
},
"ConfigRemoveRequest_unstable": {
"type": "object",
"properties": {
"key": {
"type": "string"
},
"isSecret": {
"type": "boolean",
"default": false
}
},
"required": [
"key"
],
"x-side": "agent",
"x-method": "_goose/unstable/config/remove"
},
"ConfigReadAllRequest_unstable": {
"type": "object",
"x-side": "agent",
"x-method": "_goose/unstable/config/read-all"
},
"ConfigReadAllResponse_unstable": {
"type": "object",
"properties": {
"config": {
"type": "object",
"additionalProperties": {}
}
},
"required": [
"config"
],
"x-side": "agent",
"x-method": "_goose/unstable/config/read-all"
},
"DefaultsReadRequest_unstable": {
"type": "object",
"description": "Read Goose default provider and model configuration.",
Expand Down Expand Up @@ -6043,6 +6125,42 @@
"description": "Params for _goose/unstable/preferences/remove",
"title": "PreferencesRemoveRequest_unstable"
},
{
"allOf": [
{
"$ref": "#/$defs/ConfigReadRequest_unstable"
}
],
"description": "Params for _goose/unstable/config/read",
"title": "ConfigReadRequest_unstable"
},
{
"allOf": [
{
"$ref": "#/$defs/ConfigUpsertRequest_unstable"
}
],
"description": "Params for _goose/unstable/config/upsert",
"title": "ConfigUpsertRequest_unstable"
},
{
"allOf": [
{
"$ref": "#/$defs/ConfigRemoveRequest_unstable"
}
],
"description": "Params for _goose/unstable/config/remove",
"title": "ConfigRemoveRequest_unstable"
},
{
"allOf": [
{
"$ref": "#/$defs/ConfigReadAllRequest_unstable"
}
],
"description": "Params for _goose/unstable/config/read-all",
"title": "ConfigReadAllRequest_unstable"
},
{
"allOf": [
{
Expand Down Expand Up @@ -6797,6 +6915,22 @@
],
"title": "PreferencesReadResponse_unstable"
},
{
"allOf": [
{
"$ref": "#/$defs/ConfigReadResponse_unstable"
}
],
"title": "ConfigReadResponse_unstable"
},
{
"allOf": [
{
"$ref": "#/$defs/ConfigReadAllResponse_unstable"
}
],
"title": "ConfigReadAllResponse_unstable"
},
{
"allOf": [
{
Expand Down
111 changes: 111 additions & 0 deletions crates/goose/src/acp/server/config.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,22 @@
use super::*;
use goose_providers::thinking::ThinkingEffort;

const SECRET_MASK_SHOW_LEN: usize = 8;

fn mask_secret(secret: serde_json::Value) -> String {
let as_string = match secret {
serde_json::Value::String(s) => s,
_ => serde_json::to_string(&secret).unwrap_or_else(|_| secret.to_string()),
};

let chars: Vec<_> = as_string.chars().collect();
let show_len = std::cmp::min(chars.len() / 2, SECRET_MASK_SHOW_LEN);
let visible: String = chars.iter().take(show_len).collect();
let mask = "*".repeat(chars.len() - show_len);

format!("{}{}", visible, mask)
}

impl GooseAcpAgent {
pub(super) async fn on_preferences_read(
&self,
Expand Down Expand Up @@ -58,6 +74,101 @@ impl GooseAcpAgent {
Ok(EmptyResponse {})
}

pub(super) async fn on_config_read(
&self,
req: ConfigReadRequest,
) -> Result<ConfigReadResponse, agent_client_protocol::Error> {
let config = self.config()?;

if req.key == "GOOSE_PROVIDER" || req.key == "active_provider" {
let value = config
.get_goose_provider()
.map(serde_json::Value::String)
.unwrap_or(serde_json::Value::Null);
return Ok(ConfigReadResponse { value });
}
if req.key == "GOOSE_MODEL" {
let value = config
.get_goose_model()
.map(serde_json::Value::String)
.unwrap_or(serde_json::Value::Null);
return Ok(ConfigReadResponse { value });
}

let value = match config.get(&req.key, req.is_secret) {
Ok(value) if req.is_secret => serde_json::Value::String(mask_secret(value)),
Ok(value) => value,
Err(crate::config::ConfigError::NotFound(_)) => serde_json::Value::Null,
Err(e) => {
return Err(agent_client_protocol::Error::internal_error().data(e.to_string()))
}
};
Ok(ConfigReadResponse { value })
}

pub(super) async fn on_config_upsert(
&self,
req: ConfigUpsertRequest,
) -> Result<EmptyResponse, agent_client_protocol::Error> {
let config = self.config()?;

if req.key == "GOOSE_PROVIDER" {
if let Some(name) = req.value.as_str() {
let model = crate::config::get_provider_entry(config, name)
.map(|e| e.model)
.or_else(|| config.get_goose_model().ok())
.unwrap_or_default();
crate::config::set_active_provider(config, name, &model).internal_err()?;
return Ok(EmptyResponse {});
}
}
if req.key == "GOOSE_MODEL" {
if let Some(model) = req.value.as_str() {
if let Ok(provider) = config.get_goose_provider() {
crate::config::set_active_provider(config, &provider, model).internal_err()?;
return Ok(EmptyResponse {});
}
}
}

config
.set(&req.key, &req.value, req.is_secret)
.internal_err()?;
Ok(EmptyResponse {})
}

pub(super) async fn on_config_remove(
&self,
req: ConfigRemoveRequest,
) -> Result<EmptyResponse, agent_client_protocol::Error> {
let config = self.config()?;

if req.is_secret {
config.delete_secret(&req.key).internal_err()?;
} else if req.key == "GOOSE_PROVIDER" || req.key == "active_provider" {
config.delete("active_provider").internal_err()?;
config.delete("GOOSE_PROVIDER").internal_err()?;
} else if req.key == "GOOSE_MODEL" {
if let Ok(provider) = config.get_goose_provider() {
crate::config::set_active_provider(config, &provider, "").internal_err()?;
}
config.delete("GOOSE_MODEL").internal_err()?;
} else {
config.delete(&req.key).internal_err()?;
}

Ok(EmptyResponse {})
}

pub(super) async fn on_config_read_all(
&self,
_req: ConfigReadAllRequest,
) -> Result<ConfigReadAllResponse, agent_client_protocol::Error> {
let config = self.config()?;
let values = config.all_values().internal_err()?;
Ok(ConfigReadAllResponse { config: values })
}

pub(super) async fn on_defaults_read(
&self,
_req: DefaultsReadRequest,
Expand Down
32 changes: 32 additions & 0 deletions crates/goose/src/acp/server/custom_dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,38 @@ impl GooseAcpAgent {
self.on_preferences_remove(req).await
}

#[custom_method(ConfigReadRequest)]
async fn dispatch_config_read(
&self,
req: ConfigReadRequest,
) -> Result<ConfigReadResponse, agent_client_protocol::Error> {
self.on_config_read(req).await
}

#[custom_method(ConfigUpsertRequest)]
async fn dispatch_config_upsert(
&self,
req: ConfigUpsertRequest,
) -> Result<EmptyResponse, agent_client_protocol::Error> {
self.on_config_upsert(req).await
}

#[custom_method(ConfigRemoveRequest)]
async fn dispatch_config_remove(
&self,
req: ConfigRemoveRequest,
) -> Result<EmptyResponse, agent_client_protocol::Error> {
self.on_config_remove(req).await
}

#[custom_method(ConfigReadAllRequest)]
async fn dispatch_config_read_all(
&self,
req: ConfigReadAllRequest,
) -> Result<ConfigReadAllResponse, agent_client_protocol::Error> {
self.on_config_read_all(req).await
}

#[custom_method(DefaultsReadRequest)]
async fn dispatch_defaults_read(
&self,
Expand Down
Loading
Loading