@yan162/changewayguard 6.8.25 → 6.8.27
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/agent/env.d.ts +9 -0
- package/dist/agent/env.d.ts.map +1 -1
- package/dist/agent/env.js +14 -3
- package/dist/agent/env.js.map +1 -1
- package/dist/agent/openclaw-hybrid-audit-changeway.js +7 -3
- package/dist/gateway/dist/activity.d.ts +52 -0
- package/dist/gateway/dist/activity.d.ts.map +1 -0
- package/dist/gateway/dist/activity.js +111 -0
- package/dist/gateway/dist/activity.js.map +1 -0
- package/dist/gateway/dist/config.d.ts +50 -0
- package/dist/gateway/dist/config.d.ts.map +1 -0
- package/dist/gateway/dist/config.js +200 -0
- package/dist/gateway/dist/config.js.map +1 -0
- package/dist/gateway/dist/handlers/anthropic.d.ts +12 -0
- package/dist/gateway/dist/handlers/anthropic.d.ts.map +1 -0
- package/dist/gateway/dist/handlers/anthropic.js +254 -0
- package/dist/gateway/dist/handlers/anthropic.js.map +1 -0
- package/dist/gateway/dist/handlers/gemini.d.ts +12 -0
- package/dist/gateway/dist/handlers/gemini.d.ts.map +1 -0
- package/dist/gateway/dist/handlers/gemini.js +101 -0
- package/dist/gateway/dist/handlers/gemini.js.map +1 -0
- package/dist/gateway/dist/handlers/models.d.ts +4 -0
- package/dist/gateway/dist/handlers/models.d.ts.map +1 -0
- package/dist/gateway/dist/handlers/models.js +36 -0
- package/dist/gateway/dist/handlers/models.js.map +1 -0
- package/dist/gateway/dist/handlers/openai.d.ts +16 -0
- package/dist/gateway/dist/handlers/openai.d.ts.map +1 -0
- package/dist/gateway/dist/handlers/openai.js +254 -0
- package/dist/gateway/dist/handlers/openai.js.map +1 -0
- package/dist/gateway/dist/index.d.ts +27 -0
- package/dist/gateway/dist/index.d.ts.map +1 -0
- package/dist/gateway/dist/index.js +290 -0
- package/dist/gateway/dist/index.js.map +1 -0
- package/dist/gateway/dist/mapping-store.d.ts +38 -0
- package/dist/gateway/dist/mapping-store.d.ts.map +1 -0
- package/dist/gateway/dist/mapping-store.js +74 -0
- package/dist/gateway/dist/mapping-store.js.map +1 -0
- package/dist/gateway/dist/restorer.d.ts +63 -0
- package/dist/gateway/dist/restorer.d.ts.map +1 -0
- package/dist/gateway/dist/restorer.js +284 -0
- package/dist/gateway/dist/restorer.js.map +1 -0
- package/dist/gateway/dist/sanitizer.d.ts +17 -0
- package/dist/gateway/dist/sanitizer.d.ts.map +1 -0
- package/dist/gateway/dist/sanitizer.js +228 -0
- package/dist/gateway/dist/sanitizer.js.map +1 -0
- package/dist/gateway/dist/types.d.ts +53 -0
- package/dist/gateway/dist/types.d.ts.map +1 -0
- package/dist/gateway/dist/types.js +5 -0
- package/dist/gateway/dist/types.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,254 @@
|
|
|
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, createStreamRestorer } from "../restorer.js";
|
|
8
|
+
import { generateRequestId, logSanitizeEvent, logRestoreEvent } from "../activity.js";
|
|
9
|
+
/**
|
|
10
|
+
* Handle Anthropic API request
|
|
11
|
+
*/
|
|
12
|
+
export async function handleAnthropicRequest(req, res, backend) {
|
|
13
|
+
try {
|
|
14
|
+
const requestId = generateRequestId();
|
|
15
|
+
const sanitizeStart = Date.now();
|
|
16
|
+
// 1. Parse request body
|
|
17
|
+
const body = await readBody(req);
|
|
18
|
+
const requestData = JSON.parse(body);
|
|
19
|
+
const { model, messages, system, tools, max_tokens, temperature, stream = false, ...rest } = requestData;
|
|
20
|
+
// 2. Sanitize messages
|
|
21
|
+
const { sanitized: sanitizedMessages, mappingTable, redactionCount } = sanitize(messages);
|
|
22
|
+
// 3. Sanitize system prompt if present
|
|
23
|
+
let systemRedactionCount = 0;
|
|
24
|
+
const sanitizedSystem = system
|
|
25
|
+
? (() => {
|
|
26
|
+
const result = sanitize(system);
|
|
27
|
+
systemRedactionCount = result.redactionCount;
|
|
28
|
+
return result.sanitized;
|
|
29
|
+
})()
|
|
30
|
+
: system;
|
|
31
|
+
const totalRedactionCount = redactionCount + systemRedactionCount;
|
|
32
|
+
// Log sanitization event
|
|
33
|
+
if (totalRedactionCount > 0) {
|
|
34
|
+
logSanitizeEvent({
|
|
35
|
+
requestId,
|
|
36
|
+
backend: "anthropic",
|
|
37
|
+
endpoint: "/v1/messages",
|
|
38
|
+
model,
|
|
39
|
+
mappingTable,
|
|
40
|
+
redactionCount: totalRedactionCount,
|
|
41
|
+
durationMs: Date.now() - sanitizeStart,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
// Note: We reuse the same mapping table so placeholders are consistent
|
|
45
|
+
// Debug: log what was sanitized
|
|
46
|
+
if (totalRedactionCount > 0) {
|
|
47
|
+
console.log(`[ai-security-gateway] Sanitized ${totalRedactionCount} items`);
|
|
48
|
+
}
|
|
49
|
+
// 4. Build sanitized request
|
|
50
|
+
const sanitizedRequest = {
|
|
51
|
+
model,
|
|
52
|
+
messages: sanitizedMessages,
|
|
53
|
+
...(system && { system: sanitizedSystem }),
|
|
54
|
+
...(tools && { tools }),
|
|
55
|
+
max_tokens,
|
|
56
|
+
...(temperature !== undefined && { temperature }),
|
|
57
|
+
stream,
|
|
58
|
+
...rest,
|
|
59
|
+
};
|
|
60
|
+
// 5. Forward to real Anthropic API
|
|
61
|
+
// Note: baseUrl already includes the full path prefix (e.g., /v1)
|
|
62
|
+
const apiUrl = `${backend.baseUrl}/messages`;
|
|
63
|
+
const response = await fetch(apiUrl, {
|
|
64
|
+
method: "POST",
|
|
65
|
+
headers: {
|
|
66
|
+
"Content-Type": "application/json",
|
|
67
|
+
"anthropic-version": req.headers["anthropic-version"] || "2023-06-01",
|
|
68
|
+
"x-api-key": backend.apiKey,
|
|
69
|
+
},
|
|
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
|
+
// 7. Handle streaming or non-streaming response
|
|
80
|
+
if (stream) {
|
|
81
|
+
await handleAnthropicStream(response, res, mappingTable, requestId, model);
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
await handleAnthropicNonStream(response, res, mappingTable, requestId, model);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
console.error("[ai-security-gateway] Anthropic 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 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 handleAnthropicStream(response, res, mappingTable, requestId, model) {
|
|
103
|
+
const restoreStart = Date.now();
|
|
104
|
+
// Debug: log mapping table size
|
|
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
|
+
try {
|
|
124
|
+
while (true) {
|
|
125
|
+
const { done, value } = await reader.read();
|
|
126
|
+
if (done)
|
|
127
|
+
break;
|
|
128
|
+
// Decode chunk
|
|
129
|
+
lineBuffer += decoder.decode(value, { stream: true });
|
|
130
|
+
// Process complete lines
|
|
131
|
+
const lines = lineBuffer.split("\n");
|
|
132
|
+
lineBuffer = lines.pop() || ""; // Keep incomplete line in buffer
|
|
133
|
+
for (const line of lines) {
|
|
134
|
+
if (!line.trim()) {
|
|
135
|
+
res.write("\n");
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
// Handle event lines (pass through)
|
|
139
|
+
if (line.startsWith("event:")) {
|
|
140
|
+
res.write(line + "\n");
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
// Handle data lines
|
|
144
|
+
if (!line.startsWith("data: ")) {
|
|
145
|
+
res.write(line + "\n");
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const dataContent = line.slice(6);
|
|
149
|
+
try {
|
|
150
|
+
const parsed = JSON.parse(dataContent);
|
|
151
|
+
// Check for text delta
|
|
152
|
+
if (parsed.type === "content_block_delta" && parsed.delta?.type === "text_delta") {
|
|
153
|
+
const textContent = parsed.delta.text;
|
|
154
|
+
if (textContent !== undefined && mappingTable.size > 0) {
|
|
155
|
+
// Process text through stream restorer
|
|
156
|
+
const restored = streamRestorer.process(textContent);
|
|
157
|
+
if (restored.length > 0) {
|
|
158
|
+
// We have restorable content - output it
|
|
159
|
+
const restoredChunk = {
|
|
160
|
+
...parsed,
|
|
161
|
+
delta: { ...parsed.delta, text: restored },
|
|
162
|
+
};
|
|
163
|
+
res.write(`data: ${JSON.stringify(restoredChunk)}\n`);
|
|
164
|
+
}
|
|
165
|
+
// If restorer is buffering, don't output anything yet
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
// No text content or no mappings - pass through
|
|
169
|
+
res.write(line + "\n");
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
// Non-text events - pass through
|
|
174
|
+
res.write(line + "\n");
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
// Not valid JSON, pass through
|
|
179
|
+
res.write(line + "\n");
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
// Write any remaining line buffer
|
|
184
|
+
if (lineBuffer.trim()) {
|
|
185
|
+
res.write(lineBuffer + "\n");
|
|
186
|
+
}
|
|
187
|
+
// Finalize stream restorer - flush any remaining buffered content
|
|
188
|
+
const finalContent = streamRestorer.finalize();
|
|
189
|
+
if (finalContent.length > 0) {
|
|
190
|
+
// Create a final text delta chunk with remaining content
|
|
191
|
+
const finalChunk = {
|
|
192
|
+
type: "content_block_delta",
|
|
193
|
+
index: 0,
|
|
194
|
+
delta: { type: "text_delta", text: finalContent },
|
|
195
|
+
};
|
|
196
|
+
res.write(`data: ${JSON.stringify(finalChunk)}\n`);
|
|
197
|
+
}
|
|
198
|
+
// Log restoration event
|
|
199
|
+
if (mappingTable.size > 0) {
|
|
200
|
+
logRestoreEvent({
|
|
201
|
+
requestId,
|
|
202
|
+
backend: "anthropic",
|
|
203
|
+
endpoint: "/v1/messages",
|
|
204
|
+
model,
|
|
205
|
+
mappingTable,
|
|
206
|
+
restorationCount: mappingTable.size,
|
|
207
|
+
durationMs: Date.now() - restoreStart,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
res.end();
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
console.error("[ai-security-gateway] Stream error:", error);
|
|
214
|
+
res.end();
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Handle non-streaming response
|
|
219
|
+
*/
|
|
220
|
+
async function handleAnthropicNonStream(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: "anthropic",
|
|
231
|
+
endpoint: "/v1/messages",
|
|
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=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 { BackendConfig } from "../types.js";
|
|
8
|
+
/**
|
|
9
|
+
* Handle Gemini API request
|
|
10
|
+
*/
|
|
11
|
+
export declare function handleGeminiRequest(req: IncomingMessage, res: ServerResponse, backend: BackendConfig, 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;AAK/D;;GAEG;AACH,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,OAAO,EAAE,aAAa,EACtB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAuFf"}
|
|
@@ -0,0 +1,101 @@
|
|
|
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
|
+
import { generateRequestId, logSanitizeEvent, logRestoreEvent } from "../activity.js";
|
|
9
|
+
/**
|
|
10
|
+
* Handle Gemini API request
|
|
11
|
+
*/
|
|
12
|
+
export async function handleGeminiRequest(req, res, backend, modelName) {
|
|
13
|
+
try {
|
|
14
|
+
const requestId = generateRequestId();
|
|
15
|
+
const sanitizeStart = Date.now();
|
|
16
|
+
// 1. Parse request body
|
|
17
|
+
const body = await readBody(req);
|
|
18
|
+
const requestData = JSON.parse(body);
|
|
19
|
+
const { contents, tools, generationConfig, ...rest } = requestData;
|
|
20
|
+
// 2. Sanitize contents (Gemini uses "contents" instead of "messages")
|
|
21
|
+
const { sanitized: sanitizedContents, mappingTable, redactionCount } = sanitize(contents);
|
|
22
|
+
// Log sanitization event
|
|
23
|
+
if (redactionCount > 0) {
|
|
24
|
+
logSanitizeEvent({
|
|
25
|
+
requestId,
|
|
26
|
+
backend: "gemini",
|
|
27
|
+
endpoint: `/v1/models/${modelName}:generateContent`,
|
|
28
|
+
model: modelName,
|
|
29
|
+
mappingTable,
|
|
30
|
+
redactionCount,
|
|
31
|
+
durationMs: Date.now() - sanitizeStart,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
// 3. Build sanitized request
|
|
35
|
+
const sanitizedRequest = {
|
|
36
|
+
contents: sanitizedContents,
|
|
37
|
+
...(tools && { tools }),
|
|
38
|
+
...(generationConfig && { generationConfig }),
|
|
39
|
+
...rest,
|
|
40
|
+
};
|
|
41
|
+
// 4. Forward to Gemini API
|
|
42
|
+
const apiUrl = `${backend.baseUrl}/v1/models/${modelName}:generateContent`;
|
|
43
|
+
const response = await fetch(apiUrl, {
|
|
44
|
+
method: "POST",
|
|
45
|
+
headers: {
|
|
46
|
+
"Content-Type": "application/json",
|
|
47
|
+
"x-goog-api-key": backend.apiKey,
|
|
48
|
+
},
|
|
49
|
+
body: JSON.stringify(sanitizedRequest),
|
|
50
|
+
});
|
|
51
|
+
if (!response.ok) {
|
|
52
|
+
// Forward error response
|
|
53
|
+
res.writeHead(response.status, { "Content-Type": "application/json" });
|
|
54
|
+
const errorBody = await response.text();
|
|
55
|
+
res.end(errorBody);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
// 6. Handle response (Gemini typically doesn't stream in same way)
|
|
59
|
+
const restoreStart = Date.now();
|
|
60
|
+
const responseBody = await response.text();
|
|
61
|
+
const responseData = JSON.parse(responseBody);
|
|
62
|
+
// Restore placeholders in response
|
|
63
|
+
const restoredData = restore(responseData, mappingTable);
|
|
64
|
+
// Log restoration event
|
|
65
|
+
if (mappingTable.size > 0) {
|
|
66
|
+
logRestoreEvent({
|
|
67
|
+
requestId,
|
|
68
|
+
backend: "gemini",
|
|
69
|
+
endpoint: `/v1/models/${modelName}:generateContent`,
|
|
70
|
+
model: modelName,
|
|
71
|
+
mappingTable,
|
|
72
|
+
restorationCount: mappingTable.size,
|
|
73
|
+
durationMs: Date.now() - restoreStart,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
77
|
+
res.end(JSON.stringify(restoredData));
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
console.error("[ai-security-gateway] Gemini handler error:", error);
|
|
81
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
82
|
+
res.end(JSON.stringify({
|
|
83
|
+
error: "Internal gateway error",
|
|
84
|
+
message: error instanceof Error ? error.message : String(error),
|
|
85
|
+
}));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Read request body as string
|
|
90
|
+
*/
|
|
91
|
+
function readBody(req) {
|
|
92
|
+
return new Promise((resolve, reject) => {
|
|
93
|
+
let body = "";
|
|
94
|
+
req.on("data", (chunk) => {
|
|
95
|
+
body += chunk.toString();
|
|
96
|
+
});
|
|
97
|
+
req.on("end", () => resolve(body));
|
|
98
|
+
req.on("error", reject);
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
//# 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 @@
|
|
|
1
|
+
{"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../../src/handlers/models.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGjD,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,cAAc,EACnB,MAAM,EAAE,aAAa,GACpB,OAAO,CAAC,IAAI,CAAC,CAqCf"}
|
|
@@ -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"}
|