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#[derive(Debug, Clone, Serialize, Deserialize, Default)]
14pub struct FallbackModel {
15 pub(crate) model: String,
17 #[serde(skip_serializing_if = "Option::is_none")]
19 pub(crate) max_tokens: Option<u32>,
20 #[serde(skip_serializing_if = "Option::is_none")]
22 pub(crate) thinking: Option<AnthropicThinkingConfig>,
23}
24
25#[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 StablePrefix,
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
52#[serde(rename_all = "snake_case")]
53pub enum AnthropicThinkingModeOverride {
54 #[default]
55 Inherit,
56 Disabled,
57 Adaptive,
58 ManualBudget(u32),
59}
60
61#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
62#[serde(rename_all = "snake_case")]
63pub enum AnthropicThinkingDisplayOverride {
64 #[default]
65 Inherit,
66 Summarized,
67 Omitted,
68 Updates,
69}
70
71#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
72#[serde(rename_all = "snake_case")]
73pub enum AnthropicOptionalStringOverride {
74 #[default]
75 Inherit,
76 Omit,
77 Explicit(String),
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
81#[serde(rename_all = "snake_case")]
82pub enum AnthropicOptionalU32Override {
83 #[default]
84 Inherit,
85 Omit,
86 Explicit(u32),
87}
88
89#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
90pub struct AnthropicRequestOverrides {
91 #[serde(default, skip_serializing_if = "Option::is_none")]
93 pub speed: Option<vtcode_config::AnthropicSpeed>,
94 #[serde(default)]
95 pub(crate) thinking_mode: AnthropicThinkingModeOverride,
96 #[serde(default)]
97 pub(crate) thinking_display: AnthropicThinkingDisplayOverride,
98 #[serde(default)]
99 pub(crate) effort: AnthropicOptionalStringOverride,
100 #[serde(default)]
101 pub(crate) task_budget_tokens: AnthropicOptionalU32Override,
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize, Default)]
106
107pub struct LLMRequest {
108 pub messages: Arc<Vec<Message>>,
112 pub system_prompt: Option<Arc<str>>,
116 pub tools: Option<Arc<Vec<ToolDefinition>>>,
117 pub model: String,
118 pub max_tokens: Option<u32>,
119 pub temperature: Option<f32>,
120 pub stream: bool,
121
122 pub output_format: Option<Value>,
125
126 pub tool_choice: Option<ToolChoice>,
129
130 pub parallel_tool_calls: Option<bool>,
132
133 pub parallel_tool_config: Option<Box<ParallelToolConfig>>,
135
136 pub reasoning_effort: Option<ReasoningEffortLevel>,
139
140 pub effort: Option<String>,
145
146 pub verbosity: Option<VerbosityLevel>,
149
150 pub do_sample: Option<bool>,
152 pub top_p: Option<f32>,
153 pub top_k: Option<i32>,
154 pub presence_penalty: Option<f32>,
155 pub frequency_penalty: Option<f32>,
156 pub stop_sequences: Option<Vec<String>>,
157 pub thinking_budget: Option<u32>,
160
161 pub betas: Option<Vec<String>>,
163
164 pub context_management: Option<Value>,
166
167 pub coding_agent_settings: Option<Box<CodingAgentSettings>>,
169
170 pub metadata: Option<Value>,
173
174 pub previous_response_id: Option<String>,
177
178 pub response_store: Option<bool>,
181
182 pub responses_include: Option<Vec<String>>,
185
186 pub service_tier: Option<String>,
189
190 pub prompt_cache_key: Option<String>,
193
194 pub prompt_cache_profile: Option<PromptCacheProfile>,
196
197 pub fallbacks: Option<Vec<FallbackModel>>,
204
205 pub fallback_credit_token: Option<String>,
211
212 pub anthropic_request_overrides: Option<AnthropicRequestOverrides>,
215}
216
217impl LLMRequest {
218 pub fn set_auxiliary_output_budget(
225 &mut self,
226 provider: &(impl super::LLMProvider + ?Sized),
227 visible_output_tokens: u32,
228 ) {
229 let levels = provider.supported_reasoning_efforts(&self.model);
230 let reasoning = provider.supports_reasoning(&self.model) || !levels.is_empty();
231 self.reasoning_effort = levels.contains(&"low").then_some(ReasoningEffortLevel::Low);
232 self.max_tokens = Some(visible_output_tokens.max(1).saturating_add(if reasoning { 2048 } else { 0 }));
233 self.verbosity = Some(VerbosityLevel::Low);
234 if reasoning {
235 self.temperature = None;
236 self.top_p = None;
237 self.top_k = None;
238 self.do_sample = None;
239 self.presence_penalty = None;
240 self.frequency_penalty = None;
241 }
242 }
243}
244
245#[derive(Debug, Clone, Copy, Default, PartialEq)]
249pub struct SamplingOverrides {
250 pub temperature: Option<f32>,
252 pub top_p: Option<f32>,
254 pub top_k: Option<i32>,
256 pub presence_penalty: Option<f32>,
258 pub frequency_penalty: Option<f32>,
260 pub max_tokens: Option<u32>,
262 pub reasoning_effort: Option<ReasoningEffortLevel>,
264 pub suppresses_sampling_with_reasoning: bool,
268 pub profile_aware: bool,
271}
272
273impl SamplingOverrides {
274 pub fn suppresses_sampling(&self, native_match: bool, reasoning_active: bool) -> bool {
279 reasoning_active && (self.suppresses_sampling_with_reasoning || (!self.profile_aware && native_match))
280 }
281}
282
283#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
285pub struct ResponsesCompactionOptions {
286 pub instructions: Option<String>,
288 pub max_output_tokens: Option<u32>,
290 pub reasoning_effort: Option<ReasoningEffortLevel>,
292 pub verbosity: Option<VerbosityLevel>,
294 pub responses_include: Option<Vec<String>>,
296 pub response_store: Option<bool>,
298 pub service_tier: Option<String>,
300 pub prompt_cache_key: Option<String>,
302}
303
304#[derive(Debug, Clone, Serialize, Deserialize, Default)]
306pub struct CodingAgentSettings {
307 pub(crate) long_context_optimization: bool,
312}
313
314#[derive(Debug, Clone, Serialize, Deserialize)]
318#[serde(untagged)]
319#[derive(Default)]
320pub enum ToolChoice {
321 #[default]
324 Auto,
325
326 None,
329
330 Any,
333
334 Specific(SpecificToolChoice),
337
338 AllowedTools(AllowedToolsChoice),
345}
346
347#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
348#[serde(rename_all = "lowercase")]
349pub enum AllowedToolsMode {
350 Auto,
351 Required,
352}
353
354impl AllowedToolsMode {
355 pub fn as_str(self) -> &'static str {
356 match self {
357 Self::Auto => "auto",
358 Self::Required => "required",
359 }
360 }
361
362 fn anthropic_mode(self) -> &'static str {
363 match self {
364 Self::Auto => "auto",
365 Self::Required => "any",
366 }
367 }
368
369 fn gemini_mode(self) -> &'static str {
370 match self {
371 Self::Auto => "auto",
372 Self::Required => "any",
373 }
374 }
375}
376
377#[derive(Debug, Clone, Serialize, Deserialize)]
378pub struct AllowedToolsChoice {
379 pub mode: AllowedToolsMode,
380 pub tools: Vec<String>,
381}
382
383#[derive(Debug, Clone, Serialize, Deserialize)]
385pub struct SpecificToolChoice {
386 #[serde(rename = "type")]
387 pub(crate) tool_type: String, pub(crate) function: SpecificFunctionChoice,
390}
391
392#[derive(Debug, Clone, Serialize, Deserialize)]
394pub struct SpecificFunctionChoice {
395 pub(crate) name: String,
396}
397
398impl ToolChoice {
399 pub fn auto() -> Self {
401 Self::Auto
402 }
403
404 pub fn none() -> Self {
406 Self::None
407 }
408
409 pub(crate) fn any() -> Self {
411 Self::Any
412 }
413
414 pub(crate) fn function(name: String) -> Self {
416 Self::Specific(SpecificToolChoice {
417 tool_type: "function".to_owned(),
418 function: SpecificFunctionChoice { name },
419 })
420 }
421
422 pub fn allowed_tools_auto(tools: Vec<String>) -> Self {
423 Self::AllowedTools(AllowedToolsChoice { mode: AllowedToolsMode::Auto, tools })
424 }
425
426 pub fn allows_parallel_tools(&self) -> bool {
429 match self {
430 Self::Auto => true,
432 Self::Any => true,
434 Self::AllowedTools(choice) => matches!(choice.mode, AllowedToolsMode::Auto),
435 Self::Specific(_) => false,
437 Self::None => false,
439 }
440 }
441
442 pub fn description(&self) -> &'static str {
444 match self {
445 Self::Auto => "Model decides when to use tools (allows parallel)",
446 Self::None => "No tools will be used",
447 Self::Any => "At least one tool must be used (allows parallel)",
448 Self::AllowedTools(_) => "Model decides when to use the currently allowed tool subset",
449 Self::Specific(_) => "Specific tool must be used (no parallel)",
450 }
451 }
452
453 const OPENAI_STYLE_PROVIDERS: &'static [&'static str] = &[
455 "openai",
456 "deepseek",
457 "huggingface",
458 "mistral",
459 "openrouter",
460 "zai",
461 "moonshot",
462 "stepfun",
463 "evolink",
464 "lmstudio",
465 "llamacpp",
466 "meta",
467 ];
468
469 #[inline]
471 pub(crate) fn to_provider_format(&self, provider: &str) -> Value {
472 if Self::OPENAI_STYLE_PROVIDERS.contains(&provider) {
473 return self.to_openai_format();
474 }
475
476 match provider {
477 "anthropic" => self.to_anthropic_format(),
478 "gemini" => self.to_gemini_format(),
479 _ => self.to_openai_format(), }
481 }
482
483 #[inline]
484 pub(crate) fn to_responses_format(&self) -> Value {
485 match self {
486 Self::Specific(choice) => json!({"type": "function", "name": &choice.function.name}),
487 _ => self.to_openai_format(),
488 }
489 }
490
491 #[inline]
492 fn to_openai_format(&self) -> Value {
493 match self {
494 Self::Auto => json!("auto"),
495 Self::None => json!("none"),
496 Self::Any => json!("required"),
497 Self::Specific(choice) => json!(choice),
498 Self::AllowedTools(choice) => json!(choice.mode.as_str()),
499 }
500 }
501
502 #[inline]
503 fn to_anthropic_format(&self) -> Value {
504 match self {
505 Self::Auto => json!({"type": "auto"}),
506 Self::None => json!({"type": "none"}),
507 Self::Any => json!({"type": "any"}),
508 Self::Specific(choice) => json!({"type": "tool", "name": &choice.function.name}),
509 Self::AllowedTools(choice) => json!({"type": choice.mode.anthropic_mode()}),
510 }
511 }
512
513 #[inline]
514 fn to_gemini_format(&self) -> Value {
515 match self {
516 Self::Auto => json!({"mode": "auto"}),
517 Self::None => json!({"mode": "none"}),
518 Self::Any => json!({"mode": "any"}),
519 Self::AllowedTools(choice) => json!({"mode": choice.mode.gemini_mode()}),
520 Self::Specific(choice) => {
521 json!({"mode": "any", "allowed_function_names": [&choice.function.name]})
522 }
523 }
524 }
525}
526
527#[derive(Debug, Clone, Serialize, Deserialize)]
530pub struct ParallelToolConfig {
531 pub(crate) disable_parallel_tool_use: bool,
534
535 pub(crate) max_parallel_tools: Option<usize>,
538
539 pub(crate) encourage_parallel: bool,
541}
542
543impl Default for ParallelToolConfig {
544 fn default() -> Self {
545 Self {
546 disable_parallel_tool_use: false,
547 max_parallel_tools: Some(5), encourage_parallel: true,
549 }
550 }
551}
552
553impl ParallelToolConfig {
554 pub fn anthropic_optimized() -> Self {
556 Self {
557 disable_parallel_tool_use: false,
558 max_parallel_tools: None, encourage_parallel: true,
560 }
561 }
562
563 pub(crate) fn sequential_only() -> Self {
565 Self {
566 disable_parallel_tool_use: true,
567 max_parallel_tools: Some(1),
568 encourage_parallel: false,
569 }
570 }
571}