From a2d7f82b499f8d9ed44b732056267ec8e181ebeb Mon Sep 17 00:00:00 2001 From: Sandy Tao Date: Fri, 24 Oct 2025 21:22:26 -0700 Subject: [PATCH] fix(core): Prepend user message to loop detection history if it starts with a function call (#11860) --- .../src/services/loopDetectionService.test.ts | 31 +++++++++++++++++++ .../core/src/services/loopDetectionService.ts | 6 ++++ 2 files changed, 37 insertions(+) diff --git a/packages/core/src/services/loopDetectionService.test.ts b/packages/core/src/services/loopDetectionService.test.ts index aaf7f90829..e464bfb6c9 100644 --- a/packages/core/src/services/loopDetectionService.test.ts +++ b/packages/core/src/services/loopDetectionService.test.ts @@ -5,6 +5,7 @@ */ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import type { Content } from '@google/genai'; import type { Config } from '../config/config.js'; import type { GeminiClient } from '../core/client.js'; import type { BaseLlmClient } from '../core/baseLlmClient.js'; @@ -754,4 +755,34 @@ describe('LoopDetectionService LLM Checks', () => { expect(result).toBe(false); expect(mockBaseLlmClient.generateJson).not.toHaveBeenCalled(); }); + + it('should prepend user message if history starts with a function call', async () => { + const functionCallHistory: Content[] = [ + { + role: 'model', + parts: [{ functionCall: { name: 'someTool', args: {} } }], + }, + { + role: 'model', + parts: [{ text: 'Some follow up text' }], + }, + ]; + vi.mocked(mockGeminiClient.getHistory).mockReturnValue(functionCallHistory); + + mockBaseLlmClient.generateJson = vi + .fn() + .mockResolvedValue({ confidence: 0.1 }); + + await advanceTurns(30); + + expect(mockBaseLlmClient.generateJson).toHaveBeenCalledTimes(1); + const calledArg = vi.mocked(mockBaseLlmClient.generateJson).mock + .calls[0][0]; + expect(calledArg.contents[0]).toEqual({ + role: 'user', + parts: [{ text: 'Recent conversation history:' }], + }); + // Verify the original history follows + expect(calledArg.contents[1]).toEqual(functionCallHistory[0]); + }); }); diff --git a/packages/core/src/services/loopDetectionService.ts b/packages/core/src/services/loopDetectionService.ts index d2fbb3746d..ac291b679d 100644 --- a/packages/core/src/services/loopDetectionService.ts +++ b/packages/core/src/services/loopDetectionService.ts @@ -404,6 +404,12 @@ export class LoopDetectionService { ...trimmedHistory, { role: 'user', parts: [{ text: taskPrompt }] }, ]; + if (contents.length > 0 && isFunctionCall(contents[0])) { + contents.unshift({ + role: 'user', + parts: [{ text: 'Recent conversation history:' }], + }); + } const schema: Record = { type: 'object', properties: {