@google/gemini-cli 0.13.0-nightly.20251102.d7243fb8 → 0.13.0-preview.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/google-gemini-cli-0.13.0-nightly.20251031.c89bc30d.tgz +0 -0
- package/dist/package.json +3 -3
- package/dist/src/commands/mcp/list.test.js +25 -21
- package/dist/src/commands/mcp/list.test.js.map +1 -1
- package/dist/src/config/config.js +11 -84
- package/dist/src/config/config.js.map +1 -1
- package/dist/src/config/config.test.js +13 -30
- package/dist/src/config/config.test.js.map +1 -1
- package/dist/src/config/extension-manager.d.ts +18 -6
- package/dist/src/config/extension-manager.js +25 -19
- package/dist/src/config/extension-manager.js.map +1 -1
- package/dist/src/config/extension.test.js +9 -9
- package/dist/src/config/extension.test.js.map +1 -1
- package/dist/src/config/extensions/extensionSettings.test.js +39 -43
- package/dist/src/config/extensions/extensionSettings.test.js.map +1 -1
- package/dist/src/config/extensions/github.test.js +133 -165
- package/dist/src/config/extensions/github.test.js.map +1 -1
- package/dist/src/config/keyBindings.d.ts +3 -0
- package/dist/src/config/keyBindings.js +29 -7
- package/dist/src/config/keyBindings.js.map +1 -1
- package/dist/src/config/keyBindings.test.js +17 -0
- package/dist/src/config/keyBindings.test.js.map +1 -1
- package/dist/src/config/policy.d.ts +0 -7
- package/dist/src/config/policy.js +10 -177
- package/dist/src/config/policy.js.map +1 -1
- package/dist/src/config/settings.js +1 -0
- package/dist/src/config/settings.js.map +1 -1
- package/dist/src/config/settingsSchema.d.ts +123 -22
- package/dist/src/config/settingsSchema.js +371 -21
- package/dist/src/config/settingsSchema.js.map +1 -1
- package/dist/src/config/settingsSchema.test.js +40 -1
- package/dist/src/config/settingsSchema.test.js.map +1 -1
- package/dist/src/gemini.js +15 -5
- package/dist/src/gemini.js.map +1 -1
- package/dist/src/gemini.test.js +2 -0
- package/dist/src/gemini.test.js.map +1 -1
- package/dist/src/generated/git-commit.d.ts +2 -2
- package/dist/src/generated/git-commit.js +2 -2
- package/dist/src/generated/git-commit.js.map +1 -1
- package/dist/src/nonInteractiveCli.js +68 -1
- package/dist/src/nonInteractiveCli.js.map +1 -1
- package/dist/src/services/BuiltinCommandLoader.js +4 -0
- package/dist/src/services/BuiltinCommandLoader.js.map +1 -1
- package/dist/src/services/BuiltinCommandLoader.test.js +22 -0
- package/dist/src/services/BuiltinCommandLoader.test.js.map +1 -1
- package/dist/src/services/McpPromptLoader.js +2 -2
- package/dist/src/services/McpPromptLoader.js.map +1 -1
- package/dist/src/services/McpPromptLoader.test.js +4 -2
- package/dist/src/services/McpPromptLoader.test.js.map +1 -1
- package/dist/src/test-utils/render.d.ts +2 -1
- package/dist/src/test-utils/render.js +3 -2
- package/dist/src/test-utils/render.js.map +1 -1
- package/dist/src/ui/AppContainer.js +32 -15
- package/dist/src/ui/AppContainer.js.map +1 -1
- package/dist/src/ui/AppContainer.test.js +160 -0
- package/dist/src/ui/AppContainer.test.js.map +1 -1
- package/dist/src/ui/commands/mcpCommand.js +14 -14
- package/dist/src/ui/commands/mcpCommand.js.map +1 -1
- package/dist/src/ui/commands/mcpCommand.test.js +4 -0
- package/dist/src/ui/commands/mcpCommand.test.js.map +1 -1
- package/dist/src/ui/commands/policiesCommand.d.ts +7 -0
- package/dist/src/ui/commands/policiesCommand.js +59 -0
- package/dist/src/ui/commands/policiesCommand.js.map +1 -0
- package/dist/src/ui/commands/policiesCommand.test.js +83 -0
- package/dist/src/ui/commands/policiesCommand.test.js.map +1 -0
- package/dist/src/ui/components/Composer.js +1 -1
- package/dist/src/ui/components/Composer.js.map +1 -1
- package/dist/src/ui/components/Composer.test.js +4 -1
- package/dist/src/ui/components/Composer.test.js.map +1 -1
- package/dist/src/ui/components/ConfigInitDisplay.js +4 -6
- package/dist/src/ui/components/ConfigInitDisplay.js.map +1 -1
- package/dist/src/ui/components/InputPrompt.js +22 -2
- package/dist/src/ui/components/InputPrompt.js.map +1 -1
- package/dist/src/ui/components/InputPrompt.test.js +70 -5
- package/dist/src/ui/components/InputPrompt.test.js.map +1 -1
- package/dist/src/ui/components/MainContent.js +15 -4
- package/dist/src/ui/components/MainContent.js.map +1 -1
- package/dist/src/ui/components/Notifications.js +38 -5
- package/dist/src/ui/components/Notifications.js.map +1 -1
- package/dist/src/ui/components/SettingsDialog.js +32 -25
- package/dist/src/ui/components/SettingsDialog.js.map +1 -1
- package/dist/src/ui/components/ShellConfirmationDialog.js +1 -1
- package/dist/src/ui/components/ShellConfirmationDialog.js.map +1 -1
- package/dist/src/ui/components/messages/InfoMessage.js +1 -1
- package/dist/src/ui/components/messages/InfoMessage.js.map +1 -1
- package/dist/src/ui/components/messages/ToolConfirmationMessage.js +1 -1
- package/dist/src/ui/components/messages/ToolConfirmationMessage.js.map +1 -1
- package/dist/src/ui/components/messages/WarningMessage.js +2 -2
- package/dist/src/ui/components/messages/WarningMessage.js.map +1 -1
- package/dist/src/ui/components/shared/text-buffer.d.ts +1 -0
- package/dist/src/ui/components/shared/text-buffer.js +23 -0
- package/dist/src/ui/components/shared/text-buffer.js.map +1 -1
- package/dist/src/ui/components/shared/text-buffer.test.js +246 -201
- package/dist/src/ui/components/shared/text-buffer.test.js.map +1 -1
- package/dist/src/ui/contexts/KeypressContext.js +182 -132
- package/dist/src/ui/contexts/KeypressContext.js.map +1 -1
- package/dist/src/ui/contexts/KeypressContext.test.js +144 -8
- package/dist/src/ui/contexts/KeypressContext.test.js.map +1 -1
- package/dist/src/ui/contexts/MouseContext.d.ts +21 -0
- package/dist/src/ui/contexts/MouseContext.js +89 -0
- package/dist/src/ui/contexts/MouseContext.js.map +1 -0
- package/dist/src/ui/contexts/MouseContext.test.js +164 -0
- package/dist/src/ui/contexts/MouseContext.test.js.map +1 -0
- package/dist/src/ui/hooks/slashCommandProcessor.test.js +70 -73
- package/dist/src/ui/hooks/slashCommandProcessor.test.js.map +1 -1
- package/dist/src/ui/hooks/useGeminiStream.test.js +135 -368
- package/dist/src/ui/hooks/useGeminiStream.test.js.map +1 -1
- package/dist/src/ui/hooks/useKeypress.test.js +17 -9
- package/dist/src/ui/hooks/useKeypress.test.js.map +1 -1
- package/dist/src/ui/hooks/useMouse.d.ts +17 -0
- package/dist/src/ui/hooks/useMouse.js +27 -0
- package/dist/src/ui/hooks/useMouse.js.map +1 -0
- package/dist/src/ui/hooks/useMouse.test.d.ts +6 -0
- package/dist/src/ui/hooks/useMouse.test.js +57 -0
- package/dist/src/ui/hooks/useMouse.test.js.map +1 -0
- package/dist/src/ui/hooks/useSelectionList.js +5 -4
- package/dist/src/ui/hooks/useSelectionList.js.map +1 -1
- package/dist/src/ui/hooks/useSelectionList.test.js +24 -3
- package/dist/src/ui/hooks/useSelectionList.test.js.map +1 -1
- package/dist/src/ui/hooks/useToolScheduler.test.js +109 -200
- package/dist/src/ui/hooks/useToolScheduler.test.js.map +1 -1
- package/dist/src/ui/keyMatchers.test.js +27 -0
- package/dist/src/ui/keyMatchers.test.js.map +1 -1
- package/dist/src/ui/themes/no-color.js +1 -0
- package/dist/src/ui/themes/no-color.js.map +1 -1
- package/dist/src/ui/themes/semantic-tokens.d.ts +1 -0
- package/dist/src/ui/themes/semantic-tokens.js +3 -0
- package/dist/src/ui/themes/semantic-tokens.js.map +1 -1
- package/dist/src/ui/themes/theme.d.ts +1 -0
- package/dist/src/ui/themes/theme.js +4 -0
- package/dist/src/ui/themes/theme.js.map +1 -1
- package/dist/src/ui/utils/InlineMarkdownRenderer.d.ts +1 -0
- package/dist/src/ui/utils/InlineMarkdownRenderer.js +11 -10
- package/dist/src/ui/utils/InlineMarkdownRenderer.js.map +1 -1
- package/dist/src/ui/utils/MarkdownDisplay.js +11 -9
- package/dist/src/ui/utils/MarkdownDisplay.js.map +1 -1
- package/dist/src/ui/utils/input.d.ts +17 -0
- package/dist/src/ui/utils/input.js +51 -0
- package/dist/src/ui/utils/input.js.map +1 -0
- package/dist/src/ui/utils/input.test.d.ts +6 -0
- package/dist/src/ui/utils/input.test.js +44 -0
- package/dist/src/ui/utils/input.test.js.map +1 -0
- package/dist/src/ui/utils/kittyProtocolDetector.js +13 -4
- package/dist/src/ui/utils/kittyProtocolDetector.js.map +1 -1
- package/dist/src/ui/utils/mouse.d.ts +31 -0
- package/dist/src/ui/utils/mouse.js +164 -0
- package/dist/src/ui/utils/mouse.js.map +1 -0
- package/dist/src/ui/utils/mouse.test.d.ts +6 -0
- package/dist/src/ui/utils/mouse.test.js +131 -0
- package/dist/src/ui/utils/mouse.test.js.map +1 -0
- package/dist/src/utils/events.d.ts +11 -2
- package/dist/src/utils/events.js +1 -0
- package/dist/src/utils/events.js.map +1 -1
- package/dist/src/utils/sandbox.js +16 -18
- package/dist/src/utils/sandbox.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/dist/src/config/policy-toml-loader.d.ts +0 -46
- package/dist/src/config/policy-toml-loader.js +0 -314
- package/dist/src/config/policy-toml-loader.js.map +0 -1
- package/dist/src/config/policy-toml-loader.test.js +0 -626
- package/dist/src/config/policy-toml-loader.test.js.map +0 -1
- package/dist/src/config/policy.test.js +0 -1058
- package/dist/src/config/policy.test.js.map +0 -1
- /package/dist/src/{config/policy-toml-loader.test.d.ts → ui/commands/policiesCommand.test.d.ts} +0 -0
- /package/dist/src/{config/policy.test.d.ts → ui/contexts/MouseContext.test.d.ts} +0 -0
|
@@ -265,6 +265,49 @@ describe('useGeminiStream', () => {
|
|
|
265
265
|
client,
|
|
266
266
|
};
|
|
267
267
|
};
|
|
268
|
+
// Helper to create mock tool calls - reduces boilerplate
|
|
269
|
+
const createMockToolCall = (toolName, callId, confirmationType, mockOnConfirm, status = 'awaiting_approval') => ({
|
|
270
|
+
request: {
|
|
271
|
+
callId,
|
|
272
|
+
name: toolName,
|
|
273
|
+
args: {},
|
|
274
|
+
isClientInitiated: false,
|
|
275
|
+
prompt_id: 'prompt-id-1',
|
|
276
|
+
},
|
|
277
|
+
status: status,
|
|
278
|
+
responseSubmittedToGemini: false,
|
|
279
|
+
confirmationDetails: confirmationType === 'edit'
|
|
280
|
+
? {
|
|
281
|
+
type: 'edit',
|
|
282
|
+
title: 'Confirm Edit',
|
|
283
|
+
onConfirm: mockOnConfirm,
|
|
284
|
+
fileName: 'file.txt',
|
|
285
|
+
filePath: '/test/file.txt',
|
|
286
|
+
fileDiff: 'fake diff',
|
|
287
|
+
originalContent: 'old',
|
|
288
|
+
newContent: 'new',
|
|
289
|
+
}
|
|
290
|
+
: {
|
|
291
|
+
type: 'info',
|
|
292
|
+
title: `${toolName} confirmation`,
|
|
293
|
+
onConfirm: mockOnConfirm,
|
|
294
|
+
prompt: `Execute ${toolName}?`,
|
|
295
|
+
},
|
|
296
|
+
tool: {
|
|
297
|
+
name: toolName,
|
|
298
|
+
displayName: toolName,
|
|
299
|
+
description: `${toolName} description`,
|
|
300
|
+
build: vi.fn(),
|
|
301
|
+
},
|
|
302
|
+
invocation: {
|
|
303
|
+
getDescription: () => 'Mock description',
|
|
304
|
+
},
|
|
305
|
+
});
|
|
306
|
+
// Helper to render hook with default parameters - reduces boilerplate
|
|
307
|
+
const renderHookWithDefaults = (options = {}) => {
|
|
308
|
+
const { shellModeActive = false, onCancelSubmit = () => { }, setShellInputFocused = () => { }, performMemoryRefresh = () => Promise.resolve(), onAuthError = () => { }, onEditorClose = () => { }, setModelSwitched = vi.fn(), modelSwitched = false, } = options;
|
|
309
|
+
return renderHook(() => useGeminiStream(new MockedGeminiClientClass(mockConfig), [], mockAddItem, mockConfig, mockLoadedSettings, mockOnDebugMessage, mockHandleSlashCommand, shellModeActive, () => 'vscode', onAuthError, performMemoryRefresh, modelSwitched, setModelSwitched, onEditorClose, onCancelSubmit, setShellInputFocused, 80, 24));
|
|
310
|
+
};
|
|
268
311
|
it('should not submit tool responses if not all tool calls are completed', () => {
|
|
269
312
|
const toolCalls = [
|
|
270
313
|
{
|
|
@@ -999,62 +1042,8 @@ describe('useGeminiStream', () => {
|
|
|
999
1042
|
it('should auto-approve all pending tool calls when switching to YOLO mode', async () => {
|
|
1000
1043
|
const mockOnConfirm = vi.fn().mockResolvedValue(undefined);
|
|
1001
1044
|
const awaitingApprovalToolCalls = [
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
callId: 'call1',
|
|
1005
|
-
name: 'replace',
|
|
1006
|
-
args: { old_string: 'old', new_string: 'new' },
|
|
1007
|
-
isClientInitiated: false,
|
|
1008
|
-
prompt_id: 'prompt-id-1',
|
|
1009
|
-
},
|
|
1010
|
-
status: 'awaiting_approval',
|
|
1011
|
-
responseSubmittedToGemini: false,
|
|
1012
|
-
confirmationDetails: {
|
|
1013
|
-
type: 'edit',
|
|
1014
|
-
title: 'Confirm Edit',
|
|
1015
|
-
onConfirm: mockOnConfirm,
|
|
1016
|
-
fileName: 'file.txt',
|
|
1017
|
-
filePath: '/test/file.txt',
|
|
1018
|
-
fileDiff: 'fake diff',
|
|
1019
|
-
originalContent: 'old',
|
|
1020
|
-
newContent: 'new',
|
|
1021
|
-
},
|
|
1022
|
-
tool: {
|
|
1023
|
-
name: 'replace',
|
|
1024
|
-
displayName: 'replace',
|
|
1025
|
-
description: 'Replace text',
|
|
1026
|
-
build: vi.fn(),
|
|
1027
|
-
},
|
|
1028
|
-
invocation: {
|
|
1029
|
-
getDescription: () => 'Mock description',
|
|
1030
|
-
},
|
|
1031
|
-
},
|
|
1032
|
-
{
|
|
1033
|
-
request: {
|
|
1034
|
-
callId: 'call2',
|
|
1035
|
-
name: 'read_file',
|
|
1036
|
-
args: { path: '/test/file.txt' },
|
|
1037
|
-
isClientInitiated: false,
|
|
1038
|
-
prompt_id: 'prompt-id-1',
|
|
1039
|
-
},
|
|
1040
|
-
status: 'awaiting_approval',
|
|
1041
|
-
responseSubmittedToGemini: false,
|
|
1042
|
-
confirmationDetails: {
|
|
1043
|
-
type: 'info',
|
|
1044
|
-
title: 'Read File',
|
|
1045
|
-
onConfirm: mockOnConfirm,
|
|
1046
|
-
prompt: 'Read /test/file.txt?',
|
|
1047
|
-
},
|
|
1048
|
-
tool: {
|
|
1049
|
-
name: 'read_file',
|
|
1050
|
-
displayName: 'read_file',
|
|
1051
|
-
description: 'Read file',
|
|
1052
|
-
build: vi.fn(),
|
|
1053
|
-
},
|
|
1054
|
-
invocation: {
|
|
1055
|
-
getDescription: () => 'Mock description',
|
|
1056
|
-
},
|
|
1057
|
-
},
|
|
1045
|
+
createMockToolCall('replace', 'call1', 'edit', mockOnConfirm),
|
|
1046
|
+
createMockToolCall('read_file', 'call2', 'info', mockOnConfirm),
|
|
1058
1047
|
];
|
|
1059
1048
|
const { result } = renderTestHook(awaitingApprovalToolCalls);
|
|
1060
1049
|
await act(async () => {
|
|
@@ -1062,109 +1051,23 @@ describe('useGeminiStream', () => {
|
|
|
1062
1051
|
});
|
|
1063
1052
|
// Both tool calls should be auto-approved
|
|
1064
1053
|
expect(mockOnConfirm).toHaveBeenCalledTimes(2);
|
|
1065
|
-
expect(mockOnConfirm).
|
|
1066
|
-
expect(mockOnConfirm).toHaveBeenNthCalledWith(2, ToolConfirmationOutcome.ProceedOnce);
|
|
1054
|
+
expect(mockOnConfirm).toHaveBeenCalledWith(ToolConfirmationOutcome.ProceedOnce);
|
|
1067
1055
|
});
|
|
1068
1056
|
it('should only auto-approve edit tools when switching to AUTO_EDIT mode', async () => {
|
|
1069
1057
|
const mockOnConfirmReplace = vi.fn().mockResolvedValue(undefined);
|
|
1070
1058
|
const mockOnConfirmWrite = vi.fn().mockResolvedValue(undefined);
|
|
1071
1059
|
const mockOnConfirmRead = vi.fn().mockResolvedValue(undefined);
|
|
1072
1060
|
const awaitingApprovalToolCalls = [
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
name: 'replace',
|
|
1077
|
-
args: { old_string: 'old', new_string: 'new' },
|
|
1078
|
-
isClientInitiated: false,
|
|
1079
|
-
prompt_id: 'prompt-id-1',
|
|
1080
|
-
},
|
|
1081
|
-
status: 'awaiting_approval',
|
|
1082
|
-
responseSubmittedToGemini: false,
|
|
1083
|
-
confirmationDetails: {
|
|
1084
|
-
type: 'edit',
|
|
1085
|
-
title: 'Confirm Edit',
|
|
1086
|
-
onConfirm: mockOnConfirmReplace,
|
|
1087
|
-
fileName: 'file.txt',
|
|
1088
|
-
filePath: '/test/file.txt',
|
|
1089
|
-
fileDiff: 'fake diff',
|
|
1090
|
-
originalContent: 'old',
|
|
1091
|
-
newContent: 'new',
|
|
1092
|
-
},
|
|
1093
|
-
tool: {
|
|
1094
|
-
name: 'replace',
|
|
1095
|
-
displayName: 'replace',
|
|
1096
|
-
description: 'Replace text',
|
|
1097
|
-
build: vi.fn(),
|
|
1098
|
-
},
|
|
1099
|
-
invocation: {
|
|
1100
|
-
getDescription: () => 'Mock description',
|
|
1101
|
-
},
|
|
1102
|
-
},
|
|
1103
|
-
{
|
|
1104
|
-
request: {
|
|
1105
|
-
callId: 'call2',
|
|
1106
|
-
name: 'write_file',
|
|
1107
|
-
args: { path: '/test/new.txt', content: 'content' },
|
|
1108
|
-
isClientInitiated: false,
|
|
1109
|
-
prompt_id: 'prompt-id-1',
|
|
1110
|
-
},
|
|
1111
|
-
status: 'awaiting_approval',
|
|
1112
|
-
responseSubmittedToGemini: false,
|
|
1113
|
-
confirmationDetails: {
|
|
1114
|
-
type: 'edit',
|
|
1115
|
-
title: 'Confirm Edit',
|
|
1116
|
-
onConfirm: mockOnConfirmWrite,
|
|
1117
|
-
fileName: 'new.txt',
|
|
1118
|
-
filePath: '/test/new.txt',
|
|
1119
|
-
fileDiff: 'fake diff',
|
|
1120
|
-
originalContent: null,
|
|
1121
|
-
newContent: 'content',
|
|
1122
|
-
},
|
|
1123
|
-
tool: {
|
|
1124
|
-
name: 'write_file',
|
|
1125
|
-
displayName: 'write_file',
|
|
1126
|
-
description: 'Write file',
|
|
1127
|
-
build: vi.fn(),
|
|
1128
|
-
},
|
|
1129
|
-
invocation: {
|
|
1130
|
-
getDescription: () => 'Mock description',
|
|
1131
|
-
},
|
|
1132
|
-
},
|
|
1133
|
-
{
|
|
1134
|
-
request: {
|
|
1135
|
-
callId: 'call3',
|
|
1136
|
-
name: 'read_file',
|
|
1137
|
-
args: { path: '/test/file.txt' },
|
|
1138
|
-
isClientInitiated: false,
|
|
1139
|
-
prompt_id: 'prompt-id-1',
|
|
1140
|
-
},
|
|
1141
|
-
status: 'awaiting_approval',
|
|
1142
|
-
responseSubmittedToGemini: false,
|
|
1143
|
-
confirmationDetails: {
|
|
1144
|
-
type: 'info',
|
|
1145
|
-
title: 'Read File',
|
|
1146
|
-
onConfirm: mockOnConfirmRead,
|
|
1147
|
-
prompt: 'Read /test/file.txt?',
|
|
1148
|
-
},
|
|
1149
|
-
tool: {
|
|
1150
|
-
name: 'read_file',
|
|
1151
|
-
displayName: 'read_file',
|
|
1152
|
-
description: 'Read file',
|
|
1153
|
-
build: vi.fn(),
|
|
1154
|
-
},
|
|
1155
|
-
invocation: {
|
|
1156
|
-
getDescription: () => 'Mock description',
|
|
1157
|
-
},
|
|
1158
|
-
},
|
|
1061
|
+
createMockToolCall('replace', 'call1', 'edit', mockOnConfirmReplace),
|
|
1062
|
+
createMockToolCall('write_file', 'call2', 'edit', mockOnConfirmWrite),
|
|
1063
|
+
createMockToolCall('read_file', 'call3', 'info', mockOnConfirmRead),
|
|
1159
1064
|
];
|
|
1160
1065
|
const { result } = renderTestHook(awaitingApprovalToolCalls);
|
|
1161
1066
|
await act(async () => {
|
|
1162
1067
|
await result.current.handleApprovalModeChange(ApprovalMode.AUTO_EDIT);
|
|
1163
1068
|
});
|
|
1164
1069
|
// Only replace and write_file should be auto-approved
|
|
1165
|
-
expect(mockOnConfirmReplace).toHaveBeenCalledTimes(1);
|
|
1166
1070
|
expect(mockOnConfirmReplace).toHaveBeenCalledWith(ToolConfirmationOutcome.ProceedOnce);
|
|
1167
|
-
expect(mockOnConfirmWrite).toHaveBeenCalledTimes(1);
|
|
1168
1071
|
expect(mockOnConfirmWrite).toHaveBeenCalledWith(ToolConfirmationOutcome.ProceedOnce);
|
|
1169
1072
|
// read_file should not be auto-approved
|
|
1170
1073
|
expect(mockOnConfirmRead).not.toHaveBeenCalled();
|
|
@@ -1172,36 +1075,7 @@ describe('useGeminiStream', () => {
|
|
|
1172
1075
|
it('should not auto-approve any tools when switching to REQUIRE_CONFIRMATION mode', async () => {
|
|
1173
1076
|
const mockOnConfirm = vi.fn().mockResolvedValue(undefined);
|
|
1174
1077
|
const awaitingApprovalToolCalls = [
|
|
1175
|
-
|
|
1176
|
-
request: {
|
|
1177
|
-
callId: 'call1',
|
|
1178
|
-
name: 'replace',
|
|
1179
|
-
args: { old_string: 'old', new_string: 'new' },
|
|
1180
|
-
isClientInitiated: false,
|
|
1181
|
-
prompt_id: 'prompt-id-1',
|
|
1182
|
-
},
|
|
1183
|
-
status: 'awaiting_approval',
|
|
1184
|
-
responseSubmittedToGemini: false,
|
|
1185
|
-
confirmationDetails: {
|
|
1186
|
-
type: 'edit',
|
|
1187
|
-
title: 'Confirm Edit',
|
|
1188
|
-
onConfirm: mockOnConfirm,
|
|
1189
|
-
fileName: 'file.txt',
|
|
1190
|
-
filePath: '/test/file.txt',
|
|
1191
|
-
fileDiff: 'fake diff',
|
|
1192
|
-
originalContent: 'old',
|
|
1193
|
-
newContent: 'new',
|
|
1194
|
-
},
|
|
1195
|
-
tool: {
|
|
1196
|
-
name: 'replace',
|
|
1197
|
-
displayName: 'replace',
|
|
1198
|
-
description: 'Replace text',
|
|
1199
|
-
build: vi.fn(),
|
|
1200
|
-
},
|
|
1201
|
-
invocation: {
|
|
1202
|
-
getDescription: () => 'Mock description',
|
|
1203
|
-
},
|
|
1204
|
-
},
|
|
1078
|
+
createMockToolCall('replace', 'call1', 'edit', mockOnConfirm),
|
|
1205
1079
|
];
|
|
1206
1080
|
const { result } = renderTestHook(awaitingApprovalToolCalls);
|
|
1207
1081
|
await act(async () => {
|
|
@@ -1219,74 +1093,16 @@ describe('useGeminiStream', () => {
|
|
|
1219
1093
|
.fn()
|
|
1220
1094
|
.mockRejectedValue(new Error('Approval failed'));
|
|
1221
1095
|
const awaitingApprovalToolCalls = [
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
callId: 'call1',
|
|
1225
|
-
name: 'replace',
|
|
1226
|
-
args: { old_string: 'old', new_string: 'new' },
|
|
1227
|
-
isClientInitiated: false,
|
|
1228
|
-
prompt_id: 'prompt-id-1',
|
|
1229
|
-
},
|
|
1230
|
-
status: 'awaiting_approval',
|
|
1231
|
-
responseSubmittedToGemini: false,
|
|
1232
|
-
confirmationDetails: {
|
|
1233
|
-
type: 'edit',
|
|
1234
|
-
title: 'Confirm Edit',
|
|
1235
|
-
onConfirm: mockOnConfirmSuccess,
|
|
1236
|
-
fileName: 'file.txt',
|
|
1237
|
-
filePath: '/test/file.txt',
|
|
1238
|
-
fileDiff: 'fake diff',
|
|
1239
|
-
originalContent: 'old',
|
|
1240
|
-
newContent: 'new',
|
|
1241
|
-
},
|
|
1242
|
-
tool: {
|
|
1243
|
-
name: 'replace',
|
|
1244
|
-
displayName: 'replace',
|
|
1245
|
-
description: 'Replace text',
|
|
1246
|
-
build: vi.fn(),
|
|
1247
|
-
},
|
|
1248
|
-
invocation: {
|
|
1249
|
-
getDescription: () => 'Mock description',
|
|
1250
|
-
},
|
|
1251
|
-
},
|
|
1252
|
-
{
|
|
1253
|
-
request: {
|
|
1254
|
-
callId: 'call2',
|
|
1255
|
-
name: 'write_file',
|
|
1256
|
-
args: { path: '/test/file.txt', content: 'content' },
|
|
1257
|
-
isClientInitiated: false,
|
|
1258
|
-
prompt_id: 'prompt-id-1',
|
|
1259
|
-
},
|
|
1260
|
-
status: 'awaiting_approval',
|
|
1261
|
-
responseSubmittedToGemini: false,
|
|
1262
|
-
confirmationDetails: {
|
|
1263
|
-
type: 'edit',
|
|
1264
|
-
title: 'Confirm Edit',
|
|
1265
|
-
onConfirm: mockOnConfirmError,
|
|
1266
|
-
fileName: 'file.txt',
|
|
1267
|
-
filePath: '/test/file.txt',
|
|
1268
|
-
fileDiff: 'fake diff',
|
|
1269
|
-
originalContent: null,
|
|
1270
|
-
newContent: 'content',
|
|
1271
|
-
},
|
|
1272
|
-
tool: {
|
|
1273
|
-
name: 'write_file',
|
|
1274
|
-
displayName: 'write_file',
|
|
1275
|
-
description: 'Write file',
|
|
1276
|
-
build: vi.fn(),
|
|
1277
|
-
},
|
|
1278
|
-
invocation: {
|
|
1279
|
-
getDescription: () => 'Mock description',
|
|
1280
|
-
},
|
|
1281
|
-
},
|
|
1096
|
+
createMockToolCall('replace', 'call1', 'edit', mockOnConfirmSuccess),
|
|
1097
|
+
createMockToolCall('write_file', 'call2', 'edit', mockOnConfirmError),
|
|
1282
1098
|
];
|
|
1283
1099
|
const { result } = renderTestHook(awaitingApprovalToolCalls);
|
|
1284
1100
|
await act(async () => {
|
|
1285
1101
|
await result.current.handleApprovalModeChange(ApprovalMode.YOLO);
|
|
1286
1102
|
});
|
|
1287
1103
|
// Both confirmation methods should be called
|
|
1288
|
-
expect(mockOnConfirmSuccess).
|
|
1289
|
-
expect(mockOnConfirmError).
|
|
1104
|
+
expect(mockOnConfirmSuccess).toHaveBeenCalled();
|
|
1105
|
+
expect(mockOnConfirmError).toHaveBeenCalled();
|
|
1290
1106
|
// Error should be logged
|
|
1291
1107
|
expect(debuggerSpy).toHaveBeenCalledWith('Failed to auto-approve tool call call2:', expect.any(Error));
|
|
1292
1108
|
debuggerSpy.mockRestore();
|
|
@@ -1456,53 +1272,37 @@ describe('useGeminiStream', () => {
|
|
|
1456
1272
|
beforeEach(() => {
|
|
1457
1273
|
vi.mocked(tokenLimit).mockReturnValue(100);
|
|
1458
1274
|
});
|
|
1459
|
-
it(
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
await act(async () => {
|
|
1474
|
-
await result.current.submitQuery('Test overflow');
|
|
1475
|
-
});
|
|
1476
|
-
// Check that the message was added without suggestion
|
|
1477
|
-
await waitFor(() => {
|
|
1478
|
-
expect(mockAddItem).toHaveBeenCalledWith({
|
|
1479
|
-
type: 'info',
|
|
1480
|
-
text: `Sending this message (20 tokens) might exceed the remaining context window limit (80 tokens).`,
|
|
1481
|
-
}, expect.any(Number));
|
|
1482
|
-
});
|
|
1483
|
-
});
|
|
1484
|
-
it('should add message with suggestion when remaining tokens are < 75% of limit', async () => {
|
|
1485
|
-
// Setup mock to return a stream with ContextWindowWillOverflow event
|
|
1486
|
-
// Limit is 100, remaining is 70 (< 75)
|
|
1275
|
+
it.each([
|
|
1276
|
+
{
|
|
1277
|
+
name: 'without suggestion when remaining tokens are > 75% of limit',
|
|
1278
|
+
requestTokens: 20,
|
|
1279
|
+
remainingTokens: 80,
|
|
1280
|
+
expectedMessage: 'Sending this message (20 tokens) might exceed the remaining context window limit (80 tokens).',
|
|
1281
|
+
},
|
|
1282
|
+
{
|
|
1283
|
+
name: 'with suggestion when remaining tokens are < 75% of limit',
|
|
1284
|
+
requestTokens: 30,
|
|
1285
|
+
remainingTokens: 70,
|
|
1286
|
+
expectedMessage: 'Sending this message (30 tokens) might exceed the remaining context window limit (70 tokens). Please try reducing the size of your message or use the `/compress` command to compress the chat history.',
|
|
1287
|
+
},
|
|
1288
|
+
])('should add message $name', async ({ requestTokens, remainingTokens, expectedMessage }) => {
|
|
1487
1289
|
mockSendMessageStream.mockReturnValue((async function* () {
|
|
1488
1290
|
yield {
|
|
1489
1291
|
type: ServerGeminiEventType.ContextWindowWillOverflow,
|
|
1490
1292
|
value: {
|
|
1491
|
-
estimatedRequestTokenCount:
|
|
1492
|
-
remainingTokenCount:
|
|
1293
|
+
estimatedRequestTokenCount: requestTokens,
|
|
1294
|
+
remainingTokenCount: remainingTokens,
|
|
1493
1295
|
},
|
|
1494
1296
|
};
|
|
1495
1297
|
})());
|
|
1496
|
-
const { result } =
|
|
1497
|
-
// Submit a query
|
|
1298
|
+
const { result } = renderHookWithDefaults();
|
|
1498
1299
|
await act(async () => {
|
|
1499
1300
|
await result.current.submitQuery('Test overflow');
|
|
1500
1301
|
});
|
|
1501
|
-
// Check that the message was added with suggestion
|
|
1502
1302
|
await waitFor(() => {
|
|
1503
1303
|
expect(mockAddItem).toHaveBeenCalledWith({
|
|
1504
1304
|
type: 'info',
|
|
1505
|
-
text:
|
|
1305
|
+
text: expectedMessage,
|
|
1506
1306
|
}, expect.any(Number));
|
|
1507
1307
|
});
|
|
1508
1308
|
});
|
|
@@ -1529,112 +1329,71 @@ describe('useGeminiStream', () => {
|
|
|
1529
1329
|
expect(onCancelSubmitSpy).toHaveBeenCalled();
|
|
1530
1330
|
});
|
|
1531
1331
|
});
|
|
1532
|
-
it(
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
}
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1332
|
+
it.each([
|
|
1333
|
+
{
|
|
1334
|
+
reason: 'STOP',
|
|
1335
|
+
shouldAddMessage: false,
|
|
1336
|
+
},
|
|
1337
|
+
{
|
|
1338
|
+
reason: 'FINISH_REASON_UNSPECIFIED',
|
|
1339
|
+
shouldAddMessage: false,
|
|
1340
|
+
},
|
|
1341
|
+
{
|
|
1342
|
+
reason: 'SAFETY',
|
|
1343
|
+
message: '⚠️ Response stopped due to safety reasons.',
|
|
1344
|
+
},
|
|
1345
|
+
{
|
|
1346
|
+
reason: 'RECITATION',
|
|
1347
|
+
message: '⚠️ Response stopped due to recitation policy.',
|
|
1348
|
+
},
|
|
1349
|
+
{
|
|
1350
|
+
reason: 'LANGUAGE',
|
|
1351
|
+
message: '⚠️ Response stopped due to unsupported language.',
|
|
1352
|
+
},
|
|
1353
|
+
{
|
|
1354
|
+
reason: 'BLOCKLIST',
|
|
1355
|
+
message: '⚠️ Response stopped due to forbidden terms.',
|
|
1356
|
+
},
|
|
1357
|
+
{
|
|
1358
|
+
reason: 'PROHIBITED_CONTENT',
|
|
1359
|
+
message: '⚠️ Response stopped due to prohibited content.',
|
|
1360
|
+
},
|
|
1361
|
+
{
|
|
1362
|
+
reason: 'SPII',
|
|
1363
|
+
message: '⚠️ Response stopped due to sensitive personally identifiable information.',
|
|
1364
|
+
},
|
|
1365
|
+
{
|
|
1366
|
+
reason: 'OTHER',
|
|
1367
|
+
message: '⚠️ Response stopped for other reasons.',
|
|
1368
|
+
},
|
|
1369
|
+
{
|
|
1370
|
+
reason: 'MALFORMED_FUNCTION_CALL',
|
|
1371
|
+
message: '⚠️ Response stopped due to malformed function call.',
|
|
1372
|
+
},
|
|
1373
|
+
{
|
|
1374
|
+
reason: 'IMAGE_SAFETY',
|
|
1375
|
+
message: '⚠️ Response stopped due to image safety violations.',
|
|
1376
|
+
},
|
|
1377
|
+
{
|
|
1378
|
+
reason: 'UNEXPECTED_TOOL_CALL',
|
|
1379
|
+
message: '⚠️ Response stopped due to unexpected tool call.',
|
|
1380
|
+
},
|
|
1381
|
+
])('should handle $reason finish reason correctly', async ({ reason, shouldAddMessage = true, message }) => {
|
|
1557
1382
|
mockSendMessageStream.mockReturnValue((async function* () {
|
|
1558
1383
|
yield {
|
|
1559
1384
|
type: ServerGeminiEventType.Content,
|
|
1560
|
-
value:
|
|
1385
|
+
value: `Response for ${reason}`,
|
|
1561
1386
|
};
|
|
1562
1387
|
yield {
|
|
1563
1388
|
type: ServerGeminiEventType.Finished,
|
|
1564
|
-
value: {
|
|
1565
|
-
reason: 'FINISH_REASON_UNSPECIFIED',
|
|
1566
|
-
usageMetadata: undefined,
|
|
1567
|
-
},
|
|
1389
|
+
value: { reason, usageMetadata: undefined },
|
|
1568
1390
|
};
|
|
1569
1391
|
})());
|
|
1570
|
-
const { result } =
|
|
1571
|
-
// Submit a query
|
|
1392
|
+
const { result } = renderHookWithDefaults();
|
|
1572
1393
|
await act(async () => {
|
|
1573
|
-
await result.current.submitQuery(
|
|
1394
|
+
await result.current.submitQuery(`Test ${reason}`);
|
|
1574
1395
|
});
|
|
1575
|
-
|
|
1576
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1577
|
-
// Check that no info message was added
|
|
1578
|
-
const infoMessages = mockAddItem.mock.calls.filter((call) => call[0].type === 'info');
|
|
1579
|
-
expect(infoMessages).toHaveLength(0);
|
|
1580
|
-
});
|
|
1581
|
-
it('should add appropriate messages for other finish reasons', async () => {
|
|
1582
|
-
const testCases = [
|
|
1583
|
-
{
|
|
1584
|
-
reason: 'SAFETY',
|
|
1585
|
-
message: '⚠️ Response stopped due to safety reasons.',
|
|
1586
|
-
},
|
|
1587
|
-
{
|
|
1588
|
-
reason: 'RECITATION',
|
|
1589
|
-
message: '⚠️ Response stopped due to recitation policy.',
|
|
1590
|
-
},
|
|
1591
|
-
{
|
|
1592
|
-
reason: 'LANGUAGE',
|
|
1593
|
-
message: '⚠️ Response stopped due to unsupported language.',
|
|
1594
|
-
},
|
|
1595
|
-
{
|
|
1596
|
-
reason: 'BLOCKLIST',
|
|
1597
|
-
message: '⚠️ Response stopped due to forbidden terms.',
|
|
1598
|
-
},
|
|
1599
|
-
{
|
|
1600
|
-
reason: 'PROHIBITED_CONTENT',
|
|
1601
|
-
message: '⚠️ Response stopped due to prohibited content.',
|
|
1602
|
-
},
|
|
1603
|
-
{
|
|
1604
|
-
reason: 'SPII',
|
|
1605
|
-
message: '⚠️ Response stopped due to sensitive personally identifiable information.',
|
|
1606
|
-
},
|
|
1607
|
-
{ reason: 'OTHER', message: '⚠️ Response stopped for other reasons.' },
|
|
1608
|
-
{
|
|
1609
|
-
reason: 'MALFORMED_FUNCTION_CALL',
|
|
1610
|
-
message: '⚠️ Response stopped due to malformed function call.',
|
|
1611
|
-
},
|
|
1612
|
-
{
|
|
1613
|
-
reason: 'IMAGE_SAFETY',
|
|
1614
|
-
message: '⚠️ Response stopped due to image safety violations.',
|
|
1615
|
-
},
|
|
1616
|
-
{
|
|
1617
|
-
reason: 'UNEXPECTED_TOOL_CALL',
|
|
1618
|
-
message: '⚠️ Response stopped due to unexpected tool call.',
|
|
1619
|
-
},
|
|
1620
|
-
];
|
|
1621
|
-
for (const { reason, message } of testCases) {
|
|
1622
|
-
// Reset mocks for each test case
|
|
1623
|
-
mockAddItem.mockClear();
|
|
1624
|
-
mockSendMessageStream.mockReturnValue((async function* () {
|
|
1625
|
-
yield {
|
|
1626
|
-
type: ServerGeminiEventType.Content,
|
|
1627
|
-
value: `Response for ${reason}`,
|
|
1628
|
-
};
|
|
1629
|
-
yield {
|
|
1630
|
-
type: ServerGeminiEventType.Finished,
|
|
1631
|
-
value: { reason, usageMetadata: undefined },
|
|
1632
|
-
};
|
|
1633
|
-
})());
|
|
1634
|
-
const { result } = renderHook(() => useGeminiStream(new MockedGeminiClientClass(mockConfig), [], mockAddItem, mockConfig, mockLoadedSettings, mockOnDebugMessage, mockHandleSlashCommand, false, () => 'vscode', () => { }, () => Promise.resolve(), false, () => { }, () => { }, () => { }, vi.fn(), 80, 24));
|
|
1635
|
-
await act(async () => {
|
|
1636
|
-
await result.current.submitQuery(`Test ${reason}`);
|
|
1637
|
-
});
|
|
1396
|
+
if (shouldAddMessage) {
|
|
1638
1397
|
await waitFor(() => {
|
|
1639
1398
|
expect(mockAddItem).toHaveBeenCalledWith({
|
|
1640
1399
|
type: 'info',
|
|
@@ -1642,6 +1401,14 @@ describe('useGeminiStream', () => {
|
|
|
1642
1401
|
}, expect.any(Number));
|
|
1643
1402
|
});
|
|
1644
1403
|
}
|
|
1404
|
+
else {
|
|
1405
|
+
// Verify state returns to idle without any info messages
|
|
1406
|
+
await waitFor(() => {
|
|
1407
|
+
expect(result.current.streamingState).toBe(StreamingState.Idle);
|
|
1408
|
+
});
|
|
1409
|
+
const infoMessages = mockAddItem.mock.calls.filter((call) => call[0].type === 'info');
|
|
1410
|
+
expect(infoMessages).toHaveLength(0);
|
|
1411
|
+
}
|
|
1645
1412
|
});
|
|
1646
1413
|
});
|
|
1647
1414
|
it('should process @include commands, adding user turn after processing to prevent race conditions', async () => {
|