whale-code 6.4.0

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 (319) hide show
  1. package/README.md +95 -0
  2. package/bin/swag-agent.js +9 -0
  3. package/bin/swagmanager-mcp.js +321 -0
  4. package/dist/cli/app.d.ts +26 -0
  5. package/dist/cli/app.js +64 -0
  6. package/dist/cli/chat/AgentSelector.d.ts +14 -0
  7. package/dist/cli/chat/AgentSelector.js +14 -0
  8. package/dist/cli/chat/ChatApp.d.ts +9 -0
  9. package/dist/cli/chat/ChatApp.js +267 -0
  10. package/dist/cli/chat/ChatInput.d.ts +39 -0
  11. package/dist/cli/chat/ChatInput.js +509 -0
  12. package/dist/cli/chat/MarkdownText.d.ts +10 -0
  13. package/dist/cli/chat/MarkdownText.js +20 -0
  14. package/dist/cli/chat/MessageList.d.ts +37 -0
  15. package/dist/cli/chat/MessageList.js +80 -0
  16. package/dist/cli/chat/ModelSelector.d.ts +20 -0
  17. package/dist/cli/chat/ModelSelector.js +73 -0
  18. package/dist/cli/chat/RewindViewer.d.ts +26 -0
  19. package/dist/cli/chat/RewindViewer.js +185 -0
  20. package/dist/cli/chat/StoreSelector.d.ts +14 -0
  21. package/dist/cli/chat/StoreSelector.js +24 -0
  22. package/dist/cli/chat/StreamingText.d.ts +12 -0
  23. package/dist/cli/chat/StreamingText.js +12 -0
  24. package/dist/cli/chat/SubagentPanel.d.ts +45 -0
  25. package/dist/cli/chat/SubagentPanel.js +110 -0
  26. package/dist/cli/chat/TeamPanel.d.ts +21 -0
  27. package/dist/cli/chat/TeamPanel.js +42 -0
  28. package/dist/cli/chat/ToolIndicator.d.ts +25 -0
  29. package/dist/cli/chat/ToolIndicator.js +436 -0
  30. package/dist/cli/chat/hooks/useAgentLoop.d.ts +39 -0
  31. package/dist/cli/chat/hooks/useAgentLoop.js +382 -0
  32. package/dist/cli/chat/hooks/useSlashCommands.d.ts +37 -0
  33. package/dist/cli/chat/hooks/useSlashCommands.js +387 -0
  34. package/dist/cli/commands/config-cmd.d.ts +10 -0
  35. package/dist/cli/commands/config-cmd.js +99 -0
  36. package/dist/cli/commands/doctor.d.ts +14 -0
  37. package/dist/cli/commands/doctor.js +172 -0
  38. package/dist/cli/commands/init.d.ts +16 -0
  39. package/dist/cli/commands/init.js +278 -0
  40. package/dist/cli/commands/mcp.d.ts +12 -0
  41. package/dist/cli/commands/mcp.js +162 -0
  42. package/dist/cli/login/LoginApp.d.ts +7 -0
  43. package/dist/cli/login/LoginApp.js +157 -0
  44. package/dist/cli/print-mode.d.ts +31 -0
  45. package/dist/cli/print-mode.js +202 -0
  46. package/dist/cli/serve-mode.d.ts +37 -0
  47. package/dist/cli/serve-mode.js +636 -0
  48. package/dist/cli/services/agent-definitions.d.ts +25 -0
  49. package/dist/cli/services/agent-definitions.js +91 -0
  50. package/dist/cli/services/agent-events.d.ts +178 -0
  51. package/dist/cli/services/agent-events.js +175 -0
  52. package/dist/cli/services/agent-loop.d.ts +90 -0
  53. package/dist/cli/services/agent-loop.js +762 -0
  54. package/dist/cli/services/agent-worker-base.d.ts +97 -0
  55. package/dist/cli/services/agent-worker-base.js +220 -0
  56. package/dist/cli/services/auth-service.d.ts +30 -0
  57. package/dist/cli/services/auth-service.js +160 -0
  58. package/dist/cli/services/background-processes.d.ts +126 -0
  59. package/dist/cli/services/background-processes.js +318 -0
  60. package/dist/cli/services/browser-auth.d.ts +24 -0
  61. package/dist/cli/services/browser-auth.js +180 -0
  62. package/dist/cli/services/claude-md-loader.d.ts +16 -0
  63. package/dist/cli/services/claude-md-loader.js +58 -0
  64. package/dist/cli/services/config-store.d.ts +47 -0
  65. package/dist/cli/services/config-store.js +79 -0
  66. package/dist/cli/services/debug-log.d.ts +10 -0
  67. package/dist/cli/services/debug-log.js +52 -0
  68. package/dist/cli/services/error-logger.d.ts +58 -0
  69. package/dist/cli/services/error-logger.js +269 -0
  70. package/dist/cli/services/file-history.d.ts +21 -0
  71. package/dist/cli/services/file-history.js +83 -0
  72. package/dist/cli/services/format-server-response.d.ts +16 -0
  73. package/dist/cli/services/format-server-response.js +440 -0
  74. package/dist/cli/services/git-context.d.ts +11 -0
  75. package/dist/cli/services/git-context.js +66 -0
  76. package/dist/cli/services/hooks.d.ts +85 -0
  77. package/dist/cli/services/hooks.js +258 -0
  78. package/dist/cli/services/interactive-tools.d.ts +125 -0
  79. package/dist/cli/services/interactive-tools.js +260 -0
  80. package/dist/cli/services/keybinding-manager.d.ts +52 -0
  81. package/dist/cli/services/keybinding-manager.js +115 -0
  82. package/dist/cli/services/local-tools.d.ts +22 -0
  83. package/dist/cli/services/local-tools.js +697 -0
  84. package/dist/cli/services/lsp-manager.d.ts +18 -0
  85. package/dist/cli/services/lsp-manager.js +717 -0
  86. package/dist/cli/services/mcp-client.d.ts +48 -0
  87. package/dist/cli/services/mcp-client.js +157 -0
  88. package/dist/cli/services/memory-manager.d.ts +16 -0
  89. package/dist/cli/services/memory-manager.js +57 -0
  90. package/dist/cli/services/model-manager.d.ts +18 -0
  91. package/dist/cli/services/model-manager.js +71 -0
  92. package/dist/cli/services/model-router.d.ts +26 -0
  93. package/dist/cli/services/model-router.js +149 -0
  94. package/dist/cli/services/permission-modes.d.ts +13 -0
  95. package/dist/cli/services/permission-modes.js +43 -0
  96. package/dist/cli/services/rewind.d.ts +84 -0
  97. package/dist/cli/services/rewind.js +194 -0
  98. package/dist/cli/services/ripgrep.d.ts +28 -0
  99. package/dist/cli/services/ripgrep.js +138 -0
  100. package/dist/cli/services/sandbox.d.ts +29 -0
  101. package/dist/cli/services/sandbox.js +97 -0
  102. package/dist/cli/services/server-tools.d.ts +61 -0
  103. package/dist/cli/services/server-tools.js +543 -0
  104. package/dist/cli/services/session-persistence.d.ts +23 -0
  105. package/dist/cli/services/session-persistence.js +99 -0
  106. package/dist/cli/services/subagent-worker.d.ts +19 -0
  107. package/dist/cli/services/subagent-worker.js +41 -0
  108. package/dist/cli/services/subagent.d.ts +47 -0
  109. package/dist/cli/services/subagent.js +647 -0
  110. package/dist/cli/services/system-prompt.d.ts +7 -0
  111. package/dist/cli/services/system-prompt.js +198 -0
  112. package/dist/cli/services/team-lead.d.ts +73 -0
  113. package/dist/cli/services/team-lead.js +512 -0
  114. package/dist/cli/services/team-state.d.ts +77 -0
  115. package/dist/cli/services/team-state.js +398 -0
  116. package/dist/cli/services/teammate.d.ts +31 -0
  117. package/dist/cli/services/teammate.js +689 -0
  118. package/dist/cli/services/telemetry.d.ts +61 -0
  119. package/dist/cli/services/telemetry.js +209 -0
  120. package/dist/cli/services/tools/agent-tools.d.ts +14 -0
  121. package/dist/cli/services/tools/agent-tools.js +347 -0
  122. package/dist/cli/services/tools/file-ops.d.ts +15 -0
  123. package/dist/cli/services/tools/file-ops.js +487 -0
  124. package/dist/cli/services/tools/search-tools.d.ts +8 -0
  125. package/dist/cli/services/tools/search-tools.js +186 -0
  126. package/dist/cli/services/tools/shell-exec.d.ts +10 -0
  127. package/dist/cli/services/tools/shell-exec.js +168 -0
  128. package/dist/cli/services/tools/task-manager.d.ts +28 -0
  129. package/dist/cli/services/tools/task-manager.js +209 -0
  130. package/dist/cli/services/tools/web-tools.d.ts +11 -0
  131. package/dist/cli/services/tools/web-tools.js +395 -0
  132. package/dist/cli/setup/SetupApp.d.ts +9 -0
  133. package/dist/cli/setup/SetupApp.js +191 -0
  134. package/dist/cli/shared/MatrixIntro.d.ts +4 -0
  135. package/dist/cli/shared/MatrixIntro.js +83 -0
  136. package/dist/cli/shared/Theme.d.ts +74 -0
  137. package/dist/cli/shared/Theme.js +127 -0
  138. package/dist/cli/shared/WhaleBanner.d.ts +10 -0
  139. package/dist/cli/shared/WhaleBanner.js +12 -0
  140. package/dist/cli/shared/markdown.d.ts +21 -0
  141. package/dist/cli/shared/markdown.js +756 -0
  142. package/dist/cli/status/StatusApp.d.ts +4 -0
  143. package/dist/cli/status/StatusApp.js +105 -0
  144. package/dist/cli/stores/StoreApp.d.ts +7 -0
  145. package/dist/cli/stores/StoreApp.js +81 -0
  146. package/dist/index.d.ts +15 -0
  147. package/dist/index.js +538 -0
  148. package/dist/local-agent/connection.d.ts +48 -0
  149. package/dist/local-agent/connection.js +332 -0
  150. package/dist/local-agent/discovery.d.ts +18 -0
  151. package/dist/local-agent/discovery.js +146 -0
  152. package/dist/local-agent/executor.d.ts +34 -0
  153. package/dist/local-agent/executor.js +241 -0
  154. package/dist/local-agent/index.d.ts +14 -0
  155. package/dist/local-agent/index.js +198 -0
  156. package/dist/node/adapters/base.d.ts +35 -0
  157. package/dist/node/adapters/base.js +10 -0
  158. package/dist/node/adapters/discord.d.ts +29 -0
  159. package/dist/node/adapters/discord.js +299 -0
  160. package/dist/node/adapters/email.d.ts +23 -0
  161. package/dist/node/adapters/email.js +218 -0
  162. package/dist/node/adapters/imessage.d.ts +17 -0
  163. package/dist/node/adapters/imessage.js +118 -0
  164. package/dist/node/adapters/slack.d.ts +26 -0
  165. package/dist/node/adapters/slack.js +259 -0
  166. package/dist/node/adapters/sms.d.ts +23 -0
  167. package/dist/node/adapters/sms.js +161 -0
  168. package/dist/node/adapters/telegram.d.ts +17 -0
  169. package/dist/node/adapters/telegram.js +101 -0
  170. package/dist/node/adapters/webchat.d.ts +27 -0
  171. package/dist/node/adapters/webchat.js +160 -0
  172. package/dist/node/adapters/whatsapp.d.ts +28 -0
  173. package/dist/node/adapters/whatsapp.js +230 -0
  174. package/dist/node/cli.d.ts +2 -0
  175. package/dist/node/cli.js +325 -0
  176. package/dist/node/config.d.ts +17 -0
  177. package/dist/node/config.js +31 -0
  178. package/dist/node/runtime.d.ts +50 -0
  179. package/dist/node/runtime.js +351 -0
  180. package/dist/server/handlers/__test-utils__/mock-supabase.d.ts +11 -0
  181. package/dist/server/handlers/__test-utils__/mock-supabase.js +393 -0
  182. package/dist/server/handlers/analytics.d.ts +17 -0
  183. package/dist/server/handlers/analytics.js +266 -0
  184. package/dist/server/handlers/api-keys.d.ts +6 -0
  185. package/dist/server/handlers/api-keys.js +221 -0
  186. package/dist/server/handlers/billing.d.ts +33 -0
  187. package/dist/server/handlers/billing.js +272 -0
  188. package/dist/server/handlers/browser.d.ts +10 -0
  189. package/dist/server/handlers/browser.js +517 -0
  190. package/dist/server/handlers/catalog.d.ts +99 -0
  191. package/dist/server/handlers/catalog.js +976 -0
  192. package/dist/server/handlers/comms.d.ts +254 -0
  193. package/dist/server/handlers/comms.js +588 -0
  194. package/dist/server/handlers/creations.d.ts +6 -0
  195. package/dist/server/handlers/creations.js +479 -0
  196. package/dist/server/handlers/crm.d.ts +89 -0
  197. package/dist/server/handlers/crm.js +538 -0
  198. package/dist/server/handlers/discovery.d.ts +6 -0
  199. package/dist/server/handlers/discovery.js +288 -0
  200. package/dist/server/handlers/embeddings.d.ts +92 -0
  201. package/dist/server/handlers/embeddings.js +197 -0
  202. package/dist/server/handlers/enrichment.d.ts +8 -0
  203. package/dist/server/handlers/enrichment.js +768 -0
  204. package/dist/server/handlers/image-gen.d.ts +6 -0
  205. package/dist/server/handlers/image-gen.js +409 -0
  206. package/dist/server/handlers/inventory.d.ts +319 -0
  207. package/dist/server/handlers/inventory.js +447 -0
  208. package/dist/server/handlers/kali.d.ts +10 -0
  209. package/dist/server/handlers/kali.js +210 -0
  210. package/dist/server/handlers/llm-providers.d.ts +6 -0
  211. package/dist/server/handlers/llm-providers.js +673 -0
  212. package/dist/server/handlers/local-agent.d.ts +6 -0
  213. package/dist/server/handlers/local-agent.js +118 -0
  214. package/dist/server/handlers/meta-ads.d.ts +111 -0
  215. package/dist/server/handlers/meta-ads.js +2279 -0
  216. package/dist/server/handlers/nodes.d.ts +33 -0
  217. package/dist/server/handlers/nodes.js +699 -0
  218. package/dist/server/handlers/operations.d.ts +138 -0
  219. package/dist/server/handlers/operations.js +131 -0
  220. package/dist/server/handlers/platform.d.ts +23 -0
  221. package/dist/server/handlers/platform.js +227 -0
  222. package/dist/server/handlers/supply-chain.d.ts +19 -0
  223. package/dist/server/handlers/supply-chain.js +327 -0
  224. package/dist/server/handlers/transcription.d.ts +17 -0
  225. package/dist/server/handlers/transcription.js +121 -0
  226. package/dist/server/handlers/video-gen.d.ts +6 -0
  227. package/dist/server/handlers/video-gen.js +466 -0
  228. package/dist/server/handlers/voice.d.ts +8 -0
  229. package/dist/server/handlers/voice.js +1146 -0
  230. package/dist/server/handlers/workflow-steps.d.ts +86 -0
  231. package/dist/server/handlers/workflow-steps.js +2349 -0
  232. package/dist/server/handlers/workflows.d.ts +7 -0
  233. package/dist/server/handlers/workflows.js +989 -0
  234. package/dist/server/index.d.ts +1 -0
  235. package/dist/server/index.js +2427 -0
  236. package/dist/server/lib/batch-client.d.ts +80 -0
  237. package/dist/server/lib/batch-client.js +467 -0
  238. package/dist/server/lib/code-worker-pool.d.ts +31 -0
  239. package/dist/server/lib/code-worker-pool.js +224 -0
  240. package/dist/server/lib/code-worker.d.ts +1 -0
  241. package/dist/server/lib/code-worker.js +188 -0
  242. package/dist/server/lib/compaction-service.d.ts +32 -0
  243. package/dist/server/lib/compaction-service.js +162 -0
  244. package/dist/server/lib/logger.d.ts +19 -0
  245. package/dist/server/lib/logger.js +46 -0
  246. package/dist/server/lib/otel.d.ts +38 -0
  247. package/dist/server/lib/otel.js +126 -0
  248. package/dist/server/lib/pg-rate-limiter.d.ts +21 -0
  249. package/dist/server/lib/pg-rate-limiter.js +86 -0
  250. package/dist/server/lib/prompt-sanitizer.d.ts +37 -0
  251. package/dist/server/lib/prompt-sanitizer.js +177 -0
  252. package/dist/server/lib/provider-capabilities.d.ts +85 -0
  253. package/dist/server/lib/provider-capabilities.js +190 -0
  254. package/dist/server/lib/provider-failover.d.ts +74 -0
  255. package/dist/server/lib/provider-failover.js +210 -0
  256. package/dist/server/lib/rate-limiter.d.ts +39 -0
  257. package/dist/server/lib/rate-limiter.js +147 -0
  258. package/dist/server/lib/server-agent-loop.d.ts +107 -0
  259. package/dist/server/lib/server-agent-loop.js +667 -0
  260. package/dist/server/lib/server-subagent.d.ts +78 -0
  261. package/dist/server/lib/server-subagent.js +203 -0
  262. package/dist/server/lib/session-checkpoint.d.ts +51 -0
  263. package/dist/server/lib/session-checkpoint.js +145 -0
  264. package/dist/server/lib/ssrf-guard.d.ts +13 -0
  265. package/dist/server/lib/ssrf-guard.js +240 -0
  266. package/dist/server/lib/supabase-client.d.ts +7 -0
  267. package/dist/server/lib/supabase-client.js +78 -0
  268. package/dist/server/lib/template-resolver.d.ts +31 -0
  269. package/dist/server/lib/template-resolver.js +215 -0
  270. package/dist/server/lib/utils.d.ts +16 -0
  271. package/dist/server/lib/utils.js +147 -0
  272. package/dist/server/local-agent-gateway.d.ts +82 -0
  273. package/dist/server/local-agent-gateway.js +426 -0
  274. package/dist/server/providers/anthropic.d.ts +20 -0
  275. package/dist/server/providers/anthropic.js +199 -0
  276. package/dist/server/providers/bedrock.d.ts +20 -0
  277. package/dist/server/providers/bedrock.js +194 -0
  278. package/dist/server/providers/gemini.d.ts +24 -0
  279. package/dist/server/providers/gemini.js +486 -0
  280. package/dist/server/providers/openai.d.ts +24 -0
  281. package/dist/server/providers/openai.js +522 -0
  282. package/dist/server/providers/registry.d.ts +32 -0
  283. package/dist/server/providers/registry.js +58 -0
  284. package/dist/server/providers/shared.d.ts +32 -0
  285. package/dist/server/providers/shared.js +124 -0
  286. package/dist/server/providers/types.d.ts +92 -0
  287. package/dist/server/providers/types.js +12 -0
  288. package/dist/server/proxy-handlers.d.ts +6 -0
  289. package/dist/server/proxy-handlers.js +89 -0
  290. package/dist/server/tool-router.d.ts +149 -0
  291. package/dist/server/tool-router.js +803 -0
  292. package/dist/server/validation.d.ts +24 -0
  293. package/dist/server/validation.js +301 -0
  294. package/dist/server/worker.d.ts +19 -0
  295. package/dist/server/worker.js +201 -0
  296. package/dist/setup.d.ts +8 -0
  297. package/dist/setup.js +181 -0
  298. package/dist/shared/agent-core.d.ts +157 -0
  299. package/dist/shared/agent-core.js +534 -0
  300. package/dist/shared/anthropic-types.d.ts +105 -0
  301. package/dist/shared/anthropic-types.js +7 -0
  302. package/dist/shared/api-client.d.ts +90 -0
  303. package/dist/shared/api-client.js +379 -0
  304. package/dist/shared/constants.d.ts +33 -0
  305. package/dist/shared/constants.js +80 -0
  306. package/dist/shared/sse-parser.d.ts +26 -0
  307. package/dist/shared/sse-parser.js +259 -0
  308. package/dist/shared/tool-dispatch.d.ts +52 -0
  309. package/dist/shared/tool-dispatch.js +191 -0
  310. package/dist/shared/types.d.ts +72 -0
  311. package/dist/shared/types.js +7 -0
  312. package/dist/updater.d.ts +25 -0
  313. package/dist/updater.js +140 -0
  314. package/dist/webchat/widget.d.ts +0 -0
  315. package/dist/webchat/widget.js +397 -0
  316. package/package.json +95 -0
  317. package/src/cli/services/builtin-skills/commit.md +19 -0
  318. package/src/cli/services/builtin-skills/review-pr.md +21 -0
  319. package/src/cli/services/builtin-skills/review.md +18 -0
@@ -0,0 +1,522 @@
1
+ /**
2
+ * OpenAI Provider Adapter — Responses API (2026) with format conversion to Anthropic SSE.
3
+ *
4
+ * Contains:
5
+ * - anthropicToResponsesInput() — messages → Responses API format
6
+ * - sanitizeSchemaForOpenAI() — schema cleanup
7
+ * - anthropicToResponsesTools() — tool format conversion
8
+ * - Streaming with 12 event type handlers
9
+ * - pendingFunctionCalls tracking (per-request, not shared)
10
+ *
11
+ * Phase 7.1: Also encapsulates thinking config, context management,
12
+ * output token limits, and compaction config for OpenAI models.
13
+ */
14
+ import { randomUUID } from "node:crypto";
15
+ import OpenAI from "openai";
16
+ import { sanitizeError } from "../../shared/agent-core.js";
17
+ import { isOpenAIReasoningModel } from "../../shared/constants.js";
18
+ import { getCapabilities } from "../lib/provider-capabilities.js";
19
+ import { registerProvider } from "./registry.js";
20
+ import { jsonResponse, writeSSEHeaders, emitError, resolveProviderCredentials } from "./shared.js";
21
+ // ============================================================================
22
+ // CONSTANTS — OpenAI-specific model config
23
+ // ============================================================================
24
+ const DEFAULT_OUTPUT_TOKENS = 16384;
25
+ const MODEL_MAX_OUTPUT_TOKENS = {
26
+ "gpt-5": 128000,
27
+ "gpt-5-mini": 128000,
28
+ "gpt-5-nano": 128000,
29
+ "o3": 100000,
30
+ "o4-mini": 100000,
31
+ "gpt-4o": 16384,
32
+ };
33
+ // ============================================================================
34
+ // MESSAGE CONVERSION
35
+ // ============================================================================
36
+ /**
37
+ * Convert Anthropic messages to OpenAI Responses API input format.
38
+ *
39
+ * Key differences from Chat Completions:
40
+ * - System prompt goes to `instructions` param (not in input array)
41
+ * - tool_use blocks become top-level `function_call` items
42
+ * - tool_result blocks become top-level `function_call_output` items
43
+ * - Images use `input_image` type with data URL in `image_url`
44
+ * - call_id is used directly (no ID mapping needed)
45
+ */
46
+ function anthropicToResponsesInput(messages) {
47
+ // Pass 1: collect all tool_use call_ids so we can match function_call_output items
48
+ const knownCallIds = new Set();
49
+ for (const msg of messages) {
50
+ if (msg.role === "assistant" && Array.isArray(msg.content)) {
51
+ for (const block of msg.content) {
52
+ if (block.type === "tool_use" && block.id && block.name) {
53
+ knownCallIds.add(block.id);
54
+ }
55
+ }
56
+ }
57
+ }
58
+ // Pass 2: build input array
59
+ const input = [];
60
+ for (const msg of messages) {
61
+ if (msg.role === "user") {
62
+ if (typeof msg.content === "string") {
63
+ input.push({ role: "user", content: msg.content });
64
+ }
65
+ else if (Array.isArray(msg.content)) {
66
+ const contentParts = [];
67
+ for (const block of msg.content) {
68
+ if (block.type === "tool_result" && block.tool_use_id) {
69
+ // Only emit if there's a matching function_call (prevents orphaned outputs after compaction)
70
+ if (knownCallIds.has(block.tool_use_id)) {
71
+ let output = "";
72
+ if (typeof block.content === "string") {
73
+ output = block.content;
74
+ }
75
+ else if (Array.isArray(block.content)) {
76
+ output = block.content
77
+ .filter((c) => c.type === "text")
78
+ .map((c) => c.text)
79
+ .join("\n");
80
+ }
81
+ input.push({
82
+ type: "function_call_output",
83
+ call_id: block.tool_use_id,
84
+ output: output || "(no output)",
85
+ });
86
+ }
87
+ }
88
+ else if (block.type === "text" && block.text) {
89
+ contentParts.push({ type: "input_text", text: block.text });
90
+ }
91
+ else if (block.type === "image" && block.source?.data) {
92
+ contentParts.push({
93
+ type: "input_image",
94
+ image_url: `data:${block.source.media_type};base64,${block.source.data}`,
95
+ });
96
+ }
97
+ }
98
+ // Emit content parts as user message
99
+ if (contentParts.length === 1 && contentParts[0].type === "input_text") {
100
+ input.push({ role: "user", content: contentParts[0].text });
101
+ }
102
+ else if (contentParts.length > 0) {
103
+ input.push({ role: "user", content: contentParts });
104
+ }
105
+ }
106
+ }
107
+ else if (msg.role === "assistant") {
108
+ if (typeof msg.content === "string") {
109
+ input.push({ role: "assistant", content: msg.content });
110
+ }
111
+ else if (Array.isArray(msg.content)) {
112
+ let textContent = "";
113
+ for (const block of msg.content) {
114
+ if (block.type === "text" && block.text) {
115
+ textContent += block.text;
116
+ }
117
+ else if (block.type === "tool_use" && block.name) {
118
+ // Emit accumulated text as assistant message first
119
+ if (textContent) {
120
+ input.push({ role: "assistant", content: textContent });
121
+ textContent = "";
122
+ }
123
+ // Emit function_call as top-level input item
124
+ input.push({
125
+ type: "function_call",
126
+ call_id: block.id || `call_${randomUUID().slice(0, 12)}`,
127
+ name: block.name,
128
+ arguments: JSON.stringify(block.input || {}),
129
+ });
130
+ }
131
+ // Skip thinking/compaction blocks — not relevant for OpenAI
132
+ }
133
+ if (textContent) {
134
+ input.push({ role: "assistant", content: textContent });
135
+ }
136
+ }
137
+ }
138
+ }
139
+ return input;
140
+ }
141
+ // ============================================================================
142
+ // SCHEMA SANITIZATION
143
+ // ============================================================================
144
+ /** Sanitize JSON Schema for OpenAI — ensure arrays have items, objects have properties */
145
+ function sanitizeSchemaForOpenAI(schema) {
146
+ if (!schema || typeof schema !== "object")
147
+ return schema;
148
+ if (Array.isArray(schema))
149
+ return schema.map(sanitizeSchemaForOpenAI);
150
+ const result = {};
151
+ for (const [key, value] of Object.entries(schema)) {
152
+ if (key === "properties" && typeof value === "object" && value !== null && !Array.isArray(value)) {
153
+ const props = {};
154
+ for (const [propName, propSchema] of Object.entries(value)) {
155
+ props[propName] = sanitizeSchemaForOpenAI(propSchema);
156
+ }
157
+ result[key] = props;
158
+ }
159
+ else {
160
+ result[key] = sanitizeSchemaForOpenAI(value);
161
+ }
162
+ }
163
+ // OpenAI requires arrays to have items
164
+ if (result.type === "array" && !result.items) {
165
+ result.items = { type: "string" };
166
+ }
167
+ // Filter required to only include defined properties
168
+ if (result.required && Array.isArray(result.required) && result.properties) {
169
+ const validProps = new Set(Object.keys(result.properties));
170
+ result.required = result.required.filter((r) => validProps.has(r));
171
+ if (result.required.length === 0)
172
+ delete result.required;
173
+ }
174
+ return result;
175
+ }
176
+ /** Convert Anthropic tools to OpenAI Responses API FunctionTool format */
177
+ function anthropicToResponsesTools(tools) {
178
+ return tools.map((t) => ({
179
+ type: "function",
180
+ name: t.name,
181
+ description: typeof t.description === "string" ? t.description.slice(0, 4096) : "",
182
+ parameters: sanitizeSchemaForOpenAI(t.input_schema || {}),
183
+ strict: false,
184
+ }));
185
+ }
186
+ // ============================================================================
187
+ // ADAPTER
188
+ // ============================================================================
189
+ export class OpenAIAdapter {
190
+ name = "openai";
191
+ async handleStream(res, config, corsHeaders) {
192
+ const { model, messages, system, tools, max_tokens, temperature, storeId, tool_choice } = config;
193
+ const creds = await resolveProviderCredentials("openai", storeId);
194
+ if (!creds.openai) {
195
+ jsonResponse(res, 422, { error: "OpenAI credentials not configured. Add OPENAI_API_KEY to user_tool_secrets." }, corsHeaders);
196
+ return;
197
+ }
198
+ const client = new OpenAI({ apiKey: creds.openai.apiKey });
199
+ const isReasoning = isOpenAIReasoningModel(model);
200
+ // Convert Anthropic format → Responses API format
201
+ const openaiInput = anthropicToResponsesInput(messages);
202
+ const openaiTools = tools?.length ? anthropicToResponsesTools(tools) : undefined;
203
+ // Extract system prompt → instructions parameter
204
+ let instructions = "";
205
+ if (typeof system === "string") {
206
+ instructions = system;
207
+ }
208
+ else if (Array.isArray(system)) {
209
+ instructions = system.map((s) => s.text || "").join("\n");
210
+ }
211
+ // Build Responses API request
212
+ const params = {
213
+ model,
214
+ input: openaiInput,
215
+ stream: true,
216
+ store: false,
217
+ max_output_tokens: max_tokens,
218
+ parallel_tool_calls: true,
219
+ truncation: "auto",
220
+ };
221
+ if (instructions)
222
+ params.instructions = instructions;
223
+ if (openaiTools?.length)
224
+ params.tools = openaiTools;
225
+ if (isReasoning) {
226
+ params.reasoning = { effort: "high", summary: "detailed" };
227
+ }
228
+ else {
229
+ if (temperature !== undefined)
230
+ params.temperature = temperature;
231
+ }
232
+ // Map tool_choice to OpenAI Responses API format
233
+ if (tool_choice) {
234
+ if (tool_choice === "auto") {
235
+ // "auto" is default for OpenAI — no-op
236
+ }
237
+ else if (tool_choice === "any") {
238
+ params.tool_choice = "required";
239
+ }
240
+ else if (tool_choice === "none") {
241
+ params.tool_choice = "none";
242
+ delete params.tools;
243
+ }
244
+ else if (typeof tool_choice === "object" && tool_choice.type === "tool") {
245
+ params.tool_choice = { type: "function", function: { name: tool_choice.name } };
246
+ }
247
+ }
248
+ // Defer SSE headers until stream is successfully created
249
+ // so we can return proper HTTP errors if the initial API call fails.
250
+ let totalInputTokens = 0;
251
+ let totalOutputTokens = 0;
252
+ let totalReasoningTokens = 0;
253
+ let totalCacheReadTokens = 0;
254
+ let heartbeat;
255
+ try {
256
+ const stream = await client.responses.create(params);
257
+ // Stream created successfully — now safe to commit to SSE
258
+ writeSSEHeaders(res, corsHeaders);
259
+ // Heartbeat keeps the connection alive and helps detect dead clients
260
+ heartbeat = setInterval(() => {
261
+ if (!res.writableEnded)
262
+ res.write(":ping\n\n");
263
+ }, 15_000);
264
+ // Emit Anthropic-format message_start
265
+ res.write(`data: ${JSON.stringify({
266
+ type: "message_start",
267
+ message: {
268
+ id: `msg_openai_${randomUUID().slice(0, 8)}`,
269
+ type: "message",
270
+ role: "assistant",
271
+ content: [],
272
+ model,
273
+ stop_reason: null,
274
+ usage: { input_tokens: 0, output_tokens: 0 },
275
+ },
276
+ })}\n\n`);
277
+ let blockIndex = 0;
278
+ let hasOpenTextBlock = false;
279
+ let hasOpenThinkingBlock = false;
280
+ let hasToolUse = false;
281
+ let hasEmittedContent = false;
282
+ // Per-request function call tracking (not shared between concurrent requests)
283
+ const pendingFunctionCalls = new Map();
284
+ // Helper to close any open content blocks
285
+ const closeOpenBlocks = () => {
286
+ if (hasOpenThinkingBlock) {
287
+ res.write(`data: ${JSON.stringify({ type: "content_block_stop", index: blockIndex })}\n\n`);
288
+ blockIndex++;
289
+ hasOpenThinkingBlock = false;
290
+ }
291
+ if (hasOpenTextBlock) {
292
+ res.write(`data: ${JSON.stringify({ type: "content_block_stop", index: blockIndex })}\n\n`);
293
+ blockIndex++;
294
+ hasOpenTextBlock = false;
295
+ }
296
+ };
297
+ for await (const event of stream) {
298
+ switch (event.type) {
299
+ // ---- Reasoning summary → Anthropic thinking block ----
300
+ case "response.reasoning_summary_text.delta": {
301
+ const text = event.delta;
302
+ if (!text)
303
+ break;
304
+ if (hasOpenTextBlock) {
305
+ res.write(`data: ${JSON.stringify({ type: "content_block_stop", index: blockIndex })}\n\n`);
306
+ blockIndex++;
307
+ hasOpenTextBlock = false;
308
+ }
309
+ if (!hasOpenThinkingBlock) {
310
+ res.write(`data: ${JSON.stringify({
311
+ type: "content_block_start",
312
+ index: blockIndex,
313
+ content_block: { type: "thinking", thinking: "" },
314
+ })}\n\n`);
315
+ hasOpenThinkingBlock = true;
316
+ hasEmittedContent = true;
317
+ }
318
+ res.write(`data: ${JSON.stringify({
319
+ type: "content_block_delta",
320
+ index: blockIndex,
321
+ delta: { type: "thinking_delta", thinking: text },
322
+ })}\n\n`);
323
+ break;
324
+ }
325
+ case "response.reasoning_summary_text.done": {
326
+ break;
327
+ }
328
+ // ---- Text content → Anthropic text block ----
329
+ case "response.output_text.delta": {
330
+ const text = event.delta;
331
+ if (!text)
332
+ break;
333
+ if (hasOpenThinkingBlock) {
334
+ res.write(`data: ${JSON.stringify({ type: "content_block_stop", index: blockIndex })}\n\n`);
335
+ blockIndex++;
336
+ hasOpenThinkingBlock = false;
337
+ }
338
+ if (!hasOpenTextBlock) {
339
+ res.write(`data: ${JSON.stringify({
340
+ type: "content_block_start",
341
+ index: blockIndex,
342
+ content_block: { type: "text", text: "" },
343
+ })}\n\n`);
344
+ hasOpenTextBlock = true;
345
+ hasEmittedContent = true;
346
+ }
347
+ res.write(`data: ${JSON.stringify({
348
+ type: "content_block_delta",
349
+ index: blockIndex,
350
+ delta: { type: "text_delta", text },
351
+ })}\n\n`);
352
+ break;
353
+ }
354
+ case "response.output_text.done": {
355
+ break;
356
+ }
357
+ // ---- Function calls → Anthropic tool_use blocks ----
358
+ case "response.output_item.added": {
359
+ const item = event.item;
360
+ if (item?.type === "function_call" && item.id && item.name) {
361
+ pendingFunctionCalls.set(item.id, { name: item.name, callId: item.call_id || item.id });
362
+ }
363
+ break;
364
+ }
365
+ case "response.function_call_arguments.done": {
366
+ closeOpenBlocks();
367
+ hasToolUse = true;
368
+ hasEmittedContent = true;
369
+ const pending = pendingFunctionCalls.get(event.item_id);
370
+ const name = pending?.name || event.name || "unknown_tool";
371
+ const callId = pending?.callId || event.call_id || event.item_id;
372
+ if (pending)
373
+ pendingFunctionCalls.delete(event.item_id);
374
+ const args = event.arguments;
375
+ let parsedInput = {};
376
+ try {
377
+ parsedInput = JSON.parse(args || "{}");
378
+ }
379
+ catch { /* use empty */ }
380
+ const toolUseId = callId || `toolu_openai_${randomUUID().slice(0, 12)}`;
381
+ res.write(`data: ${JSON.stringify({
382
+ type: "content_block_start",
383
+ index: blockIndex,
384
+ content_block: {
385
+ type: "tool_use",
386
+ id: toolUseId,
387
+ name,
388
+ input: {},
389
+ },
390
+ })}\n\n`);
391
+ res.write(`data: ${JSON.stringify({
392
+ type: "content_block_delta",
393
+ index: blockIndex,
394
+ delta: {
395
+ type: "input_json_delta",
396
+ partial_json: JSON.stringify(parsedInput),
397
+ },
398
+ })}\n\n`);
399
+ res.write(`data: ${JSON.stringify({ type: "content_block_stop", index: blockIndex })}\n\n`);
400
+ blockIndex++;
401
+ break;
402
+ }
403
+ // ---- Completion → usage + stop ----
404
+ case "response.completed": {
405
+ const response = event.response;
406
+ if (response?.usage) {
407
+ totalInputTokens = response.usage.input_tokens || 0;
408
+ totalOutputTokens = response.usage.output_tokens || 0;
409
+ if (response.usage.output_tokens_details?.reasoning_tokens) {
410
+ totalReasoningTokens = response.usage.output_tokens_details.reasoning_tokens;
411
+ }
412
+ if (response.usage.input_tokens_details?.cached_tokens) {
413
+ totalCacheReadTokens = response.usage.input_tokens_details.cached_tokens;
414
+ }
415
+ }
416
+ break;
417
+ }
418
+ // ---- Error during streaming ----
419
+ case "response.failed": {
420
+ const error = event.response?.error;
421
+ const errMsg = error?.message || "Unknown OpenAI error";
422
+ console.error("[openai-proxy] Response failed:", errMsg);
423
+ closeOpenBlocks();
424
+ if (!hasEmittedContent) {
425
+ res.write(`data: ${JSON.stringify({
426
+ type: "content_block_start",
427
+ index: blockIndex,
428
+ content_block: { type: "text", text: "" },
429
+ })}\n\n`);
430
+ res.write(`data: ${JSON.stringify({
431
+ type: "content_block_delta",
432
+ index: blockIndex,
433
+ delta: { type: "text_delta", text: `[OpenAI error: ${errMsg}]` },
434
+ })}\n\n`);
435
+ res.write(`data: ${JSON.stringify({ type: "content_block_stop", index: blockIndex })}\n\n`);
436
+ hasEmittedContent = true;
437
+ }
438
+ break;
439
+ }
440
+ case "response.incomplete": {
441
+ console.warn("[openai-proxy] Response incomplete:", event.response?.incomplete_details);
442
+ break;
443
+ }
444
+ default:
445
+ break;
446
+ }
447
+ }
448
+ // Close any remaining open blocks
449
+ closeOpenBlocks();
450
+ // Handle empty response
451
+ if (!hasEmittedContent) {
452
+ res.write(`data: ${JSON.stringify({
453
+ type: "content_block_start",
454
+ index: 0,
455
+ content_block: { type: "text", text: "" },
456
+ })}\n\n`);
457
+ res.write(`data: ${JSON.stringify({
458
+ type: "content_block_delta",
459
+ index: 0,
460
+ delta: { type: "text_delta", text: "[OpenAI returned an empty response. Please try again.]" },
461
+ })}\n\n`);
462
+ res.write(`data: ${JSON.stringify({ type: "content_block_stop", index: 0 })}\n\n`);
463
+ }
464
+ // Determine stop reason
465
+ const stopReason = hasToolUse ? "tool_use" : "end_turn";
466
+ // Emit message_delta with usage
467
+ res.write(`data: ${JSON.stringify({
468
+ type: "message_delta",
469
+ delta: { stop_reason: stopReason, stop_sequence: null },
470
+ usage: {
471
+ input_tokens: totalInputTokens,
472
+ output_tokens: totalOutputTokens,
473
+ ...(totalReasoningTokens > 0 ? { thinking_tokens: totalReasoningTokens } : {}),
474
+ ...(totalCacheReadTokens > 0 ? { cache_read_input_tokens: totalCacheReadTokens } : {}),
475
+ },
476
+ })}\n\n`);
477
+ res.write(`data: ${JSON.stringify({ type: "message_stop" })}\n\n`);
478
+ res.write("data: [DONE]\n\n");
479
+ }
480
+ catch (err) {
481
+ emitError(res, err, corsHeaders, sanitizeError, {
482
+ provider: "openai",
483
+ inputTokens: totalInputTokens,
484
+ outputTokens: totalOutputTokens,
485
+ thinkingTokens: totalReasoningTokens,
486
+ cacheReadTokens: totalCacheReadTokens,
487
+ });
488
+ }
489
+ finally {
490
+ if (heartbeat)
491
+ clearInterval(heartbeat);
492
+ }
493
+ res.end();
494
+ }
495
+ // ---- Provider-specific config methods ----
496
+ getThinkingConfig(model, enabled) {
497
+ if (!enabled) {
498
+ return { thinking: { type: "disabled" }, beta: "" };
499
+ }
500
+ // OpenAI reasoning models (o-series) have built-in reasoning, GPT models don't support thinking
501
+ const isReasoning = /^o\d/.test(model);
502
+ return { thinking: { type: isReasoning ? "enabled" : "disabled" }, beta: "" };
503
+ }
504
+ getMaxOutputTokens(model, agentMax) {
505
+ const modelMax = MODEL_MAX_OUTPUT_TOKENS[model] ?? DEFAULT_OUTPUT_TOKENS;
506
+ if (agentMax)
507
+ return Math.min(agentMax, modelMax);
508
+ return Math.min(DEFAULT_OUTPUT_TOKENS, modelMax);
509
+ }
510
+ getContextManagement(_model) {
511
+ // OpenAI doesn't use Anthropic betas or context management
512
+ return { betas: [], config: { edits: [] } };
513
+ }
514
+ getCompactionConfig(_model) {
515
+ return { triggerTokens: 120_000, totalBudget: 2_000_000, isNative: false };
516
+ }
517
+ getCapabilities() {
518
+ return getCapabilities("openai");
519
+ }
520
+ }
521
+ // Auto-register on import
522
+ registerProvider("openai", new OpenAIAdapter());
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Provider Adapter Registry — central lookup for provider adapters.
3
+ *
4
+ * Phase 7.1: Replaces ad-hoc provider switching with a single registry.
5
+ * Adapters self-register on import. External code calls getProviderAdapter()
6
+ * instead of maintaining its own switch/if-else chains.
7
+ */
8
+ import type { ProviderAdapter } from "./types.js";
9
+ /**
10
+ * Register a provider adapter. Called by each adapter module on import.
11
+ * Overwrites if a provider with the same name is already registered.
12
+ */
13
+ export declare function registerProvider(name: string, adapter: ProviderAdapter): void;
14
+ /**
15
+ * Get the adapter for a provider name or model ID.
16
+ *
17
+ * Accepts either:
18
+ * - A provider name directly ("anthropic", "gemini", "openai", "bedrock")
19
+ * - A full model ID (uses getProvider() to resolve the provider name)
20
+ *
21
+ * Falls back to "anthropic" if no adapter is found for the resolved provider.
22
+ * Throws if no adapters are registered at all (indicates missing initialization).
23
+ */
24
+ export declare function getProviderAdapter(modelOrProvider: string): ProviderAdapter;
25
+ /**
26
+ * Check if a provider is registered.
27
+ */
28
+ export declare function hasProvider(name: string): boolean;
29
+ /**
30
+ * Get all registered provider names.
31
+ */
32
+ export declare function getRegisteredProviders(): string[];
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Provider Adapter Registry — central lookup for provider adapters.
3
+ *
4
+ * Phase 7.1: Replaces ad-hoc provider switching with a single registry.
5
+ * Adapters self-register on import. External code calls getProviderAdapter()
6
+ * instead of maintaining its own switch/if-else chains.
7
+ */
8
+ import { getProvider } from "../../shared/constants.js";
9
+ // ============================================================================
10
+ // REGISTRY
11
+ // ============================================================================
12
+ const providers = new Map();
13
+ /**
14
+ * Register a provider adapter. Called by each adapter module on import.
15
+ * Overwrites if a provider with the same name is already registered.
16
+ */
17
+ export function registerProvider(name, adapter) {
18
+ providers.set(name, adapter);
19
+ }
20
+ /**
21
+ * Get the adapter for a provider name or model ID.
22
+ *
23
+ * Accepts either:
24
+ * - A provider name directly ("anthropic", "gemini", "openai", "bedrock")
25
+ * - A full model ID (uses getProvider() to resolve the provider name)
26
+ *
27
+ * Falls back to "anthropic" if no adapter is found for the resolved provider.
28
+ * Throws if no adapters are registered at all (indicates missing initialization).
29
+ */
30
+ export function getProviderAdapter(modelOrProvider) {
31
+ // Direct provider name match
32
+ const direct = providers.get(modelOrProvider);
33
+ if (direct)
34
+ return direct;
35
+ // Resolve model ID → provider name
36
+ const providerName = getProvider(modelOrProvider);
37
+ const resolved = providers.get(providerName);
38
+ if (resolved)
39
+ return resolved;
40
+ // Fallback to anthropic
41
+ const fallback = providers.get("anthropic");
42
+ if (fallback)
43
+ return fallback;
44
+ throw new Error(`No provider adapter registered for "${modelOrProvider}" (resolved: "${providerName}"). ` +
45
+ `Registered providers: [${Array.from(providers.keys()).join(", ")}]`);
46
+ }
47
+ /**
48
+ * Check if a provider is registered.
49
+ */
50
+ export function hasProvider(name) {
51
+ return providers.has(name);
52
+ }
53
+ /**
54
+ * Get all registered provider names.
55
+ */
56
+ export function getRegisteredProviders() {
57
+ return Array.from(providers.keys());
58
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Provider Shared Utilities — credential resolution, SSE helpers, error emission.
3
+ *
4
+ * Extracted from proxy-handlers.ts. Used by all provider adapters.
5
+ */
6
+ import http from "node:http";
7
+ export declare function jsonResponse(res: http.ServerResponse, status: number, data: unknown, corsHeaders: Record<string, string>): void;
8
+ export declare function writeSSEHeaders(res: http.ServerResponse, corsHeaders: Record<string, string>): void;
9
+ /**
10
+ * Emit an error — as SSE if headers already sent, otherwise as JSON response.
11
+ * Always emits message_delta + message_stop so clients don't hang.
12
+ */
13
+ export declare function emitError(res: http.ServerResponse, err: unknown, corsHeaders: Record<string, string>, sanitize: (e: unknown) => string, opts?: {
14
+ provider?: string;
15
+ inputTokens?: number;
16
+ outputTokens?: number;
17
+ thinkingTokens?: number;
18
+ cacheReadTokens?: number;
19
+ }): void;
20
+ export declare function resolveProviderCredentials(provider: "google" | "bedrock" | "openai", storeId: string | undefined): Promise<{
21
+ google?: {
22
+ apiKey: string;
23
+ };
24
+ bedrock?: {
25
+ accessKeyId: string;
26
+ secretAccessKey: string;
27
+ region: string;
28
+ };
29
+ openai?: {
30
+ apiKey: string;
31
+ };
32
+ }>;