From 0d212efdbb01fbf4fc12f76430f403b89c0c7fe7 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Fri, 23 Jan 2026 00:06:27 +0000 Subject: [PATCH] test: remove if conditional and add test to verify Mistral no-user-after-tool constraint --- src/api/providers/__tests__/openai.spec.ts | 74 ++++++++++++++++++++-- 1 file changed, 69 insertions(+), 5 deletions(-) diff --git a/src/api/providers/__tests__/openai.spec.ts b/src/api/providers/__tests__/openai.spec.ts index 63ac438b0c..104117affc 100644 --- a/src/api/providers/__tests__/openai.spec.ts +++ b/src/api/providers/__tests__/openai.spec.ts @@ -1201,14 +1201,78 @@ describe("OpenAiHandler", () => { // This is the key verification that mergeToolResultText is working correctly expect(toolMessage.content).toContain("File content here") expect(toolMessage.content).toContain("environment_details") + }) - // Verify there is NO user message immediately after the tool message + it("should not have user message after tool message for Mistral models", async () => { + // Create a message sequence that includes a follow-up after the tool result + // to verify the Mistral constraint is enforced + const messagesWithFollowUp: Anthropic.Messages.MessageParam[] = [ + { + role: "user", + content: [{ type: "text", text: "Read test.ts and explain it" }], + }, + { + role: "assistant", + content: [ + { + type: "tool_use", + id: "call_abc123xyz", + name: "read_file", + input: { path: "test.ts" }, + }, + ], + }, + { + role: "user", + content: [ + { + type: "tool_result", + tool_use_id: "call_abc123xyz", + content: "export const foo = 'bar'", + }, + { + type: "text", + text: "Current directory: /project", + }, + ], + }, + { + role: "assistant", + content: [{ type: "text", text: "This file exports a constant named foo with value 'bar'." }], + }, + { + role: "user", + content: [{ type: "text", text: "Thanks!" }], + }, + ] + + const mistralHandler = new OpenAiHandler({ + ...mockOptions, + openAiModelId: "mistral-large-latest", + }) + + const stream = mistralHandler.createMessage(systemPrompt, messagesWithFollowUp) + for await (const _chunk of stream) { + // Consume the stream + } + + expect(mockCreate).toHaveBeenCalled() + const callArgs = mockCreate.mock.calls[0][0] + const messages = callArgs.messages + + // Find the tool message + const toolMessageIndex = messages.findIndex((m: any) => m.role === "tool") + expect(toolMessageIndex).not.toBe(-1) + + // Verify there IS a next message (the assistant response) + const nextMessage = messages[toolMessageIndex + 1] + expect(nextMessage).toBeDefined() + + // Verify the next message is NOT a user message // This is the Mistral constraint: after tool, only assistant or tool is allowed, never user // Per mistral_common validator: elif previous_role == Roles.tool: expected_roles = {Roles.assistant, Roles.tool} - const nextMessage = messages[toolMessageIndex + 1] - if (nextMessage) { - expect(nextMessage.role).not.toBe("user") - } + expect(nextMessage.role).not.toBe("user") + expect(nextMessage.role).toBe("assistant") }) it("should detect Devstral models and apply mergeToolResultText", async () => {