@bman654/clodex 2.9.0 → 2.11.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
@@ -382,7 +382,7 @@ import { join } from "path";
382
382
  // package.json
383
383
  var package_default = {
384
384
  name: "@bman654/clodex",
385
- version: "2.9.0",
385
+ version: "2.11.0",
386
386
  publishConfig: {
387
387
  access: "public"
388
388
  },
@@ -469,7 +469,7 @@ var package_default = {
469
469
 
470
470
  // src/constants.ts
471
471
  var CODEX_RESPONSES_LITE_WS_URL = "wss://chatgpt.com/backend-api/codex/responses";
472
- var CODEX_RESPONSES_LITE_VERSION = "0.144.1";
472
+ var CODEX_RESPONSES_LITE_VERSION = "0.153.3";
473
473
  var CODEX_RESPONSES_WEBSOCKETS_BETA = "responses_websockets=2026-02-06";
474
474
  var TEST_TIMEOUT_MS = 1e4;
475
475
  var CONFLICTING_ENV_VARS = [
@@ -5873,6 +5873,18 @@ var CHATGPT_CODEX_UNSUPPORTED_MODELS = /* @__PURE__ */ new Set([
5873
5873
  // confirmed: rejected by chatgpt.com/backend-api/codex
5874
5874
  ]);
5875
5875
  var OPENAI_OAUTH_MODEL_SEEDS = [
5876
+ // GPT-6 family. The window and ceiling are what the live Codex catalog returned on
5877
+ // 2026-09-04 and are deliberately NOT the published API numbers: the model card
5878
+ // lists a 1,050,000 context window, but the Codex client is served a smaller one,
5879
+ // and this path is Codex-only. Output limit and the pricing band come from the
5880
+ // card (https://developers.openai.com/api/docs/models/gpt-6-astra), which states
5881
+ // "Prompts with more than 272K input tokens are priced at 2x input and cache rates
5882
+ // and 1.5x output for the full request" — the same boundary the GPT-5.6 family has.
5883
+ { id: "gpt-6-astra", name: "GPT-6 Astra", contextWindow: 272e3, maxContextWindow: 872e3, maxOutputTokens: 128e3, reasoning: true, useResponsesLite: true, preferWebSockets: true },
5884
+ // "An alias for our flagship general-purpose models, with safeguards calibrated
5885
+ // for defensive cybersecurity work" — access is gated on a separate opt-in
5886
+ // program, so most installs will never see this id in their catalog.
5887
+ { id: "gpt-daybreak-blue-latest", name: "GPT Daybreak Blue", contextWindow: 272e3, maxContextWindow: 872e3, maxOutputTokens: 128e3, reasoning: true, useResponsesLite: true, preferWebSockets: true },
5876
5888
  // GPT-5.6 family (Sol / Terra / Luna)
5877
5889
  { id: "gpt-5.6-sol", name: "GPT-5.6 Sol", contextWindow: 272e3, maxContextWindow: 872e3, maxOutputTokens: 128e3, reasoning: true },
5878
5890
  { id: "gpt-5.6-terra", name: "GPT-5.6 Terra", contextWindow: 272e3, maxContextWindow: 872e3, maxOutputTokens: 128e3, reasoning: true },
@@ -5891,9 +5903,17 @@ var OPENAI_OAUTH_MODEL_SEEDS = [
5891
5903
  { id: "o1", name: "o1", reasoning: true },
5892
5904
  { id: "o1-mini", name: "o1 Mini", reasoning: true }
5893
5905
  ];
5894
- var PRICING_BOUNDARY_FAMILIES = /^gpt-5\.[56]/;
5906
+ function hasPricingBoundary(id) {
5907
+ const version = /^gpt-(\d+)(?:\.(\d+))?(?:-|$)/i.exec(id);
5908
+ if (version) {
5909
+ const major = Number(version[1]);
5910
+ const minor = version[2] ? Number(version[2]) : 0;
5911
+ if (major > 5 || major === 5 && minor >= 5) return true;
5912
+ }
5913
+ return /^gpt-daybreak(?:-|$)/i.test(id);
5914
+ }
5895
5915
  function openAiPricingMetadata(id) {
5896
- if (!PRICING_BOUNDARY_FAMILIES.test(id)) return {};
5916
+ if (!hasPricingBoundary(id)) return {};
5897
5917
  return {
5898
5918
  pricingBoundary: GPT_5_6_PRICING_BOUNDARY,
5899
5919
  pricingBoundaryNote: GPT_5_6_PRICING_NOTE
@@ -5910,7 +5930,8 @@ function applyOAuthSeedContextMetadata(models) {
5910
5930
  effectiveContextPercent: model.effectiveContextPercent ?? seed?.effectiveContextPercent ?? DEFAULT_EFFECTIVE_CONTEXT_PERCENT,
5911
5931
  pricingBoundary: model.pricingBoundary ?? seed?.pricingBoundary ?? pricing.pricingBoundary,
5912
5932
  pricingBoundaryNote: model.pricingBoundaryNote ?? seed?.pricingBoundaryNote ?? pricing.pricingBoundaryNote,
5913
- maxOutputTokens: model.maxOutputTokens ?? seed?.maxOutputTokens
5933
+ maxOutputTokens: model.maxOutputTokens ?? seed?.maxOutputTokens,
5934
+ reasoning: seed?.reasoning ?? model.reasoning
5914
5935
  };
5915
5936
  });
5916
5937
  }
@@ -8009,6 +8030,369 @@ function sanitizeToolInput(input, requiredProps) {
8009
8030
  return out;
8010
8031
  }
8011
8032
 
8033
+ // src/upstream-retry.ts
8034
+ var UPSTREAM_MAX_RETRIES_ENV = "CLODEX_UPSTREAM_MAX_RETRIES";
8035
+ var UPSTREAM_IDLE_TIMEOUT_ENV = "CLODEX_UPSTREAM_IDLE_TIMEOUT_MS";
8036
+ var UPSTREAM_TOTAL_TIMEOUT_ENV = "CLODEX_UPSTREAM_TOTAL_TIMEOUT_MS";
8037
+ var DEFAULT_UPSTREAM_IDLE_TIMEOUT_MS = 12e4;
8038
+ var DEFAULT_UPSTREAM_TOTAL_TIMEOUT_MS = 10 * 6e4;
8039
+ var MIN_UPSTREAM_IDLE_TIMEOUT_MS = 1e4;
8040
+ var MAX_UPSTREAM_IDLE_TIMEOUT_MS = 60 * 6e4;
8041
+ var MIN_UPSTREAM_TOTAL_TIMEOUT_MS = 6e4;
8042
+ var MAX_UPSTREAM_TOTAL_TIMEOUT_MS = 6 * 60 * 6e4;
8043
+ var SDK_INITIAL_RETRY_DELAY_MS = 2e3;
8044
+ var reportedValues = /* @__PURE__ */ new Set();
8045
+ var defaultWarn = (message) => emitParentNotice(`clodex: ${message}`);
8046
+ function reportOnce(key, message, warn) {
8047
+ if (reportedValues.has(key)) return;
8048
+ reportedValues.add(key);
8049
+ try {
8050
+ warn(message);
8051
+ } catch {
8052
+ }
8053
+ }
8054
+ function timeoutSetting(env, envName, fallback, min, max, warn) {
8055
+ const raw = env[envName]?.trim();
8056
+ if (raw === void 0 || raw === "") return { value: fallback, explicit: false };
8057
+ const value = Number(raw);
8058
+ if (!Number.isInteger(value) || value <= 0) {
8059
+ reportOnce(
8060
+ `${envName}=${raw}`,
8061
+ `ignoring ${envName}=${raw} (expected a positive integer number of milliseconds)`,
8062
+ warn
8063
+ );
8064
+ return { value: fallback, explicit: false };
8065
+ }
8066
+ if (value < min || value > max) {
8067
+ const clamped = Math.min(max, Math.max(min, value));
8068
+ reportOnce(
8069
+ `${envName}=${raw}`,
8070
+ `clamping ${envName}=${raw} to ${clamped}ms (supported range is ${min}-${max}ms)`,
8071
+ warn
8072
+ );
8073
+ return { value: clamped, explicit: true };
8074
+ }
8075
+ return { value, explicit: true };
8076
+ }
8077
+ function maxRetriesForIdleTimeout(idleTimeoutMs) {
8078
+ let retries = 0;
8079
+ let elapsedMs = 0;
8080
+ let delayMs = SDK_INITIAL_RETRY_DELAY_MS;
8081
+ while (elapsedMs + delayMs < idleTimeoutMs) {
8082
+ elapsedMs += delayMs;
8083
+ delayMs *= 2;
8084
+ retries += 1;
8085
+ }
8086
+ return retries;
8087
+ }
8088
+ var MAX_UPSTREAM_MAX_RETRIES = maxRetriesForIdleTimeout(
8089
+ DEFAULT_UPSTREAM_IDLE_TIMEOUT_MS
8090
+ );
8091
+ var DEFAULT_UPSTREAM_MAX_RETRIES = MAX_UPSTREAM_MAX_RETRIES;
8092
+ function configuredUpstreamMaxRetries(env, warn) {
8093
+ const raw = env[UPSTREAM_MAX_RETRIES_ENV]?.trim();
8094
+ if (raw === void 0 || raw === "") return void 0;
8095
+ const value = Number(raw);
8096
+ if (!Number.isInteger(value) || value < 0) {
8097
+ reportOnce(
8098
+ `${UPSTREAM_MAX_RETRIES_ENV}=${raw}`,
8099
+ `ignoring ${UPSTREAM_MAX_RETRIES_ENV}=${raw} (expected a non-negative integer)`,
8100
+ warn
8101
+ );
8102
+ return void 0;
8103
+ }
8104
+ return value;
8105
+ }
8106
+ function resolveUpstreamMaxRetries(env, warn, idleTimeoutMs) {
8107
+ const ceiling = maxRetriesForIdleTimeout(idleTimeoutMs);
8108
+ const defaultRetries = Math.min(DEFAULT_UPSTREAM_MAX_RETRIES, ceiling);
8109
+ const value = configuredUpstreamMaxRetries(env, warn);
8110
+ if (value === void 0) return defaultRetries;
8111
+ if (value > ceiling) {
8112
+ reportOnce(
8113
+ `${UPSTREAM_MAX_RETRIES_ENV}=${value}:idle=${idleTimeoutMs}`,
8114
+ `clamping ${UPSTREAM_MAX_RETRIES_ENV}=${value} to ${ceiling} (estimated from the SDK fallback backoff and resolved ${idleTimeoutMs}ms idle timeout; provider delays may allow fewer retries)`,
8115
+ warn
8116
+ );
8117
+ return ceiling;
8118
+ }
8119
+ return value;
8120
+ }
8121
+ function upstreamRequestBudget(options = {}) {
8122
+ const env = options.env ?? process.env;
8123
+ const warn = options.warn ?? defaultWarn;
8124
+ const hasIdleOverride = options.idleTimeoutMs !== void 0;
8125
+ const configuredIdle = options.idleTimeoutMs !== void 0 ? { value: options.idleTimeoutMs, explicit: false } : timeoutSetting(
8126
+ env,
8127
+ UPSTREAM_IDLE_TIMEOUT_ENV,
8128
+ DEFAULT_UPSTREAM_IDLE_TIMEOUT_MS,
8129
+ MIN_UPSTREAM_IDLE_TIMEOUT_MS,
8130
+ MAX_UPSTREAM_IDLE_TIMEOUT_MS,
8131
+ warn
8132
+ );
8133
+ const configuredTotal = timeoutSetting(
8134
+ env,
8135
+ UPSTREAM_TOTAL_TIMEOUT_ENV,
8136
+ DEFAULT_UPSTREAM_TOTAL_TIMEOUT_MS,
8137
+ MIN_UPSTREAM_TOTAL_TIMEOUT_MS,
8138
+ MAX_UPSTREAM_TOTAL_TIMEOUT_MS,
8139
+ warn
8140
+ );
8141
+ let idleTimeoutMs = configuredIdle.value;
8142
+ let totalTimeoutMs = configuredTotal.value;
8143
+ if (totalTimeoutMs < idleTimeoutMs) {
8144
+ if (hasIdleOverride) {
8145
+ idleTimeoutMs = totalTimeoutMs;
8146
+ } else if (configuredIdle.explicit && !configuredTotal.explicit) {
8147
+ reportOnce(
8148
+ `timeout-pair:raise-total:${idleTimeoutMs}:${totalTimeoutMs}`,
8149
+ `raising the resolved total timeout from ${totalTimeoutMs}ms to ${idleTimeoutMs}ms so it is not shorter than ${UPSTREAM_IDLE_TIMEOUT_ENV}`,
8150
+ warn
8151
+ );
8152
+ totalTimeoutMs = idleTimeoutMs;
8153
+ } else {
8154
+ reportOnce(
8155
+ `timeout-pair:lower-idle:${idleTimeoutMs}:${totalTimeoutMs}`,
8156
+ `lowering the resolved idle timeout from ${idleTimeoutMs}ms to ${totalTimeoutMs}ms because it cannot exceed ${UPSTREAM_TOTAL_TIMEOUT_ENV}`,
8157
+ warn
8158
+ );
8159
+ idleTimeoutMs = totalTimeoutMs;
8160
+ }
8161
+ }
8162
+ return {
8163
+ idleTimeoutMs,
8164
+ totalTimeoutMs,
8165
+ maxRetries: resolveUpstreamMaxRetries(env, warn, idleTimeoutMs)
8166
+ };
8167
+ }
8168
+ var CLIENT_MAX_RETRIES_ENV = "CLAUDE_CODE_MAX_RETRIES";
8169
+ var DEFAULT_PASSTHROUGH_RETRIES = 1;
8170
+ function passthroughUpstreamRetries(env = process.env, warn = defaultWarn) {
8171
+ const explicit = configuredUpstreamMaxRetries(env, warn);
8172
+ if (explicit !== void 0) {
8173
+ if (explicit <= MAX_UPSTREAM_MAX_RETRIES) return explicit;
8174
+ reportOnce(
8175
+ `${UPSTREAM_MAX_RETRIES_ENV}=${explicit}:passthrough`,
8176
+ `clamping ${UPSTREAM_MAX_RETRIES_ENV}=${explicit} to ${MAX_UPSTREAM_MAX_RETRIES} (the raw HTTP MITM path supports at most this many replays)`,
8177
+ warn
8178
+ );
8179
+ return MAX_UPSTREAM_MAX_RETRIES;
8180
+ }
8181
+ const raw = env[CLIENT_MAX_RETRIES_ENV]?.trim();
8182
+ if (raw !== void 0 && raw !== "") {
8183
+ const clientRetries = Number(raw);
8184
+ if (Number.isFinite(clientRetries) && clientRetries === 0) return 0;
8185
+ }
8186
+ return DEFAULT_PASSTHROUGH_RETRIES;
8187
+ }
8188
+
8189
+ // src/oauth/ws-upgrade-pacer.ts
8190
+ var WS_NEW_CONNECTIONS_PER_MIN_ENV = "CLODEX_WS_MAX_NEW_CONNECTIONS_PER_MIN";
8191
+ var DEFAULT_WS_NEW_CONNECTIONS_PER_MIN = 60;
8192
+ var MAX_WS_NEW_CONNECTIONS_PER_MIN = 600;
8193
+ var WS_NEW_CONNECTION_BURST = 10;
8194
+ var WS_NEW_CONNECTION_MAX_WAIT_CEILING_MS = 15e3;
8195
+ function resolvedPacingBudget() {
8196
+ const { idleTimeoutMs, maxRetries } = upstreamRequestBudget();
8197
+ return { idleTimeoutMs, maxRetries };
8198
+ }
8199
+ var SDK_INITIAL_BACKOFF_MS = 2e3;
8200
+ function wsNewConnectionMaxWaitMs(idleTimeoutMs, maxRetries) {
8201
+ if (!Number.isFinite(idleTimeoutMs) || !Number.isFinite(maxRetries) || idleTimeoutMs <= 0 || maxRetries < 0) {
8202
+ return 0;
8203
+ }
8204
+ const attempts = maxRetries + 1;
8205
+ const totalBackoffMs = SDK_INITIAL_BACKOFF_MS * (2 ** maxRetries - 1);
8206
+ const shareableMs = Math.max(0, idleTimeoutMs - totalBackoffMs) / 2;
8207
+ const bound = Math.floor(shareableMs / attempts);
8208
+ return Number.isFinite(bound) ? Math.min(WS_NEW_CONNECTION_MAX_WAIT_CEILING_MS, Math.max(0, bound)) : 0;
8209
+ }
8210
+ function pacedRetryAfterSeconds(requiredWaitMs, maxWaitMs) {
8211
+ const capSeconds = Number.isFinite(maxWaitMs) ? Math.max(1, Math.floor(maxWaitMs / 1e3)) : 1;
8212
+ const wantSeconds = Number.isFinite(requiredWaitMs) ? Math.max(1, Math.ceil(requiredWaitMs / 1e3)) : 1;
8213
+ return Math.min(wantSeconds, capSeconds);
8214
+ }
8215
+ function refusalScheduleMs(maxWaitMs, maxRetries) {
8216
+ if (!Number.isFinite(maxRetries) || maxRetries <= 0) return 0;
8217
+ return maxRetries * pacedRetryAfterSeconds(Number.MAX_SAFE_INTEGER, maxWaitMs) * 1e3;
8218
+ }
8219
+ function canRefuseAtRate(maxWaitMs, maxRetries, ratePerMinute) {
8220
+ if (!Number.isFinite(ratePerMinute) || ratePerMinute <= 0) return false;
8221
+ const refillIntervalMs = 6e4 / ratePerMinute;
8222
+ return refusalScheduleMs(maxWaitMs, maxRetries) >= refillIntervalMs;
8223
+ }
8224
+ function defaultSchedule(ms, fire) {
8225
+ const timer = setTimeout(fire, ms);
8226
+ return () => clearTimeout(timer);
8227
+ }
8228
+ function abortError(signal) {
8229
+ if (signal?.reason instanceof Error) return signal.reason;
8230
+ const error = new Error(
8231
+ typeof signal?.reason === "string" ? signal.reason : "WebSocket connection pacing aborted"
8232
+ );
8233
+ error.name = "AbortError";
8234
+ return error;
8235
+ }
8236
+ var reportedNotices = /* @__PURE__ */ new Set();
8237
+ function reportOnce2(key, message, warn) {
8238
+ if (reportedNotices.has(key)) return;
8239
+ reportedNotices.add(key);
8240
+ try {
8241
+ warn(message);
8242
+ } catch {
8243
+ }
8244
+ }
8245
+ var WsUpgradePacer = class {
8246
+ /** Longest any one request will be queued. Derived; exposed for diagnostics. */
8247
+ maxWaitMs;
8248
+ enabled;
8249
+ refillPerMs;
8250
+ capacity;
8251
+ canRefuse;
8252
+ maxDebt;
8253
+ now;
8254
+ schedule;
8255
+ tokens;
8256
+ lastRefillAt;
8257
+ constructor(options = {}) {
8258
+ const ratePerMinute = options.ratePerMinute ?? DEFAULT_WS_NEW_CONNECTIONS_PER_MIN;
8259
+ const burst = options.burst ?? WS_NEW_CONNECTION_BURST;
8260
+ const budget = options.idleTimeoutMs === void 0 || options.maxRetries === void 0 ? resolvedPacingBudget() : { idleTimeoutMs: options.idleTimeoutMs, maxRetries: options.maxRetries };
8261
+ const idleTimeoutMs = options.idleTimeoutMs ?? budget.idleTimeoutMs;
8262
+ const maxRetries = options.maxRetries ?? budget.maxRetries;
8263
+ this.maxWaitMs = wsNewConnectionMaxWaitMs(idleTimeoutMs, maxRetries);
8264
+ const rateRequested = Number.isFinite(ratePerMinute) && ratePerMinute > 0;
8265
+ this.enabled = rateRequested && this.maxWaitMs > 0;
8266
+ if (rateRequested && !this.enabled) {
8267
+ reportOnce2(
8268
+ `disabled:${maxRetries}:${idleTimeoutMs}`,
8269
+ `not pacing new OpenAI connections: a ${maxRetries}-retry budget leaves no room to queue inside the resolved ${idleTimeoutMs}ms request deadline`,
8270
+ (message) => emitParentNotice(`clodex: ${message}`)
8271
+ );
8272
+ }
8273
+ this.canRefuse = maxRetries > 0 && canRefuseAtRate(this.maxWaitMs, maxRetries, ratePerMinute);
8274
+ if (this.enabled && maxRetries > 0 && !this.canRefuse) {
8275
+ reportOnce2(
8276
+ `norefuse:${ratePerMinute}:${maxRetries}:${this.maxWaitMs}`,
8277
+ `pacing new OpenAI connections at ${ratePerMinute}/minute without refusing overflow: a ${maxRetries}-retry schedule spans only ${refusalScheduleMs(this.maxWaitMs, maxRetries)}ms, which cannot outlast the ${Math.round(6e4 / ratePerMinute)}ms wait for a free connection slot, so clodex shapes the opening burst and then admits remaining overflow instead of failing it`,
8278
+ (message) => emitParentNotice(`clodex: ${message}`)
8279
+ );
8280
+ }
8281
+ this.refillPerMs = this.enabled ? ratePerMinute / 6e4 : 0;
8282
+ this.capacity = Number.isFinite(burst) ? Math.max(1, burst) : WS_NEW_CONNECTION_BURST;
8283
+ this.maxDebt = this.maxWaitMs * this.refillPerMs;
8284
+ this.now = options.now ?? Date.now;
8285
+ this.schedule = options.schedule ?? defaultSchedule;
8286
+ this.tokens = this.capacity;
8287
+ this.lastRefillAt = this.now();
8288
+ }
8289
+ /**
8290
+ * Resolves when this request may open a new connection, or resolves to a
8291
+ * refusal the caller must report as a retryable rate limit. Callers that
8292
+ * reuse an existing connection must not call this at all.
8293
+ */
8294
+ async admit(signal) {
8295
+ if (!this.enabled) return { kind: "admitted", waitedMs: 0 };
8296
+ if (signal?.aborted) throw abortError(signal);
8297
+ const reservation = this.reserve(this.now());
8298
+ if (!reservation.admitted) {
8299
+ return {
8300
+ kind: "refused",
8301
+ requiredWaitMs: reservation.requiredWaitMs,
8302
+ // Capped at the bound: the SDK substitutes this hint for its own
8303
+ // backoff rung, so an uncapped one would overrun the deadline the
8304
+ // bound was derived to fit. See `pacedRetryAfterSeconds`.
8305
+ retryAfterSeconds: clampRetryAfterSeconds(
8306
+ pacedRetryAfterSeconds(reservation.requiredWaitMs, this.maxWaitMs)
8307
+ )
8308
+ };
8309
+ }
8310
+ if (reservation.waitMs <= 0) return { kind: "admitted", waitedMs: 0 };
8311
+ const startedAt = this.now();
8312
+ try {
8313
+ await this.sleep(reservation.waitMs, signal);
8314
+ } catch (error) {
8315
+ this.refund(reservation.consumed);
8316
+ throw error;
8317
+ }
8318
+ return { kind: "admitted", waitedMs: Math.max(0, this.now() - startedAt) };
8319
+ }
8320
+ reserve(now) {
8321
+ const elapsed = Math.max(0, now - this.lastRefillAt);
8322
+ this.tokens = Math.min(this.capacity, this.tokens + elapsed * this.refillPerMs);
8323
+ this.lastRefillAt = now;
8324
+ const waitMs = this.tokens >= 1 ? 0 : Math.ceil((1 - this.tokens) / this.refillPerMs);
8325
+ if (waitMs > this.maxWaitMs) {
8326
+ if (this.canRefuse) return { admitted: false, requiredWaitMs: waitMs };
8327
+ const before = this.tokens;
8328
+ this.tokens = Math.max(-this.maxDebt, this.tokens - 1);
8329
+ const consumed = before - this.tokens;
8330
+ return { admitted: true, waitMs: consumed > 0 ? this.maxWaitMs : 0, consumed };
8331
+ }
8332
+ this.tokens -= 1;
8333
+ return { admitted: true, waitMs, consumed: 1 };
8334
+ }
8335
+ refund(consumed) {
8336
+ this.tokens = Math.min(this.capacity, this.tokens + consumed);
8337
+ }
8338
+ /**
8339
+ * INVARIANT REQUIRED OF FUTURE EDITS: `admit` must reject an aborted signal
8340
+ * before reaching here, and nothing may be awaited between that check and
8341
+ * this call. An abort arriving in such a window would leave the request
8342
+ * parked on a listener an already-aborted signal never fires, and it would
8343
+ * wait out the full duration. There is deliberately no second check here to
8344
+ * catch that, because an untested guard is not a guarantee.
8345
+ */
8346
+ sleep(ms, signal) {
8347
+ return new Promise((resolve3, reject) => {
8348
+ let settled = false;
8349
+ let cancelTimer;
8350
+ const onAbort = () => {
8351
+ if (settled) return;
8352
+ settled = true;
8353
+ cancelTimer?.();
8354
+ reject(abortError(signal));
8355
+ };
8356
+ const fire = () => {
8357
+ if (settled) return;
8358
+ settled = true;
8359
+ signal?.removeEventListener("abort", onAbort);
8360
+ resolve3();
8361
+ };
8362
+ signal?.addEventListener("abort", onAbort, { once: true });
8363
+ cancelTimer = this.schedule(ms, fire);
8364
+ if (settled) cancelTimer();
8365
+ });
8366
+ }
8367
+ };
8368
+ function wsNewConnectionsPerMinute(env = process.env, warn = (message) => emitParentNotice(`clodex: ${message}`)) {
8369
+ const raw = env[WS_NEW_CONNECTIONS_PER_MIN_ENV]?.trim();
8370
+ if (raw === void 0 || raw === "") return DEFAULT_WS_NEW_CONNECTIONS_PER_MIN;
8371
+ const value = Number(raw);
8372
+ if (!Number.isInteger(value) || value < 0) {
8373
+ reportOnce2(
8374
+ `rate:${raw}`,
8375
+ `ignoring ${WS_NEW_CONNECTIONS_PER_MIN_ENV}=${raw} (expected a non-negative integer; using ${DEFAULT_WS_NEW_CONNECTIONS_PER_MIN})`,
8376
+ warn
8377
+ );
8378
+ return DEFAULT_WS_NEW_CONNECTIONS_PER_MIN;
8379
+ }
8380
+ if (value > MAX_WS_NEW_CONNECTIONS_PER_MIN) {
8381
+ reportOnce2(
8382
+ `rate:${raw}`,
8383
+ `clamping ${WS_NEW_CONNECTIONS_PER_MIN_ENV}=${raw} to ${MAX_WS_NEW_CONNECTIONS_PER_MIN} (a higher rate shapes nothing OpenAI throttles on)`,
8384
+ warn
8385
+ );
8386
+ return MAX_WS_NEW_CONNECTIONS_PER_MIN;
8387
+ }
8388
+ return value;
8389
+ }
8390
+ var sharedPacer;
8391
+ function sharedWsUpgradePacer() {
8392
+ sharedPacer ??= new WsUpgradePacer({ ratePerMinute: wsNewConnectionsPerMinute() });
8393
+ return sharedPacer;
8394
+ }
8395
+
8012
8396
  // src/oauth/responses-websocket.ts
8013
8397
  var RESPONSES_LITE_HEADER = "x-openai-internal-codex-responses-lite";
8014
8398
  var TERMINAL_EVENT_TYPES = /* @__PURE__ */ new Set(["response.completed", "response.failed", "response.incomplete"]);
@@ -9129,6 +9513,34 @@ function handleSocketMessage(entry, data) {
9129
9513
  closeContext(ctx);
9130
9514
  }
9131
9515
  }
9516
+ function pacedRefusalResponse(retryAfterSeconds) {
9517
+ const frame = {
9518
+ type: "error",
9519
+ sequence_number: 0,
9520
+ error: {
9521
+ type: anthropicErrorType(429),
9522
+ code: "429",
9523
+ message: `clodex is limiting how fast it opens new OpenAI connections to reduce the chance of an upstream rate limit; retry after ${retryAfterSeconds}s`,
9524
+ param: null,
9525
+ retry_after_seconds: retryAfterSeconds
9526
+ }
9527
+ };
9528
+ return new Response(`data: ${JSON.stringify(frame)}
9529
+
9530
+ `, {
9531
+ status: 200,
9532
+ headers: {
9533
+ "content-type": "text/event-stream; charset=utf-8",
9534
+ // A real header, not just the prose: `getRetryDelayInMs` reads headers and
9535
+ // ignores the body, so without one the SDK falls back to its fixed 2s/4s
9536
+ // ladder. This does NOT de-correlate the group — refusals debit nothing,
9537
+ // so everyone refused at the same instant sees the same deficit and gets
9538
+ // the same hint — it defers the whole group by long enough for the bucket
9539
+ // to refill, which is what turns a retry storm into a successful retry.
9540
+ "retry-after": String(retryAfterSeconds)
9541
+ }
9542
+ });
9543
+ }
9132
9544
  function numericRetryAfterHeader(value) {
9133
9545
  const single = Array.isArray(value) ? value[0] : value;
9134
9546
  return typeof single === "string" && /^\d+$/.test(single.trim()) ? Number(single.trim()) : void 0;
@@ -9270,7 +9682,7 @@ function createResponsesWebSocketFetch(wsUrl, log12, options = {}) {
9270
9682
  const promptFieldHashes = responsesWebSocketPromptFieldHashes(payload);
9271
9683
  const instructionsSnapshot = instructionsFromPayload(payload);
9272
9684
  const diagnosticCorrelation = diagnosticContext.getStore();
9273
- const now = resolvedOptions.now();
9685
+ let now = resolvedOptions.now();
9274
9686
  const evictions = cleanupExpiredConnections(now);
9275
9687
  const candidates = partitionKey ? connectionEntries(partitionKey) : [];
9276
9688
  const idleCandidates = candidates.filter((entry) => !entry.inFlight);
@@ -9341,6 +9753,53 @@ function createResponsesWebSocketFetch(wsUrl, log12, options = {}) {
9341
9753
  } else {
9342
9754
  decision = "unpartitioned_socket";
9343
9755
  }
9756
+ let pacingWaitedMs;
9757
+ if (!selected) {
9758
+ const pacer = options.pacer ?? sharedWsUpgradePacer();
9759
+ const pacingStartedAt = resolvedOptions.now();
9760
+ let admission;
9761
+ try {
9762
+ admission = await pacer.admit(init?.signal ?? void 0);
9763
+ } catch (error) {
9764
+ emitDiagnostic(options, {
9765
+ event: "ws_new_connection_paced",
9766
+ outcome: "aborted",
9767
+ decision,
9768
+ waitedMs: Math.max(0, resolvedOptions.now() - pacingStartedAt)
9769
+ }, diagnosticCorrelation);
9770
+ throw error;
9771
+ }
9772
+ if (admission.kind === "refused") {
9773
+ debug(
9774
+ `refused a new connection to hold the pacing rate; retry after ${admission.retryAfterSeconds}s`
9775
+ );
9776
+ emitDiagnostic(options, {
9777
+ event: "ws_new_connection_paced",
9778
+ outcome: "refused",
9779
+ decision,
9780
+ requiredWaitMs: admission.requiredWaitMs,
9781
+ retryAfterSeconds: admission.retryAfterSeconds
9782
+ }, diagnosticCorrelation);
9783
+ return pacedRefusalResponse(admission.retryAfterSeconds);
9784
+ }
9785
+ if (admission.waitedMs > 0) {
9786
+ pacingWaitedMs = admission.waitedMs;
9787
+ debug(`paced new connection by ${admission.waitedMs}ms`);
9788
+ emitDiagnostic(options, {
9789
+ event: "ws_new_connection_paced",
9790
+ outcome: "admitted",
9791
+ decision,
9792
+ waitedMs: admission.waitedMs
9793
+ }, diagnosticCorrelation);
9794
+ }
9795
+ now = resolvedOptions.now();
9796
+ evictions.push(...cleanupExpiredConnections(now));
9797
+ if (persistent && partitionKey && connectionEntries(partitionKey).some((entry) => entry.inFlight)) {
9798
+ persistent = false;
9799
+ decision = "parallel_isolated";
9800
+ debug("parallel request using an isolated socket after pacing");
9801
+ }
9802
+ }
9344
9803
  if (!selected && persistent) {
9345
9804
  evictions.push(...evictOldestIdleGeneration(
9346
9805
  "nursery",
@@ -9382,6 +9841,7 @@ function createResponsesWebSocketFetch(wsUrl, log12, options = {}) {
9382
9841
  continuationMatchMode: selectedMatch?.mode,
9383
9842
  promotedConnectionId,
9384
9843
  createdConnectionId: selected ? void 0 : nextConnectionDebugId,
9844
+ ...pacingWaitedMs !== void 0 ? { pacingWaitedMs } : {},
9385
9845
  createdGeneration: selected ? void 0 : persistent ? "nursery" : "isolated",
9386
9846
  incrementalInputItems: selectedDelta?.length,
9387
9847
  heads: candidates.map((entry) => ({
@@ -9814,7 +10274,7 @@ async function createLanguageModel(spec) {
9814
10274
  }
9815
10275
  var ANTHROPIC_EFFORT_LEVELS = ["low", "medium", "high"];
9816
10276
  var OPENAI_EFFORT_LEVELS = ["low", "medium", "high", "xhigh"];
9817
- var GPT_56_EFFORT_LEVELS = ["none", "low", "medium", "high", "xhigh", "max"];
10277
+ var CODEX_EXTENDED_EFFORT_LEVELS = ["none", "low", "medium", "high", "xhigh", "max"];
9818
10278
  var GEMINI_EFFORT_LEVELS = ["low", "medium", "high"];
9819
10279
  var MISTRAL_EFFORT_LEVELS = ["high", "off"];
9820
10280
  var XAI_EFFORT_LEVELS = ["none", "low", "medium", "high"];
@@ -9978,14 +10438,32 @@ function mapCodexEffortToAnthropic(effort) {
9978
10438
  return void 0;
9979
10439
  }
9980
10440
  }
9981
- function isGpt56Model(modelId) {
9982
- return /^gpt-5\.6(?:-|$)/i.test(modelId);
10441
+ function isCodexReasoningFamily(modelId) {
10442
+ return !isChatVariant(modelId) && supportsExtendedCodexEffort(modelId);
10443
+ }
10444
+ function isChatVariant(modelId) {
10445
+ return /-chat(?:-|$)/i.test(modelId);
10446
+ }
10447
+ function supportsExtendedCodexEffort(modelId) {
10448
+ const version = /^gpt-(\d+)(?:\.(\d+))?(?:-|$)/i.exec(modelId);
10449
+ if (version) {
10450
+ const major = Number(version[1]);
10451
+ const minor = version[2] ? Number(version[2]) : 0;
10452
+ if (major > 5 || major === 5 && minor >= 6) return true;
10453
+ }
10454
+ return /^gpt-daybreak(?:-|$)/i.test(modelId);
10455
+ }
10456
+ function supportsNoneEffort(modelId) {
10457
+ return /^gpt-5\.6(?:-|$)/i.test(modelId) || /^gpt-daybreak-blue(?:-|$)/i.test(modelId);
9983
10458
  }
9984
10459
  function isReasoningSummaryUnsupportedModel(modelId) {
9985
10460
  return /codex-spark(?:-|$)/i.test(modelId);
9986
10461
  }
9987
10462
  function mapCodexEffortToOpenAI(effort, modelId) {
9988
- if (modelId && isGpt56Model(modelId) && GPT_56_EFFORT_LEVELS.includes(effort)) {
10463
+ if (effort === "none") {
10464
+ return modelId && supportsNoneEffort(modelId) ? "none" : void 0;
10465
+ }
10466
+ if (modelId && supportsExtendedCodexEffort(modelId) && CODEX_EXTENDED_EFFORT_LEVELS.includes(effort)) {
9989
10467
  return effort;
9990
10468
  }
9991
10469
  if (effort === "xhigh") return "high";
@@ -10126,9 +10604,9 @@ function getReasoningCapabilities(npm, modelId, metadata) {
10126
10604
  }
10127
10605
  if (npm === "@ai-sdk/openai" || npm === "@ai-sdk/azure") {
10128
10606
  const prefersResponses = modelPrefersResponsesApi(modelId);
10129
- if (prefersResponses || metadata?.reasoning) {
10607
+ if (prefersResponses || isCodexReasoningFamily(modelId) || metadata?.reasoning) {
10130
10608
  return {
10131
- levels: isGpt56Model(modelId) ? [...GPT_56_EFFORT_LEVELS] : [...OPENAI_EFFORT_LEVELS],
10609
+ levels: supportsExtendedCodexEffort(modelId) ? CODEX_EXTENDED_EFFORT_LEVELS.filter((level) => level !== "none" || supportsNoneEffort(modelId)) : [...OPENAI_EFFORT_LEVELS],
10132
10610
  defaultLevel: "medium",
10133
10611
  supportsSummaries: true,
10134
10612
  mode: "controllable",
@@ -10292,10 +10770,14 @@ function effortProviderOptions(npm, effort, modelId, metadata) {
10292
10770
  return mapped ? { openrouter: { reasoning: { effort: mapped, exclude: false } } } : void 0;
10293
10771
  }
10294
10772
  if (npm === "@ai-sdk/openai" || npm === "@ai-sdk/azure") {
10295
- if (!modelId || !modelPrefersResponsesApi(modelId)) return void 0;
10773
+ if (!modelId || isChatVariant(modelId)) return void 0;
10774
+ if (!(modelPrefersResponsesApi(modelId) || isCodexReasoningFamily(modelId))) {
10775
+ return void 0;
10776
+ }
10296
10777
  const reasoningEffort = mapCodexEffortToOpenAI(effort, modelId);
10297
10778
  if (!reasoningEffort) return void 0;
10298
- return isReasoningSummaryUnsupportedModel(modelId) ? { openai: { reasoningEffort, reasoningSummary: null } } : { openai: { reasoningEffort } };
10779
+ const openaiOptions = { reasoningEffort, forceReasoning: true };
10780
+ return isReasoningSummaryUnsupportedModel(modelId) ? { openai: { ...openaiOptions, reasoningSummary: null } } : { openai: openaiOptions };
10299
10781
  }
10300
10782
  if (npm === "@ai-sdk/xai") {
10301
10783
  if (!modelId || !isXaiReasoningEffortModel(modelId)) return void 0;
@@ -10926,162 +11408,6 @@ function resolveUpstreamTools(tools, messages) {
10926
11408
  return upstream;
10927
11409
  }
10928
11410
 
10929
- // src/upstream-retry.ts
10930
- var UPSTREAM_MAX_RETRIES_ENV = "CLODEX_UPSTREAM_MAX_RETRIES";
10931
- var UPSTREAM_IDLE_TIMEOUT_ENV = "CLODEX_UPSTREAM_IDLE_TIMEOUT_MS";
10932
- var UPSTREAM_TOTAL_TIMEOUT_ENV = "CLODEX_UPSTREAM_TOTAL_TIMEOUT_MS";
10933
- var DEFAULT_UPSTREAM_IDLE_TIMEOUT_MS = 12e4;
10934
- var DEFAULT_UPSTREAM_TOTAL_TIMEOUT_MS = 10 * 6e4;
10935
- var MIN_UPSTREAM_IDLE_TIMEOUT_MS = 1e4;
10936
- var MAX_UPSTREAM_IDLE_TIMEOUT_MS = 60 * 6e4;
10937
- var MIN_UPSTREAM_TOTAL_TIMEOUT_MS = 6e4;
10938
- var MAX_UPSTREAM_TOTAL_TIMEOUT_MS = 6 * 60 * 6e4;
10939
- var SDK_INITIAL_RETRY_DELAY_MS = 2e3;
10940
- var reportedValues = /* @__PURE__ */ new Set();
10941
- var defaultWarn = (message) => emitParentNotice(`clodex: ${message}`);
10942
- function reportOnce(key, message, warn) {
10943
- if (reportedValues.has(key)) return;
10944
- reportedValues.add(key);
10945
- try {
10946
- warn(message);
10947
- } catch {
10948
- }
10949
- }
10950
- function timeoutSetting(env, envName, fallback, min, max, warn) {
10951
- const raw = env[envName]?.trim();
10952
- if (raw === void 0 || raw === "") return { value: fallback, explicit: false };
10953
- const value = Number(raw);
10954
- if (!Number.isInteger(value) || value <= 0) {
10955
- reportOnce(
10956
- `${envName}=${raw}`,
10957
- `ignoring ${envName}=${raw} (expected a positive integer number of milliseconds)`,
10958
- warn
10959
- );
10960
- return { value: fallback, explicit: false };
10961
- }
10962
- if (value < min || value > max) {
10963
- const clamped = Math.min(max, Math.max(min, value));
10964
- reportOnce(
10965
- `${envName}=${raw}`,
10966
- `clamping ${envName}=${raw} to ${clamped}ms (supported range is ${min}-${max}ms)`,
10967
- warn
10968
- );
10969
- return { value: clamped, explicit: true };
10970
- }
10971
- return { value, explicit: true };
10972
- }
10973
- function maxRetriesForIdleTimeout(idleTimeoutMs) {
10974
- let retries = 0;
10975
- let elapsedMs = 0;
10976
- let delayMs = SDK_INITIAL_RETRY_DELAY_MS;
10977
- while (elapsedMs + delayMs < idleTimeoutMs) {
10978
- elapsedMs += delayMs;
10979
- delayMs *= 2;
10980
- retries += 1;
10981
- }
10982
- return retries;
10983
- }
10984
- var MAX_UPSTREAM_MAX_RETRIES = maxRetriesForIdleTimeout(
10985
- DEFAULT_UPSTREAM_IDLE_TIMEOUT_MS
10986
- );
10987
- var DEFAULT_UPSTREAM_MAX_RETRIES = MAX_UPSTREAM_MAX_RETRIES;
10988
- function configuredUpstreamMaxRetries(env, warn) {
10989
- const raw = env[UPSTREAM_MAX_RETRIES_ENV]?.trim();
10990
- if (raw === void 0 || raw === "") return void 0;
10991
- const value = Number(raw);
10992
- if (!Number.isInteger(value) || value < 0) {
10993
- reportOnce(
10994
- `${UPSTREAM_MAX_RETRIES_ENV}=${raw}`,
10995
- `ignoring ${UPSTREAM_MAX_RETRIES_ENV}=${raw} (expected a non-negative integer)`,
10996
- warn
10997
- );
10998
- return void 0;
10999
- }
11000
- return value;
11001
- }
11002
- function resolveUpstreamMaxRetries(env, warn, idleTimeoutMs) {
11003
- const ceiling = maxRetriesForIdleTimeout(idleTimeoutMs);
11004
- const defaultRetries = Math.min(DEFAULT_UPSTREAM_MAX_RETRIES, ceiling);
11005
- const value = configuredUpstreamMaxRetries(env, warn);
11006
- if (value === void 0) return defaultRetries;
11007
- if (value > ceiling) {
11008
- reportOnce(
11009
- `${UPSTREAM_MAX_RETRIES_ENV}=${value}:idle=${idleTimeoutMs}`,
11010
- `clamping ${UPSTREAM_MAX_RETRIES_ENV}=${value} to ${ceiling} (estimated from the SDK fallback backoff and resolved ${idleTimeoutMs}ms idle timeout; provider delays may allow fewer retries)`,
11011
- warn
11012
- );
11013
- return ceiling;
11014
- }
11015
- return value;
11016
- }
11017
- function upstreamRequestBudget(options = {}) {
11018
- const env = options.env ?? process.env;
11019
- const warn = options.warn ?? defaultWarn;
11020
- const hasIdleOverride = options.idleTimeoutMs !== void 0;
11021
- const configuredIdle = options.idleTimeoutMs !== void 0 ? { value: options.idleTimeoutMs, explicit: false } : timeoutSetting(
11022
- env,
11023
- UPSTREAM_IDLE_TIMEOUT_ENV,
11024
- DEFAULT_UPSTREAM_IDLE_TIMEOUT_MS,
11025
- MIN_UPSTREAM_IDLE_TIMEOUT_MS,
11026
- MAX_UPSTREAM_IDLE_TIMEOUT_MS,
11027
- warn
11028
- );
11029
- const configuredTotal = timeoutSetting(
11030
- env,
11031
- UPSTREAM_TOTAL_TIMEOUT_ENV,
11032
- DEFAULT_UPSTREAM_TOTAL_TIMEOUT_MS,
11033
- MIN_UPSTREAM_TOTAL_TIMEOUT_MS,
11034
- MAX_UPSTREAM_TOTAL_TIMEOUT_MS,
11035
- warn
11036
- );
11037
- let idleTimeoutMs = configuredIdle.value;
11038
- let totalTimeoutMs = configuredTotal.value;
11039
- if (totalTimeoutMs < idleTimeoutMs) {
11040
- if (hasIdleOverride) {
11041
- idleTimeoutMs = totalTimeoutMs;
11042
- } else if (configuredIdle.explicit && !configuredTotal.explicit) {
11043
- reportOnce(
11044
- `timeout-pair:raise-total:${idleTimeoutMs}:${totalTimeoutMs}`,
11045
- `raising the resolved total timeout from ${totalTimeoutMs}ms to ${idleTimeoutMs}ms so it is not shorter than ${UPSTREAM_IDLE_TIMEOUT_ENV}`,
11046
- warn
11047
- );
11048
- totalTimeoutMs = idleTimeoutMs;
11049
- } else {
11050
- reportOnce(
11051
- `timeout-pair:lower-idle:${idleTimeoutMs}:${totalTimeoutMs}`,
11052
- `lowering the resolved idle timeout from ${idleTimeoutMs}ms to ${totalTimeoutMs}ms because it cannot exceed ${UPSTREAM_TOTAL_TIMEOUT_ENV}`,
11053
- warn
11054
- );
11055
- idleTimeoutMs = totalTimeoutMs;
11056
- }
11057
- }
11058
- return {
11059
- idleTimeoutMs,
11060
- totalTimeoutMs,
11061
- maxRetries: resolveUpstreamMaxRetries(env, warn, idleTimeoutMs)
11062
- };
11063
- }
11064
- var CLIENT_MAX_RETRIES_ENV = "CLAUDE_CODE_MAX_RETRIES";
11065
- var DEFAULT_PASSTHROUGH_RETRIES = 1;
11066
- function passthroughUpstreamRetries(env = process.env, warn = defaultWarn) {
11067
- const explicit = configuredUpstreamMaxRetries(env, warn);
11068
- if (explicit !== void 0) {
11069
- if (explicit <= MAX_UPSTREAM_MAX_RETRIES) return explicit;
11070
- reportOnce(
11071
- `${UPSTREAM_MAX_RETRIES_ENV}=${explicit}:passthrough`,
11072
- `clamping ${UPSTREAM_MAX_RETRIES_ENV}=${explicit} to ${MAX_UPSTREAM_MAX_RETRIES} (the raw HTTP MITM path supports at most this many replays)`,
11073
- warn
11074
- );
11075
- return MAX_UPSTREAM_MAX_RETRIES;
11076
- }
11077
- const raw = env[CLIENT_MAX_RETRIES_ENV]?.trim();
11078
- if (raw !== void 0 && raw !== "") {
11079
- const clientRetries = Number(raw);
11080
- if (Number.isFinite(clientRetries) && clientRetries === 0) return 0;
11081
- }
11082
- return DEFAULT_PASSTHROUGH_RETRIES;
11083
- }
11084
-
11085
11411
  // src/upstream-attempts.ts
11086
11412
  import { RetryError as RetryError2, wrapLanguageModel as wrapLanguageModel2 } from "ai";
11087
11413
  function trackUpstreamAttempts(model) {
@@ -11125,6 +11451,12 @@ function trackUpstreamAttempts(model) {
11125
11451
  };
11126
11452
  }
11127
11453
 
11454
+ // src/claude-code-compact-prompt.ts
11455
+ var CLAUDE_CODE_COMPACT_PROMPT_MARKERS = Object.freeze({
11456
+ start: "CRITICAL: Respond with TEXT ONLY. Do NOT call any tools.",
11457
+ end: "REMINDER: Do NOT call any tools. Respond with plain text only"
11458
+ });
11459
+
11128
11460
  // src/sdk-adapter.ts
11129
11461
  function sdkTranslationErrorSignature(error) {
11130
11462
  const message = error instanceof Error ? error.message : typeof error === "string" ? error : void 0;
@@ -11393,22 +11725,66 @@ function translateToolChoice(tc) {
11393
11725
  if (tc.type === "tool" && tc.name) return { type: "tool", toolName: tc.name };
11394
11726
  return void 0;
11395
11727
  }
11396
- var COMPACT_TEXT_ONLY_START = "CRITICAL: Respond with TEXT ONLY. Do NOT call any tools.";
11397
- var COMPACT_TEXT_ONLY_END = "REMINDER: Do NOT call any tools. Respond with plain text only";
11398
- function isClaudeCodeStructuredOutputCompactRequest(body) {
11728
+ var {
11729
+ start: COMPACT_TEXT_ONLY_START,
11730
+ end: COMPACT_TEXT_ONLY_END
11731
+ } = CLAUDE_CODE_COMPACT_PROMPT_MARKERS;
11732
+ function isClaudeCodeCompactRequest(body) {
11399
11733
  if (body.diagnostics !== void 0) return false;
11400
- if (!body.tools?.some((candidate) => candidate.name === "StructuredOutput")) return false;
11401
11734
  const finalMessage = body.messages.at(-1);
11402
11735
  if (!finalMessage || finalMessage.role !== "user") return false;
11403
- const text5 = typeof finalMessage.content === "string" ? finalMessage.content : finalMessage.content.filter((block) => block.type === "text").map((block) => block.text ?? "").join("\n");
11404
- return text5.includes(COMPACT_TEXT_ONLY_START) && text5.includes(COMPACT_TEXT_ONLY_END);
11736
+ const texts = typeof finalMessage.content === "string" ? [finalMessage.content] : finalMessage.content.filter((block) => block.type === "text").map((block) => block.text ?? "");
11737
+ return texts.some((text5) => text5.startsWith(COMPACT_TEXT_ONLY_START) && text5.includes(COMPACT_TEXT_ONLY_END));
11738
+ }
11739
+ var COMPACT_DRIFT_ANCHOR = "Tool calls will be REJECTED and will waste your only turn";
11740
+ var COMPACT_DRIFT_ANCHOR_LINE = new RegExp(
11741
+ `(?:^|\\n)[-*\u2022\\s]{0,4}${COMPACT_DRIFT_ANCHOR}`
11742
+ );
11743
+ var COMPACT_DRIFT_OPENING = /^(?:(?:critical|important|warning|caution|urgent|notice):\s*)?(?:respond|return|answer|output|write|provide)\b(?=[^\n]{1,160}(?:\n|$))(?=[^\n]*\b(?:text\s+only|plain\s+text\s+only|only\s+(?:plain\s+)?text)\b)(?=[^\n]*\b(?:do not|don't|never|without)\b[^\n]{0,48}\btools?\b)/i;
11744
+ function looksLikeDriftedClaudeCodeCompactRequest(body) {
11745
+ if (body.diagnostics !== void 0) return false;
11746
+ const finalMessage = body.messages.at(-1);
11747
+ if (!finalMessage || finalMessage.role !== "user") return false;
11748
+ const texts = typeof finalMessage.content === "string" ? [finalMessage.content] : finalMessage.content.filter((block) => block.type === "text").map((block) => block.text ?? "");
11749
+ return texts.some((text5) => !text5.startsWith(COMPACT_TEXT_ONLY_START) && COMPACT_DRIFT_OPENING.test(text5) && COMPACT_DRIFT_ANCHOR_LINE.test(text5));
11750
+ }
11751
+ function claudeCodeVersionFromRequest(body) {
11752
+ const texts = typeof body.system === "string" ? [body.system] : (body.system ?? []).map((block) => typeof block === "string" ? block : block.text ?? "");
11753
+ for (const text5 of texts) {
11754
+ if (!text5.startsWith(CLAUDE_CODE_BILLING_HEADER_PREFIX)) continue;
11755
+ const match = text5.match(/\bcc_version=([0-9A-Za-z][0-9A-Za-z._+-]{0,63})(?:;|\s|$)/);
11756
+ if (match) return match[1];
11757
+ }
11758
+ return void 0;
11759
+ }
11760
+ var warnedCompactPromptDrifts = /* @__PURE__ */ new Set();
11761
+ var MAX_COMPACT_PROMPT_DRIFT_WARNINGS = 3;
11762
+ function reportClaudeCodeCompactPromptDrift(body, log12) {
11763
+ if (!looksLikeDriftedClaudeCodeCompactRequest(body)) return;
11764
+ const version = claudeCodeVersionFromRequest(body);
11765
+ const signature = version ?? "unknown-version";
11766
+ try {
11767
+ log12?.(`possible Claude Code compact prompt drift: ${signature}`);
11768
+ } catch {
11769
+ }
11770
+ if (warnedCompactPromptDrifts.has(signature)) return;
11771
+ if (warnedCompactPromptDrifts.size >= MAX_COMPACT_PROMPT_DRIFT_WARNINGS) return;
11772
+ warnedCompactPromptDrifts.add(signature);
11773
+ const versionText = version ? ` from Claude Code ${version}` : "";
11774
+ emitParentNotice(
11775
+ `clodex: warning: a request${versionText} looks like a compaction turn, but its prompt no longer matches clodex's text-only guard. Tools were left enabled and compaction may fail. Please report this at https://github.com/bman654/clodex/issues`
11776
+ );
11777
+ if (warnedCompactPromptDrifts.size === MAX_COMPACT_PROMPT_DRIFT_WARNINGS) {
11778
+ emitParentNotice("clodex: warning: further compact-prompt drift warnings suppressed.");
11779
+ }
11405
11780
  }
11406
11781
  function translateRequest(body, npm, options) {
11407
11782
  const messages = body.messages ?? [];
11408
11783
  annotateToolNames(messages);
11409
11784
  const baseSystem = systemToString(body.system, true);
11410
11785
  const systemText = baseSystem?.trim() || (options?.openAiOAuth ? "You are a coding assistant." : void 0);
11411
- const compactRequest = isClaudeCodeStructuredOutputCompactRequest(body);
11786
+ const compactRequest = isClaudeCodeCompactRequest(body);
11787
+ if (!compactRequest) reportClaudeCodeCompactPromptDrift(body, options?.log);
11412
11788
  let upstreamTools = resolveUpstreamTools(
11413
11789
  body.tools,
11414
11790
  messages
@@ -12318,6 +12694,7 @@ async function startProxyCatalog(routes, defaultAliasId, debug = false, inferenc
12318
12694
  openAiOAuth,
12319
12695
  claudeSessionId,
12320
12696
  maxTools: maxToolsForNpm(route.npm),
12697
+ log: plog,
12321
12698
  reasoningMetadata: {
12322
12699
  providerId: route.providerId,
12323
12700
  apiBaseUrl: route.baseURL,
@@ -14687,7 +15064,7 @@ function parseOpenAiModelEntries(body) {
14687
15064
  }
14688
15065
  return [];
14689
15066
  }
14690
- function buildDynamicOAuthModel(entry, seedById) {
15067
+ function buildDynamicOAuthModel(entry, seedById, codexCatalog) {
14691
15068
  const seed = seedById.get(entry.id);
14692
15069
  if (seed) {
14693
15070
  return {
@@ -14718,7 +15095,19 @@ function buildDynamicOAuthModel(entry, seedById) {
14718
15095
  ...openAiPricingMetadata(id),
14719
15096
  modelFormat: "openai",
14720
15097
  npm: "@ai-sdk/openai",
14721
- reasoning: modelPrefersResponsesApi(id),
15098
+ // Assume a model from the Codex listing reasons. That endpoint reports no
15099
+ // reasoning field of its own, so the old `modelPrefersResponsesApi(id)` was an
15100
+ // id-pattern GUESS that silently said "no" to every family it had not been
15101
+ // taught yet — gpt-6-astra and gpt-daybreak-blue-latest both landed as
15102
+ // non-reasoning that way, which dropped the user's chosen effort and removed
15103
+ // the effort selector from the patched binary (getPatchReasoningCapabilities
15104
+ // early-returns on a `false`). Verified against all 11 models in the live
15105
+ // catalog on 2026-09-04.
15106
+ //
15107
+ // This only decides what the effort UI offers. It is NOT on its own enough to
15108
+ // put reasoning.effort on the wire — effortProviderOptions admits by family —
15109
+ // so a wrong `true` here costs an unusable menu entry, not a 400.
15110
+ reasoning: codexCatalog ? true : modelPrefersResponsesApi(id),
14722
15111
  useResponsesLite: entry.useResponsesLite,
14723
15112
  preferWebSockets: entry.preferWebSockets
14724
15113
  };
@@ -14747,7 +15136,7 @@ async function fetchJsonWithAuth(url, accessToken, timeoutMs) {
14747
15136
  async function refreshOpenAiOAuthModels(accessToken) {
14748
15137
  const TIMEOUT_MS = 1e4;
14749
15138
  const seedById = new Map(buildOpenAiOAuthModels().map((m) => [m.id, m]));
14750
- const toModels = (entries) => entries.map((entry) => buildDynamicOAuthModel(entry, seedById));
15139
+ const toModels = (entries, codexCatalog) => entries.map((entry) => buildDynamicOAuthModel(entry, seedById, codexCatalog));
14751
15140
  const claudeVersion = getInstalledClaudeVersion();
14752
15141
  const codexResult = await fetchJsonWithAuth(
14753
15142
  `https://chatgpt.com/backend-api/codex/models?client_version=${claudeVersion}`,
@@ -14756,7 +15145,7 @@ async function refreshOpenAiOAuthModels(accessToken) {
14756
15145
  );
14757
15146
  const codexEntries = parseOpenAiModelEntries(codexResult.body);
14758
15147
  if (codexEntries.length > 0) {
14759
- return { models: toModels(codexEntries), source: "live" };
15148
+ return { models: toModels(codexEntries, true), source: "live" };
14760
15149
  }
14761
15150
  const chatGptResult = await fetchJsonWithAuth(
14762
15151
  "https://chatgpt.com/backend-api/models",
@@ -14765,7 +15154,7 @@ async function refreshOpenAiOAuthModels(accessToken) {
14765
15154
  );
14766
15155
  const chatGptEntries = parseOpenAiModelEntries(chatGptResult.body).filter(({ id }) => !CHATGPT_CODEX_UNSUPPORTED_MODELS.has(id));
14767
15156
  if (chatGptEntries.length > 0) {
14768
- return { models: toModels(chatGptEntries), source: "live" };
15157
+ return { models: toModels(chatGptEntries, false), source: "live" };
14769
15158
  }
14770
15159
  const failures = [codexResult.error, chatGptResult.error].filter((error) => error !== void 0);
14771
15160
  const credentialFailure = failures.find((error) => /(?:\brejected\b|\b401\b|\b403\b)/i.test(error));
@@ -17246,7 +17635,8 @@ async function handleAnthropicMessages(req, res, options, modelCache, plog) {
17246
17635
  compatibility: model.compatibility,
17247
17636
  upstreamModelId: upstreamModelId(model)
17248
17637
  },
17249
- maxTools: npmMaxTools
17638
+ maxTools: npmMaxTools,
17639
+ log: plog
17250
17640
  });
17251
17641
  const clientWantsStream = Boolean(body.stream);
17252
17642
  const responseModelId = getResponseModelId(body.model, model, options);