From 438610aa647116e40815c0720eb472791259c13d Mon Sep 17 00:00:00 2001
From: Filip <34747899+neriousy@users.noreply.github.com>
Date: Sun, 1 Mar 2026 13:48:11 +0100
Subject: fix(app): show proper usage limit errors (#15496)
---
packages/ui/src/components/session-retry.tsx | 74 ++++++++++++++++++++++++++++
packages/ui/src/components/session-turn.tsx | 3 ++
2 files changed, 77 insertions(+)
create mode 100644 packages/ui/src/components/session-retry.tsx
(limited to 'packages/ui/src/components')
diff --git a/packages/ui/src/components/session-retry.tsx b/packages/ui/src/components/session-retry.tsx
new file mode 100644
index 000000000..ac0eb035d
--- /dev/null
+++ b/packages/ui/src/components/session-retry.tsx
@@ -0,0 +1,74 @@
+import { createEffect, createMemo, createSignal, on, onCleanup, Show } from "solid-js"
+import type { SessionStatus } from "@opencode-ai/sdk/v2/client"
+import { useI18n } from "../context/i18n"
+import { Card } from "./card"
+import { Tooltip } from "./tooltip"
+import { Spinner } from "./spinner"
+
+export function SessionRetry(props: { status: SessionStatus; show?: boolean }) {
+ const i18n = useI18n()
+ const retry = createMemo(() => {
+ if (props.status.type !== "retry") return
+ return props.status
+ })
+ const [seconds, setSeconds] = createSignal(0)
+ createEffect(
+ on(retry, (current) => {
+ if (!current) return
+ const update = () => {
+ const next = retry()?.next
+ if (!next) return
+ setSeconds(Math.round((next - Date.now()) / 1000))
+ }
+ update()
+ const timer = setInterval(update, 1000)
+ onCleanup(() => clearInterval(timer))
+ }),
+ )
+ const message = createMemo(() => {
+ const current = retry()
+ if (!current) return ""
+ if (current.message.includes("exceeded your current quota") && current.message.includes("gemini")) {
+ return i18n.t("ui.sessionTurn.retry.geminiHot")
+ }
+ if (current.message.length > 80) return current.message.slice(0, 80) + "..."
+ return current.message
+ })
+ const truncated = createMemo(() => {
+ const current = retry()
+ if (!current) return false
+ return current.message.length > 80
+ })
+ const info = createMemo(() => {
+ const current = retry()
+ if (!current) return ""
+ const count = Math.max(0, seconds())
+ const delay = count > 0 ? i18n.t("ui.sessionTurn.retry.inSeconds", { seconds: count }) : ""
+ const retrying = i18n.t("ui.sessionTurn.retry.retrying")
+ const line = [retrying, delay].filter(Boolean).join(" ")
+ if (!line) return i18n.t("ui.sessionTurn.retry.attempt", { attempt: current.attempt })
+ return i18n.t("ui.sessionTurn.retry.attemptLine", { line, attempt: current.attempt })
+ })
+
+ return (
+
+
+
+
+
+
+ {message()}
}>
+
+
+ {message()}
+
+
+
+
{(line) => {line()}
}
+
+
+
+
+
+ )
+}
diff --git a/packages/ui/src/components/session-turn.tsx b/packages/ui/src/components/session-turn.tsx
index 3116d4b65..bd4f2843a 100644
--- a/packages/ui/src/components/session-turn.tsx
+++ b/packages/ui/src/components/session-turn.tsx
@@ -14,6 +14,7 @@ import { Collapsible } from "./collapsible"
import { DiffChanges } from "./diff-changes"
import { Icon } from "./icon"
import { TextShimmer } from "./text-shimmer"
+import { SessionRetry } from "./session-retry"
import { createAutoScroll } from "../hooks"
import { useI18n } from "../context/i18n"
@@ -332,6 +333,7 @@ export function SessionTurn(
)
const showThinking = createMemo(() => {
if (!working() || !!error()) return false
+ if (status().type === "retry") return false
if (showReasoningSummaries()) return assistantVisible() === 0
if (assistantTailVisible() === "text") return false
return true
@@ -384,6 +386,7 @@ export function SessionTurn(
+
0 && !working()}>
--
cgit v1.2.3