fix: adjust tui retry dialog logic to be more provider specific and error case specific (#26366)

This commit is contained in:
Aiden Cline
2026-05-08 10:48:19 -05:00
committed by GitHub
parent df75bfe07c
commit 799996db76
8 changed files with 98 additions and 64 deletions

View File

@@ -93,9 +93,29 @@ import { useBindings, useCommandShortcut } from "../../keymap"
addDefaultParsers(parsers.parsers)
const GO_UPSELL_LAST_SEEN_AT = "go_upsell_last_seen_at"
const GO_UPSELL_DONT_SHOW = "go_upsell_dont_show"
const GO_UPSELL_FREE_TIER_LAST_SEEN_AT = "go_upsell_last_seen_at"
const GO_UPSELL_FREE_TIER_DONT_SHOW = "go_upsell_dont_show"
const GO_UPSELL_ACCOUNT_RATE_LIMIT_LAST_SEEN_AT = "go_upsell_account_rate_limit_last_seen_at"
const GO_UPSELL_ACCOUNT_RATE_LIMIT_DONT_SHOW = "go_upsell_account_rate_limit_dont_show"
const GO_UPSELL_WINDOW = 86_400_000 // 24 hrs
const GO_UPSELL_PROVIDERS = new Set(["opencode", "opencode-go"])
function goUpsellKeys(action: SessionRetry.Retryable["action"]) {
if (!action) return
if (!GO_UPSELL_PROVIDERS.has(action.provider)) return
if (action.reason === "free_tier_limit") {
return {
lastSeenAt: GO_UPSELL_FREE_TIER_LAST_SEEN_AT,
dontShow: GO_UPSELL_FREE_TIER_DONT_SHOW,
}
}
if (action.reason === "account_rate_limit") {
return {
lastSeenAt: GO_UPSELL_ACCOUNT_RATE_LIMIT_LAST_SEEN_AT,
dontShow: GO_UPSELL_ACCOUNT_RATE_LIMIT_DONT_SHOW,
}
}
}
const context = createContext<{
width: number
@@ -263,14 +283,17 @@ export function Session() {
if (!evt.properties.status.action) return
if (dialog.stack.length > 0) return
const seen = kv.get(GO_UPSELL_LAST_SEEN_AT)
const keys = goUpsellKeys(evt.properties.status.action)
if (!keys) return
const seen = kv.get(keys.lastSeenAt)
if (typeof seen === "number" && Date.now() - seen < GO_UPSELL_WINDOW) return
if (kv.get(GO_UPSELL_DONT_SHOW)) return
if (kv.get(keys.dontShow)) return
void DialogRetryAction.show(dialog, evt.properties.status.action).then((dontShowAgain) => {
if (dontShowAgain) kv.set(GO_UPSELL_DONT_SHOW, true)
kv.set(GO_UPSELL_LAST_SEEN_AT, Date.now())
if (dontShowAgain) kv.set(keys.dontShow, true)
kv.set(keys.lastSeenAt, Date.now())
})
})

View File

@@ -701,6 +701,7 @@ export const layer: Layer.Layer<
),
Effect.retry(
SessionRetry.policy({
provider: input.model.providerID,
parse,
set: (info) => {
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.

View File

@@ -7,10 +7,13 @@ export type Err = ReturnType<NamedError["toObject"]>
export const GO_UPSELL_MESSAGE = "Free usage exceeded, subscribe to Go"
export const GO_UPSELL_URL = "https://opencode.ai/go"
export type RetryReason = "free_tier_limit" | "account_rate_limit" | (string & {})
export type Retryable = {
message: string
action?: {
reason: RetryReason
provider: string
title: string
message: string
label: string
@@ -60,7 +63,7 @@ export function delay(attempt: number, error?: MessageV2.APIError) {
return cap(Math.min(RETRY_INITIAL_DELAY * Math.pow(RETRY_BACKOFF_FACTOR, attempt - 1), RETRY_MAX_DELAY_NO_HEADERS))
}
export function retryable(error: Err) {
export function retryable(error: Err, provider: string) {
// context overflow errors should not be retried
if (MessageV2.ContextOverflowError.isInstance(error)) return undefined
if (MessageV2.APIError.isInstance(error)) {
@@ -72,6 +75,8 @@ export function retryable(error: Err) {
return {
message: GO_UPSELL_MESSAGE,
action: {
reason: "free_tier_limit",
provider,
title: "Free limit reached",
message: "Subscribe to OpenCode Go for reliable access to the best open-source models, starting at $5/month.",
label: "subscribe",
@@ -97,12 +102,14 @@ export function retryable(error: Err) {
return minutes > 0 ? unit(minutes, "minute") : "less than a minute"
})
const message = `${limitName} usage limit reached. It will reset in ${resetIn}. To continue using this model now, enable usage from your available balance`
const message = `${limitName ? `${limitName} usage limit` : "Usage limit"} reached. It will reset in ${resetIn}. To continue using this model now, enable usage from your available balance`
const link = `https://opencode.ai/workspace/${workspace}/go`
return {
message: `${message} - ${link}`,
action: {
reason: "account_rate_limit",
provider,
title: "Go limit reached",
message,
label: "open settings",
@@ -165,13 +172,14 @@ function parseJSON(value: unknown) {
}
export function policy(opts: {
provider: string
parse: (error: unknown) => Err
set: (input: { attempt: number; message: string; action?: Retryable["action"]; next: number }) => Effect.Effect<void>
}) {
return Schedule.fromStepWithMetadata(
Effect.succeed((meta: Schedule.InputMetadata<unknown>) => {
const error = opts.parse(meta.input)
const retry = retryable(error)
const retry = retryable(error, opts.provider)
if (!retry) return Cause.done(meta.attempt)
return Effect.gen(function* () {
const wait = delay(meta.attempt, MessageV2.APIError.isInstance(error) ? error : undefined)

View File

@@ -17,6 +17,8 @@ export const Info = Schema.Union([
message: Schema.String,
action: Schema.optional(
Schema.Struct({
reason: Schema.String,
provider: Schema.String,
title: Schema.String,
message: Schema.String,
label: Schema.String,