Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,21 @@ it.layer(McpProviderSessions.layer)("OpenCode2 adapter", (it) => {
status: 429,
},
},
{
name: "a ChatGPT provider.quota usage window (429)",
failureClass: "usage_limit",
error: {
type: "provider.quota",
message: "The usage limit has been reached",
status: 429,
response: {
body: JSON.stringify({
error: { type: "usage_limit_reached", resets_at: 1793114741, resets_in_seconds: 222 },
}),
},
},
resetAt: "2026-10-27T15:25:41.000Z",
},
{
name: "a provider.rate-limit failure (429)",
failureClass: "usage_limit",
Expand All @@ -673,7 +688,7 @@ it.layer(McpProviderSessions.layer)("OpenCode2 adapter", (it) => {
message: "Request failed. Resets at 2026-11-02T20:00:00Z.",
},
},
] as const)("classifies $name as $failureClass", ({ error, failureClass }) =>
] as const)("classifies $name as $failureClass", ({ error, failureClass, ...row }) =>
Effect.gen(function* () {
const { runtime, thread } = yield* resumed([
out("session.prompt", { sessionID: SESSION, text: "<any>" }),
Expand Down Expand Up @@ -705,6 +720,7 @@ it.layer(McpProviderSessions.layer)("OpenCode2 adapter", (it) => {
code: error.type,
message: error.message,
retryable: null,
...("resetAt" in row ? { resetAt: row.resetAt } : {}),
});
}),
);
Expand Down
13 changes: 12 additions & 1 deletion packages/provider-opencode/src/server/v2/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,17 @@ const isUsageLimit = (error: {
error.type === "provider.rate-limit" ||
claudeUsageLimit(error.message) !== undefined;

// ChatGPT-backed OpenAI models put the usage-limit reset in the 429 body as Unix seconds.
const decodeUsageLimitBody = Schema.decodeUnknownOption(
Schema.fromJsonString(Schema.Struct({ error: Schema.Struct({ resets_at: Schema.Finite }) })),
);
const usageLimitResetAt = (body: string | undefined) =>
decodeUsageLimitBody(body).pipe(
Option.flatMap(({ error }) => DateTime.make(error.resets_at * 1000)),
Option.map(DateTime.formatIso),
Option.getOrNull,
);

const INTERRUPT_TIMEOUT = 10_000;
const INSTRUCTIONS_KEY = "t3-code";
const RECONNECT_ATTEMPTS = 5;
Expand Down Expand Up @@ -2889,7 +2900,7 @@ export const make = Effect.fn("OpenCode2Adapter.make")(function* (instanceId: Pr
message: readableProviderErrorMessage(message, status, response?.body),
code: type,
class: isUsageLimit(event.data.error) ? "usage_limit" : "provider_error",
resetAt: claudeUsageLimit(message)?.resetAt ?? null,
resetAt: claudeUsageLimit(message)?.resetAt ?? usageLimitResetAt(response?.body),
}),
});
}
Expand Down
Loading