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.
- package/README.md +95 -0
- package/bin/swag-agent.js +9 -0
- package/bin/swagmanager-mcp.js +321 -0
- package/dist/cli/app.d.ts +26 -0
- package/dist/cli/app.js +64 -0
- package/dist/cli/chat/AgentSelector.d.ts +14 -0
- package/dist/cli/chat/AgentSelector.js +14 -0
- package/dist/cli/chat/ChatApp.d.ts +9 -0
- package/dist/cli/chat/ChatApp.js +267 -0
- package/dist/cli/chat/ChatInput.d.ts +39 -0
- package/dist/cli/chat/ChatInput.js +509 -0
- package/dist/cli/chat/MarkdownText.d.ts +10 -0
- package/dist/cli/chat/MarkdownText.js +20 -0
- package/dist/cli/chat/MessageList.d.ts +37 -0
- package/dist/cli/chat/MessageList.js +80 -0
- package/dist/cli/chat/ModelSelector.d.ts +20 -0
- package/dist/cli/chat/ModelSelector.js +73 -0
- package/dist/cli/chat/RewindViewer.d.ts +26 -0
- package/dist/cli/chat/RewindViewer.js +185 -0
- package/dist/cli/chat/StoreSelector.d.ts +14 -0
- package/dist/cli/chat/StoreSelector.js +24 -0
- package/dist/cli/chat/StreamingText.d.ts +12 -0
- package/dist/cli/chat/StreamingText.js +12 -0
- package/dist/cli/chat/SubagentPanel.d.ts +45 -0
- package/dist/cli/chat/SubagentPanel.js +110 -0
- package/dist/cli/chat/TeamPanel.d.ts +21 -0
- package/dist/cli/chat/TeamPanel.js +42 -0
- package/dist/cli/chat/ToolIndicator.d.ts +25 -0
- package/dist/cli/chat/ToolIndicator.js +436 -0
- package/dist/cli/chat/hooks/useAgentLoop.d.ts +39 -0
- package/dist/cli/chat/hooks/useAgentLoop.js +382 -0
- package/dist/cli/chat/hooks/useSlashCommands.d.ts +37 -0
- package/dist/cli/chat/hooks/useSlashCommands.js +387 -0
- package/dist/cli/commands/config-cmd.d.ts +10 -0
- package/dist/cli/commands/config-cmd.js +99 -0
- package/dist/cli/commands/doctor.d.ts +14 -0
- package/dist/cli/commands/doctor.js +172 -0
- package/dist/cli/commands/init.d.ts +16 -0
- package/dist/cli/commands/init.js +278 -0
- package/dist/cli/commands/mcp.d.ts +12 -0
- package/dist/cli/commands/mcp.js +162 -0
- package/dist/cli/login/LoginApp.d.ts +7 -0
- package/dist/cli/login/LoginApp.js +157 -0
- package/dist/cli/print-mode.d.ts +31 -0
- package/dist/cli/print-mode.js +202 -0
- package/dist/cli/serve-mode.d.ts +37 -0
- package/dist/cli/serve-mode.js +636 -0
- package/dist/cli/services/agent-definitions.d.ts +25 -0
- package/dist/cli/services/agent-definitions.js +91 -0
- package/dist/cli/services/agent-events.d.ts +178 -0
- package/dist/cli/services/agent-events.js +175 -0
- package/dist/cli/services/agent-loop.d.ts +90 -0
- package/dist/cli/services/agent-loop.js +762 -0
- package/dist/cli/services/agent-worker-base.d.ts +97 -0
- package/dist/cli/services/agent-worker-base.js +220 -0
- package/dist/cli/services/auth-service.d.ts +30 -0
- package/dist/cli/services/auth-service.js +160 -0
- package/dist/cli/services/background-processes.d.ts +126 -0
- package/dist/cli/services/background-processes.js +318 -0
- package/dist/cli/services/browser-auth.d.ts +24 -0
- package/dist/cli/services/browser-auth.js +180 -0
- package/dist/cli/services/claude-md-loader.d.ts +16 -0
- package/dist/cli/services/claude-md-loader.js +58 -0
- package/dist/cli/services/config-store.d.ts +47 -0
- package/dist/cli/services/config-store.js +79 -0
- package/dist/cli/services/debug-log.d.ts +10 -0
- package/dist/cli/services/debug-log.js +52 -0
- package/dist/cli/services/error-logger.d.ts +58 -0
- package/dist/cli/services/error-logger.js +269 -0
- package/dist/cli/services/file-history.d.ts +21 -0
- package/dist/cli/services/file-history.js +83 -0
- package/dist/cli/services/format-server-response.d.ts +16 -0
- package/dist/cli/services/format-server-response.js +440 -0
- package/dist/cli/services/git-context.d.ts +11 -0
- package/dist/cli/services/git-context.js +66 -0
- package/dist/cli/services/hooks.d.ts +85 -0
- package/dist/cli/services/hooks.js +258 -0
- package/dist/cli/services/interactive-tools.d.ts +125 -0
- package/dist/cli/services/interactive-tools.js +260 -0
- package/dist/cli/services/keybinding-manager.d.ts +52 -0
- package/dist/cli/services/keybinding-manager.js +115 -0
- package/dist/cli/services/local-tools.d.ts +22 -0
- package/dist/cli/services/local-tools.js +697 -0
- package/dist/cli/services/lsp-manager.d.ts +18 -0
- package/dist/cli/services/lsp-manager.js +717 -0
- package/dist/cli/services/mcp-client.d.ts +48 -0
- package/dist/cli/services/mcp-client.js +157 -0
- package/dist/cli/services/memory-manager.d.ts +16 -0
- package/dist/cli/services/memory-manager.js +57 -0
- package/dist/cli/services/model-manager.d.ts +18 -0
- package/dist/cli/services/model-manager.js +71 -0
- package/dist/cli/services/model-router.d.ts +26 -0
- package/dist/cli/services/model-router.js +149 -0
- package/dist/cli/services/permission-modes.d.ts +13 -0
- package/dist/cli/services/permission-modes.js +43 -0
- package/dist/cli/services/rewind.d.ts +84 -0
- package/dist/cli/services/rewind.js +194 -0
- package/dist/cli/services/ripgrep.d.ts +28 -0
- package/dist/cli/services/ripgrep.js +138 -0
- package/dist/cli/services/sandbox.d.ts +29 -0
- package/dist/cli/services/sandbox.js +97 -0
- package/dist/cli/services/server-tools.d.ts +61 -0
- package/dist/cli/services/server-tools.js +543 -0
- package/dist/cli/services/session-persistence.d.ts +23 -0
- package/dist/cli/services/session-persistence.js +99 -0
- package/dist/cli/services/subagent-worker.d.ts +19 -0
- package/dist/cli/services/subagent-worker.js +41 -0
- package/dist/cli/services/subagent.d.ts +47 -0
- package/dist/cli/services/subagent.js +647 -0
- package/dist/cli/services/system-prompt.d.ts +7 -0
- package/dist/cli/services/system-prompt.js +198 -0
- package/dist/cli/services/team-lead.d.ts +73 -0
- package/dist/cli/services/team-lead.js +512 -0
- package/dist/cli/services/team-state.d.ts +77 -0
- package/dist/cli/services/team-state.js +398 -0
- package/dist/cli/services/teammate.d.ts +31 -0
- package/dist/cli/services/teammate.js +689 -0
- package/dist/cli/services/telemetry.d.ts +61 -0
- package/dist/cli/services/telemetry.js +209 -0
- package/dist/cli/services/tools/agent-tools.d.ts +14 -0
- package/dist/cli/services/tools/agent-tools.js +347 -0
- package/dist/cli/services/tools/file-ops.d.ts +15 -0
- package/dist/cli/services/tools/file-ops.js +487 -0
- package/dist/cli/services/tools/search-tools.d.ts +8 -0
- package/dist/cli/services/tools/search-tools.js +186 -0
- package/dist/cli/services/tools/shell-exec.d.ts +10 -0
- package/dist/cli/services/tools/shell-exec.js +168 -0
- package/dist/cli/services/tools/task-manager.d.ts +28 -0
- package/dist/cli/services/tools/task-manager.js +209 -0
- package/dist/cli/services/tools/web-tools.d.ts +11 -0
- package/dist/cli/services/tools/web-tools.js +395 -0
- package/dist/cli/setup/SetupApp.d.ts +9 -0
- package/dist/cli/setup/SetupApp.js +191 -0
- package/dist/cli/shared/MatrixIntro.d.ts +4 -0
- package/dist/cli/shared/MatrixIntro.js +83 -0
- package/dist/cli/shared/Theme.d.ts +74 -0
- package/dist/cli/shared/Theme.js +127 -0
- package/dist/cli/shared/WhaleBanner.d.ts +10 -0
- package/dist/cli/shared/WhaleBanner.js +12 -0
- package/dist/cli/shared/markdown.d.ts +21 -0
- package/dist/cli/shared/markdown.js +756 -0
- package/dist/cli/status/StatusApp.d.ts +4 -0
- package/dist/cli/status/StatusApp.js +105 -0
- package/dist/cli/stores/StoreApp.d.ts +7 -0
- package/dist/cli/stores/StoreApp.js +81 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +538 -0
- package/dist/local-agent/connection.d.ts +48 -0
- package/dist/local-agent/connection.js +332 -0
- package/dist/local-agent/discovery.d.ts +18 -0
- package/dist/local-agent/discovery.js +146 -0
- package/dist/local-agent/executor.d.ts +34 -0
- package/dist/local-agent/executor.js +241 -0
- package/dist/local-agent/index.d.ts +14 -0
- package/dist/local-agent/index.js +198 -0
- package/dist/node/adapters/base.d.ts +35 -0
- package/dist/node/adapters/base.js +10 -0
- package/dist/node/adapters/discord.d.ts +29 -0
- package/dist/node/adapters/discord.js +299 -0
- package/dist/node/adapters/email.d.ts +23 -0
- package/dist/node/adapters/email.js +218 -0
- package/dist/node/adapters/imessage.d.ts +17 -0
- package/dist/node/adapters/imessage.js +118 -0
- package/dist/node/adapters/slack.d.ts +26 -0
- package/dist/node/adapters/slack.js +259 -0
- package/dist/node/adapters/sms.d.ts +23 -0
- package/dist/node/adapters/sms.js +161 -0
- package/dist/node/adapters/telegram.d.ts +17 -0
- package/dist/node/adapters/telegram.js +101 -0
- package/dist/node/adapters/webchat.d.ts +27 -0
- package/dist/node/adapters/webchat.js +160 -0
- package/dist/node/adapters/whatsapp.d.ts +28 -0
- package/dist/node/adapters/whatsapp.js +230 -0
- package/dist/node/cli.d.ts +2 -0
- package/dist/node/cli.js +325 -0
- package/dist/node/config.d.ts +17 -0
- package/dist/node/config.js +31 -0
- package/dist/node/runtime.d.ts +50 -0
- package/dist/node/runtime.js +351 -0
- package/dist/server/handlers/__test-utils__/mock-supabase.d.ts +11 -0
- package/dist/server/handlers/__test-utils__/mock-supabase.js +393 -0
- package/dist/server/handlers/analytics.d.ts +17 -0
- package/dist/server/handlers/analytics.js +266 -0
- package/dist/server/handlers/api-keys.d.ts +6 -0
- package/dist/server/handlers/api-keys.js +221 -0
- package/dist/server/handlers/billing.d.ts +33 -0
- package/dist/server/handlers/billing.js +272 -0
- package/dist/server/handlers/browser.d.ts +10 -0
- package/dist/server/handlers/browser.js +517 -0
- package/dist/server/handlers/catalog.d.ts +99 -0
- package/dist/server/handlers/catalog.js +976 -0
- package/dist/server/handlers/comms.d.ts +254 -0
- package/dist/server/handlers/comms.js +588 -0
- package/dist/server/handlers/creations.d.ts +6 -0
- package/dist/server/handlers/creations.js +479 -0
- package/dist/server/handlers/crm.d.ts +89 -0
- package/dist/server/handlers/crm.js +538 -0
- package/dist/server/handlers/discovery.d.ts +6 -0
- package/dist/server/handlers/discovery.js +288 -0
- package/dist/server/handlers/embeddings.d.ts +92 -0
- package/dist/server/handlers/embeddings.js +197 -0
- package/dist/server/handlers/enrichment.d.ts +8 -0
- package/dist/server/handlers/enrichment.js +768 -0
- package/dist/server/handlers/image-gen.d.ts +6 -0
- package/dist/server/handlers/image-gen.js +409 -0
- package/dist/server/handlers/inventory.d.ts +319 -0
- package/dist/server/handlers/inventory.js +447 -0
- package/dist/server/handlers/kali.d.ts +10 -0
- package/dist/server/handlers/kali.js +210 -0
- package/dist/server/handlers/llm-providers.d.ts +6 -0
- package/dist/server/handlers/llm-providers.js +673 -0
- package/dist/server/handlers/local-agent.d.ts +6 -0
- package/dist/server/handlers/local-agent.js +118 -0
- package/dist/server/handlers/meta-ads.d.ts +111 -0
- package/dist/server/handlers/meta-ads.js +2279 -0
- package/dist/server/handlers/nodes.d.ts +33 -0
- package/dist/server/handlers/nodes.js +699 -0
- package/dist/server/handlers/operations.d.ts +138 -0
- package/dist/server/handlers/operations.js +131 -0
- package/dist/server/handlers/platform.d.ts +23 -0
- package/dist/server/handlers/platform.js +227 -0
- package/dist/server/handlers/supply-chain.d.ts +19 -0
- package/dist/server/handlers/supply-chain.js +327 -0
- package/dist/server/handlers/transcription.d.ts +17 -0
- package/dist/server/handlers/transcription.js +121 -0
- package/dist/server/handlers/video-gen.d.ts +6 -0
- package/dist/server/handlers/video-gen.js +466 -0
- package/dist/server/handlers/voice.d.ts +8 -0
- package/dist/server/handlers/voice.js +1146 -0
- package/dist/server/handlers/workflow-steps.d.ts +86 -0
- package/dist/server/handlers/workflow-steps.js +2349 -0
- package/dist/server/handlers/workflows.d.ts +7 -0
- package/dist/server/handlers/workflows.js +989 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +2427 -0
- package/dist/server/lib/batch-client.d.ts +80 -0
- package/dist/server/lib/batch-client.js +467 -0
- package/dist/server/lib/code-worker-pool.d.ts +31 -0
- package/dist/server/lib/code-worker-pool.js +224 -0
- package/dist/server/lib/code-worker.d.ts +1 -0
- package/dist/server/lib/code-worker.js +188 -0
- package/dist/server/lib/compaction-service.d.ts +32 -0
- package/dist/server/lib/compaction-service.js +162 -0
- package/dist/server/lib/logger.d.ts +19 -0
- package/dist/server/lib/logger.js +46 -0
- package/dist/server/lib/otel.d.ts +38 -0
- package/dist/server/lib/otel.js +126 -0
- package/dist/server/lib/pg-rate-limiter.d.ts +21 -0
- package/dist/server/lib/pg-rate-limiter.js +86 -0
- package/dist/server/lib/prompt-sanitizer.d.ts +37 -0
- package/dist/server/lib/prompt-sanitizer.js +177 -0
- package/dist/server/lib/provider-capabilities.d.ts +85 -0
- package/dist/server/lib/provider-capabilities.js +190 -0
- package/dist/server/lib/provider-failover.d.ts +74 -0
- package/dist/server/lib/provider-failover.js +210 -0
- package/dist/server/lib/rate-limiter.d.ts +39 -0
- package/dist/server/lib/rate-limiter.js +147 -0
- package/dist/server/lib/server-agent-loop.d.ts +107 -0
- package/dist/server/lib/server-agent-loop.js +667 -0
- package/dist/server/lib/server-subagent.d.ts +78 -0
- package/dist/server/lib/server-subagent.js +203 -0
- package/dist/server/lib/session-checkpoint.d.ts +51 -0
- package/dist/server/lib/session-checkpoint.js +145 -0
- package/dist/server/lib/ssrf-guard.d.ts +13 -0
- package/dist/server/lib/ssrf-guard.js +240 -0
- package/dist/server/lib/supabase-client.d.ts +7 -0
- package/dist/server/lib/supabase-client.js +78 -0
- package/dist/server/lib/template-resolver.d.ts +31 -0
- package/dist/server/lib/template-resolver.js +215 -0
- package/dist/server/lib/utils.d.ts +16 -0
- package/dist/server/lib/utils.js +147 -0
- package/dist/server/local-agent-gateway.d.ts +82 -0
- package/dist/server/local-agent-gateway.js +426 -0
- package/dist/server/providers/anthropic.d.ts +20 -0
- package/dist/server/providers/anthropic.js +199 -0
- package/dist/server/providers/bedrock.d.ts +20 -0
- package/dist/server/providers/bedrock.js +194 -0
- package/dist/server/providers/gemini.d.ts +24 -0
- package/dist/server/providers/gemini.js +486 -0
- package/dist/server/providers/openai.d.ts +24 -0
- package/dist/server/providers/openai.js +522 -0
- package/dist/server/providers/registry.d.ts +32 -0
- package/dist/server/providers/registry.js +58 -0
- package/dist/server/providers/shared.d.ts +32 -0
- package/dist/server/providers/shared.js +124 -0
- package/dist/server/providers/types.d.ts +92 -0
- package/dist/server/providers/types.js +12 -0
- package/dist/server/proxy-handlers.d.ts +6 -0
- package/dist/server/proxy-handlers.js +89 -0
- package/dist/server/tool-router.d.ts +149 -0
- package/dist/server/tool-router.js +803 -0
- package/dist/server/validation.d.ts +24 -0
- package/dist/server/validation.js +301 -0
- package/dist/server/worker.d.ts +19 -0
- package/dist/server/worker.js +201 -0
- package/dist/setup.d.ts +8 -0
- package/dist/setup.js +181 -0
- package/dist/shared/agent-core.d.ts +157 -0
- package/dist/shared/agent-core.js +534 -0
- package/dist/shared/anthropic-types.d.ts +105 -0
- package/dist/shared/anthropic-types.js +7 -0
- package/dist/shared/api-client.d.ts +90 -0
- package/dist/shared/api-client.js +379 -0
- package/dist/shared/constants.d.ts +33 -0
- package/dist/shared/constants.js +80 -0
- package/dist/shared/sse-parser.d.ts +26 -0
- package/dist/shared/sse-parser.js +259 -0
- package/dist/shared/tool-dispatch.d.ts +52 -0
- package/dist/shared/tool-dispatch.js +191 -0
- package/dist/shared/types.d.ts +72 -0
- package/dist/shared/types.js +7 -0
- package/dist/updater.d.ts +25 -0
- package/dist/updater.js +140 -0
- package/dist/webchat/widget.d.ts +0 -0
- package/dist/webchat/widget.js +397 -0
- package/package.json +95 -0
- package/src/cli/services/builtin-skills/commit.md +19 -0
- package/src/cli/services/builtin-skills/review-pr.md +21 -0
- 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
|
+
}>;
|