fix(command-code): pass reasoning/thinking fields through to params (#4473)

Thanks @adivekar-utexas! Rebuilt on release/v3.8.32 with just the command-code commits (the combo half landed via #4472). reasoning/thinking passthrough + body.model rewrite now ship.
This commit is contained in:
Abhishek Divekar
2026-06-21 17:06:25 +05:30
committed by GitHub
parent 6c8c87dbb6
commit 3e980a0b2b
2 changed files with 98 additions and 11 deletions

View File

@@ -145,12 +145,46 @@ function clampMaxTokens(value: unknown): number {
return Math.max(1, Math.min(Math.floor(numeric), MAX_COMMAND_CODE_TOKENS));
}
// Reasoning/thinking fields that payload rules or clients may inject and that
// CommandCode's upstream accepts inside `params`. Without this pass-through,
// payload-rule overrides on these fields are silently dropped (#2986 follow-up).
const COMMAND_CODE_PASSTHROUGH_FIELDS = [
"reasoning_effort",
"reasoning",
"thinking",
"effort",
"output_config",
"extra_body",
] as const;
function buildCommandCodeBody(model: string, body: unknown, stream = false): JsonRecord {
const input = isRecord(body) ? body : {};
const converted = convertMessages(input.messages);
const explicitSystem = typeof input.system === "string" ? input.system : "";
const system = [converted.system, explicitSystem].filter(Boolean).join("\n\n");
// Payload rules may rewrite `body.model` (e.g. deepseek-v4-pro-max →
// deepseek/deepseek-v4-pro for the command-code provider). Prefer the
// rewritten value if present; fall back to the resolved combo model arg.
const resolvedModel =
typeof input.model === "string" && input.model.trim().length > 0 ? input.model : model;
const params: JsonRecord = {
model: resolvedModel,
messages: converted.messages,
tools: convertTools(input.tools),
system,
max_tokens: clampMaxTokens(input.max_tokens ?? input.max_completion_tokens),
stream: true,
};
for (const field of COMMAND_CODE_PASSTHROUGH_FIELDS) {
const value = input[field];
if (value !== undefined && value !== null) {
params[field] = value;
}
}
return {
config: {
workingDir: "/workspace",
@@ -167,14 +201,7 @@ function buildCommandCodeBody(model: string, body: unknown, stream = false): Jso
taste: "",
skills: "",
permissionMode: "standard",
params: {
model,
messages: converted.messages,
tools: convertTools(input.tools),
system,
max_tokens: clampMaxTokens(input.max_tokens ?? input.max_completion_tokens),
stream: true,
},
params,
};
}

View File

@@ -46,7 +46,7 @@ function commandCodeStream(lines: unknown[], { sse = false } = {}) {
return new Response(text, { status: 200, headers: { "Content-Type": "application/x-ndjson" } });
}
function toPlainHeaders(headers: any) {
function toPlainHeaders(headers: Headers | Record<string, string>) {
if (headers instanceof Headers) return Object.fromEntries(headers.entries());
return Object.fromEntries(Object.entries(headers).map(([key, value]) => [key, String(value)]));
}
@@ -90,8 +90,10 @@ test("getExecutor returns the specialized Command Code executor", () => {
assert.ok(getExecutor("cmd") instanceof CommandCodeExecutor);
});
type FetchCall = { url: string; init: Record<string, unknown>; body?: unknown };
test("Command Code executor posts wrapped body and required headers to /alpha/generate", async () => {
const calls: any[] = [];
const calls: FetchCall[] = [];
globalThis.fetch = async (url, init = {}) => {
calls.push({ url: String(url), init });
return commandCodeStream([{ type: "text-delta", text: "hello" }, { type: "finish" }]);
@@ -141,8 +143,66 @@ test("Command Code executor posts wrapped body and required headers to /alpha/ge
assert.equal(json.choices[0].message.content, "hello");
});
test("Command Code executor passes reasoning/thinking fields through to params (#2986 follow-up)", async () => {
const calls: FetchCall[] = [];
globalThis.fetch = async (url, init = {}) => {
calls.push({ url: String(url), init });
return commandCodeStream([{ type: "text-delta", text: "ok" }, { type: "finish" }]);
};
await getExecutor("command-code").execute({
model: "deepseek/deepseek-v4-pro",
stream: false,
credentials: { apiKey: "cc_test_key" },
body: {
stream: false,
messages: [{ role: "user", content: "Hi" }],
reasoning_effort: "high",
thinking: { type: "enabled" },
effort: "high",
output_config: { effort: "high" },
extra_body: { enable_thinking: true },
},
});
const posted = JSON.parse(String(calls[0].init.body));
assert.equal(posted.params.reasoning_effort, "high");
assert.deepEqual(posted.params.thinking, { type: "enabled" });
assert.equal(posted.params.effort, "high");
assert.deepEqual(posted.params.output_config, { effort: "high" });
assert.deepEqual(posted.params.extra_body, { enable_thinking: true });
});
test("Command Code executor honors body.model rewrite from payload rules", async () => {
const calls: FetchCall[] = [];
globalThis.fetch = async (url, init = {}) => {
calls.push({ url: String(url), init });
return commandCodeStream([{ type: "text-delta", text: "ok" }, { type: "finish" }]);
};
// Simulate a payload-rule rewrite: combo resolves to "deepseek-v4-pro-max"
// (passed as the execute() model arg), but the payload rule overwrites
// body.model to "deepseek/deepseek-v4-pro" (the vendor-prefixed form
// Command Code's API expects).
await getExecutor("command-code").execute({
model: "deepseek-v4-pro-max",
stream: false,
credentials: { apiKey: "cc_test_key" },
body: {
stream: false,
model: "deepseek/deepseek-v4-pro",
messages: [{ role: "user", content: "Hi" }],
reasoning_effort: "max",
},
});
const posted = JSON.parse(String(calls[0].init.body));
assert.equal(posted.params.model, "deepseek/deepseek-v4-pro");
assert.equal(posted.params.reasoning_effort, "max");
});
test("Command Code raw NDJSON stream becomes OpenAI chat SSE chunks", async () => {
const calls: any[] = [];
const calls: FetchCall[] = [];
globalThis.fetch = async (url, init = {}) => {
calls.push({ url: String(url), init, body: JSON.parse(String(init.body)) });
return commandCodeStream([