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