Cover ChatGPT Responses tool call IDs

This commit is contained in:
2026-08-06 23:26:25 -05:00
parent af520193a9
commit 17898e7d9b
3 changed files with 90 additions and 0 deletions
+70
View File
@@ -169,8 +169,18 @@ impl AgentRuntime for ChatGPTSubscriptionRuntime {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use rig_core::client::CompletionClient;
use rig_core::completion::{AssistantContent, CompletionModel, Message};
use rig_core::message::{ToolResultContent, UserContent};
use rig_core::providers::chatgpt::ChatGPTAuth;
use rig_core::test_utils::RecordingHttpClient;
use super::*; use super::*;
const COMPLETED_RESPONSE: &str = r#"data: {"type":"response.output_text.delta","delta":"ok"}
data: {"type":"response.completed","response":{"id":"resp_test","object":"response","created_at":1,"status":"completed","error":null,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"model":"gpt-5.3-codex","usage":{"input_tokens":1,"input_tokens_details":{"cached_tokens":0},"output_tokens":1,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":2},"output":[{"type":"message","id":"msg_test","status":"completed","role":"assistant","content":[{"type":"output_text","annotations":[],"text":"ok"}]}],"tools":[]}}
data: [DONE]"#;
#[test] #[test]
fn runtime_descriptor_identifies_chatgpt_subscription() { fn runtime_descriptor_identifies_chatgpt_subscription() {
let runtime = ChatGPTSubscriptionRuntime::new(ChatGPTSubscriptionRuntimeConfig { let runtime = ChatGPTSubscriptionRuntime::new(ChatGPTSubscriptionRuntimeConfig {
@@ -216,4 +226,64 @@ mod tests {
})) }))
); );
} }
#[tokio::test]
async fn chatgpt_follow_up_request_preserves_responses_call_ids() {
let http_client = RecordingHttpClient::new(COMPLETED_RESPONSE);
let client = chatgpt::Client::builder()
.api_key(ChatGPTAuth::AccessToken {
access_token: "test-token".to_string(),
account_id: None,
})
.http_client(http_client.clone())
.build()
.expect("client should build");
let model = client.completion_model("gpt-5.3-codex");
let assistant_tool_call = AssistantContent::tool_call_with_call_id(
"fc_native_1",
"call_native_1".to_string(),
"read_files",
serde_json::json!({"files": ["Cargo.toml"]}),
);
let tool_result = UserContent::tool_result_with_call_id(
"fc_native_1",
"call_native_1".to_string(),
rig_core::OneOrMany::one(ToolResultContent::text("contents")),
);
let chat_history = rig_core::OneOrMany::many(vec![
Message::Assistant {
id: None,
content: rig_core::OneOrMany::one(assistant_tool_call),
},
Message::User {
content: rig_core::OneOrMany::one(tool_result),
},
Message::user("Continue."),
])
.expect("history should contain messages");
model
.completion(rig_core::completion::CompletionRequest {
model: Some("gpt-5.3-codex".to_string()),
preamble: None,
chat_history,
documents: Vec::new(),
tools: Vec::new(),
temperature: None,
max_tokens: None,
tool_choice: None,
additional_params: None,
output_schema: None,
record_telemetry_content: false,
})
.await
.expect("request should reach the mocked provider");
let requests = http_client.requests();
assert_eq!(requests.len(), 1);
let body: serde_json::Value = serde_json::from_slice(&requests[0].body).unwrap();
let input = body["input"].as_array().expect("input should be an array");
assert_eq!(input[0]["call_id"], "call_native_1");
assert_eq!(input[1]["call_id"], "call_native_1");
}
} }
+16
View File
@@ -310,4 +310,20 @@ mod tests {
assert_eq!(call.id, "call_123"); assert_eq!(call.id, "call_123");
assert_eq!(call.name, "read_files"); assert_eq!(call.name, "read_files");
} }
#[test]
fn domain_tool_call_falls_back_to_wire_id_without_responses_call_id() {
let tool_call = rig_core::message::ToolCall::new(
"fc_item_123".to_string(),
rig_core::message::ToolFunction {
name: "read_files".to_string(),
arguments: serde_json::json!({"files": ["Cargo.toml"]}),
},
);
let call = domain_tool_call(tool_call);
assert_eq!(call.id, "fc_item_123");
assert_eq!(call.name, "read_files");
}
} }
+4
View File
@@ -432,6 +432,10 @@ assigned to the phase that owns the affected flow before the related work is con
monitor. monitor.
- [x] Monitor teardown now clears orphaned in-memory state when completion metadata is missing. - [x] Monitor teardown now clears orphaned in-memory state when completion metadata is missing.
- [x] Refresh requests ignore completed or no-longer-long-running blocks. - [x] Refresh requests ignore completed or no-longer-long-running blocks.
- [ ] ChatGPT subscription follow-up: a reported OAuth-backed tool turn failed because the
Responses request lacked `call_id`. Rig's stream fallback and the serialized assistant/tool
follow-up are now covered by hermetic tests; complete a fresh authenticated end-to-end check and
investigate any remaining loss in the app-owned history handoff.
- [x] Open-source project presentation: structure the About page around Galaxys local-first - [x] Open-source project presentation: structure the About page around Galaxys local-first
identity, audit the repositorys license and third-party notices, and make the root metadata, identity, audit the repositorys license and third-party notices, and make the root metadata,
contribution guidance, and license files agree on an explicit license split (the repository contribution guidance, and license files agree on an explicit license split (the repository