@ai-zen/air 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +85 -0
- package/TODO.md +8 -0
- package/dist/agent-factory.d.ts +3 -0
- package/dist/agent-factory.d.ts.map +1 -0
- package/dist/agent-factory.js +21 -0
- package/dist/agent-factory.js.map +1 -0
- package/dist/agent-runtime.d.ts +2 -0
- package/dist/agent-runtime.d.ts.map +1 -0
- package/dist/agent-runtime.js +292 -0
- package/dist/agent-runtime.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +40 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +17 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +90 -0
- package/dist/config.js.map +1 -0
- package/dist/delta-renderer.d.ts +19 -0
- package/dist/delta-renderer.d.ts.map +1 -0
- package/dist/delta-renderer.js +104 -0
- package/dist/delta-renderer.js.map +1 -0
- package/dist/migration.d.ts +5 -0
- package/dist/migration.d.ts.map +1 -0
- package/dist/migration.js +70 -0
- package/dist/migration.js.map +1 -0
- package/dist/tools.d.ts +3 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +24 -0
- package/dist/tools.js.map +1 -0
- package/package.json +28 -0
- package/src/__tests__/config.test.ts +111 -0
- package/src/__tests__/main.test.ts +43 -0
- package/src/__tests__/tools.test.ts +15 -0
- package/src/agent-factory.ts +23 -0
- package/src/agent-runtime.ts +256 -0
- package/src/cli.ts +48 -0
- package/src/config.ts +109 -0
- package/src/delta-renderer.ts +127 -0
- package/src/migration.ts +72 -0
- package/src/tools.ts +23 -0
- package/tsconfig.json +18 -0
- package/vitest.config.ts +8 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { AgentNS } from "@ai-zen/agents-core";
|
|
3
|
+
export class DeltaRenderer {
|
|
4
|
+
reasoningPrinted = false;
|
|
5
|
+
contentPrinted = false;
|
|
6
|
+
toolPrints = {};
|
|
7
|
+
opts;
|
|
8
|
+
constructor(opts = {}) {
|
|
9
|
+
this.opts = {
|
|
10
|
+
reasoningHeader: opts.reasoningHeader ?? "",
|
|
11
|
+
contentHeader: opts.contentHeader ?? "",
|
|
12
|
+
reasoningStyle: opts.reasoningStyle ?? chalk.blue,
|
|
13
|
+
contentStyle: opts.contentStyle ?? chalk.white,
|
|
14
|
+
indent: opts.indent ?? "",
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
render(delta, finishReason = null) {
|
|
18
|
+
if (delta.tool_calls) {
|
|
19
|
+
this.renderToolCalls(delta, finishReason);
|
|
20
|
+
}
|
|
21
|
+
if (delta.reasoning_content) {
|
|
22
|
+
if (!this.reasoningPrinted && this.opts.reasoningHeader) {
|
|
23
|
+
process.stdout.write(this.opts.indent + this.opts.reasoningHeader);
|
|
24
|
+
this.reasoningPrinted = true;
|
|
25
|
+
}
|
|
26
|
+
process.stdout.write(this.opts.reasoningStyle(delta.reasoning_content));
|
|
27
|
+
}
|
|
28
|
+
if (delta.content) {
|
|
29
|
+
if (!this.contentPrinted && this.opts.contentHeader) {
|
|
30
|
+
process.stdout.write(this.opts.indent + this.opts.contentHeader);
|
|
31
|
+
this.contentPrinted = true;
|
|
32
|
+
}
|
|
33
|
+
if (typeof delta.content === "string") {
|
|
34
|
+
process.stdout.write(this.opts.contentStyle(delta.content));
|
|
35
|
+
}
|
|
36
|
+
else if (Array.isArray(delta.content)) {
|
|
37
|
+
for (const s of delta.content) {
|
|
38
|
+
if (s.type === "text" && s.text) {
|
|
39
|
+
process.stdout.write(this.opts.contentStyle(s.text));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
reset() {
|
|
46
|
+
this.reasoningPrinted = false;
|
|
47
|
+
this.contentPrinted = false;
|
|
48
|
+
for (const k in this.toolPrints)
|
|
49
|
+
delete this.toolPrints[k];
|
|
50
|
+
}
|
|
51
|
+
renderToolCalls(delta, finishReason) {
|
|
52
|
+
if (!delta.tool_calls || delta.tool_calls.length === 0)
|
|
53
|
+
return;
|
|
54
|
+
const isFirstToolCall = Object.keys(this.toolPrints).length === 0 &&
|
|
55
|
+
delta.tool_calls.some((tc) => tc.function?.name || tc.function?.arguments);
|
|
56
|
+
if (isFirstToolCall)
|
|
57
|
+
process.stdout.write(chalk.blue.bold("\n\n💭 工具调用中..."));
|
|
58
|
+
for (const tc of delta.tool_calls) {
|
|
59
|
+
const index = tc.index ?? 0;
|
|
60
|
+
const func = tc.function;
|
|
61
|
+
if (!this.toolPrints[index]) {
|
|
62
|
+
this.toolPrints[index] = {
|
|
63
|
+
name: "",
|
|
64
|
+
arguments: "",
|
|
65
|
+
namePrinted: false,
|
|
66
|
+
argsPrinted: false,
|
|
67
|
+
completed: false,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const p = this.toolPrints[index];
|
|
71
|
+
if (func?.name)
|
|
72
|
+
p.name += func.name;
|
|
73
|
+
if (func?.arguments)
|
|
74
|
+
p.arguments += func.arguments;
|
|
75
|
+
if (p.name && !p.namePrinted) {
|
|
76
|
+
process.stdout.write(chalk.magenta.bold(`\n🔧 ${index} ${p.name}\n`));
|
|
77
|
+
p.namePrinted = true;
|
|
78
|
+
}
|
|
79
|
+
if (p.arguments && !p.argsPrinted && p.namePrinted)
|
|
80
|
+
p.argsPrinted = true;
|
|
81
|
+
if (func?.arguments && p.argsPrinted)
|
|
82
|
+
process.stdout.write(chalk.gray(func.arguments));
|
|
83
|
+
if (finishReason === AgentNS.FinishReason.ToolCalls)
|
|
84
|
+
p.completed = true;
|
|
85
|
+
}
|
|
86
|
+
if (finishReason === AgentNS.FinishReason.ToolCalls) {
|
|
87
|
+
for (const idx of Object.keys(this.toolPrints).map(Number)) {
|
|
88
|
+
const p = this.toolPrints[idx];
|
|
89
|
+
if (p.completed && p.arguments) {
|
|
90
|
+
process.stdout.write("\n");
|
|
91
|
+
try {
|
|
92
|
+
const parsed = JSON.parse(p.arguments);
|
|
93
|
+
process.stdout.write(chalk.gray(` ${JSON.stringify(parsed, null, 4)}\n`));
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
process.stdout.write(chalk.gray(` ${p.arguments}\n`));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
process.stdout.write("\n");
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=delta-renderer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delta-renderer.js","sourceRoot":"","sources":["../src/delta-renderer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const MAX_CONTEXT_CHARS = 500000;
|
|
2
|
+
export declare function contextSize(messages: any[]): number;
|
|
3
|
+
export declare function shouldMigrate(messages: any[]): boolean;
|
|
4
|
+
export declare function generateMigrationDoc(messages: any[]): Promise<string>;
|
|
5
|
+
//# sourceMappingURL=migration.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migration.d.ts","sourceRoot":"","sources":["../src/migration.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,iBAAiB,SAAS,CAAC;AAExC,wBAAgB,WAAW,CAAC,QAAQ,EAAE,GAAG,EAAE,GAAG,MAAM,CAEnD;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,GAAG,EAAE,GAAG,OAAO,CAEtD;AAED,wBAAsB,oBAAoB,CAAC,QAAQ,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CA0D3E"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Agent, Message, OpenAI, ChatGPT } from "@ai-zen/agents-core";
|
|
2
|
+
import { readConfig } from "./config.js";
|
|
3
|
+
export const MAX_CONTEXT_CHARS = 500000;
|
|
4
|
+
export function contextSize(messages) {
|
|
5
|
+
return JSON.stringify(messages).length;
|
|
6
|
+
}
|
|
7
|
+
export function shouldMigrate(messages) {
|
|
8
|
+
return contextSize(messages) >= MAX_CONTEXT_CHARS;
|
|
9
|
+
}
|
|
10
|
+
export async function generateMigrationDoc(messages) {
|
|
11
|
+
const config = readConfig();
|
|
12
|
+
const endpoint = new OpenAI({ openai_endpoint: "https://api.deepseek.com/v1", api_key: config.apiKey });
|
|
13
|
+
const model = new ChatGPT({
|
|
14
|
+
model_config: {},
|
|
15
|
+
request_config: await endpoint.chatCompletion("deepseek-v4-flash"),
|
|
16
|
+
});
|
|
17
|
+
const agent = new Agent({
|
|
18
|
+
model,
|
|
19
|
+
messages: [Message.System(`你是一个专业的任务交接分析师。你的任务是阅读一段完整的AI助手与用户的对话历史,筛选出对后续工作有用的关键信息,生成一份结构清晰的交接文档。
|
|
20
|
+
|
|
21
|
+
这份交接文档将作为新会话的第一条用户消息,让新的AI助手了解任务背景并继续工作。
|
|
22
|
+
|
|
23
|
+
请按以下模板生成 Markdown 格式的文档:
|
|
24
|
+
|
|
25
|
+
## ✅ 已完成的任务
|
|
26
|
+
列出已经完成的任务及其产出物(文件路径、代码片段位置等)。
|
|
27
|
+
如果没有已完成的任务,保留此标题并注明"无"。
|
|
28
|
+
注意:只记录任务标题和产出路径,不需要描述完成过程的细节。
|
|
29
|
+
|
|
30
|
+
## 📋 未完成的任务
|
|
31
|
+
列出所有待继续完成的任务,包含:
|
|
32
|
+
- 任务描述
|
|
33
|
+
- 当前进度
|
|
34
|
+
- 下一步需要做什么
|
|
35
|
+
- 相关的文件路径
|
|
36
|
+
- 优先级(如果对话中提到过)
|
|
37
|
+
|
|
38
|
+
## 🧠 重要记忆
|
|
39
|
+
记录所有对后续工作有影响的信息,包括但不限于:
|
|
40
|
+
- 用户的技术偏好和约定
|
|
41
|
+
- 踩过的坑和教训
|
|
42
|
+
- 项目特定的架构决策
|
|
43
|
+
- 任何对后续工作有指导意义的信息
|
|
44
|
+
|
|
45
|
+
## 📁 文件索引
|
|
46
|
+
按用途分类列出对话中涉及的重要文件路径,方便新 Agent 按需阅读。
|
|
47
|
+
**请对每个文件注明其用途或作用**,让接手者能快速判断哪些文件需要优先阅读。
|
|
48
|
+
|
|
49
|
+
## 🔔 接手指令
|
|
50
|
+
接手后请按以下步骤操作:
|
|
51
|
+
1. **读文件** — 使用 shell 工具(cat、grep、find 等)读取「文件索引」中列出的关键文件,以实际代码为准,不要依赖训练数据或过往经验
|
|
52
|
+
2. **对状态** — 确认代码中的关键常量、函数签名、配置等是否与文档描述一致
|
|
53
|
+
3. **再行动** — 确认无误后再继续未完成任务,做任何改动前先跟用户商量
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
注意事项:
|
|
58
|
+
1. 只包含确定的信息,不要猜测或补充对话中没有的内容
|
|
59
|
+
2. 已完成的任务只需列出任务标题和产出路径,不要罗列完成过程的每一步
|
|
60
|
+
3. 如果某个部分没有需要记录的内容,标注"无"即可
|
|
61
|
+
4. 语言风格与原始对话一致(中文)`)],
|
|
62
|
+
tools: [],
|
|
63
|
+
});
|
|
64
|
+
const result = await agent.send(`请阅读以下对话历史,生成交接文档:\n\n${messages.map((m) => `[${m.role}] ${typeof m.content === "string" ? m.content : ""}`).join("\n\n")}`);
|
|
65
|
+
const last = result.at(-1);
|
|
66
|
+
if (!last || last.status === "error")
|
|
67
|
+
throw new Error("生成交接文档失败");
|
|
68
|
+
return typeof last.content === "string" ? last.content : "";
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=migration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migration.js","sourceRoot":"","sources":["../src/migration.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AACtE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,MAAM,CAAC,MAAM,iBAAiB,GAAG,MAAM,CAAC;AAExC,MAAM,UAAU,WAAW,CAAC,QAAe;IACzC,OAAO,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC;AACzC,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,QAAe;IAC3C,OAAO,WAAW,CAAC,QAAQ,CAAC,IAAI,iBAAiB,CAAC;AACpD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,QAAe;IACxD,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,MAAM,QAAQ,GAAG,IAAI,MAAM,CAAC,EAAE,eAAe,EAAE,6BAA6B,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IACxG,MAAM,KAAK,GAAG,IAAI,OAAO,CAAC;QACxB,YAAY,EAAE,EAAE;QAChB,cAAc,EAAE,MAAM,QAAQ,CAAC,cAAc,CAAC,mBAAmB,CAAC;KACnE,CAAC,CAAC;IACH,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC;QACtB,KAAK;QACL,QAAQ,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mBA0CX,CAAC,CAAC;QACjB,KAAK,EAAE,EAAE;KACV,CAAC,CAAC;IACH,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,wBAAwB,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC7J,MAAM,IAAI,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3B,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,KAAK,OAAO;QAAE,MAAM,IAAI,KAAK,CAAC,UAAU,CAAC,CAAC;IAClE,OAAO,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;AAC9D,CAAC"}
|
package/dist/tools.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGnD,eAAO,MAAM,SAAS,cAmBpB,CAAC"}
|
package/dist/tools.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { CallbackTool } from "@ai-zen/agents-core";
|
|
2
|
+
import { execSync } from "node:child_process";
|
|
3
|
+
export const shellTool = new CallbackTool({
|
|
4
|
+
function: {
|
|
5
|
+
name: "shell",
|
|
6
|
+
description: "执行 shell 命令并返回输出",
|
|
7
|
+
parameters: {
|
|
8
|
+
type: "object",
|
|
9
|
+
properties: {
|
|
10
|
+
command: { type: "string", description: "要执行的命令" },
|
|
11
|
+
},
|
|
12
|
+
required: ["command"],
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
callback(args) {
|
|
16
|
+
try {
|
|
17
|
+
return execSync(args.command, { encoding: "utf-8", timeout: 30000 });
|
|
18
|
+
}
|
|
19
|
+
catch (e) {
|
|
20
|
+
return `错误: ${e.message}`;
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
//# sourceMappingURL=tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAE9C,MAAM,CAAC,MAAM,SAAS,GAAG,IAAI,YAAY,CAAC;IACxC,QAAQ,EAAE;QACR,IAAI,EAAE,OAAO;QACb,WAAW,EAAE,kBAAkB;QAC/B,UAAU,EAAE;YACV,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACV,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,QAAQ,EAAE;aACnD;YACD,QAAQ,EAAE,CAAC,SAAS,CAAC;SACtB;KACF;IACD,QAAQ,CAAO,IAAyB;QACtC,IAAI,CAAC;YACH,OAAO,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;QACvE,CAAC;QAAC,OAAO,CAAM,EAAE,CAAC;YAChB,OAAO,OAAO,CAAC,CAAC,OAAO,EAAE,CAAC;QAC5B,CAAC;IACH,CAAC;CACF,CAAC,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ai-zen/air",
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"description": "极简 AI 命令行助手",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/agent-runtime.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"air": "./dist/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@ai-zen/agents-core": "file:../agents/packages/core",
|
|
12
|
+
"chalk": "^5.6.2",
|
|
13
|
+
"commander": "^15.0.0",
|
|
14
|
+
"inquirer": "^9.3.8"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@types/inquirer": "^9.0.10",
|
|
18
|
+
"@types/node": "^24.12.2",
|
|
19
|
+
"typescript": "^5.3.3",
|
|
20
|
+
"vitest": "^4.1.9"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsc",
|
|
24
|
+
"start": "npm run build && node ./dist/cli.js",
|
|
25
|
+
"test": "vitest run",
|
|
26
|
+
"test:watch": "vitest"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { existsSync, rmSync, mkdirSync, writeFileSync, readFileSync, readdirSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { readConfig, saveConfig, readMessages, saveMessages, clearMessages, saveSnapshot, listSnapshots, loadSnapshot } from "../config.js";
|
|
6
|
+
|
|
7
|
+
describe("config", () => {
|
|
8
|
+
let tempDir: string;
|
|
9
|
+
let origHome: string | undefined;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
tempDir = join(tmpdir(), `air-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
13
|
+
mkdirSync(tempDir, { recursive: true });
|
|
14
|
+
origHome = process.env.HOME;
|
|
15
|
+
process.env.HOME = tempDir;
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
if (origHome) process.env.HOME = origHome;
|
|
20
|
+
else delete process.env.HOME;
|
|
21
|
+
try { rmSync(tempDir, { recursive: true, force: true }); } catch {}
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe("config (apiKey)", () => {
|
|
25
|
+
it("默认 apiKey 为空", () => {
|
|
26
|
+
expect(readConfig().apiKey).toBe("");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("保存和读取 apiKey", () => {
|
|
30
|
+
saveConfig("sk-test-key-12345");
|
|
31
|
+
expect(readConfig().apiKey).toBe("sk-test-key-12345");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("配置文件损坏时返回空 key", () => {
|
|
35
|
+
mkdirSync(join(tempDir, ".ai-zen", "air"), { recursive: true });
|
|
36
|
+
writeFileSync(join(tempDir, ".ai-zen", "air", "config.json"), "不是JSON", "utf-8");
|
|
37
|
+
expect(readConfig().apiKey).toBe("");
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe("context (messages 数组)", () => {
|
|
42
|
+
it("不存在时返回空数组", () => {
|
|
43
|
+
expect(readMessages()).toEqual([]);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("保存和读取 messages", () => {
|
|
47
|
+
const msgs = [
|
|
48
|
+
{ role: "user", content: "你好" },
|
|
49
|
+
{ role: "assistant", content: "你好!" },
|
|
50
|
+
];
|
|
51
|
+
saveMessages(msgs);
|
|
52
|
+
expect(readMessages()).toEqual(msgs);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("clearMessages 清空为 []", () => {
|
|
56
|
+
saveMessages([{ role: "user", content: "hi" }]);
|
|
57
|
+
clearMessages();
|
|
58
|
+
expect(readMessages()).toEqual([]);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("文件内容不是数组时返回空数组", () => {
|
|
62
|
+
mkdirSync(join(tempDir, ".ai-zen", "air"), { recursive: true });
|
|
63
|
+
writeFileSync(join(tempDir, ".ai-zen", "air", "context.json"), '"不是数组"', "utf-8");
|
|
64
|
+
expect(readMessages()).toEqual([]);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("文件损坏时返回空数组", () => {
|
|
68
|
+
mkdirSync(join(tempDir, ".ai-zen", "air"), { recursive: true });
|
|
69
|
+
writeFileSync(join(tempDir, ".ai-zen", "air", "context.json"), "不是JSON", "utf-8");
|
|
70
|
+
expect(readMessages()).toEqual([]);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe("snapshots", () => {
|
|
75
|
+
it("保存快照,文件名是 ISO 时间", () => {
|
|
76
|
+
const msgs = [{ role: "user", content: "测试快照" }];
|
|
77
|
+
const name = saveSnapshot(msgs);
|
|
78
|
+
expect(name).toBeTruthy();
|
|
79
|
+
|
|
80
|
+
const snapDir = join(tempDir, ".ai-zen", "air", "snapshots");
|
|
81
|
+
expect(existsSync(snapDir)).toBe(true);
|
|
82
|
+
const files = readdirSync(snapDir).filter((f) => f.endsWith(".json"));
|
|
83
|
+
expect(files.length).toBe(1);
|
|
84
|
+
expect(files[0]).toBe(name + ".json");
|
|
85
|
+
|
|
86
|
+
const loaded = JSON.parse(readFileSync(join(snapDir, files[0]), "utf-8"));
|
|
87
|
+
expect(loaded).toEqual(msgs);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
describe("listSnapshots / loadSnapshot", () => {
|
|
93
|
+
it("加载不存在的快照返回空数组", () => {
|
|
94
|
+
expect(loadSnapshot("nonexistent")).toEqual([]);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("保存后能列出并加载快照", () => {
|
|
98
|
+
const msgs = [{ role: "user", content: "测试" }];
|
|
99
|
+
const name = saveSnapshot(msgs);
|
|
100
|
+
|
|
101
|
+
const list = listSnapshots();
|
|
102
|
+
expect(list.length).toBe(1);
|
|
103
|
+
expect(list[0].name).toBe(name);
|
|
104
|
+
expect(list[0].date).toBeTruthy();
|
|
105
|
+
|
|
106
|
+
const loaded = loadSnapshot(name);
|
|
107
|
+
expect(loaded).toEqual(msgs);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { contextSize, shouldMigrate } from "../migration.js";
|
|
3
|
+
|
|
4
|
+
describe("contextSize", () => {
|
|
5
|
+
it("空数组", () => {
|
|
6
|
+
expect(contextSize([])).toBe(2);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it("一条消息", () => {
|
|
10
|
+
const msgs = [{ role: "user", content: "你好" }];
|
|
11
|
+
expect(contextSize(msgs)).toBe(JSON.stringify(msgs).length);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("多条消息", () => {
|
|
15
|
+
const msgs = [
|
|
16
|
+
{ role: "user", content: "你好" },
|
|
17
|
+
{ role: "assistant", content: "你好!有什么可以帮你的吗?" },
|
|
18
|
+
];
|
|
19
|
+
expect(contextSize(msgs)).toBe(JSON.stringify(msgs).length);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe("shouldMigrate", () => {
|
|
24
|
+
it("空消息不迁移", () => {
|
|
25
|
+
expect(shouldMigrate([])).toBe(false);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("小消息不迁移", () => {
|
|
29
|
+
expect(shouldMigrate([{ role: "user", content: "hi" }])).toBe(false);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("达到 66 万时迁移", () => {
|
|
33
|
+
const n = 500000 - JSON.stringify([{ role: "user", content: "" }]).length;
|
|
34
|
+
const bigMsg = { role: "user", content: "x".repeat(n) };
|
|
35
|
+
expect(shouldMigrate([bigMsg])).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("差一点不到时不迁移", () => {
|
|
39
|
+
const n = 500000 - JSON.stringify([{ role: "user", content: "" }]).length - 1;
|
|
40
|
+
const bigMsg = { role: "user", content: "x".repeat(n) };
|
|
41
|
+
expect(shouldMigrate([bigMsg])).toBe(false);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { shellTool } from "../tools.js";
|
|
3
|
+
|
|
4
|
+
describe("shellTool", () => {
|
|
5
|
+
it("具有正确的名称和描述", () => {
|
|
6
|
+
expect(shellTool.function.name).toBe("shell");
|
|
7
|
+
expect(shellTool.function.description).toBe("执行 shell 命令并返回输出");
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("参数定义正确", () => {
|
|
11
|
+
const params = shellTool.function.parameters;
|
|
12
|
+
expect(params.properties.command.type).toBe("string");
|
|
13
|
+
expect(params.required).toEqual(["command"]);
|
|
14
|
+
});
|
|
15
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Agent, Message, OpenAI, ChatGPT } from "@ai-zen/agents-core";
|
|
2
|
+
import type { AgentNS } from "@ai-zen/agents-core";
|
|
3
|
+
import { readConfig } from "./config.js";
|
|
4
|
+
import { shellTool } from "./tools.js";
|
|
5
|
+
|
|
6
|
+
const MODEL_NAME = "deepseek-v4-flash";
|
|
7
|
+
const API_ENDPOINT = "https://api.deepseek.com/v1";
|
|
8
|
+
|
|
9
|
+
async function buildModel(apiKey: string) {
|
|
10
|
+
const endpoint = new OpenAI({ openai_endpoint: API_ENDPOINT, api_key: apiKey });
|
|
11
|
+
return new ChatGPT({
|
|
12
|
+
model_config: {},
|
|
13
|
+
request_config: await endpoint.chatCompletion(MODEL_NAME),
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function buildAgent(savedMessages: any[]): Promise<Agent> {
|
|
18
|
+
const config = readConfig();
|
|
19
|
+
const model = await buildModel(config.apiKey);
|
|
20
|
+
const messages: AgentNS.Message[] = [];
|
|
21
|
+
for (const m of savedMessages) messages.push(new Message(m));
|
|
22
|
+
return new Agent({ model, messages, tools: [shellTool] });
|
|
23
|
+
}
|