Skip to content

Commit 9525fd8

Browse files
committed
fix(opencode): cap output retries
1 parent b070943 commit 9525fd8

2 files changed

Lines changed: 28 additions & 1 deletion

File tree

packages/opencode/src/session/retry.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export const RETRY_INITIAL_DELAY = 2000
3737
export const RETRY_BACKOFF_FACTOR = 2
3838
export const RETRY_MAX_DELAY_NO_HEADERS = 30_000 // 30 seconds
3939
export const RETRY_MAX_DELAY = 2_147_483_647 // max 32-bit signed integer for setTimeout
40+
export const OUTPUT_LENGTH_MAX_RETRIES = 3
4041

4142
function cap(ms: number) {
4243
return Math.min(ms, RETRY_MAX_DELAY)
@@ -189,11 +190,15 @@ export function policy(opts: {
189190
parse: (error: unknown) => Err
190191
set: (input: { attempt: number; message: string; action?: Retryable["action"]; next: number }) => Effect.Effect<void>
191192
}) {
193+
let outputLengthRetries = 0
192194
return Schedule.fromStepWithMetadata(
193195
Effect.succeed((meta: Schedule.InputMetadata<unknown>) => {
194196
const error = opts.parse(meta.input)
195197
const retry = retryable(error, opts.provider)
196198
if (!retry) return Cause.done(meta.attempt)
199+
if (SessionV1.OutputLengthError.isInstance(error) && ++outputLengthRetries > OUTPUT_LENGTH_MAX_RETRIES) {
200+
return Cause.done(meta.attempt)
201+
}
197202
return Effect.gen(function* () {
198203
const wait = delay(meta.attempt, SessionV1.APIError.isInstance(error) ? error : undefined)
199204
const now = yield* Clock.currentTimeMillis

packages/opencode/test/session/retry.test.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { SessionV1 } from "@opencode-ai/core/v1/session"
44
import type { NamedError } from "@opencode-ai/core/util/error"
55
import { APICallError } from "ai"
66
import { setTimeout as sleep } from "node:timers/promises"
7-
import { Effect, Schedule, Schema } from "effect"
7+
import { Effect, Exit, Schedule, Schema } from "effect"
88
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
99
import { SessionRetry } from "../../src/session/retry"
1010
import { MessageV2 } from "../../src/session/message-v2"
@@ -115,6 +115,28 @@ describe("session.retry.delay", () => {
115115
})
116116
}),
117117
)
118+
119+
it.instance("policy caps output-length errors at three retries", () =>
120+
Effect.gen(function* () {
121+
const error = new SessionV1.OutputLengthError({}).toObject()
122+
const attempts: number[] = []
123+
const step = yield* Schedule.toStep(
124+
SessionRetry.policy({
125+
provider: "test",
126+
parse: () => error,
127+
set: (info) => Effect.sync(() => attempts.push(info.attempt)),
128+
}),
129+
)
130+
131+
yield* step(0, error)
132+
yield* step(0, error)
133+
yield* step(0, error)
134+
const fourth = yield* step(0, error).pipe(Effect.exit)
135+
136+
expect(attempts).toStrictEqual([1, 2, 3])
137+
expect(Exit.isFailure(fourth)).toBe(true)
138+
}),
139+
)
118140
})
119141

120142
describe("session.retry.retryable", () => {

0 commit comments

Comments
 (0)