claude_codex/openai_compat/
mod.rs1pub 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}