diff --git a/packages/cli/src/commands/handlers/stats.ts b/packages/cli/src/commands/handlers/stats.ts index 3befbc457ee..3502ac5efe2 100644 --- a/packages/cli/src/commands/handlers/stats.ts +++ b/packages/cli/src/commands/handlers/stats.ts @@ -1,4 +1,4 @@ -import { OpenCode, type SessionStatsInfo } from "@opencode-ai/client" +import { ClientError, OpenCode, type SessionStatsInfo } from "@opencode-ai/client" import { Service } from "@opencode-ai/client/effect/service" import { Effect, Option } from "effect" import { EOL } from "node:os" @@ -67,10 +67,13 @@ export default Runtime.handler(Commands.commands.stats, (input) => ), ) -function request(url: string, run: (signal: AbortSignal) => Promise) { +export function request(url: string, run: (signal: AbortSignal) => Promise) { return Effect.tryPromise({ try: () => run(AbortSignal.timeout(30_000)), - catch: (cause) => new Error(`Could not reach server at ${url}`, { cause }), + catch: (cause) => + cause instanceof ClientError && cause.reason === "Transport" + ? new Error(`Could not reach server at ${url}`, { cause }) + : cause, }) } diff --git a/packages/cli/test/stats.test.ts b/packages/cli/test/stats.test.ts index 06f2e6c1859..0fdea8ad08c 100644 --- a/packages/cli/test/stats.test.ts +++ b/packages/cli/test/stats.test.ts @@ -1,6 +1,7 @@ import { describe, expect, test } from "bun:test" -import type { SessionStatsInfo } from "@opencode-ai/client" -import { renderStats } from "../src/commands/handlers/stats" +import { ClientError, type SessionStatsInfo } from "@opencode-ai/client" +import { Effect } from "effect" +import { renderStats, request } from "../src/commands/handlers/stats" const tools = { mode: "detail", @@ -121,6 +122,26 @@ describe("stats rendering", () => { }) }) +describe("stats requests", () => { + test("maps transport failures to the server URL", async () => { + const cause = new ClientError("Transport") + const error = await Effect.runPromise(Effect.flip(request("http://localhost:4096", () => Promise.reject(cause)))) + expect(error).toEqual(new Error("Could not reach server at http://localhost:4096", { cause })) + }) + + test("preserves declared API errors", async () => { + const cause = { _tag: "InvalidRequestError", message: "Stats range must end after it starts" } as const + const error = await Effect.runPromise(Effect.flip(request("http://localhost:4096", () => Promise.reject(cause)))) + expect(error).toBe(cause) + }) + + test("preserves unexpected response failures", async () => { + const cause = new ClientError("UnexpectedStatus", { cause: { status: 500 } }) + const error = await Effect.runPromise(Effect.flip(request("http://localhost:4096", () => Promise.reject(cause)))) + expect(error).toBe(cause) + }) +}) + function options(input: Partial[1]> = {}): Parameters[1] { return { label: "2026 so far",