mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
[codex] Assign response item IDs when recording history (#28814)
## Why Client-created response items enter history without IDs, so their identity is lost across rollout persistence and resume. IDs should be assigned once at the history-recording boundary, while IDs returned by the server must remain unchanged. The Responses API validates item IDs using type-specific prefixes. Locally generated IDs therefore use the matching prefix plus a hyphenated UUIDv7, keeping them valid while distinguishable from server-generated IDs. Because this changes persisted history and provider request shapes, the behavior is opt-in behind the under-development `item_ids` feature. Compaction triggers remain request controls whose API shape does not accept an ID. ## What changed - Register the disabled-by-default `item_ids` feature and expose it in `config.schema.json`. - Make supported optional `ResponseItem` IDs serializable and expose them in the generated app-server schemas. - When `item_ids` is enabled, assign an ID during conversation-history preparation if an item has no ID. - Generate type-prefixed, hyphenated UUIDv7 IDs using the Responses API item conventions. - Preserve existing server IDs without rewriting them. - Persist assigned IDs in rollouts and include them in subsequent Responses requests. - Remove the unsupported ID field from `CompactionTrigger` and document why it has no ID. - Add integration coverage for enabled ID persistence, preservation of server IDs, and omission of generated IDs while the feature is disabled. `prepare_conversation_items_for_history` is the single response-item ID allocation boundary. ## Test plan - `just test -p codex-features` - `just test -p codex-core response_item_ids_persist_across_resume_and_preserve_server_ids` - `just test -p codex-core non_openai_responses_requests_omit_item_turn_metadata` - `just test -p codex-core resize_all_images_prepares_failures_before_history_insertion` - `just test -p codex-protocol` - `just test -p codex-app-server-protocol` - `just test -p codex-api azure_default_store_attaches_ids_and_headers`
This commit is contained in:
+79
-1
@@ -2287,6 +2287,12 @@
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -2337,6 +2343,12 @@
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -2385,6 +2397,12 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -2428,6 +2446,13 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"description": "Legacy id field retained for compatibility with older payloads.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -2465,6 +2490,12 @@
|
||||
"call_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -2513,6 +2544,12 @@
|
||||
"execution": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -2550,6 +2587,12 @@
|
||||
"call_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -2584,6 +2627,12 @@
|
||||
"call_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"input": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -2628,6 +2677,12 @@
|
||||
"call_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -2674,6 +2729,12 @@
|
||||
"execution": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -2720,6 +2781,12 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -2753,7 +2820,6 @@
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "Existing provider ID retained on serialized history for compatibility.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
@@ -2802,6 +2868,12 @@
|
||||
"encrypted_content": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -2861,6 +2933,12 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
|
||||
+79
-1
@@ -14833,6 +14833,12 @@
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -14883,6 +14889,12 @@
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -14931,6 +14943,12 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -14974,6 +14992,13 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"description": "Legacy id field retained for compatibility with older payloads.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -15011,6 +15036,12 @@
|
||||
"call_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -15059,6 +15090,12 @@
|
||||
"execution": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -15096,6 +15133,12 @@
|
||||
"call_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -15130,6 +15173,12 @@
|
||||
"call_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"input": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -15174,6 +15223,12 @@
|
||||
"call_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -15220,6 +15275,12 @@
|
||||
"execution": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -15266,6 +15327,12 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -15299,7 +15366,6 @@
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "Existing provider ID retained on serialized history for compatibility.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
@@ -15348,6 +15414,12 @@
|
||||
"encrypted_content": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -15407,6 +15479,12 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
|
||||
+79
-1
@@ -11257,6 +11257,12 @@
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -11307,6 +11313,12 @@
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -11355,6 +11367,12 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -11398,6 +11416,13 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"description": "Legacy id field retained for compatibility with older payloads.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -11435,6 +11460,12 @@
|
||||
"call_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -11483,6 +11514,12 @@
|
||||
"execution": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -11520,6 +11557,12 @@
|
||||
"call_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -11554,6 +11597,12 @@
|
||||
"call_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"input": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -11598,6 +11647,12 @@
|
||||
"call_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -11644,6 +11699,12 @@
|
||||
"execution": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -11690,6 +11751,12 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -11723,7 +11790,6 @@
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "Existing provider ID retained on serialized history for compatibility.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
@@ -11772,6 +11838,12 @@
|
||||
"encrypted_content": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -11831,6 +11903,12 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
|
||||
+79
-1
@@ -377,6 +377,12 @@
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -427,6 +433,12 @@
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -475,6 +487,12 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -518,6 +536,13 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"description": "Legacy id field retained for compatibility with older payloads.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -555,6 +580,12 @@
|
||||
"call_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -603,6 +634,12 @@
|
||||
"execution": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -640,6 +677,12 @@
|
||||
"call_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -674,6 +717,12 @@
|
||||
"call_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"input": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -718,6 +767,12 @@
|
||||
"call_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -764,6 +819,12 @@
|
||||
"execution": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -810,6 +871,12 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -843,7 +910,6 @@
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "Existing provider ID retained on serialized history for compatibility.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
@@ -892,6 +958,12 @@
|
||||
"encrypted_content": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -951,6 +1023,12 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
|
||||
@@ -448,6 +448,12 @@
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -498,6 +504,12 @@
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -546,6 +558,12 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -589,6 +607,13 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"description": "Legacy id field retained for compatibility with older payloads.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -626,6 +651,12 @@
|
||||
"call_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -674,6 +705,12 @@
|
||||
"execution": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -711,6 +748,12 @@
|
||||
"call_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -745,6 +788,12 @@
|
||||
"call_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"input": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -789,6 +838,12 @@
|
||||
"call_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -835,6 +890,12 @@
|
||||
"execution": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -881,6 +942,12 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -914,7 +981,6 @@
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "Existing provider ID retained on serialized history for compatibility.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
@@ -963,6 +1029,12 @@
|
||||
"encrypted_content": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -1022,6 +1094,12 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
|
||||
@@ -12,12 +12,12 @@ import type { ReasoningItemReasoningSummary } from "./ReasoningItemReasoningSumm
|
||||
import type { ResponseItemMetadata } from "./ResponseItemMetadata";
|
||||
import type { WebSearchAction } from "./WebSearchAction";
|
||||
|
||||
export type ResponseItem = { "type": "message", role: string, content: Array<ContentItem>, phase?: MessagePhase, metadata?: ResponseItemMetadata, } | { "type": "agent_message", author: string, recipient: string, content: Array<AgentMessageInputContent>, metadata?: ResponseItemMetadata, } | { "type": "reasoning", summary: Array<ReasoningItemReasoningSummary>, content?: Array<ReasoningItemContent>, encrypted_content: string | null, metadata?: ResponseItemMetadata, } | { "type": "local_shell_call",
|
||||
export type ResponseItem = { "type": "message", id?: string, role: string, content: Array<ContentItem>, phase?: MessagePhase, metadata?: ResponseItemMetadata, } | { "type": "agent_message", id?: string, author: string, recipient: string, content: Array<AgentMessageInputContent>, metadata?: ResponseItemMetadata, } | { "type": "reasoning", id?: string, summary: Array<ReasoningItemReasoningSummary>, content?: Array<ReasoningItemContent>, encrypted_content: string | null, metadata?: ResponseItemMetadata, } | { "type": "local_shell_call",
|
||||
/**
|
||||
* Legacy id field retained for compatibility with older payloads.
|
||||
*/
|
||||
id?: string,
|
||||
/**
|
||||
* 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",
|
||||
/**
|
||||
* 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" };
|
||||
call_id: string | null, status: LocalShellStatus, action: LocalShellAction, metadata?: ResponseItemMetadata, } | { "type": "function_call", id?: string, name: string, namespace?: string, arguments: string, call_id: string, metadata?: ResponseItemMetadata, } | { "type": "tool_search_call", id?: string, call_id: string | null, status?: string, execution: string, arguments: unknown, metadata?: ResponseItemMetadata, } | { "type": "function_call_output", id?: string, call_id: string, output: FunctionCallOutputBody, metadata?: ResponseItemMetadata, } | { "type": "custom_tool_call", id?: string, status?: string, call_id: string, name: string, input: string, metadata?: ResponseItemMetadata, } | { "type": "custom_tool_call_output", id?: string, call_id: string, name?: string, output: FunctionCallOutputBody, metadata?: ResponseItemMetadata, } | { "type": "tool_search_output", id?: string, call_id: string | null, status: string, execution: string, tools: unknown[], metadata?: ResponseItemMetadata, } | { "type": "web_search_call", id?: string, status?: string, action?: WebSearchAction, metadata?: ResponseItemMetadata, } | { "type": "image_generation_call", id?: string, status: string, revised_prompt?: string, result: string, metadata?: ResponseItemMetadata, } | { "type": "compaction", id?: string, encrypted_content: string, metadata?: ResponseItemMetadata, } | { "type": "compaction_trigger", metadata?: ResponseItemMetadata, } | { "type": "context_compaction", id?: string, encrypted_content?: string, metadata?: ResponseItemMetadata, } | { "type": "other" };
|
||||
|
||||
@@ -9,7 +9,6 @@ use codex_protocol::models::ResponseItem;
|
||||
use http::HeaderMap;
|
||||
use http::Method;
|
||||
use serde::Deserialize;
|
||||
use serde_json::to_value;
|
||||
use std::sync::Arc;
|
||||
use std::sync::OnceLock;
|
||||
use std::time::Duration;
|
||||
@@ -76,7 +75,7 @@ impl<T: HttpTransport> CompactClient<T> {
|
||||
request_timeout: Duration,
|
||||
turn_state: Option<&OnceLock<String>>,
|
||||
) -> Result<Vec<ResponseItem>, ApiError> {
|
||||
let body = to_value(input)
|
||||
let body = serde_json::to_value(input)
|
||||
.map_err(|e| ApiError::Stream(format!("failed to encode compaction input: {e}")))?;
|
||||
self.compact(body, extra_headers, request_timeout, turn_state)
|
||||
.await
|
||||
|
||||
@@ -5,7 +5,6 @@ use crate::endpoint::session::EndpointSession;
|
||||
use crate::error::ApiError;
|
||||
use crate::provider::Provider;
|
||||
use crate::requests::Compression;
|
||||
use crate::requests::attach_item_ids;
|
||||
use crate::requests::headers::build_session_headers;
|
||||
use crate::requests::headers::insert_header;
|
||||
use crate::requests::headers::subagent_header;
|
||||
@@ -82,16 +81,8 @@ impl<T: HttpTransport> ResponsesClient<T> {
|
||||
turn_state,
|
||||
} = options;
|
||||
|
||||
let body = if request.store && self.session.provider().is_azure_responses_endpoint() {
|
||||
let mut body = serde_json::to_value(&request).map_err(|e| {
|
||||
ApiError::Stream(format!("failed to encode responses request: {e}"))
|
||||
})?;
|
||||
attach_item_ids(&mut body, &request.input);
|
||||
EncodedJsonBody::encode(&body)
|
||||
} else {
|
||||
EncodedJsonBody::encode(&request)
|
||||
}
|
||||
.map_err(|e| ApiError::Stream(format!("failed to encode responses request: {e}")))?;
|
||||
let body = EncodedJsonBody::encode(&request)
|
||||
.map_err(|e| ApiError::Stream(format!("failed to encode responses request: {e}")))?;
|
||||
|
||||
let mut headers = extra_headers;
|
||||
if let Some(ref thread_id) = thread_id {
|
||||
|
||||
@@ -149,7 +149,7 @@ mod tests {
|
||||
model: "gpt-test".to_string(),
|
||||
reasoning: None,
|
||||
input: Some(SearchInput::Items(vec![ResponseItem::Message {
|
||||
id: None,
|
||||
id: Some("msg_search".to_string()),
|
||||
role: "user".to_string(),
|
||||
content: vec![
|
||||
ContentItem::InputText {
|
||||
@@ -228,6 +228,7 @@ mod tests {
|
||||
"model": "gpt-test",
|
||||
"input": [{
|
||||
"type": "message",
|
||||
"id": "msg_search",
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"type": "input_text", "text": "find this"},
|
||||
|
||||
@@ -2,4 +2,3 @@ pub(crate) mod headers;
|
||||
pub(crate) mod responses;
|
||||
|
||||
pub use responses::Compression;
|
||||
pub(crate) use responses::attach_item_ids;
|
||||
|
||||
@@ -1,37 +1,6 @@
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use serde_json::Value;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
pub enum Compression {
|
||||
#[default]
|
||||
None,
|
||||
Zstd,
|
||||
}
|
||||
|
||||
pub(crate) fn attach_item_ids(payload_json: &mut Value, original_items: &[ResponseItem]) {
|
||||
let Some(input_value) = payload_json.get_mut("input") else {
|
||||
return;
|
||||
};
|
||||
let Value::Array(items) = input_value else {
|
||||
return;
|
||||
};
|
||||
|
||||
for (value, item) in items.iter_mut().zip(original_items.iter()) {
|
||||
if let ResponseItem::Reasoning { id: Some(id), .. }
|
||||
| ResponseItem::Message { id: Some(id), .. }
|
||||
| ResponseItem::WebSearchCall { id: Some(id), .. }
|
||||
| ResponseItem::FunctionCall { id: Some(id), .. }
|
||||
| ResponseItem::ToolSearchCall { id: Some(id), .. }
|
||||
| ResponseItem::LocalShellCall { id: Some(id), .. }
|
||||
| ResponseItem::CustomToolCall { id: Some(id), .. } = item
|
||||
{
|
||||
if id.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(obj) = value.as_object_mut() {
|
||||
obj.insert("id".to_string(), Value::String(id.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,7 +301,7 @@ async fn responses_client_uses_responses_path() -> Result<()> {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn responses_client_stream_request_preserves_exact_json_body() -> Result<()> {
|
||||
async fn responses_client_stream_request_preserves_item_ids() -> Result<()> {
|
||||
let state = RecordingState::default();
|
||||
let transport = RecordingTransport::new(state.clone());
|
||||
let client = ResponsesClient::new(transport, provider("openai"), Arc::new(NoAuth));
|
||||
@@ -327,7 +327,7 @@ async fn responses_client_stream_request_preserves_exact_json_body() -> Result<(
|
||||
text: None,
|
||||
client_metadata: None,
|
||||
};
|
||||
let expected = serde_json::to_vec(&request)?;
|
||||
let expected = serde_json::to_value(&request)?;
|
||||
|
||||
let _stream = client
|
||||
.stream_request(request, ResponsesOptions::default())
|
||||
@@ -338,7 +338,10 @@ async fn responses_client_stream_request_preserves_exact_json_body() -> Result<(
|
||||
let prepared = requests[0]
|
||||
.prepare_body_for_send()
|
||||
.expect("body should prepare");
|
||||
assert_eq!(prepared.body.as_deref(), Some(expected.as_slice()));
|
||||
let body: serde_json::Value =
|
||||
serde_json::from_slice(prepared.body.as_deref().expect("body should be JSON"))?;
|
||||
assert_eq!(body, expected);
|
||||
assert_eq!(body["input"][0]["id"], "msg_1");
|
||||
assert_eq!(
|
||||
prepared.headers.get(http::header::CONTENT_TYPE),
|
||||
Some(&HeaderValue::from_static("application/json"))
|
||||
@@ -502,7 +505,7 @@ async fn streaming_client_does_not_retry_auth_build_error() -> Result<()> {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn azure_default_store_attaches_ids_and_headers() -> Result<()> {
|
||||
async fn azure_store_sends_ids_and_headers() -> Result<()> {
|
||||
let state = RecordingState::default();
|
||||
let transport = RecordingTransport::new(state.clone());
|
||||
let client = ResponsesClient::new(transport, provider("azure"), Arc::new(NoAuth));
|
||||
|
||||
@@ -530,6 +530,9 @@
|
||||
"in_app_browser": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"item_ids": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"js_repl": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -4760,6 +4763,9 @@
|
||||
"in_app_browser": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"item_ids": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"js_repl": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
||||
@@ -177,6 +177,7 @@ struct ModelClientState {
|
||||
enable_request_compression: bool,
|
||||
include_timing_metrics: bool,
|
||||
beta_features_header: Option<String>,
|
||||
item_ids_enabled: bool,
|
||||
include_attestation: bool,
|
||||
attestation_provider: Option<Arc<dyn AttestationProvider>>,
|
||||
disable_websockets: AtomicBool,
|
||||
@@ -377,6 +378,7 @@ impl ModelClient {
|
||||
enable_request_compression: bool,
|
||||
include_timing_metrics: bool,
|
||||
beta_features_header: Option<String>,
|
||||
item_ids_enabled: bool,
|
||||
attestation_provider: Option<Arc<dyn AttestationProvider>>,
|
||||
) -> Self {
|
||||
let model_provider = create_model_provider(provider_info, auth_manager);
|
||||
@@ -397,6 +399,7 @@ impl ModelClient {
|
||||
enable_request_compression,
|
||||
include_timing_metrics,
|
||||
beta_features_header,
|
||||
item_ids_enabled,
|
||||
include_attestation,
|
||||
attestation_provider,
|
||||
disable_websockets: AtomicBool::new(false),
|
||||
@@ -519,7 +522,7 @@ impl ModelClient {
|
||||
let ResponsesApiRequest {
|
||||
model,
|
||||
instructions,
|
||||
input,
|
||||
mut input,
|
||||
tools,
|
||||
parallel_tool_calls,
|
||||
reasoning,
|
||||
@@ -528,6 +531,7 @@ impl ModelClient {
|
||||
text,
|
||||
..
|
||||
} = request;
|
||||
self.prepare_response_items_for_request(&mut input, /*store*/ false);
|
||||
let payload = ApiCompactionInput {
|
||||
model: &model,
|
||||
input: &input,
|
||||
@@ -823,6 +827,16 @@ impl ModelClient {
|
||||
Ok(request)
|
||||
}
|
||||
|
||||
fn prepare_response_items_for_request(&self, input: &mut [ResponseItem], store: bool) {
|
||||
if self.state.item_ids_enabled || store {
|
||||
return;
|
||||
}
|
||||
|
||||
for item in input {
|
||||
item.set_id(/*new_id*/ None);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether the Responses-over-WebSocket transport is active for this session.
|
||||
///
|
||||
/// WebSocket use is controlled by provider capability and session-scoped fallback state.
|
||||
@@ -1292,7 +1306,7 @@ impl ModelClientSession {
|
||||
)
|
||||
.await;
|
||||
|
||||
let request = self.client.build_responses_request(
|
||||
let mut request = self.client.build_responses_request(
|
||||
&client_setup.api_provider,
|
||||
prompt,
|
||||
model_info,
|
||||
@@ -1301,6 +1315,9 @@ impl ModelClientSession {
|
||||
service_tier.clone(),
|
||||
responses_metadata,
|
||||
)?;
|
||||
let store = request.store;
|
||||
self.client
|
||||
.prepare_response_items_for_request(&mut request.input, store);
|
||||
let inference_trace_attempt = inference_trace.start_attempt();
|
||||
inference_trace_attempt.add_request_headers(&mut options.extra_headers);
|
||||
inference_trace_attempt.record_started(&request);
|
||||
@@ -1468,6 +1485,10 @@ impl ModelClientSession {
|
||||
inference_trace.start_attempt()
|
||||
};
|
||||
stamp_ws_stream_request_start_ms(&mut ws_request);
|
||||
let ResponsesWsRequest::ResponseCreate(ws_payload) = &mut ws_request;
|
||||
let store = ws_payload.store;
|
||||
self.client
|
||||
.prepare_response_items_for_request(&mut ws_payload.input, store);
|
||||
if previous_response_id_from_untraced_warmup {
|
||||
// The transport can reuse an untraced warmup response id and omit the
|
||||
// already-sent input, but rollout replay needs the logical model-visible
|
||||
|
||||
@@ -76,6 +76,7 @@ fn test_model_client(session_source: SessionSource) -> ModelClient {
|
||||
/*enable_request_compression*/ false,
|
||||
/*include_timing_metrics*/ false,
|
||||
/*beta_features_header*/ None,
|
||||
/*item_ids_enabled*/ false,
|
||||
/*attestation_provider*/ None,
|
||||
)
|
||||
}
|
||||
@@ -566,6 +567,7 @@ fn model_client_with_counting_attestation(
|
||||
/*enable_request_compression*/ false,
|
||||
/*include_timing_metrics*/ false,
|
||||
/*beta_features_header*/ None,
|
||||
/*item_ids_enabled*/ false,
|
||||
Some(Arc::new(CountingAttestationProvider {
|
||||
calls: attestation_calls.clone(),
|
||||
})),
|
||||
|
||||
@@ -231,10 +231,7 @@ async fn run_remote_compact_task_inner_impl(
|
||||
)
|
||||
.await?;
|
||||
let mut input = prompt_input.clone();
|
||||
input.push(ResponseItem::CompactionTrigger {
|
||||
id: None,
|
||||
metadata: None,
|
||||
});
|
||||
input.push(ResponseItem::CompactionTrigger { metadata: None });
|
||||
let prompt = Prompt {
|
||||
input,
|
||||
tools: tool_router.model_visible_specs(),
|
||||
|
||||
@@ -2664,17 +2664,47 @@ impl Session {
|
||||
turn_context: &TurnContext,
|
||||
items: &'a [ResponseItem],
|
||||
) -> Cow<'a, [ResponseItem]> {
|
||||
if !turn_context
|
||||
let mut items = Cow::Borrowed(items);
|
||||
if turn_context
|
||||
.config
|
||||
.features
|
||||
.enabled(Feature::ResizeAllImages)
|
||||
{
|
||||
return Cow::Borrowed(items);
|
||||
prepare_response_items(items.to_mut());
|
||||
}
|
||||
if turn_context.config.features.enabled(Feature::ItemIds) {
|
||||
Self::assign_missing_response_item_ids(&mut items);
|
||||
}
|
||||
items
|
||||
}
|
||||
|
||||
let mut prepared_items = items.to_vec();
|
||||
prepare_response_items(&mut prepared_items);
|
||||
Cow::Owned(prepared_items)
|
||||
fn assign_missing_response_item_ids(items: &mut Cow<'_, [ResponseItem]>) {
|
||||
if items.iter().all(|item| item.id().is_some()) {
|
||||
return;
|
||||
}
|
||||
for item in items.to_mut() {
|
||||
if item.id().is_some() {
|
||||
continue;
|
||||
}
|
||||
let prefix = match item {
|
||||
ResponseItem::Message { .. } => "msg",
|
||||
ResponseItem::Reasoning { .. } => "rs",
|
||||
ResponseItem::LocalShellCall { .. } => "lsh",
|
||||
ResponseItem::FunctionCall { .. } => "fc",
|
||||
ResponseItem::ToolSearchCall { .. } => "tsc",
|
||||
ResponseItem::FunctionCallOutput { .. } => "fco",
|
||||
ResponseItem::CustomToolCall { .. } => "ctc",
|
||||
ResponseItem::CustomToolCallOutput { .. } => "ctco",
|
||||
ResponseItem::ToolSearchOutput { .. } => "tso",
|
||||
ResponseItem::WebSearchCall { .. } => "ws",
|
||||
ResponseItem::ImageGenerationCall { .. } => "ig",
|
||||
ResponseItem::Compaction { .. } | ResponseItem::ContextCompaction { .. } => "cmp",
|
||||
ResponseItem::AgentMessage { .. }
|
||||
| ResponseItem::CompactionTrigger { .. }
|
||||
| ResponseItem::Other => continue,
|
||||
};
|
||||
item.set_id(Some(format!("{prefix}_{}", Uuid::now_v7())));
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn response_item_from_user_input(
|
||||
|
||||
@@ -1039,6 +1039,7 @@ impl Session {
|
||||
config.features.enabled(Feature::EnableRequestCompression),
|
||||
config.features.enabled(Feature::RuntimeMetrics),
|
||||
Self::build_model_client_beta_features_header(config.as_ref()),
|
||||
/*item_ids_enabled*/ config.features.enabled(Feature::ItemIds),
|
||||
attestation_provider,
|
||||
)
|
||||
.with_prompt_cache_key_override(
|
||||
|
||||
@@ -42,6 +42,7 @@ use codex_protocol::config_types::ServiceTier;
|
||||
use codex_protocol::config_types::TrustLevel;
|
||||
use codex_protocol::exec_output::ExecToolCallOutput;
|
||||
use codex_protocol::models::ActivePermissionProfile;
|
||||
use codex_protocol::models::AgentMessageInputContent;
|
||||
use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_WORKSPACE;
|
||||
use codex_protocol::models::FileSystemPermissions;
|
||||
use codex_protocol::models::FunctionCallOutputBody;
|
||||
@@ -198,6 +199,27 @@ fn user_message(text: &str) -> ResponseItem {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assign_missing_response_item_ids_skips_agent_messages() {
|
||||
let mut items = Cow::Owned(vec![
|
||||
ResponseItem::AgentMessage {
|
||||
id: None,
|
||||
author: "worker".to_string(),
|
||||
recipient: "root".to_string(),
|
||||
content: vec![AgentMessageInputContent::InputText {
|
||||
text: "done".to_string(),
|
||||
}],
|
||||
metadata: None,
|
||||
},
|
||||
user_message("hello"),
|
||||
]);
|
||||
|
||||
Session::assign_missing_response_item_ids(&mut items);
|
||||
|
||||
assert_eq!(items[0].id(), None);
|
||||
assert!(items[1].id().is_some_and(|id| id.starts_with("msg_")));
|
||||
}
|
||||
|
||||
fn assistant_message(text: &str) -> ResponseItem {
|
||||
ResponseItem::Message {
|
||||
id: None,
|
||||
@@ -438,6 +460,7 @@ fn test_model_client_session() -> crate::client::ModelClientSession {
|
||||
/*enable_request_compression*/ false,
|
||||
/*include_timing_metrics*/ false,
|
||||
/*beta_features_header*/ None,
|
||||
/*item_ids_enabled*/ false,
|
||||
/*attestation_provider*/ None,
|
||||
)
|
||||
.new_session()
|
||||
@@ -1670,6 +1693,7 @@ async fn resize_all_images_prepares_failures_before_history_insertion() {
|
||||
Vec::new(),
|
||||
|config| {
|
||||
let _ = config.features.enable(Feature::ResizeAllImages);
|
||||
let _ = config.features.enable(Feature::ItemIds);
|
||||
},
|
||||
)
|
||||
.await;
|
||||
@@ -1699,8 +1723,18 @@ async fn resize_all_images_prepares_failures_before_history_insertion() {
|
||||
.record_conversation_items(turn_context.as_ref(), std::slice::from_ref(&item))
|
||||
.await;
|
||||
|
||||
let history = session.state.lock().await.clone_history();
|
||||
let id = history.raw_items()[0]
|
||||
.id()
|
||||
.expect("history item should have an ID")
|
||||
.to_string();
|
||||
let uuid = id
|
||||
.strip_prefix("fco_")
|
||||
.expect("function call output ID should have the Responses API prefix");
|
||||
let parsed_id = Uuid::parse_str(uuid).expect("history item should have a UUID ID");
|
||||
assert_eq!(parsed_id.get_version(), Some(uuid::Version::SortRand));
|
||||
let expected = vec![ResponseItem::FunctionCallOutput {
|
||||
id: None,
|
||||
id: Some(id),
|
||||
call_id: "call-1".to_string(),
|
||||
output: FunctionCallOutputPayload {
|
||||
body: FunctionCallOutputBody::ContentItems(vec![
|
||||
@@ -1719,10 +1753,7 @@ async fn resize_all_images_prepares_failures_before_history_insertion() {
|
||||
},
|
||||
metadata: None,
|
||||
}];
|
||||
assert_eq!(
|
||||
session.state.lock().await.clone_history().raw_items(),
|
||||
expected.as_slice()
|
||||
);
|
||||
assert_eq!(history.raw_items(), expected.as_slice());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -5059,6 +5090,7 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
|
||||
config.features.enabled(Feature::EnableRequestCompression),
|
||||
config.features.enabled(Feature::RuntimeMetrics),
|
||||
Session::build_model_client_beta_features_header(config.as_ref()),
|
||||
/*item_ids_enabled*/ config.features.enabled(Feature::ItemIds),
|
||||
/*attestation_provider*/ None,
|
||||
),
|
||||
code_mode_service: crate::tools::code_mode::CodeModeService::new(),
|
||||
@@ -7113,6 +7145,7 @@ where
|
||||
config.features.enabled(Feature::EnableRequestCompression),
|
||||
config.features.enabled(Feature::RuntimeMetrics),
|
||||
Session::build_model_client_beta_features_header(config.as_ref()),
|
||||
/*item_ids_enabled*/ config.features.enabled(Feature::ItemIds),
|
||||
/*attestation_provider*/ None,
|
||||
),
|
||||
code_mode_service: crate::tools::code_mode::CodeModeService::new(),
|
||||
|
||||
@@ -127,6 +127,7 @@ async fn responses_stream_includes_subagent_header_on_review() {
|
||||
/*enable_request_compression*/ false,
|
||||
/*include_timing_metrics*/ false,
|
||||
/*beta_features_header*/ None,
|
||||
/*item_ids_enabled*/ false,
|
||||
/*attestation_provider*/ None,
|
||||
);
|
||||
let responses_metadata = test_turn_responses_metadata(&client, thread_id, &session_source);
|
||||
@@ -258,6 +259,7 @@ async fn responses_stream_includes_subagent_header_on_other() {
|
||||
/*enable_request_compression*/ false,
|
||||
/*include_timing_metrics*/ false,
|
||||
/*beta_features_header*/ None,
|
||||
/*item_ids_enabled*/ false,
|
||||
/*attestation_provider*/ None,
|
||||
);
|
||||
let responses_metadata = test_turn_responses_metadata(&client, thread_id, &session_source);
|
||||
@@ -375,6 +377,7 @@ async fn responses_respects_model_info_overrides_from_config() {
|
||||
/*enable_request_compression*/ false,
|
||||
/*include_timing_metrics*/ false,
|
||||
/*beta_features_header*/ None,
|
||||
/*item_ids_enabled*/ false,
|
||||
/*attestation_provider*/ None,
|
||||
);
|
||||
let responses_metadata = test_turn_responses_metadata(&client, thread_id, &session_source);
|
||||
|
||||
@@ -134,6 +134,22 @@ fn message_input_text_contains(request: &ResponsesRequest, role: &str, needle: &
|
||||
.any(|text| text.contains(needle))
|
||||
}
|
||||
|
||||
fn response_message_item_id(request: &ResponsesRequest, role: &str, text: &str) -> String {
|
||||
request
|
||||
.inputs_of_type("message")
|
||||
.into_iter()
|
||||
.find(|item| {
|
||||
item.get("role").and_then(serde_json::Value::as_str) == Some(role)
|
||||
&& message_input_texts(item).contains(&text)
|
||||
})
|
||||
.and_then(|item| {
|
||||
item.get("id")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.map(str::to_string)
|
||||
})
|
||||
.unwrap_or_else(|| panic!("missing item ID for {role} message {text:?}"))
|
||||
}
|
||||
|
||||
fn assert_codex_client_metadata(
|
||||
request_body: &serde_json::Value,
|
||||
installation_id: &str,
|
||||
@@ -216,9 +232,74 @@ async fn non_openai_responses_requests_omit_item_turn_metadata() {
|
||||
item.get("metadata").is_none(),
|
||||
"input item should omit metadata: {item}"
|
||||
);
|
||||
assert!(
|
||||
item.get("id").is_none(),
|
||||
"input item should omit generated IDs: {item}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn response_item_ids_persist_across_resume_and_preserve_server_ids() -> anyhow::Result<()> {
|
||||
let server = MockServer::start().await;
|
||||
let response_mock = mount_sse_sequence(
|
||||
&server,
|
||||
vec![
|
||||
sse(vec![
|
||||
ev_response_created("resp-1"),
|
||||
ev_assistant_message("msg_server", "first reply"),
|
||||
ev_completed("resp-1"),
|
||||
]),
|
||||
sse(vec![ev_response_created("resp-2"), ev_completed("resp-2")]),
|
||||
],
|
||||
)
|
||||
.await;
|
||||
let mut builder = test_codex().with_config(|config| {
|
||||
let _ = config.features.enable(Feature::ItemIds);
|
||||
});
|
||||
let initial = builder.build(&server).await?;
|
||||
let home = Arc::clone(&initial.home);
|
||||
let rollout_path = initial
|
||||
.session_configured
|
||||
.rollout_path
|
||||
.clone()
|
||||
.expect("rollout path");
|
||||
|
||||
initial.submit_turn("before resume").await?;
|
||||
initial.codex.submit(Op::Shutdown).await?;
|
||||
wait_for_event(&initial.codex, |event| {
|
||||
matches!(event, EventMsg::ShutdownComplete)
|
||||
})
|
||||
.await;
|
||||
|
||||
builder = builder.with_config(|config| {
|
||||
let _ = config.features.enable(Feature::ItemIds);
|
||||
});
|
||||
let resumed = builder.resume(&server, home, rollout_path).await?;
|
||||
resumed.submit_turn("after resume").await?;
|
||||
|
||||
let requests = response_mock.requests();
|
||||
assert_eq!(requests.len(), 2);
|
||||
let user_id = response_message_item_id(&requests[0], "user", "before resume");
|
||||
let user_uuid = user_id
|
||||
.strip_prefix("msg_")
|
||||
.expect("message ID should have the Responses API prefix");
|
||||
assert_eq!(
|
||||
Uuid::parse_str(user_uuid)?.get_version(),
|
||||
Some(uuid::Version::SortRand)
|
||||
);
|
||||
assert_eq!(
|
||||
response_message_item_id(&requests[1], "user", "before resume"),
|
||||
user_id
|
||||
);
|
||||
assert_eq!(
|
||||
response_message_item_id(&requests[1], "assistant", "first reply"),
|
||||
"msg_server"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 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)]
|
||||
@@ -1022,6 +1103,7 @@ async fn send_provider_auth_request(server: &MockServer, auth: ModelProviderAuth
|
||||
/*enable_request_compression*/ false,
|
||||
/*include_timing_metrics*/ false,
|
||||
/*beta_features_header*/ None,
|
||||
/*item_ids_enabled*/ config.features.enabled(Feature::ItemIds),
|
||||
/*attestation_provider*/ None,
|
||||
);
|
||||
let responses_metadata = test_turn_responses_metadata(&client, thread_id);
|
||||
@@ -2515,6 +2597,7 @@ async fn azure_responses_request_includes_store_and_reasoning_ids() {
|
||||
/*enable_request_compression*/ false,
|
||||
/*include_timing_metrics*/ false,
|
||||
/*beta_features_header*/ None,
|
||||
/*item_ids_enabled*/ false,
|
||||
/*attestation_provider*/ None,
|
||||
);
|
||||
let responses_metadata = test_turn_responses_metadata(&client, thread_id);
|
||||
|
||||
@@ -157,7 +157,8 @@ async fn responses_websocket_streams_request() {
|
||||
|
||||
let harness = websocket_harness(&server).await;
|
||||
let mut client_session = harness.client.new_session();
|
||||
let prompt = prompt_with_input(vec![message_item("hello")]);
|
||||
let mut prompt = prompt_with_input(vec![message_item("hello")]);
|
||||
prompt.input[0].set_id(Some("msg_existing".to_string()));
|
||||
|
||||
stream_until_complete(&mut client_session, &harness, &prompt).await;
|
||||
|
||||
@@ -169,6 +170,7 @@ async fn responses_websocket_streams_request() {
|
||||
assert_eq!(body["model"].as_str(), Some(MODEL));
|
||||
assert_eq!(body["stream"], serde_json::Value::Bool(true));
|
||||
assert_eq!(body["input"].as_array().map(Vec::len), Some(1));
|
||||
assert_eq!(body["input"][0].get("id"), None);
|
||||
let handshake = server.single_handshake();
|
||||
assert_eq!(
|
||||
handshake.header(OPENAI_BETA_HEADER),
|
||||
@@ -2189,6 +2191,7 @@ async fn websocket_harness_with_provider_options(
|
||||
/*enable_request_compression*/ false,
|
||||
runtime_metrics_enabled,
|
||||
/*beta_features_header*/ None,
|
||||
/*item_ids_enabled*/ config.features.enabled(Feature::ItemIds),
|
||||
/*attestation_provider*/ None,
|
||||
);
|
||||
|
||||
|
||||
@@ -649,8 +649,8 @@ async fn generated_image_is_replayed_for_image_capable_models() -> Result<()> {
|
||||
);
|
||||
assert_eq!(
|
||||
image_generation_calls[0]["id"].as_str(),
|
||||
Some("ig_123"),
|
||||
"expected the original image generation call id to be preserved"
|
||||
None,
|
||||
"expected the image generation call id to be omitted"
|
||||
);
|
||||
assert_eq!(
|
||||
image_generation_calls[0]["result"].as_str(),
|
||||
@@ -766,8 +766,8 @@ async fn model_change_from_generated_image_to_text_preserves_prior_generated_ima
|
||||
);
|
||||
assert_eq!(
|
||||
image_generation_calls[0]["id"].as_str(),
|
||||
Some("ig_123"),
|
||||
"second request should preserve the original generated image call id"
|
||||
None,
|
||||
"second request should omit the generated image call id"
|
||||
);
|
||||
assert_eq!(
|
||||
image_generation_calls[0]["result"].as_str(),
|
||||
|
||||
@@ -29,7 +29,9 @@ pub(crate) fn recent_input(items: &[ResponseItem]) -> Option<SearchInput> {
|
||||
fn push_visible_message(messages: &mut Vec<ResponseItem>, item: &ResponseItem) {
|
||||
match item {
|
||||
ResponseItem::Message { role, .. } if role == ASSISTANT_ROLE => {
|
||||
messages.push(item.clone());
|
||||
let mut message = item.clone();
|
||||
message.set_id(/*new_id*/ None);
|
||||
messages.push(message);
|
||||
}
|
||||
ResponseItem::AgentMessage {
|
||||
author,
|
||||
@@ -50,7 +52,7 @@ fn push_visible_message(messages: &mut Vec<ResponseItem>, item: &ResponseItem) {
|
||||
}
|
||||
}
|
||||
ResponseItem::Message {
|
||||
id,
|
||||
id: _,
|
||||
role,
|
||||
content,
|
||||
phase,
|
||||
@@ -65,7 +67,7 @@ fn push_visible_message(messages: &mut Vec<ResponseItem>, item: &ResponseItem) {
|
||||
.collect::<Vec<_>>();
|
||||
if !content.is_empty() {
|
||||
messages.push(ResponseItem::Message {
|
||||
id: id.clone(),
|
||||
id: None,
|
||||
role: role.clone(),
|
||||
content,
|
||||
phase: phase.clone(),
|
||||
@@ -108,11 +110,15 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn keeps_current_user_and_previous_visible_turn() {
|
||||
let mut previous_user = message(USER_ROLE, "previous user");
|
||||
previous_user.set_id(Some("msg_previous_user".to_string()));
|
||||
let mut previous_assistant = message(ASSISTANT_ROLE, "previous assistant");
|
||||
previous_assistant.set_id(Some("msg_previous_assistant".to_string()));
|
||||
let items = vec![
|
||||
message("system", "system"),
|
||||
message(USER_ROLE, "old user"),
|
||||
message(ASSISTANT_ROLE, "old assistant"),
|
||||
message(USER_ROLE, "previous user"),
|
||||
previous_user,
|
||||
ResponseItem::FunctionCall {
|
||||
id: None,
|
||||
name: "tool".to_string(),
|
||||
@@ -121,7 +127,7 @@ mod tests {
|
||||
call_id: "call-1".to_string(),
|
||||
metadata: None,
|
||||
},
|
||||
message(ASSISTANT_ROLE, "previous assistant"),
|
||||
previous_assistant,
|
||||
message("developer", "developer"),
|
||||
message(USER_ROLE, "current user"),
|
||||
message(ASSISTANT_ROLE, "current commentary"),
|
||||
|
||||
@@ -190,6 +190,8 @@ pub enum Feature {
|
||||
ImageGenExt,
|
||||
/// Resize all inline data-URL images before recording them in history.
|
||||
ResizeAllImages,
|
||||
/// Generate Responses API item IDs for client-created history items.
|
||||
ItemIds,
|
||||
/// Allow prompting and installing missing MCP dependencies.
|
||||
SkillMcpDependencyInstall,
|
||||
/// Removed compatibility flag for deleted skill env var dependency prompting.
|
||||
@@ -1134,6 +1136,12 @@ pub const FEATURES: &[FeatureSpec] = &[
|
||||
stage: Stage::UnderDevelopment,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::ItemIds,
|
||||
key: "item_ids",
|
||||
stage: Stage::UnderDevelopment,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::SkillMcpDependencyInstall,
|
||||
key: "skill_mcp_dependency_install",
|
||||
|
||||
@@ -235,6 +235,7 @@ impl MemoryStartupContext {
|
||||
config.features.enabled(Feature::EnableRequestCompression),
|
||||
config.features.enabled(Feature::RuntimeMetrics),
|
||||
/*beta_features_header*/ None,
|
||||
config.features.enabled(Feature::ItemIds),
|
||||
/*attestation_provider*/ None,
|
||||
);
|
||||
|
||||
|
||||
@@ -918,9 +918,8 @@ pub struct ResponseItemMetadata {
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
pub enum ResponseItem {
|
||||
Message {
|
||||
#[serde(default, skip_serializing)]
|
||||
#[ts(skip)]
|
||||
#[schemars(skip)]
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
id: Option<String>,
|
||||
role: String,
|
||||
content: Vec<ContentItem>,
|
||||
@@ -935,9 +934,8 @@ pub enum ResponseItem {
|
||||
metadata: Option<ResponseItemMetadata>,
|
||||
},
|
||||
AgentMessage {
|
||||
#[serde(default, skip_serializing)]
|
||||
#[ts(skip)]
|
||||
#[schemars(skip)]
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
id: Option<String>,
|
||||
author: String,
|
||||
recipient: String,
|
||||
@@ -947,9 +945,8 @@ pub enum ResponseItem {
|
||||
metadata: Option<ResponseItemMetadata>,
|
||||
},
|
||||
Reasoning {
|
||||
#[serde(default, skip_serializing)]
|
||||
#[ts(skip)]
|
||||
#[schemars(skip)]
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
id: Option<String>,
|
||||
summary: Vec<ReasoningItemReasoningSummary>,
|
||||
#[serde(default, skip_serializing_if = "should_serialize_reasoning_content")]
|
||||
@@ -962,9 +959,8 @@ pub enum ResponseItem {
|
||||
},
|
||||
LocalShellCall {
|
||||
/// Legacy id field retained for compatibility with older payloads.
|
||||
#[serde(default, skip_serializing)]
|
||||
#[ts(skip)]
|
||||
#[schemars(skip)]
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
id: Option<String>,
|
||||
/// Set when using the Responses API.
|
||||
call_id: Option<String>,
|
||||
@@ -975,9 +971,8 @@ pub enum ResponseItem {
|
||||
metadata: Option<ResponseItemMetadata>,
|
||||
},
|
||||
FunctionCall {
|
||||
#[serde(default, skip_serializing)]
|
||||
#[ts(skip)]
|
||||
#[schemars(skip)]
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
id: Option<String>,
|
||||
name: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
@@ -993,9 +988,8 @@ pub enum ResponseItem {
|
||||
metadata: Option<ResponseItemMetadata>,
|
||||
},
|
||||
ToolSearchCall {
|
||||
#[serde(default, skip_serializing)]
|
||||
#[ts(skip)]
|
||||
#[schemars(skip)]
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
id: Option<String>,
|
||||
call_id: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
@@ -1014,9 +1008,8 @@ 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)]
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
id: Option<String>,
|
||||
call_id: String,
|
||||
#[ts(as = "FunctionCallOutputBody")]
|
||||
@@ -1027,9 +1020,8 @@ pub enum ResponseItem {
|
||||
metadata: Option<ResponseItemMetadata>,
|
||||
},
|
||||
CustomToolCall {
|
||||
#[serde(default, skip_serializing)]
|
||||
#[ts(skip)]
|
||||
#[schemars(skip)]
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
id: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
@@ -1046,9 +1038,8 @@ 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)]
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
id: Option<String>,
|
||||
call_id: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
@@ -1062,9 +1053,8 @@ pub enum ResponseItem {
|
||||
metadata: Option<ResponseItemMetadata>,
|
||||
},
|
||||
ToolSearchOutput {
|
||||
#[serde(default, skip_serializing)]
|
||||
#[ts(skip)]
|
||||
#[schemars(skip)]
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
id: Option<String>,
|
||||
call_id: Option<String>,
|
||||
status: String,
|
||||
@@ -1084,9 +1074,8 @@ pub enum ResponseItem {
|
||||
// "action": {"type":"search","query":"weather: San Francisco, CA"}
|
||||
// }
|
||||
WebSearchCall {
|
||||
#[serde(default, skip_serializing)]
|
||||
#[ts(skip)]
|
||||
#[schemars(skip)]
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
id: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
@@ -1108,7 +1097,6 @@ pub enum ResponseItem {
|
||||
// "result":"..."
|
||||
// }
|
||||
ImageGenerationCall {
|
||||
/// Existing provider ID retained on serialized history for compatibility.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
id: Option<String>,
|
||||
@@ -1123,28 +1111,24 @@ pub enum ResponseItem {
|
||||
},
|
||||
#[serde(alias = "compaction_summary")]
|
||||
Compaction {
|
||||
#[serde(default, skip_serializing)]
|
||||
#[ts(skip)]
|
||||
#[schemars(skip)]
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
id: Option<String>,
|
||||
encrypted_content: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
metadata: Option<ResponseItemMetadata>,
|
||||
},
|
||||
// Compaction triggers are request controls, and the Responses API does not
|
||||
// accept an `id` field for them.
|
||||
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)]
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
id: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
@@ -1179,14 +1163,13 @@ impl ResponseItem {
|
||||
| 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,
|
||||
Self::CompactionTrigger { .. } | Self::Other => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the Responses API item ID for variants that carry one.
|
||||
pub fn set_id(&mut self, new_id: String) {
|
||||
/// Sets or clears the Responses API item ID for variants that carry one.
|
||||
pub fn set_id(&mut self, new_id: Option<String>) {
|
||||
match self {
|
||||
Self::Message { id, .. }
|
||||
| Self::AgentMessage { id, .. }
|
||||
@@ -1201,9 +1184,8 @@ impl ResponseItem {
|
||||
| Self::Reasoning { id, .. }
|
||||
| Self::ImageGenerationCall { id, .. }
|
||||
| Self::Compaction { id, .. }
|
||||
| Self::CompactionTrigger { id, .. }
|
||||
| Self::ContextCompaction { id, .. } => *id = Some(new_id),
|
||||
Self::Other => {}
|
||||
| Self::ContextCompaction { id, .. } => *id = new_id,
|
||||
Self::CompactionTrigger { .. } | Self::Other => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2150,9 +2132,13 @@ mod tests {
|
||||
let mut item = response_item_with_metadata(/*metadata*/ None);
|
||||
assert_eq!(item.id(), None);
|
||||
|
||||
item.set_id("msg_test".to_string());
|
||||
item.set_id(Some("msg_test".to_string()));
|
||||
|
||||
assert_eq!(item.id(), Some("msg_test"));
|
||||
|
||||
item.set_id(/*new_id*/ None);
|
||||
|
||||
assert_eq!(item.id(), None);
|
||||
}
|
||||
|
||||
fn response_item_with_metadata(metadata: Option<ResponseItemMetadata>) -> ResponseItem {
|
||||
@@ -3022,10 +3008,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn serializes_compaction_trigger_without_payload() -> Result<()> {
|
||||
let item = ResponseItem::CompactionTrigger {
|
||||
id: None,
|
||||
metadata: None,
|
||||
};
|
||||
let item = ResponseItem::CompactionTrigger { metadata: None };
|
||||
|
||||
assert_eq!(
|
||||
serde_json::to_value(item)?,
|
||||
@@ -3038,10 +3021,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn serializes_stamped_compaction_trigger_metadata() -> Result<()> {
|
||||
let mut item = ResponseItem::CompactionTrigger {
|
||||
id: None,
|
||||
metadata: None,
|
||||
};
|
||||
let mut item = ResponseItem::CompactionTrigger { metadata: None };
|
||||
item.stamp_turn_id_if_missing("turn-1");
|
||||
|
||||
assert_eq!(
|
||||
@@ -3062,13 +3042,7 @@ mod tests {
|
||||
|
||||
let item: ResponseItem = serde_json::from_str(json)?;
|
||||
|
||||
assert_eq!(
|
||||
item,
|
||||
ResponseItem::CompactionTrigger {
|
||||
id: None,
|
||||
metadata: None,
|
||||
}
|
||||
);
|
||||
assert_eq!(item, ResponseItem::CompactionTrigger { metadata: None });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -3109,7 +3083,6 @@ mod tests {
|
||||
queries: Some(vec!["weather seattle".into(), "seattle weather now".into()]),
|
||||
}),
|
||||
Some("completed".into()),
|
||||
true,
|
||||
),
|
||||
(
|
||||
r#"{
|
||||
@@ -3125,7 +3098,6 @@ mod tests {
|
||||
url: Some("https://example.com".into()),
|
||||
}),
|
||||
Some("open".into()),
|
||||
true,
|
||||
),
|
||||
(
|
||||
r#"{
|
||||
@@ -3143,7 +3115,6 @@ mod tests {
|
||||
pattern: Some("installation".into()),
|
||||
}),
|
||||
Some("in_progress".into()),
|
||||
true,
|
||||
),
|
||||
(
|
||||
r#"{
|
||||
@@ -3154,12 +3125,10 @@ mod tests {
|
||||
Some("ws_partial".into()),
|
||||
None,
|
||||
Some("in_progress".into()),
|
||||
false,
|
||||
),
|
||||
];
|
||||
|
||||
for (json_literal, expected_id, expected_action, expected_status, expect_roundtrip) in cases
|
||||
{
|
||||
for (json_literal, expected_id, expected_action, expected_status) in cases {
|
||||
let parsed: ResponseItem = serde_json::from_str(json_literal)?;
|
||||
let expected = ResponseItem::WebSearchCall {
|
||||
id: expected_id.clone(),
|
||||
@@ -3170,10 +3139,7 @@ mod tests {
|
||||
assert_eq!(parsed, expected);
|
||||
|
||||
let serialized = serde_json::to_value(&parsed)?;
|
||||
let mut expected_serialized: serde_json::Value = serde_json::from_str(json_literal)?;
|
||||
if !expect_roundtrip && let Some(obj) = expected_serialized.as_object_mut() {
|
||||
obj.remove("id");
|
||||
}
|
||||
let expected_serialized: serde_json::Value = serde_json::from_str(json_literal)?;
|
||||
assert_eq!(serialized, expected_serialized);
|
||||
}
|
||||
|
||||
|
||||
@@ -515,6 +515,7 @@ mod tests {
|
||||
traced,
|
||||
json!({
|
||||
"type": "reasoning",
|
||||
"id": "rs-1",
|
||||
"summary": [{"type": "summary_text", "text": "summary"}],
|
||||
"content": [{"type": "text", "text": "raw reasoning"}],
|
||||
"encrypted_content": "encoded",
|
||||
|
||||
Reference in New Issue
Block a user