Simplify tool implemetations (#4160)
Use Result<String, FunctionCallError> for all tool handling code and rely on error propagation instead of creating failed items everywhere.
This commit is contained in:
@@ -1,13 +1,12 @@
|
|||||||
use crate::codex::Session;
|
use crate::codex::Session;
|
||||||
use crate::codex::TurnContext;
|
use crate::codex::TurnContext;
|
||||||
|
use crate::function_tool::FunctionCallError;
|
||||||
use crate::protocol::FileChange;
|
use crate::protocol::FileChange;
|
||||||
use crate::protocol::ReviewDecision;
|
use crate::protocol::ReviewDecision;
|
||||||
use crate::safety::SafetyCheck;
|
use crate::safety::SafetyCheck;
|
||||||
use crate::safety::assess_patch_safety;
|
use crate::safety::assess_patch_safety;
|
||||||
use codex_apply_patch::ApplyPatchAction;
|
use codex_apply_patch::ApplyPatchAction;
|
||||||
use codex_apply_patch::ApplyPatchFileChange;
|
use codex_apply_patch::ApplyPatchFileChange;
|
||||||
use codex_protocol::models::FunctionCallOutputPayload;
|
|
||||||
use codex_protocol::models::ResponseInputItem;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
@@ -17,7 +16,7 @@ pub(crate) enum InternalApplyPatchInvocation {
|
|||||||
/// The `apply_patch` call was handled programmatically, without any sort
|
/// The `apply_patch` call was handled programmatically, without any sort
|
||||||
/// of sandbox, because the user explicitly approved it. This is the
|
/// of sandbox, because the user explicitly approved it. This is the
|
||||||
/// result to use with the `shell` function call that contained `apply_patch`.
|
/// result to use with the `shell` function call that contained `apply_patch`.
|
||||||
Output(ResponseInputItem),
|
Output(Result<String, FunctionCallError>),
|
||||||
|
|
||||||
/// The `apply_patch` call was approved, either automatically because it
|
/// The `apply_patch` call was approved, either automatically because it
|
||||||
/// appears that it should be allowed based on the user's sandbox policy
|
/// appears that it should be allowed based on the user's sandbox policy
|
||||||
@@ -33,12 +32,6 @@ pub(crate) struct ApplyPatchExec {
|
|||||||
pub(crate) user_explicitly_approved_this_action: bool,
|
pub(crate) user_explicitly_approved_this_action: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ResponseInputItem> for InternalApplyPatchInvocation {
|
|
||||||
fn from(item: ResponseInputItem) -> Self {
|
|
||||||
InternalApplyPatchInvocation::Output(item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn apply_patch(
|
pub(crate) async fn apply_patch(
|
||||||
sess: &Session,
|
sess: &Session,
|
||||||
turn_context: &TurnContext,
|
turn_context: &TurnContext,
|
||||||
@@ -77,25 +70,15 @@ pub(crate) async fn apply_patch(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
ReviewDecision::Denied | ReviewDecision::Abort => {
|
ReviewDecision::Denied | ReviewDecision::Abort => {
|
||||||
ResponseInputItem::FunctionCallOutput {
|
InternalApplyPatchInvocation::Output(Err(FunctionCallError::RespondToModel(
|
||||||
call_id: call_id.to_owned(),
|
"patch rejected by user".to_string(),
|
||||||
output: FunctionCallOutputPayload {
|
)))
|
||||||
content: "patch rejected by user".to_string(),
|
|
||||||
success: Some(false),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
.into()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SafetyCheck::Reject { reason } => ResponseInputItem::FunctionCallOutput {
|
SafetyCheck::Reject { reason } => InternalApplyPatchInvocation::Output(Err(
|
||||||
call_id: call_id.to_owned(),
|
FunctionCallError::RespondToModel(format!("patch rejected: {reason}")),
|
||||||
output: FunctionCallOutputPayload {
|
)),
|
||||||
content: format!("patch rejected: {reason}"),
|
|
||||||
success: Some(false),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ use std::time::Duration;
|
|||||||
use crate::AuthManager;
|
use crate::AuthManager;
|
||||||
use crate::client_common::REVIEW_PROMPT;
|
use crate::client_common::REVIEW_PROMPT;
|
||||||
use crate::event_mapping::map_response_item_to_event_messages;
|
use crate::event_mapping::map_response_item_to_event_messages;
|
||||||
|
use crate::function_tool::FunctionCallError;
|
||||||
use crate::review_format::format_review_findings_block;
|
use crate::review_format::format_review_findings_block;
|
||||||
use crate::user_notification::UserNotifier;
|
use crate::user_notification::UserNotifier;
|
||||||
use async_channel::Receiver;
|
use async_channel::Receiver;
|
||||||
@@ -2232,18 +2233,41 @@ async fn handle_response_item(
|
|||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
info!("FunctionCall: {name}({arguments})");
|
info!("FunctionCall: {name}({arguments})");
|
||||||
Some(
|
if let Some((server, tool_name)) = sess.mcp_connection_manager.parse_tool_name(&name) {
|
||||||
handle_function_call(
|
let resp = handle_mcp_tool_call(
|
||||||
|
sess,
|
||||||
|
sub_id,
|
||||||
|
call_id.clone(),
|
||||||
|
server,
|
||||||
|
tool_name,
|
||||||
|
arguments,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
Some(resp)
|
||||||
|
} else {
|
||||||
|
let result = handle_function_call(
|
||||||
sess,
|
sess,
|
||||||
turn_context,
|
turn_context,
|
||||||
turn_diff_tracker,
|
turn_diff_tracker,
|
||||||
sub_id.to_string(),
|
sub_id.to_string(),
|
||||||
name,
|
name,
|
||||||
arguments,
|
arguments,
|
||||||
call_id,
|
call_id.clone(),
|
||||||
)
|
)
|
||||||
.await,
|
.await;
|
||||||
)
|
|
||||||
|
let output = match result {
|
||||||
|
Ok(content) => FunctionCallOutputPayload {
|
||||||
|
content,
|
||||||
|
success: Some(true),
|
||||||
|
},
|
||||||
|
Err(FunctionCallError::RespondToModel(msg)) => FunctionCallOutputPayload {
|
||||||
|
content: msg,
|
||||||
|
success: Some(false),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
Some(ResponseInputItem::FunctionCallOutput { call_id, output })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ResponseItem::LocalShellCall {
|
ResponseItem::LocalShellCall {
|
||||||
id,
|
id,
|
||||||
@@ -2276,17 +2300,32 @@ async fn handle_response_item(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let exec_params = to_exec_params(params, turn_context);
|
let exec_params = to_exec_params(params, turn_context);
|
||||||
Some(
|
{
|
||||||
handle_container_exec_with_params(
|
let result = handle_container_exec_with_params(
|
||||||
exec_params,
|
exec_params,
|
||||||
sess,
|
sess,
|
||||||
turn_context,
|
turn_context,
|
||||||
turn_diff_tracker,
|
turn_diff_tracker,
|
||||||
sub_id.to_string(),
|
sub_id.to_string(),
|
||||||
effective_call_id,
|
effective_call_id.clone(),
|
||||||
)
|
)
|
||||||
.await,
|
.await;
|
||||||
)
|
|
||||||
|
let output = match result {
|
||||||
|
Ok(content) => FunctionCallOutputPayload {
|
||||||
|
content,
|
||||||
|
success: Some(true),
|
||||||
|
},
|
||||||
|
Err(FunctionCallError::RespondToModel(msg)) => FunctionCallOutputPayload {
|
||||||
|
content: msg,
|
||||||
|
success: Some(false),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
Some(ResponseInputItem::FunctionCallOutput {
|
||||||
|
call_id: effective_call_id,
|
||||||
|
output,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ResponseItem::CustomToolCall {
|
ResponseItem::CustomToolCall {
|
||||||
id: _,
|
id: _,
|
||||||
@@ -2294,18 +2333,24 @@ async fn handle_response_item(
|
|||||||
name,
|
name,
|
||||||
input,
|
input,
|
||||||
status: _,
|
status: _,
|
||||||
} => Some(
|
} => {
|
||||||
handle_custom_tool_call(
|
let result = handle_custom_tool_call(
|
||||||
sess,
|
sess,
|
||||||
turn_context,
|
turn_context,
|
||||||
turn_diff_tracker,
|
turn_diff_tracker,
|
||||||
sub_id.to_string(),
|
sub_id.to_string(),
|
||||||
name,
|
name,
|
||||||
input,
|
input,
|
||||||
call_id,
|
call_id.clone(),
|
||||||
)
|
)
|
||||||
.await,
|
.await;
|
||||||
),
|
|
||||||
|
let output = match result {
|
||||||
|
Ok(content) => content,
|
||||||
|
Err(FunctionCallError::RespondToModel(msg)) => msg,
|
||||||
|
};
|
||||||
|
Some(ResponseInputItem::CustomToolCallOutput { call_id, output })
|
||||||
|
}
|
||||||
ResponseItem::FunctionCallOutput { .. } => {
|
ResponseItem::FunctionCallOutput { .. } => {
|
||||||
debug!("unexpected FunctionCallOutput from stream");
|
debug!("unexpected FunctionCallOutput from stream");
|
||||||
None
|
None
|
||||||
@@ -2342,22 +2387,17 @@ async fn handle_response_item(
|
|||||||
|
|
||||||
async fn handle_unified_exec_tool_call(
|
async fn handle_unified_exec_tool_call(
|
||||||
sess: &Session,
|
sess: &Session,
|
||||||
call_id: String,
|
|
||||||
session_id: Option<String>,
|
session_id: Option<String>,
|
||||||
arguments: Vec<String>,
|
arguments: Vec<String>,
|
||||||
timeout_ms: Option<u64>,
|
timeout_ms: Option<u64>,
|
||||||
) -> ResponseInputItem {
|
) -> Result<String, FunctionCallError> {
|
||||||
let parsed_session_id = if let Some(session_id) = session_id {
|
let parsed_session_id = if let Some(session_id) = session_id {
|
||||||
match session_id.parse::<i32>() {
|
match session_id.parse::<i32>() {
|
||||||
Ok(parsed) => Some(parsed),
|
Ok(parsed) => Some(parsed),
|
||||||
Err(output) => {
|
Err(output) => {
|
||||||
return ResponseInputItem::FunctionCallOutput {
|
return Err(FunctionCallError::RespondToModel(format!(
|
||||||
call_id: call_id.to_string(),
|
"invalid session_id: {session_id} due to error {output:?}"
|
||||||
output: FunctionCallOutputPayload {
|
)));
|
||||||
content: format!("invalid session_id: {session_id} due to error {output}"),
|
|
||||||
success: Some(false),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -2370,40 +2410,29 @@ async fn handle_unified_exec_tool_call(
|
|||||||
timeout_ms,
|
timeout_ms,
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = sess.unified_exec_manager.handle_request(request).await;
|
let value = sess
|
||||||
|
.unified_exec_manager
|
||||||
|
.handle_request(request)
|
||||||
|
.await
|
||||||
|
.map_err(|err| {
|
||||||
|
FunctionCallError::RespondToModel(format!("unified exec failed: {err:?}"))
|
||||||
|
})?;
|
||||||
|
|
||||||
let output_payload = match result {
|
#[derive(Serialize)]
|
||||||
Ok(value) => {
|
struct SerializedUnifiedExecResult {
|
||||||
#[derive(Serialize)]
|
session_id: Option<String>,
|
||||||
struct SerializedUnifiedExecResult<'a> {
|
output: String,
|
||||||
session_id: Option<String>,
|
|
||||||
output: &'a str,
|
|
||||||
}
|
|
||||||
|
|
||||||
match serde_json::to_string(&SerializedUnifiedExecResult {
|
|
||||||
session_id: value.session_id.map(|id| id.to_string()),
|
|
||||||
output: &value.output,
|
|
||||||
}) {
|
|
||||||
Ok(serialized) => FunctionCallOutputPayload {
|
|
||||||
content: serialized,
|
|
||||||
success: Some(true),
|
|
||||||
},
|
|
||||||
Err(err) => FunctionCallOutputPayload {
|
|
||||||
content: format!("failed to serialize unified exec output: {err}"),
|
|
||||||
success: Some(false),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(err) => FunctionCallOutputPayload {
|
|
||||||
content: format!("unified exec failed: {err}"),
|
|
||||||
success: Some(false),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
ResponseInputItem::FunctionCallOutput {
|
|
||||||
call_id,
|
|
||||||
output: output_payload,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
serde_json::to_string(&SerializedUnifiedExecResult {
|
||||||
|
session_id: value.session_id.map(|id| id.to_string()),
|
||||||
|
output: value.output,
|
||||||
|
})
|
||||||
|
.map_err(|err| {
|
||||||
|
FunctionCallError::RespondToModel(format!(
|
||||||
|
"failed to serialize unified exec output: {err:?}"
|
||||||
|
))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_function_call(
|
async fn handle_function_call(
|
||||||
@@ -2414,15 +2443,10 @@ async fn handle_function_call(
|
|||||||
name: String,
|
name: String,
|
||||||
arguments: String,
|
arguments: String,
|
||||||
call_id: String,
|
call_id: String,
|
||||||
) -> ResponseInputItem {
|
) -> Result<String, FunctionCallError> {
|
||||||
match name.as_str() {
|
match name.as_str() {
|
||||||
"container.exec" | "shell" => {
|
"container.exec" | "shell" => {
|
||||||
let params = match parse_container_exec_arguments(arguments, turn_context, &call_id) {
|
let params = parse_container_exec_arguments(arguments, turn_context, &call_id)?;
|
||||||
Ok(params) => params,
|
|
||||||
Err(output) => {
|
|
||||||
return *output;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
handle_container_exec_with_params(
|
handle_container_exec_with_params(
|
||||||
params,
|
params,
|
||||||
sess,
|
sess,
|
||||||
@@ -2443,74 +2467,41 @@ async fn handle_function_call(
|
|||||||
timeout_ms: Option<u64>,
|
timeout_ms: Option<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
let args = match serde_json::from_str::<UnifiedExecArgs>(&arguments) {
|
let args: UnifiedExecArgs = serde_json::from_str(&arguments).map_err(|err| {
|
||||||
Ok(args) => args,
|
FunctionCallError::RespondToModel(format!(
|
||||||
Err(err) => {
|
"failed to parse function arguments: {err:?}"
|
||||||
return ResponseInputItem::FunctionCallOutput {
|
))
|
||||||
call_id,
|
})?;
|
||||||
output: FunctionCallOutputPayload {
|
|
||||||
content: format!("failed to parse function arguments: {err}"),
|
|
||||||
success: Some(false),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
handle_unified_exec_tool_call(
|
handle_unified_exec_tool_call(sess, args.session_id, args.input, args.timeout_ms).await
|
||||||
sess,
|
|
||||||
call_id,
|
|
||||||
args.session_id,
|
|
||||||
args.input,
|
|
||||||
args.timeout_ms,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
"view_image" => {
|
"view_image" => {
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize)]
|
||||||
struct SeeImageArgs {
|
struct SeeImageArgs {
|
||||||
path: String,
|
path: String,
|
||||||
}
|
}
|
||||||
let args = match serde_json::from_str::<SeeImageArgs>(&arguments) {
|
let args: SeeImageArgs = serde_json::from_str(&arguments).map_err(|e| {
|
||||||
Ok(a) => a,
|
FunctionCallError::RespondToModel(format!(
|
||||||
Err(e) => {
|
"failed to parse function arguments: {e:?}"
|
||||||
return ResponseInputItem::FunctionCallOutput {
|
))
|
||||||
call_id,
|
})?;
|
||||||
output: FunctionCallOutputPayload {
|
|
||||||
content: format!("failed to parse function arguments: {e}"),
|
|
||||||
success: Some(false),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let abs = turn_context.resolve_path(Some(args.path));
|
let abs = turn_context.resolve_path(Some(args.path));
|
||||||
let output = match sess
|
sess.inject_input(vec![InputItem::LocalImage { path: abs }])
|
||||||
.inject_input(vec![InputItem::LocalImage { path: abs }])
|
|
||||||
.await
|
.await
|
||||||
{
|
.map_err(|_| {
|
||||||
Ok(()) => FunctionCallOutputPayload {
|
FunctionCallError::RespondToModel(
|
||||||
content: "attached local image path".to_string(),
|
"unable to attach image (no active task)".to_string(),
|
||||||
success: Some(true),
|
)
|
||||||
},
|
})?;
|
||||||
Err(_) => FunctionCallOutputPayload {
|
|
||||||
content: "unable to attach image (no active task)".to_string(),
|
Ok("attached local image path".to_string())
|
||||||
success: Some(false),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
ResponseInputItem::FunctionCallOutput { call_id, output }
|
|
||||||
}
|
}
|
||||||
"apply_patch" => {
|
"apply_patch" => {
|
||||||
let args = match serde_json::from_str::<ApplyPatchToolArgs>(&arguments) {
|
let args: ApplyPatchToolArgs = serde_json::from_str(&arguments).map_err(|e| {
|
||||||
Ok(a) => a,
|
FunctionCallError::RespondToModel(format!(
|
||||||
Err(e) => {
|
"failed to parse function arguments: {e:?}"
|
||||||
return ResponseInputItem::FunctionCallOutput {
|
))
|
||||||
call_id,
|
})?;
|
||||||
output: FunctionCallOutputPayload {
|
|
||||||
content: format!("failed to parse function arguments: {e}"),
|
|
||||||
success: None,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let exec_params = ExecParams {
|
let exec_params = ExecParams {
|
||||||
command: vec!["apply_patch".to_string(), args.input.clone()],
|
command: vec!["apply_patch".to_string(), args.input.clone()],
|
||||||
cwd: turn_context.cwd.clone(),
|
cwd: turn_context.cwd.clone(),
|
||||||
@@ -2532,69 +2523,39 @@ async fn handle_function_call(
|
|||||||
"update_plan" => handle_update_plan(sess, arguments, sub_id, call_id).await,
|
"update_plan" => handle_update_plan(sess, arguments, sub_id, call_id).await,
|
||||||
EXEC_COMMAND_TOOL_NAME => {
|
EXEC_COMMAND_TOOL_NAME => {
|
||||||
// TODO(mbolin): Sandbox check.
|
// TODO(mbolin): Sandbox check.
|
||||||
let exec_params = match serde_json::from_str::<ExecCommandParams>(&arguments) {
|
let exec_params: ExecCommandParams = serde_json::from_str(&arguments).map_err(|e| {
|
||||||
Ok(params) => params,
|
FunctionCallError::RespondToModel(format!(
|
||||||
Err(e) => {
|
"failed to parse function arguments: {e:?}"
|
||||||
return ResponseInputItem::FunctionCallOutput {
|
))
|
||||||
call_id,
|
})?;
|
||||||
output: FunctionCallOutputPayload {
|
|
||||||
content: format!("failed to parse function arguments: {e}"),
|
|
||||||
success: Some(false),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let result = sess
|
let result = sess
|
||||||
.session_manager
|
.session_manager
|
||||||
.handle_exec_command_request(exec_params)
|
.handle_exec_command_request(exec_params)
|
||||||
.await;
|
.await;
|
||||||
let function_call_output = crate::exec_command::result_into_payload(result);
|
match result {
|
||||||
ResponseInputItem::FunctionCallOutput {
|
Ok(output) => Ok(output.to_text_output()),
|
||||||
call_id,
|
Err(err) => Err(FunctionCallError::RespondToModel(err)),
|
||||||
output: function_call_output,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
WRITE_STDIN_TOOL_NAME => {
|
WRITE_STDIN_TOOL_NAME => {
|
||||||
let write_stdin_params = match serde_json::from_str::<WriteStdinParams>(&arguments) {
|
let write_stdin_params =
|
||||||
Ok(params) => params,
|
serde_json::from_str::<WriteStdinParams>(&arguments).map_err(|e| {
|
||||||
Err(e) => {
|
FunctionCallError::RespondToModel(format!(
|
||||||
return ResponseInputItem::FunctionCallOutput {
|
"failed to parse function arguments: {e:?}"
|
||||||
call_id,
|
))
|
||||||
output: FunctionCallOutputPayload {
|
})?;
|
||||||
content: format!("failed to parse function arguments: {e}"),
|
|
||||||
success: Some(false),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let result = sess
|
let result = sess
|
||||||
.session_manager
|
.session_manager
|
||||||
.handle_write_stdin_request(write_stdin_params)
|
.handle_write_stdin_request(write_stdin_params)
|
||||||
.await;
|
.await
|
||||||
let function_call_output: FunctionCallOutputPayload =
|
.map_err(FunctionCallError::RespondToModel)?;
|
||||||
crate::exec_command::result_into_payload(result);
|
|
||||||
ResponseInputItem::FunctionCallOutput {
|
Ok(result.to_text_output())
|
||||||
call_id,
|
|
||||||
output: function_call_output,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
match sess.mcp_connection_manager.parse_tool_name(&name) {
|
|
||||||
Some((server, tool_name)) => {
|
|
||||||
handle_mcp_tool_call(sess, &sub_id, call_id, server, tool_name, arguments).await
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
// Unknown function: reply with structured failure so the model can adapt.
|
|
||||||
ResponseInputItem::FunctionCallOutput {
|
|
||||||
call_id,
|
|
||||||
output: FunctionCallOutputPayload {
|
|
||||||
content: format!("unsupported call: {name}"),
|
|
||||||
success: None,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
_ => Err(FunctionCallError::RespondToModel(format!(
|
||||||
|
"unsupported call: {name}"
|
||||||
|
))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2606,7 +2567,7 @@ async fn handle_custom_tool_call(
|
|||||||
name: String,
|
name: String,
|
||||||
input: String,
|
input: String,
|
||||||
call_id: String,
|
call_id: String,
|
||||||
) -> ResponseInputItem {
|
) -> Result<String, FunctionCallError> {
|
||||||
info!("CustomToolCall: {name} {input}");
|
info!("CustomToolCall: {name} {input}");
|
||||||
match name.as_str() {
|
match name.as_str() {
|
||||||
"apply_patch" => {
|
"apply_patch" => {
|
||||||
@@ -2618,7 +2579,8 @@ async fn handle_custom_tool_call(
|
|||||||
with_escalated_permissions: None,
|
with_escalated_permissions: None,
|
||||||
justification: None,
|
justification: None,
|
||||||
};
|
};
|
||||||
let resp = handle_container_exec_with_params(
|
|
||||||
|
handle_container_exec_with_params(
|
||||||
exec_params,
|
exec_params,
|
||||||
sess,
|
sess,
|
||||||
turn_context,
|
turn_context,
|
||||||
@@ -2626,26 +2588,13 @@ async fn handle_custom_tool_call(
|
|||||||
sub_id,
|
sub_id,
|
||||||
call_id,
|
call_id,
|
||||||
)
|
)
|
||||||
.await;
|
.await
|
||||||
|
|
||||||
// Convert function-call style output into a custom tool call output
|
|
||||||
match resp {
|
|
||||||
ResponseInputItem::FunctionCallOutput { call_id, output } => {
|
|
||||||
ResponseInputItem::CustomToolCallOutput {
|
|
||||||
call_id,
|
|
||||||
output: output.content,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Pass through if already a custom tool output or other variant
|
|
||||||
other => other,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
debug!("unexpected CustomToolCall from stream");
|
debug!("unexpected CustomToolCall from stream");
|
||||||
ResponseInputItem::CustomToolCallOutput {
|
Err(FunctionCallError::RespondToModel(format!(
|
||||||
call_id,
|
"unsupported custom tool call: {name}"
|
||||||
output: format!("unsupported custom tool call: {name}"),
|
)))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2664,23 +2613,13 @@ fn to_exec_params(params: ShellToolCallParams, turn_context: &TurnContext) -> Ex
|
|||||||
fn parse_container_exec_arguments(
|
fn parse_container_exec_arguments(
|
||||||
arguments: String,
|
arguments: String,
|
||||||
turn_context: &TurnContext,
|
turn_context: &TurnContext,
|
||||||
call_id: &str,
|
_call_id: &str,
|
||||||
) -> Result<ExecParams, Box<ResponseInputItem>> {
|
) -> Result<ExecParams, FunctionCallError> {
|
||||||
// parse command
|
serde_json::from_str::<ShellToolCallParams>(&arguments)
|
||||||
match serde_json::from_str::<ShellToolCallParams>(&arguments) {
|
.map(|p| to_exec_params(p, turn_context))
|
||||||
Ok(shell_tool_call_params) => Ok(to_exec_params(shell_tool_call_params, turn_context)),
|
.map_err(|e| {
|
||||||
Err(e) => {
|
FunctionCallError::RespondToModel(format!("failed to parse function arguments: {e:?}"))
|
||||||
// allow model to re-sample
|
})
|
||||||
let output = ResponseInputItem::FunctionCallOutput {
|
|
||||||
call_id: call_id.to_string(),
|
|
||||||
output: FunctionCallOutputPayload {
|
|
||||||
content: format!("failed to parse function arguments: {e}"),
|
|
||||||
success: None,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
Err(Box::new(output))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ExecInvokeArgs<'a> {
|
pub struct ExecInvokeArgs<'a> {
|
||||||
@@ -2717,20 +2656,14 @@ async fn handle_container_exec_with_params(
|
|||||||
turn_diff_tracker: &mut TurnDiffTracker,
|
turn_diff_tracker: &mut TurnDiffTracker,
|
||||||
sub_id: String,
|
sub_id: String,
|
||||||
call_id: String,
|
call_id: String,
|
||||||
) -> ResponseInputItem {
|
) -> Result<String, FunctionCallError> {
|
||||||
if params.with_escalated_permissions.unwrap_or(false)
|
if params.with_escalated_permissions.unwrap_or(false)
|
||||||
&& !matches!(turn_context.approval_policy, AskForApproval::OnRequest)
|
&& !matches!(turn_context.approval_policy, AskForApproval::OnRequest)
|
||||||
{
|
{
|
||||||
return ResponseInputItem::FunctionCallOutput {
|
return Err(FunctionCallError::RespondToModel(format!(
|
||||||
call_id,
|
"approval policy is {policy:?}; reject command — you should not ask for escalated permissions if the approval policy is {policy:?}",
|
||||||
output: FunctionCallOutputPayload {
|
policy = turn_context.approval_policy
|
||||||
content: format!(
|
)));
|
||||||
"approval policy is {policy:?}; reject command — you should not ask for escalated permissions if the approval policy is {policy:?}",
|
|
||||||
policy = turn_context.approval_policy
|
|
||||||
),
|
|
||||||
success: None,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if this was a patch, and apply it if so
|
// check if this was a patch, and apply it if so
|
||||||
@@ -2747,13 +2680,9 @@ async fn handle_container_exec_with_params(
|
|||||||
// It looks like an invocation of `apply_patch`, but we
|
// It looks like an invocation of `apply_patch`, but we
|
||||||
// could not resolve it into a patch that would apply
|
// could not resolve it into a patch that would apply
|
||||||
// cleanly. Return to model for resample.
|
// cleanly. Return to model for resample.
|
||||||
return ResponseInputItem::FunctionCallOutput {
|
return Err(FunctionCallError::RespondToModel(format!(
|
||||||
call_id,
|
"error: {parse_error:#?}"
|
||||||
output: FunctionCallOutputPayload {
|
)));
|
||||||
content: format!("error: {parse_error:#}"),
|
|
||||||
success: None,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
MaybeApplyPatchVerified::ShellParseError(error) => {
|
MaybeApplyPatchVerified::ShellParseError(error) => {
|
||||||
trace!("Failed to parse shell command, {error:?}");
|
trace!("Failed to parse shell command, {error:?}");
|
||||||
@@ -2771,13 +2700,9 @@ async fn handle_container_exec_with_params(
|
|||||||
.ok()
|
.ok()
|
||||||
.map(|p| p.to_string_lossy().to_string());
|
.map(|p| p.to_string_lossy().to_string());
|
||||||
let Some(path_to_codex) = path_to_codex else {
|
let Some(path_to_codex) = path_to_codex else {
|
||||||
return ResponseInputItem::FunctionCallOutput {
|
return Err(FunctionCallError::RespondToModel(
|
||||||
call_id,
|
"failed to determine path to codex executable".to_string(),
|
||||||
output: FunctionCallOutputPayload {
|
));
|
||||||
content: "failed to determine path to codex executable".to_string(),
|
|
||||||
success: None,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let params = ExecParams {
|
let params = ExecParams {
|
||||||
@@ -2843,13 +2768,9 @@ async fn handle_container_exec_with_params(
|
|||||||
sess.add_approved_command(params.command.clone()).await;
|
sess.add_approved_command(params.command.clone()).await;
|
||||||
}
|
}
|
||||||
ReviewDecision::Denied | ReviewDecision::Abort => {
|
ReviewDecision::Denied | ReviewDecision::Abort => {
|
||||||
return ResponseInputItem::FunctionCallOutput {
|
return Err(FunctionCallError::RespondToModel(
|
||||||
call_id,
|
"exec command rejected by user".to_string(),
|
||||||
output: FunctionCallOutputPayload {
|
));
|
||||||
content: "exec command rejected by user".to_string(),
|
|
||||||
success: None,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// No sandboxing is applied because the user has given
|
// No sandboxing is applied because the user has given
|
||||||
@@ -2859,13 +2780,9 @@ async fn handle_container_exec_with_params(
|
|||||||
SandboxType::None
|
SandboxType::None
|
||||||
}
|
}
|
||||||
SafetyCheck::Reject { reason } => {
|
SafetyCheck::Reject { reason } => {
|
||||||
return ResponseInputItem::FunctionCallOutput {
|
return Err(FunctionCallError::RespondToModel(format!(
|
||||||
call_id,
|
"exec command rejected: {reason:?}"
|
||||||
output: FunctionCallOutputPayload {
|
)));
|
||||||
content: format!("exec command rejected: {reason}"),
|
|
||||||
success: None,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2912,15 +2829,11 @@ async fn handle_container_exec_with_params(
|
|||||||
match output_result {
|
match output_result {
|
||||||
Ok(output) => {
|
Ok(output) => {
|
||||||
let ExecToolCallOutput { exit_code, .. } = &output;
|
let ExecToolCallOutput { exit_code, .. } = &output;
|
||||||
|
|
||||||
let is_success = *exit_code == 0;
|
|
||||||
let content = format_exec_output(&output);
|
let content = format_exec_output(&output);
|
||||||
ResponseInputItem::FunctionCallOutput {
|
if *exit_code == 0 {
|
||||||
call_id: call_id.clone(),
|
Ok(content)
|
||||||
output: FunctionCallOutputPayload {
|
} else {
|
||||||
content,
|
Err(FunctionCallError::RespondToModel(content))
|
||||||
success: Some(is_success),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(CodexErr::Sandbox(error)) => {
|
Err(CodexErr::Sandbox(error)) => {
|
||||||
@@ -2935,13 +2848,9 @@ async fn handle_container_exec_with_params(
|
|||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
Err(e) => ResponseInputItem::FunctionCallOutput {
|
Err(e) => Err(FunctionCallError::RespondToModel(format!(
|
||||||
call_id: call_id.clone(),
|
"execution error: {e:?}"
|
||||||
output: FunctionCallOutputPayload {
|
))),
|
||||||
content: format!("execution error: {e}"),
|
|
||||||
success: None,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2953,35 +2862,23 @@ async fn handle_sandbox_error(
|
|||||||
sandbox_type: SandboxType,
|
sandbox_type: SandboxType,
|
||||||
sess: &Session,
|
sess: &Session,
|
||||||
turn_context: &TurnContext,
|
turn_context: &TurnContext,
|
||||||
) -> ResponseInputItem {
|
) -> Result<String, FunctionCallError> {
|
||||||
let call_id = exec_command_context.call_id.clone();
|
let call_id = exec_command_context.call_id.clone();
|
||||||
let sub_id = exec_command_context.sub_id.clone();
|
let sub_id = exec_command_context.sub_id.clone();
|
||||||
let cwd = exec_command_context.cwd.clone();
|
let cwd = exec_command_context.cwd.clone();
|
||||||
|
|
||||||
if let SandboxErr::Timeout { output } = &error {
|
if let SandboxErr::Timeout { output } = &error {
|
||||||
let content = format_exec_output(output);
|
let content = format_exec_output(output);
|
||||||
return ResponseInputItem::FunctionCallOutput {
|
return Err(FunctionCallError::RespondToModel(content));
|
||||||
call_id,
|
|
||||||
output: FunctionCallOutputPayload {
|
|
||||||
content,
|
|
||||||
success: Some(false),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Early out if either the user never wants to be asked for approval, or
|
// Early out if either the user never wants to be asked for approval, or
|
||||||
// we're letting the model manage escalation requests. Otherwise, continue
|
// we're letting the model manage escalation requests. Otherwise, continue
|
||||||
match turn_context.approval_policy {
|
match turn_context.approval_policy {
|
||||||
AskForApproval::Never | AskForApproval::OnRequest => {
|
AskForApproval::Never | AskForApproval::OnRequest => {
|
||||||
return ResponseInputItem::FunctionCallOutput {
|
return Err(FunctionCallError::RespondToModel(format!(
|
||||||
call_id,
|
"failed in sandbox {sandbox_type:?} with execution error: {error:?}"
|
||||||
output: FunctionCallOutputPayload {
|
)));
|
||||||
content: format!(
|
|
||||||
"failed in sandbox {sandbox_type:?} with execution error: {error}"
|
|
||||||
),
|
|
||||||
success: Some(false),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
AskForApproval::UnlessTrusted | AskForApproval::OnFailure => (),
|
AskForApproval::UnlessTrusted | AskForApproval::OnFailure => (),
|
||||||
}
|
}
|
||||||
@@ -3047,36 +2944,23 @@ async fn handle_sandbox_error(
|
|||||||
match retry_output_result {
|
match retry_output_result {
|
||||||
Ok(retry_output) => {
|
Ok(retry_output) => {
|
||||||
let ExecToolCallOutput { exit_code, .. } = &retry_output;
|
let ExecToolCallOutput { exit_code, .. } = &retry_output;
|
||||||
|
|
||||||
let is_success = *exit_code == 0;
|
|
||||||
let content = format_exec_output(&retry_output);
|
let content = format_exec_output(&retry_output);
|
||||||
|
if *exit_code == 0 {
|
||||||
ResponseInputItem::FunctionCallOutput {
|
Ok(content)
|
||||||
call_id: call_id.clone(),
|
} else {
|
||||||
output: FunctionCallOutputPayload {
|
Err(FunctionCallError::RespondToModel(content))
|
||||||
content,
|
|
||||||
success: Some(is_success),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => ResponseInputItem::FunctionCallOutput {
|
Err(e) => Err(FunctionCallError::RespondToModel(format!(
|
||||||
call_id: call_id.clone(),
|
"retry failed: {e}"
|
||||||
output: FunctionCallOutputPayload {
|
))),
|
||||||
content: format!("retry failed: {e}"),
|
|
||||||
success: None,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ReviewDecision::Denied | ReviewDecision::Abort => {
|
ReviewDecision::Denied | ReviewDecision::Abort => {
|
||||||
// Fall through to original failure handling.
|
// Fall through to original failure handling.
|
||||||
ResponseInputItem::FunctionCallOutput {
|
Err(FunctionCallError::RespondToModel(
|
||||||
call_id,
|
"exec command rejected by user".to_string(),
|
||||||
output: FunctionCallOutputPayload {
|
))
|
||||||
content: "exec command rejected by user".to_string(),
|
|
||||||
success: None,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3798,8 +3682,8 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let ResponseInputItem::FunctionCallOutput { output, .. } = resp else {
|
let Err(FunctionCallError::RespondToModel(output)) = resp else {
|
||||||
panic!("expected FunctionCallOutput");
|
panic!("expected error result");
|
||||||
};
|
};
|
||||||
|
|
||||||
let expected = format!(
|
let expected = format!(
|
||||||
@@ -3807,7 +3691,7 @@ mod tests {
|
|||||||
policy = turn_context.approval_policy
|
policy = turn_context.approval_policy
|
||||||
);
|
);
|
||||||
|
|
||||||
pretty_assertions::assert_eq!(output.content, expected);
|
pretty_assertions::assert_eq!(output, expected);
|
||||||
|
|
||||||
// Now retry the same command WITHOUT escalated permissions; should succeed.
|
// Now retry the same command WITHOUT escalated permissions; should succeed.
|
||||||
// Force DangerFullAccess to avoid platform sandbox dependencies in tests.
|
// Force DangerFullAccess to avoid platform sandbox dependencies in tests.
|
||||||
@@ -3823,9 +3707,7 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let ResponseInputItem::FunctionCallOutput { output, .. } = resp2 else {
|
let output = resp2.expect("expected Ok result");
|
||||||
panic!("expected FunctionCallOutput on retry");
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Deserialize, PartialEq, Eq, Debug)]
|
#[derive(Deserialize, PartialEq, Eq, Debug)]
|
||||||
struct ResponseExecMetadata {
|
struct ResponseExecMetadata {
|
||||||
@@ -3839,10 +3721,9 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let exec_output: ResponseExecOutput =
|
let exec_output: ResponseExecOutput =
|
||||||
serde_json::from_str(&output.content).expect("valid exec output json");
|
serde_json::from_str(&output).expect("valid exec output json");
|
||||||
|
|
||||||
pretty_assertions::assert_eq!(exec_output.metadata, ResponseExecMetadata { exit_code: 0 });
|
pretty_assertions::assert_eq!(exec_output.metadata, ResponseExecMetadata { exit_code: 0 });
|
||||||
assert!(exec_output.output.contains("hi"));
|
assert!(exec_output.output.contains("hi"));
|
||||||
pretty_assertions::assert_eq!(output.success, Some(true));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,4 +12,3 @@ pub use responses_api::WRITE_STDIN_TOOL_NAME;
|
|||||||
pub use responses_api::create_exec_command_tool_for_responses_api;
|
pub use responses_api::create_exec_command_tool_for_responses_api;
|
||||||
pub use responses_api::create_write_stdin_tool_for_responses_api;
|
pub use responses_api::create_write_stdin_tool_for_responses_api;
|
||||||
pub use session_manager::SessionManager as ExecSessionManager;
|
pub use session_manager::SessionManager as ExecSessionManager;
|
||||||
pub use session_manager::result_into_payload;
|
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ use crate::exec_command::exec_command_params::WriteStdinParams;
|
|||||||
use crate::exec_command::exec_command_session::ExecCommandSession;
|
use crate::exec_command::exec_command_session::ExecCommandSession;
|
||||||
use crate::exec_command::session_id::SessionId;
|
use crate::exec_command::session_id::SessionId;
|
||||||
use crate::truncate::truncate_middle;
|
use crate::truncate::truncate_middle;
|
||||||
use codex_protocol::models::FunctionCallOutputPayload;
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct SessionManager {
|
pub struct SessionManager {
|
||||||
@@ -38,7 +37,7 @@ pub struct ExecCommandOutput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ExecCommandOutput {
|
impl ExecCommandOutput {
|
||||||
fn to_text_output(&self) -> String {
|
pub(crate) fn to_text_output(&self) -> String {
|
||||||
let wall_time_secs = self.wall_time.as_secs_f32();
|
let wall_time_secs = self.wall_time.as_secs_f32();
|
||||||
let termination_status = match self.exit_status {
|
let termination_status = match self.exit_status {
|
||||||
ExitStatus::Exited(code) => format!("Process exited with code {code}"),
|
ExitStatus::Exited(code) => format!("Process exited with code {code}"),
|
||||||
@@ -68,19 +67,6 @@ pub enum ExitStatus {
|
|||||||
Ongoing(SessionId),
|
Ongoing(SessionId),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn result_into_payload(result: Result<ExecCommandOutput, String>) -> FunctionCallOutputPayload {
|
|
||||||
match result {
|
|
||||||
Ok(output) => FunctionCallOutputPayload {
|
|
||||||
content: output.to_text_output(),
|
|
||||||
success: Some(true),
|
|
||||||
},
|
|
||||||
Err(err) => FunctionCallOutputPayload {
|
|
||||||
content: err,
|
|
||||||
success: Some(false),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SessionManager {
|
impl SessionManager {
|
||||||
/// Processes the request and is required to send a response via `outgoing`.
|
/// Processes the request and is required to send a response via `outgoing`.
|
||||||
pub async fn handle_exec_command_request(
|
pub async fn handle_exec_command_request(
|
||||||
|
|||||||
7
codex-rs/core/src/function_tool.rs
Normal file
7
codex-rs/core/src/function_tool.rs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Debug, Error, PartialEq)]
|
||||||
|
pub enum FunctionCallError {
|
||||||
|
#[error("{0}")]
|
||||||
|
RespondToModel(String),
|
||||||
|
}
|
||||||
@@ -75,6 +75,7 @@ pub use rollout::find_conversation_path_by_id_str;
|
|||||||
pub use rollout::list::ConversationItem;
|
pub use rollout::list::ConversationItem;
|
||||||
pub use rollout::list::ConversationsPage;
|
pub use rollout::list::ConversationsPage;
|
||||||
pub use rollout::list::Cursor;
|
pub use rollout::list::Cursor;
|
||||||
|
mod function_tool;
|
||||||
mod user_notification;
|
mod user_notification;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
|
||||||
|
|||||||
@@ -2,13 +2,12 @@ use std::collections::BTreeMap;
|
|||||||
use std::sync::LazyLock;
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
use crate::codex::Session;
|
use crate::codex::Session;
|
||||||
|
use crate::function_tool::FunctionCallError;
|
||||||
use crate::openai_tools::JsonSchema;
|
use crate::openai_tools::JsonSchema;
|
||||||
use crate::openai_tools::OpenAiTool;
|
use crate::openai_tools::OpenAiTool;
|
||||||
use crate::openai_tools::ResponsesApiTool;
|
use crate::openai_tools::ResponsesApiTool;
|
||||||
use crate::protocol::Event;
|
use crate::protocol::Event;
|
||||||
use crate::protocol::EventMsg;
|
use crate::protocol::EventMsg;
|
||||||
use codex_protocol::models::FunctionCallOutputPayload;
|
|
||||||
use codex_protocol::models::ResponseInputItem;
|
|
||||||
|
|
||||||
// Use the canonical plan tool types from the protocol crate to ensure
|
// Use the canonical plan tool types from the protocol crate to ensure
|
||||||
// type-identity matches events transported via `codex_protocol`.
|
// type-identity matches events transported via `codex_protocol`.
|
||||||
@@ -67,44 +66,20 @@ pub(crate) async fn handle_update_plan(
|
|||||||
session: &Session,
|
session: &Session,
|
||||||
arguments: String,
|
arguments: String,
|
||||||
sub_id: String,
|
sub_id: String,
|
||||||
call_id: String,
|
_call_id: String,
|
||||||
) -> ResponseInputItem {
|
) -> Result<String, FunctionCallError> {
|
||||||
match parse_update_plan_arguments(arguments, &call_id) {
|
let args = parse_update_plan_arguments(&arguments)?;
|
||||||
Ok(args) => {
|
session
|
||||||
let output = ResponseInputItem::FunctionCallOutput {
|
.send_event(Event {
|
||||||
call_id,
|
id: sub_id.to_string(),
|
||||||
output: FunctionCallOutputPayload {
|
msg: EventMsg::PlanUpdate(args),
|
||||||
content: "Plan updated".to_string(),
|
})
|
||||||
success: Some(true),
|
.await;
|
||||||
},
|
Ok("Plan updated".to_string())
|
||||||
};
|
|
||||||
session
|
|
||||||
.send_event(Event {
|
|
||||||
id: sub_id.to_string(),
|
|
||||||
msg: EventMsg::PlanUpdate(args),
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
output
|
|
||||||
}
|
|
||||||
Err(output) => *output,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_update_plan_arguments(
|
fn parse_update_plan_arguments(arguments: &str) -> Result<UpdatePlanArgs, FunctionCallError> {
|
||||||
arguments: String,
|
serde_json::from_str::<UpdatePlanArgs>(arguments).map_err(|e| {
|
||||||
call_id: &str,
|
FunctionCallError::RespondToModel(format!("failed to parse function arguments: {e}"))
|
||||||
) -> Result<UpdatePlanArgs, Box<ResponseInputItem>> {
|
})
|
||||||
match serde_json::from_str::<UpdatePlanArgs>(&arguments) {
|
|
||||||
Ok(args) => Ok(args),
|
|
||||||
Err(e) => {
|
|
||||||
let output = ResponseInputItem::FunctionCallOutput {
|
|
||||||
call_id: call_id.to_string(),
|
|
||||||
output: FunctionCallOutputPayload {
|
|
||||||
content: format!("failed to parse function arguments: {e}"),
|
|
||||||
success: None,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
Err(Box::new(output))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user