kind_openai/
endpoints.rs

1use reqwest::Method;
2use serde::{Deserialize, Serialize};
3
4use crate::{auth, error::OpenAIAPIError, OpenAI, OpenAIResult};
5
6pub mod chat;
7pub mod chat_reasoning;
8pub mod embeddings;
9
10const API_BASE_URL: &str = "https://api.openai.com/v1";
11
12// this enum and the struct below it are hacks to deal with openai's weird response format
13// where they will return either a single error field or the success payload.
14#[derive(Deserialize)]
15#[serde(untagged)]
16enum GenericOpenAIResponse<T> {
17    Success(T),
18    Error(ResponseDeserializableOpenAIAPIError),
19}
20
21#[derive(Deserialize)]
22struct ResponseDeserializableOpenAIAPIError {
23    error: OpenAIAPIError,
24}
25
26impl<T> From<GenericOpenAIResponse<T>> for OpenAIResult<T> {
27    fn from(value: GenericOpenAIResponse<T>) -> Self {
28        match value {
29            GenericOpenAIResponse::Success(success) => Ok(success),
30            GenericOpenAIResponse::Error(error) => Err(crate::OpenAIError::API(error.error)),
31        }
32    }
33}
34
35pub(super) async fn send_request<Auth, R>(
36    openai: &OpenAI<Auth>,
37    request: &R,
38) -> OpenAIResult<R::Response>
39where
40    Auth: auth::AuthTokenProvider,
41    R: OpenAIRequestProvider,
42{
43    let bearer_token = openai
44        .auth
45        .resolve()
46        .await
47        .ok_or(crate::error::OpenAIError::MissingAuthToken)?;
48
49    // take the response text and deserialize by hand so we can log response
50    // bodies that don't conform to the same structure
51    let response_text = openai
52        .client
53        .request(
54            R::METHOD,
55            format!("{API_BASE_URL}{}", R::path_with_leading_slash()),
56        )
57        .header("Authorization", format!("Bearer {bearer_token}"))
58        // TODO: support a way to omit the body during a get request if the time comes
59        .json(request)
60        .send()
61        .await?
62        .text()
63        .await?;
64
65    match serde_json::from_str::<GenericOpenAIResponse<R::Response>>(&response_text) {
66        Ok(response) => response.into(),
67        Err(err) => Err(crate::error::OpenAIError::Serde(response_text, err)),
68    }
69}
70
71mod private {
72    pub trait Sealed {}
73}
74
75/// Any type that can be sent to the client's `req` method.
76pub trait OpenAIRequestProvider: Serialize + private::Sealed {
77    type Response: for<'de> Deserialize<'de>;
78    const METHOD: Method;
79
80    fn path_with_leading_slash() -> String;
81}