@qredence/fleet 0.9.0 → 0.10.0

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 (48) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/dist/web/client/assets/{agents-CGjUBYX7.js → agents-BMQXA18L.js} +2 -2
  3. package/dist/web/client/assets/{agents-page-CTuFJL9H.js → agents-page-D2soW46O.js} +1 -1
  4. package/dist/web/client/assets/{chat-queries-CfZiupub.js → chat-queries-ty76G6rK.js} +1 -1
  5. package/dist/web/client/assets/{connectors-CFxLqG2x.js → connectors-E4KW6hBL.js} +2 -2
  6. package/dist/web/client/assets/{connectors-page-DQJdoAnl.js → connectors-page-CDzzjCIJ.js} +1 -1
  7. package/dist/web/client/assets/globals-DDDleTbS.css +2 -0
  8. package/dist/web/client/assets/{index-C9FHZDGw.js → index-Cf8tRpHL.js} +2 -2
  9. package/dist/web/client/assets/{mcp-connections-section-DEo6Kemv.js → mcp-connections-section-BsNhp1b2.js} +2 -2
  10. package/dist/web/client/assets/routes--tnF7d6H.js +14 -0
  11. package/dist/web/client/assets/settings-dialog-BomLOETY.js +3 -0
  12. package/dist/web/client/bundle-budget.json +5 -5
  13. package/dist/web/server/{_tanstack-start-manifest_v-B6ne-00G-OFX7UD4K.js → _tanstack-start-manifest_v-Io433TLr-YWPNQWQI.js} +7 -7
  14. package/dist/web/server/{agents-BwzqhPjK-ANFHE5AG.js → agents-DOgCHixY-KUZP2VDO.js} +2 -2
  15. package/dist/web/server/{agents-page-DEqPV-eG-BR5MR74O.js → agents-page-BdiR9H8w-M6X6SXMF.js} +3 -3
  16. package/dist/web/server/{artifacts-panel-DLO8OfXb-B2ZWCDFP.js → artifacts-panel-cYqS54cM-K6SF3K6W.js} +5 -5
  17. package/dist/web/server/{chat-command-palette-CaDQAMAu-KDH7BTDS.js → chat-command-palette-BDnwcymZ-DSTFIAOD.js} +5 -5
  18. package/dist/web/server/{chunk-HUHUPGHT.js → chunk-2OC2FKHR.js} +5 -5
  19. package/dist/web/server/{chunk-V2GCVTDP.js → chunk-37EK2TYH.js} +33 -48
  20. package/dist/web/server/{chunk-TOJA3X5N.js → chunk-BLGKLQJG.js} +16 -5
  21. package/dist/web/server/{chunk-TQ2RECTH.js → chunk-C5XHGLET.js} +1 -1
  22. package/dist/web/server/{chunk-B5FKCW75.js → chunk-FI3UF2GW.js} +4 -4
  23. package/dist/web/server/{chunk-UDUG3MYZ.js → chunk-NSFKPEFI.js} +2 -2
  24. package/dist/web/server/{chunk-KDCSEISZ.js → chunk-NZCEQWK2.js} +2 -1
  25. package/dist/web/server/{chunk-VM3U6PUU.js → chunk-QOZCYKMA.js} +2 -2
  26. package/dist/web/server/{chunk-6AFVCERK.js → chunk-RHJEOG37.js} +4 -4
  27. package/dist/web/server/{chunk-TTNONWZZ.js → chunk-W2GI7NRX.js} +893 -672
  28. package/dist/web/server/{chunk-5IE54MRG.js → chunk-W7GGECTL.js} +3 -3
  29. package/dist/web/server/{connectors-C0xm91Ss-DJVBGD73.js → connectors-Dv_uPdv0-2X5VWQXK.js} +2 -2
  30. package/dist/web/server/{connectors-page-B6tJNZ6c-R5K4NSAT.js → connectors-page-DW75T-uH-EKRHUZ6K.js} +4 -4
  31. package/dist/web/server/{create-project-dialog-PFPHHWIV-GH7JR5IK.js → create-project-dialog-BS5Sva46-BO4RLW2Q.js} +5 -5
  32. package/dist/web/server/{openui-renderer-B9D8-Av--LFZP4U5V.js → openui-renderer-Q31y65CQ-QNVSP4NN.js} +4 -4
  33. package/dist/web/server/{resources-panel-CTzOOKPz-5G6RS2D6.js → resources-panel-CH8sG0Od-4BZN6SLY.js} +6 -6
  34. package/dist/web/server/{router-CnBux_Ld-YTVBMJFL.js → router-8EzTM9Eq-COVGJKKX.js} +2 -2
  35. package/dist/web/server/{routes-BUGzvQoO-WW43X7W2.js → routes-88H0fiN3-WIAZJ3RT.js} +4 -4
  36. package/dist/web/server/server.js +2 -2
  37. package/dist/web/server/{session-activity-panel-f0d3fCm7-I6L5IKFQ.js → session-activity-panel-CgSOde03-B5RCAGB4.js} +6 -6
  38. package/dist/web/server/{session-reasoning-panel-QIt_xjP0-FKJWMNHO.js → session-reasoning-panel-Dmny6YTk-D3HQV2YB.js} +5 -5
  39. package/dist/web/server/{session-tool-renderer-Cg_ZPHxS-BNQKNJNZ.js → session-tool-renderer-BrnTO2w3-V6EE6AB2.js} +5 -5
  40. package/dist/web/server/{session-tree-panel-B--el8O5-NZOZJM5L.js → session-tree-panel-BEqR_cAJ-TZS7VRCB.js} +5 -5
  41. package/dist/web/server/{settings-dialog-D_eJN2ia-W47Y474K.js → settings-dialog-DkesVBrS-BAAA3YNU.js} +145 -66
  42. package/dist/web/server/{subagent-chat-panel-C9nWP21E-D5C2TTYD.js → subagent-chat-panel-Bfmt1bCE-DKDVH3NJ.js} +8 -8
  43. package/dist/web/server/{subagents-panel-fAKvBNRG-O7QHXNE2.js → subagents-panel-ChJDDvad-GNCHDTPB.js} +8 -8
  44. package/dist/web/server/{workspace-panel-CZMNMU63-SYOSM3B7.js → workspace-panel-Cij_JylD-WPYSV5FA.js} +6 -6
  45. package/package.json +1 -1
  46. package/dist/web/client/assets/globals-DJhWyayo.css +0 -2
  47. package/dist/web/client/assets/routes-ja_liP8Z.js +0 -14
  48. 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-8EzTM9Eq.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-88H0fiN3-WIAZJ3RT.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 = {
@@ -4615,7 +4658,8 @@ async function ensurePrimeAgentRuntime(options = {}) {
4615
4658
  if (!asset) throw new Error(`No pinned Prime Agent runtime archive for ${selectedTarget}`);
4616
4659
  const installRoot = options.installRoot ?? join(options.home ?? homedir(), ".prime", "fleet", "runtimes", runtime.version, selectedTarget);
4617
4660
  const binary = join(installRoot, "prime-agent");
4618
- if (await executableMatches(binary, asset.executableSha256)) return binary;
4661
+ const packagedRuntime = join(installRoot, "prime-agent-runtime");
4662
+ if (await executableMatches(binary, asset.executableSha256) && await directoryExists(packagedRuntime)) return binary;
4619
4663
  await mkdir(dirname(installRoot), {
4620
4664
  recursive: true,
4621
4665
  mode: 448
@@ -4644,6 +4688,16 @@ async function ensurePrimeAgentRuntime(options = {}) {
4644
4688
  recursive: true,
4645
4689
  mode: 448
4646
4690
  });
4691
+ const extractedRuntime = join(extractRoot, "prime-agent-runtime");
4692
+ if (await directoryExists(extractedRuntime)) {
4693
+ await rm(packagedRuntime, {
4694
+ recursive: true,
4695
+ force: true
4696
+ });
4697
+ await rename(extractedRuntime, packagedRuntime).catch((error) => {
4698
+ if (error.code !== "ENOTEMPTY" && error.code !== "EEXIST") throw error;
4699
+ });
4700
+ }
4647
4701
  await rename(extractedBinary, binary);
4648
4702
  return binary;
4649
4703
  } finally {
@@ -4653,6 +4707,13 @@ async function ensurePrimeAgentRuntime(options = {}) {
4653
4707
  });
4654
4708
  }
4655
4709
  }
4710
+ async function directoryExists(path) {
4711
+ try {
4712
+ return (await stat(path)).isDirectory();
4713
+ } catch {
4714
+ return false;
4715
+ }
4716
+ }
4656
4717
  async function executableMatches(path, expected) {
4657
4718
  let file2;
4658
4719
  try {
@@ -4750,6 +4811,10 @@ async function probeDaemon(socketPath) {
4750
4811
  client.close();
4751
4812
  }
4752
4813
  }
4814
+ function describeDaemonSpawnError(error, command, options) {
4815
+ if (error.code !== "ENOENT") return `Could not start Prime Agent ${options.noun}: ${error.code ?? error.message}`;
4816
+ return `Could not start Prime Agent ${options.noun}: ${command} was not found, or its working directory ${options.cwd} does not exist.`;
4817
+ }
4753
4818
  async function startDaemon(socketPath, spawnCwd) {
4754
4819
  const initialProbe = await probeDaemon(socketPath);
4755
4820
  if (initialProbe.status === "compatible") return;
@@ -4773,10 +4838,16 @@ async function startDaemon(socketPath, spawnCwd) {
4773
4838
  const deadline = Date.now() + DAEMON_STARTUP_TIMEOUT_MS;
4774
4839
  while (Date.now() < deadline) {
4775
4840
  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}`);
4841
+ if (spawnError) throw new Error(describeDaemonSpawnError(spawnError, command, {
4842
+ noun: "binary",
4843
+ cwd: spawnCwd
4844
+ }));
4777
4845
  const probe = await probeDaemon(socketPath);
4778
4846
  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}`);
4847
+ if (spawnError) throw new Error(describeDaemonSpawnError(spawnError, command, {
4848
+ noun: "daemon",
4849
+ cwd: spawnCwd
4850
+ }));
4780
4851
  if (probe.status === "compatible") return;
4781
4852
  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
4853
  await delay(DAEMON_POLL_DELAY_MS);
@@ -4916,15 +4987,15 @@ async function withTransientDaemonSession(cwd, work) {
4916
4987
  }
4917
4988
  var modelCatalogListeners = /* @__PURE__ */ new Set();
4918
4989
  function notifyDaemonModelCatalogChanged() {
4919
- try {
4920
- getPrimeConfig().modelRegistry.refresh();
4921
- } catch {
4922
- }
4990
+ invalidateModelCatalog();
4923
4991
  for (const listener of [...modelCatalogListeners]) try {
4924
4992
  listener();
4925
4993
  } catch {
4926
4994
  }
4927
4995
  }
4996
+ function readDaemonModelCatalog(sessionCwd) {
4997
+ return loadModelCatalog(() => withTransientDaemonSession(sessionCwd, (client, activeSessionId) => client.getModelCatalog(activeSessionId)));
4998
+ }
4928
4999
  function applySessionBinding(route, frame) {
4929
5000
  if (!isDaemonSessionBindingFrame(frame)) return void 0;
4930
5001
  if (frame.previousActiveSessionId !== route.activeSessionId && frame.activeSessionId !== route.activeSessionId) return;
@@ -6974,156 +7045,689 @@ async function copyManagedPlanPresentationsForFork(source, forked, forkedMessage
6974
7045
  if (copied.length === 0) return;
6975
7046
  await writePresentationsAtomic(forked, copied);
6976
7047
  }
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
- }));
7048
+ var LOCK_TIMEOUT_MS = 5e3;
7049
+ var LOCK_RETRY_MS = 25;
7050
+ function isJsonObject$2(value) {
7051
+ return typeof value === "object" && value !== null && !Array.isArray(value);
7052
+ }
7053
+ function errorCode(error) {
7054
+ return isJsonObject$2(error) && typeof error.code === "string" ? error.code : void 0;
7055
+ }
7056
+ async function readJsonObjectFile(path, parse = JSON.parse) {
7057
+ let raw;
7058
+ try {
7059
+ raw = await readFile(path, "utf8");
7060
+ } catch (error) {
7061
+ if (errorCode(error) === "ENOENT") return { kind: "missing" };
7062
+ return {
7063
+ kind: "invalid",
7064
+ error: error instanceof Error ? error.message : "unreadable"
7065
+ };
7006
7066
  }
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
- });
7067
+ try {
7068
+ const parsed = parse(raw);
7069
+ if (!isJsonObject$2(parsed)) return {
7070
+ kind: "invalid",
7071
+ error: "expected a JSON object"
7072
+ };
7073
+ return {
7074
+ kind: "ok",
7075
+ value: parsed
7076
+ };
7077
+ } catch (error) {
7078
+ return {
7079
+ kind: "invalid",
7080
+ error: error instanceof Error ? error.message : "invalid JSON"
7081
+ };
7044
7082
  }
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);
7083
+ }
7084
+ function isProcessAlive(pid) {
7085
+ if (!Number.isInteger(pid) || pid <= 0) return false;
7086
+ try {
7087
+ process.kill(pid, 0);
7056
7088
  return true;
7089
+ } catch (error) {
7090
+ return errorCode(error) === "EPERM";
7057
7091
  }
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
- }
7092
+ }
7093
+ async function acquireLock(lockPath) {
7094
+ const deadline = Date.now() + LOCK_TIMEOUT_MS;
7095
+ await mkdir(dirname(lockPath), { recursive: true });
7096
+ for (; ; ) try {
7097
+ const descriptor = openSync(lockPath, "wx", 384);
7098
+ writeFileSync(descriptor, String(process.pid), "utf8");
7099
+ closeSync(descriptor);
7100
+ return { release: () => {
7101
+ rmSync(lockPath, { force: true });
7102
+ } };
7103
+ } catch (error) {
7104
+ if (errorCode(error) !== "EEXIST") throw error;
7105
+ let raw;
7106
+ try {
7107
+ const reader = openSync(lockPath, "r");
7108
+ try {
7109
+ raw = readFileSync(reader, "utf8");
7110
+ } finally {
7111
+ closeSync(reader);
7078
7112
  }
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);
7113
+ } catch (readError) {
7114
+ if (errorCode(readError) === "ENOENT") continue;
7115
+ throw readError;
7090
7116
  }
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);
7117
+ const holder = Number.parseInt(raw, 10);
7118
+ if (!isProcessAlive(holder)) {
7119
+ rmSync(lockPath, { force: true });
7120
+ continue;
7098
7121
  }
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);
7122
+ if (Date.now() >= deadline) throw new Error(`Timed out waiting for another Fleet write (held by process ${Number.isNaN(holder) ? "unknown" : holder}).`);
7123
+ await new Promise((resolve2) => setTimeout(resolve2, LOCK_RETRY_MS));
7109
7124
  }
7110
- get streamGeneration() {
7111
- return this.generation;
7125
+ }
7126
+ function currentMode(path) {
7127
+ try {
7128
+ return statSync(path).mode & 511;
7129
+ } catch {
7130
+ return;
7112
7131
  }
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;
7132
+ }
7133
+ async function writeJsonObjectFile(path, value, options = {}) {
7134
+ await mkdir(dirname(path), { recursive: true });
7135
+ const temporary = `${path}.${process.pid}.${randomUUID()}.tmp`;
7136
+ const mode = currentMode(path) ?? options.mode ?? 420;
7137
+ try {
7138
+ await writeFile(temporary, `${JSON.stringify(value, null, " ")}
7139
+ `, {
7140
+ encoding: "utf8",
7141
+ mode
7142
+ });
7143
+ await chmod(temporary, mode);
7144
+ await rename(temporary, path);
7145
+ } catch (error) {
7146
+ await rm(temporary, { force: true });
7147
+ throw error;
7122
7148
  }
7123
- /**
7124
- * Replay entries with seq > `lastEventId`. If `lastEventId` is behind the
7125
- * oldest retained entry or ahead of the current sequence (for example after a
7126
- * process restart), return `{ replayed, overflowed: true }` so the caller
7149
+ }
7150
+ var UnwritablePathError = class extends Error {
7151
+ path;
7152
+ cause;
7153
+ constructor(path, cause) {
7154
+ const detail = cause instanceof Error ? cause.message : String(cause);
7155
+ super(`${path}: ${detail}`);
7156
+ this.path = path;
7157
+ this.cause = cause;
7158
+ this.name = "UnwritablePathError";
7159
+ }
7160
+ };
7161
+ async function updateJsonObjectFile(path, mutate, options = {}) {
7162
+ const lock = await acquireLock(`${path}.lock`).catch((error) => {
7163
+ throw new UnwritablePathError(path, error);
7164
+ });
7165
+ try {
7166
+ const current = await readJsonObjectFile(path, options.parse);
7167
+ if (current.kind === "invalid") return {
7168
+ status: "refused",
7169
+ reason: `${path} is not a readable JSON object: ${current.error}`
7170
+ };
7171
+ const outcome = mutate(current.kind === "ok" ? current.value : {});
7172
+ if ("refuse" in outcome) return {
7173
+ status: "refused",
7174
+ reason: outcome.refuse
7175
+ };
7176
+ if ("unchanged" in outcome) return {
7177
+ status: "unchanged",
7178
+ value: outcome.unchanged
7179
+ };
7180
+ try {
7181
+ await writeJsonObjectFile(path, outcome.next, { mode: options.mode });
7182
+ } catch (error) {
7183
+ throw new UnwritablePathError(path, error);
7184
+ }
7185
+ return {
7186
+ status: "written",
7187
+ value: outcome.result
7188
+ };
7189
+ } finally {
7190
+ lock.release();
7191
+ }
7192
+ }
7193
+ var AUTH_FILE = "auth.json";
7194
+ function isJsonObject$1(value) {
7195
+ return typeof value === "object" && value !== null && !Array.isArray(value);
7196
+ }
7197
+ function authFilePath(agentDir) {
7198
+ return join(agentDir, AUTH_FILE);
7199
+ }
7200
+ function isWritableProviderId(providerId) {
7201
+ return providerId.length > 0 && providerId.length <= 128 && !/[\u0000-\u001f/\\]/.test(providerId);
7202
+ }
7203
+ async function readProviderApiKey(agentDir, providerId) {
7204
+ const current = await readJsonObjectFile(authFilePath(agentDir));
7205
+ if (current.kind !== "ok") return void 0;
7206
+ const entry = current.value[providerId];
7207
+ if (!isJsonObject$1(entry) || entry.type !== "api_key") return void 0;
7208
+ const key = entry.key;
7209
+ return typeof key === "string" ? key : void 0;
7210
+ }
7211
+ async function setProviderApiKey(agentDir, providerId, key) {
7212
+ if (!isWritableProviderId(providerId)) return {
7213
+ status: "refused",
7214
+ reason: `"${providerId}" is not a valid provider id.`
7215
+ };
7216
+ const trimmed = key.trim();
7217
+ if (trimmed.length === 0) return {
7218
+ status: "refused",
7219
+ reason: "The API key is empty."
7220
+ };
7221
+ const outcome = await updateJsonObjectFile(authFilePath(agentDir), (current) => {
7222
+ const existing = current[providerId];
7223
+ const next = {
7224
+ ...isJsonObject$1(existing) ? existing : {},
7225
+ type: "api_key",
7226
+ key: trimmed
7227
+ };
7228
+ return {
7229
+ next: {
7230
+ ...current,
7231
+ [providerId]: next
7232
+ },
7233
+ result: true
7234
+ };
7235
+ }, { mode: 384 });
7236
+ return outcome.status === "refused" ? outcome : { status: "written" };
7237
+ }
7238
+ async function configuredProviderIds(agentDir) {
7239
+ const current = await readJsonObjectFile(authFilePath(agentDir));
7240
+ if (current.kind !== "ok") return /* @__PURE__ */ new Set();
7241
+ return new Set(Object.entries(current.value).filter(([, value]) => isJsonObject$1(value)).map(([providerId]) => providerId));
7242
+ }
7243
+ async function removeProviderCredential(agentDir, providerId) {
7244
+ const outcome = await updateJsonObjectFile(authFilePath(agentDir), (current) => {
7245
+ if (!(providerId in current)) return {
7246
+ next: current,
7247
+ result: "absent"
7248
+ };
7249
+ const next = {};
7250
+ for (const [currentId, value] of Object.entries(current)) if (currentId !== providerId) next[currentId] = value;
7251
+ return {
7252
+ next,
7253
+ result: "removed"
7254
+ };
7255
+ }, { mode: 384 });
7256
+ return outcome.status === "refused" ? outcome : { status: "written" };
7257
+ }
7258
+ var BUILT_IN_PROVIDER_DISPLAY_NAMES = {
7259
+ anthropic: "Anthropic",
7260
+ "amazon-bedrock": "Amazon Bedrock",
7261
+ "azure-openai-responses": "Azure OpenAI Responses",
7262
+ cerebras: "Cerebras",
7263
+ "cloudflare-ai-gateway": "Cloudflare AI Gateway",
7264
+ "cloudflare-workers-ai": "Cloudflare Workers AI",
7265
+ deepseek: "DeepSeek",
7266
+ fireworks: "Fireworks",
7267
+ google: "Google Gemini",
7268
+ "google-vertex": "Google Vertex AI",
7269
+ groq: "Groq",
7270
+ huggingface: "Hugging Face",
7271
+ "kimi-coding": "Kimi For Coding",
7272
+ mistral: "Mistral",
7273
+ minimax: "MiniMax",
7274
+ "minimax-cn": "MiniMax (China)",
7275
+ moonshotai: "Moonshot AI",
7276
+ "moonshotai-cn": "Moonshot AI (China)",
7277
+ opencode: "OpenCode Zen",
7278
+ "opencode-go": "OpenCode Go",
7279
+ openai: "OpenAI",
7280
+ "openai-codex": "OpenAI Codex",
7281
+ openrouter: "OpenRouter",
7282
+ "prime-agent-traces": "Prime Agent Traces",
7283
+ "prime-inference": "Prime Inference",
7284
+ "vercel-ai-gateway": "Vercel AI Gateway",
7285
+ xai: "xAI",
7286
+ zai: "ZAI",
7287
+ xiaomi: "Xiaomi MiMo",
7288
+ "xiaomi-token-plan-cn": "Xiaomi MiMo Token Plan (China)",
7289
+ "xiaomi-token-plan-ams": "Xiaomi MiMo Token Plan (Amsterdam)",
7290
+ "xiaomi-token-plan-sgp": "Xiaomi MiMo Token Plan (Singapore)",
7291
+ "github-copilot": "GitHub Copilot"
7292
+ };
7293
+ function isLlmsProviderId(providerId) {
7294
+ return providerId !== "prime-agent-traces";
7295
+ }
7296
+ function builtInProviderIds() {
7297
+ return Object.keys(BUILT_IN_PROVIDER_DISPLAY_NAMES).filter(isLlmsProviderId);
7298
+ }
7299
+ var BUILT_IN_PROVIDER_ID_SET = new Set(builtInProviderIds());
7300
+ function isBuiltInProviderId(providerId) {
7301
+ return BUILT_IN_PROVIDER_ID_SET.has(providerId);
7302
+ }
7303
+ function storedApiForCustomProvider(api) {
7304
+ return api === "google-genai" ? "google-generative-ai" : api;
7305
+ }
7306
+ function uiApiForCustomProvider(value) {
7307
+ if (value === "google-generative-ai") return "google-genai";
7308
+ return isPiCustomProviderApi(value) ? value : void 0;
7309
+ }
7310
+ function envVarNameForManagedProvider(providerId) {
7311
+ if (providerId === "openai-chat-completions") return "OPENAI_CHAT_COMPLETIONS_API_KEY";
7312
+ const slug = providerId.slice(providerId.indexOf("+") + 1).toUpperCase().replace(/[^A-Z0-9]+/g, "_");
7313
+ return isOccProviderId(providerId) ? `OPENAI_CHAT_COMPLETIONS_${slug}_API_KEY` : `CUSTOM_${slug}_API_KEY`;
7314
+ }
7315
+ function readModelsJson(filePath) {
7316
+ try {
7317
+ if (!existsSync(filePath)) return {};
7318
+ return parseModelsJson(readFileSync(filePath, "utf-8"));
7319
+ } catch {
7320
+ return {};
7321
+ }
7322
+ }
7323
+ function stripJsonComments(input) {
7324
+ return input.replace(/"(?:\\.|[^"\\])*"|\/\/[^\n]*/g, (match) => match[0] === '"' ? match : "").replace(/"(?:\\.|[^"\\])*"|,(\s*[}\]])/g, (match, tail) => tail ?? (match[0] === '"' ? match : ""));
7325
+ }
7326
+ function parseModelsJson(raw) {
7327
+ const parsed = JSON.parse(stripJsonComments(raw));
7328
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("models.json must contain an object");
7329
+ return parsed;
7330
+ }
7331
+ var OPENAI_COMPLETIONS_TOOL_REASONING_NONE_PATTERN = /^(?:[^/]+\/)?gpt-(?:5[.-]6|6)(?:[.-]|$)/i;
7332
+ var OPENAI_COMPLETIONS_REASONING_OFF_MAP = {
7333
+ off: "none",
7334
+ minimal: null,
7335
+ low: null,
7336
+ medium: null,
7337
+ high: null,
7338
+ xhigh: null,
7339
+ max: null
7340
+ };
7341
+ function applyModelDefaults(model, storedApi) {
7342
+ if (storedApi === "openai-completions" && OPENAI_COMPLETIONS_TOOL_REASONING_NONE_PATTERN.test(model.id) && model.reasoning === void 0 && model.thinkingLevelMap === void 0) return {
7343
+ ...model,
7344
+ reasoning: true,
7345
+ thinkingLevelMap: OPENAI_COMPLETIONS_REASONING_OFF_MAP
7346
+ };
7347
+ return model;
7348
+ }
7349
+ function mergeModelDefinitions(existing, incoming, storedApi) {
7350
+ const existingById = new Map((existing ?? []).map((model) => [model.id, model]));
7351
+ const seen = /* @__PURE__ */ new Set();
7352
+ return incoming.flatMap((model) => {
7353
+ if (seen.has(model.id)) return [];
7354
+ seen.add(model.id);
7355
+ return [applyModelDefaults({
7356
+ ...existingById.get(model.id),
7357
+ ...model
7358
+ }, storedApi)];
7359
+ });
7360
+ }
7361
+ function listCustomProviders(filePath) {
7362
+ return readModelsJson(filePath).providers ?? {};
7363
+ }
7364
+ function providersMissingApiKey(filePath) {
7365
+ return Object.entries(listCustomProviders(filePath)).filter(([providerId, entry]) => {
7366
+ if (isBuiltInProviderId(providerId)) return false;
7367
+ return typeof entry.apiKey !== "string" || entry.apiKey.length === 0;
7368
+ }).map(([providerId]) => providerId);
7369
+ }
7370
+ async function updateModelsJson(filePath, mutate) {
7371
+ const outcome = await updateJsonObjectFile(filePath, (current) => {
7372
+ const next = mutate(current);
7373
+ return next ? {
7374
+ next,
7375
+ result: void 0
7376
+ } : { unchanged: void 0 };
7377
+ }, {
7378
+ mode: 420,
7379
+ parse: parseModelsJson
7380
+ });
7381
+ if (outcome.status === "refused") throw new Error(`Refusing to update models.json because it could not be parsed: ${outcome.reason}`);
7382
+ }
7383
+ function defaultCompatForCustomProvider(storedApi, baseUrl, existingCompat) {
7384
+ const existingRecord = existingCompat && typeof existingCompat === "object" && !Array.isArray(existingCompat) ? existingCompat : void 0;
7385
+ if (storedApi !== "openai-completions") return existingRecord;
7386
+ try {
7387
+ if (new URL(baseUrl).hostname === "api.openai.com") return existingRecord;
7388
+ } catch {
7389
+ }
7390
+ return {
7391
+ supportsStore: false,
7392
+ ...existingRecord
7393
+ };
7394
+ }
7395
+ async function upsertCustomProvider(filePath, providerId, input) {
7396
+ await updateModelsJson(filePath, (data) => {
7397
+ const providers = { ...data.providers ?? {} };
7398
+ const existing = providers[providerId] ?? {};
7399
+ const storedApi = storedApiForCustomProvider(input.api);
7400
+ const compat = defaultCompatForCustomProvider(storedApi, input.baseUrl, existing.compat);
7401
+ providers[providerId] = {
7402
+ ...existing,
7403
+ name: input.name ?? existing.name,
7404
+ baseUrl: input.baseUrl,
7405
+ api: storedApi,
7406
+ apiKey: input.apiKey,
7407
+ ...compat ? { compat } : {},
7408
+ models: mergeModelDefinitions(existing.models, input.models, storedApi)
7409
+ };
7410
+ return {
7411
+ ...data,
7412
+ providers
7413
+ };
7414
+ });
7415
+ }
7416
+ async function addCustomProviderModelIds(filePath, providerId, modelIds) {
7417
+ await updateModelsJson(filePath, (data) => {
7418
+ const providers = { ...data.providers ?? {} };
7419
+ const existing = providers[providerId];
7420
+ if (!existing) return;
7421
+ const compat = typeof existing.api === "string" && typeof existing.baseUrl === "string" ? defaultCompatForCustomProvider(existing.api, existing.baseUrl, existing.compat) : void 0;
7422
+ providers[providerId] = {
7423
+ ...existing,
7424
+ ...compat ? { compat } : {},
7425
+ models: mergeModelDefinitions(existing.models, [...existing.models ?? [], ...modelIds.map((id) => ({ id }))], existing.api)
7426
+ };
7427
+ return {
7428
+ ...data,
7429
+ providers
7430
+ };
7431
+ });
7432
+ }
7433
+ async function removeCustomProvider(filePath, providerId) {
7434
+ await updateModelsJson(filePath, (data) => {
7435
+ const providers = data.providers;
7436
+ if (!providers || !(providerId in providers)) return;
7437
+ const next = { ...providers };
7438
+ delete next[providerId];
7439
+ return {
7440
+ ...data,
7441
+ providers: next
7442
+ };
7443
+ });
7444
+ }
7445
+ function isDiscoverableEntry(entry) {
7446
+ const baseUrl = entry?.baseUrl;
7447
+ if (!baseUrl) return false;
7448
+ const api = uiApiForCustomProvider(entry?.api);
7449
+ if (api && !isOccFamilyApi(api)) return false;
7450
+ if (entry?.api && !api) return false;
7451
+ try {
7452
+ const parsed = new URL(baseUrl);
7453
+ return parsed.protocol === "https:" || parsed.protocol === "http:";
7454
+ } catch {
7455
+ return false;
7456
+ }
7457
+ }
7458
+ var PRIME_PROVIDER_ENV_MAP = {
7459
+ openai: "OPENAI_API_KEY",
7460
+ "azure-openai-responses": "AZURE_OPENAI_API_KEY",
7461
+ "prime-inference": "PRIME_API_KEY",
7462
+ deepseek: "DEEPSEEK_API_KEY",
7463
+ google: "GEMINI_API_KEY",
7464
+ "google-vertex": "GOOGLE_CLOUD_API_KEY",
7465
+ groq: "GROQ_API_KEY",
7466
+ cerebras: "CEREBRAS_API_KEY",
7467
+ xai: "XAI_API_KEY",
7468
+ openrouter: "OPENROUTER_API_KEY",
7469
+ "vercel-ai-gateway": "AI_GATEWAY_API_KEY",
7470
+ zai: "ZAI_API_KEY",
7471
+ mistral: "MISTRAL_API_KEY",
7472
+ minimax: "MINIMAX_API_KEY",
7473
+ "minimax-cn": "MINIMAX_CN_API_KEY",
7474
+ moonshotai: "MOONSHOT_API_KEY",
7475
+ "moonshotai-cn": "MOONSHOT_API_KEY",
7476
+ huggingface: "HF_TOKEN",
7477
+ fireworks: "FIREWORKS_API_KEY",
7478
+ opencode: "OPENCODE_API_KEY",
7479
+ "opencode-go": "OPENCODE_API_KEY",
7480
+ "kimi-coding": "KIMI_API_KEY",
7481
+ "cloudflare-workers-ai": "CLOUDFLARE_API_KEY",
7482
+ "cloudflare-ai-gateway": "CLOUDFLARE_API_KEY",
7483
+ xiaomi: "XIAOMI_API_KEY",
7484
+ "xiaomi-token-plan-cn": "XIAOMI_TOKEN_PLAN_CN_API_KEY",
7485
+ "xiaomi-token-plan-ams": "XIAOMI_TOKEN_PLAN_AMS_API_KEY",
7486
+ "xiaomi-token-plan-sgp": "XIAOMI_TOKEN_PLAN_SGP_API_KEY"
7487
+ };
7488
+ var MULTI_ENV_PROVIDER_MAP = {
7489
+ anthropic: ["ANTHROPIC_OAUTH_TOKEN", "ANTHROPIC_API_KEY"],
7490
+ "github-copilot": [
7491
+ "COPILOT_GITHUB_TOKEN",
7492
+ "GH_TOKEN",
7493
+ "GITHUB_TOKEN"
7494
+ ]
7495
+ };
7496
+ function providerEnvVarCandidates(providerId) {
7497
+ const multi = MULTI_ENV_PROVIDER_MAP[providerId];
7498
+ if (multi) return multi;
7499
+ const single = PRIME_PROVIDER_ENV_MAP[providerId];
7500
+ return single ? [single] : [];
7501
+ }
7502
+ var THINKING_LEVELS = [
7503
+ "off",
7504
+ "minimal",
7505
+ "low",
7506
+ "medium",
7507
+ "high",
7508
+ "xhigh",
7509
+ "max"
7510
+ ];
7511
+ var EXPLICIT_ONLY_LEVELS = /* @__PURE__ */ new Set(["xhigh", "max"]);
7512
+ function supportedThinkingLevels(model) {
7513
+ if (!model.reasoning) return ["off"];
7514
+ const map = model.thinkingLevelMap;
7515
+ return THINKING_LEVELS.filter((level) => {
7516
+ const mapped = map?.[level];
7517
+ if (mapped === null) return false;
7518
+ if (EXPLICIT_ONLY_LEVELS.has(level)) return mapped !== void 0;
7519
+ return true;
7520
+ });
7521
+ }
7522
+ var ENV_VAR_NAME_PATTERN = /^[A-Z][A-Z0-9_]*$/;
7523
+ function declaredApiKeyResolves(declared, env) {
7524
+ if (typeof declared !== "string" || declared.length === 0) return false;
7525
+ if (declared.startsWith("!")) return true;
7526
+ if (!ENV_VAR_NAME_PATTERN.test(declared)) return true;
7527
+ return (env[declared] ?? "").length > 0;
7528
+ }
7529
+ function hasAmbientCredential(provider, env) {
7530
+ if (provider === "google-vertex") {
7531
+ const hasProject = Boolean(env.GOOGLE_CLOUD_PROJECT || env.GCLOUD_PROJECT);
7532
+ const hasLocation = Boolean(env.GOOGLE_CLOUD_LOCATION);
7533
+ if (!hasProject || !hasLocation) return false;
7534
+ const gacPath = env.GOOGLE_APPLICATION_CREDENTIALS;
7535
+ return existsSync(gacPath || join(homedir(), ".config", "gcloud", "application_default_credentials.json"));
7536
+ }
7537
+ 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);
7538
+ return false;
7539
+ }
7540
+ function hasEnvironmentCredential(provider, env = process.env) {
7541
+ for (const envVar of providerEnvVarCandidates(provider)) if ((env[envVar] ?? "").length > 0) return true;
7542
+ return hasAmbientCredential(provider, env);
7543
+ }
7544
+ async function providerAvailability(agentDir, env = process.env) {
7545
+ const storedProviders = await configuredProviderIds(agentDir).catch(() => /* @__PURE__ */ new Set());
7546
+ const declaredKeys = listCustomProviders(`${agentDir}/models.json`);
7547
+ const usableDeclared = /* @__PURE__ */ new Set();
7548
+ for (const [providerId, entry] of Object.entries(declaredKeys)) if (declaredApiKeyResolves(entry.apiKey, env)) usableDeclared.add(providerId);
7549
+ return (provider) => {
7550
+ if (storedProviders.has(provider)) return true;
7551
+ if (hasEnvironmentCredential(provider, env)) return true;
7552
+ return usableDeclared.has(provider);
7553
+ };
7554
+ }
7555
+ function toChatModelInfo(model, available) {
7556
+ const thinkingLevels = supportedThinkingLevels(model);
7557
+ const fallback = model.reasoning ? "medium" : "off";
7558
+ return {
7559
+ key: `${model.provider}/${model.id}`,
7560
+ provider: model.provider,
7561
+ id: model.id,
7562
+ name: model.name,
7563
+ reasoning: model.reasoning,
7564
+ input: model.input,
7565
+ contextWindow: model.contextWindow,
7566
+ maxTokens: model.maxTokens,
7567
+ available,
7568
+ thinkingLevels,
7569
+ defaultThinkingLevel: thinkingLevels.includes(fallback) ? fallback : thinkingLevels[0] ?? "off"
7570
+ };
7571
+ }
7572
+ function toChatModelInfoList(catalog, isProviderAvailable) {
7573
+ return catalog.models.map((model) => toChatModelInfo(model, isProviderAvailable(model.provider)));
7574
+ }
7575
+ function catalogHasModel(catalog, provider, id) {
7576
+ return catalog.models.some((model) => model.provider === provider && model.id === id);
7577
+ }
7578
+ function firstAvailableModelId(catalog, isProviderAvailable, provider) {
7579
+ return catalog.models.find((model) => model.provider === provider && isProviderAvailable(model.provider))?.id;
7580
+ }
7581
+ var DEFAULT_TIMEOUT_MS = 6e4;
7582
+ var PendingDialogRegistry = class {
7583
+ bySession = /* @__PURE__ */ new Map();
7584
+ defaultTimeoutMs;
7585
+ emitFrame;
7586
+ constructor(options) {
7587
+ this.defaultTimeoutMs = options.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS;
7588
+ this.emitFrame = options.emitFrame;
7589
+ }
7590
+ get(sessionId, toolCallId) {
7591
+ return this.bySession.get(sessionId)?.get(toolCallId);
7592
+ }
7593
+ list(sessionId) {
7594
+ const map = this.bySession.get(sessionId);
7595
+ return map ? [...map.values()] : [];
7596
+ }
7597
+ snapshot(sessionId) {
7598
+ return this.list(sessionId).map((d) => ({
7599
+ sessionId: d.sessionId,
7600
+ toolCallId: d.toolCallId,
7601
+ kind: d.kind,
7602
+ title: d.title,
7603
+ ...d.message ? { message: d.message } : {},
7604
+ ...d.options ? { options: [...d.options] } : {},
7605
+ ...d.questions ? { questions: [...d.questions] } : {},
7606
+ ...d.placeholder ? { placeholder: d.placeholder } : {},
7607
+ createdAt: d.createdAt,
7608
+ ...d.timeoutMs !== void 0 ? { timeoutMs: d.timeoutMs } : {}
7609
+ }));
7610
+ }
7611
+ async open(args) {
7612
+ const map = this.forSession(args.sessionId);
7613
+ const timeoutDuration = args.timeoutMs ?? this.defaultTimeoutMs;
7614
+ return await new Promise((resolve2, reject) => {
7615
+ let settled = false;
7616
+ const dialog = {
7617
+ sessionId: args.sessionId,
7618
+ toolCallId: args.toolCallId,
7619
+ kind: args.kind,
7620
+ title: args.title,
7621
+ message: args.message,
7622
+ options: args.options,
7623
+ questions: args.questions,
7624
+ placeholder: args.placeholder,
7625
+ createdAt: Date.now(),
7626
+ timeoutMs: timeoutDuration,
7627
+ resolve: (value) => {
7628
+ if (settled) return;
7629
+ settled = true;
7630
+ resolve2(value);
7631
+ },
7632
+ reject: (err) => {
7633
+ if (settled) return;
7634
+ settled = true;
7635
+ reject(err);
7636
+ },
7637
+ timer: setTimeout(() => {
7638
+ if (settled) return;
7639
+ this.cancel(args.sessionId, args.toolCallId, "timeout");
7640
+ }, timeoutDuration)
7641
+ };
7642
+ map.set(args.toolCallId, dialog);
7643
+ this.emitFrame(args.sessionId, {
7644
+ type: "tool",
7645
+ part: args.signalFrame
7646
+ });
7647
+ });
7648
+ }
7649
+ /**
7650
+ * Resolve a pending dialog on `POST /api/chat/question`. Returns true iff a
7651
+ * pending entry was found (caller returns 404 when false so the client can
7652
+ * distinguish "already answered" from "unknown toolCallId").
7653
+ */
7654
+ answer(sessionId, toolCallId, answer) {
7655
+ const dialog = this.get(sessionId, toolCallId);
7656
+ if (!dialog) return false;
7657
+ clearTimeout(dialog.timer);
7658
+ this.bySession.get(sessionId)?.delete(toolCallId);
7659
+ dialog.resolve(answer);
7660
+ return true;
7661
+ }
7662
+ /**
7663
+ * Cancel a pending dialog, emitting a `tool-Question` cancellation frame so
7664
+ * connected/replaying UIs can clear the question card.
7665
+ */
7666
+ cancel(sessionId, toolCallId, reason) {
7667
+ const dialog = this.get(sessionId, toolCallId);
7668
+ if (!dialog) return false;
7669
+ clearTimeout(dialog.timer);
7670
+ this.bySession.get(sessionId)?.delete(toolCallId);
7671
+ const frame = {
7672
+ type: "tool",
7673
+ part: {
7674
+ type: "tool-Question",
7675
+ toolCallId,
7676
+ state: "output-error",
7677
+ output: {
7678
+ cancelled: true,
7679
+ reason,
7680
+ title: dialog.title
7681
+ }
7682
+ }
7683
+ };
7684
+ this.emitFrame(sessionId, frame);
7685
+ dialog.reject(/* @__PURE__ */ new Error(`Question cancelled: ${reason}`));
7686
+ return true;
7687
+ }
7688
+ /** Cancel every pending dialog in a session (used by `abort` and shutdown). */
7689
+ cancelAll(sessionId, reason) {
7690
+ const pending = this.list(sessionId);
7691
+ for (const dialog of pending) {
7692
+ if (this.bySession.get(sessionId)?.get(dialog.toolCallId) === void 0) continue;
7693
+ this.cancel(sessionId, dialog.toolCallId, reason);
7694
+ }
7695
+ return pending.length;
7696
+ }
7697
+ forSession(sessionId) {
7698
+ let existing = this.bySession.get(sessionId);
7699
+ if (!existing) {
7700
+ existing = /* @__PURE__ */ new Map();
7701
+ this.bySession.set(sessionId, existing);
7702
+ }
7703
+ return existing;
7704
+ }
7705
+ };
7706
+ var RingBuffer = class {
7707
+ entries = [];
7708
+ capacity;
7709
+ nextSeq = 1;
7710
+ generation = crypto.randomUUID();
7711
+ constructor(capacity = 500) {
7712
+ this.capacity = Math.max(1, capacity);
7713
+ }
7714
+ get streamGeneration() {
7715
+ return this.generation;
7716
+ }
7717
+ push(event, decorate) {
7718
+ const seq = this.nextSeq++;
7719
+ const entry = {
7720
+ seq,
7721
+ event: decorate ? decorate(seq, this.generation) : event
7722
+ };
7723
+ this.entries.push(entry);
7724
+ if (this.entries.length > this.capacity) this.entries.splice(0, this.entries.length - this.capacity);
7725
+ return entry;
7726
+ }
7727
+ /**
7728
+ * Replay entries with seq > `lastEventId`. If `lastEventId` is behind the
7729
+ * oldest retained entry or ahead of the current sequence (for example after a
7730
+ * process restart), return `{ replayed, overflowed: true }` so the caller
7127
7731
  * emits a resync frame and instructs the client to rehydrate.
7128
7732
  */
7129
7733
  replaySince(lastEventId) {
@@ -8198,21 +8802,32 @@ var PrimeBridge = class {
8198
8802
  }
8199
8803
  async setModel(sessionId, model) {
8200
8804
  const session = this.#requireSession(sessionId);
8201
- if (getPrimeConfig().modelRegistry.find(model.provider, model.id)) {
8805
+ const catalog = await readDaemonModelCatalog(session.cwd).catch((error) => {
8806
+ process.stderr.write(`[bridge:${sessionId.slice(0, 8)}] model catalog unavailable, passing ${model.provider}/${model.id} through: ${error instanceof Error ? error.message : String(error)}
8807
+ `);
8808
+ });
8809
+ if (!catalog) {
8810
+ await session.connection.setModel(model.provider, model.id);
8811
+ return {
8812
+ provider: model.provider,
8813
+ id: model.id
8814
+ };
8815
+ }
8816
+ if (catalogHasModel(catalog, model.provider, model.id)) {
8202
8817
  await session.connection.setModel(model.provider, model.id);
8203
8818
  return {
8204
8819
  provider: model.provider,
8205
8820
  id: model.id
8206
8821
  };
8207
8822
  }
8208
- const fallback = getPrimeConfig().modelRegistry.getAvailable().find((candidate) => candidate.provider === model.provider);
8823
+ const fallback = firstAvailableModelId(catalog, await providerAvailability(getPrimeConfig().agentDir), model.provider);
8209
8824
  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}
8825
+ process.stderr.write(`[bridge:${sessionId.slice(0, 8)}] model fallback ${model.provider}/${model.id} -> ${model.provider}/${fallback}
8211
8826
  `);
8212
- await session.connection.setModel(fallback.provider, fallback.id);
8827
+ await session.connection.setModel(model.provider, fallback);
8213
8828
  return {
8214
- provider: fallback.provider,
8215
- id: fallback.id
8829
+ provider: model.provider,
8830
+ id: fallback
8216
8831
  };
8217
8832
  }
8218
8833
  async setThinkingLevel(level) {
@@ -9765,238 +10380,32 @@ function fleetSettingsPath(agentDir) {
9765
10380
  async function readFleetSettings(agentDir) {
9766
10381
  try {
9767
10382
  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
10383
+ version: 1,
10384
+ composerIntentEnabled: JSON.parse(await readFile(fleetSettingsPath(agentDir), "utf8")).composerIntentEnabled === true
9977
10385
  };
9978
- }, { mode: 384 });
9979
- return outcome.status === "refused" ? outcome : { status: "written" };
10386
+ } catch {
10387
+ return { ...DEFAULT_FLEET_SETTINGS };
10388
+ }
9980
10389
  }
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));
10390
+ var writeChain = Promise.resolve();
10391
+ async function updateFleetSettings(agentDir, patch) {
10392
+ const update = writeChain.then(() => writeFleetSettings(agentDir, patch));
10393
+ writeChain = update.catch(() => void 0);
10394
+ return update;
9985
10395
  }
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" };
10396
+ async function writeFleetSettings(agentDir, patch) {
10397
+ const current = await readFleetSettings(agentDir);
10398
+ const next = {
10399
+ version: 1,
10400
+ composerIntentEnabled: patch.composerIntentEnabled ?? current.composerIntentEnabled
10401
+ };
10402
+ const path = fleetSettingsPath(agentDir);
10403
+ await mkdir(dirname(path), { recursive: true });
10404
+ const temporary = `${path}.${process.pid}.${crypto.randomUUID()}.tmp`;
10405
+ await writeFile(temporary, `${JSON.stringify(next, null, " ")}
10406
+ `, "utf8");
10407
+ await rename(temporary, path);
10408
+ return next;
10000
10409
  }
10001
10410
  var TYPESAFE_CREDENTIAL_ID = "typesafe";
10002
10411
  async function readStoredTypeSafeKey() {
@@ -18238,7 +18647,7 @@ function handleChatMcpOAuthPost(request, deps = defaultMcpDeps()) {
18238
18647
  if ((body.action ?? "login") === "logout") return Response.json(await logoutMcp(body.name, deps));
18239
18648
  return Response.json({
18240
18649
  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."
18650
+ 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
18651
  }, { status: 501 });
18243
18652
  }, request);
18244
18653
  }
@@ -18257,25 +18666,17 @@ function handleChatMcpTokenPost(request, deps = defaultMcpDeps()) {
18257
18666
  });
18258
18667
  }, request);
18259
18668
  }
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
- });
18669
+ function defaultChatModelsDeps() {
18670
+ const config = getPrimeConfig();
18671
+ return {
18672
+ resolveCwd: cwdForRequest,
18673
+ agentDir: config.agentDir,
18674
+ modelsJsonPath: `${config.agentDir}/models.json`,
18675
+ readCatalog: readDaemonModelCatalog,
18676
+ providerAvailability: (agentDir) => providerAvailability(agentDir),
18677
+ settingsFor: (cwd) => config.settingsFor(cwd),
18678
+ customProvidersMissingApiKey: providersMissingApiKey
18679
+ };
18279
18680
  }
18280
18681
  function toPatternCandidate(model) {
18281
18682
  return {
@@ -18283,46 +18684,31 @@ function toPatternCandidate(model) {
18283
18684
  name: model.name,
18284
18685
  provider: model.provider,
18285
18686
  modelId: model.id,
18286
- key: `${model.provider}/${model.id}`
18687
+ key: model.key
18287
18688
  };
18288
18689
  }
18289
- function isWorkingConfiguredModel(registry, model) {
18290
- return registry.getProviderAuthStatus(model.provider).configured && registry.hasConfiguredAuth(model);
18690
+ function customProviderDiagnostics(modelsJsonPath, offenders) {
18691
+ if (offenders.length === 0) return [];
18692
+ const label = safePathLabel(modelsJsonPath);
18693
+ 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
18694
  }
18292
- function handleChatModelsGet(request) {
18695
+ function handleChatModelsGet(request, deps = defaultChatModelsDeps()) {
18293
18696
  return wrapApiHandler(async () => {
18294
- const config = getPrimeConfig();
18295
- const cwd = await cwdForRequest(request);
18296
- const settings = config.settingsFor(cwd);
18297
- const registry = config.modelRegistry;
18697
+ const cwd = await deps.resolveCwd(request);
18698
+ const settings = deps.settingsFor(cwd);
18298
18699
  const scope = new URL(request.url).searchParams.get("scope") === "all" ? "all" : "enabled";
18700
+ const isProviderAvailable = await deps.providerAvailability(deps.agentDir);
18701
+ let models = toChatModelInfoList(await deps.readCatalog(cwd), isProviderAvailable);
18299
18702
  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 = [];
18703
+ if (scope === "enabled") models = models.filter((model) => model.available && isModelPatternEnabled(toPatternCandidate(model), patterns));
18704
+ const diagnostics = customProviderDiagnostics(deps.modelsJsonPath, deps.customProvidersMissingApiKey(deps.modelsJsonPath));
18303
18705
  const defaultProvider = settings.getDefaultProvider();
18304
18706
  const defaultModel = settings.getDefaultModel();
18305
18707
  const defaultThinkingLevel = settings.getDefaultThinkingLevel();
18306
18708
  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;
18709
+ const selectedModelKey = candidateModelKey && models.some((m) => m.key === candidateModelKey) ? candidateModelKey : models[0]?.key;
18308
18710
  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
- }),
18711
+ models,
18326
18712
  ...selectedModelKey ? { selectedModelKey } : {},
18327
18713
  ...defaultProvider ? { defaultProvider } : {},
18328
18714
  ...defaultModel ? { defaultModel } : {},
@@ -18331,180 +18717,6 @@ function handleChatModelsGet(request) {
18331
18717
  });
18332
18718
  }, request);
18333
18719
  }
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
18720
  function providerEnvVarName(providerId) {
18509
18721
  return PRIME_PROVIDER_ENV_MAP[providerId];
18510
18722
  }
@@ -18520,6 +18732,7 @@ function openAiModelsUrl(baseUrl) {
18520
18732
  const parsed = new URL(baseUrl);
18521
18733
  if (parsed.protocol !== "https:" && parsed.protocol !== "http:") throw new Error("model discovery requires an http(s) URL");
18522
18734
  if (parsed.protocol === "http:" && !isLoopbackHostname(parsed.hostname)) throw new Error("model discovery requires https for non-loopback hosts");
18735
+ if (parsed.username || parsed.password) throw new Error("Credentials in model discovery URLs are not supported");
18523
18736
  const path = parsed.pathname.replace(/\/+$/, "");
18524
18737
  parsed.pathname = path.endsWith("/v1") ? `${path}/models` : `${path}/v1/models`;
18525
18738
  parsed.search = "";
@@ -18534,19 +18747,22 @@ function handleChatModelsDiscoverPost(request) {
18534
18747
  const apiKey = await resolveProviderApiKey(config.agentDir, body.providerId);
18535
18748
  if (!apiKey) return Response.json({
18536
18749
  providerId: body.providerId,
18537
- models: []
18750
+ models: [],
18751
+ error: "Model discovery is unavailable. Check provider configuration and try again."
18538
18752
  });
18539
18753
  const provider = body.providerId;
18540
18754
  const entry = listCustomProviders(`${config.agentDir}/models.json`)[provider];
18541
18755
  const api = uiApiForCustomProvider(entry?.api);
18542
18756
  if (entry?.api && !api || api && !isOccFamilyApi(api)) return Response.json({
18543
18757
  providerId: provider,
18544
- models: []
18758
+ models: [],
18759
+ error: "Model discovery is unavailable. Check provider configuration and try again."
18545
18760
  });
18546
18761
  const baseUrl = entry?.baseUrl;
18547
18762
  if (!baseUrl) return Response.json({
18548
18763
  providerId: provider,
18549
- models: []
18764
+ models: [],
18765
+ error: "Model discovery is unavailable. Check provider configuration and try again."
18550
18766
  });
18551
18767
  try {
18552
18768
  const modelsUrl = openAiModelsUrl(baseUrl);
@@ -18557,9 +18773,12 @@ function handleChatModelsDiscoverPost(request) {
18557
18773
  });
18558
18774
  if (!response.ok) return Response.json({
18559
18775
  providerId: provider,
18560
- models: []
18776
+ models: [],
18777
+ error: "Model discovery is unavailable. Check provider configuration and try again."
18561
18778
  });
18562
- const models = ((await response.json()).data ?? []).map((m) => ({
18779
+ const payload = await response.json();
18780
+ if (!Array.isArray(payload.data) || !payload.data.every((m) => typeof m?.id === "string" && m.id.length > 0)) throw new Error("Invalid model catalog");
18781
+ const models = payload.data.map((m) => ({
18563
18782
  key: `${provider}/${m.id}`,
18564
18783
  provider,
18565
18784
  id: m.id,
@@ -18581,7 +18800,8 @@ function handleChatModelsDiscoverPost(request) {
18581
18800
  } catch {
18582
18801
  return Response.json({
18583
18802
  providerId: provider,
18584
- models: []
18803
+ models: [],
18804
+ error: "Could not refresh models. Previous results are retained; try again."
18585
18805
  });
18586
18806
  }
18587
18807
  }, request);
@@ -18687,46 +18907,32 @@ function handleChatPlanPresentationPut(request) {
18687
18907
  }) });
18688
18908
  }, request);
18689
18909
  }
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
18910
  var KNOWN_PROVIDER_BY_ID = new Map(KNOWN_PROVIDERS.map((provider) => [provider.id, provider]));
18726
18911
  var EXTRA_BUILTIN_ENV_HINTS = {
18727
18912
  anthropic: "ANTHROPIC_API_KEY",
18728
18913
  "amazon-bedrock": "AWS_BEARER_TOKEN_BEDROCK"
18729
18914
  };
18915
+ var CREDENTIAL_QUERY_PARAMETER_NAMES = /* @__PURE__ */ new Set([
18916
+ "auth",
18917
+ "authorization",
18918
+ "apikey",
18919
+ "bearer",
18920
+ "credential",
18921
+ "credentials",
18922
+ "key",
18923
+ "pass",
18924
+ "password",
18925
+ "passwd",
18926
+ "secret",
18927
+ "sig",
18928
+ "signature",
18929
+ "token",
18930
+ "jwt",
18931
+ "assertion"
18932
+ ]);
18933
+ function isCredentialQueryParameter(name) {
18934
+ 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));
18935
+ }
18730
18936
  function envHintForBuiltin(providerId) {
18731
18937
  return PRIME_PROVIDER_ENV_MAP[providerId] ?? EXTRA_BUILTIN_ENV_HINTS[providerId] ?? "";
18732
18938
  }
@@ -18751,14 +18957,26 @@ var BUILTIN_OAUTH_PROVIDER_IDS = [
18751
18957
  "openai-codex",
18752
18958
  "xai"
18753
18959
  ];
18754
- function isLlmsProviderId(providerId) {
18755
- return providerId !== "prime-agent-traces";
18960
+ function browserProviderBaseUrl(baseUrl) {
18961
+ if (!baseUrl) return void 0;
18962
+ try {
18963
+ const parsed = new URL(baseUrl);
18964
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return void 0;
18965
+ const credentialParameters = [...parsed.searchParams.keys()].filter(isCredentialQueryParameter);
18966
+ if (!parsed.username && !parsed.password && credentialParameters.length === 0) return baseUrl;
18967
+ parsed.username = "";
18968
+ parsed.password = "";
18969
+ for (const name of credentialParameters) parsed.searchParams.delete(name);
18970
+ return parsed.toString();
18971
+ } catch {
18972
+ return;
18973
+ }
18756
18974
  }
18757
18975
  function buildProviders(configuredFromDisk) {
18758
18976
  const config = getPrimeConfig();
18759
18977
  const oauthIds = new Set(BUILTIN_OAUTH_PROVIDER_IDS);
18760
18978
  const custom2 = listCustomProviders(`${config.agentDir}/models.json`);
18761
- const builtinIds = new Set(Object.keys(BUILT_IN_PROVIDER_DISPLAY_NAMES).filter(isLlmsProviderId));
18979
+ const builtinIds = new Set(builtInProviderIds());
18762
18980
  const allIds = /* @__PURE__ */ new Set([...builtinIds, ...Object.keys(custom2)]);
18763
18981
  for (const providerId of configuredFromDisk ?? []) if (!providerId.startsWith("mcp:")) allIds.add(providerId);
18764
18982
  allIds.add(OPENAI_CHAT_COMPLETIONS_PROVIDER_ID);
@@ -18768,7 +18986,7 @@ function buildProviders(configuredFromDisk) {
18768
18986
  const configured = configuredFromDisk?.has(id) ?? false;
18769
18987
  const catalogEntry = KNOWN_PROVIDER_BY_ID.get(id);
18770
18988
  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);
18989
+ const envVarName = isCustom ? customEntry?.apiKey === envVarNameForManagedProvider(id) ? customEntry.apiKey : catalogEntry?.envVarName ?? "" : envHintForBuiltin(id);
18772
18990
  const row = {
18773
18991
  id,
18774
18992
  name,
@@ -18776,12 +18994,13 @@ function buildProviders(configuredFromDisk) {
18776
18994
  ...resolveProviderAuthFields(id, envVarName, oauthIds),
18777
18995
  isConfigured: configured
18778
18996
  };
18779
- if (isOccProviderId(id) || isCustomProviderId(id)) {
18997
+ if (customEntry || isOccProviderId(id) || isCustomProviderId(id)) {
18780
18998
  row.providerFamily = isOccProviderId(id) ? OPENAI_CHAT_COMPLETIONS_PROVIDER_ID : "custom";
18781
18999
  if (customEntry?.name && (isNamedOccInstanceId(id) || isCustomProviderId(id))) row.displayName = customEntry.name;
18782
19000
  const customApi = uiApiForCustomProvider(customEntry?.api);
18783
19001
  if (customApi) row.api = customApi;
18784
- if (customEntry?.baseUrl) row.baseUrl = customEntry.baseUrl;
19002
+ const baseUrl = browserProviderBaseUrl(customEntry?.baseUrl);
19003
+ if (baseUrl) row.baseUrl = baseUrl;
18785
19004
  row.modelIds = (customEntry?.models ?? []).map((model) => model.id);
18786
19005
  row.discoverable = isDiscoverableEntry(customEntry);
18787
19006
  }
@@ -18799,6 +19018,7 @@ function requireBaseUrl(baseUrl, providerId) {
18799
19018
  if (!trimmed) badRequest(`baseUrl is required for ${providerId}`);
18800
19019
  try {
18801
19020
  const parsed = new URL(trimmed);
19021
+ if (parsed.username || parsed.password || [...parsed.searchParams.keys()].some(isCredentialQueryParameter)) badRequest("baseUrl must not contain credentials; use the API key field instead");
18802
19022
  if (parsed.protocol !== "https:" && parsed.protocol !== "http:") badRequest(`baseUrl must be an http(s) URL for ${providerId}`);
18803
19023
  } catch (error) {
18804
19024
  if (error && typeof error === "object" && "status" in error) throw error;
@@ -18828,7 +19048,7 @@ function handleChatProvidersPost(request) {
18828
19048
  if (isNewCustom || isNewOccInstance) {
18829
19049
  const displayName2 = body.displayName?.trim() ?? "";
18830
19050
  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))]);
19051
+ const existingIds = /* @__PURE__ */ new Set([...builtInProviderIds(), ...Object.keys(listCustomProviders(modelsJsonPath))]);
18832
19052
  providerId = allocateProviderId(toInstanceSlug(displayName2), existingIds, isNewCustom ? toCustomProviderId : toOccInstanceId);
18833
19053
  }
18834
19054
  const existingEntry = listCustomProviders(modelsJsonPath)[providerId];
@@ -18877,7 +19097,7 @@ function handleChatProvidersOAuthPost(request) {
18877
19097
  ChatProviderOAuthLoginRequestSchema.parse(await request.json().catch(() => ({})));
18878
19098
  return Response.json({
18879
19099
  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."
19100
+ 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
19101
  }, { status: 501 });
18882
19102
  }, request);
18883
19103
  }
@@ -19465,6 +19685,7 @@ function handleChatSettingsPatch(request) {
19465
19685
  code: "UNWRITABLE",
19466
19686
  message: `Fleet could not update ${safePathLabel(path)}. ${outcome.reason} Change these settings in the Prime Agent terminal interface instead.`
19467
19687
  }, { status: 409 });
19688
+ getPrimeConfig().invalidateSettings();
19468
19689
  if (outcome.impact.resourceReloadRequired) await getBridge().reloadResources().catch(() => void 0);
19469
19690
  const response = await buildResponse(cwd);
19470
19691
  return Response.json({