From 5e6370363b14e0f8a40b0f60fea48b067e65ef08 Mon Sep 17 00:00:00 2001 From: James Long Date: Fri, 7 Aug 2026 23:11:32 -0400 Subject: [PATCH] fix(tui): refine provider failure presentation (#41179) --- packages/ai/src/route/executor.ts | 16 ++++++++++++++-- packages/tui/src/app.tsx | 9 --------- packages/tui/src/routes/session/index.tsx | 22 +++++++--------------- 3 files changed, 21 insertions(+), 26 deletions(-) diff --git a/packages/ai/src/route/executor.ts b/packages/ai/src/route/executor.ts index 9b90bf1aa4f..431df10fe26 100644 --- a/packages/ai/src/route/executor.ts +++ b/packages/ai/src/route/executor.ts @@ -1,4 +1,4 @@ -import { Cause, Context, Effect, Layer } from "effect" +import { Cause, Context, Effect, Layer, Option, Schema } from "effect" import { FetchHttpClient, Headers, @@ -198,8 +198,20 @@ const responseBody = (body: string | void, request: HttpClientRequest.HttpClient return { body: redacted.slice(0, BODY_LIMIT), bodyTruncated: true } } +const decodeProviderBody = Schema.decodeUnknownOption( + Schema.fromJsonString( + Schema.Struct({ + message: Schema.optionalKey(Schema.String), + error: Schema.optionalKey(Schema.Struct({ message: Schema.optionalKey(Schema.String) })), + }), + ), +) + const providerMessage = (status: number, body: { readonly body?: string }) => { - if (body.body && body.body.length <= 500) return `Provider request failed with HTTP ${status}: ${body.body}` + if (body.body && body.body.length <= 500) { + const decoded = Option.getOrUndefined(decodeProviderBody(body.body)) + return `Provider request failed with HTTP ${status}: ${decoded?.error?.message ?? decoded?.message ?? body.body}` + } return `Provider request failed with HTTP ${status}` } diff --git a/packages/tui/src/app.tsx b/packages/tui/src/app.tsx index 90f8491f384..93ec2faafe5 100644 --- a/packages/tui/src/app.tsx +++ b/packages/tui/src/app.tsx @@ -1154,15 +1154,6 @@ function App(props: { pair?: DialogPairCredentials }) { } }) - event.on("session.execution.failed", (evt, { workspace }) => { - if (workspace !== (location.current?.workspaceID ?? data.location.default().workspaceID)) return - toast.show({ - variant: "error", - message: evt.data.error.message, - duration: 5000, - }) - }) - // Suppress the full-screen overlay for transient startup and event-stream retry states. // Initial connection gets a longer grace period; retries surface more quickly. const [showReconnecting, setShowReconnecting] = createSignal(false) diff --git a/packages/tui/src/routes/session/index.tsx b/packages/tui/src/routes/session/index.tsx index 34599bf5211..01c44b279b2 100644 --- a/packages/tui/src/routes/session/index.tsx +++ b/packages/tui/src/routes/session/index.tsx @@ -1631,21 +1631,13 @@ function AssistantFooter(props: { message: SessionMessageAssistant }) { const interrupted = createMemo(() => props.message.error?.message === "Step interrupted") return ( <> - - - {errorMessage(props.message.error)} + + + Error: {errorMessage(props.message.error)} - + {Locale.titlecase(props.message.agent)} @@ -2045,9 +2037,9 @@ function AssistantRetry(props: { retry: SessionMessageAssistant["retry"] }) { return ( {(retry) => ( - - - Retry attempt {retry().attempt} scheduled: {retry().error.message} [{retry().error.type}] + + + ⚠ Retry attempt {retry().attempt} scheduled: {retry().error.message} )}