@bman654/clodex 2.2.2 → 2.4.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/{chunk-W4SVDQCZ.js → chunk-LBEJOEUY.js} +7 -1
- package/dist/chunk-LBEJOEUY.js.map +1 -0
- package/dist/claude-wrapper.js +1 -1
- package/dist/cli.js +534 -54
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-W4SVDQCZ.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -40,7 +40,7 @@ import {
|
|
|
40
40
|
withProviderMutationLock,
|
|
41
41
|
withRegistryWriteLock,
|
|
42
42
|
withRegistryWriteLockSync
|
|
43
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-LBEJOEUY.js";
|
|
44
44
|
|
|
45
45
|
// src/cli.ts
|
|
46
46
|
import pc13 from "picocolors";
|
|
@@ -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.4.0",
|
|
222
222
|
publishConfig: {
|
|
223
223
|
access: "public"
|
|
224
224
|
},
|
|
@@ -840,6 +840,51 @@ function applyClaudeCodeThirdPartyCompat(env) {
|
|
|
840
840
|
env["ENABLE_TOOL_SEARCH"] = "true";
|
|
841
841
|
env["CLAUDE_CODE_SIMPLE_SYSTEM_PROMPT"] = "0";
|
|
842
842
|
}
|
|
843
|
+
var LOOPBACK_NO_PROXY_ENTRIES = ["localhost", "127.0.0.1", "::1"];
|
|
844
|
+
function gatewayHostname(gatewayUrl) {
|
|
845
|
+
try {
|
|
846
|
+
return new URL(gatewayUrl).hostname.replace(/^\[|\]$/g, "").toLowerCase();
|
|
847
|
+
} catch {
|
|
848
|
+
return "";
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
function isLoopbackHost(host) {
|
|
852
|
+
if (host === "localhost" || host.endsWith(".localhost")) return true;
|
|
853
|
+
if (host === "::1" || host === "0:0:0:0:0:0:0:1") return true;
|
|
854
|
+
const v4 = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(host);
|
|
855
|
+
if (!v4) return false;
|
|
856
|
+
const octets = v4.slice(1).map(Number);
|
|
857
|
+
if (octets.some((o) => o > 255)) return false;
|
|
858
|
+
return octets[0] === 127;
|
|
859
|
+
}
|
|
860
|
+
function effectiveNoProxyValue(env) {
|
|
861
|
+
return env["no_proxy"] || env["NO_PROXY"] || "";
|
|
862
|
+
}
|
|
863
|
+
function normalizedNoProxyEntries(env) {
|
|
864
|
+
return effectiveNoProxyValue(env).split(",").map((value) => value.trim()).filter(Boolean);
|
|
865
|
+
}
|
|
866
|
+
function addGatewayNoProxyBypass(env, gatewayUrl) {
|
|
867
|
+
const hasProxy = Boolean(
|
|
868
|
+
env["HTTPS_PROXY"]?.trim() || env["https_proxy"]?.trim() || env["HTTP_PROXY"]?.trim() || env["http_proxy"]?.trim()
|
|
869
|
+
);
|
|
870
|
+
if (!hasProxy) return;
|
|
871
|
+
const host = gatewayHostname(gatewayUrl);
|
|
872
|
+
if (!isLoopbackHost(host)) return;
|
|
873
|
+
if (effectiveNoProxyValue(env) === "*") return;
|
|
874
|
+
const existing = normalizedNoProxyEntries(env);
|
|
875
|
+
const additions = [...LOOPBACK_NO_PROXY_ENTRIES, host];
|
|
876
|
+
const seen = new Set(existing.map((entry) => entry.toLowerCase()));
|
|
877
|
+
const merged = [...existing];
|
|
878
|
+
for (const entry of additions) {
|
|
879
|
+
const key = entry.toLowerCase();
|
|
880
|
+
if (seen.has(key)) continue;
|
|
881
|
+
seen.add(key);
|
|
882
|
+
merged.push(entry);
|
|
883
|
+
}
|
|
884
|
+
const value = merged.join(",");
|
|
885
|
+
env["NO_PROXY"] = value;
|
|
886
|
+
env["no_proxy"] = value;
|
|
887
|
+
}
|
|
843
888
|
function buildChildEnv(baseUrl, model, apiKey, proxyPort, contextWindow, enableGatewayDiscovery) {
|
|
844
889
|
const env = { ...process.env };
|
|
845
890
|
for (const name of CONFLICTING_ENV_VARS) {
|
|
@@ -847,6 +892,7 @@ function buildChildEnv(baseUrl, model, apiKey, proxyPort, contextWindow, enableG
|
|
|
847
892
|
}
|
|
848
893
|
env["ANTHROPIC_BASE_URL"] = proxyPort ? `http://127.0.0.1:${proxyPort}` : baseUrl;
|
|
849
894
|
env["ANTHROPIC_API_KEY"] = apiKey;
|
|
895
|
+
addGatewayNoProxyBypass(env, env["ANTHROPIC_BASE_URL"]);
|
|
850
896
|
const bareModel = stripOneMContextSuffix(model);
|
|
851
897
|
env["ANTHROPIC_MODEL"] = claudeCodeClientModelId(model, contextWindow);
|
|
852
898
|
env["CLAUDE_CODE_MAX_CONTEXT_TOKENS"] = String(resolveContextWindow(bareModel, contextWindow));
|
|
@@ -3414,6 +3460,7 @@ function applyPricingToRegistryProviders(registry, cache) {
|
|
|
3414
3460
|
const index = buildPricingIndex(cache);
|
|
3415
3461
|
let changed = false;
|
|
3416
3462
|
for (const provider of registry.providers) {
|
|
3463
|
+
if (provider.preserveModelPricing) continue;
|
|
3417
3464
|
if (!provider.modelsCache?.models.length) continue;
|
|
3418
3465
|
const platform = TEMPLATE_TO_PRICING_PLATFORM[provider.templateId] ?? TEMPLATE_TO_PRICING_PLATFORM[provider.id];
|
|
3419
3466
|
const enriched = enrichModelsWithPricing(provider.modelsCache.models, index, platform);
|
|
@@ -3716,7 +3763,9 @@ function cachedModelToLocal(cached, provider) {
|
|
|
3716
3763
|
reasoning: cached.reasoning ?? modelsDev?.reasoning,
|
|
3717
3764
|
interleavedReasoningField: cached.interleavedReasoningField ?? modelsDev?.interleaved?.field,
|
|
3718
3765
|
useResponsesLite: cached.useResponsesLite,
|
|
3719
|
-
preferWebSockets: cached.preferWebSockets
|
|
3766
|
+
preferWebSockets: cached.preferWebSockets,
|
|
3767
|
+
modalities: cached.modalities,
|
|
3768
|
+
compatibility: cached.compatibility
|
|
3720
3769
|
};
|
|
3721
3770
|
}
|
|
3722
3771
|
function isAnonymousProvider(provider) {
|
|
@@ -3926,6 +3975,7 @@ function localProvidersToServerModels(localProviders) {
|
|
|
3926
3975
|
interleavedReasoningField: model.interleavedReasoningField,
|
|
3927
3976
|
useResponsesLite: model.useResponsesLite,
|
|
3928
3977
|
preferWebSockets: model.preferWebSockets,
|
|
3978
|
+
compatibility: model.compatibility,
|
|
3929
3979
|
headers: provider.headers,
|
|
3930
3980
|
providerData: provider.providerData
|
|
3931
3981
|
}))
|
|
@@ -4044,7 +4094,7 @@ function frameStatusCode(code, discriminator) {
|
|
|
4044
4094
|
const numeric = Number(code);
|
|
4045
4095
|
if (numeric >= 400 && numeric <= 599) return numeric;
|
|
4046
4096
|
}
|
|
4047
|
-
if (/insufficient_quota|rate_limit/.test(discriminator)) return 429;
|
|
4097
|
+
if (/insufficient_quota|rate_limit|usage_limit/.test(discriminator)) return 429;
|
|
4048
4098
|
if (discriminator.includes("authentication")) return 401;
|
|
4049
4099
|
if (discriminator.includes("permission")) return 403;
|
|
4050
4100
|
if (discriminator.includes("not_found")) return 404;
|
|
@@ -4496,6 +4546,61 @@ function warnReasoningNormalizationGap(fields, log12) {
|
|
|
4496
4546
|
} catch {
|
|
4497
4547
|
}
|
|
4498
4548
|
}
|
|
4549
|
+
function toolArgumentNormalizationGap(expected, actual, requiredProps) {
|
|
4550
|
+
if (conversationItemKind(expected) !== "function_call") return void 0;
|
|
4551
|
+
if (conversationItemKind(actual) !== "function_call") return void 0;
|
|
4552
|
+
const left = expected;
|
|
4553
|
+
const right = actual;
|
|
4554
|
+
const callId = left.call_id;
|
|
4555
|
+
if (typeof callId !== "string" || !callId || callId !== right.call_id) return void 0;
|
|
4556
|
+
if (typeof left.name !== "string" || left.name !== right.name) return void 0;
|
|
4557
|
+
if (canonicalJson(normalizeToolCallJson(left)) === canonicalJson(normalizeToolCallJson(right))) {
|
|
4558
|
+
return void 0;
|
|
4559
|
+
}
|
|
4560
|
+
const required = requiredProps().get(left.name);
|
|
4561
|
+
const stripped = (item) => {
|
|
4562
|
+
if (typeof item.arguments !== "string") return void 0;
|
|
4563
|
+
const raw = item.arguments.trim();
|
|
4564
|
+
try {
|
|
4565
|
+
const parsed = raw === "" ? {} : JSON.parse(raw);
|
|
4566
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return void 0;
|
|
4567
|
+
return canonicalJson({
|
|
4568
|
+
...normalizeToolCallJson(item),
|
|
4569
|
+
arguments: canonicalJson(sanitizeToolInput(parsed, required))
|
|
4570
|
+
});
|
|
4571
|
+
} catch {
|
|
4572
|
+
return void 0;
|
|
4573
|
+
}
|
|
4574
|
+
};
|
|
4575
|
+
const leftStripped = stripped(left);
|
|
4576
|
+
const rightStripped = stripped(right);
|
|
4577
|
+
return {
|
|
4578
|
+
tool: left.name,
|
|
4579
|
+
equalAfterStrip: leftStripped !== void 0 && leftStripped === rightStripped
|
|
4580
|
+
};
|
|
4581
|
+
}
|
|
4582
|
+
var warnedToolArgumentGaps = /* @__PURE__ */ new Set();
|
|
4583
|
+
var MAX_TOOL_ARGUMENT_GAP_WARNINGS = 3;
|
|
4584
|
+
function warnToolArgumentNormalizationGap(gap, log12) {
|
|
4585
|
+
const tool3 = typeof gap.tool === "string" ? gap.tool : "unknown";
|
|
4586
|
+
const signature = `${tool3}:filler`;
|
|
4587
|
+
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`;
|
|
4588
|
+
try {
|
|
4589
|
+
log12?.(`tool argument normalization gap: ${signature}`);
|
|
4590
|
+
} catch {
|
|
4591
|
+
}
|
|
4592
|
+
if (warnedToolArgumentGaps.has(signature)) return;
|
|
4593
|
+
if (warnedToolArgumentGaps.size >= MAX_TOOL_ARGUMENT_GAP_WARNINGS) return;
|
|
4594
|
+
warnedToolArgumentGaps.add(signature);
|
|
4595
|
+
try {
|
|
4596
|
+
process.stderr.write(`${message}
|
|
4597
|
+
`);
|
|
4598
|
+
if (warnedToolArgumentGaps.size === MAX_TOOL_ARGUMENT_GAP_WARNINGS) {
|
|
4599
|
+
process.stderr.write("clodex: warning: further tool-argument normalization warnings suppressed.\n");
|
|
4600
|
+
}
|
|
4601
|
+
} catch {
|
|
4602
|
+
}
|
|
4603
|
+
}
|
|
4499
4604
|
function continuationMismatchDetails(entry, payload, log12, warnOnGap = false) {
|
|
4500
4605
|
const full = inputArray(payload);
|
|
4501
4606
|
const prefix = [...entry.requestInput ?? [], ...entry.expectedAssistant ?? []];
|
|
@@ -4511,6 +4616,34 @@ function continuationMismatchDetails(entry, payload, log12, warnOnGap = false) {
|
|
|
4511
4616
|
const actual = mismatch < full.length ? full[mismatch] : void 0;
|
|
4512
4617
|
const reasoningGap = reasoningNormalizationGap(expected, actual);
|
|
4513
4618
|
if (reasoningGap && warnOnGap) warnReasoningNormalizationGap(reasoningGap, log12);
|
|
4619
|
+
let gapExpected = expected;
|
|
4620
|
+
if (conversationItemKind(expected) === "reasoning" && conversationItemKind(actual) === "function_call") {
|
|
4621
|
+
for (let index = mismatch; index < prefix.length; index += 1) {
|
|
4622
|
+
if (conversationItemKind(prefix[index]) !== "reasoning") {
|
|
4623
|
+
gapExpected = prefix[index];
|
|
4624
|
+
break;
|
|
4625
|
+
}
|
|
4626
|
+
}
|
|
4627
|
+
}
|
|
4628
|
+
let toolArgumentGap;
|
|
4629
|
+
try {
|
|
4630
|
+
toolArgumentGap = toolArgumentNormalizationGap(
|
|
4631
|
+
gapExpected,
|
|
4632
|
+
actual,
|
|
4633
|
+
// The head's own schema when it has one; the current turn's tools are only a
|
|
4634
|
+
// fallback for a head that predates the snapshot (see headRequiredToolProps).
|
|
4635
|
+
() => entry.headRequiredToolProps ?? requiredToolProps(payload)
|
|
4636
|
+
);
|
|
4637
|
+
} catch {
|
|
4638
|
+
}
|
|
4639
|
+
if (toolArgumentGap?.equalAfterStrip === true) {
|
|
4640
|
+
if (warnOnGap) warnToolArgumentNormalizationGap(toolArgumentGap, log12);
|
|
4641
|
+
} else if (toolArgumentGap && warnOnGap) {
|
|
4642
|
+
try {
|
|
4643
|
+
log12?.(`tool argument mismatch beyond the strip rule: ${String(toolArgumentGap.tool)}`);
|
|
4644
|
+
} catch {
|
|
4645
|
+
}
|
|
4646
|
+
}
|
|
4514
4647
|
return {
|
|
4515
4648
|
fullItems: full.length,
|
|
4516
4649
|
expectedPrefixItems: prefix.length,
|
|
@@ -4528,11 +4661,12 @@ function continuationMismatchDetails(entry, payload, log12, warnOnGap = false) {
|
|
|
4528
4661
|
entry.expectedAssistant ?? [],
|
|
4529
4662
|
mismatch
|
|
4530
4663
|
)
|
|
4531
|
-
} : {}
|
|
4664
|
+
} : {},
|
|
4665
|
+
...toolArgumentGap ? { toolArgumentNormalizationGap: toolArgumentGap } : {}
|
|
4532
4666
|
};
|
|
4533
4667
|
}
|
|
4534
|
-
function continuationMismatchSummary(entry, payload, log12, mismatchDump = false) {
|
|
4535
|
-
const details = continuationMismatchDetails(entry, payload, log12, true);
|
|
4668
|
+
function continuationMismatchSummary(entry, payload, log12, mismatchDump = false, precomputedDetails) {
|
|
4669
|
+
const details = precomputedDetails ?? continuationMismatchDetails(entry, payload, log12, true);
|
|
4536
4670
|
let summary = `full_items=${details.fullItems} expected_prefix_items=${details.expectedPrefixItems} first_mismatch=${details.firstMismatch} expected=${details.expectedKind} actual=${details.actualKind}`;
|
|
4537
4671
|
if (details.expectedHash || details.actualHash) {
|
|
4538
4672
|
summary += ` expected_hash=${details.expectedHash ?? "none"} actual_hash=${details.actualHash ?? "none"}`;
|
|
@@ -4668,6 +4802,7 @@ function emitContextDiagnostic(entry, ctx, details) {
|
|
|
4668
4802
|
retried: ctx.retried,
|
|
4669
4803
|
frameCount: ctx.frameCount,
|
|
4670
4804
|
emittedModelData: ctx.emittedModelData,
|
|
4805
|
+
emittedDownstreamData: ctx.emittedDownstreamData,
|
|
4671
4806
|
responseIdReceived: Boolean(ctx.responseId),
|
|
4672
4807
|
inFlightMs: entry.inFlightStartedAt === void 0 ? void 0 : Math.max(0, entry.options.now() - entry.inFlightStartedAt),
|
|
4673
4808
|
...details
|
|
@@ -4922,6 +5057,7 @@ function expectedAssistantItems(ctx) {
|
|
|
4922
5057
|
}
|
|
4923
5058
|
function encodeSse(ctx, event) {
|
|
4924
5059
|
if (ctx.closed) return;
|
|
5060
|
+
ctx.emittedDownstreamData = true;
|
|
4925
5061
|
ctx.controller.enqueue(ctx.encoder.encode(`data: ${JSON.stringify(event)}
|
|
4926
5062
|
|
|
4927
5063
|
`));
|
|
@@ -4973,12 +5109,12 @@ function failContext(entry, ctx, message, diagnosticDetails, statusCode, retryAf
|
|
|
4973
5109
|
closeContext(ctx);
|
|
4974
5110
|
}
|
|
4975
5111
|
function retryTransportFailure(entry, ctx, diagnosticDetails) {
|
|
4976
|
-
if (ctx.closed || entry.current !== ctx || ctx.retried || ctx
|
|
5112
|
+
if (ctx.closed || entry.current !== ctx || ctx.retried || !transportReplaySafe(ctx)) {
|
|
4977
5113
|
return false;
|
|
4978
5114
|
}
|
|
4979
5115
|
ctx.retried = true;
|
|
4980
5116
|
ctx.transportRetryPending = true;
|
|
4981
|
-
entry.debug("transport failed before
|
|
5117
|
+
entry.debug("transport failed before downstream output; retrying once with full context");
|
|
4982
5118
|
emitContextDiagnostic(entry, ctx, {
|
|
4983
5119
|
event: "ws_transport_retry",
|
|
4984
5120
|
outcome: "started",
|
|
@@ -5012,9 +5148,9 @@ function retryTransportFailure(entry, ctx, diagnosticDetails) {
|
|
|
5012
5148
|
function handleTransportFailure(entry, ctx, message, diagnosticDetails) {
|
|
5013
5149
|
if (retryTransportFailure(entry, ctx, diagnosticDetails)) return;
|
|
5014
5150
|
if (ctx.closed || entry.current !== ctx) return;
|
|
5015
|
-
if (ctx.retried && ctx.
|
|
5151
|
+
if (ctx.retried && ctx.transportRetryPending && transportReplaySafe(ctx)) {
|
|
5016
5152
|
ctx.transportRetryPending = false;
|
|
5017
|
-
entry.debug("transport retry exhausted before
|
|
5153
|
+
entry.debug("transport retry exhausted before downstream output");
|
|
5018
5154
|
emitContextDiagnostic(entry, ctx, {
|
|
5019
5155
|
event: "ws_transport_retry",
|
|
5020
5156
|
outcome: "exhausted",
|
|
@@ -5118,6 +5254,9 @@ function resetContextForRetry(ctx) {
|
|
|
5118
5254
|
ctx.recentUpstreamEventTypes = [];
|
|
5119
5255
|
ctx.emittedProtocolAnomalies.clear();
|
|
5120
5256
|
}
|
|
5257
|
+
function transportReplaySafe(ctx) {
|
|
5258
|
+
return !ctx.emittedDownstreamData && !ctx.emittedModelData && ctx.outputByIndex.size === 0;
|
|
5259
|
+
}
|
|
5121
5260
|
function handleSocketMessage(entry, data) {
|
|
5122
5261
|
const ctx = entry.current;
|
|
5123
5262
|
if (!ctx || ctx.closed) return;
|
|
@@ -5178,7 +5317,8 @@ function handleSocketMessage(entry, data) {
|
|
|
5178
5317
|
return;
|
|
5179
5318
|
}
|
|
5180
5319
|
const errorStatus = type === "error" && !ctx.emittedModelData ? responseErrorStatus(event) : void 0;
|
|
5181
|
-
|
|
5320
|
+
const emptyFailureTerminal = FAILURE_EVENT_TYPES.has(type ?? "") && errorStatus === void 0 && !willRetry && !ctx.emittedModelData;
|
|
5321
|
+
if (FAILURE_EVENT_TYPES.has(type ?? "") && (errorStatus === void 0 || willRetry) && !emptyFailureTerminal) {
|
|
5182
5322
|
emitResponseErrorDiagnostic(entry, ctx, {
|
|
5183
5323
|
source: "response_event",
|
|
5184
5324
|
upstreamEventType: type,
|
|
@@ -5221,6 +5361,39 @@ function handleSocketMessage(entry, data) {
|
|
|
5221
5361
|
);
|
|
5222
5362
|
return;
|
|
5223
5363
|
}
|
|
5364
|
+
if (emptyFailureTerminal) {
|
|
5365
|
+
const details = responseFailureDetails(event);
|
|
5366
|
+
const named = [details.errorType, details.errorCode, details.incompleteReason].filter((value) => typeof value === "string");
|
|
5367
|
+
const summary = "OpenAI ended the response with no output" + (named.length ? ` (${named.join(" / ")})` : ` (${type})`);
|
|
5368
|
+
const discriminator = [details.errorType, details.errorCode].filter((value) => typeof value === "string").join(" ").toLowerCase();
|
|
5369
|
+
const settledReason = details.incompleteReason === "content_filter" || details.incompleteReason === "max_output_tokens";
|
|
5370
|
+
const numericOrNamed = typeof details.errorCode === "string" ? details.errorCode : void 0;
|
|
5371
|
+
const classified = numericOrNamed !== void 0 || discriminator ? frameStatusCode(numericOrNamed, discriminator) : 500;
|
|
5372
|
+
const statusCode = classified !== 500 ? classified : settledReason ? 400 : 502;
|
|
5373
|
+
const usageLimited = statusCode === 429;
|
|
5374
|
+
const retryAfterSeconds = usageLimited && responseRetryAfterSeconds(event) !== void 0 ? clampRetryAfterSeconds(responseRetryAfterSeconds(event)) : void 0;
|
|
5375
|
+
failContext(
|
|
5376
|
+
entry,
|
|
5377
|
+
ctx,
|
|
5378
|
+
retryAfterSeconds === void 0 ? summary : `${summary}; retry after ${retryAfterSeconds}s`,
|
|
5379
|
+
{
|
|
5380
|
+
source: "empty_failure_terminal",
|
|
5381
|
+
upstreamEventType: type,
|
|
5382
|
+
...details,
|
|
5383
|
+
// Under DISTINCT keys. `failContext` fingerprints the message it was
|
|
5384
|
+
// given after spreading these, so an `errorMessage*` pair here is
|
|
5385
|
+
// overwritten by the summary's — which would silently discard the only
|
|
5386
|
+
// content-free evidence of what upstream actually said, and leave two
|
|
5387
|
+
// failures with the same type and code indistinguishable. `errorMessage*`
|
|
5388
|
+
// now means "what the client was told", `upstreamMessage*` means "what
|
|
5389
|
+
// upstream said", and both survive.
|
|
5390
|
+
...diagnosticTextFingerprint("upstreamMessage", responseErrorMessage(event))
|
|
5391
|
+
},
|
|
5392
|
+
statusCode,
|
|
5393
|
+
retryAfterSeconds
|
|
5394
|
+
);
|
|
5395
|
+
return;
|
|
5396
|
+
}
|
|
5224
5397
|
ctx.pendingEvents.push(event);
|
|
5225
5398
|
if (isModelDataEvent(type)) flushPending(ctx);
|
|
5226
5399
|
if (TERMINAL_EVENT_TYPES.has(type ?? "") || type === "error") {
|
|
@@ -5232,6 +5405,7 @@ function handleSocketMessage(entry, data) {
|
|
|
5232
5405
|
entry.responseId = ctx.responseId;
|
|
5233
5406
|
entry.requestInput = inputArray(ctx.originalPayload);
|
|
5234
5407
|
entry.expectedAssistant = expectedAssistantItems(ctx);
|
|
5408
|
+
entry.headRequiredToolProps = requiredToolProps(ctx.originalPayload);
|
|
5235
5409
|
entry.canonicalPrefix = void 0;
|
|
5236
5410
|
entry.canonicalEchoablePrefix = void 0;
|
|
5237
5411
|
entry.promptFieldHashes = ctx.promptFieldHashes;
|
|
@@ -5417,6 +5591,7 @@ function createResponsesWebSocketFetch(wsUrl, log12, options = {}) {
|
|
|
5417
5591
|
let persistent = Boolean(partitionKey);
|
|
5418
5592
|
let promotedConnectionId;
|
|
5419
5593
|
let decision;
|
|
5594
|
+
let candidateMismatchDetails;
|
|
5420
5595
|
if (selected && selectedDelta) {
|
|
5421
5596
|
sendPayload = { ...payload, input: selectedDelta, previous_response_id: selected.responseId };
|
|
5422
5597
|
continued = true;
|
|
@@ -5439,8 +5614,23 @@ function createResponsesWebSocketFetch(wsUrl, log12, options = {}) {
|
|
|
5439
5614
|
decision = "parallel_isolated";
|
|
5440
5615
|
debug("parallel request using an isolated socket");
|
|
5441
5616
|
} else if (diagnosticEntry) {
|
|
5617
|
+
const diagnosticMismatch = continuationMismatchDetails(diagnosticEntry, payload, debug, true);
|
|
5618
|
+
candidateMismatchDetails = /* @__PURE__ */ new Map([[diagnosticEntry, diagnosticMismatch]]);
|
|
5619
|
+
for (const candidate of candidates) {
|
|
5620
|
+
if (candidate === diagnosticEntry) continue;
|
|
5621
|
+
candidateMismatchDetails.set(
|
|
5622
|
+
candidate,
|
|
5623
|
+
continuationMismatchDetails(candidate, payload, debug, true)
|
|
5624
|
+
);
|
|
5625
|
+
}
|
|
5442
5626
|
debug(
|
|
5443
|
-
`history mismatch starting an additional chain; retained ${candidates.length} existing head(s) (${continuationMismatchSummary(
|
|
5627
|
+
`history mismatch starting an additional chain; retained ${candidates.length} existing head(s) (${continuationMismatchSummary(
|
|
5628
|
+
diagnosticEntry,
|
|
5629
|
+
payload,
|
|
5630
|
+
debug,
|
|
5631
|
+
mismatchDump,
|
|
5632
|
+
diagnosticMismatch
|
|
5633
|
+
)})`
|
|
5444
5634
|
);
|
|
5445
5635
|
decision = "history_mismatch_new_head";
|
|
5446
5636
|
} else if (partitionKey) {
|
|
@@ -5500,7 +5690,7 @@ function createResponsesWebSocketFetch(wsUrl, log12, options = {}) {
|
|
|
5500
5690
|
ttlPausedMs: entry.ttlPausedMs,
|
|
5501
5691
|
idleMs: Math.max(0, now - entry.lastUsedAt),
|
|
5502
5692
|
promptChanges: changedPromptFields(entry.promptFieldHashes, promptFieldHashes),
|
|
5503
|
-
mismatch: continuationMismatchDetails(entry, payload, debug)
|
|
5693
|
+
mismatch: candidateMismatchDetails?.get(entry) ?? continuationMismatchDetails(entry, payload, debug)
|
|
5504
5694
|
})),
|
|
5505
5695
|
evictions
|
|
5506
5696
|
}, diagnosticCorrelation);
|
|
@@ -5520,6 +5710,7 @@ function createResponsesWebSocketFetch(wsUrl, log12, options = {}) {
|
|
|
5520
5710
|
frameCount: 0,
|
|
5521
5711
|
pendingEvents: [],
|
|
5522
5712
|
emittedModelData: false,
|
|
5713
|
+
emittedDownstreamData: false,
|
|
5523
5714
|
transportRetryPending: false,
|
|
5524
5715
|
outputByIndex: /* @__PURE__ */ new Map(),
|
|
5525
5716
|
outputIndexByItemId: /* @__PURE__ */ new Map(),
|
|
@@ -5698,6 +5889,63 @@ function isCredentialBearingHeader(name) {
|
|
|
5698
5889
|
return CREDENTIAL_BEARING_HEADER.test(name);
|
|
5699
5890
|
}
|
|
5700
5891
|
|
|
5892
|
+
// src/model-runtime-compatibility.ts
|
|
5893
|
+
function remapMaxTokensField(body, field) {
|
|
5894
|
+
if (field === "max_tokens") {
|
|
5895
|
+
if (body.max_tokens === void 0 && body.max_completion_tokens !== void 0) {
|
|
5896
|
+
body.max_tokens = body.max_completion_tokens;
|
|
5897
|
+
}
|
|
5898
|
+
delete body.max_completion_tokens;
|
|
5899
|
+
return;
|
|
5900
|
+
}
|
|
5901
|
+
if (field === "max_completion_tokens") {
|
|
5902
|
+
if (body.max_completion_tokens === void 0 && body.max_tokens !== void 0) {
|
|
5903
|
+
body.max_completion_tokens = body.max_tokens;
|
|
5904
|
+
}
|
|
5905
|
+
delete body.max_tokens;
|
|
5906
|
+
}
|
|
5907
|
+
}
|
|
5908
|
+
function transformMessages(messages, compatibility) {
|
|
5909
|
+
if (!Array.isArray(messages)) return messages;
|
|
5910
|
+
const rewriteDeveloper = compatibility.supportsDeveloperRole === false;
|
|
5911
|
+
const replayReasoning = compatibility.requiresReasoningContentOnAssistantMessages === true;
|
|
5912
|
+
if (!rewriteDeveloper && !replayReasoning) return messages;
|
|
5913
|
+
let changed = false;
|
|
5914
|
+
const transformed = messages.map((message) => {
|
|
5915
|
+
if (!message || typeof message !== "object" || Array.isArray(message)) return message;
|
|
5916
|
+
const source = message;
|
|
5917
|
+
const role = source.role;
|
|
5918
|
+
const needsRoleRewrite = rewriteDeveloper && role === "developer";
|
|
5919
|
+
const needsReasoningReplay = replayReasoning && role === "assistant" && !Object.prototype.hasOwnProperty.call(source, "reasoning_content");
|
|
5920
|
+
if (!needsRoleRewrite && !needsReasoningReplay) return message;
|
|
5921
|
+
changed = true;
|
|
5922
|
+
return {
|
|
5923
|
+
...source,
|
|
5924
|
+
...needsRoleRewrite ? { role: "system" } : {},
|
|
5925
|
+
...needsReasoningReplay ? { reasoning_content: "" } : {}
|
|
5926
|
+
};
|
|
5927
|
+
});
|
|
5928
|
+
return changed ? transformed : messages;
|
|
5929
|
+
}
|
|
5930
|
+
function transformOpenAiCompatibleRequestBody(body, compatibility) {
|
|
5931
|
+
const transformed = { ...body };
|
|
5932
|
+
if (compatibility.supportsStore === false) delete transformed.store;
|
|
5933
|
+
if (compatibility.supportsLongCacheRetention === false) {
|
|
5934
|
+
delete transformed.prompt_cache_retention;
|
|
5935
|
+
delete transformed.promptCacheRetention;
|
|
5936
|
+
}
|
|
5937
|
+
remapMaxTokensField(transformed, compatibility.maxTokensField);
|
|
5938
|
+
const messages = transformMessages(transformed.messages, compatibility);
|
|
5939
|
+
if (messages !== transformed.messages) transformed.messages = messages;
|
|
5940
|
+
const hasReasoningEffort = typeof transformed.reasoning_effort === "string" && transformed.reasoning_effort.trim().length > 0;
|
|
5941
|
+
if (hasReasoningEffort && compatibility.thinkingFormat === "deepseek") {
|
|
5942
|
+
if (transformed.thinking === void 0) transformed.thinking = { type: "enabled" };
|
|
5943
|
+
} else if (hasReasoningEffort && compatibility.thinkingFormat === "qwen") {
|
|
5944
|
+
if (transformed.enable_thinking === void 0) transformed.enable_thinking = true;
|
|
5945
|
+
}
|
|
5946
|
+
return transformed;
|
|
5947
|
+
}
|
|
5948
|
+
|
|
5701
5949
|
// src/provider-factory.ts
|
|
5702
5950
|
var RESPONSES_ONLY_PREFIXES = [
|
|
5703
5951
|
"gpt-5-codex",
|
|
@@ -5840,7 +6088,10 @@ async function createLanguageModel(spec) {
|
|
|
5840
6088
|
baseURL: baseURL ?? "",
|
|
5841
6089
|
...spec.authType !== "none" && apiKey.trim() ? { apiKey } : {},
|
|
5842
6090
|
...spec.authType === "none" ? { fetch: fetchWithoutCredentialHeaders } : {},
|
|
5843
|
-
...spec.headers ? { headers: spec.headers } : {}
|
|
6091
|
+
...spec.headers ? { headers: spec.headers } : {},
|
|
6092
|
+
...spec.compatibility ? {
|
|
6093
|
+
transformRequestBody: (body) => transformOpenAiCompatibleRequestBody(body, spec.compatibility)
|
|
6094
|
+
} : {}
|
|
5844
6095
|
};
|
|
5845
6096
|
model = createOpenAICompatible({
|
|
5846
6097
|
...options
|
|
@@ -6096,8 +6347,68 @@ function mapCodexEffortToGeminiBudget(effort) {
|
|
|
6096
6347
|
if (!level) return void 0;
|
|
6097
6348
|
return GEMINI_25_BUDGETS[level];
|
|
6098
6349
|
}
|
|
6350
|
+
function compatibilityReasoningCapabilities(metadata) {
|
|
6351
|
+
const compatibility = metadata?.compatibility;
|
|
6352
|
+
if (!compatibility) return void 0;
|
|
6353
|
+
if (compatibility.supportsReasoningEffort === false) {
|
|
6354
|
+
return metadata?.reasoning === false ? EMPTY_REASONING : {
|
|
6355
|
+
...EMPTY_REASONING,
|
|
6356
|
+
mode: "internal-only",
|
|
6357
|
+
source: "provider-metadata",
|
|
6358
|
+
confidence: "documented"
|
|
6359
|
+
};
|
|
6360
|
+
}
|
|
6361
|
+
if (compatibility.reasoningEffortMap) {
|
|
6362
|
+
const levels = Object.entries(compatibility.reasoningEffortMap).filter(([, mapped]) => mapped !== null).map(([level]) => level);
|
|
6363
|
+
if (levels.length === 0) {
|
|
6364
|
+
return metadata?.reasoning === false ? EMPTY_REASONING : {
|
|
6365
|
+
...EMPTY_REASONING,
|
|
6366
|
+
mode: "internal-only",
|
|
6367
|
+
source: "provider-metadata",
|
|
6368
|
+
confidence: "documented"
|
|
6369
|
+
};
|
|
6370
|
+
}
|
|
6371
|
+
const preferredDefault = ["medium", "high", "max", "low"].find((level) => levels.includes(level));
|
|
6372
|
+
return {
|
|
6373
|
+
levels,
|
|
6374
|
+
defaultLevel: preferredDefault ?? levels[0],
|
|
6375
|
+
supportsSummaries: false,
|
|
6376
|
+
mode: "controllable",
|
|
6377
|
+
source: "provider-metadata",
|
|
6378
|
+
confidence: "documented",
|
|
6379
|
+
wireFormat: compatibility.thinkingFormat === "deepseek" ? { kind: "deepseek-thinking" } : { kind: "openai-reasoning-effort" }
|
|
6380
|
+
};
|
|
6381
|
+
}
|
|
6382
|
+
if (compatibility.supportsReasoningEffort === true || compatibility.thinkingFormat !== void 0) {
|
|
6383
|
+
if (metadata?.reasoning === false) return EMPTY_REASONING;
|
|
6384
|
+
return {
|
|
6385
|
+
levels: ["low", "medium", "high"],
|
|
6386
|
+
defaultLevel: "medium",
|
|
6387
|
+
supportsSummaries: false,
|
|
6388
|
+
mode: "controllable",
|
|
6389
|
+
source: "provider-metadata",
|
|
6390
|
+
confidence: "documented",
|
|
6391
|
+
wireFormat: compatibility.thinkingFormat === "deepseek" ? { kind: "deepseek-thinking" } : { kind: "openai-reasoning-effort" }
|
|
6392
|
+
};
|
|
6393
|
+
}
|
|
6394
|
+
return void 0;
|
|
6395
|
+
}
|
|
6396
|
+
function compatibilityReasoningEffort(effort, modelId, compatibility) {
|
|
6397
|
+
if (compatibility.supportsReasoningEffort === false) return void 0;
|
|
6398
|
+
const map = compatibility.reasoningEffortMap;
|
|
6399
|
+
if (map) {
|
|
6400
|
+
if (!Object.prototype.hasOwnProperty.call(map, effort)) return void 0;
|
|
6401
|
+
return map[effort] ?? void 0;
|
|
6402
|
+
}
|
|
6403
|
+
if (compatibility.supportsReasoningEffort === true || compatibility.thinkingFormat !== void 0) {
|
|
6404
|
+
return mapCodexEffortToOpenAI(effort, modelId);
|
|
6405
|
+
}
|
|
6406
|
+
return void 0;
|
|
6407
|
+
}
|
|
6099
6408
|
function getReasoningCapabilities(npm, modelId, metadata) {
|
|
6100
6409
|
const id = modelId.toLowerCase();
|
|
6410
|
+
const compatibilityCapabilities = compatibilityReasoningCapabilities(metadata);
|
|
6411
|
+
if (compatibilityCapabilities) return compatibilityCapabilities;
|
|
6101
6412
|
if (isOpenRouterRoute(npm, metadata)) {
|
|
6102
6413
|
return openRouterReasoningCapabilities(metadata);
|
|
6103
6414
|
}
|
|
@@ -6243,7 +6554,10 @@ function getReasoningCapabilities(npm, modelId, metadata) {
|
|
|
6243
6554
|
return EMPTY_REASONING;
|
|
6244
6555
|
}
|
|
6245
6556
|
function getPatchReasoningCapabilities(npm, modelId, metadata) {
|
|
6246
|
-
if (metadata?.
|
|
6557
|
+
if (metadata?.compatibility?.supportsReasoningEffort === false) {
|
|
6558
|
+
return compatibilityReasoningCapabilities(metadata) ?? EMPTY_REASONING;
|
|
6559
|
+
}
|
|
6560
|
+
if (metadata?.reasoning === false && !metadata?.compatibility?.reasoningEffortMap && !hasSupportedParameter(metadata, "reasoning_effort") && !hasSupportedParameter(metadata, "reasoning")) {
|
|
6247
6561
|
return EMPTY_REASONING;
|
|
6248
6562
|
}
|
|
6249
6563
|
const capabilities = getReasoningCapabilities(npm, modelId, metadata);
|
|
@@ -6262,11 +6576,22 @@ function getPatchReasoningCapabilities(npm, modelId, metadata) {
|
|
|
6262
6576
|
}
|
|
6263
6577
|
function effortProviderOptions(npm, effort, modelId, metadata) {
|
|
6264
6578
|
if (!effort) return void 0;
|
|
6579
|
+
if (npm === "@ai-sdk/openai-compatible" && modelId && metadata?.compatibility) {
|
|
6580
|
+
const reasoningEffort = compatibilityReasoningEffort(
|
|
6581
|
+
effort,
|
|
6582
|
+
modelId,
|
|
6583
|
+
metadata.compatibility
|
|
6584
|
+
);
|
|
6585
|
+
if (!reasoningEffort) return void 0;
|
|
6586
|
+
const key = metadata.providerId ? toCamelCase(metadata.providerId) : "openaiCompatible";
|
|
6587
|
+
return { [key]: { reasoningEffort } };
|
|
6588
|
+
}
|
|
6265
6589
|
if (isOpenRouterRoute(npm, metadata)) {
|
|
6266
6590
|
const caps = openRouterReasoningCapabilities(metadata);
|
|
6267
6591
|
if (caps.mode !== "controllable") return void 0;
|
|
6268
6592
|
const allowed = new Set(OPENROUTER_EFFORT_LEVELS);
|
|
6269
|
-
const
|
|
6593
|
+
const candidate = effort;
|
|
6594
|
+
const mapped = allowed.has(candidate) ? candidate : candidate === "max" ? "xhigh" : void 0;
|
|
6270
6595
|
return mapped ? { openrouter: { reasoning: { effort: mapped, exclude: false } } } : void 0;
|
|
6271
6596
|
}
|
|
6272
6597
|
if (npm === "@ai-sdk/openai" || npm === "@ai-sdk/azure") {
|
|
@@ -6326,7 +6651,8 @@ function effortProviderOptions(npm, effort, modelId, metadata) {
|
|
|
6326
6651
|
}
|
|
6327
6652
|
if (hasSupportedParameter(metadata, "reasoning")) {
|
|
6328
6653
|
const allowed = new Set(OPENROUTER_EFFORT_LEVELS);
|
|
6329
|
-
const
|
|
6654
|
+
const candidate = effort;
|
|
6655
|
+
const mapped = allowed.has(candidate) ? candidate : candidate === "max" ? "xhigh" : void 0;
|
|
6330
6656
|
return mapped ? { openrouter: { reasoning: { effort: mapped, exclude: false } } } : void 0;
|
|
6331
6657
|
}
|
|
6332
6658
|
return void 0;
|
|
@@ -7136,6 +7462,62 @@ function parseModelList(body, npm) {
|
|
|
7136
7462
|
}
|
|
7137
7463
|
return models;
|
|
7138
7464
|
}
|
|
7465
|
+
function materializeTemplateModel(template, model, baseUrl) {
|
|
7466
|
+
const npm = model.npm ?? template.npm;
|
|
7467
|
+
const { id, upstreamModelId: normalizedUpstream } = normalizeGoogleModelId(model.id, npm);
|
|
7468
|
+
const family = model.family ?? (id.split(/[-/:]/)[0] ?? id);
|
|
7469
|
+
const freeStatus = model.freeStatus ?? classifyFreeStatus({ model });
|
|
7470
|
+
return {
|
|
7471
|
+
...model,
|
|
7472
|
+
id,
|
|
7473
|
+
name: normalizeGoogleDisplayName(model.name, id),
|
|
7474
|
+
upstreamModelId: model.upstreamModelId ?? normalizedUpstream,
|
|
7475
|
+
family,
|
|
7476
|
+
brand: model.brand ?? deriveBrand(family),
|
|
7477
|
+
contextWindow: model.contextWindow ?? resolveContextWindow(id),
|
|
7478
|
+
isFree: model.isFree ?? isFreeStatus(freeStatus),
|
|
7479
|
+
freeStatus,
|
|
7480
|
+
modelFormat: model.modelFormat ?? modelFormatForNpm(npm),
|
|
7481
|
+
npm,
|
|
7482
|
+
apiUrl: model.apiUrl ?? baseUrl
|
|
7483
|
+
};
|
|
7484
|
+
}
|
|
7485
|
+
function normalizeTemplateOverlay(template, model) {
|
|
7486
|
+
const npm = model.npm ?? template.npm;
|
|
7487
|
+
const { id } = normalizeGoogleModelId(model.id, npm);
|
|
7488
|
+
const family = model.family;
|
|
7489
|
+
const hasFreeMetadata = model.cost !== void 0 || model.isFree !== void 0 || model.freeStatus !== void 0;
|
|
7490
|
+
const freeStatus = hasFreeMetadata ? model.freeStatus ?? classifyFreeStatus({ model }) : void 0;
|
|
7491
|
+
return {
|
|
7492
|
+
...model,
|
|
7493
|
+
id,
|
|
7494
|
+
name: normalizeGoogleDisplayName(model.name, id),
|
|
7495
|
+
...model.upstreamModelId !== void 0 ? { upstreamModelId: normalizeGoogleModelId(model.upstreamModelId, npm).upstreamModelId } : {},
|
|
7496
|
+
...model.npm !== void 0 ? { npm } : {},
|
|
7497
|
+
...model.modelFormat !== void 0 ? { modelFormat: model.modelFormat } : model.npm !== void 0 ? { modelFormat: modelFormatForNpm(npm) } : {},
|
|
7498
|
+
...family !== void 0 ? { family, brand: model.brand ?? deriveBrand(family) } : {},
|
|
7499
|
+
...freeStatus !== void 0 ? {
|
|
7500
|
+
freeStatus,
|
|
7501
|
+
isFree: model.isFree ?? isFreeStatus(freeStatus)
|
|
7502
|
+
} : {}
|
|
7503
|
+
};
|
|
7504
|
+
}
|
|
7505
|
+
function applyTemplateModelMetadata(template, discovered, _baseUrl) {
|
|
7506
|
+
const curated = new Map(
|
|
7507
|
+
(template.staticModels ?? []).map((model) => normalizeTemplateOverlay(template, model)).map((model) => [model.id, model])
|
|
7508
|
+
);
|
|
7509
|
+
const visible = template.staticModelPolicy === "allowlist" ? discovered.filter((model) => curated.has(model.id)) : discovered;
|
|
7510
|
+
return visible.map((model) => {
|
|
7511
|
+
const overlay = curated.get(model.id);
|
|
7512
|
+
if (!overlay) return model;
|
|
7513
|
+
return {
|
|
7514
|
+
...model,
|
|
7515
|
+
...overlay,
|
|
7516
|
+
id: model.id,
|
|
7517
|
+
upstreamModelId: overlay.upstreamModelId ?? model.upstreamModelId
|
|
7518
|
+
};
|
|
7519
|
+
});
|
|
7520
|
+
}
|
|
7139
7521
|
async function fetchTemplateModels(template, apiKey, baseUrlOverride, extraHeaders) {
|
|
7140
7522
|
const trimmedOverride = baseUrlOverride?.trim();
|
|
7141
7523
|
const baseUrl = (trimmedOverride || template.defaultBaseUrl)?.replace(/\/$/, "");
|
|
@@ -7147,19 +7529,7 @@ async function fetchTemplateModels(template, apiKey, baseUrlOverride, extraHeade
|
|
|
7147
7529
|
};
|
|
7148
7530
|
}
|
|
7149
7531
|
if (template.modelSource === "static-seed") {
|
|
7150
|
-
const models = (template.staticModels
|
|
7151
|
-
const family = sm.id.split(/[-/:]/)[0] ?? sm.id;
|
|
7152
|
-
return {
|
|
7153
|
-
id: sm.id,
|
|
7154
|
-
name: sm.name,
|
|
7155
|
-
upstreamModelId: sm.id,
|
|
7156
|
-
family,
|
|
7157
|
-
brand: deriveBrand(family),
|
|
7158
|
-
contextWindow: resolveContextWindow(sm.id),
|
|
7159
|
-
modelFormat: modelFormatForNpm(template.npm),
|
|
7160
|
-
npm: template.npm
|
|
7161
|
-
};
|
|
7162
|
-
});
|
|
7532
|
+
const models = (template.staticModels ?? []).map((model) => materializeTemplateModel(template, model, baseUrl));
|
|
7163
7533
|
return { models, baseUrl };
|
|
7164
7534
|
}
|
|
7165
7535
|
const url = modelsUrl(baseUrl, template);
|
|
@@ -7229,7 +7599,7 @@ async function fetchTemplateModels(template, apiKey, baseUrlOverride, extraHeade
|
|
|
7229
7599
|
}
|
|
7230
7600
|
} catch {
|
|
7231
7601
|
}
|
|
7232
|
-
const models = parseModelList(json, template.npm);
|
|
7602
|
+
const models = applyTemplateModelMetadata(template, parseModelList(json, template.npm), baseUrl);
|
|
7233
7603
|
if (models.length === 0) {
|
|
7234
7604
|
return {
|
|
7235
7605
|
models: [],
|
|
@@ -7317,11 +7687,11 @@ async function addProviderFromTemplate(template, apiKey, opts) {
|
|
|
7317
7687
|
}
|
|
7318
7688
|
const pricingCache = loadPricingCache();
|
|
7319
7689
|
const platform = pricingPlatformForProvider(template.id, template.id);
|
|
7320
|
-
const
|
|
7321
|
-
|
|
7322
|
-
|
|
7323
|
-
|
|
7324
|
-
);
|
|
7690
|
+
const discoveredModels = usableModels.map((m) => ({
|
|
7691
|
+
...m,
|
|
7692
|
+
apiUrl: m.apiUrl ?? fetched.baseUrl
|
|
7693
|
+
}));
|
|
7694
|
+
const pricedModels = template.preserveModelPricing ? discoveredModels : enrichModelsWithPricing(discoveredModels, buildPricingIndex(pricingCache), platform);
|
|
7325
7695
|
const account = `provider:${template.id}`;
|
|
7326
7696
|
const result = await withProviderMutationLock(template.id, async () => {
|
|
7327
7697
|
const currentState = await withRegistryWriteLock(() => {
|
|
@@ -7378,6 +7748,7 @@ async function addProviderFromTemplate(template, apiKey, opts) {
|
|
|
7378
7748
|
enabled: true,
|
|
7379
7749
|
authRef,
|
|
7380
7750
|
authType: trimmedKey ? template.authType : "none",
|
|
7751
|
+
...template.preserveModelPricing ? { preserveModelPricing: true } : {},
|
|
7381
7752
|
...!trimmedKey && template.anonymousFreeModels ? { subscriptionFilter: "free" } : {},
|
|
7382
7753
|
api: {
|
|
7383
7754
|
npm: template.npm,
|
|
@@ -7980,7 +8351,7 @@ async function refreshApiListProvider(provider, apiKey) {
|
|
|
7980
8351
|
return { models: [], error: fetched2.error ?? "No models returned.", baseUrl: fetched2.baseUrl };
|
|
7981
8352
|
}
|
|
7982
8353
|
return {
|
|
7983
|
-
models: fetched2.models.map((m) => ({ ...m, apiUrl: fetched2.baseUrl })),
|
|
8354
|
+
models: fetched2.models.map((m) => ({ ...m, apiUrl: m.apiUrl ?? fetched2.baseUrl })),
|
|
7984
8355
|
baseUrl: fetched2.baseUrl
|
|
7985
8356
|
};
|
|
7986
8357
|
}
|
|
@@ -7999,7 +8370,7 @@ async function refreshApiListProvider(provider, apiKey) {
|
|
|
7999
8370
|
return {
|
|
8000
8371
|
models: usableModels.map((m) => ({
|
|
8001
8372
|
...m,
|
|
8002
|
-
apiUrl: fetched.baseUrl
|
|
8373
|
+
apiUrl: m.apiUrl ?? fetched.baseUrl
|
|
8003
8374
|
})),
|
|
8004
8375
|
baseUrl: fetched.baseUrl
|
|
8005
8376
|
};
|
|
@@ -8113,7 +8484,7 @@ async function refreshProviderModels(providerId, apiKey, registry) {
|
|
|
8113
8484
|
}
|
|
8114
8485
|
const pricingCache = loadPricingCache();
|
|
8115
8486
|
const platform = pricingPlatformForProvider(provider.templateId, provider.id);
|
|
8116
|
-
const enriched = enrichModelsWithPricing(models, buildPricingIndex(pricingCache), platform);
|
|
8487
|
+
const enriched = provider.preserveModelPricing ? models : enrichModelsWithPricing(models, buildPricingIndex(pricingCache), platform);
|
|
8117
8488
|
await withRegistryWriteLock(() => {
|
|
8118
8489
|
const currentRegistry = loadRegistryStrict();
|
|
8119
8490
|
const currentProvider = currentRegistry.providers.find((candidate) => candidate.id === providerId);
|
|
@@ -9355,7 +9726,8 @@ function localModelToRoute(lp, model) {
|
|
|
9355
9726
|
reasoning: model.reasoning,
|
|
9356
9727
|
interleavedReasoningField: model.interleavedReasoningField,
|
|
9357
9728
|
useResponsesLite: model.useResponsesLite,
|
|
9358
|
-
preferWebSockets: model.preferWebSockets
|
|
9729
|
+
preferWebSockets: model.preferWebSockets,
|
|
9730
|
+
compatibility: model.compatibility
|
|
9359
9731
|
};
|
|
9360
9732
|
}
|
|
9361
9733
|
function makeRouteResolver(localProviders) {
|
|
@@ -9428,7 +9800,8 @@ function buildHttpProxyRoutes(providers, favorites, modelAliases = void 0, max =
|
|
|
9428
9800
|
unavailable.push(favorite);
|
|
9429
9801
|
continue;
|
|
9430
9802
|
}
|
|
9431
|
-
|
|
9803
|
+
const supported = model.modelFormat === "anthropic" ? Boolean(model.baseUrl) : isSdkMigratedNpm(model.npm);
|
|
9804
|
+
if (!supported) {
|
|
9432
9805
|
unsupported.push(favorite);
|
|
9433
9806
|
continue;
|
|
9434
9807
|
}
|
|
@@ -9601,7 +9974,8 @@ function routeUnavailableMessage(modelId, reason) {
|
|
|
9601
9974
|
}
|
|
9602
9975
|
|
|
9603
9976
|
// src/upstream-forward.ts
|
|
9604
|
-
import { Readable } from "stream";
|
|
9977
|
+
import { Readable, Transform } from "stream";
|
|
9978
|
+
import { StringDecoder } from "string_decoder";
|
|
9605
9979
|
|
|
9606
9980
|
// src/server/auth.ts
|
|
9607
9981
|
function sanitizeCredential(value) {
|
|
@@ -9682,6 +10056,34 @@ async function fetchWithOAuthRetry(apiKey, request3, refreshToken) {
|
|
|
9682
10056
|
response = await request3(refreshed);
|
|
9683
10057
|
return { response, apiKey: refreshed, refreshed: true };
|
|
9684
10058
|
}
|
|
10059
|
+
function anthropicSseModelRewrite(override) {
|
|
10060
|
+
const decoder = new StringDecoder("utf8");
|
|
10061
|
+
let tail = "";
|
|
10062
|
+
const rewriteLine = (line) => {
|
|
10063
|
+
if (!line.startsWith("data:") || !line.includes('"message_start"')) return line;
|
|
10064
|
+
try {
|
|
10065
|
+
const parsed = JSON.parse(line.slice(5));
|
|
10066
|
+
if (parsed.type === "message_start" && parsed.message && typeof parsed.message.model === "string") {
|
|
10067
|
+
parsed.message.model = override;
|
|
10068
|
+
return "data: " + JSON.stringify(parsed);
|
|
10069
|
+
}
|
|
10070
|
+
} catch {
|
|
10071
|
+
}
|
|
10072
|
+
return line;
|
|
10073
|
+
};
|
|
10074
|
+
return new Transform({
|
|
10075
|
+
transform(chunk, _encoding, callback) {
|
|
10076
|
+
const lines = (tail + decoder.write(chunk)).split("\n");
|
|
10077
|
+
tail = lines.pop() ?? "";
|
|
10078
|
+
const rewritten = lines.map(rewriteLine);
|
|
10079
|
+
callback(null, rewritten.length ? rewritten.join("\n") + "\n" : "");
|
|
10080
|
+
},
|
|
10081
|
+
flush(callback) {
|
|
10082
|
+
const rest = tail + decoder.end();
|
|
10083
|
+
callback(null, rest ? rewriteLine(rest) : "");
|
|
10084
|
+
}
|
|
10085
|
+
});
|
|
10086
|
+
}
|
|
9685
10087
|
async function relayAnthropicMessages(res, messagesUrl, body, apiKey, clientWantsStream, options = {}) {
|
|
9686
10088
|
const doFetch = (key) => fetch(messagesUrl, {
|
|
9687
10089
|
method: "POST",
|
|
@@ -9718,7 +10120,12 @@ async function relayAnthropicMessages(res, messagesUrl, body, apiKey, clientWant
|
|
|
9718
10120
|
"Cache-Control": "no-cache",
|
|
9719
10121
|
"Connection": "keep-alive"
|
|
9720
10122
|
});
|
|
9721
|
-
Readable.fromWeb(upstreamRes.body).on("error", () => res.destroy())
|
|
10123
|
+
const upstream = Readable.fromWeb(upstreamRes.body).on("error", () => res.destroy());
|
|
10124
|
+
if (options.responseModelOverride) {
|
|
10125
|
+
upstream.pipe(anthropicSseModelRewrite(options.responseModelOverride)).on("error", () => res.destroy()).pipe(res);
|
|
10126
|
+
} else {
|
|
10127
|
+
upstream.pipe(res);
|
|
10128
|
+
}
|
|
9722
10129
|
return;
|
|
9723
10130
|
}
|
|
9724
10131
|
if (!upstreamRes.body) {
|
|
@@ -9726,14 +10133,19 @@ async function relayAnthropicMessages(res, messagesUrl, body, apiKey, clientWant
|
|
|
9726
10133
|
res.end(JSON.stringify({ type: "error", error: { type: "api_error", message: "Upstream returned empty response body" } }));
|
|
9727
10134
|
return;
|
|
9728
10135
|
}
|
|
9729
|
-
|
|
10136
|
+
let text4 = await upstreamRes.text();
|
|
10137
|
+
let parsed;
|
|
9730
10138
|
try {
|
|
9731
|
-
JSON.parse(text4);
|
|
10139
|
+
parsed = JSON.parse(text4);
|
|
9732
10140
|
} catch {
|
|
9733
10141
|
res.writeHead(502, { "Content-Type": "application/json" });
|
|
9734
10142
|
res.end(JSON.stringify({ type: "error", error: { type: "api_error", message: "Upstream response was not valid JSON" } }));
|
|
9735
10143
|
return;
|
|
9736
10144
|
}
|
|
10145
|
+
if (options.responseModelOverride && parsed && typeof parsed === "object" && !Array.isArray(parsed) && typeof parsed.model === "string") {
|
|
10146
|
+
parsed.model = options.responseModelOverride;
|
|
10147
|
+
text4 = JSON.stringify(parsed);
|
|
10148
|
+
}
|
|
9737
10149
|
res.writeHead(200, {
|
|
9738
10150
|
"Content-Type": "application/json",
|
|
9739
10151
|
"Content-Length": Buffer.byteLength(text4).toString()
|
|
@@ -9882,6 +10294,41 @@ function resolveUpstreamTools(tools, messages) {
|
|
|
9882
10294
|
return upstream;
|
|
9883
10295
|
}
|
|
9884
10296
|
|
|
10297
|
+
// src/upstream-retry.ts
|
|
10298
|
+
var UPSTREAM_MAX_RETRIES_ENV = "CLODEX_UPSTREAM_MAX_RETRIES";
|
|
10299
|
+
var MAX_UPSTREAM_MAX_RETRIES = 5;
|
|
10300
|
+
var reportedValues = /* @__PURE__ */ new Set();
|
|
10301
|
+
function reportOnce(raw, message, warn) {
|
|
10302
|
+
if (reportedValues.has(raw)) return;
|
|
10303
|
+
reportedValues.add(raw);
|
|
10304
|
+
try {
|
|
10305
|
+
warn(message);
|
|
10306
|
+
} catch {
|
|
10307
|
+
}
|
|
10308
|
+
}
|
|
10309
|
+
function upstreamMaxRetries(env = process.env, warn = (message) => console.error(`clodex: ${message}`)) {
|
|
10310
|
+
const raw = env[UPSTREAM_MAX_RETRIES_ENV]?.trim();
|
|
10311
|
+
if (raw === void 0 || raw === "") return void 0;
|
|
10312
|
+
const value = Number(raw);
|
|
10313
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
10314
|
+
reportOnce(
|
|
10315
|
+
raw,
|
|
10316
|
+
`ignoring ${UPSTREAM_MAX_RETRIES_ENV}=${raw} (expected a non-negative integer)`,
|
|
10317
|
+
warn
|
|
10318
|
+
);
|
|
10319
|
+
return void 0;
|
|
10320
|
+
}
|
|
10321
|
+
if (value > MAX_UPSTREAM_MAX_RETRIES) {
|
|
10322
|
+
reportOnce(
|
|
10323
|
+
raw,
|
|
10324
|
+
`clamping ${UPSTREAM_MAX_RETRIES_ENV}=${raw} to ${MAX_UPSTREAM_MAX_RETRIES} (higher values exceed the 120s streaming idle budget)`,
|
|
10325
|
+
warn
|
|
10326
|
+
);
|
|
10327
|
+
return MAX_UPSTREAM_MAX_RETRIES;
|
|
10328
|
+
}
|
|
10329
|
+
return value;
|
|
10330
|
+
}
|
|
10331
|
+
|
|
9885
10332
|
// src/sdk-adapter.ts
|
|
9886
10333
|
function sdkTranslationErrorSignature(error) {
|
|
9887
10334
|
const message = error instanceof Error ? error.message : typeof error === "string" ? error : void 0;
|
|
@@ -9952,10 +10399,12 @@ function openAiCacheBreakpoint(block, enabled) {
|
|
|
9952
10399
|
function translateTopLevelSystemForOpenAi(system) {
|
|
9953
10400
|
if (!system) return [];
|
|
9954
10401
|
if (typeof system === "string") {
|
|
9955
|
-
|
|
10402
|
+
const stripped = stripClaudeCodeBillingHeader(system);
|
|
10403
|
+
return stripped?.trim() ? [{ role: "system", content: stripped }] : [];
|
|
9956
10404
|
}
|
|
9957
10405
|
return system.flatMap((block) => {
|
|
9958
|
-
const
|
|
10406
|
+
const raw = typeof block === "string" ? block : block.text ?? "";
|
|
10407
|
+
const text4 = stripClaudeCodeBillingHeader(raw) ?? "";
|
|
9959
10408
|
if (!text4.trim()) return [];
|
|
9960
10409
|
const cacheControl = typeof block === "string" ? void 0 : block.cache_control;
|
|
9961
10410
|
return [{
|
|
@@ -10141,7 +10590,7 @@ function isClaudeCodeStructuredOutputCompactRequest(body) {
|
|
|
10141
10590
|
function translateRequest(body, npm, options) {
|
|
10142
10591
|
const messages = body.messages ?? [];
|
|
10143
10592
|
annotateToolNames(messages);
|
|
10144
|
-
const baseSystem = systemToString(body.system,
|
|
10593
|
+
const baseSystem = systemToString(body.system, true);
|
|
10145
10594
|
const systemText = baseSystem?.trim() || (options?.openAiOAuth ? "You are a coding assistant." : void 0);
|
|
10146
10595
|
const compactRequest = isClaudeCodeStructuredOutputCompactRequest(body);
|
|
10147
10596
|
let upstreamTools = resolveUpstreamTools(
|
|
@@ -10432,6 +10881,7 @@ async function streamAnthropicResponse(model, params, modelId, write, log12, obs
|
|
|
10432
10881
|
const result = streamText({
|
|
10433
10882
|
model,
|
|
10434
10883
|
...params,
|
|
10884
|
+
maxRetries: upstreamMaxRetries(),
|
|
10435
10885
|
abortSignal,
|
|
10436
10886
|
onError: () => {
|
|
10437
10887
|
}
|
|
@@ -10480,6 +10930,7 @@ async function generateAnthropicResponse(model, params, modelId, options) {
|
|
|
10480
10930
|
const r = streamText({
|
|
10481
10931
|
model,
|
|
10482
10932
|
...params,
|
|
10933
|
+
maxRetries: upstreamMaxRetries(),
|
|
10483
10934
|
abortSignal,
|
|
10484
10935
|
onError: () => {
|
|
10485
10936
|
}
|
|
@@ -10536,6 +10987,7 @@ async function generateAnthropicResponse(model, params, modelId, options) {
|
|
|
10536
10987
|
const r = await generateText({
|
|
10537
10988
|
model,
|
|
10538
10989
|
...params,
|
|
10990
|
+
maxRetries: upstreamMaxRetries(),
|
|
10539
10991
|
abortSignal: generateAbort.signal
|
|
10540
10992
|
});
|
|
10541
10993
|
({ text: text4, toolCalls, finishReason, usage } = r);
|
|
@@ -10955,6 +11407,10 @@ async function startProxyCatalog(routes, defaultAliasId, debug = false, inferenc
|
|
|
10955
11407
|
route.apiKey = refreshed;
|
|
10956
11408
|
},
|
|
10957
11409
|
signal: clientAbort.signal,
|
|
11410
|
+
// A route selected through a clodex: id or short alias must echo the
|
|
11411
|
+
// exact requested id back, or patched Claude Code misses the alias
|
|
11412
|
+
// context-window key and can skip auto-compaction.
|
|
11413
|
+
responseModelOverride: typeof originalModel === "string" && originalModel !== route.realModelId ? originalModel : void 0,
|
|
10958
11414
|
onUpstreamError: inferenceLogPath ? (statusCode, errorContent) => writeInferenceResponseErrorLog(inferenceLogPath, {
|
|
10959
11415
|
modelId: originalModel,
|
|
10960
11416
|
provider: route.providerId ?? route.aliasId.split(":")[1] ?? "unknown",
|
|
@@ -10993,6 +11449,7 @@ async function startProxyCatalog(routes, defaultAliasId, debug = false, inferenc
|
|
|
10993
11449
|
supportedParameters: route.supportedParameters,
|
|
10994
11450
|
reasoning: route.reasoning,
|
|
10995
11451
|
interleavedReasoningField: route.interleavedReasoningField,
|
|
11452
|
+
compatibility: route.compatibility,
|
|
10996
11453
|
upstreamModelId: route.realModelId
|
|
10997
11454
|
}
|
|
10998
11455
|
});
|
|
@@ -11011,6 +11468,7 @@ async function startProxyCatalog(routes, defaultAliasId, debug = false, inferenc
|
|
|
11011
11468
|
headers: route.headers,
|
|
11012
11469
|
useResponsesLite: route.useResponsesLite,
|
|
11013
11470
|
preferWebSockets: route.preferWebSockets,
|
|
11471
|
+
compatibility: route.compatibility,
|
|
11014
11472
|
onDebug: (msg) => plog(() => msg),
|
|
11015
11473
|
onWebSocketDiagnostic: webSocketDiagnosticsLogPath ? (event) => writeWebSocketDiagnosticLog(webSocketDiagnosticsLogPath, event) : void 0
|
|
11016
11474
|
});
|
|
@@ -11220,6 +11678,7 @@ function startProxy(completionsUrl, modelId, debug = false, contextWindow, sdk,
|
|
|
11220
11678
|
interleavedReasoningField: sdk?.interleavedReasoningField,
|
|
11221
11679
|
useResponsesLite: sdk?.useResponsesLite,
|
|
11222
11680
|
preferWebSockets: sdk?.preferWebSockets,
|
|
11681
|
+
compatibility: sdk?.compatibility,
|
|
11223
11682
|
headers: sdk?.headers
|
|
11224
11683
|
}], clientModelId, debug);
|
|
11225
11684
|
}
|
|
@@ -11490,11 +11949,20 @@ async function collectOpenAiStream(stream) {
|
|
|
11490
11949
|
async function generateOpenAiResponse(model, params, responseModelId, options) {
|
|
11491
11950
|
let result;
|
|
11492
11951
|
if (options?.forceStream) {
|
|
11493
|
-
const { stream } = streamText2({
|
|
11494
|
-
|
|
11952
|
+
const { stream } = streamText2({
|
|
11953
|
+
model,
|
|
11954
|
+
...params,
|
|
11955
|
+
maxRetries: upstreamMaxRetries(),
|
|
11956
|
+
onError: () => {
|
|
11957
|
+
}
|
|
11958
|
+
});
|
|
11495
11959
|
result = await collectOpenAiStream(stream);
|
|
11496
11960
|
} else {
|
|
11497
|
-
result = await generateText2({
|
|
11961
|
+
result = await generateText2({
|
|
11962
|
+
model,
|
|
11963
|
+
...params,
|
|
11964
|
+
maxRetries: upstreamMaxRetries()
|
|
11965
|
+
});
|
|
11498
11966
|
}
|
|
11499
11967
|
const message = { role: "assistant", content: result.text || null };
|
|
11500
11968
|
if (result.toolCalls?.length) {
|
|
@@ -11518,7 +11986,11 @@ async function generateOpenAiResponse(model, params, responseModelId, options) {
|
|
|
11518
11986
|
};
|
|
11519
11987
|
}
|
|
11520
11988
|
async function streamOpenAiResponse(model, params, responseModelId, onChunk) {
|
|
11521
|
-
const { stream } = streamText2({
|
|
11989
|
+
const { stream } = streamText2({
|
|
11990
|
+
model,
|
|
11991
|
+
...params,
|
|
11992
|
+
maxRetries: upstreamMaxRetries()
|
|
11993
|
+
});
|
|
11522
11994
|
const baseData = {
|
|
11523
11995
|
id: `chatcmpl-${Date.now()}`,
|
|
11524
11996
|
object: "chat.completion.chunk",
|
|
@@ -11762,6 +12234,9 @@ async function handleAnthropicMessages(req, res, options, modelCache, plog) {
|
|
|
11762
12234
|
onTokenRefreshed: (refreshed) => {
|
|
11763
12235
|
model.apiKey = refreshed;
|
|
11764
12236
|
},
|
|
12237
|
+
// Echo the exact requested id when it differs from the upstream id, so
|
|
12238
|
+
// clients that key context windows on the response model still resolve.
|
|
12239
|
+
responseModelOverride: typeof body.model === "string" && body.model !== upstreamModelId(model) ? body.model : void 0,
|
|
11765
12240
|
onUpstreamError: options.inferenceLogPath ? (statusCode, errorContent) => writeInferenceResponseErrorLog(options.inferenceLogPath, {
|
|
11766
12241
|
requestId,
|
|
11767
12242
|
modelId: body.model,
|
|
@@ -11812,6 +12287,7 @@ async function handleAnthropicMessages(req, res, options, modelCache, plog) {
|
|
|
11812
12287
|
supportedParameters: model.supportedParameters,
|
|
11813
12288
|
reasoning: model.reasoning,
|
|
11814
12289
|
interleavedReasoningField: model.interleavedReasoningField,
|
|
12290
|
+
compatibility: model.compatibility,
|
|
11815
12291
|
upstreamModelId: upstreamModelId(model)
|
|
11816
12292
|
},
|
|
11817
12293
|
maxTools: npmMaxTools
|
|
@@ -12092,6 +12568,7 @@ async function getOrInitLanguageModel(modelCache, model, npm, baseURL, apiKey, w
|
|
|
12092
12568
|
headers: model.headers,
|
|
12093
12569
|
useResponsesLite: model.useResponsesLite,
|
|
12094
12570
|
preferWebSockets: model.preferWebSockets,
|
|
12571
|
+
compatibility: model.compatibility,
|
|
12095
12572
|
onWebSocketDiagnostic: webSocketDiagnosticsLogPath ? (event) => writeWebSocketDiagnosticLog(webSocketDiagnosticsLogPath, event) : void 0
|
|
12096
12573
|
});
|
|
12097
12574
|
cached = { apiKey, languageModel };
|
|
@@ -13435,7 +13912,8 @@ function enrichServerModelReasoning(model) {
|
|
|
13435
13912
|
apiBaseUrl: model.apiBaseUrl,
|
|
13436
13913
|
supportedParameters: model.supportedParameters,
|
|
13437
13914
|
reasoning: model.reasoning,
|
|
13438
|
-
interleavedReasoningField: model.interleavedReasoningField
|
|
13915
|
+
interleavedReasoningField: model.interleavedReasoningField,
|
|
13916
|
+
compatibility: model.compatibility
|
|
13439
13917
|
});
|
|
13440
13918
|
if (!caps.defaultLevel) return model;
|
|
13441
13919
|
return { ...model, defaultEffort: caps.defaultLevel };
|
|
@@ -14931,6 +15409,7 @@ function buildDesiredPatchConfig() {
|
|
|
14931
15409
|
supportedParameters: model.supportedParameters,
|
|
14932
15410
|
reasoning: model.reasoning ?? modelsDev?.reasoning,
|
|
14933
15411
|
interleavedReasoningField: model.interleavedReasoningField ?? modelsDev?.interleaved?.field,
|
|
15412
|
+
compatibility: model.compatibility,
|
|
14934
15413
|
upstreamModelId: upstreamModelId2
|
|
14935
15414
|
});
|
|
14936
15415
|
meta.set(`${provider.id}:${model.id}`, {
|
|
@@ -16570,6 +17049,7 @@ Error: ${launchPlan.error}
|
|
|
16570
17049
|
interleavedReasoningField: selectedModel.interleavedReasoningField,
|
|
16571
17050
|
useResponsesLite: selectedModel.useResponsesLite,
|
|
16572
17051
|
preferWebSockets: selectedModel.preferWebSockets,
|
|
17052
|
+
compatibility: selectedModel.compatibility,
|
|
16573
17053
|
headers: activeProvider.headers
|
|
16574
17054
|
},
|
|
16575
17055
|
launchApiKey ?? ""
|