mirror of
https://gitee.com/zhangsken/opencode.git
synced 2026-10-16 10:30:01 +08:00
fix(opencode): honor catalog output limits in V1
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
@@ -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: () => ({
|
||||
|
||||
Reference in New Issue
Block a user