@bman654/clodex 2.1.5 → 2.1.7

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
@@ -217,7 +217,7 @@ import { join } from "path";
217
217
  // package.json
218
218
  var package_default = {
219
219
  name: "@bman654/clodex",
220
- version: "2.1.5",
220
+ version: "2.1.7",
221
221
  publishConfig: {
222
222
  access: "public"
223
223
  },
@@ -4007,6 +4007,11 @@ function clampRetryAfterSeconds(value) {
4007
4007
  }
4008
4008
  return Math.min(Math.round(value), MAX_RETRY_AFTER_SECONDS);
4009
4009
  }
4010
+ function retryAfterFromText(message) {
4011
+ if (typeof message !== "string") return void 0;
4012
+ const match = /retry after (\d+)s\b/i.exec(message);
4013
+ return match ? Number(match[1]) : void 0;
4014
+ }
4010
4015
  function numericRetryAfterSeconds(inner) {
4011
4016
  const data = inner.data;
4012
4017
  const fromBody = data?.error?.retry_after_seconds;
@@ -4014,12 +4019,84 @@ function numericRetryAfterSeconds(inner) {
4014
4019
  const fromHeader = inner.responseHeaders?.["retry-after"];
4015
4020
  if (typeof fromHeader === "string" && /^\d+$/.test(fromHeader.trim())) return Number(fromHeader.trim());
4016
4021
  for (const message of [data?.error?.message, inner.message]) {
4017
- if (typeof message !== "string") continue;
4018
- const match = /retry after (\d+)s\b/i.exec(message);
4019
- if (match) return Number(match[1]);
4022
+ const fromText = retryAfterFromText(message);
4023
+ if (fromText !== void 0) return fromText;
4020
4024
  }
4021
4025
  return void 0;
4022
4026
  }
4027
+ var MAX_CLIENT_MESSAGE_CHARS = 240;
4028
+ var CHUNK_DISCRIMINATOR_TYPES = /* @__PURE__ */ new Set(["error", "response.failed"]);
4029
+ function asRecord(value) {
4030
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
4031
+ }
4032
+ function nonEmptyString(record, key) {
4033
+ const value = record[key];
4034
+ return typeof value === "string" && value.trim() !== "" ? value : void 0;
4035
+ }
4036
+ function errorCodeValue(record) {
4037
+ const value = record.code;
4038
+ if (typeof value === "number" && Number.isFinite(value)) return String(value);
4039
+ return typeof value === "string" && value.trim() !== "" ? value : void 0;
4040
+ }
4041
+ function frameStatusCode(code, discriminator) {
4042
+ if (code !== void 0 && /^\d{3}$/.test(code)) {
4043
+ const numeric = Number(code);
4044
+ if (numeric >= 400 && numeric <= 599) return numeric;
4045
+ }
4046
+ if (/insufficient_quota|rate_limit/.test(discriminator)) return 429;
4047
+ if (discriminator.includes("authentication")) return 401;
4048
+ if (discriminator.includes("permission")) return 403;
4049
+ if (discriminator.includes("not_found")) return 404;
4050
+ if (/invalid|bad_request|context_length/.test(discriminator)) return 400;
4051
+ if (discriminator.includes("overload")) return 503;
4052
+ if (discriminator.includes("timeout")) return 504;
4053
+ return 500;
4054
+ }
4055
+ function frameIsContextLengthExceeded(discriminator, message) {
4056
+ if (/context_length|context_window/.test(discriminator)) return true;
4057
+ return /context_length_exceeded|maximum context length|prompt is too long/i.test(message);
4058
+ }
4059
+ function frameIsRetryable(frame) {
4060
+ if (frame.transportCode !== void 0) return true;
4061
+ const { statusCode } = frame;
4062
+ return statusCode === 408 || statusCode === 409 || statusCode === 429 || statusCode >= 500;
4063
+ }
4064
+ function providerErrorFrame(err) {
4065
+ const outer = asRecord(err);
4066
+ if (!outer) return void 0;
4067
+ const nested = asRecord(outer.error) ?? asRecord(asRecord(outer.response)?.error);
4068
+ const unwrapped = !(err instanceof Error) && (typeof outer.type === "string" || Object.hasOwn(outer, "code") || Object.hasOwn(outer, "param")) ? outer : void 0;
4069
+ const payload = nested ?? unwrapped;
4070
+ if (!payload) return void 0;
4071
+ const rawMessage = payload.message;
4072
+ if (typeof rawMessage !== "string") return void 0;
4073
+ const message = rawMessage.trim() !== "" ? rawMessage : "Provider returned an error";
4074
+ const code = errorCodeValue(payload);
4075
+ const rawType = nonEmptyString(payload, "type");
4076
+ const type = rawType !== void 0 && CHUNK_DISCRIMINATOR_TYPES.has(rawType) ? void 0 : rawType;
4077
+ const discriminator = [code, type].filter((value) => value !== void 0).join(" ").toLowerCase();
4078
+ const statusCode = frameStatusCode(code, discriminator);
4079
+ const rawRetryAfter = statusCode === 429 ? typeof payload.retry_after_seconds === "number" && Number.isFinite(payload.retry_after_seconds) && payload.retry_after_seconds >= 0 ? payload.retry_after_seconds : retryAfterFromText(message) : void 0;
4080
+ let serialized;
4081
+ try {
4082
+ serialized = JSON.stringify(payload) ?? message;
4083
+ } catch {
4084
+ serialized = message;
4085
+ }
4086
+ return {
4087
+ message,
4088
+ statusCode,
4089
+ contextLengthExceeded: frameIsContextLengthExceeded(discriminator, message),
4090
+ ...rawRetryAfter !== void 0 ? { retryAfterSeconds: clampRetryAfterSeconds(rawRetryAfter) } : {},
4091
+ ...code === "websocket_transport_error" ? { transportCode: "websocket_transport_error" } : {},
4092
+ serialized
4093
+ };
4094
+ }
4095
+ function frameFromError(err) {
4096
+ const retry = RetryError.isInstance(err) ? err : void 0;
4097
+ const frame = providerErrorFrame(retry?.lastError ?? err);
4098
+ return frame ? { frame, attemptCount: retry?.errors.length ?? 1 } : void 0;
4099
+ }
4023
4100
  function boundedTransportCode(data) {
4024
4101
  if (!data || typeof data !== "object") return void 0;
4025
4102
  const error = data.error;
@@ -4029,7 +4106,19 @@ function boundedTransportCode(data) {
4029
4106
  function sdkUpstreamErrorDetails(err) {
4030
4107
  const retry = RetryError.isInstance(err) ? err : void 0;
4031
4108
  const inner = retry?.lastError ?? err;
4032
- if (!APICallError.isInstance(inner)) return void 0;
4109
+ if (!APICallError.isInstance(inner)) {
4110
+ const recovered = frameFromError(err);
4111
+ if (!recovered) return void 0;
4112
+ const { frame, attemptCount } = recovered;
4113
+ return {
4114
+ statusCode: frame.statusCode,
4115
+ errorContent: frame.serialized,
4116
+ isRetryable: frameIsRetryable(frame),
4117
+ attemptCount,
4118
+ ...frame.retryAfterSeconds !== void 0 ? { retryAfterSeconds: frame.retryAfterSeconds } : {},
4119
+ ...frame.transportCode !== void 0 ? { transportCode: frame.transportCode } : {}
4120
+ };
4121
+ }
4033
4122
  let errorContent = inner.responseBody;
4034
4123
  if (!errorContent && inner.data !== void 0) {
4035
4124
  try {
@@ -4050,6 +4139,8 @@ function sdkUpstreamErrorDetails(err) {
4050
4139
  };
4051
4140
  }
4052
4141
  function isContextLengthExceededError(err, formattedMessage = "") {
4142
+ const frame = frameFromError(err)?.frame;
4143
+ if (frame) return frame.contextLengthExceeded;
4053
4144
  const details = sdkUpstreamErrorDetails(err);
4054
4145
  const rec = err && typeof err === "object" ? err : void 0;
4055
4146
  const candidates = [
@@ -4068,6 +4159,11 @@ function isContextLengthExceededError(err, formattedMessage = "") {
4068
4159
  function formatUpstreamError(err) {
4069
4160
  if (!err || typeof err !== "object") return "Upstream model request failed.";
4070
4161
  const rec = err;
4162
+ const frame = frameFromError(err)?.frame;
4163
+ if (frame) {
4164
+ const short = truncateForClient(sanitizeMessage(frame.message)) || "Upstream model request failed.";
4165
+ return `${short} (HTTP ${frame.statusCode})`;
4166
+ }
4071
4167
  if (rec.data?.error?.message) {
4072
4168
  const short = sanitizeMessage(rec.data.error.message);
4073
4169
  return rec.statusCode ? `${short} (HTTP ${rec.statusCode})` : short;
@@ -4106,6 +4202,8 @@ function upstreamHttpStatus(err, message) {
4106
4202
  const code = err.statusCode;
4107
4203
  if (typeof code === "number" && code >= 400 && code <= 599) return code;
4108
4204
  }
4205
+ const frameStatus = frameFromError(err)?.frame.statusCode;
4206
+ if (frameStatus !== void 0) return frameStatus;
4109
4207
  if (message.includes("HTTP 429") || message.includes("429")) return 429;
4110
4208
  if (message.includes("HTTP 400")) return 400;
4111
4209
  return 500;
@@ -4126,6 +4224,10 @@ function anthropicErrorType(status) {
4126
4224
  return "api_error";
4127
4225
  }
4128
4226
  }
4227
+ function truncateForClient(message) {
4228
+ if (message.length <= MAX_CLIENT_MESSAGE_CHARS) return message;
4229
+ return `${[...message].slice(0, MAX_CLIENT_MESSAGE_CHARS - 1).join("")}\u2026`;
4230
+ }
4129
4231
  function sanitizeMessage(message) {
4130
4232
  const line = message.split("\n")[0]?.trim() ?? message;
4131
4233
  if (line.startsWith("RetryError") || line.includes("AI_RetryError")) {
@@ -4304,6 +4406,9 @@ function normalizeToolCallJson(value) {
4304
4406
  } catch {
4305
4407
  }
4306
4408
  }
4409
+ if (record.type === "reasoning" && Array.isArray(record.content) && record.content.length === 0) {
4410
+ delete out.content;
4411
+ }
4307
4412
  return out;
4308
4413
  }
4309
4414
  function arraysEqual(left, right) {
@@ -4319,7 +4424,37 @@ function conversationItemKind(value) {
4319
4424
  function conversationItemHash(value) {
4320
4425
  return createHash3("sha256").update(canonicalJson(normalizeToolCallJson(value))).digest("hex").slice(0, 16);
4321
4426
  }
4322
- function continuationMismatchDetails(entry, payload) {
4427
+ function reasoningNormalizationGap(expected, actual) {
4428
+ if (conversationItemKind(expected) !== "reasoning" || conversationItemKind(actual) !== "reasoning") return void 0;
4429
+ const left = expected;
4430
+ const right = actual;
4431
+ const blob = left.encrypted_content;
4432
+ if (typeof blob !== "string" || !blob || blob !== right.encrypted_content) return void 0;
4433
+ const fields = [.../* @__PURE__ */ new Set([...Object.keys(left), ...Object.keys(right)])].sort().filter((key) => canonicalJson(normalizeToolCallJson(left[key])) !== canonicalJson(normalizeToolCallJson(right[key])));
4434
+ return fields.length ? fields : void 0;
4435
+ }
4436
+ var warnedReasoningGaps = /* @__PURE__ */ new Set();
4437
+ var MAX_REASONING_GAP_WARNINGS = 3;
4438
+ function warnReasoningNormalizationGap(fields, log12) {
4439
+ const signature = fields.join(",");
4440
+ const message = `clodex: warning: a reasoning item with identical encrypted_content failed the continuation match on field(s): ${signature}. Prompt caching is degraded for this turn \u2014 this is a clodex normalization gap, please report it at https://github.com/bman654/clodex/issues`;
4441
+ try {
4442
+ log12?.(`reasoning normalization gap: ${signature}`);
4443
+ } catch {
4444
+ }
4445
+ if (warnedReasoningGaps.has(signature)) return;
4446
+ if (warnedReasoningGaps.size >= MAX_REASONING_GAP_WARNINGS) return;
4447
+ warnedReasoningGaps.add(signature);
4448
+ try {
4449
+ process.stderr.write(`${message}
4450
+ `);
4451
+ if (warnedReasoningGaps.size === MAX_REASONING_GAP_WARNINGS) {
4452
+ process.stderr.write("clodex: warning: further reasoning-normalization warnings suppressed.\n");
4453
+ }
4454
+ } catch {
4455
+ }
4456
+ }
4457
+ function continuationMismatchDetails(entry, payload, log12) {
4323
4458
  const full = inputArray(payload);
4324
4459
  const prefix = [...entry.requestInput ?? [], ...entry.expectedAssistant ?? []];
4325
4460
  const comparable = Math.min(full.length, prefix.length);
@@ -4332,6 +4467,8 @@ function continuationMismatchDetails(entry, payload) {
4332
4467
  }
4333
4468
  const expected = mismatch < prefix.length ? prefix[mismatch] : void 0;
4334
4469
  const actual = mismatch < full.length ? full[mismatch] : void 0;
4470
+ const reasoningGap = reasoningNormalizationGap(expected, actual);
4471
+ if (reasoningGap) warnReasoningNormalizationGap(reasoningGap, log12);
4335
4472
  return {
4336
4473
  fullItems: full.length,
4337
4474
  expectedPrefixItems: prefix.length,
@@ -4339,11 +4476,12 @@ function continuationMismatchDetails(entry, payload) {
4339
4476
  expectedKind: expected === void 0 ? "none" : conversationItemKind(expected),
4340
4477
  actualKind: actual === void 0 ? "none" : conversationItemKind(actual),
4341
4478
  ...expected !== void 0 ? { expectedHash: conversationItemHash(expected) } : {},
4342
- ...actual !== void 0 ? { actualHash: conversationItemHash(actual) } : {}
4479
+ ...actual !== void 0 ? { actualHash: conversationItemHash(actual) } : {},
4480
+ ...reasoningGap ? { reasoningNormalizationGap: reasoningGap } : {}
4343
4481
  };
4344
4482
  }
4345
- function continuationMismatchSummary(entry, payload) {
4346
- const details = continuationMismatchDetails(entry, payload);
4483
+ function continuationMismatchSummary(entry, payload, log12) {
4484
+ const details = continuationMismatchDetails(entry, payload, log12);
4347
4485
  return `full_items=${details.fullItems} expected_prefix_items=${details.expectedPrefixItems} first_mismatch=${details.firstMismatch} expected=${details.expectedKind} actual=${details.actualKind}`;
4348
4486
  }
4349
4487
  function continuationMatch(entry, payload) {
@@ -4374,6 +4512,15 @@ function responseErrorCode(event) {
4374
4512
  const responseError = response?.error && typeof response.error === "object" ? response.error : void 0;
4375
4513
  return typeof responseError?.code === "string" ? responseError.code : void 0;
4376
4514
  }
4515
+ function responseErrorType(event) {
4516
+ if (!event || typeof event !== "object") return void 0;
4517
+ const record = event;
4518
+ const error = record.error && typeof record.error === "object" ? record.error : void 0;
4519
+ if (typeof error?.type === "string") return error.type;
4520
+ const response = record.response && typeof record.response === "object" ? record.response : void 0;
4521
+ const responseError = response?.error && typeof response.error === "object" ? response.error : void 0;
4522
+ return typeof responseError?.type === "string" ? responseError.type : void 0;
4523
+ }
4377
4524
  function responseRetryAfterSeconds(event) {
4378
4525
  if (!event || typeof event !== "object") return void 0;
4379
4526
  const record = event;
@@ -4388,6 +4535,27 @@ function responseRetryAfterSeconds(event) {
4388
4535
  }
4389
4536
  return void 0;
4390
4537
  }
4538
+ function responseErrorStatus(event) {
4539
+ if (!event || typeof event !== "object") return void 0;
4540
+ const record = event;
4541
+ for (const candidate of [record.status, record.error?.status]) {
4542
+ if (typeof candidate === "number" && Number.isInteger(candidate) && candidate >= 400 && candidate <= 599) {
4543
+ return candidate;
4544
+ }
4545
+ }
4546
+ return void 0;
4547
+ }
4548
+ function responseErrorMessage(event) {
4549
+ if (!event || typeof event !== "object") return void 0;
4550
+ const record = event;
4551
+ const response = record.response && typeof record.response === "object" ? record.response : void 0;
4552
+ for (const candidate of [record.error, response?.error, record]) {
4553
+ if (!candidate || typeof candidate !== "object") continue;
4554
+ const message = candidate.message;
4555
+ if (typeof message === "string" && message.trim()) return message.trim();
4556
+ }
4557
+ return void 0;
4558
+ }
4391
4559
  function boundedDiagnosticIdentifier(value) {
4392
4560
  if (typeof value !== "string") return void 0;
4393
4561
  const normalized = value.trim();
@@ -4894,7 +5062,8 @@ function handleSocketMessage(entry, data) {
4894
5062
  );
4895
5063
  return;
4896
5064
  }
4897
- if (FAILURE_EVENT_TYPES.has(type ?? "")) {
5065
+ const errorStatus = type === "error" && !ctx.emittedModelData ? responseErrorStatus(event) : void 0;
5066
+ if (FAILURE_EVENT_TYPES.has(type ?? "") && (errorStatus === void 0 || willRetry)) {
4898
5067
  emitResponseErrorDiagnostic(entry, ctx, {
4899
5068
  source: "response_event",
4900
5069
  upstreamEventType: type,
@@ -4911,6 +5080,32 @@ function handleSocketMessage(entry, data) {
4911
5080
  dispatchContext(replacement, ctx);
4912
5081
  return;
4913
5082
  }
5083
+ if (errorStatus !== void 0) {
5084
+ const statedRetryAfter = errorStatus === 429 ? responseRetryAfterSeconds(event) : void 0;
5085
+ const retryAfterSeconds = statedRetryAfter === void 0 ? void 0 : clampRetryAfterSeconds(statedRetryAfter);
5086
+ const reason = responseErrorMessage(event) ?? `OpenAI rejected the request (HTTP ${errorStatus})`;
5087
+ failContext(
5088
+ entry,
5089
+ ctx,
5090
+ retryAfterSeconds === void 0 ? reason : `${reason}; retry after ${retryAfterSeconds}s`,
5091
+ {
5092
+ source: "error_frame",
5093
+ // Names the failure. Without it this record — now the ONLY one for a
5094
+ // rejection — can carry no indication of what failed, since a bare
5095
+ // error frame often has no `code` at all.
5096
+ errorType: boundedDiagnosticIdentifier(responseErrorType(event)),
5097
+ // Upstream-controlled, so bounded like every other identifier in this
5098
+ // file's diagnostics. The connection-limit branch can pass its code raw
5099
+ // only because it has just been compared `===` to a known constant.
5100
+ errorCode: boundedDiagnosticIdentifier(errorCode),
5101
+ mappedStatusCode: errorStatus,
5102
+ ...retryAfterSeconds !== void 0 ? { retryAfterSeconds } : {}
5103
+ },
5104
+ errorStatus,
5105
+ retryAfterSeconds
5106
+ );
5107
+ return;
5108
+ }
4914
5109
  ctx.pendingEvents.push(event);
4915
5110
  if (isModelDataEvent(type)) flushPending(ctx);
4916
5111
  if (TERMINAL_EVENT_TYPES.has(type ?? "") || type === "error") {
@@ -5113,7 +5308,7 @@ function createResponsesWebSocketFetch(wsUrl, log12, options = {}) {
5113
5308
  debug("parallel request using an isolated socket");
5114
5309
  } else if (diagnosticEntry) {
5115
5310
  debug(
5116
- `history mismatch starting an additional chain; retained ${candidates.length} existing head(s) (${continuationMismatchSummary(diagnosticEntry, payload)})`
5311
+ `history mismatch starting an additional chain; retained ${candidates.length} existing head(s) (${continuationMismatchSummary(diagnosticEntry, payload, debug)})`
5117
5312
  );
5118
5313
  decision = "history_mismatch_new_head";
5119
5314
  } else if (partitionKey) {
@@ -5173,7 +5368,7 @@ function createResponsesWebSocketFetch(wsUrl, log12, options = {}) {
5173
5368
  ttlPausedMs: entry.ttlPausedMs,
5174
5369
  idleMs: Math.max(0, now - entry.lastUsedAt),
5175
5370
  promptChanges: changedPromptFields(entry.promptFieldHashes, promptFieldHashes),
5176
- mismatch: continuationMismatchDetails(entry, payload)
5371
+ mismatch: continuationMismatchDetails(entry, payload, debug)
5177
5372
  })),
5178
5373
  evictions
5179
5374
  }, diagnosticCorrelation);
@@ -5706,6 +5901,9 @@ function mapCodexEffortToAnthropic(effort) {
5706
5901
  function isGpt56Model(modelId) {
5707
5902
  return /^gpt-5\.6(?:-|$)/i.test(modelId);
5708
5903
  }
5904
+ function isReasoningSummaryUnsupportedModel(modelId) {
5905
+ return /codex-spark(?:-|$)/i.test(modelId);
5906
+ }
5709
5907
  function mapCodexEffortToOpenAI(effort, modelId) {
5710
5908
  if (modelId && isGpt56Model(modelId) && GPT_56_EFFORT_LEVELS.includes(effort)) {
5711
5909
  return effort;
@@ -5942,7 +6140,8 @@ function effortProviderOptions(npm, effort, modelId, metadata) {
5942
6140
  if (npm === "@ai-sdk/openai" || npm === "@ai-sdk/azure") {
5943
6141
  if (!modelId || !modelPrefersResponsesApi(modelId)) return void 0;
5944
6142
  const reasoningEffort = mapCodexEffortToOpenAI(effort, modelId);
5945
- return reasoningEffort ? { openai: { reasoningEffort } } : void 0;
6143
+ if (!reasoningEffort) return void 0;
6144
+ return isReasoningSummaryUnsupportedModel(modelId) ? { openai: { reasoningEffort, reasoningSummary: null } } : { openai: { reasoningEffort } };
5946
6145
  }
5947
6146
  if (npm === "@ai-sdk/xai") {
5948
6147
  if (!modelId || !isXaiReasoningEffortModel(modelId)) return void 0;