Skip to main content

linger_openai_sdk/
realtime.rs

1use crate::error::{HeaderMap, LingerError};
2use crate::transport::HttpRequest;
3use crate::RequestId;
4use bytes::Bytes;
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7use std::collections::BTreeMap;
8use std::fmt;
9
10/// EN: Request body for `POST /v1/realtime/calls`.
11/// 中文:`POST /v1/realtime/calls` 的请求体。
12#[derive(Clone, Debug, PartialEq)]
13#[non_exhaustive]
14pub struct CreateRealtimeCallRequest {
15    /// EN: WebRTC SDP offer generated by the caller.
16    /// 中文:调用方生成的 WebRTC SDP offer。
17    pub sdp: String,
18    /// EN: Wire format used for the call creation request.
19    /// 中文:创建 call 请求使用的传输格式。
20    pub body_format: RealtimeCallBodyFormat,
21    /// EN: Optional session configuration sent in multipart requests.
22    /// 中文:multipart 请求中发送的可选 session 配置。
23    pub session: Option<RealtimeSessionConfig>,
24}
25
26impl CreateRealtimeCallRequest {
27    /// EN: Starts building a realtime call creation request.
28    /// 中文:开始构建 realtime call 创建请求。
29    pub fn builder() -> CreateRealtimeCallRequestBuilder {
30        CreateRealtimeCallRequestBuilder::default()
31    }
32
33    pub(crate) fn apply_body(&self, request: &mut HttpRequest) -> Result<(), LingerError> {
34        match self.body_format {
35            RealtimeCallBodyFormat::Sdp => {
36                request.insert_header("content-type", "application/sdp");
37                request.set_body(Bytes::from(self.sdp.clone()));
38            }
39            RealtimeCallBodyFormat::Multipart => {
40                let session = self
41                    .session
42                    .as_ref()
43                    .map(serde_json::to_string)
44                    .transpose()?;
45                let boundary = realtime_multipart_boundary(&self.sdp, session.as_deref());
46                request.insert_header(
47                    "content-type",
48                    format!("multipart/form-data; boundary={boundary}"),
49                );
50                request.set_body(self.multipart_body(&boundary, session.as_deref()));
51            }
52        }
53        Ok(())
54    }
55
56    fn multipart_body(&self, boundary: &str, session: Option<&str>) -> Bytes {
57        let mut body = Vec::new();
58        push_typed_multipart_field(
59            &mut body,
60            boundary,
61            "sdp",
62            "application/sdp",
63            self.sdp.as_bytes(),
64        );
65        if let Some(session) = session {
66            push_typed_multipart_field(
67                &mut body,
68                boundary,
69                "session",
70                "application/json",
71                session.as_bytes(),
72            );
73        }
74        body.extend_from_slice(format!("--{boundary}--\r\n").as_bytes());
75        Bytes::from(body)
76    }
77}
78
79/// EN: Wire body format for realtime call creation.
80/// 中文:realtime call 创建请求的传输体格式。
81#[derive(Clone, Copy, Debug, PartialEq, Eq)]
82#[non_exhaustive]
83pub enum RealtimeCallBodyFormat {
84    /// EN: Send the SDP offer directly as `application/sdp`.
85    /// 中文:直接以 `application/sdp` 发送 SDP offer。
86    Sdp,
87    /// EN: Send the SDP offer and optional session config as multipart fields.
88    /// 中文:以 multipart 字段发送 SDP offer 和可选 session 配置。
89    Multipart,
90}
91
92/// EN: Builder for realtime call creation requests.
93/// 中文:realtime call 创建请求的构建器。
94#[derive(Clone, Debug, Default)]
95#[non_exhaustive]
96pub struct CreateRealtimeCallRequestBuilder {
97    sdp: Option<String>,
98    body_format: Option<RealtimeCallBodyFormat>,
99    session: Option<RealtimeSessionConfig>,
100}
101
102impl CreateRealtimeCallRequestBuilder {
103    /// EN: Sets the WebRTC SDP offer.
104    /// 中文:设置 WebRTC SDP offer。
105    pub fn sdp(mut self, sdp: impl Into<String>) -> Self {
106        self.sdp = Some(sdp.into());
107        self
108    }
109
110    /// EN: Sets the request wire body format.
111    /// 中文:设置请求传输体格式。
112    pub fn body_format(mut self, body_format: RealtimeCallBodyFormat) -> Self {
113        self.body_format = Some(body_format);
114        self
115    }
116
117    /// EN: Sets the optional session configuration for multipart requests.
118    /// 中文:为 multipart 请求设置可选 session 配置。
119    pub fn session(mut self, session: RealtimeSessionConfig) -> Self {
120        self.session = Some(session);
121        self
122    }
123
124    /// EN: Builds and validates the request.
125    /// 中文:构建并校验请求。
126    pub fn build(self) -> Result<CreateRealtimeCallRequest, LingerError> {
127        let body_format = self
128            .body_format
129            .unwrap_or(RealtimeCallBodyFormat::Multipart);
130        if body_format == RealtimeCallBodyFormat::Sdp && self.session.is_some() {
131            return Err(LingerError::invalid_config(
132                "session requires multipart body format",
133            ));
134        }
135        Ok(CreateRealtimeCallRequest {
136            sdp: required_string("sdp", self.sdp)?,
137            body_format,
138            session: self.session,
139        })
140    }
141}
142
143/// EN: SDP answer returned by `POST /v1/realtime/calls`.
144/// 中文:`POST /v1/realtime/calls` 返回的 SDP answer。
145#[derive(Clone, Debug, PartialEq, Eq)]
146#[non_exhaustive]
147pub struct RealtimeCallSdpAnswer {
148    /// EN: SDP answer produced by OpenAI for the peer connection.
149    /// 中文:OpenAI 为 peer connection 生成的 SDP answer。
150    pub sdp: String,
151    /// EN: Relative call URL from the `Location` response header, when present.
152    /// 中文:`Location` 响应头中的相对 call URL,如存在。
153    pub location: Option<String>,
154    /// EN: OpenAI request id from response headers.
155    /// 中文:响应头中的 OpenAI 请求 ID。
156    request_id: Option<RequestId>,
157}
158
159impl RealtimeCallSdpAnswer {
160    pub(crate) fn from_parts(
161        headers: &HeaderMap,
162        request_id: Option<RequestId>,
163        body: Bytes,
164    ) -> Result<Self, LingerError> {
165        let sdp = String::from_utf8(body.to_vec())
166            .map_err(|error| LingerError::serialization(error.to_string()))?;
167        Ok(Self {
168            sdp,
169            location: headers.get("location").map(str::to_owned),
170            request_id,
171        })
172    }
173
174    /// EN: Returns the OpenAI request id, when present.
175    /// 中文:返回 OpenAI 请求 ID,如存在。
176    pub fn request_id(&self) -> Option<&RequestId> {
177        self.request_id.as_ref()
178    }
179}
180
181/// EN: Request body for `POST /v1/realtime/calls/{call_id}/refer`.
182/// 中文:`POST /v1/realtime/calls/{call_id}/refer` 的请求体。
183#[derive(Clone, Debug, Serialize, PartialEq, Eq)]
184#[non_exhaustive]
185pub struct CreateRealtimeCallReferRequest {
186    /// EN: SIP Refer-To URI for the transfer destination.
187    /// 中文:转接目标的 SIP Refer-To URI。
188    pub target_uri: String,
189}
190
191impl CreateRealtimeCallReferRequest {
192    /// EN: Starts building a realtime call refer request.
193    /// 中文:开始构建 realtime call refer 请求。
194    pub fn builder() -> CreateRealtimeCallReferRequestBuilder {
195        CreateRealtimeCallReferRequestBuilder::default()
196    }
197}
198
199/// EN: Builder for realtime call refer requests.
200/// 中文:realtime call refer 请求的构建器。
201#[derive(Clone, Debug, Default)]
202#[non_exhaustive]
203pub struct CreateRealtimeCallReferRequestBuilder {
204    target_uri: Option<String>,
205}
206
207impl CreateRealtimeCallReferRequestBuilder {
208    /// EN: Sets the destination URI for the SIP REFER request.
209    /// 中文:设置 SIP REFER 请求的目标 URI。
210    pub fn target_uri(mut self, target_uri: impl Into<String>) -> Self {
211        self.target_uri = Some(target_uri.into());
212        self
213    }
214
215    /// EN: Builds and validates the request.
216    /// 中文:构建并校验请求。
217    pub fn build(self) -> Result<CreateRealtimeCallReferRequest, LingerError> {
218        Ok(CreateRealtimeCallReferRequest {
219            target_uri: required_string("target_uri", self.target_uri)?,
220        })
221    }
222}
223
224/// EN: Request body for `POST /v1/realtime/calls/{call_id}/reject`.
225/// 中文:`POST /v1/realtime/calls/{call_id}/reject` 的请求体。
226#[derive(Clone, Debug, Default, Serialize, PartialEq, Eq)]
227#[non_exhaustive]
228pub struct RejectRealtimeCallRequest {
229    /// EN: Optional SIP response code to send back to the caller.
230    /// 中文:可选的 SIP 响应码,用于返回给呼叫方。
231    #[serde(skip_serializing_if = "Option::is_none")]
232    pub status_code: Option<u16>,
233}
234
235impl RejectRealtimeCallRequest {
236    /// EN: Starts building a realtime call reject request.
237    /// 中文:开始构建 realtime call reject 请求。
238    pub fn builder() -> RejectRealtimeCallRequestBuilder {
239        RejectRealtimeCallRequestBuilder::default()
240    }
241}
242
243/// EN: Builder for realtime call reject requests.
244/// 中文:realtime call reject 请求的构建器。
245#[derive(Clone, Debug, Default)]
246#[non_exhaustive]
247pub struct RejectRealtimeCallRequestBuilder {
248    status_code: Option<u16>,
249}
250
251impl RejectRealtimeCallRequestBuilder {
252    /// EN: Sets the SIP response code to send back to the caller.
253    /// 中文:设置返回给呼叫方的 SIP 响应码。
254    pub fn status_code(mut self, status_code: u16) -> Self {
255        self.status_code = Some(status_code);
256        self
257    }
258
259    /// EN: Builds the request.
260    /// 中文:构建请求。
261    pub fn build(self) -> Result<RejectRealtimeCallRequest, LingerError> {
262        Ok(RejectRealtimeCallRequest {
263            status_code: self.status_code,
264        })
265    }
266}
267
268/// EN: Request body for `POST /v1/realtime/sessions`.
269/// 中文:`POST /v1/realtime/sessions` 的请求体。
270#[derive(Clone, Debug, Serialize, PartialEq)]
271#[non_exhaustive]
272pub struct CreateRealtimeSessionRequest {
273    /// EN: Realtime model used for the session.
274    /// 中文:此会话使用的 realtime 模型。
275    pub model: String,
276    /// EN: Forward-compatible optional session fields.
277    /// 中文:前向兼容的可选会话字段。
278    #[serde(flatten)]
279    pub extra: BTreeMap<String, Value>,
280}
281
282impl CreateRealtimeSessionRequest {
283    /// EN: Starts building a realtime session request.
284    /// 中文:开始构建 realtime session 请求。
285    pub fn builder() -> CreateRealtimeSessionRequestBuilder {
286        CreateRealtimeSessionRequestBuilder::default()
287    }
288}
289
290/// EN: Builder for realtime session creation requests.
291/// 中文:realtime session 创建请求的构建器。
292#[derive(Clone, Debug, Default)]
293#[non_exhaustive]
294pub struct CreateRealtimeSessionRequestBuilder {
295    model: Option<String>,
296    extra: BTreeMap<String, Value>,
297}
298
299impl CreateRealtimeSessionRequestBuilder {
300    /// EN: Sets the realtime model.
301    /// 中文:设置 realtime 模型。
302    pub fn model(mut self, model: impl Into<String>) -> Self {
303        self.model = Some(model.into());
304        self
305    }
306
307    /// EN: Adds a forward-compatible JSON field.
308    /// 中文:添加一个前向兼容 JSON 字段。
309    pub fn extra(mut self, name: impl Into<String>, value: Value) -> Self {
310        self.extra.insert(name.into(), value);
311        self
312    }
313
314    /// EN: Builds and validates the request.
315    /// 中文:构建并校验请求。
316    pub fn build(self) -> Result<CreateRealtimeSessionRequest, LingerError> {
317        validate_extra_fields(&self.extra)?;
318        Ok(CreateRealtimeSessionRequest {
319            model: required_string("model", self.model)?,
320            extra: self.extra,
321        })
322    }
323}
324
325/// EN: Request body for `POST /v1/realtime/transcription_sessions`.
326/// 中文:`POST /v1/realtime/transcription_sessions` 的请求体。
327#[derive(Clone, Debug, Default, Serialize, PartialEq)]
328#[non_exhaustive]
329pub struct CreateRealtimeTranscriptionSessionRequest {
330    /// EN: Forward-compatible transcription session fields.
331    /// 中文:前向兼容的 transcription session 字段。
332    #[serde(flatten)]
333    pub extra: BTreeMap<String, Value>,
334}
335
336impl CreateRealtimeTranscriptionSessionRequest {
337    /// EN: Starts building a realtime transcription session request.
338    /// 中文:开始构建 realtime transcription session 请求。
339    pub fn builder() -> CreateRealtimeTranscriptionSessionRequestBuilder {
340        CreateRealtimeTranscriptionSessionRequestBuilder::default()
341    }
342}
343
344/// EN: Builder for realtime transcription session creation requests.
345/// 中文:realtime transcription session 创建请求的构建器。
346#[derive(Clone, Debug, Default)]
347#[non_exhaustive]
348pub struct CreateRealtimeTranscriptionSessionRequestBuilder {
349    extra: BTreeMap<String, Value>,
350}
351
352impl CreateRealtimeTranscriptionSessionRequestBuilder {
353    /// EN: Adds a forward-compatible JSON field.
354    /// 中文:添加一个前向兼容 JSON 字段。
355    pub fn extra(mut self, name: impl Into<String>, value: Value) -> Self {
356        self.extra.insert(name.into(), value);
357        self
358    }
359
360    /// EN: Builds and validates the request.
361    /// 中文:构建并校验请求。
362    pub fn build(self) -> Result<CreateRealtimeTranscriptionSessionRequest, LingerError> {
363        validate_extra_fields(&self.extra)?;
364        Ok(CreateRealtimeTranscriptionSessionRequest { extra: self.extra })
365    }
366}
367
368/// EN: Translation session config for `POST /v1/realtime/translations/client_secrets`.
369/// 中文:`POST /v1/realtime/translations/client_secrets` 的 translation session 配置。
370#[derive(Clone, Debug, Serialize, PartialEq)]
371#[non_exhaustive]
372pub struct CreateRealtimeTranslationSessionRequest {
373    /// EN: Realtime translation model used for the session.
374    /// 中文:此 session 使用的 realtime translation 模型。
375    pub model: String,
376    /// EN: Forward-compatible translation session fields.
377    /// 中文:前向兼容的 translation session 字段。
378    #[serde(flatten)]
379    pub extra: BTreeMap<String, Value>,
380}
381
382impl CreateRealtimeTranslationSessionRequest {
383    /// EN: Starts building a realtime translation session request.
384    /// 中文:开始构建 realtime translation session 请求。
385    pub fn builder() -> CreateRealtimeTranslationSessionRequestBuilder {
386        CreateRealtimeTranslationSessionRequestBuilder::default()
387    }
388}
389
390/// EN: Builder for realtime translation session configs.
391/// 中文:realtime translation session 配置的构建器。
392#[derive(Clone, Debug, Default)]
393#[non_exhaustive]
394pub struct CreateRealtimeTranslationSessionRequestBuilder {
395    model: Option<String>,
396    extra: BTreeMap<String, Value>,
397}
398
399impl CreateRealtimeTranslationSessionRequestBuilder {
400    /// EN: Sets the realtime translation model.
401    /// 中文:设置 realtime translation 模型。
402    pub fn model(mut self, model: impl Into<String>) -> Self {
403        self.model = Some(model.into());
404        self
405    }
406
407    /// EN: Adds a forward-compatible JSON field.
408    /// 中文:添加一个前向兼容 JSON 字段。
409    pub fn extra(mut self, name: impl Into<String>, value: Value) -> Self {
410        self.extra.insert(name.into(), value);
411        self
412    }
413
414    /// EN: Builds and validates the request.
415    /// 中文:构建并校验请求。
416    pub fn build(self) -> Result<CreateRealtimeTranslationSessionRequest, LingerError> {
417        validate_extra_fields(&self.extra)?;
418        Ok(CreateRealtimeTranslationSessionRequest {
419            model: required_string("model", self.model)?,
420            extra: self.extra,
421        })
422    }
423}
424
425/// EN: Request body for `POST /v1/realtime/translations/client_secrets`.
426/// 中文:`POST /v1/realtime/translations/client_secrets` 的请求体。
427#[derive(Clone, Debug, Serialize, PartialEq)]
428#[non_exhaustive]
429pub struct CreateRealtimeTranslationClientSecretRequest {
430    /// EN: Translation session configuration.
431    /// 中文:Translation session 配置。
432    pub session: CreateRealtimeTranslationSessionRequest,
433    /// EN: Forward-compatible client secret fields such as expiration settings.
434    /// 中文:前向兼容的 client secret 字段,例如过期设置。
435    #[serde(flatten)]
436    pub extra: BTreeMap<String, Value>,
437}
438
439impl CreateRealtimeTranslationClientSecretRequest {
440    /// EN: Starts building a realtime translation client secret request.
441    /// 中文:开始构建 realtime translation client secret 请求。
442    pub fn builder() -> CreateRealtimeTranslationClientSecretRequestBuilder {
443        CreateRealtimeTranslationClientSecretRequestBuilder::default()
444    }
445}
446
447/// EN: Builder for realtime translation client secret requests.
448/// 中文:realtime translation client secret 请求的构建器。
449#[derive(Clone, Debug, Default)]
450#[non_exhaustive]
451pub struct CreateRealtimeTranslationClientSecretRequestBuilder {
452    session: Option<CreateRealtimeTranslationSessionRequest>,
453    extra: BTreeMap<String, Value>,
454}
455
456impl CreateRealtimeTranslationClientSecretRequestBuilder {
457    /// EN: Sets the translation session configuration.
458    /// 中文:设置 translation session 配置。
459    pub fn session(mut self, session: CreateRealtimeTranslationSessionRequest) -> Self {
460        self.session = Some(session);
461        self
462    }
463
464    /// EN: Adds a forward-compatible JSON field.
465    /// 中文:添加一个前向兼容 JSON 字段。
466    pub fn extra(mut self, name: impl Into<String>, value: Value) -> Self {
467        self.extra.insert(name.into(), value);
468        self
469    }
470
471    /// EN: Builds and validates the request.
472    /// 中文:构建并校验请求。
473    pub fn build(self) -> Result<CreateRealtimeTranslationClientSecretRequest, LingerError> {
474        validate_extra_fields(&self.extra)?;
475        Ok(CreateRealtimeTranslationClientSecretRequest {
476            session: self
477                .session
478                .ok_or_else(|| LingerError::invalid_config("session is required"))?,
479            extra: self.extra,
480        })
481    }
482}
483
484/// EN: Request body for `POST /v1/realtime/client_secrets`.
485/// 中文:`POST /v1/realtime/client_secrets` 的请求体。
486#[derive(Clone, Debug, Serialize, PartialEq)]
487#[non_exhaustive]
488pub struct CreateRealtimeClientSecretRequest {
489    /// EN: Realtime or transcription session configuration.
490    /// 中文:Realtime 或转录会话配置。
491    pub session: RealtimeSessionConfig,
492}
493
494impl CreateRealtimeClientSecretRequest {
495    /// EN: Starts building a realtime client secret creation request.
496    /// 中文:开始构建 realtime client secret 创建请求。
497    pub fn builder() -> CreateRealtimeClientSecretRequestBuilder {
498        CreateRealtimeClientSecretRequestBuilder::default()
499    }
500}
501
502/// EN: Builder for realtime client secret creation requests.
503/// 中文:realtime client secret 创建请求的构建器。
504#[derive(Clone, Debug, Default)]
505#[non_exhaustive]
506pub struct CreateRealtimeClientSecretRequestBuilder {
507    session: Option<RealtimeSessionConfig>,
508}
509
510impl CreateRealtimeClientSecretRequestBuilder {
511    /// EN: Sets the session configuration.
512    /// 中文:设置会话配置。
513    pub fn session(mut self, session: RealtimeSessionConfig) -> Self {
514        self.session = Some(session);
515        self
516    }
517
518    /// EN: Builds and validates the request.
519    /// 中文:构建并校验请求。
520    pub fn build(self) -> Result<CreateRealtimeClientSecretRequest, LingerError> {
521        Ok(CreateRealtimeClientSecretRequest {
522            session: self
523                .session
524                .ok_or_else(|| LingerError::invalid_config("session is required"))?,
525        })
526    }
527}
528
529/// EN: Forward-compatible realtime session configuration.
530/// 中文:前向兼容的 realtime 会话配置。
531#[derive(Clone, Debug, Serialize, PartialEq)]
532#[non_exhaustive]
533pub struct RealtimeSessionConfig {
534    /// EN: Session kind, for example `realtime` or `transcription`.
535    /// 中文:会话类型,例如 `realtime` 或 `transcription`。
536    #[serde(rename = "type")]
537    pub kind: String,
538    /// EN: Optional realtime model.
539    /// 中文:可选的 realtime 模型。
540    #[serde(skip_serializing_if = "Option::is_none")]
541    pub model: Option<String>,
542    /// EN: Forward-compatible optional fields not yet covered by handwritten types.
543    /// 中文:手写类型尚未覆盖的前向兼容可选字段。
544    #[serde(flatten)]
545    pub extra: BTreeMap<String, Value>,
546}
547
548impl RealtimeSessionConfig {
549    /// EN: Starts building a session config with the documented session type.
550    /// 中文:使用文档中的会话类型开始构建会话配置。
551    pub fn builder(kind: impl Into<String>) -> RealtimeSessionConfigBuilder {
552        RealtimeSessionConfigBuilder {
553            kind: Some(kind.into()),
554            model: None,
555            extra: BTreeMap::new(),
556        }
557    }
558}
559
560/// EN: Builder for realtime session configuration.
561/// 中文:realtime 会话配置的构建器。
562#[derive(Clone, Debug, Default)]
563#[non_exhaustive]
564pub struct RealtimeSessionConfigBuilder {
565    kind: Option<String>,
566    model: Option<String>,
567    extra: BTreeMap<String, Value>,
568}
569
570impl RealtimeSessionConfigBuilder {
571    /// EN: Sets the realtime model.
572    /// 中文:设置 realtime 模型。
573    pub fn model(mut self, model: impl Into<String>) -> Self {
574        self.model = Some(model.into());
575        self
576    }
577
578    /// EN: Adds a forward-compatible JSON field.
579    /// 中文:添加一个前向兼容 JSON 字段。
580    pub fn extra(mut self, name: impl Into<String>, value: Value) -> Self {
581        self.extra.insert(name.into(), value);
582        self
583    }
584
585    /// EN: Builds and validates the session configuration.
586    /// 中文:构建并校验会话配置。
587    pub fn build(self) -> Result<RealtimeSessionConfig, LingerError> {
588        validate_optional_string("model", self.model.as_deref())?;
589        validate_extra_fields(&self.extra)?;
590        Ok(RealtimeSessionConfig {
591            kind: required_string("type", self.kind)?,
592            model: self.model,
593            extra: self.extra,
594        })
595    }
596}
597
598/// EN: Realtime client secret response.
599/// 中文:Realtime client secret 响应。
600#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
601#[non_exhaustive]
602pub struct RealtimeClientSecret {
603    /// EN: Ephemeral client secret value.
604    /// 中文:临时 client secret 值。
605    pub value: RealtimeClientSecretValue,
606    /// EN: Unix timestamp for expiration, when returned.
607    /// 中文:响应中存在时的过期 Unix 时间戳。
608    #[serde(default)]
609    pub expires_at: Option<u64>,
610    /// EN: Session configuration returned by the API.
611    /// 中文:API 返回的会话配置。
612    #[serde(default)]
613    pub session: Value,
614    /// EN: Additional fields preserved for forward compatibility.
615    /// 中文:为前向兼容保留的额外字段。
616    #[serde(flatten)]
617    pub extra: BTreeMap<String, Value>,
618    /// EN: OpenAI request id from response headers.
619    /// 中文:响应头中的 OpenAI 请求 ID。
620    #[serde(skip)]
621    request_id: Option<RequestId>,
622}
623
624impl RealtimeClientSecret {
625    pub(crate) fn with_request_id(mut self, request_id: Option<RequestId>) -> Self {
626        self.request_id = request_id;
627        self
628    }
629
630    /// EN: Returns the OpenAI request id, when present.
631    /// 中文:返回 OpenAI 请求 ID,如存在。
632    pub fn request_id(&self) -> Option<&RequestId> {
633        self.request_id.as_ref()
634    }
635}
636
637/// EN: Realtime session response with an ephemeral client secret.
638/// 中文:包含临时 client secret 的 realtime session 响应。
639#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
640#[non_exhaustive]
641pub struct RealtimeSession {
642    /// EN: Realtime session id.
643    /// 中文:Realtime session ID。
644    pub id: String,
645    /// EN: API object type, normally `realtime.session`.
646    /// 中文:API 对象类型,通常为 `realtime.session`。
647    pub object: String,
648    /// EN: Realtime model used for the session.
649    /// 中文:此会话使用的 realtime 模型。
650    pub model: String,
651    /// EN: Ephemeral client secret for client-side Realtime authentication.
652    /// 中文:用于客户端 Realtime 认证的临时 client secret。
653    pub client_secret: RealtimeClientSecret,
654    /// EN: Additional response fields preserved for forward compatibility.
655    /// 中文:为前向兼容保留的额外响应字段。
656    #[serde(flatten)]
657    pub extra: BTreeMap<String, Value>,
658    /// EN: OpenAI request id from response headers.
659    /// 中文:响应头中的 OpenAI 请求 ID。
660    #[serde(skip)]
661    request_id: Option<RequestId>,
662}
663
664impl RealtimeSession {
665    pub(crate) fn with_request_id(mut self, request_id: Option<RequestId>) -> Self {
666        self.request_id = request_id;
667        self
668    }
669
670    /// EN: Returns the OpenAI request id, when present.
671    /// 中文:返回 OpenAI 请求 ID,如存在。
672    pub fn request_id(&self) -> Option<&RequestId> {
673        self.request_id.as_ref()
674    }
675}
676
677/// EN: Realtime transcription session response.
678/// 中文:Realtime transcription session 响应。
679#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
680#[non_exhaustive]
681pub struct RealtimeTranscriptionSession {
682    /// EN: Realtime transcription session id, when returned.
683    /// 中文:返回时的 realtime transcription session ID。
684    #[serde(default)]
685    pub id: Option<String>,
686    /// EN: API object type, normally `realtime.transcription_session`.
687    /// 中文:API 对象类型,通常为 `realtime.transcription_session`。
688    #[serde(default)]
689    pub object: Option<String>,
690    /// EN: Ephemeral client secret, when returned by the API.
691    /// 中文:API 返回时的临时 client secret。
692    #[serde(default)]
693    pub client_secret: Option<RealtimeClientSecret>,
694    /// EN: Additional response fields preserved for forward compatibility.
695    /// 中文:为前向兼容保留的额外响应字段。
696    #[serde(flatten)]
697    pub extra: BTreeMap<String, Value>,
698    /// EN: OpenAI request id from response headers.
699    /// 中文:响应头中的 OpenAI 请求 ID。
700    #[serde(skip)]
701    request_id: Option<RequestId>,
702}
703
704impl RealtimeTranscriptionSession {
705    pub(crate) fn with_request_id(mut self, request_id: Option<RequestId>) -> Self {
706        self.request_id = request_id;
707        self
708    }
709
710    /// EN: Returns the OpenAI request id, when present.
711    /// 中文:返回 OpenAI 请求 ID,如存在。
712    pub fn request_id(&self) -> Option<&RequestId> {
713        self.request_id.as_ref()
714    }
715}
716
717/// EN: Realtime translation client secret response.
718/// 中文:Realtime translation client secret 响应。
719#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
720#[non_exhaustive]
721pub struct RealtimeTranslationClientSecret {
722    /// EN: Ephemeral translation client secret value.
723    /// 中文:临时 translation client secret 值。
724    pub value: RealtimeClientSecretValue,
725    /// EN: Unix timestamp for client secret expiration.
726    /// 中文:client secret 过期的 Unix 时间戳。
727    pub expires_at: u64,
728    /// EN: Translation session returned by the API.
729    /// 中文:API 返回的 translation session。
730    pub session: Value,
731    /// EN: Additional response fields preserved for forward compatibility.
732    /// 中文:为前向兼容保留的额外响应字段。
733    #[serde(flatten)]
734    pub extra: BTreeMap<String, Value>,
735    /// EN: OpenAI request id from response headers.
736    /// 中文:响应头中的 OpenAI 请求 ID。
737    #[serde(skip)]
738    request_id: Option<RequestId>,
739}
740
741impl RealtimeTranslationClientSecret {
742    pub(crate) fn with_request_id(mut self, request_id: Option<RequestId>) -> Self {
743        self.request_id = request_id;
744        self
745    }
746
747    /// EN: Returns the OpenAI request id, when present.
748    /// 中文:返回 OpenAI 请求 ID,如存在。
749    pub fn request_id(&self) -> Option<&RequestId> {
750        self.request_id.as_ref()
751    }
752}
753
754/// EN: Redacted wrapper for ephemeral realtime client secret values.
755/// 中文:临时 realtime client secret 值的脱敏包装。
756#[derive(Clone, Deserialize, Serialize, PartialEq, Eq)]
757#[serde(transparent)]
758#[non_exhaustive]
759pub struct RealtimeClientSecretValue(String);
760
761impl RealtimeClientSecretValue {
762    /// EN: Returns the raw secret value for callers that need to pass it to a client.
763    /// 中文:返回原始 secret 值,供需要传给客户端的调用方使用。
764    pub fn as_str(&self) -> &str {
765        &self.0
766    }
767}
768
769impl fmt::Debug for RealtimeClientSecretValue {
770    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
771        f.write_str("\"<redacted>\"")
772    }
773}
774
775fn required_string(name: &str, value: Option<String>) -> Result<String, LingerError> {
776    value
777        .filter(|value| !value.trim().is_empty())
778        .ok_or_else(|| LingerError::invalid_config(format!("{name} is required")))
779}
780
781fn validate_optional_string(name: &str, value: Option<&str>) -> Result<(), LingerError> {
782    if value.is_some_and(|value| value.trim().is_empty()) {
783        return Err(LingerError::invalid_config(format!(
784            "{name} must not be empty"
785        )));
786    }
787    Ok(())
788}
789
790fn validate_extra_fields(extra: &BTreeMap<String, Value>) -> Result<(), LingerError> {
791    for (key, value) in extra {
792        if key.trim().is_empty() {
793            return Err(LingerError::invalid_config(
794                "extra field names must not be empty",
795            ));
796        }
797        if value.is_null() {
798            return Err(LingerError::invalid_config(format!(
799                "extra field {key} must not be null"
800            )));
801        }
802    }
803    Ok(())
804}
805
806fn push_typed_multipart_field(
807    body: &mut Vec<u8>,
808    boundary: &str,
809    name: &str,
810    content_type: &str,
811    value: &[u8],
812) {
813    body.extend_from_slice(
814        format!(
815            "--{boundary}\r\nContent-Disposition: form-data; name=\"{name}\"\r\nContent-Type: {content_type}\r\n\r\n"
816        )
817        .as_bytes(),
818    );
819    body.extend_from_slice(value);
820    body.extend_from_slice(b"\r\n");
821}
822
823fn realtime_multipart_boundary(sdp: &str, session: Option<&str>) -> String {
824    for counter in 0.. {
825        let boundary = format!("linger-openai-sdk-realtime-boundary-{counter}");
826        let boundary_bytes = boundary.as_bytes();
827        let conflicts_with_sdp = contains_bytes(sdp.as_bytes(), boundary_bytes);
828        let conflicts_with_session =
829            session.is_some_and(|session| contains_bytes(session.as_bytes(), boundary_bytes));
830        if !conflicts_with_sdp && !conflicts_with_session {
831            return boundary;
832        }
833    }
834    unreachable!("unbounded boundary counter")
835}
836
837fn contains_bytes(haystack: &[u8], needle: &[u8]) -> bool {
838    if needle.is_empty() {
839        return true;
840    }
841    haystack
842        .windows(needle.len())
843        .any(|window| window == needle)
844}