@bman654/clodex 2.11.1 → 2.11.3
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 +26 -9
- package/dist/cli.js +516 -170
- 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.3",
|
|
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
|
}
|
|
@@ -7792,6 +7839,23 @@ function clampRetryAfterSeconds(value) {
|
|
|
7792
7839
|
}
|
|
7793
7840
|
return Math.min(Math.round(value), MAX_RETRY_AFTER_SECONDS);
|
|
7794
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
|
+
}
|
|
7795
7859
|
function retryAfterFromText(message) {
|
|
7796
7860
|
if (typeof message !== "string") return void 0;
|
|
7797
7861
|
const match = /retry after (\d+)s\b/i.exec(message);
|
|
@@ -7993,7 +8057,8 @@ function upstreamHttpStatus(err, message) {
|
|
|
7993
8057
|
if (message.includes("HTTP 400")) return 400;
|
|
7994
8058
|
return 500;
|
|
7995
8059
|
}
|
|
7996
|
-
function anthropicErrorType(status) {
|
|
8060
|
+
function anthropicErrorType(status, transportCode) {
|
|
8061
|
+
if (transportCode === "websocket_transport_error") return "overloaded_error";
|
|
7997
8062
|
switch (status) {
|
|
7998
8063
|
case 400:
|
|
7999
8064
|
return "invalid_request_error";
|
|
@@ -8291,8 +8356,11 @@ var WsUpgradePacer = class {
|
|
|
8291
8356
|
}
|
|
8292
8357
|
/**
|
|
8293
8358
|
* Resolves when this request may open a new connection, or resolves to a
|
|
8294
|
-
* refusal the caller must report as a retryable rate limit.
|
|
8295
|
-
* 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.
|
|
8296
8364
|
*/
|
|
8297
8365
|
async admit(signal) {
|
|
8298
8366
|
if (!this.enabled) return { kind: "admitted", waitedMs: 0 };
|
|
@@ -8310,7 +8378,14 @@ var WsUpgradePacer = class {
|
|
|
8310
8378
|
)
|
|
8311
8379
|
};
|
|
8312
8380
|
}
|
|
8313
|
-
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
|
+
}
|
|
8314
8389
|
const startedAt = this.now();
|
|
8315
8390
|
try {
|
|
8316
8391
|
await this.sleep(reservation.waitMs, signal);
|
|
@@ -8318,7 +8393,28 @@ var WsUpgradePacer = class {
|
|
|
8318
8393
|
this.refund(reservation.consumed);
|
|
8319
8394
|
throw error;
|
|
8320
8395
|
}
|
|
8321
|
-
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
|
+
};
|
|
8322
8418
|
}
|
|
8323
8419
|
reserve(now) {
|
|
8324
8420
|
const elapsed = Math.max(0, now - this.lastRefillAt);
|
|
@@ -8691,7 +8787,11 @@ function warnToolArgumentNormalizationGap(gap, log12) {
|
|
|
8691
8787
|
emitParentNotice("clodex: warning: further tool-argument normalization warnings suppressed.");
|
|
8692
8788
|
}
|
|
8693
8789
|
}
|
|
8694
|
-
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
|
+
};
|
|
8695
8795
|
const full = inputArray(payload);
|
|
8696
8796
|
const prefix = [...entry.requestInput ?? [], ...entry.expectedAssistant ?? []];
|
|
8697
8797
|
const comparable = Math.min(full.length, prefix.length);
|
|
@@ -8705,7 +8805,7 @@ function continuationMismatchDetails(entry, payload, log12, warnOnGap = false) {
|
|
|
8705
8805
|
const expected = mismatch < prefix.length ? prefix[mismatch] : void 0;
|
|
8706
8806
|
const actual = mismatch < full.length ? full[mismatch] : void 0;
|
|
8707
8807
|
const reasoningGap = reasoningNormalizationGap(expected, actual);
|
|
8708
|
-
if (reasoningGap && warnOnGap) warnReasoningNormalizationGap(reasoningGap, log12);
|
|
8808
|
+
if (reasoningGap && warnOnGap) raise(() => warnReasoningNormalizationGap(reasoningGap, log12));
|
|
8709
8809
|
let gapExpected = expected;
|
|
8710
8810
|
if (conversationItemKind(expected) === "reasoning" && conversationItemKind(actual) === "function_call") {
|
|
8711
8811
|
for (let index = mismatch; index < prefix.length; index += 1) {
|
|
@@ -8727,7 +8827,8 @@ function continuationMismatchDetails(entry, payload, log12, warnOnGap = false) {
|
|
|
8727
8827
|
} catch {
|
|
8728
8828
|
}
|
|
8729
8829
|
if (toolArgumentGap?.equalAfterStrip === true) {
|
|
8730
|
-
|
|
8830
|
+
const gap = toolArgumentGap;
|
|
8831
|
+
if (warnOnGap) raise(() => warnToolArgumentNormalizationGap(gap, log12));
|
|
8731
8832
|
} else if (toolArgumentGap && warnOnGap) {
|
|
8732
8833
|
try {
|
|
8733
8834
|
log12?.(`tool argument mismatch beyond the strip rule: ${String(toolArgumentGap.tool)}`);
|
|
@@ -9176,11 +9277,16 @@ function deleteEntry(entry, closeSocket = true) {
|
|
|
9176
9277
|
}
|
|
9177
9278
|
}
|
|
9178
9279
|
}
|
|
9179
|
-
function failContext(entry, ctx, message, diagnosticDetails, statusCode,
|
|
9280
|
+
function failContext(entry, ctx, message, diagnosticDetails, statusCode, retryAfter) {
|
|
9180
9281
|
if (ctx.closed || entry.current !== ctx) return;
|
|
9282
|
+
const retryAfterSeconds = retryAfter === void 0 ? void 0 : clampRetryAfterSeconds(retryAfter.seconds);
|
|
9181
9283
|
entry.debug(`fail: ${message}`);
|
|
9182
9284
|
emitResponseErrorDiagnostic(entry, ctx, {
|
|
9183
9285
|
...diagnosticDetails,
|
|
9286
|
+
...retryAfter !== void 0 ? {
|
|
9287
|
+
retryAfterSource: retryAfter.source,
|
|
9288
|
+
..."rawSeconds" in retryAfter ? { rawRetryAfterSeconds: retryAfter.rawSeconds } : {}
|
|
9289
|
+
} : {},
|
|
9184
9290
|
...diagnosticTextFingerprint("errorMessage", message)
|
|
9185
9291
|
});
|
|
9186
9292
|
flushPending(ctx);
|
|
@@ -9191,7 +9297,7 @@ function failContext(entry, ctx, message, diagnosticDetails, statusCode, retryAf
|
|
|
9191
9297
|
type: statusCode === void 0 ? "transport_error" : anthropicErrorType(statusCode),
|
|
9192
9298
|
code: statusCode === void 0 ? "websocket_transport_error" : String(statusCode),
|
|
9193
9299
|
message,
|
|
9194
|
-
param: null,
|
|
9300
|
+
param: retryAfter === void 0 ? null : retryAfterProvenanceParam(retryAfter),
|
|
9195
9301
|
...retryAfterSeconds !== void 0 ? { retry_after_seconds: retryAfterSeconds } : {}
|
|
9196
9302
|
}
|
|
9197
9303
|
});
|
|
@@ -9390,7 +9496,8 @@ function handleSocketMessage(entry, data) {
|
|
|
9390
9496
|
const previousMissing = errorCode === "previous_response_not_found";
|
|
9391
9497
|
const willRetry = previousMissing && ctx.continued && !ctx.retried && !ctx.emittedModelData;
|
|
9392
9498
|
if (errorCode === "websocket_connection_limit_reached" && !ctx.emittedModelData) {
|
|
9393
|
-
const
|
|
9499
|
+
const rawRetryAfterSeconds = responseRetryAfterSeconds(event);
|
|
9500
|
+
const retryAfterSeconds = clampRetryAfterSeconds(rawRetryAfterSeconds);
|
|
9394
9501
|
failContext(
|
|
9395
9502
|
entry,
|
|
9396
9503
|
ctx,
|
|
@@ -9402,7 +9509,7 @@ function handleSocketMessage(entry, data) {
|
|
|
9402
9509
|
retryAfterSeconds
|
|
9403
9510
|
},
|
|
9404
9511
|
429,
|
|
9405
|
-
retryAfterSeconds
|
|
9512
|
+
rawRetryAfterSeconds === void 0 ? { seconds: retryAfterSeconds, source: "default" } : { seconds: retryAfterSeconds, source: "upstream", rawSeconds: rawRetryAfterSeconds }
|
|
9406
9513
|
);
|
|
9407
9514
|
return;
|
|
9408
9515
|
}
|
|
@@ -9427,7 +9534,12 @@ function handleSocketMessage(entry, data) {
|
|
|
9427
9534
|
}
|
|
9428
9535
|
if (errorStatus !== void 0) {
|
|
9429
9536
|
const statedRetryAfter = errorStatus === 429 ? responseRetryAfterSeconds(event) : void 0;
|
|
9430
|
-
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;
|
|
9431
9543
|
const reason = responseErrorMessage(event) ?? `OpenAI rejected the request (HTTP ${errorStatus})`;
|
|
9432
9544
|
failContext(
|
|
9433
9545
|
entry,
|
|
@@ -9447,7 +9559,7 @@ function handleSocketMessage(entry, data) {
|
|
|
9447
9559
|
...retryAfterSeconds !== void 0 ? { retryAfterSeconds } : {}
|
|
9448
9560
|
},
|
|
9449
9561
|
errorStatus,
|
|
9450
|
-
|
|
9562
|
+
retryAfter
|
|
9451
9563
|
);
|
|
9452
9564
|
return;
|
|
9453
9565
|
}
|
|
@@ -9461,7 +9573,13 @@ function handleSocketMessage(entry, data) {
|
|
|
9461
9573
|
const classified = numericOrNamed !== void 0 || discriminator ? frameStatusCode(numericOrNamed, discriminator) : 500;
|
|
9462
9574
|
const statusCode = classified !== 500 ? classified : settledReason ? 400 : 502;
|
|
9463
9575
|
const usageLimited = statusCode === 429;
|
|
9464
|
-
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;
|
|
9465
9583
|
failContext(
|
|
9466
9584
|
entry,
|
|
9467
9585
|
ctx,
|
|
@@ -9480,7 +9598,7 @@ function handleSocketMessage(entry, data) {
|
|
|
9480
9598
|
...diagnosticTextFingerprint("upstreamMessage", responseErrorMessage(event))
|
|
9481
9599
|
},
|
|
9482
9600
|
statusCode,
|
|
9483
|
-
|
|
9601
|
+
retryAfter
|
|
9484
9602
|
);
|
|
9485
9603
|
return;
|
|
9486
9604
|
}
|
|
@@ -9586,15 +9704,14 @@ function createConnection(WebSocket, wsUrl, headers, persistent, key, options, d
|
|
|
9586
9704
|
return;
|
|
9587
9705
|
}
|
|
9588
9706
|
if (statusCode === 403) {
|
|
9589
|
-
const
|
|
9590
|
-
|
|
9591
|
-
);
|
|
9707
|
+
const rawRetryAfterSeconds = numericRetryAfterHeader(response.headers["retry-after"]);
|
|
9708
|
+
const retryAfterSeconds = clampRetryAfterSeconds(rawRetryAfterSeconds);
|
|
9592
9709
|
failContext(entry, ctx, `OpenAI edge throttled the Responses WebSocket upgrade (HTTP 403); retry after ${retryAfterSeconds}s`, {
|
|
9593
9710
|
source: "unexpected_response",
|
|
9594
9711
|
httpStatusCode: statusCode,
|
|
9595
9712
|
mappedStatusCode: 429,
|
|
9596
9713
|
retryAfterSeconds
|
|
9597
|
-
}, 429, retryAfterSeconds);
|
|
9714
|
+
}, 429, rawRetryAfterSeconds === void 0 ? { seconds: retryAfterSeconds, source: "default" } : { seconds: retryAfterSeconds, source: "upstream", rawSeconds: rawRetryAfterSeconds });
|
|
9598
9715
|
return;
|
|
9599
9716
|
}
|
|
9600
9717
|
failContext(entry, ctx, `WebSocket upgrade failed (HTTP ${statusCode})`, {
|
|
@@ -9687,18 +9804,25 @@ function createResponsesWebSocketFetch(wsUrl, log12, options = {}) {
|
|
|
9687
9804
|
const diagnosticCorrelation = diagnosticContext.getStore();
|
|
9688
9805
|
let now = resolvedOptions.now();
|
|
9689
9806
|
const evictions = cleanupExpiredConnections(now);
|
|
9690
|
-
const
|
|
9691
|
-
|
|
9692
|
-
|
|
9693
|
-
|
|
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();
|
|
9694
9818
|
let selected = matches[0]?.entry;
|
|
9695
|
-
|
|
9696
|
-
|
|
9819
|
+
let selectedMatch = matches[0]?.match;
|
|
9820
|
+
let selectedDelta = selectedMatch?.delta;
|
|
9697
9821
|
const diagnosticEntry = selected ?? [...idleCandidates].sort((left, right) => right.lastUsedAt - left.lastUsedAt)[0] ?? candidates[0];
|
|
9698
9822
|
debug(
|
|
9699
9823
|
`lookup key=${debugKey(partitionKey)} prompt=${debugKey(promptFingerprint)} hit=${candidates.length > 0} heads=${candidates.length} active_connections=${connectionCount()}`
|
|
9700
9824
|
);
|
|
9701
|
-
|
|
9825
|
+
let promptChanges = changedPromptFields(diagnosticEntry?.promptFieldHashes, promptFieldHashes);
|
|
9702
9826
|
if (promptChanges.length) debug(`prompt fields changed: ${promptChanges.join(",")}`);
|
|
9703
9827
|
if (promptChanges.includes("instructions")) {
|
|
9704
9828
|
const summary = instructionChangeSummary(diagnosticEntry?.instructionsSnapshot, instructionsSnapshot);
|
|
@@ -9710,35 +9834,49 @@ function createResponsesWebSocketFetch(wsUrl, log12, options = {}) {
|
|
|
9710
9834
|
let promotedConnectionId;
|
|
9711
9835
|
let decision;
|
|
9712
9836
|
let candidateMismatchDetails;
|
|
9713
|
-
|
|
9714
|
-
|
|
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 };
|
|
9715
9844
|
continued = true;
|
|
9716
|
-
if (
|
|
9845
|
+
if (entry.generation === "nursery") {
|
|
9717
9846
|
evictions.push(...evictOldestIdleGeneration(
|
|
9718
9847
|
"established",
|
|
9719
9848
|
resolvedOptions.maxConnections,
|
|
9720
9849
|
"established_lru_cap"
|
|
9721
9850
|
));
|
|
9722
|
-
|
|
9723
|
-
promotedConnectionId =
|
|
9851
|
+
entry.generation = "established";
|
|
9852
|
+
promotedConnectionId = entry.debugId;
|
|
9724
9853
|
}
|
|
9725
|
-
decision = "continuation";
|
|
9726
9854
|
debug(
|
|
9727
|
-
`continuing chain with ${
|
|
9855
|
+
`continuing chain with ${match.delta.length} incremental input item(s)` + (match.mode === "omitted_reasoning" ? " after accepting omitted reasoning" : "")
|
|
9728
9856
|
);
|
|
9857
|
+
return "continuation";
|
|
9858
|
+
};
|
|
9859
|
+
if (selected && selectedDelta) {
|
|
9860
|
+
decision = continueOnHead(selected, selectedMatch);
|
|
9729
9861
|
} else if (candidates.some((entry) => entry.inFlight)) {
|
|
9730
9862
|
selected = void 0;
|
|
9731
9863
|
persistent = false;
|
|
9732
9864
|
decision = "parallel_isolated";
|
|
9733
9865
|
debug("parallel request using an isolated socket");
|
|
9734
9866
|
} else if (diagnosticEntry) {
|
|
9735
|
-
const diagnosticMismatch = continuationMismatchDetails(
|
|
9867
|
+
const diagnosticMismatch = continuationMismatchDetails(
|
|
9868
|
+
diagnosticEntry,
|
|
9869
|
+
payload,
|
|
9870
|
+
debug,
|
|
9871
|
+
true,
|
|
9872
|
+
deferredMismatchWarnings
|
|
9873
|
+
);
|
|
9736
9874
|
candidateMismatchDetails = /* @__PURE__ */ new Map([[diagnosticEntry, diagnosticMismatch]]);
|
|
9737
9875
|
for (const candidate of candidates) {
|
|
9738
9876
|
if (candidate === diagnosticEntry) continue;
|
|
9739
9877
|
candidateMismatchDetails.set(
|
|
9740
9878
|
candidate,
|
|
9741
|
-
continuationMismatchDetails(candidate, payload, debug, true)
|
|
9879
|
+
continuationMismatchDetails(candidate, payload, debug, true, deferredMismatchWarnings)
|
|
9742
9880
|
);
|
|
9743
9881
|
}
|
|
9744
9882
|
debug(
|
|
@@ -9757,6 +9895,7 @@ function createResponsesWebSocketFetch(wsUrl, log12, options = {}) {
|
|
|
9757
9895
|
decision = "unpartitioned_socket";
|
|
9758
9896
|
}
|
|
9759
9897
|
let pacingWaitedMs;
|
|
9898
|
+
let pacingRescanOutcome;
|
|
9760
9899
|
if (!selected) {
|
|
9761
9900
|
const pacer = options.pacer ?? sharedWsUpgradePacer();
|
|
9762
9901
|
const pacingStartedAt = resolvedOptions.now();
|
|
@@ -9770,6 +9909,7 @@ function createResponsesWebSocketFetch(wsUrl, log12, options = {}) {
|
|
|
9770
9909
|
decision,
|
|
9771
9910
|
waitedMs: Math.max(0, resolvedOptions.now() - pacingStartedAt)
|
|
9772
9911
|
}, diagnosticCorrelation);
|
|
9912
|
+
flushMismatchWarnings();
|
|
9773
9913
|
throw error;
|
|
9774
9914
|
}
|
|
9775
9915
|
if (admission.kind === "refused") {
|
|
@@ -9783,6 +9923,7 @@ function createResponsesWebSocketFetch(wsUrl, log12, options = {}) {
|
|
|
9783
9923
|
requiredWaitMs: admission.requiredWaitMs,
|
|
9784
9924
|
retryAfterSeconds: admission.retryAfterSeconds
|
|
9785
9925
|
}, diagnosticCorrelation);
|
|
9926
|
+
flushMismatchWarnings();
|
|
9786
9927
|
return pacedRefusalResponse(admission.retryAfterSeconds);
|
|
9787
9928
|
}
|
|
9788
9929
|
if (admission.waitedMs > 0) {
|
|
@@ -9797,12 +9938,42 @@ function createResponsesWebSocketFetch(wsUrl, log12, options = {}) {
|
|
|
9797
9938
|
}
|
|
9798
9939
|
now = resolvedOptions.now();
|
|
9799
9940
|
evictions.push(...cleanupExpiredConnections(now));
|
|
9800
|
-
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)) {
|
|
9801
9961
|
persistent = false;
|
|
9802
9962
|
decision = "parallel_isolated";
|
|
9803
9963
|
debug("parallel request using an isolated socket after pacing");
|
|
9964
|
+
if (pacingRescanOutcome === "no_change") pacingRescanOutcome = "parallel_isolated";
|
|
9804
9965
|
}
|
|
9805
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
|
+
}
|
|
9806
9977
|
if (!selected && persistent) {
|
|
9807
9978
|
evictions.push(...evictOldestIdleGeneration(
|
|
9808
9979
|
"nursery",
|
|
@@ -9845,6 +10016,8 @@ function createResponsesWebSocketFetch(wsUrl, log12, options = {}) {
|
|
|
9845
10016
|
promotedConnectionId,
|
|
9846
10017
|
createdConnectionId: selected ? void 0 : nextConnectionDebugId,
|
|
9847
10018
|
...pacingWaitedMs !== void 0 ? { pacingWaitedMs } : {},
|
|
10019
|
+
...pacingRescanOutcome !== void 0 ? { pacingRescanOutcome } : {},
|
|
10020
|
+
...suppressedMismatchWarnings !== void 0 ? { suppressedMismatchWarnings } : {},
|
|
9848
10021
|
createdGeneration: selected ? void 0 : persistent ? "nursery" : "isolated",
|
|
9849
10022
|
incrementalInputItems: selectedDelta?.length,
|
|
9850
10023
|
heads: candidates.map((entry) => ({
|
|
@@ -10928,6 +11101,28 @@ function sendJson(res, status, body) {
|
|
|
10928
11101
|
res.writeHead(status, { "Content-Type": "application/json" });
|
|
10929
11102
|
res.end(json);
|
|
10930
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
|
+
}
|
|
10931
11126
|
|
|
10932
11127
|
// src/server/vendor-mask.ts
|
|
10933
11128
|
function reverseSegment(value) {
|
|
@@ -11412,7 +11607,23 @@ function resolveUpstreamTools(tools, messages) {
|
|
|
11412
11607
|
}
|
|
11413
11608
|
|
|
11414
11609
|
// src/upstream-attempts.ts
|
|
11415
|
-
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
|
+
}
|
|
11416
11627
|
function trackUpstreamAttempts(model) {
|
|
11417
11628
|
if (typeof model === "string") {
|
|
11418
11629
|
return { model, deadlineError: (timeoutError) => timeoutError };
|
|
@@ -11426,6 +11637,7 @@ function trackUpstreamAttempts(model) {
|
|
|
11426
11637
|
failedAttempts.length = 0;
|
|
11427
11638
|
return result;
|
|
11428
11639
|
} catch (error) {
|
|
11640
|
+
restoreSyntheticRetryAfterHeader(error);
|
|
11429
11641
|
failedAttempts.push(error);
|
|
11430
11642
|
waitingToRetry = true;
|
|
11431
11643
|
throw error;
|
|
@@ -12082,9 +12294,10 @@ async function writeAnthropicStream(stream, modelId, write, log12, observer, too
|
|
|
12082
12294
|
case "error": {
|
|
12083
12295
|
const e = part.error;
|
|
12084
12296
|
const errMsg = e?.message || (typeof part.error === "string" ? part.error : JSON.stringify(e?.data ?? part.error));
|
|
12085
|
-
const
|
|
12297
|
+
const transportCode = sdkUpstreamErrorDetails(part.error)?.transportCode;
|
|
12298
|
+
const errorType = anthropicErrorType(upstreamHttpStatus(part.error, errMsg), transportCode);
|
|
12086
12299
|
log12?.(() => `sdk stream error (${errorType}): ${errMsg}`);
|
|
12087
|
-
closeOpen();
|
|
12300
|
+
if (!(transportCode === "websocket_transport_error" && openType === "thinking")) closeOpen();
|
|
12088
12301
|
throw part.error instanceof Error || part.error && typeof part.error === "object" ? part.error : new Error(errMsg);
|
|
12089
12302
|
}
|
|
12090
12303
|
default:
|
|
@@ -12526,14 +12739,7 @@ async function startProxyCatalog(routes, defaultAliasId, debug = false, inferenc
|
|
|
12526
12739
|
anthropicError(res, 401, "Invalid proxy token");
|
|
12527
12740
|
return;
|
|
12528
12741
|
}
|
|
12529
|
-
const clientAbort =
|
|
12530
|
-
const abortForClientDisconnect = () => {
|
|
12531
|
-
if (!clientAbort.signal.aborted) clientAbort.abort(new Error("Client disconnected"));
|
|
12532
|
-
};
|
|
12533
|
-
req.once("aborted", abortForClientDisconnect);
|
|
12534
|
-
res.once("close", () => {
|
|
12535
|
-
if (!res.writableFinished) abortForClientDisconnect();
|
|
12536
|
-
});
|
|
12742
|
+
const clientAbort = watchClientDisconnect(res);
|
|
12537
12743
|
let anthropicBody;
|
|
12538
12744
|
try {
|
|
12539
12745
|
const raw = await readBody(req);
|
|
@@ -12613,7 +12819,7 @@ async function startProxyCatalog(routes, defaultAliasId, debug = false, inferenc
|
|
|
12613
12819
|
signal: clientAbort.signal
|
|
12614
12820
|
});
|
|
12615
12821
|
} catch (err) {
|
|
12616
|
-
if (clientAbort.signal
|
|
12822
|
+
if (clientDisconnected(clientAbort.signal)) return;
|
|
12617
12823
|
const message = err instanceof UpstreamUnreachableError ? err.message : String(err);
|
|
12618
12824
|
plog(() => `anthropic token-count error: ${message}`);
|
|
12619
12825
|
anthropicError(res, 502, message);
|
|
@@ -12674,7 +12880,7 @@ async function startProxyCatalog(routes, defaultAliasId, debug = false, inferenc
|
|
|
12674
12880
|
}) : void 0
|
|
12675
12881
|
});
|
|
12676
12882
|
} catch (err) {
|
|
12677
|
-
if (clientAbort.signal
|
|
12883
|
+
if (clientDisconnected(clientAbort.signal)) return;
|
|
12678
12884
|
const message = err instanceof UpstreamUnreachableError ? err.message : String(err);
|
|
12679
12885
|
plog(() => `anthropic-passthrough error: ${message}`);
|
|
12680
12886
|
anthropicError(res, 502, message);
|
|
@@ -12814,7 +13020,7 @@ async function startProxyCatalog(routes, defaultAliasId, debug = false, inferenc
|
|
|
12814
13020
|
};
|
|
12815
13021
|
let sdkAttempt = 0;
|
|
12816
13022
|
const handleSdkError = async (err) => {
|
|
12817
|
-
if (clientAbort.signal
|
|
13023
|
+
if (clientDisconnected(clientAbort.signal)) {
|
|
12818
13024
|
translationLifecycle?.cancel();
|
|
12819
13025
|
return "cancelled";
|
|
12820
13026
|
}
|
|
@@ -12870,7 +13076,7 @@ async function startProxyCatalog(routes, defaultAliasId, debug = false, inferenc
|
|
|
12870
13076
|
contextLengthExceeded ? relayRequestId ?? randomUUID3() : void 0
|
|
12871
13077
|
);
|
|
12872
13078
|
} else {
|
|
12873
|
-
const errorType = anthropicErrorType(upstreamStatus);
|
|
13079
|
+
const errorType = anthropicErrorType(upstreamStatus, details?.transportCode);
|
|
12874
13080
|
res.write(`event: error
|
|
12875
13081
|
data: ${JSON.stringify({
|
|
12876
13082
|
type: "error",
|
|
@@ -15119,9 +15325,9 @@ function buildDynamicOAuthModel(entry, seedById, codexCatalog) {
|
|
|
15119
15325
|
};
|
|
15120
15326
|
}
|
|
15121
15327
|
async function fetchJsonWithAuth(url, accessToken, timeoutMs) {
|
|
15328
|
+
const controller = new AbortController();
|
|
15329
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
15122
15330
|
try {
|
|
15123
|
-
const controller = new AbortController();
|
|
15124
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
15125
15331
|
const response = await fetch(url, {
|
|
15126
15332
|
headers: {
|
|
15127
15333
|
Accept: "application/json",
|
|
@@ -15129,7 +15335,7 @@ async function fetchJsonWithAuth(url, accessToken, timeoutMs) {
|
|
|
15129
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"
|
|
15130
15336
|
},
|
|
15131
15337
|
signal: controller.signal
|
|
15132
|
-
})
|
|
15338
|
+
});
|
|
15133
15339
|
if (!response.ok) {
|
|
15134
15340
|
const detail = await response.text().then((t) => t.slice(0, 200)).catch(() => "");
|
|
15135
15341
|
return { body: null, error: `HTTP ${response.status}${detail ? `: ${detail}` : ""}` };
|
|
@@ -15137,6 +15343,11 @@ async function fetchJsonWithAuth(url, accessToken, timeoutMs) {
|
|
|
15137
15343
|
return { body: await response.json() };
|
|
15138
15344
|
} catch (err) {
|
|
15139
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
|
+
}
|
|
15140
15351
|
}
|
|
15141
15352
|
}
|
|
15142
15353
|
async function refreshOpenAiOAuthModels(accessToken) {
|
|
@@ -17224,10 +17435,11 @@ async function collectOpenAiStream(stream) {
|
|
|
17224
17435
|
}
|
|
17225
17436
|
return collected;
|
|
17226
17437
|
}
|
|
17227
|
-
function startUpstreamBudget(model, streaming) {
|
|
17438
|
+
function startUpstreamBudget(model, streaming, clientSignal) {
|
|
17228
17439
|
const { idleTimeoutMs, totalTimeoutMs, maxRetries } = upstreamRequestBudget();
|
|
17229
17440
|
const attempts = trackUpstreamAttempts(model);
|
|
17230
17441
|
const abort = new AbortController();
|
|
17442
|
+
const stopForwardingAbort = forwardAbortSignal(clientSignal, abort);
|
|
17231
17443
|
const idleError = () => attempts.deadlineError(new Error(
|
|
17232
17444
|
`no data received from provider for ${Math.round(idleTimeoutMs / 1e3)}s`
|
|
17233
17445
|
));
|
|
@@ -17248,6 +17460,7 @@ function startUpstreamBudget(model, streaming) {
|
|
|
17248
17460
|
idleTimer = setTimeout(() => abort.abort(idleError()), idleTimeoutMs);
|
|
17249
17461
|
},
|
|
17250
17462
|
close: () => {
|
|
17463
|
+
stopForwardingAbort();
|
|
17251
17464
|
if (idleTimer !== void 0) clearTimeout(idleTimer);
|
|
17252
17465
|
clearTimeout(totalTimer);
|
|
17253
17466
|
if (!abort.signal.aborted) abort.abort();
|
|
@@ -17269,7 +17482,7 @@ async function* watchOpenAiStream(stream, budget) {
|
|
|
17269
17482
|
async function generateOpenAiResponse(model, params, responseModelId, options) {
|
|
17270
17483
|
let result;
|
|
17271
17484
|
const streaming = options?.forceStream === true;
|
|
17272
|
-
const budget = startUpstreamBudget(model, streaming);
|
|
17485
|
+
const budget = startUpstreamBudget(model, streaming, options?.abortSignal);
|
|
17273
17486
|
try {
|
|
17274
17487
|
if (streaming) {
|
|
17275
17488
|
const { stream } = streamText2({
|
|
@@ -17322,8 +17535,8 @@ async function generateOpenAiResponse(model, params, responseModelId, options) {
|
|
|
17322
17535
|
}
|
|
17323
17536
|
};
|
|
17324
17537
|
}
|
|
17325
|
-
async function streamOpenAiResponse(model, params, responseModelId, onChunk) {
|
|
17326
|
-
const budget = startUpstreamBudget(model, true);
|
|
17538
|
+
async function streamOpenAiResponse(model, params, responseModelId, onChunk, options) {
|
|
17539
|
+
const budget = startUpstreamBudget(model, true, options?.abortSignal);
|
|
17327
17540
|
try {
|
|
17328
17541
|
const { stream } = streamText2({
|
|
17329
17542
|
model: budget.model,
|
|
@@ -17499,6 +17712,7 @@ async function routeRequest(req, res, options, modelCache, plog) {
|
|
|
17499
17712
|
}
|
|
17500
17713
|
}
|
|
17501
17714
|
async function handleAnthropicMessages(req, res, options, modelCache, plog) {
|
|
17715
|
+
const clientAbort = watchClientDisconnect(res);
|
|
17502
17716
|
const body = await readJson(req);
|
|
17503
17717
|
if (!body) {
|
|
17504
17718
|
sendJson(res, 400, { error: { message: "Invalid JSON body" } });
|
|
@@ -17572,28 +17786,34 @@ async function handleAnthropicMessages(req, res, options, modelCache, plog) {
|
|
|
17572
17786
|
rejectedAccessToken
|
|
17573
17787
|
) : void 0;
|
|
17574
17788
|
plog(() => `anthropic-passthrough \u2192 ${messagesUrl} oauth=${isOAuth} stream=${clientWantsStream}`);
|
|
17575
|
-
|
|
17576
|
-
|
|
17577
|
-
|
|
17578
|
-
|
|
17579
|
-
|
|
17580
|
-
|
|
17581
|
-
|
|
17582
|
-
|
|
17583
|
-
|
|
17584
|
-
|
|
17585
|
-
|
|
17586
|
-
|
|
17587
|
-
|
|
17588
|
-
|
|
17589
|
-
|
|
17590
|
-
|
|
17591
|
-
|
|
17592
|
-
|
|
17593
|
-
|
|
17594
|
-
|
|
17595
|
-
|
|
17596
|
-
|
|
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
|
+
}
|
|
17597
17817
|
return;
|
|
17598
17818
|
}
|
|
17599
17819
|
if (model.modelFormat === "openai") {
|
|
@@ -17672,6 +17892,7 @@ async function handleAnthropicMessages(req, res, options, modelCache, plog) {
|
|
|
17672
17892
|
await withResponsesWebSocketDiagnosticContext(
|
|
17673
17893
|
{ requestId, claudeSessionId },
|
|
17674
17894
|
() => streamAnthropicResponse(languageModel, params, responseModelId, writeStreamChunk, void 0, {
|
|
17895
|
+
abortSignal: clientAbort.signal,
|
|
17675
17896
|
initialInputTokens: estimateAnthropicInputTokens(body),
|
|
17676
17897
|
onPromptTokens: (total) => reportPricingBoundaryCrossing({
|
|
17677
17898
|
modelKey: model.id,
|
|
@@ -17688,6 +17909,7 @@ async function handleAnthropicMessages(req, res, options, modelCache, plog) {
|
|
|
17688
17909
|
{ requestId, claudeSessionId },
|
|
17689
17910
|
() => generateAnthropicResponse(languageModel, params, responseModelId, {
|
|
17690
17911
|
forceStream: openAiOAuth,
|
|
17912
|
+
abortSignal: clientAbort.signal,
|
|
17691
17913
|
onPromptTokens: (total) => reportPricingBoundaryCrossing({
|
|
17692
17914
|
modelKey: model.id,
|
|
17693
17915
|
modelLabel: model.name || model.id,
|
|
@@ -17700,6 +17922,7 @@ async function handleAnthropicMessages(req, res, options, modelCache, plog) {
|
|
|
17700
17922
|
}
|
|
17701
17923
|
break;
|
|
17702
17924
|
} catch (err) {
|
|
17925
|
+
if (clientDisconnected(clientAbort.signal)) break;
|
|
17703
17926
|
const message = formatUpstreamError(err);
|
|
17704
17927
|
const details = sdkUpstreamErrorDetails(err);
|
|
17705
17928
|
const candidateStatus = details?.statusCode ?? upstreamHttpStatus(err, message);
|
|
@@ -17736,7 +17959,7 @@ async function handleAnthropicMessages(req, res, options, modelCache, plog) {
|
|
|
17736
17959
|
sendJson(res, status === 500 ? 502 : status, { error: { message: clientMessage } });
|
|
17737
17960
|
}
|
|
17738
17961
|
} else {
|
|
17739
|
-
const errorType = anthropicErrorType(status);
|
|
17962
|
+
const errorType = anthropicErrorType(status, details?.transportCode);
|
|
17740
17963
|
res.write(`event: error
|
|
17741
17964
|
data: ${JSON.stringify({
|
|
17742
17965
|
type: "error",
|
|
@@ -17755,6 +17978,7 @@ data: ${JSON.stringify({
|
|
|
17755
17978
|
sendJson(res, 400, { error: { message: `Unsupported model format: ${model.modelFormat}` } });
|
|
17756
17979
|
}
|
|
17757
17980
|
async function handleAnthropicCountTokens(req, res, options, plog) {
|
|
17981
|
+
const clientAbort = watchClientDisconnect(res);
|
|
17758
17982
|
const body = await readJson(req);
|
|
17759
17983
|
if (!body) {
|
|
17760
17984
|
sendJson(res, 400, { error: { message: "Invalid JSON body" } });
|
|
@@ -17805,18 +18029,25 @@ async function handleAnthropicCountTokens(req, res, options, plog) {
|
|
|
17805
18029
|
) : void 0;
|
|
17806
18030
|
const countTokensUrl = `${model.baseUrl}/v1/messages/count_tokens`;
|
|
17807
18031
|
plog(() => `anthropic-count-tokens \u2192 ${countTokensUrl} oauth=${isOAuth}`);
|
|
17808
|
-
|
|
17809
|
-
|
|
17810
|
-
|
|
17811
|
-
|
|
17812
|
-
|
|
17813
|
-
|
|
17814
|
-
|
|
17815
|
-
|
|
17816
|
-
|
|
17817
|
-
|
|
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
|
+
}
|
|
17818
18048
|
}
|
|
17819
18049
|
async function handleOpenAIChatCompletions(req, res, options, modelCache, plog) {
|
|
18050
|
+
const clientAbort = watchClientDisconnect(res);
|
|
17820
18051
|
const body = await readJson(req);
|
|
17821
18052
|
if (!body) {
|
|
17822
18053
|
sendJson(res, 400, { error: { message: "Invalid JSON body" } });
|
|
@@ -17857,21 +18088,27 @@ async function handleOpenAIChatCompletions(req, res, options, modelCache, plog)
|
|
|
17857
18088
|
options.apiKey,
|
|
17858
18089
|
rejectedAccessToken
|
|
17859
18090
|
) : void 0;
|
|
17860
|
-
|
|
17861
|
-
|
|
17862
|
-
|
|
17863
|
-
|
|
17864
|
-
|
|
17865
|
-
|
|
17866
|
-
|
|
17867
|
-
|
|
17868
|
-
|
|
17869
|
-
|
|
17870
|
-
|
|
17871
|
-
|
|
17872
|
-
|
|
17873
|
-
|
|
17874
|
-
|
|
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
|
+
}
|
|
17875
18112
|
return;
|
|
17876
18113
|
}
|
|
17877
18114
|
const npm = model.npm || (model.modelFormat === "anthropic" ? "@ai-sdk/anthropic" : void 0);
|
|
@@ -17923,15 +18160,21 @@ async function handleOpenAIChatCompletions(req, res, options, modelCache, plog)
|
|
|
17923
18160
|
}
|
|
17924
18161
|
res.write(chunk);
|
|
17925
18162
|
};
|
|
17926
|
-
await streamOpenAiResponse(languageModel, params, responseModelId, writeStreamChunk
|
|
18163
|
+
await streamOpenAiResponse(languageModel, params, responseModelId, writeStreamChunk, {
|
|
18164
|
+
abortSignal: clientAbort.signal
|
|
18165
|
+
});
|
|
17927
18166
|
if (!res.headersSent) writeStreamChunk("");
|
|
17928
18167
|
res.end();
|
|
17929
18168
|
} else {
|
|
17930
|
-
const response = await generateOpenAiResponse(languageModel, params, responseModelId, {
|
|
18169
|
+
const response = await generateOpenAiResponse(languageModel, params, responseModelId, {
|
|
18170
|
+
forceStream: openAiOAuth,
|
|
18171
|
+
abortSignal: clientAbort.signal
|
|
18172
|
+
});
|
|
17931
18173
|
sendJson(res, 200, response);
|
|
17932
18174
|
}
|
|
17933
18175
|
break;
|
|
17934
18176
|
} catch (err) {
|
|
18177
|
+
if (clientDisconnected(clientAbort.signal)) break;
|
|
17935
18178
|
const message = formatUpstreamError(err);
|
|
17936
18179
|
const details = sdkUpstreamErrorDetails(err);
|
|
17937
18180
|
const candidateStatus = details?.statusCode ?? upstreamHttpStatus(err, message);
|
|
@@ -18146,6 +18389,7 @@ import * as p9 from "@clack/prompts";
|
|
|
18146
18389
|
import * as http2 from "http";
|
|
18147
18390
|
import * as https from "https";
|
|
18148
18391
|
import * as net from "net";
|
|
18392
|
+
import { PassThrough } from "stream";
|
|
18149
18393
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
18150
18394
|
import { URL as URL2 } from "url";
|
|
18151
18395
|
import { createBrotliDecompress, createGunzip, createInflate } from "zlib";
|
|
@@ -18456,7 +18700,7 @@ function forwardRawAnthropicRequest(req, res, rawBody, origin, rejectUnauthorize
|
|
|
18456
18700
|
let settled = false;
|
|
18457
18701
|
let responseEnded = false;
|
|
18458
18702
|
let failed = false;
|
|
18459
|
-
let
|
|
18703
|
+
let clientDisconnected2 = false;
|
|
18460
18704
|
const writeLifecycle = (event, extra = {}) => {
|
|
18461
18705
|
if (!lifecycle) return;
|
|
18462
18706
|
writeInferenceResponseLifecycleLog(lifecycle.logPath, {
|
|
@@ -18498,7 +18742,7 @@ function forwardRawAnthropicRequest(req, res, rawBody, origin, rejectUnauthorize
|
|
|
18498
18742
|
const errorType = (err) => err.code ?? err.name;
|
|
18499
18743
|
let upstream;
|
|
18500
18744
|
let attempt = 0;
|
|
18501
|
-
const isRetryableUpstreamFailure = (err, request3) => attempt <= retryBudget && !headersReceived && !failed && !
|
|
18745
|
+
const isRetryableUpstreamFailure = (err, request3) => attempt <= retryBudget && !headersReceived && !failed && !clientDisconnected2 && !isLocalShutdown() && request3.reusedSocket === true && err.code === RETRYABLE_PASSTHROUGH_CODE;
|
|
18502
18746
|
const sendAttempt = () => {
|
|
18503
18747
|
attempt += 1;
|
|
18504
18748
|
const request3 = https.request({
|
|
@@ -18537,7 +18781,7 @@ function forwardRawAnthropicRequest(req, res, rawBody, origin, rejectUnauthorize
|
|
|
18537
18781
|
done();
|
|
18538
18782
|
});
|
|
18539
18783
|
upstreamRes.once("error", (err) => {
|
|
18540
|
-
if (
|
|
18784
|
+
if (clientDisconnected2 || failed) {
|
|
18541
18785
|
done();
|
|
18542
18786
|
return;
|
|
18543
18787
|
}
|
|
@@ -18561,7 +18805,7 @@ function forwardRawAnthropicRequest(req, res, rawBody, origin, rejectUnauthorize
|
|
|
18561
18805
|
});
|
|
18562
18806
|
upstream = request3;
|
|
18563
18807
|
request3.once("error", (err) => {
|
|
18564
|
-
if (
|
|
18808
|
+
if (clientDisconnected2) {
|
|
18565
18809
|
done();
|
|
18566
18810
|
return;
|
|
18567
18811
|
}
|
|
@@ -18609,7 +18853,7 @@ function forwardRawAnthropicRequest(req, res, rawBody, origin, rejectUnauthorize
|
|
|
18609
18853
|
};
|
|
18610
18854
|
res.once("finish", () => {
|
|
18611
18855
|
stopProgress();
|
|
18612
|
-
if (failed ||
|
|
18856
|
+
if (failed || clientDisconnected2) return;
|
|
18613
18857
|
const now = Date.now();
|
|
18614
18858
|
writeLifecycle("response_completed", {
|
|
18615
18859
|
statusCode,
|
|
@@ -18623,7 +18867,7 @@ function forwardRawAnthropicRequest(req, res, rawBody, origin, rejectUnauthorize
|
|
|
18623
18867
|
res.once("close", () => {
|
|
18624
18868
|
stopProgress();
|
|
18625
18869
|
if (res.writableFinished || failed) return;
|
|
18626
|
-
|
|
18870
|
+
clientDisconnected2 = true;
|
|
18627
18871
|
const now = Date.now();
|
|
18628
18872
|
writeLifecycle("response_client_disconnected", {
|
|
18629
18873
|
statusCode,
|
|
@@ -18652,7 +18896,7 @@ function forwardToAdapter(req, res, rawBody, adapter, adapterRequest = http2.req
|
|
|
18652
18896
|
let chunks = 0;
|
|
18653
18897
|
let adapterEnded = false;
|
|
18654
18898
|
let failed = false;
|
|
18655
|
-
let
|
|
18899
|
+
let clientDisconnected2 = false;
|
|
18656
18900
|
let adapterResponse;
|
|
18657
18901
|
let upstream;
|
|
18658
18902
|
const writeLifecycle = (event, extra = {}) => {
|
|
@@ -18690,7 +18934,7 @@ function forwardToAdapter(req, res, rawBody, adapter, adapterRequest = http2.req
|
|
|
18690
18934
|
};
|
|
18691
18935
|
res.once("finish", () => {
|
|
18692
18936
|
stopProgress();
|
|
18693
|
-
if (failed ||
|
|
18937
|
+
if (failed || clientDisconnected2) return;
|
|
18694
18938
|
const now = Date.now();
|
|
18695
18939
|
writeLifecycle("response_completed", {
|
|
18696
18940
|
statusCode,
|
|
@@ -18703,7 +18947,7 @@ function forwardToAdapter(req, res, rawBody, adapter, adapterRequest = http2.req
|
|
|
18703
18947
|
res.once("close", () => {
|
|
18704
18948
|
stopProgress();
|
|
18705
18949
|
if (res.writableFinished || failed) return;
|
|
18706
|
-
|
|
18950
|
+
clientDisconnected2 = true;
|
|
18707
18951
|
const now = Date.now();
|
|
18708
18952
|
writeLifecycle("response_client_disconnected", {
|
|
18709
18953
|
statusCode,
|
|
@@ -18720,7 +18964,7 @@ function forwardToAdapter(req, res, rawBody, adapter, adapterRequest = http2.req
|
|
|
18720
18964
|
resolve3();
|
|
18721
18965
|
});
|
|
18722
18966
|
const failAdapterRequest = (err, failureSource) => {
|
|
18723
|
-
if (
|
|
18967
|
+
if (clientDisconnected2) {
|
|
18724
18968
|
resolve3();
|
|
18725
18969
|
return;
|
|
18726
18970
|
}
|
|
@@ -18778,7 +19022,7 @@ function forwardToAdapter(req, res, rawBody, adapter, adapterRequest = http2.req
|
|
|
18778
19022
|
});
|
|
18779
19023
|
copyResponse(upstreamRes, res, void 0, lifecycle ? (usage) => writeLifecycle("response_usage", usage) : void 0);
|
|
18780
19024
|
const failAdapterResponse = (err, failureSource) => {
|
|
18781
|
-
if (
|
|
19025
|
+
if (clientDisconnected2) {
|
|
18782
19026
|
resolve3();
|
|
18783
19027
|
return;
|
|
18784
19028
|
}
|
|
@@ -18833,6 +19077,97 @@ function forwardToAdapter(req, res, rawBody, adapter, adapterRequest = http2.req
|
|
|
18833
19077
|
upstream.end(rawBody);
|
|
18834
19078
|
});
|
|
18835
19079
|
}
|
|
19080
|
+
function forwardAnthropicUpgrade(req, clientSocket, head, origin, rejectUnauthorized, agent, sockets) {
|
|
19081
|
+
if (clientSocket._httpMessage) {
|
|
19082
|
+
clientSocket.destroy();
|
|
19083
|
+
return;
|
|
19084
|
+
}
|
|
19085
|
+
const clientData = new PassThrough();
|
|
19086
|
+
clientSocket.pipe(clientData);
|
|
19087
|
+
let responseStarted = false;
|
|
19088
|
+
let upgradedSocket;
|
|
19089
|
+
const upstream = https.request({
|
|
19090
|
+
protocol: "https:",
|
|
19091
|
+
hostname: origin.hostname,
|
|
19092
|
+
port: origin.port || 443,
|
|
19093
|
+
method: req.method,
|
|
19094
|
+
path: req.url,
|
|
19095
|
+
headers: requestHeadersWithoutProxyHeaders(req),
|
|
19096
|
+
servername: net.isIP(origin.hostname) ? void 0 : origin.hostname,
|
|
19097
|
+
rejectUnauthorized,
|
|
19098
|
+
agent
|
|
19099
|
+
});
|
|
19100
|
+
const fail = () => {
|
|
19101
|
+
if (clientSocket.destroyed) return;
|
|
19102
|
+
if (responseStarted) {
|
|
19103
|
+
clientSocket.destroy();
|
|
19104
|
+
return;
|
|
19105
|
+
}
|
|
19106
|
+
responseStarted = true;
|
|
19107
|
+
clientSocket.end(
|
|
19108
|
+
"HTTP/1.1 502 Bad Gateway\r\nConnection: close\r\nContent-Length: 0\r\n\r\n",
|
|
19109
|
+
() => clientSocket.destroy()
|
|
19110
|
+
);
|
|
19111
|
+
};
|
|
19112
|
+
clientSocket.once("error", () => clientSocket.destroy());
|
|
19113
|
+
clientSocket.once("end", () => {
|
|
19114
|
+
if (!responseStarted) clientSocket.destroy();
|
|
19115
|
+
});
|
|
19116
|
+
clientSocket.once("close", () => {
|
|
19117
|
+
clientData.destroy();
|
|
19118
|
+
upstream.destroy();
|
|
19119
|
+
upgradedSocket?.destroy();
|
|
19120
|
+
});
|
|
19121
|
+
upstream.once("socket", (socket) => {
|
|
19122
|
+
if (clientSocket.destroyed) socket.destroy();
|
|
19123
|
+
});
|
|
19124
|
+
upstream.once("error", fail);
|
|
19125
|
+
upstream.once("close", () => {
|
|
19126
|
+
if (!responseStarted) fail();
|
|
19127
|
+
});
|
|
19128
|
+
upstream.once("response", (upstreamRes) => {
|
|
19129
|
+
if (clientSocket.destroyed) {
|
|
19130
|
+
upstreamRes.destroy();
|
|
19131
|
+
return;
|
|
19132
|
+
}
|
|
19133
|
+
responseStarted = true;
|
|
19134
|
+
const response = new http2.ServerResponse(req);
|
|
19135
|
+
response.shouldKeepAlive = false;
|
|
19136
|
+
response.assignSocket(clientSocket);
|
|
19137
|
+
clientSocket.on("drain", () => response.emit("drain"));
|
|
19138
|
+
response.once("error", () => clientSocket.destroy());
|
|
19139
|
+
response.once("finish", () => clientSocket.end(() => clientSocket.destroy()));
|
|
19140
|
+
copyResponse(upstreamRes, response);
|
|
19141
|
+
});
|
|
19142
|
+
upstream.once("upgrade", (upstreamRes, socket, upstreamHead) => {
|
|
19143
|
+
if (clientSocket.destroyed) {
|
|
19144
|
+
socket.destroy();
|
|
19145
|
+
return;
|
|
19146
|
+
}
|
|
19147
|
+
responseStarted = true;
|
|
19148
|
+
upgradedSocket = socket;
|
|
19149
|
+
sockets.add(socket);
|
|
19150
|
+
socket.once("error", () => clientSocket.destroy());
|
|
19151
|
+
socket.once("close", () => {
|
|
19152
|
+
sockets.delete(socket);
|
|
19153
|
+
clientSocket.destroy();
|
|
19154
|
+
});
|
|
19155
|
+
const headers = [
|
|
19156
|
+
`HTTP/${upstreamRes.httpVersion} ${upstreamRes.statusCode} ${upstreamRes.statusMessage}`
|
|
19157
|
+
];
|
|
19158
|
+
for (let i = 0; i < upstreamRes.rawHeaders.length; i += 2) {
|
|
19159
|
+
headers.push(`${upstreamRes.rawHeaders[i]}: ${upstreamRes.rawHeaders[i + 1]}`);
|
|
19160
|
+
}
|
|
19161
|
+
clientSocket.write(Buffer.from(`${headers.join("\r\n")}\r
|
|
19162
|
+
\r
|
|
19163
|
+
`, "latin1"));
|
|
19164
|
+
if (upstreamHead.length > 0) clientSocket.write(upstreamHead);
|
|
19165
|
+
if (head.length > 0) socket.write(head);
|
|
19166
|
+
clientData.pipe(socket);
|
|
19167
|
+
socket.pipe(clientSocket);
|
|
19168
|
+
});
|
|
19169
|
+
upstream.end();
|
|
19170
|
+
}
|
|
18836
19171
|
function forwardPlainHttp(req, res) {
|
|
18837
19172
|
let target;
|
|
18838
19173
|
try {
|
|
@@ -19049,6 +19384,17 @@ async function startHttpProxy(options) {
|
|
|
19049
19384
|
);
|
|
19050
19385
|
});
|
|
19051
19386
|
const sockets = /* @__PURE__ */ new Set();
|
|
19387
|
+
mitmServer.on("upgrade", (req, socket, head) => {
|
|
19388
|
+
forwardAnthropicUpgrade(
|
|
19389
|
+
req,
|
|
19390
|
+
socket,
|
|
19391
|
+
head,
|
|
19392
|
+
anthropicOrigin,
|
|
19393
|
+
options.anthropicRejectUnauthorized ?? true,
|
|
19394
|
+
anthropicAgent,
|
|
19395
|
+
sockets
|
|
19396
|
+
);
|
|
19397
|
+
});
|
|
19052
19398
|
const proxyServer = http2.createServer(forwardPlainHttp);
|
|
19053
19399
|
proxyServer.on("connection", (socket) => {
|
|
19054
19400
|
sockets.add(socket);
|