Skip to content

Commit 2d6ed17

Browse files
committed
feat(provider): resolveChunkTimeout + wrapSSE wiring
- Add resolveChunkTimeout(providerID, value) helper: explicit number/false wins, otherwise DEFAULT_CHUNK_TIMEOUT_MS=120000 or EXTENDED_THINKING_CHUNK_TIMEOUT_MS=600000 for anthropic/vertex-anthropic/ bedrock. - Export wrapSSE for test import. - Replace direct options.chunkTimeout read with resolveChunkTimeout at the model-factory fetch path so SSE stalls are detected by default on every provider. Makes A.1.3's tests green. Addresses root cause #1 (no default chunk timeout) from the plan.
1 parent 65917b4 commit 2d6ed17

1 file changed

Lines changed: 24 additions & 4 deletions

File tree

packages/opencode/src/provider/provider.ts

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,27 @@ function shouldUseCopilotResponsesApi(modelID: string): boolean {
4545
return Number(match[1]) >= 5 && !modelID.startsWith("gpt-5-mini")
4646
}
4747

48-
function wrapSSE(res: Response, ms: number, ctl: AbortController) {
48+
const DEFAULT_CHUNK_TIMEOUT_MS = 120_000
49+
const EXTENDED_THINKING_CHUNK_TIMEOUT_MS = 600_000
50+
const EXTENDED_THINKING_PROVIDERS: ReadonlySet<string> = new Set([
51+
"anthropic",
52+
"google-vertex-anthropic",
53+
"amazon-bedrock",
54+
])
55+
56+
export function resolveChunkTimeout(providerID: string, value: unknown): number {
57+
if (value === false) return 0
58+
if (typeof value === "number") {
59+
if (!Number.isFinite(value) || value <= 0) return 0
60+
return value
61+
}
62+
if (value !== undefined) log.warn("unrecognized chunkTimeout value, using provider default", { providerID, value })
63+
return EXTENDED_THINKING_PROVIDERS.has(providerID)
64+
? EXTENDED_THINKING_CHUNK_TIMEOUT_MS
65+
: DEFAULT_CHUNK_TIMEOUT_MS
66+
}
67+
68+
export function wrapSSE(res: Response, ms: number, ctl: AbortController) {
4969
if (typeof ms !== "number" || ms <= 0) return res
5070
if (!res.body) return res
5171
if (!res.headers.get("content-type")?.includes("text/event-stream")) return res
@@ -1437,13 +1457,13 @@ const layer: Layer.Layer<
14371457
if (existing) return existing
14381458

14391459
const customFetch = options["fetch"]
1440-
const chunkTimeout = options["chunkTimeout"]
1460+
const resolvedChunkTimeout = resolveChunkTimeout(model.providerID, options["chunkTimeout"])
14411461
delete options["chunkTimeout"]
14421462

14431463
options["fetch"] = async (input: any, init?: BunFetchRequestInit) => {
14441464
const fetchFn = customFetch ?? fetch
14451465
const opts = init ?? {}
1446-
const chunkAbortCtl = typeof chunkTimeout === "number" && chunkTimeout > 0 ? new AbortController() : undefined
1466+
const chunkAbortCtl = resolvedChunkTimeout > 0 ? new AbortController() : undefined
14471467
const signals: AbortSignal[] = []
14481468

14491469
if (opts.signal) signals.push(opts.signal)
@@ -1476,7 +1496,7 @@ const layer: Layer.Layer<
14761496
})
14771497

14781498
if (!chunkAbortCtl) return res
1479-
return wrapSSE(res, chunkTimeout, chunkAbortCtl)
1499+
return wrapSSE(res, resolvedChunkTimeout, chunkAbortCtl)
14801500
}
14811501

14821502
const bundledLoader = BUNDLED_PROVIDERS[model.api.npm]

0 commit comments

Comments
 (0)