snow-ai 0.1.12 → 0.2.1

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 (97) hide show
  1. package/dist/api/chat.d.ts +65 -2
  2. package/dist/api/chat.js +299 -16
  3. package/dist/api/responses.d.ts +52 -0
  4. package/dist/api/responses.js +541 -0
  5. package/dist/api/systemPrompt.d.ts +4 -0
  6. package/dist/api/systemPrompt.js +43 -0
  7. package/dist/app.js +15 -4
  8. package/dist/cli.js +17 -1
  9. package/dist/hooks/useConversation.d.ts +32 -0
  10. package/dist/hooks/useConversation.js +403 -0
  11. package/dist/hooks/useGlobalNavigation.d.ts +6 -0
  12. package/dist/hooks/useGlobalNavigation.js +15 -0
  13. package/dist/hooks/useSessionManagement.d.ts +10 -0
  14. package/dist/hooks/useSessionManagement.js +43 -0
  15. package/dist/hooks/useSessionSave.d.ts +8 -0
  16. package/dist/hooks/useSessionSave.js +52 -0
  17. package/dist/hooks/useToolConfirmation.d.ts +18 -0
  18. package/dist/hooks/useToolConfirmation.js +49 -0
  19. package/dist/mcp/bash.d.ts +57 -0
  20. package/dist/mcp/bash.js +138 -0
  21. package/dist/mcp/filesystem.d.ts +307 -0
  22. package/dist/mcp/filesystem.js +520 -0
  23. package/dist/mcp/todo.d.ts +55 -0
  24. package/dist/mcp/todo.js +329 -0
  25. package/dist/test/logger-test.d.ts +1 -0
  26. package/dist/test/logger-test.js +7 -0
  27. package/dist/types/index.d.ts +1 -1
  28. package/dist/ui/components/ChatInput.d.ts +15 -2
  29. package/dist/ui/components/ChatInput.js +445 -59
  30. package/dist/ui/components/CommandPanel.d.ts +2 -2
  31. package/dist/ui/components/CommandPanel.js +11 -7
  32. package/dist/ui/components/DiffViewer.d.ts +9 -0
  33. package/dist/ui/components/DiffViewer.js +93 -0
  34. package/dist/ui/components/FileList.d.ts +14 -0
  35. package/dist/ui/components/FileList.js +131 -0
  36. package/dist/ui/components/MCPInfoPanel.d.ts +2 -0
  37. package/dist/ui/components/MCPInfoPanel.js +74 -0
  38. package/dist/ui/components/MCPInfoScreen.d.ts +7 -0
  39. package/dist/ui/components/MCPInfoScreen.js +27 -0
  40. package/dist/ui/components/MarkdownRenderer.d.ts +7 -0
  41. package/dist/ui/components/MarkdownRenderer.js +110 -0
  42. package/dist/ui/components/Menu.d.ts +5 -2
  43. package/dist/ui/components/Menu.js +60 -9
  44. package/dist/ui/components/MessageList.d.ts +30 -2
  45. package/dist/ui/components/MessageList.js +64 -12
  46. package/dist/ui/components/PendingMessages.js +1 -1
  47. package/dist/ui/components/ScrollableSelectInput.d.ts +29 -0
  48. package/dist/ui/components/ScrollableSelectInput.js +157 -0
  49. package/dist/ui/components/SessionListScreen.d.ts +7 -0
  50. package/dist/ui/components/SessionListScreen.js +196 -0
  51. package/dist/ui/components/SessionListScreenWrapper.d.ts +7 -0
  52. package/dist/ui/components/SessionListScreenWrapper.js +14 -0
  53. package/dist/ui/components/TodoTree.d.ts +15 -0
  54. package/dist/ui/components/TodoTree.js +60 -0
  55. package/dist/ui/components/ToolConfirmation.d.ts +8 -0
  56. package/dist/ui/components/ToolConfirmation.js +38 -0
  57. package/dist/ui/components/ToolResultPreview.d.ts +12 -0
  58. package/dist/ui/components/ToolResultPreview.js +115 -0
  59. package/dist/ui/pages/ChatScreen.d.ts +4 -0
  60. package/dist/ui/pages/ChatScreen.js +385 -196
  61. package/dist/ui/pages/MCPConfigScreen.d.ts +6 -0
  62. package/dist/ui/pages/MCPConfigScreen.js +55 -0
  63. package/dist/ui/pages/ModelConfigScreen.js +73 -12
  64. package/dist/ui/pages/WelcomeScreen.js +17 -11
  65. package/dist/utils/apiConfig.d.ts +12 -0
  66. package/dist/utils/apiConfig.js +95 -9
  67. package/dist/utils/commandExecutor.d.ts +2 -1
  68. package/dist/utils/commands/init.d.ts +2 -0
  69. package/dist/utils/commands/init.js +93 -0
  70. package/dist/utils/commands/mcp.d.ts +2 -0
  71. package/dist/utils/commands/mcp.js +12 -0
  72. package/dist/utils/commands/resume.d.ts +2 -0
  73. package/dist/utils/commands/resume.js +12 -0
  74. package/dist/utils/commands/yolo.d.ts +2 -0
  75. package/dist/utils/commands/yolo.js +12 -0
  76. package/dist/utils/fileUtils.d.ts +44 -0
  77. package/dist/utils/fileUtils.js +222 -0
  78. package/dist/utils/index.d.ts +4 -0
  79. package/dist/utils/index.js +6 -0
  80. package/dist/utils/logger.d.ts +31 -0
  81. package/dist/utils/logger.js +97 -0
  82. package/dist/utils/mcpToolsManager.d.ts +47 -0
  83. package/dist/utils/mcpToolsManager.js +476 -0
  84. package/dist/utils/messageFormatter.d.ts +12 -0
  85. package/dist/utils/messageFormatter.js +32 -0
  86. package/dist/utils/sessionConverter.d.ts +6 -0
  87. package/dist/utils/sessionConverter.js +61 -0
  88. package/dist/utils/sessionManager.d.ts +39 -0
  89. package/dist/utils/sessionManager.js +141 -0
  90. package/dist/utils/textBuffer.d.ts +36 -7
  91. package/dist/utils/textBuffer.js +265 -179
  92. package/dist/utils/todoPreprocessor.d.ts +5 -0
  93. package/dist/utils/todoPreprocessor.js +19 -0
  94. package/dist/utils/toolExecutor.d.ts +21 -0
  95. package/dist/utils/toolExecutor.js +28 -0
  96. package/package.json +12 -3
  97. package/readme.md +2 -2
@@ -0,0 +1,476 @@
1
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
2
+ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
3
+ import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
4
+ import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
5
+ import { getMCPConfig } from './apiConfig.js';
6
+ import { mcpTools as filesystemTools } from '../mcp/filesystem.js';
7
+ import { mcpTools as terminalTools } from '../mcp/bash.js';
8
+ import { TodoService } from '../mcp/todo.js';
9
+ import { sessionManager } from './sessionManager.js';
10
+ import { logger } from './logger.js';
11
+ import os from 'os';
12
+ import path from 'path';
13
+ let toolsCache = null;
14
+ const CACHE_DURATION = 5 * 60 * 1000; // 5 minutes
15
+ // Initialize TODO service with sessionManager accessor
16
+ const todoService = new TodoService(path.join(os.homedir(), '.snow'), () => {
17
+ const session = sessionManager.getCurrentSession();
18
+ return session ? session.id : null;
19
+ });
20
+ /**
21
+ * Get the TODO service instance
22
+ */
23
+ export function getTodoService() {
24
+ return todoService;
25
+ }
26
+ /**
27
+ * Generate a hash of the current MCP configuration
28
+ */
29
+ function generateConfigHash() {
30
+ try {
31
+ const mcpConfig = getMCPConfig();
32
+ return JSON.stringify(mcpConfig.mcpServers);
33
+ }
34
+ catch {
35
+ return '';
36
+ }
37
+ }
38
+ /**
39
+ * Check if the cache is valid and not expired
40
+ */
41
+ function isCacheValid() {
42
+ if (!toolsCache)
43
+ return false;
44
+ const now = Date.now();
45
+ const isExpired = (now - toolsCache.lastUpdate) > CACHE_DURATION;
46
+ const configChanged = toolsCache.configHash !== generateConfigHash();
47
+ return !isExpired && !configChanged;
48
+ }
49
+ /**
50
+ * Get cached tools or build cache if needed
51
+ */
52
+ async function getCachedTools() {
53
+ if (isCacheValid()) {
54
+ return toolsCache.tools;
55
+ }
56
+ await refreshToolsCache();
57
+ return toolsCache.tools;
58
+ }
59
+ /**
60
+ * Refresh the tools cache by collecting all available tools
61
+ */
62
+ async function refreshToolsCache() {
63
+ const allTools = [];
64
+ const servicesInfo = [];
65
+ // Add built-in filesystem tools (always available)
66
+ const filesystemServiceTools = filesystemTools.map(tool => ({
67
+ name: tool.name.replace('filesystem_', ''),
68
+ description: tool.description,
69
+ inputSchema: tool.inputSchema
70
+ }));
71
+ servicesInfo.push({
72
+ serviceName: 'filesystem',
73
+ tools: filesystemServiceTools,
74
+ isBuiltIn: true,
75
+ connected: true
76
+ });
77
+ for (const tool of filesystemTools) {
78
+ allTools.push({
79
+ type: "function",
80
+ function: {
81
+ name: `filesystem-${tool.name.replace('filesystem_', '')}`,
82
+ description: tool.description,
83
+ parameters: tool.inputSchema
84
+ }
85
+ });
86
+ }
87
+ // Add built-in terminal tools (always available)
88
+ const terminalServiceTools = terminalTools.map(tool => ({
89
+ name: tool.name.replace('terminal_', ''),
90
+ description: tool.description,
91
+ inputSchema: tool.inputSchema
92
+ }));
93
+ servicesInfo.push({
94
+ serviceName: 'terminal',
95
+ tools: terminalServiceTools,
96
+ isBuiltIn: true,
97
+ connected: true
98
+ });
99
+ for (const tool of terminalTools) {
100
+ allTools.push({
101
+ type: "function",
102
+ function: {
103
+ name: `terminal-${tool.name.replace('terminal_', '')}`,
104
+ description: tool.description,
105
+ parameters: tool.inputSchema
106
+ }
107
+ });
108
+ }
109
+ // Add built-in TODO tools (always available)
110
+ await todoService.initialize();
111
+ const todoTools = todoService.getTools();
112
+ const todoServiceTools = todoTools.map(tool => ({
113
+ name: tool.name.replace('todo-', ''),
114
+ description: tool.description || '',
115
+ inputSchema: tool.inputSchema
116
+ }));
117
+ servicesInfo.push({
118
+ serviceName: 'todo',
119
+ tools: todoServiceTools,
120
+ isBuiltIn: true,
121
+ connected: true
122
+ });
123
+ for (const tool of todoTools) {
124
+ allTools.push({
125
+ type: "function",
126
+ function: {
127
+ name: tool.name,
128
+ description: tool.description || '',
129
+ parameters: tool.inputSchema
130
+ }
131
+ });
132
+ }
133
+ // Add user-configured MCP server tools (probe for availability but don't maintain connections)
134
+ try {
135
+ const mcpConfig = getMCPConfig();
136
+ for (const [serviceName, server] of Object.entries(mcpConfig.mcpServers)) {
137
+ try {
138
+ const serviceTools = await probeServiceTools(serviceName, server);
139
+ servicesInfo.push({
140
+ serviceName,
141
+ tools: serviceTools,
142
+ isBuiltIn: false,
143
+ connected: true
144
+ });
145
+ for (const tool of serviceTools) {
146
+ allTools.push({
147
+ type: "function",
148
+ function: {
149
+ name: `${serviceName}-${tool.name}`,
150
+ description: tool.description,
151
+ parameters: tool.inputSchema
152
+ }
153
+ });
154
+ }
155
+ }
156
+ catch (error) {
157
+ servicesInfo.push({
158
+ serviceName,
159
+ tools: [],
160
+ isBuiltIn: false,
161
+ connected: false,
162
+ error: error instanceof Error ? error.message : 'Unknown error'
163
+ });
164
+ }
165
+ }
166
+ }
167
+ catch (error) {
168
+ logger.warn('Failed to load MCP config:', error);
169
+ }
170
+ // Update cache
171
+ toolsCache = {
172
+ tools: allTools,
173
+ servicesInfo,
174
+ lastUpdate: Date.now(),
175
+ configHash: generateConfigHash()
176
+ };
177
+ }
178
+ /**
179
+ * Manually refresh the tools cache (for configuration changes)
180
+ */
181
+ export async function refreshMCPToolsCache() {
182
+ toolsCache = null;
183
+ await refreshToolsCache();
184
+ }
185
+ /**
186
+ * Clear the tools cache (useful for testing or forcing refresh)
187
+ */
188
+ export function clearMCPToolsCache() {
189
+ toolsCache = null;
190
+ }
191
+ /**
192
+ * Collect all available MCP tools from built-in and user-configured services
193
+ * Uses caching to avoid reconnecting on every message
194
+ */
195
+ export async function collectAllMCPTools() {
196
+ return await getCachedTools();
197
+ }
198
+ /**
199
+ * Get detailed information about all MCP services and their tools
200
+ * Uses cached data when available
201
+ */
202
+ export async function getMCPServicesInfo() {
203
+ if (!isCacheValid()) {
204
+ await refreshToolsCache();
205
+ }
206
+ return toolsCache.servicesInfo;
207
+ }
208
+ /**
209
+ * Quick probe of MCP service tools without maintaining connections
210
+ * This is used for caching tool definitions
211
+ */
212
+ async function probeServiceTools(serviceName, server) {
213
+ return await connectAndGetTools(serviceName, server, 3000); // Short timeout for probing
214
+ }
215
+ /**
216
+ * Connect to MCP service and get tools (used for both caching and execution)
217
+ * @param serviceName - Name of the service
218
+ * @param server - Server configuration
219
+ * @param timeoutMs - Timeout in milliseconds (default 10000)
220
+ */
221
+ async function connectAndGetTools(serviceName, server, timeoutMs = 10000) {
222
+ let client = null;
223
+ let transport;
224
+ try {
225
+ client = new Client({
226
+ name: `snow-cli-${serviceName}`,
227
+ version: '1.0.0',
228
+ }, {
229
+ capabilities: {}
230
+ });
231
+ // Create transport based on server configuration
232
+ if (server.url) {
233
+ let urlString = server.url;
234
+ if (server.env) {
235
+ const allEnv = { ...process.env, ...server.env };
236
+ urlString = urlString.replace(/\$\{([^}]+)\}|\$([A-Za-z_][A-Za-z0-9_]*)/g, (match, braced, simple) => {
237
+ const varName = braced || simple;
238
+ return allEnv[varName] || match;
239
+ });
240
+ }
241
+ else {
242
+ urlString = urlString.replace(/\$\{([^}]+)\}|\$([A-Za-z_][A-Za-z0-9_]*)/g, (match, braced, simple) => {
243
+ const varName = braced || simple;
244
+ return process.env[varName] || match;
245
+ });
246
+ }
247
+ const url = new URL(urlString);
248
+ try {
249
+ // Try HTTP first
250
+ const headers = {
251
+ 'Content-Type': 'application/json',
252
+ };
253
+ if (server.env) {
254
+ const allEnv = { ...process.env, ...server.env };
255
+ if (allEnv['MCP_API_KEY']) {
256
+ headers['Authorization'] = `Bearer ${allEnv['MCP_API_KEY']}`;
257
+ }
258
+ if (allEnv['MCP_AUTH_HEADER']) {
259
+ headers['Authorization'] = allEnv['MCP_AUTH_HEADER'];
260
+ }
261
+ }
262
+ transport = new StreamableHTTPClientTransport(url, {
263
+ requestInit: { headers }
264
+ });
265
+ await Promise.race([
266
+ client.connect(transport),
267
+ new Promise((_, reject) => setTimeout(() => reject(new Error('HTTP connection timeout')), timeoutMs))
268
+ ]);
269
+ }
270
+ catch (httpError) {
271
+ // Fallback to SSE
272
+ try {
273
+ await client.close();
274
+ }
275
+ catch { }
276
+ client = new Client({
277
+ name: `snow-cli-${serviceName}`,
278
+ version: '1.0.0',
279
+ }, {
280
+ capabilities: {}
281
+ });
282
+ transport = new SSEClientTransport(url);
283
+ await Promise.race([
284
+ client.connect(transport),
285
+ new Promise((_, reject) => setTimeout(() => reject(new Error('SSE connection timeout')), timeoutMs))
286
+ ]);
287
+ }
288
+ }
289
+ else if (server.command) {
290
+ const processEnv = {};
291
+ Object.entries(process.env).forEach(([key, value]) => {
292
+ if (value !== undefined) {
293
+ processEnv[key] = value;
294
+ }
295
+ });
296
+ if (server.env) {
297
+ Object.assign(processEnv, server.env);
298
+ }
299
+ transport = new StdioClientTransport({
300
+ command: server.command,
301
+ args: server.args || [],
302
+ env: processEnv,
303
+ });
304
+ await client.connect(transport);
305
+ }
306
+ else {
307
+ throw new Error('No URL or command specified');
308
+ }
309
+ // Get tools from the service
310
+ const toolsResult = await Promise.race([
311
+ client.listTools(),
312
+ new Promise((_, reject) => setTimeout(() => reject(new Error('ListTools timeout')), timeoutMs))
313
+ ]);
314
+ return toolsResult.tools?.map(tool => ({
315
+ name: tool.name,
316
+ description: tool.description || '',
317
+ inputSchema: tool.inputSchema
318
+ })) || [];
319
+ }
320
+ finally {
321
+ try {
322
+ if (client) {
323
+ await client.close();
324
+ }
325
+ }
326
+ catch (error) {
327
+ logger.warn(`Failed to close client for ${serviceName}:`, error);
328
+ }
329
+ }
330
+ }
331
+ /**
332
+ * Execute an MCP tool by parsing the prefixed tool name
333
+ * Only connects to the service when actually needed
334
+ */
335
+ export async function executeMCPTool(toolName, args) {
336
+ // Find the service name by checking against known services
337
+ let serviceName = null;
338
+ let actualToolName = null;
339
+ // Check built-in services first
340
+ if (toolName.startsWith('todo-')) {
341
+ serviceName = 'todo';
342
+ actualToolName = toolName.substring('todo-'.length);
343
+ }
344
+ else if (toolName.startsWith('filesystem-')) {
345
+ serviceName = 'filesystem';
346
+ actualToolName = toolName.substring('filesystem-'.length);
347
+ }
348
+ else if (toolName.startsWith('terminal-')) {
349
+ serviceName = 'terminal';
350
+ actualToolName = toolName.substring('terminal-'.length);
351
+ }
352
+ else {
353
+ // Check configured MCP services
354
+ try {
355
+ const mcpConfig = getMCPConfig();
356
+ for (const configuredServiceName of Object.keys(mcpConfig.mcpServers)) {
357
+ const prefix = `${configuredServiceName}-`;
358
+ if (toolName.startsWith(prefix)) {
359
+ serviceName = configuredServiceName;
360
+ actualToolName = toolName.substring(prefix.length);
361
+ break;
362
+ }
363
+ }
364
+ }
365
+ catch {
366
+ // Ignore config errors, will handle below
367
+ }
368
+ }
369
+ if (!serviceName || !actualToolName) {
370
+ throw new Error(`Invalid tool name format: ${toolName}. Expected format: serviceName-toolName`);
371
+ }
372
+ if (serviceName === 'todo') {
373
+ // Handle built-in TODO tools (no connection needed)
374
+ return await todoService.executeTool(toolName, args);
375
+ }
376
+ else if (serviceName === 'filesystem') {
377
+ // Handle built-in filesystem tools (no connection needed)
378
+ const { filesystemService } = await import('../mcp/filesystem.js');
379
+ switch (actualToolName) {
380
+ case 'read':
381
+ return await filesystemService.getFileContent(args.filePath, args.startLine, args.endLine);
382
+ case 'create':
383
+ return await filesystemService.createFile(args.filePath, args.content, args.createDirectories);
384
+ case 'delete':
385
+ return await filesystemService.deleteFile(args.filePath);
386
+ case 'list':
387
+ return await filesystemService.listFiles(args.dirPath);
388
+ case 'exists':
389
+ return await filesystemService.exists(args.filePath);
390
+ case 'info':
391
+ return await filesystemService.getFileInfo(args.filePath);
392
+ case 'edit':
393
+ return await filesystemService.editFile(args.filePath, args.startLine, args.endLine, args.newContent, args.contextLines);
394
+ case 'search':
395
+ return await filesystemService.searchCode(args.query, args.dirPath, args.fileExtensions, args.caseSensitive, args.maxResults);
396
+ default:
397
+ throw new Error(`Unknown filesystem tool: ${actualToolName}`);
398
+ }
399
+ }
400
+ else if (serviceName === 'terminal') {
401
+ // Handle built-in terminal tools (no connection needed)
402
+ const { terminalService } = await import('../mcp/bash.js');
403
+ switch (actualToolName) {
404
+ case 'execute':
405
+ return await terminalService.executeCommand(args.command, args.timeout);
406
+ default:
407
+ throw new Error(`Unknown terminal tool: ${actualToolName}`);
408
+ }
409
+ }
410
+ else {
411
+ // Handle user-configured MCP service tools - connect only when needed
412
+ const mcpConfig = getMCPConfig();
413
+ const server = mcpConfig.mcpServers[serviceName];
414
+ if (!server) {
415
+ throw new Error(`MCP service not found: ${serviceName}`);
416
+ }
417
+ // Connect to service and execute tool
418
+ logger.info(`Executing tool ${actualToolName} on MCP service ${serviceName}... args: ${args ? JSON.stringify(args) : 'none'}`);
419
+ return await executeOnExternalMCPService(serviceName, server, actualToolName, args);
420
+ }
421
+ }
422
+ /**
423
+ * Execute a tool on an external MCP service - connects only when needed
424
+ */
425
+ async function executeOnExternalMCPService(serviceName, server, toolName, args) {
426
+ let client = null;
427
+ logger.debug(`Connecting to MCP service ${serviceName} to execute tool ${toolName}...`);
428
+ try {
429
+ client = new Client({
430
+ name: `snow-cli-${serviceName}`,
431
+ version: '1.0.0',
432
+ }, {
433
+ capabilities: {}
434
+ });
435
+ // Setup transport (similar to getServiceTools)
436
+ let transport;
437
+ if (server.url) {
438
+ let urlString = server.url;
439
+ if (server.env) {
440
+ const allEnv = { ...process.env, ...server.env };
441
+ urlString = urlString.replace(/\$\{([^}]+)\}|\$([A-Za-z_][A-Za-z0-9_]*)/g, (match, braced, simple) => {
442
+ const varName = braced || simple;
443
+ return allEnv[varName] || match;
444
+ });
445
+ }
446
+ const url = new URL(urlString);
447
+ transport = new StreamableHTTPClientTransport(url);
448
+ }
449
+ else if (server.command) {
450
+ transport = new StdioClientTransport({
451
+ command: server.command,
452
+ args: server.args || [],
453
+ env: server.env ? { ...process.env, ...server.env } : process.env,
454
+ });
455
+ }
456
+ await client.connect(transport);
457
+ logger.debug(`ToolName ${toolName}, args:`, args ? JSON.stringify(args) : 'none');
458
+ // Execute the tool with the original tool name (not prefixed)
459
+ const result = await client.callTool({
460
+ name: toolName,
461
+ arguments: args
462
+ });
463
+ logger.debug(`result from ${serviceName} tool ${toolName}:`, result);
464
+ return result.content;
465
+ }
466
+ finally {
467
+ try {
468
+ if (client) {
469
+ await client.close();
470
+ }
471
+ }
472
+ catch (error) {
473
+ logger.warn(`Failed to close client for ${serviceName}:`, error);
474
+ }
475
+ }
476
+ }
@@ -0,0 +1,12 @@
1
+ import type { ToolCall } from './toolExecutor.js';
2
+ /**
3
+ * Format tool call display information for UI rendering
4
+ */
5
+ export declare function formatToolCallMessage(toolCall: ToolCall): {
6
+ toolName: string;
7
+ args: Array<{
8
+ key: string;
9
+ value: string;
10
+ isLast: boolean;
11
+ }>;
12
+ };
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Format tool call display information for UI rendering
3
+ */
4
+ export function formatToolCallMessage(toolCall) {
5
+ try {
6
+ const args = JSON.parse(toolCall.function.arguments);
7
+ const argEntries = Object.entries(args);
8
+ const formattedArgs = [];
9
+ if (argEntries.length > 0) {
10
+ argEntries.forEach(([key, value], idx, arr) => {
11
+ const valueStr = typeof value === 'string'
12
+ ? value.length > 60 ? `"${value.slice(0, 60)}..."` : `"${value}"`
13
+ : JSON.stringify(value);
14
+ formattedArgs.push({
15
+ key,
16
+ value: valueStr,
17
+ isLast: idx === arr.length - 1
18
+ });
19
+ });
20
+ }
21
+ return {
22
+ toolName: toolCall.function.name,
23
+ args: formattedArgs
24
+ };
25
+ }
26
+ catch (e) {
27
+ return {
28
+ toolName: toolCall.function.name,
29
+ args: []
30
+ };
31
+ }
32
+ }
@@ -0,0 +1,6 @@
1
+ import type { ChatMessage } from '../api/chat.js';
2
+ import type { Message } from '../ui/components/MessageList.js';
3
+ /**
4
+ * Convert API format session messages to UI format messages
5
+ */
6
+ export declare function convertSessionMessagesToUI(sessionMessages: ChatMessage[]): Message[];
@@ -0,0 +1,61 @@
1
+ import { formatToolCallMessage } from './messageFormatter.js';
2
+ /**
3
+ * Convert API format session messages to UI format messages
4
+ */
5
+ export function convertSessionMessagesToUI(sessionMessages) {
6
+ const uiMessages = [];
7
+ for (const msg of sessionMessages) {
8
+ // Skip system messages
9
+ if (msg.role === 'system')
10
+ continue;
11
+ // Handle tool role messages (tool execution results)
12
+ if (msg.role === 'tool') {
13
+ const isError = msg.content.startsWith('Error:');
14
+ const statusIcon = isError ? '✗' : '✓';
15
+ const statusText = isError ? `\n └─ ${msg.content}` : '';
16
+ const toolName = msg.tool_call_id || 'unknown-tool';
17
+ uiMessages.push({
18
+ role: 'assistant',
19
+ content: `${statusIcon} ${toolName}${statusText}`,
20
+ streaming: false,
21
+ toolResult: !isError ? msg.content : undefined
22
+ });
23
+ continue;
24
+ }
25
+ // Handle user and assistant messages
26
+ const uiMessage = {
27
+ role: msg.role,
28
+ content: msg.content,
29
+ streaming: false,
30
+ images: msg.images
31
+ };
32
+ // If assistant message has tool_calls, expand to show each tool call
33
+ if (msg.role === 'assistant' && msg.tool_calls && msg.tool_calls.length > 0) {
34
+ for (const toolCall of msg.tool_calls) {
35
+ const toolDisplay = formatToolCallMessage(toolCall);
36
+ let toolArgs;
37
+ try {
38
+ toolArgs = JSON.parse(toolCall.function.arguments);
39
+ }
40
+ catch (e) {
41
+ toolArgs = {};
42
+ }
43
+ uiMessages.push({
44
+ role: 'assistant',
45
+ content: `⚡ ${toolDisplay.toolName}`,
46
+ streaming: false,
47
+ toolCall: {
48
+ name: toolCall.function.name,
49
+ arguments: toolArgs
50
+ },
51
+ toolDisplay
52
+ });
53
+ }
54
+ }
55
+ else {
56
+ // Add regular message directly
57
+ uiMessages.push(uiMessage);
58
+ }
59
+ }
60
+ return uiMessages;
61
+ }
@@ -0,0 +1,39 @@
1
+ import type { ChatMessage as APIChatMessage } from '../api/chat.js';
2
+ export interface ChatMessage extends APIChatMessage {
3
+ timestamp: number;
4
+ }
5
+ export interface Session {
6
+ id: string;
7
+ title: string;
8
+ summary: string;
9
+ createdAt: number;
10
+ updatedAt: number;
11
+ messages: ChatMessage[];
12
+ messageCount: number;
13
+ }
14
+ export interface SessionListItem {
15
+ id: string;
16
+ title: string;
17
+ summary: string;
18
+ createdAt: number;
19
+ updatedAt: number;
20
+ messageCount: number;
21
+ }
22
+ declare class SessionManager {
23
+ private readonly sessionsDir;
24
+ private currentSession;
25
+ constructor();
26
+ private ensureSessionsDir;
27
+ private getSessionPath;
28
+ createNewSession(): Promise<Session>;
29
+ saveSession(session: Session): Promise<void>;
30
+ loadSession(sessionId: string): Promise<Session | null>;
31
+ listSessions(): Promise<SessionListItem[]>;
32
+ addMessage(message: ChatMessage): Promise<void>;
33
+ getCurrentSession(): Session | null;
34
+ setCurrentSession(session: Session): void;
35
+ clearCurrentSession(): void;
36
+ deleteSession(sessionId: string): Promise<boolean>;
37
+ }
38
+ export declare const sessionManager: SessionManager;
39
+ export {};