import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; vi.mock("@paperclipai/adapter-utils/execution-target", async (importOriginal) => { const actual = (await importOriginal()) as Record; return { ...actual, runAdapterExecutionTargetProcess: vi.fn() }; }); import { ensureRemoteOpenCodeModelConfiguredAndAvailable } from "./execute.js"; import { runAdapterExecutionTargetProcess } from "@paperclipai/adapter-utils/execution-target"; const runProcessMock = vi.mocked(runAdapterExecutionTargetProcess); function probeResult(overrides: Record) { return { exitCode: 0, signal: null, timedOut: false, stdout: "", stderr: "", pid: 123, startedAt: new Date().toISOString(), ...overrides, } as never; } describe("ensureRemoteOpenCodeModelConfiguredAndAvailable", () => { afterEach(() => { delete process.env.OPENCODE_ALLOW_ALL_MODELS; }); // The remote/sandbox execution path must honour OPENCODE_ALLOW_ALL_MODELS just // like the local path: gateway-routed models (e.g. anthropic// // via Bifrost) never appear in `opencode models`, so the availability probe // must be skipped. The early return happens before the executionTarget is ever // touched, so a bogus target proves the probe was not run. const bogusTarget = {} as never; it("skips the remote availability probe when OPENCODE_ALLOW_ALL_MODELS is set in the run env", async () => { await expect( ensureRemoteOpenCodeModelConfiguredAndAvailable({ runId: "run-1", executionTarget: bogusTarget, command: "opencode", model: "anthropic/tensorix/deepseek/deepseek-chat-v3.1", cwd: "/tmp", env: { OPENCODE_ALLOW_ALL_MODELS: "true" }, timeoutSec: 30, graceSec: 5, }), ).resolves.toBeUndefined(); }); it("honours OPENCODE_ALLOW_ALL_MODELS from the process env", async () => { process.env.OPENCODE_ALLOW_ALL_MODELS = "1"; await expect( ensureRemoteOpenCodeModelConfiguredAndAvailable({ runId: "run-2", executionTarget: bogusTarget, command: "opencode", model: "anthropic/tensorix/deepseek/deepseek-chat-v3.1", cwd: "/tmp", env: {}, timeoutSec: 30, graceSec: 5, }), ).resolves.toBeUndefined(); }); it("still enforces provider/model format even when the bypass flag is set", async () => { await expect( ensureRemoteOpenCodeModelConfiguredAndAvailable({ runId: "run-3", executionTarget: bogusTarget, command: "opencode", model: "", cwd: "/tmp", env: { OPENCODE_ALLOW_ALL_MODELS: "true" }, timeoutSec: 30, graceSec: 5, }), ).rejects.toThrow(); }); }); describe("ensureRemoteOpenCodeModelConfiguredAndAvailable — probe is non-fatal when it cannot run", () => { const target = { kind: "remote", transport: "ssh" } as never; const base = { runId: "run-probe", executionTarget: target, command: "opencode", cwd: "/tmp", env: {} as Record, timeoutSec: 30, graceSec: 5, }; beforeEach(() => { runProcessMock.mockReset(); }); it("proceeds when the remote probe exits non-zero (e.g. a transient `Unexpected error`)", async () => { runProcessMock.mockResolvedValueOnce(probeResult({ exitCode: 1, stderr: "Unexpected error" })); await expect( ensureRemoteOpenCodeModelConfiguredAndAvailable({ ...base, model: "openai/gpt-5" }), ).resolves.toBeUndefined(); }); it("proceeds when the remote probe times out", async () => { runProcessMock.mockResolvedValueOnce(probeResult({ timedOut: true, exitCode: null })); await expect( ensureRemoteOpenCodeModelConfiguredAndAvailable({ ...base, model: "openai/gpt-5" }), ).resolves.toBeUndefined(); }); it("proceeds when the remote probe returns no models", async () => { runProcessMock.mockResolvedValueOnce(probeResult({ exitCode: 0, stdout: "" })); await expect( ensureRemoteOpenCodeModelConfiguredAndAvailable({ ...base, model: "openai/gpt-5" }), ).resolves.toBeUndefined(); }); it("still rejects when the probe succeeds but the configured model is absent (guard retained)", async () => { runProcessMock.mockResolvedValueOnce(probeResult({ exitCode: 0, stdout: "openai/gpt-4.1\n" })); await expect( ensureRemoteOpenCodeModelConfiguredAndAvailable({ ...base, model: "openai/gpt-5" }), ).rejects.toThrow("Configured OpenCode model is unavailable on the remote execution target"); }); });