use stable item IDs for Responses API calls

This commit is contained in:
Owen Lin
2026-06-08 15:21:01 -07:00
parent 381f0de531
commit 7b2fb1a6f4
55 changed files with 1415 additions and 409 deletions
+90 -11
View File
@@ -20,6 +20,7 @@ use codex_client::Response;
use codex_client::StreamResponse;
use codex_client::TransportError;
use codex_protocol::models::ContentItem;
use codex_protocol::models::FunctionCallOutputPayload;
use codex_protocol::models::ResponseItem;
use codex_protocol::protocol::SessionSource;
use codex_protocol::protocol::SubAgentSource;
@@ -419,12 +420,19 @@ async fn azure_default_store_attaches_ids_and_headers() -> Result<()> {
let request = ResponsesApiRequest {
model: "gpt-test".into(),
instructions: "Say hi".into(),
input: vec![ResponseItem::Message {
id: Some("msg_1".into()),
role: "user".into(),
content: vec![ContentItem::InputText { text: "hi".into() }],
phase: None,
}],
input: vec![
ResponseItem::Message {
id: Some("msg_1".into()),
role: "user".into(),
content: vec![ContentItem::InputText { text: "hi".into() }],
phase: None,
},
ResponseItem::FunctionCallOutput {
id: Some("fco_1".into()),
call_id: "call_1".into(),
output: FunctionCallOutputPayload::from_text("ok".into()),
},
],
tools: Vec::new(),
tool_choice: "auto".into(),
parallel_tool_calls: false,
@@ -450,6 +458,7 @@ async fn azure_default_store_attaches_ids_and_headers() -> Result<()> {
extra_headers,
compression: Compression::None,
turn_state: None,
include_item_ids_for_stateless_mode: false,
},
)
.await?;
@@ -485,15 +494,85 @@ async fn azure_default_store_attaches_ids_and_headers() -> Result<()> {
Some("present")
);
let input_id = req
let input = req
.body
.as_ref()
.and_then(RequestBody::json)
.and_then(|body| body.get("input"))
.and_then(|input| input.get(0))
.and_then(|item| item.get("id"))
.and_then(|id| id.as_str());
assert_eq!(input_id, Some("msg_1"));
.expect("input array");
assert_eq!(
input
.get(0)
.and_then(|item| item.get("id"))
.and_then(|id| id.as_str()),
Some("msg_1")
);
assert_eq!(
input
.get(1)
.and_then(|item| item.get("id"))
.and_then(|id| id.as_str()),
None
);
Ok(())
}
#[tokio::test]
async fn response_item_ids_require_explicit_request_option() -> Result<()> {
let state = RecordingState::default();
let transport = RecordingTransport::new(state.clone());
let client = ResponsesClient::new(transport, provider("openai"), Arc::new(NoAuth));
let request = ResponsesApiRequest {
model: "gpt-test".into(),
instructions: "Say hi".into(),
input: vec![ResponseItem::Message {
id: Some("msg_1".into()),
role: "user".into(),
content: vec![ContentItem::InputText { text: "hi".into() }],
phase: None,
}],
tools: Vec::new(),
tool_choice: "auto".into(),
parallel_tool_calls: false,
reasoning: None,
store: false,
stream: true,
include: Vec::new(),
service_tier: None,
prompt_cache_key: None,
text: None,
client_metadata: None,
};
let _stream = client
.stream_request(request.clone(), ResponsesOptions::default())
.await?;
let _stream = client
.stream_request(
request,
ResponsesOptions {
include_item_ids_for_stateless_mode: true,
..Default::default()
},
)
.await?;
let requests = state.take_stream_requests();
assert_eq!(requests.len(), 2);
let input_id = |request: &Request| {
request
.body
.as_ref()
.and_then(RequestBody::json)
.and_then(|body| body.get("input"))
.and_then(|input| input.get(0))
.and_then(|item| item.get("id"))
.and_then(serde_json::Value::as_str)
.map(str::to_string)
};
assert_eq!(input_id(&requests[0]), None);
assert_eq!(input_id(&requests[1]), Some("msg_1".to_string()));
Ok(())
}