From 8b093935cfe230be759edb7eeeeb4cb3f1e938bf Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Sat, 20 Jun 2026 18:21:20 -0700 Subject: [PATCH] [codex] Structure unroutable app-server messages Co-authored-by: codex --- .../effect-codex-app-server/src/errors.ts | 33 ++++++++++++++++++- .../src/protocol.test.ts | 30 +++++++++++++++++ .../effect-codex-app-server/src/protocol.ts | 5 +-- 3 files changed, 63 insertions(+), 5 deletions(-) diff --git a/packages/effect-codex-app-server/src/errors.ts b/packages/effect-codex-app-server/src/errors.ts index 2559bba618c..3826a099229 100644 --- a/packages/effect-codex-app-server/src/errors.ts +++ b/packages/effect-codex-app-server/src/errors.ts @@ -82,6 +82,11 @@ const payloadKind = (payload: unknown): CodexAppServerPayloadKind => { return typeof payload; }; +const protocolMessageFields = ["id", "method", "params", "result", "error"] as const; + +export const CodexAppServerProtocolMessageField = Schema.Literals(protocolMessageFields); +export type CodexAppServerProtocolMessageField = typeof CodexAppServerProtocolMessageField.Type; + export interface CodexAppServerRequestDiagnostics { readonly method?: string; readonly requestId?: string; @@ -157,7 +162,8 @@ export class CodexAppServerProtocolParseError extends Schema.TaggedErrorClass field in message); + const method = + "method" in message && typeof message.method === "string" ? message.method : undefined; + const requestId = + "id" in message && (typeof message.id === "string" || typeof message.id === "number") + ? String(message.id) + : undefined; + return new CodexAppServerProtocolParseError({ + operation: "route-wire-message", + ...diagnostics, + presentFields, + ...(method === undefined ? {} : { method }), + ...(requestId === undefined ? {} : { requestId }), + }); + } } export class CodexAppServerTransportError extends Schema.TaggedErrorClass()( diff --git a/packages/effect-codex-app-server/src/protocol.test.ts b/packages/effect-codex-app-server/src/protocol.test.ts index f387ca382be..0ed81b3b9e6 100644 --- a/packages/effect-codex-app-server/src/protocol.test.ts +++ b/packages/effect-codex-app-server/src/protocol.test.ts @@ -311,6 +311,36 @@ it.layer(NodeServices.layer)("effect-codex-app-server protocol", (it) => { }), ); + it.effect("describes unroutable messages with safe structural diagnostics", () => + Effect.gen(function* () { + const secret = "codex-unroutable-secret-sentinel"; + const { stdio, input } = yield* makeInMemoryStdio(); + const termination = yield* Deferred.make(); + yield* CodexProtocol.makeCodexAppServerPatchedProtocol({ + stdio, + onTermination: (error) => Deferred.succeed(termination, error).pipe(Effect.asVoid), + }); + + yield* Queue.offer( + input, + encodeJsonl({ id: true, method: "thread/start", params: { token: secret } }), + ); + + const error = yield* Deferred.await(termination); + assert.instanceOf(error, CodexError.CodexAppServerProtocolParseError); + assert.deepInclude(error, { + operation: "route-wire-message", + method: "thread/start", + payloadKind: "object", + presentFields: ["id", "method", "params"], + }); + assert.isUndefined(error.requestId); + assert.notProperty(error, "detail"); + assert.notProperty(error, "cause"); + assert.notInclude(error.message, secret); + }), + ); + it.effect("classifies an input stream ending without inventing a cause", () => Effect.gen(function* () { const { stdio, input } = yield* makeInMemoryStdio(); diff --git a/packages/effect-codex-app-server/src/protocol.ts b/packages/effect-codex-app-server/src/protocol.ts index fbf173cbc5e..825c59b9b2c 100644 --- a/packages/effect-codex-app-server/src/protocol.ts +++ b/packages/effect-codex-app-server/src/protocol.ts @@ -310,10 +310,7 @@ export const makeCodexAppServerPatchedProtocol = Effect.fn("makeCodexAppServerPa return handleResponse(message); } return Effect.fail( - new CodexError.CodexAppServerProtocolParseError({ - detail: "Received protocol message in an unknown shape", - operation: "route-wire-message", - }), + CodexError.CodexAppServerProtocolParseError.fromUnroutableMessage(message), ); };