[codex] Add model tool mode selector (#25031)

## Why
Some models need to select their code-execution behavior through model
catalog metadata. Models without that metadata must continue to follow
the existing `CodeMode` and `CodeModeOnly` feature flags, including when
a newer server sends an enum value this client does not recognize.

## What changed
- add optional `ModelInfo.tool_mode` metadata with `direct`,
`code_mode`, and `code_mode_only`
- treat omitted and unknown wire values as `None`
- resolve `None` from the existing feature flags
- carry the resolved `ToolMode` directly on `TurnContext`, outside
`Config`
- use the resolved value for turn creation, model switches, review
turns, tool planning, and code execution

## Coverage
- add protocol coverage for omitted, known, and unknown enum values
- add focused coverage for flag fallback and explicit metadata
overriding feature flags
- add core integration coverage that fetches remote model metadata
through `/v1/models` and verifies the outbound `/responses` tools for
explicit `direct` and `code_mode_only` selectors

## Stack
- followed by #25032
This commit is contained in:
Ahmed Ibrahim authored and GitHub committed 2026-05-29 09:05:05 -07:00
1 parent 251b2412b2
commit 5577a9e148
19 files changed
+329 -16

No files matched your search

+22
View File
@@ -6,6 +6,7 @@ use codex_model_provider::SharedModelProvider;
use codex_model_provider::create_model_provider;
use codex_protocol::SessionId;
use codex_protocol::models::AdditionalPermissionProfile;
use codex_protocol::openai_models::ToolMode;
use codex_protocol::protocol::ThreadSource;
use codex_protocol::protocol::TurnEnvironmentSelection;
use codex_sandboxing::compatibility_sandbox_policy_for_permission_profile;
@@ -55,6 +56,7 @@ pub struct TurnContext {
pub config: Arc<Config>,
pub(crate) auth_manager: Option<Arc<AuthManager>>,
pub(crate) model_info: ModelInfo,
pub(crate) tool_mode: ToolMode,
pub(crate) session_telemetry: SessionTelemetry,
pub(crate) provider: SharedModelProvider,
pub(crate) reasoning_effort: Option<ReasoningEffortConfig>,
@@ -172,6 +174,15 @@ impl TurnContext {
let model_info = models_manager
.get_model_info(model.as_str(), &config.to_models_manager_config())
.await;
let tool_mode = model_info.tool_mode.unwrap_or_else(|| {
if config.features.enabled(Feature::CodeModeOnly) {
ToolMode::CodeModeOnly
} else if config.features.enabled(Feature::CodeMode) {
ToolMode::CodeMode
} else {
ToolMode::Direct
}
});
let truncation_policy = model_info.truncation_policy.into();
let supported_reasoning_levels = model_info
.supported_reasoning_levels
@@ -212,6 +223,7 @@ impl TurnContext {
config: Arc::new(config),
auth_manager: self.auth_manager.clone(),
model_info: model_info.clone(),
tool_mode,
session_telemetry: self
.session_telemetry
.clone()
@@ -475,6 +487,15 @@ impl Session {
);
let mut per_turn_config = per_turn_config;
let tool_mode = model_info.tool_mode.unwrap_or_else(|| {
if per_turn_config.features.enabled(Feature::CodeModeOnly) {
ToolMode::CodeModeOnly
} else if per_turn_config.features.enabled(Feature::CodeMode) {
ToolMode::CodeMode
} else {
ToolMode::Direct
}
});
per_turn_config.service_tier = get_service_tier(
per_turn_config.service_tier,
per_turn_config.features.enabled(Feature::FastMode),
@@ -501,6 +522,7 @@ impl Session {
config: per_turn_config.clone(),
auth_manager: auth_manager_for_context,
model_info: model_info.clone(),
tool_mode,
session_telemetry: session_telemetry_for_context,
provider: provider_for_context,
reasoning_effort,