import { Type } from "@sinclair/typebox"; import { describe, expect, it, vi } from "vitest"; import { getModel } from "../src/models.js"; import { streamSimple } from "../src/stream.js"; import type { Tool } from "../src/types.js"; const mockState = vi.hoisted(() => ({ lastParams: undefined as unknown })); vi.mock("openai", () => { class FakeOpenAI { chat = { completions: { create: async (params: unknown) => { mockState.lastParams = params; return { async *[Symbol.asyncIterator]() { yield { choices: [{ delta: {}, finish_reason: "stop" }], usage: { prompt_tokens: 1, completion_tokens: 1, prompt_tokens_details: { cached_tokens: 0 }, completion_tokens_details: { reasoning_tokens: 0 }, }, }; }, }; }, }, }; } return { default: FakeOpenAI }; }); describe("openai-completions tool_choice", () => { it("forwards toolChoice from simple options to payload", async () => { const { compat: _compat, ...baseModel } = getModel( "openai", "gpt-4o-mini", )!; const model = { ...baseModel, api: "openai-completions" } as const; const tools: Tool[] = [ { name: "ping", description: "Ping tool", parameters: Type.Object({ ok: Type.Boolean(), }), }, ]; let payload: unknown; await streamSimple( model, { messages: [ { role: "user", content: "Call ping with ok=true", timestamp: Date.now(), }, ], tools, }, { apiKey: "test", toolChoice: "required", onPayload: (params: unknown) => { payload = params; }, } as unknown as Parameters[2], ).result(); const params = (payload ?? mockState.lastParams) as { tool_choice?: string; tools?: unknown[]; }; expect(params.tool_choice).toBe("required"); expect(Array.isArray(params.tools)).toBe(true); expect(params.tools?.length ?? 0).toBeGreaterThan(0); }); it("omits strict when compat disables strict mode", async () => { const { compat: _compat, ...baseModel } = getModel( "openai", "gpt-4o-mini", )!; const model = { ...baseModel, api: "openai-completions", compat: { supportsStrictMode: false }, } as const; const tools: Tool[] = [ { name: "ping", description: "Ping tool", parameters: Type.Object({ ok: Type.Boolean(), }), }, ]; let payload: unknown; await streamSimple( model, { messages: [ { role: "user", content: "Call ping with ok=true", timestamp: Date.now(), }, ], tools, }, { apiKey: "test", onPayload: (params: unknown) => { payload = params; }, } as unknown as Parameters[2], ).result(); const params = (payload ?? mockState.lastParams) as { tools?: Array<{ function?: Record }>; }; const tool = params.tools?.[0]?.function; expect(tool).toBeTruthy(); expect(tool?.strict).toBeUndefined(); expect("strict" in (tool ?? {})).toBe(false); }); it("maps groq qwen3 reasoning levels to default reasoning_effort", async () => { const model = getModel("groq", "qwen/qwen3-32b")!; let payload: unknown; await streamSimple( model, { messages: [ { role: "user", content: "Hi", timestamp: Date.now(), }, ], }, { apiKey: "test", reasoning: "medium", onPayload: (params: unknown) => { payload = params; }, }, ).result(); const params = (payload ?? mockState.lastParams) as { reasoning_effort?: string; }; expect(params.reasoning_effort).toBe("default"); }); it("keeps normal reasoning_effort for groq models without compat mapping", async () => { const model = getModel("groq", "openai/gpt-oss-20b")!; let payload: unknown; await streamSimple( model, { messages: [ { role: "user", content: "Hi", timestamp: Date.now(), }, ], }, { apiKey: "test", reasoning: "medium", onPayload: (params: unknown) => { payload = params; }, }, ).result(); const params = (payload ?? mockState.lastParams) as { reasoning_effort?: string; }; expect(params.reasoning_effort).toBe("medium"); }); });