my-ai-chat-framework 2.0.0 → 2.7.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 +242 -45
- package/dist/my-ai-chat-framework.browser.es.js +1048 -184
- package/dist/my-ai-chat-framework.browser.es.js.map +1 -1
- package/dist/my-ai-chat-framework.browser.umd.js +1056 -188
- package/dist/my-ai-chat-framework.browser.umd.js.map +1 -1
- package/dist/my-ai-chat-framework.node.cjs.js +1056 -188
- package/dist/my-ai-chat-framework.node.cjs.js.map +1 -1
- package/package.json +7 -7
- package/src/adapters/openai.js +214 -64
- package/src/core/ChatService.js +298 -110
- package/src/core/Errors.js +60 -0
- package/src/core/EventEmitter.js +19 -0
- package/src/core/MessageStore.js +39 -0
- package/src/core/SystemPromptStore.js +118 -0
- package/src/index.js +10 -14
- package/src/plugins/model-registry.js +187 -0
- package/src/plugins/tool-calling.js +172 -78
- package/src/utils/MessageFormatter.js +204 -0
- package/src/utils/typeCheck.js +12 -0
- package/src/utils/url.js +18 -0
- package/.env +0 -15
- package/test.js +0 -107
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
(function(global, factory) {
|
|
2
2
|
typeof exports === "object" && typeof module !== "undefined" ? factory(exports) : typeof define === "function" && define.amd ? define(["exports"], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global.AIChatFramework = {}));
|
|
3
3
|
})(this, function(exports) {
|
|
4
|
-
Object.
|
|
5
|
-
__esModule: { value: true },
|
|
6
|
-
[Symbol.toStringTag]: { value: "Module" }
|
|
7
|
-
});
|
|
4
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
8
5
|
//#region src/core/EventEmitter.js
|
|
9
6
|
/**
|
|
10
7
|
* 极简事件发射器
|
|
8
|
+
* 支持同步/异步事件,handler 返回 Promise 时自动 await
|
|
11
9
|
*/
|
|
12
10
|
var EventEmitter = class {
|
|
13
11
|
constructor() {
|
|
@@ -24,6 +22,9 @@
|
|
|
24
22
|
if (handlers.length) this._events.set(event, handlers);
|
|
25
23
|
else this._events.delete(event);
|
|
26
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* 同步触发事件
|
|
27
|
+
*/
|
|
27
28
|
emit(event, data) {
|
|
28
29
|
if (!this._events.has(event)) return;
|
|
29
30
|
for (const handler of this._events.get(event)) try {
|
|
@@ -32,6 +33,18 @@
|
|
|
32
33
|
console.error(`事件 ${event} 处理出错:`, err);
|
|
33
34
|
}
|
|
34
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* 异步触发事件,逐个 await handler
|
|
38
|
+
* handler 返回 Promise 时自动等待
|
|
39
|
+
*/
|
|
40
|
+
async emitAsync(event, data) {
|
|
41
|
+
if (!this._events.has(event)) return;
|
|
42
|
+
for (const handler of this._events.get(event)) try {
|
|
43
|
+
await handler(data);
|
|
44
|
+
} catch (err) {
|
|
45
|
+
console.error(`事件 ${event} 处理出错:`, err);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
35
48
|
};
|
|
36
49
|
//#endregion
|
|
37
50
|
//#region src/core/MessageStore.js
|
|
@@ -42,6 +55,10 @@
|
|
|
42
55
|
* id?: string,
|
|
43
56
|
* role: 'user'|'assistant'|'system'|'tool',
|
|
44
57
|
* content: string,
|
|
58
|
+
* images?: Array<string>, // 图片引用(URL / id),框架不存储图片数据
|
|
59
|
+
* _ephemeral?: boolean, // 临时消息:仅底部连续时参与请求,续写后转正
|
|
60
|
+
* _complete?: boolean, // 流式是否完成(false=未完成/中断)
|
|
61
|
+
* prefix?: boolean, // 前缀续写标记
|
|
45
62
|
* toolCalls?: Array,
|
|
46
63
|
* toolCallId?: string,
|
|
47
64
|
* timestamp?: number,
|
|
@@ -91,6 +108,22 @@
|
|
|
91
108
|
metadata
|
|
92
109
|
});
|
|
93
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* 快速添加一次性的、带续写标记的 assistant 消息
|
|
113
|
+
* 适用于思维链引导、临时注入等场景
|
|
114
|
+
* @param {string} content — 引导内容
|
|
115
|
+
* @param {Object} [options] — { reasoningContent, prefix }
|
|
116
|
+
* @returns {Object} 添加的消息
|
|
117
|
+
*/
|
|
118
|
+
addOnceAssistant(content, options = {}) {
|
|
119
|
+
return this.add({
|
|
120
|
+
role: "assistant",
|
|
121
|
+
content,
|
|
122
|
+
reasoningContent: options.reasoningContent,
|
|
123
|
+
prefix: options.prefix !== false,
|
|
124
|
+
_ephemeral: true
|
|
125
|
+
});
|
|
126
|
+
}
|
|
94
127
|
getLast() {
|
|
95
128
|
return this._messages[this._messages.length - 1] || null;
|
|
96
129
|
}
|
|
@@ -109,106 +142,667 @@
|
|
|
109
142
|
if (lastAssistantIndex >= 0) return this._messages.splice(lastAssistantIndex + 1);
|
|
110
143
|
return [];
|
|
111
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* 更新消息:按 id 查找并合并 changes,不新增消息
|
|
147
|
+
* @param {string} id — 消息 id
|
|
148
|
+
* @param {Object} changes — 要合并的字段
|
|
149
|
+
* @returns {Object|null} 更新后的消息,未找到返回 null
|
|
150
|
+
*/
|
|
151
|
+
update(id, changes) {
|
|
152
|
+
for (const msg of this._messages) if (msg.id === id) {
|
|
153
|
+
Object.assign(msg, changes);
|
|
154
|
+
msg.timestamp = changes.timestamp || Date.now();
|
|
155
|
+
return msg;
|
|
156
|
+
}
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
112
159
|
};
|
|
113
160
|
//#endregion
|
|
114
|
-
//#region src/core/
|
|
161
|
+
//#region src/core/SystemPromptStore.js
|
|
115
162
|
/**
|
|
116
|
-
*
|
|
117
|
-
*
|
|
163
|
+
* SystemPromptStore — 系统提示词存储
|
|
164
|
+
*
|
|
165
|
+
* 职责:管理多条 system prompt 的增删改查与开关
|
|
166
|
+
* 与 MessageStore 分离,因为 system prompt 是"AI 行为准则",不是对话事件
|
|
167
|
+
*
|
|
168
|
+
* 每条记录格式:
|
|
169
|
+
* {
|
|
170
|
+
* id: string,
|
|
171
|
+
* content: string,
|
|
172
|
+
* enabled: boolean,
|
|
173
|
+
* timestamp: number
|
|
174
|
+
* }
|
|
118
175
|
*/
|
|
176
|
+
var SystemPromptStore = class {
|
|
177
|
+
constructor() {
|
|
178
|
+
this._prompts = [];
|
|
179
|
+
this._idCounter = 0;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* 添加一条 system prompt
|
|
183
|
+
* @param {string} content — 提示词内容
|
|
184
|
+
* @param {boolean} [enabled=true] — 是否启用
|
|
185
|
+
* @returns {Object} 添加的记录
|
|
186
|
+
*/
|
|
187
|
+
add(content, enabled = true) {
|
|
188
|
+
if (!content || typeof content !== "string" || !content.trim()) throw new Error("[SystemPromptStore] content 必须是非空字符串");
|
|
189
|
+
const record = {
|
|
190
|
+
id: `sys_${Date.now()}_${++this._idCounter}`,
|
|
191
|
+
content: content.trim(),
|
|
192
|
+
enabled: Boolean(enabled),
|
|
193
|
+
timestamp: Date.now()
|
|
194
|
+
};
|
|
195
|
+
this._prompts.push(record);
|
|
196
|
+
return record;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* 删除指定索引的 system prompt
|
|
200
|
+
* @param {number} index
|
|
201
|
+
* @returns {Object} 被删除的记录
|
|
202
|
+
*/
|
|
203
|
+
remove(index) {
|
|
204
|
+
if (index < 0 || index >= this._prompts.length) throw new Error(`[SystemPromptStore] 索引越界: ${index}`);
|
|
205
|
+
return this._prompts.splice(index, 1)[0];
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* 切换指定索引的启用/禁用状态
|
|
209
|
+
* @param {number} index
|
|
210
|
+
* @returns {boolean} 切换后的状态
|
|
211
|
+
*/
|
|
212
|
+
toggle(index) {
|
|
213
|
+
if (index < 0 || index >= this._prompts.length) throw new Error(`[SystemPromptStore] 索引越界: ${index}`);
|
|
214
|
+
this._prompts[index].enabled = !this._prompts[index].enabled;
|
|
215
|
+
return this._prompts[index].enabled;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* 更新指定索引的 content
|
|
219
|
+
* @param {number} index
|
|
220
|
+
* @param {string} content
|
|
221
|
+
*/
|
|
222
|
+
update(index, content) {
|
|
223
|
+
if (index < 0 || index >= this._prompts.length) throw new Error(`[SystemPromptStore] 索引越界: ${index}`);
|
|
224
|
+
if (!content || typeof content !== "string" || !content.trim()) throw new Error("[SystemPromptStore] content 必须是非空字符串");
|
|
225
|
+
this._prompts[index].content = content.trim();
|
|
226
|
+
this._prompts[index].timestamp = Date.now();
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* 清空并用一条 content 替换(便捷方法,常用于 config.system = '...')
|
|
230
|
+
* @param {string} content
|
|
231
|
+
*/
|
|
232
|
+
set(content) {
|
|
233
|
+
this._prompts = [];
|
|
234
|
+
if (content && typeof content === "string" && content.trim()) this.add(content, true);
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* 按 enabled 筛选后,转为适配器可用的格式
|
|
238
|
+
* @returns {Array<{role:'system', content:string}>}
|
|
239
|
+
*/
|
|
240
|
+
getEnabled() {
|
|
241
|
+
return this._prompts.filter((p) => p.enabled).map((p) => ({
|
|
242
|
+
role: "system",
|
|
243
|
+
content: p.content
|
|
244
|
+
}));
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* 返回所有记录(含 enabled 状态,用于 UI 展示)
|
|
248
|
+
* @returns {Array}
|
|
249
|
+
*/
|
|
250
|
+
getAll() {
|
|
251
|
+
return [...this._prompts];
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* 清空所有 system prompt
|
|
255
|
+
*/
|
|
256
|
+
clear() {
|
|
257
|
+
this._prompts = [];
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
//#endregion
|
|
261
|
+
//#region src/core/Errors.js
|
|
262
|
+
/**
|
|
263
|
+
* 自定义错误类
|
|
264
|
+
* 用于区分不同类型的错误,方便用户通过 `error.name` 或 `instanceof` 处理
|
|
265
|
+
*/
|
|
266
|
+
var APIError = class extends Error {
|
|
267
|
+
/**
|
|
268
|
+
* @param {string} message - 错误消息(通常来自 API 响应)
|
|
269
|
+
* @param {number} statusCode - HTTP 状态码
|
|
270
|
+
* @param {any} originalError - 原始错误对象或相关信息
|
|
271
|
+
* @param {string} responseText - 原始响应文本(如果有)
|
|
272
|
+
*/
|
|
273
|
+
constructor(message, statusCode, originalError, responseText) {
|
|
274
|
+
super(message);
|
|
275
|
+
this.name = "APIError";
|
|
276
|
+
this.statusCode = statusCode;
|
|
277
|
+
this.originalError = originalError;
|
|
278
|
+
this.responseText = responseText;
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
var NetworkError = class extends Error {
|
|
282
|
+
/**
|
|
283
|
+
* @param {string} message - 错误消息
|
|
284
|
+
* @param {any} originalError - 原始错误对象或相关信息
|
|
285
|
+
*/
|
|
286
|
+
constructor(message, originalError) {
|
|
287
|
+
super(message);
|
|
288
|
+
this.name = "NetworkError";
|
|
289
|
+
this.originalError = originalError;
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
var ConfigurationError = class extends Error {
|
|
293
|
+
/**
|
|
294
|
+
* @param {string} message - 错误消息
|
|
295
|
+
*/
|
|
296
|
+
constructor(message) {
|
|
297
|
+
super(message);
|
|
298
|
+
this.name = "ConfigurationError";
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
var ParsingError = class extends Error {
|
|
302
|
+
/**
|
|
303
|
+
* @param {string} message - 错误消息
|
|
304
|
+
* @param {any} originalError - 原始错误对象或相关信息
|
|
305
|
+
* @param {string} responseText - 原始响应文本(如果有)
|
|
306
|
+
**/
|
|
307
|
+
constructor(message, originalError, responseText) {
|
|
308
|
+
super(message);
|
|
309
|
+
this.name = "ParsingError";
|
|
310
|
+
this.originalError = originalError;
|
|
311
|
+
this.responseText = responseText;
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
//#endregion
|
|
315
|
+
//#region src/core/ChatService.js
|
|
119
316
|
var ChatService = class extends EventEmitter {
|
|
120
317
|
constructor(config = {}) {
|
|
121
318
|
super();
|
|
122
|
-
this.config = {
|
|
123
|
-
apiKey: config.apiKey || "",
|
|
124
|
-
model: config.model || "gpt-3.5-turbo",
|
|
125
|
-
...config
|
|
126
|
-
};
|
|
319
|
+
this.config = { ...config };
|
|
127
320
|
this.messages = new MessageStore();
|
|
128
|
-
this.
|
|
321
|
+
this.systemPrompts = new SystemPromptStore();
|
|
129
322
|
this._adapter = null;
|
|
130
|
-
this.
|
|
323
|
+
this._abortController = null;
|
|
324
|
+
this._isGenerating = false;
|
|
325
|
+
this._hooks = new EventEmitter();
|
|
326
|
+
this._processResponse = null;
|
|
327
|
+
const model = this.config.model || this.config.modelParams?.model;
|
|
328
|
+
if (!model || typeof model !== "string" || !model.trim()) throw new ConfigurationError("缺少 model 配置");
|
|
329
|
+
const temperature = this.config.modelParams?.temperature ?? this.config.temperature;
|
|
330
|
+
if (temperature !== void 0 && (typeof temperature !== "number" || temperature < 0 || temperature > 2)) throw new ConfigurationError(`temperature 必须在 0-2 之间,当前值: ${temperature}`);
|
|
331
|
+
const maxTokens = this.config.modelParams?.maxTokens ?? this.config.maxTokens;
|
|
332
|
+
if (maxTokens !== void 0 && (typeof maxTokens !== "number" || maxTokens < 1 || !Number.isInteger(maxTokens))) throw new ConfigurationError(`maxTokens 必须为正整数,当前值: ${maxTokens}`);
|
|
333
|
+
if (typeof config.system === "string" && config.system.trim()) this.systemPrompts.set(config.system);
|
|
131
334
|
}
|
|
132
|
-
/**
|
|
133
|
-
* 加载插件
|
|
134
|
-
* @param {object} plugin - 必须包含 install 方法
|
|
135
|
-
*/
|
|
136
335
|
use(plugin) {
|
|
137
|
-
|
|
138
|
-
plugin.install(this);
|
|
139
|
-
this._plugins.push(plugin);
|
|
140
|
-
} else throw new Error("插件必须提供 install 方法");
|
|
336
|
+
plugin.install(this);
|
|
141
337
|
return this;
|
|
142
338
|
}
|
|
143
|
-
/**
|
|
144
|
-
* 设置 API 适配器
|
|
145
|
-
*/
|
|
146
339
|
setAdapter(adapter) {
|
|
147
340
|
this._adapter = adapter;
|
|
148
|
-
return this;
|
|
149
341
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
342
|
+
abort() {
|
|
343
|
+
if (this._abortController) this._abortController.abort();
|
|
344
|
+
}
|
|
345
|
+
get isGenerating() {
|
|
346
|
+
return this._isGenerating;
|
|
347
|
+
}
|
|
348
|
+
async continueLast() {
|
|
349
|
+
const target = this._prepareContinue();
|
|
350
|
+
this.messages.update(target.id, { prefix: true });
|
|
351
|
+
try {
|
|
352
|
+
await this._request({
|
|
353
|
+
addUser: false,
|
|
354
|
+
isStream: false,
|
|
355
|
+
mergeToEntry: target.id
|
|
356
|
+
});
|
|
357
|
+
this.messages.update(target.id, {
|
|
358
|
+
_complete: true,
|
|
359
|
+
prefix: void 0,
|
|
360
|
+
_ephemeral: void 0
|
|
361
|
+
});
|
|
362
|
+
return target;
|
|
363
|
+
} catch (err) {
|
|
364
|
+
this.messages.update(target.id, { prefix: void 0 });
|
|
365
|
+
throw err;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
async continueLastStream(onProgress, onDone) {
|
|
369
|
+
const target = this._prepareContinue();
|
|
370
|
+
console.log("[DEBUG] continueLastStream target.id:", target.id);
|
|
371
|
+
this.messages.update(target.id, { prefix: true });
|
|
372
|
+
const baseLen = (target.content || "").length;
|
|
373
|
+
try {
|
|
374
|
+
await this._request({
|
|
375
|
+
addUser: false,
|
|
376
|
+
isStream: true,
|
|
377
|
+
mergeToEntry: target.id,
|
|
378
|
+
onProgress: (chunk) => {
|
|
379
|
+
if (onProgress) onProgress({
|
|
380
|
+
...chunk,
|
|
381
|
+
content: (chunk.content || "").slice(baseLen)
|
|
382
|
+
});
|
|
383
|
+
},
|
|
384
|
+
onDone: (final) => {
|
|
385
|
+
if (onDone) onDone(final);
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
this.messages.update(target.id, {
|
|
389
|
+
_complete: true,
|
|
390
|
+
prefix: void 0,
|
|
391
|
+
_ephemeral: void 0
|
|
392
|
+
});
|
|
393
|
+
return target;
|
|
394
|
+
} catch (err) {
|
|
395
|
+
this.messages.update(target.id, { prefix: void 0 });
|
|
396
|
+
throw err;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
_prepareContinue() {
|
|
400
|
+
const msgs = this.messages.getAll();
|
|
401
|
+
const last = msgs[msgs.length - 1];
|
|
402
|
+
if (last && (last.role === "assistant" || last._ephemeral)) return last;
|
|
403
|
+
throw new Error("最后一条消息不是 assistant,无法续写");
|
|
404
|
+
}
|
|
405
|
+
updateConfig(partial) {
|
|
406
|
+
Object.assign(this.config, partial);
|
|
407
|
+
if (typeof partial.system === "string") this.systemPrompts.set(partial.system);
|
|
408
|
+
this.emit("config-updated", {
|
|
409
|
+
changes: partial,
|
|
410
|
+
timestamp: Date.now()
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
async _request(options) {
|
|
414
|
+
let { userInput, addUser = true, isStream = false, onProgress, onDone, mergeToEntry } = options;
|
|
415
|
+
this.emit("sending", {
|
|
416
|
+
addUser,
|
|
417
|
+
userInput,
|
|
418
|
+
timestamp: Date.now()
|
|
419
|
+
});
|
|
420
|
+
this._addUserMessage(userInput, addUser);
|
|
421
|
+
await this._hooks.emitAsync("beforeRequest", {
|
|
422
|
+
messages: this.messages,
|
|
423
|
+
config: this.config,
|
|
424
|
+
options
|
|
425
|
+
});
|
|
426
|
+
if (this.config.ephemeralContinue && !mergeToEntry) {
|
|
427
|
+
const msgs = this.messages.getAll();
|
|
428
|
+
const last = msgs[msgs.length - 1];
|
|
429
|
+
if (last && last.prefix && (last.role === "assistant" || last._ephemeral)) mergeToEntry = last.id;
|
|
430
|
+
}
|
|
431
|
+
const body = this._adapter.buildRequest(this.messages.getAll(), this.config, this.systemPrompts.getEnabled());
|
|
432
|
+
const retryCfg = this.config.retry || {};
|
|
433
|
+
try {
|
|
434
|
+
return await this._withRetry(body, {
|
|
435
|
+
isStream,
|
|
436
|
+
onProgress,
|
|
437
|
+
onDone,
|
|
438
|
+
mergeToEntry,
|
|
439
|
+
maxRetries: retryCfg.maxRetries ?? 0,
|
|
440
|
+
retryDelay: retryCfg.retryDelay ?? 1e3
|
|
441
|
+
});
|
|
442
|
+
} catch (error) {
|
|
443
|
+
this.emit("error", {
|
|
444
|
+
error,
|
|
445
|
+
timestamp: Date.now()
|
|
446
|
+
});
|
|
447
|
+
throw error;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
_addUserMessage(userInput, addUser) {
|
|
451
|
+
if (addUser && userInput !== void 0) {
|
|
452
|
+
const msg = typeof userInput === "string" ? {
|
|
453
|
+
role: "user",
|
|
454
|
+
content: userInput
|
|
455
|
+
} : {
|
|
456
|
+
role: "user",
|
|
457
|
+
...userInput
|
|
458
|
+
};
|
|
459
|
+
this.messages.add(msg);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
/** retry 循环,占位消息只 push 一次 */
|
|
463
|
+
async _withRetry(body, { isStream, onProgress, onDone, mergeToEntry, maxRetries, retryDelay }) {
|
|
464
|
+
let lastError = null;
|
|
465
|
+
const adapterOptions = { signal: this._abortController?.signal };
|
|
466
|
+
let placeholder = null;
|
|
467
|
+
let base = null;
|
|
468
|
+
if (isStream) {
|
|
469
|
+
if (mergeToEntry) {
|
|
470
|
+
placeholder = this.messages.getAll().find((m) => m.id === mergeToEntry);
|
|
471
|
+
if (placeholder) base = {
|
|
472
|
+
content: placeholder.content || "",
|
|
473
|
+
reasoning: placeholder.reasoningContent || ""
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
if (!placeholder) placeholder = this.messages.add({
|
|
477
|
+
role: "assistant",
|
|
478
|
+
content: "",
|
|
479
|
+
_complete: false
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
483
|
+
if (attempt > 0) {
|
|
484
|
+
this.emit("retry", {
|
|
485
|
+
attempt,
|
|
486
|
+
maxRetries,
|
|
487
|
+
lastError,
|
|
488
|
+
timestamp: Date.now()
|
|
489
|
+
});
|
|
490
|
+
await new Promise((r) => setTimeout(r, retryDelay));
|
|
491
|
+
}
|
|
492
|
+
try {
|
|
493
|
+
this._isGenerating = true;
|
|
494
|
+
if (isStream) return await this._stream(body, adapterOptions, {
|
|
495
|
+
placeholder,
|
|
496
|
+
base,
|
|
497
|
+
mergeToEntry,
|
|
498
|
+
onProgress,
|
|
499
|
+
onDone
|
|
500
|
+
});
|
|
501
|
+
else {
|
|
502
|
+
const resp = await this._adapter.send(body, this.config, adapterOptions);
|
|
503
|
+
let result = this._handleResult(this._adapter.parseResponse(resp), mergeToEntry);
|
|
504
|
+
if (this._processResponse && !mergeToEntry) result = await this._processResponse(result, { isStream: false });
|
|
505
|
+
return result;
|
|
506
|
+
}
|
|
507
|
+
} catch (error) {
|
|
508
|
+
lastError = error;
|
|
509
|
+
if (error.name === "AbortError" || this._abortController?.signal.aborted) {
|
|
510
|
+
this._isGenerating = false;
|
|
511
|
+
throw error;
|
|
512
|
+
}
|
|
513
|
+
if (error instanceof NetworkError && attempt < maxRetries) continue;
|
|
514
|
+
this._isGenerating = false;
|
|
515
|
+
throw error;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
this._isGenerating = false;
|
|
519
|
+
throw lastError;
|
|
520
|
+
}
|
|
521
|
+
/** 流式:ChatService 维护占位,adapter 只管解析 */
|
|
522
|
+
async _stream(body, adapterOptions, { placeholder, base, mergeToEntry, onProgress, onDone }) {
|
|
523
|
+
let finalMsg = null;
|
|
524
|
+
await this._adapter.stream(body, this.config, (snap) => {
|
|
525
|
+
placeholder.content = snap.content || "";
|
|
526
|
+
if (snap.reasoningContent) placeholder.reasoningContent = snap.reasoningContent;
|
|
527
|
+
if (snap.toolCalls) placeholder.toolCalls = [...snap.toolCalls];
|
|
528
|
+
this.emit("stream-progress", snap);
|
|
529
|
+
if (onProgress) onProgress(snap);
|
|
530
|
+
}, async (final) => {
|
|
531
|
+
finalMsg = final;
|
|
532
|
+
if (this._processResponse && !mergeToEntry) finalMsg = await this._processResponse(finalMsg, {
|
|
533
|
+
isStream: true,
|
|
534
|
+
onProgress,
|
|
535
|
+
onDone
|
|
536
|
+
});
|
|
537
|
+
if (mergeToEntry && base) {
|
|
538
|
+
const changes = {
|
|
539
|
+
content: base.content + (final.content || ""),
|
|
540
|
+
_complete: true
|
|
541
|
+
};
|
|
542
|
+
if (final.reasoningContent) changes.reasoningContent = base.reasoning + final.reasoningContent;
|
|
543
|
+
if (final.toolCalls) changes.toolCalls = final.toolCalls;
|
|
544
|
+
this.messages.update(mergeToEntry, changes);
|
|
545
|
+
const updated = this.messages.getAll().find((m) => m.id === mergeToEntry);
|
|
546
|
+
this.emit("message", updated);
|
|
547
|
+
if (onDone) onDone(updated);
|
|
548
|
+
} else {
|
|
549
|
+
placeholder._complete = true;
|
|
550
|
+
this.emit("message", finalMsg);
|
|
551
|
+
if (onDone) onDone(finalMsg);
|
|
552
|
+
}
|
|
553
|
+
}, adapterOptions);
|
|
554
|
+
return mergeToEntry || finalMsg;
|
|
555
|
+
}
|
|
556
|
+
/** 非流式结果落位 */
|
|
557
|
+
_handleResult(assistantMsg, mergeToEntry) {
|
|
558
|
+
if (mergeToEntry) {
|
|
559
|
+
const entry = this.messages.getAll().find((m) => m.id === mergeToEntry);
|
|
560
|
+
if (!entry) {
|
|
561
|
+
this.messages.add(assistantMsg);
|
|
562
|
+
this.emit("message", assistantMsg);
|
|
563
|
+
return assistantMsg;
|
|
564
|
+
}
|
|
565
|
+
const changes = {
|
|
566
|
+
content: (entry.content || "") + (assistantMsg.content || ""),
|
|
567
|
+
_complete: true
|
|
568
|
+
};
|
|
569
|
+
if (assistantMsg.reasoningContent) changes.reasoningContent = (entry.reasoningContent || "") + assistantMsg.reasoningContent;
|
|
570
|
+
if (assistantMsg.toolCalls) changes.toolCalls = assistantMsg.toolCalls;
|
|
571
|
+
this.messages.update(mergeToEntry, changes);
|
|
572
|
+
const updated = this.messages.getAll().find((m) => m.id === mergeToEntry);
|
|
573
|
+
this.emit("message", updated);
|
|
574
|
+
return updated;
|
|
575
|
+
}
|
|
167
576
|
this.messages.add(assistantMsg);
|
|
168
577
|
this.emit("message", assistantMsg);
|
|
169
578
|
return assistantMsg;
|
|
170
579
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
this.
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
this.
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
580
|
+
async send(userInput) {
|
|
581
|
+
this._abortController = new AbortController();
|
|
582
|
+
try {
|
|
583
|
+
return await this._request({
|
|
584
|
+
userInput,
|
|
585
|
+
addUser: true,
|
|
586
|
+
isStream: false
|
|
587
|
+
});
|
|
588
|
+
} finally {
|
|
589
|
+
this._abortController = null;
|
|
590
|
+
this._isGenerating = false;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
async stream(userInput, onProgress, onDone) {
|
|
594
|
+
this._abortController = new AbortController();
|
|
595
|
+
try {
|
|
596
|
+
return await this._request({
|
|
597
|
+
userInput,
|
|
598
|
+
addUser: true,
|
|
599
|
+
isStream: true,
|
|
600
|
+
onProgress,
|
|
601
|
+
onDone
|
|
602
|
+
});
|
|
603
|
+
} finally {
|
|
604
|
+
this._abortController = null;
|
|
605
|
+
this._isGenerating = false;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
async sendExisting() {
|
|
609
|
+
this._abortController = new AbortController();
|
|
610
|
+
try {
|
|
611
|
+
return await this._request({
|
|
612
|
+
addUser: false,
|
|
613
|
+
isStream: false
|
|
614
|
+
});
|
|
615
|
+
} finally {
|
|
616
|
+
this._abortController = null;
|
|
617
|
+
this._isGenerating = false;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
async sendExistingStream(onProgress, onDone) {
|
|
621
|
+
this._abortController = new AbortController();
|
|
622
|
+
try {
|
|
623
|
+
return await this._request({
|
|
624
|
+
addUser: false,
|
|
625
|
+
isStream: true,
|
|
626
|
+
onProgress,
|
|
627
|
+
onDone
|
|
628
|
+
});
|
|
629
|
+
} finally {
|
|
630
|
+
this._abortController = null;
|
|
631
|
+
this._isGenerating = false;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
};
|
|
635
|
+
//#endregion
|
|
636
|
+
//#region src/utils/url.js
|
|
637
|
+
/**
|
|
638
|
+
* URL 工具函数
|
|
639
|
+
*/
|
|
640
|
+
/**
|
|
641
|
+
* 拼接 baseUrl 和 path
|
|
642
|
+
* 注意:path 应以 '/' 开头,否则会替换 baseUrl 的最后一段
|
|
643
|
+
* @param {string} baseUrl - 基础 URL(如 https://api.deepseek.com)
|
|
644
|
+
* @param {string} path - 路径(如 /v1/chat/completions)
|
|
645
|
+
* @returns {string} 完整的 URL
|
|
646
|
+
*/
|
|
647
|
+
function joinUrl(baseUrl, path) {
|
|
648
|
+
return baseUrl.replace(/\/$/, "") + (path.startsWith("/") ? path : "/" + path);
|
|
649
|
+
}
|
|
650
|
+
//#endregion
|
|
651
|
+
//#region src/utils/typeCheck.js
|
|
652
|
+
/**
|
|
653
|
+
* 类型判断工具
|
|
654
|
+
*/
|
|
655
|
+
/**
|
|
656
|
+
* 判断一个值是否为字符串
|
|
657
|
+
* @param {any} value - 要检查的值
|
|
658
|
+
* @returns {boolean} 如果是字符串则返回 true
|
|
659
|
+
*/
|
|
660
|
+
function isString(value) {
|
|
661
|
+
return typeof value === "string";
|
|
662
|
+
}
|
|
663
|
+
//#endregion
|
|
664
|
+
//#region src/utils/MessageFormatter.js
|
|
665
|
+
/**
|
|
666
|
+
* MessageFormatter — 可注册的消息格式转换器
|
|
667
|
+
*
|
|
668
|
+
* 职责:
|
|
669
|
+
* 1. 内置 OpenA I兼容格式的转换逻辑
|
|
670
|
+
* 2. 支持 register(name, fn) 注册自定义格式(如 Anthropic、Gemini 等)
|
|
671
|
+
* 3. 统一处理 capabilities 过滤(reasoning、vision 等)
|
|
672
|
+
* 4. 所有适配器通过此工具获取 API 消息数组,消除重复代码
|
|
673
|
+
*
|
|
674
|
+
* 用法:
|
|
675
|
+
* import { MessageFormatter } from 'my-ai-chat-framework';
|
|
676
|
+
*
|
|
677
|
+
* // 使用内置格式
|
|
678
|
+
* const msgs = MessageFormatter.format({
|
|
679
|
+
* messages, systemPrompts, capabilities, resolveImage
|
|
680
|
+
* }); // 默认 'openai'
|
|
681
|
+
*
|
|
682
|
+
* // 注册自定义格式
|
|
683
|
+
* MessageFormatter.register('anthropic', ({ messages, systemPrompts, capabilities }) => {
|
|
684
|
+
* // 返回 Anthropic 格式的消息数组
|
|
685
|
+
* });
|
|
686
|
+
*/
|
|
687
|
+
var IS_URL = /^https?:\/\//i;
|
|
688
|
+
function defaultResolveImage(imageId) {
|
|
689
|
+
if (IS_URL.test(imageId)) return { url: imageId };
|
|
690
|
+
return null;
|
|
691
|
+
}
|
|
692
|
+
function buildMultimodalContent(textContent, images, resolveImage) {
|
|
693
|
+
const content = [{
|
|
694
|
+
type: "text",
|
|
695
|
+
text: textContent
|
|
696
|
+
}];
|
|
697
|
+
for (const ref of images) {
|
|
698
|
+
const resolved = resolveImage(ref);
|
|
699
|
+
if (!resolved) continue;
|
|
700
|
+
if (resolved.url) content.push({
|
|
701
|
+
type: "image_url",
|
|
702
|
+
image_url: { url: resolved.url }
|
|
202
703
|
});
|
|
704
|
+
else if (resolved.data) {
|
|
705
|
+
const mime = resolved.mimeType || "image/png";
|
|
706
|
+
content.push({
|
|
707
|
+
type: "image_url",
|
|
708
|
+
image_url: { url: `data:${mime};base64,${resolved.data}` }
|
|
709
|
+
});
|
|
710
|
+
}
|
|
203
711
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
712
|
+
return content;
|
|
713
|
+
}
|
|
714
|
+
/** OpenAI 兼容格式 */
|
|
715
|
+
function toOpenAI({ messages, systemPrompts, capabilities, resolveImage }) {
|
|
716
|
+
const rImg = typeof resolveImage === "function" ? resolveImage : defaultResolveImage;
|
|
717
|
+
const result = [];
|
|
718
|
+
for (const sp of systemPrompts) if (sp?.content && isString(sp.content) && sp.content.trim()) result.push({
|
|
719
|
+
role: "system",
|
|
720
|
+
content: sp.content.trim()
|
|
721
|
+
});
|
|
722
|
+
let ephemEnd = messages.length;
|
|
723
|
+
for (let i = messages.length - 1; i >= 0; i--) if (messages[i]._ephemeral) ephemEnd = i;
|
|
724
|
+
else break;
|
|
725
|
+
for (let i = 0; i < messages.length; i++) {
|
|
726
|
+
const msg = messages[i];
|
|
727
|
+
if (msg._ephemeral) {
|
|
728
|
+
if (i < ephemEnd) continue;
|
|
729
|
+
}
|
|
730
|
+
if (msg.role === "system" && !msg._ephemeral) continue;
|
|
731
|
+
if (msg.role === "tool") {
|
|
732
|
+
result.push({
|
|
733
|
+
role: "tool",
|
|
734
|
+
content: msg.content || "",
|
|
735
|
+
tool_call_id: msg.toolCallId
|
|
736
|
+
});
|
|
737
|
+
continue;
|
|
738
|
+
}
|
|
739
|
+
if (msg.role === "assistant" && msg.toolCalls?.length) {
|
|
740
|
+
const entry = {
|
|
741
|
+
role: "assistant",
|
|
742
|
+
content: msg.content || "",
|
|
743
|
+
tool_calls: msg.toolCalls
|
|
744
|
+
};
|
|
745
|
+
if (msg.prefix) entry.prefix = true;
|
|
746
|
+
if (capabilities?.reasoning && msg.reasoningContent) entry.reasoning_content = msg.reasoningContent;
|
|
747
|
+
result.push(entry);
|
|
748
|
+
continue;
|
|
749
|
+
}
|
|
750
|
+
const hasText = msg.content && isString(msg.content) && msg.content.trim();
|
|
751
|
+
const hasImages = msg.images && Array.isArray(msg.images) && msg.images.length > 0;
|
|
752
|
+
if (!hasText && !hasImages) {
|
|
753
|
+
if (msg.role === "assistant" && msg.prefix) {
|
|
754
|
+
const prefixEntry = {
|
|
755
|
+
role: "assistant",
|
|
756
|
+
content: "",
|
|
757
|
+
prefix: true
|
|
758
|
+
};
|
|
759
|
+
if (capabilities?.reasoning && msg.reasoningContent) prefixEntry.reasoning_content = msg.reasoningContent;
|
|
760
|
+
result.push(prefixEntry);
|
|
761
|
+
}
|
|
762
|
+
continue;
|
|
763
|
+
}
|
|
764
|
+
const entry = { role: msg.role };
|
|
765
|
+
if (hasImages) {
|
|
766
|
+
if (!capabilities?.vision) throw new Error("[MessageFormatter] 消息包含图片但模型不支持视觉(capabilities.vision=false)。请切换模型或移除图片。");
|
|
767
|
+
entry.content = buildMultimodalContent(msg.content || "", msg.images, rImg);
|
|
768
|
+
} else entry.content = msg.content;
|
|
769
|
+
if (msg.prefix) entry.prefix = true;
|
|
770
|
+
result.push(entry);
|
|
771
|
+
}
|
|
772
|
+
return result;
|
|
773
|
+
}
|
|
774
|
+
var MessageFormatter = {
|
|
775
|
+
_formats: new Map([["openai", toOpenAI]]),
|
|
776
|
+
register(name, fn) {
|
|
777
|
+
if (!name || typeof name !== "string" || !name.trim()) throw new Error("[MessageFormatter] 格式名称必须是非空字符串");
|
|
778
|
+
if (typeof fn !== "function") throw new Error("[MessageFormatter] 转换函数必须是 function");
|
|
779
|
+
this._formats.set(name.trim(), fn);
|
|
780
|
+
},
|
|
781
|
+
unregister(name) {
|
|
782
|
+
if (name === "openai") throw new Error("[MessageFormatter] 内置格式 \"openai\" 不可移除");
|
|
783
|
+
this._formats.delete(name);
|
|
784
|
+
},
|
|
785
|
+
listFormats() {
|
|
786
|
+
return [...this._formats.keys()];
|
|
787
|
+
},
|
|
788
|
+
format(options = {}) {
|
|
789
|
+
const formatName = options.format || "openai";
|
|
790
|
+
const fn = this._formats.get(formatName);
|
|
791
|
+
if (!fn) throw new Error(`[MessageFormatter] 未知格式 "${formatName}"。可用格式: ${[...this._formats.keys()].join(", ")}`);
|
|
792
|
+
return fn({
|
|
793
|
+
messages: options.messages || [],
|
|
794
|
+
systemPrompts: options.systemPrompts || [],
|
|
795
|
+
capabilities: options.capabilities || {},
|
|
796
|
+
resolveImage: options.resolveImage
|
|
797
|
+
});
|
|
210
798
|
}
|
|
211
799
|
};
|
|
800
|
+
function assembleMessages(options = {}) {
|
|
801
|
+
return MessageFormatter.format({
|
|
802
|
+
...options,
|
|
803
|
+
format: "openai"
|
|
804
|
+
});
|
|
805
|
+
}
|
|
212
806
|
//#endregion
|
|
213
807
|
//#region src/adapters/openai.js
|
|
214
808
|
/**
|
|
@@ -220,40 +814,66 @@
|
|
|
220
814
|
install(chatService) {
|
|
221
815
|
chatService.setAdapter(this);
|
|
222
816
|
},
|
|
223
|
-
buildRequest(messages, config) {
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
817
|
+
buildRequest(messages, config, systemPrompts = []) {
|
|
818
|
+
const model = config.model || config.modelParams?.model;
|
|
819
|
+
if (!model) throw new Error("Missing required config: model (either at top level or in modelParams)");
|
|
820
|
+
const temperature = config.modelParams?.temperature ?? config.temperature ?? .7;
|
|
821
|
+
const maxTokens = config.modelParams?.maxTokens ?? config.maxTokens ?? 2e3;
|
|
822
|
+
const reasoningEffort = config.modelParams?.reasoningEffort ?? config.reasoningEffort;
|
|
823
|
+
const requestBody = {
|
|
824
|
+
model,
|
|
825
|
+
messages: MessageFormatter.format({
|
|
826
|
+
messages,
|
|
827
|
+
systemPrompts,
|
|
828
|
+
capabilities: config.capabilities || {},
|
|
829
|
+
resolveImage: config.resolveImage,
|
|
830
|
+
format: config.messageFormat
|
|
831
|
+
}),
|
|
832
|
+
temperature,
|
|
833
|
+
max_tokens: maxTokens,
|
|
238
834
|
stream: false
|
|
239
835
|
};
|
|
836
|
+
if (config.tools && Array.isArray(config.tools) && config.tools.length > 0) {
|
|
837
|
+
requestBody.tools = config.tools;
|
|
838
|
+
requestBody.tool_choice = "auto";
|
|
839
|
+
}
|
|
840
|
+
if (reasoningEffort && model === "deepseek-reasoner") requestBody.reasoning_effort = reasoningEffort;
|
|
841
|
+
return requestBody;
|
|
842
|
+
},
|
|
843
|
+
_getUrl(config) {
|
|
844
|
+
config = config || {};
|
|
845
|
+
const { apiUrl, baseUrl, path } = config;
|
|
846
|
+
const defaultPath = "/chat/completions";
|
|
847
|
+
if (apiUrl && isString(apiUrl)) return apiUrl;
|
|
848
|
+
if (baseUrl && isString(baseUrl)) return joinUrl(baseUrl, path && isString(path) ? path : defaultPath);
|
|
849
|
+
return "https://api.openai.com/v1/chat/completions";
|
|
240
850
|
},
|
|
241
|
-
async send(requestBody, config) {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
headers
|
|
851
|
+
async send(requestBody, config, options = {}) {
|
|
852
|
+
try {
|
|
853
|
+
const url = this._getUrl(config);
|
|
854
|
+
const headers = {
|
|
245
855
|
"Content-Type": "application/json",
|
|
246
|
-
"Authorization": `Bearer ${config.apiKey}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
856
|
+
"Authorization": `Bearer ${config.apiKey}`,
|
|
857
|
+
...config.headers || {},
|
|
858
|
+
...options.headers || {}
|
|
859
|
+
};
|
|
860
|
+
const response = await fetch(url, {
|
|
861
|
+
method: "POST",
|
|
862
|
+
headers,
|
|
863
|
+
body: JSON.stringify(requestBody),
|
|
864
|
+
signal: options.signal
|
|
865
|
+
});
|
|
866
|
+
if (!response.ok) await this.handleErrorResponse(response);
|
|
867
|
+
return await response.json();
|
|
868
|
+
} catch (error) {
|
|
869
|
+
if (error instanceof APIError) throw error;
|
|
870
|
+
if (error.name === "AbortError") throw error;
|
|
871
|
+
if (error.name === "TypeError" && error.message.includes("fetch")) throw new NetworkError(`Request failed: ${error.message}`, error);
|
|
872
|
+
throw error;
|
|
253
873
|
}
|
|
254
|
-
return await response.json();
|
|
255
874
|
},
|
|
256
875
|
parseResponse(apiResponse) {
|
|
876
|
+
if (!apiResponse || !apiResponse.choices || !apiResponse.choices[0] || !apiResponse.choices[0].message) throw new ParsingError("Invalid API response: missing choices or message", apiResponse, JSON.stringify(apiResponse));
|
|
257
877
|
const msg = apiResponse.choices[0].message;
|
|
258
878
|
const internal = {
|
|
259
879
|
role: msg.role,
|
|
@@ -263,32 +883,43 @@
|
|
|
263
883
|
if (msg.reasoning_content) internal.reasoningContent = msg.reasoning_content;
|
|
264
884
|
return internal;
|
|
265
885
|
},
|
|
266
|
-
async stream(requestBody, config, onProgress, onDone) {
|
|
886
|
+
async stream(requestBody, config, onProgress, onDone, options = {}) {
|
|
267
887
|
const streamBody = {
|
|
268
888
|
...requestBody,
|
|
269
889
|
stream: true
|
|
270
890
|
};
|
|
271
|
-
const
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
},
|
|
278
|
-
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
891
|
+
const url = this._getUrl(config);
|
|
892
|
+
let response;
|
|
893
|
+
const headers = {
|
|
894
|
+
"Content-Type": "application/json",
|
|
895
|
+
"Authorization": `Bearer ${config.apiKey}`,
|
|
896
|
+
"Accept": "text/event-stream",
|
|
897
|
+
...config.headers || {},
|
|
898
|
+
...options.headers || {}
|
|
899
|
+
};
|
|
900
|
+
try {
|
|
901
|
+
response = await fetch(url, {
|
|
902
|
+
method: "POST",
|
|
903
|
+
headers,
|
|
904
|
+
body: JSON.stringify(streamBody),
|
|
905
|
+
signal: options.signal
|
|
906
|
+
});
|
|
907
|
+
} catch (error) {
|
|
908
|
+
if (error.name === "AbortError") throw error;
|
|
909
|
+
if (error.name === "TypeError" && error.message.includes("fetch")) throw new NetworkError(`Stream request failed: ${error.message}`, error);
|
|
910
|
+
throw error;
|
|
283
911
|
}
|
|
912
|
+
if (!response.ok) await this.handleErrorResponse(response);
|
|
284
913
|
const reader = response.body.getReader();
|
|
285
914
|
const decoder = new TextDecoder();
|
|
286
915
|
let buffer = "";
|
|
287
916
|
let accumulated = {
|
|
288
917
|
role: "assistant",
|
|
289
|
-
content: ""
|
|
918
|
+
content: "",
|
|
919
|
+
reasoningContent: ""
|
|
290
920
|
};
|
|
291
921
|
while (true) {
|
|
922
|
+
if (options.signal?.aborted) break;
|
|
292
923
|
const { done, value } = await reader.read();
|
|
293
924
|
if (done) break;
|
|
294
925
|
buffer += decoder.decode(value, { stream: true });
|
|
@@ -296,22 +927,34 @@
|
|
|
296
927
|
buffer = lines.pop();
|
|
297
928
|
for (const line of lines) {
|
|
298
929
|
const dataLine = line.replace(/^data: /, "").trim();
|
|
299
|
-
if (!dataLine
|
|
930
|
+
if (!dataLine) continue;
|
|
931
|
+
if (dataLine === "[DONE]") break;
|
|
300
932
|
try {
|
|
301
933
|
const delta = JSON.parse(dataLine).choices?.[0]?.delta;
|
|
302
934
|
if (!delta) continue;
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
935
|
+
if (delta.content) accumulated.content += delta.content;
|
|
936
|
+
if (delta.tool_calls) {
|
|
937
|
+
if (!accumulated.toolCalls) accumulated.toolCalls = [];
|
|
938
|
+
for (const toolCallDelta of delta.tool_calls) {
|
|
939
|
+
const index = toolCallDelta.index;
|
|
940
|
+
if (!accumulated.toolCalls[index]) accumulated.toolCalls[index] = {
|
|
941
|
+
id: "",
|
|
942
|
+
type: "function",
|
|
943
|
+
function: {
|
|
944
|
+
name: "",
|
|
945
|
+
arguments: ""
|
|
946
|
+
}
|
|
947
|
+
};
|
|
948
|
+
if (toolCallDelta.id) accumulated.toolCalls[index].id = toolCallDelta.id;
|
|
949
|
+
if (toolCallDelta.type) accumulated.toolCalls[index].type = toolCallDelta.type;
|
|
950
|
+
if (toolCallDelta.function?.name) accumulated.toolCalls[index].function.name += toolCallDelta.function.name;
|
|
951
|
+
if (toolCallDelta.function?.arguments) accumulated.toolCalls[index].function.arguments += toolCallDelta.function.arguments;
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
if (delta.reasoning_content) accumulated.reasoningContent += delta.reasoning_content;
|
|
312
955
|
onProgress({ ...accumulated });
|
|
313
956
|
} catch (e) {
|
|
314
|
-
console.warn("
|
|
957
|
+
console.warn("stream parsing failed :", e, dataLine);
|
|
315
958
|
}
|
|
316
959
|
}
|
|
317
960
|
}
|
|
@@ -321,13 +964,32 @@
|
|
|
321
964
|
toolCalls: accumulated.toolCalls,
|
|
322
965
|
reasoningContent: accumulated.reasoningContent
|
|
323
966
|
});
|
|
967
|
+
},
|
|
968
|
+
async handleErrorResponse(response) {
|
|
969
|
+
let errorText = await response.text();
|
|
970
|
+
let errorMessage = `HTTP ${response.status}`;
|
|
971
|
+
try {
|
|
972
|
+
const errorJson = JSON.parse(errorText);
|
|
973
|
+
errorMessage = errorJson.error?.message || errorJson.message || errorText.slice(0, 200);
|
|
974
|
+
} catch (e) {
|
|
975
|
+
errorMessage = errorText.length > 200 ? errorText.slice(0, 200) + "..." : errorText;
|
|
976
|
+
}
|
|
977
|
+
throw new APIError(errorMessage, response.status, null, errorText);
|
|
324
978
|
}
|
|
325
979
|
};
|
|
326
980
|
//#endregion
|
|
327
981
|
//#region src/plugins/tool-calling.js
|
|
328
982
|
/**
|
|
329
983
|
* 工具调用插件
|
|
330
|
-
*
|
|
984
|
+
* 功能:拦截助手消息中的 tool_calls,执行对应的工具,将结果作为 tool 消息加入对话,
|
|
985
|
+
* 然后自动继续对话(通过 sendExisting / sendExistingStream),直到没有新的工具调用。
|
|
986
|
+
*
|
|
987
|
+
* 设计要点:
|
|
988
|
+
* - 支持普通请求和流式请求(通过 isStream 标志区分)
|
|
989
|
+
* - 支持多次工具调用循环(maxIterations 防止无限循环)
|
|
990
|
+
* - 串行执行工具(可后续升级为并行)
|
|
991
|
+
* - 工具执行失败时,仍然返回错误信息给 AI,而不是中断整个流程
|
|
992
|
+
* - 触发 tool-error 事件,方便用户监听工具执行异常
|
|
331
993
|
*/
|
|
332
994
|
var toolCallingPlugin = {
|
|
333
995
|
name: "tool-calling",
|
|
@@ -335,107 +997,313 @@
|
|
|
335
997
|
install(chatService) {
|
|
336
998
|
this.chatService = chatService;
|
|
337
999
|
this._tools = /* @__PURE__ */ new Map();
|
|
338
|
-
chatService.
|
|
1000
|
+
if (!chatService.config.tools) chatService.config.tools = [];
|
|
1001
|
+
/**
|
|
1002
|
+
* 注册工具
|
|
1003
|
+
* @param {string} name - 工具名称(唯一标识)
|
|
1004
|
+
* @param {string} description - 工具描述(告诉 AI 何时调用)
|
|
1005
|
+
* @param {Function} executor - 异步执行函数,接收参数对象,返回结果(字符串或对象)
|
|
1006
|
+
* @param {Object} parameters - JSON Schema 参数定义(可选,默认为空对象)
|
|
1007
|
+
* @returns {ChatService} 返回 chatService 实例,支持链式调用
|
|
1008
|
+
*/
|
|
1009
|
+
chatService.registerTool = (name, description, executor, parameters = {}) => {
|
|
339
1010
|
this._tools.set(name, {
|
|
340
1011
|
executor,
|
|
341
1012
|
description
|
|
342
1013
|
});
|
|
1014
|
+
const toolDefinition = {
|
|
1015
|
+
type: "function",
|
|
1016
|
+
function: {
|
|
1017
|
+
name,
|
|
1018
|
+
description,
|
|
1019
|
+
parameters: {
|
|
1020
|
+
type: "object",
|
|
1021
|
+
properties: parameters,
|
|
1022
|
+
required: Object.keys(parameters).filter((key) => parameters[key]?.required)
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
};
|
|
1026
|
+
if (!chatService.config.tools.find((t) => t.function.name === name)) chatService.config.tools.push(toolDefinition);
|
|
343
1027
|
return chatService;
|
|
344
1028
|
};
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
return this._handleWithTools(originalSend, input, false);
|
|
348
|
-
};
|
|
349
|
-
const originalStream = chatService.stream;
|
|
350
|
-
chatService.stream = async (input, onProgress, onDone) => {
|
|
351
|
-
return this._handleWithTools(originalStream, input, true, onProgress, onDone);
|
|
1029
|
+
chatService._processResponse = async (response, { isStream, onProgress, onDone }) => {
|
|
1030
|
+
return this._handleWithTools(response, isStream, onProgress, onDone);
|
|
352
1031
|
};
|
|
353
1032
|
},
|
|
354
|
-
async _handleWithTools(
|
|
1033
|
+
async _handleWithTools(initialResponse, isStream, onProgress, onDone) {
|
|
355
1034
|
const self = this;
|
|
356
1035
|
const chat = this.chatService;
|
|
357
1036
|
let iteration = 0;
|
|
1037
|
+
/**
|
|
1038
|
+
* 递归处理工具调用
|
|
1039
|
+
* @param {Object} initialResponse - 初始 AI 响应(可能是第一次请求的响应)
|
|
1040
|
+
* @returns {Promise<Object>} 最终 AI 响应(不含 tool_calls)
|
|
1041
|
+
*/
|
|
358
1042
|
async function processResponse(initialResponse) {
|
|
359
1043
|
let lastResponse = initialResponse;
|
|
360
1044
|
while (iteration < self.maxIterations) {
|
|
361
1045
|
const toolCalls = lastResponse?.toolCalls;
|
|
362
1046
|
if (!toolCalls || toolCalls.length === 0) break;
|
|
363
1047
|
iteration++;
|
|
364
|
-
const toolResults =
|
|
365
|
-
for (const call of toolCalls) {
|
|
1048
|
+
const toolResults = await Promise.all(toolCalls.map(async (call) => {
|
|
366
1049
|
const toolName = call.function?.name;
|
|
367
|
-
|
|
1050
|
+
let args;
|
|
1051
|
+
try {
|
|
1052
|
+
args = JSON.parse(call.function?.arguments || "{}");
|
|
1053
|
+
} catch (parseErr) {
|
|
1054
|
+
chat.emit("tool-error", {
|
|
1055
|
+
toolName: toolName || "unknown",
|
|
1056
|
+
error: parseErr,
|
|
1057
|
+
toolCallId: call.id,
|
|
1058
|
+
stage: "parse",
|
|
1059
|
+
timestamp: Date.now()
|
|
1060
|
+
});
|
|
1061
|
+
return {
|
|
1062
|
+
tool_call_id: call.id,
|
|
1063
|
+
error: `参数解析失败: ${parseErr.message}`,
|
|
1064
|
+
success: false
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
368
1067
|
const tool = self._tools.get(toolName);
|
|
369
1068
|
if (!tool) {
|
|
370
|
-
|
|
371
|
-
|
|
1069
|
+
chat.emit("tool-error", {
|
|
1070
|
+
toolName,
|
|
1071
|
+
error: /* @__PURE__ */ new Error(`Tool not registered: ${toolName}`),
|
|
1072
|
+
toolCallId: call.id,
|
|
1073
|
+
stage: "lookup",
|
|
1074
|
+
timestamp: Date.now()
|
|
1075
|
+
});
|
|
1076
|
+
return {
|
|
372
1077
|
tool_call_id: call.id,
|
|
373
1078
|
error: `工具 ${toolName} 未注册`,
|
|
374
1079
|
success: false
|
|
375
|
-
}
|
|
376
|
-
continue;
|
|
1080
|
+
};
|
|
377
1081
|
}
|
|
378
1082
|
try {
|
|
379
|
-
const
|
|
380
|
-
|
|
1083
|
+
const toolTimeout = chat.config.toolTimeout;
|
|
1084
|
+
let execPromise = tool.executor(args);
|
|
1085
|
+
if (toolTimeout && typeof toolTimeout === "number" && toolTimeout > 0) {
|
|
1086
|
+
const timeoutErr = /* @__PURE__ */ new Error(`工具 ${toolName} 执行超时 (${toolTimeout}ms)`);
|
|
1087
|
+
timeoutErr.name = "ToolTimeoutError";
|
|
1088
|
+
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(timeoutErr), toolTimeout));
|
|
1089
|
+
execPromise = Promise.race([execPromise, timeoutPromise]);
|
|
1090
|
+
}
|
|
1091
|
+
const result = await execPromise;
|
|
1092
|
+
chat.emit("tool-success", {
|
|
1093
|
+
toolName,
|
|
1094
|
+
result,
|
|
1095
|
+
toolCallId: call.id,
|
|
1096
|
+
timestamp: Date.now()
|
|
1097
|
+
});
|
|
1098
|
+
return {
|
|
381
1099
|
tool_call_id: call.id,
|
|
382
1100
|
content: typeof result === "string" ? result : JSON.stringify(result),
|
|
383
1101
|
success: true
|
|
384
|
-
}
|
|
1102
|
+
};
|
|
385
1103
|
} catch (err) {
|
|
386
|
-
|
|
387
|
-
|
|
1104
|
+
const stage = err.name === "ToolTimeoutError" ? "timeout" : "execute";
|
|
1105
|
+
chat.emit("tool-error", {
|
|
1106
|
+
toolName,
|
|
1107
|
+
error: err,
|
|
1108
|
+
toolCallId: call.id,
|
|
1109
|
+
stage,
|
|
1110
|
+
timestamp: Date.now()
|
|
1111
|
+
});
|
|
1112
|
+
return {
|
|
388
1113
|
tool_call_id: call.id,
|
|
389
1114
|
error: err.message,
|
|
390
1115
|
success: false
|
|
391
|
-
}
|
|
1116
|
+
};
|
|
392
1117
|
}
|
|
1118
|
+
}));
|
|
1119
|
+
for (const tr of toolResults) {
|
|
1120
|
+
const content = tr.success ? tr.content : tr.error || "工具执行失败";
|
|
1121
|
+
chat.messages.addTool(content, tr.tool_call_id);
|
|
393
1122
|
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
}, (final) => {
|
|
402
|
-
lastResponse = final;
|
|
403
|
-
resolve();
|
|
404
|
-
});
|
|
1123
|
+
if (isStream) await new Promise((resolve) => {
|
|
1124
|
+
chat.sendExistingStream((chunk) => {
|
|
1125
|
+
if (onProgress) onProgress(chunk);
|
|
1126
|
+
lastResponse = chunk;
|
|
1127
|
+
}, (final) => {
|
|
1128
|
+
lastResponse = final;
|
|
1129
|
+
resolve();
|
|
405
1130
|
});
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
const apiResp = await chat._adapter.send(reqBody, chat.config);
|
|
409
|
-
lastResponse = chat._adapter.parseResponse(apiResp);
|
|
410
|
-
}
|
|
1131
|
+
});
|
|
1132
|
+
else lastResponse = await chat.sendExisting();
|
|
411
1133
|
}
|
|
412
1134
|
return lastResponse;
|
|
413
1135
|
}
|
|
414
|
-
|
|
415
|
-
let finalMessage;
|
|
416
|
-
await originalMethod.call(chat, input, (chunk) => {
|
|
417
|
-
if (onProgress) onProgress(chunk);
|
|
418
|
-
}, async (final) => {
|
|
419
|
-
finalMessage = await processResponse(final);
|
|
420
|
-
if (onDone) onDone(finalMessage);
|
|
421
|
-
});
|
|
422
|
-
} else return await processResponse(await originalMethod.call(chat, input));
|
|
1136
|
+
return await processResponse(initialResponse);
|
|
423
1137
|
}
|
|
424
1138
|
};
|
|
425
1139
|
//#endregion
|
|
426
|
-
//#region src/
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
1140
|
+
//#region src/plugins/model-registry.js
|
|
1141
|
+
/**
|
|
1142
|
+
* model-registry 插件
|
|
1143
|
+
*
|
|
1144
|
+
* 职责:
|
|
1145
|
+
* 1. 内置常用模型的能力标签表(model → capabilities 映射)
|
|
1146
|
+
* 2. 安装时自动根据 config.model 查表,写入 config.capabilities
|
|
1147
|
+
* 3. 监听 config-updated 事件,切换模型时自动同步能力
|
|
1148
|
+
* 4. 提供 chat.registerModel(),允许用户追加/覆盖自定义模型
|
|
1149
|
+
*
|
|
1150
|
+
* 能力标签字段(全部可选,缺失视为 false / 未知):
|
|
1151
|
+
* {
|
|
1152
|
+
* reasoning: boolean — 是否返回 reasoning_content(思维链)
|
|
1153
|
+
* vision: boolean — 是否支持图片输入
|
|
1154
|
+
* toolCalling: boolean — 是否支持 function calling
|
|
1155
|
+
* streaming: boolean — 是否支持流式传输
|
|
1156
|
+
* maxInputTokens: number — 最大输入 token 数
|
|
1157
|
+
* maxOutputTokens: number — 最大输出 token 数
|
|
1158
|
+
* }
|
|
1159
|
+
*
|
|
1160
|
+
* 使用:
|
|
1161
|
+
* chat.use(modelRegistryPlugin);
|
|
1162
|
+
* // config.capabilities 现在已自动填充
|
|
1163
|
+
* chat.registerModel('my-custom-model', { toolCalling: true });
|
|
1164
|
+
*/
|
|
1165
|
+
var BUILTIN_MODELS = {
|
|
1166
|
+
"deepseek-chat": {
|
|
1167
|
+
reasoning: false,
|
|
1168
|
+
vision: false,
|
|
1169
|
+
toolCalling: true,
|
|
1170
|
+
streaming: true,
|
|
1171
|
+
maxInputTokens: 128e3,
|
|
1172
|
+
maxOutputTokens: 8192
|
|
1173
|
+
},
|
|
1174
|
+
"deepseek-reasoner": {
|
|
1175
|
+
reasoning: true,
|
|
1176
|
+
vision: false,
|
|
1177
|
+
toolCalling: true,
|
|
1178
|
+
streaming: true,
|
|
1179
|
+
maxInputTokens: 128e3,
|
|
1180
|
+
maxOutputTokens: 8192
|
|
1181
|
+
},
|
|
1182
|
+
"gpt-4o": {
|
|
1183
|
+
reasoning: false,
|
|
1184
|
+
vision: true,
|
|
1185
|
+
toolCalling: true,
|
|
1186
|
+
streaming: true,
|
|
1187
|
+
maxInputTokens: 128e3,
|
|
1188
|
+
maxOutputTokens: 16384
|
|
1189
|
+
},
|
|
1190
|
+
"gpt-4o-mini": {
|
|
1191
|
+
reasoning: false,
|
|
1192
|
+
vision: true,
|
|
1193
|
+
toolCalling: true,
|
|
1194
|
+
streaming: true,
|
|
1195
|
+
maxInputTokens: 128e3,
|
|
1196
|
+
maxOutputTokens: 16384
|
|
1197
|
+
},
|
|
1198
|
+
"gpt-3.5-turbo": {
|
|
1199
|
+
reasoning: false,
|
|
1200
|
+
vision: false,
|
|
1201
|
+
toolCalling: true,
|
|
1202
|
+
streaming: true,
|
|
1203
|
+
maxInputTokens: 16385,
|
|
1204
|
+
maxOutputTokens: 4096
|
|
1205
|
+
},
|
|
1206
|
+
"o1": {
|
|
1207
|
+
reasoning: true,
|
|
1208
|
+
vision: false,
|
|
1209
|
+
toolCalling: false,
|
|
1210
|
+
streaming: false,
|
|
1211
|
+
maxInputTokens: 2e5,
|
|
1212
|
+
maxOutputTokens: 1e5
|
|
1213
|
+
},
|
|
1214
|
+
"o3-mini": {
|
|
1215
|
+
reasoning: true,
|
|
1216
|
+
vision: false,
|
|
1217
|
+
toolCalling: true,
|
|
1218
|
+
streaming: true,
|
|
1219
|
+
maxInputTokens: 2e5,
|
|
1220
|
+
maxOutputTokens: 1e5
|
|
1221
|
+
},
|
|
1222
|
+
"claude-3.5-sonnet": {
|
|
1223
|
+
reasoning: false,
|
|
1224
|
+
vision: true,
|
|
1225
|
+
toolCalling: true,
|
|
1226
|
+
streaming: true,
|
|
1227
|
+
maxInputTokens: 2e5,
|
|
1228
|
+
maxOutputTokens: 8192
|
|
1229
|
+
},
|
|
1230
|
+
"claude-3.5-haiku": {
|
|
1231
|
+
reasoning: false,
|
|
1232
|
+
vision: true,
|
|
1233
|
+
toolCalling: true,
|
|
1234
|
+
streaming: true,
|
|
1235
|
+
maxInputTokens: 2e5,
|
|
1236
|
+
maxOutputTokens: 8192
|
|
1237
|
+
}
|
|
1238
|
+
};
|
|
1239
|
+
var DEFAULT_CAPABILITIES = {
|
|
1240
|
+
streaming: true,
|
|
1241
|
+
toolCalling: false,
|
|
1242
|
+
reasoning: false,
|
|
1243
|
+
vision: false
|
|
1244
|
+
};
|
|
1245
|
+
var modelRegistryPlugin = {
|
|
1246
|
+
name: "model-registry",
|
|
1247
|
+
install(chatService) {
|
|
1248
|
+
this.chat = chatService;
|
|
1249
|
+
this._registry = new Map(Object.entries(BUILTIN_MODELS));
|
|
1250
|
+
/**
|
|
1251
|
+
* 注册/覆盖一个模型的能力标签
|
|
1252
|
+
* @param {string} name — 模型名称
|
|
1253
|
+
* @param {Object} capabilities — 能力标签对象(部分字段即可,未提供的取默认值)
|
|
1254
|
+
* @returns {ChatService}
|
|
1255
|
+
*/
|
|
1256
|
+
chatService.registerModel = (name, capabilities = {}) => {
|
|
1257
|
+
if (!name || typeof name !== "string" || !name.trim()) throw new Error("[model-registry] 模型名称必须是非空字符串");
|
|
1258
|
+
const merged = {
|
|
1259
|
+
...DEFAULT_CAPABILITIES,
|
|
1260
|
+
...capabilities
|
|
1261
|
+
};
|
|
1262
|
+
this._registry.set(name.trim(), merged);
|
|
1263
|
+
if (chatService.config.model === name.trim()) this._syncCapabilities();
|
|
1264
|
+
return chatService;
|
|
1265
|
+
};
|
|
1266
|
+
/**
|
|
1267
|
+
* 列出所有已注册的模型名称
|
|
1268
|
+
* @returns {Array<string>}
|
|
1269
|
+
*/
|
|
1270
|
+
chatService.listModels = () => {
|
|
1271
|
+
return [...this._registry.keys()];
|
|
1272
|
+
};
|
|
1273
|
+
this._syncCapabilities();
|
|
1274
|
+
chatService.on("config-updated", ({ changes }) => {
|
|
1275
|
+
if ("model" in changes) this._syncCapabilities();
|
|
1276
|
+
if ("capabilities" in changes && !("model" in changes)) {
|
|
1277
|
+
const current = this._lookupCapabilities();
|
|
1278
|
+
if (current) chatService.config.capabilities = {
|
|
1279
|
+
...current,
|
|
1280
|
+
...changes.capabilities
|
|
1281
|
+
};
|
|
1282
|
+
}
|
|
1283
|
+
});
|
|
1284
|
+
},
|
|
1285
|
+
_lookupCapabilities() {
|
|
1286
|
+
const model = this.chat.config.model;
|
|
1287
|
+
if (!model) return null;
|
|
1288
|
+
return this._registry.get(model) || null;
|
|
1289
|
+
},
|
|
1290
|
+
_syncCapabilities() {
|
|
1291
|
+
const caps = this._lookupCapabilities();
|
|
1292
|
+
if (caps) this.chat.config.capabilities = { ...caps };
|
|
1293
|
+
}
|
|
433
1294
|
};
|
|
434
1295
|
//#endregion
|
|
1296
|
+
exports.APIError = APIError;
|
|
435
1297
|
exports.ChatService = ChatService;
|
|
1298
|
+
exports.ConfigurationError = ConfigurationError;
|
|
436
1299
|
exports.EventEmitter = EventEmitter;
|
|
1300
|
+
exports.MessageFormatter = MessageFormatter;
|
|
437
1301
|
exports.MessageStore = MessageStore;
|
|
438
|
-
exports.
|
|
1302
|
+
exports.NetworkError = NetworkError;
|
|
1303
|
+
exports.ParsingError = ParsingError;
|
|
1304
|
+
exports.SystemPromptStore = SystemPromptStore;
|
|
1305
|
+
exports.assembleMessages = assembleMessages;
|
|
1306
|
+
exports.modelRegistryPlugin = modelRegistryPlugin;
|
|
439
1307
|
exports.openaiAdapter = openaiAdapter;
|
|
440
1308
|
exports.toolCallingPlugin = toolCallingPlugin;
|
|
441
1309
|
});
|