From 2f8ce576dccdc1e1b28ef22da936200652daa8e4 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Mon, 11 May 2026 08:47:49 +0900 Subject: [PATCH] fix(chat-params): use conservative token fallback for invalid limits Avoid inflating non-positive maxOutputTokens values to model capability maxima by always falling back to a safe fixed budget. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- src/plugin/chat-params.test.ts | 46 +++++++++++++++++++++++++++++++--- src/plugin/chat-params.ts | 16 +++++++++--- 2 files changed, 54 insertions(+), 8 deletions(-) diff --git a/src/plugin/chat-params.test.ts b/src/plugin/chat-params.test.ts index de7acb5df..736e36d21 100644 --- a/src/plugin/chat-params.test.ts +++ b/src/plugin/chat-params.test.ts @@ -5,7 +5,7 @@ import { join } from "node:path" import { createChatParamsHandler, type ChatParamsOutput } from "./chat-params" import * as dataPathModule from "../shared/data-path" -import { writeProviderModelsCache } from "../shared" +import * as sharedModule from "../shared" import { clearSessionPromptParams, getSessionPromptParams, @@ -21,13 +21,13 @@ describe("createChatParamsHandler", () => { getCacheDirSpy = spyOn(dataPathModule, "getOmoOpenCodeCacheDir").mockReturnValue( join(tempCacheRoot, "oh-my-opencode"), ) - writeProviderModelsCache({ connected: [], models: {} }) + sharedModule.writeProviderModelsCache({ connected: [], models: {} }) }) afterEach(() => { clearSessionPromptParams("ses_chat_params") clearSessionPromptParams("ses_chat_params_temperature") - writeProviderModelsCache({ connected: [], models: {} }) + sharedModule.writeProviderModelsCache({ connected: [], models: {} }) getCacheDirSpy?.mockRestore() if (tempCacheRoot) { rmSync(tempCacheRoot, { recursive: true, force: true }) @@ -101,7 +101,7 @@ describe("createChatParamsHandler", () => { test("applies stored prompt params for the session", async () => { //#given - writeProviderModelsCache({ + sharedModule.writeProviderModelsCache({ connected: ["openai"], models: { openai: [ @@ -256,6 +256,7 @@ describe("createChatParamsHandler", () => { test("falls back to default maxOutputTokens when stored and compatibility tokens are non-positive", async () => { //#given + const logSpy = spyOn(sharedModule, "log").mockImplementation(() => undefined) setSessionPromptParams("ses_chat_params", { maxOutputTokens: 0, }) @@ -282,6 +283,43 @@ describe("createChatParamsHandler", () => { //#when await handler(input, output) + //#then + expect(output.maxOutputTokens).toBe(4096) + expect(logSpy).toHaveBeenCalledWith( + "[plugin] maxOutputTokens=0 is non-positive; using safe fallback 4096", + ) + + logSpy.mockRestore() + }) + + test("uses safe fallback instead of model max when stored maxOutputTokens is non-positive", async () => { + //#given + setSessionPromptParams("ses_chat_params", { + maxOutputTokens: -1, + }) + + const handler = createChatParamsHandler({ + anthropicEffort: null, + }) + + const input = { + sessionID: "ses_chat_params", + agent: { name: "oracle" }, + model: { providerID: "openai", modelID: "gpt-5.4" }, + provider: { id: "openai" }, + message: {}, + } + + const output: ChatParamsOutput = { + topP: 1, + topK: 1, + maxOutputTokens: -1, + options: {}, + } + + //#when + await handler(input, output) + //#then expect(output.maxOutputTokens).toBe(4096) }) diff --git a/src/plugin/chat-params.ts b/src/plugin/chat-params.ts index ac665b62b..26f35d03d 100644 --- a/src/plugin/chat-params.ts +++ b/src/plugin/chat-params.ts @@ -1,5 +1,7 @@ import { getSessionPromptParams } from "../shared/session-prompt-params-state" -import { getModelCapabilities, resolveCompatibleModelSettings } from "../shared" +import { getModelCapabilities, log, resolveCompatibleModelSettings } from "../shared" + +const SAFE_MAX_OUTPUT_TOKENS_FALLBACK = 4096 export type ChatParamsInput = { sessionID: string @@ -168,9 +170,15 @@ export function createChatParamsHandler(args: { if (compatibility.maxTokens !== undefined && compatibility.maxTokens > 0) { output.maxOutputTokens = compatibility.maxTokens } else { - const capabilitiesLimit = capabilities?.maxOutputTokens - output.maxOutputTokens = - typeof capabilitiesLimit === "number" && capabilitiesLimit > 0 ? capabilitiesLimit : 4096 + const originalMaxOutputTokens = typeof output.maxOutputTokens === "number" + ? output.maxOutputTokens + : compatibility.maxTokens + output.maxOutputTokens = SAFE_MAX_OUTPUT_TOKENS_FALLBACK + if (typeof originalMaxOutputTokens === "number" && originalMaxOutputTokens <= 0) { + log( + `[plugin] maxOutputTokens=${originalMaxOutputTokens} is non-positive; using safe fallback ${SAFE_MAX_OUTPUT_TOKENS_FALLBACK}`, + ) + } } }