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