1use std::{collections::BTreeMap, sync::Arc};
4
5use async_trait::async_trait;
6use serde_json::json;
7use starweaver_oauth::OAuthTokenSource;
8
9use crate::{
10 ModelAdapter, ModelError, ModelProfile, ModelRequestContext, ModelRequestParameters,
11 ModelResponse, ModelResponseEventStream, ModelResponseStreamEvent, ModelRunSession,
12 ModelSettings, ProtocolFamily,
13 oauth::OAuthBearerHttpClient,
14 providers::client::ProtocolModelClient,
15 transport::{DynHttpClient, HttpModelConfig},
16};
17
18pub struct CodexOAuthResponsesModel {
20 inner: ProtocolModelClient,
21}
22
23impl CodexOAuthResponsesModel {
24 pub fn new(
30 model_name: impl Into<String>,
31 http_config: HttpModelConfig,
32 token_source: Arc<dyn OAuthTokenSource>,
33 extra_headers: BTreeMap<String, String>,
34 ) -> Result<Self, ModelError> {
35 Self::new_with_profile(
36 model_name,
37 http_config,
38 token_source,
39 extra_headers,
40 codex_model_profile(),
41 )
42 }
43
44 pub fn new_with_profile(
50 model_name: impl Into<String>,
51 http_config: HttpModelConfig,
52 token_source: Arc<dyn OAuthTokenSource>,
53 extra_headers: BTreeMap<String, String>,
54 profile: ModelProfile,
55 ) -> Result<Self, ModelError> {
56 let client =
57 OAuthBearerHttpClient::with_default_http_client(token_source, "codex", extra_headers)?;
58 Ok(Self {
59 inner: ProtocolModelClient::new(
60 "codex",
61 model_name,
62 profile,
63 http_config,
64 Arc::new(client),
65 ),
66 })
67 }
68
69 pub fn with_http_client(
75 model_name: impl Into<String>,
76 http_config: HttpModelConfig,
77 token_source: Arc<dyn OAuthTokenSource>,
78 extra_headers: BTreeMap<String, String>,
79 inner_http_client: DynHttpClient,
80 ) -> Result<Self, ModelError> {
81 Self::with_http_client_and_profile(
82 model_name,
83 http_config,
84 token_source,
85 extra_headers,
86 inner_http_client,
87 codex_model_profile(),
88 )
89 }
90
91 pub fn with_http_client_and_profile(
97 model_name: impl Into<String>,
98 http_config: HttpModelConfig,
99 token_source: Arc<dyn OAuthTokenSource>,
100 extra_headers: BTreeMap<String, String>,
101 inner_http_client: DynHttpClient,
102 profile: ModelProfile,
103 ) -> Result<Self, ModelError> {
104 let client =
105 OAuthBearerHttpClient::new(inner_http_client, token_source, "codex", extra_headers)?;
106 Ok(Self {
107 inner: ProtocolModelClient::new(
108 "codex",
109 model_name,
110 profile,
111 http_config,
112 Arc::new(client),
113 ),
114 })
115 }
116}
117
118#[async_trait]
119impl ModelAdapter for CodexOAuthResponsesModel {
120 fn model_name(&self) -> &str {
121 self.inner.model_name()
122 }
123
124 fn provider_name(&self) -> Option<&str> {
125 Some("codex")
126 }
127
128 fn profile(&self) -> &ModelProfile {
129 self.inner.profile()
130 }
131
132 fn default_settings(&self) -> Option<&ModelSettings> {
133 self.inner.default_settings()
134 }
135
136 fn start_run_session(&self) -> Box<dyn ModelRunSession + '_> {
137 self.inner.start_run_session()
138 }
139
140 async fn request(
141 &self,
142 _messages: Vec<crate::ModelMessage>,
143 _settings: Option<ModelSettings>,
144 _params: ModelRequestParameters,
145 _context: ModelRequestContext,
146 ) -> Result<ModelResponse, ModelError> {
147 Err(ModelError::Transport(
148 "Codex OAuth Responses API requires streaming. Use request_stream_incremental or a streaming agent run."
149 .to_string(),
150 ))
151 }
152
153 async fn request_stream(
154 &self,
155 messages: Vec<crate::ModelMessage>,
156 settings: Option<ModelSettings>,
157 params: ModelRequestParameters,
158 context: ModelRequestContext,
159 ) -> Result<Vec<ModelResponseStreamEvent>, ModelError> {
160 self.inner
161 .request_stream(messages, settings, params, context)
162 .await
163 }
164
165 async fn request_stream_incremental(
166 &self,
167 messages: Vec<crate::ModelMessage>,
168 settings: Option<ModelSettings>,
169 params: ModelRequestParameters,
170 context: ModelRequestContext,
171 ) -> Result<ModelResponseEventStream, ModelError> {
172 self.inner
173 .request_stream_incremental(messages, settings, params, context)
174 .await
175 }
176}
177
178#[must_use]
180pub fn codex_model_profile() -> ModelProfile {
181 let mut profile = ModelProfile::for_protocol(ProtocolFamily::OpenAiResponses);
182 profile.supports_tools = true;
183 profile.supports_json_schema_output = true;
184 profile.supports_thinking = true;
185 profile.thinking_always_enabled = true;
186 profile
187}
188
189pub fn build_codex_model(
195 model_name: impl Into<String>,
196 token_source: Arc<dyn OAuthTokenSource>,
197 mut http_config: HttpModelConfig,
198 extra_headers: BTreeMap<String, String>,
199) -> Result<CodexOAuthResponsesModel, ModelError> {
200 http_config
201 .metadata
202 .insert("oauth_provider".to_string(), json!("codex"));
203 CodexOAuthResponsesModel::new(model_name, http_config, token_source, extra_headers)
204}
205
206pub fn build_codex_model_with_profile(
212 model_name: impl Into<String>,
213 token_source: Arc<dyn OAuthTokenSource>,
214 mut http_config: HttpModelConfig,
215 extra_headers: BTreeMap<String, String>,
216 profile: ModelProfile,
217) -> Result<CodexOAuthResponsesModel, ModelError> {
218 http_config
219 .metadata
220 .insert("oauth_provider".to_string(), json!("codex"));
221 CodexOAuthResponsesModel::new_with_profile(
222 model_name,
223 http_config,
224 token_source,
225 extra_headers,
226 profile,
227 )
228}