Enforce single tool output type in codex handlers (#14157)
We'll need to associate output schema with each tool. Each tool can only have on output type.
This commit is contained in:
parent
63597d1b2d
commit
d71e042694
26 changed files with 345 additions and 355 deletions
|
|
@ -281,11 +281,7 @@ async fn dynamic_tool_call_round_trip_sends_text_content_items_to_model() -> Res
|
|||
.iter()
|
||||
.find_map(|body| function_call_output_payload(body, call_id))
|
||||
.context("expected function_call_output in follow-up request")?;
|
||||
let expected_payload = FunctionCallOutputPayload::from_content_items(vec![
|
||||
FunctionCallOutputContentItem::InputText {
|
||||
text: "dynamic-ok".to_string(),
|
||||
},
|
||||
]);
|
||||
let expected_payload = FunctionCallOutputPayload::from_text("dynamic-ok".to_string());
|
||||
assert_eq!(payload, expected_payload);
|
||||
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ use crate::state::TaskKind;
|
|||
use crate::tasks::SessionTask;
|
||||
use crate::tasks::SessionTaskContext;
|
||||
use crate::tools::ToolRouter;
|
||||
use crate::tools::context::TextToolOutput;
|
||||
use crate::tools::context::FunctionToolOutput;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::handlers::ShellHandler;
|
||||
|
|
@ -89,11 +89,10 @@ use std::time::Duration as StdDuration;
|
|||
#[path = "codex_tests_guardian.rs"]
|
||||
mod guardian_tests;
|
||||
|
||||
fn expect_text_tool_output(output: &dyn std::any::Any) -> String {
|
||||
let Some(output) = output.downcast_ref::<TextToolOutput>() else {
|
||||
panic!("unexpected tool output");
|
||||
};
|
||||
output.text.clone()
|
||||
use codex_protocol::models::function_call_output_content_items_to_text;
|
||||
|
||||
fn expect_text_tool_output(output: &FunctionToolOutput) -> String {
|
||||
function_call_output_content_items_to_text(&output.body).unwrap_or_default()
|
||||
}
|
||||
|
||||
struct InstructionsTestCase {
|
||||
|
|
@ -4144,7 +4143,7 @@ async fn rejects_escalated_permissions_when_policy_not_on_request() {
|
|||
})
|
||||
.await;
|
||||
|
||||
let output = expect_text_tool_output(&*resp2.expect("expected Ok result"));
|
||||
let output = expect_text_tool_output(&resp2.expect("expected Ok result"));
|
||||
|
||||
#[derive(Deserialize, PartialEq, Eq, Debug)]
|
||||
struct ResponseExecMetadata {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use crate::features::Feature;
|
|||
use crate::guardian::GUARDIAN_SUBAGENT_NAME;
|
||||
use crate::protocol::AskForApproval;
|
||||
use crate::sandboxing::SandboxPermissions;
|
||||
use crate::tools::context::TextToolOutput;
|
||||
use crate::tools::context::FunctionToolOutput;
|
||||
use crate::turn_diff_tracker::TurnDiffTracker;
|
||||
use codex_app_server_protocol::ConfigLayerSource;
|
||||
use codex_execpolicy::Decision;
|
||||
|
|
@ -16,6 +16,7 @@ use codex_execpolicy::Evaluation;
|
|||
use codex_execpolicy::RuleMatch;
|
||||
use codex_protocol::models::NetworkPermissions;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::models::function_call_output_content_items_to_text;
|
||||
use codex_protocol::permissions::FileSystemSandboxPolicy;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
|
|
@ -33,11 +34,8 @@ use std::fs;
|
|||
use std::sync::Arc;
|
||||
use tempfile::tempdir;
|
||||
|
||||
fn expect_text_output(output: &dyn std::any::Any) -> String {
|
||||
let Some(output) = output.downcast_ref::<TextToolOutput>() else {
|
||||
panic!("unexpected tool output");
|
||||
};
|
||||
output.text.clone()
|
||||
fn expect_text_output(output: &FunctionToolOutput) -> String {
|
||||
function_call_output_content_items_to_text(&output.body).unwrap_or_default()
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
|
@ -159,7 +157,7 @@ async fn guardian_allows_shell_additional_permissions_requests_past_policy_valid
|
|||
})
|
||||
.await;
|
||||
|
||||
let output = expect_text_output(&*resp.expect("expected Ok result"));
|
||||
let output = expect_text_output(&resp.expect("expected Ok result"));
|
||||
|
||||
#[derive(Deserialize, PartialEq, Eq, Debug)]
|
||||
struct ResponseExecMetadata {
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ use codex_protocol::models::FunctionCallOutputContentItem;
|
|||
use codex_protocol::models::FunctionCallOutputPayload;
|
||||
use codex_protocol::models::ResponseInputItem;
|
||||
use codex_protocol::models::ShellToolCallParams;
|
||||
use codex_protocol::models::function_call_output_content_items_to_text;
|
||||
use codex_utils_string::take_bytes_at_char_boundary;
|
||||
use std::any::Any;
|
||||
use std::borrow::Cow;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
|
|
@ -64,16 +64,14 @@ impl ToolPayload {
|
|||
}
|
||||
}
|
||||
|
||||
pub trait ToolOutput: Any + Send {
|
||||
pub trait ToolOutput: Send {
|
||||
fn log_preview(&self) -> String;
|
||||
|
||||
fn success_for_logging(&self) -> bool;
|
||||
|
||||
fn into_response(self: Box<Self>, call_id: &str, payload: &ToolPayload) -> ResponseInputItem;
|
||||
fn into_response(self, call_id: &str, payload: &ToolPayload) -> ResponseInputItem;
|
||||
}
|
||||
|
||||
pub type ToolOutputBox = Box<dyn ToolOutput>;
|
||||
|
||||
pub struct McpToolOutput {
|
||||
pub result: Result<CallToolResult, String>,
|
||||
}
|
||||
|
|
@ -87,8 +85,8 @@ impl ToolOutput for McpToolOutput {
|
|||
self.result.is_ok()
|
||||
}
|
||||
|
||||
fn into_response(self: Box<Self>, call_id: &str, _payload: &ToolPayload) -> ResponseInputItem {
|
||||
let Self { result } = *self;
|
||||
fn into_response(self, call_id: &str, _payload: &ToolPayload) -> ResponseInputItem {
|
||||
let Self { result } = self;
|
||||
ResponseInputItem::McpToolCallOutput {
|
||||
call_id: call_id.to_string(),
|
||||
result,
|
||||
|
|
@ -96,42 +94,34 @@ impl ToolOutput for McpToolOutput {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct TextToolOutput {
|
||||
pub text: String,
|
||||
pub struct FunctionToolOutput {
|
||||
pub body: Vec<FunctionCallOutputContentItem>,
|
||||
pub success: Option<bool>,
|
||||
}
|
||||
|
||||
impl ToolOutput for TextToolOutput {
|
||||
fn log_preview(&self) -> String {
|
||||
telemetry_preview(&self.text)
|
||||
}
|
||||
|
||||
fn success_for_logging(&self) -> bool {
|
||||
self.success.unwrap_or(true)
|
||||
}
|
||||
|
||||
fn into_response(self: Box<Self>, call_id: &str, payload: &ToolPayload) -> ResponseInputItem {
|
||||
let Self { text, success } = *self;
|
||||
function_tool_response(
|
||||
call_id,
|
||||
payload,
|
||||
FunctionCallOutputBody::Text(text),
|
||||
impl FunctionToolOutput {
|
||||
pub fn from_text(text: String, success: Option<bool>) -> Self {
|
||||
Self {
|
||||
body: vec![FunctionCallOutputContentItem::InputText { text }],
|
||||
success,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_content(
|
||||
content: Vec<FunctionCallOutputContentItem>,
|
||||
success: Option<bool>,
|
||||
) -> Self {
|
||||
Self {
|
||||
body: content,
|
||||
success,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ContentToolOutput {
|
||||
pub content: Vec<FunctionCallOutputContentItem>,
|
||||
pub success: Option<bool>,
|
||||
}
|
||||
|
||||
impl ToolOutput for ContentToolOutput {
|
||||
impl ToolOutput for FunctionToolOutput {
|
||||
fn log_preview(&self) -> String {
|
||||
telemetry_preview(
|
||||
&FunctionCallOutputBody::ContentItems(self.content.clone())
|
||||
.to_text()
|
||||
.unwrap_or_default(),
|
||||
&function_call_output_content_items_to_text(&self.body).unwrap_or_default(),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -139,23 +129,25 @@ impl ToolOutput for ContentToolOutput {
|
|||
self.success.unwrap_or(true)
|
||||
}
|
||||
|
||||
fn into_response(self: Box<Self>, call_id: &str, payload: &ToolPayload) -> ResponseInputItem {
|
||||
let Self { content, success } = *self;
|
||||
function_tool_response(
|
||||
call_id,
|
||||
payload,
|
||||
FunctionCallOutputBody::ContentItems(content),
|
||||
success,
|
||||
)
|
||||
fn into_response(self, call_id: &str, payload: &ToolPayload) -> ResponseInputItem {
|
||||
let Self { body, success } = self;
|
||||
function_tool_response(call_id, payload, body, success)
|
||||
}
|
||||
}
|
||||
|
||||
fn function_tool_response(
|
||||
call_id: &str,
|
||||
payload: &ToolPayload,
|
||||
body: FunctionCallOutputBody,
|
||||
body: Vec<FunctionCallOutputContentItem>,
|
||||
success: Option<bool>,
|
||||
) -> ResponseInputItem {
|
||||
let body = match body.as_slice() {
|
||||
[FunctionCallOutputContentItem::InputText { text }] => {
|
||||
FunctionCallOutputBody::Text(text.clone())
|
||||
}
|
||||
_ => FunctionCallOutputBody::ContentItems(body),
|
||||
};
|
||||
|
||||
if matches!(payload, ToolPayload::Custom { .. }) {
|
||||
return ResponseInputItem::CustomToolCallOutput {
|
||||
call_id: call_id.to_string(),
|
||||
|
|
@ -219,17 +211,14 @@ mod tests {
|
|||
let payload = ToolPayload::Custom {
|
||||
input: "patch".to_string(),
|
||||
};
|
||||
let response = Box::new(TextToolOutput {
|
||||
text: "patched".to_string(),
|
||||
success: Some(true),
|
||||
})
|
||||
.into_response("call-42", &payload);
|
||||
let response = FunctionToolOutput::from_text("patched".to_string(), Some(true))
|
||||
.into_response("call-42", &payload);
|
||||
|
||||
match response {
|
||||
ResponseInputItem::CustomToolCallOutput { call_id, output } => {
|
||||
assert_eq!(call_id, "call-42");
|
||||
assert_eq!(output.text_content(), Some("patched"));
|
||||
assert!(output.content_items().is_none());
|
||||
assert_eq!(output.content_items(), None);
|
||||
assert_eq!(output.body.to_text().as_deref(), Some("patched"));
|
||||
assert_eq!(output.success, Some(true));
|
||||
}
|
||||
other => panic!("expected CustomToolCallOutput, got {other:?}"),
|
||||
|
|
@ -241,17 +230,14 @@ mod tests {
|
|||
let payload = ToolPayload::Function {
|
||||
arguments: "{}".to_string(),
|
||||
};
|
||||
let response = Box::new(TextToolOutput {
|
||||
text: "ok".to_string(),
|
||||
success: Some(true),
|
||||
})
|
||||
.into_response("fn-1", &payload);
|
||||
let response = FunctionToolOutput::from_text("ok".to_string(), Some(true))
|
||||
.into_response("fn-1", &payload);
|
||||
|
||||
match response {
|
||||
ResponseInputItem::FunctionCallOutput { call_id, output } => {
|
||||
assert_eq!(call_id, "fn-1");
|
||||
assert_eq!(output.text_content(), Some("ok"));
|
||||
assert!(output.content_items().is_none());
|
||||
assert_eq!(output.content_items(), None);
|
||||
assert_eq!(output.body.to_text().as_deref(), Some("ok"));
|
||||
assert_eq!(output.success, Some(true));
|
||||
}
|
||||
other => panic!("expected FunctionCallOutput, got {other:?}"),
|
||||
|
|
@ -263,8 +249,8 @@ mod tests {
|
|||
let payload = ToolPayload::Custom {
|
||||
input: "patch".to_string(),
|
||||
};
|
||||
let response = Box::new(ContentToolOutput {
|
||||
content: vec![
|
||||
let response = FunctionToolOutput::from_content(
|
||||
vec![
|
||||
FunctionCallOutputContentItem::InputText {
|
||||
text: "line 1".to_string(),
|
||||
},
|
||||
|
|
@ -276,8 +262,8 @@ mod tests {
|
|||
text: "line 2".to_string(),
|
||||
},
|
||||
],
|
||||
success: Some(true),
|
||||
})
|
||||
Some(true),
|
||||
)
|
||||
.into_response("call-99", &payload);
|
||||
|
||||
match response {
|
||||
|
|
@ -305,14 +291,18 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn log_preview_uses_content_items_when_plain_text_is_missing() {
|
||||
let output = ContentToolOutput {
|
||||
content: vec![FunctionCallOutputContentItem::InputText {
|
||||
let output = FunctionToolOutput::from_content(
|
||||
vec![FunctionCallOutputContentItem::InputText {
|
||||
text: "preview".to_string(),
|
||||
}],
|
||||
success: Some(true),
|
||||
};
|
||||
Some(true),
|
||||
);
|
||||
|
||||
assert_eq!(output.log_preview(), "preview");
|
||||
assert_eq!(
|
||||
function_call_output_content_items_to_text(&output.body),
|
||||
Some("preview".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -6,9 +6,8 @@ use crate::codex::TurnContext;
|
|||
use crate::config::Config;
|
||||
use crate::error::CodexErr;
|
||||
use crate::function_tool::FunctionCallError;
|
||||
use crate::tools::context::TextToolOutput;
|
||||
use crate::tools::context::FunctionToolOutput;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolOutputBox;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::handlers::multi_agents::build_agent_spawn_config;
|
||||
use crate::tools::handlers::parse_arguments;
|
||||
|
|
@ -175,6 +174,8 @@ impl JobProgressEmitter {
|
|||
|
||||
#[async_trait]
|
||||
impl ToolHandler for BatchJobHandler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
fn kind(&self) -> ToolKind {
|
||||
ToolKind::Function
|
||||
}
|
||||
|
|
@ -183,7 +184,7 @@ impl ToolHandler for BatchJobHandler {
|
|||
matches!(payload, ToolPayload::Function { .. })
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<ToolOutputBox, FunctionCallError> {
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
let ToolInvocation {
|
||||
session,
|
||||
turn,
|
||||
|
|
@ -223,7 +224,7 @@ mod spawn_agents_on_csv {
|
|||
session: Arc<Session>,
|
||||
turn: Arc<TurnContext>,
|
||||
arguments: String,
|
||||
) -> Result<ToolOutputBox, FunctionCallError> {
|
||||
) -> Result<FunctionToolOutput, FunctionCallError> {
|
||||
let args: SpawnAgentsOnCsvArgs = parse_arguments(arguments.as_str())?;
|
||||
if args.instruction.trim().is_empty() {
|
||||
return Err(FunctionCallError::RespondToModel(
|
||||
|
|
@ -456,10 +457,7 @@ mod spawn_agents_on_csv {
|
|||
"failed to serialize spawn_agents_on_csv result: {err}"
|
||||
))
|
||||
})?;
|
||||
Ok(Box::new(TextToolOutput {
|
||||
text: content,
|
||||
success: Some(true),
|
||||
}))
|
||||
Ok(FunctionToolOutput::from_text(content, Some(true)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -469,7 +467,7 @@ mod report_agent_job_result {
|
|||
pub async fn handle(
|
||||
session: Arc<Session>,
|
||||
arguments: String,
|
||||
) -> Result<ToolOutputBox, FunctionCallError> {
|
||||
) -> Result<FunctionToolOutput, FunctionCallError> {
|
||||
let args: ReportAgentJobResultArgs = parse_arguments(arguments.as_str())?;
|
||||
if !args.result.is_object() {
|
||||
return Err(FunctionCallError::RespondToModel(
|
||||
|
|
@ -505,10 +503,7 @@ mod report_agent_job_result {
|
|||
"failed to serialize report_agent_job_result result: {err}"
|
||||
))
|
||||
})?;
|
||||
Ok(Box::new(TextToolOutput {
|
||||
text: content,
|
||||
success: Some(true),
|
||||
}))
|
||||
Ok(FunctionToolOutput::from_text(content, Some(true)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,10 +11,9 @@ use crate::client_common::tools::ToolSpec;
|
|||
use crate::codex::Session;
|
||||
use crate::codex::TurnContext;
|
||||
use crate::function_tool::FunctionCallError;
|
||||
use crate::tools::context::FunctionToolOutput;
|
||||
use crate::tools::context::SharedTurnDiffTracker;
|
||||
use crate::tools::context::TextToolOutput;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolOutputBox;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::events::ToolEmitter;
|
||||
use crate::tools::events::ToolEventCtx;
|
||||
|
|
@ -92,6 +91,8 @@ fn write_permissions_for_paths(file_paths: &[AbsolutePathBuf]) -> Option<Permiss
|
|||
|
||||
#[async_trait]
|
||||
impl ToolHandler for ApplyPatchHandler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
fn kind(&self) -> ToolKind {
|
||||
ToolKind::Function
|
||||
}
|
||||
|
|
@ -107,7 +108,7 @@ impl ToolHandler for ApplyPatchHandler {
|
|||
true
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<ToolOutputBox, FunctionCallError> {
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
let ToolInvocation {
|
||||
session,
|
||||
turn,
|
||||
|
|
@ -140,10 +141,7 @@ impl ToolHandler for ApplyPatchHandler {
|
|||
match apply_patch::apply_patch(turn.as_ref(), changes).await {
|
||||
InternalApplyPatchInvocation::Output(item) => {
|
||||
let content = item?;
|
||||
Ok(Box::new(TextToolOutput {
|
||||
text: content,
|
||||
success: Some(true),
|
||||
}))
|
||||
Ok(FunctionToolOutput::from_text(content, Some(true)))
|
||||
}
|
||||
InternalApplyPatchInvocation::DelegateToExec(apply) => {
|
||||
let changes = convert_apply_patch_to_protocol(&apply.action);
|
||||
|
|
@ -204,10 +202,7 @@ impl ToolHandler for ApplyPatchHandler {
|
|||
Some(&tracker),
|
||||
);
|
||||
let content = emitter.finish(event_ctx, out).await?;
|
||||
Ok(Box::new(TextToolOutput {
|
||||
text: content,
|
||||
success: Some(true),
|
||||
}))
|
||||
Ok(FunctionToolOutput::from_text(content, Some(true)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -241,7 +236,7 @@ pub(crate) async fn intercept_apply_patch(
|
|||
tracker: Option<&SharedTurnDiffTracker>,
|
||||
call_id: &str,
|
||||
tool_name: &str,
|
||||
) -> Result<Option<ToolOutputBox>, FunctionCallError> {
|
||||
) -> Result<Option<FunctionToolOutput>, FunctionCallError> {
|
||||
match codex_apply_patch::maybe_parse_apply_patch_verified(command, cwd) {
|
||||
codex_apply_patch::MaybeApplyPatchVerified::Body(changes) => {
|
||||
session
|
||||
|
|
@ -255,10 +250,7 @@ pub(crate) async fn intercept_apply_patch(
|
|||
match apply_patch::apply_patch(turn.as_ref(), changes).await {
|
||||
InternalApplyPatchInvocation::Output(item) => {
|
||||
let content = item?;
|
||||
Ok(Some(Box::new(TextToolOutput {
|
||||
text: content,
|
||||
success: Some(true),
|
||||
})))
|
||||
Ok(Some(FunctionToolOutput::from_text(content, Some(true))))
|
||||
}
|
||||
InternalApplyPatchInvocation::DelegateToExec(apply) => {
|
||||
let changes = convert_apply_patch_to_protocol(&apply.action);
|
||||
|
|
@ -317,10 +309,7 @@ pub(crate) async fn intercept_apply_patch(
|
|||
tracker.as_ref().copied(),
|
||||
);
|
||||
let content = emitter.finish(event_ctx, out).await?;
|
||||
Ok(Some(Box::new(TextToolOutput {
|
||||
text: content,
|
||||
success: Some(true),
|
||||
})))
|
||||
Ok(Some(FunctionToolOutput::from_text(content, Some(true))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,9 +16,8 @@ use crate::exec::StreamOutput;
|
|||
use crate::features::Feature;
|
||||
use crate::function_tool::FunctionCallError;
|
||||
use crate::protocol::ExecCommandSource;
|
||||
use crate::tools::context::TextToolOutput;
|
||||
use crate::tools::context::FunctionToolOutput;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolOutputBox;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::events::ToolEmitter;
|
||||
use crate::tools::events::ToolEventCtx;
|
||||
|
|
@ -42,6 +41,8 @@ struct ArtifactsToolArgs {
|
|||
|
||||
#[async_trait]
|
||||
impl ToolHandler for ArtifactsHandler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
fn kind(&self) -> ToolKind {
|
||||
ToolKind::Function
|
||||
}
|
||||
|
|
@ -54,7 +55,7 @@ impl ToolHandler for ArtifactsHandler {
|
|||
true
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<ToolOutputBox, FunctionCallError> {
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
let ToolInvocation {
|
||||
session,
|
||||
turn,
|
||||
|
|
@ -112,10 +113,10 @@ impl ToolHandler for ArtifactsHandler {
|
|||
)
|
||||
.await;
|
||||
|
||||
Ok(Box::new(TextToolOutput {
|
||||
text: format_artifact_output(&output),
|
||||
success: Some(success),
|
||||
}))
|
||||
Ok(FunctionToolOutput::from_text(
|
||||
format_artifact_output(&output),
|
||||
Some(success),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,9 +3,8 @@ use async_trait::async_trait;
|
|||
use crate::features::Feature;
|
||||
use crate::function_tool::FunctionCallError;
|
||||
use crate::tools::code_mode;
|
||||
use crate::tools::context::ContentToolOutput;
|
||||
use crate::tools::context::FunctionToolOutput;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolOutputBox;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
use crate::tools::registry::ToolKind;
|
||||
|
|
@ -14,6 +13,8 @@ pub struct CodeModeHandler;
|
|||
|
||||
#[async_trait]
|
||||
impl ToolHandler for CodeModeHandler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
fn kind(&self) -> ToolKind {
|
||||
ToolKind::Function
|
||||
}
|
||||
|
|
@ -22,7 +23,7 @@ impl ToolHandler for CodeModeHandler {
|
|||
matches!(payload, ToolPayload::Custom { .. })
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<ToolOutputBox, FunctionCallError> {
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
let ToolInvocation {
|
||||
session,
|
||||
turn,
|
||||
|
|
@ -47,9 +48,6 @@ impl ToolHandler for CodeModeHandler {
|
|||
};
|
||||
|
||||
let content_items = code_mode::execute(session, turn, tracker, code).await?;
|
||||
Ok(Box::new(ContentToolOutput {
|
||||
content: content_items,
|
||||
success: Some(true),
|
||||
}))
|
||||
Ok(FunctionToolOutput::from_content(content_items, Some(true)))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
use crate::codex::Session;
|
||||
use crate::codex::TurnContext;
|
||||
use crate::function_tool::FunctionCallError;
|
||||
use crate::tools::context::ContentToolOutput;
|
||||
use crate::tools::context::FunctionToolOutput;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolOutputBox;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::handlers::parse_arguments;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
|
|
@ -23,6 +22,8 @@ pub struct DynamicToolHandler;
|
|||
|
||||
#[async_trait]
|
||||
impl ToolHandler for DynamicToolHandler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
fn kind(&self) -> ToolKind {
|
||||
ToolKind::Function
|
||||
}
|
||||
|
|
@ -31,7 +32,7 @@ impl ToolHandler for DynamicToolHandler {
|
|||
true
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<ToolOutputBox, FunctionCallError> {
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
let ToolInvocation {
|
||||
session,
|
||||
turn,
|
||||
|
|
@ -67,10 +68,7 @@ impl ToolHandler for DynamicToolHandler {
|
|||
.into_iter()
|
||||
.map(FunctionCallOutputContentItem::from)
|
||||
.collect::<Vec<_>>();
|
||||
Ok(Box::new(ContentToolOutput {
|
||||
content: body,
|
||||
success: Some(success),
|
||||
}))
|
||||
Ok(FunctionToolOutput::from_content(body, Some(success)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,9 +7,8 @@ use tokio::process::Command;
|
|||
use tokio::time::timeout;
|
||||
|
||||
use crate::function_tool::FunctionCallError;
|
||||
use crate::tools::context::TextToolOutput;
|
||||
use crate::tools::context::FunctionToolOutput;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolOutputBox;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::handlers::parse_arguments;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
|
|
@ -38,11 +37,13 @@ struct GrepFilesArgs {
|
|||
|
||||
#[async_trait]
|
||||
impl ToolHandler for GrepFilesHandler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
fn kind(&self) -> ToolKind {
|
||||
ToolKind::Function
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<ToolOutputBox, FunctionCallError> {
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
let ToolInvocation { payload, turn, .. } = invocation;
|
||||
|
||||
let arguments = match payload {
|
||||
|
|
@ -86,15 +87,15 @@ impl ToolHandler for GrepFilesHandler {
|
|||
run_rg_search(pattern, include.as_deref(), &search_path, limit, &turn.cwd).await?;
|
||||
|
||||
if search_results.is_empty() {
|
||||
Ok(Box::new(TextToolOutput {
|
||||
text: "No matches found.".to_string(),
|
||||
success: Some(false),
|
||||
}))
|
||||
Ok(FunctionToolOutput::from_text(
|
||||
"No matches found.".to_string(),
|
||||
Some(false),
|
||||
))
|
||||
} else {
|
||||
Ok(Box::new(TextToolOutput {
|
||||
text: search_results.join("\n"),
|
||||
success: Some(true),
|
||||
}))
|
||||
Ok(FunctionToolOutput::from_text(
|
||||
search_results.join("\n"),
|
||||
Some(true),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,10 +9,8 @@ use crate::exec::StreamOutput;
|
|||
use crate::features::Feature;
|
||||
use crate::function_tool::FunctionCallError;
|
||||
use crate::protocol::ExecCommandSource;
|
||||
use crate::tools::context::ContentToolOutput;
|
||||
use crate::tools::context::TextToolOutput;
|
||||
use crate::tools::context::FunctionToolOutput;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolOutputBox;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::events::ToolEmitter;
|
||||
use crate::tools::events::ToolEventCtx;
|
||||
|
|
@ -96,6 +94,8 @@ async fn emit_js_repl_exec_end(
|
|||
}
|
||||
#[async_trait]
|
||||
impl ToolHandler for JsReplHandler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
fn kind(&self) -> ToolKind {
|
||||
ToolKind::Function
|
||||
}
|
||||
|
|
@ -107,7 +107,7 @@ impl ToolHandler for JsReplHandler {
|
|||
)
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<ToolOutputBox, FunctionCallError> {
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
let ToolInvocation {
|
||||
session,
|
||||
turn,
|
||||
|
|
@ -175,26 +175,22 @@ impl ToolHandler for JsReplHandler {
|
|||
.await;
|
||||
|
||||
if items.is_empty() {
|
||||
Ok(Box::new(TextToolOutput {
|
||||
text: content,
|
||||
success: Some(true),
|
||||
}))
|
||||
Ok(FunctionToolOutput::from_text(content, Some(true)))
|
||||
} else {
|
||||
Ok(Box::new(ContentToolOutput {
|
||||
content: items,
|
||||
success: Some(true),
|
||||
}))
|
||||
Ok(FunctionToolOutput::from_content(items, Some(true)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ToolHandler for JsReplResetHandler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
fn kind(&self) -> ToolKind {
|
||||
ToolKind::Function
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<ToolOutputBox, FunctionCallError> {
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
if !invocation.session.features().enabled(Feature::JsRepl) {
|
||||
return Err(FunctionCallError::RespondToModel(
|
||||
"js_repl is disabled by feature flag".to_string(),
|
||||
|
|
@ -202,10 +198,10 @@ impl ToolHandler for JsReplResetHandler {
|
|||
}
|
||||
let manager = invocation.turn.js_repl.manager().await?;
|
||||
manager.reset().await?;
|
||||
Ok(Box::new(TextToolOutput {
|
||||
text: "js_repl kernel reset".to_string(),
|
||||
success: Some(true),
|
||||
}))
|
||||
Ok(FunctionToolOutput::from_text(
|
||||
"js_repl kernel reset".to_string(),
|
||||
Some(true),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,9 +10,8 @@ use serde::Deserialize;
|
|||
use tokio::fs;
|
||||
|
||||
use crate::function_tool::FunctionCallError;
|
||||
use crate::tools::context::TextToolOutput;
|
||||
use crate::tools::context::FunctionToolOutput;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolOutputBox;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::handlers::parse_arguments;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
|
|
@ -48,11 +47,13 @@ struct ListDirArgs {
|
|||
|
||||
#[async_trait]
|
||||
impl ToolHandler for ListDirHandler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
fn kind(&self) -> ToolKind {
|
||||
ToolKind::Function
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<ToolOutputBox, FunctionCallError> {
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
let ToolInvocation { payload, .. } = invocation;
|
||||
|
||||
let arguments = match payload {
|
||||
|
|
@ -102,10 +103,7 @@ impl ToolHandler for ListDirHandler {
|
|||
let mut output = Vec::with_capacity(entries.len() + 1);
|
||||
output.push(format!("Absolute path: {}", path.display()));
|
||||
output.extend(entries);
|
||||
Ok(Box::new(TextToolOutput {
|
||||
text: output.join("\n"),
|
||||
success: Some(true),
|
||||
}))
|
||||
Ok(FunctionToolOutput::from_text(output.join("\n"), Some(true)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,11 +3,9 @@ use std::sync::Arc;
|
|||
|
||||
use crate::function_tool::FunctionCallError;
|
||||
use crate::mcp_tool_call::handle_mcp_tool_call;
|
||||
use crate::tools::context::ContentToolOutput;
|
||||
use crate::tools::context::FunctionToolOutput;
|
||||
use crate::tools::context::McpToolOutput;
|
||||
use crate::tools::context::TextToolOutput;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolOutputBox;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
use crate::tools::registry::ToolKind;
|
||||
|
|
@ -15,13 +13,43 @@ use codex_protocol::models::ResponseInputItem;
|
|||
|
||||
pub struct McpHandler;
|
||||
|
||||
pub enum McpHandlerOutput {
|
||||
Mcp(McpToolOutput),
|
||||
Function(FunctionToolOutput),
|
||||
}
|
||||
|
||||
impl crate::tools::context::ToolOutput for McpHandlerOutput {
|
||||
fn log_preview(&self) -> String {
|
||||
match self {
|
||||
Self::Mcp(output) => output.log_preview(),
|
||||
Self::Function(output) => output.log_preview(),
|
||||
}
|
||||
}
|
||||
|
||||
fn success_for_logging(&self) -> bool {
|
||||
match self {
|
||||
Self::Mcp(output) => output.success_for_logging(),
|
||||
Self::Function(output) => output.success_for_logging(),
|
||||
}
|
||||
}
|
||||
|
||||
fn into_response(self, call_id: &str, payload: &ToolPayload) -> ResponseInputItem {
|
||||
match self {
|
||||
Self::Mcp(output) => output.into_response(call_id, payload),
|
||||
Self::Function(output) => output.into_response(call_id, payload),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ToolHandler for McpHandler {
|
||||
type Output = McpHandlerOutput;
|
||||
|
||||
fn kind(&self) -> ToolKind {
|
||||
ToolKind::Mcp
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<ToolOutputBox, FunctionCallError> {
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
let ToolInvocation {
|
||||
session,
|
||||
turn,
|
||||
|
|
@ -58,16 +86,18 @@ impl ToolHandler for McpHandler {
|
|||
|
||||
match response {
|
||||
ResponseInputItem::McpToolCallOutput { result, .. } => {
|
||||
Ok(Box::new(McpToolOutput { result }))
|
||||
Ok(McpHandlerOutput::Mcp(McpToolOutput { result }))
|
||||
}
|
||||
ResponseInputItem::FunctionCallOutput { output, .. } => {
|
||||
let success = output.success;
|
||||
match output.body {
|
||||
codex_protocol::models::FunctionCallOutputBody::Text(text) => {
|
||||
Ok(Box::new(TextToolOutput { text, success }))
|
||||
}
|
||||
codex_protocol::models::FunctionCallOutputBody::Text(text) => Ok(
|
||||
McpHandlerOutput::Function(FunctionToolOutput::from_text(text, success)),
|
||||
),
|
||||
codex_protocol::models::FunctionCallOutputBody::ContentItems(content) => {
|
||||
Ok(Box::new(ContentToolOutput { content, success }))
|
||||
Ok(McpHandlerOutput::Function(
|
||||
FunctionToolOutput::from_content(content, success),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ use std::time::Instant;
|
|||
|
||||
use async_trait::async_trait;
|
||||
use codex_protocol::mcp::CallToolResult;
|
||||
use codex_protocol::models::function_call_output_content_items_to_text;
|
||||
use rmcp::model::ListResourceTemplatesResult;
|
||||
use rmcp::model::ListResourcesResult;
|
||||
use rmcp::model::PaginatedRequestParams;
|
||||
|
|
@ -24,9 +25,8 @@ use crate::protocol::EventMsg;
|
|||
use crate::protocol::McpInvocation;
|
||||
use crate::protocol::McpToolCallBeginEvent;
|
||||
use crate::protocol::McpToolCallEndEvent;
|
||||
use crate::tools::context::TextToolOutput;
|
||||
use crate::tools::context::FunctionToolOutput;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolOutputBox;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
use crate::tools::registry::ToolKind;
|
||||
|
|
@ -180,11 +180,13 @@ struct ReadResourcePayload {
|
|||
|
||||
#[async_trait]
|
||||
impl ToolHandler for McpResourceHandler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
fn kind(&self) -> ToolKind {
|
||||
ToolKind::Function
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<ToolOutputBox, FunctionCallError> {
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
let ToolInvocation {
|
||||
session,
|
||||
turn,
|
||||
|
|
@ -245,7 +247,7 @@ async fn handle_list_resources(
|
|||
turn: Arc<TurnContext>,
|
||||
call_id: String,
|
||||
arguments: Option<Value>,
|
||||
) -> Result<ToolOutputBox, FunctionCallError> {
|
||||
) -> Result<FunctionToolOutput, FunctionCallError> {
|
||||
let args: ListResourcesArgs = parse_args_with_default(arguments.clone())?;
|
||||
let ListResourcesArgs { server, cursor } = args;
|
||||
let server = normalize_optional_string(server);
|
||||
|
|
@ -298,12 +300,8 @@ async fn handle_list_resources(
|
|||
match payload_result {
|
||||
Ok(payload) => match serialize_function_output(payload) {
|
||||
Ok(output) => {
|
||||
let Some(output_text) =
|
||||
(&*output as &dyn std::any::Any).downcast_ref::<TextToolOutput>()
|
||||
else {
|
||||
unreachable!("MCP resource handler should return text output");
|
||||
};
|
||||
let content = output_text.text.clone();
|
||||
let content =
|
||||
function_call_output_content_items_to_text(&output.body).unwrap_or_default();
|
||||
let duration = start.elapsed();
|
||||
emit_tool_call_end(
|
||||
&session,
|
||||
|
|
@ -311,7 +309,7 @@ async fn handle_list_resources(
|
|||
&call_id,
|
||||
invocation,
|
||||
duration,
|
||||
Ok(call_tool_result_from_content(&content, output_text.success)),
|
||||
Ok(call_tool_result_from_content(&content, output.success)),
|
||||
)
|
||||
.await;
|
||||
Ok(output)
|
||||
|
|
@ -353,7 +351,7 @@ async fn handle_list_resource_templates(
|
|||
turn: Arc<TurnContext>,
|
||||
call_id: String,
|
||||
arguments: Option<Value>,
|
||||
) -> Result<ToolOutputBox, FunctionCallError> {
|
||||
) -> Result<FunctionToolOutput, FunctionCallError> {
|
||||
let args: ListResourceTemplatesArgs = parse_args_with_default(arguments.clone())?;
|
||||
let ListResourceTemplatesArgs { server, cursor } = args;
|
||||
let server = normalize_optional_string(server);
|
||||
|
|
@ -408,12 +406,8 @@ async fn handle_list_resource_templates(
|
|||
match payload_result {
|
||||
Ok(payload) => match serialize_function_output(payload) {
|
||||
Ok(output) => {
|
||||
let Some(output_text) =
|
||||
(&*output as &dyn std::any::Any).downcast_ref::<TextToolOutput>()
|
||||
else {
|
||||
unreachable!("MCP resource handler should return text output");
|
||||
};
|
||||
let content = output_text.text.clone();
|
||||
let content =
|
||||
function_call_output_content_items_to_text(&output.body).unwrap_or_default();
|
||||
let duration = start.elapsed();
|
||||
emit_tool_call_end(
|
||||
&session,
|
||||
|
|
@ -421,7 +415,7 @@ async fn handle_list_resource_templates(
|
|||
&call_id,
|
||||
invocation,
|
||||
duration,
|
||||
Ok(call_tool_result_from_content(&content, output_text.success)),
|
||||
Ok(call_tool_result_from_content(&content, output.success)),
|
||||
)
|
||||
.await;
|
||||
Ok(output)
|
||||
|
|
@ -463,7 +457,7 @@ async fn handle_read_resource(
|
|||
turn: Arc<TurnContext>,
|
||||
call_id: String,
|
||||
arguments: Option<Value>,
|
||||
) -> Result<ToolOutputBox, FunctionCallError> {
|
||||
) -> Result<FunctionToolOutput, FunctionCallError> {
|
||||
let args: ReadResourceArgs = parse_args(arguments.clone())?;
|
||||
let ReadResourceArgs { server, uri } = args;
|
||||
let server = normalize_required_string("server", server)?;
|
||||
|
|
@ -503,12 +497,8 @@ async fn handle_read_resource(
|
|||
match payload_result {
|
||||
Ok(payload) => match serialize_function_output(payload) {
|
||||
Ok(output) => {
|
||||
let Some(output_text) =
|
||||
(&*output as &dyn std::any::Any).downcast_ref::<TextToolOutput>()
|
||||
else {
|
||||
unreachable!("MCP resource handler should return text output");
|
||||
};
|
||||
let content = output_text.text.clone();
|
||||
let content =
|
||||
function_call_output_content_items_to_text(&output.body).unwrap_or_default();
|
||||
let duration = start.elapsed();
|
||||
emit_tool_call_end(
|
||||
&session,
|
||||
|
|
@ -516,7 +506,7 @@ async fn handle_read_resource(
|
|||
&call_id,
|
||||
invocation,
|
||||
duration,
|
||||
Ok(call_tool_result_from_content(&content, output_text.success)),
|
||||
Ok(call_tool_result_from_content(&content, output.success)),
|
||||
)
|
||||
.await;
|
||||
Ok(output)
|
||||
|
|
@ -620,7 +610,7 @@ fn normalize_required_string(field: &str, value: String) -> Result<String, Funct
|
|||
}
|
||||
}
|
||||
|
||||
fn serialize_function_output<T>(payload: T) -> Result<ToolOutputBox, FunctionCallError>
|
||||
fn serialize_function_output<T>(payload: T) -> Result<FunctionToolOutput, FunctionCallError>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
|
|
@ -630,10 +620,7 @@ where
|
|||
))
|
||||
})?;
|
||||
|
||||
Ok(Box::new(TextToolOutput {
|
||||
text: content,
|
||||
success: Some(true),
|
||||
}))
|
||||
Ok(FunctionToolOutput::from_text(content, Some(true)))
|
||||
}
|
||||
|
||||
fn parse_arguments(raw_args: &str) -> Result<Option<Value>, FunctionCallError> {
|
||||
|
|
|
|||
|
|
@ -13,9 +13,8 @@ use crate::config::Config;
|
|||
use crate::error::CodexErr;
|
||||
use crate::features::Feature;
|
||||
use crate::function_tool::FunctionCallError;
|
||||
use crate::tools::context::TextToolOutput;
|
||||
use crate::tools::context::FunctionToolOutput;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolOutputBox;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::handlers::parse_arguments;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
|
|
@ -57,6 +56,8 @@ struct CloseAgentArgs {
|
|||
|
||||
#[async_trait]
|
||||
impl ToolHandler for MultiAgentHandler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
fn kind(&self) -> ToolKind {
|
||||
ToolKind::Function
|
||||
}
|
||||
|
|
@ -65,7 +66,7 @@ impl ToolHandler for MultiAgentHandler {
|
|||
matches!(payload, ToolPayload::Function { .. })
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<ToolOutputBox, FunctionCallError> {
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
let ToolInvocation {
|
||||
session,
|
||||
turn,
|
||||
|
|
@ -127,7 +128,7 @@ mod spawn {
|
|||
turn: Arc<TurnContext>,
|
||||
call_id: String,
|
||||
arguments: String,
|
||||
) -> Result<ToolOutputBox, FunctionCallError> {
|
||||
) -> Result<FunctionToolOutput, FunctionCallError> {
|
||||
let args: SpawnAgentArgs = parse_arguments(&arguments)?;
|
||||
let role_name = args
|
||||
.agent_type
|
||||
|
|
@ -225,10 +226,7 @@ mod spawn {
|
|||
FunctionCallError::Fatal(format!("failed to serialize spawn_agent result: {err}"))
|
||||
})?;
|
||||
|
||||
Ok(Box::new(TextToolOutput {
|
||||
text: content,
|
||||
success: Some(true),
|
||||
}))
|
||||
Ok(FunctionToolOutput::from_text(content, Some(true)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -255,7 +253,7 @@ mod send_input {
|
|||
turn: Arc<TurnContext>,
|
||||
call_id: String,
|
||||
arguments: String,
|
||||
) -> Result<ToolOutputBox, FunctionCallError> {
|
||||
) -> Result<FunctionToolOutput, FunctionCallError> {
|
||||
let args: SendInputArgs = parse_arguments(&arguments)?;
|
||||
let receiver_thread_id = agent_id(&args.id)?;
|
||||
let input_items = parse_collab_input(args.message, args.items)?;
|
||||
|
|
@ -318,10 +316,7 @@ mod send_input {
|
|||
FunctionCallError::Fatal(format!("failed to serialize send_input result: {err}"))
|
||||
})?;
|
||||
|
||||
Ok(Box::new(TextToolOutput {
|
||||
text: content,
|
||||
success: Some(true),
|
||||
}))
|
||||
Ok(FunctionToolOutput::from_text(content, Some(true)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -345,7 +340,7 @@ mod resume_agent {
|
|||
turn: Arc<TurnContext>,
|
||||
call_id: String,
|
||||
arguments: String,
|
||||
) -> Result<ToolOutputBox, FunctionCallError> {
|
||||
) -> Result<FunctionToolOutput, FunctionCallError> {
|
||||
let args: ResumeAgentArgs = parse_arguments(&arguments)?;
|
||||
let receiver_thread_id = agent_id(&args.id)?;
|
||||
let (receiver_agent_nickname, receiver_agent_role) = session
|
||||
|
|
@ -432,10 +427,7 @@ mod resume_agent {
|
|||
FunctionCallError::Fatal(format!("failed to serialize resume_agent result: {err}"))
|
||||
})?;
|
||||
|
||||
Ok(Box::new(TextToolOutput {
|
||||
text: content,
|
||||
success: Some(true),
|
||||
}))
|
||||
Ok(FunctionToolOutput::from_text(content, Some(true)))
|
||||
}
|
||||
|
||||
async fn try_resume_closed_agent(
|
||||
|
|
@ -495,7 +487,7 @@ pub(crate) mod wait {
|
|||
turn: Arc<TurnContext>,
|
||||
call_id: String,
|
||||
arguments: String,
|
||||
) -> Result<ToolOutputBox, FunctionCallError> {
|
||||
) -> Result<FunctionToolOutput, FunctionCallError> {
|
||||
let args: WaitArgs = parse_arguments(&arguments)?;
|
||||
if args.ids.is_empty() {
|
||||
return Err(FunctionCallError::RespondToModel(
|
||||
|
|
@ -645,10 +637,7 @@ pub(crate) mod wait {
|
|||
FunctionCallError::Fatal(format!("failed to serialize wait result: {err}"))
|
||||
})?;
|
||||
|
||||
Ok(Box::new(TextToolOutput {
|
||||
text: content,
|
||||
success: None,
|
||||
}))
|
||||
Ok(FunctionToolOutput::from_text(content, None))
|
||||
}
|
||||
|
||||
async fn wait_for_final_status(
|
||||
|
|
@ -688,7 +677,7 @@ pub mod close_agent {
|
|||
turn: Arc<TurnContext>,
|
||||
call_id: String,
|
||||
arguments: String,
|
||||
) -> Result<ToolOutputBox, FunctionCallError> {
|
||||
) -> Result<FunctionToolOutput, FunctionCallError> {
|
||||
let args: CloseAgentArgs = parse_arguments(&arguments)?;
|
||||
let agent_id = agent_id(&args.id)?;
|
||||
let (receiver_agent_nickname, receiver_agent_role) = session
|
||||
|
|
@ -765,10 +754,7 @@ pub mod close_agent {
|
|||
FunctionCallError::Fatal(format!("failed to serialize close_agent result: {err}"))
|
||||
})?;
|
||||
|
||||
Ok(Box::new(TextToolOutput {
|
||||
text: content,
|
||||
success: Some(true),
|
||||
}))
|
||||
Ok(FunctionToolOutput::from_text(content, Some(true)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -993,8 +979,7 @@ mod tests {
|
|||
use crate::protocol::SandboxPolicy;
|
||||
use crate::protocol::SessionSource;
|
||||
use crate::protocol::SubAgentSource;
|
||||
use crate::tools::context::TextToolOutput;
|
||||
use crate::tools::context::ToolOutputBox;
|
||||
use crate::tools::context::FunctionToolOutput;
|
||||
use crate::turn_diff_tracker::TurnDiffTracker;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::models::ContentItem;
|
||||
|
|
@ -1040,11 +1025,12 @@ mod tests {
|
|||
)
|
||||
}
|
||||
|
||||
fn expect_text_output(output: ToolOutputBox) -> (String, Option<bool>) {
|
||||
let output = (&*output as &dyn std::any::Any)
|
||||
.downcast_ref::<TextToolOutput>()
|
||||
.expect("expected text output");
|
||||
(output.text.clone(), output.success)
|
||||
fn expect_text_output(output: FunctionToolOutput) -> (String, Option<bool>) {
|
||||
(
|
||||
codex_protocol::models::function_call_output_content_items_to_text(&output.body)
|
||||
.unwrap_or_default(),
|
||||
output.success,
|
||||
)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
|
|
|||
|
|
@ -3,9 +3,8 @@ use crate::client_common::tools::ToolSpec;
|
|||
use crate::codex::Session;
|
||||
use crate::codex::TurnContext;
|
||||
use crate::function_tool::FunctionCallError;
|
||||
use crate::tools::context::TextToolOutput;
|
||||
use crate::tools::context::FunctionToolOutput;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolOutputBox;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
use crate::tools::registry::ToolKind;
|
||||
|
|
@ -63,11 +62,13 @@ At most one step can be in_progress at a time.
|
|||
|
||||
#[async_trait]
|
||||
impl ToolHandler for PlanHandler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
fn kind(&self) -> ToolKind {
|
||||
ToolKind::Function
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<ToolOutputBox, FunctionCallError> {
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
let ToolInvocation {
|
||||
session,
|
||||
turn,
|
||||
|
|
@ -88,10 +89,7 @@ impl ToolHandler for PlanHandler {
|
|||
let content =
|
||||
handle_update_plan(session.as_ref(), turn.as_ref(), arguments, call_id).await?;
|
||||
|
||||
Ok(Box::new(TextToolOutput {
|
||||
text: content,
|
||||
success: Some(true),
|
||||
}))
|
||||
Ok(FunctionToolOutput::from_text(content, Some(true)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,9 +6,8 @@ use codex_utils_string::take_bytes_at_char_boundary;
|
|||
use serde::Deserialize;
|
||||
|
||||
use crate::function_tool::FunctionCallError;
|
||||
use crate::tools::context::TextToolOutput;
|
||||
use crate::tools::context::FunctionToolOutput;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolOutputBox;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::handlers::parse_arguments;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
|
|
@ -94,11 +93,13 @@ impl LineRecord {
|
|||
|
||||
#[async_trait]
|
||||
impl ToolHandler for ReadFileHandler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
fn kind(&self) -> ToolKind {
|
||||
ToolKind::Function
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<ToolOutputBox, FunctionCallError> {
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
let ToolInvocation { payload, .. } = invocation;
|
||||
|
||||
let arguments = match payload {
|
||||
|
|
@ -146,10 +147,10 @@ impl ToolHandler for ReadFileHandler {
|
|||
indentation::read_block(&path, offset, limit, indentation).await?
|
||||
}
|
||||
};
|
||||
Ok(Box::new(TextToolOutput {
|
||||
text: collected.join("\n"),
|
||||
success: Some(true),
|
||||
}))
|
||||
Ok(FunctionToolOutput::from_text(
|
||||
collected.join("\n"),
|
||||
Some(true),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,9 +3,8 @@ use codex_protocol::request_permissions::RequestPermissionsArgs;
|
|||
|
||||
use crate::function_tool::FunctionCallError;
|
||||
use crate::sandboxing::normalize_additional_permissions;
|
||||
use crate::tools::context::TextToolOutput;
|
||||
use crate::tools::context::FunctionToolOutput;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolOutputBox;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::handlers::parse_arguments_with_base_path;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
|
|
@ -20,11 +19,13 @@ pub struct RequestPermissionsHandler;
|
|||
|
||||
#[async_trait]
|
||||
impl ToolHandler for RequestPermissionsHandler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
fn kind(&self) -> ToolKind {
|
||||
ToolKind::Function
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<ToolOutputBox, FunctionCallError> {
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
let ToolInvocation {
|
||||
session,
|
||||
turn,
|
||||
|
|
@ -67,9 +68,6 @@ impl ToolHandler for RequestPermissionsHandler {
|
|||
))
|
||||
})?;
|
||||
|
||||
Ok(Box::new(TextToolOutput {
|
||||
text: content,
|
||||
success: Some(true),
|
||||
}))
|
||||
Ok(FunctionToolOutput::from_text(content, Some(true)))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
use crate::function_tool::FunctionCallError;
|
||||
use crate::tools::context::TextToolOutput;
|
||||
use crate::tools::context::FunctionToolOutput;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolOutputBox;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::handlers::parse_arguments;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
|
|
@ -58,11 +57,13 @@ pub struct RequestUserInputHandler {
|
|||
|
||||
#[async_trait]
|
||||
impl ToolHandler for RequestUserInputHandler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
fn kind(&self) -> ToolKind {
|
||||
ToolKind::Function
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<ToolOutputBox, FunctionCallError> {
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
let ToolInvocation {
|
||||
session,
|
||||
turn,
|
||||
|
|
@ -115,10 +116,7 @@ impl ToolHandler for RequestUserInputHandler {
|
|||
))
|
||||
})?;
|
||||
|
||||
Ok(Box::new(TextToolOutput {
|
||||
text: content,
|
||||
success: Some(true),
|
||||
}))
|
||||
Ok(FunctionToolOutput::from_text(content, Some(true)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,9 +12,8 @@ use crate::connectors;
|
|||
use crate::function_tool::FunctionCallError;
|
||||
use crate::mcp::CODEX_APPS_MCP_SERVER_NAME;
|
||||
use crate::mcp_connection_manager::ToolInfo;
|
||||
use crate::tools::context::TextToolOutput;
|
||||
use crate::tools::context::FunctionToolOutput;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolOutputBox;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::handlers::parse_arguments;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
|
|
@ -74,11 +73,13 @@ impl ToolEntry {
|
|||
|
||||
#[async_trait]
|
||||
impl ToolHandler for SearchToolBm25Handler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
fn kind(&self) -> ToolKind {
|
||||
ToolKind::Function
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<ToolOutputBox, FunctionCallError> {
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
let ToolInvocation {
|
||||
payload,
|
||||
session,
|
||||
|
|
@ -141,10 +142,7 @@ impl ToolHandler for SearchToolBm25Handler {
|
|||
"tools": [],
|
||||
})
|
||||
.to_string();
|
||||
return Ok(Box::new(TextToolOutput {
|
||||
text: content,
|
||||
success: Some(true),
|
||||
}));
|
||||
return Ok(FunctionToolOutput::from_text(content, Some(true)));
|
||||
}
|
||||
|
||||
let documents: Vec<Document<usize>> = entries
|
||||
|
|
@ -184,10 +182,7 @@ impl ToolHandler for SearchToolBm25Handler {
|
|||
})
|
||||
.to_string();
|
||||
|
||||
Ok(Box::new(TextToolOutput {
|
||||
text: content,
|
||||
success: Some(true),
|
||||
}))
|
||||
Ok(FunctionToolOutput::from_text(content, Some(true)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,9 +14,8 @@ use crate::is_safe_command::is_known_safe_command;
|
|||
use crate::protocol::ExecCommandSource;
|
||||
use crate::shell::Shell;
|
||||
use crate::skills::maybe_emit_implicit_skill_invocation;
|
||||
use crate::tools::context::TextToolOutput;
|
||||
use crate::tools::context::FunctionToolOutput;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolOutputBox;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::events::ToolEmitter;
|
||||
use crate::tools::events::ToolEventCtx;
|
||||
|
|
@ -142,6 +141,8 @@ impl From<ShellCommandBackendConfig> for ShellCommandHandler {
|
|||
|
||||
#[async_trait]
|
||||
impl ToolHandler for ShellHandler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
fn kind(&self) -> ToolKind {
|
||||
ToolKind::Function
|
||||
}
|
||||
|
|
@ -165,7 +166,7 @@ impl ToolHandler for ShellHandler {
|
|||
}
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<ToolOutputBox, FunctionCallError> {
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
let ToolInvocation {
|
||||
session,
|
||||
turn,
|
||||
|
|
@ -224,6 +225,8 @@ impl ToolHandler for ShellHandler {
|
|||
|
||||
#[async_trait]
|
||||
impl ToolHandler for ShellCommandHandler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
fn kind(&self) -> ToolKind {
|
||||
ToolKind::Function
|
||||
}
|
||||
|
|
@ -253,7 +256,7 @@ impl ToolHandler for ShellCommandHandler {
|
|||
.unwrap_or(true)
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<ToolOutputBox, FunctionCallError> {
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
let ToolInvocation {
|
||||
session,
|
||||
turn,
|
||||
|
|
@ -305,7 +308,7 @@ impl ToolHandler for ShellCommandHandler {
|
|||
}
|
||||
|
||||
impl ShellHandler {
|
||||
async fn run_exec_like(args: RunExecLikeArgs) -> Result<ToolOutputBox, FunctionCallError> {
|
||||
async fn run_exec_like(args: RunExecLikeArgs) -> Result<FunctionToolOutput, FunctionCallError> {
|
||||
let RunExecLikeArgs {
|
||||
tool_name,
|
||||
exec_params,
|
||||
|
|
@ -449,10 +452,7 @@ impl ShellHandler {
|
|||
.map(|result| result.output);
|
||||
let event_ctx = ToolEventCtx::new(session.as_ref(), turn.as_ref(), &call_id, None);
|
||||
let content = emitter.finish(event_ctx, out).await?;
|
||||
Ok(Box::new(TextToolOutput {
|
||||
text: content,
|
||||
success: Some(true),
|
||||
}))
|
||||
Ok(FunctionToolOutput::from_text(content, Some(true)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,9 +10,8 @@ use tokio::sync::Barrier;
|
|||
use tokio::time::sleep;
|
||||
|
||||
use crate::function_tool::FunctionCallError;
|
||||
use crate::tools::context::TextToolOutput;
|
||||
use crate::tools::context::FunctionToolOutput;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolOutputBox;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::handlers::parse_arguments;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
|
|
@ -57,11 +56,13 @@ fn barrier_map() -> &'static tokio::sync::Mutex<HashMap<String, BarrierState>> {
|
|||
|
||||
#[async_trait]
|
||||
impl ToolHandler for TestSyncHandler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
fn kind(&self) -> ToolKind {
|
||||
ToolKind::Function
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<ToolOutputBox, FunctionCallError> {
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
let ToolInvocation { payload, .. } = invocation;
|
||||
|
||||
let arguments = match payload {
|
||||
|
|
@ -91,10 +92,7 @@ impl ToolHandler for TestSyncHandler {
|
|||
sleep(Duration::from_millis(delay)).await;
|
||||
}
|
||||
|
||||
Ok(Box::new(TextToolOutput {
|
||||
text: "ok".to_string(),
|
||||
success: Some(true),
|
||||
}))
|
||||
Ok(FunctionToolOutput::from_text("ok".to_string(), Some(true)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,9 +7,8 @@ use crate::sandboxing::SandboxPermissions;
|
|||
use crate::shell::Shell;
|
||||
use crate::shell::get_shell_by_model_provided_path;
|
||||
use crate::skills::maybe_emit_implicit_skill_invocation;
|
||||
use crate::tools::context::TextToolOutput;
|
||||
use crate::tools::context::FunctionToolOutput;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolOutputBox;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::handlers::apply_granted_turn_permissions;
|
||||
use crate::tools::handlers::apply_patch::intercept_apply_patch;
|
||||
|
|
@ -83,6 +82,8 @@ fn default_tty() -> bool {
|
|||
|
||||
#[async_trait]
|
||||
impl ToolHandler for UnifiedExecHandler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
fn kind(&self) -> ToolKind {
|
||||
ToolKind::Function
|
||||
}
|
||||
|
|
@ -114,7 +115,7 @@ impl ToolHandler for UnifiedExecHandler {
|
|||
!is_known_safe_command(&command)
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<ToolOutputBox, FunctionCallError> {
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
let ToolInvocation {
|
||||
session,
|
||||
turn,
|
||||
|
|
@ -291,10 +292,7 @@ impl ToolHandler for UnifiedExecHandler {
|
|||
|
||||
let content = format_response(&response);
|
||||
|
||||
Ok(Box::new(TextToolOutput {
|
||||
text: content,
|
||||
success: Some(true),
|
||||
}))
|
||||
Ok(FunctionToolOutput::from_text(content, Some(true)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,9 +12,8 @@ use crate::features::Feature;
|
|||
use crate::function_tool::FunctionCallError;
|
||||
use crate::protocol::EventMsg;
|
||||
use crate::protocol::ViewImageToolCallEvent;
|
||||
use crate::tools::context::ContentToolOutput;
|
||||
use crate::tools::context::FunctionToolOutput;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolOutputBox;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::handlers::parse_arguments;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
|
|
@ -32,11 +31,13 @@ struct ViewImageArgs {
|
|||
|
||||
#[async_trait]
|
||||
impl ToolHandler for ViewImageHandler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
fn kind(&self) -> ToolKind {
|
||||
ToolKind::Function
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<ToolOutputBox, FunctionCallError> {
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
if !invocation
|
||||
.turn
|
||||
.model_info
|
||||
|
|
@ -121,9 +122,6 @@ impl ToolHandler for ViewImageHandler {
|
|||
)
|
||||
.await;
|
||||
|
||||
Ok(Box::new(ContentToolOutput {
|
||||
content,
|
||||
success: Some(true),
|
||||
}))
|
||||
Ok(FunctionToolOutput::from_content(content, Some(true)))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use crate::memories::usage::emit_metric_for_tool_read;
|
|||
use crate::protocol::SandboxPolicy;
|
||||
use crate::sandbox_tags::sandbox_tag;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolOutputBox;
|
||||
use crate::tools::context::ToolOutput;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use async_trait::async_trait;
|
||||
use codex_hooks::HookEvent;
|
||||
|
|
@ -32,6 +32,8 @@ pub enum ToolKind {
|
|||
|
||||
#[async_trait]
|
||||
pub trait ToolHandler: Send + Sync {
|
||||
type Output: ToolOutput + 'static;
|
||||
|
||||
fn kind(&self) -> ToolKind;
|
||||
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
|
|
@ -52,19 +54,68 @@ pub trait ToolHandler: Send + Sync {
|
|||
|
||||
/// Perform the actual [ToolInvocation] and returns a [ToolOutput] containing
|
||||
/// the final output to return to the model.
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<ToolOutputBox, FunctionCallError>;
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError>;
|
||||
}
|
||||
|
||||
struct AnyToolResult {
|
||||
preview: String,
|
||||
success: bool,
|
||||
response: ResponseInputItem,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
trait AnyToolHandler: Send + Sync {
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool;
|
||||
|
||||
async fn is_mutating(&self, invocation: &ToolInvocation) -> bool;
|
||||
|
||||
async fn handle_any(
|
||||
&self,
|
||||
invocation: ToolInvocation,
|
||||
) -> Result<AnyToolResult, FunctionCallError>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<T> AnyToolHandler for T
|
||||
where
|
||||
T: ToolHandler,
|
||||
{
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
ToolHandler::matches_kind(self, payload)
|
||||
}
|
||||
|
||||
async fn is_mutating(&self, invocation: &ToolInvocation) -> bool {
|
||||
ToolHandler::is_mutating(self, invocation).await
|
||||
}
|
||||
|
||||
async fn handle_any(
|
||||
&self,
|
||||
invocation: ToolInvocation,
|
||||
) -> Result<AnyToolResult, FunctionCallError> {
|
||||
let call_id = invocation.call_id.clone();
|
||||
let payload = invocation.payload.clone();
|
||||
let output = self.handle(invocation).await?;
|
||||
let preview = output.log_preview();
|
||||
let success = output.success_for_logging();
|
||||
let response = output.into_response(&call_id, &payload);
|
||||
Ok(AnyToolResult {
|
||||
preview,
|
||||
success,
|
||||
response,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ToolRegistry {
|
||||
handlers: HashMap<String, Arc<dyn ToolHandler>>,
|
||||
handlers: HashMap<String, Arc<dyn AnyToolHandler>>,
|
||||
}
|
||||
|
||||
impl ToolRegistry {
|
||||
pub fn new(handlers: HashMap<String, Arc<dyn ToolHandler>>) -> Self {
|
||||
fn new(handlers: HashMap<String, Arc<dyn AnyToolHandler>>) -> Self {
|
||||
Self { handlers }
|
||||
}
|
||||
|
||||
pub fn handler(&self, name: &str) -> Option<Arc<dyn ToolHandler>> {
|
||||
fn handler(&self, name: &str) -> Option<Arc<dyn AnyToolHandler>> {
|
||||
self.handlers.get(name).map(Arc::clone)
|
||||
}
|
||||
|
||||
|
|
@ -163,7 +214,7 @@ impl ToolRegistry {
|
|||
}
|
||||
|
||||
let is_mutating = handler.is_mutating(&invocation).await;
|
||||
let output_cell = tokio::sync::Mutex::new(None);
|
||||
let response_cell = tokio::sync::Mutex::new(None);
|
||||
let invocation_for_tool = invocation.clone();
|
||||
|
||||
let started = Instant::now();
|
||||
|
|
@ -177,19 +228,22 @@ impl ToolRegistry {
|
|||
mcp_server_origin_ref,
|
||||
|| {
|
||||
let handler = handler.clone();
|
||||
let output_cell = &output_cell;
|
||||
let response_cell = &response_cell;
|
||||
async move {
|
||||
if is_mutating {
|
||||
tracing::trace!("waiting for tool gate");
|
||||
invocation_for_tool.turn.tool_call_gate.wait_ready().await;
|
||||
tracing::trace!("tool gate released");
|
||||
}
|
||||
match handler.handle(invocation_for_tool).await {
|
||||
Ok(output) => {
|
||||
let preview = output.log_preview();
|
||||
let success = output.success_for_logging();
|
||||
let mut guard = output_cell.lock().await;
|
||||
*guard = Some(output);
|
||||
match handler.handle_any(invocation_for_tool).await {
|
||||
Ok(result) => {
|
||||
let AnyToolResult {
|
||||
preview,
|
||||
success,
|
||||
response,
|
||||
} = result;
|
||||
let mut guard = response_cell.lock().await;
|
||||
*guard = Some(response);
|
||||
Ok((preview, success))
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
|
|
@ -220,11 +274,11 @@ impl ToolRegistry {
|
|||
|
||||
match result {
|
||||
Ok(_) => {
|
||||
let mut guard = output_cell.lock().await;
|
||||
let output = guard.take().ok_or_else(|| {
|
||||
let mut guard = response_cell.lock().await;
|
||||
let response = guard.take().ok_or_else(|| {
|
||||
FunctionCallError::Fatal("tool produced no output".to_string())
|
||||
})?;
|
||||
Ok(output.into_response(&call_id_owned, &payload_for_response))
|
||||
Ok(response)
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
|
|
@ -247,7 +301,7 @@ impl ConfiguredToolSpec {
|
|||
}
|
||||
|
||||
pub struct ToolRegistryBuilder {
|
||||
handlers: HashMap<String, Arc<dyn ToolHandler>>,
|
||||
handlers: HashMap<String, Arc<dyn AnyToolHandler>>,
|
||||
specs: Vec<ConfiguredToolSpec>,
|
||||
}
|
||||
|
||||
|
|
@ -272,8 +326,12 @@ impl ToolRegistryBuilder {
|
|||
.push(ConfiguredToolSpec::new(spec, supports_parallel_tool_calls));
|
||||
}
|
||||
|
||||
pub fn register_handler(&mut self, name: impl Into<String>, handler: Arc<dyn ToolHandler>) {
|
||||
pub fn register_handler<H>(&mut self, name: impl Into<String>, handler: Arc<H>)
|
||||
where
|
||||
H: ToolHandler + 'static,
|
||||
{
|
||||
let name = name.into();
|
||||
let handler: Arc<dyn AnyToolHandler> = handler;
|
||||
if self
|
||||
.handlers
|
||||
.insert(name.clone(), handler.clone())
|
||||
|
|
|
|||
|
|
@ -857,25 +857,11 @@ async fn view_image_tool_placeholder_for_non_image_files() -> anyhow::Result<()>
|
|||
request.inputs_of_type("input_image").is_empty(),
|
||||
"non-image file should not produce an input_image message"
|
||||
);
|
||||
let function_output = request.function_call_output(call_id);
|
||||
let output_items = function_output
|
||||
.get("output")
|
||||
.and_then(Value::as_array)
|
||||
.expect("function_call_output should be a content item array");
|
||||
assert_eq!(
|
||||
output_items.len(),
|
||||
1,
|
||||
"non-image placeholder should be returned as a single content item"
|
||||
);
|
||||
assert_eq!(
|
||||
output_items[0].get("type").and_then(Value::as_str),
|
||||
Some("input_text"),
|
||||
"non-image placeholder should be returned as input_text"
|
||||
);
|
||||
let placeholder = output_items[0]
|
||||
.get("text")
|
||||
.and_then(Value::as_str)
|
||||
.expect("placeholder text present");
|
||||
let (placeholder, success) = request
|
||||
.function_call_output_content_and_success(call_id)
|
||||
.expect("function_call_output should be present");
|
||||
assert_eq!(success, None);
|
||||
let placeholder = placeholder.expect("placeholder text present");
|
||||
|
||||
assert!(
|
||||
placeholder.contains("Codex could not read the local image at")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue