refactor: 项目结构重构

This commit is contained in:
李岩岩 2026-07-10 15:58:32 +08:00 committed by liyy
parent c8c47564da
commit c4a139ae85
54 changed files with 575 additions and 7 deletions

17
.github/copilot-instructions.md vendored Normal file
View File

@ -0,0 +1,17 @@
# 开发准则
## 驾驭约束
* **开始任务前先明确任务内容**:不允许为了不报错正确而随意修改架构/目录/接口,不允许为了结果不报错而随意修改测试用例
* **先读文档再动手**:涉及架构/目录/接口的修改,必须先读 `docs/design.md`
* **只实现当前要求的**:禁止预建未来可能需要的模块、方法、字段
* **最小改动范围**:每次只改最少的文件,改完一批确认一批,不要一口气创建大量文件
* **改前先读**:编辑任何文件前必须先 `read_file` 确认当前内容(用户可能已手动修改)
* **禁止 `npx tsx -e` 内联测试**:测试代码统一写到 `packages/tests/src/` 下,用 `pnpm test` 运行
* **改后必验证**:代码改动后运行 `pnpm test`,确保用例全通过
## 代码风格
* 不得为简单的需求过度抽象和设计
* 简单功能不需要写注释
* 临时文件统一放在 `tmp` 目录下,且必须在 `.gitignore` 中忽略

4
.gitignore vendored
View File

@ -1,3 +1,5 @@
node_modules
.env
pnpm-lock.yaml
pnpm-lock.yaml
.DS_Store
tmp

178
docs/design.md Normal file
View File

@ -0,0 +1,178 @@
# llm-to-agent 设计文档
## 架构设计
### 核心理念:微内核 + 事件驱动
Agent 的本质是一个循环:**用户输入 → LLM 思考 → 工具调用 → LLM 再思考 → 最终回复**。
本项目的答案是:**内核只做一件事——驱动这个循环**。LLM 调用、工具执行、日志输出等一切能力全部通过事件总线交给外部插件。
### 三层架构
```
┌──────────────────────────────────────────┐
│ Route 层 CLI / Desktop / Web │ ← 只决定 I/O 方式
├──────────────────────────────────────────┤
│ Plugin 层 provider / tool / hook │ ← 可替换的能力单元
├──────────────────────────────────────────┤
│ Core 层 Scheduler + HookBus │ ← 只做循环 + 事件路由
└──────────────────────────────────────────┘
```
### 事件总线
内核循环的每一步都通过 `HookBus` 发出事件,插件注册响应,形成“事件驱动 + 插件化”的架构。
### 插件类型
| 类型 | 职责 | 响应的事件 |
|------|------|-----------|
| `provider` | LLM 提供商适配 | `llm:call``tool:select` |
| `tool` | 工具能力 | `tool:schema``tool:execute` |
| `hook` | 生命周期副作用 | `tool:before``tool:after``run:end` |
| `prompt` | 系统提示词 | 通过配置注入,不响应事件 |
每个插件通过 `manifest.json` 声明元信息(名称、类型、提供的能力、适用平台),由 `PluginManager` 统一加载。
### 三条产品线
```
┌── @llm-to-agent/core ──┐
│ MicroKernel + HookBus │
└────────────────────────┘
┌─────────────────┼─────────────────┐
▼ ▼ ▼
CLI Desktop Web
(readline) (Electron) (Browser HTTP)
│ │ │
┌───────┴────────┐ ┌──────┴───────┐ ┌──────┴───────┐
│ 本地工具全部 │ │ 同 CLI │ │ 仅远程工具 │
│ console 日志 │ │ IPC 日志 │ │ SSE 日志 │
└────────────────┘ └──────────────┘ └──────────────┘
```
CLI 与 Desktop 共享本地工具文件读写、Shell 执行Web 端通过 `platforms` 字段自动跳过本地工具。
---
## 项目目录设计
### Monorepo 结构pnpm workspace
```
llm-to-agent/
├── pnpm-workspace.yaml
├── package.json ← 根private统一 dev/test 脚本)
├── tsconfig.json
├── docs/ ← 设计文档
│ └── design.md
├── packages/
│ │
│ ├── types/ ← @llm-to-agent/types
│ │ └── src/index.ts ← 所有共享类型Message/ToolDef/PluginManifest/...
│ │
│ ├── core/ ← @llm-to-agent/core
│ │ └── src/
│ │ ├── hook-bus.ts ← 事件总线on/emit/request/collect
│ │ ├── scheduler.ts ← Agent 循环think→tool→think
│ │ ├── context.ts ← 消息上下文管理器
│ │ ├── plugin-manager.ts← 插件加载 + 生命周期
│ │ └── kernel.ts ← MicroKernel 入口(组合以上模块)
│ │
│ ├── plugins-builtin/ ← @llm-to-agent/plugins-builtin
│ │ └── [xxxxx]/ ← 内置插件
│ │
│ ├── cli/ ← @llm-to-agent/cli终端入口
│ │ └── src/index.ts ← readline 循环 + kernel.init([...])
│ │
│ ├── desktop/ ← @llm-to-agent/desktop桌面入口
│ │ └── src/main.ts ← Electron 主进程
│ │
│ ├── server/ ← @llm-to-agent/serverWeb 后端)
│ │ └── src/index.ts ← Express + SSE
│ │
│ ├── web/ ← @llm-to-agent/webWeb 前端)
│ │ └── index.html ← 待实现 React 聊天界面
│ │
│ ├── tests/ ← @llm-to-agent/tests
│ │ └── src/ ← *.test.tsNode 原生 test runner
│ │
│ └── old/ ← @llm-to-agent/old旧代码归档
│ └── src/ ← 重构前的 Agent 实现
```
### 包依赖关系
```
@llm-to-agent/types ← 零依赖,纯类型
↑ ↑
core plugins-builtin
↑ ↑
├───────────┴────────────┐
↓ ↓ ↓
cli desktop server
web
```
### 插件清单规范
每个插件目录包含 `manifest.json`
```json
{
"name": "@agent/tool-file",
"version": "1.0.0",
"type": "tool",
"provides": ["file-read", "file-write"],
"entry": "./index.ts",
"platforms": ["cli", "desktop"]
}
```
| 字段 | 说明 |
|------|------|
| `type` | `provider` / `tool` / `hook` / `prompt` |
| `provides` | 提供的能力标识 |
| `entry` | 入口文件,默认导出 `register(bus: HookBus)` |
| `platforms` | 可用平台PluginManager 加载时自动过滤 |
### 关键类型
```typescript
// 消息
interface Message {
role: 'system' | 'user' | 'assistant' | 'tool';
content: string;
tool_calls?: ToolCall[];
tool_call_id?: string;
}
// 工具定义OpenAI 兼容)
interface ToolDef {
type: 'function';
function: {
name: string;
description: string;
parameters: Record<string, unknown>;
};
}
// 插件清单
interface PluginManifest {
name: string;
version: string;
type: 'provider' | 'tool' | 'hook' | 'prompt';
provides: string | string[];
entry: string;
platforms?: ('cli' | 'desktop' | 'web')[];
}
// 事件总线
type EventName = string;
type Listener = (data: any) => any | Promise<any>;
```

View File

@ -1,13 +1,16 @@
{
"name": "llm-to-agent",
"version": "0.1.0",
"version": "0.2.0",
"private": true,
"type": "module",
"scripts": {
"dev": "tsx src/index.ts"
},
"dependencies": {
"dotenv": "^16.x",
"openai": "^4.x"
"dev": "pnpm --filter @llm-to-agent/cli dev",
"dev:old": "pnpm --filter @llm-to-agent/old dev",
"dev:core": "pnpm --filter @llm-to-agent/core dev",
"dev:server": "pnpm --filter @llm-to-agent/server dev",
"dev:web": "pnpm --filter @llm-to-agent/web dev",
"dev:desktop": "pnpm --filter @llm-to-agent/desktop dev",
"test": "pnpm --filter @llm-to-agent/tests test"
},
"devDependencies": {
"@types/node": "^25.9.1",

19
packages/cli/package.json Normal file
View File

@ -0,0 +1,19 @@
{
"name": "@llm-to-agent/cli",
"version": "0.1.0",
"type": "module",
"private": true,
"main": "./src/index.ts",
"scripts": {
"dev": "tsx src/index.ts"
},
"dependencies": {
"@llm-to-agent/core": "workspace:*",
"@llm-to-agent/plugins-builtin": "workspace:*"
},
"devDependencies": {
"@types/node": "^25.9.1",
"tsx": "^4.x",
"typescript": "^5.x"
}
}

View File

@ -0,0 +1 @@
console.log('🚀 LLM-to-Agent CLI 初始化已完成,待开发。\n');

View File

@ -0,0 +1,7 @@
{
"compilerOptions": {
"module": "esnext",
"moduleResolution": "bundler",
"types": ["node"]
}
}

View File

@ -0,0 +1,19 @@
{
"name": "@llm-to-agent/core",
"version": "0.1.0",
"type": "module",
"private": true,
"main": "./src/index.ts",
"types": "./src/index.ts",
"scripts": {
"dev": "tsx src/index.ts"
},
"dependencies": {
"@llm-to-agent/types": "workspace:*"
},
"devDependencies": {
"@types/node": "^25.9.1",
"tsx": "^4.x",
"typescript": "^5.x"
}
}

View File

@ -0,0 +1,6 @@
export * from '@llm-to-agent/types';
export const init = () => {
console.log('🚀 Core 入口已初始化完成,待开发。');
}

View File

@ -0,0 +1,7 @@
{
"compilerOptions": {
"module": "esnext",
"moduleResolution": "bundler",
"types": ["node"]
}
}

View File

@ -0,0 +1,19 @@
{
"name": "@llm-to-agent/desktop",
"version": "0.1.0",
"type": "module",
"private": true,
"main": "./src/main.ts",
"scripts": {
"dev": "tsx './src/main.ts'"
},
"dependencies": {
"@llm-to-agent/core": "workspace:*",
"@llm-to-agent/plugins-builtin": "workspace:*"
},
"devDependencies": {
"@types/node": "^25.9.1",
"tsx": "^4.x",
"typescript": "^5.x"
}
}

View File

@ -0,0 +1 @@
console.log('🚀 Desktop 入口已初始化完成,待开发。');

View File

@ -0,0 +1,7 @@
{
"compilerOptions": {
"module": "esnext",
"moduleResolution": "bundler",
"types": ["node"]
}
}

18
packages/old/package.json Normal file
View File

@ -0,0 +1,18 @@
{
"name": "@llm-to-agent/old",
"version": "0.2.0",
"type": "module",
"private": true,
"scripts": {
"dev": "tsx src/index.ts"
},
"dependencies": {
"dotenv": "^16.x",
"openai": "^4.x"
},
"devDependencies": {
"@types/node": "^25.9.1",
"tsx": "^4.x",
"typescript": "^5.x"
}
}

View File

@ -0,0 +1,7 @@
{
"compilerOptions": {
"module": "esnext",
"moduleResolution": "bundler",
"types": ["node"]
}
}

View File

@ -0,0 +1,16 @@
{
"name": "@llm-to-agent/plugins-builtin",
"version": "0.1.0",
"type": "module",
"private": true,
"main": "./src/index.ts",
"dependencies": {
"@llm-to-agent/core": "workspace:*",
"@llm-to-agent/types": "workspace:*"
},
"devDependencies": {
"@types/node": "^25.9.1",
"tsx": "^4.x",
"typescript": "^5.x"
}
}

View File

@ -0,0 +1 @@
export default {}

View File

@ -0,0 +1,7 @@
{
"compilerOptions": {
"module": "esnext",
"moduleResolution": "bundler",
"types": ["node"]
}
}

View File

@ -0,0 +1,19 @@
{
"name": "@llm-to-agent/server",
"version": "0.1.0",
"type": "module",
"private": true,
"main": "./src/index.ts",
"scripts": {
"dev": "tsx src/index.ts"
},
"dependencies": {
"@llm-to-agent/core": "workspace:*",
"@llm-to-agent/plugins-builtin": "workspace:*"
},
"devDependencies": {
"@types/node": "^25.9.1",
"tsx": "^4.x",
"typescript": "^5.x"
}
}

View File

@ -0,0 +1 @@
console.log('🚀 Server 入口已初始化完成,待开发。');

View File

@ -0,0 +1,7 @@
{
"compilerOptions": {
"module": "esnext",
"moduleResolution": "bundler",
"types": ["node"]
}
}

View File

@ -0,0 +1,19 @@
{
"name": "@llm-to-agent/tests",
"version": "0.1.0",
"type": "module",
"private": true,
"scripts": {
"test": "tsx --test src/**/*.test.ts"
},
"dependencies": {
"@llm-to-agent/types": "workspace:*",
"@llm-to-agent/core": "workspace:*",
"@llm-to-agent/plugins-builtin": "workspace:*"
},
"devDependencies": {
"@types/node": "^25.9.1",
"tsx": "^4.x",
"typescript": "^5.x"
}
}

View File

@ -0,0 +1,17 @@
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { init } from '@llm-to-agent/core';
describe('@llm-to-agent/core', () => {
it('init() 不抛异常', () => {
assert.doesNotThrow(() => init());
});
it('core 通过 re-export 暴露 types', async () => {
const mod = await import('@llm-to-agent/core');
assert.equal(typeof mod.init, 'function', '应导出 init 函数');
});
});

View File

@ -0,0 +1,11 @@
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
describe('@llm-to-agent/plugins-builtin', () => {
it('入口文件可正常 import', async () => {
const mod = await import('@llm-to-agent/plugins-builtin');
assert.ok(mod.default !== undefined);
});
});

View File

@ -0,0 +1,57 @@
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { Message, ToolCall, ToolDef, PluginManifest, SchedulerConfig, EventName, Listener } from '@llm-to-agent/types';
describe('@llm-to-agent/types', () => {
it('Message 类型可正常构造', () => {
const msg: Message = { role: 'user', content: 'hello' };
assert.equal(msg.role, 'user');
assert.equal(msg.content, 'hello');
});
it('Message 支持 tool_calls 和 tool_call_id', () => {
const tc: ToolCall = { id: '1', function: { name: 'test', arguments: '{}' } };
const msg: Message = { role: 'assistant', content: '', tool_calls: [tc] };
assert.equal(msg.tool_calls![0].id, '1');
});
it('ToolDef 可正常构造', () => {
const tool: ToolDef = {
type: 'function',
function: {
name: 'weather',
description: '查询天气',
parameters: { type: 'object', properties: {}, required: [] },
},
};
assert.equal(tool.function.name, 'weather');
});
it('PluginManifest 类型完整', () => {
const manifest: PluginManifest = {
name: '@agent/test',
version: '1.0.0',
type: 'tool',
provides: ['test-tool'],
entry: './index.ts',
platforms: ['cli', 'desktop'],
};
assert.equal(manifest.type, 'tool');
assert.deepEqual(manifest.platforms, ['cli', 'desktop']);
});
it('SchedulerConfig 可正常构造', () => {
const config: SchedulerConfig = { maxSteps: 5 };
assert.equal(config.maxSteps, 5);
});
it('EventName 和 Listener 类型定义正确', () => {
const name: EventName = 'llm:call';
const fn: Listener = (data: any) => data;
assert.equal(typeof fn, 'function');
assert.equal(name, 'llm:call');
});
});

View File

@ -0,0 +1,7 @@
{
"compilerOptions": {
"module": "esnext",
"moduleResolution": "bundler",
"types": ["node"]
}
}

View File

@ -0,0 +1,12 @@
{
"name": "@llm-to-agent/types",
"version": "0.1.0",
"type": "module",
"private": true,
"main": "./src/index.ts",
"types": "./src/index.ts",
"scripts": {},
"devDependencies": {
"typescript": "^5.x"
}
}

View File

@ -0,0 +1,46 @@
// ===== Agent 消息 =====
export interface Message {
role: 'system' | 'user' | 'assistant' | 'tool';
content: string;
tool_calls?: ToolCall[];
tool_call_id?: string;
}
export interface ToolCall {
id: string;
function: { name: string; arguments: string };
}
// ===== 工具 =====
export interface ToolDef {
type: 'function';
function: {
name: string;
description: string;
parameters: Record<string, unknown>;
};
}
// ===== 插件 =====
export interface PluginManifest {
name: string;
version: string;
type: 'provider' | 'tool' | 'hook' | 'prompt';
provides: string | string[];
entry: string;
platforms?: ('cli' | 'desktop' | 'web')[];
}
// ===== 事件总线 =====
export type EventName = string;
export type Listener = (data: any) => any | Promise<any>;
// ===== 调度器 =====
export interface SchedulerConfig {
maxSteps?: number;
}

View File

@ -0,0 +1,7 @@
{
"compilerOptions": {
"module": "esnext",
"moduleResolution": "bundler",
"types": []
}
}

14
packages/web/index.html Normal file
View File

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>LLM-to-Agent Web</title>
</head>
<body>
<div id="app">
<h1>🚀 LLM-to-Agent Web</h1>
<p>占位页面,待实现 React 聊天界面。</p>
</div>
</body>
</html>

10
packages/web/package.json Normal file
View File

@ -0,0 +1,10 @@
{
"name": "@llm-to-agent/web",
"version": "0.1.0",
"type": "module",
"private": true,
"scripts": {
"dev": "echo 'TODO: vite dev'",
"build": "echo 'TODO: vite build'"
}
}

2
pnpm-workspace.yaml Normal file
View File

@ -0,0 +1,2 @@
packages:
- 'packages/*'

View File

@ -1,7 +1,11 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "esnext",
"moduleResolution": "bundler",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"types": ["node"]
}
}