@nickyzj2023/ai 1.4.4 → 1.6.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.
@@ -1,6 +1,5 @@
1
- import { createXMLText, extractErrorMessage, fetcher, isObject, logger, omit, parseSSE, pick, to } from "@nickyzj2023/utils";
2
- import { Client } from "@modelcontextprotocol/sdk/client";
3
- import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
1
+ import { i as estimateTokens, n as defineTool, r as estimateTextTokens } from "./helper-juLVGxlQ.mjs";
2
+ import { createXMLText, extractErrorMessage, fetcher, logger, parseSSE, pick, to } from "@nickyzj2023/utils";
4
3
  //#region src/llm.ts
5
4
  /**
6
5
  * 分离ToolDefinition中的valid/invalid字段,前者可以传给模型,后者用于本地运算
@@ -22,7 +21,7 @@ const extractReasoning = (msgLike) => {
22
21
  /**
23
22
  * 流式请求模型,转发
24
23
  */
25
- async function* stream(model, messages, tools = []) {
24
+ async function* stream(model, messages, tools = [], options) {
26
25
  const validTools = tools.map((tool) => detachToolArguments(tool)[0]);
27
26
  const api = fetcher(model.baseUrl, {
28
27
  headers: { Authorization: `Bearer ${model.apiKey}` },
@@ -32,9 +31,11 @@ async function* stream(model, messages, tools = []) {
32
31
  stream: true,
33
32
  model: model.model,
34
33
  messages,
35
- tools: validTools
36
- }));
34
+ tools: validTools,
35
+ ...options?.body
36
+ }, options));
37
37
  if (error) {
38
+ if (options?.signal?.aborted) return;
38
39
  yield {
39
40
  type: "error",
40
41
  message: error.message
@@ -44,39 +45,48 @@ async function* stream(model, messages, tools = []) {
44
45
  const toolCallBuffers = /* @__PURE__ */ new Map();
45
46
  let usage;
46
47
  let finishReason = null;
47
- for await (const chunk of parseSSE(response)) {
48
- if (typeof chunk === "string") continue;
49
- if (chunk.usage) usage = chunk.usage;
50
- const choice = chunk.choices?.[0];
51
- if (!choice) continue;
52
- const { delta } = choice;
53
- const { content: contentDelta, tool_calls: toolCalls } = delta;
54
- const reasoning = extractReasoning(delta);
55
- if (reasoning) yield {
56
- type: "reasoning_delta",
57
- delta: reasoning
58
- };
59
- if (contentDelta) yield {
60
- type: "content_delta",
61
- delta: contentDelta.toString()
62
- };
63
- if (toolCalls) for (const call of toolCalls) {
64
- const { index = 0, type = "function", id, function: fn, ...extra } = call;
65
- const existing = toolCallBuffers.getOrInsert(index, {
66
- id: "",
67
- type,
68
- function: {
69
- name: "",
70
- arguments: ""
71
- }
72
- });
73
- if (id) existing.id = id;
74
- if (fn?.name) existing.function.name += fn.name;
75
- if (fn?.arguments) existing.function.arguments += fn.arguments;
76
- if (extra) Object.assign(existing, extra);
77
- toolCallBuffers.set(index, existing);
48
+ try {
49
+ for await (const chunk of parseSSE(response)) {
50
+ if (typeof chunk === "string") continue;
51
+ if (chunk.usage) usage = chunk.usage;
52
+ const choice = chunk.choices?.[0];
53
+ if (!choice) continue;
54
+ const { delta } = choice;
55
+ const { content: contentDelta, tool_calls: toolCalls } = delta;
56
+ const reasoning = extractReasoning(delta);
57
+ if (reasoning) yield {
58
+ type: "reasoning_delta",
59
+ delta: reasoning
60
+ };
61
+ if (contentDelta) yield {
62
+ type: "content_delta",
63
+ delta: contentDelta.toString()
64
+ };
65
+ if (toolCalls) for (const call of toolCalls) {
66
+ const { index = 0, type = "function", id, function: fn, ...extra } = call;
67
+ const existing = toolCallBuffers.getOrInsert(index, {
68
+ id: "",
69
+ type,
70
+ function: {
71
+ name: "",
72
+ arguments: ""
73
+ }
74
+ });
75
+ if (id) existing.id = id;
76
+ if (fn?.name) existing.function.name += fn.name;
77
+ if (fn?.arguments) existing.function.arguments += fn.arguments;
78
+ if (extra) Object.assign(existing, extra);
79
+ toolCallBuffers.set(index, existing);
80
+ }
81
+ if (choice.finish_reason) finishReason = choice.finish_reason;
78
82
  }
79
- if (choice.finish_reason) finishReason = choice.finish_reason;
83
+ } catch (e) {
84
+ if (options?.signal?.aborted) return;
85
+ yield {
86
+ type: "error",
87
+ message: extractErrorMessage(e)
88
+ };
89
+ return;
80
90
  }
81
91
  for (const [, call] of toolCallBuffers) yield {
82
92
  type: "tool_call",
@@ -96,27 +106,48 @@ async function* runAgent(model, messages, tools) {
96
106
  const toolMap = new Map(tools.map((tool) => [tool.function.name, tool]));
97
107
  while (true) {
98
108
  let content = "";
109
+ let reasoning = "";
99
110
  const toolCalls = [];
100
- for await (const e of stream(model, messages, tools)) switch (e.type) {
101
- case "reasoning_delta":
102
- yield e;
103
- break;
104
- case "content_delta":
105
- content += e.delta;
106
- yield e;
107
- break;
108
- case "tool_call":
109
- toolCalls.push({
110
- id: e.id,
111
- type: "function",
112
- function: {
113
- name: e.name,
114
- arguments: e.args
111
+ const promptMessage = messages.slice(-1);
112
+ let startTime = 0;
113
+ for await (const e of stream(model, messages, tools)) {
114
+ startTime ||= Date.now();
115
+ switch (e.type) {
116
+ case "reasoning_delta":
117
+ reasoning += e.delta;
118
+ yield e;
119
+ break;
120
+ case "content_delta":
121
+ content += e.delta;
122
+ yield e;
123
+ break;
124
+ case "tool_call":
125
+ toolCalls.push({
126
+ id: e.id,
127
+ type: "function",
128
+ function: {
129
+ name: e.name,
130
+ arguments: e.args
131
+ }
132
+ });
133
+ yield e;
134
+ break;
135
+ case "error":
136
+ yield e;
137
+ break;
138
+ case "done":
139
+ if (!e.usage) {
140
+ const promptTokens = estimateTokens(promptMessage);
141
+ const completionTokens = estimateTextTokens(content + reasoning);
142
+ e.usage = {
143
+ prompt_tokens: promptTokens,
144
+ completion_tokens: completionTokens,
145
+ total_tokens: estimateTokens(messages) + promptTokens + completionTokens
146
+ };
115
147
  }
116
- });
117
- yield e;
118
- break;
119
- case "done": yield e;
148
+ e.usage.speed = Math.floor(e.usage.completion_tokens / ((Date.now() - startTime) / 1e3));
149
+ yield e;
150
+ }
120
151
  }
121
152
  const message = {
122
153
  role: "assistant",
@@ -149,70 +180,6 @@ async function* runAgent(model, messages, tools) {
149
180
  }
150
181
  }
151
182
  //#endregion
152
- //#region src/utils/helper.ts
153
- /**
154
- * 辅助定义一个POST /chat/completions支持的model参数
155
- * @remarks 只有baseUrl字段是必须的
156
- */
157
- const defineModel = (config) => ({
158
- modalities: ["text"],
159
- context: 131072,
160
- ...config
161
- });
162
- /**
163
- * 辅助定义一个POST /chat/completions支持的tool对象
164
- * @param execute 实际执行工具的函数
165
- */
166
- const defineTool = (name, description, properties, execute) => {
167
- const _required = [];
168
- return {
169
- type: "function",
170
- function: {
171
- name,
172
- description,
173
- parameters: {
174
- type: "object",
175
- properties: Object.entries(properties).reduce((result, [key, property]) => {
176
- if ("required" in property) {
177
- _required.push(key);
178
- delete property.required;
179
- }
180
- result[key] = property;
181
- return result;
182
- }, {}),
183
- required: _required
184
- }
185
- },
186
- execute
187
- };
188
- };
189
- /**
190
- * 根据上下文里的中/英文/多模态消息,估算出可能消耗的token
191
- * - 单词 ≈ 1.5token
192
- * - 标点/空白等非词字符每 4 个 ≈ 1token
193
- * - 图片/音频/视频/文件 ≈ 4096token(不好估算,取个较大的值)
194
- */
195
- const estimateTokens = (messages) => {
196
- if (!messages?.length) return 0;
197
- const segmenter = new Intl.Segmenter([], { granularity: "word" });
198
- const estimateTextTokens = (text) => {
199
- let words = 0;
200
- let others = 0;
201
- for (const seg of segmenter.segment(text)) if (seg.isWordLike) words++;
202
- else others++;
203
- return Math.ceil(words * 1.5 + others / 4);
204
- };
205
- return messages.reduce((acc, message) => {
206
- const { content, tool_calls, ...metadata } = message;
207
- if (typeof content === "string") acc += estimateTextTokens(content);
208
- else for (const part of content) if (part.type === "text") acc += estimateTextTokens(part.text);
209
- else acc += 4096;
210
- if (tool_calls) acc += estimateTextTokens(JSON.stringify(tool_calls));
211
- acc += estimateTextTokens(JSON.stringify(metadata));
212
- return acc;
213
- }, 0);
214
- };
215
- //#endregion
216
183
  //#region src/tools/get-time.ts
217
184
  var get_time_default = defineTool("get_time", "查询指定时区的当前时间", { timezone: {
218
185
  type: "string",
@@ -231,60 +198,6 @@ var get_weather_default = defineTool("get_weather", "查询指定城市的天气
231
198
  return fetcher("https://wttr.in", { params: { format: "j1" } }).get(`/${city}`);
232
199
  });
233
200
  //#endregion
234
- //#region src/tools/mcp.ts
235
- /** 全局单例MCP加载器 */
236
- let router = null;
237
- var MCPRouter = class {
238
- entries = /* @__PURE__ */ new Map();
239
- /** 注册一个新的MCP客户端 */
240
- async addClient(name, url, options) {
241
- if (this.entries.has(name)) return;
242
- const transport = new StreamableHTTPClientTransport(new URL(url), { requestInit: { headers: options?.headers } });
243
- const client = new Client({
244
- name,
245
- version: "1.0.0"
246
- });
247
- await client.connect(transport);
248
- const { tools } = await client.listTools();
249
- const normalizedTools = tools.filter((tool) => !options?.ignoredToolNames?.includes(tool.name)).map((tool) => {
250
- const _properties = { ...tool.inputSchema.properties ?? {} };
251
- tool.inputSchema.required?.forEach((key) => {
252
- if (isObject(_properties[key])) _properties[key] = {
253
- ..._properties[key],
254
- required: true
255
- };
256
- });
257
- return defineTool(tool.name, tool.description ?? "", _properties, (args) => client.callTool({
258
- name: tool.name,
259
- arguments: args
260
- }));
261
- });
262
- this.entries.set(name, {
263
- client,
264
- tools: normalizedTools
265
- });
266
- return client;
267
- }
268
- /** 返回OpenAI API兼容的tools数组 */
269
- async getTools() {
270
- return [...this.entries.values()].flatMap((e) => e.tools);
271
- }
272
- };
273
- /**
274
- * 把传入的MCPServer列表转换成OpenAI API兼容的tools数组
275
- */
276
- const loadMCPTools = async (mcpServers = {}) => {
277
- router ||= new MCPRouter();
278
- await Promise.allSettled(Object.entries(mcpServers).map(async ([name, server]) => {
279
- try {
280
- await router?.addClient(name, server.url, omit(server, ["type", "url"]));
281
- } catch (e) {
282
- logger(`MCP服务器${name}加载失败:${extractErrorMessage(e)}`);
283
- }
284
- }));
285
- return router.getTools();
286
- };
287
- //#endregion
288
201
  //#region src/utils/compact/helper.ts
289
202
  /**
290
203
  * 校验assistant(tool_calls)消息
@@ -296,12 +209,11 @@ const isToolCalls = (message) => {
296
209
  * 校验多模态消息
297
210
  */
298
211
  const isMediaMessage = (message) => {
299
- const MEDIA_TYPES = [
300
- "image_url",
301
- "input_audio",
302
- "video_url"
303
- ];
304
- return message && Array.isArray(message.content) && message.content.some((part) => MEDIA_TYPES.includes(part.type));
212
+ if (message && Array.isArray(message.content)) for (const part of message.content) {
213
+ if (part.type === "image_url") return "image";
214
+ if (part.type === "input_audio") return "audio";
215
+ if (part.type === "video_url") return "video";
216
+ }
305
217
  };
306
218
  /**
307
219
  * 查找assistant(tool_calls) + tool配对组范围
@@ -330,7 +242,8 @@ const defaultReplacerOfToolResultContent = async (content, options) => {
330
242
  content: "请用一句话简述上面这条消息(直击要点,不要添加任何前言后语)"
331
243
  }];
332
244
  let simplifiedContent = "";
333
- for await (const e of runAgent(model, messages, [])) if (e.type === "content_delta") simplifiedContent += e.delta;
245
+ if (!model) simplifiedContent = "工具结果已被消费";
246
+ else for await (const e of runAgent(model, messages, [])) if (e.type === "content_delta") simplifiedContent += e.delta;
334
247
  else if (e.type === "error") throw new Error(e.message);
335
248
  return simplifiedContent;
336
249
  };
@@ -352,7 +265,7 @@ const compactToolResults = async (compressible, options) => {
352
265
  };
353
266
  /** 默认的压缩多模态消息策略:让大模型精简消息内容 */
354
267
  const defaultReplacerOfMediaContent = async (content, options) => {
355
- const { model } = options ?? {};
268
+ const { model, modality = "image" } = options ?? {};
356
269
  const messages = [{
357
270
  role: "user",
358
271
  content
@@ -361,7 +274,8 @@ const defaultReplacerOfMediaContent = async (content, options) => {
361
274
  content: "请用一句话简述上面这条消息(直击要点,不要添加任何前言后语)"
362
275
  }];
363
276
  let simplifiedContent = "";
364
- for await (const e of runAgent(model, messages, [])) if (e.type === "content_delta") simplifiedContent += e.delta;
277
+ if (!model?.modalities?.includes(modality)) simplifiedContent = "媒体资源已被消费";
278
+ else for await (const e of runAgent(model, messages, [])) if (e.type === "content_delta") simplifiedContent += e.delta;
365
279
  else if (e.type === "error") throw new Error(e.message);
366
280
  return simplifiedContent;
367
281
  };
@@ -373,21 +287,28 @@ const compactMediaMessages = async (compressible, options) => {
373
287
  if (!replacer && !model) return 0;
374
288
  const _replacer = replacer || defaultReplacerOfMediaContent;
375
289
  let count = 0;
376
- for (const message of compressible) if (isMediaMessage(message)) {
377
- const compacted = await _replacer(message.content, { model });
378
- message.content = createXMLText("media", compacted);
379
- count++;
290
+ for (const message of compressible) {
291
+ const mediaModality = isMediaMessage(message);
292
+ if (mediaModality) {
293
+ const compacted = await _replacer(message.content, {
294
+ model,
295
+ modality: mediaModality
296
+ });
297
+ message.content = createXMLText("media", compacted);
298
+ count++;
299
+ }
380
300
  }
381
301
  if (count > 0) logger(`压缩了${count}条多模态消息`);
382
302
  return count;
383
303
  };
384
304
  const summarizeMessages = async (compressible, options) => {
385
- const { model, systemPrompt } = options ?? {};
305
+ const { model, systemPrompt, beforeSummarize } = options ?? {};
386
306
  const summarizable = compressible.slice(1);
387
307
  if (summarizable.length === 0) {
388
308
  logger("消息太少,无需总结");
389
309
  return 0;
390
310
  }
311
+ beforeSummarize?.(summarizable);
391
312
  const count = summarizable.length;
392
313
  summarizable.push({
393
314
  role: "system",
@@ -459,10 +380,11 @@ const compact = Object.assign(async (messages, model, options) => {
459
380
  model
460
381
  }) > 0;
461
382
  if (tokens > context * ratioToSummarize) {
462
- const { systemPrompt = "你现在的任务是总结历史消息" } = summarizeOptions ?? {};
383
+ const { systemPrompt = "你现在的任务是总结历史消息", beforeSummarize } = summarizeOptions ?? {};
463
384
  const [error, count] = await to(summarizeMessages(compressible, {
464
385
  model,
465
- systemPrompt
386
+ systemPrompt,
387
+ beforeSummarize
466
388
  }));
467
389
  if (!error) result.hasSummarized = count > 0;
468
390
  else result.hasDiscardMessages = discardMessagesUntil(compressible, (compressible) => {
@@ -484,4 +406,4 @@ const compact = Object.assign(async (messages, model, options) => {
484
406
  discardMessagesUntil
485
407
  });
486
408
  //#endregion
487
- export { get_time_default as a, runAgent as c, get_weather_default as i, MCPRouter as n, defineModel as o, loadMCPTools as r, defineTool as s, compact as t };
409
+ export { stream as a, runAgent as i, get_weather_default as n, get_time_default as r, compact as t };
@@ -0,0 +1,55 @@
1
+ import { p as ToolDefinition } from "../types--dXFoUow.mjs";
2
+ import { Client } from "@modelcontextprotocol/sdk/client";
3
+ //#region src/tools/mcp.d.ts
4
+ type McpServer = {
5
+ type: "streamable_http" | "sse";
6
+ url: string;
7
+ headers?: Record<string, any>;
8
+ ignoredToolNames?: string[];
9
+ };
10
+ declare class MCPRouter {
11
+ private entries;
12
+ /** 注册一个新的MCP客户端 */
13
+ addClient(name: string, url: string, options?: Omit<McpServer, "type" | "url">): Promise<Client<{
14
+ method: string;
15
+ params?: {
16
+ [x: string]: unknown;
17
+ _meta?: {
18
+ [x: string]: unknown;
19
+ progressToken?: string | number | undefined;
20
+ "io.modelcontextprotocol/related-task"?: {
21
+ taskId: string;
22
+ } | undefined;
23
+ } | undefined;
24
+ } | undefined;
25
+ }, {
26
+ method: string;
27
+ params?: {
28
+ [x: string]: unknown;
29
+ _meta?: {
30
+ [x: string]: unknown;
31
+ progressToken?: string | number | undefined;
32
+ "io.modelcontextprotocol/related-task"?: {
33
+ taskId: string;
34
+ } | undefined;
35
+ } | undefined;
36
+ } | undefined;
37
+ }, {
38
+ [x: string]: unknown;
39
+ _meta?: {
40
+ [x: string]: unknown;
41
+ progressToken?: string | number | undefined;
42
+ "io.modelcontextprotocol/related-task"?: {
43
+ taskId: string;
44
+ } | undefined;
45
+ } | undefined;
46
+ }> | undefined>;
47
+ /** 返回OpenAI API兼容的tools数组 */
48
+ getTools(): Promise<ToolDefinition[]>;
49
+ }
50
+ /**
51
+ * 把传入的MCPServer列表转换成OpenAI API兼容的tools数组
52
+ */
53
+ declare const loadMCPTools: (mcpServers?: Record<string, McpServer>) => Promise<ToolDefinition[]>;
54
+ //#endregion
55
+ export { MCPRouter, McpServer, loadMCPTools };
@@ -0,0 +1,2 @@
1
+ import { n as loadMCPTools, t as MCPRouter } from "../mcp-DcntQg-a.mjs";
2
+ export { MCPRouter, loadMCPTools };
@@ -0,0 +1,144 @@
1
+ //#region src/types.d.ts
2
+ type Model = {
3
+ baseUrl: string;
4
+ /** 如果不传,会尝试使用GET {baseUrl}/models获取到的第一个模型 */
5
+ model?: string;
6
+ /** 使用本地llama.cpp等服务时可以不填 */
7
+ apiKey?: string;
8
+ /**
9
+ * 模型支持的消息输入类型
10
+ * @default ["text"]
11
+ * @remarks 会在每轮对话前校验消息类型,存在不支持的输入时报错
12
+ */
13
+ modalities?: Modality[];
14
+ /**
15
+ * 模型的最大上下文
16
+ * @default 131072
17
+ * @remarks 会在每轮对话结束后检查上下文阈值,自动压缩消息
18
+ */
19
+ context?: number;
20
+ /**
21
+ * 自定义请求体,会在每轮请求时带上
22
+ * @example chat_template_kwargs: { enable_thinking: false }
23
+ */
24
+ [key: string]: unknown;
25
+ };
26
+ /** 文/图/音/视频,暂未实现文件输入 */
27
+ type Modality = "text" | "image" | "audio" | "video";
28
+ type Message = {
29
+ role: "system" | "user" | "assistant" | "tool";
30
+ reasoning?: string | null;
31
+ content: string | ContentPart[];
32
+ tool_calls?: ToolCall[];
33
+ tool_call_id?: string;
34
+ [key: string]: unknown;
35
+ };
36
+ type ContentPart = TextContent | ImageContent | AudioContent | VideoContent;
37
+ type TextContent = {
38
+ type: "text";
39
+ text: string;
40
+ };
41
+ type ImageContent = {
42
+ type: "image_url";
43
+ image_url: {
44
+ url: string;
45
+ };
46
+ };
47
+ type AudioContent = {
48
+ type: "input_audio";
49
+ input_audio: {
50
+ /** 使用公网可访问的音频链接 */
51
+ url?: string;
52
+ /** 使用base64 */
53
+ data?: string;
54
+ format: string;
55
+ };
56
+ };
57
+ type VideoContent = {
58
+ type: "video_url";
59
+ video_url: {
60
+ url: string;
61
+ };
62
+ };
63
+ type ToolDefinition = {
64
+ type: "function";
65
+ function: {
66
+ name: string;
67
+ description: string;
68
+ parameters: {
69
+ type: "object";
70
+ properties: Record<string, {
71
+ type: string;
72
+ description?: string;
73
+ /** defineTool()提供的语法糖,等价于在parameters.required.push(当前property key) */
74
+ required?: boolean;
75
+ }>;
76
+ required?: string[];
77
+ };
78
+ };
79
+ /** 工具的实际执行函数 */
80
+ execute: (...args: any) => any;
81
+ };
82
+ type ToolCall = {
83
+ id: string;
84
+ type: "function";
85
+ function: {
86
+ name: string;
87
+ arguments: string;
88
+ };
89
+ };
90
+ /** stream()对外输出的事件 */
91
+ type LLMEvent = {
92
+ type: "reasoning_delta";
93
+ delta: string;
94
+ } | {
95
+ type: "content_delta";
96
+ delta: string;
97
+ } | {
98
+ type: "tool_call";
99
+ id: string;
100
+ name: string;
101
+ args: any;
102
+ } | {
103
+ type: "done";
104
+ finishReason: FinishReason;
105
+ usage?: Usage;
106
+ } | {
107
+ type: "error";
108
+ message: string;
109
+ };
110
+ /** stream()内部要处理的事件流 */
111
+ type ChatCompletionsChunk = {
112
+ id: string;
113
+ object: "chat.completion.chunk";
114
+ created: number;
115
+ model: string;
116
+ choices: {
117
+ index: number;
118
+ delta: Pick<Message, "reasoning" | "content"> & {
119
+ tool_calls?: ({
120
+ index: number;
121
+ } & Partial<ToolCall>)[];
122
+ };
123
+ finish_reason: FinishReason;
124
+ }[];
125
+ usage?: Usage;
126
+ } | "[DONE]";
127
+ /** 模型可能返回的finish_reason值:停止/工具调用/上下文超限 */
128
+ type FinishReason = "stop" | "tool_calls" | "length" | null;
129
+ type Usage = {
130
+ prompt_tokens: number;
131
+ completion_tokens: number;
132
+ total_tokens: number;
133
+ /** 每秒生成多少个token */
134
+ speed?: number;
135
+ [key: string]: any;
136
+ };
137
+ type AgentEvent = LLMEvent | {
138
+ type: "tool_result";
139
+ id: string;
140
+ name: string;
141
+ result: string;
142
+ };
143
+ //#endregion
144
+ export { FinishReason as a, Message as c, TextContent as d, ToolCall as f, VideoContent as h, ContentPart as i, Modality as l, Usage as m, AudioContent as n, ImageContent as o, ToolDefinition as p, ChatCompletionsChunk as r, LLMEvent as s, AgentEvent as t, Model as u };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nickyzj2023/ai",
3
- "version": "1.4.4",
3
+ "version": "1.6.0",
4
4
  "description": "我的“pi”,参考了pi-from-scratch",
5
5
  "type": "module",
6
6
  "main": "./dist/index.mjs",
@@ -9,6 +9,10 @@
9
9
  ".": {
10
10
  "types": "./dist/index.d.mts",
11
11
  "import": "./dist/index.mjs"
12
+ },
13
+ "./mcp": {
14
+ "types": "./dist/tools/mcp.d.mts",
15
+ "import": "./dist/tools/mcp.mjs"
12
16
  }
13
17
  },
14
18
  "files": [
@@ -27,7 +31,7 @@
27
31
  },
28
32
  "devDependencies": {
29
33
  "@biomejs/biome": "^2.5.12",
30
- "@types/node": "^26.4.1",
34
+ "@types/node": "^26.5.0",
31
35
  "tsdown": "^0.22.14",
32
36
  "typescript": "^7.0.2"
33
37
  },