baa-conductor


baa-conductor / packages / codex-app-server / src
im_wower  ·  2026-03-22

index.test.js

  1import assert from "node:assert/strict";
  2import test from "node:test";
  3
  4import {
  5  CodexAppServerClient,
  6  CodexAppServerEventStream,
  7  createCodexAppServerReadOnlySandboxPolicy,
  8  createCodexAppServerTextInput
  9} from "../dist/index.js";
 10
 11class FakeTransport {
 12  constructor(handlersByMethod) {
 13    this.handlersByMethod = handlersByMethod;
 14    this.requests = [];
 15    this.handlers = null;
 16    this.closed = false;
 17  }
 18
 19  async connect(handlers) {
 20    this.handlers = handlers;
 21  }
 22
 23  async send(message) {
 24    const request = JSON.parse(message);
 25    this.requests.push(request);
 26
 27    const handler = this.handlersByMethod[request.method];
 28
 29    if (typeof handler !== "function") {
 30      throw new Error(`Unexpected request method in test transport: ${request.method}`);
 31    }
 32
 33    const plan = await handler(request);
 34
 35    for (const notification of plan.notifications ?? []) {
 36      this.handlers.onMessage(JSON.stringify(notification));
 37    }
 38
 39    if (plan.error) {
 40      this.handlers.onMessage(
 41        JSON.stringify({
 42          id: request.id,
 43          error: plan.error
 44        })
 45      );
 46      return;
 47    }
 48
 49    this.handlers.onMessage(
 50      JSON.stringify({
 51        id: request.id,
 52        result: plan.result ?? {}
 53      })
 54    );
 55  }
 56
 57  async close() {
 58    if (this.closed) {
 59      return;
 60    }
 61
 62    this.closed = true;
 63    this.handlers?.onClose(new Error("closed by test"));
 64  }
 65}
 66
 67test("CodexAppServerClient maps app-server methods and notifications into a reusable adapter", async () => {
 68  const thread = {
 69    id: "thread-1",
 70    preview: "hello",
 71    ephemeral: true,
 72    modelProvider: "openai",
 73    createdAt: 1,
 74    updatedAt: 2,
 75    status: { type: "idle" },
 76    cwd: "/tmp/codexd-smoke",
 77    cliVersion: "0.116.0",
 78    source: { custom: "codexd-test" },
 79    name: "smoke",
 80    turns: []
 81  };
 82  const turn = {
 83    id: "turn-1",
 84    status: "inProgress",
 85    error: null
 86  };
 87  const completedTurn = {
 88    ...turn,
 89    status: "completed"
 90  };
 91  const session = {
 92    thread,
 93    model: "gpt-5.4",
 94    modelProvider: "openai",
 95    serviceTier: null,
 96    cwd: thread.cwd,
 97    approvalPolicy: "never",
 98    sandbox: createCodexAppServerReadOnlySandboxPolicy(),
 99    reasoningEffort: "medium"
100  };
101
102  const transport = new FakeTransport({
103    initialize: async () => ({
104      result: {
105        userAgent: "codex-cli 0.116.0",
106        platformFamily: "unix",
107        platformOs: "macos"
108      }
109    }),
110    "thread/start": async () => ({
111      notifications: [
112        {
113          method: "thread/started",
114          params: { thread }
115        }
116      ],
117      result: session
118    }),
119    "thread/resume": async () => ({
120      result: session
121    }),
122    "turn/start": async () => ({
123      notifications: [
124        {
125          method: "turn/started",
126          params: {
127            threadId: thread.id,
128            turn
129          }
130        },
131        {
132          method: "item/agentMessage/delta",
133          params: {
134            threadId: thread.id,
135            turnId: turn.id,
136            itemId: "item-1",
137            delta: "hel"
138          }
139        },
140        {
141          method: "item/agentMessage/delta",
142          params: {
143            threadId: thread.id,
144            turnId: turn.id,
145            itemId: "item-1",
146            delta: "lo"
147          }
148        },
149        {
150          method: "turn/completed",
151          params: {
152            threadId: thread.id,
153            turn: completedTurn
154          }
155        }
156      ],
157      result: { turn }
158    }),
159    "turn/steer": async () => ({
160      result: {
161        turnId: turn.id
162      }
163    }),
164    "turn/interrupt": async () => ({
165      result: {}
166    })
167  });
168
169  const client = new CodexAppServerClient({
170    clientInfo: {
171      name: "codexd-smoke",
172      title: "smoke",
173      version: "0.1.0"
174    },
175    transport
176  });
177  const receivedEvents = [];
178  const subscription = client.events.subscribe((event) => {
179    receivedEvents.push(event);
180  });
181
182  const initialize = await client.initialize();
183  const startedSession = await client.threadStart({
184    cwd: thread.cwd,
185    baseInstructions: "Be concise."
186  });
187  const resumedSession = await client.threadResume({
188    threadId: thread.id
189  });
190  const startedTurn = await client.turnStart({
191    threadId: thread.id,
192    input: [createCodexAppServerTextInput("Reply with hello.")]
193  });
194  const steeredTurn = await client.turnSteer({
195    threadId: thread.id,
196    expectedTurnId: turn.id,
197    input: [createCodexAppServerTextInput("Reply with hello again.")]
198  });
199
200  await client.turnInterrupt({
201    threadId: thread.id,
202    turnId: turn.id
203  });
204
205  subscription.unsubscribe();
206  await client.close();
207
208  assert.equal(initialize.userAgent, "codex-cli 0.116.0");
209  assert.equal(startedSession.thread.id, thread.id);
210  assert.equal(resumedSession.thread.id, thread.id);
211  assert.equal(startedTurn.turn.id, turn.id);
212  assert.equal(steeredTurn.turnId, turn.id);
213  assert.deepEqual(
214    transport.requests.map((request) => request.method),
215    [
216      "initialize",
217      "thread/start",
218      "thread/resume",
219      "turn/start",
220      "turn/steer",
221      "turn/interrupt"
222    ]
223  );
224  assert.deepEqual(
225    receivedEvents.map((event) => event.type),
226    ["thread.started", "turn.started", "turn.message.delta", "turn.message.delta", "turn.completed"]
227  );
228  assert.equal(receivedEvents[2].delta, "hel");
229  assert.equal(receivedEvents[3].delta, "lo");
230});
231
232test("CodexAppServerEventStream supports async iteration for downstream codexd consumers", async () => {
233  const stream = new CodexAppServerEventStream();
234
235  const iteratorTask = (async () => {
236    const collected = [];
237
238    for await (const event of stream) {
239      collected.push(event);
240
241      if (collected.length === 2) {
242        break;
243      }
244    }
245
246    return collected;
247  })();
248
249  stream.emit({
250    type: "turn.message.delta",
251    notificationMethod: "item/agentMessage/delta",
252    threadId: "thread-1",
253    turnId: "turn-1",
254    itemId: "item-1",
255    delta: "A"
256  });
257  stream.emit({
258    type: "turn.completed",
259    notificationMethod: "turn/completed",
260    threadId: "thread-1",
261    turn: {
262      id: "turn-1",
263      status: "completed",
264      error: null
265    }
266  });
267  stream.close();
268
269  const collected = await iteratorTask;
270
271  assert.deepEqual(
272    collected.map((event) => event.type),
273    ["turn.message.delta", "turn.completed"]
274  );
275});