Skip to content

Commit df26a2f

Browse files
committed
fix(opencode): retain reported reset times for capacity failures
1 parent 385b3cf commit df26a2f

6 files changed

Lines changed: 34 additions & 22 deletions

File tree

‎apps/mobile/src/features/threads/UsageLimitRecoveryCard.test.tsx‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ describe("UsageLimitRecoveryCard", () => {
7070
});
7171

7272
it("shows provider capacity detail and keeps the reset resume action", () => {
73+
using _format = vi
74+
.spyOn(Date.prototype, "toLocaleString")
75+
.mockReturnValue("Jan 31, 1970, 00:00");
7376
using _timers = { [Symbol.dispose]: () => vi.useRealTimers() };
7477
vi.useFakeTimers({ now: 0 });
7578
const container = document.createElement("div");
@@ -87,6 +90,7 @@ describe("UsageLimitRecoveryCard", () => {
8790
);
8891

8992
expect(container.textContent).toContain(message);
93+
expect(container.textContent).toContain("Resets Jan 31, 1970, 00:00.");
9094
expect(
9195
[...container.querySelectorAll("button")].find(
9296
(button) => button.textContent === "Resume at reset",

‎apps/mobile/src/features/threads/UsageLimitRecoveryCard.tsx‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,14 @@ export function UsageLimitRecoveryCard({
4242
code: thread.runtime.lastErrorCode ?? null,
4343
message: thread.runtime.lastError ?? "",
4444
});
45+
const resetTime = resetAt
46+
? DateTime.toDateUtc(DateTime.makeUnsafe(resetAt)).toLocaleString()
47+
: null;
4548
const recoveryDescription =
4649
failurePresentation.kind === "provider_capacity"
47-
? `${failurePresentation.title}. ${thread.runtime.lastError ?? ""}`
48-
: resetAt
49-
? `Usage limit resets ${DateTime.toDateUtc(DateTime.makeUnsafe(resetAt)).toLocaleString()}.`
50+
? `${failurePresentation.title}. ${thread.runtime.lastError ?? ""}${resetTime ? ` Resets ${resetTime}.` : ""}`
51+
: resetTime
52+
? `Usage limit resets ${resetTime}.`
5053
: "The provider did not report a reset time. Retry manually when your limit is available.";
5154
const snoozed =
5255
recovery?.snooze === true &&

‎apps/mobile/src/features/threads/thread-work-log.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -897,7 +897,7 @@ const ThreadWorkLogRow = function ThreadWorkLogRow(
897897
: null;
898898
const label = warning
899899
? failurePresentation.kind === "provider_capacity"
900-
? failurePresentation.title
900+
? `${failurePresentation.title}${resetTime ? `. Retry after ${resetTime}.` : ""}`
901901
: `Usage limit reached.${resetTime ? ` Retry after ${resetTime}.` : ""}`
902902
: row.summary;
903903
return (

‎apps/web/src/components/chat/TimelineWorkEntryRow.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ function WorkEntryLogRow(props: WorkEntryRowProps) {
212212
const resetTime = resetAt ? formatUpcomingTimestamp(resetAt, timestampFormat) : null;
213213
const label = warning
214214
? failurePresentation.kind === "provider_capacity"
215-
? failurePresentation.title
215+
? `${failurePresentation.title}${resetTime ? `. Retry after ${resetTime}.` : ""}`
216216
: `Usage limit reached.${resetTime ? ` Retry after ${resetTime}.` : ""}`
217217
: workEntry.label;
218218
const retryRunId =

‎apps/web/src/components/chat/UsageLimitRecoveryBanner.test.tsx‎

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -34,21 +34,26 @@ describe("usageLimitRecoveryBannerItem", () => {
3434
);
3535
});
3636

37-
it("shows the provider capacity message and keeps the reset recovery actions", () => {
38-
const message =
39-
"Upstream request failed: [ttft_admission_refused] the cell is at capacity; retry shortly (after 30 s)";
40-
const item = usageLimitRecoveryBannerItem({
41-
runId: RunId.make("run-capacity"),
42-
resetAt: new Date(stoppedAt.getTime() + 30_000).toISOString(),
43-
stoppedAt: stoppedAt.toISOString(),
44-
snoozedUntil: null,
45-
recovery: null,
46-
timestampFormat: "24-hour",
47-
failure: { class: "usage_limit", code: "provider.rate-limit", message },
48-
onChange: async () => {},
49-
});
37+
it.each([null, new Date(2026, 9, 20, 17, 30)])(
38+
"shows provider capacity detail with the reported reset %s",
39+
(resetAt) => {
40+
using _now = vi.spyOn(Date, "now").mockReturnValue(stoppedAt.getTime());
41+
const message =
42+
"Upstream request failed: [ttft_admission_refused] the cell is at capacity; retry shortly (after 30 s)";
43+
const item = usageLimitRecoveryBannerItem({
44+
runId: RunId.make("run-capacity"),
45+
resetAt: resetAt?.toISOString() ?? null,
46+
stoppedAt: stoppedAt.toISOString(),
47+
snoozedUntil: null,
48+
recovery: null,
49+
timestampFormat: "24-hour",
50+
failure: { class: "usage_limit", code: "provider.rate-limit", message },
51+
onChange: async () => {},
52+
});
5053

51-
expect(item.description).toBe(message);
52-
expect(item.actions).not.toBeNull();
53-
});
54+
expect(item.description).toBe(resetAt ? `${message} Resets 17:30.` : message);
55+
if (resetAt) expect(item.actions).not.toBeNull();
56+
else expect(item.actions).toBeNull();
57+
},
58+
);
5459
});

‎apps/web/src/components/chat/UsageLimitRecoveryBanner.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export function usageLimitRecoveryBannerItem(props: RecoveryProps): ComposerBann
3737
title: failurePresentation?.title ?? "Usage limit reached",
3838
description:
3939
failurePresentation?.kind === "provider_capacity"
40-
? (props.failure?.message ?? "Provider at capacity")
40+
? `${props.failure?.message ?? "Provider at capacity"}${resetAt ? ` Resets ${formatUpcomingTimestamp(resetAt, timestampFormat)}.` : ""}`
4141
: resetAt
4242
? `Resets ${formatUpcomingTimestamp(resetAt, timestampFormat)}`
4343
: "Reset time unavailable; retry manually",

0 commit comments

Comments
 (0)