@jacobbd/relay-ai 0.6.3 → 0.7.1

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.
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  getTemplateById,
4
4
  init_provider_templates
5
- } from "./chunk-EJONCU3B.js";
5
+ } from "./chunk-HXGZ4CTV.js";
6
6
 
7
7
  // src/constants.ts
8
8
  import { homedir } from "os";
@@ -11,7 +11,7 @@ import { join } from "path";
11
11
  // package.json
12
12
  var package_default = {
13
13
  name: "@jacobbd/relay-ai",
14
- version: "0.6.3",
14
+ version: "0.7.1",
15
15
  publishConfig: {
16
16
  access: "public"
17
17
  },
@@ -46,7 +46,7 @@ var package_default = {
46
46
  node: ">=18"
47
47
  },
48
48
  scripts: {
49
- build: "tsup && node scripts/copy-ui-assets.mjs",
49
+ build: "tsup && tsup --config tsup.core.config.ts && node scripts/copy-ui-assets.mjs",
50
50
  dev: "tsup --watch",
51
51
  test: "vitest run",
52
52
  "test:watch": "vitest",
@@ -100,6 +100,14 @@ var package_default = {
100
100
  },
101
101
  overrides: {
102
102
  ws: "^8.21.0"
103
+ },
104
+ exports: {
105
+ "./core": {
106
+ types: "./dist/core/index.d.ts",
107
+ import: "./dist/core/index.js",
108
+ default: "./dist/core/index.js"
109
+ },
110
+ "./package.json": "./package.json"
103
111
  }
104
112
  };
105
113
 
@@ -149,6 +157,7 @@ var PARENT_SESSION_ENV_VARS = [
149
157
  ];
150
158
  var OPENCODE_CACHE_PATH = join(homedir(), ".cache", "opencode", "models.json");
151
159
  var MAX_MODEL_CATALOG = 20;
160
+ var MIN_CONTEXT_WINDOW = 128e3;
152
161
  var VERTEX_ANTHROPIC_NPM = "@ai-sdk/google-vertex/anthropic";
153
162
  function classifyModelFormat(modelId, providerNpm) {
154
163
  if (providerNpm === "@ai-sdk/anthropic") return "anthropic";
@@ -1464,13 +1473,36 @@ function providerSelectOption(provider) {
1464
1473
  }
1465
1474
  function modelSelectOption(model, hint) {
1466
1475
  const label = formatCodexModelLabel(model);
1467
- const defaultHint = hint ?? (model.name !== model.id ? model.id : model.brand || model.family || "");
1476
+ let defaultHint = hint;
1477
+ if (!defaultHint) {
1478
+ const isCloudflare = model.id.startsWith("@cf/") || model.id.startsWith("@hf/");
1479
+ if (model.isFree) {
1480
+ defaultHint = pc.green(isCloudflare ? "Free (10k/day)" : "Free");
1481
+ } else if (model.cost && (model.cost.input > 0 || model.cost.output > 0)) {
1482
+ const inputStr = `$${model.cost.input}`;
1483
+ const outputStr = `$${model.cost.output}`;
1484
+ defaultHint = pc.dim(isCloudflare ? `Paid plan req (${inputStr}/${outputStr} 1M)` : `${inputStr}/${outputStr} 1M`);
1485
+ } else {
1486
+ defaultHint = model.name !== model.id ? model.id : model.brand || model.family || "";
1487
+ }
1488
+ } else if (hint === "recent") {
1489
+ const isCloudflare = model.id.startsWith("@cf/") || model.id.startsWith("@hf/");
1490
+ const freeLabel = isCloudflare ? "Free (10k/day)" : "Free";
1491
+ const freeSuffix = model.isFree ? " \xB7 " + pc.green(freeLabel) : "";
1492
+ defaultHint = fmtRecentHint() + freeSuffix;
1493
+ }
1494
+ const ctxSuffix = fmtContextWindow(model.contextWindow);
1468
1495
  return {
1469
1496
  value: model.id,
1470
1497
  label: fmtModel(label),
1471
- hint: hint === "recent" ? fmtRecentHint() : defaultHint
1498
+ hint: defaultHint && ctxSuffix ? `${defaultHint} \xB7 ${ctxSuffix}` : defaultHint || ctxSuffix
1472
1499
  };
1473
1500
  }
1501
+ function fmtContextWindow(contextWindow) {
1502
+ if (!contextWindow) return "";
1503
+ const k = contextWindow >= 1e3 ? `${Math.round(contextWindow / 1e3)}k` : String(contextWindow);
1504
+ return pc.dim(`${k} ctx`);
1505
+ }
1474
1506
  function navOption(value, label, hint = "") {
1475
1507
  return { value, label: pc.cyan(label), hint };
1476
1508
  }
@@ -3627,7 +3659,7 @@ function parseRegistry(raw) {
3627
3659
  if (typeof data.pricingCacheAt === "string") registry.pricingCacheAt = data.pricingCacheAt;
3628
3660
  return registry;
3629
3661
  }
3630
- function loadRegistry(path = getProvidersPath()) {
3662
+ function loadRegistry(path = getProvidersPath(), { persist = true } = {}) {
3631
3663
  if (!existsSync6(path)) {
3632
3664
  return { schemaVersion: REGISTRY_SCHEMA_VERSION, providers: [] };
3633
3665
  }
@@ -3638,7 +3670,7 @@ function loadRegistry(path = getProvidersPath()) {
3638
3670
  if (migrateOAuthOpenAiProvider(registry)) migrated = true;
3639
3671
  if (migrateOAuthXaiProvider(registry)) migrated = true;
3640
3672
  if (migrateAlibabaDashScopeChinaLabel(registry)) migrated = true;
3641
- if (migrated) {
3673
+ if (migrated && persist) {
3642
3674
  try {
3643
3675
  saveRegistry(registry, path);
3644
3676
  } catch {
@@ -3681,6 +3713,7 @@ function isFreeProviderAccess(providerId, templateId) {
3681
3713
  return FREE_PROVIDER_IDS.has((providerId ?? "").toLowerCase()) || FREE_PROVIDER_IDS.has((templateId ?? "").toLowerCase());
3682
3714
  }
3683
3715
  function classifyFreeStatus(opts) {
3716
+ if (opts.freeAccess === true) return "free_provider";
3684
3717
  if (isFreeProviderAccess(opts.providerId, opts.templateId)) return "free_provider";
3685
3718
  if (isZeroCost(opts.model.cost)) return "verified_free";
3686
3719
  if (isPaidCost(opts.model.cost)) return "paid";
@@ -3842,7 +3875,12 @@ function enrichModelsWithPricing(models, index, platform) {
3842
3875
  return models.map((model) => {
3843
3876
  const cost = lookupModelCost(index, model.id, platform) ?? lookupModelCost(index, model.upstreamModelId, platform);
3844
3877
  if (!cost) return model;
3845
- const freeStatus = classifyFreeStatus({ model: { ...model, cost } });
3878
+ const freeStatus = classifyFreeStatus({
3879
+ model: { ...model, cost },
3880
+ // Keep provider-granted free access (e.g. Cloudflare's daily allowance) when
3881
+ // real pricing resolves later.
3882
+ freeAccess: model.freeStatus === "free_provider"
3883
+ });
3846
3884
  return { ...model, cost, isFree: isFreeStatus(freeStatus), freeStatus };
3847
3885
  });
3848
3886
  }
@@ -7280,16 +7318,707 @@ function localProvidersToServerModels(localProviders) {
7280
7318
  );
7281
7319
  }
7282
7320
 
7321
+ // src/antigravity/slot-registry.ts
7322
+ var AGY_SLOT_VALIDATION_SOURCE = "AGY CLI 1.0.10 / Antigravity IDE 2.1.1 fixture capture 2026-06-23";
7323
+ var AGY_NATIVE_SLOT_REGISTRY = [
7324
+ {
7325
+ slotId: "gemini-3.5-flash-low",
7326
+ model: "MODEL_PLACEHOLDER_M20",
7327
+ role: "agent-switch",
7328
+ status: "validated",
7329
+ validatedWith: AGY_SLOT_VALIDATION_SOURCE
7330
+ },
7331
+ {
7332
+ slotId: "gemini-3.5-flash-extra-low",
7333
+ model: "MODEL_PLACEHOLDER_M187",
7334
+ role: "agent-switch",
7335
+ status: "validated",
7336
+ validatedWith: AGY_SLOT_VALIDATION_SOURCE
7337
+ },
7338
+ {
7339
+ slotId: "gemini-3.1-pro-low",
7340
+ model: "MODEL_PLACEHOLDER_M36",
7341
+ role: "agent-switch",
7342
+ status: "validated",
7343
+ validatedWith: AGY_SLOT_VALIDATION_SOURCE
7344
+ },
7345
+ {
7346
+ slotId: "gemini-pro-agent",
7347
+ model: "MODEL_PLACEHOLDER_M16",
7348
+ role: "agent-switch",
7349
+ status: "validated",
7350
+ validatedWith: AGY_SLOT_VALIDATION_SOURCE
7351
+ },
7352
+ {
7353
+ slotId: "claude-sonnet-4-6",
7354
+ model: "MODEL_PLACEHOLDER_M35",
7355
+ role: "agent-switch",
7356
+ status: "validated",
7357
+ validatedWith: AGY_SLOT_VALIDATION_SOURCE
7358
+ },
7359
+ {
7360
+ slotId: "claude-opus-4-6-thinking",
7361
+ model: "MODEL_PLACEHOLDER_M26",
7362
+ role: "agent-switch",
7363
+ status: "validated",
7364
+ validatedWith: AGY_SLOT_VALIDATION_SOURCE
7365
+ },
7366
+ {
7367
+ slotId: "gpt-oss-120b-medium",
7368
+ model: "MODEL_OPENAI_GPT_OSS_120B_MEDIUM",
7369
+ role: "agent-switch",
7370
+ status: "validated",
7371
+ validatedWith: AGY_SLOT_VALIDATION_SOURCE
7372
+ },
7373
+ {
7374
+ slotId: "gemini-3-flash-agent",
7375
+ model: "MODEL_PLACEHOLDER_M132",
7376
+ role: "cascade-plan",
7377
+ status: "reserved",
7378
+ validatedWith: AGY_SLOT_VALIDATION_SOURCE,
7379
+ notes: "Visible in agentModelSorts, but reserved for cascade plan construction."
7380
+ },
7381
+ {
7382
+ slotId: "gemini-2.5-flash",
7383
+ model: "MODEL_GOOGLE_GEMINI_2_5_FLASH",
7384
+ role: "cascade-intent",
7385
+ status: "reserved",
7386
+ validatedWith: AGY_SLOT_VALIDATION_SOURCE
7387
+ },
7388
+ {
7389
+ slotId: "gemini-2.5-flash-lite",
7390
+ model: "MODEL_GOOGLE_GEMINI_2_5_FLASH_LITE",
7391
+ role: "cascade-fallback",
7392
+ status: "reserved",
7393
+ validatedWith: AGY_SLOT_VALIDATION_SOURCE
7394
+ },
7395
+ {
7396
+ slotId: "gemini-3.1-pro-high",
7397
+ model: "MODEL_PLACEHOLDER_M37",
7398
+ role: "agent-switch",
7399
+ status: "candidate",
7400
+ validatedWith: AGY_SLOT_VALIDATION_SOURCE,
7401
+ notes: "Model-shaped fixture entry; requires live switching proof before promotion."
7402
+ },
7403
+ {
7404
+ slotId: "gemini-2.5-pro",
7405
+ model: "MODEL_GOOGLE_GEMINI_2_5_PRO",
7406
+ role: "agent-switch",
7407
+ status: "candidate",
7408
+ validatedWith: AGY_SLOT_VALIDATION_SOURCE,
7409
+ notes: "Model-shaped fixture entry; requires live switching proof before promotion."
7410
+ },
7411
+ {
7412
+ slotId: "gemini-2.5-flash-thinking",
7413
+ model: "MODEL_GOOGLE_GEMINI_2_5_FLASH_THINKING",
7414
+ role: "agent-switch",
7415
+ status: "candidate",
7416
+ validatedWith: AGY_SLOT_VALIDATION_SOURCE,
7417
+ notes: "Model-shaped fixture entry; requires live switching proof before promotion."
7418
+ },
7419
+ {
7420
+ slotId: "gemini-3-flash",
7421
+ model: "MODEL_PLACEHOLDER_M18",
7422
+ role: "command",
7423
+ status: "candidate",
7424
+ validatedWith: AGY_SLOT_VALIDATION_SOURCE,
7425
+ notes: "Command model in the fixture; not switch-safe without live proof."
7426
+ },
7427
+ {
7428
+ slotId: "gemini-3.1-flash-lite",
7429
+ model: "MODEL_PLACEHOLDER_M50",
7430
+ role: "cascade-checkpoint",
7431
+ status: "candidate",
7432
+ validatedWith: AGY_SLOT_VALIDATION_SOURCE,
7433
+ notes: "Checkpoint/search/commit slot; route as helper until live proof exists."
7434
+ },
7435
+ {
7436
+ slotId: "gemini-3.1-flash-image",
7437
+ model: "MODEL_PLACEHOLDER_M21",
7438
+ role: "image",
7439
+ status: "candidate",
7440
+ validatedWith: AGY_SLOT_VALIDATION_SOURCE,
7441
+ notes: "Image generation slot; not switch-safe without live proof."
7442
+ },
7443
+ {
7444
+ slotId: "tab_jump_flash_lite_preview",
7445
+ model: "MODEL_PLACEHOLDER_M28",
7446
+ role: "tab",
7447
+ status: "unsafe",
7448
+ validatedWith: AGY_SLOT_VALIDATION_SOURCE
7449
+ },
7450
+ {
7451
+ slotId: "tab_flash_lite_preview",
7452
+ model: "MODEL_PLACEHOLDER_M19",
7453
+ role: "tab",
7454
+ status: "unsafe",
7455
+ validatedWith: AGY_SLOT_VALIDATION_SOURCE
7456
+ },
7457
+ {
7458
+ slotId: "chat_20706",
7459
+ model: "MODEL_CHAT_20706",
7460
+ role: "chat",
7461
+ status: "unsafe",
7462
+ validatedWith: AGY_SLOT_VALIDATION_SOURCE
7463
+ },
7464
+ {
7465
+ slotId: "chat_23310",
7466
+ model: "MODEL_CHAT_23310",
7467
+ role: "chat",
7468
+ status: "unsafe",
7469
+ validatedWith: AGY_SLOT_VALIDATION_SOURCE
7470
+ }
7471
+ ];
7472
+ var KNOWN_COMPATIBLE_AGY_VERSIONS = /* @__PURE__ */ new Set([
7473
+ "1.0.10",
7474
+ "1.1.0",
7475
+ "1.1.1",
7476
+ "1.1.2",
7477
+ "1.1.3",
7478
+ "1.1.4",
7479
+ "1.1.5",
7480
+ "1.1.6",
7481
+ "1.1.7"
7482
+ ]);
7483
+ var KNOWN_INCOMPATIBLE_AGY_VERSIONS = /* @__PURE__ */ new Set(["1.0.9"]);
7484
+ function withFixtureModel(definition, model) {
7485
+ return model === definition.model ? definition : { ...definition, model };
7486
+ }
7487
+ function assertNoDuplicateSwitchEnums(fixture, definitions) {
7488
+ const seen = /* @__PURE__ */ new Map();
7489
+ for (const definition of definitions) {
7490
+ if (definition.status !== "validated") continue;
7491
+ const actualModel = fixture.models[definition.slotId]?.model;
7492
+ if (!actualModel) continue;
7493
+ const previousSlotId = seen.get(actualModel);
7494
+ if (previousSlotId) {
7495
+ throw new Error(
7496
+ `Duplicate AGY switch slot enum ${actualModel}: ${previousSlotId} and ${definition.slotId}`
7497
+ );
7498
+ }
7499
+ seen.set(actualModel, definition.slotId);
7500
+ }
7501
+ }
7502
+ function validateAgySlotRegistry(fixture) {
7503
+ assertNoDuplicateSwitchEnums(fixture, AGY_NATIVE_SLOT_REGISTRY);
7504
+ const switchSlots = [];
7505
+ const reservedSlots = [];
7506
+ const candidateSlots = [];
7507
+ const warnings = [];
7508
+ for (const definition of AGY_NATIVE_SLOT_REGISTRY) {
7509
+ const entry = fixture.models[definition.slotId];
7510
+ if (!entry) {
7511
+ if (definition.status === "validated" || definition.status === "reserved") {
7512
+ warnings.push(`AGY slot ${definition.slotId} missing from fixture`);
7513
+ }
7514
+ continue;
7515
+ }
7516
+ if (entry.model !== definition.model) {
7517
+ warnings.push(
7518
+ `AGY slot ${definition.slotId} expected ${definition.model} but fixture has ${entry.model}`
7519
+ );
7520
+ continue;
7521
+ }
7522
+ if (definition.status === "validated") {
7523
+ switchSlots.push(withFixtureModel(definition, entry.model));
7524
+ } else if (definition.status === "reserved") {
7525
+ reservedSlots.push(withFixtureModel(definition, entry.model));
7526
+ } else if (definition.status === "candidate") {
7527
+ candidateSlots.push(withFixtureModel(definition, entry.model));
7528
+ }
7529
+ }
7530
+ return { switchSlots, reservedSlots, candidateSlots, warnings };
7531
+ }
7532
+ function getValidatedAgySwitchSlots(fixture) {
7533
+ return validateAgySlotRegistry(fixture).switchSlots;
7534
+ }
7535
+ function evaluateAgySwitchCompatibility(opts) {
7536
+ const validation = validateAgySlotRegistry(opts.fixture);
7537
+ const shapeMatches = validation.warnings.length === 0 && validation.switchSlots.length > 0;
7538
+ const warnings = [];
7539
+ if (opts.versionReadError) {
7540
+ warnings.push(`Could not read agy --version (${opts.versionReadError}); validating AGY fixture shape instead.`);
7541
+ }
7542
+ if (opts.version && KNOWN_INCOMPATIBLE_AGY_VERSIONS.has(opts.version)) {
7543
+ return {
7544
+ mode: "single-model",
7545
+ validatedSwitchSlotCount: validation.switchSlots.length,
7546
+ warnings: [
7547
+ ...warnings,
7548
+ `Known-incompatible AGY version ${opts.version}; falling back to single-model mode.`
7549
+ ]
7550
+ };
7551
+ }
7552
+ if (!shapeMatches) {
7553
+ return {
7554
+ mode: "single-model",
7555
+ validatedSwitchSlotCount: validation.switchSlots.length,
7556
+ warnings: [
7557
+ ...warnings,
7558
+ ...validation.warnings,
7559
+ "AGY fixture shape does not match the validated slot registry; falling back to single-model mode."
7560
+ ]
7561
+ };
7562
+ }
7563
+ if (opts.version && !KNOWN_COMPATIBLE_AGY_VERSIONS.has(opts.version)) {
7564
+ return {
7565
+ mode: "single-model",
7566
+ validatedSwitchSlotCount: validation.switchSlots.length,
7567
+ warnings: [
7568
+ ...warnings,
7569
+ `Unvalidated AGY version ${opts.version}; falling back to single-model mode for maximum stability.`
7570
+ ]
7571
+ };
7572
+ } else if (!opts.version && !opts.versionReadError) {
7573
+ warnings.push("AGY version is unknown; fixture shape matches, so multi-model switching remains enabled.");
7574
+ }
7575
+ return {
7576
+ mode: "multi-model",
7577
+ validatedSwitchSlotCount: validation.switchSlots.length,
7578
+ warnings
7579
+ };
7580
+ }
7581
+
7582
+ // src/antigravity/catalog.ts
7583
+ var RELAY_CASCADE_PLAN_MODEL = "MODEL_PLACEHOLDER_M132";
7584
+ var RELAY_AGENT_PLACEHOLDER = "MODEL_PLACEHOLDER_M20";
7585
+ var RELAY_CASCADE_CHECKPOINT_MODEL = "MODEL_PLACEHOLDER_M50";
7586
+ var RELAY_CASCADE_INTENT_MODEL = "MODEL_GOOGLE_GEMINI_2_5_FLASH";
7587
+ var RELAY_CASCADE_ANCHOR_ID = "gemini-3.5-flash-low";
7588
+ var RELAY_CASCADE_PLAN_ANCHOR_ID = "gemini-3-flash-agent";
7589
+ var RELAY_CASCADE_FALLBACK_ID = "gemini-2.5-flash-lite";
7590
+ var RELAY_CASCADE_INTENT_MODEL_ID = "gemini-2.5-flash";
7591
+ function withCascadeCheckpointer(entry, maxTokenLimit = 128e3) {
7592
+ const tokenThreshold = Math.min(5e4, Math.floor(maxTokenLimit * 0.75));
7593
+ const existingModelExperiments = entry.modelExperiments;
7594
+ entry.modelExperiments = {
7595
+ ...existingModelExperiments,
7596
+ experiments: {
7597
+ ...existingModelExperiments?.experiments ?? {},
7598
+ CASCADE_USE_EXPERIMENT_CHECKPOINTER: {
7599
+ stringValue: JSON.stringify({
7600
+ strategy: "CHECKPOINT_STRATEGY_SAME_MODEL",
7601
+ max_token_limit: String(maxTokenLimit),
7602
+ token_threshold: String(tokenThreshold),
7603
+ max_overhead_ratio: "0.15",
7604
+ moving_window_size: "1",
7605
+ enabled: true,
7606
+ max_output_tokens: "16384",
7607
+ checkpoint_model: RELAY_CASCADE_CHECKPOINT_MODEL,
7608
+ use_last_planner_model: true,
7609
+ is_sync: true,
7610
+ max_user_requests: 10,
7611
+ include_last_user_message: true,
7612
+ include_conversation_log: false,
7613
+ include_running_task_snapshots: true,
7614
+ include_subagent_snapshots: true,
7615
+ include_artifact_snapshots: true,
7616
+ retry_config: {
7617
+ max_retries: 0,
7618
+ initial_sleep_duration_ms: 1e3,
7619
+ exponential_multiplier: 2,
7620
+ include_error_feedback: false
7621
+ }
7622
+ })
7623
+ }
7624
+ }
7625
+ };
7626
+ return entry;
7627
+ }
7628
+ var ANTIGRAVITY_REQUIRED_INPUT_TOKENS = 128e3;
7629
+ var ANTIGRAVITY_MIN_OUTPUT_TOKENS = 8192;
7630
+ var ANTIGRAVITY_MIN_CONTEXT_WINDOW = ANTIGRAVITY_REQUIRED_INPUT_TOKENS + ANTIGRAVITY_MIN_OUTPUT_TOKENS;
7631
+ function applyRouteContextBounds(entry, route) {
7632
+ const maxTokenLimit = route.contextWindow ?? 128e3;
7633
+ const maxOutputTokens = Math.min(
7634
+ entry.maxOutputTokens ?? 65536,
7635
+ Math.max(
7636
+ ANTIGRAVITY_MIN_OUTPUT_TOKENS,
7637
+ maxTokenLimit - ANTIGRAVITY_REQUIRED_INPUT_TOKENS
7638
+ )
7639
+ );
7640
+ const checkpointTokenLimit = Math.min(
7641
+ 128e3,
7642
+ Math.max(1, maxTokenLimit - maxOutputTokens)
7643
+ );
7644
+ entry.maxTokens = maxTokenLimit;
7645
+ entry.maxOutputTokens = maxOutputTokens;
7646
+ return withCascadeCheckpointer(entry, checkpointTokenLimit);
7647
+ }
7648
+ var RELAY_CASCADE_FALLBACK_ENTRY = withCascadeCheckpointer({
7649
+ displayName: "Gemini 3.1 Flash Lite",
7650
+ model: "MODEL_GOOGLE_GEMINI_2_5_FLASH_LITE",
7651
+ apiProvider: "API_PROVIDER_GOOGLE_GEMINI",
7652
+ modelProvider: "MODEL_PROVIDER_GOOGLE",
7653
+ tokenizerType: "LLAMA_WITH_SPECIAL",
7654
+ maxTokens: 1048576,
7655
+ maxOutputTokens: 65535,
7656
+ quotaInfo: { remainingFraction: 1 }
7657
+ });
7658
+ var RELAY_CASCADE_INTENT_MODEL_ENTRY = withCascadeCheckpointer({
7659
+ ...RELAY_CASCADE_FALLBACK_ENTRY,
7660
+ model: RELAY_CASCADE_INTENT_MODEL
7661
+ });
7662
+ function planRelayCatalogSlots(catalog, routes, templateKey) {
7663
+ const validation = validateAgySlotRegistry(catalog);
7664
+ const switchSlots = getValidatedAgySwitchSlots(catalog);
7665
+ const templateSlot = switchSlots.find((slot) => slot.slotId === templateKey);
7666
+ const orderedSlots = templateSlot ? [templateSlot, ...switchSlots.filter((slot) => slot.slotId !== templateKey)] : switchSlots;
7667
+ if (routes.length > 0 && orderedSlots.length === 0) {
7668
+ throw new Error("No validated AGY switch slots are available for the selected launch route");
7669
+ }
7670
+ const switchableRoutes = routes.slice(0, orderedSlots.length);
7671
+ const skippedRoutes = routes.slice(orderedSlots.length);
7672
+ const slots = switchableRoutes.map((route, index) => ({
7673
+ slotId: orderedSlots[index].slotId,
7674
+ route
7675
+ }));
7676
+ return {
7677
+ slots,
7678
+ switchableRoutes,
7679
+ skippedRoutes,
7680
+ validation
7681
+ };
7682
+ }
7683
+ function resolveRelayCatalogSlots(catalog, routes, templateKey) {
7684
+ return planRelayCatalogSlots(catalog, routes, templateKey).slots;
7685
+ }
7686
+ function buildRelayCatalogEntry(route, template) {
7687
+ const entry = structuredClone(template);
7688
+ entry.displayName = route.displayName;
7689
+ entry.model = template.model ?? RELAY_AGENT_PLACEHOLDER;
7690
+ entry.requestedModelId = route.catalogId;
7691
+ entry.modelVersion = route.catalogId;
7692
+ entry.modelVersionId = route.catalogId;
7693
+ entry.quotaInfo = { remainingFraction: 1, resetTime: "2026-06-23T02:00:57Z" };
7694
+ return applyRouteContextBounds(entry, route);
7695
+ }
7696
+ function buildRelayCatalogSlotEntry(route, template) {
7697
+ const entry = structuredClone(template);
7698
+ entry.displayName = route.displayName;
7699
+ entry.quotaInfo = { remainingFraction: 1, resetTime: "2026-06-23T02:00:57Z" };
7700
+ delete entry.requestedModelId;
7701
+ delete entry.modelVersion;
7702
+ delete entry.modelVersionId;
7703
+ delete entry.isInternal;
7704
+ return applyRouteContextBounds(entry, route);
7705
+ }
7706
+ function injectRelayModels(fixture, routes, templateKey) {
7707
+ const result = structuredClone(fixture);
7708
+ const template = fixture.models[templateKey];
7709
+ if (!template) {
7710
+ throw new Error(`Template model "${templateKey}" not found in catalog fixture`);
7711
+ }
7712
+ const seen = /* @__PURE__ */ new Set();
7713
+ for (const route of routes) {
7714
+ if (seen.has(route.catalogId)) {
7715
+ throw new Error(`Catalog ID collision: ${route.catalogId}`);
7716
+ }
7717
+ if (fixture.models[route.catalogId]) {
7718
+ throw new Error(`Catalog ID collision with native model: ${route.catalogId}`);
7719
+ }
7720
+ seen.add(route.catalogId);
7721
+ }
7722
+ if (routes.length > 0) {
7723
+ result.models[RELAY_CASCADE_ANCHOR_ID] ??= structuredClone(template);
7724
+ result.models[RELAY_CASCADE_FALLBACK_ID] ??= structuredClone(RELAY_CASCADE_FALLBACK_ENTRY);
7725
+ result.models[RELAY_CASCADE_INTENT_MODEL_ID] ??= structuredClone(RELAY_CASCADE_INTENT_MODEL_ENTRY);
7726
+ if (!result.models[RELAY_CASCADE_PLAN_ANCHOR_ID]) {
7727
+ const planAnchor = withCascadeCheckpointer(structuredClone(template));
7728
+ planAnchor.model = RELAY_CASCADE_PLAN_MODEL;
7729
+ result.models[RELAY_CASCADE_PLAN_ANCHOR_ID] = planAnchor;
7730
+ }
7731
+ const slotPlan = planRelayCatalogSlots(result, routes, templateKey);
7732
+ const slots = slotPlan.slots;
7733
+ for (const { slotId, route } of slots) {
7734
+ const slotTemplate = result.models[slotId] ?? template;
7735
+ if (result.models[slotId]) {
7736
+ result.models[slotId] = buildRelayCatalogSlotEntry(route, slotTemplate);
7737
+ }
7738
+ result.models[route.catalogId] = buildRelayCatalogEntry(route, slotTemplate);
7739
+ }
7740
+ result.defaultAgentModelId = slots[0]?.slotId ?? RELAY_CASCADE_ANCHOR_ID;
7741
+ result.agentModelSorts = [
7742
+ {
7743
+ displayName: "Recommended",
7744
+ groups: [{
7745
+ modelIds: slots.map((slot) => slot.slotId)
7746
+ }]
7747
+ }
7748
+ ];
7749
+ return result;
7750
+ }
7751
+ if (!result.agentModelSorts?.[0]?.groups?.[0]) {
7752
+ result.agentModelSorts = [
7753
+ {
7754
+ displayName: "Recommended",
7755
+ groups: [{ modelIds: [] }]
7756
+ }
7757
+ ];
7758
+ }
7759
+ return result;
7760
+ }
7761
+ function buildAntigravityRoutes(resolvedFavorites, maxRoutes = MAX_MODEL_CATALOG) {
7762
+ const routes = [];
7763
+ const seen = /* @__PURE__ */ new Set();
7764
+ for (const fav of resolvedFavorites) {
7765
+ if (routes.length >= maxRoutes) break;
7766
+ const favModel = fav.model;
7767
+ const modelId = favModel.id;
7768
+ const safeModelSlug = modelId.replace(/[^a-zA-Z0-9_-]/g, "_");
7769
+ const catalogId = `relay-ai__${fav.providerId}__${safeModelSlug}`;
7770
+ if (seen.has(catalogId)) continue;
7771
+ seen.add(catalogId);
7772
+ const npm = favModel.npm || "@ai-sdk/openai-compatible";
7773
+ const upstreamModelId2 = favModel.upstreamModelId || modelId;
7774
+ const baseURL = favModel.apiBaseUrl || favModel.completionsUrl || void 0;
7775
+ const contextWindow = favModel.contextWindow;
7776
+ const modelFormat = favModel.modelFormat;
7777
+ routes.push({
7778
+ catalogId,
7779
+ providerId: fav.providerId,
7780
+ providerName: fav.providerName,
7781
+ modelId,
7782
+ upstreamModelId: upstreamModelId2,
7783
+ displayName: `${favModel.name} (Relay)`,
7784
+ ...modelFormat ? { modelFormat } : {},
7785
+ npm,
7786
+ apiKey: fav.apiKey,
7787
+ ...fav.authType ? { authType: fav.authType } : {},
7788
+ ...fav.oauthAccountId ? { oauthAccountId: fav.oauthAccountId } : {},
7789
+ ...fav.providerData ? { providerData: fav.providerData } : {},
7790
+ baseURL,
7791
+ contextWindow
7792
+ });
7793
+ }
7794
+ return applyUniqueAntigravityRouteLabels(routes);
7795
+ }
7796
+ function routeBaseModelName(route) {
7797
+ const relayMatch = route.displayName.match(/^(.*) \(Relay(?: - .*)?\)$/);
7798
+ return relayMatch?.[1] ?? route.displayName;
7799
+ }
7800
+ function authKindLabel(route) {
7801
+ if (route.authType === "oauth") return "OAuth";
7802
+ if (route.authType === "api") return "API key";
7803
+ if (route.authType === "none") return "local";
7804
+ return "provider";
7805
+ }
7806
+ function duplicateCounts(values) {
7807
+ const counts = /* @__PURE__ */ new Map();
7808
+ for (const value of values) {
7809
+ counts.set(value, (counts.get(value) ?? 0) + 1);
7810
+ }
7811
+ return counts;
7812
+ }
7813
+ function assertUniqueRouteDisplayNames(routes) {
7814
+ const counts = duplicateCounts(routes.map((route) => route.displayName));
7815
+ const duplicate = [...counts.entries()].find(([, count]) => count > 1);
7816
+ if (duplicate) {
7817
+ throw new Error(`Duplicate AGY model label after disambiguation: ${duplicate[0]}`);
7818
+ }
7819
+ }
7820
+ function applyUniqueAntigravityRouteLabels(routes) {
7821
+ const baseNames = routes.map(routeBaseModelName);
7822
+ const baseNameCounts = duplicateCounts(baseNames);
7823
+ const upstreamCounts = duplicateCounts(routes.map((route) => route.upstreamModelId));
7824
+ const providerNameCounts = duplicateCounts(routes.map((route) => route.providerName));
7825
+ const labeled = routes.map((route, index) => {
7826
+ const baseName = baseNames[index];
7827
+ const needsSuffix = (baseNameCounts.get(baseName) ?? 0) > 1 || (upstreamCounts.get(route.upstreamModelId) ?? 0) > 1;
7828
+ if (!needsSuffix) {
7829
+ return { ...route, displayName: `${baseName} (Relay)` };
7830
+ }
7831
+ const providerName = route.providerName || route.providerId;
7832
+ const providerSuffix = (providerNameCounts.get(providerName) ?? 0) > 1 ? `${providerName} ${authKindLabel(route)}` : providerName;
7833
+ return {
7834
+ ...route,
7835
+ displayName: `${baseName} (Relay - ${providerSuffix})`
7836
+ };
7837
+ });
7838
+ const firstPassCounts = duplicateCounts(labeled.map((route) => route.displayName));
7839
+ const withProviderIds = labeled.map((route) => {
7840
+ if ((firstPassCounts.get(route.displayName) ?? 0) <= 1) return route;
7841
+ return {
7842
+ ...route,
7843
+ displayName: route.displayName.replace(/\)$/, ` - ${route.providerId})`)
7844
+ };
7845
+ });
7846
+ assertUniqueRouteDisplayNames(withProviderIds);
7847
+ return withProviderIds;
7848
+ }
7849
+ function routeLabels(routes) {
7850
+ assertUniqueRouteDisplayNames(routes);
7851
+ const labels = /* @__PURE__ */ new Map();
7852
+ for (const route of routes) {
7853
+ labels.set(route.catalogId, route.displayName);
7854
+ }
7855
+ return labels;
7856
+ }
7857
+ function buildClientModelConfigData(routes, catalog, templateKey = RELAY_CASCADE_ANCHOR_ID, precomputedSlots) {
7858
+ const catalogRoutes = routes.slice(0, MAX_MODEL_CATALOG);
7859
+ const slots = precomputedSlots ?? (catalog ? resolveRelayCatalogSlots(catalog, catalogRoutes, templateKey) : catalogRoutes.map((route) => ({ slotId: route.catalogId, route })));
7860
+ const labels = routeLabels(catalogRoutes);
7861
+ const clientModelConfigs = slots.map(({ slotId, route }) => {
7862
+ const entry = catalog?.models[slotId] ?? catalog?.models[route.catalogId] ?? catalog?.models[RELAY_CASCADE_ANCHOR_ID];
7863
+ const label = labels.get(route.catalogId) ?? route.displayName;
7864
+ return {
7865
+ label,
7866
+ modelOrAlias: {
7867
+ alias: slotId,
7868
+ choice: { case: "alias", value: slotId }
7869
+ },
7870
+ disabled: false,
7871
+ supportedMimeTypes: entry?.supportedMimeTypes ?? {},
7872
+ quotaInfo: entry?.quotaInfo ?? { remainingFraction: 1 },
7873
+ tagTitle: entry?.tagTitle,
7874
+ tagDescription: entry?.tagDescription,
7875
+ supportsThoughtCirculation: entry?.supportsThoughtCirculation ?? false
7876
+ };
7877
+ });
7878
+ return {
7879
+ clientModelConfigs,
7880
+ clientModelSorts: [
7881
+ {
7882
+ name: "Recommended",
7883
+ groups: [
7884
+ {
7885
+ groupName: "",
7886
+ modelLabels: clientModelConfigs.map((config) => config.label)
7887
+ }
7888
+ ]
7889
+ }
7890
+ ],
7891
+ defaultOverrideModelConfig: clientModelConfigs[0] ?? {}
7892
+ };
7893
+ }
7894
+ function buildListModelConfigsResponse(routes, catalog, templateKey = RELAY_CASCADE_ANCHOR_ID) {
7895
+ const catalogRoutes = routes.slice(0, MAX_MODEL_CATALOG);
7896
+ const slots = catalog ? resolveRelayCatalogSlots(catalog, catalogRoutes, templateKey) : catalogRoutes.map((route) => ({ slotId: route.catalogId, route }));
7897
+ const config = slots.map(({ slotId }) => ({
7898
+ requestedModelId: slotId,
7899
+ planModel: RELAY_CASCADE_PLAN_MODEL,
7900
+ requestedModel: catalog?.models[slotId]?.model ?? RELAY_AGENT_PLACEHOLDER
7901
+ }));
7902
+ return {
7903
+ ...buildClientModelConfigData(routes, catalog, templateKey, slots),
7904
+ allowedModelConfigs: config,
7905
+ defaultAgentModelConfig: config[0] ?? {}
7906
+ };
7907
+ }
7908
+ var CURRENT_EXPERIMENT_IDS = [
7909
+ 105979552,
7910
+ 105979574,
7911
+ 106015351,
7912
+ 105979579,
7913
+ 105867471,
7914
+ 105979530,
7915
+ 105995634,
7916
+ 106121401,
7917
+ 106100625,
7918
+ 104638466,
7919
+ 101868197,
7920
+ 104817729,
7921
+ 105695344,
7922
+ 106064591,
7923
+ 104913215,
7924
+ 106324349,
7925
+ 106309078,
7926
+ 105821930,
7927
+ 104922093,
7928
+ 103012598,
7929
+ 106143956,
7930
+ 105856899,
7931
+ 106312323,
7932
+ 106064030,
7933
+ 105746183,
7934
+ 105757908,
7935
+ 104892493,
7936
+ 105822886,
7937
+ 105785683,
7938
+ 105721273,
7939
+ 105897325,
7940
+ 105658071,
7941
+ 106240758,
7942
+ 105943702,
7943
+ 106106760,
7944
+ 106283618,
7945
+ 105620019,
7946
+ 106038160,
7947
+ 106309520,
7948
+ 106281951,
7949
+ 106264532,
7950
+ 106222835,
7951
+ 106094629,
7952
+ 105887313,
7953
+ 105849474,
7954
+ 106032303,
7955
+ 106228452,
7956
+ 106113900,
7957
+ 106121607,
7958
+ 105979531,
7959
+ 105979553,
7960
+ 106015328,
7961
+ 105867469,
7962
+ 105979517,
7963
+ 106121399,
7964
+ 106100654,
7965
+ 104638459,
7966
+ 101551624,
7967
+ 104673683,
7968
+ 105695346,
7969
+ 106064590,
7970
+ 104913210,
7971
+ 105821928,
7972
+ 104922082,
7973
+ 103012592,
7974
+ 106064028,
7975
+ 105746181,
7976
+ 104892490,
7977
+ 105822881,
7978
+ 105721268,
7979
+ 105895316,
7980
+ 105658068,
7981
+ 106240748,
7982
+ 105943694,
7983
+ 106283614,
7984
+ 105620012,
7985
+ 106038153,
7986
+ 105887311,
7987
+ 106032301,
7988
+ 106113877,
7989
+ 106121604
7990
+ ];
7991
+ function buildListExperimentsResponse() {
7992
+ return {
7993
+ experimentIds: [...CURRENT_EXPERIMENT_IDS]
7994
+ };
7995
+ }
7996
+
7283
7997
  // src/target-compatibility.ts
7284
7998
  function blacklistAgentForTarget(target) {
7285
7999
  if (target === "claude-app") return "codex-app";
7286
8000
  return target;
7287
8001
  }
8002
+ function contextFloorForTarget(target) {
8003
+ if (target === "antigravity") return ANTIGRAVITY_MIN_CONTEXT_WINDOW;
8004
+ if (target === "server") return 0;
8005
+ return MIN_CONTEXT_WINDOW;
8006
+ }
8007
+ function meetsContextFloor(target, contextWindow) {
8008
+ return contextWindow === void 0 || contextWindow >= contextFloorForTarget(target);
8009
+ }
7288
8010
  function isTargetCompatibleModel(ctx) {
7289
8011
  const blacklistAgent = blacklistAgentForTarget(ctx.target);
7290
8012
  if (shouldHideModel({ providerId: ctx.providerId, modelId: ctx.model.id, agent: blacklistAgent })) {
7291
8013
  return { compatible: false, reason: "model is hidden by compatibility filters" };
7292
8014
  }
8015
+ if (!meetsContextFloor(ctx.target, ctx.model.contextWindow)) {
8016
+ const floor = contextFloorForTarget(ctx.target);
8017
+ return {
8018
+ compatible: false,
8019
+ reason: `${ctx.target} needs a ${Math.round(floor / 1e3)}K+ context window; this model has ${Math.round(ctx.model.contextWindow / 1e3)}K`
8020
+ };
8021
+ }
7293
8022
  if (ctx.model.modelFormat === "cloud-code") {
7294
8023
  if (ctx.target === "server") {
7295
8024
  return { compatible: false, reason: "Cloud Code models are not supported for the server target yet" };
@@ -7354,8 +8083,11 @@ function modelFormatForNpm(npm) {
7354
8083
  return npm === "@ai-sdk/anthropic" ? "anthropic" : "openai";
7355
8084
  }
7356
8085
  function modelsUrl(baseUrl, template) {
7357
- const trimmed = baseUrl.replace(/\/$/, "");
8086
+ let trimmed = baseUrl.replace(/\/$/, "");
7358
8087
  if (template.modelsPath) {
8088
+ if (trimmed.endsWith("/v1") && (template.modelsPath.startsWith("/models") || template.modelsPath.startsWith("/ai/models"))) {
8089
+ trimmed = trimmed.slice(0, -3);
8090
+ }
7359
8091
  const path = template.modelsPath.startsWith("/") ? template.modelsPath : `/${template.modelsPath}`;
7360
8092
  return `${trimmed}${path}`;
7361
8093
  }
@@ -7392,20 +8124,64 @@ function parseNativePricing(pricing) {
7392
8124
  if (cacheWrite !== void 0) cost.cache_write = cacheWrite;
7393
8125
  return cost;
7394
8126
  }
8127
+ function parseCloudflarePricing(priceValue) {
8128
+ if (!Array.isArray(priceValue)) return void 0;
8129
+ let input;
8130
+ let output;
8131
+ for (const item of priceValue) {
8132
+ if (typeof item !== "object" || !item) continue;
8133
+ const row = item;
8134
+ const unit = String(row.unit || "").toLowerCase();
8135
+ const price = Number(row.price);
8136
+ if (!Number.isFinite(price)) continue;
8137
+ if (unit.includes("input")) input = price;
8138
+ else if (unit.includes("output")) output = price;
8139
+ }
8140
+ if (input === void 0 && output === void 0) return void 0;
8141
+ return { input: input ?? 0, output: output ?? 0 };
8142
+ }
8143
+ var LEGACY_NON_TOOL_MODELS = /* @__PURE__ */ new Set([
8144
+ "@cf/google/gemma-2b-it-lora",
8145
+ "@cf/google/gemma-7b-it-lora",
8146
+ "@cf/meta-llama/llama-2-7b-chat-hf-lora",
8147
+ "@cf/mistral/mistral-7b-instruct-v0.2-lora"
8148
+ ]);
7395
8149
  function parseModelList(body, npm) {
7396
- const rows = body.data ?? body.models ?? [];
8150
+ const rows = body.data ?? body.models ?? body.result ?? [];
7397
8151
  const format = modelFormatForNpm(npm);
7398
8152
  const models = [];
7399
8153
  for (const row of rows) {
7400
- const rawId = row.id?.trim();
8154
+ const rawId = (row.name?.startsWith("@cf/") || row.name?.startsWith("@hf/") ? row.name : row.id)?.trim();
7401
8155
  if (!rawId) continue;
8156
+ let contextWindowFromProps;
8157
+ let isFreeFromProps;
8158
+ let costFromProps;
8159
+ if (Array.isArray(row.properties)) {
8160
+ if (LEGACY_NON_TOOL_MODELS.has(rawId)) continue;
8161
+ const cwProp = row.properties.find((p8) => p8.property_id === "context_window");
8162
+ if (cwProp?.value) contextWindowFromProps = toNumber(cwProp.value);
8163
+ const priceProp = row.properties.find((p8) => p8.property_id === "price");
8164
+ const isRestrictedPaidPlan = rawId.includes("/glm-") || rawId.includes("/kimi-");
8165
+ if (isRestrictedPaidPlan) {
8166
+ costFromProps = parseCloudflarePricing(priceProp?.value);
8167
+ isFreeFromProps = false;
8168
+ } else {
8169
+ isFreeFromProps = true;
8170
+ if (priceProp?.value) {
8171
+ costFromProps = parseCloudflarePricing(priceProp.value);
8172
+ }
8173
+ }
8174
+ }
7402
8175
  const { id, upstreamModelId: upstreamModelId2 } = normalizeGoogleModelId(rawId, npm);
7403
- const family = id.split(/[-/:]/)[0] ?? id;
7404
- const cost = parseNativePricing(row.pricing);
8176
+ const family = id.replace(/^@[a-z0-9_-]+\//i, "").split(/[-/:]/)[0] ?? id;
8177
+ const cost = costFromProps ?? parseNativePricing(row.pricing);
7405
8178
  const freeStatus = classifyFreeStatus({
7406
- model: { cost, isFree: row.isFree }
8179
+ model: { cost, isFree: row.isFree },
8180
+ // Cloudflare standard models carry a list price but are covered by the free
8181
+ // daily Neuron allowance, so free access is a provider rule, not a price.
8182
+ freeAccess: isFreeFromProps === true
7407
8183
  });
7408
- const contextWindow = row.context_length ?? row.contextWindow ?? row.context_window ?? resolveContextWindow(id);
8184
+ const contextWindow = contextWindowFromProps ?? row.context_length ?? row.contextWindow ?? row.context_window ?? resolveContextWindow(id);
7409
8185
  models.push({
7410
8186
  id,
7411
8187
  name: normalizeGoogleDisplayName(row.name, id),
@@ -10689,14 +11465,14 @@ function codexAppInstallHint() {
10689
11465
 
10690
11466
  // src/claude-desktop/app-launch.ts
10691
11467
  import { execSync as execSync3, spawn as spawn3 } from "child_process";
10692
- import { existsSync as existsSync12, readdirSync as readdirSync2, statSync as statSync4 } from "fs";
11468
+ import { existsSync as existsSync12, readdirSync as readdirSync2, readFileSync as readFileSync12, statSync as statSync4 } from "fs";
10693
11469
  import { homedir as homedir8 } from "os";
10694
11470
  import { join as join12 } from "path";
10695
11471
  import * as p7 from "@clack/prompts";
10696
11472
  var CLAUDE_BUNDLE_ID = "com.anthropic.claudefordesktop";
10697
11473
  function claudeAppSupported() {
10698
- if (process.platform !== "darwin" && process.platform !== "win32") {
10699
- throw new Error("Claude Desktop launch is supported on macOS and Windows only.");
11474
+ if (process.platform !== "darwin" && process.platform !== "win32" && process.platform !== "linux") {
11475
+ throw new Error("Claude Desktop launch is supported on macOS, Windows, and Linux only.");
10700
11476
  }
10701
11477
  }
10702
11478
  function run2(cmd, encoding = "utf8") {
@@ -10738,6 +11514,58 @@ function winClaudeExeCandidates() {
10738
11514
  }
10739
11515
  return out;
10740
11516
  }
11517
+ function linuxClaudeCandidates() {
11518
+ return [
11519
+ // Wrapper launcher installed by the .deb/.rpm — sets up the Electron sandbox.
11520
+ "/usr/bin/claude-desktop",
11521
+ "/usr/lib/claude-desktop/claude-desktop",
11522
+ "/opt/Claude/claude-desktop",
11523
+ join12(homedir8(), ".local", "bin", "claude-desktop")
11524
+ ];
11525
+ }
11526
+ function linuxWhichClaude() {
11527
+ try {
11528
+ const out = run2("command -v claude-desktop");
11529
+ return out && existsSync12(out) ? out : null;
11530
+ } catch {
11531
+ return null;
11532
+ }
11533
+ }
11534
+ function linuxMatchingPids() {
11535
+ try {
11536
+ const out = run2("pgrep -x claude-desktop");
11537
+ return out.split(/\s+/).map((s) => Number.parseInt(s, 10)).filter((n) => Number.isFinite(n) && n > 0);
11538
+ } catch {
11539
+ return [];
11540
+ }
11541
+ }
11542
+ function linuxMainPid() {
11543
+ const pids = linuxMatchingPids();
11544
+ for (const pid of pids) {
11545
+ try {
11546
+ const cmdline = readFileSync12(`/proc/${pid}/cmdline`, "utf8");
11547
+ if (!cmdline.includes("--type=")) return pid;
11548
+ } catch {
11549
+ }
11550
+ }
11551
+ return pids[0] ?? null;
11552
+ }
11553
+ function linuxQuit() {
11554
+ const pid = linuxMainPid();
11555
+ if (pid === null) return;
11556
+ try {
11557
+ process.kill(pid, "SIGTERM");
11558
+ } catch {
11559
+ }
11560
+ }
11561
+ function linuxForceQuit() {
11562
+ for (const pid of linuxMatchingPids()) {
11563
+ try {
11564
+ process.kill(pid, "SIGKILL");
11565
+ } catch {
11566
+ }
11567
+ }
11568
+ }
10741
11569
  function mdfindClaudeApp() {
10742
11570
  try {
10743
11571
  const out = run2(`mdfind "kMDItemCFBundleIdentifier == '${CLAUDE_BUNDLE_ID}'"`);
@@ -10769,6 +11597,15 @@ function findClaudeApp() {
10769
11597
  } catch {
10770
11598
  }
10771
11599
  }
11600
+ if (process.platform === "linux") {
11601
+ for (const path of linuxClaudeCandidates()) {
11602
+ try {
11603
+ if (existsSync12(path)) return path;
11604
+ } catch {
11605
+ }
11606
+ }
11607
+ return linuxWhichClaude();
11608
+ }
10772
11609
  return null;
10773
11610
  }
10774
11611
  function darwinIsRunning2() {
@@ -10801,6 +11638,7 @@ function winHasWindow2() {
10801
11638
  function isClaudeAppRunning() {
10802
11639
  if (process.platform === "darwin") return darwinIsRunning2();
10803
11640
  if (process.platform === "win32") return winMatchingPids2().length > 0 || winHasWindow2();
11641
+ if (process.platform === "linux") return linuxMatchingPids().length > 0;
10804
11642
  return false;
10805
11643
  }
10806
11644
  function sleep2(ms) {
@@ -10811,12 +11649,16 @@ async function waitForQuit2(timeoutMs) {
10811
11649
  while (Date.now() < deadline) {
10812
11650
  if (process.platform === "win32") {
10813
11651
  if (winMatchingPids2().length === 0) return true;
11652
+ } else if (process.platform === "linux") {
11653
+ if (linuxMatchingPids().length === 0) return true;
10814
11654
  } else if (!darwinIsRunning2()) {
10815
11655
  return true;
10816
11656
  }
10817
11657
  await sleep2(200);
10818
11658
  }
10819
- return process.platform === "win32" ? winMatchingPids2().length === 0 : !darwinIsRunning2();
11659
+ if (process.platform === "win32") return winMatchingPids2().length === 0;
11660
+ if (process.platform === "linux") return linuxMatchingPids().length === 0;
11661
+ return !darwinIsRunning2();
10820
11662
  }
10821
11663
  function openClaudeAppAt(path) {
10822
11664
  if (process.platform === "darwin") {
@@ -10833,6 +11675,10 @@ function openClaudeAppAt(path) {
10833
11675
  } else {
10834
11676
  runPowerShell2(`Start-Process -FilePath '${path.replace(/'/g, "''")}'`);
10835
11677
  }
11678
+ return;
11679
+ }
11680
+ if (process.platform === "linux") {
11681
+ spawn3(path, [], { stdio: "ignore", detached: true }).unref();
10836
11682
  }
10837
11683
  }
10838
11684
  function openClaudeApp() {
@@ -10859,6 +11705,7 @@ function winQuitGraceful2() {
10859
11705
  function quitClaudeAppGracefully() {
10860
11706
  if (process.platform === "darwin") darwinQuit2();
10861
11707
  else if (process.platform === "win32") winQuitGraceful2();
11708
+ else if (process.platform === "linux") linuxQuit();
10862
11709
  }
10863
11710
  function winForceQuit2() {
10864
11711
  const pids = winMatchingPids2();
@@ -10880,9 +11727,11 @@ async function launchOrRestartClaudeApp(prompt = "Restart Claude Desktop to appl
10880
11727
  return;
10881
11728
  }
10882
11729
  if (process.platform === "darwin") darwinQuit2();
10883
- else winQuitGraceful2();
11730
+ else if (process.platform === "win32") winQuitGraceful2();
11731
+ else if (process.platform === "linux") linuxQuit();
10884
11732
  if (!await waitForQuit2(5e3)) {
10885
11733
  if (process.platform === "win32") winForceQuit2();
11734
+ else if (process.platform === "linux") linuxForceQuit();
10886
11735
  await waitForQuit2(5e3);
10887
11736
  }
10888
11737
  if (appPath) openClaudeAppAt(appPath);
@@ -11062,6 +11911,13 @@ export {
11062
11911
  resolveLocalProviderApiKey,
11063
11912
  formatRegistryAuthLabel,
11064
11913
  resolveProvidersForDisplay,
11914
+ evaluateAgySwitchCompatibility,
11915
+ resolveRelayCatalogSlots,
11916
+ injectRelayModels,
11917
+ buildAntigravityRoutes,
11918
+ buildListModelConfigsResponse,
11919
+ buildListExperimentsResponse,
11920
+ meetsContextFloor,
11065
11921
  routableModelsForTarget,
11066
11922
  providersForTarget,
11067
11923
  refreshProviderModels,
@@ -11103,4 +11959,4 @@ export {
11103
11959
  supportsClaudeTransparentMode,
11104
11960
  buildHttpProxyRoutes
11105
11961
  };
11106
- //# sourceMappingURL=chunk-WHEMJ7C3.js.map
11962
+ //# sourceMappingURL=chunk-6CBNKM55.js.map