import { assert, it } from "@effect/vitest";import * as Effect from "effect/Effect";import * as Schema from "effect/Schema"; import * as CodexError from "../errors.ts";import * as Shared from "./shared.ts"; const decodeNestedNumberPayload = Schema.decodeUnknownEffect( Schema.Struct({ profile: Schema.Struct({ token: Schema.Number }) }),);const encodeUnknownJson = Schema.encodeSync(Schema.fromJsonString(Schema.Unknown)); it.effect("preserves schema decode diagnostics without deriving the message from the cause", () => Effect.gen(function* () { const error = yield* Shared.decodeOptionalPayload("thread/start", Schema.String, 42).pipe( Effect.flip, ); assert.instanceOf(error, CodexError.CodexAppServerRequestError); assert.equal(error.code, -32602); assert.equal(error.method, "thread/start"); assert.equal(error.operation, "decode-payload"); assert.equal( error.message, "Invalid payload for method 'thread/start' during 'decode-payload'", ); assert.isTrue(Schema.isSchemaError(error.cause)); const protocolError = error.toProtocolError(); assert.equal(protocolError.code, -32602); assert.equal(protocolError.message, error.message); assert.property(protocolError, "data"); assert.notProperty(protocolError, "method"); assert.notProperty(protocolError, "operation"); assert.notProperty(protocolError, "cause"); }),); it.effect("preserves schema encode diagnostics", () => Effect.gen(function* () { const error = yield* Shared.encodeOptionalPayload( "thread/start", Schema.Number, "not-a-number" as never, ).pipe(Effect.flip); assert.equal(error.method, "thread/start"); assert.equal(error.operation, "encode-payload"); assert.equal( error.message, "Invalid payload for method 'thread/start' during 'encode-payload'", ); assert.isTrue(Schema.isSchemaError(error.cause)); }),); it.effect("does not invent a cause when a method has no payload schema", () => Effect.gen(function* () { const secret = "unexpected-payload-secret"; const error = yield* Shared.decodeOptionalPayload<never, never>("initialized", undefined, { token: secret, }).pipe(Effect.flip); assert.equal(error.method, "initialized"); assert.equal(error.operation, "decode-payload"); assert.equal(error.payloadKind, "object"); assert.deepEqual(error.data, { payloadKind: "object" }); assert.isUndefined(error.cause); assert.notInclude(error.message, secret); assert.notInclude(encodeUnknownJson(error.toProtocolError()), secret); }),); it.effect("keeps invalid payload values only in the exact schema cause", () => Effect.gen(function* () { const secret = "codex-schema-payload-secret"; const cause = yield* decodeNestedNumberPayload({ profile: { token: secret } }).pipe( Effect.flip, ); const error = CodexError.CodexAppServerRequestError.invalidPayload( "thread/start", "decode-payload", cause, ); const { cause: directCause, ...directDiagnostics } = error; assert.strictEqual(directCause, cause); assert.equal(error.method, "thread/start"); assert.equal(error.operation, "decode-payload"); assert.equal(error.maximumPathDepth, 2); assert.isAbove(error.issueCount ?? 0, 0); assert.include(error.issueKinds ?? [], "Pointer"); assert.notInclude(error.message, secret); assert.notInclude(encodeUnknownJson(directDiagnostics), secret); assert.notInclude(encodeUnknownJson(error.toProtocolError()), secret); }),); it.effect("retains the request-handler error as the internal error cause", () => Effect.gen(function* () { const rootCause = new Error("socket closed"); const source = new CodexError.CodexAppServerTransportError({ operation: "read-input-stream", cause: rootCause, }); const error = yield* Shared.runHandler( (_payload: void) => Effect.fail(source), undefined, "thread/start", ).pipe(Effect.flip); assert.equal(error.code, -32603); assert.equal(error.method, "thread/start"); assert.equal(error.operation, "handle-request"); assert.equal( error.message, "Codex App Server request handler failed for method 'thread/start'", ); assert.strictEqual(error.cause, source); assert.strictEqual(source.cause, rootCause); assert.notInclude(error.message, source.message); }),); it.effect("passes request errors through without adding a wrapper", () => Effect.gen(function* () { const source = CodexError.CodexAppServerRequestError.invalidParams("Invalid thread id"); const error = yield* Shared.runHandler( (_payload: void) => Effect.fail(source), undefined, "thread/start", ).pipe(Effect.flip); assert.strictEqual(error, source); }),); it.effect("retains the full notification payload decode cause chain", () => Effect.gen(function* () { const error = yield* Shared.decodeNotificationPayload( "item/agentMessage/delta", Schema.String, 42, ).pipe(Effect.flip); assert.equal(error.method, "item/agentMessage/delta"); assert.equal(error.operation, "decode-notification-payload"); assert.instanceOf(error.cause, CodexError.CodexAppServerRequestError); assert.isTrue(Schema.isSchemaError(error.cause.cause)); }),);
import { assert, it } from "@effect/vitest";
import * as Effect from "effect/Effect";
import * as Schema from "effect/Schema";
import * as CodexError from "../errors.ts";
import * as Shared from "./shared.ts";
const decodeNestedNumberPayload = Schema.decodeUnknownEffect(
Schema.Struct({ profile: Schema.Struct({ token: Schema.Number }) }),
);
const encodeUnknownJson = Schema.encodeSync(Schema.fromJsonString(Schema.Unknown));
it.effect("preserves schema decode diagnostics without deriving the message from the cause", () =>
Effect.gen(function* () {
const error = yield* Shared.decodeOptionalPayload("thread/start", Schema.String, 42).pipe(
Effect.flip,
);
assert.instanceOf(error, CodexError.CodexAppServerRequestError);
assert.equal(error.code, -32602);
assert.equal(error.method, "thread/start");
assert.equal(error.operation, "decode-payload");
assert.equal(
error.message,
"Invalid payload for method 'thread/start' during 'decode-payload'",
);
assert.isTrue(Schema.isSchemaError(error.cause));
const protocolError = error.toProtocolError();
assert.equal(protocolError.code, -32602);
assert.equal(protocolError.message, error.message);
assert.property(protocolError, "data");
assert.notProperty(protocolError, "method");
assert.notProperty(protocolError, "operation");
assert.notProperty(protocolError, "cause");
}),
);
it.effect("preserves schema encode diagnostics", () =>
Effect.gen(function* () {
const error = yield* Shared.encodeOptionalPayload(
"thread/start",
Schema.Number,
"not-a-number" as never,
).pipe(Effect.flip);
assert.equal(error.method, "thread/start");
assert.equal(error.operation, "encode-payload");
assert.equal(
error.message,
"Invalid payload for method 'thread/start' during 'encode-payload'",
);
assert.isTrue(Schema.isSchemaError(error.cause));
}),
);
it.effect("does not invent a cause when a method has no payload schema", () =>
Effect.gen(function* () {
const secret = "unexpected-payload-secret";
const error = yield* Shared.decodeOptionalPayload<never, never>("initialized", undefined, {
token: secret,
}).pipe(Effect.flip);
assert.equal(error.method, "initialized");
assert.equal(error.operation, "decode-payload");
assert.equal(error.payloadKind, "object");
assert.deepEqual(error.data, { payloadKind: "object" });
assert.isUndefined(error.cause);
assert.notInclude(error.message, secret);
assert.notInclude(encodeUnknownJson(error.toProtocolError()), secret);
}),
);
it.effect("keeps invalid payload values only in the exact schema cause", () =>
Effect.gen(function* () {
const secret = "codex-schema-payload-secret";
const cause = yield* decodeNestedNumberPayload({ profile: { token: secret } }).pipe(
Effect.flip,
);
const error = CodexError.CodexAppServerRequestError.invalidPayload(
"thread/start",
"decode-payload",
cause,
);
const { cause: directCause, ...directDiagnostics } = error;
assert.strictEqual(directCause, cause);
assert.equal(error.method, "thread/start");
assert.equal(error.operation, "decode-payload");
assert.equal(error.maximumPathDepth, 2);
assert.isAbove(error.issueCount ?? 0, 0);
assert.include(error.issueKinds ?? [], "Pointer");
assert.notInclude(error.message, secret);
assert.notInclude(encodeUnknownJson(directDiagnostics), secret);
assert.notInclude(encodeUnknownJson(error.toProtocolError()), secret);
}),
);
it.effect("retains the request-handler error as the internal error cause", () =>
Effect.gen(function* () {
const rootCause = new Error("socket closed");
const source = new CodexError.CodexAppServerTransportError({
operation: "read-input-stream",
cause: rootCause,
});
const error = yield* Shared.runHandler(
(_payload: void) => Effect.fail(source),
undefined,
"thread/start",
).pipe(Effect.flip);
assert.equal(error.code, -32603);
assert.equal(error.method, "thread/start");
assert.equal(error.operation, "handle-request");
assert.equal(
error.message,
"Codex App Server request handler failed for method 'thread/start'",
);
assert.strictEqual(error.cause, source);
assert.strictEqual(source.cause, rootCause);
assert.notInclude(error.message, source.message);
}),
);
it.effect("passes request errors through without adding a wrapper", () =>
Effect.gen(function* () {
const source = CodexError.CodexAppServerRequestError.invalidParams("Invalid thread id");
const error = yield* Shared.runHandler(
(_payload: void) => Effect.fail(source),
undefined,
"thread/start",
).pipe(Effect.flip);
assert.strictEqual(error, source);
}),
);
it.effect("retains the full notification payload decode cause chain", () =>
Effect.gen(function* () {
const error = yield* Shared.decodeNotificationPayload(
"item/agentMessage/delta",
Schema.String,
42,
).pipe(Effect.flip);
assert.equal(error.method, "item/agentMessage/delta");
assert.equal(error.operation, "decode-notification-payload");
assert.instanceOf(error.cause, CodexError.CodexAppServerRequestError);
assert.isTrue(Schema.isSchemaError(error.cause.cause));
}),
);