mirror of
https://github.com/openai/codex.git
synced 2026-09-15 12:08:01 +00:00
Feedback: dynamic tool result enum
This commit is contained in:
@@ -2671,16 +2671,23 @@ pub struct DynamicToolCallParams {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct DynamicToolCallResponse {
|
||||
#[serde(flatten)]
|
||||
pub result: DynamicToolCallResult,
|
||||
pub success: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(untagged, rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum DynamicToolCallResult {
|
||||
/// Preferred structured tool output (for example text + images) that is
|
||||
/// forwarded directly to the model as content items.
|
||||
///
|
||||
/// At least one of `content_items` or `output` must be set.
|
||||
pub content_items: Option<Vec<DynamicToolCallOutputContentItem>>,
|
||||
ContentItems {
|
||||
#[serde(rename = "contentItems")]
|
||||
content_items: Vec<DynamicToolCallOutputContentItem>,
|
||||
},
|
||||
/// Legacy plain-text tool output.
|
||||
///
|
||||
/// At least one of `content_items` or `output` must be set.
|
||||
pub output: Option<String>,
|
||||
pub success: bool,
|
||||
Output { output: String },
|
||||
}
|
||||
|
||||
/// App-server-facing dynamic tool output items.
|
||||
@@ -3089,10 +3096,11 @@ mod tests {
|
||||
#[test]
|
||||
fn dynamic_tool_response_serializes_content_items() {
|
||||
let value = serde_json::to_value(DynamicToolCallResponse {
|
||||
content_items: Some(vec![DynamicToolCallOutputContentItem::InputText {
|
||||
text: "dynamic-ok".to_string(),
|
||||
}]),
|
||||
output: None,
|
||||
result: DynamicToolCallResult::ContentItems {
|
||||
content_items: vec![DynamicToolCallOutputContentItem::InputText {
|
||||
text: "dynamic-ok".to_string(),
|
||||
}],
|
||||
},
|
||||
success: true,
|
||||
})
|
||||
.unwrap();
|
||||
@@ -3100,7 +3108,6 @@ mod tests {
|
||||
assert_eq!(
|
||||
value,
|
||||
json!({
|
||||
"output": null,
|
||||
"success": true,
|
||||
"contentItems": [
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use codex_app_server_protocol::DynamicToolCallResponse;
|
||||
use codex_app_server_protocol::DynamicToolCallResult;
|
||||
use codex_core::CodexThread;
|
||||
use codex_protocol::dynamic_tools::DynamicToolResponse as CoreDynamicToolResponse;
|
||||
use codex_protocol::models::FunctionCallOutputContentItem;
|
||||
use codex_protocol::protocol::Op;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::oneshot;
|
||||
@@ -36,31 +36,26 @@ pub(crate) async fn on_call_response(
|
||||
}
|
||||
};
|
||||
|
||||
let mut response =
|
||||
serde_json::from_value::<DynamicToolCallResponse>(value).unwrap_or_else(|err| {
|
||||
error!("failed to deserialize DynamicToolCallResponse: {err}");
|
||||
DynamicToolCallResponse {
|
||||
content_items: None,
|
||||
output: Some("dynamic tool response was invalid".to_string()),
|
||||
success: false,
|
||||
}
|
||||
});
|
||||
|
||||
if response.content_items.is_none() && response.output.is_none() {
|
||||
error!("dynamic tool response must include output or contentItems");
|
||||
response.output = Some("dynamic tool response must include output or contentItems".into());
|
||||
response.success = false;
|
||||
}
|
||||
|
||||
let content_items = response.content_items.map(|items| {
|
||||
items
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect::<Vec<FunctionCallOutputContentItem>>()
|
||||
let response = serde_json::from_value::<DynamicToolCallResponse>(value).unwrap_or_else(|err| {
|
||||
error!("failed to deserialize DynamicToolCallResponse: {err}");
|
||||
DynamicToolCallResponse {
|
||||
result: DynamicToolCallResult::Output {
|
||||
output: "dynamic tool response was invalid".to_string(),
|
||||
},
|
||||
success: false,
|
||||
}
|
||||
});
|
||||
|
||||
let (output, content_items) = match response.result {
|
||||
DynamicToolCallResult::ContentItems { content_items } => (
|
||||
None,
|
||||
Some(content_items.into_iter().map(Into::into).collect()),
|
||||
),
|
||||
DynamicToolCallResult::Output { output } => (Some(output), None),
|
||||
};
|
||||
let response = CoreDynamicToolResponse {
|
||||
call_id: call_id.clone(),
|
||||
output: response.output,
|
||||
output,
|
||||
success: response.success,
|
||||
content_items,
|
||||
};
|
||||
|
||||
@@ -7,6 +7,7 @@ use app_test_support::to_response;
|
||||
use codex_app_server_protocol::DynamicToolCallOutputContentItem;
|
||||
use codex_app_server_protocol::DynamicToolCallParams;
|
||||
use codex_app_server_protocol::DynamicToolCallResponse;
|
||||
use codex_app_server_protocol::DynamicToolCallResult;
|
||||
use codex_app_server_protocol::DynamicToolSpec;
|
||||
use codex_app_server_protocol::JSONRPCResponse;
|
||||
use codex_app_server_protocol::RequestId;
|
||||
@@ -203,9 +204,10 @@ async fn dynamic_tool_call_round_trip_sends_output_to_model() -> Result<()> {
|
||||
|
||||
// Respond to the tool call so the model receives a function_call_output.
|
||||
let response = DynamicToolCallResponse {
|
||||
output: Some("dynamic-ok".to_string()),
|
||||
result: DynamicToolCallResult::Output {
|
||||
output: "dynamic-ok".to_string(),
|
||||
},
|
||||
success: true,
|
||||
content_items: None,
|
||||
};
|
||||
mcp.send_response(request_id, serde_json::to_value(response)?)
|
||||
.await?;
|
||||
@@ -331,9 +333,10 @@ async fn dynamic_tool_call_round_trip_sends_content_items_to_model() -> Result<(
|
||||
.map(Into::into)
|
||||
.collect::<Vec<FunctionCallOutputContentItem>>();
|
||||
let response = DynamicToolCallResponse {
|
||||
output: None,
|
||||
result: DynamicToolCallResult::ContentItems {
|
||||
content_items: response_content_items,
|
||||
},
|
||||
success: true,
|
||||
content_items: Some(response_content_items),
|
||||
};
|
||||
mcp.send_response(request_id, serde_json::to_value(response)?)
|
||||
.await?;
|
||||
|
||||
Reference in New Issue
Block a user