@bman654/clodex 2.3.0 → 2.4.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
@@ -40,7 +40,7 @@ import {
40
40
  withProviderMutationLock,
41
41
  withRegistryWriteLock,
42
42
  withRegistryWriteLockSync
43
- } from "./chunk-W4SVDQCZ.js";
43
+ } from "./chunk-LBEJOEUY.js";
44
44
 
45
45
  // src/cli.ts
46
46
  import pc13 from "picocolors";
@@ -218,7 +218,7 @@ import { join } from "path";
218
218
  // package.json
219
219
  var package_default = {
220
220
  name: "@bman654/clodex",
221
- version: "2.3.0",
221
+ version: "2.4.0",
222
222
  publishConfig: {
223
223
  access: "public"
224
224
  },
@@ -840,6 +840,51 @@ function applyClaudeCodeThirdPartyCompat(env) {
840
840
  env["ENABLE_TOOL_SEARCH"] = "true";
841
841
  env["CLAUDE_CODE_SIMPLE_SYSTEM_PROMPT"] = "0";
842
842
  }
843
+ var LOOPBACK_NO_PROXY_ENTRIES = ["localhost", "127.0.0.1", "::1"];
844
+ function gatewayHostname(gatewayUrl) {
845
+ try {
846
+ return new URL(gatewayUrl).hostname.replace(/^\[|\]$/g, "").toLowerCase();
847
+ } catch {
848
+ return "";
849
+ }
850
+ }
851
+ function isLoopbackHost(host) {
852
+ if (host === "localhost" || host.endsWith(".localhost")) return true;
853
+ if (host === "::1" || host === "0:0:0:0:0:0:0:1") return true;
854
+ const v4 = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(host);
855
+ if (!v4) return false;
856
+ const octets = v4.slice(1).map(Number);
857
+ if (octets.some((o) => o > 255)) return false;
858
+ return octets[0] === 127;
859
+ }
860
+ function effectiveNoProxyValue(env) {
861
+ return env["no_proxy"] || env["NO_PROXY"] || "";
862
+ }
863
+ function normalizedNoProxyEntries(env) {
864
+ return effectiveNoProxyValue(env).split(",").map((value) => value.trim()).filter(Boolean);
865
+ }
866
+ function addGatewayNoProxyBypass(env, gatewayUrl) {
867
+ const hasProxy = Boolean(
868
+ env["HTTPS_PROXY"]?.trim() || env["https_proxy"]?.trim() || env["HTTP_PROXY"]?.trim() || env["http_proxy"]?.trim()
869
+ );
870
+ if (!hasProxy) return;
871
+ const host = gatewayHostname(gatewayUrl);
872
+ if (!isLoopbackHost(host)) return;
873
+ if (effectiveNoProxyValue(env) === "*") return;
874
+ const existing = normalizedNoProxyEntries(env);
875
+ const additions = [...LOOPBACK_NO_PROXY_ENTRIES, host];
876
+ const seen = new Set(existing.map((entry) => entry.toLowerCase()));
877
+ const merged = [...existing];
878
+ for (const entry of additions) {
879
+ const key = entry.toLowerCase();
880
+ if (seen.has(key)) continue;
881
+ seen.add(key);
882
+ merged.push(entry);
883
+ }
884
+ const value = merged.join(",");
885
+ env["NO_PROXY"] = value;
886
+ env["no_proxy"] = value;
887
+ }
843
888
  function buildChildEnv(baseUrl, model, apiKey, proxyPort, contextWindow, enableGatewayDiscovery) {
844
889
  const env = { ...process.env };
845
890
  for (const name of CONFLICTING_ENV_VARS) {
@@ -847,6 +892,7 @@ function buildChildEnv(baseUrl, model, apiKey, proxyPort, contextWindow, enableG
847
892
  }
848
893
  env["ANTHROPIC_BASE_URL"] = proxyPort ? `http://127.0.0.1:${proxyPort}` : baseUrl;
849
894
  env["ANTHROPIC_API_KEY"] = apiKey;
895
+ addGatewayNoProxyBypass(env, env["ANTHROPIC_BASE_URL"]);
850
896
  const bareModel = stripOneMContextSuffix(model);
851
897
  env["ANTHROPIC_MODEL"] = claudeCodeClientModelId(model, contextWindow);
852
898
  env["CLAUDE_CODE_MAX_CONTEXT_TOKENS"] = String(resolveContextWindow(bareModel, contextWindow));
@@ -3414,6 +3460,7 @@ function applyPricingToRegistryProviders(registry, cache) {
3414
3460
  const index = buildPricingIndex(cache);
3415
3461
  let changed = false;
3416
3462
  for (const provider of registry.providers) {
3463
+ if (provider.preserveModelPricing) continue;
3417
3464
  if (!provider.modelsCache?.models.length) continue;
3418
3465
  const platform = TEMPLATE_TO_PRICING_PLATFORM[provider.templateId] ?? TEMPLATE_TO_PRICING_PLATFORM[provider.id];
3419
3466
  const enriched = enrichModelsWithPricing(provider.modelsCache.models, index, platform);
@@ -3716,7 +3763,9 @@ function cachedModelToLocal(cached, provider) {
3716
3763
  reasoning: cached.reasoning ?? modelsDev?.reasoning,
3717
3764
  interleavedReasoningField: cached.interleavedReasoningField ?? modelsDev?.interleaved?.field,
3718
3765
  useResponsesLite: cached.useResponsesLite,
3719
- preferWebSockets: cached.preferWebSockets
3766
+ preferWebSockets: cached.preferWebSockets,
3767
+ modalities: cached.modalities,
3768
+ compatibility: cached.compatibility
3720
3769
  };
3721
3770
  }
3722
3771
  function isAnonymousProvider(provider) {
@@ -3926,6 +3975,7 @@ function localProvidersToServerModels(localProviders) {
3926
3975
  interleavedReasoningField: model.interleavedReasoningField,
3927
3976
  useResponsesLite: model.useResponsesLite,
3928
3977
  preferWebSockets: model.preferWebSockets,
3978
+ compatibility: model.compatibility,
3929
3979
  headers: provider.headers,
3930
3980
  providerData: provider.providerData
3931
3981
  }))
@@ -4044,7 +4094,7 @@ function frameStatusCode(code, discriminator) {
4044
4094
  const numeric = Number(code);
4045
4095
  if (numeric >= 400 && numeric <= 599) return numeric;
4046
4096
  }
4047
- if (/insufficient_quota|rate_limit/.test(discriminator)) return 429;
4097
+ if (/insufficient_quota|rate_limit|usage_limit/.test(discriminator)) return 429;
4048
4098
  if (discriminator.includes("authentication")) return 401;
4049
4099
  if (discriminator.includes("permission")) return 403;
4050
4100
  if (discriminator.includes("not_found")) return 404;
@@ -5267,7 +5317,8 @@ function handleSocketMessage(entry, data) {
5267
5317
  return;
5268
5318
  }
5269
5319
  const errorStatus = type === "error" && !ctx.emittedModelData ? responseErrorStatus(event) : void 0;
5270
- if (FAILURE_EVENT_TYPES.has(type ?? "") && (errorStatus === void 0 || willRetry)) {
5320
+ const emptyFailureTerminal = FAILURE_EVENT_TYPES.has(type ?? "") && errorStatus === void 0 && !willRetry && !ctx.emittedModelData;
5321
+ if (FAILURE_EVENT_TYPES.has(type ?? "") && (errorStatus === void 0 || willRetry) && !emptyFailureTerminal) {
5271
5322
  emitResponseErrorDiagnostic(entry, ctx, {
5272
5323
  source: "response_event",
5273
5324
  upstreamEventType: type,
@@ -5310,6 +5361,39 @@ function handleSocketMessage(entry, data) {
5310
5361
  );
5311
5362
  return;
5312
5363
  }
5364
+ if (emptyFailureTerminal) {
5365
+ const details = responseFailureDetails(event);
5366
+ const named = [details.errorType, details.errorCode, details.incompleteReason].filter((value) => typeof value === "string");
5367
+ const summary = "OpenAI ended the response with no output" + (named.length ? ` (${named.join(" / ")})` : ` (${type})`);
5368
+ const discriminator = [details.errorType, details.errorCode].filter((value) => typeof value === "string").join(" ").toLowerCase();
5369
+ const settledReason = details.incompleteReason === "content_filter" || details.incompleteReason === "max_output_tokens";
5370
+ const numericOrNamed = typeof details.errorCode === "string" ? details.errorCode : void 0;
5371
+ const classified = numericOrNamed !== void 0 || discriminator ? frameStatusCode(numericOrNamed, discriminator) : 500;
5372
+ const statusCode = classified !== 500 ? classified : settledReason ? 400 : 502;
5373
+ const usageLimited = statusCode === 429;
5374
+ const retryAfterSeconds = usageLimited && responseRetryAfterSeconds(event) !== void 0 ? clampRetryAfterSeconds(responseRetryAfterSeconds(event)) : void 0;
5375
+ failContext(
5376
+ entry,
5377
+ ctx,
5378
+ retryAfterSeconds === void 0 ? summary : `${summary}; retry after ${retryAfterSeconds}s`,
5379
+ {
5380
+ source: "empty_failure_terminal",
5381
+ upstreamEventType: type,
5382
+ ...details,
5383
+ // Under DISTINCT keys. `failContext` fingerprints the message it was
5384
+ // given after spreading these, so an `errorMessage*` pair here is
5385
+ // overwritten by the summary's — which would silently discard the only
5386
+ // content-free evidence of what upstream actually said, and leave two
5387
+ // failures with the same type and code indistinguishable. `errorMessage*`
5388
+ // now means "what the client was told", `upstreamMessage*` means "what
5389
+ // upstream said", and both survive.
5390
+ ...diagnosticTextFingerprint("upstreamMessage", responseErrorMessage(event))
5391
+ },
5392
+ statusCode,
5393
+ retryAfterSeconds
5394
+ );
5395
+ return;
5396
+ }
5313
5397
  ctx.pendingEvents.push(event);
5314
5398
  if (isModelDataEvent(type)) flushPending(ctx);
5315
5399
  if (TERMINAL_EVENT_TYPES.has(type ?? "") || type === "error") {
@@ -5805,6 +5889,63 @@ function isCredentialBearingHeader(name) {
5805
5889
  return CREDENTIAL_BEARING_HEADER.test(name);
5806
5890
  }
5807
5891
 
5892
+ // src/model-runtime-compatibility.ts
5893
+ function remapMaxTokensField(body, field) {
5894
+ if (field === "max_tokens") {
5895
+ if (body.max_tokens === void 0 && body.max_completion_tokens !== void 0) {
5896
+ body.max_tokens = body.max_completion_tokens;
5897
+ }
5898
+ delete body.max_completion_tokens;
5899
+ return;
5900
+ }
5901
+ if (field === "max_completion_tokens") {
5902
+ if (body.max_completion_tokens === void 0 && body.max_tokens !== void 0) {
5903
+ body.max_completion_tokens = body.max_tokens;
5904
+ }
5905
+ delete body.max_tokens;
5906
+ }
5907
+ }
5908
+ function transformMessages(messages, compatibility) {
5909
+ if (!Array.isArray(messages)) return messages;
5910
+ const rewriteDeveloper = compatibility.supportsDeveloperRole === false;
5911
+ const replayReasoning = compatibility.requiresReasoningContentOnAssistantMessages === true;
5912
+ if (!rewriteDeveloper && !replayReasoning) return messages;
5913
+ let changed = false;
5914
+ const transformed = messages.map((message) => {
5915
+ if (!message || typeof message !== "object" || Array.isArray(message)) return message;
5916
+ const source = message;
5917
+ const role = source.role;
5918
+ const needsRoleRewrite = rewriteDeveloper && role === "developer";
5919
+ const needsReasoningReplay = replayReasoning && role === "assistant" && !Object.prototype.hasOwnProperty.call(source, "reasoning_content");
5920
+ if (!needsRoleRewrite && !needsReasoningReplay) return message;
5921
+ changed = true;
5922
+ return {
5923
+ ...source,
5924
+ ...needsRoleRewrite ? { role: "system" } : {},
5925
+ ...needsReasoningReplay ? { reasoning_content: "" } : {}
5926
+ };
5927
+ });
5928
+ return changed ? transformed : messages;
5929
+ }
5930
+ function transformOpenAiCompatibleRequestBody(body, compatibility) {
5931
+ const transformed = { ...body };
5932
+ if (compatibility.supportsStore === false) delete transformed.store;
5933
+ if (compatibility.supportsLongCacheRetention === false) {
5934
+ delete transformed.prompt_cache_retention;
5935
+ delete transformed.promptCacheRetention;
5936
+ }
5937
+ remapMaxTokensField(transformed, compatibility.maxTokensField);
5938
+ const messages = transformMessages(transformed.messages, compatibility);
5939
+ if (messages !== transformed.messages) transformed.messages = messages;
5940
+ const hasReasoningEffort = typeof transformed.reasoning_effort === "string" && transformed.reasoning_effort.trim().length > 0;
5941
+ if (hasReasoningEffort && compatibility.thinkingFormat === "deepseek") {
5942
+ if (transformed.thinking === void 0) transformed.thinking = { type: "enabled" };
5943
+ } else if (hasReasoningEffort && compatibility.thinkingFormat === "qwen") {
5944
+ if (transformed.enable_thinking === void 0) transformed.enable_thinking = true;
5945
+ }
5946
+ return transformed;
5947
+ }
5948
+
5808
5949
  // src/provider-factory.ts
5809
5950
  var RESPONSES_ONLY_PREFIXES = [
5810
5951
  "gpt-5-codex",
@@ -5947,7 +6088,10 @@ async function createLanguageModel(spec) {
5947
6088
  baseURL: baseURL ?? "",
5948
6089
  ...spec.authType !== "none" && apiKey.trim() ? { apiKey } : {},
5949
6090
  ...spec.authType === "none" ? { fetch: fetchWithoutCredentialHeaders } : {},
5950
- ...spec.headers ? { headers: spec.headers } : {}
6091
+ ...spec.headers ? { headers: spec.headers } : {},
6092
+ ...spec.compatibility ? {
6093
+ transformRequestBody: (body) => transformOpenAiCompatibleRequestBody(body, spec.compatibility)
6094
+ } : {}
5951
6095
  };
5952
6096
  model = createOpenAICompatible({
5953
6097
  ...options
@@ -6203,8 +6347,68 @@ function mapCodexEffortToGeminiBudget(effort) {
6203
6347
  if (!level) return void 0;
6204
6348
  return GEMINI_25_BUDGETS[level];
6205
6349
  }
6350
+ function compatibilityReasoningCapabilities(metadata) {
6351
+ const compatibility = metadata?.compatibility;
6352
+ if (!compatibility) return void 0;
6353
+ if (compatibility.supportsReasoningEffort === false) {
6354
+ return metadata?.reasoning === false ? EMPTY_REASONING : {
6355
+ ...EMPTY_REASONING,
6356
+ mode: "internal-only",
6357
+ source: "provider-metadata",
6358
+ confidence: "documented"
6359
+ };
6360
+ }
6361
+ if (compatibility.reasoningEffortMap) {
6362
+ const levels = Object.entries(compatibility.reasoningEffortMap).filter(([, mapped]) => mapped !== null).map(([level]) => level);
6363
+ if (levels.length === 0) {
6364
+ return metadata?.reasoning === false ? EMPTY_REASONING : {
6365
+ ...EMPTY_REASONING,
6366
+ mode: "internal-only",
6367
+ source: "provider-metadata",
6368
+ confidence: "documented"
6369
+ };
6370
+ }
6371
+ const preferredDefault = ["medium", "high", "max", "low"].find((level) => levels.includes(level));
6372
+ return {
6373
+ levels,
6374
+ defaultLevel: preferredDefault ?? levels[0],
6375
+ supportsSummaries: false,
6376
+ mode: "controllable",
6377
+ source: "provider-metadata",
6378
+ confidence: "documented",
6379
+ wireFormat: compatibility.thinkingFormat === "deepseek" ? { kind: "deepseek-thinking" } : { kind: "openai-reasoning-effort" }
6380
+ };
6381
+ }
6382
+ if (compatibility.supportsReasoningEffort === true || compatibility.thinkingFormat !== void 0) {
6383
+ if (metadata?.reasoning === false) return EMPTY_REASONING;
6384
+ return {
6385
+ levels: ["low", "medium", "high"],
6386
+ defaultLevel: "medium",
6387
+ supportsSummaries: false,
6388
+ mode: "controllable",
6389
+ source: "provider-metadata",
6390
+ confidence: "documented",
6391
+ wireFormat: compatibility.thinkingFormat === "deepseek" ? { kind: "deepseek-thinking" } : { kind: "openai-reasoning-effort" }
6392
+ };
6393
+ }
6394
+ return void 0;
6395
+ }
6396
+ function compatibilityReasoningEffort(effort, modelId, compatibility) {
6397
+ if (compatibility.supportsReasoningEffort === false) return void 0;
6398
+ const map = compatibility.reasoningEffortMap;
6399
+ if (map) {
6400
+ if (!Object.prototype.hasOwnProperty.call(map, effort)) return void 0;
6401
+ return map[effort] ?? void 0;
6402
+ }
6403
+ if (compatibility.supportsReasoningEffort === true || compatibility.thinkingFormat !== void 0) {
6404
+ return mapCodexEffortToOpenAI(effort, modelId);
6405
+ }
6406
+ return void 0;
6407
+ }
6206
6408
  function getReasoningCapabilities(npm, modelId, metadata) {
6207
6409
  const id = modelId.toLowerCase();
6410
+ const compatibilityCapabilities = compatibilityReasoningCapabilities(metadata);
6411
+ if (compatibilityCapabilities) return compatibilityCapabilities;
6208
6412
  if (isOpenRouterRoute(npm, metadata)) {
6209
6413
  return openRouterReasoningCapabilities(metadata);
6210
6414
  }
@@ -6350,7 +6554,10 @@ function getReasoningCapabilities(npm, modelId, metadata) {
6350
6554
  return EMPTY_REASONING;
6351
6555
  }
6352
6556
  function getPatchReasoningCapabilities(npm, modelId, metadata) {
6353
- if (metadata?.reasoning === false && !hasSupportedParameter(metadata, "reasoning_effort") && !hasSupportedParameter(metadata, "reasoning")) {
6557
+ if (metadata?.compatibility?.supportsReasoningEffort === false) {
6558
+ return compatibilityReasoningCapabilities(metadata) ?? EMPTY_REASONING;
6559
+ }
6560
+ if (metadata?.reasoning === false && !metadata?.compatibility?.reasoningEffortMap && !hasSupportedParameter(metadata, "reasoning_effort") && !hasSupportedParameter(metadata, "reasoning")) {
6354
6561
  return EMPTY_REASONING;
6355
6562
  }
6356
6563
  const capabilities = getReasoningCapabilities(npm, modelId, metadata);
@@ -6369,11 +6576,22 @@ function getPatchReasoningCapabilities(npm, modelId, metadata) {
6369
6576
  }
6370
6577
  function effortProviderOptions(npm, effort, modelId, metadata) {
6371
6578
  if (!effort) return void 0;
6579
+ if (npm === "@ai-sdk/openai-compatible" && modelId && metadata?.compatibility) {
6580
+ const reasoningEffort = compatibilityReasoningEffort(
6581
+ effort,
6582
+ modelId,
6583
+ metadata.compatibility
6584
+ );
6585
+ if (!reasoningEffort) return void 0;
6586
+ const key = metadata.providerId ? toCamelCase(metadata.providerId) : "openaiCompatible";
6587
+ return { [key]: { reasoningEffort } };
6588
+ }
6372
6589
  if (isOpenRouterRoute(npm, metadata)) {
6373
6590
  const caps = openRouterReasoningCapabilities(metadata);
6374
6591
  if (caps.mode !== "controllable") return void 0;
6375
6592
  const allowed = new Set(OPENROUTER_EFFORT_LEVELS);
6376
- const mapped = allowed.has(effort) ? effort : effort === "max" ? "xhigh" : void 0;
6593
+ const candidate = effort;
6594
+ const mapped = allowed.has(candidate) ? candidate : candidate === "max" ? "xhigh" : void 0;
6377
6595
  return mapped ? { openrouter: { reasoning: { effort: mapped, exclude: false } } } : void 0;
6378
6596
  }
6379
6597
  if (npm === "@ai-sdk/openai" || npm === "@ai-sdk/azure") {
@@ -6433,7 +6651,8 @@ function effortProviderOptions(npm, effort, modelId, metadata) {
6433
6651
  }
6434
6652
  if (hasSupportedParameter(metadata, "reasoning")) {
6435
6653
  const allowed = new Set(OPENROUTER_EFFORT_LEVELS);
6436
- const mapped = allowed.has(effort) ? effort : effort === "max" ? "xhigh" : void 0;
6654
+ const candidate = effort;
6655
+ const mapped = allowed.has(candidate) ? candidate : candidate === "max" ? "xhigh" : void 0;
6437
6656
  return mapped ? { openrouter: { reasoning: { effort: mapped, exclude: false } } } : void 0;
6438
6657
  }
6439
6658
  return void 0;
@@ -7243,6 +7462,62 @@ function parseModelList(body, npm) {
7243
7462
  }
7244
7463
  return models;
7245
7464
  }
7465
+ function materializeTemplateModel(template, model, baseUrl) {
7466
+ const npm = model.npm ?? template.npm;
7467
+ const { id, upstreamModelId: normalizedUpstream } = normalizeGoogleModelId(model.id, npm);
7468
+ const family = model.family ?? (id.split(/[-/:]/)[0] ?? id);
7469
+ const freeStatus = model.freeStatus ?? classifyFreeStatus({ model });
7470
+ return {
7471
+ ...model,
7472
+ id,
7473
+ name: normalizeGoogleDisplayName(model.name, id),
7474
+ upstreamModelId: model.upstreamModelId ?? normalizedUpstream,
7475
+ family,
7476
+ brand: model.brand ?? deriveBrand(family),
7477
+ contextWindow: model.contextWindow ?? resolveContextWindow(id),
7478
+ isFree: model.isFree ?? isFreeStatus(freeStatus),
7479
+ freeStatus,
7480
+ modelFormat: model.modelFormat ?? modelFormatForNpm(npm),
7481
+ npm,
7482
+ apiUrl: model.apiUrl ?? baseUrl
7483
+ };
7484
+ }
7485
+ function normalizeTemplateOverlay(template, model) {
7486
+ const npm = model.npm ?? template.npm;
7487
+ const { id } = normalizeGoogleModelId(model.id, npm);
7488
+ const family = model.family;
7489
+ const hasFreeMetadata = model.cost !== void 0 || model.isFree !== void 0 || model.freeStatus !== void 0;
7490
+ const freeStatus = hasFreeMetadata ? model.freeStatus ?? classifyFreeStatus({ model }) : void 0;
7491
+ return {
7492
+ ...model,
7493
+ id,
7494
+ name: normalizeGoogleDisplayName(model.name, id),
7495
+ ...model.upstreamModelId !== void 0 ? { upstreamModelId: normalizeGoogleModelId(model.upstreamModelId, npm).upstreamModelId } : {},
7496
+ ...model.npm !== void 0 ? { npm } : {},
7497
+ ...model.modelFormat !== void 0 ? { modelFormat: model.modelFormat } : model.npm !== void 0 ? { modelFormat: modelFormatForNpm(npm) } : {},
7498
+ ...family !== void 0 ? { family, brand: model.brand ?? deriveBrand(family) } : {},
7499
+ ...freeStatus !== void 0 ? {
7500
+ freeStatus,
7501
+ isFree: model.isFree ?? isFreeStatus(freeStatus)
7502
+ } : {}
7503
+ };
7504
+ }
7505
+ function applyTemplateModelMetadata(template, discovered, _baseUrl) {
7506
+ const curated = new Map(
7507
+ (template.staticModels ?? []).map((model) => normalizeTemplateOverlay(template, model)).map((model) => [model.id, model])
7508
+ );
7509
+ const visible = template.staticModelPolicy === "allowlist" ? discovered.filter((model) => curated.has(model.id)) : discovered;
7510
+ return visible.map((model) => {
7511
+ const overlay = curated.get(model.id);
7512
+ if (!overlay) return model;
7513
+ return {
7514
+ ...model,
7515
+ ...overlay,
7516
+ id: model.id,
7517
+ upstreamModelId: overlay.upstreamModelId ?? model.upstreamModelId
7518
+ };
7519
+ });
7520
+ }
7246
7521
  async function fetchTemplateModels(template, apiKey, baseUrlOverride, extraHeaders) {
7247
7522
  const trimmedOverride = baseUrlOverride?.trim();
7248
7523
  const baseUrl = (trimmedOverride || template.defaultBaseUrl)?.replace(/\/$/, "");
@@ -7254,19 +7529,7 @@ async function fetchTemplateModels(template, apiKey, baseUrlOverride, extraHeade
7254
7529
  };
7255
7530
  }
7256
7531
  if (template.modelSource === "static-seed") {
7257
- const models = (template.staticModels || []).map((sm) => {
7258
- const family = sm.id.split(/[-/:]/)[0] ?? sm.id;
7259
- return {
7260
- id: sm.id,
7261
- name: sm.name,
7262
- upstreamModelId: sm.id,
7263
- family,
7264
- brand: deriveBrand(family),
7265
- contextWindow: resolveContextWindow(sm.id),
7266
- modelFormat: modelFormatForNpm(template.npm),
7267
- npm: template.npm
7268
- };
7269
- });
7532
+ const models = (template.staticModels ?? []).map((model) => materializeTemplateModel(template, model, baseUrl));
7270
7533
  return { models, baseUrl };
7271
7534
  }
7272
7535
  const url = modelsUrl(baseUrl, template);
@@ -7336,7 +7599,7 @@ async function fetchTemplateModels(template, apiKey, baseUrlOverride, extraHeade
7336
7599
  }
7337
7600
  } catch {
7338
7601
  }
7339
- const models = parseModelList(json, template.npm);
7602
+ const models = applyTemplateModelMetadata(template, parseModelList(json, template.npm), baseUrl);
7340
7603
  if (models.length === 0) {
7341
7604
  return {
7342
7605
  models: [],
@@ -7424,11 +7687,11 @@ async function addProviderFromTemplate(template, apiKey, opts) {
7424
7687
  }
7425
7688
  const pricingCache = loadPricingCache();
7426
7689
  const platform = pricingPlatformForProvider(template.id, template.id);
7427
- const pricedModels = enrichModelsWithPricing(
7428
- usableModels.map((m) => ({ ...m, apiUrl: fetched.baseUrl })),
7429
- buildPricingIndex(pricingCache),
7430
- platform
7431
- );
7690
+ const discoveredModels = usableModels.map((m) => ({
7691
+ ...m,
7692
+ apiUrl: m.apiUrl ?? fetched.baseUrl
7693
+ }));
7694
+ const pricedModels = template.preserveModelPricing ? discoveredModels : enrichModelsWithPricing(discoveredModels, buildPricingIndex(pricingCache), platform);
7432
7695
  const account = `provider:${template.id}`;
7433
7696
  const result = await withProviderMutationLock(template.id, async () => {
7434
7697
  const currentState = await withRegistryWriteLock(() => {
@@ -7485,6 +7748,7 @@ async function addProviderFromTemplate(template, apiKey, opts) {
7485
7748
  enabled: true,
7486
7749
  authRef,
7487
7750
  authType: trimmedKey ? template.authType : "none",
7751
+ ...template.preserveModelPricing ? { preserveModelPricing: true } : {},
7488
7752
  ...!trimmedKey && template.anonymousFreeModels ? { subscriptionFilter: "free" } : {},
7489
7753
  api: {
7490
7754
  npm: template.npm,
@@ -8087,7 +8351,7 @@ async function refreshApiListProvider(provider, apiKey) {
8087
8351
  return { models: [], error: fetched2.error ?? "No models returned.", baseUrl: fetched2.baseUrl };
8088
8352
  }
8089
8353
  return {
8090
- models: fetched2.models.map((m) => ({ ...m, apiUrl: fetched2.baseUrl })),
8354
+ models: fetched2.models.map((m) => ({ ...m, apiUrl: m.apiUrl ?? fetched2.baseUrl })),
8091
8355
  baseUrl: fetched2.baseUrl
8092
8356
  };
8093
8357
  }
@@ -8106,7 +8370,7 @@ async function refreshApiListProvider(provider, apiKey) {
8106
8370
  return {
8107
8371
  models: usableModels.map((m) => ({
8108
8372
  ...m,
8109
- apiUrl: fetched.baseUrl
8373
+ apiUrl: m.apiUrl ?? fetched.baseUrl
8110
8374
  })),
8111
8375
  baseUrl: fetched.baseUrl
8112
8376
  };
@@ -8220,7 +8484,7 @@ async function refreshProviderModels(providerId, apiKey, registry) {
8220
8484
  }
8221
8485
  const pricingCache = loadPricingCache();
8222
8486
  const platform = pricingPlatformForProvider(provider.templateId, provider.id);
8223
- const enriched = enrichModelsWithPricing(models, buildPricingIndex(pricingCache), platform);
8487
+ const enriched = provider.preserveModelPricing ? models : enrichModelsWithPricing(models, buildPricingIndex(pricingCache), platform);
8224
8488
  await withRegistryWriteLock(() => {
8225
8489
  const currentRegistry = loadRegistryStrict();
8226
8490
  const currentProvider = currentRegistry.providers.find((candidate) => candidate.id === providerId);
@@ -9462,7 +9726,8 @@ function localModelToRoute(lp, model) {
9462
9726
  reasoning: model.reasoning,
9463
9727
  interleavedReasoningField: model.interleavedReasoningField,
9464
9728
  useResponsesLite: model.useResponsesLite,
9465
- preferWebSockets: model.preferWebSockets
9729
+ preferWebSockets: model.preferWebSockets,
9730
+ compatibility: model.compatibility
9466
9731
  };
9467
9732
  }
9468
9733
  function makeRouteResolver(localProviders) {
@@ -9535,7 +9800,8 @@ function buildHttpProxyRoutes(providers, favorites, modelAliases = void 0, max =
9535
9800
  unavailable.push(favorite);
9536
9801
  continue;
9537
9802
  }
9538
- if (model.modelFormat !== "openai" || !isSdkMigratedNpm(model.npm)) {
9803
+ const supported = model.modelFormat === "anthropic" ? Boolean(model.baseUrl) : isSdkMigratedNpm(model.npm);
9804
+ if (!supported) {
9539
9805
  unsupported.push(favorite);
9540
9806
  continue;
9541
9807
  }
@@ -9708,7 +9974,8 @@ function routeUnavailableMessage(modelId, reason) {
9708
9974
  }
9709
9975
 
9710
9976
  // src/upstream-forward.ts
9711
- import { Readable } from "stream";
9977
+ import { Readable, Transform } from "stream";
9978
+ import { StringDecoder } from "string_decoder";
9712
9979
 
9713
9980
  // src/server/auth.ts
9714
9981
  function sanitizeCredential(value) {
@@ -9789,6 +10056,34 @@ async function fetchWithOAuthRetry(apiKey, request3, refreshToken) {
9789
10056
  response = await request3(refreshed);
9790
10057
  return { response, apiKey: refreshed, refreshed: true };
9791
10058
  }
10059
+ function anthropicSseModelRewrite(override) {
10060
+ const decoder = new StringDecoder("utf8");
10061
+ let tail = "";
10062
+ const rewriteLine = (line) => {
10063
+ if (!line.startsWith("data:") || !line.includes('"message_start"')) return line;
10064
+ try {
10065
+ const parsed = JSON.parse(line.slice(5));
10066
+ if (parsed.type === "message_start" && parsed.message && typeof parsed.message.model === "string") {
10067
+ parsed.message.model = override;
10068
+ return "data: " + JSON.stringify(parsed);
10069
+ }
10070
+ } catch {
10071
+ }
10072
+ return line;
10073
+ };
10074
+ return new Transform({
10075
+ transform(chunk, _encoding, callback) {
10076
+ const lines = (tail + decoder.write(chunk)).split("\n");
10077
+ tail = lines.pop() ?? "";
10078
+ const rewritten = lines.map(rewriteLine);
10079
+ callback(null, rewritten.length ? rewritten.join("\n") + "\n" : "");
10080
+ },
10081
+ flush(callback) {
10082
+ const rest = tail + decoder.end();
10083
+ callback(null, rest ? rewriteLine(rest) : "");
10084
+ }
10085
+ });
10086
+ }
9792
10087
  async function relayAnthropicMessages(res, messagesUrl, body, apiKey, clientWantsStream, options = {}) {
9793
10088
  const doFetch = (key) => fetch(messagesUrl, {
9794
10089
  method: "POST",
@@ -9825,7 +10120,12 @@ async function relayAnthropicMessages(res, messagesUrl, body, apiKey, clientWant
9825
10120
  "Cache-Control": "no-cache",
9826
10121
  "Connection": "keep-alive"
9827
10122
  });
9828
- Readable.fromWeb(upstreamRes.body).on("error", () => res.destroy()).pipe(res);
10123
+ const upstream = Readable.fromWeb(upstreamRes.body).on("error", () => res.destroy());
10124
+ if (options.responseModelOverride) {
10125
+ upstream.pipe(anthropicSseModelRewrite(options.responseModelOverride)).on("error", () => res.destroy()).pipe(res);
10126
+ } else {
10127
+ upstream.pipe(res);
10128
+ }
9829
10129
  return;
9830
10130
  }
9831
10131
  if (!upstreamRes.body) {
@@ -9833,14 +10133,19 @@ async function relayAnthropicMessages(res, messagesUrl, body, apiKey, clientWant
9833
10133
  res.end(JSON.stringify({ type: "error", error: { type: "api_error", message: "Upstream returned empty response body" } }));
9834
10134
  return;
9835
10135
  }
9836
- const text4 = await upstreamRes.text();
10136
+ let text4 = await upstreamRes.text();
10137
+ let parsed;
9837
10138
  try {
9838
- JSON.parse(text4);
10139
+ parsed = JSON.parse(text4);
9839
10140
  } catch {
9840
10141
  res.writeHead(502, { "Content-Type": "application/json" });
9841
10142
  res.end(JSON.stringify({ type: "error", error: { type: "api_error", message: "Upstream response was not valid JSON" } }));
9842
10143
  return;
9843
10144
  }
10145
+ if (options.responseModelOverride && parsed && typeof parsed === "object" && !Array.isArray(parsed) && typeof parsed.model === "string") {
10146
+ parsed.model = options.responseModelOverride;
10147
+ text4 = JSON.stringify(parsed);
10148
+ }
9844
10149
  res.writeHead(200, {
9845
10150
  "Content-Type": "application/json",
9846
10151
  "Content-Length": Buffer.byteLength(text4).toString()
@@ -11102,6 +11407,10 @@ async function startProxyCatalog(routes, defaultAliasId, debug = false, inferenc
11102
11407
  route.apiKey = refreshed;
11103
11408
  },
11104
11409
  signal: clientAbort.signal,
11410
+ // A route selected through a clodex: id or short alias must echo the
11411
+ // exact requested id back, or patched Claude Code misses the alias
11412
+ // context-window key and can skip auto-compaction.
11413
+ responseModelOverride: typeof originalModel === "string" && originalModel !== route.realModelId ? originalModel : void 0,
11105
11414
  onUpstreamError: inferenceLogPath ? (statusCode, errorContent) => writeInferenceResponseErrorLog(inferenceLogPath, {
11106
11415
  modelId: originalModel,
11107
11416
  provider: route.providerId ?? route.aliasId.split(":")[1] ?? "unknown",
@@ -11140,6 +11449,7 @@ async function startProxyCatalog(routes, defaultAliasId, debug = false, inferenc
11140
11449
  supportedParameters: route.supportedParameters,
11141
11450
  reasoning: route.reasoning,
11142
11451
  interleavedReasoningField: route.interleavedReasoningField,
11452
+ compatibility: route.compatibility,
11143
11453
  upstreamModelId: route.realModelId
11144
11454
  }
11145
11455
  });
@@ -11158,6 +11468,7 @@ async function startProxyCatalog(routes, defaultAliasId, debug = false, inferenc
11158
11468
  headers: route.headers,
11159
11469
  useResponsesLite: route.useResponsesLite,
11160
11470
  preferWebSockets: route.preferWebSockets,
11471
+ compatibility: route.compatibility,
11161
11472
  onDebug: (msg) => plog(() => msg),
11162
11473
  onWebSocketDiagnostic: webSocketDiagnosticsLogPath ? (event) => writeWebSocketDiagnosticLog(webSocketDiagnosticsLogPath, event) : void 0
11163
11474
  });
@@ -11367,6 +11678,7 @@ function startProxy(completionsUrl, modelId, debug = false, contextWindow, sdk,
11367
11678
  interleavedReasoningField: sdk?.interleavedReasoningField,
11368
11679
  useResponsesLite: sdk?.useResponsesLite,
11369
11680
  preferWebSockets: sdk?.preferWebSockets,
11681
+ compatibility: sdk?.compatibility,
11370
11682
  headers: sdk?.headers
11371
11683
  }], clientModelId, debug);
11372
11684
  }
@@ -11922,6 +12234,9 @@ async function handleAnthropicMessages(req, res, options, modelCache, plog) {
11922
12234
  onTokenRefreshed: (refreshed) => {
11923
12235
  model.apiKey = refreshed;
11924
12236
  },
12237
+ // Echo the exact requested id when it differs from the upstream id, so
12238
+ // clients that key context windows on the response model still resolve.
12239
+ responseModelOverride: typeof body.model === "string" && body.model !== upstreamModelId(model) ? body.model : void 0,
11925
12240
  onUpstreamError: options.inferenceLogPath ? (statusCode, errorContent) => writeInferenceResponseErrorLog(options.inferenceLogPath, {
11926
12241
  requestId,
11927
12242
  modelId: body.model,
@@ -11972,6 +12287,7 @@ async function handleAnthropicMessages(req, res, options, modelCache, plog) {
11972
12287
  supportedParameters: model.supportedParameters,
11973
12288
  reasoning: model.reasoning,
11974
12289
  interleavedReasoningField: model.interleavedReasoningField,
12290
+ compatibility: model.compatibility,
11975
12291
  upstreamModelId: upstreamModelId(model)
11976
12292
  },
11977
12293
  maxTools: npmMaxTools
@@ -12252,6 +12568,7 @@ async function getOrInitLanguageModel(modelCache, model, npm, baseURL, apiKey, w
12252
12568
  headers: model.headers,
12253
12569
  useResponsesLite: model.useResponsesLite,
12254
12570
  preferWebSockets: model.preferWebSockets,
12571
+ compatibility: model.compatibility,
12255
12572
  onWebSocketDiagnostic: webSocketDiagnosticsLogPath ? (event) => writeWebSocketDiagnosticLog(webSocketDiagnosticsLogPath, event) : void 0
12256
12573
  });
12257
12574
  cached = { apiKey, languageModel };
@@ -13595,7 +13912,8 @@ function enrichServerModelReasoning(model) {
13595
13912
  apiBaseUrl: model.apiBaseUrl,
13596
13913
  supportedParameters: model.supportedParameters,
13597
13914
  reasoning: model.reasoning,
13598
- interleavedReasoningField: model.interleavedReasoningField
13915
+ interleavedReasoningField: model.interleavedReasoningField,
13916
+ compatibility: model.compatibility
13599
13917
  });
13600
13918
  if (!caps.defaultLevel) return model;
13601
13919
  return { ...model, defaultEffort: caps.defaultLevel };
@@ -15091,6 +15409,7 @@ function buildDesiredPatchConfig() {
15091
15409
  supportedParameters: model.supportedParameters,
15092
15410
  reasoning: model.reasoning ?? modelsDev?.reasoning,
15093
15411
  interleavedReasoningField: model.interleavedReasoningField ?? modelsDev?.interleaved?.field,
15412
+ compatibility: model.compatibility,
15094
15413
  upstreamModelId: upstreamModelId2
15095
15414
  });
15096
15415
  meta.set(`${provider.id}:${model.id}`, {
@@ -16730,6 +17049,7 @@ Error: ${launchPlan.error}
16730
17049
  interleavedReasoningField: selectedModel.interleavedReasoningField,
16731
17050
  useResponsesLite: selectedModel.useResponsesLite,
16732
17051
  preferWebSockets: selectedModel.preferWebSockets,
17052
+ compatibility: selectedModel.compatibility,
16733
17053
  headers: activeProvider.headers
16734
17054
  },
16735
17055
  launchApiKey ?? ""