From d686faf3105bf183102d83ca3f792ff32dfa14ce Mon Sep 17 00:00:00 2001 From: yswcyswc Date: Mon, 31 Aug 2026 04:54:34 +0000 Subject: [PATCH 1/2] chore: add development container --- .devcontainer/devcontainer-lock.json | 14 ++++++++++++++ .devcontainer/devcontainer.json | 23 +++++++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 .devcontainer/devcontainer-lock.json create mode 100644 .devcontainer/devcontainer.json diff --git a/.devcontainer/devcontainer-lock.json b/.devcontainer/devcontainer-lock.json new file mode 100644 index 00000000..e9fa93e0 --- /dev/null +++ b/.devcontainer/devcontainer-lock.json @@ -0,0 +1,14 @@ +{ + "features": { + "ghcr.io/devcontainers-extra/features/bun:1": { + "version": "1.1.0", + "resolved": "ghcr.io/devcontainers-extra/features/bun@sha256:0624284ecaead9dd4c6654616a7f939cfa4ebcbc60593700a74e35b1767befa5", + "integrity": "sha256:0624284ecaead9dd4c6654616a7f939cfa4ebcbc60593700a74e35b1767befa5" + }, + "ghcr.io/devcontainers/features/python:1": { + "version": "1.8.0", + "resolved": "ghcr.io/devcontainers/features/python@sha256:fbcad6955caeecc5ad3f7886baf652e25cba5225a6c4c2287c536de2e5607511", + "integrity": "sha256:fbcad6955caeecc5ad3f7886baf652e25cba5225a6c4c2287c536de2e5607511" + } + } +} diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..2c3d3b3a --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,23 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/debian +{ + "name": "Debian", + // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + "image": "mcr.microsoft.com/devcontainers/base:bookworm", + "features": { + "ghcr.io/devcontainers-extra/features/bun:1": {}, + "ghcr.io/devcontainers/features/python:1": {} + } + + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Configure tool-specific properties. + // "customizations": {}, + + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" +} From fa1caf430c110f542678be20c279d2a85c052f64 Mon Sep 17 00:00:00 2001 From: yswcyswc Date: Thu, 3 Sep 2026 18:28:03 +0000 Subject: [PATCH 2/2] refactor(opencode): simplify retry delay calculation --- packages/opencode/src/session/retry.ts | 46 +++++++++++--------------- 1 file changed, 19 insertions(+), 27 deletions(-) diff --git a/packages/opencode/src/session/retry.ts b/packages/opencode/src/session/retry.ts index cab48dda..082b7fb3 100644 --- a/packages/opencode/src/session/retry.ts +++ b/packages/opencode/src/session/retry.ts @@ -1,7 +1,6 @@ import type { NamedError } from "@opencode-ai/core/util/error" import { SessionV1 } from "@opencode-ai/core/v1/session" import { Cause, Clock, Duration, Effect, Schedule } from "effect" -import { MessageV2 } from "./message-v2" import { iife } from "@/util/iife" import { isRecord } from "@/util/record" @@ -42,36 +41,29 @@ function cap(ms: number) { } export function delay(attempt: number, error?: SessionV1.APIError) { - if (error) { - const headers = error.data.responseHeaders - if (headers) { - const retryAfterMs = headers["retry-after-ms"] - if (retryAfterMs) { - const parsedMs = Number.parseFloat(retryAfterMs) - if (!Number.isNaN(parsedMs)) { - return cap(parsedMs) - } - } + const headers = error?.data.responseHeaders + if (!headers) return backoff(attempt, RETRY_MAX_DELAY_NO_HEADERS) + return retryAfter(headers) ?? backoff(attempt, RETRY_MAX_DELAY) +} - const retryAfter = headers["retry-after"] - if (retryAfter) { - const parsedSeconds = Number.parseFloat(retryAfter) - if (!Number.isNaN(parsedSeconds)) { - // convert seconds to milliseconds - return cap(Math.ceil(parsedSeconds * 1000)) - } - // Try parsing as HTTP date format - const parsed = Date.parse(retryAfter) - Date.now() - if (!Number.isNaN(parsed) && parsed > 0) { - return cap(Math.ceil(parsed)) - } - } +function backoff(attempt: number, maximum: number) { + return cap(Math.min(RETRY_INITIAL_DELAY * Math.pow(RETRY_BACKOFF_FACTOR, attempt - 1), maximum)) +} - return cap(RETRY_INITIAL_DELAY * Math.pow(RETRY_BACKOFF_FACTOR, attempt - 1)) - } +function retryAfter(headers: Record) { + const milliseconds = headers["retry-after-ms"] + if (milliseconds) { + const parsed = Number.parseFloat(milliseconds) + if (!Number.isNaN(parsed)) return cap(parsed) } - return cap(Math.min(RETRY_INITIAL_DELAY * Math.pow(RETRY_BACKOFF_FACTOR, attempt - 1), RETRY_MAX_DELAY_NO_HEADERS)) + const value = headers["retry-after"] + if (!value) return + const seconds = Number.parseFloat(value) + if (!Number.isNaN(seconds)) return cap(Math.ceil(seconds * 1000)) + + const date = Date.parse(value) - Date.now() + if (!Number.isNaN(date) && date > 0) return cap(Math.ceil(date)) } export function retryable(error: Err, provider: string) {