mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-02 21:32:10 +03:00
fix(command-code): omit max_tokens when client omits it (#5221)
Cherry-picked the corrective part of #5221 only: the executor stops fabricating `max_tokens` (= per-model registry cap) when the client omits it, which caused `400 "expected <=200000"` on /alpha/generate for high-cap models. An explicit oversized client value is clamped to the 200k endpoint ceiling. The PR's registry maxOutputTokens recaps (open-sse/config/providers/registry/command-code/index.ts) are intentionally NOT included pending reconciliation; #5221 stays open for that.
This commit is contained in:
committed by
Diego Rodrigues de Sa e Souza
parent
6f150cfd2c
commit
e8d13ec17b
@@ -8,6 +8,10 @@
|
|||||||
|
|
||||||
_In development — bullets added per PR; finalized at release._
|
_In development — bullets added per PR; finalized at release._
|
||||||
|
|
||||||
|
### 🔧 Bug Fixes
|
||||||
|
|
||||||
|
- **command-code:** omit `max_tokens` when the client omits it so the upstream applies the model's native default, fixing `400 "expected <=200000"` on `/alpha/generate` for high-cap models; an explicit oversized client value is clamped to the 200k endpoint ceiling (#5221 — thanks @adivekar-utexas)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## [3.8.39] — 2026-06-28
|
## [3.8.39] — 2026-06-28
|
||||||
|
|||||||
@@ -6,6 +6,12 @@ import { BaseExecutor, mergeUpstreamExtraHeaders, type ExecuteInput } from "./ba
|
|||||||
type JsonRecord = Record<string, unknown>;
|
type JsonRecord = Record<string, unknown>;
|
||||||
|
|
||||||
export const COMMAND_CODE_VERSION = process.env.COMMAND_CODE_VERSION?.trim() || "0.33.2";
|
export const COMMAND_CODE_VERSION = process.env.COMMAND_CODE_VERSION?.trim() || "0.33.2";
|
||||||
|
// Hard server-side ceiling enforced by Command Code's /alpha/generate endpoint:
|
||||||
|
// any request with params.max_tokens > 200_000 is rejected with a 400
|
||||||
|
// "Too big: expected number to be <=200000 at params.max_tokens". We only use
|
||||||
|
// this to clamp a CLIENT-SUPPLIED max_tokens down to a value the endpoint will
|
||||||
|
// accept; we never fabricate this number for requests that omit the field (see
|
||||||
|
// clampMaxTokens / buildCommandCodeBody).
|
||||||
const MAX_COMMAND_CODE_TOKENS = 200_000;
|
const MAX_COMMAND_CODE_TOKENS = 200_000;
|
||||||
const encoder = new TextEncoder();
|
const encoder = new TextEncoder();
|
||||||
|
|
||||||
@@ -140,23 +146,16 @@ function convertMessages(messages: unknown): { system: string; messages: unknown
|
|||||||
return { system: system.join("\n\n"), messages: out };
|
return { system: system.join("\n\n"), messages: out };
|
||||||
}
|
}
|
||||||
|
|
||||||
function clampMaxTokens(value: unknown, cap: number = MAX_COMMAND_CODE_TOKENS): number {
|
// Clamp a client-supplied max_tokens to the endpoint ceiling, mirroring the
|
||||||
const numeric = numberValue(value) ?? cap;
|
// provider-driven clamp in antigravity.ts: we only intervene when the value is
|
||||||
return Math.max(1, Math.min(Math.floor(numeric), cap));
|
// present AND would otherwise be rejected (> 200_000). A valid value is
|
||||||
}
|
// returned floored; anything absent or non-numeric returns undefined so the
|
||||||
|
// caller can OMIT the field entirely and let Command Code's upstream apply the
|
||||||
// Resolve the per-model max_tokens cap for a given CommandCode model id.
|
// model's own native default (rather than us inventing a number).
|
||||||
// Falls back to MAX_COMMAND_CODE_TOKENS when the model isn't registered or
|
function clampMaxTokens(value: unknown): number | undefined {
|
||||||
// when the registry entry omits maxOutputTokens. Without this, GLM-5.x
|
const numeric = numberValue(value);
|
||||||
// requests get capped at 200_000 and rejected with "限制数值范围[1,131072]".
|
if (numeric === undefined) return undefined;
|
||||||
function getModelMaxTokensCap(modelId: string): number {
|
return Math.max(1, Math.min(Math.floor(numeric), MAX_COMMAND_CODE_TOKENS));
|
||||||
const entry = REGISTRY["command-code"];
|
|
||||||
if (!entry) return MAX_COMMAND_CODE_TOKENS;
|
|
||||||
const model = entry.models?.find((m: { id: string }) => m.id === modelId);
|
|
||||||
const registryCap = (model as { maxOutputTokens?: number } | undefined)?.maxOutputTokens;
|
|
||||||
return typeof registryCap === "number" && registryCap > 0
|
|
||||||
? registryCap
|
|
||||||
: MAX_COMMAND_CODE_TOKENS;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reasoning/thinking fields that payload rules or clients may inject and that
|
// Reasoning/thinking fields that payload rules or clients may inject and that
|
||||||
@@ -188,13 +187,20 @@ function buildCommandCodeBody(model: string, body: unknown, stream = false): Jso
|
|||||||
messages: converted.messages,
|
messages: converted.messages,
|
||||||
tools: convertTools(input.tools),
|
tools: convertTools(input.tools),
|
||||||
system,
|
system,
|
||||||
max_tokens: clampMaxTokens(
|
|
||||||
input.max_tokens ?? input.max_completion_tokens,
|
|
||||||
getModelMaxTokensCap(resolvedModel)
|
|
||||||
),
|
|
||||||
stream: true,
|
stream: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Only forward max_tokens when the client actually supplied one. Omitting it
|
||||||
|
// lets Command Code's upstream apply the model's own native default, so we
|
||||||
|
// never invent a value (the old behavior, which sent the wrong number and got
|
||||||
|
// DeepSeek V4 rejected with "Too big: expected number to be <=200000"). When
|
||||||
|
// present, it is clamped to the endpoint ceiling so an oversized client value
|
||||||
|
// degrades gracefully instead of 400ing.
|
||||||
|
const maxTokens = clampMaxTokens(input.max_tokens ?? input.max_completion_tokens);
|
||||||
|
if (maxTokens !== undefined) {
|
||||||
|
params.max_tokens = maxTokens;
|
||||||
|
}
|
||||||
|
|
||||||
for (const field of COMMAND_CODE_PASSTHROUGH_FIELDS) {
|
for (const field of COMMAND_CODE_PASSTHROUGH_FIELDS) {
|
||||||
const value = input[field];
|
const value = input[field];
|
||||||
if (value !== undefined && value !== null) {
|
if (value !== undefined && value !== null) {
|
||||||
|
|||||||
@@ -294,40 +294,60 @@ test("Command Code executor surfaces upstream and streamed errors", async () =>
|
|||||||
}, /boom/);
|
}, /boom/);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Command Code executor caps max_tokens to the registered per-model limit (GLM-5.x)", async () => {
|
test("Command Code executor omits max_tokens when the client does not supply one (GLM-5.x)", async () => {
|
||||||
const calls: FetchCall[] = [];
|
const calls: FetchCall[] = [];
|
||||||
globalThis.fetch = async (url, init = {}) => {
|
globalThis.fetch = async (url, init = {}) => {
|
||||||
calls.push({ url: String(url), init, body: JSON.parse(String(init.body)) });
|
calls.push({ url: String(url), init, body: JSON.parse(String(init.body)) });
|
||||||
return commandCodeStream([{ type: "text-delta", text: "ok" }, { type: "finish" }]);
|
return commandCodeStream([{ type: "text-delta", text: "ok" }, { type: "finish" }]);
|
||||||
};
|
};
|
||||||
|
|
||||||
// GLM-5 and GLM-5.1 are registered with maxOutputTokens: 131072.
|
// No client max_tokens: we must NOT fabricate one. Omitting the field lets
|
||||||
// Without per-model capping, the upstream rejects with
|
// Command Code's upstream apply the model's own native default.
|
||||||
// "限制数值范围[1,131072]".
|
|
||||||
await getExecutor("command-code").execute({
|
await getExecutor("command-code").execute({
|
||||||
model: "zai-org/GLM-5.1",
|
model: "zai-org/GLM-5.1",
|
||||||
stream: false,
|
stream: false,
|
||||||
credentials: { apiKey: "cc_test_key" },
|
credentials: { apiKey: "cc_test_key" },
|
||||||
body: { messages: [{ role: "user", content: "Hi" }] },
|
body: { messages: [{ role: "user", content: "Hi" }] },
|
||||||
});
|
});
|
||||||
assert.equal(calls[0].body.params.max_tokens, 131072);
|
assert.ok(!("max_tokens" in calls[0].body.params));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Command Code executor caps max_tokens to the registered per-model limit (DeepSeek v4)", async () => {
|
test("Command Code executor omits max_tokens for DeepSeek v4 when the client does not supply one", async () => {
|
||||||
const calls: FetchCall[] = [];
|
const calls: FetchCall[] = [];
|
||||||
globalThis.fetch = async (url, init = {}) => {
|
globalThis.fetch = async (url, init = {}) => {
|
||||||
calls.push({ url: String(url), init, body: JSON.parse(String(init.body)) });
|
calls.push({ url: String(url), init, body: JSON.parse(String(init.body)) });
|
||||||
return commandCodeStream([{ type: "text-delta", text: "ok" }, { type: "finish" }]);
|
return commandCodeStream([{ type: "text-delta", text: "ok" }, { type: "finish" }]);
|
||||||
};
|
};
|
||||||
|
|
||||||
// DeepSeek v4 pro is registered with maxOutputTokens: 384000.
|
// Regression: previously the executor invented max_tokens from the registry
|
||||||
|
// (384000), which /alpha/generate rejects with a 400
|
||||||
|
// "Too big: expected number to be <=200000". With no client value we now omit
|
||||||
|
// the field entirely, so the request succeeds and upstream picks the default.
|
||||||
await getExecutor("command-code").execute({
|
await getExecutor("command-code").execute({
|
||||||
model: "deepseek/deepseek-v4-pro",
|
model: "deepseek/deepseek-v4-pro",
|
||||||
stream: false,
|
stream: false,
|
||||||
credentials: { apiKey: "cc_test_key" },
|
credentials: { apiKey: "cc_test_key" },
|
||||||
body: { messages: [{ role: "user", content: "Hi" }] },
|
body: { messages: [{ role: "user", content: "Hi" }] },
|
||||||
});
|
});
|
||||||
assert.equal(calls[0].body.params.max_tokens, 384000);
|
assert.ok(!("max_tokens" in calls[0].body.params));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Command Code executor clamps an oversized client-supplied max_tokens to the endpoint ceiling", async () => {
|
||||||
|
const calls: FetchCall[] = [];
|
||||||
|
globalThis.fetch = async (url, init = {}) => {
|
||||||
|
calls.push({ url: String(url), init, body: JSON.parse(String(init.body)) });
|
||||||
|
return commandCodeStream([{ type: "text-delta", text: "ok" }, { type: "finish" }]);
|
||||||
|
};
|
||||||
|
|
||||||
|
// A client asking for more than the 200000 endpoint ceiling is clamped down
|
||||||
|
// (not 400'd), mirroring the provider-driven clamp in antigravity.ts.
|
||||||
|
await getExecutor("command-code").execute({
|
||||||
|
model: "deepseek/deepseek-v4-pro",
|
||||||
|
stream: false,
|
||||||
|
credentials: { apiKey: "cc_test_key" },
|
||||||
|
body: { messages: [{ role: "user", content: "Hi" }], max_tokens: 500000 },
|
||||||
|
});
|
||||||
|
assert.equal(calls[0].body.params.max_tokens, 200000);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Command Code executor honors a smaller client-provided max_tokens under the per-model cap", async () => {
|
test("Command Code executor honors a smaller client-provided max_tokens under the per-model cap", async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user