Skip to main content

oxi_agent/agent_loop/
config.rs

1//! Agent loop configuration types
2
3/// Configuration for an [`crate::AgentLoop`] instance.
4#[derive(Clone)]
5pub struct AgentLoopConfig {
6    /// Model identifier in `provider/model` format.
7    pub model_id: String,
8    /// Optional system prompt prepended to every request.
9    pub system_prompt: Option<String>,
10    /// Sampling temperature (0.0 – 2.0).
11    pub temperature: f32,
12    /// Maximum tokens the model may generate per request.
13    pub max_tokens: u32,
14    /// Whether tool calls run in parallel or sequentially.
15    pub tool_execution: ToolExecutionMode,
16    /// Compaction strategy for managing context window usage.
17    pub compaction_strategy: oxi_ai::CompactionStrategy,
18    /// Approximate context window size in tokens.
19    pub context_window: usize,
20    /// Optional instruction injected into the compaction prompt.
21    pub compaction_instruction: Option<String>,
22    /// Optional session identifier for logging and tracing.
23    pub session_id: Option<String>,
24    /// Optional transport override (e.g. "sse", "stdio").
25    pub transport: Option<String>,
26    /// Whether to trigger compaction before the first turn.
27    pub compact_on_start: bool,
28    /// Optional cap on retry back-off delay (milliseconds).
29    pub max_retry_delay_ms: Option<u64>,
30    /// Enable automatic retry on retryable assistant errors.
31    pub auto_retry_enabled: bool,
32    /// Maximum number of auto-retry attempts.
33    pub auto_retry_max_attempts: usize,
34    /// Base delay in milliseconds for auto-retry exponential back-off.
35    pub auto_retry_base_delay_ms: u64,
36    /// API key override for the provider.
37    ///
38    /// When set, this is injected into [`oxi_ai::StreamOptions`] so the
39    /// provider uses it instead of an environment variable.
40    pub api_key: Option<String>,
41    /// Working directory for file tools. Defaults to current directory if None.
42    pub workspace_dir: Option<std::path::PathBuf>,
43    /// Per-provider options for fine-grained control.
44    ///
45    /// Passed through to [`oxi_ai::StreamOptions::provider_options`] so the
46    /// provider can read provider-specific settings.
47    pub provider_options: Option<oxi_ai::ProviderOptions>,
48
49    /// Async hook invoked after context compaction completes.
50    ///
51    /// Unlike the `Compaction` event in the `Fn` callback, this hook is
52    /// async and its future is awaited within the agent loop. Errors are
53    /// logged at WARN level but don't fail the loop.
54    ///
55    /// Use this for side effects that require async I/O (e.g., persisting
56    /// compaction summaries to a memory store) without resorting to
57    /// `tokio::spawn` fire-and-forget.
58    pub on_compaction: Option<CompactionHook>,
59    /// Snapshot store for hashline edit mode.
60    pub snapshot_store: Option<Arc<dyn oxi_hashline::SnapshotStore>>,
61    /// Memory backend for memory tools.
62    pub memory: Option<Arc<dyn crate::tools::MemoryBackend>>,
63    /// URL resolver for internal protocol schemes.
64    pub url_resolver: Option<Arc<dyn crate::tools::UrlResolver>>,
65    /// Todo state provider for the `todo` tool.
66    pub todo: Option<Arc<dyn crate::tools::TodoStateProvider>>,
67    /// Agent pool for Hub display.
68    pub agent_pool: Option<Arc<dyn crate::tools::AgentPoolProvider>>,
69    /// LSP provider for the `lsp` tool.
70    pub lsp: Option<Arc<dyn crate::tools::LspProvider>>,
71    /// TTSR engine for stream rule checking.
72    pub ttsr_engine: Option<Arc<crate::agent_loop::ttsr::TtsrEngine>>,
73    /// In-process sub-agent runner (issue #28 gap 3).
74    /// When `Some`, the `subagent` tool prefers an in-process isolated
75    /// run over shelling out to the CLI. Library consumers set this so
76    /// delegation works without an `oxi` subprocess.
77    pub subagent_runner: Option<Arc<dyn crate::tools::SubagentRunner>>,
78    /// Current sub-agent nesting depth (issue #28 gap 3).
79    ///
80    /// The CLI backend uses env vars (`OXI_SUBAGENT_DEPTH`) for this,
81    /// which is safe because each subprocess has its own env. The
82    /// in-process backend **cannot** use env vars (concurrent
83    /// `set_var` is UB; state leaks between forks), so it reads this
84    /// field instead. Default 0 (top-level). The `subagent` tool
85    /// increments this when creating a forked `AgentLoopConfig`, and
86    /// the fork checks it against the agent definition's
87    /// `max_subagent_depth` to cap recursion.
88    pub subagent_depth: u8,
89    /// Maximum size (in bytes) of a single tool result's text content
90    /// before it is truncated (issue #28 gap 1).
91    ///
92    /// When set, tool results exceeding this limit are truncated to
93    /// the limit and a marker is appended:
94    /// `"... [truncated: N bytes omitted]"`. This prevents a single
95    /// large tool output (e.g. reading a huge file, verbose bash
96    /// output) from consuming the entire context window.
97    ///
98    /// `None` (default) = no limit. Opt-in — existing behavior is
99    /// preserved.
100    pub max_tool_result_bytes: Option<usize>,
101}
102
103impl Default for AgentLoopConfig {
104    fn default() -> Self {
105        Self {
106            model_id: String::new(),
107            system_prompt: None,
108            temperature: 0.7,
109            max_tokens: 4096,
110            tool_execution: ToolExecutionMode::Parallel,
111            compaction_strategy: oxi_ai::CompactionStrategy::default(),
112            context_window: 128_000,
113            compaction_instruction: None,
114            session_id: None,
115            transport: None,
116            compact_on_start: false,
117            max_retry_delay_ms: None,
118            auto_retry_enabled: false,
119            auto_retry_max_attempts: 3,
120            auto_retry_base_delay_ms: 2000,
121            api_key: None,
122            workspace_dir: None,
123            provider_options: None,
124            on_compaction: None,
125            snapshot_store: None,
126            memory: None,
127            url_resolver: None,
128            todo: None,
129            agent_pool: None,
130            lsp: None,
131            ttsr_engine: None,
132            subagent_runner: None,
133            subagent_depth: 0,
134            max_tool_result_bytes: None,
135        }
136    }
137}
138
139// Re-export ToolExecutionMode from crate::config to avoid duplicate definitions.
140pub use crate::config::ToolExecutionMode;
141
142use crate::AgentToolResult;
143use crate::compaction::CompactedContext;
144use anyhow::{Error, Result};
145use serde_json::Value;
146use std::future::Future;
147use std::pin::Pin;
148use std::sync::Arc;
149
150/// Async hook invoked after context compaction completes.
151///
152/// Receives the [`CompactedContext`] and returns a `Result<()>` future.
153/// The future is awaited within the agent loop, so async operations
154/// (memory storage, logging, etc.) are safe here.
155///
156/// # Example
157///
158/// ```ignore
159/// let config = AgentLoopConfig {
160///     on_compaction: Some(Arc::new(|ctx: CompactedContext| {
161///         let summary = ctx.summary.clone();
162///         Box::pin(async move {
163///             memory_store.save(summary).await
164///         })
165///     })),
166///     ..Default::default()
167/// };
168/// ```
169pub type CompactionHook =
170    Arc<dyn Fn(CompactedContext) -> Pin<Box<dyn Future<Output = Result<()>> + Send>> + Send + Sync>;
171
172/// Hook invoked before each tool call; may return an override result.
173pub type BeforeToolCallHook = Arc<
174    dyn Fn(
175            &str,
176            &Value,
177        ) -> Pin<Box<dyn Future<Output = Result<Option<AgentToolResult>, Error>> + Send>>
178        + Send
179        + Sync,
180>;
181
182/// Hook invoked after each tool call; may return a modified result.
183pub type AfterToolCallHook = Arc<
184    dyn Fn(
185            &str,
186            &AgentToolResult,
187        ) -> Pin<Box<dyn Future<Output = Result<Option<AgentToolResult>, Error>> + Send>>
188        + Send
189        + Sync,
190>;
191
192// MAX_RETRIES and BACKOFF_BASE_SECS are now defined in crate::stream_retry
193// and re-exported from crate::agent_loop::retry.
194pub use crate::stream_retry::{BACKOFF_BASE_SECS, MAX_RETRIES};