1import * as Schema from "effect/Schema";
2import type * as SchemaIssue from "effect/SchemaIssue";
3
4const CodexAppServerRequestOperation = Schema.Literals([
5 "decode-payload",
6 "encode-payload",
7 "handle-request",
8 "receive-response",
9]);
10type CodexAppServerRequestOperation = typeof CodexAppServerRequestOperation.Type;
11
12const CodexAppServerSchemaIssueKind = Schema.Literals([
13 "Filter",
14 "Encoding",
15 "Pointer",
16 "Composite",
17 "AnyOf",
18 "InvalidType",
19 "InvalidValue",
20 "MissingKey",
21 "UnexpectedKey",
22 "Forbidden",
23 "OneOf",
24]);
25type CodexAppServerSchemaIssueKind = typeof CodexAppServerSchemaIssueKind.Type;
26
27interface CodexAppServerSchemaIssueDiagnostics {
28 readonly issueCount: number;
29 readonly issueKinds: ReadonlyArray<CodexAppServerSchemaIssueKind>;
30 readonly maximumPathDepth: number;
31}
32
33const schemaIssueDiagnostics = (root: SchemaIssue.Issue): CodexAppServerSchemaIssueDiagnostics => {
34 let issueCount = 0;
35 let maximumPathDepth = 0;
36 const issueKinds = new Set<CodexAppServerSchemaIssueKind>();
37
38 const visit = (issue: SchemaIssue.Issue, pathDepth: number): void => {
39 issueCount += 1;
40 issueKinds.add(issue._tag);
41 maximumPathDepth = Math.max(maximumPathDepth, pathDepth);
42 switch (issue._tag) {
43 case "Filter":
44 case "Encoding":
45 visit(issue.issue, pathDepth);
46 break;
47 case "Pointer":
48 visit(issue.issue, pathDepth + issue.path.length);
49 break;
50 case "Composite":
51 case "AnyOf":
52 for (const child of issue.issues) visit(child, pathDepth);
53 break;
54 }
55 };
56
57 visit(root, 0);
58 return {
59 issueCount,
60 issueKinds: [...issueKinds],
61 maximumPathDepth,
62 };
63};
64
65const CodexAppServerPayloadKind = Schema.Literals([
66 "null",
67 "array",
68 "string",
69 "number",
70 "boolean",
71 "bigint",
72 "object",
73 "symbol",
74 "function",
75 "undefined",
76]);
77type CodexAppServerPayloadKind = typeof CodexAppServerPayloadKind.Type;
78
79const payloadKind = (payload: unknown): CodexAppServerPayloadKind => {
80 if (payload === null) return "null";
81 if (Array.isArray(payload)) return "array";
82 return typeof payload;
83};
84
85const protocolMessageFields = ["id", "method", "params", "result", "error"] as const;
86
87const CodexAppServerProtocolMessageField = Schema.Literals(protocolMessageFields);
88
89export interface CodexAppServerRequestDiagnostics {
90 readonly method?: string;
91 readonly requestId?: string;
92 readonly operation?: CodexAppServerRequestOperation;
93 readonly cause?: unknown;
94 readonly issueCount?: number;
95 readonly issueKinds?: ReadonlyArray<CodexAppServerSchemaIssueKind>;
96 readonly maximumPathDepth?: number;
97 readonly payloadKind?: CodexAppServerPayloadKind;
98}
99
100export const CodexAppServerProtocolParseOperation = Schema.Literals([
101 "encode-wire-message",
102 "decode-wire-message",
103 "route-wire-message",
104 "decode-notification-payload",
105 "decode-request-payload",
106 "decode-response-payload",
107]);
108export type CodexAppServerProtocolParseOperation = typeof CodexAppServerProtocolParseOperation.Type;
109
110export const CodexAppServerTransportOperation = Schema.Literals([
111 "read-input-stream",
112 "read-process-exit-status",
113]);
114export type CodexAppServerTransportOperation = typeof CodexAppServerTransportOperation.Type;
115
116export const CodexAppServerIdentifierPurpose = Schema.Literals([
117 "provider-event",
118 "command-approval-request",
119 "file-change-approval-request",
120 "mcp-elicitation-request",
121 "app-permission-approval-request",
122 "user-input-request",
123]);
124export type CodexAppServerIdentifierPurpose = typeof CodexAppServerIdentifierPurpose.Type;
125
126export interface CodexAppServerProtocolErrorShape {
127 readonly code: number;
128 readonly message: string;
129 readonly data?: unknown;
130}
131
132export class CodexAppServerSpawnError extends Schema.TaggedError<CodexAppServerSpawnError>()(
133 "CodexAppServerSpawnError",
134 {
135 command: Schema.optional(Schema.String),
136 cause: Schema.Defect(),
137 },
138) {
139 override get message() {
140 return this.command
141 ? `Failed to spawn Codex App Server process for command: ${this.command}`
142 : "Failed to spawn Codex App Server process";
143 }
144}
145
146export class CodexAppServerProcessExitedError extends Schema.TaggedError<CodexAppServerProcessExitedError>()(
147 "CodexAppServerProcessExitedError",
148 {
149 code: Schema.optional(Schema.Number),
150 pid: Schema.optionalKey(Schema.Int),
151 cause: Schema.optional(Schema.Defect()),
152 },
153) {
154 override get message() {
155 return this.code === undefined
156 ? "Codex App Server process exited"
157 : `Codex App Server process exited with code ${this.code}`;
158 }
159}
160
161export class CodexAppServerProtocolParseError extends Schema.TaggedError<CodexAppServerProtocolParseError>()(
162 "CodexAppServerProtocolParseError",
163 {
164 operation: CodexAppServerProtocolParseOperation,
165 method: Schema.optionalKey(Schema.String),
166 requestId: Schema.optionalKey(Schema.String),
167 payloadKind: Schema.optionalKey(CodexAppServerPayloadKind),
168 presentFields: Schema.optionalKey(Schema.Array(CodexAppServerProtocolMessageField)),
169 issueCount: Schema.optionalKey(Schema.Number),
170 issueKinds: Schema.optionalKey(Schema.Array(CodexAppServerSchemaIssueKind)),
171 maximumPathDepth: Schema.optionalKey(Schema.Number),
172 cause: Schema.optional(Schema.Defect()),
173 },
174) {
175 override get message() {
176 const method = this.method === undefined ? "" : ` for method '${this.method}'`;
177 return `Codex App Server protocol operation '${this.operation}' failed${method}.`;
178 }
179
180 static fromSchemaError(
181 operation: CodexAppServerProtocolParseOperation,
182 cause: Schema.SchemaError,
183 context: { readonly method?: string; readonly requestId?: string } = {},
184 ) {
185 return new CodexAppServerProtocolParseError({
186 operation,
187 ...context,
188 ...schemaIssueDiagnostics(cause.issue),
189 cause,
190 });
191 }
192
193 static fromRequestError(
194 operation: CodexAppServerProtocolParseOperation,
195 method: string,
196 cause: CodexAppServerRequestError,
197 ) {
198 return new CodexAppServerProtocolParseError({
199 operation,
200 method,
201 ...(cause.issueCount === undefined ? {} : { issueCount: cause.issueCount }),
202 ...(cause.issueKinds === undefined ? {} : { issueKinds: cause.issueKinds }),
203 ...(cause.maximumPathDepth === undefined ? {} : { maximumPathDepth: cause.maximumPathDepth }),
204 cause,
205 });
206 }
207
208 static fromUnroutableMessage(message: unknown) {
209 const diagnostics = { payloadKind: payloadKind(message) };
210 if (typeof message !== "object" || message === null || Array.isArray(message)) {
211 return new CodexAppServerProtocolParseError({
212 operation: "route-wire-message",
213 ...diagnostics,
214 });
215 }
216
217 const presentFields = protocolMessageFields.filter((field) => field in message);
218 const method =
219 "method" in message && typeof message.method === "string" ? message.method : undefined;
220 const requestId =
221 "id" in message && (typeof message.id === "string" || typeof message.id === "number")
222 ? String(message.id)
223 : undefined;
224 return new CodexAppServerProtocolParseError({
225 operation: "route-wire-message",
226 ...diagnostics,
227 presentFields,
228 ...(method === undefined ? {} : { method }),
229 ...(requestId === undefined ? {} : { requestId }),
230 });
231 }
232}
233
234export class CodexAppServerTransportError extends Schema.TaggedError<CodexAppServerTransportError>()(
235 "CodexAppServerTransportError",
236 {
237 operation: CodexAppServerTransportOperation,
238 pid: Schema.optionalKey(Schema.Int),
239 cause: Schema.Defect(),
240 },
241) {
242 override get message() {
243 return `Codex App Server transport operation '${this.operation}' failed.`;
244 }
245}
246
247export class CodexAppServerIdentifierGenerationError extends Schema.TaggedError<CodexAppServerIdentifierGenerationError>()(
248 "CodexAppServerIdentifierGenerationError",
249 {
250 purpose: CodexAppServerIdentifierPurpose,
251 cause: Schema.Defect(),
252 },
253) {
254 override get message() {
255 return `Failed to generate Codex App Server identifier for ${this.purpose}.`;
256 }
257}
258
259export class CodexAppServerInputStreamEndedError extends Schema.TaggedError<CodexAppServerInputStreamEndedError>()(
260 "CodexAppServerInputStreamEndedError",
261 {},
262) {
263 override get message() {
264 return "Codex App Server input stream ended.";
265 }
266}
267
268export class CodexAppServerRequestError extends Schema.TaggedError<CodexAppServerRequestError>()(
269 "CodexAppServerRequestError",
270 {
271 code: Schema.Number,
272 errorMessage: Schema.String,
273 data: Schema.optional(Schema.Unknown),
274 method: Schema.optionalKey(Schema.String),
275 requestId: Schema.optionalKey(Schema.String),
276 operation: Schema.optionalKey(CodexAppServerRequestOperation),
277 issueCount: Schema.optionalKey(Schema.Number),
278 issueKinds: Schema.optionalKey(Schema.Array(CodexAppServerSchemaIssueKind)),
279 maximumPathDepth: Schema.optionalKey(Schema.Number),
280 payloadKind: Schema.optionalKey(CodexAppServerPayloadKind),
281 cause: Schema.optionalKey(Schema.Defect()),
282 },
283) {
284 override get message() {
285 return this.errorMessage;
286 }
287
288 static fromProtocolError(
289 error: CodexAppServerProtocolErrorShape,
290 method: string,
291 requestId: string,
292 ) {
293 return new CodexAppServerRequestError({
294 code: error.code,
295 errorMessage: error.message,
296 ...(error.data !== undefined ? { data: error.data } : {}),
297 method,
298 requestId,
299 operation: "receive-response",
300 cause: error,
301 });
302 }
303
304 static fromAppServerError(error: CodexAppServerError, method: string) {
305 if (error._tag === "CodexAppServerRequestError") {
306 return error;
307 }
308 return CodexAppServerRequestError.internalError(
309 `Codex App Server request handler failed for method '${method}'`,
310 undefined,
311 {
312 method,
313 operation: "handle-request",
314 cause: error,
315 },
316 );
317 }
318
319 static parseError(message = "Parse error", data?: unknown) {
320 return new CodexAppServerRequestError({
321 code: -32700,
322 errorMessage: message,
323 ...(data !== undefined ? { data } : {}),
324 });
325 }
326
327 static invalidRequest(message = "Invalid request", data?: unknown) {
328 return new CodexAppServerRequestError({
329 code: -32600,
330 errorMessage: message,
331 ...(data !== undefined ? { data } : {}),
332 });
333 }
334
335 static methodNotFound(method: string) {
336 return new CodexAppServerRequestError({
337 code: -32601,
338 errorMessage: `Method not found: ${method}`,
339 });
340 }
341
342 static invalidParams(
343 message = "Invalid params",
344 data?: unknown,
345 diagnostics: CodexAppServerRequestDiagnostics = {},
346 ) {
347 return new CodexAppServerRequestError({
348 code: -32602,
349 errorMessage: message,
350 ...(data !== undefined ? { data } : {}),
351 ...diagnostics,
352 });
353 }
354
355 static invalidPayload(
356 method: string,
357 operation: "decode-payload" | "encode-payload",
358 cause: Schema.SchemaError,
359 ) {
360 const diagnostics = schemaIssueDiagnostics(cause.issue);
361 return new CodexAppServerRequestError({
362 code: -32602,
363 errorMessage: `Invalid payload for method '${method}' during '${operation}'`,
364 data: diagnostics,
365 method,
366 operation,
367 ...diagnostics,
368 cause,
369 });
370 }
371
372 static unexpectedPayload(
373 method: string,
374 operation: "decode-payload" | "encode-payload",
375 payload: unknown,
376 ) {
377 const diagnostics = { payloadKind: payloadKind(payload) };
378 return new CodexAppServerRequestError({
379 code: -32602,
380 errorMessage: `Method '${method}' does not accept a payload during '${operation}'`,
381 data: diagnostics,
382 method,
383 operation,
384 ...diagnostics,
385 });
386 }
387
388 static internalError(
389 message = "Internal error",
390 data?: unknown,
391 diagnostics: CodexAppServerRequestDiagnostics = {},
392 ) {
393 return new CodexAppServerRequestError({
394 code: -32603,
395 errorMessage: message,
396 ...(data !== undefined ? { data } : {}),
397 ...diagnostics,
398 });
399 }
400
401 static overloaded(message = "Server overloaded; retry later.", data?: unknown) {
402 return new CodexAppServerRequestError({
403 code: -32001,
404 errorMessage: message,
405 ...(data !== undefined ? { data } : {}),
406 });
407 }
408
409 toProtocolError(): CodexAppServerProtocolErrorShape {
410 return {
411 code: this.code,
412 message: this.errorMessage,
413 ...(this.data !== undefined ? { data: this.data } : {}),
414 };
415 }
416}
417
418export const CodexAppServerError = Schema.Union([
419 CodexAppServerRequestError,
420 CodexAppServerSpawnError,
421 CodexAppServerProcessExitedError,
422 CodexAppServerProtocolParseError,
423 CodexAppServerTransportError,
424 CodexAppServerIdentifierGenerationError,
425 CodexAppServerInputStreamEndedError,
426]);
427
428export type CodexAppServerError = typeof CodexAppServerError.Type;