fix(agents): forward model maxTokens by default

This commit is contained in:
Peter Steinberger
2026-05-03 14:09:49 +01:00
parent 41bbc4c048
commit 399d7f6178
3 changed files with 88 additions and 7 deletions

View File

@@ -23,6 +23,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- Agents/models: forward model `maxTokens` as the default output-token limit for OpenAI-compatible Responses and Completions transports when no runtime override is provided, preventing provider defaults from silently truncating larger outputs. (#76645) Thanks @joeyfrasier.
- Gateway/update: run `doctor --non-interactive --fix` after Control UI global package updates before reporting success, so legacy config is migrated before the gateway restart. Thanks @stevenchouai.
- Gateway/cron: stop a lazy cron startup that loses a hot-reload race, preventing the old cron service from starting after reload has already replaced cron state.
- CLI/plugins: warn when npm plugin installs remain shadowed by a failing config-selected source and surface the repair path in `plugins doctor`. Thanks @LindalyX-Lee.

View File

@@ -995,6 +995,31 @@ describe("openai transport stream", () => {
expect(params.input?.[0]).toMatchObject({ role: "developer" });
});
it("uses model maxTokens for Responses params when runtime maxTokens is omitted", () => {
const params = buildOpenAIResponsesParams(
{
id: "gpt-5.4",
name: "GPT-5.4",
api: "openai-responses",
provider: "openai",
baseUrl: "https://api.openai.com/v1",
reasoning: true,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 200000,
maxTokens: 65_536,
} satisfies Model<"openai-responses">,
{
systemPrompt: "system",
messages: [],
tools: [],
} as never,
undefined,
) as { max_output_tokens?: unknown };
expect(params.max_output_tokens).toBe(65_536);
});
it("uses top-level instructions for Codex responses and strips unsupported ChatGPT params", () => {
const params = buildOpenAIResponsesParams(
{
@@ -2373,6 +2398,57 @@ describe("openai transport stream", () => {
expect(params).not.toHaveProperty("max_completion_tokens");
});
it("uses model maxTokens for OpenAI completions params when runtime maxTokens is omitted", () => {
const params = buildOpenAICompletionsParams(
{
id: "gpt-5.4",
name: "GPT-5.4",
api: "openai-completions",
provider: "openai",
baseUrl: "https://api.openai.com/v1",
reasoning: true,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 200000,
maxTokens: 65_536,
} satisfies Model<"openai-completions">,
{
systemPrompt: "system",
messages: [],
tools: [],
} as never,
undefined,
);
expect(params.max_completion_tokens).toBe(65_536);
expect(params).not.toHaveProperty("max_tokens");
});
it("uses model maxTokens with max_tokens completions compat when runtime maxTokens is omitted", () => {
const params = buildOpenAICompletionsParams(
{
id: "zai-org/GLM-4.7-TEE",
name: "GLM 4.7 TEE",
api: "openai-completions",
provider: "chutes",
reasoning: true,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 200000,
maxTokens: 65_536,
} as never,
{
systemPrompt: "system",
messages: [],
tools: [],
} as never,
undefined,
);
expect(params.max_tokens).toBe(65_536);
expect(params).not.toHaveProperty("max_completion_tokens");
});
it("omits strict tool shaping for Z.ai default-route completions providers", () => {
const params = buildOpenAICompletionsParams(
{

View File

@@ -981,8 +981,9 @@ export function buildOpenAIResponsesParams(
...(isCodexResponses ? { instructions: buildOpenAICodexResponsesInstructions(context) } : {}),
...(metadata ? { metadata } : {}),
};
if (options?.maxTokens) {
params.max_output_tokens = options.maxTokens;
const effectiveMaxTokens = options?.maxTokens || model.maxTokens;
if (effectiveMaxTokens) {
params.max_output_tokens = effectiveMaxTokens;
}
if (options?.temperature !== undefined) {
params.temperature = options.temperature;
@@ -1863,11 +1864,14 @@ export function buildOpenAICompletionsParams(
if (compat.supportsPromptCacheKey && cacheRetention !== "none" && options?.sessionId) {
params.prompt_cache_key = options.sessionId;
}
if (options?.maxTokens) {
if (compat.maxTokensField === "max_tokens") {
params.max_tokens = options.maxTokens;
} else {
params.max_completion_tokens = options.maxTokens;
{
const effectiveMaxTokens = options?.maxTokens || model.maxTokens;
if (effectiveMaxTokens) {
if (compat.maxTokensField === "max_tokens") {
params.max_tokens = effectiveMaxTokens;
} else {
params.max_completion_tokens = effectiveMaxTokens;
}
}
}
if (options?.temperature !== undefined) {