import type * as GoogleGenAi from "@google/genai";
import type { GenerateContentParameters } from "@google/genai";
import { Type } from "typebox";
import { describe, expect, it, vi } from "vitest";
vi.mock("@google/genai", async (importOriginal) => {
const actual = await importOriginal<typeof GoogleGenAi>();
class GoogleGenAI {
models = {
generateContentStream: async function* () {
yield {
candidates: [{ content: { parts: [{ text: '{"decision":"commit"}' }] }, finishReason: "STOP" }],
usageMetadata: { promptTokenCount: 1, candidatesTokenCount: 1, totalTokenCount: 2 },
};
},
};
}
return {
...actual,
GoogleGenAI,
ResourceScope: { COLLECTION: "COLLECTION" },
ThinkingLevel: {
THINKING_LEVEL_UNSPECIFIED: "THINKING_LEVEL_UNSPECIFIED",
MINIMAL: "MINIMAL",
LOW: "LOW",
MEDIUM: "MEDIUM",
HIGH: "HIGH",
},
};
});
vi.mock("openai", () => {
class FakeOpenAI {
chat = {
completions: {
create: () => {
const stream = {
async *[Symbol.asyncIterator]() {
yield {
choices: [{ delta: { content: '{"decision":"commit"}' }, finish_reason: "stop" }],
usage: {
prompt_tokens: 1,
completion_tokens: 1,
prompt_tokens_details: { cached_tokens: 0 },
completion_tokens_details: { reasoning_tokens: 0 },
},
};
},
};
const promise = Promise.resolve(stream) as Promise<typeof stream> & {
withResponse: () => Promise<{ data: typeof stream; response: { status: number; headers: Headers } }>;
};
promise.withResponse = async () => ({
data: stream,
response: { status: 200, headers: new Headers() },
});
return promise;
},
},
};
}
return { default: FakeOpenAI };
});
import { getModel } from "../src/models.js";
import { streamGoogle } from "../src/providers/google.js";
import { streamSimple } from "../src/stream.js";
import type { Context, ResponseFormat } from "../src/types.js";
const context: Context = {
messages: [{ role: "user", content: "commit or hold?", timestamp: Date.now() }],
};
const responseFormat: ResponseFormat = {
type: "json_schema",
name: "commitment",
schema: Type.Object(
{
decision: Type.Union([Type.Literal("commit"), Type.Literal("hold")]),
},
{ additionalProperties: false },
),
};
interface SchemaNode {
type?: string;
enum?: unknown[];
const?: unknown;
anyOf?: SchemaNode[];
oneOf?: SchemaNode[];
items?: SchemaNode;
properties?: Record<string, SchemaNode>;
$defs?: Record<string, SchemaNode>;
$ref?: string;
additionalProperties?: boolean;
}
async function capturePayload<T>(run: (onPayload: (payload: unknown) => void) => Promise<void>): Promise<T> {
let captured: unknown;
await run((payload) => {
captured = payload;
});
if (captured === undefined) {
throw new Error("Expected a provider payload to be captured");
}
return captured as T;
}
async function captureGoogleSchema(format: ResponseFormat): Promise<SchemaNode> {
const model = getModel("google", "gemini-3-flash-preview")!;
const payload = await capturePayload<GenerateContentParameters>(async (onPayload) => {
await streamGoogle(model, context, { apiKey: "fake-key", responseFormat: format, onPayload }).result();
});
return payload.config?.responseJsonSchema as SchemaNode;
}
describe("structured output response format", () => {
it("sends responseJsonSchema for google-generative-ai", async () => {
const model = getModel("google", "gemini-3-flash-preview")!;
const payload = await capturePayload<GenerateContentParameters>(async (onPayload) => {
await streamSimple(model, context, { apiKey: "fake-key", responseFormat, onPayload }).result();
});
expect(payload.config?.responseMimeType).toBe("application/json");
expect(payload.config?.responseSchema).toBeUndefined();
expect(payload.config?.responseJsonSchema).toMatchObject({
type: "object",
additionalProperties: false,
properties: { decision: { type: "string", enum: ["commit", "hold"] } },
});
});
it("sends responseJsonSchema for google-vertex", async () => {
const model = getModel("google-vertex", "gemini-2.5-flash")!;
const payload = await capturePayload<GenerateContentParameters>(async (onPayload) => {
await streamSimple(model, context, { apiKey: "fake-key", responseFormat, onPayload }).result();
});
expect(payload.config?.responseMimeType).toBe("application/json");
expect(payload.config?.responseJsonSchema).toMatchObject({
properties: { decision: { type: "string", enum: ["commit", "hold"] } },
});
});
it("omits the schema fields for google when responseFormat is absent", async () => {
const model = getModel("google", "gemini-3-flash-preview")!;
const payload = await capturePayload<GenerateContentParameters>(async (onPayload) => {
await streamSimple(model, context, { apiKey: "fake-key", onPayload }).result();
});
expect(payload.config?.responseMimeType).toBeUndefined();
expect(payload.config?.responseJsonSchema).toBeUndefined();
});
it("normalizes a TypeBox literal union to enum and keeps declaration order", async () => {
const schema = await captureGoogleSchema({
type: "json_schema",
schema: Type.Object({
evidence: Type.String(),
status: Type.Union([Type.Literal("done"), Type.Literal("pending"), Type.Literal("blocked")]),
}),
});
expect(Object.keys(schema.properties ?? {})).toEqual(["evidence", "status"]);
const status = schema.properties?.status;
expect(status?.enum).toEqual(["done", "pending", "blocked"]);
expect(status?.type).toBe("string");
expect(status?.anyOf).toBeUndefined();
expect(status?.const).toBeUndefined();
});
it("normalizes a single Type.Literal to a one-value enum", async () => {
const schema = await captureGoogleSchema({
type: "json_schema",
schema: Type.Object({ kind: Type.Literal("commitment") }),
});
const kind = schema.properties?.kind;
expect(kind?.enum).toEqual(["commitment"]);
expect(kind?.type).toBe("string");
expect(kind?.const).toBeUndefined();
});
it("leaves a genuine non-const union unchanged", async () => {
const schema = await captureGoogleSchema({
type: "json_schema",
schema: Type.Object({
value: Type.Union([Type.String(), Type.Number()]),
mixed: Type.Union([Type.Literal("auto"), Type.Number()]),
}),
});
expect(schema.properties?.value?.anyOf).toEqual([{ type: "string" }, { type: "number" }]);
expect(schema.properties?.value?.enum).toBeUndefined();
const mixed = schema.properties?.mixed;
expect(mixed?.enum).toBeUndefined();
expect(mixed?.anyOf?.[0]).toEqual({ type: "string", enum: ["auto"] });
expect(mixed?.anyOf?.[1]).toEqual({ type: "number" });
});
it("normalizes unions nested in array items and in $defs", async () => {
const schema = await captureGoogleSchema({
type: "json_schema",
schema: Type.Object({
labels: Type.Array(Type.Union([Type.Literal("bug"), Type.Literal("feat")])),
status: Type.Union([Type.Literal("open"), Type.Literal("closed")]),
}),
});
const withDefs = await captureGoogleSchema({
type: "json_schema",
schema: {
type: "object",
properties: { status: { $ref: "#/$defs/Status" } },
$defs: { Status: Type.Union([Type.Literal("open"), Type.Literal("closed")]) },
},
});
expect(schema.properties?.labels?.items?.enum).toEqual(["bug", "feat"]);
expect(schema.properties?.labels?.items?.anyOf).toBeUndefined();
expect(withDefs.$defs?.Status?.enum).toEqual(["open", "closed"]);
expect(withDefs.$defs?.Status?.anyOf).toBeUndefined();
expect(withDefs.properties?.status?.$ref).toBe("#/$defs/Status");
});
it("sends response_format json_schema for openai-completions", async () => {
const { compat: _compat, ...baseModel } = getModel("openai", "gpt-4o-mini")!;
const model = { ...baseModel, api: "openai-completions" } as const;
const payload = await capturePayload<{
response_format?: { type: string; json_schema: { name: string; schema: unknown; strict?: boolean } };
}>(async (onPayload) => {
await streamSimple(model, context, { apiKey: "fake-key", responseFormat, onPayload }).result();
});
expect(payload.response_format?.type).toBe("json_schema");
expect(payload.response_format?.json_schema.name).toBe("commitment");
expect(payload.response_format?.json_schema.schema).toMatchObject({
type: "object",
additionalProperties: false,
properties: { decision: { type: "string", enum: ["commit", "hold"] } },
});
expect(payload.response_format?.json_schema.strict).toBe(true);
});
it("normalizes literal unions and keeps genuine unions for openai-completions", async () => {
const { compat: _compat, ...baseModel } = getModel("openai", "gpt-4o-mini")!;
const model = { ...baseModel, api: "openai-completions" } as const;
const payload = await capturePayload<{
response_format?: { json_schema: { schema: SchemaNode } };
}>(async (onPayload) => {
await streamSimple(model, context, {
apiKey: "fake-key",
responseFormat: {
type: "json_schema",
schema: Type.Object({
status: Type.Union([Type.Literal("open"), Type.Literal("closed")]),
value: Type.Union([Type.String(), Type.Number()]),
}),
},
onPayload,
}).result();
});
const schema = payload.response_format?.json_schema.schema;
expect(schema?.properties?.status).toEqual({ type: "string", enum: ["open", "closed"] });
expect(schema?.properties?.value?.anyOf).toEqual([{ type: "string" }, { type: "number" }]);
expect(schema?.properties?.value?.enum).toBeUndefined();
});
it("omits response_format for non-standard openai-compatible endpoints by default", async () => {
const model = getModel("deepseek", "deepseek-flash")!;
const payload = await capturePayload<{ response_format?: unknown }>(async (onPayload) => {
await streamSimple(model, context, { apiKey: "fake-key", responseFormat, onPayload }).result();
});
expect(payload.response_format).toBeUndefined();
});
it("keeps response_format for opencode-go, which is measured to support it", async () => {
const model = getModel("opencode-go", "deepseek-v4-flash")!;
const payload = await capturePayload<{ response_format?: { type: string } }>(async (onPayload) => {
await streamSimple(model, context, { apiKey: "fake-key", responseFormat, onPayload }).result();
});
expect(payload.response_format?.type).toBe("json_schema");
});
it("sends response_format for a non-standard endpoint that opts in", async () => {
const baseModel = getModel("deepseek", "deepseek-flash")!;
const model = {
...baseModel,
compat: { ...baseModel.compat, supportsResponseFormatJsonSchema: true },
} as const;
const payload = await capturePayload<{ response_format?: { type: string } }>(async (onPayload) => {
await streamSimple(model, context, { apiKey: "fake-key", responseFormat, onPayload }).result();
});
expect(payload.response_format?.type).toBe("json_schema");
});
it("omits response_format when compat disables json_schema support", async () => {
const { compat: _compat, ...baseModel } = getModel("openai", "gpt-4o-mini")!;
const model = {
...baseModel,
api: "openai-completions",
compat: { supportsResponseFormatJsonSchema: false },
} as const;
const payload = await capturePayload<{ response_format?: unknown }>(async (onPayload) => {
await streamSimple(model, context, { apiKey: "fake-key", responseFormat, onPayload }).result();
});
expect(payload.response_format).toBeUndefined();
});
});