Test and deploy / test-and-deploy (push) Successful in 49s
- New server/llm.js consolidates ModelRuntime + model selection: admin setting (app_settings.llm_model) > LLM_MODEL/PREFILL_MODEL env > first available; used by both prefill and roast evaluation - GET/PUT /api/admin/llm lists configured models and stores the choice (validated against the list; empty = auto; audited); admin page gains an LLM section with a model picker - All user-facing 'Pi agent' wording is now 'LLM'; no_model error message no longer references the pi CLI - /roasts table: Review column removed (review lives in the detail view) Co-Authored-By: Claude Fable 5 <[email protected]>
116 lines
3.8 KiB
JavaScript
116 lines
3.8 KiB
JavaScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { setup, signup, password } from "./helpers.js";
|
|
|
|
const MODELS = [
|
|
{ key: "opencode-go:minimax-m3", name: "MiniMax-M3", provider: "opencode-go" },
|
|
{ key: "opencode-go:big-model", name: "Big Model", provider: "opencode-go" },
|
|
];
|
|
|
|
async function bootstrapAdmin(agent) {
|
|
const response = await agent.post("/api/auth/bootstrap").send({
|
|
email: "[email protected]",
|
|
password,
|
|
setupToken: "a-secure-bootstrap-token",
|
|
});
|
|
assert.equal(response.status, 201);
|
|
return response.body.csrfToken;
|
|
}
|
|
|
|
function makeAlog() {
|
|
const timex = [], temp1 = [], temp2 = [];
|
|
for (let i = 0; i <= 20; i++) {
|
|
timex.push(i * 30);
|
|
temp1.push(200 + i);
|
|
temp2.push(i < 3 ? 180 - i * 30 : 90 + (i - 3) * 7);
|
|
}
|
|
return JSON.stringify({ title: "T", mode: "C", weight: [250, 212, "g"], timex, temp1, temp2, timeindex: [1, 8, 14, 0, 0, 0, 20, 0] });
|
|
}
|
|
|
|
test("admin can list and change the LLM model; the setting reaches evaluations", async () => {
|
|
const evalCalls = [];
|
|
const { agent } = await setup(
|
|
{},
|
|
{
|
|
listModels: async () => MODELS,
|
|
evaluateRoast: async (parsed, plan, preferredModel) => {
|
|
evalCalls.push(preferredModel);
|
|
return { summary: "ok", grade: "good", highlights: [], concerns: [], suggestions: [], planComparison: null };
|
|
},
|
|
},
|
|
);
|
|
const csrf = await bootstrapAdmin(agent);
|
|
|
|
const listing = await agent.get("/api/admin/llm");
|
|
assert.equal(listing.status, 200);
|
|
assert.equal(listing.body.current, "");
|
|
assert.equal(listing.body.models.length, 2);
|
|
assert.equal(listing.body.modelsError, null);
|
|
|
|
const set = await agent
|
|
.put("/api/admin/llm")
|
|
.set("x-csrf-token", csrf)
|
|
.send({ model: "opencode-go:big-model" });
|
|
assert.equal(set.status, 200);
|
|
assert.equal((await agent.get("/api/admin/llm")).body.current, "opencode-go:big-model");
|
|
|
|
// An unknown model is rejected, a non-string is rejected
|
|
assert.equal(
|
|
(await agent.put("/api/admin/llm").set("x-csrf-token", csrf).send({ model: "nope:x" })).status,
|
|
400,
|
|
);
|
|
assert.equal(
|
|
(await agent.put("/api/admin/llm").set("x-csrf-token", csrf).send({ model: 5 })).status,
|
|
400,
|
|
);
|
|
|
|
// The chosen model is what evaluations receive
|
|
const up = await agent
|
|
.post("/api/roasts")
|
|
.set("x-csrf-token", csrf)
|
|
.send({ filename: "r.alog", content: makeAlog() });
|
|
assert.equal(up.status, 201);
|
|
for (let i = 0; i < 100 && !evalCalls.length; i++)
|
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
assert.deepEqual(evalCalls, ["opencode-go:big-model"]);
|
|
|
|
// Back to auto
|
|
const clear = await agent.put("/api/admin/llm").set("x-csrf-token", csrf).send({ model: "" });
|
|
assert.equal(clear.status, 200);
|
|
assert.equal((await agent.get("/api/admin/llm")).body.current, "");
|
|
});
|
|
|
|
test("LLM setting is admin-only and degrades when no models are configured", async () => {
|
|
const { agent, app } = await setup(
|
|
{},
|
|
{
|
|
listModels: async () => {
|
|
throw new Error("no runtime");
|
|
},
|
|
},
|
|
);
|
|
const { csrf } = await signup(agent, "[email protected]");
|
|
assert.equal((await agent.get("/api/admin/llm")).status, 403);
|
|
assert.equal(
|
|
(await agent.put("/api/admin/llm").set("x-csrf-token", csrf).send({ model: "" })).status,
|
|
403,
|
|
);
|
|
|
|
const request = (await import("supertest")).default;
|
|
const adminAgent = request.agent(app);
|
|
const adminCsrf = await bootstrapAdmin(adminAgent);
|
|
const listing = await adminAgent.get("/api/admin/llm");
|
|
assert.equal(listing.status, 200);
|
|
assert.equal(listing.body.modelsError, "llm_unavailable");
|
|
assert.deepEqual(listing.body.models, []);
|
|
// A concrete model can't be validated with no runtime, but clearing to auto still works
|
|
assert.equal(
|
|
(await adminAgent.put("/api/admin/llm").set("x-csrf-token", adminCsrf).send({ model: "a:b" })).status,
|
|
503,
|
|
);
|
|
assert.equal(
|
|
(await adminAgent.put("/api/admin/llm").set("x-csrf-token", adminCsrf).send({ model: "" })).status,
|
|
200,
|
|
);
|
|
});
|