@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
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider 统一类型定义
|
|
3
|
+
*
|
|
4
|
+
* 核心设计原则:
|
|
5
|
+
* 1. Adapter 只负责 API 格式转换,不处理业务逻辑
|
|
6
|
+
* 2. Orchestrator 统一处理工具调用循环、消息历史、事件发射
|
|
7
|
+
* 3. 所有 Adapter 返回标准化的 StreamChunk
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { ChatMessage, Tool, ToolResult } from '../types';
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
/** 简化的工具定义(供 Adapter 使用) */
|
|
14
|
+
import type { JsonSchemaObject } from '../types';
|
|
15
|
+
|
|
16
|
+
export interface SimpleToolDefinition {
|
|
17
|
+
name: string;
|
|
18
|
+
description: string;
|
|
19
|
+
parameters: JsonSchemaObject;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// ==================== 标准化流式响应 ====================
|
|
23
|
+
|
|
24
|
+
/** 流式响应块类型 */
|
|
25
|
+
export type StreamChunkType =
|
|
26
|
+
| 'text' // 文本内容
|
|
27
|
+
| 'thinking' // 思考内容
|
|
28
|
+
| 'thinking_done' // 思考完成
|
|
29
|
+
| 'tool_call' // 工具调用请求
|
|
30
|
+
| 'search_result' // 搜索结果
|
|
31
|
+
| 'done' // 响应完成
|
|
32
|
+
| 'error'; // 错误
|
|
33
|
+
|
|
34
|
+
/** 工具调用请求 */
|
|
35
|
+
export interface ToolCallRequest {
|
|
36
|
+
id: string;
|
|
37
|
+
name: string;
|
|
38
|
+
arguments: string;
|
|
39
|
+
/** Gemini 模型需要的 thought_signature(用于工具调用循环) */
|
|
40
|
+
thought_signature?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** 搜索结果项 */
|
|
44
|
+
export interface SearchResultItem {
|
|
45
|
+
title: string;
|
|
46
|
+
url: string;
|
|
47
|
+
snippet: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** 流式响应块 - 所有 Adapter 必须返回此格式 */
|
|
51
|
+
/** Token 使用统计 */
|
|
52
|
+
export interface StreamTokenUsage {
|
|
53
|
+
promptTokens?: number;
|
|
54
|
+
completionTokens?: number;
|
|
55
|
+
totalTokens?: number;
|
|
56
|
+
reasoningTokens?: number;
|
|
57
|
+
cachedTokens?: number;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface StreamChunk {
|
|
61
|
+
type: StreamChunkType;
|
|
62
|
+
|
|
63
|
+
// 文本内容 (type: 'text')
|
|
64
|
+
text?: string;
|
|
65
|
+
|
|
66
|
+
// 思考内容 (type: 'thinking')
|
|
67
|
+
thinking?: string;
|
|
68
|
+
|
|
69
|
+
// 工具调用 (type: 'tool_call')
|
|
70
|
+
toolCall?: ToolCallRequest;
|
|
71
|
+
|
|
72
|
+
// 搜索结果 (type: 'search_result')
|
|
73
|
+
searchResults?: SearchResultItem[];
|
|
74
|
+
|
|
75
|
+
// 完成信号 (type: 'done')
|
|
76
|
+
finishReason?: 'stop' | 'tool_calls' | 'length' | 'error';
|
|
77
|
+
/** Token 使用统计(done 事件携带) */
|
|
78
|
+
usage?: StreamTokenUsage;
|
|
79
|
+
|
|
80
|
+
// 错误信息 (type: 'error')
|
|
81
|
+
error?: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ==================== 标准化消息格式 ====================
|
|
85
|
+
|
|
86
|
+
/** 标准化消息 - Orchestrator 内部使用 */
|
|
87
|
+
export interface StandardMessage {
|
|
88
|
+
role: 'system' | 'user' | 'assistant' | 'tool';
|
|
89
|
+
content: string;
|
|
90
|
+
|
|
91
|
+
// 图片(用户消息)
|
|
92
|
+
images?: string[];
|
|
93
|
+
|
|
94
|
+
// 工具调用(assistant 消息)
|
|
95
|
+
toolCalls?: ToolCallRequest[];
|
|
96
|
+
|
|
97
|
+
// 工具调用 ID(tool 消息)
|
|
98
|
+
toolCallId?: string;
|
|
99
|
+
|
|
100
|
+
// 工具名称(tool 消息,Gemini 需要)
|
|
101
|
+
toolName?: string;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ==================== Adapter 接口 ====================
|
|
105
|
+
|
|
106
|
+
/** Adapter 配置 */
|
|
107
|
+
export interface AdapterConfig {
|
|
108
|
+
apiKey: string;
|
|
109
|
+
apiUrl?: string;
|
|
110
|
+
/** Tavily API Key(用于统一 Web Search) */
|
|
111
|
+
tavilyApiKey?: string;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** 单次调用选项 */
|
|
115
|
+
export interface StreamOnceOptions {
|
|
116
|
+
/** 模型 ID */
|
|
117
|
+
model: string;
|
|
118
|
+
/** 是否启用思考 */
|
|
119
|
+
enableThinking?: boolean;
|
|
120
|
+
/** 是否启用搜索 */
|
|
121
|
+
enableSearch?: boolean;
|
|
122
|
+
/** 中断信号 */
|
|
123
|
+
signal: AbortSignal;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Provider Adapter 接口
|
|
128
|
+
*
|
|
129
|
+
* 职责:
|
|
130
|
+
* - 将标准消息转换为 API 特定格式
|
|
131
|
+
* - 调用 API 并返回标准化的 StreamChunk
|
|
132
|
+
* - 不处理工具执行、消息历史维护
|
|
133
|
+
*/
|
|
134
|
+
export interface ProviderAdapter {
|
|
135
|
+
/** 适配器名称 */
|
|
136
|
+
readonly name: string;
|
|
137
|
+
|
|
138
|
+
/** 支持的模型列表 */
|
|
139
|
+
readonly supportedModels: string[];
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* 单次流式调用
|
|
143
|
+
*
|
|
144
|
+
* @param messages 标准化消息列表
|
|
145
|
+
* @param tools 工具定义列表
|
|
146
|
+
* @param options 调用选项
|
|
147
|
+
* @returns 标准化的流式响应
|
|
148
|
+
*/
|
|
149
|
+
streamOnce(
|
|
150
|
+
messages: StandardMessage[],
|
|
151
|
+
tools: SimpleToolDefinition[],
|
|
152
|
+
options: StreamOnceOptions
|
|
153
|
+
): AsyncGenerator<StreamChunk>;
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* 检查是否支持指定模型
|
|
157
|
+
*/
|
|
158
|
+
supportsModel(model: string): boolean;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ==================== Orchestrator 类型 ====================
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* 工具执行器
|
|
165
|
+
*
|
|
166
|
+
* 返回值:
|
|
167
|
+
* - string: 简单结果,使用工具的静态 sideEffects
|
|
168
|
+
* - ToolResult: 带动态副作用的结果
|
|
169
|
+
*/
|
|
170
|
+
export interface ToolExecutionHooks {
|
|
171
|
+
/** 工具调用 ID(用于把输出归属到具体 tool_call 卡片) */
|
|
172
|
+
toolCallId: string;
|
|
173
|
+
/** 工具名称 */
|
|
174
|
+
toolName: string;
|
|
175
|
+
/** stdout 增量 */
|
|
176
|
+
onStdout?: (chunk: string) => void;
|
|
177
|
+
/** stderr 增量 */
|
|
178
|
+
onStderr?: (chunk: string) => void;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export type ToolExecutor = (
|
|
182
|
+
name: string,
|
|
183
|
+
args: Record<string, unknown>,
|
|
184
|
+
signal?: AbortSignal,
|
|
185
|
+
hooks?: ToolExecutionHooks
|
|
186
|
+
) => Promise<string | ToolResult>;
|
|
187
|
+
|
|
188
|
+
/** 自动运行配置 */
|
|
189
|
+
export interface AutoRunConfig {
|
|
190
|
+
/**
|
|
191
|
+
* 自动运行模式
|
|
192
|
+
* - 'run-everything': 运行所有内容(自动执行)
|
|
193
|
+
* - 'manual': 手动批准(每次执行前询问)
|
|
194
|
+
*/
|
|
195
|
+
mode?: 'run-everything' | 'manual';
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** 工具批准回调函数 */
|
|
199
|
+
export type ToolApprovalCallback = (toolCall: {
|
|
200
|
+
id: string;
|
|
201
|
+
name: string;
|
|
202
|
+
args: Record<string, unknown>;
|
|
203
|
+
}) => Promise<boolean>;
|
|
204
|
+
|
|
205
|
+
/** 获取自动运行配置回调 */
|
|
206
|
+
export type GetAutoRunConfigCallback = () => Promise<AutoRunConfig | undefined>;
|
|
207
|
+
|
|
208
|
+
/** Orchestrator 配置 */
|
|
209
|
+
export interface OrchestratorConfig {
|
|
210
|
+
/** 最大迭代次数 */
|
|
211
|
+
maxIterations?: number;
|
|
212
|
+
/** 工具执行器 */
|
|
213
|
+
executeTool: ToolExecutor;
|
|
214
|
+
/**
|
|
215
|
+
* 完整的工具列表(用于获取 sideEffects)
|
|
216
|
+
* key: 工具名称, value: 工具定义
|
|
217
|
+
*/
|
|
218
|
+
tools?: Map<string, Tool>;
|
|
219
|
+
/** 自动运行配置(静态配置,优先使用 getAutoRunConfig) */
|
|
220
|
+
autoRunConfig?: AutoRunConfig;
|
|
221
|
+
/**
|
|
222
|
+
* 动态获取自动运行配置回调
|
|
223
|
+
* 每次检查工具批准时调用,获取最新配置
|
|
224
|
+
*/
|
|
225
|
+
getAutoRunConfig?: GetAutoRunConfigCallback;
|
|
226
|
+
/**
|
|
227
|
+
* 工具批准回调(manual 模式使用)
|
|
228
|
+
* 返回 true 表示批准执行,false 表示跳过
|
|
229
|
+
*/
|
|
230
|
+
onToolApprovalRequest?: ToolApprovalCallback;
|
|
231
|
+
/**
|
|
232
|
+
* 客户端工具名称集合(透传模式)
|
|
233
|
+
*
|
|
234
|
+
* 这些工具不在服务端执行,而是:
|
|
235
|
+
* 1. 发送 tool_call_request 事件给客户端
|
|
236
|
+
* 2. 结束本轮对话
|
|
237
|
+
* 3. 客户端执行后,发新请求继续对话
|
|
238
|
+
*/
|
|
239
|
+
clientToolNames?: Set<string>;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** Orchestrator 上下文 */
|
|
243
|
+
export interface OrchestratorContext {
|
|
244
|
+
/** 系统提示 */
|
|
245
|
+
systemPrompt: string;
|
|
246
|
+
/** 对话历史(会被修改) */
|
|
247
|
+
history: ChatMessage[];
|
|
248
|
+
/** 工具定义 */
|
|
249
|
+
tools: SimpleToolDefinition[];
|
|
250
|
+
/** 中断信号 */
|
|
251
|
+
signal: AbortSignal;
|
|
252
|
+
/** 图片列表 */
|
|
253
|
+
images?: string[];
|
|
254
|
+
/**
|
|
255
|
+
* 客户端工具名称集合(透传模式)
|
|
256
|
+
*
|
|
257
|
+
* 这些工具不在服务端执行,而是发送 tool_call_request 事件给客户端
|
|
258
|
+
*/
|
|
259
|
+
clientToolNames?: Set<string>;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** Orchestrator 选项 */
|
|
263
|
+
export interface OrchestratorOptions {
|
|
264
|
+
/** 模型 ID */
|
|
265
|
+
model: string;
|
|
266
|
+
/** 是否启用思考 */
|
|
267
|
+
enableThinking?: boolean;
|
|
268
|
+
/** 是否启用搜索 */
|
|
269
|
+
enableSearch?: boolean;
|
|
270
|
+
/** 自动运行配置 */
|
|
271
|
+
autoRunConfig?: AutoRunConfig;
|
|
272
|
+
}
|
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UnifiedAdapter - 统一适配器
|
|
3
|
+
*
|
|
4
|
+
* 组合 Protocol + FamilyConfig,产出标准 StreamChunk
|
|
5
|
+
*
|
|
6
|
+
* 设计原则:
|
|
7
|
+
* 1. Protocol 只负责 HTTP/SSE 通信,产出 RawEvent
|
|
8
|
+
* 2. UnifiedAdapter 根据 FamilyConfig 将 RawEvent 转换为 StreamChunk
|
|
9
|
+
* 3. 所有模型都使用同一个 UnifiedAdapter,行为差异由 FamilyConfig 决定
|
|
10
|
+
* 4. 实现 ProviderAdapter 接口,可与现有 Orchestrator 无缝集成
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type {
|
|
14
|
+
Protocol,
|
|
15
|
+
ProtocolMessage,
|
|
16
|
+
ProtocolToolDefinition,
|
|
17
|
+
RawEvent,
|
|
18
|
+
} from './protocols';
|
|
19
|
+
import {
|
|
20
|
+
createArkProtocol,
|
|
21
|
+
createDeepSeekProtocol,
|
|
22
|
+
createQwenProtocol,
|
|
23
|
+
createGeminiProtocol,
|
|
24
|
+
createOpenAIProtocol,
|
|
25
|
+
createAnthropicProtocol,
|
|
26
|
+
} from './protocols';
|
|
27
|
+
import {
|
|
28
|
+
getModelFamily,
|
|
29
|
+
getModelProtocol,
|
|
30
|
+
getVisibleModels,
|
|
31
|
+
type ModelFamilyConfig,
|
|
32
|
+
type ProtocolId,
|
|
33
|
+
} from './model-registry';
|
|
34
|
+
import type {
|
|
35
|
+
ProviderAdapter,
|
|
36
|
+
StreamChunk,
|
|
37
|
+
ToolCallRequest,
|
|
38
|
+
SearchResultItem,
|
|
39
|
+
StandardMessage,
|
|
40
|
+
SimpleToolDefinition,
|
|
41
|
+
StreamOnceOptions,
|
|
42
|
+
} from './types';
|
|
43
|
+
import { DebugLogger } from '../utils';
|
|
44
|
+
|
|
45
|
+
const logger = DebugLogger.module('UnifiedAdapter');
|
|
46
|
+
|
|
47
|
+
// ==================== 配置类型 ====================
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* UnifiedAdapter 配置
|
|
51
|
+
*/
|
|
52
|
+
export interface UnifiedAdapterConfig {
|
|
53
|
+
/** ARK API Key(豆包/DeepSeek) */
|
|
54
|
+
arkApiKey?: string;
|
|
55
|
+
arkApiUrl?: string;
|
|
56
|
+
|
|
57
|
+
/** Qwen API Key(通义千问) */
|
|
58
|
+
qwenApiKey?: string;
|
|
59
|
+
qwenApiUrl?: string;
|
|
60
|
+
|
|
61
|
+
/** Gemini API Key */
|
|
62
|
+
geminiApiKey?: string;
|
|
63
|
+
geminiApiUrl?: string;
|
|
64
|
+
|
|
65
|
+
/** OpenRouter API Key */
|
|
66
|
+
openrouterApiKey?: string;
|
|
67
|
+
openrouterApiUrl?: string;
|
|
68
|
+
|
|
69
|
+
/** Vercel API Key(用于 Vercel AI Gateway 访问 Claude) */
|
|
70
|
+
vercelApiKey?: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* 流式调用选项
|
|
75
|
+
*/
|
|
76
|
+
export interface StreamOptions {
|
|
77
|
+
/** 模型 ID */
|
|
78
|
+
model: string;
|
|
79
|
+
/** 是否启用 thinking */
|
|
80
|
+
enableThinking?: boolean;
|
|
81
|
+
/** 是否启用搜索 */
|
|
82
|
+
enableSearch?: boolean;
|
|
83
|
+
/** 中断信号 */
|
|
84
|
+
signal: AbortSignal;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ==================== UnifiedAdapter ====================
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* 统一适配器
|
|
91
|
+
*
|
|
92
|
+
* 实现 ProviderAdapter 接口,根据模型自动选择 Protocol,并根据 FamilyConfig 转换事件
|
|
93
|
+
*/
|
|
94
|
+
export class UnifiedAdapter implements ProviderAdapter {
|
|
95
|
+
readonly name = 'unified';
|
|
96
|
+
|
|
97
|
+
/** 支持的模型列表(从 Registry 获取) */
|
|
98
|
+
get supportedModels(): string[] {
|
|
99
|
+
return getVisibleModels().map(m => m.id);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private protocols: Map<ProtocolId, Protocol> = new Map();
|
|
103
|
+
|
|
104
|
+
constructor(config: UnifiedAdapterConfig) {
|
|
105
|
+
// 初始化各 Protocol
|
|
106
|
+
if (config.arkApiKey) {
|
|
107
|
+
// 豆包(ARK 原生)
|
|
108
|
+
this.protocols.set('ark', createArkProtocol({
|
|
109
|
+
apiKey: config.arkApiKey,
|
|
110
|
+
apiUrl: config.arkApiUrl,
|
|
111
|
+
}));
|
|
112
|
+
// DeepSeek(通过 ARK,但独立 Protocol)
|
|
113
|
+
this.protocols.set('deepseek', createDeepSeekProtocol({
|
|
114
|
+
apiKey: config.arkApiKey,
|
|
115
|
+
apiUrl: config.arkApiUrl,
|
|
116
|
+
}));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (config.qwenApiKey) {
|
|
120
|
+
this.protocols.set('qwen', createQwenProtocol({
|
|
121
|
+
apiKey: config.qwenApiKey,
|
|
122
|
+
apiUrl: config.qwenApiUrl,
|
|
123
|
+
}));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (config.geminiApiKey) {
|
|
127
|
+
this.protocols.set('gemini', createGeminiProtocol({
|
|
128
|
+
apiKey: config.geminiApiKey,
|
|
129
|
+
apiUrl: config.geminiApiUrl,
|
|
130
|
+
}));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// OpenAI 通过 OpenRouter 访问
|
|
134
|
+
if (config.openrouterApiKey) {
|
|
135
|
+
this.protocols.set('openai', createOpenAIProtocol({
|
|
136
|
+
apiKey: config.openrouterApiKey,
|
|
137
|
+
apiUrl: config.openrouterApiUrl,
|
|
138
|
+
}));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Anthropic 使用 Vercel AI SDK
|
|
142
|
+
if (config.vercelApiKey) {
|
|
143
|
+
this.protocols.set('anthropic', createAnthropicProtocol({
|
|
144
|
+
apiKey: config.vercelApiKey,
|
|
145
|
+
}));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* 检查是否支持指定模型
|
|
151
|
+
*/
|
|
152
|
+
supportsModel(model: string): boolean {
|
|
153
|
+
const protocol = getModelProtocol(model);
|
|
154
|
+
return protocol ? this.protocols.has(protocol) : false;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* 获取模型的家族配置
|
|
159
|
+
*/
|
|
160
|
+
getModelFamilyConfig(model: string): ModelFamilyConfig | undefined {
|
|
161
|
+
return getModelFamily(model);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* 实现 ProviderAdapter.streamOnce 接口
|
|
166
|
+
*
|
|
167
|
+
* @param messages 标准消息列表
|
|
168
|
+
* @param tools 工具定义
|
|
169
|
+
* @param options 调用选项
|
|
170
|
+
* @returns 标准 StreamChunk 流
|
|
171
|
+
*/
|
|
172
|
+
async *streamOnce(
|
|
173
|
+
messages: StandardMessage[],
|
|
174
|
+
tools: SimpleToolDefinition[],
|
|
175
|
+
options: StreamOnceOptions
|
|
176
|
+
): AsyncGenerator<StreamChunk> {
|
|
177
|
+
const { model, enableThinking = false, enableSearch = false, signal } = options;
|
|
178
|
+
|
|
179
|
+
// 转换消息格式(StandardMessage → ProtocolMessage)
|
|
180
|
+
const protocolMessages: ProtocolMessage[] = messages.map(m => ({
|
|
181
|
+
role: m.role,
|
|
182
|
+
content: m.content,
|
|
183
|
+
images: m.images,
|
|
184
|
+
toolCalls: m.toolCalls?.map(tc => ({
|
|
185
|
+
id: tc.id,
|
|
186
|
+
name: tc.name,
|
|
187
|
+
arguments: tc.arguments,
|
|
188
|
+
thoughtSignature: tc.thought_signature,
|
|
189
|
+
})),
|
|
190
|
+
toolCallId: m.toolCallId,
|
|
191
|
+
toolName: m.toolName,
|
|
192
|
+
}));
|
|
193
|
+
|
|
194
|
+
// 转换工具格式(SimpleToolDefinition → ProtocolToolDefinition)
|
|
195
|
+
const protocolTools: ProtocolToolDefinition[] = tools;
|
|
196
|
+
|
|
197
|
+
yield* this.stream(protocolMessages, protocolTools, {
|
|
198
|
+
model,
|
|
199
|
+
enableThinking,
|
|
200
|
+
enableSearch,
|
|
201
|
+
signal,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* 流式调用(内部方法)
|
|
207
|
+
*/
|
|
208
|
+
private async *stream(
|
|
209
|
+
messages: ProtocolMessage[],
|
|
210
|
+
tools: ProtocolToolDefinition[],
|
|
211
|
+
options: StreamOptions
|
|
212
|
+
): AsyncGenerator<StreamChunk> {
|
|
213
|
+
const { model, enableThinking = false, enableSearch = false, signal } = options;
|
|
214
|
+
|
|
215
|
+
// 获取模型配置
|
|
216
|
+
const protocolId = getModelProtocol(model);
|
|
217
|
+
const familyConfig = getModelFamily(model);
|
|
218
|
+
|
|
219
|
+
if (!protocolId) {
|
|
220
|
+
yield { type: 'error', error: `未知模型: ${model}` };
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (!familyConfig) {
|
|
225
|
+
yield { type: 'error', error: `模型 ${model} 缺少家族配置` };
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const protocol = this.protocols.get(protocolId);
|
|
230
|
+
if (!protocol) {
|
|
231
|
+
yield { type: 'error', error: `Protocol ${protocolId} 未配置` };
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
logger.debug('开始流式调用', {
|
|
236
|
+
model,
|
|
237
|
+
protocol: protocolId,
|
|
238
|
+
family: familyConfig.id,
|
|
239
|
+
enableThinking,
|
|
240
|
+
enableSearch,
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// 调用 Protocol 获取原始事件流
|
|
244
|
+
const rawStream = protocol.stream(messages, tools, {
|
|
245
|
+
model,
|
|
246
|
+
familyConfig,
|
|
247
|
+
enableThinking,
|
|
248
|
+
enableSearch,
|
|
249
|
+
signal,
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// 转换为标准 StreamChunk
|
|
253
|
+
yield* this.transformEvents(rawStream, familyConfig, enableThinking, enableSearch);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* 将 RawEvent 转换为 StreamChunk
|
|
258
|
+
*
|
|
259
|
+
* 根据 FamilyConfig 处理行为差异
|
|
260
|
+
*/
|
|
261
|
+
private async *transformEvents(
|
|
262
|
+
rawStream: AsyncGenerator<RawEvent>,
|
|
263
|
+
familyConfig: ModelFamilyConfig,
|
|
264
|
+
enableThinking: boolean,
|
|
265
|
+
enableSearch: boolean
|
|
266
|
+
): AsyncGenerator<StreamChunk> {
|
|
267
|
+
// 状态跟踪
|
|
268
|
+
let thinkingBuffer = '';
|
|
269
|
+
let thinkingStarted = false;
|
|
270
|
+
|
|
271
|
+
for await (const event of rawStream) {
|
|
272
|
+
switch (event.type) {
|
|
273
|
+
// ========== Thinking 处理 ==========
|
|
274
|
+
case 'thinking_delta':
|
|
275
|
+
// 只有启用 thinking 且家族支持时才输出
|
|
276
|
+
if (enableThinking && familyConfig.supportsThinking && event.delta) {
|
|
277
|
+
// 去除开头的换行
|
|
278
|
+
let delta = event.delta;
|
|
279
|
+
if (!thinkingStarted) {
|
|
280
|
+
delta = delta.replace(/^\n+/, '');
|
|
281
|
+
if (delta) thinkingStarted = true;
|
|
282
|
+
}
|
|
283
|
+
if (delta) {
|
|
284
|
+
thinkingBuffer += delta;
|
|
285
|
+
yield { type: 'thinking', thinking: delta };
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
break;
|
|
289
|
+
|
|
290
|
+
case 'thinking_done':
|
|
291
|
+
if (enableThinking && familyConfig.supportsThinking) {
|
|
292
|
+
yield { type: 'thinking_done' };
|
|
293
|
+
}
|
|
294
|
+
break;
|
|
295
|
+
|
|
296
|
+
// ========== 文本处理 ==========
|
|
297
|
+
case 'text_delta':
|
|
298
|
+
if (event.delta) {
|
|
299
|
+
yield { type: 'text', text: event.delta };
|
|
300
|
+
}
|
|
301
|
+
break;
|
|
302
|
+
|
|
303
|
+
// ========== 工具调用处理 ==========
|
|
304
|
+
case 'tool_call_done':
|
|
305
|
+
if (event.toolCall) {
|
|
306
|
+
const toolCall: ToolCallRequest = {
|
|
307
|
+
id: event.toolCall.id || '',
|
|
308
|
+
name: event.toolCall.name || '',
|
|
309
|
+
arguments: event.toolCall.arguments || '{}',
|
|
310
|
+
};
|
|
311
|
+
// 保留 Gemini 的 thoughtSignature
|
|
312
|
+
if (event.toolCall.thoughtSignature) {
|
|
313
|
+
toolCall.thought_signature = event.toolCall.thoughtSignature;
|
|
314
|
+
}
|
|
315
|
+
yield { type: 'tool_call', toolCall };
|
|
316
|
+
}
|
|
317
|
+
break;
|
|
318
|
+
|
|
319
|
+
// ========== 搜索结果处理 ==========
|
|
320
|
+
case 'search_result':
|
|
321
|
+
if (enableSearch && event.searchResults) {
|
|
322
|
+
const searchResults: SearchResultItem[] = event.searchResults.map(r => ({
|
|
323
|
+
title: r.title,
|
|
324
|
+
url: r.url,
|
|
325
|
+
snippet: r.snippet,
|
|
326
|
+
}));
|
|
327
|
+
yield { type: 'search_result', searchResults };
|
|
328
|
+
}
|
|
329
|
+
break;
|
|
330
|
+
|
|
331
|
+
// ========== 完成处理 ==========
|
|
332
|
+
case 'done':
|
|
333
|
+
// usage 数据由 protocol 层透传
|
|
334
|
+
yield {
|
|
335
|
+
type: 'done',
|
|
336
|
+
finishReason: event.finishReason || 'stop',
|
|
337
|
+
// 透传 Token 使用统计
|
|
338
|
+
usage: event.usage ? {
|
|
339
|
+
promptTokens: event.usage.promptTokens,
|
|
340
|
+
completionTokens: event.usage.completionTokens,
|
|
341
|
+
totalTokens: event.usage.totalTokens,
|
|
342
|
+
reasoningTokens: event.usage.reasoningTokens,
|
|
343
|
+
cachedTokens: event.usage.cachedTokens,
|
|
344
|
+
} : undefined,
|
|
345
|
+
};
|
|
346
|
+
break;
|
|
347
|
+
|
|
348
|
+
// ========== 错误处理 ==========
|
|
349
|
+
case 'error':
|
|
350
|
+
yield { type: 'error', error: event.error || '未知错误' };
|
|
351
|
+
break;
|
|
352
|
+
|
|
353
|
+
// 忽略其他事件(如 tool_call_start, tool_call_delta)
|
|
354
|
+
default:
|
|
355
|
+
break;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* 创建 UnifiedAdapter
|
|
363
|
+
*/
|
|
364
|
+
export function createUnifiedAdapter(config: UnifiedAdapterConfig): UnifiedAdapter {
|
|
365
|
+
return new UnifiedAdapter(config);
|
|
366
|
+
}
|
|
367
|
+
|
package/src/router.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 模型路由模块
|
|
3
|
+
*
|
|
4
|
+
* 根据模型 ID 查询对应的 Protocol 和 Family 配置
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
getModelEntry,
|
|
9
|
+
getModelFamily,
|
|
10
|
+
type ProtocolId,
|
|
11
|
+
type ModelFamilyConfig,
|
|
12
|
+
type ModelRegistryEntry,
|
|
13
|
+
} from './providers/model-registry';
|
|
14
|
+
|
|
15
|
+
/** Provider 类型 = Protocol 类型 */
|
|
16
|
+
export type ProviderType = ProtocolId;
|
|
17
|
+
|
|
18
|
+
/** 路由结果 */
|
|
19
|
+
export interface RouteResult {
|
|
20
|
+
/** Protocol 类型 */
|
|
21
|
+
provider: ProviderType;
|
|
22
|
+
/** 是否未注册(使用默认 Provider) */
|
|
23
|
+
isDefault: boolean;
|
|
24
|
+
/** 模型注册信息 */
|
|
25
|
+
registryEntry?: ModelRegistryEntry;
|
|
26
|
+
/** 模型家族配置 */
|
|
27
|
+
familyConfig?: ModelFamilyConfig;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** 默认 Provider(未注册模型的兜底) */
|
|
31
|
+
const DEFAULT_PROVIDER: ProviderType = 'ark';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 获取模型的 Provider
|
|
35
|
+
*/
|
|
36
|
+
export function routeModelToProvider(model: string): ProviderType {
|
|
37
|
+
return routeModelWithDetails(model).provider;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 获取模型的完整路由信息
|
|
42
|
+
*/
|
|
43
|
+
export function routeModelWithDetails(model: string): RouteResult {
|
|
44
|
+
const registryEntry = getModelEntry(model);
|
|
45
|
+
if (registryEntry) {
|
|
46
|
+
return {
|
|
47
|
+
provider: registryEntry.protocol,
|
|
48
|
+
registryEntry,
|
|
49
|
+
familyConfig: getModelFamily(model),
|
|
50
|
+
isDefault: false,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
provider: DEFAULT_PROVIDER,
|
|
56
|
+
isDefault: true
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 获取默认 Provider
|
|
62
|
+
*/
|
|
63
|
+
export function getDefaultProvider(): ProviderType {
|
|
64
|
+
return DEFAULT_PROVIDER;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 检查模型是否属于指定 Provider
|
|
69
|
+
*/
|
|
70
|
+
export function isModelForProvider(model: string, provider: ProviderType): boolean {
|
|
71
|
+
return routeModelToProvider(model) === provider;
|
|
72
|
+
}
|