@yan162/changewayguard 6.8.25
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/LICENSE +21 -0
- package/OpenClaw-linux_Mac-Guide-zh.md +89 -0
- package/dashboard-dist/api/122.index.js +95 -0
- package/dashboard-dist/api/122.index.js.map +1 -0
- package/dashboard-dist/api/143.index.js +2734 -0
- package/dashboard-dist/api/143.index.js.map +1 -0
- package/dashboard-dist/api/154.index.js +4151 -0
- package/dashboard-dist/api/154.index.js.map +1 -0
- package/dashboard-dist/api/173.index.js +24112 -0
- package/dashboard-dist/api/173.index.js.map +1 -0
- package/dashboard-dist/api/217.index.js +44 -0
- package/dashboard-dist/api/217.index.js.map +1 -0
- package/dashboard-dist/api/222.index.js +90 -0
- package/dashboard-dist/api/222.index.js.map +1 -0
- package/dashboard-dist/api/280.index.js +213 -0
- package/dashboard-dist/api/280.index.js.map +1 -0
- package/dashboard-dist/api/369.index.js +115 -0
- package/dashboard-dist/api/369.index.js.map +1 -0
- package/dashboard-dist/api/374.index.js +1896 -0
- package/dashboard-dist/api/374.index.js.map +1 -0
- package/dashboard-dist/api/424.index.js +135 -0
- package/dashboard-dist/api/424.index.js.map +1 -0
- package/dashboard-dist/api/445.index.js +3562 -0
- package/dashboard-dist/api/445.index.js.map +1 -0
- package/dashboard-dist/api/555.index.js +496 -0
- package/dashboard-dist/api/555.index.js.map +1 -0
- package/dashboard-dist/api/573.index.js +806 -0
- package/dashboard-dist/api/573.index.js.map +1 -0
- package/dashboard-dist/api/580.index.js +1420 -0
- package/dashboard-dist/api/580.index.js.map +1 -0
- package/dashboard-dist/api/581.index.js +67 -0
- package/dashboard-dist/api/581.index.js.map +1 -0
- package/dashboard-dist/api/598.index.js +328 -0
- package/dashboard-dist/api/598.index.js.map +1 -0
- package/dashboard-dist/api/720.index.js +105 -0
- package/dashboard-dist/api/720.index.js.map +1 -0
- package/dashboard-dist/api/744.index.js +333 -0
- package/dashboard-dist/api/744.index.js.map +1 -0
- package/dashboard-dist/api/818.index.js +374 -0
- package/dashboard-dist/api/818.index.js.map +1 -0
- package/dashboard-dist/api/831.index.js +99 -0
- package/dashboard-dist/api/831.index.js.map +1 -0
- package/dashboard-dist/api/84.index.js +64 -0
- package/dashboard-dist/api/84.index.js.map +1 -0
- package/dashboard-dist/api/900.index.js +81 -0
- package/dashboard-dist/api/900.index.js.map +1 -0
- package/dashboard-dist/api/917.index.js +88 -0
- package/dashboard-dist/api/917.index.js.map +1 -0
- package/dashboard-dist/api/927.index.js +4250 -0
- package/dashboard-dist/api/927.index.js.map +1 -0
- package/dashboard-dist/api/948.index.js +64 -0
- package/dashboard-dist/api/948.index.js.map +1 -0
- package/dashboard-dist/api/982.index.js +67 -0
- package/dashboard-dist/api/982.index.js.map +1 -0
- package/dashboard-dist/api/99.index.js +1176 -0
- package/dashboard-dist/api/99.index.js.map +1 -0
- package/dashboard-dist/api/drizzle/sqlite/0000_short_captain_stacy.sql +70 -0
- package/dashboard-dist/api/drizzle/sqlite/0001_closed_magus.sql +10 -0
- package/dashboard-dist/api/drizzle/sqlite/0002_agent_capability_observation.sql +38 -0
- package/dashboard-dist/api/drizzle/sqlite/0003_auth_magic_link.sql +28 -0
- package/dashboard-dist/api/drizzle/sqlite/0004_static_scan_fields.sql +8 -0
- package/dashboard-dist/api/drizzle/sqlite/0005_gateway_activity.sql +24 -0
- package/dashboard-dist/api/drizzle/sqlite/0006_sour_marauders.sql +41 -0
- package/dashboard-dist/api/drizzle/sqlite/meta/0000_snapshot.json +460 -0
- package/dashboard-dist/api/drizzle/sqlite/meta/0001_snapshot.json +536 -0
- package/dashboard-dist/api/drizzle/sqlite/meta/0006_snapshot.json +1249 -0
- package/dashboard-dist/api/drizzle/sqlite/meta/_journal.json +55 -0
- package/dashboard-dist/api/index.js +28482 -0
- package/dashboard-dist/api/index.js.map +1 -0
- package/dashboard-dist/api/package.json +16 -0
- package/dashboard-dist/api/sourcemap-register.cjs +1 -0
- package/dashboard-dist/web/assets/index-BKUfzbIg.js +148 -0
- package/dashboard-dist/web/assets/index-rHRH99IQ.css +1 -0
- package/dashboard-dist/web/changeway-logo.png +0 -0
- package/dashboard-dist/web/favicon.svg +29 -0
- package/dashboard-dist/web/index.html +15 -0
- package/dashboard-dist/web/logo.svg +16 -0
- package/dist/agent/activation.d.ts +21 -0
- package/dist/agent/activation.d.ts.map +1 -0
- package/dist/agent/activation.js +94 -0
- package/dist/agent/activation.js.map +1 -0
- package/dist/agent/auth.d.ts +73 -0
- package/dist/agent/auth.d.ts.map +1 -0
- package/dist/agent/auth.js +363 -0
- package/dist/agent/auth.js.map +1 -0
- package/dist/agent/behavior-detector.d.ts +150 -0
- package/dist/agent/behavior-detector.d.ts.map +1 -0
- package/dist/agent/behavior-detector.js +559 -0
- package/dist/agent/behavior-detector.js.map +1 -0
- package/dist/agent/business-reporter.d.ts +114 -0
- package/dist/agent/business-reporter.d.ts.map +1 -0
- package/dist/agent/business-reporter.js +359 -0
- package/dist/agent/business-reporter.js.map +1 -0
- package/dist/agent/config-sync.d.ts +70 -0
- package/dist/agent/config-sync.d.ts.map +1 -0
- package/dist/agent/config-sync.js +133 -0
- package/dist/agent/config-sync.js.map +1 -0
- package/dist/agent/config.d.ts +98 -0
- package/dist/agent/config.d.ts.map +1 -0
- package/dist/agent/config.js +348 -0
- package/dist/agent/config.js.map +1 -0
- package/dist/agent/content-injection-scanner.d.ts +35 -0
- package/dist/agent/content-injection-scanner.d.ts.map +1 -0
- package/dist/agent/content-injection-scanner.js +270 -0
- package/dist/agent/content-injection-scanner.js.map +1 -0
- package/dist/agent/engine-log-writer.d.ts +6 -0
- package/dist/agent/engine-log-writer.d.ts.map +1 -0
- package/dist/agent/engine-log-writer.js +18 -0
- package/dist/agent/engine-log-writer.js.map +1 -0
- package/dist/agent/env.d.ts +19 -0
- package/dist/agent/env.d.ts.map +1 -0
- package/dist/agent/env.js +44 -0
- package/dist/agent/env.js.map +1 -0
- package/dist/agent/event-reporter.d.ts +87 -0
- package/dist/agent/event-reporter.d.ts.map +1 -0
- package/dist/agent/event-reporter.js +306 -0
- package/dist/agent/event-reporter.js.map +1 -0
- package/dist/agent/file-watcher.d.ts +50 -0
- package/dist/agent/file-watcher.d.ts.map +1 -0
- package/dist/agent/file-watcher.js +135 -0
- package/dist/agent/file-watcher.js.map +1 -0
- package/dist/agent/fs-utils.d.ts +22 -0
- package/dist/agent/fs-utils.d.ts.map +1 -0
- package/dist/agent/fs-utils.js +41 -0
- package/dist/agent/fs-utils.js.map +1 -0
- package/dist/agent/gateway-manager.d.ts +59 -0
- package/dist/agent/gateway-manager.d.ts.map +1 -0
- package/dist/agent/gateway-manager.js +583 -0
- package/dist/agent/gateway-manager.js.map +1 -0
- package/dist/agent/hook-types.d.ts +276 -0
- package/dist/agent/hook-types.d.ts.map +1 -0
- package/dist/agent/hook-types.js +51 -0
- package/dist/agent/hook-types.js.map +1 -0
- package/dist/agent/http-client.d.ts +19 -0
- package/dist/agent/http-client.d.ts.map +1 -0
- package/dist/agent/http-client.js +37 -0
- package/dist/agent/http-client.js.map +1 -0
- package/dist/agent/index.d.ts +8 -0
- package/dist/agent/index.d.ts.map +1 -0
- package/dist/agent/index.js +8 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/agent/openclaw-hybrid-audit-changeway.js +1447 -0
- package/dist/agent/prompt-gate.d.ts +16 -0
- package/dist/agent/prompt-gate.d.ts.map +1 -0
- package/dist/agent/prompt-gate.js +58 -0
- package/dist/agent/prompt-gate.js.map +1 -0
- package/dist/agent/prompt-input.d.ts +9 -0
- package/dist/agent/prompt-input.d.ts.map +1 -0
- package/dist/agent/prompt-input.js +173 -0
- package/dist/agent/prompt-input.js.map +1 -0
- package/dist/agent/prompt-output.d.ts +4 -0
- package/dist/agent/prompt-output.d.ts.map +1 -0
- package/dist/agent/prompt-output.js +19 -0
- package/dist/agent/prompt-output.js.map +1 -0
- package/dist/agent/runner.d.ts +23 -0
- package/dist/agent/runner.d.ts.map +1 -0
- package/dist/agent/runner.js +165 -0
- package/dist/agent/runner.js.map +1 -0
- package/dist/agent/runtime-mode.d.ts +10 -0
- package/dist/agent/runtime-mode.d.ts.map +1 -0
- package/dist/agent/runtime-mode.js +19 -0
- package/dist/agent/runtime-mode.js.map +1 -0
- package/dist/agent/sanitizer.d.ts +10 -0
- package/dist/agent/sanitizer.d.ts.map +1 -0
- package/dist/agent/sanitizer.js +175 -0
- package/dist/agent/sanitizer.js.map +1 -0
- package/dist/agent/scan-activity.d.ts +19 -0
- package/dist/agent/scan-activity.d.ts.map +1 -0
- package/dist/agent/scan-activity.js +34 -0
- package/dist/agent/scan-activity.js.map +1 -0
- package/dist/agent/types.d.ts +177 -0
- package/dist/agent/types.d.ts.map +1 -0
- package/dist/agent/types.js +5 -0
- package/dist/agent/types.js.map +1 -0
- package/dist/agent/workspace-scanner.d.ts +35 -0
- package/dist/agent/workspace-scanner.d.ts.map +1 -0
- package/dist/agent/workspace-scanner.js +137 -0
- package/dist/agent/workspace-scanner.js.map +1 -0
- package/dist/dashboard-launcher.d.ts +52 -0
- package/dist/dashboard-launcher.d.ts.map +1 -0
- package/dist/dashboard-launcher.js +363 -0
- package/dist/dashboard-launcher.js.map +1 -0
- package/dist/gateway/activity.d.ts +52 -0
- package/dist/gateway/activity.d.ts.map +1 -0
- package/dist/gateway/activity.js +111 -0
- package/dist/gateway/activity.js.map +1 -0
- package/dist/gateway/config.d.ts +50 -0
- package/dist/gateway/config.d.ts.map +1 -0
- package/dist/gateway/config.js +200 -0
- package/dist/gateway/config.js.map +1 -0
- package/dist/gateway/handlers/anthropic.d.ts +12 -0
- package/dist/gateway/handlers/anthropic.d.ts.map +1 -0
- package/dist/gateway/handlers/anthropic.js +254 -0
- package/dist/gateway/handlers/anthropic.js.map +1 -0
- package/dist/gateway/handlers/gemini.d.ts +12 -0
- package/dist/gateway/handlers/gemini.d.ts.map +1 -0
- package/dist/gateway/handlers/gemini.js +101 -0
- package/dist/gateway/handlers/gemini.js.map +1 -0
- package/dist/gateway/handlers/models.d.ts +4 -0
- package/dist/gateway/handlers/models.d.ts.map +1 -0
- package/dist/gateway/handlers/models.js +36 -0
- package/dist/gateway/handlers/models.js.map +1 -0
- package/dist/gateway/handlers/openai.d.ts +16 -0
- package/dist/gateway/handlers/openai.d.ts.map +1 -0
- package/dist/gateway/handlers/openai.js +254 -0
- package/dist/gateway/handlers/openai.js.map +1 -0
- package/dist/gateway/index.d.ts +27 -0
- package/dist/gateway/index.d.ts.map +1 -0
- package/dist/gateway/index.js +290 -0
- package/dist/gateway/index.js.map +1 -0
- package/dist/gateway/mapping-store.d.ts +38 -0
- package/dist/gateway/mapping-store.d.ts.map +1 -0
- package/dist/gateway/mapping-store.js +74 -0
- package/dist/gateway/mapping-store.js.map +1 -0
- package/dist/gateway/restorer.d.ts +63 -0
- package/dist/gateway/restorer.d.ts.map +1 -0
- package/dist/gateway/restorer.js +284 -0
- package/dist/gateway/restorer.js.map +1 -0
- package/dist/gateway/sanitizer.d.ts +17 -0
- package/dist/gateway/sanitizer.d.ts.map +1 -0
- package/dist/gateway/sanitizer.js +228 -0
- package/dist/gateway/sanitizer.js.map +1 -0
- package/dist/gateway/types.d.ts +53 -0
- package/dist/gateway/types.d.ts.map +1 -0
- package/dist/gateway/types.js +5 -0
- package/dist/gateway/types.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2990 -0
- package/dist/index.js.map +1 -0
- package/dist/memory/index.d.ts +5 -0
- package/dist/memory/index.d.ts.map +1 -0
- package/dist/memory/index.js +5 -0
- package/dist/memory/index.js.map +1 -0
- package/dist/memory/store.d.ts +82 -0
- package/dist/memory/store.d.ts.map +1 -0
- package/dist/memory/store.js +194 -0
- package/dist/memory/store.js.map +1 -0
- package/dist/platform-client/index.d.ts +63 -0
- package/dist/platform-client/index.d.ts.map +1 -0
- package/dist/platform-client/index.js +294 -0
- package/dist/platform-client/index.js.map +1 -0
- package/dist/platform-client/types.d.ts +109 -0
- package/dist/platform-client/types.d.ts.map +1 -0
- package/dist/platform-client/types.js +3 -0
- package/dist/platform-client/types.js.map +1 -0
- package/dist/workspace-agents-guide.d.ts +22 -0
- package/dist/workspace-agents-guide.d.ts.map +1 -0
- package/dist/workspace-agents-guide.js +92 -0
- package/dist/workspace-agents-guide.js.map +1 -0
- package/dist/workspace-agents-sync.d.ts +24 -0
- package/dist/workspace-agents-sync.d.ts.map +1 -0
- package/dist/workspace-agents-sync.js +41 -0
- package/dist/workspace-agents-sync.js.map +1 -0
- package/dist/workspace-agents-watcher.d.ts +23 -0
- package/dist/workspace-agents-watcher.d.ts.map +1 -0
- package/dist/workspace-agents-watcher.js +152 -0
- package/dist/workspace-agents-watcher.js.map +1 -0
- package/dist/workspace-discovery.d.ts +11 -0
- package/dist/workspace-discovery.d.ts.map +1 -0
- package/dist/workspace-discovery.js +116 -0
- package/dist/workspace-discovery.js.map +1 -0
- package/gateway/package-lock.json +597 -0
- package/gateway/package.json +57 -0
- package/gateway/pnpm-lock.yaml +342 -0
- package/gateway/src/activity.ts +142 -0
- package/gateway/src/config.ts +246 -0
- package/gateway/src/handlers/anthropic.ts +328 -0
- package/gateway/src/handlers/gemini.ts +122 -0
- package/gateway/src/handlers/models.ts +45 -0
- package/gateway/src/handlers/openai.ts +333 -0
- package/gateway/src/index.ts +344 -0
- package/gateway/src/mapping-store.ts +88 -0
- package/gateway/src/restorer.ts +322 -0
- package/gateway/src/sanitizer.ts +298 -0
- package/gateway/src/types.ts +73 -0
- package/gateway/tsconfig.json +20 -0
- package/openclaw.plugin.json +86 -0
- package/package.json +74 -0
- package/samples/Untitled +1 -0
- package/samples/clean-email.txt +20 -0
- package/samples/test-document.md +53 -0
- package/samples/test-email-popup.txt +44 -0
- package/samples/test-email.txt +32 -0
- package/samples/test-webpage.html +51 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gateway configuration management
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
6
|
+
import { homedir } from "node:os";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import type { GatewayConfig, ApiType } from "./types.js";
|
|
9
|
+
|
|
10
|
+
const DEFAULT_CONFIG_PATH = join(homedir(), ".openclaw", "extensions", "moltguard", "data", "gateway.json");
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Load gateway configuration from file or environment
|
|
14
|
+
*/
|
|
15
|
+
export function loadConfig(configPath?: string): GatewayConfig {
|
|
16
|
+
const path = configPath || DEFAULT_CONFIG_PATH;
|
|
17
|
+
|
|
18
|
+
// Default configuration
|
|
19
|
+
const defaultConfig: GatewayConfig = {
|
|
20
|
+
port: parseInt(process.env.GATEWAY_PORT || "53669", 10),
|
|
21
|
+
backends: {},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Try to load from file
|
|
25
|
+
if (existsSync(path)) {
|
|
26
|
+
try {
|
|
27
|
+
const fileContent = readFileSync(path, "utf-8");
|
|
28
|
+
const fileConfig = JSON.parse(fileContent);
|
|
29
|
+
return mergeConfig(defaultConfig, fileConfig);
|
|
30
|
+
} catch (error) {
|
|
31
|
+
console.warn(
|
|
32
|
+
`[ai-security-gateway] Failed to load config from ${path}:`,
|
|
33
|
+
error,
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Load from environment variables
|
|
39
|
+
return loadFromEnv(defaultConfig);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Load backend configs from environment variables
|
|
44
|
+
*/
|
|
45
|
+
function loadFromEnv(config: GatewayConfig): GatewayConfig {
|
|
46
|
+
// Anthropic
|
|
47
|
+
if (process.env.ANTHROPIC_API_KEY) {
|
|
48
|
+
config.backends.anthropic = {
|
|
49
|
+
baseUrl: process.env.ANTHROPIC_BASE_URL || "https://api.anthropic.com",
|
|
50
|
+
apiKey: process.env.ANTHROPIC_API_KEY,
|
|
51
|
+
type: "anthropic",
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// OpenAI
|
|
56
|
+
if (process.env.OPENAI_API_KEY) {
|
|
57
|
+
config.backends.openai = {
|
|
58
|
+
baseUrl: process.env.OPENAI_BASE_URL || "https://api.openai.com",
|
|
59
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
60
|
+
type: "openai",
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Kimi (Moonshot) — only set if openai backend not already configured
|
|
65
|
+
if (
|
|
66
|
+
(process.env.KIMI_API_KEY || process.env.MOONSHOT_API_KEY) &&
|
|
67
|
+
!config.backends.openai
|
|
68
|
+
) {
|
|
69
|
+
config.backends.kimi = {
|
|
70
|
+
baseUrl: process.env.KIMI_BASE_URL || "https://api.moonshot.cn",
|
|
71
|
+
apiKey: process.env.KIMI_API_KEY || process.env.MOONSHOT_API_KEY || "",
|
|
72
|
+
type: "openai",
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Gemini
|
|
77
|
+
if (process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY) {
|
|
78
|
+
config.backends.gemini = {
|
|
79
|
+
baseUrl:
|
|
80
|
+
process.env.GEMINI_BASE_URL ||
|
|
81
|
+
"https://generativelanguage.googleapis.com",
|
|
82
|
+
apiKey: process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY || "",
|
|
83
|
+
type: "gemini",
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// OpenRouter
|
|
88
|
+
if (process.env.OPENROUTER_API_KEY) {
|
|
89
|
+
config.backends.openrouter = {
|
|
90
|
+
baseUrl: process.env.OPENROUTER_BASE_URL || "https://openrouter.ai/api",
|
|
91
|
+
apiKey: process.env.OPENROUTER_API_KEY,
|
|
92
|
+
type: "openai",
|
|
93
|
+
...(process.env.OPENROUTER_REFERER && {
|
|
94
|
+
referer: process.env.OPENROUTER_REFERER,
|
|
95
|
+
}),
|
|
96
|
+
...(process.env.OPENROUTER_TITLE && {
|
|
97
|
+
title: process.env.OPENROUTER_TITLE,
|
|
98
|
+
}),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return config;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Merge file config with default config
|
|
107
|
+
*/
|
|
108
|
+
function mergeConfig(
|
|
109
|
+
defaultConfig: GatewayConfig,
|
|
110
|
+
fileConfig: Partial<GatewayConfig>,
|
|
111
|
+
): GatewayConfig {
|
|
112
|
+
return {
|
|
113
|
+
port: fileConfig.port ?? defaultConfig.port,
|
|
114
|
+
backends: {
|
|
115
|
+
...defaultConfig.backends,
|
|
116
|
+
...fileConfig.backends,
|
|
117
|
+
},
|
|
118
|
+
routing: fileConfig.routing,
|
|
119
|
+
defaultBackends: fileConfig.defaultBackends,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Validate configuration
|
|
125
|
+
*/
|
|
126
|
+
export function validateConfig(config: GatewayConfig): void {
|
|
127
|
+
if (config.port < 1 || config.port > 65535) {
|
|
128
|
+
throw new Error(`Invalid port: ${config.port}`);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Note: Backends are now optional. Gateway will act as transparent proxy.
|
|
132
|
+
// If no backends configured, gateway will forward requests based on routing rules
|
|
133
|
+
// or pass through to the original target.
|
|
134
|
+
|
|
135
|
+
// Validate each backend (if any)
|
|
136
|
+
for (const [name, backend] of Object.entries(config.backends)) {
|
|
137
|
+
if (!backend.baseUrl) {
|
|
138
|
+
throw new Error(`Backend ${name} missing baseUrl`);
|
|
139
|
+
}
|
|
140
|
+
if (!backend.apiKey) {
|
|
141
|
+
throw new Error(`Backend ${name} missing apiKey`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Infer API type from backend name
|
|
148
|
+
*/
|
|
149
|
+
export function inferApiType(name: string): ApiType {
|
|
150
|
+
const lower = name.toLowerCase();
|
|
151
|
+
if (lower.includes("anthropic") || lower.includes("claude")) {
|
|
152
|
+
return "anthropic";
|
|
153
|
+
}
|
|
154
|
+
if (lower.includes("gemini") || lower.includes("google")) {
|
|
155
|
+
return "gemini";
|
|
156
|
+
}
|
|
157
|
+
// Default to OpenAI-compatible for everything else
|
|
158
|
+
return "openai";
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Get API type for a backend
|
|
163
|
+
*/
|
|
164
|
+
export function getBackendApiType(name: string, config: GatewayConfig): ApiType {
|
|
165
|
+
const backend = config.backends[name];
|
|
166
|
+
if (backend?.type) {
|
|
167
|
+
return backend.type;
|
|
168
|
+
}
|
|
169
|
+
return inferApiType(name);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Find backend by API key
|
|
174
|
+
*/
|
|
175
|
+
export function findBackendByApiKey(
|
|
176
|
+
apiKey: string,
|
|
177
|
+
config: GatewayConfig,
|
|
178
|
+
): { name: string; backend: typeof config.backends[string] } | null {
|
|
179
|
+
for (const [name, backend] of Object.entries(config.backends)) {
|
|
180
|
+
if (backend.apiKey === apiKey) {
|
|
181
|
+
return { name, backend };
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Find default backend for an API type
|
|
189
|
+
*/
|
|
190
|
+
export function findDefaultBackend(
|
|
191
|
+
apiType: ApiType,
|
|
192
|
+
config: GatewayConfig,
|
|
193
|
+
): { name: string; backend: typeof config.backends[string] } | null {
|
|
194
|
+
// Check explicit default first
|
|
195
|
+
const defaultName = config.defaultBackends?.[apiType];
|
|
196
|
+
if (defaultName && config.backends[defaultName]) {
|
|
197
|
+
return { name: defaultName, backend: config.backends[defaultName] };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Find first backend matching the API type
|
|
201
|
+
for (const [name, backend] of Object.entries(config.backends)) {
|
|
202
|
+
if (getBackendApiType(name, config) === apiType) {
|
|
203
|
+
return { name, backend };
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Find backend by request path prefix
|
|
212
|
+
* Matches the longest pathPrefix that is a prefix of the request path
|
|
213
|
+
*/
|
|
214
|
+
export function findBackendByPathPrefix(
|
|
215
|
+
requestPath: string,
|
|
216
|
+
config: GatewayConfig,
|
|
217
|
+
): { name: string; backend: typeof config.backends[string] } | null {
|
|
218
|
+
let bestMatch: { name: string; backend: typeof config.backends[string] } | null = null;
|
|
219
|
+
let bestMatchLength = 0;
|
|
220
|
+
|
|
221
|
+
for (const [name, backend] of Object.entries(config.backends)) {
|
|
222
|
+
if (backend.pathPrefix && requestPath.startsWith(backend.pathPrefix)) {
|
|
223
|
+
if (backend.pathPrefix.length > bestMatchLength) {
|
|
224
|
+
bestMatch = { name, backend };
|
|
225
|
+
bestMatchLength = backend.pathPrefix.length;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return bestMatch;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Find backend by model name
|
|
235
|
+
*/
|
|
236
|
+
export function findBackendByModel(
|
|
237
|
+
modelName: string,
|
|
238
|
+
config: GatewayConfig,
|
|
239
|
+
): { name: string; backend: typeof config.backends[string] } | null {
|
|
240
|
+
for (const [name, backend] of Object.entries(config.backends)) {
|
|
241
|
+
if (backend.models?.includes(modelName)) {
|
|
242
|
+
return { name, backend };
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Security Gateway - Anthropic Messages API handler
|
|
3
|
+
*
|
|
4
|
+
* Handles POST /v1/messages requests in Anthropic's native format.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
8
|
+
import type { BackendConfig, MappingTable } from "../types.js";
|
|
9
|
+
import { sanitize } from "../sanitizer.js";
|
|
10
|
+
import { restore, createStreamRestorer } from "../restorer.js";
|
|
11
|
+
import { generateRequestId, logSanitizeEvent, logRestoreEvent } from "../activity.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Handle Anthropic API request
|
|
15
|
+
*/
|
|
16
|
+
export async function handleAnthropicRequest(
|
|
17
|
+
req: IncomingMessage,
|
|
18
|
+
res: ServerResponse,
|
|
19
|
+
backend: BackendConfig,
|
|
20
|
+
): Promise<void> {
|
|
21
|
+
try {
|
|
22
|
+
const requestId = generateRequestId();
|
|
23
|
+
const sanitizeStart = Date.now();
|
|
24
|
+
|
|
25
|
+
// 1. Parse request body
|
|
26
|
+
const body = await readBody(req);
|
|
27
|
+
const requestData = JSON.parse(body);
|
|
28
|
+
|
|
29
|
+
const {
|
|
30
|
+
model,
|
|
31
|
+
messages,
|
|
32
|
+
system,
|
|
33
|
+
tools,
|
|
34
|
+
max_tokens,
|
|
35
|
+
temperature,
|
|
36
|
+
stream = false,
|
|
37
|
+
...rest
|
|
38
|
+
} = requestData;
|
|
39
|
+
|
|
40
|
+
// 2. Sanitize messages
|
|
41
|
+
const { sanitized: sanitizedMessages, mappingTable, redactionCount } = sanitize(messages);
|
|
42
|
+
|
|
43
|
+
// 3. Sanitize system prompt if present
|
|
44
|
+
let systemRedactionCount = 0;
|
|
45
|
+
const sanitizedSystem = system
|
|
46
|
+
? (() => {
|
|
47
|
+
const result = sanitize(system);
|
|
48
|
+
systemRedactionCount = result.redactionCount;
|
|
49
|
+
return result.sanitized;
|
|
50
|
+
})()
|
|
51
|
+
: system;
|
|
52
|
+
|
|
53
|
+
const totalRedactionCount = redactionCount + systemRedactionCount;
|
|
54
|
+
|
|
55
|
+
// Log sanitization event
|
|
56
|
+
if (totalRedactionCount > 0) {
|
|
57
|
+
logSanitizeEvent({
|
|
58
|
+
requestId,
|
|
59
|
+
backend: "anthropic",
|
|
60
|
+
endpoint: "/v1/messages",
|
|
61
|
+
model,
|
|
62
|
+
mappingTable,
|
|
63
|
+
redactionCount: totalRedactionCount,
|
|
64
|
+
durationMs: Date.now() - sanitizeStart,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Note: We reuse the same mapping table so placeholders are consistent
|
|
69
|
+
|
|
70
|
+
// Debug: log what was sanitized
|
|
71
|
+
if (totalRedactionCount > 0) {
|
|
72
|
+
console.log(`[ai-security-gateway] Sanitized ${totalRedactionCount} items`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 4. Build sanitized request
|
|
76
|
+
const sanitizedRequest = {
|
|
77
|
+
model,
|
|
78
|
+
messages: sanitizedMessages,
|
|
79
|
+
...(system && { system: sanitizedSystem }),
|
|
80
|
+
...(tools && { tools }),
|
|
81
|
+
max_tokens,
|
|
82
|
+
...(temperature !== undefined && { temperature }),
|
|
83
|
+
stream,
|
|
84
|
+
...rest,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// 5. Forward to real Anthropic API
|
|
88
|
+
// Note: baseUrl already includes the full path prefix (e.g., /v1)
|
|
89
|
+
const apiUrl = `${backend.baseUrl}/messages`;
|
|
90
|
+
const response = await fetch(apiUrl, {
|
|
91
|
+
method: "POST",
|
|
92
|
+
headers: {
|
|
93
|
+
"Content-Type": "application/json",
|
|
94
|
+
"anthropic-version": req.headers["anthropic-version"] as string || "2023-06-01",
|
|
95
|
+
"x-api-key": backend.apiKey,
|
|
96
|
+
},
|
|
97
|
+
body: JSON.stringify(sanitizedRequest),
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
if (!response.ok) {
|
|
101
|
+
// Forward error response
|
|
102
|
+
res.writeHead(response.status, { "Content-Type": "application/json" });
|
|
103
|
+
const errorBody = await response.text();
|
|
104
|
+
res.end(errorBody);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// 7. Handle streaming or non-streaming response
|
|
109
|
+
if (stream) {
|
|
110
|
+
await handleAnthropicStream(response, res, mappingTable, requestId, model);
|
|
111
|
+
} else {
|
|
112
|
+
await handleAnthropicNonStream(response, res, mappingTable, requestId, model);
|
|
113
|
+
}
|
|
114
|
+
} catch (error) {
|
|
115
|
+
console.error("[ai-security-gateway] Anthropic handler error:", error);
|
|
116
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
117
|
+
res.end(
|
|
118
|
+
JSON.stringify({
|
|
119
|
+
error: "Internal gateway error",
|
|
120
|
+
message: error instanceof Error ? error.message : String(error),
|
|
121
|
+
}),
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Handle streaming response with smart placeholder restoration
|
|
128
|
+
*
|
|
129
|
+
* Uses StreamRestorer to detect `__` and buffer potential placeholders.
|
|
130
|
+
* Only buffers when necessary, maintaining streaming UX.
|
|
131
|
+
*/
|
|
132
|
+
async function handleAnthropicStream(
|
|
133
|
+
response: Response,
|
|
134
|
+
res: ServerResponse,
|
|
135
|
+
mappingTable: MappingTable,
|
|
136
|
+
requestId: string,
|
|
137
|
+
model?: string,
|
|
138
|
+
): Promise<void> {
|
|
139
|
+
const restoreStart = Date.now();
|
|
140
|
+
|
|
141
|
+
// Debug: log mapping table size
|
|
142
|
+
if (mappingTable.size > 0) {
|
|
143
|
+
console.log(`[ai-security-gateway] Streaming with ${mappingTable.size} placeholders to restore`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Set SSE headers
|
|
147
|
+
res.writeHead(200, {
|
|
148
|
+
"Content-Type": "text/event-stream",
|
|
149
|
+
"Cache-Control": "no-cache",
|
|
150
|
+
"Connection": "keep-alive",
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const reader = response.body?.getReader();
|
|
154
|
+
if (!reader) {
|
|
155
|
+
res.end();
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const decoder = new TextDecoder();
|
|
160
|
+
let lineBuffer = "";
|
|
161
|
+
|
|
162
|
+
// Create stream restorer for text content
|
|
163
|
+
const streamRestorer = createStreamRestorer(mappingTable);
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
while (true) {
|
|
167
|
+
const { done, value } = await reader.read();
|
|
168
|
+
if (done) break;
|
|
169
|
+
|
|
170
|
+
// Decode chunk
|
|
171
|
+
lineBuffer += decoder.decode(value, { stream: true });
|
|
172
|
+
|
|
173
|
+
// Process complete lines
|
|
174
|
+
const lines = lineBuffer.split("\n");
|
|
175
|
+
lineBuffer = lines.pop() || ""; // Keep incomplete line in buffer
|
|
176
|
+
|
|
177
|
+
for (const line of lines) {
|
|
178
|
+
if (!line.trim()) {
|
|
179
|
+
res.write("\n");
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Handle event lines (pass through)
|
|
184
|
+
if (line.startsWith("event:")) {
|
|
185
|
+
res.write(line + "\n");
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Handle data lines
|
|
190
|
+
if (!line.startsWith("data: ")) {
|
|
191
|
+
res.write(line + "\n");
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const dataContent = line.slice(6);
|
|
196
|
+
|
|
197
|
+
try {
|
|
198
|
+
const parsed = JSON.parse(dataContent) as AnthropicSSEChunk;
|
|
199
|
+
|
|
200
|
+
// Check for text delta
|
|
201
|
+
if (parsed.type === "content_block_delta" && parsed.delta?.type === "text_delta") {
|
|
202
|
+
const textContent = parsed.delta.text;
|
|
203
|
+
|
|
204
|
+
if (textContent !== undefined && mappingTable.size > 0) {
|
|
205
|
+
// Process text through stream restorer
|
|
206
|
+
const restored = streamRestorer.process(textContent);
|
|
207
|
+
|
|
208
|
+
if (restored.length > 0) {
|
|
209
|
+
// We have restorable content - output it
|
|
210
|
+
const restoredChunk = {
|
|
211
|
+
...parsed,
|
|
212
|
+
delta: { ...parsed.delta, text: restored },
|
|
213
|
+
};
|
|
214
|
+
res.write(`data: ${JSON.stringify(restoredChunk)}\n`);
|
|
215
|
+
}
|
|
216
|
+
// If restorer is buffering, don't output anything yet
|
|
217
|
+
} else {
|
|
218
|
+
// No text content or no mappings - pass through
|
|
219
|
+
res.write(line + "\n");
|
|
220
|
+
}
|
|
221
|
+
} else {
|
|
222
|
+
// Non-text events - pass through
|
|
223
|
+
res.write(line + "\n");
|
|
224
|
+
}
|
|
225
|
+
} catch {
|
|
226
|
+
// Not valid JSON, pass through
|
|
227
|
+
res.write(line + "\n");
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Write any remaining line buffer
|
|
233
|
+
if (lineBuffer.trim()) {
|
|
234
|
+
res.write(lineBuffer + "\n");
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Finalize stream restorer - flush any remaining buffered content
|
|
238
|
+
const finalContent = streamRestorer.finalize();
|
|
239
|
+
if (finalContent.length > 0) {
|
|
240
|
+
// Create a final text delta chunk with remaining content
|
|
241
|
+
const finalChunk: AnthropicSSEChunk = {
|
|
242
|
+
type: "content_block_delta",
|
|
243
|
+
index: 0,
|
|
244
|
+
delta: { type: "text_delta", text: finalContent },
|
|
245
|
+
};
|
|
246
|
+
res.write(`data: ${JSON.stringify(finalChunk)}\n`);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Log restoration event
|
|
250
|
+
if (mappingTable.size > 0) {
|
|
251
|
+
logRestoreEvent({
|
|
252
|
+
requestId,
|
|
253
|
+
backend: "anthropic",
|
|
254
|
+
endpoint: "/v1/messages",
|
|
255
|
+
model,
|
|
256
|
+
mappingTable,
|
|
257
|
+
restorationCount: mappingTable.size,
|
|
258
|
+
durationMs: Date.now() - restoreStart,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
res.end();
|
|
263
|
+
} catch (error) {
|
|
264
|
+
console.error("[ai-security-gateway] Stream error:", error);
|
|
265
|
+
res.end();
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Anthropic SSE chunk structure
|
|
271
|
+
*/
|
|
272
|
+
interface AnthropicSSEChunk {
|
|
273
|
+
type: string;
|
|
274
|
+
index?: number;
|
|
275
|
+
delta?: {
|
|
276
|
+
type: string;
|
|
277
|
+
text?: string;
|
|
278
|
+
};
|
|
279
|
+
[key: string]: unknown;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Handle non-streaming response
|
|
284
|
+
*/
|
|
285
|
+
async function handleAnthropicNonStream(
|
|
286
|
+
response: Response,
|
|
287
|
+
res: ServerResponse,
|
|
288
|
+
mappingTable: MappingTable,
|
|
289
|
+
requestId: string,
|
|
290
|
+
model?: string,
|
|
291
|
+
): Promise<void> {
|
|
292
|
+
const restoreStart = Date.now();
|
|
293
|
+
const responseBody = await response.text();
|
|
294
|
+
const responseData = JSON.parse(responseBody);
|
|
295
|
+
|
|
296
|
+
// Restore placeholders in response
|
|
297
|
+
const restoredData = restore(responseData, mappingTable);
|
|
298
|
+
|
|
299
|
+
// Log restoration event
|
|
300
|
+
if (mappingTable.size > 0) {
|
|
301
|
+
logRestoreEvent({
|
|
302
|
+
requestId,
|
|
303
|
+
backend: "anthropic",
|
|
304
|
+
endpoint: "/v1/messages",
|
|
305
|
+
model,
|
|
306
|
+
mappingTable,
|
|
307
|
+
restorationCount: mappingTable.size,
|
|
308
|
+
durationMs: Date.now() - restoreStart,
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
313
|
+
res.end(JSON.stringify(restoredData));
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Read request body as string
|
|
318
|
+
*/
|
|
319
|
+
function readBody(req: IncomingMessage): Promise<string> {
|
|
320
|
+
return new Promise((resolve, reject) => {
|
|
321
|
+
let body = "";
|
|
322
|
+
req.on("data", (chunk) => {
|
|
323
|
+
body += chunk.toString();
|
|
324
|
+
});
|
|
325
|
+
req.on("end", () => resolve(body));
|
|
326
|
+
req.on("error", reject);
|
|
327
|
+
});
|
|
328
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Security Gateway - Google Gemini API handler
|
|
3
|
+
*
|
|
4
|
+
* Handles POST /v1/models/:model:generateContent requests in Gemini's format.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
8
|
+
import type { BackendConfig, MappingTable } from "../types.js";
|
|
9
|
+
import { sanitize } from "../sanitizer.js";
|
|
10
|
+
import { restore } from "../restorer.js";
|
|
11
|
+
import { generateRequestId, logSanitizeEvent, logRestoreEvent } from "../activity.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Handle Gemini API request
|
|
15
|
+
*/
|
|
16
|
+
export async function handleGeminiRequest(
|
|
17
|
+
req: IncomingMessage,
|
|
18
|
+
res: ServerResponse,
|
|
19
|
+
backend: BackendConfig,
|
|
20
|
+
modelName: string,
|
|
21
|
+
): Promise<void> {
|
|
22
|
+
try {
|
|
23
|
+
const requestId = generateRequestId();
|
|
24
|
+
const sanitizeStart = Date.now();
|
|
25
|
+
|
|
26
|
+
// 1. Parse request body
|
|
27
|
+
const body = await readBody(req);
|
|
28
|
+
const requestData = JSON.parse(body);
|
|
29
|
+
|
|
30
|
+
const { contents, tools, generationConfig, ...rest } = requestData;
|
|
31
|
+
|
|
32
|
+
// 2. Sanitize contents (Gemini uses "contents" instead of "messages")
|
|
33
|
+
const { sanitized: sanitizedContents, mappingTable, redactionCount } = sanitize(contents);
|
|
34
|
+
|
|
35
|
+
// Log sanitization event
|
|
36
|
+
if (redactionCount > 0) {
|
|
37
|
+
logSanitizeEvent({
|
|
38
|
+
requestId,
|
|
39
|
+
backend: "gemini",
|
|
40
|
+
endpoint: `/v1/models/${modelName}:generateContent`,
|
|
41
|
+
model: modelName,
|
|
42
|
+
mappingTable,
|
|
43
|
+
redactionCount,
|
|
44
|
+
durationMs: Date.now() - sanitizeStart,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 3. Build sanitized request
|
|
49
|
+
const sanitizedRequest = {
|
|
50
|
+
contents: sanitizedContents,
|
|
51
|
+
...(tools && { tools }),
|
|
52
|
+
...(generationConfig && { generationConfig }),
|
|
53
|
+
...rest,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// 4. Forward to Gemini API
|
|
57
|
+
const apiUrl = `${backend.baseUrl}/v1/models/${modelName}:generateContent`;
|
|
58
|
+
const response = await fetch(apiUrl, {
|
|
59
|
+
method: "POST",
|
|
60
|
+
headers: {
|
|
61
|
+
"Content-Type": "application/json",
|
|
62
|
+
"x-goog-api-key": backend.apiKey,
|
|
63
|
+
},
|
|
64
|
+
body: JSON.stringify(sanitizedRequest),
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
if (!response.ok) {
|
|
68
|
+
// Forward error response
|
|
69
|
+
res.writeHead(response.status, { "Content-Type": "application/json" });
|
|
70
|
+
const errorBody = await response.text();
|
|
71
|
+
res.end(errorBody);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 6. Handle response (Gemini typically doesn't stream in same way)
|
|
76
|
+
const restoreStart = Date.now();
|
|
77
|
+
const responseBody = await response.text();
|
|
78
|
+
const responseData = JSON.parse(responseBody);
|
|
79
|
+
|
|
80
|
+
// Restore placeholders in response
|
|
81
|
+
const restoredData = restore(responseData, mappingTable);
|
|
82
|
+
|
|
83
|
+
// Log restoration event
|
|
84
|
+
if (mappingTable.size > 0) {
|
|
85
|
+
logRestoreEvent({
|
|
86
|
+
requestId,
|
|
87
|
+
backend: "gemini",
|
|
88
|
+
endpoint: `/v1/models/${modelName}:generateContent`,
|
|
89
|
+
model: modelName,
|
|
90
|
+
mappingTable,
|
|
91
|
+
restorationCount: mappingTable.size,
|
|
92
|
+
durationMs: Date.now() - restoreStart,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
97
|
+
res.end(JSON.stringify(restoredData));
|
|
98
|
+
} catch (error) {
|
|
99
|
+
console.error("[ai-security-gateway] Gemini handler error:", error);
|
|
100
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
101
|
+
res.end(
|
|
102
|
+
JSON.stringify({
|
|
103
|
+
error: "Internal gateway error",
|
|
104
|
+
message: error instanceof Error ? error.message : String(error),
|
|
105
|
+
}),
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Read request body as string
|
|
112
|
+
*/
|
|
113
|
+
function readBody(req: IncomingMessage): Promise<string> {
|
|
114
|
+
return new Promise((resolve, reject) => {
|
|
115
|
+
let body = "";
|
|
116
|
+
req.on("data", (chunk) => {
|
|
117
|
+
body += chunk.toString();
|
|
118
|
+
});
|
|
119
|
+
req.on("end", () => resolve(body));
|
|
120
|
+
req.on("error", reject);
|
|
121
|
+
});
|
|
122
|
+
}
|