Просмотр исходного кода

fix: parse mid stream openai responses style errors to prevent infinite retries for errors that should STOP execution (#12768)

Aiden Cline 1 неделя назад
Родитель
Сommit
62f38087b8

+ 70 - 1
packages/opencode/src/session/message-v2.ts

@@ -796,7 +796,76 @@ export namespace MessageV2 {
       case e instanceof Error:
         return new NamedError.Unknown({ message: e.toString() }, { cause: e }).toObject()
       default:
-        return new NamedError.Unknown({ message: JSON.stringify(e) }, { cause: e })
+        try {
+          const json = iife(() => {
+            if (typeof e === "string") {
+              try {
+                return JSON.parse(e)
+              } catch {
+                return undefined
+              }
+            }
+
+            if (typeof e === "object" && e !== null) {
+              return e
+            }
+            return undefined
+          })
+          if (json) {
+            const responseBody = JSON.stringify(json)
+            // Handle Responses API mid stream style errors
+            if (json?.type === "error") {
+              switch (json?.error?.code) {
+                case "context_length_exceeded":
+                  return new MessageV2.APIError(
+                    {
+                      message: "Input exceeds context window of this model",
+                      isRetryable: false,
+                      responseBody,
+                    },
+                    {
+                      cause: e,
+                    },
+                  ).toObject()
+                case "insufficient_quota":
+                  return new MessageV2.APIError(
+                    {
+                      message: "Quota exceeded. Check your plan and billing details.",
+                      isRetryable: false,
+                      responseBody,
+                    },
+                    {
+                      cause: e,
+                    },
+                  ).toObject()
+                case "usage_not_included":
+                  return new MessageV2.APIError(
+                    {
+                      message:
+                        "To use Codex with your ChatGPT plan, upgrade to Plus: https://chatgpt.com/explore/plus.",
+                      isRetryable: false,
+                      responseBody,
+                    },
+                    {
+                      cause: e,
+                    },
+                  ).toObject()
+                case "invalid_prompt":
+                  return new MessageV2.APIError(
+                    {
+                      message: json?.error?.message || "Invalid prompt.",
+                      isRetryable: false,
+                      responseBody,
+                    },
+                    {
+                      cause: e,
+                    },
+                  ).toObject()
+              }
+            }
+          }
+        } catch {}
+        return new NamedError.Unknown({ message: JSON.stringify(e) }, { cause: e }).toObject()
     }
   }
 }

+ 54 - 0
packages/opencode/test/session/message-v2.test.ts

@@ -784,3 +784,57 @@ describe("session.message-v2.toModelMessage", () => {
     ])
   })
 })
+
+describe("session.message-v2.fromError", () => {
+  test("serializes response error codes", () => {
+    const cases = [
+      {
+        code: "context_length_exceeded",
+        message: "Input exceeds context window of this model",
+      },
+      {
+        code: "insufficient_quota",
+        message: "Quota exceeded. Check your plan and billing details.",
+      },
+      {
+        code: "usage_not_included",
+        message: "To use Codex with your ChatGPT plan, upgrade to Plus: https://chatgpt.com/explore/plus.",
+      },
+      {
+        code: "invalid_prompt",
+        message: "Invalid prompt from test",
+      },
+    ]
+
+    cases.forEach((item) => {
+      const input = {
+        type: "error",
+        error: {
+          code: item.code,
+          message: item.code === "invalid_prompt" ? item.message : undefined,
+        },
+      }
+      const result = MessageV2.fromError(input, { providerID: "test" })
+
+      expect(result).toStrictEqual({
+        name: "APIError",
+        data: {
+          message: item.message,
+          isRetryable: false,
+          responseBody: JSON.stringify(input),
+        },
+      })
+    })
+  })
+
+  test("serializes unknown inputs", () => {
+    const result = MessageV2.fromError(123, { providerID: "test" })
+
+    expect(result).toStrictEqual({
+      name: "UnknownError",
+      data: {
+        message: "123",
+      },
+    })
+  })
+})