mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-20 08:47:44 +00:00
Merge tag 'v0.1.15' into feature/yiheng/sync-gemini-cli-0.1.15
This commit is contained in:
@@ -16,7 +16,12 @@ import {
|
||||
TrackedExecutingToolCall,
|
||||
TrackedCancelledToolCall,
|
||||
} from './useReactToolScheduler.js';
|
||||
import { Config, EditorType, AuthType } from '@qwen-code/qwen-code-core';
|
||||
import {
|
||||
Config,
|
||||
EditorType,
|
||||
AuthType,
|
||||
GeminiEventType as ServerGeminiEventType,
|
||||
} from '@qwen-code/qwen-code-core';
|
||||
import { Part, PartListUnion } from '@google/genai';
|
||||
import { UseHistoryManagerReturn } from './useHistoryManager.js';
|
||||
import {
|
||||
@@ -1053,6 +1058,65 @@ describe('useGeminiStream', () => {
|
||||
expect(mockSendMessageStream).not.toHaveBeenCalled(); // No LLM call made
|
||||
});
|
||||
});
|
||||
|
||||
it('should call Gemini with prompt content when slash command returns a `submit_prompt` action', async () => {
|
||||
const customCommandResult: SlashCommandProcessorResult = {
|
||||
type: 'submit_prompt',
|
||||
content: 'This is the actual prompt from the command file.',
|
||||
};
|
||||
mockHandleSlashCommand.mockResolvedValue(customCommandResult);
|
||||
|
||||
const { result, mockSendMessageStream: localMockSendMessageStream } =
|
||||
renderTestHook();
|
||||
|
||||
await act(async () => {
|
||||
await result.current.submitQuery('/my-custom-command');
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockHandleSlashCommand).toHaveBeenCalledWith(
|
||||
'/my-custom-command',
|
||||
);
|
||||
|
||||
expect(localMockSendMessageStream).not.toHaveBeenCalledWith(
|
||||
'/my-custom-command',
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
);
|
||||
|
||||
expect(localMockSendMessageStream).toHaveBeenCalledWith(
|
||||
'This is the actual prompt from the command file.',
|
||||
expect.any(AbortSignal),
|
||||
expect.any(String),
|
||||
);
|
||||
|
||||
expect(mockScheduleToolCalls).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('should correctly handle a submit_prompt action with empty content', async () => {
|
||||
const emptyPromptResult: SlashCommandProcessorResult = {
|
||||
type: 'submit_prompt',
|
||||
content: '',
|
||||
};
|
||||
mockHandleSlashCommand.mockResolvedValue(emptyPromptResult);
|
||||
|
||||
const { result, mockSendMessageStream: localMockSendMessageStream } =
|
||||
renderTestHook();
|
||||
|
||||
await act(async () => {
|
||||
await result.current.submitQuery('/emptycmd');
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockHandleSlashCommand).toHaveBeenCalledWith('/emptycmd');
|
||||
expect(localMockSendMessageStream).toHaveBeenCalledWith(
|
||||
'',
|
||||
expect.any(AbortSignal),
|
||||
expect.any(String),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Memory Refresh on save_memory', () => {
|
||||
@@ -1178,4 +1242,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),
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user