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
This commit is contained in:
Roo Code 2026-01-29 13:50:43 +00:00
parent 40b2bdc4d0
commit 332b66ce57
2 changed files with 13 additions and 8 deletions

View file

@ -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 () => {

View file

@ -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) {