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.js
CHANGED
|
@@ -203,6 +203,37 @@ var init_fetch_error = __esm({
|
|
|
203
203
|
}
|
|
204
204
|
});
|
|
205
205
|
|
|
206
|
+
// src/proxy/trace-context.ts
|
|
207
|
+
import { randomBytes } from "crypto";
|
|
208
|
+
function parseTraceparent(header) {
|
|
209
|
+
if (!header) return null;
|
|
210
|
+
const trimmed = header.trim().toLowerCase();
|
|
211
|
+
const match = trimmed.match(TRACEPARENT_REGEX);
|
|
212
|
+
if (!match) return null;
|
|
213
|
+
const [, traceId, spanId, traceFlags] = match;
|
|
214
|
+
if (traceId === "00000000000000000000000000000000") return null;
|
|
215
|
+
if (spanId === "0000000000000000") return null;
|
|
216
|
+
return { traceId, spanId, traceFlags };
|
|
217
|
+
}
|
|
218
|
+
function generateTraceContext() {
|
|
219
|
+
return {
|
|
220
|
+
traceId: randomBytes(16).toString("hex"),
|
|
221
|
+
spanId: randomBytes(8).toString("hex"),
|
|
222
|
+
traceFlags: "01"
|
|
223
|
+
// sampled
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
function formatTraceparent(ctx) {
|
|
227
|
+
return `00-${ctx.traceId}-${ctx.spanId}-${ctx.traceFlags}`;
|
|
228
|
+
}
|
|
229
|
+
var TRACEPARENT_REGEX;
|
|
230
|
+
var init_trace_context = __esm({
|
|
231
|
+
"src/proxy/trace-context.ts"() {
|
|
232
|
+
"use strict";
|
|
233
|
+
TRACEPARENT_REGEX = /^00-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/;
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
|
|
206
237
|
// src/proxy/streaming.ts
|
|
207
238
|
function parseRetryAfter(header) {
|
|
208
239
|
if (!header) return void 0;
|
|
@@ -229,7 +260,7 @@ function stripSkalpelHeaders(headers) {
|
|
|
229
260
|
async function doStreamingFetch(url, body, headers) {
|
|
230
261
|
return fetch(url, { method: "POST", headers, body, dispatcher: skalpelDispatcher });
|
|
231
262
|
}
|
|
232
|
-
async function handleStreamingRequest(_req, res, _config, _source, body, skalpelUrl, directUrl, useSkalpel, forwardHeaders, logger) {
|
|
263
|
+
async function handleStreamingRequest(_req, res, _config, _source, body, skalpelUrl, directUrl, useSkalpel, forwardHeaders, logger, traceCtx) {
|
|
233
264
|
let response = null;
|
|
234
265
|
let fetchError = null;
|
|
235
266
|
let usedFallback = false;
|
|
@@ -350,6 +381,9 @@ data: ${JSON.stringify(envelope)}
|
|
|
350
381
|
}
|
|
351
382
|
sseHeaders["Content-Type"] = "text/event-stream";
|
|
352
383
|
sseHeaders["Cache-Control"] = "no-cache";
|
|
384
|
+
if (traceCtx) {
|
|
385
|
+
sseHeaders["traceparent"] = formatTraceparent(traceCtx);
|
|
386
|
+
}
|
|
353
387
|
res.writeHead(response.status, sseHeaders);
|
|
354
388
|
if (!response.body) {
|
|
355
389
|
res.write(`event: error
|
|
@@ -401,6 +435,7 @@ var init_streaming = __esm({
|
|
|
401
435
|
init_envelope();
|
|
402
436
|
init_recovery();
|
|
403
437
|
init_fetch_error();
|
|
438
|
+
init_trace_context();
|
|
404
439
|
TIMEOUT_CODES2 = /* @__PURE__ */ new Set(["ETIMEDOUT", "TIMEOUT", "UND_ERR_HEADERS_TIMEOUT"]);
|
|
405
440
|
HTTP_BAD_GATEWAY = 502;
|
|
406
441
|
HOP_BY_HOP = /* @__PURE__ */ new Set([
|
|
@@ -505,6 +540,7 @@ var init_ws_client = __esm({
|
|
|
505
540
|
"src/proxy/ws-client.ts"() {
|
|
506
541
|
"use strict";
|
|
507
542
|
init_codex_oauth();
|
|
543
|
+
init_trace_context();
|
|
508
544
|
WS_SUBPROTOCOL = "skalpel-codex-v1";
|
|
509
545
|
MAX_RECONNECTS = 5;
|
|
510
546
|
MAX_BACKOFF_MS = 6e4;
|
|
@@ -528,13 +564,17 @@ var init_ws_client = __esm({
|
|
|
528
564
|
const freshToken = getFreshAccessToken();
|
|
529
565
|
const bearer = freshToken ?? this.opts.oauthToken;
|
|
530
566
|
return new Promise((resolve, reject) => {
|
|
567
|
+
const headers = {
|
|
568
|
+
"X-Skalpel-API-Key": this.opts.apiKey,
|
|
569
|
+
Authorization: `Bearer ${bearer}`,
|
|
570
|
+
"x-skalpel-source": this.opts.source
|
|
571
|
+
};
|
|
572
|
+
if (this.opts.traceCtx) {
|
|
573
|
+
headers["traceparent"] = formatTraceparent(this.opts.traceCtx);
|
|
574
|
+
}
|
|
531
575
|
const ws = new WebSocket(this.opts.url, [WS_SUBPROTOCOL], {
|
|
532
576
|
agent: pickAgent(this.opts.url),
|
|
533
|
-
headers
|
|
534
|
-
"X-Skalpel-API-Key": this.opts.apiKey,
|
|
535
|
-
Authorization: `Bearer ${bearer}`,
|
|
536
|
-
"x-skalpel-source": this.opts.source
|
|
537
|
-
}
|
|
577
|
+
headers
|
|
538
578
|
});
|
|
539
579
|
this.ws = ws;
|
|
540
580
|
ws.once("unexpected-response", (_req, res) => {
|
|
@@ -689,13 +729,16 @@ async function isSkalpelBackendFailure(response, err, logger) {
|
|
|
689
729
|
return true;
|
|
690
730
|
}
|
|
691
731
|
}
|
|
692
|
-
function buildForwardHeaders(req, config, source, useSkalpel) {
|
|
732
|
+
function buildForwardHeaders(req, config, source, useSkalpel, traceCtx) {
|
|
693
733
|
const forwardHeaders = {};
|
|
694
734
|
for (const [key, value] of Object.entries(req.headers)) {
|
|
695
735
|
if (value === void 0) continue;
|
|
696
736
|
if (FORWARD_HEADER_STRIP.has(key.toLowerCase())) continue;
|
|
697
737
|
forwardHeaders[key] = Array.isArray(value) ? value.join(", ") : value;
|
|
698
738
|
}
|
|
739
|
+
if (traceCtx) {
|
|
740
|
+
forwardHeaders["traceparent"] = formatTraceparent(traceCtx);
|
|
741
|
+
}
|
|
699
742
|
if (useSkalpel) {
|
|
700
743
|
forwardHeaders["X-Skalpel-API-Key"] = config.apiKey;
|
|
701
744
|
forwardHeaders["X-Skalpel-Source"] = source;
|
|
@@ -760,9 +803,13 @@ async function handleRequest(req, res, config, source, logger) {
|
|
|
760
803
|
try {
|
|
761
804
|
const body = await collectBody(req);
|
|
762
805
|
logger.info(`body collected bytes=${body.length}`);
|
|
806
|
+
const inboundTraceparent = req.headers["traceparent"];
|
|
807
|
+
const traceCtx = parseTraceparent(
|
|
808
|
+
typeof inboundTraceparent === "string" ? inboundTraceparent : null
|
|
809
|
+
) ?? generateTraceContext();
|
|
763
810
|
const useSkalpel = shouldRouteToSkalpel(path4, source);
|
|
764
|
-
logger.info(`routing useSkalpel=${useSkalpel}`);
|
|
765
|
-
const forwardHeaders = buildForwardHeaders(req, config, source, useSkalpel);
|
|
811
|
+
logger.info(`routing useSkalpel=${useSkalpel} traceId=${traceCtx.traceId}`);
|
|
812
|
+
const forwardHeaders = buildForwardHeaders(req, config, source, useSkalpel, traceCtx);
|
|
766
813
|
logger.debug(`headers built skalpelHeaders=${useSkalpel} authConverted=${!forwardHeaders["authorization"] && !!forwardHeaders["x-api-key"]}`);
|
|
767
814
|
let isStreaming = false;
|
|
768
815
|
if (body) {
|
|
@@ -776,7 +823,7 @@ async function handleRequest(req, res, config, source, logger) {
|
|
|
776
823
|
if (isStreaming) {
|
|
777
824
|
const skalpelUrl2 = `${config.remoteBaseUrl}${path4}`;
|
|
778
825
|
const directUrl2 = source === "claude-code" ? `${config.anthropicDirectUrl}${path4}` : source === "cursor" ? `${config.cursorDirectUrl}${path4}` : `${config.openaiDirectUrl}${path4}`;
|
|
779
|
-
await handleStreamingRequest(req, res, config, source, body, skalpelUrl2, directUrl2, useSkalpel, forwardHeaders, logger);
|
|
826
|
+
await handleStreamingRequest(req, res, config, source, body, skalpelUrl2, directUrl2, useSkalpel, forwardHeaders, logger, traceCtx);
|
|
780
827
|
logger.info(`${method} ${path4} source=${source} streaming latency=${Date.now() - start}ms`);
|
|
781
828
|
return;
|
|
782
829
|
}
|
|
@@ -871,6 +918,7 @@ async function handleRequest(req, res, config, source, logger) {
|
|
|
871
918
|
const responseHeaders = extractResponseHeaders(response);
|
|
872
919
|
const responseBody = Buffer.from(await response.arrayBuffer());
|
|
873
920
|
responseHeaders["content-length"] = String(responseBody.length);
|
|
921
|
+
responseHeaders["traceparent"] = formatTraceparent(traceCtx);
|
|
874
922
|
logger.info(`response forwarding status=${response.status} bodyBytes=${responseBody.length}`);
|
|
875
923
|
res.writeHead(response.status, responseHeaders);
|
|
876
924
|
res.end(responseBody);
|
|
@@ -1068,7 +1116,8 @@ async function fallbackToHttp(clientWs, config, source, logger, requestBody, inb
|
|
|
1068
1116
|
Authorization: authHeader,
|
|
1069
1117
|
Accept: "text/event-stream"
|
|
1070
1118
|
},
|
|
1071
|
-
body: requestBody
|
|
1119
|
+
body: requestBody,
|
|
1120
|
+
dispatcher: skalpelDispatcher
|
|
1072
1121
|
});
|
|
1073
1122
|
if (!resp.ok) {
|
|
1074
1123
|
let errorBody = "";
|
|
@@ -1156,6 +1205,7 @@ var init_handler = __esm({
|
|
|
1156
1205
|
init_codex_oauth();
|
|
1157
1206
|
init_recovery();
|
|
1158
1207
|
init_fetch_error();
|
|
1208
|
+
init_trace_context();
|
|
1159
1209
|
TIMEOUT_CODES3 = /* @__PURE__ */ new Set(["ETIMEDOUT", "TIMEOUT", "UND_ERR_HEADERS_TIMEOUT"]);
|
|
1160
1210
|
HTTP_BAD_GATEWAY2 = 502;
|
|
1161
1211
|
SKALPEL_EXACT_PATHS = /* @__PURE__ */ new Set(["/v1/messages"]);
|
|
@@ -1328,10 +1378,12 @@ var Logger = class _Logger {
|
|
|
1328
1378
|
this.log("error", msg);
|
|
1329
1379
|
}
|
|
1330
1380
|
/** Returns a new Logger that writes to the same file but prefixes every
|
|
1331
|
-
* emitted line with `[conn=<connId>]
|
|
1332
|
-
* work unchanged. IPv6 colons should already
|
|
1333
|
-
|
|
1334
|
-
|
|
1381
|
+
* emitted line with `[conn=<connId>] ` or `[conn=<connId> trace=<traceId>] `.
|
|
1382
|
+
* The parent logger continues to work unchanged. IPv6 colons should already
|
|
1383
|
+
* be sanitized by the caller. */
|
|
1384
|
+
child(connId, traceId) {
|
|
1385
|
+
const prefix = traceId ? `[conn=${connId} trace=${traceId}] ` : `[conn=${connId}] `;
|
|
1386
|
+
const child = new _Logger(this.logFile, this.level, prefix);
|
|
1335
1387
|
return child;
|
|
1336
1388
|
}
|
|
1337
1389
|
log(level, msg) {
|