Skip to main content

claude_codex/openai_compat/
mod.rs

1pub mod request;
2pub mod response;
3pub mod stream;
4
5use axum::{
6    Json,
7    http::StatusCode,
8    response::{IntoResponse, Response},
9};
10use serde_json::{Value, json};
11
12use crate::provider::{ProviderError, ProviderErrorKind};
13
14pub const MAX_OPENAI_REQUEST_BYTES: usize = 16 * 1024 * 1024;
15pub const MAX_PROVIDER_STREAM_BYTES: usize = 32 * 1024 * 1024;
16pub const MAX_SSE_EVENT_BYTES: usize = 2 * 1024 * 1024;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum OpenAiSurface {
20    ChatCompletions,
21    Responses,
22}
23
24impl OpenAiSurface {
25    pub fn label(self) -> &'static str {
26        match self {
27            Self::ChatCompletions => "chat_completions",
28            Self::Responses => "responses",
29        }
30    }
31}
32
33#[derive(Debug, Clone)]
34pub struct OpenAiResponseMetadata {
35    pub tools: Vec<Value>,
36    pub tool_choice: Value,
37}
38
39impl Default for OpenAiResponseMetadata {
40    fn default() -> Self {
41        Self {
42            tools: Vec::new(),
43            tool_choice: json!("auto"),
44        }
45    }
46}
47
48#[derive(Debug, Clone)]
49pub struct OpenAiError {
50    pub status: StatusCode,
51    pub kind: Box<str>,
52    pub message: Box<str>,
53    pub param: Option<Box<str>>,
54    pub code: Option<Box<str>>,
55    pub retry_after: Option<Box<str>>,
56}
57
58impl OpenAiError {
59    pub fn invalid(message: impl Into<String>, param: Option<impl Into<String>>) -> Self {
60        Self {
61            status: StatusCode::BAD_REQUEST,
62            kind: "invalid_request_error".into(),
63            message: message.into().into_boxed_str(),
64            param: param.map(|value| value.into().into_boxed_str()),
65            code: None,
66            retry_after: None,
67        }
68    }
69
70    pub fn upstream_protocol(message: impl Into<String>) -> Self {
71        Self {
72            status: StatusCode::BAD_GATEWAY,
73            kind: "api_error".into(),
74            message: message.into().into_boxed_str(),
75            param: None,
76            code: Some("upstream_protocol_error".into()),
77            retry_after: None,
78        }
79    }
80
81    pub fn unsupported(param: impl Into<String>) -> Self {
82        let param = param.into();
83        Self {
84            status: StatusCode::BAD_REQUEST,
85            kind: "invalid_request_error".into(),
86            message: format!("Unsupported parameter: '{param}'").into(),
87            param: Some(param.into()),
88            code: Some("unsupported_parameter".into()),
89            retry_after: None,
90        }
91    }
92
93    pub fn response(self) -> Response {
94        let status = self.status;
95        let retry_after = self.retry_after.clone();
96        let mut response = (
97            status,
98            Json(json!({
99                "error": {
100                    "message": self.message,
101                    "type": self.kind,
102                    "param": self.param,
103                    "code": self.code,
104                }
105            })),
106        )
107            .into_response();
108        if let Some(value) = retry_after.and_then(|value| value.parse().ok()) {
109            response
110                .headers_mut()
111                .insert(http::header::RETRY_AFTER, value);
112        }
113        response
114    }
115}
116
117impl From<ProviderError> for OpenAiError {
118    fn from(error: ProviderError) -> Self {
119        Self {
120            status: error.status,
121            kind: match error.kind {
122                ProviderErrorKind::Authentication => "authentication_error",
123                ProviderErrorKind::Permission => "permission_error",
124                ProviderErrorKind::RateLimit => "rate_limit_error",
125                ProviderErrorKind::InvalidRequest => "invalid_request_error",
126                ProviderErrorKind::Api => "api_error",
127            }
128            .into(),
129            message: error.message.into(),
130            param: error.param.map(Into::into),
131            code: error.code.map(Into::into),
132            retry_after: error.retry_after.map(Into::into),
133        }
134    }
135}