@bman654/clodex 2.11.0 → 2.11.2
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 +41 -21
- package/dist/cli.js +423 -174
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -382,7 +382,7 @@ import { join } from "path";
|
|
|
382
382
|
// package.json
|
|
383
383
|
var package_default = {
|
|
384
384
|
name: "@bman654/clodex",
|
|
385
|
-
version: "2.11.
|
|
385
|
+
version: "2.11.2",
|
|
386
386
|
publishConfig: {
|
|
387
387
|
access: "public"
|
|
388
388
|
},
|
|
@@ -856,21 +856,31 @@ async function sleepMs(ms) {
|
|
|
856
856
|
}
|
|
857
857
|
|
|
858
858
|
// src/oauth/refresh-http.ts
|
|
859
|
-
var
|
|
860
|
-
async function
|
|
861
|
-
const isJson = options.contentType === "json";
|
|
859
|
+
var OAUTH_REQUEST_TIMEOUT_MS = 3e4;
|
|
860
|
+
async function withOAuthRequestTimeout(operation, timeoutMs = OAUTH_REQUEST_TIMEOUT_MS) {
|
|
862
861
|
const abortController = new AbortController();
|
|
863
862
|
const timeout = setTimeout(() => {
|
|
864
863
|
abortController.abort(new DOMException(
|
|
865
864
|
"The operation was aborted due to timeout",
|
|
866
865
|
"TimeoutError"
|
|
867
866
|
));
|
|
868
|
-
},
|
|
867
|
+
}, timeoutMs);
|
|
869
868
|
timeout.unref();
|
|
870
869
|
try {
|
|
870
|
+
return await operation(abortController.signal);
|
|
871
|
+
} finally {
|
|
872
|
+
clearTimeout(timeout);
|
|
873
|
+
if (!abortController.signal.aborted) {
|
|
874
|
+
abortController.abort(new Error("OAuth request completed"));
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
async function postOAuthRefresh(url, body, options) {
|
|
879
|
+
const isJson = options.contentType === "json";
|
|
880
|
+
return withOAuthRequestTimeout(async (signal) => {
|
|
871
881
|
const response = await fetch(url, {
|
|
872
882
|
method: "POST",
|
|
873
|
-
signal
|
|
883
|
+
signal,
|
|
874
884
|
headers: {
|
|
875
885
|
"Content-Type": isJson ? "application/json" : "application/x-www-form-urlencoded",
|
|
876
886
|
Accept: "application/json",
|
|
@@ -892,9 +902,7 @@ async function postOAuthRefresh(url, body, options) {
|
|
|
892
902
|
throw new Error(`${options.errorPrefix}${status}${detail ? `: ${detail}` : ""}`);
|
|
893
903
|
}
|
|
894
904
|
return await response.json();
|
|
895
|
-
}
|
|
896
|
-
clearTimeout(timeout);
|
|
897
|
-
}
|
|
905
|
+
});
|
|
898
906
|
}
|
|
899
907
|
|
|
900
908
|
// src/oauth/callback-server.ts
|
|
@@ -1007,6 +1015,9 @@ var OAUTH_POLLING_SAFETY_MARGIN_MS = 3e3;
|
|
|
1007
1015
|
var DEVICE_CODE_DEFAULT_EXPIRES_MS = 5 * 60 * 1e3;
|
|
1008
1016
|
var BROWSER_CALLBACK_PORTS = [1455, 1457];
|
|
1009
1017
|
var BROWSER_CALLBACK_PATH = "/auth/callback";
|
|
1018
|
+
function isTimeoutError(error) {
|
|
1019
|
+
return error instanceof Error && error.name === "TimeoutError";
|
|
1020
|
+
}
|
|
1010
1021
|
function extractOpenAiAccountId(tokens) {
|
|
1011
1022
|
const token = tokens.id_token ?? tokens.access_token;
|
|
1012
1023
|
if (!token) return void 0;
|
|
@@ -1020,18 +1031,30 @@ function extractOpenAiAccountId(tokens) {
|
|
|
1020
1031
|
}
|
|
1021
1032
|
}
|
|
1022
1033
|
async function requestOpenAiDeviceCode() {
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1034
|
+
try {
|
|
1035
|
+
return await withOAuthRequestTimeout(async (signal) => {
|
|
1036
|
+
const response = await fetch(`${ISSUER}/api/accounts/deviceauth/usercode`, {
|
|
1037
|
+
method: "POST",
|
|
1038
|
+
headers: {
|
|
1039
|
+
"Content-Type": "application/json",
|
|
1040
|
+
"User-Agent": `clodex/${VERSION}`
|
|
1041
|
+
},
|
|
1042
|
+
body: JSON.stringify({ client_id: CLIENT_ID }),
|
|
1043
|
+
signal
|
|
1044
|
+
});
|
|
1045
|
+
if (!response.ok) {
|
|
1046
|
+
throw new Error("Failed to initiate OpenAI device authorization");
|
|
1047
|
+
}
|
|
1048
|
+
return await response.json();
|
|
1049
|
+
});
|
|
1050
|
+
} catch (error) {
|
|
1051
|
+
if (isTimeoutError(error)) {
|
|
1052
|
+
throw new Error("OpenAI device authorization timed out while requesting a sign-in code", {
|
|
1053
|
+
cause: error
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
1056
|
+
throw error;
|
|
1033
1057
|
}
|
|
1034
|
-
return response.json();
|
|
1035
1058
|
}
|
|
1036
1059
|
function openAiDeviceCodeUrl() {
|
|
1037
1060
|
return `${ISSUER}/codex/device`;
|
|
@@ -1041,41 +1064,65 @@ async function pollOpenAiDeviceCodeToken(deviceData, opts) {
|
|
|
1041
1064
|
const now = opts?.now ?? (() => Date.now());
|
|
1042
1065
|
const intervalMs = Math.max(parseInt(deviceData.interval, 10) || 5, 1) * 1e3;
|
|
1043
1066
|
const deadline = now() + positiveSecondsToMs(deviceData.expires_in, DEVICE_CODE_DEFAULT_EXPIRES_MS);
|
|
1067
|
+
const remainingPollTimeoutMs = () => Math.min(OAUTH_REQUEST_TIMEOUT_MS, Math.max(0, deadline - now()));
|
|
1068
|
+
const waitForNextPoll = () => sleep(Math.min(intervalMs + OAUTH_POLLING_SAFETY_MARGIN_MS, Math.max(0, deadline - now())));
|
|
1044
1069
|
while (now() < deadline) {
|
|
1045
|
-
const
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1070
|
+
const pollTimeoutMs = remainingPollTimeoutMs();
|
|
1071
|
+
if (pollTimeoutMs <= 0) break;
|
|
1072
|
+
let result;
|
|
1073
|
+
try {
|
|
1074
|
+
result = await withOAuthRequestTimeout(async (signal) => {
|
|
1075
|
+
const response = await fetch(`${ISSUER}/api/accounts/deviceauth/token`, {
|
|
1076
|
+
method: "POST",
|
|
1077
|
+
headers: {
|
|
1078
|
+
"Content-Type": "application/json",
|
|
1079
|
+
"User-Agent": `clodex/${VERSION}`
|
|
1080
|
+
},
|
|
1081
|
+
body: JSON.stringify({
|
|
1082
|
+
device_auth_id: deviceData.device_auth_id,
|
|
1083
|
+
user_code: deviceData.user_code
|
|
1084
|
+
}),
|
|
1085
|
+
signal
|
|
1086
|
+
});
|
|
1087
|
+
const data = response.ok ? await response.json() : void 0;
|
|
1088
|
+
return { status: response.status, data };
|
|
1089
|
+
}, pollTimeoutMs);
|
|
1090
|
+
} catch (error) {
|
|
1091
|
+
if (!isTimeoutError(error)) throw error;
|
|
1092
|
+
if (now() >= deadline) break;
|
|
1093
|
+
await waitForNextPoll();
|
|
1094
|
+
continue;
|
|
1095
|
+
}
|
|
1096
|
+
if (result.data) {
|
|
1097
|
+
let tokens;
|
|
1098
|
+
try {
|
|
1099
|
+
tokens = await postOAuthRefresh(
|
|
1100
|
+
`${ISSUER}/oauth/token`,
|
|
1101
|
+
new URLSearchParams({
|
|
1102
|
+
grant_type: "authorization_code",
|
|
1103
|
+
code: result.data.authorization_code,
|
|
1104
|
+
redirect_uri: `${ISSUER}/deviceauth/callback`,
|
|
1105
|
+
client_id: CLIENT_ID,
|
|
1106
|
+
code_verifier: result.data.code_verifier
|
|
1107
|
+
}),
|
|
1108
|
+
{
|
|
1109
|
+
contentType: "form",
|
|
1110
|
+
errorPrefix: "OpenAI token exchange failed",
|
|
1111
|
+
includeStatus: true
|
|
1112
|
+
}
|
|
1113
|
+
);
|
|
1114
|
+
} catch (error) {
|
|
1115
|
+
if (isTimeoutError(error)) {
|
|
1116
|
+
throw new Error("OpenAI token exchange timed out", { cause: error });
|
|
1117
|
+
}
|
|
1118
|
+
throw error;
|
|
1071
1119
|
}
|
|
1072
|
-
const tokens = await tokenResponse.json();
|
|
1073
1120
|
return { tokens, accountId: extractOpenAiAccountId(tokens) };
|
|
1074
1121
|
}
|
|
1075
|
-
if (
|
|
1076
|
-
throw new Error(`OpenAI device authorization failed (${
|
|
1122
|
+
if (result.status !== 403 && result.status !== 404) {
|
|
1123
|
+
throw new Error(`OpenAI device authorization failed (${result.status})`);
|
|
1077
1124
|
}
|
|
1078
|
-
await
|
|
1125
|
+
await waitForNextPoll();
|
|
1079
1126
|
}
|
|
1080
1127
|
throw new Error("OpenAI device authorization timed out");
|
|
1081
1128
|
}
|
|
@@ -3121,7 +3168,6 @@ async function deleteProviderCredential(authRef, diag) {
|
|
|
3121
3168
|
}
|
|
3122
3169
|
|
|
3123
3170
|
// src/context-modes.ts
|
|
3124
|
-
var DEFAULT_EFFECTIVE_CONTEXT_PERCENT = 95;
|
|
3125
3171
|
function positiveInteger(value) {
|
|
3126
3172
|
return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : void 0;
|
|
3127
3173
|
}
|
|
@@ -5919,6 +5965,10 @@ function openAiPricingMetadata(id) {
|
|
|
5919
5965
|
pricingBoundaryNote: GPT_5_6_PRICING_NOTE
|
|
5920
5966
|
};
|
|
5921
5967
|
}
|
|
5968
|
+
var LEGACY_IMPOSED_CONTEXT_PERCENT = 95;
|
|
5969
|
+
function migratedEffectiveContextPercent(cached) {
|
|
5970
|
+
return cached === LEGACY_IMPOSED_CONTEXT_PERCENT ? void 0 : cached;
|
|
5971
|
+
}
|
|
5922
5972
|
function applyOAuthSeedContextMetadata(models) {
|
|
5923
5973
|
const seedById = new Map(buildOpenAiOAuthModels().map((model) => [model.id, model]));
|
|
5924
5974
|
return models.map((model) => {
|
|
@@ -5927,7 +5977,7 @@ function applyOAuthSeedContextMetadata(models) {
|
|
|
5927
5977
|
return {
|
|
5928
5978
|
...model,
|
|
5929
5979
|
maxContextWindow: model.maxContextWindow ?? seed?.maxContextWindow,
|
|
5930
|
-
effectiveContextPercent: model.effectiveContextPercent
|
|
5980
|
+
effectiveContextPercent: migratedEffectiveContextPercent(model.effectiveContextPercent),
|
|
5931
5981
|
pricingBoundary: model.pricingBoundary ?? seed?.pricingBoundary ?? pricing.pricingBoundary,
|
|
5932
5982
|
pricingBoundaryNote: model.pricingBoundaryNote ?? seed?.pricingBoundaryNote ?? pricing.pricingBoundaryNote,
|
|
5933
5983
|
maxOutputTokens: model.maxOutputTokens ?? seed?.maxOutputTokens,
|
|
@@ -5947,7 +5997,7 @@ function buildOpenAiOAuthModels() {
|
|
|
5947
5997
|
brand: deriveBrand(prefix),
|
|
5948
5998
|
contextWindow: resolveContextWindow(seed.id, seed.contextWindow),
|
|
5949
5999
|
maxContextWindow: seed.maxContextWindow,
|
|
5950
|
-
effectiveContextPercent: seed.effectiveContextPercent
|
|
6000
|
+
effectiveContextPercent: seed.effectiveContextPercent,
|
|
5951
6001
|
pricingBoundary: seed.pricingBoundary ?? pricing.pricingBoundary,
|
|
5952
6002
|
pricingBoundaryNote: seed.pricingBoundaryNote ?? pricing.pricingBoundaryNote,
|
|
5953
6003
|
maxOutputTokens: seed.maxOutputTokens,
|
|
@@ -7789,6 +7839,23 @@ function clampRetryAfterSeconds(value) {
|
|
|
7789
7839
|
}
|
|
7790
7840
|
return Math.min(Math.round(value), MAX_RETRY_AFTER_SECONDS);
|
|
7791
7841
|
}
|
|
7842
|
+
function clampAiSdkRetryAfterSeconds(value) {
|
|
7843
|
+
return Math.min(clampRetryAfterSeconds(value), MAX_RETRY_AFTER_SECONDS - 1);
|
|
7844
|
+
}
|
|
7845
|
+
var RETRY_AFTER_PARAM_PREFIX = "clodex_retry_after:";
|
|
7846
|
+
function retryAfterProvenanceParam(provenance) {
|
|
7847
|
+
return provenance.source === "default" ? `${RETRY_AFTER_PARAM_PREFIX}default` : `${RETRY_AFTER_PARAM_PREFIX}upstream:${String(provenance.rawSeconds)}`;
|
|
7848
|
+
}
|
|
7849
|
+
function retryAfterProvenanceFromParam(value) {
|
|
7850
|
+
if (value === `${RETRY_AFTER_PARAM_PREFIX}default`) return { source: "default" };
|
|
7851
|
+
if (typeof value !== "string" || !value.startsWith(`${RETRY_AFTER_PARAM_PREFIX}upstream:`)) {
|
|
7852
|
+
return void 0;
|
|
7853
|
+
}
|
|
7854
|
+
const rawValue = value.slice(`${RETRY_AFTER_PARAM_PREFIX}upstream:`.length);
|
|
7855
|
+
if (rawValue.length === 0) return void 0;
|
|
7856
|
+
const rawSeconds = Number(rawValue);
|
|
7857
|
+
return Number.isFinite(rawSeconds) ? { source: "upstream", rawSeconds } : void 0;
|
|
7858
|
+
}
|
|
7792
7859
|
function retryAfterFromText(message) {
|
|
7793
7860
|
if (typeof message !== "string") return void 0;
|
|
7794
7861
|
const match = /retry after (\d+)s\b/i.exec(message);
|
|
@@ -7990,7 +8057,8 @@ function upstreamHttpStatus(err, message) {
|
|
|
7990
8057
|
if (message.includes("HTTP 400")) return 400;
|
|
7991
8058
|
return 500;
|
|
7992
8059
|
}
|
|
7993
|
-
function anthropicErrorType(status) {
|
|
8060
|
+
function anthropicErrorType(status, transportCode) {
|
|
8061
|
+
if (transportCode === "websocket_transport_error") return "overloaded_error";
|
|
7994
8062
|
switch (status) {
|
|
7995
8063
|
case 400:
|
|
7996
8064
|
return "invalid_request_error";
|
|
@@ -8288,8 +8356,11 @@ var WsUpgradePacer = class {
|
|
|
8288
8356
|
}
|
|
8289
8357
|
/**
|
|
8290
8358
|
* Resolves when this request may open a new connection, or resolves to a
|
|
8291
|
-
* refusal the caller must report as a retryable rate limit.
|
|
8292
|
-
* reuse an existing connection must not call this at all
|
|
8359
|
+
* refusal the caller must report as a retryable rate limit. A caller that can
|
|
8360
|
+
* already reuse an existing connection must not call this at all; one that
|
|
8361
|
+
* discovers a reusable connection only after being admitted must hand the
|
|
8362
|
+
* token back through `release` instead of holding it for a socket it never
|
|
8363
|
+
* opened.
|
|
8293
8364
|
*/
|
|
8294
8365
|
async admit(signal) {
|
|
8295
8366
|
if (!this.enabled) return { kind: "admitted", waitedMs: 0 };
|
|
@@ -8307,7 +8378,14 @@ var WsUpgradePacer = class {
|
|
|
8307
8378
|
)
|
|
8308
8379
|
};
|
|
8309
8380
|
}
|
|
8310
|
-
if (reservation.waitMs <= 0)
|
|
8381
|
+
if (reservation.waitMs <= 0) {
|
|
8382
|
+
return {
|
|
8383
|
+
kind: "admitted",
|
|
8384
|
+
waitedMs: 0,
|
|
8385
|
+
queued: false,
|
|
8386
|
+
release: this.releaser(reservation.consumed)
|
|
8387
|
+
};
|
|
8388
|
+
}
|
|
8311
8389
|
const startedAt = this.now();
|
|
8312
8390
|
try {
|
|
8313
8391
|
await this.sleep(reservation.waitMs, signal);
|
|
@@ -8315,7 +8393,28 @@ var WsUpgradePacer = class {
|
|
|
8315
8393
|
this.refund(reservation.consumed);
|
|
8316
8394
|
throw error;
|
|
8317
8395
|
}
|
|
8318
|
-
return {
|
|
8396
|
+
return {
|
|
8397
|
+
kind: "admitted",
|
|
8398
|
+
// A clock moved backwards during the wait would otherwise report this
|
|
8399
|
+
// request as never queued at all.
|
|
8400
|
+
waitedMs: Math.max(0, this.now() - startedAt),
|
|
8401
|
+
queued: true,
|
|
8402
|
+
release: this.releaser(reservation.consumed)
|
|
8403
|
+
};
|
|
8404
|
+
}
|
|
8405
|
+
/**
|
|
8406
|
+
* One-shot refund for an admission whose caller opened nothing. Guarded
|
|
8407
|
+
* because a second call would mint a token the bucket never charged, which
|
|
8408
|
+
* is the one way a refund can raise the sustained rate instead of correcting
|
|
8409
|
+
* it.
|
|
8410
|
+
*/
|
|
8411
|
+
releaser(consumed) {
|
|
8412
|
+
let released = false;
|
|
8413
|
+
return () => {
|
|
8414
|
+
if (released) return;
|
|
8415
|
+
released = true;
|
|
8416
|
+
this.refund(consumed);
|
|
8417
|
+
};
|
|
8319
8418
|
}
|
|
8320
8419
|
reserve(now) {
|
|
8321
8420
|
const elapsed = Math.max(0, now - this.lastRefillAt);
|
|
@@ -8688,7 +8787,11 @@ function warnToolArgumentNormalizationGap(gap, log12) {
|
|
|
8688
8787
|
emitParentNotice("clodex: warning: further tool-argument normalization warnings suppressed.");
|
|
8689
8788
|
}
|
|
8690
8789
|
}
|
|
8691
|
-
function continuationMismatchDetails(entry, payload, log12, warnOnGap = false) {
|
|
8790
|
+
function continuationMismatchDetails(entry, payload, log12, warnOnGap = false, deferWarnings) {
|
|
8791
|
+
const raise = (warn) => {
|
|
8792
|
+
if (deferWarnings) deferWarnings.push(warn);
|
|
8793
|
+
else warn();
|
|
8794
|
+
};
|
|
8692
8795
|
const full = inputArray(payload);
|
|
8693
8796
|
const prefix = [...entry.requestInput ?? [], ...entry.expectedAssistant ?? []];
|
|
8694
8797
|
const comparable = Math.min(full.length, prefix.length);
|
|
@@ -8702,7 +8805,7 @@ function continuationMismatchDetails(entry, payload, log12, warnOnGap = false) {
|
|
|
8702
8805
|
const expected = mismatch < prefix.length ? prefix[mismatch] : void 0;
|
|
8703
8806
|
const actual = mismatch < full.length ? full[mismatch] : void 0;
|
|
8704
8807
|
const reasoningGap = reasoningNormalizationGap(expected, actual);
|
|
8705
|
-
if (reasoningGap && warnOnGap) warnReasoningNormalizationGap(reasoningGap, log12);
|
|
8808
|
+
if (reasoningGap && warnOnGap) raise(() => warnReasoningNormalizationGap(reasoningGap, log12));
|
|
8706
8809
|
let gapExpected = expected;
|
|
8707
8810
|
if (conversationItemKind(expected) === "reasoning" && conversationItemKind(actual) === "function_call") {
|
|
8708
8811
|
for (let index = mismatch; index < prefix.length; index += 1) {
|
|
@@ -8724,7 +8827,8 @@ function continuationMismatchDetails(entry, payload, log12, warnOnGap = false) {
|
|
|
8724
8827
|
} catch {
|
|
8725
8828
|
}
|
|
8726
8829
|
if (toolArgumentGap?.equalAfterStrip === true) {
|
|
8727
|
-
|
|
8830
|
+
const gap = toolArgumentGap;
|
|
8831
|
+
if (warnOnGap) raise(() => warnToolArgumentNormalizationGap(gap, log12));
|
|
8728
8832
|
} else if (toolArgumentGap && warnOnGap) {
|
|
8729
8833
|
try {
|
|
8730
8834
|
log12?.(`tool argument mismatch beyond the strip rule: ${String(toolArgumentGap.tool)}`);
|
|
@@ -9173,11 +9277,16 @@ function deleteEntry(entry, closeSocket = true) {
|
|
|
9173
9277
|
}
|
|
9174
9278
|
}
|
|
9175
9279
|
}
|
|
9176
|
-
function failContext(entry, ctx, message, diagnosticDetails, statusCode,
|
|
9280
|
+
function failContext(entry, ctx, message, diagnosticDetails, statusCode, retryAfter) {
|
|
9177
9281
|
if (ctx.closed || entry.current !== ctx) return;
|
|
9282
|
+
const retryAfterSeconds = retryAfter === void 0 ? void 0 : clampRetryAfterSeconds(retryAfter.seconds);
|
|
9178
9283
|
entry.debug(`fail: ${message}`);
|
|
9179
9284
|
emitResponseErrorDiagnostic(entry, ctx, {
|
|
9180
9285
|
...diagnosticDetails,
|
|
9286
|
+
...retryAfter !== void 0 ? {
|
|
9287
|
+
retryAfterSource: retryAfter.source,
|
|
9288
|
+
..."rawSeconds" in retryAfter ? { rawRetryAfterSeconds: retryAfter.rawSeconds } : {}
|
|
9289
|
+
} : {},
|
|
9181
9290
|
...diagnosticTextFingerprint("errorMessage", message)
|
|
9182
9291
|
});
|
|
9183
9292
|
flushPending(ctx);
|
|
@@ -9188,7 +9297,7 @@ function failContext(entry, ctx, message, diagnosticDetails, statusCode, retryAf
|
|
|
9188
9297
|
type: statusCode === void 0 ? "transport_error" : anthropicErrorType(statusCode),
|
|
9189
9298
|
code: statusCode === void 0 ? "websocket_transport_error" : String(statusCode),
|
|
9190
9299
|
message,
|
|
9191
|
-
param: null,
|
|
9300
|
+
param: retryAfter === void 0 ? null : retryAfterProvenanceParam(retryAfter),
|
|
9192
9301
|
...retryAfterSeconds !== void 0 ? { retry_after_seconds: retryAfterSeconds } : {}
|
|
9193
9302
|
}
|
|
9194
9303
|
});
|
|
@@ -9387,7 +9496,8 @@ function handleSocketMessage(entry, data) {
|
|
|
9387
9496
|
const previousMissing = errorCode === "previous_response_not_found";
|
|
9388
9497
|
const willRetry = previousMissing && ctx.continued && !ctx.retried && !ctx.emittedModelData;
|
|
9389
9498
|
if (errorCode === "websocket_connection_limit_reached" && !ctx.emittedModelData) {
|
|
9390
|
-
const
|
|
9499
|
+
const rawRetryAfterSeconds = responseRetryAfterSeconds(event);
|
|
9500
|
+
const retryAfterSeconds = clampRetryAfterSeconds(rawRetryAfterSeconds);
|
|
9391
9501
|
failContext(
|
|
9392
9502
|
entry,
|
|
9393
9503
|
ctx,
|
|
@@ -9399,7 +9509,7 @@ function handleSocketMessage(entry, data) {
|
|
|
9399
9509
|
retryAfterSeconds
|
|
9400
9510
|
},
|
|
9401
9511
|
429,
|
|
9402
|
-
retryAfterSeconds
|
|
9512
|
+
rawRetryAfterSeconds === void 0 ? { seconds: retryAfterSeconds, source: "default" } : { seconds: retryAfterSeconds, source: "upstream", rawSeconds: rawRetryAfterSeconds }
|
|
9403
9513
|
);
|
|
9404
9514
|
return;
|
|
9405
9515
|
}
|
|
@@ -9424,7 +9534,12 @@ function handleSocketMessage(entry, data) {
|
|
|
9424
9534
|
}
|
|
9425
9535
|
if (errorStatus !== void 0) {
|
|
9426
9536
|
const statedRetryAfter = errorStatus === 429 ? responseRetryAfterSeconds(event) : void 0;
|
|
9427
|
-
const
|
|
9537
|
+
const retryAfter = statedRetryAfter === void 0 ? void 0 : {
|
|
9538
|
+
seconds: clampRetryAfterSeconds(statedRetryAfter),
|
|
9539
|
+
source: "upstream",
|
|
9540
|
+
rawSeconds: statedRetryAfter
|
|
9541
|
+
};
|
|
9542
|
+
const retryAfterSeconds = retryAfter?.seconds;
|
|
9428
9543
|
const reason = responseErrorMessage(event) ?? `OpenAI rejected the request (HTTP ${errorStatus})`;
|
|
9429
9544
|
failContext(
|
|
9430
9545
|
entry,
|
|
@@ -9444,7 +9559,7 @@ function handleSocketMessage(entry, data) {
|
|
|
9444
9559
|
...retryAfterSeconds !== void 0 ? { retryAfterSeconds } : {}
|
|
9445
9560
|
},
|
|
9446
9561
|
errorStatus,
|
|
9447
|
-
|
|
9562
|
+
retryAfter
|
|
9448
9563
|
);
|
|
9449
9564
|
return;
|
|
9450
9565
|
}
|
|
@@ -9458,7 +9573,13 @@ function handleSocketMessage(entry, data) {
|
|
|
9458
9573
|
const classified = numericOrNamed !== void 0 || discriminator ? frameStatusCode(numericOrNamed, discriminator) : 500;
|
|
9459
9574
|
const statusCode = classified !== 500 ? classified : settledReason ? 400 : 502;
|
|
9460
9575
|
const usageLimited = statusCode === 429;
|
|
9461
|
-
const
|
|
9576
|
+
const rawRetryAfterSeconds = usageLimited ? responseRetryAfterSeconds(event) : void 0;
|
|
9577
|
+
const retryAfter = rawRetryAfterSeconds === void 0 ? void 0 : {
|
|
9578
|
+
seconds: clampRetryAfterSeconds(rawRetryAfterSeconds),
|
|
9579
|
+
source: "upstream",
|
|
9580
|
+
rawSeconds: rawRetryAfterSeconds
|
|
9581
|
+
};
|
|
9582
|
+
const retryAfterSeconds = retryAfter?.seconds;
|
|
9462
9583
|
failContext(
|
|
9463
9584
|
entry,
|
|
9464
9585
|
ctx,
|
|
@@ -9477,7 +9598,7 @@ function handleSocketMessage(entry, data) {
|
|
|
9477
9598
|
...diagnosticTextFingerprint("upstreamMessage", responseErrorMessage(event))
|
|
9478
9599
|
},
|
|
9479
9600
|
statusCode,
|
|
9480
|
-
|
|
9601
|
+
retryAfter
|
|
9481
9602
|
);
|
|
9482
9603
|
return;
|
|
9483
9604
|
}
|
|
@@ -9583,15 +9704,14 @@ function createConnection(WebSocket, wsUrl, headers, persistent, key, options, d
|
|
|
9583
9704
|
return;
|
|
9584
9705
|
}
|
|
9585
9706
|
if (statusCode === 403) {
|
|
9586
|
-
const
|
|
9587
|
-
|
|
9588
|
-
);
|
|
9707
|
+
const rawRetryAfterSeconds = numericRetryAfterHeader(response.headers["retry-after"]);
|
|
9708
|
+
const retryAfterSeconds = clampRetryAfterSeconds(rawRetryAfterSeconds);
|
|
9589
9709
|
failContext(entry, ctx, `OpenAI edge throttled the Responses WebSocket upgrade (HTTP 403); retry after ${retryAfterSeconds}s`, {
|
|
9590
9710
|
source: "unexpected_response",
|
|
9591
9711
|
httpStatusCode: statusCode,
|
|
9592
9712
|
mappedStatusCode: 429,
|
|
9593
9713
|
retryAfterSeconds
|
|
9594
|
-
}, 429, retryAfterSeconds);
|
|
9714
|
+
}, 429, rawRetryAfterSeconds === void 0 ? { seconds: retryAfterSeconds, source: "default" } : { seconds: retryAfterSeconds, source: "upstream", rawSeconds: rawRetryAfterSeconds });
|
|
9595
9715
|
return;
|
|
9596
9716
|
}
|
|
9597
9717
|
failContext(entry, ctx, `WebSocket upgrade failed (HTTP ${statusCode})`, {
|
|
@@ -9684,18 +9804,25 @@ function createResponsesWebSocketFetch(wsUrl, log12, options = {}) {
|
|
|
9684
9804
|
const diagnosticCorrelation = diagnosticContext.getStore();
|
|
9685
9805
|
let now = resolvedOptions.now();
|
|
9686
9806
|
const evictions = cleanupExpiredConnections(now);
|
|
9687
|
-
const
|
|
9688
|
-
|
|
9689
|
-
|
|
9690
|
-
|
|
9807
|
+
const scanForHeads = () => {
|
|
9808
|
+
const scanned = partitionKey ? connectionEntries(partitionKey) : [];
|
|
9809
|
+
const idle = scanned.filter((entry) => !entry.inFlight);
|
|
9810
|
+
const clientItems = idle.length ? canonicalItemStrings(inputArray(payload)) : [];
|
|
9811
|
+
return {
|
|
9812
|
+
candidates: scanned,
|
|
9813
|
+
idleCandidates: idle,
|
|
9814
|
+
matches: idle.map((entry) => ({ entry, match: continuationMatch(entry, payload, clientItems) })).filter((candidate) => candidate.match !== void 0).sort((left, right) => left.match.delta.length - right.match.delta.length || (left.match.mode === right.match.mode ? 0 : left.match.mode === "exact" ? -1 : 1))
|
|
9815
|
+
};
|
|
9816
|
+
};
|
|
9817
|
+
let { candidates, idleCandidates, matches } = scanForHeads();
|
|
9691
9818
|
let selected = matches[0]?.entry;
|
|
9692
|
-
|
|
9693
|
-
|
|
9819
|
+
let selectedMatch = matches[0]?.match;
|
|
9820
|
+
let selectedDelta = selectedMatch?.delta;
|
|
9694
9821
|
const diagnosticEntry = selected ?? [...idleCandidates].sort((left, right) => right.lastUsedAt - left.lastUsedAt)[0] ?? candidates[0];
|
|
9695
9822
|
debug(
|
|
9696
9823
|
`lookup key=${debugKey(partitionKey)} prompt=${debugKey(promptFingerprint)} hit=${candidates.length > 0} heads=${candidates.length} active_connections=${connectionCount()}`
|
|
9697
9824
|
);
|
|
9698
|
-
|
|
9825
|
+
let promptChanges = changedPromptFields(diagnosticEntry?.promptFieldHashes, promptFieldHashes);
|
|
9699
9826
|
if (promptChanges.length) debug(`prompt fields changed: ${promptChanges.join(",")}`);
|
|
9700
9827
|
if (promptChanges.includes("instructions")) {
|
|
9701
9828
|
const summary = instructionChangeSummary(diagnosticEntry?.instructionsSnapshot, instructionsSnapshot);
|
|
@@ -9707,35 +9834,49 @@ function createResponsesWebSocketFetch(wsUrl, log12, options = {}) {
|
|
|
9707
9834
|
let promotedConnectionId;
|
|
9708
9835
|
let decision;
|
|
9709
9836
|
let candidateMismatchDetails;
|
|
9710
|
-
|
|
9711
|
-
|
|
9837
|
+
const deferredMismatchWarnings = [];
|
|
9838
|
+
const flushMismatchWarnings = () => {
|
|
9839
|
+
for (const warn of deferredMismatchWarnings) warn();
|
|
9840
|
+
deferredMismatchWarnings.length = 0;
|
|
9841
|
+
};
|
|
9842
|
+
const continueOnHead = (entry, match) => {
|
|
9843
|
+
sendPayload = { ...payload, input: match.delta, previous_response_id: entry.responseId };
|
|
9712
9844
|
continued = true;
|
|
9713
|
-
if (
|
|
9845
|
+
if (entry.generation === "nursery") {
|
|
9714
9846
|
evictions.push(...evictOldestIdleGeneration(
|
|
9715
9847
|
"established",
|
|
9716
9848
|
resolvedOptions.maxConnections,
|
|
9717
9849
|
"established_lru_cap"
|
|
9718
9850
|
));
|
|
9719
|
-
|
|
9720
|
-
promotedConnectionId =
|
|
9851
|
+
entry.generation = "established";
|
|
9852
|
+
promotedConnectionId = entry.debugId;
|
|
9721
9853
|
}
|
|
9722
|
-
decision = "continuation";
|
|
9723
9854
|
debug(
|
|
9724
|
-
`continuing chain with ${
|
|
9855
|
+
`continuing chain with ${match.delta.length} incremental input item(s)` + (match.mode === "omitted_reasoning" ? " after accepting omitted reasoning" : "")
|
|
9725
9856
|
);
|
|
9857
|
+
return "continuation";
|
|
9858
|
+
};
|
|
9859
|
+
if (selected && selectedDelta) {
|
|
9860
|
+
decision = continueOnHead(selected, selectedMatch);
|
|
9726
9861
|
} else if (candidates.some((entry) => entry.inFlight)) {
|
|
9727
9862
|
selected = void 0;
|
|
9728
9863
|
persistent = false;
|
|
9729
9864
|
decision = "parallel_isolated";
|
|
9730
9865
|
debug("parallel request using an isolated socket");
|
|
9731
9866
|
} else if (diagnosticEntry) {
|
|
9732
|
-
const diagnosticMismatch = continuationMismatchDetails(
|
|
9867
|
+
const diagnosticMismatch = continuationMismatchDetails(
|
|
9868
|
+
diagnosticEntry,
|
|
9869
|
+
payload,
|
|
9870
|
+
debug,
|
|
9871
|
+
true,
|
|
9872
|
+
deferredMismatchWarnings
|
|
9873
|
+
);
|
|
9733
9874
|
candidateMismatchDetails = /* @__PURE__ */ new Map([[diagnosticEntry, diagnosticMismatch]]);
|
|
9734
9875
|
for (const candidate of candidates) {
|
|
9735
9876
|
if (candidate === diagnosticEntry) continue;
|
|
9736
9877
|
candidateMismatchDetails.set(
|
|
9737
9878
|
candidate,
|
|
9738
|
-
continuationMismatchDetails(candidate, payload, debug, true)
|
|
9879
|
+
continuationMismatchDetails(candidate, payload, debug, true, deferredMismatchWarnings)
|
|
9739
9880
|
);
|
|
9740
9881
|
}
|
|
9741
9882
|
debug(
|
|
@@ -9754,6 +9895,7 @@ function createResponsesWebSocketFetch(wsUrl, log12, options = {}) {
|
|
|
9754
9895
|
decision = "unpartitioned_socket";
|
|
9755
9896
|
}
|
|
9756
9897
|
let pacingWaitedMs;
|
|
9898
|
+
let pacingRescanOutcome;
|
|
9757
9899
|
if (!selected) {
|
|
9758
9900
|
const pacer = options.pacer ?? sharedWsUpgradePacer();
|
|
9759
9901
|
const pacingStartedAt = resolvedOptions.now();
|
|
@@ -9767,6 +9909,7 @@ function createResponsesWebSocketFetch(wsUrl, log12, options = {}) {
|
|
|
9767
9909
|
decision,
|
|
9768
9910
|
waitedMs: Math.max(0, resolvedOptions.now() - pacingStartedAt)
|
|
9769
9911
|
}, diagnosticCorrelation);
|
|
9912
|
+
flushMismatchWarnings();
|
|
9770
9913
|
throw error;
|
|
9771
9914
|
}
|
|
9772
9915
|
if (admission.kind === "refused") {
|
|
@@ -9780,6 +9923,7 @@ function createResponsesWebSocketFetch(wsUrl, log12, options = {}) {
|
|
|
9780
9923
|
requiredWaitMs: admission.requiredWaitMs,
|
|
9781
9924
|
retryAfterSeconds: admission.retryAfterSeconds
|
|
9782
9925
|
}, diagnosticCorrelation);
|
|
9926
|
+
flushMismatchWarnings();
|
|
9783
9927
|
return pacedRefusalResponse(admission.retryAfterSeconds);
|
|
9784
9928
|
}
|
|
9785
9929
|
if (admission.waitedMs > 0) {
|
|
@@ -9794,12 +9938,42 @@ function createResponsesWebSocketFetch(wsUrl, log12, options = {}) {
|
|
|
9794
9938
|
}
|
|
9795
9939
|
now = resolvedOptions.now();
|
|
9796
9940
|
evictions.push(...cleanupExpiredConnections(now));
|
|
9797
|
-
if (
|
|
9941
|
+
if (admission.queued ?? admission.waitedMs > 0) {
|
|
9942
|
+
const rescan = scanForHeads();
|
|
9943
|
+
const rematched = rescan.matches[0];
|
|
9944
|
+
if (rematched) {
|
|
9945
|
+
({ candidates, idleCandidates, matches } = rescan);
|
|
9946
|
+
selected = rematched.entry;
|
|
9947
|
+
selectedMatch = rematched.match;
|
|
9948
|
+
selectedDelta = rematched.match.delta;
|
|
9949
|
+
persistent = Boolean(partitionKey);
|
|
9950
|
+
promptChanges = changedPromptFields(rematched.entry.promptFieldHashes, promptFieldHashes);
|
|
9951
|
+
decision = continueOnHead(rematched.entry, rematched.match);
|
|
9952
|
+
pacingRescanOutcome = "continuation";
|
|
9953
|
+
debug("continuing a chain head that freed up during the pacing wait");
|
|
9954
|
+
if (promptChanges.length) debug(`prompt fields changed: ${promptChanges.join(",")}`);
|
|
9955
|
+
admission.release?.();
|
|
9956
|
+
} else {
|
|
9957
|
+
pacingRescanOutcome = "no_change";
|
|
9958
|
+
}
|
|
9959
|
+
}
|
|
9960
|
+
if (!selected && persistent && partitionKey && connectionEntries(partitionKey).some((entry) => entry.inFlight)) {
|
|
9798
9961
|
persistent = false;
|
|
9799
9962
|
decision = "parallel_isolated";
|
|
9800
9963
|
debug("parallel request using an isolated socket after pacing");
|
|
9964
|
+
if (pacingRescanOutcome === "no_change") pacingRescanOutcome = "parallel_isolated";
|
|
9801
9965
|
}
|
|
9802
9966
|
}
|
|
9967
|
+
let suppressedMismatchWarnings;
|
|
9968
|
+
if (selected && deferredMismatchWarnings.length) {
|
|
9969
|
+
suppressedMismatchWarnings = deferredMismatchWarnings.length;
|
|
9970
|
+
debug(
|
|
9971
|
+
`suppressed ${suppressedMismatchWarnings} arrival mismatch warning(s) after continuing a head that freed up during the pacing wait`
|
|
9972
|
+
);
|
|
9973
|
+
deferredMismatchWarnings.length = 0;
|
|
9974
|
+
} else {
|
|
9975
|
+
flushMismatchWarnings();
|
|
9976
|
+
}
|
|
9803
9977
|
if (!selected && persistent) {
|
|
9804
9978
|
evictions.push(...evictOldestIdleGeneration(
|
|
9805
9979
|
"nursery",
|
|
@@ -9842,6 +10016,8 @@ function createResponsesWebSocketFetch(wsUrl, log12, options = {}) {
|
|
|
9842
10016
|
promotedConnectionId,
|
|
9843
10017
|
createdConnectionId: selected ? void 0 : nextConnectionDebugId,
|
|
9844
10018
|
...pacingWaitedMs !== void 0 ? { pacingWaitedMs } : {},
|
|
10019
|
+
...pacingRescanOutcome !== void 0 ? { pacingRescanOutcome } : {},
|
|
10020
|
+
...suppressedMismatchWarnings !== void 0 ? { suppressedMismatchWarnings } : {},
|
|
9845
10021
|
createdGeneration: selected ? void 0 : persistent ? "nursery" : "isolated",
|
|
9846
10022
|
incrementalInputItems: selectedDelta?.length,
|
|
9847
10023
|
heads: candidates.map((entry) => ({
|
|
@@ -10925,6 +11101,28 @@ function sendJson(res, status, body) {
|
|
|
10925
11101
|
res.writeHead(status, { "Content-Type": "application/json" });
|
|
10926
11102
|
res.end(json);
|
|
10927
11103
|
}
|
|
11104
|
+
var RESPONSE_COMPLETED = /* @__PURE__ */ Symbol.for("clodex.responseCompleted");
|
|
11105
|
+
var ResponseCompleted = class extends Error {
|
|
11106
|
+
[RESPONSE_COMPLETED] = true;
|
|
11107
|
+
constructor() {
|
|
11108
|
+
super("Response completed");
|
|
11109
|
+
this.name = "ResponseCompleted";
|
|
11110
|
+
}
|
|
11111
|
+
};
|
|
11112
|
+
function clientDisconnected(signal) {
|
|
11113
|
+
if (!signal.aborted) return false;
|
|
11114
|
+
const reason = signal.reason;
|
|
11115
|
+
return !(typeof reason === "object" && reason !== null && RESPONSE_COMPLETED in reason);
|
|
11116
|
+
}
|
|
11117
|
+
function watchClientDisconnect(res) {
|
|
11118
|
+
const controller = new AbortController();
|
|
11119
|
+
res.once("close", () => {
|
|
11120
|
+
controller.abort(
|
|
11121
|
+
res.writableFinished ? new ResponseCompleted() : new Error("Client disconnected")
|
|
11122
|
+
);
|
|
11123
|
+
});
|
|
11124
|
+
return controller;
|
|
11125
|
+
}
|
|
10928
11126
|
|
|
10929
11127
|
// src/server/vendor-mask.ts
|
|
10930
11128
|
function reverseSegment(value) {
|
|
@@ -11409,7 +11607,23 @@ function resolveUpstreamTools(tools, messages) {
|
|
|
11409
11607
|
}
|
|
11410
11608
|
|
|
11411
11609
|
// src/upstream-attempts.ts
|
|
11412
|
-
import { RetryError as RetryError2, wrapLanguageModel as wrapLanguageModel2 } from "ai";
|
|
11610
|
+
import { APICallError as APICallError2, RetryError as RetryError2, wrapLanguageModel as wrapLanguageModel2 } from "ai";
|
|
11611
|
+
function restoreSyntheticRetryAfterHeader(error) {
|
|
11612
|
+
if (!APICallError2.isInstance(error) || error.statusCode !== 429) return;
|
|
11613
|
+
const headers = error.responseHeaders;
|
|
11614
|
+
if (!headers || headers["retry-after"] !== void 0 || headers["retry-after-ms"] !== void 0) return;
|
|
11615
|
+
const rawFrame = error.data;
|
|
11616
|
+
if (!rawFrame || typeof rawFrame !== "object" || rawFrame.type !== "error") return;
|
|
11617
|
+
const frame = providerErrorFrame(rawFrame);
|
|
11618
|
+
if (frame?.retryAfterSeconds === void 0) return;
|
|
11619
|
+
const nestedError = rawFrame.error;
|
|
11620
|
+
const provenance = retryAfterProvenanceFromParam(
|
|
11621
|
+
nestedError && typeof nestedError === "object" ? nestedError.param : void 0
|
|
11622
|
+
);
|
|
11623
|
+
if (provenance?.source !== "upstream") return;
|
|
11624
|
+
if (provenance.rawSeconds < 0 || provenance.rawSeconds > MAX_RETRY_AFTER_SECONDS) return;
|
|
11625
|
+
headers["retry-after"] = String(clampAiSdkRetryAfterSeconds(provenance.rawSeconds));
|
|
11626
|
+
}
|
|
11413
11627
|
function trackUpstreamAttempts(model) {
|
|
11414
11628
|
if (typeof model === "string") {
|
|
11415
11629
|
return { model, deadlineError: (timeoutError) => timeoutError };
|
|
@@ -11423,6 +11637,7 @@ function trackUpstreamAttempts(model) {
|
|
|
11423
11637
|
failedAttempts.length = 0;
|
|
11424
11638
|
return result;
|
|
11425
11639
|
} catch (error) {
|
|
11640
|
+
restoreSyntheticRetryAfterHeader(error);
|
|
11426
11641
|
failedAttempts.push(error);
|
|
11427
11642
|
waitingToRetry = true;
|
|
11428
11643
|
throw error;
|
|
@@ -12079,9 +12294,10 @@ async function writeAnthropicStream(stream, modelId, write, log12, observer, too
|
|
|
12079
12294
|
case "error": {
|
|
12080
12295
|
const e = part.error;
|
|
12081
12296
|
const errMsg = e?.message || (typeof part.error === "string" ? part.error : JSON.stringify(e?.data ?? part.error));
|
|
12082
|
-
const
|
|
12297
|
+
const transportCode = sdkUpstreamErrorDetails(part.error)?.transportCode;
|
|
12298
|
+
const errorType = anthropicErrorType(upstreamHttpStatus(part.error, errMsg), transportCode);
|
|
12083
12299
|
log12?.(() => `sdk stream error (${errorType}): ${errMsg}`);
|
|
12084
|
-
closeOpen();
|
|
12300
|
+
if (!(transportCode === "websocket_transport_error" && openType === "thinking")) closeOpen();
|
|
12085
12301
|
throw part.error instanceof Error || part.error && typeof part.error === "object" ? part.error : new Error(errMsg);
|
|
12086
12302
|
}
|
|
12087
12303
|
default:
|
|
@@ -12523,14 +12739,7 @@ async function startProxyCatalog(routes, defaultAliasId, debug = false, inferenc
|
|
|
12523
12739
|
anthropicError(res, 401, "Invalid proxy token");
|
|
12524
12740
|
return;
|
|
12525
12741
|
}
|
|
12526
|
-
const clientAbort =
|
|
12527
|
-
const abortForClientDisconnect = () => {
|
|
12528
|
-
if (!clientAbort.signal.aborted) clientAbort.abort(new Error("Client disconnected"));
|
|
12529
|
-
};
|
|
12530
|
-
req.once("aborted", abortForClientDisconnect);
|
|
12531
|
-
res.once("close", () => {
|
|
12532
|
-
if (!res.writableFinished) abortForClientDisconnect();
|
|
12533
|
-
});
|
|
12742
|
+
const clientAbort = watchClientDisconnect(res);
|
|
12534
12743
|
let anthropicBody;
|
|
12535
12744
|
try {
|
|
12536
12745
|
const raw = await readBody(req);
|
|
@@ -12610,7 +12819,7 @@ async function startProxyCatalog(routes, defaultAliasId, debug = false, inferenc
|
|
|
12610
12819
|
signal: clientAbort.signal
|
|
12611
12820
|
});
|
|
12612
12821
|
} catch (err) {
|
|
12613
|
-
if (clientAbort.signal
|
|
12822
|
+
if (clientDisconnected(clientAbort.signal)) return;
|
|
12614
12823
|
const message = err instanceof UpstreamUnreachableError ? err.message : String(err);
|
|
12615
12824
|
plog(() => `anthropic token-count error: ${message}`);
|
|
12616
12825
|
anthropicError(res, 502, message);
|
|
@@ -12671,7 +12880,7 @@ async function startProxyCatalog(routes, defaultAliasId, debug = false, inferenc
|
|
|
12671
12880
|
}) : void 0
|
|
12672
12881
|
});
|
|
12673
12882
|
} catch (err) {
|
|
12674
|
-
if (clientAbort.signal
|
|
12883
|
+
if (clientDisconnected(clientAbort.signal)) return;
|
|
12675
12884
|
const message = err instanceof UpstreamUnreachableError ? err.message : String(err);
|
|
12676
12885
|
plog(() => `anthropic-passthrough error: ${message}`);
|
|
12677
12886
|
anthropicError(res, 502, message);
|
|
@@ -12811,7 +13020,7 @@ async function startProxyCatalog(routes, defaultAliasId, debug = false, inferenc
|
|
|
12811
13020
|
};
|
|
12812
13021
|
let sdkAttempt = 0;
|
|
12813
13022
|
const handleSdkError = async (err) => {
|
|
12814
|
-
if (clientAbort.signal
|
|
13023
|
+
if (clientDisconnected(clientAbort.signal)) {
|
|
12815
13024
|
translationLifecycle?.cancel();
|
|
12816
13025
|
return "cancelled";
|
|
12817
13026
|
}
|
|
@@ -12867,7 +13076,7 @@ async function startProxyCatalog(routes, defaultAliasId, debug = false, inferenc
|
|
|
12867
13076
|
contextLengthExceeded ? relayRequestId ?? randomUUID3() : void 0
|
|
12868
13077
|
);
|
|
12869
13078
|
} else {
|
|
12870
|
-
const errorType = anthropicErrorType(upstreamStatus);
|
|
13079
|
+
const errorType = anthropicErrorType(upstreamStatus, details?.transportCode);
|
|
12871
13080
|
res.write(`event: error
|
|
12872
13081
|
data: ${JSON.stringify({
|
|
12873
13082
|
type: "error",
|
|
@@ -15090,7 +15299,10 @@ function buildDynamicOAuthModel(entry, seedById, codexCatalog) {
|
|
|
15090
15299
|
brand: deriveBrand(prefix),
|
|
15091
15300
|
contextWindow: entry.context_window ?? resolveContextWindow(id),
|
|
15092
15301
|
maxContextWindow: entry.max_context_window,
|
|
15093
|
-
|
|
15302
|
+
// Absent means no reduction. clodex reports the window the provider actually
|
|
15303
|
+
// gives; deciding how much of it to leave free is the client's job, and Claude
|
|
15304
|
+
// Code already reserves a flat 33,000 tokens below whatever it is told.
|
|
15305
|
+
effectiveContextPercent: entry.effective_context_window_percent,
|
|
15094
15306
|
maxOutputTokens: entry.max_output_tokens,
|
|
15095
15307
|
...openAiPricingMetadata(id),
|
|
15096
15308
|
modelFormat: "openai",
|
|
@@ -15113,9 +15325,9 @@ function buildDynamicOAuthModel(entry, seedById, codexCatalog) {
|
|
|
15113
15325
|
};
|
|
15114
15326
|
}
|
|
15115
15327
|
async function fetchJsonWithAuth(url, accessToken, timeoutMs) {
|
|
15328
|
+
const controller = new AbortController();
|
|
15329
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
15116
15330
|
try {
|
|
15117
|
-
const controller = new AbortController();
|
|
15118
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
15119
15331
|
const response = await fetch(url, {
|
|
15120
15332
|
headers: {
|
|
15121
15333
|
Accept: "application/json",
|
|
@@ -15123,7 +15335,7 @@ async function fetchJsonWithAuth(url, accessToken, timeoutMs) {
|
|
|
15123
15335
|
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
|
15124
15336
|
},
|
|
15125
15337
|
signal: controller.signal
|
|
15126
|
-
})
|
|
15338
|
+
});
|
|
15127
15339
|
if (!response.ok) {
|
|
15128
15340
|
const detail = await response.text().then((t) => t.slice(0, 200)).catch(() => "");
|
|
15129
15341
|
return { body: null, error: `HTTP ${response.status}${detail ? `: ${detail}` : ""}` };
|
|
@@ -15131,6 +15343,11 @@ async function fetchJsonWithAuth(url, accessToken, timeoutMs) {
|
|
|
15131
15343
|
return { body: await response.json() };
|
|
15132
15344
|
} catch (err) {
|
|
15133
15345
|
return { body: null, error: err instanceof Error ? err.message : String(err) };
|
|
15346
|
+
} finally {
|
|
15347
|
+
clearTimeout(timer);
|
|
15348
|
+
if (!controller.signal.aborted) {
|
|
15349
|
+
controller.abort(new Error("OpenAI catalog request completed"));
|
|
15350
|
+
}
|
|
15134
15351
|
}
|
|
15135
15352
|
}
|
|
15136
15353
|
async function refreshOpenAiOAuthModels(accessToken) {
|
|
@@ -17218,10 +17435,11 @@ async function collectOpenAiStream(stream) {
|
|
|
17218
17435
|
}
|
|
17219
17436
|
return collected;
|
|
17220
17437
|
}
|
|
17221
|
-
function startUpstreamBudget(model, streaming) {
|
|
17438
|
+
function startUpstreamBudget(model, streaming, clientSignal) {
|
|
17222
17439
|
const { idleTimeoutMs, totalTimeoutMs, maxRetries } = upstreamRequestBudget();
|
|
17223
17440
|
const attempts = trackUpstreamAttempts(model);
|
|
17224
17441
|
const abort = new AbortController();
|
|
17442
|
+
const stopForwardingAbort = forwardAbortSignal(clientSignal, abort);
|
|
17225
17443
|
const idleError = () => attempts.deadlineError(new Error(
|
|
17226
17444
|
`no data received from provider for ${Math.round(idleTimeoutMs / 1e3)}s`
|
|
17227
17445
|
));
|
|
@@ -17242,6 +17460,7 @@ function startUpstreamBudget(model, streaming) {
|
|
|
17242
17460
|
idleTimer = setTimeout(() => abort.abort(idleError()), idleTimeoutMs);
|
|
17243
17461
|
},
|
|
17244
17462
|
close: () => {
|
|
17463
|
+
stopForwardingAbort();
|
|
17245
17464
|
if (idleTimer !== void 0) clearTimeout(idleTimer);
|
|
17246
17465
|
clearTimeout(totalTimer);
|
|
17247
17466
|
if (!abort.signal.aborted) abort.abort();
|
|
@@ -17263,7 +17482,7 @@ async function* watchOpenAiStream(stream, budget) {
|
|
|
17263
17482
|
async function generateOpenAiResponse(model, params, responseModelId, options) {
|
|
17264
17483
|
let result;
|
|
17265
17484
|
const streaming = options?.forceStream === true;
|
|
17266
|
-
const budget = startUpstreamBudget(model, streaming);
|
|
17485
|
+
const budget = startUpstreamBudget(model, streaming, options?.abortSignal);
|
|
17267
17486
|
try {
|
|
17268
17487
|
if (streaming) {
|
|
17269
17488
|
const { stream } = streamText2({
|
|
@@ -17316,8 +17535,8 @@ async function generateOpenAiResponse(model, params, responseModelId, options) {
|
|
|
17316
17535
|
}
|
|
17317
17536
|
};
|
|
17318
17537
|
}
|
|
17319
|
-
async function streamOpenAiResponse(model, params, responseModelId, onChunk) {
|
|
17320
|
-
const budget = startUpstreamBudget(model, true);
|
|
17538
|
+
async function streamOpenAiResponse(model, params, responseModelId, onChunk, options) {
|
|
17539
|
+
const budget = startUpstreamBudget(model, true, options?.abortSignal);
|
|
17321
17540
|
try {
|
|
17322
17541
|
const { stream } = streamText2({
|
|
17323
17542
|
model: budget.model,
|
|
@@ -17493,6 +17712,7 @@ async function routeRequest(req, res, options, modelCache, plog) {
|
|
|
17493
17712
|
}
|
|
17494
17713
|
}
|
|
17495
17714
|
async function handleAnthropicMessages(req, res, options, modelCache, plog) {
|
|
17715
|
+
const clientAbort = watchClientDisconnect(res);
|
|
17496
17716
|
const body = await readJson(req);
|
|
17497
17717
|
if (!body) {
|
|
17498
17718
|
sendJson(res, 400, { error: { message: "Invalid JSON body" } });
|
|
@@ -17566,28 +17786,34 @@ async function handleAnthropicMessages(req, res, options, modelCache, plog) {
|
|
|
17566
17786
|
rejectedAccessToken
|
|
17567
17787
|
) : void 0;
|
|
17568
17788
|
plog(() => `anthropic-passthrough \u2192 ${messagesUrl} oauth=${isOAuth} stream=${clientWantsStream}`);
|
|
17569
|
-
|
|
17570
|
-
|
|
17571
|
-
|
|
17572
|
-
|
|
17573
|
-
|
|
17574
|
-
|
|
17575
|
-
|
|
17576
|
-
|
|
17577
|
-
|
|
17578
|
-
|
|
17579
|
-
|
|
17580
|
-
|
|
17581
|
-
|
|
17582
|
-
|
|
17583
|
-
|
|
17584
|
-
|
|
17585
|
-
|
|
17586
|
-
|
|
17587
|
-
|
|
17588
|
-
|
|
17589
|
-
|
|
17590
|
-
|
|
17789
|
+
try {
|
|
17790
|
+
await relayAnthropicMessages(res, messagesUrl, forwardBody, apiKey, clientWantsStream, {
|
|
17791
|
+
inboundBeta: effectiveBeta,
|
|
17792
|
+
authType,
|
|
17793
|
+
log: (message) => plog(message),
|
|
17794
|
+
claudeCodeSessionId,
|
|
17795
|
+
extraHeaders: model.headers,
|
|
17796
|
+
refreshToken,
|
|
17797
|
+
onTokenRefreshed: (refreshed) => {
|
|
17798
|
+
model.apiKey = refreshed;
|
|
17799
|
+
},
|
|
17800
|
+
signal: clientAbort.signal,
|
|
17801
|
+
// Echo the exact requested id when it differs from the upstream id, so
|
|
17802
|
+
// clients that key context windows on the response model still resolve.
|
|
17803
|
+
responseModelOverride: typeof body.model === "string" && body.model !== upstreamModelId(model) ? body.model : void 0,
|
|
17804
|
+
onUpstreamError: options.inferenceLogPath ? (statusCode, errorContent) => writeInferenceResponseErrorLog(options.inferenceLogPath, {
|
|
17805
|
+
requestId,
|
|
17806
|
+
modelId: body.model,
|
|
17807
|
+
provider: inferenceProvider(model),
|
|
17808
|
+
route: "passthrough",
|
|
17809
|
+
statusCode,
|
|
17810
|
+
errorContent
|
|
17811
|
+
}) : void 0
|
|
17812
|
+
});
|
|
17813
|
+
} catch (err) {
|
|
17814
|
+
if (clientDisconnected(clientAbort.signal)) return;
|
|
17815
|
+
throw err;
|
|
17816
|
+
}
|
|
17591
17817
|
return;
|
|
17592
17818
|
}
|
|
17593
17819
|
if (model.modelFormat === "openai") {
|
|
@@ -17666,6 +17892,7 @@ async function handleAnthropicMessages(req, res, options, modelCache, plog) {
|
|
|
17666
17892
|
await withResponsesWebSocketDiagnosticContext(
|
|
17667
17893
|
{ requestId, claudeSessionId },
|
|
17668
17894
|
() => streamAnthropicResponse(languageModel, params, responseModelId, writeStreamChunk, void 0, {
|
|
17895
|
+
abortSignal: clientAbort.signal,
|
|
17669
17896
|
initialInputTokens: estimateAnthropicInputTokens(body),
|
|
17670
17897
|
onPromptTokens: (total) => reportPricingBoundaryCrossing({
|
|
17671
17898
|
modelKey: model.id,
|
|
@@ -17682,6 +17909,7 @@ async function handleAnthropicMessages(req, res, options, modelCache, plog) {
|
|
|
17682
17909
|
{ requestId, claudeSessionId },
|
|
17683
17910
|
() => generateAnthropicResponse(languageModel, params, responseModelId, {
|
|
17684
17911
|
forceStream: openAiOAuth,
|
|
17912
|
+
abortSignal: clientAbort.signal,
|
|
17685
17913
|
onPromptTokens: (total) => reportPricingBoundaryCrossing({
|
|
17686
17914
|
modelKey: model.id,
|
|
17687
17915
|
modelLabel: model.name || model.id,
|
|
@@ -17694,6 +17922,7 @@ async function handleAnthropicMessages(req, res, options, modelCache, plog) {
|
|
|
17694
17922
|
}
|
|
17695
17923
|
break;
|
|
17696
17924
|
} catch (err) {
|
|
17925
|
+
if (clientDisconnected(clientAbort.signal)) break;
|
|
17697
17926
|
const message = formatUpstreamError(err);
|
|
17698
17927
|
const details = sdkUpstreamErrorDetails(err);
|
|
17699
17928
|
const candidateStatus = details?.statusCode ?? upstreamHttpStatus(err, message);
|
|
@@ -17730,7 +17959,7 @@ async function handleAnthropicMessages(req, res, options, modelCache, plog) {
|
|
|
17730
17959
|
sendJson(res, status === 500 ? 502 : status, { error: { message: clientMessage } });
|
|
17731
17960
|
}
|
|
17732
17961
|
} else {
|
|
17733
|
-
const errorType = anthropicErrorType(status);
|
|
17962
|
+
const errorType = anthropicErrorType(status, details?.transportCode);
|
|
17734
17963
|
res.write(`event: error
|
|
17735
17964
|
data: ${JSON.stringify({
|
|
17736
17965
|
type: "error",
|
|
@@ -17749,6 +17978,7 @@ data: ${JSON.stringify({
|
|
|
17749
17978
|
sendJson(res, 400, { error: { message: `Unsupported model format: ${model.modelFormat}` } });
|
|
17750
17979
|
}
|
|
17751
17980
|
async function handleAnthropicCountTokens(req, res, options, plog) {
|
|
17981
|
+
const clientAbort = watchClientDisconnect(res);
|
|
17752
17982
|
const body = await readJson(req);
|
|
17753
17983
|
if (!body) {
|
|
17754
17984
|
sendJson(res, 400, { error: { message: "Invalid JSON body" } });
|
|
@@ -17799,18 +18029,25 @@ async function handleAnthropicCountTokens(req, res, options, plog) {
|
|
|
17799
18029
|
) : void 0;
|
|
17800
18030
|
const countTokensUrl = `${model.baseUrl}/v1/messages/count_tokens`;
|
|
17801
18031
|
plog(() => `anthropic-count-tokens \u2192 ${countTokensUrl} oauth=${isOAuth}`);
|
|
17802
|
-
|
|
17803
|
-
|
|
17804
|
-
|
|
17805
|
-
|
|
17806
|
-
|
|
17807
|
-
|
|
17808
|
-
|
|
17809
|
-
|
|
17810
|
-
|
|
17811
|
-
|
|
18032
|
+
try {
|
|
18033
|
+
await relayAnthropicMessages(res, countTokensUrl, forwardBody, apiKey, false, {
|
|
18034
|
+
inboundBeta,
|
|
18035
|
+
authType,
|
|
18036
|
+
log: (message) => plog(message),
|
|
18037
|
+
extraHeaders: model.headers,
|
|
18038
|
+
refreshToken,
|
|
18039
|
+
onTokenRefreshed: (refreshed) => {
|
|
18040
|
+
model.apiKey = refreshed;
|
|
18041
|
+
},
|
|
18042
|
+
signal: clientAbort.signal
|
|
18043
|
+
});
|
|
18044
|
+
} catch (err) {
|
|
18045
|
+
if (clientDisconnected(clientAbort.signal)) return;
|
|
18046
|
+
throw err;
|
|
18047
|
+
}
|
|
17812
18048
|
}
|
|
17813
18049
|
async function handleOpenAIChatCompletions(req, res, options, modelCache, plog) {
|
|
18050
|
+
const clientAbort = watchClientDisconnect(res);
|
|
17814
18051
|
const body = await readJson(req);
|
|
17815
18052
|
if (!body) {
|
|
17816
18053
|
sendJson(res, 400, { error: { message: "Invalid JSON body" } });
|
|
@@ -17851,21 +18088,27 @@ async function handleOpenAIChatCompletions(req, res, options, modelCache, plog)
|
|
|
17851
18088
|
options.apiKey,
|
|
17852
18089
|
rejectedAccessToken
|
|
17853
18090
|
) : void 0;
|
|
17854
|
-
|
|
17855
|
-
|
|
17856
|
-
|
|
17857
|
-
|
|
17858
|
-
|
|
17859
|
-
|
|
17860
|
-
|
|
17861
|
-
|
|
17862
|
-
|
|
17863
|
-
|
|
17864
|
-
|
|
17865
|
-
|
|
17866
|
-
|
|
17867
|
-
|
|
17868
|
-
|
|
18091
|
+
try {
|
|
18092
|
+
await relayAnthropicMessages(res, completionsUrl, forwardBody, apiKey2, Boolean(body.stream), {
|
|
18093
|
+
authType: model.authType ?? "api",
|
|
18094
|
+
extraHeaders: model.headers,
|
|
18095
|
+
refreshToken,
|
|
18096
|
+
onTokenRefreshed: (refreshed) => {
|
|
18097
|
+
model.apiKey = refreshed;
|
|
18098
|
+
},
|
|
18099
|
+
signal: clientAbort.signal,
|
|
18100
|
+
onUpstreamError: options.inferenceLogPath ? (statusCode, errorContent) => writeInferenceResponseErrorLog(options.inferenceLogPath, {
|
|
18101
|
+
modelId: body.model,
|
|
18102
|
+
provider: inferenceProvider(model),
|
|
18103
|
+
route: "passthrough",
|
|
18104
|
+
statusCode,
|
|
18105
|
+
errorContent
|
|
18106
|
+
}) : void 0
|
|
18107
|
+
});
|
|
18108
|
+
} catch (err) {
|
|
18109
|
+
if (clientDisconnected(clientAbort.signal)) return;
|
|
18110
|
+
throw err;
|
|
18111
|
+
}
|
|
17869
18112
|
return;
|
|
17870
18113
|
}
|
|
17871
18114
|
const npm = model.npm || (model.modelFormat === "anthropic" ? "@ai-sdk/anthropic" : void 0);
|
|
@@ -17917,15 +18160,21 @@ async function handleOpenAIChatCompletions(req, res, options, modelCache, plog)
|
|
|
17917
18160
|
}
|
|
17918
18161
|
res.write(chunk);
|
|
17919
18162
|
};
|
|
17920
|
-
await streamOpenAiResponse(languageModel, params, responseModelId, writeStreamChunk
|
|
18163
|
+
await streamOpenAiResponse(languageModel, params, responseModelId, writeStreamChunk, {
|
|
18164
|
+
abortSignal: clientAbort.signal
|
|
18165
|
+
});
|
|
17921
18166
|
if (!res.headersSent) writeStreamChunk("");
|
|
17922
18167
|
res.end();
|
|
17923
18168
|
} else {
|
|
17924
|
-
const response = await generateOpenAiResponse(languageModel, params, responseModelId, {
|
|
18169
|
+
const response = await generateOpenAiResponse(languageModel, params, responseModelId, {
|
|
18170
|
+
forceStream: openAiOAuth,
|
|
18171
|
+
abortSignal: clientAbort.signal
|
|
18172
|
+
});
|
|
17925
18173
|
sendJson(res, 200, response);
|
|
17926
18174
|
}
|
|
17927
18175
|
break;
|
|
17928
18176
|
} catch (err) {
|
|
18177
|
+
if (clientDisconnected(clientAbort.signal)) break;
|
|
17929
18178
|
const message = formatUpstreamError(err);
|
|
17930
18179
|
const details = sdkUpstreamErrorDetails(err);
|
|
17931
18180
|
const candidateStatus = details?.statusCode ?? upstreamHttpStatus(err, message);
|
|
@@ -18450,7 +18699,7 @@ function forwardRawAnthropicRequest(req, res, rawBody, origin, rejectUnauthorize
|
|
|
18450
18699
|
let settled = false;
|
|
18451
18700
|
let responseEnded = false;
|
|
18452
18701
|
let failed = false;
|
|
18453
|
-
let
|
|
18702
|
+
let clientDisconnected2 = false;
|
|
18454
18703
|
const writeLifecycle = (event, extra = {}) => {
|
|
18455
18704
|
if (!lifecycle) return;
|
|
18456
18705
|
writeInferenceResponseLifecycleLog(lifecycle.logPath, {
|
|
@@ -18492,7 +18741,7 @@ function forwardRawAnthropicRequest(req, res, rawBody, origin, rejectUnauthorize
|
|
|
18492
18741
|
const errorType = (err) => err.code ?? err.name;
|
|
18493
18742
|
let upstream;
|
|
18494
18743
|
let attempt = 0;
|
|
18495
|
-
const isRetryableUpstreamFailure = (err, request3) => attempt <= retryBudget && !headersReceived && !failed && !
|
|
18744
|
+
const isRetryableUpstreamFailure = (err, request3) => attempt <= retryBudget && !headersReceived && !failed && !clientDisconnected2 && !isLocalShutdown() && request3.reusedSocket === true && err.code === RETRYABLE_PASSTHROUGH_CODE;
|
|
18496
18745
|
const sendAttempt = () => {
|
|
18497
18746
|
attempt += 1;
|
|
18498
18747
|
const request3 = https.request({
|
|
@@ -18531,7 +18780,7 @@ function forwardRawAnthropicRequest(req, res, rawBody, origin, rejectUnauthorize
|
|
|
18531
18780
|
done();
|
|
18532
18781
|
});
|
|
18533
18782
|
upstreamRes.once("error", (err) => {
|
|
18534
|
-
if (
|
|
18783
|
+
if (clientDisconnected2 || failed) {
|
|
18535
18784
|
done();
|
|
18536
18785
|
return;
|
|
18537
18786
|
}
|
|
@@ -18555,7 +18804,7 @@ function forwardRawAnthropicRequest(req, res, rawBody, origin, rejectUnauthorize
|
|
|
18555
18804
|
});
|
|
18556
18805
|
upstream = request3;
|
|
18557
18806
|
request3.once("error", (err) => {
|
|
18558
|
-
if (
|
|
18807
|
+
if (clientDisconnected2) {
|
|
18559
18808
|
done();
|
|
18560
18809
|
return;
|
|
18561
18810
|
}
|
|
@@ -18603,7 +18852,7 @@ function forwardRawAnthropicRequest(req, res, rawBody, origin, rejectUnauthorize
|
|
|
18603
18852
|
};
|
|
18604
18853
|
res.once("finish", () => {
|
|
18605
18854
|
stopProgress();
|
|
18606
|
-
if (failed ||
|
|
18855
|
+
if (failed || clientDisconnected2) return;
|
|
18607
18856
|
const now = Date.now();
|
|
18608
18857
|
writeLifecycle("response_completed", {
|
|
18609
18858
|
statusCode,
|
|
@@ -18617,7 +18866,7 @@ function forwardRawAnthropicRequest(req, res, rawBody, origin, rejectUnauthorize
|
|
|
18617
18866
|
res.once("close", () => {
|
|
18618
18867
|
stopProgress();
|
|
18619
18868
|
if (res.writableFinished || failed) return;
|
|
18620
|
-
|
|
18869
|
+
clientDisconnected2 = true;
|
|
18621
18870
|
const now = Date.now();
|
|
18622
18871
|
writeLifecycle("response_client_disconnected", {
|
|
18623
18872
|
statusCode,
|
|
@@ -18646,7 +18895,7 @@ function forwardToAdapter(req, res, rawBody, adapter, adapterRequest = http2.req
|
|
|
18646
18895
|
let chunks = 0;
|
|
18647
18896
|
let adapterEnded = false;
|
|
18648
18897
|
let failed = false;
|
|
18649
|
-
let
|
|
18898
|
+
let clientDisconnected2 = false;
|
|
18650
18899
|
let adapterResponse;
|
|
18651
18900
|
let upstream;
|
|
18652
18901
|
const writeLifecycle = (event, extra = {}) => {
|
|
@@ -18684,7 +18933,7 @@ function forwardToAdapter(req, res, rawBody, adapter, adapterRequest = http2.req
|
|
|
18684
18933
|
};
|
|
18685
18934
|
res.once("finish", () => {
|
|
18686
18935
|
stopProgress();
|
|
18687
|
-
if (failed ||
|
|
18936
|
+
if (failed || clientDisconnected2) return;
|
|
18688
18937
|
const now = Date.now();
|
|
18689
18938
|
writeLifecycle("response_completed", {
|
|
18690
18939
|
statusCode,
|
|
@@ -18697,7 +18946,7 @@ function forwardToAdapter(req, res, rawBody, adapter, adapterRequest = http2.req
|
|
|
18697
18946
|
res.once("close", () => {
|
|
18698
18947
|
stopProgress();
|
|
18699
18948
|
if (res.writableFinished || failed) return;
|
|
18700
|
-
|
|
18949
|
+
clientDisconnected2 = true;
|
|
18701
18950
|
const now = Date.now();
|
|
18702
18951
|
writeLifecycle("response_client_disconnected", {
|
|
18703
18952
|
statusCode,
|
|
@@ -18714,7 +18963,7 @@ function forwardToAdapter(req, res, rawBody, adapter, adapterRequest = http2.req
|
|
|
18714
18963
|
resolve3();
|
|
18715
18964
|
});
|
|
18716
18965
|
const failAdapterRequest = (err, failureSource) => {
|
|
18717
|
-
if (
|
|
18966
|
+
if (clientDisconnected2) {
|
|
18718
18967
|
resolve3();
|
|
18719
18968
|
return;
|
|
18720
18969
|
}
|
|
@@ -18772,7 +19021,7 @@ function forwardToAdapter(req, res, rawBody, adapter, adapterRequest = http2.req
|
|
|
18772
19021
|
});
|
|
18773
19022
|
copyResponse(upstreamRes, res, void 0, lifecycle ? (usage) => writeLifecycle("response_usage", usage) : void 0);
|
|
18774
19023
|
const failAdapterResponse = (err, failureSource) => {
|
|
18775
|
-
if (
|
|
19024
|
+
if (clientDisconnected2) {
|
|
18776
19025
|
resolve3();
|
|
18777
19026
|
return;
|
|
18778
19027
|
}
|