@bman654/clodex 2.2.1 → 2.3.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 +9 -0
- package/dist/cli.js +247 -28
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -275,6 +275,15 @@ clodex --version # version
|
|
|
275
275
|
- Proxied routes forward configured provider headers for API-key and OAuth authentication. Anonymous routes preserve non-credential headers while removing authorization, API-key, cookie, token, secret, and credential-bearing header names before dispatch.
|
|
276
276
|
- `CLODEX_CLAUDE_PATH` overrides Claude Code binary discovery.
|
|
277
277
|
- **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).
|
|
278
|
+
- **Upstream retries:** set `CLODEX_UPSTREAM_MAX_RETRIES` to an integer from
|
|
279
|
+
`0` through `5` to override the SDK's default of two retries for retryable
|
|
280
|
+
provider failures. `0` disables retries. The SDK honors valid
|
|
281
|
+
`retry-after`/`retry-after-ms` headers and otherwise uses exponential
|
|
282
|
+
backoff. Larger integers clamp to `5` with a one-time warning because a sixth
|
|
283
|
+
retry cannot complete before the translated streaming paths' 120-second
|
|
284
|
+
no-data timeout. Unset, empty, or malformed values preserve the default. A
|
|
285
|
+
stream that fails after output begins cannot be replayed safely and still
|
|
286
|
+
terminates the request.
|
|
278
287
|
|
|
279
288
|
## Known limitations
|
|
280
289
|
|
package/dist/cli.js
CHANGED
|
@@ -218,7 +218,7 @@ import { join } from "path";
|
|
|
218
218
|
// package.json
|
|
219
219
|
var package_default = {
|
|
220
220
|
name: "@bman654/clodex",
|
|
221
|
-
version: "2.
|
|
221
|
+
version: "2.3.0",
|
|
222
222
|
publishConfig: {
|
|
223
223
|
access: "public"
|
|
224
224
|
},
|
|
@@ -4237,6 +4237,17 @@ function sanitizeMessage(message) {
|
|
|
4237
4237
|
return line;
|
|
4238
4238
|
}
|
|
4239
4239
|
|
|
4240
|
+
// src/tool-input-sanitize.ts
|
|
4241
|
+
function sanitizeToolInput(input, requiredProps) {
|
|
4242
|
+
const out = /* @__PURE__ */ Object.create(null);
|
|
4243
|
+
for (const [k, v] of Object.entries(input)) {
|
|
4244
|
+
if (v === null) continue;
|
|
4245
|
+
if (Array.isArray(v) && v.length === 0 && !requiredProps?.has(k)) continue;
|
|
4246
|
+
out[k] = v;
|
|
4247
|
+
}
|
|
4248
|
+
return out;
|
|
4249
|
+
}
|
|
4250
|
+
|
|
4240
4251
|
// src/oauth/responses-websocket.ts
|
|
4241
4252
|
var RESPONSES_LITE_HEADER = "x-openai-internal-codex-responses-lite";
|
|
4242
4253
|
var TERMINAL_EVENT_TYPES = /* @__PURE__ */ new Set(["response.completed", "response.failed", "response.incomplete"]);
|
|
@@ -4485,6 +4496,61 @@ function warnReasoningNormalizationGap(fields, log12) {
|
|
|
4485
4496
|
} catch {
|
|
4486
4497
|
}
|
|
4487
4498
|
}
|
|
4499
|
+
function toolArgumentNormalizationGap(expected, actual, requiredProps) {
|
|
4500
|
+
if (conversationItemKind(expected) !== "function_call") return void 0;
|
|
4501
|
+
if (conversationItemKind(actual) !== "function_call") return void 0;
|
|
4502
|
+
const left = expected;
|
|
4503
|
+
const right = actual;
|
|
4504
|
+
const callId = left.call_id;
|
|
4505
|
+
if (typeof callId !== "string" || !callId || callId !== right.call_id) return void 0;
|
|
4506
|
+
if (typeof left.name !== "string" || left.name !== right.name) return void 0;
|
|
4507
|
+
if (canonicalJson(normalizeToolCallJson(left)) === canonicalJson(normalizeToolCallJson(right))) {
|
|
4508
|
+
return void 0;
|
|
4509
|
+
}
|
|
4510
|
+
const required = requiredProps().get(left.name);
|
|
4511
|
+
const stripped = (item) => {
|
|
4512
|
+
if (typeof item.arguments !== "string") return void 0;
|
|
4513
|
+
const raw = item.arguments.trim();
|
|
4514
|
+
try {
|
|
4515
|
+
const parsed = raw === "" ? {} : JSON.parse(raw);
|
|
4516
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return void 0;
|
|
4517
|
+
return canonicalJson({
|
|
4518
|
+
...normalizeToolCallJson(item),
|
|
4519
|
+
arguments: canonicalJson(sanitizeToolInput(parsed, required))
|
|
4520
|
+
});
|
|
4521
|
+
} catch {
|
|
4522
|
+
return void 0;
|
|
4523
|
+
}
|
|
4524
|
+
};
|
|
4525
|
+
const leftStripped = stripped(left);
|
|
4526
|
+
const rightStripped = stripped(right);
|
|
4527
|
+
return {
|
|
4528
|
+
tool: left.name,
|
|
4529
|
+
equalAfterStrip: leftStripped !== void 0 && leftStripped === rightStripped
|
|
4530
|
+
};
|
|
4531
|
+
}
|
|
4532
|
+
var warnedToolArgumentGaps = /* @__PURE__ */ new Set();
|
|
4533
|
+
var MAX_TOOL_ARGUMENT_GAP_WARNINGS = 3;
|
|
4534
|
+
function warnToolArgumentNormalizationGap(gap, log12) {
|
|
4535
|
+
const tool3 = typeof gap.tool === "string" ? gap.tool : "unknown";
|
|
4536
|
+
const signature = `${tool3}:filler`;
|
|
4537
|
+
const message = `clodex: warning: tool call "${tool3}" failed the continuation match, but both sides are identical once clodex's filler-strip rule is applied, so the head should have matched. Prompt caching is degraded for this turn \u2014 please report it, with the adapter debug log from --trace if you can, at https://github.com/bman654/clodex/issues`;
|
|
4538
|
+
try {
|
|
4539
|
+
log12?.(`tool argument normalization gap: ${signature}`);
|
|
4540
|
+
} catch {
|
|
4541
|
+
}
|
|
4542
|
+
if (warnedToolArgumentGaps.has(signature)) return;
|
|
4543
|
+
if (warnedToolArgumentGaps.size >= MAX_TOOL_ARGUMENT_GAP_WARNINGS) return;
|
|
4544
|
+
warnedToolArgumentGaps.add(signature);
|
|
4545
|
+
try {
|
|
4546
|
+
process.stderr.write(`${message}
|
|
4547
|
+
`);
|
|
4548
|
+
if (warnedToolArgumentGaps.size === MAX_TOOL_ARGUMENT_GAP_WARNINGS) {
|
|
4549
|
+
process.stderr.write("clodex: warning: further tool-argument normalization warnings suppressed.\n");
|
|
4550
|
+
}
|
|
4551
|
+
} catch {
|
|
4552
|
+
}
|
|
4553
|
+
}
|
|
4488
4554
|
function continuationMismatchDetails(entry, payload, log12, warnOnGap = false) {
|
|
4489
4555
|
const full = inputArray(payload);
|
|
4490
4556
|
const prefix = [...entry.requestInput ?? [], ...entry.expectedAssistant ?? []];
|
|
@@ -4500,6 +4566,34 @@ function continuationMismatchDetails(entry, payload, log12, warnOnGap = false) {
|
|
|
4500
4566
|
const actual = mismatch < full.length ? full[mismatch] : void 0;
|
|
4501
4567
|
const reasoningGap = reasoningNormalizationGap(expected, actual);
|
|
4502
4568
|
if (reasoningGap && warnOnGap) warnReasoningNormalizationGap(reasoningGap, log12);
|
|
4569
|
+
let gapExpected = expected;
|
|
4570
|
+
if (conversationItemKind(expected) === "reasoning" && conversationItemKind(actual) === "function_call") {
|
|
4571
|
+
for (let index = mismatch; index < prefix.length; index += 1) {
|
|
4572
|
+
if (conversationItemKind(prefix[index]) !== "reasoning") {
|
|
4573
|
+
gapExpected = prefix[index];
|
|
4574
|
+
break;
|
|
4575
|
+
}
|
|
4576
|
+
}
|
|
4577
|
+
}
|
|
4578
|
+
let toolArgumentGap;
|
|
4579
|
+
try {
|
|
4580
|
+
toolArgumentGap = toolArgumentNormalizationGap(
|
|
4581
|
+
gapExpected,
|
|
4582
|
+
actual,
|
|
4583
|
+
// The head's own schema when it has one; the current turn's tools are only a
|
|
4584
|
+
// fallback for a head that predates the snapshot (see headRequiredToolProps).
|
|
4585
|
+
() => entry.headRequiredToolProps ?? requiredToolProps(payload)
|
|
4586
|
+
);
|
|
4587
|
+
} catch {
|
|
4588
|
+
}
|
|
4589
|
+
if (toolArgumentGap?.equalAfterStrip === true) {
|
|
4590
|
+
if (warnOnGap) warnToolArgumentNormalizationGap(toolArgumentGap, log12);
|
|
4591
|
+
} else if (toolArgumentGap && warnOnGap) {
|
|
4592
|
+
try {
|
|
4593
|
+
log12?.(`tool argument mismatch beyond the strip rule: ${String(toolArgumentGap.tool)}`);
|
|
4594
|
+
} catch {
|
|
4595
|
+
}
|
|
4596
|
+
}
|
|
4503
4597
|
return {
|
|
4504
4598
|
fullItems: full.length,
|
|
4505
4599
|
expectedPrefixItems: prefix.length,
|
|
@@ -4517,12 +4611,31 @@ function continuationMismatchDetails(entry, payload, log12, warnOnGap = false) {
|
|
|
4517
4611
|
entry.expectedAssistant ?? [],
|
|
4518
4612
|
mismatch
|
|
4519
4613
|
)
|
|
4520
|
-
} : {}
|
|
4614
|
+
} : {},
|
|
4615
|
+
...toolArgumentGap ? { toolArgumentNormalizationGap: toolArgumentGap } : {}
|
|
4521
4616
|
};
|
|
4522
4617
|
}
|
|
4523
|
-
function continuationMismatchSummary(entry, payload, log12) {
|
|
4524
|
-
const details = continuationMismatchDetails(entry, payload, log12, true);
|
|
4525
|
-
|
|
4618
|
+
function continuationMismatchSummary(entry, payload, log12, mismatchDump = false, precomputedDetails) {
|
|
4619
|
+
const details = precomputedDetails ?? continuationMismatchDetails(entry, payload, log12, true);
|
|
4620
|
+
let summary = `full_items=${details.fullItems} expected_prefix_items=${details.expectedPrefixItems} first_mismatch=${details.firstMismatch} expected=${details.expectedKind} actual=${details.actualKind}`;
|
|
4621
|
+
if (details.expectedHash || details.actualHash) {
|
|
4622
|
+
summary += ` expected_hash=${details.expectedHash ?? "none"} actual_hash=${details.actualHash ?? "none"}`;
|
|
4623
|
+
if (mismatchDump && log12) {
|
|
4624
|
+
const full = inputArray(payload);
|
|
4625
|
+
const prefix = [...entry.requestInput ?? [], ...entry.expectedAssistant ?? []];
|
|
4626
|
+
const index = details.firstMismatch;
|
|
4627
|
+
log12(`mismatch dump expected[${index}]: ${mismatchDumpLine(prefix, index)}`);
|
|
4628
|
+
log12(`mismatch dump actual[${index}]: ${mismatchDumpLine(full, index)}`);
|
|
4629
|
+
}
|
|
4630
|
+
}
|
|
4631
|
+
return summary;
|
|
4632
|
+
}
|
|
4633
|
+
function mismatchDumpLine(items, index) {
|
|
4634
|
+
if (index >= items.length) return "(absent)";
|
|
4635
|
+
const line = canonicalJson(normalizeToolCallJson(items[index]));
|
|
4636
|
+
const max = 2e3;
|
|
4637
|
+
const marker = " [truncated]";
|
|
4638
|
+
return line.length <= max ? line : line.slice(0, max - marker.length) + marker;
|
|
4526
4639
|
}
|
|
4527
4640
|
function canonicalItemStrings(items) {
|
|
4528
4641
|
return items.map((item) => canonicalJson(normalizeToolCallJson([item])));
|
|
@@ -4639,6 +4752,7 @@ function emitContextDiagnostic(entry, ctx, details) {
|
|
|
4639
4752
|
retried: ctx.retried,
|
|
4640
4753
|
frameCount: ctx.frameCount,
|
|
4641
4754
|
emittedModelData: ctx.emittedModelData,
|
|
4755
|
+
emittedDownstreamData: ctx.emittedDownstreamData,
|
|
4642
4756
|
responseIdReceived: Boolean(ctx.responseId),
|
|
4643
4757
|
inFlightMs: entry.inFlightStartedAt === void 0 ? void 0 : Math.max(0, entry.options.now() - entry.inFlightStartedAt),
|
|
4644
4758
|
...details
|
|
@@ -4831,8 +4945,42 @@ function withoutEphemeralFields(item) {
|
|
|
4831
4945
|
}
|
|
4832
4946
|
return out;
|
|
4833
4947
|
}
|
|
4948
|
+
function requiredToolProps(payload) {
|
|
4949
|
+
const map = /* @__PURE__ */ new Map();
|
|
4950
|
+
const add = (tool3) => {
|
|
4951
|
+
if (!tool3 || typeof tool3 !== "object") return;
|
|
4952
|
+
const record = tool3;
|
|
4953
|
+
if (record.type === "namespace" && Array.isArray(record.tools)) {
|
|
4954
|
+
for (const nested of record.tools) add(nested);
|
|
4955
|
+
return;
|
|
4956
|
+
}
|
|
4957
|
+
if (record.type !== "function" || typeof record.name !== "string") return;
|
|
4958
|
+
const parameters = record.parameters;
|
|
4959
|
+
const required = parameters && typeof parameters === "object" && Array.isArray(parameters.required) ? parameters.required : [];
|
|
4960
|
+
map.set(record.name, new Set(required.filter((p13) => typeof p13 === "string")));
|
|
4961
|
+
};
|
|
4962
|
+
if (Array.isArray(payload.tools)) for (const tool3 of payload.tools) add(tool3);
|
|
4963
|
+
return map;
|
|
4964
|
+
}
|
|
4965
|
+
function sanitizedCallArguments(item, requiredProps) {
|
|
4966
|
+
if (typeof item.arguments !== "string") return item;
|
|
4967
|
+
const raw = item.arguments.trim();
|
|
4968
|
+
let parsed;
|
|
4969
|
+
try {
|
|
4970
|
+
parsed = raw === "" ? {} : JSON.parse(raw);
|
|
4971
|
+
} catch {
|
|
4972
|
+
return item;
|
|
4973
|
+
}
|
|
4974
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return item;
|
|
4975
|
+
const required = requiredProps.get(typeof item.name === "string" ? item.name : "");
|
|
4976
|
+
return {
|
|
4977
|
+
...item,
|
|
4978
|
+
arguments: JSON.stringify(sanitizeToolInput(parsed, required))
|
|
4979
|
+
};
|
|
4980
|
+
}
|
|
4834
4981
|
function expectedAssistantItems(ctx) {
|
|
4835
4982
|
const output = [];
|
|
4983
|
+
const requiredProps = requiredToolProps(ctx.originalPayload);
|
|
4836
4984
|
for (const [, accumulator] of [...ctx.outputByIndex.entries()].sort(([left], [right]) => left - right)) {
|
|
4837
4985
|
const done = accumulator.done ?? {};
|
|
4838
4986
|
const type = accumulator.type ?? (typeof done.type === "string" ? done.type : void 0);
|
|
@@ -4847,7 +4995,11 @@ function expectedAssistantItems(ctx) {
|
|
|
4847
4995
|
output.push({ ...withoutEphemeralFields(done), type: "reasoning", summary });
|
|
4848
4996
|
continue;
|
|
4849
4997
|
}
|
|
4850
|
-
if (type === "function_call"
|
|
4998
|
+
if (type === "function_call") {
|
|
4999
|
+
output.push({ ...sanitizedCallArguments(withoutEphemeralFields(done), requiredProps), type });
|
|
5000
|
+
continue;
|
|
5001
|
+
}
|
|
5002
|
+
if (type === "custom_tool_call") {
|
|
4851
5003
|
output.push({ ...withoutEphemeralFields(done), type });
|
|
4852
5004
|
}
|
|
4853
5005
|
}
|
|
@@ -4855,6 +5007,7 @@ function expectedAssistantItems(ctx) {
|
|
|
4855
5007
|
}
|
|
4856
5008
|
function encodeSse(ctx, event) {
|
|
4857
5009
|
if (ctx.closed) return;
|
|
5010
|
+
ctx.emittedDownstreamData = true;
|
|
4858
5011
|
ctx.controller.enqueue(ctx.encoder.encode(`data: ${JSON.stringify(event)}
|
|
4859
5012
|
|
|
4860
5013
|
`));
|
|
@@ -4906,12 +5059,12 @@ function failContext(entry, ctx, message, diagnosticDetails, statusCode, retryAf
|
|
|
4906
5059
|
closeContext(ctx);
|
|
4907
5060
|
}
|
|
4908
5061
|
function retryTransportFailure(entry, ctx, diagnosticDetails) {
|
|
4909
|
-
if (ctx.closed || entry.current !== ctx || ctx.retried || ctx
|
|
5062
|
+
if (ctx.closed || entry.current !== ctx || ctx.retried || !transportReplaySafe(ctx)) {
|
|
4910
5063
|
return false;
|
|
4911
5064
|
}
|
|
4912
5065
|
ctx.retried = true;
|
|
4913
5066
|
ctx.transportRetryPending = true;
|
|
4914
|
-
entry.debug("transport failed before
|
|
5067
|
+
entry.debug("transport failed before downstream output; retrying once with full context");
|
|
4915
5068
|
emitContextDiagnostic(entry, ctx, {
|
|
4916
5069
|
event: "ws_transport_retry",
|
|
4917
5070
|
outcome: "started",
|
|
@@ -4945,9 +5098,9 @@ function retryTransportFailure(entry, ctx, diagnosticDetails) {
|
|
|
4945
5098
|
function handleTransportFailure(entry, ctx, message, diagnosticDetails) {
|
|
4946
5099
|
if (retryTransportFailure(entry, ctx, diagnosticDetails)) return;
|
|
4947
5100
|
if (ctx.closed || entry.current !== ctx) return;
|
|
4948
|
-
if (ctx.retried && ctx.
|
|
5101
|
+
if (ctx.retried && ctx.transportRetryPending && transportReplaySafe(ctx)) {
|
|
4949
5102
|
ctx.transportRetryPending = false;
|
|
4950
|
-
entry.debug("transport retry exhausted before
|
|
5103
|
+
entry.debug("transport retry exhausted before downstream output");
|
|
4951
5104
|
emitContextDiagnostic(entry, ctx, {
|
|
4952
5105
|
event: "ws_transport_retry",
|
|
4953
5106
|
outcome: "exhausted",
|
|
@@ -5051,6 +5204,9 @@ function resetContextForRetry(ctx) {
|
|
|
5051
5204
|
ctx.recentUpstreamEventTypes = [];
|
|
5052
5205
|
ctx.emittedProtocolAnomalies.clear();
|
|
5053
5206
|
}
|
|
5207
|
+
function transportReplaySafe(ctx) {
|
|
5208
|
+
return !ctx.emittedDownstreamData && !ctx.emittedModelData && ctx.outputByIndex.size === 0;
|
|
5209
|
+
}
|
|
5054
5210
|
function handleSocketMessage(entry, data) {
|
|
5055
5211
|
const ctx = entry.current;
|
|
5056
5212
|
if (!ctx || ctx.closed) return;
|
|
@@ -5165,6 +5321,7 @@ function handleSocketMessage(entry, data) {
|
|
|
5165
5321
|
entry.responseId = ctx.responseId;
|
|
5166
5322
|
entry.requestInput = inputArray(ctx.originalPayload);
|
|
5167
5323
|
entry.expectedAssistant = expectedAssistantItems(ctx);
|
|
5324
|
+
entry.headRequiredToolProps = requiredToolProps(ctx.originalPayload);
|
|
5168
5325
|
entry.canonicalPrefix = void 0;
|
|
5169
5326
|
entry.canonicalEchoablePrefix = void 0;
|
|
5170
5327
|
entry.promptFieldHashes = ctx.promptFieldHashes;
|
|
@@ -5294,6 +5451,7 @@ function createResponsesWebSocketFetch(wsUrl, log12, options = {}) {
|
|
|
5294
5451
|
} catch {
|
|
5295
5452
|
}
|
|
5296
5453
|
};
|
|
5454
|
+
const mismatchDump = process.env.CLODEX_MISMATCH_DUMP === "1";
|
|
5297
5455
|
const resolvedOptions = {
|
|
5298
5456
|
hardTtlMs: options.hardTtlMs ?? RESPONSES_WS_HARD_TTL_MS,
|
|
5299
5457
|
idleTtlMs: options.idleTtlMs ?? RESPONSES_WS_IDLE_TTL_MS,
|
|
@@ -5349,6 +5507,7 @@ function createResponsesWebSocketFetch(wsUrl, log12, options = {}) {
|
|
|
5349
5507
|
let persistent = Boolean(partitionKey);
|
|
5350
5508
|
let promotedConnectionId;
|
|
5351
5509
|
let decision;
|
|
5510
|
+
let candidateMismatchDetails;
|
|
5352
5511
|
if (selected && selectedDelta) {
|
|
5353
5512
|
sendPayload = { ...payload, input: selectedDelta, previous_response_id: selected.responseId };
|
|
5354
5513
|
continued = true;
|
|
@@ -5371,8 +5530,23 @@ function createResponsesWebSocketFetch(wsUrl, log12, options = {}) {
|
|
|
5371
5530
|
decision = "parallel_isolated";
|
|
5372
5531
|
debug("parallel request using an isolated socket");
|
|
5373
5532
|
} else if (diagnosticEntry) {
|
|
5533
|
+
const diagnosticMismatch = continuationMismatchDetails(diagnosticEntry, payload, debug, true);
|
|
5534
|
+
candidateMismatchDetails = /* @__PURE__ */ new Map([[diagnosticEntry, diagnosticMismatch]]);
|
|
5535
|
+
for (const candidate of candidates) {
|
|
5536
|
+
if (candidate === diagnosticEntry) continue;
|
|
5537
|
+
candidateMismatchDetails.set(
|
|
5538
|
+
candidate,
|
|
5539
|
+
continuationMismatchDetails(candidate, payload, debug, true)
|
|
5540
|
+
);
|
|
5541
|
+
}
|
|
5374
5542
|
debug(
|
|
5375
|
-
`history mismatch starting an additional chain; retained ${candidates.length} existing head(s) (${continuationMismatchSummary(
|
|
5543
|
+
`history mismatch starting an additional chain; retained ${candidates.length} existing head(s) (${continuationMismatchSummary(
|
|
5544
|
+
diagnosticEntry,
|
|
5545
|
+
payload,
|
|
5546
|
+
debug,
|
|
5547
|
+
mismatchDump,
|
|
5548
|
+
diagnosticMismatch
|
|
5549
|
+
)})`
|
|
5376
5550
|
);
|
|
5377
5551
|
decision = "history_mismatch_new_head";
|
|
5378
5552
|
} else if (partitionKey) {
|
|
@@ -5432,7 +5606,7 @@ function createResponsesWebSocketFetch(wsUrl, log12, options = {}) {
|
|
|
5432
5606
|
ttlPausedMs: entry.ttlPausedMs,
|
|
5433
5607
|
idleMs: Math.max(0, now - entry.lastUsedAt),
|
|
5434
5608
|
promptChanges: changedPromptFields(entry.promptFieldHashes, promptFieldHashes),
|
|
5435
|
-
mismatch: continuationMismatchDetails(entry, payload, debug)
|
|
5609
|
+
mismatch: candidateMismatchDetails?.get(entry) ?? continuationMismatchDetails(entry, payload, debug)
|
|
5436
5610
|
})),
|
|
5437
5611
|
evictions
|
|
5438
5612
|
}, diagnosticCorrelation);
|
|
@@ -5452,6 +5626,7 @@ function createResponsesWebSocketFetch(wsUrl, log12, options = {}) {
|
|
|
5452
5626
|
frameCount: 0,
|
|
5453
5627
|
pendingEvents: [],
|
|
5454
5628
|
emittedModelData: false,
|
|
5629
|
+
emittedDownstreamData: false,
|
|
5455
5630
|
transportRetryPending: false,
|
|
5456
5631
|
outputByIndex: /* @__PURE__ */ new Map(),
|
|
5457
5632
|
outputIndexByItemId: /* @__PURE__ */ new Map(),
|
|
@@ -9814,6 +9989,41 @@ function resolveUpstreamTools(tools, messages) {
|
|
|
9814
9989
|
return upstream;
|
|
9815
9990
|
}
|
|
9816
9991
|
|
|
9992
|
+
// src/upstream-retry.ts
|
|
9993
|
+
var UPSTREAM_MAX_RETRIES_ENV = "CLODEX_UPSTREAM_MAX_RETRIES";
|
|
9994
|
+
var MAX_UPSTREAM_MAX_RETRIES = 5;
|
|
9995
|
+
var reportedValues = /* @__PURE__ */ new Set();
|
|
9996
|
+
function reportOnce(raw, message, warn) {
|
|
9997
|
+
if (reportedValues.has(raw)) return;
|
|
9998
|
+
reportedValues.add(raw);
|
|
9999
|
+
try {
|
|
10000
|
+
warn(message);
|
|
10001
|
+
} catch {
|
|
10002
|
+
}
|
|
10003
|
+
}
|
|
10004
|
+
function upstreamMaxRetries(env = process.env, warn = (message) => console.error(`clodex: ${message}`)) {
|
|
10005
|
+
const raw = env[UPSTREAM_MAX_RETRIES_ENV]?.trim();
|
|
10006
|
+
if (raw === void 0 || raw === "") return void 0;
|
|
10007
|
+
const value = Number(raw);
|
|
10008
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
10009
|
+
reportOnce(
|
|
10010
|
+
raw,
|
|
10011
|
+
`ignoring ${UPSTREAM_MAX_RETRIES_ENV}=${raw} (expected a non-negative integer)`,
|
|
10012
|
+
warn
|
|
10013
|
+
);
|
|
10014
|
+
return void 0;
|
|
10015
|
+
}
|
|
10016
|
+
if (value > MAX_UPSTREAM_MAX_RETRIES) {
|
|
10017
|
+
reportOnce(
|
|
10018
|
+
raw,
|
|
10019
|
+
`clamping ${UPSTREAM_MAX_RETRIES_ENV}=${raw} to ${MAX_UPSTREAM_MAX_RETRIES} (higher values exceed the 120s streaming idle budget)`,
|
|
10020
|
+
warn
|
|
10021
|
+
);
|
|
10022
|
+
return MAX_UPSTREAM_MAX_RETRIES;
|
|
10023
|
+
}
|
|
10024
|
+
return value;
|
|
10025
|
+
}
|
|
10026
|
+
|
|
9817
10027
|
// src/sdk-adapter.ts
|
|
9818
10028
|
function sdkTranslationErrorSignature(error) {
|
|
9819
10029
|
const message = error instanceof Error ? error.message : typeof error === "string" ? error : void 0;
|
|
@@ -9884,10 +10094,12 @@ function openAiCacheBreakpoint(block, enabled) {
|
|
|
9884
10094
|
function translateTopLevelSystemForOpenAi(system) {
|
|
9885
10095
|
if (!system) return [];
|
|
9886
10096
|
if (typeof system === "string") {
|
|
9887
|
-
|
|
10097
|
+
const stripped = stripClaudeCodeBillingHeader(system);
|
|
10098
|
+
return stripped?.trim() ? [{ role: "system", content: stripped }] : [];
|
|
9888
10099
|
}
|
|
9889
10100
|
return system.flatMap((block) => {
|
|
9890
|
-
const
|
|
10101
|
+
const raw = typeof block === "string" ? block : block.text ?? "";
|
|
10102
|
+
const text4 = stripClaudeCodeBillingHeader(raw) ?? "";
|
|
9891
10103
|
if (!text4.trim()) return [];
|
|
9892
10104
|
const cacheControl = typeof block === "string" ? void 0 : block.cache_control;
|
|
9893
10105
|
return [{
|
|
@@ -10035,15 +10247,6 @@ function translateMessages(messages, npm, openAiPromptCacheBreakpoints = false)
|
|
|
10035
10247
|
}
|
|
10036
10248
|
return out;
|
|
10037
10249
|
}
|
|
10038
|
-
function sanitizeToolInput(input, requiredProps) {
|
|
10039
|
-
const out = {};
|
|
10040
|
-
for (const [k, v] of Object.entries(input)) {
|
|
10041
|
-
if (v === null) continue;
|
|
10042
|
-
if (Array.isArray(v) && v.length === 0 && !requiredProps?.has(k)) continue;
|
|
10043
|
-
out[k] = v;
|
|
10044
|
-
}
|
|
10045
|
-
return out;
|
|
10046
|
-
}
|
|
10047
10250
|
function toolRequiredProps(tools) {
|
|
10048
10251
|
const map = /* @__PURE__ */ new Map();
|
|
10049
10252
|
for (const [name, t] of Object.entries(tools ?? {})) {
|
|
@@ -10082,7 +10285,7 @@ function isClaudeCodeStructuredOutputCompactRequest(body) {
|
|
|
10082
10285
|
function translateRequest(body, npm, options) {
|
|
10083
10286
|
const messages = body.messages ?? [];
|
|
10084
10287
|
annotateToolNames(messages);
|
|
10085
|
-
const baseSystem = systemToString(body.system,
|
|
10288
|
+
const baseSystem = systemToString(body.system, true);
|
|
10086
10289
|
const systemText = baseSystem?.trim() || (options?.openAiOAuth ? "You are a coding assistant." : void 0);
|
|
10087
10290
|
const compactRequest = isClaudeCodeStructuredOutputCompactRequest(body);
|
|
10088
10291
|
let upstreamTools = resolveUpstreamTools(
|
|
@@ -10373,6 +10576,7 @@ async function streamAnthropicResponse(model, params, modelId, write, log12, obs
|
|
|
10373
10576
|
const result = streamText({
|
|
10374
10577
|
model,
|
|
10375
10578
|
...params,
|
|
10579
|
+
maxRetries: upstreamMaxRetries(),
|
|
10376
10580
|
abortSignal,
|
|
10377
10581
|
onError: () => {
|
|
10378
10582
|
}
|
|
@@ -10421,6 +10625,7 @@ async function generateAnthropicResponse(model, params, modelId, options) {
|
|
|
10421
10625
|
const r = streamText({
|
|
10422
10626
|
model,
|
|
10423
10627
|
...params,
|
|
10628
|
+
maxRetries: upstreamMaxRetries(),
|
|
10424
10629
|
abortSignal,
|
|
10425
10630
|
onError: () => {
|
|
10426
10631
|
}
|
|
@@ -10477,6 +10682,7 @@ async function generateAnthropicResponse(model, params, modelId, options) {
|
|
|
10477
10682
|
const r = await generateText({
|
|
10478
10683
|
model,
|
|
10479
10684
|
...params,
|
|
10685
|
+
maxRetries: upstreamMaxRetries(),
|
|
10480
10686
|
abortSignal: generateAbort.signal
|
|
10481
10687
|
});
|
|
10482
10688
|
({ text: text4, toolCalls, finishReason, usage } = r);
|
|
@@ -11431,11 +11637,20 @@ async function collectOpenAiStream(stream) {
|
|
|
11431
11637
|
async function generateOpenAiResponse(model, params, responseModelId, options) {
|
|
11432
11638
|
let result;
|
|
11433
11639
|
if (options?.forceStream) {
|
|
11434
|
-
const { stream } = streamText2({
|
|
11435
|
-
|
|
11640
|
+
const { stream } = streamText2({
|
|
11641
|
+
model,
|
|
11642
|
+
...params,
|
|
11643
|
+
maxRetries: upstreamMaxRetries(),
|
|
11644
|
+
onError: () => {
|
|
11645
|
+
}
|
|
11646
|
+
});
|
|
11436
11647
|
result = await collectOpenAiStream(stream);
|
|
11437
11648
|
} else {
|
|
11438
|
-
result = await generateText2({
|
|
11649
|
+
result = await generateText2({
|
|
11650
|
+
model,
|
|
11651
|
+
...params,
|
|
11652
|
+
maxRetries: upstreamMaxRetries()
|
|
11653
|
+
});
|
|
11439
11654
|
}
|
|
11440
11655
|
const message = { role: "assistant", content: result.text || null };
|
|
11441
11656
|
if (result.toolCalls?.length) {
|
|
@@ -11459,7 +11674,11 @@ async function generateOpenAiResponse(model, params, responseModelId, options) {
|
|
|
11459
11674
|
};
|
|
11460
11675
|
}
|
|
11461
11676
|
async function streamOpenAiResponse(model, params, responseModelId, onChunk) {
|
|
11462
|
-
const { stream } = streamText2({
|
|
11677
|
+
const { stream } = streamText2({
|
|
11678
|
+
model,
|
|
11679
|
+
...params,
|
|
11680
|
+
maxRetries: upstreamMaxRetries()
|
|
11681
|
+
});
|
|
11463
11682
|
const baseData = {
|
|
11464
11683
|
id: `chatcmpl-${Date.now()}`,
|
|
11465
11684
|
object: "chat.completion.chunk",
|