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