diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 4c87b8dc05..6def74c55d 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -1183,6 +1183,7 @@ dependencies = [ "regex", "regex-lite", "reqwest", + "schemars 0.8.22", "seccompiler", "serde", "serde_json", @@ -1534,6 +1535,7 @@ dependencies = [ "pretty_assertions", "reqwest", "rmcp", + "schemars 0.8.22", "serde", "serde_json", "serial_test", diff --git a/codex-rs/app-server-protocol/src/protocol/common.rs b/codex-rs/app-server-protocol/src/protocol/common.rs index 441efad7b8..cb1f20a769 100644 --- a/codex-rs/app-server-protocol/src/protocol/common.rs +++ b/codex-rs/app-server-protocol/src/protocol/common.rs @@ -168,6 +168,10 @@ client_request_definitions! { params: v2::ConfigReadParams, response: v2::ConfigReadResponse, }, + ConfigSchemaRead => "config/schema/read" { + params: v2::ConfigSchemaReadParams, + response: v2::ConfigSchemaReadResponse, + }, ConfigValueWrite => "config/value/write" { params: v2::ConfigValueWriteParams, response: v2::ConfigWriteResponse, diff --git a/codex-rs/app-server-protocol/src/protocol/v2.rs b/codex-rs/app-server-protocol/src/protocol/v2.rs index 0a04e8a69a..9f9a811de2 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2.rs @@ -228,6 +228,18 @@ pub struct ConfigReadResponse { pub layers: Option>, } +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, Default)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct ConfigSchemaReadParams {} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct ConfigSchemaReadResponse { + pub schema: JsonValue, +} + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index 80ec99e81c..02634325f4 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -471,6 +471,7 @@ impl CodexMessageProcessor { self.exec_one_off_command(request_id, params).await; } ClientRequest::ConfigRead { .. } + | ClientRequest::ConfigSchemaRead { .. } | ClientRequest::ConfigValueWrite { .. } | ClientRequest::ConfigBatchWrite { .. } => { warn!("Config request reached CodexMessageProcessor unexpectedly"); diff --git a/codex-rs/app-server/src/config_api.rs b/codex-rs/app-server/src/config_api.rs index 68bbdd8c66..98728c3677 100644 --- a/codex-rs/app-server/src/config_api.rs +++ b/codex-rs/app-server/src/config_api.rs @@ -7,6 +7,8 @@ use codex_app_server_protocol::ConfigLayerMetadata; use codex_app_server_protocol::ConfigLayerName; use codex_app_server_protocol::ConfigReadParams; use codex_app_server_protocol::ConfigReadResponse; +use codex_app_server_protocol::ConfigSchemaReadParams; +use codex_app_server_protocol::ConfigSchemaReadResponse; use codex_app_server_protocol::ConfigValueWriteParams; use codex_app_server_protocol::ConfigWriteErrorCode; use codex_app_server_protocol::ConfigWriteResponse; @@ -15,6 +17,7 @@ use codex_app_server_protocol::MergeStrategy; use codex_app_server_protocol::OverriddenMetadata; use codex_app_server_protocol::WriteStatus; use codex_core::config::ConfigToml; +use codex_core::config::schema::config_json_schema_value; use codex_core::config_loader::LoadedConfigLayers; use codex_core::config_loader::LoaderOverrides; use codex_core::config_loader::load_config_layers_with_overrides; @@ -84,6 +87,15 @@ impl ConfigApi { Ok(response) } + pub(crate) async fn schema_read( + &self, + _params: ConfigSchemaReadParams, + ) -> Result { + Ok(ConfigSchemaReadResponse { + schema: config_json_schema_value(), + }) + } + pub(crate) async fn write_value( &self, params: ConfigValueWriteParams, diff --git a/codex-rs/app-server/src/message_processor.rs b/codex-rs/app-server/src/message_processor.rs index 90560e9b3c..c5d8b59aab 100644 --- a/codex-rs/app-server/src/message_processor.rs +++ b/codex-rs/app-server/src/message_processor.rs @@ -9,6 +9,7 @@ use codex_app_server_protocol::ClientInfo; use codex_app_server_protocol::ClientRequest; use codex_app_server_protocol::ConfigBatchWriteParams; use codex_app_server_protocol::ConfigReadParams; +use codex_app_server_protocol::ConfigSchemaReadParams; use codex_app_server_protocol::ConfigValueWriteParams; use codex_app_server_protocol::InitializeResponse; use codex_app_server_protocol::JSONRPCError; @@ -148,6 +149,9 @@ impl MessageProcessor { ClientRequest::ConfigRead { request_id, params } => { self.handle_config_read(request_id, params).await; } + ClientRequest::ConfigSchemaRead { request_id, params } => { + self.handle_config_schema_read(request_id, params).await; + } ClientRequest::ConfigValueWrite { request_id, params } => { self.handle_config_value_write(request_id, params).await; } @@ -196,6 +200,17 @@ impl MessageProcessor { } } + async fn handle_config_schema_read( + &self, + request_id: RequestId, + params: ConfigSchemaReadParams, + ) { + match self.config_api.schema_read(params).await { + Ok(response) => self.outgoing.send_response(request_id, response).await, + Err(error) => self.outgoing.send_error(request_id, error).await, + } + } + async fn handle_config_batch_write( &self, request_id: RequestId, diff --git a/codex-rs/app-server/tests/common/mcp_process.rs b/codex-rs/app-server/tests/common/mcp_process.rs index e2da40bf2d..f40500febd 100644 --- a/codex-rs/app-server/tests/common/mcp_process.rs +++ b/codex-rs/app-server/tests/common/mcp_process.rs @@ -20,6 +20,7 @@ use codex_app_server_protocol::ClientInfo; use codex_app_server_protocol::ClientNotification; use codex_app_server_protocol::ConfigBatchWriteParams; use codex_app_server_protocol::ConfigReadParams; +use codex_app_server_protocol::ConfigSchemaReadParams; use codex_app_server_protocol::ConfigValueWriteParams; use codex_app_server_protocol::FeedbackUploadParams; use codex_app_server_protocol::GetAccountParams; @@ -412,6 +413,14 @@ impl McpProcess { self.send_request("config/read", params).await } + pub async fn send_config_schema_read_request( + &mut self, + params: ConfigSchemaReadParams, + ) -> anyhow::Result { + let params = Some(serde_json::to_value(params)?); + self.send_request("config/schema/read", params).await + } + pub async fn send_config_value_write_request( &mut self, params: ConfigValueWriteParams, diff --git a/codex-rs/app-server/tests/suite/v2/config_rpc.rs b/codex-rs/app-server/tests/suite/v2/config_rpc.rs index 343a13c3c4..e904018447 100644 --- a/codex-rs/app-server/tests/suite/v2/config_rpc.rs +++ b/codex-rs/app-server/tests/suite/v2/config_rpc.rs @@ -6,6 +6,8 @@ use codex_app_server_protocol::ConfigEdit; use codex_app_server_protocol::ConfigLayerName; use codex_app_server_protocol::ConfigReadParams; use codex_app_server_protocol::ConfigReadResponse; +use codex_app_server_protocol::ConfigSchemaReadParams; +use codex_app_server_protocol::ConfigSchemaReadResponse; use codex_app_server_protocol::ConfigValueWriteParams; use codex_app_server_protocol::ConfigWriteResponse; use codex_app_server_protocol::JSONRPCError; @@ -70,6 +72,48 @@ sandbox_mode = "workspace-write" Ok(()) } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn config_schema_read_returns_schema() -> Result<()> { + let codex_home = TempDir::new()?; + let mut mcp = McpProcess::new(codex_home.path()).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; + + let request_id = mcp + .send_config_schema_read_request(ConfigSchemaReadParams::default()) + .await?; + let resp: JSONRPCResponse = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(request_id)), + ) + .await??; + let ConfigSchemaReadResponse { schema } = to_response(resp)?; + + let properties = schema + .get("definitions") + .and_then(|defs| defs.get("ConfigToml")) + .and_then(|v| v.get("properties")) + .cloned() + .expect("schema properties present"); + + assert!( + properties + .get("mcp_servers") + .and_then(|v| v.get("additionalProperties")) + .is_some() + ); + + assert!( + properties + .get("sandbox_mode") + .and_then(|v| v.get("enum")) + .and_then(|vals| vals.as_array()) + .map(|vals| vals.contains(&json!("workspace-write"))) + .unwrap_or(false) + ); + + Ok(()) +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn config_read_includes_system_layer_and_overrides() -> Result<()> { let codex_home = TempDir::new()?; diff --git a/codex-rs/core/Cargo.toml b/codex-rs/core/Cargo.toml index 7bd2d652e1..a2441d143e 100644 --- a/codex-rs/core/Cargo.toml +++ b/codex-rs/core/Cargo.toml @@ -50,6 +50,7 @@ os_info = { workspace = true } rand = { workspace = true } regex-lite = { workspace = true } reqwest = { workspace = true, features = ["json", "stream"] } +schemars = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } sha1 = { workspace = true } diff --git a/codex-rs/core/src/auth/storage.rs b/codex-rs/core/src/auth/storage.rs index a238eb9c38..48b67aca05 100644 --- a/codex-rs/core/src/auth/storage.rs +++ b/codex-rs/core/src/auth/storage.rs @@ -1,5 +1,6 @@ use chrono::DateTime; use chrono::Utc; +use schemars::JsonSchema; use serde::Deserialize; use serde::Serialize; use sha2::Digest; @@ -21,7 +22,7 @@ use codex_keyring_store::DefaultKeyringStore; use codex_keyring_store::KeyringStore; /// Determine where Codex should store CLI auth credentials. -#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "lowercase")] pub enum AuthCredentialsStoreMode { #[default] diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index 44cc651a0c..17c1ea88f2 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -45,6 +45,7 @@ use codex_protocol::config_types::Verbosity; use codex_rmcp_client::OAuthCredentialsStoreMode; use dirs::home_dir; use dunce::canonicalize; +use schemars::JsonSchema; use serde::Deserialize; use similar::DiffableStr; use std::collections::BTreeMap; @@ -59,6 +60,7 @@ use toml_edit::DocumentMut; pub mod edit; pub mod profile; +pub mod schema; pub mod types; pub const OPENAI_DEFAULT_MODEL: &str = "gpt-5.1-codex"; @@ -559,7 +561,7 @@ fn apply_toml_override(root: &mut TomlValue, path: &str, value: TomlValue) { } /// Base config deserialized from ~/.codex/config.toml. -#[derive(Deserialize, Debug, Clone, Default, PartialEq)] +#[derive(Deserialize, Debug, Clone, Default, PartialEq, JsonSchema)] pub struct ConfigToml { /// Optional override of model selection. pub model: Option, @@ -746,7 +748,7 @@ impl From for UserSavedConfig { } } -#[derive(Deserialize, Debug, Clone, PartialEq, Eq)] +#[derive(Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema)] pub struct ProjectConfig { pub trust_level: Option, } @@ -761,7 +763,7 @@ impl ProjectConfig { } } -#[derive(Deserialize, Debug, Clone, Default, PartialEq)] +#[derive(Deserialize, Debug, Clone, Default, PartialEq, JsonSchema)] pub struct ToolsToml { #[serde(default, alias = "web_search_request")] pub web_search: Option, diff --git a/codex-rs/core/src/config/profile.rs b/codex-rs/core/src/config/profile.rs index 3d9e60b8e5..c2e60050f9 100644 --- a/codex-rs/core/src/config/profile.rs +++ b/codex-rs/core/src/config/profile.rs @@ -6,10 +6,11 @@ use codex_protocol::config_types::ReasoningEffort; use codex_protocol::config_types::ReasoningSummary; use codex_protocol::config_types::SandboxMode; use codex_protocol::config_types::Verbosity; +use schemars::JsonSchema; /// Collection of common configuration options that a user can define as a unit /// in `config.toml`. -#[derive(Debug, Clone, Default, PartialEq, Deserialize)] +#[derive(Debug, Clone, Default, PartialEq, Deserialize, JsonSchema)] pub struct ConfigProfile { pub model: Option, /// The key in the `model_providers` map identifying the diff --git a/codex-rs/core/src/config/schema.rs b/codex-rs/core/src/config/schema.rs new file mode 100644 index 0000000000..3a1742501f --- /dev/null +++ b/codex-rs/core/src/config/schema.rs @@ -0,0 +1,70 @@ +use schemars::schema::RootSchema; +use schemars::schema_for; +use serde_json::Value; + +use super::ConfigToml; + +pub fn config_json_schema() -> RootSchema { + schema_for!(ConfigToml) +} + +pub fn config_json_schema_value() -> Value { + serde_json::to_value(config_json_schema()).expect("serialize config schema") +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn schema_includes_core_fields_and_shapes() { + let schema = config_json_schema_value(); + let config_schema = schema + .get("definitions") + .and_then(|defs| defs.get("ConfigToml")) + .or_else(|| schema.get("schema")) + .unwrap_or(&schema); + let Some(props) = config_schema.get("properties") else { + panic!("config schema missing properties"); + }; + + let model_schema = props.get("model").expect("model schema present"); + let model_is_string = match model_schema.get("type") { + Some(Value::String(t)) => t == "string", + Some(Value::Array(types)) => types + .iter() + .any(|t| t.as_str().is_some_and(|inner| inner == "string")), + _ => false, + }; + assert!(model_is_string); + + let sandbox_mode_definition = schema + .get("definitions") + .and_then(|defs| defs.get("SandboxMode")) + .expect("SandboxMode schema present"); + let sandbox_enums = sandbox_mode_definition + .get("enum") + .and_then(Value::as_array) + .cloned() + .unwrap_or_default(); + let sandbox_values = sandbox_enums + .iter() + .filter_map(Value::as_str) + .collect::>(); + assert!(sandbox_values.contains(&"read-only")); + assert!(sandbox_values.contains(&"workspace-write")); + assert!(sandbox_values.contains(&"danger-full-access")); + + assert!( + props + .get("mcp_servers") + .and_then(|v| v.get("additionalProperties")) + .is_some() + ); + assert!( + props + .get("profiles") + .and_then(|v| v.get("additionalProperties")) + .is_some() + ); + } +} diff --git a/codex-rs/core/src/config/types.rs b/codex-rs/core/src/config/types.rs index 869ec82973..5628734699 100644 --- a/codex-rs/core/src/config/types.rs +++ b/codex-rs/core/src/config/types.rs @@ -9,13 +9,14 @@ use std::path::PathBuf; use std::time::Duration; use wildmatch::WildMatchPattern; +use schemars::JsonSchema; use serde::Deserialize; use serde::Serialize; use serde::de::Error as SerdeError; pub const DEFAULT_OTEL_ENVIRONMENT: &str = "dev"; -#[derive(Serialize, Debug, Clone, PartialEq)] +#[derive(Serialize, Debug, Clone, PartialEq, JsonSchema)] pub struct McpServerConfig { #[serde(flatten)] pub transport: McpServerTransportConfig, @@ -30,10 +31,12 @@ pub struct McpServerConfig { with = "option_duration_secs", skip_serializing_if = "Option::is_none" )] + #[schemars(with = "Option")] pub startup_timeout_sec: Option, /// Default timeout for MCP tool calls initiated via this server. #[serde(default, with = "option_duration_secs")] + #[schemars(with = "Option")] pub tool_timeout_sec: Option, /// Explicit allow-list of tools exposed from this server. When set, only these tools will be registered. @@ -161,7 +164,7 @@ const fn default_enabled() -> bool { true } -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema)] #[serde(untagged, deny_unknown_fields, rename_all = "snake_case")] pub enum McpServerTransportConfig { /// https://modelcontextprotocol.io/specification/2025-06-18/basic/transports#stdio @@ -219,7 +222,7 @@ mod option_duration_secs { } } -#[derive(Deserialize, Debug, Copy, Clone, PartialEq)] +#[derive(Deserialize, Debug, Copy, Clone, PartialEq, JsonSchema)] pub enum UriBasedFileOpener { #[serde(rename = "vscode")] VsCode, @@ -251,7 +254,7 @@ impl UriBasedFileOpener { } /// Settings that govern if and what will be written to `~/.codex/history.jsonl`. -#[derive(Deserialize, Debug, Clone, PartialEq, Default)] +#[derive(Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)] pub struct History { /// If true, history entries will not be written to disk. pub persistence: HistoryPersistence, @@ -261,7 +264,7 @@ pub struct History { pub max_bytes: Option, } -#[derive(Deserialize, Debug, Copy, Clone, PartialEq, Default)] +#[derive(Deserialize, Debug, Copy, Clone, PartialEq, Default, JsonSchema)] #[serde(rename_all = "kebab-case")] pub enum HistoryPersistence { /// Save all history entries to disk. @@ -273,7 +276,7 @@ pub enum HistoryPersistence { // ===== OTEL configuration ===== -#[derive(Deserialize, Debug, Clone, PartialEq)] +#[derive(Deserialize, Debug, Clone, PartialEq, JsonSchema)] #[serde(rename_all = "kebab-case")] pub enum OtelHttpProtocol { /// Binary payload @@ -282,7 +285,7 @@ pub enum OtelHttpProtocol { Json, } -#[derive(Deserialize, Debug, Clone, PartialEq, Default)] +#[derive(Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)] #[serde(rename_all = "kebab-case")] pub struct OtelTlsConfig { pub ca_certificate: Option, @@ -291,7 +294,7 @@ pub struct OtelTlsConfig { } /// Which OTEL exporter to use. -#[derive(Deserialize, Debug, Clone, PartialEq)] +#[derive(Deserialize, Debug, Clone, PartialEq, JsonSchema)] #[serde(rename_all = "kebab-case")] pub enum OtelExporterKind { None, @@ -313,7 +316,7 @@ pub enum OtelExporterKind { } /// OTEL settings loaded from config.toml. Fields are optional so we can apply defaults. -#[derive(Deserialize, Debug, Clone, PartialEq, Default)] +#[derive(Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)] pub struct OtelConfigToml { /// Log user prompt in traces pub log_user_prompt: Option, @@ -326,7 +329,7 @@ pub struct OtelConfigToml { } /// Effective OTEL settings after defaults are applied. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, JsonSchema)] pub struct OtelConfig { pub log_user_prompt: bool, pub environment: String, @@ -343,7 +346,7 @@ impl Default for OtelConfig { } } -#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, JsonSchema)] #[serde(untagged)] pub enum Notifications { Enabled(bool), @@ -357,7 +360,7 @@ impl Default for Notifications { } /// Collection of settings that are specific to the TUI. -#[derive(Deserialize, Debug, Clone, PartialEq, Default)] +#[derive(Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)] pub struct Tui { /// Enable desktop notifications from the TUI when the terminal is unfocused. /// Defaults to `true`. @@ -377,7 +380,7 @@ const fn default_true() -> bool { /// Settings for notices we display to users via the tui and app-server clients /// (primarily the Codex IDE extension). NOTE: these are different from /// notifications - notices are warnings, NUX screens, acknowledgements, etc. -#[derive(Deserialize, Debug, Clone, PartialEq, Default)] +#[derive(Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)] pub struct Notice { /// Tracks whether the user has acknowledged the full access warning prompt. pub hide_full_access_warning: Option, @@ -397,7 +400,7 @@ impl Notice { pub(crate) const TABLE_KEY: &'static str = "notice"; } -#[derive(Deserialize, Debug, Clone, PartialEq, Default)] +#[derive(Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)] pub struct SandboxWorkspaceWrite { #[serde(default)] pub writable_roots: Vec, @@ -420,7 +423,7 @@ impl From for codex_app_server_protocol::SandboxSettings } } -#[derive(Deserialize, Debug, Clone, PartialEq, Default)] +#[derive(Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)] #[serde(rename_all = "kebab-case")] pub enum ShellEnvironmentPolicyInherit { /// "Core" environment variables for the platform. On UNIX, this would @@ -437,7 +440,7 @@ pub enum ShellEnvironmentPolicyInherit { /// Policy for building the `env` when spawning a process via either the /// `shell` or `local_shell` tool. -#[derive(Deserialize, Debug, Clone, PartialEq, Default)] +#[derive(Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)] pub struct ShellEnvironmentPolicyToml { pub inherit: Option, @@ -516,7 +519,7 @@ impl From for ShellEnvironmentPolicy { } } -#[derive(Deserialize, Debug, Clone, PartialEq, Eq, Default, Hash)] +#[derive(Deserialize, Debug, Clone, PartialEq, Eq, Default, Hash, JsonSchema)] #[serde(rename_all = "kebab-case")] pub enum ReasoningSummaryFormat { #[default] diff --git a/codex-rs/core/src/features.rs b/codex-rs/core/src/features.rs index 0c67c9ff5c..5424e3c45d 100644 --- a/codex-rs/core/src/features.rs +++ b/codex-rs/core/src/features.rs @@ -7,6 +7,7 @@ use crate::config::ConfigToml; use crate::config::profile::ConfigProfile; +use schemars::JsonSchema; use serde::Deserialize; use std::collections::BTreeMap; use std::collections::BTreeSet; @@ -236,7 +237,7 @@ pub fn is_known_feature_key(key: &str) -> bool { } /// Deserializable features table for TOML. -#[derive(Deserialize, Debug, Clone, Default, PartialEq)] +#[derive(Deserialize, Debug, Clone, Default, PartialEq, JsonSchema)] pub struct FeaturesToml { #[serde(flatten)] pub entries: BTreeMap, diff --git a/codex-rs/core/src/model_provider_info.rs b/codex-rs/core/src/model_provider_info.rs index 4912a64694..36cdc1ea96 100644 --- a/codex-rs/core/src/model_provider_info.rs +++ b/codex-rs/core/src/model_provider_info.rs @@ -12,6 +12,7 @@ use codex_app_server_protocol::AuthMode; use http::HeaderMap; use http::header::HeaderName; use http::header::HeaderValue; +use schemars::JsonSchema; use serde::Deserialize; use serde::Serialize; use std::collections::HashMap; @@ -33,7 +34,7 @@ const MAX_REQUEST_MAX_RETRIES: u64 = 100; /// *Responses* API. The two protocols use different request/response shapes /// and *cannot* be auto-detected at runtime, therefore each provider entry /// must declare which one it expects. -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "lowercase")] pub enum WireApi { /// The Responses API exposed by OpenAI at `/v1/responses`. @@ -45,7 +46,7 @@ pub enum WireApi { } /// Serializable representation of a provider definition. -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema)] pub struct ModelProviderInfo { /// Friendly display name. pub name: String, diff --git a/codex-rs/rmcp-client/Cargo.toml b/codex-rs/rmcp-client/Cargo.toml index f2525c18d3..584a5d29a5 100644 --- a/codex-rs/rmcp-client/Cargo.toml +++ b/codex-rs/rmcp-client/Cargo.toml @@ -36,6 +36,7 @@ rmcp = { workspace = true, default-features = false, features = [ "transport-streamable-http-client-reqwest", "transport-streamable-http-server", ] } +schemars = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } sha2 = { workspace = true } diff --git a/codex-rs/rmcp-client/src/oauth.rs b/codex-rs/rmcp-client/src/oauth.rs index f8eafaf23e..a3a256374b 100644 --- a/codex-rs/rmcp-client/src/oauth.rs +++ b/codex-rs/rmcp-client/src/oauth.rs @@ -26,6 +26,7 @@ use oauth2::Scope; use oauth2::TokenResponse; use oauth2::basic::BasicTokenType; use rmcp::transport::auth::OAuthTokenResponse; +use schemars::JsonSchema; use serde::Deserialize; use serde::Serialize; use serde_json::Value; @@ -63,7 +64,7 @@ pub struct StoredOAuthTokens { } /// Determine where Codex should store and read MCP credentials. -#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "lowercase")] pub enum OAuthCredentialsStoreMode { /// `Keyring` when available; otherwise, `File`.