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
package/src/core/ChatService.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { EventEmitter } from './EventEmitter.js';
|
|
2
2
|
import { MessageStore } from './MessageStore.js';
|
|
3
3
|
import { SystemPromptStore } from './SystemPromptStore.js';
|
|
4
|
-
import { ConfigurationError, NetworkError } from './Errors.js';
|
|
4
|
+
import { ConfigurationError, NetworkError, ValidationError } from './Errors.js';
|
|
5
5
|
import { Pipeline } from './Pipeline.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -38,19 +38,29 @@ import { Pipeline } from './Pipeline.js';
|
|
|
38
38
|
*/
|
|
39
39
|
|
|
40
40
|
// updateConfig 白名单:会话层字段(运输层/请求默认参数归适配器,能力表归插件)
|
|
41
|
-
const SESSION_CONFIG_KEYS = ['model', 'temperature', 'maxTokens', 'modelParams', 'system', 'retry'
|
|
41
|
+
const SESSION_CONFIG_KEYS = ['model', 'temperature', 'maxTokens', 'modelParams', 'system', 'retry'];
|
|
42
|
+
|
|
43
|
+
// 内部车间四步 + 发送(顺序即执行顺序);v3.1 起每一步都可以被 replaceStage 顶替、被 unstage 关闭
|
|
44
|
+
const INTERNAL_STAGES = ['prepareInput', 'beforeSend', 'autoContinue', 'buildRequest', 'send'];
|
|
42
45
|
|
|
43
46
|
export class ChatService extends EventEmitter {
|
|
44
47
|
constructor(config = {}) {
|
|
45
48
|
super();
|
|
46
49
|
this.config = { ...config };
|
|
47
|
-
|
|
50
|
+
// v4.0(B3):消息存储可注入 —— 想换成自定义 store(如将来支持对话树)不用改核心
|
|
51
|
+
if (config.store) {
|
|
52
|
+
if (typeof config.store.getAll !== 'function' || typeof config.store.add !== 'function') {
|
|
53
|
+
throw new ValidationError('store 必须实现 MessageStore 的接口(至少 getAll / add / update)');
|
|
54
|
+
}
|
|
55
|
+
this.messages = config.store;
|
|
56
|
+
} else {
|
|
57
|
+
this.messages = new MessageStore();
|
|
58
|
+
}
|
|
48
59
|
this.systemPrompts = new SystemPromptStore();
|
|
49
60
|
this._adapter = null;
|
|
50
|
-
|
|
51
|
-
this.
|
|
52
|
-
this.
|
|
53
|
-
this._processResponse = null;
|
|
61
|
+
// v4.0(B1):每个请求各自持有 AbortController —— 多请求互不干扰(工具循环里再发请求也安全)
|
|
62
|
+
this._activeRequests = new Map(); // requestId → AbortController
|
|
63
|
+
this._requestSeq = 0;
|
|
54
64
|
|
|
55
65
|
const model = this.config.model || this.config.modelParams?.model;
|
|
56
66
|
if (!model || typeof model !== 'string' || !model.trim()) {
|
|
@@ -68,6 +78,10 @@ export class ChatService extends EventEmitter {
|
|
|
68
78
|
|
|
69
79
|
// ===== 内部管道(v3.0):内部车间 + 用户车间(pipe 注册)按 phase 插入 =====
|
|
70
80
|
this._userStages = [];
|
|
81
|
+
this._stageOverrides = new Map(); // v3.1 出口:内部步骤名 → 用户实现
|
|
82
|
+
this._disabledStages = new Set(); // v3.1 出口:被 unstage 关闭的内部步骤
|
|
83
|
+
this._extraConfigKeys = new Map(); // v4.0:插件注册的运行时可改字段 → 校验器(可空)
|
|
84
|
+
this.plugins = {}; // v4.0(B4):插件命名空间 —— 插件 API 挂在 chat.plugins.<插件名> 下,互不撞名
|
|
71
85
|
this._pipeline = this._buildPipeline();
|
|
72
86
|
}
|
|
73
87
|
|
|
@@ -92,8 +106,18 @@ export class ChatService extends EventEmitter {
|
|
|
92
106
|
}
|
|
93
107
|
}
|
|
94
108
|
|
|
109
|
+
/**
|
|
110
|
+
* 安装插件。
|
|
111
|
+
* - 插件实现 `install(chat, options)`
|
|
112
|
+
* - 若 install 返回一个对象,会挂到 **`chat.plugins[plugin.name]`**(v4.0 命名空间,插件之间不会撞名)
|
|
113
|
+
* - 插件要提供卸载时,自己在返回值里带 `uninstall()`(或调用 `plugin.uninstall(chat)`)
|
|
114
|
+
* @returns {ChatService} this
|
|
115
|
+
*/
|
|
95
116
|
use(plugin, options = {}) {
|
|
96
|
-
plugin.install(this, options);
|
|
117
|
+
const api = plugin.install(this, options);
|
|
118
|
+
if (plugin.name && api && typeof api === 'object') {
|
|
119
|
+
this.plugins[plugin.name] = api;
|
|
120
|
+
}
|
|
97
121
|
return this;
|
|
98
122
|
}
|
|
99
123
|
|
|
@@ -115,8 +139,7 @@ export class ChatService extends EventEmitter {
|
|
|
115
139
|
if (typeof stage.name !== 'string' || !stage.name.trim()) {
|
|
116
140
|
throw new ConfigurationError('pipe: 需要 name(车间名,非空字符串)');
|
|
117
141
|
}
|
|
118
|
-
|
|
119
|
-
if (INTERNAL.includes(stage.name)) {
|
|
142
|
+
if (INTERNAL_STAGES.includes(stage.name)) {
|
|
120
143
|
throw new ConfigurationError(`pipe: "${stage.name}" 是内部车间名,请换一个名字`);
|
|
121
144
|
}
|
|
122
145
|
if (this._userStages.some(s => s.name === stage.name)) {
|
|
@@ -141,19 +164,104 @@ export class ChatService extends EventEmitter {
|
|
|
141
164
|
return this._pipeline.names();
|
|
142
165
|
}
|
|
143
166
|
|
|
144
|
-
|
|
167
|
+
// ========== 出口 API(v3.1):替换 / 关闭 内部步骤 ==========
|
|
168
|
+
|
|
169
|
+
/** 内部步骤名(顺序即执行顺序):prepareInput / beforeSend / autoContinue / buildRequest / send */
|
|
170
|
+
get internalStages() {
|
|
171
|
+
return [...INTERNAL_STAGES];
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** 内部步骤的默认实现表 */
|
|
175
|
+
_defaultStageRuns() {
|
|
176
|
+
return {
|
|
177
|
+
prepareInput: (ctx) => this._stagePrepareInput(ctx),
|
|
178
|
+
beforeSend: (ctx) => this._stageBeforeSend(ctx),
|
|
179
|
+
autoContinue: (ctx) => this._stageAutoContinue(ctx),
|
|
180
|
+
buildRequest: (ctx) => this._stageBuildRequest(ctx),
|
|
181
|
+
send: (ctx) => this._stageSend(ctx),
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* 用你自己的实现**顶替**某个内部步骤。框架不再执行该步的默认行为。
|
|
187
|
+
* 例:`chat.replaceStage('send', async (ctx) => { ctx.result = await 我的发送(ctx.body) })`
|
|
188
|
+
* 想"包一层"默认实现:先 `const base = chat.getStage('send')`,再在自己的函数里调用它。
|
|
189
|
+
* @param {string} name 步骤名(见 internalStages)
|
|
190
|
+
* @param {Function} run (ctx) => void | Promise<void>
|
|
191
|
+
* @throws {ConfigurationError} 步骤名未知 / run 不是函数
|
|
192
|
+
*/
|
|
193
|
+
replaceStage(name, run) {
|
|
194
|
+
if (!INTERNAL_STAGES.includes(name)) {
|
|
195
|
+
throw new ConfigurationError(`replaceStage: 未知的内部步骤 "${name}"。可选: ${INTERNAL_STAGES.join(', ')}`);
|
|
196
|
+
}
|
|
197
|
+
if (typeof run !== 'function') {
|
|
198
|
+
throw new ConfigurationError('replaceStage: 第二个参数必须是函数 (ctx) => void | Promise<void>');
|
|
199
|
+
}
|
|
200
|
+
this._stageOverrides.set(name, run);
|
|
201
|
+
this._rebuildPipeline();
|
|
202
|
+
return this;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** 撤销替换,恢复该步骤的默认实现(不影响 unstage 的关闭状态) */
|
|
206
|
+
restoreStage(name) {
|
|
207
|
+
if (!INTERNAL_STAGES.includes(name)) {
|
|
208
|
+
throw new ConfigurationError(`restoreStage: 未知的内部步骤 "${name}"。可选: ${INTERNAL_STAGES.join(', ')}`);
|
|
209
|
+
}
|
|
210
|
+
this._stageOverrides.delete(name);
|
|
211
|
+
this._rebuildPipeline();
|
|
212
|
+
return this;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* **关闭**某个内部步骤(从管道里移除,不再执行)。
|
|
217
|
+
* 例:`chat.unstage('autoContinue')` —— 彻底不要"自动续写检测"这一步。
|
|
218
|
+
* 警告:关闭 prepareInput / buildRequest / send 会让流程失去必要产物(如 ctx.body)。
|
|
219
|
+
*/
|
|
220
|
+
unstage(name) {
|
|
221
|
+
if (!INTERNAL_STAGES.includes(name)) {
|
|
222
|
+
throw new ConfigurationError(`unstage: 未知的内部步骤 "${name}"。可选: ${INTERNAL_STAGES.join(', ')}`);
|
|
223
|
+
}
|
|
224
|
+
this._disabledStages.add(name);
|
|
225
|
+
this._rebuildPipeline();
|
|
226
|
+
return this;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** 重新启用被 unstage 关闭的步骤 */
|
|
230
|
+
restage(name) {
|
|
231
|
+
if (!INTERNAL_STAGES.includes(name)) {
|
|
232
|
+
throw new ConfigurationError(`restage: 未知的内部步骤 "${name}"。可选: ${INTERNAL_STAGES.join(', ')}`);
|
|
233
|
+
}
|
|
234
|
+
this._disabledStages.delete(name);
|
|
235
|
+
this._rebuildPipeline();
|
|
236
|
+
return this;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** 取得某个内部步骤的默认实现(用于"包一层":做自己的事,再调用它) */
|
|
240
|
+
getStage(name) {
|
|
241
|
+
if (!INTERNAL_STAGES.includes(name)) {
|
|
242
|
+
throw new ConfigurationError(`getStage: 未知的内部步骤 "${name}"。可选: ${INTERNAL_STAGES.join(', ')}`);
|
|
243
|
+
}
|
|
244
|
+
return this._defaultStageRuns()[name];
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* 重建内部管道:内部步骤(可被 replaceStage 顶替 / 被 unstage 关闭)+ 用户车间按 phase 插入
|
|
249
|
+
* 顺序:prepareInput → beforeSend → [用户 beforeSend 车间] → autoContinue → buildRequest → send → [用户 afterSend 车间]
|
|
250
|
+
*/
|
|
145
251
|
_buildPipeline() {
|
|
146
252
|
const p = new Pipeline();
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
253
|
+
const defaults = this._defaultStageRuns();
|
|
254
|
+
const insertUsers = (phase) => {
|
|
255
|
+
for (const s of this._userStages) {
|
|
256
|
+
if (s.phase === phase) p.register({ name: s.name, run: (ctx) => s.run(ctx) });
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
for (const name of INTERNAL_STAGES) {
|
|
260
|
+
if (!this._disabledStages.has(name)) {
|
|
261
|
+
p.register({ name, run: this._stageOverrides.get(name) || defaults[name] });
|
|
262
|
+
}
|
|
263
|
+
if (name === 'beforeSend') insertUsers('beforeSend');
|
|
264
|
+
if (name === 'send') insertUsers('afterSend');
|
|
157
265
|
}
|
|
158
266
|
return p;
|
|
159
267
|
}
|
|
@@ -167,95 +275,100 @@ export class ChatService extends EventEmitter {
|
|
|
167
275
|
this._adapter = adapter;
|
|
168
276
|
}
|
|
169
277
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
278
|
+
/**
|
|
279
|
+
* 中断请求(v4.0:按请求隔离)。
|
|
280
|
+
* - 不传参数:中断**所有**活跃请求(旧版只会中断"最后一个",多请求时会中断错的那个)
|
|
281
|
+
* - 传 requestId:只中断那一个(id 见 `chat.activeRequests`)
|
|
282
|
+
*/
|
|
283
|
+
abort(requestId) {
|
|
284
|
+
if (requestId) {
|
|
285
|
+
const controller = this._activeRequests.get(requestId);
|
|
286
|
+
if (controller) controller.abort();
|
|
287
|
+
return;
|
|
173
288
|
}
|
|
289
|
+
for (const controller of this._activeRequests.values()) controller.abort();
|
|
174
290
|
}
|
|
175
291
|
|
|
176
|
-
|
|
292
|
+
/** 是否有请求正在生成中 */
|
|
293
|
+
get isGenerating() { return this._activeRequests.size > 0; }
|
|
177
294
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
} catch (err) {
|
|
188
|
-
this.messages.update(target.id, {
|
|
189
|
-
_complete: true, prefix: undefined, _ephemeral: false
|
|
190
|
-
});
|
|
191
|
-
if (err.name === 'AbortError') {
|
|
192
|
-
this.emit('aborted', { timestamp: Date.now() });
|
|
193
|
-
return target;
|
|
194
|
-
}
|
|
195
|
-
throw err;
|
|
196
|
-
}
|
|
295
|
+
/** 当前活跃请求的 id 列表(调试 / 精确中断用) */
|
|
296
|
+
get activeRequests() { return [...this._activeRequests.keys()]; }
|
|
297
|
+
|
|
298
|
+
/** 内部:登记一个请求,返回 { id, signal } */
|
|
299
|
+
_beginRequest() {
|
|
300
|
+
const id = 'req_' + (++this._requestSeq);
|
|
301
|
+
const controller = new AbortController();
|
|
302
|
+
this._activeRequests.set(id, controller);
|
|
303
|
+
return { id, signal: controller.signal };
|
|
197
304
|
}
|
|
198
305
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
this.
|
|
202
|
-
|
|
306
|
+
/** 内部:注销一个请求 */
|
|
307
|
+
_endRequest(id) {
|
|
308
|
+
this._activeRequests.delete(id);
|
|
309
|
+
}
|
|
203
310
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
// 保留已累积的内容,标记完成
|
|
220
|
-
this.messages.update(target.id, {
|
|
221
|
-
_complete: true, prefix: undefined, _ephemeral: false
|
|
222
|
-
});
|
|
223
|
-
// abort 是用户主动行为,静默处理,通知外部
|
|
224
|
-
if (err.name === 'AbortError') {
|
|
225
|
-
this.emit('aborted', { timestamp: Date.now() });
|
|
226
|
-
return target;
|
|
311
|
+
// 注(v4.0):continueLast / continueLastStream / _prepareContinue 已迁出核心。
|
|
312
|
+
// 续写是「玩法」,由 src/plugins/continuation.js 提供 —— chat.use(createContinuationPlugin())。
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* 注册"运行时可改的配置字段"(v4.0)。
|
|
316
|
+
* 让插件自带配置(如 continuation 的 autoContinue)不必回头改核心白名单。
|
|
317
|
+
* @param {string[]|Object<string, Function>} keys 字段名数组,或 { 字段名: 校验器(value)=>boolean }
|
|
318
|
+
* @returns {ChatService} this
|
|
319
|
+
* @throws {ConfigurationError} 字段名非法 / 校验器不是函数
|
|
320
|
+
*/
|
|
321
|
+
registerConfigKeys(keys) {
|
|
322
|
+
const entries = Array.isArray(keys) ? keys.map(k => [k, null]) : Object.entries(keys || {});
|
|
323
|
+
for (const [key, validator] of entries) {
|
|
324
|
+
if (typeof key !== 'string' || !key.trim()) {
|
|
325
|
+
throw new ConfigurationError('registerConfigKeys: 字段名必须是非空字符串');
|
|
227
326
|
}
|
|
228
|
-
|
|
327
|
+
if (validator != null && typeof validator !== 'function') {
|
|
328
|
+
throw new ConfigurationError(`registerConfigKeys: "${key}" 的校验器必须是函数`);
|
|
329
|
+
}
|
|
330
|
+
this._extraConfigKeys.set(key.trim(), validator || null);
|
|
229
331
|
}
|
|
332
|
+
return this;
|
|
230
333
|
}
|
|
231
334
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
|
|
335
|
+
/** 注销运行时可改字段(插件卸载时调用)@returns {ChatService} this */
|
|
336
|
+
unregisterConfigKeys(keys) {
|
|
337
|
+
for (const k of (Array.isArray(keys) ? keys : [keys])) this._extraConfigKeys.delete(k);
|
|
338
|
+
return this;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/** 当前允许在运行时修改的配置字段(内置基础字段 + 插件注册) */
|
|
342
|
+
get configKeys() {
|
|
343
|
+
return [...SESSION_CONFIG_KEYS, ...this._extraConfigKeys.keys()];
|
|
237
344
|
}
|
|
238
345
|
|
|
239
346
|
/**
|
|
240
|
-
*
|
|
347
|
+
* 运行时修改配置(仅"可改字段":内置基础字段 + 插件注册字段)。
|
|
241
348
|
*
|
|
242
|
-
* 白名单:model / temperature / maxTokens / modelParams / system / retry / ephemeralContinue。
|
|
243
349
|
* - 运输层(apiKey/baseUrl/headers):归适配器,要改就换适配器实例
|
|
244
350
|
* - 请求参数默认值:走适配器工厂 options 或请求级覆盖(chat.send(x, params))
|
|
245
|
-
* -
|
|
351
|
+
* - 插件自有配置:插件用 `registerConfigKeys` 登记后才可改(工厂 options 仍是首选)
|
|
246
352
|
*
|
|
247
353
|
* 与构造器一样经过校验,不能注入非法值。
|
|
248
|
-
* @throws {ConfigurationError}
|
|
354
|
+
* @throws {ConfigurationError} 非白名单字段、非法值、或插件校验器拒绝
|
|
249
355
|
*/
|
|
250
356
|
updateConfig(partial) {
|
|
357
|
+
const allowed = this.configKeys;
|
|
251
358
|
for (const key of Object.keys(partial)) {
|
|
252
|
-
if (!
|
|
359
|
+
if (!allowed.includes(key)) {
|
|
253
360
|
throw new ConfigurationError(
|
|
254
|
-
`updateConfig 不支持修改 "${key}"
|
|
361
|
+
`updateConfig 不支持修改 "${key}"。允许的字段: ${allowed.join(', ')}`
|
|
255
362
|
);
|
|
256
363
|
}
|
|
257
364
|
}
|
|
258
365
|
this._assertValidConfig(partial);
|
|
366
|
+
// 插件注册字段的校验(校验器返回 false 即拒绝)
|
|
367
|
+
for (const [key, validator] of this._extraConfigKeys) {
|
|
368
|
+
if (validator && key in partial && validator(partial[key]) === false) {
|
|
369
|
+
throw new ConfigurationError(`updateConfig: 字段 "${key}" 的值不合法: ${JSON.stringify(partial[key])}`);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
259
372
|
Object.assign(this.config, partial);
|
|
260
373
|
if (typeof partial.system === 'string') this.systemPrompts.set(partial.system);
|
|
261
374
|
this.emit('config-updated', { changes: partial, timestamp: Date.now() });
|
|
@@ -314,6 +427,7 @@ export class ChatService extends EventEmitter {
|
|
|
314
427
|
onProgress: options.onProgress || null,
|
|
315
428
|
onDone: options.onDone || null,
|
|
316
429
|
mergeToEntry: options.mergeToEntry || null,
|
|
430
|
+
signal: options.signal || null, // v4.0(B1):本请求自己的中断信号
|
|
317
431
|
body: null,
|
|
318
432
|
result: null,
|
|
319
433
|
};
|
|
@@ -333,29 +447,28 @@ export class ChatService extends EventEmitter {
|
|
|
333
447
|
/** 车间 1:加用户消息 → 合并请求参数 → 广播 sending */
|
|
334
448
|
async _stagePrepareInput(ctx) {
|
|
335
449
|
this._addUserMessage(ctx.userInput, ctx.addUser);
|
|
336
|
-
//
|
|
450
|
+
// 合并请求参数(合并后的 ctx.config 供后续车间使用)
|
|
337
451
|
ctx.config = this._mergeRequestConfig(ctx.options.params);
|
|
338
452
|
this.emit('sending', { addUser: ctx.addUser, userInput: ctx.userInput, timestamp: Date.now() });
|
|
339
453
|
}
|
|
340
454
|
|
|
341
|
-
/**
|
|
455
|
+
/**
|
|
456
|
+
* 车间 2:发送前 —— **v4.0 起默认什么都不做**。
|
|
457
|
+
* 需要在这里做事的(注入、改写消息/配置):`chat.replaceStage('beforeSend', fn)`
|
|
458
|
+
* 或挂一个 `chat.pipe({ phase: 'beforeSend' })` 车间(在它之后执行)。
|
|
459
|
+
* 旧的 `_hooks.on('beforeRequest')` 兼容桥已于 v4.0 移除。
|
|
460
|
+
*/
|
|
342
461
|
async _stageBeforeSend(ctx) {
|
|
343
|
-
|
|
344
|
-
messages: ctx.messages,
|
|
345
|
-
config: ctx.config,
|
|
346
|
-
options: ctx.options
|
|
347
|
-
});
|
|
462
|
+
// 默认空实现(no-op)
|
|
348
463
|
}
|
|
349
464
|
|
|
350
|
-
/**
|
|
465
|
+
/**
|
|
466
|
+
* 车间 3:自动续写检测 —— **v4.0 起默认什么都不做**。
|
|
467
|
+
* 续写是玩法:需要它的装上 continuation 插件,由插件 replaceStage('autoContinue', ...) 接管。
|
|
468
|
+
* 步骤名与位置保留,方便插件替换、也方便使用者 `unstage('autoContinue')` 明确移除。
|
|
469
|
+
*/
|
|
351
470
|
async _stageAutoContinue(ctx) {
|
|
352
|
-
|
|
353
|
-
const msgs = ctx.messages.getAll();
|
|
354
|
-
const last = msgs[msgs.length - 1];
|
|
355
|
-
if (last && last.prefix && (last.role === 'assistant' || last._ephemeral)) {
|
|
356
|
-
ctx.mergeToEntry = last.id;
|
|
357
|
-
}
|
|
358
|
-
}
|
|
471
|
+
// 默认空实现(no-op)
|
|
359
472
|
}
|
|
360
473
|
|
|
361
474
|
/** 车间 4:构建请求体 */
|
|
@@ -375,7 +488,8 @@ export class ChatService extends EventEmitter {
|
|
|
375
488
|
mergeToEntry: ctx.mergeToEntry,
|
|
376
489
|
config: ctx.config,
|
|
377
490
|
maxRetries: retryCfg.maxRetries ?? 0,
|
|
378
|
-
retryDelay: retryCfg.retryDelay ?? 1000
|
|
491
|
+
retryDelay: retryCfg.retryDelay ?? 1000,
|
|
492
|
+
signal: ctx.signal
|
|
379
493
|
});
|
|
380
494
|
}
|
|
381
495
|
|
|
@@ -389,9 +503,10 @@ export class ChatService extends EventEmitter {
|
|
|
389
503
|
}
|
|
390
504
|
|
|
391
505
|
/** retry 循环,占位消息只 push 一次 */
|
|
392
|
-
async _withRetry(body, { isStream, onProgress, onDone, mergeToEntry, config, maxRetries, retryDelay }) {
|
|
506
|
+
async _withRetry(body, { isStream, onProgress, onDone, mergeToEntry, config, maxRetries, retryDelay, signal }) {
|
|
393
507
|
let lastError = null;
|
|
394
|
-
|
|
508
|
+
// v4.0(B1):使用"本请求自己的"信号(不再读写共享的 this._abortController)
|
|
509
|
+
const adapterOptions = { signal };
|
|
395
510
|
|
|
396
511
|
let placeholder = null;
|
|
397
512
|
let base = null;
|
|
@@ -411,28 +526,22 @@ export class ChatService extends EventEmitter {
|
|
|
411
526
|
await new Promise(r => setTimeout(r, retryDelay));
|
|
412
527
|
}
|
|
413
528
|
try {
|
|
414
|
-
this._isGenerating = true;
|
|
415
529
|
if (isStream) {
|
|
416
530
|
return await this._stream(body, adapterOptions, { placeholder, base, mergeToEntry, onProgress, onDone, config });
|
|
417
531
|
} else {
|
|
418
532
|
const resp = await this._adapter.send(body, config, adapterOptions);
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
if (this._processResponse && !mergeToEntry) {
|
|
422
|
-
result = await this._processResponse(result, { isStream: false });
|
|
423
|
-
}
|
|
424
|
-
return result;
|
|
533
|
+
// 响应落位后交给管道:afterSend 车间接管后续处理(如工具调用循环)
|
|
534
|
+
return this._handleResult(this._adapter.parseResponse(resp), mergeToEntry);
|
|
425
535
|
}
|
|
426
536
|
} catch (error) {
|
|
427
537
|
lastError = error;
|
|
428
|
-
if (error.name === 'AbortError' ||
|
|
429
|
-
|
|
538
|
+
if (error.name === 'AbortError' || signal?.aborted) {
|
|
539
|
+
throw error;
|
|
430
540
|
}
|
|
431
541
|
if (error instanceof NetworkError && attempt < maxRetries) continue;
|
|
432
|
-
|
|
542
|
+
throw error;
|
|
433
543
|
}
|
|
434
544
|
}
|
|
435
|
-
this._isGenerating = false;
|
|
436
545
|
throw lastError;
|
|
437
546
|
}
|
|
438
547
|
|
|
@@ -451,7 +560,8 @@ export class ChatService extends EventEmitter {
|
|
|
451
560
|
|
|
452
561
|
await this._adapter.stream(body, config,
|
|
453
562
|
// === onProgress:每收到一个 SSE chunk 触发 ===
|
|
454
|
-
|
|
563
|
+
// snap = 全量累积快照;delta = 本次新增片段(v4.0 起两个都给你)
|
|
564
|
+
(snap, delta) => {
|
|
455
565
|
// snap.content 是 adapter 累积的全部 API 新内容(不是增量)
|
|
456
566
|
// 续写时:base.content(旧内容)+ snap.content(API 新内容)= 完整内容
|
|
457
567
|
// 普通发送时:直接用 snap.content
|
|
@@ -466,19 +576,12 @@ export class ChatService extends EventEmitter {
|
|
|
466
576
|
}
|
|
467
577
|
if (snap.toolCalls) placeholder.toolCalls = [...snap.toolCalls];
|
|
468
578
|
this.emit('stream-progress', snap);
|
|
469
|
-
if (onProgress) onProgress(snap);
|
|
579
|
+
if (onProgress) onProgress(snap, delta); // v4.0:第二个参数是本次增量
|
|
470
580
|
},
|
|
471
581
|
// === onDone:流结束,做最终落位 ===
|
|
472
582
|
async (final) => {
|
|
473
583
|
finalMsg = final;
|
|
474
584
|
|
|
475
|
-
// 工具调用钩子(仅普通发送时,续写不走 tool-calling 循环)
|
|
476
|
-
if (this._processResponse && !mergeToEntry) {
|
|
477
|
-
finalMsg = await this._processResponse(finalMsg, {
|
|
478
|
-
isStream: true, onProgress, onDone
|
|
479
|
-
});
|
|
480
|
-
}
|
|
481
|
-
|
|
482
585
|
if (mergeToEntry && base) {
|
|
483
586
|
// 续写:把 base + 最终内容写回 target,标记完成
|
|
484
587
|
const newContent = base.content + (final.content || '');
|
|
@@ -539,9 +642,9 @@ export class ChatService extends EventEmitter {
|
|
|
539
642
|
* @param {Object} [params] - 请求级参数覆盖(model/temperature/modelParams 等,仅本次生效)
|
|
540
643
|
*/
|
|
541
644
|
async send(userInput, params) {
|
|
542
|
-
|
|
645
|
+
const { id, signal } = this._beginRequest();
|
|
543
646
|
try {
|
|
544
|
-
return await this._request({ userInput, addUser: true, isStream: false, params });
|
|
647
|
+
return await this._request({ userInput, addUser: true, isStream: false, params, signal });
|
|
545
648
|
} catch (err) {
|
|
546
649
|
if (err.name === 'AbortError') {
|
|
547
650
|
this.emit('aborted', { timestamp: Date.now() });
|
|
@@ -549,8 +652,7 @@ export class ChatService extends EventEmitter {
|
|
|
549
652
|
}
|
|
550
653
|
throw err;
|
|
551
654
|
} finally {
|
|
552
|
-
this.
|
|
553
|
-
this._isGenerating = false;
|
|
655
|
+
this._endRequest(id);
|
|
554
656
|
}
|
|
555
657
|
}
|
|
556
658
|
|
|
@@ -568,9 +670,9 @@ export class ChatService extends EventEmitter {
|
|
|
568
670
|
onProgress = params;
|
|
569
671
|
params = undefined;
|
|
570
672
|
}
|
|
571
|
-
|
|
673
|
+
const { id, signal } = this._beginRequest();
|
|
572
674
|
try {
|
|
573
|
-
return await this._request({ userInput, addUser: true, isStream: true, onProgress, onDone, params });
|
|
675
|
+
return await this._request({ userInput, addUser: true, isStream: true, onProgress, onDone, params, signal });
|
|
574
676
|
} catch (err) {
|
|
575
677
|
if (err.name === 'AbortError') {
|
|
576
678
|
this.emit('aborted', { timestamp: Date.now() });
|
|
@@ -578,19 +680,20 @@ export class ChatService extends EventEmitter {
|
|
|
578
680
|
}
|
|
579
681
|
throw err;
|
|
580
682
|
} finally {
|
|
581
|
-
this.
|
|
582
|
-
this._isGenerating = false;
|
|
683
|
+
this._endRequest(id);
|
|
583
684
|
}
|
|
584
685
|
}
|
|
585
686
|
|
|
586
687
|
/**
|
|
587
688
|
* 重发当前消息(不加用户消息)。
|
|
588
689
|
* @param {Object} [params] - 请求级参数覆盖(仅本次生效)
|
|
690
|
+
* @param {Object} [options] - { mergeToEntry } 可选:把结果合并进指定的已有消息(续写等场景)
|
|
589
691
|
*/
|
|
590
|
-
async sendExisting(params) {
|
|
591
|
-
|
|
692
|
+
async sendExisting(params, options = {}) {
|
|
693
|
+
const mergeToEntry = options?.mergeToEntry || null;
|
|
694
|
+
const { id, signal } = this._beginRequest();
|
|
592
695
|
try {
|
|
593
|
-
return await this._request({ addUser: false, isStream: false, params });
|
|
696
|
+
return await this._request({ addUser: false, isStream: false, params, mergeToEntry, signal });
|
|
594
697
|
} catch (err) {
|
|
595
698
|
if (err.name === 'AbortError') {
|
|
596
699
|
this.emit('aborted', { timestamp: Date.now() });
|
|
@@ -598,27 +701,34 @@ export class ChatService extends EventEmitter {
|
|
|
598
701
|
}
|
|
599
702
|
throw err;
|
|
600
703
|
} finally {
|
|
601
|
-
this.
|
|
602
|
-
this._isGenerating = false;
|
|
704
|
+
this._endRequest(id);
|
|
603
705
|
}
|
|
604
706
|
}
|
|
605
707
|
|
|
606
708
|
/**
|
|
607
709
|
* 流式重发(不加用户消息)。
|
|
608
710
|
* @param {Object} [params] - 请求级参数覆盖(仅本次生效)
|
|
711
|
+
* @param {Object} [options] - { mergeToEntry } 可选:把结果合并进指定的已有消息
|
|
609
712
|
* @param {Function} [onProgress] - 流式进度回调
|
|
610
713
|
* @param {Function} [onDone] - 流式完成回调
|
|
611
714
|
*/
|
|
612
|
-
async sendExistingStream(params, onProgress, onDone) {
|
|
613
|
-
//
|
|
715
|
+
async sendExistingStream(params, options, onProgress, onDone) {
|
|
716
|
+
// 兼容签名1:sendExistingStream(onProgress, onDone)
|
|
614
717
|
if (typeof params === 'function') {
|
|
615
|
-
onDone =
|
|
718
|
+
onDone = options;
|
|
616
719
|
onProgress = params;
|
|
720
|
+
options = {};
|
|
617
721
|
params = undefined;
|
|
722
|
+
} else if (typeof options === 'function') {
|
|
723
|
+
// 兼容签名2:sendExistingStream(params, onProgress, onDone)
|
|
724
|
+
onDone = onProgress;
|
|
725
|
+
onProgress = options;
|
|
726
|
+
options = {};
|
|
618
727
|
}
|
|
619
|
-
|
|
728
|
+
const mergeToEntry = options?.mergeToEntry || null;
|
|
729
|
+
const { id, signal } = this._beginRequest();
|
|
620
730
|
try {
|
|
621
|
-
return await this._request({ addUser: false, isStream: true, onProgress, onDone, params });
|
|
731
|
+
return await this._request({ addUser: false, isStream: true, onProgress, onDone, params, mergeToEntry, signal });
|
|
622
732
|
} catch (err) {
|
|
623
733
|
if (err.name === 'AbortError') {
|
|
624
734
|
this.emit('aborted', { timestamp: Date.now() });
|
|
@@ -626,8 +736,7 @@ export class ChatService extends EventEmitter {
|
|
|
626
736
|
}
|
|
627
737
|
throw err;
|
|
628
738
|
} finally {
|
|
629
|
-
this.
|
|
630
|
-
this._isGenerating = false;
|
|
739
|
+
this._endRequest(id);
|
|
631
740
|
}
|
|
632
741
|
}
|
|
633
742
|
}
|
package/src/core/Errors.js
CHANGED
|
@@ -44,6 +44,19 @@ export class ConfigurationError extends Error {
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
// 校验错误 (传参非法、索引越界、调用前提不满足等 —— 使用者传错了东西)
|
|
48
|
+
// 继承 ConfigurationError:老的 `catch (e) { if (e instanceof ConfigurationError) }` 仍然命中,
|
|
49
|
+
// 需要精细区分时用 `instanceof ValidationError`。
|
|
50
|
+
export class ValidationError extends ConfigurationError {
|
|
51
|
+
/**
|
|
52
|
+
* @param {string} message - 错误消息
|
|
53
|
+
*/
|
|
54
|
+
constructor(message) {
|
|
55
|
+
super(message)
|
|
56
|
+
this.name = 'ValidationError'
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
47
60
|
// 解析错误 (解析 API 响应失败、响应格式不正确,不符合预期等)
|
|
48
61
|
export class ParsingError extends Error {
|
|
49
62
|
/**
|