|
|
@@ -7,8 +7,7 @@ import { LSP } from "../lsp"
|
|
|
import { Snapshot } from "@/snapshot"
|
|
|
import { fn } from "@/util/fn"
|
|
|
import { Storage } from "@/storage/storage"
|
|
|
-import { ProviderTransform } from "@/provider/transform"
|
|
|
-import { STATUS_CODES } from "http"
|
|
|
+import { ProviderError } from "@/provider/error"
|
|
|
import { iife } from "@/util/iife"
|
|
|
import { type SystemError } from "bun"
|
|
|
import type { Provider } from "@/provider/provider"
|
|
|
@@ -35,6 +34,10 @@ export namespace MessageV2 {
|
|
|
}),
|
|
|
)
|
|
|
export type APIError = z.infer<typeof APIError.Schema>
|
|
|
+ export const ContextOverflowError = NamedError.create(
|
|
|
+ "ContextOverflowError",
|
|
|
+ z.object({ message: z.string(), responseBody: z.string().optional() }),
|
|
|
+ )
|
|
|
|
|
|
const PartBase = z.object({
|
|
|
id: z.string(),
|
|
|
@@ -361,6 +364,7 @@ export namespace MessageV2 {
|
|
|
NamedError.Unknown.Schema,
|
|
|
OutputLengthError.Schema,
|
|
|
AbortedError.Schema,
|
|
|
+ ContextOverflowError.Schema,
|
|
|
APIError.Schema,
|
|
|
])
|
|
|
.optional(),
|
|
|
@@ -711,13 +715,6 @@ export namespace MessageV2 {
|
|
|
return result
|
|
|
}
|
|
|
|
|
|
- const isOpenAiErrorRetryable = (e: APICallError) => {
|
|
|
- const status = e.statusCode
|
|
|
- if (!status) return e.isRetryable
|
|
|
- // openai sometimes returns 404 for models that are actually available
|
|
|
- return status === 404 || e.isRetryable
|
|
|
- }
|
|
|
-
|
|
|
export function fromError(e: unknown, ctx: { providerID: string }) {
|
|
|
switch (true) {
|
|
|
case e instanceof DOMException && e.name === "AbortError":
|
|
|
@@ -751,45 +748,28 @@ export namespace MessageV2 {
|
|
|
{ cause: e },
|
|
|
).toObject()
|
|
|
case APICallError.isInstance(e):
|
|
|
- const message = iife(() => {
|
|
|
- let msg = e.message
|
|
|
- if (msg === "") {
|
|
|
- if (e.responseBody) return e.responseBody
|
|
|
- if (e.statusCode) {
|
|
|
- const err = STATUS_CODES[e.statusCode]
|
|
|
- if (err) return err
|
|
|
- }
|
|
|
- return "Unknown error"
|
|
|
- }
|
|
|
- const transformed = ProviderTransform.error(ctx.providerID, e)
|
|
|
- if (transformed !== msg) {
|
|
|
- return transformed
|
|
|
- }
|
|
|
- if (!e.responseBody || (e.statusCode && msg !== STATUS_CODES[e.statusCode])) {
|
|
|
- return msg
|
|
|
- }
|
|
|
-
|
|
|
- try {
|
|
|
- const body = JSON.parse(e.responseBody)
|
|
|
- // try to extract common error message fields
|
|
|
- const errMsg = body.message || body.error || body.error?.message
|
|
|
- if (errMsg && typeof errMsg === "string") {
|
|
|
- return `${msg}: ${errMsg}`
|
|
|
- }
|
|
|
- } catch {}
|
|
|
-
|
|
|
- return `${msg}: ${e.responseBody}`
|
|
|
- }).trim()
|
|
|
+ const parsed = ProviderError.parseAPICallError({
|
|
|
+ providerID: ctx.providerID,
|
|
|
+ error: e,
|
|
|
+ })
|
|
|
+ if (parsed.type === "context_overflow") {
|
|
|
+ return new MessageV2.ContextOverflowError(
|
|
|
+ {
|
|
|
+ message: parsed.message,
|
|
|
+ responseBody: parsed.responseBody,
|
|
|
+ },
|
|
|
+ { cause: e },
|
|
|
+ ).toObject()
|
|
|
+ }
|
|
|
|
|
|
- const metadata = e.url ? { url: e.url } : undefined
|
|
|
return new MessageV2.APIError(
|
|
|
{
|
|
|
- message,
|
|
|
- statusCode: e.statusCode,
|
|
|
- isRetryable: ctx.providerID.startsWith("openai") ? isOpenAiErrorRetryable(e) : e.isRetryable,
|
|
|
- responseHeaders: e.responseHeaders,
|
|
|
- responseBody: e.responseBody,
|
|
|
- metadata,
|
|
|
+ message: parsed.message,
|
|
|
+ statusCode: parsed.statusCode,
|
|
|
+ isRetryable: parsed.isRetryable,
|
|
|
+ responseHeaders: parsed.responseHeaders,
|
|
|
+ responseBody: parsed.responseBody,
|
|
|
+ metadata: parsed.metadata,
|
|
|
},
|
|
|
{ cause: e },
|
|
|
).toObject()
|
|
|
@@ -797,72 +777,27 @@ export namespace MessageV2 {
|
|
|
return new NamedError.Unknown({ message: e.toString() }, { cause: e }).toObject()
|
|
|
default:
|
|
|
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()
|
|
|
- }
|
|
|
+ const parsed = ProviderError.parseStreamError(e)
|
|
|
+ if (parsed) {
|
|
|
+ if (parsed.type === "context_overflow") {
|
|
|
+ return new MessageV2.ContextOverflowError(
|
|
|
+ {
|
|
|
+ message: parsed.message,
|
|
|
+ responseBody: parsed.responseBody,
|
|
|
+ },
|
|
|
+ { cause: e },
|
|
|
+ ).toObject()
|
|
|
}
|
|
|
+ return new MessageV2.APIError(
|
|
|
+ {
|
|
|
+ message: parsed.message,
|
|
|
+ isRetryable: parsed.isRetryable,
|
|
|
+ responseBody: parsed.responseBody,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ cause: e,
|
|
|
+ },
|
|
|
+ ).toObject()
|
|
|
}
|
|
|
} catch {}
|
|
|
return new NamedError.Unknown({ message: JSON.stringify(e) }, { cause: e }).toObject()
|