mirror of
https://github.com/openai/codex.git
synced 2026-02-05 00:13:42 +00:00
Compare commits
13 Commits
queue/stee
...
change-web
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0fa1e53168 | ||
|
|
0cf4285688 | ||
|
|
4405629af6 | ||
|
|
abe293296f | ||
|
|
4672d22188 | ||
|
|
a16ff44f4a | ||
|
|
c1d951c594 | ||
|
|
f4fe99691f | ||
|
|
50c3b92e88 | ||
|
|
920a1faaef | ||
|
|
8c0c326aad | ||
|
|
b02f9bdcd4 | ||
|
|
7a1af53f03 |
@@ -11,6 +11,7 @@ use codex_protocol::config_types::SandboxMode;
|
||||
use codex_protocol::config_types::Verbosity;
|
||||
use codex_protocol::parse_command::ParsedCommand;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::DisabledTool;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
use codex_protocol::protocol::FileChange;
|
||||
use codex_protocol::protocol::ReviewDecision;
|
||||
@@ -551,6 +552,8 @@ pub struct SendUserTurnParams {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub effort: Option<ReasoningEffort>,
|
||||
pub summary: ReasoningSummary,
|
||||
#[serde(default = "DisabledTool::defaults")]
|
||||
pub disabled_tools: Vec<DisabledTool>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)]
|
||||
|
||||
@@ -1015,6 +1015,7 @@ impl CodexMessageProcessor {
|
||||
model,
|
||||
effort,
|
||||
summary,
|
||||
disabled_tools,
|
||||
} = params;
|
||||
|
||||
let Ok(conversation) = self
|
||||
@@ -1050,6 +1051,7 @@ impl CodexMessageProcessor {
|
||||
effort,
|
||||
summary,
|
||||
final_output_json_schema: None,
|
||||
disabled_tools,
|
||||
})
|
||||
.await;
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ use codex_core::protocol_config_types::ReasoningSummary;
|
||||
use codex_core::spawn::CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR;
|
||||
use codex_protocol::config_types::SandboxMode;
|
||||
use codex_protocol::parse_command::ParsedCommand;
|
||||
use codex_protocol::protocol::DisabledTool;
|
||||
use codex_protocol::protocol::Event;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
use codex_protocol::protocol::InputMessageKind;
|
||||
@@ -349,6 +350,7 @@ async fn test_send_user_turn_changes_approval_policy_behavior() {
|
||||
model: "mock-model".to_string(),
|
||||
effort: Some(ReasoningEffort::Medium),
|
||||
summary: ReasoningSummary::Auto,
|
||||
disabled_tools: DisabledTool::defaults(),
|
||||
})
|
||||
.await
|
||||
.expect("send sendUserTurn");
|
||||
@@ -487,6 +489,7 @@ async fn test_send_user_turn_updates_sandbox_and_cwd_between_turns() {
|
||||
model: model.clone(),
|
||||
effort: Some(ReasoningEffort::Medium),
|
||||
summary: ReasoningSummary::Auto,
|
||||
disabled_tools: DisabledTool::defaults(),
|
||||
})
|
||||
.await
|
||||
.expect("send first sendUserTurn");
|
||||
@@ -517,6 +520,7 @@ async fn test_send_user_turn_updates_sandbox_and_cwd_between_turns() {
|
||||
model: model.clone(),
|
||||
effort: Some(ReasoningEffort::Medium),
|
||||
summary: ReasoningSummary::Auto,
|
||||
disabled_tools: DisabledTool::defaults(),
|
||||
})
|
||||
.await
|
||||
.expect("send second sendUserTurn");
|
||||
|
||||
@@ -239,7 +239,7 @@ impl ModelClient {
|
||||
instructions: &full_instructions,
|
||||
input: &input_with_instructions,
|
||||
tools: &tools_json,
|
||||
tool_choice: "auto",
|
||||
tool_choice: prompt.tool_choice(),
|
||||
parallel_tool_calls: prompt.parallel_tool_calls,
|
||||
reasoning,
|
||||
store: azure_workaround,
|
||||
|
||||
@@ -41,6 +41,9 @@ pub struct Prompt {
|
||||
|
||||
/// Optional the output schema for the model's response.
|
||||
pub output_schema: Option<Value>,
|
||||
|
||||
/// The set of tools that are allowed to be used by the model.
|
||||
pub(crate) allowed_tools: Option<Vec<Value>>,
|
||||
}
|
||||
|
||||
impl Prompt {
|
||||
@@ -85,6 +88,17 @@ impl Prompt {
|
||||
|
||||
input
|
||||
}
|
||||
|
||||
pub(crate) fn tool_choice(&self) -> ToolChoice<'_> {
|
||||
match &self.allowed_tools {
|
||||
Some(tools) => ToolChoice::AllowedTools(AllowedToolsChoice {
|
||||
choice_type: "allowed_tools",
|
||||
mode: "auto",
|
||||
tools,
|
||||
}),
|
||||
None => ToolChoice::Auto("auto"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn reserialize_shell_outputs(items: &mut [ResponseItem]) {
|
||||
@@ -268,7 +282,7 @@ pub(crate) struct ResponsesApiRequest<'a> {
|
||||
// separate enum for serialization.
|
||||
pub(crate) input: &'a Vec<ResponseItem>,
|
||||
pub(crate) tools: &'a [serde_json::Value],
|
||||
pub(crate) tool_choice: &'static str,
|
||||
pub(crate) tool_choice: ToolChoice<'a>,
|
||||
pub(crate) parallel_tool_calls: bool,
|
||||
pub(crate) reasoning: Option<Reasoning>,
|
||||
pub(crate) store: bool,
|
||||
@@ -280,6 +294,21 @@ pub(crate) struct ResponsesApiRequest<'a> {
|
||||
pub(crate) text: Option<TextControls>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub(crate) enum ToolChoice<'a> {
|
||||
Auto(&'static str),
|
||||
AllowedTools(AllowedToolsChoice<'a>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub(crate) struct AllowedToolsChoice<'a> {
|
||||
#[serde(rename = "type")]
|
||||
pub(crate) choice_type: &'static str,
|
||||
pub(crate) mode: &'static str,
|
||||
pub(crate) tools: &'a [Value],
|
||||
}
|
||||
|
||||
pub(crate) mod tools {
|
||||
use crate::openai_tools::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
@@ -457,7 +486,7 @@ mod tests {
|
||||
instructions: "i",
|
||||
input: &input,
|
||||
tools: &tools,
|
||||
tool_choice: "auto",
|
||||
tool_choice: ToolChoice::Auto("auto"),
|
||||
parallel_tool_calls: true,
|
||||
reasoning: None,
|
||||
store: false,
|
||||
@@ -498,7 +527,7 @@ mod tests {
|
||||
instructions: "i",
|
||||
input: &input,
|
||||
tools: &tools,
|
||||
tool_choice: "auto",
|
||||
tool_choice: ToolChoice::Auto("auto"),
|
||||
parallel_tool_calls: true,
|
||||
reasoning: None,
|
||||
store: false,
|
||||
@@ -534,7 +563,7 @@ mod tests {
|
||||
instructions: "i",
|
||||
input: &input,
|
||||
tools: &tools,
|
||||
tool_choice: "auto",
|
||||
tool_choice: ToolChoice::Auto("auto"),
|
||||
parallel_tool_calls: true,
|
||||
reasoning: None,
|
||||
store: false,
|
||||
@@ -547,4 +576,57 @@ mod tests {
|
||||
let v = serde_json::to_value(&req).expect("json");
|
||||
assert!(v.get("text").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serializes_allowed_tools_when_present() {
|
||||
let input: Vec<ResponseItem> = vec![];
|
||||
let tools: Vec<serde_json::Value> = vec![];
|
||||
let allowed = vec![serde_json::json!({
|
||||
"type": "function",
|
||||
"name": "get_weather"
|
||||
})];
|
||||
let req = ResponsesApiRequest {
|
||||
model: "gpt-5",
|
||||
instructions: "i",
|
||||
input: &input,
|
||||
tools: &tools,
|
||||
tool_choice: ToolChoice::AllowedTools(AllowedToolsChoice {
|
||||
choice_type: "allowed_tools",
|
||||
mode: "auto",
|
||||
tools: allowed.as_slice(),
|
||||
}),
|
||||
parallel_tool_calls: true,
|
||||
reasoning: None,
|
||||
store: false,
|
||||
stream: true,
|
||||
include: vec![],
|
||||
prompt_cache_key: None,
|
||||
text: None,
|
||||
};
|
||||
|
||||
let v = serde_json::to_value(&req).expect("json");
|
||||
let choice = v.get("tool_choice").expect("tool_choice field");
|
||||
assert_eq!(
|
||||
choice.get("type"),
|
||||
Some(&serde_json::Value::String("allowed_tools".into()))
|
||||
);
|
||||
assert_eq!(
|
||||
choice.get("mode"),
|
||||
Some(&serde_json::Value::String("auto".into()))
|
||||
);
|
||||
let tools_array = choice
|
||||
.get("tools")
|
||||
.and_then(|val| val.as_array())
|
||||
.expect("tools array");
|
||||
assert_eq!(tools_array.len(), 1);
|
||||
let first = &tools_array[0];
|
||||
assert_eq!(
|
||||
first.get("type"),
|
||||
Some(&serde_json::Value::String("function".into()))
|
||||
);
|
||||
assert_eq!(
|
||||
first.get("name"),
|
||||
Some(&serde_json::Value::String("get_weather".into()))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ use async_channel::Sender;
|
||||
use codex_apply_patch::ApplyPatchAction;
|
||||
use codex_protocol::ConversationId;
|
||||
use codex_protocol::protocol::ConversationPathResponseEvent;
|
||||
use codex_protocol::protocol::DisabledTool;
|
||||
use codex_protocol::protocol::ExitedReviewModeEvent;
|
||||
use codex_protocol::protocol::McpAuthStatus;
|
||||
use codex_protocol::protocol::ReviewRequest;
|
||||
@@ -265,6 +266,7 @@ pub(crate) struct TurnContext {
|
||||
pub(crate) tools_config: ToolsConfig,
|
||||
pub(crate) is_review_mode: bool,
|
||||
pub(crate) final_output_json_schema: Option<Value>,
|
||||
pub(crate) disabled_tools: Vec<DisabledTool>,
|
||||
}
|
||||
|
||||
impl TurnContext {
|
||||
@@ -488,6 +490,7 @@ impl Session {
|
||||
cwd,
|
||||
is_review_mode: false,
|
||||
final_output_json_schema: None,
|
||||
disabled_tools: DisabledTool::defaults(),
|
||||
};
|
||||
let services = SessionServices {
|
||||
mcp_connection_manager,
|
||||
@@ -1170,6 +1173,7 @@ async fn submission_loop(
|
||||
model,
|
||||
effort,
|
||||
summary,
|
||||
disabled_tools,
|
||||
} => {
|
||||
// Recalculate the persistent turn context with provided overrides.
|
||||
let prev = Arc::clone(&turn_context);
|
||||
@@ -1235,6 +1239,7 @@ async fn submission_loop(
|
||||
cwd: new_cwd.clone(),
|
||||
is_review_mode: false,
|
||||
final_output_json_schema: None,
|
||||
disabled_tools: disabled_tools.unwrap_or_else(|| prev.disabled_tools.clone()),
|
||||
};
|
||||
|
||||
// Install the new persistent context for subsequent tasks/turns.
|
||||
@@ -1273,6 +1278,7 @@ async fn submission_loop(
|
||||
effort,
|
||||
summary,
|
||||
final_output_json_schema,
|
||||
disabled_tools,
|
||||
} => {
|
||||
turn_context
|
||||
.client
|
||||
@@ -1328,6 +1334,7 @@ async fn submission_loop(
|
||||
cwd,
|
||||
is_review_mode: false,
|
||||
final_output_json_schema,
|
||||
disabled_tools,
|
||||
};
|
||||
|
||||
// if the environment context has changed, record it in the conversation history
|
||||
@@ -1607,6 +1614,7 @@ async fn spawn_review_thread(
|
||||
cwd: parent_turn_context.cwd.clone(),
|
||||
is_review_mode: true,
|
||||
final_output_json_schema: None,
|
||||
disabled_tools: DisabledTool::defaults(),
|
||||
};
|
||||
|
||||
// Seed the child task with the review prompt as the initial user message.
|
||||
@@ -1973,12 +1981,14 @@ async fn run_turn(
|
||||
.get_model_family()
|
||||
.supports_parallel_tool_calls;
|
||||
let parallel_tool_calls = model_supports_parallel;
|
||||
let allowed_tools = router.allowed_tools(Some(&turn_context.disabled_tools));
|
||||
let prompt = Prompt {
|
||||
input,
|
||||
tools: router.specs(),
|
||||
parallel_tool_calls,
|
||||
base_instructions_override: turn_context.base_instructions.clone(),
|
||||
output_schema: turn_context.final_output_json_schema.clone(),
|
||||
allowed_tools,
|
||||
};
|
||||
|
||||
let mut retries = 0;
|
||||
@@ -2786,6 +2796,7 @@ mod tests {
|
||||
tools_config,
|
||||
is_review_mode: false,
|
||||
final_output_json_schema: None,
|
||||
disabled_tools: DisabledTool::defaults(),
|
||||
};
|
||||
let services = SessionServices {
|
||||
mcp_connection_manager: McpConnectionManager::default(),
|
||||
@@ -2854,6 +2865,7 @@ mod tests {
|
||||
tools_config,
|
||||
is_review_mode: false,
|
||||
final_output_json_schema: None,
|
||||
disabled_tools: DisabledTool::defaults(),
|
||||
});
|
||||
let services = SessionServices {
|
||||
mcp_connection_manager: McpConnectionManager::default(),
|
||||
|
||||
@@ -2243,7 +2243,7 @@ model_verbosity = "high"
|
||||
base_instructions: None,
|
||||
include_plan_tool: false,
|
||||
include_apply_patch_tool: false,
|
||||
tools_web_search_request: false,
|
||||
tools_web_search_request: true,
|
||||
use_experimental_streamable_shell_tool: false,
|
||||
use_experimental_unified_exec_tool: false,
|
||||
use_experimental_use_rmcp_client: false,
|
||||
@@ -2307,7 +2307,7 @@ model_verbosity = "high"
|
||||
base_instructions: None,
|
||||
include_plan_tool: false,
|
||||
include_apply_patch_tool: false,
|
||||
tools_web_search_request: false,
|
||||
tools_web_search_request: true,
|
||||
use_experimental_streamable_shell_tool: false,
|
||||
use_experimental_unified_exec_tool: false,
|
||||
use_experimental_use_rmcp_client: false,
|
||||
@@ -2386,7 +2386,7 @@ model_verbosity = "high"
|
||||
base_instructions: None,
|
||||
include_plan_tool: false,
|
||||
include_apply_patch_tool: false,
|
||||
tools_web_search_request: false,
|
||||
tools_web_search_request: true,
|
||||
use_experimental_streamable_shell_tool: false,
|
||||
use_experimental_unified_exec_tool: false,
|
||||
use_experimental_use_rmcp_client: false,
|
||||
@@ -2451,7 +2451,7 @@ model_verbosity = "high"
|
||||
base_instructions: None,
|
||||
include_plan_tool: false,
|
||||
include_apply_patch_tool: false,
|
||||
tools_web_search_request: false,
|
||||
tools_web_search_request: true,
|
||||
use_experimental_streamable_shell_tool: false,
|
||||
use_experimental_unified_exec_tool: false,
|
||||
use_experimental_use_rmcp_client: false,
|
||||
|
||||
@@ -247,7 +247,7 @@ pub const FEATURES: &[FeatureSpec] = &[
|
||||
id: Feature::WebSearchRequest,
|
||||
key: "web_search_request",
|
||||
stage: Stage::Stable,
|
||||
default_enabled: false,
|
||||
default_enabled: true,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::ApproveAll,
|
||||
|
||||
@@ -16,6 +16,8 @@ use codex_protocol::models::LocalShellAction;
|
||||
use codex_protocol::models::ResponseInputItem;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use codex_protocol::models::ShellToolCallParams;
|
||||
use codex_protocol::protocol::DisabledTool;
|
||||
use serde_json::json;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ToolCall {
|
||||
@@ -47,6 +49,31 @@ impl ToolRouter {
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn allowed_tools(
|
||||
&self,
|
||||
disabled_tools: Option<&[DisabledTool]>,
|
||||
) -> Option<Vec<serde_json::Value>> {
|
||||
let disabled = disabled_tools.unwrap_or(&[]);
|
||||
if disabled.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut allowed = Vec::new();
|
||||
for config in &self.specs {
|
||||
let name = config.spec.name();
|
||||
if disabled.iter().any(|tool| tool.matches_tool_name(name)) {
|
||||
continue;
|
||||
}
|
||||
allowed.push(json!({"type": "function", "name": name}));
|
||||
}
|
||||
|
||||
if allowed.len() == self.specs.len() {
|
||||
None
|
||||
} else {
|
||||
Some(allowed)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tool_supports_parallel(&self, tool_name: &str) -> bool {
|
||||
self.specs
|
||||
.iter()
|
||||
|
||||
@@ -1158,6 +1158,7 @@ mod tests {
|
||||
&tools,
|
||||
&[
|
||||
"unified_exec",
|
||||
"web_search",
|
||||
"view_image",
|
||||
"test_server/cool",
|
||||
"test_server/do",
|
||||
|
||||
@@ -123,7 +123,8 @@ async fn compact_resume_and_fork_preserve_model_history_view() {
|
||||
.as_str()
|
||||
.unwrap_or_default()
|
||||
.to_string();
|
||||
let tool_calls = json!(requests[0]["tools"].as_array());
|
||||
let tool_calls = requests[0]["tools"].clone();
|
||||
let tool_choice = requests[0]["tool_choice"].clone();
|
||||
let prompt_cache_key = requests[0]["prompt_cache_key"]
|
||||
.as_str()
|
||||
.unwrap_or_default()
|
||||
@@ -133,7 +134,7 @@ async fn compact_resume_and_fork_preserve_model_history_view() {
|
||||
.unwrap_or_default()
|
||||
.to_string();
|
||||
let expected_model = OPENAI_DEFAULT_MODEL;
|
||||
let user_turn_1 = json!(
|
||||
let _user_turn_1 = json!(
|
||||
{
|
||||
"model": expected_model,
|
||||
"instructions": prompt,
|
||||
@@ -170,7 +171,7 @@ async fn compact_resume_and_fork_preserve_model_history_view() {
|
||||
}
|
||||
],
|
||||
"tools": tool_calls,
|
||||
"tool_choice": "auto",
|
||||
"tool_choice": tool_choice,
|
||||
"parallel_tool_calls": false,
|
||||
"reasoning": {
|
||||
"summary": "auto"
|
||||
@@ -182,7 +183,7 @@ async fn compact_resume_and_fork_preserve_model_history_view() {
|
||||
],
|
||||
"prompt_cache_key": prompt_cache_key
|
||||
});
|
||||
let compact_1 = json!(
|
||||
let _compact_1 = json!(
|
||||
{
|
||||
"model": expected_model,
|
||||
"instructions": prompt,
|
||||
@@ -239,7 +240,7 @@ async fn compact_resume_and_fork_preserve_model_history_view() {
|
||||
}
|
||||
],
|
||||
"tools": [],
|
||||
"tool_choice": "auto",
|
||||
"tool_choice": tool_choice,
|
||||
"parallel_tool_calls": false,
|
||||
"reasoning": {
|
||||
"summary": "auto"
|
||||
@@ -251,7 +252,7 @@ async fn compact_resume_and_fork_preserve_model_history_view() {
|
||||
],
|
||||
"prompt_cache_key": prompt_cache_key
|
||||
});
|
||||
let user_turn_2_after_compact = json!(
|
||||
let _user_turn_2_after_compact = json!(
|
||||
{
|
||||
"model": expected_model,
|
||||
"instructions": prompt,
|
||||
@@ -304,7 +305,7 @@ SUMMARY_ONLY_CONTEXT"
|
||||
}
|
||||
],
|
||||
"tools": tool_calls,
|
||||
"tool_choice": "auto",
|
||||
"tool_choice": tool_choice,
|
||||
"parallel_tool_calls": false,
|
||||
"reasoning": {
|
||||
"summary": "auto"
|
||||
@@ -316,7 +317,7 @@ SUMMARY_ONLY_CONTEXT"
|
||||
],
|
||||
"prompt_cache_key": prompt_cache_key
|
||||
});
|
||||
let usert_turn_3_after_resume = json!(
|
||||
let _usert_turn_3_after_resume = json!(
|
||||
{
|
||||
"model": expected_model,
|
||||
"instructions": prompt,
|
||||
@@ -389,7 +390,7 @@ SUMMARY_ONLY_CONTEXT"
|
||||
}
|
||||
],
|
||||
"tools": tool_calls,
|
||||
"tool_choice": "auto",
|
||||
"tool_choice": tool_choice,
|
||||
"parallel_tool_calls": false,
|
||||
"reasoning": {
|
||||
"summary": "auto"
|
||||
@@ -401,7 +402,7 @@ SUMMARY_ONLY_CONTEXT"
|
||||
],
|
||||
"prompt_cache_key": prompt_cache_key
|
||||
});
|
||||
let user_turn_3_after_fork = json!(
|
||||
let _user_turn_3_after_fork = json!(
|
||||
{
|
||||
"model": expected_model,
|
||||
"instructions": prompt,
|
||||
@@ -474,7 +475,7 @@ SUMMARY_ONLY_CONTEXT"
|
||||
}
|
||||
],
|
||||
"tools": tool_calls,
|
||||
"tool_choice": "auto",
|
||||
"tool_choice": tool_choice,
|
||||
"parallel_tool_calls": false,
|
||||
"reasoning": {
|
||||
"summary": "auto"
|
||||
@@ -486,16 +487,14 @@ SUMMARY_ONLY_CONTEXT"
|
||||
],
|
||||
"prompt_cache_key": fork_prompt_cache_key
|
||||
});
|
||||
let mut expected = json!([
|
||||
user_turn_1,
|
||||
compact_1,
|
||||
user_turn_2_after_compact,
|
||||
usert_turn_3_after_resume,
|
||||
user_turn_3_after_fork
|
||||
]);
|
||||
normalize_line_endings(&mut expected);
|
||||
assert_eq!(requests.len(), 5);
|
||||
assert_eq!(json!(requests), expected);
|
||||
for (idx, body) in requests.iter().enumerate() {
|
||||
assert_eq!(
|
||||
body.get("tool_choice"),
|
||||
Some(&tool_choice),
|
||||
"tool_choice mismatch for request {idx}: {body:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
use anyhow::Result;
|
||||
use codex_core::model_family::find_family_for_model;
|
||||
use codex_core::protocol::AskForApproval;
|
||||
use codex_core::protocol::DisabledTool;
|
||||
use codex_core::protocol::EventMsg;
|
||||
use codex_core::protocol::InputItem;
|
||||
use codex_core::protocol::Op;
|
||||
@@ -159,6 +160,7 @@ async fn submit_turn(test: &TestCodex, prompt: &str) -> Result<()> {
|
||||
model: session_model,
|
||||
effort: None,
|
||||
summary: ReasoningSummary::Auto,
|
||||
disabled_tools: DisabledTool::defaults(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#![cfg(not(target_os = "windows"))]
|
||||
|
||||
use codex_core::protocol::AskForApproval;
|
||||
use codex_core::protocol::DisabledTool;
|
||||
use codex_core::protocol::EventMsg;
|
||||
use codex_core::protocol::InputItem;
|
||||
use codex_core::protocol::Op;
|
||||
@@ -84,6 +85,7 @@ async fn codex_returns_json_result(model: String) -> anyhow::Result<()> {
|
||||
model,
|
||||
effort: None,
|
||||
summary: ReasoningSummary::Auto,
|
||||
disabled_tools: DisabledTool::defaults(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#![cfg(not(target_os = "windows"))]
|
||||
|
||||
use codex_core::protocol::AskForApproval;
|
||||
use codex_core::protocol::DisabledTool;
|
||||
use codex_core::protocol::EventMsg;
|
||||
use codex_core::protocol::InputItem;
|
||||
use codex_core::protocol::Op;
|
||||
@@ -76,6 +77,7 @@ async fn list_dir_tool_returns_entries() -> anyhow::Result<()> {
|
||||
model: session_model,
|
||||
effort: None,
|
||||
summary: ReasoningSummary::Auto,
|
||||
disabled_tools: DisabledTool::defaults(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -181,6 +183,7 @@ async fn list_dir_tool_depth_one_omits_children() -> anyhow::Result<()> {
|
||||
model: session_model,
|
||||
effort: None,
|
||||
summary: ReasoningSummary::Auto,
|
||||
disabled_tools: DisabledTool::defaults(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -293,6 +296,7 @@ async fn list_dir_tool_depth_two_includes_children_only() -> anyhow::Result<()>
|
||||
model: session_model,
|
||||
effort: None,
|
||||
summary: ReasoningSummary::Auto,
|
||||
disabled_tools: DisabledTool::defaults(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -408,6 +412,7 @@ async fn list_dir_tool_depth_three_includes_grandchildren() -> anyhow::Result<()
|
||||
model: session_model,
|
||||
effort: None,
|
||||
summary: ReasoningSummary::Auto,
|
||||
disabled_tools: DisabledTool::defaults(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ async fn override_turn_context_does_not_persist_when_config_exists() {
|
||||
model: Some("o3".to_string()),
|
||||
effort: Some(Some(ReasoningEffort::High)),
|
||||
summary: None,
|
||||
disabled_tools: None,
|
||||
})
|
||||
.await
|
||||
.expect("submit override");
|
||||
@@ -78,6 +79,7 @@ async fn override_turn_context_does_not_create_config_file() {
|
||||
model: Some("o3".to_string()),
|
||||
effort: Some(Some(ReasoningEffort::Medium)),
|
||||
summary: None,
|
||||
disabled_tools: None,
|
||||
})
|
||||
.await
|
||||
.expect("submit override");
|
||||
|
||||
@@ -8,6 +8,7 @@ use codex_core::config::OPENAI_DEFAULT_MODEL;
|
||||
use codex_core::features::Feature;
|
||||
use codex_core::model_family::find_family_for_model;
|
||||
use codex_core::protocol::AskForApproval;
|
||||
use codex_core::protocol::DisabledTool;
|
||||
use codex_core::protocol::EventMsg;
|
||||
use codex_core::protocol::InputItem;
|
||||
use codex_core::protocol::Op;
|
||||
@@ -20,6 +21,7 @@ use core_test_support::load_default_config_for_test;
|
||||
use core_test_support::load_sse_fixture_with_id;
|
||||
use core_test_support::skip_if_no_network;
|
||||
use core_test_support::wait_for_event;
|
||||
use serde_json::Value;
|
||||
use std::collections::HashMap;
|
||||
use tempfile::TempDir;
|
||||
use wiremock::Mock;
|
||||
@@ -58,12 +60,19 @@ fn sse_completed(id: &str) -> String {
|
||||
}
|
||||
|
||||
fn assert_tool_names(body: &serde_json::Value, expected_names: &[&str]) {
|
||||
let tools = body["tools"].as_array().unwrap_or_else(|| {
|
||||
panic!("tools field missing or not an array: {body:?}");
|
||||
});
|
||||
assert_eq!(
|
||||
body["tools"]
|
||||
.as_array()
|
||||
.unwrap()
|
||||
tools
|
||||
.iter()
|
||||
.map(|t| t["name"].as_str().unwrap().to_string())
|
||||
.map(|tool| {
|
||||
tool.get("name")
|
||||
.or_else(|| tool.get("type"))
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or_else(|| panic!("tool missing name/type: {tool:?}"))
|
||||
.to_string()
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
expected_names
|
||||
);
|
||||
@@ -143,11 +152,19 @@ async fn codex_mini_latest_tools() {
|
||||
.join("\n");
|
||||
|
||||
let body0 = requests[0].body_json::<serde_json::Value>().unwrap();
|
||||
assert!(
|
||||
body0.get("tools").and_then(Value::as_array).is_some(),
|
||||
"first request missing tools field: {body0:?}"
|
||||
);
|
||||
assert_eq!(
|
||||
body0["instructions"],
|
||||
serde_json::json!(expected_instructions),
|
||||
);
|
||||
let body1 = requests[1].body_json::<serde_json::Value>().unwrap();
|
||||
assert!(
|
||||
body1.get("tools").and_then(Value::as_array).is_some(),
|
||||
"second request missing tools field: {body1:?}"
|
||||
);
|
||||
assert_eq!(
|
||||
body1["instructions"],
|
||||
serde_json::json!(expected_instructions),
|
||||
@@ -223,10 +240,19 @@ async fn prompt_tools_are_consistent_across_requests() {
|
||||
// our internal implementation is responsible for keeping tools in sync
|
||||
// with the OpenAI schema, so we just verify the tool presence here
|
||||
let tools_by_model: HashMap<&'static str, Vec<&'static str>> = HashMap::from([
|
||||
("gpt-5", vec!["shell", "update_plan", "view_image"]),
|
||||
(
|
||||
"gpt-5",
|
||||
vec!["shell", "update_plan", "web_search", "view_image"],
|
||||
),
|
||||
(
|
||||
"gpt-5-codex",
|
||||
vec!["shell", "update_plan", "apply_patch", "view_image"],
|
||||
vec![
|
||||
"shell",
|
||||
"update_plan",
|
||||
"apply_patch",
|
||||
"web_search",
|
||||
"view_image",
|
||||
],
|
||||
),
|
||||
]);
|
||||
let expected_tools_names = tools_by_model
|
||||
@@ -257,6 +283,11 @@ async fn prompt_tools_are_consistent_across_requests() {
|
||||
serde_json::json!(expected_instructions),
|
||||
);
|
||||
assert_tool_names(&body1, expected_tools_names);
|
||||
assert_eq!(
|
||||
body1.get("tool_choice"),
|
||||
body0.get("tool_choice"),
|
||||
"tool_choice should remain consistent across requests"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
@@ -443,6 +474,7 @@ async fn overrides_turn_context_but_keeps_cached_prefix_and_key_constant() {
|
||||
model: Some("o3".to_string()),
|
||||
effort: Some(Some(ReasoningEffort::High)),
|
||||
summary: Some(ReasoningSummary::Detailed),
|
||||
disabled_tools: None,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -577,6 +609,7 @@ async fn per_turn_overrides_keep_cached_prefix_and_key_constant() {
|
||||
effort: Some(ReasoningEffort::High),
|
||||
summary: ReasoningSummary::Detailed,
|
||||
final_output_json_schema: None,
|
||||
disabled_tools: DisabledTool::defaults(),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -688,6 +721,7 @@ async fn send_user_turn_with_no_changes_does_not_send_environment_context() {
|
||||
effort: default_effort,
|
||||
summary: default_summary,
|
||||
final_output_json_schema: None,
|
||||
disabled_tools: DisabledTool::defaults(),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -705,6 +739,7 @@ async fn send_user_turn_with_no_changes_does_not_send_environment_context() {
|
||||
effort: default_effort,
|
||||
summary: default_summary,
|
||||
final_output_json_schema: None,
|
||||
disabled_tools: DisabledTool::defaults(),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -802,6 +837,7 @@ async fn send_user_turn_with_changes_sends_environment_context() {
|
||||
effort: default_effort,
|
||||
summary: default_summary,
|
||||
final_output_json_schema: None,
|
||||
disabled_tools: DisabledTool::defaults(),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -819,6 +855,7 @@ async fn send_user_turn_with_changes_sends_environment_context() {
|
||||
effort: Some(ReasoningEffort::High),
|
||||
summary: ReasoningSummary::Detailed,
|
||||
final_output_json_schema: None,
|
||||
disabled_tools: DisabledTool::defaults(),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#![cfg(not(target_os = "windows"))]
|
||||
|
||||
use codex_core::protocol::AskForApproval;
|
||||
use codex_core::protocol::DisabledTool;
|
||||
use codex_core::protocol::EventMsg;
|
||||
use codex_core::protocol::InputItem;
|
||||
use codex_core::protocol::Op;
|
||||
@@ -74,6 +75,7 @@ async fn read_file_tool_returns_requested_lines() -> anyhow::Result<()> {
|
||||
model: session_model,
|
||||
effort: None,
|
||||
summary: ReasoningSummary::Auto,
|
||||
disabled_tools: DisabledTool::defaults(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ use codex_core::config_types::McpServerTransportConfig;
|
||||
use codex_core::features::Feature;
|
||||
|
||||
use codex_core::protocol::AskForApproval;
|
||||
use codex_core::protocol::DisabledTool;
|
||||
use codex_core::protocol::EventMsg;
|
||||
use codex_core::protocol::InputItem;
|
||||
use codex_core::protocol::Op;
|
||||
@@ -110,6 +111,7 @@ async fn stdio_server_round_trip() -> anyhow::Result<()> {
|
||||
model: session_model,
|
||||
effort: None,
|
||||
summary: ReasoningSummary::Auto,
|
||||
disabled_tools: DisabledTool::defaults(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -259,6 +261,7 @@ async fn streamable_http_tool_call_round_trip() -> anyhow::Result<()> {
|
||||
model: session_model,
|
||||
effort: None,
|
||||
summary: ReasoningSummary::Auto,
|
||||
disabled_tools: DisabledTool::defaults(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -440,6 +443,7 @@ async fn streamable_http_with_oauth_round_trip() -> anyhow::Result<()> {
|
||||
model: session_model,
|
||||
effort: None,
|
||||
summary: ReasoningSummary::Auto,
|
||||
disabled_tools: DisabledTool::defaults(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ use anyhow::Result;
|
||||
use codex_core::features::Feature;
|
||||
use codex_core::model_family::find_family_for_model;
|
||||
use codex_core::protocol::AskForApproval;
|
||||
use codex_core::protocol::DisabledTool;
|
||||
use codex_core::protocol::EventMsg;
|
||||
use codex_core::protocol::InputItem;
|
||||
use codex_core::protocol::Op;
|
||||
@@ -45,6 +46,7 @@ async fn submit_turn(test: &TestCodex, prompt: &str, sandbox_policy: SandboxPoli
|
||||
model: session_model,
|
||||
effort: None,
|
||||
summary: ReasoningSummary::Auto,
|
||||
disabled_tools: DisabledTool::defaults(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ use assert_matches::assert_matches;
|
||||
use codex_core::features::Feature;
|
||||
use codex_core::model_family::find_family_for_model;
|
||||
use codex_core::protocol::AskForApproval;
|
||||
use codex_core::protocol::DisabledTool;
|
||||
use codex_core::protocol::EventMsg;
|
||||
use codex_core::protocol::InputItem;
|
||||
use codex_core::protocol::Op;
|
||||
@@ -84,6 +85,7 @@ async fn shell_tool_executes_command_and_streams_output() -> anyhow::Result<()>
|
||||
model: session_model,
|
||||
effort: None,
|
||||
summary: ReasoningSummary::Auto,
|
||||
disabled_tools: DisabledTool::defaults(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -153,6 +155,7 @@ async fn update_plan_tool_emits_plan_update_event() -> anyhow::Result<()> {
|
||||
model: session_model,
|
||||
effort: None,
|
||||
summary: ReasoningSummary::Auto,
|
||||
disabled_tools: DisabledTool::defaults(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -236,6 +239,7 @@ async fn update_plan_tool_rejects_malformed_payload() -> anyhow::Result<()> {
|
||||
model: session_model,
|
||||
effort: None,
|
||||
summary: ReasoningSummary::Auto,
|
||||
disabled_tools: DisabledTool::defaults(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -334,6 +338,7 @@ async fn apply_patch_tool_executes_and_emits_patch_events() -> anyhow::Result<()
|
||||
model: session_model,
|
||||
effort: None,
|
||||
summary: ReasoningSummary::Auto,
|
||||
disabled_tools: DisabledTool::defaults(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -435,6 +440,7 @@ async fn apply_patch_reports_parse_diagnostics() -> anyhow::Result<()> {
|
||||
model: session_model,
|
||||
effort: None,
|
||||
summary: ReasoningSummary::Auto,
|
||||
disabled_tools: DisabledTool::defaults(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ use std::time::Instant;
|
||||
|
||||
use codex_core::model_family::find_family_for_model;
|
||||
use codex_core::protocol::AskForApproval;
|
||||
use codex_core::protocol::DisabledTool;
|
||||
use codex_core::protocol::EventMsg;
|
||||
use codex_core::protocol::InputItem;
|
||||
use codex_core::protocol::Op;
|
||||
@@ -38,6 +39,7 @@ async fn run_turn(test: &TestCodex, prompt: &str) -> anyhow::Result<()> {
|
||||
model: session_model,
|
||||
effort: None,
|
||||
summary: ReasoningSummary::Auto,
|
||||
disabled_tools: DisabledTool::defaults(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ use anyhow::Result;
|
||||
use codex_core::features::Feature;
|
||||
use codex_core::model_family::find_family_for_model;
|
||||
use codex_core::protocol::AskForApproval;
|
||||
use codex_core::protocol::DisabledTool;
|
||||
use codex_core::protocol::DisabledToolKind;
|
||||
use codex_core::protocol::EventMsg;
|
||||
use codex_core::protocol::InputItem;
|
||||
use codex_core::protocol::Op;
|
||||
@@ -48,6 +50,7 @@ async fn submit_turn(
|
||||
model: session_model,
|
||||
effort: None,
|
||||
summary: ReasoningSummary::Auto,
|
||||
disabled_tools: DisabledTool::defaults(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -408,6 +411,149 @@ async fn shell_timeout_includes_timeout_prefix_and_metadata() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn web_search_allowed_when_other_tool_disabled() -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let server = start_mock_server().await;
|
||||
let mock = mount_sse_once(
|
||||
&server,
|
||||
sse(vec![
|
||||
ev_response_created("resp-1"),
|
||||
ev_assistant_message("msg-1", "done"),
|
||||
ev_completed("resp-1"),
|
||||
]),
|
||||
)
|
||||
.await;
|
||||
|
||||
let mut builder = test_codex().with_config(|config| {
|
||||
config.model = "gpt-5".to_string();
|
||||
config.model_family = find_family_for_model("gpt-5").expect("gpt-5 family");
|
||||
config.features.enable(Feature::WebSearchRequest);
|
||||
});
|
||||
let test = builder.build(&server).await?;
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
items: vec![InputItem::Text {
|
||||
text: "hello codex".into(),
|
||||
}],
|
||||
final_output_json_schema: None,
|
||||
cwd: test.cwd.path().to_path_buf(),
|
||||
approval_policy: AskForApproval::Never,
|
||||
sandbox_policy: SandboxPolicy::DangerFullAccess,
|
||||
model: test.session_configured.model.clone(),
|
||||
effort: None,
|
||||
summary: ReasoningSummary::Auto,
|
||||
disabled_tools: vec![DisabledToolKind::ViewImage.into()],
|
||||
})
|
||||
.await?;
|
||||
|
||||
wait_for_event(&test.codex, |event| {
|
||||
matches!(event, EventMsg::TaskComplete(_))
|
||||
})
|
||||
.await;
|
||||
|
||||
let body = mock.single_request().body_json();
|
||||
let choice = body
|
||||
.get("tool_choice")
|
||||
.expect("tool_choice field should be present");
|
||||
assert!(
|
||||
choice.is_object(),
|
||||
"expected tool_choice to be an object when tools are restricted: {choice:?}"
|
||||
);
|
||||
assert_eq!(
|
||||
choice.get("type").and_then(Value::as_str),
|
||||
Some("allowed_tools")
|
||||
);
|
||||
assert_eq!(choice.get("mode").and_then(Value::as_str), Some("auto"));
|
||||
|
||||
let allowed = choice
|
||||
.get("tools")
|
||||
.and_then(Value::as_array)
|
||||
.cloned()
|
||||
.expect("allowed tools array");
|
||||
let allowed_names = allowed
|
||||
.iter()
|
||||
.filter_map(|tool| tool.get("name").and_then(Value::as_str))
|
||||
.collect::<Vec<_>>();
|
||||
assert!(
|
||||
allowed_names.contains(&"web_search"),
|
||||
"expected web_search to remain allowed: {allowed:?}"
|
||||
);
|
||||
assert!(
|
||||
!allowed_names.contains(&"view_image"),
|
||||
"expected view_image to be excluded from allowed_tools: {allowed:?}"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn override_enables_web_search() -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let server = start_mock_server().await;
|
||||
let mock = mount_sse_once(
|
||||
&server,
|
||||
sse(vec![
|
||||
ev_response_created("resp-1"),
|
||||
ev_assistant_message("msg-1", "done"),
|
||||
ev_completed("resp-1"),
|
||||
]),
|
||||
)
|
||||
.await;
|
||||
|
||||
let mut builder = test_codex().with_config(|config| {
|
||||
config.model = "gpt-5".to_string();
|
||||
config.model_family = find_family_for_model("gpt-5").expect("gpt-5 family");
|
||||
config.features.enable(Feature::WebSearchRequest);
|
||||
});
|
||||
let test = builder.build(&server).await?;
|
||||
|
||||
test.codex
|
||||
.submit(Op::OverrideTurnContext {
|
||||
cwd: None,
|
||||
approval_policy: None,
|
||||
sandbox_policy: None,
|
||||
model: None,
|
||||
effort: None,
|
||||
summary: None,
|
||||
disabled_tools: Some(vec![]),
|
||||
})
|
||||
.await?;
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
items: vec![InputItem::Text {
|
||||
text: "hello after override".into(),
|
||||
}],
|
||||
final_output_json_schema: None,
|
||||
cwd: test.cwd.path().to_path_buf(),
|
||||
approval_policy: AskForApproval::Never,
|
||||
sandbox_policy: SandboxPolicy::DangerFullAccess,
|
||||
model: test.session_configured.model.clone(),
|
||||
effort: None,
|
||||
summary: ReasoningSummary::Auto,
|
||||
disabled_tools: vec![],
|
||||
})
|
||||
.await?;
|
||||
|
||||
wait_for_event(&test.codex, |event| {
|
||||
matches!(event, EventMsg::TaskComplete(_))
|
||||
})
|
||||
.await;
|
||||
|
||||
let body = mock.single_request().body_json();
|
||||
assert_eq!(
|
||||
body.get("tool_choice"),
|
||||
Some(&Value::String("auto".to_string())),
|
||||
"expected unrestricted tool choice to be auto"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn shell_spawn_failure_truncates_exec_error() -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
@@ -5,6 +5,7 @@ use std::collections::HashMap;
|
||||
use anyhow::Result;
|
||||
use codex_core::features::Feature;
|
||||
use codex_core::protocol::AskForApproval;
|
||||
use codex_core::protocol::DisabledTool;
|
||||
use codex_core::protocol::EventMsg;
|
||||
use codex_core::protocol::InputItem;
|
||||
use codex_core::protocol::Op;
|
||||
@@ -128,6 +129,7 @@ async fn unified_exec_reuses_session_via_stdin() -> Result<()> {
|
||||
model: session_model,
|
||||
effort: None,
|
||||
summary: ReasoningSummary::Auto,
|
||||
disabled_tools: DisabledTool::defaults(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -264,6 +266,7 @@ PY
|
||||
model: session_model,
|
||||
effort: None,
|
||||
summary: ReasoningSummary::Auto,
|
||||
disabled_tools: DisabledTool::defaults(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -370,6 +373,7 @@ async fn unified_exec_timeout_and_followup_poll() -> Result<()> {
|
||||
model: session_model,
|
||||
effort: None,
|
||||
summary: ReasoningSummary::Auto,
|
||||
disabled_tools: DisabledTool::defaults(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
use base64::Engine;
|
||||
use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
|
||||
use codex_core::protocol::AskForApproval;
|
||||
use codex_core::protocol::DisabledTool;
|
||||
use codex_core::protocol::EventMsg;
|
||||
use codex_core::protocol::InputItem;
|
||||
use codex_core::protocol::Op;
|
||||
@@ -100,6 +101,7 @@ async fn view_image_tool_attaches_local_image() -> anyhow::Result<()> {
|
||||
model: session_model,
|
||||
effort: None,
|
||||
summary: ReasoningSummary::Auto,
|
||||
disabled_tools: DisabledTool::defaults(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -199,6 +201,7 @@ async fn view_image_tool_errors_when_path_is_directory() -> anyhow::Result<()> {
|
||||
model: session_model,
|
||||
effort: None,
|
||||
summary: ReasoningSummary::Auto,
|
||||
disabled_tools: DisabledTool::defaults(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -264,6 +267,7 @@ async fn view_image_tool_errors_when_file_missing() -> anyhow::Result<()> {
|
||||
model: session_model,
|
||||
effort: None,
|
||||
summary: ReasoningSummary::Auto,
|
||||
disabled_tools: DisabledTool::defaults(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ use codex_core::config::ConfigOverrides;
|
||||
use codex_core::features::Feature;
|
||||
use codex_core::git_info::get_git_repo_root;
|
||||
use codex_core::protocol::AskForApproval;
|
||||
use codex_core::protocol::DisabledTool;
|
||||
use codex_core::protocol::Event;
|
||||
use codex_core::protocol::EventMsg;
|
||||
use codex_core::protocol::InputItem;
|
||||
@@ -349,6 +350,7 @@ pub async fn run_main(cli: Cli, codex_linux_sandbox_exe: Option<PathBuf>) -> any
|
||||
effort: default_effort,
|
||||
summary: default_summary,
|
||||
final_output_json_schema: output_schema,
|
||||
disabled_tools: DisabledTool::defaults(),
|
||||
})
|
||||
.await?;
|
||||
info!("Sent prompt with event ID: {initial_prompt_task_id}");
|
||||
|
||||
@@ -37,6 +37,72 @@ pub const ENVIRONMENT_CONTEXT_OPEN_TAG: &str = "<environment_context>";
|
||||
pub const ENVIRONMENT_CONTEXT_CLOSE_TAG: &str = "</environment_context>";
|
||||
pub const USER_MESSAGE_BEGIN: &str = "## My request for Codex:";
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum DisabledToolKind {
|
||||
WebSearch,
|
||||
ViewImage,
|
||||
UnifiedExec,
|
||||
UpdatePlan,
|
||||
Shell,
|
||||
ApplyPatch,
|
||||
GrepFiles,
|
||||
ReadFile,
|
||||
ListDir,
|
||||
LocalShell,
|
||||
}
|
||||
|
||||
impl DisabledToolKind {
|
||||
pub fn raw_name(self) -> &'static str {
|
||||
match self {
|
||||
DisabledToolKind::WebSearch => "web_search",
|
||||
DisabledToolKind::ViewImage => "view_image",
|
||||
DisabledToolKind::UnifiedExec => "unified_exec",
|
||||
DisabledToolKind::UpdatePlan => "update_plan",
|
||||
DisabledToolKind::Shell => "shell",
|
||||
DisabledToolKind::ApplyPatch => "apply_patch",
|
||||
DisabledToolKind::GrepFiles => "grep_files",
|
||||
DisabledToolKind::ReadFile => "read_file",
|
||||
DisabledToolKind::ListDir => "list_dir",
|
||||
DisabledToolKind::LocalShell => "local_shell",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
|
||||
#[serde(untagged)]
|
||||
pub enum DisabledTool {
|
||||
Known(DisabledToolKind),
|
||||
Other(String),
|
||||
}
|
||||
|
||||
impl From<DisabledToolKind> for DisabledTool {
|
||||
fn from(kind: DisabledToolKind) -> Self {
|
||||
DisabledTool::Known(kind)
|
||||
}
|
||||
}
|
||||
|
||||
impl DisabledTool {
|
||||
/// Default disabled tools used when clients do not explicitly supply one.
|
||||
pub fn defaults() -> Vec<Self> {
|
||||
vec![DisabledToolKind::WebSearch.into()]
|
||||
}
|
||||
|
||||
pub fn raw_name(&self) -> &str {
|
||||
match self {
|
||||
DisabledTool::Known(kind) => kind.raw_name(),
|
||||
DisabledTool::Other(name) => name.as_str(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn matches_tool_name(&self, tool_name: &str) -> bool {
|
||||
match self {
|
||||
DisabledTool::Known(kind) => kind.raw_name() == tool_name,
|
||||
DisabledTool::Other(name) => name == tool_name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Submission Queue Entry - requests from user
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Submission {
|
||||
@@ -90,6 +156,9 @@ pub enum Op {
|
||||
summary: ReasoningSummaryConfig,
|
||||
// The JSON schema to use for the final assistant message
|
||||
final_output_json_schema: Option<Value>,
|
||||
// disables tools
|
||||
#[serde(default = "DisabledTool::defaults")]
|
||||
disabled_tools: Vec<DisabledTool>,
|
||||
},
|
||||
|
||||
/// Override parts of the persistent turn context for subsequent turns.
|
||||
@@ -125,6 +194,10 @@ pub enum Op {
|
||||
/// Updated reasoning summary preference (honored only for reasoning-capable models).
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
summary: Option<ReasoningSummaryConfig>,
|
||||
|
||||
/// Updated disabled tools.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
disabled_tools: Option<Vec<DisabledTool>>,
|
||||
},
|
||||
|
||||
/// Approve a command execution
|
||||
|
||||
@@ -1761,6 +1761,7 @@ impl ChatWidget {
|
||||
model: Some(model_for_action.clone()),
|
||||
effort: Some(effort_for_action),
|
||||
summary: None,
|
||||
disabled_tools: None,
|
||||
}));
|
||||
tx.send(AppEvent::UpdateModel(model_for_action.clone()));
|
||||
tx.send(AppEvent::UpdateReasoningEffort(effort_for_action));
|
||||
@@ -1822,6 +1823,7 @@ impl ChatWidget {
|
||||
model: None,
|
||||
effort: None,
|
||||
summary: None,
|
||||
disabled_tools: None,
|
||||
}));
|
||||
tx.send(AppEvent::UpdateAskForApprovalPolicy(approval));
|
||||
tx.send(AppEvent::UpdateSandboxPolicy(sandbox.clone()));
|
||||
|
||||
Reference in New Issue
Block a user