import { describe, expect, it } from "vitest"; import { PROVIDER_SEND_TURN_MAX_ATTACHMENTS, providerCheckpointSchema, providerEventSchema, providerGetCheckpointDiffInputSchema, providerGetCheckpointDiffResultSchema, providerListCheckpointsInputSchema, providerListCheckpointsResultSchema, providerRevertToCheckpointInputSchema, providerRevertToCheckpointResultSchema, providerRespondToRequestInputSchema, providerSendTurnInputSchema, providerSessionStartInputSchema,} from "./provider"; describe("providerSessionStartInputSchema", () => { it("defaults to codex with safe policies", () => { const parsed = providerSessionStartInputSchema.parse({}); expect(parsed.provider).toBe("codex"); expect(parsed.approvalPolicy).toBe("never"); expect(parsed.sandboxMode).toBe("workspace-write"); }); it("accepts optional resumeThreadId", () => { const parsed = providerSessionStartInputSchema.parse({ resumeThreadId: "thread_123", }); expect(parsed.resumeThreadId).toBe("thread_123"); }); it("rejects blank resumeThreadId", () => { expect(() => providerSessionStartInputSchema.parse({ resumeThreadId: " ", }), ).toThrow(); }); it("accepts optional codex binary and home path overrides", () => { const parsed = providerSessionStartInputSchema.parse({ codexBinaryPath: "/opt/codex/bin/codex", codexHomePath: "/Users/theo/.codex", }); expect(parsed.codexBinaryPath).toBe("/opt/codex/bin/codex"); expect(parsed.codexHomePath).toBe("/Users/theo/.codex"); }); it("rejects blank codex overrides", () => { expect(() => providerSessionStartInputSchema.parse({ codexBinaryPath: " ", }), ).toThrow(); expect(() => providerSessionStartInputSchema.parse({ codexHomePath: " ", }), ).toThrow(); });}); describe("providerSendTurnInputSchema", () => { it("trims input text and optional model/effort", () => { const parsed = providerSendTurnInputSchema.parse({ sessionId: "sess_1", input: " summarize this repo ", model: " gpt-5.2-codex ", effort: " high ", }); expect(parsed.input).toBe("summarize this repo"); expect(parsed.attachments).toEqual([]); expect(parsed.model).toBe("gpt-5.2-codex"); expect(parsed.effort).toBe("high"); }); it("accepts image-only turns", () => { const parsed = providerSendTurnInputSchema.parse({ sessionId: "sess_1", attachments: [ { type: "image", name: "diagram.png", mimeType: "image/png", sizeBytes: 1_024, dataUrl: "data:image/png;base64,AAAA", }, ], }); expect(parsed.input).toBeUndefined(); expect(parsed.attachments).toHaveLength(1); }); it("rejects turns with neither text nor attachments", () => { expect(() => providerSendTurnInputSchema.parse({ sessionId: "sess_1", }), ).toThrow(); }); it("rejects non-image data urls", () => { expect(() => providerSendTurnInputSchema.parse({ sessionId: "sess_1", attachments: [ { type: "image", name: "not-image.txt", mimeType: "text/plain", sizeBytes: 25, dataUrl: "data:text/plain;base64,QQ==", }, ], }), ).toThrow(); }); it("rejects more than the max attachment count", () => { expect(() => providerSendTurnInputSchema.parse({ sessionId: "sess_1", attachments: Array.from({ length: PROVIDER_SEND_TURN_MAX_ATTACHMENTS + 1 }, (_, index) => ({ type: "image" as const, name: `image-${index}.png`, mimeType: "image/png", sizeBytes: 1_024, dataUrl: "data:image/png;base64,AAAA", })), }), ).toThrow(); });}); describe("providerEventSchema", () => { it("accepts notification events with routing metadata", () => { const parsed = providerEventSchema.parse({ id: "evt_1", kind: "notification", provider: "codex", sessionId: "sess_1", createdAt: "2026-01-01T00:00:00.000Z", method: "item/agentMessage/delta", threadId: "thr_1", turnId: "turn_1", itemId: "item_1", textDelta: "hi", }); expect(parsed.method).toBe("item/agentMessage/delta"); }); it("accepts request approval metadata", () => { const parsed = providerEventSchema.parse({ id: "evt_2", kind: "request", provider: "codex", sessionId: "sess_1", createdAt: "2026-01-01T00:00:00.000Z", method: "item/commandExecution/requestApproval", requestId: "req_123", requestKind: "command", }); expect(parsed.requestId).toBe("req_123"); expect(parsed.requestKind).toBe("command"); });}); describe("providerRespondToRequestInputSchema", () => { it("accepts valid decisions", () => { const parsed = providerRespondToRequestInputSchema.parse({ sessionId: "sess_1", requestId: "req_1", decision: "acceptForSession", }); expect(parsed.decision).toBe("acceptForSession"); }); it("rejects unknown decisions", () => { expect(() => providerRespondToRequestInputSchema.parse({ sessionId: "sess_1", requestId: "req_1", decision: "always", }), ).toThrow(); });}); describe("provider checkpoint schemas", () => { it("accepts list checkpoint inputs", () => { const parsed = providerListCheckpointsInputSchema.parse({ sessionId: "sess_1", }); expect(parsed.sessionId).toBe("sess_1"); }); it("accepts checkpoint records and list results", () => { const checkpoint = providerCheckpointSchema.parse({ id: "turn_1", turnCount: 1, messageCount: 2, label: "Turn 1", preview: "Summarize this file", isCurrent: true, }); expect(checkpoint.turnCount).toBe(1); const listResult = providerListCheckpointsResultSchema.parse({ threadId: "thr_1", checkpoints: [checkpoint], }); expect(listResult.checkpoints).toHaveLength(1); }); it("accepts revert checkpoint input/result", () => { const input = providerRevertToCheckpointInputSchema.parse({ sessionId: "sess_1", turnCount: 2, }); expect(input.turnCount).toBe(2); const result = providerRevertToCheckpointResultSchema.parse({ threadId: "thr_1", turnCount: 2, messageCount: 4, rolledBackTurns: 1, checkpoints: [ { id: "root", turnCount: 0, messageCount: 0, label: "Start of conversation", isCurrent: false, }, { id: "turn_2", turnCount: 2, messageCount: 4, label: "Turn 2", isCurrent: true, }, ], }); expect(result.rolledBackTurns).toBe(1); }); it("rejects negative turn counts", () => { expect(() => providerRevertToCheckpointInputSchema.parse({ sessionId: "sess_1", turnCount: -1, }), ).toThrow(); }); it("accepts checkpoint diff input/result", () => { const input = providerGetCheckpointDiffInputSchema.parse({ sessionId: "sess_1", fromTurnCount: 1, toTurnCount: 2, }); expect(input.fromTurnCount).toBe(1); const result = providerGetCheckpointDiffResultSchema.parse({ threadId: "thr_1", fromTurnCount: 1, toTurnCount: 2, diff: "diff --git a/src/app.ts b/src/app.ts", }); expect(result.toTurnCount).toBe(2); }); it("rejects checkpoint diff ranges where start is after end", () => { expect(() => providerGetCheckpointDiffInputSchema.parse({ sessionId: "sess_1", fromTurnCount: 3, toTurnCount: 2, }), ).toThrow(); });});
import { describe, expect, it } from "vitest";
import {
PROVIDER_SEND_TURN_MAX_ATTACHMENTS,
providerCheckpointSchema,
providerEventSchema,
providerGetCheckpointDiffInputSchema,
providerGetCheckpointDiffResultSchema,
providerListCheckpointsInputSchema,
providerListCheckpointsResultSchema,
providerRevertToCheckpointInputSchema,
providerRevertToCheckpointResultSchema,
providerRespondToRequestInputSchema,
providerSendTurnInputSchema,
providerSessionStartInputSchema,
} from "./provider";
describe("providerSessionStartInputSchema", () => {
it("defaults to codex with safe policies", () => {
const parsed = providerSessionStartInputSchema.parse({});
expect(parsed.provider).toBe("codex");
expect(parsed.approvalPolicy).toBe("never");
expect(parsed.sandboxMode).toBe("workspace-write");
});
it("accepts optional resumeThreadId", () => {
const parsed = providerSessionStartInputSchema.parse({
resumeThreadId: "thread_123",
});
expect(parsed.resumeThreadId).toBe("thread_123");
});
it("rejects blank resumeThreadId", () => {
expect(() =>
providerSessionStartInputSchema.parse({
resumeThreadId: " ",
}),
).toThrow();
});
it("accepts optional codex binary and home path overrides", () => {
const parsed = providerSessionStartInputSchema.parse({
codexBinaryPath: "/opt/codex/bin/codex",
codexHomePath: "/Users/theo/.codex",
});
expect(parsed.codexBinaryPath).toBe("/opt/codex/bin/codex");
expect(parsed.codexHomePath).toBe("/Users/theo/.codex");
});
it("rejects blank codex overrides", () => {
expect(() =>
providerSessionStartInputSchema.parse({
codexBinaryPath: " ",
}),
).toThrow();
expect(() =>
providerSessionStartInputSchema.parse({
codexHomePath: " ",
}),
).toThrow();
});
});
describe("providerSendTurnInputSchema", () => {
it("trims input text and optional model/effort", () => {
const parsed = providerSendTurnInputSchema.parse({
sessionId: "sess_1",
input: " summarize this repo ",
model: " gpt-5.2-codex ",
effort: " high ",
});
expect(parsed.input).toBe("summarize this repo");
expect(parsed.attachments).toEqual([]);
expect(parsed.model).toBe("gpt-5.2-codex");
expect(parsed.effort).toBe("high");
});
it("accepts image-only turns", () => {
const parsed = providerSendTurnInputSchema.parse({
sessionId: "sess_1",
attachments: [
{
type: "image",
name: "diagram.png",
mimeType: "image/png",
sizeBytes: 1_024,
dataUrl: "data:image/png;base64,AAAA",
},
],
});
expect(parsed.input).toBeUndefined();
expect(parsed.attachments).toHaveLength(1);
});
it("rejects turns with neither text nor attachments", () => {
expect(() =>
providerSendTurnInputSchema.parse({
sessionId: "sess_1",
}),
).toThrow();
});
it("rejects non-image data urls", () => {
expect(() =>
providerSendTurnInputSchema.parse({
sessionId: "sess_1",
attachments: [
{
type: "image",
name: "not-image.txt",
mimeType: "text/plain",
sizeBytes: 25,
dataUrl: "data:text/plain;base64,QQ==",
},
],
}),
).toThrow();
});
it("rejects more than the max attachment count", () => {
expect(() =>
providerSendTurnInputSchema.parse({
sessionId: "sess_1",
attachments: Array.from({ length: PROVIDER_SEND_TURN_MAX_ATTACHMENTS + 1 }, (_, index) => ({
type: "image" as const,
name: `image-${index}.png`,
mimeType: "image/png",
sizeBytes: 1_024,
dataUrl: "data:image/png;base64,AAAA",
})),
}),
).toThrow();
});
});
describe("providerEventSchema", () => {
it("accepts notification events with routing metadata", () => {
const parsed = providerEventSchema.parse({
id: "evt_1",
kind: "notification",
provider: "codex",
sessionId: "sess_1",
createdAt: "2026-01-01T00:00:00.000Z",
method: "item/agentMessage/delta",
threadId: "thr_1",
turnId: "turn_1",
itemId: "item_1",
textDelta: "hi",
});
expect(parsed.method).toBe("item/agentMessage/delta");
});
it("accepts request approval metadata", () => {
const parsed = providerEventSchema.parse({
id: "evt_2",
kind: "request",
provider: "codex",
sessionId: "sess_1",
createdAt: "2026-01-01T00:00:00.000Z",
method: "item/commandExecution/requestApproval",
requestId: "req_123",
requestKind: "command",
});
expect(parsed.requestId).toBe("req_123");
expect(parsed.requestKind).toBe("command");
});
});
describe("providerRespondToRequestInputSchema", () => {
it("accepts valid decisions", () => {
const parsed = providerRespondToRequestInputSchema.parse({
sessionId: "sess_1",
requestId: "req_1",
decision: "acceptForSession",
});
expect(parsed.decision).toBe("acceptForSession");
});
it("rejects unknown decisions", () => {
expect(() =>
providerRespondToRequestInputSchema.parse({
sessionId: "sess_1",
requestId: "req_1",
decision: "always",
}),
).toThrow();
});
});
describe("provider checkpoint schemas", () => {
it("accepts list checkpoint inputs", () => {
const parsed = providerListCheckpointsInputSchema.parse({
sessionId: "sess_1",
});
expect(parsed.sessionId).toBe("sess_1");
});
it("accepts checkpoint records and list results", () => {
const checkpoint = providerCheckpointSchema.parse({
id: "turn_1",
turnCount: 1,
messageCount: 2,
label: "Turn 1",
preview: "Summarize this file",
isCurrent: true,
});
expect(checkpoint.turnCount).toBe(1);
const listResult = providerListCheckpointsResultSchema.parse({
threadId: "thr_1",
checkpoints: [checkpoint],
});
expect(listResult.checkpoints).toHaveLength(1);
});
it("accepts revert checkpoint input/result", () => {
const input = providerRevertToCheckpointInputSchema.parse({
sessionId: "sess_1",
turnCount: 2,
});
expect(input.turnCount).toBe(2);
const result = providerRevertToCheckpointResultSchema.parse({
threadId: "thr_1",
turnCount: 2,
messageCount: 4,
rolledBackTurns: 1,
checkpoints: [
{
id: "root",
turnCount: 0,
messageCount: 0,
label: "Start of conversation",
isCurrent: false,
},
{
id: "turn_2",
turnCount: 2,
messageCount: 4,
label: "Turn 2",
isCurrent: true,
},
],
});
expect(result.rolledBackTurns).toBe(1);
});
it("rejects negative turn counts", () => {
expect(() =>
providerRevertToCheckpointInputSchema.parse({
sessionId: "sess_1",
turnCount: -1,
}),
).toThrow();
});
it("accepts checkpoint diff input/result", () => {
const input = providerGetCheckpointDiffInputSchema.parse({
sessionId: "sess_1",
fromTurnCount: 1,
toTurnCount: 2,
});
expect(input.fromTurnCount).toBe(1);
const result = providerGetCheckpointDiffResultSchema.parse({
threadId: "thr_1",
fromTurnCount: 1,
toTurnCount: 2,
diff: "diff --git a/src/app.ts b/src/app.ts",
});
expect(result.toTurnCount).toBe(2);
});
it("rejects checkpoint diff ranges where start is after end", () => {
expect(() =>
providerGetCheckpointDiffInputSchema.parse({
sessionId: "sess_1",
fromTurnCount: 3,
toTurnCount: 2,
}),
).toThrow();
});
});