@bman654/clodex 2.16.0 → 2.18.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
@@ -74,7 +74,7 @@ import {
74
74
  withRegistryWriteLockSync,
75
75
  writeParentNoticeLines,
76
76
  writeParentNoticeLinesSync
77
- } from "./chunk-5TG736TX.js";
77
+ } from "./chunk-YGEPQOPA.js";
78
78
 
79
79
  // src/cli.ts
80
80
  import pc13 from "picocolors";
@@ -249,7 +249,7 @@ function printFavoritesOnlyPanel() {
249
249
 
250
250
  // src/cli.ts
251
251
  import * as p13 from "@clack/prompts";
252
- import { realpathSync as realpathSync3 } from "fs";
252
+ import { realpathSync as realpathSync4 } from "fs";
253
253
  import { fileURLToPath as fileURLToPath2 } from "url";
254
254
 
255
255
  // src/launch.ts
@@ -346,7 +346,7 @@ import { join } from "path";
346
346
  // package.json
347
347
  var package_default = {
348
348
  name: "@bman654/clodex",
349
- version: "2.16.0",
349
+ version: "2.18.0",
350
350
  publishConfig: {
351
351
  access: "public"
352
352
  },
@@ -434,7 +434,7 @@ var package_default = {
434
434
 
435
435
  // src/constants.ts
436
436
  var CODEX_RESPONSES_LITE_WS_URL = "wss://chatgpt.com/backend-api/codex/responses";
437
- var CODEX_RESPONSES_LITE_VERSION = "0.153.3";
437
+ var CODEX_RESPONSES_LITE_VERSION = "0.156.0";
438
438
  var CODEX_RESPONSES_WEBSOCKETS_BETA = "responses_websockets=2026-02-06";
439
439
  var TEST_TIMEOUT_MS = 1e4;
440
440
  var CONFLICTING_ENV_VARS = [
@@ -631,6 +631,7 @@ var HEURISTIC_RULES = [
631
631
  [/claude/i, 2e5],
632
632
  [/deepseek-v4|deepseek-r1|deepseek-reasoner/i, 1e6],
633
633
  [/deepseek/i, 64e3],
634
+ [/gpt-6-(?:astra|sol|luna)/i, 105e4],
634
635
  [/gpt-5|gpt-4\.1|o3-|o4-/i, 1e6],
635
636
  [/gpt-4o|gpt-4-turbo|gpt-4/i, 128e3],
636
637
  [/gpt-oss/i, 131072],
@@ -3185,7 +3186,7 @@ function pricingBoundaryWarning(modelLabel, limits, resolved) {
3185
3186
  const boundary = positiveInteger(limits.pricingBoundary);
3186
3187
  if (boundary === void 0 || !resolved.crossesPricingBoundary) return null;
3187
3188
  const note = limits.pricingBoundaryNote?.trim();
3188
- return `${modelLabel}: a ${withThousands(resolved.effective)}-token window can grow past the ${withThousands(boundary)}-token pricing boundary.` + (note ? ` ${note}` : "") + " Use the standard stop to stay under it.";
3189
+ return `${modelLabel}: a ${withThousands(resolved.effective)}-token window can grow past the ${withThousands(boundary)}-token pricing boundary.` + (note ? ` ${note}` : "") + " Choose a smaller context stop to reduce the chance of higher-rate requests.";
3189
3190
  }
3190
3191
  function contextClampNotice(modelLabel, resolved) {
3191
3192
  if (resolved.clampedFrom === void 0) return null;
@@ -3866,7 +3867,7 @@ import {
3866
3867
  existsSync as existsSync4,
3867
3868
  mkdtempSync,
3868
3869
  mkdirSync as mkdirSync5,
3869
- readFileSync as readFileSync9,
3870
+ readFileSync as readFileSync10,
3870
3871
  renameSync as renameSync2,
3871
3872
  rmSync,
3872
3873
  statSync as statSync7,
@@ -3874,10 +3875,10 @@ import {
3874
3875
  writeFileSync as writeFileSync5,
3875
3876
  openSync as openSync8,
3876
3877
  closeSync as closeSync8,
3877
- realpathSync as realpathSync2
3878
+ realpathSync as realpathSync3
3878
3879
  } from "fs";
3879
- import { homedir as homedir2 } from "os";
3880
- import { basename, dirname as dirname5, join as join7 } from "path";
3880
+ import { homedir as homedir3 } from "os";
3881
+ import { basename, dirname as dirname5, join as join8 } from "path";
3881
3882
  import pc3 from "picocolors";
3882
3883
  import * as p2 from "@clack/prompts";
3883
3884
 
@@ -5978,30 +5979,94 @@ function deriveBrand(family) {
5978
5979
  return "Other";
5979
5980
  }
5980
5981
 
5982
+ // src/registry/server-text.ts
5983
+ function isControlCode(code) {
5984
+ return code <= 31 || code >= 127 && code <= 159;
5985
+ }
5986
+ function hasControlChars(value) {
5987
+ for (let i = 0; i < value.length; i++) {
5988
+ if (isControlCode(value.charCodeAt(i))) return true;
5989
+ }
5990
+ return false;
5991
+ }
5992
+ function printableServerText(value) {
5993
+ let out = "";
5994
+ for (const ch of value) out += isControlCode(ch.charCodeAt(0)) ? " " : ch;
5995
+ return out.trim();
5996
+ }
5997
+
5998
+ // src/codex-client-version.ts
5999
+ var VERSION2 = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*))*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$(?![\s\S])/;
6000
+ function readCodexClientVersion(value) {
6001
+ return typeof value === "string" && VERSION2.test(value) ? value : void 0;
6002
+ }
6003
+ function compareCodexClientVersions(a, b) {
6004
+ const left = VERSION2.exec(a);
6005
+ const right = VERSION2.exec(b);
6006
+ if (!left || !right) return void 0;
6007
+ for (let i = 1; i <= 3; i++) {
6008
+ const x = BigInt(left[i]);
6009
+ const y = BigInt(right[i]);
6010
+ if (x !== y) return x < y ? -1 : 1;
6011
+ }
6012
+ const preA = left[4];
6013
+ const preB = right[4];
6014
+ if (preA === preB) return 0;
6015
+ if (preA === void 0) return 1;
6016
+ if (preB === void 0) return -1;
6017
+ const partsA = preA.split(".");
6018
+ const partsB = preB.split(".");
6019
+ for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
6020
+ const x = partsA[i];
6021
+ const y = partsB[i];
6022
+ if (x === void 0) return -1;
6023
+ if (y === void 0) return 1;
6024
+ if (x === y) continue;
6025
+ const numericX = /^\d+$/.test(x);
6026
+ const numericY = /^\d+$/.test(y);
6027
+ if (numericX && numericY) return BigInt(x) < BigInt(y) ? -1 : 1;
6028
+ if (numericX !== numericY) return numericX ? -1 : 1;
6029
+ return x < y ? -1 : 1;
6030
+ }
6031
+ return 0;
6032
+ }
6033
+ function requiresNewerCodexClient(model) {
6034
+ if (!model.useResponsesLite) return false;
6035
+ const minimum = readCodexClientVersion(model.minimalClientVersion);
6036
+ return minimum !== void 0 && compareCodexClientVersions(minimum, CODEX_RESPONSES_LITE_VERSION) === 1;
6037
+ }
6038
+ function codexClientVersionWarning(models) {
6039
+ const unavailable = models.filter(requiresNewerCodexClient);
6040
+ if (unavailable.length === 0) return void 0;
6041
+ const details = unavailable.map((model) => `${printableServerText(model.id)} (requires ${model.minimalClientVersion})`).join(", ");
6042
+ return `Hidden ChatGPT-plan models: ${details}. clodex sends Codex client version ${CODEX_RESPONSES_LITE_VERSION} for Responses-Lite; their catalog minimum exceeds this version. Update clodex to a release supporting their required version to use them.`;
6043
+ }
6044
+
5981
6045
  // src/data/openai-oauth-models.ts
5982
- var GPT_5_6_PRICING_BOUNDARY = 272e3;
5983
- var GPT_5_6_PRICING_NOTE = "Above it, OpenAI prices the full request at 2x input and 1.5x output.";
6046
+ var GPT_HIGH_CONTEXT_PRICING_BOUNDARY = 272e3;
6047
+ var GPT_HIGH_CONTEXT_PRICING_NOTE = "Above it, OpenAI prices the full request at 2x input and 1.5x output.";
5984
6048
  var CHATGPT_CODEX_UNSUPPORTED_MODELS = /* @__PURE__ */ new Set([
5985
6049
  "gpt-5.5-fast"
5986
6050
  // confirmed: rejected by chatgpt.com/backend-api/codex
5987
6051
  ]);
5988
6052
  var OPENAI_OAUTH_MODEL_SEEDS = [
5989
- // GPT-6 family. The window and ceiling are what the live Codex catalog returned on
5990
- // 2026-09-04 and are deliberately NOT the published API numbers: the model card
5991
- // lists a 1,050,000 context window, but the Codex client is served a smaller one,
5992
- // and this path is Codex-only. Output limit and the pricing band come from the
5993
- // card (https://developers.openai.com/api/docs/models/gpt-6-astra), which states
5994
- // "Prompts with more than 272K input tokens are priced at 2x input and cache rates
5995
- // and 1.5x output for the full request" — the same boundary the GPT-5.6 family has.
5996
- { id: "gpt-6-astra", name: "GPT-6 Astra", contextWindow: 272e3, maxContextWindow: 872e3, maxOutputTokens: 128e3, reasoning: true, useResponsesLite: true, preferWebSockets: true },
6053
+ // GPT-6 family. The window, ceiling and Responses-Lite flags are what the live
6054
+ // Codex catalog returned (Astra on 2026-09-04, Sol and Luna on 2026-09-22) and are
6055
+ // deliberately NOT the published API numbers: the model cards list a 1,050,000
6056
+ // context window, but the Codex client is served a smaller one, and this path is
6057
+ // Codex-only. All three cards publish a 128,000 output limit and the same 272K
6058
+ // pricing boundary as the GPT-5.6 family.
6059
+ { id: "gpt-6-astra", name: "GPT-6 Astra", contextWindow: 272e3, maxContextWindow: 872e3, maxOutputTokens: 128e3, reasoning: true, useResponsesLite: true, preferWebSockets: true, minimalClientVersion: "0.153.0" },
6060
+ { id: "gpt-6-sol", name: "GPT-6 Sol", contextWindow: 272e3, maxContextWindow: 872e3, maxOutputTokens: 128e3, reasoning: true, useResponsesLite: true, preferWebSockets: true, minimalClientVersion: "0.155.0" },
6061
+ { id: "gpt-6-luna", name: "GPT-6 Luna", contextWindow: 272e3, maxContextWindow: 872e3, maxOutputTokens: 128e3, reasoning: true, useResponsesLite: true, preferWebSockets: true, minimalClientVersion: "0.155.0" },
5997
6062
  // "An alias for our flagship general-purpose models, with safeguards calibrated
5998
6063
  // for defensive cybersecurity work" — access is gated on a separate opt-in
5999
6064
  // program, so most installs will never see this id in their catalog.
6000
- { id: "gpt-daybreak-blue-latest", name: "GPT Daybreak Blue", contextWindow: 272e3, maxContextWindow: 872e3, maxOutputTokens: 128e3, reasoning: true, useResponsesLite: true, preferWebSockets: true },
6065
+ { id: "gpt-daybreak-blue-latest", name: "GPT Daybreak Blue", contextWindow: 272e3, maxContextWindow: 872e3, maxOutputTokens: 128e3, reasoning: true, useResponsesLite: true, preferWebSockets: true, minimalClientVersion: "0.144.0" },
6001
6066
  // GPT-5.6 family (Sol / Terra / Luna)
6002
- { id: "gpt-5.6-sol", name: "GPT-5.6 Sol", contextWindow: 272e3, maxContextWindow: 872e3, maxOutputTokens: 128e3, reasoning: true },
6003
- { id: "gpt-5.6-terra", name: "GPT-5.6 Terra", contextWindow: 272e3, maxContextWindow: 872e3, maxOutputTokens: 128e3, reasoning: true },
6004
- { id: "gpt-5.6-luna", name: "GPT-5.6 Luna", contextWindow: 272e3, maxContextWindow: 872e3, maxOutputTokens: 128e3, reasoning: true, useResponsesLite: true, preferWebSockets: true },
6067
+ { id: "gpt-5.6-sol", name: "GPT-5.6 Sol", contextWindow: 272e3, maxContextWindow: 872e3, maxOutputTokens: 128e3, reasoning: true, minimalClientVersion: "0.144.0" },
6068
+ { id: "gpt-5.6-terra", name: "GPT-5.6 Terra", contextWindow: 272e3, maxContextWindow: 872e3, maxOutputTokens: 128e3, reasoning: true, minimalClientVersion: "0.144.0" },
6069
+ { id: "gpt-5.6-luna", name: "GPT-5.6 Luna", contextWindow: 272e3, maxContextWindow: 872e3, maxOutputTokens: 128e3, reasoning: true, useResponsesLite: true, preferWebSockets: true, minimalClientVersion: "0.144.0" },
6005
6070
  // GPT-5.5 family (Pro)
6006
6071
  { id: "gpt-5.5", name: "GPT-5.5", contextWindow: 272e3, maxOutputTokens: 128e3, reasoning: true },
6007
6072
  // GPT-5.4 family
@@ -6028,8 +6093,8 @@ function hasPricingBoundary(id) {
6028
6093
  function openAiPricingMetadata(id) {
6029
6094
  if (!hasPricingBoundary(id)) return {};
6030
6095
  return {
6031
- pricingBoundary: GPT_5_6_PRICING_BOUNDARY,
6032
- pricingBoundaryNote: GPT_5_6_PRICING_NOTE
6096
+ pricingBoundary: GPT_HIGH_CONTEXT_PRICING_BOUNDARY,
6097
+ pricingBoundaryNote: GPT_HIGH_CONTEXT_PRICING_NOTE
6033
6098
  };
6034
6099
  }
6035
6100
  var LEGACY_IMPOSED_CONTEXT_PERCENT = 95;
@@ -6043,12 +6108,16 @@ function applyOAuthSeedContextMetadata(models) {
6043
6108
  const pricing = openAiPricingMetadata(model.id);
6044
6109
  return {
6045
6110
  ...model,
6111
+ contextWindow: model.contextWindow ?? seed?.contextWindow,
6046
6112
  maxContextWindow: model.maxContextWindow ?? seed?.maxContextWindow,
6047
6113
  effectiveContextPercent: migratedEffectiveContextPercent(model.effectiveContextPercent),
6048
6114
  pricingBoundary: model.pricingBoundary ?? seed?.pricingBoundary ?? pricing.pricingBoundary,
6049
6115
  pricingBoundaryNote: model.pricingBoundaryNote ?? seed?.pricingBoundaryNote ?? pricing.pricingBoundaryNote,
6050
6116
  maxOutputTokens: model.maxOutputTokens ?? seed?.maxOutputTokens,
6051
- reasoning: seed?.reasoning ?? model.reasoning
6117
+ reasoning: seed?.reasoning ?? model.reasoning,
6118
+ useResponsesLite: model.useResponsesLite ?? seed?.useResponsesLite,
6119
+ preferWebSockets: model.preferWebSockets ?? seed?.preferWebSockets,
6120
+ minimalClientVersion: model.minimalClientVersion ?? seed?.minimalClientVersion
6052
6121
  };
6053
6122
  });
6054
6123
  }
@@ -6072,7 +6141,8 @@ function buildOpenAiOAuthModels() {
6072
6141
  npm: "@ai-sdk/openai",
6073
6142
  reasoning: seed.reasoning,
6074
6143
  useResponsesLite: seed.useResponsesLite,
6075
- preferWebSockets: seed.preferWebSockets
6144
+ preferWebSockets: seed.preferWebSockets,
6145
+ minimalClientVersion: seed.minimalClientVersion
6076
6146
  };
6077
6147
  });
6078
6148
  }
@@ -6102,22 +6172,6 @@ function normalizeGoogleDisplayName(rawName, bareId) {
6102
6172
  return stripGoogleModelPrefix(trimmed);
6103
6173
  }
6104
6174
 
6105
- // src/registry/server-text.ts
6106
- function isControlCode(code) {
6107
- return code <= 31 || code >= 127 && code <= 159;
6108
- }
6109
- function hasControlChars(value) {
6110
- for (let i = 0; i < value.length; i++) {
6111
- if (isControlCode(value.charCodeAt(i))) return true;
6112
- }
6113
- return false;
6114
- }
6115
- function printableServerText(value) {
6116
- let out = "";
6117
- for (const ch of value) out += isControlCode(ch.charCodeAt(0)) ? " " : ch;
6118
- return out.trim();
6119
- }
6120
-
6121
6175
  // src/trace-log.ts
6122
6176
  import {
6123
6177
  chmodSync as chmodSync3,
@@ -6588,6 +6642,7 @@ function parseModelList(body, npm, allowBareArray) {
6588
6642
  family,
6589
6643
  brand: deriveBrand(family),
6590
6644
  contextWindow,
6645
+ ...npm === "@ai-sdk/openai" ? openAiPricingMetadata(id) : {},
6591
6646
  cost,
6592
6647
  isFree: isFreeStatus(freeStatus),
6593
6648
  freeStatus,
@@ -6605,6 +6660,7 @@ function materializeTemplateModel(template, model, baseUrl) {
6605
6660
  const { id, upstreamModelId: normalizedUpstream } = normalizeGoogleModelId(model.id, npm);
6606
6661
  const family = model.family ?? (id.split(/[-/:]/)[0] ?? id);
6607
6662
  const freeStatus = model.freeStatus ?? classifyFreeStatus({ model });
6663
+ const pricing = npm === "@ai-sdk/openai" ? openAiPricingMetadata(model.upstreamModelId ?? id) : {};
6608
6664
  return {
6609
6665
  ...model,
6610
6666
  id,
@@ -6613,6 +6669,8 @@ function materializeTemplateModel(template, model, baseUrl) {
6613
6669
  family,
6614
6670
  brand: model.brand ?? deriveBrand(family),
6615
6671
  contextWindow: model.contextWindow ?? lookupKnownContextWindow(id),
6672
+ pricingBoundary: model.pricingBoundary ?? pricing.pricingBoundary,
6673
+ pricingBoundaryNote: model.pricingBoundaryNote ?? pricing.pricingBoundaryNote,
6616
6674
  isFree: model.isFree ?? isFreeStatus(freeStatus),
6617
6675
  freeStatus,
6618
6676
  modelFormat: model.modelFormat ?? modelFormatForNpm(npm),
@@ -6805,8 +6863,21 @@ function resolveEndpoint(npm, apiUrl) {
6805
6863
  }
6806
6864
  function projectProviderCachedModels(provider) {
6807
6865
  const cached = provider.modelsCache?.models ?? [];
6808
- if (isChatGptOAuthProvider(provider)) return applyOAuthSeedContextMetadata(cached);
6809
- if (!isRetainedOpenCodeGoProvider(provider)) return cached;
6866
+ if (isChatGptOAuthProvider(provider)) {
6867
+ return applyOAuthSeedContextMetadata(cached).filter((model) => !requiresNewerCodexClient(model));
6868
+ }
6869
+ if (!isRetainedOpenCodeGoProvider(provider)) {
6870
+ if (!cached.some((model) => (model.npm ?? provider.api.npm) === "@ai-sdk/openai")) return cached;
6871
+ return cached.map((model) => {
6872
+ if ((model.npm ?? provider.api.npm) !== "@ai-sdk/openai") return model;
6873
+ const pricing = openAiPricingMetadata(model.upstreamModelId ?? model.id);
6874
+ return {
6875
+ ...model,
6876
+ pricingBoundary: model.pricingBoundary ?? pricing.pricingBoundary,
6877
+ pricingBoundaryNote: model.pricingBoundaryNote ?? pricing.pricingBoundaryNote
6878
+ };
6879
+ });
6880
+ }
6810
6881
  const template = retainedOpenCodeGoTemplate();
6811
6882
  return template ? applyTemplateModelMetadata(template, cached) : [];
6812
6883
  }
@@ -6960,8 +7031,162 @@ function materializeRegistry(registry, resolveCredential, opts) {
6960
7031
  return result;
6961
7032
  }
6962
7033
 
7034
+ // src/editor-extension-version.ts
7035
+ import { execFileSync, execSync } from "child_process";
7036
+ import { readdirSync, readFileSync as readFileSync7, realpathSync } from "fs";
7037
+ import { homedir as homedir2 } from "os";
7038
+ import { join as join6 } from "path";
7039
+ var CLAUDE_EXTENSION_ID = "anthropic.claude-code";
7040
+ var EDITOR_EXTENSION_ROOTS = [
7041
+ { editor: "VS Code", dir: ".vscode" },
7042
+ { editor: "VS Code Insiders", dir: ".vscode-insiders" },
7043
+ { editor: "VS Code (remote server)", dir: ".vscode-server" },
7044
+ { editor: "VSCodium", dir: ".vscode-oss" },
7045
+ { editor: "Cursor", dir: ".cursor" },
7046
+ { editor: "Windsurf", dir: ".windsurf" }
7047
+ ];
7048
+ var CHECKED_EDITORS_DESCRIPTION = EDITOR_EXTENSION_ROOTS.map((root) => root.editor).join(", ");
7049
+ function releaseKey(version) {
7050
+ const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(version.trim());
7051
+ return match ? match.slice(1).map(Number) : null;
7052
+ }
7053
+ function compareVersionKeys(a, b) {
7054
+ for (let i = 0; i < Math.max(a.length, b.length); i += 1) {
7055
+ const diff = (a[i] ?? 0) - (b[i] ?? 0);
7056
+ if (diff !== 0) return diff;
7057
+ }
7058
+ return 0;
7059
+ }
7060
+ function newestVersion(versions) {
7061
+ let best = null;
7062
+ for (const version of versions) {
7063
+ const key = releaseKey(version);
7064
+ if (key && (!best || compareVersionKeys(key, best.key) > 0)) best = { version, key };
7065
+ }
7066
+ return best?.version ?? null;
7067
+ }
7068
+ var EXTENSION_DIR_PATTERN = /^anthropic\.claude-code-(\d+\.\d+\.\d+)(?:-[a-z0-9]+)*$/i;
7069
+ function readJson(path) {
7070
+ try {
7071
+ return JSON.parse(readFileSync7(path, "utf8"));
7072
+ } catch {
7073
+ return void 0;
7074
+ }
7075
+ }
7076
+ function entryDirectoryName(entry) {
7077
+ if (typeof entry.relativeLocation === "string") return entry.relativeLocation;
7078
+ const location = entry.location;
7079
+ const raw = typeof location === "string" ? location : location && typeof location === "object" ? location.fsPath ?? location.path : void 0;
7080
+ if (typeof raw !== "string") return null;
7081
+ const parts = raw.split(/[\\/]/).filter(Boolean);
7082
+ return parts.at(-1) ?? null;
7083
+ }
7084
+ function readClaudeExtensionVersion(extensionsDir) {
7085
+ let names;
7086
+ try {
7087
+ names = readdirSync(extensionsDir);
7088
+ } catch {
7089
+ return null;
7090
+ }
7091
+ const present = new Set(names);
7092
+ const listed = readJson(join6(extensionsDir, "extensions.json"));
7093
+ if (Array.isArray(listed)) {
7094
+ const versions2 = [];
7095
+ for (const item of listed) {
7096
+ if (!item || typeof item !== "object") continue;
7097
+ const entry = item;
7098
+ const identifier = entry.identifier;
7099
+ if (typeof identifier?.id !== "string" || identifier.id.toLowerCase() !== CLAUDE_EXTENSION_ID) continue;
7100
+ if (typeof entry.version !== "string") continue;
7101
+ const dirName = entryDirectoryName(entry);
7102
+ if (dirName !== null && !present.has(dirName)) continue;
7103
+ versions2.push(entry.version);
7104
+ }
7105
+ return newestVersion(versions2);
7106
+ }
7107
+ const obsoleteRaw = readJson(join6(extensionsDir, ".obsolete"));
7108
+ const obsolete = obsoleteRaw && typeof obsoleteRaw === "object" && !Array.isArray(obsoleteRaw) ? new Set(Object.entries(obsoleteRaw).filter(([, flagged]) => flagged).map(([name]) => name)) : /* @__PURE__ */ new Set();
7109
+ const versions = [];
7110
+ for (const name of names) {
7111
+ if (obsolete.has(name)) continue;
7112
+ const match = EXTENSION_DIR_PATTERN.exec(name);
7113
+ if (match) versions.push(match[1]);
7114
+ }
7115
+ return newestVersion(versions);
7116
+ }
7117
+ function findInstalledClaudeExtensions(home = homedir2()) {
7118
+ const found = [];
7119
+ for (const root of EDITOR_EXTENSION_ROOTS) {
7120
+ const extensionsDir = join6(home, root.dir, "extensions");
7121
+ const version = readClaudeExtensionVersion(extensionsDir);
7122
+ if (version) found.push({ editor: root.editor, extensionsDir, version });
7123
+ }
7124
+ return found;
7125
+ }
7126
+ var NPM_ROOT_TIMEOUT_MS = 1e4;
7127
+ function readNpmGlobalRoot(env = process.env) {
7128
+ try {
7129
+ const options = {
7130
+ env,
7131
+ encoding: "utf8",
7132
+ stdio: ["ignore", "pipe", "ignore"],
7133
+ timeout: NPM_ROOT_TIMEOUT_MS
7134
+ };
7135
+ const output = process.platform === "win32" ? execSync("npm root -g", options) : execFileSync("npm", ["root", "-g"], options);
7136
+ const root = output.trim().split(/\r?\n/).at(-1)?.trim();
7137
+ return root || null;
7138
+ } catch {
7139
+ return null;
7140
+ }
7141
+ }
7142
+ function comparablePath(path) {
7143
+ let real = path;
7144
+ try {
7145
+ real = realpathSync(path);
7146
+ } catch {
7147
+ }
7148
+ const normalized = real.replace(/\\/g, "/").replace(/\/+$/, "");
7149
+ return /^[a-z]:\//i.test(normalized) ? normalized.toLowerCase() : normalized;
7150
+ }
7151
+ function claudeInstallCommand(binaryPath, version, npmGlobalRoot) {
7152
+ const path = comparablePath(binaryPath);
7153
+ if (/\/\.local\/share\/claude\/versions\/[^/]+$/.test(path)) return `claude install ${version}`;
7154
+ if (/\/node_modules\/@anthropic-ai\/claude-code/.test(path)) {
7155
+ const root = npmGlobalRoot();
7156
+ const packagePrefix = root === null ? null : `${comparablePath(root)}/@anthropic-ai/claude-code`;
7157
+ const underGlobalPackage = packagePrefix !== null && path.startsWith(packagePrefix) && ["/", "-"].includes(path.charAt(packagePrefix.length));
7158
+ if (underGlobalPackage) return `npm install -g @anthropic-ai/claude-code@${version}`;
7159
+ }
7160
+ return null;
7161
+ }
7162
+ function describeExtensionDrift(extension, patched, npmGlobalRoot) {
7163
+ const extensionKey = releaseKey(extension.version);
7164
+ const patchedKey = releaseKey(patched.version);
7165
+ if (!extensionKey || !patchedKey) return null;
7166
+ const order = compareVersionKeys(extensionKey, patchedKey);
7167
+ if (order === 0) return null;
7168
+ const { editor, version } = extension;
7169
+ const install = claudeInstallCommand(patched.binaryPath, version, npmGlobalRoot);
7170
+ const lines = [
7171
+ `${editor}'s Claude Code extension is ${version}, but the Claude Code clodex patched is ${patched.version} (${patched.binaryPath}).`,
7172
+ `When ${editor} launches Claude Code through clodex-claude (claudeCode.claudeProcessWrapper), its chats run the extension's own bundled ${version} binary and clodex models will not appear in its model picker.`,
7173
+ ...install ? ["To fix it, run:", ` ${install}`, " clodex patch"] : [`To fix it, update the Claude Code at ${patched.binaryPath} to ${version} with the tool that installed it, then run:`, " clodex patch"]
7174
+ ];
7175
+ if (order < 0) lines.push(`Or update the extension in ${editor} to ${patched.version}.`);
7176
+ return lines.join("\n");
7177
+ }
7178
+ function claudeExtensionDriftWarnings(patched, home = homedir2(), npmGlobalRoot = () => readNpmGlobalRoot()) {
7179
+ let root = null;
7180
+ const cachedRoot = () => (root ??= { value: npmGlobalRoot() }).value;
7181
+ try {
7182
+ return findInstalledClaudeExtensions(home).map((extension) => describeExtensionDrift(extension, patched, cachedRoot)).filter((warning) => warning !== null);
7183
+ } catch {
7184
+ return [];
7185
+ }
7186
+ }
7187
+
6963
7188
  // src/npm-shim.ts
6964
- import { readFileSync as readFileSync7, realpathSync, statSync as statSync4 } from "fs";
7189
+ import { readFileSync as readFileSync8, realpathSync as realpathSync2, statSync as statSync4 } from "fs";
6965
7190
  import { dirname as dirname3, resolve as resolve2 } from "path";
6966
7191
  var MAX_LAUNCHER_BYTES = 64 * 1024;
6967
7192
  var MAX_LAUNCHER_HOPS = 4;
@@ -6984,7 +7209,7 @@ function launcherKind(shimPath) {
6984
7209
  function readLauncherText(path) {
6985
7210
  try {
6986
7211
  if (statSync4(path).size > MAX_LAUNCHER_BYTES) return null;
6987
- return readFileSync7(path, "utf8");
7212
+ return readFileSync8(path, "utf8");
6988
7213
  } catch {
6989
7214
  return null;
6990
7215
  }
@@ -7041,7 +7266,7 @@ function resolveThroughNpmShims(startPath) {
7041
7266
  }
7042
7267
  let hopped;
7043
7268
  try {
7044
- hopped = realpathSync(reading.target);
7269
+ hopped = realpathSync2(reading.target);
7045
7270
  } catch {
7046
7271
  return {
7047
7272
  ok: false,
@@ -7066,13 +7291,13 @@ import {
7066
7291
  closeSync as closeSync2,
7067
7292
  fstatSync,
7068
7293
  openSync as openSync2,
7069
- readFileSync as readFileSync8,
7294
+ readFileSync as readFileSync9,
7070
7295
  readSync,
7071
7296
  statSync as statSync5
7072
7297
  } from "fs";
7073
7298
  import { createRequire } from "module";
7074
7299
  import { arch } from "os";
7075
- import { dirname as dirname4, join as join6 } from "path";
7300
+ import { dirname as dirname4, join as join7 } from "path";
7076
7301
  var MAX_PLACEHOLDER_BYTES = 64 * 1024;
7077
7302
  var CLAUDE_PACKAGE = "@anthropic-ai/claude-code";
7078
7303
  var MISSING_BINARY_MARKER = "claude native binary not installed";
@@ -7099,11 +7324,11 @@ function getPlatformKey() {
7099
7324
  }
7100
7325
  function inspectNativePackage(binaryPath) {
7101
7326
  const packageRoot2 = dirname4(dirname4(binaryPath));
7102
- const packageJsonPath = join6(packageRoot2, "package.json");
7103
- const installScriptPath = join6(packageRoot2, "install.cjs");
7327
+ const packageJsonPath = join7(packageRoot2, "package.json");
7328
+ const installScriptPath = join7(packageRoot2, "install.cjs");
7104
7329
  let manifest;
7105
7330
  try {
7106
- manifest = JSON.parse(readFileSync8(packageJsonPath, "utf8"));
7331
+ manifest = JSON.parse(readFileSync9(packageJsonPath, "utf8"));
7107
7332
  if (manifest.name !== CLAUDE_PACKAGE || !statSync5(installScriptPath).isFile()) {
7108
7333
  return { nativePackageState: "unknown", installScriptPath: null };
7109
7334
  }
@@ -7157,7 +7382,7 @@ function inspectClaudeNativeBinaryPlaceholder(path) {
7157
7382
 
7158
7383
  // src/bun-entry-module.ts
7159
7384
  import { closeSync as closeSync3, openSync as openSync3, readSync as readSync2, statSync as statSync6, writeSync } from "fs";
7160
- import { execFileSync } from "child_process";
7385
+ import { execFileSync as execFileSync2 } from "child_process";
7161
7386
  var BUN_TRAILER = Buffer.from("\n---- Bun! ----\n");
7162
7387
  var BUN_OFFSETS_BYTES = 32;
7163
7388
  var TAIL_SCAN_BYTES = 16 * 1024 * 1024;
@@ -7413,7 +7638,7 @@ function resignMachOBinary(path) {
7413
7638
  closeSync3(fd);
7414
7639
  }
7415
7640
  if (machO && process.platform === "darwin") {
7416
- execFileSync("codesign", ["-s", "-", "-f", path], { stdio: "ignore" });
7641
+ execFileSync2("codesign", ["-s", "-", "-f", path], { stdio: "ignore" });
7417
7642
  }
7418
7643
  }
7419
7644
 
@@ -11080,7 +11305,7 @@ function supportsExtendedCodexEffort(modelId) {
11080
11305
  return /^gpt-daybreak(?:-|$)/i.test(modelId);
11081
11306
  }
11082
11307
  function supportsNoneEffort(modelId) {
11083
- return /^gpt-5\.6(?:-|$)/i.test(modelId) || /^gpt-daybreak-blue(?:-|$)/i.test(modelId);
11308
+ return /^gpt-5\.6(?:-|$)/i.test(modelId) || /^gpt-6-(?:sol|luna)(?:-|$)/i.test(modelId) || /^gpt-daybreak-blue(?:-|$)/i.test(modelId);
11084
11309
  }
11085
11310
  function isReasoningSummaryUnsupportedModel(modelId) {
11086
11311
  return /codex-spark(?:-|$)/i.test(modelId);
@@ -11703,7 +11928,7 @@ function withThousands2(value) {
11703
11928
  return value.toLocaleString("en-US");
11704
11929
  }
11705
11930
  function formatPricingBoundaryWarning(modelLabel, boundary, inputTokens) {
11706
- return `clodex: ${modelLabel} request counted ${withThousands2(inputTokens)} input tokens, above the ${withThousands2(boundary)}-token pricing boundary. The provider prices the full request at a higher rate above this point. Set this model to the standard context stop to stay under it.`;
11931
+ return `clodex: ${modelLabel} request counted ${withThousands2(inputTokens)} input tokens, above the ${withThousands2(boundary)}-token pricing boundary. The provider prices the full request at a higher rate above this point. Choose a smaller context stop to reduce the chance of higher-rate requests.`;
11707
11932
  }
11708
11933
  function reportPricingBoundaryCrossing(observation, emit = emitParentNotice) {
11709
11934
  const { modelKey, modelLabel, pricingBoundary, inputTokens } = observation;
@@ -12513,15 +12738,10 @@ function annotateToolNames(messages) {
12513
12738
  }
12514
12739
  }
12515
12740
  function thinkingToSdkPart(block, npm) {
12516
- const text6 = block.thinking ?? "";
12517
- if (npm === "@ai-sdk/openai" && !block.signature && !text6.trim()) return null;
12518
- const part = { type: "reasoning", text: text6 };
12519
- if (block.signature) {
12520
- if (npm === "@ai-sdk/google") {
12521
- part.providerOptions = { google: { thoughtSignature: block.signature } };
12522
- } else if (npm === "@ai-sdk/openai" || npm === "@ai-sdk/openai-compatible") {
12523
- part.providerOptions = { openai: { reasoningEncryptedContent: block.signature } };
12524
- }
12741
+ if (npm === "@ai-sdk/openai") return null;
12742
+ const part = { type: "reasoning", text: block.thinking ?? "" };
12743
+ if (block.signature && npm === "@ai-sdk/google") {
12744
+ part.providerOptions = { google: { thoughtSignature: block.signature } };
12525
12745
  }
12526
12746
  return part;
12527
12747
  }
@@ -14060,7 +14280,7 @@ function buildHttpProxyRoutes(providers, favorites, modelAliases = void 0, max =
14060
14280
 
14061
14281
  // src/patcher.ts
14062
14282
  function getPatchLockPath() {
14063
- return join7(getAppHome(), "patch.lock");
14283
+ return join8(getAppHome(), "patch.lock");
14064
14284
  }
14065
14285
  function writePatchManifest(manifest, path = getPatchManifestPath()) {
14066
14286
  mkdirSync5(getAppHome(), { recursive: true, mode: 448 });
@@ -14217,7 +14437,7 @@ function pidIsAlive(pid) {
14217
14437
  function tryAcquirePatchLock(lockPath = getPatchLockPath(), opts = {}) {
14218
14438
  const now = opts.now ?? Date.now();
14219
14439
  const isAlive = opts.isAlive ?? pidIsAlive;
14220
- mkdirSync5(join7(lockPath, ".."), { recursive: true, mode: 448 });
14440
+ mkdirSync5(join8(lockPath, ".."), { recursive: true, mode: 448 });
14221
14441
  for (let attempt = 0; attempt < 2; attempt++) {
14222
14442
  try {
14223
14443
  const fd = openSync8(lockPath, "wx");
@@ -14233,7 +14453,7 @@ function tryAcquirePatchLock(lockPath = getPatchLockPath(), opts = {}) {
14233
14453
  } catch {
14234
14454
  let stale = false;
14235
14455
  try {
14236
- const existing = JSON.parse(readFileSync9(lockPath, "utf8"));
14456
+ const existing = JSON.parse(readFileSync10(lockPath, "utf8"));
14237
14457
  stale = !existing.pid || !isAlive(existing.pid) || typeof existing.startedAt === "number" && now - existing.startedAt > PATCH_LOCK_STALE_MS;
14238
14458
  } catch {
14239
14459
  stale = true;
@@ -14255,7 +14475,7 @@ function takeIgnoredLaunchOverride() {
14255
14475
  }
14256
14476
  function resolveClaudeBinaryForPatch() {
14257
14477
  const envOverride = process.env["TWEAKCC_CC_INSTALLATION_PATH"]?.trim() || null;
14258
- const nativeSymlink = join7(homedir2(), ".local", "bin", "claude");
14478
+ const nativeSymlink = join8(homedir3(), ".local", "bin", "claude");
14259
14479
  if (envOverride && !existsSync4(envOverride)) {
14260
14480
  return { ok: false, reason: "patch-target-missing", declaredPath: envOverride };
14261
14481
  }
@@ -14263,7 +14483,7 @@ function resolveClaudeBinaryForPatch() {
14263
14483
  if (!source) return { ok: false, reason: "binary-not-found" };
14264
14484
  let resolved;
14265
14485
  try {
14266
- resolved = realpathSync2(source);
14486
+ resolved = realpathSync3(source);
14267
14487
  } catch {
14268
14488
  return { ok: false, reason: "binary-not-found" };
14269
14489
  }
@@ -14283,8 +14503,8 @@ function resolveClaudeBinaryForPatch() {
14283
14503
  let launchOverrideResolved = null;
14284
14504
  if (launchOverride) {
14285
14505
  try {
14286
- const followedOverride = resolveThroughNpmShims(realpathSync2(launchOverride));
14287
- launchOverrideResolved = followedOverride.ok ? followedOverride.path : realpathSync2(launchOverride);
14506
+ const followedOverride = resolveThroughNpmShims(realpathSync3(launchOverride));
14507
+ launchOverrideResolved = followedOverride.ok ? followedOverride.path : realpathSync3(launchOverride);
14288
14508
  } catch {
14289
14509
  launchOverrideResolved = launchOverride;
14290
14510
  }
@@ -14423,8 +14643,8 @@ async function applyPatch(binaryPath, version, desired, configHash, opts) {
14423
14643
  try {
14424
14644
  mkdirSync5(backupDir(), { recursive: true });
14425
14645
  const { tryDetectInstallation, readContent, writeContent } = await import("tweakcc");
14426
- candidateDir = mkdtempSync(join7(dirname5(binaryPath), ".clodex-patch-"));
14427
- const candidatePath = join7(candidateDir, basename(binaryPath));
14646
+ candidateDir = mkdtempSync(join8(dirname5(binaryPath), ".clodex-patch-"));
14647
+ const candidatePath = join8(candidateDir, basename(binaryPath));
14428
14648
  const seedCandidate = async (from) => {
14429
14649
  copyFileSync(from, candidatePath);
14430
14650
  const shim = shimEntryModuleName(candidatePath);
@@ -14711,6 +14931,9 @@ function runRestoreCommand(target) {
14711
14931
  p2.log.success(`Restored pristine claude ${version} from ${plan.backupPath}.`);
14712
14932
  return 0;
14713
14933
  }
14934
+ function warnOnEditorExtensionDrift(binaryPath, version) {
14935
+ for (const warning of claudeExtensionDriftWarnings({ binaryPath, version })) p2.log.warn(warning);
14936
+ }
14714
14937
  async function runPatchCommand(opts = {}) {
14715
14938
  const target = resolveClaudeBinaryForPatch();
14716
14939
  const ignoredOverride = takeIgnoredLaunchOverride();
@@ -14756,6 +14979,7 @@ async function runPatchCommand(opts = {}) {
14756
14979
  });
14757
14980
  if (state === "current") {
14758
14981
  p2.log.success(`claude ${version} is already patched with the current model config \u2014 nothing to do.`);
14982
+ warnOnEditorExtensionDrift(binaryPath, version);
14759
14983
  return 0;
14760
14984
  }
14761
14985
  const release = tryAcquirePatchLock();
@@ -14791,6 +15015,7 @@ async function runPatchCommand(opts = {}) {
14791
15015
  if (!opts.trace) {
14792
15016
  for (const line of outcome.detailLines ?? []) p2.log.info(pc3.dim(line));
14793
15017
  }
15018
+ warnOnEditorExtensionDrift(binaryPath, version);
14794
15019
  return 0;
14795
15020
  } finally {
14796
15021
  release();
@@ -15164,7 +15389,7 @@ import {
15164
15389
  lstatSync,
15165
15390
  mkdirSync as mkdirSync6,
15166
15391
  openSync as openSync9,
15167
- readFileSync as readFileSync10,
15392
+ readFileSync as readFileSync11,
15168
15393
  renameSync as renameSync3,
15169
15394
  unlinkSync as unlinkSync4,
15170
15395
  writeFileSync as writeFileSync6
@@ -15271,7 +15496,7 @@ function readJournalUnlocked(path) {
15271
15496
  if (opened.size > MAX_JOURNAL_BYTES) {
15272
15497
  throw new Error("Credential cleanup journal is too large.");
15273
15498
  }
15274
- return parseJournal(JSON.parse(readFileSync10(fd, "utf8")));
15499
+ return parseJournal(JSON.parse(readFileSync11(fd, "utf8")));
15275
15500
  } catch (error) {
15276
15501
  const message = error instanceof Error ? error.message : String(error);
15277
15502
  throw new Error(`Could not read credential cleanup journal: ${message}`);
@@ -16338,7 +16563,8 @@ function readCapabilityFlags(m) {
16338
16563
  const bool = (v) => typeof v === "boolean" ? v : void 0;
16339
16564
  return {
16340
16565
  useResponsesLite: bool(m["use_responses_lite"]),
16341
- preferWebSockets: bool(m["prefer_websockets"])
16566
+ preferWebSockets: bool(m["prefer_websockets"]),
16567
+ minimalClientVersion: readCodexClientVersion(m["minimal_client_version"])
16342
16568
  };
16343
16569
  }
16344
16570
  function parseOpenAiModelEntries(body) {
@@ -16375,7 +16601,8 @@ function buildDynamicOAuthModel(entry, seedById, codexCatalog) {
16375
16601
  effectiveContextPercent: entry.effective_context_window_percent ?? seed.effectiveContextPercent,
16376
16602
  maxOutputTokens: entry.max_output_tokens ?? seed.maxOutputTokens,
16377
16603
  useResponsesLite: entry.useResponsesLite ?? seed.useResponsesLite,
16378
- preferWebSockets: entry.preferWebSockets ?? seed.preferWebSockets
16604
+ preferWebSockets: entry.preferWebSockets ?? seed.preferWebSockets,
16605
+ minimalClientVersion: entry.minimalClientVersion ?? seed.minimalClientVersion
16379
16606
  };
16380
16607
  }
16381
16608
  const { id } = entry;
@@ -16410,7 +16637,8 @@ function buildDynamicOAuthModel(entry, seedById, codexCatalog) {
16410
16637
  // so a wrong `true` here costs an unusable menu entry, not a 400.
16411
16638
  reasoning: codexCatalog ? true : modelPrefersResponsesApi(id),
16412
16639
  useResponsesLite: entry.useResponsesLite,
16413
- preferWebSockets: entry.preferWebSockets
16640
+ preferWebSockets: entry.preferWebSockets,
16641
+ minimalClientVersion: entry.minimalClientVersion
16414
16642
  };
16415
16643
  }
16416
16644
  async function fetchJsonWithAuth(url, accessToken, timeoutMs) {
@@ -16471,6 +16699,10 @@ async function refreshOpenAiOAuthModels(accessToken) {
16471
16699
  credentialRejected: credentialFailure !== void 0
16472
16700
  };
16473
16701
  }
16702
+ function warningSuffix(models) {
16703
+ const warning = codexClientVersionWarning(applyOAuthSeedContextMetadata(models));
16704
+ return warning ? ` ${warning}` : "";
16705
+ }
16474
16706
  async function refreshApiListProvider(provider, apiKey) {
16475
16707
  const npm = provider.api.npm ?? "@ai-sdk/openai-compatible";
16476
16708
  const retained = isRetainedOpenCodeGoProvider(provider);
@@ -16680,7 +16912,7 @@ async function refreshProviderModels(providerId, apiKey, registry, credentialSna
16680
16912
  if (oauthResult.source === "seed" && cachedModelCount(cacheProvider) > 0) {
16681
16913
  return skipWithCachedModels(
16682
16914
  cacheProvider,
16683
- `Live model discovery failed${failureDetail} \u2014 kept your existing cached model list instead of overwriting it with clodex's built-in fallback list. Try refreshing again later.`
16915
+ `Live model discovery failed${failureDetail} \u2014 kept your existing cached model list instead of overwriting it with clodex's built-in fallback list. Try refreshing again later.` + warningSuffix(cacheProvider.modelsCache.models)
16684
16916
  );
16685
16917
  }
16686
16918
  if (oauthResult.source === "seed") {
@@ -16774,7 +17006,7 @@ async function refreshProviderModels(providerId, apiKey, registry, credentialSna
16774
17006
  ok: true,
16775
17007
  modelCount: enriched.length,
16776
17008
  previousModelCount: hadPreviousRefresh ? previousModelCount : void 0,
16777
- reason: oauthFallbackReason
17009
+ reason: isChatGptOAuthProvider(provider) ? [oauthFallbackReason, codexClientVersionWarning(models)].filter(Boolean).join(" ") || void 0 : void 0
16778
17010
  };
16779
17011
  } catch (err) {
16780
17012
  return {
@@ -17131,6 +17363,7 @@ async function authenticateProvider(providerId, options = {}) {
17131
17363
  refreshSpinner.stop(`Models not refreshed${refreshResult.reason ? ` \u2014 ${refreshResult.reason}` : ""}`);
17132
17364
  } else if (refreshResult.ok) {
17133
17365
  refreshSpinner.stop("Models refreshed");
17366
+ if (refreshResult.reason) p4.log.warn(refreshResult.reason);
17134
17367
  } else {
17135
17368
  refreshSpinner.stop(`Could not refresh models${refreshResult.reason ? ` \u2014 ${refreshResult.reason}` : ""}`);
17136
17369
  }
@@ -18810,7 +19043,7 @@ async function routeRequest(req, res, options, modelCache, plog) {
18810
19043
  }
18811
19044
  async function handleAnthropicMessages(req, res, options, modelCache, plog) {
18812
19045
  const clientAbort = watchClientDisconnect(res);
18813
- const body = await readJson(req);
19046
+ const body = await readJson2(req);
18814
19047
  if (!body) {
18815
19048
  sendJson(res, 400, { error: { message: "Invalid JSON body" } });
18816
19049
  return;
@@ -19080,7 +19313,7 @@ data: ${JSON.stringify({
19080
19313
  }
19081
19314
  async function handleAnthropicCountTokens(req, res, options, plog) {
19082
19315
  const clientAbort = watchClientDisconnect(res);
19083
- const body = await readJson(req);
19316
+ const body = await readJson2(req);
19084
19317
  if (!body) {
19085
19318
  sendJson(res, 400, { error: { message: "Invalid JSON body" } });
19086
19319
  return;
@@ -19149,7 +19382,7 @@ async function handleAnthropicCountTokens(req, res, options, plog) {
19149
19382
  }
19150
19383
  async function handleOpenAIChatCompletions(req, res, options, modelCache, plog) {
19151
19384
  const clientAbort = watchClientDisconnect(res);
19152
- const body = await readJson(req);
19385
+ const body = await readJson2(req);
19153
19386
  if (!body) {
19154
19387
  sendJson(res, 400, { error: { message: "Invalid JSON body" } });
19155
19388
  return;
@@ -19356,7 +19589,7 @@ function getResponseModelId(bodyModel, model, options) {
19356
19589
  if (typeof bodyModel === "string" && options.aliasNames?.has(bodyModel)) return bodyModel;
19357
19590
  return options.gateway?.maskGatewayIds ? gatewayDisplayName(model, options.gateway) : typeof bodyModel === "string" ? bodyModel : model.id;
19358
19591
  }
19359
- async function readJson(req) {
19592
+ async function readJson2(req) {
19360
19593
  try {
19361
19594
  const raw = await readBody(req);
19362
19595
  return raw ? JSON.parse(raw) : {};
@@ -19500,8 +19733,8 @@ import { createBrotliDecompress, createGunzip, createInflate } from "zlib";
19500
19733
 
19501
19734
  // src/http-proxy/ca.ts
19502
19735
  import { randomBytes as randomBytes2 } from "crypto";
19503
- import { chmodSync as chmodSync5, existsSync as existsSync6, mkdirSync as mkdirSync7, readFileSync as readFileSync11, writeFileSync as writeFileSync7 } from "fs";
19504
- import { dirname as dirname7, join as join8, resolve as resolve3 } from "path";
19736
+ import { chmodSync as chmodSync5, existsSync as existsSync6, mkdirSync as mkdirSync7, readFileSync as readFileSync12, writeFileSync as writeFileSync7 } from "fs";
19737
+ import { dirname as dirname7, join as join9, resolve as resolve3 } from "path";
19505
19738
  import forge from "node-forge";
19506
19739
  var CERT_DIR = "http-proxy";
19507
19740
  var CA_CERT_FILE = "clodex-ca.pem";
@@ -19516,14 +19749,14 @@ function serialNumber() {
19516
19749
  return bytes.toString("hex");
19517
19750
  }
19518
19751
  function certPaths() {
19519
- const dir = join8(getAppHome(), CERT_DIR);
19752
+ const dir = join9(getAppHome(), CERT_DIR);
19520
19753
  return {
19521
19754
  dir,
19522
- caCert: join8(dir, CA_CERT_FILE),
19523
- caKey: join8(dir, CA_KEY_FILE),
19524
- serverCert: join8(dir, SERVER_CERT_FILE),
19525
- serverKey: join8(dir, SERVER_KEY_FILE),
19526
- version: join8(dir, CERT_VERSION_FILE)
19755
+ caCert: join9(dir, CA_CERT_FILE),
19756
+ caKey: join9(dir, CA_KEY_FILE),
19757
+ serverCert: join9(dir, SERVER_CERT_FILE),
19758
+ serverKey: join9(dir, SERVER_KEY_FILE),
19759
+ version: join9(dir, CERT_VERSION_FILE)
19527
19760
  };
19528
19761
  }
19529
19762
  function writePrivate(path, value) {
@@ -19576,8 +19809,8 @@ function generateCertificates(paths) {
19576
19809
  }
19577
19810
  function storedCertificatesAreCurrent(paths) {
19578
19811
  try {
19579
- const ca = forge.pki.certificateFromPem(readFileSync11(paths.caCert, "utf8"));
19580
- const server = forge.pki.certificateFromPem(readFileSync11(paths.serverCert, "utf8"));
19812
+ const ca = forge.pki.certificateFromPem(readFileSync12(paths.caCert, "utf8"));
19813
+ const server = forge.pki.certificateFromPem(readFileSync12(paths.serverCert, "utf8"));
19581
19814
  const now = Date.now();
19582
19815
  const renewalBuffer = 7 * 24 * 60 * 60 * 1e3;
19583
19816
  return ca.validity.notBefore.getTime() <= now && ca.validity.notAfter.getTime() > now + renewalBuffer && server.validity.notBefore.getTime() <= now && server.validity.notAfter.getTime() > now + renewalBuffer && ca.verify(ca) && ca.verify(server);
@@ -19588,13 +19821,13 @@ function storedCertificatesAreCurrent(paths) {
19588
19821
  function ensureHttpProxyCertificates() {
19589
19822
  const paths = certPaths();
19590
19823
  const required = [paths.caCert, paths.caKey, paths.serverCert, paths.serverKey, paths.version];
19591
- const current = required.every(existsSync6) && readFileSync11(paths.version, "utf8") === CERT_VERSION && storedCertificatesAreCurrent(paths);
19824
+ const current = required.every(existsSync6) && readFileSync12(paths.version, "utf8") === CERT_VERSION && storedCertificatesAreCurrent(paths);
19592
19825
  if (!current) generateCertificates(paths);
19593
19826
  return {
19594
19827
  caCertPath: paths.caCert,
19595
- caCert: readFileSync11(paths.caCert, "utf8"),
19596
- serverCert: readFileSync11(paths.serverCert, "utf8"),
19597
- serverKey: readFileSync11(paths.serverKey, "utf8")
19828
+ caCert: readFileSync12(paths.caCert, "utf8"),
19829
+ serverCert: readFileSync12(paths.serverCert, "utf8"),
19830
+ serverKey: readFileSync12(paths.serverKey, "utf8")
19598
19831
  };
19599
19832
  }
19600
19833
  function ensureHttpProxyCaBundle(relayCaCertPath, additionalCaCertPath, onWarning) {
@@ -19608,7 +19841,7 @@ function ensureHttpProxyCaBundle(relayCaCertPath, additionalCaCertPath, onWarnin
19608
19841
  let additionalCa;
19609
19842
  try {
19610
19843
  if (resolve3(additionalCaCertPath) === resolve3(relayCaCertPath)) return relayCaCertPath;
19611
- additionalCa = readFileSync11(additionalCaCertPath, "utf8").trim();
19844
+ additionalCa = readFileSync12(additionalCaCertPath, "utf8").trim();
19612
19845
  } catch (err) {
19613
19846
  return warn(
19614
19847
  `NODE_EXTRA_CA_CERTS=${additionalCaCertPath} cannot be read (${err instanceof Error ? err.message : String(err)}), so it is not part of the proxy CA bundle. Where node reports this itself it says only "Ignoring extra certs ... load failed", without naming the variable. Clear or correct it.`
@@ -19618,8 +19851,8 @@ function ensureHttpProxyCaBundle(relayCaCertPath, additionalCaCertPath, onWarnin
19618
19851
  return warn(`NODE_EXTRA_CA_CERTS=${additionalCaCertPath} is empty, so it adds nothing.`);
19619
19852
  }
19620
19853
  try {
19621
- const relayCa = readFileSync11(relayCaCertPath, "utf8").trimEnd();
19622
- const combinedPath = join8(dirname7(relayCaCertPath), "combined-ca.pem");
19854
+ const relayCa = readFileSync12(relayCaCertPath, "utf8").trimEnd();
19855
+ const combinedPath = join9(dirname7(relayCaCertPath), "combined-ca.pem");
19623
19856
  writePublic(combinedPath, `${relayCa}
19624
19857
  ${additionalCa}
19625
19858
  `);
@@ -21500,9 +21733,9 @@ function planLaunchWizard(opts) {
21500
21733
 
21501
21734
  // src/vscode-launcher.ts
21502
21735
  import { spawnSync as spawnSync2 } from "child_process";
21503
- import { existsSync as existsSync7, mkdirSync as mkdirSync8, mkdtempSync as mkdtempSync2, readdirSync, readFileSync as readFileSync12, rmSync as rmSync2, statSync as statSync8, writeFileSync as writeFileSync8 } from "fs";
21504
- import { tmpdir } from "os";
21505
- import { dirname as dirname8, join as join9 } from "path";
21736
+ import { existsSync as existsSync7, mkdirSync as mkdirSync8, mkdtempSync as mkdtempSync2, readdirSync as readdirSync2, readFileSync as readFileSync13, rmSync as rmSync2, statSync as statSync8, writeFileSync as writeFileSync8 } from "fs";
21737
+ import { homedir as homedir4, tmpdir } from "os";
21738
+ import { dirname as dirname8, join as join10 } from "path";
21506
21739
  import { fileURLToPath } from "url";
21507
21740
  var LAUNCHER_EXE_NAME = "clodex-claude.exe";
21508
21741
  var VSCODE_WRAPPER_SETTING = "claudeCode.claudeProcessWrapper";
@@ -21511,15 +21744,15 @@ var WRAPPER_PATH_PLACEHOLDER = '"__CLODEX_WRAPPER_SCRIPT_PATH__"';
21511
21744
  var LAUNCHER_SOURCE_FILE = "clodex-claude-launcher.cs";
21512
21745
  var UTF8_BOM = "\uFEFF";
21513
21746
  var CSC_ARGS = ["/nologo", "/target:exe", "/platform:anycpu", "/optimize+", `/out:${LAUNCHER_EXE_NAME}`, LAUNCHER_SOURCE_FILE];
21514
- var packageRoot = join9(dirname8(fileURLToPath(import.meta.url)), "..");
21747
+ var packageRoot = join10(dirname8(fileURLToPath(import.meta.url)), "..");
21515
21748
  function defaultLauncherInputs() {
21516
21749
  return {
21517
- launcherSourcePath: join9(packageRoot, "launcher", LAUNCHER_SOURCE_FILE),
21518
- wrapperScriptPath: join9(packageRoot, "dist", "claude-wrapper.js")
21750
+ launcherSourcePath: join10(packageRoot, "launcher", LAUNCHER_SOURCE_FILE),
21751
+ wrapperScriptPath: join10(packageRoot, "dist", "claude-wrapper.js")
21519
21752
  };
21520
21753
  }
21521
21754
  function launcherInstallPath(env = process.env) {
21522
- return join9(getAppHome(env), "bin", LAUNCHER_EXE_NAME);
21755
+ return join10(getAppHome(env), "bin", LAUNCHER_EXE_NAME);
21523
21756
  }
21524
21757
  function csharpStringLiteral(value) {
21525
21758
  let literal = '"';
@@ -21545,30 +21778,23 @@ function renderLauncherSource(template, paths) {
21545
21778
  }
21546
21779
  function cscSearchRoots(env = process.env) {
21547
21780
  const windir = env.WINDIR?.trim() || env.SystemRoot?.trim() || "C:\\Windows";
21548
- const dotnet = join9(windir, "Microsoft.NET");
21549
- return [join9(dotnet, "Framework64"), join9(dotnet, "Framework")];
21781
+ const dotnet = join10(windir, "Microsoft.NET");
21782
+ return [join10(dotnet, "Framework64"), join10(dotnet, "Framework")];
21550
21783
  }
21551
21784
  function frameworkVersionKey(name) {
21552
21785
  return name.slice(1).split(".").map((part) => Number.parseInt(part, 10) || 0);
21553
21786
  }
21554
- function compareVersionKeys(a, b) {
21555
- for (let i = 0; i < Math.max(a.length, b.length); i += 1) {
21556
- const diff = (a[i] ?? 0) - (b[i] ?? 0);
21557
- if (diff !== 0) return diff;
21558
- }
21559
- return 0;
21560
- }
21561
21787
  function findCsc(env = process.env) {
21562
21788
  for (const root of cscSearchRoots(env)) {
21563
21789
  let entries;
21564
21790
  try {
21565
- entries = readdirSync(root);
21791
+ entries = readdirSync2(root);
21566
21792
  } catch {
21567
21793
  continue;
21568
21794
  }
21569
21795
  const candidates = entries.filter((name) => /^v4\.\d+(\.\d+)*$/i.test(name)).sort((a, b) => compareVersionKeys(frameworkVersionKey(b), frameworkVersionKey(a)));
21570
21796
  for (const version of candidates) {
21571
- const csc = join9(root, version, "csc.exe");
21797
+ const csc = join10(root, version, "csc.exe");
21572
21798
  try {
21573
21799
  if (statSync8(csc).isFile()) return csc;
21574
21800
  } catch {
@@ -21627,7 +21853,7 @@ function installVscodeLauncher(opts = {}) {
21627
21853
  }
21628
21854
  let source;
21629
21855
  try {
21630
- source = renderLauncherSource(readFileSync12(launcherSourcePath, "utf8"), { nodePath, wrapperScriptPath });
21856
+ source = renderLauncherSource(readFileSync13(launcherSourcePath, "utf8"), { nodePath, wrapperScriptPath });
21631
21857
  } catch (err) {
21632
21858
  return { ok: false, reason: "missing-input", message: `could not prepare the launcher source: ${describeError(err)}` };
21633
21859
  }
@@ -21635,7 +21861,7 @@ function installVscodeLauncher(opts = {}) {
21635
21861
  const tempRoot = opts.tempRoot ?? tmpdir();
21636
21862
  let scratch;
21637
21863
  try {
21638
- scratch = mkdtempSync2(join9(tempRoot, "clodex-vscode-launcher-"));
21864
+ scratch = mkdtempSync2(join10(tempRoot, "clodex-vscode-launcher-"));
21639
21865
  } catch (err) {
21640
21866
  return { ok: false, reason: "install-failed", message: `could not create a scratch directory under ${tempRoot}: ${describeError(err)}` };
21641
21867
  }
@@ -21650,9 +21876,9 @@ function installVscodeLauncher(opts = {}) {
21650
21876
  }
21651
21877
  function buildAndPublish(step) {
21652
21878
  const { scratch, cscPath, exePath } = step;
21653
- writeFileSync8(join9(scratch, LAUNCHER_SOURCE_FILE), UTF8_BOM + step.source, "utf8");
21879
+ writeFileSync8(join10(scratch, LAUNCHER_SOURCE_FILE), UTF8_BOM + step.source, "utf8");
21654
21880
  const compiled = step.compile(cscPath, CSC_ARGS, scratch);
21655
- const builtExe = join9(scratch, LAUNCHER_EXE_NAME);
21881
+ const builtExe = join10(scratch, LAUNCHER_EXE_NAME);
21656
21882
  if (compiled.error || compiled.status !== 0 || !existsSync7(builtExe)) {
21657
21883
  const output = [compiled.stdout, compiled.stderr].map((text6) => text6.trim()).filter(Boolean).join("\n");
21658
21884
  return {
@@ -21707,16 +21933,27 @@ function successReport(result) {
21707
21933
  "install: the paths above are compiled into the executable."
21708
21934
  ];
21709
21935
  }
21936
+ function launcherExtensionDriftWarnings(opts = {}) {
21937
+ const env = opts.env ?? process.env;
21938
+ const manifest = readPatchManifest(getPatchManifestPath(env));
21939
+ if (!manifest || typeof manifest.claudeVersion !== "string") return [];
21940
+ return claudeExtensionDriftWarnings(
21941
+ { binaryPath: manifest.binaryPath, version: manifest.claudeVersion },
21942
+ opts.homeDir ?? homedir4(),
21943
+ () => readNpmGlobalRoot(env)
21944
+ );
21945
+ }
21710
21946
  function runInstallVscodeLauncherCommand(opts = {}) {
21711
21947
  const result = installVscodeLauncher(opts);
21712
21948
  if (result.warning) console.error(`clodex: warning: ${result.warning}`);
21713
21949
  if (!result.ok) {
21714
21950
  console.error(`clodex: ${result.message}`);
21715
21951
  if (result.detail) console.error(result.detail);
21716
- return 1;
21952
+ } else {
21953
+ console.log(successReport(result).join("\n"));
21717
21954
  }
21718
- console.log(successReport(result).join("\n"));
21719
- return 0;
21955
+ for (const drift of launcherExtensionDriftWarnings(opts)) console.error(`clodex: warning: ${drift}`);
21956
+ return result.ok ? 0 : 1;
21720
21957
  }
21721
21958
 
21722
21959
  // src/cli.ts
@@ -22050,8 +22287,10 @@ ${pc13.bold("Options:")}
22050
22287
  --trace Write debug logs to ~/.clodex/logs/ and show errors on exit
22051
22288
  --fast Request Codex fast mode (service_tier=priority) on ChatGPT-OAuth models
22052
22289
  (equivalent to CLODEX_SERVICE_TIER=fast; warns if the SDK omits it)
22053
- --provider Boot provider id (skip wizard when paired with --model or in print mode)
22054
- --model Boot model id (skip wizard when paired with --provider or in print mode)
22290
+ --provider Endpoint mode only: boot provider id (skip wizard when paired with
22291
+ --model or in print mode)
22292
+ --model Endpoint mode only: boot model id (skip wizard when paired with
22293
+ --provider or in print mode)
22055
22294
  --context <model=stop> use a different share of a model's window for this
22056
22295
  launch only (standard, max, default, or a token count). Nothing is
22057
22296
  saved. It reaches Claude Code through the exported catalog, so a
@@ -22092,7 +22331,7 @@ ${pc13.bold("Examples:")}
22092
22331
  clodex claude --trace --resume abc-123
22093
22332
  clodex claude --endpoint
22094
22333
  clodex claude --endpoint --save-mode
22095
- clodex claude --provider openai-oauth --model gpt-5.6-sol
22334
+ clodex claude --endpoint --provider openai-oauth --model gpt-6-sol
22096
22335
  clodex claude -- --print "hello"
22097
22336
  clodex claude -- --dangerously-skip-permissions`;
22098
22337
  }
@@ -22180,7 +22419,7 @@ Manage favorite models for mid-session switching.
22180
22419
  ${pc13.bold("Usage:")}
22181
22420
  clodex favorites
22182
22421
  clodex models --list
22183
- clodex models --alias sol=clodex:openai-oauth:gpt-5.6-sol
22422
+ clodex models --alias sol=clodex:openai-oauth:gpt-6-sol
22184
22423
  clodex models --unalias sol
22185
22424
  clodex models --context sol=max --save
22186
22425
  clodex models --json
@@ -22213,13 +22452,16 @@ ${pc13.bold("How it works:")}
22213
22452
  Claude Code binary so they pass model validation and report real context.
22214
22453
 
22215
22454
  ${pc13.bold("Context stops and cost:")}
22216
- Some providers price large prompts in bands: GPT-5.6 bills the full request at
22217
- a higher rate above 272,000 input tokens. The standard stop stays under that
22218
- line, so max is opt-in and reports the boundary when you select it.
22455
+ Some providers price large prompts in bands: GPT-5.6 and GPT-6 bill the full request at
22456
+ a higher rate above 272,000 input tokens. For ChatGPT/Codex-plan models whose
22457
+ catalog reports a 272,000-token window, standard stays at that line and a
22458
+ larger window is opt-in. OpenAI API-key models retain their full window, so
22459
+ standard may already cross the boundary. Choose a smaller numeric stop such
22460
+ as 250k to limit exposure to the higher rate.
22219
22461
 
22220
22462
  ${pc13.bold("Examples:")}
22221
22463
  clodex favorites
22222
- clodex models --alias sol=clodex:openai-oauth:gpt-5.6-sol
22464
+ clodex models --alias sol=clodex:openai-oauth:gpt-6-sol
22223
22465
  clodex models --context sol=max --save
22224
22466
  clodex claude # switch menu active when favorites are set`;
22225
22467
  }
@@ -22252,7 +22494,13 @@ ${pc13.bold("Behavior:")}
22252
22494
  Local patches execute after the built-ins as an all-or-none set. Enabling
22253
22495
  them executes trusted JavaScript from local-patches.mjs with your full user
22254
22496
  permissions. Local failures are reported but never block the built-ins.
22255
- Run clodex patch again after every claude update.`;
22497
+ Run clodex patch again after every claude update.
22498
+
22499
+ An editor launching Claude Code through clodex-claude runs this patched
22500
+ install only when its Claude Code extension is the same build. When an
22501
+ installed extension is a different version, clodex patch warns and prints the
22502
+ commands that align them. Editors checked:
22503
+ ${CHECKED_EDITORS_DESCRIPTION}`;
22256
22504
  }
22257
22505
  function installVscodeLauncherHelpText() {
22258
22506
  return `${pc13.bold("clodex install-vscode-launcher")} v${VERSION}
@@ -22282,6 +22530,8 @@ ${pc13.bold("Behavior:")}
22282
22530
  As on macOS and Linux, clodex-claude then runs your clodex-patched install in
22283
22531
  place of the bundled claude.exe when the two builds match, so clodex models
22284
22532
  appear in the extension's model picker; re-run clodex patch after updates.
22533
+ Like clodex patch, it warns when an installed Claude Code extension is a
22534
+ different version from the Claude Code clodex last patched.
22285
22535
 
22286
22536
  The paths to node.exe and to the clodex install are compiled in: re-run this
22287
22537
  command after switching Node versions or moving the clodex install.
@@ -23232,7 +23482,7 @@ Error: ${resolved.error}
23232
23482
  function isCliEntryPoint() {
23233
23483
  if (!process.argv[1]) return false;
23234
23484
  try {
23235
- return realpathSync3(fileURLToPath2(import.meta.url)) === realpathSync3(process.argv[1]);
23485
+ return realpathSync4(fileURLToPath2(import.meta.url)) === realpathSync4(process.argv[1]);
23236
23486
  } catch {
23237
23487
  return false;
23238
23488
  }