@google/gemini-cli 0.18.0-preview.1 → 0.19.0-nightly.20251122.42c2e1b21

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.
Files changed (203) hide show
  1. package/dist/google-gemini-cli-0.19.0-nightly.20251120.8e531dc02.tgz +0 -0
  2. package/dist/index.js +8 -6
  3. package/dist/index.js.map +1 -1
  4. package/dist/package.json +3 -3
  5. package/dist/src/commands/extensions/examples/mcp-server/package.json +1 -1
  6. package/dist/src/config/auth.js +4 -0
  7. package/dist/src/config/auth.js.map +1 -1
  8. package/dist/src/config/auth.test.js +61 -37
  9. package/dist/src/config/auth.test.js.map +1 -1
  10. package/dist/src/config/config.integration.test.js +81 -198
  11. package/dist/src/config/config.integration.test.js.map +1 -1
  12. package/dist/src/config/config.js +2 -6
  13. package/dist/src/config/config.js.map +1 -1
  14. package/dist/src/config/config.test.js +196 -299
  15. package/dist/src/config/config.test.js.map +1 -1
  16. package/dist/src/config/extension.test.js +109 -133
  17. package/dist/src/config/extension.test.js.map +1 -1
  18. package/dist/src/config/extensions/consent.test.js +152 -0
  19. package/dist/src/config/extensions/consent.test.js.map +1 -0
  20. package/dist/src/config/extensions/extensionEnablement.test.js +82 -15
  21. package/dist/src/config/extensions/extensionEnablement.test.js.map +1 -1
  22. package/dist/src/config/extensions/extensionSettings.test.js +105 -1
  23. package/dist/src/config/extensions/extensionSettings.test.js.map +1 -1
  24. package/dist/src/config/extensions/github.d.ts +1 -0
  25. package/dist/src/config/extensions/github.js +1 -1
  26. package/dist/src/config/extensions/github.js.map +1 -1
  27. package/dist/src/config/extensions/github.test.js +197 -318
  28. package/dist/src/config/extensions/github.test.js.map +1 -1
  29. package/dist/src/config/extensions/storage.test.d.ts +6 -0
  30. package/dist/src/config/extensions/storage.test.js +64 -0
  31. package/dist/src/config/extensions/storage.test.js.map +1 -0
  32. package/dist/src/config/extensions/update.test.js +154 -263
  33. package/dist/src/config/extensions/update.test.js.map +1 -1
  34. package/dist/src/config/extensions/variables.test.js +87 -1
  35. package/dist/src/config/extensions/variables.test.js.map +1 -1
  36. package/dist/src/config/sandboxConfig.d.ts +1 -1
  37. package/dist/src/config/sandboxConfig.js.map +1 -1
  38. package/dist/src/config/sandboxConfig.test.d.ts +6 -0
  39. package/dist/src/config/sandboxConfig.test.js +178 -0
  40. package/dist/src/config/sandboxConfig.test.js.map +1 -0
  41. package/dist/src/config/settingPaths.test.d.ts +6 -0
  42. package/dist/src/config/settingPaths.test.js +22 -0
  43. package/dist/src/config/settingPaths.test.js.map +1 -0
  44. package/dist/src/config/settings.test.js +164 -226
  45. package/dist/src/config/settings.test.js.map +1 -1
  46. package/dist/src/config/settingsSchema.d.ts +10 -10
  47. package/dist/src/config/settingsSchema.js +10 -10
  48. package/dist/src/config/settingsSchema.js.map +1 -1
  49. package/dist/src/config/settingsSchema.test.js +0 -6
  50. package/dist/src/config/settingsSchema.test.js.map +1 -1
  51. package/dist/src/gemini.d.ts +1 -1
  52. package/dist/src/gemini.js +5 -7
  53. package/dist/src/gemini.js.map +1 -1
  54. package/dist/src/gemini.test.js +18 -21
  55. package/dist/src/gemini.test.js.map +1 -1
  56. package/dist/src/generated/git-commit.d.ts +2 -2
  57. package/dist/src/generated/git-commit.js +2 -2
  58. package/dist/src/generated/git-commit.js.map +1 -1
  59. package/dist/src/services/BuiltinCommandLoader.js +1 -1
  60. package/dist/src/services/BuiltinCommandLoader.js.map +1 -1
  61. package/dist/src/services/BuiltinCommandLoader.test.js +0 -22
  62. package/dist/src/services/BuiltinCommandLoader.test.js.map +1 -1
  63. package/dist/src/test-utils/mockCommandContext.js +1 -1
  64. package/dist/src/test-utils/render.js +1 -1
  65. package/dist/src/test-utils/render.js.map +1 -1
  66. package/dist/src/ui/AppContainer.js +13 -11
  67. package/dist/src/ui/AppContainer.js.map +1 -1
  68. package/dist/src/ui/AppContainer.test.js +10 -16
  69. package/dist/src/ui/AppContainer.test.js.map +1 -1
  70. package/dist/src/ui/auth/AuthDialog.js +17 -10
  71. package/dist/src/ui/auth/AuthDialog.js.map +1 -1
  72. package/dist/src/ui/auth/AuthDialog.test.js +5 -2
  73. package/dist/src/ui/auth/AuthDialog.test.js.map +1 -1
  74. package/dist/src/ui/components/AppHeader.js +3 -17
  75. package/dist/src/ui/components/AppHeader.js.map +1 -1
  76. package/dist/src/ui/components/AppHeader.test.js +10 -4
  77. package/dist/src/ui/components/AppHeader.test.js.map +1 -1
  78. package/dist/src/ui/components/DebugProfiler.js +1 -1
  79. package/dist/src/ui/components/DebugProfiler.js.map +1 -1
  80. package/dist/src/ui/components/DialogManager.js +6 -1
  81. package/dist/src/ui/components/DialogManager.js.map +1 -1
  82. package/dist/src/ui/components/InputPrompt.js +1 -1
  83. package/dist/src/ui/components/InputPrompt.js.map +1 -1
  84. package/dist/src/ui/components/LoadingIndicator.js +6 -1
  85. package/dist/src/ui/components/LoadingIndicator.js.map +1 -1
  86. package/dist/src/ui/components/ModelDialog.test.js +1 -1
  87. package/dist/src/ui/components/ModelDialog.test.js.map +1 -1
  88. package/dist/src/ui/components/SessionBrowser.d.ts +98 -0
  89. package/dist/src/ui/components/SessionBrowser.js +457 -0
  90. package/dist/src/ui/components/SessionBrowser.js.map +1 -0
  91. package/dist/src/ui/components/SessionBrowser.test.d.ts +6 -0
  92. package/dist/src/ui/components/SessionBrowser.test.js +245 -0
  93. package/dist/src/ui/components/SessionBrowser.test.js.map +1 -0
  94. package/dist/src/ui/components/messages/ShellToolMessage.js +2 -2
  95. package/dist/src/ui/components/messages/ShellToolMessage.js.map +1 -1
  96. package/dist/src/ui/components/messages/ToolMessage.d.ts +5 -0
  97. package/dist/src/ui/components/messages/ToolMessage.js +32 -3
  98. package/dist/src/ui/components/messages/ToolMessage.js.map +1 -1
  99. package/dist/src/ui/components/shared/text-buffer.js +20 -4
  100. package/dist/src/ui/components/shared/text-buffer.js.map +1 -1
  101. package/dist/src/ui/components/shared/text-buffer.test.js +20 -0
  102. package/dist/src/ui/components/shared/text-buffer.test.js.map +1 -1
  103. package/dist/src/ui/constants.d.ts +1 -0
  104. package/dist/src/ui/constants.js +1 -0
  105. package/dist/src/ui/constants.js.map +1 -1
  106. package/dist/src/ui/hooks/shellCommandProcessor.d.ts +1 -0
  107. package/dist/src/ui/hooks/shellCommandProcessor.js +3 -1
  108. package/dist/src/ui/hooks/shellCommandProcessor.js.map +1 -1
  109. package/dist/src/ui/hooks/useAlternateBuffer.js +1 -1
  110. package/dist/src/ui/hooks/useAlternateBuffer.js.map +1 -1
  111. package/dist/src/ui/hooks/useBanner.d.ts +14 -0
  112. package/dist/src/ui/hooks/useBanner.js +48 -0
  113. package/dist/src/ui/hooks/useBanner.js.map +1 -0
  114. package/dist/src/ui/hooks/useBanner.test.d.ts +6 -0
  115. package/dist/src/ui/hooks/useBanner.test.js +92 -0
  116. package/dist/src/ui/hooks/useBanner.test.js.map +1 -0
  117. package/dist/src/ui/hooks/useBracketedPaste.js +3 -5
  118. package/dist/src/ui/hooks/useBracketedPaste.js.map +1 -1
  119. package/dist/src/ui/hooks/useGeminiStream.d.ts +1 -0
  120. package/dist/src/ui/hooks/useGeminiStream.js +6 -4
  121. package/dist/src/ui/hooks/useGeminiStream.js.map +1 -1
  122. package/dist/src/ui/hooks/useGeminiStream.test.js +1 -1
  123. package/dist/src/ui/hooks/useGeminiStream.test.js.map +1 -1
  124. package/dist/src/ui/hooks/useInactivityTimer.d.ts +14 -0
  125. package/dist/src/ui/hooks/useInactivityTimer.js +30 -0
  126. package/dist/src/ui/hooks/useInactivityTimer.js.map +1 -0
  127. package/dist/src/ui/hooks/useLoadingIndicator.d.ts +1 -1
  128. package/dist/src/ui/hooks/useLoadingIndicator.js +2 -2
  129. package/dist/src/ui/hooks/useLoadingIndicator.js.map +1 -1
  130. package/dist/src/ui/hooks/useLoadingIndicator.test.js +16 -5
  131. package/dist/src/ui/hooks/useLoadingIndicator.test.js.map +1 -1
  132. package/dist/src/ui/hooks/usePhraseCycler.d.ts +4 -1
  133. package/dist/src/ui/hooks/usePhraseCycler.js +52 -43
  134. package/dist/src/ui/hooks/usePhraseCycler.js.map +1 -1
  135. package/dist/src/ui/hooks/usePhraseCycler.test.js +52 -3
  136. package/dist/src/ui/hooks/usePhraseCycler.test.js.map +1 -1
  137. package/dist/src/ui/hooks/useReactToolScheduler.d.ts +2 -1
  138. package/dist/src/ui/hooks/useReactToolScheduler.js +3 -0
  139. package/dist/src/ui/hooks/useReactToolScheduler.js.map +1 -1
  140. package/dist/src/ui/hooks/useSessionBrowser.d.ts +18 -1
  141. package/dist/src/ui/hooks/useSessionBrowser.js +59 -0
  142. package/dist/src/ui/hooks/useSessionBrowser.js.map +1 -1
  143. package/dist/src/ui/hooks/useSessionBrowser.test.js +154 -526
  144. package/dist/src/ui/hooks/useSessionBrowser.test.js.map +1 -1
  145. package/dist/src/ui/hooks/useSlashCompletion.test.js +1 -1
  146. package/dist/src/ui/hooks/useSlashCompletion.test.js.map +1 -1
  147. package/dist/src/ui/hooks/useToolScheduler.test.js +1 -1
  148. package/dist/src/ui/hooks/useToolScheduler.test.js.map +1 -1
  149. package/dist/src/ui/privacy/CloudFreePrivacyNotice.test.d.ts +6 -0
  150. package/dist/src/ui/privacy/CloudFreePrivacyNotice.test.js +121 -0
  151. package/dist/src/ui/privacy/CloudFreePrivacyNotice.test.js.map +1 -0
  152. package/dist/src/ui/privacy/CloudPaidPrivacyNotice.test.d.ts +6 -0
  153. package/dist/src/ui/privacy/CloudPaidPrivacyNotice.test.js +34 -0
  154. package/dist/src/ui/privacy/CloudPaidPrivacyNotice.test.js.map +1 -0
  155. package/dist/src/ui/privacy/GeminiPrivacyNotice.test.d.ts +6 -0
  156. package/dist/src/ui/privacy/GeminiPrivacyNotice.test.js +34 -0
  157. package/dist/src/ui/privacy/GeminiPrivacyNotice.test.js.map +1 -0
  158. package/dist/src/ui/privacy/PrivacyNotice.test.d.ts +6 -0
  159. package/dist/src/ui/privacy/PrivacyNotice.test.js +62 -0
  160. package/dist/src/ui/privacy/PrivacyNotice.test.js.map +1 -0
  161. package/dist/src/ui/types.js +1 -1
  162. package/dist/src/ui/utils/bracketedPaste.d.ts +7 -0
  163. package/dist/src/ui/utils/bracketedPaste.js +15 -0
  164. package/dist/src/ui/utils/bracketedPaste.js.map +1 -0
  165. package/dist/src/ui/utils/kittyProtocolDetector.js +3 -4
  166. package/dist/src/ui/utils/kittyProtocolDetector.js.map +1 -1
  167. package/dist/src/ui/utils/mouse.d.ts +2 -2
  168. package/dist/src/ui/utils/mouse.js +2 -11
  169. package/dist/src/ui/utils/mouse.js.map +1 -1
  170. package/dist/src/utils/persistentState.d.ts +1 -1
  171. package/dist/src/utils/sessionCleanup.test.js +38 -0
  172. package/dist/src/utils/sessionCleanup.test.js.map +1 -1
  173. package/dist/src/utils/sessionUtils.d.ts +49 -4
  174. package/dist/src/utils/sessionUtils.js +100 -25
  175. package/dist/src/utils/sessionUtils.js.map +1 -1
  176. package/dist/src/utils/sessionUtils.test.js +46 -3
  177. package/dist/src/utils/sessionUtils.test.js.map +1 -1
  178. package/dist/src/utils/sessions.js +4 -1
  179. package/dist/src/utils/sessions.js.map +1 -1
  180. package/dist/src/utils/sessions.test.js +42 -0
  181. package/dist/src/utils/sessions.test.js.map +1 -1
  182. package/dist/src/zed-integration/connection.test.d.ts +6 -0
  183. package/dist/src/zed-integration/connection.test.js +175 -0
  184. package/dist/src/zed-integration/connection.test.js.map +1 -0
  185. package/dist/src/zed-integration/fileSystemService.test.d.ts +6 -0
  186. package/dist/src/zed-integration/fileSystemService.test.js +98 -0
  187. package/dist/src/zed-integration/fileSystemService.test.js.map +1 -0
  188. package/dist/src/zed-integration/schema.d.ts +30 -30
  189. package/dist/src/zed-integration/zedIntegration.d.ts +31 -1
  190. package/dist/src/zed-integration/zedIntegration.js +5 -2
  191. package/dist/src/zed-integration/zedIntegration.js.map +1 -1
  192. package/dist/src/zed-integration/zedIntegration.test.d.ts +6 -0
  193. package/dist/src/zed-integration/zedIntegration.test.js +619 -0
  194. package/dist/src/zed-integration/zedIntegration.test.js.map +1 -0
  195. package/dist/tsconfig.tsbuildinfo +1 -1
  196. package/package.json +4 -4
  197. package/dist/google-gemini-cli-0.18.0-preview.0.tgz +0 -0
  198. package/dist/src/utils/stdio.d.ts +0 -32
  199. package/dist/src/utils/stdio.js +0 -85
  200. package/dist/src/utils/stdio.js.map +0 -1
  201. package/dist/src/utils/stdio.test.js +0 -47
  202. package/dist/src/utils/stdio.test.js.map +0 -1
  203. /package/dist/src/{utils/stdio.test.d.ts → config/extensions/consent.test.d.ts} +0 -0
@@ -3,38 +3,120 @@
3
3
  * Copyright 2025 Google LLC
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
- import { convertSessionToHistoryFormats } from './useSessionBrowser.js';
7
- import { MessageType, ToolCallStatus } from '../types.js';
6
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
7
+ import { renderHook } from '../../test-utils/render.js';
8
+ import { act } from 'react';
9
+ import { useSessionBrowser, convertSessionToHistoryFormats, } from './useSessionBrowser.js';
10
+ import * as fs from 'node:fs/promises';
11
+ import path from 'node:path';
12
+ import { getSessionFiles } from '../../utils/sessionUtils.js';
13
+ // Mock modules
14
+ vi.mock('fs/promises');
15
+ vi.mock('path');
16
+ vi.mock('../../utils/sessionUtils.js');
17
+ const MOCKED_PROJECT_TEMP_DIR = '/test/project/temp';
18
+ const MOCKED_CHATS_DIR = '/test/project/temp/chats';
19
+ const MOCKED_SESSION_ID = 'test-session-123';
20
+ const MOCKED_CURRENT_SESSION_ID = 'current-session-id';
21
+ describe('useSessionBrowser', () => {
22
+ const mockedFs = vi.mocked(fs);
23
+ const mockedPath = vi.mocked(path);
24
+ const mockedGetSessionFiles = vi.mocked(getSessionFiles);
25
+ const mockConfig = {
26
+ storage: {
27
+ getProjectTempDir: vi.fn(),
28
+ },
29
+ setSessionId: vi.fn(),
30
+ getSessionId: vi.fn(),
31
+ getGeminiClient: vi.fn().mockReturnValue({
32
+ getChatRecordingService: vi.fn().mockReturnValue({
33
+ deleteSession: vi.fn(),
34
+ }),
35
+ }),
36
+ };
37
+ const mockOnLoadHistory = vi.fn();
38
+ beforeEach(() => {
39
+ vi.resetAllMocks();
40
+ mockedPath.join.mockImplementation((...args) => args.join('/'));
41
+ vi.mocked(mockConfig.storage.getProjectTempDir).mockReturnValue(MOCKED_PROJECT_TEMP_DIR);
42
+ vi.mocked(mockConfig.getSessionId).mockReturnValue(MOCKED_CURRENT_SESSION_ID);
43
+ });
44
+ it('should successfully resume a session', async () => {
45
+ const MOCKED_FILENAME = 'session-2025-01-01-test-session-123.json';
46
+ const mockConversation = {
47
+ sessionId: 'existing-session-456',
48
+ messages: [{ type: 'user', content: 'Hello' }],
49
+ };
50
+ const mockSession = {
51
+ id: MOCKED_SESSION_ID,
52
+ fileName: MOCKED_FILENAME,
53
+ };
54
+ mockedGetSessionFiles.mockResolvedValue([mockSession]);
55
+ mockedFs.readFile.mockResolvedValue(JSON.stringify(mockConversation));
56
+ const { result } = renderHook(() => useSessionBrowser(mockConfig, mockOnLoadHistory));
57
+ await act(async () => {
58
+ await result.current.handleResumeSession(mockSession);
59
+ });
60
+ expect(mockedFs.readFile).toHaveBeenCalledWith(`${MOCKED_CHATS_DIR}/${MOCKED_FILENAME}`, 'utf8');
61
+ expect(mockConfig.setSessionId).toHaveBeenCalledWith('existing-session-456');
62
+ expect(result.current.isSessionBrowserOpen).toBe(false);
63
+ expect(mockOnLoadHistory).toHaveBeenCalled();
64
+ });
65
+ it('should handle file read error', async () => {
66
+ const MOCKED_FILENAME = 'session-2025-01-01-test-session-123.json';
67
+ const mockSession = {
68
+ id: MOCKED_SESSION_ID,
69
+ fileName: MOCKED_FILENAME,
70
+ };
71
+ mockedFs.readFile.mockRejectedValue(new Error('File not found'));
72
+ const consoleErrorSpy = vi
73
+ .spyOn(console, 'error')
74
+ .mockImplementation(() => { });
75
+ const { result } = renderHook(() => useSessionBrowser(mockConfig, mockOnLoadHistory));
76
+ await act(async () => {
77
+ await result.current.handleResumeSession(mockSession);
78
+ });
79
+ expect(consoleErrorSpy).toHaveBeenCalled();
80
+ expect(result.current.isSessionBrowserOpen).toBe(false);
81
+ consoleErrorSpy.mockRestore();
82
+ });
83
+ it('should handle JSON parse error', async () => {
84
+ const MOCKED_FILENAME = 'invalid.json';
85
+ const mockSession = {
86
+ id: MOCKED_SESSION_ID,
87
+ fileName: MOCKED_FILENAME,
88
+ };
89
+ mockedFs.readFile.mockResolvedValue('invalid json');
90
+ const consoleErrorSpy = vi
91
+ .spyOn(console, 'error')
92
+ .mockImplementation(() => { });
93
+ const { result } = renderHook(() => useSessionBrowser(mockConfig, mockOnLoadHistory));
94
+ await act(async () => {
95
+ await result.current.handleResumeSession(mockSession);
96
+ });
97
+ expect(consoleErrorSpy).toHaveBeenCalled();
98
+ expect(result.current.isSessionBrowserOpen).toBe(false);
99
+ consoleErrorSpy.mockRestore();
100
+ });
101
+ });
102
+ // The convertSessionToHistoryFormats tests are self-contained and do not need changes.
8
103
  describe('convertSessionToHistoryFormats', () => {
9
104
  it('should convert empty messages array', () => {
10
105
  const result = convertSessionToHistoryFormats([]);
11
106
  expect(result.uiHistory).toEqual([]);
12
107
  expect(result.clientHistory).toEqual([]);
13
108
  });
14
- it('should convert basic user and gemini messages', () => {
109
+ it('should convert basic user and model messages', () => {
15
110
  const messages = [
16
- {
17
- id: 'msg-1',
18
- timestamp: '2025-01-01T00:01:00Z',
19
- content: 'Hello',
20
- type: 'user',
21
- },
22
- {
23
- id: 'msg-2',
24
- timestamp: '2025-01-01T00:02:00Z',
25
- content: 'Hi there!',
26
- type: 'gemini',
27
- },
111
+ { type: 'user', content: 'Hello' },
112
+ { type: 'gemini', content: 'Hi there' },
28
113
  ];
29
114
  const result = convertSessionToHistoryFormats(messages);
30
115
  expect(result.uiHistory).toHaveLength(2);
31
- expect(result.uiHistory[0]).toEqual({
32
- type: MessageType.USER,
33
- text: 'Hello',
34
- });
35
- expect(result.uiHistory[1]).toEqual({
36
- type: MessageType.GEMINI,
37
- text: 'Hi there!',
116
+ expect(result.uiHistory[0]).toMatchObject({ type: 'user', text: 'Hello' });
117
+ expect(result.uiHistory[1]).toMatchObject({
118
+ type: 'gemini',
119
+ text: 'Hi there',
38
120
  });
39
121
  expect(result.clientHistory).toHaveLength(2);
40
122
  expect(result.clientHistory[0]).toEqual({
@@ -43,538 +125,84 @@ describe('convertSessionToHistoryFormats', () => {
43
125
  });
44
126
  expect(result.clientHistory[1]).toEqual({
45
127
  role: 'model',
46
- parts: [{ text: 'Hi there!' }],
128
+ parts: [{ text: 'Hi there' }],
47
129
  });
48
130
  });
49
- it('should convert system, warning, and error messages to appropriate types', () => {
131
+ it('should filter out slash commands from client history but keep in UI', () => {
50
132
  const messages = [
51
- {
52
- id: 'msg-1',
53
- timestamp: '2025-01-01T00:01:00Z',
54
- content: 'System message',
55
- type: 'info',
56
- },
57
- {
58
- id: 'msg-2',
59
- timestamp: '2025-01-01T00:02:00Z',
60
- content: 'Warning message',
61
- type: 'warning',
62
- },
63
- {
64
- id: 'msg-3',
65
- timestamp: '2025-01-01T00:03:00Z',
66
- content: 'Error occurred',
67
- type: 'error',
68
- },
133
+ { type: 'user', content: '/help' },
134
+ { type: 'info', content: 'Help text' },
69
135
  ];
70
136
  const result = convertSessionToHistoryFormats(messages);
71
- expect(result.uiHistory[0]).toEqual({
72
- type: MessageType.INFO,
73
- text: 'System message',
74
- });
75
- expect(result.uiHistory[1]).toEqual({
76
- type: MessageType.WARNING,
77
- text: 'Warning message',
78
- });
79
- expect(result.uiHistory[2]).toEqual({
80
- type: MessageType.ERROR,
81
- text: 'Error occurred',
82
- });
83
- // System, warning, and error messages should not be included in client history
84
- expect(result.clientHistory).toEqual([]);
85
- });
86
- it('should filter out slash commands from client history', () => {
87
- const messages = [
88
- {
89
- id: 'msg-1',
90
- timestamp: '2025-01-01T00:01:00Z',
91
- content: '/help',
92
- type: 'user',
93
- },
94
- {
95
- id: 'msg-2',
96
- timestamp: '2025-01-01T00:02:00Z',
97
- content: '?quit',
98
- type: 'user',
99
- },
100
- {
101
- id: 'msg-3',
102
- timestamp: '2025-01-01T00:03:00Z',
103
- content: 'Regular message',
104
- type: 'user',
105
- },
106
- ];
107
- const result = convertSessionToHistoryFormats(messages);
108
- // All messages should appear in UI history
109
- expect(result.uiHistory).toHaveLength(3);
110
- // Only non-slash commands should appear in client history
111
- expect(result.clientHistory).toHaveLength(1);
112
- expect(result.clientHistory[0]).toEqual({
113
- role: 'user',
114
- parts: [{ text: 'Regular message' }],
115
- });
116
- });
117
- it('should handle tool calls correctly', () => {
118
- const messages = [
119
- {
120
- id: 'msg-1',
121
- timestamp: '2025-01-01T00:01:00Z',
122
- content: "I'll help you with that.",
123
- type: 'gemini',
124
- toolCalls: [
125
- {
126
- id: 'tool-1',
127
- name: 'bash',
128
- displayName: 'Execute Command',
129
- description: 'Run bash command',
130
- args: { command: 'ls -la' },
131
- status: 'success',
132
- timestamp: '2025-01-01T00:01:30Z',
133
- resultDisplay: 'total 4\ndrwxr-xr-x 2 user user 4096 Jan 1 00:00 .',
134
- renderOutputAsMarkdown: false,
135
- },
136
- {
137
- id: 'tool-2',
138
- name: 'read',
139
- displayName: 'Read File',
140
- description: 'Read file contents',
141
- args: { path: '/etc/hosts' },
142
- status: 'error',
143
- timestamp: '2025-01-01T00:01:45Z',
144
- resultDisplay: 'Permission denied',
145
- },
146
- ],
147
- },
148
- ];
149
- const result = convertSessionToHistoryFormats(messages);
150
- expect(result.uiHistory).toHaveLength(2); // text message + tool group
151
- expect(result.uiHistory[0]).toEqual({
152
- type: MessageType.GEMINI,
153
- text: "I'll help you with that.",
154
- });
155
- expect(result.uiHistory[1].type).toBe('tool_group');
156
- // This if-statement is only necessary because TypeScript can't tell that the toBe() assertion
157
- // protects the .tools access below.
158
- if (result.uiHistory[1].type === 'tool_group') {
159
- expect(result.uiHistory[1].tools).toHaveLength(2);
160
- expect(result.uiHistory[1].tools[0]).toEqual({
161
- callId: 'tool-1',
162
- name: 'Execute Command',
163
- description: 'Run bash command',
164
- renderOutputAsMarkdown: false,
165
- status: ToolCallStatus.Success,
166
- resultDisplay: 'total 4\ndrwxr-xr-x 2 user user 4096 Jan 1 00:00 .',
167
- confirmationDetails: undefined,
168
- });
169
- expect(result.uiHistory[1].tools[1]).toEqual({
170
- callId: 'tool-2',
171
- name: 'Read File',
172
- description: 'Read file contents',
173
- renderOutputAsMarkdown: true, // default value
174
- status: ToolCallStatus.Error,
175
- resultDisplay: 'Permission denied',
176
- confirmationDetails: undefined,
177
- });
178
- }
179
- });
180
- it('should skip empty tool calls arrays', () => {
181
- const messages = [
182
- {
183
- id: 'msg-1',
184
- timestamp: '2025-01-01T00:01:00Z',
185
- content: 'Message with empty tools',
186
- type: 'gemini',
187
- toolCalls: [],
188
- },
189
- ];
190
- const result = convertSessionToHistoryFormats(messages);
191
- expect(result.uiHistory).toHaveLength(1); // Only text message
192
- expect(result.uiHistory[0]).toEqual({
193
- type: MessageType.GEMINI,
194
- text: 'Message with empty tools',
195
- });
196
- });
197
- it('should not add tool calls for user messages', () => {
198
- const messages = [
199
- {
200
- id: 'msg-1',
201
- timestamp: '2025-01-01T00:01:00Z',
202
- content: 'User message',
203
- type: 'user',
204
- // This would be invalid in real usage, but testing robustness
205
- toolCalls: [
206
- {
207
- id: 'tool-1',
208
- name: 'invalid',
209
- args: {},
210
- status: 'success',
211
- timestamp: '2025-01-01T00:01:30Z',
212
- },
213
- ],
214
- },
215
- ];
216
- const result = convertSessionToHistoryFormats(messages);
217
- expect(result.uiHistory).toHaveLength(1); // Only user message, no tool group
218
- expect(result.uiHistory[0]).toEqual({
219
- type: MessageType.USER,
220
- text: 'User message',
137
+ expect(result.uiHistory).toHaveLength(2);
138
+ expect(result.uiHistory[0]).toMatchObject({ type: 'user', text: '/help' });
139
+ expect(result.uiHistory[1]).toMatchObject({
140
+ type: 'info',
141
+ text: 'Help text',
221
142
  });
143
+ expect(result.clientHistory).toHaveLength(0);
222
144
  });
223
- it('should handle missing tool call fields gracefully', () => {
145
+ it('should handle tool calls and responses', () => {
224
146
  const messages = [
147
+ { type: 'user', content: 'What time is it?' },
225
148
  {
226
- id: 'msg-1',
227
- timestamp: '2025-01-01T00:01:00Z',
228
- content: 'Message with minimal tool',
229
149
  type: 'gemini',
150
+ content: '',
230
151
  toolCalls: [
231
152
  {
232
- id: 'tool-1',
233
- name: 'minimal_tool',
153
+ id: 'call_1',
154
+ name: 'get_time',
234
155
  args: {},
235
156
  status: 'success',
236
- timestamp: '2025-01-01T00:01:30Z',
237
- // Missing optional fields
157
+ result: '12:00',
238
158
  },
239
159
  ],
240
160
  },
241
161
  ];
242
162
  const result = convertSessionToHistoryFormats(messages);
243
163
  expect(result.uiHistory).toHaveLength(2);
244
- expect(result.uiHistory[1].type).toBe('tool_group');
245
- if (result.uiHistory[1].type === 'tool_group') {
246
- expect(result.uiHistory[1].tools[0]).toEqual({
247
- callId: 'tool-1',
248
- name: 'minimal_tool', // Falls back to name when displayName missing
249
- description: '', // Default empty string
250
- renderOutputAsMarkdown: true, // Default value
251
- status: ToolCallStatus.Success,
252
- resultDisplay: undefined,
253
- confirmationDetails: undefined,
254
- });
255
- }
256
- else {
257
- throw new Error('unreachable');
258
- }
259
- });
260
- describe('tool calls in client history', () => {
261
- it('should convert tool calls to correct Gemini client history format', () => {
262
- const messages = [
263
- {
264
- id: 'msg-1',
265
- timestamp: '2025-01-01T00:01:00Z',
266
- content: 'List files',
267
- type: 'user',
268
- },
269
- {
270
- id: 'msg-2',
271
- timestamp: '2025-01-01T00:02:00Z',
272
- content: "I'll list the files for you.",
273
- type: 'gemini',
274
- toolCalls: [
275
- {
276
- id: 'tool-1',
277
- name: 'list_directory',
278
- args: { path: '/home/user' },
279
- result: {
280
- functionResponse: {
281
- id: 'list_directory-1753650620141-f3b8b9e73919d',
282
- name: 'list_directory',
283
- response: {
284
- output: 'file1.txt\nfile2.txt',
285
- },
286
- },
287
- },
288
- status: 'success',
289
- timestamp: '2025-01-01T00:02:30Z',
290
- },
291
- ],
292
- },
293
- ];
294
- const result = convertSessionToHistoryFormats(messages);
295
- // Should have: user message, model with function call, user with function response
296
- expect(result.clientHistory).toHaveLength(3);
297
- // User message
298
- expect(result.clientHistory[0]).toEqual({
299
- role: 'user',
300
- parts: [{ text: 'List files' }],
301
- });
302
- // Model message with function call
303
- expect(result.clientHistory[1]).toEqual({
304
- role: 'model',
305
- parts: [
306
- { text: "I'll list the files for you." },
307
- {
308
- functionCall: {
309
- name: 'list_directory',
310
- args: { path: '/home/user' },
311
- id: 'tool-1',
312
- },
313
- },
314
- ],
315
- });
316
- // Function response
317
- expect(result.clientHistory[2]).toEqual({
318
- role: 'user',
319
- parts: [
320
- {
321
- functionResponse: {
322
- id: 'list_directory-1753650620141-f3b8b9e73919d',
323
- name: 'list_directory',
324
- response: { output: 'file1.txt\nfile2.txt' },
325
- },
326
- },
327
- ],
328
- });
164
+ expect(result.uiHistory[0]).toMatchObject({
165
+ type: 'user',
166
+ text: 'What time is it?',
167
+ });
168
+ expect(result.uiHistory[1]).toMatchObject({
169
+ type: 'tool_group',
170
+ tools: [
171
+ expect.objectContaining({
172
+ callId: 'call_1',
173
+ name: 'get_time',
174
+ status: 'Success',
175
+ }),
176
+ ],
177
+ });
178
+ expect(result.clientHistory).toHaveLength(3); // User, Model (call), User (response)
179
+ expect(result.clientHistory[0]).toEqual({
180
+ role: 'user',
181
+ parts: [{ text: 'What time is it?' }],
329
182
  });
330
- it('should handle tool calls without text content', () => {
331
- const messages = [
183
+ expect(result.clientHistory[1]).toEqual({
184
+ role: 'model',
185
+ parts: [
332
186
  {
333
- id: 'msg-1',
334
- timestamp: '2025-01-01T00:01:00Z',
335
- content: '',
336
- type: 'gemini',
337
- toolCalls: [
338
- {
339
- id: 'tool-1',
340
- name: 'bash',
341
- args: { command: 'ls' },
342
- result: 'file1.txt\nfile2.txt',
343
- status: 'success',
344
- timestamp: '2025-01-01T00:01:30Z',
345
- },
346
- ],
347
- },
348
- ];
349
- const result = convertSessionToHistoryFormats(messages);
350
- expect(result.clientHistory).toHaveLength(2);
351
- // Model message with only function call (no text)
352
- expect(result.clientHistory[0]).toEqual({
353
- role: 'model',
354
- parts: [
355
- {
356
- functionCall: {
357
- name: 'bash',
358
- args: { command: 'ls' },
359
- id: 'tool-1',
360
- },
361
- },
362
- ],
363
- });
364
- // Function response
365
- expect(result.clientHistory[1]).toEqual({
366
- role: 'user',
367
- parts: [
368
- {
369
- functionResponse: {
370
- id: 'tool-1',
371
- name: 'bash',
372
- response: {
373
- output: 'file1.txt\nfile2.txt',
374
- },
375
- },
187
+ functionCall: {
188
+ name: 'get_time',
189
+ args: {},
190
+ id: 'call_1',
376
191
  },
377
- ],
378
- });
379
- });
380
- it('should handle multiple tool calls in one message', () => {
381
- const messages = [
382
- {
383
- id: 'msg-1',
384
- timestamp: '2025-01-01T00:01:00Z',
385
- content: 'Running multiple commands',
386
- type: 'gemini',
387
- toolCalls: [
388
- {
389
- id: 'tool-1',
390
- name: 'bash',
391
- args: { command: 'pwd' },
392
- result: '/home/user',
393
- status: 'success',
394
- timestamp: '2025-01-01T00:01:30Z',
395
- },
396
- {
397
- id: 'tool-2',
398
- name: 'bash',
399
- args: { command: 'ls' },
400
- result: [
401
- {
402
- functionResponse: {
403
- id: 'tool-2',
404
- name: 'bash',
405
- response: {
406
- output: 'file1.txt',
407
- },
408
- },
409
- },
410
- {
411
- functionResponse: {
412
- id: 'tool-2',
413
- name: 'bash',
414
- response: {
415
- output: 'file2.txt',
416
- },
417
- },
418
- },
419
- ],
420
- status: 'success',
421
- timestamp: '2025-01-01T00:01:35Z',
422
- },
423
- ],
424
192
  },
425
- ];
426
- const result = convertSessionToHistoryFormats(messages);
427
- // Should have: model with both function calls, then one response
428
- expect(result.clientHistory).toHaveLength(2);
429
- // Model message with both function calls
430
- expect(result.clientHistory[0]).toEqual({
431
- role: 'model',
432
- parts: [
433
- { text: 'Running multiple commands' },
434
- {
435
- functionCall: {
436
- name: 'bash',
437
- args: { command: 'pwd' },
438
- id: 'tool-1',
439
- },
440
- },
441
- {
442
- functionCall: {
443
- name: 'bash',
444
- args: { command: 'ls' },
445
- id: 'tool-2',
446
- },
447
- },
448
- ],
449
- });
450
- // First function response
451
- expect(result.clientHistory[1]).toEqual({
452
- role: 'user',
453
- parts: [
454
- {
455
- functionResponse: {
456
- id: 'tool-1',
457
- name: 'bash',
458
- response: { output: '/home/user' },
459
- },
460
- },
461
- {
462
- functionResponse: {
463
- id: 'tool-2',
464
- name: 'bash',
465
- response: { output: 'file1.txt' },
466
- },
467
- },
468
- {
469
- functionResponse: {
470
- id: 'tool-2',
471
- name: 'bash',
472
- response: { output: 'file2.txt' },
473
- },
474
- },
475
- ],
476
- });
193
+ ],
477
194
  });
478
- it('should handle Part array results from tools', () => {
479
- const messages = [
195
+ expect(result.clientHistory[2]).toEqual({
196
+ role: 'user',
197
+ parts: [
480
198
  {
481
- id: 'msg-1',
482
- timestamp: '2025-01-01T00:01:00Z',
483
- content: 'Reading file',
484
- type: 'gemini',
485
- toolCalls: [
486
- {
487
- id: 'tool-1',
488
- name: 'read_file',
489
- args: { path: 'test.txt' },
490
- result: [
491
- {
492
- functionResponse: {
493
- id: 'tool-1',
494
- name: 'read_file',
495
- response: {
496
- output: 'Hello',
497
- },
498
- },
499
- },
500
- {
501
- functionResponse: {
502
- id: 'tool-1',
503
- name: 'read_file',
504
- response: {
505
- output: ' World',
506
- },
507
- },
508
- },
509
- ],
510
- status: 'success',
511
- timestamp: '2025-01-01T00:01:30Z',
512
- },
513
- ],
514
- },
515
- ];
516
- const result = convertSessionToHistoryFormats(messages);
517
- expect(result.clientHistory).toHaveLength(2);
518
- // Function response should extract both function responses
519
- expect(result.clientHistory[1]).toEqual({
520
- role: 'user',
521
- parts: [
522
- {
523
- functionResponse: {
524
- id: 'tool-1',
525
- name: 'read_file',
526
- response: {
527
- output: 'Hello',
528
- },
529
- },
530
- },
531
- {
532
- functionResponse: {
533
- id: 'tool-1',
534
- name: 'read_file',
535
- response: {
536
- output: ' World',
537
- },
538
- },
199
+ functionResponse: {
200
+ id: 'call_1',
201
+ name: 'get_time',
202
+ response: { output: '12:00' },
539
203
  },
540
- ],
541
- });
542
- });
543
- it('should skip tool calls without results', () => {
544
- const messages = [
545
- {
546
- id: 'msg-1',
547
- timestamp: '2025-01-01T00:01:00Z',
548
- content: 'Testing tool',
549
- type: 'gemini',
550
- toolCalls: [
551
- {
552
- id: 'tool-1',
553
- name: 'test_tool',
554
- args: { arg: 'value' },
555
- // No result field
556
- status: 'error',
557
- timestamp: '2025-01-01T00:01:30Z',
558
- },
559
- ],
560
204
  },
561
- ];
562
- const result = convertSessionToHistoryFormats(messages);
563
- // Should only have the model message with function call, no function response
564
- expect(result.clientHistory).toHaveLength(1);
565
- expect(result.clientHistory[0]).toEqual({
566
- role: 'model',
567
- parts: [
568
- { text: 'Testing tool' },
569
- {
570
- functionCall: {
571
- name: 'test_tool',
572
- args: { arg: 'value' },
573
- id: 'tool-1',
574
- },
575
- },
576
- ],
577
- });
205
+ ],
578
206
  });
579
207
  });
580
208
  });