mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
Adds the openai-decisions classifier API with gpt-6-luna on the openai provider. bool questions map to predicates with the criteria in the instructions, refusals fail the result, and gateway timeouts (504) are not retried. The model needs an API key and is hidden from availability while openai uses Sign in with ChatGPT. Adds optional images to ClassifierContext. Models without image input and APIs that cannot send images return an error result. Codemode's models.classify() accepts images. Moves the shared classifier HTTP code out of system-one-shared.ts into classifier-shared.ts.
89 lines
3.2 KiB
TypeScript
89 lines
3.2 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
import { retryProviderRequest } from "../src/utils/provider-retry.ts";
|
|
|
|
function providerError(status: number | undefined, headers?: Record<string, string>): Error {
|
|
return Object.assign(new Error(`Provider error: ${status}`), {
|
|
status,
|
|
headers: new Headers(headers),
|
|
});
|
|
}
|
|
|
|
describe("provider request retries", () => {
|
|
afterEach(() => {
|
|
vi.useRealTimers();
|
|
});
|
|
|
|
it("retries retryable provider errors", async () => {
|
|
vi.useFakeTimers();
|
|
const request = vi
|
|
.fn<() => Promise<string>>()
|
|
.mockRejectedValueOnce(providerError(429, { "retry-after-ms": "1000" }))
|
|
.mockResolvedValue("ok");
|
|
|
|
const result = retryProviderRequest(request, { maxRetries: 1 });
|
|
await vi.advanceTimersByTimeAsync(999);
|
|
expect(request).toHaveBeenCalledTimes(1);
|
|
await vi.advanceTimersByTimeAsync(1);
|
|
|
|
await expect(result).resolves.toBe("ok");
|
|
expect(request).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
it("does not retry errors the provider marks as non-retryable", async () => {
|
|
const error = providerError(429, { "x-should-retry": "false" });
|
|
const request = vi.fn<() => Promise<string>>().mockRejectedValue(error);
|
|
|
|
await expect(retryProviderRequest(request, { maxRetries: 2 })).rejects.toBe(error);
|
|
expect(request).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("does not retry statuses listed in noRetryStatuses", async () => {
|
|
const error = providerError(504, { "retry-after-ms": "0" });
|
|
const request = vi.fn<() => Promise<string>>().mockRejectedValue(error);
|
|
|
|
await expect(retryProviderRequest(request, { maxRetries: 2, noRetryStatuses: [504] })).rejects.toBe(error);
|
|
expect(request).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("rejects a provider-requested retry delay above the limit", async () => {
|
|
const request = vi.fn<() => Promise<string>>().mockRejectedValue(providerError(429, { "retry-after": "277403" }));
|
|
|
|
await expect(retryProviderRequest(request, { maxRetries: 1, maxRetryDelayMs: 1000 })).rejects.toThrow(
|
|
"Server requested 277403s retry delay (max: 1s)",
|
|
);
|
|
expect(request).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("allows disabling the provider-requested retry delay cap", async () => {
|
|
vi.useFakeTimers();
|
|
const request = vi
|
|
.fn<() => Promise<string>>()
|
|
.mockRejectedValueOnce(providerError(429, { "retry-after": "2" }))
|
|
.mockResolvedValue("ok");
|
|
|
|
const result = retryProviderRequest(request, { maxRetries: 1, maxRetryDelayMs: 0 });
|
|
await vi.advanceTimersByTimeAsync(1999);
|
|
expect(request).toHaveBeenCalledTimes(1);
|
|
await vi.advanceTimersByTimeAsync(1);
|
|
|
|
await expect(result).resolves.toBe("ok");
|
|
expect(request).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
it("aborts a provider-requested retry delay", async () => {
|
|
vi.useFakeTimers();
|
|
const controller = new AbortController();
|
|
const request = vi.fn<() => Promise<string>>().mockRejectedValue(providerError(429, { "retry-after": "277403" }));
|
|
|
|
const result = retryProviderRequest(request, { maxRetries: 2, maxRetryDelayMs: 0, signal: controller.signal });
|
|
await vi.advanceTimersByTimeAsync(0);
|
|
expect(request).toHaveBeenCalledTimes(1);
|
|
expect(vi.getTimerCount()).toBe(1);
|
|
|
|
controller.abort();
|
|
|
|
await expect(result).rejects.toMatchObject({ name: "AbortError" });
|
|
expect(request).toHaveBeenCalledTimes(1);
|
|
expect(vi.getTimerCount()).toBe(0);
|
|
});
|
|
});
|