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,486 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gemini Provider Adapter — full format conversion to/from Anthropic SSE.
|
|
3
|
+
*
|
|
4
|
+
* Contains:
|
|
5
|
+
* - anthropicToGeminiMessages() — message format conversion
|
|
6
|
+
* - sanitizeSchemaForGemini() — type uppercase, enum stringification, key stripping
|
|
7
|
+
* - anthropicToGeminiFunctions() — tool conversion
|
|
8
|
+
* - Streaming event loop with thinking/text/function call detection
|
|
9
|
+
* - seenFunctionCalls deduplication
|
|
10
|
+
*
|
|
11
|
+
* Phase 7.1: Also encapsulates thinking config, context management,
|
|
12
|
+
* output token limits, and compaction config for Gemini models.
|
|
13
|
+
*/
|
|
14
|
+
import { randomUUID } from "node:crypto";
|
|
15
|
+
import { GoogleGenAI } from "@google/genai";
|
|
16
|
+
import { sanitizeError } from "../../shared/agent-core.js";
|
|
17
|
+
import { getCapabilities } from "../lib/provider-capabilities.js";
|
|
18
|
+
import { registerProvider } from "./registry.js";
|
|
19
|
+
import { jsonResponse, writeSSEHeaders, emitError, resolveProviderCredentials } from "./shared.js";
|
|
20
|
+
// ============================================================================
|
|
21
|
+
// CONSTANTS — Gemini-specific model config
|
|
22
|
+
// ============================================================================
|
|
23
|
+
const GEMINI_MAX_TOKENS = 65536;
|
|
24
|
+
const DEFAULT_OUTPUT_TOKENS = 16384;
|
|
25
|
+
const MODEL_MAX_OUTPUT_TOKENS = {
|
|
26
|
+
"gemini-3-pro-preview": 65536,
|
|
27
|
+
"gemini-3-flash-preview": 65536,
|
|
28
|
+
"gemini-2.5-pro": 65536,
|
|
29
|
+
"gemini-2.5-flash": 65536,
|
|
30
|
+
"gemini-2.5-flash-lite": 65536,
|
|
31
|
+
};
|
|
32
|
+
// ============================================================================
|
|
33
|
+
// MESSAGE CONVERSION
|
|
34
|
+
// ============================================================================
|
|
35
|
+
/** Convert Anthropic messages to Gemini Content format */
|
|
36
|
+
function anthropicToGeminiMessages(messages) {
|
|
37
|
+
const contents = [];
|
|
38
|
+
// Track tool_use_id → name for tool_result conversion
|
|
39
|
+
const toolIdMap = new Map();
|
|
40
|
+
for (const msg of messages) {
|
|
41
|
+
const role = msg.role === "assistant" ? "model" : "user";
|
|
42
|
+
const parts = [];
|
|
43
|
+
if (typeof msg.content === "string") {
|
|
44
|
+
parts.push({ text: msg.content });
|
|
45
|
+
}
|
|
46
|
+
else if (Array.isArray(msg.content)) {
|
|
47
|
+
for (const block of msg.content) {
|
|
48
|
+
if (block.type === "text" && block.text) {
|
|
49
|
+
parts.push({ text: block.text });
|
|
50
|
+
}
|
|
51
|
+
else if (block.type === "tool_use") {
|
|
52
|
+
toolIdMap.set(block.id, block.name);
|
|
53
|
+
parts.push({
|
|
54
|
+
functionCall: { name: block.name, args: block.input || {} },
|
|
55
|
+
// Preserve Gemini thought signature for function call round-trips
|
|
56
|
+
...(block.thought_signature ? { thoughtSignature: block.thought_signature } : {}),
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
else if (block.type === "image" && block.source?.data) {
|
|
60
|
+
parts.push({
|
|
61
|
+
inlineData: {
|
|
62
|
+
mimeType: block.source.media_type,
|
|
63
|
+
data: block.source.data,
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
else if (block.type === "tool_result") {
|
|
68
|
+
const toolName = toolIdMap.get(block.tool_use_id) || block.tool_use_id;
|
|
69
|
+
let resultContent = "";
|
|
70
|
+
if (typeof block.content === "string") {
|
|
71
|
+
resultContent = block.content;
|
|
72
|
+
}
|
|
73
|
+
else if (Array.isArray(block.content)) {
|
|
74
|
+
resultContent = block.content
|
|
75
|
+
.filter((c) => c.type === "text")
|
|
76
|
+
.map((c) => c.text)
|
|
77
|
+
.join("\n");
|
|
78
|
+
}
|
|
79
|
+
parts.push({
|
|
80
|
+
functionResponse: {
|
|
81
|
+
name: toolName,
|
|
82
|
+
response: { result: resultContent },
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (parts.length > 0) {
|
|
89
|
+
contents.push({ role, parts });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return contents;
|
|
93
|
+
}
|
|
94
|
+
// ============================================================================
|
|
95
|
+
// SCHEMA SANITIZATION
|
|
96
|
+
// ============================================================================
|
|
97
|
+
/** Gemini uses uppercase OpenAPI schema types, not lowercase JSON Schema types. */
|
|
98
|
+
const GEMINI_TYPE_MAP = {
|
|
99
|
+
string: "STRING",
|
|
100
|
+
number: "NUMBER",
|
|
101
|
+
integer: "INTEGER",
|
|
102
|
+
boolean: "BOOLEAN",
|
|
103
|
+
array: "ARRAY",
|
|
104
|
+
object: "OBJECT",
|
|
105
|
+
};
|
|
106
|
+
/**
|
|
107
|
+
* Keys that are JSON Schema-only and not supported by Gemini's OpenAPI Schema.
|
|
108
|
+
* IMPORTANT: Only strip keys that can't collide with property names in our tools.
|
|
109
|
+
*/
|
|
110
|
+
const GEMINI_SCHEMA_ONLY_KEYS = new Set([
|
|
111
|
+
"$schema", "additionalProperties", "propertyNames",
|
|
112
|
+
"minProperties", "maxProperties",
|
|
113
|
+
"anyOf", "oneOf", "allOf", "not", "const",
|
|
114
|
+
]);
|
|
115
|
+
/** Keys to strip only from schema definition objects (objects with a "type" field) */
|
|
116
|
+
const GEMINI_SCHEMA_VALIDATION_KEYS = new Set([
|
|
117
|
+
"exclusiveMinimum", "exclusiveMaximum",
|
|
118
|
+
"minItems", "maxItems", "minLength", "maxLength",
|
|
119
|
+
"minimum", "maximum", "pattern",
|
|
120
|
+
"default",
|
|
121
|
+
]);
|
|
122
|
+
/**
|
|
123
|
+
* Sanitize JSON Schema → Gemini OpenAPI Schema:
|
|
124
|
+
* - Convert type values to uppercase (string → STRING)
|
|
125
|
+
* - Stringify all enum values
|
|
126
|
+
* - Remove unsupported JSON Schema keys (carefully — don't strip property names)
|
|
127
|
+
* - Ensure arrays have items, objects have properties
|
|
128
|
+
* - Filter required to only include defined properties
|
|
129
|
+
*/
|
|
130
|
+
function sanitizeSchemaForGemini(schema) {
|
|
131
|
+
if (!schema || typeof schema !== "object")
|
|
132
|
+
return schema;
|
|
133
|
+
if (Array.isArray(schema))
|
|
134
|
+
return schema.map((s) => sanitizeSchemaForGemini(s));
|
|
135
|
+
const result = {};
|
|
136
|
+
const isSchemaNode = "type" in schema;
|
|
137
|
+
for (const [key, value] of Object.entries(schema)) {
|
|
138
|
+
// Always strip structural JSON Schema keys
|
|
139
|
+
if (GEMINI_SCHEMA_ONLY_KEYS.has(key))
|
|
140
|
+
continue;
|
|
141
|
+
// Strip validation keys only from schema nodes (not property name maps)
|
|
142
|
+
if (isSchemaNode && GEMINI_SCHEMA_VALIDATION_KEYS.has(key))
|
|
143
|
+
continue;
|
|
144
|
+
if (key === "type" && typeof value === "string") {
|
|
145
|
+
result[key] = GEMINI_TYPE_MAP[value] || value.toUpperCase();
|
|
146
|
+
}
|
|
147
|
+
else if (key === "enum" && Array.isArray(value)) {
|
|
148
|
+
result[key] = value.map((v) => String(v));
|
|
149
|
+
}
|
|
150
|
+
else if (key === "properties" && typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
151
|
+
// Properties map: keys are property names, values are schema definitions
|
|
152
|
+
const props = {};
|
|
153
|
+
for (const [propName, propSchema] of Object.entries(value)) {
|
|
154
|
+
props[propName] = sanitizeSchemaForGemini(propSchema);
|
|
155
|
+
}
|
|
156
|
+
result[key] = props;
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
result[key] = sanitizeSchemaForGemini(value);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
// Gemini requires array items to have a type
|
|
163
|
+
if (result.type === "ARRAY") {
|
|
164
|
+
if (!result.items || (typeof result.items === "object" && Object.keys(result.items).length === 0)) {
|
|
165
|
+
result.items = { type: "STRING" };
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// Gemini requires object types to have properties
|
|
169
|
+
if (result.type === "OBJECT" && !result.properties) {
|
|
170
|
+
result.properties = {};
|
|
171
|
+
}
|
|
172
|
+
// Gemini only allows enum on STRING type — force type to STRING if enum is present
|
|
173
|
+
if (result.enum && Array.isArray(result.enum) && result.type && result.type !== "STRING") {
|
|
174
|
+
result.type = "STRING";
|
|
175
|
+
}
|
|
176
|
+
// Filter required to only include properties that actually exist
|
|
177
|
+
if (result.required && Array.isArray(result.required) && result.properties) {
|
|
178
|
+
const validProps = new Set(Object.keys(result.properties));
|
|
179
|
+
result.required = result.required.filter((r) => validProps.has(r));
|
|
180
|
+
if (result.required.length === 0)
|
|
181
|
+
delete result.required;
|
|
182
|
+
}
|
|
183
|
+
return result;
|
|
184
|
+
}
|
|
185
|
+
function anthropicToGeminiFunctions(tools) {
|
|
186
|
+
return tools.map((t) => ({
|
|
187
|
+
name: t.name,
|
|
188
|
+
description: typeof t.description === "string" ? t.description.slice(0, 4096) : "",
|
|
189
|
+
parameters: sanitizeSchemaForGemini(t.input_schema || {}),
|
|
190
|
+
}));
|
|
191
|
+
}
|
|
192
|
+
// ============================================================================
|
|
193
|
+
// ADAPTER
|
|
194
|
+
// ============================================================================
|
|
195
|
+
export class GeminiAdapter {
|
|
196
|
+
name = "gemini";
|
|
197
|
+
async handleStream(res, config, corsHeaders) {
|
|
198
|
+
const { model, messages, system, tools, max_tokens, temperature, storeId, tool_choice } = config;
|
|
199
|
+
const creds = await resolveProviderCredentials("google", storeId);
|
|
200
|
+
if (!creds.google) {
|
|
201
|
+
jsonResponse(res, 422, { error: "Google AI credentials not configured. Add google_ai_api_key to platform_secrets." }, corsHeaders);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const client = new GoogleGenAI({ apiKey: creds.google.apiKey });
|
|
205
|
+
// Extract system prompt text
|
|
206
|
+
let systemText = "";
|
|
207
|
+
if (typeof system === "string") {
|
|
208
|
+
systemText = system;
|
|
209
|
+
}
|
|
210
|
+
else if (Array.isArray(system)) {
|
|
211
|
+
systemText = system.map((s) => s.text || "").join("\n");
|
|
212
|
+
}
|
|
213
|
+
// Build generation config
|
|
214
|
+
const genConfig = { maxOutputTokens: max_tokens };
|
|
215
|
+
if (temperature !== undefined)
|
|
216
|
+
genConfig.temperature = temperature;
|
|
217
|
+
// Map tool_choice to Gemini functionCallingConfig
|
|
218
|
+
let geminiFunctionCallingConfig = { mode: "AUTO" };
|
|
219
|
+
let omitGeminiTools = false;
|
|
220
|
+
if (tool_choice) {
|
|
221
|
+
if (tool_choice === "auto") {
|
|
222
|
+
geminiFunctionCallingConfig = { mode: "AUTO" };
|
|
223
|
+
}
|
|
224
|
+
else if (tool_choice === "any") {
|
|
225
|
+
geminiFunctionCallingConfig = { mode: "ANY" };
|
|
226
|
+
}
|
|
227
|
+
else if (tool_choice === "none") {
|
|
228
|
+
geminiFunctionCallingConfig = { mode: "NONE" };
|
|
229
|
+
omitGeminiTools = true;
|
|
230
|
+
}
|
|
231
|
+
else if (typeof tool_choice === "object" && tool_choice.type === "tool") {
|
|
232
|
+
geminiFunctionCallingConfig = { mode: "ANY", allowedFunctionNames: [tool_choice.name] };
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
// Build request config for the new SDK — model is passed per-request
|
|
236
|
+
const requestConfig = {
|
|
237
|
+
model,
|
|
238
|
+
config: {
|
|
239
|
+
...genConfig,
|
|
240
|
+
...(systemText ? { systemInstruction: systemText } : {}),
|
|
241
|
+
...(!omitGeminiTools && tools?.length ? {
|
|
242
|
+
tools: [{ functionDeclarations: anthropicToGeminiFunctions(tools) }],
|
|
243
|
+
toolConfig: { functionCallingConfig: geminiFunctionCallingConfig },
|
|
244
|
+
} : {}),
|
|
245
|
+
},
|
|
246
|
+
};
|
|
247
|
+
const genModel = client.models;
|
|
248
|
+
// Convert messages
|
|
249
|
+
const contents = anthropicToGeminiMessages(messages);
|
|
250
|
+
// Track tokens outside try so catch block can reference them
|
|
251
|
+
let totalOutputTokens = 0;
|
|
252
|
+
let totalInputTokens = 0;
|
|
253
|
+
let totalThinkingTokens = 0;
|
|
254
|
+
let totalCacheReadTokens = 0;
|
|
255
|
+
// Defer SSE headers until stream is successfully created
|
|
256
|
+
// so we can return proper HTTP errors if the initial API call fails.
|
|
257
|
+
let heartbeat;
|
|
258
|
+
try {
|
|
259
|
+
// Request with includeThoughts so we stream Gemini's thinking process
|
|
260
|
+
const streamResult = await genModel.generateContentStream({
|
|
261
|
+
...requestConfig,
|
|
262
|
+
contents,
|
|
263
|
+
config: {
|
|
264
|
+
...requestConfig.config,
|
|
265
|
+
thinkingConfig: { includeThoughts: true },
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
// Stream created successfully — now safe to commit to SSE
|
|
269
|
+
writeSSEHeaders(res, corsHeaders);
|
|
270
|
+
// Heartbeat keeps the connection alive and helps detect dead clients
|
|
271
|
+
heartbeat = setInterval(() => {
|
|
272
|
+
if (!res.writableEnded)
|
|
273
|
+
res.write(":ping\n\n");
|
|
274
|
+
}, 15_000);
|
|
275
|
+
// Emit Anthropic-format message_start
|
|
276
|
+
res.write(`data: ${JSON.stringify({
|
|
277
|
+
type: "message_start",
|
|
278
|
+
message: {
|
|
279
|
+
id: `msg_gemini_${randomUUID().slice(0, 8)}`,
|
|
280
|
+
type: "message",
|
|
281
|
+
role: "assistant",
|
|
282
|
+
content: [],
|
|
283
|
+
model,
|
|
284
|
+
stop_reason: null,
|
|
285
|
+
usage: { input_tokens: 0, output_tokens: 0 },
|
|
286
|
+
},
|
|
287
|
+
})}\n\n`);
|
|
288
|
+
let blockIndex = 0;
|
|
289
|
+
let hasOpenTextBlock = false;
|
|
290
|
+
let hasOpenThinkingBlock = false;
|
|
291
|
+
let hasToolUse = false;
|
|
292
|
+
let hasEmittedContent = false;
|
|
293
|
+
const seenFunctionCalls = new Set();
|
|
294
|
+
for await (const chunk of streamResult) {
|
|
295
|
+
const candidates = chunk.candidates || [];
|
|
296
|
+
const usage = chunk.usageMetadata;
|
|
297
|
+
if (usage) {
|
|
298
|
+
totalOutputTokens = usage.candidatesTokenCount || usage.totalTokenCount || 0;
|
|
299
|
+
totalInputTokens = usage.promptTokenCount || 0;
|
|
300
|
+
totalThinkingTokens = usage.thoughtsTokenCount || 0;
|
|
301
|
+
if (usage.cachedContentTokenCount) {
|
|
302
|
+
totalCacheReadTokens = usage.cachedContentTokenCount;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
for (const candidate of candidates) {
|
|
306
|
+
const allParts = candidate.content?.parts || [];
|
|
307
|
+
for (const part of allParts) {
|
|
308
|
+
const isThought = !!part.thought;
|
|
309
|
+
// Thinking text delta
|
|
310
|
+
if (isThought && part.text) {
|
|
311
|
+
if (hasOpenTextBlock) {
|
|
312
|
+
res.write(`data: ${JSON.stringify({ type: "content_block_stop", index: blockIndex })}\n\n`);
|
|
313
|
+
blockIndex++;
|
|
314
|
+
hasOpenTextBlock = false;
|
|
315
|
+
}
|
|
316
|
+
if (!hasOpenThinkingBlock) {
|
|
317
|
+
res.write(`data: ${JSON.stringify({
|
|
318
|
+
type: "content_block_start",
|
|
319
|
+
index: blockIndex,
|
|
320
|
+
content_block: { type: "thinking", thinking: "" },
|
|
321
|
+
})}\n\n`);
|
|
322
|
+
hasOpenThinkingBlock = true;
|
|
323
|
+
hasEmittedContent = true;
|
|
324
|
+
}
|
|
325
|
+
res.write(`data: ${JSON.stringify({
|
|
326
|
+
type: "content_block_delta",
|
|
327
|
+
index: blockIndex,
|
|
328
|
+
delta: { type: "thinking_delta", thinking: part.text },
|
|
329
|
+
})}\n\n`);
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
// Response text delta
|
|
333
|
+
if (part.text && !isThought) {
|
|
334
|
+
if (hasOpenThinkingBlock) {
|
|
335
|
+
res.write(`data: ${JSON.stringify({ type: "content_block_stop", index: blockIndex })}\n\n`);
|
|
336
|
+
blockIndex++;
|
|
337
|
+
hasOpenThinkingBlock = false;
|
|
338
|
+
}
|
|
339
|
+
if (!hasOpenTextBlock) {
|
|
340
|
+
res.write(`data: ${JSON.stringify({
|
|
341
|
+
type: "content_block_start",
|
|
342
|
+
index: blockIndex,
|
|
343
|
+
content_block: { type: "text", text: "" },
|
|
344
|
+
})}\n\n`);
|
|
345
|
+
hasOpenTextBlock = true;
|
|
346
|
+
hasEmittedContent = true;
|
|
347
|
+
}
|
|
348
|
+
res.write(`data: ${JSON.stringify({
|
|
349
|
+
type: "content_block_delta",
|
|
350
|
+
index: blockIndex,
|
|
351
|
+
delta: { type: "text_delta", text: part.text },
|
|
352
|
+
})}\n\n`);
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
// Function call
|
|
356
|
+
if (part.functionCall) {
|
|
357
|
+
const fc = {
|
|
358
|
+
name: part.functionCall.name || "",
|
|
359
|
+
args: part.functionCall.args || {},
|
|
360
|
+
thoughtSignature: part.thoughtSignature,
|
|
361
|
+
};
|
|
362
|
+
const fcKey = `${fc.name}:${JSON.stringify(fc.args)}`;
|
|
363
|
+
if (seenFunctionCalls.has(fcKey))
|
|
364
|
+
continue;
|
|
365
|
+
seenFunctionCalls.add(fcKey);
|
|
366
|
+
// Close any open thinking block
|
|
367
|
+
if (hasOpenThinkingBlock) {
|
|
368
|
+
res.write(`data: ${JSON.stringify({ type: "content_block_stop", index: blockIndex })}\n\n`);
|
|
369
|
+
blockIndex++;
|
|
370
|
+
hasOpenThinkingBlock = false;
|
|
371
|
+
}
|
|
372
|
+
// Close any open text block
|
|
373
|
+
if (hasOpenTextBlock) {
|
|
374
|
+
res.write(`data: ${JSON.stringify({ type: "content_block_stop", index: blockIndex })}\n\n`);
|
|
375
|
+
blockIndex++;
|
|
376
|
+
hasOpenTextBlock = false;
|
|
377
|
+
}
|
|
378
|
+
hasToolUse = true;
|
|
379
|
+
hasEmittedContent = true;
|
|
380
|
+
const toolUseId = `toolu_gemini_${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: fc.name,
|
|
388
|
+
input: {},
|
|
389
|
+
// Preserve Gemini thought_signature — required for tool call round-trips with thinking
|
|
390
|
+
...(fc.thoughtSignature ? { thought_signature: fc.thoughtSignature } : {}),
|
|
391
|
+
},
|
|
392
|
+
})}\n\n`);
|
|
393
|
+
res.write(`data: ${JSON.stringify({
|
|
394
|
+
type: "content_block_delta",
|
|
395
|
+
index: blockIndex,
|
|
396
|
+
delta: {
|
|
397
|
+
type: "input_json_delta",
|
|
398
|
+
partial_json: JSON.stringify(fc.args || {}),
|
|
399
|
+
},
|
|
400
|
+
})}\n\n`);
|
|
401
|
+
res.write(`data: ${JSON.stringify({ type: "content_block_stop", index: blockIndex })}\n\n`);
|
|
402
|
+
blockIndex++;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
// Close any open blocks — no signature_delta for Gemini thinking
|
|
408
|
+
if (hasOpenThinkingBlock) {
|
|
409
|
+
res.write(`data: ${JSON.stringify({ type: "content_block_stop", index: blockIndex })}\n\n`);
|
|
410
|
+
}
|
|
411
|
+
if (hasOpenTextBlock) {
|
|
412
|
+
res.write(`data: ${JSON.stringify({ type: "content_block_stop", index: blockIndex })}\n\n`);
|
|
413
|
+
}
|
|
414
|
+
// Handle empty response — Gemini sometimes returns finishReason: STOP with no content
|
|
415
|
+
if (!hasEmittedContent) {
|
|
416
|
+
res.write(`data: ${JSON.stringify({
|
|
417
|
+
type: "content_block_start",
|
|
418
|
+
index: 0,
|
|
419
|
+
content_block: { type: "text", text: "" },
|
|
420
|
+
})}\n\n`);
|
|
421
|
+
res.write(`data: ${JSON.stringify({
|
|
422
|
+
type: "content_block_delta",
|
|
423
|
+
index: 0,
|
|
424
|
+
delta: { type: "text_delta", text: "[Gemini returned an empty response. Please try again.]" },
|
|
425
|
+
})}\n\n`);
|
|
426
|
+
res.write(`data: ${JSON.stringify({ type: "content_block_stop", index: 0 })}\n\n`);
|
|
427
|
+
}
|
|
428
|
+
// Determine stop reason
|
|
429
|
+
const stopReason = hasToolUse ? "tool_use" : "end_turn";
|
|
430
|
+
// Emit message_delta with stop reason + full usage
|
|
431
|
+
res.write(`data: ${JSON.stringify({
|
|
432
|
+
type: "message_delta",
|
|
433
|
+
delta: { stop_reason: stopReason, stop_sequence: null },
|
|
434
|
+
usage: {
|
|
435
|
+
input_tokens: totalInputTokens,
|
|
436
|
+
output_tokens: totalOutputTokens,
|
|
437
|
+
...(totalThinkingTokens > 0 ? { thinking_tokens: totalThinkingTokens } : {}),
|
|
438
|
+
...(totalCacheReadTokens > 0 ? { cache_read_input_tokens: totalCacheReadTokens } : {}),
|
|
439
|
+
},
|
|
440
|
+
})}\n\n`);
|
|
441
|
+
// Emit message_stop
|
|
442
|
+
res.write(`data: ${JSON.stringify({ type: "message_stop" })}\n\n`);
|
|
443
|
+
res.write("data: [DONE]\n\n");
|
|
444
|
+
}
|
|
445
|
+
catch (err) {
|
|
446
|
+
emitError(res, err, corsHeaders, sanitizeError, {
|
|
447
|
+
provider: "gemini",
|
|
448
|
+
inputTokens: totalInputTokens,
|
|
449
|
+
outputTokens: totalOutputTokens,
|
|
450
|
+
thinkingTokens: totalThinkingTokens,
|
|
451
|
+
cacheReadTokens: totalCacheReadTokens,
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
finally {
|
|
455
|
+
if (heartbeat)
|
|
456
|
+
clearInterval(heartbeat);
|
|
457
|
+
}
|
|
458
|
+
res.end();
|
|
459
|
+
}
|
|
460
|
+
// ---- Provider-specific config methods ----
|
|
461
|
+
getThinkingConfig(_model, enabled) {
|
|
462
|
+
if (!enabled) {
|
|
463
|
+
return { thinking: { type: "disabled" }, beta: "" };
|
|
464
|
+
}
|
|
465
|
+
// Gemini models: thinking is always-on for 2.5+/3.x — signal pass-through
|
|
466
|
+
return { thinking: { type: "enabled" }, beta: "" };
|
|
467
|
+
}
|
|
468
|
+
getMaxOutputTokens(model, agentMax) {
|
|
469
|
+
const modelMax = MODEL_MAX_OUTPUT_TOKENS[model] ?? DEFAULT_OUTPUT_TOKENS;
|
|
470
|
+
if (agentMax)
|
|
471
|
+
return Math.min(agentMax, modelMax);
|
|
472
|
+
return Math.min(DEFAULT_OUTPUT_TOKENS, modelMax);
|
|
473
|
+
}
|
|
474
|
+
getContextManagement(_model) {
|
|
475
|
+
// Gemini doesn't use Anthropic betas or context management
|
|
476
|
+
return { betas: [], config: { edits: [] } };
|
|
477
|
+
}
|
|
478
|
+
getCompactionConfig(_model) {
|
|
479
|
+
return { triggerTokens: 700_000, totalBudget: 4_000_000, isNative: false };
|
|
480
|
+
}
|
|
481
|
+
getCapabilities() {
|
|
482
|
+
return getCapabilities("gemini");
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
// Auto-register on import
|
|
486
|
+
registerProvider("gemini", new GeminiAdapter());
|
|
@@ -0,0 +1,24 @@
|
|
|
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 type { ProviderAdapter, ProviderStreamConfig, ProviderCapabilities, ThinkingConfigResult, ContextManagementConfig, CompactionConfig } from "./types.js";
|
|
15
|
+
import type http from "node:http";
|
|
16
|
+
export declare class OpenAIAdapter implements ProviderAdapter {
|
|
17
|
+
name: string;
|
|
18
|
+
handleStream(res: http.ServerResponse, config: ProviderStreamConfig, corsHeaders: Record<string, string>): Promise<void>;
|
|
19
|
+
getThinkingConfig(model: string, enabled: boolean): ThinkingConfigResult;
|
|
20
|
+
getMaxOutputTokens(model: string, agentMax?: number): number;
|
|
21
|
+
getContextManagement(_model: string): ContextManagementConfig;
|
|
22
|
+
getCompactionConfig(_model: string): CompactionConfig;
|
|
23
|
+
getCapabilities(): ProviderCapabilities;
|
|
24
|
+
}
|