my-ai-chat-framework 3.0.0 → 4.0.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/CHANGELOG.md +52 -0
- package/README.md +3 -3
- package/README_ZH.md +22 -5
- package/dist/my-ai-chat-framework.browser.es.js +599 -292
- package/dist/my-ai-chat-framework.browser.es.js.map +1 -1
- package/dist/my-ai-chat-framework.browser.umd.js +601 -291
- package/dist/my-ai-chat-framework.browser.umd.js.map +1 -1
- package/dist/my-ai-chat-framework.node.cjs.js +601 -291
- package/dist/my-ai-chat-framework.node.cjs.js.map +1 -1
- package/docs/DEVELOPER.md +100 -10
- package/docs/README.md +33 -0
- package/package.json +1 -1
- package/src/adapters/openai.js +44 -18
- package/src/core/ChatService.js +250 -141
- package/src/core/Errors.js +13 -0
- package/src/core/SystemPromptStore.js +7 -5
- package/src/index.js +2 -1
- package/src/plugins/continuation.js +156 -0
- package/src/plugins/model-registry.js +16 -3
- package/src/plugins/tool-calling.js +46 -18
- package/src/utils/MessageFormatter.js +138 -46
|
@@ -177,6 +177,69 @@
|
|
|
177
177
|
}
|
|
178
178
|
};
|
|
179
179
|
//#endregion
|
|
180
|
+
//#region src/core/Errors.js
|
|
181
|
+
/**
|
|
182
|
+
* 自定义错误类
|
|
183
|
+
* 用于区分不同类型的错误,方便用户通过 `error.name` 或 `instanceof` 处理
|
|
184
|
+
*/
|
|
185
|
+
var APIError = class extends Error {
|
|
186
|
+
/**
|
|
187
|
+
* @param {string} message - 错误消息(通常来自 API 响应)
|
|
188
|
+
* @param {number} statusCode - HTTP 状态码
|
|
189
|
+
* @param {any} originalError - 原始错误对象或相关信息
|
|
190
|
+
* @param {string} responseText - 原始响应文本(如果有)
|
|
191
|
+
*/
|
|
192
|
+
constructor(message, statusCode, originalError, responseText) {
|
|
193
|
+
super(message);
|
|
194
|
+
this.name = "APIError";
|
|
195
|
+
this.statusCode = statusCode;
|
|
196
|
+
this.originalError = originalError;
|
|
197
|
+
this.responseText = responseText;
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
var NetworkError = class extends Error {
|
|
201
|
+
/**
|
|
202
|
+
* @param {string} message - 错误消息
|
|
203
|
+
* @param {any} originalError - 原始错误对象或相关信息
|
|
204
|
+
*/
|
|
205
|
+
constructor(message, originalError) {
|
|
206
|
+
super(message);
|
|
207
|
+
this.name = "NetworkError";
|
|
208
|
+
this.originalError = originalError;
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
var ConfigurationError = class extends Error {
|
|
212
|
+
/**
|
|
213
|
+
* @param {string} message - 错误消息
|
|
214
|
+
*/
|
|
215
|
+
constructor(message) {
|
|
216
|
+
super(message);
|
|
217
|
+
this.name = "ConfigurationError";
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
var ValidationError = class extends ConfigurationError {
|
|
221
|
+
/**
|
|
222
|
+
* @param {string} message - 错误消息
|
|
223
|
+
*/
|
|
224
|
+
constructor(message) {
|
|
225
|
+
super(message);
|
|
226
|
+
this.name = "ValidationError";
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
var ParsingError = class extends Error {
|
|
230
|
+
/**
|
|
231
|
+
* @param {string} message - 错误消息
|
|
232
|
+
* @param {any} originalError - 原始错误对象或相关信息
|
|
233
|
+
* @param {string} responseText - 原始响应文本(如果有)
|
|
234
|
+
**/
|
|
235
|
+
constructor(message, originalError, responseText) {
|
|
236
|
+
super(message);
|
|
237
|
+
this.name = "ParsingError";
|
|
238
|
+
this.originalError = originalError;
|
|
239
|
+
this.responseText = responseText;
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
//#endregion
|
|
180
243
|
//#region src/core/SystemPromptStore.js
|
|
181
244
|
/**
|
|
182
245
|
* SystemPromptStore — 系统提示词存储
|
|
@@ -204,7 +267,7 @@
|
|
|
204
267
|
* @returns {Object} 添加的记录
|
|
205
268
|
*/
|
|
206
269
|
add(content, enabled = true) {
|
|
207
|
-
if (!content || typeof content !== "string" || !content.trim()) throw new
|
|
270
|
+
if (!content || typeof content !== "string" || !content.trim()) throw new ValidationError("[SystemPromptStore] content 必须是非空字符串");
|
|
208
271
|
const record = {
|
|
209
272
|
id: `sys_${Date.now()}_${++this._idCounter}`,
|
|
210
273
|
content: content.trim(),
|
|
@@ -220,7 +283,7 @@
|
|
|
220
283
|
* @returns {Object} 被删除的记录
|
|
221
284
|
*/
|
|
222
285
|
remove(index) {
|
|
223
|
-
if (index < 0 || index >= this._prompts.length) throw new
|
|
286
|
+
if (index < 0 || index >= this._prompts.length) throw new ValidationError(`[SystemPromptStore] 索引越界: ${index}`);
|
|
224
287
|
return this._prompts.splice(index, 1)[0];
|
|
225
288
|
}
|
|
226
289
|
/**
|
|
@@ -229,7 +292,7 @@
|
|
|
229
292
|
* @returns {boolean} 切换后的状态
|
|
230
293
|
*/
|
|
231
294
|
toggle(index) {
|
|
232
|
-
if (index < 0 || index >= this._prompts.length) throw new
|
|
295
|
+
if (index < 0 || index >= this._prompts.length) throw new ValidationError(`[SystemPromptStore] 索引越界: ${index}`);
|
|
233
296
|
this._prompts[index].enabled = !this._prompts[index].enabled;
|
|
234
297
|
return this._prompts[index].enabled;
|
|
235
298
|
}
|
|
@@ -239,8 +302,8 @@
|
|
|
239
302
|
* @param {string} content
|
|
240
303
|
*/
|
|
241
304
|
update(index, content) {
|
|
242
|
-
if (index < 0 || index >= this._prompts.length) throw new
|
|
243
|
-
if (!content || typeof content !== "string" || !content.trim()) throw new
|
|
305
|
+
if (index < 0 || index >= this._prompts.length) throw new ValidationError(`[SystemPromptStore] 索引越界: ${index}`);
|
|
306
|
+
if (!content || typeof content !== "string" || !content.trim()) throw new ValidationError("[SystemPromptStore] content 必须是非空字符串");
|
|
244
307
|
this._prompts[index].content = content.trim();
|
|
245
308
|
this._prompts[index].timestamp = Date.now();
|
|
246
309
|
}
|
|
@@ -277,60 +340,6 @@
|
|
|
277
340
|
}
|
|
278
341
|
};
|
|
279
342
|
//#endregion
|
|
280
|
-
//#region src/core/Errors.js
|
|
281
|
-
/**
|
|
282
|
-
* 自定义错误类
|
|
283
|
-
* 用于区分不同类型的错误,方便用户通过 `error.name` 或 `instanceof` 处理
|
|
284
|
-
*/
|
|
285
|
-
var APIError = class extends Error {
|
|
286
|
-
/**
|
|
287
|
-
* @param {string} message - 错误消息(通常来自 API 响应)
|
|
288
|
-
* @param {number} statusCode - HTTP 状态码
|
|
289
|
-
* @param {any} originalError - 原始错误对象或相关信息
|
|
290
|
-
* @param {string} responseText - 原始响应文本(如果有)
|
|
291
|
-
*/
|
|
292
|
-
constructor(message, statusCode, originalError, responseText) {
|
|
293
|
-
super(message);
|
|
294
|
-
this.name = "APIError";
|
|
295
|
-
this.statusCode = statusCode;
|
|
296
|
-
this.originalError = originalError;
|
|
297
|
-
this.responseText = responseText;
|
|
298
|
-
}
|
|
299
|
-
};
|
|
300
|
-
var NetworkError = class extends Error {
|
|
301
|
-
/**
|
|
302
|
-
* @param {string} message - 错误消息
|
|
303
|
-
* @param {any} originalError - 原始错误对象或相关信息
|
|
304
|
-
*/
|
|
305
|
-
constructor(message, originalError) {
|
|
306
|
-
super(message);
|
|
307
|
-
this.name = "NetworkError";
|
|
308
|
-
this.originalError = originalError;
|
|
309
|
-
}
|
|
310
|
-
};
|
|
311
|
-
var ConfigurationError = class extends Error {
|
|
312
|
-
/**
|
|
313
|
-
* @param {string} message - 错误消息
|
|
314
|
-
*/
|
|
315
|
-
constructor(message) {
|
|
316
|
-
super(message);
|
|
317
|
-
this.name = "ConfigurationError";
|
|
318
|
-
}
|
|
319
|
-
};
|
|
320
|
-
var ParsingError = class extends Error {
|
|
321
|
-
/**
|
|
322
|
-
* @param {string} message - 错误消息
|
|
323
|
-
* @param {any} originalError - 原始错误对象或相关信息
|
|
324
|
-
* @param {string} responseText - 原始响应文本(如果有)
|
|
325
|
-
**/
|
|
326
|
-
constructor(message, originalError, responseText) {
|
|
327
|
-
super(message);
|
|
328
|
-
this.name = "ParsingError";
|
|
329
|
-
this.originalError = originalError;
|
|
330
|
-
this.responseText = responseText;
|
|
331
|
-
}
|
|
332
|
-
};
|
|
333
|
-
//#endregion
|
|
334
343
|
//#region src/core/Pipeline.js
|
|
335
344
|
/**
|
|
336
345
|
* Pipeline —— 极简顺序管道(v3.0 提案 · 阶段 1)
|
|
@@ -407,26 +416,37 @@
|
|
|
407
416
|
"maxTokens",
|
|
408
417
|
"modelParams",
|
|
409
418
|
"system",
|
|
410
|
-
"retry"
|
|
411
|
-
|
|
419
|
+
"retry"
|
|
420
|
+
];
|
|
421
|
+
var INTERNAL_STAGES = [
|
|
422
|
+
"prepareInput",
|
|
423
|
+
"beforeSend",
|
|
424
|
+
"autoContinue",
|
|
425
|
+
"buildRequest",
|
|
426
|
+
"send"
|
|
412
427
|
];
|
|
413
428
|
var ChatService = class extends EventEmitter {
|
|
414
429
|
constructor(config = {}) {
|
|
415
430
|
super();
|
|
416
431
|
this.config = { ...config };
|
|
417
|
-
|
|
432
|
+
if (config.store) {
|
|
433
|
+
if (typeof config.store.getAll !== "function" || typeof config.store.add !== "function") throw new ValidationError("store 必须实现 MessageStore 的接口(至少 getAll / add / update)");
|
|
434
|
+
this.messages = config.store;
|
|
435
|
+
} else this.messages = new MessageStore();
|
|
418
436
|
this.systemPrompts = new SystemPromptStore();
|
|
419
437
|
this._adapter = null;
|
|
420
|
-
this.
|
|
421
|
-
this.
|
|
422
|
-
this._hooks = new EventEmitter();
|
|
423
|
-
this._processResponse = null;
|
|
438
|
+
this._activeRequests = /* @__PURE__ */ new Map();
|
|
439
|
+
this._requestSeq = 0;
|
|
424
440
|
const model = this.config.model || this.config.modelParams?.model;
|
|
425
441
|
if (!model || typeof model !== "string" || !model.trim()) throw new ConfigurationError("缺少 model 配置");
|
|
426
442
|
this._assertValidConfig(this.config);
|
|
427
443
|
if (typeof config.system === "string" && config.system.trim()) this.systemPrompts.set(config.system);
|
|
428
444
|
if (config.adapter) this.setAdapter(config.adapter);
|
|
429
445
|
this._userStages = [];
|
|
446
|
+
this._stageOverrides = /* @__PURE__ */ new Map();
|
|
447
|
+
this._disabledStages = /* @__PURE__ */ new Set();
|
|
448
|
+
this._extraConfigKeys = /* @__PURE__ */ new Map();
|
|
449
|
+
this.plugins = {};
|
|
430
450
|
this._pipeline = this._buildPipeline();
|
|
431
451
|
}
|
|
432
452
|
/**
|
|
@@ -441,8 +461,16 @@
|
|
|
441
461
|
const model = cfg.model ?? cfg.modelParams?.model;
|
|
442
462
|
if (model !== void 0 && (typeof model !== "string" || !model.trim())) throw new ConfigurationError("model 必须是非空字符串");
|
|
443
463
|
}
|
|
464
|
+
/**
|
|
465
|
+
* 安装插件。
|
|
466
|
+
* - 插件实现 `install(chat, options)`
|
|
467
|
+
* - 若 install 返回一个对象,会挂到 **`chat.plugins[plugin.name]`**(v4.0 命名空间,插件之间不会撞名)
|
|
468
|
+
* - 插件要提供卸载时,自己在返回值里带 `uninstall()`(或调用 `plugin.uninstall(chat)`)
|
|
469
|
+
* @returns {ChatService} this
|
|
470
|
+
*/
|
|
444
471
|
use(plugin, options = {}) {
|
|
445
|
-
plugin.install(this, options);
|
|
472
|
+
const api = plugin.install(this, options);
|
|
473
|
+
if (plugin.name && api && typeof api === "object") this.plugins[plugin.name] = api;
|
|
446
474
|
return this;
|
|
447
475
|
}
|
|
448
476
|
/**
|
|
@@ -457,13 +485,7 @@
|
|
|
457
485
|
pipe(stage) {
|
|
458
486
|
if (!stage || typeof stage !== "object" || typeof stage.run !== "function") throw new ConfigurationError("pipe: 需要 { name, phase?, run(ctx) },run 必须是函数");
|
|
459
487
|
if (typeof stage.name !== "string" || !stage.name.trim()) throw new ConfigurationError("pipe: 需要 name(车间名,非空字符串)");
|
|
460
|
-
if (
|
|
461
|
-
"prepareInput",
|
|
462
|
-
"beforeSend",
|
|
463
|
-
"autoContinue",
|
|
464
|
-
"buildRequest",
|
|
465
|
-
"send"
|
|
466
|
-
].includes(stage.name)) throw new ConfigurationError(`pipe: "${stage.name}" 是内部车间名,请换一个名字`);
|
|
488
|
+
if (INTERNAL_STAGES.includes(stage.name)) throw new ConfigurationError(`pipe: "${stage.name}" 是内部车间名,请换一个名字`);
|
|
467
489
|
if (this._userStages.some((s) => s.name === stage.name)) throw new ConfigurationError(`pipe: 车间 "${stage.name}" 已存在,请先 chat.unpipe("${stage.name}")`);
|
|
468
490
|
const phase = stage.phase === "afterSend" ? "afterSend" : "beforeSend";
|
|
469
491
|
this._userStages.push({
|
|
@@ -485,37 +507,86 @@
|
|
|
485
507
|
get pipelineStages() {
|
|
486
508
|
return this._pipeline.names();
|
|
487
509
|
}
|
|
488
|
-
/**
|
|
510
|
+
/** 内部步骤名(顺序即执行顺序):prepareInput / beforeSend / autoContinue / buildRequest / send */
|
|
511
|
+
get internalStages() {
|
|
512
|
+
return [...INTERNAL_STAGES];
|
|
513
|
+
}
|
|
514
|
+
/** 内部步骤的默认实现表 */
|
|
515
|
+
_defaultStageRuns() {
|
|
516
|
+
return {
|
|
517
|
+
prepareInput: (ctx) => this._stagePrepareInput(ctx),
|
|
518
|
+
beforeSend: (ctx) => this._stageBeforeSend(ctx),
|
|
519
|
+
autoContinue: (ctx) => this._stageAutoContinue(ctx),
|
|
520
|
+
buildRequest: (ctx) => this._stageBuildRequest(ctx),
|
|
521
|
+
send: (ctx) => this._stageSend(ctx)
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* 用你自己的实现**顶替**某个内部步骤。框架不再执行该步的默认行为。
|
|
526
|
+
* 例:`chat.replaceStage('send', async (ctx) => { ctx.result = await 我的发送(ctx.body) })`
|
|
527
|
+
* 想"包一层"默认实现:先 `const base = chat.getStage('send')`,再在自己的函数里调用它。
|
|
528
|
+
* @param {string} name 步骤名(见 internalStages)
|
|
529
|
+
* @param {Function} run (ctx) => void | Promise<void>
|
|
530
|
+
* @throws {ConfigurationError} 步骤名未知 / run 不是函数
|
|
531
|
+
*/
|
|
532
|
+
replaceStage(name, run) {
|
|
533
|
+
if (!INTERNAL_STAGES.includes(name)) throw new ConfigurationError(`replaceStage: 未知的内部步骤 "${name}"。可选: ${INTERNAL_STAGES.join(", ")}`);
|
|
534
|
+
if (typeof run !== "function") throw new ConfigurationError("replaceStage: 第二个参数必须是函数 (ctx) => void | Promise<void>");
|
|
535
|
+
this._stageOverrides.set(name, run);
|
|
536
|
+
this._rebuildPipeline();
|
|
537
|
+
return this;
|
|
538
|
+
}
|
|
539
|
+
/** 撤销替换,恢复该步骤的默认实现(不影响 unstage 的关闭状态) */
|
|
540
|
+
restoreStage(name) {
|
|
541
|
+
if (!INTERNAL_STAGES.includes(name)) throw new ConfigurationError(`restoreStage: 未知的内部步骤 "${name}"。可选: ${INTERNAL_STAGES.join(", ")}`);
|
|
542
|
+
this._stageOverrides.delete(name);
|
|
543
|
+
this._rebuildPipeline();
|
|
544
|
+
return this;
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* **关闭**某个内部步骤(从管道里移除,不再执行)。
|
|
548
|
+
* 例:`chat.unstage('autoContinue')` —— 彻底不要"自动续写检测"这一步。
|
|
549
|
+
* 警告:关闭 prepareInput / buildRequest / send 会让流程失去必要产物(如 ctx.body)。
|
|
550
|
+
*/
|
|
551
|
+
unstage(name) {
|
|
552
|
+
if (!INTERNAL_STAGES.includes(name)) throw new ConfigurationError(`unstage: 未知的内部步骤 "${name}"。可选: ${INTERNAL_STAGES.join(", ")}`);
|
|
553
|
+
this._disabledStages.add(name);
|
|
554
|
+
this._rebuildPipeline();
|
|
555
|
+
return this;
|
|
556
|
+
}
|
|
557
|
+
/** 重新启用被 unstage 关闭的步骤 */
|
|
558
|
+
restage(name) {
|
|
559
|
+
if (!INTERNAL_STAGES.includes(name)) throw new ConfigurationError(`restage: 未知的内部步骤 "${name}"。可选: ${INTERNAL_STAGES.join(", ")}`);
|
|
560
|
+
this._disabledStages.delete(name);
|
|
561
|
+
this._rebuildPipeline();
|
|
562
|
+
return this;
|
|
563
|
+
}
|
|
564
|
+
/** 取得某个内部步骤的默认实现(用于"包一层":做自己的事,再调用它) */
|
|
565
|
+
getStage(name) {
|
|
566
|
+
if (!INTERNAL_STAGES.includes(name)) throw new ConfigurationError(`getStage: 未知的内部步骤 "${name}"。可选: ${INTERNAL_STAGES.join(", ")}`);
|
|
567
|
+
return this._defaultStageRuns()[name];
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* 重建内部管道:内部步骤(可被 replaceStage 顶替 / 被 unstage 关闭)+ 用户车间按 phase 插入
|
|
571
|
+
* 顺序:prepareInput → beforeSend → [用户 beforeSend 车间] → autoContinue → buildRequest → send → [用户 afterSend 车间]
|
|
572
|
+
*/
|
|
489
573
|
_buildPipeline() {
|
|
490
574
|
const p = new Pipeline();
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
name
|
|
505
|
-
|
|
506
|
-
});
|
|
507
|
-
p.register({
|
|
508
|
-
name: "buildRequest",
|
|
509
|
-
run: (ctx) => this._stageBuildRequest(ctx)
|
|
510
|
-
});
|
|
511
|
-
p.register({
|
|
512
|
-
name: "send",
|
|
513
|
-
run: (ctx) => this._stageSend(ctx)
|
|
514
|
-
});
|
|
515
|
-
for (const s of this._userStages) if (s.phase === "afterSend") p.register({
|
|
516
|
-
name: s.name,
|
|
517
|
-
run: (ctx) => s.run(ctx)
|
|
518
|
-
});
|
|
575
|
+
const defaults = this._defaultStageRuns();
|
|
576
|
+
const insertUsers = (phase) => {
|
|
577
|
+
for (const s of this._userStages) if (s.phase === phase) p.register({
|
|
578
|
+
name: s.name,
|
|
579
|
+
run: (ctx) => s.run(ctx)
|
|
580
|
+
});
|
|
581
|
+
};
|
|
582
|
+
for (const name of INTERNAL_STAGES) {
|
|
583
|
+
if (!this._disabledStages.has(name)) p.register({
|
|
584
|
+
name,
|
|
585
|
+
run: this._stageOverrides.get(name) || defaults[name]
|
|
586
|
+
});
|
|
587
|
+
if (name === "beforeSend") insertUsers("beforeSend");
|
|
588
|
+
if (name === "send") insertUsers("afterSend");
|
|
589
|
+
}
|
|
519
590
|
return p;
|
|
520
591
|
}
|
|
521
592
|
_rebuildPipeline() {
|
|
@@ -525,98 +596,81 @@
|
|
|
525
596
|
assertAdapter(adapter);
|
|
526
597
|
this._adapter = adapter;
|
|
527
598
|
}
|
|
528
|
-
|
|
529
|
-
|
|
599
|
+
/**
|
|
600
|
+
* 中断请求(v4.0:按请求隔离)。
|
|
601
|
+
* - 不传参数:中断**所有**活跃请求(旧版只会中断"最后一个",多请求时会中断错的那个)
|
|
602
|
+
* - 传 requestId:只中断那一个(id 见 `chat.activeRequests`)
|
|
603
|
+
*/
|
|
604
|
+
abort(requestId) {
|
|
605
|
+
if (requestId) {
|
|
606
|
+
const controller = this._activeRequests.get(requestId);
|
|
607
|
+
if (controller) controller.abort();
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
for (const controller of this._activeRequests.values()) controller.abort();
|
|
530
611
|
}
|
|
612
|
+
/** 是否有请求正在生成中 */
|
|
531
613
|
get isGenerating() {
|
|
532
|
-
return this.
|
|
614
|
+
return this._activeRequests.size > 0;
|
|
615
|
+
}
|
|
616
|
+
/** 当前活跃请求的 id 列表(调试 / 精确中断用) */
|
|
617
|
+
get activeRequests() {
|
|
618
|
+
return [...this._activeRequests.keys()];
|
|
619
|
+
}
|
|
620
|
+
/** 内部:登记一个请求,返回 { id, signal } */
|
|
621
|
+
_beginRequest() {
|
|
622
|
+
const id = "req_" + ++this._requestSeq;
|
|
623
|
+
const controller = new AbortController();
|
|
624
|
+
this._activeRequests.set(id, controller);
|
|
625
|
+
return {
|
|
626
|
+
id,
|
|
627
|
+
signal: controller.signal
|
|
628
|
+
};
|
|
533
629
|
}
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
this.
|
|
537
|
-
try {
|
|
538
|
-
await this._request({
|
|
539
|
-
addUser: false,
|
|
540
|
-
isStream: false,
|
|
541
|
-
mergeToEntry: target.id
|
|
542
|
-
});
|
|
543
|
-
this.messages.update(target.id, {
|
|
544
|
-
_complete: true,
|
|
545
|
-
prefix: void 0,
|
|
546
|
-
_ephemeral: false
|
|
547
|
-
});
|
|
548
|
-
return target;
|
|
549
|
-
} catch (err) {
|
|
550
|
-
this.messages.update(target.id, {
|
|
551
|
-
_complete: true,
|
|
552
|
-
prefix: void 0,
|
|
553
|
-
_ephemeral: false
|
|
554
|
-
});
|
|
555
|
-
if (err.name === "AbortError") {
|
|
556
|
-
this.emit("aborted", { timestamp: Date.now() });
|
|
557
|
-
return target;
|
|
558
|
-
}
|
|
559
|
-
throw err;
|
|
560
|
-
}
|
|
630
|
+
/** 内部:注销一个请求 */
|
|
631
|
+
_endRequest(id) {
|
|
632
|
+
this._activeRequests.delete(id);
|
|
561
633
|
}
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
});
|
|
576
|
-
},
|
|
577
|
-
onDone: (final) => {
|
|
578
|
-
if (onDone) onDone(final);
|
|
579
|
-
}
|
|
580
|
-
});
|
|
581
|
-
this.messages.update(target.id, {
|
|
582
|
-
_complete: true,
|
|
583
|
-
prefix: void 0,
|
|
584
|
-
_ephemeral: false
|
|
585
|
-
});
|
|
586
|
-
return target;
|
|
587
|
-
} catch (err) {
|
|
588
|
-
this.messages.update(target.id, {
|
|
589
|
-
_complete: true,
|
|
590
|
-
prefix: void 0,
|
|
591
|
-
_ephemeral: false
|
|
592
|
-
});
|
|
593
|
-
if (err.name === "AbortError") {
|
|
594
|
-
this.emit("aborted", { timestamp: Date.now() });
|
|
595
|
-
return target;
|
|
596
|
-
}
|
|
597
|
-
throw err;
|
|
634
|
+
/**
|
|
635
|
+
* 注册"运行时可改的配置字段"(v4.0)。
|
|
636
|
+
* 让插件自带配置(如 continuation 的 autoContinue)不必回头改核心白名单。
|
|
637
|
+
* @param {string[]|Object<string, Function>} keys 字段名数组,或 { 字段名: 校验器(value)=>boolean }
|
|
638
|
+
* @returns {ChatService} this
|
|
639
|
+
* @throws {ConfigurationError} 字段名非法 / 校验器不是函数
|
|
640
|
+
*/
|
|
641
|
+
registerConfigKeys(keys) {
|
|
642
|
+
const entries = Array.isArray(keys) ? keys.map((k) => [k, null]) : Object.entries(keys || {});
|
|
643
|
+
for (const [key, validator] of entries) {
|
|
644
|
+
if (typeof key !== "string" || !key.trim()) throw new ConfigurationError("registerConfigKeys: 字段名必须是非空字符串");
|
|
645
|
+
if (validator != null && typeof validator !== "function") throw new ConfigurationError(`registerConfigKeys: "${key}" 的校验器必须是函数`);
|
|
646
|
+
this._extraConfigKeys.set(key.trim(), validator || null);
|
|
598
647
|
}
|
|
648
|
+
return this;
|
|
599
649
|
}
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
const
|
|
603
|
-
|
|
604
|
-
|
|
650
|
+
/** 注销运行时可改字段(插件卸载时调用)@returns {ChatService} this */
|
|
651
|
+
unregisterConfigKeys(keys) {
|
|
652
|
+
for (const k of Array.isArray(keys) ? keys : [keys]) this._extraConfigKeys.delete(k);
|
|
653
|
+
return this;
|
|
654
|
+
}
|
|
655
|
+
/** 当前允许在运行时修改的配置字段(内置基础字段 + 插件注册) */
|
|
656
|
+
get configKeys() {
|
|
657
|
+
return [...SESSION_CONFIG_KEYS, ...this._extraConfigKeys.keys()];
|
|
605
658
|
}
|
|
606
659
|
/**
|
|
607
|
-
*
|
|
660
|
+
* 运行时修改配置(仅"可改字段":内置基础字段 + 插件注册字段)。
|
|
608
661
|
*
|
|
609
|
-
* 白名单:model / temperature / maxTokens / modelParams / system / retry / ephemeralContinue。
|
|
610
662
|
* - 运输层(apiKey/baseUrl/headers):归适配器,要改就换适配器实例
|
|
611
663
|
* - 请求参数默认值:走适配器工厂 options 或请求级覆盖(chat.send(x, params))
|
|
612
|
-
* -
|
|
664
|
+
* - 插件自有配置:插件用 `registerConfigKeys` 登记后才可改(工厂 options 仍是首选)
|
|
613
665
|
*
|
|
614
666
|
* 与构造器一样经过校验,不能注入非法值。
|
|
615
|
-
* @throws {ConfigurationError}
|
|
667
|
+
* @throws {ConfigurationError} 非白名单字段、非法值、或插件校验器拒绝
|
|
616
668
|
*/
|
|
617
669
|
updateConfig(partial) {
|
|
618
|
-
|
|
670
|
+
const allowed = this.configKeys;
|
|
671
|
+
for (const key of Object.keys(partial)) if (!allowed.includes(key)) throw new ConfigurationError(`updateConfig 不支持修改 "${key}"。允许的字段: ${allowed.join(", ")}`);
|
|
619
672
|
this._assertValidConfig(partial);
|
|
673
|
+
for (const [key, validator] of this._extraConfigKeys) if (validator && key in partial && validator(partial[key]) === false) throw new ConfigurationError(`updateConfig: 字段 "${key}" 的值不合法: ${JSON.stringify(partial[key])}`);
|
|
620
674
|
Object.assign(this.config, partial);
|
|
621
675
|
if (typeof partial.system === "string") this.systemPrompts.set(partial.system);
|
|
622
676
|
this.emit("config-updated", {
|
|
@@ -682,6 +736,7 @@
|
|
|
682
736
|
onProgress: options.onProgress || null,
|
|
683
737
|
onDone: options.onDone || null,
|
|
684
738
|
mergeToEntry: options.mergeToEntry || null,
|
|
739
|
+
signal: options.signal || null,
|
|
685
740
|
body: null,
|
|
686
741
|
result: null
|
|
687
742
|
};
|
|
@@ -707,22 +762,19 @@
|
|
|
707
762
|
timestamp: Date.now()
|
|
708
763
|
});
|
|
709
764
|
}
|
|
710
|
-
/**
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
if (last && last.prefix && (last.role === "assistant" || last._ephemeral)) ctx.mergeToEntry = last.id;
|
|
724
|
-
}
|
|
725
|
-
}
|
|
765
|
+
/**
|
|
766
|
+
* 车间 2:发送前 —— **v4.0 起默认什么都不做**。
|
|
767
|
+
* 需要在这里做事的(注入、改写消息/配置):`chat.replaceStage('beforeSend', fn)`
|
|
768
|
+
* 或挂一个 `chat.pipe({ phase: 'beforeSend' })` 车间(在它之后执行)。
|
|
769
|
+
* 旧的 `_hooks.on('beforeRequest')` 兼容桥已于 v4.0 移除。
|
|
770
|
+
*/
|
|
771
|
+
async _stageBeforeSend(ctx) {}
|
|
772
|
+
/**
|
|
773
|
+
* 车间 3:自动续写检测 —— **v4.0 起默认什么都不做**。
|
|
774
|
+
* 续写是玩法:需要它的装上 continuation 插件,由插件 replaceStage('autoContinue', ...) 接管。
|
|
775
|
+
* 步骤名与位置保留,方便插件替换、也方便使用者 `unstage('autoContinue')` 明确移除。
|
|
776
|
+
*/
|
|
777
|
+
async _stageAutoContinue(ctx) {}
|
|
726
778
|
/** 车间 4:构建请求体 */
|
|
727
779
|
async _stageBuildRequest(ctx) {
|
|
728
780
|
ctx.body = this._adapter.buildRequest(ctx.messages.getAll(), ctx.config, ctx.systemPrompts.getEnabled());
|
|
@@ -737,7 +789,8 @@
|
|
|
737
789
|
mergeToEntry: ctx.mergeToEntry,
|
|
738
790
|
config: ctx.config,
|
|
739
791
|
maxRetries: retryCfg.maxRetries ?? 0,
|
|
740
|
-
retryDelay: retryCfg.retryDelay ?? 1e3
|
|
792
|
+
retryDelay: retryCfg.retryDelay ?? 1e3,
|
|
793
|
+
signal: ctx.signal
|
|
741
794
|
});
|
|
742
795
|
}
|
|
743
796
|
_addUserMessage(userInput, addUser) {
|
|
@@ -753,9 +806,9 @@
|
|
|
753
806
|
}
|
|
754
807
|
}
|
|
755
808
|
/** retry 循环,占位消息只 push 一次 */
|
|
756
|
-
async _withRetry(body, { isStream, onProgress, onDone, mergeToEntry, config, maxRetries, retryDelay }) {
|
|
809
|
+
async _withRetry(body, { isStream, onProgress, onDone, mergeToEntry, config, maxRetries, retryDelay, signal }) {
|
|
757
810
|
let lastError = null;
|
|
758
|
-
const adapterOptions = { signal
|
|
811
|
+
const adapterOptions = { signal };
|
|
759
812
|
let placeholder = null;
|
|
760
813
|
let base = null;
|
|
761
814
|
if (isStream) {
|
|
@@ -783,7 +836,6 @@
|
|
|
783
836
|
await new Promise((r) => setTimeout(r, retryDelay));
|
|
784
837
|
}
|
|
785
838
|
try {
|
|
786
|
-
this._isGenerating = true;
|
|
787
839
|
if (isStream) return await this._stream(body, adapterOptions, {
|
|
788
840
|
placeholder,
|
|
789
841
|
base,
|
|
@@ -794,22 +846,15 @@
|
|
|
794
846
|
});
|
|
795
847
|
else {
|
|
796
848
|
const resp = await this._adapter.send(body, config, adapterOptions);
|
|
797
|
-
|
|
798
|
-
if (this._processResponse && !mergeToEntry) result = await this._processResponse(result, { isStream: false });
|
|
799
|
-
return result;
|
|
849
|
+
return this._handleResult(this._adapter.parseResponse(resp), mergeToEntry);
|
|
800
850
|
}
|
|
801
851
|
} catch (error) {
|
|
802
852
|
lastError = error;
|
|
803
|
-
if (error.name === "AbortError" ||
|
|
804
|
-
this._isGenerating = false;
|
|
805
|
-
throw error;
|
|
806
|
-
}
|
|
853
|
+
if (error.name === "AbortError" || signal?.aborted) throw error;
|
|
807
854
|
if (error instanceof NetworkError && attempt < maxRetries) continue;
|
|
808
|
-
this._isGenerating = false;
|
|
809
855
|
throw error;
|
|
810
856
|
}
|
|
811
857
|
}
|
|
812
|
-
this._isGenerating = false;
|
|
813
858
|
throw lastError;
|
|
814
859
|
}
|
|
815
860
|
/**
|
|
@@ -824,19 +869,14 @@
|
|
|
824
869
|
*/
|
|
825
870
|
async _stream(body, adapterOptions, { placeholder, base, mergeToEntry, onProgress, onDone, config }) {
|
|
826
871
|
let finalMsg = null;
|
|
827
|
-
await this._adapter.stream(body, config, (snap) => {
|
|
872
|
+
await this._adapter.stream(body, config, (snap, delta) => {
|
|
828
873
|
placeholder.content = mergeToEntry && base ? base.content + (snap.content || "") : snap.content || "";
|
|
829
874
|
if (snap.reasoningContent) placeholder.reasoningContent = mergeToEntry && base ? base.reasoning + snap.reasoningContent : snap.reasoningContent;
|
|
830
875
|
if (snap.toolCalls) placeholder.toolCalls = [...snap.toolCalls];
|
|
831
876
|
this.emit("stream-progress", snap);
|
|
832
|
-
if (onProgress) onProgress(snap);
|
|
877
|
+
if (onProgress) onProgress(snap, delta);
|
|
833
878
|
}, async (final) => {
|
|
834
879
|
finalMsg = final;
|
|
835
|
-
if (this._processResponse && !mergeToEntry) finalMsg = await this._processResponse(finalMsg, {
|
|
836
|
-
isStream: true,
|
|
837
|
-
onProgress,
|
|
838
|
-
onDone
|
|
839
|
-
});
|
|
840
880
|
if (mergeToEntry && base) {
|
|
841
881
|
const changes = {
|
|
842
882
|
content: base.content + (final.content || ""),
|
|
@@ -890,13 +930,14 @@
|
|
|
890
930
|
* @param {Object} [params] - 请求级参数覆盖(model/temperature/modelParams 等,仅本次生效)
|
|
891
931
|
*/
|
|
892
932
|
async send(userInput, params) {
|
|
893
|
-
|
|
933
|
+
const { id, signal } = this._beginRequest();
|
|
894
934
|
try {
|
|
895
935
|
return await this._request({
|
|
896
936
|
userInput,
|
|
897
937
|
addUser: true,
|
|
898
938
|
isStream: false,
|
|
899
|
-
params
|
|
939
|
+
params,
|
|
940
|
+
signal
|
|
900
941
|
});
|
|
901
942
|
} catch (err) {
|
|
902
943
|
if (err.name === "AbortError") {
|
|
@@ -905,8 +946,7 @@
|
|
|
905
946
|
}
|
|
906
947
|
throw err;
|
|
907
948
|
} finally {
|
|
908
|
-
this.
|
|
909
|
-
this._isGenerating = false;
|
|
949
|
+
this._endRequest(id);
|
|
910
950
|
}
|
|
911
951
|
}
|
|
912
952
|
/**
|
|
@@ -922,7 +962,7 @@
|
|
|
922
962
|
onProgress = params;
|
|
923
963
|
params = void 0;
|
|
924
964
|
}
|
|
925
|
-
|
|
965
|
+
const { id, signal } = this._beginRequest();
|
|
926
966
|
try {
|
|
927
967
|
return await this._request({
|
|
928
968
|
userInput,
|
|
@@ -930,7 +970,8 @@
|
|
|
930
970
|
isStream: true,
|
|
931
971
|
onProgress,
|
|
932
972
|
onDone,
|
|
933
|
-
params
|
|
973
|
+
params,
|
|
974
|
+
signal
|
|
934
975
|
});
|
|
935
976
|
} catch (err) {
|
|
936
977
|
if (err.name === "AbortError") {
|
|
@@ -939,21 +980,24 @@
|
|
|
939
980
|
}
|
|
940
981
|
throw err;
|
|
941
982
|
} finally {
|
|
942
|
-
this.
|
|
943
|
-
this._isGenerating = false;
|
|
983
|
+
this._endRequest(id);
|
|
944
984
|
}
|
|
945
985
|
}
|
|
946
986
|
/**
|
|
947
987
|
* 重发当前消息(不加用户消息)。
|
|
948
988
|
* @param {Object} [params] - 请求级参数覆盖(仅本次生效)
|
|
989
|
+
* @param {Object} [options] - { mergeToEntry } 可选:把结果合并进指定的已有消息(续写等场景)
|
|
949
990
|
*/
|
|
950
|
-
async sendExisting(params) {
|
|
951
|
-
|
|
991
|
+
async sendExisting(params, options = {}) {
|
|
992
|
+
const mergeToEntry = options?.mergeToEntry || null;
|
|
993
|
+
const { id, signal } = this._beginRequest();
|
|
952
994
|
try {
|
|
953
995
|
return await this._request({
|
|
954
996
|
addUser: false,
|
|
955
997
|
isStream: false,
|
|
956
|
-
params
|
|
998
|
+
params,
|
|
999
|
+
mergeToEntry,
|
|
1000
|
+
signal
|
|
957
1001
|
});
|
|
958
1002
|
} catch (err) {
|
|
959
1003
|
if (err.name === "AbortError") {
|
|
@@ -962,30 +1006,38 @@
|
|
|
962
1006
|
}
|
|
963
1007
|
throw err;
|
|
964
1008
|
} finally {
|
|
965
|
-
this.
|
|
966
|
-
this._isGenerating = false;
|
|
1009
|
+
this._endRequest(id);
|
|
967
1010
|
}
|
|
968
1011
|
}
|
|
969
1012
|
/**
|
|
970
1013
|
* 流式重发(不加用户消息)。
|
|
971
1014
|
* @param {Object} [params] - 请求级参数覆盖(仅本次生效)
|
|
1015
|
+
* @param {Object} [options] - { mergeToEntry } 可选:把结果合并进指定的已有消息
|
|
972
1016
|
* @param {Function} [onProgress] - 流式进度回调
|
|
973
1017
|
* @param {Function} [onDone] - 流式完成回调
|
|
974
1018
|
*/
|
|
975
|
-
async sendExistingStream(params, onProgress, onDone) {
|
|
1019
|
+
async sendExistingStream(params, options, onProgress, onDone) {
|
|
976
1020
|
if (typeof params === "function") {
|
|
977
|
-
onDone =
|
|
1021
|
+
onDone = options;
|
|
978
1022
|
onProgress = params;
|
|
1023
|
+
options = {};
|
|
979
1024
|
params = void 0;
|
|
1025
|
+
} else if (typeof options === "function") {
|
|
1026
|
+
onDone = onProgress;
|
|
1027
|
+
onProgress = options;
|
|
1028
|
+
options = {};
|
|
980
1029
|
}
|
|
981
|
-
|
|
1030
|
+
const mergeToEntry = options?.mergeToEntry || null;
|
|
1031
|
+
const { id, signal } = this._beginRequest();
|
|
982
1032
|
try {
|
|
983
1033
|
return await this._request({
|
|
984
1034
|
addUser: false,
|
|
985
1035
|
isStream: true,
|
|
986
1036
|
onProgress,
|
|
987
1037
|
onDone,
|
|
988
|
-
params
|
|
1038
|
+
params,
|
|
1039
|
+
mergeToEntry,
|
|
1040
|
+
signal
|
|
989
1041
|
});
|
|
990
1042
|
} catch (err) {
|
|
991
1043
|
if (err.name === "AbortError") {
|
|
@@ -994,8 +1046,7 @@
|
|
|
994
1046
|
}
|
|
995
1047
|
throw err;
|
|
996
1048
|
} finally {
|
|
997
|
-
this.
|
|
998
|
-
this._isGenerating = false;
|
|
1049
|
+
this._endRequest(id);
|
|
999
1050
|
}
|
|
1000
1051
|
}
|
|
1001
1052
|
};
|
|
@@ -1046,26 +1097,23 @@
|
|
|
1046
1097
|
//#endregion
|
|
1047
1098
|
//#region src/utils/MessageFormatter.js
|
|
1048
1099
|
/**
|
|
1049
|
-
* MessageFormatter — 可注册的消息格式转换器
|
|
1100
|
+
* MessageFormatter — 可注册的消息格式转换器 + 可注册的过滤器链(v4.0)
|
|
1050
1101
|
*
|
|
1051
1102
|
* 职责:
|
|
1052
|
-
* 1. 内置
|
|
1103
|
+
* 1. 内置 OpenAI 兼容格式的转换逻辑
|
|
1053
1104
|
* 2. 支持 register(name, fn) 注册自定义格式(如 Anthropic、Gemini 等)
|
|
1054
|
-
* 3.
|
|
1055
|
-
*
|
|
1105
|
+
* 3. v4.0:消息"保留 / 丢弃"规则改为可注册的过滤器链(registerFilter)
|
|
1106
|
+
* —— 原来硬编码在格式里的 ephemeral / system / 空内容规则,现在都是过滤器;
|
|
1107
|
+
* 业务插件(如 continuation)可以同名覆盖内置过滤器,接管规则。
|
|
1056
1108
|
*
|
|
1057
1109
|
* 用法:
|
|
1058
1110
|
* import { MessageFormatter } from 'my-ai-chat-framework';
|
|
1059
1111
|
*
|
|
1060
1112
|
* // 使用内置格式
|
|
1061
|
-
* const msgs = MessageFormatter.format({
|
|
1062
|
-
* messages, systemPrompts, capabilities, resolveImage
|
|
1063
|
-
* }); // 默认 'openai'
|
|
1113
|
+
* const msgs = MessageFormatter.format({ messages, systemPrompts, capabilities, resolveImage });
|
|
1064
1114
|
*
|
|
1065
|
-
* //
|
|
1066
|
-
* MessageFormatter.
|
|
1067
|
-
* // 返回 Anthropic 格式的消息数组
|
|
1068
|
-
* });
|
|
1115
|
+
* // 注册自己的过滤器(决定哪些消息进入请求)
|
|
1116
|
+
* MessageFormatter.registerFilter('my-rule', (msg, ctx) => msg.role === 'system' ? null : msg);
|
|
1069
1117
|
*/
|
|
1070
1118
|
var IS_URL = /^https?:\/\//i;
|
|
1071
1119
|
function defaultResolveImage(imageId) {
|
|
@@ -1094,7 +1142,23 @@
|
|
|
1094
1142
|
}
|
|
1095
1143
|
return content;
|
|
1096
1144
|
}
|
|
1097
|
-
/**
|
|
1145
|
+
/** system 角色消息不进对话数组(system 由 systemPrompts 统一置顶) */
|
|
1146
|
+
function filterSystemDrop(msg) {
|
|
1147
|
+
if (msg.role === "system" && !msg._ephemeral) return null;
|
|
1148
|
+
return msg;
|
|
1149
|
+
}
|
|
1150
|
+
/** 空内容清理:无文本、无图片的消息丢弃(tool、带 toolCalls 的 assistant、prefix 空消息例外) */
|
|
1151
|
+
function filterEmptyDrop(msg) {
|
|
1152
|
+
if (msg.role === "system" || msg.role === "tool") return msg;
|
|
1153
|
+
if (msg.role === "assistant" && msg.toolCalls?.length) return msg;
|
|
1154
|
+
const hasText = msg.content && isString(msg.content) && msg.content.trim();
|
|
1155
|
+
const hasImages = Array.isArray(msg.images) && msg.images.length > 0;
|
|
1156
|
+
if (!hasText && !hasImages) {
|
|
1157
|
+
if (msg.role === "assistant" && msg.prefix) return msg;
|
|
1158
|
+
return null;
|
|
1159
|
+
}
|
|
1160
|
+
return msg;
|
|
1161
|
+
}
|
|
1098
1162
|
function toOpenAI({ messages, systemPrompts, capabilities, resolveImage }) {
|
|
1099
1163
|
const rImg = typeof resolveImage === "function" ? resolveImage : defaultResolveImage;
|
|
1100
1164
|
const result = [];
|
|
@@ -1102,15 +1166,8 @@
|
|
|
1102
1166
|
role: "system",
|
|
1103
1167
|
content: sp.content.trim()
|
|
1104
1168
|
});
|
|
1105
|
-
|
|
1106
|
-
for (
|
|
1107
|
-
else break;
|
|
1108
|
-
for (let i = 0; i < messages.length; i++) {
|
|
1109
|
-
const msg = messages[i];
|
|
1110
|
-
if (msg._ephemeral) {
|
|
1111
|
-
if (i < ephemEnd) continue;
|
|
1112
|
-
}
|
|
1113
|
-
if (msg.role === "system" && !msg._ephemeral) continue;
|
|
1169
|
+
const kept = MessageFormatter._runFilters(messages, capabilities || {});
|
|
1170
|
+
for (const msg of kept) {
|
|
1114
1171
|
if (msg.role === "tool") {
|
|
1115
1172
|
result.push({
|
|
1116
1173
|
role: "tool",
|
|
@@ -1146,7 +1203,7 @@
|
|
|
1146
1203
|
}
|
|
1147
1204
|
const entry = { role: msg.role };
|
|
1148
1205
|
if (hasImages) {
|
|
1149
|
-
if (!capabilities?.vision) throw new
|
|
1206
|
+
if (!capabilities?.vision) throw new ValidationError("[MessageFormatter] 消息包含图片但模型不支持视觉(capabilities.vision=false)。请切换模型或移除图片。");
|
|
1150
1207
|
entry.content = buildMultimodalContent(msg.content || "", msg.images, rImg);
|
|
1151
1208
|
} else entry.content = msg.content;
|
|
1152
1209
|
if (msg.prefix) entry.prefix = true;
|
|
@@ -1156,22 +1213,71 @@
|
|
|
1156
1213
|
}
|
|
1157
1214
|
var MessageFormatter = {
|
|
1158
1215
|
_formats: new Map([["openai", toOpenAI]]),
|
|
1216
|
+
_filters: /* @__PURE__ */ new Map(),
|
|
1217
|
+
_filterOrder: 0,
|
|
1159
1218
|
register(name, fn) {
|
|
1160
|
-
if (!name || typeof name !== "string" || !name.trim()) throw new
|
|
1161
|
-
if (typeof fn !== "function") throw new
|
|
1219
|
+
if (!name || typeof name !== "string" || !name.trim()) throw new ValidationError("[MessageFormatter] 格式名称必须是非空字符串");
|
|
1220
|
+
if (typeof fn !== "function") throw new ValidationError("[MessageFormatter] 转换函数必须是 function");
|
|
1162
1221
|
this._formats.set(name.trim(), fn);
|
|
1163
1222
|
},
|
|
1164
1223
|
unregister(name) {
|
|
1165
|
-
if (name === "openai") throw new
|
|
1224
|
+
if (name === "openai") throw new ValidationError("[MessageFormatter] 内置格式 \"openai\" 不可移除");
|
|
1166
1225
|
this._formats.delete(name);
|
|
1167
1226
|
},
|
|
1168
1227
|
listFormats() {
|
|
1169
1228
|
return [...this._formats.keys()];
|
|
1170
1229
|
},
|
|
1230
|
+
registerFilter(name, fn, options = {}) {
|
|
1231
|
+
if (!name || typeof name !== "string" || !name.trim()) throw new ValidationError("[MessageFormatter] 过滤器名称必须是非空字符串");
|
|
1232
|
+
if (typeof fn !== "function") throw new ValidationError("[MessageFormatter] 过滤器必须是 function");
|
|
1233
|
+
const key = name.trim();
|
|
1234
|
+
const priority = typeof options.priority === "number" ? options.priority : 100;
|
|
1235
|
+
const prev = this._filters.get(key);
|
|
1236
|
+
this._filters.set(key, {
|
|
1237
|
+
name: key,
|
|
1238
|
+
fn,
|
|
1239
|
+
priority,
|
|
1240
|
+
order: ++this._filterOrder
|
|
1241
|
+
});
|
|
1242
|
+
return prev ? prev.fn : void 0;
|
|
1243
|
+
},
|
|
1244
|
+
unregisterFilter(name) {
|
|
1245
|
+
return this._filters.delete(name);
|
|
1246
|
+
},
|
|
1247
|
+
listFilters() {
|
|
1248
|
+
return [...this._filters.values()].sort((a, b) => a.priority - b.priority || a.order - b.order).map((f) => ({
|
|
1249
|
+
name: f.name,
|
|
1250
|
+
priority: f.priority
|
|
1251
|
+
}));
|
|
1252
|
+
},
|
|
1253
|
+
_runFilters(messages, capabilities = {}) {
|
|
1254
|
+
if (this._filters.size === 0) return [...messages];
|
|
1255
|
+
const list = [...this._filters.values()].sort((a, b) => a.priority - b.priority || a.order - b.order);
|
|
1256
|
+
const out = [];
|
|
1257
|
+
for (let i = 0; i < messages.length; i++) {
|
|
1258
|
+
let msg = messages[i];
|
|
1259
|
+
const ctx = {
|
|
1260
|
+
index: i,
|
|
1261
|
+
total: messages.length,
|
|
1262
|
+
messages,
|
|
1263
|
+
capabilities
|
|
1264
|
+
};
|
|
1265
|
+
let dropped = false;
|
|
1266
|
+
for (const f of list) {
|
|
1267
|
+
msg = f.fn(msg, ctx);
|
|
1268
|
+
if (msg === null || msg === void 0) {
|
|
1269
|
+
dropped = true;
|
|
1270
|
+
break;
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
if (!dropped) out.push(msg);
|
|
1274
|
+
}
|
|
1275
|
+
return out;
|
|
1276
|
+
},
|
|
1171
1277
|
format(options = {}) {
|
|
1172
1278
|
const formatName = options.format || "openai";
|
|
1173
1279
|
const fn = this._formats.get(formatName);
|
|
1174
|
-
if (!fn) throw new
|
|
1280
|
+
if (!fn) throw new ValidationError(`[MessageFormatter] 未知格式 "${formatName}"。可用格式: ${[...this._formats.keys()].join(", ")}`);
|
|
1175
1281
|
return fn({
|
|
1176
1282
|
messages: options.messages || [],
|
|
1177
1283
|
systemPrompts: options.systemPrompts || [],
|
|
@@ -1180,6 +1286,8 @@
|
|
|
1180
1286
|
});
|
|
1181
1287
|
}
|
|
1182
1288
|
};
|
|
1289
|
+
MessageFormatter.registerFilter("system-drop", filterSystemDrop, { priority: 900 });
|
|
1290
|
+
MessageFormatter.registerFilter("empty-drop", filterEmptyDrop, { priority: 910 });
|
|
1183
1291
|
function assembleMessages(options = {}) {
|
|
1184
1292
|
return MessageFormatter.format({
|
|
1185
1293
|
...options,
|
|
@@ -1229,10 +1337,10 @@
|
|
|
1229
1337
|
buildRequest(messages, config, systemPrompts = []) {
|
|
1230
1338
|
config = this._resolveConfig(config);
|
|
1231
1339
|
const model = config.model || config.modelParams?.model;
|
|
1232
|
-
if (!model) throw new
|
|
1340
|
+
if (!model) throw new ConfigurationError("Missing required config: model (either at top level or in modelParams)");
|
|
1233
1341
|
const mp = config.modelParams || {};
|
|
1234
|
-
const temperature = mp.temperature ?? config.temperature
|
|
1235
|
-
const maxTokens = mp.maxTokens ?? config.maxTokens
|
|
1342
|
+
const temperature = mp.temperature ?? config.temperature;
|
|
1343
|
+
const maxTokens = mp.maxTokens ?? config.maxTokens;
|
|
1236
1344
|
const reasoningEffort = mp.reasoningEffort ?? config.reasoningEffort;
|
|
1237
1345
|
const requestBody = {
|
|
1238
1346
|
model,
|
|
@@ -1243,10 +1351,10 @@
|
|
|
1243
1351
|
resolveImage: config.resolveImage,
|
|
1244
1352
|
format: config.messageFormat
|
|
1245
1353
|
}),
|
|
1246
|
-
temperature,
|
|
1247
|
-
max_tokens: maxTokens,
|
|
1248
1354
|
stream: false
|
|
1249
1355
|
};
|
|
1356
|
+
if (temperature !== void 0) requestBody.temperature = temperature;
|
|
1357
|
+
if (maxTokens !== void 0) requestBody.max_tokens = maxTokens;
|
|
1250
1358
|
if (mp.topP !== void 0) requestBody.top_p = mp.topP;
|
|
1251
1359
|
if (mp.frequencyPenalty !== void 0) requestBody.frequency_penalty = mp.frequencyPenalty;
|
|
1252
1360
|
if (mp.presencePenalty !== void 0) requestBody.presence_penalty = mp.presencePenalty;
|
|
@@ -1353,22 +1461,37 @@
|
|
|
1353
1461
|
content: "",
|
|
1354
1462
|
reasoningContent: ""
|
|
1355
1463
|
};
|
|
1464
|
+
let reachedDone = false;
|
|
1356
1465
|
while (true) {
|
|
1357
1466
|
if (options.signal?.aborted) break;
|
|
1358
1467
|
const { done, value } = await reader.read();
|
|
1359
1468
|
if (done) break;
|
|
1360
1469
|
buffer += decoder.decode(value, { stream: true });
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
const
|
|
1365
|
-
|
|
1366
|
-
|
|
1470
|
+
let sepMatch;
|
|
1471
|
+
const EVENT_SEP = /\r?\n\r?\n/;
|
|
1472
|
+
while ((sepMatch = EVENT_SEP.exec(buffer)) !== null) {
|
|
1473
|
+
const rawEvent = buffer.slice(0, sepMatch.index);
|
|
1474
|
+
buffer = buffer.slice(sepMatch.index + sepMatch[0].length);
|
|
1475
|
+
const dataPayload = rawEvent.split(/\r?\n/).filter((l) => l.startsWith("data:")).map((l) => l.slice(5).replace(/^ /, "")).join("\n");
|
|
1476
|
+
if (!dataPayload) continue;
|
|
1477
|
+
if (dataPayload.trim() === "[DONE]") {
|
|
1478
|
+
reachedDone = true;
|
|
1479
|
+
break;
|
|
1480
|
+
}
|
|
1367
1481
|
try {
|
|
1368
|
-
const delta = JSON.parse(
|
|
1482
|
+
const delta = JSON.parse(dataPayload).choices?.[0]?.delta;
|
|
1369
1483
|
if (!delta) continue;
|
|
1370
|
-
|
|
1484
|
+
const deltaInfo = {
|
|
1485
|
+
content: "",
|
|
1486
|
+
reasoningContent: "",
|
|
1487
|
+
toolCalls: null
|
|
1488
|
+
};
|
|
1489
|
+
if (delta.content) {
|
|
1490
|
+
accumulated.content += delta.content;
|
|
1491
|
+
deltaInfo.content = delta.content;
|
|
1492
|
+
}
|
|
1371
1493
|
if (delta.tool_calls) {
|
|
1494
|
+
const newCalls = [];
|
|
1372
1495
|
if (!accumulated.toolCalls) accumulated.toolCalls = [];
|
|
1373
1496
|
for (const toolCallDelta of delta.tool_calls) {
|
|
1374
1497
|
const index = toolCallDelta.index;
|
|
@@ -1384,14 +1507,23 @@
|
|
|
1384
1507
|
if (toolCallDelta.type) accumulated.toolCalls[index].type = toolCallDelta.type;
|
|
1385
1508
|
if (toolCallDelta.function?.name) accumulated.toolCalls[index].function.name += toolCallDelta.function.name;
|
|
1386
1509
|
if (toolCallDelta.function?.arguments) accumulated.toolCalls[index].function.arguments += toolCallDelta.function.arguments;
|
|
1510
|
+
newCalls.push({
|
|
1511
|
+
index,
|
|
1512
|
+
delta: toolCallDelta
|
|
1513
|
+
});
|
|
1387
1514
|
}
|
|
1515
|
+
deltaInfo.toolCalls = newCalls;
|
|
1516
|
+
}
|
|
1517
|
+
if (delta.reasoning_content) {
|
|
1518
|
+
accumulated.reasoningContent += delta.reasoning_content;
|
|
1519
|
+
deltaInfo.reasoningContent = delta.reasoning_content;
|
|
1388
1520
|
}
|
|
1389
|
-
|
|
1390
|
-
onProgress({ ...accumulated });
|
|
1521
|
+
onProgress({ ...accumulated }, deltaInfo);
|
|
1391
1522
|
} catch (e) {
|
|
1392
|
-
console.warn("stream parsing failed :", e,
|
|
1523
|
+
console.warn("stream parsing failed :", e, dataPayload);
|
|
1393
1524
|
}
|
|
1394
1525
|
}
|
|
1526
|
+
if (reachedDone) break;
|
|
1395
1527
|
}
|
|
1396
1528
|
onDone({
|
|
1397
1529
|
role: "assistant",
|
|
@@ -1453,12 +1585,14 @@
|
|
|
1453
1585
|
var toolCallingPlugin = createToolCallingPlugin();
|
|
1454
1586
|
/**
|
|
1455
1587
|
* 创建工具调用插件实例(工厂)。
|
|
1456
|
-
* @param {Object} [options] — { timeout
|
|
1588
|
+
* @param {Object} [options] — { timeout?, maxIterations? }
|
|
1589
|
+
* - 两者**默认都不限制**:不传就是"循环到没有工具调用为止 / 不超时"
|
|
1590
|
+
* - 想限制才传(这是使用者的选择,不是框架替你做的决定)
|
|
1457
1591
|
*/
|
|
1458
1592
|
function createToolCallingPlugin(options = {}) {
|
|
1459
1593
|
return {
|
|
1460
1594
|
name: "tool-calling",
|
|
1461
|
-
maxIterations: options.maxIterations
|
|
1595
|
+
maxIterations: options.maxIterations ?? Infinity,
|
|
1462
1596
|
_options: { ...options },
|
|
1463
1597
|
_tools: /* @__PURE__ */ new Map(),
|
|
1464
1598
|
_toolDefs: [],
|
|
@@ -1484,11 +1618,18 @@
|
|
|
1484
1618
|
* @param {Object} parameters - JSON Schema 参数定义(可选,默认为空对象)
|
|
1485
1619
|
* @returns {ChatService} 返回 chatService 实例,支持链式调用
|
|
1486
1620
|
*/
|
|
1487
|
-
|
|
1621
|
+
const registerTool = (name, description, executor, parameters = {}) => {
|
|
1488
1622
|
this._tools.set(name, {
|
|
1489
1623
|
executor,
|
|
1490
1624
|
description
|
|
1491
1625
|
});
|
|
1626
|
+
const properties = {};
|
|
1627
|
+
const requiredKeys = [];
|
|
1628
|
+
for (const [key, def] of Object.entries(parameters || {})) {
|
|
1629
|
+
const { required: isRequired, ...schema } = def || {};
|
|
1630
|
+
properties[key] = schema;
|
|
1631
|
+
if (isRequired) requiredKeys.push(key);
|
|
1632
|
+
}
|
|
1492
1633
|
const toolDefinition = {
|
|
1493
1634
|
type: "function",
|
|
1494
1635
|
function: {
|
|
@@ -1496,8 +1637,8 @@
|
|
|
1496
1637
|
description,
|
|
1497
1638
|
parameters: {
|
|
1498
1639
|
type: "object",
|
|
1499
|
-
properties
|
|
1500
|
-
required:
|
|
1640
|
+
properties,
|
|
1641
|
+
required: requiredKeys
|
|
1501
1642
|
}
|
|
1502
1643
|
}
|
|
1503
1644
|
};
|
|
@@ -1511,6 +1652,18 @@
|
|
|
1511
1652
|
ctx.result = await this._handleWithTools(ctx.result, ctx.isStream, ctx.onProgress || void 0, ctx.onDone || void 0);
|
|
1512
1653
|
}
|
|
1513
1654
|
});
|
|
1655
|
+
return {
|
|
1656
|
+
registerTool,
|
|
1657
|
+
tools: this._tools,
|
|
1658
|
+
uninstall: (c) => this.uninstall(c)
|
|
1659
|
+
};
|
|
1660
|
+
},
|
|
1661
|
+
uninstall(chat) {
|
|
1662
|
+
if (!chat) return chat;
|
|
1663
|
+
chat.unpipe?.("tool-calling-inject");
|
|
1664
|
+
chat.unpipe?.("tool-calling-loop");
|
|
1665
|
+
if (chat.plugins && chat.plugins["tool-calling"]) delete chat.plugins["tool-calling"];
|
|
1666
|
+
return chat;
|
|
1514
1667
|
},
|
|
1515
1668
|
async _handleWithTools(initialResponse, isStream, onProgress, onDone) {
|
|
1516
1669
|
const self = this;
|
|
@@ -1562,15 +1715,27 @@
|
|
|
1562
1715
|
};
|
|
1563
1716
|
}
|
|
1564
1717
|
try {
|
|
1565
|
-
const toolTimeout = self._options.timeout
|
|
1566
|
-
let
|
|
1567
|
-
if (
|
|
1718
|
+
const toolTimeout = self._options.timeout;
|
|
1719
|
+
let result;
|
|
1720
|
+
if (typeof toolTimeout === "number" && toolTimeout > 0) {
|
|
1721
|
+
const controller = new AbortController();
|
|
1568
1722
|
const timeoutErr = /* @__PURE__ */ new Error(`工具 ${toolName} 执行超时 (${toolTimeout}ms)`);
|
|
1569
1723
|
timeoutErr.name = "ToolTimeoutError";
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1724
|
+
let timer;
|
|
1725
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
1726
|
+
timer = setTimeout(() => {
|
|
1727
|
+
controller.abort();
|
|
1728
|
+
reject(timeoutErr);
|
|
1729
|
+
}, toolTimeout);
|
|
1730
|
+
});
|
|
1731
|
+
const execPromise = Promise.resolve().then(() => tool.executor(args, { signal: controller.signal }));
|
|
1732
|
+
execPromise.catch(() => {});
|
|
1733
|
+
try {
|
|
1734
|
+
result = await Promise.race([execPromise, timeoutPromise]);
|
|
1735
|
+
} finally {
|
|
1736
|
+
clearTimeout(timer);
|
|
1737
|
+
}
|
|
1738
|
+
} else result = await tool.executor(args);
|
|
1574
1739
|
chat.emit("tool-success", {
|
|
1575
1740
|
toolName,
|
|
1576
1741
|
result,
|
|
@@ -1753,8 +1918,8 @@
|
|
|
1753
1918
|
* @param {Object} capabilities — 能力标签对象(部分字段即可,未提供的取默认值)
|
|
1754
1919
|
* @returns {ChatService}
|
|
1755
1920
|
*/
|
|
1756
|
-
|
|
1757
|
-
if (!name || typeof name !== "string" || !name.trim()) throw new
|
|
1921
|
+
const registerModel = (name, capabilities = {}) => {
|
|
1922
|
+
if (!name || typeof name !== "string" || !name.trim()) throw new ValidationError("[model-registry] 模型名称必须是非空字符串");
|
|
1758
1923
|
const merged = {
|
|
1759
1924
|
...DEFAULT_CAPABILITIES,
|
|
1760
1925
|
...capabilities
|
|
@@ -1767,7 +1932,7 @@
|
|
|
1767
1932
|
* 列出所有已注册的模型名称
|
|
1768
1933
|
* @returns {Array<string>}
|
|
1769
1934
|
*/
|
|
1770
|
-
|
|
1935
|
+
const listModels = () => {
|
|
1771
1936
|
return [...this._registry.keys()];
|
|
1772
1937
|
};
|
|
1773
1938
|
this._syncCapabilities();
|
|
@@ -1790,6 +1955,17 @@
|
|
|
1790
1955
|
};
|
|
1791
1956
|
}
|
|
1792
1957
|
});
|
|
1958
|
+
return {
|
|
1959
|
+
registerModel,
|
|
1960
|
+
listModels,
|
|
1961
|
+
uninstall: (c) => this.uninstall(c)
|
|
1962
|
+
};
|
|
1963
|
+
},
|
|
1964
|
+
uninstall(chat = this.chat) {
|
|
1965
|
+
if (!chat) return chat;
|
|
1966
|
+
chat.unpipe?.("model-registry-caps");
|
|
1967
|
+
if (chat.plugins && chat.plugins["model-registry"]) delete chat.plugins["model-registry"];
|
|
1968
|
+
return chat;
|
|
1793
1969
|
},
|
|
1794
1970
|
_lookupCapabilities() {
|
|
1795
1971
|
const model = this.chat.config.model;
|
|
@@ -1803,6 +1979,137 @@
|
|
|
1803
1979
|
};
|
|
1804
1980
|
}
|
|
1805
1981
|
//#endregion
|
|
1982
|
+
//#region src/plugins/continuation.js
|
|
1983
|
+
/**
|
|
1984
|
+
* continuation 插件(v4.0)
|
|
1985
|
+
*
|
|
1986
|
+
* 「续写 / 临时消息」这套玩法住在这里 —— 核心不认识它,装上才有:
|
|
1987
|
+
* 1. 续写检测(替换核心的 autoContinue 步骤):底部若有带 prefix 标记的消息,就把结果合并回它
|
|
1988
|
+
* 2. ephemeral 过滤器:临时消息只有"位于底部连续段"时才发给 API
|
|
1989
|
+
* 3. continueLast / continueLastStream 两个 API
|
|
1990
|
+
* 4. uninstall():把自己装的东西全部还原(步骤、过滤器、方法)
|
|
1991
|
+
*
|
|
1992
|
+
* 用法:
|
|
1993
|
+
* import { createContinuationPlugin } from 'my-ai-chat-framework';
|
|
1994
|
+
* chat.use(createContinuationPlugin({ autoContinue: true }));
|
|
1995
|
+
*
|
|
1996
|
+
* // 模拟思维链:注入一条临时引导,结果合并回上一条 assistant,不留痕
|
|
1997
|
+
* chat.messages.addOnceAssistant('(内心:他肯定又要熬夜)');
|
|
1998
|
+
* await chat.stream('在吗');
|
|
1999
|
+
*/
|
|
2000
|
+
/**
|
|
2001
|
+
* ephemeral 规则:只有底部连续的临时消息保留,其余丢弃(与 v3.x 核心内置行为一致)。
|
|
2002
|
+
* 导出以便使用者直接注册到 MessageFormatter(不使用插件时也能用这条规则)。
|
|
2003
|
+
*/
|
|
2004
|
+
function ephemeralFilterRule(msg, { index, messages }) {
|
|
2005
|
+
if (!msg._ephemeral) return msg;
|
|
2006
|
+
let start = messages.length;
|
|
2007
|
+
for (let i = messages.length - 1; i >= 0; i--) if (messages[i]._ephemeral) start = i;
|
|
2008
|
+
else break;
|
|
2009
|
+
return index >= start ? msg : null;
|
|
2010
|
+
}
|
|
2011
|
+
/**
|
|
2012
|
+
* 找续写目标:最后一条必须是 assistant 或临时消息(否则抛错)。
|
|
2013
|
+
* @param {object} chat ChatService 实例
|
|
2014
|
+
*/
|
|
2015
|
+
function prepareContinue(chat) {
|
|
2016
|
+
const msgs = chat.messages.getAll();
|
|
2017
|
+
const last = msgs[msgs.length - 1];
|
|
2018
|
+
if (last && (last.role === "assistant" || last._ephemeral)) return last;
|
|
2019
|
+
throw new ValidationError("最后一条消息不是 assistant,无法续写");
|
|
2020
|
+
}
|
|
2021
|
+
/** 续写结束后的"转正" */
|
|
2022
|
+
function finalize(chat, targetId) {
|
|
2023
|
+
chat.messages.update(targetId, {
|
|
2024
|
+
_complete: true,
|
|
2025
|
+
prefix: void 0,
|
|
2026
|
+
_ephemeral: false
|
|
2027
|
+
});
|
|
2028
|
+
}
|
|
2029
|
+
/**
|
|
2030
|
+
* 创建续写插件实例(工厂)
|
|
2031
|
+
* @param {Object} [options] — { autoContinue?: boolean } 是否自动识别底部 prefix 消息并合并(默认 false)
|
|
2032
|
+
*/
|
|
2033
|
+
function createContinuationPlugin(options = {}) {
|
|
2034
|
+
return {
|
|
2035
|
+
name: "continuation",
|
|
2036
|
+
_options: {
|
|
2037
|
+
autoContinue: false,
|
|
2038
|
+
...options
|
|
2039
|
+
},
|
|
2040
|
+
_prevEphemeralFilter: void 0,
|
|
2041
|
+
_installedOn: null,
|
|
2042
|
+
install(chat) {
|
|
2043
|
+
this._installedOn = chat;
|
|
2044
|
+
if (typeof chat.registerConfigKeys === "function") chat.registerConfigKeys({ autoContinue: (v) => typeof v === "boolean" });
|
|
2045
|
+
chat.replaceStage("autoContinue", (ctx) => {
|
|
2046
|
+
if (!(chat.config.autoContinue ?? this._options.autoContinue)) return;
|
|
2047
|
+
if (ctx.mergeToEntry) return;
|
|
2048
|
+
const last = ctx.messages.getLast();
|
|
2049
|
+
if (last && last.prefix && (last.role === "assistant" || last._ephemeral)) ctx.mergeToEntry = last.id;
|
|
2050
|
+
});
|
|
2051
|
+
this._prevEphemeralFilter = MessageFormatter.registerFilter("ephemeral", ephemeralFilterRule, { priority: 100 });
|
|
2052
|
+
const continueLast = async () => {
|
|
2053
|
+
const target = prepareContinue(chat);
|
|
2054
|
+
chat.messages.update(target.id, { prefix: true });
|
|
2055
|
+
try {
|
|
2056
|
+
await chat.sendExisting(void 0, { mergeToEntry: target.id });
|
|
2057
|
+
finalize(chat, target.id);
|
|
2058
|
+
return target;
|
|
2059
|
+
} catch (err) {
|
|
2060
|
+
finalize(chat, target.id);
|
|
2061
|
+
if (err.name === "AbortError") {
|
|
2062
|
+
chat.emit("aborted", { timestamp: Date.now() });
|
|
2063
|
+
return target;
|
|
2064
|
+
}
|
|
2065
|
+
throw err;
|
|
2066
|
+
}
|
|
2067
|
+
};
|
|
2068
|
+
const continueLastStream = async (onProgress, onDone) => {
|
|
2069
|
+
const target = prepareContinue(chat);
|
|
2070
|
+
chat.messages.update(target.id, { prefix: true });
|
|
2071
|
+
const baseLen = (target.content || "").length;
|
|
2072
|
+
try {
|
|
2073
|
+
await chat.sendExistingStream(void 0, { mergeToEntry: target.id }, (chunk) => {
|
|
2074
|
+
if (onProgress) onProgress({
|
|
2075
|
+
...chunk,
|
|
2076
|
+
content: (chunk.content || "").slice(baseLen)
|
|
2077
|
+
});
|
|
2078
|
+
}, (final) => {
|
|
2079
|
+
if (onDone) onDone(final);
|
|
2080
|
+
});
|
|
2081
|
+
finalize(chat, target.id);
|
|
2082
|
+
return target;
|
|
2083
|
+
} catch (err) {
|
|
2084
|
+
finalize(chat, target.id);
|
|
2085
|
+
if (err.name === "AbortError") {
|
|
2086
|
+
chat.emit("aborted", { timestamp: Date.now() });
|
|
2087
|
+
return target;
|
|
2088
|
+
}
|
|
2089
|
+
throw err;
|
|
2090
|
+
}
|
|
2091
|
+
};
|
|
2092
|
+
return {
|
|
2093
|
+
continueLast,
|
|
2094
|
+
continueLastStream,
|
|
2095
|
+
uninstall: () => this.uninstall(chat)
|
|
2096
|
+
};
|
|
2097
|
+
},
|
|
2098
|
+
uninstall(chat = this._installedOn) {
|
|
2099
|
+
if (!chat) return chat;
|
|
2100
|
+
if (typeof chat.restoreStage === "function") chat.restoreStage("autoContinue");
|
|
2101
|
+
if (this._prevEphemeralFilter) MessageFormatter.registerFilter("ephemeral", this._prevEphemeralFilter, { priority: 100 });
|
|
2102
|
+
else MessageFormatter.unregisterFilter("ephemeral");
|
|
2103
|
+
if (chat.plugins && chat.plugins["continuation"]) delete chat.plugins["continuation"];
|
|
2104
|
+
if (typeof chat.unregisterConfigKeys === "function") chat.unregisterConfigKeys(["autoContinue"]);
|
|
2105
|
+
this._installedOn = null;
|
|
2106
|
+
return chat;
|
|
2107
|
+
}
|
|
2108
|
+
};
|
|
2109
|
+
}
|
|
2110
|
+
/** 兼容用单例(多实例场景请用工厂) */
|
|
2111
|
+
var continuationPlugin = createContinuationPlugin();
|
|
2112
|
+
//#endregion
|
|
1806
2113
|
exports.APIError = APIError;
|
|
1807
2114
|
exports.ChatService = ChatService;
|
|
1808
2115
|
exports.ConfigurationError = ConfigurationError;
|
|
@@ -1813,8 +2120,11 @@
|
|
|
1813
2120
|
exports.ParsingError = ParsingError;
|
|
1814
2121
|
exports.Pipeline = Pipeline;
|
|
1815
2122
|
exports.SystemPromptStore = SystemPromptStore;
|
|
2123
|
+
exports.ValidationError = ValidationError;
|
|
1816
2124
|
exports.assembleMessages = assembleMessages;
|
|
1817
2125
|
exports.assertAdapter = assertAdapter;
|
|
2126
|
+
exports.continuationPlugin = continuationPlugin;
|
|
2127
|
+
exports.createContinuationPlugin = createContinuationPlugin;
|
|
1818
2128
|
exports.createModelRegistryPlugin = createModelRegistryPlugin;
|
|
1819
2129
|
exports.createOpenAIAdapter = createOpenAIAdapter;
|
|
1820
2130
|
exports.createToolCallingPlugin = createToolCallingPlugin;
|