fix(coding-agent): remove explicit tool choice from compaction calls

Fixes #8649\n\nFixes #8638
This commit is contained in:
Vegard Stikbakke
2026-08-26 11:20:44 +02:00
parent 7af2d27dce
commit 6b36eb5927
8 changed files with 10 additions and 11 deletions
+1 -1
View File
@@ -4,7 +4,7 @@
### Fixed
- Fixed OpenAI-compatible Chat Completions requests sending `tool_choice` without tools, which gateways can reject during compaction ([#8607](https://github.com/earendil-works/pi/issues/8607)).
- Fixed OpenAI-compatible Chat Completions ignoring an explicitly requested `toolChoice` when no tools are defined.
## [0.84.3] - 2026-08-24
+1 -1
View File
@@ -835,7 +835,7 @@ function buildParams(
applyAnthropicCacheControl(messages, params.tools, cacheControl);
}
if (options?.toolChoice && params.tools?.length) {
if (options?.toolChoice) {
params.tool_choice = options.toolChoice;
}
+1 -1
View File
@@ -312,7 +312,7 @@ export interface AnthropicAllowedFallbackModel {
// Unified options with reasoning passed to streamSimple() and completeSimple()
export interface SimpleStreamOptions extends StreamOptions {
/** Provider-neutral tool selection for simple requests. Default: "auto". */
/** Provider-neutral tool selection for simple requests. When omitted, adapters use provider-specific behavior. */
toolChoice?: ToolChoice;
reasoning?: ThinkingLevel;
/** Ask a capable provider to return a durable handle and continue the request asynchronously. */
@@ -150,7 +150,7 @@ describe("openai-completions tool_choice", () => {
expect(params.tools?.length ?? 0).toBeGreaterThan(0);
});
it("omits toolChoice when no tools are provided", async () => {
it("includes toolChoice when no tools are provided", async () => {
const { compat: _compat, ...baseModel } = getModel("openai", "gpt-4o-mini")!;
const model = { ...baseModel, api: "openai-completions" } as const;
let payload: unknown;
@@ -170,7 +170,7 @@ describe("openai-completions tool_choice", () => {
).result();
const params = (payload ?? mockState.lastParams) as { tool_choice?: string; tools?: unknown[] };
expect(params).not.toHaveProperty("tool_choice");
expect(params.tool_choice).toBe("none");
expect(params).not.toHaveProperty("tools");
});
+1
View File
@@ -10,6 +10,7 @@
- Fixed Windows shell aborts crashing Pi when `taskkill.exe` is unavailable on `PATH` ([#6596](https://github.com/earendil-works/pi/issues/6596)).
- Fixed extension messages sent with `triggerTurn: false` while the agent is running being inserted between a tool call and its result, which made providers that validate message order reject the replayed history. They are now appended once the turn's tool results are in ([#8537](https://github.com/earendil-works/pi/issues/8537)).
- Fixed compaction and branch summaries forcing `toolChoice: "none"` ([#8649](https://github.com/earendil-works/pi/issues/8649), [#8638](https://github.com/earendil-works/pi/issues/8638)).
## [0.84.3] - 2026-08-24
@@ -590,7 +590,6 @@ export async function completeSummarization(
...options,
cacheRetention: "none",
sessionId: options.sessionId ?? uuidv7(),
toolChoice: "none",
};
const produce = async (): Promise<AssistantMessage> =>
streamFn
@@ -44,7 +44,7 @@ function response(content: AssistantMessage["content"]): AssistantMessage {
}
describe("branch summarization", () => {
it("disables tools for branch summaries", async () => {
it("does not override tool choice for branch summaries", async () => {
let requestOptions: SimpleStreamOptions | undefined;
const streamFn: StreamFn = (_model, _context, options) => {
requestOptions = options;
@@ -61,7 +61,7 @@ describe("branch summarization", () => {
streamFn,
});
expect(requestOptions?.toolChoice).toBe("none");
expect(requestOptions?.toolChoice).toBeUndefined();
});
it("rejects tool calls from branch summaries", async () => {
@@ -109,13 +109,12 @@ describe("generateSummary reasoning options", () => {
const requestOptions = completeSimpleMock.mock.calls.map((call) => call[2]);
expect(requestOptions).toHaveLength(2);
expect(requestOptions.every((options) => options?.cacheRetention === "none")).toBe(true);
expect(requestOptions.every((options) => options?.toolChoice === "none")).toBe(true);
const sessionIds = requestOptions.map((options) => options?.sessionId);
expect(sessionIds[0]).not.toBe(sessionIds[1]);
});
it("honors a caller-supplied routing session without prompt caching", async () => {
it("honors caller-supplied routing session and tool choice without prompt caching", async () => {
await completeSummarization(
createModel(false),
{ systemPrompt: "Summarize", messages: [] },
@@ -125,7 +124,7 @@ describe("generateSummary reasoning options", () => {
expect(completeSimpleMock.mock.calls[0][2]).toMatchObject({
sessionId: "current-routing-session",
cacheRetention: "none",
toolChoice: "none",
toolChoice: "auto",
});
});