snow-ai 0.4.5 → 0.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/anthropic.js +30 -1
- package/dist/api/chat.js +25 -0
- package/dist/api/gemini.js +26 -8
- package/dist/api/responses.js +32 -5
- package/dist/api/systemPrompt.js +51 -101
- package/dist/hooks/useAgentPicker.d.ts +4 -0
- package/dist/hooks/useAgentPicker.js +91 -4
- package/dist/hooks/useFilePicker.d.ts +1 -1
- package/dist/hooks/useKeyboardInput.d.ts +2 -1
- package/dist/hooks/useKeyboardInput.js +18 -6
- package/dist/i18n/lang/en.js +2 -1
- package/dist/i18n/lang/es.js +2 -1
- package/dist/i18n/lang/ja.js +2 -1
- package/dist/i18n/lang/ko.js +2 -1
- package/dist/i18n/lang/zh-TW.js +2 -1
- package/dist/i18n/lang/zh.js +2 -1
- package/dist/i18n/types.d.ts +1 -0
- package/dist/mcp/filesystem.d.ts +28 -17
- package/dist/mcp/filesystem.js +177 -28
- package/dist/mcp/types/filesystem.types.d.ts +75 -0
- package/dist/mcp/types/filesystem.types.js +24 -1
- package/dist/mcp/utils/filesystem/office-parser.utils.d.ts +43 -0
- package/dist/mcp/utils/filesystem/office-parser.utils.js +163 -0
- package/dist/mcp/utils/filesystem/path-fixer.utils.d.ts +7 -0
- package/dist/mcp/utils/filesystem/path-fixer.utils.js +60 -0
- package/dist/ui/components/AgentPickerPanel.d.ts +3 -1
- package/dist/ui/components/AgentPickerPanel.js +3 -5
- package/dist/ui/components/ChatInput.js +4 -3
- package/dist/ui/components/HelpPanel.js +3 -0
- package/dist/ui/pages/ChatScreen.js +4 -0
- package/dist/utils/toolExecutor.d.ts +2 -0
- package/dist/utils/toolExecutor.js +58 -1
- package/package.json +9 -4
package/dist/i18n/lang/en.js
CHANGED
|
@@ -283,6 +283,7 @@ export const en = {
|
|
|
283
283
|
quickAccessTitle: '🔍 Quick Access:',
|
|
284
284
|
insertFiles: '@ - Insert files from project',
|
|
285
285
|
searchContent: '@@ - Search file content',
|
|
286
|
+
selectAgent: '# - Select sub-agent for task execution',
|
|
286
287
|
showCommands: '/ - Show available commands',
|
|
287
288
|
navigationTitle: '📋 Navigation:',
|
|
288
289
|
navigateHistory: '↑/↓ - Navigate command/message history',
|
|
@@ -329,7 +330,7 @@ export const en = {
|
|
|
329
330
|
headerExplanations: 'Ask for code explanations and debugging help',
|
|
330
331
|
headerInterrupt: 'Press ESC during response to interrupt',
|
|
331
332
|
headerYolo: 'Press Shift+Tab: toggle YOLO',
|
|
332
|
-
headerShortcuts: "Shortcuts: Ctrl+L (delete to start) • Ctrl+R (delete to end) • {pasteKey} (paste images) • '@' (files) • '@@' (search content) • '/' (commands)",
|
|
333
|
+
headerShortcuts: "Shortcuts: Ctrl+L (delete to start) • Ctrl+R (delete to end) • {pasteKey} (paste images) • '@' (files) • '@@' (search content) • '#' (sub-agents) • '/' (commands)",
|
|
333
334
|
headerWorkingDirectory: 'Working directory: {directory}',
|
|
334
335
|
// Status messages
|
|
335
336
|
statusThinking: 'Thinking...',
|
package/dist/i18n/lang/es.js
CHANGED
|
@@ -283,6 +283,7 @@ export const es = {
|
|
|
283
283
|
quickAccessTitle: '🔍 Acceso Rápido:',
|
|
284
284
|
insertFiles: '@ - Insertar archivos del proyecto',
|
|
285
285
|
searchContent: '@@ - Buscar contenido de archivos',
|
|
286
|
+
selectAgent: '# - Seleccionar sub-agente para ejecutar tarea',
|
|
286
287
|
showCommands: '/ - Mostrar comandos disponibles',
|
|
287
288
|
navigationTitle: '📋 Navegación:',
|
|
288
289
|
navigateHistory: '↑/↓ - Navegar por el historial de comandos/mensajes',
|
|
@@ -329,7 +330,7 @@ export const es = {
|
|
|
329
330
|
headerExplanations: 'Solicita explicaciones de código y ayuda de depuración',
|
|
330
331
|
headerInterrupt: 'Presiona ESC durante la respuesta para interrumpir',
|
|
331
332
|
headerYolo: 'Presiona Shift+Tab: Alternar YOLO',
|
|
332
|
-
headerShortcuts: "Atajos: Ctrl+L (eliminar hasta inicio) • Ctrl+R (eliminar hasta final) • {pasteKey} (pegar imagen) • '@' (archivo) • '@@' (buscar contenido) • '/' (comando)",
|
|
333
|
+
headerShortcuts: "Atajos: Ctrl+L (eliminar hasta inicio) • Ctrl+R (eliminar hasta final) • {pasteKey} (pegar imagen) • '@' (archivo) • '@@' (buscar contenido) • '#' (sub-agentes) • '/' (comando)",
|
|
333
334
|
headerWorkingDirectory: 'Directorio de Trabajo: {directory}',
|
|
334
335
|
// Status messages
|
|
335
336
|
statusThinking: 'Pensando...',
|
package/dist/i18n/lang/ja.js
CHANGED
|
@@ -283,6 +283,7 @@ export const ja = {
|
|
|
283
283
|
quickAccessTitle: '🔍 クイックアクセス:',
|
|
284
284
|
insertFiles: '@ - プロジェクトからファイルを挿入',
|
|
285
285
|
searchContent: '@@ - ファイル内容を検索',
|
|
286
|
+
selectAgent: '# - サブエージェントを選択してタスクを実行',
|
|
286
287
|
showCommands: '/ - 利用可能なコマンドを表示',
|
|
287
288
|
navigationTitle: '📋 ナビゲーション:',
|
|
288
289
|
navigateHistory: '↑/↓ - コマンド/メッセージ履歴を移動',
|
|
@@ -329,7 +330,7 @@ export const ja = {
|
|
|
329
330
|
headerExplanations: 'コードの説明とデバッグヘルプを問い合わせ',
|
|
330
331
|
headerInterrupt: '応答中にESCで中断',
|
|
331
332
|
headerYolo: 'Shift+Tabを押す: YOLOを切替',
|
|
332
|
-
headerShortcuts: "ショートカット: Ctrl+L (先頭まで削除) • Ctrl+R (末尾まで削除) • {pasteKey} (画像貼付) • '@' (ファイル) • '@@' (内容検索) • '/' (コマンド)",
|
|
333
|
+
headerShortcuts: "ショートカット: Ctrl+L (先頭まで削除) • Ctrl+R (末尾まで削除) • {pasteKey} (画像貼付) • '@' (ファイル) • '@@' (内容検索) • '#' (サブエージェント) • '/' (コマンド)",
|
|
333
334
|
headerWorkingDirectory: '作業ディレクトリ: {directory}',
|
|
334
335
|
// Status messages
|
|
335
336
|
statusThinking: '思考中...',
|
package/dist/i18n/lang/ko.js
CHANGED
|
@@ -283,6 +283,7 @@ export const ko = {
|
|
|
283
283
|
quickAccessTitle: '🔍 빠른 접근:',
|
|
284
284
|
insertFiles: '@ - 프로젝트에서 파일 삽입',
|
|
285
285
|
searchContent: '@@ - 파일 내용 검색',
|
|
286
|
+
selectAgent: '# - 작업 실행을 위한 하위 에이전트 선택',
|
|
286
287
|
showCommands: '/ - 사용 가능한 명령 표시',
|
|
287
288
|
navigationTitle: '📋 탐색:',
|
|
288
289
|
navigateHistory: '↑/↓ - 명령/메시지 기록 탐색',
|
|
@@ -329,7 +330,7 @@ export const ko = {
|
|
|
329
330
|
headerExplanations: '코드 설명 및 디버그 도움 요청',
|
|
330
331
|
headerInterrupt: '응답 중 ESC로 중단',
|
|
331
332
|
headerYolo: 'Shift+Tab 누르기: YOLO 토글',
|
|
332
|
-
headerShortcuts: "단축키: Ctrl+L (시작까지 삭제) • Ctrl+R (끝까지 삭제) • {pasteKey} (이미지 붙여넣기) • '@' (파일) • '@@' (내용 검색) • '/' (명령)",
|
|
333
|
+
headerShortcuts: "단축키: Ctrl+L (시작까지 삭제) • Ctrl+R (끝까지 삭제) • {pasteKey} (이미지 붙여넣기) • '@' (파일) • '@@' (내용 검색) • '#' (하위 에이전트) • '/' (명령)",
|
|
333
334
|
headerWorkingDirectory: '작업 디렉토리: {directory}',
|
|
334
335
|
// Status messages
|
|
335
336
|
statusThinking: '생각 중...',
|
package/dist/i18n/lang/zh-TW.js
CHANGED
|
@@ -283,6 +283,7 @@ export const zhTW = {
|
|
|
283
283
|
quickAccessTitle: '🔍 快速存取:',
|
|
284
284
|
insertFiles: '@ - 從專案插入檔案',
|
|
285
285
|
searchContent: '@@ - 搜尋檔案內容',
|
|
286
|
+
selectAgent: '# - 選擇子代理執行任務',
|
|
286
287
|
showCommands: '/ - 顯示可用命令',
|
|
287
288
|
navigationTitle: '📋 導航:',
|
|
288
289
|
navigateHistory: '↑/↓ - 導航命令/訊息歷史',
|
|
@@ -329,7 +330,7 @@ export const zhTW = {
|
|
|
329
330
|
headerExplanations: '詢問程式碼說明和偵錯協助',
|
|
330
331
|
headerInterrupt: '在回應期間按 ESC 中斷',
|
|
331
332
|
headerYolo: '按 Shift+Tab: 切換 YOLO',
|
|
332
|
-
headerShortcuts: "快捷鍵: Ctrl+L (刪除至開頭) • Ctrl+R (刪除至末尾) • {pasteKey} (貼上圖片) • '@' (檔案) • '@@' (搜尋內容) • '/' (命令)",
|
|
333
|
+
headerShortcuts: "快捷鍵: Ctrl+L (刪除至開頭) • Ctrl+R (刪除至末尾) • {pasteKey} (貼上圖片) • '@' (檔案) • '@@' (搜尋內容) • '#' (子代理) • '/' (命令)",
|
|
333
334
|
headerWorkingDirectory: '工作目錄: {directory}',
|
|
334
335
|
// Status messages
|
|
335
336
|
statusThinking: '思考中...',
|
package/dist/i18n/lang/zh.js
CHANGED
|
@@ -283,6 +283,7 @@ export const zh = {
|
|
|
283
283
|
quickAccessTitle: '🔍 快速访问:',
|
|
284
284
|
insertFiles: '@ - 从项目插入文件',
|
|
285
285
|
searchContent: '@@ - 搜索文件内容',
|
|
286
|
+
selectAgent: '# - 选择子代理执行任务',
|
|
286
287
|
showCommands: '/ - 显示可用命令',
|
|
287
288
|
navigationTitle: '📋 导航:',
|
|
288
289
|
navigateHistory: '↑/↓ - 导航命令/消息历史',
|
|
@@ -329,7 +330,7 @@ export const zh = {
|
|
|
329
330
|
headerExplanations: '询问代码说明和调试帮助',
|
|
330
331
|
headerInterrupt: '在响应期间按 ESC 中断',
|
|
331
332
|
headerYolo: '按 Shift+Tab: 切换 YOLO',
|
|
332
|
-
headerShortcuts: "快捷键: Ctrl+L (删除至开头) • Ctrl+R (删除至末尾) • {pasteKey} (粘贴图片) • '@' (文件) • '@@' (搜索内容) • '/' (命令)",
|
|
333
|
+
headerShortcuts: "快捷键: Ctrl+L (删除至开头) • Ctrl+R (删除至末尾) • {pasteKey} (粘贴图片) • '@' (文件) • '@@' (搜索内容) • '#' (子代理) • '/' (命令)",
|
|
333
334
|
headerWorkingDirectory: '工作目录: {directory}',
|
|
334
335
|
// Status messages
|
|
335
336
|
statusThinking: '思考中...',
|
package/dist/i18n/types.d.ts
CHANGED
package/dist/mcp/filesystem.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { EditBySearchConfig, EditByLineConfig, EditBySearchResult, EditByLineResult } from './types/filesystem.types.js';
|
|
1
|
+
import type { EditBySearchConfig, EditByLineConfig, EditBySearchResult, EditByLineResult, SingleFileReadResult, MultipleFilesReadResult } from './types/filesystem.types.js';
|
|
2
2
|
/**
|
|
3
3
|
* Filesystem MCP Service
|
|
4
4
|
* Provides basic file operations: read, create, and delete files
|
|
@@ -10,6 +10,30 @@ export declare class FilesystemMCPService {
|
|
|
10
10
|
*/
|
|
11
11
|
private readonly prettierSupportedExtensions;
|
|
12
12
|
constructor(basePath?: string);
|
|
13
|
+
/**
|
|
14
|
+
* Check if a file is an image based on extension
|
|
15
|
+
* @param filePath - Path to the file
|
|
16
|
+
* @returns True if the file is an image
|
|
17
|
+
*/
|
|
18
|
+
private isImageFile;
|
|
19
|
+
/**
|
|
20
|
+
* Check if a file is an Office document based on extension
|
|
21
|
+
* @param filePath - Path to the file
|
|
22
|
+
* @returns True if the file is an Office document
|
|
23
|
+
*/
|
|
24
|
+
private isOfficeFile;
|
|
25
|
+
/**
|
|
26
|
+
* Get MIME type for an image file
|
|
27
|
+
* @param filePath - Path to the file
|
|
28
|
+
* @returns MIME type or undefined if not an image
|
|
29
|
+
*/
|
|
30
|
+
private getImageMimeType;
|
|
31
|
+
/**
|
|
32
|
+
* Read image file and convert to base64
|
|
33
|
+
* @param fullPath - Full path to the image file
|
|
34
|
+
* @returns ImageContent object with base64 data
|
|
35
|
+
*/
|
|
36
|
+
private readImageAsBase64;
|
|
13
37
|
/**
|
|
14
38
|
* Extract relevant symbol information for a specific line range
|
|
15
39
|
* This provides context that helps AI make more accurate modifications
|
|
@@ -29,31 +53,18 @@ export declare class FilesystemMCPService {
|
|
|
29
53
|
/**
|
|
30
54
|
* Get the content of a file with optional line range
|
|
31
55
|
* Enhanced with symbol information for better AI context
|
|
56
|
+
* Supports multimodal content (text + images)
|
|
32
57
|
* @param filePath - Path to the file (relative to base path or absolute) or array of file paths or array of file config objects
|
|
33
58
|
* @param startLine - Starting line number (1-indexed, inclusive, optional - defaults to 1). Used for single file or as default for array of strings
|
|
34
59
|
* @param endLine - Ending line number (1-indexed, inclusive, optional - defaults to file end). Used for single file or as default for array of strings
|
|
35
|
-
* @returns Object containing the requested content with line numbers and metadata
|
|
60
|
+
* @returns Object containing the requested content with line numbers and metadata (supports multimodal content)
|
|
36
61
|
* @throws Error if file doesn't exist or cannot be read
|
|
37
62
|
*/
|
|
38
63
|
getFileContent(filePath: string | string[] | Array<{
|
|
39
64
|
path: string;
|
|
40
65
|
startLine?: number;
|
|
41
66
|
endLine?: number;
|
|
42
|
-
}>, startLine?: number, endLine?: number): Promise<
|
|
43
|
-
content: string;
|
|
44
|
-
startLine: number;
|
|
45
|
-
endLine: number;
|
|
46
|
-
totalLines: number;
|
|
47
|
-
} | {
|
|
48
|
-
content: string;
|
|
49
|
-
files: Array<{
|
|
50
|
-
path: string;
|
|
51
|
-
startLine: number;
|
|
52
|
-
endLine: number;
|
|
53
|
-
totalLines: number;
|
|
54
|
-
}>;
|
|
55
|
-
totalFiles: number;
|
|
56
|
-
}>;
|
|
67
|
+
}>, startLine?: number, endLine?: number): Promise<SingleFileReadResult | MultipleFilesReadResult>;
|
|
57
68
|
/**
|
|
58
69
|
* Create a new file with specified content
|
|
59
70
|
* @param filePath - Path where the file should be created
|
package/dist/mcp/filesystem.js
CHANGED
|
@@ -5,16 +5,19 @@ import * as prettier from 'prettier';
|
|
|
5
5
|
import { vscodeConnection } from '../utils/vscodeConnection.js';
|
|
6
6
|
import { incrementalSnapshotManager } from '../utils/incrementalSnapshot.js';
|
|
7
7
|
import { tryUnescapeFix, trimPairIfPossible, isOverEscaped, } from '../utils/escapeHandler.js';
|
|
8
|
+
import { IMAGE_MIME_TYPES, OFFICE_FILE_TYPES } from './types/filesystem.types.js';
|
|
8
9
|
// Utility functions
|
|
9
10
|
import { calculateSimilarity, normalizeForDisplay, } from './utils/filesystem/similarity.utils.js';
|
|
10
11
|
import { analyzeCodeStructure, findSmartContextBoundaries, } from './utils/filesystem/code-analysis.utils.js';
|
|
11
12
|
import { findClosestMatches, generateDiffMessage, } from './utils/filesystem/match-finder.utils.js';
|
|
12
13
|
import { parseEditBySearchParams, parseEditByLineParams, executeBatchOperation, } from './utils/filesystem/batch-operations.utils.js';
|
|
14
|
+
import { tryFixPath } from './utils/filesystem/path-fixer.utils.js';
|
|
15
|
+
import { readOfficeDocument } from './utils/filesystem/office-parser.utils.js';
|
|
13
16
|
// ACE Code Search utilities for symbol parsing
|
|
14
17
|
import { parseFileSymbols } from './utils/aceCodeSearch/symbol.utils.js';
|
|
15
18
|
// Notebook utilities for automatic note retrieval
|
|
16
19
|
import { queryNotebook } from '../utils/notebookManager.js';
|
|
17
|
-
const { resolve, dirname, isAbsolute } = path;
|
|
20
|
+
const { resolve, dirname, isAbsolute, extname } = path;
|
|
18
21
|
/**
|
|
19
22
|
* Filesystem MCP Service
|
|
20
23
|
* Provides basic file operations: read, create, and delete files
|
|
@@ -54,6 +57,57 @@ export class FilesystemMCPService {
|
|
|
54
57
|
});
|
|
55
58
|
this.basePath = resolve(basePath);
|
|
56
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Check if a file is an image based on extension
|
|
62
|
+
* @param filePath - Path to the file
|
|
63
|
+
* @returns True if the file is an image
|
|
64
|
+
*/
|
|
65
|
+
isImageFile(filePath) {
|
|
66
|
+
const ext = extname(filePath).toLowerCase();
|
|
67
|
+
return ext in IMAGE_MIME_TYPES;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Check if a file is an Office document based on extension
|
|
71
|
+
* @param filePath - Path to the file
|
|
72
|
+
* @returns True if the file is an Office document
|
|
73
|
+
*/
|
|
74
|
+
isOfficeFile(filePath) {
|
|
75
|
+
const ext = extname(filePath).toLowerCase();
|
|
76
|
+
return ext in OFFICE_FILE_TYPES;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Get MIME type for an image file
|
|
80
|
+
* @param filePath - Path to the file
|
|
81
|
+
* @returns MIME type or undefined if not an image
|
|
82
|
+
*/
|
|
83
|
+
getImageMimeType(filePath) {
|
|
84
|
+
const ext = extname(filePath).toLowerCase();
|
|
85
|
+
return IMAGE_MIME_TYPES[ext];
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Read image file and convert to base64
|
|
89
|
+
* @param fullPath - Full path to the image file
|
|
90
|
+
* @returns ImageContent object with base64 data
|
|
91
|
+
*/
|
|
92
|
+
async readImageAsBase64(fullPath) {
|
|
93
|
+
try {
|
|
94
|
+
const mimeType = this.getImageMimeType(fullPath);
|
|
95
|
+
if (!mimeType) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
const buffer = await fs.readFile(fullPath);
|
|
99
|
+
const base64Data = buffer.toString('base64');
|
|
100
|
+
return {
|
|
101
|
+
type: 'image',
|
|
102
|
+
data: base64Data,
|
|
103
|
+
mimeType,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
console.error(`Failed to read image ${fullPath}:`, error);
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
57
111
|
/**
|
|
58
112
|
* Extract relevant symbol information for a specific line range
|
|
59
113
|
* This provides context that helps AI make more accurate modifications
|
|
@@ -155,10 +209,11 @@ export class FilesystemMCPService {
|
|
|
155
209
|
/**
|
|
156
210
|
* Get the content of a file with optional line range
|
|
157
211
|
* Enhanced with symbol information for better AI context
|
|
212
|
+
* Supports multimodal content (text + images)
|
|
158
213
|
* @param filePath - Path to the file (relative to base path or absolute) or array of file paths or array of file config objects
|
|
159
214
|
* @param startLine - Starting line number (1-indexed, inclusive, optional - defaults to 1). Used for single file or as default for array of strings
|
|
160
215
|
* @param endLine - Ending line number (1-indexed, inclusive, optional - defaults to file end). Used for single file or as default for array of strings
|
|
161
|
-
* @returns Object containing the requested content with line numbers and metadata
|
|
216
|
+
* @returns Object containing the requested content with line numbers and metadata (supports multimodal content)
|
|
162
217
|
* @throws Error if file doesn't exist or cannot be read
|
|
163
218
|
*/
|
|
164
219
|
async getFileContent(filePath, startLine, endLine) {
|
|
@@ -166,7 +221,7 @@ export class FilesystemMCPService {
|
|
|
166
221
|
// Handle array of files
|
|
167
222
|
if (Array.isArray(filePath)) {
|
|
168
223
|
const filesData = [];
|
|
169
|
-
const
|
|
224
|
+
const multimodalContent = [];
|
|
170
225
|
for (const fileItem of filePath) {
|
|
171
226
|
try {
|
|
172
227
|
// Support both string format and object format
|
|
@@ -195,7 +250,10 @@ export class FilesystemMCPService {
|
|
|
195
250
|
if (stats.isDirectory()) {
|
|
196
251
|
const dirFiles = await this.listFiles(file);
|
|
197
252
|
const fileList = dirFiles.join('\n');
|
|
198
|
-
|
|
253
|
+
multimodalContent.push({
|
|
254
|
+
type: 'text',
|
|
255
|
+
text: `📁 Directory: ${file}\n${fileList}`,
|
|
256
|
+
});
|
|
199
257
|
filesData.push({
|
|
200
258
|
path: file,
|
|
201
259
|
startLine: 1,
|
|
@@ -204,6 +262,44 @@ export class FilesystemMCPService {
|
|
|
204
262
|
});
|
|
205
263
|
continue;
|
|
206
264
|
}
|
|
265
|
+
// Check if this is an image file
|
|
266
|
+
if (this.isImageFile(fullPath)) {
|
|
267
|
+
const imageContent = await this.readImageAsBase64(fullPath);
|
|
268
|
+
if (imageContent) {
|
|
269
|
+
// Add text description first
|
|
270
|
+
multimodalContent.push({
|
|
271
|
+
type: 'text',
|
|
272
|
+
text: `🖼️ Image: ${file} (${imageContent.mimeType})`,
|
|
273
|
+
});
|
|
274
|
+
// Add image content
|
|
275
|
+
multimodalContent.push(imageContent);
|
|
276
|
+
filesData.push({
|
|
277
|
+
path: file,
|
|
278
|
+
isImage: true,
|
|
279
|
+
mimeType: imageContent.mimeType,
|
|
280
|
+
});
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
// Check if this is an Office document file
|
|
285
|
+
if (this.isOfficeFile(fullPath)) {
|
|
286
|
+
const docContent = await readOfficeDocument(fullPath);
|
|
287
|
+
if (docContent) {
|
|
288
|
+
// Add text description first
|
|
289
|
+
multimodalContent.push({
|
|
290
|
+
type: 'text',
|
|
291
|
+
text: `📄 ${docContent.fileType.toUpperCase()} Document: ${file}`,
|
|
292
|
+
});
|
|
293
|
+
// Add document content
|
|
294
|
+
multimodalContent.push(docContent);
|
|
295
|
+
filesData.push({
|
|
296
|
+
path: file,
|
|
297
|
+
isDocument: true,
|
|
298
|
+
fileType: docContent.fileType,
|
|
299
|
+
});
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
207
303
|
const content = await fs.readFile(fullPath, 'utf-8');
|
|
208
304
|
const lines = content.split('\n');
|
|
209
305
|
const totalLines = lines.length;
|
|
@@ -217,10 +313,8 @@ export class FilesystemMCPService {
|
|
|
217
313
|
if (actualEndLine < actualStartLine) {
|
|
218
314
|
throw new Error(`End line must be greater than or equal to start line for ${file}`);
|
|
219
315
|
}
|
|
220
|
-
if
|
|
221
|
-
|
|
222
|
-
}
|
|
223
|
-
const start = actualStartLine;
|
|
316
|
+
// Auto-adjust if startLine exceeds file length
|
|
317
|
+
const start = Math.min(actualStartLine, totalLines);
|
|
224
318
|
const end = Math.min(totalLines, actualEndLine);
|
|
225
319
|
// Extract specified lines
|
|
226
320
|
const selectedLines = lines.slice(start - 1, end);
|
|
@@ -245,7 +339,10 @@ export class FilesystemMCPService {
|
|
|
245
339
|
if (notebookInfo) {
|
|
246
340
|
fileContent += notebookInfo;
|
|
247
341
|
}
|
|
248
|
-
|
|
342
|
+
multimodalContent.push({
|
|
343
|
+
type: 'text',
|
|
344
|
+
text: fileContent,
|
|
345
|
+
});
|
|
249
346
|
filesData.push({
|
|
250
347
|
path: file,
|
|
251
348
|
startLine: start,
|
|
@@ -257,11 +354,14 @@ export class FilesystemMCPService {
|
|
|
257
354
|
const errorMsg = error instanceof Error ? error.message : 'Unknown error';
|
|
258
355
|
// Extract file path for error message
|
|
259
356
|
const filePath = typeof fileItem === 'string' ? fileItem : fileItem.path;
|
|
260
|
-
|
|
357
|
+
multimodalContent.push({
|
|
358
|
+
type: 'text',
|
|
359
|
+
text: `❌ ${filePath}: ${errorMsg}`,
|
|
360
|
+
});
|
|
261
361
|
}
|
|
262
362
|
}
|
|
263
363
|
return {
|
|
264
|
-
content:
|
|
364
|
+
content: multimodalContent,
|
|
265
365
|
files: filesData,
|
|
266
366
|
totalFiles: filePath.length,
|
|
267
367
|
};
|
|
@@ -285,6 +385,41 @@ export class FilesystemMCPService {
|
|
|
285
385
|
totalLines: lines.length,
|
|
286
386
|
};
|
|
287
387
|
}
|
|
388
|
+
// Check if this is an image file
|
|
389
|
+
if (this.isImageFile(fullPath)) {
|
|
390
|
+
const imageContent = await this.readImageAsBase64(fullPath);
|
|
391
|
+
if (imageContent) {
|
|
392
|
+
return {
|
|
393
|
+
content: [
|
|
394
|
+
{
|
|
395
|
+
type: 'text',
|
|
396
|
+
text: `🖼️ Image: ${filePath} (${imageContent.mimeType})`,
|
|
397
|
+
},
|
|
398
|
+
imageContent,
|
|
399
|
+
],
|
|
400
|
+
isImage: true,
|
|
401
|
+
mimeType: imageContent.mimeType,
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
// Check if this is an Office document file
|
|
406
|
+
if (this.isOfficeFile(fullPath)) {
|
|
407
|
+
const docContent = await readOfficeDocument(fullPath);
|
|
408
|
+
if (docContent) {
|
|
409
|
+
return {
|
|
410
|
+
content: [
|
|
411
|
+
{
|
|
412
|
+
type: 'text',
|
|
413
|
+
text: `📄 ${docContent.fileType.toUpperCase()} Document: ${filePath}`,
|
|
414
|
+
},
|
|
415
|
+
docContent,
|
|
416
|
+
],
|
|
417
|
+
isDocument: true,
|
|
418
|
+
fileType: docContent.fileType,
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
// Text file processing
|
|
288
423
|
const content = await fs.readFile(fullPath, 'utf-8');
|
|
289
424
|
// Parse lines
|
|
290
425
|
const lines = content.split('\n');
|
|
@@ -302,10 +437,8 @@ export class FilesystemMCPService {
|
|
|
302
437
|
if (actualEndLine < actualStartLine) {
|
|
303
438
|
throw new Error('End line must be greater than or equal to start line');
|
|
304
439
|
}
|
|
305
|
-
if
|
|
306
|
-
|
|
307
|
-
}
|
|
308
|
-
const start = actualStartLine;
|
|
440
|
+
// Auto-adjust if startLine exceeds file length
|
|
441
|
+
const start = Math.min(actualStartLine, totalLines);
|
|
309
442
|
const end = Math.min(totalLines, actualEndLine);
|
|
310
443
|
// Extract specified lines (convert to 0-indexed) and add line numbers
|
|
311
444
|
const selectedLines = lines.slice(start - 1, end);
|
|
@@ -340,6 +473,24 @@ export class FilesystemMCPService {
|
|
|
340
473
|
};
|
|
341
474
|
}
|
|
342
475
|
catch (error) {
|
|
476
|
+
// Try to fix common path issues if it's a file not found error
|
|
477
|
+
if (error instanceof Error &&
|
|
478
|
+
error.message.includes('ENOENT') &&
|
|
479
|
+
typeof filePath === 'string') {
|
|
480
|
+
const fixedPath = await tryFixPath(filePath, this.basePath);
|
|
481
|
+
if (fixedPath && fixedPath !== filePath) {
|
|
482
|
+
// Verify the fixed path actually exists before suggesting
|
|
483
|
+
const fixedFullPath = this.resolvePath(fixedPath);
|
|
484
|
+
try {
|
|
485
|
+
await fs.access(fixedFullPath);
|
|
486
|
+
// File exists, provide helpful suggestion to AI
|
|
487
|
+
throw new Error(`Failed to read file ${filePath}: ${error instanceof Error ? error.message : 'Unknown error'}\n💡 Tip: File not found. Did you mean "${fixedPath}"? Please use the correct path.`);
|
|
488
|
+
}
|
|
489
|
+
catch {
|
|
490
|
+
// Fixed path also doesn't work, just throw original error
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
343
494
|
throw new Error(`Failed to read file ${filePath}: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
344
495
|
}
|
|
345
496
|
}
|
|
@@ -855,25 +1006,23 @@ export class FilesystemMCPService {
|
|
|
855
1006
|
if (startLine > endLine) {
|
|
856
1007
|
throw new Error('Start line must be less than or equal to end line');
|
|
857
1008
|
}
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
}
|
|
861
|
-
// Adjust endLine if it exceeds file length
|
|
1009
|
+
// Adjust startLine and endLine if they exceed file length
|
|
1010
|
+
const adjustedStartLine = Math.min(startLine, totalLines);
|
|
862
1011
|
const adjustedEndLine = Math.min(endLine, totalLines);
|
|
863
|
-
const linesToModify = adjustedEndLine -
|
|
1012
|
+
const linesToModify = adjustedEndLine - adjustedStartLine + 1;
|
|
864
1013
|
// Backup file before editing
|
|
865
1014
|
await incrementalSnapshotManager.backupFile(fullPath);
|
|
866
1015
|
// Extract the lines that will be replaced (for comparison)
|
|
867
1016
|
// Compress whitespace for display readability
|
|
868
|
-
const replacedLines = lines.slice(
|
|
1017
|
+
const replacedLines = lines.slice(adjustedStartLine - 1, adjustedEndLine);
|
|
869
1018
|
const replacedContent = replacedLines
|
|
870
1019
|
.map((line, idx) => {
|
|
871
|
-
const lineNum =
|
|
1020
|
+
const lineNum = adjustedStartLine + idx;
|
|
872
1021
|
return `${lineNum}→${normalizeForDisplay(line)}`;
|
|
873
1022
|
})
|
|
874
1023
|
.join('\n');
|
|
875
1024
|
// Calculate context range using smart boundary detection
|
|
876
|
-
const smartBoundaries = findSmartContextBoundaries(lines,
|
|
1025
|
+
const smartBoundaries = findSmartContextBoundaries(lines, adjustedStartLine, adjustedEndLine, contextLines);
|
|
877
1026
|
const contextStart = smartBoundaries.start;
|
|
878
1027
|
const contextEnd = smartBoundaries.end;
|
|
879
1028
|
// Extract old content for context (compress whitespace for readability)
|
|
@@ -886,12 +1035,12 @@ export class FilesystemMCPService {
|
|
|
886
1035
|
.join('\n');
|
|
887
1036
|
// Replace the specified lines
|
|
888
1037
|
const newContentLines = newContent.split('\n');
|
|
889
|
-
const beforeLines = lines.slice(0,
|
|
1038
|
+
const beforeLines = lines.slice(0, adjustedStartLine - 1);
|
|
890
1039
|
const afterLines = lines.slice(adjustedEndLine);
|
|
891
1040
|
const modifiedLines = [...beforeLines, ...newContentLines, ...afterLines];
|
|
892
1041
|
// Calculate new context range
|
|
893
1042
|
const newTotalLines = modifiedLines.length;
|
|
894
|
-
const lineDifference = newContentLines.length - (adjustedEndLine -
|
|
1043
|
+
const lineDifference = newContentLines.length - (adjustedEndLine - adjustedStartLine + 1);
|
|
895
1044
|
const newContextEnd = Math.min(newTotalLines, contextEnd + lineDifference);
|
|
896
1045
|
// Extract new content for context with line numbers (compress whitespace)
|
|
897
1046
|
const newContextLines = modifiedLines.slice(contextStart - 1, newContextEnd);
|
|
@@ -941,7 +1090,7 @@ export class FilesystemMCPService {
|
|
|
941
1090
|
}
|
|
942
1091
|
}
|
|
943
1092
|
// Analyze code structure of the edited content (using formatted content if available)
|
|
944
|
-
const editedContentLines = finalLines.slice(
|
|
1093
|
+
const editedContentLines = finalLines.slice(adjustedStartLine - 1, adjustedStartLine - 1 + newContentLines.length);
|
|
945
1094
|
const structureAnalysis = analyzeCodeStructure(finalLines.join('\n'), filePath, editedContentLines);
|
|
946
1095
|
// Try to get diagnostics from IDE (VSCode or JetBrains) after editing (non-blocking)
|
|
947
1096
|
let diagnostics = [];
|
|
@@ -958,7 +1107,7 @@ export class FilesystemMCPService {
|
|
|
958
1107
|
}
|
|
959
1108
|
const result = {
|
|
960
1109
|
message: `✅ File edited successfully,Please check the edit results and pay attention to code boundary issues to avoid syntax errors caused by missing closed parts: ${filePath}\n` +
|
|
961
|
-
` Replaced: lines ${
|
|
1110
|
+
` Replaced: lines ${adjustedStartLine}-${adjustedEndLine} (${linesToModify} lines)\n` +
|
|
962
1111
|
` Result: ${newContentLines.length} new lines` +
|
|
963
1112
|
(smartBoundaries.extended
|
|
964
1113
|
? `\n 📍 Context auto-extended to show complete code block (lines ${contextStart}-${finalContextEnd})`
|
|
@@ -1077,7 +1226,7 @@ export const filesystemService = new FilesystemMCPService();
|
|
|
1077
1226
|
export const mcpTools = [
|
|
1078
1227
|
{
|
|
1079
1228
|
name: 'filesystem-read',
|
|
1080
|
-
description: 'Read file content with line numbers. **Read only when the actual file or folder path is found or provided by the user, do not make random guesses,Search for specific documents or line numbers before reading more accurately** **SUPPORTS MULTIPLE FILES WITH FLEXIBLE LINE RANGES**: Pass either (1) a single file path (string), (2) array of file paths (strings) with unified startLine/endLine, or (3) array of file config objects with per-file line ranges. **INTEGRATED DIRECTORY LISTING**: When filePath is a directory, automatically lists its contents instead of throwing error. ⚠️ **IMPORTANT WORKFLOW**: (1) ALWAYS use ACE search tools FIRST (ace-text_search/ace-search_symbols/ace-file_outline) to locate the relevant code, (2) ONLY use filesystem-read when you know the approximate location and need precise line numbers for editing. **ANTI-PATTERN**: Reading files line-by-line from the top wastes tokens - use search instead! **USAGE**: Call without parameters to read entire file(s), or specify startLine/endLine for partial reads. Returns content with line numbers (format: "123→code") for
|
|
1229
|
+
description: 'Read file content with line numbers and multimodal support (text + images + Office documents). **MULTIMODAL SUPPORT**: Automatically detects and processes: (1) Image files (.png, .jpg, .jpeg, .gif, .webp, .bmp, .svg) - returns base64-encoded image data, (2) Office documents (.pdf, .docx, .doc, .xlsx, .xls, .pptx, .ppt) - extracts and returns readable text content. All returned in MCP content format for AI analysis. **Read only when the actual file or folder path is found or provided by the user, do not make random guesses,Search for specific documents or line numbers before reading more accurately** **SUPPORTS MULTIPLE FILES WITH FLEXIBLE LINE RANGES**: Pass either (1) a single file path (string), (2) array of file paths (strings) with unified startLine/endLine, or (3) array of file config objects with per-file line ranges. **INTEGRATED DIRECTORY LISTING**: When filePath is a directory, automatically lists its contents instead of throwing error. ⚠️ **IMPORTANT WORKFLOW**: (1) ALWAYS use ACE search tools FIRST (ace-text_search/ace-search_symbols/ace-file_outline) to locate the relevant code, (2) ONLY use filesystem-read when you know the approximate location and need precise line numbers for editing. **ANTI-PATTERN**: Reading files line-by-line from the top wastes tokens - use search instead! **USAGE**: Call without parameters to read entire file(s), or specify startLine/endLine for partial reads. Returns content with line numbers (format: "123→code") for text files or multimodal content array for images/documents. **EXAMPLES**: (A) Unified: filePath=["a.ts", "b.ts"], startLine=1, endLine=500 reads lines 1-500 from both. (B) Per-file: filePath=[{path:"a.ts", startLine:1, endLine:300}, {path:"b.ts", startLine:100, endLine:550}] reads different ranges from each file. (C) Directory: filePath="./src" returns list of files in src/. (D) Image: filePath="screenshot.png" returns multimodal content with base64 image data. (E) Office: filePath="report.pdf" or "data.xlsx" extracts and returns document text.',
|
|
1081
1230
|
inputSchema: {
|
|
1082
1231
|
type: 'object',
|
|
1083
1232
|
properties: {
|
|
@@ -2,6 +2,51 @@
|
|
|
2
2
|
* Type definitions for Filesystem MCP Service
|
|
3
3
|
*/
|
|
4
4
|
import type { Diagnostic } from '../../utils/vscodeConnection.js';
|
|
5
|
+
/**
|
|
6
|
+
* MCP Content Types - supports multimodal content
|
|
7
|
+
*/
|
|
8
|
+
export type MCPContentType = 'text' | 'image' | 'document';
|
|
9
|
+
/**
|
|
10
|
+
* Text content block
|
|
11
|
+
*/
|
|
12
|
+
export interface TextContent {
|
|
13
|
+
type: 'text';
|
|
14
|
+
text: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Image content block (base64 encoded)
|
|
18
|
+
*/
|
|
19
|
+
export interface ImageContent {
|
|
20
|
+
type: 'image';
|
|
21
|
+
data: string;
|
|
22
|
+
mimeType: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Document content block (for Office files like PDF, Word, Excel, PPT)
|
|
26
|
+
*/
|
|
27
|
+
export interface DocumentContent {
|
|
28
|
+
type: 'document';
|
|
29
|
+
text: string;
|
|
30
|
+
fileType: 'pdf' | 'word' | 'excel' | 'powerpoint';
|
|
31
|
+
metadata?: {
|
|
32
|
+
pages?: number;
|
|
33
|
+
sheets?: string[];
|
|
34
|
+
slides?: number;
|
|
35
|
+
[key: string]: unknown;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Multimodal content - array of text, image, and document blocks
|
|
40
|
+
*/
|
|
41
|
+
export type MultimodalContent = Array<TextContent | ImageContent | DocumentContent>;
|
|
42
|
+
/**
|
|
43
|
+
* Supported image MIME types
|
|
44
|
+
*/
|
|
45
|
+
export declare const IMAGE_MIME_TYPES: Record<string, string>;
|
|
46
|
+
/**
|
|
47
|
+
* Supported Office document types
|
|
48
|
+
*/
|
|
49
|
+
export declare const OFFICE_FILE_TYPES: Record<string, 'pdf' | 'word' | 'excel' | 'powerpoint'>;
|
|
5
50
|
/**
|
|
6
51
|
* Structure analysis result for code validation
|
|
7
52
|
*/
|
|
@@ -51,6 +96,36 @@ export interface FileReadConfig {
|
|
|
51
96
|
startLine?: number;
|
|
52
97
|
endLine?: number;
|
|
53
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* Single file read result
|
|
101
|
+
*/
|
|
102
|
+
export interface SingleFileReadResult {
|
|
103
|
+
content: string | MultimodalContent;
|
|
104
|
+
startLine?: number;
|
|
105
|
+
endLine?: number;
|
|
106
|
+
totalLines?: number;
|
|
107
|
+
isImage?: boolean;
|
|
108
|
+
isDocument?: boolean;
|
|
109
|
+
fileType?: 'pdf' | 'word' | 'excel' | 'powerpoint';
|
|
110
|
+
mimeType?: string;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Multiple files read result
|
|
114
|
+
*/
|
|
115
|
+
export interface MultipleFilesReadResult {
|
|
116
|
+
content: string | MultimodalContent;
|
|
117
|
+
files: Array<{
|
|
118
|
+
path: string;
|
|
119
|
+
startLine?: number;
|
|
120
|
+
endLine?: number;
|
|
121
|
+
totalLines?: number;
|
|
122
|
+
isImage?: boolean;
|
|
123
|
+
isDocument?: boolean;
|
|
124
|
+
fileType?: 'pdf' | 'word' | 'excel' | 'powerpoint';
|
|
125
|
+
mimeType?: string;
|
|
126
|
+
}>;
|
|
127
|
+
totalFiles: number;
|
|
128
|
+
}
|
|
54
129
|
/**
|
|
55
130
|
* Edit by search configuration
|
|
56
131
|
*/
|