Skip to main content

agent_base/engine/runtime/
mod.rs

1use std::sync::Arc;
2
3use tokio_util::sync::CancellationToken;
4
5use crate::engine::AgentSession;
6use crate::engine::session_store::SessionStore;
7use crate::types::{
8    AgentConfig, AgentError, AgentResult, CheckpointData, MessageRole, RunOutcome, RuntimeEvent,
9    SessionId, TurnContext,
10};
11
12use super::approval::ApprovalHandler;
13
14mod event_bus;
15pub(crate) use event_bus::EventBus;
16mod llm_engine;
17mod message_queue;
18mod plan_runner;
19mod react_loop;
20mod session_manager;
21mod tool_engine;
22
23pub(super) const DEFAULT_MAX_TURNS: u32 = 50;
24
25pub use llm_engine::LlmEngine;
26pub use message_queue::QueueMode;
27pub(crate) use plan_runner::RuntimeCore;
28pub use session_manager::SessionManager;
29pub(crate) use tool_engine::ToolEngine;
30
31#[derive(Clone)]
32pub struct AgentRuntime {
33    pub(crate) runner: Arc<RuntimeCore>,
34}
35
36impl AgentRuntime {
37    pub async fn create_session(&self) -> SessionId {
38        let config = self.runner.config.read().await;
39        self.runner
40            .session_manager
41            .create_session(config.system_prompt.as_deref())
42            .await
43    }
44
45    pub async fn restore_session(&self, session_id: &SessionId) -> Option<AgentSession> {
46        self.runner
47            .session_manager
48            .restore_session(session_id)
49            .await
50    }
51
52    pub async fn session(&self, session_id: &SessionId) -> Option<AgentSession> {
53        self.runner.session_manager.session(session_id).await
54    }
55
56    pub async fn session_or_err(&self, session_id: &SessionId) -> AgentResult<AgentSession> {
57        self.runner.session_manager.session_or_err(session_id).await
58    }
59
60    pub async fn with_session_mut<F, R>(&self, session_id: &SessionId, f: F) -> AgentResult<R>
61    where
62        F: FnOnce(&mut AgentSession) -> R,
63    {
64        self.runner
65            .session_manager
66            .with_session_mut(session_id, f)
67            .await
68    }
69
70    pub fn emit_event(&self, event: RuntimeEvent) {
71        self.runner.event_bus.emit(event);
72    }
73
74    /// Subscribe to runtime events from the internal broadcast channel.
75    ///
76    /// Events are delivered directly from the runtime's event bus (capacity 2048).
77    /// Slow consumers may receive `Lagged(n)` errors if they cannot keep up —
78    /// ensure the receiver loop processes events promptly or use a buffering
79    /// layer in the consumer if backpressure is a concern.
80    pub fn subscribe_runtime_events(&self) -> tokio::sync::broadcast::Receiver<RuntimeEvent> {
81        self.runner.event_bus.subscribe()
82    }
83
84    pub fn session_manager(&self) -> &SessionManager {
85        &self.runner.session_manager
86    }
87
88    pub fn llm_engine(&self) -> &LlmEngine {
89        &self.runner.llm_engine
90    }
91
92    pub fn client(&self) -> Arc<dyn crate::llm::StreamClient> {
93        self.runner.llm_engine.get_client()
94    }
95
96    /// Replace the LLM client at runtime (e.g., model switch).
97    /// Requires `&mut self` — obtain via `runtime.lock().await`.
98    pub fn set_client(&mut self, client: Arc<dyn crate::llm::StreamClient>) {
99        self.runner.llm_engine.set_client(client);
100    }
101
102    pub fn tools_mut(&self) -> Arc<tokio::sync::RwLock<crate::tool::ToolRegistry>> {
103        self.runner.tool_engine.tools_arc()
104    }
105
106    /// Inject the internal EventBus into framework tools in the given registry.
107    /// Call this after replacing tools in the registry (e.g., in `build_tools`).
108    pub fn inject_framework_deps(&self, tools: &crate::tool::ToolRegistry) {
109        self.runner.tool_engine.inject_event_bus_into(tools);
110    }
111
112    pub fn config(&self) -> tokio::sync::RwLockReadGuard<'_, AgentConfig> {
113        self.runner.config.blocking_read()
114    }
115
116    /// 设置 reasoning effort(异步版本)
117    pub async fn set_reasoning_effort(&self, effort: crate::llm::ReasoningEffort) {
118        let mut config = self.runner.config.write().await;
119        let mut reasoning = config.reasoning.take().unwrap_or_default();
120        reasoning.effort = Some(effort);
121        config.reasoning = Some(reasoning);
122    }
123
124    /// 设置 reasoning effort(同步版本,只在同步上下文中使用)
125    pub fn set_reasoning_effort_sync(&self, effort: crate::llm::ReasoningEffort) {
126        let mut config = self.runner.config.blocking_write();
127        let mut reasoning = config.reasoning.take().unwrap_or_default();
128        reasoning.effort = Some(effort);
129        config.reasoning = Some(reasoning);
130    }
131
132    pub fn approval_handler(&self) -> Option<&Arc<dyn ApprovalHandler>> {
133        self.runner.tool_engine.approval_handler()
134    }
135
136    pub async fn cached_approval(&self, session_id: &SessionId, action_key: &str) -> bool {
137        self.runner
138            .session_manager
139            .cached_approval(session_id, action_key)
140            .await
141    }
142
143    pub async fn cache_approval(&self, session_id: &SessionId, action_key: String) {
144        self.runner
145            .session_manager
146            .cache_approval(session_id, action_key)
147            .await
148    }
149
150    pub async fn save_checkpoint(
151        &self,
152        session_id: &SessionId,
153        checkpoint: CheckpointData,
154    ) -> AgentResult<()> {
155        self.emit_event(RuntimeEvent::Checkpoint {
156            session_id: session_id.clone(),
157            checkpoint,
158            agent_id: None,
159            trace_id: None,
160        });
161        Ok(())
162    }
163
164    pub async fn load_checkpoint(
165        &self,
166        _session_id: &SessionId,
167        _checkpoint: &CheckpointData,
168    ) -> AgentResult<Option<CheckpointData>> {
169        Ok(None)
170    }
171
172    pub async fn run<F>(&self, session_id: SessionId, on_event: F) -> AgentResult<RunOutcome>
173    where
174        F: FnMut(RuntimeEvent) -> AgentResult<()> + Send,
175    {
176        self.runner.run(session_id, on_event).await
177    }
178
179    pub async fn run_turn<F>(
180        &self,
181        session_id: SessionId,
182        user_input: &str,
183        on_event: F,
184    ) -> AgentResult<RunOutcome>
185    where
186        F: FnMut(RuntimeEvent) -> AgentResult<()> + Send,
187    {
188        self.runner.run_turn(session_id, user_input, on_event).await
189    }
190
191    pub async fn run_turn_collect(
192        &self,
193        session_id: SessionId,
194        user_input: &str,
195    ) -> AgentResult<(Vec<RuntimeEvent>, RunOutcome)> {
196        self.runner.run_turn_collect(session_id, user_input).await
197    }
198
199    pub async fn add_user_message(
200        &self,
201        session_id: &SessionId,
202        text: impl Into<String>,
203    ) -> AgentResult<()> {
204        let text = text.into();
205        self.with_session_mut(session_id, |session| {
206            session.push_message(MessageRole::User, &text);
207        })
208        .await
209    }
210
211    pub async fn add_system_message(
212        &self,
213        session_id: &SessionId,
214        text: impl Into<String>,
215    ) -> AgentResult<()> {
216        let text = text.into();
217        self.with_session_mut(session_id, |session| {
218            session.push_message(MessageRole::System, &text);
219        })
220        .await
221    }
222
223    pub async fn add_tool_result(
224        &self,
225        session_id: &SessionId,
226        tool_call_id: &str,
227        summary: impl Into<String>,
228    ) -> AgentResult<()> {
229        let summary = summary.into();
230        self.with_session_mut(session_id, |session| {
231            session.push_tool_result(tool_call_id, summary.clone());
232        })
233        .await
234    }
235
236    pub async fn get_messages(
237        &self,
238        session_id: &SessionId,
239    ) -> AgentResult<Vec<crate::types::ChatMessage>> {
240        let session = self.session_or_err(session_id).await?;
241        Ok(session.chat_messages().to_vec())
242    }
243
244    /// Replace the chat messages for a session — only for persistence restore.
245    /// Validates message sequence before applying.
246    ///
247    /// 仅供持久化恢复使用。
248    pub async fn set_messages(
249        &self,
250        session_id: &SessionId,
251        messages: Vec<crate::types::ChatMessage>,
252    ) -> AgentResult<()> {
253        self.with_session_mut(session_id, |session| session.set_chat_messages(messages))
254            .await?
255            .map_err(AgentError::internal)
256    }
257
258    pub async fn validate_session(&self, session_id: &SessionId) -> AgentResult<()> {
259        if self
260            .runner
261            .session_manager
262            .session(session_id)
263            .await
264            .is_none()
265        {
266            return Err(AgentError::session_not_found(session_id.id));
267        }
268        Ok(())
269    }
270
271    pub fn session_store(&self) -> Arc<dyn SessionStore> {
272        self.runner.session_manager.session_store().clone()
273    }
274
275    // ── Observability hook ──
276
277    /// Register a turn-end callback. The callback receives a [`TurnContext`]
278    /// with raw data about the completed turn iteration. Consumers (e.g.
279    /// phi-telemetry) use this to build their own metrics without agent-base
280    /// knowing anything about metrics.
281    pub fn on_turn_end<F>(&self, f: F)
282    where
283        F: Fn(&TurnContext) + Send + Sync + 'static,
284    {
285        self.runner
286            .turn_end_callbacks
287            .write()
288            .unwrap()
289            .push(Arc::new(f));
290    }
291
292    // --- Cancellation support ---
293
294    /// Cancel the currently executing run_turn / run.
295    /// No-op if there is no current execution.
296    pub fn cancel(&self) {
297        self.runner.cancel();
298    }
299
300    /// Reset the cancel token (called automatically before each run_turn)
301    pub fn reset_cancel(&self) {
302        self.runner.reset_cancel();
303    }
304
305    /// Get a clone of the cancel token
306    pub fn cancel_token(&self) -> CancellationToken {
307        self.runner.cancel_token()
308    }
309
310    /// Check if cancellation has been requested
311    pub fn is_cancelled(&self) -> bool {
312        self.runner.is_cancelled()
313    }
314
315    // ── Message Queue (P2) ──
316
317    /// Push a steering message — will be processed at the start of the next turn
318    /// in the current `run_managed()` loop.
319    pub fn steer(&self, message: String) {
320        self.runner.message_queue.steer(message);
321    }
322
323    /// Push a follow-up message — will be processed after the inner turn loop
324    /// stops naturally (no tool calls or max turns).
325    pub fn follow_up(&self, message: String) {
326        self.runner.message_queue.follow_up(message);
327    }
328
329    /// Run the agent in managed mode with message queue support.
330    ///
331    /// This wraps `run_turn()` (or `run()`) in an outer loop: after the inner
332    /// turn loop completes, any follow-up messages are drained and a new inner
333    /// loop is started. Steering messages are drained automatically at each
334    /// iteration of the inner turn loop.
335    ///
336    /// The `on_event` callback receives all events from every inner run.
337    pub async fn run_managed<F>(
338        &self,
339        session_id: SessionId,
340        user_input: &str,
341        on_event: F,
342    ) -> AgentResult<RunOutcome>
343    where
344        F: FnMut(RuntimeEvent) -> AgentResult<()> + Send,
345    {
346        self.runner
347            .run_managed(session_id, user_input, on_event)
348            .await
349    }
350
351    /// Set the drain mode for the message queues.
352    pub fn set_queue_mode(&self, mode: crate::engine::runtime::message_queue::QueueMode) {
353        self.runner.message_queue.set_mode(mode);
354    }
355}