@congzhen/changewayguard 6.8.12
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/README.md +270 -0
- package/dashboard-dist/api/104.index.js +1420 -0
- package/dashboard-dist/api/104.index.js.map +1 -0
- package/dashboard-dist/api/113.index.js +496 -0
- package/dashboard-dist/api/113.index.js.map +1 -0
- package/dashboard-dist/api/18.index.js +67 -0
- package/dashboard-dist/api/18.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/25.index.js +3562 -0
- package/dashboard-dist/api/25.index.js.map +1 -0
- package/dashboard-dist/api/280.index.js +206 -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/377.index.js +1176 -0
- package/dashboard-dist/api/377.index.js.map +1 -0
- package/dashboard-dist/api/411.index.js +4250 -0
- package/dashboard-dist/api/411.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/573.index.js +806 -0
- package/dashboard-dist/api/573.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/62.index.js +4151 -0
- package/dashboard-dist/api/62.index.js.map +1 -0
- package/dashboard-dist/api/67.index.js +23383 -0
- package/dashboard-dist/api/67.index.js.map +1 -0
- package/dashboard-dist/api/678.index.js +2734 -0
- package/dashboard-dist/api/678.index.js.map +1 -0
- package/dashboard-dist/api/698.index.js +1896 -0
- package/dashboard-dist/api/698.index.js.map +1 -0
- package/dashboard-dist/api/720.index.js +98 -0
- package/dashboard-dist/api/720.index.js.map +1 -0
- package/dashboard-dist/api/830.index.js +95 -0
- package/dashboard-dist/api/830.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 +65 -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/948.index.js +64 -0
- package/dashboard-dist/api/948.index.js.map +1 -0
- package/dashboard-dist/api/953.index.js +67 -0
- package/dashboard-dist/api/953.index.js.map +1 -0
- package/dashboard-dist/api/975.index.js +374 -0
- package/dashboard-dist/api/975.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 +27340 -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-CqWIeBTD.js +158 -0
- package/dashboard-dist/web/assets/index-Dw7--9q4.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 +14 -0
- package/dashboard-dist/web/logo.svg +16 -0
- package/dist/agent/auth.d.ts +37 -0
- package/dist/agent/auth.d.ts.map +1 -0
- package/dist/agent/auth.js +151 -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 +573 -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 +97 -0
- package/dist/agent/config.d.ts.map +1 -0
- package/dist/agent/config.js +359 -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 +43 -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 +315 -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/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/prompt-gate.d.ts +13 -0
- package/dist/agent/prompt-gate.d.ts.map +1 -0
- package/dist/agent/prompt-gate.js +28 -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 +158 -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 +154 -0
- package/dist/agent/runner.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 +18 -0
- package/dist/agent/scan-activity.d.ts.map +1 -0
- package/dist/agent/scan-activity.js +32 -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/gateway/activity.d.ts +52 -0
- package/dist/gateway/gateway/activity.d.ts.map +1 -0
- package/dist/gateway/gateway/activity.js +111 -0
- package/dist/gateway/gateway/activity.js.map +1 -0
- package/dist/gateway/gateway/config.d.ts +50 -0
- package/dist/gateway/gateway/config.d.ts.map +1 -0
- package/dist/gateway/gateway/config.js +200 -0
- package/dist/gateway/gateway/config.js.map +1 -0
- package/dist/gateway/gateway/handlers/anthropic.d.ts +12 -0
- package/dist/gateway/gateway/handlers/anthropic.d.ts.map +1 -0
- package/dist/gateway/gateway/handlers/anthropic.js +254 -0
- package/dist/gateway/gateway/handlers/anthropic.js.map +1 -0
- package/dist/gateway/gateway/handlers/gemini.d.ts +12 -0
- package/dist/gateway/gateway/handlers/gemini.d.ts.map +1 -0
- package/dist/gateway/gateway/handlers/gemini.js +101 -0
- package/dist/gateway/gateway/handlers/gemini.js.map +1 -0
- package/dist/gateway/gateway/handlers/models.d.ts +4 -0
- package/dist/gateway/gateway/handlers/models.d.ts.map +1 -0
- package/dist/gateway/gateway/handlers/models.js +36 -0
- package/dist/gateway/gateway/handlers/models.js.map +1 -0
- package/dist/gateway/gateway/handlers/openai.d.ts +16 -0
- package/dist/gateway/gateway/handlers/openai.d.ts.map +1 -0
- package/dist/gateway/gateway/handlers/openai.js +254 -0
- package/dist/gateway/gateway/handlers/openai.js.map +1 -0
- package/dist/gateway/gateway/index.d.ts +27 -0
- package/dist/gateway/gateway/index.d.ts.map +1 -0
- package/dist/gateway/gateway/index.js +293 -0
- package/dist/gateway/gateway/index.js.map +1 -0
- package/dist/gateway/gateway/mapping-store.d.ts +38 -0
- package/dist/gateway/gateway/mapping-store.d.ts.map +1 -0
- package/dist/gateway/gateway/mapping-store.js +74 -0
- package/dist/gateway/gateway/mapping-store.js.map +1 -0
- package/dist/gateway/gateway/restorer.d.ts +63 -0
- package/dist/gateway/gateway/restorer.d.ts.map +1 -0
- package/dist/gateway/gateway/restorer.js +284 -0
- package/dist/gateway/gateway/restorer.js.map +1 -0
- package/dist/gateway/gateway/sanitizer.d.ts +17 -0
- package/dist/gateway/gateway/sanitizer.d.ts.map +1 -0
- package/dist/gateway/gateway/sanitizer.js +228 -0
- package/dist/gateway/gateway/sanitizer.js.map +1 -0
- package/dist/gateway/gateway/types.d.ts +53 -0
- package/dist/gateway/gateway/types.d.ts.map +1 -0
- package/dist/gateway/gateway/types.js +5 -0
- package/dist/gateway/gateway/types.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 +293 -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 +2084 -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/gateway/activity.d.ts +52 -0
- package/gateway/activity.d.ts.map +1 -0
- package/gateway/activity.js +111 -0
- package/gateway/activity.js.map +1 -0
- package/gateway/config.d.ts +50 -0
- package/gateway/config.d.ts.map +1 -0
- package/gateway/config.js +200 -0
- package/gateway/config.js.map +1 -0
- package/gateway/handlers/anthropic.d.ts +12 -0
- package/gateway/handlers/anthropic.d.ts.map +1 -0
- package/gateway/handlers/anthropic.js +254 -0
- package/gateway/handlers/anthropic.js.map +1 -0
- package/gateway/handlers/gemini.d.ts +12 -0
- package/gateway/handlers/gemini.d.ts.map +1 -0
- package/gateway/handlers/gemini.js +101 -0
- package/gateway/handlers/gemini.js.map +1 -0
- package/gateway/handlers/models.d.ts +4 -0
- package/gateway/handlers/models.d.ts.map +1 -0
- package/gateway/handlers/models.js +36 -0
- package/gateway/handlers/models.js.map +1 -0
- package/gateway/handlers/openai.d.ts +16 -0
- package/gateway/handlers/openai.d.ts.map +1 -0
- package/gateway/handlers/openai.js +254 -0
- package/gateway/handlers/openai.js.map +1 -0
- package/gateway/index.d.ts +27 -0
- package/gateway/index.d.ts.map +1 -0
- package/gateway/index.js +293 -0
- package/gateway/index.js.map +1 -0
- package/gateway/mapping-store.d.ts +38 -0
- package/gateway/mapping-store.d.ts.map +1 -0
- package/gateway/mapping-store.js +74 -0
- package/gateway/mapping-store.js.map +1 -0
- package/gateway/restorer.d.ts +63 -0
- package/gateway/restorer.d.ts.map +1 -0
- package/gateway/restorer.js +284 -0
- package/gateway/restorer.js.map +1 -0
- package/gateway/sanitizer.d.ts +17 -0
- package/gateway/sanitizer.d.ts.map +1 -0
- package/gateway/sanitizer.js +228 -0
- package/gateway/sanitizer.js.map +1 -0
- package/gateway/types.d.ts +53 -0
- package/gateway/types.d.ts.map +1 -0
- package/gateway/types.js +5 -0
- package/gateway/types.js.map +1 -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
- package/scripts/enterprise-enroll.sh +89 -0
- package/scripts/enterprise-unenroll.sh +75 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { findDefaultBackend } from "../config.js";
|
|
2
|
+
export async function handleModelsRequest(res, config) {
|
|
3
|
+
try {
|
|
4
|
+
// Find an OpenAI-compatible backend for models listing
|
|
5
|
+
const resolved = findDefaultBackend("openai", config);
|
|
6
|
+
if (!resolved) {
|
|
7
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
8
|
+
res.end(JSON.stringify({ error: "No OpenAI-compatible backend configured" }));
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
const { backend } = resolved;
|
|
12
|
+
const modelsUrl = `${backend.baseUrl}/v1/models`;
|
|
13
|
+
const headers = {
|
|
14
|
+
"Authorization": `Bearer ${backend.apiKey}`,
|
|
15
|
+
};
|
|
16
|
+
if (backend.referer) {
|
|
17
|
+
headers["HTTP-Referer"] = backend.referer;
|
|
18
|
+
}
|
|
19
|
+
if (backend.title) {
|
|
20
|
+
headers["X-Title"] = backend.title;
|
|
21
|
+
}
|
|
22
|
+
const response = await fetch(modelsUrl, { headers });
|
|
23
|
+
const body = await response.text();
|
|
24
|
+
res.writeHead(response.status, { "Content-Type": "application/json" });
|
|
25
|
+
res.end(body);
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
console.error("[ai-security-gateway] Models request error:", error);
|
|
29
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
30
|
+
res.end(JSON.stringify({
|
|
31
|
+
error: "Internal gateway error",
|
|
32
|
+
message: error instanceof Error ? error.message : String(error),
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=models.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"models.js","sourceRoot":"","sources":["../../src/handlers/models.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAqB,MAAM,cAAc,CAAC;AAErE,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,GAAmB,EACnB,MAAqB;IAErB,IAAI,CAAC;QACH,uDAAuD;QACvD,MAAM,QAAQ,GAAG,kBAAkB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QACtD,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;YAC3D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,yCAAyC,EAAE,CAAC,CAAC,CAAC;YAC9E,OAAO;QACT,CAAC;QAED,MAAM,EAAE,OAAO,EAAE,GAAG,QAAQ,CAAC;QAC7B,MAAM,SAAS,GAAG,GAAG,OAAO,CAAC,OAAO,YAAY,CAAC;QACjD,MAAM,OAAO,GAA2B;YACtC,eAAe,EAAE,UAAU,OAAO,CAAC,MAAM,EAAE;SAC5C,CAAC;QAEF,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;YACpB,OAAO,CAAC,cAAc,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;QAC5C,CAAC;QACD,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;YAClB,OAAO,CAAC,SAAS,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC;QACrC,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,SAAS,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;QACrD,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QACnC,GAAG,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;QACvE,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAChB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,6CAA6C,EAAE,KAAK,CAAC,CAAC;QACpE,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;QAC3D,GAAG,CAAC,GAAG,CACL,IAAI,CAAC,SAAS,CAAC;YACb,KAAK,EAAE,wBAAwB;YAC/B,OAAO,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;SAChE,CAAC,CACH,CAAC;IACJ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Security Gateway - OpenAI Chat Completions API handler
|
|
3
|
+
*
|
|
4
|
+
* Handles POST /v1/chat/completions requests in OpenAI's format.
|
|
5
|
+
* Also compatible with OpenAI-compatible APIs (Kimi, DeepSeek, etc.)
|
|
6
|
+
*/
|
|
7
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
8
|
+
import type { BackendConfig } from "../types.js";
|
|
9
|
+
/**
|
|
10
|
+
* Handle OpenAI API request
|
|
11
|
+
*
|
|
12
|
+
* @param backend - Config for OpenAI-compatible backend
|
|
13
|
+
* @param extraHeaders - Optional additional headers (e.g., OpenRouter attribution)
|
|
14
|
+
*/
|
|
15
|
+
export declare function handleOpenAIRequest(req: IncomingMessage, res: ServerResponse, backend: BackendConfig, extraHeaders?: Record<string, string>): Promise<void>;
|
|
16
|
+
//# sourceMappingURL=openai.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai.d.ts","sourceRoot":"","sources":["../../src/handlers/openai.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACjE,OAAO,KAAK,EAAE,aAAa,EAAgB,MAAM,aAAa,CAAC;AAK/D;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,OAAO,EAAE,aAAa,EACtB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACpC,OAAO,CAAC,IAAI,CAAC,CAiGf"}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Security Gateway - OpenAI Chat Completions API handler
|
|
3
|
+
*
|
|
4
|
+
* Handles POST /v1/chat/completions requests in OpenAI's format.
|
|
5
|
+
* Also compatible with OpenAI-compatible APIs (Kimi, DeepSeek, etc.)
|
|
6
|
+
*/
|
|
7
|
+
import { sanitize } from "../sanitizer.js";
|
|
8
|
+
import { restore, createStreamRestorer } from "../restorer.js";
|
|
9
|
+
import { generateRequestId, logSanitizeEvent, logRestoreEvent } from "../activity.js";
|
|
10
|
+
/**
|
|
11
|
+
* Handle OpenAI API request
|
|
12
|
+
*
|
|
13
|
+
* @param backend - Config for OpenAI-compatible backend
|
|
14
|
+
* @param extraHeaders - Optional additional headers (e.g., OpenRouter attribution)
|
|
15
|
+
*/
|
|
16
|
+
export async function handleOpenAIRequest(req, res, backend, extraHeaders) {
|
|
17
|
+
try {
|
|
18
|
+
const requestId = generateRequestId();
|
|
19
|
+
const sanitizeStart = Date.now();
|
|
20
|
+
// 1. Parse request body
|
|
21
|
+
const body = await readBody(req);
|
|
22
|
+
const requestData = JSON.parse(body);
|
|
23
|
+
const { model, messages, tools, tool_choice, temperature, max_tokens, stream = false, ...rest } = requestData;
|
|
24
|
+
// 2. Sanitize messages
|
|
25
|
+
const { sanitized: sanitizedMessages, mappingTable, redactionCount } = sanitize(messages);
|
|
26
|
+
// Debug: log what was sanitized
|
|
27
|
+
console.log(`[ai-security-gateway] Sanitized ${redactionCount} items`);
|
|
28
|
+
if (mappingTable.size > 0) {
|
|
29
|
+
for (const [placeholder, original] of mappingTable.entries()) {
|
|
30
|
+
console.log(`[ai-security-gateway] ${placeholder} <- (${original.length} chars)`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// Log sanitization event
|
|
34
|
+
if (redactionCount > 0) {
|
|
35
|
+
logSanitizeEvent({
|
|
36
|
+
requestId,
|
|
37
|
+
backend: "openai",
|
|
38
|
+
endpoint: "/v1/chat/completions",
|
|
39
|
+
model,
|
|
40
|
+
mappingTable,
|
|
41
|
+
redactionCount,
|
|
42
|
+
durationMs: Date.now() - sanitizeStart,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
// 3. Build sanitized request
|
|
46
|
+
const sanitizedRequest = {
|
|
47
|
+
model,
|
|
48
|
+
messages: sanitizedMessages,
|
|
49
|
+
...(tools && { tools }),
|
|
50
|
+
...(tool_choice && { tool_choice }),
|
|
51
|
+
...(temperature !== undefined && { temperature }),
|
|
52
|
+
...(max_tokens && { max_tokens }),
|
|
53
|
+
stream,
|
|
54
|
+
...rest,
|
|
55
|
+
};
|
|
56
|
+
// 4. Use provided backend config
|
|
57
|
+
// Note: baseUrl already includes the full path prefix (e.g., /v1 or /v1/coding)
|
|
58
|
+
const apiUrl = `${backend.baseUrl}/chat/completions`;
|
|
59
|
+
const headers = {
|
|
60
|
+
"Content-Type": "application/json",
|
|
61
|
+
"Authorization": `Bearer ${backend.apiKey}`,
|
|
62
|
+
};
|
|
63
|
+
// Merge extra headers (e.g., OpenRouter attribution headers)
|
|
64
|
+
if (extraHeaders) {
|
|
65
|
+
Object.assign(headers, extraHeaders);
|
|
66
|
+
}
|
|
67
|
+
const response = await fetch(apiUrl, {
|
|
68
|
+
method: "POST",
|
|
69
|
+
headers,
|
|
70
|
+
body: JSON.stringify(sanitizedRequest),
|
|
71
|
+
});
|
|
72
|
+
if (!response.ok) {
|
|
73
|
+
// Forward error response
|
|
74
|
+
res.writeHead(response.status, { "Content-Type": "application/json" });
|
|
75
|
+
const errorBody = await response.text();
|
|
76
|
+
res.end(errorBody);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
// 6. Handle streaming or non-streaming response
|
|
80
|
+
if (stream) {
|
|
81
|
+
await handleOpenAIStream(response, res, mappingTable, requestId, model);
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
await handleOpenAINonStream(response, res, mappingTable, requestId, model);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
console.error("[ai-security-gateway] OpenAI handler error:", error);
|
|
89
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
90
|
+
res.end(JSON.stringify({
|
|
91
|
+
error: "Internal gateway error",
|
|
92
|
+
message: error instanceof Error ? error.message : String(error),
|
|
93
|
+
}));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Handle streaming response (SSE) with smart placeholder restoration
|
|
98
|
+
*
|
|
99
|
+
* Uses StreamRestorer to detect `__` and buffer potential placeholders.
|
|
100
|
+
* Only buffers when necessary, maintaining streaming UX.
|
|
101
|
+
*/
|
|
102
|
+
async function handleOpenAIStream(response, res, mappingTable, requestId, model) {
|
|
103
|
+
const restoreStart = Date.now();
|
|
104
|
+
// Debug: log mapping table
|
|
105
|
+
if (mappingTable.size > 0) {
|
|
106
|
+
console.log(`[ai-security-gateway] Streaming with ${mappingTable.size} placeholders to restore`);
|
|
107
|
+
}
|
|
108
|
+
// Set SSE headers
|
|
109
|
+
res.writeHead(200, {
|
|
110
|
+
"Content-Type": "text/event-stream",
|
|
111
|
+
"Cache-Control": "no-cache",
|
|
112
|
+
"Connection": "keep-alive",
|
|
113
|
+
});
|
|
114
|
+
const reader = response.body?.getReader();
|
|
115
|
+
if (!reader) {
|
|
116
|
+
res.end();
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const decoder = new TextDecoder();
|
|
120
|
+
let lineBuffer = "";
|
|
121
|
+
// Create stream restorer for text content
|
|
122
|
+
const streamRestorer = createStreamRestorer(mappingTable);
|
|
123
|
+
// Buffer for SSE chunks waiting for restoration
|
|
124
|
+
const pendingChunks = [];
|
|
125
|
+
try {
|
|
126
|
+
while (true) {
|
|
127
|
+
const { done, value } = await reader.read();
|
|
128
|
+
if (done)
|
|
129
|
+
break;
|
|
130
|
+
// Decode chunk
|
|
131
|
+
lineBuffer += decoder.decode(value, { stream: true });
|
|
132
|
+
// Process complete lines
|
|
133
|
+
const lines = lineBuffer.split("\n");
|
|
134
|
+
lineBuffer = lines.pop() || ""; // Keep incomplete line in buffer
|
|
135
|
+
for (const line of lines) {
|
|
136
|
+
if (!line.trim()) {
|
|
137
|
+
// Flush pending chunks before empty line
|
|
138
|
+
flushPendingChunks(pendingChunks, streamRestorer, res);
|
|
139
|
+
res.write("\n");
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (!line.startsWith("data: ")) {
|
|
143
|
+
res.write(line + "\n");
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
const dataContent = line.slice(6);
|
|
147
|
+
if (dataContent === "[DONE]") {
|
|
148
|
+
// Finalize any pending content
|
|
149
|
+
flushPendingChunks(pendingChunks, streamRestorer, res);
|
|
150
|
+
res.write(line + "\n");
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
try {
|
|
154
|
+
const parsed = JSON.parse(dataContent);
|
|
155
|
+
const textContent = parsed.choices?.[0]?.delta?.content;
|
|
156
|
+
if (textContent !== undefined && mappingTable.size > 0) {
|
|
157
|
+
// Process text through stream restorer
|
|
158
|
+
const restored = streamRestorer.process(textContent);
|
|
159
|
+
if (restored.length > 0) {
|
|
160
|
+
// We have restorable content - flush it
|
|
161
|
+
const restoredChunk = { ...parsed };
|
|
162
|
+
restoredChunk.choices = parsed.choices.map((c, i) => i === 0 ? { ...c, delta: { ...c.delta, content: restored } } : c);
|
|
163
|
+
res.write(`data: ${JSON.stringify(restoredChunk)}\n`);
|
|
164
|
+
}
|
|
165
|
+
// If restorer is buffering, we don't output anything yet
|
|
166
|
+
// Content will be output when buffer is flushed
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
// No text content or no mappings - pass through
|
|
170
|
+
res.write(line + "\n");
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
// Not valid JSON, pass through
|
|
175
|
+
res.write(line + "\n");
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// Write any remaining line buffer
|
|
180
|
+
if (lineBuffer.trim()) {
|
|
181
|
+
res.write(lineBuffer + "\n");
|
|
182
|
+
}
|
|
183
|
+
// Finalize stream restorer - flush any remaining buffered content
|
|
184
|
+
const finalContent = streamRestorer.finalize();
|
|
185
|
+
if (finalContent.length > 0) {
|
|
186
|
+
// Create a final chunk with remaining content
|
|
187
|
+
const finalChunk = {
|
|
188
|
+
choices: [{ delta: { content: finalContent }, index: 0, finish_reason: null }],
|
|
189
|
+
};
|
|
190
|
+
res.write(`data: ${JSON.stringify(finalChunk)}\n`);
|
|
191
|
+
}
|
|
192
|
+
// Log restoration event
|
|
193
|
+
if (mappingTable.size > 0) {
|
|
194
|
+
logRestoreEvent({
|
|
195
|
+
requestId,
|
|
196
|
+
backend: "openai",
|
|
197
|
+
endpoint: "/v1/chat/completions",
|
|
198
|
+
model,
|
|
199
|
+
mappingTable,
|
|
200
|
+
restorationCount: mappingTable.size,
|
|
201
|
+
durationMs: Date.now() - restoreStart,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
res.end();
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
console.error("[ai-security-gateway] Stream error:", error);
|
|
208
|
+
res.end();
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Flush pending chunks with restored content
|
|
213
|
+
*/
|
|
214
|
+
function flushPendingChunks(_pendingChunks, _streamRestorer, _res) {
|
|
215
|
+
// Currently unused - StreamRestorer handles buffering internally
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Handle non-streaming response
|
|
219
|
+
*/
|
|
220
|
+
async function handleOpenAINonStream(response, res, mappingTable, requestId, model) {
|
|
221
|
+
const restoreStart = Date.now();
|
|
222
|
+
const responseBody = await response.text();
|
|
223
|
+
const responseData = JSON.parse(responseBody);
|
|
224
|
+
// Restore placeholders in response
|
|
225
|
+
const restoredData = restore(responseData, mappingTable);
|
|
226
|
+
// Log restoration event
|
|
227
|
+
if (mappingTable.size > 0) {
|
|
228
|
+
logRestoreEvent({
|
|
229
|
+
requestId,
|
|
230
|
+
backend: "openai",
|
|
231
|
+
endpoint: "/v1/chat/completions",
|
|
232
|
+
model,
|
|
233
|
+
mappingTable,
|
|
234
|
+
restorationCount: mappingTable.size,
|
|
235
|
+
durationMs: Date.now() - restoreStart,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
239
|
+
res.end(JSON.stringify(restoredData));
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Read request body as string
|
|
243
|
+
*/
|
|
244
|
+
function readBody(req) {
|
|
245
|
+
return new Promise((resolve, reject) => {
|
|
246
|
+
let body = "";
|
|
247
|
+
req.on("data", (chunk) => {
|
|
248
|
+
body += chunk.toString();
|
|
249
|
+
});
|
|
250
|
+
req.on("end", () => resolve(body));
|
|
251
|
+
req.on("error", reject);
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
//# sourceMappingURL=openai.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai.js","sourceRoot":"","sources":["../../src/handlers/openai.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* OpenGuardrails AI Security Gateway
|
|
4
|
+
*
|
|
5
|
+
* Local HTTP proxy that intercepts LLM API calls, sanitizes sensitive data
|
|
6
|
+
* before sending to providers, and restores it in responses.
|
|
7
|
+
* Supports Anthropic, OpenAI, and Gemini protocols.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Stop the gateway server
|
|
11
|
+
*/
|
|
12
|
+
export declare function stopGateway(): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Check if gateway is running
|
|
15
|
+
*/
|
|
16
|
+
export declare function isGatewayServerRunning(): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Start gateway server
|
|
19
|
+
* @param configPath - Path to config file
|
|
20
|
+
* @param embedded - If true, don't call process.exit on errors (for in-process use)
|
|
21
|
+
*/
|
|
22
|
+
export declare function startGateway(configPath?: string, embedded?: boolean): void;
|
|
23
|
+
export { sanitize, sanitizeMessages } from "./sanitizer.js";
|
|
24
|
+
export { restore, restoreJSON, restoreSSELine } from "./restorer.js";
|
|
25
|
+
export { addActivityListener, removeActivityListener, clearActivityListeners, } from "./activity.js";
|
|
26
|
+
export type { GatewayConfig, MappingTable, SanitizeResult, EntityMatch, GatewayActivityEvent, ActivityListener, } from "./types.js";
|
|
27
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;GAMG;AAwMH;;GAEG;AACH,wBAAgB,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAY3C;AAED;;GAEG;AACH,wBAAgB,sBAAsB,IAAI,OAAO,CAEhD;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,QAAQ,UAAQ,GAAG,IAAI,CAmFxE;AAGD,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAC5D,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACrE,OAAO,EACL,mBAAmB,EACnB,sBAAsB,EACtB,sBAAsB,GACvB,MAAM,eAAe,CAAC;AAEvB,YAAY,EACV,aAAa,EACb,YAAY,EACZ,cAAc,EACd,WAAW,EACX,oBAAoB,EACpB,gBAAgB,GACjB,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* OpenGuardrails AI Security Gateway
|
|
4
|
+
*
|
|
5
|
+
* Local HTTP proxy that intercepts LLM API calls, sanitizes sensitive data
|
|
6
|
+
* before sending to providers, and restores it in responses.
|
|
7
|
+
* Supports Anthropic, OpenAI, and Gemini protocols.
|
|
8
|
+
*/
|
|
9
|
+
import { createServer } from "node:http";
|
|
10
|
+
import { loadConfig, validateConfig, findBackendByApiKey, findDefaultBackend, findBackendByPathPrefix } from "./config.js";
|
|
11
|
+
import { handleAnthropicRequest } from "./handlers/anthropic.js";
|
|
12
|
+
import { handleOpenAIRequest } from "./handlers/openai.js";
|
|
13
|
+
import { handleGeminiRequest } from "./handlers/gemini.js";
|
|
14
|
+
import { handleModelsRequest } from "./handlers/models.js";
|
|
15
|
+
let config;
|
|
16
|
+
let currentServer = null;
|
|
17
|
+
/**
|
|
18
|
+
* Extract API key from request headers
|
|
19
|
+
*/
|
|
20
|
+
function extractApiKey(req) {
|
|
21
|
+
// Try x-api-key header (Anthropic style)
|
|
22
|
+
const xApiKey = req.headers["x-api-key"];
|
|
23
|
+
if (xApiKey && typeof xApiKey === "string") {
|
|
24
|
+
return xApiKey;
|
|
25
|
+
}
|
|
26
|
+
// Try Authorization: Bearer (OpenAI style)
|
|
27
|
+
const auth = req.headers["authorization"];
|
|
28
|
+
if (auth && typeof auth === "string" && auth.startsWith("Bearer ")) {
|
|
29
|
+
return auth.slice(7);
|
|
30
|
+
}
|
|
31
|
+
// Try x-goog-api-key (Gemini style)
|
|
32
|
+
const googKey = req.headers["x-goog-api-key"];
|
|
33
|
+
if (googKey && typeof googKey === "string") {
|
|
34
|
+
return googKey;
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Resolve backend for a request based on path prefix, API key, or defaults
|
|
40
|
+
* Priority: pathPrefix > apiKey > defaultBackend
|
|
41
|
+
*/
|
|
42
|
+
function resolveBackend(req, apiType) {
|
|
43
|
+
const url = req.url || "";
|
|
44
|
+
// 1. Try to find backend by path prefix (most specific)
|
|
45
|
+
const byPath = findBackendByPathPrefix(url, config);
|
|
46
|
+
if (byPath) {
|
|
47
|
+
return byPath;
|
|
48
|
+
}
|
|
49
|
+
// 2. Try to find backend by API key
|
|
50
|
+
const apiKey = extractApiKey(req);
|
|
51
|
+
if (apiKey) {
|
|
52
|
+
const byKey = findBackendByApiKey(apiKey, config);
|
|
53
|
+
if (byKey) {
|
|
54
|
+
return byKey;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// 3. Fall back to default backend for the API type
|
|
58
|
+
return findDefaultBackend(apiType, config);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Main request handler
|
|
62
|
+
*/
|
|
63
|
+
async function handleRequest(req, res) {
|
|
64
|
+
const { method, url } = req;
|
|
65
|
+
// Log request (skip health checks to reduce noise)
|
|
66
|
+
if (url !== "/health") {
|
|
67
|
+
console.log(`[ai-security-gateway] ${method} ${url}`);
|
|
68
|
+
}
|
|
69
|
+
// CORS headers (for browser-based clients)
|
|
70
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
71
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
72
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, x-api-key, anthropic-version");
|
|
73
|
+
// Handle OPTIONS for CORS preflight
|
|
74
|
+
if (method === "OPTIONS") {
|
|
75
|
+
res.writeHead(204);
|
|
76
|
+
res.end();
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
// Health check (allow GET)
|
|
80
|
+
if (url === "/health") {
|
|
81
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
82
|
+
res.end(JSON.stringify({ status: "ok", version: "1.0.0" }));
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
// Handle GET /v1/models — proxy to configured backend's models endpoint
|
|
86
|
+
if (method === "GET" && url === "/v1/models") {
|
|
87
|
+
await handleModelsRequest(res, config);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
// Only allow POST for API endpoints
|
|
91
|
+
if (method !== "POST") {
|
|
92
|
+
res.writeHead(405, { "Content-Type": "application/json" });
|
|
93
|
+
res.end(JSON.stringify({ error: "Method not allowed" }));
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
// Route to appropriate handler based on path suffix
|
|
97
|
+
// This allows flexible path prefixes (e.g., /v1/coding/chat/completions)
|
|
98
|
+
try {
|
|
99
|
+
if (url?.endsWith("/messages")) {
|
|
100
|
+
// Anthropic Messages API (matches /v1/messages, /v1/xxx/messages, etc.)
|
|
101
|
+
const resolved = resolveBackend(req, "anthropic");
|
|
102
|
+
if (!resolved) {
|
|
103
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
104
|
+
res.end(JSON.stringify({ error: "No Anthropic-compatible backend configured" }));
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
await handleAnthropicRequest(req, res, resolved.backend);
|
|
108
|
+
}
|
|
109
|
+
else if (url?.endsWith("/chat/completions")) {
|
|
110
|
+
// OpenAI/OpenRouter Chat Completions API
|
|
111
|
+
// Try to extract backend name from URL: /backend/{name}/chat/completions
|
|
112
|
+
const backendMatch = url.match(/^\/backend\/([^/]+)\//);
|
|
113
|
+
let resolved = null;
|
|
114
|
+
if (backendMatch) {
|
|
115
|
+
const backendName = backendMatch[1];
|
|
116
|
+
const backend = config.backends[backendName];
|
|
117
|
+
if (backend) {
|
|
118
|
+
resolved = { name: backendName, backend };
|
|
119
|
+
console.log(`[ai-security-gateway] Backend from URL: ${backendName}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// Fallback to path prefix or default
|
|
123
|
+
if (!resolved) {
|
|
124
|
+
resolved = resolveBackend(req, "openai");
|
|
125
|
+
console.log(`[ai-security-gateway] Resolved backend: ${resolved?.name}`);
|
|
126
|
+
}
|
|
127
|
+
// Check explicit routing config
|
|
128
|
+
const explicitBackendName = config.routing?.["/v1/chat/completions"];
|
|
129
|
+
const backend = explicitBackendName
|
|
130
|
+
? config.backends[explicitBackendName]
|
|
131
|
+
: resolved?.backend;
|
|
132
|
+
if (!backend) {
|
|
133
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
134
|
+
res.end(JSON.stringify({ error: "No OpenAI-compatible backend configured" }));
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const extraHeaders = {};
|
|
138
|
+
if (backend.referer) {
|
|
139
|
+
extraHeaders["HTTP-Referer"] = backend.referer;
|
|
140
|
+
}
|
|
141
|
+
if (backend.title) {
|
|
142
|
+
extraHeaders["X-Title"] = backend.title;
|
|
143
|
+
}
|
|
144
|
+
await handleOpenAIRequest(req, res, backend, extraHeaders);
|
|
145
|
+
}
|
|
146
|
+
else if (url?.match(/\/models\/(.+):generateContent$/)) {
|
|
147
|
+
// Gemini API (matches any path ending with /models/{model}:generateContent)
|
|
148
|
+
const match = url.match(/\/models\/(.+):generateContent$/);
|
|
149
|
+
const modelName = match?.[1];
|
|
150
|
+
if (modelName) {
|
|
151
|
+
const resolved = resolveBackend(req, "gemini");
|
|
152
|
+
if (!resolved) {
|
|
153
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
154
|
+
res.end(JSON.stringify({ error: "No Gemini backend configured" }));
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
await handleGeminiRequest(req, res, resolved.backend, modelName);
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
161
|
+
res.end(JSON.stringify({ error: "Model name required" }));
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
// Unknown endpoint
|
|
166
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
167
|
+
res.end(JSON.stringify({ error: "Not found", url }));
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
console.error("[ai-security-gateway] Request handler error:", error);
|
|
172
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
173
|
+
res.end(JSON.stringify({
|
|
174
|
+
error: "Internal server error",
|
|
175
|
+
message: error instanceof Error ? error.message : String(error),
|
|
176
|
+
}));
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Stop the gateway server
|
|
181
|
+
*/
|
|
182
|
+
export function stopGateway() {
|
|
183
|
+
return new Promise((resolve) => {
|
|
184
|
+
if (currentServer) {
|
|
185
|
+
currentServer.close(() => {
|
|
186
|
+
currentServer = null;
|
|
187
|
+
console.log("[ai-security-gateway] Server stopped");
|
|
188
|
+
resolve();
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
resolve();
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Check if gateway is running
|
|
198
|
+
*/
|
|
199
|
+
export function isGatewayServerRunning() {
|
|
200
|
+
return currentServer !== null;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Start gateway server
|
|
204
|
+
* @param configPath - Path to config file
|
|
205
|
+
* @param embedded - If true, don't call process.exit on errors (for in-process use)
|
|
206
|
+
*/
|
|
207
|
+
export function startGateway(configPath, embedded = false) {
|
|
208
|
+
// Stop existing server if running (same process)
|
|
209
|
+
if (currentServer) {
|
|
210
|
+
if (!embedded)
|
|
211
|
+
console.log("[ai-security-gateway] Stopping existing server for restart...");
|
|
212
|
+
currentServer.close();
|
|
213
|
+
currentServer = null;
|
|
214
|
+
}
|
|
215
|
+
try {
|
|
216
|
+
// Load and validate configuration
|
|
217
|
+
config = loadConfig(configPath);
|
|
218
|
+
validateConfig(config);
|
|
219
|
+
if (!embedded) {
|
|
220
|
+
console.log("[ai-security-gateway] Configuration loaded:");
|
|
221
|
+
console.log(` Port: ${config.port}`);
|
|
222
|
+
console.log(` Backends: ${Object.keys(config.backends).join(", ") || "(none)"}`);
|
|
223
|
+
}
|
|
224
|
+
// Create HTTP server
|
|
225
|
+
const server = createServer(handleRequest);
|
|
226
|
+
currentServer = server;
|
|
227
|
+
// Handle server errors
|
|
228
|
+
server.on("error", (err) => {
|
|
229
|
+
console.error("[ai-security-gateway] Server error:", err);
|
|
230
|
+
currentServer = null;
|
|
231
|
+
if (!embedded) {
|
|
232
|
+
process.exit(1);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
// Embedded mode: never crash host process on gateway bind/runtime errors.
|
|
236
|
+
// The plugin can continue running and other features remain available.
|
|
237
|
+
console.warn("[ai-security-gateway] Embedded mode: gateway error ignored");
|
|
238
|
+
});
|
|
239
|
+
// Start listening
|
|
240
|
+
server.listen(config.port, "127.0.0.1", () => {
|
|
241
|
+
if (!embedded) {
|
|
242
|
+
console.log(`[ai-security-gateway] Server listening on http://127.0.0.1:${config.port}`);
|
|
243
|
+
console.log("[ai-security-gateway] Ready to proxy requests");
|
|
244
|
+
console.log("");
|
|
245
|
+
console.log("Endpoints:");
|
|
246
|
+
console.log(` POST http://127.0.0.1:${config.port}/v1/messages - Anthropic`);
|
|
247
|
+
console.log(` POST http://127.0.0.1:${config.port}/v1/chat/completions - OpenAI / OpenRouter`);
|
|
248
|
+
console.log(` POST http://127.0.0.1:${config.port}/v1/models/:model:generateContent - Gemini`);
|
|
249
|
+
console.log(` GET http://127.0.0.1:${config.port}/v1/models - List models (OpenAI / OpenRouter)`);
|
|
250
|
+
console.log(` GET http://127.0.0.1:${config.port}/health - Health check`);
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
// In embedded mode, don't let the server prevent process exit
|
|
254
|
+
if (embedded) {
|
|
255
|
+
server.unref();
|
|
256
|
+
}
|
|
257
|
+
// Only register shutdown handlers if not embedded
|
|
258
|
+
if (!embedded) {
|
|
259
|
+
process.on("SIGINT", () => {
|
|
260
|
+
console.log("\n[ai-security-gateway] Shutting down...");
|
|
261
|
+
server.close(() => {
|
|
262
|
+
console.log("[ai-security-gateway] Server stopped");
|
|
263
|
+
process.exit(0);
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
process.on("SIGTERM", () => {
|
|
267
|
+
console.log("\n[ai-security-gateway] Shutting down...");
|
|
268
|
+
server.close(() => {
|
|
269
|
+
console.log("[ai-security-gateway] Server stopped");
|
|
270
|
+
process.exit(0);
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
catch (error) {
|
|
276
|
+
console.error("[ai-security-gateway] Failed to start:", error);
|
|
277
|
+
currentServer = null;
|
|
278
|
+
if (!embedded) {
|
|
279
|
+
process.exit(1);
|
|
280
|
+
}
|
|
281
|
+
throw error;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
// Re-export for programmatic use
|
|
285
|
+
export { sanitize, sanitizeMessages } from "./sanitizer.js";
|
|
286
|
+
export { restore, restoreJSON, restoreSSELine } from "./restorer.js";
|
|
287
|
+
export { addActivityListener, removeActivityListener, clearActivityListeners, } from "./activity.js";
|
|
288
|
+
// Start if run directly
|
|
289
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
290
|
+
const configPath = process.argv[2];
|
|
291
|
+
startGateway(configPath);
|
|
292
|
+
}
|
|
293
|
+
//# sourceMappingURL=index.js.map
|