@bman654/clodex 2.0.0 → 2.1.1

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.js CHANGED
@@ -20,6 +20,7 @@ import {
20
20
  loadRegistryStrict,
21
21
  recordLaunchSelection,
22
22
  registerServerRuntimeState,
23
+ removeAnthropicProxyBypass,
23
24
  resolveBridgeMode,
24
25
  savePreferences,
25
26
  saveRegistry,
@@ -32,7 +33,7 @@ import {
32
33
  withCredentialMutationLock,
33
34
  withRegistryWriteLock,
34
35
  withRegistryWriteLockSync
35
- } from "./chunk-ZYGZ5GDI.js";
36
+ } from "./chunk-QLGIKUKC.js";
36
37
 
37
38
  // src/cli.ts
38
39
  import pc13 from "picocolors";
@@ -210,7 +211,7 @@ import { join } from "path";
210
211
  // package.json
211
212
  var package_default = {
212
213
  name: "@bman654/clodex",
213
- version: "2.0.0",
214
+ version: "2.1.1",
214
215
  publishConfig: {
215
216
  access: "public"
216
217
  },
@@ -544,6 +545,10 @@ var ONE_M_CONTEXT_WINDOW = 1e6;
544
545
  function stripOneMContextSuffix(modelId) {
545
546
  return modelId.replace(/\[1m\]$/i, "");
546
547
  }
548
+ function normalizeRouteLookupId(id) {
549
+ const bare = stripOneMContextSuffix(id);
550
+ return bare.startsWith("models/") ? bare.slice("models/".length) : bare;
551
+ }
547
552
  function claudeCodeClientModelId(modelId, contextWindow) {
548
553
  const bare = stripOneMContextSuffix(modelId);
549
554
  const window = resolveContextWindow(bare, contextWindow);
@@ -552,19 +557,6 @@ function claudeCodeClientModelId(modelId, contextWindow) {
552
557
  }
553
558
  return bare;
554
559
  }
555
- function routeLookupIds(id) {
556
- const bare = stripOneMContextSuffix(id);
557
- const googleBare = bare.startsWith("models/") ? bare.slice("models/".length) : bare;
558
- return [.../* @__PURE__ */ new Set([
559
- id,
560
- bare,
561
- `${bare}${ONE_M_CONTEXT_SUFFIX}`,
562
- googleBare,
563
- `${googleBare}${ONE_M_CONTEXT_SUFFIX}`,
564
- `models/${googleBare}`,
565
- `models/${bare}`
566
- ])];
567
- }
568
560
 
569
561
  // src/oauth/types.ts
570
562
  function oauthCredentialToKeychainJson(cred) {
@@ -830,24 +822,7 @@ function buildHttpProxyChildEnv(proxyPort, caCertPath) {
830
822
  env["https_proxy"] = proxyUrl;
831
823
  env["http_proxy"] = proxyUrl;
832
824
  env["NODE_EXTRA_CA_CERTS"] = caCertPath;
833
- const noProxy = env["NO_PROXY"] ?? env["no_proxy"];
834
- if (noProxy !== void 0) {
835
- const filtered = noProxy.split(",").map((value) => value.trim()).filter(Boolean).filter((value) => {
836
- const entry = value.toLowerCase().replace(/^https?:\/\//, "");
837
- const host = entry.replace(/:\d+$/, "");
838
- if (host === "*") return false;
839
- const suffix = host.startsWith("*.") ? host.slice(1) : host;
840
- const bypassesAnthropic = suffix.startsWith(".") ? "api.anthropic.com".endsWith(suffix) : "api.anthropic.com" === suffix || "api.anthropic.com".endsWith(`.${suffix}`);
841
- return !bypassesAnthropic;
842
- }).join(",");
843
- if (filtered) {
844
- env["NO_PROXY"] = filtered;
845
- env["no_proxy"] = filtered;
846
- } else {
847
- delete env["NO_PROXY"];
848
- delete env["no_proxy"];
849
- }
850
- }
825
+ removeAnthropicProxyBypass(env);
851
826
  return env;
852
827
  }
853
828
  function classifyKeyringError(err) {
@@ -2562,6 +2537,12 @@ function numericRetryAfterSeconds(inner) {
2562
2537
  }
2563
2538
  return void 0;
2564
2539
  }
2540
+ function boundedTransportCode(data) {
2541
+ if (!data || typeof data !== "object") return void 0;
2542
+ const error = data.error;
2543
+ if (!error || typeof error !== "object") return void 0;
2544
+ return error.code === "websocket_transport_error" ? "websocket_transport_error" : void 0;
2545
+ }
2565
2546
  function sdkUpstreamErrorDetails(err) {
2566
2547
  const retry = RetryError.isInstance(err) ? err : void 0;
2567
2548
  const inner = retry?.lastError ?? err;
@@ -2580,7 +2561,8 @@ function sdkUpstreamErrorDetails(err) {
2580
2561
  errorContent: errorContent || inner.message,
2581
2562
  isRetryable: inner.isRetryable,
2582
2563
  attemptCount: retry?.errors.length ?? 1,
2583
- ...retryAfterSeconds !== void 0 ? { retryAfterSeconds } : {}
2564
+ ...retryAfterSeconds !== void 0 ? { retryAfterSeconds } : {},
2565
+ transportCode: boundedTransportCode(inner.data)
2584
2566
  };
2585
2567
  }
2586
2568
  function isContextLengthExceededError(err, formattedMessage = "") {
@@ -5062,6 +5044,7 @@ function nonNegativeInteger(value) {
5062
5044
  return value !== void 0 && Number.isFinite(value) ? Math.max(0, Math.round(value)) : void 0;
5063
5045
  }
5064
5046
  function writeInferenceResponseLifecycleLog(path, entry) {
5047
+ const claudeSessionId = safeClaudeSessionId(entry.claudeSessionId);
5065
5048
  const statusCode = nonNegativeInteger(entry.statusCode);
5066
5049
  const durationMs = nonNegativeInteger(entry.durationMs);
5067
5050
  const timeToFirstByteMs = nonNegativeInteger(entry.timeToFirstByteMs);
@@ -5081,6 +5064,7 @@ function writeInferenceResponseLifecycleLog(path, entry) {
5081
5064
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
5082
5065
  event: entry.event,
5083
5066
  requestId: compactLogValue(entry.requestId, 100),
5067
+ ...claudeSessionId ? { claudeSessionId } : {},
5084
5068
  modelId: compactLogValue(entry.modelId),
5085
5069
  provider: compactLogValue(entry.provider, 200),
5086
5070
  route: entry.route,
@@ -5103,7 +5087,10 @@ function writeInferenceResponseLifecycleLog(path, entry) {
5103
5087
  ...cacheReadInputTokens !== void 0 ? { cacheReadInputTokens } : {},
5104
5088
  ...entry.lastPartType ? { lastPartType: compactLogValue(entry.lastPartType, 100) } : {},
5105
5089
  ...entry.errorType ? { errorType: compactLogValue(entry.errorType, 200) } : {},
5106
- ...entry.errorSignature ? { errorSignature: compactLogValue(entry.errorSignature, 100) } : {}
5090
+ ...entry.errorCode ? { errorCode: compactLogValue(entry.errorCode, 100) } : {},
5091
+ ...entry.errorSignature ? { errorSignature: compactLogValue(entry.errorSignature, 100) } : {},
5092
+ ...entry.failureSource ? { failureSource: entry.failureSource } : {},
5093
+ ...entry.terminationSource ? { terminationSource: entry.terminationSource } : {}
5107
5094
  }));
5108
5095
  }
5109
5096
  function writeProxyLifecycleLog(path, entry) {
@@ -5154,6 +5141,15 @@ function writeInferenceResponseErrorLog(path, entry) {
5154
5141
  ...includeContent ? { errorContent: compactLogValueWithMarker(entry.errorContent, RESPONSE_ERROR_MAX) } : {}
5155
5142
  }));
5156
5143
  }
5144
+ function writeInferenceRouteUnavailableLog(path, entry) {
5145
+ writeSecureLogLine(path, JSON.stringify({
5146
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
5147
+ event: "route_unavailable",
5148
+ requestId: compactLogValue(entry.requestId, 100),
5149
+ modelId: compactLogValue(entry.modelId),
5150
+ statusCode: entry.statusCode
5151
+ }));
5152
+ }
5157
5153
  function makeTraceLogger(logPath) {
5158
5154
  resetTraceLog(logPath);
5159
5155
  return (message) => writeSecureLogLine(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${message}`);
@@ -7309,6 +7305,38 @@ function sendJson(res, status, body) {
7309
7305
  res.end(json);
7310
7306
  }
7311
7307
 
7308
+ // src/model-aliases.ts
7309
+ var MODEL_ALIAS_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
7310
+ function isValidModelAlias(name) {
7311
+ return MODEL_ALIAS_PATTERN.test(name);
7312
+ }
7313
+ function parseModelAliasAssignment(value) {
7314
+ const separator = value.indexOf("=");
7315
+ if (separator < 1 || separator === value.length - 1) {
7316
+ return { error: "Alias must use name=clodex:<provider-id>:<model-id>." };
7317
+ }
7318
+ const name = value.slice(0, separator).trim();
7319
+ if (!isValidModelAlias(name)) {
7320
+ return { error: "Alias names must be 1-64 letters, numbers, dots, underscores, or hyphens." };
7321
+ }
7322
+ const rawTarget = value.slice(separator + 1).trim();
7323
+ const target = rawTarget.startsWith("clodex:") ? rawTarget.slice("clodex:".length) : rawTarget;
7324
+ const targetSeparator = target.indexOf(":");
7325
+ if (targetSeparator < 1 || targetSeparator === target.length - 1) {
7326
+ return { error: "Alias target must use clodex:<provider-id>:<model-id>." };
7327
+ }
7328
+ return {
7329
+ name,
7330
+ providerId: target.slice(0, targetSeparator),
7331
+ // `models --list` prints Claude's synthetic context suffix. It is a client
7332
+ // routing hint, not part of the provider catalog id stored in favorites.
7333
+ modelId: stripOneMContextSuffix(target.slice(targetSeparator + 1))
7334
+ };
7335
+ }
7336
+ function modelAliasTarget(alias) {
7337
+ return `clodex:${alias.providerId}:${alias.modelId}`;
7338
+ }
7339
+
7312
7340
  // src/catalog.ts
7313
7341
  function localModelToRoute(lp, model) {
7314
7342
  if (model.modelFormat === "anthropic" && !model.baseUrl) return null;
@@ -7349,6 +7377,12 @@ function makeRouteResolver(localProviders) {
7349
7377
  return provider && model ? localModelToRoute(provider, model) ?? void 0 : void 0;
7350
7378
  };
7351
7379
  }
7380
+ function resolveCatalogModelAliases(modelAliases, resolveRoute) {
7381
+ return modelAliases.map((alias) => ({
7382
+ name: alias.name,
7383
+ routeId: resolveRoute(alias.providerId, alias.modelId)?.aliasId ?? modelAliasTarget(alias)
7384
+ }));
7385
+ }
7352
7386
  function buildCatalogRoutes(startingRoute, favorites, resolveRoute, max = MAX_MODEL_CATALOG) {
7353
7387
  const droppedFavorites = [];
7354
7388
  const tail = favorites.map((fav) => {
@@ -7363,38 +7397,6 @@ function buildCatalogRoutes(startingRoute, favorites, resolveRoute, max = MAX_MO
7363
7397
  return { routes, droppedFavorites };
7364
7398
  }
7365
7399
 
7366
- // src/model-aliases.ts
7367
- var MODEL_ALIAS_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
7368
- function isValidModelAlias(name) {
7369
- return MODEL_ALIAS_PATTERN.test(name);
7370
- }
7371
- function parseModelAliasAssignment(value) {
7372
- const separator = value.indexOf("=");
7373
- if (separator < 1 || separator === value.length - 1) {
7374
- return { error: "Alias must use name=clodex:<provider-id>:<model-id>." };
7375
- }
7376
- const name = value.slice(0, separator).trim();
7377
- if (!isValidModelAlias(name)) {
7378
- return { error: "Alias names must be 1-64 letters, numbers, dots, underscores, or hyphens." };
7379
- }
7380
- const rawTarget = value.slice(separator + 1).trim();
7381
- const target = rawTarget.startsWith("clodex:") ? rawTarget.slice("clodex:".length) : rawTarget;
7382
- const targetSeparator = target.indexOf(":");
7383
- if (targetSeparator < 1 || targetSeparator === target.length - 1) {
7384
- return { error: "Alias target must use clodex:<provider-id>:<model-id>." };
7385
- }
7386
- return {
7387
- name,
7388
- providerId: target.slice(0, targetSeparator),
7389
- // `models --list` prints Claude's synthetic context suffix. It is a client
7390
- // routing hint, not part of the provider catalog id stored in favorites.
7391
- modelId: stripOneMContextSuffix(target.slice(targetSeparator + 1))
7392
- };
7393
- }
7394
- function modelAliasTarget(alias) {
7395
- return `clodex:${alias.providerId}:${alias.modelId}`;
7396
- }
7397
-
7398
7400
  // src/http-proxy/routes.ts
7399
7401
  var HTTP_PROXY_MODEL_PREFIX = "clodex:";
7400
7402
  function httpProxyModelId(providerId, modelId) {
@@ -7578,6 +7580,11 @@ function formatOpenAIModels(models) {
7578
7580
  };
7579
7581
  }
7580
7582
 
7583
+ // src/route-unavailable.ts
7584
+ function routeUnavailableMessage(modelId) {
7585
+ return `Clodex model route '${modelId}' is unavailable. Run \`clodex models --list\` to see available routes, or \`clodex patch\` to refresh saved aliases.`;
7586
+ }
7587
+
7581
7588
  // src/upstream-forward.ts
7582
7589
  import { Readable } from "stream";
7583
7590
 
@@ -8678,7 +8685,7 @@ async function listenTcpServer(server, port, host) {
8678
8685
  // src/proxy.ts
8679
8686
  var STREAM_KEEPALIVE_INTERVAL_MS = 2e4;
8680
8687
  var STREAM_KEEPALIVE_PING = 'event: ping\ndata: {"type":"ping"}\n\n';
8681
- function createTranslationLifecycle(logPath, requestId, modelId, provider) {
8688
+ function createTranslationLifecycle(logPath, requestId, claudeSessionId, modelId, provider) {
8682
8689
  if (!logPath || !requestId) return void 0;
8683
8690
  const startedAt = Date.now();
8684
8691
  let firstPartAt;
@@ -8693,6 +8700,7 @@ function createTranslationLifecycle(logPath, requestId, modelId, provider) {
8693
8700
  const write = (event, extra = {}) => writeInferenceResponseLifecycleLog(logPath, {
8694
8701
  event,
8695
8702
  requestId,
8703
+ claudeSessionId,
8696
8704
  modelId,
8697
8705
  provider,
8698
8706
  route: "translated",
@@ -8749,11 +8757,16 @@ function createTranslationLifecycle(logPath, requestId, modelId, provider) {
8749
8757
  clearInterval(timer);
8750
8758
  write("translation_cancelled", snapshot(Date.now()));
8751
8759
  },
8752
- fail(errorType, errorSignature) {
8760
+ fail(errorType, errorSignature, errorCode) {
8753
8761
  if (stopped) return;
8754
8762
  stopped = true;
8755
8763
  clearInterval(timer);
8756
- write("translation_failed", { ...snapshot(Date.now()), errorType, errorSignature });
8764
+ write("translation_failed", {
8765
+ ...snapshot(Date.now()),
8766
+ errorType,
8767
+ errorSignature,
8768
+ errorCode
8769
+ });
8757
8770
  }
8758
8771
  };
8759
8772
  }
@@ -8798,11 +8811,7 @@ function aliasModelId(realId, providerId) {
8798
8811
  return `anthropic-${sanitized}__${realId}`;
8799
8812
  }
8800
8813
  function lookupRoute(byAlias, id) {
8801
- for (const key of routeLookupIds(id)) {
8802
- const route = byAlias.get(key);
8803
- if (route) return route;
8804
- }
8805
- return void 0;
8814
+ return byAlias.get(normalizeRouteLookupId(id));
8806
8815
  }
8807
8816
  async function startProxyCatalog(routes, defaultAliasId, debug = false, inferenceLogPath, debugLogPath, webSocketDiagnosticsLogPath, modelAliases) {
8808
8817
  const proxyToken = randomUUID6();
@@ -8810,12 +8819,16 @@ async function startProxyCatalog(routes, defaultAliasId, debug = false, inferenc
8810
8819
  if (routes.length === 0) {
8811
8820
  throw new Error("Proxy catalog requires at least one route");
8812
8821
  }
8813
- const byAlias = new Map(routes.map((r) => [r.aliasId, r]));
8822
+ const byAlias = new Map(routes.map((r) => [normalizeRouteLookupId(r.aliasId), r]));
8823
+ const configuredAliasNames = new Set(
8824
+ (modelAliases ?? []).map((alias) => normalizeRouteLookupId(alias.name))
8825
+ );
8814
8826
  for (const alias of modelAliases ?? []) {
8815
- const route = byAlias.get(alias.routeId);
8816
- if (route && !byAlias.has(alias.name)) byAlias.set(alias.name, route);
8827
+ const route = lookupRoute(byAlias, alias.routeId);
8828
+ const aliasId = normalizeRouteLookupId(alias.name);
8829
+ if (route && !byAlias.has(aliasId)) byAlias.set(aliasId, route);
8817
8830
  }
8818
- const defaultRoute = byAlias.get(defaultAliasId) ?? routes[0];
8831
+ const defaultRoute = lookupRoute(byAlias, defaultAliasId) ?? routes[0];
8819
8832
  const plog = makeProxyLog(debug, debugLogPath);
8820
8833
  const onRejection = (reason) => {
8821
8834
  plog(() => `Unhandled Rejection: ${reason instanceof Error ? reason.stack || reason.message : String(reason)}`);
@@ -8882,7 +8895,13 @@ async function startProxyCatalog(routes, defaultAliasId, debug = false, inferenc
8882
8895
  const clientWantsStream = Boolean(anthropicBody.stream);
8883
8896
  const relayRequestIdRaw = req.headers["x-relay-request-id"];
8884
8897
  const relayRequestId = Array.isArray(relayRequestIdRaw) ? relayRequestIdRaw[0] : relayRequestIdRaw;
8885
- const route = lookupRoute(byAlias, originalModel) ?? defaultRoute;
8898
+ const resolvedRoute = typeof originalModel === "string" ? lookupRoute(byAlias, originalModel) : void 0;
8899
+ const configuredModelUnavailable = typeof originalModel === "string" && (normalizeRouteLookupId(originalModel).startsWith("clodex:") || configuredAliasNames.has(normalizeRouteLookupId(originalModel)));
8900
+ if (!resolvedRoute && configuredModelUnavailable) {
8901
+ anthropicError(res, 400, routeUnavailableMessage(originalModel));
8902
+ return;
8903
+ }
8904
+ const route = resolvedRoute ?? defaultRoute;
8886
8905
  if (messagesEndpoint === "count_tokens" && route.modelFormat !== "anthropic") {
8887
8906
  const inputTokens = estimateAnthropicInputTokens(anthropicBody);
8888
8907
  plog(() => `token-count: local estimate model=${originalModel} input_tokens=${inputTokens}`);
@@ -8994,15 +9013,16 @@ async function startProxyCatalog(routes, defaultAliasId, debug = false, inferenc
8994
9013
  }
8995
9014
  if (usesSdkAdapter) {
8996
9015
  const openAiOAuth = route.npm === "@ai-sdk/openai" && route.authType === "oauth";
9016
+ const claudeSessionIdHeader = Array.isArray(req.headers["x-claude-code-session-id"]) ? req.headers["x-claude-code-session-id"][0] : req.headers["x-claude-code-session-id"];
9017
+ const claudeSessionId = extractClaudeSessionId(anthropicBody, claudeSessionIdHeader);
8997
9018
  const translationLifecycle = createTranslationLifecycle(
8998
9019
  inferenceLogPath,
8999
9020
  relayRequestId,
9021
+ claudeSessionId,
9000
9022
  originalModel,
9001
9023
  route.providerId ?? route.aliasId.split(":")[1] ?? "unknown"
9002
9024
  );
9003
9025
  const runSdkRequest = async () => {
9004
- const claudeSessionIdHeader = Array.isArray(req.headers["x-claude-code-session-id"]) ? req.headers["x-claude-code-session-id"][0] : req.headers["x-claude-code-session-id"];
9005
- const claudeSessionId = extractClaudeSessionId(anthropicBody, claudeSessionIdHeader);
9006
9026
  const params = translateRequest(anthropicBody, route.npm, {
9007
9027
  openAiOAuth,
9008
9028
  claudeSessionId,
@@ -9133,7 +9153,8 @@ async function startProxyCatalog(routes, defaultAliasId, debug = false, inferenc
9133
9153
  }
9134
9154
  translationLifecycle?.fail(
9135
9155
  err instanceof Error ? err.name : "UpstreamError",
9136
- sdkTranslationErrorSignature(err)
9156
+ sdkTranslationErrorSignature(err),
9157
+ details?.transportCode
9137
9158
  );
9138
9159
  const contextLengthExceeded = upstreamStatus === 400 && isContextLengthExceededError(err, message);
9139
9160
  const clientMessage = contextLengthExceeded ? anthropicPromptTooLongMessage(
@@ -10528,7 +10549,7 @@ function requestHeadersWithoutProxyHeaders(req) {
10528
10549
  }
10529
10550
  return headers;
10530
10551
  }
10531
- function forwardRawAnthropicRequest(req, res, rawBody, origin, rejectUnauthorized, onErrorResponse, onResponseUsage, lifecycle) {
10552
+ function forwardRawAnthropicRequest(req, res, rawBody, origin, rejectUnauthorized, onErrorResponse, onResponseUsage, lifecycle, isLocalShutdown = () => false) {
10532
10553
  return new Promise((resolve2) => {
10533
10554
  const startedAt = Date.now();
10534
10555
  let lastActivityAt = startedAt;
@@ -10546,6 +10567,7 @@ function forwardRawAnthropicRequest(req, res, rawBody, origin, rejectUnauthorize
10546
10567
  writeInferenceResponseLifecycleLog(lifecycle.logPath, {
10547
10568
  event,
10548
10569
  requestId: lifecycle.requestId,
10570
+ claudeSessionId: lifecycle.claudeSessionId,
10549
10571
  modelId: lifecycle.modelId,
10550
10572
  provider: lifecycle.provider,
10551
10573
  route: "passthrough",
@@ -10628,7 +10650,8 @@ function forwardRawAnthropicRequest(req, res, rawBody, origin, rejectUnauthorize
10628
10650
  idleMs: now - lastActivityAt,
10629
10651
  bytes,
10630
10652
  chunks,
10631
- errorType: errorType(err)
10653
+ errorType: errorType(err),
10654
+ terminationSource: "upstream_failure"
10632
10655
  });
10633
10656
  done();
10634
10657
  });
@@ -10657,7 +10680,8 @@ function forwardRawAnthropicRequest(req, res, rawBody, origin, rejectUnauthorize
10657
10680
  ...firstByteAt !== void 0 ? { timeToFirstByteMs: firstByteAt - startedAt } : {},
10658
10681
  idleMs: now - lastActivityAt,
10659
10682
  bytes,
10660
- chunks
10683
+ chunks,
10684
+ terminationSource: isLocalShutdown() ? "local_shutdown" : "downstream_client"
10661
10685
  });
10662
10686
  upstream.destroy(new Error("Client disconnected"));
10663
10687
  done();
@@ -10677,7 +10701,8 @@ function forwardRawAnthropicRequest(req, res, rawBody, origin, rejectUnauthorize
10677
10701
  idleMs: now - lastActivityAt,
10678
10702
  bytes,
10679
10703
  chunks,
10680
- errorType: errorType(err)
10704
+ errorType: errorType(err),
10705
+ terminationSource: "upstream_failure"
10681
10706
  });
10682
10707
  onErrorResponse?.(502, `Anthropic upstream unreachable: ${err.message}`);
10683
10708
  if (!res.headersSent) res.writeHead(502, { "Content-Type": "text/plain" });
@@ -10687,7 +10712,7 @@ function forwardRawAnthropicRequest(req, res, rawBody, origin, rejectUnauthorize
10687
10712
  upstream.end(rawBody);
10688
10713
  });
10689
10714
  }
10690
- function forwardToAdapter(req, res, rawBody, adapter, lifecycle) {
10715
+ function forwardToAdapter(req, res, rawBody, adapter, adapterRequest = http.request, lifecycle, isLocalShutdown = () => false) {
10691
10716
  return new Promise((resolve2) => {
10692
10717
  const startedAt = Date.now();
10693
10718
  let lastActivityAt = startedAt;
@@ -10706,6 +10731,7 @@ function forwardToAdapter(req, res, rawBody, adapter, lifecycle) {
10706
10731
  writeInferenceResponseLifecycleLog(lifecycle.logPath, {
10707
10732
  event,
10708
10733
  requestId: lifecycle.requestId,
10734
+ claudeSessionId: lifecycle.claudeSessionId,
10709
10735
  modelId: lifecycle.modelId,
10710
10736
  provider: lifecycle.provider,
10711
10737
  route: "translated",
@@ -10757,13 +10783,14 @@ function forwardToAdapter(req, res, rawBody, adapter, lifecycle) {
10757
10783
  ...firstByteAt !== void 0 ? { timeToFirstByteMs: firstByteAt - startedAt } : {},
10758
10784
  idleMs: now - lastActivityAt,
10759
10785
  bytes,
10760
- chunks
10786
+ chunks,
10787
+ terminationSource: isLocalShutdown() ? "local_shutdown" : "downstream_client"
10761
10788
  });
10762
10789
  adapterResponse?.destroy(new Error("Client disconnected"));
10763
10790
  upstream?.destroy(new Error("Client disconnected"));
10764
10791
  resolve2();
10765
10792
  });
10766
- const failAdapterRequest = (err) => {
10793
+ const failAdapterRequest = (err, failureSource) => {
10767
10794
  if (clientDisconnected) {
10768
10795
  resolve2();
10769
10796
  return;
@@ -10779,13 +10806,16 @@ function forwardToAdapter(req, res, rawBody, adapter, lifecycle) {
10779
10806
  idleMs: now - lastActivityAt,
10780
10807
  bytes,
10781
10808
  chunks,
10782
- errorType: err.name
10809
+ errorType: err.name,
10810
+ errorCode: err.code,
10811
+ failureSource,
10812
+ terminationSource: "upstream_failure"
10783
10813
  });
10784
10814
  if (!res.headersSent) res.writeHead(502, { "Content-Type": "text/plain" });
10785
10815
  res.end(`Relay adapter unreachable: ${err.message}`);
10786
10816
  resolve2();
10787
10817
  };
10788
- upstream = http.request({
10818
+ upstream = adapterRequest({
10789
10819
  hostname: "127.0.0.1",
10790
10820
  port: adapter.port,
10791
10821
  method: "POST",
@@ -10817,7 +10847,7 @@ function forwardToAdapter(req, res, rawBody, adapter, lifecycle) {
10817
10847
  chunks += 1;
10818
10848
  });
10819
10849
  copyResponse(upstreamRes, res, void 0, lifecycle ? (usage) => writeLifecycle("response_usage", usage) : void 0);
10820
- const failAdapterResponse = (err) => {
10850
+ const failAdapterResponse = (err, failureSource) => {
10821
10851
  if (clientDisconnected) {
10822
10852
  resolve2();
10823
10853
  return;
@@ -10834,7 +10864,10 @@ function forwardToAdapter(req, res, rawBody, adapter, lifecycle) {
10834
10864
  idleMs: now - lastActivityAt,
10835
10865
  bytes,
10836
10866
  chunks,
10837
- errorType: err.name
10867
+ errorType: err.name,
10868
+ errorCode: err.code,
10869
+ failureSource,
10870
+ terminationSource: "upstream_failure"
10838
10871
  });
10839
10872
  if (!res.writableEnded) res.destroy(err);
10840
10873
  resolve2();
@@ -10844,16 +10877,27 @@ function forwardToAdapter(req, res, rawBody, adapter, lifecycle) {
10844
10877
  lastActivityAt = Date.now();
10845
10878
  resolve2();
10846
10879
  });
10847
- upstreamRes.once("error", failAdapterResponse);
10848
- upstreamRes.once("aborted", () => failAdapterResponse(new Error("Relay adapter response aborted")));
10880
+ upstreamRes.once("error", (err) => failAdapterResponse(err, "adapter_response_error"));
10881
+ upstreamRes.once("aborted", () => failAdapterResponse(
10882
+ new Error("Relay adapter response aborted"),
10883
+ "adapter_response_aborted"
10884
+ ));
10849
10885
  upstreamRes.once("close", () => {
10850
- if (!upstreamRes.complete) failAdapterResponse(new Error("Relay adapter response closed before completion"));
10886
+ if (!upstreamRes.complete) {
10887
+ failAdapterResponse(
10888
+ new Error("Relay adapter response closed before completion"),
10889
+ "adapter_response_close"
10890
+ );
10891
+ }
10851
10892
  });
10852
10893
  });
10853
- upstream.once("error", failAdapterRequest);
10894
+ upstream.once("error", (err) => failAdapterRequest(err, "adapter_request_error"));
10854
10895
  upstream.once("close", () => {
10855
10896
  if (!headersReceived && !failed) {
10856
- failAdapterRequest(new Error("Relay adapter connection closed before a response"));
10897
+ failAdapterRequest(
10898
+ new Error("Relay adapter connection closed before a response"),
10899
+ "adapter_request_close"
10900
+ );
10857
10901
  }
10858
10902
  });
10859
10903
  upstream.end(rawBody);
@@ -10886,13 +10930,19 @@ function forwardPlainHttp(req, res) {
10886
10930
  async function startHttpProxy(options) {
10887
10931
  const certificates = ensureHttpProxyCertificates();
10888
10932
  const routesById = /* @__PURE__ */ new Map();
10933
+ const reservedModelIds = /* @__PURE__ */ new Set();
10889
10934
  for (const route of options.routes) {
10890
- for (const id of routeLookupIds(route.aliasId)) routesById.set(id, route);
10935
+ routesById.set(normalizeRouteLookupId(route.aliasId), route);
10891
10936
  }
10892
10937
  for (const alias of options.modelAliases ?? []) {
10893
- const route = routesById.get(alias.routeId);
10938
+ const aliasId = normalizeRouteLookupId(alias.name);
10939
+ reservedModelIds.add(aliasId);
10940
+ const route = routesById.get(normalizeRouteLookupId(alias.routeId));
10894
10941
  if (!route) continue;
10895
- for (const id of routeLookupIds(alias.name)) routesById.set(id, route);
10942
+ routesById.set(aliasId, route);
10943
+ }
10944
+ for (const modelId of options.reservedModelIds ?? []) {
10945
+ reservedModelIds.add(normalizeRouteLookupId(modelId));
10896
10946
  }
10897
10947
  const anthropicOrigin = new URL2(options.anthropicOrigin ?? "https://api.anthropic.com");
10898
10948
  let adapter = options.adapterHandle ?? null;
@@ -10907,6 +10957,7 @@ async function startHttpProxy(options) {
10907
10957
  options.modelAliases
10908
10958
  );
10909
10959
  }
10960
+ let shuttingDown = false;
10910
10961
  const mitmServer = https.createServer({
10911
10962
  key: certificates.serverKey,
10912
10963
  cert: certificates.serverCert,
@@ -10925,13 +10976,49 @@ async function startHttpProxy(options) {
10925
10976
  const requestId = randomUUID8();
10926
10977
  let parsed = null;
10927
10978
  let route;
10979
+ let adapterBody = rawBody;
10980
+ const contentEncoding = (Array.isArray(req.headers["content-encoding"]) ? req.headers["content-encoding"].join(",") : req.headers["content-encoding"] ?? "").trim().toLowerCase();
10981
+ const encodedRequestBody = contentEncoding !== "" && contentEncoding !== "identity";
10928
10982
  try {
10929
- parsed = JSON.parse(rawBody.toString("utf8"));
10930
- if (typeof parsed.model === "string") route = routesById.get(parsed.model);
10983
+ const decodedBody = decodeRequestBody(rawBody, req.headers["content-encoding"]);
10984
+ parsed = JSON.parse(decodedBody);
10985
+ if (encodedRequestBody) adapterBody = Buffer.from(decodedBody);
10986
+ if (typeof parsed.model === "string") {
10987
+ route = routesById.get(normalizeRouteLookupId(parsed.model));
10988
+ }
10931
10989
  } catch {
10990
+ if (encodedRequestBody) {
10991
+ res.writeHead(400, { "Content-Type": "application/json" });
10992
+ res.end(JSON.stringify({
10993
+ type: "error",
10994
+ error: {
10995
+ type: "invalid_request_error",
10996
+ message: "Unable to inspect compressed request body"
10997
+ }
10998
+ }));
10999
+ return;
11000
+ }
10932
11001
  }
10933
11002
  const claudeSessionIdHeader = Array.isArray(req.headers["x-claude-code-session-id"]) ? req.headers["x-claude-code-session-id"][0] : req.headers["x-claude-code-session-id"];
10934
11003
  const claudeSessionId = parsed ? extractClaudeSessionId(parsed, claudeSessionIdHeader) : void 0;
11004
+ const requestedModel = typeof parsed?.model === "string" ? parsed.model : void 0;
11005
+ const unresolvedRoutedModel = !route && requestedModel !== void 0 && (normalizeRouteLookupId(requestedModel).startsWith(HTTP_PROXY_MODEL_PREFIX) || reservedModelIds.has(normalizeRouteLookupId(requestedModel)));
11006
+ if (unresolvedRoutedModel) {
11007
+ const message = routeUnavailableMessage(requestedModel);
11008
+ if (messagesEndpoint === "messages" && options.inferenceLogPath) {
11009
+ writeInferenceRouteUnavailableLog(options.inferenceLogPath, {
11010
+ requestId,
11011
+ modelId: requestedModel,
11012
+ statusCode: 400
11013
+ });
11014
+ }
11015
+ res.writeHead(400, { "Content-Type": "application/json" });
11016
+ res.end(JSON.stringify({
11017
+ type: "error",
11018
+ error: { type: "invalid_request_error", message }
11019
+ }));
11020
+ return;
11021
+ }
10935
11022
  if (messagesEndpoint === "messages" && options.inferenceLogPath) {
10936
11023
  const provider = route ? route.providerId ?? route.aliasId.split(":")[1] ?? "unknown" : "anthropic";
10937
11024
  writeInferenceRequestLog(options.inferenceLogPath, {
@@ -10957,13 +11044,22 @@ async function startHttpProxy(options) {
10957
11044
  });
10958
11045
  }
10959
11046
  if (route && adapter) {
10960
- await forwardToAdapter(req, res, rawBody, adapter, messagesEndpoint === "messages" && options.inferenceLogPath ? {
10961
- logPath: options.inferenceLogPath,
10962
- requestId,
10963
- modelId: typeof parsed?.model === "string" ? parsed.model : "unknown",
10964
- provider: route.providerId ?? route.aliasId.split(":")[1] ?? "unknown",
10965
- progressIntervalMs: options.responseProgressIntervalMs ?? INFERENCE_PROGRESS_INTERVAL_MS
10966
- } : void 0);
11047
+ await forwardToAdapter(
11048
+ req,
11049
+ res,
11050
+ adapterBody,
11051
+ adapter,
11052
+ options.adapterRequest,
11053
+ messagesEndpoint === "messages" && options.inferenceLogPath ? {
11054
+ logPath: options.inferenceLogPath,
11055
+ requestId,
11056
+ claudeSessionId,
11057
+ modelId: typeof parsed?.model === "string" ? parsed.model : "unknown",
11058
+ provider: route.providerId ?? route.aliasId.split(":")[1] ?? "unknown",
11059
+ progressIntervalMs: options.responseProgressIntervalMs ?? INFERENCE_PROGRESS_INTERVAL_MS
11060
+ } : void 0,
11061
+ () => shuttingDown
11062
+ );
10967
11063
  return;
10968
11064
  }
10969
11065
  await forwardRawAnthropicRequest(
@@ -10983,6 +11079,7 @@ async function startHttpProxy(options) {
10983
11079
  messagesEndpoint === "messages" && options.inferenceLogPath ? (usage) => writeInferenceResponseLifecycleLog(options.inferenceLogPath, {
10984
11080
  event: "response_usage",
10985
11081
  requestId,
11082
+ claudeSessionId,
10986
11083
  modelId: typeof parsed?.model === "string" ? parsed.model : "unknown",
10987
11084
  provider: "anthropic",
10988
11085
  route: "passthrough",
@@ -10991,10 +11088,12 @@ async function startHttpProxy(options) {
10991
11088
  messagesEndpoint === "messages" && options.inferenceLogPath ? {
10992
11089
  logPath: options.inferenceLogPath,
10993
11090
  requestId,
11091
+ claudeSessionId,
10994
11092
  modelId: typeof parsed?.model === "string" ? parsed.model : "unknown",
10995
11093
  provider: "anthropic",
10996
11094
  progressIntervalMs: options.responseProgressIntervalMs ?? INFERENCE_PROGRESS_INTERVAL_MS
10997
- } : void 0
11095
+ } : void 0,
11096
+ () => shuttingDown
10998
11097
  );
10999
11098
  return;
11000
11099
  }
@@ -11072,6 +11171,7 @@ async function startHttpProxy(options) {
11072
11171
  inferenceLogPath: options.inferenceLogPath,
11073
11172
  webSocketDiagnosticsLogPath: options.webSocketDiagnosticsLogPath,
11074
11173
  close: async () => {
11174
+ shuttingDown = true;
11075
11175
  for (const socket of sockets) socket.destroy();
11076
11176
  await new Promise((resolve2) => proxyServer.close(() => resolve2()));
11077
11177
  mitmServer.close();
@@ -11139,18 +11239,40 @@ function reportSkippedHttpProxyFavorites(loaded) {
11139
11239
  );
11140
11240
  }
11141
11241
  }
11142
- async function startConfiguredHttpProxy(port, debug = false, inferenceLogPath = getInferenceRequestLogPath(), debugLogPath, webSocketDiagnosticsLogPath) {
11143
- const loaded = await loadHttpProxyRoutes();
11144
- const handle = await startHttpProxy({
11242
+ function buildConfiguredHttpProxyOptions(loaded, port, debug = false, inferenceLogPath = getInferenceRequestLogPath(), debugLogPath, webSocketDiagnosticsLogPath) {
11243
+ return {
11145
11244
  host: "127.0.0.1",
11146
11245
  port,
11147
11246
  routes: loaded.routes,
11148
11247
  modelAliases: loaded.aliases,
11248
+ reservedModelIds: loaded.unavailableAliases.map((alias) => alias.name),
11149
11249
  debug,
11150
11250
  debugLogPath,
11151
11251
  inferenceLogPath,
11152
11252
  webSocketDiagnosticsLogPath
11153
- });
11253
+ };
11254
+ }
11255
+ function formatHttpProxyEnvironmentLines(handle, baseEnv = process.env) {
11256
+ const env = { ...baseEnv };
11257
+ const hadNoProxy = env["NO_PROXY"] !== void 0 || env["no_proxy"] !== void 0;
11258
+ removeAnthropicProxyBypass(env);
11259
+ return [
11260
+ ` HTTPS_PROXY=http://127.0.0.1:${handle.port}`,
11261
+ ` HTTP_PROXY=http://127.0.0.1:${handle.port}`,
11262
+ ` NODE_EXTRA_CA_CERTS=${handle.caCertPath}`,
11263
+ ...hadNoProxy ? [` NO_PROXY=${env["NO_PROXY"] ?? ""}`, ` no_proxy=${env["no_proxy"] ?? ""}`] : []
11264
+ ];
11265
+ }
11266
+ async function startConfiguredHttpProxy(port, debug = false, inferenceLogPath = getInferenceRequestLogPath(), debugLogPath, webSocketDiagnosticsLogPath) {
11267
+ const loaded = await loadHttpProxyRoutes();
11268
+ const handle = await startHttpProxy(buildConfiguredHttpProxyOptions(
11269
+ loaded,
11270
+ port,
11271
+ debug,
11272
+ inferenceLogPath,
11273
+ debugLogPath,
11274
+ webSocketDiagnosticsLogPath
11275
+ ));
11154
11276
  handle.caCertPath = ensureHttpProxyCaBundle(
11155
11277
  handle.caCertPath,
11156
11278
  process.env["NODE_EXTRA_CA_CERTS"]
@@ -11194,9 +11316,7 @@ async function runHttpProxyServerCommand(debug = false, webSocketDiagnostics = f
11194
11316
  });
11195
11317
  console.log("");
11196
11318
  console.log(pc9.bold(pc9.green("clodex proxy-mode server running")));
11197
- console.log(` HTTPS_PROXY=http://127.0.0.1:${handle.port}`);
11198
- console.log(` HTTP_PROXY=http://127.0.0.1:${handle.port}`);
11199
- console.log(` NODE_EXTRA_CA_CERTS=${handle.caCertPath}`);
11319
+ for (const line of formatHttpProxyEnvironmentLines(handle)) console.log(line);
11200
11320
  console.log(` Request log: ${handle.inferenceLogPath}`);
11201
11321
  if (handle.webSocketDiagnosticsLogPath) {
11202
11322
  console.log(` WebSocket diagnostics: ${handle.webSocketDiagnosticsLogPath}`);
@@ -12824,10 +12944,18 @@ function printHelp(text4) {
12824
12944
  ${text4}
12825
12945
  `);
12826
12946
  }
12827
- async function launchClaudeViaCatalog(catalogRoutes, startingRoute, contextWindow, trace, claudeArgs) {
12947
+ async function launchClaudeViaCatalog(catalogRoutes, startingRoute, modelAliases, contextWindow, trace, claudeArgs) {
12828
12948
  let proxyHandle;
12829
12949
  try {
12830
- proxyHandle = await startProxyCatalog(catalogRoutes, startingRoute.aliasId, trace);
12950
+ proxyHandle = await startProxyCatalog(
12951
+ catalogRoutes,
12952
+ startingRoute.aliasId,
12953
+ trace,
12954
+ void 0,
12955
+ void 0,
12956
+ void 0,
12957
+ modelAliases
12958
+ );
12831
12959
  p12.log.info(
12832
12960
  `Switch menu active \u2014 proxy on port ${proxyHandle.port} ` + pc13.dim(`(${catalogRoutes.length} model${catalogRoutes.length !== 1 ? "s" : ""} in /model)`)
12833
12961
  );
@@ -13380,6 +13508,7 @@ Error: ${launchPlan.error}
13380
13508
  return launchClaudeViaCatalog(
13381
13509
  catalogRoutes,
13382
13510
  startingRoute,
13511
+ resolveCatalogModelAliases(prefs.modelAliases ?? [], resolveRoute),
13383
13512
  selectedModel.contextWindow,
13384
13513
  trace,
13385
13514
  claudeArgs