mirror of
https://github.com/openai/codex.git
synced 2026-10-19 08:16:48 +00:00
Add capability overrides for custom model providers (#50459)
## What changed Allow Responses-compatible providers to configure live web access and remote compaction under `model_providers.<id>.capabilities`: ```toml [model_providers.custom.capabilities] external_web_access = false remote_compaction = "v2" ``` `external_web_access = false` keeps hosted web search from accessing the live web. `remote_compaction` accepts `v2` to send `compaction_trigger` items through the Responses endpoint, or `unsupported` to use local compaction. Omitted capabilities retain existing provider defaults. ## Testing Add unit coverage for preserving unspecified defaults and a request-snapshot scenario verifying cached web search and V2 compaction for a custom provider. GitOrigin-RevId: fbcc4870aa51c112af575d711679a3941d1e7878
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -316,6 +316,7 @@ mod tests {
|
||||
requires_openai_auth: false,
|
||||
supports_websockets: true,
|
||||
supports_standalone_web_search: true,
|
||||
capabilities: None,
|
||||
include_internal_metadata: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
+60
@@ -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_INSTRUCTIONS>
|
||||
model: "gpt-5.5"
|
||||
parallel_tool_calls: true
|
||||
prompt_cache_key: "<PROMPT_CACHE_KEY 1>"
|
||||
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:
|
||||
<PERMISSIONS_INSTRUCTIONS>
|
||||
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.
|
||||
<OMITTED 14 LINES; ~204 TOKENS; hash=f7be62db50c2fa5f>
|
||||
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:
|
||||
<MULTI_AGENT_MODE>
|
||||
03:message/user:
|
||||
<ENVIRONMENT_CONTEXT>
|
||||
04:message/user:
|
||||
Check the configuration.
|
||||
-- request 2 (compaction) --
|
||||
05:compaction_trigger
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
@@ -79,6 +79,7 @@ mod tests {
|
||||
requires_openai_auth: false,
|
||||
supports_websockets: false,
|
||||
supports_standalone_web_search: false,
|
||||
capabilities: None,
|
||||
include_internal_metadata: false,
|
||||
};
|
||||
|
||||
|
||||
@@ -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<bool>,
|
||||
/// Remote context-compaction protocol; omission preserves provider defaults.
|
||||
pub remote_compaction: Option<RemoteCompactionSupport>,
|
||||
}
|
||||
|
||||
/// 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,
|
||||
}
|
||||
@@ -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<ModelProviderCapabilities>,
|
||||
/// Optional query parameters to append to the base URL.
|
||||
pub query_params: Option<HashMap<String, RedactedString>>,
|
||||
/// 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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -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
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user