447 lines · 15.2 KB · TypeScript
RawDownload
import * as Deferred from "effect/Deferred";import * as Effect from "effect/Effect";import * as Fiber from "effect/Fiber";import * as Queue from "effect/Queue";import * as Schema from "effect/Schema";import * as Stream from "effect/Stream"; import * as NodeServices from "@effect/platform-node/NodeServices";import { assert, it } from "@effect/vitest"; import * as CodexError from "./errors.ts";import * as CodexProtocol from "./protocol.ts";import * as CodexRpc from "./rpc.ts";import * as CodexSchema from "./schema.ts";import { makeInMemoryStdio } from "./_internal/stdio.ts";const encodeUnknownJsonString = Schema.encodeUnknownSync(Schema.UnknownFromJsonString); const encoder = new TextEncoder(); const encodeJsonl = (value: unknown) => encoder.encode(`${encodeUnknownJsonString(value)}\n`); const decodeJson = Schema.decodeEffect(Schema.UnknownFromJsonString);const decodeAccountTokenUsageResponse = Schema.decodeUnknownEffect(  CodexRpc.CLIENT_REQUEST_RESPONSES["account/usage/read"],);const decodeAccountRateLimitsResponse = Schema.decodeUnknownEffect(  CodexRpc.CLIENT_REQUEST_RESPONSES["account/rateLimits/read"],);const decodeConsumeRateLimitResetCreditParams = Schema.decodeUnknownEffect(  CodexRpc.CLIENT_REQUEST_PARAMS["account/rateLimitResetCredit/consume"],);const decodeConsumeRateLimitResetCreditResponse = Schema.decodeUnknownEffect(  CodexRpc.CLIENT_REQUEST_RESPONSES["account/rateLimitResetCredit/consume"],); it.layer(NodeServices.layer)("effect-codex-app-server protocol", (it) => {  it.effect("maps account usage responses to the upstream token usage schema", () =>    Effect.gen(function* () {      assert.strictEqual(        CodexRpc.CLIENT_REQUEST_RESPONSES["account/usage/read"],        CodexSchema.V2GetAccountTokenUsageResponse,      );      const decoded = yield* decodeAccountTokenUsageResponse({        dailyUsageBuckets: [{ startDate: "2026-06-10", tokens: 42 }],        summary: { lifetimeTokens: 42 },      });      assert.deepEqual(decoded, {        dailyUsageBuckets: [{ startDate: "2026-06-10", tokens: 42 }],        summary: { lifetimeTokens: 42 },      });    }),  );   it.effect("maps earned rate-limit reset credits from account rate-limit snapshots", () =>    Effect.gen(function* () {      assert.strictEqual(        CodexRpc.CLIENT_REQUEST_RESPONSES["account/rateLimits/read"],        CodexSchema.V2GetAccountRateLimitsResponse,      );       const response = {        rateLimits: {},        rateLimitResetCredits: {          availableCount: 2,          credits: [            {              id: "RateLimitResetCredit_1",              resetType: "codexRateLimits",              status: "available",              grantedAt: 1_781_654_400,              expiresAt: 1_784_246_400,              title: "Full reset",              description: "Ready to redeem",            },            {              id: "RateLimitResetCredit_2",              resetType: "unknown",              status: "unknown",              grantedAt: 1_781_654_401,              expiresAt: null,            },          ],        },      } as const;       assert.deepEqual(yield* decodeAccountRateLimitsResponse(response), response);      assert.deepEqual(        yield* decodeAccountRateLimitsResponse({          rateLimits: {},          rateLimitResetCredits: { availableCount: 2, credits: null },        }),        {          rateLimits: {},          rateLimitResetCredits: { availableCount: 2, credits: null },        },      );    }),  );   it.effect("maps the earned rate-limit reset consume request and response", () =>    Effect.gen(function* () {      assert.equal(        CodexRpc.CLIENT_REQUEST_METHODS["account/rateLimitResetCredit/consume"],        "account/rateLimitResetCredit/consume",      );      assert.strictEqual(        CodexRpc.CLIENT_REQUEST_PARAMS["account/rateLimitResetCredit/consume"],        CodexSchema.V2ConsumeAccountRateLimitResetCreditParams,      );      assert.strictEqual(        CodexRpc.CLIENT_REQUEST_RESPONSES["account/rateLimitResetCredit/consume"],        CodexSchema.V2ConsumeAccountRateLimitResetCreditResponse,      );       assert.deepEqual(        yield* decodeConsumeRateLimitResetCreditParams({          idempotencyKey: "8ae96ff3-3425-4f4c-8772-b6fd61502868",          creditId: "RateLimitResetCredit_1",        }),        {          idempotencyKey: "8ae96ff3-3425-4f4c-8772-b6fd61502868",          creditId: "RateLimitResetCredit_1",        },      );      assert.deepEqual(yield* decodeConsumeRateLimitResetCreditResponse({ outcome: "reset" }), {        outcome: "reset",      });    }),  );   it.effect(    "encodes requests without a jsonrpc field and routes inbound requests and notifications",    () =>      Effect.gen(function* () {        const { stdio, input, output } = yield* makeInMemoryStdio();        const transport = yield* CodexProtocol.makeCodexAppServerPatchedProtocol({ stdio });         const notificationDeferred =          yield* Deferred.make<ReadonlyArray<CodexProtocol.CodexAppServerIncomingNotification>>();        const requestDeferred =          yield* Deferred.make<ReadonlyArray<CodexProtocol.CodexAppServerIncomingRequest>>();         yield* transport.incomingNotifications.pipe(          Stream.take(1),          Stream.runCollect,          Effect.flatMap((notifications) => Deferred.succeed(notificationDeferred, notifications)),          Effect.forkScoped,        );         yield* transport.incomingRequests.pipe(          Stream.take(1),          Stream.runCollect,          Effect.flatMap((requests) => Deferred.succeed(requestDeferred, requests)),          Effect.forkScoped,        );         yield* transport.notify("initialized");        assert.equal(yield* Queue.take(output), '{"method":"initialized"}\n');         const initializeParams = {          clientInfo: {            name: "effect-codex-app-server-test",            title: "Effect Codex App Server Test",            version: "0.0.0",          },          capabilities: {            experimentalApi: true,            optOutNotificationMethods: null,          },        };         const pendingInitialize = yield* transport          .request("initialize", initializeParams)          .pipe(Effect.forkScoped);        assert.deepEqual(yield* decodeJson(yield* Queue.take(output)), {          id: 1,          method: "initialize",          params: initializeParams,        });         yield* Queue.offer(          input,          encodeJsonl({            method: "item/agentMessage/delta",            params: {              delta: "Hello from the mock peer.",              itemId: "item-1",              threadId: "thread-1",              turnId: "turn-1",            },          }),        );        yield* Queue.offer(          input,          encodeJsonl({            id: 77,            method: "item/tool/requestUserInput",            params: {              itemId: "item-approval-1",              threadId: "thread-1",              turnId: "turn-1",              questions: [                {                  id: "approved",                  header: "Approve",                  question: "Continue?",                },              ],            },          }),        );        yield* Queue.offer(          input,          encodeJsonl({            id: 1,            result: {              userAgent: "mock-codex-app-server",              codexHome: "/tmp/codex-home",              platformFamily: "unix",              platformOs: "macos",            },          }),        );         assert.deepEqual(yield* Fiber.join(pendingInitialize), {          userAgent: "mock-codex-app-server",          codexHome: "/tmp/codex-home",          platformFamily: "unix",          platformOs: "macos",        });        assert.deepEqual(yield* Deferred.await(notificationDeferred), [          {            method: "item/agentMessage/delta",            params: {              delta: "Hello from the mock peer.",              itemId: "item-1",              threadId: "thread-1",              turnId: "turn-1",            },          },        ]);        assert.deepEqual(yield* Deferred.await(requestDeferred), [          {            id: 77,            method: "item/tool/requestUserInput",            params: {              itemId: "item-approval-1",              threadId: "thread-1",              turnId: "turn-1",              questions: [                {                  id: "approved",                  header: "Approve",                  question: "Continue?",                },              ],            },          },        ]);         yield* transport.respond(77, {          answers: {            approved: {              answers: ["yes"],            },          },        });        assert.deepEqual(yield* decodeJson(yield* Queue.take(output)), {          id: 77,          result: {            answers: {              approved: {                answers: ["yes"],              },            },          },        });         yield* transport.respondError(          78,          CodexError.CodexAppServerRequestError.methodNotFound("x/test"),        );        assert.deepEqual(yield* decodeJson(yield* Queue.take(output)), {          id: 78,          error: {            code: -32601,            message: "Method not found: x/test",          },        });      }),  );   it.effect("surfaces JSON encoding failures as protocol parse errors", () =>    Effect.gen(function* () {      const { stdio } = yield* makeInMemoryStdio();      const transport = yield* CodexProtocol.makeCodexAppServerPatchedProtocol({ stdio });       const bigintError = yield* transport.notify("x/test", 1n).pipe(Effect.flip);      assert.instanceOf(bigintError, CodexError.CodexAppServerProtocolParseError);      assert.equal(bigintError.operation, "encode-wire-message");      assert.equal(bigintError.method, "x/test");      assert.exists(bigintError.cause);      assert.equal(        bigintError.message,        "Codex App Server protocol operation 'encode-wire-message' failed for method 'x/test'.",      );       const circular: Record<string, unknown> = {};      circular.self = circular;      const circularError = yield* transport.notify("x/test", circular).pipe(Effect.flip);      assert.instanceOf(circularError, CodexError.CodexAppServerProtocolParseError);      assert.equal(circularError.operation, "encode-wire-message");      assert.equal(circularError.method, "x/test");      assert.exists(circularError.cause);       const requestError = yield* transport.request("x/request", 1n).pipe(        Effect.match({          onFailure: (error) => error,          onSuccess: () => assert.fail("Expected request encoding to fail"),        }),      );      assert.instanceOf(requestError, CodexError.CodexAppServerProtocolParseError);      assert.deepInclude(requestError, {        operation: "encode-wire-message",        method: "x/request",        requestId: "1",      });    }),  );   it.effect("correlates response errors with the originating request", () =>    Effect.gen(function* () {      const { stdio, input, output } = yield* makeInMemoryStdio();      const transport = yield* CodexProtocol.makeCodexAppServerPatchedProtocol({ stdio });       const response = yield* transport.request("thread/start", {}).pipe(Effect.forkScoped);      yield* Queue.take(output);      yield* Queue.offer(        input,        encodeJsonl({          id: 1,          error: {            code: -32602,            message: "Invalid params",            data: { field: "cwd" },          },        }),      );       const error = yield* Fiber.join(response).pipe(        Effect.match({          onFailure: (error) => error,          onSuccess: () => assert.fail("Expected Codex App Server request to fail"),        }),      );      assert.instanceOf(error, CodexError.CodexAppServerRequestError);      assert.deepInclude(error, {        code: -32602,        errorMessage: "Invalid params",        method: "thread/start",        requestId: "1",        operation: "receive-response",      });    }),  );   it.effect("logs decode failures without copying the cause or wire payload", () =>    Effect.gen(function* () {      const secret = "codex-wire-secret-sentinel";      const { stdio, input } = yield* makeInMemoryStdio();      const events: Array<CodexProtocol.CodexAppServerProtocolLogEvent> = [];      const termination = yield* Deferred.make<CodexError.CodexAppServerError>();      yield* CodexProtocol.makeCodexAppServerPatchedProtocol({        stdio,        logIncoming: true,        logger: (event) =>          Effect.sync(() => {            events.push(event);          }),        onTermination: (error) => Deferred.succeed(termination, error).pipe(Effect.asVoid),      });       yield* Queue.offer(input, encoder.encode(`{"secret":"${secret}"\n`));      yield* Deferred.await(termination);       const event = events.find(({ stage }) => stage === "decode_failed");      assert.exists(event);      assert.equal(event.direction, "incoming");      const payload = event.payload as Record<string, unknown>;      assert.equal(payload.operation, "decode-wire-message");      assert.isNumber(payload.issueCount);      assert.isArray(payload.issueKinds);      assert.isNumber(payload.maximumPathDepth);      assert.equal("cause" in payload, false);      assert.equal("detail" in payload, false);      assert.notInclude(encodeUnknownJsonString(event), secret);    }),  );   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<CodexError.CodexAppServerError>();      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();      const termination = yield* Deferred.make<CodexError.CodexAppServerError>();      yield* CodexProtocol.makeCodexAppServerPatchedProtocol({        stdio,        onTermination: (error) => Deferred.succeed(termination, error).pipe(Effect.asVoid),      });       yield* Queue.end(input);       const error = yield* Deferred.await(termination);      assert.instanceOf(error, CodexError.CodexAppServerInputStreamEndedError);      assert.equal(error.message, "Codex App Server input stream ended.");      assert.equal("cause" in error, false);    }),  );});