feat(FN-3195): document llama.cpp provider setup and onboarding

Docs(FN-3195): adds llama.cpp provider setup and onboarding documentation to the main and dashboard READMEs.

Fusion-Task-Id: FN-3195
This commit is contained in:
Fusion
2026-05-04 10:12:43 -07:00
committed by gsxdsm
parent e9f2a4cfb5
commit b7ac46ecef
37 changed files with 1440 additions and 38 deletions

View File

@@ -1,6 +1,7 @@
import { isGhAvailable, isGhAuthenticated } from "@fusion/core";
import { probeClaudeCli } from "../claude-cli-probe.js";
import { probeDroidCli } from "../droid-cli-probe.js";
import { probeLlamaCpp } from "../llama-cpp-probe.js";
import { ApiError, badRequest, conflict } from "../api-error.js";
import { clearUsageCache } from "../usage.js";
import { invalidateAllGlobalSettingsCaches } from "../project-store-resolver.js";
@@ -283,6 +284,26 @@ export const registerAuthRoutes: ApiRouteRegistrar = (ctx) => {
});
}
// Inject synthetic llama.cpp provider.
if (store) {
let llamaEnabled = false;
try {
const globalSettings = await store.getGlobalSettingsStore().getSettings();
llamaEnabled = globalSettings.useLlamaCpp === true;
} catch {
// Best-effort
}
const llamaExtension = options?.getLlamaCppExtensionStatus?.() ?? null;
const extensionOk = llamaExtension === null || llamaExtension.status === "ok";
const probe = await probeLlamaCpp();
providers.push({
id: "llama-cpp",
name: "llama.cpp — via HTTP server",
authenticated: llamaEnabled && probe.reachable && extensionOk,
type: "cli" as const,
});
}
const ghCli = {
available: isGhAvailable(),
authenticated: isGhAuthenticated(),
@@ -517,6 +538,93 @@ export const registerAuthRoutes: ApiRouteRegistrar = (ctx) => {
}
});
router.post("/auth/llama-cpp", async (req, res) => {
try {
if (!store) {
throw new ApiError(500, "Settings store unavailable");
}
const enabled = req.body?.enabled;
if (typeof enabled !== "boolean") {
throw badRequest("enabled must be a boolean");
}
if (enabled) {
const probe = await probeLlamaCpp();
if (!probe.reachable) {
throw new ApiError(400, `Cannot enable llama.cpp routing: ${probe.reason ?? "server unreachable"}`);
}
}
let prev = false;
try {
const priorGlobal = await store.getGlobalSettingsStore().getSettings();
prev = priorGlobal.useLlamaCpp === true;
} catch {
// best effort
}
const settings = await store.updateGlobalSettings({ useLlamaCpp: enabled });
invalidateAllGlobalSettingsCaches();
const engineManager = options?.engineManager;
if (engineManager) {
for (const engine of engineManager.getAllEngines().values()) {
engine.getTaskStore().getGlobalSettingsStore().invalidateCache();
}
}
const next = settings.useLlamaCpp === true;
if (options?.onUseLlamaCppToggled && prev !== next) {
try {
options.onUseLlamaCppToggled(prev, next);
} catch (hookErr) {
console.warn(
`[auth/llama-cpp] onUseLlamaCppToggled callback threw: ${hookErr instanceof Error ? hookErr.message : String(hookErr)}`,
);
}
}
res.json({ enabled: next, restartRequired: false });
} catch (err: unknown) {
if (err instanceof ApiError) {
throw err;
}
rethrowAsApiError(err);
}
});
router.get("/providers/llama-cpp/status", async (_req, res) => {
try {
const probe = await probeLlamaCpp();
let enabled = false;
if (store) {
try {
const globalSettings = await store.getGlobalSettingsStore().getSettings();
enabled = globalSettings.useLlamaCpp === true;
} catch {
// best-effort
}
}
const extension = options?.getLlamaCppExtensionStatus?.() ?? null;
const ready = enabled && probe.reachable && (extension === null || extension.status === "ok");
res.json({
enabled,
extension,
ready,
server: {
available: probe.reachable,
url: probe.url,
hasApiKey: probe.hasApiKey,
reason: probe.reason,
},
});
} catch (err: unknown) {
if (err instanceof ApiError) {
throw err;
}
rethrowAsApiError(err);
}
});
/**
* POST /api/auth/login
* Initiates OAuth login for a provider.

View File

@@ -13,6 +13,7 @@ export const registerModelRoutes: ApiRouteRegistrar = (ctx) => {
let defaultModelId: string | undefined;
let useClaudeCli = false;
let useDroidCli = false;
let useLlamaCpp = false;
let resolvedPlanningProvider: string | undefined;
let resolvedPlanningModelId: string | undefined;
if (store) {
@@ -25,6 +26,7 @@ export const registerModelRoutes: ApiRouteRegistrar = (ctx) => {
defaultModelId = globalSettings.defaultModelId;
useClaudeCli = globalSettings.useClaudeCli === true;
useDroidCli = globalSettings.useDroidCli === true;
useLlamaCpp = globalSettings.useLlamaCpp === true;
const mergedSettings = await store.getSettingsFast();
const resolvedPlanningModel = resolvePlanningSettingsModel(mergedSettings);
@@ -82,6 +84,9 @@ export const registerModelRoutes: ApiRouteRegistrar = (ctx) => {
if (!useDroidCli) {
models = models.filter((m) => m.provider !== "droid-cli");
}
if (!useLlamaCpp) {
models = models.filter((m) => m.provider !== "llama-server");
}
res.json({
models,

View File

@@ -1615,6 +1615,14 @@ export function registerSettingsMemoryRoutes(ctx: ApiRoutesContext, deps: Settin
// Best-effort: on read failure assume false so a flip-on still fires.
}
let prevUseLlamaCpp = false;
try {
const priorGlobal = await store.getGlobalSettingsStore().getSettings();
prevUseLlamaCpp = priorGlobal.useLlamaCpp === true;
} catch {
// Best-effort: on read failure assume false so a flip-on still fires.
}
const settings = await store.updateGlobalSettings(req.body);
// Invalidate global settings caches in all project-scoped stores so the
// next GET /settings?projectId=xxx reads fresh values from disk rather
@@ -1653,6 +1661,17 @@ export function registerSettingsMemoryRoutes(ctx: ApiRoutesContext, deps: Settin
}
}
const nextUseLlamaCpp = settings.useLlamaCpp === true;
if (options?.onUseLlamaCppToggled && prevUseLlamaCpp !== nextUseLlamaCpp) {
try {
options.onUseLlamaCppToggled(prevUseLlamaCpp, nextUseLlamaCpp);
} catch (hookErr) {
runtimeLogger.warn(
`onUseLlamaCppToggled callback threw: ${hookErr instanceof Error ? hookErr.message : String(hookErr)}`,
);
}
}
res.json(settings);
} catch (err: unknown) {
if (err instanceof ApiError) {