bamboo_agent/agent/core/tools/context.rs
1//! Execution context for tool calls.
2//!
3//! Tools normally return a single `ToolResult` after completion. Some tools
4//! (for example, long-running CLIs) may want to stream intermediate progress
5//! to clients. The agent loop passes a `ToolExecutionContext` that allows tools
6//! to emit `AgentEvent`s while they run.
7
8use tokio::sync::mpsc;
9
10use crate::agent::core::AgentEvent;
11
12/// Context passed to tools during execution.
13///
14/// All fields are optional and should be treated as best-effort hints.
15#[derive(Clone, Copy, Debug)]
16pub struct ToolExecutionContext<'a> {
17 /// Bamboo session id that is executing the tool.
18 pub session_id: Option<&'a str>,
19 /// Tool call id from the model (`ToolCall.id`).
20 pub tool_call_id: &'a str,
21 /// Event sender for streaming progress to clients (agent SSE stream).
22 pub event_tx: Option<&'a mpsc::Sender<AgentEvent>>,
23}
24
25impl<'a> ToolExecutionContext<'a> {
26 pub fn none(tool_call_id: &'a str) -> Self {
27 Self {
28 session_id: None,
29 tool_call_id,
30 event_tx: None,
31 }
32 }
33
34 /// Clone the sender (when present) for use in spawned tasks.
35 pub fn cloned_sender(&self) -> Option<mpsc::Sender<AgentEvent>> {
36 self.event_tx.cloned()
37 }
38
39 /// Best-effort emit of an event (ignored if no sender).
40 pub async fn emit(&self, event: AgentEvent) {
41 if let Some(tx) = self.event_tx {
42 // Tools sometimes want to stream incremental output. Historically they emitted
43 // `AgentEvent::Token`, but that mixes tool output into the assistant stream.
44 // When emitting from a tool context, treat `Token` as tool-scoped output.
45 let event = match event {
46 AgentEvent::Token { content } => AgentEvent::ToolToken {
47 tool_call_id: self.tool_call_id.to_string(),
48 content,
49 },
50 other => other,
51 };
52 let _ = tx.send(event).await;
53 }
54 }
55
56 /// Convenience helper for streaming tool-scoped output.
57 pub async fn emit_tool_token(&self, content: impl Into<String>) {
58 self.emit(AgentEvent::ToolToken {
59 tool_call_id: self.tool_call_id.to_string(),
60 content: content.into(),
61 })
62 .await;
63 }
64}