@huyooo/ai-chat-core 0.2.19 → 0.2.21
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/events.d.ts +452 -0
- package/dist/events.js +1 -0
- package/dist/index.d.ts +202 -550
- package/dist/index.js +1 -1
- package/package.json +23 -4
- package/src/agent.ts +399 -0
- package/src/constants.ts +125 -0
- package/src/events.ts +797 -0
- package/src/index.ts +309 -0
- package/src/internal/update-plan.ts +2 -0
- package/src/internal/web-search.ts +78 -0
- package/src/mcp/client-manager.ts +301 -0
- package/src/mcp/index.ts +2 -0
- package/src/mcp/types.ts +43 -0
- package/src/providers/context-compressor.ts +149 -0
- package/src/providers/index.ts +120 -0
- package/src/providers/model-registry.ts +320 -0
- package/src/providers/orchestrator.ts +761 -0
- package/src/providers/protocols/anthropic.ts +406 -0
- package/src/providers/protocols/ark.ts +362 -0
- package/src/providers/protocols/deepseek.ts +344 -0
- package/src/providers/protocols/error-utils.ts +74 -0
- package/src/providers/protocols/gemini.ts +350 -0
- package/src/providers/protocols/index.ts +36 -0
- package/src/providers/protocols/openai.ts +420 -0
- package/src/providers/protocols/qwen.ts +326 -0
- package/src/providers/protocols/types.ts +189 -0
- package/src/providers/types.ts +272 -0
- package/src/providers/unified-adapter.ts +367 -0
- package/src/router.ts +72 -0
- package/src/test-utils/mock-sse.ts +32 -0
- package/src/tools.ts +162 -0
- package/src/types.ts +531 -0
- package/src/utils.ts +86 -0
package/src/events.ts
ADDED
|
@@ -0,0 +1,797 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Chat 事件系统
|
|
3
|
+
*
|
|
4
|
+
* 模块化的事件类型定义,用于 Agent 与前端之间的通信。
|
|
5
|
+
*
|
|
6
|
+
* 设计原则:
|
|
7
|
+
* 1. 每种事件类型有明确的数据结构
|
|
8
|
+
* 2. 事件分组便于管理和扩展
|
|
9
|
+
* 3. 类型安全,避免 unknown
|
|
10
|
+
* 4. 统一的时间追踪(开始/结束/耗时)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// ==================== 基础类型 ====================
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 搜索结果项(统一契约)
|
|
17
|
+
* 无论搜索来源是厂商原生(ARK annotation)还是 Tavily 工具,协议/编排层都会归一为此结构,
|
|
18
|
+
* 前端只需依赖 title/url/snippet 即可。
|
|
19
|
+
*/
|
|
20
|
+
export interface SearchResult {
|
|
21
|
+
title: string;
|
|
22
|
+
url: string;
|
|
23
|
+
snippet: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** 工具调用状态 */
|
|
27
|
+
export type ToolCallStatus = 'pending' | 'running' | 'success' | 'error';
|
|
28
|
+
|
|
29
|
+
/** 工具调用信息(用于前端状态管理) */
|
|
30
|
+
export interface ToolCallInfo {
|
|
31
|
+
id: string;
|
|
32
|
+
name: string;
|
|
33
|
+
args: Record<string, unknown>;
|
|
34
|
+
status: ToolCallStatus;
|
|
35
|
+
result?: string;
|
|
36
|
+
error?: string;
|
|
37
|
+
startedAt?: number;
|
|
38
|
+
duration?: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Token 使用统计 */
|
|
42
|
+
export interface TokenUsage {
|
|
43
|
+
promptTokens: number;
|
|
44
|
+
completionTokens: number;
|
|
45
|
+
totalTokens: number;
|
|
46
|
+
/** 思考 token 数(如果有) */
|
|
47
|
+
reasoningTokens?: number;
|
|
48
|
+
/** 缓存命中 token 数(如果有) */
|
|
49
|
+
cachedTokens?: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ==================== 错误类型 ====================
|
|
53
|
+
|
|
54
|
+
/** 错误类别 - 参考 AI SDK 设计 */
|
|
55
|
+
export type ErrorCategory =
|
|
56
|
+
| 'api' // API 调用错误(网络、认证、服务端错误)
|
|
57
|
+
| 'rate_limit' // 速率限制
|
|
58
|
+
| 'validation' // 参数验证错误
|
|
59
|
+
| 'tool' // 工具执行错误
|
|
60
|
+
| 'timeout' // 超时错误
|
|
61
|
+
| 'abort' // 用户取消
|
|
62
|
+
| 'parse' // 解析错误(JSON、响应格式)
|
|
63
|
+
| 'unknown'; // 未知错误
|
|
64
|
+
|
|
65
|
+
/** 错误详情 - 结构化错误信息 */
|
|
66
|
+
export interface ErrorDetails {
|
|
67
|
+
/** 错误分类 */
|
|
68
|
+
category: ErrorCategory;
|
|
69
|
+
/** 人类可读的错误信息 */
|
|
70
|
+
message: string;
|
|
71
|
+
/** 错误代码(如 'RATE_LIMIT', 'NETWORK_ERROR') */
|
|
72
|
+
code?: string;
|
|
73
|
+
/** HTTP 状态码(如果适用) */
|
|
74
|
+
statusCode?: number;
|
|
75
|
+
/** HTTP 状态文本 */
|
|
76
|
+
statusText?: string;
|
|
77
|
+
/** 是否可重试 */
|
|
78
|
+
retryable?: boolean;
|
|
79
|
+
/** 重试等待时间(秒) */
|
|
80
|
+
retryAfter?: number;
|
|
81
|
+
/** 原始错误信息(用于调试) */
|
|
82
|
+
cause?: string;
|
|
83
|
+
/** 错误发生的上下文(如模型名、工具名) */
|
|
84
|
+
context?: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ==================== 思考相关事件 ====================
|
|
88
|
+
|
|
89
|
+
/** 思考开始事件 */
|
|
90
|
+
export interface ThinkingStartEvent {
|
|
91
|
+
type: 'thinking_start';
|
|
92
|
+
data: {
|
|
93
|
+
/** 开始时间戳(毫秒) */
|
|
94
|
+
startedAt: number;
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** 思考内容增量事件 */
|
|
99
|
+
export interface ThinkingDeltaEvent {
|
|
100
|
+
type: 'thinking_delta';
|
|
101
|
+
data: {
|
|
102
|
+
/** 增量内容 */
|
|
103
|
+
content: string;
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** 思考结束事件 */
|
|
108
|
+
export interface ThinkingEndEvent {
|
|
109
|
+
type: 'thinking_end';
|
|
110
|
+
data: {
|
|
111
|
+
/** 结束时间戳(毫秒) */
|
|
112
|
+
endedAt: number;
|
|
113
|
+
/** 思考耗时(毫秒) */
|
|
114
|
+
duration: number;
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** 思考相关事件联合类型 */
|
|
119
|
+
export type ThinkingEvent = ThinkingStartEvent | ThinkingDeltaEvent | ThinkingEndEvent;
|
|
120
|
+
|
|
121
|
+
// ==================== 搜索相关事件 ====================
|
|
122
|
+
|
|
123
|
+
/** 搜索开始事件 */
|
|
124
|
+
export interface SearchStartEvent {
|
|
125
|
+
type: 'search_start';
|
|
126
|
+
data: {
|
|
127
|
+
/** 搜索查询词 */
|
|
128
|
+
query: string;
|
|
129
|
+
/** 开始时间戳(毫秒) */
|
|
130
|
+
startedAt: number;
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** 搜索结果事件(成功完成) */
|
|
135
|
+
export interface SearchResultEvent {
|
|
136
|
+
type: 'search_result';
|
|
137
|
+
data: {
|
|
138
|
+
/** 搜索结果列表 */
|
|
139
|
+
results: SearchResult[];
|
|
140
|
+
/** 结束时间戳(毫秒) */
|
|
141
|
+
endedAt: number;
|
|
142
|
+
/** 搜索耗时(毫秒) */
|
|
143
|
+
duration: number;
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** 搜索结束事件(失败或取消) */
|
|
148
|
+
export interface SearchEndEvent {
|
|
149
|
+
type: 'search_end';
|
|
150
|
+
data: {
|
|
151
|
+
/** 是否成功 */
|
|
152
|
+
success: boolean;
|
|
153
|
+
/** 错误信息(失败时) */
|
|
154
|
+
error?: string;
|
|
155
|
+
/** 结束时间戳(毫秒) */
|
|
156
|
+
endedAt: number;
|
|
157
|
+
/** 搜索耗时(毫秒) */
|
|
158
|
+
duration: number;
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** 搜索相关事件联合类型 */
|
|
163
|
+
export type SearchEvent = SearchStartEvent | SearchResultEvent | SearchEndEvent;
|
|
164
|
+
|
|
165
|
+
// ==================== 工具相关事件 ====================
|
|
166
|
+
|
|
167
|
+
/** 工具调用开始事件 */
|
|
168
|
+
export interface ToolCallStartEvent {
|
|
169
|
+
type: 'tool_call_start';
|
|
170
|
+
data: {
|
|
171
|
+
/** 工具调用 ID */
|
|
172
|
+
id: string;
|
|
173
|
+
/** 工具名称 */
|
|
174
|
+
name: string;
|
|
175
|
+
/** 工具参数 */
|
|
176
|
+
args: Record<string, unknown>;
|
|
177
|
+
/** 开始时间戳(毫秒) */
|
|
178
|
+
startedAt: number;
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** 副作用定义(从 types.ts 复制,避免循环依赖) */
|
|
183
|
+
export interface SideEffect {
|
|
184
|
+
type: string;
|
|
185
|
+
success: boolean;
|
|
186
|
+
data?: unknown;
|
|
187
|
+
message?: string;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** 工具调用结果事件 */
|
|
191
|
+
export interface ToolCallResultEvent {
|
|
192
|
+
type: 'tool_call_result';
|
|
193
|
+
data: {
|
|
194
|
+
/** 工具调用 ID */
|
|
195
|
+
id: string;
|
|
196
|
+
/** 工具名称 */
|
|
197
|
+
name: string;
|
|
198
|
+
/** 执行结果 */
|
|
199
|
+
result: string;
|
|
200
|
+
/** 是否成功 */
|
|
201
|
+
success: boolean;
|
|
202
|
+
/** 错误信息(失败时) */
|
|
203
|
+
error?: string;
|
|
204
|
+
/** 结束时间戳(毫秒) */
|
|
205
|
+
endedAt: number;
|
|
206
|
+
/** 执行耗时(毫秒) */
|
|
207
|
+
duration: number;
|
|
208
|
+
/**
|
|
209
|
+
* 工具副作用
|
|
210
|
+
* 前端可根据此字段处理通知、刷新文件列表等
|
|
211
|
+
*/
|
|
212
|
+
sideEffects?: SideEffect[];
|
|
213
|
+
/**
|
|
214
|
+
* 结果类型(用于前端渲染)
|
|
215
|
+
*
|
|
216
|
+
* 当工具定义了 resultType 且执行成功时,前端会根据此类型生成对应的 ContentPart
|
|
217
|
+
* 例如:resultType: 'weather' 会生成 { type: 'weather', ...result }
|
|
218
|
+
*/
|
|
219
|
+
resultType?: string;
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** 工具输出增量事件(用于 stdout/stderr 流式展示) */
|
|
224
|
+
export interface ToolCallOutputEvent {
|
|
225
|
+
type: 'tool_call_output';
|
|
226
|
+
data: {
|
|
227
|
+
/** 工具调用 ID */
|
|
228
|
+
id: string;
|
|
229
|
+
/** 工具名称 */
|
|
230
|
+
name: string;
|
|
231
|
+
/** 输出流类型 */
|
|
232
|
+
stream: 'stdout' | 'stderr';
|
|
233
|
+
/** 输出增量内容 */
|
|
234
|
+
chunk: string;
|
|
235
|
+
/** 时间戳 */
|
|
236
|
+
at: number;
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** 工具执行批准请求事件(manual 模式) */
|
|
241
|
+
export interface ToolApprovalRequestEvent {
|
|
242
|
+
type: 'tool_approval_request';
|
|
243
|
+
data: {
|
|
244
|
+
/** 工具调用 ID */
|
|
245
|
+
id: string;
|
|
246
|
+
/** 工具名称 */
|
|
247
|
+
name: string;
|
|
248
|
+
/** 工具参数 */
|
|
249
|
+
args: Record<string, unknown>;
|
|
250
|
+
/** 请求时间戳 */
|
|
251
|
+
requestedAt: number;
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* 客户端工具调用请求事件(透传模式)
|
|
257
|
+
*
|
|
258
|
+
* 用于用户自定义工具:
|
|
259
|
+
* - AI 返回 tool_call
|
|
260
|
+
* - 服务端不执行,透传给客户端
|
|
261
|
+
* - 客户端执行后,发新请求继续对话
|
|
262
|
+
*/
|
|
263
|
+
export interface ToolCallRequestEvent {
|
|
264
|
+
type: 'tool_call_request';
|
|
265
|
+
data: {
|
|
266
|
+
/** 工具调用 ID */
|
|
267
|
+
id: string;
|
|
268
|
+
/** 工具名称 */
|
|
269
|
+
name: string;
|
|
270
|
+
/** 工具参数 */
|
|
271
|
+
args: Record<string, unknown>;
|
|
272
|
+
/** 请求时间戳 */
|
|
273
|
+
requestedAt: number;
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** 工具相关事件联合类型 */
|
|
278
|
+
export type ToolEvent = ToolCallStartEvent | ToolCallResultEvent | ToolApprovalRequestEvent | ToolCallOutputEvent | ToolCallRequestEvent;
|
|
279
|
+
|
|
280
|
+
// ==================== 文本相关事件 ====================
|
|
281
|
+
|
|
282
|
+
/** 文本增量事件 */
|
|
283
|
+
export interface TextDeltaEvent {
|
|
284
|
+
type: 'text_delta';
|
|
285
|
+
data: {
|
|
286
|
+
/** 增量文本 */
|
|
287
|
+
content: string;
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/** 文本相关事件联合类型 */
|
|
292
|
+
export type TextEvent = TextDeltaEvent;
|
|
293
|
+
|
|
294
|
+
// ==================== 计划相关 ====================
|
|
295
|
+
|
|
296
|
+
/** 计划步骤状态 */
|
|
297
|
+
export type PlanStepStatus = 'pending' | 'in_progress' | 'done' | 'failed';
|
|
298
|
+
|
|
299
|
+
/** 计划步骤 */
|
|
300
|
+
export interface PlanStep {
|
|
301
|
+
/** 步骤 ID(模型生成,如 "1", "scan", "move-images") */
|
|
302
|
+
id: string;
|
|
303
|
+
/** 步骤描述 */
|
|
304
|
+
title: string;
|
|
305
|
+
/** 步骤状态 */
|
|
306
|
+
status: PlanStepStatus;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// ==================== 状态事件 ====================
|
|
310
|
+
|
|
311
|
+
/** 完成事件 */
|
|
312
|
+
export interface DoneEvent {
|
|
313
|
+
type: 'done';
|
|
314
|
+
data: {
|
|
315
|
+
/** 完整的最终文本 */
|
|
316
|
+
text: string;
|
|
317
|
+
/** Token 使用统计 */
|
|
318
|
+
usage?: TokenUsage;
|
|
319
|
+
/** 总耗时(毫秒) */
|
|
320
|
+
duration?: number;
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/** 错误事件 - 结构化错误信息 */
|
|
325
|
+
export interface ErrorEvent {
|
|
326
|
+
type: 'error';
|
|
327
|
+
data: ErrorDetails;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/** 中止事件 - 用户主动取消 */
|
|
331
|
+
export interface AbortEvent {
|
|
332
|
+
type: 'abort';
|
|
333
|
+
data: {
|
|
334
|
+
/** 中止原因 */
|
|
335
|
+
reason?: string;
|
|
336
|
+
/** 中止时间戳 */
|
|
337
|
+
abortedAt: number;
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/** 状态相关事件联合类型 */
|
|
342
|
+
export type StatusEvent = DoneEvent | ErrorEvent | AbortEvent;
|
|
343
|
+
|
|
344
|
+
// ==================== 步骤追踪事件 ====================
|
|
345
|
+
|
|
346
|
+
/** 步骤开始事件 */
|
|
347
|
+
export interface StepStartEvent {
|
|
348
|
+
type: 'step_start';
|
|
349
|
+
data: {
|
|
350
|
+
/** 步骤编号(从 1 开始) */
|
|
351
|
+
stepNumber: number;
|
|
352
|
+
/** 步骤描述(可选) */
|
|
353
|
+
description?: string;
|
|
354
|
+
/** 开始时间戳 */
|
|
355
|
+
startedAt: number;
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/** 步骤结束事件 */
|
|
360
|
+
export interface StepEndEvent {
|
|
361
|
+
type: 'step_end';
|
|
362
|
+
data: {
|
|
363
|
+
/** 步骤编号 */
|
|
364
|
+
stepNumber: number;
|
|
365
|
+
/** 结束时间戳 */
|
|
366
|
+
endedAt: number;
|
|
367
|
+
/** 耗时(毫秒) */
|
|
368
|
+
duration: number;
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/** 步骤相关事件联合类型 */
|
|
373
|
+
export type StepEvent = StepStartEvent | StepEndEvent;
|
|
374
|
+
|
|
375
|
+
// ==================== Agent 状态事件 ====================
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Agent 当前阶段:前端根据此字段直接渲染 loading 状态,不需要「猜」。
|
|
379
|
+
*
|
|
380
|
+
* - thinking:模型正在思考(初始请求 / 工具执行完后等待下一轮)→ 显示「正在思考...」
|
|
381
|
+
* - null:有可见活动(思考/搜索/工具/文字流),不需要额外 loading
|
|
382
|
+
*/
|
|
383
|
+
export type AgentPhase = 'thinking' | null;
|
|
384
|
+
|
|
385
|
+
/** Agent 状态事件(前端用于渲染 loading 提示) */
|
|
386
|
+
export interface AgentStatusEvent {
|
|
387
|
+
type: 'agent_status';
|
|
388
|
+
data: {
|
|
389
|
+
/** 当前阶段 */
|
|
390
|
+
phase: AgentPhase;
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/** 创建 agent_status 事件 */
|
|
395
|
+
export function createAgentStatus(phase: AgentPhase): AgentStatusEvent {
|
|
396
|
+
return { type: 'agent_status', data: { phase } };
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// ==================== 聚合类型 ====================
|
|
400
|
+
|
|
401
|
+
/** 所有事件类型 */
|
|
402
|
+
export type ChatEvent =
|
|
403
|
+
| ThinkingEvent
|
|
404
|
+
| SearchEvent
|
|
405
|
+
| ToolEvent
|
|
406
|
+
| TextEvent
|
|
407
|
+
| StatusEvent
|
|
408
|
+
| StepEvent
|
|
409
|
+
| AgentStatusEvent;
|
|
410
|
+
|
|
411
|
+
/** 事件类型字符串 */
|
|
412
|
+
export type ChatEventType = ChatEvent['type'];
|
|
413
|
+
|
|
414
|
+
/** 所有合法事件类型(用于契约测试:Orchestrator/适配器产出必须在此集合内) */
|
|
415
|
+
export const CHAT_EVENT_TYPES: readonly ChatEventType[] = [
|
|
416
|
+
'thinking_start', 'thinking_delta', 'thinking_end',
|
|
417
|
+
'search_start', 'search_result', 'search_end',
|
|
418
|
+
'tool_call_start', 'tool_call_result', 'tool_call_output', 'tool_approval_request', 'tool_call_request',
|
|
419
|
+
'text_delta',
|
|
420
|
+
'done', 'error', 'abort',
|
|
421
|
+
'step_start', 'step_end',
|
|
422
|
+
'agent_status',
|
|
423
|
+
] as const;
|
|
424
|
+
|
|
425
|
+
// ==================== 工具函数 ====================
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* 创建思考开始事件
|
|
429
|
+
*/
|
|
430
|
+
export function createThinkingStart(): ThinkingStartEvent {
|
|
431
|
+
return {
|
|
432
|
+
type: 'thinking_start',
|
|
433
|
+
data: { startedAt: Date.now() }
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* 创建思考增量事件
|
|
439
|
+
*/
|
|
440
|
+
export function createThinkingDelta(content: string): ThinkingDeltaEvent {
|
|
441
|
+
return {
|
|
442
|
+
type: 'thinking_delta',
|
|
443
|
+
data: { content }
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* 创建思考结束事件
|
|
449
|
+
*/
|
|
450
|
+
export function createThinkingEnd(startedAt: number): ThinkingEndEvent {
|
|
451
|
+
const endedAt = Date.now();
|
|
452
|
+
return {
|
|
453
|
+
type: 'thinking_end',
|
|
454
|
+
data: {
|
|
455
|
+
endedAt,
|
|
456
|
+
duration: endedAt - startedAt
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* 创建搜索开始事件
|
|
463
|
+
*/
|
|
464
|
+
export function createSearchStart(query: string): SearchStartEvent {
|
|
465
|
+
return {
|
|
466
|
+
type: 'search_start',
|
|
467
|
+
data: { query, startedAt: Date.now() }
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* 创建搜索结果事件(成功)
|
|
473
|
+
*/
|
|
474
|
+
export function createSearchResult(results: SearchResult[], startedAt: number): SearchResultEvent {
|
|
475
|
+
const endedAt = Date.now();
|
|
476
|
+
return {
|
|
477
|
+
type: 'search_result',
|
|
478
|
+
data: {
|
|
479
|
+
results,
|
|
480
|
+
endedAt,
|
|
481
|
+
duration: endedAt - startedAt
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* 创建搜索结束事件(失败或取消)
|
|
488
|
+
*/
|
|
489
|
+
export function createSearchEnd(
|
|
490
|
+
success: boolean,
|
|
491
|
+
startedAt: number,
|
|
492
|
+
error?: string
|
|
493
|
+
): SearchEndEvent {
|
|
494
|
+
const endedAt = Date.now();
|
|
495
|
+
return {
|
|
496
|
+
type: 'search_end',
|
|
497
|
+
data: {
|
|
498
|
+
success,
|
|
499
|
+
error,
|
|
500
|
+
endedAt,
|
|
501
|
+
duration: endedAt - startedAt
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* 创建工具调用开始事件
|
|
508
|
+
*/
|
|
509
|
+
export function createToolCallStart(
|
|
510
|
+
id: string,
|
|
511
|
+
name: string,
|
|
512
|
+
args: Record<string, unknown>
|
|
513
|
+
): ToolCallStartEvent {
|
|
514
|
+
return {
|
|
515
|
+
type: 'tool_call_start',
|
|
516
|
+
data: { id, name, args, startedAt: Date.now() }
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* 创建工具调用结果事件
|
|
522
|
+
*/
|
|
523
|
+
export function createToolCallResult(
|
|
524
|
+
id: string,
|
|
525
|
+
name: string,
|
|
526
|
+
result: string,
|
|
527
|
+
success: boolean,
|
|
528
|
+
startedAt: number,
|
|
529
|
+
error?: string,
|
|
530
|
+
sideEffects?: SideEffect[],
|
|
531
|
+
resultType?: string
|
|
532
|
+
): ToolCallResultEvent {
|
|
533
|
+
const endedAt = Date.now();
|
|
534
|
+
return {
|
|
535
|
+
type: 'tool_call_result',
|
|
536
|
+
data: {
|
|
537
|
+
id,
|
|
538
|
+
name,
|
|
539
|
+
result,
|
|
540
|
+
success,
|
|
541
|
+
error,
|
|
542
|
+
endedAt,
|
|
543
|
+
duration: endedAt - startedAt,
|
|
544
|
+
sideEffects,
|
|
545
|
+
resultType
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* 创建工具输出增量事件
|
|
552
|
+
*/
|
|
553
|
+
export function createToolCallOutput(
|
|
554
|
+
id: string,
|
|
555
|
+
name: string,
|
|
556
|
+
stream: 'stdout' | 'stderr',
|
|
557
|
+
chunk: string
|
|
558
|
+
): ToolCallOutputEvent {
|
|
559
|
+
return {
|
|
560
|
+
type: 'tool_call_output',
|
|
561
|
+
data: {
|
|
562
|
+
id,
|
|
563
|
+
name,
|
|
564
|
+
stream,
|
|
565
|
+
chunk,
|
|
566
|
+
at: Date.now(),
|
|
567
|
+
},
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* 创建客户端工具调用请求事件(透传模式)
|
|
573
|
+
*
|
|
574
|
+
* 用于用户自定义工具,服务端不执行,透传给客户端
|
|
575
|
+
*/
|
|
576
|
+
export function createToolCallRequest(
|
|
577
|
+
id: string,
|
|
578
|
+
name: string,
|
|
579
|
+
args: Record<string, unknown>
|
|
580
|
+
): ToolCallRequestEvent {
|
|
581
|
+
return {
|
|
582
|
+
type: 'tool_call_request',
|
|
583
|
+
data: {
|
|
584
|
+
id,
|
|
585
|
+
name,
|
|
586
|
+
args,
|
|
587
|
+
requestedAt: Date.now(),
|
|
588
|
+
},
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* 创建文本增量事件
|
|
594
|
+
*/
|
|
595
|
+
export function createTextDelta(content: string): TextDeltaEvent {
|
|
596
|
+
return {
|
|
597
|
+
type: 'text_delta',
|
|
598
|
+
data: { content }
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* 创建完成事件
|
|
604
|
+
*/
|
|
605
|
+
export function createDone(text: string, usage?: TokenUsage, duration?: number): DoneEvent {
|
|
606
|
+
return {
|
|
607
|
+
type: 'done',
|
|
608
|
+
data: { text, usage, duration }
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/**
|
|
613
|
+
* 创建错误事件(完整版)
|
|
614
|
+
*/
|
|
615
|
+
export function createError(details: ErrorDetails): ErrorEvent {
|
|
616
|
+
return {
|
|
617
|
+
type: 'error',
|
|
618
|
+
data: details
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
* 创建 API 错误事件
|
|
624
|
+
*/
|
|
625
|
+
export function createApiError(
|
|
626
|
+
message: string,
|
|
627
|
+
options: {
|
|
628
|
+
code?: string;
|
|
629
|
+
statusCode?: number;
|
|
630
|
+
statusText?: string;
|
|
631
|
+
retryable?: boolean;
|
|
632
|
+
retryAfter?: number;
|
|
633
|
+
cause?: string;
|
|
634
|
+
context?: string;
|
|
635
|
+
} = {}
|
|
636
|
+
): ErrorEvent {
|
|
637
|
+
return createError({
|
|
638
|
+
category: 'api',
|
|
639
|
+
message,
|
|
640
|
+
...options
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* 创建速率限制错误事件
|
|
646
|
+
*/
|
|
647
|
+
export function createRateLimitError(
|
|
648
|
+
message: string,
|
|
649
|
+
retryAfter?: number,
|
|
650
|
+
context?: string
|
|
651
|
+
): ErrorEvent {
|
|
652
|
+
return createError({
|
|
653
|
+
category: 'rate_limit',
|
|
654
|
+
message,
|
|
655
|
+
code: 'RATE_LIMIT',
|
|
656
|
+
statusCode: 429,
|
|
657
|
+
retryable: true,
|
|
658
|
+
retryAfter,
|
|
659
|
+
context
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* 创建工具错误事件
|
|
665
|
+
*/
|
|
666
|
+
export function createToolError(
|
|
667
|
+
message: string,
|
|
668
|
+
toolName: string,
|
|
669
|
+
cause?: string
|
|
670
|
+
): ErrorEvent {
|
|
671
|
+
return createError({
|
|
672
|
+
category: 'tool',
|
|
673
|
+
message,
|
|
674
|
+
code: 'TOOL_ERROR',
|
|
675
|
+
context: toolName,
|
|
676
|
+
cause,
|
|
677
|
+
retryable: false
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/**
|
|
682
|
+
* 创建超时错误事件
|
|
683
|
+
*/
|
|
684
|
+
export function createTimeoutError(
|
|
685
|
+
message: string,
|
|
686
|
+
context?: string
|
|
687
|
+
): ErrorEvent {
|
|
688
|
+
return createError({
|
|
689
|
+
category: 'timeout',
|
|
690
|
+
message,
|
|
691
|
+
code: 'TIMEOUT',
|
|
692
|
+
retryable: true,
|
|
693
|
+
context
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
/**
|
|
698
|
+
* 创建解析错误事件
|
|
699
|
+
*/
|
|
700
|
+
export function createParseError(
|
|
701
|
+
message: string,
|
|
702
|
+
cause?: string
|
|
703
|
+
): ErrorEvent {
|
|
704
|
+
return createError({
|
|
705
|
+
category: 'parse',
|
|
706
|
+
message,
|
|
707
|
+
code: 'PARSE_ERROR',
|
|
708
|
+
cause,
|
|
709
|
+
retryable: false
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
/**
|
|
714
|
+
* 创建中止事件
|
|
715
|
+
*/
|
|
716
|
+
export function createAbort(reason?: string): AbortEvent {
|
|
717
|
+
return {
|
|
718
|
+
type: 'abort',
|
|
719
|
+
data: {
|
|
720
|
+
reason,
|
|
721
|
+
abortedAt: Date.now(),
|
|
722
|
+
}
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
/**
|
|
727
|
+
* 创建步骤开始事件
|
|
728
|
+
*/
|
|
729
|
+
export function createStepStart(stepNumber: number, description?: string): StepStartEvent {
|
|
730
|
+
return {
|
|
731
|
+
type: 'step_start',
|
|
732
|
+
data: { stepNumber, description, startedAt: Date.now() }
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
/**
|
|
737
|
+
* 创建步骤结束事件
|
|
738
|
+
*/
|
|
739
|
+
export function createStepEnd(stepNumber: number, startedAt: number): StepEndEvent {
|
|
740
|
+
const endedAt = Date.now();
|
|
741
|
+
return {
|
|
742
|
+
type: 'step_end',
|
|
743
|
+
data: {
|
|
744
|
+
stepNumber,
|
|
745
|
+
endedAt,
|
|
746
|
+
duration: endedAt - startedAt
|
|
747
|
+
}
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// ==================== 类型守卫 ====================
|
|
752
|
+
|
|
753
|
+
/** 检查是否为思考事件 */
|
|
754
|
+
export function isThinkingEvent(event: ChatEvent): event is ThinkingEvent {
|
|
755
|
+
return event.type.startsWith('thinking_');
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
/** 检查是否为搜索事件 */
|
|
759
|
+
export function isSearchEvent(event: ChatEvent): event is SearchEvent {
|
|
760
|
+
return event.type.startsWith('search_');
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
/** 检查是否为工具事件 */
|
|
764
|
+
export function isToolEvent(event: ChatEvent): event is ToolEvent {
|
|
765
|
+
return event.type.startsWith('tool_');
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
/** 检查是否为文本事件 */
|
|
769
|
+
export function isTextEvent(event: ChatEvent): event is TextEvent {
|
|
770
|
+
return event.type === 'text_delta';
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
/** 检查是否为状态事件 */
|
|
774
|
+
export function isStatusEvent(event: ChatEvent): event is StatusEvent {
|
|
775
|
+
return event.type === 'done' || event.type === 'error' || event.type === 'abort';
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
/** 检查是否为错误事件 */
|
|
779
|
+
export function isErrorEvent(event: ChatEvent): event is ErrorEvent {
|
|
780
|
+
return event.type === 'error';
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
/** 检查是否为中止事件 */
|
|
784
|
+
export function isAbortEvent(event: ChatEvent): event is AbortEvent {
|
|
785
|
+
return event.type === 'abort';
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
/** 检查是否为步骤事件 */
|
|
789
|
+
export function isStepEvent(event: ChatEvent): event is StepEvent {
|
|
790
|
+
return event.type.startsWith('step_');
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
/** 检查错误是否可重试 */
|
|
794
|
+
export function isRetryableError(event: ChatEvent): boolean {
|
|
795
|
+
if (event.type !== 'error') return false;
|
|
796
|
+
return event.data.retryable === true;
|
|
797
|
+
}
|