mod todo;
pub use todo::{TodoItem, TodoTool};
use std::sync::Arc;
use adk_core::{Agent, Llm, Result, Tool};
use adk_devtools::{DevToolset, Workspace};
use crate::llm_agent::{LlmAgent, LlmAgentBuilder};
pub const CODING_SYSTEM_PROMPT: &str = "\
You are a precise software engineering agent working inside a sandboxed workspace.
Tools:
- read_file/glob/grep to explore before you change anything.
- write_file/edit_file to make changes. You must read a file before editing it.
- bash to build, run tests, and run commands (it runs in the workspace root).
- write_todos to track a multi-step plan; keep it updated as you work.
Operating rules:
- For non-trivial tasks, first write_todos with a short plan, then execute it.
- Make the smallest change that solves the task; verify by running the build/tests.
- Never invent file contents โ read first. Report what you actually did.
- When the task is complete and verified, summarize the changes concisely.";
pub struct CodingAgent {
agent: Arc<LlmAgent>,
todos: TodoTool,
}
impl CodingAgent {
pub fn builder() -> CodingAgentBuilder {
CodingAgentBuilder::new()
}
pub fn agent(&self) -> Arc<dyn Agent> {
self.agent.clone()
}
pub fn into_agent(self) -> Arc<dyn Agent> {
self.agent
}
pub fn todos(&self) -> Vec<TodoItem> {
self.todos.items()
}
}
pub struct CodingAgentBuilder {
name: String,
model: Option<Arc<dyn Llm>>,
workspace: Option<Workspace>,
instruction: Option<String>,
include_todo: bool,
extra_tools: Vec<Arc<dyn Tool>>,
}
impl CodingAgentBuilder {
pub fn new() -> Self {
Self {
name: "coding-agent".to_string(),
model: None,
workspace: None,
instruction: None,
include_todo: true,
extra_tools: Vec::new(),
}
}
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = name.into();
self
}
pub fn model(mut self, model: Arc<dyn Llm>) -> Self {
self.model = Some(model);
self
}
pub fn workspace(mut self, workspace: Workspace) -> Self {
self.workspace = Some(workspace);
self
}
pub fn instruction(mut self, instruction: impl Into<String>) -> Self {
self.instruction = Some(instruction.into());
self
}
pub fn without_todos(mut self) -> Self {
self.include_todo = false;
self
}
pub fn tool(mut self, tool: Arc<dyn Tool>) -> Self {
self.extra_tools.push(tool);
self
}
pub fn build(self) -> Result<CodingAgent> {
let model =
self.model.ok_or_else(|| adk_core::AdkError::config("CodingAgent requires a model"))?;
let workspace = self
.workspace
.ok_or_else(|| adk_core::AdkError::config("CodingAgent requires a workspace"))?;
let instruction = match &self.instruction {
Some(extra) => format!("{CODING_SYSTEM_PROMPT}\n\n{extra}"),
None => CODING_SYSTEM_PROMPT.to_string(),
};
let mut builder = LlmAgentBuilder::new(self.name)
.model(model)
.description(
"A coding agent that reads, edits, and runs code in a sandboxed workspace.",
)
.instruction(instruction)
.toolset(Arc::new(DevToolset::new(workspace)));
let todos = TodoTool::new();
if self.include_todo {
builder = builder.tool(Arc::new(todos.clone()));
}
for tool in self.extra_tools {
builder = builder.tool(tool);
}
let agent = builder.build()?;
Ok(CodingAgent { agent: Arc::new(agent), todos })
}
}
impl Default for CodingAgentBuilder {
fn default() -> Self {
Self::new()
}
}