diff --git a/packages/server/src/server/agent/providers/codex-app-server-agent.test.ts b/packages/server/src/server/agent/providers/codex-app-server-agent.test.ts index f0a7feca14..d7868b5438 100644 --- a/packages/server/src/server/agent/providers/codex-app-server-agent.test.ts +++ b/packages/server/src/server/agent/providers/codex-app-server-agent.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, test, vi } from "vitest"; +import { beforeEach, describe, expect, test, vi } from "vitest"; import type { ChildProcessWithoutNullStreams } from "node:child_process"; import { EventEmitter } from "node:events"; import { type Dirent, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; @@ -100,6 +100,12 @@ import { createTestLogger } from "../../../test-utils/test-logger.js"; import { asInternals as castInternals, createStub } from "../../test-utils/class-mocks.js"; import { AgentManager } from "../agent-manager.js"; import { buildProviderRegistry } from "../provider-registry.js"; +import { sessionTurnIds } from "./test-utils/turn-ids.js"; + +let codexTurnId = sessionTurnIds("codex"); +beforeEach(() => { + codexTurnId = sessionTurnIds("codex"); +}); interface CollaborationModeRecord { name: string; @@ -4634,7 +4640,7 @@ describe("Codex app-server provider", () => { await expect(child).resolves.toMatchObject({ type: "provider_subagent", provider: "codex", - turnId: expect.stringMatching(/^codex-turn-[0-9a-f]+-0$/), + turnId: codexTurnId(0), event: { type: "upsert", id: "legacy-only-child-thread", @@ -4644,7 +4650,7 @@ describe("Codex app-server provider", () => { await expect(spawn).resolves.toMatchObject({ type: "timeline", provider: "codex", - turnId: expect.stringMatching(/^codex-turn-[0-9a-f]+-0$/), + turnId: codexTurnId(0), item: { type: "tool_call", callId: "spawn-legacy-only-child", diff --git a/packages/server/src/server/agent/providers/opencode-agent.test.ts b/packages/server/src/server/agent/providers/opencode-agent.test.ts index 7bb6055761..6bf4b77e2e 100644 --- a/packages/server/src/server/agent/providers/opencode-agent.test.ts +++ b/packages/server/src/server/agent/providers/opencode-agent.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, test, vi } from "vitest"; +import { beforeEach, describe, expect, test, vi } from "vitest"; import { mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs"; import os from "node:os"; import path from "node:path"; @@ -25,6 +25,7 @@ import type { AssistantMessageTimelineItem, AgentTimelineItem, } from "../agent-sdk-types.js"; +import { sessionTurnIds } from "./test-utils/turn-ids.js"; // Deliberately an independent literal rather than the production constant these tests // guard: deriving the boundary from OPENCODE_SERVER_STARTUP_TIMEOUT_MS would keep the @@ -32,14 +33,10 @@ import type { const EXPECTED_STARTUP_BUDGET_MS = 30_000; const EXPECTED_EVENT_READINESS_BUDGET_MS = 45_000; -/** Matches the ordinal of an OpenCode turn id; the instance nonce in the middle is random. */ -function openCodeTurnId(ordinal: number) { - return expect.stringMatching(new RegExp(`^opencode-turn-[0-9a-f]+-${ordinal}$`)); -} - -function isOpenCodeTurn(turnId: string | undefined, ordinal: number): boolean { - return new RegExp(`^opencode-turn-[0-9a-f]+-${ordinal}$`).test(turnId ?? ""); -} +let openCodeTurnId = sessionTurnIds("opencode"); +beforeEach(() => { + openCodeTurnId = sessionTurnIds("opencode"); +}); function tmpCwd(): string { const dir = mkdtempSync(path.join(os.tmpdir(), "opencode-agent-test-")); @@ -2896,7 +2893,8 @@ describe("OpenCode adapter startTurn error handling", () => { }), ); const autonomousCompletion = events.find( - (event) => event.type === "turn_completed" && isOpenCodeTurn(event.turnId, 1), + (event) => + event.type === "turn_completed" && openCodeTurnId(1).asymmetricMatch(event.turnId), ); expect(autonomousCompletion?.type).toBe("turn_completed"); if (autonomousCompletion?.type === "turn_completed") { diff --git a/packages/server/src/server/agent/providers/test-utils/turn-ids.ts b/packages/server/src/server/agent/providers/test-utils/turn-ids.ts new file mode 100644 index 0000000000..c9e5e35cc1 --- /dev/null +++ b/packages/server/src/server/agent/providers/test-utils/turn-ids.ts @@ -0,0 +1,30 @@ +/** A Vitest asymmetric matcher for one provider turn id. */ +export interface TurnIdMatcher { + asymmetricMatch(actual: unknown): boolean; + toString(): string; + toAsymmetricMatcher(): string; +} + +/** + * Matches the turn ids one provider session issues. Each session mixes a random nonce into its + * ids, so the first id seen binds that nonce and every later match must be the exact id + * `-turn--`. Two events of one turn therefore match only when their ids + * are identical. Create one per test. + */ +export function sessionTurnIds(owner: string): (ordinal: number) => TurnIdMatcher { + let nonce: string | null = null; + const pattern = new RegExp(`^${owner}-turn-([0-9a-f]+)-(\\d+)$`); + return (ordinal) => { + const describe = () => `${owner}-turn-${nonce ?? ""}-${ordinal}`; + return { + asymmetricMatch(actual) { + const match = typeof actual === "string" ? pattern.exec(actual) : null; + if (!match || Number(match[2]) !== ordinal) return false; + nonce ??= match[1]!; + return match[1] === nonce; + }, + toString: describe, + toAsymmetricMatcher: describe, + }; + }; +}