feat: Secret injection as environment variables into agent tools

This commit is contained in:
2025-11-03 15:10:34 -07:00
parent 474c5bc76f
commit 843abe0621
3 changed files with 29 additions and 13 deletions
+11 -1
View File
@@ -5,7 +5,9 @@ use crate::{
function::{run_llm_function, Functions}, function::{run_llm_function, Functions},
}; };
use crate::vault::SECRET_RE;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use fancy_regex::Captures;
use inquire::{validator::Validation, Text}; use inquire::{validator::Validation, Text};
use rust_embed::Embed; use rust_embed::Embed;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -30,6 +32,7 @@ pub struct Agent {
functions: Functions, functions: Functions,
rag: Option<Arc<Rag>>, rag: Option<Arc<Rag>>,
model: Model, model: Model,
vault: GlobalVault,
} }
impl Agent { impl Agent {
@@ -195,6 +198,7 @@ impl Agent {
functions, functions,
rag, rag,
model, model,
vault: Arc::clone(&config.read().vault),
}) })
} }
@@ -325,7 +329,13 @@ impl Agent {
.map(|(k, v)| { .map(|(k, v)| {
( (
format!("LLM_AGENT_VAR_{}", normalize_env_name(k)), format!("LLM_AGENT_VAR_{}", normalize_env_name(k)),
v.clone(), SECRET_RE
.replace(v, |caps: &Captures| {
self.vault
.get_secret(caps[1].trim(), false)
.unwrap_or(v.clone())
})
.to_string(),
) )
}) })
.collect() .collect()
+12 -8
View File
@@ -24,7 +24,7 @@ use crate::utils::*;
use crate::mcp::{ use crate::mcp::{
McpRegistry, MCP_INVOKE_META_FUNCTION_NAME_PREFIX, MCP_LIST_META_FUNCTION_NAME_PREFIX, McpRegistry, MCP_INVOKE_META_FUNCTION_NAME_PREFIX, MCP_LIST_META_FUNCTION_NAME_PREFIX,
}; };
use crate::vault::{create_vault_password_file, interpolate_secrets, Vault}; use crate::vault::{create_vault_password_file, interpolate_secrets, GlobalVault, Vault};
use anyhow::{anyhow, bail, Context, Result}; use anyhow::{anyhow, bail, Context, Result};
use fancy_regex::Regex; use fancy_regex::Regex;
use indexmap::IndexMap; use indexmap::IndexMap;
@@ -159,7 +159,6 @@ pub struct Config {
pub left_prompt: Option<String>, pub left_prompt: Option<String>,
pub right_prompt: Option<String>, pub right_prompt: Option<String>,
pub serve_addr: Option<String>,
pub user_agent: Option<String>, pub user_agent: Option<String>,
pub save_shell_history: bool, pub save_shell_history: bool,
pub sync_models_url: Option<String>, pub sync_models_url: Option<String>,
@@ -167,7 +166,7 @@ pub struct Config {
pub clients: Vec<ClientConfig>, pub clients: Vec<ClientConfig>,
#[serde(skip)] #[serde(skip)]
pub vault: Vault, pub vault: GlobalVault,
#[serde(skip)] #[serde(skip)]
pub macro_flag: bool, pub macro_flag: bool,
@@ -244,7 +243,6 @@ impl Default for Config {
left_prompt: None, left_prompt: None,
right_prompt: None, right_prompt: None,
serve_addr: None,
user_agent: None, user_agent: None,
save_shell_history: true, save_shell_history: true,
sync_models_url: None, sync_models_url: None,
@@ -317,7 +315,9 @@ impl Config {
let (parsed_config, missing_secrets) = interpolate_secrets(&content, &vault); let (parsed_config, missing_secrets) = interpolate_secrets(&content, &vault);
if !missing_secrets.is_empty() && !info_flag { if !missing_secrets.is_empty() && !info_flag {
debug!("Global config references secrets that are missing from the vault: {missing_secrets:?}"); debug!(
"Global config references secrets that are missing from the vault: {missing_secrets:?}"
);
return Err(anyhow!(formatdoc!( return Err(anyhow!(formatdoc!(
" "
Global config file references secrets that are missing from the vault: {:?} Global config file references secrets that are missing from the vault: {:?}
@@ -341,7 +341,7 @@ impl Config {
config.working_mode = working_mode; config.working_mode = working_mode;
config.info_flag = info_flag; config.info_flag = info_flag;
config.vault = vault; config.vault = Arc::new(vault);
Agent::install_builtin_agents()?; Agent::install_builtin_agents()?;
@@ -769,7 +769,9 @@ impl Config {
if let Some(servers) = value.as_ref() { if let Some(servers) = value.as_ref() {
if let Some(registry) = &config.read().mcp_registry { if let Some(registry) = &config.read().mcp_registry {
if registry.list_configured_servers().is_empty() { if registry.list_configured_servers().is_empty() {
bail!("No MCP servers are configured. Please configure MCP servers first before setting 'use_mcp_servers'."); bail!(
"No MCP servers are configured. Please configure MCP servers first before setting 'use_mcp_servers'."
);
} }
if !servers.split(',').all(|s| { if !servers.split(',').all(|s| {
@@ -778,7 +780,9 @@ impl Config {
.contains(&s.trim().to_string()) .contains(&s.trim().to_string())
|| s == "all" || s == "all"
}) { }) {
bail!("Some of the specified MCP servers in 'use_mcp_servers' are configured. Please check your MCP server configuration."); bail!(
"Some of the specified MCP servers in 'use_mcp_servers' are configured. Please check your MCP server configuration."
);
} }
} }
} }
+4 -2
View File
@@ -12,16 +12,18 @@ use fancy_regex::Regex;
use gman::providers::local::LocalProvider; use gman::providers::local::LocalProvider;
use gman::providers::SecretProvider; use gman::providers::SecretProvider;
use inquire::{required, Password, PasswordDisplayMode}; use inquire::{required, Password, PasswordDisplayMode};
use std::sync::LazyLock; use std::sync::{Arc, LazyLock};
use tokio::runtime::Handle; use tokio::runtime::Handle;
static SECRET_RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"\{\{(.+)}}").unwrap()); pub static SECRET_RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"\{\{(.+)}}").unwrap());
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
pub struct Vault { pub struct Vault {
local_provider: LocalProvider, local_provider: LocalProvider,
} }
pub type GlobalVault = Arc<Vault>;
impl Vault { impl Vault {
pub fn init_bare() -> Self { pub fn init_bare() -> Self {
let vault_password_file = Config::default().vault_password_file(); let vault_password_file = Config::default().vault_password_file();