diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index 04cb15ef8d02..4d2a90a39ca9 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -971,6 +971,14 @@ export function fromError( cause: e, }, ).toObject() + case e instanceof DOMException && e.name === "TimeoutError": + return new APIError( + { + message: e.message || "Operation timed out", + isRetryable: true, + }, + { cause: e }, + ).toObject() case OutputLengthError.isInstance(e): return e case LoadAPIKeyError.isInstance(e): diff --git a/packages/opencode/test/session/message-v2.test.ts b/packages/opencode/test/session/message-v2.test.ts index 55ae65c56029..7072a86a3395 100644 --- a/packages/opencode/test/session/message-v2.test.ts +++ b/packages/opencode/test/session/message-v2.test.ts @@ -950,6 +950,14 @@ describe("session.message-v2.toModelMessage", () => { }) describe("session.message-v2.fromError", () => { + test("classifies timeout DOMException as retryable APIError", () => { + const result = MessageV2.fromError(new DOMException("operation timed out", "TimeoutError"), { providerID }) + + expect(MessageV2.APIError.isInstance(result)).toBe(true) + expect((result as MessageV2.APIError).data.message).toBe("operation timed out") + expect((result as MessageV2.APIError).data.isRetryable).toBe(true) + }) + test("serializes context_length_exceeded as ContextOverflowError", () => { const input = { type: "error",