@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 +16 -6
- package/dist/cli.js +173 -44
- package/dist/cli.js.map +1 -1
- package/docs/opencode-go.md +3 -2
- package/package.json +1 -1
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-
|
|
26
|
-
clodex models --alias luna=clodex:openai-oauth:gpt-
|
|
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-
|
|
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-
|
|
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
|
|
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.
|
|
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.
|
|
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
|
|
698
|
-
|
|
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,
|
|
707
|
-
return
|
|
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
|
|
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
|
|
3164
|
-
const
|
|
3165
|
-
const
|
|
3166
|
-
const
|
|
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
|
|
5929
|
-
var
|
|
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
|
|
5936
|
-
// 2026-09-04 and
|
|
5937
|
-
//
|
|
5938
|
-
//
|
|
5939
|
-
//
|
|
5940
|
-
//
|
|
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:
|
|
5978
|
-
pricingBoundaryNote:
|
|
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:
|
|
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 ??
|
|
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 ??
|
|
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
|
|
6819
|
+
return openCodeGoPinnedModelApiUrl(npm);
|
|
6762
6820
|
}
|
|
6763
6821
|
function projectContextStop(cached, providerId, modelId) {
|
|
6764
|
-
const limits = contextLimitsFrom(cached,
|
|
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
|
-
|
|
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,
|
|
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(
|
|
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:
|
|
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 ??
|
|
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-
|
|
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-
|
|
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
|
|
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-
|
|
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,
|
|
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})`;
|