summaryrefslogtreecommitdiffhomepage
path: root/packages/openai-stream/src/provider.test.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/openai-stream/src/provider.test.ts')
-rw-r--r--packages/openai-stream/src/provider.test.ts279
1 files changed, 150 insertions, 129 deletions
diff --git a/packages/openai-stream/src/provider.test.ts b/packages/openai-stream/src/provider.test.ts
index 434c405..13a303e 100644
--- a/packages/openai-stream/src/provider.test.ts
+++ b/packages/openai-stream/src/provider.test.ts
@@ -4,151 +4,172 @@ import { describe, expect, it, vi } from "vitest";
import { createOpenAICompatProvider } from "./provider.js";
function makeCreds(): ApiKeyCredentials {
- return {
- type: "api-key",
- apiKey: "sk-test-1234567890abcdef",
- baseURL: "https://api.example.com/v1",
- };
+ return {
+ type: "api-key",
+ apiKey: "sk-test-1234567890abcdef",
+ baseURL: "https://api.example.com/v1",
+ };
}
function makeMessages(): readonly ChatMessage[] {
- return [{ role: "user", chunks: [{ type: "text", text: "Hello" }] }];
+ return [{ role: "user", chunks: [{ type: "text", text: "Hello" }] }];
}
function sseBody(...lines: string[]): ReadableStream<Uint8Array> {
- const encoder = new TextEncoder();
- const chunks = lines.map((l) => encoder.encode(`${l}\n`));
- let index = 0;
- return new ReadableStream<Uint8Array>({
- pull(controller) {
- if (index < chunks.length) {
- const chunk = chunks[index];
- if (chunk === undefined) throw new Error("empty chunk");
- controller.enqueue(chunk);
- index++;
- } else {
- controller.close();
- }
- },
- });
+ const encoder = new TextEncoder();
+ const chunks = lines.map((l) => encoder.encode(`${l}\n`));
+ let index = 0;
+ return new ReadableStream<Uint8Array>({
+ pull(controller) {
+ if (index < chunks.length) {
+ const chunk = chunks[index];
+ if (chunk === undefined) throw new Error("empty chunk");
+ controller.enqueue(chunk);
+ index++;
+ } else {
+ controller.close();
+ }
+ },
+ });
}
function okSseResponse(): Response {
- return new Response(
- sseBody(
- 'data: {"id":"cmpl-1","choices":[{"delta":{"content":"Hi"},"index":0}]}',
- 'data: {"id":"cmpl-1","choices":[{"delta":{},"finish_reason":"stop","index":0}]}',
- "data: [DONE]",
- ),
- { status: 200, headers: { "Content-Type": "text/event-stream" } },
- );
+ return new Response(
+ sseBody(
+ 'data: {"id":"cmpl-1","choices":[{"delta":{"content":"Hi"},"index":0}]}',
+ 'data: {"id":"cmpl-1","choices":[{"delta":{},"finish_reason":"stop","index":0}]}',
+ "data: [DONE]",
+ ),
+ { status: 200, headers: { "Content-Type": "text/event-stream" } },
+ );
}
async function collectEvents(iter: AsyncIterable<unknown>): Promise<unknown[]> {
- const events: unknown[] = [];
- for await (const event of iter) {
- events.push(event);
- }
- return events;
+ const events: unknown[] = [];
+ for await (const event of iter) {
+ events.push(event);
+ }
+ return events;
}
describe("createOpenAICompatProvider stamps the given id on the ProviderContract + listModels", () => {
- it("stamps opts.id on ProviderContract.id", () => {
- const provider = createOpenAICompatProvider({
- credentials: makeCreds(),
- model: "test-model",
- id: "my-custom-id",
- });
- expect(provider.id).toBe("my-custom-id");
- });
-
- it("uses opts.id in listModels error labels (was hardcoded 'openai-compat')", async () => {
- const fetchFn = vi.fn(
- () =>
- new Response("Unauthorized", {
- status: 401,
- headers: { "Content-Type": "text/plain" },
- }) as unknown as ReturnType<FetchLike>,
- );
- const provider = createOpenAICompatProvider({
- credentials: makeCreds(),
- model: "test-model",
- id: "my-custom-id",
- fetchFn,
- });
- const listModels = provider.listModels;
- if (!listModels) throw new Error("listModels not defined");
-
- await expect(listModels()).rejects.toThrow("listModels[my-custom-id]: HTTP 401 — Unauthorized");
- });
+ it("stamps opts.id on ProviderContract.id", () => {
+ const provider = createOpenAICompatProvider({
+ credentials: makeCreds(),
+ model: "test-model",
+ id: "my-custom-id",
+ });
+ expect(provider.id).toBe("my-custom-id");
+ });
+
+ it("uses opts.id in listModels error labels (was hardcoded 'openai-compat')", async () => {
+ const fetchFn = vi.fn(
+ () =>
+ new Response("Unauthorized", {
+ status: 401,
+ headers: { "Content-Type": "text/plain" },
+ }) as unknown as ReturnType<FetchLike>,
+ );
+ const provider = createOpenAICompatProvider({
+ credentials: makeCreds(),
+ model: "test-model",
+ id: "my-custom-id",
+ fetchFn,
+ });
+ const listModels = provider.listModels;
+ if (!listModels) throw new Error("listModels not defined");
+
+ await expect(listModels()).rejects.toThrow("listModels[my-custom-id]: HTTP 401 — Unauthorized");
+ });
+
+ it("exposes getUsage that returns the upstream concurrent_sessions", async () => {
+ const fetchFn = vi.fn(
+ () =>
+ new Response(JSON.stringify({ usage: { concurrent_sessions: 2 } }), {
+ status: 200,
+ headers: { "Content-Type": "application/json" },
+ }) as unknown as ReturnType<FetchLike>,
+ );
+ const provider = createOpenAICompatProvider({
+ credentials: makeCreds(),
+ model: "test-model",
+ id: "umans",
+ fetchFn,
+ });
+ const getUsage = provider.getUsage;
+ if (!getUsage) throw new Error("getUsage not defined");
+
+ const usage = await getUsage();
+ expect(usage).toEqual({ concurrentSessions: 2 });
+ });
});
describe("transformBody", () => {
- it("transformBody merges its returned fields into the request body", async () => {
- let capturedInit: RequestInit | undefined;
- const fetchFn = vi.fn(async (_url: string | URL | Request, init?: RequestInit) => {
- capturedInit = init;
- return okSseResponse();
- }) as unknown as FetchLike;
-
- let receivedBody: Record<string, unknown> | undefined;
- let receivedOpts: ProviderStreamOptions | undefined;
- const provider = createOpenAICompatProvider({
- credentials: makeCreds(),
- model: "test-model",
- id: "umans",
- fetchFn,
- transformBody: (body, opts) => {
- receivedBody = body;
- receivedOpts = opts;
- return { reasoning_effort: "high" };
- },
- });
-
- await collectEvents(provider.stream(makeMessages(), [], { temperature: 0.5 }));
-
- // The hook was called with the body built so far + the stream opts.
- expect(receivedBody).toBeDefined();
- expect(receivedOpts?.temperature).toBe(0.5);
- expect(receivedBody?.model).toBe("test-model");
-
- // The captured wire body carries the merged field.
- expect(capturedInit?.body).toBeTypeOf("string");
- const wireBody = JSON.parse(capturedInit?.body as string) as Record<string, unknown>;
- expect(wireBody.reasoning_effort).toBe("high");
- expect(wireBody.model).toBe("test-model");
- expect(wireBody.stream).toBe(true);
- expect(wireBody.temperature).toBe(0.5);
- });
-
- it("transformBody absent → body byte-identical to before (regression)", async () => {
- let capturedInit: RequestInit | undefined;
- const fetchFn = vi.fn(async (_url: string | URL | Request, init?: RequestInit) => {
- capturedInit = init;
- return okSseResponse();
- }) as unknown as FetchLike;
-
- const provider = createOpenAICompatProvider({
- credentials: makeCreds(),
- model: "test-model",
- id: "openai-compat",
- fetchFn,
- // No transformBody — default behavior.
- });
-
- await collectEvents(provider.stream(makeMessages(), [], { temperature: 0.5, maxTokens: 42 }));
-
- expect(capturedInit?.body).toBeTypeOf("string");
- const wireBody = JSON.parse(capturedInit?.body as string) as Record<string, unknown>;
- // Exact pre-refactor shape — no extra fields, no transformBody key leakage.
- expect(wireBody).toEqual({
- model: "test-model",
- messages: [{ role: "user", content: "Hello" }],
- stream: true,
- stream_options: { include_usage: true },
- temperature: 0.5,
- max_tokens: 42,
- });
- expect("reasoning_effort" in wireBody).toBe(false);
- });
+ it("transformBody merges its returned fields into the request body", async () => {
+ let capturedInit: RequestInit | undefined;
+ const fetchFn = vi.fn(async (_url: string | URL | Request, init?: RequestInit) => {
+ capturedInit = init;
+ return okSseResponse();
+ }) as unknown as FetchLike;
+
+ let receivedBody: Record<string, unknown> | undefined;
+ let receivedOpts: ProviderStreamOptions | undefined;
+ const provider = createOpenAICompatProvider({
+ credentials: makeCreds(),
+ model: "test-model",
+ id: "umans",
+ fetchFn,
+ transformBody: (body, opts) => {
+ receivedBody = body;
+ receivedOpts = opts;
+ return { reasoning_effort: "high" };
+ },
+ });
+
+ await collectEvents(provider.stream(makeMessages(), [], { temperature: 0.5 }));
+
+ // The hook was called with the body built so far + the stream opts.
+ expect(receivedBody).toBeDefined();
+ expect(receivedOpts?.temperature).toBe(0.5);
+ expect(receivedBody?.model).toBe("test-model");
+
+ // The captured wire body carries the merged field.
+ expect(capturedInit?.body).toBeTypeOf("string");
+ const wireBody = JSON.parse(capturedInit?.body as string) as Record<string, unknown>;
+ expect(wireBody.reasoning_effort).toBe("high");
+ expect(wireBody.model).toBe("test-model");
+ expect(wireBody.stream).toBe(true);
+ expect(wireBody.temperature).toBe(0.5);
+ });
+
+ it("transformBody absent → body byte-identical to before (regression)", async () => {
+ let capturedInit: RequestInit | undefined;
+ const fetchFn = vi.fn(async (_url: string | URL | Request, init?: RequestInit) => {
+ capturedInit = init;
+ return okSseResponse();
+ }) as unknown as FetchLike;
+
+ const provider = createOpenAICompatProvider({
+ credentials: makeCreds(),
+ model: "test-model",
+ id: "openai-compat",
+ fetchFn,
+ // No transformBody — default behavior.
+ });
+
+ await collectEvents(provider.stream(makeMessages(), [], { temperature: 0.5, maxTokens: 42 }));
+
+ expect(capturedInit?.body).toBeTypeOf("string");
+ const wireBody = JSON.parse(capturedInit?.body as string) as Record<string, unknown>;
+ // Exact pre-refactor shape — no extra fields, no transformBody key leakage.
+ expect(wireBody).toEqual({
+ model: "test-model",
+ messages: [{ role: "user", content: "Hello" }],
+ stream: true,
+ stream_options: { include_usage: true },
+ temperature: 0.5,
+ max_tokens: 42,
+ });
+ expect("reasoning_effort" in wireBody).toBe(false);
+ });
});