authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-06-20 18:30:19-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-06-20 18:30:19-07:00
log6424197a19a28d5992be555633a6e37fd0a58573
tree6ff5d87ae10e57c21239149fea986312588927c3
parent8d4a8b4f3179a054b545b252a1b4c005e9da7875
signature Signed by PGP key B5690EEEBB952194

[codex] Structure unroutable app-server messages (#3463)

Co-authored-by: codex <codex@users.noreply.github.com>

3 files changed, 63 insertions(+), 5 deletions(-)

packages/effect-codex-app-server/src/errors.ts+32-1
...@@ -82,6 +82,11 @@ const payloadKind = (payload: unknown): CodexAppServerPayloadKind => {...@@ -82,6 +82,11 @@ const payloadKind = (payload: unknown): CodexAppServerPayloadKind => {
82 return typeof payload;82 return typeof payload;
83};83};
8484
85const protocolMessageFields = ["id", "method", "params", "result", "error"] as const;
86
87export const CodexAppServerProtocolMessageField = Schema.Literals(protocolMessageFields);
88export type CodexAppServerProtocolMessageField = typeof CodexAppServerProtocolMessageField.Type;
89
85export interface CodexAppServerRequestDiagnostics {90export interface CodexAppServerRequestDiagnostics {
86 readonly method?: string;91 readonly method?: string;
87 readonly requestId?: string;92 readonly requestId?: string;
...@@ -157,7 +162,8 @@ export class CodexAppServerProtocolParseError extends Schema.TaggedErrorClass<Co...@@ -157,7 +162,8 @@ export class CodexAppServerProtocolParseError extends Schema.TaggedErrorClass<Co
157 operation: CodexAppServerProtocolParseOperation,162 operation: CodexAppServerProtocolParseOperation,
158 method: Schema.optionalKey(Schema.String),163 method: Schema.optionalKey(Schema.String),
159 requestId: Schema.optionalKey(Schema.String),164 requestId: Schema.optionalKey(Schema.String),
160 detail: Schema.optionalKey(Schema.String),165 payloadKind: Schema.optionalKey(CodexAppServerPayloadKind),
166 presentFields: Schema.optionalKey(Schema.Array(CodexAppServerProtocolMessageField)),
161 issueCount: Schema.optionalKey(Schema.Number),167 issueCount: Schema.optionalKey(Schema.Number),
162 issueKinds: Schema.optionalKey(Schema.Array(CodexAppServerSchemaIssueKind)),168 issueKinds: Schema.optionalKey(Schema.Array(CodexAppServerSchemaIssueKind)),
163 maximumPathDepth: Schema.optionalKey(Schema.Number),169 maximumPathDepth: Schema.optionalKey(Schema.Number),
...@@ -196,6 +202,31 @@ export class CodexAppServerProtocolParseError extends Schema.TaggedErrorClass<Co...@@ -196,6 +202,31 @@ export class CodexAppServerProtocolParseError extends Schema.TaggedErrorClass<Co
196 cause,202 cause,
197 });203 });
198 }204 }
205
206 static fromUnroutableMessage(message: unknown) {
207 const diagnostics = { payloadKind: payloadKind(message) };
208 if (typeof message !== "object" || message === null || Array.isArray(message)) {
209 return new CodexAppServerProtocolParseError({
210 operation: "route-wire-message",
211 ...diagnostics,
212 });
213 }
214
215 const presentFields = protocolMessageFields.filter((field) => field in message);
216 const method =
217 "method" in message && typeof message.method === "string" ? message.method : undefined;
218 const requestId =
219 "id" in message && (typeof message.id === "string" || typeof message.id === "number")
220 ? String(message.id)
221 : undefined;
222 return new CodexAppServerProtocolParseError({
223 operation: "route-wire-message",
224 ...diagnostics,
225 presentFields,
226 ...(method === undefined ? {} : { method }),
227 ...(requestId === undefined ? {} : { requestId }),
228 });
229 }
199}230}
200231
201export class CodexAppServerTransportError extends Schema.TaggedErrorClass<CodexAppServerTransportError>()(232export class CodexAppServerTransportError extends Schema.TaggedErrorClass<CodexAppServerTransportError>()(
packages/effect-codex-app-server/src/protocol.test.ts+30
...@@ -311,6 +311,36 @@ it.layer(NodeServices.layer)("effect-codex-app-server protocol", (it) => {...@@ -311,6 +311,36 @@ it.layer(NodeServices.layer)("effect-codex-app-server protocol", (it) => {
311 }),311 }),
312 );312 );
313313
314 it.effect("describes unroutable messages with safe structural diagnostics", () =>
315 Effect.gen(function* () {
316 const secret = "codex-unroutable-secret-sentinel";
317 const { stdio, input } = yield* makeInMemoryStdio();
318 const termination = yield* Deferred.make<CodexError.CodexAppServerError>();
319 yield* CodexProtocol.makeCodexAppServerPatchedProtocol({
320 stdio,
321 onTermination: (error) => Deferred.succeed(termination, error).pipe(Effect.asVoid),
322 });
323
324 yield* Queue.offer(
325 input,
326 encodeJsonl({ id: true, method: "thread/start", params: { token: secret } }),
327 );
328
329 const error = yield* Deferred.await(termination);
330 assert.instanceOf(error, CodexError.CodexAppServerProtocolParseError);
331 assert.deepInclude(error, {
332 operation: "route-wire-message",
333 method: "thread/start",
334 payloadKind: "object",
335 presentFields: ["id", "method", "params"],
336 });
337 assert.isUndefined(error.requestId);
338 assert.notProperty(error, "detail");
339 assert.notProperty(error, "cause");
340 assert.notInclude(error.message, secret);
341 }),
342 );
343
314 it.effect("classifies an input stream ending without inventing a cause", () =>344 it.effect("classifies an input stream ending without inventing a cause", () =>
315 Effect.gen(function* () {345 Effect.gen(function* () {
316 const { stdio, input } = yield* makeInMemoryStdio();346 const { stdio, input } = yield* makeInMemoryStdio();
packages/effect-codex-app-server/src/protocol.ts+1-4
...@@ -310,10 +310,7 @@ export const makeCodexAppServerPatchedProtocol = Effect.fn("makeCodexAppServerPa...@@ -310,10 +310,7 @@ export const makeCodexAppServerPatchedProtocol = Effect.fn("makeCodexAppServerPa
310 return handleResponse(message);310 return handleResponse(message);
311 }311 }
312 return Effect.fail(312 return Effect.fail(
313 new CodexError.CodexAppServerProtocolParseError({313 CodexError.CodexAppServerProtocolParseError.fromUnroutableMessage(message),
314 detail: "Received protocol message in an unknown shape",
315 operation: "route-wire-message",
316 }),
317 );314 );
318 };315 };
319316