refactor(ai/providers): removed parallel tool call restriction
- Removed the logic that forced sequential tool calling when custom tools were present in both Codex and OpenAI providers. - Exported `buildTransformedCodexRequestBody` for testing purposes.
This commit is contained in:
@@ -257,8 +257,9 @@ session/provider path.
|
||||
### Responses and Codex custom tools
|
||||
|
||||
Responses and Codex both support freeform custom grammar tools for `apply_patch`.
|
||||
Both disable request-level parallel tool calls when any custom grammar tool is
|
||||
present. Responses additionally:
|
||||
Custom grammar tools do not force request-level `parallel_tool_calls`; Codex
|
||||
`responsesLite` separately disables request-level parallel tool calls whenever
|
||||
tools are present. Responses additionally:
|
||||
|
||||
- sanitizes schemas differently
|
||||
- quarantines invalid enum/const schema contradictions
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Changed
|
||||
|
||||
- Changed OpenAI Responses and Codex Responses custom grammar tool requests to leave `parallel_tool_calls` unset instead of forcing serial tool calls; Codex `responsesLite` still disables parallel tool calls when tools are present.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed Bedrock `/btw` and other no-tool ephemeral turns failing after prior tool calls by sending the required sentinel `toolConfig` whenever replayed history contains `toolUse`/`toolResult` blocks. ([#3124](https://github.com/can1357/oh-my-pi/issues/3124))
|
||||
|
||||
@@ -714,7 +714,8 @@ async function buildCodexRequestContext(
|
||||
};
|
||||
}
|
||||
|
||||
async function buildTransformedCodexRequestBody(
|
||||
/** @internal Exported for tests. */
|
||||
export async function buildTransformedCodexRequestBody(
|
||||
model: Model<"openai-codex-responses">,
|
||||
context: Context,
|
||||
options: OpenAICodexResponsesOptions | undefined,
|
||||
@@ -744,16 +745,6 @@ async function buildTransformedCodexRequestBody(
|
||||
params.tool_choice = toolChoice;
|
||||
}
|
||||
}
|
||||
// When a custom-tool is active, force serial tool-calling. OpenAI's
|
||||
// `parallel_tool_calls` is request-scoped — disabling it here affects
|
||||
// every tool in the turn, not just the custom one. That's coarser
|
||||
// than spec §1's "supports_parallel_tool_calls = false" (which
|
||||
// strictly targets `apply_patch`), but the platform API offers no
|
||||
// per-tool flag.
|
||||
const emittedTools = params.tools as CodexToolPayload[];
|
||||
if (emittedTools.some(t => t.type === "custom")) {
|
||||
params.parallel_tool_calls = false;
|
||||
}
|
||||
}
|
||||
|
||||
const systemPrompts = normalizeSystemPrompts(context.systemPrompt);
|
||||
|
||||
@@ -862,15 +862,6 @@ export function buildParams(
|
||||
params.tool_choice = toolChoice;
|
||||
}
|
||||
}
|
||||
// The apply_patch spec §1 marks only `apply_patch` itself as
|
||||
// `supports_parallel_tool_calls = false`. OpenAI's Responses API
|
||||
// exposes `parallel_tool_calls` as a request-scoped flag, not a
|
||||
// per-tool one, so when a custom grammar tool is in the list we
|
||||
// disable parallelism for the whole turn. Slightly coarser than
|
||||
// the spec requires — but the platform API offers no finer knob.
|
||||
if (params.tools.some(t => (t as { type?: string }).type === "custom")) {
|
||||
params.parallel_tool_calls = false;
|
||||
}
|
||||
}
|
||||
|
||||
const reasoningPolicy = resolveOpenAICompatPolicy(model, {
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import {
|
||||
buildTransformedCodexRequestBody,
|
||||
convertOpenAICodexResponsesTools as convertCodexTools,
|
||||
normalizeCodexToolChoice,
|
||||
} from "@oh-my-pi/pi-ai/providers/openai-codex-responses";
|
||||
import {
|
||||
buildParams,
|
||||
convertTools,
|
||||
mapOpenAIResponsesToolChoiceForTools,
|
||||
supportsFreeformApplyPatch,
|
||||
@@ -74,6 +76,13 @@ const plainTool: Tool = {
|
||||
parameters: type({ path: "string" }),
|
||||
};
|
||||
|
||||
function hasCustomTool(tools: unknown): boolean {
|
||||
return (
|
||||
Array.isArray(tools) &&
|
||||
tools.some(tool => typeof tool === "object" && tool !== null && (tool as { type?: unknown }).type === "custom")
|
||||
);
|
||||
}
|
||||
|
||||
const unionBranches = [
|
||||
{
|
||||
type: "object",
|
||||
@@ -221,6 +230,31 @@ describe("tool choice mapping: freeform emission", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("request params: freeform custom tools", () => {
|
||||
test("openai responses leaves parallel tool calls unset", () => {
|
||||
const { params } = buildParams(
|
||||
makeModel({ applyPatchToolType: "freeform" }),
|
||||
{ messages: [{ role: "user", content: "edit", timestamp: 0 }], tools: [editTool] },
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
|
||||
expect(hasCustomTool(params.tools)).toBe(true);
|
||||
expect(params.parallel_tool_calls).toBeUndefined();
|
||||
});
|
||||
|
||||
test("codex responses leaves parallel tool calls unset for custom tools", async () => {
|
||||
const params = await buildTransformedCodexRequestBody(
|
||||
makeCodexModel({ applyPatchToolType: "freeform" }),
|
||||
{ messages: [{ role: "user", content: "edit", timestamp: 0 }], tools: [editTool] },
|
||||
undefined,
|
||||
);
|
||||
|
||||
expect(hasCustomTool(params.tools)).toBe(true);
|
||||
expect(params.parallel_tool_calls).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("custom_tool_call stream receive", () => {
|
||||
async function* makeStream(events: unknown[]): AsyncIterable<ResponseStreamEvent> {
|
||||
for (const e of events) yield e as ResponseStreamEvent;
|
||||
|
||||
Reference in New Issue
Block a user