diff --git a/packages/adapters/opencode-local/src/server/parse.ts b/packages/adapters/opencode-local/src/server/parse.ts index c244fa0e102..db2d1e1e7cc 100644 --- a/packages/adapters/opencode-local/src/server/parse.ts +++ b/packages/adapters/opencode-local/src/server/parse.ts @@ -106,6 +106,17 @@ export function createOpenCodeJsonlParser() { }; } +export function openCodeErrorStatusCodes(stdout: string): number[] { + const statuses: number[] = []; + for (const rawLine of stdout.split(/\r?\n/)) { + const event = parseJson(rawLine.trim()); + if (!event || asString(event.type, "") !== "error") continue; + const status = parseObject(parseObject(event.error).data).statusCode; + if (typeof status === "number" && Number.isInteger(status)) statuses.push(status); + } + return statuses; +} + export function isOpenCodeUnknownSessionError(stdout: string, stderr: string): boolean { const haystack = `${stdout}\n${stderr}` .split(/\r?\n/) diff --git a/packages/adapters/opencode-local/src/server/test.remote.test.ts b/packages/adapters/opencode-local/src/server/test.remote.test.ts index e83848e3f15..7a73d363edd 100644 --- a/packages/adapters/opencode-local/src/server/test.remote.test.ts +++ b/packages/adapters/opencode-local/src/server/test.remote.test.ts @@ -119,6 +119,29 @@ describe("opencode remote environment diagnostics", () => { }, ); + it.each(["Missing Authentication header", "User not found."])( + "reports a provider 401 as missing authentication, not a generic failure (%s)", async message => { + const successfulProbe = await runAdapterExecutionTargetProcess.getMockImplementation()!(); + runAdapterExecutionTargetProcess.mockResolvedValueOnce({ ...successfulProbe, exitCode: 1, stderr: "", + stdout: JSON.stringify({ type: "error", sessionID: "session-1", error: { name: "APIError", + data: { message, statusCode: 401, isRetryable: false, responseHeaders: { "access-control-expose-headers": "WWW-Authenticate" } } } }) }); + const result = await testEnvironment({ companyId: "company-1", adapterType: "paperclip_runner", + config: { model: "openrouter/deepseek/deepseek-v4-flash-0731", env: { XDG_CONFIG_HOME: configHome } } }); + expect(result.status).toBe("warn"); + expect(result.checks.map(check => [check.code, check.level])).toContainEqual(["opencode_hello_probe_auth_required", "warn"]); + expect(result.checks.some(check => check.level === "error")).toBe(false); + }, + ); + + it.each([500, 429])("keeps other provider statuses a generic probe failure (%s)", async statusCode => { + const successfulProbe = await runAdapterExecutionTargetProcess.getMockImplementation()!(); + runAdapterExecutionTargetProcess.mockResolvedValueOnce({ ...successfulProbe, exitCode: 1, stderr: "", + stdout: JSON.stringify({ type: "error", error: { name: "APIError", data: { message: "Upstream error", statusCode } } }) }); + const result = await testEnvironment({ companyId: "company-1", adapterType: "paperclip_runner", + config: { model: "openrouter/deepseek/deepseek-v4-flash-0731", env: { XDG_CONFIG_HOME: configHome } } }); + expect(result.checks).toContainEqual(expect.objectContaining({ code: "opencode_hello_probe_failed", level: "error" })); + }); + it("gives each concurrent native probe its own OpenCode data directory", async () => { vi.stubEnv("HOME", "/host/user"); vi.stubEnv("XDG_DATA_HOME", "/host/user/.local/share"); diff --git a/packages/adapters/opencode-local/src/server/test.ts b/packages/adapters/opencode-local/src/server/test.ts index aad64db7234..367ac7c6aa1 100644 --- a/packages/adapters/opencode-local/src/server/test.ts +++ b/packages/adapters/opencode-local/src/server/test.ts @@ -27,7 +27,7 @@ import { overrideAdapterExecutionTargetRemoteCwd, } from "@paperclipai/adapter-utils/execution-target"; import { discoverOpenCodeModels, ensureOpenCodeModelConfiguredAndAvailable, requireOpenCodeModelId } from "./models.js"; -import { parseOpenCodeJsonl } from "./parse.js"; +import { openCodeErrorStatusCodes, parseOpenCodeJsonl } from "./parse.js"; import { SANDBOX_INSTALL_COMMAND } from "../index.js"; import { prepareOpenCodeRuntimeConfig, prepareManagedOpenCodeRemoteHomes } from "./runtime-config.js"; @@ -418,6 +418,7 @@ export async function testEnvironment( const parsed = parseOpenCodeJsonl(probe.stdout); const detail = summarizeProbeDetail(probe.stdout, probe.stderr, parsed.errorMessage); const authEvidence = `${parsed.errorMessage ?? ""}\n${probe.stdout}\n${probe.stderr}`.trim(); + const providerRejectedAuth = openCodeErrorStatusCodes(probe.stdout).includes(401); if (probe.timedOut) { checks.push({ @@ -450,7 +451,7 @@ export async function testEnvironment( ...(detail ? { detail } : {}), hint: "Run `opencode models` and choose an available provider/model ID.", }); - } else if (OPENCODE_AUTH_REQUIRED_RE.test(authEvidence)) { + } else if (providerRejectedAuth || OPENCODE_AUTH_REQUIRED_RE.test(authEvidence)) { checks.push({ code: "opencode_hello_probe_auth_required", level: "warn",