@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.
- package/CHANGELOG.md +59 -0
- package/dist/web/client/assets/{agents-CGjUBYX7.js → agents-BMQXA18L.js} +2 -2
- package/dist/web/client/assets/{agents-page-CTuFJL9H.js → agents-page-D2soW46O.js} +1 -1
- package/dist/web/client/assets/{chat-queries-CfZiupub.js → chat-queries-ty76G6rK.js} +1 -1
- package/dist/web/client/assets/{connectors-CFxLqG2x.js → connectors-E4KW6hBL.js} +2 -2
- package/dist/web/client/assets/{connectors-page-DQJdoAnl.js → connectors-page-CDzzjCIJ.js} +1 -1
- package/dist/web/client/assets/globals-DDDleTbS.css +2 -0
- package/dist/web/client/assets/{index-C9FHZDGw.js → index-Cf8tRpHL.js} +2 -2
- package/dist/web/client/assets/{mcp-connections-section-DEo6Kemv.js → mcp-connections-section-BsNhp1b2.js} +2 -2
- package/dist/web/client/assets/routes--tnF7d6H.js +14 -0
- package/dist/web/client/assets/settings-dialog-BomLOETY.js +3 -0
- package/dist/web/client/bundle-budget.json +5 -5
- package/dist/web/server/{_tanstack-start-manifest_v-B6ne-00G-OFX7UD4K.js → _tanstack-start-manifest_v-Io433TLr-YWPNQWQI.js} +7 -7
- package/dist/web/server/{agents-BwzqhPjK-ANFHE5AG.js → agents-DOgCHixY-KUZP2VDO.js} +2 -2
- package/dist/web/server/{agents-page-DEqPV-eG-BR5MR74O.js → agents-page-BdiR9H8w-M6X6SXMF.js} +3 -3
- package/dist/web/server/{artifacts-panel-DLO8OfXb-B2ZWCDFP.js → artifacts-panel-cYqS54cM-K6SF3K6W.js} +5 -5
- package/dist/web/server/{chat-command-palette-CaDQAMAu-KDH7BTDS.js → chat-command-palette-BDnwcymZ-DSTFIAOD.js} +5 -5
- package/dist/web/server/{chunk-HUHUPGHT.js → chunk-2OC2FKHR.js} +5 -5
- package/dist/web/server/{chunk-V2GCVTDP.js → chunk-37EK2TYH.js} +33 -48
- package/dist/web/server/{chunk-TOJA3X5N.js → chunk-BLGKLQJG.js} +16 -5
- package/dist/web/server/{chunk-TQ2RECTH.js → chunk-C5XHGLET.js} +1 -1
- package/dist/web/server/{chunk-B5FKCW75.js → chunk-FI3UF2GW.js} +4 -4
- package/dist/web/server/{chunk-UDUG3MYZ.js → chunk-NSFKPEFI.js} +2 -2
- package/dist/web/server/{chunk-KDCSEISZ.js → chunk-NZCEQWK2.js} +2 -1
- package/dist/web/server/{chunk-VM3U6PUU.js → chunk-QOZCYKMA.js} +2 -2
- package/dist/web/server/{chunk-6AFVCERK.js → chunk-RHJEOG37.js} +4 -4
- package/dist/web/server/{chunk-TTNONWZZ.js → chunk-W2GI7NRX.js} +893 -672
- package/dist/web/server/{chunk-5IE54MRG.js → chunk-W7GGECTL.js} +3 -3
- package/dist/web/server/{connectors-C0xm91Ss-DJVBGD73.js → connectors-Dv_uPdv0-2X5VWQXK.js} +2 -2
- package/dist/web/server/{connectors-page-B6tJNZ6c-R5K4NSAT.js → connectors-page-DW75T-uH-EKRHUZ6K.js} +4 -4
- package/dist/web/server/{create-project-dialog-PFPHHWIV-GH7JR5IK.js → create-project-dialog-BS5Sva46-BO4RLW2Q.js} +5 -5
- package/dist/web/server/{openui-renderer-B9D8-Av--LFZP4U5V.js → openui-renderer-Q31y65CQ-QNVSP4NN.js} +4 -4
- package/dist/web/server/{resources-panel-CTzOOKPz-5G6RS2D6.js → resources-panel-CH8sG0Od-4BZN6SLY.js} +6 -6
- package/dist/web/server/{router-CnBux_Ld-YTVBMJFL.js → router-8EzTM9Eq-COVGJKKX.js} +2 -2
- package/dist/web/server/{routes-BUGzvQoO-WW43X7W2.js → routes-88H0fiN3-WIAZJ3RT.js} +4 -4
- package/dist/web/server/server.js +2 -2
- package/dist/web/server/{session-activity-panel-f0d3fCm7-I6L5IKFQ.js → session-activity-panel-CgSOde03-B5RCAGB4.js} +6 -6
- package/dist/web/server/{session-reasoning-panel-QIt_xjP0-FKJWMNHO.js → session-reasoning-panel-Dmny6YTk-D3HQV2YB.js} +5 -5
- package/dist/web/server/{session-tool-renderer-Cg_ZPHxS-BNQKNJNZ.js → session-tool-renderer-BrnTO2w3-V6EE6AB2.js} +5 -5
- package/dist/web/server/{session-tree-panel-B--el8O5-NZOZJM5L.js → session-tree-panel-BEqR_cAJ-TZS7VRCB.js} +5 -5
- package/dist/web/server/{settings-dialog-D_eJN2ia-W47Y474K.js → settings-dialog-DkesVBrS-BAAA3YNU.js} +145 -66
- package/dist/web/server/{subagent-chat-panel-C9nWP21E-D5C2TTYD.js → subagent-chat-panel-Bfmt1bCE-DKDVH3NJ.js} +8 -8
- package/dist/web/server/{subagents-panel-fAKvBNRG-O7QHXNE2.js → subagents-panel-ChJDDvad-GNCHDTPB.js} +8 -8
- package/dist/web/server/{workspace-panel-CZMNMU63-SYOSM3B7.js → workspace-panel-Cij_JylD-WPYSV5FA.js} +6 -6
- package/package.json +1 -1
- package/dist/web/client/assets/globals-DJhWyayo.css +0 -2
- package/dist/web/client/assets/routes-ja_liP8Z.js +0 -14
- 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-
|
|
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-
|
|
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,
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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,
|
|
4572
|
+
settingsByCwd.set(currentDefaultCwd, createLegacySettingsManager(currentDefaultCwd, agentDir));
|
|
4531
4573
|
return {
|
|
4532
4574
|
authStorage,
|
|
4533
|
-
modelRegistry: modelRegistry2,
|
|
4534
4575
|
get defaultSettings() {
|
|
4535
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
6978
|
-
var
|
|
6979
|
-
|
|
6980
|
-
|
|
6981
|
-
|
|
6982
|
-
|
|
6983
|
-
|
|
6984
|
-
|
|
6985
|
-
|
|
6986
|
-
|
|
6987
|
-
|
|
6988
|
-
|
|
6989
|
-
|
|
6990
|
-
|
|
6991
|
-
return
|
|
6992
|
-
|
|
6993
|
-
|
|
6994
|
-
|
|
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
|
-
|
|
7008
|
-
const
|
|
7009
|
-
|
|
7010
|
-
|
|
7011
|
-
|
|
7012
|
-
|
|
7013
|
-
|
|
7014
|
-
|
|
7015
|
-
|
|
7016
|
-
|
|
7017
|
-
|
|
7018
|
-
|
|
7019
|
-
|
|
7020
|
-
|
|
7021
|
-
|
|
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
|
-
|
|
7047
|
-
|
|
7048
|
-
|
|
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
|
-
|
|
7060
|
-
|
|
7061
|
-
|
|
7062
|
-
|
|
7063
|
-
const
|
|
7064
|
-
|
|
7065
|
-
|
|
7066
|
-
|
|
7067
|
-
|
|
7068
|
-
|
|
7069
|
-
|
|
7070
|
-
|
|
7071
|
-
|
|
7072
|
-
|
|
7073
|
-
|
|
7074
|
-
|
|
7075
|
-
|
|
7076
|
-
|
|
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
|
-
|
|
7081
|
-
|
|
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
|
-
|
|
7092
|
-
|
|
7093
|
-
|
|
7094
|
-
|
|
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
|
-
|
|
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
|
-
|
|
7111
|
-
|
|
7125
|
+
}
|
|
7126
|
+
function currentMode(path) {
|
|
7127
|
+
try {
|
|
7128
|
+
return statSync(path).mode & 511;
|
|
7129
|
+
} catch {
|
|
7130
|
+
return;
|
|
7112
7131
|
}
|
|
7113
|
-
|
|
7114
|
-
|
|
7115
|
-
|
|
7116
|
-
|
|
7117
|
-
|
|
7118
|
-
|
|
7119
|
-
|
|
7120
|
-
|
|
7121
|
-
|
|
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
|
-
|
|
7125
|
-
|
|
7126
|
-
|
|
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
|
-
|
|
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().
|
|
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} -> ${
|
|
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(
|
|
8827
|
+
await session.connection.setModel(model.provider, fallback);
|
|
8213
8828
|
return {
|
|
8214
|
-
provider:
|
|
8215
|
-
id: fallback
|
|
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
|
-
}
|
|
9979
|
-
|
|
10386
|
+
} catch {
|
|
10387
|
+
return { ...DEFAULT_FLEET_SETTINGS };
|
|
10388
|
+
}
|
|
9980
10389
|
}
|
|
9981
|
-
|
|
9982
|
-
|
|
9983
|
-
|
|
9984
|
-
|
|
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
|
|
9987
|
-
const
|
|
9988
|
-
|
|
9989
|
-
|
|
9990
|
-
|
|
9991
|
-
|
|
9992
|
-
|
|
9993
|
-
|
|
9994
|
-
|
|
9995
|
-
|
|
9996
|
-
|
|
9997
|
-
|
|
9998
|
-
|
|
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:
|
|
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
|
-
|
|
18261
|
-
|
|
18262
|
-
|
|
18263
|
-
|
|
18264
|
-
|
|
18265
|
-
|
|
18266
|
-
|
|
18267
|
-
|
|
18268
|
-
|
|
18269
|
-
|
|
18270
|
-
|
|
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:
|
|
18687
|
+
key: model.key
|
|
18287
18688
|
};
|
|
18288
18689
|
}
|
|
18289
|
-
function
|
|
18290
|
-
|
|
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
|
|
18295
|
-
const
|
|
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
|
-
|
|
18301
|
-
|
|
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) =>
|
|
18709
|
+
const selectedModelKey = candidateModelKey && models.some((m) => m.key === candidateModelKey) ? candidateModelKey : models[0]?.key;
|
|
18308
18710
|
return Response.json({
|
|
18309
|
-
models
|
|
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
|
|
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
|
|
18755
|
-
return
|
|
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(
|
|
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
|
|
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
|
-
|
|
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([...
|
|
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: "
|
|
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({
|