@bman654/clodex 2.0.0 → 2.1.0

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.0",
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) {
@@ -5062,6 +5037,7 @@ function nonNegativeInteger(value) {
5062
5037
  return value !== void 0 && Number.isFinite(value) ? Math.max(0, Math.round(value)) : void 0;
5063
5038
  }
5064
5039
  function writeInferenceResponseLifecycleLog(path, entry) {
5040
+ const claudeSessionId = safeClaudeSessionId(entry.claudeSessionId);
5065
5041
  const statusCode = nonNegativeInteger(entry.statusCode);
5066
5042
  const durationMs = nonNegativeInteger(entry.durationMs);
5067
5043
  const timeToFirstByteMs = nonNegativeInteger(entry.timeToFirstByteMs);
@@ -5081,6 +5057,7 @@ function writeInferenceResponseLifecycleLog(path, entry) {
5081
5057
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
5082
5058
  event: entry.event,
5083
5059
  requestId: compactLogValue(entry.requestId, 100),
5060
+ ...claudeSessionId ? { claudeSessionId } : {},
5084
5061
  modelId: compactLogValue(entry.modelId),
5085
5062
  provider: compactLogValue(entry.provider, 200),
5086
5063
  route: entry.route,
@@ -5103,7 +5080,8 @@ function writeInferenceResponseLifecycleLog(path, entry) {
5103
5080
  ...cacheReadInputTokens !== void 0 ? { cacheReadInputTokens } : {},
5104
5081
  ...entry.lastPartType ? { lastPartType: compactLogValue(entry.lastPartType, 100) } : {},
5105
5082
  ...entry.errorType ? { errorType: compactLogValue(entry.errorType, 200) } : {},
5106
- ...entry.errorSignature ? { errorSignature: compactLogValue(entry.errorSignature, 100) } : {}
5083
+ ...entry.errorSignature ? { errorSignature: compactLogValue(entry.errorSignature, 100) } : {},
5084
+ ...entry.terminationSource ? { terminationSource: entry.terminationSource } : {}
5107
5085
  }));
5108
5086
  }
5109
5087
  function writeProxyLifecycleLog(path, entry) {
@@ -5154,6 +5132,15 @@ function writeInferenceResponseErrorLog(path, entry) {
5154
5132
  ...includeContent ? { errorContent: compactLogValueWithMarker(entry.errorContent, RESPONSE_ERROR_MAX) } : {}
5155
5133
  }));
5156
5134
  }
5135
+ function writeInferenceRouteUnavailableLog(path, entry) {
5136
+ writeSecureLogLine(path, JSON.stringify({
5137
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
5138
+ event: "route_unavailable",
5139
+ requestId: compactLogValue(entry.requestId, 100),
5140
+ modelId: compactLogValue(entry.modelId),
5141
+ statusCode: entry.statusCode
5142
+ }));
5143
+ }
5157
5144
  function makeTraceLogger(logPath) {
5158
5145
  resetTraceLog(logPath);
5159
5146
  return (message) => writeSecureLogLine(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${message}`);
@@ -7309,6 +7296,38 @@ function sendJson(res, status, body) {
7309
7296
  res.end(json);
7310
7297
  }
7311
7298
 
7299
+ // src/model-aliases.ts
7300
+ var MODEL_ALIAS_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
7301
+ function isValidModelAlias(name) {
7302
+ return MODEL_ALIAS_PATTERN.test(name);
7303
+ }
7304
+ function parseModelAliasAssignment(value) {
7305
+ const separator = value.indexOf("=");
7306
+ if (separator < 1 || separator === value.length - 1) {
7307
+ return { error: "Alias must use name=clodex:<provider-id>:<model-id>." };
7308
+ }
7309
+ const name = value.slice(0, separator).trim();
7310
+ if (!isValidModelAlias(name)) {
7311
+ return { error: "Alias names must be 1-64 letters, numbers, dots, underscores, or hyphens." };
7312
+ }
7313
+ const rawTarget = value.slice(separator + 1).trim();
7314
+ const target = rawTarget.startsWith("clodex:") ? rawTarget.slice("clodex:".length) : rawTarget;
7315
+ const targetSeparator = target.indexOf(":");
7316
+ if (targetSeparator < 1 || targetSeparator === target.length - 1) {
7317
+ return { error: "Alias target must use clodex:<provider-id>:<model-id>." };
7318
+ }
7319
+ return {
7320
+ name,
7321
+ providerId: target.slice(0, targetSeparator),
7322
+ // `models --list` prints Claude's synthetic context suffix. It is a client
7323
+ // routing hint, not part of the provider catalog id stored in favorites.
7324
+ modelId: stripOneMContextSuffix(target.slice(targetSeparator + 1))
7325
+ };
7326
+ }
7327
+ function modelAliasTarget(alias) {
7328
+ return `clodex:${alias.providerId}:${alias.modelId}`;
7329
+ }
7330
+
7312
7331
  // src/catalog.ts
7313
7332
  function localModelToRoute(lp, model) {
7314
7333
  if (model.modelFormat === "anthropic" && !model.baseUrl) return null;
@@ -7349,6 +7368,12 @@ function makeRouteResolver(localProviders) {
7349
7368
  return provider && model ? localModelToRoute(provider, model) ?? void 0 : void 0;
7350
7369
  };
7351
7370
  }
7371
+ function resolveCatalogModelAliases(modelAliases, resolveRoute) {
7372
+ return modelAliases.map((alias) => ({
7373
+ name: alias.name,
7374
+ routeId: resolveRoute(alias.providerId, alias.modelId)?.aliasId ?? modelAliasTarget(alias)
7375
+ }));
7376
+ }
7352
7377
  function buildCatalogRoutes(startingRoute, favorites, resolveRoute, max = MAX_MODEL_CATALOG) {
7353
7378
  const droppedFavorites = [];
7354
7379
  const tail = favorites.map((fav) => {
@@ -7363,38 +7388,6 @@ function buildCatalogRoutes(startingRoute, favorites, resolveRoute, max = MAX_MO
7363
7388
  return { routes, droppedFavorites };
7364
7389
  }
7365
7390
 
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
7391
  // src/http-proxy/routes.ts
7399
7392
  var HTTP_PROXY_MODEL_PREFIX = "clodex:";
7400
7393
  function httpProxyModelId(providerId, modelId) {
@@ -7578,6 +7571,11 @@ function formatOpenAIModels(models) {
7578
7571
  };
7579
7572
  }
7580
7573
 
7574
+ // src/route-unavailable.ts
7575
+ function routeUnavailableMessage(modelId) {
7576
+ return `Clodex model route '${modelId}' is unavailable. Run \`clodex models --list\` to see available routes, or \`clodex patch\` to refresh saved aliases.`;
7577
+ }
7578
+
7581
7579
  // src/upstream-forward.ts
7582
7580
  import { Readable } from "stream";
7583
7581
 
@@ -8678,7 +8676,7 @@ async function listenTcpServer(server, port, host) {
8678
8676
  // src/proxy.ts
8679
8677
  var STREAM_KEEPALIVE_INTERVAL_MS = 2e4;
8680
8678
  var STREAM_KEEPALIVE_PING = 'event: ping\ndata: {"type":"ping"}\n\n';
8681
- function createTranslationLifecycle(logPath, requestId, modelId, provider) {
8679
+ function createTranslationLifecycle(logPath, requestId, claudeSessionId, modelId, provider) {
8682
8680
  if (!logPath || !requestId) return void 0;
8683
8681
  const startedAt = Date.now();
8684
8682
  let firstPartAt;
@@ -8693,6 +8691,7 @@ function createTranslationLifecycle(logPath, requestId, modelId, provider) {
8693
8691
  const write = (event, extra = {}) => writeInferenceResponseLifecycleLog(logPath, {
8694
8692
  event,
8695
8693
  requestId,
8694
+ claudeSessionId,
8696
8695
  modelId,
8697
8696
  provider,
8698
8697
  route: "translated",
@@ -8798,11 +8797,7 @@ function aliasModelId(realId, providerId) {
8798
8797
  return `anthropic-${sanitized}__${realId}`;
8799
8798
  }
8800
8799
  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;
8800
+ return byAlias.get(normalizeRouteLookupId(id));
8806
8801
  }
8807
8802
  async function startProxyCatalog(routes, defaultAliasId, debug = false, inferenceLogPath, debugLogPath, webSocketDiagnosticsLogPath, modelAliases) {
8808
8803
  const proxyToken = randomUUID6();
@@ -8810,12 +8805,16 @@ async function startProxyCatalog(routes, defaultAliasId, debug = false, inferenc
8810
8805
  if (routes.length === 0) {
8811
8806
  throw new Error("Proxy catalog requires at least one route");
8812
8807
  }
8813
- const byAlias = new Map(routes.map((r) => [r.aliasId, r]));
8808
+ const byAlias = new Map(routes.map((r) => [normalizeRouteLookupId(r.aliasId), r]));
8809
+ const configuredAliasNames = new Set(
8810
+ (modelAliases ?? []).map((alias) => normalizeRouteLookupId(alias.name))
8811
+ );
8814
8812
  for (const alias of modelAliases ?? []) {
8815
- const route = byAlias.get(alias.routeId);
8816
- if (route && !byAlias.has(alias.name)) byAlias.set(alias.name, route);
8813
+ const route = lookupRoute(byAlias, alias.routeId);
8814
+ const aliasId = normalizeRouteLookupId(alias.name);
8815
+ if (route && !byAlias.has(aliasId)) byAlias.set(aliasId, route);
8817
8816
  }
8818
- const defaultRoute = byAlias.get(defaultAliasId) ?? routes[0];
8817
+ const defaultRoute = lookupRoute(byAlias, defaultAliasId) ?? routes[0];
8819
8818
  const plog = makeProxyLog(debug, debugLogPath);
8820
8819
  const onRejection = (reason) => {
8821
8820
  plog(() => `Unhandled Rejection: ${reason instanceof Error ? reason.stack || reason.message : String(reason)}`);
@@ -8882,7 +8881,13 @@ async function startProxyCatalog(routes, defaultAliasId, debug = false, inferenc
8882
8881
  const clientWantsStream = Boolean(anthropicBody.stream);
8883
8882
  const relayRequestIdRaw = req.headers["x-relay-request-id"];
8884
8883
  const relayRequestId = Array.isArray(relayRequestIdRaw) ? relayRequestIdRaw[0] : relayRequestIdRaw;
8885
- const route = lookupRoute(byAlias, originalModel) ?? defaultRoute;
8884
+ const resolvedRoute = typeof originalModel === "string" ? lookupRoute(byAlias, originalModel) : void 0;
8885
+ const configuredModelUnavailable = typeof originalModel === "string" && (normalizeRouteLookupId(originalModel).startsWith("clodex:") || configuredAliasNames.has(normalizeRouteLookupId(originalModel)));
8886
+ if (!resolvedRoute && configuredModelUnavailable) {
8887
+ anthropicError(res, 400, routeUnavailableMessage(originalModel));
8888
+ return;
8889
+ }
8890
+ const route = resolvedRoute ?? defaultRoute;
8886
8891
  if (messagesEndpoint === "count_tokens" && route.modelFormat !== "anthropic") {
8887
8892
  const inputTokens = estimateAnthropicInputTokens(anthropicBody);
8888
8893
  plog(() => `token-count: local estimate model=${originalModel} input_tokens=${inputTokens}`);
@@ -8994,15 +8999,16 @@ async function startProxyCatalog(routes, defaultAliasId, debug = false, inferenc
8994
8999
  }
8995
9000
  if (usesSdkAdapter) {
8996
9001
  const openAiOAuth = route.npm === "@ai-sdk/openai" && route.authType === "oauth";
9002
+ 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"];
9003
+ const claudeSessionId = extractClaudeSessionId(anthropicBody, claudeSessionIdHeader);
8997
9004
  const translationLifecycle = createTranslationLifecycle(
8998
9005
  inferenceLogPath,
8999
9006
  relayRequestId,
9007
+ claudeSessionId,
9000
9008
  originalModel,
9001
9009
  route.providerId ?? route.aliasId.split(":")[1] ?? "unknown"
9002
9010
  );
9003
9011
  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
9012
  const params = translateRequest(anthropicBody, route.npm, {
9007
9013
  openAiOAuth,
9008
9014
  claudeSessionId,
@@ -10528,7 +10534,7 @@ function requestHeadersWithoutProxyHeaders(req) {
10528
10534
  }
10529
10535
  return headers;
10530
10536
  }
10531
- function forwardRawAnthropicRequest(req, res, rawBody, origin, rejectUnauthorized, onErrorResponse, onResponseUsage, lifecycle) {
10537
+ function forwardRawAnthropicRequest(req, res, rawBody, origin, rejectUnauthorized, onErrorResponse, onResponseUsage, lifecycle, isLocalShutdown = () => false) {
10532
10538
  return new Promise((resolve2) => {
10533
10539
  const startedAt = Date.now();
10534
10540
  let lastActivityAt = startedAt;
@@ -10546,6 +10552,7 @@ function forwardRawAnthropicRequest(req, res, rawBody, origin, rejectUnauthorize
10546
10552
  writeInferenceResponseLifecycleLog(lifecycle.logPath, {
10547
10553
  event,
10548
10554
  requestId: lifecycle.requestId,
10555
+ claudeSessionId: lifecycle.claudeSessionId,
10549
10556
  modelId: lifecycle.modelId,
10550
10557
  provider: lifecycle.provider,
10551
10558
  route: "passthrough",
@@ -10628,7 +10635,8 @@ function forwardRawAnthropicRequest(req, res, rawBody, origin, rejectUnauthorize
10628
10635
  idleMs: now - lastActivityAt,
10629
10636
  bytes,
10630
10637
  chunks,
10631
- errorType: errorType(err)
10638
+ errorType: errorType(err),
10639
+ terminationSource: "upstream_failure"
10632
10640
  });
10633
10641
  done();
10634
10642
  });
@@ -10657,7 +10665,8 @@ function forwardRawAnthropicRequest(req, res, rawBody, origin, rejectUnauthorize
10657
10665
  ...firstByteAt !== void 0 ? { timeToFirstByteMs: firstByteAt - startedAt } : {},
10658
10666
  idleMs: now - lastActivityAt,
10659
10667
  bytes,
10660
- chunks
10668
+ chunks,
10669
+ terminationSource: isLocalShutdown() ? "local_shutdown" : "downstream_client"
10661
10670
  });
10662
10671
  upstream.destroy(new Error("Client disconnected"));
10663
10672
  done();
@@ -10677,7 +10686,8 @@ function forwardRawAnthropicRequest(req, res, rawBody, origin, rejectUnauthorize
10677
10686
  idleMs: now - lastActivityAt,
10678
10687
  bytes,
10679
10688
  chunks,
10680
- errorType: errorType(err)
10689
+ errorType: errorType(err),
10690
+ terminationSource: "upstream_failure"
10681
10691
  });
10682
10692
  onErrorResponse?.(502, `Anthropic upstream unreachable: ${err.message}`);
10683
10693
  if (!res.headersSent) res.writeHead(502, { "Content-Type": "text/plain" });
@@ -10687,7 +10697,7 @@ function forwardRawAnthropicRequest(req, res, rawBody, origin, rejectUnauthorize
10687
10697
  upstream.end(rawBody);
10688
10698
  });
10689
10699
  }
10690
- function forwardToAdapter(req, res, rawBody, adapter, lifecycle) {
10700
+ function forwardToAdapter(req, res, rawBody, adapter, lifecycle, isLocalShutdown = () => false) {
10691
10701
  return new Promise((resolve2) => {
10692
10702
  const startedAt = Date.now();
10693
10703
  let lastActivityAt = startedAt;
@@ -10706,6 +10716,7 @@ function forwardToAdapter(req, res, rawBody, adapter, lifecycle) {
10706
10716
  writeInferenceResponseLifecycleLog(lifecycle.logPath, {
10707
10717
  event,
10708
10718
  requestId: lifecycle.requestId,
10719
+ claudeSessionId: lifecycle.claudeSessionId,
10709
10720
  modelId: lifecycle.modelId,
10710
10721
  provider: lifecycle.provider,
10711
10722
  route: "translated",
@@ -10757,7 +10768,8 @@ function forwardToAdapter(req, res, rawBody, adapter, lifecycle) {
10757
10768
  ...firstByteAt !== void 0 ? { timeToFirstByteMs: firstByteAt - startedAt } : {},
10758
10769
  idleMs: now - lastActivityAt,
10759
10770
  bytes,
10760
- chunks
10771
+ chunks,
10772
+ terminationSource: isLocalShutdown() ? "local_shutdown" : "downstream_client"
10761
10773
  });
10762
10774
  adapterResponse?.destroy(new Error("Client disconnected"));
10763
10775
  upstream?.destroy(new Error("Client disconnected"));
@@ -10779,7 +10791,8 @@ function forwardToAdapter(req, res, rawBody, adapter, lifecycle) {
10779
10791
  idleMs: now - lastActivityAt,
10780
10792
  bytes,
10781
10793
  chunks,
10782
- errorType: err.name
10794
+ errorType: err.name,
10795
+ terminationSource: "upstream_failure"
10783
10796
  });
10784
10797
  if (!res.headersSent) res.writeHead(502, { "Content-Type": "text/plain" });
10785
10798
  res.end(`Relay adapter unreachable: ${err.message}`);
@@ -10834,7 +10847,8 @@ function forwardToAdapter(req, res, rawBody, adapter, lifecycle) {
10834
10847
  idleMs: now - lastActivityAt,
10835
10848
  bytes,
10836
10849
  chunks,
10837
- errorType: err.name
10850
+ errorType: err.name,
10851
+ terminationSource: "upstream_failure"
10838
10852
  });
10839
10853
  if (!res.writableEnded) res.destroy(err);
10840
10854
  resolve2();
@@ -10886,13 +10900,19 @@ function forwardPlainHttp(req, res) {
10886
10900
  async function startHttpProxy(options) {
10887
10901
  const certificates = ensureHttpProxyCertificates();
10888
10902
  const routesById = /* @__PURE__ */ new Map();
10903
+ const reservedModelIds = /* @__PURE__ */ new Set();
10889
10904
  for (const route of options.routes) {
10890
- for (const id of routeLookupIds(route.aliasId)) routesById.set(id, route);
10905
+ routesById.set(normalizeRouteLookupId(route.aliasId), route);
10891
10906
  }
10892
10907
  for (const alias of options.modelAliases ?? []) {
10893
- const route = routesById.get(alias.routeId);
10908
+ const aliasId = normalizeRouteLookupId(alias.name);
10909
+ reservedModelIds.add(aliasId);
10910
+ const route = routesById.get(normalizeRouteLookupId(alias.routeId));
10894
10911
  if (!route) continue;
10895
- for (const id of routeLookupIds(alias.name)) routesById.set(id, route);
10912
+ routesById.set(aliasId, route);
10913
+ }
10914
+ for (const modelId of options.reservedModelIds ?? []) {
10915
+ reservedModelIds.add(normalizeRouteLookupId(modelId));
10896
10916
  }
10897
10917
  const anthropicOrigin = new URL2(options.anthropicOrigin ?? "https://api.anthropic.com");
10898
10918
  let adapter = options.adapterHandle ?? null;
@@ -10907,6 +10927,7 @@ async function startHttpProxy(options) {
10907
10927
  options.modelAliases
10908
10928
  );
10909
10929
  }
10930
+ let shuttingDown = false;
10910
10931
  const mitmServer = https.createServer({
10911
10932
  key: certificates.serverKey,
10912
10933
  cert: certificates.serverCert,
@@ -10925,13 +10946,49 @@ async function startHttpProxy(options) {
10925
10946
  const requestId = randomUUID8();
10926
10947
  let parsed = null;
10927
10948
  let route;
10949
+ let adapterBody = rawBody;
10950
+ const contentEncoding = (Array.isArray(req.headers["content-encoding"]) ? req.headers["content-encoding"].join(",") : req.headers["content-encoding"] ?? "").trim().toLowerCase();
10951
+ const encodedRequestBody = contentEncoding !== "" && contentEncoding !== "identity";
10928
10952
  try {
10929
- parsed = JSON.parse(rawBody.toString("utf8"));
10930
- if (typeof parsed.model === "string") route = routesById.get(parsed.model);
10953
+ const decodedBody = decodeRequestBody(rawBody, req.headers["content-encoding"]);
10954
+ parsed = JSON.parse(decodedBody);
10955
+ if (encodedRequestBody) adapterBody = Buffer.from(decodedBody);
10956
+ if (typeof parsed.model === "string") {
10957
+ route = routesById.get(normalizeRouteLookupId(parsed.model));
10958
+ }
10931
10959
  } catch {
10960
+ if (encodedRequestBody) {
10961
+ res.writeHead(400, { "Content-Type": "application/json" });
10962
+ res.end(JSON.stringify({
10963
+ type: "error",
10964
+ error: {
10965
+ type: "invalid_request_error",
10966
+ message: "Unable to inspect compressed request body"
10967
+ }
10968
+ }));
10969
+ return;
10970
+ }
10932
10971
  }
10933
10972
  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
10973
  const claudeSessionId = parsed ? extractClaudeSessionId(parsed, claudeSessionIdHeader) : void 0;
10974
+ const requestedModel = typeof parsed?.model === "string" ? parsed.model : void 0;
10975
+ const unresolvedRoutedModel = !route && requestedModel !== void 0 && (normalizeRouteLookupId(requestedModel).startsWith(HTTP_PROXY_MODEL_PREFIX) || reservedModelIds.has(normalizeRouteLookupId(requestedModel)));
10976
+ if (unresolvedRoutedModel) {
10977
+ const message = routeUnavailableMessage(requestedModel);
10978
+ if (messagesEndpoint === "messages" && options.inferenceLogPath) {
10979
+ writeInferenceRouteUnavailableLog(options.inferenceLogPath, {
10980
+ requestId,
10981
+ modelId: requestedModel,
10982
+ statusCode: 400
10983
+ });
10984
+ }
10985
+ res.writeHead(400, { "Content-Type": "application/json" });
10986
+ res.end(JSON.stringify({
10987
+ type: "error",
10988
+ error: { type: "invalid_request_error", message }
10989
+ }));
10990
+ return;
10991
+ }
10935
10992
  if (messagesEndpoint === "messages" && options.inferenceLogPath) {
10936
10993
  const provider = route ? route.providerId ?? route.aliasId.split(":")[1] ?? "unknown" : "anthropic";
10937
10994
  writeInferenceRequestLog(options.inferenceLogPath, {
@@ -10957,13 +11014,21 @@ async function startHttpProxy(options) {
10957
11014
  });
10958
11015
  }
10959
11016
  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);
11017
+ await forwardToAdapter(
11018
+ req,
11019
+ res,
11020
+ adapterBody,
11021
+ adapter,
11022
+ messagesEndpoint === "messages" && options.inferenceLogPath ? {
11023
+ logPath: options.inferenceLogPath,
11024
+ requestId,
11025
+ claudeSessionId,
11026
+ modelId: typeof parsed?.model === "string" ? parsed.model : "unknown",
11027
+ provider: route.providerId ?? route.aliasId.split(":")[1] ?? "unknown",
11028
+ progressIntervalMs: options.responseProgressIntervalMs ?? INFERENCE_PROGRESS_INTERVAL_MS
11029
+ } : void 0,
11030
+ () => shuttingDown
11031
+ );
10967
11032
  return;
10968
11033
  }
10969
11034
  await forwardRawAnthropicRequest(
@@ -10983,6 +11048,7 @@ async function startHttpProxy(options) {
10983
11048
  messagesEndpoint === "messages" && options.inferenceLogPath ? (usage) => writeInferenceResponseLifecycleLog(options.inferenceLogPath, {
10984
11049
  event: "response_usage",
10985
11050
  requestId,
11051
+ claudeSessionId,
10986
11052
  modelId: typeof parsed?.model === "string" ? parsed.model : "unknown",
10987
11053
  provider: "anthropic",
10988
11054
  route: "passthrough",
@@ -10991,10 +11057,12 @@ async function startHttpProxy(options) {
10991
11057
  messagesEndpoint === "messages" && options.inferenceLogPath ? {
10992
11058
  logPath: options.inferenceLogPath,
10993
11059
  requestId,
11060
+ claudeSessionId,
10994
11061
  modelId: typeof parsed?.model === "string" ? parsed.model : "unknown",
10995
11062
  provider: "anthropic",
10996
11063
  progressIntervalMs: options.responseProgressIntervalMs ?? INFERENCE_PROGRESS_INTERVAL_MS
10997
- } : void 0
11064
+ } : void 0,
11065
+ () => shuttingDown
10998
11066
  );
10999
11067
  return;
11000
11068
  }
@@ -11072,6 +11140,7 @@ async function startHttpProxy(options) {
11072
11140
  inferenceLogPath: options.inferenceLogPath,
11073
11141
  webSocketDiagnosticsLogPath: options.webSocketDiagnosticsLogPath,
11074
11142
  close: async () => {
11143
+ shuttingDown = true;
11075
11144
  for (const socket of sockets) socket.destroy();
11076
11145
  await new Promise((resolve2) => proxyServer.close(() => resolve2()));
11077
11146
  mitmServer.close();
@@ -11139,18 +11208,40 @@ function reportSkippedHttpProxyFavorites(loaded) {
11139
11208
  );
11140
11209
  }
11141
11210
  }
11142
- async function startConfiguredHttpProxy(port, debug = false, inferenceLogPath = getInferenceRequestLogPath(), debugLogPath, webSocketDiagnosticsLogPath) {
11143
- const loaded = await loadHttpProxyRoutes();
11144
- const handle = await startHttpProxy({
11211
+ function buildConfiguredHttpProxyOptions(loaded, port, debug = false, inferenceLogPath = getInferenceRequestLogPath(), debugLogPath, webSocketDiagnosticsLogPath) {
11212
+ return {
11145
11213
  host: "127.0.0.1",
11146
11214
  port,
11147
11215
  routes: loaded.routes,
11148
11216
  modelAliases: loaded.aliases,
11217
+ reservedModelIds: loaded.unavailableAliases.map((alias) => alias.name),
11149
11218
  debug,
11150
11219
  debugLogPath,
11151
11220
  inferenceLogPath,
11152
11221
  webSocketDiagnosticsLogPath
11153
- });
11222
+ };
11223
+ }
11224
+ function formatHttpProxyEnvironmentLines(handle, baseEnv = process.env) {
11225
+ const env = { ...baseEnv };
11226
+ const hadNoProxy = env["NO_PROXY"] !== void 0 || env["no_proxy"] !== void 0;
11227
+ removeAnthropicProxyBypass(env);
11228
+ return [
11229
+ ` HTTPS_PROXY=http://127.0.0.1:${handle.port}`,
11230
+ ` HTTP_PROXY=http://127.0.0.1:${handle.port}`,
11231
+ ` NODE_EXTRA_CA_CERTS=${handle.caCertPath}`,
11232
+ ...hadNoProxy ? [` NO_PROXY=${env["NO_PROXY"] ?? ""}`, ` no_proxy=${env["no_proxy"] ?? ""}`] : []
11233
+ ];
11234
+ }
11235
+ async function startConfiguredHttpProxy(port, debug = false, inferenceLogPath = getInferenceRequestLogPath(), debugLogPath, webSocketDiagnosticsLogPath) {
11236
+ const loaded = await loadHttpProxyRoutes();
11237
+ const handle = await startHttpProxy(buildConfiguredHttpProxyOptions(
11238
+ loaded,
11239
+ port,
11240
+ debug,
11241
+ inferenceLogPath,
11242
+ debugLogPath,
11243
+ webSocketDiagnosticsLogPath
11244
+ ));
11154
11245
  handle.caCertPath = ensureHttpProxyCaBundle(
11155
11246
  handle.caCertPath,
11156
11247
  process.env["NODE_EXTRA_CA_CERTS"]
@@ -11194,9 +11285,7 @@ async function runHttpProxyServerCommand(debug = false, webSocketDiagnostics = f
11194
11285
  });
11195
11286
  console.log("");
11196
11287
  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}`);
11288
+ for (const line of formatHttpProxyEnvironmentLines(handle)) console.log(line);
11200
11289
  console.log(` Request log: ${handle.inferenceLogPath}`);
11201
11290
  if (handle.webSocketDiagnosticsLogPath) {
11202
11291
  console.log(` WebSocket diagnostics: ${handle.webSocketDiagnosticsLogPath}`);
@@ -12824,10 +12913,18 @@ function printHelp(text4) {
12824
12913
  ${text4}
12825
12914
  `);
12826
12915
  }
12827
- async function launchClaudeViaCatalog(catalogRoutes, startingRoute, contextWindow, trace, claudeArgs) {
12916
+ async function launchClaudeViaCatalog(catalogRoutes, startingRoute, modelAliases, contextWindow, trace, claudeArgs) {
12828
12917
  let proxyHandle;
12829
12918
  try {
12830
- proxyHandle = await startProxyCatalog(catalogRoutes, startingRoute.aliasId, trace);
12919
+ proxyHandle = await startProxyCatalog(
12920
+ catalogRoutes,
12921
+ startingRoute.aliasId,
12922
+ trace,
12923
+ void 0,
12924
+ void 0,
12925
+ void 0,
12926
+ modelAliases
12927
+ );
12831
12928
  p12.log.info(
12832
12929
  `Switch menu active \u2014 proxy on port ${proxyHandle.port} ` + pc13.dim(`(${catalogRoutes.length} model${catalogRoutes.length !== 1 ? "s" : ""} in /model)`)
12833
12930
  );
@@ -13380,6 +13477,7 @@ Error: ${launchPlan.error}
13380
13477
  return launchClaudeViaCatalog(
13381
13478
  catalogRoutes,
13382
13479
  startingRoute,
13480
+ resolveCatalogModelAliases(prefs.modelAliases ?? [], resolveRoute),
13383
13481
  selectedModel.contextWindow,
13384
13482
  trace,
13385
13483
  claudeArgs