denormalize sandbox and support input/output uuid collapse

This commit is contained in:
Roy Han
2026-03-20 14:19:33 -07:00
parent 894aa05eb8
commit 6fe352a5fb
12 changed files with 381 additions and 50 deletions
+27 -27
View File
@@ -1071,6 +1071,17 @@ fn session_source_metadata_patch(session_source: &SessionSource) -> ResponseItem
}
}
fn base_item_metadata_patch(turn_context: &TurnContext) -> ResponseItemMetadata {
let mut patch = ResponseItemMetadata {
sandbox_policy: Some(sandbox_policy_to_metadata(
turn_context.sandbox_policy.get(),
)),
..ResponseItemMetadata::default()
};
patch.merge_from(session_source_metadata_patch(&turn_context.session_source));
patch
}
fn response_item_tool_call_id(item: &ResponseItem) -> Option<&str> {
match item {
ResponseItem::LocalShellCall {
@@ -1146,7 +1157,6 @@ struct ToolApprovalMetadataSnapshot {
}
fn stamp_tool_approval_metadata_with_snapshot(
turn_context: &TurnContext,
response_item: ResponseItem,
snapshot: Option<&ToolApprovalMetadataSnapshot>,
) -> ResponseItem {
@@ -1164,9 +1174,6 @@ fn stamp_tool_approval_metadata_with_snapshot(
Some(metadata) => metadata,
None => return response_item,
};
metadata.sandbox_policy = Some(sandbox_policy_to_metadata(
turn_context.sandbox_policy.get(),
));
match outcome {
Some(outcome) => {
@@ -3458,7 +3465,7 @@ impl Session {
pub(crate) async fn stamp_tool_approval_metadata(
&self,
turn_context: &TurnContext,
_turn_context: &TurnContext,
response_item: ResponseItem,
) -> ResponseItem {
if !self.enabled(Feature::ItemMetadata) {
@@ -3477,12 +3484,12 @@ impl Session {
pending_approval_call_ids,
}
};
stamp_tool_approval_metadata_with_snapshot(turn_context, response_item, Some(&snapshot))
stamp_tool_approval_metadata_with_snapshot(response_item, Some(&snapshot))
}
pub(crate) async fn stamp_tool_approval_metadata_on_items(
&self,
turn_context: &TurnContext,
_turn_context: &TurnContext,
response_items: Vec<ResponseItem>,
) -> Vec<ResponseItem> {
if !self.enabled(Feature::ItemMetadata) {
@@ -3504,9 +3511,7 @@ impl Session {
response_items
.into_iter()
.map(|item| {
stamp_tool_approval_metadata_with_snapshot(turn_context, item, Some(&snapshot))
})
.map(|item| stamp_tool_approval_metadata_with_snapshot(item, Some(&snapshot)))
.collect()
}
@@ -3548,7 +3553,7 @@ impl Session {
turn_context: &TurnContext,
items: &[ResponseItem],
) {
let items = self.prepare_history_items(items);
let items = self.prepare_history_items(items, turn_context);
self.record_into_history_prepared(&items, turn_context)
.await;
self.persist_rollout_response_items(&items).await;
@@ -3561,17 +3566,25 @@ impl Session {
items: &[ResponseItem],
turn_context: &TurnContext,
) {
let items = self.prepare_history_items(items);
let items = self.prepare_history_items(items, turn_context);
self.record_into_history_prepared(&items, turn_context)
.await;
}
fn prepare_history_items(&self, items: &[ResponseItem]) -> Vec<ResponseItem> {
fn prepare_history_items(
&self,
items: &[ResponseItem],
turn_context: &TurnContext,
) -> Vec<ResponseItem> {
if self.enabled(Feature::ItemMetadata) {
let metadata_patch = base_item_metadata_patch(turn_context);
items
.iter()
.cloned()
.map(ResponseItem::with_generated_metadata_uuid)
.map(|item| {
stamp_response_item_metadata_on_response_item(item, metadata_patch.clone())
})
.collect()
} else {
items.to_vec()
@@ -4043,14 +4056,6 @@ impl Session {
turn_context: &TurnContext,
response_item: ResponseItem,
) {
let response_item = if self.enabled(Feature::ItemMetadata) {
stamp_response_item_metadata_on_response_item(
response_item,
session_source_metadata_patch(&turn_context.session_source),
)
} else {
response_item
};
let response_item = self
.stamp_tool_approval_metadata(turn_context, response_item)
.await;
@@ -4074,12 +4079,7 @@ impl Session {
message_metadata: Option<ResponseItemMetadata>,
) {
let metadata_patch = if self.enabled(Feature::ItemMetadata) {
let mut patch = message_metadata.unwrap_or_default();
patch.sandbox_policy = Some(sandbox_policy_to_metadata(
turn_context.sandbox_policy.get(),
));
patch.merge_from(session_source_metadata_patch(&turn_context.session_source));
Some(patch)
Some(message_metadata.unwrap_or_default())
} else {
None
};
+7 -1
View File
@@ -4402,6 +4402,7 @@ async fn task_finish_emits_prompt_queued_metadata_for_injected_user_input_when_f
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn record_response_item_stamps_session_source_on_all_supported_variants() {
let (mut sess, tc, _rx) = make_session_and_context_with_rx().await;
let expected_sandbox_policy = sandbox_policy_to_metadata(tc.sandbox_policy.get());
Arc::get_mut(&mut sess)
.expect("session should be uniquely owned in this test")
.features
@@ -4589,7 +4590,10 @@ async fn record_response_item_stamps_session_source_on_all_supported_variants()
| ResponseItem::Compaction {
metadata: Some(metadata),
..
} => metadata.session_source == Some(expected_session_source.clone()),
} => {
metadata.session_source == Some(expected_session_source.clone())
&& metadata.sandbox_policy == Some(expected_sandbox_policy.clone())
}
_ => false,
})
.count();
@@ -4815,6 +4819,7 @@ async fn tool_call_metadata_stamps_non_escalated_false_when_feature_enabled() {
async fn handle_output_item_done_stamps_tool_call_metadata_when_feature_enabled() {
let (mut sess, tc, _rx) = make_session_and_context_with_rx().await;
let expected_sandbox_policy = sandbox_policy_to_metadata(tc.sandbox_policy.get());
let expected_session_source = session_source_to_metadata(&tc.session_source);
Arc::get_mut(&mut sess)
.expect("session should be uniquely owned in this test")
.features
@@ -4865,6 +4870,7 @@ async fn handle_output_item_done_stamps_tool_call_metadata_when_feature_enabled(
&& metadata.is_tool_call_escalated == Some(false)
&& metadata.review_decision.is_none()
&& metadata.sandbox_policy == Some(expected_sandbox_policy.clone())
&& metadata.session_source == Some(expected_session_source.clone())
)
}));
}
+21 -11
View File
@@ -251,6 +251,7 @@ pub(crate) async fn handle_output_item_done(
body: FunctionCallOutputBody::Text(msg.to_string()),
..Default::default()
},
metadata: None,
};
record_completed_response_item(ctx.sess.as_ref(), ctx.turn_context.as_ref(), &item)
.await;
@@ -273,6 +274,7 @@ pub(crate) async fn handle_output_item_done(
body: FunctionCallOutputBody::Text(message),
..Default::default()
},
metadata: None,
};
record_completed_response_item(ctx.sess.as_ref(), ctx.turn_context.as_ref(), &item)
.await;
@@ -382,29 +384,36 @@ pub(crate) fn last_assistant_message_from_item(
pub(crate) fn response_input_to_response_item(input: &ResponseInputItem) -> Option<ResponseItem> {
match input {
ResponseInputItem::FunctionCallOutput { call_id, output } => {
Some(ResponseItem::FunctionCallOutput {
call_id: call_id.clone(),
output: output.clone(),
metadata: None,
})
}
ResponseInputItem::FunctionCallOutput {
call_id,
output,
metadata,
} => Some(ResponseItem::FunctionCallOutput {
call_id: call_id.clone(),
output: output.clone(),
metadata: metadata.clone(),
}),
ResponseInputItem::CustomToolCallOutput {
call_id,
name,
output,
metadata,
} => Some(ResponseItem::CustomToolCallOutput {
call_id: call_id.clone(),
name: name.clone(),
output: output.clone(),
metadata: None,
metadata: metadata.clone(),
}),
ResponseInputItem::McpToolCallOutput { call_id, output } => {
ResponseInputItem::McpToolCallOutput {
call_id,
output,
metadata,
} => {
let output = output.as_function_call_output_payload();
Some(ResponseItem::FunctionCallOutput {
call_id: call_id.clone(),
output,
metadata: None,
metadata: metadata.clone(),
})
}
ResponseInputItem::ToolSearchOutput {
@@ -412,12 +421,13 @@ pub(crate) fn response_input_to_response_item(input: &ResponseInputItem) -> Opti
status,
execution,
tools,
metadata,
} => Some(ResponseItem::ToolSearchOutput {
call_id: Some(call_id.clone()),
status: status.clone(),
execution: execution.clone(),
tools: tools.clone(),
metadata: None,
metadata: metadata.clone(),
}),
_ => None,
}
@@ -87,6 +87,7 @@ impl CodeModeProcess {
call_id: notify.call_id.clone(),
name: Some(PUBLIC_TOOL_NAME.to_string()),
output: FunctionCallOutputPayload::from_text(notify.text),
metadata: None,
}])
.await
.is_err()
+6
View File
@@ -104,6 +104,7 @@ impl ToolOutput for CallToolResult {
ResponseInputItem::McpToolCallOutput {
call_id: call_id.to_string(),
output: self.clone(),
metadata: None,
}
}
@@ -151,6 +152,7 @@ impl ToolOutput for ToolSearchOutput {
})
})
.collect(),
metadata: None,
}
}
}
@@ -254,10 +256,12 @@ impl ToolOutput for AbortedToolOutput {
status: "completed".to_string(),
execution: "client".to_string(),
tools: Vec::new(),
metadata: None,
},
ToolPayload::Mcp { .. } => ResponseInputItem::McpToolCallOutput {
call_id: call_id.to_string(),
output: CallToolResult::from_error_text(self.message.clone()),
metadata: None,
},
_ => function_tool_response(
call_id,
@@ -454,12 +458,14 @@ fn function_tool_response(
call_id: call_id.to_string(),
name: None,
output: FunctionCallOutputPayload { body, success },
metadata: None,
};
}
ResponseInputItem::FunctionCallOutput {
call_id: call_id.to_string(),
output: FunctionCallOutputPayload { body, success },
metadata: None,
}
}
+7 -2
View File
@@ -33,7 +33,9 @@ fn function_payloads_remain_function_outputs() {
.to_response_item("fn-1", &payload);
match response {
ResponseInputItem::FunctionCallOutput { call_id, output } => {
ResponseInputItem::FunctionCallOutput {
call_id, output, ..
} => {
assert_eq!(call_id, "fn-1");
assert_eq!(output.content_items(), None);
assert_eq!(output.body.to_text().as_deref(), Some("ok"));
@@ -164,6 +166,7 @@ fn tool_search_payloads_roundtrip_as_tool_search_outputs() {
status,
execution,
tools,
..
} => {
assert_eq!(call_id, "search-1");
assert_eq!(status, "completed");
@@ -258,7 +261,9 @@ fn exec_command_tool_output_formats_truncated_response() {
.to_response_item("call-42", &payload);
match response {
ResponseInputItem::FunctionCallOutput { call_id, output } => {
ResponseInputItem::FunctionCallOutput {
call_id, output, ..
} => {
assert_eq!(call_id, "call-42");
assert_eq!(output.success, Some(true));
let text = output
+1
View File
@@ -41,6 +41,7 @@ impl ToolOutput for PlanToolOutput {
ResponseInputItem::FunctionCallOutput {
call_id: call_id.to_string(),
output,
metadata: None,
}
}
@@ -191,6 +191,7 @@ impl ToolOutput for ViewImageOutput {
ResponseInputItem::FunctionCallOutput {
call_id: call_id.to_string(),
output,
metadata: None,
}
}
@@ -249,6 +249,7 @@ fn summarize_tool_call_response_for_multimodal_function_output() {
detail: None,
},
]),
metadata: None,
};
let actual = JsReplManager::summarize_tool_call_response(&response);
@@ -383,6 +384,7 @@ fn summarize_tool_call_response_for_multimodal_custom_output() {
detail: None,
},
]),
metadata: None,
};
let actual = JsReplManager::summarize_tool_call_response(&response);
+3
View File
@@ -141,6 +141,7 @@ impl ToolCallRuntime {
status: "completed".to_string(),
execution: "client".to_string(),
tools: Vec::new(),
metadata: None,
},
ToolPayload::Custom { .. } => ResponseInputItem::CustomToolCallOutput {
call_id: call.call_id,
@@ -149,6 +150,7 @@ impl ToolCallRuntime {
body: codex_protocol::models::FunctionCallOutputBody::Text(message),
success: Some(false),
},
metadata: None,
},
_ => ResponseInputItem::FunctionCallOutput {
call_id: call.call_id,
@@ -156,6 +158,7 @@ impl ToolCallRuntime {
body: codex_protocol::models::FunctionCallOutputBody::Text(message),
success: Some(false),
},
metadata: None,
},
}
}
+144 -2
View File
@@ -36,6 +36,7 @@ use codex_protocol::openai_models::ModelsResponse;
use codex_protocol::openai_models::ReasoningEffort;
use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::Op;
use codex_protocol::protocol::RawResponseItemEvent;
use codex_protocol::protocol::RolloutItem;
use codex_protocol::protocol::RolloutLine;
use codex_protocol::protocol::SessionMeta;
@@ -58,6 +59,7 @@ use core_test_support::skip_if_no_network;
use core_test_support::test_codex::TestCodex;
use core_test_support::test_codex::test_codex;
use core_test_support::wait_for_event;
use core_test_support::wait_for_event_match;
use dunce::canonicalize as normalize_path;
use futures::StreamExt;
use pretty_assertions::assert_eq;
@@ -90,6 +92,33 @@ fn message_input_texts(item: &serde_json::Value) -> Vec<&str> {
.collect()
}
#[expect(clippy::unwrap_used)]
fn assistant_message_item_by_text<'a>(
input: &'a [serde_json::Value],
text: &str,
) -> &'a serde_json::Value {
input
.iter()
.find(|item| {
if item.get("type").and_then(serde_json::Value::as_str) != Some("message")
|| item.get("role").and_then(serde_json::Value::as_str) != Some("assistant")
{
return false;
}
item.get("content")
.and_then(serde_json::Value::as_array)
.is_some_and(|content| {
content.iter().any(|entry| {
entry.get("type").and_then(serde_json::Value::as_str) == Some("output_text")
&& entry.get("text").and_then(serde_json::Value::as_str) == Some(text)
})
})
})
.unwrap_or_else(|| {
panic!("submitted assistant message input item not found for text: {text}")
})
}
/// Writes an `auth.json` into the provided `codex_home` with the specified parameters.
/// Returns the fake JWT string written to `tokens.id_token`.
#[expect(clippy::unwrap_used)]
@@ -634,7 +663,14 @@ async fn includes_conversation_id_and_model_headers_in_request() {
)
.await;
let mut builder = test_codex().with_auth(CodexAuth::from_api_key("Test API Key"));
let mut builder = test_codex()
.with_auth(CodexAuth::from_api_key("Test API Key"))
.with_config(|config| {
config
.features
.enable(Feature::ItemMetadata)
.expect("feature flag should be enabled for this test");
});
let test = builder
.build(&server)
.await
@@ -2572,7 +2608,14 @@ async fn history_dedupes_streamed_and_final_messages_across_turns() {
let request_log = mount_sse_sequence(&server, vec![sse1.clone(), sse1.clone(), sse1]).await;
let mut builder = test_codex().with_auth(CodexAuth::from_api_key("Test API Key"));
let mut builder = test_codex()
.with_auth(CodexAuth::from_api_key("Test API Key"))
.with_config(|config| {
config
.features
.enable(Feature::ItemMetadata)
.expect("feature flag should be enabled for this test");
});
let codex = builder
.build(&server)
.await
@@ -2669,3 +2712,102 @@ async fn history_dedupes_streamed_and_final_messages_across_turns() {
"request 3 tail mismatch",
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn replayed_output_item_keeps_uuid_across_turns() {
skip_if_no_network!();
let server = MockServer::start().await;
let sse_raw = r##"[
{"type":"response.output_item.added", "item":{
"type":"message", "role":"assistant",
"content":[{"type":"output_text","text":""}]
}},
{"type":"response.output_text.delta", "delta":"Hey "},
{"type":"response.output_text.delta", "delta":"there"},
{"type":"response.output_text.delta", "delta":"!\n"},
{"type":"response.output_item.done", "item":{
"type":"message", "role":"assistant",
"content":[{"type":"output_text","text":"Hey there!\n"}]
}},
{"type":"response.completed", "response": {"id": "__ID__"}}
]"##;
let sse1 = core_test_support::load_sse_fixture_with_id_from_str(sse_raw, "resp1");
let request_log = mount_sse_sequence(&server, vec![sse1.clone(), sse1]).await;
let mut builder = test_codex()
.with_auth(CodexAuth::from_api_key("Test API Key"))
.with_config(|config| {
config
.features
.enable(Feature::ItemMetadata)
.expect("feature flag should be enabled for this test");
});
let codex = builder
.build(&server)
.await
.expect("create new conversation")
.codex;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
text: "U1".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
.await
.unwrap();
let original_uuid = wait_for_event_match(&codex, |ev| match ev {
EventMsg::RawResponseItem(RawResponseItemEvent {
item:
ResponseItem::Message {
role,
content,
metadata: Some(metadata),
..
},
}) if role == "assistant"
&& *content
== vec![ContentItem::OutputText {
text: "Hey there!\n".to_string(),
}] =>
{
metadata.uuid.clone()
}
_ => None,
})
.await;
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
text: "U2".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
.await
.unwrap();
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
let requests = request_log.requests();
assert_eq!(requests.len(), 2, "expected 2 requests (one per turn)");
let request_2_input = requests[1]
.body_json()
.get("input")
.and_then(serde_json::Value::as_array)
.cloned()
.expect("request 2 missing input array");
let replayed_uuid = assistant_message_item_by_text(&request_2_input, "Hey there!\n")
.get("metadata")
.and_then(serde_json::Value::as_object)
.and_then(|metadata| metadata.get("uuid"))
.and_then(serde_json::Value::as_str)
.expect("replayed assistant message should carry a uuid");
assert_eq!(replayed_uuid, original_uuid);
}
+161 -7
View File
@@ -237,10 +237,16 @@ pub enum ResponseInputItem {
#[ts(as = "FunctionCallOutputBody")]
#[schemars(with = "FunctionCallOutputBody")]
output: FunctionCallOutputPayload,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
metadata: Option<ResponseItemMetadata>,
},
McpToolCallOutput {
call_id: String,
output: CallToolResult,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
metadata: Option<ResponseItemMetadata>,
},
CustomToolCallOutput {
call_id: String,
@@ -250,6 +256,9 @@ pub enum ResponseInputItem {
#[ts(as = "FunctionCallOutputBody")]
#[schemars(with = "FunctionCallOutputBody")]
output: FunctionCallOutputPayload,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
metadata: Option<ResponseItemMetadata>,
},
ToolSearchOutput {
call_id: String,
@@ -257,6 +266,9 @@ pub enum ResponseInputItem {
execution: String,
#[ts(type = "unknown[]")]
tools: Vec<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
metadata: Option<ResponseItemMetadata>,
},
}
@@ -1328,40 +1340,50 @@ impl From<ResponseInputItem> for ResponseItem {
end_turn: None,
phase: None,
},
ResponseInputItem::FunctionCallOutput { call_id, output } => Self::FunctionCallOutput {
ResponseInputItem::FunctionCallOutput {
call_id,
output,
metadata: None,
metadata,
} => Self::FunctionCallOutput {
call_id,
output,
metadata,
},
ResponseInputItem::McpToolCallOutput { call_id, output } => {
ResponseInputItem::McpToolCallOutput {
call_id,
output,
metadata,
} => {
let output = output.into_function_call_output_payload();
Self::FunctionCallOutput {
call_id,
output,
metadata: None,
metadata,
}
}
ResponseInputItem::CustomToolCallOutput {
call_id,
name,
output,
metadata,
} => Self::CustomToolCallOutput {
call_id,
name,
output,
metadata: None,
metadata,
},
ResponseInputItem::ToolSearchOutput {
call_id,
status,
execution,
tools,
metadata,
} => Self::ToolSearchOutput {
call_id: Some(call_id),
status,
execution,
tools,
metadata: None,
metadata,
},
}
}
@@ -2661,6 +2683,7 @@ mod tests {
let item = ResponseInputItem::FunctionCallOutput {
call_id: "call1".into(),
output: FunctionCallOutputPayload::from_text("ok".into()),
metadata: None,
};
let json = serde_json::to_string(&item)?;
@@ -2679,6 +2702,7 @@ mod tests {
body: FunctionCallOutputBody::Text("bad".into()),
success: Some(false),
},
metadata: None,
};
let json = serde_json::to_string(&item)?;
@@ -2722,6 +2746,7 @@ mod tests {
let item = ResponseInputItem::FunctionCallOutput {
call_id: "call1".into(),
output: payload,
metadata: None,
};
let json = serde_json::to_string(&item)?;
@@ -2744,6 +2769,7 @@ mod tests {
detail: None,
},
]),
metadata: None,
};
let json = serde_json::to_string(&item)?;
@@ -2755,6 +2781,117 @@ mod tests {
Ok(())
}
#[test]
fn function_call_output_roundtrips_metadata() -> Result<()> {
let input = ResponseInputItem::FunctionCallOutput {
call_id: "call-1".to_string(),
output: FunctionCallOutputPayload::from_text("ok".to_string()),
metadata: Some(ResponseItemMetadata {
uuid: Some("uuid-1".to_string()),
session_source: Some(SessionSourceMetadata::Exec),
..ResponseItemMetadata::default()
}),
};
assert_eq!(
ResponseItem::from(input.clone()),
ResponseItem::FunctionCallOutput {
call_id: "call-1".to_string(),
output: FunctionCallOutputPayload::from_text("ok".to_string()),
metadata: Some(ResponseItemMetadata {
uuid: Some("uuid-1".to_string()),
session_source: Some(SessionSourceMetadata::Exec),
..ResponseItemMetadata::default()
}),
}
);
assert_eq!(
serde_json::to_value(input)?,
serde_json::json!({
"type": "function_call_output",
"call_id": "call-1",
"output": "ok",
"metadata": {
"uuid": "uuid-1",
"session_source": "exec"
},
})
);
Ok(())
}
#[test]
fn mcp_tool_call_output_roundtrips_metadata() -> Result<()> {
let input = ResponseInputItem::McpToolCallOutput {
call_id: "call-1".to_string(),
output: CallToolResult {
content: vec![serde_json::json!({"type":"text","text":"ok"})],
structured_content: None,
is_error: Some(false),
meta: None,
},
metadata: Some(ResponseItemMetadata {
uuid: Some("uuid-1".to_string()),
sandbox_policy: Some(SandboxPolicyMetadata::ReadOnly),
..ResponseItemMetadata::default()
}),
};
let expected_output = match &input {
ResponseInputItem::McpToolCallOutput { output, .. } => {
output.as_function_call_output_payload()
}
other => panic!("unexpected input variant: {other:?}"),
};
assert_eq!(
ResponseItem::from(input.clone()),
ResponseItem::FunctionCallOutput {
call_id: "call-1".to_string(),
output: expected_output,
metadata: Some(ResponseItemMetadata {
uuid: Some("uuid-1".to_string()),
sandbox_policy: Some(SandboxPolicyMetadata::ReadOnly),
..ResponseItemMetadata::default()
}),
}
);
Ok(())
}
#[test]
fn custom_tool_call_output_roundtrips_metadata() -> Result<()> {
let input = ResponseInputItem::CustomToolCallOutput {
call_id: "call-1".to_string(),
name: Some("custom_tool".to_string()),
output: FunctionCallOutputPayload::from_text("done".to_string()),
metadata: Some(ResponseItemMetadata {
uuid: Some("uuid-1".to_string()),
review_decision: Some(ReviewDecisionMetadata::Approved),
..ResponseItemMetadata::default()
}),
};
assert_eq!(
ResponseItem::from(input.clone()),
ResponseItem::CustomToolCallOutput {
call_id: "call-1".to_string(),
name: Some("custom_tool".to_string()),
output: FunctionCallOutputPayload::from_text("done".to_string()),
metadata: Some(ResponseItemMetadata {
uuid: Some("uuid-1".to_string()),
review_decision: Some(ReviewDecisionMetadata::Approved),
..ResponseItemMetadata::default()
}),
}
);
Ok(())
}
#[test]
fn preserves_existing_image_data_urls() -> Result<()> {
let call_tool_result = CallToolResult {
@@ -3037,6 +3174,12 @@ mod tests {
"additionalProperties": false,
}
})],
metadata: Some(ResponseItemMetadata {
uuid: Some("uuid-1".to_string()),
sandbox_policy: Some(SandboxPolicyMetadata::ReadOnly),
session_source: Some(SessionSourceMetadata::Cli),
..ResponseItemMetadata::default()
}),
};
assert_eq!(
ResponseItem::from(input.clone()),
@@ -3058,7 +3201,12 @@ mod tests {
"additionalProperties": false,
}
})],
metadata: None,
metadata: Some(ResponseItemMetadata {
uuid: Some("uuid-1".to_string()),
sandbox_policy: Some(SandboxPolicyMetadata::ReadOnly),
session_source: Some(SessionSourceMetadata::Cli),
..ResponseItemMetadata::default()
}),
}
);
@@ -3083,6 +3231,12 @@ mod tests {
"additionalProperties": false,
}
}]
,
"metadata": {
"uuid": "uuid-1",
"sandbox_policy": "read_only",
"session_source": "cli"
}
})
);