use ds_api::{AgentEvent, DeepseekAgent, tool};
use futures::StreamExt;
use serde_json::json;
use std::io::{self, Write};
use std::process::Stdio;
use tokio::process::Command;
struct ShellTool;
#[tool]
impl Tool for ShellTool {
async fn run(&self, command: String) -> Value {
let output = Command::new("sh")
.arg("-c")
.arg(&command)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()
.await;
match output {
Ok(out) => {
json!({
"command": command,
"status": out.status.code(),
"stdout": String::from_utf8_lossy(&out.stdout),
"stderr": String::from_utf8_lossy(&out.stderr),
})
}
Err(e) => json!({
"command": command,
"error": e.to_string(),
}),
}
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let token = std::env::var("DEEPSEEK_API_KEY")?;
println!("DeepSeek REPL");
println!("Type a prompt and press Enter. Ctrl+C to exit.\n");
let mut line = String::new();
let mut agent = DeepseekAgent::new(&token)
.with_streaming()
.add_tool(ShellTool)
.with_system_prompt(
"You may call shell.run(command) to execute shell commands. \
Avoid destructive operations.",
);
loop {
print!("> ");
io::stdout().flush()?;
line.clear();
if io::stdin().read_line(&mut line)? == 0 {
break;
}
let prompt = line.trim();
if prompt.is_empty() {
continue;
}
let mut stream = agent.chat(prompt);
while let Some(event_res) = stream.next().await {
match event_res {
Err(e) => {
eprintln!("\nError: {}", e);
break;
}
Ok(AgentEvent::Token(fragment)) => {
print!("{fragment}");
io::stdout().flush().ok();
}
Ok(AgentEvent::ReasoningToken(fragment)) => {
print!("{fragment}");
io::stdout().flush().ok();
}
Ok(AgentEvent::ToolCall(c)) => {
if c.delta.is_empty() {
println!("\n[calling {} id={}]", c.name, c.id);
} else {
print!("{}", c.delta);
io::stdout().flush().ok();
}
}
Ok(AgentEvent::ToolResult(r)) => {
println!("\n[tool result] {} -> {}", r.name, r.result);
}
}
}
if let Some(a) = stream.into_agent() {
agent = a;
} else {
eprintln!("Agent was not returned from stream; exiting.");
break;
}
println!("\n");
}
Ok(())
}