@huyooo/ai-chat-core 0.2.45 → 0.3.2

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.
Files changed (247) hide show
  1. package/dist/adapter/index.d.ts +11 -0
  2. package/dist/adapter/index.d.ts.map +1 -0
  3. package/dist/adapter/model-adapter.d.ts +25 -0
  4. package/dist/adapter/model-adapter.d.ts.map +1 -0
  5. package/dist/adapter/model-options.d.ts +53 -0
  6. package/dist/adapter/model-options.d.ts.map +1 -0
  7. package/dist/adapter/types.d.ts +28 -0
  8. package/dist/adapter/types.d.ts.map +1 -0
  9. package/dist/chat-runtime.d.ts +96 -0
  10. package/dist/chat-runtime.d.ts.map +1 -0
  11. package/dist/constants.d.ts +12 -0
  12. package/dist/constants.d.ts.map +1 -0
  13. package/dist/events.d.ts +605 -1
  14. package/dist/events.d.ts.map +1 -0
  15. package/dist/events.js +1 -1
  16. package/dist/extension/index.d.ts +9 -0
  17. package/dist/extension/index.d.ts.map +1 -0
  18. package/dist/extension/types.d.ts +46 -0
  19. package/dist/extension/types.d.ts.map +1 -0
  20. package/dist/families/index.d.ts +11 -0
  21. package/dist/families/index.d.ts.map +1 -0
  22. package/dist/families/presets.d.ts +31 -0
  23. package/dist/families/presets.d.ts.map +1 -0
  24. package/dist/families/resolver.d.ts +11 -0
  25. package/dist/families/resolver.d.ts.map +1 -0
  26. package/dist/families/types.d.ts +29 -0
  27. package/dist/families/types.d.ts.map +1 -0
  28. package/dist/governance/command-safety.d.ts +34 -0
  29. package/dist/governance/command-safety.d.ts.map +1 -0
  30. package/dist/governance/governance.d.ts +19 -0
  31. package/dist/governance/governance.d.ts.map +1 -0
  32. package/dist/governance/index.d.ts +12 -0
  33. package/dist/governance/index.d.ts.map +1 -0
  34. package/dist/governance/types.d.ts +29 -0
  35. package/dist/governance/types.d.ts.map +1 -0
  36. package/dist/index.d.ts +72 -804
  37. package/dist/index.d.ts.map +1 -0
  38. package/dist/index.js +51 -1
  39. package/dist/internal/management-args.d.ts +13 -0
  40. package/dist/internal/management-args.d.ts.map +1 -0
  41. package/dist/internal/management-results.d.ts +21 -0
  42. package/dist/internal/management-results.d.ts.map +1 -0
  43. package/dist/llm-config.d.ts +108 -0
  44. package/dist/llm-config.d.ts.map +1 -0
  45. package/dist/logger/core.d.ts +31 -0
  46. package/dist/logger/core.d.ts.map +1 -0
  47. package/dist/logger/index.d.ts +9 -0
  48. package/dist/logger/index.d.ts.map +1 -0
  49. package/dist/orchestrator/compression-handler.d.ts +29 -0
  50. package/dist/orchestrator/compression-handler.d.ts.map +1 -0
  51. package/dist/orchestrator/context-compressor.d.ts +51 -0
  52. package/dist/orchestrator/context-compressor.d.ts.map +1 -0
  53. package/dist/orchestrator/context-summarizer.d.ts +41 -0
  54. package/dist/orchestrator/context-summarizer.d.ts.map +1 -0
  55. package/dist/orchestrator/index.d.ts +12 -0
  56. package/dist/orchestrator/index.d.ts.map +1 -0
  57. package/dist/orchestrator/orchestrator.d.ts +46 -0
  58. package/dist/orchestrator/orchestrator.d.ts.map +1 -0
  59. package/dist/orchestrator/types.d.ts +58 -0
  60. package/dist/orchestrator/types.d.ts.map +1 -0
  61. package/dist/parts/index.d.ts +13 -0
  62. package/dist/parts/index.d.ts.map +1 -0
  63. package/dist/parts/registry.d.ts +11 -0
  64. package/dist/parts/registry.d.ts.map +1 -0
  65. package/dist/parts/summaries.d.ts +9 -0
  66. package/dist/parts/summaries.d.ts.map +1 -0
  67. package/dist/parts/types.d.ts +61 -0
  68. package/dist/parts/types.d.ts.map +1 -0
  69. package/dist/platform.d.ts +17 -0
  70. package/dist/platform.d.ts.map +1 -0
  71. package/dist/platform.js +1 -0
  72. package/dist/protocols/anthropic.d.ts +20 -0
  73. package/dist/protocols/anthropic.d.ts.map +1 -0
  74. package/dist/protocols/ark.d.ts +36 -0
  75. package/dist/protocols/ark.d.ts.map +1 -0
  76. package/dist/protocols/deepseek.d.ts +24 -0
  77. package/dist/protocols/deepseek.d.ts.map +1 -0
  78. package/dist/protocols/error-utils.d.ts +14 -0
  79. package/dist/protocols/error-utils.d.ts.map +1 -0
  80. package/dist/protocols/gemini.d.ts +24 -0
  81. package/dist/protocols/gemini.d.ts.map +1 -0
  82. package/dist/protocols/glm.d.ts +20 -0
  83. package/dist/protocols/glm.d.ts.map +1 -0
  84. package/dist/protocols/grok.d.ts +20 -0
  85. package/dist/protocols/grok.d.ts.map +1 -0
  86. package/dist/protocols/index.d.ts +31 -0
  87. package/dist/protocols/index.d.ts.map +1 -0
  88. package/dist/protocols/minimax.d.ts +38 -0
  89. package/dist/protocols/minimax.d.ts.map +1 -0
  90. package/dist/protocols/moonshot.d.ts +20 -0
  91. package/dist/protocols/moonshot.d.ts.map +1 -0
  92. package/dist/protocols/openai-sse.d.ts +33 -0
  93. package/dist/protocols/openai-sse.d.ts.map +1 -0
  94. package/dist/protocols/openai.d.ts +19 -0
  95. package/dist/protocols/openai.d.ts.map +1 -0
  96. package/dist/protocols/qwen.d.ts +26 -0
  97. package/dist/protocols/qwen.d.ts.map +1 -0
  98. package/dist/protocols/responses-sse.d.ts +30 -0
  99. package/dist/protocols/responses-sse.d.ts.map +1 -0
  100. package/dist/protocols/sse-reader.d.ts +23 -0
  101. package/dist/protocols/sse-reader.d.ts.map +1 -0
  102. package/dist/protocols/tool-arguments.d.ts +8 -0
  103. package/dist/protocols/tool-arguments.d.ts.map +1 -0
  104. package/dist/protocols/types.d.ts +148 -0
  105. package/dist/protocols/types.d.ts.map +1 -0
  106. package/dist/protocols/vercel-gateway.d.ts +15 -0
  107. package/dist/protocols/vercel-gateway.d.ts.map +1 -0
  108. package/dist/runtime.d.ts +151 -0
  109. package/dist/runtime.d.ts.map +1 -0
  110. package/dist/runtime.js +1 -0
  111. package/dist/skills/index.d.ts +14 -0
  112. package/dist/skills/index.d.ts.map +1 -0
  113. package/dist/skills/management/admin.d.ts +10 -0
  114. package/dist/skills/management/admin.d.ts.map +1 -0
  115. package/dist/skills/management/index.d.ts +11 -0
  116. package/dist/skills/management/index.d.ts.map +1 -0
  117. package/dist/skills/management/inputs.d.ts +44 -0
  118. package/dist/skills/management/inputs.d.ts.map +1 -0
  119. package/dist/skills/management/operations.d.ts +78 -0
  120. package/dist/skills/management/operations.d.ts.map +1 -0
  121. package/dist/skills/management/types.d.ts +70 -0
  122. package/dist/skills/management/types.d.ts.map +1 -0
  123. package/dist/skills/registry.d.ts +37 -0
  124. package/dist/skills/registry.d.ts.map +1 -0
  125. package/dist/skills/summaries.d.ts +9 -0
  126. package/dist/skills/summaries.d.ts.map +1 -0
  127. package/dist/skills/types.d.ts +61 -0
  128. package/dist/skills/types.d.ts.map +1 -0
  129. package/dist/test-utils/mock-sse.d.ts +13 -0
  130. package/dist/test-utils/mock-sse.d.ts.map +1 -0
  131. package/dist/tool-manager/define-tool.d.ts +35 -0
  132. package/dist/tool-manager/define-tool.d.ts.map +1 -0
  133. package/dist/tool-manager/formats.d.ts +46 -0
  134. package/dist/tool-manager/formats.d.ts.map +1 -0
  135. package/dist/tool-manager/identity.d.ts +18 -0
  136. package/dist/tool-manager/identity.d.ts.map +1 -0
  137. package/dist/tool-manager/in-process-provider.d.ts +15 -0
  138. package/dist/tool-manager/in-process-provider.d.ts.map +1 -0
  139. package/dist/tool-manager/index.d.ts +18 -0
  140. package/dist/tool-manager/index.d.ts.map +1 -0
  141. package/dist/tool-manager/manager.d.ts +18 -0
  142. package/dist/tool-manager/manager.d.ts.map +1 -0
  143. package/dist/tool-manager/mcp-provider.d.ts +21 -0
  144. package/dist/tool-manager/mcp-provider.d.ts.map +1 -0
  145. package/dist/tool-manager/summaries.d.ts +39 -0
  146. package/dist/tool-manager/summaries.d.ts.map +1 -0
  147. package/dist/tool-manager/types.d.ts +314 -0
  148. package/dist/tool-manager/types.d.ts.map +1 -0
  149. package/dist/types.d.ts +663 -0
  150. package/dist/types.d.ts.map +1 -0
  151. package/package.json +26 -15
  152. package/src/adapter/index.ts +25 -0
  153. package/src/adapter/model-adapter.ts +196 -0
  154. package/src/adapter/model-options.ts +143 -0
  155. package/src/adapter/types.ts +41 -0
  156. package/src/chat-runtime.ts +515 -0
  157. package/src/constants.ts +9 -102
  158. package/src/events.ts +364 -150
  159. package/src/extension/index.ts +24 -0
  160. package/src/extension/types.ts +49 -0
  161. package/src/families/index.ts +28 -0
  162. package/src/families/presets.ts +124 -0
  163. package/src/families/resolver.ts +22 -0
  164. package/src/families/types.ts +55 -0
  165. package/src/governance/command-safety.ts +224 -0
  166. package/src/governance/governance.ts +125 -0
  167. package/src/governance/index.ts +38 -0
  168. package/src/governance/types.ts +44 -0
  169. package/src/index.ts +250 -145
  170. package/src/internal/management-args.ts +39 -0
  171. package/src/internal/management-results.ts +60 -0
  172. package/src/llm-config.ts +137 -0
  173. package/src/logger/core.ts +96 -0
  174. package/src/logger/index.ts +8 -0
  175. package/src/orchestrator/compression-handler.ts +137 -0
  176. package/src/{providers → orchestrator}/context-compressor.ts +79 -47
  177. package/src/orchestrator/context-summarizer.ts +123 -0
  178. package/src/orchestrator/index.ts +20 -0
  179. package/src/orchestrator/orchestrator.ts +1002 -0
  180. package/src/orchestrator/types.ts +70 -0
  181. package/src/parts/index.ts +20 -0
  182. package/src/parts/registry.ts +95 -0
  183. package/src/parts/summaries.ts +40 -0
  184. package/src/parts/types.ts +63 -0
  185. package/src/platform.ts +73 -0
  186. package/src/protocols/anthropic.ts +377 -0
  187. package/src/protocols/ark.ts +300 -0
  188. package/src/protocols/deepseek.ts +192 -0
  189. package/src/{providers/protocols → protocols}/error-utils.ts +17 -20
  190. package/src/protocols/gemini.ts +352 -0
  191. package/src/protocols/glm.ts +212 -0
  192. package/src/protocols/grok.ts +98 -0
  193. package/src/protocols/index.ts +48 -0
  194. package/src/protocols/minimax.ts +308 -0
  195. package/src/protocols/moonshot.ts +186 -0
  196. package/src/protocols/openai-sse.ts +156 -0
  197. package/src/protocols/openai.ts +97 -0
  198. package/src/protocols/qwen.ts +358 -0
  199. package/src/protocols/responses-sse.ts +224 -0
  200. package/src/protocols/sse-reader.ts +54 -0
  201. package/src/protocols/tool-arguments.ts +32 -0
  202. package/src/{providers/protocols → protocols}/types.ts +46 -37
  203. package/src/protocols/vercel-gateway.ts +391 -0
  204. package/src/runtime.ts +167 -0
  205. package/src/skills/index.ts +29 -0
  206. package/src/skills/management/admin.ts +170 -0
  207. package/src/skills/management/index.ts +27 -0
  208. package/src/skills/management/inputs.ts +79 -0
  209. package/src/skills/management/operations.ts +256 -0
  210. package/src/skills/management/types.ts +57 -0
  211. package/src/skills/registry.ts +120 -0
  212. package/src/skills/summaries.ts +48 -0
  213. package/src/skills/types.ts +65 -0
  214. package/src/test-utils/mock-sse.ts +3 -3
  215. package/src/tool-manager/define-tool.ts +201 -0
  216. package/src/tool-manager/formats.ts +146 -0
  217. package/src/tool-manager/identity.ts +80 -0
  218. package/src/tool-manager/in-process-provider.ts +164 -0
  219. package/src/tool-manager/index.ts +63 -0
  220. package/src/tool-manager/manager.ts +562 -0
  221. package/src/tool-manager/mcp-provider.ts +509 -0
  222. package/src/tool-manager/summaries.ts +136 -0
  223. package/src/tool-manager/types.ts +389 -0
  224. package/src/types.ts +750 -191
  225. package/dist/events-CU5D5ray.d.ts +0 -1128
  226. package/src/agent.ts +0 -409
  227. package/src/internal/update-plan.ts +0 -2
  228. package/src/internal/web-search.ts +0 -77
  229. package/src/mcp/client-manager.ts +0 -302
  230. package/src/mcp/index.ts +0 -2
  231. package/src/mcp/types.ts +0 -43
  232. package/src/providers/context-summarizer.ts +0 -70
  233. package/src/providers/index.ts +0 -125
  234. package/src/providers/model-registry.ts +0 -466
  235. package/src/providers/orchestrator.ts +0 -839
  236. package/src/providers/protocols/anthropic.ts +0 -406
  237. package/src/providers/protocols/ark.ts +0 -362
  238. package/src/providers/protocols/deepseek.ts +0 -344
  239. package/src/providers/protocols/gemini.ts +0 -350
  240. package/src/providers/protocols/index.ts +0 -36
  241. package/src/providers/protocols/openai.ts +0 -420
  242. package/src/providers/protocols/qwen.ts +0 -315
  243. package/src/providers/types.ts +0 -264
  244. package/src/providers/unified-adapter.ts +0 -367
  245. package/src/router.ts +0 -72
  246. package/src/tools.ts +0 -162
  247. package/src/utils.ts +0 -86
@@ -12,15 +12,31 @@
12
12
  * - 工具调用格式差异(由 Family 处理)
13
13
  */
14
14
 
15
- import type { ModelFamilyConfig } from '../model-registry';
15
+ import type { ModelFamilyConfig } from '../families';
16
+
17
+ // ==================== 协议标识 ====================
18
+
19
+ /**
20
+ * 内置协议类型标识
21
+ *
22
+ * 默认带版本 _v1,当上游 API 有 breaking change 时新增 _v2、_v3 等。
23
+ */
24
+ export type ProtocolId =
25
+ | 'ark_v1' | 'deepseek_v1' | 'qwen_v1' | 'glm_v1' | 'moonshot_v1' | 'minimax_v1'
26
+ | 'vercel_gateway_v1' | 'anthropic_v1' | 'gemini_v1' | 'openai_v1' | 'grok_v1'
27
+ ;
28
+
29
+ /** 从 ProtocolId 提取 base 协议(如 anthropic_v1 → anthropic) */
30
+ export function getBaseProtocol(protocol: string): string {
31
+ return protocol.replace(/_v\d+$/, '');
32
+ }
16
33
 
17
34
  // ==================== 原始事件类型 ====================
18
35
 
19
36
  /**
20
37
  * Protocol 原始事件类型
21
38
  *
22
- * 这是 Protocol 层产出的"原始"事件,
23
- * 后续由 FamilyBehavior 转换为标准 StreamChunk
39
+ * Protocol 层产出的事件,Orchestrator 直接消费
24
40
  */
25
41
  export type RawEventType =
26
42
  | 'text_delta' // 文本增量
@@ -29,14 +45,10 @@ export type RawEventType =
29
45
  | 'tool_call_start' // 工具调用开始
30
46
  | 'tool_call_delta' // 工具调用参数增量
31
47
  | 'tool_call_done' // 工具调用完成
32
- | 'search_query' // 搜索查询
33
- | 'search_result' // 搜索结果
34
48
  | 'done' // 流结束
35
49
  | 'error'; // 错误
36
50
 
37
- /**
38
- * 原始工具调用
39
- */
51
+ /** 工具调用 */
40
52
  export interface RawToolCall {
41
53
  id: string;
42
54
  name: string;
@@ -45,26 +57,6 @@ export interface RawToolCall {
45
57
  thoughtSignature?: string;
46
58
  }
47
59
 
48
- /**
49
- * Protocol 消息中的工具调用
50
- */
51
- export interface ProtocolToolCall {
52
- id: string;
53
- name: string;
54
- arguments: string;
55
- /** Gemini 需要的 thoughtSignature */
56
- thoughtSignature?: string;
57
- }
58
-
59
- /**
60
- * 原始搜索结果
61
- */
62
- export interface RawSearchResult {
63
- title: string;
64
- url: string;
65
- snippet: string;
66
- }
67
-
68
60
  /**
69
61
  * Protocol 原始事件
70
62
  */
@@ -77,23 +69,29 @@ export interface RawTokenUsage {
77
69
  cachedTokens?: number;
78
70
  }
79
71
 
72
+ /** 输出内容片段(用于图片生成、多模态结果等非纯文本产物) */
73
+ export type RawOutputPart =
74
+ | { type: 'text'; text: string }
75
+ | { type: 'inline_data'; mimeType: string; data: string };
76
+
80
77
  export interface RawEvent {
81
78
  type: RawEventType;
82
79
 
83
80
  // text_delta / thinking_delta
84
81
  delta?: string;
85
82
 
83
+ // thinking_done(Anthropic 的 thinking signature)
84
+ thinkingSignature?: string;
85
+
86
86
  // tool_call_*
87
87
  toolCall?: Partial<RawToolCall>;
88
88
 
89
- // search_*
90
- searchQuery?: string;
91
- searchResults?: RawSearchResult[];
92
-
93
89
  // done
94
90
  finishReason?: 'stop' | 'tool_calls' | 'length' | 'error';
95
91
  /** Token 使用统计(done 事件携带) */
96
92
  usage?: RawTokenUsage;
93
+ /** 完整输出片段(done 事件携带;用于图片生成等场景) */
94
+ outputParts?: RawOutputPart[];
97
95
 
98
96
  // error
99
97
  error?: string;
@@ -110,9 +108,18 @@ export interface ProtocolMessage {
110
108
 
111
109
  /** 图片列表(base64 或 URL) */
112
110
  images?: string[];
111
+
112
+ /** 多模态文件附件。Gemini 使用 fileData 引用 Files API URI。 */
113
+ attachments?: ProtocolAttachment[];
114
+
115
+ /** 思考内容(assistant 消息,工具调用循环需回传给 API) */
116
+ thinkingContent?: string;
117
+
118
+ /** 思考签名(Anthropic extended thinking 的 signature,工具调用循环需回传) */
119
+ thinkingSignature?: string;
113
120
 
114
121
  /** 工具调用(assistant 消息) */
115
- toolCalls?: ProtocolToolCall[];
122
+ toolCalls?: RawToolCall[];
116
123
 
117
124
  /** 工具调用 ID(tool 消息) */
118
125
  toolCallId?: string;
@@ -121,10 +128,13 @@ export interface ProtocolMessage {
121
128
  toolName?: string;
122
129
  }
123
130
 
131
+ export type ProtocolAttachment =
132
+ | { type: 'file_data'; mimeType: string; fileUri: string };
133
+
124
134
  /**
125
135
  * Protocol 工具定义(简化格式)
126
136
  */
127
- import type { JsonSchemaObject } from '../../types';
137
+ import type { JsonSchemaObject } from '../types';
128
138
 
129
139
  export interface ProtocolToolDefinition {
130
140
  name: string;
@@ -140,10 +150,10 @@ export interface ProtocolRequestOptions {
140
150
  model: string;
141
151
  /** 模型家族配置 */
142
152
  familyConfig: ModelFamilyConfig;
153
+ /** 最大输出 token(从 ModelConfig.maxOutputTokens 直传) */
154
+ maxOutputTokens: number;
143
155
  /** 是否启用 thinking */
144
156
  enableThinking: boolean;
145
- /** 是否启用搜索 */
146
- enableSearch: boolean;
147
157
  /** 中断信号 */
148
158
  signal: AbortSignal;
149
159
  }
@@ -186,4 +196,3 @@ export interface Protocol {
186
196
  * Protocol 工厂函数类型
187
197
  */
188
198
  export type ProtocolFactory = (config: ProtocolConfig) => Protocol;
189
-
@@ -0,0 +1,391 @@
1
+ /**
2
+ * Vercel AI Gateway Protocol(LanguageModelV3)
3
+ *
4
+ * 调用 Vercel AI Gateway 内部协议端点 /v3/ai/language-model,
5
+ * 使用 LanguageModelV3 格式,完整保留各厂商原生能力:
6
+ * - Gemini: thinking + thoughtSignature(tool call 循环必需)
7
+ * - GPT: reasoning summary
8
+ * - Claude: reasoning
9
+ *
10
+ * 所有厂商返回统一的事件格式(reasoning-delta / text-delta / tool-call 等),
11
+ * 厂商特有字段通过 providerMetadata 透传。
12
+ */
13
+
14
+ import type {
15
+ Protocol,
16
+ ProtocolConfig,
17
+ ProtocolMessage,
18
+ ProtocolToolDefinition,
19
+ ProtocolRequestOptions,
20
+ RawEvent,
21
+ RawTokenUsage,
22
+ } from './types';
23
+ import { createModuleLogger } from '../logger';
24
+ import { friendlyHttpError } from './error-utils';
25
+ import { readSSEJsonStream } from './sse-reader';
26
+ import { parseProtocolToolArguments } from './tool-arguments';
27
+
28
+ const logger = createModuleLogger('VercelGateway');
29
+
30
+ const GATEWAY_BASE = 'https://ai-gateway.vercel.sh/v3/ai';
31
+
32
+ // ==================== 消息转换(LanguageModelV3 Prompt) ====================
33
+
34
+ /**
35
+ * LanguageModelV3 Prompt 格式:
36
+ * - system: { role: 'system', content: string }
37
+ * - user: { role: 'user', content: [{ type: 'text', text } | { type: 'file', data, mediaType }] }
38
+ * - assistant: { role: 'assistant', content: [text | reasoning | tool-call] }
39
+ * - tool: { role: 'tool', content: [{ type: 'tool-result', ... }] }
40
+ */
41
+ function convertToPrompt(messages: ProtocolMessage[]): unknown[] {
42
+ const prompt: unknown[] = [];
43
+
44
+ for (const msg of messages) {
45
+ switch (msg.role) {
46
+ case 'system':
47
+ prompt.push({ role: 'system', content: msg.content });
48
+ break;
49
+
50
+ case 'user': {
51
+ const textContent = msg.content || (msg.images?.length ? '请分析这张图片' : '');
52
+ const parts: unknown[] = [{ type: 'text', text: textContent }];
53
+ if (msg.images?.length) {
54
+ for (const img of msg.images) {
55
+ const dataUrl = img.startsWith('data:') ? img : `data:image/jpeg;base64,${img}`;
56
+ parts.push({ type: 'file', data: dataUrl, mediaType: 'image/jpeg' });
57
+ }
58
+ }
59
+ prompt.push({ role: 'user', content: parts });
60
+ break;
61
+ }
62
+
63
+ case 'assistant': {
64
+ const parts: unknown[] = [];
65
+ if (msg.thinkingContent) {
66
+ // 回传思考内容,Gemini 的 thoughtSignature 在 providerOptions 中
67
+ const reasoningPart: Record<string, unknown> = { type: 'reasoning', text: msg.thinkingContent };
68
+ if (msg.toolCalls?.[0]?.thoughtSignature) {
69
+ reasoningPart.providerOptions = {
70
+ google: { thoughtSignature: msg.toolCalls[0].thoughtSignature },
71
+ vertex: { thoughtSignature: msg.toolCalls[0].thoughtSignature },
72
+ };
73
+ }
74
+ parts.push(reasoningPart);
75
+ }
76
+ if (msg.toolCalls?.length) {
77
+ if (msg.content) {
78
+ parts.push({ type: 'text', text: msg.content });
79
+ }
80
+ for (const tc of msg.toolCalls) {
81
+ // V3 协议要求 input 是对象(Gemini 需要 google.protobuf.Struct)
82
+ const toolPart: Record<string, unknown> = {
83
+ type: 'tool-call',
84
+ toolCallId: tc.id,
85
+ toolName: tc.name,
86
+ input: parseProtocolToolArguments(tc.arguments, {
87
+ protocol: 'vercel-gateway',
88
+ toolCallId: tc.id,
89
+ toolName: tc.name,
90
+ }),
91
+ };
92
+ if (tc.thoughtSignature) {
93
+ toolPart.providerOptions = {
94
+ google: { thoughtSignature: tc.thoughtSignature },
95
+ vertex: { thoughtSignature: tc.thoughtSignature },
96
+ };
97
+ }
98
+ parts.push(toolPart);
99
+ }
100
+ } else {
101
+ parts.push({ type: 'text', text: msg.content || '' });
102
+ }
103
+ prompt.push({ role: 'assistant', content: parts });
104
+ break;
105
+ }
106
+
107
+ case 'tool':
108
+ prompt.push({
109
+ role: 'tool',
110
+ content: [{
111
+ type: 'tool-result',
112
+ toolCallId: msg.toolCallId || '',
113
+ toolName: msg.toolName || 'unknown',
114
+ output: formatToolOutput(msg.content),
115
+ }],
116
+ });
117
+ break;
118
+ }
119
+ }
120
+
121
+ return prompt;
122
+ }
123
+
124
+ function formatToolOutput(content: string): { type: 'json'; value: unknown } | { type: 'text'; value: string } {
125
+ try {
126
+ return { type: 'json', value: JSON.parse(content) };
127
+ } catch {
128
+ return { type: 'text', value: content };
129
+ }
130
+ }
131
+
132
+ // ==================== 工具转换 ====================
133
+
134
+ function convertTools(tools: ProtocolToolDefinition[]): unknown[] | undefined {
135
+ if (tools.length === 0) return undefined;
136
+ // V3 协议用 inputSchema(非 parameters),Claude/Bedrock 严格要求此字段
137
+ return tools.map(t => ({
138
+ type: 'function',
139
+ name: t.name,
140
+ description: t.description,
141
+ inputSchema: t.parameters,
142
+ }));
143
+ }
144
+
145
+ // ==================== Provider Options(thinking 配置) ====================
146
+
147
+ /**
148
+ * 根据 model ID 前缀构建厂商特有的 thinking 配置:
149
+ * - google/*: providerOptions.google.thinkingConfig
150
+ * - openai/*: providerOptions.openai.reasoningEffort + reasoningSummary
151
+ * - anthropic/*: providerOptions.anthropic.thinking
152
+ */
153
+ function buildProviderOptions(modelId: string, enableThinking: boolean): Record<string, unknown> | undefined {
154
+ if (modelId.startsWith('google/')) {
155
+ if (!enableThinking) return undefined;
156
+ return {
157
+ google: { thinkingConfig: { thinkingLevel: 'high', includeThoughts: true } },
158
+ };
159
+ }
160
+ if (modelId.startsWith('openai/')) {
161
+ return {
162
+ openai: {
163
+ reasoningEffort: enableThinking ? 'high' : 'low',
164
+ reasoningSummary: enableThinking ? 'detailed' : 'auto',
165
+ },
166
+ };
167
+ }
168
+ if (modelId.startsWith('anthropic/')) {
169
+ if (!enableThinking) return undefined;
170
+ return {
171
+ anthropic: { thinking: { type: 'enabled', budgetTokens: 10000 } },
172
+ };
173
+ }
174
+ return undefined;
175
+ }
176
+
177
+ // ==================== 请求体构建 ====================
178
+
179
+ function buildRequestBody(
180
+ messages: ProtocolMessage[],
181
+ tools: ProtocolToolDefinition[],
182
+ options: ProtocolRequestOptions,
183
+ ): Record<string, unknown> {
184
+ const body: Record<string, unknown> = {
185
+ prompt: convertToPrompt(messages),
186
+ };
187
+
188
+ body.maxOutputTokens = options.maxOutputTokens;
189
+
190
+ const providerOptions = buildProviderOptions(options.model, options.enableThinking);
191
+ if (providerOptions) {
192
+ body.providerOptions = providerOptions;
193
+ }
194
+
195
+ const convertedTools = convertTools(tools);
196
+ if (convertedTools) {
197
+ body.tools = convertedTools;
198
+ }
199
+
200
+ return body;
201
+ }
202
+
203
+ // ==================== V3 SSE 流解析 ====================
204
+
205
+ /**
206
+ * LanguageModelV3 流事件类型:
207
+ *
208
+ * 思考: reasoning-start → reasoning-delta { delta } → reasoning-end
209
+ * 文本: text-start → text-delta { delta } → text-end
210
+ * 工具调用: tool-input-start { toolName } → tool-input-delta { delta } → tool-input-end → tool-call { toolCallId, toolName, input }
211
+ * 完成: finish { finishReason, usage }
212
+ * 错误: error { error }
213
+ *
214
+ * 所有事件都可能携带 providerMetadata(如 Gemini 的 thoughtSignature)
215
+ */
216
+ /**
217
+ * 解析 LanguageModelV3 SSE 流
218
+ *
219
+ * Layer 1 负责 bytes → JSON,此处做 V3 格式的语义映射。
220
+ */
221
+ async function* parseV3Stream(
222
+ reader: ReadableStreamDefaultReader<Uint8Array>,
223
+ ): AsyncGenerator<RawEvent> {
224
+ let hasToolCalls = false;
225
+ const pendingCalls = new Map<string, { name: string; args: string; thoughtSignature?: string }>();
226
+
227
+ for await (const event of readSSEJsonStream(reader)) {
228
+ const type = event.type as string;
229
+
230
+ switch (type) {
231
+ case 'reasoning-delta': {
232
+ const delta = event.delta as string;
233
+ if (delta) {
234
+ yield { type: 'thinking_delta', delta };
235
+ }
236
+ break;
237
+ }
238
+ case 'reasoning-end': {
239
+ yield { type: 'thinking_done' };
240
+ break;
241
+ }
242
+
243
+ case 'text-delta': {
244
+ const delta = event.delta as string;
245
+ if (delta) {
246
+ yield { type: 'text_delta', delta };
247
+ }
248
+ break;
249
+ }
250
+
251
+ case 'tool-input-start': {
252
+ hasToolCalls = true;
253
+ const id = event.id as string;
254
+ const name = event.toolName as string;
255
+ const sig = extractThoughtSignature(event);
256
+ pendingCalls.set(id, { name, args: '', thoughtSignature: sig });
257
+ yield { type: 'tool_call_start', toolCall: { id, name } };
258
+ break;
259
+ }
260
+
261
+ case 'tool-input-delta': {
262
+ const id = event.id as string;
263
+ const delta = event.delta as string;
264
+ if (id && delta) {
265
+ const pending = pendingCalls.get(id);
266
+ if (pending) pending.args += delta;
267
+ yield { type: 'tool_call_delta', toolCall: { id, arguments: delta } };
268
+ }
269
+ break;
270
+ }
271
+
272
+ case 'tool-call': {
273
+ const id = event.toolCallId as string;
274
+ const name = event.toolName as string;
275
+ const input = (event.input ?? '{}') as string;
276
+ const pending = pendingCalls.get(id);
277
+ const sig = extractThoughtSignature(event) || pending?.thoughtSignature;
278
+ yield {
279
+ type: 'tool_call_done',
280
+ toolCall: {
281
+ id,
282
+ name: name || pending?.name || '',
283
+ arguments: input || pending?.args || '{}',
284
+ ...(sig ? { thoughtSignature: sig } : {}),
285
+ },
286
+ };
287
+ pendingCalls.delete(id);
288
+ break;
289
+ }
290
+
291
+ case 'finish': {
292
+ const rawUsage = event.usage as Record<string, unknown> | undefined;
293
+ let usage: RawTokenUsage | undefined;
294
+ if (rawUsage) {
295
+ const input = rawUsage.inputTokens as Record<string, number> | undefined;
296
+ const output = rawUsage.outputTokens as Record<string, number> | undefined;
297
+ usage = {
298
+ promptTokens: input?.total ?? 0,
299
+ completionTokens: output?.total ?? 0,
300
+ totalTokens: (input?.total ?? 0) + (output?.total ?? 0),
301
+ reasoningTokens: output?.reasoning ?? 0,
302
+ cachedTokens: input?.cacheRead ?? 0,
303
+ };
304
+ }
305
+ const reason = event.finishReason as Record<string, string> | undefined;
306
+ const unified = reason?.unified ?? 'stop';
307
+ const finishReason = hasToolCalls || unified === 'tool-calls' ? 'tool_calls' as const
308
+ : unified === 'length' ? 'length' as const
309
+ : unified === 'error' ? 'error' as const
310
+ : 'stop' as const;
311
+ yield { type: 'done', finishReason, usage };
312
+ return;
313
+ }
314
+
315
+ case 'error': {
316
+ const err = event.error;
317
+ const msg = typeof err === 'string' ? err : JSON.stringify(err);
318
+ yield { type: 'error', error: msg };
319
+ break;
320
+ }
321
+ }
322
+ }
323
+
324
+ yield { type: 'done', finishReason: hasToolCalls ? 'tool_calls' : 'stop' };
325
+ }
326
+
327
+ /** 从 providerMetadata 中提取 Gemini 的 thoughtSignature */
328
+ function extractThoughtSignature(event: Record<string, unknown>): string | undefined {
329
+ const meta = event.providerMetadata as Record<string, Record<string, unknown>> | undefined;
330
+ const sig = meta?.vertex?.thoughtSignature ?? meta?.google?.thoughtSignature;
331
+ return typeof sig === 'string' ? sig : undefined;
332
+ }
333
+
334
+ // ==================== Protocol 实现 ====================
335
+
336
+ class VercelGatewayProtocol implements Protocol {
337
+ readonly name = 'vercel_gateway';
338
+ private apiKey: string;
339
+
340
+ constructor(config: ProtocolConfig) {
341
+ this.apiKey = config.apiKey;
342
+ }
343
+
344
+ async *stream(
345
+ messages: ProtocolMessage[],
346
+ tools: ProtocolToolDefinition[],
347
+ options: ProtocolRequestOptions,
348
+ ): AsyncGenerator<RawEvent> {
349
+ const body = buildRequestBody(messages, tools, options);
350
+
351
+ logger.debug({
352
+ model: options.model,
353
+ enableThinking: options.enableThinking,
354
+ toolsCount: tools.length,
355
+ }, 'Vercel Gateway V3 请求');
356
+
357
+ const response = await fetch(`${GATEWAY_BASE}/language-model`, {
358
+ method: 'POST',
359
+ headers: {
360
+ 'Content-Type': 'application/json',
361
+ 'Authorization': `Bearer ${this.apiKey}`,
362
+ 'ai-gateway-protocol-version': '0.0.1',
363
+ 'ai-gateway-auth-method': 'api-key',
364
+ 'ai-language-model-specification-version': '3',
365
+ 'ai-language-model-id': options.model,
366
+ 'ai-language-model-streaming': 'true',
367
+ },
368
+ body: JSON.stringify(body),
369
+ signal: options.signal,
370
+ });
371
+
372
+ if (!response.ok) {
373
+ const errorText = await response.text();
374
+ logger.error({ status: response.status, body: errorText.slice(0, 500) }, 'Vercel Gateway 错误');
375
+ yield { type: 'error', error: friendlyHttpError(response.status, errorText, 'Vercel Gateway') };
376
+ return;
377
+ }
378
+
379
+ const reader = response.body?.getReader();
380
+ if (!reader) {
381
+ yield { type: 'error', error: '无法获取响应流' };
382
+ return;
383
+ }
384
+
385
+ yield* parseV3Stream(reader);
386
+ }
387
+ }
388
+
389
+ export function createVercelGatewayProtocol(config: ProtocolConfig): Protocol {
390
+ return new VercelGatewayProtocol(config);
391
+ }