turbollm 1.12.1 → 1.12.2
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/README.md +17 -0
- package/dist/cli.js +294 -32
- package/dist/webdist/assets/{AgentEditPage-B9mKK6Ob.js → AgentEditPage-DC5b8GLz.js} +1 -1
- package/dist/webdist/assets/{ChatScreen-DLD1CDS1.js → ChatScreen-_trAuOm4.js} +1 -1
- package/dist/webdist/assets/{CodeComposer-DHE_iTkC.js → CodeComposer-N7JTyzpl.js} +1 -1
- package/dist/webdist/assets/{CodeHomeScreen-6CKrrrZ5.js → CodeHomeScreen-CY7CTv_Q.js} +1 -1
- package/dist/webdist/assets/{CodeSessionScreen-JUpzeYHF.js → CodeSessionScreen-tx2s3iWr.js} +1 -1
- package/dist/webdist/assets/{CustomizeScreen-C7f4ZRSa.js → CustomizeScreen-BUiEu5vl.js} +1 -1
- package/dist/webdist/assets/{DeveloperScreen-n6gJC3-E.js → DeveloperScreen-JAV1WwIi.js} +1 -1
- package/dist/webdist/assets/EnginesScreen-CL-rA9uX.js +8 -0
- package/dist/webdist/assets/{ModelDetailDialog-BdPGMTTz.js → ModelDetailDialog-B_-j8TNF.js} +1 -1
- package/dist/webdist/assets/{ModelLoadMenu-D3zo79dw.js → ModelLoadMenu-8zL_YHxh.js} +1 -1
- package/dist/webdist/assets/{ModelsScreen-7AJUC2wa.js → ModelsScreen-Cse1wZKp.js} +1 -1
- package/dist/webdist/assets/{OnboardingScreen-CGbbx6bS.js → OnboardingScreen-DNUgdieT.js} +1 -1
- package/dist/webdist/assets/{RoutineEditPage-Dd_snYdQ.js → RoutineEditPage-Dkt04QR1.js} +1 -1
- package/dist/webdist/assets/{RoutinesPanel-5M00XOUM.js → RoutinesPanel-iULR-iL1.js} +1 -1
- package/dist/webdist/assets/{SettingsScreen-B489HDiC.js → SettingsScreen-CQXDBVEu.js} +1 -1
- package/dist/webdist/assets/{SidebarResizeHandle-B0wgsZJ6.js → SidebarResizeHandle-Bx_x_ICv.js} +1 -1
- package/dist/webdist/assets/{SkillEditPage-CVYFfzLc.js → SkillEditPage-BKrXTpY3.js} +1 -1
- package/dist/webdist/assets/{TokensScreen-CQ4m20pP.js → TokensScreen-DTazcqcy.js} +1 -1
- package/dist/webdist/assets/{ToolApprovalBar-B2nX56dI.js → ToolApprovalBar-DDgGZOxI.js} +3 -3
- package/dist/webdist/assets/WorkspaceScreen-DpkSZQhK.js +1 -0
- package/dist/webdist/assets/{abnfDiagram-N423BO3Z-DoOYvhD1.js → abnfDiagram-N423BO3Z-h22xWhjH.js} +1 -1
- package/dist/webdist/assets/{agent-api-BAMDY8-v.js → agent-api-BXUf3UuJ.js} +1 -1
- package/dist/webdist/assets/{alert-dialog-rMeq-XIF.js → alert-dialog-hkwXUo0d.js} +1 -1
- package/dist/webdist/assets/{arc-BmVPVvg0.js → arc-BSvZ7mxZ.js} +1 -1
- package/dist/webdist/assets/{architectureDiagram-T3A2C74G-Bh4HLM3j.js → architectureDiagram-T3A2C74G-D840pkTs.js} +1 -1
- package/dist/webdist/assets/{arrow-left-Bawx_G0m.js → arrow-left-tnJKNvU8.js} +1 -1
- package/dist/webdist/assets/{badge-BK8Xn6K3.js → badge-HTozgaNk.js} +1 -1
- package/dist/webdist/assets/{blockDiagram-VBNYF7ZC-D4dEMw56.js → blockDiagram-VBNYF7ZC-DlvennNB.js} +1 -1
- package/dist/webdist/assets/{bot-DmBk_i45.js → bot-BEKBqPcU.js} +1 -1
- package/dist/webdist/assets/{c4Diagram-5PPSVZJV-cnPwu_pX.js → c4Diagram-5PPSVZJV-CneJCPhz.js} +1 -1
- package/dist/webdist/assets/channel-ZaF9JSkw.js +1 -0
- package/dist/webdist/assets/{chat-api-Bgrb0rnf.js → chat-api-B_SlDoXA.js} +1 -1
- package/dist/webdist/assets/{chat-queries-zz2ch8OT.js → chat-queries-CxkwlyLc.js} +1 -1
- package/dist/webdist/assets/{chevron-down-CNDC69O9.js → chevron-down-DfzNO8NY.js} +1 -1
- package/dist/webdist/assets/{chevron-left-CxvUI3ck.js → chevron-left-DHH-nnSd.js} +1 -1
- package/dist/webdist/assets/{chevron-right-CpdnK5S2.js → chevron-right-DnT71PVy.js} +1 -1
- package/dist/webdist/assets/{chunk-2GRJ4B5K-CtDLA9D7.js → chunk-2GRJ4B5K-DfGpb0ih.js} +1 -1
- package/dist/webdist/assets/{chunk-2Q5K7J3B-CclvIDBO.js → chunk-2Q5K7J3B-CbNbsgTh.js} +1 -1
- package/dist/webdist/assets/{chunk-5RXB4S5H-DFYQzU9L.js → chunk-5RXB4S5H-Bc_97P9Q.js} +1 -1
- package/dist/webdist/assets/{chunk-5VM5RSS4-CGpXaRvE.js → chunk-5VM5RSS4-Dc1cXgV2.js} +1 -1
- package/dist/webdist/assets/{chunk-6Q2QTUOP-CI7s7JEA.js → chunk-6Q2QTUOP-CdFOJa-e.js} +1 -1
- package/dist/webdist/assets/{chunk-GF5L2VYU-y-SeWQKo.js → chunk-GF5L2VYU-D3PC0Vtx.js} +1 -1
- package/dist/webdist/assets/{chunk-JWPE2WC7-BvVsBdrN.js → chunk-JWPE2WC7-BPW77F2i.js} +1 -1
- package/dist/webdist/assets/{chunk-KBJHAD2P-CUc-YWns.js → chunk-KBJHAD2P-BHXAIZXB.js} +1 -1
- package/dist/webdist/assets/{chunk-RYQCIY6F-CABY0N-y.js → chunk-RYQCIY6F-CrnPyqjW.js} +1 -1
- package/dist/webdist/assets/{chunk-XXDRQBXY-ChEtYkzy.js → chunk-XXDRQBXY-Bv9q4UGg.js} +1 -1
- package/dist/webdist/assets/{circle-check-D-vn9nY2.js → circle-check-BgrXRJTZ.js} +1 -1
- package/dist/webdist/assets/{circle-x-BcnmRVTe.js → circle-x-CYZSobXW.js} +1 -1
- package/dist/webdist/assets/classDiagram-JCYQIIEL-CjrbKhLT.js +1 -0
- package/dist/webdist/assets/classDiagram-v2-OCEON4UE-CjrbKhLT.js +1 -0
- package/dist/webdist/assets/{common-xWkNwbHY.js → common-C48Zd9_u.js} +1 -1
- package/dist/webdist/assets/{cose-bilkent-JH36ORCC-CJpKH0B3.js → cose-bilkent-JH36ORCC-B9I1Xo2f.js} +1 -1
- package/dist/webdist/assets/{cynefin-VYW2F7L2-B97tFXrq.js → cynefin-VYW2F7L2-Djx9Q5Zl.js} +1 -1
- package/dist/webdist/assets/{cynefinDiagram-MW4NZA55-CTmmk7Xt.js → cynefinDiagram-MW4NZA55-egTrJ3T2.js} +1 -1
- package/dist/webdist/assets/{dagre-VZM6K2ZE-BMkcnGJJ.js → dagre-VZM6K2ZE-ClfYLjT5.js} +1 -1
- package/dist/webdist/assets/{diagram-7IWD3JNH-DxA708mT.js → diagram-7IWD3JNH-C-AaXLmL.js} +1 -1
- package/dist/webdist/assets/{diagram-B4RE2ZJO-Bkjy34TD.js → diagram-B4RE2ZJO-5ZsOP19O.js} +1 -1
- package/dist/webdist/assets/{diagram-LBJQPF4R-BorjsNJT.js → diagram-LBJQPF4R-BAa7bkkY.js} +1 -1
- package/dist/webdist/assets/{diagram-Q27KOJAE-hW5SDR9k.js → diagram-Q27KOJAE-HarfGeNl.js} +1 -1
- package/dist/webdist/assets/{diagram-UB23O5K3-c07HJJ5p.js → diagram-UB23O5K3-CvSFCTH8.js} +1 -1
- package/dist/webdist/assets/{dropdown-menu-CaecUWTU.js → dropdown-menu-BOUsiJco.js} +1 -1
- package/dist/webdist/assets/{ebnfDiagram-BXEA7PRR-CvgAWjhG.js → ebnfDiagram-BXEA7PRR-CD94llbm.js} +1 -1
- package/dist/webdist/assets/{erDiagram-JOGREHBK-C66LM90T.js → erDiagram-JOGREHBK-iSGYBlqE.js} +1 -1
- package/dist/webdist/assets/{external-link-NvytYVfT.js → external-link-c_B7G__W.js} +1 -1
- package/dist/webdist/assets/{file-text-BsC3M8zz.js → file-text-BMSTQeV2.js} +1 -1
- package/dist/webdist/assets/{flowDiagram-UKHOOZJN-BcXu-_m9.js → flowDiagram-UKHOOZJN-CfRmhZIc.js} +1 -1
- package/dist/webdist/assets/{folder-input-B5dvV2Rt.js → folder-input-BtE_5cPV.js} +1 -1
- package/dist/webdist/assets/{folder-plus-CfgNM2Bq.js → folder-plus-CFn90Mxi.js} +1 -1
- package/dist/webdist/assets/{ganttDiagram-PKOTCBZU-DtorJ7JE.js → ganttDiagram-PKOTCBZU-DoQkfMfx.js} +1 -1
- package/dist/webdist/assets/{git-branch-BRH6t3pJ.js → git-branch-ojL3vNiD.js} +1 -1
- package/dist/webdist/assets/{gitGraphDiagram-DS77QQ5N-krn_sqdS.js → gitGraphDiagram-DS77QQ5N-DsvnFgBc.js} +1 -1
- package/dist/webdist/assets/{index-C9ZXfFYo.js → index-CSavhRGL.js} +6 -6
- package/dist/webdist/assets/{infoDiagram-6WML65LV-DdSRJqp_.js → infoDiagram-6WML65LV-mDSA5sHn.js} +1 -1
- package/dist/webdist/assets/{ishikawaDiagram-WSZJBQD7-CXU7wA7v.js → ishikawaDiagram-WSZJBQD7-B5Yq0JqB.js} +1 -1
- package/dist/webdist/assets/{journeyDiagram-NVQOT4AX-CIzJLnh4.js → journeyDiagram-NVQOT4AX-5jpV1XZE.js} +1 -1
- package/dist/webdist/assets/{kanban-definition-27J2QSJJ-D7PciwFU.js → kanban-definition-27J2QSJJ-Cuyvwopj.js} +1 -1
- package/dist/webdist/assets/{linear-DJzOvrgv.js → linear-DqzYFlqe.js} +1 -1
- package/dist/webdist/assets/{link-queries-BfuN5xQI.js → link-queries-R-5RNwSV.js} +1 -1
- package/dist/webdist/assets/{lock-BpNNUDlH.js → lock-BMvhGs4r.js} +1 -1
- package/dist/webdist/assets/{mermaid.core-DEfoMsry.js → mermaid.core-BSsGYq_y.js} +4 -4
- package/dist/webdist/assets/{mindmap-definition-FAOFIHXS-B-p8gHME.js → mindmap-definition-FAOFIHXS-bGoQuSrf.js} +1 -1
- package/dist/webdist/assets/{monitor-3-JqrGh0.js → monitor-BgsqLI--.js} +1 -1
- package/dist/webdist/assets/{pegDiagram-VL7TDLO6-CVN-UtZ2.js → pegDiagram-VL7TDLO6-BSfifu-4.js} +1 -1
- package/dist/webdist/assets/{pencil-CLBnFmc5.js → pencil-CMc0BVmG.js} +1 -1
- package/dist/webdist/assets/{personas-Dkq9JD72.js → personas-CKCwxU6o.js} +1 -1
- package/dist/webdist/assets/{pieDiagram-7S7Q4E2Y-Cyowg7jU.js → pieDiagram-7S7Q4E2Y-DFMcMGg7.js} +1 -1
- package/dist/webdist/assets/{plus-BsBN79CH.js → plus-5OMKNJj_.js} +1 -1
- package/dist/webdist/assets/{quadrantDiagram-CIZ2JOQS-CLEre7Bo.js → quadrantDiagram-CIZ2JOQS-BRQ2ESFh.js} +1 -1
- package/dist/webdist/assets/{railroadDiagram-AXF67PYL-CstFYJPW.js → railroadDiagram-AXF67PYL-DPodM5Aj.js} +1 -1
- package/dist/webdist/assets/{refresh-cw-DHnSGytz.js → refresh-cw-ZKW14GBQ.js} +1 -1
- package/dist/webdist/assets/{remote-failure-DQsv9RYM.js → remote-failure-nVuOLb4V.js} +1 -1
- package/dist/webdist/assets/{requirementDiagram-LRYGKXZP-BVS10AjP.js → requirementDiagram-LRYGKXZP-DWCCxYHs.js} +1 -1
- package/dist/webdist/assets/{rocket-0CLawS4F.js → rocket-fYq9X9BT.js} +1 -1
- package/dist/webdist/assets/{rotate-ccw-kHM_pFpt.js → rotate-ccw-g_DuozeG.js} +1 -1
- package/dist/webdist/assets/{sankeyDiagram-W5VNT64P-DWg6hEFJ.js → sankeyDiagram-W5VNT64P-DrI6xZpj.js} +1 -1
- package/dist/webdist/assets/{sequenceDiagram-SI44F4Z6-CPe4ECQK.js → sequenceDiagram-SI44F4Z6-BrlRaYxD.js} +1 -1
- package/dist/webdist/assets/{sizeCapture-X5ZJPWSS-tYK0sN8k.js → sizeCapture-X5ZJPWSS-BWkHzhyu.js} +1 -1
- package/dist/webdist/assets/{skeleton-1pxWnguF.js → skeleton-Bv8b9m3J.js} +1 -1
- package/dist/webdist/assets/{sliders-horizontal-CJnL4ema.js → sliders-horizontal-ePjH5qK0.js} +1 -1
- package/dist/webdist/assets/{sparkles-CtVbnpZt.js → sparkles-CshkhEsq.js} +1 -1
- package/dist/webdist/assets/{star-BWg7n83j.js → star-ENKV_riY.js} +1 -1
- package/dist/webdist/assets/{stateDiagram-OKZ733FA-DdMFYbZ9.js → stateDiagram-OKZ733FA-T_6O5IHv.js} +1 -1
- package/dist/webdist/assets/stateDiagram-v2-UEYNNEHI-97ixhjGa.js +1 -0
- package/dist/webdist/assets/{swimlanes-SLNWSIFB-DgWqKJOe.js → swimlanes-SLNWSIFB-Bmme-2xD.js} +2 -2
- package/dist/webdist/assets/swimlanesDiagram-ULZ7WXOC-BxJRyIBv.js +8 -0
- package/dist/webdist/assets/{switch-ChtEKTkl.js → switch-BtGEUEC2.js} +1 -1
- package/dist/webdist/assets/{terminal-BksEQh4H.js → terminal-C_QOTYmG.js} +1 -1
- package/dist/webdist/assets/{timeline-definition-Z64GVDOM-CrwOPdAi.js → timeline-definition-Z64GVDOM-B3fBfREF.js} +1 -1
- package/dist/webdist/assets/{trash-2-BXZkODa0.js → trash-2-B__gvkzD.js} +1 -1
- package/dist/webdist/assets/{triangle-alert-tsQmbLn8.js → triangle-alert-BbhRFFB2.js} +1 -1
- package/dist/webdist/assets/{useIsDesktop-BKLpQjiw.js → useIsDesktop-Do2taDTq.js} +1 -1
- package/dist/webdist/assets/{vennDiagram-T6HMQDX7-B7_yA6Pn.js → vennDiagram-T6HMQDX7-DnVI7ph_.js} +1 -1
- package/dist/webdist/assets/{wardleyDiagram-T6FBY63Y-CImMUYg_.js → wardleyDiagram-T6FBY63Y-CY4YnfZL.js} +1 -1
- package/dist/webdist/assets/{wrench-CuSXx_Zn.js → wrench-WKNRyiMt.js} +1 -1
- package/dist/webdist/assets/{xychartDiagram-ELKLHX3M-B2wGD83d.js → xychartDiagram-ELKLHX3M-DgqvW4os.js} +1 -1
- package/dist/webdist/index.html +1 -1
- package/package.json +1 -1
- package/dist/webdist/assets/EnginesScreen-BWRix4d7.js +0 -7
- package/dist/webdist/assets/WorkspaceScreen-RLGQrw5_.js +0 -1
- package/dist/webdist/assets/channel-BoFujHnv.js +0 -1
- package/dist/webdist/assets/classDiagram-JCYQIIEL-CWuM4mUX.js +0 -1
- package/dist/webdist/assets/classDiagram-v2-OCEON4UE-CWuM4mUX.js +0 -1
- package/dist/webdist/assets/stateDiagram-v2-UEYNNEHI-Pg2RCR6-.js +0 -1
- package/dist/webdist/assets/swimlanesDiagram-ULZ7WXOC-D-HgKzxH.js +0 -8
package/README.md
CHANGED
|
@@ -735,6 +735,23 @@ access your GPU directly for accelerated local LLM inference.
|
|
|
735
735
|
> [GitHub repository](https://github.com/mohitsoni48/TurboLLM), not in the npm package —
|
|
736
736
|
> clone the repo to use them.
|
|
737
737
|
|
|
738
|
+
### Getting your API key
|
|
739
|
+
|
|
740
|
+
The container publishes port 6996 on `0.0.0.0`, so TurboLLM's daemon requires an API key
|
|
741
|
+
for every request the moment it starts — there is no browser inside the container to log
|
|
742
|
+
in with. Both images launch with **`--print-token`**, which mints a fresh key and prints
|
|
743
|
+
it on every container start (not just the first), so it's always one command away:
|
|
744
|
+
|
|
745
|
+
```bash
|
|
746
|
+
docker compose -f docker-compose-nvidia.yaml logs | grep 'Token:' # or docker-compose-amd.yaml
|
|
747
|
+
```
|
|
748
|
+
|
|
749
|
+
Grab the value after `Token:` and either paste it into the web UI's login prompt, or send
|
|
750
|
+
it yourself as `X-TurboLLM-Auth: <key>` (or `Authorization: Bearer <key>`). Each restart
|
|
751
|
+
mints a new one, so re-run the command above after a restart rather than reusing an old
|
|
752
|
+
value — old keys keep working too and can be revoked from Developer → API Keys once
|
|
753
|
+
you're in.
|
|
754
|
+
|
|
738
755
|
---
|
|
739
756
|
|
|
740
757
|
### NVIDIA GPU (CUDA)
|
package/dist/cli.js
CHANGED
|
@@ -2713,6 +2713,49 @@ function buildEnv(toolchainDirs = []) {
|
|
|
2713
2713
|
if (env[key] !== void 0 || dirs.length > 0) env[key] = segments.join(delimiter);
|
|
2714
2714
|
return env;
|
|
2715
2715
|
}
|
|
2716
|
+
var LINUX_PACKAGE_MANAGERS = ["apt-get", "dnf", "pacman", "zypper"];
|
|
2717
|
+
var PACKAGES = {
|
|
2718
|
+
"apt-get": { git: ["git"], cmake: ["cmake"], gcc: ["build-essential"], cuda: ["nvidia-cuda-toolkit"] },
|
|
2719
|
+
dnf: { git: ["git"], cmake: ["cmake"], gcc: ["gcc-c++", "make"] },
|
|
2720
|
+
pacman: { git: ["git"], cmake: ["cmake"], gcc: ["base-devel"], cuda: ["cuda"] },
|
|
2721
|
+
zypper: { git: ["git"], cmake: ["cmake"], gcc: ["gcc-c++", "make"] },
|
|
2722
|
+
// macOS: the compiler is the Xcode Command Line Tools (`xcode-select --install` opens a GUI
|
|
2723
|
+
// prompt, so it isn't something to run for the user headlessly) and there is no CUDA at all.
|
|
2724
|
+
brew: { git: ["git"], cmake: ["cmake"] }
|
|
2725
|
+
};
|
|
2726
|
+
function privilegePrefix(asRoot) {
|
|
2727
|
+
return asRoot ? [] : ["sudo", "-n"];
|
|
2728
|
+
}
|
|
2729
|
+
function prereqInstallCommands(pm, id, asRoot) {
|
|
2730
|
+
const pkgs = PACKAGES[pm][id];
|
|
2731
|
+
if (!pkgs || pkgs.length === 0) return void 0;
|
|
2732
|
+
if (pm === "brew") return [["brew", "install", ...pkgs]];
|
|
2733
|
+
const sudo = privilegePrefix(asRoot);
|
|
2734
|
+
switch (pm) {
|
|
2735
|
+
case "apt-get":
|
|
2736
|
+
return [[...sudo, "apt-get", "update"], [...sudo, "apt-get", "install", "-y", ...pkgs]];
|
|
2737
|
+
case "dnf":
|
|
2738
|
+
return [[...sudo, "dnf", "install", "-y", ...pkgs]];
|
|
2739
|
+
case "pacman":
|
|
2740
|
+
return [[...sudo, "pacman", "-Sy", "--noconfirm", ...pkgs]];
|
|
2741
|
+
case "zypper":
|
|
2742
|
+
return [[...sudo, "zypper", "--non-interactive", "install", ...pkgs]];
|
|
2743
|
+
}
|
|
2744
|
+
}
|
|
2745
|
+
function runningAsRoot() {
|
|
2746
|
+
return typeof process.getuid === "function" && process.getuid() === 0;
|
|
2747
|
+
}
|
|
2748
|
+
async function detectPackageManager(env) {
|
|
2749
|
+
const candidates = process.platform === "linux" ? LINUX_PACKAGE_MANAGERS : process.platform === "darwin" ? ["brew"] : [];
|
|
2750
|
+
for (const pm of candidates) {
|
|
2751
|
+
try {
|
|
2752
|
+
await runVersion(pm, ["--version"], env);
|
|
2753
|
+
return pm;
|
|
2754
|
+
} catch {
|
|
2755
|
+
}
|
|
2756
|
+
}
|
|
2757
|
+
return null;
|
|
2758
|
+
}
|
|
2716
2759
|
var INSTALL_URLS = {
|
|
2717
2760
|
git: "https://git-scm.com/downloads",
|
|
2718
2761
|
cmake: "https://cmake.org/download/",
|
|
@@ -2826,15 +2869,28 @@ async function checkGcc(env) {
|
|
|
2826
2869
|
async function checkBuildPrereqs(toolchainDirs = []) {
|
|
2827
2870
|
const platform = process.platform;
|
|
2828
2871
|
if (platform !== "win32" && platform !== "linux" && platform !== "darwin") {
|
|
2829
|
-
return { supported: false, os: "other", tools: [] };
|
|
2872
|
+
return { supported: false, os: "other", tools: [], packageManager: null };
|
|
2830
2873
|
}
|
|
2831
2874
|
const env = buildEnv(toolchainDirs);
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2875
|
+
const [tools, packageManager] = await Promise.all([
|
|
2876
|
+
platform === "darwin" ? Promise.all([checkGit(env), checkCmake(env), checkGcc(env)]) : platform === "win32" ? Promise.all([checkGit(env), checkCmake(env), checkCuda(env), checkMsvc(env)]) : Promise.all([checkGit(env), checkCmake(env), checkCuda(env), checkGcc(env)]),
|
|
2877
|
+
detectPackageManager(env)
|
|
2878
|
+
]);
|
|
2879
|
+
return {
|
|
2880
|
+
supported: true,
|
|
2881
|
+
os: platform === "win32" ? "windows" : platform === "darwin" ? "macos" : "linux",
|
|
2882
|
+
tools: withInstallCommands(tools, packageManager),
|
|
2883
|
+
packageManager
|
|
2884
|
+
};
|
|
2885
|
+
}
|
|
2886
|
+
function withInstallCommands(tools, pm) {
|
|
2887
|
+
if (!pm) return tools;
|
|
2888
|
+
const asRoot = runningAsRoot();
|
|
2889
|
+
return tools.map((t) => {
|
|
2890
|
+
if (t.found) return t;
|
|
2891
|
+
const installCommands = prereqInstallCommands(pm, t.id, asRoot);
|
|
2892
|
+
return installCommands ? { ...t, installCommands } : t;
|
|
2893
|
+
});
|
|
2838
2894
|
}
|
|
2839
2895
|
var CMAKE_CONFIGURE_ARGS = ["-DGGML_CUDA=ON", "-DCMAKE_BUILD_TYPE=Release", "-DCMAKE_CUDA_FLAGS=-allow-unsupported-compiler"];
|
|
2840
2896
|
var CMAKE_CONFIGURE_ARGS_MACOS = ["-DGGML_METAL=ON", "-DCMAKE_BUILD_TYPE=Release"];
|
|
@@ -2903,6 +2959,20 @@ function suggestEngineName(binPath, version2) {
|
|
|
2903
2959
|
|
|
2904
2960
|
// src/engines/build-runner.ts
|
|
2905
2961
|
var execFileP8 = promisify8(execFile10);
|
|
2962
|
+
async function findLibcudaDir(signal) {
|
|
2963
|
+
try {
|
|
2964
|
+
const { stdout } = await execFileP8("ldconfig", ["-p"], {
|
|
2965
|
+
timeout: 5e3,
|
|
2966
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
2967
|
+
signal
|
|
2968
|
+
});
|
|
2969
|
+
const line = stdout.split("\n").find((l) => /\blibcuda\.so\b/.test(l));
|
|
2970
|
+
const m = line ? line.match(/=>\s*(\S+)/) : null;
|
|
2971
|
+
return m ? dirname5(m[1]) : null;
|
|
2972
|
+
} catch {
|
|
2973
|
+
return null;
|
|
2974
|
+
}
|
|
2975
|
+
}
|
|
2906
2976
|
function chooseEngineName(name, priorName) {
|
|
2907
2977
|
return name ?? priorName ?? "";
|
|
2908
2978
|
}
|
|
@@ -3194,6 +3264,36 @@ function neutralizeGratuitousAsm(srcDir, log) {
|
|
|
3194
3264
|
);
|
|
3195
3265
|
}
|
|
3196
3266
|
}
|
|
3267
|
+
function noInstallCommandError(tool, packageManager) {
|
|
3268
|
+
if ((tool.installCommands?.length ?? 0) > 0) return null;
|
|
3269
|
+
return (packageManager ? `TurboLLM doesn't have a one-command ${packageManager} install for ${tool.name} on this system. ` : `No supported package manager (apt-get, dnf, pacman, zypper, or Homebrew) was found on this system. `) + `Install ${tool.name} yourself \u2014 see ${tool.installUrl} \u2014 then re-check.`;
|
|
3270
|
+
}
|
|
3271
|
+
async function runPrereqInstall(req, hooks, signal) {
|
|
3272
|
+
hooks.phase("provisioning");
|
|
3273
|
+
const prereqs = await checkBuildPrereqs(req.toolchainDirs);
|
|
3274
|
+
if (!prereqs.supported) throw new Error("Installing build prerequisites is currently Linux and macOS only.");
|
|
3275
|
+
const tool = prereqs.tools.find((t) => t.id === req.tool);
|
|
3276
|
+
if (!tool) throw new Error(`"${req.tool}" isn't a build prerequisite on this system.`);
|
|
3277
|
+
if (tool.found) {
|
|
3278
|
+
hooks.log(`${tool.name} is already installed${tool.version ? ` (${tool.version})` : ""} \u2014 nothing to do.`);
|
|
3279
|
+
return;
|
|
3280
|
+
}
|
|
3281
|
+
const noCommand = noInstallCommandError(tool, prereqs.packageManager);
|
|
3282
|
+
if (noCommand) throw new Error(noCommand);
|
|
3283
|
+
const env = { ...buildEnv(req.toolchainDirs), DEBIAN_FRONTEND: "noninteractive" };
|
|
3284
|
+
for (const [cmd, ...args] of tool.installCommands) {
|
|
3285
|
+
hooks.log(`$ ${[cmd, ...args].join(" ")}`);
|
|
3286
|
+
await runStep(cmd, args, { env, signal, onLine: hooks.log });
|
|
3287
|
+
}
|
|
3288
|
+
const after = await checkBuildPrereqs(req.toolchainDirs);
|
|
3289
|
+
const now = after.tools.find((t) => t.id === req.tool);
|
|
3290
|
+
if (!now?.found) {
|
|
3291
|
+
throw new Error(
|
|
3292
|
+
`The install commands ran, but ${tool.name} still isn't detected. Install it yourself \u2014 see ${tool.installUrl} \u2014 or, if it landed somewhere off PATH, add that folder under Build environment and re-check.`
|
|
3293
|
+
);
|
|
3294
|
+
}
|
|
3295
|
+
hooks.log(`${now.name}${now.version ? ` ${now.version}` : ""} installed \u2713`);
|
|
3296
|
+
}
|
|
3197
3297
|
async function runBuild(req, hooks, signal) {
|
|
3198
3298
|
const isWindows = process.platform === "win32";
|
|
3199
3299
|
const isMac = process.platform === "darwin";
|
|
@@ -3300,9 +3400,17 @@ async function runBuild(req, hooks, signal) {
|
|
|
3300
3400
|
await runStep("cmake", compileArgs, { cwd: buildRoot, env, signal, onLine: captureLog });
|
|
3301
3401
|
}
|
|
3302
3402
|
};
|
|
3403
|
+
let linuxCudaArgs = CMAKE_CONFIGURE_ARGS;
|
|
3404
|
+
if (!isWindows && !isMac) {
|
|
3405
|
+
const libcudaDir = await findLibcudaDir(signal);
|
|
3406
|
+
if (libcudaDir) {
|
|
3407
|
+
hooks.log(`libcuda.so found outside the CUDA Toolkit path \u2014 adding CMAKE_LIBRARY_PATH=${libcudaDir}`);
|
|
3408
|
+
linuxCudaArgs = [...CMAKE_CONFIGURE_ARGS, `-DCMAKE_LIBRARY_PATH=${libcudaDir}`];
|
|
3409
|
+
}
|
|
3410
|
+
}
|
|
3303
3411
|
const buildLog = [];
|
|
3304
3412
|
try {
|
|
3305
|
-
await configureAndCompile(isMac ? CMAKE_CONFIGURE_ARGS_MACOS :
|
|
3413
|
+
await configureAndCompile(isMac ? CMAKE_CONFIGURE_ARGS_MACOS : linuxCudaArgs, buildLog);
|
|
3306
3414
|
} catch (e) {
|
|
3307
3415
|
if (!isMac || !isIncompleteMetalBackendError(buildLog)) throw e;
|
|
3308
3416
|
hooks.log(
|
|
@@ -4650,6 +4758,27 @@ function getLanIp() {
|
|
|
4650
4758
|
const real = candidates.find((c) => !VIRTUAL_ADAPTER_NAME.test(c.name));
|
|
4651
4759
|
return (real ?? candidates[0])?.address ?? null;
|
|
4652
4760
|
}
|
|
4761
|
+
function getAdvertisedHost() {
|
|
4762
|
+
const raw = process.env.TURBOLLM_ADVERTISED_HOST?.trim();
|
|
4763
|
+
if (!raw) return null;
|
|
4764
|
+
let v = raw.replace(/^[a-z][a-z0-9+.-]*:\/\//i, "");
|
|
4765
|
+
const slash = v.indexOf("/");
|
|
4766
|
+
if (slash >= 0) v = v.slice(0, slash);
|
|
4767
|
+
v = v.trim();
|
|
4768
|
+
if (!v) return null;
|
|
4769
|
+
const bracketed = /^(\[[^\]]+\])(?::(\d{1,5}))?$/.exec(v);
|
|
4770
|
+
if (bracketed) {
|
|
4771
|
+
const port2 = bracketed[2] ? Number(bracketed[2]) : null;
|
|
4772
|
+
return { host: bracketed[1], port: port2 && port2 > 0 && port2 <= 65535 ? port2 : null };
|
|
4773
|
+
}
|
|
4774
|
+
if (v.indexOf(":") !== v.lastIndexOf(":")) return { host: `[${v}]`, port: null };
|
|
4775
|
+
const withPort = /^(.+):(\d{1,5})$/.exec(v);
|
|
4776
|
+
if (withPort) {
|
|
4777
|
+
const port2 = Number(withPort[2]);
|
|
4778
|
+
if (port2 > 0 && port2 <= 65535) return { host: withPort[1], port: port2 };
|
|
4779
|
+
}
|
|
4780
|
+
return { host: v, port: null };
|
|
4781
|
+
}
|
|
4653
4782
|
|
|
4654
4783
|
// src/chat/parser.ts
|
|
4655
4784
|
var THINK_OPEN = "<think>";
|
|
@@ -5303,6 +5432,7 @@ function buildUpstream(remote, path) {
|
|
|
5303
5432
|
return `${base2}${FACADE_PREFIX}${suffix}`;
|
|
5304
5433
|
}
|
|
5305
5434
|
var FORWARDED = ["content-type", "accept"];
|
|
5435
|
+
var LINK_AUTH_HEADER = "X-TurboLLM-Auth";
|
|
5306
5436
|
function linkHeaders(remote, incoming) {
|
|
5307
5437
|
const out = new Headers();
|
|
5308
5438
|
if (incoming) {
|
|
@@ -5311,7 +5441,7 @@ function linkHeaders(remote, incoming) {
|
|
|
5311
5441
|
if (v) out.set(name, v);
|
|
5312
5442
|
}
|
|
5313
5443
|
}
|
|
5314
|
-
out.set(
|
|
5444
|
+
out.set(LINK_AUTH_HEADER, remote.token);
|
|
5315
5445
|
return out;
|
|
5316
5446
|
}
|
|
5317
5447
|
async function proxyStream(remote, path, init, signal, fetchImpl = fetch) {
|
|
@@ -5496,11 +5626,11 @@ function generateApiKey() {
|
|
|
5496
5626
|
const hash = createHash3("sha256").update(full).digest("hex");
|
|
5497
5627
|
return { full, hash, prefix: full.slice(0, 12) };
|
|
5498
5628
|
}
|
|
5499
|
-
function provisionTunnelApiKey(d) {
|
|
5629
|
+
function provisionTunnelApiKey(d, label = "tunnel") {
|
|
5500
5630
|
const { full, hash, prefix } = generateApiKey();
|
|
5501
5631
|
const key = {
|
|
5502
5632
|
id: randomUUID3(),
|
|
5503
|
-
name:
|
|
5633
|
+
name: `${label}-${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
5504
5634
|
hash,
|
|
5505
5635
|
prefix,
|
|
5506
5636
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -5509,6 +5639,28 @@ function provisionTunnelApiKey(d) {
|
|
|
5509
5639
|
d.store.update((cfg2) => cfg2.apiKeys.push(key));
|
|
5510
5640
|
return full;
|
|
5511
5641
|
}
|
|
5642
|
+
function isLoopbackBindHost(host2) {
|
|
5643
|
+
const h = host2.trim().toLowerCase().replace(/^\[|\]$/g, "");
|
|
5644
|
+
if (h === "localhost" || h === "::1" || h === "::ffff:127.0.0.1") return true;
|
|
5645
|
+
return /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(h);
|
|
5646
|
+
}
|
|
5647
|
+
function provisionBootstrapApiKey(d, bindHost) {
|
|
5648
|
+
if (isLoopbackBindHost(bindHost)) return null;
|
|
5649
|
+
const cfg2 = d.store.snapshot();
|
|
5650
|
+
if (cfg2.daemon.requireApiKey !== true) return null;
|
|
5651
|
+
if (cfg2.apiKeys.length > 0) return null;
|
|
5652
|
+
const { full, hash, prefix } = generateApiKey();
|
|
5653
|
+
const key = {
|
|
5654
|
+
id: randomUUID3(),
|
|
5655
|
+
name: `bootstrap-${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
5656
|
+
hash,
|
|
5657
|
+
prefix,
|
|
5658
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5659
|
+
lastUsedAt: null
|
|
5660
|
+
};
|
|
5661
|
+
d.store.update((mut) => mut.apiKeys.push(key));
|
|
5662
|
+
return full;
|
|
5663
|
+
}
|
|
5512
5664
|
function presentedKey(c) {
|
|
5513
5665
|
const direct = c.req.header("X-TurboLLM-Auth") ?? c.req.header("x-api-key");
|
|
5514
5666
|
if (direct && direct.trim()) return direct.trim();
|
|
@@ -10099,10 +10251,9 @@ ${m.content}` : m.content;
|
|
|
10099
10251
|
const conv = db2.getConversation(convId);
|
|
10100
10252
|
if (!conv) return err3(c, 404, "not_found", "Conversation not found.");
|
|
10101
10253
|
const cfg2 = d.store.snapshot();
|
|
10102
|
-
const
|
|
10103
|
-
const url = `http://${
|
|
10104
|
-
|
|
10105
|
-
return c.json({ url, onlyLocal });
|
|
10254
|
+
const share = getShareTarget(cfg2.daemon.port);
|
|
10255
|
+
const url = `http://${share.host}:${share.port}/chat/${convId}`;
|
|
10256
|
+
return c.json({ url, onlyLocal: share.onlyLocal });
|
|
10106
10257
|
});
|
|
10107
10258
|
app2.post("/api/v1/conversations/import", async (c) => {
|
|
10108
10259
|
let raw;
|
|
@@ -10188,8 +10339,11 @@ function importOpenAiMessages(c, db2, messages, titleField, modelField) {
|
|
|
10188
10339
|
}
|
|
10189
10340
|
return c.json({ id: newConv.id }, 201);
|
|
10190
10341
|
}
|
|
10191
|
-
function
|
|
10192
|
-
|
|
10342
|
+
function getShareTarget(defaultPort) {
|
|
10343
|
+
const advertised = getAdvertisedHost();
|
|
10344
|
+
if (advertised) return { host: advertised.host, port: advertised.port ?? defaultPort, onlyLocal: false };
|
|
10345
|
+
const host2 = getLanIp() ?? "127.0.0.1";
|
|
10346
|
+
return { host: host2, port: defaultPort, onlyLocal: host2 === "127.0.0.1" };
|
|
10193
10347
|
}
|
|
10194
10348
|
function reportChatBenchResult(d, ms, stats) {
|
|
10195
10349
|
try {
|
|
@@ -11073,6 +11227,7 @@ Cannot execute bash commands.`);
|
|
|
11073
11227
|
|
|
11074
11228
|
// src/code/code-upstream.ts
|
|
11075
11229
|
var FALLBACK_CTX = 8192;
|
|
11230
|
+
var LINK_PLACEHOLDER_KEY = "turbo-link";
|
|
11076
11231
|
function resolveCodeUpstream(d, requestedModel) {
|
|
11077
11232
|
const res = resolveChatUpstream(d, requestedModel);
|
|
11078
11233
|
if (!res.ok) throw new Error(res.code === "model_not_loaded" ? "model_not_loaded" : res.message);
|
|
@@ -11086,14 +11241,34 @@ function resolveCodeUpstream(d, requestedModel) {
|
|
|
11086
11241
|
remote: u.remote,
|
|
11087
11242
|
target: "",
|
|
11088
11243
|
provider: {
|
|
11089
|
-
|
|
11090
|
-
|
|
11244
|
+
// THE FAÇADE, not the host's public `/v1` mount. pi's openai client appends
|
|
11245
|
+
// `/chat/completions` to this, so `<base>/v1` addressed `<host>/v1/chat/completions`
|
|
11246
|
+
// — the public gateway — where a link token is refused outright: it carries a
|
|
11247
|
+
// `grant`, `isFacadeOnlyKey` makes `verifyKeyValue` treat it as no match at all, and
|
|
11248
|
+
// lanAuth 401s in the LAN-open/key-required configuration every Turbo Link host runs
|
|
11249
|
+
// in (pinned by link-auth.test.ts, "a GRANTED link token is refused by lanAuth on the
|
|
11250
|
+
// public /v1/* gateway"). That is precisely why chat over a link worked while Code
|
|
11251
|
+
// did not: chat's transport is `proxyStream`, which derives the URL from
|
|
11252
|
+
// `buildUpstream`; Code was the one caller that spelled a link URL by hand.
|
|
11253
|
+
//
|
|
11254
|
+
// Built with `buildUpstream` rather than a second literal so there stays exactly ONE
|
|
11255
|
+
// place that knows how a link URL is spelled — which also normalises the trailing
|
|
11256
|
+
// slash a tunnel URL pasted from a browser address bar routinely carries (`https://h/`
|
|
11257
|
+
// → `https://h//v1/...`, a 404 on some proxies).
|
|
11258
|
+
//
|
|
11259
|
+
// Reaching the façade is not merely cosmetic: the public mount would bypass
|
|
11260
|
+
// `requireCapability('models:use')`, the model allowlist, the wake gate and the
|
|
11261
|
+
// link-chaining refusal — so on a host running LAN-open WITHOUT a required key, the
|
|
11262
|
+
// old URL would have silently *worked* while ignoring the entire capability grant.
|
|
11263
|
+
baseUrl: buildUpstream(u.remote, "/v1"),
|
|
11264
|
+
apiKey: LINK_PLACEHOLDER_KEY,
|
|
11091
11265
|
// The link token travels as X-TurboLLM-Auth (linkHeaders, link-proxy.ts) and NOT as
|
|
11092
11266
|
// a bearer: `Authorization` on the host is its own API-key credential, a different
|
|
11093
11267
|
// secret with different scope, and presenting a link token there would either be
|
|
11094
|
-
// rejected or — worse — accepted as something it is not.
|
|
11268
|
+
// rejected or — worse — accepted as something it is not. `authHeader: false` stops
|
|
11269
|
+
// pi adding one; `LINK_PLACEHOLDER_KEY` stops the openai SDK adding one from apiKey.
|
|
11095
11270
|
authHeader: false,
|
|
11096
|
-
headers: {
|
|
11271
|
+
headers: { [LINK_AUTH_HEADER]: u.remote.token }
|
|
11097
11272
|
}
|
|
11098
11273
|
};
|
|
11099
11274
|
}
|
|
@@ -18178,6 +18353,9 @@ var UI_ACTIONS = [
|
|
|
18178
18353
|
"dismiss_build_success",
|
|
18179
18354
|
"cancel_engine_build",
|
|
18180
18355
|
"download_cuda_toolkit",
|
|
18356
|
+
// Install a missing build prereq with the host's own package manager (the headless fix:
|
|
18357
|
+
// the install LINK opens a page in the operator's browser, not on the daemon machine).
|
|
18358
|
+
"install_build_prereq",
|
|
18181
18359
|
"toggle_agent_tool_group",
|
|
18182
18360
|
"back_to_agents",
|
|
18183
18361
|
"reset_agent_to_default",
|
|
@@ -24653,8 +24831,8 @@ function registerApi(app2, d) {
|
|
|
24653
24831
|
async (c) => c.json(await checkBuildPrereqs(d.store.snapshot().build.toolchainDirs))
|
|
24654
24832
|
);
|
|
24655
24833
|
app2.post("/api/v1/build/run", async (c) => {
|
|
24656
|
-
if (!
|
|
24657
|
-
return err5(c, 403, "forbidden", "Building an engine
|
|
24834
|
+
if (!isLocalOrAuthenticated(c, d))
|
|
24835
|
+
return err5(c, 403, "forbidden", "Building an engine requires a valid API key from a non-host device.");
|
|
24658
24836
|
if (process.platform !== "win32" && process.platform !== "linux" && process.platform !== "darwin")
|
|
24659
24837
|
return err5(c, 409, "unsupported_platform", "In-app build is currently Windows, Linux, or macOS only.");
|
|
24660
24838
|
const b = await body4(c);
|
|
@@ -24748,9 +24926,42 @@ function registerApi(app2, d) {
|
|
|
24748
24926
|
}
|
|
24749
24927
|
return c.json({ ok: false });
|
|
24750
24928
|
});
|
|
24929
|
+
app2.post("/api/v1/build/install-prereq", async (c) => {
|
|
24930
|
+
if (!isLocalOrAuthenticated(c, d))
|
|
24931
|
+
return err5(c, 403, "forbidden", "Installing build prerequisites requires a valid API key from a non-host device.");
|
|
24932
|
+
if (process.platform !== "linux" && process.platform !== "darwin")
|
|
24933
|
+
return err5(c, 409, "unsupported_platform", "Installing build prerequisites is currently Linux and macOS only \u2014 on Windows these tools need their own installers.");
|
|
24934
|
+
const b = await body4(c);
|
|
24935
|
+
const tool = (b.tool ?? "").trim();
|
|
24936
|
+
if (tool !== "git" && tool !== "cmake" && tool !== "cuda" && tool !== "gcc")
|
|
24937
|
+
return err5(c, 400, "invalid_config_value", "tool must be one of: git, cmake, cuda, gcc.");
|
|
24938
|
+
const busy = engineWorkBusy(d);
|
|
24939
|
+
if (busy) return err5(c, 409, "engine_already_running", busy);
|
|
24940
|
+
const toolchainDirs = d.store.snapshot().build.toolchainDirs;
|
|
24941
|
+
const ac = new AbortController();
|
|
24942
|
+
buildAbort = ac;
|
|
24943
|
+
d.build.start(tool);
|
|
24944
|
+
d.build.phase("provisioning");
|
|
24945
|
+
void (async () => {
|
|
24946
|
+
try {
|
|
24947
|
+
await runPrereqInstall({ tool, toolchainDirs }, { phase: (p) => d.build.phase(p), log: (line) => d.build.log(line) }, ac.signal);
|
|
24948
|
+
d.build.done();
|
|
24949
|
+
} catch (e) {
|
|
24950
|
+
if (e?.name === "AbortError") {
|
|
24951
|
+
d.build.log("Install cancelled.");
|
|
24952
|
+
d.build.cancel("Install cancelled.");
|
|
24953
|
+
} else {
|
|
24954
|
+
d.build.fail(`Could not install ${tool}: ${e instanceof Error ? e.message : String(e)}`);
|
|
24955
|
+
}
|
|
24956
|
+
} finally {
|
|
24957
|
+
if (buildAbort === ac) buildAbort = null;
|
|
24958
|
+
}
|
|
24959
|
+
})();
|
|
24960
|
+
return c.json({ accepted: true }, 202);
|
|
24961
|
+
});
|
|
24751
24962
|
app2.post("/api/v1/build/cuda", async (c) => {
|
|
24752
|
-
if (!
|
|
24753
|
-
return err5(c, 403, "forbidden", "Downloading CUDA
|
|
24963
|
+
if (!isLocalOrAuthenticated(c, d))
|
|
24964
|
+
return err5(c, 403, "forbidden", "Downloading CUDA requires a valid API key from a non-host device.");
|
|
24754
24965
|
if (process.platform !== "win32")
|
|
24755
24966
|
return err5(c, 409, "unsupported_platform", "Automatic CUDA download is currently Windows x86_64 only.");
|
|
24756
24967
|
const busy = engineWorkBusy(d);
|
|
@@ -24922,8 +25133,8 @@ function registerApi(app2, d) {
|
|
|
24922
25133
|
return c.json({ accepted: true, engine: "llamafile" }, 202);
|
|
24923
25134
|
});
|
|
24924
25135
|
app2.post("/api/v1/engines", async (c) => {
|
|
24925
|
-
if (!
|
|
24926
|
-
return err5(c, 403, "forbidden", "
|
|
25136
|
+
if (!isLocalOrAuthenticated(c, d))
|
|
25137
|
+
return err5(c, 403, "forbidden", "Adding an engine requires a valid API key from a non-host device.");
|
|
24927
25138
|
const b = await body4(c);
|
|
24928
25139
|
if (!b.binPath || !b.binPath.trim()) return err5(c, 400, "invalid_config_value", "binPath is required.");
|
|
24929
25140
|
try {
|
|
@@ -24943,8 +25154,8 @@ function registerApi(app2, d) {
|
|
|
24943
25154
|
}
|
|
24944
25155
|
});
|
|
24945
25156
|
app2.post("/api/v1/engines/scan", async (c) => {
|
|
24946
|
-
if (!
|
|
24947
|
-
return err5(c, 403, "forbidden", "Engine scanning
|
|
25157
|
+
if (!isLocalOrAuthenticated(c, d))
|
|
25158
|
+
return err5(c, 403, "forbidden", "Engine scanning requires a valid API key from a non-host device.");
|
|
24948
25159
|
const b = await body4(c);
|
|
24949
25160
|
const path = (b.path ?? "").trim();
|
|
24950
25161
|
if (!path) return err5(c, 400, "invalid_config_value", "path is required.");
|
|
@@ -26905,7 +27116,7 @@ function registerLinkAdminRoutes(app2, d, opts) {
|
|
|
26905
27116
|
const cfg2 = d.store.snapshot();
|
|
26906
27117
|
const port2 = cfg2.daemon.port ?? 6996;
|
|
26907
27118
|
const tunnelUrl = d.tunnel?.url() ?? null;
|
|
26908
|
-
const baseUrl = tunnelUrl ??
|
|
27119
|
+
const baseUrl = tunnelUrl ?? lanBaseUrl(port2);
|
|
26909
27120
|
if (d.telemetry) {
|
|
26910
27121
|
const preset = typeof body10?.preset === "string" && Object.prototype.hasOwnProperty.call(LINK_PRESETS, body10.preset) ? body10.preset : void 0;
|
|
26911
27122
|
emit(d.telemetry, linkMinted, { capabilityCount: caps.length, ...preset ? { preset } : {} });
|
|
@@ -27239,8 +27450,10 @@ function remoteFailure(c, rec, probe2) {
|
|
|
27239
27450
|
status
|
|
27240
27451
|
);
|
|
27241
27452
|
}
|
|
27242
|
-
function
|
|
27243
|
-
|
|
27453
|
+
function lanBaseUrl(port2) {
|
|
27454
|
+
const advertised = getAdvertisedHost();
|
|
27455
|
+
if (advertised) return `http://${advertised.host}:${advertised.port ?? port2}`;
|
|
27456
|
+
return `http://${getLanIp() ?? "localhost"}:${port2}`;
|
|
27244
27457
|
}
|
|
27245
27458
|
|
|
27246
27459
|
// src/chat/chat-agent-routes.ts
|
|
@@ -33830,10 +34043,18 @@ Commands:
|
|
|
33830
34043
|
Options:
|
|
33831
34044
|
--port <n> Port to listen on / connect to (default: 6996)
|
|
33832
34045
|
--addr <h:p> Full host:port override (e.g. 0.0.0.0:6996)
|
|
34046
|
+
Binding a non-loopback address turns on API-key enforcement;
|
|
34047
|
+
if no key exists yet, one is generated and printed on startup.
|
|
33833
34048
|
--no-open Do not open a browser window on startup
|
|
33834
34049
|
--tunnel Expose this daemon on the internet via a cloudflared quick
|
|
33835
34050
|
tunnel (Cloud Launch) \u2014 prints the public URL + a required
|
|
33836
34051
|
access token. For running TurboLLM on a rented cloud GPU box.
|
|
34052
|
+
--print-token Mint a fresh API key and print it as a stable 'Token: <key>'
|
|
34053
|
+
line on every startup, whether or not one already existed \u2014
|
|
34054
|
+
unlike the automatic non-loopback bootstrap key (which only
|
|
34055
|
+
ever mints once, on the very first boot), this always gives
|
|
34056
|
+
you a freshly known-good token to grep out of 'docker logs'
|
|
34057
|
+
after any restart. Default in the official Docker images.
|
|
33837
34058
|
--config <f> Path to a custom config file
|
|
33838
34059
|
--no-telemetry Disable telemetry entirely, whatever this install's saved
|
|
33839
34060
|
setting says (same as TURBOLLM_TELEMETRY=off). For CI,
|
|
@@ -33847,11 +34068,22 @@ Examples:
|
|
|
33847
34068
|
turbollm --no-open # start without opening a browser
|
|
33848
34069
|
turbollm --addr 0.0.0.0:6996 # bind to all interfaces (LAN sharing)
|
|
33849
34070
|
turbollm --tunnel --no-open # run on a rented GPU box, reachable via a public URL
|
|
34071
|
+
turbollm --addr 0.0.0.0:6996 --print-token # LAN/Docker: always print a usable token
|
|
33850
34072
|
turbollm --stop # stop the running daemon
|
|
33851
34073
|
turbollm launch claude # open Claude Code on your loaded model
|
|
33852
34074
|
turbollm launch claude --model qwen3-8b # load qwen3-8b, then launch
|
|
33853
34075
|
turbollm launch opencode # wire opencode to TurboLLM, then launch it
|
|
33854
34076
|
|
|
34077
|
+
Environment:
|
|
34078
|
+
TURBOLLM_ADVERTISED_HOST
|
|
34079
|
+
Host (or host:port) to put in the URLs TurboLLM hands out \u2014
|
|
34080
|
+
Turbo Link strings and chat-share links. Set this when the
|
|
34081
|
+
address others must use isn't one this machine can see, e.g.
|
|
34082
|
+
inside a Docker container, where the only interface found is
|
|
34083
|
+
the container-internal bridge IP (172.17.0.2) and the real
|
|
34084
|
+
address is the published host:port outside.
|
|
34085
|
+
e.g. TURBOLLM_ADVERTISED_HOST=192.168.1.50:6996
|
|
34086
|
+
|
|
33855
34087
|
`
|
|
33856
34088
|
);
|
|
33857
34089
|
process.exit(0);
|
|
@@ -34065,6 +34297,8 @@ if (portFlag) {
|
|
|
34065
34297
|
var lastColon = addr.lastIndexOf(":");
|
|
34066
34298
|
var host = addr.slice(0, lastColon) || "127.0.0.1";
|
|
34067
34299
|
var port = Number(addr.slice(lastColon + 1)) || 6996;
|
|
34300
|
+
var bootstrapKey = provisionBootstrapApiKey(deps, host);
|
|
34301
|
+
var advertisedHost = getAdvertisedHost();
|
|
34068
34302
|
function openBrowser(url) {
|
|
34069
34303
|
let cmd;
|
|
34070
34304
|
let args;
|
|
@@ -34085,6 +34319,7 @@ function openBrowser(url) {
|
|
|
34085
34319
|
});
|
|
34086
34320
|
}
|
|
34087
34321
|
var noOpen = hasFlag("--no-open");
|
|
34322
|
+
var printTokenRequested = hasFlag("--print-token");
|
|
34088
34323
|
var tunnelToken = null;
|
|
34089
34324
|
var server;
|
|
34090
34325
|
var rebinding = false;
|
|
@@ -34105,10 +34340,37 @@ function listen(attempt = 0) {
|
|
|
34105
34340
|
if (host === "0.0.0.0") {
|
|
34106
34341
|
console.log(` Network: http://<your-ip>:${info.port} (LAN)`);
|
|
34107
34342
|
}
|
|
34343
|
+
if (advertisedHost) {
|
|
34344
|
+
console.log(` Advertised: http://${advertisedHost.host}:${advertisedHost.port ?? info.port} (TURBOLLM_ADVERTISED_HOST)`);
|
|
34345
|
+
}
|
|
34108
34346
|
console.log(``);
|
|
34109
34347
|
console.log(` API: ${uiUrl}/api/v1/status`);
|
|
34110
34348
|
console.log(` Stop: Ctrl+C`);
|
|
34111
34349
|
console.log(``);
|
|
34350
|
+
if (bootstrapKey) {
|
|
34351
|
+
console.log(` ============================================================`);
|
|
34352
|
+
console.log(` This daemon is reachable from other machines, so the API`);
|
|
34353
|
+
console.log(` requires a key. One was generated for you just now:`);
|
|
34354
|
+
console.log(``);
|
|
34355
|
+
console.log(` ${bootstrapKey}`);
|
|
34356
|
+
console.log(``);
|
|
34357
|
+
console.log(` SAVE IT NOW \u2014 it is shown once and cannot be recovered.`);
|
|
34358
|
+
console.log(` Send it as 'X-TurboLLM-Auth: <key>' (or 'Authorization:`);
|
|
34359
|
+
console.log(` Bearer <key>'), or paste it into the web UI. Manage or`);
|
|
34360
|
+
console.log(` revoke it later under Developer -> API Keys.`);
|
|
34361
|
+
console.log(` ============================================================`);
|
|
34362
|
+
console.log(``);
|
|
34363
|
+
}
|
|
34364
|
+
if (printTokenRequested) {
|
|
34365
|
+
const printToken = bootstrapKey ?? provisionTunnelApiKey(deps, "print-token");
|
|
34366
|
+
if (!bootstrapKey) {
|
|
34367
|
+
console.log(` --print-token: a fresh API key was generated for you just now.`);
|
|
34368
|
+
console.log(` SAVE IT NOW \u2014 it is shown once and cannot be recovered.`);
|
|
34369
|
+
console.log(``);
|
|
34370
|
+
}
|
|
34371
|
+
console.log(` Token: ${printToken}`);
|
|
34372
|
+
console.log(``);
|
|
34373
|
+
}
|
|
34112
34374
|
if (!noOpen) {
|
|
34113
34375
|
openBrowser(uiUrl);
|
|
34114
34376
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{j as e}from"./radix-B0YBjihE.js";import{b as W,u as $,r as b}from"./vendor-C1I6euDx.js";import{u as G,d as H,e as Y,f as J,g as U,a as z,B as C,t as j,c as q,b as h,A as _}from"./index-C9ZXfFYo.js";import{f as V}from"./chat-api-Bgrb0rnf.js";import{f as X,a as Z}from"./agent-api-BAMDY8-v.js";import{P as B}from"./personas-Dkq9JD72.js";import{C as ee}from"./chevron-left-CxvUI3ck.js";import{R as te}from"./rotate-ccw-kHM_pFpt.js";import{T as se}from"./trash-2-BXZkODa0.js";import{C as ne}from"./chevron-right-CpdnK5S2.js";import"./dropdown-menu-CaecUWTU.js";const ae=()=>({name:"",description:"",systemPrompt:"",skillIds:[],tools:[]});function oe(s){const d=[],x=new Map;for(const n of s){const a=/^mcp__([^_]+(?:_[^_]+)*)__/.exec(n.name);if(!a){d.push(n);continue}const i=a[1],p=/^\[([^\]]+)\]/.exec(n.description),c=p?p[1]:i.replace(/_/g," ");x.has(i)||x.set(i,{key:i,label:c,tools:[]}),x.get(i).tools.push(n)}return{builtin:d,groups:[...x.values()].sort((n,a)=>n.label.localeCompare(a.label))}}function le(s){return s.replace(/^mcp__[^_]+(?:_[^_]+)*__/,"")}function re(s){return s.replace(/^\[[^\]]+\]\s*/,"")}function D({tool:s,checked:d,onToggle:x}){return e.jsxs("label",{className:"flex cursor-pointer items-start gap-2 text-[12px]",children:[e.jsx("input",{type:"checkbox",className:"mt-0.5",checked:d,onChange:x}),e.jsxs("span",{className:"flex flex-col",children:[e.jsx("span",{className:"font-mono text-ink",children:le(s.name)}),s.description&&e.jsx("span",{className:"text-faint",children:re(s.description)})]})]})}function ie({group:s,selected:d,onToggleAll:x,onToggleOne:n}){const[a,i]=b.useState(!1),p=s.tools.map(l=>l.name),c=p.filter(l=>d.has(l)).length,o=c===p.length,g=c>0&&!o,u=b.useRef(null);return b.useEffect(()=>{u.current&&(u.current.indeterminate=g)},[g]),e.jsxs("div",{className:"rounded-md border border-border",children:[e.jsxs("div",{className:"flex items-center gap-2 px-2.5 py-2",children:[e.jsx("input",{ref:u,type:"checkbox",checked:o,onChange:()=>x(p,!o)}),e.jsxs("button",{type:"button",onClick:()=>{j("agents","toggle_agent_tool_group"),i(l=>!l)},className:"flex flex-1 items-center gap-1.5 text-left text-[13px] text-ink",children:[e.jsx(ne,{size:12,className:q("shrink-0 text-faint transition-transform",a&&"rotate-90")}),s.label,e.jsxs("span",{className:"text-[11px] text-faint",children:["(",c,"/",p.length,")"]})]})]}),a&&e.jsx("div",{className:"flex flex-col gap-1.5 border-t border-border px-3 py-2 pl-9",children:s.tools.map(l=>e.jsx(D,{tool:l,checked:d.has(l.name),onToggle:()=>n(l.name)},l.name))})]})}function ce({tools:s,selected:d,onChange:x}){const n=new Set(d),{builtin:a,groups:i}=oe(s),p=o=>x(n.has(o)?d.filter(g=>g!==o):[...d,o]),c=(o,g)=>{const u=d.filter(l=>!o.includes(l));x(g?[...u,...o]:u)};return s.length===0?e.jsx("p",{className:"text-[12px] text-faint",children:"No tools available yet."}):e.jsxs("div",{className:"flex flex-col gap-2",children:[a.length>0&&e.jsx("div",{className:"flex flex-col gap-2 rounded-md border border-border bg-panel-2 p-3",children:a.map(o=>e.jsx(D,{tool:o,checked:n.has(o.name),onToggle:()=>p(o.name)},o.name))}),i.map(o=>e.jsx(ie,{group:o,selected:n,onToggleAll:c,onToggleOne:p},o.key))]})}function je(){const{agentId:s="new"}=W(),d=$(),x=G(),n=s==="new",a=!n&&B.some(t=>t.id===s),i=a?B.find(t=>t.id===s):void 0,p=H(),c=!n&&!a?(p.data??[]).find(t=>t.id===s):void 0,o=Y(),g=J(),u=U(),l=a?g.data?.[s]:void 0,I=z({queryKey:Z.list(),queryFn:X,staleTime:0}),k=I.data??[],w=z({queryKey:["available-tools"],queryFn:V}),N=w.data??[],[m,f]=b.useState(ae()),[S,T]=b.useState(!1),[A,P]=b.useState(!1),[L,R]=b.useState(!1),[E,Q]=b.useState("skills");n&&!S&&(k.length||N.length)&&(f(t=>({...t,skillIds:k.map(r=>r.id),tools:N.map(r=>r.name)})),T(!0)),!n&&!a&&!S&&c&&(f({name:c.name,description:c.description,systemPrompt:c.systemPrompt,skillIds:c.skillIds,tools:c.tools}),T(!0)),a&&!S&&i&&g.isSuccess&&(k.length||N.length)&&(f({name:l?.name??i.name,description:l?.description??i.description,systemPrompt:l?.systemPrompt??i.systemPrompt,skillIds:l?.skillIds??k.map(t=>t.id),tools:l?.tools??N.map(t=>t.name)}),T(!0));const y=()=>{j("agents","back_to_agents"),d("/customize")},O=t=>f(r=>({...r,skillIds:r.skillIds.includes(t)?r.skillIds.filter(v=>v!==t):[...r.skillIds,t]})),F=()=>{if(!m.name.trim()){h.error("Name is required.");return}P(!0);const t={name:m.name.trim(),description:m.description.trim(),systemPrompt:m.systemPrompt,skillIds:m.skillIds,tools:m.tools};if(a){u.save.mutate({id:s,override:t},{onSuccess:()=>{h.success("Agent saved."),y()},onError:v=>{h.error(v instanceof _?v.message:"Could not save agent."),P(!1)}});return}const r={onSuccess:()=>{x.invalidateQueries({queryKey:["chat-agents"]}),h.success(n?"Agent created.":"Agent saved."),y()},onError:v=>{h.error(v instanceof _?v.message:"Could not save agent."),P(!1)}};n?o.add.mutate(t,r):o.update.mutate({id:s,patch:t},r)},K=()=>{u.reset.mutate(s,{onSuccess:()=>{h.success("Reset to default."),i&&f({name:i.name,description:i.description,systemPrompt:i.systemPrompt,skillIds:k.map(t=>t.id),tools:N.map(t=>t.name)})},onError:t=>h.error(t instanceof _?t.message:"Could not reset agent.")})},M=()=>{o.remove.mutate(s,{onSuccess:()=>{h.success("Agent deleted."),y()},onError:t=>h.error(t instanceof _?t.message:"Could not delete agent.")})};return!n&&!a&&p.isSuccess&&!c?e.jsxs("div",{className:"flex w-full flex-col items-center gap-3 px-8 py-16",children:[e.jsx("p",{className:"text-[14px] text-muted",children:"That agent no longer exists."}),e.jsx(C,{size:"sm",variant:"outline",onClick:y,children:"Back to agents"})]}):e.jsxs("div",{className:"flex w-full max-w-4xl flex-col gap-5 px-4 py-6 md:px-8",children:[e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e.jsx("button",{type:"button",onClick:y,className:"text-faint hover:text-ink",title:"Back to agents",children:e.jsx(ee,{size:18})}),e.jsx("span",{className:"text-[15px] font-medium text-ink",children:n?"New agent":a?l?"Edit agent (modified built-in)":"Edit agent (built-in)":"Edit agent"}),e.jsxs("div",{className:"ml-auto flex items-center gap-2",children:[a&&e.jsxs(C,{size:"sm",variant:"outline",onClick:()=>{j("agents","reset_agent_to_default"),K()},disabled:u.reset.isPending,children:[e.jsx(te,{size:13})," Reset to default"]}),e.jsx(C,{size:"sm",variant:"outline",onClick:y,children:"Cancel"}),e.jsx(C,{size:"sm",onClick:()=>{j("agents","save_agent"),F()},disabled:A||!m.name.trim(),children:A?"Saving…":"Save"})]})]}),a&&e.jsx("p",{className:"rounded-md border border-border bg-panel px-3 py-2 text-[12px] text-muted",children:"Built-in agents can be edited in place — Reset to default discards your changes and restores TurboLLM's original prompt and access."}),e.jsxs("div",{className:"flex flex-col gap-1.5",children:[e.jsx("label",{className:"text-[12px] font-medium text-muted",children:"Name"}),e.jsx("input",{className:"rounded-md border border-border bg-bg px-3 py-1.5 text-[13px] text-ink outline-none focus:border-accent placeholder:text-faint",placeholder:"Release Notes Writer",value:m.name,onChange:t=>f(r=>({...r,name:t.target.value}))})]}),e.jsxs("div",{className:"flex flex-col gap-1.5",children:[e.jsx("label",{className:"text-[12px] font-medium text-muted",children:"Description"}),e.jsx("input",{className:"rounded-md border border-border bg-bg px-3 py-1.5 text-[13px] text-ink outline-none focus:border-accent placeholder:text-faint",placeholder:"What this agent is for, in one line",value:m.description,onChange:t=>f(r=>({...r,description:t.target.value}))})]}),e.jsxs("div",{className:"flex flex-col gap-1.5",children:[e.jsx("label",{className:"text-[12px] font-medium text-muted",children:"System prompt"}),e.jsx("textarea",{className:"min-h-[160px] resize-y rounded-md border border-border bg-bg px-3 py-2 font-mono text-[12px] leading-relaxed text-ink outline-none focus:border-accent placeholder:text-faint",placeholder:"You are…",value:m.systemPrompt,onChange:t=>f(r=>({...r,systemPrompt:t.target.value}))})]}),e.jsxs("div",{className:"flex flex-col gap-2.5",children:[e.jsx("div",{className:"inline-flex w-fit rounded-md border border-border p-0.5",children:["skills","tools"].map(t=>e.jsx("button",{type:"button",onClick:()=>{j("agents","switch_agent_form_tab"),Q(t)},className:q("rounded px-3 py-1.5 text-[13px] font-medium transition-colors",E===t?"bg-accent/12 text-accent":"text-muted hover:text-ink"),children:t==="skills"?`Skills (${m.skillIds.length})`:`Tools (${m.tools.length})`},t))}),E==="skills"?I.isLoading?e.jsx("p",{className:"text-[12px] text-faint",children:"Loading…"}):k.length===0?e.jsx("p",{className:"text-[12px] text-faint",children:"No skills yet — create one under Customize → Skills."}):e.jsx("div",{className:"flex flex-col gap-2 rounded-md border border-border bg-panel-2 p-3",children:k.map(t=>e.jsxs("label",{className:"flex cursor-pointer items-start gap-2 text-[13px]",children:[e.jsx("input",{type:"checkbox",className:"mt-0.5",checked:m.skillIds.includes(t.id),onChange:()=>O(t.id)}),e.jsxs("span",{className:"flex flex-col",children:[e.jsx("span",{className:"text-ink",children:t.name}),t.description&&e.jsx("span",{className:"text-[12px] text-muted",children:t.description})]})]},t.id))}):w.isLoading?e.jsx("p",{className:"text-[12px] text-faint",children:"Loading…"}):e.jsx(ce,{tools:N,selected:m.tools,onChange:t=>f(r=>({...r,tools:t}))})]}),!n&&!a&&(L?e.jsxs("div",{className:"flex items-center gap-2 self-start rounded-md border border-border bg-panel p-2 text-[12px]",children:[e.jsx("span",{className:"flex-1 text-muted",children:"Delete this agent?"}),e.jsx("button",{type:"button",onClick:()=>{j("agents","delete_agent"),M()},className:"rounded px-2 py-1 text-[color:var(--err)] hover:bg-[color:color-mix(in_srgb,var(--err)_12%,transparent)]",children:"Delete"}),e.jsx("button",{type:"button",onClick:()=>{j("agents","cancel_delete_agent"),R(!1)},className:"rounded px-2 py-1 text-faint hover:text-ink",children:"Cancel"})]}):e.jsxs("button",{type:"button",onClick:()=>R(!0),className:"flex items-center gap-1.5 self-start text-[12px] text-faint hover:text-[color:var(--err)]",children:[e.jsx(se,{size:13})," Delete agent"]}))]})}export{je as AgentEditPage};
|
|
1
|
+
import{j as e}from"./radix-B0YBjihE.js";import{b as W,u as $,r as b}from"./vendor-C1I6euDx.js";import{u as G,d as H,e as Y,f as J,g as U,a as z,B as C,t as j,c as q,b as h,A as _}from"./index-CSavhRGL.js";import{f as V}from"./chat-api-B_SlDoXA.js";import{f as X,a as Z}from"./agent-api-BXUf3UuJ.js";import{P as B}from"./personas-CKCwxU6o.js";import{C as ee}from"./chevron-left-DHH-nnSd.js";import{R as te}from"./rotate-ccw-g_DuozeG.js";import{T as se}from"./trash-2-B__gvkzD.js";import{C as ne}from"./chevron-right-DnT71PVy.js";import"./dropdown-menu-BOUsiJco.js";const ae=()=>({name:"",description:"",systemPrompt:"",skillIds:[],tools:[]});function oe(s){const d=[],x=new Map;for(const n of s){const a=/^mcp__([^_]+(?:_[^_]+)*)__/.exec(n.name);if(!a){d.push(n);continue}const i=a[1],p=/^\[([^\]]+)\]/.exec(n.description),c=p?p[1]:i.replace(/_/g," ");x.has(i)||x.set(i,{key:i,label:c,tools:[]}),x.get(i).tools.push(n)}return{builtin:d,groups:[...x.values()].sort((n,a)=>n.label.localeCompare(a.label))}}function le(s){return s.replace(/^mcp__[^_]+(?:_[^_]+)*__/,"")}function re(s){return s.replace(/^\[[^\]]+\]\s*/,"")}function D({tool:s,checked:d,onToggle:x}){return e.jsxs("label",{className:"flex cursor-pointer items-start gap-2 text-[12px]",children:[e.jsx("input",{type:"checkbox",className:"mt-0.5",checked:d,onChange:x}),e.jsxs("span",{className:"flex flex-col",children:[e.jsx("span",{className:"font-mono text-ink",children:le(s.name)}),s.description&&e.jsx("span",{className:"text-faint",children:re(s.description)})]})]})}function ie({group:s,selected:d,onToggleAll:x,onToggleOne:n}){const[a,i]=b.useState(!1),p=s.tools.map(l=>l.name),c=p.filter(l=>d.has(l)).length,o=c===p.length,g=c>0&&!o,u=b.useRef(null);return b.useEffect(()=>{u.current&&(u.current.indeterminate=g)},[g]),e.jsxs("div",{className:"rounded-md border border-border",children:[e.jsxs("div",{className:"flex items-center gap-2 px-2.5 py-2",children:[e.jsx("input",{ref:u,type:"checkbox",checked:o,onChange:()=>x(p,!o)}),e.jsxs("button",{type:"button",onClick:()=>{j("agents","toggle_agent_tool_group"),i(l=>!l)},className:"flex flex-1 items-center gap-1.5 text-left text-[13px] text-ink",children:[e.jsx(ne,{size:12,className:q("shrink-0 text-faint transition-transform",a&&"rotate-90")}),s.label,e.jsxs("span",{className:"text-[11px] text-faint",children:["(",c,"/",p.length,")"]})]})]}),a&&e.jsx("div",{className:"flex flex-col gap-1.5 border-t border-border px-3 py-2 pl-9",children:s.tools.map(l=>e.jsx(D,{tool:l,checked:d.has(l.name),onToggle:()=>n(l.name)},l.name))})]})}function ce({tools:s,selected:d,onChange:x}){const n=new Set(d),{builtin:a,groups:i}=oe(s),p=o=>x(n.has(o)?d.filter(g=>g!==o):[...d,o]),c=(o,g)=>{const u=d.filter(l=>!o.includes(l));x(g?[...u,...o]:u)};return s.length===0?e.jsx("p",{className:"text-[12px] text-faint",children:"No tools available yet."}):e.jsxs("div",{className:"flex flex-col gap-2",children:[a.length>0&&e.jsx("div",{className:"flex flex-col gap-2 rounded-md border border-border bg-panel-2 p-3",children:a.map(o=>e.jsx(D,{tool:o,checked:n.has(o.name),onToggle:()=>p(o.name)},o.name))}),i.map(o=>e.jsx(ie,{group:o,selected:n,onToggleAll:c,onToggleOne:p},o.key))]})}function je(){const{agentId:s="new"}=W(),d=$(),x=G(),n=s==="new",a=!n&&B.some(t=>t.id===s),i=a?B.find(t=>t.id===s):void 0,p=H(),c=!n&&!a?(p.data??[]).find(t=>t.id===s):void 0,o=Y(),g=J(),u=U(),l=a?g.data?.[s]:void 0,I=z({queryKey:Z.list(),queryFn:X,staleTime:0}),k=I.data??[],w=z({queryKey:["available-tools"],queryFn:V}),N=w.data??[],[m,f]=b.useState(ae()),[S,T]=b.useState(!1),[A,P]=b.useState(!1),[L,R]=b.useState(!1),[E,Q]=b.useState("skills");n&&!S&&(k.length||N.length)&&(f(t=>({...t,skillIds:k.map(r=>r.id),tools:N.map(r=>r.name)})),T(!0)),!n&&!a&&!S&&c&&(f({name:c.name,description:c.description,systemPrompt:c.systemPrompt,skillIds:c.skillIds,tools:c.tools}),T(!0)),a&&!S&&i&&g.isSuccess&&(k.length||N.length)&&(f({name:l?.name??i.name,description:l?.description??i.description,systemPrompt:l?.systemPrompt??i.systemPrompt,skillIds:l?.skillIds??k.map(t=>t.id),tools:l?.tools??N.map(t=>t.name)}),T(!0));const y=()=>{j("agents","back_to_agents"),d("/customize")},O=t=>f(r=>({...r,skillIds:r.skillIds.includes(t)?r.skillIds.filter(v=>v!==t):[...r.skillIds,t]})),F=()=>{if(!m.name.trim()){h.error("Name is required.");return}P(!0);const t={name:m.name.trim(),description:m.description.trim(),systemPrompt:m.systemPrompt,skillIds:m.skillIds,tools:m.tools};if(a){u.save.mutate({id:s,override:t},{onSuccess:()=>{h.success("Agent saved."),y()},onError:v=>{h.error(v instanceof _?v.message:"Could not save agent."),P(!1)}});return}const r={onSuccess:()=>{x.invalidateQueries({queryKey:["chat-agents"]}),h.success(n?"Agent created.":"Agent saved."),y()},onError:v=>{h.error(v instanceof _?v.message:"Could not save agent."),P(!1)}};n?o.add.mutate(t,r):o.update.mutate({id:s,patch:t},r)},K=()=>{u.reset.mutate(s,{onSuccess:()=>{h.success("Reset to default."),i&&f({name:i.name,description:i.description,systemPrompt:i.systemPrompt,skillIds:k.map(t=>t.id),tools:N.map(t=>t.name)})},onError:t=>h.error(t instanceof _?t.message:"Could not reset agent.")})},M=()=>{o.remove.mutate(s,{onSuccess:()=>{h.success("Agent deleted."),y()},onError:t=>h.error(t instanceof _?t.message:"Could not delete agent.")})};return!n&&!a&&p.isSuccess&&!c?e.jsxs("div",{className:"flex w-full flex-col items-center gap-3 px-8 py-16",children:[e.jsx("p",{className:"text-[14px] text-muted",children:"That agent no longer exists."}),e.jsx(C,{size:"sm",variant:"outline",onClick:y,children:"Back to agents"})]}):e.jsxs("div",{className:"flex w-full max-w-4xl flex-col gap-5 px-4 py-6 md:px-8",children:[e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e.jsx("button",{type:"button",onClick:y,className:"text-faint hover:text-ink",title:"Back to agents",children:e.jsx(ee,{size:18})}),e.jsx("span",{className:"text-[15px] font-medium text-ink",children:n?"New agent":a?l?"Edit agent (modified built-in)":"Edit agent (built-in)":"Edit agent"}),e.jsxs("div",{className:"ml-auto flex items-center gap-2",children:[a&&e.jsxs(C,{size:"sm",variant:"outline",onClick:()=>{j("agents","reset_agent_to_default"),K()},disabled:u.reset.isPending,children:[e.jsx(te,{size:13})," Reset to default"]}),e.jsx(C,{size:"sm",variant:"outline",onClick:y,children:"Cancel"}),e.jsx(C,{size:"sm",onClick:()=>{j("agents","save_agent"),F()},disabled:A||!m.name.trim(),children:A?"Saving…":"Save"})]})]}),a&&e.jsx("p",{className:"rounded-md border border-border bg-panel px-3 py-2 text-[12px] text-muted",children:"Built-in agents can be edited in place — Reset to default discards your changes and restores TurboLLM's original prompt and access."}),e.jsxs("div",{className:"flex flex-col gap-1.5",children:[e.jsx("label",{className:"text-[12px] font-medium text-muted",children:"Name"}),e.jsx("input",{className:"rounded-md border border-border bg-bg px-3 py-1.5 text-[13px] text-ink outline-none focus:border-accent placeholder:text-faint",placeholder:"Release Notes Writer",value:m.name,onChange:t=>f(r=>({...r,name:t.target.value}))})]}),e.jsxs("div",{className:"flex flex-col gap-1.5",children:[e.jsx("label",{className:"text-[12px] font-medium text-muted",children:"Description"}),e.jsx("input",{className:"rounded-md border border-border bg-bg px-3 py-1.5 text-[13px] text-ink outline-none focus:border-accent placeholder:text-faint",placeholder:"What this agent is for, in one line",value:m.description,onChange:t=>f(r=>({...r,description:t.target.value}))})]}),e.jsxs("div",{className:"flex flex-col gap-1.5",children:[e.jsx("label",{className:"text-[12px] font-medium text-muted",children:"System prompt"}),e.jsx("textarea",{className:"min-h-[160px] resize-y rounded-md border border-border bg-bg px-3 py-2 font-mono text-[12px] leading-relaxed text-ink outline-none focus:border-accent placeholder:text-faint",placeholder:"You are…",value:m.systemPrompt,onChange:t=>f(r=>({...r,systemPrompt:t.target.value}))})]}),e.jsxs("div",{className:"flex flex-col gap-2.5",children:[e.jsx("div",{className:"inline-flex w-fit rounded-md border border-border p-0.5",children:["skills","tools"].map(t=>e.jsx("button",{type:"button",onClick:()=>{j("agents","switch_agent_form_tab"),Q(t)},className:q("rounded px-3 py-1.5 text-[13px] font-medium transition-colors",E===t?"bg-accent/12 text-accent":"text-muted hover:text-ink"),children:t==="skills"?`Skills (${m.skillIds.length})`:`Tools (${m.tools.length})`},t))}),E==="skills"?I.isLoading?e.jsx("p",{className:"text-[12px] text-faint",children:"Loading…"}):k.length===0?e.jsx("p",{className:"text-[12px] text-faint",children:"No skills yet — create one under Customize → Skills."}):e.jsx("div",{className:"flex flex-col gap-2 rounded-md border border-border bg-panel-2 p-3",children:k.map(t=>e.jsxs("label",{className:"flex cursor-pointer items-start gap-2 text-[13px]",children:[e.jsx("input",{type:"checkbox",className:"mt-0.5",checked:m.skillIds.includes(t.id),onChange:()=>O(t.id)}),e.jsxs("span",{className:"flex flex-col",children:[e.jsx("span",{className:"text-ink",children:t.name}),t.description&&e.jsx("span",{className:"text-[12px] text-muted",children:t.description})]})]},t.id))}):w.isLoading?e.jsx("p",{className:"text-[12px] text-faint",children:"Loading…"}):e.jsx(ce,{tools:N,selected:m.tools,onChange:t=>f(r=>({...r,tools:t}))})]}),!n&&!a&&(L?e.jsxs("div",{className:"flex items-center gap-2 self-start rounded-md border border-border bg-panel p-2 text-[12px]",children:[e.jsx("span",{className:"flex-1 text-muted",children:"Delete this agent?"}),e.jsx("button",{type:"button",onClick:()=>{j("agents","delete_agent"),M()},className:"rounded px-2 py-1 text-[color:var(--err)] hover:bg-[color:color-mix(in_srgb,var(--err)_12%,transparent)]",children:"Delete"}),e.jsx("button",{type:"button",onClick:()=>{j("agents","cancel_delete_agent"),R(!1)},className:"rounded px-2 py-1 text-faint hover:text-ink",children:"Cancel"})]}):e.jsxs("button",{type:"button",onClick:()=>R(!0),className:"flex items-center gap-1.5 self-start text-[12px] text-faint hover:text-[color:var(--err)]",children:[e.jsx(se,{size:13})," Delete agent"]}))]})}export{je as AgentEditPage};
|