evOo-local/packages/adapters/src/pi-local-provider.test.ts
cynarAI 6c7eed7580 Initial clean evOo-local from f00577b
Fresh history cut from cynarAI/evoo antigravity-handoff/project-timeline
(Voice First + project timeline). Brand docs/titles as evOo; keep @rakazo/*
packages as tech debt. Excludes node_modules, caches, secrets, voice blobs.
2026-09-20 17:15:16 +02:00

118 lines
4.2 KiB
TypeScript

import { builtinModels } from "@earendil-works/pi-ai/providers/all";
import { afterEach, describe, expect, it } from "vitest";
import {
LOCAL_PROVIDER_ID,
localBaseUrl,
localProvider,
registerLocalProvider,
} from "./pi-local-provider.js";
const ENV_KEYS = [
"RAKAZO_LOCAL_MODELS",
"RAKAZO_LOCAL_MODELS_URL",
"RAKAZO_LOCAL_CONTEXT_WINDOW",
"RAKAZO_LOCAL_MAX_TOKENS",
] as const;
const saved = new Map<string, string | undefined>();
for (const key of ENV_KEYS) saved.set(key, process.env[key]);
afterEach(() => {
for (const key of ENV_KEYS) {
const value = saved.get(key);
if (value === undefined) delete process.env[key];
else process.env[key] = value;
}
});
function setModels(value: string | undefined) {
if (value === undefined) delete process.env.RAKAZO_LOCAL_MODELS;
else process.env.RAKAZO_LOCAL_MODELS = value;
}
describe("local model provider", () => {
it("stays absent when no local models are configured", () => {
setModels(undefined);
expect(localProvider()).toBeUndefined();
// Blank and whitespace-only values are configuration noise, not a model id.
setModels("");
expect(localProvider()).toBeUndefined();
setModels(" , ,");
expect(localProvider()).toBeUndefined();
});
it("exposes each configured model id verbatim", () => {
setModels("qwen3:4b, llama3.1:8b");
const provider = localProvider();
expect(provider?.id).toBe(LOCAL_PROVIDER_ID);
expect(provider?.getModels().map((model) => model.id)).toEqual(["qwen3:4b", "llama3.1:8b"]);
});
it("bills nothing, because the model runs on the operator's own hardware", () => {
setModels("qwen3:4b");
const model = localProvider()?.getModels()[0];
expect(model?.cost).toEqual({ input: 0, output: 0, cacheRead: 0, cacheWrite: 0 });
});
it("defaults to the Ollama endpoint and honours an override", () => {
delete process.env.RAKAZO_LOCAL_MODELS_URL;
expect(localBaseUrl()).toBe("http://127.0.0.1:11434/v1");
process.env.RAKAZO_LOCAL_MODELS_URL = "http://127.0.0.1:1234/v1";
setModels("some-local-model");
expect(localBaseUrl()).toBe("http://127.0.0.1:1234/v1");
expect(localProvider()?.getModels()[0]?.baseUrl).toBe("http://127.0.0.1:1234/v1");
});
it("rejects an invalid or non-HTTP endpoint", () => {
for (const bad of ["not-a-url", "file:///tmp/model.sock", "ftp://127.0.0.1/models"]) {
process.env.RAKAZO_LOCAL_MODELS_URL = bad;
expect(() => localBaseUrl(), `endpoint "${bad}"`).toThrow(
/RAKAZO_LOCAL_MODELS_URL must be an absolute HTTP\(S\) URL/,
);
}
});
it("defaults token limits when unset, and honours valid overrides", () => {
setModels("qwen3:4b");
delete process.env.RAKAZO_LOCAL_CONTEXT_WINDOW;
delete process.env.RAKAZO_LOCAL_MAX_TOKENS;
let model = localProvider()?.getModels()[0];
expect(model?.contextWindow).toBe(32_768);
expect(model?.maxTokens).toBe(4_096);
process.env.RAKAZO_LOCAL_CONTEXT_WINDOW = "8192";
process.env.RAKAZO_LOCAL_MAX_TOKENS = "512";
model = localProvider()?.getModels()[0];
expect(model?.contextWindow).toBe(8192);
expect(model?.maxTokens).toBe(512);
});
it("rejects token limits that are not positive integers", () => {
setModels("qwen3:4b");
// A typo must not masquerade as the default, and a negative or fractional
// window must not reach the model definition.
for (const bad of ["abc", "-5", "0", "3.7", "Infinity", "1e400"]) {
process.env.RAKAZO_LOCAL_CONTEXT_WINDOW = bad;
expect(() => localProvider(), `context window "${bad}"`).toThrow(
/RAKAZO_LOCAL_CONTEXT_WINDOW must be a positive integer/,
);
}
delete process.env.RAKAZO_LOCAL_CONTEXT_WINDOW;
process.env.RAKAZO_LOCAL_MAX_TOKENS = "-1";
expect(() => localProvider()).toThrow(/RAKAZO_LOCAL_MAX_TOKENS must be a positive integer/);
});
it("registers onto a Models collection only when configured", () => {
setModels(undefined);
const bare = registerLocalProvider(builtinModels());
expect(bare.getProviders().some((p) => p.id === LOCAL_PROVIDER_ID)).toBe(false);
setModels("qwen3:4b");
const withLocal = registerLocalProvider(builtinModels());
expect(withLocal.getModel(LOCAL_PROVIDER_ID, "qwen3:4b")).toBeDefined();
});
});