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.
@@ -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>] `. The parent logger continues to
1365
- * work unchanged. IPv6 colons should already be sanitized by the caller. */
1366
- child(connId) {
1367
- const child = new _Logger(this.logFile, this.level, `[conn=${connId}] `);
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) {