@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/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-IYYLLN5T.js";
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-EJONCU3B.js";
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 path2 = (isWindows ? lines.find((l) => l.toLowerCase().endsWith(".cmd")) : null) ?? lines[0];
212
- if (path2) return path2;
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 path2 of OPENCODE_FALLBACK_PATHS) {
216
- if (existsSync(path2)) return path2;
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: path2 } = detectShellProfile();
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(path2) ? readFileSync(path2, "utf8") : "";
660
+ const existing = existsSync2(path3) ? readFileSync(path3, "utf8") : "";
652
661
  if (!existing.includes(autoLoadLine)) {
653
- appendFileSync(path2, `
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(path2)) appendFileSync(path2, "");
697
+ if (!existsSync2(path3)) appendFileSync(path3, "");
689
698
  const escapedKey = trimmedKey.replace(/'/g, "'\\''");
690
- appendFileSync(path2, `
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.urlPrompt) {
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(path2, content) {
3800
- mkdirSync(dirname(path2), { recursive: true });
3801
- const tmp = `${path2}.tmp.${process.pid}`;
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, path2);
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(path2, content, env = process.env) {
3822
- rotateBackups(path2, env);
3823
- atomicWriteFile(path2, content);
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 path2 = getSessionLockPath(env);
3827
- if (!existsSync3(path2)) return null;
3844
+ const path3 = getSessionLockPath(env);
3845
+ if (!existsSync3(path3)) return null;
3828
3846
  try {
3829
- const parsed = JSON.parse(readFileSync2(path2, "utf8"));
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 path2 = getSessionLockPath(env);
3854
+ const path3 = getSessionLockPath(env);
3837
3855
  mkdirSync(getRelayAiCodexDir(env), { recursive: true });
3838
- atomicWriteFile(path2, `${JSON.stringify(lock, null, 2)}
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 path2 of ownedOverlayPaths(env)) {
3856
- if (!existsSync3(path2)) continue;
3873
+ for (const path3 of ownedOverlayPaths(env)) {
3874
+ if (!existsSync3(path3)) continue;
3857
3875
  try {
3858
- rmSync(path2, { force: true });
3859
- removed.push(path2);
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 path2 of candidates) {
4007
- if (!path2 || seen.has(path2)) continue;
4008
- seen.add(path2);
4009
- if (exists(path2) && canRun(path2)) return path2;
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(path2) {
4031
+ function canRunCodexBinary(path3) {
4014
4032
  try {
4015
- execFileSync(path2, ["--version"], {
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
- startSse();
8611
- const chunk = formatCloudCodeChunk({
8612
- text: text4,
8613
- modelVersion: route.catalogId,
8614
- responseId
8615
- });
8616
- res.write(`data: ${JSON.stringify(chunk)}
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(resolved, maxRoutes),
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 path2 = result.trim().split("\n")[0]?.trim();
8809
- if (path2) return path2;
8253
+ const path3 = result.trim().split("\n")[0]?.trim();
8254
+ if (path3) return path3;
8810
8255
  } catch {
8811
8256
  }
8812
- for (const path2 of FALLBACK_PATHS) {
8813
- if (existsSync6(path2)) return path2;
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
- if (process.platform !== "darwin") return "";
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", ["-axo", "pid=,command="], {
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('Antigravity app bundle not found at "/Applications/Antigravity.app".');
9067
- console.error("Please make sure Antigravity is installed on your Mac.");
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
- stdio: "inherit",
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('Antigravity IDE app bundle not found at "/Applications/Antigravity IDE.app".');
9097
- console.error("Please make sure Antigravity IDE is installed on your Mac.");
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
- stdio: "inherit",
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(path2 = getCodexConfigPath()) {
9617
- if (!existsSync8(path2)) return "";
9618
- return readFileSync3(path2, "utf8");
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 path2 = getAppSessionLockPath(env);
9827
- if (!existsSync9(path2)) return null;
9326
+ const path3 = getAppSessionLockPath(env);
9327
+ if (!existsSync9(path3)) return null;
9828
9328
  try {
9829
- const parsed = JSON.parse(readFileSync4(path2, "utf8"));
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 path2 = getAppSessionLockPath(env);
9841
- if (existsSync9(path2)) rmSync4(path2, { force: true });
9340
+ const path3 = getAppSessionLockPath(env);
9341
+ if (existsSync9(path3)) rmSync4(path3, { force: true });
9842
9342
  }
9843
9343
  function readAppRestoreState(env = process.env) {
9844
- const path2 = getAppRestoreStatePath(env);
9845
- if (!existsSync9(path2)) return null;
9344
+ const path3 = getAppRestoreStatePath(env);
9345
+ if (!existsSync9(path3)) return null;
9846
9346
  try {
9847
- return JSON.parse(readFileSync4(path2, "utf8"));
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 path2 = getAppRestoreStatePath(env);
9859
- if (existsSync9(path2)) rmSync4(path2, { force: true });
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 path2 of ownedAppCatalogPaths(env)) {
9389
+ for (const path3 of ownedAppCatalogPaths(env)) {
9890
9390
  try {
9891
- rmSync4(path2, { force: true });
9892
- removed.push(path2);
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 path2 = getSessionLockPath2();
10738
- if (!existsSync11(path2)) return { status: "missing" };
10240
+ const path3 = getSessionLockPath2();
10241
+ if (!existsSync11(path3)) return { status: "missing" };
10739
10242
  try {
10740
- const parsed = JSON.parse(readFileSync6(path2, "utf8"));
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 path2 = getSessionLockPath2();
10750
- const tempPath = `${path2}.tmp.${process.pid}`;
10751
- mkdirSync6(dirname4(path2), { recursive: true });
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, path2);
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 Windows. Linux is not supported.
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 path2 of result.installed) console.error(` \u2713 ${path2}`);
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: path2, fromVersion } of result.updated) {
11162
+ for (const { path: path3, fromVersion } of result.updated) {
11660
11163
  const from = fromVersion ? `v${fromVersion}` : "unknown";
11661
- console.error(` \u2713 ${path2} (${from} \u2192 v${result.version})`);
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 path2 of result.skipped) console.error(` \xB7 ${path2}`);
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 path2 of result.failed) console.error(` \u2717 ${path2}`);
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 mkdirSync8,
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 writeFileSync7
11309
+ writeFileSync as writeFileSync8
11770
11310
  } from "fs";
11771
- import { dirname as dirname5, join as join14, resolve } from "path";
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 = join14(appHome, SESSION_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 = join14(root, name);
11335
+ const sessionDir = join15(root, name);
11796
11336
  try {
11797
11337
  const stat = statSync2(sessionDir);
11798
11338
  if (!stat.isDirectory()) continue;
11799
- const ownerPath = join14(sessionDir, OWNER_FILE);
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 = join14(appHome, SESSION_ROOT);
11823
- mkdirSync8(root, { recursive: true, mode: 448 });
11362
+ const root = join15(appHome, SESSION_ROOT);
11363
+ mkdirSync9(root, { recursive: true, mode: 448 });
11824
11364
  chmodSync(root, 448);
11825
- const sessionDir = join14(root, randomUUID3());
11826
- mkdirSync8(sessionDir, { mode: 448 });
11365
+ const sessionDir = join15(root, randomUUID3());
11366
+ mkdirSync9(sessionDir, { mode: 448 });
11827
11367
  chmodSync(sessionDir, 448);
11828
- writeFileSync7(join14(sessionDir, OWNER_FILE), `${process.pid}
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: "api.anthropic.com" }]);
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: "api.anthropic.com" }] },
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 = join14(sessionDir, "relay-ai-ca.pem");
11864
- writeFileSync7(caCertPath, caCert, { encoding: "utf8", mode: 384 });
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 = join14(dirname5(relayCaCertPath), "combined-ca.pem");
11910
- writeFileSync7(
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 = "api.anthropic.com";
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-3CWMSARO.js");
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") {