@huyooo/ai-chat-core 0.2.44 → 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,186 @@
1
+ /**
2
+ * Moonshot Protocol(Kimi K2.5 OpenAI 兼容 API)
3
+ *
4
+ * 完全兼容 OpenAI Chat Completions 格式,主要区别:
5
+ * - thinking 参数:`"reasoning": {"enabled": true}` + `temperature: 1.0`
6
+ * - 默认 URL:https://api.moonshot.cn/v1
7
+ * - 流式 thinking 输出:delta.reasoning_content(与 DeepSeek/GLM 一致)
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('MoonshotProtocol');
25
+
26
+ const DEFAULT_MOONSHOT_URL = 'https://api.moonshot.cn/v1';
27
+
28
+ const MOONSHOT_SSE_CONFIG: OpenAIStreamConfig = {
29
+ thinkingField: (delta) => delta.reasoning_content as string | undefined,
30
+ errorFinishReasons: ['content_filter'],
31
+ protocolName: 'Moonshot',
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 MoonshotProtocol implements Protocol {
41
+ readonly name = 'moonshot';
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_MOONSHOT_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
+ }, '发送 Moonshot 请求');
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) }, 'Moonshot API 错误');
79
+ yield { type: 'error', error: friendlyHttpError(response.status, errorText, 'Moonshot') };
80
+ return;
81
+ }
82
+
83
+ const reader = response.body?.getReader();
84
+ if (!reader) {
85
+ yield { type: 'error', error: '无法获取响应流' };
86
+ return;
87
+ }
88
+
89
+ yield* mapOpenAIStream(readSSEJsonStream(reader), MOONSHOT_SSE_CONFIG);
90
+ }
91
+
92
+ private buildRequestBody(
93
+ messages: ProtocolMessage[],
94
+ tools: ProtocolToolDefinition[],
95
+ options: ProtocolRequestOptions
96
+ ): Record<string, unknown> {
97
+ const convertedMessages = this.convertMessages(messages);
98
+
99
+ const body: Record<string, unknown> = {
100
+ model: options.model,
101
+ messages: convertedMessages,
102
+ stream: true,
103
+ stream_options: { include_usage: true },
104
+ };
105
+
106
+ // Moonshot thinking 格式:reasoning: {enabled: true},需 temperature=1.0
107
+ if (options.enableThinking) {
108
+ body.reasoning = { enabled: true };
109
+ body.temperature = 1.0;
110
+ }
111
+
112
+ if (tools.length > 0) {
113
+ body.tools = tools.map(t => ({
114
+ type: 'function',
115
+ function: {
116
+ name: t.name,
117
+ description: t.description,
118
+ parameters: t.parameters,
119
+ },
120
+ }));
121
+ }
122
+
123
+ return body;
124
+ }
125
+
126
+ private convertMessages(messages: ProtocolMessage[]): unknown[] {
127
+ const result: unknown[] = [];
128
+
129
+ for (const msg of messages) {
130
+ switch (msg.role) {
131
+ case 'system':
132
+ result.push({ role: 'system', content: msg.content });
133
+ break;
134
+
135
+ case 'user': {
136
+ const textContent = msg.content || (msg.images?.length ? '请分析这张图片' : '');
137
+ if (msg.images?.length) {
138
+ const content: unknown[] = [{ type: 'text', text: textContent }];
139
+ for (const img of msg.images) {
140
+ content.push({
141
+ type: 'image_url',
142
+ image_url: { url: img.startsWith('data:') ? img : `data:image/jpeg;base64,${img}` },
143
+ });
144
+ }
145
+ result.push({ role: 'user', content });
146
+ } else {
147
+ result.push({ role: 'user', content: textContent });
148
+ }
149
+ break;
150
+ }
151
+
152
+ case 'assistant':
153
+ if (msg.toolCalls?.length) {
154
+ result.push({
155
+ role: 'assistant',
156
+ content: msg.content || null,
157
+ ...(msg.thinkingContent ? { reasoning_content: msg.thinkingContent } : {}),
158
+ tool_calls: msg.toolCalls.map(tc => ({
159
+ id: tc.id,
160
+ type: 'function',
161
+ function: { name: tc.name, arguments: tc.arguments },
162
+ })),
163
+ });
164
+ } else {
165
+ result.push({ role: 'assistant', content: msg.content });
166
+ }
167
+ break;
168
+
169
+ case 'tool':
170
+ result.push({
171
+ role: 'tool',
172
+ tool_call_id: msg.toolCallId,
173
+ content: msg.content,
174
+ });
175
+ break;
176
+ }
177
+ }
178
+
179
+ return result;
180
+ }
181
+
182
+ }
183
+
184
+ export function createMoonshotProtocol(config: ProtocolConfig): MoonshotProtocol {
185
+ return new MoonshotProtocol(config);
186
+ }
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Layer 2: OpenAI 兼容 SSE 语义映射器
3
+ *
4
+ * 消费 Layer 1 的 JSON 流,映射为 RawEvent。
5
+ * 通过 config 注入协议差异,零 if/else 分支。
6
+ *
7
+ * 共享逻辑:
8
+ * - choices[0].delta.content → text_delta
9
+ * - delta.tool_calls → tool_call_start / tool_call_done
10
+ * - thinking 状态机(首段 text 自动关闭 thinking)
11
+ * - [流结束] tool_call_done 发射 + done 事件
12
+ *
13
+ * 使用方:deepseek, glm, moonshot, grok, openai
14
+ */
15
+
16
+ import type { RawEvent, RawToolCall, RawTokenUsage } from './types';
17
+ import { createModuleLogger } from '../logger';
18
+
19
+ const logger = createModuleLogger('OpenAISSE');
20
+
21
+ // ==================== 配置接口 ====================
22
+
23
+ export interface OpenAIStreamConfig {
24
+ /** 从 delta 提取 thinking 内容(不提供 = 不支持 thinking) */
25
+ thinkingField?: (delta: Record<string, unknown>) => string | undefined;
26
+ /** 厂商特有的 finish_reason → error 映射(列出视为 error 的 reason) */
27
+ errorFinishReasons?: string[];
28
+ /** 协议名(用于错误消息) */
29
+ protocolName?: string;
30
+ /** 自定义 usage 提取(不提供则用默认 OpenAI 格式) */
31
+ parseUsage?: (usage: Record<string, unknown>) => RawTokenUsage;
32
+ }
33
+
34
+ // ==================== 默认 usage 提取 ====================
35
+
36
+ function defaultParseUsage(usage: Record<string, unknown>): RawTokenUsage {
37
+ const details = usage.completion_tokens_details as Record<string, number> | undefined;
38
+ const promptDetails = usage.prompt_tokens_details as Record<string, number> | undefined;
39
+ return {
40
+ promptTokens: (usage.prompt_tokens as number) ?? 0,
41
+ completionTokens: (usage.completion_tokens as number) ?? 0,
42
+ totalTokens: (usage.total_tokens as number) ?? 0,
43
+ reasoningTokens: details?.reasoning_tokens ?? 0,
44
+ cachedTokens: promptDetails?.cached_tokens ?? (usage.prompt_cache_hit_tokens as number) ?? 0,
45
+ };
46
+ }
47
+
48
+ // ==================== finish_reason 映射 ====================
49
+
50
+ function mapFinishReason(
51
+ reason: string | undefined,
52
+ errorReasons?: string[],
53
+ ): RawEvent['finishReason'] {
54
+ if (!reason) return 'stop';
55
+ if (reason === 'tool_calls' || reason === 'length' || reason === 'error') return reason;
56
+ if (errorReasons?.includes(reason)) return 'error';
57
+ return 'stop';
58
+ }
59
+
60
+ // ==================== 核心映射器 ====================
61
+
62
+ /**
63
+ * 将 OpenAI 兼容的 JSON 流映射为 RawEvent 流
64
+ *
65
+ * @param jsonStream - Layer 1 的 readSSEJsonStream 输出
66
+ * @param config - 协议差异配置(可选,不传则为标准 OpenAI 行为)
67
+ */
68
+ export async function* mapOpenAIStream(
69
+ jsonStream: AsyncGenerator<Record<string, unknown>>,
70
+ config?: OpenAIStreamConfig,
71
+ ): AsyncGenerator<RawEvent> {
72
+ const toolCallsMap = new Map<number, RawToolCall>();
73
+ let textStarted = false;
74
+ let thinkingDone = false;
75
+ let lastUsage: RawTokenUsage | undefined;
76
+ let lastFinishReason: string | undefined;
77
+
78
+ const parseUsage = config?.parseUsage ?? defaultParseUsage;
79
+ const thinkingField = config?.thinkingField;
80
+
81
+ for await (const json of jsonStream) {
82
+ // Usage
83
+ if (json.usage) {
84
+ lastUsage = parseUsage(json.usage as Record<string, unknown>);
85
+ }
86
+
87
+ const choices = json.choices as Array<Record<string, unknown>> | undefined;
88
+ const choice = choices?.[0];
89
+ if (!choice) continue;
90
+
91
+ const delta = choice.delta as Record<string, unknown> | undefined;
92
+ if (!delta) continue;
93
+
94
+ // Thinking
95
+ if (thinkingField && !thinkingDone) {
96
+ const thinking = thinkingField(delta);
97
+ if (thinking) {
98
+ yield { type: 'thinking_delta', delta: thinking };
99
+ }
100
+ }
101
+
102
+ // Text
103
+ const content = delta.content as string | undefined;
104
+ if (content) {
105
+ if (!textStarted && !thinkingDone && thinkingField) {
106
+ thinkingDone = true;
107
+ yield { type: 'thinking_done' };
108
+ }
109
+ textStarted = true;
110
+ yield { type: 'text_delta', delta: content };
111
+ }
112
+
113
+ // Tool calls
114
+ const toolCalls = delta.tool_calls as Array<Record<string, unknown>> | undefined;
115
+ if (toolCalls?.length) {
116
+ for (const tc of toolCalls) {
117
+ const index = (tc.index as number) ?? 0;
118
+ const fn = tc.function as Record<string, string> | undefined;
119
+ const existing = toolCallsMap.get(index);
120
+
121
+ if (existing) {
122
+ if (fn?.arguments) {
123
+ existing.arguments += fn.arguments;
124
+ }
125
+ } else {
126
+ const id = (tc.id as string) || `call_${index}`;
127
+ const name = fn?.name || '';
128
+ toolCallsMap.set(index, { id, name, arguments: fn?.arguments || '' });
129
+ yield { type: 'tool_call_start', toolCall: { id, name } };
130
+ }
131
+ }
132
+ }
133
+
134
+ // Finish reason
135
+ const finishReason = choice.finish_reason as string | undefined;
136
+ if (finishReason) {
137
+ lastFinishReason = finishReason;
138
+ }
139
+ }
140
+
141
+ // 流结束:发射累积的 tool calls + done
142
+ const allToolCalls = Array.from(toolCallsMap.values());
143
+ if (allToolCalls.length > 0) {
144
+ for (const tc of allToolCalls) {
145
+ yield { type: 'tool_call_done', toolCall: tc };
146
+ }
147
+ yield { type: 'done', finishReason: 'tool_calls', usage: lastUsage };
148
+ } else {
149
+ const reason = mapFinishReason(lastFinishReason, config?.errorFinishReasons);
150
+ if (reason === 'error') {
151
+ const name = config?.protocolName ?? 'LLM';
152
+ yield { type: 'error', error: `${name} 推理异常 (finish_reason: ${lastFinishReason})` };
153
+ }
154
+ yield { type: 'done', finishReason: reason, usage: lastUsage };
155
+ }
156
+ }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * OpenAI Protocol(Responses API)
3
+ *
4
+ * 使用 /v1/responses 端点,统一支持所有 OpenAI 模型(GPT、Codex、o-series)。
5
+ *
6
+ * 支持直连或代理(如 llm.huyooo.com):
7
+ * - 直连:apiUrl = https://api.openai.com/v1,Key 走 Authorization
8
+ * - 代理:apiUrl = https://llm.huyooo.com/openai,Key 为代理认证
9
+ */
10
+
11
+ import type {
12
+ Protocol,
13
+ ProtocolConfig,
14
+ ProtocolMessage,
15
+ ProtocolToolDefinition,
16
+ ProtocolRequestOptions,
17
+ RawEvent,
18
+ } from './types';
19
+ import { createModuleLogger } from '../logger';
20
+ import { friendlyHttpError } from './error-utils';
21
+ import { convertToResponsesInput, convertToResponsesTools, mapResponsesStream } from './responses-sse';
22
+
23
+ const logger = createModuleLogger('OpenAIProtocol');
24
+
25
+ const DEFAULT_OPENAI_URL = 'https://api.openai.com/v1';
26
+
27
+ export class OpenAIProtocol implements Protocol {
28
+ readonly name = 'openai';
29
+
30
+ private apiKey: string;
31
+ private apiUrl: string;
32
+
33
+ constructor(config: ProtocolConfig) {
34
+ this.apiKey = config.apiKey;
35
+ this.apiUrl = config.apiUrl ?? DEFAULT_OPENAI_URL;
36
+ }
37
+
38
+ async *stream(
39
+ messages: ProtocolMessage[],
40
+ tools: ProtocolToolDefinition[],
41
+ options: ProtocolRequestOptions,
42
+ ): AsyncGenerator<RawEvent> {
43
+ const body: Record<string, unknown> = {
44
+ model: options.model,
45
+ input: convertToResponsesInput(messages),
46
+ stream: true,
47
+ max_output_tokens: options.maxOutputTokens,
48
+ store: false,
49
+ };
50
+
51
+ if (options.enableThinking) {
52
+ body.reasoning = { effort: 'high' };
53
+ }
54
+
55
+ if (tools.length > 0) {
56
+ body.tools = convertToResponsesTools(tools);
57
+ }
58
+
59
+ const url = `${this.apiUrl}/responses`;
60
+
61
+ logger.debug({
62
+ url: url.replace(this.apiKey, '***'),
63
+ model: options.model,
64
+ enableThinking: options.enableThinking,
65
+ toolsCount: tools.length,
66
+ }, 'OpenAI Responses 请求');
67
+
68
+ const response = await fetch(url, {
69
+ method: 'POST',
70
+ headers: {
71
+ 'Authorization': `Bearer ${this.apiKey}`,
72
+ 'Content-Type': 'application/json',
73
+ },
74
+ body: JSON.stringify(body),
75
+ signal: options.signal,
76
+ });
77
+
78
+ if (!response.ok) {
79
+ const errorText = await response.text();
80
+ logger.error({ status: response.status, body: errorText.slice(0, 500) }, 'OpenAI API 错误');
81
+ yield { type: 'error', error: friendlyHttpError(response.status, errorText, 'OpenAI') };
82
+ return;
83
+ }
84
+
85
+ const reader = response.body?.getReader();
86
+ if (!reader) {
87
+ yield { type: 'error', error: '无法获取响应流' };
88
+ return;
89
+ }
90
+
91
+ yield* mapResponsesStream(reader, { protocolName: 'OpenAI' });
92
+ }
93
+ }
94
+
95
+ export function createOpenAIProtocol(config: ProtocolConfig): OpenAIProtocol {
96
+ return new OpenAIProtocol(config);
97
+ }