diff options
| author | darkcocoa <[email protected]> | 2025-07-22 06:57:11 +0900 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-07-21 21:57:11 +0000 |
| commit | 4c3532d2b395859fb0d9db00bc5209f160ce2e29 (patch) | |
| tree | ec8066df91e155ab318ef107eff7c0ac813292f6 /packages/cli/src/ui/hooks/useGeminiStream.test.tsx | |
| parent | dc2ac144b7059ec2d66f1e90316df40d3822c8b5 (diff) | |
fix: Add warning message for token limit truncation (#2260)
Co-authored-by: Sandy Tao <[email protected]>
Diffstat (limited to 'packages/cli/src/ui/hooks/useGeminiStream.test.tsx')
| -rw-r--r-- | packages/cli/src/ui/hooks/useGeminiStream.test.tsx | 238 |
1 files changed, 237 insertions, 1 deletions
diff --git a/packages/cli/src/ui/hooks/useGeminiStream.test.tsx b/packages/cli/src/ui/hooks/useGeminiStream.test.tsx index 7e45cab2..d7fd35c8 100644 --- a/packages/cli/src/ui/hooks/useGeminiStream.test.tsx +++ b/packages/cli/src/ui/hooks/useGeminiStream.test.tsx @@ -16,7 +16,12 @@ import { TrackedExecutingToolCall, TrackedCancelledToolCall, } from './useReactToolScheduler.js'; -import { Config, EditorType, AuthType } from '@google/gemini-cli-core'; +import { + Config, + EditorType, + AuthType, + GeminiEventType as ServerGeminiEventType, +} from '@google/gemini-cli-core'; import { Part, PartListUnion } from '@google/genai'; import { UseHistoryManagerReturn } from './useHistoryManager.js'; import { @@ -1178,4 +1183,235 @@ describe('useGeminiStream', () => { }); }); }); + + describe('handleFinishedEvent', () => { + it('should add info message for MAX_TOKENS finish reason', async () => { + // Setup mock to return a stream with MAX_TOKENS finish reason + mockSendMessageStream.mockReturnValue( + (async function* () { + yield { + type: ServerGeminiEventType.Content, + value: 'This is a truncated response...', + }; + yield { type: ServerGeminiEventType.Finished, value: 'MAX_TOKENS' }; + })(), + ); + + const { result } = renderHook(() => + useGeminiStream( + new MockedGeminiClientClass(mockConfig), + [], + mockAddItem, + mockSetShowHelp, + mockConfig, + mockOnDebugMessage, + mockHandleSlashCommand, + false, + () => 'vscode' as EditorType, + () => {}, + () => Promise.resolve(), + false, + () => {}, + ), + ); + + // Submit a query + await act(async () => { + await result.current.submitQuery('Generate long text'); + }); + + // Check that the info message was added + await waitFor(() => { + expect(mockAddItem).toHaveBeenCalledWith( + { + type: 'info', + text: '⚠️ Response truncated due to token limits.', + }, + expect.any(Number), + ); + }); + }); + + it('should not add message for STOP finish reason', async () => { + // Setup mock to return a stream with STOP finish reason + mockSendMessageStream.mockReturnValue( + (async function* () { + yield { + type: ServerGeminiEventType.Content, + value: 'Complete response', + }; + yield { type: ServerGeminiEventType.Finished, value: 'STOP' }; + })(), + ); + + const { result } = renderHook(() => + useGeminiStream( + new MockedGeminiClientClass(mockConfig), + [], + mockAddItem, + mockSetShowHelp, + mockConfig, + mockOnDebugMessage, + mockHandleSlashCommand, + false, + () => 'vscode' as EditorType, + () => {}, + () => Promise.resolve(), + false, + () => {}, + ), + ); + + // Submit a query + await act(async () => { + await result.current.submitQuery('Test normal completion'); + }); + + // Wait a bit to ensure no message is added + await new Promise((resolve) => setTimeout(resolve, 100)); + + // Check that no info message was added for STOP + const infoMessages = mockAddItem.mock.calls.filter( + (call) => call[0].type === 'info', + ); + expect(infoMessages).toHaveLength(0); + }); + + it('should not add message for FINISH_REASON_UNSPECIFIED', async () => { + // Setup mock to return a stream with FINISH_REASON_UNSPECIFIED + mockSendMessageStream.mockReturnValue( + (async function* () { + yield { + type: ServerGeminiEventType.Content, + value: 'Response with unspecified finish', + }; + yield { + type: ServerGeminiEventType.Finished, + value: 'FINISH_REASON_UNSPECIFIED', + }; + })(), + ); + + const { result } = renderHook(() => + useGeminiStream( + new MockedGeminiClientClass(mockConfig), + [], + mockAddItem, + mockSetShowHelp, + mockConfig, + mockOnDebugMessage, + mockHandleSlashCommand, + false, + () => 'vscode' as EditorType, + () => {}, + () => Promise.resolve(), + false, + () => {}, + ), + ); + + // Submit a query + await act(async () => { + await result.current.submitQuery('Test unspecified finish'); + }); + + // Wait a bit to ensure no message is added + await new Promise((resolve) => setTimeout(resolve, 100)); + + // Check that no info message was added + const infoMessages = mockAddItem.mock.calls.filter( + (call) => call[0].type === 'info', + ); + expect(infoMessages).toHaveLength(0); + }); + + it('should add appropriate messages for other finish reasons', async () => { + const testCases = [ + { + reason: 'SAFETY', + message: '⚠️ Response stopped due to safety reasons.', + }, + { + reason: 'RECITATION', + message: '⚠️ Response stopped due to recitation policy.', + }, + { + reason: 'LANGUAGE', + message: '⚠️ Response stopped due to unsupported language.', + }, + { + reason: 'BLOCKLIST', + message: '⚠️ Response stopped due to forbidden terms.', + }, + { + reason: 'PROHIBITED_CONTENT', + message: '⚠️ Response stopped due to prohibited content.', + }, + { + reason: 'SPII', + message: + '⚠️ Response stopped due to sensitive personally identifiable information.', + }, + { reason: 'OTHER', message: '⚠️ Response stopped for other reasons.' }, + { + reason: 'MALFORMED_FUNCTION_CALL', + message: '⚠️ Response stopped due to malformed function call.', + }, + { + reason: 'IMAGE_SAFETY', + message: '⚠️ Response stopped due to image safety violations.', + }, + { + reason: 'UNEXPECTED_TOOL_CALL', + message: '⚠️ Response stopped due to unexpected tool call.', + }, + ]; + + for (const { reason, message } of testCases) { + // Reset mocks for each test case + mockAddItem.mockClear(); + mockSendMessageStream.mockReturnValue( + (async function* () { + yield { + type: ServerGeminiEventType.Content, + value: `Response for ${reason}`, + }; + yield { type: ServerGeminiEventType.Finished, value: reason }; + })(), + ); + + const { result } = renderHook(() => + useGeminiStream( + new MockedGeminiClientClass(mockConfig), + [], + mockAddItem, + mockSetShowHelp, + mockConfig, + mockOnDebugMessage, + mockHandleSlashCommand, + false, + () => 'vscode' as EditorType, + () => {}, + () => Promise.resolve(), + false, + () => {}, + ), + ); + + await act(async () => { + await result.current.submitQuery(`Test ${reason}`); + }); + + await waitFor(() => { + expect(mockAddItem).toHaveBeenCalledWith( + { + type: 'info', + text: message, + }, + expect.any(Number), + ); + }); + } + }); + }); }); |
