@google/gemini-cli-core 0.1.22 → 0.2.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.
Files changed (176) hide show
  1. package/dist/src/code_assist/oauth2.d.ts +1 -0
  2. package/dist/src/code_assist/oauth2.js +7 -2
  3. package/dist/src/code_assist/oauth2.js.map +1 -1
  4. package/dist/src/code_assist/oauth2.test.js +49 -1
  5. package/dist/src/code_assist/oauth2.test.js.map +1 -1
  6. package/dist/src/config/config.d.ts +19 -2
  7. package/dist/src/config/config.js +29 -4
  8. package/dist/src/config/config.js.map +1 -1
  9. package/dist/src/core/client.js +5 -2
  10. package/dist/src/core/client.js.map +1 -1
  11. package/dist/src/core/client.test.js +2 -1
  12. package/dist/src/core/client.test.js.map +1 -1
  13. package/dist/src/core/coreToolScheduler.d.ts +2 -1
  14. package/dist/src/core/coreToolScheduler.js +20 -2
  15. package/dist/src/core/coreToolScheduler.js.map +1 -1
  16. package/dist/src/core/coreToolScheduler.test.js +190 -12
  17. package/dist/src/core/coreToolScheduler.test.js.map +1 -1
  18. package/dist/src/core/geminiChat.js +1 -1
  19. package/dist/src/core/geminiChat.js.map +1 -1
  20. package/dist/src/core/loggingContentGenerator.d.ts +1 -5
  21. package/dist/src/core/loggingContentGenerator.js +4 -6
  22. package/dist/src/core/loggingContentGenerator.js.map +1 -1
  23. package/dist/src/core/nonInteractiveToolExecutor.d.ts +2 -2
  24. package/dist/src/core/nonInteractiveToolExecutor.js +10 -2
  25. package/dist/src/core/nonInteractiveToolExecutor.js.map +1 -1
  26. package/dist/src/core/nonInteractiveToolExecutor.test.js +17 -11
  27. package/dist/src/core/nonInteractiveToolExecutor.test.js.map +1 -1
  28. package/dist/src/core/subagent.js +5 -5
  29. package/dist/src/core/subagent.js.map +1 -1
  30. package/dist/src/core/subagent.test.js +3 -3
  31. package/dist/src/core/subagent.test.js.map +1 -1
  32. package/dist/src/generated/git-commit.d.ts +2 -2
  33. package/dist/src/generated/git-commit.js +2 -2
  34. package/dist/src/generated/git-commit.js.map +1 -1
  35. package/dist/src/ide/ide-client.d.ts +4 -0
  36. package/dist/src/ide/ide-client.js +36 -2
  37. package/dist/src/ide/ide-client.js.map +1 -1
  38. package/dist/src/ide/ide-installer.js +17 -4
  39. package/dist/src/ide/ide-installer.js.map +1 -1
  40. package/dist/src/ide/ide-installer.test.js +6 -4
  41. package/dist/src/ide/ide-installer.test.js.map +1 -1
  42. package/dist/src/ide/process-utils.d.ts +10 -5
  43. package/dist/src/ide/process-utils.js +113 -30
  44. package/dist/src/ide/process-utils.js.map +1 -1
  45. package/dist/src/index.d.ts +3 -0
  46. package/dist/src/index.js +3 -0
  47. package/dist/src/index.js.map +1 -1
  48. package/dist/src/services/chatRecordingService.d.ts +150 -0
  49. package/dist/src/services/chatRecordingService.js +318 -0
  50. package/dist/src/services/chatRecordingService.js.map +1 -0
  51. package/dist/src/services/chatRecordingService.test.d.ts +6 -0
  52. package/dist/src/services/chatRecordingService.test.js +288 -0
  53. package/dist/src/services/chatRecordingService.test.js.map +1 -0
  54. package/dist/src/services/fileSystemService.d.ts +31 -0
  55. package/dist/src/services/fileSystemService.js +18 -0
  56. package/dist/src/services/fileSystemService.js.map +1 -0
  57. package/dist/src/services/fileSystemService.test.d.ts +6 -0
  58. package/dist/src/services/fileSystemService.test.js +41 -0
  59. package/dist/src/services/fileSystemService.test.js.map +1 -0
  60. package/dist/src/services/shellExecutionService.d.ts +8 -10
  61. package/dist/src/services/shellExecutionService.js +289 -133
  62. package/dist/src/services/shellExecutionService.js.map +1 -1
  63. package/dist/src/services/shellExecutionService.test.js +275 -41
  64. package/dist/src/services/shellExecutionService.test.js.map +1 -1
  65. package/dist/src/telemetry/clearcut-logger/clearcut-logger.d.ts +10 -3
  66. package/dist/src/telemetry/clearcut-logger/clearcut-logger.js +57 -103
  67. package/dist/src/telemetry/clearcut-logger/clearcut-logger.js.map +1 -1
  68. package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js +13 -4
  69. package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js.map +1 -1
  70. package/dist/src/telemetry/clearcut-logger/event-metadata-key.d.ts +2 -1
  71. package/dist/src/telemetry/clearcut-logger/event-metadata-key.js +2 -0
  72. package/dist/src/telemetry/clearcut-logger/event-metadata-key.js.map +1 -1
  73. package/dist/src/telemetry/loggers.js +2 -2
  74. package/dist/src/telemetry/loggers.js.map +1 -1
  75. package/dist/src/telemetry/loggers.test.js +10 -5
  76. package/dist/src/telemetry/loggers.test.js.map +1 -1
  77. package/dist/src/telemetry/metrics.d.ts +1 -1
  78. package/dist/src/telemetry/metrics.js +2 -1
  79. package/dist/src/telemetry/metrics.js.map +1 -1
  80. package/dist/src/telemetry/sdk.js +3 -3
  81. package/dist/src/telemetry/sdk.js.map +1 -1
  82. package/dist/src/telemetry/types.d.ts +1 -0
  83. package/dist/src/telemetry/types.js +6 -0
  84. package/dist/src/telemetry/types.js.map +1 -1
  85. package/dist/src/telemetry/uiTelemetry.d.ts +4 -0
  86. package/dist/src/telemetry/uiTelemetry.js +14 -1
  87. package/dist/src/telemetry/uiTelemetry.js.map +1 -1
  88. package/dist/src/telemetry/uiTelemetry.test.js +45 -9
  89. package/dist/src/telemetry/uiTelemetry.test.js.map +1 -1
  90. package/dist/src/test-utils/tools.d.ts +8 -2
  91. package/dist/src/test-utils/tools.js +2 -3
  92. package/dist/src/test-utils/tools.js.map +1 -1
  93. package/dist/src/tools/edit.d.ts +1 -1
  94. package/dist/src/tools/edit.js +15 -9
  95. package/dist/src/tools/edit.js.map +1 -1
  96. package/dist/src/tools/edit.test.js +11 -1
  97. package/dist/src/tools/edit.test.js.map +1 -1
  98. package/dist/src/tools/glob.d.ts +1 -1
  99. package/dist/src/tools/glob.js +8 -8
  100. package/dist/src/tools/glob.js.map +1 -1
  101. package/dist/src/tools/glob.test.js +20 -0
  102. package/dist/src/tools/glob.test.js.map +1 -1
  103. package/dist/src/tools/grep.d.ts +1 -1
  104. package/dist/src/tools/grep.js +1 -6
  105. package/dist/src/tools/grep.js.map +1 -1
  106. package/dist/src/tools/ls.d.ts +1 -1
  107. package/dist/src/tools/ls.js +1 -6
  108. package/dist/src/tools/ls.js.map +1 -1
  109. package/dist/src/tools/mcp-client-manager.d.ts +38 -0
  110. package/dist/src/tools/mcp-client-manager.js +74 -0
  111. package/dist/src/tools/mcp-client-manager.js.map +1 -0
  112. package/dist/src/tools/mcp-client-manager.test.d.ts +6 -0
  113. package/dist/src/tools/mcp-client-manager.test.js +39 -0
  114. package/dist/src/tools/mcp-client-manager.test.js.map +1 -0
  115. package/dist/src/tools/mcp-client.d.ts +43 -0
  116. package/dist/src/tools/mcp-client.js +117 -1
  117. package/dist/src/tools/mcp-client.js.map +1 -1
  118. package/dist/src/tools/mcp-client.test.js +58 -302
  119. package/dist/src/tools/mcp-client.test.js.map +1 -1
  120. package/dist/src/tools/mcp-tool.test.js +17 -17
  121. package/dist/src/tools/mcp-tool.test.js.map +1 -1
  122. package/dist/src/tools/memoryTool.d.ts +1 -1
  123. package/dist/src/tools/memoryTool.js +1 -6
  124. package/dist/src/tools/memoryTool.js.map +1 -1
  125. package/dist/src/tools/read-file.d.ts +1 -1
  126. package/dist/src/tools/read-file.js +5 -7
  127. package/dist/src/tools/read-file.js.map +1 -1
  128. package/dist/src/tools/read-file.test.js +8 -0
  129. package/dist/src/tools/read-file.test.js.map +1 -1
  130. package/dist/src/tools/read-many-files.d.ts +0 -1
  131. package/dist/src/tools/read-many-files.js +16 -11
  132. package/dist/src/tools/read-many-files.js.map +1 -1
  133. package/dist/src/tools/read-many-files.test.js +34 -0
  134. package/dist/src/tools/read-many-files.test.js.map +1 -1
  135. package/dist/src/tools/shell.d.ts +1 -1
  136. package/dist/src/tools/shell.js +10 -24
  137. package/dist/src/tools/shell.js.map +1 -1
  138. package/dist/src/tools/shell.test.js +11 -19
  139. package/dist/src/tools/shell.test.js.map +1 -1
  140. package/dist/src/tools/tool-registry.d.ts +5 -0
  141. package/dist/src/tools/tool-registry.js +13 -4
  142. package/dist/src/tools/tool-registry.js.map +1 -1
  143. package/dist/src/tools/tool-registry.test.js +9 -21
  144. package/dist/src/tools/tool-registry.test.js.map +1 -1
  145. package/dist/src/tools/tools.d.ts +3 -1
  146. package/dist/src/tools/tools.js +12 -0
  147. package/dist/src/tools/tools.js.map +1 -1
  148. package/dist/src/tools/web-fetch.d.ts +1 -1
  149. package/dist/src/tools/web-fetch.js +1 -6
  150. package/dist/src/tools/web-fetch.js.map +1 -1
  151. package/dist/src/tools/web-search.d.ts +1 -1
  152. package/dist/src/tools/web-search.js +1 -6
  153. package/dist/src/tools/web-search.js.map +1 -1
  154. package/dist/src/tools/write-file.d.ts +1 -1
  155. package/dist/src/tools/write-file.js +7 -8
  156. package/dist/src/tools/write-file.js.map +1 -1
  157. package/dist/src/tools/write-file.test.js +25 -38
  158. package/dist/src/tools/write-file.test.js.map +1 -1
  159. package/dist/src/utils/environmentContext.js +2 -2
  160. package/dist/src/utils/environmentContext.js.map +1 -1
  161. package/dist/src/utils/environmentContext.test.js +3 -2
  162. package/dist/src/utils/environmentContext.test.js.map +1 -1
  163. package/dist/src/utils/fileUtils.d.ts +2 -1
  164. package/dist/src/utils/fileUtils.js +3 -3
  165. package/dist/src/utils/fileUtils.js.map +1 -1
  166. package/dist/src/utils/fileUtils.test.js +18 -17
  167. package/dist/src/utils/fileUtils.test.js.map +1 -1
  168. package/dist/src/utils/getPty.d.ts +19 -0
  169. package/dist/src/utils/getPty.js +23 -0
  170. package/dist/src/utils/getPty.js.map +1 -0
  171. package/dist/src/utils/user_account.js +58 -48
  172. package/dist/src/utils/user_account.js.map +1 -1
  173. package/dist/src/utils/user_account.test.js +76 -9
  174. package/dist/src/utils/user_account.test.js.map +1 -1
  175. package/dist/tsconfig.tsbuildinfo +1 -1
  176. package/package.json +20 -10
@@ -0,0 +1,318 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ import { getProjectHash } from '../utils/paths.js';
7
+ import path from 'node:path';
8
+ import fs from 'node:fs';
9
+ import { randomUUID } from 'node:crypto';
10
+ /**
11
+ * Service for automatically recording chat conversations to disk.
12
+ *
13
+ * This service provides comprehensive conversation recording that captures:
14
+ * - All user and assistant messages
15
+ * - Tool calls and their execution results
16
+ * - Token usage statistics
17
+ * - Assistant thoughts and reasoning
18
+ *
19
+ * Sessions are stored as JSON files in ~/.gemini/tmp/<project_hash>/chats/
20
+ */
21
+ export class ChatRecordingService {
22
+ conversationFile = null;
23
+ cachedLastConvData = null;
24
+ sessionId;
25
+ projectHash;
26
+ queuedThoughts = [];
27
+ queuedTokens = null;
28
+ config;
29
+ constructor(config) {
30
+ this.config = config;
31
+ this.sessionId = config.getSessionId();
32
+ this.projectHash = getProjectHash(config.getProjectRoot());
33
+ }
34
+ /**
35
+ * Initializes the chat recording service: creates a new conversation file and associates it with
36
+ * this service instance, or resumes from an existing session if resumedSessionData is provided.
37
+ */
38
+ initialize(resumedSessionData) {
39
+ try {
40
+ if (resumedSessionData) {
41
+ // Resume from existing session
42
+ this.conversationFile = resumedSessionData.filePath;
43
+ this.sessionId = resumedSessionData.conversation.sessionId;
44
+ // Update the session ID in the existing file
45
+ this.updateConversation((conversation) => {
46
+ conversation.sessionId = this.sessionId;
47
+ });
48
+ // Clear any cached data to force fresh reads
49
+ this.cachedLastConvData = null;
50
+ }
51
+ else {
52
+ // Create new session
53
+ const chatsDir = path.join(this.config.getProjectTempDir(), 'chats');
54
+ fs.mkdirSync(chatsDir, { recursive: true });
55
+ const timestamp = new Date()
56
+ .toISOString()
57
+ .slice(0, 16)
58
+ .replace(/:/g, '-');
59
+ const filename = `session-${timestamp}-${this.sessionId.slice(0, 8)}.json`;
60
+ this.conversationFile = path.join(chatsDir, filename);
61
+ this.writeConversation({
62
+ sessionId: this.sessionId,
63
+ projectHash: this.projectHash,
64
+ startTime: new Date().toISOString(),
65
+ lastUpdated: new Date().toISOString(),
66
+ messages: [],
67
+ });
68
+ }
69
+ // Clear any queued data since this is a fresh start
70
+ this.queuedThoughts = [];
71
+ this.queuedTokens = null;
72
+ }
73
+ catch (error) {
74
+ console.error('Error initializing chat recording service:', error);
75
+ throw error;
76
+ }
77
+ }
78
+ getLastMessage(conversation) {
79
+ return conversation.messages.at(-1);
80
+ }
81
+ newMessage(type, content) {
82
+ return {
83
+ id: randomUUID(),
84
+ timestamp: new Date().toISOString(),
85
+ type,
86
+ content,
87
+ };
88
+ }
89
+ /**
90
+ * Records a message in the conversation.
91
+ */
92
+ recordMessage(message) {
93
+ if (!this.conversationFile)
94
+ return;
95
+ try {
96
+ this.updateConversation((conversation) => {
97
+ if (message.append) {
98
+ const lastMsg = this.getLastMessage(conversation);
99
+ if (lastMsg && lastMsg.type === message.type) {
100
+ lastMsg.content += message.content;
101
+ return;
102
+ }
103
+ }
104
+ // We're not appending, or we are appending but the last message's type is not the same as
105
+ // the specified type, so just create a new message.
106
+ const msg = this.newMessage(message.type, message.content);
107
+ if (msg.type === 'gemini') {
108
+ // If it's a new Gemini message then incorporate any queued thoughts.
109
+ conversation.messages.push({
110
+ ...msg,
111
+ thoughts: this.queuedThoughts,
112
+ tokens: this.queuedTokens,
113
+ model: this.config.getModel(),
114
+ });
115
+ this.queuedThoughts = [];
116
+ this.queuedTokens = null;
117
+ }
118
+ else {
119
+ // Or else just add it.
120
+ conversation.messages.push(msg);
121
+ }
122
+ });
123
+ }
124
+ catch (error) {
125
+ console.error('Error saving message:', error);
126
+ throw error;
127
+ }
128
+ }
129
+ /**
130
+ * Records a thought from the assistant's reasoning process.
131
+ */
132
+ recordThought(thought) {
133
+ if (!this.conversationFile)
134
+ return;
135
+ try {
136
+ this.queuedThoughts.push({
137
+ ...thought,
138
+ timestamp: new Date().toISOString(),
139
+ });
140
+ }
141
+ catch (error) {
142
+ if (this.config.getDebugMode()) {
143
+ console.error('Error saving thought:', error);
144
+ throw error;
145
+ }
146
+ }
147
+ }
148
+ /**
149
+ * Updates the tokens for the last message in the conversation (which should be by Gemini).
150
+ */
151
+ recordMessageTokens(tokens) {
152
+ if (!this.conversationFile)
153
+ return;
154
+ try {
155
+ this.updateConversation((conversation) => {
156
+ const lastMsg = this.getLastMessage(conversation);
157
+ // If the last message already has token info, it's because this new token info is for a
158
+ // new message that hasn't been recorded yet.
159
+ if (lastMsg && lastMsg.type === 'gemini' && !lastMsg.tokens) {
160
+ lastMsg.tokens = tokens;
161
+ this.queuedTokens = null;
162
+ }
163
+ else {
164
+ this.queuedTokens = tokens;
165
+ }
166
+ });
167
+ }
168
+ catch (error) {
169
+ console.error('Error updating message tokens:', error);
170
+ throw error;
171
+ }
172
+ }
173
+ /**
174
+ * Adds tool calls to the last message in the conversation (which should be by Gemini).
175
+ */
176
+ recordToolCalls(toolCalls) {
177
+ if (!this.conversationFile)
178
+ return;
179
+ try {
180
+ this.updateConversation((conversation) => {
181
+ const lastMsg = this.getLastMessage(conversation);
182
+ // If a tool call was made, but the last message isn't from Gemini, it's because Gemini is
183
+ // calling tools without starting the message with text. So the user submits a prompt, and
184
+ // Gemini immediately calls a tool (maybe with some thinking first). In that case, create
185
+ // a new empty Gemini message.
186
+ // Also if there are any queued thoughts, it means this tool call(s) is from a new Gemini
187
+ // message--because it's thought some more since we last, if ever, created a new Gemini
188
+ // message from tool calls, when we dequeued the thoughts.
189
+ if (!lastMsg ||
190
+ lastMsg.type !== 'gemini' ||
191
+ this.queuedThoughts.length > 0) {
192
+ const newMsg = {
193
+ ...this.newMessage('gemini', ''),
194
+ // This isn't strictly necessary, but TypeScript apparently can't
195
+ // tell that the first parameter to newMessage() becomes the
196
+ // resulting message's type, and so it thinks that toolCalls may
197
+ // not be present. Confirming the type here satisfies it.
198
+ type: 'gemini',
199
+ toolCalls,
200
+ thoughts: this.queuedThoughts,
201
+ model: this.config.getModel(),
202
+ };
203
+ // If there are any queued thoughts join them to this message.
204
+ if (this.queuedThoughts.length > 0) {
205
+ newMsg.thoughts = this.queuedThoughts;
206
+ this.queuedThoughts = [];
207
+ }
208
+ // If there's any queued tokens info join it to this message.
209
+ if (this.queuedTokens) {
210
+ newMsg.tokens = this.queuedTokens;
211
+ this.queuedTokens = null;
212
+ }
213
+ conversation.messages.push(newMsg);
214
+ }
215
+ else {
216
+ // The last message is an existing Gemini message that we need to update.
217
+ // Update any existing tool call entries.
218
+ if (!lastMsg.toolCalls) {
219
+ lastMsg.toolCalls = [];
220
+ }
221
+ lastMsg.toolCalls = lastMsg.toolCalls.map((toolCall) => {
222
+ // If there are multiple tool calls with the same ID, this will take the first one.
223
+ const incomingToolCall = toolCalls.find((tc) => tc.id === toolCall.id);
224
+ if (incomingToolCall) {
225
+ // Merge in the new data to keep preserve thoughts, etc., that were assigned to older
226
+ // versions of the tool call.
227
+ return { ...toolCall, ...incomingToolCall };
228
+ }
229
+ else {
230
+ return toolCall;
231
+ }
232
+ });
233
+ // Add any new tools calls that aren't in the message yet.
234
+ for (const toolCall of toolCalls) {
235
+ const existingToolCall = lastMsg.toolCalls.find((tc) => tc.id === toolCall.id);
236
+ if (!existingToolCall) {
237
+ lastMsg.toolCalls.push(toolCall);
238
+ }
239
+ }
240
+ }
241
+ });
242
+ }
243
+ catch (error) {
244
+ console.error('Error adding tool call to message:', error);
245
+ throw error;
246
+ }
247
+ }
248
+ /**
249
+ * Loads up the conversation record from disk.
250
+ */
251
+ readConversation() {
252
+ try {
253
+ this.cachedLastConvData = fs.readFileSync(this.conversationFile, 'utf8');
254
+ return JSON.parse(this.cachedLastConvData);
255
+ }
256
+ catch (error) {
257
+ if (error.code !== 'ENOENT') {
258
+ console.error('Error reading conversation file:', error);
259
+ throw error;
260
+ }
261
+ // Placeholder empty conversation if file doesn't exist.
262
+ return {
263
+ sessionId: this.sessionId,
264
+ projectHash: this.projectHash,
265
+ startTime: new Date().toISOString(),
266
+ lastUpdated: new Date().toISOString(),
267
+ messages: [],
268
+ };
269
+ }
270
+ }
271
+ /**
272
+ * Saves the conversation record; overwrites the file.
273
+ */
274
+ writeConversation(conversation) {
275
+ try {
276
+ if (!this.conversationFile)
277
+ return;
278
+ // Don't write the file yet until there's at least one message.
279
+ if (conversation.messages.length === 0)
280
+ return;
281
+ // Only write the file if this change would change the file.
282
+ if (this.cachedLastConvData !== JSON.stringify(conversation, null, 2)) {
283
+ conversation.lastUpdated = new Date().toISOString();
284
+ const newContent = JSON.stringify(conversation, null, 2);
285
+ this.cachedLastConvData = newContent;
286
+ fs.writeFileSync(this.conversationFile, newContent);
287
+ }
288
+ }
289
+ catch (error) {
290
+ console.error('Error writing conversation file:', error);
291
+ throw error;
292
+ }
293
+ }
294
+ /**
295
+ * Convenient helper for updating the conversation without file reading and writing and time
296
+ * updating boilerplate.
297
+ */
298
+ updateConversation(updateFn) {
299
+ const conversation = this.readConversation();
300
+ updateFn(conversation);
301
+ this.writeConversation(conversation);
302
+ }
303
+ /**
304
+ * Deletes a session file by session ID.
305
+ */
306
+ deleteSession(sessionId) {
307
+ try {
308
+ const chatsDir = path.join(this.config.getProjectTempDir(), 'chats');
309
+ const sessionPath = path.join(chatsDir, `${sessionId}.json`);
310
+ fs.unlinkSync(sessionPath);
311
+ }
312
+ catch (error) {
313
+ console.error('Error deleting session:', error);
314
+ throw error;
315
+ }
316
+ }
317
+ }
318
+ //# sourceMappingURL=chatRecordingService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chatRecordingService.js","sourceRoot":"","sources":["../../../src/services/chatRecordingService.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ export {};
@@ -0,0 +1,288 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ import { expect, it, describe, vi, beforeEach, afterEach, } from 'vitest';
7
+ import fs from 'node:fs';
8
+ import path from 'node:path';
9
+ import { randomUUID } from 'node:crypto';
10
+ import { ChatRecordingService, } from './chatRecordingService.js';
11
+ import { getProjectHash } from '../utils/paths.js';
12
+ vi.mock('node:fs');
13
+ vi.mock('node:path');
14
+ vi.mock('node:crypto');
15
+ vi.mock('../utils/paths.js');
16
+ describe('ChatRecordingService', () => {
17
+ let chatRecordingService;
18
+ let mockConfig;
19
+ let mkdirSyncSpy;
20
+ let writeFileSyncSpy;
21
+ beforeEach(() => {
22
+ mockConfig = {
23
+ getSessionId: vi.fn().mockReturnValue('test-session-id'),
24
+ getProjectRoot: vi.fn().mockReturnValue('/test/project/root'),
25
+ getProjectTempDir: vi
26
+ .fn()
27
+ .mockReturnValue('/test/project/root/.gemini/tmp'),
28
+ getModel: vi.fn().mockReturnValue('gemini-pro'),
29
+ getDebugMode: vi.fn().mockReturnValue(false),
30
+ };
31
+ vi.mocked(getProjectHash).mockReturnValue('test-project-hash');
32
+ vi.mocked(randomUUID).mockReturnValue('this-is-a-test-uuid');
33
+ vi.mocked(path.join).mockImplementation((...args) => args.join('/'));
34
+ chatRecordingService = new ChatRecordingService(mockConfig);
35
+ mkdirSyncSpy = vi
36
+ .spyOn(fs, 'mkdirSync')
37
+ .mockImplementation(() => undefined);
38
+ writeFileSyncSpy = vi
39
+ .spyOn(fs, 'writeFileSync')
40
+ .mockImplementation(() => undefined);
41
+ });
42
+ afterEach(() => {
43
+ vi.restoreAllMocks();
44
+ });
45
+ describe('initialize', () => {
46
+ it('should create a new session if none is provided', () => {
47
+ chatRecordingService.initialize();
48
+ expect(mkdirSyncSpy).toHaveBeenCalledWith('/test/project/root/.gemini/tmp/chats', { recursive: true });
49
+ expect(writeFileSyncSpy).not.toHaveBeenCalled();
50
+ });
51
+ it('should resume from an existing session if provided', () => {
52
+ const readFileSyncSpy = vi.spyOn(fs, 'readFileSync').mockReturnValue(JSON.stringify({
53
+ sessionId: 'old-session-id',
54
+ projectHash: 'test-project-hash',
55
+ messages: [],
56
+ }));
57
+ const writeFileSyncSpy = vi
58
+ .spyOn(fs, 'writeFileSync')
59
+ .mockImplementation(() => undefined);
60
+ chatRecordingService.initialize({
61
+ filePath: '/test/project/root/.gemini/tmp/chats/session.json',
62
+ conversation: {
63
+ sessionId: 'old-session-id',
64
+ },
65
+ });
66
+ expect(mkdirSyncSpy).not.toHaveBeenCalled();
67
+ expect(readFileSyncSpy).toHaveBeenCalled();
68
+ expect(writeFileSyncSpy).not.toHaveBeenCalled();
69
+ });
70
+ });
71
+ describe('recordMessage', () => {
72
+ beforeEach(() => {
73
+ chatRecordingService.initialize();
74
+ vi.spyOn(fs, 'readFileSync').mockReturnValue(JSON.stringify({
75
+ sessionId: 'test-session-id',
76
+ projectHash: 'test-project-hash',
77
+ messages: [],
78
+ }));
79
+ });
80
+ it('should record a new message', () => {
81
+ const writeFileSyncSpy = vi
82
+ .spyOn(fs, 'writeFileSync')
83
+ .mockImplementation(() => undefined);
84
+ chatRecordingService.recordMessage({ type: 'user', content: 'Hello' });
85
+ expect(mkdirSyncSpy).toHaveBeenCalled();
86
+ expect(writeFileSyncSpy).toHaveBeenCalled();
87
+ const conversation = JSON.parse(writeFileSyncSpy.mock.calls[0][1]);
88
+ expect(conversation.messages).toHaveLength(1);
89
+ expect(conversation.messages[0].content).toBe('Hello');
90
+ expect(conversation.messages[0].type).toBe('user');
91
+ });
92
+ it('should append to the last message if append is true and types match', () => {
93
+ const writeFileSyncSpy = vi
94
+ .spyOn(fs, 'writeFileSync')
95
+ .mockImplementation(() => undefined);
96
+ const initialConversation = {
97
+ sessionId: 'test-session-id',
98
+ projectHash: 'test-project-hash',
99
+ messages: [
100
+ {
101
+ id: '1',
102
+ type: 'user',
103
+ content: 'Hello',
104
+ timestamp: new Date().toISOString(),
105
+ },
106
+ ],
107
+ };
108
+ vi.spyOn(fs, 'readFileSync').mockReturnValue(JSON.stringify(initialConversation));
109
+ chatRecordingService.recordMessage({
110
+ type: 'user',
111
+ content: ' World',
112
+ append: true,
113
+ });
114
+ expect(mkdirSyncSpy).toHaveBeenCalled();
115
+ expect(writeFileSyncSpy).toHaveBeenCalled();
116
+ const conversation = JSON.parse(writeFileSyncSpy.mock.calls[0][1]);
117
+ expect(conversation.messages).toHaveLength(1);
118
+ expect(conversation.messages[0].content).toBe('Hello World');
119
+ });
120
+ });
121
+ describe('recordThought', () => {
122
+ it('should queue a thought', () => {
123
+ chatRecordingService.initialize();
124
+ chatRecordingService.recordThought({
125
+ subject: 'Thinking',
126
+ description: 'Thinking...',
127
+ });
128
+ // @ts-expect-error private property
129
+ expect(chatRecordingService.queuedThoughts).toHaveLength(1);
130
+ // @ts-expect-error private property
131
+ expect(chatRecordingService.queuedThoughts[0].subject).toBe('Thinking');
132
+ // @ts-expect-error private property
133
+ expect(chatRecordingService.queuedThoughts[0].description).toBe('Thinking...');
134
+ });
135
+ });
136
+ describe('recordMessageTokens', () => {
137
+ beforeEach(() => {
138
+ chatRecordingService.initialize();
139
+ });
140
+ it('should update the last message with token info', () => {
141
+ const writeFileSyncSpy = vi
142
+ .spyOn(fs, 'writeFileSync')
143
+ .mockImplementation(() => undefined);
144
+ const initialConversation = {
145
+ sessionId: 'test-session-id',
146
+ projectHash: 'test-project-hash',
147
+ messages: [
148
+ {
149
+ id: '1',
150
+ type: 'gemini',
151
+ content: 'Response',
152
+ timestamp: new Date().toISOString(),
153
+ },
154
+ ],
155
+ };
156
+ vi.spyOn(fs, 'readFileSync').mockReturnValue(JSON.stringify(initialConversation));
157
+ chatRecordingService.recordMessageTokens({
158
+ input: 1,
159
+ output: 2,
160
+ total: 3,
161
+ cached: 0,
162
+ });
163
+ expect(mkdirSyncSpy).toHaveBeenCalled();
164
+ expect(writeFileSyncSpy).toHaveBeenCalled();
165
+ const conversation = JSON.parse(writeFileSyncSpy.mock.calls[0][1]);
166
+ expect(conversation.messages[0]).toEqual({
167
+ ...initialConversation.messages[0],
168
+ tokens: { input: 1, output: 2, total: 3, cached: 0 },
169
+ });
170
+ });
171
+ it('should queue token info if the last message already has tokens', () => {
172
+ const initialConversation = {
173
+ sessionId: 'test-session-id',
174
+ projectHash: 'test-project-hash',
175
+ messages: [
176
+ {
177
+ id: '1',
178
+ type: 'gemini',
179
+ content: 'Response',
180
+ timestamp: new Date().toISOString(),
181
+ tokens: { input: 1, output: 1, total: 2, cached: 0 },
182
+ },
183
+ ],
184
+ };
185
+ vi.spyOn(fs, 'readFileSync').mockReturnValue(JSON.stringify(initialConversation));
186
+ chatRecordingService.recordMessageTokens({
187
+ input: 2,
188
+ output: 2,
189
+ total: 4,
190
+ cached: 0,
191
+ });
192
+ // @ts-expect-error private property
193
+ expect(chatRecordingService.queuedTokens).toEqual({
194
+ input: 2,
195
+ output: 2,
196
+ total: 4,
197
+ cached: 0,
198
+ });
199
+ });
200
+ });
201
+ describe('recordToolCalls', () => {
202
+ beforeEach(() => {
203
+ chatRecordingService.initialize();
204
+ });
205
+ it('should add new tool calls to the last message', () => {
206
+ const writeFileSyncSpy = vi
207
+ .spyOn(fs, 'writeFileSync')
208
+ .mockImplementation(() => undefined);
209
+ const initialConversation = {
210
+ sessionId: 'test-session-id',
211
+ projectHash: 'test-project-hash',
212
+ messages: [
213
+ {
214
+ id: '1',
215
+ type: 'gemini',
216
+ content: '',
217
+ timestamp: new Date().toISOString(),
218
+ },
219
+ ],
220
+ };
221
+ vi.spyOn(fs, 'readFileSync').mockReturnValue(JSON.stringify(initialConversation));
222
+ const toolCall = {
223
+ id: 'tool-1',
224
+ name: 'testTool',
225
+ args: {},
226
+ status: 'awaiting_approval',
227
+ timestamp: new Date().toISOString(),
228
+ };
229
+ chatRecordingService.recordToolCalls([toolCall]);
230
+ expect(mkdirSyncSpy).toHaveBeenCalled();
231
+ expect(writeFileSyncSpy).toHaveBeenCalled();
232
+ const conversation = JSON.parse(writeFileSyncSpy.mock.calls[0][1]);
233
+ expect(conversation.messages[0]).toEqual({
234
+ ...initialConversation.messages[0],
235
+ toolCalls: [toolCall],
236
+ });
237
+ });
238
+ it('should create a new message if the last message is not from gemini', () => {
239
+ const writeFileSyncSpy = vi
240
+ .spyOn(fs, 'writeFileSync')
241
+ .mockImplementation(() => undefined);
242
+ const initialConversation = {
243
+ sessionId: 'test-session-id',
244
+ projectHash: 'test-project-hash',
245
+ messages: [
246
+ {
247
+ id: 'a-uuid',
248
+ type: 'user',
249
+ content: 'call a tool',
250
+ timestamp: new Date().toISOString(),
251
+ },
252
+ ],
253
+ };
254
+ vi.spyOn(fs, 'readFileSync').mockReturnValue(JSON.stringify(initialConversation));
255
+ const toolCall = {
256
+ id: 'tool-1',
257
+ name: 'testTool',
258
+ args: {},
259
+ status: 'awaiting_approval',
260
+ timestamp: new Date().toISOString(),
261
+ };
262
+ chatRecordingService.recordToolCalls([toolCall]);
263
+ expect(mkdirSyncSpy).toHaveBeenCalled();
264
+ expect(writeFileSyncSpy).toHaveBeenCalled();
265
+ const conversation = JSON.parse(writeFileSyncSpy.mock.calls[0][1]);
266
+ expect(conversation.messages).toHaveLength(2);
267
+ expect(conversation.messages[1]).toEqual({
268
+ ...conversation.messages[1],
269
+ id: 'this-is-a-test-uuid',
270
+ model: 'gemini-pro',
271
+ type: 'gemini',
272
+ thoughts: [],
273
+ content: '',
274
+ toolCalls: [toolCall],
275
+ });
276
+ });
277
+ });
278
+ describe('deleteSession', () => {
279
+ it('should delete the session file', () => {
280
+ const unlinkSyncSpy = vi
281
+ .spyOn(fs, 'unlinkSync')
282
+ .mockImplementation(() => undefined);
283
+ chatRecordingService.deleteSession('test-session-id');
284
+ expect(unlinkSyncSpy).toHaveBeenCalledWith('/test/project/root/.gemini/tmp/chats/test-session-id.json');
285
+ });
286
+ });
287
+ });
288
+ //# sourceMappingURL=chatRecordingService.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chatRecordingService.test.js","sourceRoot":"","sources":["../../../src/services/chatRecordingService.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,MAAM,EACN,EAAE,EACF,QAAQ,EACR,EAAE,EACF,UAAU,EACV,SAAS,GAEV,MAAM,QAAQ,CAAC;AAChB,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EACL,oBAAoB,GAGrB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEnD,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;AACnB,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;AACrB,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;AACvB,EAAE,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;AAE7B,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;IACpC,IAAI,oBAA0C,CAAC;IAC/C,IAAI,UAAkB,CAAC;IAEvB,IAAI,YAA+C,CAAC;IACpD,IAAI,gBAAuD,CAAC;IAE5D,UAAU,CAAC,GAAG,EAAE;QACd,UAAU,GAAG;YACX,YAAY,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,iBAAiB,CAAC;YACxD,cAAc,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,oBAAoB,CAAC;YAC7D,iBAAiB,EAAE,EAAE;iBAClB,EAAE,EAAE;iBACJ,eAAe,CAAC,gCAAgC,CAAC;YACpD,QAAQ,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,YAAY,CAAC;YAC/C,YAAY,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,KAAK,CAAC;SACxB,CAAC;QAEvB,EAAE,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,eAAe,CAAC,mBAAmB,CAAC,CAAC;QAC/D,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,eAAe,CAAC,qBAAqB,CAAC,CAAC;QAC7D,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,kBAAkB,CAAC,CAAC,GAAG,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QAErE,oBAAoB,GAAG,IAAI,oBAAoB,CAAC,UAAU,CAAC,CAAC;QAE5D,YAAY,GAAG,EAAE;aACd,KAAK,CAAC,EAAE,EAAE,WAAW,CAAC;aACtB,kBAAkB,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAEvC,gBAAgB,GAAG,EAAE;aAClB,KAAK,CAAC,EAAE,EAAE,eAAe,CAAC;aAC1B,kBAAkB,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,GAAG,EAAE;QACb,EAAE,CAAC,eAAe,EAAE,CAAC;IACvB,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;QAC1B,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;YACzD,oBAAoB,CAAC,UAAU,EAAE,CAAC;YAElC,MAAM,CAAC,YAAY,CAAC,CAAC,oBAAoB,CACvC,sCAAsC,EACtC,EAAE,SAAS,EAAE,IAAI,EAAE,CACpB,CAAC;YACF,MAAM,CAAC,gBAAgB,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;QAClD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;YAC5D,MAAM,eAAe,GAAG,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,cAAc,CAAC,CAAC,eAAe,CAClE,IAAI,CAAC,SAAS,CAAC;gBACb,SAAS,EAAE,gBAAgB;gBAC3B,WAAW,EAAE,mBAAmB;gBAChC,QAAQ,EAAE,EAAE;aACb,CAAC,CACH,CAAC;YACF,MAAM,gBAAgB,GAAG,EAAE;iBACxB,KAAK,CAAC,EAAE,EAAE,eAAe,CAAC;iBAC1B,kBAAkB,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;YAEvC,oBAAoB,CAAC,UAAU,CAAC;gBAC9B,QAAQ,EAAE,mDAAmD;gBAC7D,YAAY,EAAE;oBACZ,SAAS,EAAE,gBAAgB;iBACN;aACxB,CAAC,CAAC;YAEH,MAAM,CAAC,YAAY,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;YAC5C,MAAM,CAAC,eAAe,CAAC,CAAC,gBAAgB,EAAE,CAAC;YAC3C,MAAM,CAAC,gBAAgB,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;QAClD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;QAC7B,UAAU,CAAC,GAAG,EAAE;YACd,oBAAoB,CAAC,UAAU,EAAE,CAAC;YAClC,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,cAAc,CAAC,CAAC,eAAe,CAC1C,IAAI,CAAC,SAAS,CAAC;gBACb,SAAS,EAAE,iBAAiB;gBAC5B,WAAW,EAAE,mBAAmB;gBAChC,QAAQ,EAAE,EAAE;aACb,CAAC,CACH,CAAC;QACJ,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,6BAA6B,EAAE,GAAG,EAAE;YACrC,MAAM,gBAAgB,GAAG,EAAE;iBACxB,KAAK,CAAC,EAAE,EAAE,eAAe,CAAC;iBAC1B,kBAAkB,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;YACvC,oBAAoB,CAAC,aAAa,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;YACvE,MAAM,CAAC,YAAY,CAAC,CAAC,gBAAgB,EAAE,CAAC;YACxC,MAAM,CAAC,gBAAgB,CAAC,CAAC,gBAAgB,EAAE,CAAC;YAC5C,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAC7B,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAW,CACtB,CAAC;YACxB,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YAC9C,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACvD,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACrD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,qEAAqE,EAAE,GAAG,EAAE;YAC7E,MAAM,gBAAgB,GAAG,EAAE;iBACxB,KAAK,CAAC,EAAE,EAAE,eAAe,CAAC;iBAC1B,kBAAkB,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;YACvC,MAAM,mBAAmB,GAAG;gBAC1B,SAAS,EAAE,iBAAiB;gBAC5B,WAAW,EAAE,mBAAmB;gBAChC,QAAQ,EAAE;oBACR;wBACE,EAAE,EAAE,GAAG;wBACP,IAAI,EAAE,MAAM;wBACZ,OAAO,EAAE,OAAO;wBAChB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;qBACpC;iBACF;aACF,CAAC;YACF,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,cAAc,CAAC,CAAC,eAAe,CAC1C,IAAI,CAAC,SAAS,CAAC,mBAAmB,CAAC,CACpC,CAAC;YAEF,oBAAoB,CAAC,aAAa,CAAC;gBACjC,IAAI,EAAE,MAAM;gBACZ,OAAO,EAAE,QAAQ;gBACjB,MAAM,EAAE,IAAI;aACb,CAAC,CAAC;YAEH,MAAM,CAAC,YAAY,CAAC,CAAC,gBAAgB,EAAE,CAAC;YACxC,MAAM,CAAC,gBAAgB,CAAC,CAAC,gBAAgB,EAAE,CAAC;YAC5C,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAC7B,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAW,CACtB,CAAC;YACxB,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YAC9C,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAC/D,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;QAC7B,EAAE,CAAC,wBAAwB,EAAE,GAAG,EAAE;YAChC,oBAAoB,CAAC,UAAU,EAAE,CAAC;YAClC,oBAAoB,CAAC,aAAa,CAAC;gBACjC,OAAO,EAAE,UAAU;gBACnB,WAAW,EAAE,aAAa;aAC3B,CAAC,CAAC;YACH,oCAAoC;YACpC,MAAM,CAAC,oBAAoB,CAAC,cAAc,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YAC5D,oCAAoC;YACpC,MAAM,CAAC,oBAAoB,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YACxE,oCAAoC;YACpC,MAAM,CAAC,oBAAoB,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,IAAI,CAC7D,aAAa,CACd,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;QACnC,UAAU,CAAC,GAAG,EAAE;YACd,oBAAoB,CAAC,UAAU,EAAE,CAAC;QACpC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;YACxD,MAAM,gBAAgB,GAAG,EAAE;iBACxB,KAAK,CAAC,EAAE,EAAE,eAAe,CAAC;iBAC1B,kBAAkB,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;YACvC,MAAM,mBAAmB,GAAG;gBAC1B,SAAS,EAAE,iBAAiB;gBAC5B,WAAW,EAAE,mBAAmB;gBAChC,QAAQ,EAAE;oBACR;wBACE,EAAE,EAAE,GAAG;wBACP,IAAI,EAAE,QAAQ;wBACd,OAAO,EAAE,UAAU;wBACnB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;qBACpC;iBACF;aACF,CAAC;YACF,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,cAAc,CAAC,CAAC,eAAe,CAC1C,IAAI,CAAC,SAAS,CAAC,mBAAmB,CAAC,CACpC,CAAC;YAEF,oBAAoB,CAAC,mBAAmB,CAAC;gBACvC,KAAK,EAAE,CAAC;gBACR,MAAM,EAAE,CAAC;gBACT,KAAK,EAAE,CAAC;gBACR,MAAM,EAAE,CAAC;aACV,CAAC,CAAC;YAEH,MAAM,CAAC,YAAY,CAAC,CAAC,gBAAgB,EAAE,CAAC;YACxC,MAAM,CAAC,gBAAgB,CAAC,CAAC,gBAAgB,EAAE,CAAC;YAC5C,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAC7B,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAW,CACtB,CAAC;YACxB,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;gBACvC,GAAG,mBAAmB,CAAC,QAAQ,CAAC,CAAC,CAAC;gBAClC,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE;aACrD,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,gEAAgE,EAAE,GAAG,EAAE;YACxE,MAAM,mBAAmB,GAAG;gBAC1B,SAAS,EAAE,iBAAiB;gBAC5B,WAAW,EAAE,mBAAmB;gBAChC,QAAQ,EAAE;oBACR;wBACE,EAAE,EAAE,GAAG;wBACP,IAAI,EAAE,QAAQ;wBACd,OAAO,EAAE,UAAU;wBACnB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;wBACnC,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE;qBACrD;iBACF;aACF,CAAC;YACF,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,cAAc,CAAC,CAAC,eAAe,CAC1C,IAAI,CAAC,SAAS,CAAC,mBAAmB,CAAC,CACpC,CAAC;YAEF,oBAAoB,CAAC,mBAAmB,CAAC;gBACvC,KAAK,EAAE,CAAC;gBACR,MAAM,EAAE,CAAC;gBACT,KAAK,EAAE,CAAC;gBACR,MAAM,EAAE,CAAC;aACV,CAAC,CAAC;YAEH,oCAAoC;YACpC,MAAM,CAAC,oBAAoB,CAAC,YAAY,CAAC,CAAC,OAAO,CAAC;gBAChD,KAAK,EAAE,CAAC;gBACR,MAAM,EAAE,CAAC;gBACT,KAAK,EAAE,CAAC;gBACR,MAAM,EAAE,CAAC;aACV,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE;QAC/B,UAAU,CAAC,GAAG,EAAE;YACd,oBAAoB,CAAC,UAAU,EAAE,CAAC;QACpC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;YACvD,MAAM,gBAAgB,GAAG,EAAE;iBACxB,KAAK,CAAC,EAAE,EAAE,eAAe,CAAC;iBAC1B,kBAAkB,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;YACvC,MAAM,mBAAmB,GAAG;gBAC1B,SAAS,EAAE,iBAAiB;gBAC5B,WAAW,EAAE,mBAAmB;gBAChC,QAAQ,EAAE;oBACR;wBACE,EAAE,EAAE,GAAG;wBACP,IAAI,EAAE,QAAQ;wBACd,OAAO,EAAE,EAAE;wBACX,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;qBACpC;iBACF;aACF,CAAC;YACF,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,cAAc,CAAC,CAAC,eAAe,CAC1C,IAAI,CAAC,SAAS,CAAC,mBAAmB,CAAC,CACpC,CAAC;YAEF,MAAM,QAAQ,GAAmB;gBAC/B,EAAE,EAAE,QAAQ;gBACZ,IAAI,EAAE,UAAU;gBAChB,IAAI,EAAE,EAAE;gBACR,MAAM,EAAE,mBAAmB;gBAC3B,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;aACpC,CAAC;YACF,oBAAoB,CAAC,eAAe,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;YAEjD,MAAM,CAAC,YAAY,CAAC,CAAC,gBAAgB,EAAE,CAAC;YACxC,MAAM,CAAC,gBAAgB,CAAC,CAAC,gBAAgB,EAAE,CAAC;YAC5C,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAC7B,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAW,CACtB,CAAC;YACxB,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;gBACvC,GAAG,mBAAmB,CAAC,QAAQ,CAAC,CAAC,CAAC;gBAClC,SAAS,EAAE,CAAC,QAAQ,CAAC;aACtB,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,oEAAoE,EAAE,GAAG,EAAE;YAC5E,MAAM,gBAAgB,GAAG,EAAE;iBACxB,KAAK,CAAC,EAAE,EAAE,eAAe,CAAC;iBAC1B,kBAAkB,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;YACvC,MAAM,mBAAmB,GAAG;gBAC1B,SAAS,EAAE,iBAAiB;gBAC5B,WAAW,EAAE,mBAAmB;gBAChC,QAAQ,EAAE;oBACR;wBACE,EAAE,EAAE,QAAQ;wBACZ,IAAI,EAAE,MAAM;wBACZ,OAAO,EAAE,aAAa;wBACtB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;qBACpC;iBACF;aACF,CAAC;YACF,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,cAAc,CAAC,CAAC,eAAe,CAC1C,IAAI,CAAC,SAAS,CAAC,mBAAmB,CAAC,CACpC,CAAC;YAEF,MAAM,QAAQ,GAAmB;gBAC/B,EAAE,EAAE,QAAQ;gBACZ,IAAI,EAAE,UAAU;gBAChB,IAAI,EAAE,EAAE;gBACR,MAAM,EAAE,mBAAmB;gBAC3B,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;aACpC,CAAC;YACF,oBAAoB,CAAC,eAAe,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;YAEjD,MAAM,CAAC,YAAY,CAAC,CAAC,gBAAgB,EAAE,CAAC;YACxC,MAAM,CAAC,gBAAgB,CAAC,CAAC,gBAAgB,EAAE,CAAC;YAC5C,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAC7B,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAW,CACtB,CAAC;YACxB,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YAC9C,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;gBACvC,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC;gBAC3B,EAAE,EAAE,qBAAqB;gBACzB,KAAK,EAAE,YAAY;gBACnB,IAAI,EAAE,QAAQ;gBACd,QAAQ,EAAE,EAAE;gBACZ,OAAO,EAAE,EAAE;gBACX,SAAS,EAAE,CAAC,QAAQ,CAAC;aACtB,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;QAC7B,EAAE,CAAC,gCAAgC,EAAE,GAAG,EAAE;YACxC,MAAM,aAAa,GAAG,EAAE;iBACrB,KAAK,CAAC,EAAE,EAAE,YAAY,CAAC;iBACvB,kBAAkB,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;YACvC,oBAAoB,CAAC,aAAa,CAAC,iBAAiB,CAAC,CAAC;YACtD,MAAM,CAAC,aAAa,CAAC,CAAC,oBAAoB,CACxC,2DAA2D,CAC5D,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}