feat: retry parts (#3369)

This commit is contained in:
Aiden Cline
2025-10-22 18:31:36 -05:00
committed by GitHub
parent 9def7cff2d
commit 7c7ebb0a9d
5 changed files with 487 additions and 214 deletions

View File

@@ -2,7 +2,7 @@ import z from "zod/v4"
import { Bus } from "../bus"
import { NamedError } from "../util/error"
import { Message } from "./message"
import { convertToModelMessages, type ModelMessage, type UIMessage } from "ai"
import { APICallError, convertToModelMessages, LoadAPIKeyError, type ModelMessage, type UIMessage } from "ai"
import { Identifier } from "../id/id"
import { LSP } from "../lsp"
import { Snapshot } from "@/snapshot"
@@ -18,6 +18,17 @@ export namespace MessageV2 {
message: z.string(),
}),
)
export const APIError = NamedError.create(
"APIError",
z.object({
message: z.string(),
statusCode: z.number().optional(),
isRetryable: z.boolean(),
responseHeaders: z.record(z.string(), z.string()).optional(),
responseBody: z.string().optional(),
}),
)
export type APIError = z.infer<typeof APIError.Schema>
const PartBase = z.object({
id: z.string(),
@@ -130,6 +141,18 @@ export namespace MessageV2 {
})
export type AgentPart = z.infer<typeof AgentPart>
export const RetryPart = PartBase.extend({
type: z.literal("retry"),
attempt: z.number(),
error: APIError.Schema,
time: z.object({
created: z.number(),
}),
}).meta({
ref: "RetryPart",
})
export type RetryPart = z.infer<typeof RetryPart>
export const StepStartPart = PartBase.extend({
type: z.literal("step-start"),
snapshot: z.string().optional(),
@@ -265,6 +288,7 @@ export namespace MessageV2 {
SnapshotPart,
PatchPart,
AgentPart,
RetryPart,
])
.meta({
ref: "Part",
@@ -283,6 +307,7 @@ export namespace MessageV2 {
NamedError.Unknown.Schema,
OutputLengthError.Schema,
AbortedError.Schema,
APIError.Schema,
])
.optional(),
system: z.string().array(),
@@ -610,4 +635,41 @@ export namespace MessageV2 {
if (i === -1) return msgs.slice()
return msgs.slice(i)
}
export function fromError(e: unknown, ctx: { providerID: string }) {
switch (true) {
case e instanceof DOMException && e.name === "AbortError":
return new MessageV2.AbortedError(
{ message: e.message },
{
cause: e,
},
).toObject()
case MessageV2.OutputLengthError.isInstance(e):
return e
case LoadAPIKeyError.isInstance(e):
return new MessageV2.AuthError(
{
providerID: ctx.providerID,
message: e.message,
},
{ cause: e },
).toObject()
case APICallError.isInstance(e):
return new MessageV2.APIError(
{
message: e.message,
statusCode: e.statusCode,
isRetryable: e.isRetryable,
responseHeaders: e.responseHeaders,
responseBody: e.responseBody,
},
{ cause: e },
).toObject()
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 })
}
}
}