my-ai-chat-framework 2.0.0 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +242 -45
- package/dist/my-ai-chat-framework.browser.es.js +1048 -184
- package/dist/my-ai-chat-framework.browser.es.js.map +1 -1
- package/dist/my-ai-chat-framework.browser.umd.js +1056 -188
- package/dist/my-ai-chat-framework.browser.umd.js.map +1 -1
- package/dist/my-ai-chat-framework.node.cjs.js +1056 -188
- package/dist/my-ai-chat-framework.node.cjs.js.map +1 -1
- package/package.json +7 -7
- package/src/adapters/openai.js +214 -64
- package/src/core/ChatService.js +298 -110
- package/src/core/Errors.js +60 -0
- package/src/core/EventEmitter.js +19 -0
- package/src/core/MessageStore.js +39 -0
- package/src/core/SystemPromptStore.js +118 -0
- package/src/index.js +10 -14
- package/src/plugins/model-registry.js +187 -0
- package/src/plugins/tool-calling.js +172 -78
- package/src/utils/MessageFormatter.js +204 -0
- package/src/utils/typeCheck.js +12 -0
- package/src/utils/url.js +18 -0
- package/.env +0 -15
- package/test.js +0 -107
package/src/core/ChatService.js
CHANGED
|
@@ -1,110 +1,298 @@
|
|
|
1
|
-
import { EventEmitter } from './EventEmitter.js';
|
|
2
|
-
import { MessageStore } from './MessageStore.js';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
this.
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
this.
|
|
17
|
-
|
|
18
|
-
this.
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
this
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
this.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
1
|
+
import { EventEmitter } from './EventEmitter.js';
|
|
2
|
+
import { MessageStore } from './MessageStore.js';
|
|
3
|
+
import { SystemPromptStore } from './SystemPromptStore.js';
|
|
4
|
+
import { ConfigurationError, NetworkError } from './Errors.js';
|
|
5
|
+
|
|
6
|
+
export class ChatService extends EventEmitter {
|
|
7
|
+
constructor(config = {}) {
|
|
8
|
+
super();
|
|
9
|
+
this.config = { ...config };
|
|
10
|
+
this.messages = new MessageStore();
|
|
11
|
+
this.systemPrompts = new SystemPromptStore();
|
|
12
|
+
this._adapter = null;
|
|
13
|
+
this._abortController = null;
|
|
14
|
+
this._isGenerating = false;
|
|
15
|
+
this._hooks = new EventEmitter();
|
|
16
|
+
this._processResponse = null;
|
|
17
|
+
|
|
18
|
+
const model = this.config.model || this.config.modelParams?.model;
|
|
19
|
+
if (!model || typeof model !== 'string' || !model.trim()) {
|
|
20
|
+
throw new ConfigurationError('缺少 model 配置');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const temperature = this.config.modelParams?.temperature ?? this.config.temperature;
|
|
24
|
+
if (temperature !== undefined && (typeof temperature !== 'number' || temperature < 0 || temperature > 2)) {
|
|
25
|
+
throw new ConfigurationError(`temperature 必须在 0-2 之间,当前值: ${temperature}`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const maxTokens = this.config.modelParams?.maxTokens ?? this.config.maxTokens;
|
|
29
|
+
if (maxTokens !== undefined && (typeof maxTokens !== 'number' || maxTokens < 1 || !Number.isInteger(maxTokens))) {
|
|
30
|
+
throw new ConfigurationError(`maxTokens 必须为正整数,当前值: ${maxTokens}`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (typeof config.system === 'string' && config.system.trim()) {
|
|
34
|
+
this.systemPrompts.set(config.system);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
use(plugin) {
|
|
39
|
+
plugin.install(this);
|
|
40
|
+
return this;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
setAdapter(adapter) {
|
|
44
|
+
this._adapter = adapter;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
abort() {
|
|
48
|
+
if (this._abortController) this._abortController.abort();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
get isGenerating() { return this._isGenerating; }
|
|
52
|
+
|
|
53
|
+
async continueLast() {
|
|
54
|
+
const target = this._prepareContinue();
|
|
55
|
+
this.messages.update(target.id, { prefix: true });
|
|
56
|
+
try {
|
|
57
|
+
await this._request({ addUser: false, isStream: false, mergeToEntry: target.id });
|
|
58
|
+
this.messages.update(target.id, {
|
|
59
|
+
_complete: true, prefix: undefined, _ephemeral: undefined
|
|
60
|
+
});
|
|
61
|
+
return target;
|
|
62
|
+
} catch (err) {
|
|
63
|
+
this.messages.update(target.id, { prefix: undefined });
|
|
64
|
+
throw err;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async continueLastStream(onProgress, onDone) {
|
|
69
|
+
const target = this._prepareContinue();
|
|
70
|
+
console.log('[DEBUG] continueLastStream target.id:', target.id);
|
|
71
|
+
this.messages.update(target.id, { prefix: true });
|
|
72
|
+
const baseLen = (target.content || '').length;
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
await this._request({
|
|
76
|
+
addUser: false, isStream: true, mergeToEntry: target.id,
|
|
77
|
+
onProgress: (chunk) => {
|
|
78
|
+
if (onProgress) {
|
|
79
|
+
onProgress({ ...chunk, content: (chunk.content || '').slice(baseLen) });
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
onDone: (final) => { if (onDone) onDone(final); }
|
|
83
|
+
});
|
|
84
|
+
this.messages.update(target.id, {
|
|
85
|
+
_complete: true, prefix: undefined, _ephemeral: undefined
|
|
86
|
+
});
|
|
87
|
+
return target;
|
|
88
|
+
} catch (err) {
|
|
89
|
+
this.messages.update(target.id, { prefix: undefined });
|
|
90
|
+
throw err;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
_prepareContinue() {
|
|
95
|
+
const msgs = this.messages.getAll();
|
|
96
|
+
const last = msgs[msgs.length - 1];
|
|
97
|
+
if (last && (last.role === 'assistant' || last._ephemeral)) return last;
|
|
98
|
+
throw new Error('最后一条消息不是 assistant,无法续写');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
updateConfig(partial) {
|
|
102
|
+
Object.assign(this.config, partial);
|
|
103
|
+
if (typeof partial.system === 'string') this.systemPrompts.set(partial.system);
|
|
104
|
+
this.emit('config-updated', { changes: partial, timestamp: Date.now() });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ========== 编排角色 ==========
|
|
108
|
+
async _request(options) {
|
|
109
|
+
let { userInput, addUser = true, isStream = false, onProgress, onDone, mergeToEntry } = options;
|
|
110
|
+
|
|
111
|
+
this.emit('sending', { addUser, userInput, timestamp: Date.now() });
|
|
112
|
+
this._addUserMessage(userInput, addUser);
|
|
113
|
+
|
|
114
|
+
// 发送前钩子:在 adapter 转换 messages 之前执行,可修改 messages/config
|
|
115
|
+
await this._hooks.emitAsync('beforeRequest', {
|
|
116
|
+
messages: this.messages,
|
|
117
|
+
config: this.config,
|
|
118
|
+
options
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// 自动续写:启用 autoContinue 时,检测底部是否有 prefix 标记的消息,自动设为续写目标
|
|
122
|
+
// 用于 ephemeral 注入场景:注入一条带 prefix 的引导消息后,自动合并结果回原消息
|
|
123
|
+
// 不启用时 prefix 仅透传给 API,结果仍作为新消息追加
|
|
124
|
+
if (this.config.ephemeralContinue && !mergeToEntry) {
|
|
125
|
+
const msgs = this.messages.getAll();
|
|
126
|
+
const last = msgs[msgs.length - 1];
|
|
127
|
+
if (last && last.prefix && (last.role === 'assistant' || last._ephemeral)) {
|
|
128
|
+
mergeToEntry = last.id;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const body = this._adapter.buildRequest(
|
|
133
|
+
this.messages.getAll(), this.config, this.systemPrompts.getEnabled()
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
const retryCfg = this.config.retry || {};
|
|
137
|
+
try {
|
|
138
|
+
return await this._withRetry(body, {
|
|
139
|
+
isStream, onProgress, onDone, mergeToEntry,
|
|
140
|
+
maxRetries: retryCfg.maxRetries ?? 0,
|
|
141
|
+
retryDelay: retryCfg.retryDelay ?? 1000
|
|
142
|
+
});
|
|
143
|
+
} catch (error) {
|
|
144
|
+
this.emit('error', { error, timestamp: Date.now() });
|
|
145
|
+
throw error;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
_addUserMessage(userInput, addUser) {
|
|
150
|
+
if (addUser && userInput !== undefined) {
|
|
151
|
+
const msg = typeof userInput === 'string'
|
|
152
|
+
? { role: 'user', content: userInput }
|
|
153
|
+
: { role: 'user', ...userInput };
|
|
154
|
+
this.messages.add(msg);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** retry 循环,占位消息只 push 一次 */
|
|
159
|
+
async _withRetry(body, { isStream, onProgress, onDone, mergeToEntry, maxRetries, retryDelay }) {
|
|
160
|
+
let lastError = null;
|
|
161
|
+
const adapterOptions = { signal: this._abortController?.signal };
|
|
162
|
+
|
|
163
|
+
let placeholder = null;
|
|
164
|
+
let base = null;
|
|
165
|
+
if (isStream) {
|
|
166
|
+
if (mergeToEntry) {
|
|
167
|
+
placeholder = this.messages.getAll().find(m => m.id === mergeToEntry);
|
|
168
|
+
if (placeholder) base = { content: placeholder.content || '', reasoning: placeholder.reasoningContent || '' };
|
|
169
|
+
}
|
|
170
|
+
if (!placeholder) {
|
|
171
|
+
placeholder = this.messages.add({ role: 'assistant', content: '', _complete: false });
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
176
|
+
if (attempt > 0) {
|
|
177
|
+
this.emit('retry', { attempt, maxRetries, lastError, timestamp: Date.now() });
|
|
178
|
+
await new Promise(r => setTimeout(r, retryDelay));
|
|
179
|
+
}
|
|
180
|
+
try {
|
|
181
|
+
this._isGenerating = true;
|
|
182
|
+
if (isStream) {
|
|
183
|
+
return await this._stream(body, adapterOptions, { placeholder, base, mergeToEntry, onProgress, onDone });
|
|
184
|
+
} else {
|
|
185
|
+
const resp = await this._adapter.send(body, this.config, adapterOptions);
|
|
186
|
+
let result = this._handleResult(this._adapter.parseResponse(resp), mergeToEntry);
|
|
187
|
+
// 响应后处理钩子(tool-calling 用),仅非续写时
|
|
188
|
+
if (this._processResponse && !mergeToEntry) {
|
|
189
|
+
result = await this._processResponse(result, { isStream: false });
|
|
190
|
+
}
|
|
191
|
+
return result;
|
|
192
|
+
}
|
|
193
|
+
} catch (error) {
|
|
194
|
+
lastError = error;
|
|
195
|
+
if (error.name === 'AbortError' || this._abortController?.signal.aborted) {
|
|
196
|
+
this._isGenerating = false; throw error;
|
|
197
|
+
}
|
|
198
|
+
if (error instanceof NetworkError && attempt < maxRetries) continue;
|
|
199
|
+
this._isGenerating = false; throw error;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
this._isGenerating = false;
|
|
203
|
+
throw lastError;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** 流式:ChatService 维护占位,adapter 只管解析 */
|
|
207
|
+
async _stream(body, adapterOptions, { placeholder, base, mergeToEntry, onProgress, onDone }) {
|
|
208
|
+
let finalMsg = null;
|
|
209
|
+
|
|
210
|
+
await this._adapter.stream(body, this.config,
|
|
211
|
+
(snap) => {
|
|
212
|
+
placeholder.content = snap.content || '';
|
|
213
|
+
if (snap.reasoningContent) placeholder.reasoningContent = snap.reasoningContent;
|
|
214
|
+
if (snap.toolCalls) placeholder.toolCalls = [...snap.toolCalls];
|
|
215
|
+
this.emit('stream-progress', snap);
|
|
216
|
+
if (onProgress) onProgress(snap);
|
|
217
|
+
},
|
|
218
|
+
async (final) => {
|
|
219
|
+
finalMsg = final;
|
|
220
|
+
|
|
221
|
+
if (this._processResponse && !mergeToEntry) {
|
|
222
|
+
finalMsg = await this._processResponse(finalMsg, {
|
|
223
|
+
isStream: true, onProgress, onDone
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (mergeToEntry && base) {
|
|
228
|
+
const newContent = base.content + (final.content || '');
|
|
229
|
+
const changes = { content: newContent, _complete: true };
|
|
230
|
+
if (final.reasoningContent) changes.reasoningContent = base.reasoning + final.reasoningContent;
|
|
231
|
+
if (final.toolCalls) changes.toolCalls = final.toolCalls;
|
|
232
|
+
this.messages.update(mergeToEntry, changes);
|
|
233
|
+
const updated = this.messages.getAll().find(m => m.id === mergeToEntry);
|
|
234
|
+
this.emit('message', updated);
|
|
235
|
+
if (onDone) onDone(updated);
|
|
236
|
+
} else {
|
|
237
|
+
placeholder._complete = true;
|
|
238
|
+
this.emit('message', finalMsg);
|
|
239
|
+
if (onDone) onDone(finalMsg);
|
|
240
|
+
}
|
|
241
|
+
},
|
|
242
|
+
adapterOptions
|
|
243
|
+
);
|
|
244
|
+
return mergeToEntry || finalMsg;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** 非流式结果落位 */
|
|
248
|
+
_handleResult(assistantMsg, mergeToEntry) {
|
|
249
|
+
if (mergeToEntry) {
|
|
250
|
+
const entry = this.messages.getAll().find(m => m.id === mergeToEntry);
|
|
251
|
+
if (!entry) {
|
|
252
|
+
this.messages.add(assistantMsg);
|
|
253
|
+
this.emit('message', assistantMsg);
|
|
254
|
+
return assistantMsg;
|
|
255
|
+
}
|
|
256
|
+
const changes = {
|
|
257
|
+
content: (entry.content || '') + (assistantMsg.content || ''),
|
|
258
|
+
_complete: true
|
|
259
|
+
};
|
|
260
|
+
if (assistantMsg.reasoningContent) {
|
|
261
|
+
changes.reasoningContent = (entry.reasoningContent || '') + assistantMsg.reasoningContent;
|
|
262
|
+
}
|
|
263
|
+
if (assistantMsg.toolCalls) changes.toolCalls = assistantMsg.toolCalls;
|
|
264
|
+
this.messages.update(mergeToEntry, changes);
|
|
265
|
+
const updated = this.messages.getAll().find(m => m.id === mergeToEntry);
|
|
266
|
+
this.emit('message', updated);
|
|
267
|
+
return updated;
|
|
268
|
+
}
|
|
269
|
+
this.messages.add(assistantMsg);
|
|
270
|
+
this.emit('message', assistantMsg);
|
|
271
|
+
return assistantMsg;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// ========== 公共 API ==========
|
|
275
|
+
async send(userInput) {
|
|
276
|
+
this._abortController = new AbortController();
|
|
277
|
+
try { return await this._request({ userInput, addUser: true, isStream: false }); }
|
|
278
|
+
finally { this._abortController = null; this._isGenerating = false; }
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
async stream(userInput, onProgress, onDone) {
|
|
282
|
+
this._abortController = new AbortController();
|
|
283
|
+
try { return await this._request({ userInput, addUser: true, isStream: true, onProgress, onDone }); }
|
|
284
|
+
finally { this._abortController = null; this._isGenerating = false; }
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
async sendExisting() {
|
|
288
|
+
this._abortController = new AbortController();
|
|
289
|
+
try { return await this._request({ addUser: false, isStream: false }); }
|
|
290
|
+
finally { this._abortController = null; this._isGenerating = false; }
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
async sendExistingStream(onProgress, onDone) {
|
|
294
|
+
this._abortController = new AbortController();
|
|
295
|
+
try { return await this._request({ addUser: false, isStream: true, onProgress, onDone }); }
|
|
296
|
+
finally { this._abortController = null; this._isGenerating = false; }
|
|
297
|
+
}
|
|
298
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 自定义错误类
|
|
3
|
+
* 用于区分不同类型的错误,方便用户通过 `error.name` 或 `instanceof` 处理
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// API 错误 (如 401 认证失败、400 参数错误、429 限流等)
|
|
7
|
+
export class APIError extends Error {
|
|
8
|
+
/**
|
|
9
|
+
* @param {string} message - 错误消息(通常来自 API 响应)
|
|
10
|
+
* @param {number} statusCode - HTTP 状态码
|
|
11
|
+
* @param {any} originalError - 原始错误对象或相关信息
|
|
12
|
+
* @param {string} responseText - 原始响应文本(如果有)
|
|
13
|
+
*/
|
|
14
|
+
constructor(message, statusCode, originalError, responseText) {
|
|
15
|
+
super(message)
|
|
16
|
+
this.name = 'APIError'
|
|
17
|
+
this.statusCode = statusCode
|
|
18
|
+
this.originalError = originalError
|
|
19
|
+
this.responseText = responseText
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// 网络错误 (fetch 失败、连接超时等)
|
|
24
|
+
export class NetworkError extends Error {
|
|
25
|
+
/**
|
|
26
|
+
* @param {string} message - 错误消息
|
|
27
|
+
* @param {any} originalError - 原始错误对象或相关信息
|
|
28
|
+
*/
|
|
29
|
+
constructor(message, originalError) {
|
|
30
|
+
super(message)
|
|
31
|
+
this.name = 'NetworkError'
|
|
32
|
+
this.originalError = originalError
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 配置错误 (缺少必要配置项、配置项类型错误等)
|
|
37
|
+
export class ConfigurationError extends Error {
|
|
38
|
+
/**
|
|
39
|
+
* @param {string} message - 错误消息
|
|
40
|
+
*/
|
|
41
|
+
constructor(message) {
|
|
42
|
+
super(message)
|
|
43
|
+
this.name = 'ConfigurationError'
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// 解析错误 (解析 API 响应失败、响应格式不正确,不符合预期等)
|
|
48
|
+
export class ParsingError extends Error {
|
|
49
|
+
/**
|
|
50
|
+
* @param {string} message - 错误消息
|
|
51
|
+
* @param {any} originalError - 原始错误对象或相关信息
|
|
52
|
+
* @param {string} responseText - 原始响应文本(如果有)
|
|
53
|
+
**/
|
|
54
|
+
constructor(message, originalError, responseText) {
|
|
55
|
+
super(message)
|
|
56
|
+
this.name = 'ParsingError'
|
|
57
|
+
this.originalError = originalError
|
|
58
|
+
this.responseText = responseText
|
|
59
|
+
}
|
|
60
|
+
}
|
package/src/core/EventEmitter.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 极简事件发射器
|
|
3
|
+
* 支持同步/异步事件,handler 返回 Promise 时自动 await
|
|
3
4
|
*/
|
|
4
5
|
export class EventEmitter {
|
|
5
6
|
constructor() {
|
|
@@ -19,6 +20,9 @@ export class EventEmitter {
|
|
|
19
20
|
else this._events.delete(event);
|
|
20
21
|
}
|
|
21
22
|
|
|
23
|
+
/**
|
|
24
|
+
* 同步触发事件
|
|
25
|
+
*/
|
|
22
26
|
emit(event, data) {
|
|
23
27
|
if (!this._events.has(event)) return;
|
|
24
28
|
for (const handler of this._events.get(event)) {
|
|
@@ -29,4 +33,19 @@ export class EventEmitter {
|
|
|
29
33
|
}
|
|
30
34
|
}
|
|
31
35
|
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 异步触发事件,逐个 await handler
|
|
39
|
+
* handler 返回 Promise 时自动等待
|
|
40
|
+
*/
|
|
41
|
+
async emitAsync(event, data) {
|
|
42
|
+
if (!this._events.has(event)) return;
|
|
43
|
+
for (const handler of this._events.get(event)) {
|
|
44
|
+
try {
|
|
45
|
+
await handler(data);
|
|
46
|
+
} catch (err) {
|
|
47
|
+
console.error(`事件 ${event} 处理出错:`, err);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
32
51
|
}
|
package/src/core/MessageStore.js
CHANGED
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
* id?: string,
|
|
6
6
|
* role: 'user'|'assistant'|'system'|'tool',
|
|
7
7
|
* content: string,
|
|
8
|
+
* images?: Array<string>, // 图片引用(URL / id),框架不存储图片数据
|
|
9
|
+
* _ephemeral?: boolean, // 临时消息:仅底部连续时参与请求,续写后转正
|
|
10
|
+
* _complete?: boolean, // 流式是否完成(false=未完成/中断)
|
|
11
|
+
* prefix?: boolean, // 前缀续写标记
|
|
8
12
|
* toolCalls?: Array,
|
|
9
13
|
* toolCallId?: string,
|
|
10
14
|
* timestamp?: number,
|
|
@@ -43,6 +47,23 @@ export class MessageStore {
|
|
|
43
47
|
return this.add({ role: 'tool', content, toolCallId, metadata });
|
|
44
48
|
}
|
|
45
49
|
|
|
50
|
+
/**
|
|
51
|
+
* 快速添加一次性的、带续写标记的 assistant 消息
|
|
52
|
+
* 适用于思维链引导、临时注入等场景
|
|
53
|
+
* @param {string} content — 引导内容
|
|
54
|
+
* @param {Object} [options] — { reasoningContent, prefix }
|
|
55
|
+
* @returns {Object} 添加的消息
|
|
56
|
+
*/
|
|
57
|
+
addOnceAssistant(content, options = {}) {
|
|
58
|
+
return this.add({
|
|
59
|
+
role: 'assistant',
|
|
60
|
+
content,
|
|
61
|
+
reasoningContent: options.reasoningContent,
|
|
62
|
+
prefix: options.prefix !== false, // 默认开启续写
|
|
63
|
+
_ephemeral: true
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
46
67
|
getLast() {
|
|
47
68
|
return this._messages[this._messages.length - 1] || null;
|
|
48
69
|
}
|
|
@@ -70,4 +91,22 @@ export class MessageStore {
|
|
|
70
91
|
}
|
|
71
92
|
return [];
|
|
72
93
|
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 更新消息:按 id 查找并合并 changes,不新增消息
|
|
97
|
+
* @param {string} id — 消息 id
|
|
98
|
+
* @param {Object} changes — 要合并的字段
|
|
99
|
+
* @returns {Object|null} 更新后的消息,未找到返回 null
|
|
100
|
+
*/
|
|
101
|
+
update(id, changes) {
|
|
102
|
+
for (const msg of this._messages) {
|
|
103
|
+
if (msg.id === id) {
|
|
104
|
+
Object.assign(msg, changes);
|
|
105
|
+
// 更新 timestamp 便于排查
|
|
106
|
+
msg.timestamp = changes.timestamp || Date.now();
|
|
107
|
+
return msg;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
73
112
|
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SystemPromptStore — 系统提示词存储
|
|
3
|
+
*
|
|
4
|
+
* 职责:管理多条 system prompt 的增删改查与开关
|
|
5
|
+
* 与 MessageStore 分离,因为 system prompt 是"AI 行为准则",不是对话事件
|
|
6
|
+
*
|
|
7
|
+
* 每条记录格式:
|
|
8
|
+
* {
|
|
9
|
+
* id: string,
|
|
10
|
+
* content: string,
|
|
11
|
+
* enabled: boolean,
|
|
12
|
+
* timestamp: number
|
|
13
|
+
* }
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export class SystemPromptStore {
|
|
17
|
+
constructor() {
|
|
18
|
+
this._prompts = [];
|
|
19
|
+
this._idCounter = 0;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 添加一条 system prompt
|
|
24
|
+
* @param {string} content — 提示词内容
|
|
25
|
+
* @param {boolean} [enabled=true] — 是否启用
|
|
26
|
+
* @returns {Object} 添加的记录
|
|
27
|
+
*/
|
|
28
|
+
add(content, enabled = true) {
|
|
29
|
+
if (!content || typeof content !== 'string' || !content.trim()) {
|
|
30
|
+
throw new Error('[SystemPromptStore] content 必须是非空字符串');
|
|
31
|
+
}
|
|
32
|
+
const record = {
|
|
33
|
+
id: `sys_${Date.now()}_${++this._idCounter}`,
|
|
34
|
+
content: content.trim(),
|
|
35
|
+
enabled: Boolean(enabled),
|
|
36
|
+
timestamp: Date.now()
|
|
37
|
+
};
|
|
38
|
+
this._prompts.push(record);
|
|
39
|
+
return record;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 删除指定索引的 system prompt
|
|
44
|
+
* @param {number} index
|
|
45
|
+
* @returns {Object} 被删除的记录
|
|
46
|
+
*/
|
|
47
|
+
remove(index) {
|
|
48
|
+
if (index < 0 || index >= this._prompts.length) {
|
|
49
|
+
throw new Error(`[SystemPromptStore] 索引越界: ${index}`);
|
|
50
|
+
}
|
|
51
|
+
return this._prompts.splice(index, 1)[0];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 切换指定索引的启用/禁用状态
|
|
56
|
+
* @param {number} index
|
|
57
|
+
* @returns {boolean} 切换后的状态
|
|
58
|
+
*/
|
|
59
|
+
toggle(index) {
|
|
60
|
+
if (index < 0 || index >= this._prompts.length) {
|
|
61
|
+
throw new Error(`[SystemPromptStore] 索引越界: ${index}`);
|
|
62
|
+
}
|
|
63
|
+
this._prompts[index].enabled = !this._prompts[index].enabled;
|
|
64
|
+
return this._prompts[index].enabled;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 更新指定索引的 content
|
|
69
|
+
* @param {number} index
|
|
70
|
+
* @param {string} content
|
|
71
|
+
*/
|
|
72
|
+
update(index, content) {
|
|
73
|
+
if (index < 0 || index >= this._prompts.length) {
|
|
74
|
+
throw new Error(`[SystemPromptStore] 索引越界: ${index}`);
|
|
75
|
+
}
|
|
76
|
+
if (!content || typeof content !== 'string' || !content.trim()) {
|
|
77
|
+
throw new Error('[SystemPromptStore] content 必须是非空字符串');
|
|
78
|
+
}
|
|
79
|
+
this._prompts[index].content = content.trim();
|
|
80
|
+
this._prompts[index].timestamp = Date.now();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 清空并用一条 content 替换(便捷方法,常用于 config.system = '...')
|
|
85
|
+
* @param {string} content
|
|
86
|
+
*/
|
|
87
|
+
set(content) {
|
|
88
|
+
this._prompts = [];
|
|
89
|
+
if (content && typeof content === 'string' && content.trim()) {
|
|
90
|
+
this.add(content, true);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 按 enabled 筛选后,转为适配器可用的格式
|
|
96
|
+
* @returns {Array<{role:'system', content:string}>}
|
|
97
|
+
*/
|
|
98
|
+
getEnabled() {
|
|
99
|
+
return this._prompts
|
|
100
|
+
.filter(p => p.enabled)
|
|
101
|
+
.map(p => ({ role: 'system', content: p.content }));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* 返回所有记录(含 enabled 状态,用于 UI 展示)
|
|
106
|
+
* @returns {Array}
|
|
107
|
+
*/
|
|
108
|
+
getAll() {
|
|
109
|
+
return [...this._prompts];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* 清空所有 system prompt
|
|
114
|
+
*/
|
|
115
|
+
clear() {
|
|
116
|
+
this._prompts = [];
|
|
117
|
+
}
|
|
118
|
+
}
|