chat_responses/api/
completion.rs1use crate::api::types::error::handle_responses_error;
2use crate::api::types::request::{ResponsesRequest, ResponsesRequestConfig};
3use crate::api::types::response::ResponsesApiResponse;
4use crate::client::ResponsesClient;
5use chat_core::error::{ChatError, ChatFailure};
6use chat_core::traits::CompletionProvider;
7use chat_core::transport::Transport;
8use chat_core::types::messages::Messages;
9use chat_core::types::options::ChatOptions;
10use chat_core::types::provider_meta::ProviderMeta;
11use chat_core::types::response::ChatResponse;
12use chat_core::types::tools::ToolDeclarations;
13
14#[async_trait::async_trait]
15impl<T: Transport> CompletionProvider for ResponsesClient<T> {
16 async fn complete(
17 &mut self,
18 messages: &mut Messages,
19 tool_declarations: Option<&dyn ToolDeclarations>,
20 options: Option<&ChatOptions>,
21 structured_output: Option<&schemars::Schema>,
22 ) -> Result<ChatResponse, ChatFailure> {
23 let previous_response_id = if self.use_previous_response_id {
24 self.last_response_id.clone()
25 } else {
26 None
27 };
28
29 let request_body = ResponsesRequest::from_core(ResponsesRequestConfig {
30 model_name: &self.model_name,
31 messages,
32 tool_declarations,
33 extra_tool_declarations: &self.extra_tool_declarations,
34 reasoning_effort: self.reasoning_effort.clone(),
35 options,
36 output_shape: structured_output,
37 previous_response_id,
38 store: self.store,
39 })
40 .map_err(ChatFailure::from_err)?;
41
42 let body = serde_json::to_vec(&request_body)
43 .map_err(|e| ChatFailure::from_err(ChatError::InvalidResponse(e.to_string())))?;
44
45 let req = chat_core::transport::Request {
46 scheme: self.scheme.clone(),
47 host: self.host.clone(),
48 path: format!("{}/responses", self.base_path),
49 headers: vec![
50 ("Authorization".into(), format!("Bearer {}", self.api_key)),
51 ("Content-Type".into(), "application/json".into()),
52 ],
53 body,
54 };
55
56 let res = self
57 .transport
58 .send(req)
59 .await
60 .map_err(ChatFailure::from_err)?;
61
62 let res = handle_responses_error(res)?;
63
64 let oai_data: ResponsesApiResponse = serde_json::from_slice(&res.body)
65 .map_err(|e| ChatFailure::from_err(ChatError::InvalidResponse(e.to_string())))?;
66
67 let (response, response_id) = oai_data
68 .into_core_chat_response()
69 .map_err(ChatFailure::from_err)?;
70
71 if self.use_previous_response_id
72 && let Some(id) = response_id
73 {
74 self.last_response_id = Some(id);
75 }
76
77 Ok(response)
78 }
79
80 fn metadata(&self) -> Option<&ProviderMeta> {
81 Some(&self.meta)
82 }
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88
89 #[test]
90 fn test_from_core_without_previous_response_id() {
91 let messages = chat_core::types::messages::from_user(vec!["hello"]);
92 let req = ResponsesRequest::from_core(ResponsesRequestConfig {
93 model_name: "gpt-4o",
94 messages: &messages,
95 tool_declarations: None,
96 extra_tool_declarations: &[],
97 reasoning_effort: None,
98 options: None,
99 output_shape: None,
100 previous_response_id: None,
101 store: None,
102 })
103 .unwrap();
104
105 let json = serde_json::to_value(&req).unwrap();
106 assert!(json.get("previous_response_id").is_none());
107 }
108
109 #[test]
110 fn test_from_core_with_previous_response_id() {
111 let messages = chat_core::types::messages::from_user(vec!["hello"]);
112 let req = ResponsesRequest::from_core(ResponsesRequestConfig {
113 model_name: "gpt-4o",
114 messages: &messages,
115 tool_declarations: None,
116 extra_tool_declarations: &[],
117 reasoning_effort: None,
118 options: None,
119 output_shape: None,
120 previous_response_id: Some("resp_abc".to_string()),
121 store: None,
122 })
123 .unwrap();
124
125 let json = serde_json::to_value(&req).unwrap();
126 assert_eq!(json["previous_response_id"], "resp_abc");
127 }
128}