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 compact_history(
143 &self,
144 model: &str,
145 history: &[provider::Message],
146 ) -> Result<Vec<provider::Message>, provider::LLMError> {
147 if !self.supports_manual_openai_compaction(model) {
148 return Err(provider::LLMError::Provider {
149 message: "OpenAI Responses compaction is not supported for this endpoint/model".to_string(),
150 metadata: None,
151 });
152 }
153
154 self.compact_history_request(model, history).await
155 }
156
157 async fn compact_history_with_options(
158 &self,
159 model: &str,
160 history: &[provider::Message],
161 options: &provider::ResponsesCompactionOptions,
162 ) -> Result<Vec<provider::Message>, provider::LLMError> {
163 let requested = if model.trim().is_empty() {
164 self.model.as_ref()
165 } else {
166 model
167 };
168 if !self.supports_manual_openai_compaction_for_model(requested) {
169 return Err(provider::LLMError::Provider {
170 message: self.manual_openai_compaction_unavailable_message_for_model(requested),
171 metadata: None,
172 });
173 }
174
175 self.compact_history_request_with_options(requested, history, options).await
176 }
177
178 fn supported_models(&self) -> Vec<String> {
179 if let Some(models) = &self.supported_models_override {
180 return models.clone();
181 }
182 if self.provider_key_override.is_some() {
183 return vec![self.model.to_string()];
184 }
185 models::openai::SUPPORTED_MODELS.iter().map(|s| s.to_string()).collect()
186 }
187
188 fn validate_request(&self, request: &provider::LLMRequest) -> Result<(), provider::LLMError> {
189 let supported_models = (!self.is_native_openai_api()).then(|| self.supported_models());
190
191 let display_name = self.provider_display_override.as_deref().unwrap_or("OpenAI");
192 let key = self.provider_key_override.as_deref().unwrap_or("openai");
193 super::super::super::common::validate_request_common(request, display_name, key, supported_models.as_deref())
194 }
195}
196
197#[async_trait]
198impl LLMClient for OpenAIProvider {
199 async fn generate(&mut self, prompt: &str) -> Result<llm_types::LLMResponse, provider::LLMError> {
200 let request = super::super::super::common::make_default_request(prompt, &self.model);
201 let request_model = request.model.to_string();
202 let response = provider::LLMProvider::generate(self, request).await?;
203
204 Ok(llm_types::LLMResponse {
205 content: Some(response.content.unwrap_or_default()),
206 model: request_model,
207 usage: response.usage.map(super::super::super::common::convert_usage_to_llm_types),
208 reasoning: response.reasoning,
209 reasoning_details: response.reasoning_details,
210 request_id: response.request_id,
211 organization_id: response.organization_id,
212 finish_reason: response.finish_reason,
213 tool_calls: response.tool_calls,
214 tool_references: response.tool_references,
215 compaction: None,
216 })
217 }
218
219 fn model_id(&self) -> &str {
220 &self.model
221 }
222}