Skip to content

Commit ccaf7ca

Browse files
Feat: Added custom headers and toggle keyring CLI options (#5017)
Signed-off-by: Blair Allan <[email protected]> Co-authored-by: dianed-square <[email protected]>
1 parent c3c71a2 commit ccaf7ca

File tree

5 files changed

+129
-3
lines changed

5 files changed

+129
-3
lines changed

crates/goose-cli/src/commands/configure.rs

Lines changed: 115 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1174,6 +1174,11 @@ pub async fn configure_settings_dialog() -> anyhow::Result<()> {
11741174
"Max Turns",
11751175
"Set maximum number of turns without user input",
11761176
)
1177+
.item(
1178+
"keyring",
1179+
"Secret Storage",
1180+
"Configure how secrets are stored (keyring vs file)",
1181+
)
11771182
.item(
11781183
"experiment",
11791184
"Toggle Experiment",
@@ -1206,6 +1211,9 @@ pub async fn configure_settings_dialog() -> anyhow::Result<()> {
12061211
"max_turns" => {
12071212
configure_max_turns_dialog()?;
12081213
}
1214+
"keyring" => {
1215+
configure_keyring_dialog()?;
1216+
}
12091217
"experiment" => {
12101218
toggle_experiments_dialog()?;
12111219
}
@@ -1225,7 +1233,6 @@ pub async fn configure_settings_dialog() -> anyhow::Result<()> {
12251233
pub fn configure_goose_mode_dialog() -> anyhow::Result<()> {
12261234
let config = Config::global();
12271235

1228-
// Check if GOOSE_MODE is set as an environment variable
12291236
if std::env::var("GOOSE_MODE").is_ok() {
12301237
let _ = cliclack::log::info("Notice: GOOSE_MODE environment variable is set and will override the configuration here.");
12311238
}
@@ -1293,7 +1300,7 @@ pub fn configure_goose_router_strategy_dialog() -> anyhow::Result<()> {
12931300

12941301
pub fn configure_tool_output_dialog() -> anyhow::Result<()> {
12951302
let config = Config::global();
1296-
// Check if GOOSE_CLI_MIN_PRIORITY is set as an environment variable
1303+
12971304
if std::env::var("GOOSE_CLI_MIN_PRIORITY").is_ok() {
12981305
let _ = cliclack::log::info("Notice: GOOSE_CLI_MIN_PRIORITY environment variable is set and will override the configuration here.");
12991306
}
@@ -1322,6 +1329,60 @@ pub fn configure_tool_output_dialog() -> anyhow::Result<()> {
13221329
Ok(())
13231330
}
13241331

1332+
pub fn configure_keyring_dialog() -> anyhow::Result<()> {
1333+
let config = Config::global();
1334+
1335+
if std::env::var("GOOSE_DISABLE_KEYRING").is_ok() {
1336+
let _ = cliclack::log::info("Notice: GOOSE_DISABLE_KEYRING environment variable is set and will override the configuration here.");
1337+
}
1338+
1339+
let currently_disabled = config.get_param::<String>("GOOSE_DISABLE_KEYRING").is_ok();
1340+
1341+
let current_status = if currently_disabled {
1342+
"Disabled (using file-based storage)"
1343+
} else {
1344+
"Enabled (using system keyring)"
1345+
};
1346+
1347+
let _ = cliclack::log::info(format!("Current secret storage: {}", current_status));
1348+
let _ = cliclack::log::warning("Note: Disabling the keyring stores secrets in a plain text file (~/.config/goose/secrets.yaml)");
1349+
1350+
let storage_option = cliclack::select("How would you like to store secrets?")
1351+
.item(
1352+
"keyring",
1353+
"System Keyring (recommended)",
1354+
"Use secure system keyring for storing API keys and secrets",
1355+
)
1356+
.item(
1357+
"file",
1358+
"File-based Storage",
1359+
"Store secrets in a local file (useful when keyring access is restricted)",
1360+
)
1361+
.interact()?;
1362+
1363+
match storage_option {
1364+
"keyring" => {
1365+
// Set to empty string to enable keyring (absence or empty = enabled)
1366+
config.set_param("GOOSE_DISABLE_KEYRING", Value::String("".to_string()))?;
1367+
cliclack::outro("Secret storage set to system keyring (secure)")?;
1368+
let _ =
1369+
cliclack::log::info("You may need to restart goose for this change to take effect");
1370+
}
1371+
"file" => {
1372+
// Set the disable flag to use file storage
1373+
config.set_param("GOOSE_DISABLE_KEYRING", Value::String("true".to_string()))?;
1374+
cliclack::outro(
1375+
"Secret storage set to file (~/.config/goose/secrets.yaml). Keep this file secure!",
1376+
)?;
1377+
let _ =
1378+
cliclack::log::info("You may need to restart goose for this change to take effect");
1379+
}
1380+
_ => unreachable!(),
1381+
};
1382+
1383+
Ok(())
1384+
}
1385+
13251386
/// Configure experiment features that can be used with goose
13261387
/// Dialog for toggling which experiments are enabled/disabled
13271388
pub fn toggle_experiments_dialog() -> anyhow::Result<()> {
@@ -1740,6 +1801,50 @@ pub async fn handle_tetrate_auth() -> anyhow::Result<()> {
17401801
Ok(())
17411802
}
17421803

1804+
/// Prompts the user to collect custom HTTP headers for a provider.
1805+
fn collect_custom_headers() -> anyhow::Result<Option<std::collections::HashMap<String, String>>> {
1806+
let use_custom_headers = cliclack::confirm("Does this provider require custom headers?")
1807+
.initial_value(false)
1808+
.interact()?;
1809+
1810+
if !use_custom_headers {
1811+
return Ok(None);
1812+
}
1813+
1814+
let mut custom_headers = std::collections::HashMap::new();
1815+
1816+
loop {
1817+
let header_name: String = cliclack::input("Header name:")
1818+
.placeholder("e.g., x-origin-client-id")
1819+
.required(false)
1820+
.interact()?;
1821+
1822+
if header_name.is_empty() {
1823+
break;
1824+
}
1825+
1826+
let header_value: String = cliclack::password(format!("Value for '{}':", header_name))
1827+
.mask('▪')
1828+
.interact()?;
1829+
1830+
custom_headers.insert(header_name, header_value);
1831+
1832+
let add_more = cliclack::confirm("Add another header?")
1833+
.initial_value(false)
1834+
.interact()?;
1835+
1836+
if !add_more {
1837+
break;
1838+
}
1839+
}
1840+
1841+
if custom_headers.is_empty() {
1842+
Ok(None)
1843+
} else {
1844+
Ok(Some(custom_headers))
1845+
}
1846+
}
1847+
17431848
fn add_provider() -> anyhow::Result<()> {
17441849
let provider_type = cliclack::select("What type of API is this?")
17451850
.item(
@@ -1807,13 +1912,21 @@ fn add_provider() -> anyhow::Result<()> {
18071912
.initial_value(true)
18081913
.interact()?;
18091914

1915+
// Ask about custom headers for OpenAI compatible providers
1916+
let headers = if provider_type == "openai_compatible" {
1917+
collect_custom_headers()?
1918+
} else {
1919+
None
1920+
};
1921+
18101922
create_custom_provider(
18111923
provider_type,
18121924
display_name.clone(),
18131925
api_url,
18141926
api_key,
18151927
models,
18161928
Some(supports_streaming),
1929+
headers,
18171930
)?;
18181931

18191932
cliclack::outro(format!("Custom provider added: {}", display_name))?;

crates/goose-server/src/routes/config_management.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ pub struct UpdateCustomProviderRequest {
8787
pub api_key: String,
8888
pub models: Vec<String>,
8989
pub supports_streaming: Option<bool>,
90+
pub headers: Option<std::collections::HashMap<String, String>>,
9091
}
9192

9293
#[derive(Deserialize, ToSchema)]
@@ -706,6 +707,7 @@ pub async fn create_custom_provider(
706707
request.api_key,
707708
request.models,
708709
request.supports_streaming,
710+
request.headers,
709711
)
710712
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
711713

crates/goose/src/config/declarative_providers.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ pub fn create_custom_provider(
9292
api_key: String,
9393
models: Vec<String>,
9494
supports_streaming: Option<bool>,
95+
headers: Option<HashMap<String, String>>,
9596
) -> Result<DeclarativeProviderConfig> {
9697
let id = generate_id(&display_name);
9798
let api_key_name = generate_api_key_name(&id);
@@ -117,7 +118,7 @@ pub fn create_custom_provider(
117118
api_key_env: api_key_name,
118119
base_url: api_url,
119120
models: model_infos,
120-
headers: None,
121+
headers,
121122
timeout_seconds: None,
122123
supports_streaming,
123124
};

ui/desktop/openapi.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5275,6 +5275,13 @@
52755275
"engine": {
52765276
"type": "string"
52775277
},
5278+
"headers": {
5279+
"type": "object",
5280+
"additionalProperties": {
5281+
"type": "string"
5282+
},
5283+
"nullable": true
5284+
},
52785285
"models": {
52795286
"type": "array",
52805287
"items": {

ui/desktop/src/api/types.gen.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -935,6 +935,9 @@ export type UpdateCustomProviderRequest = {
935935
api_url: string;
936936
display_name: string;
937937
engine: string;
938+
headers?: {
939+
[key: string]: string;
940+
} | null;
938941
models: Array<string>;
939942
supports_streaming?: boolean | null;
940943
};

0 commit comments

Comments
 (0)