@blueking/chat-helper 0.0.12-beta.10 → 0.0.12-beta.11
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/dist/agent/use-agent.d.ts +6 -4
- package/dist/agent/use-agent.ts.js +208 -7
- package/dist/event/ag-ui.d.ts +1 -0
- package/dist/event/ag-ui.ts.js +7 -0
- package/dist/index.d.ts +2 -1
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ApprovalInterruptTicketStatus, ResumeStatus, RunFinishedOutcomeType, type IResume } from '../event';
|
|
1
|
+
import { ApprovalInterruptTicketStatus, EventType, ResumeStatus, RunFinishedOutcomeType, type IResume } from '../event';
|
|
2
2
|
import { MessageRole, MessageStatus, UserOperation } from '../message';
|
|
3
3
|
import type { IRequestConfig, ISSEProtocol } from '../http';
|
|
4
4
|
import type { IMediatorModule } from '../mediator';
|
|
@@ -154,7 +154,7 @@ export declare const useAgent: (mediator: IMediatorModule, protocol: ISSEProtoco
|
|
|
154
154
|
};
|
|
155
155
|
runId: number;
|
|
156
156
|
threadId: string;
|
|
157
|
-
type:
|
|
157
|
+
type: EventType.RunFinished;
|
|
158
158
|
outcome?: {
|
|
159
159
|
type: RunFinishedOutcomeType.Success;
|
|
160
160
|
} | {
|
|
@@ -1090,7 +1090,7 @@ export declare const useAgent: (mediator: IMediatorModule, protocol: ISSEProtoco
|
|
|
1090
1090
|
};
|
|
1091
1091
|
runId: number;
|
|
1092
1092
|
threadId: string;
|
|
1093
|
-
type:
|
|
1093
|
+
type: EventType.RunFinished;
|
|
1094
1094
|
outcome?: {
|
|
1095
1095
|
type: RunFinishedOutcomeType.Success;
|
|
1096
1096
|
} | {
|
|
@@ -1896,13 +1896,15 @@ export declare const useAgent: (mediator: IMediatorModule, protocol: ISSEProtoco
|
|
|
1896
1896
|
pollResumeSession: (sessionCode: string) => void;
|
|
1897
1897
|
clearLongPollTimer: () => void;
|
|
1898
1898
|
userOperationStreamRequest: (sessionCode: string, operation: UserOperation, payload: IUserOperationPayload, config?: IRequestConfig) => Promise<void>;
|
|
1899
|
-
streamRequest: ({ sessionCode, url, config, resume, input, lastMessageId, }: {
|
|
1899
|
+
streamRequest: ({ sessionCode, url, config, resume, input, lastMessageId, isReconnect, }: {
|
|
1900
1900
|
sessionCode: string;
|
|
1901
1901
|
url?: string;
|
|
1902
1902
|
config?: IRequestConfig;
|
|
1903
1903
|
resume?: IResume;
|
|
1904
1904
|
input?: string;
|
|
1905
1905
|
lastMessageId?: string;
|
|
1906
|
+
/** 内部静默重连,不重置重连计数 */
|
|
1907
|
+
isReconnect?: boolean;
|
|
1906
1908
|
}) => Promise<void>;
|
|
1907
1909
|
};
|
|
1908
1910
|
export type IAgentModule = ReturnType<typeof useAgent>;
|
|
@@ -80,9 +80,38 @@ function _object_spread(target) {
|
|
|
80
80
|
return target;
|
|
81
81
|
}
|
|
82
82
|
import { ref } from 'vue';
|
|
83
|
-
import { AGUIProtocol, ApprovalInterruptTicketStatus, ResumeStatus, RunFinishedOutcomeType } from '../event/index.ts.js';
|
|
83
|
+
import { AGUIProtocol, ApprovalInterruptTicketStatus, EventType, ResumeStatus, RunFinishedOutcomeType } from '../event/index.ts.js';
|
|
84
84
|
import { MessageRole, MessageStatus, UserOperation } from '../message/index.ts.js';
|
|
85
85
|
import { SessionStatus } from '../session/type.ts.js';
|
|
86
|
+
/** SSE 静默重连最大次数(退避总时长约 23s,落在后端 orphan grace ~30s 内) */ const MAX_RECONNECT_ATTEMPTS = 5;
|
|
87
|
+
const RECONNECT_BASE_DELAY_MS = 1000;
|
|
88
|
+
const RECONNECT_MAX_DELAY_MS = 8000;
|
|
89
|
+
const getReconnectDelayMs = (attempt)=>Math.min(RECONNECT_BASE_DELAY_MS * Math.pow(2, attempt - 1), RECONNECT_MAX_DELAY_MS);
|
|
90
|
+
/**
|
|
91
|
+
* 判断流式错误是否可静默重连。
|
|
92
|
+
* 不重试:Abort、401/403 等业务 4xx(408/429 除外);可重试:网络错误、5xx、无状态码的中断。
|
|
93
|
+
*/ const isRecoverableStreamError = (error)=>{
|
|
94
|
+
var _requestError_response;
|
|
95
|
+
if (error.name === 'AbortError') {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
const requestError = error;
|
|
99
|
+
const statusFromResponse = (_requestError_response = requestError.response) === null || _requestError_response === void 0 ? void 0 : _requestError_response.status;
|
|
100
|
+
const statusFromMessage = error.message.match(/status code (\d+)/i);
|
|
101
|
+
const status = statusFromResponse !== null && statusFromResponse !== void 0 ? statusFromResponse : statusFromMessage ? Number(statusFromMessage[1]) : undefined;
|
|
102
|
+
if (status !== undefined) {
|
|
103
|
+
if (status === 408 || status === 429) {
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
if (status >= 400 && status < 500) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
if (status >= 500) {
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return true;
|
|
114
|
+
};
|
|
86
115
|
/**
|
|
87
116
|
* Agent 模块
|
|
88
117
|
* @param options - 配置选项
|
|
@@ -95,6 +124,12 @@ import { SessionStatus } from '../session/type.ts.js';
|
|
|
95
124
|
let chatAbortController = null;
|
|
96
125
|
let resumeAbortController = null;
|
|
97
126
|
let longPollTimer = null;
|
|
127
|
+
/** 用户主动中止(abortChat),禁止自动重连 */ let manualAbort = false;
|
|
128
|
+
/** 当前轮次已静默重连次数 */ let reconnectAttempt = 0;
|
|
129
|
+
/** 本轮流是否已收到 RUN_FINISHED */ let runFinished = false;
|
|
130
|
+
let reconnectTimer = null;
|
|
131
|
+
let reconnectWaitResolve = null;
|
|
132
|
+
let activeStreamContext = null;
|
|
98
133
|
const getAgentInfo = ()=>{
|
|
99
134
|
var _mediator_http;
|
|
100
135
|
isInfoLoading.value = true;
|
|
@@ -137,9 +172,115 @@ import { SessionStatus } from '../session/type.ts.js';
|
|
|
137
172
|
}
|
|
138
173
|
});
|
|
139
174
|
};
|
|
175
|
+
const clearReconnectTimer = (continued = false)=>{
|
|
176
|
+
if (reconnectTimer) {
|
|
177
|
+
clearTimeout(reconnectTimer);
|
|
178
|
+
reconnectTimer = null;
|
|
179
|
+
}
|
|
180
|
+
if (reconnectWaitResolve) {
|
|
181
|
+
const resolve = reconnectWaitResolve;
|
|
182
|
+
reconnectWaitResolve = null;
|
|
183
|
+
resolve(continued);
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
const waitForReconnectDelay = (ms)=>new Promise((resolve)=>{
|
|
187
|
+
reconnectWaitResolve = resolve;
|
|
188
|
+
reconnectTimer = setTimeout(()=>{
|
|
189
|
+
reconnectTimer = null;
|
|
190
|
+
reconnectWaitResolve = null;
|
|
191
|
+
resolve(true);
|
|
192
|
+
}, ms);
|
|
193
|
+
});
|
|
194
|
+
const resetStreamReconnectState = ()=>{
|
|
195
|
+
manualAbort = false;
|
|
196
|
+
reconnectAttempt = 0;
|
|
197
|
+
runFinished = false;
|
|
198
|
+
clearReconnectTimer(false);
|
|
199
|
+
};
|
|
200
|
+
/** 裁掉尾部尚未完成的非用户消息,避免 DLQ 重放时重复气泡 */ const pruneTrailingIncompleteMessages = ()=>{
|
|
201
|
+
var _mediator_message;
|
|
202
|
+
const list = (_mediator_message = mediator.message) === null || _mediator_message === void 0 ? void 0 : _mediator_message.list.value;
|
|
203
|
+
if (!(list === null || list === void 0 ? void 0 : list.length)) {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
while(list.length > 0){
|
|
207
|
+
const last = list[list.length - 1];
|
|
208
|
+
if (last.role === MessageRole.User) {
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
if (last.status === MessageStatus.Streaming || last.status === MessageStatus.Pending) {
|
|
212
|
+
list.pop();
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
const isSameActiveSession = (sessionCode)=>{
|
|
219
|
+
var _mediator_session_current_value, _mediator_session_current, _mediator_session;
|
|
220
|
+
return sessionCode === ((_mediator_session = mediator.session) === null || _mediator_session === void 0 ? void 0 : (_mediator_session_current = _mediator_session.current) === null || _mediator_session_current === void 0 ? void 0 : (_mediator_session_current_value = _mediator_session_current.value) === null || _mediator_session_current_value === void 0 ? void 0 : _mediator_session_current_value.sessionCode);
|
|
221
|
+
};
|
|
222
|
+
/**
|
|
223
|
+
* 尝试静默重连。
|
|
224
|
+
* @returns reconnected | finished(服务端已结束)| failed(需对外报错或中止)
|
|
225
|
+
*/ const attemptSilentReconnect = function() {
|
|
226
|
+
var _ref = _async_to_generator(function*(sessionCode) {
|
|
227
|
+
var _mediator_session_current_value, _mediator_session, _mediator_session_current_value1, _mediator_session1, _mediator_message_list_value_at, _mediator_message;
|
|
228
|
+
if (manualAbort || !isSameActiveSession(sessionCode)) {
|
|
229
|
+
return 'failed';
|
|
230
|
+
}
|
|
231
|
+
try {
|
|
232
|
+
var _mediator_session2;
|
|
233
|
+
yield (_mediator_session2 = mediator.session) === null || _mediator_session2 === void 0 ? void 0 : _mediator_session2.getSession(sessionCode);
|
|
234
|
+
} catch (e) {
|
|
235
|
+
// 状态刷新失败时沿用本地 session.status
|
|
236
|
+
}
|
|
237
|
+
if (manualAbort || !isSameActiveSession(sessionCode)) {
|
|
238
|
+
return 'failed';
|
|
239
|
+
}
|
|
240
|
+
if (((_mediator_session = mediator.session) === null || _mediator_session === void 0 ? void 0 : (_mediator_session_current_value = _mediator_session.current.value) === null || _mediator_session_current_value === void 0 ? void 0 : _mediator_session_current_value.status) !== SessionStatus.Running) {
|
|
241
|
+
return 'finished';
|
|
242
|
+
}
|
|
243
|
+
if (reconnectAttempt >= MAX_RECONNECT_ATTEMPTS) {
|
|
244
|
+
return 'failed';
|
|
245
|
+
}
|
|
246
|
+
reconnectAttempt += 1;
|
|
247
|
+
// 静默重连期间保持「生成中」态
|
|
248
|
+
isChatting.value = true;
|
|
249
|
+
const delayMs = getReconnectDelayMs(reconnectAttempt);
|
|
250
|
+
const waited = yield waitForReconnectDelay(delayMs);
|
|
251
|
+
if (!waited || manualAbort || !isSameActiveSession(sessionCode)) {
|
|
252
|
+
return 'failed';
|
|
253
|
+
}
|
|
254
|
+
if (((_mediator_session1 = mediator.session) === null || _mediator_session1 === void 0 ? void 0 : (_mediator_session_current_value1 = _mediator_session1.current.value) === null || _mediator_session_current_value1 === void 0 ? void 0 : _mediator_session_current_value1.status) !== SessionStatus.Running) {
|
|
255
|
+
return 'finished';
|
|
256
|
+
}
|
|
257
|
+
pruneTrailingIncompleteMessages();
|
|
258
|
+
const lastMessageId = (_mediator_message = mediator.message) === null || _mediator_message === void 0 ? void 0 : (_mediator_message_list_value_at = _mediator_message.list.value.at(-1)) === null || _mediator_message_list_value_at === void 0 ? void 0 : _mediator_message_list_value_at.id;
|
|
259
|
+
const ctx = activeStreamContext;
|
|
260
|
+
streamRequest({
|
|
261
|
+
sessionCode,
|
|
262
|
+
url: ctx === null || ctx === void 0 ? void 0 : ctx.url,
|
|
263
|
+
config: ctx === null || ctx === void 0 ? void 0 : ctx.config,
|
|
264
|
+
lastMessageId: lastMessageId !== undefined ? String(lastMessageId) : undefined,
|
|
265
|
+
isReconnect: true
|
|
266
|
+
});
|
|
267
|
+
return 'reconnected';
|
|
268
|
+
});
|
|
269
|
+
return function attemptSilentReconnect(sessionCode) {
|
|
270
|
+
return _ref.apply(this, arguments);
|
|
271
|
+
};
|
|
272
|
+
}();
|
|
140
273
|
const streamRequest = function() {
|
|
141
|
-
var _ref = _async_to_generator(function*({ sessionCode, url, config, resume, input, lastMessageId }) {
|
|
274
|
+
var _ref = _async_to_generator(function*({ sessionCode, url, config, resume, input, lastMessageId, isReconnect = false }) {
|
|
142
275
|
var _mediator_http;
|
|
276
|
+
if (!isReconnect) {
|
|
277
|
+
resetStreamReconnectState();
|
|
278
|
+
}
|
|
279
|
+
activeStreamContext = {
|
|
280
|
+
sessionCode,
|
|
281
|
+
url,
|
|
282
|
+
config
|
|
283
|
+
};
|
|
143
284
|
// ag-ui 协议需要注入消息模块
|
|
144
285
|
if (usedProtocol instanceof AGUIProtocol) {
|
|
145
286
|
usedProtocol.injectMessageModule(mediator.message);
|
|
@@ -154,8 +295,7 @@ import { SessionStatus } from '../session/type.ts.js';
|
|
|
154
295
|
sessionCode
|
|
155
296
|
});
|
|
156
297
|
}
|
|
157
|
-
|
|
158
|
-
const onDone = ()=>{
|
|
298
|
+
const finishSuccessfully = ()=>{
|
|
159
299
|
var _usedProtocol_onDone;
|
|
160
300
|
isChatting.value = false;
|
|
161
301
|
(_usedProtocol_onDone = usedProtocol.onDone) === null || _usedProtocol_onDone === void 0 ? void 0 : _usedProtocol_onDone.call(usedProtocol);
|
|
@@ -166,19 +306,73 @@ import { SessionStatus } from '../session/type.ts.js';
|
|
|
166
306
|
var _mediator_message;
|
|
167
307
|
const lastUserMessage = (_mediator_message = mediator.message) === null || _mediator_message === void 0 ? void 0 : _mediator_message.list.value.findLast((item)=>item.role === MessageRole.User);
|
|
168
308
|
const lastApiUserMessage = res.findLast((item)=>item.role === MessageRole.User);
|
|
169
|
-
lastUserMessage
|
|
309
|
+
if (lastUserMessage && lastApiUserMessage) {
|
|
310
|
+
lastUserMessage.id = lastApiUserMessage.id;
|
|
311
|
+
}
|
|
170
312
|
});
|
|
171
313
|
}
|
|
172
314
|
// 轮询接口,判断是否可以继续聊天
|
|
173
315
|
pollResumeSession(sessionCode);
|
|
174
316
|
};
|
|
175
|
-
const
|
|
317
|
+
const failWithError = (error)=>{
|
|
176
318
|
var _usedProtocol_onError;
|
|
177
319
|
isChatting.value = false;
|
|
178
320
|
(_usedProtocol_onError = usedProtocol.onError) === null || _usedProtocol_onError === void 0 ? void 0 : _usedProtocol_onError.call(usedProtocol, error);
|
|
179
321
|
};
|
|
322
|
+
// 事件代理
|
|
323
|
+
const onDone = ()=>{
|
|
324
|
+
// 用户 abort:FetchClient 将 AbortError 转为 onDone,不重连
|
|
325
|
+
if (manualAbort || runFinished) {
|
|
326
|
+
finishSuccessfully();
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
// 连接被干净掐断且未收到 RUN_FINISHED:尝试静默重连
|
|
330
|
+
void attemptSilentReconnect(sessionCode).then((result)=>{
|
|
331
|
+
if (result === 'reconnected') {
|
|
332
|
+
// 保持 isChatting=true,不展示错误
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
if (result === 'finished') {
|
|
336
|
+
finishSuccessfully();
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
// abort / 切会话导致的失败:静默结束,不展示错误气泡
|
|
340
|
+
if (manualAbort || !isSameActiveSession(sessionCode)) {
|
|
341
|
+
isChatting.value = false;
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
failWithError(new Error('Connection lost, please try again'));
|
|
345
|
+
});
|
|
346
|
+
};
|
|
347
|
+
const onError = (error)=>{
|
|
348
|
+
if (manualAbort) {
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
if (!isRecoverableStreamError(error)) {
|
|
352
|
+
failWithError(error);
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
void attemptSilentReconnect(sessionCode).then((result)=>{
|
|
356
|
+
if (result === 'reconnected') {
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
if (result === 'finished') {
|
|
360
|
+
finishSuccessfully();
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
if (manualAbort || !isSameActiveSession(sessionCode)) {
|
|
364
|
+
isChatting.value = false;
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
failWithError(error);
|
|
368
|
+
});
|
|
369
|
+
};
|
|
180
370
|
const onMessage = (event)=>{
|
|
181
371
|
var _usedProtocol_onMessage;
|
|
372
|
+
const typedEvent = event;
|
|
373
|
+
if ((typedEvent === null || typedEvent === void 0 ? void 0 : typedEvent.type) === EventType.RunFinished) {
|
|
374
|
+
runFinished = true;
|
|
375
|
+
}
|
|
182
376
|
(_usedProtocol_onMessage = usedProtocol.onMessage) === null || _usedProtocol_onMessage === void 0 ? void 0 : _usedProtocol_onMessage.call(usedProtocol, event);
|
|
183
377
|
};
|
|
184
378
|
const onStart = ()=>{
|
|
@@ -322,6 +516,8 @@ import { SessionStatus } from '../session/type.ts.js';
|
|
|
322
516
|
* 中止聊天(纯前端中止,后端继续处理)
|
|
323
517
|
*/ const abortChat = ()=>{
|
|
324
518
|
var _chatAbortController_abort;
|
|
519
|
+
manualAbort = true;
|
|
520
|
+
clearReconnectTimer(false);
|
|
325
521
|
chatAbortController === null || chatAbortController === void 0 ? void 0 : (_chatAbortController_abort = chatAbortController.abort) === null || _chatAbortController_abort === void 0 ? void 0 : _chatAbortController_abort.call(chatAbortController);
|
|
326
522
|
chatAbortController = null;
|
|
327
523
|
};
|
|
@@ -396,10 +592,15 @@ import { SessionStatus } from '../session/type.ts.js';
|
|
|
396
592
|
};
|
|
397
593
|
}();
|
|
398
594
|
const reset = (protocol)=>{
|
|
399
|
-
|
|
595
|
+
abortChat();
|
|
596
|
+
// 重置状态(保留 manualAbort=true,避免 abort 异步 onDone 误触发重连;下次 streamRequest 会复位)
|
|
400
597
|
usedProtocol = protocol || new AGUIProtocol();
|
|
401
598
|
info.value = null;
|
|
402
599
|
isInfoLoading.value = false;
|
|
600
|
+
isChatting.value = false;
|
|
601
|
+
activeStreamContext = null;
|
|
602
|
+
reconnectAttempt = 0;
|
|
603
|
+
runFinished = false;
|
|
403
604
|
};
|
|
404
605
|
return {
|
|
405
606
|
info,
|
package/dist/event/ag-ui.d.ts
CHANGED
|
@@ -131,6 +131,7 @@ export declare class AGUIProtocol implements ISSEProtocol {
|
|
|
131
131
|
handleTextMessageEndEvent(event: ITextMessageEndEvent): void;
|
|
132
132
|
/**
|
|
133
133
|
* 处理文本消息开始事件
|
|
134
|
+
* 断线续传时 DLQ 会重放事件:同 messageId 已存在则重置内容,避免重复气泡/文本翻倍
|
|
134
135
|
*/
|
|
135
136
|
handleTextMessageStartEvent(event: ITextMessageStartEvent): void;
|
|
136
137
|
/**
|
package/dist/event/ag-ui.ts.js
CHANGED
|
@@ -349,7 +349,14 @@ import { CustomEventName, EventType, FlowTaskState, RunFinishedOutcomeType } fro
|
|
|
349
349
|
}
|
|
350
350
|
/**
|
|
351
351
|
* 处理文本消息开始事件
|
|
352
|
+
* 断线续传时 DLQ 会重放事件:同 messageId 已存在则重置内容,避免重复气泡/文本翻倍
|
|
352
353
|
*/ handleTextMessageStartEvent(event) {
|
|
354
|
+
const existing = this.messageModule.getMessageByMessageId(event.messageId);
|
|
355
|
+
if (existing) {
|
|
356
|
+
existing.content = '';
|
|
357
|
+
existing.status = MessageStatus.Streaming;
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
353
360
|
this.messageModule.plusMessage({
|
|
354
361
|
role: event.role,
|
|
355
362
|
content: '',
|
package/dist/index.d.ts
CHANGED
|
@@ -1898,13 +1898,14 @@ export declare const useChatHelper: (options: IUseChatHelperOptions) => {
|
|
|
1898
1898
|
pollResumeSession: (sessionCode: string) => void;
|
|
1899
1899
|
clearLongPollTimer: () => void;
|
|
1900
1900
|
userOperationStreamRequest: (sessionCode: string, operation: import("./message").UserOperation, payload: import("./message").IUserOperationPayload, config?: import("./http").IRequestConfig) => Promise<void>;
|
|
1901
|
-
streamRequest: ({ sessionCode, url, config, resume, input, lastMessageId, }: {
|
|
1901
|
+
streamRequest: ({ sessionCode, url, config, resume, input, lastMessageId, isReconnect, }: {
|
|
1902
1902
|
sessionCode: string;
|
|
1903
1903
|
url?: string;
|
|
1904
1904
|
config?: import("./http").IRequestConfig;
|
|
1905
1905
|
resume?: import("./event").IResume;
|
|
1906
1906
|
input?: string;
|
|
1907
1907
|
lastMessageId?: string;
|
|
1908
|
+
isReconnect?: boolean;
|
|
1908
1909
|
}) => Promise<void>;
|
|
1909
1910
|
};
|
|
1910
1911
|
session: {
|