| 类别 | 选型 | 说明 |
|---|---|---|
| 语言 | TypeScript 7 | 开启 strict 严格模式,配合 noUncheckedIndexedAccess、exactOptionalPropertyTypes 等强类型选项 |
| 模块体系 | ESM(module: nodenext) |
使用 type: "module",代码中显式携带 .ts 扩展名导入 |
| 运行方式 | tsx | 直接执行 TypeScript 源码,无需预先编译;tsc --noEmit 负责类型检查 |
| 包管理器 | pnpm | 通过 devEngines 锁定 pnpm ^11.22.0 |
| 数据契约 | TypeScript 类型(编译期) | 请求体 / 响应体类型定义于 responses.ts,仅编译期强类型,无运行时校验 |
| HTTP 客户端 | Node.js 原生 fetch | 无第三方 HTTP 依赖 |
| 日志 | pino + pino-pretty | logger.ts 统一封装,控制台彩色输出,贯穿所有层 |
| Shell 执行 | node:child_process(exec) | 工具层执行命令,zsh 环境,无第三方依赖 |
| 环境变量 | dotenv | 读取 ../../.env 中的 DEEPSEEK_API_KEY |
| 模型 API | DeepSeek /responses |
兼容 OpenAI Responses API 格式 |
| 测试 | node:test + node:assert | Node 内置测试框架,经 tsx 直接运行 TS 源码,无第三方测试依赖 |
src/
├── main.ts # 程序入口:启动 Express 服务(端口 30000)
├── logger.ts # pino 日志封装(横切所有类与工具)
├── Agents/
│ └── Planner/
│ ├── PlannerAgent.ts # 具体 Agent:注册 web_search 与 execute_shell_command 工具
│ └── instructions.md # Agent 系统指令(独立于代码维护)
├── DeepSeek/
│ ├── ModelClient.ts # 模型客户端:封装 /responses API 的 HTTP 调用
│ ├── BaseAgent.ts # Agent 基类:实现多轮对话循环
│ └── API/
│ └── responses.ts # 请求体 / 响应体 TypeScript 类型契约(编译期强类型)
└── Tools/
├── shell-execute.ts # Shell 命令执行工具(zsh 环境,cwd 指定工作目录)
└── README.md # 工具调用链路与实现说明
整体呈分层调用结构,依赖方向自上而下:
main.ts
├── PlannerAgent (Agents 层)
│ ├── BaseAgent (Agent 基类)
│ │ └── ModelClient (网络层)
│ │ └── responses.ts (数据契约层)
│ │ └── DeepSeek API
│ └── Tools 层
│ └── shell-execute.ts
└── logger.ts (日志,横切所有层)
-
Agent 继承体系:
PlanAgent继承BaseAgent,通过构造函数传入user、funcTools、model、instructions完成定制;instructions从同级instructions.md文件读取,指令与代码解耦。 -
多轮对话循环:
BaseAgent.loop()将用户输入加入消息上下文后循环调用ModelClient.requestResponsesAPI(),逐条处理响应中的四类输出项:message(assistant 文本回复,追加进上下文)reasoning(推理过程,追加进上下文)function_call(函数调用,追加进上下文,置hasFunctionCall = true)web_search_call(web 搜索调用)
当一轮响应中不再包含
function_call时循环终止。 -
数据契约:请求体与响应体由
responses.ts中的类型契约定义,全程编译期强类型;运行时不做校验。 -
工具机制:
PlanAgent通过ToolsType声明两个工具——web_search(内建搜索)与execute_shell_command(本地执行 Shell 命令)。模型发出function_call后,requestFunctionCall()按name分发到Tools下的对应实现,执行结果通过createFunctionCallOutputItemAndPush()以function_call_output形式回填上下文,供模型下一轮推理使用。工具的完整调用链路见ToolsDME.md。
本项目(ModelClient、BaseAgent、API schemas)与 DeepSeek 这一特定 model provider 强绑定,原因如下:
-
model 与 model-provider 天然强绑定。不同 provider 的 API 格式截然不同(例如 OpenAI 与 Anthropic 的 client 完全不同),无法抽象出统一接口。国内大部分 provider 目前兼容 OpenAI 或 Anthropic 的 API 格式,但未来模型训练范式可能变化,API 格式也存在变数,因此围绕单一 provider 开发是合理选择。
-
API 文档即最佳公开资料。
responses.ts直接翻译 DeepSeek API 文档的 request / responses 部分,BaseAgent的字段与 API 请求字段一一对应。开发者只需对照文档即可开发,无需参考其他文件或代码,实现简单、便于维护。 -
耦合换取实现简洁。
BaseAgent直接以 provider 的请求字段形态组织代码,便于调用ModelClient的方法,省去了中间抽象层。代价是:如需更换模型 provider,可能需要对类型契约、client 与 agent 字段做重构。
综上,当前阶段以"快速可用、贴合文档"为优先,接受与单一 provider 的耦合,为未来的抽象与扩展预留了空间。
测试套件位于根目录 test/,运行 pnpm test(等价 node --import tsx --test test/**/*.ts)。覆盖:
- shell 工具(
test/shell-execute.test.ts、test/shell-pwd.test.ts、test/shell-ls.test.ts):sudo 拦截边界、命令执行 / 失败、cwd 指定、输出 trim - ModelClient(
test/model-client.test.ts):mock 全局fetch,断言请求 URL / 请求头 / 请求体结构与响应解析 - PlanAgent(
test/plan-agent.test.ts):工具分发与function_call_output上下文回填 - AppServer / Session(
test/app-server.test.ts、test/session.test.ts):只读 DB 查询,不写入数据
测试细节见根目录 README.md 的「测试说明」章节。