import assert from "node:assert/strict";import fs from "node:fs";import os from "node:os";import path from "node:path";import { ApprovalRequestId, EventId, ProviderItemId, type ProviderApprovalDecision, type ProviderEvent, type ProviderSession, type ProviderTurnStartResult, type ProviderUserInputAnswers, ThreadId, TurnId,} from "@t3tools/contracts";import { createModelSelection } from "@t3tools/shared/model";import * as NodeServices from "@effect/platform-node/NodeServices";import { it, vi } from "@effect/vitest"; import { Effect, Exit, Fiber, Layer, Option, Queue, Scope, Stream } from "effect";import * as CodexErrors from "effect-codex-app-server/errors"; import { ServerConfig } from "../../config.ts";import { ServerSettingsService } from "../../serverSettings.ts";import { ProviderAdapterValidationError } from "../Errors.ts";import { CodexAdapter } from "../Services/CodexAdapter.ts";import { ProviderSessionDirectory } from "../Services/ProviderSessionDirectory.ts";import { type CodexSessionRuntimeOptions, type CodexSessionRuntimeSendTurnInput, type CodexSessionRuntimeShape, type CodexThreadSnapshot,} from "./CodexSessionRuntime.ts";import { makeCodexAdapterLive } from "./CodexAdapter.ts"; const asThreadId = (value: string): ThreadId => ThreadId.make(value);const asTurnId = (value: string): TurnId => TurnId.make(value);const asEventId = (value: string): EventId => EventId.make(value);const asItemId = (value: string): ProviderItemId => ProviderItemId.make(value); class FakeCodexRuntime implements CodexSessionRuntimeShape { private readonly eventQueue = Effect.runSync(Queue.unbounded<ProviderEvent>()); private readonly now = new Date().toISOString(); public readonly startImpl = vi.fn(() => Promise.resolve({ provider: "codex" as const, status: "ready" as const, runtimeMode: this.options.runtimeMode, threadId: this.options.threadId, cwd: this.options.cwd, ...(this.options.model ? { model: this.options.model } : {}), createdAt: this.now, updatedAt: this.now, } satisfies ProviderSession), ); public readonly sendTurnImpl = vi.fn( (_input: CodexSessionRuntimeSendTurnInput): Promise<ProviderTurnStartResult> => Promise.resolve({ threadId: this.options.threadId, turnId: asTurnId("turn-1"), }), ); public readonly interruptTurnImpl = vi.fn( (_turnId?: TurnId): Promise<void> => Promise.resolve(undefined), ); public readonly readThreadImpl = vi.fn( (): Promise<CodexThreadSnapshot> => Promise.resolve({ threadId: "provider-thread-1", turns: [], }), ); public readonly rollbackThreadImpl = vi.fn( (_numTurns: number): Promise<CodexThreadSnapshot> => Promise.resolve({ threadId: "provider-thread-1", turns: [], }), ); public readonly respondToRequestImpl = vi.fn( (_requestId: ApprovalRequestId, _decision: ProviderApprovalDecision): Promise<void> => Promise.resolve(undefined), ); public readonly respondToUserInputImpl = vi.fn( (_requestId: ApprovalRequestId, _answers: ProviderUserInputAnswers): Promise<void> => Promise.resolve(undefined), ); public readonly closeImpl = vi.fn(() => Promise.resolve(undefined)); readonly options: CodexSessionRuntimeOptions; constructor(options: CodexSessionRuntimeOptions) { this.options = options; } start() { return Effect.promise(() => this.startImpl()); } getSession = Effect.promise(() => this.startImpl()); sendTurn(input: CodexSessionRuntimeSendTurnInput) { return Effect.promise(() => this.sendTurnImpl(input)); } interruptTurn(turnId?: TurnId) { return Effect.promise(() => this.interruptTurnImpl(turnId)); } readThread = Effect.promise(() => this.readThreadImpl()); rollbackThread(numTurns: number) { return Effect.promise(() => this.rollbackThreadImpl(numTurns)); } respondToRequest(requestId: ApprovalRequestId, decision: ProviderApprovalDecision) { return Effect.promise(() => this.respondToRequestImpl(requestId, decision)); } respondToUserInput(requestId: ApprovalRequestId, answers: ProviderUserInputAnswers) { return Effect.promise(() => this.respondToUserInputImpl(requestId, answers)); } get events() { return Stream.fromQueue(this.eventQueue); } close = Effect.promise(() => this.closeImpl()); emit(event: ProviderEvent) { return Queue.offer(this.eventQueue, event).pipe(Effect.asVoid); }} function makeRuntimeFactory() { const runtimes: Array<FakeCodexRuntime> = []; const factory = vi.fn((options: CodexSessionRuntimeOptions) => { const runtime = new FakeCodexRuntime(options); runtimes.push(runtime); return Effect.succeed(runtime); }); return { factory, get lastRuntime(): FakeCodexRuntime | undefined { return runtimes.at(-1); }, };} function makeScopedRuntimeFactory(options?: { readonly failConstruction?: boolean }) { const runtimes: Array<FakeCodexRuntime> = []; const releasedThreadIds: Array<ThreadId> = []; const factory = vi.fn((runtimeOptions: CodexSessionRuntimeOptions) => Effect.gen(function* () { yield* Scope.Scope; yield* Effect.addFinalizer(() => Effect.sync(() => { releasedThreadIds.push(runtimeOptions.threadId); }), ); if (options?.failConstruction) { return yield* new CodexErrors.CodexAppServerSpawnError({ command: `${runtimeOptions.binaryPath} app-server`, cause: new Error("runtime construction failed"), }); } const runtime = new FakeCodexRuntime(runtimeOptions); runtimes.push(runtime); return runtime; }), ); return { factory, releasedThreadIds, get lastRuntime(): FakeCodexRuntime | undefined { return runtimes.at(-1); }, };} const providerSessionDirectoryTestLayer = Layer.succeed(ProviderSessionDirectory, { upsert: () => Effect.void, getProvider: () => Effect.die(new Error("ProviderSessionDirectory.getProvider is not used in test")), getBinding: () => Effect.succeed(Option.none()), listThreadIds: () => Effect.succeed([]), listBindings: () => Effect.succeed([]),}); const validationRuntimeFactory = makeRuntimeFactory();const validationLayer = it.layer( makeCodexAdapterLive({ makeRuntime: validationRuntimeFactory.factory }).pipe( Layer.provideMerge(ServerConfig.layerTest(process.cwd(), process.cwd())), Layer.provideMerge(ServerSettingsService.layerTest()), Layer.provideMerge(providerSessionDirectoryTestLayer), Layer.provideMerge(NodeServices.layer), ),); validationLayer("CodexAdapterLive validation", (it) => { it.effect("returns validation error for non-codex provider on startSession", () => Effect.gen(function* () { const adapter = yield* CodexAdapter; const result = yield* adapter .startSession({ provider: "claudeAgent", threadId: asThreadId("thread-1"), runtimeMode: "full-access", }) .pipe(Effect.result); assert.equal(result._tag, "Failure"); assert.deepStrictEqual( result.failure, new ProviderAdapterValidationError({ provider: "codex", operation: "startSession", issue: "Expected provider 'codex' but received 'claudeAgent'.", }), ); assert.equal(validationRuntimeFactory.factory.mock.calls.length, 0); }), ); it.effect("maps codex model options before starting a session", () => Effect.gen(function* () { validationRuntimeFactory.factory.mockClear(); const adapter = yield* CodexAdapter; yield* adapter.startSession({ provider: "codex", threadId: asThreadId("thread-1"), modelSelection: createModelSelection("codex", "gpt-5.3-codex", [ { id: "fastMode", value: true }, ]), runtimeMode: "full-access", }); assert.deepStrictEqual(validationRuntimeFactory.factory.mock.calls[0]?.[0], { binaryPath: "codex", cwd: process.cwd(), model: "gpt-5.3-codex", serviceTier: "fast", threadId: asThreadId("thread-1"), runtimeMode: "full-access", }); }), );}); const sessionRuntimeFactory = makeRuntimeFactory();const sessionErrorLayer = it.layer( makeCodexAdapterLive({ makeRuntime: sessionRuntimeFactory.factory }).pipe( Layer.provideMerge(ServerConfig.layerTest(process.cwd(), process.cwd())), Layer.provideMerge(ServerSettingsService.layerTest()), Layer.provideMerge(providerSessionDirectoryTestLayer), Layer.provideMerge(NodeServices.layer), ),); sessionErrorLayer("CodexAdapterLive session errors", (it) => { it.effect("maps missing adapter sessions to ProviderAdapterSessionNotFoundError", () => Effect.gen(function* () { const adapter = yield* CodexAdapter; const result = yield* adapter .sendTurn({ threadId: asThreadId("sess-missing"), input: "hello", attachments: [], }) .pipe(Effect.result); assert.equal(result._tag, "Failure"); assert.equal(result.failure._tag, "ProviderAdapterSessionNotFoundError"); assert.equal(result.failure.provider, "codex"); assert.equal(result.failure.threadId, "sess-missing"); }), ); it.effect("maps codex model options before sending a turn", () => Effect.gen(function* () { const adapter = yield* CodexAdapter; yield* adapter.startSession({ provider: "codex", threadId: asThreadId("sess-missing"), runtimeMode: "full-access", }); const runtime = sessionRuntimeFactory.lastRuntime; assert.ok(runtime); runtime.sendTurnImpl.mockClear(); yield* Effect.ignore( adapter.sendTurn({ threadId: asThreadId("sess-missing"), input: "hello", modelSelection: createModelSelection("codex", "gpt-5.3-codex", [ { id: "reasoningEffort", value: "high" }, { id: "fastMode", value: true }, ]), attachments: [], }), ); assert.deepStrictEqual(runtime.sendTurnImpl.mock.calls[0]?.[0], { input: "hello", model: "gpt-5.3-codex", effort: "high", serviceTier: "fast", }); }), );}); const lifecycleRuntimeFactory = makeRuntimeFactory();const lifecycleLayer = it.layer( makeCodexAdapterLive({ makeRuntime: lifecycleRuntimeFactory.factory }).pipe( Layer.provideMerge(ServerConfig.layerTest(process.cwd(), process.cwd())), Layer.provideMerge(ServerSettingsService.layerTest()), Layer.provideMerge(providerSessionDirectoryTestLayer), Layer.provideMerge(NodeServices.layer), ),); function startLifecycleRuntime() { return Effect.gen(function* () { const adapter = yield* CodexAdapter; yield* adapter.startSession({ provider: "codex", threadId: asThreadId("thread-1"), runtimeMode: "full-access", }); const runtime = lifecycleRuntimeFactory.lastRuntime; assert.ok(runtime); return { adapter, runtime }; });} lifecycleLayer("CodexAdapterLive lifecycle", (it) => { it.effect("maps completed agent message items to canonical item.completed events", () => Effect.gen(function* () { const { adapter, runtime } = yield* startLifecycleRuntime(); const firstEventFiber = yield* Stream.runHead(adapter.streamEvents).pipe(Effect.forkChild); const event: ProviderEvent = { id: asEventId("evt-msg-complete"), kind: "notification", provider: "codex", createdAt: new Date().toISOString(), method: "item/completed", threadId: asThreadId("thread-1"), turnId: asTurnId("turn-1"), itemId: asItemId("msg_1"), payload: { threadId: "thread-1", turnId: "turn-1", item: { type: "agentMessage", id: "msg_1", text: "done", }, }, }; yield* runtime.emit(event); const firstEvent = yield* Fiber.join(firstEventFiber); assert.equal(firstEvent._tag, "Some"); if (firstEvent._tag !== "Some") { return; } assert.equal(firstEvent.value.type, "item.completed"); if (firstEvent.value.type !== "item.completed") { return; } assert.equal(firstEvent.value.itemId, "msg_1"); assert.equal(firstEvent.value.turnId, "turn-1"); assert.equal(firstEvent.value.payload.itemType, "assistant_message"); }), ); it.effect("maps completed plan items to canonical proposed-plan completion events", () => Effect.gen(function* () { const { adapter, runtime } = yield* startLifecycleRuntime(); const firstEventFiber = yield* Stream.runHead(adapter.streamEvents).pipe(Effect.forkChild); const event: ProviderEvent = { id: asEventId("evt-plan-complete"), kind: "notification", provider: "codex", createdAt: new Date().toISOString(), method: "item/completed", threadId: asThreadId("thread-1"), turnId: asTurnId("turn-1"), itemId: asItemId("plan_1"), payload: { threadId: "thread-1", turnId: "turn-1", item: { type: "plan", id: "plan_1", text: "## Final plan\n\n- one\n- two", }, }, }; yield* runtime.emit(event); const firstEvent = yield* Fiber.join(firstEventFiber); assert.equal(firstEvent._tag, "Some"); if (firstEvent._tag !== "Some") { return; } assert.equal(firstEvent.value.type, "turn.proposed.completed"); if (firstEvent.value.type !== "turn.proposed.completed") { return; } assert.equal(firstEvent.value.turnId, "turn-1"); assert.equal(firstEvent.value.payload.planMarkdown, "## Final plan\n\n- one\n- two"); }), ); it.effect("maps plan deltas to canonical proposed-plan delta events", () => Effect.gen(function* () { const { adapter, runtime } = yield* startLifecycleRuntime(); const firstEventFiber = yield* Stream.runHead(adapter.streamEvents).pipe(Effect.forkChild); yield* runtime.emit({ id: asEventId("evt-plan-delta"), kind: "notification", provider: "codex", createdAt: new Date().toISOString(), method: "item/plan/delta", threadId: asThreadId("thread-1"), turnId: asTurnId("turn-1"), itemId: asItemId("plan_1"), payload: { threadId: "thread-1", turnId: "turn-1", itemId: "plan_1", delta: "## Final plan", }, } 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.turnId, "turn-1"); assert.equal(firstEvent.value.payload.delta, "## Final plan"); }), ); it.effect("maps session/closed lifecycle events to canonical session.exited runtime events", () => Effect.gen(function* () { const { adapter, runtime } = yield* startLifecycleRuntime(); const firstEventFiber = yield* Stream.runHead(adapter.streamEvents).pipe(Effect.forkChild); const event: ProviderEvent = { id: asEventId("evt-session-closed"), kind: "session", provider: "codex", threadId: asThreadId("thread-1"), createdAt: new Date().toISOString(), method: "session/closed", message: "Session stopped", }; yield* runtime.emit(event); const firstEvent = yield* Fiber.join(firstEventFiber); assert.equal(firstEvent._tag, "Some"); if (firstEvent._tag !== "Some") { return; } assert.equal(firstEvent.value.type, "session.exited"); if (firstEvent.value.type !== "session.exited") { return; } assert.equal(firstEvent.value.threadId, "thread-1"); assert.equal(firstEvent.value.payload.reason, "Session stopped"); }),Lines 1–500 of 1024. Load more
import assert from "node:assert/strict"; import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { ApprovalRequestId, EventId, ProviderItemId, type ProviderApprovalDecision, type ProviderEvent, type ProviderSession, type ProviderTurnStartResult, type ProviderUserInputAnswers, ThreadId, TurnId, } from "@t3tools/contracts"; import { createModelSelection } from "@t3tools/shared/model"; import * as NodeServices from "@effect/platform-node/NodeServices"; import { it, vi } from "@effect/vitest"; import { Effect, Exit, Fiber, Layer, Option, Queue, Scope, Stream } from "effect"; import * as CodexErrors from "effect-codex-app-server/errors"; import { ServerConfig } from "../../config.ts"; import { ServerSettingsService } from "../../serverSettings.ts"; import { ProviderAdapterValidationError } from "../Errors.ts"; import { CodexAdapter } from "../Services/CodexAdapter.ts"; import { ProviderSessionDirectory } from "../Services/ProviderSessionDirectory.ts"; import { type CodexSessionRuntimeOptions, type CodexSessionRuntimeSendTurnInput, type CodexSessionRuntimeShape, type CodexThreadSnapshot, } from "./CodexSessionRuntime.ts"; import { makeCodexAdapterLive } from "./CodexAdapter.ts"; const asThreadId = (value: string): ThreadId => ThreadId.make(value); const asTurnId = (value: string): TurnId => TurnId.make(value); const asEventId = (value: string): EventId => EventId.make(value); const asItemId = (value: string): ProviderItemId => ProviderItemId.make(value); class FakeCodexRuntime implements CodexSessionRuntimeShape { private readonly eventQueue = Effect.runSync(Queue.unbounded<ProviderEvent>()); private readonly now = new Date().toISOString(); public readonly startImpl = vi.fn(() => Promise.resolve({ provider: "codex" as const, status: "ready" as const, runtimeMode: this.options.runtimeMode, threadId: this.options.threadId, cwd: this.options.cwd, ...(this.options.model ? { model: this.options.model } : {}), createdAt: this.now, updatedAt: this.now, } satisfies ProviderSession), ); public readonly sendTurnImpl = vi.fn( (_input: CodexSessionRuntimeSendTurnInput): Promise<ProviderTurnStartResult> => Promise.resolve({ threadId: this.options.threadId, turnId: asTurnId("turn-1"), }), ); public readonly interruptTurnImpl = vi.fn( (_turnId?: TurnId): Promise<void> => Promise.resolve(undefined), ); public readonly readThreadImpl = vi.fn( (): Promise<CodexThreadSnapshot> => Promise.resolve({ threadId: "provider-thread-1", turns: [], }), ); public readonly rollbackThreadImpl = vi.fn( (_numTurns: number): Promise<CodexThreadSnapshot> => Promise.resolve({ threadId: "provider-thread-1", turns: [], }), ); public readonly respondToRequestImpl = vi.fn( (_requestId: ApprovalRequestId, _decision: ProviderApprovalDecision): Promise<void> => Promise.resolve(undefined), ); public readonly respondToUserInputImpl = vi.fn( (_requestId: ApprovalRequestId, _answers: ProviderUserInputAnswers): Promise<void> => Promise.resolve(undefined), ); public readonly closeImpl = vi.fn(() => Promise.resolve(undefined)); readonly options: CodexSessionRuntimeOptions; constructor(options: CodexSessionRuntimeOptions) { this.options = options; } start() { return Effect.promise(() => this.startImpl()); } getSession = Effect.promise(() => this.startImpl()); sendTurn(input: CodexSessionRuntimeSendTurnInput) { return Effect.promise(() => this.sendTurnImpl(input)); } interruptTurn(turnId?: TurnId) { return Effect.promise(() => this.interruptTurnImpl(turnId)); } readThread = Effect.promise(() => this.readThreadImpl()); rollbackThread(numTurns: number) { return Effect.promise(() => this.rollbackThreadImpl(numTurns)); } respondToRequest(requestId: ApprovalRequestId, decision: ProviderApprovalDecision) { return Effect.promise(() => this.respondToRequestImpl(requestId, decision)); } respondToUserInput(requestId: ApprovalRequestId, answers: ProviderUserInputAnswers) { return Effect.promise(() => this.respondToUserInputImpl(requestId, answers)); } get events() { return Stream.fromQueue(this.eventQueue); } close = Effect.promise(() => this.closeImpl()); emit(event: ProviderEvent) { return Queue.offer(this.eventQueue, event).pipe(Effect.asVoid); } } function makeRuntimeFactory() { const runtimes: Array<FakeCodexRuntime> = []; const factory = vi.fn((options: CodexSessionRuntimeOptions) => { const runtime = new FakeCodexRuntime(options); runtimes.push(runtime); return Effect.succeed(runtime); }); return { factory, get lastRuntime(): FakeCodexRuntime | undefined { return runtimes.at(-1); }, }; } function makeScopedRuntimeFactory(options?: { readonly failConstruction?: boolean }) { const runtimes: Array<FakeCodexRuntime> = []; const releasedThreadIds: Array<ThreadId> = []; const factory = vi.fn((runtimeOptions: CodexSessionRuntimeOptions) => Effect.gen(function* () { yield* Scope.Scope; yield* Effect.addFinalizer(() => Effect.sync(() => { releasedThreadIds.push(runtimeOptions.threadId); }), ); if (options?.failConstruction) { return yield* new CodexErrors.CodexAppServerSpawnError({ command: `${runtimeOptions.binaryPath} app-server`, cause: new Error("runtime construction failed"), }); } const runtime = new FakeCodexRuntime(runtimeOptions); runtimes.push(runtime); return runtime; }), ); return { factory, releasedThreadIds, get lastRuntime(): FakeCodexRuntime | undefined { return runtimes.at(-1); }, }; } const providerSessionDirectoryTestLayer = Layer.succeed(ProviderSessionDirectory, { upsert: () => Effect.void, getProvider: () => Effect.die(new Error("ProviderSessionDirectory.getProvider is not used in test")), getBinding: () => Effect.succeed(Option.none()), listThreadIds: () => Effect.succeed([]), listBindings: () => Effect.succeed([]), }); const validationRuntimeFactory = makeRuntimeFactory(); const validationLayer = it.layer( makeCodexAdapterLive({ makeRuntime: validationRuntimeFactory.factory }).pipe( Layer.provideMerge(ServerConfig.layerTest(process.cwd(), process.cwd())), Layer.provideMerge(ServerSettingsService.layerTest()), Layer.provideMerge(providerSessionDirectoryTestLayer), Layer.provideMerge(NodeServices.layer), ), ); validationLayer("CodexAdapterLive validation", (it) => { it.effect("returns validation error for non-codex provider on startSession", () => Effect.gen(function* () { const adapter = yield* CodexAdapter; const result = yield* adapter .startSession({ provider: "claudeAgent", threadId: asThreadId("thread-1"), runtimeMode: "full-access", }) .pipe(Effect.result); assert.equal(result._tag, "Failure"); assert.deepStrictEqual( result.failure, new ProviderAdapterValidationError({ provider: "codex", operation: "startSession", issue: "Expected provider 'codex' but received 'claudeAgent'.", }), ); assert.equal(validationRuntimeFactory.factory.mock.calls.length, 0); }), ); it.effect("maps codex model options before starting a session", () => Effect.gen(function* () { validationRuntimeFactory.factory.mockClear(); const adapter = yield* CodexAdapter; yield* adapter.startSession({ provider: "codex", threadId: asThreadId("thread-1"), modelSelection: createModelSelection("codex", "gpt-5.3-codex", [ { id: "fastMode", value: true }, ]), runtimeMode: "full-access", }); assert.deepStrictEqual(validationRuntimeFactory.factory.mock.calls[0]?.[0], { binaryPath: "codex", cwd: process.cwd(), model: "gpt-5.3-codex", serviceTier: "fast", threadId: asThreadId("thread-1"), runtimeMode: "full-access", }); }), ); }); const sessionRuntimeFactory = makeRuntimeFactory(); const sessionErrorLayer = it.layer( makeCodexAdapterLive({ makeRuntime: sessionRuntimeFactory.factory }).pipe( Layer.provideMerge(ServerConfig.layerTest(process.cwd(), process.cwd())), Layer.provideMerge(ServerSettingsService.layerTest()), Layer.provideMerge(providerSessionDirectoryTestLayer), Layer.provideMerge(NodeServices.layer), ), ); sessionErrorLayer("CodexAdapterLive session errors", (it) => { it.effect("maps missing adapter sessions to ProviderAdapterSessionNotFoundError", () => Effect.gen(function* () { const adapter = yield* CodexAdapter; const result = yield* adapter .sendTurn({ threadId: asThreadId("sess-missing"), input: "hello", attachments: [], }) .pipe(Effect.result); assert.equal(result._tag, "Failure"); assert.equal(result.failure._tag, "ProviderAdapterSessionNotFoundError"); assert.equal(result.failure.provider, "codex"); assert.equal(result.failure.threadId, "sess-missing"); }), ); it.effect("maps codex model options before sending a turn", () => Effect.gen(function* () { const adapter = yield* CodexAdapter; yield* adapter.startSession({ provider: "codex", threadId: asThreadId("sess-missing"), runtimeMode: "full-access", }); const runtime = sessionRuntimeFactory.lastRuntime; assert.ok(runtime); runtime.sendTurnImpl.mockClear(); yield* Effect.ignore( adapter.sendTurn({ threadId: asThreadId("sess-missing"), input: "hello", modelSelection: createModelSelection("codex", "gpt-5.3-codex", [ { id: "reasoningEffort", value: "high" }, { id: "fastMode", value: true }, ]), attachments: [], }), ); assert.deepStrictEqual(runtime.sendTurnImpl.mock.calls[0]?.[0], { input: "hello", model: "gpt-5.3-codex", effort: "high", serviceTier: "fast", }); }), ); }); const lifecycleRuntimeFactory = makeRuntimeFactory(); const lifecycleLayer = it.layer( makeCodexAdapterLive({ makeRuntime: lifecycleRuntimeFactory.factory }).pipe( Layer.provideMerge(ServerConfig.layerTest(process.cwd(), process.cwd())), Layer.provideMerge(ServerSettingsService.layerTest()), Layer.provideMerge(providerSessionDirectoryTestLayer), Layer.provideMerge(NodeServices.layer), ), ); function startLifecycleRuntime() { return Effect.gen(function* () { const adapter = yield* CodexAdapter; yield* adapter.startSession({ provider: "codex", threadId: asThreadId("thread-1"), runtimeMode: "full-access", }); const runtime = lifecycleRuntimeFactory.lastRuntime; assert.ok(runtime); return { adapter, runtime }; }); } lifecycleLayer("CodexAdapterLive lifecycle", (it) => { it.effect("maps completed agent message items to canonical item.completed events", () => Effect.gen(function* () { const { adapter, runtime } = yield* startLifecycleRuntime(); const firstEventFiber = yield* Stream.runHead(adapter.streamEvents).pipe(Effect.forkChild); const event: ProviderEvent = { id: asEventId("evt-msg-complete"), kind: "notification", provider: "codex", createdAt: new Date().toISOString(), method: "item/completed", threadId: asThreadId("thread-1"), turnId: asTurnId("turn-1"), itemId: asItemId("msg_1"), payload: { threadId: "thread-1", turnId: "turn-1", item: { type: "agentMessage", id: "msg_1", text: "done", }, }, }; yield* runtime.emit(event); const firstEvent = yield* Fiber.join(firstEventFiber); assert.equal(firstEvent._tag, "Some"); if (firstEvent._tag !== "Some") { return; } assert.equal(firstEvent.value.type, "item.completed"); if (firstEvent.value.type !== "item.completed") { return; } assert.equal(firstEvent.value.itemId, "msg_1"); assert.equal(firstEvent.value.turnId, "turn-1"); assert.equal(firstEvent.value.payload.itemType, "assistant_message"); }), ); it.effect("maps completed plan items to canonical proposed-plan completion events", () => Effect.gen(function* () { const { adapter, runtime } = yield* startLifecycleRuntime(); const firstEventFiber = yield* Stream.runHead(adapter.streamEvents).pipe(Effect.forkChild); const event: ProviderEvent = { id: asEventId("evt-plan-complete"), kind: "notification", provider: "codex", createdAt: new Date().toISOString(), method: "item/completed", threadId: asThreadId("thread-1"), turnId: asTurnId("turn-1"), itemId: asItemId("plan_1"), payload: { threadId: "thread-1", turnId: "turn-1", item: { type: "plan", id: "plan_1", text: "## Final plan\n\n- one\n- two", }, }, }; yield* runtime.emit(event); const firstEvent = yield* Fiber.join(firstEventFiber); assert.equal(firstEvent._tag, "Some"); if (firstEvent._tag !== "Some") { return; } assert.equal(firstEvent.value.type, "turn.proposed.completed"); if (firstEvent.value.type !== "turn.proposed.completed") { return; } assert.equal(firstEvent.value.turnId, "turn-1"); assert.equal(firstEvent.value.payload.planMarkdown, "## Final plan\n\n- one\n- two"); }), ); it.effect("maps plan deltas to canonical proposed-plan delta events", () => Effect.gen(function* () { const { adapter, runtime } = yield* startLifecycleRuntime(); const firstEventFiber = yield* Stream.runHead(adapter.streamEvents).pipe(Effect.forkChild); yield* runtime.emit({ id: asEventId("evt-plan-delta"), kind: "notification", provider: "codex", createdAt: new Date().toISOString(), method: "item/plan/delta", threadId: asThreadId("thread-1"), turnId: asTurnId("turn-1"), itemId: asItemId("plan_1"), payload: { threadId: "thread-1", turnId: "turn-1", itemId: "plan_1", delta: "## Final plan", }, } 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.turnId, "turn-1"); assert.equal(firstEvent.value.payload.delta, "## Final plan"); }), ); it.effect("maps session/closed lifecycle events to canonical session.exited runtime events", () => Effect.gen(function* () { const { adapter, runtime } = yield* startLifecycleRuntime(); const firstEventFiber = yield* Stream.runHead(adapter.streamEvents).pipe(Effect.forkChild); const event: ProviderEvent = { id: asEventId("evt-session-closed"), kind: "session", provider: "codex", threadId: asThreadId("thread-1"), createdAt: new Date().toISOString(), method: "session/closed", message: "Session stopped", }; yield* runtime.emit(event); const firstEvent = yield* Fiber.join(firstEventFiber); assert.equal(firstEvent._tag, "Some"); if (firstEvent._tag !== "Some") { return; } assert.equal(firstEvent.value.type, "session.exited"); if (firstEvent.value.type !== "session.exited") { return; } assert.equal(firstEvent.value.threadId, "thread-1"); assert.equal(firstEvent.value.payload.reason, "Session stopped"); }),