@nickyzj2023/ai 1.6.1 → 1.6.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/dist/cli.mjs CHANGED
@@ -1,7 +1,7 @@
1
- import { i as runAgent, n as get_weather_default, r as get_time_default } from "./src-BzQy5h8z.mjs";
2
- import { t as defineModel } from "./helper-juLVGxlQ.mjs";
3
- import { n as loadMCPTools } from "./mcp-DcntQg-a.mjs";
4
- import { compactStr, extractErrorMessage, isObject } from "@nickyzj2023/utils";
1
+ import { i as runAgent, n as get_weather_default, r as get_time_default } from "./src-Bh2ZFPb4.mjs";
2
+ import { a as listModels, t as defineModel } from "./helper-DJw58Gmz.mjs";
3
+ import { n as loadMCPTools } from "./mcp-BBjcb8es.mjs";
4
+ import { compactStr, extractErrorMessage, isObject, to } from "@nickyzj2023/utils";
5
5
  import readline from "node:readline";
6
6
  import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
7
7
  import { homedir } from "node:os";
@@ -110,12 +110,12 @@ const askMcpServer = async (current) => {
110
110
  };
111
111
  };
112
112
  /**
113
- * 把菜单输入的数字解析成对应的服务器名称
113
+ * 把菜单输入的数字解析成对应的名称
114
114
  * @param input 用户输入的数字串
115
- * @param names 当前菜单的服务器名称列表
115
+ * @param names 当前菜单的名称列表
116
116
  * @returns 对应名称;非整数或越界返回undefined
117
117
  */
118
- const pickServerName = (input, names) => {
118
+ const pickByIndex = (input, names) => {
119
119
  const index = Number(input) - 1;
120
120
  return Number.isInteger(index) ? names[index] : void 0;
121
121
  };
@@ -141,7 +141,7 @@ const configMcp = async (mcpServers) => {
141
141
  }
142
142
  case "d": {
143
143
  const target = (await ask("输入要删除的服务器编号: ")).trim();
144
- const oldName = pickServerName(target, names);
144
+ const oldName = pickByIndex(target, names);
145
145
  if (!oldName) {
146
146
  console.log("无效编号,请输入列表中的数字");
147
147
  break;
@@ -153,7 +153,7 @@ const configMcp = async (mcpServers) => {
153
153
  break;
154
154
  }
155
155
  default: {
156
- const oldName = pickServerName(choice, names);
156
+ const oldName = pickByIndex(choice, names);
157
157
  if (oldName) {
158
158
  const server = mcpServers[oldName];
159
159
  if (!server) console.log("该服务器配置缺失,将重新录入");
@@ -173,17 +173,33 @@ const configMcp = async (mcpServers) => {
173
173
  }
174
174
  };
175
175
  /**
176
+ * 询问MODEL:能连上服务时先列出GET /models的模型,输入序号即可选中,同时也支持直接手输模型名
177
+ * @param baseUrl 刚录入的BASE_URL
178
+ * @param apiKey 刚录入的APIKEY
179
+ * @param fallback 直接回车时沿用的模型
180
+ */
181
+ const askModel = async (baseUrl, apiKey, fallback) => {
182
+ const [error, models] = await to(baseUrl ? listModels(baseUrl, apiKey) : Promise.resolve([]));
183
+ const names = models ?? [];
184
+ if (error) console.log(`获取模型列表失败:${extractErrorMessage(error)}`);
185
+ else if (names.length === 0) console.log("服务端未返回任何模型,请手动输入模型名");
186
+ else {
187
+ console.log("可用模型(输入序号快速选择,也可直接输入模型名):");
188
+ names.forEach((name, i) => {
189
+ console.log(` ${i + 1}. ${name}`);
190
+ });
191
+ }
192
+ const input = (await ask(`MODEL [当前为${fallback}]: `)).trim();
193
+ return pickByIndex(input, names) || input || fallback;
194
+ };
195
+ /**
176
196
  * setup入口:依次询问BASE_URL / MODEL / APIKEY,确认后写入全局配置
177
197
  */
178
198
  async function runSetup() {
179
199
  const config = loadConfig();
180
- if (config) {
181
- console.log(`当前配置:BASE_URL ${config.baseUrl},APIKEY ${config.apiKey},MODEL ${config.model}`);
182
- console.log("直接回车可沿用当前值。\n");
183
- }
184
200
  const baseUrl = await ask(`BASE_URL [当前为${config?.baseUrl}]: `) || config?.baseUrl;
185
201
  const apiKey = await ask(`APIKEY [当前为${config?.apiKey}]: `) || config?.apiKey;
186
- const model = await ask(`MODEL [当前为${config?.model}]: `) || config?.model;
202
+ const model = await askModel(baseUrl, apiKey, config?.model);
187
203
  const mcpServers = { ...config?.mcpServers };
188
204
  if ((await ask("是否配置MCP服务器?(y/n): ")).trim().toLowerCase() === "y") await configMcp(mcpServers);
189
205
  try {
@@ -462,7 +478,7 @@ var TUI = class {
462
478
  printToolResult(name, result, options) {
463
479
  const { ellipsis = true } = options ?? {};
464
480
  const _result = ellipsis ? compactStr(result, {
465
- maxLength: 200,
481
+ maxLength: 100,
466
482
  truncateMiddle: true
467
483
  }) : result;
468
484
  this.preparePrint("tool_result");
@@ -1,3 +1,4 @@
1
+ import { fetcher } from "@nickyzj2023/utils";
1
2
  //#region src/utils/helper.ts
2
3
  /**
3
4
  * 辅助定义一个POST /chat/completions支持的model参数
@@ -44,6 +45,17 @@ const estimateTextTokens = (text) => {
44
45
  return Math.ceil(words * 1.5 + others / 4);
45
46
  };
46
47
  /**
48
+ * 列出GET /models返回的模型id
49
+ * @param baseUrl 接口前缀,如http://127.0.0.1:11434/v1
50
+ * @param apiKey 本地llama.cpp等不校验鉴权的服务可以不传,不传就不带Authorization头
51
+ * @returns 模型的id列表
52
+ * @remarks 请求失败(服务没起、鉴权不过等)会抛异常,由调用方自行兜底
53
+ */
54
+ const listModels = async (baseUrl, apiKey) => {
55
+ const { data } = await fetcher(baseUrl, { headers: apiKey ? { Authorization: `Bearer ${apiKey}` } : void 0 }).get("/models");
56
+ return (data ?? []).map((model) => model.id).filter((id) => typeof id === "string");
57
+ };
58
+ /**
47
59
  * 根据上下文里的中/英文/多模态消息,估算出可能消耗的token
48
60
  * - 单词 ≈ 1.5token
49
61
  * - 标点/空白等非词字符每4个 ≈ 1token
@@ -62,4 +74,4 @@ const estimateTokens = (messages) => {
62
74
  }, 0);
63
75
  };
64
76
  //#endregion
65
- export { estimateTokens as i, defineTool as n, estimateTextTokens as r, defineModel as t };
77
+ export { listModels as a, estimateTokens as i, defineTool as n, estimateTextTokens as r, defineModel as t };
package/dist/index.d.mts CHANGED
@@ -137,6 +137,14 @@ declare const defineModel: (config: Model) => Model;
137
137
  */
138
138
  declare const defineTool: (name: ToolDefinition["function"]["name"], description: ToolDefinition["function"]["description"], properties: ToolDefinition["function"]["parameters"]["properties"], execute: ToolDefinition["execute"]) => ToolDefinition;
139
139
  declare const estimateTextTokens: (text: string) => number;
140
+ /**
141
+ * 列出GET /models返回的模型id
142
+ * @param baseUrl 接口前缀,如http://127.0.0.1:11434/v1
143
+ * @param apiKey 本地llama.cpp等不校验鉴权的服务可以不传,不传就不带Authorization头
144
+ * @returns 模型的id列表
145
+ * @remarks 请求失败(服务没起、鉴权不过等)会抛异常,由调用方自行兜底
146
+ */
147
+ declare const listModels: (baseUrl: string, apiKey?: string) => Promise<string[]>;
140
148
  /**
141
149
  * 根据上下文里的中/英文/多模态消息,估算出可能消耗的token
142
150
  * - 单词 ≈ 1.5token
@@ -145,4 +153,4 @@ declare const estimateTextTokens: (text: string) => number;
145
153
  */
146
154
  declare const estimateTokens: (messages?: Message[]) => number;
147
155
  //#endregion
148
- export { type AgentEvent, type AudioContent, type ChatCompletionsChunk, type ContentPart, type FinishReason, type ImageContent, type LLMEvent, type Message, type Modality, type Model, type TextContent, type ToolCall, type ToolDefinition, type Usage, type VideoContent, compact, defineModel, defineTool, estimateTextTokens, estimateTokens, _default as getTime, _default$1 as getWeather, runAgent, stream };
156
+ export { type AgentEvent, type AudioContent, type ChatCompletionsChunk, type ContentPart, type FinishReason, type ImageContent, type LLMEvent, type Message, type Modality, type Model, type TextContent, type ToolCall, type ToolDefinition, type Usage, type VideoContent, compact, defineModel, defineTool, estimateTextTokens, estimateTokens, _default as getTime, _default$1 as getWeather, listModels, runAgent, stream };
package/dist/index.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import { a as stream, i as runAgent, n as get_weather_default, r as get_time_default, t as compact } from "./src-BzQy5h8z.mjs";
2
- import { i as estimateTokens, n as defineTool, r as estimateTextTokens, t as defineModel } from "./helper-juLVGxlQ.mjs";
3
- export { compact, defineModel, defineTool, estimateTextTokens, estimateTokens, get_time_default as getTime, get_weather_default as getWeather, runAgent, stream };
1
+ import { a as stream, i as runAgent, n as get_weather_default, r as get_time_default, t as compact } from "./src-Bh2ZFPb4.mjs";
2
+ import { a as listModels, i as estimateTokens, n as defineTool, r as estimateTextTokens, t as defineModel } from "./helper-DJw58Gmz.mjs";
3
+ export { compact, defineModel, defineTool, estimateTextTokens, estimateTokens, get_time_default as getTime, get_weather_default as getWeather, listModels, runAgent, stream };
@@ -1,4 +1,4 @@
1
- import { n as defineTool } from "./helper-juLVGxlQ.mjs";
1
+ import { n as defineTool } from "./helper-DJw58Gmz.mjs";
2
2
  import { extractErrorMessage, isObject, logger, omit } from "@nickyzj2023/utils";
3
3
  //#region src/tools/mcp.ts
4
4
  /** 全局单例MCP加载器 */
@@ -1,4 +1,4 @@
1
- import { i as estimateTokens, n as defineTool, r as estimateTextTokens } from "./helper-juLVGxlQ.mjs";
1
+ import { i as estimateTokens, n as defineTool, r as estimateTextTokens } from "./helper-DJw58Gmz.mjs";
2
2
  import { createXMLText, extractErrorMessage, fetcher, logger, parseSSE, pick, to } from "@nickyzj2023/utils";
3
3
  //#region src/llm.ts
4
4
  /**
@@ -190,12 +190,64 @@ var get_time_default = defineTool("get_time", "查询指定时区的当前时间
190
190
  });
191
191
  //#endregion
192
192
  //#region src/tools/get-weather.ts
193
+ /**
194
+ * 把wttr.in的时间转成好读的"HH:mm"
195
+ * @param rawTime - wttr.in hourly里的time字段,3小时粒度不带冒号,比如"0"、"300"、"1200"
196
+ * @returns 补零后的时间字符串,比如"00:00"、"03:00"、"12:00"
197
+ * @example toHHmm("930") // "09:30"
198
+ */
199
+ const toHHmm = (rawTime) => {
200
+ const padded = String(rawTime ?? "").padStart(4, "0");
201
+ return `${padded.slice(0, 2)}:${padded.slice(2)}`;
202
+ };
203
+ /**
204
+ * 精简wttr.in返回的天气数据
205
+ * @param rawData - wttr.in API返回的原始JSON
206
+ * @returns 精简后的天气数据
207
+ */
208
+ const pickValuableFields = (rawData) => {
209
+ if (!rawData) return null;
210
+ const area = rawData.nearest_area?.[0] || {};
211
+ return {
212
+ location: {
213
+ city: area.areaName?.[0]?.value || "",
214
+ region: area.region?.[0]?.value || "",
215
+ country: area.country?.[0]?.value || "",
216
+ latitude: area.latitude,
217
+ longitude: area.longitude
218
+ },
219
+ forecast: (rawData.weather || []).map((day) => ({
220
+ date: day.date,
221
+ maxTempC: day.maxtempC,
222
+ minTempC: day.mintempC,
223
+ astro: {
224
+ sunrise: day.astronomy?.[0]?.sunrise || "",
225
+ sunset: day.astronomy?.[0]?.sunset || "",
226
+ moonPhase: day.astronomy?.[0]?.moon_phase || ""
227
+ },
228
+ hourly: (day.hourly || []).map((h) => ({
229
+ time: toHHmm(h.time),
230
+ tempC: h.tempC,
231
+ feelsLikeC: h.FeelsLikeC,
232
+ weatherDesc: h.weatherDesc?.[0]?.value || "",
233
+ chanceOfRain: h.chanceofrain,
234
+ precipMM: h.precipMM,
235
+ humidity: h.humidity,
236
+ windSpeedKmph: h.windspeedKmph,
237
+ windDir: h.winddir16Point
238
+ }))
239
+ }))
240
+ };
241
+ };
193
242
  var get_weather_default = defineTool("get_weather", "查询指定城市的天气情况", { city: {
194
243
  type: "string",
195
244
  description: "城市名,如shanghai、tokyo",
196
245
  required: true
197
246
  } }, async ({ city }) => {
198
- return fetcher("https://wttr.in", { params: { format: "j1" } }).get(`/${city}`);
247
+ const api = fetcher("https://wttr.in", { params: { format: "j1" } });
248
+ const [error, response] = await to(api.get(`/${city}`));
249
+ if (error) return error.message;
250
+ return pickValuableFields(response);
199
251
  });
200
252
  //#endregion
201
253
  //#region src/utils/compact/helper.ts
@@ -1,2 +1,2 @@
1
- import { n as loadMCPTools, t as MCPRouter } from "../mcp-DcntQg-a.mjs";
1
+ import { n as loadMCPTools, t as MCPRouter } from "../mcp-BBjcb8es.mjs";
2
2
  export { MCPRouter, loadMCPTools };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nickyzj2023/ai",
3
- "version": "1.6.1",
3
+ "version": "1.6.3",
4
4
  "description": "我的“pi”,参考了pi-from-scratch",
5
5
  "type": "module",
6
6
  "main": "./dist/index.mjs",