| 1 | import * as Schema from "effect/Schema"; |
| 2 | import type * as SchemaIssue from "effect/SchemaIssue"; |
| 3 | |
| 4 | const CodexAppServerRequestOperation = Schema.Literals([ |
| 5 | "decode-payload", |
| 6 | "encode-payload", |
| 7 | "handle-request", |
| 8 | "receive-response", |
| 9 | ]); |
| 10 | type CodexAppServerRequestOperation = typeof CodexAppServerRequestOperation.Type; |
| 11 | |
| 12 | const 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 | ]); |
| 25 | type CodexAppServerSchemaIssueKind = typeof CodexAppServerSchemaIssueKind.Type; |
| 26 | |
| 27 | interface CodexAppServerSchemaIssueDiagnostics { |
| 28 | readonly issueCount: number; |
| 29 | readonly issueKinds: ReadonlyArray<CodexAppServerSchemaIssueKind>; |
| 30 | readonly maximumPathDepth: number; |
| 31 | } |
| 32 | |
| 33 | const 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 | |
| 65 | const CodexAppServerPayloadKind = Schema.Literals([ |
| 66 | "null", |
| 67 | "array", |
| 68 | "string", |
| 69 | "number", |
| 70 | "boolean", |
| 71 | "bigint", |
| 72 | "object", |
| 73 | "symbol", |
| 74 | "function", |
| 75 | "undefined", |
| 76 | ]); |
| 77 | type CodexAppServerPayloadKind = typeof CodexAppServerPayloadKind.Type; |
| 78 | |
| 79 | const payloadKind = (payload: unknown): CodexAppServerPayloadKind => { |
| 80 | if (payload === null) return "null"; |
| 81 | if (Array.isArray(payload)) return "array"; |
| 82 | return typeof payload; |
| 83 | }; |
| 84 | |
| 85 | const protocolMessageFields = ["id", "method", "params", "result", "error"] as const; |
| 86 | |
| 87 | const CodexAppServerProtocolMessageField = Schema.Literals(protocolMessageFields); |
| 88 | |
| 89 | export 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 | |
| 100 | export 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 | ]); |
| 108 | export type CodexAppServerProtocolParseOperation = typeof CodexAppServerProtocolParseOperation.Type; |
| 109 | |
| 110 | export const CodexAppServerTransportOperation = Schema.Literals([ |
| 111 | "read-input-stream", |
| 112 | "read-process-exit-status", |
| 113 | ]); |
| 114 | export type CodexAppServerTransportOperation = typeof CodexAppServerTransportOperation.Type; |
| 115 | |
| 116 | export 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 | ]); |
| 124 | export type CodexAppServerIdentifierPurpose = typeof CodexAppServerIdentifierPurpose.Type; |
| 125 | |
| 126 | export interface CodexAppServerProtocolErrorShape { |
| 127 | readonly code: number; |
| 128 | readonly message: string; |
| 129 | readonly data?: unknown; |
| 130 | } |
| 131 | |
| 132 | export 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 | |
| 146 | export 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 | |
| 161 | export 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 | |
| 234 | export 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 | |
| 247 | export 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 | |
| 259 | export 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 | |
| 268 | export 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 | |
| 418 | export const CodexAppServerError = Schema.Union([ |
| 419 | CodexAppServerRequestError, |
| 420 | CodexAppServerSpawnError, |
| 421 | CodexAppServerProcessExitedError, |
| 422 | CodexAppServerProtocolParseError, |
| 423 | CodexAppServerTransportError, |
| 424 | CodexAppServerIdentifierGenerationError, |
| 425 | CodexAppServerInputStreamEndedError, |
| 426 | ]); |
| 427 | |
| 428 | export type CodexAppServerError = typeof CodexAppServerError.Type; |