1use crate::{
2 agent::Agent,
3 completion::{CompletionModel, Prompt, PromptError},
4 tool::{Tool, ToolCallExtensions},
5};
6use schemars::{JsonSchema, schema_for};
7use serde::{Deserialize, Serialize};
8use serde_json::json;
9
10#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
11pub struct AgentToolArgs {
12 prompt: String,
14}
15
16impl<M: CompletionModel + 'static> Tool for Agent<M> {
17 const NAME: &'static str = "agent_tool";
18
19 type Error = PromptError;
20 type Args = AgentToolArgs;
21 type Output = String;
22
23 fn description(&self) -> String {
24 format!(
25 "
26 Prompt a sub-agent to do a task for you.
27
28 Agent name: {name}
29 Agent description: {description}
30 Agent system prompt: {sysprompt}
31 ",
32 name = self.name(),
33 description = self.description.clone().unwrap_or_default(),
34 sysprompt = self.preamble.clone().unwrap_or_default()
35 )
36 }
37
38 fn parameters(&self) -> serde_json::Value {
39 json!(schema_for!(AgentToolArgs))
40 }
41
42 async fn call(&self, args: Self::Args) -> Result<Self::Output, Self::Error> {
43 self.prompt(args.prompt).await
44 }
45
46 async fn call_with_extensions(
51 &self,
52 args: Self::Args,
53 extensions: &ToolCallExtensions,
54 ) -> Result<Self::Output, Self::Error> {
55 self.prompt(args.prompt)
56 .tool_extensions(extensions.clone())
57 .await
58 }
59
60 fn name(&self) -> String {
61 self.name.clone().unwrap_or_else(|| Self::NAME.to_string())
62 }
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68 use crate::agent::AgentBuilder;
69 use crate::test_utils::{MockCompletionModel, MockExtensionsProbeTool, MockTurn, SessionId};
70
71 #[tokio::test]
74 async fn context_propagates_into_sub_agent() {
75 let probe = MockExtensionsProbeTool::default();
77 let inner_model = MockCompletionModel::new([
78 MockTurn::tool_call("c1", "context_probe", json!({})),
79 MockTurn::text("inner done"),
80 ]);
81 let inner = AgentBuilder::new(inner_model)
82 .name("researcher")
83 .tool(probe.clone())
84 .build();
85
86 let outer_model = MockCompletionModel::new([
89 MockTurn::tool_call("c2", "researcher", json!({"prompt": "do research"})),
90 MockTurn::text("outer done"),
91 ]);
92 let outer = AgentBuilder::new(outer_model).tool(inner).build();
93
94 let mut extensions = ToolCallExtensions::new();
95 extensions.insert(SessionId("abc-123".to_string()));
96
97 let out = outer
98 .prompt("start")
99 .tool_extensions(extensions)
100 .max_turns(5)
101 .await
102 .expect("run succeeds");
103
104 assert_eq!(out, "outer done");
105 assert_eq!(probe.observed().as_deref(), Some("session:abc-123"));
106 }
107}