import * as NodeAssert from "node:assert/strict"; import { it } from "@effect/vitest";import * as Effect from "effect/Effect";import * as Schema from "effect/Schema";import { describe } from "vite-plus/test";import { DEFAULT_MODEL, ThreadId } from "@t3tools/contracts";import * as CodexErrors from "effect-codex-app-server/errors";import * as CodexRpc from "effect-codex-app-server/rpc"; import { buildCodexDeveloperInstructions, CODEX_DEFAULT_MODE_DEVELOPER_INSTRUCTIONS, CODEX_PLAN_MODE_DEVELOPER_INSTRUCTIONS,} from "../CodexDeveloperInstructions.ts";import { codexSessionAppServerArgs } from "./codexLaunchArgs.ts";import { buildTurnStartParams, hasConfiguredMcpServer, isRecoverableThreadResumeError, openCodexThread,} from "./CodexSessionRuntime.ts";const isCodexAppServerRequestError = Schema.is(CodexErrors.CodexAppServerRequestError); describe("CodexSessionRuntimeIdentifierGenerationError", () => { it("retains identifier purpose and the random source failure", () => { const cause = new Error("random source unavailable"); const error = new CodexErrors.CodexAppServerIdentifierGenerationError({ purpose: "provider-event", cause, }); NodeAssert.equal(error.purpose, "provider-event"); NodeAssert.strictEqual(error.cause, cause); NodeAssert.equal( error.message, "Failed to generate Codex App Server identifier for provider-event.", ); });}); function makeThreadOpenResponse( threadId: string,): CodexRpc.ClientRequestResponsesByMethod["thread/start"] { return { cwd: "/tmp/project", model: "gpt-5.3-codex", modelProvider: "openai", approvalPolicy: "never", approvalsReviewer: "user", sandbox: { type: "danger-full-access" }, thread: { id: threadId, createdAt: "2026-04-18T00:00:00.000Z", source: { session: "cli" }, turns: [], status: { state: "idle", activeFlags: [], }, }, } as unknown as CodexRpc.ClientRequestResponsesByMethod["thread/start"];} describe("buildTurnStartParams", () => { it("keeps invalid turn values only in the schema cause", () => { const secret = "codex-turn-input-secret-sentinel"; const error = Effect.runSync( buildTurnStartParams({ threadId: "provider-thread-1", runtimeMode: "full-access", attachments: [ { type: "image", url: { secret } as unknown as string, }, ], }).pipe(Effect.flip), ); const { cause, ...directDiagnostics } = error; NodeAssert.equal(error.operation, "decode-request-payload"); NodeAssert.equal(error.method, "turn/start"); NodeAssert.ok((error.issueCount ?? 0) > 0); NodeAssert.ok(error.issueKinds?.includes("Pointer")); NodeAssert.ok((error.maximumPathDepth ?? 0) > 0); NodeAssert.ok(Schema.isSchemaError(cause)); NodeAssert.doesNotMatch(error.message, new RegExp(secret)); NodeAssert.doesNotMatch(JSON.stringify(directDiagnostics), new RegExp(secret)); }); it("includes plan collaboration mode when requested", () => { const params = Effect.runSync( buildTurnStartParams({ threadId: "provider-thread-1", runtimeMode: "full-access", prompt: "Make a plan", model: "gpt-5.3-codex", effort: "medium", interactionMode: "plan", }), ); NodeAssert.deepStrictEqual(params, { threadId: "provider-thread-1", approvalPolicy: "never", approvalsReviewer: "user", sandboxPolicy: { type: "dangerFullAccess", }, input: [ { type: "text", text: "Make a plan", }, ], model: "gpt-5.3-codex", effort: "medium", collaborationMode: { mode: "plan", settings: { model: "gpt-5.3-codex", reasoning_effort: "medium", developer_instructions: buildCodexDeveloperInstructions("plan", { model: "gpt-5.3-codex", reasoningEffort: "medium", }), }, }, }); }); it("includes default collaboration mode and image attachments", () => { const params = Effect.runSync( buildTurnStartParams({ threadId: "provider-thread-1", runtimeMode: "auto-accept-edits", prompt: "Implement it", model: "gpt-5.3-codex", interactionMode: "default", attachments: [ { type: "image", url: "data:image/png;base64,abc", }, ], }), ); NodeAssert.deepStrictEqual(params, { threadId: "provider-thread-1", approvalPolicy: "on-request", approvalsReviewer: "user", sandboxPolicy: { type: "workspaceWrite", }, input: [ { type: "text", text: "Implement it", }, { type: "image", url: "data:image/png;base64,abc", }, ], model: "gpt-5.3-codex", collaborationMode: { mode: "default", settings: { model: "gpt-5.3-codex", reasoning_effort: "medium", developer_instructions: buildCodexDeveloperInstructions("default", { model: "gpt-5.3-codex", reasoningEffort: "medium", }), }, }, }); }); it("reports the same fallback model and effort in settings and instructions", () => { const params = Effect.runSync( buildTurnStartParams({ threadId: "provider-thread-1", runtimeMode: "full-access", prompt: "Go", interactionMode: "default", }), ); const settings = params.collaborationMode?.settings; NodeAssert.equal(settings?.model, DEFAULT_MODEL); NodeAssert.equal(settings?.reasoning_effort, "medium"); NodeAssert.ok(settings?.developer_instructions?.includes(`as ${DEFAULT_MODEL} with medium`)); }); it.effect("routes approvals to the auto reviewer in auto mode", () => Effect.gen(function* () { const params = yield* buildTurnStartParams({ threadId: "provider-thread-1", runtimeMode: "auto", prompt: "Ship it", }); NodeAssert.deepStrictEqual(params, { threadId: "provider-thread-1", approvalPolicy: "on-request", approvalsReviewer: "auto_review", sandboxPolicy: { type: "workspaceWrite", }, input: [ { type: "text", text: "Ship it", }, ], }); }), ); it("omits collaboration mode when interaction mode is absent", () => { const params = Effect.runSync( buildTurnStartParams({ threadId: "provider-thread-1", runtimeMode: "approval-required", prompt: "Review", }), ); NodeAssert.deepStrictEqual(params, { threadId: "provider-thread-1", approvalPolicy: "untrusted", approvalsReviewer: "user", sandboxPolicy: { type: "readOnly", }, input: [ { type: "text", text: "Review", }, ], }); });}); describe("buildCodexDeveloperInstructions", () => { it("appends runtime info after the mode instructions", () => { const instructions = buildCodexDeveloperInstructions("default", { model: "gpt-5.3-codex", reasoningEffort: "high", }); NodeAssert.ok(instructions.startsWith(CODEX_DEFAULT_MODE_DEVELOPER_INSTRUCTIONS)); NodeAssert.match(instructions, /T3 Code/); NodeAssert.match(instructions, /Codex harness/); NodeAssert.match(instructions, /as gpt-5\.3-codex with high reasoning effort/); }); it("includes runtime info alongside plan mode instructions", () => { const instructions = buildCodexDeveloperInstructions("plan", { model: "gpt-5.3-codex", reasoningEffort: "medium", }); NodeAssert.ok(instructions.startsWith(CODEX_PLAN_MODE_DEVELOPER_INSTRUCTIONS)); NodeAssert.match(instructions, /as gpt-5\.3-codex with medium reasoning effort/); }); it("varies with the model and effort of each turn", () => { const first = buildCodexDeveloperInstructions("default", { model: "gpt-5.3-codex", reasoningEffort: "medium", }); const second = buildCodexDeveloperInstructions("default", { model: "gpt-5.4", reasoningEffort: "high", }); NodeAssert.notEqual(first, second); }); it("flattens multiline metadata into single-line runtime info", () => { const instructions = buildCodexDeveloperInstructions("default", { model: "gpt\n5.3\ncodex", reasoningEffort: " high\neffort ", }); NodeAssert.match(instructions, /as gpt 5\.3 codex with high effort reasoning effort/); NodeAssert.doesNotMatch(instructions, /<runtime_info>[^<]*\n/); });}); describe("T3 browser developer instructions", () => { it("prefers the product-native preview tools in both collaboration modes", () => { for (const instructions of [ CODEX_DEFAULT_MODE_DEVELOPER_INSTRUCTIONS, CODEX_PLAN_MODE_DEVELOPER_INSTRUCTIONS, ]) { NodeAssert.match(instructions, /t3-code/); NodeAssert.match(instructions, /preview_status/); NodeAssert.match(instructions, /preview_open/); NodeAssert.match(instructions, /Do not switch to global browser skills/); } });}); describe("hasConfiguredMcpServer", () => { it("detects inline Codex MCP configuration arguments", () => { NodeAssert.equal(hasConfiguredMcpServer(undefined), false); NodeAssert.equal(hasConfiguredMcpServer(["--model", "gpt-5.4"]), false); NodeAssert.equal( hasConfiguredMcpServer(["-c", 'mcp_servers.t3-code.url="http://127.0.0.1/mcp"']), true, ); });}); describe("codexSessionAppServerArgs", () => { it("keeps the app-server subcommand when explicit args are provided", () => { NodeAssert.deepStrictEqual(codexSessionAppServerArgs(["-c", "model=gpt-5"], undefined), [ "app-server", "-c", "model=gpt-5", ]); }); it("keeps launch args when explicit app-server args are provided", () => { NodeAssert.deepStrictEqual( codexSessionAppServerArgs( ["-c", "mcp_servers.t3-code.url=http://127.0.0.1/mcp"], "--strict-config --enable foo", ), [ "app-server", "--strict-config", "--enable", "foo", "-c", "mcp_servers.t3-code.url=http://127.0.0.1/mcp", ], ); });}); describe("isRecoverableThreadResumeError", () => { it("matches missing thread errors", () => { NodeAssert.equal( isRecoverableThreadResumeError( new CodexErrors.CodexAppServerRequestError({ code: -32603, errorMessage: "Thread does not exist", }), ), true, ); }); it("ignores non-recoverable resume errors", () => { NodeAssert.equal( isRecoverableThreadResumeError( new CodexErrors.CodexAppServerRequestError({ code: -32603, errorMessage: "Permission denied", }), ), false, ); }); it("ignores unrelated missing-resource errors that do not mention threads", () => { NodeAssert.equal( isRecoverableThreadResumeError( new CodexErrors.CodexAppServerRequestError({ code: -32603, errorMessage: "Config file not found", }), ), false, ); NodeAssert.equal( isRecoverableThreadResumeError( new CodexErrors.CodexAppServerRequestError({ code: -32603, errorMessage: "Model does not exist", }), ), false, ); });}); describe("openCodexThread", () => { it.effect("falls back to thread/start when resume fails recoverably", () => Effect.gen(function* () { const calls: Array<{ method: "thread/start" | "thread/resume"; payload: unknown }> = []; const started = makeThreadOpenResponse("fresh-thread"); const client = { request: <M extends "thread/start" | "thread/resume">( method: M, payload: CodexRpc.ClientRequestParamsByMethod[M], ) => { calls.push({ method, payload }); if (method === "thread/resume") { return Effect.fail( new CodexErrors.CodexAppServerRequestError({ code: -32603, errorMessage: "thread not found", }), ); } return Effect.succeed(started as CodexRpc.ClientRequestResponsesByMethod[M]); }, }; const opened = yield* openCodexThread({ client, threadId: ThreadId.make("thread-1"), runtimeMode: "full-access", cwd: "/tmp/project", requestedModel: "gpt-5.3-codex", serviceTier: undefined, resumeThreadId: "stale-thread", }); NodeAssert.equal(opened.thread.id, "fresh-thread"); NodeAssert.deepStrictEqual( calls.map((call) => call.method), ["thread/resume", "thread/start"], ); }), ); it.effect("propagates non-recoverable resume failures", () => Effect.gen(function* () { const client = { request: <M extends "thread/start" | "thread/resume">( method: M, _payload: CodexRpc.ClientRequestParamsByMethod[M], ) => { if (method === "thread/resume") { return Effect.fail( new CodexErrors.CodexAppServerRequestError({ code: -32603, errorMessage: "timed out waiting for server", }), ); } return Effect.succeed( makeThreadOpenResponse("fresh-thread") as CodexRpc.ClientRequestResponsesByMethod[M], ); }, }; const error = yield* openCodexThread({ client, threadId: ThreadId.make("thread-1"), runtimeMode: "full-access", cwd: "/tmp/project", requestedModel: "gpt-5.3-codex", serviceTier: undefined, resumeThreadId: "stale-thread", }).pipe(Effect.flip); NodeAssert.ok(isCodexAppServerRequestError(error)); NodeAssert.equal(error.errorMessage, "timed out waiting for server"); }), );});
import * as NodeAssert from "node:assert/strict";
import { it } from "@effect/vitest";
import * as Effect from "effect/Effect";
import * as Schema from "effect/Schema";
import { describe } from "vite-plus/test";
import { DEFAULT_MODEL, ThreadId } from "@t3tools/contracts";
import * as CodexErrors from "effect-codex-app-server/errors";
import * as CodexRpc from "effect-codex-app-server/rpc";
import {
buildCodexDeveloperInstructions,
CODEX_DEFAULT_MODE_DEVELOPER_INSTRUCTIONS,
CODEX_PLAN_MODE_DEVELOPER_INSTRUCTIONS,
} from "../CodexDeveloperInstructions.ts";
import { codexSessionAppServerArgs } from "./codexLaunchArgs.ts";
import {
buildTurnStartParams,
hasConfiguredMcpServer,
isRecoverableThreadResumeError,
openCodexThread,
} from "./CodexSessionRuntime.ts";
const isCodexAppServerRequestError = Schema.is(CodexErrors.CodexAppServerRequestError);
describe("CodexSessionRuntimeIdentifierGenerationError", () => {
it("retains identifier purpose and the random source failure", () => {
const cause = new Error("random source unavailable");
const error = new CodexErrors.CodexAppServerIdentifierGenerationError({
purpose: "provider-event",
cause,
});
NodeAssert.equal(error.purpose, "provider-event");
NodeAssert.strictEqual(error.cause, cause);
NodeAssert.equal(
error.message,
"Failed to generate Codex App Server identifier for provider-event.",
);
});
});
function makeThreadOpenResponse(
threadId: string,
): CodexRpc.ClientRequestResponsesByMethod["thread/start"] {
return {
cwd: "/tmp/project",
model: "gpt-5.3-codex",
modelProvider: "openai",
approvalPolicy: "never",
approvalsReviewer: "user",
sandbox: { type: "danger-full-access" },
thread: {
id: threadId,
createdAt: "2026-04-18T00:00:00.000Z",
source: { session: "cli" },
turns: [],
status: {
state: "idle",
activeFlags: [],
},
},
} as unknown as CodexRpc.ClientRequestResponsesByMethod["thread/start"];
}
describe("buildTurnStartParams", () => {
it("keeps invalid turn values only in the schema cause", () => {
const secret = "codex-turn-input-secret-sentinel";
const error = Effect.runSync(
buildTurnStartParams({
threadId: "provider-thread-1",
runtimeMode: "full-access",
attachments: [
{
type: "image",
url: { secret } as unknown as string,
},
],
}).pipe(Effect.flip),
);
const { cause, ...directDiagnostics } = error;
NodeAssert.equal(error.operation, "decode-request-payload");
NodeAssert.equal(error.method, "turn/start");
NodeAssert.ok((error.issueCount ?? 0) > 0);
NodeAssert.ok(error.issueKinds?.includes("Pointer"));
NodeAssert.ok((error.maximumPathDepth ?? 0) > 0);
NodeAssert.ok(Schema.isSchemaError(cause));
NodeAssert.doesNotMatch(error.message, new RegExp(secret));
NodeAssert.doesNotMatch(JSON.stringify(directDiagnostics), new RegExp(secret));
});
it("includes plan collaboration mode when requested", () => {
const params = Effect.runSync(
buildTurnStartParams({
threadId: "provider-thread-1",
runtimeMode: "full-access",
prompt: "Make a plan",
model: "gpt-5.3-codex",
effort: "medium",
interactionMode: "plan",
}),
);
NodeAssert.deepStrictEqual(params, {
threadId: "provider-thread-1",
approvalPolicy: "never",
approvalsReviewer: "user",
sandboxPolicy: {
type: "dangerFullAccess",
},
input: [
{
type: "text",
text: "Make a plan",
},
],
model: "gpt-5.3-codex",
effort: "medium",
collaborationMode: {
mode: "plan",
settings: {
model: "gpt-5.3-codex",
reasoning_effort: "medium",
developer_instructions: buildCodexDeveloperInstructions("plan", {
model: "gpt-5.3-codex",
reasoningEffort: "medium",
}),
},
},
});
});
it("includes default collaboration mode and image attachments", () => {
const params = Effect.runSync(
buildTurnStartParams({
threadId: "provider-thread-1",
runtimeMode: "auto-accept-edits",
prompt: "Implement it",
model: "gpt-5.3-codex",
interactionMode: "default",
attachments: [
{
type: "image",
url: "data:image/png;base64,abc",
},
],
}),
);
NodeAssert.deepStrictEqual(params, {
threadId: "provider-thread-1",
approvalPolicy: "on-request",
approvalsReviewer: "user",
sandboxPolicy: {
type: "workspaceWrite",
},
input: [
{
type: "text",
text: "Implement it",
},
{
type: "image",
url: "data:image/png;base64,abc",
},
],
model: "gpt-5.3-codex",
collaborationMode: {
mode: "default",
settings: {
model: "gpt-5.3-codex",
reasoning_effort: "medium",
developer_instructions: buildCodexDeveloperInstructions("default", {
model: "gpt-5.3-codex",
reasoningEffort: "medium",
}),
},
},
});
});
it("reports the same fallback model and effort in settings and instructions", () => {
const params = Effect.runSync(
buildTurnStartParams({
threadId: "provider-thread-1",
runtimeMode: "full-access",
prompt: "Go",
interactionMode: "default",
}),
);
const settings = params.collaborationMode?.settings;
NodeAssert.equal(settings?.model, DEFAULT_MODEL);
NodeAssert.equal(settings?.reasoning_effort, "medium");
NodeAssert.ok(settings?.developer_instructions?.includes(`as ${DEFAULT_MODEL} with medium`));
});
it.effect("routes approvals to the auto reviewer in auto mode", () =>
Effect.gen(function* () {
const params = yield* buildTurnStartParams({
threadId: "provider-thread-1",
runtimeMode: "auto",
prompt: "Ship it",
});
NodeAssert.deepStrictEqual(params, {
threadId: "provider-thread-1",
approvalPolicy: "on-request",
approvalsReviewer: "auto_review",
sandboxPolicy: {
type: "workspaceWrite",
},
input: [
{
type: "text",
text: "Ship it",
},
],
});
}),
);
it("omits collaboration mode when interaction mode is absent", () => {
const params = Effect.runSync(
buildTurnStartParams({
threadId: "provider-thread-1",
runtimeMode: "approval-required",
prompt: "Review",
}),
);
NodeAssert.deepStrictEqual(params, {
threadId: "provider-thread-1",
approvalPolicy: "untrusted",
approvalsReviewer: "user",
sandboxPolicy: {
type: "readOnly",
},
input: [
{
type: "text",
text: "Review",
},
],
});
});
});
describe("buildCodexDeveloperInstructions", () => {
it("appends runtime info after the mode instructions", () => {
const instructions = buildCodexDeveloperInstructions("default", {
model: "gpt-5.3-codex",
reasoningEffort: "high",
});
NodeAssert.ok(instructions.startsWith(CODEX_DEFAULT_MODE_DEVELOPER_INSTRUCTIONS));
NodeAssert.match(instructions, /T3 Code/);
NodeAssert.match(instructions, /Codex harness/);
NodeAssert.match(instructions, /as gpt-5\.3-codex with high reasoning effort/);
});
it("includes runtime info alongside plan mode instructions", () => {
const instructions = buildCodexDeveloperInstructions("plan", {
model: "gpt-5.3-codex",
reasoningEffort: "medium",
});
NodeAssert.ok(instructions.startsWith(CODEX_PLAN_MODE_DEVELOPER_INSTRUCTIONS));
NodeAssert.match(instructions, /as gpt-5\.3-codex with medium reasoning effort/);
});
it("varies with the model and effort of each turn", () => {
const first = buildCodexDeveloperInstructions("default", {
model: "gpt-5.3-codex",
reasoningEffort: "medium",
});
const second = buildCodexDeveloperInstructions("default", {
model: "gpt-5.4",
reasoningEffort: "high",
});
NodeAssert.notEqual(first, second);
});
it("flattens multiline metadata into single-line runtime info", () => {
const instructions = buildCodexDeveloperInstructions("default", {
model: "gpt\n5.3\ncodex",
reasoningEffort: " high\neffort ",
});
NodeAssert.match(instructions, /as gpt 5\.3 codex with high effort reasoning effort/);
NodeAssert.doesNotMatch(instructions, /<runtime_info>[^<]*\n/);
});
});
describe("T3 browser developer instructions", () => {
it("prefers the product-native preview tools in both collaboration modes", () => {
for (const instructions of [
CODEX_DEFAULT_MODE_DEVELOPER_INSTRUCTIONS,
CODEX_PLAN_MODE_DEVELOPER_INSTRUCTIONS,
]) {
NodeAssert.match(instructions, /t3-code/);
NodeAssert.match(instructions, /preview_status/);
NodeAssert.match(instructions, /preview_open/);
NodeAssert.match(instructions, /Do not switch to global browser skills/);
}
});
});
describe("hasConfiguredMcpServer", () => {
it("detects inline Codex MCP configuration arguments", () => {
NodeAssert.equal(hasConfiguredMcpServer(undefined), false);
NodeAssert.equal(hasConfiguredMcpServer(["--model", "gpt-5.4"]), false);
NodeAssert.equal(
hasConfiguredMcpServer(["-c", 'mcp_servers.t3-code.url="http://127.0.0.1/mcp"']),
true,
);
});
});
describe("codexSessionAppServerArgs", () => {
it("keeps the app-server subcommand when explicit args are provided", () => {
NodeAssert.deepStrictEqual(codexSessionAppServerArgs(["-c", "model=gpt-5"], undefined), [
"app-server",
"-c",
"model=gpt-5",
]);
});
it("keeps launch args when explicit app-server args are provided", () => {
NodeAssert.deepStrictEqual(
codexSessionAppServerArgs(
["-c", "mcp_servers.t3-code.url=http://127.0.0.1/mcp"],
"--strict-config --enable foo",
),
[
"app-server",
"--strict-config",
"--enable",
"foo",
"-c",
"mcp_servers.t3-code.url=http://127.0.0.1/mcp",
],
);
});
});
describe("isRecoverableThreadResumeError", () => {
it("matches missing thread errors", () => {
NodeAssert.equal(
isRecoverableThreadResumeError(
new CodexErrors.CodexAppServerRequestError({
code: -32603,
errorMessage: "Thread does not exist",
}),
),
true,
);
});
it("ignores non-recoverable resume errors", () => {
NodeAssert.equal(
isRecoverableThreadResumeError(
new CodexErrors.CodexAppServerRequestError({
code: -32603,
errorMessage: "Permission denied",
}),
),
false,
);
});
it("ignores unrelated missing-resource errors that do not mention threads", () => {
NodeAssert.equal(
isRecoverableThreadResumeError(
new CodexErrors.CodexAppServerRequestError({
code: -32603,
errorMessage: "Config file not found",
}),
),
false,
);
NodeAssert.equal(
isRecoverableThreadResumeError(
new CodexErrors.CodexAppServerRequestError({
code: -32603,
errorMessage: "Model does not exist",
}),
),
false,
);
});
});
describe("openCodexThread", () => {
it.effect("falls back to thread/start when resume fails recoverably", () =>
Effect.gen(function* () {
const calls: Array<{ method: "thread/start" | "thread/resume"; payload: unknown }> = [];
const started = makeThreadOpenResponse("fresh-thread");
const client = {
request: <M extends "thread/start" | "thread/resume">(
method: M,
payload: CodexRpc.ClientRequestParamsByMethod[M],
) => {
calls.push({ method, payload });
if (method === "thread/resume") {
return Effect.fail(
new CodexErrors.CodexAppServerRequestError({
code: -32603,
errorMessage: "thread not found",
}),
);
}
return Effect.succeed(started as CodexRpc.ClientRequestResponsesByMethod[M]);
},
};
const opened = yield* openCodexThread({
client,
threadId: ThreadId.make("thread-1"),
runtimeMode: "full-access",
cwd: "/tmp/project",
requestedModel: "gpt-5.3-codex",
serviceTier: undefined,
resumeThreadId: "stale-thread",
});
NodeAssert.equal(opened.thread.id, "fresh-thread");
NodeAssert.deepStrictEqual(
calls.map((call) => call.method),
["thread/resume", "thread/start"],
);
}),
);
it.effect("propagates non-recoverable resume failures", () =>
Effect.gen(function* () {
const client = {
request: <M extends "thread/start" | "thread/resume">(
method: M,
_payload: CodexRpc.ClientRequestParamsByMethod[M],
) => {
if (method === "thread/resume") {
return Effect.fail(
new CodexErrors.CodexAppServerRequestError({
code: -32603,
errorMessage: "timed out waiting for server",
}),
);
}
return Effect.succeed(
makeThreadOpenResponse("fresh-thread") as CodexRpc.ClientRequestResponsesByMethod[M],
);
},
};
const error = yield* openCodexThread({
client,
threadId: ThreadId.make("thread-1"),
runtimeMode: "full-access",
cwd: "/tmp/project",
requestedModel: "gpt-5.3-codex",
serviceTier: undefined,
resumeThreadId: "stale-thread",
}).pipe(Effect.flip);
NodeAssert.ok(isCodexAppServerRequestError(error));
NodeAssert.equal(error.errorMessage, "timed out waiting for server");
}),
);
});