Skip to content

Commit 6489b3f

Browse files
okuyam2yYoshiaki Okuyama
authored andcommitted
fix: add response.status extraction, extract inner message from JSON errors
- Add response.status to status code extraction chain - Extract human-readable message from JSON-encoded error messages - Add tests for response.status and plain object with response.status - Verify inner message extraction from JSON error bodies
1 parent 44babea commit 6489b3f

2 files changed

Lines changed: 40 additions & 11 deletions

File tree

packages/opencode/src/session/message-v2.ts

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -990,24 +990,29 @@ export namespace MessageV2 {
990990
{ cause: e },
991991
).toObject()
992992
case e instanceof Error: {
993+
// Non-APICallError with HTTP status — treat 5xx as retryable.
993994
const code =
994-
(e as any).status ?? (e as any).statusCode ?? (e as any).response?.statusCode ?? (() => {
995+
(e as any).status ?? (e as any).statusCode ??
996+
(e as any).response?.status ?? (e as any).response?.statusCode ??
997+
(() => {
995998
try {
996999
const obj = JSON.parse(e.message)
997-
return obj?.status ?? obj?.statusCode ?? obj?.response?.statusCode
1000+
return obj?.status ?? obj?.statusCode
9981001
} catch {
9991002
return undefined
10001003
}
10011004
})()
1002-
if (typeof code === "number" && code >= 500)
1005+
if (typeof code === "number" && code >= 500) {
1006+
let msg = e.message
1007+
try {
1008+
const obj = JSON.parse(msg)
1009+
if (typeof obj?.message === "string") msg = obj.message
1010+
} catch {}
10031011
return new MessageV2.APIError(
1004-
{
1005-
message: e.message,
1006-
statusCode: code,
1007-
isRetryable: true,
1008-
},
1012+
{ message: msg, statusCode: code, isRetryable: true },
10091013
{ cause: e },
10101014
).toObject()
1015+
}
10111016
return new NamedError.Unknown({ message: e.message }, { cause: e }).toObject()
10121017
}
10131018
default:
@@ -1035,12 +1040,15 @@ export namespace MessageV2 {
10351040
).toObject()
10361041
}
10371042
} catch {}
1043+
// Plain-object 5xx — treat as retryable.
10381044
if (typeof e === "object" && e !== null) {
1039-
const code = (e as any).status ?? (e as any).statusCode ?? (e as any).response?.statusCode
1045+
const code =
1046+
(e as any).status ?? (e as any).statusCode ??
1047+
(e as any).response?.status ?? (e as any).response?.statusCode
10401048
if (typeof code === "number" && code >= 500)
10411049
return new MessageV2.APIError(
10421050
{
1043-
message: JSON.stringify(e),
1051+
message: typeof (e as any).message === "string" ? (e as any).message : JSON.stringify(e),
10441052
statusCode: code,
10451053
isRetryable: true,
10461054
},

packages/opencode/test/session/message-v2.test.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -950,14 +950,26 @@ describe("session.message-v2.fromError", () => {
950950
expect((result as MessageV2.APIError).data.statusCode).toBe(503)
951951
})
952952

953-
test("retries 5xx from JSON-encoded Error message", () => {
953+
test("retries 5xx Error with response.status", () => {
954+
const err = new Error("Gateway Timeout")
955+
;(err as any).response = { status: 504 }
956+
957+
const result = MessageV2.fromError(err, { providerID })
958+
959+
expect(MessageV2.APIError.isInstance(result)).toBe(true)
960+
expect((result as MessageV2.APIError).data.isRetryable).toBe(true)
961+
expect((result as MessageV2.APIError).data.statusCode).toBe(504)
962+
})
963+
964+
test("retries 5xx from JSON-encoded Error message and extracts inner message", () => {
954965
const err = new Error(JSON.stringify({ statusCode: 500, message: "server error" }))
955966

956967
const result = MessageV2.fromError(err, { providerID })
957968

958969
expect(MessageV2.APIError.isInstance(result)).toBe(true)
959970
expect((result as MessageV2.APIError).data.isRetryable).toBe(true)
960971
expect((result as MessageV2.APIError).data.statusCode).toBe(500)
972+
expect((result as MessageV2.APIError).data.message).toBe("server error")
961973
})
962974

963975
test("retries 5xx from plain object with statusCode", () => {
@@ -970,6 +982,15 @@ describe("session.message-v2.fromError", () => {
970982
expect((result as MessageV2.APIError).data.statusCode).toBe(502)
971983
})
972984

985+
test("retries 5xx from plain object with response.status", () => {
986+
const err = { response: { status: 500 }, message: "fail" }
987+
988+
const result = MessageV2.fromError(err, { providerID })
989+
990+
expect(MessageV2.APIError.isInstance(result)).toBe(true)
991+
expect((result as MessageV2.APIError).data.isRetryable).toBe(true)
992+
})
993+
973994
test("does not retry 4xx Error", () => {
974995
const err = new Error("Not Found")
975996
;(err as any).statusCode = 404

0 commit comments

Comments
 (0)