fix(opencode): honor catalog output limits in V1

This commit is contained in:
MrMushrooooom
2026-09-29 14:48:45 +00:00
parent 7945de2089
commit da2971a7ec
4 changed files with 59 additions and 8 deletions
+5 -3
View File
@@ -15,7 +15,9 @@ function mimeToModality(mime: string): Modality | undefined {
return undefined
}
export const OUTPUT_TOKEN_MAX = 32_000
export const OUTPUT_TOKEN_MAX = 256_000
const OUTPUT_TOKEN_FALLBACK = 32_000
const REASONING_BUDGET_MAX = 32_000
// OpenAI Responses `include` value that returns the encrypted reasoning state
// needed for stateless multi-turn reasoning (store: false). Hoisted so every
@@ -1479,7 +1481,7 @@ export function providerOptions(model: Provider.Model, options: { [x: string]: a
}
export function maxOutputTokens(model: Provider.Model, outputTokenMax = OUTPUT_TOKEN_MAX): number {
return Math.min(model.limit.output, outputTokenMax) || outputTokenMax
return Math.min(model.limit.output > 0 ? model.limit.output : OUTPUT_TOKEN_FALLBACK, outputTokenMax)
}
type JsonRecord = Record<string, unknown>
@@ -1747,7 +1749,7 @@ function effortVariants(model: Provider.Model, values: readonly unknown[]) {
}
function budgetVariants(model: Provider.Model, min?: number, max?: number) {
const maximum = Math.min(max ?? OUTPUT_TOKEN_MAX - 1, model.limit.output - 1, OUTPUT_TOKEN_MAX - 1)
const maximum = Math.min(max ?? REASONING_BUDGET_MAX - 1, model.limit.output - 1, REASONING_BUDGET_MAX - 1)
if (maximum <= 0) return {}
const high = Math.min(Math.max(min ?? 0, Math.floor((maximum + 1) / 2)), maximum)
return Object.fromEntries(
+4 -1
View File
@@ -16,6 +16,7 @@ import type { Plugin } from "@/plugin"
import { mergeDeep } from "remeda"
const USER_AGENT = `opencode/${InstallationVersion}`
const SUMMARY_OUTPUT_MAX = 32_000
type PrepareInput = {
readonly user: SessionV1.User
@@ -111,6 +112,7 @@ export const prepare = Effect.fn("LLMRequestPrep.prepare")(function* (input: Pre
...input.messages,
]
const outputTokenLimit = ProviderTransform.maxOutputTokens(input.model, input.flags.outputTokenMax)
const params = yield* input.plugin.trigger(
"chat.params",
{
@@ -126,7 +128,8 @@ export const prepare = Effect.fn("LLMRequestPrep.prepare")(function* (input: Pre
: undefined,
topP: input.agent.topP ?? ProviderTransform.topP(input.model),
topK: ProviderTransform.topK(input.model),
maxOutputTokens: ProviderTransform.maxOutputTokens(input.model, input.flags.outputTokenMax),
maxOutputTokens:
input.agent.name === "compaction" ? Math.min(outputTokenLimit, SUMMARY_OUTPUT_MAX) : outputTokenLimit,
options,
},
)
@@ -0,0 +1,28 @@
import { describe, expect, test } from "bun:test"
import type { ConfigV1 } from "@opencode-ai/core/v1/config/config"
import type { Provider } from "@/provider/provider"
import { ProviderTransform } from "@/provider/transform"
import { usable } from "@/session/overflow"
const model = (output: number) => ({ limit: { context: 1_000_000, output } }) as Provider.Model
describe("ProviderTransform.maxOutputTokens", () => {
test("uses the catalog output limit for LongCat instead of 32K", () => {
expect(ProviderTransform.maxOutputTokens(model(131_072))).toBe(131_072)
expect(usable({ cfg: {} as ConfigV1.Info, model: model(131_072) })).toBe(1_000_000 - 131_072)
})
test("keeps smaller model limits and the 32K fallback for unknown output limits", () => {
expect(ProviderTransform.maxOutputTokens(model(8_192))).toBe(8_192)
expect(ProviderTransform.maxOutputTokens(model(0))).toBe(32_000)
})
test("caps exceptionally large catalog limits at the V2 256K ceiling", () => {
expect(ProviderTransform.maxOutputTokens(model(384_000))).toBe(256_000)
})
test("preserves the explicit experimental output-token override", () => {
expect(ProviderTransform.maxOutputTokens(model(131_072), 16_000)).toBe(16_000)
expect(ProviderTransform.maxOutputTokens(model(384_000), 384_000)).toBe(384_000)
})
})
+22 -4
View File
@@ -824,7 +824,7 @@ describe("session.llm.stream", () => {
)
it.instance(
"sends temperature, tokens, and reasoning options for openai-compatible models",
"sends catalog output limits and caps compaction requests for openai-compatible models",
() =>
Effect.gen(function* () {
const fixture = loadFixture(vivgridFixture.providerID, vivgridFixture.modelID)
@@ -840,6 +840,7 @@ describe("session.llm.stream", () => {
ProviderV2.ID.make(vivgridFixture.providerID),
ModelV2.ID.make(fixture.model.id),
)
const model = { ...resolved, limit: { ...resolved.limit, output: 131_072 } }
const sessionID = SessionID.make("session-test-1")
const agent = {
name: "test",
@@ -862,7 +863,7 @@ describe("session.llm.stream", () => {
yield* drain({
user,
sessionID,
model: resolved,
model,
agent,
system: ["You are a helpful assistant."],
messages: [{ role: "user", content: "Hello" }],
@@ -884,11 +885,28 @@ describe("session.llm.stream", () => {
expect(body.stream).toBe(true)
const maxTokens = (body.max_tokens as number | undefined) ?? (body.max_output_tokens as number | undefined)
const expectedMaxTokens = ProviderTransform.maxOutputTokens(resolved)
expect(maxTokens).toBe(expectedMaxTokens)
expect(maxTokens).toBe(131_072)
const reasoning = (body.reasoningEffort as string | undefined) ?? (body.reasoning_effort as string | undefined)
expect(reasoning).toBe("high")
const compaction = waitRequest(
"/chat/completions",
new Response(createChatStream("Summary"), {
status: 200,
headers: { "Content-Type": "text/event-stream" },
}),
)
yield* drain({
user: { ...user, agent: "compaction" },
sessionID,
model,
agent: { ...agent, name: "compaction" },
system: ["Summarize the conversation."],
messages: [{ role: "user", content: "Summarize" }],
tools: {},
})
expect((yield* Effect.promise(() => compaction)).body.max_tokens).toBe(32_000)
}),
{
config: () => ({