@qredence/fleet 0.9.0 → 0.10.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.
Files changed (50) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/bin/prime-runtime.d.mts +19 -0
  3. package/bin/prime-runtime.mjs +42 -5
  4. package/dist/web/client/assets/{agents-CGjUBYX7.js → agents-BMQXA18L.js} +2 -2
  5. package/dist/web/client/assets/{agents-page-CTuFJL9H.js → agents-page-D2soW46O.js} +1 -1
  6. package/dist/web/client/assets/{chat-queries-CfZiupub.js → chat-queries-ty76G6rK.js} +1 -1
  7. package/dist/web/client/assets/{connectors-CFxLqG2x.js → connectors-E4KW6hBL.js} +2 -2
  8. package/dist/web/client/assets/{connectors-page-DQJdoAnl.js → connectors-page-CDzzjCIJ.js} +1 -1
  9. package/dist/web/client/assets/globals-DDDleTbS.css +2 -0
  10. package/dist/web/client/assets/{index-C9FHZDGw.js → index-CEZTSuLf.js} +2 -2
  11. package/dist/web/client/assets/{mcp-connections-section-DEo6Kemv.js → mcp-connections-section-BsNhp1b2.js} +2 -2
  12. package/dist/web/client/assets/routes-EKRITFZz.js +14 -0
  13. package/dist/web/client/assets/settings-dialog-BomLOETY.js +3 -0
  14. package/dist/web/client/bundle-budget.json +5 -5
  15. package/dist/web/server/{_tanstack-start-manifest_v-B6ne-00G-OFX7UD4K.js → _tanstack-start-manifest_v-BiMhF9NX-QUZEGAB6.js} +7 -7
  16. package/dist/web/server/{agents-BwzqhPjK-ANFHE5AG.js → agents-DOgCHixY-KUZP2VDO.js} +2 -2
  17. package/dist/web/server/{agents-page-DEqPV-eG-BR5MR74O.js → agents-page-BdiR9H8w-M6X6SXMF.js} +3 -3
  18. package/dist/web/server/{artifacts-panel-DLO8OfXb-B2ZWCDFP.js → artifacts-panel-BNiaRF83-HDC6VQ66.js} +5 -5
  19. package/dist/web/server/{chat-command-palette-CaDQAMAu-KDH7BTDS.js → chat-command-palette-CT9aJAWG-LV26BDEW.js} +5 -5
  20. package/dist/web/server/{chunk-UDUG3MYZ.js → chunk-27J2LPAW.js} +2 -2
  21. package/dist/web/server/{chunk-V2GCVTDP.js → chunk-3CU2ITG7.js} +33 -48
  22. package/dist/web/server/{chunk-B5FKCW75.js → chunk-3R7XCOWI.js} +4 -4
  23. package/dist/web/server/{chunk-6AFVCERK.js → chunk-4TKPOOIG.js} +4 -4
  24. package/dist/web/server/{chunk-HUHUPGHT.js → chunk-4YQ7RBB7.js} +5 -5
  25. package/dist/web/server/{chunk-5IE54MRG.js → chunk-5XRESJQR.js} +3 -3
  26. package/dist/web/server/{chunk-TOJA3X5N.js → chunk-BLGKLQJG.js} +16 -5
  27. package/dist/web/server/{chunk-TQ2RECTH.js → chunk-C5XHGLET.js} +1 -1
  28. package/dist/web/server/{chunk-TTNONWZZ.js → chunk-LEZMRXSP.js} +923 -697
  29. package/dist/web/server/{chunk-KDCSEISZ.js → chunk-NZCEQWK2.js} +2 -1
  30. package/dist/web/server/{chunk-VM3U6PUU.js → chunk-QOZCYKMA.js} +2 -2
  31. package/dist/web/server/{connectors-C0xm91Ss-DJVBGD73.js → connectors-Dv_uPdv0-2X5VWQXK.js} +2 -2
  32. package/dist/web/server/{connectors-page-B6tJNZ6c-R5K4NSAT.js → connectors-page-DW75T-uH-EKRHUZ6K.js} +4 -4
  33. package/dist/web/server/{create-project-dialog-PFPHHWIV-GH7JR5IK.js → create-project-dialog-EwieT1Vw-X5MYQSZM.js} +5 -5
  34. package/dist/web/server/{openui-renderer-B9D8-Av--LFZP4U5V.js → openui-renderer-DCkg_JoD-4QCKSVY5.js} +4 -4
  35. package/dist/web/server/{resources-panel-CTzOOKPz-5G6RS2D6.js → resources-panel-s0mgyvgQ-FVMBBLJJ.js} +6 -6
  36. package/dist/web/server/{router-CnBux_Ld-YTVBMJFL.js → router-DXxChQOI-B7VVEX3H.js} +2 -2
  37. package/dist/web/server/{routes-BUGzvQoO-WW43X7W2.js → routes-D-g2ILNg-LDYCQ4NU.js} +4 -4
  38. package/dist/web/server/server.js +2 -2
  39. package/dist/web/server/{session-activity-panel-f0d3fCm7-I6L5IKFQ.js → session-activity-panel-wC0jAJvs-QAHJXPAM.js} +6 -6
  40. package/dist/web/server/{session-reasoning-panel-QIt_xjP0-FKJWMNHO.js → session-reasoning-panel-B_K8YgF5-AQPMFM3Y.js} +5 -5
  41. package/dist/web/server/{session-tool-renderer-Cg_ZPHxS-BNQKNJNZ.js → session-tool-renderer-D96BACVh-CH5CV3TS.js} +5 -5
  42. package/dist/web/server/{session-tree-panel-B--el8O5-NZOZJM5L.js → session-tree-panel-CZGXNZIP-FBB3XGMN.js} +5 -5
  43. package/dist/web/server/{settings-dialog-D_eJN2ia-W47Y474K.js → settings-dialog-BrjqiTVe-H7WOTB4I.js} +145 -66
  44. package/dist/web/server/{subagent-chat-panel-C9nWP21E-D5C2TTYD.js → subagent-chat-panel-Dz62jo_m-X4ZRN5KI.js} +8 -8
  45. package/dist/web/server/{subagents-panel-fAKvBNRG-O7QHXNE2.js → subagents-panel-CwpbzJJz-N3ISHODM.js} +8 -8
  46. package/dist/web/server/{workspace-panel-CZMNMU63-SYOSM3B7.js → workspace-panel-C7akv5b9-XUKEHPOP.js} +6 -6
  47. package/package.json +1 -1
  48. package/dist/web/client/assets/globals-DJhWyayo.css +0 -2
  49. package/dist/web/client/assets/routes-ja_liP8Z.js +0 -14
  50. package/dist/web/client/assets/settings-dialog-DiikSLhF.js +0 -1
@@ -78,7 +78,7 @@ import {
78
78
  external_exports,
79
79
  isServer,
80
80
  iso_exports
81
- } from "./chunk-KDCSEISZ.js";
81
+ } from "./chunk-NZCEQWK2.js";
82
82
  import {
83
83
  LazyMotion,
84
84
  MotionConfig,
@@ -123,7 +123,7 @@ import {
123
123
  lazyRouteComponent
124
124
  } from "./chunk-35RIM6VP.js";
125
125
 
126
- // web/app/dist/server/assets/router-CnBux_Ld.js
126
+ // web/app/dist/server/assets/router-DXxChQOI.js
127
127
  import { useEffect, useLayoutEffect, useState } from "react";
128
128
  import { jsx, jsxs } from "react/jsx-runtime";
129
129
  import { chmod, mkdir, mkdtemp, open, readFile, readdir, realpath, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
@@ -134,7 +134,7 @@ import { arch, homedir, platform, tmpdir } from "node:os";
134
134
  import { fileURLToPath } from "node:url";
135
135
  import { createHash, randomUUID } from "node:crypto";
136
136
  import { createConnection } from "node:net";
137
- import { AuthStorage, DefaultResourceLoader, ModelRegistry, SettingsManager, buildSessionContext, migrateSessionEntries, parseSessionEntries } from "prime-agent";
137
+ import { AuthStorage, DefaultResourceLoader, SettingsManager, buildSessionContext, migrateSessionEntries, parseSessionEntries } from "prime-agent";
138
138
  var CSP_META = `<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data:; font-src 'none'; connect-src 'none'; frame-src 'none'; object-src 'none'; base-uri 'none'; form-action 'none'; media-src data:; navigate-to 'none';">`;
139
139
  var MAX_TITLE_BYTES = 240;
140
140
  var FORBIDDEN_ELEMENTS = /<(?:link|iframe|object|embed|base|form|portal|frameset)\b/i;
@@ -2194,7 +2194,7 @@ function getQueryClient() {
2194
2194
  if (!browserQueryClient) browserQueryClient = makeQueryClient();
2195
2195
  return browserQueryClient;
2196
2196
  }
2197
- var globals_default = "/assets/globals-DJhWyayo.css";
2197
+ var globals_default = "/assets/globals-DDDleTbS.css";
2198
2198
  var Route$32 = createRootRoute({
2199
2199
  head: () => ({
2200
2200
  meta: [
@@ -2266,11 +2266,11 @@ function RootDocument({ children }) {
2266
2266
  })]
2267
2267
  });
2268
2268
  }
2269
- var $$splitComponentImporter$2 = () => import("./routes-BUGzvQoO-WW43X7W2.js").then((n) => n.t);
2269
+ var $$splitComponentImporter$2 = () => import("./routes-D-g2ILNg-LDYCQ4NU.js").then((n) => n.t);
2270
2270
  var Route$31 = createFileRoute("/")({ component: lazyRouteComponent($$splitComponentImporter$2, "component") });
2271
- var $$splitComponentImporter$1 = () => import("./agents-BwzqhPjK-ANFHE5AG.js");
2271
+ var $$splitComponentImporter$1 = () => import("./agents-DOgCHixY-KUZP2VDO.js");
2272
2272
  var Route$30 = createFileRoute("/agents")({ component: lazyRouteComponent($$splitComponentImporter$1, "component") });
2273
- var $$splitComponentImporter = () => import("./connectors-C0xm91Ss-DJVBGD73.js");
2273
+ var $$splitComponentImporter = () => import("./connectors-Dv_uPdv0-2X5VWQXK.js");
2274
2274
  var Route$29 = createFileRoute("/connectors")({ component: lazyRouteComponent($$splitComponentImporter, "component") });
2275
2275
  var ManagedAttachmentValidationError = class extends Error {
2276
2276
  status;
@@ -4135,12 +4135,56 @@ var FleetDaemonAgentConnection = class FleetDaemonAgentConnection2 {
4135
4135
  if (this.closeClientOnDispose) this.client.close();
4136
4136
  }
4137
4137
  };
4138
+ var MODEL_CATALOG_MAX_AGE_MS = 5e3;
4139
+ function cacheState() {
4140
+ const globalStore2 = globalThis;
4141
+ globalStore2.__fleetModelCatalogCache ??= { generation: 0 };
4142
+ return globalStore2.__fleetModelCatalogCache;
4143
+ }
4144
+ function readModelCatalogSnapshot(maxAgeMs = MODEL_CATALOG_MAX_AGE_MS) {
4145
+ const { snapshot } = cacheState();
4146
+ if (!snapshot) return void 0;
4147
+ return Date.now() - snapshot.fetchedAt <= maxAgeMs ? snapshot.catalog : void 0;
4148
+ }
4149
+ async function loadModelCatalog(refresh, maxAgeMs = MODEL_CATALOG_MAX_AGE_MS, attemptsRemaining = 1) {
4150
+ const cached = readModelCatalogSnapshot(maxAgeMs);
4151
+ if (cached) return cached;
4152
+ const state = cacheState();
4153
+ if (!state.inFlight) {
4154
+ const startedAt = state.generation;
4155
+ state.inFlight = (async () => {
4156
+ try {
4157
+ const next = {
4158
+ catalog: await refresh(),
4159
+ fetchedAt: Date.now()
4160
+ };
4161
+ const current = cacheState();
4162
+ if (current.generation !== startedAt) return void 0;
4163
+ current.snapshot = next;
4164
+ current.lastGood = next;
4165
+ return next;
4166
+ } finally {
4167
+ cacheState().inFlight = void 0;
4168
+ }
4169
+ })();
4170
+ }
4171
+ const settled = await state.inFlight;
4172
+ if (settled) return settled.catalog;
4173
+ if (attemptsRemaining <= 0) {
4174
+ const lastGood = cacheState().lastGood;
4175
+ if (lastGood) return lastGood.catalog;
4176
+ throw new Error("The model catalog kept changing while it was being read. Try again.");
4177
+ }
4178
+ return loadModelCatalog(refresh, maxAgeMs, attemptsRemaining - 1);
4179
+ }
4180
+ function invalidateModelCatalog() {
4181
+ const state = cacheState();
4182
+ state.snapshot = void 0;
4183
+ state.generation += 1;
4184
+ }
4138
4185
  function createLegacyAuthStorage(authPath) {
4139
4186
  return AuthStorage.create(authPath);
4140
4187
  }
4141
- function createLegacyModelRegistry(authStorage, modelsJsonPath) {
4142
- return ModelRegistry.create(authStorage, modelsJsonPath);
4143
- }
4144
4188
  function createLegacySettingsManager(cwd, agentDir) {
4145
4189
  return SettingsManager.create(cwd, agentDir);
4146
4190
  }
@@ -4522,17 +4566,14 @@ function resolveDefaultWorkspaceRoot(startDir, env = process.env) {
4522
4566
  function createPrimeConfig(initialCwd) {
4523
4567
  const agentDir = resolveAgentDir();
4524
4568
  const authStorage = createLegacyAuthStorage(join(agentDir, "auth.json"));
4525
- const modelRegistry2 = createLegacyModelRegistry(authStorage, join(agentDir, "models.json"));
4526
4569
  const projectRegistry = new ProjectRegistry(agentDir, initialCwd);
4527
4570
  const currentDefaultCwd = resolve(initialCwd);
4528
- const defaultSettings = createLegacySettingsManager(currentDefaultCwd, agentDir);
4529
4571
  const settingsByCwd = /* @__PURE__ */ new Map();
4530
- settingsByCwd.set(currentDefaultCwd, defaultSettings);
4572
+ settingsByCwd.set(currentDefaultCwd, createLegacySettingsManager(currentDefaultCwd, agentDir));
4531
4573
  return {
4532
4574
  authStorage,
4533
- modelRegistry: modelRegistry2,
4534
4575
  get defaultSettings() {
4535
- return defaultSettings;
4576
+ return this.settingsFor(currentDefaultCwd);
4536
4577
  },
4537
4578
  get defaultCwd() {
4538
4579
  return currentDefaultCwd;
@@ -4540,13 +4581,15 @@ function createPrimeConfig(initialCwd) {
4540
4581
  agentDir,
4541
4582
  projectRegistry,
4542
4583
  settingsFor(cwd) {
4543
- if (cwd === currentDefaultCwd) return defaultSettings;
4544
4584
  const existing = settingsByCwd.get(cwd);
4545
4585
  if (existing) return existing;
4546
4586
  const created = createLegacySettingsManager(cwd, agentDir);
4547
4587
  settingsByCwd.set(cwd, created);
4548
4588
  return created;
4549
4589
  },
4590
+ invalidateSettings() {
4591
+ settingsByCwd.clear();
4592
+ },
4550
4593
  resourceLoaderFor(cwd) {
4551
4594
  return loadLegacyResources({
4552
4595
  cwd,
@@ -4556,14 +4599,14 @@ function createPrimeConfig(initialCwd) {
4556
4599
  },
4557
4600
  reloadAuth() {
4558
4601
  this.authStorage.reload();
4559
- this.modelRegistry.refresh();
4602
+ invalidateModelCatalog();
4560
4603
  }
4561
4604
  };
4562
4605
  }
4563
4606
  function getPrimeConfig() {
4564
4607
  const globalStore2 = globalThis;
4565
4608
  const existing = globalStore2.__primeConfig;
4566
- if (!existing || !existing.projectRegistry) globalStore2.__primeConfig = createPrimeConfig(resolveDefaultWorkspaceRoot(process.cwd()));
4609
+ if (!existing || !existing.projectRegistry || !existing.invalidateSettings) globalStore2.__primeConfig = createPrimeConfig(resolveDefaultWorkspaceRoot(process.cwd()));
4567
4610
  return globalStore2.__primeConfig;
4568
4611
  }
4569
4612
  var PRIME_AGENT_RUNTIME_default = {
@@ -4598,7 +4641,7 @@ var PRIME_AGENT_RUNTIME_default = {
4598
4641
  }
4599
4642
  }
4600
4643
  };
4601
- function runtimeArchiveTarget(os, cpu) {
4644
+ function runtimeTarget(os = platform(), cpu = arch()) {
4602
4645
  const target = `${os}-${cpu}`;
4603
4646
  if (![
4604
4647
  "darwin-arm64",
@@ -4608,44 +4651,53 @@ function runtimeArchiveTarget(os, cpu) {
4608
4651
  ].includes(target)) throw new Error(`Fleet's Rust Prime Agent runtime does not support ${target}; supported targets: darwin-arm64, darwin-x64, linux-arm64, linux-x64.`);
4609
4652
  return target;
4610
4653
  }
4611
- async function ensurePrimeAgentRuntime(options = {}) {
4612
- const selectedTarget = options.target ?? runtimeArchiveTarget(platform(), arch());
4613
- const runtime = PRIME_AGENT_RUNTIME_default.rust;
4614
- const asset = runtime.platforms[selectedTarget];
4615
- if (!asset) throw new Error(`No pinned Prime Agent runtime archive for ${selectedTarget}`);
4616
- const installRoot = options.installRoot ?? join(options.home ?? homedir(), ".prime", "fleet", "runtimes", runtime.version, selectedTarget);
4617
- const binary = join(installRoot, "prime-agent");
4618
- if (await executableMatches(binary, asset.executableSha256)) return binary;
4619
- await mkdir(dirname(installRoot), {
4654
+ async function ensurePrimeRuntime({ manifest, installRoot, home = homedir(), fetch: fetchImpl = fetch, target = runtimeTarget() }) {
4655
+ const runtime = manifest.rust;
4656
+ const asset = runtime?.platforms?.[target];
4657
+ if (!asset) throw new Error(`Fleet has no pinned Prime Agent runtime for ${target}`);
4658
+ const destination = installRoot ?? join(home, ".prime", "fleet", "runtimes", runtime.version, target);
4659
+ const executable = join(destination, "prime-agent");
4660
+ const packagedRuntime = join(destination, "prime-agent-runtime");
4661
+ if (await matchesDigest(executable, asset.executableSha256) && await directoryExists(packagedRuntime)) return executable;
4662
+ await mkdir(dirname(destination), {
4620
4663
  recursive: true,
4621
4664
  mode: 448
4622
4665
  });
4623
- const staging = await mkdtemp(join(dirname(installRoot), ".fleet-prime-runtime-"));
4666
+ const staging = await mkdtemp(join(dirname(destination), ".fleet-prime-runtime-"));
4624
4667
  try {
4625
4668
  const archivePath = join(staging, asset.archive);
4626
- const response = await (options.fetch ?? fetch)(`${runtime.baseUrl}/${asset.archive}`, { signal: AbortSignal.timeout(12e4) });
4669
+ const response = await fetchImpl(`${runtime.baseUrl}/${asset.archive}`, { signal: AbortSignal.timeout(12e4) });
4627
4670
  if (!response.ok) throw new Error(`Could not download Prime Agent ${runtime.version} (${response.status}). Check your network and retry.`);
4628
- const bytes = Buffer.from(await response.arrayBuffer());
4629
- if (digest(bytes) !== asset.sha256) throw new Error(`Prime Agent ${runtime.version} archive checksum mismatch; refusing to install it.`);
4630
- await writeFile(archivePath, bytes, { mode: 384 });
4631
- const extractRoot = join(staging, "extracted");
4632
- await mkdir(extractRoot);
4633
- const extraction = spawnSync("tar", [
4671
+ const archive = Buffer.from(await response.arrayBuffer());
4672
+ if (digest(archive) !== asset.sha256) throw new Error(`Prime Agent ${runtime.version} archive checksum mismatch; refusing to install it.`);
4673
+ await writeFile(archivePath, archive, { mode: 384 });
4674
+ const unpacked = join(staging, "unpacked");
4675
+ await mkdir(unpacked);
4676
+ const result = spawnSync("tar", [
4634
4677
  "-xzf",
4635
4678
  archivePath,
4636
4679
  "-C",
4637
- extractRoot
4680
+ unpacked
4638
4681
  ], { encoding: "utf8" });
4639
- if (extraction.status !== 0) throw new Error(`Could not extract Prime Agent runtime: ${extraction.stderr || "tar failed"}`);
4640
- const extractedBinary = join(extractRoot, "prime-agent");
4641
- if (!await executableMatches(extractedBinary, asset.executableSha256)) throw new Error("Extracted Prime Agent executable checksum mismatch; refusing to install it.");
4682
+ if (result.status !== 0) throw new Error(`Could not extract Prime Agent runtime: ${result.stderr || "tar failed"}`);
4683
+ const extractedBinary = join(unpacked, "prime-agent");
4684
+ if (!await matchesDigest(extractedBinary, asset.executableSha256)) throw new Error("Extracted Prime Agent executable checksum mismatch; refusing to install it.");
4642
4685
  await chmod(extractedBinary, 493);
4643
- await mkdir(installRoot, {
4686
+ await mkdir(destination, {
4644
4687
  recursive: true,
4645
4688
  mode: 448
4646
4689
  });
4647
- await rename(extractedBinary, binary);
4648
- return binary;
4690
+ const extractedRuntime = join(unpacked, "prime-agent-runtime");
4691
+ if (await directoryExists(extractedRuntime)) {
4692
+ await rename(packagedRuntime, join(staging, "previous-runtime")).catch((error) => {
4693
+ if (error.code !== "ENOENT") throw error;
4694
+ });
4695
+ await rename(extractedRuntime, packagedRuntime).catch((error) => {
4696
+ if (error.code !== "ENOTEMPTY" && error.code !== "EEXIST") throw error;
4697
+ });
4698
+ }
4699
+ await rename(extractedBinary, executable);
4700
+ return executable;
4649
4701
  } finally {
4650
4702
  await rm(staging, {
4651
4703
  recursive: true,
@@ -4653,7 +4705,15 @@ async function ensurePrimeAgentRuntime(options = {}) {
4653
4705
  });
4654
4706
  }
4655
4707
  }
4656
- async function executableMatches(path, expected) {
4708
+ async function directoryExists(path) {
4709
+ try {
4710
+ return (await stat(path)).isDirectory();
4711
+ } catch (error) {
4712
+ if (error.code === "ENOENT" || error.code === "ENOTDIR") return false;
4713
+ throw error;
4714
+ }
4715
+ }
4716
+ async function matchesDigest(path, expected) {
4657
4717
  let file2;
4658
4718
  try {
4659
4719
  file2 = await open(path, "r");
@@ -4664,8 +4724,14 @@ async function executableMatches(path, expected) {
4664
4724
  await file2?.close();
4665
4725
  }
4666
4726
  }
4667
- function digest(bytes) {
4668
- return createHash("sha256").update(bytes).digest("hex");
4727
+ function digest(value) {
4728
+ return createHash("sha256").update(value).digest("hex");
4729
+ }
4730
+ function ensurePrimeAgentRuntime(options = {}) {
4731
+ return ensurePrimeRuntime({
4732
+ manifest: PRIME_AGENT_RUNTIME_default,
4733
+ ...options
4734
+ });
4669
4735
  }
4670
4736
  var DAEMON_STARTUP_TIMEOUT_MS = 3e4;
4671
4737
  var DAEMON_PROBE_TIMEOUT_MS = 1e3;
@@ -4750,6 +4816,10 @@ async function probeDaemon(socketPath) {
4750
4816
  client.close();
4751
4817
  }
4752
4818
  }
4819
+ function describeDaemonSpawnError(error, command, options) {
4820
+ if (error.code !== "ENOENT") return `Could not start Prime Agent ${options.noun}: ${error.code ?? error.message}`;
4821
+ return `Could not start Prime Agent ${options.noun}: ${command} was not found, or its working directory ${options.cwd} does not exist.`;
4822
+ }
4753
4823
  async function startDaemon(socketPath, spawnCwd) {
4754
4824
  const initialProbe = await probeDaemon(socketPath);
4755
4825
  if (initialProbe.status === "compatible") return;
@@ -4773,10 +4843,16 @@ async function startDaemon(socketPath, spawnCwd) {
4773
4843
  const deadline = Date.now() + DAEMON_STARTUP_TIMEOUT_MS;
4774
4844
  while (Date.now() < deadline) {
4775
4845
  let spawnError = spawnErrors[0];
4776
- if (spawnError) throw new Error(spawnError.code === "ENOENT" ? `Prime Agent binary not found: ${command}` : `Could not start Prime Agent binary ${command}: ${spawnError.code ?? spawnError.message}`);
4846
+ if (spawnError) throw new Error(describeDaemonSpawnError(spawnError, command, {
4847
+ noun: "binary",
4848
+ cwd: spawnCwd
4849
+ }));
4777
4850
  const probe = await probeDaemon(socketPath);
4778
4851
  spawnError = spawnErrors[0];
4779
- if (spawnError) throw new Error(spawnError.code === "ENOENT" ? `Prime Agent binary not found: ${command}` : `Could not start Prime Agent daemon: ${spawnError.code || spawnError.message}`);
4852
+ if (spawnError) throw new Error(describeDaemonSpawnError(spawnError, command, {
4853
+ noun: "daemon",
4854
+ cwd: spawnCwd
4855
+ }));
4780
4856
  if (probe.status === "compatible") return;
4781
4857
  if (probe.status === "incompatible") throw new Error(`The Prime Agent daemon socket at ${socketPath} was claimed by a non-pinned daemon (${probe.detail}). Stop that daemon or configure a different daemon socket path.`);
4782
4858
  await delay(DAEMON_POLL_DELAY_MS);
@@ -4916,15 +4992,15 @@ async function withTransientDaemonSession(cwd, work) {
4916
4992
  }
4917
4993
  var modelCatalogListeners = /* @__PURE__ */ new Set();
4918
4994
  function notifyDaemonModelCatalogChanged() {
4919
- try {
4920
- getPrimeConfig().modelRegistry.refresh();
4921
- } catch {
4922
- }
4995
+ invalidateModelCatalog();
4923
4996
  for (const listener of [...modelCatalogListeners]) try {
4924
4997
  listener();
4925
4998
  } catch {
4926
4999
  }
4927
5000
  }
5001
+ function readDaemonModelCatalog(sessionCwd) {
5002
+ return loadModelCatalog(() => withTransientDaemonSession(sessionCwd, (client, activeSessionId) => client.getModelCatalog(activeSessionId)));
5003
+ }
4928
5004
  function applySessionBinding(route, frame) {
4929
5005
  if (!isDaemonSessionBindingFrame(frame)) return void 0;
4930
5006
  if (frame.previousActiveSessionId !== route.activeSessionId && frame.activeSessionId !== route.activeSessionId) return;
@@ -6974,154 +7050,687 @@ async function copyManagedPlanPresentationsForFork(source, forked, forkedMessage
6974
7050
  if (copied.length === 0) return;
6975
7051
  await writePresentationsAtomic(forked, copied);
6976
7052
  }
6977
- var DEFAULT_TIMEOUT_MS = 6e4;
6978
- var PendingDialogRegistry = class {
6979
- bySession = /* @__PURE__ */ new Map();
6980
- defaultTimeoutMs;
6981
- emitFrame;
6982
- constructor(options) {
6983
- this.defaultTimeoutMs = options.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS;
6984
- this.emitFrame = options.emitFrame;
6985
- }
6986
- get(sessionId, toolCallId) {
6987
- return this.bySession.get(sessionId)?.get(toolCallId);
6988
- }
6989
- list(sessionId) {
6990
- const map = this.bySession.get(sessionId);
6991
- return map ? [...map.values()] : [];
6992
- }
6993
- snapshot(sessionId) {
6994
- return this.list(sessionId).map((d) => ({
6995
- sessionId: d.sessionId,
6996
- toolCallId: d.toolCallId,
6997
- kind: d.kind,
6998
- title: d.title,
6999
- ...d.message ? { message: d.message } : {},
7000
- ...d.options ? { options: [...d.options] } : {},
7001
- ...d.questions ? { questions: [...d.questions] } : {},
7002
- ...d.placeholder ? { placeholder: d.placeholder } : {},
7003
- createdAt: d.createdAt,
7004
- ...d.timeoutMs !== void 0 ? { timeoutMs: d.timeoutMs } : {}
7005
- }));
7053
+ var LOCK_TIMEOUT_MS = 5e3;
7054
+ var LOCK_RETRY_MS = 25;
7055
+ function isJsonObject$2(value) {
7056
+ return typeof value === "object" && value !== null && !Array.isArray(value);
7057
+ }
7058
+ function errorCode(error) {
7059
+ return isJsonObject$2(error) && typeof error.code === "string" ? error.code : void 0;
7060
+ }
7061
+ async function readJsonObjectFile(path, parse = JSON.parse) {
7062
+ let raw;
7063
+ try {
7064
+ raw = await readFile(path, "utf8");
7065
+ } catch (error) {
7066
+ if (errorCode(error) === "ENOENT") return { kind: "missing" };
7067
+ return {
7068
+ kind: "invalid",
7069
+ error: error instanceof Error ? error.message : "unreadable"
7070
+ };
7006
7071
  }
7007
- async open(args) {
7008
- const map = this.forSession(args.sessionId);
7009
- const timeoutDuration = args.timeoutMs ?? this.defaultTimeoutMs;
7010
- return await new Promise((resolve2, reject) => {
7011
- let settled = false;
7012
- const dialog = {
7013
- sessionId: args.sessionId,
7014
- toolCallId: args.toolCallId,
7015
- kind: args.kind,
7016
- title: args.title,
7017
- message: args.message,
7018
- options: args.options,
7019
- questions: args.questions,
7020
- placeholder: args.placeholder,
7021
- createdAt: Date.now(),
7022
- timeoutMs: timeoutDuration,
7023
- resolve: (value) => {
7024
- if (settled) return;
7025
- settled = true;
7026
- resolve2(value);
7027
- },
7028
- reject: (err) => {
7029
- if (settled) return;
7030
- settled = true;
7031
- reject(err);
7032
- },
7033
- timer: setTimeout(() => {
7034
- if (settled) return;
7035
- this.cancel(args.sessionId, args.toolCallId, "timeout");
7036
- }, timeoutDuration)
7037
- };
7038
- map.set(args.toolCallId, dialog);
7039
- this.emitFrame(args.sessionId, {
7040
- type: "tool",
7041
- part: args.signalFrame
7042
- });
7043
- });
7072
+ try {
7073
+ const parsed = parse(raw);
7074
+ if (!isJsonObject$2(parsed)) return {
7075
+ kind: "invalid",
7076
+ error: "expected a JSON object"
7077
+ };
7078
+ return {
7079
+ kind: "ok",
7080
+ value: parsed
7081
+ };
7082
+ } catch (error) {
7083
+ return {
7084
+ kind: "invalid",
7085
+ error: error instanceof Error ? error.message : "invalid JSON"
7086
+ };
7044
7087
  }
7045
- /**
7046
- * Resolve a pending dialog on `POST /api/chat/question`. Returns true iff a
7047
- * pending entry was found (caller returns 404 when false so the client can
7048
- * distinguish "already answered" from "unknown toolCallId").
7049
- */
7050
- answer(sessionId, toolCallId, answer) {
7051
- const dialog = this.get(sessionId, toolCallId);
7052
- if (!dialog) return false;
7053
- clearTimeout(dialog.timer);
7054
- this.bySession.get(sessionId)?.delete(toolCallId);
7055
- dialog.resolve(answer);
7088
+ }
7089
+ function isProcessAlive(pid) {
7090
+ if (!Number.isInteger(pid) || pid <= 0) return false;
7091
+ try {
7092
+ process.kill(pid, 0);
7056
7093
  return true;
7094
+ } catch (error) {
7095
+ return errorCode(error) === "EPERM";
7057
7096
  }
7058
- /**
7059
- * Cancel a pending dialog, emitting a `tool-Question` cancellation frame so
7060
- * connected/replaying UIs can clear the question card.
7061
- */
7062
- cancel(sessionId, toolCallId, reason) {
7063
- const dialog = this.get(sessionId, toolCallId);
7064
- if (!dialog) return false;
7065
- clearTimeout(dialog.timer);
7066
- this.bySession.get(sessionId)?.delete(toolCallId);
7067
- const frame = {
7068
- type: "tool",
7069
- part: {
7070
- type: "tool-Question",
7071
- toolCallId,
7072
- state: "output-error",
7073
- output: {
7074
- cancelled: true,
7075
- reason,
7076
- title: dialog.title
7077
- }
7097
+ }
7098
+ async function acquireLock(lockPath) {
7099
+ const deadline = Date.now() + LOCK_TIMEOUT_MS;
7100
+ await mkdir(dirname(lockPath), { recursive: true });
7101
+ for (; ; ) try {
7102
+ const descriptor = openSync(lockPath, "wx", 384);
7103
+ writeFileSync(descriptor, String(process.pid), "utf8");
7104
+ closeSync(descriptor);
7105
+ return { release: () => {
7106
+ rmSync(lockPath, { force: true });
7107
+ } };
7108
+ } catch (error) {
7109
+ if (errorCode(error) !== "EEXIST") throw error;
7110
+ let raw;
7111
+ try {
7112
+ const reader = openSync(lockPath, "r");
7113
+ try {
7114
+ raw = readFileSync(reader, "utf8");
7115
+ } finally {
7116
+ closeSync(reader);
7078
7117
  }
7079
- };
7080
- this.emitFrame(sessionId, frame);
7081
- dialog.reject(/* @__PURE__ */ new Error(`Question cancelled: ${reason}`));
7082
- return true;
7083
- }
7084
- /** Cancel every pending dialog in a session (used by `abort` and shutdown). */
7085
- cancelAll(sessionId, reason) {
7086
- const pending = this.list(sessionId);
7087
- for (const dialog of pending) {
7088
- if (this.bySession.get(sessionId)?.get(dialog.toolCallId) === void 0) continue;
7089
- this.cancel(sessionId, dialog.toolCallId, reason);
7118
+ } catch (readError) {
7119
+ if (errorCode(readError) === "ENOENT") continue;
7120
+ throw readError;
7090
7121
  }
7091
- return pending.length;
7092
- }
7093
- forSession(sessionId) {
7094
- let existing = this.bySession.get(sessionId);
7095
- if (!existing) {
7096
- existing = /* @__PURE__ */ new Map();
7097
- this.bySession.set(sessionId, existing);
7122
+ const holder = Number.parseInt(raw, 10);
7123
+ if (!isProcessAlive(holder)) {
7124
+ rmSync(lockPath, { force: true });
7125
+ continue;
7098
7126
  }
7099
- return existing;
7100
- }
7101
- };
7102
- var RingBuffer = class {
7103
- entries = [];
7104
- capacity;
7105
- nextSeq = 1;
7106
- generation = crypto.randomUUID();
7107
- constructor(capacity = 500) {
7108
- this.capacity = Math.max(1, capacity);
7127
+ if (Date.now() >= deadline) throw new Error(`Timed out waiting for another Fleet write (held by process ${Number.isNaN(holder) ? "unknown" : holder}).`);
7128
+ await new Promise((resolve2) => setTimeout(resolve2, LOCK_RETRY_MS));
7109
7129
  }
7110
- get streamGeneration() {
7111
- return this.generation;
7130
+ }
7131
+ function currentMode(path) {
7132
+ try {
7133
+ return statSync(path).mode & 511;
7134
+ } catch {
7135
+ return;
7112
7136
  }
7113
- push(event, decorate) {
7114
- const seq = this.nextSeq++;
7115
- const entry = {
7116
- seq,
7117
- event: decorate ? decorate(seq, this.generation) : event
7118
- };
7119
- this.entries.push(entry);
7120
- if (this.entries.length > this.capacity) this.entries.splice(0, this.entries.length - this.capacity);
7121
- return entry;
7137
+ }
7138
+ async function writeJsonObjectFile(path, value, options = {}) {
7139
+ await mkdir(dirname(path), { recursive: true });
7140
+ const temporary = `${path}.${process.pid}.${randomUUID()}.tmp`;
7141
+ const mode = currentMode(path) ?? options.mode ?? 420;
7142
+ try {
7143
+ await writeFile(temporary, `${JSON.stringify(value, null, " ")}
7144
+ `, {
7145
+ encoding: "utf8",
7146
+ mode
7147
+ });
7148
+ await chmod(temporary, mode);
7149
+ await rename(temporary, path);
7150
+ } catch (error) {
7151
+ await rm(temporary, { force: true });
7152
+ throw error;
7122
7153
  }
7123
- /**
7124
- * Replay entries with seq > `lastEventId`. If `lastEventId` is behind the
7154
+ }
7155
+ var UnwritablePathError = class extends Error {
7156
+ path;
7157
+ cause;
7158
+ constructor(path, cause) {
7159
+ const detail = cause instanceof Error ? cause.message : String(cause);
7160
+ super(`${path}: ${detail}`);
7161
+ this.path = path;
7162
+ this.cause = cause;
7163
+ this.name = "UnwritablePathError";
7164
+ }
7165
+ };
7166
+ async function updateJsonObjectFile(path, mutate, options = {}) {
7167
+ const lock = await acquireLock(`${path}.lock`).catch((error) => {
7168
+ throw new UnwritablePathError(path, error);
7169
+ });
7170
+ try {
7171
+ const current = await readJsonObjectFile(path, options.parse);
7172
+ if (current.kind === "invalid") return {
7173
+ status: "refused",
7174
+ reason: `${path} is not a readable JSON object: ${current.error}`
7175
+ };
7176
+ const outcome = mutate(current.kind === "ok" ? current.value : {});
7177
+ if ("refuse" in outcome) return {
7178
+ status: "refused",
7179
+ reason: outcome.refuse
7180
+ };
7181
+ if ("unchanged" in outcome) return {
7182
+ status: "unchanged",
7183
+ value: outcome.unchanged
7184
+ };
7185
+ try {
7186
+ await writeJsonObjectFile(path, outcome.next, { mode: options.mode });
7187
+ } catch (error) {
7188
+ throw new UnwritablePathError(path, error);
7189
+ }
7190
+ return {
7191
+ status: "written",
7192
+ value: outcome.result
7193
+ };
7194
+ } finally {
7195
+ lock.release();
7196
+ }
7197
+ }
7198
+ var AUTH_FILE = "auth.json";
7199
+ function isJsonObject$1(value) {
7200
+ return typeof value === "object" && value !== null && !Array.isArray(value);
7201
+ }
7202
+ function authFilePath(agentDir) {
7203
+ return join(agentDir, AUTH_FILE);
7204
+ }
7205
+ function isWritableProviderId(providerId) {
7206
+ return providerId.length > 0 && providerId.length <= 128 && !/[\u0000-\u001f/\\]/.test(providerId);
7207
+ }
7208
+ async function readProviderApiKey(agentDir, providerId) {
7209
+ const current = await readJsonObjectFile(authFilePath(agentDir));
7210
+ if (current.kind !== "ok") return void 0;
7211
+ const entry = current.value[providerId];
7212
+ if (!isJsonObject$1(entry) || entry.type !== "api_key") return void 0;
7213
+ const key = entry.key;
7214
+ return typeof key === "string" ? key : void 0;
7215
+ }
7216
+ async function setProviderApiKey(agentDir, providerId, key) {
7217
+ if (!isWritableProviderId(providerId)) return {
7218
+ status: "refused",
7219
+ reason: `"${providerId}" is not a valid provider id.`
7220
+ };
7221
+ const trimmed = key.trim();
7222
+ if (trimmed.length === 0) return {
7223
+ status: "refused",
7224
+ reason: "The API key is empty."
7225
+ };
7226
+ const outcome = await updateJsonObjectFile(authFilePath(agentDir), (current) => {
7227
+ const existing = current[providerId];
7228
+ const next = {
7229
+ ...isJsonObject$1(existing) ? existing : {},
7230
+ type: "api_key",
7231
+ key: trimmed
7232
+ };
7233
+ return {
7234
+ next: {
7235
+ ...current,
7236
+ [providerId]: next
7237
+ },
7238
+ result: true
7239
+ };
7240
+ }, { mode: 384 });
7241
+ return outcome.status === "refused" ? outcome : { status: "written" };
7242
+ }
7243
+ async function configuredProviderIds(agentDir) {
7244
+ const current = await readJsonObjectFile(authFilePath(agentDir));
7245
+ if (current.kind !== "ok") return /* @__PURE__ */ new Set();
7246
+ return new Set(Object.entries(current.value).filter(([, value]) => isJsonObject$1(value)).map(([providerId]) => providerId));
7247
+ }
7248
+ async function removeProviderCredential(agentDir, providerId) {
7249
+ const outcome = await updateJsonObjectFile(authFilePath(agentDir), (current) => {
7250
+ if (!(providerId in current)) return {
7251
+ next: current,
7252
+ result: "absent"
7253
+ };
7254
+ const next = {};
7255
+ for (const [currentId, value] of Object.entries(current)) if (currentId !== providerId) next[currentId] = value;
7256
+ return {
7257
+ next,
7258
+ result: "removed"
7259
+ };
7260
+ }, { mode: 384 });
7261
+ return outcome.status === "refused" ? outcome : { status: "written" };
7262
+ }
7263
+ var BUILT_IN_PROVIDER_DISPLAY_NAMES = {
7264
+ anthropic: "Anthropic",
7265
+ "amazon-bedrock": "Amazon Bedrock",
7266
+ "azure-openai-responses": "Azure OpenAI Responses",
7267
+ cerebras: "Cerebras",
7268
+ "cloudflare-ai-gateway": "Cloudflare AI Gateway",
7269
+ "cloudflare-workers-ai": "Cloudflare Workers AI",
7270
+ deepseek: "DeepSeek",
7271
+ fireworks: "Fireworks",
7272
+ google: "Google Gemini",
7273
+ "google-vertex": "Google Vertex AI",
7274
+ groq: "Groq",
7275
+ huggingface: "Hugging Face",
7276
+ "kimi-coding": "Kimi For Coding",
7277
+ mistral: "Mistral",
7278
+ minimax: "MiniMax",
7279
+ "minimax-cn": "MiniMax (China)",
7280
+ moonshotai: "Moonshot AI",
7281
+ "moonshotai-cn": "Moonshot AI (China)",
7282
+ opencode: "OpenCode Zen",
7283
+ "opencode-go": "OpenCode Go",
7284
+ openai: "OpenAI",
7285
+ "openai-codex": "OpenAI Codex",
7286
+ openrouter: "OpenRouter",
7287
+ "prime-agent-traces": "Prime Agent Traces",
7288
+ "prime-inference": "Prime Inference",
7289
+ "vercel-ai-gateway": "Vercel AI Gateway",
7290
+ xai: "xAI",
7291
+ zai: "ZAI",
7292
+ xiaomi: "Xiaomi MiMo",
7293
+ "xiaomi-token-plan-cn": "Xiaomi MiMo Token Plan (China)",
7294
+ "xiaomi-token-plan-ams": "Xiaomi MiMo Token Plan (Amsterdam)",
7295
+ "xiaomi-token-plan-sgp": "Xiaomi MiMo Token Plan (Singapore)",
7296
+ "github-copilot": "GitHub Copilot"
7297
+ };
7298
+ function isLlmsProviderId(providerId) {
7299
+ return providerId !== "prime-agent-traces";
7300
+ }
7301
+ function builtInProviderIds() {
7302
+ return Object.keys(BUILT_IN_PROVIDER_DISPLAY_NAMES).filter(isLlmsProviderId);
7303
+ }
7304
+ var BUILT_IN_PROVIDER_ID_SET = new Set(builtInProviderIds());
7305
+ function isBuiltInProviderId(providerId) {
7306
+ return BUILT_IN_PROVIDER_ID_SET.has(providerId);
7307
+ }
7308
+ function storedApiForCustomProvider(api) {
7309
+ return api === "google-genai" ? "google-generative-ai" : api;
7310
+ }
7311
+ function uiApiForCustomProvider(value) {
7312
+ if (value === "google-generative-ai") return "google-genai";
7313
+ return isPiCustomProviderApi(value) ? value : void 0;
7314
+ }
7315
+ function envVarNameForManagedProvider(providerId) {
7316
+ if (providerId === "openai-chat-completions") return "OPENAI_CHAT_COMPLETIONS_API_KEY";
7317
+ const slug = providerId.slice(providerId.indexOf("+") + 1).toUpperCase().replace(/[^A-Z0-9]+/g, "_");
7318
+ return isOccProviderId(providerId) ? `OPENAI_CHAT_COMPLETIONS_${slug}_API_KEY` : `CUSTOM_${slug}_API_KEY`;
7319
+ }
7320
+ function readModelsJson(filePath) {
7321
+ try {
7322
+ if (!existsSync(filePath)) return {};
7323
+ return parseModelsJson(readFileSync(filePath, "utf-8"));
7324
+ } catch {
7325
+ return {};
7326
+ }
7327
+ }
7328
+ function stripJsonComments(input) {
7329
+ return input.replace(/"(?:\\.|[^"\\])*"|\/\/[^\n]*/g, (match) => match[0] === '"' ? match : "").replace(/"(?:\\.|[^"\\])*"|,(\s*[}\]])/g, (match, tail) => tail ?? (match[0] === '"' ? match : ""));
7330
+ }
7331
+ function parseModelsJson(raw) {
7332
+ const parsed = JSON.parse(stripJsonComments(raw));
7333
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("models.json must contain an object");
7334
+ return parsed;
7335
+ }
7336
+ var OPENAI_COMPLETIONS_TOOL_REASONING_NONE_PATTERN = /^(?:[^/]+\/)?gpt-(?:5[.-]6|6)(?:[.-]|$)/i;
7337
+ var OPENAI_COMPLETIONS_REASONING_OFF_MAP = {
7338
+ off: "none",
7339
+ minimal: null,
7340
+ low: null,
7341
+ medium: null,
7342
+ high: null,
7343
+ xhigh: null,
7344
+ max: null
7345
+ };
7346
+ function applyModelDefaults(model, storedApi) {
7347
+ if (storedApi === "openai-completions" && OPENAI_COMPLETIONS_TOOL_REASONING_NONE_PATTERN.test(model.id) && model.reasoning === void 0 && model.thinkingLevelMap === void 0) return {
7348
+ ...model,
7349
+ reasoning: true,
7350
+ thinkingLevelMap: OPENAI_COMPLETIONS_REASONING_OFF_MAP
7351
+ };
7352
+ return model;
7353
+ }
7354
+ function mergeModelDefinitions(existing, incoming, storedApi) {
7355
+ const existingById = new Map((existing ?? []).map((model) => [model.id, model]));
7356
+ const seen = /* @__PURE__ */ new Set();
7357
+ return incoming.flatMap((model) => {
7358
+ if (seen.has(model.id)) return [];
7359
+ seen.add(model.id);
7360
+ return [applyModelDefaults({
7361
+ ...existingById.get(model.id),
7362
+ ...model
7363
+ }, storedApi)];
7364
+ });
7365
+ }
7366
+ function listCustomProviders(filePath) {
7367
+ return readModelsJson(filePath).providers ?? {};
7368
+ }
7369
+ function providersMissingApiKey(filePath) {
7370
+ return Object.entries(listCustomProviders(filePath)).filter(([providerId, entry]) => {
7371
+ if (isBuiltInProviderId(providerId)) return false;
7372
+ return typeof entry.apiKey !== "string" || entry.apiKey.length === 0;
7373
+ }).map(([providerId]) => providerId);
7374
+ }
7375
+ async function updateModelsJson(filePath, mutate) {
7376
+ const outcome = await updateJsonObjectFile(filePath, (current) => {
7377
+ const next = mutate(current);
7378
+ return next ? {
7379
+ next,
7380
+ result: void 0
7381
+ } : { unchanged: void 0 };
7382
+ }, {
7383
+ mode: 420,
7384
+ parse: parseModelsJson
7385
+ });
7386
+ if (outcome.status === "refused") throw new Error(`Refusing to update models.json because it could not be parsed: ${outcome.reason}`);
7387
+ }
7388
+ function defaultCompatForCustomProvider(storedApi, baseUrl, existingCompat) {
7389
+ const existingRecord = existingCompat && typeof existingCompat === "object" && !Array.isArray(existingCompat) ? existingCompat : void 0;
7390
+ if (storedApi !== "openai-completions") return existingRecord;
7391
+ try {
7392
+ if (new URL(baseUrl).hostname === "api.openai.com") return existingRecord;
7393
+ } catch {
7394
+ }
7395
+ return {
7396
+ supportsStore: false,
7397
+ ...existingRecord
7398
+ };
7399
+ }
7400
+ async function upsertCustomProvider(filePath, providerId, input) {
7401
+ await updateModelsJson(filePath, (data) => {
7402
+ const providers = { ...data.providers ?? {} };
7403
+ const existing = providers[providerId] ?? {};
7404
+ const storedApi = storedApiForCustomProvider(input.api);
7405
+ const compat = defaultCompatForCustomProvider(storedApi, input.baseUrl, existing.compat);
7406
+ providers[providerId] = {
7407
+ ...existing,
7408
+ name: input.name ?? existing.name,
7409
+ baseUrl: input.baseUrl,
7410
+ api: storedApi,
7411
+ apiKey: input.apiKey,
7412
+ ...compat ? { compat } : {},
7413
+ models: mergeModelDefinitions(existing.models, input.models, storedApi)
7414
+ };
7415
+ return {
7416
+ ...data,
7417
+ providers
7418
+ };
7419
+ });
7420
+ }
7421
+ async function addCustomProviderModelIds(filePath, providerId, modelIds) {
7422
+ await updateModelsJson(filePath, (data) => {
7423
+ const providers = { ...data.providers ?? {} };
7424
+ const existing = providers[providerId];
7425
+ if (!existing) return;
7426
+ const compat = typeof existing.api === "string" && typeof existing.baseUrl === "string" ? defaultCompatForCustomProvider(existing.api, existing.baseUrl, existing.compat) : void 0;
7427
+ providers[providerId] = {
7428
+ ...existing,
7429
+ ...compat ? { compat } : {},
7430
+ models: mergeModelDefinitions(existing.models, [...existing.models ?? [], ...modelIds.map((id) => ({ id }))], existing.api)
7431
+ };
7432
+ return {
7433
+ ...data,
7434
+ providers
7435
+ };
7436
+ });
7437
+ }
7438
+ async function removeCustomProvider(filePath, providerId) {
7439
+ await updateModelsJson(filePath, (data) => {
7440
+ const providers = data.providers;
7441
+ if (!providers || !(providerId in providers)) return;
7442
+ const next = { ...providers };
7443
+ delete next[providerId];
7444
+ return {
7445
+ ...data,
7446
+ providers: next
7447
+ };
7448
+ });
7449
+ }
7450
+ function isDiscoverableEntry(entry) {
7451
+ const baseUrl = entry?.baseUrl;
7452
+ if (!baseUrl) return false;
7453
+ const api = uiApiForCustomProvider(entry?.api);
7454
+ if (api && !isOccFamilyApi(api)) return false;
7455
+ if (entry?.api && !api) return false;
7456
+ try {
7457
+ const parsed = new URL(baseUrl);
7458
+ return parsed.protocol === "https:" || parsed.protocol === "http:";
7459
+ } catch {
7460
+ return false;
7461
+ }
7462
+ }
7463
+ var PRIME_PROVIDER_ENV_MAP = {
7464
+ openai: "OPENAI_API_KEY",
7465
+ "azure-openai-responses": "AZURE_OPENAI_API_KEY",
7466
+ "prime-inference": "PRIME_API_KEY",
7467
+ deepseek: "DEEPSEEK_API_KEY",
7468
+ google: "GEMINI_API_KEY",
7469
+ "google-vertex": "GOOGLE_CLOUD_API_KEY",
7470
+ groq: "GROQ_API_KEY",
7471
+ cerebras: "CEREBRAS_API_KEY",
7472
+ xai: "XAI_API_KEY",
7473
+ openrouter: "OPENROUTER_API_KEY",
7474
+ "vercel-ai-gateway": "AI_GATEWAY_API_KEY",
7475
+ zai: "ZAI_API_KEY",
7476
+ mistral: "MISTRAL_API_KEY",
7477
+ minimax: "MINIMAX_API_KEY",
7478
+ "minimax-cn": "MINIMAX_CN_API_KEY",
7479
+ moonshotai: "MOONSHOT_API_KEY",
7480
+ "moonshotai-cn": "MOONSHOT_API_KEY",
7481
+ huggingface: "HF_TOKEN",
7482
+ fireworks: "FIREWORKS_API_KEY",
7483
+ opencode: "OPENCODE_API_KEY",
7484
+ "opencode-go": "OPENCODE_API_KEY",
7485
+ "kimi-coding": "KIMI_API_KEY",
7486
+ "cloudflare-workers-ai": "CLOUDFLARE_API_KEY",
7487
+ "cloudflare-ai-gateway": "CLOUDFLARE_API_KEY",
7488
+ xiaomi: "XIAOMI_API_KEY",
7489
+ "xiaomi-token-plan-cn": "XIAOMI_TOKEN_PLAN_CN_API_KEY",
7490
+ "xiaomi-token-plan-ams": "XIAOMI_TOKEN_PLAN_AMS_API_KEY",
7491
+ "xiaomi-token-plan-sgp": "XIAOMI_TOKEN_PLAN_SGP_API_KEY"
7492
+ };
7493
+ var MULTI_ENV_PROVIDER_MAP = {
7494
+ anthropic: ["ANTHROPIC_OAUTH_TOKEN", "ANTHROPIC_API_KEY"],
7495
+ "github-copilot": [
7496
+ "COPILOT_GITHUB_TOKEN",
7497
+ "GH_TOKEN",
7498
+ "GITHUB_TOKEN"
7499
+ ]
7500
+ };
7501
+ function providerEnvVarCandidates(providerId) {
7502
+ const multi = MULTI_ENV_PROVIDER_MAP[providerId];
7503
+ if (multi) return multi;
7504
+ const single = PRIME_PROVIDER_ENV_MAP[providerId];
7505
+ return single ? [single] : [];
7506
+ }
7507
+ var THINKING_LEVELS = [
7508
+ "off",
7509
+ "minimal",
7510
+ "low",
7511
+ "medium",
7512
+ "high",
7513
+ "xhigh",
7514
+ "max"
7515
+ ];
7516
+ var EXPLICIT_ONLY_LEVELS = /* @__PURE__ */ new Set(["xhigh", "max"]);
7517
+ function supportedThinkingLevels(model) {
7518
+ if (!model.reasoning) return ["off"];
7519
+ const map = model.thinkingLevelMap;
7520
+ return THINKING_LEVELS.filter((level) => {
7521
+ const mapped = map?.[level];
7522
+ if (mapped === null) return false;
7523
+ if (EXPLICIT_ONLY_LEVELS.has(level)) return mapped !== void 0;
7524
+ return true;
7525
+ });
7526
+ }
7527
+ var ENV_VAR_NAME_PATTERN = /^[A-Z][A-Z0-9_]*$/;
7528
+ function declaredApiKeyResolves(declared, env) {
7529
+ if (typeof declared !== "string" || declared.length === 0) return false;
7530
+ if (declared.startsWith("!")) return true;
7531
+ if (!ENV_VAR_NAME_PATTERN.test(declared)) return true;
7532
+ return (env[declared] ?? "").length > 0;
7533
+ }
7534
+ function hasAmbientCredential(provider, env) {
7535
+ if (provider === "google-vertex") {
7536
+ const hasProject = Boolean(env.GOOGLE_CLOUD_PROJECT || env.GCLOUD_PROJECT);
7537
+ const hasLocation = Boolean(env.GOOGLE_CLOUD_LOCATION);
7538
+ if (!hasProject || !hasLocation) return false;
7539
+ const gacPath = env.GOOGLE_APPLICATION_CREDENTIALS;
7540
+ return existsSync(gacPath || join(homedir(), ".config", "gcloud", "application_default_credentials.json"));
7541
+ }
7542
+ if (provider === "amazon-bedrock") return Boolean(env.AWS_PROFILE || env.AWS_ACCESS_KEY_ID && env.AWS_SECRET_ACCESS_KEY || env.AWS_BEARER_TOKEN_BEDROCK || env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI || env.AWS_CONTAINER_CREDENTIALS_FULL_URI || env.AWS_WEB_IDENTITY_TOKEN_FILE);
7543
+ return false;
7544
+ }
7545
+ function hasEnvironmentCredential(provider, env = process.env) {
7546
+ for (const envVar of providerEnvVarCandidates(provider)) if ((env[envVar] ?? "").length > 0) return true;
7547
+ return hasAmbientCredential(provider, env);
7548
+ }
7549
+ async function providerAvailability(agentDir, env = process.env) {
7550
+ const storedProviders = await configuredProviderIds(agentDir).catch(() => /* @__PURE__ */ new Set());
7551
+ const declaredKeys = listCustomProviders(`${agentDir}/models.json`);
7552
+ const usableDeclared = /* @__PURE__ */ new Set();
7553
+ for (const [providerId, entry] of Object.entries(declaredKeys)) if (declaredApiKeyResolves(entry.apiKey, env)) usableDeclared.add(providerId);
7554
+ return (provider) => {
7555
+ if (storedProviders.has(provider)) return true;
7556
+ if (hasEnvironmentCredential(provider, env)) return true;
7557
+ return usableDeclared.has(provider);
7558
+ };
7559
+ }
7560
+ function toChatModelInfo(model, available) {
7561
+ const thinkingLevels = supportedThinkingLevels(model);
7562
+ const fallback = model.reasoning ? "medium" : "off";
7563
+ return {
7564
+ key: `${model.provider}/${model.id}`,
7565
+ provider: model.provider,
7566
+ id: model.id,
7567
+ name: model.name,
7568
+ reasoning: model.reasoning,
7569
+ input: model.input,
7570
+ contextWindow: model.contextWindow,
7571
+ maxTokens: model.maxTokens,
7572
+ available,
7573
+ thinkingLevels,
7574
+ defaultThinkingLevel: thinkingLevels.includes(fallback) ? fallback : thinkingLevels[0] ?? "off"
7575
+ };
7576
+ }
7577
+ function toChatModelInfoList(catalog, isProviderAvailable) {
7578
+ return catalog.models.map((model) => toChatModelInfo(model, isProviderAvailable(model.provider)));
7579
+ }
7580
+ function catalogHasModel(catalog, provider, id) {
7581
+ return catalog.models.some((model) => model.provider === provider && model.id === id);
7582
+ }
7583
+ function firstAvailableModelId(catalog, isProviderAvailable, provider) {
7584
+ return catalog.models.find((model) => model.provider === provider && isProviderAvailable(model.provider))?.id;
7585
+ }
7586
+ var DEFAULT_TIMEOUT_MS = 6e4;
7587
+ var PendingDialogRegistry = class {
7588
+ bySession = /* @__PURE__ */ new Map();
7589
+ defaultTimeoutMs;
7590
+ emitFrame;
7591
+ constructor(options) {
7592
+ this.defaultTimeoutMs = options.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS;
7593
+ this.emitFrame = options.emitFrame;
7594
+ }
7595
+ get(sessionId, toolCallId) {
7596
+ return this.bySession.get(sessionId)?.get(toolCallId);
7597
+ }
7598
+ list(sessionId) {
7599
+ const map = this.bySession.get(sessionId);
7600
+ return map ? [...map.values()] : [];
7601
+ }
7602
+ snapshot(sessionId) {
7603
+ return this.list(sessionId).map((d) => ({
7604
+ sessionId: d.sessionId,
7605
+ toolCallId: d.toolCallId,
7606
+ kind: d.kind,
7607
+ title: d.title,
7608
+ ...d.message ? { message: d.message } : {},
7609
+ ...d.options ? { options: [...d.options] } : {},
7610
+ ...d.questions ? { questions: [...d.questions] } : {},
7611
+ ...d.placeholder ? { placeholder: d.placeholder } : {},
7612
+ createdAt: d.createdAt,
7613
+ ...d.timeoutMs !== void 0 ? { timeoutMs: d.timeoutMs } : {}
7614
+ }));
7615
+ }
7616
+ async open(args) {
7617
+ const map = this.forSession(args.sessionId);
7618
+ const timeoutDuration = args.timeoutMs ?? this.defaultTimeoutMs;
7619
+ return await new Promise((resolve2, reject) => {
7620
+ let settled = false;
7621
+ const dialog = {
7622
+ sessionId: args.sessionId,
7623
+ toolCallId: args.toolCallId,
7624
+ kind: args.kind,
7625
+ title: args.title,
7626
+ message: args.message,
7627
+ options: args.options,
7628
+ questions: args.questions,
7629
+ placeholder: args.placeholder,
7630
+ createdAt: Date.now(),
7631
+ timeoutMs: timeoutDuration,
7632
+ resolve: (value) => {
7633
+ if (settled) return;
7634
+ settled = true;
7635
+ resolve2(value);
7636
+ },
7637
+ reject: (err) => {
7638
+ if (settled) return;
7639
+ settled = true;
7640
+ reject(err);
7641
+ },
7642
+ timer: setTimeout(() => {
7643
+ if (settled) return;
7644
+ this.cancel(args.sessionId, args.toolCallId, "timeout");
7645
+ }, timeoutDuration)
7646
+ };
7647
+ map.set(args.toolCallId, dialog);
7648
+ this.emitFrame(args.sessionId, {
7649
+ type: "tool",
7650
+ part: args.signalFrame
7651
+ });
7652
+ });
7653
+ }
7654
+ /**
7655
+ * Resolve a pending dialog on `POST /api/chat/question`. Returns true iff a
7656
+ * pending entry was found (caller returns 404 when false so the client can
7657
+ * distinguish "already answered" from "unknown toolCallId").
7658
+ */
7659
+ answer(sessionId, toolCallId, answer) {
7660
+ const dialog = this.get(sessionId, toolCallId);
7661
+ if (!dialog) return false;
7662
+ clearTimeout(dialog.timer);
7663
+ this.bySession.get(sessionId)?.delete(toolCallId);
7664
+ dialog.resolve(answer);
7665
+ return true;
7666
+ }
7667
+ /**
7668
+ * Cancel a pending dialog, emitting a `tool-Question` cancellation frame so
7669
+ * connected/replaying UIs can clear the question card.
7670
+ */
7671
+ cancel(sessionId, toolCallId, reason) {
7672
+ const dialog = this.get(sessionId, toolCallId);
7673
+ if (!dialog) return false;
7674
+ clearTimeout(dialog.timer);
7675
+ this.bySession.get(sessionId)?.delete(toolCallId);
7676
+ const frame = {
7677
+ type: "tool",
7678
+ part: {
7679
+ type: "tool-Question",
7680
+ toolCallId,
7681
+ state: "output-error",
7682
+ output: {
7683
+ cancelled: true,
7684
+ reason,
7685
+ title: dialog.title
7686
+ }
7687
+ }
7688
+ };
7689
+ this.emitFrame(sessionId, frame);
7690
+ dialog.reject(/* @__PURE__ */ new Error(`Question cancelled: ${reason}`));
7691
+ return true;
7692
+ }
7693
+ /** Cancel every pending dialog in a session (used by `abort` and shutdown). */
7694
+ cancelAll(sessionId, reason) {
7695
+ const pending = this.list(sessionId);
7696
+ for (const dialog of pending) {
7697
+ if (this.bySession.get(sessionId)?.get(dialog.toolCallId) === void 0) continue;
7698
+ this.cancel(sessionId, dialog.toolCallId, reason);
7699
+ }
7700
+ return pending.length;
7701
+ }
7702
+ forSession(sessionId) {
7703
+ let existing = this.bySession.get(sessionId);
7704
+ if (!existing) {
7705
+ existing = /* @__PURE__ */ new Map();
7706
+ this.bySession.set(sessionId, existing);
7707
+ }
7708
+ return existing;
7709
+ }
7710
+ };
7711
+ var RingBuffer = class {
7712
+ entries = [];
7713
+ capacity;
7714
+ nextSeq = 1;
7715
+ generation = crypto.randomUUID();
7716
+ constructor(capacity = 500) {
7717
+ this.capacity = Math.max(1, capacity);
7718
+ }
7719
+ get streamGeneration() {
7720
+ return this.generation;
7721
+ }
7722
+ push(event, decorate) {
7723
+ const seq = this.nextSeq++;
7724
+ const entry = {
7725
+ seq,
7726
+ event: decorate ? decorate(seq, this.generation) : event
7727
+ };
7728
+ this.entries.push(entry);
7729
+ if (this.entries.length > this.capacity) this.entries.splice(0, this.entries.length - this.capacity);
7730
+ return entry;
7731
+ }
7732
+ /**
7733
+ * Replay entries with seq > `lastEventId`. If `lastEventId` is behind the
7125
7734
  * oldest retained entry or ahead of the current sequence (for example after a
7126
7735
  * process restart), return `{ replayed, overflowed: true }` so the caller
7127
7736
  * emits a resync frame and instructs the client to rehydrate.
@@ -8198,21 +8807,32 @@ var PrimeBridge = class {
8198
8807
  }
8199
8808
  async setModel(sessionId, model) {
8200
8809
  const session = this.#requireSession(sessionId);
8201
- if (getPrimeConfig().modelRegistry.find(model.provider, model.id)) {
8810
+ const catalog = await readDaemonModelCatalog(session.cwd).catch((error) => {
8811
+ process.stderr.write(`[bridge:${sessionId.slice(0, 8)}] model catalog unavailable, passing ${model.provider}/${model.id} through: ${error instanceof Error ? error.message : String(error)}
8812
+ `);
8813
+ });
8814
+ if (!catalog) {
8815
+ await session.connection.setModel(model.provider, model.id);
8816
+ return {
8817
+ provider: model.provider,
8818
+ id: model.id
8819
+ };
8820
+ }
8821
+ if (catalogHasModel(catalog, model.provider, model.id)) {
8202
8822
  await session.connection.setModel(model.provider, model.id);
8203
8823
  return {
8204
8824
  provider: model.provider,
8205
8825
  id: model.id
8206
8826
  };
8207
8827
  }
8208
- const fallback = getPrimeConfig().modelRegistry.getAvailable().find((candidate) => candidate.provider === model.provider);
8828
+ const fallback = firstAvailableModelId(catalog, await providerAvailability(getPrimeConfig().agentDir), model.provider);
8209
8829
  if (!fallback) throw new Error(`Unknown model: ${model.provider}/${model.id}`);
8210
- process.stderr.write(`[bridge:${sessionId.slice(0, 8)}] model fallback ${model.provider}/${model.id} -> ${fallback.provider}/${fallback.id}
8830
+ process.stderr.write(`[bridge:${sessionId.slice(0, 8)}] model fallback ${model.provider}/${model.id} -> ${model.provider}/${fallback}
8211
8831
  `);
8212
- await session.connection.setModel(fallback.provider, fallback.id);
8832
+ await session.connection.setModel(model.provider, fallback);
8213
8833
  return {
8214
- provider: fallback.provider,
8215
- id: fallback.id
8834
+ provider: model.provider,
8835
+ id: fallback
8216
8836
  };
8217
8837
  }
8218
8838
  async setThinkingLevel(level) {
@@ -9765,238 +10385,32 @@ function fleetSettingsPath(agentDir) {
9765
10385
  async function readFleetSettings(agentDir) {
9766
10386
  try {
9767
10387
  return {
9768
- version: 1,
9769
- composerIntentEnabled: JSON.parse(await readFile(fleetSettingsPath(agentDir), "utf8")).composerIntentEnabled === true
9770
- };
9771
- } catch {
9772
- return { ...DEFAULT_FLEET_SETTINGS };
9773
- }
9774
- }
9775
- var writeChain = Promise.resolve();
9776
- async function updateFleetSettings(agentDir, patch) {
9777
- const update = writeChain.then(() => writeFleetSettings(agentDir, patch));
9778
- writeChain = update.catch(() => void 0);
9779
- return update;
9780
- }
9781
- async function writeFleetSettings(agentDir, patch) {
9782
- const current = await readFleetSettings(agentDir);
9783
- const next = {
9784
- version: 1,
9785
- composerIntentEnabled: patch.composerIntentEnabled ?? current.composerIntentEnabled
9786
- };
9787
- const path = fleetSettingsPath(agentDir);
9788
- await mkdir(dirname(path), { recursive: true });
9789
- const temporary = `${path}.${process.pid}.${crypto.randomUUID()}.tmp`;
9790
- await writeFile(temporary, `${JSON.stringify(next, null, " ")}
9791
- `, "utf8");
9792
- await rename(temporary, path);
9793
- return next;
9794
- }
9795
- var LOCK_TIMEOUT_MS = 5e3;
9796
- var LOCK_RETRY_MS = 25;
9797
- function isJsonObject$2(value) {
9798
- return typeof value === "object" && value !== null && !Array.isArray(value);
9799
- }
9800
- function errorCode(error) {
9801
- return isJsonObject$2(error) && typeof error.code === "string" ? error.code : void 0;
9802
- }
9803
- async function readJsonObjectFile(path) {
9804
- let raw;
9805
- try {
9806
- raw = await readFile(path, "utf8");
9807
- } catch (error) {
9808
- if (errorCode(error) === "ENOENT") return { kind: "missing" };
9809
- return {
9810
- kind: "invalid",
9811
- error: error instanceof Error ? error.message : "unreadable"
9812
- };
9813
- }
9814
- try {
9815
- const parsed = JSON.parse(raw);
9816
- if (!isJsonObject$2(parsed)) return {
9817
- kind: "invalid",
9818
- error: "expected a JSON object"
9819
- };
9820
- return {
9821
- kind: "ok",
9822
- value: parsed
9823
- };
9824
- } catch (error) {
9825
- return {
9826
- kind: "invalid",
9827
- error: error instanceof Error ? error.message : "invalid JSON"
9828
- };
9829
- }
9830
- }
9831
- function isProcessAlive(pid) {
9832
- if (!Number.isInteger(pid) || pid <= 0) return false;
9833
- try {
9834
- process.kill(pid, 0);
9835
- return true;
9836
- } catch (error) {
9837
- return errorCode(error) === "EPERM";
9838
- }
9839
- }
9840
- async function acquireLock(lockPath) {
9841
- const deadline = Date.now() + LOCK_TIMEOUT_MS;
9842
- await mkdir(dirname(lockPath), { recursive: true });
9843
- for (; ; ) try {
9844
- const descriptor = openSync(lockPath, "wx", 384);
9845
- writeFileSync(descriptor, String(process.pid), "utf8");
9846
- closeSync(descriptor);
9847
- return { release: () => {
9848
- rmSync(lockPath, { force: true });
9849
- } };
9850
- } catch (error) {
9851
- if (errorCode(error) !== "EEXIST") throw error;
9852
- let raw;
9853
- try {
9854
- const reader = openSync(lockPath, "r");
9855
- try {
9856
- raw = readFileSync(reader, "utf8");
9857
- } finally {
9858
- closeSync(reader);
9859
- }
9860
- } catch (readError) {
9861
- if (errorCode(readError) === "ENOENT") continue;
9862
- throw readError;
9863
- }
9864
- const holder = Number.parseInt(raw, 10);
9865
- if (!isProcessAlive(holder)) {
9866
- rmSync(lockPath, { force: true });
9867
- continue;
9868
- }
9869
- if (Date.now() >= deadline) throw new Error(`Timed out waiting for another Fleet write (held by process ${Number.isNaN(holder) ? "unknown" : holder}).`);
9870
- await new Promise((resolve2) => setTimeout(resolve2, LOCK_RETRY_MS));
9871
- }
9872
- }
9873
- function currentMode(path) {
9874
- try {
9875
- return statSync(path).mode & 511;
9876
- } catch {
9877
- return;
9878
- }
9879
- }
9880
- async function writeJsonObjectFile(path, value, options = {}) {
9881
- await mkdir(dirname(path), { recursive: true });
9882
- const temporary = `${path}.${process.pid}.${randomUUID()}.tmp`;
9883
- const mode = currentMode(path) ?? options.mode ?? 420;
9884
- try {
9885
- await writeFile(temporary, `${JSON.stringify(value, null, " ")}
9886
- `, {
9887
- encoding: "utf8",
9888
- mode
9889
- });
9890
- await chmod(temporary, mode);
9891
- await rename(temporary, path);
9892
- } catch (error) {
9893
- await rm(temporary, { force: true });
9894
- throw error;
9895
- }
9896
- }
9897
- var UnwritablePathError = class extends Error {
9898
- path;
9899
- cause;
9900
- constructor(path, cause) {
9901
- const detail = cause instanceof Error ? cause.message : String(cause);
9902
- super(`${path}: ${detail}`);
9903
- this.path = path;
9904
- this.cause = cause;
9905
- this.name = "UnwritablePathError";
9906
- }
9907
- };
9908
- async function updateJsonObjectFile(path, mutate, options = {}) {
9909
- const lock = await acquireLock(`${path}.lock`).catch((error) => {
9910
- throw new UnwritablePathError(path, error);
9911
- });
9912
- try {
9913
- const current = await readJsonObjectFile(path);
9914
- if (current.kind === "invalid") return {
9915
- status: "refused",
9916
- reason: `${path} is not a readable JSON object: ${current.error}`
9917
- };
9918
- const outcome = mutate(current.kind === "ok" ? current.value : {});
9919
- if ("refuse" in outcome) return {
9920
- status: "refused",
9921
- reason: outcome.refuse
9922
- };
9923
- try {
9924
- await writeJsonObjectFile(path, outcome.next, options);
9925
- } catch (error) {
9926
- throw new UnwritablePathError(path, error);
9927
- }
9928
- return {
9929
- status: "written",
9930
- value: outcome.result
9931
- };
9932
- } finally {
9933
- lock.release();
9934
- }
9935
- }
9936
- var AUTH_FILE = "auth.json";
9937
- function isJsonObject$1(value) {
9938
- return typeof value === "object" && value !== null && !Array.isArray(value);
9939
- }
9940
- function authFilePath(agentDir) {
9941
- return join(agentDir, AUTH_FILE);
9942
- }
9943
- function isWritableProviderId(providerId) {
9944
- return providerId.length > 0 && providerId.length <= 128 && !/[\u0000-\u001f/\\]/.test(providerId);
9945
- }
9946
- async function readProviderApiKey(agentDir, providerId) {
9947
- const current = await readJsonObjectFile(authFilePath(agentDir));
9948
- if (current.kind !== "ok") return void 0;
9949
- const entry = current.value[providerId];
9950
- if (!isJsonObject$1(entry) || entry.type !== "api_key") return void 0;
9951
- const key = entry.key;
9952
- return typeof key === "string" ? key : void 0;
9953
- }
9954
- async function setProviderApiKey(agentDir, providerId, key) {
9955
- if (!isWritableProviderId(providerId)) return {
9956
- status: "refused",
9957
- reason: `"${providerId}" is not a valid provider id.`
9958
- };
9959
- const trimmed = key.trim();
9960
- if (trimmed.length === 0) return {
9961
- status: "refused",
9962
- reason: "The API key is empty."
9963
- };
9964
- const outcome = await updateJsonObjectFile(authFilePath(agentDir), (current) => {
9965
- const existing = current[providerId];
9966
- const next = {
9967
- ...isJsonObject$1(existing) ? existing : {},
9968
- type: "api_key",
9969
- key: trimmed
9970
- };
9971
- return {
9972
- next: {
9973
- ...current,
9974
- [providerId]: next
9975
- },
9976
- result: true
10388
+ version: 1,
10389
+ composerIntentEnabled: JSON.parse(await readFile(fleetSettingsPath(agentDir), "utf8")).composerIntentEnabled === true
9977
10390
  };
9978
- }, { mode: 384 });
9979
- return outcome.status === "refused" ? outcome : { status: "written" };
10391
+ } catch {
10392
+ return { ...DEFAULT_FLEET_SETTINGS };
10393
+ }
9980
10394
  }
9981
- async function configuredProviderIds(agentDir) {
9982
- const current = await readJsonObjectFile(authFilePath(agentDir));
9983
- if (current.kind !== "ok") return /* @__PURE__ */ new Set();
9984
- return new Set(Object.entries(current.value).filter(([, value]) => isJsonObject$1(value)).map(([providerId]) => providerId));
10395
+ var writeChain = Promise.resolve();
10396
+ async function updateFleetSettings(agentDir, patch) {
10397
+ const update = writeChain.then(() => writeFleetSettings(agentDir, patch));
10398
+ writeChain = update.catch(() => void 0);
10399
+ return update;
9985
10400
  }
9986
- async function removeProviderCredential(agentDir, providerId) {
9987
- const outcome = await updateJsonObjectFile(authFilePath(agentDir), (current) => {
9988
- if (!(providerId in current)) return {
9989
- next: current,
9990
- result: "absent"
9991
- };
9992
- const next = {};
9993
- for (const [currentId, value] of Object.entries(current)) if (currentId !== providerId) next[currentId] = value;
9994
- return {
9995
- next,
9996
- result: "removed"
9997
- };
9998
- }, { mode: 384 });
9999
- return outcome.status === "refused" ? outcome : { status: "written" };
10401
+ async function writeFleetSettings(agentDir, patch) {
10402
+ const current = await readFleetSettings(agentDir);
10403
+ const next = {
10404
+ version: 1,
10405
+ composerIntentEnabled: patch.composerIntentEnabled ?? current.composerIntentEnabled
10406
+ };
10407
+ const path = fleetSettingsPath(agentDir);
10408
+ await mkdir(dirname(path), { recursive: true });
10409
+ const temporary = `${path}.${process.pid}.${crypto.randomUUID()}.tmp`;
10410
+ await writeFile(temporary, `${JSON.stringify(next, null, " ")}
10411
+ `, "utf8");
10412
+ await rename(temporary, path);
10413
+ return next;
10000
10414
  }
10001
10415
  var TYPESAFE_CREDENTIAL_ID = "typesafe";
10002
10416
  async function readStoredTypeSafeKey() {
@@ -18238,7 +18652,7 @@ function handleChatMcpOAuthPost(request, deps = defaultMcpDeps()) {
18238
18652
  if ((body.action ?? "login") === "logout") return Response.json(await logoutMcp(body.name, deps));
18239
18653
  return Response.json({
18240
18654
  status: "error",
18241
- error: "MCP OAuth sign-in is unavailable in Fleet settings. Launch Prime Agent in a terminal using the same configuration directory as Fleet and complete MCP sign-in there. Restart Fleet afterward to reload the credentials."
18655
+ error: `Sign-in for ${body.name} has to run inside Prime Agent. In a terminal, start Prime Agent against the same account as Fleet, then run /mcp login ${body.name}. Fleet picks the credential up on its next refresh. If Fleet uses a non-default account directory, start Prime Agent with the same PRIME_AGENT_CODING_AGENT_DIR it uses.`
18242
18656
  }, { status: 501 });
18243
18657
  }, request);
18244
18658
  }
@@ -18257,25 +18671,17 @@ function handleChatMcpTokenPost(request, deps = defaultMcpDeps()) {
18257
18671
  });
18258
18672
  }, request);
18259
18673
  }
18260
- var THINKING_LEVELS = [
18261
- "off",
18262
- "minimal",
18263
- "low",
18264
- "medium",
18265
- "high",
18266
- "xhigh",
18267
- "max"
18268
- ];
18269
- var EXPLICIT_ONLY_LEVELS = /* @__PURE__ */ new Set(["xhigh", "max"]);
18270
- function supportedThinkingLevels(model) {
18271
- if (!model.reasoning) return ["off"];
18272
- const map = model.thinkingLevelMap;
18273
- return THINKING_LEVELS.filter((level) => {
18274
- const mapped = map?.[level];
18275
- if (mapped === null) return false;
18276
- if (EXPLICIT_ONLY_LEVELS.has(level)) return mapped !== void 0;
18277
- return true;
18278
- });
18674
+ function defaultChatModelsDeps() {
18675
+ const config = getPrimeConfig();
18676
+ return {
18677
+ resolveCwd: cwdForRequest,
18678
+ agentDir: config.agentDir,
18679
+ modelsJsonPath: `${config.agentDir}/models.json`,
18680
+ readCatalog: readDaemonModelCatalog,
18681
+ providerAvailability: (agentDir) => providerAvailability(agentDir),
18682
+ settingsFor: (cwd) => config.settingsFor(cwd),
18683
+ customProvidersMissingApiKey: providersMissingApiKey
18684
+ };
18279
18685
  }
18280
18686
  function toPatternCandidate(model) {
18281
18687
  return {
@@ -18283,46 +18689,31 @@ function toPatternCandidate(model) {
18283
18689
  name: model.name,
18284
18690
  provider: model.provider,
18285
18691
  modelId: model.id,
18286
- key: `${model.provider}/${model.id}`
18692
+ key: model.key
18287
18693
  };
18288
18694
  }
18289
- function isWorkingConfiguredModel(registry, model) {
18290
- return registry.getProviderAuthStatus(model.provider).configured && registry.hasConfiguredAuth(model);
18695
+ function customProviderDiagnostics(modelsJsonPath, offenders) {
18696
+ if (offenders.length === 0) return [];
18697
+ const label = safePathLabel(modelsJsonPath);
18698
+ return offenders.map((providerId) => `Provider ${providerId} in ${label} has no apiKey, so none of your custom providers can load. Add a non-empty "apiKey" to ${providerId} in models.json to fix it.`);
18291
18699
  }
18292
- function handleChatModelsGet(request) {
18700
+ function handleChatModelsGet(request, deps = defaultChatModelsDeps()) {
18293
18701
  return wrapApiHandler(async () => {
18294
- const config = getPrimeConfig();
18295
- const cwd = await cwdForRequest(request);
18296
- const settings = config.settingsFor(cwd);
18297
- const registry = config.modelRegistry;
18702
+ const cwd = await deps.resolveCwd(request);
18703
+ const settings = deps.settingsFor(cwd);
18298
18704
  const scope = new URL(request.url).searchParams.get("scope") === "all" ? "all" : "enabled";
18705
+ const isProviderAvailable = await deps.providerAvailability(deps.agentDir);
18706
+ let models = toChatModelInfoList(await deps.readCatalog(cwd), isProviderAvailable);
18299
18707
  const patterns = settings.getEnabledModels();
18300
- let models = registry.getAll();
18301
- if (scope === "enabled") models = models.filter((model) => isWorkingConfiguredModel(registry, model) && isModelPatternEnabled(toPatternCandidate(model), patterns));
18302
- const diagnostics = [];
18708
+ if (scope === "enabled") models = models.filter((model) => model.available && isModelPatternEnabled(toPatternCandidate(model), patterns));
18709
+ const diagnostics = customProviderDiagnostics(deps.modelsJsonPath, deps.customProvidersMissingApiKey(deps.modelsJsonPath));
18303
18710
  const defaultProvider = settings.getDefaultProvider();
18304
18711
  const defaultModel = settings.getDefaultModel();
18305
18712
  const defaultThinkingLevel = settings.getDefaultThinkingLevel();
18306
18713
  const candidateModelKey = defaultProvider && defaultModel ? `${defaultProvider}/${defaultModel}` : void 0;
18307
- const selectedModelKey = candidateModelKey && models.some((m) => `${m.provider}/${m.id}` === candidateModelKey) ? candidateModelKey : models[0] ? `${models[0].provider}/${models[0].id}` : void 0;
18714
+ const selectedModelKey = candidateModelKey && models.some((m) => m.key === candidateModelKey) ? candidateModelKey : models[0]?.key;
18308
18715
  return Response.json({
18309
- models: models.map((m) => {
18310
- const thinkingLevels = supportedThinkingLevels(m);
18311
- const fallback = m.reasoning ? "medium" : "off";
18312
- return {
18313
- key: `${m.provider}/${m.id}`,
18314
- provider: m.provider,
18315
- id: m.id,
18316
- name: m.name,
18317
- reasoning: m.reasoning,
18318
- input: m.input,
18319
- contextWindow: m.contextWindow,
18320
- maxTokens: m.maxTokens,
18321
- available: isWorkingConfiguredModel(registry, m),
18322
- thinkingLevels,
18323
- defaultThinkingLevel: thinkingLevels.includes(fallback) ? fallback : thinkingLevels[0] ?? "off"
18324
- };
18325
- }),
18716
+ models,
18326
18717
  ...selectedModelKey ? { selectedModelKey } : {},
18327
18718
  ...defaultProvider ? { defaultProvider } : {},
18328
18719
  ...defaultModel ? { defaultModel } : {},
@@ -18331,180 +18722,6 @@ function handleChatModelsGet(request) {
18331
18722
  });
18332
18723
  }, request);
18333
18724
  }
18334
- function storedApiForCustomProvider(api) {
18335
- return api === "google-genai" ? "google-generative-ai" : api;
18336
- }
18337
- function uiApiForCustomProvider(value) {
18338
- if (value === "google-generative-ai") return "google-genai";
18339
- return isPiCustomProviderApi(value) ? value : void 0;
18340
- }
18341
- function envVarNameForManagedProvider(providerId) {
18342
- if (providerId === "openai-chat-completions") return "OPENAI_CHAT_COMPLETIONS_API_KEY";
18343
- const slug = providerId.slice(providerId.indexOf("+") + 1).toUpperCase().replace(/[^A-Z0-9]+/g, "_");
18344
- return isOccProviderId(providerId) ? `OPENAI_CHAT_COMPLETIONS_${slug}_API_KEY` : `CUSTOM_${slug}_API_KEY`;
18345
- }
18346
- function readModelsJson(filePath) {
18347
- try {
18348
- if (!existsSync(filePath)) return {};
18349
- return parseModelsJson(readFileSync(filePath, "utf-8"));
18350
- } catch {
18351
- return {};
18352
- }
18353
- }
18354
- function stripJsonComments(input) {
18355
- return input.replace(/"(?:\\.|[^"\\])*"|\/\/[^\n]*/g, (match) => match[0] === '"' ? match : "").replace(/"(?:\\.|[^"\\])*"|,(\s*[}\]])/g, (match, tail) => tail ?? (match[0] === '"' ? match : ""));
18356
- }
18357
- function parseModelsJson(raw) {
18358
- const parsed = JSON.parse(stripJsonComments(raw));
18359
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("models.json must contain an object");
18360
- return parsed;
18361
- }
18362
- function readModelsJsonForWrite(filePath) {
18363
- if (!existsSync(filePath)) return {};
18364
- try {
18365
- return parseModelsJson(readFileSync(filePath, "utf-8"));
18366
- } catch (error) {
18367
- throw new Error(`Refusing to update models.json because it could not be parsed: ${error instanceof Error ? error.message : error}`);
18368
- }
18369
- }
18370
- var OPENAI_COMPLETIONS_TOOL_REASONING_NONE_PATTERN = /^(?:[^/]+\/)?gpt-(?:5[.-]6|6)(?:[.-]|$)/i;
18371
- var OPENAI_COMPLETIONS_REASONING_OFF_MAP = {
18372
- off: "none",
18373
- minimal: null,
18374
- low: null,
18375
- medium: null,
18376
- high: null,
18377
- xhigh: null,
18378
- max: null
18379
- };
18380
- function applyModelDefaults(model, storedApi) {
18381
- if (storedApi === "openai-completions" && OPENAI_COMPLETIONS_TOOL_REASONING_NONE_PATTERN.test(model.id) && model.reasoning === void 0 && model.thinkingLevelMap === void 0) return {
18382
- ...model,
18383
- reasoning: true,
18384
- thinkingLevelMap: OPENAI_COMPLETIONS_REASONING_OFF_MAP
18385
- };
18386
- return model;
18387
- }
18388
- function mergeModelDefinitions(existing, incoming, storedApi) {
18389
- const existingById = new Map((existing ?? []).map((model) => [model.id, model]));
18390
- const seen = /* @__PURE__ */ new Set();
18391
- return incoming.flatMap((model) => {
18392
- if (seen.has(model.id)) return [];
18393
- seen.add(model.id);
18394
- return [applyModelDefaults({
18395
- ...existingById.get(model.id),
18396
- ...model
18397
- }, storedApi)];
18398
- });
18399
- }
18400
- function listCustomProviders(filePath) {
18401
- return readModelsJson(filePath).providers ?? {};
18402
- }
18403
- async function writeModelsJson(filePath, data) {
18404
- await writeJsonObjectFile(filePath, data, { mode: 420 });
18405
- }
18406
- function defaultCompatForCustomProvider(storedApi, baseUrl, existingCompat) {
18407
- const existingRecord = existingCompat && typeof existingCompat === "object" && !Array.isArray(existingCompat) ? existingCompat : void 0;
18408
- if (storedApi !== "openai-completions") return existingRecord;
18409
- try {
18410
- if (new URL(baseUrl).hostname === "api.openai.com") return existingRecord;
18411
- } catch {
18412
- }
18413
- return {
18414
- supportsStore: false,
18415
- ...existingRecord
18416
- };
18417
- }
18418
- async function upsertCustomProvider(filePath, providerId, input) {
18419
- const data = readModelsJsonForWrite(filePath);
18420
- const providers = { ...data.providers ?? {} };
18421
- const existing = providers[providerId] ?? {};
18422
- const storedApi = storedApiForCustomProvider(input.api);
18423
- const compat = defaultCompatForCustomProvider(storedApi, input.baseUrl, existing.compat);
18424
- providers[providerId] = {
18425
- ...existing,
18426
- name: input.name ?? existing.name,
18427
- baseUrl: input.baseUrl,
18428
- api: storedApi,
18429
- apiKey: input.apiKey,
18430
- ...compat ? { compat } : {},
18431
- models: mergeModelDefinitions(existing.models, input.models, storedApi)
18432
- };
18433
- await writeModelsJson(filePath, {
18434
- ...data,
18435
- providers
18436
- });
18437
- }
18438
- async function addCustomProviderModelIds(filePath, providerId, modelIds) {
18439
- const data = readModelsJsonForWrite(filePath);
18440
- const providers = { ...data.providers ?? {} };
18441
- const existing = providers[providerId];
18442
- if (!existing) return;
18443
- const compat = typeof existing.api === "string" && typeof existing.baseUrl === "string" ? defaultCompatForCustomProvider(existing.api, existing.baseUrl, existing.compat) : void 0;
18444
- providers[providerId] = {
18445
- ...existing,
18446
- ...compat ? { compat } : {},
18447
- models: mergeModelDefinitions(existing.models, [...existing.models ?? [], ...modelIds.map((id) => ({ id }))], existing.api)
18448
- };
18449
- await writeModelsJson(filePath, {
18450
- ...data,
18451
- providers
18452
- });
18453
- }
18454
- async function removeCustomProvider(filePath, providerId) {
18455
- const data = readModelsJsonForWrite(filePath);
18456
- const providers = data.providers;
18457
- if (!providers || !(providerId in providers)) return;
18458
- const next = { ...providers };
18459
- delete next[providerId];
18460
- await writeModelsJson(filePath, {
18461
- ...data,
18462
- providers: next
18463
- });
18464
- }
18465
- function isDiscoverableEntry(entry) {
18466
- const baseUrl = entry?.baseUrl;
18467
- if (!baseUrl) return false;
18468
- const api = uiApiForCustomProvider(entry?.api);
18469
- if (api && !isOccFamilyApi(api)) return false;
18470
- if (entry?.api && !api) return false;
18471
- try {
18472
- const parsed = new URL(baseUrl);
18473
- return parsed.protocol === "https:" || parsed.protocol === "http:";
18474
- } catch {
18475
- return false;
18476
- }
18477
- }
18478
- var PRIME_PROVIDER_ENV_MAP = {
18479
- openai: "OPENAI_API_KEY",
18480
- "azure-openai-responses": "AZURE_OPENAI_API_KEY",
18481
- "prime-inference": "PRIME_API_KEY",
18482
- deepseek: "DEEPSEEK_API_KEY",
18483
- google: "GEMINI_API_KEY",
18484
- "google-vertex": "GOOGLE_CLOUD_API_KEY",
18485
- groq: "GROQ_API_KEY",
18486
- cerebras: "CEREBRAS_API_KEY",
18487
- xai: "XAI_API_KEY",
18488
- openrouter: "OPENROUTER_API_KEY",
18489
- "vercel-ai-gateway": "AI_GATEWAY_API_KEY",
18490
- zai: "ZAI_API_KEY",
18491
- mistral: "MISTRAL_API_KEY",
18492
- minimax: "MINIMAX_API_KEY",
18493
- "minimax-cn": "MINIMAX_CN_API_KEY",
18494
- moonshotai: "MOONSHOT_API_KEY",
18495
- "moonshotai-cn": "MOONSHOT_API_KEY",
18496
- huggingface: "HF_TOKEN",
18497
- fireworks: "FIREWORKS_API_KEY",
18498
- opencode: "OPENCODE_API_KEY",
18499
- "opencode-go": "OPENCODE_API_KEY",
18500
- "kimi-coding": "KIMI_API_KEY",
18501
- "cloudflare-workers-ai": "CLOUDFLARE_API_KEY",
18502
- "cloudflare-ai-gateway": "CLOUDFLARE_API_KEY",
18503
- xiaomi: "XIAOMI_API_KEY",
18504
- "xiaomi-token-plan-cn": "XIAOMI_TOKEN_PLAN_CN_API_KEY",
18505
- "xiaomi-token-plan-ams": "XIAOMI_TOKEN_PLAN_AMS_API_KEY",
18506
- "xiaomi-token-plan-sgp": "XIAOMI_TOKEN_PLAN_SGP_API_KEY"
18507
- };
18508
18725
  function providerEnvVarName(providerId) {
18509
18726
  return PRIME_PROVIDER_ENV_MAP[providerId];
18510
18727
  }
@@ -18520,6 +18737,7 @@ function openAiModelsUrl(baseUrl) {
18520
18737
  const parsed = new URL(baseUrl);
18521
18738
  if (parsed.protocol !== "https:" && parsed.protocol !== "http:") throw new Error("model discovery requires an http(s) URL");
18522
18739
  if (parsed.protocol === "http:" && !isLoopbackHostname(parsed.hostname)) throw new Error("model discovery requires https for non-loopback hosts");
18740
+ if (parsed.username || parsed.password) throw new Error("Credentials in model discovery URLs are not supported");
18523
18741
  const path = parsed.pathname.replace(/\/+$/, "");
18524
18742
  parsed.pathname = path.endsWith("/v1") ? `${path}/models` : `${path}/v1/models`;
18525
18743
  parsed.search = "";
@@ -18534,19 +18752,22 @@ function handleChatModelsDiscoverPost(request) {
18534
18752
  const apiKey = await resolveProviderApiKey(config.agentDir, body.providerId);
18535
18753
  if (!apiKey) return Response.json({
18536
18754
  providerId: body.providerId,
18537
- models: []
18755
+ models: [],
18756
+ error: "Model discovery is unavailable. Check provider configuration and try again."
18538
18757
  });
18539
18758
  const provider = body.providerId;
18540
18759
  const entry = listCustomProviders(`${config.agentDir}/models.json`)[provider];
18541
18760
  const api = uiApiForCustomProvider(entry?.api);
18542
18761
  if (entry?.api && !api || api && !isOccFamilyApi(api)) return Response.json({
18543
18762
  providerId: provider,
18544
- models: []
18763
+ models: [],
18764
+ error: "Model discovery is unavailable. Check provider configuration and try again."
18545
18765
  });
18546
18766
  const baseUrl = entry?.baseUrl;
18547
18767
  if (!baseUrl) return Response.json({
18548
18768
  providerId: provider,
18549
- models: []
18769
+ models: [],
18770
+ error: "Model discovery is unavailable. Check provider configuration and try again."
18550
18771
  });
18551
18772
  try {
18552
18773
  const modelsUrl = openAiModelsUrl(baseUrl);
@@ -18557,9 +18778,12 @@ function handleChatModelsDiscoverPost(request) {
18557
18778
  });
18558
18779
  if (!response.ok) return Response.json({
18559
18780
  providerId: provider,
18560
- models: []
18781
+ models: [],
18782
+ error: "Model discovery is unavailable. Check provider configuration and try again."
18561
18783
  });
18562
- const models = ((await response.json()).data ?? []).map((m) => ({
18784
+ const payload = await response.json();
18785
+ if (!Array.isArray(payload.data) || !payload.data.every((m) => typeof m?.id === "string" && m.id.length > 0)) throw new Error("Invalid model catalog");
18786
+ const models = payload.data.map((m) => ({
18563
18787
  key: `${provider}/${m.id}`,
18564
18788
  provider,
18565
18789
  id: m.id,
@@ -18581,7 +18805,8 @@ function handleChatModelsDiscoverPost(request) {
18581
18805
  } catch {
18582
18806
  return Response.json({
18583
18807
  providerId: provider,
18584
- models: []
18808
+ models: [],
18809
+ error: "Could not refresh models. Previous results are retained; try again."
18585
18810
  });
18586
18811
  }
18587
18812
  }, request);
@@ -18687,46 +18912,32 @@ function handleChatPlanPresentationPut(request) {
18687
18912
  }) });
18688
18913
  }, request);
18689
18914
  }
18690
- var BUILT_IN_PROVIDER_DISPLAY_NAMES = {
18691
- anthropic: "Anthropic",
18692
- "amazon-bedrock": "Amazon Bedrock",
18693
- "azure-openai-responses": "Azure OpenAI Responses",
18694
- cerebras: "Cerebras",
18695
- "cloudflare-ai-gateway": "Cloudflare AI Gateway",
18696
- "cloudflare-workers-ai": "Cloudflare Workers AI",
18697
- deepseek: "DeepSeek",
18698
- fireworks: "Fireworks",
18699
- google: "Google Gemini",
18700
- "google-vertex": "Google Vertex AI",
18701
- groq: "Groq",
18702
- huggingface: "Hugging Face",
18703
- "kimi-coding": "Kimi For Coding",
18704
- mistral: "Mistral",
18705
- minimax: "MiniMax",
18706
- "minimax-cn": "MiniMax (China)",
18707
- moonshotai: "Moonshot AI",
18708
- "moonshotai-cn": "Moonshot AI (China)",
18709
- opencode: "OpenCode Zen",
18710
- "opencode-go": "OpenCode Go",
18711
- openai: "OpenAI",
18712
- "openai-codex": "OpenAI Codex",
18713
- openrouter: "OpenRouter",
18714
- "prime-agent-traces": "Prime Agent Traces",
18715
- "prime-inference": "Prime Inference",
18716
- "vercel-ai-gateway": "Vercel AI Gateway",
18717
- xai: "xAI",
18718
- zai: "ZAI",
18719
- xiaomi: "Xiaomi MiMo",
18720
- "xiaomi-token-plan-cn": "Xiaomi MiMo Token Plan (China)",
18721
- "xiaomi-token-plan-ams": "Xiaomi MiMo Token Plan (Amsterdam)",
18722
- "xiaomi-token-plan-sgp": "Xiaomi MiMo Token Plan (Singapore)",
18723
- "github-copilot": "GitHub Copilot"
18724
- };
18725
18915
  var KNOWN_PROVIDER_BY_ID = new Map(KNOWN_PROVIDERS.map((provider) => [provider.id, provider]));
18726
18916
  var EXTRA_BUILTIN_ENV_HINTS = {
18727
18917
  anthropic: "ANTHROPIC_API_KEY",
18728
18918
  "amazon-bedrock": "AWS_BEARER_TOKEN_BEDROCK"
18729
18919
  };
18920
+ var CREDENTIAL_QUERY_PARAMETER_NAMES = /* @__PURE__ */ new Set([
18921
+ "auth",
18922
+ "authorization",
18923
+ "apikey",
18924
+ "bearer",
18925
+ "credential",
18926
+ "credentials",
18927
+ "key",
18928
+ "pass",
18929
+ "password",
18930
+ "passwd",
18931
+ "secret",
18932
+ "sig",
18933
+ "signature",
18934
+ "token",
18935
+ "jwt",
18936
+ "assertion"
18937
+ ]);
18938
+ function isCredentialQueryParameter(name) {
18939
+ return name.replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase().split(/[^a-z0-9]+/).filter(Boolean).some((token) => CREDENTIAL_QUERY_PARAMETER_NAMES.has(token));
18940
+ }
18730
18941
  function envHintForBuiltin(providerId) {
18731
18942
  return PRIME_PROVIDER_ENV_MAP[providerId] ?? EXTRA_BUILTIN_ENV_HINTS[providerId] ?? "";
18732
18943
  }
@@ -18751,14 +18962,26 @@ var BUILTIN_OAUTH_PROVIDER_IDS = [
18751
18962
  "openai-codex",
18752
18963
  "xai"
18753
18964
  ];
18754
- function isLlmsProviderId(providerId) {
18755
- return providerId !== "prime-agent-traces";
18965
+ function browserProviderBaseUrl(baseUrl) {
18966
+ if (!baseUrl) return void 0;
18967
+ try {
18968
+ const parsed = new URL(baseUrl);
18969
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return void 0;
18970
+ const credentialParameters = [...parsed.searchParams.keys()].filter(isCredentialQueryParameter);
18971
+ if (!parsed.username && !parsed.password && credentialParameters.length === 0) return baseUrl;
18972
+ parsed.username = "";
18973
+ parsed.password = "";
18974
+ for (const name of credentialParameters) parsed.searchParams.delete(name);
18975
+ return parsed.toString();
18976
+ } catch {
18977
+ return;
18978
+ }
18756
18979
  }
18757
18980
  function buildProviders(configuredFromDisk) {
18758
18981
  const config = getPrimeConfig();
18759
18982
  const oauthIds = new Set(BUILTIN_OAUTH_PROVIDER_IDS);
18760
18983
  const custom2 = listCustomProviders(`${config.agentDir}/models.json`);
18761
- const builtinIds = new Set(Object.keys(BUILT_IN_PROVIDER_DISPLAY_NAMES).filter(isLlmsProviderId));
18984
+ const builtinIds = new Set(builtInProviderIds());
18762
18985
  const allIds = /* @__PURE__ */ new Set([...builtinIds, ...Object.keys(custom2)]);
18763
18986
  for (const providerId of configuredFromDisk ?? []) if (!providerId.startsWith("mcp:")) allIds.add(providerId);
18764
18987
  allIds.add(OPENAI_CHAT_COMPLETIONS_PROVIDER_ID);
@@ -18768,7 +18991,7 @@ function buildProviders(configuredFromDisk) {
18768
18991
  const configured = configuredFromDisk?.has(id) ?? false;
18769
18992
  const catalogEntry = KNOWN_PROVIDER_BY_ID.get(id);
18770
18993
  const name = isCustom ? customEntry?.name ?? catalogEntry?.name ?? id : BUILT_IN_PROVIDER_DISPLAY_NAMES[id] ?? catalogEntry?.name ?? id;
18771
- const envVarName = isCustom ? customEntry?.apiKey ?? catalogEntry?.envVarName ?? "" : envHintForBuiltin(id);
18994
+ const envVarName = isCustom ? customEntry?.apiKey === envVarNameForManagedProvider(id) ? customEntry.apiKey : catalogEntry?.envVarName ?? "" : envHintForBuiltin(id);
18772
18995
  const row = {
18773
18996
  id,
18774
18997
  name,
@@ -18776,12 +18999,13 @@ function buildProviders(configuredFromDisk) {
18776
18999
  ...resolveProviderAuthFields(id, envVarName, oauthIds),
18777
19000
  isConfigured: configured
18778
19001
  };
18779
- if (isOccProviderId(id) || isCustomProviderId(id)) {
19002
+ if (customEntry || isOccProviderId(id) || isCustomProviderId(id)) {
18780
19003
  row.providerFamily = isOccProviderId(id) ? OPENAI_CHAT_COMPLETIONS_PROVIDER_ID : "custom";
18781
19004
  if (customEntry?.name && (isNamedOccInstanceId(id) || isCustomProviderId(id))) row.displayName = customEntry.name;
18782
19005
  const customApi = uiApiForCustomProvider(customEntry?.api);
18783
19006
  if (customApi) row.api = customApi;
18784
- if (customEntry?.baseUrl) row.baseUrl = customEntry.baseUrl;
19007
+ const baseUrl = browserProviderBaseUrl(customEntry?.baseUrl);
19008
+ if (baseUrl) row.baseUrl = baseUrl;
18785
19009
  row.modelIds = (customEntry?.models ?? []).map((model) => model.id);
18786
19010
  row.discoverable = isDiscoverableEntry(customEntry);
18787
19011
  }
@@ -18799,6 +19023,7 @@ function requireBaseUrl(baseUrl, providerId) {
18799
19023
  if (!trimmed) badRequest(`baseUrl is required for ${providerId}`);
18800
19024
  try {
18801
19025
  const parsed = new URL(trimmed);
19026
+ if (parsed.username || parsed.password || [...parsed.searchParams.keys()].some(isCredentialQueryParameter)) badRequest("baseUrl must not contain credentials; use the API key field instead");
18802
19027
  if (parsed.protocol !== "https:" && parsed.protocol !== "http:") badRequest(`baseUrl must be an http(s) URL for ${providerId}`);
18803
19028
  } catch (error) {
18804
19029
  if (error && typeof error === "object" && "status" in error) throw error;
@@ -18828,7 +19053,7 @@ function handleChatProvidersPost(request) {
18828
19053
  if (isNewCustom || isNewOccInstance) {
18829
19054
  const displayName2 = body.displayName?.trim() ?? "";
18830
19055
  if (!displayName2) badRequest("displayName is required when creating a new provider instance");
18831
- const existingIds = /* @__PURE__ */ new Set([...Object.keys(BUILT_IN_PROVIDER_DISPLAY_NAMES).filter(isLlmsProviderId), ...Object.keys(listCustomProviders(modelsJsonPath))]);
19056
+ const existingIds = /* @__PURE__ */ new Set([...builtInProviderIds(), ...Object.keys(listCustomProviders(modelsJsonPath))]);
18832
19057
  providerId = allocateProviderId(toInstanceSlug(displayName2), existingIds, isNewCustom ? toCustomProviderId : toOccInstanceId);
18833
19058
  }
18834
19059
  const existingEntry = listCustomProviders(modelsJsonPath)[providerId];
@@ -18877,7 +19102,7 @@ function handleChatProvidersOAuthPost(request) {
18877
19102
  ChatProviderOAuthLoginRequestSchema.parse(await request.json().catch(() => ({})));
18878
19103
  return Response.json({
18879
19104
  status: "error",
18880
- error: "Provider OAuth sign-in is unavailable in Fleet settings. Launch Prime Agent in a terminal using the same configuration directory as Fleet and complete provider sign-in there. Restart Fleet afterward to reload the credentials."
19105
+ error: "Sign-in has to run inside Prime Agent. In a terminal, start Prime Agent against the same account as Fleet, then run /login and choose the provider. Fleet reads the stored credential on every request. If Fleet uses a non-default account directory, start Prime Agent with the same PRIME_AGENT_CODING_AGENT_DIR it uses."
18881
19106
  }, { status: 501 });
18882
19107
  }, request);
18883
19108
  }
@@ -19465,6 +19690,7 @@ function handleChatSettingsPatch(request) {
19465
19690
  code: "UNWRITABLE",
19466
19691
  message: `Fleet could not update ${safePathLabel(path)}. ${outcome.reason} Change these settings in the Prime Agent terminal interface instead.`
19467
19692
  }, { status: 409 });
19693
+ getPrimeConfig().invalidateSettings();
19468
19694
  if (outcome.impact.resourceReloadRequired) await getBridge().reloadResources().catch(() => void 0);
19469
19695
  const response = await buildResponse(cwd);
19470
19696
  return Response.json({