summaryrefslogtreecommitdiffhomepage
path: root/packages/ui/src
diff options
context:
space:
mode:
authorAdam <[email protected]>2026-02-06 05:50:38 -0600
committerGitHub <[email protected]>2026-02-06 05:50:38 -0600
commit5d922198121338ba67ec1e809a57cf257889bb3c (patch)
tree7c89d9904a2c2b73ea4127f6745ca2ffa363fee8 /packages/ui/src
parent80a5c3d7ed65e6fce599f82f7db68f3d5ac01d91 (diff)
downloadopencode-5d922198121338ba67ec1e809a57cf257889bb3c.tar.gz
opencode-5d922198121338ba67ec1e809a57cf257889bb3c.zip
fix(app): retry error unwrapping (#12462)
Diffstat (limited to 'packages/ui/src')
-rw-r--r--packages/ui/src/components/session-turn.css8
-rw-r--r--packages/ui/src/components/session-turn.tsx99
2 files changed, 104 insertions, 3 deletions
diff --git a/packages/ui/src/components/session-turn.css b/packages/ui/src/components/session-turn.css
index 4b8ba8d7a..9887ce2fc 100644
--- a/packages/ui/src/components/session-turn.css
+++ b/packages/ui/src/components/session-turn.css
@@ -501,6 +501,7 @@
[data-slot="session-turn-collapsible-trigger-content"] {
max-width: 100%;
+ min-width: 0;
display: flex;
align-items: center;
gap: 8px;
@@ -525,6 +526,10 @@
[data-slot="session-turn-retry-message"] {
font-weight: 500;
color: var(--syntax-critical);
+ min-width: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
}
[data-slot="session-turn-retry-seconds"] {
@@ -549,6 +554,9 @@
.error-card {
color: var(--text-on-critical-base);
max-height: 240px;
+ white-space: pre-wrap;
+ overflow-wrap: anywhere;
+ word-break: break-word;
overflow-y: auto;
}
diff --git a/packages/ui/src/components/session-turn.tsx b/packages/ui/src/components/session-turn.tsx
index 5ea9f64bb..c2e26b9c7 100644
--- a/packages/ui/src/components/session-turn.tsx
+++ b/packages/ui/src/components/session-turn.tsx
@@ -27,6 +27,59 @@ import { createResizeObserver } from "@solid-primitives/resize-observer"
type Translator = (key: UiI18nKey, params?: UiI18nParams) => string
+function record(value: unknown): value is Record<string, unknown> {
+ return !!value && typeof value === "object" && !Array.isArray(value)
+}
+
+function unwrap(message: string) {
+ const text = message.replace(/^Error:\s*/, "").trim()
+
+ const parse = (value: string) => {
+ try {
+ return JSON.parse(value) as unknown
+ } catch {
+ return undefined
+ }
+ }
+
+ const read = (value: string) => {
+ const first = parse(value)
+ if (typeof first !== "string") return first
+ return parse(first.trim())
+ }
+
+ let json = read(text)
+
+ if (json === undefined) {
+ const start = text.indexOf("{")
+ const end = text.lastIndexOf("}")
+ if (start !== -1 && end > start) {
+ json = read(text.slice(start, end + 1))
+ }
+ }
+
+ if (!record(json)) return message
+
+ const err = record(json.error) ? json.error : undefined
+ if (err) {
+ const type = typeof err.type === "string" ? err.type : undefined
+ const msg = typeof err.message === "string" ? err.message : undefined
+ if (type && msg) return `${type}: ${msg}`
+ if (msg) return msg
+ if (type) return type
+ const code = typeof err.code === "string" ? err.code : undefined
+ if (code) return code
+ }
+
+ const msg = typeof json.message === "string" ? json.message : undefined
+ if (msg) return msg
+
+ const reason = typeof json.error === "string" ? json.error : undefined
+ if (reason) return reason
+
+ return message
+}
+
function computeStatusFromPart(part: PartType | undefined, t: Translator): string | undefined {
if (!part) return undefined
@@ -236,6 +289,12 @@ export function SessionTurn(
const lastAssistantMessage = createMemo(() => assistantMessages().at(-1))
const error = createMemo(() => assistantMessages().find((m) => m.error)?.error)
+ const errorText = createMemo(() => {
+ const msg = error()?.data?.message
+ if (typeof msg === "string") return unwrap(msg)
+ if (msg === undefined || msg === null) return ""
+ return unwrap(String(msg))
+ })
const lastTextPart = createMemo(() => {
const msgs = assistantMessages()
@@ -463,6 +522,39 @@ export function SessionTurn(
onCleanup(() => clearInterval(timer))
})
+ let retryLog = ""
+ createEffect(() => {
+ const r = retry()
+ if (!r) return
+ const key = `${r.attempt}:${r.next}:${r.message}`
+ if (key === retryLog) return
+ retryLog = key
+ console.warn("[session-turn] retry", {
+ sessionID: props.sessionID,
+ messageID: props.messageID,
+ attempt: r.attempt,
+ next: r.next,
+ raw: r.message,
+ parsed: unwrap(r.message),
+ })
+ })
+
+ let errorLog = ""
+ createEffect(() => {
+ const value = error()?.data?.message
+ if (value === undefined || value === null) return
+ const raw = typeof value === "string" ? value : String(value)
+ if (!raw) return
+ if (raw === errorLog) return
+ errorLog = raw
+ console.warn("[session-turn] assistant-error", {
+ sessionID: props.sessionID,
+ messageID: props.messageID,
+ raw,
+ parsed: unwrap(raw),
+ })
+ })
+
createEffect(() => {
const update = () => {
setStore("duration", duration())
@@ -595,7 +687,8 @@ export function SessionTurn(
{(() => {
const r = retry()
if (!r) return ""
- return r.message.length > 60 ? r.message.slice(0, 60) + "..." : r.message
+ const msg = unwrap(r.message)
+ return msg.length > 60 ? msg.slice(0, 60) + "..." : msg
})()}
</span>
<span data-slot="session-turn-retry-seconds">
@@ -640,7 +733,7 @@ export function SessionTurn(
</For>
<Show when={error()}>
<Card variant="error" class="error-card">
- {error()?.data?.message as string}
+ {errorText()}
</Card>
</Show>
</div>
@@ -696,7 +789,7 @@ export function SessionTurn(
</Show>
<Show when={error() && !props.stepsExpanded}>
<Card variant="error" class="error-card">
- {error()?.data?.message as string}
+ {errorText()}
</Card>
</Show>
</Match>