feat: finish donor-led custom endpoint integration

This commit is contained in:
amosblomqvist
2026-10-29 04:43:53 +02:00
parent 80662d3d3b
commit b48821309b
33 changed files with 1116 additions and 1117 deletions
+6 -7
View File
@@ -26,7 +26,6 @@ import {
buildOpenAiCompatibleProvider,
discoverModels,
mergeManualModels,
syncCustomProvider,
} from "../src/server/custom-provider.ts";
import { piModels } from "../src/agent/models.ts";
import { getSupportedThinkingLevels } from "@earendil-works/pi-ai";
@@ -92,13 +91,13 @@ describe("sanitizeUserSettings — customProvider", () => {
// ─── per-model pins: the read side + THE prune law ───────────────────────────
describe("sanitizeUserSettings / sanitizeCustomModelOverrides — pins", () => {
it("keeps well-formed pins, clamps token counts up to the floor, keeps reasoning:false", () => {
it("keeps all positive integer pins and reasoning:false", () => {
const out = sanitizeUserSettings({
customProvider: {
baseUrl: "https://h/v1",
overrides: {
llama: { context: 131072, maxTokens: 8192, reasoning: true },
qwen: { context: 10 }, // under the floor → clamped up (clamp-never-reject on read)
qwen: { context: 10 }, // Small positive budgets are preserved.
quiet: { reasoning: false }, // a real pin, not an absent one
junk: { context: "big", maxTokens: -1, reasoning: "yes" }, // every field junk
" ": { context: 4096 }, // no usable id
@@ -108,7 +107,7 @@ describe("sanitizeUserSettings / sanitizeCustomModelOverrides — pins", () => {
});
assert.deepEqual(out?.customProvider?.overrides, {
llama: { context: 131072, maxTokens: 8192, reasoning: true },
qwen: { context: CUSTOM_MODEL_PIN_FLOOR },
qwen: { context: 10 },
quiet: { reasoning: false },
});
});
@@ -281,11 +280,11 @@ describe("buildOpenAiCompatibleProvider / syncCustomProvider", () => {
assert.deepEqual(getSupportedThinkingLevels(plain!), ["off"]);
});
it("registers on a usable sync and removes when the base URL clears", () => {
syncCustomProvider({ baseUrl: "http://h/v1", models: [{ id: "live" }] });
it("registers the native provider in the shared collection", () => {
piModels().setProvider(buildOpenAiCompatibleProvider({ baseUrl: "http://h/v1", models: [{ id: "live" }] }));
assert.ok(piModels().getProvider(CUSTOM_PROVIDER.id), "provider registered");
assert.ok(piModels().getModel(CUSTOM_PROVIDER.id, "live"), "model resolvable");
syncCustomProvider({ baseUrl: "", models: [{ id: "live" }] });
piModels().deleteProvider(CUSTOM_PROVIDER.id);
assert.equal(piModels().getProvider(CUSTOM_PROVIDER.id), undefined, "provider removed");
});
});
+191
View File
@@ -0,0 +1,191 @@
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { after, beforeEach, test, mock } from "node:test";
const scratch = fs.mkdtempSync(path.join(os.tmpdir(), "lh-custom-boundaries-"));
const originalEnv = process.env;
const originalFetch = globalThis.fetch;
process.env = { ...(originalEnv.NODE_TEST_CONTEXT ? { NODE_TEST_CONTEXT: originalEnv.NODE_TEST_CONTEXT } : {}), ...(originalEnv.NODE_OPTIONS ? { NODE_OPTIONS: originalEnv.NODE_OPTIONS } : {}), NODE_ENV: "test", HOME: scratch, LH_DATA_DIR: scratch, LH_AUTH_FILE: path.join(scratch, "auth.json"), LH_IGNORE_ENV_KEYS: "1" };
const route = await import("../app/api/custom-provider/route.ts");
const keys = await import("../src/server/keys.ts");
const { authEnvRecord } = await import("../src/core/auth-env.ts");
const { CUSTOM_PROVIDER } = await import("../src/core/auth-keys.ts");
const { authorizeFixture } = await import("./helpers/custom-provider-auth.ts");
const { readCustomAuth } = await import("../src/store/auth.ts");
const { persistCustomAuth } = await import("../src/server/custom-provider-auth.ts");
const settingsFile = path.join(scratch, "settings.json");
const seed = (cp: import("../src/core/settings.ts").CustomProviderSettings = { baseUrl: "http://old/v1", authMode: "keyless", models: [{ id: "m" }] }, key?: string) => fs.writeFileSync(settingsFile, JSON.stringify({ version: 1, customProvider: authorizeFixture(cp, key) }));
const setFileKey = (key: string) => { const auth = readCustomAuth(); persistCustomAuth({ ...auth.binding!, fileKeyBaseUrl: auth.binding!.baseUrl }, key); };
const current = () => JSON.parse(fs.readFileSync(settingsFile, "utf8"));
const get = (inspect = false) => route.GET(new Request(`http://localhost/api/custom-provider${inspect ? "?authMode=apiKey" : ""}`));
function mutate(body: unknown, method = "POST", headers: Record<string, string> = {}) {
return route.POST(new Request("http://localhost/api/custom-provider", { method, headers: { origin: "http://localhost", "content-type": "application/json", "if-match": get().headers.get("etag")!, ...headers }, ...(body === undefined ? {} : { body: JSON.stringify(body) }) }));
}
let requests: { url: string; authorization: string | null }[];
beforeEach(() => {
mock.restoreAll();
process.env.LH_IGNORE_ENV_KEYS = "1";
delete process.env.LH_DESKTOP_SERVER; delete process.env.LH_DESKTOP_API_SECRET;
fs.writeFileSync(path.join(scratch, "auth.json"), "{}"); seed();
requests = [];
globalThis.fetch = async (url, options) => {
requests.push({ url: String(url), authorization: new Headers(options?.headers).get("authorization") });
assert.equal(options?.redirect, "manual");
assert.ok(options?.signal instanceof AbortSignal);
return Response.json({ data: [{ id: "m" }] });
};
});
after(() => { mock.restoreAll(); process.env = originalEnv; globalThis.fetch = originalFetch; fs.rmSync(scratch, { recursive: true, force: true }); });
test("keyless probe never forwards a stored key, and clearing removes authentication", async () => {
setFileKey("private-old");
assert.equal((await mutate({ baseUrl: "http://new/v1", authMode: "keyless", key: "do-not-use-this" })).status, 200);
assert.equal(requests[0]?.authorization, null);
assert.equal(keys.keyValue(CUSTOM_PROVIDER.envVar), undefined);
assert.equal(current().customProvider.authMode, "keyless");
});
test("new keys have no arbitrary minimum; the exact probed key is stored", async () => {
assert.equal((await mutate({ baseUrl: "http://new/v1", authMode: "apiKey", key: " k " })).status, 200);
assert.equal(requests[0]?.authorization, "Bearer k");
assert.equal(keys.keyValue(CUSTOM_PROVIDER.envVar), "k");
const body = await get().text(); assert.ok(!body.includes("Bearer"));
});
test("changing destination cannot silently reuse an existing credential", async () => {
setFileKey("private-old");
const payload = { baseUrl: "http://new/v1", authMode: "apiKey" };
assert.equal((await mutate(payload)).status, 400); assert.equal(requests.length, 0);
assert.equal((await mutate({ ...payload, reuseKey: true })).status, 200);
assert.equal(requests[0]?.authorization, "Bearer private-old");
});
test("same-URL unbound development key needs existing explicit reuse consent", async () => {
seed({ baseUrl: "http://old/v1", authMode: "apiKey", models: [{ id: "m" }] }, "legacy-key");
const { CUSTOM_AUTH_BINDING } = await import("../src/core/custom-provider-auth.ts");
const file = path.join(scratch, "auth.json"); const map = JSON.parse(fs.readFileSync(file, "utf8")); delete map[CUSTOM_AUTH_BINDING]; fs.writeFileSync(file, JSON.stringify(map));
assert.equal((await get().json()).keyReuseRequired, true);
assert.equal((await mutate({ baseUrl: "http://old/v1", authMode: "apiKey" })).status, 400); assert.equal(requests.length, 0);
assert.equal((await mutate({ baseUrl: "http://old/v1", authMode: "apiKey", reuseKey: true })).status, 200);
assert.equal(requests[0]?.authorization, "Bearer legacy-key"); assert.equal((await get().json()).keyReuseRequired, false);
});
test("generic custom/reserved key writes cannot bypass the atomic binding seam", async () => {
const ordinary = await import("../app/api/keys/[id]/route.ts");
const { CUSTOM_AUTH_BINDING } = await import("../src/core/custom-provider-auth.ts");
const { authStore } = await import("../src/store/auth.ts");
const before = fs.readFileSync(path.join(scratch, "auth.json"), "utf8");
for (const id of [CUSTOM_PROVIDER.envVar, CUSTOM_AUTH_BINDING]) {
const context = { params: Promise.resolve({ id }) };
assert.equal((await ordinary.PUT(new Request("http://localhost", { method: "PUT", body: JSON.stringify({ value: "bypass" }) }), context)).status, 404);
assert.equal((await ordinary.DELETE(new Request("http://localhost", { method: "DELETE" }), context)).status, 404);
assert.equal(keys.saveKey(id, "bypass").ok, false); assert.throws(() => keys.deleteKey(id));
assert.throws(() => authStore.setAuthVar(id, "bypass")); assert.throws(() => authStore.deleteAuthVar(id));
}
assert.equal(fs.readFileSync(path.join(scratch, "auth.json"), "utf8"), before);
assert.ok(!keys.keyStatuses().some(row => row.id === CUSTOM_AUTH_BINDING)); assert.equal(process.env[CUSTOM_AUTH_BINDING], undefined);
});
test("same target can retain its effective key", async () => {
seed({ baseUrl: "http://old/v1", authMode: "apiKey", models: [{ id: "m" }] }, "retained");
assert.equal((await mutate({ baseUrl: "http://old/v1", authMode: "apiKey" })).status, 200);
assert.equal(requests[0]?.authorization, "Bearer retained");
});
test("environment ownership is shown; consent uses the environment key in probe and inference", async () => {
process.env.LH_IGNORE_ENV_KEYS = "0";
process.env[CUSTOM_PROVIDER.envVar] = "environment-secret";
authEnvRecord().realEnv.add(CUSTOM_PROVIDER.envVar);
try {
assert.equal((await get(true).json()).keySource, "env");
assert.equal((await mutate({ baseUrl: "http://new/v1", authMode: "apiKey", key: "shadowed" })).status, 400);
assert.equal((await mutate({ baseUrl: "http://new/v1", authMode: "apiKey", reuseKey: true })).status, 200);
assert.equal(requests[0]?.authorization, "Bearer environment-secret");
assert.equal(keys.keyValue(CUSTOM_PROVIDER.envVar), "environment-secret");
assert.ok(!(await get().text()).includes("environment-secret"));
assert.equal((await mutate({ baseUrl: "http://other/v1", authMode: "keyless" })).status, 200);
assert.equal(requests.at(-1)?.authorization, null);
} finally { authEnvRecord().realEnv.delete(CUSTOM_PROVIDER.envVar); delete process.env[CUSTOM_PROVIDER.envVar]; }
});
test("cross-origin/opaque/missing-origin and non-JSON writes cannot probe or mutate", async () => {
for (const origin of ["http://evil", "null", ""]) assert.equal((await mutate({ baseUrl: "http://new", authMode: "keyless" }, "POST", { origin })).status, 403);
assert.equal((await mutate({}, "POST", { "content-type": "text/plain" })).status, 415);
assert.equal((await mutate({}, "POST", { "sec-fetch-site": "cross-site" })).status, 403);
assert.equal(requests.length, 0);
});
test("standalone internal URL uses the addressed Host origin, never forwarded origins", async () => {
const headers = { "content-type": "application/json", "if-match": get().headers.get("etag")!, host: "127.0.0.1:3317", origin: "http://127.0.0.1:3317" };
assert.equal((await route.DELETE(new Request("http://localhost:3317/api/custom-provider", { method: "DELETE", headers }))).status, 200);
assert.equal((await mutate({}, "DELETE", { "x-forwarded-host": "evil", origin: "http://evil" })).status, 403);
});
test("empty DELETE streams normalize consistently without accepting malformed JSON", async () => {
const headers = { "content-type": "application/json", "if-match": get().headers.get("etag")!, origin: "http://localhost" };
const init: RequestInit & { duplex: "half" } = { method: "DELETE", headers, body: new ReadableStream({ start(controller) { controller.close(); } }), duplex: "half" };
assert.equal((await route.DELETE(new Request("http://localhost/api/custom-provider", init))).status, 200);
assert.equal((await route.DELETE(new Request("http://localhost/api/custom-provider", { method: "DELETE", headers: { ...headers, "if-match": get().headers.get("etag")! }, body: "{" }))).status, 400);
assert.equal((await route.POST(new Request("http://localhost/api/custom-provider", { method: "POST", headers, body: "" }))).status, 400);
});
test("desktop capability remains mandatory in desktop mode", async () => {
process.env.LH_DESKTOP_SERVER = "1"; process.env.LH_DESKTOP_API_SECRET = "x".repeat(64);
assert.equal((await mutate({}, "DELETE")).status, 403);
assert.equal((await mutate(undefined, "DELETE", { origin: "null", "x-learnharness-desktop": "x".repeat(64) })).status, 200);
});
test("forbidden URL components are rejected before probing", async () => {
for (const baseUrl of ["ftp://h", "https://user:pass@h/v1", "http://h/v1?q=secret", "http://h/v1#x", "http://h/v1?", "http://h/v1#"]) assert.equal((await mutate({ baseUrl, authMode: "keyless" })).status, 400);
assert.equal(requests.length, 0);
});
test("redirects/auth failures/model and byte limits cannot be bypassed with typed IDs", async () => {
for (const answer of [
() => new Response(null, { status: 302, headers: { location: "http://other" } }),
() => new Response("private response", { status: 401 }),
() => new Response("private response", { status: 403 }),
() => Response.json({ data: Array(10001).fill({ id: "m" }) }),
() => new Response("x".repeat(8 * 1024 * 1024 + 1)),
() => new Response("{}", { headers: { "content-length": String(8 * 1024 * 1024 + 1) } }),
]) {
globalThis.fetch = async () => answer();
const before = current();
const res = await mutate({ baseUrl: "http://h/v1", authMode: "keyless", manualModels: "m" });
assert.equal(res.status, 502); assert.deepEqual(current(), before);
assert.ok(!(await res.text()).includes("private response"));
}
});
test("write size bounds are explicit for streamed bodies", async () => {
assert.equal((await mutate({ baseUrl: "x".repeat(65536), authMode: "keyless" })).status, 413);
assert.equal((await mutate({ padding: "x".repeat(65536) }, "DELETE")).status, 413);
assert.equal(requests.length, 0);
});
test("stale save, pin edit and disconnect all return reload guidance without writing", async () => {
const etag = get().headers.get("etag")!;
seed({ baseUrl: "http://other/v1", authMode: "keyless", models: [{ id: "different" }] });
const before = current();
for (const method of ["POST", "PATCH", "DELETE"]) {
const res = await mutate({ baseUrl: "http://h", authMode: "keyless", overrides: {} }, method, { "if-match": etag });
assert.equal(res.status, 409); assert.match(await res.text(), /Reload/);
}
assert.equal(requests.length, 0); assert.deepEqual(current(), before);
});
test("after discovery preserves unrelated edits and rejects intervening custom edits", async () => {
globalThis.fetch = async () => { fs.writeFileSync(settingsFile, JSON.stringify({ ...current(), appearance: { mode: "dark" }, claudeCode: { enabled: true } })); return Response.json({ data: [{ id: "m" }] }); };
assert.equal((await mutate({ baseUrl: "http://h", authMode: "keyless" })).status, 200);
assert.deepEqual(current().appearance, { mode: "dark" }); assert.equal(current().claudeCode.enabled, true);
globalThis.fetch = async () => { seed({ baseUrl: "http://changed", authMode: "keyless", models: [] }); return Response.json({ data: [{ id: "m" }] }); };
assert.equal((await mutate({ baseUrl: "http://h", authMode: "keyless" })).status, 409);
assert.equal(current().customProvider.baseUrl, "http://changed");
});
test("after discovery rejects a changed effective key", async () => {
seed({ baseUrl: "http://old/v1", authMode: "apiKey", models: [{ id: "m" }] }, "before");
globalThis.fetch = async () => { setFileKey("rotated"); return Response.json({ data: [{ id: "m" }] }); };
assert.equal((await mutate({ baseUrl: "http://old/v1", authMode: "apiKey" })).status, 409);
assert.equal(keys.keyValue(CUSTOM_PROVIDER.envVar), "rotated");
});
test("offline pins accept 512/1, retain support declaration, and do not probe", async () => {
const res = await mutate({ overrides: { m: { context: 1, maxTokens: 512, reasoning: false } } }, "PATCH");
assert.equal(res.status, 200); assert.equal(requests.length, 0);
assert.deepEqual(current().customProvider.overrides.m, { context: 1, maxTokens: 512, reasoning: false });
});
test("settings-write failure restores previous key/config and reports failure", async () => {
setFileKey("old-key");
const before = current();
const write = fs.writeFileSync;
mock.method(fs, "writeFileSync", (...args: Parameters<typeof write>) => { if (args[0] === settingsFile) throw new Error("private-io-details"); return write(...args); });
const res = await mutate({ baseUrl: "http://new", authMode: "apiKey", key: "new-key" });
assert.equal(res.status, 500); assert.ok(!(await res.text()).includes("private-io-details"));
assert.equal(keys.keyValue(CUSTOM_PROVIDER.envVar), "old-key"); assert.deepEqual(current(), before);
});
+129
View File
@@ -0,0 +1,129 @@
/** Native Pi payloads with synthetic transport and owned scratch files only. */
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { after, beforeEach, test } from "node:test";
import { createCanvas } from "@napi-rs/canvas";
import { normalizeContext, fauxAssistantMessage, fauxToolCall, type Message } from "@earendil-works/pi-ai";
const scratch = fs.mkdtempSync(path.join(os.tmpdir(), "lh-custom-lifecycle-"));
const originalEnv = process.env;
const originalFetch = globalThis.fetch;
process.env = { ...(originalEnv.NODE_TEST_CONTEXT ? { NODE_TEST_CONTEXT: originalEnv.NODE_TEST_CONTEXT } : {}), ...(originalEnv.NODE_OPTIONS ? { NODE_OPTIONS: originalEnv.NODE_OPTIONS } : {}), NODE_ENV: "test", HOME: scratch, LH_DATA_DIR: scratch, LH_AUTH_FILE: path.join(scratch, "auth.json"), LH_IGNORE_ENV_KEYS: "1" };
const { writeSettings, readSettings } = await import("../src/store/settings.ts");
const { piEngineForSpec, piModels } = await import("../src/agent/models.ts");
await import("../src/server/keys.ts");
const { readCustomAuth } = await import("../src/store/auth.ts");
const { persistCustomAuth, customAuthState } = await import("../src/server/custom-provider-auth.ts");
const { authorizeFixture } = await import("./helpers/custom-provider-auth.ts");
const { modelCatalog, catalogContextLimits } = await import("../src/server/model-catalog.ts");
const { callCost } = await import("../src/server/pricing.ts");
const { createReadTool } = await import("../src/agent/tools/read.ts");
const { installCustomProviderSource } = await import("../src/server/custom-provider.ts");
let requests: { url: string; auth: string | null; body: Record<string, unknown> }[];
function configure(baseUrl = "http://first/v1", reasoning = false, maxTokens = 512, authMode: "keyless" | "apiKey" = "apiKey") {
const key = authMode === "apiKey" ? readCustomAuth().fileKey() ?? "first-key" : undefined;
writeSettings({ version: 1, customProvider: authorizeFixture({ baseUrl, authMode, models: [{ id: "m", context: 16384, maxTokens, reasoning }] }, key) });
}
function answer() {
return new Response(`data: ${JSON.stringify({ id: "fake", choices: [{ index: 0, delta: { role: "assistant", content: "ok" }, finish_reason: null }] })}\n\ndata: ${JSON.stringify({ choices: [{ index: 0, delta: {}, finish_reason: "stop" }], usage: { prompt_tokens: 1, completion_tokens: 1 } })}\n\ndata: [DONE]\n\n`, { headers: { "content-type": "text/event-stream" } });
}
function capture(url: unknown, init?: RequestInit) {
requests.push({ url: String(url), auth: new Headers(init?.headers).get("authorization"), body: JSON.parse(String(init?.body)) });
}
const context = (messages: Message[] = [{ role: "user", content: "synthetic", timestamp: 0 }]) => normalizeContext({ messages });
const run = (engine: ReturnType<typeof piEngineForSpec>, messages?: Message[]) => engine.streamFn(engine.model, context(messages), {}).result();
beforeEach(() => {
requests = []; fs.writeFileSync(path.join(scratch, "auth.json"), "{}"); configure();
globalThis.fetch = async (url, init) => { capture(url, init); return answer(); };
});
after(() => { process.env = originalEnv; globalThis.fetch = originalFetch; fs.rmSync(scratch, { recursive: true, force: true }); });
test("native requests revalidate endpoint/model/key/limits with no fallback and retain in-flight setup", async () => {
const engine = piEngineForSpec({ provider: "custom", id: "m" });
let started!: () => void; const ready = new Promise<void>((r) => { started = r; });
let release!: () => void;
globalThis.fetch = async (url, init) => { capture(url, init); started(); return new Promise<Response>((resolve) => { release = () => resolve(answer()); }); };
const inFlight = run(engine); await ready;
configure("http://second/v1", false, 1024); persistCustomAuth(readCustomAuth().binding, "second-key");
globalThis.fetch = async (url, init) => { capture(url, init); return answer(); };
assert.equal((await run(engine)).stopReason, "stop");
release(); assert.equal((await inFlight).stopReason, "stop");
assert.match(requests[0]!.url, /^http:\/\/first\/v1\/chat\/completions$/);
assert.equal(requests[0]!.auth, "Bearer first-key"); assert.equal(requests[0]!.body.max_completion_tokens, 512);
assert.match(requests[1]!.url, /^http:\/\/second\/v1\/chat\/completions$/);
assert.equal(requests[1]!.auth, "Bearer second-key"); assert.equal(requests[1]!.body.max_completion_tokens, 1024);
writeSettings({ version: 1, customProvider: { ...readSettings()!.customProvider, models: [{ id: "other" }] } });
assert.ok(piModels().getProvider("custom"), "provider stays authorized while this model is removed");
assert.equal((await run(engine)).stopReason, "error");
writeSettings({ version: 1 }); assert.equal((await run(engine)).stopReason, "error");
assert.equal(requests.length, 2);
assert.ok(piModels().getProvider("openai"), "native route survives custom removal");
});
test("current key changes without configuration edits are reflected on the next operation", async () => {
const engine = piEngineForSpec({ provider: "custom", id: "m" });
await run(engine); persistCustomAuth(readCustomAuth().binding, "rotated-key"); await run(engine);
assert.deepEqual(requests.map((r) => r.auth), ["Bearer first-key", "Bearer rotated-key"]);
});
test("original saved thinking preference recovers after capability expansion and contracts again", async () => {
const engine = piEngineForSpec({ provider: "custom", id: "m", thinkingLevel: "high" });
assert.equal(engine.level, "off");
await run(engine); configure("http://first/v1", true); await run(engine); configure(); await run(engine);
assert.deepEqual(requests.map((r) => r.body.reasoning_effort), [undefined, "high", undefined]);
});
test("unset preference reselects the existing default against current reasoning metadata", async () => {
const engine = piEngineForSpec({ provider: "custom", id: "m" });
configure("http://first/v1", true); await run(engine);
assert.equal(requests[0]!.body.reasoning_effort, "medium");
configure(); await run(engine); assert.equal(requests[1]!.body.reasoning_effort, undefined);
});
test("catalog capabilities remain reported and prices unknown despite runtime image permission", () => {
const engine = piEngineForSpec({ provider: "custom", id: "m" });
assert.deepEqual(engine.model.input, ["text", "image"]);
const dto = modelCatalog().models.find((m) => m.provider === "custom")!;
assert.deepEqual(dto.input, ["text"]); assert.equal(dto.cost, null);
assert.equal(callCost({ inputTokens: 100, outputTokens: 100 }, "custom:m"), null);
assert.equal(catalogContextLimits().get("custom:m"), 16384);
});
const image = { type: "image" as const, data: createCanvas(1, 1).toBuffer("image/png").toString("base64"), mimeType: "image/png" };
test("actual native encoder preserves USER and tool-result images under missing metadata, including after reload", async () => {
const engine = piEngineForSpec({ provider: "custom", id: "m" });
const messages: Message[] = [
{ role: "user", content: [{ type: "text", text: "look" }, image], timestamp: 0 },
fauxAssistantMessage([fauxToolCall("read", {}, { id: "read-1" })], { stopReason: "toolUse" }),
{ role: "toolResult", toolCallId: "read-1", toolName: "read", content: [image], isError: false, timestamp: 1 },
];
await run(engine, messages); configure("http://second/v1"); await run(engine, messages);
for (const request of requests) {
const body = JSON.stringify(request.body.messages);
assert.equal((body.match(/"type":"image_url"/g) ?? []).length, 2);
assert.equal((body.match(/data:image\/png;base64/g) ?? []).length, 2);
assert.ok(!body.includes("image omitted"));
}
assert.deepEqual(piModels().getModel("custom", "m")!.input, ["text"]);
});
test("teacher-bound read returns image without contradictory omission note", async () => {
const file = path.join(scratch, "synthetic.png"); fs.writeFileSync(file, Buffer.from(image.data, "base64"));
const engine = piEngineForSpec({ provider: "custom", id: "m" });
const result = await createReadTool(engine.model).execute("read-1", { path: file });
assert.ok(result.content.some((block) => block.type === "image"));
assert.ok(!JSON.stringify(result).includes("image will be omitted"));
});
test("backend image rejection surfaces once, never retries without images", async () => {
globalThis.fetch = async (url, init) => { capture(url, init); return Response.json({ error: { message: "images rejected by backend" } }, { status: 400 }); };
const result = await run(piEngineForSpec({ provider: "custom", id: "m" }), [{ role: "user", content: [image], timestamp: 0 }]);
assert.equal(result.stopReason, "error"); assert.match(result.errorMessage ?? "", /images rejected/); assert.equal(requests.length, 1);
});
test("existing engine refuses nonce mismatch on EVERY operation, without falling back to env", async () => {
const engine = piEngineForSpec({ provider: "custom", id: "m" }); await run(engine);
persistCustomAuth({ ...readCustomAuth().binding!, id: "b".repeat(32) }, "different-key");
assert.equal((await run(engine)).stopReason, "error"); assert.equal(requests.length, 1);
});
test("keyless publication never reads the credential lookup", async () => {
configure("http://first/v1", false, 512, "keyless");
let credentialLookups = 0;
installCustomProviderSource((inspect, cp) => { if (inspect) credentialLookups++; return customAuthState(inspect, cp); });
try { assert.equal((await run(piEngineForSpec({ provider: "custom", id: "m" }))).stopReason, "stop"); assert.equal(credentialLookups, 0); assert.equal(requests[0]!.auth, "Bearer not-needed"); }
finally { installCustomProviderSource(); }
});
+81
View File
@@ -0,0 +1,81 @@
/** No real endpoints/accounts. Actual process death and cold startup, not HMR. */
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { spawnSync } from "node:child_process";
import { after, test } from "node:test";
import { CUSTOM_AUTH_BINDING } from "../src/core/custom-provider-auth.ts";
const root = fs.mkdtempSync(path.join(os.tmpdir(), "lh-custom-restart-"));
after(() => fs.rmSync(root, { recursive: true, force: true }));
const base = "http://first.invalid/v1";
function profile() {
const home = fs.mkdtempSync(path.join(root, "case-"));
const id = "a".repeat(32);
fs.writeFileSync(path.join(home, "settings.json"), JSON.stringify({ version: 1, appearance: { mode: "dark" }, customProvider: { baseUrl: base, authMode: "apiKey", credentialBindingId: id, models: [{ id: "m" }] } }));
fs.writeFileSync(path.join(home, "auth.json"), JSON.stringify({ CUSTOM_OPENAI_API_KEY: "fixture-old-key", OTHER_PROVIDER_KEY: "untouched", future: { preserve: true }, [CUSTOM_AUTH_BINDING]: JSON.stringify({ version: 1, id, baseUrl: base, authMode: "apiKey", fileKeyBaseUrl: base }) }), { mode: 0o600 });
return home;
}
function child(home: string, mode: "mutate" | "inspect", action = "key", fault = "none", envKey?: string) {
const result = spawnSync(process.execPath, ["--import", "tsx", "tests/helpers/custom-provider-crash.ts", mode, action, fault], { cwd: process.cwd(), env: { PATH: process.env.PATH, HOME: home, USERPROFILE: home, NODE_ENV: "test", LH_DATA_DIR: home, LH_AUTH_FILE: path.join(home, "auth.json"), LH_IGNORE_ENV_KEYS: "0", ...(envKey ? { CUSTOM_OPENAI_API_KEY: envKey } : {}) }, encoding: "utf8", timeout: 15000 });
assert.ifError(result.error);
assert.ok(result.status === 0 || result.status === 77, `${result.status}: ${result.stderr}`);
return { exit: result.status, data: result.stdout.trim() ? JSON.parse(result.stdout.trim().split("\n").at(-1)!) : undefined };
}
function closed(home: string, envKey?: string) {
const { data } = child(home, "inspect", "key", "none", envKey);
assert.equal(data.enabled, false); assert.deepEqual(data.requests, []); assert.equal(data.metadataInEnv, false);
assert.ok(!data.statusIds.includes(CUSTOM_AUTH_BINDING));
}
test("cold restart refuses the new auth/old URL gap and double-I/O failure, with or without env", () => {
for (const fault of ["after-auth", "double-io"]) {
const home = profile(); const result = child(home, "mutate", "key", fault);
if (fault === "after-auth") assert.equal(result.exit, 77); else assert.equal(result.data.status, 500);
closed(home); closed(home, "fixture-real-env");
const auth = JSON.parse(fs.readFileSync(path.join(home, "auth.json"), "utf8"));
assert.equal(auth.OTHER_PROVIDER_KEY, "untouched"); assert.deepEqual(auth.future, { preserve: true });
}
});
test("failed auth publication retains old matching setup; landed settings plus restored auth fails closed", () => {
const home = profile(); assert.equal(child(home, "mutate", "key", "auth-before-rename").data.status, 500);
const old = child(home, "inspect").data; assert.equal(old.enabled, true); assert.equal(old.requests[0].url, base + "/chat/completions"); assert.equal(old.requests[0].authorization, "Bearer fixture-old-key");
const other = profile(); assert.equal(child(other, "mutate", "key", "settings-landed").data.status, 500); closed(other);
});
test("clear/disconnect half commits stay closed across restart even with genuine env keys", () => {
for (const action of ["keyless", "disconnect"]) {
for (const fault of ["after-auth", "double-io"]) {
const home = profile(); child(home, "mutate", action, fault, "fixture-real-env"); closed(home, "fixture-real-env");
}
}
});
test("successful keyless checks only binding; disconnect tombstone blocks latent env after restart", () => {
const home = profile(); assert.equal(child(home, "mutate", "keyless", "none", "fixture-real-env").data.status, 200);
const state = child(home, "inspect", "key", "none", "fixture-real-env").data; assert.equal(state.enabled, true); assert.equal(state.requests[0].authorization, "Bearer not-needed");
assert.equal(child(home, "mutate", "disconnect", "none", "fixture-real-env").data.status, 200); closed(home, "fixture-real-env");
});
test("env reuse never reauthorizes a shadowed file key for another destination", () => {
const home = profile(); assert.equal(child(home, "mutate", "envMove", "none", "fixture-real-env").data.status, 200);
const live = child(home, "inspect", "key", "none", "fixture-real-env").data; assert.equal(live.enabled, true); assert.equal(live.requests[0].url, "http://second.invalid/v1/chat/completions"); assert.equal(live.requests[0].authorization, "Bearer fixture-real-env");
closed(home);
const auth = JSON.parse(fs.readFileSync(path.join(home, "auth.json"), "utf8")); assert.equal(JSON.parse(auth[CUSTOM_AUTH_BINDING]).fileKeyBaseUrl, base); assert.equal(auth.CUSTOM_OPENAI_API_KEY, "fixture-old-key");
assert.equal(child(home, "mutate", "reuse").data.status, 200); assert.equal(child(home, "inspect").data.requests[0].authorization, "Bearer fixture-old-key");
});
test("unbound development records require explicit manual reauthorization, never automatic migration", () => {
const home = profile(); const file = path.join(home, "auth.json"); const auth = JSON.parse(fs.readFileSync(file, "utf8")); delete auth[CUSTOM_AUTH_BINDING]; fs.writeFileSync(file, JSON.stringify(auth));
closed(home); closed(home, "fixture-real-env");
assert.equal(child(home, "mutate", "reuse").data.status, 200); assert.equal(child(home, "inspect").data.enabled, true);
});
test("malformed/unreadable private snapshots are never rewritten or used, unrelated bytes survive", () => {
for (const raw of ['{"OTHER_PROVIDER_KEY":"do-not-erase",', '[]', JSON.stringify({ OTHER_PROVIDER_KEY: "do-not-erase", [CUSTOM_AUTH_BINDING]: "not-json" })]) {
const home = profile(); const file = path.join(home, "auth.json"); fs.writeFileSync(file, raw);
closed(home, "fixture-real-env"); assert.equal(child(home, "mutate").data.status, 500); assert.equal(fs.readFileSync(file, "utf8"), raw);
}
const home = profile(); const file = path.join(home, "auth.json"); const before = fs.readFileSync(file, "utf8");
assert.equal(child(home, "mutate", "key", "unreadable").data.status, 500); assert.equal(fs.readFileSync(file, "utf8"), before);
assert.equal(child(home, "inspect", "key", "unreadable", "fixture-real-env").data.enabled, false);
});
test("missing auth file initializes normally with explicit new key, preserving other settings", () => {
const home = profile(); fs.unlinkSync(path.join(home, "auth.json")); closed(home);
assert.equal(child(home, "mutate").data.status, 200); const live = child(home, "inspect").data; assert.equal(live.enabled, true); assert.equal(live.requests[0].authorization, "Bearer fixture-new-key");
assert.deepEqual(JSON.parse(fs.readFileSync(path.join(home, "settings.json"), "utf8")).appearance, { mode: "dark" });
});
+3 -2
View File
@@ -37,6 +37,7 @@ const CONNECTED = {
version: 1,
customProvider: {
baseUrl: BASE,
authMode: "keyless",
models: [{ id: "disc-1", context: 131072 }, { id: "disc-2" }],
overrides: { "disc-1": { context: 200000 }, "disc-2": { maxTokens: 8192 } },
manualModels: ["m1"],
@@ -59,8 +60,8 @@ function post(payload: unknown): Promise<Response> {
return route.POST(
new Request("http://localhost/api/custom-provider", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(payload),
headers: { "content-type": "application/json", origin: "http://localhost", "if-match": route.GET(new Request("http://localhost/api/custom-provider")).headers.get("etag")! },
body: JSON.stringify({ authMode: payload && typeof payload === "object" && "key" in payload ? "apiKey" : "keyless", ...payload as object }),
}),
);
}
-362
View File
@@ -1,362 +0,0 @@
import assert from "node:assert/strict";
import { after, describe, it } from "node:test";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { createModels, normalizeContext, type SimpleStreamOptions } from "@earendil-works/pi-ai";
import type { ThinkingLevel } from "@earendil-works/pi-agent-core";
import { parseCustomProviders, resolveCustomValue } from "../src/core/custom-providers.ts";
import { createCustomProvider, customProviderApis } from "../src/agent/custom-providers.ts";
import { CLAUDE_CODE_PROVIDER } from "../src/core/contracts/claude-code.ts";
const zero = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
function model(id = "chat", extra = {}) {
return { id, contextWindow: 8192, maxTokens: 1024, cost: zero, ...extra };
}
function config(extra = {}, models = [model()]) {
return { providers: { gateway: { baseUrl: "http://example.invalid/custom/v1", api: "openai-completions", apiKey: "dummy", models, ...extra } } };
}
function parse(value: unknown) { return parseCustomProviders(JSON.stringify(value), new Set(["openai", "chatgpt"]), customProviderApis); }
const temp = fs.mkdtempSync(path.join(os.tmpdir(), "lh-custom-providers-"));
process.env.LH_DATA_DIR = temp;
process.env.LH_AUTH_FILE = path.join(temp, "auth.json");
process.env.LH_IGNORE_ENV_KEYS = "1";
fs.writeFileSync(process.env.LH_AUTH_FILE, JSON.stringify({ GW_KEY: "first-key", GW_HEADER: "first-header" }));
const { CUSTOM_MODELS_FILE, SETTINGS_FILE } = await import("../config.ts");
const { modelCatalog, validateModelsSettings, clampModelsLevels, catalogContextLimits } = await import("../src/server/model-catalog.ts");
const { piModels, piEngineForSpec } = await import("../src/agent/models.ts");
const { providerKeyPresent, keyValue } = await import("../src/server/keys.ts");
const { authStore } = await import("../src/store/auth.ts");
const { CustomProviderSource, customProviderConfigured, installCustomProviderSource } = await import("../src/server/custom-providers.ts");
const { callCost } = await import("../src/server/pricing.ts");
after(() => fs.rmSync(temp, { recursive: true, force: true }));
function save(value: unknown) { fs.writeFileSync(CUSTOM_MODELS_FILE, JSON.stringify(value)); }
function sseText(text = "ok") {
const chunks = [
{ choices: [{ index: 0, delta: { role: "assistant", content: text }, finish_reason: null }] },
{ choices: [{ index: 0, delta: {}, finish_reason: "stop" }], usage: { prompt_tokens: 10, completion_tokens: 2, total_tokens: 12 } },
];
return new Response(chunks.map((chunk) => `data: ${JSON.stringify({ id: "test", object: "chat.completion.chunk", created: 1, model: "chat", ...chunk })}\n\n`).join("") + "data: [DONE]\n\n", { headers: { "content-type": "text/event-stream" } });
}
describe("declarative custom-provider configuration", () => {
it("uses Pi metadata and provider/model inheritance without rewriting base URLs", () => {
const [parsed] = parse(config({ compat: { supportsStore: false, chatTemplateKwargs: { a: true } } }, [model("named", {
name: "Named model", reasoning: true, input: ["text", "image"], thinkingLevelMap: { high: "high", xhigh: null },
compat: { maxTokensField: "max_tokens", chatTemplateKwargs: { b: false } },
samplingParams: { temperature: 0.5 }, samplingParamsByThinkingLevel: { high: { top_p: 0.9 } },
})]));
assert.equal(parsed!.models[0]!.baseUrl, "http://example.invalid/custom/v1");
assert.equal(parsed!.models[0]!.provider, "gateway");
assert.equal(parsed!.models[0]!.name, "Named model");
assert.deepEqual(parsed!.models[0]!.input, ["text", "image"]);
assert.deepEqual(parsed!.models[0]!.compat, { supportsStore: false, maxTokensField: "max_tokens", chatTemplateKwargs: { a: true, b: false } });
assert.deepEqual(parsed!.models[0]!.samplingParamsByThinkingLevel, { high: { top_p: 0.9 } });
});
it("accepts Pi line comments, trailing commas and BOM without corrupting strings", () => {
const text = `\uFEFF${JSON.stringify(config()).replace('"providers":', '// comment\n"providers":').replace(/}$/, ',}')}`;
assert.equal(parseCustomProviders(text, new Set(), customProviderApis)[0]!.models[0]!.baseUrl, "http://example.invalid/custom/v1");
});
it("registers multiple providers and mixed native APIs without fixed backend IDs", () => {
const value = config();
const raw = { providers: { ...value.providers, other: { ...value.providers.gateway, models: [model("chat"), model("anthropic", { api: "anthropic-messages", baseUrl: "https://example.invalid/anthropic" })] } } };
const models = createModels();
for (const entry of parse(raw)) models.setProvider(createCustomProvider(entry, () => undefined));
assert.equal(models.getProviders().length, 2);
assert.equal(models.getModel("other", "anthropic")!.api, "anthropic-messages");
assert.equal(models.getModel("other", "anthropic")!.baseUrl, "https://example.invalid/anthropic");
});
it("requires explicit finite limits and nonnegative costs, instead of guessing", () => {
for (const field of ["contextWindow", "maxTokens", "cost"]) {
const m: Record<string, unknown> = model(); delete m[field];
assert.throws(() => parse(config({}, [m as ReturnType<typeof model>])));
}
assert.throws(() => parse(config({}, [model("chat", { contextWindow: -1 })])));
assert.throws(() => parse(config({}, [model("chat", { cost: { ...zero, output: -1 } })])));
assert.throws(() => parseCustomProviders(JSON.stringify(config()).replace('"contextWindow":8192', '"contextWindow":1e999'), new Set(), customProviderApis));
});
it("rejects collisions, duplicate IDs, unsupported protocols, executable fields and invalid URLs", () => {
for (const id of ["openai", "chatgpt", "", "has:colon"]) assert.throws(() => parse({ providers: { [id]: config().providers.gateway } }));
assert.throws(() => parse(config({}, [model(), model()])));
assert.throws(() => parse(config({ api: "unknown-api" })));
assert.throws(() => parse(config({ oauth: "radius" })));
assert.throws(() => parse(config({ modelOverrides: {} })));
assert.throws(() => parse(config({ apiKey: "!touch /never-execute" })));
assert.throws(() => parse(config({ headers: { "x-key": "!secret-command" } })));
assert.throws(() => parse(config({}, [model("chat", { headers: { Authorization: "!command" } })])));
for (const baseUrl of ["file:///tmp/model", "https://secret@example.invalid", "http://example.invalid/#fragment"]) assert.throws(() => parse(config({ baseUrl })));
});
it("matches Pi value interpolation and escapes, without recursive expansion or commands", () => {
const lookup = (key: string) => ({ KEY: "secret", DATA: "!never-executed" })[key as "KEY"];
assert.equal(resolveCustomValue("Bearer ${KEY}/$KEY", lookup), "Bearer secret/secret");
assert.equal(resolveCustomValue("$$KEY $!literal", lookup), "$KEY !literal");
assert.equal(resolveCustomValue("$MISSING", lookup), undefined);
assert.equal(resolveCustomValue("$DATA", lookup), "!never-executed");
});
});
describe("custom providers in the existing application wiring", () => {
it("is empty before configuration and never creates a configuration file", () => {
assert.equal(fs.existsSync(CUSTOM_MODELS_FILE), false);
assert.equal(modelCatalog().customProvidersError, undefined);
assert.equal(fs.existsSync(CUSTOM_MODELS_FILE), false);
});
it("projects into existing pickers, validation, context limits and Pi pricing", async () => {
save(config({ apiKey: "$GW_KEY", headers: { "x-gateway": "$GW_HEADER" } }, [model("chat", { cost: { ...zero, input: 2, output: 4 } })]));
const catalog = modelCatalog();
assert.ok(catalog.providers.find((p) => p.id === "gateway")?.keyPresent);
assert.ok(catalog.models.some((m) => m.provider === "gateway" && m.id === "chat"));
assert.equal(JSON.stringify(catalog).includes("first-key"), false);
assert.equal(JSON.stringify(catalog).includes("first-header"), false);
assert.equal(validateModelsSettings({ strong: { model: { provider: "gateway", id: "chat" } } }).ok, true);
assert.equal(catalogContextLimits().get("gateway:chat"), 8192);
assert.ok(Math.abs(callCost({ inputTokens: 100, outputTokens: 50 }, "gateway:chat")! - 0.0004) < 1e-12);
assert.equal(providerKeyPresent("gateway"), true);
assert.equal((await piModels().getAuth("gateway"))!.auth.apiKey, "first-key");
});
it("uses current app key precedence and marks unresolved credentials/headers unavailable", () => {
process.env.LH_IGNORE_ENV_KEYS = "0";
process.env.GW_KEY = "real-env-key";
try { assert.equal(keyValue("GW_KEY"), "real-env-key"); }
finally { process.env.LH_IGNORE_ENV_KEYS = "1"; delete process.env.GW_KEY; }
save(config({ apiKey: "$MISSING_KEY" }));
assert.equal(providerKeyPresent("gateway"), false);
assert.equal(modelCatalog().models.some((m) => m.provider === "gateway"), false);
save(config({ headers: { "x-required": "$MISSING_HEADER" } }));
assert.equal(providerKeyPresent("gateway"), false);
save(config({ apiKey: undefined }));
assert.equal(providerKeyPresent("gateway"), false, "Pi-style keyless endpoints need an explicit dummy key");
});
it("revalidates every request of one engine while leaving an in-flight HTTP operation alone", async () => {
save(config({ apiKey: "$GW_KEY", headers: { "x-gateway": "$GW_HEADER" }, compat: { supportsStore: false, supportsDeveloperRole: false, supportsReasoningEffort: false, maxTokensField: "max_tokens" } }));
const engine = piEngineForSpec({ provider: "gateway", id: "chat" });
const context = normalizeContext({ messages: [{ role: "user", content: "hello", timestamp: 1 }] });
let started!: () => void, release!: (response: Response) => void;
const requestStarted = new Promise<void>((resolve) => { started = resolve; });
const firstResponse = new Promise<Response>((resolve) => { release = resolve; });
const requests: { url: string; headers: Headers; body: Record<string, unknown> }[] = [];
const original = globalThis.fetch;
globalThis.fetch = async (input, init) => {
const request = input instanceof Request ? input : new Request(input, init);
requests.push({ url: request.url, headers: request.headers, body: await request.json() as Record<string, unknown> });
if (requests.length === 1) { started(); return firstResponse; }
return sseText("héllo 🌍");
};
const pending = engine.streamFn(engine.model, context);
try {
await requestStarted;
authStore.setAuthVar("GW_KEY", "second-key");
authStore.setAuthVar("GW_HEADER", "second-header");
save(config({ baseUrl: "https://new.example.invalid/inference/v1", apiKey: "$GW_KEY", headers: { "x-gateway": "$GW_HEADER" }, compat: { supportsStore: false, maxTokensField: "max_tokens" } }, [model("chat", { maxTokens: 512 })]));
const second = await engine.streamFn(engine.model, context).result();
assert.equal(second.stopReason, "stop");
assert.deepEqual(second.content, [{ type: "text", text: "héllo 🌍" }]);
assert.equal(second.usage.input, 10);
assert.equal(second.usage.output, 2);
// Credential-only edits must also take effect on this same engine.
authStore.setAuthVar("GW_KEY", "third-key");
assert.equal((await engine.streamFn(engine.model, context).result()).stopReason, "stop");
// None of these cases may dispatch using the engine's old model/key.
save(config({ apiKey: "$MISSING_KEY" }));
assert.equal((await engine.streamFn(engine.model, context).result()).stopReason, "error");
save(config({}, [model("other-model")]));
assert.equal((await engine.streamFn(engine.model, context).result()).stopReason, "error");
fs.writeFileSync(CUSTOM_MODELS_FILE, "invalid configuration");
assert.equal((await engine.streamFn(engine.model, context).result()).stopReason, "error");
fs.rmSync(CUSTOM_MODELS_FILE);
assert.equal((await engine.streamFn(engine.model, context).result()).stopReason, "error");
release(sseText("original request finished"));
const first = await pending.result();
assert.equal(first.stopReason, "stop");
assert.deepEqual(first.content, [{ type: "text", text: "original request finished" }]);
} finally {
release(sseText());
await pending.result();
globalThis.fetch = original;
}
assert.equal(requests.length, 3, "unavailable configurations must not cause additional transport calls");
assert.equal(requests[0]!.url, "http://example.invalid/custom/v1/chat/completions");
assert.equal(requests[0]!.headers.get("authorization"), "Bearer first-key");
assert.equal(requests[0]!.headers.get("x-gateway"), "first-header");
assert.equal(requests[1]!.url, "https://new.example.invalid/inference/v1/chat/completions");
assert.equal(requests[1]!.headers.get("authorization"), "Bearer second-key");
assert.equal(requests[1]!.headers.get("x-gateway"), "second-header");
assert.equal(requests[2]!.headers.get("authorization"), "Bearer third-key");
assert.equal(requests[0]!.body.model, "chat");
assert.equal("store" in requests[0]!.body, false);
assert.equal(requests[0]!.body.max_tokens, 1024);
assert.equal(requests[1]!.body.max_tokens, 512, "current Pi model metadata, not the stale engine model, controls the request");
});
const thinking = (...levels: ThinkingLevel[]) => ({
reasoning: true,
// Pi treats omitted levels through high as supported; mark every
// unselected level explicitly unsupported for these narrow fixtures.
thinkingLevelMap: { off: null, minimal: null, low: null, medium: null, high: null, xhigh: null, max: null, ...Object.fromEntries(levels.map((level) => [level, level])) },
});
const thinkingCases: { name: string; preference?: ThinkingLevel; initial: object; updated: object; before: ThinkingLevel; after: ThinkingLevel }[] = [
{ name: "recovers saved max after high-only metadata expands", preference: "max", initial: thinking("high"), updated: thinking("high", "max"), before: "high", after: "max" },
{ name: "recovers saved max after reasoning-off metadata expands", preference: "max", initial: { reasoning: false }, updated: thinking("high", "max"), before: "off", after: "max" },
{ name: "selects the existing medium-first default after reasoning becomes supported", initial: { reasoning: false }, updated: thinking("low", "medium", "high"), before: "off", after: "medium" },
{ name: "reselects the existing low-before-high default against updated metadata", initial: thinking("high"), updated: thinking("low", "high"), before: "high", after: "low" },
{ name: "clamps saved max when supported thinking contracts", preference: "max", initial: thinking("high", "max"), updated: thinking("high"), before: "max", after: "high" },
{ name: "omits reasoning when the model loses thinking support", preference: "max", initial: thinking("high", "max"), updated: { reasoning: false }, before: "max", after: "off" },
];
for (const scenario of thinkingCases) it(scenario.name, async () => {
const definition = (metadata: object) => config({ compat: { supportsReasoningEffort: true, thinkingFormat: "openai" } }, [model("chat", metadata)]);
save(definition(scenario.initial));
const engine = piEngineForSpec({ provider: "gateway", id: "chat", thinkingLevel: scenario.preference });
assert.equal(engine.level, scenario.before);
const requests: Record<string, unknown>[] = [];
const original = globalThis.fetch;
globalThis.fetch = async (input, init) => {
const request = input instanceof Request ? input : new Request(input, init);
requests.push(await request.json() as Record<string, unknown>);
return sseText();
};
try {
const context = normalizeContext({ messages: [{ role: "user", content: "hello", timestamp: 1 }] });
// A long-lived agent still forwards its initial thinking setting.
const options: SimpleStreamOptions = { reasoning: engine.level === "off" ? undefined : engine.level };
assert.equal((await engine.streamFn(engine.model, context, options).result()).stopReason, "stop");
save(definition(scenario.updated));
assert.equal((await engine.streamFn(engine.model, context, options).result()).stopReason, "stop");
} finally { globalThis.fetch = original; }
assert.equal(requests.length, 2);
for (const [index, level] of [scenario.before, scenario.after].entries()) {
assert.equal(requests[index]!.reasoning_effort, level === "off" ? undefined : level);
if (level === "off") assert.equal("reasoning_effort" in requests[index]!, false);
}
});
it("disables custom routes on malformed edits without removing built-ins or saved choices", () => {
fs.writeFileSync(SETTINGS_FILE, JSON.stringify({ version: 1, models: { strong: { model: { provider: "gateway", id: "chat" }, thinkingLevel: "high" } } }));
const before = fs.readFileSync(SETTINGS_FILE, "utf8");
fs.writeFileSync(CUSTOM_MODELS_FILE, '{ secret-token-do-not-log');
const catalog = modelCatalog();
assert.ok(catalog.customProvidersError);
assert.equal(catalog.customProvidersError!.includes("secret-token"), false);
assert.equal(piModels().getProvider("gateway"), undefined);
assert.ok(piModels().getProvider("openai"));
assert.deepEqual(catalog.slots.strong!.model, { provider: "gateway", id: "chat" });
const stored = { strong: { model: { provider: "gateway", id: "chat" }, thinkingLevel: "high" as const } };
const repaired = { strong: { ...stored.strong, thinkingLevel: "max" as const }, cheap: { model: { provider: "openai", id: piModels().getModels("openai")[0]!.id } } };
assert.equal(validateModelsSettings(repaired, stored).ok, true, "an unavailable custom pin must not prevent independent slot repair");
assert.equal(clampModelsLevels(repaired).strong!.thinkingLevel, "max", "preserve thinking while custom metadata is unavailable");
assert.equal(validateModelsSettings({ cheap: stored.strong }, stored).ok, false, "unchanged identity preservation is same-slot only");
assert.equal(fs.readFileSync(SETTINGS_FILE, "utf8"), before);
assert.throws(() => piEngineForSpec({ provider: "gateway", id: "chat" }), /catalog has no model/);
save(config());
assert.equal(modelCatalog().customProvidersError, undefined);
assert.ok(piModels().getProvider("gateway"));
fs.rmSync(CUSTOM_MODELS_FILE);
assert.equal(modelCatalog().models.some((m) => m.provider === "gateway"), false);
assert.equal(modelCatalog().customProvidersError, undefined);
assert.equal(fs.readFileSync(SETTINGS_FILE, "utf8"), before);
});
it("refreshes credentials without a file edit, preserving native header precedence and snapshots", async () => {
const values = new Map([["KEY", "one"], ["HEADER", "first"]]);
const source = new CustomProviderSource(() => ({ text: JSON.stringify(config({ apiKey: "$KEY", authHeader: true, headers: { "x-shared": "$HEADER" } }, [model("chat", { headers: { "x-shared": "model-${HEADER}" } })])) }), (name) => values.get(name));
const models = createModels();
source.sync(models);
const captured = createModels();
captured.setProvider(models.getProvider("gateway")!);
values.set("KEY", "two"); values.set("HEADER", "second");
source.sync(models);
const current = (await models.getAuth(models.getModel("gateway", "chat")!))!.auth;
const previous = (await captured.getAuth(captured.getModel("gateway", "chat")!))!.auth;
assert.equal(current.apiKey, "two");
assert.equal(current.headers!["x-shared"], "model-second");
assert.equal(current.headers!.Authorization, "Bearer two");
assert.equal(previous.apiKey, "one");
assert.equal(previous.headers!["x-shared"], "model-first");
});
it("rejects built-in, subscription and native CLI collisions without replacing their providers", () => {
for (const id of ["openai", "github-copilot", "chatgpt", "github-copilot-subscription", "xai-subscription", CLAUDE_CODE_PROVIDER]) {
const existing = piModels().getProvider("openai")!;
const nativeCli = piModels().getProvider(CLAUDE_CODE_PROVIDER)!;
const models = createModels(); models.setProvider(existing); models.setProvider(nativeCli);
const source = new CustomProviderSource(() => ({ text: JSON.stringify({ providers: { [id]: config().providers.gateway } }) }), () => undefined);
source.sync(models);
assert.ok(source.getError());
assert.equal(models.getProvider("openai"), existing);
assert.equal(models.getProvider(CLAUDE_CODE_PROVIDER), nativeCli);
assert.equal(models.getProviders().length, 2);
}
});
it("cannot bypass CLI opt-in or replace/remove the native provider across custom configuration reloads", (t) => {
const nativeCli = piModels().getProvider(CLAUDE_CODE_PROVIDER)!;
assert.ok(nativeCli);
const aliases = piModels().getModels(CLAUDE_CODE_PROVIDER);
const savedConfig = fs.existsSync(CUSTOM_MODELS_FILE) ? fs.readFileSync(CUSTOM_MODELS_FILE, "utf8") : undefined;
const savedSettings = fs.existsSync(SETTINGS_FILE) ? fs.readFileSync(SETTINGS_FILE, "utf8") : undefined;
t.after(() => {
if (savedConfig === undefined) fs.rmSync(CUSTOM_MODELS_FILE, { force: true }); else fs.writeFileSync(CUSTOM_MODELS_FILE, savedConfig);
if (savedSettings === undefined) fs.rmSync(SETTINGS_FILE, { force: true }); else fs.writeFileSync(SETTINGS_FILE, savedSettings);
piModels();
});
fs.writeFileSync(SETTINGS_FILE, JSON.stringify({ version: 1, claudeCode: { enabled: false } }));
const collision = { providers: { [CLAUDE_CODE_PROVIDER]: config().providers.gateway } };
for (const [value, error] of [
[collision, true], // Initial collision must never enter registered/ready.
[config(), false], // A normal custom route still works.
[collision, true], // Invalid reload retires custom routes, not native ones.
["malformed configuration", true],
[undefined, false], // Removing the file must not delete the native CLI.
[config(), false],
[undefined, false],
] as const) {
if (value === undefined) fs.rmSync(CUSTOM_MODELS_FILE, { force: true });
else if (typeof value === "string") fs.writeFileSync(CUSTOM_MODELS_FILE, value);
else save(value);
const catalog = modelCatalog();
assert.equal(Boolean(catalog.customProvidersError), error);
assert.equal(customProviderConfigured(CLAUDE_CODE_PROVIDER), undefined);
assert.equal(providerKeyPresent(CLAUDE_CODE_PROVIDER), false, "custom auth must never bypass disabled CLI admission");
assert.equal(piModels().getProvider(CLAUDE_CODE_PROVIDER), nativeCli, "preserve the native provider identity, not a reconstructed substitute");
assert.deepEqual(piModels().getModels(CLAUDE_CODE_PROVIDER), aliases);
assert.equal(catalog.models.some((m) => m.provider === CLAUDE_CODE_PROVIDER), false);
}
});
it("fails closed across partial publication and recovers without leaking internal errors", () => {
const text = JSON.stringify({ providers: { ...config().providers, second: config().providers.gateway } });
const source = new CustomProviderSource(() => ({ text }), () => undefined);
const models = createModels();
const builtin = piModels().getProvider("openai")!;
models.setProvider(builtin);
const publish = models.setProvider.bind(models);
models.setProvider = (provider) => {
if (provider.id === "second") throw new Error("private-value-do-not-log");
publish(provider);
};
source.sync(models);
assert.equal(models.getProvider("gateway"), undefined);
assert.equal(models.getProvider("second"), undefined);
assert.equal(models.getProvider("openai"), builtin);
assert.equal(source.getError()!.includes("private-value"), false);
models.setProvider = publish;
source.sync(models);
assert.ok(models.getProvider("gateway"));
assert.ok(models.getProvider("second"));
assert.equal(source.getError(), undefined);
});
it("retires old custom routes when server configuration bootstrap is reinstalled", () => {
save(config());
assert.ok(piModels().getProvider("gateway"));
fs.rmSync(CUSTOM_MODELS_FILE);
installCustomProviderSource(keyValue);
assert.equal(piModels().getProvider("gateway"), undefined);
assert.ok(piModels().getProvider("openai"));
});
it("treats unreadable config as unavailable and recovers without a restart", () => {
let unreadable = true;
const source = new CustomProviderSource(() => unreadable ? { error: "Unable to read models.json." } : { text: JSON.stringify(config()) }, () => undefined);
const models = createModels();
source.sync(models);
assert.equal(source.getError(), "Unable to read models.json.");
unreadable = false;
source.sync(models);
assert.ok(models.getProvider("gateway"));
assert.equal(source.getError(), undefined);
});
});
+10
View File
@@ -0,0 +1,10 @@
/** Synthetic authorized records, imported only after each test's isolated env. */
import { randomBytes } from "node:crypto";
import type { CustomProviderSettings } from "../../src/core/settings.ts";
import { persistCustomAuth } from "../../src/server/custom-provider-auth.ts";
export function authorizeFixture(cp: CustomProviderSettings, fileKey?: string): CustomProviderSettings {
const id = randomBytes(16).toString("hex");
persistCustomAuth({ version: 1, id, baseUrl: cp.baseUrl, authMode: cp.authMode ?? "keyless", ...(fileKey ? { fileKeyBaseUrl: cp.baseUrl } : {}) }, fileKey);
return { ...cp, credentialBindingId: id };
}
+56
View File
@@ -0,0 +1,56 @@
/** Child-process fixture: owned profile only; every upstream fetch is synthetic. */
import fs from "node:fs";
import { AUTH_FILE, SETTINGS_FILE } from "../../config.ts";
const [mode, action = "key", fault = "none"] = process.argv.slice(2);
const requests: { url: string; authorization: string | null }[] = [];
globalThis.fetch = async (url, init) => {
requests.push({ url: String(url), authorization: new Headers(init?.headers).get("authorization") });
if (String(url).endsWith("/models")) return Response.json({ data: [{ id: "m" }] });
return new Response('data: {"choices":[{"index":0,"delta":{"role":"assistant","content":"ok"},"finish_reason":null}]}\n\ndata: {"choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}\n\ndata: [DONE]\n\n', { headers: { "content-type": "text/event-stream" } });
};
const rename = fs.renameSync;
const write = fs.writeFileSync;
const read = fs.readFileSync;
let authRenames = 0;
fs.renameSync = (...args: Parameters<typeof rename>) => {
if (args[1] === AUTH_FILE) {
authRenames++;
if (fault === "auth-before-rename" || (fault === "double-io" && authRenames > 1)) throw new Error("fixture auth I/O failure");
rename(...args);
if (fault === "after-auth") process.exit(77);
return;
}
return rename(...args);
};
fs.writeFileSync = (...args: Parameters<typeof write>) => {
if (args[0] === SETTINGS_FILE && fault === "double-io") throw new Error("fixture settings I/O failure");
if (args[0] === SETTINGS_FILE && fault === "settings-landed") { write(...args); throw new Error("fixture ambiguous settings completion"); }
return write(...args);
};
fs.readFileSync = ((...args: Parameters<typeof read>) => {
if (args[0] === AUTH_FILE && fault === "unreadable") throw Object.assign(new Error("fixture unreadable auth"), { code: "EACCES" });
return read(...args);
}) as typeof read;
await import("../../src/server/keys.ts");
if (mode === "inspect") {
const { piModels, piEngineForSpec } = await import("../../src/agent/models.ts");
const { normalizeContext } = await import("@earendil-works/pi-ai");
const enabled = Boolean(piModels().getProvider("custom"));
let reason: string | undefined;
try {
const engine = piEngineForSpec({ provider: "custom", id: "m" });
reason = (await engine.streamFn(engine.model, normalizeContext({ messages: [{ role: "user", content: "fixture", timestamp: 0 }] }), {}).result()).stopReason;
} catch { reason = "refused"; }
const { keyStatuses } = await import("../../src/server/keys.ts");
console.log(JSON.stringify({ enabled, reason, requests, statusIds: keyStatuses().map(row => row.id), metadataInEnv: process.env.__learnharness_custom_provider_binding !== undefined }));
} else {
const route = await import("../../app/api/custom-provider/route.ts");
const get = route.GET(new Request("http://localhost/api/custom-provider"));
const payload = action === "keyless" ? { baseUrl: "http://second.invalid/v1", authMode: "keyless" }
: action === "envMove" ? { baseUrl: "http://second.invalid/v1", authMode: "apiKey", reuseKey: true }
: action === "reuse" ? { baseUrl: "http://first.invalid/v1", authMode: "apiKey", reuseKey: true }
: { baseUrl: "http://second.invalid/v1", authMode: "apiKey", key: "fixture-new-key" };
const request = new Request("http://localhost/api/custom-provider", { method: action === "disconnect" ? "DELETE" : "POST", headers: { origin: "http://localhost", "content-type": "application/json", "if-match": get.headers.get("etag")! }, body: JSON.stringify(payload) });
const result = await route.POST(request);
console.log(JSON.stringify({ status: result.status, body: await result.json(), requests }));
}