@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
@@ -0,0 +1,212 @@
1
+ /**
2
+ * GLM Protocol(GLM OpenAI 兼容模式 API)
3
+ *
4
+ * API 格式与 Qwen 几乎一致(OpenAI Chat Completions),
5
+ * 主要区别:
6
+ * - thinking 参数:`"thinking": {"type": "enabled"}` (Qwen 用 `"enable_thinking": true`)
7
+ * - 默认 URL:https://open.bigmodel.cn/api/paas/v4
8
+ */
9
+
10
+ import type {
11
+ Protocol,
12
+ ProtocolConfig,
13
+ ProtocolMessage,
14
+ ProtocolToolDefinition,
15
+ ProtocolRequestOptions,
16
+ RawEvent,
17
+ } from './types';
18
+ import { createModuleLogger } from '../logger';
19
+ import { friendlyHttpError } from './error-utils';
20
+ import { readSSEJsonStream } from './sse-reader';
21
+ import { mapOpenAIStream } from './openai-sse';
22
+ import type { OpenAIStreamConfig } from './openai-sse';
23
+
24
+ const logger = createModuleLogger('GlmProtocol');
25
+
26
+ const DEFAULT_GLM_URL = 'https://open.bigmodel.cn/api/paas/v4';
27
+
28
+ const GLM_SSE_CONFIG: OpenAIStreamConfig = {
29
+ thinkingField: (delta) => delta.reasoning_content as string | undefined,
30
+ errorFinishReasons: ['sensitive', 'network_error'],
31
+ protocolName: 'GLM',
32
+ parseUsage: (usage) => ({
33
+ promptTokens: (usage.prompt_tokens as number) ?? 0,
34
+ completionTokens: (usage.completion_tokens as number) ?? 0,
35
+ totalTokens: (usage.total_tokens as number) ?? 0,
36
+ cachedTokens: (usage.prompt_tokens_details as Record<string, number> | undefined)?.cached_tokens ?? 0,
37
+ }),
38
+ };
39
+
40
+ export class GlmProtocol implements Protocol {
41
+ readonly name = 'glm';
42
+
43
+ private apiKey: string;
44
+ private apiUrl: string;
45
+
46
+ constructor(config: ProtocolConfig) {
47
+ this.apiKey = config.apiKey;
48
+ this.apiUrl = config.apiUrl || DEFAULT_GLM_URL;
49
+ }
50
+
51
+ async *stream(
52
+ messages: ProtocolMessage[],
53
+ tools: ProtocolToolDefinition[],
54
+ options: ProtocolRequestOptions
55
+ ): AsyncGenerator<RawEvent> {
56
+ const requestBody = this.buildRequestBody(messages, tools, options);
57
+ const url = `${this.apiUrl}/chat/completions`;
58
+
59
+ logger.debug({
60
+ url,
61
+ model: options.model,
62
+ enableThinking: options.enableThinking,
63
+ toolsCount: tools.length,
64
+ }, '发送 GLM 请求');
65
+
66
+ const response = await fetch(url, {
67
+ method: 'POST',
68
+ headers: {
69
+ 'Authorization': `Bearer ${this.apiKey}`,
70
+ 'Content-Type': 'application/json',
71
+ },
72
+ body: JSON.stringify(requestBody),
73
+ signal: options.signal,
74
+ });
75
+
76
+ if (!response.ok) {
77
+ const errorText = await response.text();
78
+ logger.error({ status: response.status, body: errorText.slice(0, 500) }, 'GLM API 错误');
79
+ yield { type: 'error', error: friendlyHttpError(response.status, errorText, 'GLM') };
80
+ return;
81
+ }
82
+
83
+ const reader = response.body?.getReader();
84
+ if (!reader) {
85
+ yield { type: 'error', error: '无法获取响应流' };
86
+ return;
87
+ }
88
+
89
+ let hadThinkingDelta = false;
90
+ let hadTextDelta = false;
91
+ let hadToolCall = false;
92
+ for await (const ev of mapOpenAIStream(readSSEJsonStream(reader), GLM_SSE_CONFIG)) {
93
+ if (ev.type === 'thinking_delta') hadThinkingDelta = true;
94
+ if (ev.type === 'text_delta') hadTextDelta = true;
95
+ if (ev.type === 'tool_call_start' || ev.type === 'tool_call_done') hadToolCall = true;
96
+ yield ev;
97
+ }
98
+ const payload = {
99
+ model: options.model,
100
+ toolsCount: tools.length,
101
+ hadThinkingDelta,
102
+ hadTextDelta,
103
+ hadToolCall,
104
+ };
105
+ if (!hadThinkingDelta && !hadTextDelta && !hadToolCall) {
106
+ // 与 Orchestrator「空响应」合并:此处仅 debug,避免与编排层重复 WARN
107
+ logger.debug(payload, 'GLM 流结束:未产出思考/正文/工具调用(常见于工具或上下文过大)');
108
+ } else {
109
+ logger.debug(payload, 'GLM 流结束');
110
+ }
111
+ }
112
+
113
+ private buildRequestBody(
114
+ messages: ProtocolMessage[],
115
+ tools: ProtocolToolDefinition[],
116
+ options: ProtocolRequestOptions
117
+ ): Record<string, unknown> {
118
+ const convertedMessages = this.convertMessages(messages);
119
+
120
+ const maxTokens = options.maxOutputTokens;
121
+
122
+ const body: Record<string, unknown> = {
123
+ model: options.model,
124
+ messages: convertedMessages,
125
+ stream: true,
126
+ stream_options: { include_usage: true },
127
+ max_tokens: maxTokens,
128
+ };
129
+
130
+ // GLM thinking 默认 enabled(文档默认值),必须显式发送 disabled 才能关闭
131
+ if (options.enableThinking) {
132
+ body.thinking = { type: 'enabled' };
133
+ body.temperature = 1.0;
134
+ } else {
135
+ body.thinking = { type: 'disabled' };
136
+ }
137
+
138
+ if (tools.length > 0) {
139
+ body.tools = tools.map(t => ({
140
+ type: 'function',
141
+ function: {
142
+ name: t.name,
143
+ description: t.description,
144
+ parameters: t.parameters,
145
+ },
146
+ }));
147
+ }
148
+
149
+ return body;
150
+ }
151
+
152
+ private convertMessages(messages: ProtocolMessage[]): unknown[] {
153
+ const result: unknown[] = [];
154
+
155
+ for (const msg of messages) {
156
+ switch (msg.role) {
157
+ case 'system':
158
+ result.push({ role: 'system', content: msg.content });
159
+ break;
160
+
161
+ case 'user': {
162
+ const textContent = msg.content || (msg.images?.length ? '请分析这张图片' : '');
163
+ if (msg.images?.length) {
164
+ const content: unknown[] = [{ type: 'text', text: textContent }];
165
+ for (const img of msg.images) {
166
+ content.push({
167
+ type: 'image_url',
168
+ image_url: { url: img.startsWith('data:') ? img : `data:image/jpeg;base64,${img}` },
169
+ });
170
+ }
171
+ result.push({ role: 'user', content });
172
+ } else {
173
+ result.push({ role: 'user', content: textContent });
174
+ }
175
+ break;
176
+ }
177
+
178
+ case 'assistant':
179
+ if (msg.toolCalls?.length) {
180
+ result.push({
181
+ role: 'assistant',
182
+ content: msg.content || null,
183
+ ...(msg.thinkingContent ? { reasoning_content: msg.thinkingContent } : {}),
184
+ tool_calls: msg.toolCalls.map(tc => ({
185
+ id: tc.id,
186
+ type: 'function',
187
+ function: { name: tc.name, arguments: tc.arguments },
188
+ })),
189
+ });
190
+ } else {
191
+ result.push({ role: 'assistant', content: msg.content });
192
+ }
193
+ break;
194
+
195
+ case 'tool':
196
+ result.push({
197
+ role: 'tool',
198
+ tool_call_id: msg.toolCallId,
199
+ content: msg.content,
200
+ });
201
+ break;
202
+ }
203
+ }
204
+
205
+ return result;
206
+ }
207
+
208
+ }
209
+
210
+ export function createGlmProtocol(config: ProtocolConfig): GlmProtocol {
211
+ return new GlmProtocol(config);
212
+ }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Grok Protocol(xAI Responses API)
3
+ *
4
+ * xAI 与 OpenAI 使用相同的 Responses API 格式(/v1/responses),
5
+ * 共享消息转换和 SSE 解析逻辑,仅保留 Grok 特有的入口和参数差异。
6
+ *
7
+ * 支持直连 api.x.ai 或代理(如 llm.huyooo.com/grok)。
8
+ *
9
+ * 文档:https://docs.x.ai/developers/rest-api-reference/inference/chat
10
+ */
11
+
12
+ import type {
13
+ Protocol,
14
+ ProtocolConfig,
15
+ ProtocolMessage,
16
+ ProtocolToolDefinition,
17
+ ProtocolRequestOptions,
18
+ RawEvent,
19
+ } from './types';
20
+ import { createModuleLogger } from '../logger';
21
+ import { friendlyHttpError } from './error-utils';
22
+ import { convertToResponsesInput, convertToResponsesTools, mapResponsesStream } from './responses-sse';
23
+
24
+ const logger = createModuleLogger('GrokProtocol');
25
+
26
+ const DEFAULT_GROK_URL = 'https://api.x.ai/v1';
27
+
28
+ export class GrokProtocol implements Protocol {
29
+ readonly name = 'grok';
30
+
31
+ private apiKey: string;
32
+ private apiUrl: string;
33
+
34
+ constructor(config: ProtocolConfig) {
35
+ this.apiKey = config.apiKey;
36
+ this.apiUrl = config.apiUrl ?? DEFAULT_GROK_URL;
37
+ }
38
+
39
+ async *stream(
40
+ messages: ProtocolMessage[],
41
+ tools: ProtocolToolDefinition[],
42
+ options: ProtocolRequestOptions,
43
+ ): AsyncGenerator<RawEvent> {
44
+ const body: Record<string, unknown> = {
45
+ model: options.model,
46
+ input: convertToResponsesInput(messages),
47
+ stream: true,
48
+ max_output_tokens: options.maxOutputTokens,
49
+ store: false,
50
+ };
51
+
52
+ if (options.enableThinking) {
53
+ body.reasoning = { effort: 'high' };
54
+ }
55
+
56
+ if (tools.length > 0) {
57
+ body.tools = convertToResponsesTools(tools);
58
+ }
59
+
60
+ const url = `${this.apiUrl}/responses`;
61
+
62
+ logger.debug({
63
+ url,
64
+ model: options.model,
65
+ enableThinking: options.enableThinking,
66
+ toolsCount: tools.length,
67
+ }, 'Grok Responses 请求');
68
+
69
+ const response = await fetch(url, {
70
+ method: 'POST',
71
+ headers: {
72
+ 'Authorization': `Bearer ${this.apiKey}`,
73
+ 'Content-Type': 'application/json',
74
+ },
75
+ body: JSON.stringify(body),
76
+ signal: options.signal,
77
+ });
78
+
79
+ if (!response.ok) {
80
+ const errorText = await response.text();
81
+ logger.error({ status: response.status, body: errorText.slice(0, 500) }, 'Grok API 错误');
82
+ yield { type: 'error', error: friendlyHttpError(response.status, errorText, 'Grok') };
83
+ return;
84
+ }
85
+
86
+ const reader = response.body?.getReader();
87
+ if (!reader) {
88
+ yield { type: 'error', error: '无法获取响应流' };
89
+ return;
90
+ }
91
+
92
+ yield* mapResponsesStream(reader, { protocolName: 'Grok' });
93
+ }
94
+ }
95
+
96
+ export function createGrokProtocol(config: ProtocolConfig): GrokProtocol {
97
+ return new GrokProtocol(config);
98
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Protocol Layer 导出
3
+ *
4
+ * Protocol 只负责 HTTP/SSE 通信,不处理业务逻辑
5
+ *
6
+ * 每个 Protocol 对应一种 API 协议:
7
+ * - ArkProtocol: 火山引擎 Responses API(Doubao)
8
+ * - DeepSeekProtocol: DeepSeek 原生 Chat Completions API
9
+ * - QwenProtocol: Qwen DashScope API
10
+ * - GlmProtocol: GLM API
11
+ * - MoonshotProtocol: Kimi API
12
+ * - MiniMaxProtocol: MiniMax Anthropic 兼容 API
13
+ * - VercelGateway: GPT + Gemini + Claude(通过 Vercel AI Gateway V3 协议)
14
+ * - GeminiProtocol: Google Gemini 原生 API(直连 / 代理透传)
15
+ * - OpenAIProtocol: OpenAI Responses API(直连 / 代理透传,统一支持 GPT / Codex / o-series)
16
+ * - GrokProtocol: xAI Grok(独立实现,reasoning_content 思考格式)
17
+ */
18
+
19
+ // 类型 + 协议标识
20
+ export type {
21
+ ProtocolId,
22
+ RawEventType,
23
+ RawEvent,
24
+ RawOutputPart,
25
+ RawToolCall,
26
+ RawTokenUsage,
27
+ ProtocolMessage,
28
+ ProtocolToolDefinition,
29
+ ProtocolRequestOptions,
30
+ Protocol,
31
+ ProtocolConfig,
32
+ ProtocolAttachment,
33
+ ProtocolFactory,
34
+ } from './types';
35
+ export { getBaseProtocol } from './types';
36
+
37
+ // 各 Protocol 实现
38
+ export { ArkProtocol, createArkProtocol } from './ark';
39
+ export { DeepSeekProtocol, createDeepSeekProtocol } from './deepseek';
40
+ export { QwenProtocol, createQwenProtocol } from './qwen';
41
+ export { GlmProtocol, createGlmProtocol } from './glm';
42
+ export { MoonshotProtocol, createMoonshotProtocol } from './moonshot';
43
+ export { MiniMaxProtocol, createMiniMaxProtocol } from './minimax';
44
+ export { createVercelGatewayProtocol } from './vercel-gateway';
45
+ export { AnthropicProtocol, createAnthropicProtocol } from './anthropic';
46
+ export { GeminiProtocol, createGeminiProtocol } from './gemini';
47
+ export { OpenAIProtocol, createOpenAIProtocol } from './openai';
48
+ export { GrokProtocol, createGrokProtocol } from './grok';
@@ -0,0 +1,308 @@
1
+ /**
2
+ * MiniMax Protocol(Anthropic Messages API 兼容)
3
+ *
4
+ * 官方推荐使用 Anthropic 格式接入 MiniMax-M2.5。
5
+ * 主要区别于 OpenAI 兼容协议:
6
+ * - SSE 使用 Anthropic 命名事件(content_block_start/delta/stop)
7
+ * - Thinking 始终开启,通过 thinking_delta 事件输出
8
+ * - 工具调用使用 tool_use/tool_result 格式
9
+ * - system 是顶层参数(不在 messages 内)
10
+ *
11
+ * @see https://platform.minimaxi.com/docs/api-reference/text-anthropic-api
12
+ */
13
+
14
+ import type {
15
+ Protocol,
16
+ ProtocolConfig,
17
+ ProtocolMessage,
18
+ ProtocolToolDefinition,
19
+ ProtocolRequestOptions,
20
+ RawEvent,
21
+ RawToolCall,
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('MiniMaxProtocol');
29
+
30
+ const DEFAULT_MINIMAX_URL = 'https://api.minimaxi.com/anthropic';
31
+
32
+ export class MiniMaxProtocol implements Protocol {
33
+ readonly name = 'minimax';
34
+
35
+ private apiKey: string;
36
+ private apiUrl: string;
37
+
38
+ constructor(config: ProtocolConfig) {
39
+ this.apiKey = config.apiKey;
40
+ this.apiUrl = config.apiUrl || DEFAULT_MINIMAX_URL;
41
+ }
42
+
43
+ async *stream(
44
+ messages: ProtocolMessage[],
45
+ tools: ProtocolToolDefinition[],
46
+ options: ProtocolRequestOptions
47
+ ): AsyncGenerator<RawEvent> {
48
+ const requestBody = this.buildRequestBody(messages, tools, options);
49
+ const url = `${this.apiUrl}/v1/messages`;
50
+
51
+ logger.debug({
52
+ url,
53
+ model: options.model,
54
+ enableThinking: options.enableThinking,
55
+ toolsCount: tools.length,
56
+ }, '发送 MiniMax 请求(Anthropic 格式)');
57
+
58
+ const response = await fetch(url, {
59
+ method: 'POST',
60
+ headers: {
61
+ 'x-api-key': this.apiKey,
62
+ 'anthropic-version': '2023-06-01',
63
+ 'Content-Type': 'application/json',
64
+ },
65
+ body: JSON.stringify(requestBody),
66
+ signal: options.signal,
67
+ });
68
+
69
+ if (!response.ok) {
70
+ const errorText = await response.text();
71
+ logger.error({ status: response.status, body: errorText.slice(0, 500) }, 'MiniMax API 错误');
72
+ yield { type: 'error', error: friendlyHttpError(response.status, errorText, 'MiniMax') };
73
+ return;
74
+ }
75
+
76
+ const reader = response.body?.getReader();
77
+ if (!reader) {
78
+ yield { type: 'error', error: '无法获取响应流' };
79
+ return;
80
+ }
81
+
82
+ yield* this.parseAnthropicSSE(reader);
83
+ }
84
+
85
+ private buildRequestBody(
86
+ messages: ProtocolMessage[],
87
+ tools: ProtocolToolDefinition[],
88
+ options: ProtocolRequestOptions
89
+ ): Record<string, unknown> {
90
+ const { systemPrompt, convertedMessages } = this.convertMessages(messages);
91
+
92
+ const body: Record<string, unknown> = {
93
+ model: options.model,
94
+ messages: convertedMessages,
95
+ max_tokens: options.maxOutputTokens,
96
+ stream: true,
97
+ };
98
+
99
+ if (systemPrompt) {
100
+ body.system = systemPrompt;
101
+ }
102
+
103
+ if (tools.length > 0) {
104
+ body.tools = tools.map(t => ({
105
+ name: t.name,
106
+ description: t.description,
107
+ input_schema: t.parameters,
108
+ }));
109
+ }
110
+
111
+ return body;
112
+ }
113
+
114
+ /**
115
+ * 转换消息格式(ProtocolMessage → Anthropic Messages 格式)
116
+ *
117
+ * Anthropic 格式要求:
118
+ * - system 是顶层参数,不在 messages 里
119
+ * - messages 必须 user/assistant 交替
120
+ * - 工具结果用 tool_result 块放在 user 消息里
121
+ */
122
+ private convertMessages(messages: ProtocolMessage[]): {
123
+ systemPrompt: string;
124
+ convertedMessages: unknown[];
125
+ } {
126
+ let systemPrompt = '';
127
+ const result: unknown[] = [];
128
+
129
+ for (const msg of messages) {
130
+ switch (msg.role) {
131
+ case 'system':
132
+ systemPrompt += (systemPrompt ? '\n\n' : '') + msg.content;
133
+ break;
134
+
135
+ case 'user':
136
+ result.push({
137
+ role: 'user',
138
+ content: [{ type: 'text', text: msg.content || '' }],
139
+ });
140
+ break;
141
+
142
+ case 'assistant':
143
+ if (msg.toolCalls?.length) {
144
+ // assistant 带工具调用:先文本再 tool_use 块
145
+ const content: unknown[] = [];
146
+ if (msg.content) {
147
+ content.push({ type: 'text', text: msg.content });
148
+ }
149
+ for (const tc of msg.toolCalls) {
150
+ content.push({
151
+ type: 'tool_use',
152
+ id: tc.id,
153
+ name: tc.name,
154
+ input: parseProtocolToolArguments(tc.arguments, {
155
+ protocol: this.name,
156
+ toolCallId: tc.id,
157
+ toolName: tc.name,
158
+ }),
159
+ });
160
+ }
161
+ result.push({ role: 'assistant', content });
162
+ } else {
163
+ result.push({
164
+ role: 'assistant',
165
+ content: [{ type: 'text', text: msg.content || '' }],
166
+ });
167
+ }
168
+ break;
169
+
170
+ case 'tool':
171
+ // Anthropic 格式:tool_result 放在 user 消息中
172
+ result.push({
173
+ role: 'user',
174
+ content: [{
175
+ type: 'tool_result',
176
+ tool_use_id: msg.toolCallId,
177
+ content: msg.content,
178
+ }],
179
+ });
180
+ break;
181
+ }
182
+ }
183
+
184
+ return { systemPrompt, convertedMessages: result };
185
+ }
186
+
187
+ /**
188
+ * Anthropic SSE 解析(MiniMax 兼容)
189
+ *
190
+ * Layer 1 负责 bytes → JSON,此处通过 json.type 做语义映射。
191
+ */
192
+ private async *parseAnthropicSSE(
193
+ reader: ReadableStreamDefaultReader<Uint8Array>,
194
+ ): AsyncGenerator<RawEvent> {
195
+ let thinkingStarted = false;
196
+ let thinkingDone = false;
197
+ const toolCalls = new Map<number, RawToolCall & { jsonBuffer: string }>();
198
+ let inputTokens = 0;
199
+ let outputTokens = 0;
200
+
201
+ for await (const json of readSSEJsonStream(reader)) {
202
+ switch (json.type) {
203
+ case 'message_start': {
204
+ const msg = json.message as Record<string, unknown> | undefined;
205
+ const msgUsage = msg?.usage as Record<string, number> | undefined;
206
+ if (msgUsage?.input_tokens) {
207
+ inputTokens = msgUsage.input_tokens;
208
+ }
209
+ break;
210
+ }
211
+
212
+ case 'content_block_start': {
213
+ const block = json.content_block as Record<string, unknown> | undefined;
214
+ if (block?.type === 'tool_use') {
215
+ if (!block.id) logger.warn({ index: json.index }, 'content_block_start tool_use 缺少 id');
216
+ if (!block.name) logger.warn({ index: json.index, id: block.id }, 'content_block_start tool_use 缺少 name');
217
+ const index = (json.index as number) ?? toolCalls.size;
218
+ const id = (block.id as string) || `call_${index}`;
219
+ const name = (block.name as string) || '';
220
+ toolCalls.set(index, { id, name, arguments: '', jsonBuffer: '' });
221
+ yield { type: 'tool_call_start', toolCall: { id, name } };
222
+ }
223
+ break;
224
+ }
225
+
226
+ case 'content_block_delta': {
227
+ const delta = json.delta as Record<string, unknown> | undefined;
228
+ if (!delta) break;
229
+
230
+ if (delta.type === 'thinking_delta' && delta.thinking) {
231
+ if (!thinkingDone) {
232
+ thinkingStarted = true;
233
+ yield { type: 'thinking_delta', delta: delta.thinking as string };
234
+ }
235
+ } else if (delta.type === 'text_delta' && delta.text) {
236
+ if (thinkingStarted && !thinkingDone) {
237
+ thinkingDone = true;
238
+ yield { type: 'thinking_done' };
239
+ }
240
+ yield { type: 'text_delta', delta: delta.text as string };
241
+ } else if (delta.type === 'input_json_delta' && delta.partial_json !== undefined) {
242
+ const index = json.index as number;
243
+ const tc = toolCalls.get(index);
244
+ if (tc) {
245
+ tc.jsonBuffer += delta.partial_json;
246
+ }
247
+ }
248
+ break;
249
+ }
250
+
251
+ case 'content_block_stop': {
252
+ const index = json.index as number;
253
+ const tc = toolCalls.get(index);
254
+ if (tc) {
255
+ tc.arguments = tc.jsonBuffer;
256
+ yield {
257
+ type: 'tool_call_done',
258
+ toolCall: { id: tc.id, name: tc.name, arguments: tc.arguments },
259
+ };
260
+ }
261
+ break;
262
+ }
263
+
264
+ case 'message_delta': {
265
+ const deltaUsage = json.usage as Record<string, number> | undefined;
266
+ if (deltaUsage?.output_tokens) {
267
+ outputTokens = deltaUsage.output_tokens;
268
+ }
269
+ break;
270
+ }
271
+
272
+ case 'message_stop': {
273
+ const hasToolCalls = toolCalls.size > 0;
274
+ const usage = {
275
+ promptTokens: inputTokens,
276
+ completionTokens: outputTokens,
277
+ totalTokens: inputTokens + outputTokens,
278
+ };
279
+ if (thinkingStarted && !thinkingDone) {
280
+ thinkingDone = true;
281
+ yield { type: 'thinking_done' };
282
+ }
283
+ yield {
284
+ type: 'done',
285
+ finishReason: hasToolCalls ? 'tool_calls' : 'stop',
286
+ usage,
287
+ };
288
+ return;
289
+ }
290
+ }
291
+ }
292
+
293
+ // 流异常中断兜底
294
+ if (thinkingStarted && !thinkingDone) {
295
+ yield { type: 'thinking_done' };
296
+ }
297
+ const hasToolCalls = toolCalls.size > 0;
298
+ yield {
299
+ type: 'done',
300
+ finishReason: hasToolCalls ? 'tool_calls' : 'stop',
301
+ usage: { promptTokens: inputTokens, completionTokens: outputTokens, totalTokens: inputTokens + outputTokens },
302
+ };
303
+ }
304
+ }
305
+
306
+ export function createMiniMaxProtocol(config: ProtocolConfig): MiniMaxProtocol {
307
+ return new MiniMaxProtocol(config);
308
+ }