mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
[codex] Add optional IDs to response items (#28812)
## Why `ResponseItem` variants do not have a consistent internal ID shape: some variants carry required IDs, some carry optional IDs, and some cannot represent an ID at all. The existing fields also use inconsistent serde, TypeScript, and JSON-schema annotations. A single enum-level access path is needed before history recording can assign and retain IDs. This PR establishes that internal model only. It intentionally does not generate or serialize IDs; allocation and wire persistence are isolated in the stacked follow-up. ## What changed - Give every concrete `ResponseItem` variant an `Option<String>` ID field. - Apply the same internal-only annotations to every ID field: `#[serde(default, skip_serializing)]`, `#[ts(skip)]`, and `#[schemars(skip)]`. - Add `ResponseItem::id()` and `ResponseItem::set_id()` as the shared accessors. - Preserve IDs when history items are rewritten for truncation. - Adapt consumers that previously assumed reasoning and image-generation IDs were required. - Regenerate app-server schemas so the hidden fields are represented consistently. The serde catch-all `ResponseItem::Other` remains ID-less because it must remain a unit variant. ## Test plan - `cargo check --tests -p codex-core -p codex-api -p codex-rollout-trace -p codex-image-generation-extension` - `just test -p codex-protocol` - `just test -p codex-app-server-protocol` - `just test -p codex-api -p codex-rollout-trace -p codex-image-generation-extension` - `just test -p codex-core event_mapping`
This commit is contained in:
committed by
GitHub
Unverified
parent
c274a83f8b
commit
dbd2857f4b
+5
-45
@@ -2289,13 +2289,6 @@
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"writeOnly": true
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -2437,14 +2430,6 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"description": "Legacy id field retained for compatibility with older payloads.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"writeOnly": true
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -2482,13 +2467,6 @@
|
||||
"call_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"writeOnly": true
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -2537,13 +2515,6 @@
|
||||
"execution": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"writeOnly": true
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -2615,13 +2586,6 @@
|
||||
"call_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"writeOnly": true
|
||||
},
|
||||
"input": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -2758,13 +2722,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"writeOnly": true
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -2798,7 +2755,11 @@
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
"description": "Existing provider ID retained on serialized history for compatibility.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
@@ -2831,7 +2792,6 @@
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"result",
|
||||
"status",
|
||||
"type"
|
||||
|
||||
+5
-45
@@ -14791,13 +14791,6 @@
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"writeOnly": true
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -14939,14 +14932,6 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"description": "Legacy id field retained for compatibility with older payloads.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"writeOnly": true
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -14984,13 +14969,6 @@
|
||||
"call_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"writeOnly": true
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -15039,13 +15017,6 @@
|
||||
"execution": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"writeOnly": true
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -15117,13 +15088,6 @@
|
||||
"call_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"writeOnly": true
|
||||
},
|
||||
"input": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -15260,13 +15224,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"writeOnly": true
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -15300,7 +15257,11 @@
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
"description": "Existing provider ID retained on serialized history for compatibility.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
@@ -15333,7 +15294,6 @@
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"result",
|
||||
"status",
|
||||
"type"
|
||||
|
||||
+5
-45
@@ -11236,13 +11236,6 @@
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"writeOnly": true
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -11384,14 +11377,6 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"description": "Legacy id field retained for compatibility with older payloads.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"writeOnly": true
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -11429,13 +11414,6 @@
|
||||
"call_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"writeOnly": true
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -11484,13 +11462,6 @@
|
||||
"execution": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"writeOnly": true
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -11562,13 +11533,6 @@
|
||||
"call_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"writeOnly": true
|
||||
},
|
||||
"input": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -11705,13 +11669,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"writeOnly": true
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -11745,7 +11702,11 @@
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
"description": "Existing provider ID retained on serialized history for compatibility.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
@@ -11778,7 +11739,6 @@
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"result",
|
||||
"status",
|
||||
"type"
|
||||
|
||||
+5
-45
@@ -377,13 +377,6 @@
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"writeOnly": true
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -525,14 +518,6 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"description": "Legacy id field retained for compatibility with older payloads.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"writeOnly": true
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -570,13 +555,6 @@
|
||||
"call_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"writeOnly": true
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -625,13 +603,6 @@
|
||||
"execution": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"writeOnly": true
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -703,13 +674,6 @@
|
||||
"call_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"writeOnly": true
|
||||
},
|
||||
"input": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -846,13 +810,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"writeOnly": true
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -886,7 +843,11 @@
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
"description": "Existing provider ID retained on serialized history for compatibility.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
@@ -919,7 +880,6 @@
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"result",
|
||||
"status",
|
||||
"type"
|
||||
|
||||
@@ -448,13 +448,6 @@
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"writeOnly": true
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -596,14 +589,6 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"description": "Legacy id field retained for compatibility with older payloads.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"writeOnly": true
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -641,13 +626,6 @@
|
||||
"call_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"writeOnly": true
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -696,13 +674,6 @@
|
||||
"execution": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"writeOnly": true
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -774,13 +745,6 @@
|
||||
"call_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"writeOnly": true
|
||||
},
|
||||
"input": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -917,13 +881,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"writeOnly": true
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -957,7 +914,11 @@
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
"description": "Existing provider ID retained on serialized history for compatibility.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
@@ -990,7 +951,6 @@
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"result",
|
||||
"status",
|
||||
"type"
|
||||
|
||||
@@ -16,4 +16,8 @@ export type ResponseItem = { "type": "message", role: string, content: Array<Con
|
||||
/**
|
||||
* Set when using the Responses API.
|
||||
*/
|
||||
call_id: string | null, status: LocalShellStatus, action: LocalShellAction, metadata?: ResponseItemMetadata, } | { "type": "function_call", name: string, namespace?: string, arguments: string, call_id: string, metadata?: ResponseItemMetadata, } | { "type": "tool_search_call", call_id: string | null, status?: string, execution: string, arguments: unknown, metadata?: ResponseItemMetadata, } | { "type": "function_call_output", call_id: string, output: FunctionCallOutputBody, metadata?: ResponseItemMetadata, } | { "type": "custom_tool_call", status?: string, call_id: string, name: string, input: string, metadata?: ResponseItemMetadata, } | { "type": "custom_tool_call_output", call_id: string, name?: string, output: FunctionCallOutputBody, metadata?: ResponseItemMetadata, } | { "type": "tool_search_output", call_id: string | null, status: string, execution: string, tools: unknown[], metadata?: ResponseItemMetadata, } | { "type": "web_search_call", status?: string, action?: WebSearchAction, metadata?: ResponseItemMetadata, } | { "type": "image_generation_call", id: string, status: string, revised_prompt?: string, result: string, metadata?: ResponseItemMetadata, } | { "type": "compaction", encrypted_content: string, metadata?: ResponseItemMetadata, } | { "type": "compaction_trigger", metadata?: ResponseItemMetadata, } | { "type": "context_compaction", encrypted_content?: string, metadata?: ResponseItemMetadata, } | { "type": "other" };
|
||||
call_id: string | null, status: LocalShellStatus, action: LocalShellAction, metadata?: ResponseItemMetadata, } | { "type": "function_call", name: string, namespace?: string, arguments: string, call_id: string, metadata?: ResponseItemMetadata, } | { "type": "tool_search_call", call_id: string | null, status?: string, execution: string, arguments: unknown, metadata?: ResponseItemMetadata, } | { "type": "function_call_output", call_id: string, output: FunctionCallOutputBody, metadata?: ResponseItemMetadata, } | { "type": "custom_tool_call", status?: string, call_id: string, name: string, input: string, metadata?: ResponseItemMetadata, } | { "type": "custom_tool_call_output", call_id: string, name?: string, output: FunctionCallOutputBody, metadata?: ResponseItemMetadata, } | { "type": "tool_search_output", call_id: string | null, status: string, execution: string, tools: unknown[], metadata?: ResponseItemMetadata, } | { "type": "web_search_call", status?: string, action?: WebSearchAction, metadata?: ResponseItemMetadata, } | { "type": "image_generation_call",
|
||||
/**
|
||||
* Existing provider ID retained on serialized history for compatibility.
|
||||
*/
|
||||
id?: string, status: string, revised_prompt?: string, result: string, metadata?: ResponseItemMetadata, } | { "type": "compaction", encrypted_content: string, metadata?: ResponseItemMetadata, } | { "type": "compaction_trigger", metadata?: ResponseItemMetadata, } | { "type": "context_compaction", encrypted_content?: string, metadata?: ResponseItemMetadata, } | { "type": "other" };
|
||||
|
||||
@@ -137,6 +137,7 @@ async fn auto_compaction_remote_emits_started_and_completed_items() -> Result<()
|
||||
metadata: None,
|
||||
},
|
||||
ResponseItem::Compaction {
|
||||
id: None,
|
||||
encrypted_content: "ENCRYPTED_COMPACTION_SUMMARY".to_string(),
|
||||
metadata: None,
|
||||
},
|
||||
|
||||
@@ -17,7 +17,7 @@ pub(crate) fn attach_item_ids(payload_json: &mut Value, original_items: &[Respon
|
||||
};
|
||||
|
||||
for (value, item) in items.iter_mut().zip(original_items.iter()) {
|
||||
if let ResponseItem::Reasoning { id, .. }
|
||||
if let ResponseItem::Reasoning { id: Some(id), .. }
|
||||
| ResponseItem::Message { id: Some(id), .. }
|
||||
| ResponseItem::WebSearchCall { id: Some(id), .. }
|
||||
| ResponseItem::FunctionCall { id: Some(id), .. }
|
||||
|
||||
@@ -878,7 +878,7 @@ async fn spawn_agent_can_fork_parent_thread_history_with_sanitized_items() {
|
||||
assistant_message("parent final answer", Some(MessagePhase::FinalAnswer)),
|
||||
assistant_message("parent unknown phase", /*phase*/ None),
|
||||
ResponseItem::Reasoning {
|
||||
id: "parent-reasoning".to_string(),
|
||||
id: Some("parent-reasoning".to_string()),
|
||||
summary: Vec::new(),
|
||||
content: None,
|
||||
encrypted_content: None,
|
||||
|
||||
@@ -23,6 +23,7 @@ fn prompt_with_image_outputs() -> Prompt {
|
||||
metadata: None,
|
||||
},
|
||||
ResponseItem::FunctionCallOutput {
|
||||
id: None,
|
||||
call_id: "function-call".to_string(),
|
||||
output: FunctionCallOutputPayload::from_content_items(vec![
|
||||
FunctionCallOutputContentItem::InputImage {
|
||||
@@ -33,6 +34,7 @@ fn prompt_with_image_outputs() -> Prompt {
|
||||
metadata: None,
|
||||
},
|
||||
ResponseItem::CustomToolCallOutput {
|
||||
id: None,
|
||||
call_id: "custom-call".to_string(),
|
||||
name: None,
|
||||
output: FunctionCallOutputPayload::from_content_items(vec![
|
||||
@@ -69,6 +71,7 @@ fn responses_lite_request_copies_strip_image_details() {
|
||||
metadata: None,
|
||||
},
|
||||
ResponseItem::FunctionCallOutput {
|
||||
id: None,
|
||||
call_id: "function-call".to_string(),
|
||||
output: FunctionCallOutputPayload::from_content_items(vec![
|
||||
FunctionCallOutputContentItem::InputImage {
|
||||
@@ -79,6 +82,7 @@ fn responses_lite_request_copies_strip_image_details() {
|
||||
metadata: None,
|
||||
},
|
||||
ResponseItem::CustomToolCallOutput {
|
||||
id: None,
|
||||
call_id: "custom-call".to_string(),
|
||||
name: None,
|
||||
output: FunctionCallOutputPayload::from_content_items(vec![
|
||||
|
||||
@@ -403,20 +403,24 @@ pub(crate) fn trim_function_call_history_to_fit_context_window(
|
||||
fn rewritten_output_for_context_window(item: &ResponseItem) -> Option<ResponseItem> {
|
||||
Some(match item {
|
||||
ResponseItem::FunctionCallOutput {
|
||||
id,
|
||||
call_id,
|
||||
output,
|
||||
metadata,
|
||||
} => ResponseItem::FunctionCallOutput {
|
||||
id: id.clone(),
|
||||
call_id: call_id.clone(),
|
||||
output: truncated_output_payload(output),
|
||||
metadata: metadata.clone(),
|
||||
},
|
||||
ResponseItem::CustomToolCallOutput {
|
||||
id,
|
||||
call_id,
|
||||
name,
|
||||
output,
|
||||
metadata,
|
||||
} => ResponseItem::CustomToolCallOutput {
|
||||
id: id.clone(),
|
||||
call_id: call_id.clone(),
|
||||
name: name.clone(),
|
||||
output: truncated_output_payload(output),
|
||||
@@ -429,6 +433,7 @@ fn rewritten_output_for_context_window(item: &ResponseItem) -> Option<ResponseIt
|
||||
metadata,
|
||||
..
|
||||
} => ResponseItem::ToolSearchOutput {
|
||||
id: item.id().map(str::to_string),
|
||||
call_id: call_id.clone(),
|
||||
status: status.clone(),
|
||||
execution: execution.clone(),
|
||||
|
||||
@@ -231,7 +231,10 @@ async fn run_remote_compact_task_inner_impl(
|
||||
)
|
||||
.await?;
|
||||
let mut input = prompt_input.clone();
|
||||
input.push(ResponseItem::CompactionTrigger { metadata: None });
|
||||
input.push(ResponseItem::CompactionTrigger {
|
||||
id: None,
|
||||
metadata: None,
|
||||
});
|
||||
let prompt = Prompt {
|
||||
input,
|
||||
tools: tool_router.model_visible_specs(),
|
||||
@@ -609,11 +612,13 @@ mod tests {
|
||||
metadata: None,
|
||||
},
|
||||
ResponseItem::Compaction {
|
||||
id: None,
|
||||
encrypted_content: "old".to_string(),
|
||||
metadata: None,
|
||||
},
|
||||
];
|
||||
let output = ResponseItem::Compaction {
|
||||
id: None,
|
||||
encrypted_content: "new".to_string(),
|
||||
metadata: None,
|
||||
};
|
||||
@@ -642,6 +647,7 @@ mod tests {
|
||||
new.clone(),
|
||||
];
|
||||
let output = ResponseItem::Compaction {
|
||||
id: None,
|
||||
encrypted_content: "new".to_string(),
|
||||
metadata: None,
|
||||
};
|
||||
@@ -673,6 +679,7 @@ mod tests {
|
||||
metadata: None,
|
||||
}];
|
||||
let output = ResponseItem::Compaction {
|
||||
id: None,
|
||||
encrypted_content: "new".to_string(),
|
||||
metadata: None,
|
||||
};
|
||||
@@ -800,6 +807,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn collect_compaction_output_accepts_additional_output_items() {
|
||||
let compaction = ResponseItem::Compaction {
|
||||
id: None,
|
||||
encrypted_content: "encrypted".to_string(),
|
||||
metadata: None,
|
||||
};
|
||||
|
||||
@@ -654,6 +654,7 @@ fn insert_initial_context_before_last_real_user_or_summary_keeps_summary_last()
|
||||
#[test]
|
||||
fn insert_initial_context_before_last_real_user_or_summary_keeps_compaction_last() {
|
||||
let compacted_history = vec![ResponseItem::Compaction {
|
||||
id: None,
|
||||
encrypted_content: "encrypted".to_string(),
|
||||
metadata: None,
|
||||
}];
|
||||
@@ -680,6 +681,7 @@ fn insert_initial_context_before_last_real_user_or_summary_keeps_compaction_last
|
||||
metadata: None,
|
||||
},
|
||||
ResponseItem::Compaction {
|
||||
id: None,
|
||||
encrypted_content: "encrypted".to_string(),
|
||||
metadata: None,
|
||||
},
|
||||
|
||||
@@ -339,20 +339,24 @@ impl ContextManager {
|
||||
let policy_with_serialization_budget = policy * 1.2;
|
||||
match item {
|
||||
ResponseItem::FunctionCallOutput {
|
||||
id,
|
||||
call_id,
|
||||
output,
|
||||
metadata,
|
||||
} => ResponseItem::FunctionCallOutput {
|
||||
id: id.clone(),
|
||||
call_id: call_id.clone(),
|
||||
output: truncate_function_output_payload(output, policy_with_serialization_budget),
|
||||
metadata: metadata.clone(),
|
||||
},
|
||||
ResponseItem::CustomToolCallOutput {
|
||||
id,
|
||||
call_id,
|
||||
name,
|
||||
output,
|
||||
metadata,
|
||||
} => ResponseItem::CustomToolCallOutput {
|
||||
id: id.clone(),
|
||||
call_id: call_id.clone(),
|
||||
name: name.clone(),
|
||||
output: truncate_function_output_payload(output, policy_with_serialization_budget),
|
||||
|
||||
@@ -154,6 +154,7 @@ fn reference_context_item() -> TurnContextItem {
|
||||
|
||||
fn custom_tool_call_output(call_id: &str, output: &str) -> ResponseItem {
|
||||
ResponseItem::CustomToolCallOutput {
|
||||
id: None,
|
||||
call_id: call_id.to_string(),
|
||||
name: None,
|
||||
output: FunctionCallOutputPayload::from_text(output.to_string()),
|
||||
@@ -163,7 +164,7 @@ fn custom_tool_call_output(call_id: &str, output: &str) -> ResponseItem {
|
||||
|
||||
fn reasoning_msg(text: &str) -> ResponseItem {
|
||||
ResponseItem::Reasoning {
|
||||
id: String::new(),
|
||||
id: None,
|
||||
summary: vec![ReasoningItemReasoningSummary::SummaryText {
|
||||
text: "summary".to_string(),
|
||||
}],
|
||||
@@ -177,7 +178,7 @@ fn reasoning_msg(text: &str) -> ResponseItem {
|
||||
|
||||
fn reasoning_with_encrypted_content(len: usize) -> ResponseItem {
|
||||
ResponseItem::Reasoning {
|
||||
id: String::new(),
|
||||
id: None,
|
||||
summary: vec![ReasoningItemReasoningSummary::SummaryText {
|
||||
text: "summary".to_string(),
|
||||
}],
|
||||
@@ -222,7 +223,7 @@ fn filters_non_api_messages() {
|
||||
items,
|
||||
vec![
|
||||
ResponseItem::Reasoning {
|
||||
id: String::new(),
|
||||
id: None,
|
||||
summary: vec![ReasoningItemReasoningSummary::SummaryText {
|
||||
text: "summary".to_string(),
|
||||
}],
|
||||
@@ -409,6 +410,7 @@ fn for_prompt_strips_images_when_model_does_not_support_images() {
|
||||
metadata: None,
|
||||
},
|
||||
ResponseItem::FunctionCallOutput {
|
||||
id: None,
|
||||
call_id: "call-1".to_string(),
|
||||
output: FunctionCallOutputPayload::from_content_items(vec![
|
||||
FunctionCallOutputContentItem::InputText {
|
||||
@@ -430,6 +432,7 @@ fn for_prompt_strips_images_when_model_does_not_support_images() {
|
||||
metadata: None,
|
||||
},
|
||||
ResponseItem::CustomToolCallOutput {
|
||||
id: None,
|
||||
call_id: "tool-1".to_string(),
|
||||
name: None,
|
||||
output: FunctionCallOutputPayload::from_content_items(vec![
|
||||
@@ -476,6 +479,7 @@ fn for_prompt_strips_images_when_model_does_not_support_images() {
|
||||
metadata: None,
|
||||
},
|
||||
ResponseItem::FunctionCallOutput {
|
||||
id: None,
|
||||
call_id: "call-1".to_string(),
|
||||
output: FunctionCallOutputPayload::from_content_items(vec![
|
||||
FunctionCallOutputContentItem::InputText {
|
||||
@@ -497,6 +501,7 @@ fn for_prompt_strips_images_when_model_does_not_support_images() {
|
||||
metadata: None,
|
||||
},
|
||||
ResponseItem::CustomToolCallOutput {
|
||||
id: None,
|
||||
call_id: "tool-1".to_string(),
|
||||
name: None,
|
||||
output: FunctionCallOutputPayload::from_content_items(vec![
|
||||
@@ -544,7 +549,7 @@ fn for_prompt_strips_images_when_model_does_not_support_images() {
|
||||
fn for_prompt_preserves_image_generation_calls_when_images_are_supported() {
|
||||
let history = create_history_with_items(vec![
|
||||
ResponseItem::ImageGenerationCall {
|
||||
id: "ig_123".to_string(),
|
||||
id: Some("ig_123".to_string()),
|
||||
status: "generating".to_string(),
|
||||
revised_prompt: Some("lobster".to_string()),
|
||||
result: "Zm9v".to_string(),
|
||||
@@ -565,7 +570,7 @@ fn for_prompt_preserves_image_generation_calls_when_images_are_supported() {
|
||||
history.for_prompt(&default_input_modalities()),
|
||||
vec![
|
||||
ResponseItem::ImageGenerationCall {
|
||||
id: "ig_123".to_string(),
|
||||
id: Some("ig_123".to_string()),
|
||||
status: "generating".to_string(),
|
||||
revised_prompt: Some("lobster".to_string()),
|
||||
result: "Zm9v".to_string(),
|
||||
@@ -597,7 +602,7 @@ fn for_prompt_clears_image_generation_result_when_images_are_unsupported() {
|
||||
metadata: None,
|
||||
},
|
||||
ResponseItem::ImageGenerationCall {
|
||||
id: "ig_123".to_string(),
|
||||
id: Some("ig_123".to_string()),
|
||||
status: "completed".to_string(),
|
||||
revised_prompt: Some("lobster".to_string()),
|
||||
result: "Zm9v".to_string(),
|
||||
@@ -618,7 +623,7 @@ fn for_prompt_clears_image_generation_result_when_images_are_unsupported() {
|
||||
metadata: None,
|
||||
},
|
||||
ResponseItem::ImageGenerationCall {
|
||||
id: "ig_123".to_string(),
|
||||
id: Some("ig_123".to_string()),
|
||||
status: "completed".to_string(),
|
||||
revised_prompt: Some("lobster".to_string()),
|
||||
result: String::new(),
|
||||
@@ -662,6 +667,7 @@ fn remove_first_item_removes_matching_output_for_function_call() {
|
||||
metadata: None,
|
||||
},
|
||||
ResponseItem::FunctionCallOutput {
|
||||
id: None,
|
||||
call_id: "call-1".to_string(),
|
||||
output: FunctionCallOutputPayload::from_text("ok".to_string()),
|
||||
metadata: None,
|
||||
@@ -676,6 +682,7 @@ fn remove_first_item_removes_matching_output_for_function_call() {
|
||||
fn remove_first_item_removes_matching_call_for_output() {
|
||||
let items = vec![
|
||||
ResponseItem::FunctionCallOutput {
|
||||
id: None,
|
||||
call_id: "call-2".to_string(),
|
||||
output: FunctionCallOutputPayload::from_text("ok".to_string()),
|
||||
metadata: None,
|
||||
@@ -699,6 +706,7 @@ fn replace_last_turn_images_replaces_tool_output_images() {
|
||||
let items = vec![
|
||||
user_input_text_msg("hi"),
|
||||
ResponseItem::FunctionCallOutput {
|
||||
id: None,
|
||||
call_id: "call-1".to_string(),
|
||||
output: FunctionCallOutputPayload {
|
||||
body: FunctionCallOutputBody::ContentItems(vec![
|
||||
@@ -721,6 +729,7 @@ fn replace_last_turn_images_replaces_tool_output_images() {
|
||||
vec![
|
||||
user_input_text_msg("hi"),
|
||||
ResponseItem::FunctionCallOutput {
|
||||
id: None,
|
||||
call_id: "call-1".to_string(),
|
||||
output: FunctionCallOutputPayload {
|
||||
body: FunctionCallOutputBody::ContentItems(vec![
|
||||
@@ -771,6 +780,7 @@ fn remove_first_item_handles_local_shell_pair() {
|
||||
metadata: None,
|
||||
},
|
||||
ResponseItem::FunctionCallOutput {
|
||||
id: None,
|
||||
call_id: "call-3".to_string(),
|
||||
output: FunctionCallOutputPayload::from_text("ok".to_string()),
|
||||
metadata: None,
|
||||
@@ -998,6 +1008,7 @@ fn remove_first_item_handles_custom_tool_pair() {
|
||||
metadata: None,
|
||||
},
|
||||
ResponseItem::CustomToolCallOutput {
|
||||
id: None,
|
||||
call_id: "tool-1".to_string(),
|
||||
name: None,
|
||||
output: FunctionCallOutputPayload::from_text("ok".to_string()),
|
||||
@@ -1026,6 +1037,7 @@ fn normalization_retains_local_shell_outputs() {
|
||||
metadata: None,
|
||||
},
|
||||
ResponseItem::FunctionCallOutput {
|
||||
id: None,
|
||||
call_id: "shell-1".to_string(),
|
||||
output: FunctionCallOutputPayload::from_text("Total output lines: 1\n\nok".to_string()),
|
||||
metadata: None,
|
||||
@@ -1047,6 +1059,7 @@ fn record_items_truncates_function_call_output_content() {
|
||||
let long_line = "a very long line to trigger truncation\n";
|
||||
let long_output = long_line.repeat(2_500);
|
||||
let item = ResponseItem::FunctionCallOutput {
|
||||
id: None,
|
||||
call_id: "call-100".to_string(),
|
||||
output: FunctionCallOutputPayload {
|
||||
body: FunctionCallOutputBody::Text(long_output.clone()),
|
||||
@@ -1086,6 +1099,7 @@ fn record_items_truncates_custom_tool_call_output_content() {
|
||||
let line = "custom output that is very long\n";
|
||||
let long_output = line.repeat(2_500);
|
||||
let item = ResponseItem::CustomToolCallOutput {
|
||||
id: None,
|
||||
call_id: "tool-200".to_string(),
|
||||
name: None,
|
||||
output: FunctionCallOutputPayload::from_text(long_output.clone()),
|
||||
@@ -1118,6 +1132,7 @@ fn record_items_respects_custom_token_limit() {
|
||||
let policy = TruncationPolicy::Tokens(10);
|
||||
let long_output = "tokenized content repeated many times ".repeat(200);
|
||||
let item = ResponseItem::FunctionCallOutput {
|
||||
id: None,
|
||||
call_id: "call-custom-limit".to_string(),
|
||||
output: FunctionCallOutputPayload {
|
||||
body: FunctionCallOutputBody::Text(long_output),
|
||||
@@ -1261,6 +1276,7 @@ fn normalize_adds_missing_output_for_function_call() {
|
||||
metadata: None,
|
||||
},
|
||||
ResponseItem::FunctionCallOutput {
|
||||
id: None,
|
||||
call_id: "call-x".to_string(),
|
||||
output: FunctionCallOutputPayload::from_text("aborted".to_string()),
|
||||
metadata: None,
|
||||
@@ -1296,6 +1312,7 @@ fn normalize_adds_missing_output_for_custom_tool_call() {
|
||||
metadata: None,
|
||||
},
|
||||
ResponseItem::CustomToolCallOutput {
|
||||
id: None,
|
||||
call_id: "tool-x".to_string(),
|
||||
name: None,
|
||||
output: FunctionCallOutputPayload::from_text("aborted".to_string()),
|
||||
@@ -1342,6 +1359,7 @@ fn normalize_adds_missing_output_for_local_shell_call_with_id() {
|
||||
metadata: None,
|
||||
},
|
||||
ResponseItem::FunctionCallOutput {
|
||||
id: None,
|
||||
call_id: "shell-1".to_string(),
|
||||
output: FunctionCallOutputPayload::from_text("aborted".to_string()),
|
||||
metadata: None,
|
||||
@@ -1354,6 +1372,7 @@ fn normalize_adds_missing_output_for_local_shell_call_with_id() {
|
||||
#[test]
|
||||
fn normalize_removes_orphan_function_call_output() {
|
||||
let items = vec![ResponseItem::FunctionCallOutput {
|
||||
id: None,
|
||||
call_id: "orphan-1".to_string(),
|
||||
output: FunctionCallOutputPayload::from_text("ok".to_string()),
|
||||
metadata: None,
|
||||
@@ -1369,6 +1388,7 @@ fn normalize_removes_orphan_function_call_output() {
|
||||
#[test]
|
||||
fn normalize_removes_orphan_custom_tool_call_output() {
|
||||
let items = vec![ResponseItem::CustomToolCallOutput {
|
||||
id: None,
|
||||
call_id: "orphan-2".to_string(),
|
||||
name: None,
|
||||
output: FunctionCallOutputPayload::from_text("ok".to_string()),
|
||||
@@ -1396,6 +1416,7 @@ fn normalize_mixed_inserts_and_removals() {
|
||||
},
|
||||
// Orphan output that should be removed
|
||||
ResponseItem::FunctionCallOutput {
|
||||
id: None,
|
||||
call_id: "c2".to_string(),
|
||||
output: FunctionCallOutputPayload::from_text("ok".to_string()),
|
||||
metadata: None,
|
||||
@@ -1440,6 +1461,7 @@ fn normalize_mixed_inserts_and_removals() {
|
||||
metadata: None,
|
||||
},
|
||||
ResponseItem::FunctionCallOutput {
|
||||
id: None,
|
||||
call_id: "c1".to_string(),
|
||||
output: FunctionCallOutputPayload::from_text("aborted".to_string()),
|
||||
metadata: None,
|
||||
@@ -1453,6 +1475,7 @@ fn normalize_mixed_inserts_and_removals() {
|
||||
metadata: None,
|
||||
},
|
||||
ResponseItem::CustomToolCallOutput {
|
||||
id: None,
|
||||
call_id: "t1".to_string(),
|
||||
name: None,
|
||||
output: FunctionCallOutputPayload::from_text("aborted".to_string()),
|
||||
@@ -1472,6 +1495,7 @@ fn normalize_mixed_inserts_and_removals() {
|
||||
metadata: None,
|
||||
},
|
||||
ResponseItem::FunctionCallOutput {
|
||||
id: None,
|
||||
call_id: "s1".to_string(),
|
||||
output: FunctionCallOutputPayload::from_text("aborted".to_string()),
|
||||
metadata: None,
|
||||
@@ -1504,6 +1528,7 @@ fn normalize_adds_missing_output_for_function_call_inserts_output() {
|
||||
metadata: None,
|
||||
},
|
||||
ResponseItem::FunctionCallOutput {
|
||||
id: None,
|
||||
call_id: "call-x".to_string(),
|
||||
output: FunctionCallOutputPayload::from_text("aborted".to_string()),
|
||||
metadata: None,
|
||||
@@ -1538,6 +1563,7 @@ fn normalize_adds_missing_output_for_tool_search_call() {
|
||||
metadata: None,
|
||||
},
|
||||
ResponseItem::ToolSearchOutput {
|
||||
id: None,
|
||||
call_id: Some("search-call-x".to_string()),
|
||||
status: "completed".to_string(),
|
||||
execution: "client".to_string(),
|
||||
@@ -1590,6 +1616,7 @@ fn normalize_adds_missing_output_for_local_shell_call_with_id_panics_in_debug()
|
||||
#[should_panic]
|
||||
fn normalize_removes_orphan_function_call_output_panics_in_debug() {
|
||||
let items = vec![ResponseItem::FunctionCallOutput {
|
||||
id: None,
|
||||
call_id: "orphan-1".to_string(),
|
||||
output: FunctionCallOutputPayload::from_text("ok".to_string()),
|
||||
metadata: None,
|
||||
@@ -1603,6 +1630,7 @@ fn normalize_removes_orphan_function_call_output_panics_in_debug() {
|
||||
#[should_panic]
|
||||
fn normalize_removes_orphan_custom_tool_call_output_panics_in_debug() {
|
||||
let items = vec![ResponseItem::CustomToolCallOutput {
|
||||
id: None,
|
||||
call_id: "orphan-2".to_string(),
|
||||
name: None,
|
||||
output: FunctionCallOutputPayload::from_text("ok".to_string()),
|
||||
@@ -1616,6 +1644,7 @@ fn normalize_removes_orphan_custom_tool_call_output_panics_in_debug() {
|
||||
#[test]
|
||||
fn normalize_removes_orphan_client_tool_search_output() {
|
||||
let items = vec![ResponseItem::ToolSearchOutput {
|
||||
id: None,
|
||||
call_id: Some("orphan-search".to_string()),
|
||||
status: "completed".to_string(),
|
||||
execution: "client".to_string(),
|
||||
@@ -1634,6 +1663,7 @@ fn normalize_removes_orphan_client_tool_search_output() {
|
||||
#[should_panic]
|
||||
fn normalize_removes_orphan_client_tool_search_output_panics_in_debug() {
|
||||
let items = vec![ResponseItem::ToolSearchOutput {
|
||||
id: None,
|
||||
call_id: Some("orphan-search".to_string()),
|
||||
status: "completed".to_string(),
|
||||
execution: "client".to_string(),
|
||||
@@ -1647,6 +1677,7 @@ fn normalize_removes_orphan_client_tool_search_output_panics_in_debug() {
|
||||
#[test]
|
||||
fn normalize_keeps_server_tool_search_output_without_matching_call() {
|
||||
let items = vec![ResponseItem::ToolSearchOutput {
|
||||
id: None,
|
||||
call_id: Some("server-search".to_string()),
|
||||
status: "completed".to_string(),
|
||||
execution: "server".to_string(),
|
||||
@@ -1660,6 +1691,7 @@ fn normalize_keeps_server_tool_search_output_without_matching_call() {
|
||||
assert_eq!(
|
||||
h.raw_items(),
|
||||
vec![ResponseItem::ToolSearchOutput {
|
||||
id: None,
|
||||
call_id: Some("server-search".to_string()),
|
||||
status: "completed".to_string(),
|
||||
execution: "server".to_string(),
|
||||
@@ -1683,6 +1715,7 @@ fn normalize_mixed_inserts_and_removals_panics_in_debug() {
|
||||
metadata: None,
|
||||
},
|
||||
ResponseItem::FunctionCallOutput {
|
||||
id: None,
|
||||
call_id: "c2".to_string(),
|
||||
output: FunctionCallOutputPayload::from_text("ok".to_string()),
|
||||
metadata: None,
|
||||
@@ -1757,6 +1790,7 @@ fn image_data_url_payload_does_not_dominate_function_call_output_estimate() {
|
||||
let payload = "B".repeat(50_000);
|
||||
let image_url = format!("data:image/png;base64,{payload}");
|
||||
let item = ResponseItem::FunctionCallOutput {
|
||||
id: None,
|
||||
call_id: "call-abc".to_string(),
|
||||
output: FunctionCallOutputPayload::from_content_items(vec![
|
||||
FunctionCallOutputContentItem::InputText {
|
||||
@@ -1783,6 +1817,7 @@ fn image_data_url_payload_does_not_dominate_custom_tool_call_output_estimate() {
|
||||
let payload = "C".repeat(50_000);
|
||||
let image_url = format!("data:image/png;base64,{payload}");
|
||||
let item = ResponseItem::CustomToolCallOutput {
|
||||
id: None,
|
||||
call_id: "call-js-repl".to_string(),
|
||||
name: None,
|
||||
output: FunctionCallOutputPayload::from_content_items(vec![
|
||||
@@ -1818,6 +1853,7 @@ fn non_base64_image_urls_are_unchanged() {
|
||||
metadata: None,
|
||||
};
|
||||
let function_output_item = ResponseItem::FunctionCallOutput {
|
||||
id: None,
|
||||
call_id: "call-1".to_string(),
|
||||
output: FunctionCallOutputPayload::from_content_items(vec![
|
||||
FunctionCallOutputContentItem::InputImage {
|
||||
@@ -1842,6 +1878,7 @@ fn non_base64_image_urls_are_unchanged() {
|
||||
fn encrypted_function_output_uses_plaintext_byte_estimate() {
|
||||
let encrypted_content = "A".repeat(1_868);
|
||||
let item = ResponseItem::FunctionCallOutput {
|
||||
id: None,
|
||||
call_id: "call-encrypted".to_string(),
|
||||
output: FunctionCallOutputPayload::from_content_items(vec![
|
||||
FunctionCallOutputContentItem::EncryptedContent {
|
||||
@@ -1883,6 +1920,7 @@ fn non_image_base64_data_url_is_unchanged() {
|
||||
let payload = "C".repeat(4_096);
|
||||
let image_url = format!("data:application/octet-stream;base64,{payload}");
|
||||
let item = ResponseItem::FunctionCallOutput {
|
||||
id: None,
|
||||
call_id: "call-octet".to_string(),
|
||||
output: FunctionCallOutputPayload::from_content_items(vec![
|
||||
FunctionCallOutputContentItem::InputImage {
|
||||
@@ -1971,6 +2009,7 @@ fn original_detail_images_scale_with_dimensions() {
|
||||
let payload = BASE64_STANDARD.encode(bytes.get_ref());
|
||||
let image_url = format!("data:image/png;base64,{payload}");
|
||||
let item = ResponseItem::FunctionCallOutput {
|
||||
id: None,
|
||||
call_id: "call-original".to_string(),
|
||||
output: FunctionCallOutputPayload::from_content_items(vec![
|
||||
FunctionCallOutputContentItem::InputImage {
|
||||
@@ -2002,6 +2041,7 @@ fn original_detail_images_are_capped_at_max_patch_count() {
|
||||
let payload = BASE64_STANDARD.encode(bytes.get_ref());
|
||||
let image_url = format!("data:image/png;base64,{payload}");
|
||||
let item = ResponseItem::FunctionCallOutput {
|
||||
id: None,
|
||||
call_id: "call-original-capped".to_string(),
|
||||
output: FunctionCallOutputPayload::from_content_items(vec![
|
||||
FunctionCallOutputContentItem::InputImage {
|
||||
@@ -2036,6 +2076,7 @@ fn original_detail_webp_images_scale_with_dimensions() {
|
||||
let payload = BASE64_STANDARD.encode(bytes.get_ref());
|
||||
let image_url = format!("data:image/webp;base64,{payload}");
|
||||
let item = ResponseItem::FunctionCallOutput {
|
||||
id: None,
|
||||
call_id: "call-original-webp".to_string(),
|
||||
output: FunctionCallOutputPayload::from_content_items(vec![
|
||||
FunctionCallOutputContentItem::InputImage {
|
||||
|
||||
@@ -47,6 +47,7 @@ pub(crate) fn ensure_call_outputs_present(items: &mut Vec<ResponseItem>) {
|
||||
missing_outputs_to_insert.push((
|
||||
idx,
|
||||
ResponseItem::FunctionCallOutput {
|
||||
id: None,
|
||||
call_id: call_id.clone(),
|
||||
output: FunctionCallOutputPayload::from_text("aborted".to_string()),
|
||||
metadata: None,
|
||||
@@ -61,6 +62,7 @@ pub(crate) fn ensure_call_outputs_present(items: &mut Vec<ResponseItem>) {
|
||||
missing_outputs_to_insert.push((
|
||||
idx,
|
||||
ResponseItem::ToolSearchOutput {
|
||||
id: None,
|
||||
call_id: Some(call_id.clone()),
|
||||
status: "completed".to_string(),
|
||||
execution: "client".to_string(),
|
||||
@@ -78,6 +80,7 @@ pub(crate) fn ensure_call_outputs_present(items: &mut Vec<ResponseItem>) {
|
||||
missing_outputs_to_insert.push((
|
||||
idx,
|
||||
ResponseItem::CustomToolCallOutput {
|
||||
id: None,
|
||||
call_id: call_id.clone(),
|
||||
name: None,
|
||||
output: FunctionCallOutputPayload::from_text("aborted".to_string()),
|
||||
@@ -96,6 +99,7 @@ pub(crate) fn ensure_call_outputs_present(items: &mut Vec<ResponseItem>) {
|
||||
missing_outputs_to_insert.push((
|
||||
idx,
|
||||
ResponseItem::FunctionCallOutput {
|
||||
id: None,
|
||||
call_id: call_id.clone(),
|
||||
output: FunctionCallOutputPayload::from_text("aborted".to_string()),
|
||||
metadata: None,
|
||||
|
||||
@@ -178,7 +178,7 @@ pub fn parse_turn_item(item: &ResponseItem) -> Option<TurnItem> {
|
||||
})
|
||||
.collect();
|
||||
Some(TurnItem::Reasoning(ReasoningItem {
|
||||
id: id.clone(),
|
||||
id: id.clone().unwrap_or_default(),
|
||||
summary_text,
|
||||
raw_content,
|
||||
}))
|
||||
@@ -202,7 +202,7 @@ pub fn parse_turn_item(item: &ResponseItem) -> Option<TurnItem> {
|
||||
..
|
||||
} => Some(TurnItem::ImageGeneration(
|
||||
codex_protocol::items::ImageGenerationItem {
|
||||
id: id.clone(),
|
||||
id: id.clone()?,
|
||||
status: status.clone(),
|
||||
revised_prompt: revised_prompt.clone(),
|
||||
result: result.clone(),
|
||||
|
||||
@@ -391,7 +391,7 @@ fn parses_agent_message() {
|
||||
#[test]
|
||||
fn parses_reasoning_summary_and_raw_content() {
|
||||
let item = ResponseItem::Reasoning {
|
||||
id: "reasoning_1".to_string(),
|
||||
id: Some("reasoning_1".to_string()),
|
||||
summary: vec![
|
||||
ReasoningItemReasoningSummary::SummaryText {
|
||||
text: "Step 1".to_string(),
|
||||
@@ -424,7 +424,7 @@ fn parses_reasoning_summary_and_raw_content() {
|
||||
#[test]
|
||||
fn parses_reasoning_including_raw_content() {
|
||||
let item = ResponseItem::Reasoning {
|
||||
id: "reasoning_2".to_string(),
|
||||
id: Some("reasoning_2".to_string()),
|
||||
summary: vec![ReasoningItemReasoningSummary::SummaryText {
|
||||
text: "Summarized step".to_string(),
|
||||
}],
|
||||
|
||||
@@ -309,6 +309,7 @@ async fn seed_guardian_parent_history(session: &Arc<Session>, turn: &Arc<TurnCon
|
||||
metadata: None,
|
||||
},
|
||||
ResponseItem::FunctionCallOutput {
|
||||
id: None,
|
||||
call_id: "call-1".to_string(),
|
||||
output: codex_protocol::models::FunctionCallOutputPayload::from_text(
|
||||
"repo visibility: public".to_string(),
|
||||
@@ -827,6 +828,7 @@ fn collect_guardian_transcript_entries_includes_recent_tool_calls_and_output() {
|
||||
metadata: None,
|
||||
},
|
||||
ResponseItem::FunctionCallOutput {
|
||||
id: None,
|
||||
call_id: "call-1".to_string(),
|
||||
output: codex_protocol::models::FunctionCallOutputPayload::from_text(
|
||||
"repo is public".to_string(),
|
||||
|
||||
@@ -106,6 +106,7 @@ fn preparation_replaces_only_failed_tool_images_and_preserves_metadata() {
|
||||
let (valid_image_url, _) = png_data_url(/*width*/ 64, /*height*/ 32);
|
||||
let expected_valid_image_url = valid_image_url.clone();
|
||||
let mut items = vec![ResponseItem::CustomToolCallOutput {
|
||||
id: None,
|
||||
call_id: "call-1".to_string(),
|
||||
name: None,
|
||||
output: FunctionCallOutputPayload {
|
||||
@@ -140,6 +141,7 @@ fn preparation_replaces_only_failed_tool_images_and_preserves_metadata() {
|
||||
assert_eq!(
|
||||
items,
|
||||
vec![ResponseItem::CustomToolCallOutput {
|
||||
id: None,
|
||||
call_id: "call-1".to_string(),
|
||||
name: None,
|
||||
output: FunctionCallOutputPayload {
|
||||
|
||||
@@ -1664,6 +1664,7 @@ async fn resize_all_images_prepares_failures_before_history_insertion() {
|
||||
)
|
||||
.await;
|
||||
let item = ResponseItem::FunctionCallOutput {
|
||||
id: None,
|
||||
call_id: "call-1".to_string(),
|
||||
output: FunctionCallOutputPayload {
|
||||
body: FunctionCallOutputBody::ContentItems(vec![
|
||||
@@ -1689,6 +1690,7 @@ async fn resize_all_images_prepares_failures_before_history_insertion() {
|
||||
.await;
|
||||
|
||||
let expected = vec![ResponseItem::FunctionCallOutput {
|
||||
id: None,
|
||||
call_id: "call-1".to_string(),
|
||||
output: FunctionCallOutputPayload {
|
||||
body: FunctionCallOutputBody::ContentItems(vec![
|
||||
@@ -7720,7 +7722,7 @@ async fn build_initial_context_omits_default_image_save_location_with_image_hist
|
||||
session
|
||||
.replace_history(
|
||||
vec![ResponseItem::ImageGenerationCall {
|
||||
id: "ig-test".to_string(),
|
||||
id: Some("ig-test".to_string()),
|
||||
status: "completed".to_string(),
|
||||
revised_prompt: Some("a tiny blue square".to_string()),
|
||||
result: "Zm9v".to_string(),
|
||||
@@ -7973,7 +7975,7 @@ async fn handle_output_item_done_records_image_save_history_message() {
|
||||
);
|
||||
let _ = std::fs::remove_file(&expected_saved_path);
|
||||
let item = ResponseItem::ImageGenerationCall {
|
||||
id: call_id.to_string(),
|
||||
id: Some(call_id.to_string()),
|
||||
status: "completed".to_string(),
|
||||
revised_prompt: Some("a tiny blue square".to_string()),
|
||||
result: "Zm9v".to_string(),
|
||||
@@ -8030,7 +8032,7 @@ async fn handle_output_item_done_skips_image_save_message_when_save_fails() {
|
||||
);
|
||||
let _ = std::fs::remove_file(&expected_saved_path);
|
||||
let item = ResponseItem::ImageGenerationCall {
|
||||
id: call_id.to_string(),
|
||||
id: Some(call_id.to_string()),
|
||||
status: "completed".to_string(),
|
||||
revised_prompt: Some("broken payload".to_string()),
|
||||
result: "_-8".to_string(),
|
||||
|
||||
@@ -624,6 +624,7 @@ pub(crate) fn response_input_to_response_item(input: &ResponseInputItem) -> Opti
|
||||
match input {
|
||||
ResponseInputItem::FunctionCallOutput { call_id, output } => {
|
||||
Some(ResponseItem::FunctionCallOutput {
|
||||
id: None,
|
||||
call_id: call_id.clone(),
|
||||
output: output.clone(),
|
||||
metadata: None,
|
||||
@@ -634,6 +635,7 @@ pub(crate) fn response_input_to_response_item(input: &ResponseInputItem) -> Opti
|
||||
name,
|
||||
output,
|
||||
} => Some(ResponseItem::CustomToolCallOutput {
|
||||
id: None,
|
||||
call_id: call_id.clone(),
|
||||
name: name.clone(),
|
||||
output: output.clone(),
|
||||
@@ -642,6 +644,7 @@ pub(crate) fn response_input_to_response_item(input: &ResponseInputItem) -> Opti
|
||||
ResponseInputItem::McpToolCallOutput { call_id, output } => {
|
||||
let output = output.as_function_call_output_payload();
|
||||
Some(ResponseItem::FunctionCallOutput {
|
||||
id: None,
|
||||
call_id: call_id.clone(),
|
||||
output,
|
||||
metadata: None,
|
||||
@@ -653,6 +656,7 @@ pub(crate) fn response_input_to_response_item(input: &ResponseInputItem) -> Opti
|
||||
execution,
|
||||
tools,
|
||||
} => Some(ResponseItem::ToolSearchOutput {
|
||||
id: None,
|
||||
call_id: Some(call_id.clone()),
|
||||
status: status.clone(),
|
||||
execution: execution.clone(),
|
||||
|
||||
@@ -64,6 +64,7 @@ fn external_context_pollution_items_include_web_search_and_tool_search() {
|
||||
metadata: None,
|
||||
},
|
||||
ResponseItem::ToolSearchOutput {
|
||||
id: None,
|
||||
call_id: Some("search-1".to_string()),
|
||||
status: "completed".to_string(),
|
||||
execution: "client".to_string(),
|
||||
@@ -104,6 +105,7 @@ fn external_context_pollution_items_exclude_local_tool_calls() {
|
||||
metadata: None,
|
||||
},
|
||||
ResponseItem::FunctionCallOutput {
|
||||
id: None,
|
||||
call_id: "call-1".to_string(),
|
||||
output: FunctionCallOutputPayload::from_text("ok".to_string()),
|
||||
metadata: None,
|
||||
@@ -117,6 +119,7 @@ fn external_context_pollution_items_exclude_local_tool_calls() {
|
||||
metadata: None,
|
||||
},
|
||||
ResponseItem::CustomToolCallOutput {
|
||||
id: None,
|
||||
call_id: "custom-1".to_string(),
|
||||
name: Some("apply_patch".to_string()),
|
||||
output: FunctionCallOutputPayload::from_text("ok".to_string()),
|
||||
@@ -418,7 +421,7 @@ fn completed_item_keeps_mailbox_delivery_open_for_commentary_messages() {
|
||||
#[test]
|
||||
fn completed_item_defers_mailbox_delivery_for_image_generation_calls() {
|
||||
let item = ResponseItem::ImageGenerationCall {
|
||||
id: "ig-1".to_string(),
|
||||
id: Some("ig-1".to_string()),
|
||||
status: "completed".to_string(),
|
||||
revised_prompt: None,
|
||||
result: "Zm9v".to_string(),
|
||||
|
||||
@@ -76,7 +76,7 @@ fn truncates_before_requested_user_message() {
|
||||
user_msg("u2"),
|
||||
assistant_msg("a3"),
|
||||
ResponseItem::Reasoning {
|
||||
id: "r1".to_string(),
|
||||
id: Some("r1".to_string()),
|
||||
summary: vec![ReasoningItemReasoningSummary::SummaryText {
|
||||
text: "s".to_string(),
|
||||
}],
|
||||
|
||||
@@ -73,7 +73,7 @@ fn truncates_rollout_from_start_before_nth_user_only() {
|
||||
user_msg("u2"),
|
||||
assistant_msg("a3"),
|
||||
ResponseItem::Reasoning {
|
||||
id: "r1".to_string(),
|
||||
id: Some("r1".to_string()),
|
||||
summary: vec![ReasoningItemReasoningSummary::SummaryText {
|
||||
text: "s".to_string(),
|
||||
}],
|
||||
|
||||
@@ -299,6 +299,7 @@ impl CoreTurnHost {
|
||||
self.exec
|
||||
.session
|
||||
.inject_if_running(vec![ResponseItem::CustomToolCallOutput {
|
||||
id: None,
|
||||
call_id,
|
||||
name: Some(PUBLIC_TOOL_NAME.to_string()),
|
||||
output: FunctionCallOutputPayload::from_text(text),
|
||||
|
||||
@@ -149,6 +149,7 @@ fn response_item_records_turn_ttft_ignores_empty_non_output_items() {
|
||||
}));
|
||||
assert!(!response_item_records_turn_ttft(
|
||||
&ResponseItem::FunctionCallOutput {
|
||||
id: None,
|
||||
call_id: "call-1".to_string(),
|
||||
output: FunctionCallOutputPayload::from_text("ok".to_string()),
|
||||
metadata: None,
|
||||
|
||||
@@ -611,6 +611,7 @@ async fn resume_replays_legacy_js_repl_image_rollout_shapes() {
|
||||
RolloutLine {
|
||||
timestamp: "2024-01-01T00:00:02.000Z".to_string(),
|
||||
item: RolloutItem::ResponseItem(ResponseItem::CustomToolCallOutput {
|
||||
id: None,
|
||||
call_id: "legacy-js-call".to_string(),
|
||||
name: None,
|
||||
output: FunctionCallOutputPayload::from_text("legacy js_repl stdout".to_string()),
|
||||
@@ -751,6 +752,7 @@ async fn resume_replays_image_tool_outputs_with_detail() {
|
||||
RolloutLine {
|
||||
timestamp: "2024-01-01T00:00:01.500Z".to_string(),
|
||||
item: RolloutItem::ResponseItem(ResponseItem::FunctionCallOutput {
|
||||
id: None,
|
||||
call_id: function_call_id.to_string(),
|
||||
output: FunctionCallOutputPayload::from_content_items(vec![
|
||||
FunctionCallOutputContentItem::InputImage {
|
||||
@@ -775,6 +777,7 @@ async fn resume_replays_image_tool_outputs_with_detail() {
|
||||
RolloutLine {
|
||||
timestamp: "2024-01-01T00:00:02.500Z".to_string(),
|
||||
item: RolloutItem::ResponseItem(ResponseItem::CustomToolCallOutput {
|
||||
id: None,
|
||||
call_id: custom_call_id.to_string(),
|
||||
name: None,
|
||||
output: FunctionCallOutputPayload::from_content_items(vec![
|
||||
@@ -2518,7 +2521,7 @@ async fn azure_responses_request_includes_store_and_reasoning_ids() {
|
||||
|
||||
let mut prompt = Prompt::default();
|
||||
prompt.input.push(ResponseItem::Reasoning {
|
||||
id: "reasoning-id".into(),
|
||||
id: Some("reasoning-id".into()),
|
||||
summary: vec![ReasoningItemReasoningSummary::SummaryText {
|
||||
text: "summary".into(),
|
||||
}],
|
||||
@@ -2555,6 +2558,7 @@ async fn azure_responses_request_includes_store_and_reasoning_ids() {
|
||||
metadata: None,
|
||||
});
|
||||
prompt.input.push(ResponseItem::FunctionCallOutput {
|
||||
id: None,
|
||||
call_id: "function-call-id".into(),
|
||||
output: FunctionCallOutputPayload::from_text("ok".into()),
|
||||
metadata: None,
|
||||
@@ -2581,6 +2585,7 @@ async fn azure_responses_request_includes_store_and_reasoning_ids() {
|
||||
metadata: None,
|
||||
});
|
||||
prompt.input.push(ResponseItem::CustomToolCallOutput {
|
||||
id: None,
|
||||
call_id: "custom-tool-call-id".into(),
|
||||
name: None,
|
||||
output: FunctionCallOutputPayload::from_text("ok".into()),
|
||||
|
||||
@@ -1995,6 +1995,7 @@ async fn auto_compact_runs_after_resume_when_token_usage_is_over_limit() {
|
||||
metadata: None,
|
||||
},
|
||||
codex_protocol::models::ResponseItem::Compaction {
|
||||
id: None,
|
||||
encrypted_content: "ENCRYPTED_COMPACTION_SUMMARY".to_string(),
|
||||
metadata: None,
|
||||
},
|
||||
@@ -3994,6 +3995,7 @@ async fn auto_compact_counts_encrypted_reasoning_before_last_user() {
|
||||
metadata: None,
|
||||
},
|
||||
codex_protocol::models::ResponseItem::Compaction {
|
||||
id: None,
|
||||
encrypted_content: "ENCRYPTED_COMPACTION_SUMMARY".to_string(),
|
||||
metadata: None,
|
||||
},
|
||||
@@ -4121,6 +4123,7 @@ async fn auto_compact_runs_when_reasoning_header_clears_between_turns() {
|
||||
metadata: None,
|
||||
},
|
||||
codex_protocol::models::ResponseItem::Compaction {
|
||||
id: None,
|
||||
encrypted_content: "ENCRYPTED_COMPACTION_SUMMARY".to_string(),
|
||||
metadata: None,
|
||||
},
|
||||
|
||||
@@ -157,6 +157,7 @@ fn format_labeled_requests_snapshot(
|
||||
|
||||
fn compacted_summary_only_output(summary: &str) -> Vec<ResponseItem> {
|
||||
vec![ResponseItem::Compaction {
|
||||
id: None,
|
||||
encrypted_content: summary_with_prefix(summary),
|
||||
metadata: None,
|
||||
}]
|
||||
@@ -329,6 +330,7 @@ async fn remote_compact_replaces_history_for_followups() -> Result<()> {
|
||||
.await;
|
||||
|
||||
let compacted_history = vec![ResponseItem::Compaction {
|
||||
id: None,
|
||||
encrypted_content: "ENCRYPTED_COMPACTION_SUMMARY".to_string(),
|
||||
metadata: None,
|
||||
}];
|
||||
@@ -2356,6 +2358,7 @@ async fn remote_compact_persists_replacement_history_in_rollout() -> Result<()>
|
||||
|
||||
let compacted_history = vec![
|
||||
ResponseItem::Compaction {
|
||||
id: None,
|
||||
encrypted_content: "ENCRYPTED_COMPACTION_SUMMARY".to_string(),
|
||||
metadata: None,
|
||||
},
|
||||
@@ -2511,6 +2514,7 @@ async fn remote_compact_and_resume_refresh_stale_developer_instructions() -> Res
|
||||
metadata: None,
|
||||
},
|
||||
ResponseItem::Compaction {
|
||||
id: None,
|
||||
encrypted_content: "ENCRYPTED_COMPACTION_SUMMARY".to_string(),
|
||||
metadata: None,
|
||||
},
|
||||
@@ -2653,6 +2657,7 @@ async fn remote_compact_refreshes_stale_developer_instructions_without_resume()
|
||||
metadata: None,
|
||||
},
|
||||
ResponseItem::Compaction {
|
||||
id: None,
|
||||
encrypted_content: "ENCRYPTED_COMPACTION_SUMMARY".to_string(),
|
||||
metadata: None,
|
||||
},
|
||||
@@ -4059,6 +4064,7 @@ async fn snapshot_request_shape_remote_mid_turn_compaction_summary_only_reinject
|
||||
.await;
|
||||
|
||||
let compacted_history = vec![ResponseItem::Compaction {
|
||||
id: None,
|
||||
encrypted_content: summary_with_prefix("REMOTE_SUMMARY_ONLY"),
|
||||
metadata: None,
|
||||
}];
|
||||
|
||||
@@ -87,6 +87,7 @@ async fn recent_image_fallback_selects_newest_images_in_chronological_order() {
|
||||
metadata: None,
|
||||
},
|
||||
ResponseItem::FunctionCallOutput {
|
||||
id: None,
|
||||
call_id: "mcp-call".to_string(),
|
||||
output: image_output("mcp"),
|
||||
metadata: None,
|
||||
@@ -100,19 +101,21 @@ async fn recent_image_fallback_selects_newest_images_in_chronological_order() {
|
||||
metadata: None,
|
||||
},
|
||||
ResponseItem::CustomToolCallOutput {
|
||||
id: None,
|
||||
call_id: "code-mode-call".to_string(),
|
||||
name: Some("exec".to_string()),
|
||||
output: image_output("code-mode"),
|
||||
metadata: None,
|
||||
},
|
||||
ResponseItem::ImageGenerationCall {
|
||||
id: "generated-call".to_string(),
|
||||
id: Some("generated-call".to_string()),
|
||||
status: "completed".to_string(),
|
||||
revised_prompt: None,
|
||||
result: "generated".to_string(),
|
||||
metadata: None,
|
||||
},
|
||||
ResponseItem::FunctionCallOutput {
|
||||
id: None,
|
||||
call_id: "orphan-call".to_string(),
|
||||
output: image_output("orphan"),
|
||||
metadata: None,
|
||||
|
||||
@@ -742,6 +742,7 @@ mod tests {
|
||||
let serialized =
|
||||
job::serialize_filtered_rollout_response_items(&[RolloutItem::ResponseItem(
|
||||
ResponseItem::FunctionCallOutput {
|
||||
id: None,
|
||||
call_id: "call_123".to_string(),
|
||||
output: codex_protocol::models::FunctionCallOutputPayload {
|
||||
body: codex_protocol::models::FunctionCallOutputBody::Text(
|
||||
|
||||
@@ -920,6 +920,7 @@ pub enum ResponseItem {
|
||||
Message {
|
||||
#[serde(default, skip_serializing)]
|
||||
#[ts(skip)]
|
||||
#[schemars(skip)]
|
||||
id: Option<String>,
|
||||
role: String,
|
||||
content: Vec<ContentItem>,
|
||||
@@ -934,6 +935,10 @@ pub enum ResponseItem {
|
||||
metadata: Option<ResponseItemMetadata>,
|
||||
},
|
||||
AgentMessage {
|
||||
#[serde(default, skip_serializing)]
|
||||
#[ts(skip)]
|
||||
#[schemars(skip)]
|
||||
id: Option<String>,
|
||||
author: String,
|
||||
recipient: String,
|
||||
content: Vec<AgentMessageInputContent>,
|
||||
@@ -945,7 +950,7 @@ pub enum ResponseItem {
|
||||
#[serde(default, skip_serializing)]
|
||||
#[ts(skip)]
|
||||
#[schemars(skip)]
|
||||
id: String,
|
||||
id: Option<String>,
|
||||
summary: Vec<ReasoningItemReasoningSummary>,
|
||||
#[serde(default, skip_serializing_if = "should_serialize_reasoning_content")]
|
||||
#[ts(optional)]
|
||||
@@ -959,6 +964,7 @@ pub enum ResponseItem {
|
||||
/// Legacy id field retained for compatibility with older payloads.
|
||||
#[serde(default, skip_serializing)]
|
||||
#[ts(skip)]
|
||||
#[schemars(skip)]
|
||||
id: Option<String>,
|
||||
/// Set when using the Responses API.
|
||||
call_id: Option<String>,
|
||||
@@ -971,6 +977,7 @@ pub enum ResponseItem {
|
||||
FunctionCall {
|
||||
#[serde(default, skip_serializing)]
|
||||
#[ts(skip)]
|
||||
#[schemars(skip)]
|
||||
id: Option<String>,
|
||||
name: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
@@ -988,6 +995,7 @@ pub enum ResponseItem {
|
||||
ToolSearchCall {
|
||||
#[serde(default, skip_serializing)]
|
||||
#[ts(skip)]
|
||||
#[schemars(skip)]
|
||||
id: Option<String>,
|
||||
call_id: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
@@ -1006,6 +1014,10 @@ pub enum ResponseItem {
|
||||
// - an array of structured content items (`content_items`)
|
||||
// We keep this behavior centralized in `FunctionCallOutputPayload`.
|
||||
FunctionCallOutput {
|
||||
#[serde(default, skip_serializing)]
|
||||
#[ts(skip)]
|
||||
#[schemars(skip)]
|
||||
id: Option<String>,
|
||||
call_id: String,
|
||||
#[ts(as = "FunctionCallOutputBody")]
|
||||
#[schemars(with = "FunctionCallOutputBody")]
|
||||
@@ -1017,6 +1029,7 @@ pub enum ResponseItem {
|
||||
CustomToolCall {
|
||||
#[serde(default, skip_serializing)]
|
||||
#[ts(skip)]
|
||||
#[schemars(skip)]
|
||||
id: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
@@ -1033,6 +1046,10 @@ pub enum ResponseItem {
|
||||
// `function_call_output.output` so freeform tools can return either plain
|
||||
// text or structured content items.
|
||||
CustomToolCallOutput {
|
||||
#[serde(default, skip_serializing)]
|
||||
#[ts(skip)]
|
||||
#[schemars(skip)]
|
||||
id: Option<String>,
|
||||
call_id: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
@@ -1045,6 +1062,10 @@ pub enum ResponseItem {
|
||||
metadata: Option<ResponseItemMetadata>,
|
||||
},
|
||||
ToolSearchOutput {
|
||||
#[serde(default, skip_serializing)]
|
||||
#[ts(skip)]
|
||||
#[schemars(skip)]
|
||||
id: Option<String>,
|
||||
call_id: Option<String>,
|
||||
status: String,
|
||||
execution: String,
|
||||
@@ -1065,6 +1086,7 @@ pub enum ResponseItem {
|
||||
WebSearchCall {
|
||||
#[serde(default, skip_serializing)]
|
||||
#[ts(skip)]
|
||||
#[schemars(skip)]
|
||||
id: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
@@ -1086,7 +1108,10 @@ pub enum ResponseItem {
|
||||
// "result":"..."
|
||||
// }
|
||||
ImageGenerationCall {
|
||||
id: String,
|
||||
/// Existing provider ID retained on serialized history for compatibility.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
id: Option<String>,
|
||||
status: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
@@ -1098,17 +1123,29 @@ pub enum ResponseItem {
|
||||
},
|
||||
#[serde(alias = "compaction_summary")]
|
||||
Compaction {
|
||||
#[serde(default, skip_serializing)]
|
||||
#[ts(skip)]
|
||||
#[schemars(skip)]
|
||||
id: Option<String>,
|
||||
encrypted_content: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
metadata: Option<ResponseItemMetadata>,
|
||||
},
|
||||
CompactionTrigger {
|
||||
#[serde(default, skip_serializing)]
|
||||
#[ts(skip)]
|
||||
#[schemars(skip)]
|
||||
id: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
metadata: Option<ResponseItemMetadata>,
|
||||
},
|
||||
ContextCompaction {
|
||||
#[serde(default, skip_serializing)]
|
||||
#[ts(skip)]
|
||||
#[schemars(skip)]
|
||||
id: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
encrypted_content: Option<String>,
|
||||
@@ -1126,6 +1163,50 @@ impl ResponseItem {
|
||||
matches!(self, Self::Message { role, .. } if role == "user")
|
||||
}
|
||||
|
||||
/// Returns the non-empty Responses API item ID, if present.
|
||||
pub fn id(&self) -> Option<&str> {
|
||||
match self {
|
||||
Self::Message { id, .. }
|
||||
| Self::AgentMessage { id, .. }
|
||||
| Self::LocalShellCall { id, .. }
|
||||
| Self::FunctionCall { id, .. }
|
||||
| Self::ToolSearchCall { id, .. }
|
||||
| Self::FunctionCallOutput { id, .. }
|
||||
| Self::CustomToolCall { id, .. }
|
||||
| Self::CustomToolCallOutput { id, .. }
|
||||
| Self::ToolSearchOutput { id, .. }
|
||||
| Self::WebSearchCall { id, .. }
|
||||
| Self::Reasoning { id, .. }
|
||||
| Self::ImageGenerationCall { id, .. }
|
||||
| Self::Compaction { id, .. }
|
||||
| Self::CompactionTrigger { id, .. }
|
||||
| Self::ContextCompaction { id, .. } => id.as_deref().filter(|id| !id.is_empty()),
|
||||
Self::Other => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the Responses API item ID for variants that carry one.
|
||||
pub fn set_id(&mut self, new_id: String) {
|
||||
match self {
|
||||
Self::Message { id, .. }
|
||||
| Self::AgentMessage { id, .. }
|
||||
| Self::LocalShellCall { id, .. }
|
||||
| Self::FunctionCall { id, .. }
|
||||
| Self::ToolSearchCall { id, .. }
|
||||
| Self::FunctionCallOutput { id, .. }
|
||||
| Self::CustomToolCall { id, .. }
|
||||
| Self::CustomToolCallOutput { id, .. }
|
||||
| Self::ToolSearchOutput { id, .. }
|
||||
| Self::WebSearchCall { id, .. }
|
||||
| Self::Reasoning { id, .. }
|
||||
| Self::ImageGenerationCall { id, .. }
|
||||
| Self::Compaction { id, .. }
|
||||
| Self::CompactionTrigger { id, .. }
|
||||
| Self::ContextCompaction { id, .. } => *id = Some(new_id),
|
||||
Self::Other => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the non-empty turn ID stamped onto this item, if present.
|
||||
pub fn turn_id(&self) -> Option<&str> {
|
||||
self.metadata()
|
||||
@@ -1168,7 +1249,7 @@ impl ResponseItem {
|
||||
| Self::WebSearchCall { metadata, .. }
|
||||
| Self::ImageGenerationCall { metadata, .. }
|
||||
| Self::Compaction { metadata, .. }
|
||||
| Self::CompactionTrigger { metadata }
|
||||
| Self::CompactionTrigger { metadata, .. }
|
||||
| Self::ContextCompaction { metadata, .. } => metadata.as_ref(),
|
||||
Self::Other => None,
|
||||
}
|
||||
@@ -1189,7 +1270,7 @@ impl ResponseItem {
|
||||
| Self::WebSearchCall { metadata, .. }
|
||||
| Self::ImageGenerationCall { metadata, .. }
|
||||
| Self::Compaction { metadata, .. }
|
||||
| Self::CompactionTrigger { metadata }
|
||||
| Self::CompactionTrigger { metadata, .. }
|
||||
| Self::ContextCompaction { metadata, .. } => Some(metadata),
|
||||
Self::Other => None,
|
||||
}
|
||||
@@ -1435,6 +1516,7 @@ impl From<ResponseInputItem> for ResponseItem {
|
||||
metadata: None,
|
||||
},
|
||||
ResponseInputItem::FunctionCallOutput { call_id, output } => Self::FunctionCallOutput {
|
||||
id: None,
|
||||
call_id,
|
||||
output,
|
||||
metadata: None,
|
||||
@@ -1442,6 +1524,7 @@ impl From<ResponseInputItem> for ResponseItem {
|
||||
ResponseInputItem::McpToolCallOutput { call_id, output } => {
|
||||
let output = output.into_function_call_output_payload();
|
||||
Self::FunctionCallOutput {
|
||||
id: None,
|
||||
call_id,
|
||||
output,
|
||||
metadata: None,
|
||||
@@ -1452,6 +1535,7 @@ impl From<ResponseInputItem> for ResponseItem {
|
||||
name,
|
||||
output,
|
||||
} => Self::CustomToolCallOutput {
|
||||
id: None,
|
||||
call_id,
|
||||
name,
|
||||
output,
|
||||
@@ -1467,6 +1551,7 @@ impl From<ResponseInputItem> for ResponseItem {
|
||||
status,
|
||||
execution,
|
||||
tools,
|
||||
id: None,
|
||||
metadata: None,
|
||||
},
|
||||
}
|
||||
@@ -2060,6 +2145,16 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn response_item_id_getter_and_setter() {
|
||||
let mut item = response_item_with_metadata(/*metadata*/ None);
|
||||
assert_eq!(item.id(), None);
|
||||
|
||||
item.set_id("msg_test".to_string());
|
||||
|
||||
assert_eq!(item.id(), Some("msg_test"));
|
||||
}
|
||||
|
||||
fn response_item_with_metadata(metadata: Option<ResponseItemMetadata>) -> ResponseItem {
|
||||
ResponseItem::Message {
|
||||
id: None,
|
||||
@@ -2176,7 +2271,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
item,
|
||||
ResponseItem::ImageGenerationCall {
|
||||
id: "ig_123".to_string(),
|
||||
id: Some("ig_123".to_string()),
|
||||
status: "completed".to_string(),
|
||||
revised_prompt: Some("A small blue square".to_string()),
|
||||
result: "Zm9v".to_string(),
|
||||
@@ -2198,7 +2293,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
item,
|
||||
ResponseItem::ImageGenerationCall {
|
||||
id: "ig_123".to_string(),
|
||||
id: Some("ig_123".to_string()),
|
||||
status: "completed".to_string(),
|
||||
revised_prompt: None,
|
||||
result: "Zm9v".to_string(),
|
||||
@@ -2900,6 +2995,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
item,
|
||||
ResponseItem::Compaction {
|
||||
id: None,
|
||||
encrypted_content: "abc".into(),
|
||||
metadata: None,
|
||||
}
|
||||
@@ -2916,6 +3012,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
item,
|
||||
ResponseItem::ContextCompaction {
|
||||
id: None,
|
||||
encrypted_content: Some("abc".into()),
|
||||
metadata: None,
|
||||
}
|
||||
@@ -2925,7 +3022,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn serializes_compaction_trigger_without_payload() -> Result<()> {
|
||||
let item = ResponseItem::CompactionTrigger { metadata: None };
|
||||
let item = ResponseItem::CompactionTrigger {
|
||||
id: None,
|
||||
metadata: None,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
serde_json::to_value(item)?,
|
||||
@@ -2938,7 +3038,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn serializes_stamped_compaction_trigger_metadata() -> Result<()> {
|
||||
let mut item = ResponseItem::CompactionTrigger { metadata: None };
|
||||
let mut item = ResponseItem::CompactionTrigger {
|
||||
id: None,
|
||||
metadata: None,
|
||||
};
|
||||
item.stamp_turn_id_if_missing("turn-1");
|
||||
|
||||
assert_eq!(
|
||||
@@ -2959,7 +3062,13 @@ mod tests {
|
||||
|
||||
let item: ResponseItem = serde_json::from_str(json)?;
|
||||
|
||||
assert_eq!(item, ResponseItem::CompactionTrigger { metadata: None });
|
||||
assert_eq!(
|
||||
item,
|
||||
ResponseItem::CompactionTrigger {
|
||||
id: None,
|
||||
metadata: None,
|
||||
}
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -3188,6 +3297,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
ResponseItem::from(input.clone()),
|
||||
ResponseItem::ToolSearchOutput {
|
||||
id: None,
|
||||
call_id: Some("search-1".to_string()),
|
||||
status: "completed".to_string(),
|
||||
execution: "client".to_string(),
|
||||
@@ -3275,6 +3385,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
parsed_output,
|
||||
ResponseItem::ToolSearchOutput {
|
||||
id: None,
|
||||
call_id: None,
|
||||
status: "completed".to_string(),
|
||||
execution: "server".to_string(),
|
||||
|
||||
@@ -762,6 +762,7 @@ impl InterAgentCommunication {
|
||||
}],
|
||||
};
|
||||
ResponseItem::AgentMessage {
|
||||
id: None,
|
||||
author: self.author.to_string(),
|
||||
recipient: self.recipient.to_string(),
|
||||
content,
|
||||
@@ -4284,6 +4285,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
communication.to_model_input_item(),
|
||||
ResponseItem::AgentMessage {
|
||||
id: None,
|
||||
author: "/root/worker".to_string(),
|
||||
recipient: "/root".to_string(),
|
||||
content: vec![
|
||||
|
||||
@@ -496,7 +496,7 @@ mod tests {
|
||||
#[test]
|
||||
fn traced_response_item_preserves_reasoning_content_omitted_by_normal_serializer() {
|
||||
let item = ResponseItem::Reasoning {
|
||||
id: "rs-1".to_string(),
|
||||
id: Some("rs-1".to_string()),
|
||||
summary: vec![ReasoningItemReasoningSummary::SummaryText {
|
||||
text: "summary".to_string(),
|
||||
}],
|
||||
|
||||
@@ -510,6 +510,7 @@ mod tests {
|
||||
memory_citation: None,
|
||||
})),
|
||||
RolloutItem::ResponseItem(ResponseItem::FunctionCallOutput {
|
||||
id: None,
|
||||
call_id: "call-1".to_string(),
|
||||
output: FunctionCallOutputPayload::from_text("tool output".to_string()),
|
||||
metadata: None,
|
||||
|
||||
Reference in New Issue
Block a user