309 lines
11 KiB
TypeScript
309 lines
11 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { readFile } from "node:fs/promises";
|
|
import test from "node:test";
|
|
import { liteLlm, liteLlmSandboxName } from "../src/litellm.ts";
|
|
import { SkipError } from "../src/needs.ts";
|
|
import type { DaytonaExec } from "@openwork/hosts";
|
|
import type { LiteLlmUpstreamRequest, Place } from "../src/index.ts";
|
|
|
|
const MODEL_ID = "openwork-litellm-unit-model";
|
|
const PINNED_IMAGE = "ghcr.io/berriai/litellm:v1.97.0@sha256:468c25f35f3e5ec4e414974f00deab93337b1b4d9953cabcfd3722e59415f834";
|
|
|
|
interface ExecCall {
|
|
args: string[];
|
|
opts?: { input?: string; timeoutMs?: number };
|
|
}
|
|
|
|
interface FakeOptions {
|
|
deleteFailures?: number;
|
|
invalidHealth?: boolean;
|
|
previewOnStderr?: boolean;
|
|
requests?: () => LiteLlmUpstreamRequest[];
|
|
}
|
|
|
|
const daytonaPlace: Place = {
|
|
kind: "daytona",
|
|
host() {
|
|
return undefined;
|
|
},
|
|
async db() {
|
|
throw new Error("unused test database");
|
|
},
|
|
async exposeMock(handle) {
|
|
return new URL(handle.url);
|
|
},
|
|
denBase() {
|
|
return { kind: "daytona", ref: "unit-test" };
|
|
},
|
|
};
|
|
|
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
}
|
|
|
|
function remoteScript(args: string[]): string {
|
|
const wrapped = args[3] ?? "";
|
|
const prefix = "bash -lc '";
|
|
return wrapped.startsWith(prefix) && wrapped.endsWith("'")
|
|
? wrapped.slice(prefix.length, -1)
|
|
: "";
|
|
}
|
|
|
|
function makeFake(options: FakeOptions = {}): {
|
|
calls: ExecCall[];
|
|
exec: DaytonaExec;
|
|
fetchImpl: typeof fetch;
|
|
dockerfile(): string;
|
|
masterKey(): string;
|
|
upstreamKey(): string;
|
|
controlKey(): string;
|
|
modelInfo(): Record<string, unknown>;
|
|
} {
|
|
const calls: ExecCall[] = [];
|
|
const uploads = new Map<string, string>();
|
|
let dockerfile = "";
|
|
let masterKey = "";
|
|
let upstreamKey = "";
|
|
let controlKey = "";
|
|
let modelInfo: Record<string, unknown> = {};
|
|
let deleteAttempts = 0;
|
|
const exec: DaytonaExec = async (args, opts) => {
|
|
calls.push({ args: [...args], opts });
|
|
if (args[0] === "create") {
|
|
const dockerfileIndex = args.indexOf("--dockerfile") + 1;
|
|
dockerfile = await readFile(args[dockerfileIndex] ?? "", "utf8");
|
|
return { stdout: "created\n", stderr: "", code: 0 };
|
|
}
|
|
if (args[0] === "preview-url") {
|
|
const port = args[args.indexOf("-p") + 1] ?? "";
|
|
if (options.previewOnStderr) {
|
|
return { stdout: "", stderr: "Upgrade at https://updates.example.test\n", code: 0 };
|
|
}
|
|
return { stdout: `Preview URL: https://port-${port}.example.test\n`, stderr: "", code: 0 };
|
|
}
|
|
if (args[0] === "delete") {
|
|
deleteAttempts += 1;
|
|
if (deleteAttempts <= (options.deleteFailures ?? 0)) {
|
|
return { stdout: "transient deletion failure\n", stderr: "", code: 1 };
|
|
}
|
|
return { stdout: "deleted\n", stderr: "", code: 0 };
|
|
}
|
|
if (args[0] !== "exec") return { stdout: "", stderr: "", code: 0 };
|
|
|
|
const script = remoteScript(args);
|
|
const chunk = /^printf %s ([A-Za-z0-9+/=]+) >> (\/tmp\/[A-Za-z0-9._/-]+\.b64)$/.exec(script);
|
|
if (chunk?.[1] && chunk[2]) uploads.set(chunk[2], `${uploads.get(chunk[2]) ?? ""}${chunk[1]}`);
|
|
const finalize = /base64 -d (\/tmp\/[A-Za-z0-9._/-]+\.b64) > (\/tmp\/[A-Za-z0-9._/-]+)/.exec(script);
|
|
if (finalize?.[1] && finalize[2]) {
|
|
const content = Buffer.from(uploads.get(finalize[1]) ?? "", "base64").toString("utf8");
|
|
if (finalize[2].endsWith("config.json")) {
|
|
const parsed: unknown = JSON.parse(content);
|
|
const general = isRecord(parsed) && isRecord(parsed.general_settings) ? parsed.general_settings : {};
|
|
const models = isRecord(parsed) && Array.isArray(parsed.model_list) ? parsed.model_list.filter(isRecord) : [];
|
|
const params = models[0] && isRecord(models[0].litellm_params) ? models[0].litellm_params : {};
|
|
masterKey = typeof general.master_key === "string" ? general.master_key : "";
|
|
upstreamKey = typeof params.api_key === "string" ? params.api_key : "";
|
|
modelInfo = models[0] && isRecord(models[0].model_info) ? models[0].model_info : {};
|
|
}
|
|
}
|
|
if (script.includes("tail -80")) {
|
|
return { stdout: `${masterKey}\n${upstreamKey}\n${controlKey}\n`, stderr: "", code: 0 };
|
|
}
|
|
return { stdout: "", stderr: "", code: 0 };
|
|
};
|
|
const fetchImpl: typeof fetch = async (input, init) => {
|
|
const url = new URL(String(input));
|
|
if (url.pathname === "/__openwork_litellm/health") {
|
|
const authorization = new Headers(init?.headers).get("authorization") ?? "";
|
|
controlKey = authorization.startsWith("Bearer ") ? authorization.slice("Bearer ".length) : "";
|
|
return Response.json(options.invalidHealth ? { ok: false, sequence: 0 } : { ok: true, sequence: 3 });
|
|
}
|
|
if (url.pathname === "/__openwork_litellm/requests") {
|
|
return Response.json({ sequence: 3, requests: options.requests?.() ?? [] });
|
|
}
|
|
if (url.pathname === "/v1/models") {
|
|
return Response.json({ object: "list", data: [{ id: MODEL_ID }] });
|
|
}
|
|
return Response.json({ error: "not found" }, { status: 404 });
|
|
};
|
|
return {
|
|
calls,
|
|
exec,
|
|
fetchImpl,
|
|
dockerfile: () => dockerfile,
|
|
masterKey: () => masterKey,
|
|
upstreamKey: () => upstreamKey,
|
|
controlKey: () => controlKey,
|
|
modelInfo: () => modelInfo,
|
|
};
|
|
}
|
|
|
|
test("LiteLLM Daytona sandbox names are safe and unique", () => {
|
|
const first = liteLlmSandboxName();
|
|
const second = liteLlmSandboxName();
|
|
|
|
assert.match(first, /^openwork-litellm-eval-[0-9]+-[a-z0-9]+-[0-9a-f]{8}$/);
|
|
assert.notEqual(first, second);
|
|
});
|
|
|
|
test("LiteLLM database mode skips Daytona placement before provisioning", async () => {
|
|
const fake = makeFake();
|
|
await assert.rejects(
|
|
liteLlm({
|
|
place: daytonaPlace,
|
|
modelId: MODEL_ID,
|
|
reply: "deterministic reply",
|
|
database: true,
|
|
daytonaExec: fake.exec,
|
|
fetchImpl: fake.fetchImpl,
|
|
}),
|
|
(error: unknown) => error instanceof SkipError
|
|
&& error.reason === "LiteLLM database mode currently requires docker placement",
|
|
);
|
|
assert.equal(fake.calls.length, 0);
|
|
});
|
|
|
|
test("Daytona LiteLLM pins its image, verifies bounded uploads, exposes both ports, and uses sequence cursors", async () => {
|
|
let upstreamFingerprint = "";
|
|
const fake = makeFake({
|
|
requests: () => [
|
|
{ sequence: 1, model: "older", tokenId: "older-token", bodyText: "older" },
|
|
{ sequence: 2, model: MODEL_ID, tokenId: upstreamFingerprint, bodyText: "probe" },
|
|
{ sequence: 3, model: MODEL_ID, tokenId: "other-token", bodyText: "chat" },
|
|
],
|
|
});
|
|
const gateway = await liteLlm({
|
|
place: daytonaPlace,
|
|
modelId: MODEL_ID,
|
|
reply: "deterministic reply",
|
|
maxInputTokens: 96_000,
|
|
maxOutputTokens: 12_345,
|
|
daytonaExec: fake.exec,
|
|
fetchImpl: fake.fetchImpl,
|
|
});
|
|
upstreamFingerprint = gateway.tokenId(gateway.upstreamKey);
|
|
|
|
const create = fake.calls.find((call) => call.args[0] === "create");
|
|
assert(create);
|
|
assert.match(fake.dockerfile(), new RegExp(`^FROM ${PINNED_IMAGE.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`, "m"));
|
|
assert.match(fake.dockerfile(), /^ENTRYPOINT \["\/usr\/bin\/bash", "-lc"\]$/m);
|
|
assert.match(fake.dockerfile(), /^CMD \["sleep infinity"\]$/m);
|
|
for (const [flag, value] of [
|
|
["--auto-stop", "60"],
|
|
["--auto-delete", "0"],
|
|
["--ttl", "120"],
|
|
["--target", "us"],
|
|
]) {
|
|
assert.equal(create.args[create.args.indexOf(flag) + 1], value);
|
|
}
|
|
assert(create.args.includes("--dockerfile"));
|
|
assert(create.args.includes("--public"));
|
|
|
|
const launchIndex = fake.calls.findIndex((call) => remoteScript(call.args).includes("/app/.venv/bin/litellm"));
|
|
assert(launchIndex > 0);
|
|
const uploadCalls = fake.calls
|
|
.map((call, index) => ({ index, script: remoteScript(call.args) }))
|
|
.filter(({ script }) => script.includes(".b64"));
|
|
assert(uploadCalls.length > 4);
|
|
assert(uploadCalls.every(({ index }) => index < launchIndex));
|
|
const chunks = uploadCalls.flatMap(({ script }) => {
|
|
const match = /^printf %s ([A-Za-z0-9+/=]+) >> /.exec(script);
|
|
return match?.[1] ? [match[1]] : [];
|
|
});
|
|
assert(chunks.length >= 2);
|
|
assert(chunks.every((chunk) => chunk.length <= 8 * 1_024));
|
|
const finalizers = uploadCalls.filter(({ script }) => script.includes("actual_bytes=$(wc -c"));
|
|
assert.equal(finalizers.length, 2);
|
|
assert(finalizers.every(({ script }) => script.includes('test "$decode_status" -eq 0 && test "$actual_bytes"')));
|
|
assert(finalizers.every(({ script }) => /test "\$actual_bytes" -eq [1-9][0-9]*/.test(script)));
|
|
|
|
const previews = fake.calls.filter((call) => call.args[0] === "preview-url");
|
|
assert.deepEqual(
|
|
previews.map((call) => ({
|
|
port: call.args[call.args.indexOf("-p") + 1],
|
|
expires: call.args[call.args.indexOf("--expires") + 1],
|
|
})),
|
|
[{ port: "4000", expires: "7200" }, { port: "4001", expires: "7200" }],
|
|
);
|
|
assert.equal(gateway.baseUrl, "https://port-4000.example.test/v1");
|
|
assert.deepEqual(fake.modelInfo(), {
|
|
max_input_tokens: 96_000,
|
|
max_output_tokens: 12_345,
|
|
supports_function_calling: true,
|
|
supports_vision: true,
|
|
supports_reasoning: false,
|
|
supports_response_schema: true,
|
|
supported_openai_params: ["temperature", "tools", "response_format"],
|
|
});
|
|
assert.equal(await gateway.checkpoint(), 3);
|
|
assert.deepEqual((await gateway.upstreamRequests({ after: 1 })).map((request) => request.sequence), [2, 3]);
|
|
assert.equal((await gateway.waitForUpstreamRequest({
|
|
after: 1,
|
|
model: MODEL_ID,
|
|
key: gateway.upstreamKey,
|
|
timeoutMs: 1_000,
|
|
})).sequence, 2);
|
|
|
|
await gateway[Symbol.asyncDispose]();
|
|
await gateway[Symbol.asyncDispose]();
|
|
const deletions = fake.calls.filter((call) => call.args[0] === "delete");
|
|
assert.equal(deletions.length, 1);
|
|
assert.equal(deletions[0]?.opts?.input, "y\n");
|
|
});
|
|
|
|
test("Daytona LiteLLM redacts every key and deletes its sandbox after startup failure", async () => {
|
|
const fake = makeFake({ invalidHealth: true });
|
|
let failure: unknown;
|
|
try {
|
|
await liteLlm({
|
|
place: daytonaPlace,
|
|
modelId: MODEL_ID,
|
|
reply: "deterministic reply",
|
|
daytonaExec: fake.exec,
|
|
fetchImpl: fake.fetchImpl,
|
|
});
|
|
} catch (error) {
|
|
failure = error;
|
|
}
|
|
|
|
assert(failure instanceof Error);
|
|
for (const secret of [fake.masterKey(), fake.upstreamKey(), fake.controlKey()]) {
|
|
assert(secret.length > 20);
|
|
assert(!failure.message.includes(secret));
|
|
}
|
|
assert.match(failure.message, /\[REDACTED\]/);
|
|
assert.match(failure.message, /Daytona logs/);
|
|
assert.equal(fake.calls.filter((call) => call.args[0] === "delete").length, 1);
|
|
});
|
|
|
|
test("Daytona LiteLLM ignores unrelated HTTPS URLs on CLI stderr", async () => {
|
|
const fake = makeFake({ previewOnStderr: true });
|
|
|
|
await assert.rejects(
|
|
liteLlm({
|
|
place: daytonaPlace,
|
|
modelId: MODEL_ID,
|
|
reply: "deterministic reply",
|
|
daytonaExec: fake.exec,
|
|
fetchImpl: fake.fetchImpl,
|
|
}),
|
|
/did not return an HTTPS URL/,
|
|
);
|
|
assert.equal(fake.calls.filter((call) => call.args[0] === "delete").length, 1);
|
|
});
|
|
|
|
test("Daytona LiteLLM disposal retries a transient sandbox deletion failure", async () => {
|
|
const fake = makeFake({ deleteFailures: 1 });
|
|
const gateway = await liteLlm({
|
|
place: daytonaPlace,
|
|
modelId: MODEL_ID,
|
|
reply: "deterministic reply",
|
|
daytonaExec: fake.exec,
|
|
fetchImpl: fake.fetchImpl,
|
|
});
|
|
|
|
await assert.rejects(gateway[Symbol.asyncDispose](), /Sandbox deletion gate failed/);
|
|
await gateway[Symbol.asyncDispose]();
|
|
assert.equal(fake.calls.filter((call) => call.args[0] === "delete").length, 2);
|
|
});
|