vtcode_llm/provider/request.rs
1use serde::{Deserialize, Serialize};
2use serde_json::{Value, json};
3use std::sync::Arc;
4use vtcode_config::types::{ReasoningEffortLevel, VerbosityLevel};
5
6use super::{Message, ToolDefinition};
7
8/// Fallback model configuration for Anthropic server-side fallback.
9/// The explicit-list form of `fallbacks`, sent with the
10/// `server-side-fallback-2026-06-01` beta header (the `"default"` keyword form
11/// uses `server-side-fallback-2026-07-01`; pairing a header with the other form
12/// is rejected).
13#[derive(Debug, Clone, Serialize, Deserialize, Default)]
14pub struct FallbackModel {
15 /// The model identifier to fall back to (e.g., "claude-opus-5")
16 pub(crate) model: String,
17 /// Optional max_tokens override for this fallback attempt
18 #[serde(skip_serializing_if = "Option::is_none")]
19 pub(crate) max_tokens: Option<u32>,
20 /// Optional thinking configuration override for this fallback attempt
21 #[serde(skip_serializing_if = "Option::is_none")]
22 pub(crate) thinking: Option<AnthropicThinkingConfig>,
23}
24
25/// Anthropic thinking configuration for fallback models.
26#[derive(Debug, Clone, Serialize, Deserialize, Default)]
27#[serde(tag = "type", rename_all = "lowercase")]
28pub enum AnthropicThinkingConfig {
29 #[default]
30 Disabled,
31 Enabled {
32 budget_tokens: u32,
33 #[serde(skip_serializing_if = "Option::is_none")]
34 display: Option<String>,
35 },
36 Adaptive {
37 #[serde(skip_serializing_if = "Option::is_none")]
38 display: Option<String>,
39 },
40}
41
42#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
43#[serde(rename_all = "snake_case")]
44pub enum PromptCacheProfile {
45 BudgetContinuation,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
49#[serde(rename_all = "snake_case")]
50pub enum AnthropicThinkingModeOverride {
51 #[default]
52 Inherit,
53 Disabled,
54 Adaptive,
55 ManualBudget(u32),
56}
57
58#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
59#[serde(rename_all = "snake_case")]
60pub enum AnthropicThinkingDisplayOverride {
61 #[default]
62 Inherit,
63 Summarized,
64 Omitted,
65 Updates,
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
69#[serde(rename_all = "snake_case")]
70pub enum AnthropicOptionalStringOverride {
71 #[default]
72 Inherit,
73 Omit,
74 Explicit(String),
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
78#[serde(rename_all = "snake_case")]
79pub enum AnthropicOptionalU32Override {
80 #[default]
81 Inherit,
82 Omit,
83 Explicit(u32),
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
87pub struct AnthropicRequestOverrides {
88 /// Explicit request speed takes precedence over provider configuration.
89 #[serde(default, skip_serializing_if = "Option::is_none")]
90 pub speed: Option<vtcode_config::AnthropicSpeed>,
91 #[serde(default)]
92 pub(crate) thinking_mode: AnthropicThinkingModeOverride,
93 #[serde(default)]
94 pub(crate) thinking_display: AnthropicThinkingDisplayOverride,
95 #[serde(default)]
96 pub(crate) effort: AnthropicOptionalStringOverride,
97 #[serde(default)]
98 pub(crate) task_budget_tokens: AnthropicOptionalU32Override,
99}
100
101/// Universal LLM request structure
102#[derive(Debug, Clone, Serialize, Deserialize, Default)]
103
104pub struct LLMRequest {
105 /// Conversation history shared via `Arc` so per-turn request construction
106 /// and continuation bookkeeping are O(1) instead of deep-copying the
107 /// entire history (which grows unbounded over a session).
108 pub messages: Arc<Vec<Message>>,
109 /// Shared via `Arc<str>` (single allocation) so per-turn request
110 /// construction can reuse the prompt envelope's `Arc<str>` via a cheap
111 /// refcount bump instead of cloning the full system prompt string.
112 pub system_prompt: Option<Arc<str>>,
113 pub tools: Option<Arc<Vec<ToolDefinition>>>,
114 pub model: String,
115 pub max_tokens: Option<u32>,
116 pub temperature: Option<f32>,
117 pub stream: bool,
118
119 /// Optional structured output JSON schema to request from providers that support it
120 /// For Anthropic this will be sent as `output_config.format = { type: "json_schema", schema: ... }`
121 pub output_format: Option<Value>,
122
123 /// Tool choice configuration based on official API docs
124 /// Supports: "auto" (default), "none", "any", or specific tool selection
125 pub tool_choice: Option<ToolChoice>,
126
127 /// Whether to enable parallel tool calls (OpenAI specific)
128 pub parallel_tool_calls: Option<bool>,
129
130 /// Parallel tool use configuration following Anthropic best practices
131 pub parallel_tool_config: Option<Box<ParallelToolConfig>>,
132
133 /// Reasoning effort level for models that support it (none, minimal, low, medium, high, xhigh)
134 /// Applies to: Claude, GPT-5 family, Gemini, Qwen3, DeepSeek with reasoning capability
135 pub reasoning_effort: Option<ReasoningEffortLevel>,
136
137 /// Effort level for overall token usage (low, medium, high, xhigh, max)
138 /// Applies to: Anthropic adaptive-thinking models such as Claude Opus 4.7
139 /// Controls how many tokens Claude uses when responding, trading off between
140 /// response thoroughness and token efficiency.
141 pub effort: Option<String>,
142
143 /// Verbosity level for output text (low, medium, high)
144 /// Applies to: GPT-5.4-family Responses workflows and other models that support verbosity control
145 pub verbosity: Option<VerbosityLevel>,
146
147 /// Advanced generation parameters
148 pub do_sample: Option<bool>,
149 pub top_p: Option<f32>,
150 pub top_k: Option<i32>,
151 pub presence_penalty: Option<f32>,
152 pub frequency_penalty: Option<f32>,
153 pub stop_sequences: Option<Vec<String>>,
154 /// Optional budget for extended thinking (Anthropic specific)
155 /// Minimum value: 1024
156 pub thinking_budget: Option<u32>,
157
158 /// Optional beta headers for Anthropic (and potentially others)
159 pub betas: Option<Vec<String>>,
160
161 /// Optional provider-specific context management configuration (Anthropic compaction/editing).
162 pub context_management: Option<Value>,
163
164 /// Optional coding agent specific settings
165 pub coding_agent_settings: Option<Box<CodingAgentSettings>>,
166
167 /// Optional turn metadata for git context (remote URLs, commit hash, etc.)
168 /// This is sent as X-Turn-Metadata header to providers that support it
169 pub metadata: Option<Value>,
170
171 /// Optional Responses API continuity pointer for server-side context chaining.
172 /// Used by providers that support stateful response continuation.
173 pub previous_response_id: Option<String>,
174
175 /// Optional Responses API storage flag.
176 /// When set, providers that support `store` pass this through directly.
177 pub response_store: Option<bool>,
178
179 /// Optional Responses API include fields (e.g. reasoning encrypted content).
180 /// Passed through only for providers/APIs that support include selectors.
181 pub responses_include: Option<Vec<String>>,
182
183 /// Optional native OpenAI `service_tier` request parameter.
184 /// Passed through only for native OpenAI endpoints that support service tiers.
185 pub service_tier: Option<String>,
186
187 /// Optional provider routing hint for prompt cache stickiness.
188 /// OpenAI uses this value to improve routing locality for repeated prefixes.
189 pub prompt_cache_key: Option<String>,
190
191 /// Optional request-scoped prompt cache profile for provider-specific TTL overrides.
192 pub prompt_cache_profile: Option<PromptCacheProfile>,
193
194 /// Optional explicit fallback models for Anthropic server-side fallback.
195 /// Overrides `provider.anthropic.fallbacks`; sent with the
196 /// `server-side-fallback-2026-06-01` beta header. Subject to the same
197 /// gates as the config list (first-party endpoint, a model that supports
198 /// server-side fallbacks, no credit-token retry) and the same entry
199 /// validation; an empty list sends nothing.
200 pub fallbacks: Option<Vec<FallbackModel>>,
201
202 /// Optional opaque credit token from a refused request's `stop_details.fallback_credit_token`.
203 /// Echoed on the retry to avoid paying the prompt-cache cost twice.
204 /// Sent with the `fallback-credit-2026-07-01` beta header. The refused request must have
205 /// carried that header or `server-side-fallback-2026-07-01`, which grants the same
206 /// `stop_details` fields.
207 pub fallback_credit_token: Option<String>,
208
209 /// Optional Anthropic-specific request overrides used when request semantics must
210 /// not inherit VT Code's provider defaults, such as the Anthropic compatibility server.
211 pub anthropic_request_overrides: Option<AnthropicRequestOverrides>,
212}
213
214/// Per-model sampling parameter overrides resolved from a custom provider
215/// profile. Every field defaults to `None`, meaning the agent loop's global
216/// configuration value applies unchanged.
217#[derive(Debug, Clone, Copy, Default, PartialEq)]
218pub struct SamplingOverrides {
219 /// Sampling temperature (0.0-2.0).
220 pub temperature: Option<f32>,
221 /// Nucleus sampling mass (0.0-1.0).
222 pub top_p: Option<f32>,
223 /// Top-k token cutoff (>= 0).
224 pub top_k: Option<i32>,
225 /// Presence penalty (-2.0-2.0).
226 pub presence_penalty: Option<f32>,
227 /// Frequency penalty (-2.0-2.0).
228 pub frequency_penalty: Option<f32>,
229 /// Maximum output tokens (> 0).
230 pub max_tokens: Option<u32>,
231 /// Reasoning effort for models that accept it.
232 pub reasoning_effort: Option<ReasoningEffortLevel>,
233 /// Whether this model's wire format rejects sampling parameters while
234 /// reasoning is active (e.g. custom endpoints speaking the Anthropic
235 /// Messages shape).
236 pub suppresses_sampling_with_reasoning: bool,
237 /// Set by providers that resolve overrides from an explicit user profile;
238 /// callers use it to trust these values over name-based heuristics.
239 pub profile_aware: bool,
240}
241
242impl SamplingOverrides {
243 /// Whether sampling parameters must be dropped for this request: either
244 /// the model's wire format rejects sampling while reasoning is active, or
245 /// a built-in Anthropic/MiniMax-shaped backend (`native_match`) enforces
246 /// the same rule outside of profile-aware routing.
247 pub fn suppresses_sampling(&self, native_match: bool, reasoning_active: bool) -> bool {
248 reasoning_active && (self.suppresses_sampling_with_reasoning || (!self.profile_aware && native_match))
249 }
250}
251
252/// Optional overrides for standalone Responses compaction requests.
253#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
254pub struct ResponsesCompactionOptions {
255 /// Optional custom instructions appended to the derived replay instructions.
256 pub instructions: Option<String>,
257 /// Optional output token limit for the compaction response.
258 pub max_output_tokens: Option<u32>,
259 /// Optional reasoning effort override for the compaction pass.
260 pub reasoning_effort: Option<ReasoningEffortLevel>,
261 /// Optional verbosity override for the compaction output text settings.
262 pub verbosity: Option<VerbosityLevel>,
263 /// Optional include selectors override.
264 pub responses_include: Option<Vec<String>>,
265 /// Optional storage override.
266 pub response_store: Option<bool>,
267 /// Optional native OpenAI service tier override.
268 pub service_tier: Option<String>,
269 /// Optional prompt cache routing override.
270 pub prompt_cache_key: Option<String>,
271}
272
273/// Settings to refine model behavior for coding agent tasks
274#[derive(Debug, Clone, Serialize, Deserialize, Default)]
275pub struct CodingAgentSettings {
276 /// Optimize for long context by hoisting the largest user message.
277 /// Hoisting reorders earlier turns, so the Anthropic builder skips it on
278 /// preserved-thinking models (Claude Sonnet 5.5, Claude Opus 5.5, Claude
279 /// Fable 5.1).
280 pub(crate) long_context_optimization: bool,
281}
282
283/// Tool choice configuration that works across different providers
284/// Based on OpenAI, Anthropic, and Gemini API specifications
285/// Follows Anthropic's tool use best practices for optimal performance
286#[derive(Debug, Clone, Serialize, Deserialize)]
287#[serde(untagged)]
288#[derive(Default)]
289pub enum ToolChoice {
290 /// Let the model decide whether to call tools ("auto")
291 /// Default behavior - allows model to use tools when appropriate
292 #[default]
293 Auto,
294
295 /// Force the model to not call any tools ("none")
296 /// Useful for pure conversational responses without tool usage
297 None,
298
299 /// Force the model to call at least one tool ("any")
300 /// Ensures tool usage even when model might prefer direct response
301 Any,
302
303 /// Force the model to call a specific tool
304 /// Useful for directing model to use particular functionality
305 Specific(SpecificToolChoice),
306
307 /// OpenAI Responses native allowed-tools filtering.
308 ///
309 /// This is advisory provider-side filtering only; runtime dispatch remains
310 /// authoritative. Providers without native support should degrade this to
311 /// the equivalent auto/required tool choice while keeping the full
312 /// catalogue visible.
313 AllowedTools(AllowedToolsChoice),
314}
315
316#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
317#[serde(rename_all = "lowercase")]
318pub enum AllowedToolsMode {
319 Auto,
320 Required,
321}
322
323impl AllowedToolsMode {
324 pub fn as_str(self) -> &'static str {
325 match self {
326 Self::Auto => "auto",
327 Self::Required => "required",
328 }
329 }
330
331 fn anthropic_mode(self) -> &'static str {
332 match self {
333 Self::Auto => "auto",
334 Self::Required => "any",
335 }
336 }
337
338 fn gemini_mode(self) -> &'static str {
339 match self {
340 Self::Auto => "auto",
341 Self::Required => "any",
342 }
343 }
344}
345
346#[derive(Debug, Clone, Serialize, Deserialize)]
347pub struct AllowedToolsChoice {
348 pub mode: AllowedToolsMode,
349 pub tools: Vec<String>,
350}
351
352/// Specific tool choice for forcing a particular function call
353#[derive(Debug, Clone, Serialize, Deserialize)]
354pub struct SpecificToolChoice {
355 #[serde(rename = "type")]
356 pub(crate) tool_type: String, // "function"
357
358 pub(crate) function: SpecificFunctionChoice,
359}
360
361/// Specific function choice details
362#[derive(Debug, Clone, Serialize, Deserialize)]
363pub struct SpecificFunctionChoice {
364 pub(crate) name: String,
365}
366
367impl ToolChoice {
368 /// Create auto tool choice (default behavior)
369 pub fn auto() -> Self {
370 Self::Auto
371 }
372
373 /// Create none tool choice (disable tool calling)
374 pub fn none() -> Self {
375 Self::None
376 }
377
378 /// Create any tool choice (force at least one tool call)
379 pub(crate) fn any() -> Self {
380 Self::Any
381 }
382
383 /// Create specific function tool choice
384 pub(crate) fn function(name: String) -> Self {
385 Self::Specific(SpecificToolChoice {
386 tool_type: "function".to_owned(),
387 function: SpecificFunctionChoice { name },
388 })
389 }
390
391 pub fn allowed_tools_auto(tools: Vec<String>) -> Self {
392 Self::AllowedTools(AllowedToolsChoice { mode: AllowedToolsMode::Auto, tools })
393 }
394
395 /// Check if this tool choice allows parallel tool use
396 /// Based on Anthropic's parallel tool use guidelines
397 pub fn allows_parallel_tools(&self) -> bool {
398 match self {
399 // Auto allows parallel tools by default
400 Self::Auto => true,
401 // Any forces at least one tool, may allow parallel
402 Self::Any => true,
403 Self::AllowedTools(choice) => matches!(choice.mode, AllowedToolsMode::Auto),
404 // Specific forces one particular tool, typically no parallel
405 Self::Specific(_) => false,
406 // None disables tools entirely
407 Self::None => false,
408 }
409 }
410
411 /// Get human-readable description of tool choice behavior
412 pub fn description(&self) -> &'static str {
413 match self {
414 Self::Auto => "Model decides when to use tools (allows parallel)",
415 Self::None => "No tools will be used",
416 Self::Any => "At least one tool must be used (allows parallel)",
417 Self::AllowedTools(_) => "Model decides when to use the currently allowed tool subset",
418 Self::Specific(_) => "Specific tool must be used (no parallel)",
419 }
420 }
421
422 /// OpenAI-compatible providers that share the same tool_choice format
423 const OPENAI_STYLE_PROVIDERS: &'static [&'static str] = &[
424 "openai",
425 "deepseek",
426 "huggingface",
427 "mistral",
428 "openrouter",
429 "zai",
430 "moonshot",
431 "stepfun",
432 "evolink",
433 "lmstudio",
434 "llamacpp",
435 "meta",
436 ];
437
438 /// Convert to provider-specific format
439 #[inline]
440 pub(crate) fn to_provider_format(&self, provider: &str) -> Value {
441 if Self::OPENAI_STYLE_PROVIDERS.contains(&provider) {
442 return self.to_openai_format();
443 }
444
445 match provider {
446 "anthropic" => self.to_anthropic_format(),
447 "gemini" => self.to_gemini_format(),
448 _ => self.to_openai_format(), // Default to OpenAI format
449 }
450 }
451
452 #[inline]
453 fn to_openai_format(&self) -> Value {
454 match self {
455 Self::Auto => json!("auto"),
456 Self::None => json!("none"),
457 Self::Any => json!("required"),
458 Self::Specific(choice) => json!(choice),
459 Self::AllowedTools(choice) => json!(choice.mode.as_str()),
460 }
461 }
462
463 #[inline]
464 fn to_anthropic_format(&self) -> Value {
465 match self {
466 Self::Auto => json!({"type": "auto"}),
467 Self::None => json!({"type": "none"}),
468 Self::Any => json!({"type": "any"}),
469 Self::Specific(choice) => json!({"type": "tool", "name": &choice.function.name}),
470 Self::AllowedTools(choice) => json!({"type": choice.mode.anthropic_mode()}),
471 }
472 }
473
474 #[inline]
475 fn to_gemini_format(&self) -> Value {
476 match self {
477 Self::Auto => json!({"mode": "auto"}),
478 Self::None => json!({"mode": "none"}),
479 Self::Any => json!({"mode": "any"}),
480 Self::AllowedTools(choice) => json!({"mode": choice.mode.gemini_mode()}),
481 Self::Specific(choice) => {
482 json!({"mode": "any", "allowed_function_names": [&choice.function.name]})
483 }
484 }
485 }
486}
487
488/// Configuration for parallel tool use behavior
489/// Based on Anthropic's parallel tool use guidelines
490#[derive(Debug, Clone, Serialize, Deserialize)]
491pub struct ParallelToolConfig {
492 /// Whether to disable parallel tool use
493 /// When true, forces sequential tool execution
494 pub(crate) disable_parallel_tool_use: bool,
495
496 /// Maximum number of tools to execute in parallel
497 /// None means no limit (provider default)
498 pub(crate) max_parallel_tools: Option<usize>,
499
500 /// Whether to encourage parallel tool use in prompts
501 pub(crate) encourage_parallel: bool,
502}
503
504impl Default for ParallelToolConfig {
505 fn default() -> Self {
506 Self {
507 disable_parallel_tool_use: false,
508 max_parallel_tools: Some(5), // Reasonable default
509 encourage_parallel: true,
510 }
511 }
512}
513
514impl ParallelToolConfig {
515 /// Create configuration optimized for Anthropic models
516 pub fn anthropic_optimized() -> Self {
517 Self {
518 disable_parallel_tool_use: false,
519 max_parallel_tools: None, // Let Anthropic decide
520 encourage_parallel: true,
521 }
522 }
523
524 /// Create configuration for sequential tool use
525 pub(crate) fn sequential_only() -> Self {
526 Self {
527 disable_parallel_tool_use: true,
528 max_parallel_tools: Some(1),
529 encourage_parallel: false,
530 }
531 }
532}