tui: go plan payg msg (#26248)

This commit is contained in:
Aiden Cline
2026-05-07 16:53:24 -05:00
committed by GitHub
parent 22e64cac67
commit f5d0371efe
10 changed files with 285 additions and 83 deletions
+69 -11
View File
@@ -118,12 +118,12 @@ describe("session.retry.delay", () => {
describe("session.retry.retryable", () => {
test("maps too_many_requests json messages", () => {
const error = wrap(JSON.stringify({ type: "error", error: { type: "too_many_requests" } }))
expect(SessionRetry.retryable(error)).toBe("Too Many Requests")
expect(SessionRetry.retryable(error)).toEqual({ message: "Too Many Requests" })
})
test("maps overloaded provider codes", () => {
const error = wrap(JSON.stringify({ code: "resource_exhausted" }))
expect(SessionRetry.retryable(error)).toBe("Provider is overloaded")
expect(SessionRetry.retryable(error)).toEqual({ message: "Provider is overloaded" })
})
test("does not retry unknown json messages", () => {
@@ -146,19 +146,19 @@ describe("session.retry.retryable", () => {
const msg =
"Upstream error from Alibaba: Request rate increased too quickly. To ensure system stability, please adjust your client logic to scale requests more smoothly over time."
const error = wrap(msg)
expect(SessionRetry.retryable(error)).toBe(msg)
expect(SessionRetry.retryable(error)).toEqual({ message: msg })
})
test("retries plain text rate limit errors", () => {
const msg = "Rate limit exceeded, please try again later"
const error = wrap(msg)
expect(SessionRetry.retryable(error)).toBe(msg)
expect(SessionRetry.retryable(error)).toEqual({ message: msg })
})
test("retries too many requests in plain text", () => {
const msg = "Too many requests, please slow down"
const error = wrap(msg)
expect(SessionRetry.retryable(error)).toBe(msg)
expect(SessionRetry.retryable(error)).toEqual({ message: msg })
})
test("does not retry context overflow errors", () => {
@@ -180,7 +180,7 @@ describe("session.retry.retryable", () => {
}).toObject(),
)
expect(SessionRetry.retryable(error)).toBe("Internal server error")
expect(SessionRetry.retryable(error)).toEqual({ message: "Internal server error" })
})
test("retries 502 bad gateway errors", () => {
@@ -192,7 +192,7 @@ describe("session.retry.retryable", () => {
}).toObject(),
)
expect(SessionRetry.retryable(error)).toBe("Bad gateway")
expect(SessionRetry.retryable(error)).toEqual({ message: "Bad gateway" })
})
test("retries 503 service unavailable errors", () => {
@@ -204,7 +204,7 @@ describe("session.retry.retryable", () => {
}).toObject(),
)
expect(SessionRetry.retryable(error)).toBe("Service unavailable")
expect(SessionRetry.retryable(error)).toEqual({ message: "Service unavailable" })
})
test("does not retry 4xx errors when isRetryable is false", () => {
@@ -230,7 +230,65 @@ describe("session.retry.retryable", () => {
const retryable = SessionRetry.retryable(error)
expect(retryable).toBeDefined()
expect(retryable).toBe("Response decompression failed")
expect(retryable).toEqual({ message: "Response decompression failed" })
})
test("maps free limits to Go upsell action", () => {
const error = MessageV2.APIError.Schema.parse(
new MessageV2.APIError({
message: "Free usage exceeded",
isRetryable: true,
statusCode: 429,
responseBody: JSON.stringify({
type: "error",
error: { type: "FreeUsageLimitError", message: "Free usage exceeded" },
}),
}).toObject(),
)
expect(SessionRetry.retryable(error)).toEqual({
message: SessionRetry.GO_UPSELL_MESSAGE,
action: {
title: "Free limit reached",
message:
"Subscribe to OpenCode Go for reliable access to the best open-source models, starting at $5/month.",
label: "subscribe",
link: SessionRetry.GO_UPSELL_URL,
},
})
})
test("maps Go subscription limits to workspace PAYG upsell", () => {
const error = MessageV2.APIError.Schema.parse(
new MessageV2.APIError({
message: "Subscription quota exceeded. You can continue using free models.",
isRetryable: true,
statusCode: 429,
responseBody: JSON.stringify({
type: "error",
error: {
type: "GoUsageLimitError",
message: "Subscription quota exceeded. You can continue using free models.",
},
metadata: {
workspace: "wrk_01K6XGM22R6FM8JVABE9XDQXGH",
limit: "5 hour",
resetAt: 19_380,
},
}),
}).toObject(),
)
expect(SessionRetry.retryable(error)).toEqual({
message: SessionRetry.PAYG_UPSELL_MESSAGE,
action: {
title: "Go limit reached",
message:
"You hit your 5 hour limit. It will reset in 5 hours 23 minutes. You can also enable pay-as-you-go.",
label: "enable PAYG",
link: "https://opencode.ai/workspace/wrk_01K6XGM22R6FM8JVABE9XDQXGH/go",
},
})
})
})
@@ -283,7 +341,7 @@ describe("session.message-v2.fromError", () => {
const retryable = SessionRetry.retryable(error)
expect(retryable).toBeDefined()
expect(retryable).toBe("Connection reset by server")
expect(retryable).toEqual({ message: "Connection reset by server" })
})
test("marks OpenAI 404 status codes as retryable", () => {
@@ -321,6 +379,6 @@ describe("session.message-v2.fromError", () => {
expect(MessageV2.APIError.isInstance(result)).toBe(true)
if (!MessageV2.APIError.isInstance(result)) throw new Error("expected APIError")
expect(result.data.isRetryable).toBe(true)
expect(SessionRetry.retryable(result)).toBe("An error occurred while processing your request.")
expect(SessionRetry.retryable(result)).toEqual({ message: "An error occurred while processing your request." })
})
})
@@ -230,8 +230,19 @@ describe("SessionStatus.Info", () => {
expect(SessionStatus.Info.zod.parse({ type: "idle" })).toEqual({ type: "idle" })
})
test("retry carries attempt/message/next", () => {
const input = { type: "retry" as const, attempt: 1, message: "transient", next: 500 }
test("retry carries attempt/message/action/next", () => {
const input = {
type: "retry" as const,
attempt: 1,
message: "transient",
action: {
title: "Free limit reached",
message: "Subscribe to OpenCode Go.",
label: "subscribe",
link: "https://opencode.ai/go",
},
next: 500,
}
expect(decode(input)).toEqual(input)
expect(SessionStatus.Info.zod.parse(input)).toEqual(input)
})