From f8e5b12f41e03345488fa44d1ba5ce1346ae7117 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Wed, 24 Jun 2026 22:43:18 -0400 Subject: [PATCH] fix(core): preserve provider session failures --- packages/core/src/session/runner/llm.ts | 30 ++++- .../src/session/runner/publish-llm-event.ts | 37 +++++- .../core/src/session/runner/to-llm-message.ts | 17 ++- .../core/test/session-runner-message.test.ts | 77 ++++++++++++ packages/core/test/session-runner.test.ts | 111 +++++++++++++++++- packages/core/test/shared-schema.test.ts | 16 +++ .../llm/src/protocols/openai-responses.ts | 2 + packages/llm/src/schema/errors.ts | 14 +++ packages/llm/src/schema/events.ts | 4 +- .../test/provider/openai-responses.test.ts | 9 +- packages/schema/src/session-event.ts | 6 +- packages/schema/src/session-message.ts | 29 ++++- packages/sdk/js/src/v2/gen/types.gen.ts | 29 ++++- 13 files changed, 359 insertions(+), 22 deletions(-) diff --git a/packages/core/src/session/runner/llm.ts b/packages/core/src/session/runner/llm.ts index 9e29ae71ee..f1d6023669 100644 --- a/packages/core/src/session/runner/llm.ts +++ b/packages/core/src/session/runner/llm.ts @@ -6,6 +6,7 @@ import { Message, SystemPart, isContextOverflowFailure, + type LLMErrorReason, type ProviderErrorEvent, } from "@opencode-ai/llm" import { Cause, DateTime, Effect, FiberSet, Layer, Option, Semaphore, Stream } from "effect" @@ -32,7 +33,7 @@ import { SessionSchema } from "../schema" import { SessionStore } from "../store" import { type RunError, Service } from "./index" import { SessionRunnerModel } from "./model" -import { createLLMEventPublisher } from "./publish-llm-event" +import { createLLMEventPublisher, providerError } from "./publish-llm-event" import { toLLMMessages } from "./to-llm-message" import { MAX_STEPS_PROMPT } from "./max-steps" import { Snapshot } from "../../snapshot" @@ -87,6 +88,29 @@ import { Snapshot } from "../../snapshot" * explicit loop starts the next provider turn after local settlement. Configured agent step limits bound the loop. */ +const providerCategories = { + InvalidRequest: "invalid-request", + NoRoute: "no-route", + Authentication: "authentication", + RateLimit: "rate-limit", + QuotaExceeded: "quota-exceeded", + ContentPolicy: "content-policy", + ProviderInternal: "provider-internal", + Transport: "transport", + InvalidProviderOutput: "invalid-provider-output", + UnknownProvider: "unknown", +} as const satisfies Record[0]["category"]> + +const toSessionProviderError = (reason: LLMErrorReason) => + providerError({ + category: providerCategories[reason._tag], + status: + ("http" in reason ? reason.http?.response?.status : undefined) ?? + ("status" in reason ? reason.status : undefined), + retryable: reason.retryable, + retryAfterMs: "retryAfterMs" in reason ? reason.retryAfterMs : undefined, + }) + export const layer = Layer.effect( Service, Effect.gen(function* () { @@ -283,7 +307,7 @@ export const layer = Layer.effect( const llmFailure = failure instanceof LLMError ? failure : undefined if (llmFailure && !publisher.hasProviderError()) { yield* withPublication(publisher.failUnsettledTools("Provider did not return a tool result", true)) - yield* withPublication(publisher.failAssistant(llmFailure.reason.message)) + yield* withPublication(publisher.failAssistant(toSessionProviderError(llmFailure.reason))) } if (stream._tag === "Failure" && Cause.hasInterrupts(stream.cause)) yield* FiberSet.clear(toolFibers) const settled = yield* restore(awaitToolFibers(toolFibers)).pipe(Effect.exit) @@ -299,7 +323,7 @@ export const layer = Layer.effect( yield* FiberSet.clear(toolFibers) yield* withPublication(publisher.failUnsettledTools("Tool execution interrupted")) if (publisher.hasActiveAssistant()) - yield* withPublication(publisher.failAssistant("Provider turn interrupted")) + yield* withPublication(publisher.failAssistant({ type: "unknown", message: "Provider turn interrupted" })) } if (settled._tag === "Failure" && !Cause.hasInterrupts(settled.cause)) { const failure = Cause.squash(settled.cause) diff --git a/packages/core/src/session/runner/publish-llm-event.ts b/packages/core/src/session/runner/publish-llm-event.ts index 33652a618c..21ed113d5d 100644 --- a/packages/core/src/session/runner/publish-llm-event.ts +++ b/packages/core/src/session/runner/publish-llm-event.ts @@ -50,6 +50,33 @@ const settledOutput = (value: ToolOutput | undefined, result: ToolResultValue): return { structured: record(settled.structured), content: settled.content } } +const providerMessages: Record = { + "invalid-request": "Provider rejected the request", + "no-route": "No provider route is available", + authentication: "Provider authentication failed", + "rate-limit": "Provider rate limit exceeded", + "quota-exceeded": "Provider quota exceeded", + "content-policy": "Provider content policy rejected the request", + "provider-internal": "Provider service error", + transport: "Provider connection failed", + "invalid-provider-output": "Provider returned an invalid response", + unknown: "Provider request failed", +} + +export const providerError = (input: { + readonly category: SessionMessage.ProviderErrorCategory + readonly status?: number + readonly retryable?: boolean + readonly retryAfterMs?: number +}): SessionMessage.ProviderError => ({ + type: "provider", + category: input.category, + message: providerMessages[input.category], + status: input.status, + retryable: input.retryable ?? (input.category === "rate-limit" || input.category === "provider-internal"), + retryAfterMs: input.retryAfterMs, +}) + /** Persist one provider turn without executing tools or starting a continuation turn. */ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input) => { const tools = new Map< @@ -196,7 +223,7 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input) yield* flushFragments() }) - const failAssistant = Effect.fnUntraced(function* (message: string) { + const failAssistant = Effect.fnUntraced(function* (error: SessionMessage.Error) { if (assistantFailed) return yield* flush() const assistantMessageID = yield* startAssistant() @@ -206,7 +233,7 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input) sessionID: input.sessionID, timestamp: yield* timestamp, assistantMessageID, - error: { type: "unknown", message }, + error, }) }) @@ -403,7 +430,11 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input) return case "provider-error": providerFailed = true - yield* failAssistant(event.message) + yield* failAssistant( + event.category === undefined + ? { type: "unknown", message: event.message } + : providerError({ category: event.category, status: event.status, retryable: event.retryable }), + ) return } }) diff --git a/packages/core/src/session/runner/to-llm-message.ts b/packages/core/src/session/runner/to-llm-message.ts index f0ce7eef7f..9314795681 100644 --- a/packages/core/src/session/runner/to-llm-message.ts +++ b/packages/core/src/session/runner/to-llm-message.ts @@ -70,16 +70,20 @@ const toolResult = (tool: SessionMessage.AssistantTool, providerMetadata: Provid const assistant = (message: SessionMessage.Assistant, model: Model) => { const sameModel = String(message.model.providerID) === String(model.provider) && String(message.model.id) === String(model.id) + const preserveProviderMetadata = sameModel && message.finish !== "error" const content = message.content.flatMap((item): ContentPart[] => { if (item.type === "text") return [{ type: "text", text: item.text }] if (item.type === "reasoning") - return sameModel + return preserveProviderMetadata ? [{ type: "reasoning", text: item.text, providerMetadata: item.providerMetadata }] : item.text.length > 0 ? [{ type: "text", text: item.text }] : [] - const call = toolCall(item, sameModel ? item.provider?.metadata : undefined) - const result = toolResult(item, sameModel ? (item.provider?.resultMetadata ?? item.provider?.metadata) : undefined) + const call = toolCall(item, preserveProviderMetadata ? item.provider?.metadata : undefined) + const result = toolResult( + item, + preserveProviderMetadata ? (item.provider?.resultMetadata ?? item.provider?.metadata) : undefined, + ) return item.provider?.executed === true && result ? [call, result] : [call] }) const meaningful = content.filter((part) => { @@ -89,7 +93,12 @@ const assistant = (message: SessionMessage.Assistant, model: Model) => { }) const results = message.content .filter((item): item is SessionMessage.AssistantTool => item.type === "tool" && item.provider?.executed !== true) - .map((item) => toolResult(item, sameModel ? (item.provider?.resultMetadata ?? item.provider?.metadata) : undefined)) + .map((item) => + toolResult( + item, + preserveProviderMetadata ? (item.provider?.resultMetadata ?? item.provider?.metadata) : undefined, + ), + ) .filter((message) => message !== undefined) .map(Message.tool) if (meaningful.length === 0) return results diff --git a/packages/core/test/session-runner-message.test.ts b/packages/core/test/session-runner-message.test.ts index 653a192a62..d5313d2d49 100644 --- a/packages/core/test/session-runner-message.test.ts +++ b/packages/core/test/session-runner-message.test.ts @@ -327,6 +327,83 @@ Recent work ]) }) + test("drops same-model provider metadata from errored turns without dropping useful output", () => { + const messages = toLLMMessages( + [ + SessionMessage.Assistant.make({ + id: id("assistant-error"), + type: "assistant", + agent: "build", + model: { id: ModelV2.ID.make("model"), providerID: ProviderV2.ID.make("provider") }, + finish: "error", + error: { type: "unknown", message: "Interrupted" }, + content: [ + SessionMessage.AssistantText.make({ type: "text", id: "text-error", text: "Partial answer" }), + SessionMessage.AssistantReasoning.make({ + type: "reasoning", + id: "reasoning-error", + text: "Visible thought", + providerMetadata: { openai: { reasoningEncryptedContent: "stale-secret" } }, + }), + SessionMessage.AssistantTool.make({ + type: "tool", + id: "tool-error", + name: "read", + provider: { + executed: false, + metadata: { openai: { itemId: "stale-call" } }, + resultMetadata: { openai: { itemId: "stale-result" } }, + }, + state: SessionMessage.ToolStateError.make({ + status: "error", + input: { path: "README.md" }, + content: [{ type: "text", text: "Permission denied" }], + structured: {}, + error: { type: "unknown", message: "Permission denied" }, + }), + time: { created, completed: created }, + }), + ], + time: { created, completed: created }, + }), + ], + model, + ) + + expect(messages).toHaveLength(2) + expect(messages[0]?.content).toEqual([ + { type: "text", text: "Partial answer" }, + { type: "text", text: "Visible thought" }, + { + type: "tool-call", + id: "tool-error", + name: "read", + input: { path: "README.md" }, + providerExecuted: false, + providerMetadata: undefined, + }, + ]) + expect(messages[1]?.content).toEqual([ + { + type: "tool-result", + id: "tool-error", + name: "read", + result: { + type: "error", + value: { + error: { type: "unknown", message: "Permission denied" }, + content: [{ type: "text", text: "Permission denied" }], + structured: {}, + }, + }, + providerExecuted: false, + cache: undefined, + metadata: undefined, + providerMetadata: undefined, + }, + ]) + }) + test("drops provider-native continuation metadata after a model switch", () => { const messages = toLLMMessages( [ diff --git a/packages/core/test/session-runner.test.ts b/packages/core/test/session-runner.test.ts index 6df50ac575..24f00f9b37 100644 --- a/packages/core/test/session-runner.test.ts +++ b/packages/core/test/session-runner.test.ts @@ -4,6 +4,11 @@ import { LLMError, LLMEvent, Model, + HttpContext, + HttpRateLimitDetails, + HttpRequestDetails, + HttpResponseDetails, + RateLimitReason, TransportReason, InvalidRequestReason, type LLMClientShape, @@ -519,7 +524,7 @@ const verifyPartialFlushOnFailure = (kind: FragmentKind) => { type: "assistant", finish: "error", - error: { type: "unknown", message: "Provider unavailable" }, + error: { type: "provider", category: "transport", message: "Provider connection failed", retryable: false }, content: [fixture.expectedContent], }, ]) @@ -2984,11 +2989,106 @@ describe("SessionRunnerLLM", () => { yield* replaySessionProjection(sessionID) expect(yield* session.context(sessionID)).toMatchObject([ { type: "user", text: "Fail raw stream durably" }, - { type: "assistant", finish: "error", error: { type: "unknown", message: "Provider unavailable" } }, + { + type: "assistant", + finish: "error", + error: { type: "provider", category: "transport", message: "Provider connection failed", retryable: false }, + }, ]) }), ) + it.effect("preserves sanitized HTTP rate-limit details in the durable event and projection", () => + Effect.gen(function* () { + yield* setup + const session = yield* SessionV2.Service + const db = yield* Database.Service + yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Fail with rate limit" }), resume: false }) + const failure = new LLMError({ + module: "RequestExecutor", + method: "execute", + reason: new RateLimitReason({ + message: "secret provider response message", + retryAfterMs: 12_000, + rateLimit: new HttpRateLimitDetails({ retryAfterMs: 12_000, limit: { requests: "secret-limit" } }), + http: new HttpContext({ + request: new HttpRequestDetails({ + method: "POST", + url: "https://secret.example/v1/responses?api_key=credential", + headers: { authorization: "Bearer credential" }, + }), + response: new HttpResponseDetails({ status: 429, headers: { "x-secret": "secret-header" } }), + body: '{"secret":"provider body"}', + requestId: "secret-request-id", + }), + }), + }) + responseStream = Stream.fail(failure) + + expect(yield* session.resume(sessionID).pipe(Effect.flip)).toBe(failure) + + const event = yield* db.db + .select({ data: EventTable.data }) + .from(EventTable) + .where(eq(EventTable.type, EventV2.versionedType(SessionEvent.Step.Failed.type, 2))) + .get() + .pipe(Effect.orDie) + const expected = { + type: "provider", + category: "rate-limit", + message: "Provider rate limit exceeded", + status: 429, + retryable: true, + retryAfterMs: 12_000, + } + expect(event?.data.error).toEqual(expected) + expect(yield* session.context(sessionID)).toMatchObject([ + { type: "user", text: "Fail with rate limit" }, + { type: "assistant", finish: "error", error: expected }, + ]) + expect(JSON.stringify(event)).not.toMatch( + /secret provider|secret\.example|credential|secret-header|provider body|secret-request-id|secret-limit/, + ) + expect(JSON.stringify(yield* session.context(sessionID))).not.toMatch( + /secret provider|secret\.example|credential|secret-header|provider body|secret-request-id|secret-limit/, + ) + }), + ) + + it.effect("projects in-band rate limits without fabricating an HTTP status", () => + Effect.gen(function* () { + yield* setup + const session = yield* SessionV2.Service + yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Fail in band" }), resume: false }) + response = [ + LLMEvent.providerError({ + message: "rate_limit_exceeded: secret provider message", + category: "rate-limit", + retryable: true, + }), + ] + + yield* session.resume(sessionID) + + expect(yield* session.context(sessionID)).toMatchObject([ + { type: "user", text: "Fail in band" }, + { + type: "assistant", + finish: "error", + error: { + type: "provider", + category: "rate-limit", + message: "Provider rate limit exceeded", + retryable: true, + }, + }, + ]) + const assistant = (yield* session.context(sessionID))[1] + expect(assistant?.type === "assistant" ? assistant.error : undefined).not.toHaveProperty("status") + expect(JSON.stringify(assistant)).not.toContain("secret provider message") + }), + ) + it.effect("does not continue automatically after a provider error follows a local tool call", () => Effect.gen(function* () { yield* setup @@ -3100,7 +3200,12 @@ describe("SessionRunnerLLM", () => { { type: "assistant", finish: "error", - error: { type: "unknown", message: "Provider unavailable" }, + error: { + type: "provider", + category: "transport", + message: "Provider connection failed", + retryable: false, + }, content: [{ type: "tool", id: "call-hosted-raw-failure", state: { status: "error" } }], }, ]) diff --git a/packages/core/test/shared-schema.test.ts b/packages/core/test/shared-schema.test.ts index 81b813a878..25dd611a14 100644 --- a/packages/core/test/shared-schema.test.ts +++ b/packages/core/test/shared-schema.test.ts @@ -122,6 +122,8 @@ test("Core reuses the canonical shared schemas", async () => { [coreSessionInput.Admitted, SessionInput.Admitted], [coreSessionMessage.ID, SessionMessage.ID], [coreSessionMessage.UnknownError, SessionMessage.UnknownError], + [coreSessionMessage.ProviderError, SessionMessage.ProviderError], + [coreSessionMessage.Error, SessionMessage.Error], [coreSessionMessage.AgentSwitched, SessionMessage.AgentSwitched], [coreSessionMessage.ModelSwitched, SessionMessage.ModelSwitched], [coreSessionMessage.User, SessionMessage.User], @@ -178,3 +180,17 @@ test("shared record schemas construct and decode plain objects", () => { expect(Prompt.fromUserMessage({ text: "hello" })).toEqual(made) expect(Workspace.ID.ascending("")).toStartWith("wrk_") }) + +test("assistant errors retain legacy unknown decode compatibility", () => { + const assistant = Schema.decodeUnknownSync(SessionMessage.Assistant)({ + id: "msg_legacy_error", + type: "assistant", + agent: "build", + model: { id: "model", providerID: "provider" }, + content: [], + error: { type: "unknown", message: "Legacy failure" }, + time: { created: 0 }, + }) + + expect(assistant.error).toEqual({ type: "unknown", message: "Legacy failure" }) +}) diff --git a/packages/llm/src/protocols/openai-responses.ts b/packages/llm/src/protocols/openai-responses.ts index b8a955640e..a12e7d0f28 100644 --- a/packages/llm/src/protocols/openai-responses.ts +++ b/packages/llm/src/protocols/openai-responses.ts @@ -891,6 +891,8 @@ const providerError = (event: OpenAIResponsesEvent, fallback: string) => { return LLMEvent.providerError({ message, classification: code === "context_length_exceeded" || isContextOverflow(message) ? "context-overflow" : undefined, + category: code === "rate_limit_exceeded" ? "rate-limit" : undefined, + retryable: code === "rate_limit_exceeded" ? true : undefined, }) } diff --git a/packages/llm/src/schema/errors.ts b/packages/llm/src/schema/errors.ts index 072e4e8389..9cea9d75d6 100644 --- a/packages/llm/src/schema/errors.ts +++ b/packages/llm/src/schema/errors.ts @@ -4,6 +4,20 @@ import { ModelID, ProviderID, ProviderMetadata, RouteID } from "./ids" export const ProviderFailureClassification = Schema.Literal("context-overflow") export type ProviderFailureClassification = typeof ProviderFailureClassification.Type +export const ProviderFailureCategory = Schema.Literals([ + "invalid-request", + "no-route", + "authentication", + "rate-limit", + "quota-exceeded", + "content-policy", + "provider-internal", + "transport", + "invalid-provider-output", + "unknown", +]) +export type ProviderFailureCategory = typeof ProviderFailureCategory.Type + export class HttpRequestDetails extends Schema.Class("LLM.HttpRequestDetails")({ method: Schema.String, url: Schema.String, diff --git a/packages/llm/src/schema/events.ts b/packages/llm/src/schema/events.ts index 3e46013521..9d839f407f 100644 --- a/packages/llm/src/schema/events.ts +++ b/packages/llm/src/schema/events.ts @@ -2,7 +2,7 @@ import { Schema } from "effect" import { ContentBlockID, FinishReason, ProtocolID, ProviderMetadata, RouteID, ToolCallID } from "./ids" import { ModelSchema } from "./options" import { ToolOutput, ToolResultValue } from "./messages" -import { ProviderFailureClassification } from "./errors" +import { ProviderFailureCategory, ProviderFailureClassification } from "./errors" /** * Token usage reported by an LLM provider. @@ -201,6 +201,8 @@ export const ProviderErrorEvent = Schema.Struct({ type: Schema.tag("provider-error"), message: Schema.String, classification: Schema.optional(ProviderFailureClassification), + category: Schema.optional(ProviderFailureCategory), + status: Schema.optional(Schema.Number), retryable: Schema.optional(Schema.Boolean), providerMetadata: Schema.optional(ProviderMetadata), }).annotate({ identifier: "LLM.Event.ProviderError" }) diff --git a/packages/llm/test/provider/openai-responses.test.ts b/packages/llm/test/provider/openai-responses.test.ts index b854537fe2..31e895162b 100644 --- a/packages/llm/test/provider/openai-responses.test.ts +++ b/packages/llm/test/provider/openai-responses.test.ts @@ -1326,7 +1326,14 @@ describe("OpenAI Responses route", () => { // sometimes-generic provider message. The bare message alone meant // production errors like rate limits were indistinguishable from // unrelated stream failures. - expect(response.events).toEqual([{ type: "provider-error", message: "rate_limit_exceeded: Slow down" }]) + expect(response.events).toEqual([ + { + type: "provider-error", + message: "rate_limit_exceeded: Slow down", + category: "rate-limit", + retryable: true, + }, + ]) }), ) diff --git a/packages/schema/src/session-event.ts b/packages/schema/src/session-event.ts index 2e71667e37..fb04594c08 100644 --- a/packages/schema/src/session-event.ts +++ b/packages/schema/src/session-event.ts @@ -50,6 +50,10 @@ const stepSettlementOptions = { export const UnknownError = SessionMessage.UnknownError export type UnknownError = SessionMessage.UnknownError +export const ProviderError = SessionMessage.ProviderError +export type ProviderError = SessionMessage.ProviderError +export const Error = SessionMessage.Error +export type Error = SessionMessage.Error export const AgentSwitched = Event.define({ type: "session.next.agent.switched", @@ -188,7 +192,7 @@ export namespace Step { schema: { ...Base, assistantMessageID: SessionMessageID.ID, - error: UnknownError, + error: Error, }, }) export type Failed = typeof Failed.Type diff --git a/packages/schema/src/session-message.ts b/packages/schema/src/session-message.ts index 63cc25b43d..e51b4d886a 100644 --- a/packages/schema/src/session-message.ts +++ b/packages/schema/src/session-message.ts @@ -17,6 +17,33 @@ export const UnknownError = Schema.Struct({ message: Schema.String, }).annotate({ identifier: "Session.Error.Unknown" }) +export const ProviderErrorCategory = Schema.Literals([ + "invalid-request", + "no-route", + "authentication", + "rate-limit", + "quota-exceeded", + "content-policy", + "provider-internal", + "transport", + "invalid-provider-output", + "unknown", +]) +export type ProviderErrorCategory = typeof ProviderErrorCategory.Type + +export interface ProviderError extends Schema.Schema.Type {} +export const ProviderError = Schema.Struct({ + type: Schema.Literal("provider"), + category: ProviderErrorCategory, + message: Schema.String, + status: Schema.Finite.pipe(Schema.optional), + retryable: Schema.Boolean, + retryAfterMs: Schema.Finite.pipe(Schema.optional), +}).annotate({ identifier: "Session.Error.Provider" }) + +export const Error = Schema.Union([UnknownError, ProviderError]).pipe(Schema.toTaggedUnion("type")) +export type Error = UnknownError | ProviderError + const Base = { id: ID, metadata: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional), @@ -173,7 +200,7 @@ export const Assistant = Schema.Struct({ reasoning: Schema.Finite, cache: Schema.Struct({ read: Schema.Finite, write: Schema.Finite }), }).pipe(Schema.optional), - error: UnknownError.pipe(Schema.optional), + error: Error.pipe(Schema.optional), time: Schema.Struct({ created: DateTimeUtcFromMillis, completed: DateTimeUtcFromMillis.pipe(Schema.optional), diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 2db1871772..8b182b4d07 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -960,7 +960,7 @@ export type GlobalEvent = { timestamp: number sessionID: string assistantMessageID: string - error: SessionErrorUnknown + error: SessionErrorUnknown | SessionErrorProvider } } | { @@ -3005,6 +3005,25 @@ export type SessionErrorUnknown = { message: string } +export type SessionErrorProvider = { + type: "provider" + category: + | "invalid-request" + | "no-route" + | "authentication" + | "rate-limit" + | "quota-exceeded" + | "content-policy" + | "provider-internal" + | "transport" + | "invalid-provider-output" + | "unknown" + message: string + status?: number + retryable: boolean + retryAfterMs?: number +} + export type ToolTextContent = { type: "text" text: string @@ -3420,7 +3439,7 @@ export type SyncEventSessionNextStepFailed = { timestamp: number sessionID: string assistantMessageID: string - error: SessionErrorUnknown + error: SessionErrorUnknown | SessionErrorProvider } } } @@ -4076,7 +4095,7 @@ export type SessionMessageAssistant = { write: number } } - error?: SessionErrorUnknown + error?: SessionErrorUnknown | SessionErrorProvider } export type SessionMessageCompaction = { @@ -4780,7 +4799,7 @@ export type V2EventSessionNextStepFailed = { timestamp: number sessionID: string assistantMessageID: string - error: SessionErrorUnknown + error: SessionErrorUnknown | SessionErrorProvider } } @@ -6412,7 +6431,7 @@ export type EventSessionNextStepFailed = { timestamp: number sessionID: string assistantMessageID: string - error: SessionErrorUnknown + error: SessionErrorUnknown | SessionErrorProvider } }