skalpel 2.0.6 → 2.0.7

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.
@@ -2,42 +2,84 @@
2
2
  import http from "http";
3
3
 
4
4
  // src/proxy/streaming.ts
5
- async function handleStreamingRequest(_req, res, _config, _source, body, forwardUrl, forwardHeaders, logger) {
6
- const HOP_BY_HOP = /* @__PURE__ */ new Set([
7
- "connection",
8
- "keep-alive",
9
- "proxy-authenticate",
10
- "proxy-authorization",
11
- "te",
12
- "trailer",
13
- "transfer-encoding",
14
- "upgrade"
15
- ]);
16
- let response;
17
- try {
18
- response = await fetch(forwardUrl, {
19
- method: "POST",
20
- headers: forwardHeaders,
21
- body
22
- });
23
- } catch (err) {
24
- logger.error(`streaming fetch failed: ${err.message}`);
5
+ var HOP_BY_HOP = /* @__PURE__ */ new Set([
6
+ "connection",
7
+ "keep-alive",
8
+ "proxy-authenticate",
9
+ "proxy-authorization",
10
+ "te",
11
+ "trailer",
12
+ "transfer-encoding",
13
+ "upgrade"
14
+ ]);
15
+ var STRIP_HEADERS = /* @__PURE__ */ new Set([
16
+ ...HOP_BY_HOP,
17
+ "content-encoding",
18
+ "content-length"
19
+ ]);
20
+ function stripSkalpelHeaders(headers) {
21
+ const cleaned = { ...headers };
22
+ delete cleaned["X-Skalpel-API-Key"];
23
+ delete cleaned["X-Skalpel-Source"];
24
+ delete cleaned["X-Skalpel-Agent-Type"];
25
+ delete cleaned["X-Skalpel-SDK-Version"];
26
+ return cleaned;
27
+ }
28
+ function isSkalpelBackendFailure(response, err) {
29
+ if (err) return true;
30
+ if (!response) return true;
31
+ if (response.status >= 500) return true;
32
+ return false;
33
+ }
34
+ async function doStreamingFetch(url, body, headers) {
35
+ return fetch(url, { method: "POST", headers, body });
36
+ }
37
+ async function handleStreamingRequest(_req, res, _config, _source, body, skalpelUrl, directUrl, useSkalpel, forwardHeaders, logger) {
38
+ let response = null;
39
+ let fetchError = null;
40
+ let usedFallback = false;
41
+ if (useSkalpel) {
42
+ try {
43
+ response = await doStreamingFetch(skalpelUrl, body, forwardHeaders);
44
+ } catch (err) {
45
+ fetchError = err;
46
+ }
47
+ if (isSkalpelBackendFailure(response, fetchError)) {
48
+ logger.warn(`streaming: Skalpel backend failed (${fetchError ? fetchError.message : `status ${response?.status}`}), falling back to direct Anthropic API`);
49
+ usedFallback = true;
50
+ response = null;
51
+ fetchError = null;
52
+ const directHeaders = stripSkalpelHeaders(forwardHeaders);
53
+ try {
54
+ response = await doStreamingFetch(directUrl, body, directHeaders);
55
+ } catch (err) {
56
+ fetchError = err;
57
+ }
58
+ }
59
+ } else {
60
+ try {
61
+ response = await doStreamingFetch(directUrl, body, forwardHeaders);
62
+ } catch (err) {
63
+ fetchError = err;
64
+ }
65
+ }
66
+ if (!response || fetchError) {
67
+ const errMsg = fetchError ? fetchError.message : "no response from upstream";
68
+ logger.error(`streaming fetch failed: ${errMsg}`);
25
69
  res.writeHead(502, {
26
70
  "Content-Type": "text/event-stream",
27
71
  "Cache-Control": "no-cache"
28
72
  });
29
73
  res.write(`event: error
30
- data: ${JSON.stringify({ error: err.message })}
74
+ data: ${JSON.stringify({ error: errMsg })}
31
75
 
32
76
  `);
33
77
  res.end();
34
78
  return;
35
79
  }
36
- const STRIP_HEADERS = /* @__PURE__ */ new Set([
37
- ...HOP_BY_HOP,
38
- "content-encoding",
39
- "content-length"
40
- ]);
80
+ if (usedFallback) {
81
+ logger.info("streaming: using direct Anthropic API fallback");
82
+ }
41
83
  if (response.status >= 300) {
42
84
  const errorBody = Buffer.from(await response.arrayBuffer());
43
85
  logger.error(`streaming upstream error: status=${response.status} body=${errorBody.toString().slice(0, 500)}`);
@@ -103,6 +145,67 @@ function shouldRouteToSkalpel(path4, source) {
103
145
  const pathname = path4.split("?")[0];
104
146
  return SKALPEL_EXACT_PATHS.has(pathname);
105
147
  }
148
+ function isSkalpelBackendFailure2(response, err) {
149
+ if (err) return true;
150
+ if (!response) return true;
151
+ if (response.status >= 500) return true;
152
+ return false;
153
+ }
154
+ function buildForwardHeaders(req, config, source, useSkalpel) {
155
+ const forwardHeaders = {};
156
+ for (const [key, value] of Object.entries(req.headers)) {
157
+ if (value !== void 0) {
158
+ forwardHeaders[key] = Array.isArray(value) ? value.join(", ") : value;
159
+ }
160
+ }
161
+ delete forwardHeaders["host"];
162
+ delete forwardHeaders["connection"];
163
+ if (useSkalpel) {
164
+ forwardHeaders["X-Skalpel-API-Key"] = config.apiKey;
165
+ forwardHeaders["X-Skalpel-Source"] = source;
166
+ forwardHeaders["X-Skalpel-Agent-Type"] = source;
167
+ forwardHeaders["X-Skalpel-SDK-Version"] = "proxy-1.0.0";
168
+ if (source === "claude-code" && !forwardHeaders["x-api-key"]) {
169
+ const authHeader = forwardHeaders["authorization"] ?? "";
170
+ if (authHeader.toLowerCase().startsWith("bearer ")) {
171
+ const token = authHeader.slice(7).trim();
172
+ if (token.startsWith("sk-ant-")) {
173
+ forwardHeaders["x-api-key"] = token;
174
+ }
175
+ }
176
+ }
177
+ }
178
+ return forwardHeaders;
179
+ }
180
+ function stripSkalpelHeaders2(headers) {
181
+ const cleaned = { ...headers };
182
+ delete cleaned["X-Skalpel-API-Key"];
183
+ delete cleaned["X-Skalpel-Source"];
184
+ delete cleaned["X-Skalpel-Agent-Type"];
185
+ delete cleaned["X-Skalpel-SDK-Version"];
186
+ return cleaned;
187
+ }
188
+ var STRIP_RESPONSE_HEADERS = /* @__PURE__ */ new Set([
189
+ "connection",
190
+ "keep-alive",
191
+ "proxy-authenticate",
192
+ "proxy-authorization",
193
+ "te",
194
+ "trailer",
195
+ "transfer-encoding",
196
+ "upgrade",
197
+ "content-encoding",
198
+ "content-length"
199
+ ]);
200
+ function extractResponseHeaders(response) {
201
+ const headers = {};
202
+ for (const [key, value] of response.headers.entries()) {
203
+ if (!STRIP_RESPONSE_HEADERS.has(key)) {
204
+ headers[key] = value;
205
+ }
206
+ }
207
+ return headers;
208
+ }
106
209
  async function handleRequest(req, res, config, source, logger) {
107
210
  const start = Date.now();
108
211
  const method = req.method ?? "GET";
@@ -110,30 +213,7 @@ async function handleRequest(req, res, config, source, logger) {
110
213
  try {
111
214
  const body = await collectBody(req);
112
215
  const useSkalpel = shouldRouteToSkalpel(path4, source);
113
- const forwardUrl = `${useSkalpel ? config.remoteBaseUrl : config.anthropicDirectUrl}${path4}`;
114
- const forwardHeaders = {};
115
- for (const [key, value] of Object.entries(req.headers)) {
116
- if (value !== void 0) {
117
- forwardHeaders[key] = Array.isArray(value) ? value.join(", ") : value;
118
- }
119
- }
120
- delete forwardHeaders["host"];
121
- delete forwardHeaders["connection"];
122
- if (useSkalpel) {
123
- forwardHeaders["X-Skalpel-API-Key"] = config.apiKey;
124
- forwardHeaders["X-Skalpel-Source"] = source;
125
- forwardHeaders["X-Skalpel-Agent-Type"] = source;
126
- forwardHeaders["X-Skalpel-SDK-Version"] = "proxy-1.0.0";
127
- if (source === "claude-code" && !forwardHeaders["x-api-key"]) {
128
- const authHeader = forwardHeaders["authorization"] ?? "";
129
- if (authHeader.toLowerCase().startsWith("bearer ")) {
130
- const token = authHeader.slice(7).trim();
131
- if (token.startsWith("sk-ant-")) {
132
- forwardHeaders["x-api-key"] = token;
133
- }
134
- }
135
- }
136
- }
216
+ const forwardHeaders = buildForwardHeaders(req, config, source, useSkalpel);
137
217
  let isStreaming = false;
138
218
  if (body) {
139
219
  try {
@@ -143,38 +223,52 @@ async function handleRequest(req, res, config, source, logger) {
143
223
  }
144
224
  }
145
225
  if (isStreaming) {
146
- await handleStreamingRequest(req, res, config, source, body, forwardUrl, forwardHeaders, logger);
226
+ const skalpelUrl2 = `${config.remoteBaseUrl}${path4}`;
227
+ const directUrl2 = `${config.anthropicDirectUrl}${path4}`;
228
+ await handleStreamingRequest(req, res, config, source, body, skalpelUrl2, directUrl2, useSkalpel, forwardHeaders, logger);
147
229
  logger.info(`${method} ${path4} source=${source} streaming latency=${Date.now() - start}ms`);
148
230
  return;
149
231
  }
150
- const response = await fetch(forwardUrl, {
151
- method,
152
- headers: forwardHeaders,
153
- body: method !== "GET" && method !== "HEAD" ? body : void 0
154
- });
155
- const STRIP_HEADERS = /* @__PURE__ */ new Set([
156
- "connection",
157
- "keep-alive",
158
- "proxy-authenticate",
159
- "proxy-authorization",
160
- "te",
161
- "trailer",
162
- "transfer-encoding",
163
- "upgrade",
164
- "content-encoding",
165
- "content-length"
166
- ]);
167
- const responseHeaders = {};
168
- for (const [key, value] of response.headers.entries()) {
169
- if (!STRIP_HEADERS.has(key)) {
170
- responseHeaders[key] = value;
232
+ const skalpelUrl = `${config.remoteBaseUrl}${path4}`;
233
+ const directUrl = `${config.anthropicDirectUrl}${path4}`;
234
+ const fetchBody = method !== "GET" && method !== "HEAD" ? body : void 0;
235
+ let response = null;
236
+ let fetchError = null;
237
+ let usedFallback = false;
238
+ if (useSkalpel) {
239
+ try {
240
+ response = await fetch(skalpelUrl, { method, headers: forwardHeaders, body: fetchBody });
241
+ } catch (err) {
242
+ fetchError = err;
243
+ }
244
+ if (isSkalpelBackendFailure2(response, fetchError)) {
245
+ logger.warn(`${method} ${path4} Skalpel backend failed (${fetchError ? fetchError.message : `status ${response?.status}`}), falling back to direct Anthropic API`);
246
+ usedFallback = true;
247
+ response = null;
248
+ fetchError = null;
249
+ const directHeaders = stripSkalpelHeaders2(forwardHeaders);
250
+ try {
251
+ response = await fetch(directUrl, { method, headers: directHeaders, body: fetchBody });
252
+ } catch (err) {
253
+ fetchError = err;
254
+ }
255
+ }
256
+ } else {
257
+ try {
258
+ response = await fetch(directUrl, { method, headers: forwardHeaders, body: fetchBody });
259
+ } catch (err) {
260
+ fetchError = err;
171
261
  }
172
262
  }
263
+ if (!response || fetchError) {
264
+ throw fetchError ?? new Error("no response from upstream");
265
+ }
266
+ const responseHeaders = extractResponseHeaders(response);
173
267
  const responseBody = Buffer.from(await response.arrayBuffer());
174
268
  responseHeaders["content-length"] = String(responseBody.length);
175
269
  res.writeHead(response.status, responseHeaders);
176
270
  res.end(responseBody);
177
- logger.info(`${method} ${path4} source=${source} status=${response.status} latency=${Date.now() - start}ms`);
271
+ logger.info(`${method} ${path4} source=${source} status=${response.status}${usedFallback ? " (fallback)" : ""} latency=${Date.now() - start}ms`);
178
272
  } catch (err) {
179
273
  logger.error(`${method} ${path4} source=${source} error=${err.message}`);
180
274
  if (!res.headersSent) {
@@ -308,6 +402,24 @@ function startProxy(config) {
308
402
  }
309
403
  handleRequest(req, res, config, "codex", logger);
310
404
  });
405
+ anthropicServer.on("error", (err) => {
406
+ if (err.code === "EADDRINUSE") {
407
+ logger.error(`Port ${config.anthropicPort} is already in use. Another Skalpel proxy or process may be running.`);
408
+ } else {
409
+ logger.error(`Anthropic proxy failed to bind port ${config.anthropicPort}: ${err.message}`);
410
+ }
411
+ removePid(config.pidFile);
412
+ process.exit(1);
413
+ });
414
+ openaiServer.on("error", (err) => {
415
+ if (err.code === "EADDRINUSE") {
416
+ logger.error(`Port ${config.openaiPort} is already in use. Another Skalpel proxy or process may be running.`);
417
+ } else {
418
+ logger.error(`OpenAI proxy failed to bind port ${config.openaiPort}: ${err.message}`);
419
+ }
420
+ removePid(config.pidFile);
421
+ process.exit(1);
422
+ });
311
423
  anthropicServer.listen(config.anthropicPort, () => {
312
424
  logger.info(`Anthropic proxy listening on port ${config.anthropicPort}`);
313
425
  });
@@ -325,6 +437,16 @@ function startProxy(config) {
325
437
  };
326
438
  process.on("SIGTERM", cleanup);
327
439
  process.on("SIGINT", cleanup);
440
+ process.on("uncaughtException", (err) => {
441
+ logger.error(`Uncaught exception: ${err.message}`);
442
+ removePid(config.pidFile);
443
+ process.exit(1);
444
+ });
445
+ process.on("unhandledRejection", (reason) => {
446
+ logger.error(`Unhandled rejection: ${reason}`);
447
+ removePid(config.pidFile);
448
+ process.exit(1);
449
+ });
328
450
  return { anthropicServer, openaiServer };
329
451
  }
330
452
  function stopProxy(config) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/proxy/server.ts","../../src/proxy/streaming.ts","../../src/proxy/handler.ts","../../src/proxy/health.ts","../../src/proxy/pid.ts","../../src/proxy/logger.ts","../../src/proxy/config.ts"],"sourcesContent":["import http from 'node:http';\nimport type { ProxyConfig, ProxyStatus } from './types.js';\nimport { handleRequest } from './handler.js';\nimport { handleHealthRequest } from './health.js';\nimport { writePid, readPid, removePid } from './pid.js';\nimport { Logger } from './logger.js';\n\nlet proxyStartTime = 0;\n\nexport function startProxy(config: ProxyConfig): { anthropicServer: http.Server; openaiServer: http.Server } {\n const logger = new Logger(config.logFile, config.logLevel);\n const startTime = Date.now();\n proxyStartTime = Date.now();\n\n const anthropicServer = http.createServer((req, res) => {\n if (req.url === '/health' && req.method === 'GET') {\n handleHealthRequest(res, config, startTime);\n return;\n }\n handleRequest(req, res, config, 'claude-code', logger);\n });\n\n const openaiServer = http.createServer((req, res) => {\n if (req.url === '/health' && req.method === 'GET') {\n handleHealthRequest(res, config, startTime);\n return;\n }\n handleRequest(req, res, config, 'codex', logger);\n });\n\n anthropicServer.listen(config.anthropicPort, () => {\n logger.info(`Anthropic proxy listening on port ${config.anthropicPort}`);\n });\n\n openaiServer.listen(config.openaiPort, () => {\n logger.info(`OpenAI proxy listening on port ${config.openaiPort}`);\n });\n\n writePid(config.pidFile);\n logger.info(`Proxy started (pid=${process.pid}) ports=${config.anthropicPort},${config.openaiPort}`);\n\n const cleanup = () => {\n logger.info('Shutting down proxy...');\n anthropicServer.close();\n openaiServer.close();\n removePid(config.pidFile);\n process.exit(0);\n };\n\n process.on('SIGTERM', cleanup);\n process.on('SIGINT', cleanup);\n\n return { anthropicServer, openaiServer };\n}\n\nexport function stopProxy(config: ProxyConfig): boolean {\n const pid = readPid(config.pidFile);\n if (pid === null) return false;\n\n try {\n process.kill(pid, 'SIGTERM');\n } catch {\n // Process already gone\n }\n\n removePid(config.pidFile);\n return true;\n}\n\nexport function getProxyStatus(config: ProxyConfig): ProxyStatus {\n const pid = readPid(config.pidFile);\n return {\n running: pid !== null,\n pid,\n uptime: proxyStartTime > 0 ? Date.now() - proxyStartTime : 0,\n anthropicPort: config.anthropicPort,\n openaiPort: config.openaiPort,\n };\n}\n","import type { IncomingMessage, ServerResponse } from 'node:http';\nimport type { ProxyConfig } from './types.js';\nimport type { Logger } from './logger.js';\n\nexport async function handleStreamingRequest(\n _req: IncomingMessage,\n res: ServerResponse,\n _config: ProxyConfig,\n _source: string,\n body: string,\n forwardUrl: string,\n forwardHeaders: Record<string, string>,\n logger: Logger,\n): Promise<void> {\n const HOP_BY_HOP = new Set([\n 'connection', 'keep-alive', 'proxy-authenticate', 'proxy-authorization',\n 'te', 'trailer', 'transfer-encoding', 'upgrade',\n ]);\n\n let response: Response;\n try {\n response = await fetch(forwardUrl, {\n method: 'POST',\n headers: forwardHeaders,\n body,\n });\n } catch (err) {\n logger.error(`streaming fetch failed: ${(err as Error).message}`);\n res.writeHead(502, {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n });\n res.write(`event: error\\ndata: ${JSON.stringify({ error: (err as Error).message })}\\n\\n`);\n res.end();\n return;\n }\n\n // Headers to strip: hop-by-hop + content-encoding/content-length (fetch\n // auto-decompresses, so forwarding the original encoding header causes\n // the client to try decompressing already-decompressed data → ZlibError)\n const STRIP_HEADERS = new Set([\n ...HOP_BY_HOP,\n 'content-encoding', 'content-length',\n ]);\n\n // For non-2xx responses, pass through as-is so the SDK can parse error bodies correctly\n if (response.status >= 300) {\n const errorBody = Buffer.from(await response.arrayBuffer());\n logger.error(`streaming upstream error: status=${response.status} body=${errorBody.toString().slice(0, 500)}`);\n const passthroughHeaders: Record<string, string> = {};\n for (const [key, value] of response.headers.entries()) {\n if (!STRIP_HEADERS.has(key)) {\n passthroughHeaders[key] = value;\n }\n }\n passthroughHeaders['content-length'] = String(errorBody.length);\n res.writeHead(response.status, passthroughHeaders);\n res.end(errorBody);\n return;\n }\n\n // Build SSE headers, stripping hop-by-hop/encoding and normalizing content-type\n const sseHeaders: Record<string, string> = {};\n for (const [key, value] of response.headers.entries()) {\n if (!STRIP_HEADERS.has(key) && key !== 'content-type') {\n sseHeaders[key] = value;\n }\n }\n sseHeaders['Content-Type'] = 'text/event-stream';\n sseHeaders['Cache-Control'] = 'no-cache';\n res.writeHead(response.status, sseHeaders);\n\n if (!response.body) {\n res.write(`event: error\\ndata: ${JSON.stringify({ error: 'no response body' })}\\n\\n`);\n res.end();\n return;\n }\n\n try {\n const reader = (response.body as ReadableStream<Uint8Array>).getReader();\n const decoder = new TextDecoder();\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n const chunk = decoder.decode(value, { stream: true });\n res.write(chunk);\n }\n } catch (err) {\n logger.error(`streaming error: ${(err as Error).message}`);\n res.write(`event: error\\ndata: ${JSON.stringify({ error: (err as Error).message })}\\n\\n`);\n }\n\n res.end();\n}\n","import type { IncomingMessage, ServerResponse } from 'node:http';\nimport type { ProxyConfig } from './types.js';\nimport { handleStreamingRequest } from './streaming.js';\nimport type { Logger } from './logger.js';\n\n// Exact paths that should route through Skalpel optimization for Claude Code.\n// Sub-paths like /v1/messages/count_tokens go direct to Anthropic.\nconst SKALPEL_EXACT_PATHS = new Set(['/v1/messages']);\n\nfunction collectBody(req: IncomingMessage): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on('data', (chunk: Buffer) => chunks.push(chunk));\n req.on('end', () => resolve(Buffer.concat(chunks).toString()));\n req.on('error', reject);\n });\n}\n\nexport function shouldRouteToSkalpel(path: string, source: string): boolean {\n if (source !== 'claude-code') return true;\n // Strip query string — Claude Code sends /v1/messages?beta=true\n const pathname = path.split('?')[0];\n return SKALPEL_EXACT_PATHS.has(pathname);\n}\n\nexport async function handleRequest(\n req: IncomingMessage,\n res: ServerResponse,\n config: ProxyConfig,\n source: string,\n logger: Logger,\n): Promise<void> {\n const start = Date.now();\n const method = req.method ?? 'GET';\n const path = req.url ?? '/';\n\n try {\n const body = await collectBody(req);\n const useSkalpel = shouldRouteToSkalpel(path, source);\n const forwardUrl = `${useSkalpel ? config.remoteBaseUrl : config.anthropicDirectUrl}${path}`;\n\n const forwardHeaders: Record<string, string> = {};\n for (const [key, value] of Object.entries(req.headers)) {\n if (value !== undefined) {\n forwardHeaders[key] = Array.isArray(value) ? value.join(', ') : value;\n }\n }\n delete forwardHeaders['host'];\n delete forwardHeaders['connection'];\n\n if (useSkalpel) {\n forwardHeaders['X-Skalpel-API-Key'] = config.apiKey;\n forwardHeaders['X-Skalpel-Source'] = source;\n forwardHeaders['X-Skalpel-Agent-Type'] = source;\n forwardHeaders['X-Skalpel-SDK-Version'] = 'proxy-1.0.0';\n\n // By default, requests go through Skalpel's optimization pipeline.\n // The backend resolves provider credentials from its KeyVault/DB.\n // Passthrough mode (skipping optimization) can be enabled per-request\n // by setting the X-Skalpel-Auth-Mode header on the client side.\n\n // Claude Code may send either x-api-key (API key auth) or\n // Authorization: Bearer (OAuth auth). Only convert Bearer to x-api-key\n // for actual API keys (sk-ant-*). OAuth tokens must stay as\n // Authorization: Bearer — Anthropic rejects them in x-api-key.\n if (source === 'claude-code' && !forwardHeaders['x-api-key']) {\n const authHeader = forwardHeaders['authorization'] ?? '';\n if (authHeader.toLowerCase().startsWith('bearer ')) {\n const token = authHeader.slice(7).trim();\n if (token.startsWith('sk-ant-')) {\n forwardHeaders['x-api-key'] = token;\n }\n // else: OAuth token — leave Authorization: Bearer intact\n }\n }\n }\n\n let isStreaming = false;\n if (body) {\n try {\n const parsed = JSON.parse(body);\n isStreaming = parsed.stream === true;\n } catch {\n // Not JSON — treat as non-streaming\n }\n }\n\n if (isStreaming) {\n await handleStreamingRequest(req, res, config, source, body, forwardUrl, forwardHeaders, logger);\n logger.info(`${method} ${path} source=${source} streaming latency=${Date.now() - start}ms`);\n return;\n }\n\n const response = await fetch(forwardUrl, {\n method,\n headers: forwardHeaders,\n body: method !== 'GET' && method !== 'HEAD' ? body : undefined,\n });\n\n // Strip hop-by-hop headers that should not be forwarded by a proxy.\n // Also strip content-encoding and content-length because fetch()\n // automatically decompresses gzip/deflate/br responses — the body we\n // read via arrayBuffer() is already decompressed, so forwarding the\n // original content-encoding header causes the client (Claude Code) to\n // try to decompress plain text → ZlibError.\n const STRIP_HEADERS = new Set([\n 'connection', 'keep-alive', 'proxy-authenticate', 'proxy-authorization',\n 'te', 'trailer', 'transfer-encoding', 'upgrade',\n 'content-encoding', 'content-length',\n ]);\n const responseHeaders: Record<string, string> = {};\n for (const [key, value] of response.headers.entries()) {\n if (!STRIP_HEADERS.has(key)) {\n responseHeaders[key] = value;\n }\n }\n\n const responseBody = Buffer.from(await response.arrayBuffer());\n responseHeaders['content-length'] = String(responseBody.length);\n res.writeHead(response.status, responseHeaders);\n res.end(responseBody);\n\n logger.info(`${method} ${path} source=${source} status=${response.status} latency=${Date.now() - start}ms`);\n } catch (err) {\n logger.error(`${method} ${path} source=${source} error=${(err as Error).message}`);\n if (!res.headersSent) {\n res.writeHead(502, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'proxy_error', message: (err as Error).message }));\n }\n }\n}\n","import type { ServerResponse } from 'node:http';\nimport type { ProxyConfig } from './types.js';\n\nexport function handleHealthRequest(\n res: ServerResponse,\n config: ProxyConfig,\n startTime: number,\n): void {\n const body = JSON.stringify({\n status: 'ok',\n uptime: Date.now() - startTime,\n ports: {\n anthropic: config.anthropicPort,\n openai: config.openaiPort,\n },\n version: 'proxy-1.0.0',\n });\n\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(body);\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\n\nexport function writePid(pidFile: string): void {\n fs.mkdirSync(path.dirname(pidFile), { recursive: true });\n fs.writeFileSync(pidFile, String(process.pid));\n}\n\nexport function readPid(pidFile: string): number | null {\n try {\n const raw = fs.readFileSync(pidFile, 'utf-8').trim();\n const pid = parseInt(raw, 10);\n if (isNaN(pid)) return null;\n return isRunning(pid) ? pid : null;\n } catch {\n return null;\n }\n}\n\nexport function isRunning(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\nexport function removePid(pidFile: string): void {\n try {\n fs.unlinkSync(pidFile);\n } catch {\n // Already removed\n }\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\n\nconst MAX_SIZE = 5 * 1024 * 1024; // 5MB\nconst MAX_ROTATIONS = 3;\n\nconst LEVELS = { debug: 0, info: 1, warn: 2, error: 3 } as const;\n\nexport class Logger {\n private logFile: string;\n private level: keyof typeof LEVELS;\n\n constructor(logFile: string, level: keyof typeof LEVELS = 'info') {\n this.logFile = logFile;\n this.level = level;\n fs.mkdirSync(path.dirname(logFile), { recursive: true });\n }\n\n debug(msg: string): void { this.log('debug', msg); }\n info(msg: string): void { this.log('info', msg); }\n warn(msg: string): void { this.log('warn', msg); }\n error(msg: string): void { this.log('error', msg); }\n\n private log(level: keyof typeof LEVELS, msg: string): void {\n if (LEVELS[level] < LEVELS[this.level]) return;\n\n const line = `${new Date().toISOString()} [${level.toUpperCase()}] ${msg}\\n`;\n\n if (level === 'debug' || level === 'error') {\n process.stderr.write(line);\n }\n\n try {\n this.rotate();\n fs.appendFileSync(this.logFile, line);\n } catch {\n // Best-effort logging\n }\n }\n\n private rotate(): void {\n try {\n const stat = fs.statSync(this.logFile);\n if (stat.size < MAX_SIZE) return;\n } catch {\n return;\n }\n\n for (let i = MAX_ROTATIONS; i >= 1; i--) {\n const src = i === 1 ? this.logFile : `${this.logFile}.${i - 1}`;\n const dst = `${this.logFile}.${i}`;\n try {\n fs.renameSync(src, dst);\n } catch {\n // File may not exist\n }\n }\n }\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport os from 'node:os';\nimport type { ProxyConfig } from './types.js';\n\nfunction expandHome(filePath: string): string {\n if (filePath.startsWith('~')) {\n return path.join(os.homedir(), filePath.slice(1));\n }\n return filePath;\n}\n\nconst DEFAULTS: ProxyConfig = {\n apiKey: '',\n remoteBaseUrl: 'https://api.skalpel.ai',\n anthropicDirectUrl: 'https://api.anthropic.com',\n anthropicPort: 18100,\n openaiPort: 18101,\n logLevel: 'info',\n logFile: '~/.skalpel/logs/proxy.log',\n pidFile: '~/.skalpel/proxy.pid',\n configFile: '~/.skalpel/config.json',\n};\n\nexport function loadConfig(configPath?: string): ProxyConfig {\n const filePath = expandHome(configPath ?? DEFAULTS.configFile);\n let fileConfig: Partial<ProxyConfig> = {};\n\n try {\n const raw = fs.readFileSync(filePath, 'utf-8');\n fileConfig = JSON.parse(raw) as Partial<ProxyConfig>;\n } catch {\n // Config file doesn't exist or is invalid — use defaults\n }\n\n return {\n apiKey: fileConfig.apiKey ?? DEFAULTS.apiKey,\n remoteBaseUrl: fileConfig.remoteBaseUrl ?? DEFAULTS.remoteBaseUrl,\n anthropicDirectUrl: fileConfig.anthropicDirectUrl ?? DEFAULTS.anthropicDirectUrl,\n anthropicPort: fileConfig.anthropicPort ?? DEFAULTS.anthropicPort,\n openaiPort: fileConfig.openaiPort ?? DEFAULTS.openaiPort,\n logLevel: fileConfig.logLevel ?? DEFAULTS.logLevel,\n logFile: expandHome(fileConfig.logFile ?? DEFAULTS.logFile),\n pidFile: expandHome(fileConfig.pidFile ?? DEFAULTS.pidFile),\n configFile: filePath,\n };\n}\n\nexport function saveConfig(config: ProxyConfig): void {\n const dir = path.dirname(config.configFile);\n fs.mkdirSync(dir, { recursive: true });\n fs.writeFileSync(config.configFile, JSON.stringify(config, null, 2) + '\\n');\n}\n"],"mappings":"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","names":["path","fs","path","fs","path"]}
1
+ {"version":3,"sources":["../../src/proxy/server.ts","../../src/proxy/streaming.ts","../../src/proxy/handler.ts","../../src/proxy/health.ts","../../src/proxy/pid.ts","../../src/proxy/logger.ts","../../src/proxy/config.ts"],"sourcesContent":["import http from 'node:http';\nimport type { ProxyConfig, ProxyStatus } from './types.js';\nimport { handleRequest } from './handler.js';\nimport { handleHealthRequest } from './health.js';\nimport { writePid, readPid, removePid } from './pid.js';\nimport { Logger } from './logger.js';\n\nlet proxyStartTime = 0;\n\nexport function startProxy(config: ProxyConfig): { anthropicServer: http.Server; openaiServer: http.Server } {\n const logger = new Logger(config.logFile, config.logLevel);\n const startTime = Date.now();\n proxyStartTime = Date.now();\n\n const anthropicServer = http.createServer((req, res) => {\n if (req.url === '/health' && req.method === 'GET') {\n handleHealthRequest(res, config, startTime);\n return;\n }\n handleRequest(req, res, config, 'claude-code', logger);\n });\n\n const openaiServer = http.createServer((req, res) => {\n if (req.url === '/health' && req.method === 'GET') {\n handleHealthRequest(res, config, startTime);\n return;\n }\n handleRequest(req, res, config, 'codex', logger);\n });\n\n // Handle port binding errors (EADDRINUSE, EACCES, etc.)\n anthropicServer.on('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EADDRINUSE') {\n logger.error(`Port ${config.anthropicPort} is already in use. Another Skalpel proxy or process may be running.`);\n } else {\n logger.error(`Anthropic proxy failed to bind port ${config.anthropicPort}: ${err.message}`);\n }\n removePid(config.pidFile);\n process.exit(1);\n });\n\n openaiServer.on('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EADDRINUSE') {\n logger.error(`Port ${config.openaiPort} is already in use. Another Skalpel proxy or process may be running.`);\n } else {\n logger.error(`OpenAI proxy failed to bind port ${config.openaiPort}: ${err.message}`);\n }\n removePid(config.pidFile);\n process.exit(1);\n });\n\n anthropicServer.listen(config.anthropicPort, () => {\n logger.info(`Anthropic proxy listening on port ${config.anthropicPort}`);\n });\n\n openaiServer.listen(config.openaiPort, () => {\n logger.info(`OpenAI proxy listening on port ${config.openaiPort}`);\n });\n\n writePid(config.pidFile);\n logger.info(`Proxy started (pid=${process.pid}) ports=${config.anthropicPort},${config.openaiPort}`);\n\n const cleanup = () => {\n logger.info('Shutting down proxy...');\n anthropicServer.close();\n openaiServer.close();\n removePid(config.pidFile);\n process.exit(0);\n };\n\n process.on('SIGTERM', cleanup);\n process.on('SIGINT', cleanup);\n\n // Catch unexpected errors so PID file is always cleaned up\n process.on('uncaughtException', (err) => {\n logger.error(`Uncaught exception: ${err.message}`);\n removePid(config.pidFile);\n process.exit(1);\n });\n\n process.on('unhandledRejection', (reason) => {\n logger.error(`Unhandled rejection: ${reason}`);\n removePid(config.pidFile);\n process.exit(1);\n });\n\n return { anthropicServer, openaiServer };\n}\n\nexport function stopProxy(config: ProxyConfig): boolean {\n const pid = readPid(config.pidFile);\n if (pid === null) return false;\n\n try {\n process.kill(pid, 'SIGTERM');\n } catch {\n // Process already gone\n }\n\n removePid(config.pidFile);\n return true;\n}\n\nexport function getProxyStatus(config: ProxyConfig): ProxyStatus {\n const pid = readPid(config.pidFile);\n return {\n running: pid !== null,\n pid,\n uptime: proxyStartTime > 0 ? Date.now() - proxyStartTime : 0,\n anthropicPort: config.anthropicPort,\n openaiPort: config.openaiPort,\n };\n}\n","import type { IncomingMessage, ServerResponse } from 'node:http';\nimport type { ProxyConfig } from './types.js';\nimport type { Logger } from './logger.js';\n\nconst HOP_BY_HOP = new Set([\n 'connection', 'keep-alive', 'proxy-authenticate', 'proxy-authorization',\n 'te', 'trailer', 'transfer-encoding', 'upgrade',\n]);\n\nconst STRIP_HEADERS = new Set([\n ...HOP_BY_HOP,\n 'content-encoding', 'content-length',\n]);\n\n/** Remove Skalpel-specific headers so a direct-to-Anthropic fallback request is clean. */\nfunction stripSkalpelHeaders(headers: Record<string, string>): Record<string, string> {\n const cleaned = { ...headers };\n delete cleaned['X-Skalpel-API-Key'];\n delete cleaned['X-Skalpel-Source'];\n delete cleaned['X-Skalpel-Agent-Type'];\n delete cleaned['X-Skalpel-SDK-Version'];\n return cleaned;\n}\n\n/** Returns true if the error or HTTP status indicates the Skalpel backend\n * itself is unreachable or broken (not a normal API error from Anthropic). */\nfunction isSkalpelBackendFailure(response: Response | null, err: unknown): boolean {\n if (err) return true;\n if (!response) return true;\n if (response.status >= 500) return true;\n return false;\n}\n\nasync function doStreamingFetch(\n url: string,\n body: string,\n headers: Record<string, string>,\n): Promise<Response> {\n return fetch(url, { method: 'POST', headers, body });\n}\n\nexport async function handleStreamingRequest(\n _req: IncomingMessage,\n res: ServerResponse,\n _config: ProxyConfig,\n _source: string,\n body: string,\n skalpelUrl: string,\n directUrl: string,\n useSkalpel: boolean,\n forwardHeaders: Record<string, string>,\n logger: Logger,\n): Promise<void> {\n let response: Response | null = null;\n let fetchError: unknown = null;\n let usedFallback = false;\n\n // Try Skalpel backend first (if this request should be optimized)\n if (useSkalpel) {\n try {\n response = await doStreamingFetch(skalpelUrl, body, forwardHeaders);\n } catch (err) {\n fetchError = err;\n }\n\n // If Skalpel backend is down, fall back to direct Anthropic API\n if (isSkalpelBackendFailure(response, fetchError)) {\n logger.warn(`streaming: Skalpel backend failed (${fetchError ? (fetchError as Error).message : `status ${response?.status}`}), falling back to direct Anthropic API`);\n usedFallback = true;\n response = null;\n fetchError = null;\n const directHeaders = stripSkalpelHeaders(forwardHeaders);\n try {\n response = await doStreamingFetch(directUrl, body, directHeaders);\n } catch (err) {\n fetchError = err;\n }\n }\n } else {\n // Non-Skalpel path — go direct\n try {\n response = await doStreamingFetch(directUrl, body, forwardHeaders);\n } catch (err) {\n fetchError = err;\n }\n }\n\n // If even the direct request failed, return error\n if (!response || fetchError) {\n const errMsg = fetchError ? (fetchError as Error).message : 'no response from upstream';\n logger.error(`streaming fetch failed: ${errMsg}`);\n res.writeHead(502, {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n });\n res.write(`event: error\\ndata: ${JSON.stringify({ error: errMsg })}\\n\\n`);\n res.end();\n return;\n }\n\n if (usedFallback) {\n logger.info('streaming: using direct Anthropic API fallback');\n }\n\n // For non-2xx responses, pass through as-is so the SDK can parse error bodies correctly\n if (response.status >= 300) {\n const errorBody = Buffer.from(await response.arrayBuffer());\n logger.error(`streaming upstream error: status=${response.status} body=${errorBody.toString().slice(0, 500)}`);\n const passthroughHeaders: Record<string, string> = {};\n for (const [key, value] of response.headers.entries()) {\n if (!STRIP_HEADERS.has(key)) {\n passthroughHeaders[key] = value;\n }\n }\n passthroughHeaders['content-length'] = String(errorBody.length);\n res.writeHead(response.status, passthroughHeaders);\n res.end(errorBody);\n return;\n }\n\n // Build SSE headers, stripping hop-by-hop/encoding and normalizing content-type\n const sseHeaders: Record<string, string> = {};\n for (const [key, value] of response.headers.entries()) {\n if (!STRIP_HEADERS.has(key) && key !== 'content-type') {\n sseHeaders[key] = value;\n }\n }\n sseHeaders['Content-Type'] = 'text/event-stream';\n sseHeaders['Cache-Control'] = 'no-cache';\n res.writeHead(response.status, sseHeaders);\n\n if (!response.body) {\n res.write(`event: error\\ndata: ${JSON.stringify({ error: 'no response body' })}\\n\\n`);\n res.end();\n return;\n }\n\n try {\n const reader = (response.body as ReadableStream<Uint8Array>).getReader();\n const decoder = new TextDecoder();\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n const chunk = decoder.decode(value, { stream: true });\n res.write(chunk);\n }\n } catch (err) {\n logger.error(`streaming error: ${(err as Error).message}`);\n res.write(`event: error\\ndata: ${JSON.stringify({ error: (err as Error).message })}\\n\\n`);\n }\n\n res.end();\n}\n","import type { IncomingMessage, ServerResponse } from 'node:http';\nimport type { ProxyConfig } from './types.js';\nimport { handleStreamingRequest } from './streaming.js';\nimport type { Logger } from './logger.js';\n\n// Exact paths that should route through Skalpel optimization for Claude Code.\n// Sub-paths like /v1/messages/count_tokens go direct to Anthropic.\nconst SKALPEL_EXACT_PATHS = new Set(['/v1/messages']);\n\nfunction collectBody(req: IncomingMessage): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on('data', (chunk: Buffer) => chunks.push(chunk));\n req.on('end', () => resolve(Buffer.concat(chunks).toString()));\n req.on('error', reject);\n });\n}\n\nexport function shouldRouteToSkalpel(path: string, source: string): boolean {\n if (source !== 'claude-code') return true;\n // Strip query string — Claude Code sends /v1/messages?beta=true\n const pathname = path.split('?')[0];\n return SKALPEL_EXACT_PATHS.has(pathname);\n}\n\n/** Returns true if the error or HTTP status indicates the Skalpel backend\n * itself is unreachable or broken (not a normal API error from Anthropic). */\nfunction isSkalpelBackendFailure(response: Response | null, err: unknown): boolean {\n // Network-level failure (DNS, connection refused, timeout, etc.)\n if (err) return true;\n if (!response) return true;\n // 5xx from the Skalpel backend means the backend is having issues\n if (response.status >= 500) return true;\n return false;\n}\n\n/** Build the set of headers to forward, adding Skalpel-specific headers when routing through Skalpel. */\nexport function buildForwardHeaders(\n req: IncomingMessage,\n config: ProxyConfig,\n source: string,\n useSkalpel: boolean,\n): Record<string, string> {\n const forwardHeaders: Record<string, string> = {};\n for (const [key, value] of Object.entries(req.headers)) {\n if (value !== undefined) {\n forwardHeaders[key] = Array.isArray(value) ? value.join(', ') : value;\n }\n }\n delete forwardHeaders['host'];\n delete forwardHeaders['connection'];\n\n if (useSkalpel) {\n forwardHeaders['X-Skalpel-API-Key'] = config.apiKey;\n forwardHeaders['X-Skalpel-Source'] = source;\n forwardHeaders['X-Skalpel-Agent-Type'] = source;\n forwardHeaders['X-Skalpel-SDK-Version'] = 'proxy-1.0.0';\n\n // Claude Code may send either x-api-key (API key auth) or\n // Authorization: Bearer (OAuth auth). Only convert Bearer to x-api-key\n // for actual API keys (sk-ant-*). OAuth tokens must stay as\n // Authorization: Bearer — Anthropic rejects them in x-api-key.\n if (source === 'claude-code' && !forwardHeaders['x-api-key']) {\n const authHeader = forwardHeaders['authorization'] ?? '';\n if (authHeader.toLowerCase().startsWith('bearer ')) {\n const token = authHeader.slice(7).trim();\n if (token.startsWith('sk-ant-')) {\n forwardHeaders['x-api-key'] = token;\n }\n }\n }\n }\n\n return forwardHeaders;\n}\n\n/** Remove Skalpel-specific headers so a direct-to-Anthropic fallback request is clean. */\nfunction stripSkalpelHeaders(headers: Record<string, string>): Record<string, string> {\n const cleaned = { ...headers };\n delete cleaned['X-Skalpel-API-Key'];\n delete cleaned['X-Skalpel-Source'];\n delete cleaned['X-Skalpel-Agent-Type'];\n delete cleaned['X-Skalpel-SDK-Version'];\n return cleaned;\n}\n\n// Headers that should not be forwarded by a proxy.\n// Also strips content-encoding and content-length because fetch()\n// automatically decompresses gzip/deflate/br responses — the body we\n// read via arrayBuffer() is already decompressed, so forwarding the\n// original content-encoding header causes the client to try to decompress\n// plain text → ZlibError.\nconst STRIP_RESPONSE_HEADERS = new Set([\n 'connection', 'keep-alive', 'proxy-authenticate', 'proxy-authorization',\n 'te', 'trailer', 'transfer-encoding', 'upgrade',\n 'content-encoding', 'content-length',\n]);\n\nfunction extractResponseHeaders(response: Response): Record<string, string> {\n const headers: Record<string, string> = {};\n for (const [key, value] of response.headers.entries()) {\n if (!STRIP_RESPONSE_HEADERS.has(key)) {\n headers[key] = value;\n }\n }\n return headers;\n}\n\nexport async function handleRequest(\n req: IncomingMessage,\n res: ServerResponse,\n config: ProxyConfig,\n source: string,\n logger: Logger,\n): Promise<void> {\n const start = Date.now();\n const method = req.method ?? 'GET';\n const path = req.url ?? '/';\n\n try {\n const body = await collectBody(req);\n const useSkalpel = shouldRouteToSkalpel(path, source);\n const forwardHeaders = buildForwardHeaders(req, config, source, useSkalpel);\n\n let isStreaming = false;\n if (body) {\n try {\n const parsed = JSON.parse(body);\n isStreaming = parsed.stream === true;\n } catch {\n // Not JSON — treat as non-streaming\n }\n }\n\n if (isStreaming) {\n const skalpelUrl = `${config.remoteBaseUrl}${path}`;\n const directUrl = `${config.anthropicDirectUrl}${path}`;\n await handleStreamingRequest(req, res, config, source, body, skalpelUrl, directUrl, useSkalpel, forwardHeaders, logger);\n logger.info(`${method} ${path} source=${source} streaming latency=${Date.now() - start}ms`);\n return;\n }\n\n const skalpelUrl = `${config.remoteBaseUrl}${path}`;\n const directUrl = `${config.anthropicDirectUrl}${path}`;\n const fetchBody = method !== 'GET' && method !== 'HEAD' ? body : undefined;\n\n let response: Response | null = null;\n let fetchError: unknown = null;\n let usedFallback = false;\n\n // Try Skalpel backend first (if this request should be optimized)\n if (useSkalpel) {\n try {\n response = await fetch(skalpelUrl, { method, headers: forwardHeaders, body: fetchBody });\n } catch (err) {\n fetchError = err;\n }\n\n // If Skalpel backend is down, fall back to direct Anthropic API\n if (isSkalpelBackendFailure(response, fetchError)) {\n logger.warn(`${method} ${path} Skalpel backend failed (${fetchError ? (fetchError as Error).message : `status ${response?.status}`}), falling back to direct Anthropic API`);\n usedFallback = true;\n response = null;\n fetchError = null;\n const directHeaders = stripSkalpelHeaders(forwardHeaders);\n try {\n response = await fetch(directUrl, { method, headers: directHeaders, body: fetchBody });\n } catch (err) {\n fetchError = err;\n }\n }\n } else {\n // Non-Skalpel path — go direct\n try {\n response = await fetch(directUrl, { method, headers: forwardHeaders, body: fetchBody });\n } catch (err) {\n fetchError = err;\n }\n }\n\n // If even the direct request failed, return 502\n if (!response || fetchError) {\n throw fetchError ?? new Error('no response from upstream');\n }\n\n const responseHeaders = extractResponseHeaders(response);\n const responseBody = Buffer.from(await response.arrayBuffer());\n responseHeaders['content-length'] = String(responseBody.length);\n res.writeHead(response.status, responseHeaders);\n res.end(responseBody);\n\n logger.info(`${method} ${path} source=${source} status=${response.status}${usedFallback ? ' (fallback)' : ''} latency=${Date.now() - start}ms`);\n } catch (err) {\n logger.error(`${method} ${path} source=${source} error=${(err as Error).message}`);\n if (!res.headersSent) {\n res.writeHead(502, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'proxy_error', message: (err as Error).message }));\n }\n }\n}\n","import type { ServerResponse } from 'node:http';\nimport type { ProxyConfig } from './types.js';\n\nexport function handleHealthRequest(\n res: ServerResponse,\n config: ProxyConfig,\n startTime: number,\n): void {\n const body = JSON.stringify({\n status: 'ok',\n uptime: Date.now() - startTime,\n ports: {\n anthropic: config.anthropicPort,\n openai: config.openaiPort,\n },\n version: 'proxy-1.0.0',\n });\n\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(body);\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\n\nexport function writePid(pidFile: string): void {\n fs.mkdirSync(path.dirname(pidFile), { recursive: true });\n fs.writeFileSync(pidFile, String(process.pid));\n}\n\nexport function readPid(pidFile: string): number | null {\n try {\n const raw = fs.readFileSync(pidFile, 'utf-8').trim();\n const pid = parseInt(raw, 10);\n if (isNaN(pid)) return null;\n return isRunning(pid) ? pid : null;\n } catch {\n return null;\n }\n}\n\nexport function isRunning(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\nexport function removePid(pidFile: string): void {\n try {\n fs.unlinkSync(pidFile);\n } catch {\n // Already removed\n }\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\n\nconst MAX_SIZE = 5 * 1024 * 1024; // 5MB\nconst MAX_ROTATIONS = 3;\n\nconst LEVELS = { debug: 0, info: 1, warn: 2, error: 3 } as const;\n\nexport class Logger {\n private logFile: string;\n private level: keyof typeof LEVELS;\n\n constructor(logFile: string, level: keyof typeof LEVELS = 'info') {\n this.logFile = logFile;\n this.level = level;\n fs.mkdirSync(path.dirname(logFile), { recursive: true });\n }\n\n debug(msg: string): void { this.log('debug', msg); }\n info(msg: string): void { this.log('info', msg); }\n warn(msg: string): void { this.log('warn', msg); }\n error(msg: string): void { this.log('error', msg); }\n\n private log(level: keyof typeof LEVELS, msg: string): void {\n if (LEVELS[level] < LEVELS[this.level]) return;\n\n const line = `${new Date().toISOString()} [${level.toUpperCase()}] ${msg}\\n`;\n\n if (level === 'debug' || level === 'error') {\n process.stderr.write(line);\n }\n\n try {\n this.rotate();\n fs.appendFileSync(this.logFile, line);\n } catch {\n // Best-effort logging\n }\n }\n\n private rotate(): void {\n try {\n const stat = fs.statSync(this.logFile);\n if (stat.size < MAX_SIZE) return;\n } catch {\n return;\n }\n\n for (let i = MAX_ROTATIONS; i >= 1; i--) {\n const src = i === 1 ? this.logFile : `${this.logFile}.${i - 1}`;\n const dst = `${this.logFile}.${i}`;\n try {\n fs.renameSync(src, dst);\n } catch {\n // File may not exist\n }\n }\n }\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport os from 'node:os';\nimport type { ProxyConfig } from './types.js';\n\nfunction expandHome(filePath: string): string {\n if (filePath.startsWith('~')) {\n return path.join(os.homedir(), filePath.slice(1));\n }\n return filePath;\n}\n\nconst DEFAULTS: ProxyConfig = {\n apiKey: '',\n remoteBaseUrl: 'https://api.skalpel.ai',\n anthropicDirectUrl: 'https://api.anthropic.com',\n anthropicPort: 18100,\n openaiPort: 18101,\n logLevel: 'info',\n logFile: '~/.skalpel/logs/proxy.log',\n pidFile: '~/.skalpel/proxy.pid',\n configFile: '~/.skalpel/config.json',\n};\n\nexport function loadConfig(configPath?: string): ProxyConfig {\n const filePath = expandHome(configPath ?? DEFAULTS.configFile);\n let fileConfig: Partial<ProxyConfig> = {};\n\n try {\n const raw = fs.readFileSync(filePath, 'utf-8');\n fileConfig = JSON.parse(raw) as Partial<ProxyConfig>;\n } catch {\n // Config file doesn't exist or is invalid — use defaults\n }\n\n return {\n apiKey: fileConfig.apiKey ?? DEFAULTS.apiKey,\n remoteBaseUrl: fileConfig.remoteBaseUrl ?? DEFAULTS.remoteBaseUrl,\n anthropicDirectUrl: fileConfig.anthropicDirectUrl ?? DEFAULTS.anthropicDirectUrl,\n anthropicPort: fileConfig.anthropicPort ?? DEFAULTS.anthropicPort,\n openaiPort: fileConfig.openaiPort ?? DEFAULTS.openaiPort,\n logLevel: fileConfig.logLevel ?? DEFAULTS.logLevel,\n logFile: expandHome(fileConfig.logFile ?? DEFAULTS.logFile),\n pidFile: expandHome(fileConfig.pidFile ?? DEFAULTS.pidFile),\n configFile: filePath,\n };\n}\n\nexport function saveConfig(config: ProxyConfig): void {\n const dir = path.dirname(config.configFile);\n fs.mkdirSync(dir, { recursive: true });\n fs.writeFileSync(config.configFile, JSON.stringify(config, null, 2) + '\\n');\n}\n"],"mappings":"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","names":["path","isSkalpelBackendFailure","stripSkalpelHeaders","skalpelUrl","directUrl","fs","path","fs","path"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skalpel",
3
- "version": "2.0.6",
3
+ "version": "2.0.7",
4
4
  "type": "module",
5
5
  "description": "Skalpel AI SDK — optimize your OpenAI and Anthropic API calls",
6
6
  "main": "./dist/index.cjs",