skalpel 2.0.21 → 2.0.23
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/cli/index.js +11 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/proxy-runner.js +67 -15
- package/dist/cli/proxy-runner.js.map +1 -1
- package/dist/index.cjs +67 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +67 -15
- package/dist/index.js.map +1 -1
- package/dist/proxy/index.cjs +67 -15
- package/dist/proxy/index.cjs.map +1 -1
- package/dist/proxy/index.js +67 -15
- package/dist/proxy/index.js.map +1 -1
- package/package.json +1 -1
package/dist/proxy/index.cjs
CHANGED
|
@@ -225,6 +225,37 @@ var init_fetch_error = __esm({
|
|
|
225
225
|
}
|
|
226
226
|
});
|
|
227
227
|
|
|
228
|
+
// src/proxy/trace-context.ts
|
|
229
|
+
function parseTraceparent(header) {
|
|
230
|
+
if (!header) return null;
|
|
231
|
+
const trimmed = header.trim().toLowerCase();
|
|
232
|
+
const match = trimmed.match(TRACEPARENT_REGEX);
|
|
233
|
+
if (!match) return null;
|
|
234
|
+
const [, traceId, spanId, traceFlags] = match;
|
|
235
|
+
if (traceId === "00000000000000000000000000000000") return null;
|
|
236
|
+
if (spanId === "0000000000000000") return null;
|
|
237
|
+
return { traceId, spanId, traceFlags };
|
|
238
|
+
}
|
|
239
|
+
function generateTraceContext() {
|
|
240
|
+
return {
|
|
241
|
+
traceId: (0, import_node_crypto2.randomBytes)(16).toString("hex"),
|
|
242
|
+
spanId: (0, import_node_crypto2.randomBytes)(8).toString("hex"),
|
|
243
|
+
traceFlags: "01"
|
|
244
|
+
// sampled
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
function formatTraceparent(ctx) {
|
|
248
|
+
return `00-${ctx.traceId}-${ctx.spanId}-${ctx.traceFlags}`;
|
|
249
|
+
}
|
|
250
|
+
var import_node_crypto2, TRACEPARENT_REGEX;
|
|
251
|
+
var init_trace_context = __esm({
|
|
252
|
+
"src/proxy/trace-context.ts"() {
|
|
253
|
+
"use strict";
|
|
254
|
+
import_node_crypto2 = require("crypto");
|
|
255
|
+
TRACEPARENT_REGEX = /^00-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/;
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
|
|
228
259
|
// src/proxy/streaming.ts
|
|
229
260
|
function parseRetryAfter(header) {
|
|
230
261
|
if (!header) return void 0;
|
|
@@ -251,7 +282,7 @@ function stripSkalpelHeaders(headers) {
|
|
|
251
282
|
async function doStreamingFetch(url, body, headers) {
|
|
252
283
|
return fetch(url, { method: "POST", headers, body, dispatcher: skalpelDispatcher });
|
|
253
284
|
}
|
|
254
|
-
async function handleStreamingRequest(_req, res, _config, _source, body, skalpelUrl, directUrl, useSkalpel, forwardHeaders, logger) {
|
|
285
|
+
async function handleStreamingRequest(_req, res, _config, _source, body, skalpelUrl, directUrl, useSkalpel, forwardHeaders, logger, traceCtx) {
|
|
255
286
|
let response = null;
|
|
256
287
|
let fetchError = null;
|
|
257
288
|
let usedFallback = false;
|
|
@@ -372,6 +403,9 @@ data: ${JSON.stringify(envelope)}
|
|
|
372
403
|
}
|
|
373
404
|
sseHeaders["Content-Type"] = "text/event-stream";
|
|
374
405
|
sseHeaders["Cache-Control"] = "no-cache";
|
|
406
|
+
if (traceCtx) {
|
|
407
|
+
sseHeaders["traceparent"] = formatTraceparent(traceCtx);
|
|
408
|
+
}
|
|
375
409
|
res.writeHead(response.status, sseHeaders);
|
|
376
410
|
if (!response.body) {
|
|
377
411
|
res.write(`event: error
|
|
@@ -423,6 +457,7 @@ var init_streaming = __esm({
|
|
|
423
457
|
init_envelope();
|
|
424
458
|
init_recovery();
|
|
425
459
|
init_fetch_error();
|
|
460
|
+
init_trace_context();
|
|
426
461
|
TIMEOUT_CODES2 = /* @__PURE__ */ new Set(["ETIMEDOUT", "TIMEOUT", "UND_ERR_HEADERS_TIMEOUT"]);
|
|
427
462
|
HTTP_BAD_GATEWAY = 502;
|
|
428
463
|
HOP_BY_HOP = /* @__PURE__ */ new Set([
|
|
@@ -527,6 +562,7 @@ var init_ws_client = __esm({
|
|
|
527
562
|
import_node_http = __toESM(require("http"), 1);
|
|
528
563
|
import_ws = __toESM(require("ws"), 1);
|
|
529
564
|
init_codex_oauth();
|
|
565
|
+
init_trace_context();
|
|
530
566
|
WS_SUBPROTOCOL = "skalpel-codex-v1";
|
|
531
567
|
MAX_RECONNECTS = 5;
|
|
532
568
|
MAX_BACKOFF_MS = 6e4;
|
|
@@ -550,13 +586,17 @@ var init_ws_client = __esm({
|
|
|
550
586
|
const freshToken = getFreshAccessToken();
|
|
551
587
|
const bearer = freshToken ?? this.opts.oauthToken;
|
|
552
588
|
return new Promise((resolve, reject) => {
|
|
589
|
+
const headers = {
|
|
590
|
+
"X-Skalpel-API-Key": this.opts.apiKey,
|
|
591
|
+
Authorization: `Bearer ${bearer}`,
|
|
592
|
+
"x-skalpel-source": this.opts.source
|
|
593
|
+
};
|
|
594
|
+
if (this.opts.traceCtx) {
|
|
595
|
+
headers["traceparent"] = formatTraceparent(this.opts.traceCtx);
|
|
596
|
+
}
|
|
553
597
|
const ws = new import_ws.default(this.opts.url, [WS_SUBPROTOCOL], {
|
|
554
598
|
agent: pickAgent(this.opts.url),
|
|
555
|
-
headers
|
|
556
|
-
"X-Skalpel-API-Key": this.opts.apiKey,
|
|
557
|
-
Authorization: `Bearer ${bearer}`,
|
|
558
|
-
"x-skalpel-source": this.opts.source
|
|
559
|
-
}
|
|
599
|
+
headers
|
|
560
600
|
});
|
|
561
601
|
this.ws = ws;
|
|
562
602
|
ws.once("unexpected-response", (_req, res) => {
|
|
@@ -711,13 +751,16 @@ async function isSkalpelBackendFailure(response, err, logger) {
|
|
|
711
751
|
return true;
|
|
712
752
|
}
|
|
713
753
|
}
|
|
714
|
-
function buildForwardHeaders(req, config, source, useSkalpel) {
|
|
754
|
+
function buildForwardHeaders(req, config, source, useSkalpel, traceCtx) {
|
|
715
755
|
const forwardHeaders = {};
|
|
716
756
|
for (const [key, value] of Object.entries(req.headers)) {
|
|
717
757
|
if (value === void 0) continue;
|
|
718
758
|
if (FORWARD_HEADER_STRIP.has(key.toLowerCase())) continue;
|
|
719
759
|
forwardHeaders[key] = Array.isArray(value) ? value.join(", ") : value;
|
|
720
760
|
}
|
|
761
|
+
if (traceCtx) {
|
|
762
|
+
forwardHeaders["traceparent"] = formatTraceparent(traceCtx);
|
|
763
|
+
}
|
|
721
764
|
if (useSkalpel) {
|
|
722
765
|
forwardHeaders["X-Skalpel-API-Key"] = config.apiKey;
|
|
723
766
|
forwardHeaders["X-Skalpel-Source"] = source;
|
|
@@ -782,9 +825,13 @@ async function handleRequest(req, res, config, source, logger) {
|
|
|
782
825
|
try {
|
|
783
826
|
const body = await collectBody(req);
|
|
784
827
|
logger.info(`body collected bytes=${body.length}`);
|
|
828
|
+
const inboundTraceparent = req.headers["traceparent"];
|
|
829
|
+
const traceCtx = parseTraceparent(
|
|
830
|
+
typeof inboundTraceparent === "string" ? inboundTraceparent : null
|
|
831
|
+
) ?? generateTraceContext();
|
|
785
832
|
const useSkalpel = shouldRouteToSkalpel(path4, source);
|
|
786
|
-
logger.info(`routing useSkalpel=${useSkalpel}`);
|
|
787
|
-
const forwardHeaders = buildForwardHeaders(req, config, source, useSkalpel);
|
|
833
|
+
logger.info(`routing useSkalpel=${useSkalpel} traceId=${traceCtx.traceId}`);
|
|
834
|
+
const forwardHeaders = buildForwardHeaders(req, config, source, useSkalpel, traceCtx);
|
|
788
835
|
logger.debug(`headers built skalpelHeaders=${useSkalpel} authConverted=${!forwardHeaders["authorization"] && !!forwardHeaders["x-api-key"]}`);
|
|
789
836
|
let isStreaming = false;
|
|
790
837
|
if (body) {
|
|
@@ -798,7 +845,7 @@ async function handleRequest(req, res, config, source, logger) {
|
|
|
798
845
|
if (isStreaming) {
|
|
799
846
|
const skalpelUrl2 = `${config.remoteBaseUrl}${path4}`;
|
|
800
847
|
const directUrl2 = source === "claude-code" ? `${config.anthropicDirectUrl}${path4}` : source === "cursor" ? `${config.cursorDirectUrl}${path4}` : `${config.openaiDirectUrl}${path4}`;
|
|
801
|
-
await handleStreamingRequest(req, res, config, source, body, skalpelUrl2, directUrl2, useSkalpel, forwardHeaders, logger);
|
|
848
|
+
await handleStreamingRequest(req, res, config, source, body, skalpelUrl2, directUrl2, useSkalpel, forwardHeaders, logger, traceCtx);
|
|
802
849
|
logger.info(`${method} ${path4} source=${source} streaming latency=${Date.now() - start}ms`);
|
|
803
850
|
return;
|
|
804
851
|
}
|
|
@@ -893,6 +940,7 @@ async function handleRequest(req, res, config, source, logger) {
|
|
|
893
940
|
const responseHeaders = extractResponseHeaders(response);
|
|
894
941
|
const responseBody = Buffer.from(await response.arrayBuffer());
|
|
895
942
|
responseHeaders["content-length"] = String(responseBody.length);
|
|
943
|
+
responseHeaders["traceparent"] = formatTraceparent(traceCtx);
|
|
896
944
|
logger.info(`response forwarding status=${response.status} bodyBytes=${responseBody.length}`);
|
|
897
945
|
res.writeHead(response.status, responseHeaders);
|
|
898
946
|
res.end(responseBody);
|
|
@@ -1090,7 +1138,8 @@ async function fallbackToHttp(clientWs, config, source, logger, requestBody, inb
|
|
|
1090
1138
|
Authorization: authHeader,
|
|
1091
1139
|
Accept: "text/event-stream"
|
|
1092
1140
|
},
|
|
1093
|
-
body: requestBody
|
|
1141
|
+
body: requestBody,
|
|
1142
|
+
dispatcher: skalpelDispatcher
|
|
1094
1143
|
});
|
|
1095
1144
|
if (!resp.ok) {
|
|
1096
1145
|
let errorBody = "";
|
|
@@ -1178,6 +1227,7 @@ var init_handler = __esm({
|
|
|
1178
1227
|
init_codex_oauth();
|
|
1179
1228
|
init_recovery();
|
|
1180
1229
|
init_fetch_error();
|
|
1230
|
+
init_trace_context();
|
|
1181
1231
|
TIMEOUT_CODES3 = /* @__PURE__ */ new Set(["ETIMEDOUT", "TIMEOUT", "UND_ERR_HEADERS_TIMEOUT"]);
|
|
1182
1232
|
HTTP_BAD_GATEWAY2 = 502;
|
|
1183
1233
|
SKALPEL_EXACT_PATHS = /* @__PURE__ */ new Set(["/v1/messages"]);
|
|
@@ -1361,10 +1411,12 @@ var Logger = class _Logger {
|
|
|
1361
1411
|
this.log("error", msg);
|
|
1362
1412
|
}
|
|
1363
1413
|
/** Returns a new Logger that writes to the same file but prefixes every
|
|
1364
|
-
* emitted line with `[conn=<connId>]
|
|
1365
|
-
* work unchanged. IPv6 colons should already
|
|
1366
|
-
|
|
1367
|
-
|
|
1414
|
+
* emitted line with `[conn=<connId>] ` or `[conn=<connId> trace=<traceId>] `.
|
|
1415
|
+
* The parent logger continues to work unchanged. IPv6 colons should already
|
|
1416
|
+
* be sanitized by the caller. */
|
|
1417
|
+
child(connId, traceId) {
|
|
1418
|
+
const prefix = traceId ? `[conn=${connId} trace=${traceId}] ` : `[conn=${connId}] `;
|
|
1419
|
+
const child = new _Logger(this.logFile, this.level, prefix);
|
|
1368
1420
|
return child;
|
|
1369
1421
|
}
|
|
1370
1422
|
log(level, msg) {
|