my-ai-chat-framework 2.7.0 → 3.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.
@@ -140,6 +140,25 @@ var MessageStore = class {
140
140
  return [];
141
141
  }
142
142
  /**
143
+ * 撤回到指定消息 id 的上一个用户消息,删除两者之间的所有消息
144
+ * 适用于重发
145
+ * @param {string} id — 目标消息 id
146
+ * @returns {Array} 被删除的消息列表
147
+ */
148
+ undoToPreviousUser(id) {
149
+ let targetIndex = -1;
150
+ let previousUserIndex = -1;
151
+ for (let i = this._messages.length - 1; i >= 0; i--) {
152
+ if (this._messages[i].id === id) targetIndex = i;
153
+ if (targetIndex >= 0 && this._messages[i].role === "user") {
154
+ previousUserIndex = i;
155
+ break;
156
+ }
157
+ }
158
+ if (targetIndex >= 0 && previousUserIndex >= 0) return this._messages.splice(previousUserIndex + 1, targetIndex - previousUserIndex);
159
+ return [];
160
+ }
161
+ /**
143
162
  * 更新消息:按 id 查找并合并 changes,不新增消息
144
163
  * @param {string} id — 消息 id
145
164
  * @param {Object} changes — 要合并的字段
@@ -309,7 +328,85 @@ var ParsingError = class extends Error {
309
328
  }
310
329
  };
311
330
  //#endregion
331
+ //#region src/core/Pipeline.js
332
+ /**
333
+ * Pipeline —— 极简顺序管道(v3.0 提案 · 阶段 1)
334
+ *
335
+ * 三个概念(对照 pipeline-demo.html):
336
+ * - 小车 ctx —— 本次请求的全部"状态",车间之间只通过它交接
337
+ * - 车间 stage —— { name, run(ctx) },只干一件事,不认别的车间
338
+ * - 调度 run() —— for + await,顺序执行(线性,无"回程";洋葱能力留给阶段 2 的 after 车间)
339
+ */
340
+ var Pipeline = class {
341
+ constructor() {
342
+ this._stages = [];
343
+ }
344
+ /**
345
+ * 注册一个车间(追加到末尾)。
346
+ * @param {{name: string, run: (ctx: object) => void | Promise<void>}} stage
347
+ * @returns {Pipeline} this,支持链式
348
+ */
349
+ register(stage) {
350
+ if (!stage || typeof stage.run !== "function") throw new TypeError("Pipeline.register: stage 需要 { name, run(ctx) },运行 run 必须是函数");
351
+ this._stages.push(stage);
352
+ return this;
353
+ }
354
+ /** 按名字移除车间(移除不存在的名字是安全的) */
355
+ unregister(name) {
356
+ this._stages = this._stages.filter((s) => s.name !== name);
357
+ return this;
358
+ }
359
+ /** 列出当前车间名(调试用) */
360
+ names() {
361
+ return this._stages.map((s) => s.name);
362
+ }
363
+ /** 把小车开过所有车间;返回 ctx(车间可改 ctx 上的字段) */
364
+ async run(ctx) {
365
+ for (const stage of this._stages) await stage.run(ctx);
366
+ return ctx;
367
+ }
368
+ };
369
+ //#endregion
312
370
  //#region src/core/ChatService.js
371
+ /**
372
+ * @typedef {Object} ChatAdapter 适配器协议(v3.0:写同级适配器只需实现这 4+1 个方法)
373
+ * @property {Function} buildRequest (messages, config, systemPrompts) => requestBody
374
+ * @property {Function} send (body, config, opts) => Promise<apiResponse>
375
+ * @property {Function} stream (body, config, onProgress, onDone, opts) => Promise<void>
376
+ * @property {Function} parseResponse (apiResponse) => assistantMessage
377
+ * @property {Function} [getRequestDefaults] () => 默认请求参数(可选)
378
+ * @property {Function} [install] (chatService, options) 插件式安装(可选)
379
+ */
380
+ /**
381
+ * @typedef {Object} PipelineContext 管道小车 ctx(public pipe 车间可见/可改的全部字段)
382
+ * @property {Object} options 原始请求选项
383
+ * @property {*} userInput 用户输入(可为 undefined)
384
+ * @property {boolean} addUser 是否已新增/将新增用户消息
385
+ * @property {MessageStore} messages 消息列表(可读可改:push / unshift / update)
386
+ * @property {SystemPromptStore} systemPrompts 系统提示词(可读可改)
387
+ * @property {Object} config 合并后的请求级配置(prepareInput 后生效)
388
+ * @property {boolean} isStream 本次是否流式
389
+ * @property {Function|null} onProgress 流式进度回调
390
+ * @property {Function|null} onDone 流式完成回调
391
+ * @property {string|null} mergeToEntry 续写目标 id(null = 普通发送)
392
+ * @property {*} body 请求体(buildRequest 后产出)
393
+ * @property {*} result 最终结果(send 后产出)
394
+ */
395
+ /**
396
+ * @typedef {Object} PipeStage 车间(v3.0 公开扩展单元)
397
+ * @property {string} name 车间名(唯一,不能与内部车间重名)
398
+ * @property {string} [phase] 'beforeSend'(默认)| 'afterSend'
399
+ * @property {Function} run (ctx: PipelineContext) => void | Promise<void>
400
+ */
401
+ var SESSION_CONFIG_KEYS = [
402
+ "model",
403
+ "temperature",
404
+ "maxTokens",
405
+ "modelParams",
406
+ "system",
407
+ "retry",
408
+ "ephemeralContinue"
409
+ ];
313
410
  var ChatService = class extends EventEmitter {
314
411
  constructor(config = {}) {
315
412
  super();
@@ -323,17 +420,106 @@ var ChatService = class extends EventEmitter {
323
420
  this._processResponse = null;
324
421
  const model = this.config.model || this.config.modelParams?.model;
325
422
  if (!model || typeof model !== "string" || !model.trim()) throw new ConfigurationError("缺少 model 配置");
326
- const temperature = this.config.modelParams?.temperature ?? this.config.temperature;
423
+ this._assertValidConfig(this.config);
424
+ if (typeof config.system === "string" && config.system.trim()) this.systemPrompts.set(config.system);
425
+ if (config.adapter) this.setAdapter(config.adapter);
426
+ this._userStages = [];
427
+ this._pipeline = this._buildPipeline();
428
+ }
429
+ /**
430
+ * 校验配置中的可校验字段(model 必填由构造器负责,这里只校验存在性)
431
+ * @throws {ConfigurationError}
432
+ */
433
+ _assertValidConfig(cfg) {
434
+ const temperature = cfg.modelParams?.temperature ?? cfg.temperature;
327
435
  if (temperature !== void 0 && (typeof temperature !== "number" || temperature < 0 || temperature > 2)) throw new ConfigurationError(`temperature 必须在 0-2 之间,当前值: ${temperature}`);
328
- const maxTokens = this.config.modelParams?.maxTokens ?? this.config.maxTokens;
436
+ const maxTokens = cfg.modelParams?.maxTokens ?? cfg.maxTokens;
329
437
  if (maxTokens !== void 0 && (typeof maxTokens !== "number" || maxTokens < 1 || !Number.isInteger(maxTokens))) throw new ConfigurationError(`maxTokens 必须为正整数,当前值: ${maxTokens}`);
330
- if (typeof config.system === "string" && config.system.trim()) this.systemPrompts.set(config.system);
438
+ const model = cfg.model ?? cfg.modelParams?.model;
439
+ if (model !== void 0 && (typeof model !== "string" || !model.trim())) throw new ConfigurationError("model 必须是非空字符串");
440
+ }
441
+ use(plugin, options = {}) {
442
+ plugin.install(this, options);
443
+ return this;
331
444
  }
332
- use(plugin) {
333
- plugin.install(this);
445
+ /**
446
+ * 往请求管道里挂一个"车间"。
447
+ * - phase 'beforeSend'(默认):在内部 beforeRequest 钩子之后、构建请求体之前执行
448
+ * - phase 'afterSend' :在内部发送(流式/非流式)完成、结果落位之后执行(可改 ctx.result)
449
+ * 不注册任何车间 = 行为与版本 2.8.x 完全一致。
450
+ * @param {PipeStage} stage
451
+ * @returns {ChatService} this
452
+ * @throws {ConfigurationError} 参数非法 / 名字被占用 / 与内部车间重名
453
+ */
454
+ pipe(stage) {
455
+ if (!stage || typeof stage !== "object" || typeof stage.run !== "function") throw new ConfigurationError("pipe: 需要 { name, phase?, run(ctx) },run 必须是函数");
456
+ if (typeof stage.name !== "string" || !stage.name.trim()) throw new ConfigurationError("pipe: 需要 name(车间名,非空字符串)");
457
+ if ([
458
+ "prepareInput",
459
+ "beforeSend",
460
+ "autoContinue",
461
+ "buildRequest",
462
+ "send"
463
+ ].includes(stage.name)) throw new ConfigurationError(`pipe: "${stage.name}" 是内部车间名,请换一个名字`);
464
+ if (this._userStages.some((s) => s.name === stage.name)) throw new ConfigurationError(`pipe: 车间 "${stage.name}" 已存在,请先 chat.unpipe("${stage.name}")`);
465
+ const phase = stage.phase === "afterSend" ? "afterSend" : "beforeSend";
466
+ this._userStages.push({
467
+ name: stage.name,
468
+ phase,
469
+ run: stage.run
470
+ });
471
+ this._rebuildPipeline();
334
472
  return this;
335
473
  }
474
+ /** 移除一个用户车间(移除不存在的名字是安全的)。@returns {ChatService} this */
475
+ unpipe(name) {
476
+ const before = this._userStages.length;
477
+ this._userStages = this._userStages.filter((s) => s.name !== name);
478
+ if (this._userStages.length !== before) this._rebuildPipeline();
479
+ return this;
480
+ }
481
+ /** 查看当前管道里所有车间名(调试/说明用,含内部车间) */
482
+ get pipelineStages() {
483
+ return this._pipeline.names();
484
+ }
485
+ /** 重建内部管道:内部 5 车间 + 用户车间按 phase 插入 */
486
+ _buildPipeline() {
487
+ const p = new Pipeline();
488
+ p.register({
489
+ name: "prepareInput",
490
+ run: (ctx) => this._stagePrepareInput(ctx)
491
+ });
492
+ p.register({
493
+ name: "beforeSend",
494
+ run: (ctx) => this._stageBeforeSend(ctx)
495
+ });
496
+ for (const s of this._userStages) if (s.phase === "beforeSend") p.register({
497
+ name: s.name,
498
+ run: (ctx) => s.run(ctx)
499
+ });
500
+ p.register({
501
+ name: "autoContinue",
502
+ run: (ctx) => this._stageAutoContinue(ctx)
503
+ });
504
+ p.register({
505
+ name: "buildRequest",
506
+ run: (ctx) => this._stageBuildRequest(ctx)
507
+ });
508
+ p.register({
509
+ name: "send",
510
+ run: (ctx) => this._stageSend(ctx)
511
+ });
512
+ for (const s of this._userStages) if (s.phase === "afterSend") p.register({
513
+ name: s.name,
514
+ run: (ctx) => s.run(ctx)
515
+ });
516
+ return p;
517
+ }
518
+ _rebuildPipeline() {
519
+ this._pipeline = this._buildPipeline();
520
+ }
336
521
  setAdapter(adapter) {
522
+ assertAdapter(adapter);
337
523
  this._adapter = adapter;
338
524
  }
339
525
  abort() {
@@ -354,17 +540,24 @@ var ChatService = class extends EventEmitter {
354
540
  this.messages.update(target.id, {
355
541
  _complete: true,
356
542
  prefix: void 0,
357
- _ephemeral: void 0
543
+ _ephemeral: false
358
544
  });
359
545
  return target;
360
546
  } catch (err) {
361
- this.messages.update(target.id, { prefix: void 0 });
547
+ this.messages.update(target.id, {
548
+ _complete: true,
549
+ prefix: void 0,
550
+ _ephemeral: false
551
+ });
552
+ if (err.name === "AbortError") {
553
+ this.emit("aborted", { timestamp: Date.now() });
554
+ return target;
555
+ }
362
556
  throw err;
363
557
  }
364
558
  }
365
559
  async continueLastStream(onProgress, onDone) {
366
560
  const target = this._prepareContinue();
367
- console.log("[DEBUG] continueLastStream target.id:", target.id);
368
561
  this.messages.update(target.id, { prefix: true });
369
562
  const baseLen = (target.content || "").length;
370
563
  try {
@@ -385,11 +578,19 @@ var ChatService = class extends EventEmitter {
385
578
  this.messages.update(target.id, {
386
579
  _complete: true,
387
580
  prefix: void 0,
388
- _ephemeral: void 0
581
+ _ephemeral: false
389
582
  });
390
583
  return target;
391
584
  } catch (err) {
392
- this.messages.update(target.id, { prefix: void 0 });
585
+ this.messages.update(target.id, {
586
+ _complete: true,
587
+ prefix: void 0,
588
+ _ephemeral: false
589
+ });
590
+ if (err.name === "AbortError") {
591
+ this.emit("aborted", { timestamp: Date.now() });
592
+ return target;
593
+ }
393
594
  throw err;
394
595
  }
395
596
  }
@@ -399,7 +600,20 @@ var ChatService = class extends EventEmitter {
399
600
  if (last && (last.role === "assistant" || last._ephemeral)) return last;
400
601
  throw new Error("最后一条消息不是 assistant,无法续写");
401
602
  }
603
+ /**
604
+ * 运行时修改配置(仅会话层字段)。
605
+ *
606
+ * 白名单:model / temperature / maxTokens / modelParams / system / retry / ephemeralContinue。
607
+ * - 运输层(apiKey/baseUrl/headers):归适配器,要改就换适配器实例
608
+ * - 请求参数默认值:走适配器工厂 options 或请求级覆盖(chat.send(x, params))
609
+ * - 能力表:归 modelRegistry 插件
610
+ *
611
+ * 与构造器一样经过校验,不能注入非法值。
612
+ * @throws {ConfigurationError} 非白名单字段或非法值
613
+ */
402
614
  updateConfig(partial) {
615
+ for (const key of Object.keys(partial)) if (!SESSION_CONFIG_KEYS.includes(key)) throw new ConfigurationError(`updateConfig 不支持修改 "${key}"。允许的会话层字段: ${SESSION_CONFIG_KEYS.join(", ")}`);
616
+ this._assertValidConfig(partial);
403
617
  Object.assign(this.config, partial);
404
618
  if (typeof partial.system === "string") this.systemPrompts.set(partial.system);
405
619
  this.emit("config-updated", {
@@ -407,36 +621,72 @@ var ChatService = class extends EventEmitter {
407
621
  timestamp: Date.now()
408
622
  });
409
623
  }
410
- async _request(options) {
411
- let { userInput, addUser = true, isStream = false, onProgress, onDone, mergeToEntry } = options;
412
- this.emit("sending", {
413
- addUser,
414
- userInput,
415
- timestamp: Date.now()
416
- });
417
- this._addUserMessage(userInput, addUser);
418
- await this._hooks.emitAsync("beforeRequest", {
419
- messages: this.messages,
420
- config: this.config,
421
- options
422
- });
423
- if (this.config.ephemeralContinue && !mergeToEntry) {
424
- const msgs = this.messages.getAll();
425
- const last = msgs[msgs.length - 1];
426
- if (last && last.prefix && (last.role === "assistant" || last._ephemeral)) mergeToEntry = last.id;
624
+ /**
625
+ * 合并请求参数(近者优先):
626
+ * 适配器默认(getRequestDefaults) ← 会话配置(this.config) ← 本次覆盖(params)
627
+ * modelParams 深层合并;平铺便捷键(temperature/maxTokens/reasoningEffort)自动折叠进 modelParams
628
+ */
629
+ _mergeRequestConfig(params = {}) {
630
+ const adapterDefaults = this._adapter?.getRequestDefaults?.() || {};
631
+ const requestConfig = {
632
+ ...adapterDefaults,
633
+ ...this.config,
634
+ ...params
635
+ };
636
+ const modelParams = {
637
+ ...adapterDefaults.modelParams || {},
638
+ ...this.config.modelParams || {},
639
+ ...params.modelParams || {}
640
+ };
641
+ for (const key of [
642
+ "temperature",
643
+ "maxTokens",
644
+ "reasoningEffort"
645
+ ]) {
646
+ const value = params[key] ?? this.config[key] ?? adapterDefaults[key];
647
+ if (value !== void 0) modelParams[key] = value;
427
648
  }
428
- const body = this._adapter.buildRequest(this.messages.getAll(), this.config, this.systemPrompts.getEnabled());
429
- const retryCfg = this.config.retry || {};
649
+ requestConfig.modelParams = modelParams;
650
+ return requestConfig;
651
+ }
652
+ /**
653
+ * 内部调度(v3.0 提案 · 阶段 1):造一辆小车 ctx,开过内部管道,返回结果。
654
+ * 行为与旧版 _request 完全一致,只是把固定线拆成了车间(见 _stage* 方法)。
655
+ *
656
+ * ctx 字段(车间可见):
657
+ * options —— 原始请求选项
658
+ * userInput —— 用户输入
659
+ * addUser —— 是否新增用户消息
660
+ * messages —— MessageStore(可读可改)
661
+ * systemPrompts —— SystemPromptStore
662
+ * config —— 合并后的请求级配置(prepareInput 车间产出)
663
+ * isStream —— 本次是否流式
664
+ * onProgress —— 流式进度回调(可为 null)
665
+ * onDone —— 流式完成回调(可为 null)
666
+ * mergeToEntry —— 续写目标 id(空 = 普通发送)
667
+ * body —— adapter 构建出的请求体(buildRequest 车间产出)
668
+ * result —— 最终结果(send 车间产出)
669
+ */
670
+ async _request(options = {}) {
671
+ const ctx = {
672
+ options,
673
+ userInput: options.userInput,
674
+ addUser: options.addUser !== false,
675
+ messages: this.messages,
676
+ systemPrompts: this.systemPrompts,
677
+ config: null,
678
+ isStream: !!options.isStream,
679
+ onProgress: options.onProgress || null,
680
+ onDone: options.onDone || null,
681
+ mergeToEntry: options.mergeToEntry || null,
682
+ body: null,
683
+ result: null
684
+ };
430
685
  try {
431
- return await this._withRetry(body, {
432
- isStream,
433
- onProgress,
434
- onDone,
435
- mergeToEntry,
436
- maxRetries: retryCfg.maxRetries ?? 0,
437
- retryDelay: retryCfg.retryDelay ?? 1e3
438
- });
686
+ await this._pipeline.run(ctx);
687
+ return ctx.result;
439
688
  } catch (error) {
689
+ if (error.name === "AbortError") throw error;
440
690
  this.emit("error", {
441
691
  error,
442
692
  timestamp: Date.now()
@@ -444,6 +694,49 @@ var ChatService = class extends EventEmitter {
444
694
  throw error;
445
695
  }
446
696
  }
697
+ /** 车间 1:加用户消息 → 合并请求参数 → 广播 sending */
698
+ async _stagePrepareInput(ctx) {
699
+ this._addUserMessage(ctx.userInput, ctx.addUser);
700
+ ctx.config = this._mergeRequestConfig(ctx.options.params);
701
+ this.emit("sending", {
702
+ addUser: ctx.addUser,
703
+ userInput: ctx.userInput,
704
+ timestamp: Date.now()
705
+ });
706
+ }
707
+ /** 车间 2:发送前钩子(兼容桥:事件钩子照发;阶段 4 会迁到 beforeSend 公开位置) */
708
+ async _stageBeforeSend(ctx) {
709
+ await this._hooks.emitAsync("beforeRequest", {
710
+ messages: ctx.messages,
711
+ config: ctx.config,
712
+ options: ctx.options
713
+ });
714
+ }
715
+ /** 车间 3:自动续写检测(ephemeralContinue 时,底部若有 prefix 消息则续写) */
716
+ async _stageAutoContinue(ctx) {
717
+ if (this.config.ephemeralContinue && !ctx.mergeToEntry) {
718
+ const msgs = ctx.messages.getAll();
719
+ const last = msgs[msgs.length - 1];
720
+ if (last && last.prefix && (last.role === "assistant" || last._ephemeral)) ctx.mergeToEntry = last.id;
721
+ }
722
+ }
723
+ /** 车间 4:构建请求体 */
724
+ async _stageBuildRequest(ctx) {
725
+ ctx.body = this._adapter.buildRequest(ctx.messages.getAll(), ctx.config, ctx.systemPrompts.getEnabled());
726
+ }
727
+ /** 车间 5:发送(流式/非流式 + 重试 + 占位消息 + _processResponse 工具钩子) */
728
+ async _stageSend(ctx) {
729
+ const retryCfg = this.config.retry || {};
730
+ ctx.result = await this._withRetry(ctx.body, {
731
+ isStream: ctx.isStream,
732
+ onProgress: ctx.onProgress,
733
+ onDone: ctx.onDone,
734
+ mergeToEntry: ctx.mergeToEntry,
735
+ config: ctx.config,
736
+ maxRetries: retryCfg.maxRetries ?? 0,
737
+ retryDelay: retryCfg.retryDelay ?? 1e3
738
+ });
739
+ }
447
740
  _addUserMessage(userInput, addUser) {
448
741
  if (addUser && userInput !== void 0) {
449
742
  const msg = typeof userInput === "string" ? {
@@ -457,7 +750,7 @@ var ChatService = class extends EventEmitter {
457
750
  }
458
751
  }
459
752
  /** retry 循环,占位消息只 push 一次 */
460
- async _withRetry(body, { isStream, onProgress, onDone, mergeToEntry, maxRetries, retryDelay }) {
753
+ async _withRetry(body, { isStream, onProgress, onDone, mergeToEntry, config, maxRetries, retryDelay }) {
461
754
  let lastError = null;
462
755
  const adapterOptions = { signal: this._abortController?.signal };
463
756
  let placeholder = null;
@@ -493,10 +786,11 @@ var ChatService = class extends EventEmitter {
493
786
  base,
494
787
  mergeToEntry,
495
788
  onProgress,
496
- onDone
789
+ onDone,
790
+ config
497
791
  });
498
792
  else {
499
- const resp = await this._adapter.send(body, this.config, adapterOptions);
793
+ const resp = await this._adapter.send(body, config, adapterOptions);
500
794
  let result = this._handleResult(this._adapter.parseResponse(resp), mergeToEntry);
501
795
  if (this._processResponse && !mergeToEntry) result = await this._processResponse(result, { isStream: false });
502
796
  return result;
@@ -515,12 +809,21 @@ var ChatService = class extends EventEmitter {
515
809
  this._isGenerating = false;
516
810
  throw lastError;
517
811
  }
518
- /** 流式:ChatService 维护占位,adapter 只管解析 */
519
- async _stream(body, adapterOptions, { placeholder, base, mergeToEntry, onProgress, onDone }) {
812
+ /**
813
+ * 流式处理:ChatService 维护占位消息,adapter 只负责解析 SSE
814
+ *
815
+ * 参数说明:
816
+ * - placeholder:流式期间实时更新的目标消息(续写时 = target 本身,普通发送时 = 新建的空消息)
817
+ * - base:续写时保存的旧内容快照,用于和 API 新内容拼接
818
+ * - mergeToEntry:续写目标的 id,有值时走续写逻辑
819
+ * - onProgress:外部回调,每收到一个 chunk 触发
820
+ * - onDone:流结束回调
821
+ */
822
+ async _stream(body, adapterOptions, { placeholder, base, mergeToEntry, onProgress, onDone, config }) {
520
823
  let finalMsg = null;
521
- await this._adapter.stream(body, this.config, (snap) => {
522
- placeholder.content = snap.content || "";
523
- if (snap.reasoningContent) placeholder.reasoningContent = snap.reasoningContent;
824
+ await this._adapter.stream(body, config, (snap) => {
825
+ placeholder.content = mergeToEntry && base ? base.content + (snap.content || "") : snap.content || "";
826
+ if (snap.reasoningContent) placeholder.reasoningContent = mergeToEntry && base ? base.reasoning + snap.reasoningContent : snap.reasoningContent;
524
827
  if (snap.toolCalls) placeholder.toolCalls = [...snap.toolCalls];
525
828
  this.emit("stream-progress", snap);
526
829
  if (onProgress) onProgress(snap);
@@ -534,7 +837,9 @@ var ChatService = class extends EventEmitter {
534
837
  if (mergeToEntry && base) {
535
838
  const changes = {
536
839
  content: base.content + (final.content || ""),
537
- _complete: true
840
+ _complete: true,
841
+ _ephemeral: false,
842
+ prefix: void 0
538
843
  };
539
844
  if (final.reasoningContent) changes.reasoningContent = base.reasoning + final.reasoningContent;
540
845
  if (final.toolCalls) changes.toolCalls = final.toolCalls;
@@ -561,7 +866,9 @@ var ChatService = class extends EventEmitter {
561
866
  }
562
867
  const changes = {
563
868
  content: (entry.content || "") + (assistantMsg.content || ""),
564
- _complete: true
869
+ _complete: true,
870
+ _ephemeral: false,
871
+ prefix: void 0
565
872
  };
566
873
  if (assistantMsg.reasoningContent) changes.reasoningContent = (entry.reasoningContent || "") + assistantMsg.reasoningContent;
567
874
  if (assistantMsg.toolCalls) changes.toolCalls = assistantMsg.toolCalls;
@@ -574,20 +881,44 @@ var ChatService = class extends EventEmitter {
574
881
  this.emit("message", assistantMsg);
575
882
  return assistantMsg;
576
883
  }
577
- async send(userInput) {
884
+ /**
885
+ * 发送消息。
886
+ * @param {string|Object} userInput - 用户输入
887
+ * @param {Object} [params] - 请求级参数覆盖(model/temperature/modelParams 等,仅本次生效)
888
+ */
889
+ async send(userInput, params) {
578
890
  this._abortController = new AbortController();
579
891
  try {
580
892
  return await this._request({
581
893
  userInput,
582
894
  addUser: true,
583
- isStream: false
895
+ isStream: false,
896
+ params
584
897
  });
898
+ } catch (err) {
899
+ if (err.name === "AbortError") {
900
+ this.emit("aborted", { timestamp: Date.now() });
901
+ return;
902
+ }
903
+ throw err;
585
904
  } finally {
586
905
  this._abortController = null;
587
906
  this._isGenerating = false;
588
907
  }
589
908
  }
590
- async stream(userInput, onProgress, onDone) {
909
+ /**
910
+ * 流式发送。
911
+ * @param {string|Object} userInput - 用户输入
912
+ * @param {Object} [params] - 请求级参数覆盖(仅本次生效)
913
+ * @param {Function} [onProgress] - 流式进度回调
914
+ * @param {Function} [onDone] - 流式完成回调
915
+ */
916
+ async stream(userInput, params, onProgress, onDone) {
917
+ if (typeof params === "function") {
918
+ onDone = onProgress;
919
+ onProgress = params;
920
+ params = void 0;
921
+ }
591
922
  this._abortController = new AbortController();
592
923
  try {
593
924
  return await this._request({
@@ -595,40 +926,92 @@ var ChatService = class extends EventEmitter {
595
926
  addUser: true,
596
927
  isStream: true,
597
928
  onProgress,
598
- onDone
929
+ onDone,
930
+ params
599
931
  });
932
+ } catch (err) {
933
+ if (err.name === "AbortError") {
934
+ this.emit("aborted", { timestamp: Date.now() });
935
+ return;
936
+ }
937
+ throw err;
600
938
  } finally {
601
939
  this._abortController = null;
602
940
  this._isGenerating = false;
603
941
  }
604
942
  }
605
- async sendExisting() {
943
+ /**
944
+ * 重发当前消息(不加用户消息)。
945
+ * @param {Object} [params] - 请求级参数覆盖(仅本次生效)
946
+ */
947
+ async sendExisting(params) {
606
948
  this._abortController = new AbortController();
607
949
  try {
608
950
  return await this._request({
609
951
  addUser: false,
610
- isStream: false
952
+ isStream: false,
953
+ params
611
954
  });
955
+ } catch (err) {
956
+ if (err.name === "AbortError") {
957
+ this.emit("aborted", { timestamp: Date.now() });
958
+ return;
959
+ }
960
+ throw err;
612
961
  } finally {
613
962
  this._abortController = null;
614
963
  this._isGenerating = false;
615
964
  }
616
965
  }
617
- async sendExistingStream(onProgress, onDone) {
966
+ /**
967
+ * 流式重发(不加用户消息)。
968
+ * @param {Object} [params] - 请求级参数覆盖(仅本次生效)
969
+ * @param {Function} [onProgress] - 流式进度回调
970
+ * @param {Function} [onDone] - 流式完成回调
971
+ */
972
+ async sendExistingStream(params, onProgress, onDone) {
973
+ if (typeof params === "function") {
974
+ onDone = onProgress;
975
+ onProgress = params;
976
+ params = void 0;
977
+ }
618
978
  this._abortController = new AbortController();
619
979
  try {
620
980
  return await this._request({
621
981
  addUser: false,
622
982
  isStream: true,
623
983
  onProgress,
624
- onDone
984
+ onDone,
985
+ params
625
986
  });
987
+ } catch (err) {
988
+ if (err.name === "AbortError") {
989
+ this.emit("aborted", { timestamp: Date.now() });
990
+ return;
991
+ }
992
+ throw err;
626
993
  } finally {
627
994
  this._abortController = null;
628
995
  this._isGenerating = false;
629
996
  }
630
997
  }
631
998
  };
999
+ /**
1000
+ * 校验 adapter 是否满足协议(v3.0 · 阶段 3)。
1001
+ * 缺少必要方法时抛 ConfigurationError,并列出缺少的方法名——写"同级适配器"不再靠猜。
1002
+ * @param {*} adapter
1003
+ * @throws {ConfigurationError}
1004
+ */
1005
+ function assertAdapter(adapter) {
1006
+ if (!adapter || typeof adapter !== "object") throw new ConfigurationError("setAdapter: adapter 必须是对象(如 openaiAdapter / createOpenAIAdapter() 实例)");
1007
+ const missing = [
1008
+ "buildRequest",
1009
+ "send",
1010
+ "stream",
1011
+ "parseResponse"
1012
+ ].filter((k) => typeof adapter[k] !== "function");
1013
+ if (missing.length) throw new ConfigurationError(`setAdapter: adapter 缺少必要方法: ${missing.join(", ")}(协议见 ChatService.js 顶部的 @typedef ChatAdapter)`);
1014
+ }
632
1015
  //#endregion
633
1016
  //#region src/utils/url.js
634
1017
  /**
@@ -806,17 +1189,48 @@ function assembleMessages(options = {}) {
806
1189
  * OpenAI 兼容 API 适配器
807
1190
  * 支持 DeepSeek 等完全兼容 OpenAI 接口的服务
808
1191
  */
1192
+ var TRANSPORT_KEYS = [
1193
+ "apiKey",
1194
+ "baseUrl",
1195
+ "apiUrl",
1196
+ "path",
1197
+ "headers"
1198
+ ];
809
1199
  var openaiAdapter = {
810
1200
  name: "openai",
811
- install(chatService) {
1201
+ install(chatService, options = {}) {
1202
+ if (this._transport) this._transport = {
1203
+ ...this._transport,
1204
+ ...options
1205
+ };
812
1206
  chatService.setAdapter(this);
813
1207
  },
1208
+ _resolveConfig(config) {
1209
+ const transport = this._transport || {};
1210
+ const resolved = {
1211
+ ...config,
1212
+ ...transport
1213
+ };
1214
+ if (transport.modelParams) resolved.modelParams = {
1215
+ ...transport.modelParams,
1216
+ ...config.modelParams || {}
1217
+ };
1218
+ return resolved;
1219
+ },
1220
+ getRequestDefaults() {
1221
+ if (!this._transport) return {};
1222
+ const defaults = {};
1223
+ for (const [key, value] of Object.entries(this._transport)) if (!TRANSPORT_KEYS.includes(key) && value !== void 0) defaults[key] = value;
1224
+ return defaults;
1225
+ },
814
1226
  buildRequest(messages, config, systemPrompts = []) {
1227
+ config = this._resolveConfig(config);
815
1228
  const model = config.model || config.modelParams?.model;
816
1229
  if (!model) throw new Error("Missing required config: model (either at top level or in modelParams)");
817
- const temperature = config.modelParams?.temperature ?? config.temperature ?? .7;
818
- const maxTokens = config.modelParams?.maxTokens ?? config.maxTokens ?? 2e3;
819
- const reasoningEffort = config.modelParams?.reasoningEffort ?? config.reasoningEffort;
1230
+ const mp = config.modelParams || {};
1231
+ const temperature = mp.temperature ?? config.temperature ?? .7;
1232
+ const maxTokens = mp.maxTokens ?? config.maxTokens ?? 2e3;
1233
+ const reasoningEffort = mp.reasoningEffort ?? config.reasoningEffort;
820
1234
  const requestBody = {
821
1235
  model,
822
1236
  messages: MessageFormatter.format({
@@ -830,11 +1244,30 @@ var openaiAdapter = {
830
1244
  max_tokens: maxTokens,
831
1245
  stream: false
832
1246
  };
1247
+ if (mp.topP !== void 0) requestBody.top_p = mp.topP;
1248
+ if (mp.frequencyPenalty !== void 0) requestBody.frequency_penalty = mp.frequencyPenalty;
1249
+ if (mp.presencePenalty !== void 0) requestBody.presence_penalty = mp.presencePenalty;
1250
+ if (mp.stop !== void 0) requestBody.stop = mp.stop;
1251
+ if (mp.responseFormat !== void 0) requestBody.response_format = mp.responseFormat;
1252
+ if (mp.seed !== void 0) requestBody.seed = mp.seed;
833
1253
  if (config.tools && Array.isArray(config.tools) && config.tools.length > 0) {
834
1254
  requestBody.tools = config.tools;
835
1255
  requestBody.tool_choice = "auto";
836
1256
  }
837
- if (reasoningEffort && model === "deepseek-reasoner") requestBody.reasoning_effort = reasoningEffort;
1257
+ if (reasoningEffort !== void 0) requestBody.reasoning_effort = reasoningEffort;
1258
+ const consumedKeys = new Set([
1259
+ "model",
1260
+ "temperature",
1261
+ "maxTokens",
1262
+ "reasoningEffort",
1263
+ "topP",
1264
+ "frequencyPenalty",
1265
+ "presencePenalty",
1266
+ "stop",
1267
+ "responseFormat",
1268
+ "seed"
1269
+ ]);
1270
+ for (const [key, value] of Object.entries(mp)) if (!consumedKeys.has(key) && value !== void 0) requestBody[key] = value;
838
1271
  return requestBody;
839
1272
  },
840
1273
  _getUrl(config) {
@@ -847,6 +1280,7 @@ var openaiAdapter = {
847
1280
  },
848
1281
  async send(requestBody, config, options = {}) {
849
1282
  try {
1283
+ config = this._resolveConfig(config);
850
1284
  const url = this._getUrl(config);
851
1285
  const headers = {
852
1286
  "Content-Type": "application/json",
@@ -881,6 +1315,7 @@ var openaiAdapter = {
881
1315
  return internal;
882
1316
  },
883
1317
  async stream(requestBody, config, onProgress, onDone, options = {}) {
1318
+ config = this._resolveConfig(config);
884
1319
  const streamBody = {
885
1320
  ...requestBody,
886
1321
  stream: true
@@ -974,165 +1409,213 @@ var openaiAdapter = {
974
1409
  throw new APIError(errorMessage, response.status, null, errorText);
975
1410
  }
976
1411
  };
1412
+ /**
1413
+ * 创建 OpenAI 兼容适配器实例(工厂)。
1414
+ *
1415
+ * 与单例 openaiAdapter 的区别:每个实例自持一份运输配置(apiKey/baseUrl/apiUrl/path/headers),
1416
+ * 互不干扰——解决"同一适配器装到多个 ChatService 互相覆盖"的单例陷阱。
1417
+ *
1418
+ * 用法:
1419
+ * const adapter = createOpenAIAdapter({ apiKey, baseUrl, modelParams: { temperature: 0.8 } });
1420
+ * const chat = new ChatService({ adapter, model: 'deepseek-chat' });
1421
+ * chat.use(adapter); // 或直接 new ChatService({ adapter })
1422
+ *
1423
+ * 非运输键(model/modelParams/messageFormat/resolveImage/capabilities 等)会作为
1424
+ * "请求默认参数"供会话层合并,单次请求仍可覆盖。
1425
+ */
1426
+ function createOpenAIAdapter(options = {}) {
1427
+ return {
1428
+ ...openaiAdapter,
1429
+ _transport: { ...options }
1430
+ };
1431
+ }
977
1432
  //#endregion
978
1433
  //#region src/plugins/tool-calling.js
979
1434
  /**
980
1435
  * 工具调用插件
981
1436
  * 功能:拦截助手消息中的 tool_calls,执行对应的工具,将结果作为 tool 消息加入对话,
982
1437
  * 然后自动继续对话(通过 sendExisting / sendExistingStream),直到没有新的工具调用。
983
- *
1438
+ *
984
1439
  * 设计要点:
985
1440
  * - 支持普通请求和流式请求(通过 isStream 标志区分)
986
1441
  * - 支持多次工具调用循环(maxIterations 防止无限循环)
987
- * - 串行执行工具(可后续升级为并行)
1442
+ * - 并行执行工具
988
1443
  * - 工具执行失败时,仍然返回错误信息给 AI,而不是中断整个流程
989
1444
  * - 触发 tool-error 事件,方便用户监听工具执行异常
1445
+ *
1446
+ * 注意:请使用 createToolCallingPlugin() 工厂创建实例。
1447
+ * 默认导出的 toolCallingPlugin 是兼容用的模块级单例,装到多个 ChatService
1448
+ * 实例会互相覆盖(executor 表、工具定义、配置),新代码不要直接用单例。
990
1449
  */
991
- var toolCallingPlugin = {
992
- name: "tool-calling",
993
- maxIterations: 5,
994
- install(chatService) {
995
- this.chatService = chatService;
996
- this._tools = /* @__PURE__ */ new Map();
997
- if (!chatService.config.tools) chatService.config.tools = [];
998
- /**
999
- * 注册工具
1000
- * @param {string} name - 工具名称(唯一标识)
1001
- * @param {string} description - 工具描述(告诉 AI 何时调用)
1002
- * @param {Function} executor - 异步执行函数,接收参数对象,返回结果(字符串或对象)
1003
- * @param {Object} parameters - JSON Schema 参数定义(可选,默认为空对象)
1004
- * @returns {ChatService} 返回 chatService 实例,支持链式调用
1005
- */
1006
- chatService.registerTool = (name, description, executor, parameters = {}) => {
1007
- this._tools.set(name, {
1008
- executor,
1009
- description
1450
+ var toolCallingPlugin = createToolCallingPlugin();
1451
+ /**
1452
+ * 创建工具调用插件实例(工厂)。
1453
+ * @param {Object} [options] — { timeout, maxIterations }
1454
+ */
1455
+ function createToolCallingPlugin(options = {}) {
1456
+ return {
1457
+ name: "tool-calling",
1458
+ maxIterations: options.maxIterations || 5,
1459
+ _options: { ...options },
1460
+ _tools: /* @__PURE__ */ new Map(),
1461
+ _toolDefs: [],
1462
+ chatService: null,
1463
+ install(chatService, options = {}) {
1464
+ this._options = {
1465
+ ...this._options,
1466
+ ...options
1467
+ };
1468
+ this.chatService = chatService;
1469
+ chatService.pipe({
1470
+ name: "tool-calling-inject",
1471
+ phase: "beforeSend",
1472
+ run: ({ config }) => {
1473
+ if (this._toolDefs.length) config.tools = this._toolDefs;
1474
+ }
1010
1475
  });
1011
- const toolDefinition = {
1012
- type: "function",
1013
- function: {
1014
- name,
1015
- description,
1016
- parameters: {
1017
- type: "object",
1018
- properties: parameters,
1019
- required: Object.keys(parameters).filter((key) => parameters[key]?.required)
1476
+ /**
1477
+ * 注册工具
1478
+ * @param {string} name - 工具名称(唯一标识)
1479
+ * @param {string} description - 工具描述(告诉 AI 何时调用)
1480
+ * @param {Function} executor - 异步执行函数,接收参数对象,返回结果(字符串或对象)
1481
+ * @param {Object} parameters - JSON Schema 参数定义(可选,默认为空对象)
1482
+ * @returns {ChatService} 返回 chatService 实例,支持链式调用
1483
+ */
1484
+ chatService.registerTool = (name, description, executor, parameters = {}) => {
1485
+ this._tools.set(name, {
1486
+ executor,
1487
+ description
1488
+ });
1489
+ const toolDefinition = {
1490
+ type: "function",
1491
+ function: {
1492
+ name,
1493
+ description,
1494
+ parameters: {
1495
+ type: "object",
1496
+ properties: parameters,
1497
+ required: Object.keys(parameters).filter((key) => parameters[key]?.required)
1498
+ }
1020
1499
  }
1021
- }
1500
+ };
1501
+ if (!this._toolDefs.find((t) => t.function.name === name)) this._toolDefs.push(toolDefinition);
1502
+ return chatService;
1022
1503
  };
1023
- if (!chatService.config.tools.find((t) => t.function.name === name)) chatService.config.tools.push(toolDefinition);
1024
- return chatService;
1025
- };
1026
- chatService._processResponse = async (response, { isStream, onProgress, onDone }) => {
1027
- return this._handleWithTools(response, isStream, onProgress, onDone);
1028
- };
1029
- },
1030
- async _handleWithTools(initialResponse, isStream, onProgress, onDone) {
1031
- const self = this;
1032
- const chat = this.chatService;
1033
- let iteration = 0;
1034
- /**
1035
- * 递归处理工具调用
1036
- * @param {Object} initialResponse - 初始 AI 响应(可能是第一次请求的响应)
1037
- * @returns {Promise<Object>} 最终 AI 响应(不含 tool_calls)
1038
- */
1039
- async function processResponse(initialResponse) {
1040
- let lastResponse = initialResponse;
1041
- while (iteration < self.maxIterations) {
1042
- const toolCalls = lastResponse?.toolCalls;
1043
- if (!toolCalls || toolCalls.length === 0) break;
1044
- iteration++;
1045
- const toolResults = await Promise.all(toolCalls.map(async (call) => {
1046
- const toolName = call.function?.name;
1047
- let args;
1048
- try {
1049
- args = JSON.parse(call.function?.arguments || "{}");
1050
- } catch (parseErr) {
1051
- chat.emit("tool-error", {
1052
- toolName: toolName || "unknown",
1053
- error: parseErr,
1054
- toolCallId: call.id,
1055
- stage: "parse",
1056
- timestamp: Date.now()
1057
- });
1058
- return {
1059
- tool_call_id: call.id,
1060
- error: `参数解析失败: ${parseErr.message}`,
1061
- success: false
1062
- };
1063
- }
1064
- const tool = self._tools.get(toolName);
1065
- if (!tool) {
1066
- chat.emit("tool-error", {
1067
- toolName,
1068
- error: /* @__PURE__ */ new Error(`Tool not registered: ${toolName}`),
1069
- toolCallId: call.id,
1070
- stage: "lookup",
1071
- timestamp: Date.now()
1072
- });
1073
- return {
1074
- tool_call_id: call.id,
1075
- error: `工具 ${toolName} 未注册`,
1076
- success: false
1077
- };
1078
- }
1079
- try {
1080
- const toolTimeout = chat.config.toolTimeout;
1081
- let execPromise = tool.executor(args);
1082
- if (toolTimeout && typeof toolTimeout === "number" && toolTimeout > 0) {
1083
- const timeoutErr = /* @__PURE__ */ new Error(`工具 ${toolName} 执行超时 (${toolTimeout}ms)`);
1084
- timeoutErr.name = "ToolTimeoutError";
1085
- const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(timeoutErr), toolTimeout));
1086
- execPromise = Promise.race([execPromise, timeoutPromise]);
1504
+ chatService.pipe({
1505
+ name: "tool-calling-loop",
1506
+ phase: "afterSend",
1507
+ run: async (ctx) => {
1508
+ ctx.result = await this._handleWithTools(ctx.result, ctx.isStream, ctx.onProgress || void 0, ctx.onDone || void 0);
1509
+ }
1510
+ });
1511
+ },
1512
+ async _handleWithTools(initialResponse, isStream, onProgress, onDone) {
1513
+ const self = this;
1514
+ const chat = this.chatService;
1515
+ let iteration = 0;
1516
+ /**
1517
+ * 递归处理工具调用
1518
+ * @param {Object} initialResponse - 初始 AI 响应(可能是第一次请求的响应)
1519
+ * @returns {Promise<Object>} 最终 AI 响应(不含 tool_calls)
1520
+ */
1521
+ async function processResponse(initialResponse) {
1522
+ let lastResponse = initialResponse;
1523
+ while (iteration < self.maxIterations) {
1524
+ const toolCalls = lastResponse?.toolCalls;
1525
+ if (!toolCalls || toolCalls.length === 0) break;
1526
+ iteration++;
1527
+ const toolResults = await Promise.all(toolCalls.map(async (call) => {
1528
+ const toolName = call.function?.name;
1529
+ let args;
1530
+ try {
1531
+ args = JSON.parse(call.function?.arguments || "{}");
1532
+ } catch (parseErr) {
1533
+ chat.emit("tool-error", {
1534
+ toolName: toolName || "unknown",
1535
+ error: parseErr,
1536
+ toolCallId: call.id,
1537
+ stage: "parse",
1538
+ timestamp: Date.now()
1539
+ });
1540
+ return {
1541
+ tool_call_id: call.id,
1542
+ error: `参数解析失败: ${parseErr.message}`,
1543
+ success: false
1544
+ };
1087
1545
  }
1088
- const result = await execPromise;
1089
- chat.emit("tool-success", {
1090
- toolName,
1091
- result,
1092
- toolCallId: call.id,
1093
- timestamp: Date.now()
1094
- });
1095
- return {
1096
- tool_call_id: call.id,
1097
- content: typeof result === "string" ? result : JSON.stringify(result),
1098
- success: true
1099
- };
1100
- } catch (err) {
1101
- const stage = err.name === "ToolTimeoutError" ? "timeout" : "execute";
1102
- chat.emit("tool-error", {
1103
- toolName,
1104
- error: err,
1105
- toolCallId: call.id,
1106
- stage,
1107
- timestamp: Date.now()
1108
- });
1109
- return {
1110
- tool_call_id: call.id,
1111
- error: err.message,
1112
- success: false
1113
- };
1546
+ const tool = self._tools.get(toolName);
1547
+ if (!tool) {
1548
+ chat.emit("tool-error", {
1549
+ toolName,
1550
+ error: /* @__PURE__ */ new Error(`Tool not registered: ${toolName}`),
1551
+ toolCallId: call.id,
1552
+ stage: "lookup",
1553
+ timestamp: Date.now()
1554
+ });
1555
+ return {
1556
+ tool_call_id: call.id,
1557
+ error: `工具 ${toolName} 未注册`,
1558
+ success: false
1559
+ };
1560
+ }
1561
+ try {
1562
+ const toolTimeout = self._options.timeout ?? chat.config.toolTimeout;
1563
+ let execPromise = tool.executor(args);
1564
+ if (toolTimeout && typeof toolTimeout === "number" && toolTimeout > 0) {
1565
+ const timeoutErr = /* @__PURE__ */ new Error(`工具 ${toolName} 执行超时 (${toolTimeout}ms)`);
1566
+ timeoutErr.name = "ToolTimeoutError";
1567
+ const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(timeoutErr), toolTimeout));
1568
+ execPromise = Promise.race([execPromise, timeoutPromise]);
1569
+ }
1570
+ const result = await execPromise;
1571
+ chat.emit("tool-success", {
1572
+ toolName,
1573
+ result,
1574
+ toolCallId: call.id,
1575
+ timestamp: Date.now()
1576
+ });
1577
+ return {
1578
+ tool_call_id: call.id,
1579
+ content: typeof result === "string" ? result : JSON.stringify(result),
1580
+ success: true
1581
+ };
1582
+ } catch (err) {
1583
+ const stage = err.name === "ToolTimeoutError" ? "timeout" : "execute";
1584
+ chat.emit("tool-error", {
1585
+ toolName,
1586
+ error: err,
1587
+ toolCallId: call.id,
1588
+ stage,
1589
+ timestamp: Date.now()
1590
+ });
1591
+ return {
1592
+ tool_call_id: call.id,
1593
+ error: err.message,
1594
+ success: false
1595
+ };
1596
+ }
1597
+ }));
1598
+ for (const tr of toolResults) {
1599
+ const content = tr.success ? tr.content : tr.error || "工具执行失败";
1600
+ chat.messages.addTool(content, tr.tool_call_id);
1114
1601
  }
1115
- }));
1116
- for (const tr of toolResults) {
1117
- const content = tr.success ? tr.content : tr.error || "工具执行失败";
1118
- chat.messages.addTool(content, tr.tool_call_id);
1119
- }
1120
- if (isStream) await new Promise((resolve) => {
1121
- chat.sendExistingStream((chunk) => {
1122
- if (onProgress) onProgress(chunk);
1123
- lastResponse = chunk;
1124
- }, (final) => {
1125
- lastResponse = final;
1126
- resolve();
1602
+ if (isStream) await new Promise((resolve) => {
1603
+ chat.sendExistingStream((chunk) => {
1604
+ if (onProgress) onProgress(chunk);
1605
+ lastResponse = chunk;
1606
+ }, (final) => {
1607
+ lastResponse = final;
1608
+ resolve();
1609
+ });
1127
1610
  });
1128
- });
1129
- else lastResponse = await chat.sendExisting();
1611
+ else lastResponse = await chat.sendExisting();
1612
+ }
1613
+ return lastResponse;
1130
1614
  }
1131
- return lastResponse;
1615
+ return await processResponse(initialResponse);
1132
1616
  }
1133
- return await processResponse(initialResponse);
1134
- }
1135
- };
1617
+ };
1618
+ }
1136
1619
  //#endregion
1137
1620
  //#region src/plugins/model-registry.js
1138
1621
  /**
@@ -1239,56 +1722,83 @@ var DEFAULT_CAPABILITIES = {
1239
1722
  reasoning: false,
1240
1723
  vision: false
1241
1724
  };
1242
- var modelRegistryPlugin = {
1243
- name: "model-registry",
1244
- install(chatService) {
1245
- this.chat = chatService;
1246
- this._registry = new Map(Object.entries(BUILTIN_MODELS));
1247
- /**
1248
- * 注册/覆盖一个模型的能力标签
1249
- * @param {string} name — 模型名称
1250
- * @param {Object} capabilities — 能力标签对象(部分字段即可,未提供的取默认值)
1251
- * @returns {ChatService}
1252
- */
1253
- chatService.registerModel = (name, capabilities = {}) => {
1254
- if (!name || typeof name !== "string" || !name.trim()) throw new Error("[model-registry] 模型名称必须是非空字符串");
1255
- const merged = {
1256
- ...DEFAULT_CAPABILITIES,
1257
- ...capabilities
1725
+ /**
1726
+ * 注意:请使用 createModelRegistryPlugin() 工厂创建实例。
1727
+ * 默认导出的 modelRegistryPlugin 是兼容用的模块级单例,装到多个 ChatService
1728
+ * 实例会互相覆盖(注册表、实例引用),新代码不要直接用单例。
1729
+ */
1730
+ var modelRegistryPlugin = createModelRegistryPlugin();
1731
+ /**
1732
+ * 创建模型能力注册表插件实例(工厂)。
1733
+ * @param {Object} [options] — { models: { '模型名': capabilities } }(合并进内置表)
1734
+ */
1735
+ function createModelRegistryPlugin(options = {}) {
1736
+ return {
1737
+ name: "model-registry",
1738
+ _options: { ...options },
1739
+ _registry: new Map([...Object.entries(BUILTIN_MODELS), ...Object.entries(options.models || {})]),
1740
+ chat: null,
1741
+ install(chatService, options = {}) {
1742
+ this._options = {
1743
+ ...this._options,
1744
+ ...options
1258
1745
  };
1259
- this._registry.set(name.trim(), merged);
1260
- if (chatService.config.model === name.trim()) this._syncCapabilities();
1261
- return chatService;
1262
- };
1263
- /**
1264
- * 列出所有已注册的模型名称
1265
- * @returns {Array<string>}
1266
- */
1267
- chatService.listModels = () => {
1268
- return [...this._registry.keys()];
1269
- };
1270
- this._syncCapabilities();
1271
- chatService.on("config-updated", ({ changes }) => {
1272
- if ("model" in changes) this._syncCapabilities();
1273
- if ("capabilities" in changes && !("model" in changes)) {
1274
- const current = this._lookupCapabilities();
1275
- if (current) chatService.config.capabilities = {
1276
- ...current,
1277
- ...changes.capabilities
1746
+ this.chat = chatService;
1747
+ /**
1748
+ * 注册/覆盖一个模型的能力标签
1749
+ * @param {string} name — 模型名称
1750
+ * @param {Object} capabilities — 能力标签对象(部分字段即可,未提供的取默认值)
1751
+ * @returns {ChatService}
1752
+ */
1753
+ chatService.registerModel = (name, capabilities = {}) => {
1754
+ if (!name || typeof name !== "string" || !name.trim()) throw new Error("[model-registry] 模型名称必须是非空字符串");
1755
+ const merged = {
1756
+ ...DEFAULT_CAPABILITIES,
1757
+ ...capabilities
1278
1758
  };
1279
- }
1280
- });
1281
- },
1282
- _lookupCapabilities() {
1283
- const model = this.chat.config.model;
1284
- if (!model) return null;
1285
- return this._registry.get(model) || null;
1286
- },
1287
- _syncCapabilities() {
1288
- const caps = this._lookupCapabilities();
1289
- if (caps) this.chat.config.capabilities = { ...caps };
1290
- }
1291
- };
1759
+ this._registry.set(name.trim(), merged);
1760
+ if (chatService.config.model === name.trim()) this._syncCapabilities();
1761
+ return chatService;
1762
+ };
1763
+ /**
1764
+ * 列出所有已注册的模型名称
1765
+ * @returns {Array<string>}
1766
+ */
1767
+ chatService.listModels = () => {
1768
+ return [...this._registry.keys()];
1769
+ };
1770
+ this._syncCapabilities();
1771
+ chatService.pipe({
1772
+ name: "model-registry-caps",
1773
+ phase: "beforeSend",
1774
+ run: ({ config }) => {
1775
+ if (!config.model) return;
1776
+ const caps = this._registry.get(config.model);
1777
+ if (caps) config.capabilities = { ...caps };
1778
+ }
1779
+ });
1780
+ chatService.on("config-updated", ({ changes }) => {
1781
+ if ("model" in changes) this._syncCapabilities();
1782
+ if ("capabilities" in changes && !("model" in changes)) {
1783
+ const current = this._lookupCapabilities();
1784
+ if (current) chatService.config.capabilities = {
1785
+ ...current,
1786
+ ...changes.capabilities
1787
+ };
1788
+ }
1789
+ });
1790
+ },
1791
+ _lookupCapabilities() {
1792
+ const model = this.chat.config.model;
1793
+ if (!model) return null;
1794
+ return this._registry.get(model) || null;
1795
+ },
1796
+ _syncCapabilities() {
1797
+ const caps = this._lookupCapabilities();
1798
+ if (caps) this.chat.config.capabilities = { ...caps };
1799
+ }
1800
+ };
1801
+ }
1292
1802
  //#endregion
1293
1803
  exports.APIError = APIError;
1294
1804
  exports.ChatService = ChatService;
@@ -1298,8 +1808,13 @@ exports.MessageFormatter = MessageFormatter;
1298
1808
  exports.MessageStore = MessageStore;
1299
1809
  exports.NetworkError = NetworkError;
1300
1810
  exports.ParsingError = ParsingError;
1811
+ exports.Pipeline = Pipeline;
1301
1812
  exports.SystemPromptStore = SystemPromptStore;
1302
1813
  exports.assembleMessages = assembleMessages;
1814
+ exports.assertAdapter = assertAdapter;
1815
+ exports.createModelRegistryPlugin = createModelRegistryPlugin;
1816
+ exports.createOpenAIAdapter = createOpenAIAdapter;
1817
+ exports.createToolCallingPlugin = createToolCallingPlugin;
1303
1818
  exports.modelRegistryPlugin = modelRegistryPlugin;
1304
1819
  exports.openaiAdapter = openaiAdapter;
1305
1820
  exports.toolCallingPlugin = toolCallingPlugin;