@nickyzj2023/ai 1.1.3 → 1.2.0
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 +7 -2
- package/dist/cli.mjs +140 -36
- package/dist/index.mjs +1 -1
- package/dist/{src-B0tl6AT5.mjs → src-BJFCp7ur.mjs} +3 -27
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -46,10 +46,15 @@ for await (const e of runAgent(model, messages, tools)) {
|
|
|
46
46
|
### 在终端里使用
|
|
47
47
|
|
|
48
48
|
```bash
|
|
49
|
-
#
|
|
50
|
-
|
|
49
|
+
# 首次使用:交互式配置BASE_URL / APIKEY / MODEL / ...
|
|
50
|
+
ai setup
|
|
51
|
+
|
|
52
|
+
# 启动对话
|
|
53
|
+
ai
|
|
51
54
|
```
|
|
52
55
|
|
|
56
|
+
配置保存在 `~/.@nickyzj2023/ai/config.json`,任意目录下执行`ai`都能读取到
|
|
57
|
+
|
|
53
58
|
## License
|
|
54
59
|
|
|
55
60
|
ISC
|
package/dist/cli.mjs
CHANGED
|
@@ -1,7 +1,83 @@
|
|
|
1
|
-
import { i as defineModel, n as get_weather_default, o as runAgent, r as get_time_default } from "./src-
|
|
2
|
-
import {
|
|
3
|
-
import { loadEnvFile } from "node:process";
|
|
1
|
+
import { i as defineModel, n as get_weather_default, o as runAgent, r as get_time_default } from "./src-BJFCp7ur.mjs";
|
|
2
|
+
import { extractErrorMessage } from "@nickyzj2023/utils";
|
|
4
3
|
import readline from "node:readline";
|
|
4
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
import { dirname, join } from "node:path";
|
|
7
|
+
//#region src/utils/config.ts
|
|
8
|
+
/**
|
|
9
|
+
* 获取全局配置文件路径(Windows/macOS/Linux通用)
|
|
10
|
+
* @returns (Windows下)C:/Users/Administrator/.@nickyzj2023/ai/config.json
|
|
11
|
+
*/
|
|
12
|
+
const getConfigPath = () => {
|
|
13
|
+
return join(homedir(), ".@nickyzj2023", "ai", "config.json");
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* 读取配置文件
|
|
17
|
+
* @returns 正常返回Config,配置不存在或损坏时返回null
|
|
18
|
+
*/
|
|
19
|
+
const loadConfig = () => {
|
|
20
|
+
try {
|
|
21
|
+
const config = JSON.parse(readFileSync(getConfigPath(), "utf8"));
|
|
22
|
+
if (typeof config.baseUrl !== "string" || typeof config.apiKey !== "string" || typeof config.model !== "string") return null;
|
|
23
|
+
return config;
|
|
24
|
+
} catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* 将配置写回全局文件(自动创建目录)
|
|
30
|
+
* @remarks 写入失败会抛异常
|
|
31
|
+
*/
|
|
32
|
+
const saveConfig = (config) => {
|
|
33
|
+
const configPath = getConfigPath();
|
|
34
|
+
mkdirSync(dirname(configPath), { recursive: true });
|
|
35
|
+
writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
|
|
36
|
+
};
|
|
37
|
+
//#endregion
|
|
38
|
+
//#region src/interfaces/setup.ts
|
|
39
|
+
/**
|
|
40
|
+
* 临时创建readline接口,问答结束就关闭
|
|
41
|
+
* @param question 提问
|
|
42
|
+
* @returns 用户输入的回答
|
|
43
|
+
*/
|
|
44
|
+
const ask = (question) => {
|
|
45
|
+
return new Promise((resolve) => {
|
|
46
|
+
const rl = readline.createInterface({
|
|
47
|
+
input: process.stdin,
|
|
48
|
+
output: process.stdout
|
|
49
|
+
});
|
|
50
|
+
rl.question(question, (answer) => {
|
|
51
|
+
rl.close();
|
|
52
|
+
resolve(answer);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* setup入口:依次询问BASE_URL / MODEL / APIKEY,确认后写入全局配置
|
|
58
|
+
*/
|
|
59
|
+
async function runSetup() {
|
|
60
|
+
const config = loadConfig();
|
|
61
|
+
if (config) {
|
|
62
|
+
console.log(`当前配置:BASE_URL ${config.baseUrl},APIKEY ${config.apiKey},MODEL ${config.model}`);
|
|
63
|
+
console.log("直接回车可沿用当前值。\n");
|
|
64
|
+
}
|
|
65
|
+
const baseUrl = await ask(`BASE_URL [当前为${config?.baseUrl}]: `) || config?.baseUrl;
|
|
66
|
+
const apiKey = await ask(`APIKEY [当前为${config?.apiKey}]: `) || config?.apiKey;
|
|
67
|
+
const model = await ask(`MODEL [当前为${config?.model}]: `) || config?.model;
|
|
68
|
+
try {
|
|
69
|
+
saveConfig({
|
|
70
|
+
baseUrl,
|
|
71
|
+
apiKey,
|
|
72
|
+
model
|
|
73
|
+
});
|
|
74
|
+
console.log(`配置已保存到${getConfigPath()},直接运行ai即可开始对话`);
|
|
75
|
+
} catch (e) {
|
|
76
|
+
console.error(`保存配置失败:${extractErrorMessage(e)}`);
|
|
77
|
+
process.exitCode = 1;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
//#endregion
|
|
5
81
|
//#region src/interfaces/tui.ts
|
|
6
82
|
var TUI = class {
|
|
7
83
|
rl = null;
|
|
@@ -94,39 +170,67 @@ var TUI = class {
|
|
|
94
170
|
};
|
|
95
171
|
//#endregion
|
|
96
172
|
//#region src/cli.ts
|
|
97
|
-
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if (!model.apiKey) {
|
|
104
|
-
console.error("请先在环境变量或.env文件中填入APIKEY(目前仅支持DeepSeek)");
|
|
105
|
-
process.exit(1);
|
|
106
|
-
}
|
|
107
|
-
const messages = [];
|
|
108
|
-
const tools = [get_weather_default, get_time_default];
|
|
109
|
-
const tui = new TUI(async (input) => {
|
|
110
|
-
messages.push({
|
|
111
|
-
role: "user",
|
|
112
|
-
content: input
|
|
113
|
-
});
|
|
114
|
-
for await (const e of runAgent(model, messages, tools)) switch (e.type) {
|
|
115
|
-
case "reasoning_delta":
|
|
116
|
-
tui.printReasoning(e.delta);
|
|
117
|
-
break;
|
|
118
|
-
case "content_delta":
|
|
119
|
-
tui.printContent(e.delta);
|
|
120
|
-
break;
|
|
121
|
-
case "tool_call":
|
|
122
|
-
tui.printToolCall(e.name, e.args);
|
|
123
|
-
break;
|
|
124
|
-
case "tool_result":
|
|
125
|
-
tui.printToolResult(e.name, e.result);
|
|
126
|
-
break;
|
|
127
|
-
case "done": tui.printFinish(e.finishReason, e.usage);
|
|
173
|
+
/** 启动交互对话:配置来自全局配置文件,环境变量可临时覆盖 */
|
|
174
|
+
const startChat = () => {
|
|
175
|
+
const config = loadConfig();
|
|
176
|
+
if (!config) {
|
|
177
|
+
console.error("请先运行 `ai setup` 配置一个模型");
|
|
178
|
+
process.exit(1);
|
|
128
179
|
}
|
|
129
|
-
|
|
130
|
-
|
|
180
|
+
const model = defineModel(config);
|
|
181
|
+
const messages = [];
|
|
182
|
+
const tools = [get_weather_default, get_time_default];
|
|
183
|
+
const tui = new TUI(async (input) => {
|
|
184
|
+
messages.push({
|
|
185
|
+
role: "user",
|
|
186
|
+
content: input
|
|
187
|
+
});
|
|
188
|
+
for await (const e of runAgent(model, messages, tools)) switch (e.type) {
|
|
189
|
+
case "reasoning_delta":
|
|
190
|
+
tui.printReasoning(e.delta);
|
|
191
|
+
break;
|
|
192
|
+
case "content_delta":
|
|
193
|
+
tui.printContent(e.delta);
|
|
194
|
+
break;
|
|
195
|
+
case "tool_call":
|
|
196
|
+
tui.printToolCall(e.name, e.args);
|
|
197
|
+
break;
|
|
198
|
+
case "tool_result":
|
|
199
|
+
tui.printToolResult(e.name, e.result);
|
|
200
|
+
break;
|
|
201
|
+
case "error":
|
|
202
|
+
tui.printContent(e.message);
|
|
203
|
+
break;
|
|
204
|
+
case "done": tui.printFinish(e.finishReason, e.usage);
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
tui.start();
|
|
208
|
+
};
|
|
209
|
+
/** 打印命令用法 */
|
|
210
|
+
const printHelp = () => {
|
|
211
|
+
console.log(`用法: ai [命令]
|
|
212
|
+
|
|
213
|
+
命令:
|
|
214
|
+
--help 显示帮助
|
|
215
|
+
setup 交互式配置模型APIKEY / BASE_URL / MODEL(保存到 ~/.@nickyzj2023/ai/config.json)
|
|
216
|
+
|
|
217
|
+
不带命令则启动对话`);
|
|
218
|
+
};
|
|
219
|
+
const command = process.argv[2];
|
|
220
|
+
switch (command) {
|
|
221
|
+
case void 0:
|
|
222
|
+
startChat();
|
|
223
|
+
break;
|
|
224
|
+
case "setup":
|
|
225
|
+
await runSetup();
|
|
226
|
+
break;
|
|
227
|
+
case "--help":
|
|
228
|
+
case "-h":
|
|
229
|
+
printHelp();
|
|
230
|
+
break;
|
|
231
|
+
default:
|
|
232
|
+
console.error(`未知命令:${command}(可以运行ai --help查看用法)`);
|
|
233
|
+
process.exit(1);
|
|
234
|
+
}
|
|
131
235
|
//#endregion
|
|
132
236
|
export {};
|
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as defineTool, i as defineModel, n as get_weather_default, o as runAgent, r as get_time_default, t as compact } from "./src-
|
|
1
|
+
import { a as defineTool, i as defineModel, n as get_weather_default, o as runAgent, r as get_time_default, t as compact } from "./src-BJFCp7ur.mjs";
|
|
2
2
|
export { compact, defineModel, defineTool, get_time_default as getTime, get_weather_default as getWeather, runAgent };
|
|
@@ -130,7 +130,7 @@ async function* runAgent(model, messages, tools) {
|
|
|
130
130
|
if (!tool) result = `不存在工具“${name}”`;
|
|
131
131
|
else {
|
|
132
132
|
const [error, response] = await to(tool.execute(JSON.parse(args)));
|
|
133
|
-
result = error ? `工具“${name}”执行出错:${error.message}` :
|
|
133
|
+
result = error ? `工具“${name}”执行出错:${error.message}` : JSON.stringify(response);
|
|
134
134
|
}
|
|
135
135
|
messages.push({
|
|
136
136
|
role: "tool",
|
|
@@ -212,27 +212,12 @@ const estimateTokens = (messages) => {
|
|
|
212
212
|
};
|
|
213
213
|
//#endregion
|
|
214
214
|
//#region src/tools/get-time.ts
|
|
215
|
-
const weekdayNames = {
|
|
216
|
-
Sunday: "星期日",
|
|
217
|
-
Monday: "星期一",
|
|
218
|
-
Tuesday: "星期二",
|
|
219
|
-
Wednesday: "星期三",
|
|
220
|
-
Thursday: "星期四",
|
|
221
|
-
Friday: "星期五",
|
|
222
|
-
Saturday: "星期六"
|
|
223
|
-
};
|
|
224
215
|
var get_time_default = defineTool("get_time", "查询指定时区的当前时间", { timezone: {
|
|
225
216
|
type: "string",
|
|
226
217
|
description: "完整的IANA时区名称,如Europe/Amsterdam。用户未明确提及时,传入对方语言对应的时区,例如对方使用中文时可传入Asia/Shanghai。",
|
|
227
218
|
required: true
|
|
228
219
|
} }, async ({ timezone }) => {
|
|
229
|
-
|
|
230
|
-
const pad = (n) => String(n).padStart(2, "0");
|
|
231
|
-
return [
|
|
232
|
-
`时区:${data.timeZone}${data.dstActive ? "(夏令时已生效)" : ""}`,
|
|
233
|
-
`日期:${data.year}-${pad(data.month)}-${pad(data.day)}(${weekdayNames[data.dayOfWeek] ?? data.dayOfWeek})`,
|
|
234
|
-
`时间:${pad(data.hour)}:${pad(data.minute)}:${pad(data.seconds)}`
|
|
235
|
-
].join("\n");
|
|
220
|
+
return fetcher("https://timeapi.io/api", { params: { timezone } }).get("/time/current/zone");
|
|
236
221
|
});
|
|
237
222
|
//#endregion
|
|
238
223
|
//#region src/tools/get-weather.ts
|
|
@@ -241,16 +226,7 @@ var get_weather_default = defineTool("get_weather", "查询指定城市的天气
|
|
|
241
226
|
description: "城市名,如shanghai、tokyo",
|
|
242
227
|
required: true
|
|
243
228
|
} }, async ({ city }) => {
|
|
244
|
-
|
|
245
|
-
const nearestArea = data.nearest_area[0];
|
|
246
|
-
const currentCondition = data.current_condition[0];
|
|
247
|
-
return [
|
|
248
|
-
`位置:${nearestArea.country[0].value}-${nearestArea.region[0].value}-${nearestArea.areaName[0].value}`,
|
|
249
|
-
`当前温度: ${currentCondition.temp_C}°C`,
|
|
250
|
-
`体感温度: ${currentCondition.FeelsLikeC}°C`,
|
|
251
|
-
`天气状况: ${currentCondition.weatherDesc[0].value}`,
|
|
252
|
-
`湿度: ${currentCondition.humidity}%`
|
|
253
|
-
].join("\n");
|
|
229
|
+
return fetcher("https://wttr.in", { params: { format: "j1" } }).get(`/${city}`);
|
|
254
230
|
});
|
|
255
231
|
//#endregion
|
|
256
232
|
//#region src/utils/compact/helper.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nickyzj2023/ai",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "我的“pi”,参考了pi-from-scratch",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.mjs",
|
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
"access": "public"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
|
-
"@biomejs/biome": "^2.5.
|
|
30
|
-
"@types/node": "^26.4.
|
|
29
|
+
"@biomejs/biome": "^2.5.12",
|
|
30
|
+
"@types/node": "^26.4.1",
|
|
31
31
|
"tsdown": "^0.22.14",
|
|
32
32
|
"typescript": "^7.0.2"
|
|
33
33
|
},
|