Skip to content

Commit 62f3808

Browse files
authored
fix: parse mid stream openai responses style errors to prevent infinite retries for errors that should STOP execution (#12768)
1 parent 79879b4 commit 62f3808

2 files changed

Lines changed: 124 additions & 1 deletion

File tree

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

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -796,7 +796,76 @@ export namespace MessageV2 {
796796
case e instanceof Error:
797797
return new NamedError.Unknown({ message: e.toString() }, { cause: e }).toObject()
798798
default:
799-
return new NamedError.Unknown({ message: JSON.stringify(e) }, { cause: e })
799+
try {
800+
const json = iife(() => {
801+
if (typeof e === "string") {
802+
try {
803+
return JSON.parse(e)
804+
} catch {
805+
return undefined
806+
}
807+
}
808+
809+
if (typeof e === "object" && e !== null) {
810+
return e
811+
}
812+
return undefined
813+
})
814+
if (json) {
815+
const responseBody = JSON.stringify(json)
816+
// Handle Responses API mid stream style errors
817+
if (json?.type === "error") {
818+
switch (json?.error?.code) {
819+
case "context_length_exceeded":
820+
return new MessageV2.APIError(
821+
{
822+
message: "Input exceeds context window of this model",
823+
isRetryable: false,
824+
responseBody,
825+
},
826+
{
827+
cause: e,
828+
},
829+
).toObject()
830+
case "insufficient_quota":
831+
return new MessageV2.APIError(
832+
{
833+
message: "Quota exceeded. Check your plan and billing details.",
834+
isRetryable: false,
835+
responseBody,
836+
},
837+
{
838+
cause: e,
839+
},
840+
).toObject()
841+
case "usage_not_included":
842+
return new MessageV2.APIError(
843+
{
844+
message:
845+
"To use Codex with your ChatGPT plan, upgrade to Plus: https://chatgpt.com/explore/plus.",
846+
isRetryable: false,
847+
responseBody,
848+
},
849+
{
850+
cause: e,
851+
},
852+
).toObject()
853+
case "invalid_prompt":
854+
return new MessageV2.APIError(
855+
{
856+
message: json?.error?.message || "Invalid prompt.",
857+
isRetryable: false,
858+
responseBody,
859+
},
860+
{
861+
cause: e,
862+
},
863+
).toObject()
864+
}
865+
}
866+
}
867+
} catch {}
868+
return new NamedError.Unknown({ message: JSON.stringify(e) }, { cause: e }).toObject()
800869
}
801870
}
802871
}

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

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -784,3 +784,57 @@ describe("session.message-v2.toModelMessage", () => {
784784
])
785785
})
786786
})
787+
788+
describe("session.message-v2.fromError", () => {
789+
test("serializes response error codes", () => {
790+
const cases = [
791+
{
792+
code: "context_length_exceeded",
793+
message: "Input exceeds context window of this model",
794+
},
795+
{
796+
code: "insufficient_quota",
797+
message: "Quota exceeded. Check your plan and billing details.",
798+
},
799+
{
800+
code: "usage_not_included",
801+
message: "To use Codex with your ChatGPT plan, upgrade to Plus: https://chatgpt.com/explore/plus.",
802+
},
803+
{
804+
code: "invalid_prompt",
805+
message: "Invalid prompt from test",
806+
},
807+
]
808+
809+
cases.forEach((item) => {
810+
const input = {
811+
type: "error",
812+
error: {
813+
code: item.code,
814+
message: item.code === "invalid_prompt" ? item.message : undefined,
815+
},
816+
}
817+
const result = MessageV2.fromError(input, { providerID: "test" })
818+
819+
expect(result).toStrictEqual({
820+
name: "APIError",
821+
data: {
822+
message: item.message,
823+
isRetryable: false,
824+
responseBody: JSON.stringify(input),
825+
},
826+
})
827+
})
828+
})
829+
830+
test("serializes unknown inputs", () => {
831+
const result = MessageV2.fromError(123, { providerID: "test" })
832+
833+
expect(result).toStrictEqual({
834+
name: "UnknownError",
835+
data: {
836+
message: "123",
837+
},
838+
})
839+
})
840+
})

0 commit comments

Comments
 (0)