tweak: add new ContextOverflowError type (#12777)

This commit is contained in:
Aiden Cline
2026-02-08 23:54:01 -06:00
committed by GitHub
parent d40dffb854
commit 99ea1351ce
6 changed files with 350 additions and 126 deletions

View File

@@ -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
}
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()
}
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 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()

View File

@@ -59,6 +59,9 @@ export namespace SessionRetry {
}
export function retryable(error: ReturnType<NamedError["toObject"]>) {
// DO NOT retry context overflow errors
if (MessageV2.ContextOverflowError.isInstance(error)) return undefined
if (MessageV2.APIError.isInstance(error)) {
if (!error.data.isRetryable) return undefined
return error.data.message.includes("Overloaded") ? "Provider is overloaded" : error.data.message