use vtcode_core::ActivePrimaryAgent;
use vtcode_core::config::{OpenAIPromptCacheKeyMode, PromptCachingConfig};
use vtcode_core::core::agent::features::FeatureSet;
use vtcode_core::llm::provider::{self as uni};
use crate::agent::runloop::unified::incremental_system_prompt::PromptCacheShapingMode;
use crate::agent::runloop::unified::run_loop_context::TurnExecutionSnapshot;
use crate::agent::runloop::unified::session_setup::active_deferred_tool_policy;
use crate::agent::runloop::unified::turn::context::TurnProcessingContext;
const DEFAULT_TURN_TIMEOUT_SECS: u64 = 300;
pub(super) fn is_openai_prompt_cache_enabled(
provider_name: &str,
global_prompt_cache_enabled: bool,
openai_prompt_cache_enabled: bool,
) -> bool {
provider_name.eq_ignore_ascii_case("openai") && global_prompt_cache_enabled && openai_prompt_cache_enabled
}
pub(super) fn resolve_prompt_cache_shaping_mode(
provider_name: &str,
prompt_cache: &PromptCachingConfig,
) -> PromptCacheShapingMode {
debug_assert_eq!(provider_name, provider_name.to_ascii_lowercase());
if !prompt_cache.cache_friendly_prompt_shaping || !prompt_cache.is_provider_enabled(provider_name) {
return PromptCacheShapingMode::Disabled;
}
if matches!(provider_name, "anthropic" | "minimax") {
PromptCacheShapingMode::AnthropicBlockRuntimeContext
} else {
PromptCacheShapingMode::TrailingRuntimeContext
}
}
#[derive(Clone)]
pub(super) struct TurnRequestSnapshot {
pub provider_name: String,
pub planning_active: bool,
pub full_auto: bool,
pub auto_permission: bool,
pub tool_free_recovery: bool,
pub recovery_reason: Option<String>,
pub request_user_input_enabled: bool,
pub context_window_size: usize,
pub turn_timeout_secs: u64,
pub active_model: String,
pub active_primary_agent: ActivePrimaryAgent,
pub openai_prompt_cache_enabled: bool,
pub openai_prompt_cache_key_mode: OpenAIPromptCacheKeyMode,
pub prompt_cache_shaping_mode: PromptCacheShapingMode,
pub capabilities: uni::ProviderCapabilities,
pub execution: TurnExecutionSnapshot,
pub client_local_tool_deferral: bool,
}
pub(super) fn capture_turn_request_snapshot(
ctx: &mut TurnProcessingContext<'_>,
active_model: &str,
tool_free_recovery: bool,
) -> TurnRequestSnapshot {
let prompt_cache_config = &ctx.config.prompt_cache;
let planning_active = ctx.is_planning_active();
let auto_permission = ctx.full_auto && !planning_active;
let provider_name = ctx.provider_client.name().to_ascii_lowercase();
let openai_prompt_cache_enabled = is_openai_prompt_cache_enabled(
&provider_name,
prompt_cache_config.enabled,
prompt_cache_config.providers.openai.enabled,
);
let prompt_cache_shaping_mode = resolve_prompt_cache_shaping_mode(&provider_name, prompt_cache_config);
let request_user_input_enabled = FeatureSet::from_config(ctx.vt_cfg)
.request_user_input_enabled(planning_active, ctx.renderer.supports_inline_ui())
&& !ctx.plan_session.is_interview_denied();
let active_primary_agent = ctx.active_primary_agent.active().clone();
let active_model = resolve_effective_request_model(active_model, &active_primary_agent);
let context_window_size = ctx.provider_client.effective_context_size(&active_model);
let turn_timeout_secs = ctx
.vt_cfg
.map(|cfg| cfg.optimization.agent_execution.max_execution_time_secs)
.unwrap_or(DEFAULT_TURN_TIMEOUT_SECS);
let openai_prompt_cache_key_mode = prompt_cache_config.providers.openai.prompt_cache_key_mode.clone();
let full_auto = ctx.full_auto;
let capabilities = uni::get_cached_capabilities(&**ctx.provider_client, &active_model);
let client_local_tool_deferral =
active_deferred_tool_policy(&*ctx.config, ctx.vt_cfg, &**ctx.provider_client).is_client_local();
TurnRequestSnapshot {
provider_name,
planning_active,
full_auto,
auto_permission,
tool_free_recovery,
recovery_reason: ctx.recovery_reason().map(str::to_string),
request_user_input_enabled,
context_window_size,
turn_timeout_secs,
active_model,
active_primary_agent,
openai_prompt_cache_enabled,
openai_prompt_cache_key_mode,
prompt_cache_shaping_mode,
capabilities,
execution: ctx.harness_state.execution_snapshot(),
client_local_tool_deferral,
}
}
fn resolve_effective_request_model(base_model: &str, active_primary_agent: &ActivePrimaryAgent) -> String {
active_primary_agent
.model
.as_deref()
.map(str::trim)
.filter(|model| !model.is_empty() && !model.eq_ignore_ascii_case("inherit"))
.unwrap_or(base_model)
.to_string()
}
pub(super) fn resolve_effective_reasoning_effort(
cfg: Option<&vtcode_core::config::loader::VTCodeConfig>,
turn_snapshot: &TurnRequestSnapshot,
) -> Option<vtcode_core::config::types::ReasoningEffortLevel> {
if !turn_snapshot.capabilities.reasoning_effort || turn_snapshot.tool_free_recovery {
return None;
}
turn_snapshot
.active_primary_agent
.reasoning_effort
.or_else(|| cfg.map(|cfg| cfg.agent.reasoning_effort))
}