fix(plugins): cancel self-hosted probe error bodies

This commit is contained in:
Vincent Koc
2026-06-19 12:03:31 +02:00
parent 797865c9dc
commit eb7da0a2e5
2 changed files with 51 additions and 1 deletions

View File

@@ -86,10 +86,30 @@ async function configureSelfHostedTestProvider(params: {
});
}
function cancelTrackedResponse(init?: ResponseInit): {
response: Response;
wasCanceled: () => boolean;
} {
let canceled = false;
const stream = new ReadableStream<Uint8Array>({
start(controller) {
controller.enqueue(new TextEncoder().encode("ignored"));
},
cancel() {
canceled = true;
},
});
return {
response: new Response(stream, init),
wasCanceled: () => canceled,
};
}
describe("discoverOpenAICompatibleLocalModels", () => {
it("uses guarded fetch pinned to the configured self-hosted provider", async () => {
const release = vi.fn(async () => undefined);
const propsRelease = vi.fn(async () => undefined);
const propsResponse = cancelTrackedResponse({ status: 404 });
fetchWithSsrFGuardMock.mockResolvedValueOnce({
response: new Response(JSON.stringify({ data: [{ id: "Qwen/Qwen3-32B" }] }), {
status: 200,
@@ -98,7 +118,7 @@ describe("discoverOpenAICompatibleLocalModels", () => {
release,
});
fetchWithSsrFGuardMock.mockResolvedValueOnce({
response: new Response("{}", { status: 404 }),
response: propsResponse.response,
finalUrl: "http://127.0.0.1:8000/props",
release: propsRelease,
});
@@ -141,6 +161,28 @@ describe("discoverOpenAICompatibleLocalModels", () => {
});
expect(release).toHaveBeenCalledOnce();
expect(propsRelease).toHaveBeenCalledOnce();
expect(propsResponse.wasCanceled()).toBe(true);
});
it("cancels model discovery error bodies before falling back", async () => {
const release = vi.fn(async () => undefined);
const response = cancelTrackedResponse({ status: 503, statusText: "Service Unavailable" });
fetchWithSsrFGuardMock.mockResolvedValueOnce({
response: response.response,
finalUrl: "http://127.0.0.1:8000/v1/models",
release,
});
const models = await discoverOpenAICompatibleLocalModels({
baseUrl: "http://127.0.0.1:8000/v1/",
apiKey: "self-hosted-test-key",
label: "vLLM",
env: {},
});
expect(models).toEqual([]);
expect(release).toHaveBeenCalledOnce();
expect(response.wasCanceled()).toBe(true);
});
it("uses llama.cpp nested /props n_ctx as the runtime context cap", async () => {

View File

@@ -86,6 +86,12 @@ function readPositiveInteger(value: unknown): number | undefined {
return Math.trunc(value);
}
async function cancelUnreadResponseBody(response: Response): Promise<void> {
if (!response.bodyUsed) {
await response.body?.cancel().catch(() => undefined);
}
}
function resolveLlamaCppPropsUrl(baseUrl: string, modelId?: string): string {
const parsed = new URL(baseUrl);
const pathname = parsed.pathname.replace(/\/+$/, "");
@@ -124,6 +130,7 @@ async function discoverLlamaCppRuntimeContextTokens(params: {
});
try {
if (!response.ok) {
await cancelUnreadResponseBody(response);
return undefined;
}
const data = (await response.json()) as LlamaCppPropsResponse;
@@ -167,6 +174,7 @@ export async function discoverOpenAICompatibleLocalModels(params: {
});
try {
if (!response.ok) {
await cancelUnreadResponseBody(response);
log.warn(`Failed to discover ${params.label} models: ${response.status}`);
return [];
}