fix(codex): preserve Lite tool-use constraints

This commit is contained in:
can1357
2026-07-17 03:57:41 +02:00
parent f961d824bf
commit 1f0d8d9750
2 changed files with 30 additions and 9 deletions
@@ -279,14 +279,14 @@ export interface CodexLiteShapedBody {
* `build_responses_request` with `use_responses_lite`): strips pinned image
* detail, forces parallel tool calling off, moves tools into a leading
* `additional_tools` developer item and the base instructions into a
* developer message, then omits top-level `instructions`/`tools` and forces
* `tool_choice: "auto"`. Because the rewrite removes top-level `tools`, any
* forced hosted-tool choice (e.g. `{ type: "web_search" }`) would leave the
* backend unable to validate the choice against a tools collection and it
* rejects the request with HTTP 400 (#5771); codex-rs always sends
* `tool_choice: "auto"` here. Shared by normal turns and both
* remote-compaction paths — codex-rs routes `/responses/compact` through the
* same builder.
* developer message, then omits top-level `instructions`/`tools`. Because the
* rewrite removes top-level `tools`, a forced hosted-tool choice (e.g.
* `{ type: "web_search" }`) would leave the backend unable to validate the
* choice against a tools collection and it rejects the request with HTTP 400
* (#5771). Such choices must fall back to `"auto"`; explicit string constraints
* such as `"none"` and `"required"` remain valid. Shared by normal turns and
* both remote-compaction paths — codex-rs routes `/responses/compact` through
* the same builder.
*/
export function applyCodexResponsesLiteShape(body: CodexLiteShapedBody): void {
const input = Array.isArray(body.input) ? body.input : [];
@@ -303,7 +303,9 @@ export function applyCodexResponsesLiteShape(body: CodexLiteShapedBody): void {
});
}
body.input = [...prefix, ...input];
body.tool_choice = "auto";
if (body.tool_choice !== "none" && body.tool_choice !== "required") {
body.tool_choice = "auto";
}
delete body.instructions;
delete body.tools;
}
@@ -344,6 +344,25 @@ describe("openai-codex Responses Lite input shaping", () => {
expect(noTools.parallel_tool_calls).toBe(false);
});
it("falls back from forced hosted tool choices without weakening explicit tool-use constraints", async () => {
const model = createCodexModel("gpt-5.6-terra");
const tools = [{ type: "function", name: "handoff", parameters: { type: "object" } }];
const forced = await transformRequestBody(
{ model: model.id, tools, tool_choice: { type: "web_search" } },
model,
{ responsesLite: true },
);
expect(forced.tool_choice).toBe("auto");
expect(forced.tools).toBeUndefined();
const disabled = await transformRequestBody({ model: model.id, tools, tool_choice: "none" }, model, {
responsesLite: true,
});
expect(disabled.tool_choice).toBe("none");
expect(disabled.tools).toBeUndefined();
});
it("moves instructions and tools into input items under lite", async () => {
const model = createCodexModel("gpt-5.6-terra");
const tools = [{ type: "function", name: "shot", parameters: { type: "object" } }];