Skip to content
Source
Sign inSign up

schema.test.ts

266 lines8.2 KB

1import { assert, it } from "@effect/vitest";
2import * as Schema from "effect/Schema";
3​
4import * as CodexSchema from "./schema.ts";
5​
6const isGetAccountResponse = Schema.is(CodexSchema.V2GetAccountResponse);
7const isThreadReadResponse = Schema.is(CodexSchema.V2ThreadReadResponse);
8const isThreadResumeResponse = Schema.is(CodexSchema.V2ThreadResumeResponse);
9const isThreadForkResponse = Schema.is(CodexSchema.V2ThreadForkResponse);
10const isTurnCompletedNotification = Schema.is(CodexSchema.V2TurnCompletedNotification);
11const isHookStartedNotification = Schema.is(CodexSchema.V2HookStartedNotification);
12const isHookCompletedNotification = Schema.is(CodexSchema.V2HookCompletedNotification);
13const decodeThreadResumeResponse = Schema.decodeUnknownSync(CodexSchema.V2ThreadResumeResponse);
14​
15it("keeps async questions in live notifications and thread history", () => {
16 const item = {
17 type: "agentMessage",
18 id: "question-1",
19 text: "Which package?\n- pnpm\n- npm\n\nWhat should it be named?",
20 phase: "final_answer",
21 delivery: "async",
22 questions: [
23 { title: "Which package manager?", options: ["pnpm", "npm"] },
24 { title: "What should it be named?" },
25 ],
26 } as const;
27 for (const schema of [
28 CodexSchema.ServerNotification__ThreadItem,
29 CodexSchema.V2ItemStartedNotification__ThreadItem,
30 CodexSchema.V2ItemCompletedNotification__ThreadItem,
31 CodexSchema.V2ThreadReadResponse__ThreadItem,
32 CodexSchema.V2ThreadResumeResponse__ThreadItem,
33 ]) {
34 assert.deepEqual(Schema.decodeSync(schema)(item), item);
35 }
36});
37​
38it("keeps partial answers in notifications and persisted thread histories", () => {
39 const item = {
40 type: "agentMessage",
41 id: "partial-1",
42 text: "A stable answer before more tool work.",
43 phase: "partial_answer",
44 } as const;
45 for (const schema of [
46 CodexSchema.ServerNotification__ThreadItem,
47 CodexSchema.V2ItemStartedNotification__ThreadItem,
48 CodexSchema.V2ItemCompletedNotification__ThreadItem,
49 CodexSchema.V2ThreadReadResponse__ThreadItem,
50 CodexSchema.V2ThreadResumeResponse__ThreadItem,
51 CodexSchema.V2ThreadForkResponse__ThreadItem,
52 CodexSchema.V2TurnCompletedNotification__ThreadItem,
53 ]) {
54 assert.deepEqual(Schema.decodeSync(schema)(item), item);
55 }
56 const response = {
57 approvalPolicy: "never",
58 approvalsReviewer: "user",
59 cwd: "/tmp/project",
60 model: "gpt-5.6-sol",
61 modelProvider: "openai",
62 sandbox: { type: "dangerFullAccess" },
63 thread: {
64 cliVersion: "0.162.0",
65 createdAt: 0,
66 updatedAt: 0,
67 cwd: "/tmp/project",
68 ephemeral: false,
69 id: "thread-1",
70 modelProvider: "openai",
71 preview: "",
72 projectId: null,
73 sessionId: "session-1",
74 source: "cli",
75 status: { type: "idle" },
76 turns: [{ id: "turn-1", status: "completed", items: [item] }],
77 },
78 };
79 assert.equal(isThreadForkResponse(response), true);
80 assert.equal(isThreadResumeResponse(response), true);
81 assert.equal(isThreadReadResponse({ thread: response.thread }), true);
82});
83​
84it("accepts Codex 0.150 multi-agent values", () => {
85 const schemas = [
86 CodexSchema.ServerNotification__SubAgentActivityKind,
87 CodexSchema.V2ItemStartedNotification__SubAgentActivityKind,
88 CodexSchema.V2ItemCompletedNotification__SubAgentActivityKind,
89 CodexSchema.V2ThreadReadResponse__SubAgentActivityKind,
90 CodexSchema.V2ThreadResumeResponse__SubAgentActivityKind,
91 ];
92​
93 for (const schema of schemas) {
94 assert.equal(Schema.is(schema)("completed"), true);
95 }
96 const isServerNotificationCollabAgentTool = Schema.is(
97 CodexSchema.ServerNotification__CollabAgentTool,
98 );
99​
100 const isV2ThreadResumeResponseCollabAgentTool = Schema.is(
101 CodexSchema.V2ThreadResumeResponse__CollabAgentTool,
102 );
103​
104 for (const tool of ["sendMessage", "followupTask", "interruptAgent", "listAgents"]) {
105 assert.equal(isServerNotificationCollabAgentTool(tool), true);
106 assert.equal(isV2ThreadResumeResponseCollabAgentTool(tool), true);
107 }
108 const isServerNotificationCollabAgentToolCallStatus = Schema.is(
109 CodexSchema.ServerNotification__CollabAgentToolCallStatus,
110 );
111​
112 assert.equal(isServerNotificationCollabAgentToolCallStatus("interrupted"), true);
113 const isV2ThreadResumeResponseCollabAgentToolCallStatus = Schema.is(
114 CodexSchema.V2ThreadResumeResponse__CollabAgentToolCallStatus,
115 );
116​
117 assert.equal(isV2ThreadResumeResponseCollabAgentToolCallStatus("interrupted"), true);
118​
119 const resumeResponse = {
120 approvalPolicy: "never",
121 approvalsReviewer: "user",
122 cwd: "/tmp/project",
123 model: "gpt-5.6-sol",
124 modelProvider: "openai",
125 sandbox: { type: "dangerFullAccess" },
126 thread: {
127 cliVersion: "0.150.0",
128 createdAt: 0,
129 cwd: "/tmp/project",
130 ephemeral: false,
131 id: "root-thread",
132 modelProvider: "openai",
133 preview: "",
134 projectId: null,
135 sessionId: "session-1",
136 source: "cli",
137 status: { type: "idle" },
138 turns: [
139 {
140 id: "turn-1",
141 status: "completed",
142 items: [
143 {
144 agentsStates: {},
145 id: "item-1",
146 receiverThreadIds: ["child-thread"],
147 senderThreadId: "root-thread",
148 status: "interrupted",
149 tool: "followupTask",
150 type: "collabAgentToolCall",
151 },
152 ],
153 },
154 ],
155 updatedAt: 0,
156 },
157 };
158 const isV2ThreadResumeResponse = Schema.is(CodexSchema.V2ThreadResumeResponse);
159​
160 assert.equal(isV2ThreadResumeResponse(resumeResponse), true);
161});
162​
163it("accepts Codex misalignment policy errors for thread responses", () => {
164 const failedThread = {
165 cliVersion: "0.150.0",
166 createdAt: 0,
167 cwd: "/tmp/project",
168 ephemeral: false,
169 id: "thread-1",
170 modelProvider: "openai",
171 preview: "",
172 projectId: null,
173 sessionId: "session-1",
174 source: "cli",
175 status: { type: "idle" },
176 turns: [
177 {
178 error: {
179 codexErrorInfo: "misalignmentPolicyViolation",
180 message: "Misalignment policy violation",
181 },
182 id: "turn-1",
183 items: [],
184 status: "failed",
185 },
186 ],
187 updatedAt: 0,
188 };
189 const resumeLikeResponse = {
190 approvalPolicy: "never",
191 approvalsReviewer: "user",
192 cwd: "/tmp/project",
193 model: "gpt-5.6-sol",
194 modelProvider: "openai",
195 sandbox: { type: "dangerFullAccess" },
196 thread: failedThread,
197 };
198 assert.equal(isThreadReadResponse({ thread: failedThread }), true);
199 assert.equal(isThreadResumeResponse(resumeLikeResponse), true);
200 assert.equal(isThreadForkResponse(resumeLikeResponse), true);
201 const decodedResume = decodeThreadResumeResponse(resumeLikeResponse);
202 assert.equal(decodedResume.thread.turns[0]?.error?.codexErrorInfo, "misalignmentPolicyViolation");
203 assert.equal(
204 isTurnCompletedNotification({
205 threadId: "thread-1",
206 turn: {
207 error: {
208 codexErrorInfo: "misalignmentPolicyViolation",
209 message: "Misalignment policy violation",
210 },
211 id: "turn-1",
212 items: [],
213 status: "failed",
214 },
215 }),
216 true,
217 );
218});
219​
220it("accepts account plan slugs newer than the pinned protocol", () => {
221 const planTypes = [
222 "self_serve_business_prolite",
223 "ent26",
224 "enterprise_cbp_automation",
225 "edu_plus",
226 "edu_pro",
227 "promax",
228 "some_future_plan",
229 ];
230​
231 for (const planType of planTypes) {
232 const accountResponse = {
233 account: {
234 email: "user@example.com",
235 planType,
236 type: "chatgpt",
237 },
238 requiresOpenaiAuth: true,
239 };
240​
241 assert.equal(isGetAccountResponse(accountResponse), true);
242 }
243});
244​
245it("accepts Codex model-request hook runs", () => {
246 for (const eventName of ["preModelRequest", "postModelRequest"]) {
247 const notification = {
248 threadId: "thread-1",
249 turnId: "turn-1",
250 run: {
251 id: `hook-${eventName}`,
252 eventName,
253 executionMode: "sync",
254 handlerType: "command",
255 scope: "turn",
256 sourcePath: "/tmp/project/.codex/hooks.json",
257 startedAt: 1,
258 status: "stopped",
259 displayOrder: 0,
260 entries: [{ kind: "stop", text: "Stop reason supplied by hook" }],
261 },
262 };
263 assert.equal(isHookStartedNotification(notification), true);
264 assert.equal(isHookCompletedNotification(notification), true);
265 }
266});