mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
[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:
1 parent
251b2412b2
commit
5577a9e148
19 files changed
+329
-16
No files matched your search
@@ -8,7 +8,9 @@ use std::str::FromStr;
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Deserializer;
|
||||
use serde::Serialize;
|
||||
use serde::de::DeserializeOwned;
|
||||
use strum::IntoEnumIterator;
|
||||
use strum_macros::Display;
|
||||
use strum_macros::EnumIter;
|
||||
@@ -227,6 +229,25 @@ pub enum TruncationMode {
|
||||
Tokens,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, TS, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ToolMode {
|
||||
Direct,
|
||||
CodeMode,
|
||||
CodeModeOnly,
|
||||
}
|
||||
|
||||
fn deserialize_optional_model_selector<'de, D, T>(deserializer: D) -> Result<Option<T>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
let Some(value) = Option::<String>::deserialize(deserializer)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
Ok(serde_json::from_value(serde_json::Value::String(value)).ok())
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, TS, JsonSchema)]
|
||||
pub struct TruncationPolicyConfig {
|
||||
pub mode: TruncationMode,
|
||||
@@ -318,6 +339,12 @@ pub struct ModelInfo {
|
||||
pub used_fallback_model_metadata: bool,
|
||||
#[serde(default)]
|
||||
pub supports_search_tool: bool,
|
||||
#[serde(
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none",
|
||||
deserialize_with = "deserialize_optional_model_selector"
|
||||
)]
|
||||
pub tool_mode: Option<ToolMode>,
|
||||
}
|
||||
|
||||
impl ModelInfo {
|
||||
@@ -612,6 +639,7 @@ mod tests {
|
||||
input_modalities: default_input_modalities(),
|
||||
used_fallback_model_metadata: false,
|
||||
supports_search_tool: false,
|
||||
tool_mode: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -829,6 +857,44 @@ mod tests {
|
||||
assert!(!model.supports_image_detail_original);
|
||||
assert_eq!(model.web_search_tool_type, WebSearchToolType::Text);
|
||||
assert!(!model.supports_search_tool);
|
||||
assert_eq!(model.tool_mode, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn model_info_deserializes_known_tool_mode() {
|
||||
let mut value =
|
||||
serde_json::to_value(test_model(/*spec*/ None)).expect("serialize test model");
|
||||
let object = value
|
||||
.as_object_mut()
|
||||
.expect("model info should be an object");
|
||||
object.insert(
|
||||
"tool_mode".to_string(),
|
||||
serde_json::Value::String("code_mode_only".to_string()),
|
||||
);
|
||||
let model = serde_json::from_value::<ModelInfo>(value).expect("deserialize model info");
|
||||
|
||||
assert_eq!(model.tool_mode, Some(ToolMode::CodeModeOnly));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn model_info_treats_unknown_tool_mode_as_omitted() {
|
||||
let mut value =
|
||||
serde_json::to_value(test_model(/*spec*/ None)).expect("serialize test model");
|
||||
let object = value
|
||||
.as_object_mut()
|
||||
.expect("model info should be an object");
|
||||
object.insert(
|
||||
"tool_mode".to_string(),
|
||||
serde_json::Value::String("future_tool_mode".to_string()),
|
||||
);
|
||||
let model = serde_json::from_value::<ModelInfo>(value).expect("deserialize model info");
|
||||
|
||||
assert_eq!(model.tool_mode, None);
|
||||
let serialized = serde_json::to_value(model).expect("serialize model info");
|
||||
let object = serialized
|
||||
.as_object()
|
||||
.expect("model info should be an object");
|
||||
assert!(!object.contains_key("tool_mode"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in new issue
Block a user