package cjagent
import std.collection.*
import std.env.*
import std.fs.Path
import stdx.encoding.json.*
/*
* Agent 主循环:把「用户意图 → 模型思考 → 工具执行 → 观察结果」串成闭环。
*
* 每收到一句用户输入,就反复请求模型:
* - 模型返回工具调用 → 逐个执行并把结果回灌,继续下一轮;
* - 模型返回纯文本 → 视为最终答复,本轮结束。
* 直到给出答复或达到 maxSteps 上限。
*/
class Agent {
let cfg: Config
let llm: Llm
let tools: Array<Tool>
let byName: HashMap<String, Tool>
init(cfg: Config) {
this.cfg = cfg
this.llm = Llm(cfg)
this.tools = buildTools(cfg)
let index = HashMap<String, Tool>()
for (t in tools) {
index.add(t.name, t)
}
this.byName = index
}
func run(): Unit {
printBanner()
let messages = ArrayList<Message>()
messages.add(Message.system(systemPrompt(cfg.root)))
match (cfg.task) {
case Some(task) =>
println("📋 任务:${task}")
handleTurn(messages, task)
case None => repl(messages)
}
}
// 交互式对话循环。
func repl(messages: ArrayList<Message>): Unit {
println("进入交互模式,输入任务开始;/exit 或 Ctrl+Z 回车退出。")
while (true) {
print("\n🧑 你> ")
match (getStdIn().readln()) {
case Some(line) =>
let text = line.trimAscii()
if (text.isEmpty()) {
continue
}
if (text == "/exit" || text == "/quit") {
break
}
handleTurn(messages, text)
case None => break
}
}
println("\n再见。")
}
// 处理一轮用户输入:多步工具调用直至模型给出最终答复。
func handleTurn(messages: ArrayList<Message>, userInput: String): Unit {
messages.add(Message.user(userInput))
var step = 0
while (step < cfg.maxSteps) {
step += 1
let (content, toolCalls) = try {
llm.complete(messages, tools)
} catch (e: Exception) {
println("[警告] 调用模型失败:${e.message}")
return
}
if (toolCalls.size == 0) {
if (!content.isEmpty()) {
println("\n🤖 ${content}")
}
messages.add(Message.assistant(content))
return
}
// 模型请求调用工具:记录 assistant 消息(含 tool_calls),再逐个执行。
if (!content.isEmpty()) {
println("\n🤖 ${content}")
}
messages.add(Message.assistant(content, tcs: toolCalls))
for (tc in toolCalls) {
messages.add(Message.tool(tc.id, dispatch(tc)))
}
}
println("[警告] 已达最大步数(${cfg.maxSteps}),中止本轮。")
}
// 执行单个工具调用并打印简洁轨迹。
func dispatch(tc: ToolCall): String {
println(" » ${tc.name} ${preview(tc.arguments, 120)}")
let tool = match (byName.get(tc.name)) {
case Some(t) => t
case None => return "错误:未知工具 ${tc.name}"
}
let args = try {
JsonValue.fromStr(tc.arguments).asObject()
} catch (_: Exception) {
return "错误:无法解析工具参数:${tc.arguments}"
}
let result = tool.handler(args)
println(" ↳ ${preview(firstLine(result), 160)}")
return result
}
func printBanner(): Unit {
println("┌─ 极简仓颉 Coding Agent ─────────────────────")
println("│ 沙箱目录: ${cfg.root}")
println("│ 模型: ${cfg.model}")
println("│ 端点: ${cfg.endpoint}")
println("│ 工具: read_file / write_file / list_dir / run_command")
println("└─────────────────────────────────────────────")
}
}
func systemPrompt(root: Path): String {
return """
你是一个运行在命令行中的编码助手(coding agent),在一个受限的工作目录里帮用户完成开发任务。
工作目录(沙箱根):${root}
你只能在该目录内读写文件、执行命令;所有路径都相对于它,无法也不得访问其外层。
可用工具:
- read_file(path) 读取文件文本
- write_file(path, content) 写入/覆盖文件(父目录自动创建)
- list_dir(path) 列目录,path 可省略表示根目录
- run_command(command) 在工作目录内执行 shell 命令(编译、运行、测试)
工作准则:
1. 先用 list_dir / read_file 摸清现状,再动手改。
2. 只做必要的最小改动,代码保持整洁规范。
3. 需要验证时用 run_command 编译或运行。
4. 任务完成后用简洁中文说明你做了什么,并停止调用工具。
"""
}
// 取首行,便于轨迹展示。
func firstLine(s: String): String {
match (s.indexOf("\n")) {
case Some(i) => s[0..i]
case None => s
}
}
// 单行化并截断,用于打印工具调用轨迹。
func preview(s: String, limit: Int64): String {
truncate(s.replace("\n", " "), limit)
}