@bman654/clodex 2.15.0 → 2.17.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/README.md CHANGED
@@ -22,8 +22,8 @@ Contributions are welcome — see [CONTRIBUTING.md](./CONTRIBUTING.md) for how t
22
22
  npm install -g @bman654/clodex # 1. install the CLI (Node 22+)
23
23
  clodex providers auth openai # 2. sign in with your ChatGPT/Codex plan (device-code OAuth)
24
24
  clodex models # 3. pick favorite models and aliases
25
- clodex models --alias sol=clodex:openai-oauth:gpt-5.6-sol
26
- clodex models --alias luna=clodex:openai-oauth:gpt-5.6-luna
25
+ clodex models --alias sol=clodex:openai-oauth:gpt-6-sol
26
+ clodex models --alias luna=clodex:openai-oauth:gpt-6-luna
27
27
  clodex models --alias terra=clodex:openai-oauth:gpt-5.6-terra
28
28
  clodex patch # 4. (optional) patch Claude Code so those models are first-class
29
29
  clodex claude # 5. launch Claude Code on an OpenAI model
@@ -80,7 +80,7 @@ Clodex avoids this. In proxy mode it uses an HTTP proxy to intercept requests bo
80
80
 
81
81
  Both `clodex claude` and `clodex server` support two bridge modes. A mode flag applies to **that run only**; to change a command's default, add `--save-mode` (e.g. `clodex claude --endpoint --save-mode`). With no flag and nothing saved, both commands default to **proxy** mode, which works with your existing Claude auth.
82
82
 
83
- - **`--proxy`** (the default): a selective man-in-the-middle proxy for `api.anthropic.com`. Claude Code keeps its normal Anthropic login — Anthropic models work untouched — while models named `clodex:<provider-id>:<model-id>` (or their saved aliases) route to the selected configured provider. Switch with `/model clodex:openai-oauth:gpt-5.6-sol` or `/model sol` after patching.
83
+ - **`--proxy`** (the default): a selective man-in-the-middle proxy for `api.anthropic.com`. Claude Code keeps its normal Anthropic login — Anthropic models work untouched — while models named `clodex:<provider-id>:<model-id>` (or their saved aliases) route to the selected configured provider. Switch with `/model clodex:openai-oauth:gpt-6-sol` or `/model sol` after patching.
84
84
  - **`--endpoint`**: clodex runs a local Anthropic-format gateway and launches Claude Code with `ANTHROPIC_BASE_URL` pointed at it. All traffic goes through the gateway. With favorites saved, the gateway is multi-route and Claude Code's `/model` menu lists your starting model plus favorites for live switching.
85
85
 
86
86
  > [!TIP]
@@ -238,7 +238,7 @@ Manage favorite models (max 20) and short aliases. Favorites feed the endpoint-m
238
238
  | --- | --- |
239
239
  | *(none)* | Interactive manager: search all providers or browse one at a time |
240
240
  | `--list` | Print the exact `clodex:<provider-id>:<model-id>` names (and aliases) without opening the manager |
241
- | `--alias <name=target>` | Save a short name for a favorite, e.g. `--alias sol=clodex:openai-oauth:gpt-5.6-sol` (the `clodex:` prefix is optional in the target) |
241
+ | `--alias <name=target>` | Save a short name for a favorite, e.g. `--alias sol=clodex:openai-oauth:gpt-6-sol` (the `clodex:` prefix is optional in the target) |
242
242
  | `--unalias <name>` | Remove a saved short name |
243
243
  | `--context <model=stop>` | Choose how much of a model's context window to use: `standard`, `max`, `default` to clear, or a token count such as `500k`. Applies to this run unless `--save` is given |
244
244
  | `--save` | With `--context`: store the stop as that model's default |
@@ -276,6 +276,16 @@ Two things worth knowing about the numbers:
276
276
  number first only costs usable context. A provider that declares a share of its own
277
277
  is still honoured; clodex just never invents one. Use `--context` if you want a
278
278
  smaller window than the provider offers.
279
+ - **A window nobody published is not a ceiling.** Some servers list their models
280
+ without saying how much context each one takes. Clodex reports the 200,000 Claude
281
+ Code assumes anyway, but that number is a guess, not a measurement — so
282
+ `clodex models --context <model>=1m --save` raises it instead of being clamped back
283
+ down to the guess. Clamping still applies in full wherever a window *is* published,
284
+ by the provider or by clodex's own catalog. If the model is already baked into a
285
+ patched binary, re-run `clodex patch` so it picks the new window up. Set this model up
286
+ before this version? Run `clodex providers refresh-models <provider>` once — an older
287
+ clodex stored the guess as if the server had published it, and until you refresh it is
288
+ indistinguishable from a real limit and still clamps.
279
289
  - **The account ceiling moves.** It is server-side and per-account, and it has
280
290
  changed by more than 2x within a single day in the past. `max` reads whatever the
281
291
  catalog reports now and clamps to it, so a stale ceiling shrinks the stop rather
@@ -292,7 +302,7 @@ Two things worth knowing about the numbers:
292
302
  | `remove <id>` | Remove a provider by id |
293
303
  | `refresh-models [id]` | Update cached model lists |
294
304
 
295
- Providers supported: `openai` (API key, platform.openai.com), `openai-oauth` (ChatGPT/Codex plan), and `opencode-go` (OpenCode Go API key). OpenCode Go exposes its Anthropic Messages and Chat Completions models; Responses-only entries are intentionally excluded. See [OpenCode Go provider](docs/opencode-go.md).
305
+ Providers supported: `openai` (API key, platform.openai.com), `openai-oauth` (ChatGPT/Codex plan), and `opencode-go` (OpenCode Go API key). OpenCode Go exposes its Anthropic Messages, Chat Completions, and Responses models; entries whose transport has not been verified against the live endpoint are left out. See [OpenCode Go provider](docs/opencode-go.md).
296
306
 
297
307
  A **custom OpenAI-compatible server** is any OpenAI-style API, given by its base URL (the part before `/chat/completions`), such as `https://openrouter.ai/api/v1`. `providers add` asks for a name, the base URL and an API key (leave it empty for a local server without auth), lists the server's models to check the connection, and saves the provider as `custom-<name>`. Its models then appear in `clodex models`; once saved as favorites they are addressed as `clodex:custom-<name>:<model>` and can be given a short alias with `clodex models --alias`. The base URL is checked before anything is saved: plain `http://` is accepted only after you confirm it, and only for loopback or private-network addresses, and an `https://` URL whose host is, or resolves to, a loopback (`127.0.0.0/8`, `::1`), private, link-local, unique-local, carrier-grade-NAT or well-known cloud-metadata address is refused. A model's id and name are stored with surrounding whitespace trimmed, and a model whose trimmed id or name still contains a control character is skipped, whether the list is fetched by `providers add` or by `providers refresh-models`, and error text from the server is shown with control characters replaced by spaces. Only the OpenAI-compatible kind is offered in the menu.
298
308
 
@@ -357,7 +367,7 @@ clodex --version # version
357
367
  this pre-dispatch intent, not proof of wire serialization. If the provider
358
368
  SDK reports that it omitted the tier for a model, clodex warns once and the
359
369
  backend default remains in use.
360
- - **Outbound proxy:** when `HTTP_PROXY`/`HTTPS_PROXY` (and optionally `NO_PROXY`) are set in clodex's environment, all clodex-originated network calls honor them — OAuth sign-in and token refresh, model-list and models.dev refreshes, upstream OpenAI API calls, and the ChatGPT/Codex OAuth WebSocket transport (tunneled via HTTP CONNECT).
370
+ - **Outbound proxy:** when `HTTP_PROXY`/`HTTPS_PROXY` (and optionally `NO_PROXY`) are set in clodex's environment, all clodex-originated network calls honor them — OAuth sign-in and token refresh, model-list and models.dev refreshes, upstream OpenAI API calls, and the ChatGPT/Codex OAuth WebSocket transport (tunneled via HTTP CONNECT). In proxy mode this also covers the web traffic Claude Code itself makes through the clodex proxy, so web fetches and searches work behind a corporate proxy instead of failing on a blocked direct connection.
361
371
  - **Provider timeouts:** `CLODEX_UPSTREAM_IDLE_TIMEOUT_MS` controls how long an
362
372
  SDK-backed translated stream may produce no event (default `120000`; range
363
373
  `10000`–`3600000` ms). `CLODEX_UPSTREAM_TOTAL_TIMEOUT_MS` limits each call
package/dist/cli.js CHANGED
@@ -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.15.0",
349
+ version: "2.17.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],
@@ -694,20 +695,22 @@ function getCacheIndex() {
694
695
  }
695
696
  return cacheIndex;
696
697
  }
697
- function contextWindowFromHeuristics(modelId) {
698
- const cached = heuristicCache.get(modelId);
699
- if (cached !== void 0) return cached;
698
+ function matchHeuristicWindow(modelId) {
699
+ if (heuristicCache.has(modelId)) return heuristicCache.get(modelId);
700
700
  for (const [pattern, size] of HEURISTIC_RULES) {
701
701
  if (pattern.test(modelId)) {
702
702
  heuristicCache.set(modelId, size);
703
703
  return size;
704
704
  }
705
705
  }
706
- heuristicCache.set(modelId, DEFAULT_CONTEXT_WINDOW);
707
- return DEFAULT_CONTEXT_WINDOW;
706
+ heuristicCache.set(modelId, void 0);
707
+ return void 0;
708
+ }
709
+ function lookupKnownContextWindow(modelId) {
710
+ return getCacheIndex().get(modelId) ?? matchHeuristicWindow(modelId);
708
711
  }
709
712
  function lookupContextWindow(modelId) {
710
- return getCacheIndex().get(modelId) ?? contextWindowFromHeuristics(modelId);
713
+ return lookupKnownContextWindow(modelId) ?? DEFAULT_CONTEXT_WINDOW;
711
714
  }
712
715
  function resolveContextWindow(modelId, explicit) {
713
716
  if (typeof explicit === "number" && explicit > 0) return explicit;
@@ -3160,17 +3163,19 @@ function parseContextStop(value) {
3160
3163
  return parsed;
3161
3164
  }
3162
3165
  function resolveContextStop(limits, stop = "standard") {
3163
- const standardRaw = positiveInteger(limits.contextWindow) ?? 0;
3164
- const ceiling = positiveInteger(limits.maxContextWindow) ?? standardRaw;
3165
- const requested = stop === "standard" ? standardRaw : stop === "max" ? ceiling : positiveInteger(stop) ?? standardRaw;
3166
- const raw = Math.min(requested, ceiling);
3166
+ const declaredWindow = positiveInteger(limits.contextWindow);
3167
+ const declaredCeiling = positiveInteger(limits.maxContextWindow);
3168
+ const standardRaw = declaredWindow ?? DEFAULT_CONTEXT_WINDOW;
3169
+ const ceiling = declaredCeiling ?? declaredWindow;
3170
+ const requested = stop === "standard" ? standardRaw : stop === "max" ? declaredCeiling ?? standardRaw : positiveInteger(stop) ?? standardRaw;
3171
+ const raw = ceiling === void 0 ? requested : Math.min(requested, ceiling);
3167
3172
  const effective = effectiveContextWindow(raw, limits.effectiveContextPercent);
3168
3173
  const boundary = positiveInteger(limits.pricingBoundary);
3169
3174
  return {
3170
3175
  stop,
3171
3176
  raw,
3172
3177
  effective,
3173
- ...requested > ceiling ? { clampedFrom: requested } : {},
3178
+ ...ceiling !== void 0 && requested > ceiling ? { clampedFrom: requested } : {},
3174
3179
  crossesPricingBoundary: boundary !== void 0 && effective > boundary
3175
3180
  };
3176
3181
  }
@@ -5208,6 +5213,52 @@ var opencode_go_models_default = [
5208
5213
  upstreamModelId: "minimax-m3",
5209
5214
  family: "minimax"
5210
5215
  },
5216
+ {
5217
+ id: "muse-spark-1.2-contributor",
5218
+ name: "Muse Spark 1.2 Contributor",
5219
+ contextWindow: 1048576,
5220
+ cost: {
5221
+ input: 0.1,
5222
+ output: 0.2,
5223
+ cache_read: 2e-3
5224
+ },
5225
+ modelFormat: "openai",
5226
+ npm: "@ai-sdk/openai",
5227
+ apiUrl: "https://opencode.ai/zen/go/v1",
5228
+ reasoning: true,
5229
+ modalities: [
5230
+ "text",
5231
+ "image"
5232
+ ],
5233
+ compatibility: {
5234
+ supportsReasoningEffort: false
5235
+ },
5236
+ upstreamModelId: "muse-spark-1.2-contributor",
5237
+ family: "muse"
5238
+ },
5239
+ {
5240
+ id: "muse-spark-1.3-contributor",
5241
+ name: "Muse Spark 1.3 Contributor",
5242
+ contextWindow: 1048576,
5243
+ cost: {
5244
+ input: 0.1,
5245
+ output: 0.2,
5246
+ cache_read: 2e-3
5247
+ },
5248
+ modelFormat: "openai",
5249
+ npm: "@ai-sdk/openai",
5250
+ apiUrl: "https://opencode.ai/zen/go/v1",
5251
+ reasoning: true,
5252
+ modalities: [
5253
+ "text",
5254
+ "image"
5255
+ ],
5256
+ compatibility: {
5257
+ supportsReasoningEffort: false
5258
+ },
5259
+ upstreamModelId: "muse-spark-1.3-contributor",
5260
+ family: "muse"
5261
+ },
5211
5262
  {
5212
5263
  id: "qwen3.6-plus",
5213
5264
  name: "Qwen3.6 Plus",
@@ -5471,6 +5522,10 @@ function openCodeGoPinnedApiUrl(npm) {
5471
5522
  if (npm === "@ai-sdk/anthropic") return OPENCODE_GO_ANTHROPIC_BASE_URL;
5472
5523
  return null;
5473
5524
  }
5525
+ function openCodeGoPinnedModelApiUrl(npm) {
5526
+ if (npm === "@ai-sdk/openai") return OPENCODE_GO_COMPLETIONS_BASE_URL;
5527
+ return openCodeGoPinnedApiUrl(npm);
5528
+ }
5474
5529
  function isProviderConfiguredForTemplate(provider, templateId) {
5475
5530
  if (provider.id === templateId) return true;
5476
5531
  return templateId === OPENCODE_GO_PROVIDER_ID && isRetainedOpenCodeGoProvider(provider);
@@ -5925,21 +5980,22 @@ function deriveBrand(family) {
5925
5980
  }
5926
5981
 
5927
5982
  // src/data/openai-oauth-models.ts
5928
- var GPT_5_6_PRICING_BOUNDARY = 272e3;
5929
- var GPT_5_6_PRICING_NOTE = "Above it, OpenAI prices the full request at 2x input and 1.5x output.";
5983
+ var GPT_HIGH_CONTEXT_PRICING_BOUNDARY = 272e3;
5984
+ var GPT_HIGH_CONTEXT_PRICING_NOTE = "Above it, OpenAI prices the full request at 2x input and 1.5x output.";
5930
5985
  var CHATGPT_CODEX_UNSUPPORTED_MODELS = /* @__PURE__ */ new Set([
5931
5986
  "gpt-5.5-fast"
5932
5987
  // confirmed: rejected by chatgpt.com/backend-api/codex
5933
5988
  ]);
5934
5989
  var OPENAI_OAUTH_MODEL_SEEDS = [
5935
- // GPT-6 family. The window and ceiling are what the live Codex catalog returned on
5936
- // 2026-09-04 and are deliberately NOT the published API numbers: the model card
5937
- // lists a 1,050,000 context window, but the Codex client is served a smaller one,
5938
- // and this path is Codex-only. Output limit and the pricing band come from the
5939
- // card (https://developers.openai.com/api/docs/models/gpt-6-astra), which states
5940
- // "Prompts with more than 272K input tokens are priced at 2x input and cache rates
5941
- // and 1.5x output for the full request" — the same boundary the GPT-5.6 family has.
5990
+ // GPT-6 family. The window, ceiling and Responses-Lite flags are what the live
5991
+ // Codex catalog returned (Astra on 2026-09-04, Sol and Luna on 2026-09-22) and are
5992
+ // deliberately NOT the published API numbers: the model cards list a 1,050,000
5993
+ // context window, but the Codex client is served a smaller one, and this path is
5994
+ // Codex-only. All three cards publish a 128,000 output limit and the same 272K
5995
+ // pricing boundary as the GPT-5.6 family.
5942
5996
  { id: "gpt-6-astra", name: "GPT-6 Astra", contextWindow: 272e3, maxContextWindow: 872e3, maxOutputTokens: 128e3, reasoning: true, useResponsesLite: true, preferWebSockets: true },
5997
+ { id: "gpt-6-sol", name: "GPT-6 Sol", contextWindow: 272e3, maxContextWindow: 872e3, maxOutputTokens: 128e3, reasoning: true, useResponsesLite: true, preferWebSockets: true },
5998
+ { id: "gpt-6-luna", name: "GPT-6 Luna", contextWindow: 272e3, maxContextWindow: 872e3, maxOutputTokens: 128e3, reasoning: true, useResponsesLite: true, preferWebSockets: true },
5943
5999
  // "An alias for our flagship general-purpose models, with safeguards calibrated
5944
6000
  // for defensive cybersecurity work" — access is gated on a separate opt-in
5945
6001
  // program, so most installs will never see this id in their catalog.
@@ -5957,10 +6013,10 @@ var OPENAI_OAUTH_MODEL_SEEDS = [
5957
6013
  { id: "gpt-5", name: "GPT-5", contextWindow: 272e3, reasoning: true },
5958
6014
  // o-series reasoning (Plus+)
5959
6015
  { id: "o4-mini", name: "o4 Mini", reasoning: true },
5960
- { id: "o3", name: "o3", reasoning: true },
6016
+ { id: "o3", name: "o3", contextWindow: 2e5, reasoning: true },
5961
6017
  { id: "o3-mini", name: "o3 Mini", reasoning: true },
5962
- { id: "o1", name: "o1", reasoning: true },
5963
- { id: "o1-mini", name: "o1 Mini", reasoning: true }
6018
+ { id: "o1", name: "o1", contextWindow: 2e5, reasoning: true },
6019
+ { id: "o1-mini", name: "o1 Mini", contextWindow: 128e3, reasoning: true }
5964
6020
  ];
5965
6021
  function hasPricingBoundary(id) {
5966
6022
  const version = /^gpt-(\d+)(?:\.(\d+))?(?:-|$)/i.exec(id);
@@ -5974,8 +6030,8 @@ function hasPricingBoundary(id) {
5974
6030
  function openAiPricingMetadata(id) {
5975
6031
  if (!hasPricingBoundary(id)) return {};
5976
6032
  return {
5977
- pricingBoundary: GPT_5_6_PRICING_BOUNDARY,
5978
- pricingBoundaryNote: GPT_5_6_PRICING_NOTE
6033
+ pricingBoundary: GPT_HIGH_CONTEXT_PRICING_BOUNDARY,
6034
+ pricingBoundaryNote: GPT_HIGH_CONTEXT_PRICING_NOTE
5979
6035
  };
5980
6036
  }
5981
6037
  var LEGACY_IMPOSED_CONTEXT_PERCENT = 95;
@@ -6008,7 +6064,7 @@ function buildOpenAiOAuthModels() {
6008
6064
  upstreamModelId: seed.id,
6009
6065
  family: prefix,
6010
6066
  brand: deriveBrand(prefix),
6011
- contextWindow: resolveContextWindow(seed.id, seed.contextWindow),
6067
+ contextWindow: seed.contextWindow ?? lookupKnownContextWindow(seed.id),
6012
6068
  maxContextWindow: seed.maxContextWindow,
6013
6069
  effectiveContextPercent: seed.effectiveContextPercent,
6014
6070
  pricingBoundary: seed.pricingBoundary ?? pricing.pricingBoundary,
@@ -6524,7 +6580,9 @@ function parseModelList(body, npm, allowBareArray) {
6524
6580
  const freeStatus = classifyFreeStatus({
6525
6581
  model: { cost, isFree: row.isFree }
6526
6582
  });
6527
- const contextWindow = row.context_length ?? row.contextWindow ?? row.context_window ?? row.max_model_len ?? resolveContextWindow(id);
6583
+ const contextWindow = row.context_length ?? row.contextWindow ?? row.context_window ?? row.max_model_len ?? // Left undefined when nothing is known: persisting the invented 200,000
6584
+ // would make it this model's permanent ceiling.
6585
+ lookupKnownContextWindow(id);
6528
6586
  models.push({
6529
6587
  id,
6530
6588
  name: normalizeGoogleDisplayName(row.name, id),
@@ -6556,7 +6614,7 @@ function materializeTemplateModel(template, model, baseUrl) {
6556
6614
  upstreamModelId: model.upstreamModelId ?? normalizedUpstream,
6557
6615
  family,
6558
6616
  brand: model.brand ?? deriveBrand(family),
6559
- contextWindow: model.contextWindow ?? resolveContextWindow(id),
6617
+ contextWindow: model.contextWindow ?? lookupKnownContextWindow(id),
6560
6618
  isFree: model.isFree ?? isFreeStatus(freeStatus),
6561
6619
  freeStatus,
6562
6620
  modelFormat: model.modelFormat ?? modelFormatForNpm(npm),
@@ -6758,10 +6816,10 @@ function resolveMaterializedApiUrl(cached, provider, npm) {
6758
6816
  if (!isRetainedOpenCodeGoProvider(provider)) {
6759
6817
  return cached.apiUrl ?? provider.api.url ?? "";
6760
6818
  }
6761
- return openCodeGoPinnedApiUrl(npm);
6819
+ return openCodeGoPinnedModelApiUrl(npm);
6762
6820
  }
6763
6821
  function projectContextStop(cached, providerId, modelId) {
6764
- const limits = contextLimitsFrom(cached, resolveContextWindow(modelId));
6822
+ const limits = contextLimitsFrom(cached, lookupKnownContextWindow(modelId));
6765
6823
  const stop = selectContextStop(providerId, modelId);
6766
6824
  const resolved = resolveContextStop(limits, stop);
6767
6825
  return {
@@ -7893,7 +7951,7 @@ function outboundHttpProxyAgent(targetUrl, env = process.env) {
7893
7951
  }
7894
7952
  return new HttpsProxyAgent(parsedProxy, { keepAlive: true });
7895
7953
  } catch (err) {
7896
- console.error(
7954
+ emitParentNotice(
7897
7955
  `clodex: HTTP(S)_PROXY cannot be used for a CONNECT tunnel; using a direct connection (${err instanceof Error ? err.message : String(err)})`
7898
7956
  );
7899
7957
  return void 0;
@@ -10777,7 +10835,7 @@ async function createLanguageModel(spec) {
10777
10835
  apiKey: "",
10778
10836
  ...spec.headers ? { headers: spec.headers } : {},
10779
10837
  fetch: fetchWithoutCredentialHeaders
10780
- } : { apiKey, ...spec.headers ? { headers: spec.headers } : {} };
10838
+ } : { apiKey, ...baseURL ? { baseURL } : {}, ...spec.headers ? { headers: spec.headers } : {} };
10781
10839
  const openai = createOpenAI(oauthOptions);
10782
10840
  return useResponsesEndpoint ? openai.responses(modelId) : openai.chat(modelId);
10783
10841
  }
@@ -11024,7 +11082,7 @@ function supportsExtendedCodexEffort(modelId) {
11024
11082
  return /^gpt-daybreak(?:-|$)/i.test(modelId);
11025
11083
  }
11026
11084
  function supportsNoneEffort(modelId) {
11027
- return /^gpt-5\.6(?:-|$)/i.test(modelId) || /^gpt-daybreak-blue(?:-|$)/i.test(modelId);
11085
+ return /^gpt-5\.6(?:-|$)/i.test(modelId) || /^gpt-6-(?:sol|luna)(?:-|$)/i.test(modelId) || /^gpt-daybreak-blue(?:-|$)/i.test(modelId);
11028
11086
  }
11029
11087
  function isReasoningSummaryUnsupportedModel(modelId) {
11030
11088
  return /codex-spark(?:-|$)/i.test(modelId);
@@ -14109,7 +14167,7 @@ function buildDesiredPatchConfig(options = {}) {
14109
14167
  compatibility: model.compatibility,
14110
14168
  upstreamModelId: upstreamModelId2
14111
14169
  });
14112
- const limits = contextLimitsFrom(model, resolveContextWindow(model.id));
14170
+ const limits = contextLimitsFrom(model, lookupKnownContextWindow(model.id));
14113
14171
  const stop = resolveContextStop(
14114
14172
  limits,
14115
14173
  options.sessionStops ? selectContextStop(provider.id, model.id, prefs.modelContextModes) : readContextStop(prefs.modelContextModes, provider.id, model.id) ?? "standard"
@@ -14678,7 +14736,9 @@ async function runPatchCommand(opts = {}) {
14678
14736
  return 1;
14679
14737
  }
14680
14738
  for (const id of desired.unknownWindows) {
14681
- p2.log.warn(`No context window metadata for ${id} \u2014 Claude Code will assume the 200k default.`);
14739
+ p2.log.warn(
14740
+ `No context window metadata for ${id} \u2014 Claude Code will assume the 200k default. Refresh the provider's models, then set a window with \`clodex models --context <model=stop> --save\`, then re-run \`clodex patch\`.`
14741
+ );
14682
14742
  }
14683
14743
  reportRejectedModelAliases(desired.rejectedAliasRejections);
14684
14744
  const localPatches = inspectLocalPatchSource(
@@ -15781,7 +15841,7 @@ async function fetchAnthropicModels(baseUrl, apiKey, extraHeaders) {
15781
15841
  upstreamModelId: id,
15782
15842
  family: id.split("-")[0] ?? id,
15783
15843
  brand: deriveBrand(id),
15784
- contextWindow: resolveContextWindow(id),
15844
+ contextWindow: lookupKnownContextWindow(id),
15785
15845
  modelFormat: "anthropic",
15786
15846
  npm: "@ai-sdk/anthropic",
15787
15847
  apiUrl: root
@@ -16328,7 +16388,7 @@ function buildDynamicOAuthModel(entry, seedById, codexCatalog) {
16328
16388
  upstreamModelId: id,
16329
16389
  family: prefix,
16330
16390
  brand: deriveBrand(prefix),
16331
- contextWindow: entry.context_window ?? resolveContextWindow(id),
16391
+ contextWindow: entry.context_window ?? lookupKnownContextWindow(id),
16332
16392
  maxContextWindow: entry.max_context_window,
16333
16393
  // Absent means no reduction. clodex reports the window the provider actually
16334
16394
  // gives; deciding how much of it to leave free is the client's job, and Claude
@@ -20433,6 +20493,14 @@ async function startHttpProxy(options) {
20433
20493
  );
20434
20494
  });
20435
20495
  const sockets = /* @__PURE__ */ new Set();
20496
+ let warnedConnectSelfProxy = false;
20497
+ const connectTunnelAgents = /* @__PURE__ */ new Map();
20498
+ const connectTunnelAgent = (proxyUrl, targetUrl) => {
20499
+ if (!connectTunnelAgents.has(proxyUrl)) {
20500
+ connectTunnelAgents.set(proxyUrl, outboundHttpProxyAgent(targetUrl));
20501
+ }
20502
+ return connectTunnelAgents.get(proxyUrl);
20503
+ };
20436
20504
  mitmServer.on("upgrade", (req, socket, head) => {
20437
20505
  forwardAnthropicUpgrade(
20438
20506
  req,
@@ -20462,6 +20530,65 @@ async function startHttpProxy(options) {
20462
20530
  clientSocket.end("HTTP/1.1 400 Bad Request\r\n\r\n");
20463
20531
  return;
20464
20532
  }
20533
+ const targetUrl = `https://${req.url}`;
20534
+ const outboundProxyUrl = outboundProxyUrlForTarget(targetUrl);
20535
+ const bound = proxyServer.address();
20536
+ const selfTargeting = outboundProxyUrl !== void 0 && bound !== null && typeof bound !== "string" && proxyUrlTargetsListener(outboundProxyUrl, bound.address, bound.port);
20537
+ if (selfTargeting && !warnedConnectSelfProxy) {
20538
+ warnedConnectSelfProxy = true;
20539
+ emitParentNotice(
20540
+ "clodex: HTTP(S)_PROXY points at this proxy; tunneling CONNECT direct"
20541
+ );
20542
+ }
20543
+ const outboundAgent = outboundProxyUrl !== void 0 && !selfTargeting ? connectTunnelAgent(outboundProxyUrl, targetUrl) : void 0;
20544
+ if (outboundAgent) {
20545
+ let upstream2;
20546
+ let proxyConnectStatus;
20547
+ req.once("proxyConnect", (response) => {
20548
+ proxyConnectStatus = response.statusCode;
20549
+ });
20550
+ clientSocket.once("close", () => {
20551
+ if (upstream2 && !upstream2.destroyed) upstream2.destroy();
20552
+ });
20553
+ void outboundAgent.connect(req, {
20554
+ host: target.host,
20555
+ port: target.port,
20556
+ secureEndpoint: false
20557
+ }).then((connected) => {
20558
+ upstream2 = connected;
20559
+ if (proxyConnectStatus !== 200 || clientSocket.destroyed) {
20560
+ connected.destroy();
20561
+ if (!clientSocket.destroyed) {
20562
+ clientSocket.end("HTTP/1.1 502 Bad Gateway\r\n\r\n", () => clientSocket.destroy());
20563
+ }
20564
+ return;
20565
+ }
20566
+ let tunnelEstablished2 = false;
20567
+ sockets.add(connected);
20568
+ connected.once("close", () => {
20569
+ sockets.delete(connected);
20570
+ if (tunnelEstablished2 && !clientSocket.destroyed) clientSocket.destroy();
20571
+ });
20572
+ connected.once("error", () => {
20573
+ if (clientSocket.destroyed) return;
20574
+ if (tunnelEstablished2) {
20575
+ clientSocket.destroy();
20576
+ return;
20577
+ }
20578
+ clientSocket.end("HTTP/1.1 502 Bad Gateway\r\n\r\n", () => clientSocket.destroy());
20579
+ });
20580
+ tunnelEstablished2 = true;
20581
+ clientSocket.write("HTTP/1.1 200 Connection Established\r\n\r\n");
20582
+ if (head.length > 0) connected.write(head);
20583
+ clientSocket.pipe(connected);
20584
+ connected.pipe(clientSocket);
20585
+ }).catch(() => {
20586
+ if (!clientSocket.destroyed) {
20587
+ clientSocket.end("HTTP/1.1 502 Bad Gateway\r\n\r\n", () => clientSocket.destroy());
20588
+ }
20589
+ });
20590
+ return;
20591
+ }
20465
20592
  const upstream = net.connect(target.port, target.host);
20466
20593
  let tunnelEstablished = false;
20467
20594
  sockets.add(upstream);
@@ -20527,6 +20654,8 @@ async function startHttpProxy(options) {
20527
20654
  for (const socket of sockets) socket.destroy();
20528
20655
  await new Promise((resolve4) => proxyServer.close(() => resolve4()));
20529
20656
  mitmServer.close();
20657
+ for (const agent of connectTunnelAgents.values()) agent?.destroy();
20658
+ connectTunnelAgents.clear();
20530
20659
  anthropicAgent?.destroy();
20531
20660
  adapter?.close();
20532
20661
  }
@@ -21965,7 +22094,7 @@ ${pc13.bold("Examples:")}
21965
22094
  clodex claude --trace --resume abc-123
21966
22095
  clodex claude --endpoint
21967
22096
  clodex claude --endpoint --save-mode
21968
- clodex claude --provider openai-oauth --model gpt-5.6-sol
22097
+ clodex claude --provider openai-oauth --model gpt-6-sol
21969
22098
  clodex claude -- --print "hello"
21970
22099
  clodex claude -- --dangerously-skip-permissions`;
21971
22100
  }
@@ -22053,7 +22182,7 @@ Manage favorite models for mid-session switching.
22053
22182
  ${pc13.bold("Usage:")}
22054
22183
  clodex favorites
22055
22184
  clodex models --list
22056
- clodex models --alias sol=clodex:openai-oauth:gpt-5.6-sol
22185
+ clodex models --alias sol=clodex:openai-oauth:gpt-6-sol
22057
22186
  clodex models --unalias sol
22058
22187
  clodex models --context sol=max --save
22059
22188
  clodex models --json
@@ -22086,13 +22215,13 @@ ${pc13.bold("How it works:")}
22086
22215
  Claude Code binary so they pass model validation and report real context.
22087
22216
 
22088
22217
  ${pc13.bold("Context stops and cost:")}
22089
- Some providers price large prompts in bands: GPT-5.6 bills the full request at
22218
+ Some providers price large prompts in bands: GPT-5.6 and GPT-6 bill the full request at
22090
22219
  a higher rate above 272,000 input tokens. The standard stop stays under that
22091
22220
  line, so max is opt-in and reports the boundary when you select it.
22092
22221
 
22093
22222
  ${pc13.bold("Examples:")}
22094
22223
  clodex favorites
22095
- clodex models --alias sol=clodex:openai-oauth:gpt-5.6-sol
22224
+ clodex models --alias sol=clodex:openai-oauth:gpt-6-sol
22096
22225
  clodex models --context sol=max --save
22097
22226
  clodex claude # switch menu active when favorites are set`;
22098
22227
  }
@@ -22242,7 +22371,7 @@ function applyContextStopAssignments(assignments, save, report, saveHint = "Add
22242
22371
  );
22243
22372
  return 1;
22244
22373
  }
22245
- const limits = contextLimitsFrom(cached, resolveContextWindow(assignment.modelId));
22374
+ const limits = contextLimitsFrom(cached, lookupKnownContextWindow(assignment.modelId));
22246
22375
  const resolved = resolveContextStop(limits, assignment.stop ?? "standard");
22247
22376
  const target = modelAliasTarget(assignment);
22248
22377
  const label = assignment.label === target ? target : `${assignment.label} (${target})`;