@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 CHANGED
@@ -46,10 +46,15 @@ for await (const e of runAgent(model, messages, tools)) {
46
46
  ### 在终端里使用
47
47
 
48
48
  ```bash
49
- # 配置deepseek环境变量、启动对话
50
- SET APIKEY=sk-xxx && ai
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-B0tl6AT5.mjs";
2
- import { existsSync } from "node:fs";
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
- if (existsSync(".env")) loadEnvFile(".env");
98
- const model = defineModel({
99
- baseUrl: process.env.BASE_URL ?? "https://api.deepseek.com/v1",
100
- apiKey: process.env.APIKEY,
101
- model: process.env.MODEL ?? "deepseek-v4-flash"
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
- tui.start();
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-B0tl6AT5.mjs";
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}` : String(response);
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
- const data = await fetcher("https://timeapi.io/api", { params: { timezone } }).get("/time/current/zone");
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
- const data = await fetcher("https://wttr.in", { params: { format: "j1" } }).get(`/${city}`);
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.1.3",
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.11",
30
- "@types/node": "^26.4.0",
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
  },