Codex Handbook
core/src/config/edit/document_helpers.rs 309 lines
use codex_config::types::AppToolApproval;use codex_config::types::McpServerConfig;use codex_config::types::McpServerEnvVar;use codex_config::types::McpServerToolConfig;use codex_config::types::McpServerTransportConfig;use codex_config::types::ToolSuggestDisabledTool;use codex_config::types::ToolSuggestDiscoverableType;use toml_edit::Array as TomlArray;use toml_edit::InlineTable;use toml_edit::Item as TomlItem;use toml_edit::Table as TomlTable;use toml_edit::Value as TomlValue;use toml_edit::value;pub(super) fn ensure_table_for_write(item: &mut TomlItem) -> Option<&mut TomlTable> {    match item {        TomlItem::Table(table) => Some(table),        TomlItem::Value(value) => {            if let Some(inline) = value.as_inline_table() {                *item = TomlItem::Table(table_from_inline(inline));                item.as_table_mut()            } else {                *item = TomlItem::Table(new_implicit_table());                item.as_table_mut()            }        }        TomlItem::None => {            *item = TomlItem::Table(new_implicit_table());            item.as_table_mut()        }        _ => None,    }}pub(super) fn ensure_table_for_read(item: &mut TomlItem) -> Option<&mut TomlTable> {    match item {        TomlItem::Table(table) => Some(table),        TomlItem::Value(value) => {            let inline = value.as_inline_table()?;            *item = TomlItem::Table(table_from_inline(inline));            item.as_table_mut()        }        _ => None,    }}fn serialize_mcp_server_table(config: &McpServerConfig) -> TomlTable {    let mut entry = TomlTable::new();    entry.set_implicit(false);    match &config.transport {        McpServerTransportConfig::Stdio {            command,            args,            env,            env_vars,            cwd,        } => {            entry["command"] = value(command.clone());            if !args.is_empty() {                entry["args"] = array_from_iter(args.iter().cloned());            }            if let Some(env) = env                && !env.is_empty()            {                entry["env"] = table_from_pairs(env.iter());            }            if !env_vars.is_empty() {                entry["env_vars"] = array_from_env_vars(env_vars);            }            if let Some(cwd) = cwd {                entry["cwd"] = value(cwd.to_string_lossy().to_string());            }        }        McpServerTransportConfig::StreamableHttp {            url,            bearer_token_env_var,            http_headers,            env_http_headers,        } => {            entry["url"] = value(url.clone());            if let Some(env_var) = bearer_token_env_var {                entry["bearer_token_env_var"] = value(env_var.clone());            }            if let Some(headers) = http_headers                && !headers.is_empty()            {                entry["http_headers"] = table_from_pairs(headers.iter());            }            if let Some(headers) = env_http_headers                && !headers.is_empty()            {                entry["env_http_headers"] = table_from_pairs(headers.iter());            }        }    }    if !config.enabled {        entry["enabled"] = value(false);    }    if !config.is_local_environment() {        entry["environment_id"] = value(config.environment_id.clone());    }    if config.required {        entry["required"] = value(true);    }    if config.supports_parallel_tool_calls {        entry["supports_parallel_tool_calls"] = value(true);    }    if let Some(timeout) = config.startup_timeout_sec {        entry["startup_timeout_sec"] = value(timeout.as_secs_f64());    }    if let Some(timeout) = config.tool_timeout_sec {        entry["tool_timeout_sec"] = value(timeout.as_secs_f64());    }    if let Some(approval_mode) = config.default_tools_approval_mode {        entry["default_tools_approval_mode"] = value(match approval_mode {            AppToolApproval::Auto => "auto",            AppToolApproval::Prompt => "prompt",            AppToolApproval::Approve => "approve",        });    }    if let Some(enabled_tools) = &config.enabled_tools        && !enabled_tools.is_empty()    {        entry["enabled_tools"] = array_from_iter(enabled_tools.iter().cloned());    }    if let Some(disabled_tools) = &config.disabled_tools        && !disabled_tools.is_empty()    {        entry["disabled_tools"] = array_from_iter(disabled_tools.iter().cloned());    }    if let Some(scopes) = &config.scopes        && !scopes.is_empty()    {        entry["scopes"] = array_from_iter(scopes.iter().cloned());    }    if let Some(oauth) = &config.oauth        && let Some(client_id) = &oauth.client_id        && !client_id.is_empty()    {        let mut oauth_table = TomlTable::new();        oauth_table.set_implicit(false);        oauth_table["client_id"] = value(client_id.clone());        entry["oauth"] = TomlItem::Table(oauth_table);    }    if let Some(resource) = &config.oauth_resource        && !resource.is_empty()    {        entry["oauth_resource"] = value(resource.clone());    }    if !config.tools.is_empty() {        let mut tools = new_implicit_table();        let mut tool_entries: Vec<_> = config.tools.iter().collect();        tool_entries.sort_by_key(|(name, _)| *name);        for (name, tool_config) in tool_entries {            tools.insert(name, serialize_mcp_server_tool(tool_config));        }        entry.insert("tools", TomlItem::Table(tools));    }    entry}fn serialize_mcp_server_tool(config: &McpServerToolConfig) -> TomlItem {    let mut entry = TomlTable::new();    entry.set_implicit(false);    if let Some(approval_mode) = config.approval_mode {        entry["approval_mode"] = value(match approval_mode {            AppToolApproval::Auto => "auto",            AppToolApproval::Prompt => "prompt",            AppToolApproval::Approve => "approve",        });    }    TomlItem::Table(entry)}pub(super) fn serialize_mcp_server(config: &McpServerConfig) -> TomlItem {    TomlItem::Table(serialize_mcp_server_table(config))}pub(super) fn serialize_mcp_server_inline(config: &McpServerConfig) -> InlineTable {    serialize_mcp_server_table(config).into_inline_table()}pub(super) fn merge_inline_table(existing: &mut InlineTable, replacement: InlineTable) {    existing.retain(|key, _| replacement.get(key).is_some());    for (key, value) in replacement.iter() {        if let Some(existing_value) = existing.get_mut(key) {            let mut updated_value = value.clone();            *updated_value.decor_mut() = existing_value.decor().clone();            *existing_value = updated_value;        } else {            existing.insert(key.to_string(), value.clone());        }    }}fn table_from_inline(inline: &InlineTable) -> TomlTable {    let mut table = new_implicit_table();    for (key, value) in inline.iter() {        let mut value = value.clone();        let decor = value.decor_mut();        decor.set_suffix("");        table.insert(key, TomlItem::Value(value));    }    table}pub(super) fn new_implicit_table() -> TomlTable {    let mut table = TomlTable::new();    table.set_implicit(true);    table}pub(super) fn parse_tool_suggest_disabled_tool(    value: &TomlValue,) -> Option<ToolSuggestDisabledTool> {    let table = value.as_inline_table()?;    let kind = match table.get("type").and_then(TomlValue::as_str) {        Some("connector") => ToolSuggestDiscoverableType::Connector,        Some("plugin") => ToolSuggestDiscoverableType::Plugin,        _ => return None,    };    let id = table.get("id").and_then(TomlValue::as_str)?;    Some(ToolSuggestDisabledTool {        kind,        id: id.to_string(),    })}pub(super) fn parse_tool_suggest_disabled_tool_table(    table: &TomlTable,) -> Option<ToolSuggestDisabledTool> {    let kind = match table.get("type").and_then(TomlItem::as_str) {        Some("connector") => ToolSuggestDiscoverableType::Connector,        Some("plugin") => ToolSuggestDiscoverableType::Plugin,        _ => return None,    };    let id = table.get("id").and_then(TomlItem::as_str)?;    Some(ToolSuggestDisabledTool {        kind,        id: id.to_string(),    })}pub(super) fn tool_suggest_disabled_tools_value(    disabled_tools: &[ToolSuggestDisabledTool],) -> TomlItem {    let mut array = TomlArray::new();    for disabled_tool in disabled_tools {        let mut table = InlineTable::new();        table.insert(            "type",            match disabled_tool.kind {                ToolSuggestDiscoverableType::Connector => "connector",                ToolSuggestDiscoverableType::Plugin => "plugin",            }            .into(),        );        table.insert("id", disabled_tool.id.clone().into());        array.push(table);    }    TomlItem::Value(array.into())}fn array_from_iter<I>(iter: I) -> TomlItemwhere    I: Iterator<Item = String>,{    let mut array = TomlArray::new();    for value in iter {        array.push(value);    }    TomlItem::Value(array.into())}fn array_from_env_vars(env_vars: &[McpServerEnvVar]) -> TomlItem {    let mut array = TomlArray::new();    for env_var in env_vars {        match env_var {            McpServerEnvVar::Name(name) => array.push(name.clone()),            McpServerEnvVar::Config { name, source } => {                let mut table = InlineTable::new();                table.insert("name", name.clone().into());                if let Some(source) = source {                    table.insert("source", source.clone().into());                }                array.push(table);            }        }    }    TomlItem::Value(array.into())}fn table_from_pairs<'a, I>(pairs: I) -> TomlItemwhere    I: IntoIterator<Item = (&'a String, &'a String)>,{    let mut entries: Vec<_> = pairs.into_iter().collect();    entries.sort_by_key(|(key, _)| *key);    let mut table = TomlTable::new();    table.set_implicit(false);    for (key, val) in entries {        table.insert(key, value(val.clone()));    }    TomlItem::Table(table)}