diff --git a/codex-rs/app-server/src/request_processors/thread_processor_tests.rs b/codex-rs/app-server/src/request_processors/thread_processor_tests.rs index 102696601d..af878ff8e0 100644 --- a/codex-rs/app-server/src/request_processors/thread_processor_tests.rs +++ b/codex-rs/app-server/src/request_processors/thread_processor_tests.rs @@ -612,6 +612,7 @@ mod thread_processor_behavior_tests { requires_openai_auth: false, supports_websockets: true, supports_standalone_web_search: false, + capabilities: None, include_internal_metadata: false, }; let config_manager = ConfigManager::new( diff --git a/codex-rs/config/src/thread_config.rs b/codex-rs/config/src/thread_config.rs index 0c1012f09b..0518ea887b 100644 --- a/codex-rs/config/src/thread_config.rs +++ b/codex-rs/config/src/thread_config.rs @@ -316,6 +316,7 @@ mod tests { requires_openai_auth: false, supports_websockets: true, supports_standalone_web_search: true, + capabilities: None, include_internal_metadata: false, } } diff --git a/codex-rs/config/src/thread_config/remote.rs b/codex-rs/config/src/thread_config/remote.rs index 1e16cc482b..da0af3bca0 100644 --- a/codex-rs/config/src/thread_config/remote.rs +++ b/codex-rs/config/src/thread_config/remote.rs @@ -194,6 +194,7 @@ fn model_provider_from_proto( requires_openai_auth: provider.requires_openai_auth, supports_websockets: provider.supports_websockets, supports_standalone_web_search: provider.supports_standalone_web_search, + capabilities: None, include_internal_metadata: false, }; Ok((id, info)) @@ -226,6 +227,7 @@ fn model_provider_to_proto( supports_websockets, supports_standalone_web_search, include_internal_metadata: _, + capabilities: _, } = provider; proto::ModelProvider { @@ -581,6 +583,7 @@ mod tests { supports_standalone_web_search: true, gateway_oauth: None, aws: None, + capabilities: None, include_internal_metadata: false, } } diff --git a/codex-rs/core/config.schema.json b/codex-rs/core/config.schema.json index 1ab7592e96..ce99d95f99 100644 --- a/codex-rs/core/config.schema.json +++ b/codex-rs/core/config.schema.json @@ -2693,6 +2693,25 @@ }, "type": "object" }, + "ModelProviderCapabilities": { + "additionalProperties": false, + "description": "Overrides for the optional API features supported by a custom provider.", + "properties": { + "external_web_access": { + "description": "Whether hosted web search may access the live web.", + "type": "boolean" + }, + "remote_compaction": { + "allOf": [ + { + "$ref": "#/definitions/RemoteCompactionSupport" + } + ], + "description": "Remote context-compaction protocol; omission preserves provider defaults." + } + }, + "type": "object" + }, "ModelProviderInfo": { "additionalProperties": false, "description": "Serializable representation of a provider definition.", @@ -2717,6 +2736,14 @@ "description": "Base URL for the provider's OpenAI-compatible API.", "type": "string" }, + "capabilities": { + "allOf": [ + { + "$ref": "#/definitions/ModelProviderCapabilities" + } + ], + "description": "Optional API capability overrides for a custom Responses-compatible provider. Unspecified capabilities retain their existing provider defaults." + }, "env_http_headers": { "additionalProperties": { "type": "string" @@ -3919,6 +3946,25 @@ } ] }, + "RemoteCompactionSupport": { + "description": "Remote context-compaction protocols supported by a model provider.", + "oneOf": [ + { + "description": "Use local compaction instead of a provider compaction protocol.", + "enum": [ + "unsupported" + ], + "type": "string" + }, + { + "description": "Send `compaction_trigger` items over the Responses endpoint.", + "enum": [ + "v2" + ], + "type": "string" + } + ] + }, "RemoteMessageBoardConfigToml": { "additionalProperties": false, "description": "Connection to a board provisioned by the research host.", diff --git a/codex-rs/core/tests/responses_headers.rs b/codex-rs/core/tests/responses_headers.rs index 58e780b7e8..84bf461e78 100644 --- a/codex-rs/core/tests/responses_headers.rs +++ b/codex-rs/core/tests/responses_headers.rs @@ -92,6 +92,7 @@ async fn responses_stream_includes_subagent_header_on_review() { requires_openai_auth: false, supports_websockets: false, supports_standalone_web_search: false, + capabilities: None, include_internal_metadata: false, }; @@ -235,6 +236,7 @@ async fn responses_stream_includes_subagent_header_on_other() { requires_openai_auth: false, supports_websockets: false, supports_standalone_web_search: false, + capabilities: None, include_internal_metadata: false, }; @@ -359,6 +361,7 @@ async fn responses_respects_model_info_overrides_from_config() { requires_openai_auth: false, supports_websockets: false, supports_standalone_web_search: false, + capabilities: None, include_internal_metadata: false, }; diff --git a/codex-rs/core/tests/suite/client.rs b/codex-rs/core/tests/suite/client.rs index 16f58fb41e..1d7359de86 100644 --- a/codex-rs/core/tests/suite/client.rs +++ b/codex-rs/core/tests/suite/client.rs @@ -1642,6 +1642,7 @@ async fn send_provider_auth_request(server: &MockServer, auth: ModelProviderAuth requires_openai_auth: false, supports_websockets: false, supports_standalone_web_search: false, + capabilities: None, include_internal_metadata: false, }; @@ -3161,6 +3162,7 @@ async fn azure_responses_request_does_not_store_and_preserves_prefixed_item_ids( requires_openai_auth: false, supports_websockets: false, supports_standalone_web_search: false, + capabilities: None, include_internal_metadata: false, }; @@ -3799,6 +3801,7 @@ async fn azure_overrides_assign_properties_used_for_responses_url() { requires_openai_auth: false, supports_websockets: false, supports_standalone_web_search: false, + capabilities: None, include_internal_metadata: false, }; @@ -3886,6 +3889,7 @@ async fn env_var_overrides_loaded_auth() { requires_openai_auth: false, supports_websockets: false, supports_standalone_web_search: false, + capabilities: None, include_internal_metadata: false, }; diff --git a/codex-rs/core/tests/suite/client_websockets.rs b/codex-rs/core/tests/suite/client_websockets.rs index c6345c160d..04dc933444 100755 --- a/codex-rs/core/tests/suite/client_websockets.rs +++ b/codex-rs/core/tests/suite/client_websockets.rs @@ -2661,6 +2661,7 @@ fn websocket_provider_with_connect_timeout( requires_openai_auth: false, supports_websockets: true, supports_standalone_web_search: false, + capabilities: None, include_internal_metadata: false, } } diff --git a/codex-rs/core/tests/suite/scenarios.rs b/codex-rs/core/tests/suite/scenarios.rs index 0ef0ee3013..ff38ead973 100644 --- a/codex-rs/core/tests/suite/scenarios.rs +++ b/codex-rs/core/tests/suite/scenarios.rs @@ -123,6 +123,9 @@ mod preparation; #[path = "scenarios_content_filter.rs"] mod content_filter; +#[path = "scenarios_provider_capabilities.rs"] +mod provider_capabilities; + #[path = "scenarios_shared_instructions.rs"] mod shared_instructions; diff --git a/codex-rs/core/tests/suite/scenarios_provider_capabilities.rs b/codex-rs/core/tests/suite/scenarios_provider_capabilities.rs new file mode 100644 index 0000000000..3afb3a879e --- /dev/null +++ b/codex-rs/core/tests/suite/scenarios_provider_capabilities.rs @@ -0,0 +1,100 @@ +//! Custom provider overrides constrain web access and enable V2 compaction. + +use super::*; +use codex_protocol::models::PermissionProfile; +use pretty_assertions::assert_eq; + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn custom_provider_capabilities_constrain_web_access_and_enable_compaction() -> Result<()> { + skip_if_no_network!(Ok(())); + let server = start_mock_server().await; + let home = Arc::new(TempDir::new()?); + fs::write( + home.path().join("config.toml"), + r#" +model_provider = "custom" +web_search = "cached" +[features] +standalone_web_search = false +[features.multi_agent_v2] +enabled = true +tool_namespace = "collaboration" +expose_spawn_agent_model_overrides = false +[model_providers.custom] +name = "Custom" +requires_openai_auth = true +[model_providers.custom.capabilities] +external_web_access = false +remote_compaction = "v2" +"#, + )?; + let test = test_codex() + .with_home(home) + .with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing()) + .with_config(|config| { + configure_scenario_catalog(config); + let base_url = config.model_provider.base_url.clone(); + // Restore the configured provider after the test builder installs its default. + config.model_provider = config.model_providers["custom"].clone(); + config.model_provider.base_url = base_url; + }) + .with_model_info_override("gpt-5.5", |model| { + model.use_responses_lite = false; + model.tool_mode = Some(ToolMode::Direct); + model.apply_patch_tool_type = None; + }) + .build_with_auto_env(&server) + .await?; + let mock = mount_sse_sequence( + &server, + vec![ + sse(vec![ev_completed("response")]), + sse(vec![ + json!({ + "type": "response.output_item.done", + "item": {"type": "compaction", "encrypted_content": "CUSTOM_SUMMARY"}, + }), + ev_completed("compact-response"), + ]), + ], + ) + .await; + test.submit_turn_with_permission_profile( + "Check the configuration.", + PermissionProfile::Disabled, + ) + .await?; + test.codex.submit(Op::Compact).await?; + wait_for_event(&test.codex, |event| { + assert!(!matches!(event, EventMsg::Error(_)), "{event:?}"); + matches!(event, EventMsg::TurnComplete(_)) + }) + .await; + + let requests = mock.requests(); + assert_eq!(requests.len(), 2); + assert_eq!(requests[1].inputs_of_type("compaction_trigger").len(), 1); + let body = requests[0].body_json(); + let tools = body["tools"].as_array().expect("request tools"); + assert!( + requests[0] + .tool_by_name("collaboration", "list_agents") + .is_some() + ); + let web_search = tools + .iter() + .find(|tool| tool["type"] == "web_search") + .expect("cached search"); + assert_eq!(web_search["external_web_access"], false); + insta::assert_snapshot!( + "custom_provider_capabilities", + context_snapshot::format_request_history_snapshot( + "Custom provider overrides keep unrestricted search cached and enable V2 compaction.", + &requests, + &ContextSnapshotOptions::default() + .rewrite_known_segments() + .include_request_settings(), + ) + ); + Ok(()) +} diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__provider_capabilities__custom_provider_capabilities.snap b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__provider_capabilities__custom_provider_capabilities.snap new file mode 100644 index 0000000000..69e0f41cd6 --- /dev/null +++ b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__provider_capabilities__custom_provider_capabilities.snap @@ -0,0 +1,60 @@ +--- +source: core/tests/suite/scenarios_provider_capabilities.rs +expression: "context_snapshot::format_request_history_snapshot(\"Custom provider overrides keep unrestricted search cached and enable V2 compaction.\",\n&requests,\n&ContextSnapshotOptions::default().rewrite_known_segments().include_request_settings(),)" +--- +Scenario: Custom provider overrides keep unrestricted search cached and enable V2 compaction. + +## Window 1 +Settings: + include: ["reasoning.encrypted_content"] + instructions: + model: "gpt-5.5" + parallel_tool_calls: true + prompt_cache_key: "" + reasoning: {"effort":"medium"} + store: false + stream: true + text: {"verbosity":"low"} + tool_choice: "auto" + tools (6; hash=3b8fa9a9ca9788e3): + - function/exec_command: Runs a command in a PTY, returning output or a session ID for ongoing interaction.; args=[cmd, justification, login, max_output_tokens, prefix_rule, sandbox_permissions, shell, tty, workdir, yield_time_ms]; hash=c04108e51a8548c5 + - function/write_stdin: Writes characters to an existing unified exec session and returns recent output.; args=[chars, max_output_tokens, session_id, yield_time_ms]; hash=7fb62d8a3e2df4e2 + - function/request_user_input: Request user input for one to three short questions and wait for the response. This tool is only ...; args=[questions]; hash=64a729b25c424fc6 + - function/view_image: View a local image file from the filesystem when visual inspection is needed. Use this for images...; args=[detail, path]; hash=3b766d5bdec5d50e + - namespace/collaboration: Tools for spawning and managing sub-agents.; hash=3b8ac3e34b9c55bb + - function/followup_task + - function/interrupt_agent + - function/list_agents + - function/send_message + - function/spawn_agent + - function/wait_agent + - web_search; hash=f103e327c6870a46 +-- request 1 (turn) -- +00:message/developer: + +01:message/developer: + You are `/root`, the primary agent in a team of agents collaborating to fulfill the user's goals. + + At the start of your turn, you are the active agent. + You can spawn sub-agents to handle subtasks, and those sub-agents can spawn their own sub-agents. + All agents in the team, including the agents that you can assign tasks to, are equally intelligent and capable, and have access to the same set of tools. + + You can use `spawn_agent` to create a new agent, `followup_task` to give an existing agent a new task an...message to a running agent without triggering a turn. [hash=7d40ff8b58977202] + Child agents can also spawn their own sub-agents. + + All agents share the same directory. In detail: + - All agents have access to the same container and filesystem as you. + - All agents use the same current working directory. + - As a result, edits made by one agent are immediately visible to all other agents. + + When calling `wait_agent`, prefer longer waits (minutes) to avoid busy polling. + + There are 4 available concurrency slots, meaning that up to 4 agents can be active at once, including you. +02:message/developer: + +03:message/user: + +04:message/user: + Check the configuration. +-- request 2 (compaction) -- +05:compaction_trigger diff --git a/codex-rs/core/tests/suite/stream_error_allows_next_turn.rs b/codex-rs/core/tests/suite/stream_error_allows_next_turn.rs index ea0a818b4c..c381b78cda 100644 --- a/codex-rs/core/tests/suite/stream_error_allows_next_turn.rs +++ b/codex-rs/core/tests/suite/stream_error_allows_next_turn.rs @@ -84,6 +84,7 @@ async fn continue_after_stream_error() { requires_openai_auth: false, supports_websockets: false, supports_standalone_web_search: false, + capabilities: None, include_internal_metadata: false, }; diff --git a/codex-rs/core/tests/suite/stream_no_completed.rs b/codex-rs/core/tests/suite/stream_no_completed.rs index f163c27f82..1823275ea4 100644 --- a/codex-rs/core/tests/suite/stream_no_completed.rs +++ b/codex-rs/core/tests/suite/stream_no_completed.rs @@ -70,6 +70,7 @@ async fn retries_on_early_close() { requires_openai_auth: false, supports_websockets: false, supports_standalone_web_search: false, + capabilities: None, include_internal_metadata: false, }; diff --git a/codex-rs/login/src/auth_env_telemetry.rs b/codex-rs/login/src/auth_env_telemetry.rs index 0b6d741537..6ee0c43920 100644 --- a/codex-rs/login/src/auth_env_telemetry.rs +++ b/codex-rs/login/src/auth_env_telemetry.rs @@ -79,6 +79,7 @@ mod tests { requires_openai_auth: false, supports_websockets: false, supports_standalone_web_search: false, + capabilities: None, include_internal_metadata: false, }; diff --git a/codex-rs/model-provider-info/src/capabilities.rs b/codex-rs/model-provider-info/src/capabilities.rs new file mode 100644 index 0000000000..18db46cf66 --- /dev/null +++ b/codex-rs/model-provider-info/src/capabilities.rs @@ -0,0 +1,28 @@ +//! Serializable capability overrides for custom Responses-compatible providers. +//! +//! Unspecified capabilities inherit the provider's existing defaults. Model and +//! turn settings may further restrict the capabilities declared here. + +use schemars::JsonSchema; +use serde::Deserialize; +use serde::Serialize; + +/// Overrides for the optional API features supported by a custom provider. +#[derive(Debug, Clone, Copy, Default, Deserialize, Serialize, PartialEq, Eq, JsonSchema)] +#[schemars(deny_unknown_fields)] +pub struct ModelProviderCapabilities { + /// Whether hosted web search may access the live web. + pub external_web_access: Option, + /// Remote context-compaction protocol; omission preserves provider defaults. + pub remote_compaction: Option, +} + +/// Remote context-compaction protocols supported by a model provider. +#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum RemoteCompactionSupport { + /// Use local compaction instead of a provider compaction protocol. + Unsupported, + /// Send `compaction_trigger` items over the Responses endpoint. + V2, +} diff --git a/codex-rs/model-provider-info/src/lib.rs b/codex-rs/model-provider-info/src/lib.rs index 5d5b1f92e5..f020d34c25 100644 --- a/codex-rs/model-provider-info/src/lib.rs +++ b/codex-rs/model-provider-info/src/lib.rs @@ -31,7 +31,10 @@ use std::sync::PoisonError; use std::sync::RwLock; use std::time::Duration; +mod capabilities; mod gateway_oauth; +pub use capabilities::ModelProviderCapabilities; +pub use capabilities::RemoteCompactionSupport; pub use gateway_oauth::GatewayOAuthConfig; pub use gateway_oauth::GatewayOAuthDelivery; @@ -161,6 +164,9 @@ pub struct ModelProviderInfo { /// Which wire protocol this provider expects. #[serde(default)] pub wire_api: WireApi, + /// Optional API capability overrides for a custom Responses-compatible provider. + /// Unspecified capabilities retain their existing provider defaults. + pub capabilities: Option, /// Optional query parameters to append to the base URL. pub query_params: Option>, /// Additional HTTP headers to include in requests to this provider where @@ -554,6 +560,7 @@ other non-default provider fields are not supported" requires_openai_auth: true, supports_websockets: true, supports_standalone_web_search: true, + capabilities: None, include_internal_metadata: true, } } @@ -593,6 +600,7 @@ other non-default provider fields are not supported" requires_openai_auth: false, supports_websockets: false, supports_standalone_web_search: false, + capabilities: None, include_internal_metadata: false, } } @@ -766,6 +774,7 @@ pub fn create_oss_provider_with_base_url(base_url: &str, wire_api: WireApi) -> M requires_openai_auth: false, supports_websockets: false, supports_standalone_web_search: false, + capabilities: None, include_internal_metadata: false, } } diff --git a/codex-rs/model-provider-info/src/model_provider_info_tests.rs b/codex-rs/model-provider-info/src/model_provider_info_tests.rs index 3cbd8b2bac..9c15c54b70 100644 --- a/codex-rs/model-provider-info/src/model_provider_info_tests.rs +++ b/codex-rs/model-provider-info/src/model_provider_info_tests.rs @@ -96,6 +96,7 @@ base_url = "http://localhost:11434/v1" requires_openai_auth: false, supports_websockets: false, supports_standalone_web_search: false, + capabilities: None, include_internal_metadata: false, }; @@ -134,6 +135,7 @@ query_params = { api-version = "2025-04-01-preview" } requires_openai_auth: false, supports_websockets: false, supports_standalone_web_search: false, + capabilities: None, include_internal_metadata: false, }; @@ -176,6 +178,7 @@ supports_standalone_web_search = true requires_openai_auth: false, supports_websockets: false, supports_standalone_web_search: true, + capabilities: None, include_internal_metadata: false, }; @@ -365,6 +368,7 @@ fn test_create_amazon_bedrock_provider() { requires_openai_auth: false, supports_websockets: false, supports_standalone_web_search: false, + capabilities: None, include_internal_metadata: false, } ); diff --git a/codex-rs/model-provider/src/amazon_bedrock/mod.rs b/codex-rs/model-provider/src/amazon_bedrock/mod.rs index caf47686f3..21ab79ce06 100644 --- a/codex-rs/model-provider/src/amazon_bedrock/mod.rs +++ b/codex-rs/model-provider/src/amazon_bedrock/mod.rs @@ -30,6 +30,8 @@ use codex_protocol::error::CodexErr; use codex_protocol::error::Result; use codex_protocol::openai_models::ModelsResponse; +use crate::ProviderCapabilities; +use crate::RemoteCompactionSupport; use crate::auth::auth_manager_for_provider; use crate::auth::resolve_provider_auth as resolve_configured_provider_auth; use crate::provider::ModelProvider; @@ -37,9 +39,7 @@ use crate::provider::ModelProviderFuture; use crate::provider::ProviderAccountResult; use crate::provider::ProviderAccountState; use crate::provider::ProviderAuthRecoveryMessages; -use crate::provider::ProviderCapabilities; use crate::provider::ProviderUnauthorizedRecovery; -use crate::provider::RemoteCompactionSupport; use crate::shared_state::process_shared_state; use auth::resolve_provider_auth as resolve_bedrock_provider_auth; pub(crate) use auth_refresh::AwsAuthRecovery; diff --git a/codex-rs/model-provider/src/capabilities.rs b/codex-rs/model-provider/src/capabilities.rs new file mode 100644 index 0000000000..f8e8891fb0 --- /dev/null +++ b/codex-rs/model-provider/src/capabilities.rs @@ -0,0 +1,57 @@ +//! Provider capability defaults and resolution of custom provider overrides. + +use codex_api::is_azure_responses_provider; +use codex_model_provider_info::ModelProviderInfo; +use codex_model_provider_info::RemoteCompactionSupport; + +/// Optional provider-backed features that Codex may expose at runtime. +/// +/// These capabilities are a provider-owned upper bound. Model and turn settings +/// can further restrict the functionality exposed to the model. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ProviderCapabilities { + pub image_generation: bool, + pub web_search: bool, + pub external_web_access: bool, + pub remote_compaction: RemoteCompactionSupport, +} + +impl Default for ProviderCapabilities { + fn default() -> Self { + Self { + image_generation: true, + web_search: true, + external_web_access: true, + remote_compaction: RemoteCompactionSupport::Unsupported, + } + } +} + +impl ProviderCapabilities { + pub(crate) fn from_config(info: &ModelProviderInfo) -> Self { + let defaults = Self { + remote_compaction: if info.is_openai() + || is_azure_responses_provider(&info.name, info.base_url.as_deref()) + { + RemoteCompactionSupport::V2 + } else { + RemoteCompactionSupport::Unsupported + }, + ..Self::default() + }; + let overrides = info.capabilities.unwrap_or_default(); + Self { + external_web_access: overrides + .external_web_access + .unwrap_or(defaults.external_web_access), + remote_compaction: overrides + .remote_compaction + .unwrap_or(defaults.remote_compaction), + ..defaults + } + } +} + +#[cfg(test)] +#[path = "capabilities_tests.rs"] +mod tests; diff --git a/codex-rs/model-provider/src/capabilities_tests.rs b/codex-rs/model-provider/src/capabilities_tests.rs new file mode 100644 index 0000000000..c22c17f1c7 --- /dev/null +++ b/codex-rs/model-provider/src/capabilities_tests.rs @@ -0,0 +1,35 @@ +use super::*; +use crate::create_model_provider; +use codex_model_provider_info::ModelProviderCapabilities; +use pretty_assertions::assert_eq; + +#[test] +fn capability_overrides_preserve_unspecified_provider_defaults() { + for info in [ + ModelProviderInfo::default(), + ModelProviderInfo::create_openai_provider(/*base_url*/ None), + ModelProviderInfo { + name: "Azure".to_string(), + ..Default::default() + }, + ] { + let defaults = create_model_provider(info.clone(), /*auth_manager*/ None).capabilities(); + let provider = create_model_provider( + ModelProviderInfo { + capabilities: Some(ModelProviderCapabilities { + external_web_access: Some(false), + ..Default::default() + }), + ..info + }, + /*auth_manager*/ None, + ); + assert_eq!( + provider.capabilities(), + ProviderCapabilities { + external_web_access: false, + ..defaults + }, + ); + } +} diff --git a/codex-rs/model-provider/src/lib.rs b/codex-rs/model-provider/src/lib.rs index 825bd669b0..9c818af7dd 100644 --- a/codex-rs/model-provider/src/lib.rs +++ b/codex-rs/model-provider/src/lib.rs @@ -1,6 +1,7 @@ mod amazon_bedrock; mod auth; mod bearer_auth_provider; +mod capabilities; mod combined_auth; mod models_endpoint; mod models_identity; @@ -24,9 +25,11 @@ pub use auth::auth_provider_from_auth_manager; pub use auth::unauthenticated_auth_provider; pub use bearer_auth_provider::BearerAuthProvider; pub use bearer_auth_provider::BearerAuthProvider as CoreAuthProvider; +pub use capabilities::ProviderCapabilities; pub use codex_model_provider_info::AMAZON_BEDROCK_PROVIDER_ID; pub use codex_model_provider_info::AMAZON_BEDROCK_RUNTIME_PROVIDER_ID; pub use codex_model_provider_info::CHATGPT_CODEX_BASE_URL; +pub use codex_model_provider_info::RemoteCompactionSupport; pub use codex_protocol::account::ProviderAccount; pub use provider::ModelProvider; pub use provider::ModelProviderFuture; @@ -34,9 +37,7 @@ pub use provider::ProviderAccountError; pub use provider::ProviderAccountResult; pub use provider::ProviderAccountState; pub use provider::ProviderAuthRecoveryMessages; -pub use provider::ProviderCapabilities; pub use provider::ProviderUnauthorizedRecovery; -pub use provider::RemoteCompactionSupport; pub use provider::SharedModelProvider; pub use provider::create_model_provider; diff --git a/codex-rs/model-provider/src/provider.rs b/codex-rs/model-provider/src/provider.rs index cfe780a2f5..8d93a39de1 100644 --- a/codex-rs/model-provider/src/provider.rs +++ b/codex-rs/model-provider/src/provider.rs @@ -8,7 +8,6 @@ use codex_api::ApiError; use codex_api::Provider; use codex_api::SharedAuthProvider; use codex_api::TransportError; -use codex_api::is_azure_responses_provider; use codex_login::AuthManager; use codex_login::CodexAuth; use codex_login::GatewayAuthManager; @@ -23,6 +22,9 @@ use codex_protocol::account::ProviderAccount; use codex_protocol::error::CodexErr; use codex_protocol::openai_models::ModelsResponse; +use crate::ProviderCapabilities; +#[cfg(test)] +use crate::RemoteCompactionSupport; use crate::ResolvedResponsesProvider; use crate::amazon_bedrock::AmazonBedrockModelProvider; use crate::auth::ProviderAuthScope; @@ -34,39 +36,6 @@ use crate::combined_auth::compose_auth; use crate::models_endpoint::OpenAiModelsEndpoint; use crate::workspace_routing::WorkspaceRoutingContext; -/// Remote context-compaction protocols supported by a model provider. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum RemoteCompactionSupport { - /// The provider does not support remote compaction. - Unsupported, - /// The provider supports `compaction_trigger` items over the Responses endpoint. - V2, -} - -/// Optional provider-backed features that Codex may expose at runtime. -/// -/// These capabilities are a provider-owned upper bound. Callers can disable -/// more functionality through normal config, but should not expose a feature -/// that the active provider marks unsupported here. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct ProviderCapabilities { - pub image_generation: bool, - pub web_search: bool, - pub external_web_access: bool, - pub remote_compaction: RemoteCompactionSupport, -} - -impl Default for ProviderCapabilities { - fn default() -> Self { - Self { - image_generation: true, - web_search: true, - external_web_access: true, - remote_compaction: RemoteCompactionSupport::Unsupported, - } - } -} - /// Current app-visible account state for a model provider. #[derive(Debug, Clone, PartialEq, Eq)] pub struct ProviderAccountState { @@ -457,18 +426,7 @@ impl ModelProvider for ConfiguredModelProvider { } fn capabilities(&self) -> ProviderCapabilities { - let remote_compaction = if self.info.is_openai() - || is_azure_responses_provider(&self.info.name, self.info.base_url.as_deref()) - { - RemoteCompactionSupport::V2 - } else { - RemoteCompactionSupport::Unsupported - }; - - ProviderCapabilities { - remote_compaction, - ..ProviderCapabilities::default() - } + ProviderCapabilities::from_config(&self.info) } fn approval_review_preferred_model(&self) -> &'static str { @@ -696,6 +654,7 @@ mod tests { requires_openai_auth: false, supports_websockets: false, supports_standalone_web_search: false, + capabilities: None, include_internal_metadata: false, } }