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, 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}
286
287#[derive(Debug, Clone)]
289pub struct ResponseStreamRequestBuilder {
290 inner: ResponseCreateRequestBuilder,
291 response_id: Option<String>,
292 starting_after: Option<u64>,
293}
294
295#[cfg(feature = "realtime")]
297#[cfg_attr(docsrs, doc(cfg(feature = "realtime")))]
298#[derive(Debug, Clone)]
299pub struct RealtimeSocketRequestBuilder {
300 client: Client,
301 model: Option<String>,
302 options: RequestOptions,
303}
304
305#[cfg(feature = "responses-ws")]
307#[cfg_attr(docsrs, doc(cfg(feature = "responses-ws")))]
308#[derive(Debug, Clone)]
309pub struct ResponsesSocketRequestBuilder {
310 client: Client,
311 options: RequestOptions,
312}
313
314impl ResponseStreamRequestBuilder {
315 pub(crate) fn new(client: Client) -> Self {
316 Self {
317 inner: ResponseCreateRequestBuilder::new(client),
318 response_id: None,
319 starting_after: None,
320 }
321 }
322
323 pub fn model(mut self, model: impl Into<String>) -> Self {
325 self.inner = self.inner.model(model);
326 self
327 }
328
329 pub fn input_text(mut self, input: impl Into<String>) -> Self {
331 self.inner = self.inner.input_text(input);
332 self
333 }
334
335 pub fn input_items(mut self, items: Vec<ResponseInputItemPayload>) -> Self {
337 self.inner = self.inner.input_items(items);
338 self
339 }
340
341 pub fn input(mut self, input: impl Into<ResponseInputPayload>) -> Self {
343 self.inner = self.inner.input(input);
344 self
345 }
346
347 pub fn temperature(mut self, temperature: f32) -> Self {
349 self.inner = self.inner.temperature(temperature);
350 self
351 }
352
353 pub fn tool(mut self, tool: ChatToolDefinition) -> Self {
355 self.inner = self.inner.tool(tool);
356 self
357 }
358
359 pub fn extra_body(mut self, key: impl Into<String>, value: impl Into<JsonPayload>) -> Self {
361 self.inner = self.inner.extra_body(key, value);
362 self
363 }
364
365 pub fn provider_option(
367 mut self,
368 key: impl Into<String>,
369 value: impl Into<JsonPayload>,
370 ) -> Self {
371 self.inner = self.inner.provider_option(key, value);
372 self
373 }
374
375 pub fn extra_header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
377 self.inner.options.insert_header(key, value);
378 self
379 }
380
381 pub fn extra_query(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
383 self.inner.options.insert_query(key, value);
384 self
385 }
386
387 pub fn timeout(mut self, timeout: Duration) -> Self {
389 self.inner.options.timeout = Some(timeout);
390 self
391 }
392
393 pub fn max_retries(mut self, max_retries: u32) -> Self {
395 self.inner.options.max_retries = Some(max_retries);
396 self
397 }
398
399 pub fn cancellation_token(mut self, token: CancellationToken) -> Self {
401 self.inner.options.cancellation_token = Some(token);
402 self
403 }
404
405 pub fn response_id(mut self, response_id: impl Into<String>) -> Self {
407 self.response_id = Some(response_id.into());
408 self
409 }
410
411 pub fn starting_after(mut self, sequence_number: u64) -> Self {
413 self.starting_after = Some(sequence_number);
414 self
415 }
416
417 pub async fn send(mut self) -> Result<ResponseStream> {
423 let (client, spec) = if let Some(response_id) = self.response_id.take() {
424 if self.inner.params.model.is_some()
425 || self.inner.params.input.is_some()
426 || self.inner.params.temperature.is_some()
427 || !self.inner.params.tools.is_empty()
428 || !self.inner.extra_body.is_empty()
429 || !self.inner.provider_options.is_empty()
430 {
431 return Err(Error::InvalidConfig(
432 "按 response_id 继续流时,不应再设置创建期参数或请求体扩展字段".into(),
433 ));
434 }
435
436 let client = self
437 .inner
438 .client
439 .take()
440 .ok_or_else(|| Error::InvalidConfig("Responses 构建器缺少客户端".into()))?;
441 let mut spec = RequestSpec::new(
442 "responses.stream.retrieve",
443 Method::GET,
444 format!("/responses/{}", encode_path_segment(response_id)),
445 );
446 spec.options = self.inner.options;
447 spec.options.insert_query("stream", "true");
448 if let Some(sequence_number) = self.starting_after {
449 spec.options
450 .insert_query("starting_after", sequence_number.to_string());
451 }
452 (client, spec)
453 } else {
454 if self.starting_after.is_some() {
455 return Err(Error::InvalidConfig(
456 "`starting_after` 只能与 `response_id` 一起使用".into(),
457 ));
458 }
459 self.inner.build_spec(true)?
460 };
461 Ok(ResponseStream::new(client.execute_sse(spec).await?))
462 }
463
464 pub async fn send_events(self) -> Result<ResponseEventStream> {
470 Ok(self.send().await?.events())
471 }
472}
473
474#[cfg(feature = "realtime")]
475impl RealtimeSocketRequestBuilder {
476 pub(crate) fn new(client: Client) -> Self {
477 Self {
478 client,
479 model: None,
480 options: RequestOptions::default(),
481 }
482 }
483
484 pub fn model(mut self, model: impl Into<String>) -> Self {
486 self.model = Some(model.into());
487 self
488 }
489
490 pub fn extra_header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
492 self.options.insert_header(key, value);
493 self
494 }
495
496 pub fn extra_query(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
498 self.options.insert_query(key, value);
499 self
500 }
501
502 pub async fn connect(self) -> Result<RealtimeSocket> {
508 RealtimeSocket::connect(&self.client, self.model, self.options).await
509 }
510}
511
512#[cfg(feature = "responses-ws")]
513impl ResponsesSocketRequestBuilder {
514 pub(crate) fn new(client: Client) -> Self {
515 Self {
516 client,
517 options: RequestOptions::default(),
518 }
519 }
520
521 pub fn extra_header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
523 self.options.insert_header(key, value);
524 self
525 }
526
527 pub fn extra_query(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
529 self.options.insert_query(key, value);
530 self
531 }
532
533 pub async fn connect(self) -> Result<ResponsesSocket> {
539 ResponsesSocket::connect(&self.client, self.options).await
540 }
541}
542
543impl ResponseCreateRequestBuilder {
544 pub(crate) fn new(client: Client) -> Self {
545 Self {
546 client: Some(client),
547 ..Self::default()
548 }
549 }
550
551 pub fn model(mut self, model: impl Into<String>) -> Self {
553 self.params.model = Some(model.into());
554 self
555 }
556
557 pub fn input_text(mut self, input: impl Into<String>) -> Self {
559 self.params.input = Some(ResponseInputPayload::from(input.into()));
560 self
561 }
562
563 pub fn input_items(mut self, items: Vec<ResponseInputItemPayload>) -> Self {
565 self.params.input = Some(ResponseInputPayload::from(items));
566 self
567 }
568
569 pub fn input(mut self, input: impl Into<ResponseInputPayload>) -> Self {
571 self.params.input = Some(input.into());
572 self
573 }
574
575 pub fn temperature(mut self, temperature: f32) -> Self {
577 self.params.temperature = Some(temperature);
578 self
579 }
580
581 pub fn tool(mut self, tool: ChatToolDefinition) -> Self {
583 self.params.tools.push(tool);
584 self
585 }
586
587 pub fn extra_body(mut self, key: impl Into<String>, value: impl Into<JsonPayload>) -> Self {
589 self.extra_body.insert(key.into(), value.into().into_raw());
590 self
591 }
592
593 pub fn provider_option(
595 mut self,
596 key: impl Into<String>,
597 value: impl Into<JsonPayload>,
598 ) -> Self {
599 self.provider_options
600 .insert(key.into(), value.into().into_raw());
601 self
602 }
603
604 pub fn extra_header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
606 self.options.insert_header(key, value);
607 self
608 }
609
610 pub fn extra_query(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
612 self.options.insert_query(key, value);
613 self
614 }
615
616 pub fn timeout(mut self, timeout: Duration) -> Self {
618 self.options.timeout = Some(timeout);
619 self
620 }
621
622 pub fn max_retries(mut self, max_retries: u32) -> Self {
624 self.options.max_retries = Some(max_retries);
625 self
626 }
627
628 pub fn cancellation_token(mut self, token: CancellationToken) -> Self {
630 self.options.cancellation_token = Some(token);
631 self
632 }
633
634 pub(crate) fn build_spec(mut self, stream: bool) -> Result<(Client, RequestSpec)> {
635 let client = self
636 .client
637 .take()
638 .ok_or_else(|| Error::InvalidConfig("Responses 构建器缺少客户端".into()))?;
639 if self.params.model.as_deref().unwrap_or_default().is_empty() {
640 return Err(Error::MissingRequiredField { field: "model" });
641 }
642 if self.params.input.is_none() {
643 return Err(Error::MissingRequiredField { field: "input" });
644 }
645
646 self.params.stream = Some(stream);
647 let provider_key = client.provider().kind().as_key();
648 let mut body = merge_json_body(
649 Some(value_from(&self.params)?),
650 &self.extra_body,
651 provider_key,
652 &self.provider_options,
653 );
654 if !self.params.tools.is_empty()
655 && let Some(object) = body.as_object_mut()
656 {
657 object.insert(
658 "tools".into(),
659 Value::Array(
660 self.params
661 .tools
662 .iter()
663 .map(ChatToolDefinition::as_response_tool_value)
664 .collect(),
665 ),
666 );
667 }
668 let mut spec = RequestSpec::new(
669 if stream {
670 "responses.stream"
671 } else {
672 "responses.create"
673 },
674 Method::POST,
675 "/responses",
676 );
677 spec.body = Some(body);
678 spec.options = self.options;
679 Ok((client, spec))
680 }
681
682 pub async fn send(self) -> Result<Response> {
688 Ok(self.send_with_meta().await?.data)
689 }
690
691 pub async fn send_with_meta(self) -> Result<ApiResponse<Response>> {
697 let (client, spec) = self.build_spec(false)?;
698 client.execute_json(spec).await
699 }
700}
701
702#[cfg(feature = "structured-output")]
704#[derive(Debug, Clone)]
705pub struct ResponseParseRequestBuilder<T> {
706 inner: ResponseCreateRequestBuilder,
707 _marker: PhantomData<T>,
708}
709
710#[cfg(feature = "structured-output")]
711impl<T> ResponseParseRequestBuilder<T> {
712 pub(crate) fn new(client: Client) -> Self {
713 Self {
714 inner: ResponseCreateRequestBuilder::new(client),
715 _marker: PhantomData,
716 }
717 }
718
719 pub fn model(mut self, model: impl Into<String>) -> Self {
721 self.inner = self.inner.model(model);
722 self
723 }
724
725 pub fn input_text(mut self, input: impl Into<String>) -> Self {
727 self.inner = self.inner.input_text(input);
728 self
729 }
730
731 pub fn input_items(mut self, items: Vec<ResponseInputItemPayload>) -> Self {
733 self.inner = self.inner.input_items(items);
734 self
735 }
736}
737
738#[cfg(feature = "structured-output")]
739impl<T> ResponseParseRequestBuilder<T>
740where
741 T: JsonSchema + serde::de::DeserializeOwned,
742{
743 pub async fn send(self) -> Result<ParsedResponse<T>> {
749 let response = self.inner.send().await?;
750 let output_text = response
751 .output_text()
752 .ok_or_else(|| Error::InvalidConfig("Responses 返回中缺少可解析文本".into()))?;
753 let parsed = parse_json_payload(&output_text)?;
754 Ok(ParsedResponse { response, parsed })
755 }
756}