@openguardrails/gateway 1.0.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/dist/config.d.ts +13 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +100 -0
- package/dist/config.js.map +1 -0
- package/dist/handlers/anthropic.d.ts +12 -0
- package/dist/handlers/anthropic.d.ts.map +1 -0
- package/dist/handlers/anthropic.js +150 -0
- package/dist/handlers/anthropic.js.map +1 -0
- package/dist/handlers/gemini.d.ts +12 -0
- package/dist/handlers/gemini.d.ts.map +1 -0
- package/dist/handlers/gemini.js +80 -0
- package/dist/handlers/gemini.js.map +1 -0
- package/dist/handlers/openai.d.ts +13 -0
- package/dist/handlers/openai.d.ts.map +1 -0
- package/dist/handlers/openai.js +145 -0
- package/dist/handlers/openai.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +136 -0
- package/dist/index.js.map +1 -0
- package/dist/restorer.d.ts +21 -0
- package/dist/restorer.d.ts.map +1 -0
- package/dist/restorer.js +91 -0
- package/dist/restorer.js.map +1 -0
- package/dist/sanitizer.d.ts +17 -0
- package/dist/sanitizer.d.ts.map +1 -0
- package/dist/sanitizer.js +226 -0
- package/dist/sanitizer.js.map +1 -0
- package/dist/types.d.ts +35 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/package.json +55 -0
- package/src/config.ts +122 -0
- package/src/handlers/anthropic.ts +195 -0
- package/src/handlers/gemini.ts +99 -0
- package/src/handlers/openai.ts +188 -0
- package/src/index.ts +159 -0
- package/src/restorer.ts +101 -0
- package/src/sanitizer.ts +278 -0
- package/src/types.ts +43 -0
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gateway configuration management
|
|
3
|
+
*/
|
|
4
|
+
import type { GatewayConfig } from "./types.js";
|
|
5
|
+
/**
|
|
6
|
+
* Load gateway configuration from file or environment
|
|
7
|
+
*/
|
|
8
|
+
export declare function loadConfig(configPath?: string): GatewayConfig;
|
|
9
|
+
/**
|
|
10
|
+
* Validate configuration
|
|
11
|
+
*/
|
|
12
|
+
export declare function validateConfig(config: GatewayConfig): void;
|
|
13
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAIhD;;GAEG;AACH,wBAAgB,UAAU,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,aAAa,CAyB7D;AA6DD;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CAkB1D"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gateway configuration management
|
|
3
|
+
*/
|
|
4
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
const DEFAULT_CONFIG_PATH = join(homedir(), ".openguardrails", "gateway.json");
|
|
8
|
+
/**
|
|
9
|
+
* Load gateway configuration from file or environment
|
|
10
|
+
*/
|
|
11
|
+
export function loadConfig(configPath) {
|
|
12
|
+
const path = configPath || DEFAULT_CONFIG_PATH;
|
|
13
|
+
// Default configuration
|
|
14
|
+
const defaultConfig = {
|
|
15
|
+
port: parseInt(process.env.GATEWAY_PORT || "8900", 10),
|
|
16
|
+
backends: {},
|
|
17
|
+
};
|
|
18
|
+
// Try to load from file
|
|
19
|
+
if (existsSync(path)) {
|
|
20
|
+
try {
|
|
21
|
+
const fileContent = readFileSync(path, "utf-8");
|
|
22
|
+
const fileConfig = JSON.parse(fileContent);
|
|
23
|
+
return mergeConfig(defaultConfig, fileConfig);
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
console.warn(`[ai-security-gateway] Failed to load config from ${path}:`, error);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// Load from environment variables
|
|
30
|
+
return loadFromEnv(defaultConfig);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Load backend configs from environment variables
|
|
34
|
+
*/
|
|
35
|
+
function loadFromEnv(config) {
|
|
36
|
+
// Anthropic
|
|
37
|
+
if (process.env.ANTHROPIC_API_KEY) {
|
|
38
|
+
config.backends.anthropic = {
|
|
39
|
+
baseUrl: process.env.ANTHROPIC_BASE_URL || "https://api.anthropic.com",
|
|
40
|
+
apiKey: process.env.ANTHROPIC_API_KEY,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
// OpenAI
|
|
44
|
+
if (process.env.OPENAI_API_KEY) {
|
|
45
|
+
config.backends.openai = {
|
|
46
|
+
baseUrl: process.env.OPENAI_BASE_URL || "https://api.openai.com",
|
|
47
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
// Kimi (Moonshot)
|
|
51
|
+
if (process.env.KIMI_API_KEY || process.env.MOONSHOT_API_KEY) {
|
|
52
|
+
config.backends.openai = {
|
|
53
|
+
baseUrl: process.env.KIMI_BASE_URL || "https://api.moonshot.cn",
|
|
54
|
+
apiKey: process.env.KIMI_API_KEY || process.env.MOONSHOT_API_KEY || "",
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
// Gemini
|
|
58
|
+
if (process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY) {
|
|
59
|
+
config.backends.gemini = {
|
|
60
|
+
baseUrl: process.env.GEMINI_BASE_URL ||
|
|
61
|
+
"https://generativelanguage.googleapis.com",
|
|
62
|
+
apiKey: process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY || "",
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
return config;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Merge file config with default config
|
|
69
|
+
*/
|
|
70
|
+
function mergeConfig(defaultConfig, fileConfig) {
|
|
71
|
+
return {
|
|
72
|
+
port: fileConfig.port ?? defaultConfig.port,
|
|
73
|
+
backends: {
|
|
74
|
+
...defaultConfig.backends,
|
|
75
|
+
...fileConfig.backends,
|
|
76
|
+
},
|
|
77
|
+
routing: fileConfig.routing,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Validate configuration
|
|
82
|
+
*/
|
|
83
|
+
export function validateConfig(config) {
|
|
84
|
+
if (config.port < 1 || config.port > 65535) {
|
|
85
|
+
throw new Error(`Invalid port: ${config.port}`);
|
|
86
|
+
}
|
|
87
|
+
// Note: Backends are now optional. Gateway will act as transparent proxy.
|
|
88
|
+
// If no backends configured, gateway will forward requests based on routing rules
|
|
89
|
+
// or pass through to the original target.
|
|
90
|
+
// Validate each backend (if any)
|
|
91
|
+
for (const [name, backend] of Object.entries(config.backends)) {
|
|
92
|
+
if (!backend.baseUrl) {
|
|
93
|
+
throw new Error(`Backend ${name} missing baseUrl`);
|
|
94
|
+
}
|
|
95
|
+
if (!backend.apiKey) {
|
|
96
|
+
throw new Error(`Backend ${name} missing apiKey`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Security Gateway - Anthropic Messages API handler
|
|
3
|
+
*
|
|
4
|
+
* Handles POST /v1/messages requests in Anthropic's native format.
|
|
5
|
+
*/
|
|
6
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
7
|
+
import type { GatewayConfig } from "../types.js";
|
|
8
|
+
/**
|
|
9
|
+
* Handle Anthropic API request
|
|
10
|
+
*/
|
|
11
|
+
export declare function handleAnthropicRequest(req: IncomingMessage, res: ServerResponse, config: GatewayConfig): Promise<void>;
|
|
12
|
+
//# sourceMappingURL=anthropic.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anthropic.d.ts","sourceRoot":"","sources":["../../src/handlers/anthropic.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACjE,OAAO,KAAK,EAAE,aAAa,EAAgB,MAAM,aAAa,CAAC;AAI/D;;GAEG;AACH,wBAAsB,sBAAsB,CAC1C,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,MAAM,EAAE,aAAa,GACpB,OAAO,CAAC,IAAI,CAAC,CAmFf"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Security Gateway - Anthropic Messages API handler
|
|
3
|
+
*
|
|
4
|
+
* Handles POST /v1/messages requests in Anthropic's native format.
|
|
5
|
+
*/
|
|
6
|
+
import { sanitize } from "../sanitizer.js";
|
|
7
|
+
import { restore, restoreSSELine } from "../restorer.js";
|
|
8
|
+
/**
|
|
9
|
+
* Handle Anthropic API request
|
|
10
|
+
*/
|
|
11
|
+
export async function handleAnthropicRequest(req, res, config) {
|
|
12
|
+
try {
|
|
13
|
+
// 1. Parse request body
|
|
14
|
+
const body = await readBody(req);
|
|
15
|
+
const requestData = JSON.parse(body);
|
|
16
|
+
const { model, messages, system, tools, max_tokens, temperature, stream = false, ...rest } = requestData;
|
|
17
|
+
// 2. Sanitize messages
|
|
18
|
+
const { sanitized: sanitizedMessages, mappingTable } = sanitize(messages);
|
|
19
|
+
// 3. Sanitize system prompt if present
|
|
20
|
+
const sanitizedSystem = system
|
|
21
|
+
? sanitize(system).sanitized
|
|
22
|
+
: system;
|
|
23
|
+
// Note: We reuse the same mapping table so placeholders are consistent
|
|
24
|
+
// 4. Build sanitized request
|
|
25
|
+
const sanitizedRequest = {
|
|
26
|
+
model,
|
|
27
|
+
messages: sanitizedMessages,
|
|
28
|
+
...(system && { system: sanitizedSystem }),
|
|
29
|
+
...(tools && { tools }),
|
|
30
|
+
max_tokens,
|
|
31
|
+
...(temperature !== undefined && { temperature }),
|
|
32
|
+
stream,
|
|
33
|
+
...rest,
|
|
34
|
+
};
|
|
35
|
+
// 5. Get backend config
|
|
36
|
+
const backend = config.backends.anthropic;
|
|
37
|
+
if (!backend) {
|
|
38
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
39
|
+
res.end(JSON.stringify({ error: "Anthropic backend not configured" }));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
// 6. Forward to real Anthropic API
|
|
43
|
+
const apiUrl = `${backend.baseUrl}/v1/messages`;
|
|
44
|
+
const response = await fetch(apiUrl, {
|
|
45
|
+
method: "POST",
|
|
46
|
+
headers: {
|
|
47
|
+
"Content-Type": "application/json",
|
|
48
|
+
"anthropic-version": req.headers["anthropic-version"] || "2023-06-01",
|
|
49
|
+
"x-api-key": backend.apiKey,
|
|
50
|
+
},
|
|
51
|
+
body: JSON.stringify(sanitizedRequest),
|
|
52
|
+
});
|
|
53
|
+
if (!response.ok) {
|
|
54
|
+
// Forward error response
|
|
55
|
+
res.writeHead(response.status, { "Content-Type": "application/json" });
|
|
56
|
+
const errorBody = await response.text();
|
|
57
|
+
res.end(errorBody);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
// 7. Handle streaming response
|
|
61
|
+
if (stream) {
|
|
62
|
+
await handleAnthropicStream(response, res, mappingTable);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
await handleAnthropicNonStream(response, res, mappingTable);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
console.error("[ai-security-gateway] Anthropic handler error:", error);
|
|
70
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
71
|
+
res.end(JSON.stringify({
|
|
72
|
+
error: "Internal gateway error",
|
|
73
|
+
message: error instanceof Error ? error.message : String(error),
|
|
74
|
+
}));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Handle streaming response
|
|
79
|
+
*/
|
|
80
|
+
async function handleAnthropicStream(response, res, mappingTable) {
|
|
81
|
+
// Set SSE headers
|
|
82
|
+
res.writeHead(200, {
|
|
83
|
+
"Content-Type": "text/event-stream",
|
|
84
|
+
"Cache-Control": "no-cache",
|
|
85
|
+
"Connection": "keep-alive",
|
|
86
|
+
});
|
|
87
|
+
const reader = response.body?.getReader();
|
|
88
|
+
if (!reader) {
|
|
89
|
+
res.end();
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const decoder = new TextDecoder();
|
|
93
|
+
let buffer = "";
|
|
94
|
+
try {
|
|
95
|
+
while (true) {
|
|
96
|
+
const { done, value } = await reader.read();
|
|
97
|
+
if (done)
|
|
98
|
+
break;
|
|
99
|
+
// Decode chunk
|
|
100
|
+
buffer += decoder.decode(value, { stream: true });
|
|
101
|
+
// Process complete lines
|
|
102
|
+
const lines = buffer.split("\n");
|
|
103
|
+
buffer = lines.pop() || ""; // Keep incomplete line in buffer
|
|
104
|
+
for (const line of lines) {
|
|
105
|
+
if (!line.trim()) {
|
|
106
|
+
res.write("\n");
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
// Restore placeholders in SSE line
|
|
110
|
+
const restoredLine = restoreSSELine(line, mappingTable);
|
|
111
|
+
res.write(restoredLine + "\n");
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// Write any remaining buffer
|
|
115
|
+
if (buffer.trim()) {
|
|
116
|
+
const restoredLine = restoreSSELine(buffer, mappingTable);
|
|
117
|
+
res.write(restoredLine + "\n");
|
|
118
|
+
}
|
|
119
|
+
res.end();
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
console.error("[ai-security-gateway] Stream error:", error);
|
|
123
|
+
res.end();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Handle non-streaming response
|
|
128
|
+
*/
|
|
129
|
+
async function handleAnthropicNonStream(response, res, mappingTable) {
|
|
130
|
+
const responseBody = await response.text();
|
|
131
|
+
const responseData = JSON.parse(responseBody);
|
|
132
|
+
// Restore placeholders in response
|
|
133
|
+
const restoredData = restore(responseData, mappingTable);
|
|
134
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
135
|
+
res.end(JSON.stringify(restoredData));
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Read request body as string
|
|
139
|
+
*/
|
|
140
|
+
function readBody(req) {
|
|
141
|
+
return new Promise((resolve, reject) => {
|
|
142
|
+
let body = "";
|
|
143
|
+
req.on("data", (chunk) => {
|
|
144
|
+
body += chunk.toString();
|
|
145
|
+
});
|
|
146
|
+
req.on("end", () => resolve(body));
|
|
147
|
+
req.on("error", reject);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=anthropic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anthropic.js","sourceRoot":"","sources":["../../src/handlers/anthropic.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Security Gateway - Google Gemini API handler
|
|
3
|
+
*
|
|
4
|
+
* Handles POST /v1/models/:model:generateContent requests in Gemini's format.
|
|
5
|
+
*/
|
|
6
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
7
|
+
import type { GatewayConfig } from "../types.js";
|
|
8
|
+
/**
|
|
9
|
+
* Handle Gemini API request
|
|
10
|
+
*/
|
|
11
|
+
export declare function handleGeminiRequest(req: IncomingMessage, res: ServerResponse, config: GatewayConfig, modelName: string): Promise<void>;
|
|
12
|
+
//# sourceMappingURL=gemini.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gemini.d.ts","sourceRoot":"","sources":["../../src/handlers/gemini.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACjE,OAAO,KAAK,EAAE,aAAa,EAAgB,MAAM,aAAa,CAAC;AAI/D;;GAEG;AACH,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,MAAM,EAAE,aAAa,EACrB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAiEf"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Security Gateway - Google Gemini API handler
|
|
3
|
+
*
|
|
4
|
+
* Handles POST /v1/models/:model:generateContent requests in Gemini's format.
|
|
5
|
+
*/
|
|
6
|
+
import { sanitize } from "../sanitizer.js";
|
|
7
|
+
import { restore } from "../restorer.js";
|
|
8
|
+
/**
|
|
9
|
+
* Handle Gemini API request
|
|
10
|
+
*/
|
|
11
|
+
export async function handleGeminiRequest(req, res, config, modelName) {
|
|
12
|
+
try {
|
|
13
|
+
// 1. Parse request body
|
|
14
|
+
const body = await readBody(req);
|
|
15
|
+
const requestData = JSON.parse(body);
|
|
16
|
+
const { contents, tools, generationConfig, ...rest } = requestData;
|
|
17
|
+
// 2. Sanitize contents (Gemini uses "contents" instead of "messages")
|
|
18
|
+
const { sanitized: sanitizedContents, mappingTable } = sanitize(contents);
|
|
19
|
+
// 3. Build sanitized request
|
|
20
|
+
const sanitizedRequest = {
|
|
21
|
+
contents: sanitizedContents,
|
|
22
|
+
...(tools && { tools }),
|
|
23
|
+
...(generationConfig && { generationConfig }),
|
|
24
|
+
...rest,
|
|
25
|
+
};
|
|
26
|
+
// 4. Get backend config
|
|
27
|
+
const backend = config.backends.gemini;
|
|
28
|
+
if (!backend) {
|
|
29
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
30
|
+
res.end(JSON.stringify({ error: "Gemini backend not configured" }));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
// 5. Forward to Gemini API
|
|
34
|
+
const apiUrl = `${backend.baseUrl}/v1/models/${modelName}:generateContent`;
|
|
35
|
+
const response = await fetch(apiUrl, {
|
|
36
|
+
method: "POST",
|
|
37
|
+
headers: {
|
|
38
|
+
"Content-Type": "application/json",
|
|
39
|
+
"x-goog-api-key": backend.apiKey,
|
|
40
|
+
},
|
|
41
|
+
body: JSON.stringify(sanitizedRequest),
|
|
42
|
+
});
|
|
43
|
+
if (!response.ok) {
|
|
44
|
+
// Forward error response
|
|
45
|
+
res.writeHead(response.status, { "Content-Type": "application/json" });
|
|
46
|
+
const errorBody = await response.text();
|
|
47
|
+
res.end(errorBody);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
// 6. Handle response (Gemini typically doesn't stream in same way)
|
|
51
|
+
const responseBody = await response.text();
|
|
52
|
+
const responseData = JSON.parse(responseBody);
|
|
53
|
+
// Restore placeholders in response
|
|
54
|
+
const restoredData = restore(responseData, mappingTable);
|
|
55
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
56
|
+
res.end(JSON.stringify(restoredData));
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
console.error("[ai-security-gateway] Gemini handler error:", error);
|
|
60
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
61
|
+
res.end(JSON.stringify({
|
|
62
|
+
error: "Internal gateway error",
|
|
63
|
+
message: error instanceof Error ? error.message : String(error),
|
|
64
|
+
}));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Read request body as string
|
|
69
|
+
*/
|
|
70
|
+
function readBody(req) {
|
|
71
|
+
return new Promise((resolve, reject) => {
|
|
72
|
+
let body = "";
|
|
73
|
+
req.on("data", (chunk) => {
|
|
74
|
+
body += chunk.toString();
|
|
75
|
+
});
|
|
76
|
+
req.on("end", () => resolve(body));
|
|
77
|
+
req.on("error", reject);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=gemini.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gemini.js","sourceRoot":"","sources":["../../src/handlers/gemini.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
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 { GatewayConfig } from "../types.js";
|
|
9
|
+
/**
|
|
10
|
+
* Handle OpenAI API request
|
|
11
|
+
*/
|
|
12
|
+
export declare function handleOpenAIRequest(req: IncomingMessage, res: ServerResponse, config: GatewayConfig): Promise<void>;
|
|
13
|
+
//# 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;AAI/D;;GAEG;AACH,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,MAAM,EAAE,aAAa,GACpB,OAAO,CAAC,IAAI,CAAC,CA2Ef"}
|
|
@@ -0,0 +1,145 @@
|
|
|
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, restoreSSELine } from "../restorer.js";
|
|
9
|
+
/**
|
|
10
|
+
* Handle OpenAI API request
|
|
11
|
+
*/
|
|
12
|
+
export async function handleOpenAIRequest(req, res, config) {
|
|
13
|
+
try {
|
|
14
|
+
// 1. Parse request body
|
|
15
|
+
const body = await readBody(req);
|
|
16
|
+
const requestData = JSON.parse(body);
|
|
17
|
+
const { model, messages, tools, tool_choice, temperature, max_tokens, stream = false, ...rest } = requestData;
|
|
18
|
+
// 2. Sanitize messages
|
|
19
|
+
const { sanitized: sanitizedMessages, mappingTable } = sanitize(messages);
|
|
20
|
+
// 3. Build sanitized request
|
|
21
|
+
const sanitizedRequest = {
|
|
22
|
+
model,
|
|
23
|
+
messages: sanitizedMessages,
|
|
24
|
+
...(tools && { tools }),
|
|
25
|
+
...(tool_choice && { tool_choice }),
|
|
26
|
+
...(temperature !== undefined && { temperature }),
|
|
27
|
+
...(max_tokens && { max_tokens }),
|
|
28
|
+
stream,
|
|
29
|
+
...rest,
|
|
30
|
+
};
|
|
31
|
+
// 4. Get backend config
|
|
32
|
+
const backend = config.backends.openai;
|
|
33
|
+
if (!backend) {
|
|
34
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
35
|
+
res.end(JSON.stringify({ error: "OpenAI backend not configured" }));
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
// 5. Forward to OpenAI (or compatible) API
|
|
39
|
+
const apiUrl = `${backend.baseUrl}/v1/chat/completions`;
|
|
40
|
+
const response = await fetch(apiUrl, {
|
|
41
|
+
method: "POST",
|
|
42
|
+
headers: {
|
|
43
|
+
"Content-Type": "application/json",
|
|
44
|
+
"Authorization": `Bearer ${backend.apiKey}`,
|
|
45
|
+
},
|
|
46
|
+
body: JSON.stringify(sanitizedRequest),
|
|
47
|
+
});
|
|
48
|
+
if (!response.ok) {
|
|
49
|
+
// Forward error response
|
|
50
|
+
res.writeHead(response.status, { "Content-Type": "application/json" });
|
|
51
|
+
const errorBody = await response.text();
|
|
52
|
+
res.end(errorBody);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
// 6. Handle streaming or non-streaming response
|
|
56
|
+
if (stream) {
|
|
57
|
+
await handleOpenAIStream(response, res, mappingTable);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
await handleOpenAINonStream(response, res, mappingTable);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
console.error("[ai-security-gateway] OpenAI handler error:", error);
|
|
65
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
66
|
+
res.end(JSON.stringify({
|
|
67
|
+
error: "Internal gateway error",
|
|
68
|
+
message: error instanceof Error ? error.message : String(error),
|
|
69
|
+
}));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Handle streaming response (SSE)
|
|
74
|
+
*/
|
|
75
|
+
async function handleOpenAIStream(response, res, mappingTable) {
|
|
76
|
+
// Set SSE headers
|
|
77
|
+
res.writeHead(200, {
|
|
78
|
+
"Content-Type": "text/event-stream",
|
|
79
|
+
"Cache-Control": "no-cache",
|
|
80
|
+
"Connection": "keep-alive",
|
|
81
|
+
});
|
|
82
|
+
const reader = response.body?.getReader();
|
|
83
|
+
if (!reader) {
|
|
84
|
+
res.end();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const decoder = new TextDecoder();
|
|
88
|
+
let buffer = "";
|
|
89
|
+
try {
|
|
90
|
+
while (true) {
|
|
91
|
+
const { done, value } = await reader.read();
|
|
92
|
+
if (done)
|
|
93
|
+
break;
|
|
94
|
+
// Decode chunk
|
|
95
|
+
buffer += decoder.decode(value, { stream: true });
|
|
96
|
+
// Process complete lines
|
|
97
|
+
const lines = buffer.split("\n");
|
|
98
|
+
buffer = lines.pop() || ""; // Keep incomplete line in buffer
|
|
99
|
+
for (const line of lines) {
|
|
100
|
+
if (!line.trim()) {
|
|
101
|
+
res.write("\n");
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
// Restore placeholders in SSE line
|
|
105
|
+
const restoredLine = restoreSSELine(line, mappingTable);
|
|
106
|
+
res.write(restoredLine + "\n");
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// Write any remaining buffer
|
|
110
|
+
if (buffer.trim()) {
|
|
111
|
+
const restoredLine = restoreSSELine(buffer, mappingTable);
|
|
112
|
+
res.write(restoredLine + "\n");
|
|
113
|
+
}
|
|
114
|
+
res.end();
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
console.error("[ai-security-gateway] Stream error:", error);
|
|
118
|
+
res.end();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Handle non-streaming response
|
|
123
|
+
*/
|
|
124
|
+
async function handleOpenAINonStream(response, res, mappingTable) {
|
|
125
|
+
const responseBody = await response.text();
|
|
126
|
+
const responseData = JSON.parse(responseBody);
|
|
127
|
+
// Restore placeholders in response
|
|
128
|
+
const restoredData = restore(responseData, mappingTable);
|
|
129
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
130
|
+
res.end(JSON.stringify(restoredData));
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Read request body as string
|
|
134
|
+
*/
|
|
135
|
+
function readBody(req) {
|
|
136
|
+
return new Promise((resolve, reject) => {
|
|
137
|
+
let body = "";
|
|
138
|
+
req.on("data", (chunk) => {
|
|
139
|
+
body += chunk.toString();
|
|
140
|
+
});
|
|
141
|
+
req.on("end", () => resolve(body));
|
|
142
|
+
req.on("error", reject);
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
//# 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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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
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
|
+
* Start gateway server
|
|
11
|
+
*/
|
|
12
|
+
export declare function startGateway(configPath?: string): void;
|
|
13
|
+
export { sanitize, sanitizeMessages } from "./sanitizer.js";
|
|
14
|
+
export { restore, restoreJSON, restoreSSELine } from "./restorer.js";
|
|
15
|
+
export type { GatewayConfig, MappingTable, SanitizeResult, EntityMatch } from "./types.js";
|
|
16
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;GAMG;AAuFH;;GAEG;AACH,wBAAgB,YAAY,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAkDtD;AAGD,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAC5D,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACrE,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC"}
|