121 lines
3.5 KiB
TypeScript
121 lines
3.5 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
import registerLmStudio from "./index.ts";
|
|
|
|
type Registration = {
|
|
name: string;
|
|
config: {
|
|
baseUrl?: string;
|
|
refreshModels?: (context: {
|
|
allowNetwork: boolean;
|
|
signal: AbortSignal;
|
|
stored?: { models: readonly unknown[] };
|
|
}) => Promise<unknown[]>;
|
|
};
|
|
};
|
|
|
|
function captureRegistration(): Registration {
|
|
let registration: Registration | undefined;
|
|
registerLmStudio({
|
|
registerProvider(name: string, config: Registration["config"]) {
|
|
registration = { name, config };
|
|
},
|
|
} as unknown as ExtensionAPI);
|
|
assert.ok(registration);
|
|
return registration;
|
|
}
|
|
|
|
test("registers one LM Studio provider at the exact loopback endpoint", () => {
|
|
const registration = captureRegistration();
|
|
assert.equal(registration.name, "lm-studio");
|
|
assert.equal(registration.config.baseUrl, "http://127.0.0.1:1234/v1");
|
|
});
|
|
|
|
test("refresh validates and maps the LM Studio model catalog", async () => {
|
|
const registration = captureRegistration();
|
|
assert.ok(registration.config.refreshModels);
|
|
const originalFetch = globalThis.fetch;
|
|
globalThis.fetch = async (input) => {
|
|
assert.equal(String(input), "http://127.0.0.1:1234/v1/models");
|
|
return new Response(
|
|
JSON.stringify({
|
|
data: [
|
|
{
|
|
id: "fixture-model",
|
|
name: "Fixture Model",
|
|
context_window: 32768,
|
|
max_tokens: 4096,
|
|
},
|
|
],
|
|
}),
|
|
{ status: 200, headers: { "content-type": "application/json" } },
|
|
);
|
|
};
|
|
try {
|
|
const models = await registration.config.refreshModels({
|
|
allowNetwork: true,
|
|
signal: AbortSignal.timeout(1000),
|
|
});
|
|
assert.deepEqual(models, [
|
|
{
|
|
id: "fixture-model",
|
|
name: "Fixture Model",
|
|
reasoning: false,
|
|
input: ["text"],
|
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
contextWindow: 32768,
|
|
maxTokens: 4096,
|
|
compat: {
|
|
supportsDeveloperRole: false,
|
|
supportsReasoningEffort: false,
|
|
supportsUsageInStreaming: false,
|
|
maxTokensField: "max_tokens",
|
|
},
|
|
},
|
|
]);
|
|
} finally {
|
|
globalThis.fetch = originalFetch;
|
|
}
|
|
});
|
|
|
|
test("refresh reuses stored models when network access is disabled", async () => {
|
|
const registration = captureRegistration();
|
|
assert.ok(registration.config.refreshModels);
|
|
const originalFetch = globalThis.fetch;
|
|
globalThis.fetch = async () => {
|
|
throw new Error("offline refresh attempted network access");
|
|
};
|
|
const stored = [{ id: "stored-model" }];
|
|
try {
|
|
const models = await registration.config.refreshModels({
|
|
allowNetwork: false,
|
|
signal: AbortSignal.timeout(1000),
|
|
stored: { models: stored },
|
|
});
|
|
assert.deepEqual(models, stored);
|
|
assert.notEqual(models, stored);
|
|
} finally {
|
|
globalThis.fetch = originalFetch;
|
|
}
|
|
});
|
|
|
|
test("refresh rejects malformed model catalogs", async () => {
|
|
const registration = captureRegistration();
|
|
assert.ok(registration.config.refreshModels);
|
|
const originalFetch = globalThis.fetch;
|
|
globalThis.fetch = async () =>
|
|
new Response(JSON.stringify({ data: "not-an-array" }), { status: 200 });
|
|
try {
|
|
await assert.rejects(
|
|
registration.config.refreshModels({
|
|
allowNetwork: true,
|
|
signal: AbortSignal.timeout(1000),
|
|
}),
|
|
/invalid \/v1\/models response/,
|
|
);
|
|
} finally {
|
|
globalThis.fetch = originalFetch;
|
|
}
|
|
});
|