Skip to content

Commit 379dbf4

Browse files
committed
Merge F6: derive extended chunkTimeout from model.reasoning (audit remediation)
2 parents 5e16159 + 781d7e7 commit 379dbf4

2 files changed

Lines changed: 39 additions & 25 deletions

File tree

packages/opencode/src/provider/provider.ts

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -48,22 +48,23 @@ function shouldUseCopilotResponsesApi(modelID: string): boolean {
4848

4949
const DEFAULT_CHUNK_TIMEOUT_MS = 120_000
5050
const EXTENDED_THINKING_CHUNK_TIMEOUT_MS = 600_000
51-
const EXTENDED_THINKING_PROVIDERS: ReadonlySet<string> = new Set([
52-
"anthropic",
53-
"google-vertex-anthropic",
54-
"amazon-bedrock",
55-
])
5651

57-
export function resolveChunkTimeout(providerID: string, value: unknown): number {
52+
export function resolveChunkTimeout(
53+
model: { readonly providerID: string; readonly reasoning: boolean },
54+
value: unknown,
55+
): number {
5856
if (value === false) return 0
5957
if (typeof value === "number") {
6058
if (!Number.isFinite(value) || value <= 0) return 0
6159
return value
6260
}
63-
if (value !== undefined) log.warn("unrecognized chunkTimeout value, using provider default", { providerID, value })
64-
return EXTENDED_THINKING_PROVIDERS.has(providerID)
65-
? EXTENDED_THINKING_CHUNK_TIMEOUT_MS
66-
: DEFAULT_CHUNK_TIMEOUT_MS
61+
if (value !== undefined)
62+
log.warn("unrecognized chunkTimeout value, using model default", {
63+
providerID: model.providerID,
64+
reasoning: model.reasoning,
65+
value,
66+
})
67+
return model.reasoning ? EXTENDED_THINKING_CHUNK_TIMEOUT_MS : DEFAULT_CHUNK_TIMEOUT_MS
6768
}
6869

6970
export function wrapSSE(res: Response, ms: number, ctl: AbortController) {
@@ -1468,7 +1469,10 @@ const layer: Layer.Layer<
14681469
if (existing) return existing
14691470

14701471
const customFetch = options["fetch"]
1471-
const resolvedChunkTimeout = resolveChunkTimeout(model.providerID, options["chunkTimeout"])
1472+
const resolvedChunkTimeout = resolveChunkTimeout(
1473+
{ providerID: model.providerID, reasoning: model.capabilities.reasoning },
1474+
options["chunkTimeout"],
1475+
)
14721476
delete options["chunkTimeout"]
14731477

14741478
options["fetch"] = async (input: any, init?: BunFetchRequestInit) => {

packages/opencode/test/provider/chunk-timeout.test.ts

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,41 +2,51 @@ import { describe, expect, test } from "bun:test"
22
import { resolveChunkTimeout, SSEStallError, wrapSSE } from "../../src/provider/provider"
33

44
describe("provider.resolveChunkTimeout", () => {
5-
test("returns 120s default when undefined for generic provider", () => {
6-
expect(resolveChunkTimeout("github-copilot", undefined)).toBe(120_000)
5+
test("returns 120s default when undefined and reasoning=false", () => {
6+
expect(resolveChunkTimeout({ providerID: "github-copilot", reasoning: false }, undefined)).toBe(120_000)
77
})
88

9-
test("returns 600s default for Anthropic", () => {
10-
expect(resolveChunkTimeout("anthropic", undefined)).toBe(600_000)
9+
test("returns 600s default when reasoning=true on anthropic", () => {
10+
expect(resolveChunkTimeout({ providerID: "anthropic", reasoning: true }, undefined)).toBe(600_000)
1111
})
1212

13-
test("returns 600s default for google-vertex-anthropic", () => {
14-
expect(resolveChunkTimeout("google-vertex-anthropic", undefined)).toBe(600_000)
13+
test("returns 600s default for reasoning=true regardless of provider ID (openrouter → anthropic/*)", () => {
14+
expect(resolveChunkTimeout({ providerID: "openrouter", reasoning: true }, undefined)).toBe(600_000)
1515
})
1616

17-
test("returns 600s default for amazon-bedrock", () => {
18-
expect(resolveChunkTimeout("amazon-bedrock", undefined)).toBe(600_000)
17+
test("returns 600s default when reasoning=true on google-vertex-anthropic", () => {
18+
expect(resolveChunkTimeout({ providerID: "google-vertex-anthropic", reasoning: true }, undefined)).toBe(600_000)
19+
})
20+
21+
test("returns 600s default when reasoning=true on amazon-bedrock", () => {
22+
expect(resolveChunkTimeout({ providerID: "amazon-bedrock", reasoning: true }, undefined)).toBe(600_000)
23+
})
24+
25+
test("returns 120s default when reasoning=false on anthropic (non-reasoning Claude)", () => {
26+
expect(resolveChunkTimeout({ providerID: "anthropic", reasoning: false }, undefined)).toBe(120_000)
1927
})
2028

2129
test("returns 0 when explicitly disabled with false", () => {
22-
expect(resolveChunkTimeout("github-copilot", false)).toBe(0)
30+
expect(resolveChunkTimeout({ providerID: "github-copilot", reasoning: false }, false)).toBe(0)
2331
})
2432

2533
test("returns the user value when a positive number", () => {
26-
expect(resolveChunkTimeout("github-copilot", 60_000)).toBe(60_000)
34+
expect(resolveChunkTimeout({ providerID: "github-copilot", reasoning: false }, 60_000)).toBe(60_000)
2735
})
2836

2937
test("explicit positive number wins over extended-thinking default", () => {
30-
expect(resolveChunkTimeout("anthropic", 30_000)).toBe(30_000)
38+
expect(resolveChunkTimeout({ providerID: "anthropic", reasoning: true }, 30_000)).toBe(30_000)
3139
})
3240

3341
test("false wins over extended-thinking default (returns 0)", () => {
34-
expect(resolveChunkTimeout("anthropic", false)).toBe(0)
42+
expect(resolveChunkTimeout({ providerID: "anthropic", reasoning: true }, false)).toBe(0)
3543
})
3644

37-
test("falls back to provider default for non-numeric junk", () => {
45+
test("falls back to model default for non-numeric junk", () => {
3846
// Defensive branch — config schema prevents this, but runtime check guards misconfig.
39-
expect(resolveChunkTimeout("github-copilot", "not-a-number" as never)).toBe(120_000)
47+
expect(resolveChunkTimeout({ providerID: "github-copilot", reasoning: false }, "not-a-number" as never)).toBe(
48+
120_000,
49+
)
4050
})
4151
})
4252

0 commit comments

Comments
 (0)