mirror of
https://gitee.com/zhangsken/opencode.git
synced 2026-10-16 10:30:01 +08:00
feat(core): preserve provider response body in session errors (#52136)
This commit is contained in:
@@ -710,7 +710,12 @@ export type SessionLogOutput =
|
||||
| undefined
|
||||
readonly data: {
|
||||
readonly sessionID: Session.ID
|
||||
readonly error: { readonly type: string; readonly message: string; readonly status?: number | undefined }
|
||||
readonly error: {
|
||||
readonly type: string
|
||||
readonly message: string
|
||||
readonly status?: number | undefined
|
||||
readonly response?: { readonly body: string } | undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
| {
|
||||
@@ -902,7 +907,12 @@ export type SessionLogOutput =
|
||||
readonly data: {
|
||||
readonly sessionID: Session.ID
|
||||
readonly assistantMessageID: SessionMessage.ID
|
||||
readonly error: { readonly type: string; readonly message: string; readonly status?: number | undefined }
|
||||
readonly error: {
|
||||
readonly type: string
|
||||
readonly message: string
|
||||
readonly status?: number | undefined
|
||||
readonly response?: { readonly body: string } | undefined
|
||||
}
|
||||
readonly finish?: "content-filter" | undefined
|
||||
readonly rawFinish?: string | undefined
|
||||
readonly providerState?: SessionMessage.ProviderState | undefined
|
||||
@@ -1112,7 +1122,12 @@ export type SessionLogOutput =
|
||||
readonly sessionID: Session.ID
|
||||
readonly assistantMessageID: SessionMessage.ID
|
||||
readonly id: string
|
||||
readonly error: { readonly type: string; readonly message: string; readonly status?: number | undefined }
|
||||
readonly error: {
|
||||
readonly type: string
|
||||
readonly message: string
|
||||
readonly status?: number | undefined
|
||||
readonly response?: { readonly body: string } | undefined
|
||||
}
|
||||
readonly content?:
|
||||
| readonly [
|
||||
(
|
||||
@@ -1157,7 +1172,12 @@ export type SessionLogOutput =
|
||||
readonly assistantMessageID: SessionMessage.ID
|
||||
readonly attempt: number
|
||||
readonly at: number
|
||||
readonly error: { readonly type: string; readonly message: string; readonly status?: number | undefined }
|
||||
readonly error: {
|
||||
readonly type: string
|
||||
readonly message: string
|
||||
readonly status?: number | undefined
|
||||
readonly response?: { readonly body: string } | undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
| {
|
||||
@@ -1238,7 +1258,12 @@ export type SessionLogOutput =
|
||||
readonly data: {
|
||||
readonly sessionID: Session.ID
|
||||
readonly reason: "auto" | "manual"
|
||||
readonly error: { readonly type: string; readonly message: string; readonly status?: number | undefined }
|
||||
readonly error: {
|
||||
readonly type: string
|
||||
readonly message: string
|
||||
readonly status?: number | undefined
|
||||
readonly response?: { readonly body: string } | undefined
|
||||
}
|
||||
readonly inputID?: SessionMessage.ID | undefined
|
||||
readonly cost?: (number & Brand.Brand<"Money.USD">) | undefined
|
||||
readonly tokens?:
|
||||
|
||||
@@ -133,7 +133,7 @@ export type ToolTextContent = { type: "text"; text: string }
|
||||
|
||||
export type ToolFileContent = { type: "file"; uri: string; mime: string; name?: string | null }
|
||||
|
||||
export type SessionStructuredError = { type: string; message: string; status?: number }
|
||||
export type SessionStructuredError = { type: string; message: string; status?: number; response?: { body: string } }
|
||||
|
||||
export type SessionMessageCompactionRunning = {
|
||||
type: "compaction"
|
||||
@@ -3183,7 +3183,12 @@ export type SessionImportInput = {
|
||||
| {
|
||||
readonly status: "error"
|
||||
readonly input: { readonly [x: string]: JsonValue }
|
||||
readonly error: { readonly type: string; readonly message: string; readonly status?: number }
|
||||
readonly error: {
|
||||
readonly type: string
|
||||
readonly message: string
|
||||
readonly status?: number
|
||||
readonly response?: { readonly body: string }
|
||||
}
|
||||
readonly content?: readonly [
|
||||
(
|
||||
| { readonly type: "text"; readonly text: string }
|
||||
@@ -3220,11 +3225,21 @@ export type SessionImportInput = {
|
||||
readonly reasoning: number
|
||||
readonly cache: { readonly read: number; readonly write: number }
|
||||
}
|
||||
readonly error?: { readonly type: string; readonly message: string; readonly status?: number }
|
||||
readonly error?: {
|
||||
readonly type: string
|
||||
readonly message: string
|
||||
readonly status?: number
|
||||
readonly response?: { readonly body: string }
|
||||
}
|
||||
readonly retry?: {
|
||||
readonly attempt: number
|
||||
readonly at: number
|
||||
readonly error: { readonly type: string; readonly message: string; readonly status?: number }
|
||||
readonly error: {
|
||||
readonly type: string
|
||||
readonly message: string
|
||||
readonly status?: number
|
||||
readonly response?: { readonly body: string }
|
||||
}
|
||||
}
|
||||
}
|
||||
| (
|
||||
@@ -3276,7 +3291,12 @@ export type SessionImportInput = {
|
||||
readonly time: { readonly created: number }
|
||||
readonly status: "failed"
|
||||
readonly reason: "auto" | "manual"
|
||||
readonly error: { readonly type: string; readonly message: string; readonly status?: number }
|
||||
readonly error: {
|
||||
readonly type: string
|
||||
readonly message: string
|
||||
readonly status?: number
|
||||
readonly response?: { readonly body: string }
|
||||
}
|
||||
readonly cost?: number
|
||||
readonly tokens?: {
|
||||
readonly input: number
|
||||
@@ -3500,7 +3520,12 @@ export type SessionImportInput = {
|
||||
| {
|
||||
readonly status: "error"
|
||||
readonly input: { readonly [x: string]: JsonValue }
|
||||
readonly error: { readonly type: string; readonly message: string; readonly status?: number }
|
||||
readonly error: {
|
||||
readonly type: string
|
||||
readonly message: string
|
||||
readonly status?: number
|
||||
readonly response?: { readonly body: string }
|
||||
}
|
||||
readonly content?: readonly [
|
||||
(
|
||||
| { readonly type: "text"; readonly text: string }
|
||||
@@ -3537,11 +3562,21 @@ export type SessionImportInput = {
|
||||
readonly reasoning: number
|
||||
readonly cache: { readonly read: number; readonly write: number }
|
||||
}
|
||||
readonly error?: { readonly type: string; readonly message: string; readonly status?: number }
|
||||
readonly error?: {
|
||||
readonly type: string
|
||||
readonly message: string
|
||||
readonly status?: number
|
||||
readonly response?: { readonly body: string }
|
||||
}
|
||||
readonly retry?: {
|
||||
readonly attempt: number
|
||||
readonly at: number
|
||||
readonly error: { readonly type: string; readonly message: string; readonly status?: number }
|
||||
readonly error: {
|
||||
readonly type: string
|
||||
readonly message: string
|
||||
readonly status?: number
|
||||
readonly response?: { readonly body: string }
|
||||
}
|
||||
}
|
||||
}
|
||||
| (
|
||||
@@ -3593,7 +3628,12 @@ export type SessionImportInput = {
|
||||
readonly time: { readonly created: number }
|
||||
readonly status: "failed"
|
||||
readonly reason: "auto" | "manual"
|
||||
readonly error: { readonly type: string; readonly message: string; readonly status?: number }
|
||||
readonly error: {
|
||||
readonly type: string
|
||||
readonly message: string
|
||||
readonly status?: number
|
||||
readonly response?: { readonly body: string }
|
||||
}
|
||||
readonly cost?: number
|
||||
readonly tokens?: {
|
||||
readonly input: number
|
||||
@@ -3817,7 +3857,12 @@ export type SessionImportInput = {
|
||||
| {
|
||||
readonly status: "error"
|
||||
readonly input: { readonly [x: string]: JsonValue }
|
||||
readonly error: { readonly type: string; readonly message: string; readonly status?: number }
|
||||
readonly error: {
|
||||
readonly type: string
|
||||
readonly message: string
|
||||
readonly status?: number
|
||||
readonly response?: { readonly body: string }
|
||||
}
|
||||
readonly content?: readonly [
|
||||
(
|
||||
| { readonly type: "text"; readonly text: string }
|
||||
@@ -3854,11 +3899,21 @@ export type SessionImportInput = {
|
||||
readonly reasoning: number
|
||||
readonly cache: { readonly read: number; readonly write: number }
|
||||
}
|
||||
readonly error?: { readonly type: string; readonly message: string; readonly status?: number }
|
||||
readonly error?: {
|
||||
readonly type: string
|
||||
readonly message: string
|
||||
readonly status?: number
|
||||
readonly response?: { readonly body: string }
|
||||
}
|
||||
readonly retry?: {
|
||||
readonly attempt: number
|
||||
readonly at: number
|
||||
readonly error: { readonly type: string; readonly message: string; readonly status?: number }
|
||||
readonly error: {
|
||||
readonly type: string
|
||||
readonly message: string
|
||||
readonly status?: number
|
||||
readonly response?: { readonly body: string }
|
||||
}
|
||||
}
|
||||
}
|
||||
| (
|
||||
@@ -3910,7 +3965,12 @@ export type SessionImportInput = {
|
||||
readonly time: { readonly created: number }
|
||||
readonly status: "failed"
|
||||
readonly reason: "auto" | "manual"
|
||||
readonly error: { readonly type: string; readonly message: string; readonly status?: number }
|
||||
readonly error: {
|
||||
readonly type: string
|
||||
readonly message: string
|
||||
readonly status?: number
|
||||
readonly response?: { readonly body: string }
|
||||
}
|
||||
readonly cost?: number
|
||||
readonly tokens?: {
|
||||
readonly input: number
|
||||
|
||||
@@ -69,5 +69,10 @@ export function toSessionError(cause: unknown): SessionError.Error {
|
||||
|
||||
function providerError(type: string, reason: AIError["reason"]): SessionError.Error {
|
||||
const status = reason.http?.status
|
||||
return { type, message: reason.message, ...(status === undefined ? {} : { status }) }
|
||||
return {
|
||||
type,
|
||||
message: reason.message,
|
||||
...(status === undefined ? {} : { status }),
|
||||
...(reason.body === undefined ? {} : { response: { body: reason.body } }),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,6 +110,7 @@ describe("toSessionError", () => {
|
||||
type: "provider.invalid-request",
|
||||
message: "too large",
|
||||
status: 413,
|
||||
response: { body: '{"error":"context limit"}' },
|
||||
})
|
||||
expect(
|
||||
toSessionError(
|
||||
@@ -127,6 +128,14 @@ describe("toSessionError", () => {
|
||||
})
|
||||
})
|
||||
|
||||
test("preserves provider response body without HTTP status", () => {
|
||||
expect(toSessionError(llm(new RateLimitError({ message: "Slow down", body: '{"error":"rate limit"}' })))).toEqual({
|
||||
type: "provider.rate-limit",
|
||||
message: "Slow down",
|
||||
response: { body: '{"error":"rate limit"}' },
|
||||
})
|
||||
})
|
||||
|
||||
test("preserves unresolved provider endpoint errors", () => {
|
||||
const error = new ModelResolver.UnresolvedProviderVariablesError({
|
||||
providerID: Provider.ID.make("cloudflare-workers-ai"),
|
||||
|
||||
@@ -8,4 +8,5 @@ export const Error = Schema.Struct({
|
||||
type: Schema.String,
|
||||
message: Schema.String,
|
||||
status: Schema.Int.check(Schema.isBetween({ minimum: 100, maximum: 599 })).pipe(optional),
|
||||
response: Schema.Struct({ body: Schema.String }).pipe(optional),
|
||||
}).annotate({ identifier: "Session.StructuredError" })
|
||||
|
||||
@@ -11,6 +11,7 @@ describe("SessionError", () => {
|
||||
test("round trips current and future error types through JSON", () => {
|
||||
const values: SessionError.Error[] = [
|
||||
{ type: "provider.rate-limit", message: "Slow down" },
|
||||
{ type: "provider.rate-limit", message: "Slow down", status: 429, response: { body: '{"error":{}}' } },
|
||||
{ type: "provider.auth", message: "Authentication failed" },
|
||||
{ type: "provider.future-condition", message: "A future provider failure" },
|
||||
{ type: "unknown", message: "Unexpected" },
|
||||
@@ -36,6 +37,9 @@ describe("SessionError", () => {
|
||||
test("rejects missing envelope fields", () => {
|
||||
expect(() => Schema.decodeUnknownSync(SessionError.Error)({ type: "provider.auth" })).toThrow()
|
||||
expect(() => Schema.decodeUnknownSync(SessionError.Error)({ message: "Missing type" })).toThrow()
|
||||
expect(() =>
|
||||
Schema.decodeUnknownSync(SessionError.Error)({ type: "provider.auth", message: "Failed", response: {} }),
|
||||
).toThrow()
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user