use crate::constants::{defaults, llm_generation, prompt_budget};
use crate::types::{
ReasoningEffortLevel, SystemPromptMode, ToolDocumentationMode, UiSurfacePreference,
VerbosityLevel,
};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
const DEFAULT_CHECKPOINTS_ENABLED: bool = true;
const DEFAULT_MAX_SNAPSHOTS: usize = 50;
const DEFAULT_MAX_AGE_DAYS: u64 = 30;
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct AgentConfig {
#[serde(default = "default_provider")]
pub provider: String,
#[serde(default = "default_api_key_env")]
pub api_key_env: String,
#[serde(default = "default_model")]
pub default_model: String,
#[serde(default = "default_theme")]
pub theme: String,
#[serde(default)]
pub system_prompt_mode: SystemPromptMode,
#[serde(default)]
pub tool_documentation_mode: ToolDocumentationMode,
#[serde(default = "default_enable_split_tool_results")]
pub enable_split_tool_results: bool,
#[serde(default = "default_todo_planning_mode")]
pub todo_planning_mode: bool,
#[serde(default)]
pub ui_surface: UiSurfacePreference,
#[serde(default = "default_max_conversation_turns")]
pub max_conversation_turns: usize,
#[serde(default = "default_reasoning_effort")]
pub reasoning_effort: ReasoningEffortLevel,
#[serde(default = "default_verbosity")]
pub verbosity: VerbosityLevel,
#[serde(default = "default_temperature")]
pub temperature: f32,
#[serde(default = "default_refine_temperature")]
pub refine_temperature: f32,
#[serde(default = "default_enable_self_review")]
pub enable_self_review: bool,
#[serde(default = "default_max_review_passes")]
pub max_review_passes: usize,
#[serde(default = "default_refine_prompts_enabled")]
pub refine_prompts_enabled: bool,
#[serde(default = "default_refine_max_passes")]
pub refine_prompts_max_passes: usize,
#[serde(default)]
pub refine_prompts_model: String,
#[serde(default)]
pub small_model: AgentSmallModelConfig,
#[serde(default)]
pub prompt_suggestions: AgentPromptSuggestionsConfig,
#[serde(default)]
pub onboarding: AgentOnboardingConfig,
#[serde(default = "default_project_doc_max_bytes")]
pub project_doc_max_bytes: usize,
#[serde(default)]
pub project_doc_fallback_filenames: Vec<String>,
#[serde(
default = "default_instruction_max_bytes",
alias = "rule_doc_max_bytes"
)]
pub instruction_max_bytes: usize,
#[serde(default, alias = "instruction_paths", alias = "instructions")]
pub instruction_files: Vec<String>,
#[serde(default)]
pub instruction_excludes: Vec<String>,
#[serde(default = "default_instruction_import_max_depth")]
pub instruction_import_max_depth: usize,
#[serde(default)]
pub persistent_memory: PersistentMemoryConfig,
#[serde(default, skip_serializing)]
pub custom_api_keys: BTreeMap<String, String>,
#[serde(default)]
pub credential_storage_mode: crate::auth::AuthCredentialsStoreMode,
#[serde(default)]
pub checkpointing: AgentCheckpointingConfig,
#[serde(default)]
pub vibe_coding: AgentVibeCodingConfig,
#[serde(default = "default_max_task_retries")]
pub max_task_retries: u32,
#[serde(default)]
pub harness: AgentHarnessConfig,
#[serde(default)]
pub codex_app_server: AgentCodexAppServerConfig,
#[serde(default = "default_include_temporal_context")]
pub include_temporal_context: bool,
#[serde(default)]
pub temporal_context_use_utc: bool,
#[serde(default = "default_include_working_directory")]
pub include_working_directory: bool,
#[serde(default)]
pub include_structured_reasoning_tags: Option<bool>,
#[serde(default)]
pub user_instructions: Option<String>,
#[serde(default = "default_require_plan_confirmation")]
pub require_plan_confirmation: bool,
#[serde(default)]
pub circuit_breaker: CircuitBreakerConfig,
#[serde(default)]
pub open_responses: OpenResponsesConfig,
}
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "schema", schemars(rename_all = "snake_case"))]
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ContinuationPolicy {
Off,
ExecOnly,
#[default]
All,
}
impl ContinuationPolicy {
pub fn as_str(&self) -> &'static str {
match self {
Self::Off => "off",
Self::ExecOnly => "exec_only",
Self::All => "all",
}
}
pub fn parse(value: &str) -> Option<Self> {
let normalized = value.trim();
if normalized.eq_ignore_ascii_case("off") {
Some(Self::Off)
} else if normalized.eq_ignore_ascii_case("exec_only")
|| normalized.eq_ignore_ascii_case("exec-only")
{
Some(Self::ExecOnly)
} else if normalized.eq_ignore_ascii_case("all") {
Some(Self::All)
} else {
None
}
}
}
impl<'de> Deserialize<'de> for ContinuationPolicy {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let raw = String::deserialize(deserializer)?;
Ok(Self::parse(&raw).unwrap_or_default())
}
}
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "schema", schemars(rename_all = "snake_case"))]
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum HarnessOrchestrationMode {
#[default]
Single,
PlanBuildEvaluate,
}
impl HarnessOrchestrationMode {
pub fn as_str(&self) -> &'static str {
match self {
Self::Single => "single",
Self::PlanBuildEvaluate => "plan_build_evaluate",
}
}
pub fn parse(value: &str) -> Option<Self> {
let normalized = value.trim();
if normalized.eq_ignore_ascii_case("single") {
Some(Self::Single)
} else if normalized.eq_ignore_ascii_case("plan_build_evaluate")
|| normalized.eq_ignore_ascii_case("plan-build-evaluate")
|| normalized.eq_ignore_ascii_case("planner_generator_evaluator")
|| normalized.eq_ignore_ascii_case("planner-generator-evaluator")
{
Some(Self::PlanBuildEvaluate)
} else {
None
}
}
}
impl<'de> Deserialize<'de> for HarnessOrchestrationMode {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let raw = String::deserialize(deserializer)?;
Ok(Self::parse(&raw).unwrap_or_default())
}
}
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct AgentHarnessConfig {
#[serde(default = "default_harness_max_tool_calls_per_turn")]
pub max_tool_calls_per_turn: usize,
#[serde(default = "default_harness_max_tool_wall_clock_secs")]
pub max_tool_wall_clock_secs: u64,
#[serde(default = "default_harness_max_tool_retries")]
pub max_tool_retries: u32,
#[serde(default = "default_harness_auto_compaction_enabled")]
pub auto_compaction_enabled: bool,
#[serde(default)]
pub auto_compaction_threshold_tokens: Option<u64>,
#[serde(default)]
pub tool_result_clearing: ToolResultClearingConfig,
#[serde(default)]
pub max_budget_usd: Option<f64>,
#[serde(default)]
pub continuation_policy: ContinuationPolicy,
#[serde(default)]
pub event_log_path: Option<String>,
#[serde(default)]
pub orchestration_mode: HarnessOrchestrationMode,
#[serde(default = "default_harness_max_revision_rounds")]
pub max_revision_rounds: usize,
}
impl Default for AgentHarnessConfig {
fn default() -> Self {
Self {
max_tool_calls_per_turn: default_harness_max_tool_calls_per_turn(),
max_tool_wall_clock_secs: default_harness_max_tool_wall_clock_secs(),
max_tool_retries: default_harness_max_tool_retries(),
auto_compaction_enabled: default_harness_auto_compaction_enabled(),
auto_compaction_threshold_tokens: None,
tool_result_clearing: ToolResultClearingConfig::default(),
max_budget_usd: None,
continuation_policy: ContinuationPolicy::default(),
event_log_path: None,
orchestration_mode: HarnessOrchestrationMode::default(),
max_revision_rounds: default_harness_max_revision_rounds(),
}
}
}
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ToolResultClearingConfig {
#[serde(default = "default_tool_result_clearing_enabled")]
pub enabled: bool,
#[serde(default = "default_tool_result_clearing_trigger_tokens")]
pub trigger_tokens: u64,
#[serde(default = "default_tool_result_clearing_keep_tool_uses")]
pub keep_tool_uses: u32,
#[serde(default = "default_tool_result_clearing_clear_at_least_tokens")]
pub clear_at_least_tokens: u64,
#[serde(default)]
pub clear_tool_inputs: bool,
}
impl Default for ToolResultClearingConfig {
fn default() -> Self {
Self {
enabled: default_tool_result_clearing_enabled(),
trigger_tokens: default_tool_result_clearing_trigger_tokens(),
keep_tool_uses: default_tool_result_clearing_keep_tool_uses(),
clear_at_least_tokens: default_tool_result_clearing_clear_at_least_tokens(),
clear_tool_inputs: false,
}
}
}
impl ToolResultClearingConfig {
pub fn validate(&self) -> Result<(), String> {
if self.trigger_tokens == 0 {
return Err("tool_result_clearing.trigger_tokens must be greater than 0".to_string());
}
if self.keep_tool_uses == 0 {
return Err("tool_result_clearing.keep_tool_uses must be greater than 0".to_string());
}
if self.clear_at_least_tokens == 0 {
return Err(
"tool_result_clearing.clear_at_least_tokens must be greater than 0".to_string(),
);
}
Ok(())
}
}
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct AgentCodexAppServerConfig {
#[serde(default = "default_codex_app_server_command")]
pub command: String,
#[serde(default = "default_codex_app_server_args")]
pub args: Vec<String>,
#[serde(default = "default_codex_app_server_startup_timeout_secs")]
pub startup_timeout_secs: u64,
#[serde(default = "default_codex_app_server_experimental_features")]
pub experimental_features: bool,
}
impl Default for AgentCodexAppServerConfig {
fn default() -> Self {
Self {
command: default_codex_app_server_command(),
args: default_codex_app_server_args(),
startup_timeout_secs: default_codex_app_server_startup_timeout_secs(),
experimental_features: default_codex_app_server_experimental_features(),
}
}
}
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct CircuitBreakerConfig {
#[serde(default = "default_circuit_breaker_enabled")]
pub enabled: bool,
#[serde(default = "default_failure_threshold")]
pub failure_threshold: u32,
#[serde(default = "default_pause_on_open")]
pub pause_on_open: bool,
#[serde(default = "default_max_open_circuits")]
pub max_open_circuits: usize,
#[serde(default = "default_recovery_cooldown")]
pub recovery_cooldown: u64,
}
impl Default for CircuitBreakerConfig {
fn default() -> Self {
Self {
enabled: default_circuit_breaker_enabled(),
failure_threshold: default_failure_threshold(),
pause_on_open: default_pause_on_open(),
max_open_circuits: default_max_open_circuits(),
recovery_cooldown: default_recovery_cooldown(),
}
}
}
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct OpenResponsesConfig {
#[serde(default)]
pub enabled: bool,
#[serde(default = "default_open_responses_emit_events")]
pub emit_events: bool,
#[serde(default = "default_open_responses_include_extensions")]
pub include_extensions: bool,
#[serde(default = "default_open_responses_map_tool_calls")]
pub map_tool_calls: bool,
#[serde(default = "default_open_responses_include_reasoning")]
pub include_reasoning: bool,
}
impl Default for OpenResponsesConfig {
fn default() -> Self {
Self {
enabled: false, emit_events: default_open_responses_emit_events(),
include_extensions: default_open_responses_include_extensions(),
map_tool_calls: default_open_responses_map_tool_calls(),
include_reasoning: default_open_responses_include_reasoning(),
}
}
}
#[inline]
const fn default_open_responses_emit_events() -> bool {
true }
#[inline]
const fn default_open_responses_include_extensions() -> bool {
true }
#[inline]
const fn default_open_responses_map_tool_calls() -> bool {
true }
#[inline]
const fn default_open_responses_include_reasoning() -> bool {
true }
#[inline]
fn default_codex_app_server_command() -> String {
"codex".to_string()
}
#[inline]
fn default_codex_app_server_args() -> Vec<String> {
vec!["app-server".to_string()]
}
#[inline]
const fn default_codex_app_server_startup_timeout_secs() -> u64 {
10
}
#[inline]
const fn default_codex_app_server_experimental_features() -> bool {
false
}
impl Default for AgentConfig {
fn default() -> Self {
Self {
provider: default_provider(),
api_key_env: default_api_key_env(),
default_model: default_model(),
theme: default_theme(),
system_prompt_mode: SystemPromptMode::default(),
tool_documentation_mode: ToolDocumentationMode::default(),
enable_split_tool_results: default_enable_split_tool_results(),
todo_planning_mode: default_todo_planning_mode(),
ui_surface: UiSurfacePreference::default(),
max_conversation_turns: default_max_conversation_turns(),
reasoning_effort: default_reasoning_effort(),
verbosity: default_verbosity(),
temperature: default_temperature(),
refine_temperature: default_refine_temperature(),
enable_self_review: default_enable_self_review(),
max_review_passes: default_max_review_passes(),
refine_prompts_enabled: default_refine_prompts_enabled(),
refine_prompts_max_passes: default_refine_max_passes(),
refine_prompts_model: String::new(),
small_model: AgentSmallModelConfig::default(),
prompt_suggestions: AgentPromptSuggestionsConfig::default(),
onboarding: AgentOnboardingConfig::default(),
project_doc_max_bytes: default_project_doc_max_bytes(),
project_doc_fallback_filenames: Vec::new(),
instruction_max_bytes: default_instruction_max_bytes(),
instruction_files: Vec::new(),
instruction_excludes: Vec::new(),
instruction_import_max_depth: default_instruction_import_max_depth(),
persistent_memory: PersistentMemoryConfig::default(),
custom_api_keys: BTreeMap::new(),
credential_storage_mode: crate::auth::AuthCredentialsStoreMode::default(),
checkpointing: AgentCheckpointingConfig::default(),
vibe_coding: AgentVibeCodingConfig::default(),
max_task_retries: default_max_task_retries(),
harness: AgentHarnessConfig::default(),
codex_app_server: AgentCodexAppServerConfig::default(),
include_temporal_context: default_include_temporal_context(),
temporal_context_use_utc: false, include_working_directory: default_include_working_directory(),
include_structured_reasoning_tags: None,
user_instructions: None,
require_plan_confirmation: default_require_plan_confirmation(),
circuit_breaker: CircuitBreakerConfig::default(),
open_responses: OpenResponsesConfig::default(),
}
}
}
impl AgentConfig {
pub fn should_include_structured_reasoning_tags(&self) -> bool {
self.include_structured_reasoning_tags.unwrap_or(matches!(
self.system_prompt_mode,
SystemPromptMode::Specialized
))
}
pub fn validate_llm_params(&self) -> Result<(), String> {
if !(0.0..=1.0).contains(&self.temperature) {
return Err(format!(
"temperature must be between 0.0 and 1.0, got {}",
self.temperature
));
}
if !(0.0..=1.0).contains(&self.refine_temperature) {
return Err(format!(
"refine_temperature must be between 0.0 and 1.0, got {}",
self.refine_temperature
));
}
if self.instruction_import_max_depth == 0 {
return Err("instruction_import_max_depth must be greater than 0".to_string());
}
self.persistent_memory.validate()?;
self.harness.tool_result_clearing.validate()?;
Ok(())
}
}
#[inline]
fn default_provider() -> String {
defaults::DEFAULT_PROVIDER.into()
}
#[inline]
fn default_api_key_env() -> String {
defaults::DEFAULT_API_KEY_ENV.into()
}
#[inline]
fn default_model() -> String {
defaults::DEFAULT_MODEL.into()
}
#[inline]
fn default_theme() -> String {
defaults::DEFAULT_THEME.into()
}
#[inline]
const fn default_todo_planning_mode() -> bool {
true
}
#[inline]
const fn default_enable_split_tool_results() -> bool {
true }
#[inline]
const fn default_max_conversation_turns() -> usize {
defaults::DEFAULT_MAX_CONVERSATION_TURNS
}
#[inline]
fn default_reasoning_effort() -> ReasoningEffortLevel {
ReasoningEffortLevel::None
}
#[inline]
fn default_verbosity() -> VerbosityLevel {
VerbosityLevel::default()
}
#[inline]
const fn default_temperature() -> f32 {
llm_generation::DEFAULT_TEMPERATURE
}
#[inline]
const fn default_refine_temperature() -> f32 {
llm_generation::DEFAULT_REFINE_TEMPERATURE
}
#[inline]
const fn default_enable_self_review() -> bool {
false
}
#[inline]
const fn default_max_review_passes() -> usize {
1
}
#[inline]
const fn default_refine_prompts_enabled() -> bool {
false
}
#[inline]
const fn default_refine_max_passes() -> usize {
1
}
#[inline]
const fn default_project_doc_max_bytes() -> usize {
prompt_budget::DEFAULT_MAX_BYTES
}
#[inline]
const fn default_instruction_max_bytes() -> usize {
prompt_budget::DEFAULT_MAX_BYTES
}
#[inline]
const fn default_instruction_import_max_depth() -> usize {
5
}
#[inline]
const fn default_max_task_retries() -> u32 {
2 }
#[inline]
const fn default_harness_max_tool_calls_per_turn() -> usize {
defaults::DEFAULT_MAX_TOOL_CALLS_PER_TURN
}
#[inline]
const fn default_harness_max_tool_wall_clock_secs() -> u64 {
defaults::DEFAULT_MAX_TOOL_WALL_CLOCK_SECS
}
#[inline]
const fn default_harness_max_tool_retries() -> u32 {
defaults::DEFAULT_MAX_TOOL_RETRIES
}
#[inline]
const fn default_harness_auto_compaction_enabled() -> bool {
false
}
#[inline]
const fn default_tool_result_clearing_enabled() -> bool {
false
}
#[inline]
const fn default_tool_result_clearing_trigger_tokens() -> u64 {
100_000
}
#[inline]
const fn default_tool_result_clearing_keep_tool_uses() -> u32 {
3
}
#[inline]
const fn default_tool_result_clearing_clear_at_least_tokens() -> u64 {
30_000
}
#[inline]
const fn default_harness_max_revision_rounds() -> usize {
2
}
#[inline]
const fn default_include_temporal_context() -> bool {
true }
#[inline]
const fn default_include_working_directory() -> bool {
true }
#[inline]
const fn default_require_plan_confirmation() -> bool {
true }
#[inline]
const fn default_circuit_breaker_enabled() -> bool {
true }
#[inline]
const fn default_failure_threshold() -> u32 {
7 }
#[inline]
const fn default_pause_on_open() -> bool {
true }
#[inline]
const fn default_max_open_circuits() -> usize {
3 }
#[inline]
const fn default_recovery_cooldown() -> u64 {
60 }
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct AgentCheckpointingConfig {
#[serde(default = "default_checkpointing_enabled")]
pub enabled: bool,
#[serde(default)]
pub storage_dir: Option<String>,
#[serde(default = "default_checkpointing_max_snapshots")]
pub max_snapshots: usize,
#[serde(default = "default_checkpointing_max_age_days")]
pub max_age_days: Option<u64>,
}
impl Default for AgentCheckpointingConfig {
fn default() -> Self {
Self {
enabled: default_checkpointing_enabled(),
storage_dir: None,
max_snapshots: default_checkpointing_max_snapshots(),
max_age_days: default_checkpointing_max_age_days(),
}
}
}
#[inline]
const fn default_checkpointing_enabled() -> bool {
DEFAULT_CHECKPOINTS_ENABLED
}
#[inline]
const fn default_checkpointing_max_snapshots() -> usize {
DEFAULT_MAX_SNAPSHOTS
}
#[inline]
const fn default_checkpointing_max_age_days() -> Option<u64> {
Some(DEFAULT_MAX_AGE_DAYS)
}
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct PersistentMemoryConfig {
#[serde(default = "default_persistent_memory_enabled")]
pub enabled: bool,
#[serde(default = "default_persistent_memory_auto_write")]
pub auto_write: bool,
#[serde(default)]
pub directory_override: Option<String>,
#[serde(default = "default_persistent_memory_startup_line_limit")]
pub startup_line_limit: usize,
#[serde(default = "default_persistent_memory_startup_byte_limit")]
pub startup_byte_limit: usize,
}
impl Default for PersistentMemoryConfig {
fn default() -> Self {
Self {
enabled: default_persistent_memory_enabled(),
auto_write: default_persistent_memory_auto_write(),
directory_override: None,
startup_line_limit: default_persistent_memory_startup_line_limit(),
startup_byte_limit: default_persistent_memory_startup_byte_limit(),
}
}
}
impl PersistentMemoryConfig {
pub fn validate(&self) -> Result<(), String> {
if self.startup_line_limit == 0 {
return Err("persistent_memory.startup_line_limit must be greater than 0".to_string());
}
if self.startup_byte_limit == 0 {
return Err("persistent_memory.startup_byte_limit must be greater than 0".to_string());
}
Ok(())
}
}
#[inline]
const fn default_persistent_memory_enabled() -> bool {
false
}
#[inline]
const fn default_persistent_memory_auto_write() -> bool {
true
}
#[inline]
const fn default_persistent_memory_startup_line_limit() -> usize {
200
}
#[inline]
const fn default_persistent_memory_startup_byte_limit() -> usize {
25 * 1024
}
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct AgentOnboardingConfig {
#[serde(default = "default_onboarding_enabled")]
pub enabled: bool,
#[serde(default = "default_intro_text")]
pub intro_text: String,
#[serde(default = "default_show_project_overview")]
pub include_project_overview: bool,
#[serde(default = "default_show_language_summary")]
pub include_language_summary: bool,
#[serde(default = "default_show_guideline_highlights")]
pub include_guideline_highlights: bool,
#[serde(default = "default_show_usage_tips_in_welcome")]
pub include_usage_tips_in_welcome: bool,
#[serde(default = "default_show_recommended_actions_in_welcome")]
pub include_recommended_actions_in_welcome: bool,
#[serde(default = "default_guideline_highlight_limit")]
pub guideline_highlight_limit: usize,
#[serde(default = "default_usage_tips")]
pub usage_tips: Vec<String>,
#[serde(default = "default_recommended_actions")]
pub recommended_actions: Vec<String>,
#[serde(default)]
pub chat_placeholder: Option<String>,
}
impl Default for AgentOnboardingConfig {
fn default() -> Self {
Self {
enabled: default_onboarding_enabled(),
intro_text: default_intro_text(),
include_project_overview: default_show_project_overview(),
include_language_summary: default_show_language_summary(),
include_guideline_highlights: default_show_guideline_highlights(),
include_usage_tips_in_welcome: default_show_usage_tips_in_welcome(),
include_recommended_actions_in_welcome: default_show_recommended_actions_in_welcome(),
guideline_highlight_limit: default_guideline_highlight_limit(),
usage_tips: default_usage_tips(),
recommended_actions: default_recommended_actions(),
chat_placeholder: None,
}
}
}
#[inline]
const fn default_onboarding_enabled() -> bool {
true
}
const DEFAULT_INTRO_TEXT: &str =
"Let's get oriented. I preloaded workspace context so we can move fast.";
#[inline]
fn default_intro_text() -> String {
DEFAULT_INTRO_TEXT.into()
}
#[inline]
const fn default_show_project_overview() -> bool {
true
}
#[inline]
const fn default_show_language_summary() -> bool {
false
}
#[inline]
const fn default_show_guideline_highlights() -> bool {
true
}
#[inline]
const fn default_show_usage_tips_in_welcome() -> bool {
false
}
#[inline]
const fn default_show_recommended_actions_in_welcome() -> bool {
false
}
#[inline]
const fn default_guideline_highlight_limit() -> usize {
3
}
const DEFAULT_USAGE_TIPS: &[&str] = &[
"Describe your current coding goal or ask for a quick status overview.",
"Reference AGENTS.md guidelines when proposing changes.",
"Prefer asking for targeted file reads or diffs before editing.",
];
const DEFAULT_RECOMMENDED_ACTIONS: &[&str] = &[
"Review the highlighted guidelines and share the task you want to tackle.",
"Ask for a workspace tour if you need more context.",
];
fn default_usage_tips() -> Vec<String> {
DEFAULT_USAGE_TIPS.iter().map(|s| (*s).into()).collect()
}
fn default_recommended_actions() -> Vec<String> {
DEFAULT_RECOMMENDED_ACTIONS
.iter()
.map(|s| (*s).into())
.collect()
}
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct AgentSmallModelConfig {
#[serde(default = "default_small_model_enabled")]
pub enabled: bool,
#[serde(default)]
pub model: String,
#[serde(default = "default_small_model_temperature")]
pub temperature: f32,
#[serde(default = "default_small_model_for_large_reads")]
pub use_for_large_reads: bool,
#[serde(default = "default_small_model_for_web_summary")]
pub use_for_web_summary: bool,
#[serde(default = "default_small_model_for_git_history")]
pub use_for_git_history: bool,
#[serde(default = "default_small_model_for_memory")]
pub use_for_memory: bool,
}
impl Default for AgentSmallModelConfig {
fn default() -> Self {
Self {
enabled: default_small_model_enabled(),
model: String::new(),
temperature: default_small_model_temperature(),
use_for_large_reads: default_small_model_for_large_reads(),
use_for_web_summary: default_small_model_for_web_summary(),
use_for_git_history: default_small_model_for_git_history(),
use_for_memory: default_small_model_for_memory(),
}
}
}
#[inline]
const fn default_small_model_enabled() -> bool {
true }
#[inline]
const fn default_small_model_temperature() -> f32 {
0.3 }
#[inline]
const fn default_small_model_for_large_reads() -> bool {
true
}
#[inline]
const fn default_small_model_for_web_summary() -> bool {
true
}
#[inline]
const fn default_small_model_for_git_history() -> bool {
true
}
#[inline]
const fn default_small_model_for_memory() -> bool {
true
}
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct AgentPromptSuggestionsConfig {
#[serde(default = "default_prompt_suggestions_enabled")]
pub enabled: bool,
#[serde(default)]
pub model: String,
#[serde(default = "default_prompt_suggestions_temperature")]
pub temperature: f32,
#[serde(default = "default_prompt_suggestions_show_cost_notice")]
pub show_cost_notice: bool,
}
impl Default for AgentPromptSuggestionsConfig {
fn default() -> Self {
Self {
enabled: default_prompt_suggestions_enabled(),
model: String::new(),
temperature: default_prompt_suggestions_temperature(),
show_cost_notice: default_prompt_suggestions_show_cost_notice(),
}
}
}
#[inline]
const fn default_prompt_suggestions_enabled() -> bool {
true
}
#[inline]
const fn default_prompt_suggestions_temperature() -> f32 {
0.3
}
#[inline]
const fn default_prompt_suggestions_show_cost_notice() -> bool {
true
}
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct AgentVibeCodingConfig {
#[serde(default = "default_vibe_coding_enabled")]
pub enabled: bool,
#[serde(default = "default_vibe_min_prompt_length")]
pub min_prompt_length: usize,
#[serde(default = "default_vibe_min_prompt_words")]
pub min_prompt_words: usize,
#[serde(default = "default_vibe_entity_resolution")]
pub enable_entity_resolution: bool,
#[serde(default = "default_vibe_entity_cache")]
pub entity_index_cache: String,
#[serde(default = "default_vibe_max_entity_matches")]
pub max_entity_matches: usize,
#[serde(default = "default_vibe_track_workspace")]
pub track_workspace_state: bool,
#[serde(default = "default_vibe_max_recent_files")]
pub max_recent_files: usize,
#[serde(default = "default_vibe_track_values")]
pub track_value_history: bool,
#[serde(default = "default_vibe_conversation_memory")]
pub enable_conversation_memory: bool,
#[serde(default = "default_vibe_max_memory_turns")]
pub max_memory_turns: usize,
#[serde(default = "default_vibe_pronoun_resolution")]
pub enable_pronoun_resolution: bool,
#[serde(default = "default_vibe_proactive_context")]
pub enable_proactive_context: bool,
#[serde(default = "default_vibe_max_context_files")]
pub max_context_files: usize,
#[serde(default = "default_vibe_max_snippets_per_file")]
pub max_context_snippets_per_file: usize,
#[serde(default = "default_vibe_max_search_results")]
pub max_search_results: usize,
#[serde(default = "default_vibe_value_inference")]
pub enable_relative_value_inference: bool,
}
impl Default for AgentVibeCodingConfig {
fn default() -> Self {
Self {
enabled: default_vibe_coding_enabled(),
min_prompt_length: default_vibe_min_prompt_length(),
min_prompt_words: default_vibe_min_prompt_words(),
enable_entity_resolution: default_vibe_entity_resolution(),
entity_index_cache: default_vibe_entity_cache(),
max_entity_matches: default_vibe_max_entity_matches(),
track_workspace_state: default_vibe_track_workspace(),
max_recent_files: default_vibe_max_recent_files(),
track_value_history: default_vibe_track_values(),
enable_conversation_memory: default_vibe_conversation_memory(),
max_memory_turns: default_vibe_max_memory_turns(),
enable_pronoun_resolution: default_vibe_pronoun_resolution(),
enable_proactive_context: default_vibe_proactive_context(),
max_context_files: default_vibe_max_context_files(),
max_context_snippets_per_file: default_vibe_max_snippets_per_file(),
max_search_results: default_vibe_max_search_results(),
enable_relative_value_inference: default_vibe_value_inference(),
}
}
}
#[inline]
const fn default_vibe_coding_enabled() -> bool {
false }
#[inline]
const fn default_vibe_min_prompt_length() -> usize {
5
}
#[inline]
const fn default_vibe_min_prompt_words() -> usize {
2
}
#[inline]
const fn default_vibe_entity_resolution() -> bool {
true
}
#[inline]
fn default_vibe_entity_cache() -> String {
".vtcode/entity_index.json".into()
}
#[inline]
const fn default_vibe_max_entity_matches() -> usize {
5
}
#[inline]
const fn default_vibe_track_workspace() -> bool {
true
}
#[inline]
const fn default_vibe_max_recent_files() -> usize {
20
}
#[inline]
const fn default_vibe_track_values() -> bool {
true
}
#[inline]
const fn default_vibe_conversation_memory() -> bool {
true
}
#[inline]
const fn default_vibe_max_memory_turns() -> usize {
50
}
#[inline]
const fn default_vibe_pronoun_resolution() -> bool {
true
}
#[inline]
const fn default_vibe_proactive_context() -> bool {
true
}
#[inline]
const fn default_vibe_max_context_files() -> usize {
3
}
#[inline]
const fn default_vibe_max_snippets_per_file() -> usize {
20
}
#[inline]
const fn default_vibe_max_search_results() -> usize {
5
}
#[inline]
const fn default_vibe_value_inference() -> bool {
true
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_continuation_policy_defaults_and_parses() {
assert_eq!(ContinuationPolicy::default(), ContinuationPolicy::All);
assert_eq!(
ContinuationPolicy::parse("off"),
Some(ContinuationPolicy::Off)
);
assert_eq!(
ContinuationPolicy::parse("exec-only"),
Some(ContinuationPolicy::ExecOnly)
);
assert_eq!(
ContinuationPolicy::parse("all"),
Some(ContinuationPolicy::All)
);
assert_eq!(ContinuationPolicy::parse("invalid"), None);
}
#[test]
fn test_harness_config_continuation_policy_deserializes_with_fallback() {
let parsed: AgentHarnessConfig =
toml::from_str("continuation_policy = \"all\"").expect("valid harness config");
assert_eq!(parsed.continuation_policy, ContinuationPolicy::All);
let fallback: AgentHarnessConfig =
toml::from_str("continuation_policy = \"unexpected\"").expect("fallback config");
assert_eq!(fallback.continuation_policy, ContinuationPolicy::All);
}
#[test]
fn test_harness_orchestration_mode_defaults_and_parses() {
assert_eq!(
HarnessOrchestrationMode::default(),
HarnessOrchestrationMode::Single
);
assert_eq!(
HarnessOrchestrationMode::parse("single"),
Some(HarnessOrchestrationMode::Single)
);
assert_eq!(
HarnessOrchestrationMode::parse("plan_build_evaluate"),
Some(HarnessOrchestrationMode::PlanBuildEvaluate)
);
assert_eq!(
HarnessOrchestrationMode::parse("planner-generator-evaluator"),
Some(HarnessOrchestrationMode::PlanBuildEvaluate)
);
assert_eq!(HarnessOrchestrationMode::parse("unexpected"), None);
}
#[test]
fn test_harness_config_orchestration_deserializes_with_fallback() {
let parsed: AgentHarnessConfig =
toml::from_str("orchestration_mode = \"plan_build_evaluate\"")
.expect("valid harness config");
assert_eq!(
parsed.orchestration_mode,
HarnessOrchestrationMode::PlanBuildEvaluate
);
assert_eq!(parsed.max_revision_rounds, 2);
let fallback: AgentHarnessConfig =
toml::from_str("orchestration_mode = \"unexpected\"").expect("fallback config");
assert_eq!(
fallback.orchestration_mode,
HarnessOrchestrationMode::Single
);
}
#[test]
fn test_plan_confirmation_config_default() {
let config = AgentConfig::default();
assert!(config.require_plan_confirmation);
}
#[test]
fn test_persistent_memory_is_disabled_by_default() {
let config = AgentConfig::default();
assert!(!config.persistent_memory.enabled);
assert!(config.persistent_memory.auto_write);
}
#[test]
fn test_tool_result_clearing_defaults() {
let config = AgentConfig::default();
let clearing = config.harness.tool_result_clearing;
assert!(!clearing.enabled);
assert_eq!(clearing.trigger_tokens, 100_000);
assert_eq!(clearing.keep_tool_uses, 3);
assert_eq!(clearing.clear_at_least_tokens, 30_000);
assert!(!clearing.clear_tool_inputs);
}
#[test]
fn test_codex_app_server_experimental_features_default_to_disabled() {
let config = AgentConfig::default();
assert!(!config.codex_app_server.experimental_features);
}
#[test]
fn test_codex_app_server_experimental_features_parse_from_toml() {
let parsed: AgentCodexAppServerConfig = toml::from_str(
r#"
command = "codex"
args = ["app-server"]
startup_timeout_secs = 15
experimental_features = true
"#,
)
.expect("valid codex app-server config");
assert!(parsed.experimental_features);
assert_eq!(parsed.startup_timeout_secs, 15);
}
#[test]
fn test_tool_result_clearing_parses_and_validates() {
let parsed: AgentHarnessConfig = toml::from_str(
r#"
[tool_result_clearing]
enabled = true
trigger_tokens = 123456
keep_tool_uses = 6
clear_at_least_tokens = 4096
clear_tool_inputs = true
"#,
)
.expect("valid harness config");
assert!(parsed.tool_result_clearing.enabled);
assert_eq!(parsed.tool_result_clearing.trigger_tokens, 123_456);
assert_eq!(parsed.tool_result_clearing.keep_tool_uses, 6);
assert_eq!(parsed.tool_result_clearing.clear_at_least_tokens, 4_096);
assert!(parsed.tool_result_clearing.clear_tool_inputs);
assert!(parsed.tool_result_clearing.validate().is_ok());
}
#[test]
fn test_tool_result_clearing_rejects_zero_values() {
let clearing = ToolResultClearingConfig {
trigger_tokens: 0,
..ToolResultClearingConfig::default()
};
assert!(clearing.validate().is_err());
let clearing = ToolResultClearingConfig {
keep_tool_uses: 0,
..ToolResultClearingConfig::default()
};
assert!(clearing.validate().is_err());
let clearing = ToolResultClearingConfig {
clear_at_least_tokens: 0,
..ToolResultClearingConfig::default()
};
assert!(clearing.validate().is_err());
}
#[test]
fn test_structured_reasoning_defaults_follow_prompt_mode() {
let default_mode = AgentConfig {
system_prompt_mode: SystemPromptMode::Default,
..Default::default()
};
assert!(!default_mode.should_include_structured_reasoning_tags());
let specialized_mode = AgentConfig {
system_prompt_mode: SystemPromptMode::Specialized,
..Default::default()
};
assert!(specialized_mode.should_include_structured_reasoning_tags());
let minimal_mode = AgentConfig {
system_prompt_mode: SystemPromptMode::Minimal,
..Default::default()
};
assert!(!minimal_mode.should_include_structured_reasoning_tags());
let lightweight_mode = AgentConfig {
system_prompt_mode: SystemPromptMode::Lightweight,
..Default::default()
};
assert!(!lightweight_mode.should_include_structured_reasoning_tags());
}
#[test]
fn test_structured_reasoning_explicit_override() {
let mut config = AgentConfig {
system_prompt_mode: SystemPromptMode::Minimal,
include_structured_reasoning_tags: Some(true),
..AgentConfig::default()
};
assert!(config.should_include_structured_reasoning_tags());
config.include_structured_reasoning_tags = Some(false);
assert!(!config.should_include_structured_reasoning_tags());
}
}