diff --git a/src/agents/pi-embedded-runner/run.attempt-param-forwarding.test.ts b/src/agents/pi-embedded-runner/run.attempt-param-forwarding.test.ts deleted file mode 100644 index 515656c5f14..00000000000 --- a/src/agents/pi-embedded-runner/run.attempt-param-forwarding.test.ts +++ /dev/null @@ -1,185 +0,0 @@ -import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import type { AgentInternalEvent } from "../internal-events.js"; -import { makeAttemptResult } from "./run.overflow-compaction.fixture.js"; -import { - loadRunOverflowCompactionHarness, - mockedGetApiKeyForModel, - mockedRunEmbeddedAttempt, - overflowBaseRunParams, - resetRunOverflowCompactionHarnessMocks, -} from "./run.overflow-compaction.harness.js"; -import type { RunEmbeddedPiAgentParams } from "./run/params.js"; - -type ForwardingCase = { - runId: string; - params: Partial; - expected: Record; -}; - -let runEmbeddedPiAgent: typeof import("./run.js").runEmbeddedPiAgent; -const internalEvents: AgentInternalEvent[] = []; -const forwardingCase = { - runId: "forward-attempt-params", - params: { - toolsAllow: ["exec", "read"], - bootstrapContextMode: "lightweight", - bootstrapContextRunKind: "cron", - disableMessageTool: true, - forceMessageTool: true, - requireExplicitMessageTarget: true, - internalEvents, - }, - expected: { - toolsAllow: ["exec", "read"], - bootstrapContextMode: "lightweight", - bootstrapContextRunKind: "cron", - disableMessageTool: true, - forceMessageTool: true, - requireExplicitMessageTarget: true, - internalEvents, - }, -} satisfies ForwardingCase; - -describe("runEmbeddedPiAgent forwards optional params to runEmbeddedAttempt", () => { - beforeAll(async () => { - ({ runEmbeddedPiAgent } = await loadRunOverflowCompactionHarness()); - }); - - beforeEach(() => { - resetRunOverflowCompactionHarnessMocks(); - }); - - it("forwards optional attempt params in one attempt call", async () => { - mockedRunEmbeddedAttempt.mockResolvedValueOnce(makeAttemptResult({ promptError: null })); - - await runEmbeddedPiAgent({ - ...overflowBaseRunParams, - ...forwardingCase.params, - runId: forwardingCase.runId, - }); - - expect(mockedRunEmbeddedAttempt).toHaveBeenCalledWith( - expect.objectContaining(forwardingCase.expected), - ); - }); - - it("lets plugin harnesses own auth before the attempt runs", async () => { - const { clearAgentHarnesses, registerAgentHarness } = await import("../harness/registry.js"); - const pluginRunAttempt = vi.fn(async () => makeAttemptResult({ assistantTexts: ["ok"] })); - clearAgentHarnesses(); - registerAgentHarness({ - id: "codex", - label: "Codex", - supports: (ctx) => - ctx.provider === "codex" ? { supported: true, priority: 100 } : { supported: false }, - runAttempt: pluginRunAttempt, - }); - mockedGetApiKeyForModel.mockRejectedValueOnce(new Error("generic auth should be skipped")); - - try { - await runEmbeddedPiAgent({ - ...overflowBaseRunParams, - provider: "codex", - model: "gpt-5.4", - config: { - agents: { - defaults: { - embeddedHarness: { runtime: "codex", fallback: "none" }, - }, - }, - }, - runId: "plugin-harness-skips-generic-auth", - }); - } finally { - clearAgentHarnesses(); - } - - expect(mockedGetApiKeyForModel).not.toHaveBeenCalled(); - expect(pluginRunAttempt).toHaveBeenCalledWith(expect.objectContaining({ provider: "codex" })); - }); - - it("forwards explicit OpenAI Codex auth profiles to codex plugin harnesses", async () => { - const { clearAgentHarnesses, registerAgentHarness } = await import("../harness/registry.js"); - const pluginRunAttempt = vi.fn(async () => makeAttemptResult({ assistantTexts: ["ok"] })); - clearAgentHarnesses(); - registerAgentHarness({ - id: "codex", - label: "Codex", - supports: (ctx) => - ctx.provider === "codex" ? { supported: true, priority: 100 } : { supported: false }, - runAttempt: pluginRunAttempt, - }); - mockedGetApiKeyForModel.mockRejectedValueOnce(new Error("generic auth should be skipped")); - - try { - await runEmbeddedPiAgent({ - ...overflowBaseRunParams, - provider: "codex", - model: "gpt-5.4", - config: { - agents: { - defaults: { - embeddedHarness: { runtime: "codex", fallback: "none" }, - }, - }, - }, - authProfileId: "openai-codex:work", - authProfileIdSource: "user", - runId: "plugin-harness-forwards-openai-codex-auth", - }); - } finally { - clearAgentHarnesses(); - } - - expect(mockedGetApiKeyForModel).not.toHaveBeenCalled(); - expect(pluginRunAttempt).toHaveBeenCalledWith( - expect.objectContaining({ - provider: "codex", - authProfileId: "openai-codex:work", - authProfileIdSource: "user", - }), - ); - }); - - it("forwards OpenAI Codex auth profiles when openai/* is forced through codex", async () => { - const { clearAgentHarnesses, registerAgentHarness } = await import("../harness/registry.js"); - const pluginRunAttempt = vi.fn(async () => makeAttemptResult({ assistantTexts: ["ok"] })); - clearAgentHarnesses(); - registerAgentHarness({ - id: "codex", - label: "Codex", - supports: () => ({ supported: false }), - runAttempt: pluginRunAttempt, - }); - mockedGetApiKeyForModel.mockRejectedValueOnce(new Error("generic auth should be skipped")); - - try { - await runEmbeddedPiAgent({ - ...overflowBaseRunParams, - provider: "openai", - model: "gpt-5.4", - config: { - agents: { - defaults: { - embeddedHarness: { runtime: "codex", fallback: "none" }, - }, - }, - }, - authProfileId: "openai-codex:work", - authProfileIdSource: "user", - runId: "forced-codex-harness-forwards-openai-codex-auth", - }); - } finally { - clearAgentHarnesses(); - } - - expect(mockedGetApiKeyForModel).not.toHaveBeenCalled(); - expect(pluginRunAttempt).toHaveBeenCalledWith( - expect.objectContaining({ - provider: "openai", - authProfileId: "openai-codex:work", - authProfileIdSource: "user", - }), - ); - }); -}); diff --git a/src/agents/pi-embedded-runner/run.overflow-compaction.harness.ts b/src/agents/pi-embedded-runner/run.overflow-compaction.harness.ts index 3868c75d16f..9d3c20ff7ba 100644 --- a/src/agents/pi-embedded-runner/run.overflow-compaction.harness.ts +++ b/src/agents/pi-embedded-runner/run.overflow-compaction.harness.ts @@ -10,6 +10,7 @@ import type { } from "../../plugins/types.js"; import { normalizeLowercaseStringOrEmpty } from "../../shared/string-coerce.js"; import type { FailoverReason } from "../pi-embedded-helpers/types.js"; +import type { buildEmbeddedRunPayloads } from "./run/payloads.js"; import type { EmbeddedRunAttemptResult } from "./run/types.js"; type MockCompactionResult = @@ -72,11 +73,18 @@ export const mockedContextEngine = { export const mockedContextEngineCompact = mockedContextEngine.compact; export const mockedCompactDirect = mockedContextEngine.compact; +export const mockedResolveContextEngine = vi.fn(async () => mockedContextEngine); +export const mockedBuildAgentRuntimePlan = vi.fn(() => ({})); export const mockedRunPostCompactionSideEffects = vi.fn(async () => {}); export const mockedEnsureRuntimePluginsLoaded = vi.fn<(params?: unknown) => void>(); export const mockedPrepareProviderRuntimeAuth = vi.fn(async () => undefined); export const mockedRunEmbeddedAttempt = vi.fn<(params: unknown) => Promise>(); +export const mockedBuildEmbeddedRunPayloads = vi.fn< + ( + ...args: Parameters + ) => ReturnType +>(() => []); export const mockedRunContextEngineMaintenance = vi.fn(async () => undefined); export const mockedSessionLikelyHasOversizedToolResults = vi.fn(() => false); export const mockedResolveLiveToolResultMaxChars = vi.fn(() => 32_000); @@ -223,6 +231,10 @@ export function resetRunOverflowCompactionHarnessMocks(): void { mockedGlobalHookRunner.runAfterCompaction.mockResolvedValue(undefined); mockedContextEngine.info.ownsCompaction = false; + mockedResolveContextEngine.mockReset(); + mockedResolveContextEngine.mockResolvedValue(mockedContextEngine); + mockedBuildAgentRuntimePlan.mockReset(); + mockedBuildAgentRuntimePlan.mockReturnValue({}); mockedContextEngineCompact.mockReset(); mockedContextEngineCompact.mockResolvedValue({ ok: false, @@ -234,6 +246,8 @@ export function resetRunOverflowCompactionHarnessMocks(): void { mockedPrepareProviderRuntimeAuth.mockReset(); mockedPrepareProviderRuntimeAuth.mockResolvedValue(undefined); mockedRunEmbeddedAttempt.mockReset(); + mockedBuildEmbeddedRunPayloads.mockReset(); + mockedBuildEmbeddedRunPayloads.mockReturnValue([]); mockedRunContextEngineMaintenance.mockReset(); mockedRunContextEngineMaintenance.mockResolvedValue(undefined); mockedSessionLikelyHasOversizedToolResults.mockReset(); @@ -361,13 +375,17 @@ export async function loadRunOverflowCompactionHarness(): Promise<{ ensureContextEnginesInitialized: vi.fn(), })); vi.doMock("../../context-engine/registry.js", () => ({ - resolveContextEngine: vi.fn(async () => mockedContextEngine), + resolveContextEngine: mockedResolveContextEngine, })); vi.doMock("../runtime-plugins.js", () => ({ ensureRuntimePluginsLoaded: mockedEnsureRuntimePluginsLoaded, })); + vi.doMock("../runtime-plan/build.js", () => ({ + buildAgentRuntimePlan: mockedBuildAgentRuntimePlan, + })); + vi.doMock("../../plugins/provider-runtime.js", () => ({ prepareProviderRuntimeAuth: mockedPrepareProviderRuntimeAuth, resolveProviderCapabilitiesWithPlugin: vi.fn(() => ({})), @@ -515,7 +533,7 @@ export async function loadRunOverflowCompactionHarness(): Promise<{ })); vi.doMock("./run/payloads.js", () => ({ - buildEmbeddedRunPayloads: vi.fn(() => []), + buildEmbeddedRunPayloads: mockedBuildEmbeddedRunPayloads, })); vi.doMock("./compaction-hooks.js", () => ({ diff --git a/src/agents/pi-embedded-runner/run.overflow-compaction.test.ts b/src/agents/pi-embedded-runner/run.overflow-compaction.test.ts index 8c90e7c7108..1f93ded4b4a 100644 --- a/src/agents/pi-embedded-runner/run.overflow-compaction.test.ts +++ b/src/agents/pi-embedded-runner/run.overflow-compaction.test.ts @@ -1,4 +1,4 @@ -import { beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { makeAttemptResult, makeCompactionSuccess, @@ -8,12 +8,14 @@ import { } from "./run.overflow-compaction.fixture.js"; import { loadRunOverflowCompactionHarness, + mockedBuildEmbeddedRunPayloads, mockedCoerceToFailoverError, mockedCompactDirect, mockedContextEngine, mockedDescribeFailoverError, mockedEvaluateContextWindowGuard, mockedGlobalHookRunner, + mockedGetApiKeyForModel, mockedPickFallbackThinkingLevel, mockedResolveContextWindowInfo, mockedResolveFailoverStatus, @@ -34,6 +36,7 @@ describe("runEmbeddedPiAgent overflow compaction trigger routing", () => { beforeEach(() => { resetRunOverflowCompactionHarnessMocks(); + mockedBuildEmbeddedRunPayloads.mockReturnValue([{ text: "ok" }]); }); it("passes precomputed legacy before_agent_start result into the attempt", async () => { @@ -72,7 +75,6 @@ describe("runEmbeddedPiAgent overflow compaction trigger routing", () => { ...overflowBaseRunParams, runId: "run-auth-profile-passthrough", }); - expect(mockedRunEmbeddedAttempt).toHaveBeenCalledWith( expect.objectContaining({ authProfileId: "test-profile", @@ -81,6 +83,91 @@ describe("runEmbeddedPiAgent overflow compaction trigger routing", () => { ); }); + it("forwards explicit OpenAI Codex auth profiles to codex plugin harnesses", async () => { + const { clearAgentHarnesses, registerAgentHarness } = await import("../harness/registry.js"); + const pluginRunAttempt = vi.fn(async () => makeAttemptResult({ assistantTexts: ["ok"] })); + clearAgentHarnesses(); + registerAgentHarness({ + id: "codex", + label: "Codex", + supports: (ctx) => + ctx.provider === "codex" ? { supported: true, priority: 100 } : { supported: false }, + runAttempt: pluginRunAttempt, + }); + mockedGetApiKeyForModel.mockRejectedValueOnce(new Error("generic auth should be skipped")); + + try { + await runEmbeddedPiAgent({ + ...overflowBaseRunParams, + provider: "codex", + model: "gpt-5.4", + config: { + agents: { + defaults: { + embeddedHarness: { runtime: "codex", fallback: "none" }, + }, + }, + }, + authProfileId: "openai-codex:work", + authProfileIdSource: "user", + runId: "plugin-harness-forwards-openai-codex-auth", + }); + } finally { + clearAgentHarnesses(); + } + + expect(mockedGetApiKeyForModel).not.toHaveBeenCalled(); + expect(pluginRunAttempt).toHaveBeenCalledWith( + expect.objectContaining({ + provider: "codex", + authProfileId: "openai-codex:work", + authProfileIdSource: "user", + }), + ); + }); + + it("forwards OpenAI Codex auth profiles when openai/* is forced through codex", async () => { + const { clearAgentHarnesses, registerAgentHarness } = await import("../harness/registry.js"); + const pluginRunAttempt = vi.fn(async () => makeAttemptResult({ assistantTexts: ["ok"] })); + clearAgentHarnesses(); + registerAgentHarness({ + id: "codex", + label: "Codex", + supports: () => ({ supported: false }), + runAttempt: pluginRunAttempt, + }); + mockedGetApiKeyForModel.mockRejectedValueOnce(new Error("generic auth should be skipped")); + + try { + await runEmbeddedPiAgent({ + ...overflowBaseRunParams, + provider: "openai", + model: "gpt-5.4", + config: { + agents: { + defaults: { + embeddedHarness: { runtime: "codex", fallback: "none" }, + }, + }, + }, + authProfileId: "openai-codex:work", + authProfileIdSource: "user", + runId: "forced-codex-harness-forwards-openai-codex-auth", + }); + } finally { + clearAgentHarnesses(); + } + + expect(mockedGetApiKeyForModel).not.toHaveBeenCalled(); + expect(pluginRunAttempt).toHaveBeenCalledWith( + expect.objectContaining({ + provider: "openai", + authProfileId: "openai-codex:work", + authProfileIdSource: "user", + }), + ); + }); + it("blocks undersized models before dispatching a provider attempt", async () => { mockedResolveContextWindowInfo.mockReturnValue({ tokens: 800,