1use std::collections::BTreeMap;
4#[cfg(feature = "structured-output")]
5use std::marker::PhantomData;
6use std::time::Duration;
7
8use http::Method;
9#[cfg(feature = "structured-output")]
10use schemars::JsonSchema;
11use serde::{Deserialize, Serialize};
12use serde_json::Value;
13use tokio_util::sync::CancellationToken;
14
15use crate::Client;
16use crate::config::RequestOptions;
17use crate::error::{Error, Result};
18use crate::generated::endpoints;
19#[cfg(feature = "structured-output")]
20use crate::helpers::{ParsedResponse, parse_json_payload};
21use crate::json_payload::JsonPayload;
22use crate::response_meta::ApiResponse;
23use crate::stream::{ResponseEventStream, ResponseStream};
24use crate::transport::{RequestSpec, merge_json_body};
25#[cfg(feature = "realtime")]
26use crate::websocket::RealtimeSocket;
27#[cfg(feature = "responses-ws")]
28use crate::websocket::ResponsesSocket;
29
30use super::{
31 ChatToolDefinition, ConversationItem, DeleteResponse, InputTokenCount, JsonRequestBuilder,
32 ListRequestBuilder, McpToolDefinition, NoContentRequestBuilder, RealtimeCallsResource,
33 RealtimeClientSecretsResource, RealtimeResource, RealtimeSessionPayload, Response,
34 ResponseCreateParams, ResponseInputItemPayload, ResponseInputItemsResource,
35 ResponseInputPayload, ResponseInputTokensResource, ResponsesResource, encode_path_segment,
36 value_from,
37};
38
39#[derive(Debug, Clone, Serialize, Deserialize, Default)]
41pub struct RealtimeSessionClientSecret {
42 pub expires_at: u64,
44 #[serde(default)]
46 pub value: String,
47 #[serde(flatten)]
49 pub extra: BTreeMap<String, Value>,
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize, Default)]
54pub struct RealtimeClientSecretCreateResponse {
55 pub value: Option<String>,
57 pub expires_at: Option<u64>,
59 pub client_secret: Option<RealtimeSessionClientSecret>,
61 pub secret: Option<String>,
63 #[serde(rename = "type")]
65 pub session_type: Option<String>,
66 pub session: Option<RealtimeSessionPayload>,
68 #[serde(flatten)]
70 pub extra: BTreeMap<String, Value>,
71}
72
73impl RealtimeClientSecretCreateResponse {
74 pub fn secret_value(&self) -> Option<&str> {
76 self.client_secret
77 .as_ref()
78 .map(|secret| secret.value.as_str())
79 .or(self.value.as_deref())
80 .or(self.secret.as_deref())
81 }
82}
83
84impl ResponsesResource {
85 pub fn create(&self) -> ResponseCreateRequestBuilder {
87 ResponseCreateRequestBuilder::new(self.client.clone())
88 }
89
90 #[cfg(feature = "structured-output")]
92 #[cfg_attr(docsrs, doc(cfg(feature = "structured-output")))]
93 pub fn parse<T>(&self) -> ResponseParseRequestBuilder<T> {
94 ResponseParseRequestBuilder::new(self.client.clone())
95 }
96
97 pub fn stream(&self) -> ResponseStreamRequestBuilder {
99 ResponseStreamRequestBuilder::new(self.client.clone())
100 }
101
102 pub fn stream_response(&self, response_id: impl Into<String>) -> ResponseStreamRequestBuilder {
104 ResponseStreamRequestBuilder::new(self.client.clone()).response_id(response_id)
105 }
106
107 #[cfg(feature = "responses-ws")]
109 #[cfg_attr(docsrs, doc(cfg(feature = "responses-ws")))]
110 pub fn ws(&self) -> ResponsesSocketRequestBuilder {
111 ResponsesSocketRequestBuilder::new(self.client.clone())
112 }
113
114 pub fn retrieve(&self, response_id: impl Into<String>) -> JsonRequestBuilder<Response> {
116 JsonRequestBuilder::new(
117 self.client.clone(),
118 "responses.retrieve",
119 Method::GET,
120 format!("/responses/{}", encode_path_segment(response_id.into())),
121 )
122 }
123
124 pub fn delete(&self, response_id: impl Into<String>) -> JsonRequestBuilder<DeleteResponse> {
126 JsonRequestBuilder::new(
127 self.client.clone(),
128 "responses.delete",
129 Method::DELETE,
130 format!("/responses/{}", encode_path_segment(response_id.into())),
131 )
132 }
133
134 pub fn cancel(&self, response_id: impl Into<String>) -> JsonRequestBuilder<Response> {
136 JsonRequestBuilder::new(
137 self.client.clone(),
138 "responses.cancel",
139 Method::POST,
140 format!(
141 "/responses/{}/cancel",
142 encode_path_segment(response_id.into())
143 ),
144 )
145 }
146
147 pub fn compact(&self) -> JsonRequestBuilder<Response> {
149 JsonRequestBuilder::new(
150 self.client.clone(),
151 "responses.compact",
152 Method::POST,
153 "/responses/compact",
154 )
155 }
156
157 pub fn input_items(&self) -> ResponseInputItemsResource {
159 ResponseInputItemsResource::new(self.client.clone())
160 }
161
162 pub fn input_tokens(&self) -> ResponseInputTokensResource {
164 ResponseInputTokensResource::new(self.client.clone())
165 }
166}
167
168impl ResponseInputItemsResource {
169 pub fn list(&self, response_id: impl Into<String>) -> ListRequestBuilder<ConversationItem> {
171 let endpoint = endpoints::responses::RESPONSES_INPUT_ITEMS_LIST;
172 ListRequestBuilder::new(
173 self.client.clone(),
174 endpoint.id,
175 endpoint.render(&[("response_id", &encode_path_segment(response_id.into()))]),
176 )
177 }
178}
179
180impl ResponseInputTokensResource {
181 pub fn count(&self) -> JsonRequestBuilder<InputTokenCount> {
183 let endpoint = endpoints::responses::RESPONSES_INPUT_TOKENS_COUNT;
184 JsonRequestBuilder::new(
185 self.client.clone(),
186 endpoint.id,
187 Method::POST,
188 endpoint.template,
189 )
190 }
191}
192
193impl RealtimeResource {
194 #[cfg(feature = "realtime")]
196 #[cfg_attr(docsrs, doc(cfg(feature = "realtime")))]
197 pub fn ws(&self) -> RealtimeSocketRequestBuilder {
198 RealtimeSocketRequestBuilder::new(self.client.clone())
199 }
200
201 pub fn client_secrets(&self) -> RealtimeClientSecretsResource {
203 RealtimeClientSecretsResource::new(self.client.clone())
204 }
205
206 pub fn calls(&self) -> RealtimeCallsResource {
208 RealtimeCallsResource::new(self.client.clone())
209 }
210}
211
212impl RealtimeClientSecretsResource {
213 pub fn create(&self) -> JsonRequestBuilder<RealtimeClientSecretCreateResponse> {
215 let endpoint = endpoints::responses::REALTIME_CLIENT_SECRETS_CREATE;
216 JsonRequestBuilder::new(
217 self.client.clone(),
218 endpoint.id,
219 Method::POST,
220 endpoint.template,
221 )
222 }
223}
224
225impl RealtimeCallsResource {
226 pub fn accept(&self, call_id: impl Into<String>) -> NoContentRequestBuilder {
228 realtime_call_action(
229 self.client.clone(),
230 endpoints::responses::REALTIME_CALLS_ACCEPT,
231 call_id,
232 )
233 }
234
235 pub fn hangup(&self, call_id: impl Into<String>) -> NoContentRequestBuilder {
237 realtime_call_action(
238 self.client.clone(),
239 endpoints::responses::REALTIME_CALLS_HANGUP,
240 call_id,
241 )
242 }
243
244 pub fn refer(&self, call_id: impl Into<String>) -> NoContentRequestBuilder {
246 realtime_call_action(
247 self.client.clone(),
248 endpoints::responses::REALTIME_CALLS_REFER,
249 call_id,
250 )
251 }
252
253 pub fn reject(&self, call_id: impl Into<String>) -> NoContentRequestBuilder {
255 realtime_call_action(
256 self.client.clone(),
257 endpoints::responses::REALTIME_CALLS_REJECT,
258 call_id,
259 )
260 }
261}
262
263fn realtime_call_action(
264 client: Client,
265 endpoint: endpoints::PathTemplateEndpoint,
266 call_id: impl Into<String>,
267) -> NoContentRequestBuilder {
268 NoContentRequestBuilder::new(
269 client,
270 endpoint.id,
271 Method::POST,
272 endpoint.render(&[("call_id", &encode_path_segment(call_id.into()))]),
273 )
274 .extra_header("accept", "*/*")
275}
276
277#[derive(Debug, Clone, Default)]
279pub struct ResponseCreateRequestBuilder {
280 client: Option<Client>,
281 pub(crate) params: ResponseCreateParams,
282 options: RequestOptions,
283 extra_body: BTreeMap<String, Value>,
284 provider_options: BTreeMap<String, Value>,
285 mcp_tools: Vec<McpToolDefinition>,
286}
287
288#[derive(Debug, Clone)]
290pub struct ResponseStreamRequestBuilder {
291 inner: ResponseCreateRequestBuilder,
292 response_id: Option<String>,
293 starting_after: Option<u64>,
294}
295
296#[cfg(feature = "realtime")]
298#[cfg_attr(docsrs, doc(cfg(feature = "realtime")))]
299#[derive(Debug, Clone)]
300pub struct RealtimeSocketRequestBuilder {
301 client: Client,
302 model: Option<String>,
303 options: RequestOptions,
304}
305
306#[cfg(feature = "responses-ws")]
308#[cfg_attr(docsrs, doc(cfg(feature = "responses-ws")))]
309#[derive(Debug, Clone)]
310pub struct ResponsesSocketRequestBuilder {
311 client: Client,
312 options: RequestOptions,
313}
314
315impl ResponseStreamRequestBuilder {
316 pub(crate) fn new(client: Client) -> Self {
317 Self {
318 inner: ResponseCreateRequestBuilder::new(client),
319 response_id: None,
320 starting_after: None,
321 }
322 }
323
324 pub fn model(mut self, model: impl Into<String>) -> Self {
326 self.inner = self.inner.model(model);
327 self
328 }
329
330 pub fn input_text(mut self, input: impl Into<String>) -> Self {
332 self.inner = self.inner.input_text(input);
333 self
334 }
335
336 pub fn input_items(mut self, items: Vec<ResponseInputItemPayload>) -> Self {
338 self.inner = self.inner.input_items(items);
339 self
340 }
341
342 pub fn input(mut self, input: impl Into<ResponseInputPayload>) -> Self {
344 self.inner = self.inner.input(input);
345 self
346 }
347
348 pub fn temperature(mut self, temperature: f32) -> Self {
350 self.inner = self.inner.temperature(temperature);
351 self
352 }
353
354 pub fn tool(mut self, tool: ChatToolDefinition) -> Self {
356 self.inner = self.inner.tool(tool);
357 self
358 }
359
360 pub fn mcp_tool(mut self, tool: McpToolDefinition) -> Self {
362 self.inner = self.inner.mcp_tool(tool);
363 self
364 }
365
366 pub fn extra_body(mut self, key: impl Into<String>, value: impl Into<JsonPayload>) -> Self {
368 self.inner = self.inner.extra_body(key, value);
369 self
370 }
371
372 pub fn provider_option(
374 mut self,
375 key: impl Into<String>,
376 value: impl Into<JsonPayload>,
377 ) -> Self {
378 self.inner = self.inner.provider_option(key, value);
379 self
380 }
381
382 pub fn extra_header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
384 self.inner.options.insert_header(key, value);
385 self
386 }
387
388 pub fn extra_query(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
390 self.inner.options.insert_query(key, value);
391 self
392 }
393
394 pub fn timeout(mut self, timeout: Duration) -> Self {
396 self.inner.options.timeout = Some(timeout);
397 self
398 }
399
400 pub fn max_retries(mut self, max_retries: u32) -> Self {
402 self.inner.options.max_retries = Some(max_retries);
403 self
404 }
405
406 pub fn cancellation_token(mut self, token: CancellationToken) -> Self {
408 self.inner.options.cancellation_token = Some(token);
409 self
410 }
411
412 pub fn response_id(mut self, response_id: impl Into<String>) -> Self {
414 self.response_id = Some(response_id.into());
415 self
416 }
417
418 pub fn starting_after(mut self, sequence_number: u64) -> Self {
420 self.starting_after = Some(sequence_number);
421 self
422 }
423
424 pub async fn send(mut self) -> Result<ResponseStream> {
430 let (client, spec) = if let Some(response_id) = self.response_id.take() {
431 if self.inner.params.model.is_some()
432 || self.inner.params.input.is_some()
433 || self.inner.params.temperature.is_some()
434 || !self.inner.params.tools.is_empty()
435 || !self.inner.mcp_tools.is_empty()
436 || !self.inner.extra_body.is_empty()
437 || !self.inner.provider_options.is_empty()
438 {
439 return Err(Error::InvalidConfig(
440 "按 response_id 继续流时,不应再设置创建期参数或请求体扩展字段".into(),
441 ));
442 }
443
444 let client = self
445 .inner
446 .client
447 .take()
448 .ok_or_else(|| Error::InvalidConfig("Responses 构建器缺少客户端".into()))?;
449 let mut spec = RequestSpec::new(
450 "responses.stream.retrieve",
451 Method::GET,
452 format!("/responses/{}", encode_path_segment(response_id)),
453 );
454 spec.options = self.inner.options;
455 spec.options.insert_query("stream", "true");
456 if let Some(sequence_number) = self.starting_after {
457 spec.options
458 .insert_query("starting_after", sequence_number.to_string());
459 }
460 (client, spec)
461 } else {
462 if self.starting_after.is_some() {
463 return Err(Error::InvalidConfig(
464 "`starting_after` 只能与 `response_id` 一起使用".into(),
465 ));
466 }
467 self.inner.build_spec(true)?
468 };
469 Ok(ResponseStream::new(client.execute_sse(spec).await?))
470 }
471
472 pub async fn send_events(self) -> Result<ResponseEventStream> {
478 Ok(self.send().await?.events())
479 }
480}
481
482#[cfg(feature = "realtime")]
483impl RealtimeSocketRequestBuilder {
484 pub(crate) fn new(client: Client) -> Self {
485 Self {
486 client,
487 model: None,
488 options: RequestOptions::default(),
489 }
490 }
491
492 pub fn model(mut self, model: impl Into<String>) -> Self {
494 self.model = Some(model.into());
495 self
496 }
497
498 pub fn extra_header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
500 self.options.insert_header(key, value);
501 self
502 }
503
504 pub fn extra_query(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
506 self.options.insert_query(key, value);
507 self
508 }
509
510 pub async fn connect(self) -> Result<RealtimeSocket> {
516 RealtimeSocket::connect(&self.client, self.model, self.options).await
517 }
518}
519
520#[cfg(feature = "responses-ws")]
521impl ResponsesSocketRequestBuilder {
522 pub(crate) fn new(client: Client) -> Self {
523 Self {
524 client,
525 options: RequestOptions::default(),
526 }
527 }
528
529 pub fn extra_header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
531 self.options.insert_header(key, value);
532 self
533 }
534
535 pub fn extra_query(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
537 self.options.insert_query(key, value);
538 self
539 }
540
541 pub async fn connect(self) -> Result<ResponsesSocket> {
547 ResponsesSocket::connect(&self.client, self.options).await
548 }
549}
550
551impl ResponseCreateRequestBuilder {
552 pub(crate) fn new(client: Client) -> Self {
553 Self {
554 client: Some(client),
555 ..Self::default()
556 }
557 }
558
559 pub fn model(mut self, model: impl Into<String>) -> Self {
561 self.params.model = Some(model.into());
562 self
563 }
564
565 pub fn input_text(mut self, input: impl Into<String>) -> Self {
567 self.params.input = Some(ResponseInputPayload::from(input.into()));
568 self
569 }
570
571 pub fn input_items(mut self, items: Vec<ResponseInputItemPayload>) -> Self {
573 self.params.input = Some(ResponseInputPayload::from(items));
574 self
575 }
576
577 pub fn input(mut self, input: impl Into<ResponseInputPayload>) -> Self {
579 self.params.input = Some(input.into());
580 self
581 }
582
583 pub fn temperature(mut self, temperature: f32) -> Self {
585 self.params.temperature = Some(temperature);
586 self
587 }
588
589 pub fn tool(mut self, tool: ChatToolDefinition) -> Self {
591 self.params.tools.push(tool);
592 self
593 }
594
595 pub fn mcp_tool(mut self, tool: McpToolDefinition) -> Self {
597 self.mcp_tools.push(tool);
598 self
599 }
600
601 pub fn extra_body(mut self, key: impl Into<String>, value: impl Into<JsonPayload>) -> Self {
603 self.extra_body.insert(key.into(), value.into().into_raw());
604 self
605 }
606
607 pub fn provider_option(
609 mut self,
610 key: impl Into<String>,
611 value: impl Into<JsonPayload>,
612 ) -> Self {
613 self.provider_options
614 .insert(key.into(), value.into().into_raw());
615 self
616 }
617
618 pub fn extra_header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
620 self.options.insert_header(key, value);
621 self
622 }
623
624 pub fn extra_query(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
626 self.options.insert_query(key, value);
627 self
628 }
629
630 pub fn timeout(mut self, timeout: Duration) -> Self {
632 self.options.timeout = Some(timeout);
633 self
634 }
635
636 pub fn max_retries(mut self, max_retries: u32) -> Self {
638 self.options.max_retries = Some(max_retries);
639 self
640 }
641
642 pub fn cancellation_token(mut self, token: CancellationToken) -> Self {
644 self.options.cancellation_token = Some(token);
645 self
646 }
647
648 pub(crate) fn build_spec(mut self, stream: bool) -> Result<(Client, RequestSpec)> {
649 let client = self
650 .client
651 .take()
652 .ok_or_else(|| Error::InvalidConfig("Responses 构建器缺少客户端".into()))?;
653 if self.params.model.as_deref().unwrap_or_default().is_empty() {
654 return Err(Error::MissingRequiredField { field: "model" });
655 }
656 if self.params.input.is_none() {
657 return Err(Error::MissingRequiredField { field: "input" });
658 }
659
660 self.params.stream = Some(stream);
661 let provider_key = client.provider().kind().as_key();
662 let mut body = merge_json_body(
663 Some(value_from(&self.params)?),
664 &self.extra_body,
665 provider_key,
666 &self.provider_options,
667 );
668 if (!self.params.tools.is_empty() || !self.mcp_tools.is_empty())
669 && let Some(object) = body.as_object_mut()
670 {
671 let mut tools = Vec::with_capacity(self.params.tools.len() + self.mcp_tools.len());
672 tools.extend(
673 self.params
674 .tools
675 .iter()
676 .map(ChatToolDefinition::as_response_tool_value),
677 );
678 for tool in &self.mcp_tools {
679 tools.push(value_from(tool)?);
680 }
681 object.insert("tools".into(), Value::Array(tools));
682 }
683 let mut spec = RequestSpec::new(
684 if stream {
685 "responses.stream"
686 } else {
687 "responses.create"
688 },
689 Method::POST,
690 "/responses",
691 );
692 spec.body = Some(body);
693 spec.options = self.options;
694 Ok((client, spec))
695 }
696
697 pub async fn send(self) -> Result<Response> {
703 Ok(self.send_with_meta().await?.data)
704 }
705
706 pub async fn send_with_meta(self) -> Result<ApiResponse<Response>> {
712 let (client, spec) = self.build_spec(false)?;
713 client.execute_json(spec).await
714 }
715}
716
717#[cfg(feature = "structured-output")]
719#[derive(Debug, Clone)]
720pub struct ResponseParseRequestBuilder<T> {
721 inner: ResponseCreateRequestBuilder,
722 _marker: PhantomData<T>,
723}
724
725#[cfg(feature = "structured-output")]
726impl<T> ResponseParseRequestBuilder<T> {
727 pub(crate) fn new(client: Client) -> Self {
728 Self {
729 inner: ResponseCreateRequestBuilder::new(client),
730 _marker: PhantomData,
731 }
732 }
733
734 pub fn model(mut self, model: impl Into<String>) -> Self {
736 self.inner = self.inner.model(model);
737 self
738 }
739
740 pub fn input_text(mut self, input: impl Into<String>) -> Self {
742 self.inner = self.inner.input_text(input);
743 self
744 }
745
746 pub fn input_items(mut self, items: Vec<ResponseInputItemPayload>) -> Self {
748 self.inner = self.inner.input_items(items);
749 self
750 }
751}
752
753#[cfg(feature = "structured-output")]
754impl<T> ResponseParseRequestBuilder<T>
755where
756 T: JsonSchema + serde::de::DeserializeOwned,
757{
758 pub async fn send(self) -> Result<ParsedResponse<T>> {
764 let response = self.inner.send().await?;
765 let output_text = response
766 .output_text()
767 .ok_or_else(|| Error::InvalidConfig("Responses 返回中缺少可解析文本".into()))?;
768 let parsed = parse_json_payload(&output_text)?;
769 Ok(ParsedResponse { response, parsed })
770 }
771}