# AACode-rs 设计文档
> Rust 版 AACode 编码 Agent —— 用于集成到 AACodeApp 安卓客户端
>
> 位置:`/Volumes/lenovops9/fastshell_local/aacode-rs/`
> 状态:设计阶段(尚未开始编码)
> python版位置:/Users/xiefujin_mac2025/PycharmProjects/aacode_local
>
> 说明:Rust crate 名与目录名统一为 `aacode-rs`,以区别于 Python 版 `aacode`。
---
## 1. 背景与目标
### 1.1 现状
AACode 目前是一个 **100% Python 的 ReAct 架构 AI 编码 Agent**(源码:`PycharmProjects/aacode_local/aacode/`)。它通过 DeepSeek/OpenAI/Anthropic 兼容 API 驱动,核心能力是"用 `run_shell` 作为万能适配器 + 文件化上下文 + 分层工具系统"完成编码任务。目前已经经过几十个版本优化,有不少用户,运行良好。
安卓客户端 `AACodeApp` 的既定方案是:
- 用 Rust 写的 `fastshell` 提供沙箱 Shell(180+ 命令)与内嵌 CPython 3.12;
- 把 aacode 的 Python 源码 + pip 依赖打包进 APK assets,运行时拷贝到沙箱;
- 通过 JNI → `fastshell_execute_python_script()` 在内嵌 CPython 里跑 aacode,用文件轮询方式把 JSONL 事件回传给 Kotlin UI。
**问题**:内嵌 CPython 这条路走不通(`libpython3.12.so` 动态加载、pip 依赖 native 扩展、asyncio/子进程拦截、ARM64 MTE、冷启动与体积等一系列脆弱点),Python 在安卓上跑不起来或极不稳定。
### 1.2 目标
**用 Rust 重新实现 aacode 的完整功能**,直接编译进 `libfastshell.a`/`libaacode_rs.a`,彻底去掉对 CPython 与 pip 依赖的运行时需求:
- Agent 循环、LLM 调用、工具系统全部原生 Rust;
- 工具执行复用 `fastshell` 已有的沙箱 Shell 引擎(`Fastshell::execute()`),无需子进程、无需系统 `/bin/sh`;
- 通过 C ABI + JNI 暴露给安卓,**输出协议与现有 Kotlin `AgentOutputParser` 完全兼容**,尽量少改 App 端;
- 保持桌面 CLI 可运行(便于本地开发调试)。
### 1.3 范围说明
**本次为一次性完整实现**:复刻 Python 版 aacode 的全部核心功能(Agent 循环、LLM 双网关、完整工具集、会话/上下文/压缩、安全、多模态、MCP、Skills、Sub-Agent 委托),不做分期裁剪。
仅有两点因平台性质天然不同:
- 不内嵌 Python(Rust 版不依赖 CPython)。因此 Python 型 skill 通过内嵌 CPython 执行的能力不适用;`run_skills` 采用"文档型 skill + 借助 `run_shell` 执行"的等价实现(见 §5.3)。
- 不实现桌面 Tauri GUI(那是 Python 版的桌面客户端;本 crate 只提供 CLI + FFI)。
---
## 2. 源工程分析总结(三方对照)
### 2.1 Python 版 aacode 核心结构
| 入口 | `main.py` (836) | CLI 解析、`AICoder` 类、交互式会话循环 |
| 主 Agent | `core/main_agent.py` (1705) | 模型调用器(OpenAI/Anthropic 流式 + 原生 function calling)、工具装配、会话/todo 编排、`execute()` |
| ReAct 循环 | `core/react_loop.py` (2213) | 迭代循环、tool_calls 执行、上下文压缩(round-aware compact view)、死循环检测、自适应截断 |
| Agent 基类 | `core/agent.py` (248) | `BaseAgent`、文本回退解析 |
| 子 Agent | `core/sub_agent.py` | 专用子 Agent(code/test/research)|
| 提示词 | `core/prompts.py` (131) | 主系统提示词(工具说明 + TDD 规则 + 工作流)|
| 原子工具 | `tools/atomic_tools.py` (195) | 仅 `run_shell`(异步子进程 + 安全检查 + 流式行读取 + 截断/归档)|
| 网络工具 | `tools/web_tools.py` | `search_web`(SearXNG/Brave/Google/Bing/SerpAPI + HTML 回退)、`fetch_url`、`search_code` |
| Todo 工具 | `tools/todo_tools.py` (256) | add/mark/update/summary/list,按 session 隔离 |
| Skills | `tools/skills_tools.py` | `run_skills`(__list__/__info__/execute 三模式),从 `SKILL.md` 自动发现 |
| 多模态 | `tools/multimodal_tools.py` | 图片/视频/UI 稿理解 |
| 工具 Schema | `utils/tool_schemas.py` (616) | 所有工具的参数 schema(含别名、示例)|
| 工具注册表 | `utils/tool_registry.py` (267) | 参数校验、别名归一化、文档生成、相似工具建议 |
| 工具适配 | `utils/tool_adapter.py` (101) | Schema → OpenAI/Anthropic native tools 格式 |
| 会话管理 | `utils/session_manager.py` (686) | `.aacode/sessions/*.json` 持久化、token 计数、多轮衔接、原子写盘 + 文件锁 |
| Todo 管理 | `utils/todo_manager.py` | markdown todo 文件读写 |
| 上下文管理 | `utils/context_manager.py` (550) | 文件化上下文、大输出归档到 `.aacode/context/` |
| 消息工具 | `utils/message_utils.py` | token 估算、按 round 拆分、compact view 构建 |
| 安全 | `utils/safety.py` (2400+) | 命令风险评估、路径检查、危险命令确认、AST 检查 |
| 配置 | `config.py` (704) | 模型/工具/安全/上下文/超时/限制/skills/多模态 配置,env > yaml |
| 服务器 | `server/runner.py` (191) | `AICoderRunner`:流式事件生成、API key 校验、会话列表 |
| 多 Agent | `core/multi_agent.py` | 多 Agent 系统:任务委托与协调 |
**关键运行流程**:
```
AICoder.run(task)
→ 分析项目结构(class-method map)
→ 创建 todo list
→ MainAgent.execute(task, ...)
→ 组装完整 system prompt(SYSTEM_PROMPT + skills + working_dir + analysis + init + todo)
→ 载入历史消息(多轮衔接)
→ AsyncReActLoop.run()
for iteration in max_iterations:
messages = build_compact_view(messages) # round-aware 压缩
response = model_caller(messages) # 流式,返回 {text, tool_calls, reasoning_content}
if no tool_calls: 任务完成,输出总结 → break
for each tool_call: execute → observation
append assistant(tool_calls) + tool(results) 到 messages
增量持久化 messages 到 session 文件
if tokens > trigger: compact_context() # AI 生成三段式摘要
```
**LLM 调用要点**(`main_agent.py`):
- OpenAI 网关:`client.chat.completions.create(stream=True, tools=..., tool_choice="auto")`,逐 chunk 解析 `delta.content` / `delta.reasoning_content` / `delta.tool_calls`(流式累积 tool_calls 的 name/arguments);`finish_reason == "length"` 检测截断。
- Anthropic 网关:`client.messages.stream(...)`,解析 `content_block_delta`(thinking_delta/text_delta)+ `get_final_message()` 拿 tool_use。
- 两个网关的 base_url 会按模型名自动推断(deepseek/kimi/minimax/claude/gpt)。
### 2.2 fastshell(Rust)现状
- 单 workspace 成员 `fastshell/`,`crate-type = ["lib", "cdylib", "staticlib"]`。
- **可直接复用**:`Fastshell` SDK(`sdk/mod.rs`),`Fastshell::execute(command) -> CommandResult { stdout, stderr, exit_code }`,沙箱 VFS,180+ 命令,网络权限门控。
- **已有 C ABI**(`sdk/ffi.rs`):`fastshell_init/execute/execute_python/get_cwd/set_permission/cancel_execution/register_stream_callback/start_agent_server/submit_task/free_string`。
- **已有流式回调基建**(历史):`fastshell_register_stream_callback` + C 侧 `stream_trampoline` → Kotlin `onChunk(String)`。aacode-rs 的 Agent 流式改走**句柄式 + userdata**(`aacode_task_start` 的 `cb(line, userdata)`),不再复用该注册槽。
- **`start_agent_server`/`submit_task` 目前委托给 Python 引擎**(stub),Rust 版可接管。
- 依赖已含:`serde/serde_json`、`ureq`(同步 HTTP + rustls)、`tokio`(rt/net/io-util/macros)、`regex`、`uuid`、`base64`、`sha2`。**没有 LLM 客户端**。
### 2.3 AACodeApp(安卓)集成契约
- Kotlin `com.fastshell.Sdk` 通过 `System.loadLibrary("fastshell_jni")` 加载,`external fun nativeXxx(...)` → `jni_glue.c` → Rust C ABI。
- 现用 **文件轮询** 读 Agent 输出:Python 把 JSONL 写到 `tmp/agent_<uuid>.jsonl`,Kotlin `AgentBridge` 轮询解析。
- **`AgentOutputParser` 期望的事件协议**(这是 Rust 版必须严格对齐的输出契约):
- 以 `{` 开头的行按 JSON 事件解析,`type` 取值:`seg_content`(含 `seg` ∈ {thinking, thought, action, observation}、`content`)、`tool_progress`(`state` ∈ {building, done}、`name`、`chars`)、`done`、`error`、`start`、`session_created`、`session_switched`;
- 其它行按"流式 token"处理,`\x00` 需还原为 `\n`(管道模式下 aacode 用 `\x00` 转义 token 内换行)。
- App 只编 `arm64-v8a`,NDK 27,`minSdk 26`,CMake 链接 `libfastshell.a` + `jni_glue.c` → `libfastshell_jni.so`。
- `AndroidManifest` 已设 `memtagMode="off"`。
**结论**:Rust 版只要 (1) 复用 `Fastshell::execute` 作为 run_shell 后端,(2) 通过流式回调按上面的 JSONL 协议 emit 事件,就能在**几乎不改 App UI 层**的前提下替换掉 Python Agent。
---
## 3. 总体架构
```
┌──────────────────────────────────────────────────────────────┐
│ Android App (Kotlin) │
│ ChatViewModel → AgentBridge → com.fastshell.Sdk (JNI) │
└───────────────────────────┬──────────────────────────────────┘
│ nativeAgentRunTaskWithCallback(taskJson, cb) → jlong
┌───────────────────────────▼──────────────────────────────────┐
│ fastshell_c/jni_glue.c (C ABI 桥) │
└───────────────────────────┬──────────────────────────────────┘
│ aacode_task_start() / wait / cancel / free
┌───────────────────────────▼──────────────────────────────────┐
│ aacode-rs crate (Rust) ★新增 │
│ ┌────────────┐ ┌───────────┐ ┌──────────┐ ┌────────────┐ │
│ │ ffi.rs │ │ agent/ │ │ llm/ │ │ tools/ │ │
│ │ C ABI + │→ │ react loop│→ │ openai │ │ registry │ │
│ │ JSONL emit │ │ main agent│ │ anthropic│ │ shell/web/ │ │
│ └────────────┘ └───────────┘ │ sse parse│ │ todo/... │ │
│ ┌────────────┐ ┌───────────┐ └──────────┘ └─────┬──────┘ │
│ │ session │ │ context │ ┌──────────┐ │ │
│ │ todo │ │ compact │ │ config │ │ │
│ └────────────┘ └───────────┘ └──────────┘ │ │
└──────────────────────────────────────────────────────┼────────┘
│ run_shell
┌───────────────────────────────────────────────────────▼────────┐
│ fastshell crate (Rust, 已有) │
│ Fastshell::execute() → Shell(180+ cmds) → VFS 沙箱 │
└──────────────────────────────────────────────────────────────┘
```
### 3.1 Crate 组织方案
**方案 A(推荐):`aacode-rs` 独立 workspace 成员,依赖 `fastshell`**
- 目录:`fastshell_local/aacode-rs/`(`Cargo.toml` + `src/`)。crate 名 `aacode-rs`(lib 名 `aacode_rs`),FFI 符号前缀仍用 `aacode_*`(C 标识符不能带 `-`)。
- `aacode-rs/Cargo.toml`:`[package] name = "aacode-rs"`,`crate-type = ["lib", "staticlib", "cdylib"]`,`[dependencies] fastshell = { path = "../fastshell" }`。
- 根 `Cargo.toml` `members = ["fastshell", "aacode-rs"]`。
- 安卓构建切换为 `cargo build -p aacode-rs --target aarch64-linux-android --lib` → `libaacode_rs.a`(静态包含 fastshell)。`jni_glue.c` 链接 `libaacode_rs.a`,可同时使用 `fastshell_*` 与 `aacode_*` 两套 C ABI(fastshell 的符号仍在归档里)。
- 优点:物理隔离清晰、无循环依赖、fastshell 保持纯运行时不被 Agent 逻辑污染。
- 代价:安卓 CMake/`build.sh` 需把 `-p fastshell` 改成 `-p aacode-rs`,产物名 `libfastshell.a` → `libaacode_rs.a`(`c_dist/arm64-v8a/`)。
> 备选方案 B:把 Agent 作为 `fastshell` 的 feature-gated 模块(`--features agent`),复用现有 `fastshell_submit_task` 符号,App 端零改动。缺点是 Agent 逻辑与运行时耦合。**采用方案 A**,并在 ffi 层保留一组"形状"与 `fastshell_submit_task` 一致的入口以便平滑迁移。
### 3.2 运行模型(并发/线程)
- Android/JNI 侧:`aacode_task_start` 在**共享 tokio runtime 的 worker 线程**运行(不阻塞 JNI 调用线程),通过 `cb(line, userdata)` 流式回调把 JSONL 行推给 Kotlin(每任务独立 userdata,比文件轮询延迟更低、更简单)。
- LLM HTTP 采用**同步 `ureq` + 手写 SSE 行解析**(在 worker 线程里阻塞读取 response body 的 `Read`),避免引入 `reqwest` + 全量 tokio,控制二进制体积。(备选:`reqwest`+`tokio` 异步流,若后续需要并发多请求再切换。)
- 取消:`aacode_task_cancel(handle)` 置 `AtomicBool`,ReAct 循环与 SSE 读取在检查点响应取消;同时调用 `fastshell_cancel_execution()` 打断正在跑的 shell。
- 全局单例:`OnceLock<Mutex<AgentRuntime>>`,与 fastshell FFI 单例风格一致。
---
## 4. 代码结构(模块设计)
```
aacode-rs/
├── Cargo.toml
├── design.md # 本文档
├── README.md # 使用说明(后续)
└── src/
├── lib.rs # crate 根,pub mod 声明与再导出
├── config.rs # AgentConfig:模型/网关/超时/限制/安全,env + JSON
├── error.rs # AacodeError 统一错误类型(thiserror 风格,手写或引入)
│
├── llm/
│ ├── mod.rs # LlmClient trait + 网关分发 + base_url 自动推断
│ ├── openai.rs # OpenAI 兼容:chat/completions 流式 + tool_calls 累积
│ ├── anthropic.rs # Anthropic 兼容:messages stream + tool_use
│ ├── sse.rs # SSE 行解析器(data: {...} 分帧)
│ └── types.rs # ChatMessage, ToolCall, LlmResponse{text,tool_calls,reasoning}
│
├── agent/
│ ├── mod.rs
│ ├── main_agent.rs # MainAgent:装配工具、组装 system prompt、execute()
│ ├── react_loop.rs # ReactLoop:迭代、tool_calls 执行、完成判定、重试、死循环检测
│ ├── sub_agent.rs # SubAgent:code/test/research 专用子 Agent
│ ├── prompts.rs # SYSTEM_PROMPT 常量(移植 core/prompts.py)
│ └── compact.rs # 上下文压缩:round 拆分 + compact view + 三段式摘要
│
├── tools/
│ ├── mod.rs
│ ├── registry.rs # ToolRegistry:注册、校验、别名归一化、not-found 建议
│ ├── schema.rs # ToolSchema/ToolParameter + to_openai_tools/to_anthropic_tools
│ ├── shell.rs # run_shell:转发 fastshell::Fastshell::execute + 截断/归档
│ ├── web.rs # search_web / fetch_url / search_code(ureq)
│ ├── todo.rs # add/mark/update/summary/list(markdown 文件)
│ ├── session_tools.rs # new/continue/list/switch/delete/history/stats 会话工具
│ ├── skills.rs # run_skills(三模式,SKILL.md 自动发现,文档型 skill)
│ ├── mcp.rs # list/call/status(MCP stdio + sse 客户端)
│ ├── multimodal.rs # understand_image/video/ui_design/consistency(vision API)
│ └── delegate.rs # delegate_task / create_sub_agent 工具入口
│
├── mcp/
│ └── mod.rs # MCP 管理器:stdio/sse 传输、工具发现、调用
│
├── session/
│ └── mod.rs # SessionManager:.aacode/sessions/*.json 读写、多轮衔接
│
├── context/
│ └── mod.rs # 大输出归档 .aacode/context|extracts/、init.md 载入、项目结构分析
│
├── stream/
│ └── mod.rs # EventEmitter:seg_content/tool_progress/done/error/token
│ # —— 严格对齐 Kotlin AgentOutputParser 协议
│
├── bin/
│ └── aacode.rs # 桌面 CLI 入口(-p/-t/--session/--plan-first/--interactive)
│
└── ffi.rs # C ABI:aacode_task_start/wait/cancel/free...
```
### 4.1 关键类型草图(示意,非最终 API)
```rust
// llm/types.rs
pub struct ChatMessage {
pub role: String, // system|user|assistant|tool
pub content: String,
pub tool_calls: Option<Vec<ToolCall>>, // assistant
pub tool_call_id: Option<String>, // tool
pub reasoning_content: Option<String>,
}
pub struct ToolCall { pub id: String, pub name: String, pub arguments: String /* JSON */ }
pub struct LlmResponse {
pub text: String,
pub tool_calls: Vec<ToolCall>,
pub reasoning_content: Option<String>,
pub finish_reason: Option<String>, // "length" => 截断告警
}
// llm/mod.rs
pub trait LlmClient: Send {
/// 流式调用;每个可显示片段通过 emitter 推送(thinking/thought token)
fn chat_stream(
&self,
messages: &[ChatMessage],
tools: &[serde_json::Value],
emitter: &dyn EventSink,
cancel: &AtomicBool,
) -> Result<LlmResponse, AacodeError>;
}
// tools/registry.rs
pub trait Tool: Send + Sync {
fn name(&self) -> &str;
fn schema(&self) -> &ToolSchema;
fn call(&self, args: serde_json::Value) -> Result<String, AacodeError>; // 返回 observation
}
// stream/mod.rs —— 输出协议核心
pub trait EventSink: Send {
fn seg(&self, seg: &str, content: &str); // seg_content
fn tool_progress(&self, state: &str, name: &str, chars: usize);
fn token(&self, text: &str); // 流式 token(\n → \x00)
fn started(&self, task: &str); // start
fn session(&self, ev: &str, session_id: &str); // session_created/switched
fn done(&self, session_id: &str);
fn error(&self, message: &str);
}
```
### 4.2 run_shell(工具层与 fastshell 的对接)
```rust
// tools/shell.rs
pub struct ShellTool { fs: Arc<Mutex<fastshell::Fastshell>>, sandbox: PathBuf }
impl Tool for ShellTool {
fn call(&self, args: Value) -> Result<String, AacodeError> {
let cmd = args["command"].as_str().ok_or(...)?;
let max_output = args["max_output"].as_u64();
let r = self.fs.lock().unwrap().execute(cmd); // CommandResult{stdout,stderr,exit_code}
// 组装 JSON:{success, returncode, stdout, stderr, command}
// 超长时写入 .aacode/extracts/ 并返回预览 + 归档路径(对齐 Python 行为)
}
}
```
> 注意:fastshell 已在 VFS 沙箱内执行,`timeout` 由 `Fastshell::execute` 支持(`config.timeout`)。危险命令确认在移动端默认放行/或走权限回调(见 §8 安全)。
---
## 5. 功能清单(Feature List)
**全部一次性实现,不分优先级。** 以下为对齐 Python 版的完整能力清单,均为本次交付范围。
### 5.1 Agent 核心
- ReAct 迭代循环(max_iterations、完成判定:无 tool_calls 即完成并输出总结)
- 原生 function calling(OpenAI tools / Anthropic tools)
- 流式输出(token 级 + thinking/reasoning 分离)
- 多工具并行调用(一轮多个 tool_calls)
- 工具执行错误重试(可重试错误:timeout/connection,指数退避)
- 完成后自包含总结
- 上下文压缩(round-aware compact view + 触发阈值,保护首轮 + 最近 N 轮 user)
- AI 生成三段式历史摘要(文件内容 / 工具执行 / 保留信息)
- 死循环检测(fetch_url stale domain:同域名连续 N 次无可读内容时告警)
- Sub-Agent 委托(delegate_task / create_sub_agent,code/test/research 专用提示词)
- Plan-first 模式(`--plan-first`:先在 thought 中规划再执行)
- 项目结构分析(class-method map 生成,任务前注入上下文)
### 5.2 LLM 客户端
- OpenAI 兼容网关(DeepSeek/Kimi/GPT 等)流式 + tool_calls 分片累积
- Anthropic 兼容网关(MiniMax/Claude)messages stream + tool_use + thinking
- base_url 按模型名 + gateway 自动推断(deepseek/kimi/minimax/claude/gpt → 端点)
- `finish_reason == length` / `stop_reason == max_tokens` 截断告警
- 网络错误自动重试(指数退避,5xx/连接/超时)
- API key 缺失/401/配额 友好错误分类
- API key 校验(validate_api_key 等价,轻量探测请求)
- 流式失败降级为非流式(Anthropic 路径)
### 5.3 工具集(全部实现)
- `run_shell`(转发 fastshell,截断/归档,max_output,stdin_input,timeout)
- `add_todo_item` / `mark_todo_completed` / `update_todo_item` / `get_todo_summary` / `list_todo_files` / `add_execution_record`
- `search_web`(SearXNG / Brave / Google CSE / Bing / SerpAPI 多后端 + HTML 回退抓取)
- `fetch_url`(ureq + HTML 清洗/预览 + JS 重定向探测 + 结果落盘)
- `search_code`(代码示例搜索)
- 会话管理工具(new_session / continue_session / list_sessions / switch_session / delete_session / get_conversation_history / get_session_stats)
- `run_skills`(三模式 `__list__` / `__info__` / execute;从 `SKILL.md` 自动发现;文档型 skill 返回指令由模型借 `run_shell` 执行)
- MCP 工具(list_mcp_tools / call_mcp_tool / get_mcp_status;stdio + sse 客户端)
- 多模态工具(understand_image / understand_video / understand_ui_design / analyze_image_consistency,走 vision API)
- Sub-Agent 委托工具(delegate_task / create_sub_agent)
### 5.4 会话 / 上下文 / 持久化
- Session:`.aacode/sessions/<id>.json` 读写、`sessions_index.json` 索引、增量持久化、多轮衔接
- 原子写盘(临时文件 + rename)+ 文件锁(跨进程并发保护 + index 合并)
- token 计数(字符估算 4:1;不依赖 tiktoken)
- `init.md` 项目指令载入(不存在时生成默认模板)
- 大输出归档到 `.aacode/context/`、`.aacode/extracts/`(预览 + 归档路径)
- 会话恢复保护(上次中断在 tool 消息时注入恢复提示)
- 旧版 session JSON 格式兼容(float 时间戳、占位 system 消息过滤)
### 5.5 工具注册 / 校验
- ToolSchema + 参数别名归一化 + 必填/类型校验
- Schema → OpenAI / Anthropic tools 格式转换
- 未知工具的相似名建议 + 工具文档回传
- 参数缺失 / 类型错误 的友好错误消息(复刻 Python 体验)
### 5.6 配置
- 模型配置(name/api_key/base_url/gateway/temperature/max_tokens/multimodal)
- env 优先(`LLM_API_KEY`/`LLM_API_URL`/`LLM_MODEL_NAME`/`LLM_GATEWAY`/`LLM_MULTIMODAL`)
- 通过 `aacode_task_start` 的 taskJson 传入运行时配置(App 从设置页下发)
- JSON 配置文件(沙箱内 `aacode_config.json`)
- 搜索引擎配置(`SEARCHXNG_URL`/`SEARCH_API_KEY` 等)
- 安全 / 上下文 / 超时 / 限制 / skills / 多模态 各分区配置
### 5.7 平台集成
- C ABI:`aacode_task_start` / `aacode_task_wait` / `aacode_task_cancel` / `aacode_task_free` / `aacode_free_string` / `aacode_validate_api_key` / `aacode_list_sessions` / `aacode_get_session_messages`
- JSONL 事件输出协议(对齐 Kotlin `AgentOutputParser`)
- 桌面 CLI(`bin/aacode.rs`:`-p <project> "task"`,交互式会话模式)
- JNI glue(`jni_glue.c`:`nativeAgentRunTaskWithCallback`(返回句柄)/ `nativeAgentWaitTask` / `nativeAgentCancelTask` / `nativeAgentFreeTask`)
- 会话列表 / 消息读取 C ABI(供 App 历史页)
---
## 6. LLM 流式与工具调用设计(重点)
### 6.1 OpenAI 兼容流式解析(`llm/openai.rs`)
请求体:`{model, messages, temperature, max_tokens, stream:true, tools, tool_choice:"auto"}`。用 `ureq` POST,`Accept: text/event-stream`,读取响应 body 的 `Read`,按 SSE 行解析(`sse.rs`):
```
每行 "data: {json}":
delta.reasoning_content → emitter.seg("thinking", ...) + token 累积到 reasoning_content
delta.content → emitter.seg/token 到 thought,累积到 text
delta.tool_calls[i] → 按 index 累积 id/function.name/function.arguments
首次拿到 name 时 emitter.tool_progress("building", name, 0)
arguments 每增长 ~500 字符 emit 一次 tool_progress
choices[0].finish_reason=="length" → 记录截断告警
"data: [DONE]" → 结束
```
流结束后:组装 `tool_calls` 列表,对每个 emit `tool_progress("done", name, 0)` 与 `🛠️ Action: {name}` 行(对齐 Python 管道输出)。thinking 与正文分离返回。
### 6.2 Anthropic 兼容流式(`llm/anthropic.rs`)
`messages` 端点 SSE:`content_block_start/delta`(`thinking_delta`/`text_delta`)+ `content_block_stop`;最终从 `message_stop` 或聚合的 content blocks 提取 `tool_use`(id/name/input)。system 消息单独提到顶层 `system` 字段;tool 结果转成 `role:user` 的 `tool_result` block。base_url 处理 `/v1` → `/anthropic` 的适配(复刻 Python 逻辑)。流式失败时降级为非流式 `messages.create`。
### 6.3 ReAct 循环(`agent/react_loop.rs`)
```rust
messages = [system_prompt] + history + [user_task];
for it in 0..max_iterations {
if cancel.load() { emit error("cancelled"); break; }
let view = compact::build_view(&messages, &cfg); // round-aware 压缩视图
let resp = llm.chat_stream(&view, &tools_json, emitter, cancel)?;
if resp.tool_calls.is_empty() { // 完成
messages.push(assistant(resp.text));
persist(&messages);
emitter.done(session_id); break;
}
if let Some(w) = detect_stale_loop(&resp.tool_calls) { // 死循环检测
messages.push(system(format!("[SYSTEM WARNING]: {w}")));
}
messages.push(assistant_with_tool_calls(resp));
for tc in &resp.tool_calls {
let obs = registry.execute(&tc.name, parse(tc.arguments), cancel);
emitter.seg("observation", &preview(obs));
messages.push(tool_result(tc.id, obs));
}
persist(&messages); // 增量持久化
if estimate_tokens(&messages) > trigger { compact(&mut messages); } // 上下文压缩
}
```
**完成判定**与 Python 一致:模型不再返回 tool_calls 即视为完成,其文本作为最终总结。
---
## 7. 输出事件协议(与 Android 对齐 —— 必须严格遵守)
`stream::EventSink` 产出的每一行写入回调(管道模式),格式:
| start | `{"type":"start","task":"..."}` | 任务开始 |
| session_created | `{"type":"session_created","session_id":"..."}` | 新建会话 |
| session_switched | `{"type":"session_switched","session_id":"..."}` | 切换会话 |
| thinking 段 | `{"type":"seg_content","seg":"thinking","content":"..."}` | reasoning 段结束 |
| thought 段 | `{"type":"seg_content","seg":"thought","content":"..."}` | 正文段结束 |
| action | `🛠️ Action: {name}\x00Action Input: {args}` (非 `{` 开头,走 token 分支) | 工具调用声明 |
| tool_progress | `{"type":"tool_progress","state":"building\|done","name":"...","chars":N}` | 参数流式累积/完成 |
| observation | `{"type":"seg_content","seg":"observation","content":"..."}` | 工具执行结果 |
| token | 原始文本行(`\n` → `\x00`) | 流式 token |
| done | `{"type":"done","session_id":"..."}` | 任务完成 |
| error | `{"type":"error","message":"..."}` | 出错 |
> 这些 `type`/`seg` 取值直接来自 Kotlin `AgentOutputParser.parseLine()` 与 Python `main_agent.py` 的 `seg_content` 输出,Rust 版逐字复刻即可零改 App 解析层。
**双模式**:
- 管道模式(Android/子进程):JSON 事件独占一行;token 行内换行用 `\x00` 转义。
- TTY 模式(桌面 CLI):直接彩色打印,不转义。
用 `is_tty`(`libc::isatty`)区分,与 Python `_is_tty` 一致。
---
## 8. 安全模型
Python 版有 2400 行 `SafetyGuard`(命令风险评估、路径限制、AST 检查、危险命令弹窗确认)。Rust 版分层处理:
- **首要防线复用 fastshell**:所有命令都在 VFS 沙箱内执行,路径逃逸已被 `Vfs::resolve` 拦截;网络访问有 `network_ask_permission` 门控。**移动端不再需要重造大部分 SafetyGuard**。
- **危险命令策略**(`config.safety.dangerous_command_action`):
- `log`(移动端默认):记录不拦截;
- `reject`:命中危险模式(`rm -rf /`、fork bomb、写系统路径等,用 `regex` 维护一份精简黑名单)直接拒绝;
- `ask`:通过 fastshell 的权限回调/App 弹窗确认。
- 不移植 Python AST 安全检查(Rust 版不执行 Python 代码)。
- 秘钥安全:API key 仅存于内存/App 加密存储,日志脱敏,绝不写入 session 文件明文(沿用 Python 只存对话不存 key 的做法)。
---
## 9. 代码执行与测试策略
Agent"写完代码要能测"是核心闭环。这里区分两件**性质完全不同**的事:
- **跑 aacode 这个 Agent 框架本身**(openai/anthropic/aiohttp/pydantic + asyncio + 大量带 native 扩展的 pip 包)——又重又脆,是"Python 在安卓走不通"的真正原因,故用 Rust 重写。
- **跑用户写的代码做测试**(`python3 test.py`、`pytest` 等)——轻量得多,只要有 Python 运行时即可,无需 Agent 依赖栈。
**关键事实**:`fastshell` 已内嵌 CPython 3.12(Chaquopy 提供的 `libpython3.12.so` + 标准库 + `site-packages`)。Rust 版 aacode 自己不带 Python,但它的 `run_shell` 转发到 `Fastshell::execute()`,fastshell 的 shell 引擎会**识别 `python`/`python3` 命令并路由到内嵌 CPython 引擎执行**(`bridge/executor.rs::is_python_command`、`python/mod.rs`)。`wrap_code_with_syspath()` 已把 `python/site-packages` 注入 `sys.path`。
### 9.1 Python 测试链路(本地内嵌 CPython)
```
模型: run_shell("python3 -c 'import unittest; ...'")
→ aacode-rs tools::shell
→ fastshell Runtime.is_python_command() 识别
→ 内嵌 CPython VM 执行 → stdout/stderr/exit_code 回传给模型
→ 模型据 returncode 判断通过与否,决定是否继续修复
```
语法快检同理:`run_shell("python3 -c 'import ast; ast.parse(open(\"x.py\").read())'")`。
### 9.2 fastshell 前置依赖(必须补齐,否则 Python 测试不闭环)⚠️
审阅 `bridge/executor.rs` 后发现**当前内嵌 CPython 仅稳妥支持 `python -c "内联代码"`**,以下常用形式尚不支持,需在 **fastshell 侧**补齐(属 fastshell 能力,非 aacode-rs):
| `python3 -c "code"` | ✅ 正确路由到 `execute()` | —— |
| `python3 test.py [args]` | ❌ 整串被当作 Python 源码 → SyntaxError | `extract_python_code` 增加脚本分支 → 路由到 `execute_script`(该函数已存在可用)|
| `python -m pytest` / `-m unittest` | ❌ `is_python_command` 不识别 `-m` 语义 | 支持 `python -m <module> [args]` |
| 裸 `pytest` / `pytest tests/` | ❌ 走 shell fallthrough,找不到命令 | 可选:rewrite 成 `python -m pytest`;或注册为内嵌入口 |
> `execute_python_script(script_path, cwd)` 在 `python/mod.rs:396`、`bridge/executor.rs:444` 已实现且可用,补齐主要是 `is_python_command` / `extract_python_code` 的分派逻辑。
**aacode-rs 侧的配合**(在 fastshell 增强落地前的兜底):系统提示词引导模型优先用**已确认可用的调用形式**(如 `python3 -c "..."` 内联,或 `execute_script` 对应形式)跑测试;并把上述 fastshell 增强列为**本项目的硬依赖项**,随 aacode-rs 一并推进。
### 9.3 能力边界
| 纯 Python + 标准库 | ✅ | 内嵌 CPython 直接跑 |
| 带 native 扩展的 pip 包(numpy/pandas…) | ⚠️ | 需预置对应 Android ABI 的 wheel 到 `site-packages`,否则 import 失败 |
| Shell 脚本 / fastshell 180+ 命令可验证的 | ✅ | fastshell 原生执行 |
| 语法/静态检查(任意语言) | ✅ | `ast.parse`、lint 等不需运行时 |
| Node / Go / Rust / C / Java 等 | ❌ | 手机上无对应运行时,见 §9.5 远程扩展点 |
| 重型/长时/需 GPU 的测试 | ❌ | 受手机 CPU/内存限制,见 §9.5 |
### 9.4 分层测试策略(Agent 提示词与工具引导)
1. **静态校验优先**:语法解析(CPython `ast`)、lint —— 最稳、零运行时依赖。
2. **内嵌 CPython 执行**:Python 单测(stdlib + 已打包 wheel)。
3. **fastshell shell 级验证**:文件存在性、grep、diff、git 状态等。
4. **兜底**:本地跑不动的 → 走 §9.5。
### 9.5 远程执行扩展点(其他语言 / 重型测试)
对于手机本地无法执行的场景(其它语言运行时、重型/长时测试),提供**远程执行扩展点**(本次为可选/预留接口,非强依赖):
- **git commit & push → 远程 CI**:复用 AACodeApp 已有的 Git 能力(`ChangesScreen` 的 commit & push),把代码推到远端仓库触发 CI,回读 CI 状态/日志作为测试结果。
- **远程执行沙箱**:可选接一个服务端沙箱 API(发送代码/测试命令 → 服务端执行 → 回传 stdout/stderr/exit_code),作为 `run_shell` 的旁路工具(如 `run_remote`)。这也是手机端 coding agent 的通用做法。
- 设计上以 `tools::` 下新增工具的形式接入,不影响本地执行主链路;具体后端 URL/鉴权走配置下发。
---
## 10. 配置与初始化
### 10.1 配置来源优先级
`taskJson 内联配置 > 环境变量 > 沙箱内 aacode_config.json > 默认值`
### 10.2 `aacode_task_start` 的 taskJson 结构(示意)
```json
{
"task": "给登录表单加输入校验",
"project_path": "/sandbox/projects/demo",
"session_id": "session_2026...", // 可选,续接会话
"model": {
"name": "deepseek-chat",
"api_key": "sk-...",
"base_url": "https://api.deepseek.com/v1",
"gateway": "openai",
"temperature": 0.1,
"max_tokens": 8192
},
"max_iterations": 30,
"plan_first": false, // 可选,Plan-first 模式
"search": { "searxng_url": "https://..." },
"multimodal": { // 可选,vision 模型配置
"name": "kimi-k2.5", "api_key": "...", "base_url": "https://api.moonshot.cn/v1"
}
}
```
### 10.3 AgentConfig(`config.rs`)
移植 Python `ModelConfig` 的 base_url 自动推断(deepseek/kimi/minimax/claude/gpt → 对应端点与 gateway),字段:`name/api_key/base_url/gateway/temperature/max_tokens/multimodal`;并含 `max_react_iterations`、`timeouts`、`limits`、`safety`、`context`、`skills`、`mcp`、`multimodal` 各分区(对齐 Python `config.py` 全部分区)。
---
## 11. FFI / JNI 接口设计
### 11.1 C ABI(`aacode-rs/src/ffi.rs`,句柄式 + userdata 回调)
```c
typedef void (*aacode_event_fn)(const char* line, void* userdata);
void* aacode_task_start(const char* task_json, aacode_event_fn cb, void* userdata); // 非阻塞,返回句柄
char* aacode_task_wait(void* handle); // 阻塞到结束,返回终态 JSON
void aacode_task_cancel(void* handle); // 非阻塞,按句柄取消
void aacode_task_free(void* handle);
char* aacode_validate_api_key(const char* config_json);
char* aacode_list_sessions(const char* project_path);
char* aacode_get_session_messages(const char* project_path, const char* session_id);
void aacode_free_string(char* ptr);
```
> 设计要点:**句柄**代替字符串取消(`client_task_id` 已移除)、**userdata** 回调上下文
> (消除 TLS/全局 trampoline)、**单一事件流**(早退失败也发 `error` 事件,终态为富化
> `done`,见 §7)。事件从共享 `TOKIO_RT` 的 worker 线程发出(非调用线程)。
> `fastshell_init` 仍由 App 先调用完成 VFS/沙箱初始化;`aacode_task_start` 内部拿到同一个
> 沙箱路径构造 `Fastshell`。
### 11.2 JNI 层(`fastshell_c/jni_glue.c`)
`nativeAgentRunTaskWithCallback(json, cb) -> jlong`(start)、`nativeAgentWaitTask`(wait)、
`nativeAgentCancelTask(handle)`(cancel)、`nativeAgentFreeTask(handle)`(free)。流式回调用
每任务 `jni_cb_ctx`(GlobalRef + methodID)作 userdata,`event_trampoline` 经 `get_env` 按线程
attach 后转发 `onChunk`;handle→ctx 注册表 `g_agent_tasks` 在 free 时释放 GlobalRef。
### 11.3 App 侧改动(最小化)
- `Sdk.kt` / `NativeFFI.swift` 增加对应声明;
- `AgentBridge` 改为 `start → wait → free`,取消按句柄;`AgentOutputParser` **不变**
(`done` 富化字段向后兼容,`ignoreUnknownKeys` 丢弃多余字段);
- 构建脚本产物名与 `-p` 目标见 §12。
---
## 12. 构建与集成
1. 根 `Cargo.toml`:`members = ["fastshell", "aacode-rs"]`。
2. `aacode-rs/Cargo.toml`:`name = "aacode-rs"`,依赖 `fastshell`(path),复用树内 `ureq`/`serde`/`regex`/`uuid`/`base64`;如需异步再评估 `reqwest`。`crate-type=["lib","staticlib","cdylib"]`。
3. 桌面验证:`cargo build -p aacode-rs`;`cargo run -p aacode-rs --bin aacode -- -p ./demo "写个 hello"`。
4. 安卓:`cargo build --release -p aacode-rs --target aarch64-linux-android --lib` → `libaacode_rs.a` → `c_dist/arm64-v8a/`。
5. `fastshell_c/CMakeLists.txt`:`FASTSHELL_A` 指向 `libaacode_rs.a`(或并列链接);`jni_glue.c` 增加 aacode JNI 方法。
6. `AACodeApp/build.sh`:`-p fastshell` → `-p aacode-rs`,产物拷贝名同步。
7. `release` profile 已含 `lto/codegen-units=1/strip`,控制体积。
---
## 13. 实现顺序(一次性完整交付的内部构建次序)
**功能不分期,全部实现**;下表仅为工程上"由底向上"的搭建次序,用于组织开发与联调,最终交付包含全部功能。
| 1 | 骨架 | crate + Cargo.toml + lib.rs + `config` + `error` + `stream` 协议 |
| 2 | LLM 层 | `llm/sse` + `llm/openai` + `llm/anthropic` + 网关分发 + base_url 推断 |
| 3 | 工具基座 | `tools/schema` + `tools/registry` + `tools/shell`(对接 fastshell) |
| 4 | 会话/上下文 | `session` + `context` + `agent/compact` |
| 5 | Agent 循环 | `agent/react_loop` + `agent/main_agent` + `agent/prompts` + `agent/sub_agent` |
| 6 | 完整工具 | `tools/{web,todo,session_tools,skills,mcp,multimodal,delegate}` + `mcp` |
| 7 | 平台入口 | `bin/aacode`(CLI)+ `ffi.rs`(C ABI) |
| 8 | 安卓联调 | `jni_glue.c` 扩展 + `CMakeLists`/`build.sh` 调整 + App AgentBridge 切换 |
| 9 | 测试固化 | 单元 + 协议一致性 + 集成 + FFI + 真机冒烟(见 §14) |
---
## 14. 测试要点(Testing Strategy)
### 14.1 单元测试(`#[cfg(test)]`,纯 Rust,无网络)
- **SSE 解析**:喂入录制的 OpenAI/Anthropic SSE 字节流,断言 `LlmResponse.text/tool_calls/reasoning/finish_reason` 正确;覆盖 tool_calls 分片累积、`[DONE]`、截断。
- **工具注册/校验**:别名归一化(`cmd→command`)、必填缺失错误消息、类型错误、未知工具相似建议。
- **Schema 转换**:`to_openai_tools`/`to_anthropic_tools` 生成的 JSON 结构(type/function/parameters、required)。
- **run_shell 转发**:mock/真实 `Fastshell`,断言返回 JSON 含 returncode/stdout;超长归档到 `extracts/` 且返回预览。
- **Python 执行/测试链路**:通过 `run_shell` 跑 `python3 -c "print(1+1)"` 断言 stdout;跑抛异常代码断言非零 returncode;(fastshell 增强落地后)`python3 test.py` 与 `python -m pytest` 的返回码与输出。
- **Session 持久化**:创建/追加/读取/多轮衔接;原子写(中断不损坏);索引合并;旧格式兼容。
- **Todo**:add 返回 id、mark by id、summary 统计、markdown 往返。
- **配置**:env > json > 默认;base_url 自动推断各模型分支。
- **上下文压缩**:round 拆分不切断 tool_calls/tool 配对;compact view token 下降;保护首轮 + 最近 N 轮 user。
- **完成判定**:无 tool_calls → done;有 → 继续。
### 14.2 流式协议一致性测试(关键!保护 App 契约)
- 用 `EventSink` 的测试实现收集所有输出行,断言:
- 每个 JSON 事件独占一行、字段与 §7 表格逐字一致;
- token 行内 `\n` 被 `\x00` 转义(管道模式)、TTY 模式不转义;
- 事件顺序:start → (thinking/thought/tool_progress/action/observation)* → done。
- 可加一个"契约快照"测试:固定输入下生成的 JSONL 与预期快照比对(对齐 Kotlin `AgentOutputParser` 的 golden case)。
### 14.3 集成测试
- **Mock LLM server**:起一个本地 HTTP(`std::net::TcpListener` 或 `tiny_http`)返回预设 SSE,跑完整 ReAct 一轮(含一次 run_shell),断言 session 文件与事件流。
- **端到端(真实 API,标 `#[ignore]`)**:设 `LLM_API_KEY` 后手动跑,验证 DeepSeek 真实返回。
- **取消测试**:长命令中途 `aacode_task_cancel`,断言循环退出并 emit `error("cancelled")`。
- **Python 编码任务闭环(真实 API,标 `#[ignore]`)**:给 Agent "写一个函数并写 unittest 跑通"的任务,验证写码 → `run_shell` 跑测 → 据结果修复 → 完成的完整链路。
### 14.4 安卓/FFI 测试
- C ABI round-trip:`aacode_task_wait` 返回字符串可被 `aacode_free_string` 释放,无泄漏(valgrind/asan 桌面侧)。
- 句柄生命周期:start/wait/cancel/free、早退错误事件、userdata 上下文回传、多句柄并发、同 session 拒绝(`tests/concurrency.rs` + `ffi.rs` 单测)。
- 回调线程安全:事件从 tokio worker 线程 emit、宿主线程取消,无数据竞争。
- 真机冒烟:安装后跑一条简单任务,观察 UI 是否正确渲染 thinking/observation/done;真机验证内嵌 RustPython 能跑 `python3 -c` 与脚本测试。
### 14.5 性能/体积
- `libaacode_rs.a` 体积对比、link 后 `.so` 体积;
- 冷启动到首个 token 延迟;
- 单轮迭代内存占用(对齐 fastshell ≤30MB 目标)。
---
## 15. 风险与开放问题
1. **HTTP 客户端选型**:`ureq`(同步、体积小、已在树)vs `reqwest`(异步、生态好、体积大)。默认用 ureq + 手写 SSE;若并发/性能不足再切 reqwest。**待定**。
2. **TLS 证书**:Android 上 rustls 的根证书来源(`webpki-roots` 已在树);需验证真机 TLS 握手。
3. **tiktoken 不移植**:token 计数用字符估算(4:1),压缩阈值需据此调参,可能与 Python 略有偏差 —— 可接受。
4. **危险命令确认(ask 模式)**:移动端交互式确认需 App 弹窗回调;无回调时回退 `log` 策略。
5. **run_skills / MCP / 多模态依赖**:`run_skills` 因不内嵌 Python 采用文档型 skill(返回指令由模型借 `run_shell` 执行);MCP 需 stdio/sse 传输实现;多模态需 vision API 配置。三者均在本次范围内实现,但对运行环境有额外要求(网络、外部 MCP server、vision 模型)。
6. **fastshell 单例 vs 多实例**:`aacode_task_start` 是否复用 `fastshell` 的全局单例,需与 `fastshell_init` 的调用时序协调(当前:App 先 `fastshell_init`,aacode 复用同沙箱 vfs_root,每任务另建独立 Fastshell 实例)。
7. **产物改名影响**:`libfastshell.a` → `libaacode_rs.a` 影响 App 构建与 CI,需同步文档与脚本(或方案 B 保持原符号)。
---
## 16. 附:与 Python 版的功能映射速查(全部实现)
| `main.py / AICoder` | `bin/aacode.rs` + `agent::MainAgent` |
| `core/main_agent.py` 模型调用器 | `llm::{openai,anthropic}` + `agent::main_agent` |
| `core/react_loop.py` | `agent::react_loop` + `agent::compact` |
| `core/prompts.py` | `agent::prompts` |
| `core/sub_agent.py` / `multi_agent.py` | `agent::sub_agent` + `tools::delegate` |
| `tools/atomic_tools.py` run_shell | `tools::shell`(→ fastshell) |
| `tools/web_tools.py` | `tools::web` |
| `tools/todo_tools.py` | `tools::todo` |
| `tools/skills_tools.py` | `tools::skills` |
| `tools/multimodal_tools.py` | `tools::multimodal` |
| `utils/mcp_manager.py` + `sandbox/mcp_client.py` | `mcp` + `tools::mcp` |
| `utils/tool_registry.py` | `tools::registry` |
| `utils/tool_schemas.py` | `tools::schema` |
| `utils/tool_adapter.py` | `tools::schema::to_*_tools` |
| `utils/session_manager.py` | `session` |
| `utils/context_manager.py` + `class_method_mapper.py` | `context` |
| `utils/todo_manager.py` | `tools::todo`(markdown 读写) |
| `utils/message_utils.py` | `agent::compact` |
| `utils/safety.py` | 复用 fastshell + `config::safety` 精简黑名单 |
| `server/runner.py` 流式事件 | `stream::EventSink` + `ffi` |
| `config.py` | `config` |
| 会话管理工具(new/continue/...) | `tools::session_tools` |