Skip to content
Open
1 change: 1 addition & 0 deletions packages/opencode/src/provider/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,7 @@ function custom(dep: CustomDep): Record<string, CustomLoader> {
Effect.succeed({
autoload: provider.source === "config",
options: {
headerTimeout: 60_000,
headers: {
"HTTP-Referer": "https://opencode.ai/",
"X-Title": "opencode",
Expand Down
9 changes: 9 additions & 0 deletions packages/opencode/src/session/message-v2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -684,6 +684,15 @@ export function fromError(
},
{ cause: e },
).toObject()
case typeof (e as any)?.message === "string" && (e as any).message.includes("EngineCore"):
return new APIError(
{
message: (e as any).message,
isRetryable: true,
metadata: { code: "ProviderEngineError" },
},
{ cause: e },
).toObject()
case APICallError.isInstance(e):
const parsed = ProviderError.parseAPICallError({
providerID: ctx.providerID,
Expand Down
14 changes: 14 additions & 0 deletions packages/opencode/src/session/retry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,22 @@ function cap(ms: number) {
return Math.min(ms, RETRY_MAX_DELAY)
}

export const RETRY_DELAY_BLOCKED = 60_000

function isConnectionBlockingError(error: SessionV1.APIError) {
const code = error.data.metadata?.["code"]
return (
code === "ECONNRESET" ||
code === "ProviderHeaderTimeoutError" ||
code === "ProviderEngineError" ||
error.data.statusCode === 504
)
}

export function delay(attempt: number, error?: SessionV1.APIError) {
if (error) {
// Provider hung or gateway timeout: fixed 5s delay regardless of attempt count
if (isConnectionBlockingError(error)) return RETRY_DELAY_BLOCKED
const headers = error.data.responseHeaders
if (headers) {
const retryAfterMs = headers["retry-after-ms"]
Expand Down
Loading