diff --git a/packages/client/src/effect/api/api.ts b/packages/client/src/effect/api/api.ts index a4890a7b9b6..d3cf01ed138 100644 --- a/packages/client/src/effect/api/api.ts +++ b/packages/client/src/effect/api/api.ts @@ -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?: diff --git a/packages/client/src/promise/generated/types.ts b/packages/client/src/promise/generated/types.ts index 9682ad47149..1704e549c4d 100644 --- a/packages/client/src/promise/generated/types.ts +++ b/packages/client/src/promise/generated/types.ts @@ -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 diff --git a/packages/core/src/session/to-session-error.ts b/packages/core/src/session/to-session-error.ts index 8698c8a5645..61cae41132b 100644 --- a/packages/core/src/session/to-session-error.ts +++ b/packages/core/src/session/to-session-error.ts @@ -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 } }), + } } diff --git a/packages/core/test/session-error.test.ts b/packages/core/test/session-error.test.ts index 34b09cf96a8..4c1d6ae1d76 100644 --- a/packages/core/test/session-error.test.ts +++ b/packages/core/test/session-error.test.ts @@ -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"), diff --git a/packages/schema/src/session-error.ts b/packages/schema/src/session-error.ts index 279044ac89b..e9bb70570fe 100644 --- a/packages/schema/src/session-error.ts +++ b/packages/schema/src/session-error.ts @@ -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" }) diff --git a/packages/schema/test/session-error.test.ts b/packages/schema/test/session-error.test.ts index 0d851bf430f..9eeac44dab1 100644 --- a/packages/schema/test/session-error.test.ts +++ b/packages/schema/test/session-error.test.ts @@ -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() }) })