vtcode_llm/providers/openai/provider/
provider_impl.rs1use super::OpenAIProvider;
2use crate::client::LLMClient;
3use crate::provider::{self, LLMNormalizedStream};
4use crate::types as llm_types;
5use async_trait::async_trait;
6use vtcode_config::constants::models;
7
8#[async_trait]
9impl provider::LLMProvider for OpenAIProvider {
10 fn supports_decisions(&self) -> bool {
11 self.decisions_eligible()
12 }
13
14 async fn decide_choice(
15 &self,
16 request: provider::ChoiceDecisionRequest,
17 ) -> Result<provider::ChoiceDecisionResponse, provider::LLMError> {
18 self.decide_choice_request(request).await
19 }
20 fn name(&self) -> &str {
21 self.provider_key_override.as_deref().unwrap_or("openai")
22 }
23
24 fn supports_streaming(&self) -> bool {
25 true
26 }
27
28 fn supports_non_streaming(&self, model: &str) -> bool {
29 let requested = if model.trim().is_empty() {
30 self.model.as_ref()
31 } else {
32 model
33 };
34
35 !self.is_chatgpt_backend() && !Self::requires_streaming_responses(requested)
36 }
37
38 fn effective_context_size(&self, model: &str) -> usize {
39 if let Some(context_window) = self.context_window_override {
40 return context_window;
41 }
42
43 let requested = if model.trim().is_empty() {
44 self.model.as_ref()
45 } else {
46 model
47 };
48
49 vtcode_config::models::model_catalog_entry(self.name(), requested)
50 .map(|entry| entry.context_window)
51 .filter(|context_window| *context_window > 0)
52 .unwrap_or(128_000)
53 }
54
55 fn supports_reasoning(&self, model: &str) -> bool {
56 let requested = if model.trim().is_empty() {
57 self.model.as_ref()
58 } else {
59 model
60 };
61
62 models::openai::REASONING_MODELS.contains(&requested)
65 || self
66 .model_behavior
67 .as_ref()
68 .and_then(|b| b.model_supports_reasoning)
69 .unwrap_or(false)
70 }
71
72 fn supports_reasoning_effort(&self, model: &str) -> bool {
73 let requested = if model.trim().is_empty() {
74 self.model.as_ref()
75 } else {
76 model
77 };
78
79 models::openai::REASONING_MODELS.iter().any(|candidate| *candidate == requested)
81 || self
82 .model_behavior
83 .as_ref()
84 .and_then(|b| b.model_supports_reasoning_effort)
85 .unwrap_or(false)
86 }
87
88 fn supports_tools(&self, model: &str) -> bool {
89 let requested = if model.trim().is_empty() {
90 self.model.as_ref()
91 } else {
92 model
93 };
94
95 !models::openai::TOOL_UNAVAILABLE_MODELS.contains(&requested)
96 }
97
98 fn supports_responses_compaction(&self, model: &str) -> bool {
99 if self.is_chatgpt_backend() {
100 return false;
101 }
102 let requested = if model.trim().is_empty() {
103 self.model.as_ref()
104 } else {
105 model
106 };
107 !matches!(self.responses_api_state(requested), super::super::types::ResponsesApiState::Disabled)
108 }
109
110 fn supports_native_allowed_tools(&self, model: &str) -> bool {
111 self.supports_responses_allowed_tools(model)
112 }
113
114 fn supports_manual_openai_compaction(&self, model: &str) -> bool {
115 let requested = if model.trim().is_empty() {
116 self.model.as_ref()
117 } else {
118 model
119 };
120 self.supports_manual_openai_compaction_for_model(requested)
121 }
122
123 fn manual_openai_compaction_unavailable_message(&self, model: &str) -> String {
124 self.manual_openai_compaction_unavailable_message_for_model(model)
125 }
126
127 async fn stream(&self, request: provider::LLMRequest) -> Result<provider::LLMStream, provider::LLMError> {
128 self.stream_request(request).await
129 }
130
131 async fn stream_normalized(
132 &self,
133 request: provider::LLMRequest,
134 ) -> Result<LLMNormalizedStream, provider::LLMError> {
135 self.stream_normalized_request(request).await
136 }
137
138 async fn generate(&self, request: provider::LLMRequest) -> Result<provider::LLMResponse, provider::LLMError> {
139 self.generate_request(request).await
140 }
141
142 async fn generate_auxiliary(
143 &self,
144 request: provider::LLMRequest,
145 ) -> Result<provider::LLMResponse, provider::LLMError> {
146 self.generate_request_with_websocket(request, false).await
147 }
148
149 async fn compact_history(
150 &self,
151 model: &str,
152 history: &[provider::Message],
153 ) -> Result<Vec<provider::Message>, provider::LLMError> {
154 if !self.supports_manual_openai_compaction(model) {
155 return Err(provider::LLMError::Provider {
156 message: "OpenAI Responses compaction is not supported for this endpoint/model".to_string(),
157 metadata: None,
158 });
159 }
160
161 self.compact_history_request(model, history).await
162 }
163
164 async fn compact_history_with_options(
165 &self,
166 model: &str,
167 history: &[provider::Message],
168 options: &provider::ResponsesCompactionOptions,
169 ) -> Result<Vec<provider::Message>, provider::LLMError> {
170 let requested = if model.trim().is_empty() {
171 self.model.as_ref()
172 } else {
173 model
174 };
175 if !self.supports_manual_openai_compaction_for_model(requested) {
176 return Err(provider::LLMError::Provider {
177 message: self.manual_openai_compaction_unavailable_message_for_model(requested),
178 metadata: None,
179 });
180 }
181
182 self.compact_history_request_with_options(requested, history, options).await
183 }
184
185 fn supported_models(&self) -> Vec<String> {
186 if let Some(models) = &self.supported_models_override {
187 return models.clone();
188 }
189 if self.provider_key_override.is_some() {
190 return vec![self.model.to_string()];
191 }
192 models::openai::SUPPORTED_MODELS.iter().map(|s| s.to_string()).collect()
193 }
194
195 fn validate_request(&self, request: &provider::LLMRequest) -> Result<(), provider::LLMError> {
196 let supported_models = (!self.is_native_openai_api()).then(|| self.supported_models());
197
198 let display_name = self.provider_display_override.as_deref().unwrap_or("OpenAI");
199 let key = self.provider_key_override.as_deref().unwrap_or("openai");
200 super::super::super::common::validate_request_common(request, display_name, key, supported_models.as_deref())
201 }
202}
203
204#[async_trait]
205impl LLMClient for OpenAIProvider {
206 async fn generate(&mut self, prompt: &str) -> Result<llm_types::LLMResponse, provider::LLMError> {
207 let request = super::super::super::common::make_default_request(prompt, &self.model);
208 let request_model = request.model.to_string();
209 let response = provider::LLMProvider::generate(self, request).await?;
210
211 Ok(llm_types::LLMResponse {
212 content: Some(response.content.unwrap_or_default()),
213 model: request_model,
214 usage: response.usage.map(super::super::super::common::convert_usage_to_llm_types),
215 reasoning: response.reasoning,
216 reasoning_details: response.reasoning_details,
217 request_id: response.request_id,
218 organization_id: response.organization_id,
219 finish_reason: response.finish_reason,
220 tool_calls: response.tool_calls,
221 tool_references: response.tool_references,
222 compaction: None,
223 })
224 }
225
226 fn model_id(&self) -> &str {
227 &self.model
228 }
229}