Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions apps/server/src/provider/Layers/CodexAdapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,41 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => {
}),
);

it.effect("maps raw codex/event/plan_delta to canonical turn.proposed.delta", () =>
Effect.gen(function* () {
const adapter = yield* CodexAdapter;
const firstEventFiber = yield* Stream.runHead(adapter.streamEvents).pipe(Effect.forkChild);

lifecycleManager.emit("event", {
id: asEventId("evt-raw-plan-delta"),
kind: "notification",
provider: "codex",
createdAt: new Date().toISOString(),
method: "codex/event/plan_delta",
threadId: asThreadId("thread-1"),
turnId: asTurnId("turn-1"),
payload: {
msg: {
turn_id: "turn-1",
delta: "- Step 1: read the file",
},
},
} satisfies ProviderEvent);

const firstEvent = yield* Fiber.join(firstEventFiber);

assert.equal(firstEvent._tag, "Some");
if (firstEvent._tag !== "Some") {
return;
}
assert.equal(firstEvent.value.type, "turn.proposed.delta");
if (firstEvent.value.type !== "turn.proposed.delta") {
return;
}
assert.equal(firstEvent.value.payload.delta, "- Step 1: read the file");
}),
);

it.effect("maps session/closed lifecycle events to canonical session.exited runtime events", () =>
Effect.gen(function* () {
const adapter = yield* CodexAdapter;
Expand Down
116 changes: 116 additions & 0 deletions apps/server/src/provider/Layers/codexEventMapping.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/**
* Unit tests for codexEventMapping — calls mapToRuntimeEvents directly
* without the full adapter/stream infrastructure.
*/
import { describe, it, expect } from "bun:test";
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 New test file imports from bun:test instead of @effect/vitest, violating AGENTS.md and project conventions

The new test file codexEventMapping.test.ts imports describe, it, and expect from bun:test, while every other test file in apps/server/src/ uses @effect/vitest (see e.g. apps/server/src/provider/Layers/CodexAdapter.test.ts:15, apps/server/src/provider/Layers/ProviderHealth.test.ts:2, and all ~18 other test files in the server). AGENTS.md explicitly states: "NEVER run bun test. Always use bun run test (runs Vitest)." The server's test script is "test": "vitest run" (apps/server/package.json). When vitest run processes this file, tests registered via bun:test's describe/it won't integrate with Vitest's runner — they'll either fail to import or silently not execute, meaning these tests provide no verification.

Prompt for agents
In apps/server/src/provider/Layers/codexEventMapping.test.ts, replace the import on line 5 from bun:test with @effect/vitest, matching the convention used by every other test file in the server app. Change:

import { describe, it, expect } from "bun:test";

to:

import { describe, it, assert } from "@effect/vitest";

Then update all expect() calls to use Node's assert module (assert.equal, assert.deepStrictEqual, etc.) or vitest's assert, matching the patterns in CodexAdapter.test.ts and other sibling test files. For example, expect(events).toHaveLength(1) should become assert.equal(events.length, 1), and expect(events[0]!.type).toBe("turn.proposed.delta") should become assert.equal(events[0]?.type, "turn.proposed.delta").
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

import { mapToRuntimeEvents } from "./codexEventMapping.ts";
import {
EventId,
type ProviderEvent,
ProviderItemId,
ThreadId,
TurnId,
} from "@t3tools/contracts";

const threadId = ThreadId.makeUnsafe("thread-1");

function makeEvent(overrides: Partial<ProviderEvent>): ProviderEvent {
return {
id: EventId.makeUnsafe("evt-1"),
kind: "notification",
provider: "codex",
createdAt: new Date().toISOString(),
threadId,
...overrides,
} as ProviderEvent;
}

describe("codexEventMapping — codex/event/plan_delta", () => {
it("maps plan_delta with msg.delta to turn.proposed.delta", () => {
const events = mapToRuntimeEvents(
makeEvent({
method: "codex/event/plan_delta",
turnId: TurnId.makeUnsafe("turn-1"),
payload: {
msg: {
turn_id: "turn-1",
delta: "- Step 1: read the file",
},
},
}),
threadId,
);

expect(events).toHaveLength(1);
expect(events[0]!.type).toBe("turn.proposed.delta");
if (events[0]!.type === "turn.proposed.delta") {
expect(events[0]!.payload.delta).toBe("- Step 1: read the file");
}
});

it("maps plan_delta with msg.text fallback", () => {
const events = mapToRuntimeEvents(
makeEvent({
method: "codex/event/plan_delta",
payload: {
msg: {
text: "plan text via text field",
},
},
}),
threadId,
);

expect(events).toHaveLength(1);
if (events[0]!.type === "turn.proposed.delta") {
expect(events[0]!.payload.delta).toBe("plan text via text field");
}
});

it("maps plan_delta with msg.content.text fallback", () => {
const events = mapToRuntimeEvents(
makeEvent({
method: "codex/event/plan_delta",
payload: {
msg: {
content: { text: "nested content text" },
},
},
}),
threadId,
);

expect(events).toHaveLength(1);
if (events[0]!.type === "turn.proposed.delta") {
expect(events[0]!.payload.delta).toBe("nested content text");
}
});

it("returns empty for plan_delta with no extractable delta", () => {
const events = mapToRuntimeEvents(
makeEvent({
method: "codex/event/plan_delta",
payload: {
msg: {},
},
}),
threadId,
);

expect(events).toHaveLength(0);
});

it("returns empty for plan_delta with empty delta string", () => {
const events = mapToRuntimeEvents(
makeEvent({
method: "codex/event/plan_delta",
payload: {
msg: { delta: "" },
},
}),
threadId,
);

expect(events).toHaveLength(0);
});
});
16 changes: 16 additions & 0 deletions apps/server/src/provider/Layers/codexEventMapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1362,6 +1362,22 @@ export function mapToRuntimeEvents(
];
}

if (event.method === "codex/event/plan_delta") {
const msg = codexEventMessage(payload);
const delta =
asString(msg?.delta) ?? asString(msg?.text) ?? asString(asObject(msg?.content)?.text);
if (!delta || delta.length === 0) {
return [];
}
return [
{
...codexEventBase(event, canonicalThreadId),
type: "turn.proposed.delta" as const,
payload: { delta },
},
];
}

// Log unmapped events to aid debugging — skip known-noisy non-critical ones.
if (event.method && !QUIET_UNMAPPED_EVENTS.has(event.method)) {
console.debug(`[codexEventMapping] unmapped event: ${event.method}`, event.payload ?? "");
Expand Down
Loading