btca-server 1.0.962 → 2.0.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 (43) hide show
  1. package/package.json +3 -3
  2. package/src/agent/agent.test.ts +31 -24
  3. package/src/agent/index.ts +8 -2
  4. package/src/agent/loop.ts +303 -346
  5. package/src/agent/service.ts +252 -233
  6. package/src/agent/types.ts +2 -2
  7. package/src/collections/index.ts +2 -1
  8. package/src/collections/service.ts +352 -345
  9. package/src/config/config.test.ts +3 -1
  10. package/src/config/index.ts +615 -727
  11. package/src/config/remote.ts +214 -369
  12. package/src/context/index.ts +6 -12
  13. package/src/context/transaction.ts +23 -30
  14. package/src/effect/errors.ts +45 -0
  15. package/src/effect/layers.ts +26 -0
  16. package/src/effect/runtime.ts +19 -0
  17. package/src/effect/services.ts +154 -0
  18. package/src/index.ts +291 -369
  19. package/src/metrics/index.ts +46 -46
  20. package/src/pricing/models-dev.ts +104 -106
  21. package/src/providers/auth.ts +159 -200
  22. package/src/providers/index.ts +19 -2
  23. package/src/providers/model.ts +115 -135
  24. package/src/providers/openai.ts +3 -3
  25. package/src/resources/impls/git.ts +123 -146
  26. package/src/resources/impls/npm.test.ts +16 -5
  27. package/src/resources/impls/npm.ts +66 -75
  28. package/src/resources/index.ts +6 -1
  29. package/src/resources/schema.ts +7 -6
  30. package/src/resources/service.test.ts +13 -12
  31. package/src/resources/service.ts +153 -112
  32. package/src/stream/index.ts +1 -1
  33. package/src/stream/service.test.ts +5 -5
  34. package/src/stream/service.ts +282 -293
  35. package/src/tools/glob.ts +126 -141
  36. package/src/tools/grep.ts +205 -210
  37. package/src/tools/index.ts +8 -4
  38. package/src/tools/list.ts +118 -140
  39. package/src/tools/read.ts +209 -235
  40. package/src/tools/virtual-sandbox.ts +91 -83
  41. package/src/validation/index.ts +18 -22
  42. package/src/vfs/virtual-fs.test.ts +37 -25
  43. package/src/vfs/virtual-fs.ts +218 -216
package/src/tools/read.ts CHANGED
@@ -4,266 +4,240 @@
4
4
  */
5
5
  import * as path from 'node:path';
6
6
  import { z } from 'zod';
7
- import { Result } from 'better-result';
8
7
 
9
8
  import type { ToolContext } from './context.ts';
10
- import { VirtualSandbox } from './virtual-sandbox.ts';
11
- import { VirtualFs } from '../vfs/virtual-fs.ts';
12
-
13
- export namespace ReadTool {
14
- // Configuration
15
- const MAX_LINES = 2000;
16
- const MAX_BYTES = 50 * 1024; // 50KB
17
- const MAX_LINE_LENGTH = 2000;
18
-
19
- // Schema for tool parameters
20
- export const Parameters = z.object({
21
- path: z.string().describe('The absolute path to the file to read'),
22
- offset: z.coerce
23
- .number()
24
- .optional()
25
- .describe('The line number to start reading from (0-based)'),
26
- limit: z.coerce.number().optional().describe('The number of lines to read (defaults to 2000)')
27
- });
28
-
29
- export type ParametersType = z.infer<typeof Parameters>;
30
-
31
- // Result type
32
- export type Result = {
33
- title: string;
34
- output: string;
35
- metadata: {
36
- lines: number;
37
- truncated: boolean;
38
- truncatedByLines?: boolean;
39
- truncatedByBytes?: boolean;
40
- isImage?: boolean;
41
- isPdf?: boolean;
42
- isBinary?: boolean;
43
- };
44
- // For images/PDFs, we return attachments
45
- attachments?: Array<{
46
- type: 'file';
47
- mime: string;
48
- data: string; // base64
49
- }>;
9
+ import { resolveSandboxPathWithSymlinks } from './virtual-sandbox.ts';
10
+ import {
11
+ existsInVirtualFs,
12
+ readVirtualFsFile,
13
+ readVirtualFsFileBuffer,
14
+ readdirVirtualFs
15
+ } from '../vfs/virtual-fs.ts';
16
+
17
+ const MAX_LINES = 2000;
18
+ const MAX_BYTES = 50 * 1024;
19
+ const MAX_LINE_LENGTH = 2000;
20
+
21
+ export const ReadToolParameters = z.object({
22
+ path: z.string().describe('The absolute path to the file to read'),
23
+ offset: z.coerce.number().optional().describe('The line number to start reading from (0-based)'),
24
+ limit: z.coerce.number().optional().describe('The number of lines to read (defaults to 2000)')
25
+ });
26
+
27
+ export type ReadToolParametersType = z.infer<typeof ReadToolParameters>;
28
+
29
+ export type ReadToolResult = {
30
+ title: string;
31
+ output: string;
32
+ metadata: {
33
+ lines: number;
34
+ truncated: boolean;
35
+ truncatedByLines?: boolean;
36
+ truncatedByBytes?: boolean;
37
+ isImage?: boolean;
38
+ isPdf?: boolean;
39
+ isBinary?: boolean;
50
40
  };
51
-
52
- // Image extensions
53
- const IMAGE_EXTENSIONS = new Set([
54
- '.png',
55
- '.jpg',
56
- '.jpeg',
57
- '.gif',
58
- '.webp',
59
- '.bmp',
60
- '.ico',
61
- '.svg'
62
- ]);
63
-
64
- // PDF extension
65
- const PDF_EXTENSIONS = new Set(['.pdf']);
66
-
67
- /**
68
- * Check if a file is binary by looking for null bytes
69
- */
70
- function isBinaryBuffer(bytes: Uint8Array): boolean {
71
- for (const byte of bytes) {
72
- if (byte === 0) return true;
73
- }
74
- return false;
41
+ attachments?: Array<{
42
+ type: 'file';
43
+ mime: string;
44
+ data: string;
45
+ }>;
46
+ };
47
+
48
+ const IMAGE_EXTENSIONS = new Set([
49
+ '.png',
50
+ '.jpg',
51
+ '.jpeg',
52
+ '.gif',
53
+ '.webp',
54
+ '.bmp',
55
+ '.ico',
56
+ '.svg'
57
+ ]);
58
+
59
+ const PDF_EXTENSIONS = new Set(['.pdf']);
60
+
61
+ const isBinaryBuffer = (bytes: Uint8Array): boolean => {
62
+ for (const byte of bytes) {
63
+ if (byte === 0) return true;
75
64
  }
76
-
77
- /**
78
- * Execute the read tool
79
- */
80
- export async function execute(params: ParametersType, context: ToolContext): Promise<Result> {
81
- const { basePath, vfsId } = context;
82
-
83
- // Validate and resolve path within sandbox
84
- const resolvedPath = await VirtualSandbox.resolvePathWithSymlinks(basePath, params.path, vfsId);
85
-
86
- // Check if file exists
87
- const exists = await VirtualFs.exists(resolvedPath, vfsId);
88
- if (!exists) {
89
- // Try to provide suggestions
90
- const dir = path.dirname(resolvedPath);
91
- const filename = path.basename(resolvedPath);
92
- let suggestions: string[] = [];
93
-
94
- const filesResult = await Result.tryPromise(() => VirtualFs.readdir(dir, vfsId));
95
- const files = filesResult.match({
96
- ok: (entries) => entries.map((entry) => entry.name),
97
- err: () => []
98
- });
99
- suggestions = files
100
- .filter((f) => f.toLowerCase().includes(filename.toLowerCase().slice(0, 3)))
101
- .slice(0, 5);
102
-
103
- const suggestionText =
104
- suggestions.length > 0
105
- ? `\nDid you mean:\n${suggestions.map((s) => ` - ${s}`).join('\n')}`
106
- : '';
107
-
108
- return {
109
- title: params.path,
110
- output: `File not found: ${params.path}${suggestionText}`,
111
- metadata: {
112
- lines: 0,
113
- truncated: false
114
- }
115
- };
65
+ return false;
66
+ };
67
+
68
+ export const executeReadTool = async (
69
+ params: ReadToolParametersType,
70
+ context: ToolContext
71
+ ): Promise<ReadToolResult> => {
72
+ const { basePath, vfsId } = context;
73
+ const resolvedPath = await resolveSandboxPathWithSymlinks(basePath, params.path, vfsId);
74
+ const exists = await existsInVirtualFs(resolvedPath, vfsId);
75
+ if (!exists) {
76
+ const dir = path.dirname(resolvedPath);
77
+ const filename = path.basename(resolvedPath);
78
+
79
+ let files: string[] = [];
80
+ try {
81
+ const entries = await readdirVirtualFs(dir, vfsId);
82
+ files = entries.map((entry) => entry.name);
83
+ } catch {
84
+ files = [];
116
85
  }
117
86
 
118
- const ext = path.extname(resolvedPath).toLowerCase();
119
-
120
- // Handle images
121
- if (IMAGE_EXTENSIONS.has(ext)) {
122
- const bytes = await VirtualFs.readFileBuffer(resolvedPath, vfsId);
123
- const base64 = Buffer.from(bytes).toString('base64');
124
- const mime = getImageMime(ext);
87
+ const suggestions = files
88
+ .filter((f) => f.toLowerCase().includes(filename.toLowerCase().slice(0, 3)))
89
+ .slice(0, 5);
90
+ const suggestionText =
91
+ suggestions.length > 0
92
+ ? `\nDid you mean:\n${suggestions.map((s) => ` - ${s}`).join('\n')}`
93
+ : '';
125
94
 
126
- return {
127
- title: params.path,
128
- output: `[Image file: ${path.basename(resolvedPath)}]`,
129
- metadata: {
130
- lines: 0,
131
- truncated: false,
132
- isImage: true
133
- },
134
- attachments: [
135
- {
136
- type: 'file',
137
- mime,
138
- data: base64
139
- }
140
- ]
141
- };
142
- }
95
+ return {
96
+ title: params.path,
97
+ output: `File not found: ${params.path}${suggestionText}`,
98
+ metadata: {
99
+ lines: 0,
100
+ truncated: false
101
+ }
102
+ };
103
+ }
143
104
 
144
- // Handle PDFs
145
- if (PDF_EXTENSIONS.has(ext)) {
146
- const bytes = await VirtualFs.readFileBuffer(resolvedPath, vfsId);
147
- const base64 = Buffer.from(bytes).toString('base64');
105
+ const ext = path.extname(resolvedPath).toLowerCase();
148
106
 
149
- return {
150
- title: params.path,
151
- output: `[PDF file: ${path.basename(resolvedPath)}]`,
152
- metadata: {
153
- lines: 0,
154
- truncated: false,
155
- isPdf: true
156
- },
157
- attachments: [
158
- {
159
- type: 'file',
160
- mime: 'application/pdf',
161
- data: base64
162
- }
163
- ]
164
- };
165
- }
107
+ if (IMAGE_EXTENSIONS.has(ext)) {
108
+ const bytes = await readVirtualFsFileBuffer(resolvedPath, vfsId);
109
+ const base64 = Buffer.from(bytes).toString('base64');
110
+ const mime = getImageMime(ext);
166
111
 
167
- // Check for binary files
168
- if (isBinaryBuffer(await VirtualFs.readFileBuffer(resolvedPath, vfsId))) {
169
- return {
170
- title: params.path,
171
- output: `[Binary file: ${path.basename(resolvedPath)}]`,
172
- metadata: {
173
- lines: 0,
174
- truncated: false,
175
- isBinary: true
112
+ return {
113
+ title: params.path,
114
+ output: `[Image file: ${path.basename(resolvedPath)}]`,
115
+ metadata: {
116
+ lines: 0,
117
+ truncated: false,
118
+ isImage: true
119
+ },
120
+ attachments: [
121
+ {
122
+ type: 'file',
123
+ mime,
124
+ data: base64
176
125
  }
177
- };
178
- }
179
-
180
- // Read text file
181
- const text = await VirtualFs.readFile(resolvedPath, vfsId);
182
- const allLines = text.split('\n');
183
-
184
- const offset = params.offset ?? 0;
185
- const limit = params.limit ?? MAX_LINES;
186
-
187
- // Apply truncation
188
- let truncatedByLines = false;
189
- let truncatedByBytes = false;
190
-
191
- const outputLines: string[] = [];
192
- let totalBytes = 0;
126
+ ]
127
+ };
128
+ }
193
129
 
194
- const endLine = Math.min(allLines.length, offset + limit);
130
+ if (PDF_EXTENSIONS.has(ext)) {
131
+ const bytes = await readVirtualFsFileBuffer(resolvedPath, vfsId);
132
+ const base64 = Buffer.from(bytes).toString('base64');
195
133
 
196
- for (let i = offset; i < endLine; i++) {
197
- let line = allLines[i] ?? '';
134
+ return {
135
+ title: params.path,
136
+ output: `[PDF file: ${path.basename(resolvedPath)}]`,
137
+ metadata: {
138
+ lines: 0,
139
+ truncated: false,
140
+ isPdf: true
141
+ },
142
+ attachments: [
143
+ {
144
+ type: 'file',
145
+ mime: 'application/pdf',
146
+ data: base64
147
+ }
148
+ ]
149
+ };
150
+ }
198
151
 
199
- // Truncate long lines
200
- if (line.length > MAX_LINE_LENGTH) {
201
- line = line.substring(0, MAX_LINE_LENGTH) + '...';
152
+ if (isBinaryBuffer(await readVirtualFsFileBuffer(resolvedPath, vfsId))) {
153
+ return {
154
+ title: params.path,
155
+ output: `[Binary file: ${path.basename(resolvedPath)}]`,
156
+ metadata: {
157
+ lines: 0,
158
+ truncated: false,
159
+ isBinary: true
202
160
  }
161
+ };
162
+ }
203
163
 
204
- const lineBytes = Buffer.byteLength(line, 'utf8');
164
+ const text = await readVirtualFsFile(resolvedPath, vfsId);
165
+ const allLines = text.split('\n');
205
166
 
206
- if (totalBytes + lineBytes > MAX_BYTES) {
207
- truncatedByBytes = true;
208
- break;
209
- }
167
+ const offset = params.offset ?? 0;
168
+ const limit = params.limit ?? MAX_LINES;
210
169
 
211
- outputLines.push(line);
212
- totalBytes += lineBytes;
170
+ let truncatedByLines = false;
171
+ let truncatedByBytes = false;
172
+ const outputLines: string[] = [];
173
+ let totalBytes = 0;
174
+ const endLine = Math.min(allLines.length, offset + limit);
175
+
176
+ for (let i = offset; i < endLine; i++) {
177
+ let line = allLines[i] ?? '';
178
+ if (line.length > MAX_LINE_LENGTH) {
179
+ line = line.substring(0, MAX_LINE_LENGTH) + '...';
213
180
  }
214
181
 
215
- if (outputLines.length < endLine - offset || endLine < allLines.length) {
216
- truncatedByLines = !truncatedByBytes && outputLines.length >= limit;
182
+ const lineBytes = Buffer.byteLength(line, 'utf8');
183
+ if (totalBytes + lineBytes > MAX_BYTES) {
184
+ truncatedByBytes = true;
185
+ break;
217
186
  }
218
187
 
219
- // Format output with line numbers
220
- const formattedOutput = outputLines
221
- .map((line, index) => {
222
- const lineNum = (index + offset + 1).toString().padStart(5, ' ');
223
- return `${lineNum}\t${line}`;
224
- })
225
- .join('\n');
188
+ outputLines.push(line);
189
+ totalBytes += lineBytes;
190
+ }
226
191
 
227
- // Build truncation message
228
- let truncationMessage = '';
229
- if (truncatedByBytes || truncatedByLines) {
230
- const remaining = allLines.length - offset - outputLines.length;
231
- if (remaining > 0) {
232
- truncationMessage = `\n\n[Truncated: ${remaining} more lines. Use offset=${offset + outputLines.length} to continue reading.]`;
233
- }
234
- }
192
+ if (outputLines.length < endLine - offset || endLine < allLines.length) {
193
+ truncatedByLines = !truncatedByBytes && outputLines.length >= limit;
194
+ }
235
195
 
236
- return {
237
- title: params.path,
238
- output: formattedOutput + truncationMessage,
239
- metadata: {
240
- lines: outputLines.length,
241
- truncated: truncatedByBytes || truncatedByLines,
242
- truncatedByLines,
243
- truncatedByBytes
244
- }
245
- };
196
+ const formattedOutput = outputLines
197
+ .map((line, index) => {
198
+ const lineNum = (index + offset + 1).toString().padStart(5, ' ');
199
+ return `${lineNum}\t${line}`;
200
+ })
201
+ .join('\n');
202
+
203
+ let truncationMessage = '';
204
+ if (truncatedByBytes || truncatedByLines) {
205
+ const remaining = allLines.length - offset - outputLines.length;
206
+ if (remaining > 0) {
207
+ truncationMessage = `\n\n[Truncated: ${remaining} more lines. Use offset=${offset + outputLines.length} to continue reading.]`;
208
+ }
246
209
  }
247
210
 
248
- function getImageMime(ext: string): string {
249
- switch (ext) {
250
- case '.png':
251
- return 'image/png';
252
- case '.jpg':
253
- case '.jpeg':
254
- return 'image/jpeg';
255
- case '.gif':
256
- return 'image/gif';
257
- case '.webp':
258
- return 'image/webp';
259
- case '.bmp':
260
- return 'image/bmp';
261
- case '.ico':
262
- return 'image/x-icon';
263
- case '.svg':
264
- return 'image/svg+xml';
265
- default:
266
- return 'application/octet-stream';
211
+ return {
212
+ title: params.path,
213
+ output: formattedOutput + truncationMessage,
214
+ metadata: {
215
+ lines: outputLines.length,
216
+ truncated: truncatedByBytes || truncatedByLines,
217
+ truncatedByLines,
218
+ truncatedByBytes
267
219
  }
220
+ };
221
+ };
222
+
223
+ const getImageMime = (ext: string): string => {
224
+ switch (ext) {
225
+ case '.png':
226
+ return 'image/png';
227
+ case '.jpg':
228
+ case '.jpeg':
229
+ return 'image/jpeg';
230
+ case '.gif':
231
+ return 'image/gif';
232
+ case '.webp':
233
+ return 'image/webp';
234
+ case '.bmp':
235
+ return 'image/bmp';
236
+ case '.ico':
237
+ return 'image/x-icon';
238
+ case '.svg':
239
+ return 'image/svg+xml';
240
+ default:
241
+ return 'application/octet-stream';
268
242
  }
269
- }
243
+ };
@@ -1,103 +1,111 @@
1
1
  import * as path from 'node:path';
2
2
 
3
- import { Result } from 'better-result';
4
-
5
- import { VirtualFs } from '../vfs/virtual-fs.ts';
3
+ import { existsInVirtualFs, realpathVirtualFs, statVirtualFs } from '../vfs/virtual-fs.ts';
6
4
 
7
5
  const posix = path.posix;
8
6
 
9
- export namespace VirtualSandbox {
10
- export class PathEscapeError extends Error {
11
- readonly _tag = 'PathEscapeError';
12
- readonly requestedPath: string;
13
- readonly basePath: string;
7
+ export class PathEscapeError extends Error {
8
+ readonly _tag = 'PathEscapeError';
9
+ readonly requestedPath: string;
10
+ readonly basePath: string;
14
11
 
15
- constructor(requestedPath: string, basePath: string) {
16
- super(
17
- `Path "${requestedPath}" is outside the allowed directory "${basePath}". Access denied.`
18
- );
19
- this.requestedPath = requestedPath;
20
- this.basePath = basePath;
21
- }
12
+ constructor(requestedPath: string, basePath: string) {
13
+ super(`Path "${requestedPath}" is outside the allowed directory "${basePath}". Access denied.`);
14
+ this.requestedPath = requestedPath;
15
+ this.basePath = basePath;
22
16
  }
17
+ }
23
18
 
24
- export class PathNotFoundError extends Error {
25
- readonly _tag = 'PathNotFoundError';
26
- readonly requestedPath: string;
19
+ export class PathNotFoundError extends Error {
20
+ readonly _tag = 'PathNotFoundError';
21
+ readonly requestedPath: string;
27
22
 
28
- constructor(requestedPath: string) {
29
- super(`Path "${requestedPath}" does not exist.`);
30
- this.requestedPath = requestedPath;
31
- }
23
+ constructor(requestedPath: string) {
24
+ super(`Path "${requestedPath}" does not exist.`);
25
+ this.requestedPath = requestedPath;
32
26
  }
27
+ }
33
28
 
34
- export function resolvePath(basePath: string, requestedPath: string): string {
35
- const normalizedBase = posix.resolve('/', basePath);
36
- const resolved = posix.isAbsolute(requestedPath)
37
- ? posix.resolve(requestedPath)
38
- : posix.resolve(normalizedBase, requestedPath);
39
- const normalized = posix.normalize(resolved);
40
- const relative = posix.relative(normalizedBase, normalized);
41
-
42
- if (relative.startsWith('..') || posix.isAbsolute(relative)) {
43
- throw new PathEscapeError(requestedPath, basePath);
44
- }
29
+ export const resolveSandboxPath = (basePath: string, requestedPath: string): string => {
30
+ const normalizedBase = posix.resolve('/', basePath);
31
+ const resolved = posix.isAbsolute(requestedPath)
32
+ ? posix.resolve(requestedPath)
33
+ : posix.resolve(normalizedBase, requestedPath);
34
+ const normalized = posix.normalize(resolved);
35
+ const relative = posix.relative(normalizedBase, normalized);
45
36
 
46
- return normalized;
37
+ if (relative.startsWith('..') || posix.isAbsolute(relative)) {
38
+ throw new PathEscapeError(requestedPath, basePath);
47
39
  }
48
40
 
49
- export async function resolvePathWithSymlinks(
50
- basePath: string,
51
- requestedPath: string,
52
- vfsId?: string
53
- ) {
54
- const resolved = resolvePath(basePath, requestedPath);
55
- const result = await Result.tryPromise(() => VirtualFs.realpath(resolved, vfsId));
56
- return result.match({
57
- ok: (value) => value,
58
- err: () => resolved
59
- });
41
+ return normalized;
42
+ };
43
+
44
+ export const resolveSandboxPathWithSymlinks = async (
45
+ basePath: string,
46
+ requestedPath: string,
47
+ vfsId?: string
48
+ ) => {
49
+ const resolved = resolveSandboxPath(basePath, requestedPath);
50
+ try {
51
+ return await realpathVirtualFs(resolved, vfsId);
52
+ } catch {
53
+ return resolved;
60
54
  }
61
-
62
- export async function exists(basePath: string, requestedPath: string, vfsId?: string) {
63
- const resolvedResult = Result.try(() => resolvePath(basePath, requestedPath));
64
- if (!Result.isOk(resolvedResult)) return false;
65
- const result = await Result.tryPromise(() => VirtualFs.exists(resolvedResult.value, vfsId));
66
- return result.match({
67
- ok: (value) => value,
68
- err: () => false
69
- });
55
+ };
56
+
57
+ export const sandboxPathExists = async (
58
+ basePath: string,
59
+ requestedPath: string,
60
+ vfsId?: string
61
+ ) => {
62
+ try {
63
+ const resolved = resolveSandboxPath(basePath, requestedPath);
64
+ return await existsInVirtualFs(resolved, vfsId);
65
+ } catch {
66
+ return false;
70
67
  }
71
-
72
- export async function isDirectory(basePath: string, requestedPath: string, vfsId?: string) {
73
- const resolvedResult = Result.try(() => resolvePath(basePath, requestedPath));
74
- if (!Result.isOk(resolvedResult)) return false;
75
- const result = await Result.tryPromise(() => VirtualFs.stat(resolvedResult.value, vfsId));
76
- return result.match({
77
- ok: (stats) => stats.isDirectory,
78
- err: () => false
79
- });
68
+ };
69
+
70
+ export const sandboxPathIsDirectory = async (
71
+ basePath: string,
72
+ requestedPath: string,
73
+ vfsId?: string
74
+ ) => {
75
+ try {
76
+ const resolved = resolveSandboxPath(basePath, requestedPath);
77
+ const stats = await statVirtualFs(resolved, vfsId);
78
+ return stats.isDirectory;
79
+ } catch {
80
+ return false;
80
81
  }
81
-
82
- export async function isFile(basePath: string, requestedPath: string, vfsId?: string) {
83
- const resolvedResult = Result.try(() => resolvePath(basePath, requestedPath));
84
- if (!Result.isOk(resolvedResult)) return false;
85
- const result = await Result.tryPromise(() => VirtualFs.stat(resolvedResult.value, vfsId));
86
- return result.match({
87
- ok: (stats) => stats.isFile,
88
- err: () => false
89
- });
82
+ };
83
+
84
+ export const sandboxPathIsFile = async (
85
+ basePath: string,
86
+ requestedPath: string,
87
+ vfsId?: string
88
+ ) => {
89
+ try {
90
+ const resolved = resolveSandboxPath(basePath, requestedPath);
91
+ const stats = await statVirtualFs(resolved, vfsId);
92
+ return stats.isFile;
93
+ } catch {
94
+ return false;
90
95
  }
91
-
92
- export async function validatePath(basePath: string, requestedPath: string, vfsId?: string) {
93
- const resolved = resolvePath(basePath, requestedPath);
94
- if (!(await VirtualFs.exists(resolved, vfsId))) {
95
- throw new PathNotFoundError(requestedPath);
96
- }
97
- return resolved;
96
+ };
97
+
98
+ export const validateSandboxPath = async (
99
+ basePath: string,
100
+ requestedPath: string,
101
+ vfsId?: string
102
+ ) => {
103
+ const resolved = resolveSandboxPath(basePath, requestedPath);
104
+ if (!(await existsInVirtualFs(resolved, vfsId))) {
105
+ throw new PathNotFoundError(requestedPath);
98
106
  }
107
+ return resolved;
108
+ };
99
109
 
100
- export function getRelativePath(basePath: string, resolvedPath: string): string {
101
- return posix.relative(basePath, resolvedPath);
102
- }
103
- }
110
+ export const getSandboxRelativePath = (basePath: string, resolvedPath: string): string =>
111
+ posix.relative(basePath, resolvedPath);