anygate 0.6.1 → 0.6.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 +1 -1
- package/dist/{chunk-BY4AKT2X.js → chunk-NU4KMZIM.js} +914 -239
- package/dist/chunk-NU4KMZIM.js.map +1 -0
- package/dist/{chunk-UE2I2ETX.js → chunk-RNW2MGKL.js} +4 -2
- package/dist/chunk-RNW2MGKL.js.map +1 -0
- package/dist/cli.js +139 -24
- package/dist/cli.js.map +1 -1
- package/dist/{command-XIDZGWNR.js → command-JOEFUFLD.js} +291 -116
- package/dist/command-JOEFUFLD.js.map +1 -0
- package/dist/{constants-WLMOLKEO.js → constants-7DBGCJ34.js} +4 -2
- package/dist/registry/data/templates/claude-code.json +1 -3
- package/dist/registry/data/templates/openai.json +13 -0
- package/dist/ui/dist/assets/index-CsUw6OG3.js +7 -0
- package/dist/ui/dist/index.html +1 -1
- package/package.json +1 -1
- package/dist/chunk-BY4AKT2X.js.map +0 -1
- package/dist/chunk-UE2I2ETX.js.map +0 -1
- package/dist/command-XIDZGWNR.js.map +0 -1
- package/dist/ui/dist/assets/index-Bnth1xGD.js +0 -7
- /package/dist/{constants-WLMOLKEO.js.map → constants-7DBGCJ34.js.map} +0 -0
|
@@ -10,10 +10,11 @@ import {
|
|
|
10
10
|
OPENCODE_CACHE_PATH,
|
|
11
11
|
RATE_LIMIT_MAX_REQUESTS,
|
|
12
12
|
RATE_LIMIT_WINDOW_MS,
|
|
13
|
+
REQUEST_TIMEOUT_MS,
|
|
13
14
|
VERSION,
|
|
14
15
|
VERTEX_ANTHROPIC_NPM,
|
|
15
16
|
classifyModelFormat
|
|
16
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-RNW2MGKL.js";
|
|
17
18
|
import {
|
|
18
19
|
getTemplateById,
|
|
19
20
|
listAddableTemplates
|
|
@@ -164,7 +165,18 @@ async function pollOpenAiDeviceCodeToken(deviceData, opts) {
|
|
|
164
165
|
const tokens = await tokenResponse.json();
|
|
165
166
|
return { tokens, accountId: extractOpenAiAccountId(tokens) };
|
|
166
167
|
}
|
|
167
|
-
if (response.status
|
|
168
|
+
if (response.status === 403) {
|
|
169
|
+
const detail = await response.text().catch(() => "");
|
|
170
|
+
try {
|
|
171
|
+
const parsed = JSON.parse(detail);
|
|
172
|
+
if (parsed.error?.code === "deviceauth_authorization_pending") break;
|
|
173
|
+
} catch {
|
|
174
|
+
}
|
|
175
|
+
if (detail.trim()) {
|
|
176
|
+
const msg = detail.replace(/\s+/g, " ").trim().slice(0, 300);
|
|
177
|
+
throw new Error(`OpenAI device authorization failed (403): ${msg}`);
|
|
178
|
+
}
|
|
179
|
+
} else if (response.status !== 404) {
|
|
168
180
|
const detail = await response.text().catch(() => "");
|
|
169
181
|
throw new Error(
|
|
170
182
|
`OpenAI device authorization failed (${response.status})${detail.trim() ? `: ${detail.replace(/\s+/g, " ").trim().slice(0, 200)}` : ""}`
|
|
@@ -174,7 +186,9 @@ async function pollOpenAiDeviceCodeToken(deviceData, opts) {
|
|
|
174
186
|
Math.min(intervalMs + OAUTH_POLLING_SAFETY_MARGIN_MS, Math.max(0, deadline - now()))
|
|
175
187
|
);
|
|
176
188
|
}
|
|
177
|
-
throw new Error(
|
|
189
|
+
throw new Error(
|
|
190
|
+
"OpenAI device authorization timed out \u2014 your account may not have device code authorization enabled. Enable it in ChatGPT Security Settings and try again, or use the CLI: anygate providers auth openai"
|
|
191
|
+
);
|
|
178
192
|
}
|
|
179
193
|
async function refreshOpenAiAccessToken(refreshToken) {
|
|
180
194
|
return postOAuthRefresh(
|
|
@@ -196,6 +210,47 @@ async function runOpenAiDeviceCodeFlow(onDeviceCode, opts) {
|
|
|
196
210
|
onDeviceCode({ url: openAiDeviceCodeUrl(), userCode: deviceData.user_code });
|
|
197
211
|
return pollOpenAiDeviceCodeToken(deviceData, opts);
|
|
198
212
|
}
|
|
213
|
+
async function buildOpenAiBrowserAuthUrl(redirectUri) {
|
|
214
|
+
const { verifier, challenge } = await generatePkce();
|
|
215
|
+
const state = generateOAuthState();
|
|
216
|
+
const params = new URLSearchParams({
|
|
217
|
+
client_id: CLIENT_ID,
|
|
218
|
+
response_type: "code",
|
|
219
|
+
redirect_uri: redirectUri,
|
|
220
|
+
scope: "openid profile email",
|
|
221
|
+
state,
|
|
222
|
+
code_challenge: challenge,
|
|
223
|
+
code_challenge_method: "S256",
|
|
224
|
+
// Prompt to force fresh sign-in if needed
|
|
225
|
+
prompt: "login"
|
|
226
|
+
});
|
|
227
|
+
return {
|
|
228
|
+
authUrl: `${ISSUER}/v1/authorize?${params}`,
|
|
229
|
+
codeVerifier: verifier,
|
|
230
|
+
oauthState: state
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
async function exchangeOpenAiBrowserToken(code, codeVerifier) {
|
|
234
|
+
const tokenResponse = await fetch(`${ISSUER}/auth/token`, {
|
|
235
|
+
method: "POST",
|
|
236
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
237
|
+
body: new URLSearchParams({
|
|
238
|
+
grant_type: "authorization_code",
|
|
239
|
+
code,
|
|
240
|
+
redirect_uri: `${ISSUER}/deviceauth/callback`,
|
|
241
|
+
client_id: CLIENT_ID,
|
|
242
|
+
code_verifier: codeVerifier
|
|
243
|
+
}).toString()
|
|
244
|
+
});
|
|
245
|
+
if (!tokenResponse.ok) {
|
|
246
|
+
const detail = await tokenResponse.text().catch(() => "");
|
|
247
|
+
throw new Error(
|
|
248
|
+
`OpenAI token exchange failed (${tokenResponse.status})${detail.trim() ? `: ${detail.replace(/\s+/g, " ").trim().slice(0, 200)}` : ""}`
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
const tokens = await tokenResponse.json();
|
|
252
|
+
return { tokens, accountId: extractOpenAiAccountId(tokens) };
|
|
253
|
+
}
|
|
199
254
|
|
|
200
255
|
// src/auth/responses-websocket.ts
|
|
201
256
|
var RESPONSES_LITE_HEADER = "x-openai-internal-codex-responses-lite";
|
|
@@ -474,6 +529,178 @@ function injectClaudeIdentity(body, providerData, seed) {
|
|
|
474
529
|
return { sessionId, userId };
|
|
475
530
|
}
|
|
476
531
|
|
|
532
|
+
// src/gateway/providers/stream-sanitizer.ts
|
|
533
|
+
function createNullChunkStripper() {
|
|
534
|
+
const decoder = new TextDecoder();
|
|
535
|
+
const encoder = new TextEncoder();
|
|
536
|
+
let buffer = "";
|
|
537
|
+
const isNullDataLine = (line) => {
|
|
538
|
+
const trimmed = line.trimStart();
|
|
539
|
+
if (!trimmed.startsWith("data:")) return false;
|
|
540
|
+
return trimmed.slice(5).trim() === "null";
|
|
541
|
+
};
|
|
542
|
+
return new TransformStream({
|
|
543
|
+
transform(chunk, controller) {
|
|
544
|
+
buffer += decoder.decode(chunk, { stream: true });
|
|
545
|
+
const newlineIdx = buffer.lastIndexOf("\n");
|
|
546
|
+
if (newlineIdx === -1) return;
|
|
547
|
+
const complete = buffer.slice(0, newlineIdx + 1);
|
|
548
|
+
buffer = buffer.slice(newlineIdx + 1);
|
|
549
|
+
const kept = complete.split("\n").filter((line) => !isNullDataLine(line)).join("\n");
|
|
550
|
+
if (kept) controller.enqueue(encoder.encode(kept));
|
|
551
|
+
},
|
|
552
|
+
flush(controller) {
|
|
553
|
+
const tail = buffer + decoder.decode();
|
|
554
|
+
if (tail && !isNullDataLine(tail)) controller.enqueue(encoder.encode(tail));
|
|
555
|
+
}
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
function flattenContent(content) {
|
|
559
|
+
const text4 = [];
|
|
560
|
+
const reasoning = [];
|
|
561
|
+
const blocks = Array.isArray(content) ? content : [content];
|
|
562
|
+
for (const block of blocks) {
|
|
563
|
+
if (block == null) continue;
|
|
564
|
+
if (typeof block === "string") {
|
|
565
|
+
text4.push(block);
|
|
566
|
+
continue;
|
|
567
|
+
}
|
|
568
|
+
if (typeof block !== "object") {
|
|
569
|
+
text4.push(String(block));
|
|
570
|
+
continue;
|
|
571
|
+
}
|
|
572
|
+
const b = block;
|
|
573
|
+
const isReasoning = b.type === "thinking" || b.type === "reasoning" || b.thinking != null;
|
|
574
|
+
if (isReasoning) {
|
|
575
|
+
const t = b.thinking;
|
|
576
|
+
if (Array.isArray(t)) {
|
|
577
|
+
for (const item of t) {
|
|
578
|
+
if (typeof item === "string") reasoning.push(item);
|
|
579
|
+
else if (item && typeof item.text === "string") reasoning.push(item.text);
|
|
580
|
+
}
|
|
581
|
+
} else if (typeof t === "string") {
|
|
582
|
+
reasoning.push(t);
|
|
583
|
+
}
|
|
584
|
+
if (typeof b.text === "string") reasoning.push(b.text);
|
|
585
|
+
} else if (typeof b.text === "string") {
|
|
586
|
+
text4.push(b.text);
|
|
587
|
+
} else if (typeof b.content === "string") {
|
|
588
|
+
text4.push(b.content);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
return { text: text4.join(""), reasoning: reasoning.join("") };
|
|
592
|
+
}
|
|
593
|
+
function normalizePart(part, idMap, preserveThinking = false) {
|
|
594
|
+
if (!part || typeof part !== "object") return;
|
|
595
|
+
if (Array.isArray(part.content) && !preserveThinking) {
|
|
596
|
+
const { text: text4, reasoning } = flattenContent(part.content);
|
|
597
|
+
part.content = text4;
|
|
598
|
+
if (reasoning) {
|
|
599
|
+
part.reasoning_content = (typeof part.reasoning_content === "string" ? part.reasoning_content : "") + reasoning;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
if (Array.isArray(part.tool_calls)) {
|
|
603
|
+
part.tool_calls.forEach((tc, i) => {
|
|
604
|
+
if (!tc || typeof tc !== "object") return;
|
|
605
|
+
const tcIdx = typeof tc.index === "number" ? tc.index : i;
|
|
606
|
+
if (typeof tc.id !== "string" || tc.id === "") {
|
|
607
|
+
let existing = idMap.get(tcIdx);
|
|
608
|
+
if (!existing) {
|
|
609
|
+
existing = `call_${tcIdx}_${Math.random().toString(36).slice(2, 10)}`;
|
|
610
|
+
idMap.set(tcIdx, existing);
|
|
611
|
+
}
|
|
612
|
+
tc.id = existing;
|
|
613
|
+
}
|
|
614
|
+
if (!tc.function || typeof tc.function !== "object") tc.function = {};
|
|
615
|
+
if (typeof tc.function.name !== "string") tc.function.name = "";
|
|
616
|
+
if (typeof tc.function.arguments !== "string") tc.function.arguments = "";
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
function normalizeChunk(raw, ids, preserveThinking = false) {
|
|
621
|
+
if (!raw || typeof raw !== "object" || !Array.isArray(raw.choices)) return raw;
|
|
622
|
+
for (const choice of raw.choices) {
|
|
623
|
+
if (!choice || typeof choice !== "object") continue;
|
|
624
|
+
const choiceIdx = typeof choice.index === "number" ? choice.index : 0;
|
|
625
|
+
let idMap = ids.get(choiceIdx);
|
|
626
|
+
if (!idMap) {
|
|
627
|
+
idMap = /* @__PURE__ */ new Map();
|
|
628
|
+
ids.set(choiceIdx, idMap);
|
|
629
|
+
}
|
|
630
|
+
normalizePart(choice.delta, idMap, preserveThinking);
|
|
631
|
+
normalizePart(choice.message, idMap, preserveThinking);
|
|
632
|
+
}
|
|
633
|
+
return raw;
|
|
634
|
+
}
|
|
635
|
+
function createGlmChunkSanitizer(preserveThinking = false) {
|
|
636
|
+
const dec = new TextDecoder();
|
|
637
|
+
const enc = new TextEncoder();
|
|
638
|
+
let buffer = "";
|
|
639
|
+
const ids = /* @__PURE__ */ new Map();
|
|
640
|
+
const normalizeLine = (line) => {
|
|
641
|
+
if (!line.startsWith("data:")) return line;
|
|
642
|
+
const payload = line.slice(5).replace(/^\s+/, "");
|
|
643
|
+
if (payload === "" || payload === "[DONE]" || payload === "null") return line;
|
|
644
|
+
try {
|
|
645
|
+
const parsed = JSON.parse(payload);
|
|
646
|
+
if (parsed && typeof parsed === "object") {
|
|
647
|
+
return `data: ${JSON.stringify(normalizeChunk(parsed, ids, preserveThinking))}`;
|
|
648
|
+
}
|
|
649
|
+
} catch {
|
|
650
|
+
}
|
|
651
|
+
return line;
|
|
652
|
+
};
|
|
653
|
+
return new TransformStream({
|
|
654
|
+
transform(chunk, controller) {
|
|
655
|
+
buffer += dec.decode(chunk, { stream: true });
|
|
656
|
+
let nl;
|
|
657
|
+
while ((nl = buffer.indexOf("\n")) !== -1) {
|
|
658
|
+
const line = buffer.slice(0, nl);
|
|
659
|
+
buffer = buffer.slice(nl + 1);
|
|
660
|
+
controller.enqueue(enc.encode(normalizeLine(line) + "\n"));
|
|
661
|
+
}
|
|
662
|
+
},
|
|
663
|
+
flush(controller) {
|
|
664
|
+
const tail = buffer + dec.decode();
|
|
665
|
+
if (tail.length > 0) controller.enqueue(enc.encode(normalizeLine(tail)));
|
|
666
|
+
}
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
async function sanitizeJsonResponse(resp, preserveThinking = false) {
|
|
670
|
+
const text4 = await resp.clone().text();
|
|
671
|
+
try {
|
|
672
|
+
const parsed = JSON.parse(text4);
|
|
673
|
+
if (parsed && typeof parsed === "object") {
|
|
674
|
+
const fixed = normalizeChunk(parsed, /* @__PURE__ */ new Map(), preserveThinking);
|
|
675
|
+
return new Response(JSON.stringify(fixed), {
|
|
676
|
+
status: resp.status,
|
|
677
|
+
statusText: resp.statusText,
|
|
678
|
+
headers: resp.headers
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
} catch {
|
|
682
|
+
}
|
|
683
|
+
return resp;
|
|
684
|
+
}
|
|
685
|
+
function makeSanitizingFetch(upstream = fetch, preserveThinking = false) {
|
|
686
|
+
return (async (input, init) => {
|
|
687
|
+
const resp = await upstream(input, init);
|
|
688
|
+
const contentType = resp.headers.get("content-type") ?? "";
|
|
689
|
+
if (resp.body && contentType.includes("text/event-stream")) {
|
|
690
|
+
const sanitized = resp.body.pipeThrough(createNullChunkStripper()).pipeThrough(createGlmChunkSanitizer(preserveThinking));
|
|
691
|
+
return new Response(sanitized, {
|
|
692
|
+
status: resp.status,
|
|
693
|
+
statusText: resp.statusText,
|
|
694
|
+
headers: resp.headers
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
if (resp.body && contentType.includes("application/json")) {
|
|
698
|
+
return sanitizeJsonResponse(resp);
|
|
699
|
+
}
|
|
700
|
+
return resp;
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
|
|
477
704
|
// src/gateway/providers/provider-reasoning.ts
|
|
478
705
|
var ANTHROPIC_EFFORT_LEVELS = ["low", "medium", "high"];
|
|
479
706
|
var OPENAI_EFFORT_LEVELS = ["low", "medium", "high", "xhigh"];
|
|
@@ -954,37 +1181,49 @@ function thinkingProviderOptions(npm) {
|
|
|
954
1181
|
}
|
|
955
1182
|
};
|
|
956
1183
|
}
|
|
1184
|
+
if (npm === "@ai-sdk/mistral") {
|
|
1185
|
+
return { mistral: { reasoningEffort: "high" } };
|
|
1186
|
+
}
|
|
957
1187
|
return void 0;
|
|
958
1188
|
}
|
|
959
1189
|
|
|
960
1190
|
// src/gateway/providers/provider-factory.ts
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
1191
|
+
function buildUpstreamFetch(spec, preserveThinking = false) {
|
|
1192
|
+
return makeSanitizingFetch(
|
|
1193
|
+
async (input, init) => {
|
|
1194
|
+
const url = typeof input === "string" ? input : input?.url ?? String(input);
|
|
1195
|
+
const hdrs = new Headers(init?.headers ?? {});
|
|
1196
|
+
if (spec.headers) {
|
|
1197
|
+
for (const [k, v] of Object.entries(spec.headers)) hdrs.set(k, v);
|
|
1198
|
+
}
|
|
1199
|
+
const nextInit = { ...init, headers: hdrs };
|
|
1200
|
+
if (process.env.ANYGATE_TRACE === "1") {
|
|
1201
|
+
const shown = [];
|
|
1202
|
+
hdrs.forEach((v, k) => {
|
|
1203
|
+
shown.push(`${k}: ${/authorization|api-key/i.test(k) ? v.slice(0, 12) + "\u2026" : v}`);
|
|
1204
|
+
});
|
|
1205
|
+
spec.onDebug?.(`[sdk-fetch] POST ${url}
|
|
1206
|
+
[sdk-fetch] headers:
|
|
1207
|
+
${shown.join("\n ")}`);
|
|
1208
|
+
if (typeof init?.body === "string") {
|
|
1209
|
+
spec.onDebug?.(`[sdk-fetch] \u2192 body: ${init.body.slice(0, 8e3)}`);
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
const resp = await fetch(input, nextInit);
|
|
1213
|
+
if (process.env.ANYGATE_TRACE === "1") {
|
|
1214
|
+
spec.onDebug?.(`[sdk-fetch] \u2190 HTTP ${resp.status}`);
|
|
1215
|
+
if (resp.status >= 400) {
|
|
1216
|
+
const body = await resp.clone().text().catch(() => "");
|
|
1217
|
+
spec.onDebug?.(`[sdk-fetch] \u2190 body: ${body.slice(0, 500)}`);
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
return resp;
|
|
980
1221
|
},
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
if (tail && !isNullDataLine(tail)) controller.enqueue(encoder.encode(tail));
|
|
984
|
-
}
|
|
985
|
-
});
|
|
1222
|
+
preserveThinking
|
|
1223
|
+
);
|
|
986
1224
|
}
|
|
987
1225
|
var factoryCache = /* @__PURE__ */ new Map();
|
|
1226
|
+
var RESPONSES_ONLY_PREFIXES = ["gpt-5-codex", "gpt-5-pro", "gpt-5.2-pro", "o3", "o4"];
|
|
988
1227
|
function modelPrefersResponsesApi(modelId) {
|
|
989
1228
|
const lower = modelId.toLowerCase();
|
|
990
1229
|
if (RESPONSES_ONLY_PREFIXES.some((prefix) => lower === prefix || lower.startsWith(`${prefix}-`))) {
|
|
@@ -1118,51 +1357,10 @@ async function createLanguageModel(spec) {
|
|
|
1118
1357
|
// Custom headers like User-Agent must be forced at the fetch layer: the AI
|
|
1119
1358
|
// SDK builds its own User-Agent and overwrites a header-option value on some
|
|
1120
1359
|
// call paths (streaming vs non-streaming differ), which breaks providers that
|
|
1121
|
-
// gate on client identity. This wrapper
|
|
1122
|
-
//
|
|
1123
|
-
//
|
|
1124
|
-
|
|
1125
|
-
fetch: (async (input, init) => {
|
|
1126
|
-
const url = typeof input === "string" ? input : input?.url ?? String(input);
|
|
1127
|
-
const hdrs = new Headers(init?.headers ?? {});
|
|
1128
|
-
if (spec.headers) {
|
|
1129
|
-
for (const [k, v] of Object.entries(spec.headers)) hdrs.set(k, v);
|
|
1130
|
-
}
|
|
1131
|
-
const nextInit = { ...init, headers: hdrs };
|
|
1132
|
-
if (process.env.ANYGATE_TRACE === "1") {
|
|
1133
|
-
const shown = [];
|
|
1134
|
-
hdrs.forEach((v, k) => {
|
|
1135
|
-
shown.push(`${k}: ${/authorization|api-key/i.test(k) ? v.slice(0, 12) + "\u2026" : v}`);
|
|
1136
|
-
});
|
|
1137
|
-
spec.onDebug?.(
|
|
1138
|
-
`[sdk-fetch] POST ${url}
|
|
1139
|
-
[sdk-fetch] headers:
|
|
1140
|
-
${shown.join("\n ")}`
|
|
1141
|
-
);
|
|
1142
|
-
if (typeof init?.body === "string") {
|
|
1143
|
-
spec.onDebug?.(`[sdk-fetch] \u2192 body: ${init.body.slice(0, 8e3)}`);
|
|
1144
|
-
}
|
|
1145
|
-
}
|
|
1146
|
-
const resp = await fetch(input, nextInit);
|
|
1147
|
-
if (process.env.ANYGATE_TRACE === "1") {
|
|
1148
|
-
spec.onDebug?.(`[sdk-fetch] \u2190 HTTP ${resp.status}`);
|
|
1149
|
-
if (resp.status >= 400) {
|
|
1150
|
-
const body = await resp.clone().text().catch(() => "");
|
|
1151
|
-
spec.onDebug?.(`[sdk-fetch] \u2190 body: ${body.slice(0, 500)}`);
|
|
1152
|
-
}
|
|
1153
|
-
}
|
|
1154
|
-
const contentType = resp.headers.get("content-type") ?? "";
|
|
1155
|
-
if (resp.body && contentType.includes("text/event-stream")) {
|
|
1156
|
-
const sanitized = resp.body.pipeThrough(createNullChunkStripper());
|
|
1157
|
-
return new Response(sanitized, {
|
|
1158
|
-
status: resp.status,
|
|
1159
|
-
statusText: resp.statusText,
|
|
1160
|
-
headers: resp.headers
|
|
1161
|
-
});
|
|
1162
|
-
}
|
|
1163
|
-
return resp;
|
|
1164
|
-
})
|
|
1165
|
-
} : {}
|
|
1360
|
+
// gate on client identity. This wrapper also normalizes non-spec-compliant
|
|
1361
|
+
// upstream streams (array `content`, missing tool-call `id`, `data: null`
|
|
1362
|
+
// keepalives) so third-party models such as GLM-5.2 stream through cleanly.
|
|
1363
|
+
fetch: buildUpstreamFetch(spec)
|
|
1166
1364
|
};
|
|
1167
1365
|
model = createOpenAICompatible({
|
|
1168
1366
|
...options
|
|
@@ -1179,11 +1377,12 @@ async function createLanguageModel(spec) {
|
|
|
1179
1377
|
const provider = create({
|
|
1180
1378
|
apiKey,
|
|
1181
1379
|
...baseURL ? { baseURL } : {},
|
|
1182
|
-
...spec.headers ? { headers: spec.headers } : {}
|
|
1380
|
+
...spec.headers ? { headers: spec.headers } : {},
|
|
1381
|
+
fetch: buildUpstreamFetch(spec, npm === "@ai-sdk/mistral")
|
|
1183
1382
|
});
|
|
1184
1383
|
model = provider(modelId);
|
|
1185
1384
|
}
|
|
1186
|
-
const isReasoning = modelId.toLowerCase().match(/deepseek-r1|think|reasoning|qwq/);
|
|
1385
|
+
const isReasoning = modelId.toLowerCase().match(/deepseek-(?:r1|v4)|think|reasoning|qwq/);
|
|
1187
1386
|
if (isReasoning) {
|
|
1188
1387
|
return wrapLanguageModel({
|
|
1189
1388
|
model,
|
|
@@ -2398,6 +2597,12 @@ function shouldHideByModelsDevCapabilities(entry) {
|
|
|
2398
2597
|
if (entry.interactions === true && entry.chat === false) return true;
|
|
2399
2598
|
return false;
|
|
2400
2599
|
}
|
|
2600
|
+
function resolveReasoning(providerId, modelId, cache = loadModelsDevCache()) {
|
|
2601
|
+
const entry = findModelsDevModel(providerId, modelId, cache);
|
|
2602
|
+
if (!entry) return void 0;
|
|
2603
|
+
if (typeof entry.reasoning === "boolean") return entry.reasoning;
|
|
2604
|
+
return void 0;
|
|
2605
|
+
}
|
|
2401
2606
|
|
|
2402
2607
|
// src/apps/shared/trace-log.ts
|
|
2403
2608
|
import { chmodSync as chmodSync5, existsSync as existsSync4, mkdirSync as mkdirSync5, readFileSync as readFileSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
@@ -2649,46 +2854,33 @@ async function getServerPasswordKeyring() {
|
|
|
2649
2854
|
}
|
|
2650
2855
|
}
|
|
2651
2856
|
async function getSavedServerPassword() {
|
|
2652
|
-
const config = readConfig();
|
|
2653
|
-
if (config.server?.savedPassword) {
|
|
2654
|
-
const pwd = config.server.savedPassword;
|
|
2655
|
-
const keyring2 = await getServerPasswordKeyring();
|
|
2656
|
-
if (keyring2) {
|
|
2657
|
-
try {
|
|
2658
|
-
await keyring2.setPassword(pwd);
|
|
2659
|
-
delete config.server.savedPassword;
|
|
2660
|
-
if (Object.keys(config.server).length === 0) delete config.server;
|
|
2661
|
-
writeConfig(config);
|
|
2662
|
-
} catch {
|
|
2663
|
-
}
|
|
2664
|
-
}
|
|
2665
|
-
return pwd;
|
|
2666
|
-
}
|
|
2667
2857
|
const keyring = await getServerPasswordKeyring();
|
|
2668
|
-
|
|
2858
|
+
const config = readConfig();
|
|
2859
|
+
const legacy = config.server?.savedPassword;
|
|
2860
|
+
if (legacy && keyring) {
|
|
2669
2861
|
try {
|
|
2670
|
-
|
|
2862
|
+
await keyring.setPassword(legacy);
|
|
2863
|
+
delete config.server.savedPassword;
|
|
2864
|
+
if (Object.keys(config.server).length === 0) delete config.server;
|
|
2865
|
+
writeConfig(config);
|
|
2671
2866
|
} catch {
|
|
2672
|
-
return null;
|
|
2673
2867
|
}
|
|
2674
2868
|
}
|
|
2675
|
-
return null;
|
|
2869
|
+
if (!keyring) return null;
|
|
2870
|
+
try {
|
|
2871
|
+
return await keyring.getPassword();
|
|
2872
|
+
} catch {
|
|
2873
|
+
return null;
|
|
2874
|
+
}
|
|
2676
2875
|
}
|
|
2677
2876
|
async function setSavedServerPassword(password) {
|
|
2678
2877
|
const keyring = await getServerPasswordKeyring();
|
|
2679
|
-
if (keyring) {
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
} catch {
|
|
2684
|
-
}
|
|
2878
|
+
if (!keyring) {
|
|
2879
|
+
throw new Error(
|
|
2880
|
+
"Network-mode server password requires OS keychain support (@napi-rs/keyring). Install the optional dependency or start the server with `--password <value>` for this run only."
|
|
2881
|
+
);
|
|
2685
2882
|
}
|
|
2686
|
-
|
|
2687
|
-
config.server = {
|
|
2688
|
-
...config.server ?? {},
|
|
2689
|
-
savedPassword: password
|
|
2690
|
-
};
|
|
2691
|
-
writeConfig(config);
|
|
2883
|
+
await keyring.setPassword(password);
|
|
2692
2884
|
}
|
|
2693
2885
|
function getServerExposedProviders() {
|
|
2694
2886
|
const list = readConfig().server?.exposedProviders;
|
|
@@ -2746,6 +2938,9 @@ function setServerListenMode(listenMode) {
|
|
|
2746
2938
|
};
|
|
2747
2939
|
writeConfig(config);
|
|
2748
2940
|
}
|
|
2941
|
+
function getServerBudget() {
|
|
2942
|
+
return readConfig().server?.budget ?? {};
|
|
2943
|
+
}
|
|
2749
2944
|
|
|
2750
2945
|
// src/apps/shared/context-window.ts
|
|
2751
2946
|
import { readFileSync as readFileSync7 } from "fs";
|
|
@@ -3345,12 +3540,6 @@ function guiCallbackRedirectUri(host) {
|
|
|
3345
3540
|
return `http://${host}/auth/callback`;
|
|
3346
3541
|
}
|
|
3347
3542
|
|
|
3348
|
-
// src/auth/antigravity-oauth.ts
|
|
3349
|
-
import open2 from "open";
|
|
3350
|
-
import { readFileSync as readFileSync8 } from "fs";
|
|
3351
|
-
import { homedir as homedir2 } from "os";
|
|
3352
|
-
import { join as pathJoin } from "path";
|
|
3353
|
-
|
|
3354
3543
|
// src/auth/callback-server.ts
|
|
3355
3544
|
import http from "http";
|
|
3356
3545
|
var SUCCESS_HTML = `<!DOCTYPE html><html><head><meta charset="utf-8"><title>Authorized</title></head>
|
|
@@ -3405,6 +3594,10 @@ function startCallbackServer() {
|
|
|
3405
3594
|
}
|
|
3406
3595
|
|
|
3407
3596
|
// src/auth/antigravity-oauth.ts
|
|
3597
|
+
import open2 from "open";
|
|
3598
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
3599
|
+
import { homedir as homedir2 } from "os";
|
|
3600
|
+
import { join as pathJoin } from "path";
|
|
3408
3601
|
var DEFAULT_ANTIGRAVITY_CLIENT_ID = [
|
|
3409
3602
|
"107100606059",
|
|
3410
3603
|
"1-tmhssin2h2",
|
|
@@ -3868,6 +4061,7 @@ function buildChildEnv(baseUrl, model, apiKey, proxyPort, contextWindow, enableG
|
|
|
3868
4061
|
}
|
|
3869
4062
|
env["ANTHROPIC_BASE_URL"] = proxyPort ? `http://127.0.0.1:${proxyPort}` : baseUrl;
|
|
3870
4063
|
env["ANTHROPIC_API_KEY"] = apiKey;
|
|
4064
|
+
env["ANTHROPIC_AUTH_TOKEN"] = apiKey;
|
|
3871
4065
|
const bareModel = stripOneMContextSuffix(model);
|
|
3872
4066
|
env["ANTHROPIC_MODEL"] = claudeCodeClientModelId(model, contextWindow);
|
|
3873
4067
|
env["CLAUDE_CODE_MAX_CONTEXT_TOKENS"] = String(resolveContextWindow(bareModel, contextWindow));
|
|
@@ -5053,6 +5247,16 @@ function parseModelList(body, npm, providerId) {
|
|
|
5053
5247
|
const freeStatus = classifyFreeStatus({
|
|
5054
5248
|
model: { cost, isFree: row.isFree }
|
|
5055
5249
|
});
|
|
5250
|
+
let reasoning;
|
|
5251
|
+
const caps = row.capabilities;
|
|
5252
|
+
if (caps && typeof caps === "object") {
|
|
5253
|
+
const capsObj = caps;
|
|
5254
|
+
const r = capsObj.reasoning;
|
|
5255
|
+
if (typeof r === "boolean") reasoning = r;
|
|
5256
|
+
}
|
|
5257
|
+
if (reasoning === void 0 && typeof row.reasoning === "boolean") {
|
|
5258
|
+
reasoning = row.reasoning;
|
|
5259
|
+
}
|
|
5056
5260
|
const contextWindow = row.context_length ?? row.contextWindow ?? row.context_window ?? resolveContextWindow(id, void 0, providerId);
|
|
5057
5261
|
models.push({
|
|
5058
5262
|
id,
|
|
@@ -5069,7 +5273,8 @@ function parseModelList(body, npm, providerId) {
|
|
|
5069
5273
|
providerId,
|
|
5070
5274
|
supportedParameters: Array.isArray(row.supported_parameters) ? row.supported_parameters : void 0,
|
|
5071
5275
|
useResponsesLite: typeof row.use_responses_lite === "boolean" ? row.use_responses_lite : void 0,
|
|
5072
|
-
preferWebSockets: typeof row.prefer_websockets === "boolean" ? row.prefer_websockets : void 0
|
|
5276
|
+
preferWebSockets: typeof row.prefer_websockets === "boolean" ? row.prefer_websockets : void 0,
|
|
5277
|
+
reasoning: reasoning === void 0 ? void 0 : reasoning
|
|
5073
5278
|
});
|
|
5074
5279
|
}
|
|
5075
5280
|
return models;
|
|
@@ -5320,47 +5525,102 @@ async function addProviderFromTemplate(template, apiKey, opts) {
|
|
|
5320
5525
|
|
|
5321
5526
|
// src/shared/http.ts
|
|
5322
5527
|
import * as zlib from "zlib";
|
|
5323
|
-
|
|
5324
|
-
|
|
5325
|
-
|
|
5326
|
-
|
|
5528
|
+
var RequestBodyTooLargeError = class extends Error {
|
|
5529
|
+
constructor(context) {
|
|
5530
|
+
super(`Request body too large (${context}, limit ${MAX_REQUEST_BODY_BYTES} bytes)`);
|
|
5531
|
+
this.name = "RequestBodyTooLargeError";
|
|
5532
|
+
}
|
|
5533
|
+
};
|
|
5534
|
+
function decodeBoundedChunk(createTransform, chunk, maxBytes) {
|
|
5535
|
+
return new Promise((resolve, reject) => {
|
|
5536
|
+
const transform = createTransform();
|
|
5537
|
+
const outChunks = [];
|
|
5538
|
+
let total = 0;
|
|
5539
|
+
let settled = false;
|
|
5540
|
+
const finish = (err) => {
|
|
5541
|
+
if (settled) return;
|
|
5542
|
+
settled = true;
|
|
5543
|
+
if (err) reject(err);
|
|
5544
|
+
else resolve(Buffer.concat(outChunks).toString());
|
|
5545
|
+
};
|
|
5546
|
+
transform.on("data", (out) => {
|
|
5547
|
+
total += out.length;
|
|
5548
|
+
if (total > maxBytes) {
|
|
5549
|
+
finish(new RequestBodyTooLargeError("decompressed"));
|
|
5550
|
+
transform.destroy();
|
|
5551
|
+
return;
|
|
5552
|
+
}
|
|
5553
|
+
outChunks.push(out);
|
|
5554
|
+
});
|
|
5555
|
+
transform.on("error", (err) => finish(err));
|
|
5556
|
+
transform.on("end", () => finish(null));
|
|
5557
|
+
transform.end(chunk);
|
|
5558
|
+
});
|
|
5559
|
+
}
|
|
5560
|
+
function decoderFor(encoding) {
|
|
5561
|
+
switch (encoding) {
|
|
5327
5562
|
case "gzip":
|
|
5328
5563
|
case "x-gzip":
|
|
5329
|
-
return
|
|
5564
|
+
return { label: encoding, createTransform: () => zlib.createGunzip() };
|
|
5330
5565
|
case "deflate":
|
|
5331
|
-
return
|
|
5566
|
+
return { label: encoding, createTransform: () => zlib.createInflate() };
|
|
5332
5567
|
case "br":
|
|
5333
|
-
return
|
|
5568
|
+
return { label: encoding, createTransform: () => zlib.createBrotliDecompress() };
|
|
5334
5569
|
case "zstd":
|
|
5335
|
-
if (typeof zlib.
|
|
5570
|
+
if (typeof zlib.createZstdDecompress !== "function") {
|
|
5336
5571
|
throw new Error("zstd request encoding requires Node >= 22.15");
|
|
5337
5572
|
}
|
|
5338
|
-
return
|
|
5573
|
+
return { label: encoding, createTransform: () => zlib.createZstdDecompress() };
|
|
5339
5574
|
default:
|
|
5340
|
-
return
|
|
5575
|
+
return { label: `${encoding || "identity"} (passthrough)` };
|
|
5576
|
+
}
|
|
5577
|
+
}
|
|
5578
|
+
async function inflateBounded(raw, spec, maxBytes) {
|
|
5579
|
+
if (!spec.createTransform) return raw.toString();
|
|
5580
|
+
const SLICE = 64 * 1024;
|
|
5581
|
+
let text4 = "";
|
|
5582
|
+
for (let offset = 0; offset < raw.length || offset === 0; offset += SLICE) {
|
|
5583
|
+
const slice = raw.subarray(offset, Math.min(offset + SLICE, raw.length));
|
|
5584
|
+
const part = await decodeBoundedChunk(spec.createTransform, slice, maxBytes - Buffer.byteLength(text4));
|
|
5585
|
+
text4 += part;
|
|
5586
|
+
if (Buffer.byteLength(text4) > maxBytes) {
|
|
5587
|
+
throw new RequestBodyTooLargeError(`decompressed (${spec.label})`);
|
|
5588
|
+
}
|
|
5589
|
+
if (offset + SLICE >= raw.length) break;
|
|
5341
5590
|
}
|
|
5591
|
+
return text4;
|
|
5592
|
+
}
|
|
5593
|
+
async function decodeRequestBody(raw, encoding) {
|
|
5594
|
+
const enc = (Array.isArray(encoding) ? encoding.join(",") : encoding ?? "").toLowerCase().trim();
|
|
5595
|
+
if (!enc || enc === "identity") return raw.toString();
|
|
5596
|
+
const spec = decoderFor(enc);
|
|
5597
|
+
return inflateBounded(raw, spec, MAX_REQUEST_BODY_BYTES);
|
|
5342
5598
|
}
|
|
5343
5599
|
function readBody(req) {
|
|
5344
5600
|
return new Promise((resolve, reject) => {
|
|
5345
5601
|
const chunks = [];
|
|
5346
5602
|
let totalSize = 0;
|
|
5603
|
+
let settled = false;
|
|
5604
|
+
const fail = (err) => {
|
|
5605
|
+
if (settled) return;
|
|
5606
|
+
settled = true;
|
|
5607
|
+
reject(err);
|
|
5608
|
+
req.destroy();
|
|
5609
|
+
};
|
|
5347
5610
|
req.on("data", (c) => {
|
|
5348
5611
|
totalSize += c.length;
|
|
5349
|
-
if (totalSize >
|
|
5350
|
-
|
|
5351
|
-
req.destroy();
|
|
5612
|
+
if (totalSize > MAX_REQUEST_BODY_BYTES) {
|
|
5613
|
+
fail(new RequestBodyTooLargeError("compressed"));
|
|
5352
5614
|
return;
|
|
5353
5615
|
}
|
|
5354
5616
|
chunks.push(c);
|
|
5355
5617
|
});
|
|
5356
5618
|
req.on("end", () => {
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
reject(err);
|
|
5361
|
-
}
|
|
5619
|
+
if (settled) return;
|
|
5620
|
+
settled = true;
|
|
5621
|
+
decodeRequestBody(Buffer.concat(chunks), req.headers["content-encoding"]).then(resolve, reject);
|
|
5362
5622
|
});
|
|
5363
|
-
req.on("error",
|
|
5623
|
+
req.on("error", fail);
|
|
5364
5624
|
});
|
|
5365
5625
|
}
|
|
5366
5626
|
function extractApiKey(req) {
|
|
@@ -5383,7 +5643,22 @@ function sendJson(res, status, body, extraHeaders) {
|
|
|
5383
5643
|
res.end(json);
|
|
5384
5644
|
}
|
|
5385
5645
|
var rateLimitStore = /* @__PURE__ */ new Map();
|
|
5646
|
+
var RATE_LIMIT_PRUNE_THRESHOLD = 1e4;
|
|
5647
|
+
function pruneRateLimits(now = Date.now()) {
|
|
5648
|
+
const staleBefore = now - 2 * RATE_LIMIT_WINDOW_MS;
|
|
5649
|
+
let pruned = 0;
|
|
5650
|
+
for (const [key, entry] of rateLimitStore) {
|
|
5651
|
+
if (entry.resetAt < staleBefore) {
|
|
5652
|
+
rateLimitStore.delete(key);
|
|
5653
|
+
pruned++;
|
|
5654
|
+
}
|
|
5655
|
+
}
|
|
5656
|
+
return pruned;
|
|
5657
|
+
}
|
|
5386
5658
|
function checkRateLimit(clientId) {
|
|
5659
|
+
if (rateLimitStore.size > RATE_LIMIT_PRUNE_THRESHOLD) {
|
|
5660
|
+
pruneRateLimits();
|
|
5661
|
+
}
|
|
5387
5662
|
const now = Date.now();
|
|
5388
5663
|
const entry = rateLimitStore.get(clientId);
|
|
5389
5664
|
if (!entry || now > entry.resetAt) {
|
|
@@ -5409,16 +5684,24 @@ import { appendFileSync as appendFileSync2, openSync as openSync4, writeSync as
|
|
|
5409
5684
|
import { Readable } from "stream";
|
|
5410
5685
|
|
|
5411
5686
|
// src/gateway/server/auth.ts
|
|
5687
|
+
import { timingSafeEqual } from "crypto";
|
|
5688
|
+
import { createHash as createHash2 } from "crypto";
|
|
5412
5689
|
function sanitizeCredential(value) {
|
|
5413
5690
|
if (!value) return null;
|
|
5414
5691
|
const firstLine = value.trim().split(/\r?\n/)[0]?.trim();
|
|
5415
5692
|
return firstLine || null;
|
|
5416
5693
|
}
|
|
5694
|
+
function timingSafeEqualStrings(a, b) {
|
|
5695
|
+
const da = createHash2("sha256").update(a, "utf8").digest();
|
|
5696
|
+
const db = createHash2("sha256").update(b, "utf8").digest();
|
|
5697
|
+
return timingSafeEqual(da, db);
|
|
5698
|
+
}
|
|
5417
5699
|
function isAuthorized(request, serverPassword) {
|
|
5418
5700
|
if (serverPassword === null) return true;
|
|
5419
5701
|
const bearerToken = extractBearerToken(request.headers.get("authorization"));
|
|
5420
|
-
if (bearerToken
|
|
5421
|
-
|
|
5702
|
+
if (bearerToken !== null && timingSafeEqualStrings(bearerToken, serverPassword)) return true;
|
|
5703
|
+
const apiKey = sanitizeCredential(request.headers.get("x-api-key"));
|
|
5704
|
+
return apiKey !== null && timingSafeEqualStrings(apiKey, serverPassword);
|
|
5422
5705
|
}
|
|
5423
5706
|
function extractBearerToken(value) {
|
|
5424
5707
|
if (!value) return null;
|
|
@@ -5583,73 +5866,169 @@ async function fetchWithOAuthRetry(apiKey, request, refreshToken) {
|
|
|
5583
5866
|
response = await request(refreshed);
|
|
5584
5867
|
return { response, apiKey: refreshed, refreshed: true };
|
|
5585
5868
|
}
|
|
5586
|
-
|
|
5587
|
-
const
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
throw new UpstreamUnreachableError(err);
|
|
5869
|
+
function scanAnthropicSseUsage(chunkText, usage) {
|
|
5870
|
+
for (const line of chunkText.split("\n")) {
|
|
5871
|
+
const t = line.startsWith("data:") ? line.slice(5).trim() : line.trim();
|
|
5872
|
+
if (!t || t === "[DONE]") continue;
|
|
5873
|
+
try {
|
|
5874
|
+
const obj = JSON.parse(t);
|
|
5875
|
+
const startUsage = obj.message?.usage;
|
|
5876
|
+
if (startUsage) {
|
|
5877
|
+
usage.inputTokens = startUsage.input_tokens ?? usage.inputTokens;
|
|
5878
|
+
if (typeof startUsage.output_tokens === "number")
|
|
5879
|
+
usage.outputTokens = startUsage.output_tokens;
|
|
5880
|
+
continue;
|
|
5881
|
+
}
|
|
5882
|
+
const deltaUsage = obj.usage;
|
|
5883
|
+
if (deltaUsage && typeof deltaUsage.output_tokens === "number") {
|
|
5884
|
+
usage.outputTokens = deltaUsage.output_tokens;
|
|
5885
|
+
}
|
|
5886
|
+
} catch {
|
|
5887
|
+
}
|
|
5606
5888
|
}
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
|
|
5889
|
+
}
|
|
5890
|
+
async function forwardAnthropicMessages(res, messagesUrl, body, apiKey, clientWantsStream, inboundBeta, authType, log6, claudeCodeSessionId, extraHeaders, refreshToken, onTokenRefreshed, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
5891
|
+
const usage = { inputTokens: 0, outputTokens: 0 };
|
|
5892
|
+
const disconnectController = new AbortController();
|
|
5893
|
+
const onClientGone = () => disconnectController.abort();
|
|
5894
|
+
res.on("close", onClientGone);
|
|
5895
|
+
res.on("error", onClientGone);
|
|
5896
|
+
const timeoutController = new AbortController();
|
|
5897
|
+
const timer = setTimeout(() => timeoutController.abort(), timeoutMs);
|
|
5898
|
+
const combinedSignal = disconnectController.signal.aborted ? disconnectController.signal : connectAbortSignals(disconnectController.signal, timeoutController.signal);
|
|
5899
|
+
try {
|
|
5900
|
+
return await forwardInner(combinedSignal);
|
|
5901
|
+
} finally {
|
|
5902
|
+
clearTimeout(timer);
|
|
5903
|
+
res.off("close", onClientGone);
|
|
5904
|
+
res.off("error", onClientGone);
|
|
5905
|
+
}
|
|
5906
|
+
function connectAbortSignals(a, b) {
|
|
5907
|
+
const merged = new AbortController();
|
|
5908
|
+
const relay = () => merged.abort();
|
|
5909
|
+
if (a.aborted || b.aborted) {
|
|
5910
|
+
merged.abort();
|
|
5911
|
+
return merged.signal;
|
|
5912
|
+
}
|
|
5913
|
+
a.addEventListener("abort", relay, { once: true });
|
|
5914
|
+
b.addEventListener("abort", relay, { once: true });
|
|
5915
|
+
return merged.signal;
|
|
5916
|
+
}
|
|
5917
|
+
async function forwardInner(signal) {
|
|
5918
|
+
const doFetch = (key) => fetch(messagesUrl, {
|
|
5919
|
+
method: "POST",
|
|
5920
|
+
headers: anthropicUpstreamHeaders(
|
|
5921
|
+
key,
|
|
5922
|
+
clientWantsStream,
|
|
5923
|
+
inboundBeta,
|
|
5924
|
+
authType,
|
|
5925
|
+
claudeCodeSessionId,
|
|
5926
|
+
extraHeaders
|
|
5927
|
+
),
|
|
5928
|
+
body: JSON.stringify(body),
|
|
5929
|
+
signal
|
|
5612
5930
|
});
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
5931
|
+
let upstreamRes;
|
|
5932
|
+
try {
|
|
5933
|
+
const retryResult = await fetchWithOAuthRetry(apiKey, doFetch, refreshToken);
|
|
5934
|
+
upstreamRes = retryResult.response;
|
|
5935
|
+
if (retryResult.refreshed) onTokenRefreshed?.(retryResult.apiKey);
|
|
5936
|
+
} catch (err) {
|
|
5937
|
+
if (signal.aborted) {
|
|
5938
|
+
emitAborted(res, clientWantsStream);
|
|
5939
|
+
return usage;
|
|
5940
|
+
}
|
|
5941
|
+
throw new UpstreamUnreachableError(err);
|
|
5942
|
+
}
|
|
5943
|
+
if (!upstreamRes.ok) {
|
|
5944
|
+
const errBody = await upstreamRes.text();
|
|
5945
|
+
log6?.(`anthropic upstream ${upstreamRes.status}: ${errBody}`);
|
|
5946
|
+
res.writeHead(upstreamRes.status, {
|
|
5947
|
+
"Content-Type": upstreamRes.headers.get("content-type") || "application/json"
|
|
5948
|
+
});
|
|
5949
|
+
res.end(errBody);
|
|
5950
|
+
return usage;
|
|
5951
|
+
}
|
|
5952
|
+
if (clientWantsStream && upstreamRes.body) {
|
|
5953
|
+
res.writeHead(200, {
|
|
5954
|
+
"Content-Type": "text/event-stream",
|
|
5955
|
+
"Cache-Control": "no-cache",
|
|
5956
|
+
Connection: "keep-alive"
|
|
5957
|
+
});
|
|
5958
|
+
await new Promise((resolve, reject) => {
|
|
5959
|
+
Readable.fromWeb(upstreamRes.body).on("data", (chunk) => scanAnthropicSseUsage(chunk.toString("utf8"), usage)).on("error", (err) => {
|
|
5960
|
+
res.destroy();
|
|
5961
|
+
reject(err);
|
|
5962
|
+
}).on("end", () => resolve()).pipe(res);
|
|
5963
|
+
}).catch(() => {
|
|
5964
|
+
});
|
|
5965
|
+
return usage;
|
|
5966
|
+
}
|
|
5967
|
+
if (!upstreamRes.body) {
|
|
5968
|
+
res.writeHead(502, { "Content-Type": "application/json" });
|
|
5969
|
+
res.end(
|
|
5970
|
+
JSON.stringify({
|
|
5971
|
+
type: "error",
|
|
5972
|
+
error: { type: "api_error", message: "Upstream returned empty response body" }
|
|
5973
|
+
})
|
|
5974
|
+
);
|
|
5975
|
+
return usage;
|
|
5976
|
+
}
|
|
5977
|
+
let text4;
|
|
5978
|
+
try {
|
|
5979
|
+
text4 = await upstreamRes.text();
|
|
5980
|
+
} catch {
|
|
5981
|
+
emitAborted(res, clientWantsStream);
|
|
5982
|
+
return usage;
|
|
5983
|
+
}
|
|
5984
|
+
let parsed;
|
|
5985
|
+
try {
|
|
5986
|
+
parsed = JSON.parse(text4);
|
|
5987
|
+
} catch {
|
|
5988
|
+
res.writeHead(502, { "Content-Type": "application/json" });
|
|
5989
|
+
res.end(
|
|
5990
|
+
JSON.stringify({
|
|
5991
|
+
type: "error",
|
|
5992
|
+
error: { type: "api_error", message: "Upstream response was not valid JSON" }
|
|
5993
|
+
})
|
|
5994
|
+
);
|
|
5995
|
+
return usage;
|
|
5996
|
+
}
|
|
5997
|
+
const parsedUsage = parsed.usage;
|
|
5998
|
+
if (parsedUsage) {
|
|
5999
|
+
usage.inputTokens = parsedUsage.input_tokens ?? 0;
|
|
6000
|
+
usage.outputTokens = parsedUsage.output_tokens ?? 0;
|
|
6001
|
+
}
|
|
5617
6002
|
res.writeHead(200, {
|
|
5618
|
-
"Content-Type": "
|
|
5619
|
-
"
|
|
5620
|
-
Connection: "keep-alive"
|
|
6003
|
+
"Content-Type": "application/json",
|
|
6004
|
+
"Content-Length": Buffer.byteLength(text4).toString()
|
|
5621
6005
|
});
|
|
5622
|
-
|
|
5623
|
-
return;
|
|
6006
|
+
res.end(text4);
|
|
6007
|
+
return usage;
|
|
5624
6008
|
}
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
|
|
5628
|
-
|
|
5629
|
-
type: "error",
|
|
5630
|
-
error: { type: "api_error", message: "Upstream returned empty response body" }
|
|
5631
|
-
})
|
|
5632
|
-
);
|
|
6009
|
+
}
|
|
6010
|
+
function emitAborted(res, clientWantsStream) {
|
|
6011
|
+
if (res.headersSent || res.writableEnded || res.destroyed) {
|
|
6012
|
+
res.destroy();
|
|
5633
6013
|
return;
|
|
5634
6014
|
}
|
|
5635
|
-
|
|
5636
|
-
|
|
5637
|
-
JSON.parse(text4);
|
|
5638
|
-
} catch {
|
|
5639
|
-
res.writeHead(502, { "Content-Type": "application/json" });
|
|
6015
|
+
if (clientWantsStream) {
|
|
6016
|
+
res.writeHead(504, { "Content-Type": "application/json" });
|
|
5640
6017
|
res.end(
|
|
5641
6018
|
JSON.stringify({
|
|
5642
6019
|
type: "error",
|
|
5643
|
-
error: { type: "
|
|
6020
|
+
error: { type: "timeout", message: "Upstream request timed out or was aborted." }
|
|
5644
6021
|
})
|
|
5645
6022
|
);
|
|
5646
6023
|
return;
|
|
5647
6024
|
}
|
|
5648
|
-
res.writeHead(
|
|
5649
|
-
|
|
5650
|
-
|
|
5651
|
-
|
|
5652
|
-
|
|
6025
|
+
res.writeHead(504, { "Content-Type": "application/json" });
|
|
6026
|
+
res.end(
|
|
6027
|
+
JSON.stringify({
|
|
6028
|
+
type: "error",
|
|
6029
|
+
error: { type: "timeout", message: "Upstream request timed out or was aborted." }
|
|
6030
|
+
})
|
|
6031
|
+
);
|
|
5653
6032
|
}
|
|
5654
6033
|
|
|
5655
6034
|
// src/gateway/antigravity/anthropic-to-cloudcode.ts
|
|
@@ -6442,7 +6821,7 @@ async function collectCloudCodeToAnthropic(upstreamRes, model, log6) {
|
|
|
6442
6821
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
6443
6822
|
|
|
6444
6823
|
// src/gateway/adapters/sdk-adapter.ts
|
|
6445
|
-
import { streamText,
|
|
6824
|
+
import { streamText, tool as tool3, jsonSchema as jsonSchema3, stepCountIs } from "ai";
|
|
6446
6825
|
|
|
6447
6826
|
// src/apps/shared/tool-search.ts
|
|
6448
6827
|
var TOOL_SEARCH_TYPE_PREFIX = "tool_search_tool";
|
|
@@ -6831,15 +7210,21 @@ function annotateToolNames(messages) {
|
|
|
6831
7210
|
}
|
|
6832
7211
|
function thinkingToSdkPart(block, npm) {
|
|
6833
7212
|
const text4 = block.thinking ?? "";
|
|
6834
|
-
if (npm === "@ai-sdk/openai" && !block.signature && !text4.trim())
|
|
7213
|
+
if ((npm === "@ai-sdk/openai" || npm === "@ai-sdk/openai-compatible") && !block.signature && !text4.trim())
|
|
7214
|
+
return null;
|
|
6835
7215
|
const part = { type: "reasoning", text: text4 };
|
|
6836
7216
|
if (block.signature) {
|
|
6837
7217
|
if (npm === "@ai-sdk/google") {
|
|
6838
7218
|
part.providerOptions = { google: { thoughtSignature: block.signature } };
|
|
6839
|
-
} else if (npm === "@ai-sdk/openai"
|
|
7219
|
+
} else if (npm === "@ai-sdk/openai") {
|
|
6840
7220
|
part.providerOptions = { openai: { reasoningEncryptedContent: block.signature } };
|
|
7221
|
+
} else if (npm === "@ai-sdk/openai-compatible") {
|
|
7222
|
+
part.providerOptions = { openaiCompatible: { reasoningEncryptedContent: block.signature } };
|
|
6841
7223
|
}
|
|
6842
7224
|
}
|
|
7225
|
+
if (npm === "@ai-sdk/openai-compatible" && text4) {
|
|
7226
|
+
part.reasoning_content = text4;
|
|
7227
|
+
}
|
|
6843
7228
|
return part;
|
|
6844
7229
|
}
|
|
6845
7230
|
function translateMessages(messages, npm) {
|
|
@@ -7102,14 +7487,18 @@ async function writeAnthropicStream(fullStream, modelId, write, log6, hiddenTool
|
|
|
7102
7487
|
hiddenIds.add(part.id ?? "");
|
|
7103
7488
|
break;
|
|
7104
7489
|
}
|
|
7490
|
+
const toolKey = part.id ?? "";
|
|
7491
|
+
if (idToBlock.has(toolKey)) {
|
|
7492
|
+
break;
|
|
7493
|
+
}
|
|
7105
7494
|
const sig = grabRoundTripSignature(part);
|
|
7106
7495
|
openBlock("tool", {
|
|
7107
7496
|
type: "tool_use",
|
|
7108
|
-
id: encodeToolUseId(
|
|
7497
|
+
id: encodeToolUseId(toolKey, sig),
|
|
7109
7498
|
name: part.toolName,
|
|
7110
7499
|
input: {}
|
|
7111
7500
|
});
|
|
7112
|
-
idToBlock.set(
|
|
7501
|
+
idToBlock.set(toolKey, blockIndex);
|
|
7113
7502
|
break;
|
|
7114
7503
|
}
|
|
7115
7504
|
case "tool-input-delta":
|
|
@@ -7215,29 +7604,32 @@ async function generateAnthropicResponse(model, params, modelId, options) {
|
|
|
7215
7604
|
let toolCalls;
|
|
7216
7605
|
let finishReason;
|
|
7217
7606
|
let usage;
|
|
7607
|
+
let reasoningText = "";
|
|
7218
7608
|
const { webSearchToolName, ...callParams } = params;
|
|
7219
7609
|
const stopWhen = webSearchToolName ? stepCountIs(MAX_WEB_SEARCH_STEPS) : void 0;
|
|
7220
|
-
|
|
7221
|
-
|
|
7222
|
-
|
|
7223
|
-
|
|
7224
|
-
|
|
7225
|
-
|
|
7226
|
-
|
|
7227
|
-
|
|
7228
|
-
r.finishReason,
|
|
7229
|
-
r.usage
|
|
7230
|
-
]);
|
|
7231
|
-
} else {
|
|
7232
|
-
const r = await generateText({ model, ...callParams, stopWhen });
|
|
7233
|
-
({ text: text4, toolCalls, finishReason, usage } = r);
|
|
7610
|
+
const r = streamText({ model, ...callParams, stopWhen, onError: () => {
|
|
7611
|
+
} });
|
|
7612
|
+
Promise.resolve(r.toolResults).catch(() => {
|
|
7613
|
+
});
|
|
7614
|
+
for await (const part of r.fullStream) {
|
|
7615
|
+
if (part.type === "reasoning-delta") {
|
|
7616
|
+
reasoningText += part.text ?? "";
|
|
7617
|
+
}
|
|
7234
7618
|
}
|
|
7619
|
+
;
|
|
7620
|
+
[text4, toolCalls, finishReason, usage] = await Promise.all([
|
|
7621
|
+
r.text,
|
|
7622
|
+
r.toolCalls,
|
|
7623
|
+
r.finishReason,
|
|
7624
|
+
r.usage
|
|
7625
|
+
]);
|
|
7235
7626
|
return {
|
|
7236
7627
|
id: "msg_" + Date.now(),
|
|
7237
7628
|
type: "message",
|
|
7238
7629
|
role: "assistant",
|
|
7239
7630
|
model: modelId,
|
|
7240
7631
|
content: [
|
|
7632
|
+
...reasoningText ? [{ type: "thinking", thinking: reasoningText, signature: "" }] : [],
|
|
7241
7633
|
...text4 ? [{ type: "text", text: text4 }] : [],
|
|
7242
7634
|
...toolCalls.filter((tc) => tc.toolName !== webSearchToolName).map((tc) => ({
|
|
7243
7635
|
type: "tool_use",
|
|
@@ -7672,10 +8064,10 @@ function startProxyCatalog(routes, defaultAliasId, debug = false, opts) {
|
|
|
7672
8064
|
const originalModel = anthropicBody.model;
|
|
7673
8065
|
const clientWantsStream = Boolean(anthropicBody.stream);
|
|
7674
8066
|
const route = resolveRoute(byAlias, originalModel, defaultRoute, plog);
|
|
8067
|
+
let aliasWarning = null;
|
|
7675
8068
|
if (route === defaultRoute && originalModel !== defaultRoute.aliasId) {
|
|
7676
|
-
|
|
7677
|
-
|
|
7678
|
-
);
|
|
8069
|
+
aliasWarning = `model alias '${originalModel}' not found; using '${defaultRoute.aliasId}'`;
|
|
8070
|
+
quietErrorLog(`POST /v1/messages - ${aliasWarning}`);
|
|
7679
8071
|
}
|
|
7680
8072
|
const apiKey = route.apiKey;
|
|
7681
8073
|
const upstreamUrl = route.upstreamUrl;
|
|
@@ -7771,8 +8163,12 @@ function startProxyCatalog(routes, defaultAliasId, debug = false, opts) {
|
|
|
7771
8163
|
res.writeHead(200, {
|
|
7772
8164
|
"Content-Type": "text/event-stream",
|
|
7773
8165
|
"Cache-Control": "no-cache",
|
|
7774
|
-
Connection: "keep-alive"
|
|
8166
|
+
Connection: "keep-alive",
|
|
8167
|
+
...aliasWarning ? { "anthropic-warning": aliasWarning } : {}
|
|
7775
8168
|
});
|
|
8169
|
+
if (aliasWarning) res.write(`: ${aliasWarning}
|
|
8170
|
+
|
|
8171
|
+
`);
|
|
7776
8172
|
const usage = await streamAnthropicResponse(
|
|
7777
8173
|
model,
|
|
7778
8174
|
params,
|
|
@@ -7807,7 +8203,12 @@ function startProxyCatalog(routes, defaultAliasId, debug = false, opts) {
|
|
|
7807
8203
|
inputTokens: u?.inputTokens ?? 0,
|
|
7808
8204
|
outputTokens: u?.outputTokens ?? 0
|
|
7809
8205
|
});
|
|
7810
|
-
sendJson(
|
|
8206
|
+
sendJson(
|
|
8207
|
+
res,
|
|
8208
|
+
200,
|
|
8209
|
+
anthropicResponse,
|
|
8210
|
+
aliasWarning ? { "anthropic-warning": aliasWarning } : void 0
|
|
8211
|
+
);
|
|
7811
8212
|
}
|
|
7812
8213
|
} catch (err) {
|
|
7813
8214
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -7963,6 +8364,7 @@ function startProxy(completionsUrl, modelId, debug = false, contextWindow, sdk,
|
|
|
7963
8364
|
interleavedReasoningField: sdk?.interleavedReasoningField,
|
|
7964
8365
|
useResponsesLite: sdk?.useResponsesLite,
|
|
7965
8366
|
preferWebSockets: sdk?.preferWebSockets,
|
|
8367
|
+
headers: sdk?.headers,
|
|
7966
8368
|
app: sdk?.app
|
|
7967
8369
|
}
|
|
7968
8370
|
],
|
|
@@ -10911,6 +11313,206 @@ async function streamOpenAiResponse(model, params, responseModelId, onChunk) {
|
|
|
10911
11313
|
return usage;
|
|
10912
11314
|
}
|
|
10913
11315
|
|
|
11316
|
+
// src/gateway/server/budget.ts
|
|
11317
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync9, openSync as openSync5, readFileSync as readFileSync14, writeSync as writeSync5, closeSync as closeSync5 } from "fs";
|
|
11318
|
+
import { dirname as dirname7, join as join14 } from "path";
|
|
11319
|
+
var STATE_FILENAME = "budget-state.json";
|
|
11320
|
+
function utcDayKey(now = /* @__PURE__ */ new Date()) {
|
|
11321
|
+
return now.toISOString().slice(0, 10);
|
|
11322
|
+
}
|
|
11323
|
+
function utcMonthKey(now = /* @__PURE__ */ new Date()) {
|
|
11324
|
+
return now.toISOString().slice(0, 7);
|
|
11325
|
+
}
|
|
11326
|
+
function statePath() {
|
|
11327
|
+
return join14(getAppHome(), STATE_FILENAME);
|
|
11328
|
+
}
|
|
11329
|
+
function emptyState(now = /* @__PURE__ */ new Date()) {
|
|
11330
|
+
return { day: utcDayKey(now), dayTokens: 0, month: utcMonthKey(now), monthTokens: 0 };
|
|
11331
|
+
}
|
|
11332
|
+
function readStateFile() {
|
|
11333
|
+
const path = statePath();
|
|
11334
|
+
if (!existsSync15(path)) return null;
|
|
11335
|
+
try {
|
|
11336
|
+
const raw = JSON.parse(readFileSync14(path, "utf8"));
|
|
11337
|
+
if (typeof raw.day !== "string" || typeof raw.month !== "string" || typeof raw.dayTokens !== "number" || typeof raw.monthTokens !== "number") {
|
|
11338
|
+
return null;
|
|
11339
|
+
}
|
|
11340
|
+
return raw;
|
|
11341
|
+
} catch {
|
|
11342
|
+
return null;
|
|
11343
|
+
}
|
|
11344
|
+
}
|
|
11345
|
+
function writeState(state) {
|
|
11346
|
+
const path = statePath();
|
|
11347
|
+
try {
|
|
11348
|
+
mkdirSync9(dirname7(path), { recursive: true, mode: 448 });
|
|
11349
|
+
} catch {
|
|
11350
|
+
}
|
|
11351
|
+
try {
|
|
11352
|
+
const json = `${JSON.stringify(state)}
|
|
11353
|
+
`;
|
|
11354
|
+
const fd = openSync5(path, "w", 384);
|
|
11355
|
+
try {
|
|
11356
|
+
writeSync5(fd, json);
|
|
11357
|
+
} finally {
|
|
11358
|
+
closeSync5(fd);
|
|
11359
|
+
}
|
|
11360
|
+
} catch {
|
|
11361
|
+
}
|
|
11362
|
+
}
|
|
11363
|
+
var BudgetTracker = class {
|
|
11364
|
+
constructor(config, now = /* @__PURE__ */ new Date()) {
|
|
11365
|
+
this.config = config;
|
|
11366
|
+
this.state = this.loadRollover(now);
|
|
11367
|
+
}
|
|
11368
|
+
config;
|
|
11369
|
+
state;
|
|
11370
|
+
loadRollover(now) {
|
|
11371
|
+
const persisted = readStateFile();
|
|
11372
|
+
const day = utcDayKey(now);
|
|
11373
|
+
const month = utcMonthKey(now);
|
|
11374
|
+
if (!persisted) return emptyState(now);
|
|
11375
|
+
return {
|
|
11376
|
+
day,
|
|
11377
|
+
// Day/month rollover: stale periods reset to zero automatically.
|
|
11378
|
+
dayTokens: persisted.day === day ? persisted.dayTokens : 0,
|
|
11379
|
+
month,
|
|
11380
|
+
monthTokens: persisted.month === month ? persisted.monthTokens : 0
|
|
11381
|
+
};
|
|
11382
|
+
}
|
|
11383
|
+
get enabled() {
|
|
11384
|
+
return this.config.enabled === true;
|
|
11385
|
+
}
|
|
11386
|
+
dayExceeded() {
|
|
11387
|
+
const max = this.config.maxTokensPerDay;
|
|
11388
|
+
return typeof max === "number" && max > 0 && this.state.dayTokens >= max;
|
|
11389
|
+
}
|
|
11390
|
+
monthExceeded() {
|
|
11391
|
+
const max = this.config.maxTokensPerMonth;
|
|
11392
|
+
return typeof max === "number" && max > 0 && this.state.monthTokens >= max;
|
|
11393
|
+
}
|
|
11394
|
+
/** Check whether requests may proceed under the configured budgets. */
|
|
11395
|
+
check() {
|
|
11396
|
+
if (!this.enabled) return { allowed: true };
|
|
11397
|
+
const now = /* @__PURE__ */ new Date();
|
|
11398
|
+
if (this.state.day !== utcDayKey(now)) this.state.dayTokens = 0;
|
|
11399
|
+
if (this.state.month !== utcMonthKey(now)) this.state.monthTokens = 0;
|
|
11400
|
+
if (this.dayExceeded()) {
|
|
11401
|
+
return {
|
|
11402
|
+
allowed: false,
|
|
11403
|
+
reason: `Daily token budget exceeded (${this.state.dayTokens}/${this.config.maxTokensPerDay}).`
|
|
11404
|
+
};
|
|
11405
|
+
}
|
|
11406
|
+
if (this.monthExceeded()) {
|
|
11407
|
+
return {
|
|
11408
|
+
allowed: false,
|
|
11409
|
+
reason: `Monthly token budget exceeded (${this.state.monthTokens}/${this.config.maxTokensPerMonth}).`
|
|
11410
|
+
};
|
|
11411
|
+
}
|
|
11412
|
+
return { allowed: true };
|
|
11413
|
+
}
|
|
11414
|
+
/** Record actual usage after a completed response. */
|
|
11415
|
+
add(inputTokens, outputTokens) {
|
|
11416
|
+
const total = Math.max(0, inputTokens) + Math.max(0, outputTokens);
|
|
11417
|
+
if (total === 0) return;
|
|
11418
|
+
const now = /* @__PURE__ */ new Date();
|
|
11419
|
+
if (this.state.day !== utcDayKey(now)) {
|
|
11420
|
+
this.state.day = utcDayKey(now);
|
|
11421
|
+
this.state.dayTokens = 0;
|
|
11422
|
+
}
|
|
11423
|
+
if (this.state.month !== utcMonthKey(now)) {
|
|
11424
|
+
this.state.month = utcMonthKey(now);
|
|
11425
|
+
this.state.monthTokens = 0;
|
|
11426
|
+
}
|
|
11427
|
+
this.state.dayTokens += total;
|
|
11428
|
+
this.state.monthTokens += total;
|
|
11429
|
+
writeState(this.state);
|
|
11430
|
+
}
|
|
11431
|
+
/** Current counters (for dashboards/tests). */
|
|
11432
|
+
snapshot() {
|
|
11433
|
+
return { ...this.state };
|
|
11434
|
+
}
|
|
11435
|
+
};
|
|
11436
|
+
|
|
11437
|
+
// src/services/provider-health.ts
|
|
11438
|
+
var healthMap = /* @__PURE__ */ new Map();
|
|
11439
|
+
function updateProviderHealth(status) {
|
|
11440
|
+
healthMap.set(status.providerId, status);
|
|
11441
|
+
}
|
|
11442
|
+
|
|
11443
|
+
// src/gateway/server/upstream-resilience.ts
|
|
11444
|
+
var MAX_UPSTREAM_RETRIES = 2;
|
|
11445
|
+
var UNHEALTHY_WINDOW_MS = 6e4;
|
|
11446
|
+
var RETRYABLE_STATUS = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
|
|
11447
|
+
function isRetryableUpstreamError(err) {
|
|
11448
|
+
if (!err || typeof err !== "object") return false;
|
|
11449
|
+
const rec = err;
|
|
11450
|
+
for (const candidate of [rec.statusCode, rec.httpStatus]) {
|
|
11451
|
+
if (typeof candidate === "number" && RETRYABLE_STATUS.has(candidate)) return true;
|
|
11452
|
+
}
|
|
11453
|
+
return false;
|
|
11454
|
+
}
|
|
11455
|
+
function backoffDelayMs(attempt, retryAfterMs) {
|
|
11456
|
+
if (retryAfterMs != null) return retryAfterMs;
|
|
11457
|
+
return 500 * 2 ** attempt;
|
|
11458
|
+
}
|
|
11459
|
+
async function withUpstreamRetry(fn, onRetry, sleep = defaultSleep) {
|
|
11460
|
+
let lastErr;
|
|
11461
|
+
for (let attempt = 0; attempt <= MAX_UPSTREAM_RETRIES; attempt++) {
|
|
11462
|
+
try {
|
|
11463
|
+
return await fn();
|
|
11464
|
+
} catch (err) {
|
|
11465
|
+
lastErr = err;
|
|
11466
|
+
if (attempt === MAX_UPSTREAM_RETRIES || !isRetryableUpstreamError(err)) throw err;
|
|
11467
|
+
const delay = backoffDelayMs(attempt);
|
|
11468
|
+
await onRetry?.(attempt + 1, delay, err);
|
|
11469
|
+
await sleep(delay);
|
|
11470
|
+
}
|
|
11471
|
+
}
|
|
11472
|
+
throw lastErr;
|
|
11473
|
+
}
|
|
11474
|
+
function defaultSleep(ms) {
|
|
11475
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
11476
|
+
}
|
|
11477
|
+
var unhealthyUntil = /* @__PURE__ */ new Map();
|
|
11478
|
+
function recordProviderFailure(providerId, now = Date.now()) {
|
|
11479
|
+
const existing = failCounts.get(providerId) ?? { count: 0, firstAt: now };
|
|
11480
|
+
const count = now - existing.firstAt > UNHEALTHY_WINDOW_MS ? 1 : existing.count + 1;
|
|
11481
|
+
failCounts.set(providerId, { count, firstAt: count === 1 ? now : existing.firstAt });
|
|
11482
|
+
if (count >= 2) {
|
|
11483
|
+
unhealthyUntil.set(providerId, now + UNHEALTHY_WINDOW_MS);
|
|
11484
|
+
updateProviderHealthSafe(providerId, false, now);
|
|
11485
|
+
}
|
|
11486
|
+
}
|
|
11487
|
+
function recordProviderSuccess(providerId, now = Date.now()) {
|
|
11488
|
+
failCounts.delete(providerId);
|
|
11489
|
+
unhealthyUntil.delete(providerId);
|
|
11490
|
+
updateProviderHealthSafe(providerId, true, now);
|
|
11491
|
+
}
|
|
11492
|
+
function isProviderUnavailable(providerId, now = Date.now()) {
|
|
11493
|
+
const until = unhealthyUntil.get(providerId);
|
|
11494
|
+
if (until === void 0) return false;
|
|
11495
|
+
if (now >= until) {
|
|
11496
|
+
unhealthyUntil.delete(providerId);
|
|
11497
|
+
failCounts.delete(providerId);
|
|
11498
|
+
updateProviderHealthSafe(providerId, true, now);
|
|
11499
|
+
return false;
|
|
11500
|
+
}
|
|
11501
|
+
return true;
|
|
11502
|
+
}
|
|
11503
|
+
var failCounts = /* @__PURE__ */ new Map();
|
|
11504
|
+
function updateProviderHealthSafe(providerId, healthy, now) {
|
|
11505
|
+
try {
|
|
11506
|
+
updateProviderHealth({
|
|
11507
|
+
providerId,
|
|
11508
|
+
healthy,
|
|
11509
|
+
lastChecked: now,
|
|
11510
|
+
...healthy ? {} : { error: "repeated upstream failures" }
|
|
11511
|
+
});
|
|
11512
|
+
} catch {
|
|
11513
|
+
}
|
|
11514
|
+
}
|
|
11515
|
+
|
|
10914
11516
|
// src/gateway/server/router.ts
|
|
10915
11517
|
function makeServerLog(debugLogPath) {
|
|
10916
11518
|
if (!debugLogPath) return () => {
|
|
@@ -10922,8 +11524,10 @@ async function startServer(options) {
|
|
|
10922
11524
|
silenceSdkWarnings();
|
|
10923
11525
|
const languageModelCache = /* @__PURE__ */ new Map();
|
|
10924
11526
|
const plog = makeServerLog(options.debugLogPath);
|
|
11527
|
+
const budgetConfig = options.budget ?? getServerBudget();
|
|
11528
|
+
const budget = new BudgetTracker(budgetConfig);
|
|
10925
11529
|
const server = createServer2((req, res) => {
|
|
10926
|
-
void routeRequest(req, res, options, languageModelCache, plog);
|
|
11530
|
+
void routeRequest(req, res, options, languageModelCache, plog, budget);
|
|
10927
11531
|
});
|
|
10928
11532
|
await new Promise((resolve, reject) => {
|
|
10929
11533
|
server.once("error", reject);
|
|
@@ -10946,7 +11550,7 @@ async function startServer(options) {
|
|
|
10946
11550
|
})
|
|
10947
11551
|
};
|
|
10948
11552
|
}
|
|
10949
|
-
async function routeRequest(req, res, options, modelCache, plog) {
|
|
11553
|
+
async function routeRequest(req, res, options, modelCache, plog, budget) {
|
|
10950
11554
|
try {
|
|
10951
11555
|
const pathname = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`).pathname;
|
|
10952
11556
|
plog(`${req.method} ${pathname}`);
|
|
@@ -10958,7 +11562,7 @@ async function routeRequest(req, res, options, modelCache, plog) {
|
|
|
10958
11562
|
sendJson(res, 401, { error: { message: "Unauthorized" } });
|
|
10959
11563
|
return;
|
|
10960
11564
|
}
|
|
10961
|
-
const clientId = req.
|
|
11565
|
+
const clientId = req.socket.remoteAddress ?? "unknown";
|
|
10962
11566
|
const rateLimit = checkRateLimit(String(clientId));
|
|
10963
11567
|
if (!rateLimit.allowed) {
|
|
10964
11568
|
sendJson(
|
|
@@ -10986,11 +11590,11 @@ async function routeRequest(req, res, options, modelCache, plog) {
|
|
|
10986
11590
|
return;
|
|
10987
11591
|
}
|
|
10988
11592
|
if (req.method === "POST" && pathname === "/anthropic/v1/messages") {
|
|
10989
|
-
await handleAnthropicMessages(req, res, options, modelCache, plog);
|
|
11593
|
+
await handleAnthropicMessages(req, res, options, modelCache, plog, budget);
|
|
10990
11594
|
return;
|
|
10991
11595
|
}
|
|
10992
11596
|
if (req.method === "POST" && pathname === "/openai/v1/chat/completions") {
|
|
10993
|
-
await handleOpenAIChatCompletions(req, res, options, modelCache, plog);
|
|
11597
|
+
await handleOpenAIChatCompletions(req, res, options, modelCache, plog, budget);
|
|
10994
11598
|
return;
|
|
10995
11599
|
}
|
|
10996
11600
|
sendJson(res, 404, { error: { message: "Not found" } });
|
|
@@ -11005,12 +11609,19 @@ async function routeRequest(req, res, options, modelCache, plog) {
|
|
|
11005
11609
|
}
|
|
11006
11610
|
}
|
|
11007
11611
|
}
|
|
11008
|
-
async function handleAnthropicMessages(req, res, options, modelCache, plog) {
|
|
11612
|
+
async function handleAnthropicMessages(req, res, options, modelCache, plog, budget) {
|
|
11009
11613
|
const body = await readJson(req);
|
|
11010
11614
|
if (!body) {
|
|
11011
11615
|
sendJson(res, 400, { error: { message: "Invalid JSON body" } });
|
|
11012
11616
|
return;
|
|
11013
11617
|
}
|
|
11618
|
+
const budgetCheck = budget.check();
|
|
11619
|
+
if (!budgetCheck.allowed) {
|
|
11620
|
+
sendJson(res, 429, {
|
|
11621
|
+
error: { type: "budget_exceeded", message: budgetCheck.reason ?? "Token budget exceeded" }
|
|
11622
|
+
});
|
|
11623
|
+
return;
|
|
11624
|
+
}
|
|
11014
11625
|
const model = lookupModel(res, options.catalog, body.model, plog);
|
|
11015
11626
|
if (!model) {
|
|
11016
11627
|
plog(`model not found: ${body.model}`);
|
|
@@ -11046,7 +11657,7 @@ async function handleAnthropicMessages(req, res, options, modelCache, plog) {
|
|
|
11046
11657
|
plog(
|
|
11047
11658
|
() => `anthropic-passthrough \u2192 ${messagesUrl} oauth=${isOAuth} stream=${clientWantsStream}`
|
|
11048
11659
|
);
|
|
11049
|
-
await forwardAnthropicMessages(
|
|
11660
|
+
const usage = await forwardAnthropicMessages(
|
|
11050
11661
|
res,
|
|
11051
11662
|
messagesUrl,
|
|
11052
11663
|
forwardBody,
|
|
@@ -11062,6 +11673,16 @@ async function handleAnthropicMessages(req, res, options, modelCache, plog) {
|
|
|
11062
11673
|
model.apiKey = refreshed;
|
|
11063
11674
|
}
|
|
11064
11675
|
);
|
|
11676
|
+
recordUsage({
|
|
11677
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11678
|
+
modelId: getResponseModelId(body.model, model, options),
|
|
11679
|
+
npm: void 0,
|
|
11680
|
+
providerId: model.providerId,
|
|
11681
|
+
app: options.app ?? "gateway",
|
|
11682
|
+
inputTokens: usage.inputTokens,
|
|
11683
|
+
outputTokens: usage.outputTokens
|
|
11684
|
+
});
|
|
11685
|
+
budget.add(usage.inputTokens, usage.outputTokens);
|
|
11065
11686
|
return;
|
|
11066
11687
|
}
|
|
11067
11688
|
if (model.modelFormat === "openai") {
|
|
@@ -11081,8 +11702,10 @@ async function handleAnthropicMessages(req, res, options, modelCache, plog) {
|
|
|
11081
11702
|
);
|
|
11082
11703
|
const npmMaxTools = maxToolsForNpm(model.npm);
|
|
11083
11704
|
const toolCount = Array.isArray(body.tools) ? body.tools.length : 0;
|
|
11705
|
+
let toolTruncationWarning = null;
|
|
11084
11706
|
if (npmMaxTools !== void 0 && toolCount > npmMaxTools) {
|
|
11085
|
-
|
|
11707
|
+
toolTruncationWarning = `tools truncated: ${toolCount} \u2192 ${npmMaxTools} (provider limit)`;
|
|
11708
|
+
plog(toolTruncationWarning);
|
|
11086
11709
|
}
|
|
11087
11710
|
const params = translateRequest2(body, model.npm, {
|
|
11088
11711
|
defaultEffort: anthropicEffortFromRequest(body) ? void 0 : model.defaultEffort,
|
|
@@ -11103,13 +11726,25 @@ async function handleAnthropicMessages(req, res, options, modelCache, plog) {
|
|
|
11103
11726
|
plog(
|
|
11104
11727
|
() => `sdk npm=${model.npm} upstream=${upstreamModelId(model)} responseModel=${responseModelId} stream=${clientWantsStream}`
|
|
11105
11728
|
);
|
|
11729
|
+
if (!clientWantsStream && model.providerId && isProviderUnavailable(model.providerId)) {
|
|
11730
|
+
sendJson(res, 502, {
|
|
11731
|
+
error: {
|
|
11732
|
+
message: `Provider ${model.providerId} temporarily unavailable (recent failures) \u2014 retry shortly.`
|
|
11733
|
+
}
|
|
11734
|
+
});
|
|
11735
|
+
return;
|
|
11736
|
+
}
|
|
11106
11737
|
try {
|
|
11107
11738
|
if (clientWantsStream) {
|
|
11108
11739
|
res.writeHead(200, {
|
|
11109
11740
|
"Content-Type": "text/event-stream",
|
|
11110
11741
|
"Cache-Control": "no-cache",
|
|
11111
|
-
Connection: "keep-alive"
|
|
11742
|
+
Connection: "keep-alive",
|
|
11743
|
+
...toolTruncationWarning ? { "anthropic-warning": toolTruncationWarning } : {}
|
|
11112
11744
|
});
|
|
11745
|
+
if (toolTruncationWarning) res.write(`: ${toolTruncationWarning}
|
|
11746
|
+
|
|
11747
|
+
`);
|
|
11113
11748
|
const usage = await streamAnthropicResponse(
|
|
11114
11749
|
languageModel,
|
|
11115
11750
|
params,
|
|
@@ -11125,32 +11760,46 @@ async function handleAnthropicMessages(req, res, options, modelCache, plog) {
|
|
|
11125
11760
|
inputTokens: usage.inputTokens,
|
|
11126
11761
|
outputTokens: usage.outputTokens
|
|
11127
11762
|
});
|
|
11763
|
+
budget.add(usage.inputTokens, usage.outputTokens);
|
|
11128
11764
|
res.end();
|
|
11129
11765
|
} else {
|
|
11130
|
-
const anthropicResponse = await
|
|
11131
|
-
languageModel,
|
|
11132
|
-
params,
|
|
11133
|
-
responseModelId
|
|
11766
|
+
const anthropicResponse = await withUpstreamRetry(
|
|
11767
|
+
() => generateAnthropicResponse(languageModel, params, responseModelId)
|
|
11134
11768
|
);
|
|
11769
|
+
recordProviderSuccess(model.providerId ?? upstreamModelId(model));
|
|
11770
|
+
const genUsage = anthropicResponse._usage ?? {
|
|
11771
|
+
inputTokens: 0,
|
|
11772
|
+
outputTokens: 0
|
|
11773
|
+
};
|
|
11135
11774
|
recordUsage({
|
|
11136
11775
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11137
11776
|
modelId: responseModelId,
|
|
11138
11777
|
npm: model.npm,
|
|
11139
11778
|
providerId: model.providerId,
|
|
11140
11779
|
app: options.app ?? "gateway",
|
|
11141
|
-
inputTokens:
|
|
11142
|
-
outputTokens:
|
|
11780
|
+
inputTokens: genUsage.inputTokens ?? 0,
|
|
11781
|
+
outputTokens: genUsage.outputTokens ?? 0
|
|
11143
11782
|
});
|
|
11144
|
-
|
|
11783
|
+
budget.add(genUsage.inputTokens ?? 0, genUsage.outputTokens ?? 0);
|
|
11784
|
+
sendJson(
|
|
11785
|
+
res,
|
|
11786
|
+
200,
|
|
11787
|
+
anthropicResponse,
|
|
11788
|
+
toolTruncationWarning ? { "anthropic-warning": toolTruncationWarning } : void 0
|
|
11789
|
+
);
|
|
11145
11790
|
}
|
|
11146
11791
|
} catch (err) {
|
|
11147
11792
|
const message = formatUpstreamError(err);
|
|
11148
11793
|
plog(`sdk error npm=${model.npm} upstream=${upstreamModelId(model)}: ${message}`);
|
|
11794
|
+
const status = upstreamHttpStatus(err);
|
|
11795
|
+
if ((status === 429 || status >= 500) && model.providerId) {
|
|
11796
|
+
recordProviderFailure(model.providerId);
|
|
11797
|
+
plog(`provider ${model.providerId} failure recorded (status ${status})`);
|
|
11798
|
+
}
|
|
11149
11799
|
if (!res.headersSent) {
|
|
11150
|
-
const status = upstreamHttpStatus(err);
|
|
11151
11800
|
sendJson(res, status === 500 ? 502 : status, { error: { message } });
|
|
11152
11801
|
} else {
|
|
11153
|
-
const errorType = anthropicErrorType(
|
|
11802
|
+
const errorType = anthropicErrorType(status);
|
|
11154
11803
|
res.write(
|
|
11155
11804
|
`event: error
|
|
11156
11805
|
data: ${JSON.stringify({ type: "error", error: { type: errorType, message } })}
|
|
@@ -11164,12 +11813,19 @@ data: ${JSON.stringify({ type: "error", error: { type: errorType, message } })}
|
|
|
11164
11813
|
}
|
|
11165
11814
|
sendJson(res, 400, { error: { message: `Unsupported model format: ${model.modelFormat}` } });
|
|
11166
11815
|
}
|
|
11167
|
-
async function handleOpenAIChatCompletions(req, res, options, modelCache, plog) {
|
|
11816
|
+
async function handleOpenAIChatCompletions(req, res, options, modelCache, plog, budget) {
|
|
11168
11817
|
const body = await readJson(req);
|
|
11169
11818
|
if (!body) {
|
|
11170
11819
|
sendJson(res, 400, { error: { message: "Invalid JSON body" } });
|
|
11171
11820
|
return;
|
|
11172
11821
|
}
|
|
11822
|
+
const budgetCheck = budget.check();
|
|
11823
|
+
if (!budgetCheck.allowed) {
|
|
11824
|
+
sendJson(res, 429, {
|
|
11825
|
+
error: { type: "budget_exceeded", message: budgetCheck.reason ?? "Token budget exceeded" }
|
|
11826
|
+
});
|
|
11827
|
+
return;
|
|
11828
|
+
}
|
|
11173
11829
|
const model = lookupModel(res, options.catalog, body.model, plog);
|
|
11174
11830
|
if (!model) return;
|
|
11175
11831
|
if (supportsDirectOpenAIChatCompletions(model)) {
|
|
@@ -11205,6 +11861,14 @@ async function handleOpenAIChatCompletions(req, res, options, modelCache, plog)
|
|
|
11205
11861
|
plog(
|
|
11206
11862
|
() => `sdk-openai npm=${npm} upstream=${upstreamModelId(model)} responseModel=${responseModelId} stream=${clientWantsStream}`
|
|
11207
11863
|
);
|
|
11864
|
+
if (!clientWantsStream && model.providerId && isProviderUnavailable(model.providerId)) {
|
|
11865
|
+
sendJson(res, 502, {
|
|
11866
|
+
error: {
|
|
11867
|
+
message: `Provider ${model.providerId} temporarily unavailable (recent failures) \u2014 retry shortly.`
|
|
11868
|
+
}
|
|
11869
|
+
});
|
|
11870
|
+
return;
|
|
11871
|
+
}
|
|
11208
11872
|
try {
|
|
11209
11873
|
if (clientWantsStream) {
|
|
11210
11874
|
res.writeHead(200, {
|
|
@@ -11227,13 +11891,13 @@ async function handleOpenAIChatCompletions(req, res, options, modelCache, plog)
|
|
|
11227
11891
|
inputTokens: usage.inputTokens,
|
|
11228
11892
|
outputTokens: usage.outputTokens
|
|
11229
11893
|
});
|
|
11894
|
+
budget.add(usage.inputTokens, usage.outputTokens);
|
|
11230
11895
|
res.end();
|
|
11231
11896
|
} else {
|
|
11232
|
-
const { response, usage } = await
|
|
11233
|
-
languageModel,
|
|
11234
|
-
params,
|
|
11235
|
-
responseModelId
|
|
11897
|
+
const { response, usage } = await withUpstreamRetry(
|
|
11898
|
+
() => generateOpenAiResponse(languageModel, params, responseModelId)
|
|
11236
11899
|
);
|
|
11900
|
+
recordProviderSuccess(model.providerId ?? upstreamModelId(model));
|
|
11237
11901
|
recordUsage({
|
|
11238
11902
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11239
11903
|
modelId: responseModelId,
|
|
@@ -11243,16 +11907,21 @@ async function handleOpenAIChatCompletions(req, res, options, modelCache, plog)
|
|
|
11243
11907
|
inputTokens: usage.inputTokens,
|
|
11244
11908
|
outputTokens: usage.outputTokens
|
|
11245
11909
|
});
|
|
11910
|
+
budget.add(usage.inputTokens, usage.outputTokens);
|
|
11246
11911
|
sendJson(res, 200, response);
|
|
11247
11912
|
}
|
|
11248
11913
|
} catch (err) {
|
|
11249
11914
|
const message = formatUpstreamError(err);
|
|
11250
|
-
plog(`sdk error npm=${
|
|
11915
|
+
plog(`sdk error npm=${npm} upstream=${upstreamModelId(model)}: ${message}`);
|
|
11916
|
+
const status = upstreamHttpStatus(err);
|
|
11917
|
+
if ((status === 429 || status >= 500) && model.providerId) {
|
|
11918
|
+
recordProviderFailure(model.providerId);
|
|
11919
|
+
plog(`provider ${model.providerId} failure recorded (status ${status})`);
|
|
11920
|
+
}
|
|
11251
11921
|
if (!res.headersSent) {
|
|
11252
|
-
const status = upstreamHttpStatus(err);
|
|
11253
11922
|
sendJson(res, status === 500 ? 502 : status, { error: { message } });
|
|
11254
11923
|
} else {
|
|
11255
|
-
const errorType = anthropicErrorType(
|
|
11924
|
+
const errorType = anthropicErrorType(status);
|
|
11256
11925
|
res.write(
|
|
11257
11926
|
`event: error
|
|
11258
11927
|
data: ${JSON.stringify({ type: "error", error: { type: errorType, message } })}
|
|
@@ -11373,7 +12042,7 @@ function summarizeServerProviders(models) {
|
|
|
11373
12042
|
}
|
|
11374
12043
|
|
|
11375
12044
|
// src/apps/claude/desktop-launch.ts
|
|
11376
|
-
import { existsSync as
|
|
12045
|
+
import { existsSync as existsSync16 } from "fs";
|
|
11377
12046
|
var CLAUDE_BUNDLE_ID = "com.anthropic.claudefordesktop";
|
|
11378
12047
|
var ClaudeAppLauncher = class extends AppLauncher {
|
|
11379
12048
|
appName = "Claude Desktop";
|
|
@@ -11388,7 +12057,7 @@ var ClaudeAppLauncher = class extends AppLauncher {
|
|
|
11388
12057
|
try {
|
|
11389
12058
|
const out = this.run(`mdfind "kMDItemCFBundleIdentifier == '${CLAUDE_BUNDLE_ID}'"`);
|
|
11390
12059
|
const first = out.split("\n").map((l) => l.trim()).find(Boolean);
|
|
11391
|
-
return first &&
|
|
12060
|
+
return first && existsSync16(first) ? first : null;
|
|
11392
12061
|
} catch {
|
|
11393
12062
|
return null;
|
|
11394
12063
|
}
|
|
@@ -12096,9 +12765,12 @@ function favoriteProviderDisplayName(provider) {
|
|
|
12096
12765
|
}
|
|
12097
12766
|
|
|
12098
12767
|
export {
|
|
12768
|
+
ISSUER,
|
|
12099
12769
|
requestOpenAiDeviceCode,
|
|
12100
12770
|
openAiDeviceCodeUrl,
|
|
12101
12771
|
pollOpenAiDeviceCodeToken,
|
|
12772
|
+
buildOpenAiBrowserAuthUrl,
|
|
12773
|
+
exchangeOpenAiBrowserToken,
|
|
12102
12774
|
buildClaudeCodeBillingSystemLine,
|
|
12103
12775
|
selectBetaFlags,
|
|
12104
12776
|
injectClaudeIdentity,
|
|
@@ -12161,6 +12833,7 @@ export {
|
|
|
12161
12833
|
freeStatusLabel,
|
|
12162
12834
|
refreshModelsDevCacheAsync,
|
|
12163
12835
|
resolveInputTypes,
|
|
12836
|
+
resolveReasoning,
|
|
12164
12837
|
loadPreferences,
|
|
12165
12838
|
savePreferences,
|
|
12166
12839
|
loadLaunchPresets,
|
|
@@ -12192,6 +12865,7 @@ export {
|
|
|
12192
12865
|
requestXaiDeviceCode,
|
|
12193
12866
|
pollXaiDeviceCodeToken,
|
|
12194
12867
|
guiCallbackRedirectUri,
|
|
12868
|
+
startCallbackServer,
|
|
12195
12869
|
ANTIGRAVITY_BASE_URLS,
|
|
12196
12870
|
buildAntigravityAuthUrl,
|
|
12197
12871
|
completeAntigravityExchange,
|
|
@@ -12249,6 +12923,7 @@ export {
|
|
|
12249
12923
|
aliasModelId,
|
|
12250
12924
|
startProxyCatalog,
|
|
12251
12925
|
startProxy,
|
|
12926
|
+
dedupeByKey,
|
|
12252
12927
|
routableModelsForTarget,
|
|
12253
12928
|
providersForTarget,
|
|
12254
12929
|
fetchProviderCatalog,
|
|
@@ -12312,4 +12987,4 @@ export {
|
|
|
12312
12987
|
runServerCommand,
|
|
12313
12988
|
favoriteProviderDisplayName
|
|
12314
12989
|
};
|
|
12315
|
-
//# sourceMappingURL=chunk-
|
|
12990
|
+
//# sourceMappingURL=chunk-NU4KMZIM.js.map
|