@@ -69,7 +69,7 @@ class OpencodeApiClient(private val client: HttpClient) {
6969 text: String,
7070 model: ModelRef? = null,
7171 agent: String? = null,
72− ): MessageWithParts = withRetry {
72+ ): MessageWithParts =
7373 client.post("session/$sessionId/message") {
7474 contentType(ContentType.Application.Json)
7575 setBody(
@@ -80,7 +80,6 @@ class OpencodeApiClient(private val client: HttpClient) {
8080 ),
8181 )
8282 }.body()
83− }
8483
8584 suspend fun abort(sessionId: String) {
8685 client.post("session/$sessionId/abort")
@@ -145,7 +144,7 @@ private suspend fun <T> withRetry(
145144 maxAttempts: Int = NetworkConfig.retryMaxAttempts,
146145 initialDelayMs: Long = NetworkConfig.retryInitialDelayMs,
147146 block: suspend () -> T,
148−): T = withRetryInternal(maxAttempts, initialDelayMs, block)
147+): T = withRetryInternal(maxAttempts, initialDelayMs, jitterFactor = NetworkConfig.retryJitterFactor, block)
149148
150149/**
151150 * Retry a suspending block up to [maxAttempts] times with exponential backoff.
@@ -158,6 +157,7 @@ private suspend fun <T> withRetry(
158157internal suspend fun <T> withRetryInternal(
159158 maxAttempts: Int = NetworkConfig.retryMaxAttempts,
160159 initialDelayMs: Long = NetworkConfig.retryInitialDelayMs,
160+ jitterFactor: Double = NetworkConfig.retryJitterFactor,
161161 block: suspend () -> T,
162162): T {
163163 var lastError: Throwable? = null
@@ -169,7 +169,11 @@ internal suspend fun <T> withRetryInternal(
169169 } catch (t: Throwable) {
170170 lastError = t
171171 if (t is ClientRequestException) throw t
172− if (attempt < maxAttempts) delay(initialDelayMs * (1 shl (attempt - 1)))
172+ if (attempt < maxAttempts) {
173+ val baseDelay = initialDelayMs * (1 shl (attempt - 1))
174+ val jitter = (baseDelay * jitterFactor * Math.random()).toLong()
175+ delay(baseDelay + jitter)
176+ }
173177 }
174178 }
175179 throw lastError ?: IllegalStateException("withRetry failed without error")