807 lines · 29.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 Stdio from "effect/Stdio";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.fromJsonString(Schema.Unknown)); const encoder = new TextEncoder(); const encodeJsonl = (value: unknown) => encoder.encode(`${encodeUnknownJsonString(value)}\n`); const decodeJson = Schema.decodeEffect(Schema.fromJsonString(Schema.Unknown));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("routes a large notification fragmented across thousands of input chunks", () =>    Effect.gen(function* () {      const { stdio, input, output } = yield* makeInMemoryStdio();      const notifications: Array<CodexProtocol.CodexAppServerIncomingNotification> = [];      const transport = yield* CodexProtocol.makeCodexAppServerPatchedProtocol({        stdio,        onNotification: (notification) =>          Effect.sync(() => {            notifications.push(notification);          }),      });      const response = yield* transport.request("thread/read", {}).pipe(Effect.forkScoped);      yield* Queue.take(output);       const notification = {        method: "turn/diff/updated",        params: {          threadId: "thread-1",          turnId: "turn-1",          diff: "x".repeat(4 * 1024 * 1024),        },      };      const bytes = encoder.encode(        `${encodeUnknownJsonString(notification)}\n${encodeUnknownJsonString({ id: 1, result: { ok: true } })}\n`,      );      for (let offset = 0; offset < bytes.length; offset += 1024) {        yield* Queue.offer(input, bytes.subarray(offset, offset + 1024));      }       assert.deepEqual(yield* Fiber.join(response), { ok: true });      assert.deepEqual(notifications, [notification]);    }),  );   it.effect.each([1, 7, 1024])(    "preserves JSONL framing and UTF-8 across %i-byte input chunks",    (chunkSize) =>      Effect.gen(function* () {        const { stdio, input } = yield* makeInMemoryStdio();        const notifications: Array<CodexProtocol.CodexAppServerIncomingNotification> = [];        const rawLines: Array<unknown> = [];        const termination = yield* Deferred.make<CodexError.CodexAppServerError>();        yield* CodexProtocol.makeCodexAppServerPatchedProtocol({          stdio,          logIncoming: true,          logger: (event) =>            Effect.sync(() => {              if (event.stage === "raw") {                rawLines.push(event.payload);              }            }),          onNotification: (notification) =>            Effect.sync(() => {              notifications.push(notification);            }),          onTermination: (error) => Deferred.succeed(termination, error).pipe(Effect.asVoid),        });         const firstLine = '{"method":"x/first",\r"params":{"text":"hé🙂"}}';        const secondLine = '{"method":"x/second","params":{"value":2}}';        const finalLine = '{"method":"x/final","params":{"text":"最後"}}\r';        const bytes = encoder.encode(`\n \t\r\n${firstLine}\r\n\n${secondLine}\n${finalLine}`);        for (let offset = 0; offset < bytes.length; offset += chunkSize) {          yield* Queue.offer(input, bytes.subarray(offset, offset + chunkSize));        }        yield* Queue.end(input);         assert.instanceOf(          yield* Deferred.await(termination),          CodexError.CodexAppServerInputStreamEndedError,        );        assert.deepEqual(notifications, [          { method: "x/first", params: { text: "hé🙂" } },          { method: "x/second", params: { value: 2 } },          { method: "x/final", params: { text: "最後" } },        ]);        assert.deepEqual(rawLines, [firstLine, secondLine, finalLine]);      }),  );   it.effect("reports a malformed fragmented final line before input stream termination", () =>    Effect.gen(function* () {      const { stdio, input, output } = yield* makeInMemoryStdio();      const termination = yield* Deferred.make<CodexError.CodexAppServerError>();      const transport = yield* CodexProtocol.makeCodexAppServerPatchedProtocol({        stdio,        onTermination: (error) => Deferred.succeed(termination, error).pipe(Effect.asVoid),      });      const response = yield* transport.request("thread/read", {}).pipe(Effect.forkScoped);      yield* Queue.take(output);       yield* Queue.offer(input, encoder.encode('{"id":1,'));      yield* Queue.offer(input, encoder.encode('"result":'));      yield* Queue.end(input);       const error = yield* Deferred.await(termination);      assert.instanceOf(error, CodexError.CodexAppServerProtocolParseError);      assert.equal(error.operation, "decode-wire-message");      const responseError = yield* Fiber.join(response).pipe(        Effect.match({          onFailure: (failure) => failure,          onSuccess: () => assert.fail("Expected the malformed response to fail the request"),        }),      );      assert.strictEqual(responseError, error);    }),  );   it.effect("keeps only recent raw notifications after their callbacks run", () =>    Effect.gen(function* () {      const { stdio, input } = yield* makeInMemoryStdio();      const handled = yield* Deferred.make<void>();      let handledCount = 0;      const transport = yield* CodexProtocol.makeCodexAppServerPatchedProtocol({        stdio,        onNotification: () =>          Effect.sync(() => ++handledCount).pipe(            Effect.flatMap((count) =>              count === 64 ? Deferred.succeed(handled, undefined).pipe(Effect.asVoid) : Effect.void,            ),          ),      });       const messages = Array.from({ length: 64 }, (_, index) =>        encodeUnknownJsonString({          method: "item/agentMessage/delta",          params: { index },        }),      );      yield* Queue.offer(input, encoder.encode(`${messages.join("\n")}\n`));      yield* Deferred.await(handled);       const retained = yield* transport.incomingNotifications.pipe(        Stream.take(32),        Stream.runCollect,      );       assert.equal(handledCount, 64);      assert.equal(retained.length, 32);      assert.deepEqual(retained[0]?.params, { index: 32 });      assert.deepEqual(retained[31]?.params, { index: 63 });    }),  );   it.effect("keeps processing protocol messages while an approval is pending", () =>    Effect.gen(function* () {      const { stdio, input, output } = yield* makeInMemoryStdio();      const approvalStarted = yield* Deferred.make<void>();      const approvalDecision = yield* Deferred.make<{ readonly decision: string }>();      const notificationReceived = yield* Deferred.make<void>();      const transport = yield* CodexProtocol.makeCodexAppServerPatchedProtocol({        stdio,        onRequest: () =>          Deferred.succeed(approvalStarted, undefined).pipe(            Effect.andThen(Deferred.await(approvalDecision)),          ),        onNotification: () => Deferred.succeed(notificationReceived, undefined).pipe(Effect.asVoid),      });       const pendingRequest = yield* transport.request("thread/read", {}).pipe(Effect.forkScoped);      yield* Queue.take(output);      yield* Queue.offer(        input,        encoder.encode(          `${[            encodeUnknownJsonString({ id: 7, method: "item/tool/requestUserInput", params: {} }),            encodeUnknownJsonString({ method: "item/agentMessage/delta", params: { delta: "ok" } }),            encodeUnknownJsonString({ id: 1, result: { threadId: "thread-1" } }),          ].join("\n")}\n`,        ),      );       yield* Deferred.await(approvalStarted);      yield* Deferred.await(notificationReceived);      assert.deepEqual(yield* Fiber.join(pendingRequest), { threadId: "thread-1" });       yield* Deferred.succeed(approvalDecision, { decision: "accept" });      assert.deepEqual(yield* decodeJson(yield* Queue.take(output)), {        id: 7,        result: { decision: "accept" },      });    }),  );   it.effect("rejects incoming requests after the active handler limit is reached", () =>    Effect.gen(function* () {      const { stdio, input, output } = yield* makeInMemoryStdio();      const handlersStarted = yield* Deferred.make<void>();      const releaseHandlers = yield* Deferred.make<void>();      let activeHandlers = 0;      yield* CodexProtocol.makeCodexAppServerPatchedProtocol({        stdio,        onRequest: () =>          Effect.sync(() => ++activeHandlers).pipe(            Effect.flatMap((count) =>              count === 32                ? Deferred.succeed(handlersStarted, undefined).pipe(Effect.asVoid)                : Effect.void,            ),            Effect.andThen(Deferred.await(releaseHandlers)),            Effect.as({ decision: "accept" }),          ),      });       const requests = Array.from({ length: 33 }, (_, index) =>        encodeUnknownJsonString({          id: index + 1,

Lines 1–500 of 807.