@jacobbd/relay-ai 0.7.0 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/{chunk-EJONCU3B.js → chunk-HXGZ4CTV.js} +13 -1
- package/dist/chunk-HXGZ4CTV.js.map +1 -0
- package/dist/{chunk-IYYLLN5T.js → chunk-LNQ46VW7.js} +873 -18
- package/dist/chunk-LNQ46VW7.js.map +1 -0
- package/dist/cli.js +429 -754
- package/dist/cli.js.map +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/{provider-templates-BPGB5V2L.js → provider-templates-4H3C4DRL.js} +2 -2
- package/dist/ui/public/app.js +71 -15
- package/dist/ui/public/provider-model-browser.js +4 -1
- package/dist/ui/public/style.css +7 -0
- package/dist/{ui-command-3CWMSARO.js → ui-command-5VEFZ5GM.js} +32 -11
- package/dist/ui-command-5VEFZ5GM.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-EJONCU3B.js.map +0 -1
- package/dist/chunk-IYYLLN5T.js.map +0 -1
- package/dist/ui-command-3CWMSARO.js.map +0 -1
- /package/dist/{provider-templates-BPGB5V2L.js.map → provider-templates-4H3C4DRL.js.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
appendCodexBodyDump,
|
|
18
18
|
authenticateProvider,
|
|
19
19
|
buildAntigravityChildEnv,
|
|
20
|
+
buildAntigravityRoutes,
|
|
20
21
|
buildAppCatalogFile,
|
|
21
22
|
buildCatalogFile,
|
|
22
23
|
buildCatalogRoutes,
|
|
@@ -25,6 +26,8 @@ import {
|
|
|
25
26
|
buildCodexAppRootConfig,
|
|
26
27
|
buildHttpProxyRoutes,
|
|
27
28
|
buildImportProviderList,
|
|
29
|
+
buildListExperimentsResponse,
|
|
30
|
+
buildListModelConfigsResponse,
|
|
28
31
|
buildVertexRuntimeConfig,
|
|
29
32
|
cachedModelToLocal,
|
|
30
33
|
catalogEntryFromModel,
|
|
@@ -42,6 +45,7 @@ import {
|
|
|
42
45
|
effectiveProviderBaseUrl,
|
|
43
46
|
effortProviderOptions,
|
|
44
47
|
encodeToolUseId,
|
|
48
|
+
evaluateAgySwitchCompatibility,
|
|
45
49
|
extractApiKey,
|
|
46
50
|
favoriteProviderDisplayName,
|
|
47
51
|
fetchAnthropicModels,
|
|
@@ -56,6 +60,8 @@ import {
|
|
|
56
60
|
fmtProvider,
|
|
57
61
|
fmtProviderBracket,
|
|
58
62
|
fmtUrl,
|
|
63
|
+
formatAnthropicModelEntry,
|
|
64
|
+
formatAnthropicModelList,
|
|
59
65
|
formatCodexModelLabel,
|
|
60
66
|
formatRegistryAuthLabel,
|
|
61
67
|
formatUpdateNotification,
|
|
@@ -73,6 +79,7 @@ import {
|
|
|
73
79
|
hasApplicationDefaultCredentials,
|
|
74
80
|
httpProxyModelId,
|
|
75
81
|
injectClaudeIdentity,
|
|
82
|
+
injectRelayModels,
|
|
76
83
|
isClaudeAppRunning,
|
|
77
84
|
isCodexAppRunning,
|
|
78
85
|
isFreeStatus,
|
|
@@ -92,6 +99,7 @@ import {
|
|
|
92
99
|
makeRouteResolver,
|
|
93
100
|
makeTraceLogger,
|
|
94
101
|
maxToolsForNpm,
|
|
102
|
+
meetsContextFloor,
|
|
95
103
|
migrateGlobalOpencodeCredential,
|
|
96
104
|
migrateLegacyCloudProviders,
|
|
97
105
|
modelSelectOption,
|
|
@@ -137,6 +145,7 @@ import {
|
|
|
137
145
|
resolveProviderTemplate,
|
|
138
146
|
resolveProvidersForDisplay,
|
|
139
147
|
resolveRefreshCredential,
|
|
148
|
+
resolveRelayCatalogSlots,
|
|
140
149
|
routableModelsForTarget,
|
|
141
150
|
routeLookupIds,
|
|
142
151
|
runServerCommand,
|
|
@@ -165,14 +174,14 @@ import {
|
|
|
165
174
|
validateCustomEndpointUrl,
|
|
166
175
|
writeSecureLogLine,
|
|
167
176
|
zenRegistryStub
|
|
168
|
-
} from "./chunk-
|
|
177
|
+
} from "./chunk-LNQ46VW7.js";
|
|
169
178
|
import {
|
|
170
179
|
filterTemplates,
|
|
171
180
|
init_provider_templates,
|
|
172
181
|
listAddableTemplates,
|
|
173
182
|
listSupportedTemplates,
|
|
174
183
|
listVisibleOAuthTemplates
|
|
175
|
-
} from "./chunk-
|
|
184
|
+
} from "./chunk-HXGZ4CTV.js";
|
|
176
185
|
|
|
177
186
|
// src/cli.ts
|
|
178
187
|
import pc12 from "picocolors";
|
|
@@ -208,12 +217,12 @@ function findOpencodeBinary() {
|
|
|
208
217
|
stdio: ["pipe", "pipe", "pipe"]
|
|
209
218
|
});
|
|
210
219
|
const lines = result.trim().split("\n").map((l) => l.trim()).filter(Boolean);
|
|
211
|
-
const
|
|
212
|
-
if (
|
|
220
|
+
const path3 = (isWindows ? lines.find((l) => l.toLowerCase().endsWith(".cmd")) : null) ?? lines[0];
|
|
221
|
+
if (path3) return path3;
|
|
213
222
|
} catch {
|
|
214
223
|
}
|
|
215
|
-
for (const
|
|
216
|
-
if (existsSync(
|
|
224
|
+
for (const path3 of OPENCODE_FALLBACK_PATHS) {
|
|
225
|
+
if (existsSync(path3)) return path3;
|
|
217
226
|
}
|
|
218
227
|
return null;
|
|
219
228
|
}
|
|
@@ -589,7 +598,7 @@ async function resolveOrCollectApiKey(simulate = false, trace = false) {
|
|
|
589
598
|
if (isLinux && !simulate) {
|
|
590
599
|
secretServiceAvailable = await isSecretServiceAvailable();
|
|
591
600
|
}
|
|
592
|
-
const { display, path:
|
|
601
|
+
const { display, path: path3 } = detectShellProfile();
|
|
593
602
|
const saveOptions = (() => {
|
|
594
603
|
if (isMac) {
|
|
595
604
|
return [
|
|
@@ -648,9 +657,9 @@ async function resolveOrCollectApiKey(simulate = false, trace = false) {
|
|
|
648
657
|
if (await saveToCredentialStore(trimmedKey)) {
|
|
649
658
|
try {
|
|
650
659
|
const autoLoadLine = `export OPENCODE_API_KEY="$(security find-generic-password -s relay-ai -a ${GLOBAL_OPENCODE_KEYRING_ACCOUNT} -w 2>/dev/null)"`;
|
|
651
|
-
const existing = existsSync2(
|
|
660
|
+
const existing = existsSync2(path3) ? readFileSync(path3, "utf8") : "";
|
|
652
661
|
if (!existing.includes(autoLoadLine)) {
|
|
653
|
-
appendFileSync(
|
|
662
|
+
appendFileSync(path3, `
|
|
654
663
|
# relay-ai: load API key from macOS Keychain
|
|
655
664
|
${autoLoadLine}
|
|
656
665
|
`);
|
|
@@ -685,9 +694,9 @@ ${autoLoadLine}
|
|
|
685
694
|
}
|
|
686
695
|
} else if (saveChoice === "profile") {
|
|
687
696
|
try {
|
|
688
|
-
if (!existsSync2(
|
|
697
|
+
if (!existsSync2(path3)) appendFileSync(path3, "");
|
|
689
698
|
const escapedKey = trimmedKey.replace(/'/g, "'\\''");
|
|
690
|
-
appendFileSync(
|
|
699
|
+
appendFileSync(path3, `
|
|
691
700
|
export OPENCODE_API_KEY='${escapedKey}'
|
|
692
701
|
`);
|
|
693
702
|
p.log.success(`Key saved to ${display} \u2014 active now and in all future terminals.`);
|
|
@@ -1538,7 +1547,16 @@ async function runTemplateAddFlow() {
|
|
|
1538
1547
|
]);
|
|
1539
1548
|
}
|
|
1540
1549
|
let baseUrlOverride;
|
|
1541
|
-
if (template.
|
|
1550
|
+
if (template.accountIdPrompt) {
|
|
1551
|
+
const accountInput = await p5.text({
|
|
1552
|
+
message: template.accountIdPrompt,
|
|
1553
|
+
placeholder: "e.g. 4ff191dac2d0bd7538cb1c9126594de3",
|
|
1554
|
+
validate: (v) => v.trim() ? void 0 : "Account ID is required"
|
|
1555
|
+
});
|
|
1556
|
+
if (p5.isCancel(accountInput)) return 0;
|
|
1557
|
+
const accountId = String(accountInput).trim();
|
|
1558
|
+
baseUrlOverride = template.defaultBaseUrl?.replace("{ACCOUNT_ID}", accountId);
|
|
1559
|
+
} else if (template.urlPrompt) {
|
|
1542
1560
|
const urlInput = await p5.text({
|
|
1543
1561
|
message: template.urlPrompt,
|
|
1544
1562
|
initialValue: template.defaultBaseUrl,
|
|
@@ -3796,11 +3814,11 @@ function ownedOverlayPaths(env = process.env) {
|
|
|
3796
3814
|
}
|
|
3797
3815
|
return paths;
|
|
3798
3816
|
}
|
|
3799
|
-
function atomicWriteFile(
|
|
3800
|
-
mkdirSync(dirname(
|
|
3801
|
-
const tmp = `${
|
|
3817
|
+
function atomicWriteFile(path3, content) {
|
|
3818
|
+
mkdirSync(dirname(path3), { recursive: true });
|
|
3819
|
+
const tmp = `${path3}.tmp.${process.pid}`;
|
|
3802
3820
|
writeFileSync(tmp, content, "utf8");
|
|
3803
|
-
renameSync(tmp,
|
|
3821
|
+
renameSync(tmp, path3);
|
|
3804
3822
|
}
|
|
3805
3823
|
function rotateBackups(filePath, env = process.env) {
|
|
3806
3824
|
if (!existsSync3(filePath)) return;
|
|
@@ -3818,24 +3836,24 @@ function rotateBackups(filePath, env = process.env) {
|
|
|
3818
3836
|
}
|
|
3819
3837
|
}
|
|
3820
3838
|
}
|
|
3821
|
-
function writeOverlayFile(
|
|
3822
|
-
rotateBackups(
|
|
3823
|
-
atomicWriteFile(
|
|
3839
|
+
function writeOverlayFile(path3, content, env = process.env) {
|
|
3840
|
+
rotateBackups(path3, env);
|
|
3841
|
+
atomicWriteFile(path3, content);
|
|
3824
3842
|
}
|
|
3825
3843
|
function readSessionLock(env = process.env) {
|
|
3826
|
-
const
|
|
3827
|
-
if (!existsSync3(
|
|
3844
|
+
const path3 = getSessionLockPath(env);
|
|
3845
|
+
if (!existsSync3(path3)) return null;
|
|
3828
3846
|
try {
|
|
3829
|
-
const parsed = JSON.parse(readFileSync2(
|
|
3847
|
+
const parsed = JSON.parse(readFileSync2(path3, "utf8"));
|
|
3830
3848
|
if (typeof parsed.pid === "number" && typeof parsed.startedAt === "string") return parsed;
|
|
3831
3849
|
} catch {
|
|
3832
3850
|
}
|
|
3833
3851
|
return null;
|
|
3834
3852
|
}
|
|
3835
3853
|
function writeSessionLock(lock, env = process.env) {
|
|
3836
|
-
const
|
|
3854
|
+
const path3 = getSessionLockPath(env);
|
|
3837
3855
|
mkdirSync(getRelayAiCodexDir(env), { recursive: true });
|
|
3838
|
-
atomicWriteFile(
|
|
3856
|
+
atomicWriteFile(path3, `${JSON.stringify(lock, null, 2)}
|
|
3839
3857
|
`);
|
|
3840
3858
|
}
|
|
3841
3859
|
function isProcessAlive(pid) {
|
|
@@ -3852,11 +3870,11 @@ function isConcurrentSession(lock) {
|
|
|
3852
3870
|
}
|
|
3853
3871
|
function restoreCodexOverlay(env = process.env) {
|
|
3854
3872
|
const removed = [];
|
|
3855
|
-
for (const
|
|
3856
|
-
if (!existsSync3(
|
|
3873
|
+
for (const path3 of ownedOverlayPaths(env)) {
|
|
3874
|
+
if (!existsSync3(path3)) continue;
|
|
3857
3875
|
try {
|
|
3858
|
-
rmSync(
|
|
3859
|
-
removed.push(
|
|
3876
|
+
rmSync(path3, { force: true });
|
|
3877
|
+
removed.push(path3);
|
|
3860
3878
|
} catch {
|
|
3861
3879
|
}
|
|
3862
3880
|
}
|
|
@@ -4003,16 +4021,16 @@ function findCodexBinary() {
|
|
|
4003
4021
|
}
|
|
4004
4022
|
function selectCodexBinary(candidates, exists, canRun) {
|
|
4005
4023
|
const seen = /* @__PURE__ */ new Set();
|
|
4006
|
-
for (const
|
|
4007
|
-
if (!
|
|
4008
|
-
seen.add(
|
|
4009
|
-
if (exists(
|
|
4024
|
+
for (const path3 of candidates) {
|
|
4025
|
+
if (!path3 || seen.has(path3)) continue;
|
|
4026
|
+
seen.add(path3);
|
|
4027
|
+
if (exists(path3) && canRun(path3)) return path3;
|
|
4010
4028
|
}
|
|
4011
4029
|
return null;
|
|
4012
4030
|
}
|
|
4013
|
-
function canRunCodexBinary(
|
|
4031
|
+
function canRunCodexBinary(path3) {
|
|
4014
4032
|
try {
|
|
4015
|
-
execFileSync(
|
|
4033
|
+
execFileSync(path3, ["--version"], {
|
|
4016
4034
|
encoding: "utf8",
|
|
4017
4035
|
stdio: ["ignore", "pipe", "pipe"],
|
|
4018
4036
|
timeout: 5e3,
|
|
@@ -6468,655 +6486,6 @@ function formatCloudCodeChunk(opts) {
|
|
|
6468
6486
|
};
|
|
6469
6487
|
}
|
|
6470
6488
|
|
|
6471
|
-
// src/antigravity/slot-registry.ts
|
|
6472
|
-
var AGY_SLOT_VALIDATION_SOURCE = "AGY CLI 1.0.10 / Antigravity IDE 2.1.1 fixture capture 2026-06-23";
|
|
6473
|
-
var AGY_NATIVE_SLOT_REGISTRY = [
|
|
6474
|
-
{
|
|
6475
|
-
slotId: "gemini-3.5-flash-low",
|
|
6476
|
-
model: "MODEL_PLACEHOLDER_M20",
|
|
6477
|
-
role: "agent-switch",
|
|
6478
|
-
status: "validated",
|
|
6479
|
-
validatedWith: AGY_SLOT_VALIDATION_SOURCE
|
|
6480
|
-
},
|
|
6481
|
-
{
|
|
6482
|
-
slotId: "gemini-3.5-flash-extra-low",
|
|
6483
|
-
model: "MODEL_PLACEHOLDER_M187",
|
|
6484
|
-
role: "agent-switch",
|
|
6485
|
-
status: "validated",
|
|
6486
|
-
validatedWith: AGY_SLOT_VALIDATION_SOURCE
|
|
6487
|
-
},
|
|
6488
|
-
{
|
|
6489
|
-
slotId: "gemini-3.1-pro-low",
|
|
6490
|
-
model: "MODEL_PLACEHOLDER_M36",
|
|
6491
|
-
role: "agent-switch",
|
|
6492
|
-
status: "validated",
|
|
6493
|
-
validatedWith: AGY_SLOT_VALIDATION_SOURCE
|
|
6494
|
-
},
|
|
6495
|
-
{
|
|
6496
|
-
slotId: "gemini-pro-agent",
|
|
6497
|
-
model: "MODEL_PLACEHOLDER_M16",
|
|
6498
|
-
role: "agent-switch",
|
|
6499
|
-
status: "validated",
|
|
6500
|
-
validatedWith: AGY_SLOT_VALIDATION_SOURCE
|
|
6501
|
-
},
|
|
6502
|
-
{
|
|
6503
|
-
slotId: "claude-sonnet-4-6",
|
|
6504
|
-
model: "MODEL_PLACEHOLDER_M35",
|
|
6505
|
-
role: "agent-switch",
|
|
6506
|
-
status: "validated",
|
|
6507
|
-
validatedWith: AGY_SLOT_VALIDATION_SOURCE
|
|
6508
|
-
},
|
|
6509
|
-
{
|
|
6510
|
-
slotId: "claude-opus-4-6-thinking",
|
|
6511
|
-
model: "MODEL_PLACEHOLDER_M26",
|
|
6512
|
-
role: "agent-switch",
|
|
6513
|
-
status: "validated",
|
|
6514
|
-
validatedWith: AGY_SLOT_VALIDATION_SOURCE
|
|
6515
|
-
},
|
|
6516
|
-
{
|
|
6517
|
-
slotId: "gpt-oss-120b-medium",
|
|
6518
|
-
model: "MODEL_OPENAI_GPT_OSS_120B_MEDIUM",
|
|
6519
|
-
role: "agent-switch",
|
|
6520
|
-
status: "validated",
|
|
6521
|
-
validatedWith: AGY_SLOT_VALIDATION_SOURCE
|
|
6522
|
-
},
|
|
6523
|
-
{
|
|
6524
|
-
slotId: "gemini-3-flash-agent",
|
|
6525
|
-
model: "MODEL_PLACEHOLDER_M132",
|
|
6526
|
-
role: "cascade-plan",
|
|
6527
|
-
status: "reserved",
|
|
6528
|
-
validatedWith: AGY_SLOT_VALIDATION_SOURCE,
|
|
6529
|
-
notes: "Visible in agentModelSorts, but reserved for cascade plan construction."
|
|
6530
|
-
},
|
|
6531
|
-
{
|
|
6532
|
-
slotId: "gemini-2.5-flash",
|
|
6533
|
-
model: "MODEL_GOOGLE_GEMINI_2_5_FLASH",
|
|
6534
|
-
role: "cascade-intent",
|
|
6535
|
-
status: "reserved",
|
|
6536
|
-
validatedWith: AGY_SLOT_VALIDATION_SOURCE
|
|
6537
|
-
},
|
|
6538
|
-
{
|
|
6539
|
-
slotId: "gemini-2.5-flash-lite",
|
|
6540
|
-
model: "MODEL_GOOGLE_GEMINI_2_5_FLASH_LITE",
|
|
6541
|
-
role: "cascade-fallback",
|
|
6542
|
-
status: "reserved",
|
|
6543
|
-
validatedWith: AGY_SLOT_VALIDATION_SOURCE
|
|
6544
|
-
},
|
|
6545
|
-
{
|
|
6546
|
-
slotId: "gemini-3.1-pro-high",
|
|
6547
|
-
model: "MODEL_PLACEHOLDER_M37",
|
|
6548
|
-
role: "agent-switch",
|
|
6549
|
-
status: "candidate",
|
|
6550
|
-
validatedWith: AGY_SLOT_VALIDATION_SOURCE,
|
|
6551
|
-
notes: "Model-shaped fixture entry; requires live switching proof before promotion."
|
|
6552
|
-
},
|
|
6553
|
-
{
|
|
6554
|
-
slotId: "gemini-2.5-pro",
|
|
6555
|
-
model: "MODEL_GOOGLE_GEMINI_2_5_PRO",
|
|
6556
|
-
role: "agent-switch",
|
|
6557
|
-
status: "candidate",
|
|
6558
|
-
validatedWith: AGY_SLOT_VALIDATION_SOURCE,
|
|
6559
|
-
notes: "Model-shaped fixture entry; requires live switching proof before promotion."
|
|
6560
|
-
},
|
|
6561
|
-
{
|
|
6562
|
-
slotId: "gemini-2.5-flash-thinking",
|
|
6563
|
-
model: "MODEL_GOOGLE_GEMINI_2_5_FLASH_THINKING",
|
|
6564
|
-
role: "agent-switch",
|
|
6565
|
-
status: "candidate",
|
|
6566
|
-
validatedWith: AGY_SLOT_VALIDATION_SOURCE,
|
|
6567
|
-
notes: "Model-shaped fixture entry; requires live switching proof before promotion."
|
|
6568
|
-
},
|
|
6569
|
-
{
|
|
6570
|
-
slotId: "gemini-3-flash",
|
|
6571
|
-
model: "MODEL_PLACEHOLDER_M18",
|
|
6572
|
-
role: "command",
|
|
6573
|
-
status: "candidate",
|
|
6574
|
-
validatedWith: AGY_SLOT_VALIDATION_SOURCE,
|
|
6575
|
-
notes: "Command model in the fixture; not switch-safe without live proof."
|
|
6576
|
-
},
|
|
6577
|
-
{
|
|
6578
|
-
slotId: "gemini-3.1-flash-lite",
|
|
6579
|
-
model: "MODEL_PLACEHOLDER_M50",
|
|
6580
|
-
role: "cascade-checkpoint",
|
|
6581
|
-
status: "candidate",
|
|
6582
|
-
validatedWith: AGY_SLOT_VALIDATION_SOURCE,
|
|
6583
|
-
notes: "Checkpoint/search/commit slot; route as helper until live proof exists."
|
|
6584
|
-
},
|
|
6585
|
-
{
|
|
6586
|
-
slotId: "gemini-3.1-flash-image",
|
|
6587
|
-
model: "MODEL_PLACEHOLDER_M21",
|
|
6588
|
-
role: "image",
|
|
6589
|
-
status: "candidate",
|
|
6590
|
-
validatedWith: AGY_SLOT_VALIDATION_SOURCE,
|
|
6591
|
-
notes: "Image generation slot; not switch-safe without live proof."
|
|
6592
|
-
},
|
|
6593
|
-
{
|
|
6594
|
-
slotId: "tab_jump_flash_lite_preview",
|
|
6595
|
-
model: "MODEL_PLACEHOLDER_M28",
|
|
6596
|
-
role: "tab",
|
|
6597
|
-
status: "unsafe",
|
|
6598
|
-
validatedWith: AGY_SLOT_VALIDATION_SOURCE
|
|
6599
|
-
},
|
|
6600
|
-
{
|
|
6601
|
-
slotId: "tab_flash_lite_preview",
|
|
6602
|
-
model: "MODEL_PLACEHOLDER_M19",
|
|
6603
|
-
role: "tab",
|
|
6604
|
-
status: "unsafe",
|
|
6605
|
-
validatedWith: AGY_SLOT_VALIDATION_SOURCE
|
|
6606
|
-
},
|
|
6607
|
-
{
|
|
6608
|
-
slotId: "chat_20706",
|
|
6609
|
-
model: "MODEL_CHAT_20706",
|
|
6610
|
-
role: "chat",
|
|
6611
|
-
status: "unsafe",
|
|
6612
|
-
validatedWith: AGY_SLOT_VALIDATION_SOURCE
|
|
6613
|
-
},
|
|
6614
|
-
{
|
|
6615
|
-
slotId: "chat_23310",
|
|
6616
|
-
model: "MODEL_CHAT_23310",
|
|
6617
|
-
role: "chat",
|
|
6618
|
-
status: "unsafe",
|
|
6619
|
-
validatedWith: AGY_SLOT_VALIDATION_SOURCE
|
|
6620
|
-
}
|
|
6621
|
-
];
|
|
6622
|
-
var KNOWN_COMPATIBLE_AGY_VERSIONS = /* @__PURE__ */ new Set(["1.0.10"]);
|
|
6623
|
-
var KNOWN_INCOMPATIBLE_AGY_VERSIONS = /* @__PURE__ */ new Set(["1.0.9"]);
|
|
6624
|
-
function withFixtureModel(definition, model) {
|
|
6625
|
-
return model === definition.model ? definition : { ...definition, model };
|
|
6626
|
-
}
|
|
6627
|
-
function assertNoDuplicateSwitchEnums(fixture, definitions) {
|
|
6628
|
-
const seen = /* @__PURE__ */ new Map();
|
|
6629
|
-
for (const definition of definitions) {
|
|
6630
|
-
if (definition.status !== "validated") continue;
|
|
6631
|
-
const actualModel = fixture.models[definition.slotId]?.model;
|
|
6632
|
-
if (!actualModel) continue;
|
|
6633
|
-
const previousSlotId = seen.get(actualModel);
|
|
6634
|
-
if (previousSlotId) {
|
|
6635
|
-
throw new Error(
|
|
6636
|
-
`Duplicate AGY switch slot enum ${actualModel}: ${previousSlotId} and ${definition.slotId}`
|
|
6637
|
-
);
|
|
6638
|
-
}
|
|
6639
|
-
seen.set(actualModel, definition.slotId);
|
|
6640
|
-
}
|
|
6641
|
-
}
|
|
6642
|
-
function validateAgySlotRegistry(fixture) {
|
|
6643
|
-
assertNoDuplicateSwitchEnums(fixture, AGY_NATIVE_SLOT_REGISTRY);
|
|
6644
|
-
const switchSlots = [];
|
|
6645
|
-
const reservedSlots = [];
|
|
6646
|
-
const candidateSlots = [];
|
|
6647
|
-
const warnings = [];
|
|
6648
|
-
for (const definition of AGY_NATIVE_SLOT_REGISTRY) {
|
|
6649
|
-
const entry = fixture.models[definition.slotId];
|
|
6650
|
-
if (!entry) {
|
|
6651
|
-
if (definition.status === "validated" || definition.status === "reserved") {
|
|
6652
|
-
warnings.push(`AGY slot ${definition.slotId} missing from fixture`);
|
|
6653
|
-
}
|
|
6654
|
-
continue;
|
|
6655
|
-
}
|
|
6656
|
-
if (entry.model !== definition.model) {
|
|
6657
|
-
warnings.push(
|
|
6658
|
-
`AGY slot ${definition.slotId} expected ${definition.model} but fixture has ${entry.model}`
|
|
6659
|
-
);
|
|
6660
|
-
continue;
|
|
6661
|
-
}
|
|
6662
|
-
if (definition.status === "validated") {
|
|
6663
|
-
switchSlots.push(withFixtureModel(definition, entry.model));
|
|
6664
|
-
} else if (definition.status === "reserved") {
|
|
6665
|
-
reservedSlots.push(withFixtureModel(definition, entry.model));
|
|
6666
|
-
} else if (definition.status === "candidate") {
|
|
6667
|
-
candidateSlots.push(withFixtureModel(definition, entry.model));
|
|
6668
|
-
}
|
|
6669
|
-
}
|
|
6670
|
-
return { switchSlots, reservedSlots, candidateSlots, warnings };
|
|
6671
|
-
}
|
|
6672
|
-
function getValidatedAgySwitchSlots(fixture) {
|
|
6673
|
-
return validateAgySlotRegistry(fixture).switchSlots;
|
|
6674
|
-
}
|
|
6675
|
-
function evaluateAgySwitchCompatibility(opts) {
|
|
6676
|
-
const validation = validateAgySlotRegistry(opts.fixture);
|
|
6677
|
-
const shapeMatches = validation.warnings.length === 0 && validation.switchSlots.length > 0;
|
|
6678
|
-
const warnings = [];
|
|
6679
|
-
if (opts.versionReadError) {
|
|
6680
|
-
warnings.push(`Could not read agy --version (${opts.versionReadError}); validating AGY fixture shape instead.`);
|
|
6681
|
-
}
|
|
6682
|
-
if (opts.version && KNOWN_INCOMPATIBLE_AGY_VERSIONS.has(opts.version)) {
|
|
6683
|
-
return {
|
|
6684
|
-
mode: "single-model",
|
|
6685
|
-
validatedSwitchSlotCount: validation.switchSlots.length,
|
|
6686
|
-
warnings: [
|
|
6687
|
-
...warnings,
|
|
6688
|
-
`Known-incompatible AGY version ${opts.version}; falling back to single-model mode.`
|
|
6689
|
-
]
|
|
6690
|
-
};
|
|
6691
|
-
}
|
|
6692
|
-
if (!shapeMatches) {
|
|
6693
|
-
return {
|
|
6694
|
-
mode: "single-model",
|
|
6695
|
-
validatedSwitchSlotCount: validation.switchSlots.length,
|
|
6696
|
-
warnings: [
|
|
6697
|
-
...warnings,
|
|
6698
|
-
...validation.warnings,
|
|
6699
|
-
"AGY fixture shape does not match the validated slot registry; falling back to single-model mode."
|
|
6700
|
-
]
|
|
6701
|
-
};
|
|
6702
|
-
}
|
|
6703
|
-
if (opts.version && !KNOWN_COMPATIBLE_AGY_VERSIONS.has(opts.version)) {
|
|
6704
|
-
warnings.push(`Unvalidated AGY version ${opts.version}; fixture shape matches, so multi-model switching remains enabled.`);
|
|
6705
|
-
} else if (!opts.version && !opts.versionReadError) {
|
|
6706
|
-
warnings.push("AGY version is unknown; fixture shape matches, so multi-model switching remains enabled.");
|
|
6707
|
-
}
|
|
6708
|
-
return {
|
|
6709
|
-
mode: "multi-model",
|
|
6710
|
-
validatedSwitchSlotCount: validation.switchSlots.length,
|
|
6711
|
-
warnings
|
|
6712
|
-
};
|
|
6713
|
-
}
|
|
6714
|
-
|
|
6715
|
-
// src/antigravity/catalog.ts
|
|
6716
|
-
var RELAY_CASCADE_PLAN_MODEL = "MODEL_PLACEHOLDER_M132";
|
|
6717
|
-
var RELAY_AGENT_PLACEHOLDER = "MODEL_PLACEHOLDER_M20";
|
|
6718
|
-
var RELAY_CASCADE_CHECKPOINT_MODEL = "MODEL_PLACEHOLDER_M50";
|
|
6719
|
-
var RELAY_CASCADE_INTENT_MODEL = "MODEL_GOOGLE_GEMINI_2_5_FLASH";
|
|
6720
|
-
var RELAY_CASCADE_ANCHOR_ID = "gemini-3.5-flash-low";
|
|
6721
|
-
var RELAY_CASCADE_PLAN_ANCHOR_ID = "gemini-3-flash-agent";
|
|
6722
|
-
var RELAY_CASCADE_FALLBACK_ID = "gemini-2.5-flash-lite";
|
|
6723
|
-
var RELAY_CASCADE_INTENT_MODEL_ID = "gemini-2.5-flash";
|
|
6724
|
-
function withCascadeCheckpointer(entry, maxTokenLimit = 128e3) {
|
|
6725
|
-
const tokenThreshold = Math.min(5e4, Math.floor(maxTokenLimit * 0.75));
|
|
6726
|
-
const existingModelExperiments = entry.modelExperiments;
|
|
6727
|
-
entry.modelExperiments = {
|
|
6728
|
-
...existingModelExperiments,
|
|
6729
|
-
experiments: {
|
|
6730
|
-
...existingModelExperiments?.experiments ?? {},
|
|
6731
|
-
CASCADE_USE_EXPERIMENT_CHECKPOINTER: {
|
|
6732
|
-
stringValue: JSON.stringify({
|
|
6733
|
-
strategy: "CHECKPOINT_STRATEGY_SAME_MODEL",
|
|
6734
|
-
max_token_limit: String(maxTokenLimit),
|
|
6735
|
-
token_threshold: String(tokenThreshold),
|
|
6736
|
-
max_overhead_ratio: "0.15",
|
|
6737
|
-
moving_window_size: "1",
|
|
6738
|
-
enabled: true,
|
|
6739
|
-
max_output_tokens: "16384",
|
|
6740
|
-
checkpoint_model: RELAY_CASCADE_CHECKPOINT_MODEL,
|
|
6741
|
-
use_last_planner_model: true,
|
|
6742
|
-
is_sync: true,
|
|
6743
|
-
max_user_requests: 10,
|
|
6744
|
-
include_last_user_message: true,
|
|
6745
|
-
include_conversation_log: false,
|
|
6746
|
-
include_running_task_snapshots: true,
|
|
6747
|
-
include_subagent_snapshots: true,
|
|
6748
|
-
include_artifact_snapshots: true,
|
|
6749
|
-
retry_config: {
|
|
6750
|
-
max_retries: 0,
|
|
6751
|
-
initial_sleep_duration_ms: 1e3,
|
|
6752
|
-
exponential_multiplier: 2,
|
|
6753
|
-
include_error_feedback: false
|
|
6754
|
-
}
|
|
6755
|
-
})
|
|
6756
|
-
}
|
|
6757
|
-
}
|
|
6758
|
-
};
|
|
6759
|
-
return entry;
|
|
6760
|
-
}
|
|
6761
|
-
function applyRouteContextBounds(entry, route) {
|
|
6762
|
-
const maxTokenLimit = route.contextWindow ?? 128e3;
|
|
6763
|
-
const maxOutputTokens = Math.min(entry.maxOutputTokens ?? 65536, maxTokenLimit);
|
|
6764
|
-
const checkpointTokenLimit = Math.min(
|
|
6765
|
-
128e3,
|
|
6766
|
-
Math.max(1, maxTokenLimit - maxOutputTokens)
|
|
6767
|
-
);
|
|
6768
|
-
entry.maxTokens = maxTokenLimit;
|
|
6769
|
-
entry.maxOutputTokens = maxOutputTokens;
|
|
6770
|
-
return withCascadeCheckpointer(entry, checkpointTokenLimit);
|
|
6771
|
-
}
|
|
6772
|
-
var RELAY_CASCADE_FALLBACK_ENTRY = withCascadeCheckpointer({
|
|
6773
|
-
displayName: "Gemini 3.1 Flash Lite",
|
|
6774
|
-
model: "MODEL_GOOGLE_GEMINI_2_5_FLASH_LITE",
|
|
6775
|
-
apiProvider: "API_PROVIDER_GOOGLE_GEMINI",
|
|
6776
|
-
modelProvider: "MODEL_PROVIDER_GOOGLE",
|
|
6777
|
-
tokenizerType: "LLAMA_WITH_SPECIAL",
|
|
6778
|
-
maxTokens: 1048576,
|
|
6779
|
-
maxOutputTokens: 65535,
|
|
6780
|
-
quotaInfo: { remainingFraction: 1 }
|
|
6781
|
-
});
|
|
6782
|
-
var RELAY_CASCADE_INTENT_MODEL_ENTRY = withCascadeCheckpointer({
|
|
6783
|
-
...RELAY_CASCADE_FALLBACK_ENTRY,
|
|
6784
|
-
model: RELAY_CASCADE_INTENT_MODEL
|
|
6785
|
-
});
|
|
6786
|
-
function planRelayCatalogSlots(catalog, routes, templateKey) {
|
|
6787
|
-
const validation = validateAgySlotRegistry(catalog);
|
|
6788
|
-
const switchSlots = getValidatedAgySwitchSlots(catalog);
|
|
6789
|
-
const templateSlot = switchSlots.find((slot) => slot.slotId === templateKey);
|
|
6790
|
-
const orderedSlots = templateSlot ? [templateSlot, ...switchSlots.filter((slot) => slot.slotId !== templateKey)] : switchSlots;
|
|
6791
|
-
if (routes.length > 0 && orderedSlots.length === 0) {
|
|
6792
|
-
throw new Error("No validated AGY switch slots are available for the selected launch route");
|
|
6793
|
-
}
|
|
6794
|
-
const switchableRoutes = routes.slice(0, orderedSlots.length);
|
|
6795
|
-
const skippedRoutes = routes.slice(orderedSlots.length);
|
|
6796
|
-
const slots = switchableRoutes.map((route, index) => ({
|
|
6797
|
-
slotId: orderedSlots[index].slotId,
|
|
6798
|
-
route
|
|
6799
|
-
}));
|
|
6800
|
-
return {
|
|
6801
|
-
slots,
|
|
6802
|
-
switchableRoutes,
|
|
6803
|
-
skippedRoutes,
|
|
6804
|
-
validation
|
|
6805
|
-
};
|
|
6806
|
-
}
|
|
6807
|
-
function resolveRelayCatalogSlots(catalog, routes, templateKey) {
|
|
6808
|
-
return planRelayCatalogSlots(catalog, routes, templateKey).slots;
|
|
6809
|
-
}
|
|
6810
|
-
function buildRelayCatalogEntry(route, template) {
|
|
6811
|
-
const entry = structuredClone(template);
|
|
6812
|
-
entry.displayName = route.displayName;
|
|
6813
|
-
entry.model = template.model ?? RELAY_AGENT_PLACEHOLDER;
|
|
6814
|
-
entry.requestedModelId = route.catalogId;
|
|
6815
|
-
entry.modelVersion = route.catalogId;
|
|
6816
|
-
entry.modelVersionId = route.catalogId;
|
|
6817
|
-
entry.quotaInfo = { remainingFraction: 1, resetTime: "2026-06-23T02:00:57Z" };
|
|
6818
|
-
return applyRouteContextBounds(entry, route);
|
|
6819
|
-
}
|
|
6820
|
-
function buildRelayCatalogSlotEntry(route, template) {
|
|
6821
|
-
const entry = structuredClone(template);
|
|
6822
|
-
entry.displayName = route.displayName;
|
|
6823
|
-
entry.quotaInfo = { remainingFraction: 1, resetTime: "2026-06-23T02:00:57Z" };
|
|
6824
|
-
delete entry.requestedModelId;
|
|
6825
|
-
delete entry.modelVersion;
|
|
6826
|
-
delete entry.modelVersionId;
|
|
6827
|
-
delete entry.isInternal;
|
|
6828
|
-
return applyRouteContextBounds(entry, route);
|
|
6829
|
-
}
|
|
6830
|
-
function injectRelayModels(fixture, routes, templateKey) {
|
|
6831
|
-
const result = structuredClone(fixture);
|
|
6832
|
-
const template = fixture.models[templateKey];
|
|
6833
|
-
if (!template) {
|
|
6834
|
-
throw new Error(`Template model "${templateKey}" not found in catalog fixture`);
|
|
6835
|
-
}
|
|
6836
|
-
const seen = /* @__PURE__ */ new Set();
|
|
6837
|
-
for (const route of routes) {
|
|
6838
|
-
if (seen.has(route.catalogId)) {
|
|
6839
|
-
throw new Error(`Catalog ID collision: ${route.catalogId}`);
|
|
6840
|
-
}
|
|
6841
|
-
if (fixture.models[route.catalogId]) {
|
|
6842
|
-
throw new Error(`Catalog ID collision with native model: ${route.catalogId}`);
|
|
6843
|
-
}
|
|
6844
|
-
seen.add(route.catalogId);
|
|
6845
|
-
}
|
|
6846
|
-
if (routes.length > 0) {
|
|
6847
|
-
result.models[RELAY_CASCADE_ANCHOR_ID] ??= structuredClone(template);
|
|
6848
|
-
result.models[RELAY_CASCADE_FALLBACK_ID] ??= structuredClone(RELAY_CASCADE_FALLBACK_ENTRY);
|
|
6849
|
-
result.models[RELAY_CASCADE_INTENT_MODEL_ID] ??= structuredClone(RELAY_CASCADE_INTENT_MODEL_ENTRY);
|
|
6850
|
-
if (!result.models[RELAY_CASCADE_PLAN_ANCHOR_ID]) {
|
|
6851
|
-
const planAnchor = withCascadeCheckpointer(structuredClone(template));
|
|
6852
|
-
planAnchor.model = RELAY_CASCADE_PLAN_MODEL;
|
|
6853
|
-
result.models[RELAY_CASCADE_PLAN_ANCHOR_ID] = planAnchor;
|
|
6854
|
-
}
|
|
6855
|
-
const slotPlan = planRelayCatalogSlots(result, routes, templateKey);
|
|
6856
|
-
const slots = slotPlan.slots;
|
|
6857
|
-
for (const { slotId, route } of slots) {
|
|
6858
|
-
const slotTemplate = result.models[slotId] ?? template;
|
|
6859
|
-
if (result.models[slotId]) {
|
|
6860
|
-
result.models[slotId] = buildRelayCatalogSlotEntry(route, slotTemplate);
|
|
6861
|
-
}
|
|
6862
|
-
result.models[route.catalogId] = buildRelayCatalogEntry(route, slotTemplate);
|
|
6863
|
-
}
|
|
6864
|
-
result.defaultAgentModelId = slots[0]?.slotId ?? RELAY_CASCADE_ANCHOR_ID;
|
|
6865
|
-
result.agentModelSorts = [
|
|
6866
|
-
{
|
|
6867
|
-
displayName: "Recommended",
|
|
6868
|
-
groups: [{
|
|
6869
|
-
modelIds: slots.map((slot) => slot.slotId)
|
|
6870
|
-
}]
|
|
6871
|
-
}
|
|
6872
|
-
];
|
|
6873
|
-
return result;
|
|
6874
|
-
}
|
|
6875
|
-
if (!result.agentModelSorts?.[0]?.groups?.[0]) {
|
|
6876
|
-
result.agentModelSorts = [
|
|
6877
|
-
{
|
|
6878
|
-
displayName: "Recommended",
|
|
6879
|
-
groups: [{ modelIds: [] }]
|
|
6880
|
-
}
|
|
6881
|
-
];
|
|
6882
|
-
}
|
|
6883
|
-
return result;
|
|
6884
|
-
}
|
|
6885
|
-
function buildAntigravityRoutes(resolvedFavorites, maxRoutes = MAX_MODEL_CATALOG) {
|
|
6886
|
-
const routes = [];
|
|
6887
|
-
const seen = /* @__PURE__ */ new Set();
|
|
6888
|
-
for (const fav of resolvedFavorites) {
|
|
6889
|
-
if (routes.length >= maxRoutes) break;
|
|
6890
|
-
const favModel = fav.model;
|
|
6891
|
-
const modelId = favModel.id;
|
|
6892
|
-
const catalogId = `relay-ai__${fav.providerId}__${modelId}`;
|
|
6893
|
-
if (seen.has(catalogId)) continue;
|
|
6894
|
-
seen.add(catalogId);
|
|
6895
|
-
const npm = favModel.npm || "@ai-sdk/openai-compatible";
|
|
6896
|
-
const upstreamModelId = favModel.upstreamModelId || modelId;
|
|
6897
|
-
const baseURL = favModel.apiBaseUrl || favModel.completionsUrl || void 0;
|
|
6898
|
-
const contextWindow = favModel.contextWindow;
|
|
6899
|
-
const modelFormat = favModel.modelFormat;
|
|
6900
|
-
routes.push({
|
|
6901
|
-
catalogId,
|
|
6902
|
-
providerId: fav.providerId,
|
|
6903
|
-
providerName: fav.providerName,
|
|
6904
|
-
modelId,
|
|
6905
|
-
upstreamModelId,
|
|
6906
|
-
displayName: `${favModel.name} (Relay)`,
|
|
6907
|
-
...modelFormat ? { modelFormat } : {},
|
|
6908
|
-
npm,
|
|
6909
|
-
apiKey: fav.apiKey,
|
|
6910
|
-
...fav.authType ? { authType: fav.authType } : {},
|
|
6911
|
-
...fav.oauthAccountId ? { oauthAccountId: fav.oauthAccountId } : {},
|
|
6912
|
-
...fav.providerData ? { providerData: fav.providerData } : {},
|
|
6913
|
-
baseURL,
|
|
6914
|
-
contextWindow
|
|
6915
|
-
});
|
|
6916
|
-
}
|
|
6917
|
-
return applyUniqueAntigravityRouteLabels(routes);
|
|
6918
|
-
}
|
|
6919
|
-
function routeBaseModelName(route) {
|
|
6920
|
-
const relayMatch = route.displayName.match(/^(.*) \(Relay(?: - .*)?\)$/);
|
|
6921
|
-
return relayMatch?.[1] ?? route.displayName;
|
|
6922
|
-
}
|
|
6923
|
-
function authKindLabel(route) {
|
|
6924
|
-
if (route.authType === "oauth") return "OAuth";
|
|
6925
|
-
if (route.authType === "api") return "API key";
|
|
6926
|
-
if (route.authType === "none") return "local";
|
|
6927
|
-
return "provider";
|
|
6928
|
-
}
|
|
6929
|
-
function duplicateCounts(values) {
|
|
6930
|
-
const counts = /* @__PURE__ */ new Map();
|
|
6931
|
-
for (const value of values) {
|
|
6932
|
-
counts.set(value, (counts.get(value) ?? 0) + 1);
|
|
6933
|
-
}
|
|
6934
|
-
return counts;
|
|
6935
|
-
}
|
|
6936
|
-
function assertUniqueRouteDisplayNames(routes) {
|
|
6937
|
-
const counts = duplicateCounts(routes.map((route) => route.displayName));
|
|
6938
|
-
const duplicate = [...counts.entries()].find(([, count]) => count > 1);
|
|
6939
|
-
if (duplicate) {
|
|
6940
|
-
throw new Error(`Duplicate AGY model label after disambiguation: ${duplicate[0]}`);
|
|
6941
|
-
}
|
|
6942
|
-
}
|
|
6943
|
-
function applyUniqueAntigravityRouteLabels(routes) {
|
|
6944
|
-
const baseNames = routes.map(routeBaseModelName);
|
|
6945
|
-
const baseNameCounts = duplicateCounts(baseNames);
|
|
6946
|
-
const upstreamCounts = duplicateCounts(routes.map((route) => route.upstreamModelId));
|
|
6947
|
-
const providerNameCounts = duplicateCounts(routes.map((route) => route.providerName));
|
|
6948
|
-
const labeled = routes.map((route, index) => {
|
|
6949
|
-
const baseName = baseNames[index];
|
|
6950
|
-
const needsSuffix = (baseNameCounts.get(baseName) ?? 0) > 1 || (upstreamCounts.get(route.upstreamModelId) ?? 0) > 1;
|
|
6951
|
-
if (!needsSuffix) {
|
|
6952
|
-
return { ...route, displayName: `${baseName} (Relay)` };
|
|
6953
|
-
}
|
|
6954
|
-
const providerName = route.providerName || route.providerId;
|
|
6955
|
-
const providerSuffix = (providerNameCounts.get(providerName) ?? 0) > 1 ? `${providerName} ${authKindLabel(route)}` : providerName;
|
|
6956
|
-
return {
|
|
6957
|
-
...route,
|
|
6958
|
-
displayName: `${baseName} (Relay - ${providerSuffix})`
|
|
6959
|
-
};
|
|
6960
|
-
});
|
|
6961
|
-
const firstPassCounts = duplicateCounts(labeled.map((route) => route.displayName));
|
|
6962
|
-
const withProviderIds = labeled.map((route) => {
|
|
6963
|
-
if ((firstPassCounts.get(route.displayName) ?? 0) <= 1) return route;
|
|
6964
|
-
return {
|
|
6965
|
-
...route,
|
|
6966
|
-
displayName: route.displayName.replace(/\)$/, ` - ${route.providerId})`)
|
|
6967
|
-
};
|
|
6968
|
-
});
|
|
6969
|
-
assertUniqueRouteDisplayNames(withProviderIds);
|
|
6970
|
-
return withProviderIds;
|
|
6971
|
-
}
|
|
6972
|
-
function routeLabels(routes) {
|
|
6973
|
-
assertUniqueRouteDisplayNames(routes);
|
|
6974
|
-
const labels = /* @__PURE__ */ new Map();
|
|
6975
|
-
for (const route of routes) {
|
|
6976
|
-
labels.set(route.catalogId, route.displayName);
|
|
6977
|
-
}
|
|
6978
|
-
return labels;
|
|
6979
|
-
}
|
|
6980
|
-
function buildClientModelConfigData(routes, catalog, templateKey = RELAY_CASCADE_ANCHOR_ID, precomputedSlots) {
|
|
6981
|
-
const catalogRoutes = routes.slice(0, MAX_MODEL_CATALOG);
|
|
6982
|
-
const slots = precomputedSlots ?? (catalog ? resolveRelayCatalogSlots(catalog, catalogRoutes, templateKey) : catalogRoutes.map((route) => ({ slotId: route.catalogId, route })));
|
|
6983
|
-
const labels = routeLabels(catalogRoutes);
|
|
6984
|
-
const clientModelConfigs = slots.map(({ slotId, route }) => {
|
|
6985
|
-
const entry = catalog?.models[slotId] ?? catalog?.models[route.catalogId] ?? catalog?.models[RELAY_CASCADE_ANCHOR_ID];
|
|
6986
|
-
const label = labels.get(route.catalogId) ?? route.displayName;
|
|
6987
|
-
return {
|
|
6988
|
-
label,
|
|
6989
|
-
modelOrAlias: {
|
|
6990
|
-
alias: slotId,
|
|
6991
|
-
choice: { case: "alias", value: slotId }
|
|
6992
|
-
},
|
|
6993
|
-
disabled: false,
|
|
6994
|
-
supportedMimeTypes: entry?.supportedMimeTypes ?? {},
|
|
6995
|
-
quotaInfo: entry?.quotaInfo ?? { remainingFraction: 1 },
|
|
6996
|
-
tagTitle: entry?.tagTitle,
|
|
6997
|
-
tagDescription: entry?.tagDescription,
|
|
6998
|
-
supportsThoughtCirculation: entry?.supportsThoughtCirculation ?? false
|
|
6999
|
-
};
|
|
7000
|
-
});
|
|
7001
|
-
return {
|
|
7002
|
-
clientModelConfigs,
|
|
7003
|
-
clientModelSorts: [
|
|
7004
|
-
{
|
|
7005
|
-
name: "Recommended",
|
|
7006
|
-
groups: [
|
|
7007
|
-
{
|
|
7008
|
-
groupName: "",
|
|
7009
|
-
modelLabels: clientModelConfigs.map((config) => config.label)
|
|
7010
|
-
}
|
|
7011
|
-
]
|
|
7012
|
-
}
|
|
7013
|
-
],
|
|
7014
|
-
defaultOverrideModelConfig: clientModelConfigs[0] ?? {}
|
|
7015
|
-
};
|
|
7016
|
-
}
|
|
7017
|
-
function buildListModelConfigsResponse(routes, catalog, templateKey = RELAY_CASCADE_ANCHOR_ID) {
|
|
7018
|
-
const catalogRoutes = routes.slice(0, MAX_MODEL_CATALOG);
|
|
7019
|
-
const slots = catalog ? resolveRelayCatalogSlots(catalog, catalogRoutes, templateKey) : catalogRoutes.map((route) => ({ slotId: route.catalogId, route }));
|
|
7020
|
-
const config = slots.map(({ slotId }) => ({
|
|
7021
|
-
requestedModelId: slotId,
|
|
7022
|
-
planModel: RELAY_CASCADE_PLAN_MODEL,
|
|
7023
|
-
requestedModel: catalog?.models[slotId]?.model ?? RELAY_AGENT_PLACEHOLDER
|
|
7024
|
-
}));
|
|
7025
|
-
return {
|
|
7026
|
-
...buildClientModelConfigData(routes, catalog, templateKey, slots),
|
|
7027
|
-
allowedModelConfigs: config,
|
|
7028
|
-
defaultAgentModelConfig: config[0] ?? {}
|
|
7029
|
-
};
|
|
7030
|
-
}
|
|
7031
|
-
var CURRENT_EXPERIMENT_IDS = [
|
|
7032
|
-
105979552,
|
|
7033
|
-
105979574,
|
|
7034
|
-
106015351,
|
|
7035
|
-
105979579,
|
|
7036
|
-
105867471,
|
|
7037
|
-
105979530,
|
|
7038
|
-
105995634,
|
|
7039
|
-
106121401,
|
|
7040
|
-
106100625,
|
|
7041
|
-
104638466,
|
|
7042
|
-
101868197,
|
|
7043
|
-
104817729,
|
|
7044
|
-
105695344,
|
|
7045
|
-
106064591,
|
|
7046
|
-
104913215,
|
|
7047
|
-
106324349,
|
|
7048
|
-
106309078,
|
|
7049
|
-
105821930,
|
|
7050
|
-
104922093,
|
|
7051
|
-
103012598,
|
|
7052
|
-
106143956,
|
|
7053
|
-
105856899,
|
|
7054
|
-
106312323,
|
|
7055
|
-
106064030,
|
|
7056
|
-
105746183,
|
|
7057
|
-
105757908,
|
|
7058
|
-
104892493,
|
|
7059
|
-
105822886,
|
|
7060
|
-
105785683,
|
|
7061
|
-
105721273,
|
|
7062
|
-
105897325,
|
|
7063
|
-
105658071,
|
|
7064
|
-
106240758,
|
|
7065
|
-
105943702,
|
|
7066
|
-
106106760,
|
|
7067
|
-
106283618,
|
|
7068
|
-
105620019,
|
|
7069
|
-
106038160,
|
|
7070
|
-
106309520,
|
|
7071
|
-
106281951,
|
|
7072
|
-
106264532,
|
|
7073
|
-
106222835,
|
|
7074
|
-
106094629,
|
|
7075
|
-
105887313,
|
|
7076
|
-
105849474,
|
|
7077
|
-
106032303,
|
|
7078
|
-
106228452,
|
|
7079
|
-
106113900,
|
|
7080
|
-
106121607,
|
|
7081
|
-
105979531,
|
|
7082
|
-
105979553,
|
|
7083
|
-
106015328,
|
|
7084
|
-
105867469,
|
|
7085
|
-
105979517,
|
|
7086
|
-
106121399,
|
|
7087
|
-
106100654,
|
|
7088
|
-
104638459,
|
|
7089
|
-
101551624,
|
|
7090
|
-
104673683,
|
|
7091
|
-
105695346,
|
|
7092
|
-
106064590,
|
|
7093
|
-
104913210,
|
|
7094
|
-
105821928,
|
|
7095
|
-
104922082,
|
|
7096
|
-
103012592,
|
|
7097
|
-
106064028,
|
|
7098
|
-
105746181,
|
|
7099
|
-
104892490,
|
|
7100
|
-
105822881,
|
|
7101
|
-
105721268,
|
|
7102
|
-
105895316,
|
|
7103
|
-
105658068,
|
|
7104
|
-
106240748,
|
|
7105
|
-
105943694,
|
|
7106
|
-
106283614,
|
|
7107
|
-
105620012,
|
|
7108
|
-
106038153,
|
|
7109
|
-
105887311,
|
|
7110
|
-
106032301,
|
|
7111
|
-
106113877,
|
|
7112
|
-
106121604
|
|
7113
|
-
];
|
|
7114
|
-
function buildListExperimentsResponse() {
|
|
7115
|
-
return {
|
|
7116
|
-
experimentIds: [...CURRENT_EXPERIMENT_IDS]
|
|
7117
|
-
};
|
|
7118
|
-
}
|
|
7119
|
-
|
|
7120
6489
|
// src/antigravity/fixtures/loadCodeAssist.json
|
|
7121
6490
|
var loadCodeAssist_default = {
|
|
7122
6491
|
currentTier: {
|
|
@@ -8551,6 +7920,22 @@ function respondJson(res, status, data) {
|
|
|
8551
7920
|
});
|
|
8552
7921
|
res.end(body);
|
|
8553
7922
|
}
|
|
7923
|
+
function parsePseudoToolCall(text4, knownToolNames) {
|
|
7924
|
+
const trimmed = text4.trim();
|
|
7925
|
+
if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) return null;
|
|
7926
|
+
try {
|
|
7927
|
+
const obj = JSON.parse(trimmed);
|
|
7928
|
+
if (obj && typeof obj === "object") {
|
|
7929
|
+
const name = obj.name || obj.type === "function" && typeof obj.function === "object" && obj.function?.name || obj.function_name;
|
|
7930
|
+
if (typeof name === "string" && knownToolNames.has(name)) {
|
|
7931
|
+
const args = obj.parameters || obj.arguments || obj.args || typeof obj.function === "object" && obj.function?.parameters || {};
|
|
7932
|
+
return { name, args: typeof args === "object" && args ? args : {} };
|
|
7933
|
+
}
|
|
7934
|
+
}
|
|
7935
|
+
} catch {
|
|
7936
|
+
}
|
|
7937
|
+
return null;
|
|
7938
|
+
}
|
|
8554
7939
|
async function handleStreamingRequest(res, route, providerOptions, parsed, log14, options = {}) {
|
|
8555
7940
|
const sdkParams = applyClaudeCodeOAuthIdentity(route, translateRequest(parsed, {
|
|
8556
7941
|
...options.requestOptions,
|
|
@@ -8589,8 +7974,39 @@ async function handleStreamingRequest(res, route, providerOptions, parsed, log14
|
|
|
8589
7974
|
};
|
|
8590
7975
|
const thinkFilter = createThinkFilter();
|
|
8591
7976
|
const toolCallBuffers = /* @__PURE__ */ new Map();
|
|
7977
|
+
const knownToolNames = new Set(Object.keys(sdkParams.tools ?? {}));
|
|
8592
7978
|
let responseReasoning = "";
|
|
8593
7979
|
let sawToolCall = false;
|
|
7980
|
+
let textBuffer = "";
|
|
7981
|
+
let bufferingJsonText = false;
|
|
7982
|
+
const flushBufferedText = () => {
|
|
7983
|
+
if (!textBuffer) return;
|
|
7984
|
+
startSse();
|
|
7985
|
+
const chunk = formatCloudCodeChunk({
|
|
7986
|
+
text: textBuffer,
|
|
7987
|
+
modelVersion: route.catalogId,
|
|
7988
|
+
responseId
|
|
7989
|
+
});
|
|
7990
|
+
res.write(`data: ${JSON.stringify(chunk)}
|
|
7991
|
+
|
|
7992
|
+
`);
|
|
7993
|
+
textBuffer = "";
|
|
7994
|
+
bufferingJsonText = false;
|
|
7995
|
+
};
|
|
7996
|
+
const emitPseudoToolCall = (pseudoTool) => {
|
|
7997
|
+
sawToolCall = true;
|
|
7998
|
+
startSse();
|
|
7999
|
+
const chunk = formatCloudCodeChunk({
|
|
8000
|
+
functionCall: { name: pseudoTool.name, args: normalizeFunctionCallArgs(pseudoTool.args) },
|
|
8001
|
+
modelVersion: route.catalogId,
|
|
8002
|
+
responseId
|
|
8003
|
+
});
|
|
8004
|
+
res.write(`data: ${JSON.stringify(chunk)}
|
|
8005
|
+
|
|
8006
|
+
`);
|
|
8007
|
+
textBuffer = "";
|
|
8008
|
+
bufferingJsonText = false;
|
|
8009
|
+
};
|
|
8594
8010
|
for await (const part of fullStream) {
|
|
8595
8011
|
const p15 = part;
|
|
8596
8012
|
if (p15.type === "reasoning-delta" || p15.type === "reasoning") {
|
|
@@ -8607,15 +8023,27 @@ async function handleStreamingRequest(res, route, providerOptions, parsed, log14
|
|
|
8607
8023
|
}
|
|
8608
8024
|
if (text4) {
|
|
8609
8025
|
log14(`[gateway] text-delta: ${JSON.stringify(text4.slice(0, 500))}`);
|
|
8610
|
-
|
|
8611
|
-
|
|
8612
|
-
|
|
8613
|
-
|
|
8614
|
-
|
|
8615
|
-
|
|
8616
|
-
|
|
8026
|
+
if (!bufferingJsonText && (textBuffer + text4).trimStart().startsWith("{")) {
|
|
8027
|
+
bufferingJsonText = true;
|
|
8028
|
+
}
|
|
8029
|
+
if (bufferingJsonText) {
|
|
8030
|
+
textBuffer += text4;
|
|
8031
|
+
const pseudoTool = textBuffer.trimEnd().endsWith("}") ? parsePseudoToolCall(textBuffer, knownToolNames) : null;
|
|
8032
|
+
if (pseudoTool) {
|
|
8033
|
+
log14(`[gateway] parsed pseudo tool-call from text: ${pseudoTool.name}`);
|
|
8034
|
+
emitPseudoToolCall(pseudoTool);
|
|
8035
|
+
}
|
|
8036
|
+
} else {
|
|
8037
|
+
startSse();
|
|
8038
|
+
const chunk = formatCloudCodeChunk({
|
|
8039
|
+
text: text4,
|
|
8040
|
+
modelVersion: route.catalogId,
|
|
8041
|
+
responseId
|
|
8042
|
+
});
|
|
8043
|
+
res.write(`data: ${JSON.stringify(chunk)}
|
|
8617
8044
|
|
|
8618
8045
|
`);
|
|
8046
|
+
}
|
|
8619
8047
|
}
|
|
8620
8048
|
} else if (p15.type === "tool-input-start") {
|
|
8621
8049
|
const id = p15.id ?? p15.toolCallId;
|
|
@@ -8647,6 +8075,15 @@ async function handleStreamingRequest(res, route, providerOptions, parsed, log14
|
|
|
8647
8075
|
`);
|
|
8648
8076
|
} else if (p15.type === "finish") {
|
|
8649
8077
|
log14(`[gateway] finish: ${p15.finishReason ?? "unknown"}`);
|
|
8078
|
+
if (textBuffer) {
|
|
8079
|
+
const pseudoTool = parsePseudoToolCall(textBuffer, knownToolNames);
|
|
8080
|
+
if (pseudoTool) {
|
|
8081
|
+
log14(`[gateway] parsed pseudo tool-call on finish: ${pseudoTool.name}`);
|
|
8082
|
+
emitPseudoToolCall(pseudoTool);
|
|
8083
|
+
} else {
|
|
8084
|
+
flushBufferedText();
|
|
8085
|
+
}
|
|
8086
|
+
}
|
|
8650
8087
|
startSse();
|
|
8651
8088
|
const reason = mapFinishReason2(p15.finishReason ?? "");
|
|
8652
8089
|
const chunk = formatCloudCodeChunk({
|
|
@@ -8664,6 +8101,7 @@ async function handleStreamingRequest(res, route, providerOptions, parsed, log14
|
|
|
8664
8101
|
} else if (p15.type === "error") {
|
|
8665
8102
|
const message = formatUpstreamError(p15.error);
|
|
8666
8103
|
log14(`[gateway] stream provider error: ${message}`);
|
|
8104
|
+
flushBufferedText();
|
|
8667
8105
|
emitStreamError(res, route, responseId, message, startSse);
|
|
8668
8106
|
break;
|
|
8669
8107
|
} else if (p15.type === "reasoning-start" || p15.type === "reasoning-end") {
|
|
@@ -8773,10 +8211,17 @@ async function resolveAntigravityLaunchRoutes(opts) {
|
|
|
8773
8211
|
maxRoutes,
|
|
8774
8212
|
{ dropEmptyApiKey: true, trackCapacitySkipped: true }
|
|
8775
8213
|
);
|
|
8214
|
+
const tooSmall = resolved.slice(1).filter(
|
|
8215
|
+
(entry) => !meetsContextFloor("antigravity", entry.model.contextWindow)
|
|
8216
|
+
);
|
|
8217
|
+
const launchable = resolved.filter((entry) => !tooSmall.includes(entry));
|
|
8776
8218
|
return {
|
|
8777
|
-
routes: buildAntigravityRoutes(
|
|
8219
|
+
routes: buildAntigravityRoutes(launchable, maxRoutes),
|
|
8778
8220
|
apiKey,
|
|
8779
|
-
droppedFavorites
|
|
8221
|
+
droppedFavorites: [
|
|
8222
|
+
...droppedFavorites,
|
|
8223
|
+
...tooSmall.map((entry) => ({ providerId: entry.providerId, modelId: entry.model.id }))
|
|
8224
|
+
],
|
|
8780
8225
|
capacitySkippedFavorites
|
|
8781
8226
|
};
|
|
8782
8227
|
}
|
|
@@ -8805,12 +8250,12 @@ function findAntigravityCliBinary() {
|
|
|
8805
8250
|
encoding: "utf8",
|
|
8806
8251
|
stdio: ["pipe", "pipe", "pipe"]
|
|
8807
8252
|
});
|
|
8808
|
-
const
|
|
8809
|
-
if (
|
|
8253
|
+
const path3 = result.trim().split("\n")[0]?.trim();
|
|
8254
|
+
if (path3) return path3;
|
|
8810
8255
|
} catch {
|
|
8811
8256
|
}
|
|
8812
|
-
for (const
|
|
8813
|
-
if (existsSync6(
|
|
8257
|
+
for (const path3 of FALLBACK_PATHS) {
|
|
8258
|
+
if (existsSync6(path3)) return path3;
|
|
8814
8259
|
}
|
|
8815
8260
|
return null;
|
|
8816
8261
|
}
|
|
@@ -8906,9 +8351,35 @@ function prepareIdeProfile(profileDir, gatewayUrl) {
|
|
|
8906
8351
|
}
|
|
8907
8352
|
|
|
8908
8353
|
// src/antigravity/launch-ide.ts
|
|
8354
|
+
var LINUX_APP_PROFILE_DIR = join7(homedir7(), ".relay-ai", "antigravity", "app-profile");
|
|
8355
|
+
var LINUX_IDE_PROFILE_DIR = join7(homedir7(), ".relay-ai", "antigravity", "profile");
|
|
8909
8356
|
function sleep(ms) {
|
|
8910
8357
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
8911
8358
|
}
|
|
8359
|
+
function linuxAntigravityBinary() {
|
|
8360
|
+
const candidates = [
|
|
8361
|
+
"/usr/share/antigravity/antigravity",
|
|
8362
|
+
"/opt/antigravity/antigravity",
|
|
8363
|
+
join7(homedir7(), ".local", "share", "antigravity", "antigravity")
|
|
8364
|
+
];
|
|
8365
|
+
for (const candidate of candidates) {
|
|
8366
|
+
if (existsSync7(candidate)) return candidate;
|
|
8367
|
+
}
|
|
8368
|
+
return null;
|
|
8369
|
+
}
|
|
8370
|
+
function linuxKillByProfile(profileDir, signal) {
|
|
8371
|
+
const output = defaultProcessList();
|
|
8372
|
+
for (const line of output.split("\n")) {
|
|
8373
|
+
if (!line.includes(`--user-data-dir=${profileDir}`)) continue;
|
|
8374
|
+
const pid = Number.parseInt(line.trim().split(/\s+/)[0] ?? "", 10);
|
|
8375
|
+
if (Number.isFinite(pid) && pid > 0) {
|
|
8376
|
+
try {
|
|
8377
|
+
process.kill(pid, signal);
|
|
8378
|
+
} catch {
|
|
8379
|
+
}
|
|
8380
|
+
}
|
|
8381
|
+
}
|
|
8382
|
+
}
|
|
8912
8383
|
function runPowerShell(script) {
|
|
8913
8384
|
return execSync4(`powershell.exe -NoProfile -Command ${JSON.stringify(script)}`, {
|
|
8914
8385
|
encoding: "utf8",
|
|
@@ -8944,9 +8415,10 @@ function winForceQuitProcess(exeName, profileDir) {
|
|
|
8944
8415
|
}
|
|
8945
8416
|
}
|
|
8946
8417
|
function defaultProcessList() {
|
|
8947
|
-
|
|
8418
|
+
const psArgs = process.platform === "linux" ? ["-eo", "pid=,args="] : ["-axo", "pid=,command="];
|
|
8419
|
+
if (process.platform !== "darwin" && process.platform !== "linux") return "";
|
|
8948
8420
|
try {
|
|
8949
|
-
return execFileSync3("ps",
|
|
8421
|
+
return execFileSync3("ps", psArgs, {
|
|
8950
8422
|
encoding: "utf8",
|
|
8951
8423
|
stdio: ["ignore", "pipe", "ignore"],
|
|
8952
8424
|
maxBuffer: 1024 * 1024 * 4
|
|
@@ -8958,11 +8430,17 @@ function defaultProcessList() {
|
|
|
8958
8430
|
function isAntigravityIdeRunning(profileDir, processList = defaultProcessList) {
|
|
8959
8431
|
if (process.platform === "win32") return winIsProcessRunningForProfile("Antigravity IDE.exe", profileDir);
|
|
8960
8432
|
const output = processList();
|
|
8433
|
+
if (process.platform === "linux") {
|
|
8434
|
+
return output.split("\n").some((line) => line.includes(`--user-data-dir=${profileDir}`));
|
|
8435
|
+
}
|
|
8961
8436
|
return output.split("\n").some((line) => line.includes("Antigravity IDE.app") && line.includes(`--user-data-dir=${profileDir}`));
|
|
8962
8437
|
}
|
|
8963
8438
|
function isAntigravityAppRunning(profileDir, processList = defaultProcessList) {
|
|
8964
8439
|
if (process.platform === "win32") return winIsProcessRunningForProfile("Antigravity.exe", profileDir);
|
|
8965
8440
|
const output = processList();
|
|
8441
|
+
if (process.platform === "linux") {
|
|
8442
|
+
return output.split("\n").some((line) => line.includes(`--user-data-dir=${profileDir}`));
|
|
8443
|
+
}
|
|
8966
8444
|
return output.split("\n").some((line) => line.includes("Antigravity.app") && line.includes(`--user-data-dir=${profileDir}`));
|
|
8967
8445
|
}
|
|
8968
8446
|
async function waitForAntigravityIdeQuit(profileDir, options = {}) {
|
|
@@ -8987,15 +8465,21 @@ async function waitForAntigravityAppQuit(profileDir, options = {}) {
|
|
|
8987
8465
|
}
|
|
8988
8466
|
function forceQuitAntigravityIde(profileDir) {
|
|
8989
8467
|
if (process.platform === "win32") winForceQuitProcess("Antigravity IDE.exe", profileDir);
|
|
8468
|
+
else if (process.platform === "linux") linuxKillByProfile(profileDir, "SIGKILL");
|
|
8990
8469
|
}
|
|
8991
8470
|
function forceQuitAntigravityApp(profileDir) {
|
|
8992
8471
|
if (process.platform === "win32") winForceQuitProcess("Antigravity.exe", profileDir);
|
|
8472
|
+
else if (process.platform === "linux") linuxKillByProfile(profileDir, "SIGKILL");
|
|
8993
8473
|
}
|
|
8994
8474
|
function quitAntigravityIdeGracefully() {
|
|
8995
8475
|
if (process.platform === "win32") {
|
|
8996
8476
|
winQuitProcess("Antigravity IDE.exe");
|
|
8997
8477
|
return;
|
|
8998
8478
|
}
|
|
8479
|
+
if (process.platform === "linux") {
|
|
8480
|
+
linuxKillByProfile(LINUX_IDE_PROFILE_DIR, "SIGTERM");
|
|
8481
|
+
return;
|
|
8482
|
+
}
|
|
8999
8483
|
if (process.platform !== "darwin") return;
|
|
9000
8484
|
try {
|
|
9001
8485
|
execFileSync3("osascript", ["-e", 'tell application "Antigravity IDE" to quit'], {
|
|
@@ -9012,6 +8496,10 @@ function quitAntigravityAppGracefully() {
|
|
|
9012
8496
|
winQuitProcess("Antigravity.exe");
|
|
9013
8497
|
return;
|
|
9014
8498
|
}
|
|
8499
|
+
if (process.platform === "linux") {
|
|
8500
|
+
linuxKillByProfile(LINUX_APP_PROFILE_DIR, "SIGTERM");
|
|
8501
|
+
return;
|
|
8502
|
+
}
|
|
9015
8503
|
if (process.platform !== "darwin") return;
|
|
9016
8504
|
try {
|
|
9017
8505
|
execFileSync3("osascript", ["-e", 'tell application "Antigravity" to quit'], {
|
|
@@ -9031,6 +8519,7 @@ function findAntigravityAppBinary() {
|
|
|
9031
8519
|
const winPath = join7(localAppData, "Programs", "Antigravity", "Antigravity.exe");
|
|
9032
8520
|
return existsSync7(winPath) ? winPath : null;
|
|
9033
8521
|
}
|
|
8522
|
+
if (process.platform === "linux") return linuxAntigravityBinary();
|
|
9034
8523
|
if (process.platform !== "darwin") return null;
|
|
9035
8524
|
const defaultPath = "/Applications/Antigravity.app/Contents/MacOS/Antigravity";
|
|
9036
8525
|
if (existsSync7(defaultPath)) return defaultPath;
|
|
@@ -9046,6 +8535,7 @@ function findAntigravityIdeBinary() {
|
|
|
9046
8535
|
const winPath = join7(localAppData, "Programs", "Antigravity IDE", "Antigravity IDE.exe");
|
|
9047
8536
|
return existsSync7(winPath) ? winPath : null;
|
|
9048
8537
|
}
|
|
8538
|
+
if (process.platform === "linux") return linuxAntigravityBinary();
|
|
9049
8539
|
if (process.platform !== "darwin") return null;
|
|
9050
8540
|
const defaultPath = "/Applications/Antigravity IDE.app/Contents/Resources/app/bin/antigravity-ide";
|
|
9051
8541
|
if (existsSync7(defaultPath)) return defaultPath;
|
|
@@ -9063,8 +8553,8 @@ function launchAntigravityApp(env, profileDir, gatewayUrl, extraArgs) {
|
|
|
9063
8553
|
};
|
|
9064
8554
|
const binaryPath = findAntigravityAppBinary();
|
|
9065
8555
|
if (!binaryPath) {
|
|
9066
|
-
console.error(
|
|
9067
|
-
console.error("Please make sure Antigravity is installed
|
|
8556
|
+
console.error("Antigravity app not found.");
|
|
8557
|
+
console.error("Please make sure Antigravity is installed.");
|
|
9068
8558
|
settle(127);
|
|
9069
8559
|
return;
|
|
9070
8560
|
}
|
|
@@ -9074,7 +8564,12 @@ function launchAntigravityApp(env, profileDir, gatewayUrl, extraArgs) {
|
|
|
9074
8564
|
...extraArgs
|
|
9075
8565
|
];
|
|
9076
8566
|
const child = spawn5(binaryPath, args, {
|
|
9077
|
-
|
|
8567
|
+
// GUI app: don't inherit the terminal's stdio (its Electron logs would
|
|
8568
|
+
// corrupt relay's interactive prompts) and detach into its own process
|
|
8569
|
+
// group so a Ctrl+C meant for the relay gateway doesn't also kill the app
|
|
8570
|
+
// mid-render. Mirrors the Claude Desktop launcher.
|
|
8571
|
+
stdio: "ignore",
|
|
8572
|
+
detached: true,
|
|
9078
8573
|
env
|
|
9079
8574
|
});
|
|
9080
8575
|
child.on("spawn", () => {
|
|
@@ -9093,8 +8588,8 @@ function launchAntigravityIde(env, profileDir, gatewayUrl, extraArgs) {
|
|
|
9093
8588
|
return new Promise((resolve2) => {
|
|
9094
8589
|
const binaryPath = findAntigravityIdeBinary();
|
|
9095
8590
|
if (!binaryPath) {
|
|
9096
|
-
console.error(
|
|
9097
|
-
console.error("Please make sure Antigravity IDE is installed
|
|
8591
|
+
console.error("Antigravity IDE not found.");
|
|
8592
|
+
console.error("Please make sure Antigravity IDE is installed.");
|
|
9098
8593
|
resolve2(127);
|
|
9099
8594
|
return;
|
|
9100
8595
|
}
|
|
@@ -9106,7 +8601,12 @@ function launchAntigravityIde(env, profileDir, gatewayUrl, extraArgs) {
|
|
|
9106
8601
|
...extraArgs
|
|
9107
8602
|
];
|
|
9108
8603
|
const child = spawn5(binaryPath, args, {
|
|
9109
|
-
|
|
8604
|
+
// GUI app: don't inherit the terminal's stdio (its Electron logs would
|
|
8605
|
+
// corrupt relay's interactive prompts) and detach into its own process
|
|
8606
|
+
// group so a Ctrl+C meant for the relay gateway doesn't also kill the app
|
|
8607
|
+
// mid-render. Mirrors the Claude Desktop launcher.
|
|
8608
|
+
stdio: "ignore",
|
|
8609
|
+
detached: true,
|
|
9110
8610
|
env
|
|
9111
8611
|
});
|
|
9112
8612
|
child.on("exit", (code) => {
|
|
@@ -9613,9 +9113,9 @@ function applyRestoreNumber(config, key, had, value) {
|
|
|
9613
9113
|
delete config[key];
|
|
9614
9114
|
}
|
|
9615
9115
|
}
|
|
9616
|
-
function readCodexConfigText(
|
|
9617
|
-
if (!existsSync8(
|
|
9618
|
-
return readFileSync3(
|
|
9116
|
+
function readCodexConfigText(path3 = getCodexConfigPath()) {
|
|
9117
|
+
if (!existsSync8(path3)) return "";
|
|
9118
|
+
return readFileSync3(path3, "utf8");
|
|
9619
9119
|
}
|
|
9620
9120
|
function parseCodexConfig(text4) {
|
|
9621
9121
|
if (!text4.trim()) return {};
|
|
@@ -9823,10 +9323,10 @@ function getAppCatalogPath(providerId, env = process.env) {
|
|
|
9823
9323
|
return join10(getRelayAiCodexDir(env), `app-models-${providerId}.json`);
|
|
9824
9324
|
}
|
|
9825
9325
|
function readAppSessionLock(env = process.env) {
|
|
9826
|
-
const
|
|
9827
|
-
if (!existsSync9(
|
|
9326
|
+
const path3 = getAppSessionLockPath(env);
|
|
9327
|
+
if (!existsSync9(path3)) return null;
|
|
9828
9328
|
try {
|
|
9829
|
-
const parsed = JSON.parse(readFileSync4(
|
|
9329
|
+
const parsed = JSON.parse(readFileSync4(path3, "utf8"));
|
|
9830
9330
|
if (typeof parsed.pid === "number" && typeof parsed.startedAt === "string") return parsed;
|
|
9831
9331
|
} catch {
|
|
9832
9332
|
}
|
|
@@ -9837,14 +9337,14 @@ function writeAppSessionLock(lock, env = process.env) {
|
|
|
9837
9337
|
`);
|
|
9838
9338
|
}
|
|
9839
9339
|
function clearAppSessionLock(env = process.env) {
|
|
9840
|
-
const
|
|
9841
|
-
if (existsSync9(
|
|
9340
|
+
const path3 = getAppSessionLockPath(env);
|
|
9341
|
+
if (existsSync9(path3)) rmSync4(path3, { force: true });
|
|
9842
9342
|
}
|
|
9843
9343
|
function readAppRestoreState(env = process.env) {
|
|
9844
|
-
const
|
|
9845
|
-
if (!existsSync9(
|
|
9344
|
+
const path3 = getAppRestoreStatePath(env);
|
|
9345
|
+
if (!existsSync9(path3)) return null;
|
|
9846
9346
|
try {
|
|
9847
|
-
return JSON.parse(readFileSync4(
|
|
9347
|
+
return JSON.parse(readFileSync4(path3, "utf8"));
|
|
9848
9348
|
} catch {
|
|
9849
9349
|
return null;
|
|
9850
9350
|
}
|
|
@@ -9855,8 +9355,8 @@ function writeAppRestoreState(state, env = process.env) {
|
|
|
9855
9355
|
`);
|
|
9856
9356
|
}
|
|
9857
9357
|
function clearAppRestoreState(env = process.env) {
|
|
9858
|
-
const
|
|
9859
|
-
if (existsSync9(
|
|
9358
|
+
const path3 = getAppRestoreStatePath(env);
|
|
9359
|
+
if (existsSync9(path3)) rmSync4(path3, { force: true });
|
|
9860
9360
|
}
|
|
9861
9361
|
function backupConfigToml(env = process.env) {
|
|
9862
9362
|
const configPath = getCodexConfigPath();
|
|
@@ -9886,10 +9386,10 @@ function ownedAppCatalogPaths(env = process.env) {
|
|
|
9886
9386
|
}
|
|
9887
9387
|
function removeAppCatalogs(env = process.env) {
|
|
9888
9388
|
const removed = [];
|
|
9889
|
-
for (const
|
|
9389
|
+
for (const path3 of ownedAppCatalogPaths(env)) {
|
|
9890
9390
|
try {
|
|
9891
|
-
rmSync4(
|
|
9892
|
-
removed.push(
|
|
9391
|
+
rmSync4(path3, { force: true });
|
|
9392
|
+
removed.push(path3);
|
|
9893
9393
|
} catch {
|
|
9894
9394
|
}
|
|
9895
9395
|
}
|
|
@@ -10530,6 +10030,9 @@ function getClaudeDesktopHome() {
|
|
|
10530
10030
|
if (process.platform === "win32") {
|
|
10531
10031
|
return join11(process.env.LOCALAPPDATA || join11(homedir9(), "AppData", "Local"), "Claude-3p");
|
|
10532
10032
|
}
|
|
10033
|
+
if (process.platform === "linux") {
|
|
10034
|
+
return join11(process.env.XDG_CONFIG_HOME || join11(homedir9(), ".config"), "Claude-3p");
|
|
10035
|
+
}
|
|
10533
10036
|
return join11(homedir9(), "Library", "Application Support", "Claude-3p");
|
|
10534
10037
|
}
|
|
10535
10038
|
function getConfigLibraryPath() {
|
|
@@ -10734,10 +10237,10 @@ function getSessionLockPath2() {
|
|
|
10734
10237
|
return join12(getClaudeDesktopHome(), ".relay-ai.lock");
|
|
10735
10238
|
}
|
|
10736
10239
|
function inspectSessionLock() {
|
|
10737
|
-
const
|
|
10738
|
-
if (!existsSync11(
|
|
10240
|
+
const path3 = getSessionLockPath2();
|
|
10241
|
+
if (!existsSync11(path3)) return { status: "missing" };
|
|
10739
10242
|
try {
|
|
10740
|
-
const parsed = JSON.parse(readFileSync6(
|
|
10243
|
+
const parsed = JSON.parse(readFileSync6(path3, "utf8"));
|
|
10741
10244
|
if (typeof parsed.pid === "number" && typeof parsed.startedAt === "string" && typeof parsed.uuid === "string" && typeof parsed.proxyPort === "number") {
|
|
10742
10245
|
return { status: "valid", lock: parsed };
|
|
10743
10246
|
}
|
|
@@ -10746,13 +10249,13 @@ function inspectSessionLock() {
|
|
|
10746
10249
|
return { status: "unreadable" };
|
|
10747
10250
|
}
|
|
10748
10251
|
function writeSessionLock2(lock) {
|
|
10749
|
-
const
|
|
10750
|
-
const tempPath = `${
|
|
10751
|
-
mkdirSync6(dirname4(
|
|
10252
|
+
const path3 = getSessionLockPath2();
|
|
10253
|
+
const tempPath = `${path3}.tmp.${process.pid}`;
|
|
10254
|
+
mkdirSync6(dirname4(path3), { recursive: true });
|
|
10752
10255
|
try {
|
|
10753
10256
|
writeFileSync5(tempPath, `${JSON.stringify(lock, null, 2)}
|
|
10754
10257
|
`, "utf8");
|
|
10755
|
-
renameSync2(tempPath,
|
|
10258
|
+
renameSync2(tempPath, path3);
|
|
10756
10259
|
} finally {
|
|
10757
10260
|
try {
|
|
10758
10261
|
rmSync5(tempPath, { force: true });
|
|
@@ -10903,7 +10406,7 @@ ${pc11.bold("Description:")}
|
|
|
10903
10406
|
Keep this terminal open while using Claude.
|
|
10904
10407
|
|
|
10905
10408
|
${pc11.bold("Platforms:")}
|
|
10906
|
-
macOS and
|
|
10409
|
+
macOS, Windows, and Linux.
|
|
10907
10410
|
|
|
10908
10411
|
${pc11.bold("Cleanup:")}
|
|
10909
10412
|
Ctrl+C stops the proxy and restores your previous Claude config.
|
|
@@ -11652,26 +11155,61 @@ function printAiInstallResult(result) {
|
|
|
11652
11155
|
console.error(`relay-ai agent skill target version: v${result.version}`);
|
|
11653
11156
|
if (result.installed.length > 0) {
|
|
11654
11157
|
console.error(`Installed ${result.installed.length} new skill(s):`);
|
|
11655
|
-
for (const
|
|
11158
|
+
for (const path3 of result.installed) console.error(` \u2713 ${path3}`);
|
|
11656
11159
|
}
|
|
11657
11160
|
if (result.updated.length > 0) {
|
|
11658
11161
|
console.error(`Updated ${result.updated.length} skill(s):`);
|
|
11659
|
-
for (const { path:
|
|
11162
|
+
for (const { path: path3, fromVersion } of result.updated) {
|
|
11660
11163
|
const from = fromVersion ? `v${fromVersion}` : "unknown";
|
|
11661
|
-
console.error(` \u2713 ${
|
|
11164
|
+
console.error(` \u2713 ${path3} (${from} \u2192 v${result.version})`);
|
|
11662
11165
|
}
|
|
11663
11166
|
}
|
|
11664
11167
|
if (result.skipped.length > 0) {
|
|
11665
11168
|
console.error(`Skipped ${result.skipped.length} (already v${result.version}):`);
|
|
11666
|
-
for (const
|
|
11169
|
+
for (const path3 of result.skipped) console.error(` \xB7 ${path3}`);
|
|
11667
11170
|
}
|
|
11668
11171
|
if (result.failed.length > 0) {
|
|
11669
11172
|
console.error(`Failed ${result.failed.length}:`);
|
|
11670
|
-
for (const
|
|
11173
|
+
for (const path3 of result.failed) console.error(` \u2717 ${path3}`);
|
|
11671
11174
|
}
|
|
11672
11175
|
return result.failed.length > 0 ? 1 : 0;
|
|
11673
11176
|
}
|
|
11674
11177
|
|
|
11178
|
+
// src/http-proxy/discovery-cache.ts
|
|
11179
|
+
import * as fs2 from "fs";
|
|
11180
|
+
import * as path2 from "path";
|
|
11181
|
+
import * as os from "os";
|
|
11182
|
+
|
|
11183
|
+
// src/http-proxy/anthropic-host.ts
|
|
11184
|
+
var ANTHROPIC_UPSTREAM_HOST = "api.anthropic.com";
|
|
11185
|
+
var RELAY_SENTINEL_HOST = "api.anthropic.com.relay.invalid";
|
|
11186
|
+
var RELAY_BASE_URL = `https://${RELAY_SENTINEL_HOST}`;
|
|
11187
|
+
|
|
11188
|
+
// src/http-proxy/discovery-cache.ts
|
|
11189
|
+
var GATEWAY_DISCOVERY_BASE_URL = RELAY_BASE_URL;
|
|
11190
|
+
var CACHE_FILE = "gateway-models.json";
|
|
11191
|
+
function cachePath(baseEnv) {
|
|
11192
|
+
const claudeDir = baseEnv["CLAUDE_CONFIG_DIR"]?.trim() || path2.join(os.homedir(), ".claude");
|
|
11193
|
+
return path2.join(claudeDir, "cache", CACHE_FILE);
|
|
11194
|
+
}
|
|
11195
|
+
function writeGatewayDiscoveryCache(baseEnv, routes) {
|
|
11196
|
+
try {
|
|
11197
|
+
const models = routes.filter((route) => Boolean(route.gatewayAliasId)).map((route) => formatAnthropicModelEntry(
|
|
11198
|
+
route.gatewayAliasId,
|
|
11199
|
+
route.displayName,
|
|
11200
|
+
route.contextWindow
|
|
11201
|
+
));
|
|
11202
|
+
const file = cachePath(baseEnv);
|
|
11203
|
+
fs2.mkdirSync(path2.dirname(file), { recursive: true });
|
|
11204
|
+
fs2.writeFileSync(
|
|
11205
|
+
file,
|
|
11206
|
+
JSON.stringify({ baseUrl: GATEWAY_DISCOVERY_BASE_URL, fetchedAt: Date.now(), models }),
|
|
11207
|
+
{ encoding: "utf8", mode: 384 }
|
|
11208
|
+
);
|
|
11209
|
+
} catch {
|
|
11210
|
+
}
|
|
11211
|
+
}
|
|
11212
|
+
|
|
11675
11213
|
// src/http-proxy/env.ts
|
|
11676
11214
|
var PROXY_ENV_NAMES = [
|
|
11677
11215
|
"HTTPS_PROXY",
|
|
@@ -11753,6 +11291,8 @@ function buildHttpProxyChildEnv(baseEnv, proxyUrl, caCertPath) {
|
|
|
11753
11291
|
if (name === "ANTHROPIC_API_KEY" || name === "ANTHROPIC_AUTH_TOKEN") continue;
|
|
11754
11292
|
delete env[name];
|
|
11755
11293
|
}
|
|
11294
|
+
env["ANTHROPIC_BASE_URL"] = RELAY_BASE_URL;
|
|
11295
|
+
env["CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY"] = "1";
|
|
11756
11296
|
return env;
|
|
11757
11297
|
}
|
|
11758
11298
|
|
|
@@ -11761,14 +11301,14 @@ import { randomBytes, randomUUID as randomUUID3 } from "crypto";
|
|
|
11761
11301
|
import {
|
|
11762
11302
|
chmodSync,
|
|
11763
11303
|
existsSync as existsSync13,
|
|
11764
|
-
mkdirSync as
|
|
11304
|
+
mkdirSync as mkdirSync9,
|
|
11765
11305
|
readFileSync as readFileSync8,
|
|
11766
11306
|
readdirSync as readdirSync3,
|
|
11767
11307
|
rmSync as rmSync6,
|
|
11768
11308
|
statSync as statSync2,
|
|
11769
|
-
writeFileSync as
|
|
11309
|
+
writeFileSync as writeFileSync8
|
|
11770
11310
|
} from "fs";
|
|
11771
|
-
import { dirname as
|
|
11311
|
+
import { dirname as dirname6, join as join15, resolve } from "path";
|
|
11772
11312
|
import forge from "node-forge";
|
|
11773
11313
|
var SESSION_ROOT = "http-proxy-sessions";
|
|
11774
11314
|
var OWNER_FILE = "owner.pid";
|
|
@@ -11788,15 +11328,15 @@ function processIsRunning(pid) {
|
|
|
11788
11328
|
}
|
|
11789
11329
|
}
|
|
11790
11330
|
function cleanupStaleHttpProxySessions(appHome = getAppHome()) {
|
|
11791
|
-
const root =
|
|
11331
|
+
const root = join15(appHome, SESSION_ROOT);
|
|
11792
11332
|
if (!existsSync13(root)) return;
|
|
11793
11333
|
const now = Date.now();
|
|
11794
11334
|
for (const name of readdirSync3(root)) {
|
|
11795
|
-
const sessionDir =
|
|
11335
|
+
const sessionDir = join15(root, name);
|
|
11796
11336
|
try {
|
|
11797
11337
|
const stat = statSync2(sessionDir);
|
|
11798
11338
|
if (!stat.isDirectory()) continue;
|
|
11799
|
-
const ownerPath =
|
|
11339
|
+
const ownerPath = join15(sessionDir, OWNER_FILE);
|
|
11800
11340
|
if (!existsSync13(ownerPath)) {
|
|
11801
11341
|
if (now - stat.mtimeMs > MID_CREATION_GRACE_MS) {
|
|
11802
11342
|
rmSync6(sessionDir, { recursive: true, force: true });
|
|
@@ -11819,13 +11359,13 @@ function cleanupStaleHttpProxySessions(appHome = getAppHome()) {
|
|
|
11819
11359
|
}
|
|
11820
11360
|
function createHttpProxyCertificates(appHome = getAppHome()) {
|
|
11821
11361
|
cleanupStaleHttpProxySessions(appHome);
|
|
11822
|
-
const root =
|
|
11823
|
-
|
|
11362
|
+
const root = join15(appHome, SESSION_ROOT);
|
|
11363
|
+
mkdirSync9(root, { recursive: true, mode: 448 });
|
|
11824
11364
|
chmodSync(root, 448);
|
|
11825
|
-
const sessionDir =
|
|
11826
|
-
|
|
11365
|
+
const sessionDir = join15(root, randomUUID3());
|
|
11366
|
+
mkdirSync9(sessionDir, { mode: 448 });
|
|
11827
11367
|
chmodSync(sessionDir, 448);
|
|
11828
|
-
|
|
11368
|
+
writeFileSync8(join15(sessionDir, OWNER_FILE), `${process.pid}
|
|
11829
11369
|
`, { mode: 384 });
|
|
11830
11370
|
try {
|
|
11831
11371
|
const caKeys = forge.pki.rsa.generateKeyPair(2048);
|
|
@@ -11849,19 +11389,19 @@ function createHttpProxyCertificates(appHome = getAppHome()) {
|
|
|
11849
11389
|
server.serialNumber = serialNumber();
|
|
11850
11390
|
server.validity.notBefore = new Date(Date.now() - 6e4);
|
|
11851
11391
|
server.validity.notAfter = new Date(Date.now() + 48 * 60 * 60 * 1e3);
|
|
11852
|
-
server.setSubject([{ name: "commonName", value:
|
|
11392
|
+
server.setSubject([{ name: "commonName", value: RELAY_SENTINEL_HOST }]);
|
|
11853
11393
|
server.setIssuer(ca.subject.attributes);
|
|
11854
11394
|
server.setExtensions([
|
|
11855
11395
|
{ name: "basicConstraints", cA: false, critical: true },
|
|
11856
11396
|
{ name: "keyUsage", digitalSignature: true, keyEncipherment: true, critical: true },
|
|
11857
11397
|
{ name: "extKeyUsage", serverAuth: true },
|
|
11858
|
-
{ name: "subjectAltName", altNames: [{ type: 2, value:
|
|
11398
|
+
{ name: "subjectAltName", altNames: [{ type: 2, value: RELAY_SENTINEL_HOST }] },
|
|
11859
11399
|
{ name: "subjectKeyIdentifier" }
|
|
11860
11400
|
]);
|
|
11861
11401
|
server.sign(caKeys.privateKey, forge.md.sha256.create());
|
|
11862
11402
|
const caCert = forge.pki.certificateToPem(ca);
|
|
11863
|
-
const caCertPath =
|
|
11864
|
-
|
|
11403
|
+
const caCertPath = join15(sessionDir, "relay-ai-ca.pem");
|
|
11404
|
+
writeFileSync8(caCertPath, caCert, { encoding: "utf8", mode: 384 });
|
|
11865
11405
|
chmodSync(caCertPath, 384);
|
|
11866
11406
|
let cleaned = false;
|
|
11867
11407
|
const cleanupOnExit = () => {
|
|
@@ -11906,8 +11446,8 @@ function createHttpProxyCaBundle(relayCaCertPath, additionalCaCertPath) {
|
|
|
11906
11446
|
const relayCa = readFileSync8(relayCaCertPath, "utf8").trimEnd();
|
|
11907
11447
|
const additionalCa = readFileSync8(additionalCaCertPath, "utf8").trim();
|
|
11908
11448
|
if (!additionalCa) return relayCaCertPath;
|
|
11909
|
-
const combinedPath =
|
|
11910
|
-
|
|
11449
|
+
const combinedPath = join15(dirname6(relayCaCertPath), "combined-ca.pem");
|
|
11450
|
+
writeFileSync8(
|
|
11911
11451
|
combinedPath,
|
|
11912
11452
|
`${relayCa}
|
|
11913
11453
|
${additionalCa}
|
|
@@ -11928,6 +11468,20 @@ import { URL as URL2 } from "url";
|
|
|
11928
11468
|
// src/anthropic-endpoints.ts
|
|
11929
11469
|
var MESSAGE_PATH = "/v1/messages";
|
|
11930
11470
|
var COUNT_TOKENS_PATH = "/v1/messages/count_tokens";
|
|
11471
|
+
var MODELS_PATH = "/v1/models";
|
|
11472
|
+
function anthropicModelsEndpoint(url) {
|
|
11473
|
+
if (!url) return null;
|
|
11474
|
+
try {
|
|
11475
|
+
const pathname = new URL(url, "http://relay.local").pathname;
|
|
11476
|
+
if (pathname === MODELS_PATH || pathname === `${MODELS_PATH}/`) return "list";
|
|
11477
|
+
if (pathname.startsWith(`${MODELS_PATH}/`)) {
|
|
11478
|
+
const id = decodeURIComponent(pathname.slice(MODELS_PATH.length + 1));
|
|
11479
|
+
if (id) return { id };
|
|
11480
|
+
}
|
|
11481
|
+
} catch {
|
|
11482
|
+
}
|
|
11483
|
+
return null;
|
|
11484
|
+
}
|
|
11931
11485
|
function anthropicMessagesEndpoint(url) {
|
|
11932
11486
|
if (!url) return null;
|
|
11933
11487
|
try {
|
|
@@ -11958,7 +11512,7 @@ function estimateAnthropicInputTokens(body) {
|
|
|
11958
11512
|
}
|
|
11959
11513
|
|
|
11960
11514
|
// src/http-proxy/server.ts
|
|
11961
|
-
var ANTHROPIC_HOST =
|
|
11515
|
+
var ANTHROPIC_HOST = RELAY_SENTINEL_HOST;
|
|
11962
11516
|
var MAX_BODY_BYTES = 50 * 1024 * 1024;
|
|
11963
11517
|
var PROXY_USERNAME = "relay-ai";
|
|
11964
11518
|
function authorityParts(authority) {
|
|
@@ -12032,15 +11586,29 @@ function readRawBody(req) {
|
|
|
12032
11586
|
req.once("error", fail);
|
|
12033
11587
|
});
|
|
12034
11588
|
}
|
|
12035
|
-
function requestHeadersWithoutProxyHeaders(req) {
|
|
11589
|
+
function requestHeadersWithoutProxyHeaders(req, hostOverride) {
|
|
12036
11590
|
const headers = [];
|
|
12037
11591
|
for (let index = 0; index < req.rawHeaders.length; index += 2) {
|
|
12038
11592
|
const name = req.rawHeaders[index];
|
|
12039
11593
|
if (/^proxy-(authorization|connection)$/i.test(name)) continue;
|
|
11594
|
+
if (hostOverride && /^host$/i.test(name)) {
|
|
11595
|
+
headers.push(name, hostOverride);
|
|
11596
|
+
continue;
|
|
11597
|
+
}
|
|
12040
11598
|
headers.push(name, req.rawHeaders[index + 1] ?? "");
|
|
12041
11599
|
}
|
|
12042
11600
|
return headers;
|
|
12043
11601
|
}
|
|
11602
|
+
function restoreBillingCch(headers) {
|
|
11603
|
+
for (let index = 0; index < headers.length; index += 2) {
|
|
11604
|
+
if (!/^x-anthropic-billing-header$/i.test(headers[index])) continue;
|
|
11605
|
+
const value = headers[index + 1] ?? "";
|
|
11606
|
+
if (/(?:^|;)\s*cch=/.test(value)) break;
|
|
11607
|
+
headers[index + 1] = `${value.replace(/;?\s*$/, "")}; cch=00000;`;
|
|
11608
|
+
break;
|
|
11609
|
+
}
|
|
11610
|
+
return headers;
|
|
11611
|
+
}
|
|
12044
11612
|
function copyResponse(upstream, res) {
|
|
12045
11613
|
res.writeHead(upstream.statusCode ?? 502, upstream.statusMessage, upstream.rawHeaders);
|
|
12046
11614
|
upstream.once("error", (error) => res.destroy(error));
|
|
@@ -12061,7 +11629,7 @@ function forwardRawRequest(req, res, rawBody, origin, rejectUnauthorized) {
|
|
|
12061
11629
|
port: origin.port || void 0,
|
|
12062
11630
|
method: req.method,
|
|
12063
11631
|
path: req.url,
|
|
12064
|
-
headers: requestHeadersWithoutProxyHeaders(req),
|
|
11632
|
+
headers: restoreBillingCch(requestHeadersWithoutProxyHeaders(req, ANTHROPIC_UPSTREAM_HOST)),
|
|
12065
11633
|
...origin.protocol === "https:" ? { rejectUnauthorized } : {}
|
|
12066
11634
|
}, (upstreamRes) => {
|
|
12067
11635
|
copyResponse(upstreamRes, res);
|
|
@@ -12117,6 +11685,40 @@ function forwardToAdapter(req, res, rawBody, adapter) {
|
|
|
12117
11685
|
upstream.end(rawBody);
|
|
12118
11686
|
});
|
|
12119
11687
|
}
|
|
11688
|
+
function fetchUpstreamModelsList(req, origin, rejectUnauthorized) {
|
|
11689
|
+
return new Promise((resolve2) => {
|
|
11690
|
+
const transport = origin.protocol === "https:" ? https : http2;
|
|
11691
|
+
const rawHeaders = requestHeadersWithoutProxyHeaders(req, ANTHROPIC_UPSTREAM_HOST);
|
|
11692
|
+
const headers = ["accept-encoding", "identity"];
|
|
11693
|
+
for (let i = 0; i < rawHeaders.length; i += 2) {
|
|
11694
|
+
const name = rawHeaders[i];
|
|
11695
|
+
if (/^accept-encoding$/i.test(name)) continue;
|
|
11696
|
+
headers.push(name, rawHeaders[i + 1] ?? "");
|
|
11697
|
+
}
|
|
11698
|
+
const upstream = transport.request({
|
|
11699
|
+
protocol: origin.protocol,
|
|
11700
|
+
hostname: origin.hostname,
|
|
11701
|
+
port: origin.port || void 0,
|
|
11702
|
+
method: "GET",
|
|
11703
|
+
path: req.url,
|
|
11704
|
+
headers,
|
|
11705
|
+
...origin.protocol === "https:" ? { rejectUnauthorized } : {}
|
|
11706
|
+
}, (upstreamRes) => {
|
|
11707
|
+
const chunks = [];
|
|
11708
|
+
upstreamRes.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
|
|
11709
|
+
upstreamRes.once("end", () => {
|
|
11710
|
+
resolve2({
|
|
11711
|
+
statusCode: upstreamRes.statusCode ?? 500,
|
|
11712
|
+
headers: upstreamRes.headers,
|
|
11713
|
+
body: Buffer.concat(chunks)
|
|
11714
|
+
});
|
|
11715
|
+
});
|
|
11716
|
+
upstreamRes.once("error", () => resolve2(null));
|
|
11717
|
+
});
|
|
11718
|
+
upstream.once("error", () => resolve2(null));
|
|
11719
|
+
upstream.end();
|
|
11720
|
+
});
|
|
11721
|
+
}
|
|
12120
11722
|
function forwardPlainHttp(req, res) {
|
|
12121
11723
|
let target;
|
|
12122
11724
|
try {
|
|
@@ -12155,6 +11757,9 @@ async function startHttpProxy(options) {
|
|
|
12155
11757
|
const routesById = /* @__PURE__ */ new Map();
|
|
12156
11758
|
for (const route of options.routes) {
|
|
12157
11759
|
for (const id of routeLookupIds(route.aliasId)) routesById.set(id, route);
|
|
11760
|
+
if (route.gatewayAliasId) {
|
|
11761
|
+
for (const id of routeLookupIds(route.gatewayAliasId)) routesById.set(id, route);
|
|
11762
|
+
}
|
|
12158
11763
|
}
|
|
12159
11764
|
const anthropicOrigin = new URL2(options.anthropicOrigin ?? "https://api.anthropic.com");
|
|
12160
11765
|
if (anthropicOrigin.protocol !== "http:" && anthropicOrigin.protocol !== "https:") {
|
|
@@ -12189,6 +11794,75 @@ async function startHttpProxy(options) {
|
|
|
12189
11794
|
}
|
|
12190
11795
|
return;
|
|
12191
11796
|
}
|
|
11797
|
+
const modelsEndpoint = anthropicModelsEndpoint(req.url);
|
|
11798
|
+
if (req.method === "GET" && modelsEndpoint && options.routes.length > 0) {
|
|
11799
|
+
if (modelsEndpoint === "list") {
|
|
11800
|
+
const upstreamResult = await fetchUpstreamModelsList(
|
|
11801
|
+
req,
|
|
11802
|
+
anthropicOrigin,
|
|
11803
|
+
options.anthropicRejectUnauthorized ?? true
|
|
11804
|
+
);
|
|
11805
|
+
const relayEntries = options.routes.filter((r) => Boolean(r.gatewayAliasId)).map((r) => formatAnthropicModelEntry(
|
|
11806
|
+
r.gatewayAliasId,
|
|
11807
|
+
r.displayName,
|
|
11808
|
+
r.contextWindow
|
|
11809
|
+
));
|
|
11810
|
+
if (upstreamResult && upstreamResult.statusCode === 200) {
|
|
11811
|
+
try {
|
|
11812
|
+
const parsed = JSON.parse(upstreamResult.body.toString("utf8"));
|
|
11813
|
+
if (Array.isArray(parsed.data)) {
|
|
11814
|
+
const existingIds = new Set(parsed.data.map((m) => String(m.id ?? "")));
|
|
11815
|
+
for (const entry of relayEntries) {
|
|
11816
|
+
if (!existingIds.has(entry.id)) {
|
|
11817
|
+
parsed.data.push(entry);
|
|
11818
|
+
}
|
|
11819
|
+
}
|
|
11820
|
+
const responseBuffer = Buffer.from(JSON.stringify(parsed));
|
|
11821
|
+
const headers = { ...upstreamResult.headers };
|
|
11822
|
+
delete headers["content-length"];
|
|
11823
|
+
delete headers["content-encoding"];
|
|
11824
|
+
headers["content-type"] = "application/json";
|
|
11825
|
+
headers["content-length"] = String(responseBuffer.length);
|
|
11826
|
+
res.writeHead(200, headers);
|
|
11827
|
+
res.end(responseBuffer);
|
|
11828
|
+
return;
|
|
11829
|
+
}
|
|
11830
|
+
} catch {
|
|
11831
|
+
}
|
|
11832
|
+
}
|
|
11833
|
+
const fallbackList = formatAnthropicModelList(
|
|
11834
|
+
options.routes.map((r) => ({
|
|
11835
|
+
id: r.gatewayAliasId ?? r.aliasId,
|
|
11836
|
+
name: r.displayName,
|
|
11837
|
+
contextWindow: r.contextWindow
|
|
11838
|
+
}))
|
|
11839
|
+
);
|
|
11840
|
+
const buffer = Buffer.from(JSON.stringify(fallbackList));
|
|
11841
|
+
res.writeHead(200, {
|
|
11842
|
+
"content-type": "application/json",
|
|
11843
|
+
"content-length": String(buffer.length)
|
|
11844
|
+
});
|
|
11845
|
+
res.end(buffer);
|
|
11846
|
+
return;
|
|
11847
|
+
}
|
|
11848
|
+
if (typeof modelsEndpoint === "object" && modelsEndpoint.id) {
|
|
11849
|
+
const matched = routesById.get(modelsEndpoint.id);
|
|
11850
|
+
if (matched) {
|
|
11851
|
+
const entry = formatAnthropicModelEntry(
|
|
11852
|
+
matched.gatewayAliasId ?? matched.aliasId,
|
|
11853
|
+
matched.displayName,
|
|
11854
|
+
matched.contextWindow
|
|
11855
|
+
);
|
|
11856
|
+
const buffer = Buffer.from(JSON.stringify(entry));
|
|
11857
|
+
res.writeHead(200, {
|
|
11858
|
+
"content-type": "application/json",
|
|
11859
|
+
"content-length": String(buffer.length)
|
|
11860
|
+
});
|
|
11861
|
+
res.end(buffer);
|
|
11862
|
+
return;
|
|
11863
|
+
}
|
|
11864
|
+
}
|
|
11865
|
+
}
|
|
12192
11866
|
const endpoint = anthropicMessagesEndpoint(req.url);
|
|
12193
11867
|
if (req.method === "POST" && endpoint) {
|
|
12194
11868
|
let parsed = null;
|
|
@@ -12390,6 +12064,7 @@ async function launchClaudeWithHttpProxy(options, dependencies = defaultDependen
|
|
|
12390
12064
|
proxy.handle.proxyUrl,
|
|
12391
12065
|
proxy.handle.caCertPath
|
|
12392
12066
|
);
|
|
12067
|
+
writeGatewayDiscoveryCache(options.baseEnv, proxy.loaded.routes);
|
|
12393
12068
|
const exitCode = await dependencies.launch(
|
|
12394
12069
|
childEnv,
|
|
12395
12070
|
proxy.startingModel,
|
|
@@ -13763,7 +13438,7 @@ Options:
|
|
|
13763
13438
|
--trace Write debug logs under ~/.relay-ai/logs/`);
|
|
13764
13439
|
return 0;
|
|
13765
13440
|
}
|
|
13766
|
-
const { runUiCommand } = await import("./ui-command-
|
|
13441
|
+
const { runUiCommand } = await import("./ui-command-5VEFZ5GM.js");
|
|
13767
13442
|
return runUiCommand({ trace: parsed.trace, serverMode: parsed.uiServerMode });
|
|
13768
13443
|
}
|
|
13769
13444
|
if (parsed.command === "models") {
|