From 332b66ce5786eb73de4478d05c3c83f7658332a6 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Thu, 29 Jan 2026 13:50:43 +0000 Subject: [PATCH] fix(lm-studio): only include parallel_tool_calls when explicitly enabled This fixes an issue where GLM4.5 and other models via LM Studio get stuck in a loop repeatedly reading the same file. The problem was introduced in commit ed35b09aa which changed parallel_tool_calls default from false to true. By only including the parallel_tool_calls parameter when explicitly set to true, we maintain compatibility with models that do not support this parameter (e.g., GLM4.5). This follows the same pattern used in commit 2d4dba028 for LiteLLM/Bedrock compatibility. Fixes #11071 --- .../__tests__/lmstudio-native-tools.spec.ts | 17 ++++++++++------- src/api/providers/lm-studio.ts | 4 +++- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/api/providers/__tests__/lmstudio-native-tools.spec.ts b/src/api/providers/__tests__/lmstudio-native-tools.spec.ts index cca543a269b..b195cc1bdd2 100644 --- a/src/api/providers/__tests__/lmstudio-native-tools.spec.ts +++ b/src/api/providers/__tests__/lmstudio-native-tools.spec.ts @@ -82,9 +82,10 @@ describe("LmStudioHandler Native Tools", () => { ]), }), ) - // parallel_tool_calls should be true by default when not explicitly set + // parallel_tool_calls should NOT be included by default for compatibility with + // models that don't support it (e.g., GLM4.5) const callArgs = mockCreate.mock.calls[0][0] - expect(callArgs).toHaveProperty("parallel_tool_calls", true) + expect(callArgs).not.toHaveProperty("parallel_tool_calls") }) it("should include tool_choice when provided", async () => { @@ -128,8 +129,9 @@ describe("LmStudioHandler Native Tools", () => { // Tools are now always present (minimum 6 from ALWAYS_AVAILABLE_TOOLS) expect(callArgs).toHaveProperty("tools") expect(callArgs).toHaveProperty("tool_choice") - // parallel_tool_calls should be true by default when not explicitly set - expect(callArgs).toHaveProperty("parallel_tool_calls", true) + // parallel_tool_calls should NOT be included by default for compatibility with + // models that don't support it (e.g., GLM4.5) + expect(callArgs).not.toHaveProperty("parallel_tool_calls") }) it("should yield tool_call_partial chunks during streaming", async () => { @@ -283,7 +285,7 @@ describe("LmStudioHandler Native Tools", () => { expect(endChunks[0].id).toBe("call_lmstudio_test") }) - it("should work with parallel tool calls disabled (sends false)", async () => { + it("should work with parallel tool calls disabled (omits parameter)", async () => { mockCreate.mockImplementationOnce(() => ({ [Symbol.asyncIterator]: async function* () { yield { @@ -299,9 +301,10 @@ describe("LmStudioHandler Native Tools", () => { }) await stream.next() - // When parallelToolCalls is false, the parameter should be sent as false + // When parallelToolCalls is false, the parameter should NOT be included + // to maintain compatibility with models that don't support it const callArgs = mockCreate.mock.calls[0][0] - expect(callArgs).toHaveProperty("parallel_tool_calls", false) + expect(callArgs).not.toHaveProperty("parallel_tool_calls") }) it("should handle reasoning content alongside tool calls", async () => { diff --git a/src/api/providers/lm-studio.ts b/src/api/providers/lm-studio.ts index a771394c535..a461f790d50 100644 --- a/src/api/providers/lm-studio.ts +++ b/src/api/providers/lm-studio.ts @@ -90,7 +90,9 @@ export class LmStudioHandler extends BaseProvider implements SingleCompletionHan stream: true, tools: this.convertToolsForOpenAI(metadata?.tools), tool_choice: metadata?.tool_choice, - parallel_tool_calls: metadata?.parallelToolCalls ?? true, + // Only include parallel_tool_calls when explicitly enabled to maintain + // compatibility with models that don't support it (e.g., GLM4.5) + ...(metadata?.parallelToolCalls === true && { parallel_tool_calls: true }), } if (this.options.lmStudioSpeculativeDecodingEnabled && this.options.lmStudioDraftModelId) {