codetether_agent/session/helper/
bootstrap.rs1use crate::provider::ToolDefinition;
2use serde_json::{Value, json};
3
4pub fn inject_tool_prompt(base_prompt: &str, tools: &[ToolDefinition]) -> String {
5 let tool_lines: String = tools
6 .iter()
7 .map(|t| format!("- {}: {}", t.name, t.description))
8 .collect::<Vec<_>>()
9 .join("\n");
10 format!(
11 "{base_prompt}\n\n\
12 # Tool Use\n\
13 You have access to the following tools. To use a tool, output a \
14 <tool_call> XML block with a JSON object containing \"name\" and \
15 \"arguments\" fields. You may output multiple tool calls in one response.\n\n\
16 Example:\n\
17 <tool_call>\n\
18 {{\"name\": \"bash\", \"arguments\": {{\"command\": \"ls /tmp\"}}}}\n\
19 </tool_call>\n\n\
20 Available tools:\n{tool_lines}\n\n\
21 RULES:\n\
22 1. To use a tool, output <tool_call> blocks. Do NOT describe or \
23 simulate tool usage in plain text. Do NOT fabricate tool output.\n\
24 2. After a tool result is returned, review the result and provide \
25 your final answer in plain text WITHOUT any <tool_call> blocks. \
26 Only call another tool if the result was insufficient.\n\
27 3. Do NOT call the same tool with the same arguments more than once.\n\
28 4. Prefer the lsp tool for code intelligence (symbols/definitions/references). \
29 Prefer the bash tool for shell commands.\n\
30 5. During refactors, NEVER create placeholder/stub implementations \
31 (e.g., TODO, FIXME, \"not implemented\", \"fallback\"). Always preserve \
32 concrete behavior."
33 )
34}
35
36pub fn list_tools_bootstrap_definition() -> ToolDefinition {
37 ToolDefinition {
38 name: "list_tools".to_string(),
39 description: "List available tools (optionally filter by query) or fetch a full schema for one tool via {\"tool\":\"name\"}.".to_string(),
40 parameters: json!({
41 "type": "object",
42 "properties": {
43 "query": {
44 "type": "string",
45 "description": "Optional substring filter over tool names/descriptions"
46 },
47 "tool": {
48 "type": "string",
49 "description": "Optional exact tool name to return full schema"
50 }
51 }
52 }),
53 }
54}
55
56pub fn list_tools_bootstrap_output(tools: &[ToolDefinition], tool_input: &Value) -> String {
57 let requested_tool = tool_input
58 .get("tool")
59 .and_then(Value::as_str)
60 .map(str::trim)
61 .filter(|s| !s.is_empty());
62
63 if let Some(name) = requested_tool {
64 if let Some(found) = tools.iter().find(|t| t.name.eq_ignore_ascii_case(name)) {
65 return serde_json::to_string_pretty(&json!({
66 "mode": "tool",
67 "tool": {
68 "name": found.name,
69 "description": found.description,
70 "parameters": found.parameters
71 }
72 }))
73 .unwrap_or_else(|_| format!("tool: {}", found.name));
74 }
75 return serde_json::to_string_pretty(&json!({
76 "mode": "tool",
77 "error": format!("Tool '{}' not found", name)
78 }))
79 .unwrap_or_else(|_| format!("Tool '{}' not found", name));
80 }
81
82 let query = tool_input
83 .get("query")
84 .and_then(Value::as_str)
85 .map(str::trim)
86 .filter(|s| !s.is_empty())
87 .map(|s| s.to_ascii_lowercase());
88
89 let mut listed: Vec<Value> = tools
90 .iter()
91 .filter(|t| {
92 if let Some(q) = &query {
93 t.name.to_ascii_lowercase().contains(q)
94 || t.description.to_ascii_lowercase().contains(q)
95 } else {
96 true
97 }
98 })
99 .map(|t| {
100 json!({
101 "name": t.name,
102 "description": t.description,
103 })
104 })
105 .collect();
106
107 listed.sort_by(|a, b| {
108 let an = a["name"].as_str().unwrap_or_default();
109 let bn = b["name"].as_str().unwrap_or_default();
110 an.cmp(bn)
111 });
112
113 serde_json::to_string_pretty(&json!({
114 "mode": "list",
115 "count": listed.len(),
116 "tools": listed,
117 "hint": "Call list_tools with {\"tool\":\"<name>\"} to fetch full parameter schema for one tool."
118 }))
119 .unwrap_or_else(|_| "{\"mode\":\"list\",\"error\":\"serialization_failed\"}".to_string())
120}