deepagents-s3-backend 1.8.2 → 1.8.4
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/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));let c=require(`@aws-sdk/client-s3`),l=require(`micromatch`);l=s(l);var u=class{s3Client;bucketName;cwd;maxFileSizeBytes;getErrorMessage(e,t){return e instanceof Error&&e.message?e.message:typeof e==`string`&&e.length>0?e:t}constructor(e={}){if(this.s3Client=new c.S3Client(e?.s3ClientConfig||{}),this.cwd=e?.rootPrefix||`/`,this.maxFileSizeBytes=e?.maxFileSizeMb?e?.maxFileSizeMb*1024*1024:void 0,e?.bucketName)this.bucketName=e.bucketName;else throw Error(`bucketName is required in options`)}resolvePath(e){if(typeof e!=`string`)throw Error(`Path must be a string`);let t=e.replace(/\\/g,`/`);if(t.includes(`\0`))throw Error(`Path contains invalid characters`);let n=this.cwd.replace(/\\/g,`/`).split(`/`).filter(Boolean);for(let e of n)if(e===`.`||e===`..`)throw Error(`Invalid rootPrefix; path traversal is not allowed`);let r=t.split(`/`).filter(e=>e.length>0&&e!==`.`);for(let e of r){if(e===`..`)throw Error(`Path traversal is not allowed`);if(e===`~`||e.startsWith(`~`))throw Error(`Home-relative paths are not allowed`);if(e.includes(`\0`))throw Error(`Path contains invalid characters`)}return`/${[...n,...r].join(`/`)}`}async lsInfo(e){try{let t=this.resolvePath(e),n=t===`/`?``:`${t.slice(1).replace(/\/+$/,``)}/`,r=await this.dangerouslyListAllObjects({Bucket:this.bucketName,Prefix:n,Delimiter:`/`}),i=(r.Contents||[]).filter(e=>{let t=e.Key||``;if(!t.startsWith(n))return!1;let r=t.slice(n.length);return r.length>0&&!r.includes(`/`)}).map(e=>({path:`/${e.Key}`,is_dir:!1,size:e.Size,modified_at:e.LastModified?.toISOString()})),a=(r.CommonPrefixes||[]).map(e=>e.Prefix||``).filter(e=>{if(!e.startsWith(n))return!1;let t=e.slice(n.length).replace(/\/+$/,``);return t.length>0&&!t.includes(`/`)}).map(e=>({path:`/${e}`,is_dir:!0,size:void 0,modified_at:void 0}));return[...i,...a]}catch{return[]}}async read(e,t=0,n=500){try{return(await this.readRaw(e)).content.slice(t,t+n).map((e,n)=>`${t+n+1}: ${e}`).join(`
|
|
2
|
-
`)}catch(e){return this.getErrorMessage(e,`Unknown error during read operation`)}}async readRaw(e){try{let t=this.resolvePath(e),[n,r]=await Promise.all([this.s3Client.send(new c.GetObjectCommand({Bucket:this.bucketName,Key:t.slice(1)})),this.s3Client.send(new c.HeadObjectCommand({Bucket:this.bucketName,Key:t.slice(1)}))]);if(!n.Body)return{content:[],created_at:``,modified_at:``};let i={size:r.ContentLength,created_at:r.Metadata?.CreatedAt||r.LastModified?.toISOString()||``,modified_at:r.LastModified?.toISOString()||``}
|
|
2
|
+
`)}catch(e){return this.getErrorMessage(e,`Unknown error during read operation`)}}async readRaw(e){try{let t=this.resolvePath(e),[n,r]=await Promise.all([this.s3Client.send(new c.GetObjectCommand({Bucket:this.bucketName,Key:t.slice(1)})),this.s3Client.send(new c.HeadObjectCommand({Bucket:this.bucketName,Key:t.slice(1)}))]);if(!n.Body)return{content:[],created_at:``,modified_at:``};let i={size:r.ContentLength,created_at:r.Metadata?.CreatedAt||r.LastModified?.toISOString()||``,modified_at:r.LastModified?.toISOString()||``};return{content:(await n.Body.transformToString(`utf-8`)).split(/\r?\n/),created_at:i.created_at,modified_at:i.modified_at}}catch(e){return{content:[this.getErrorMessage(e,`Unknown error during readRaw operation`)],created_at:``,modified_at:``}}}async grepRaw(e,t,n){try{let r=t?this.resolvePath(t):this.cwd,i=r===`/`?``:r.slice(1).replace(/^\/+/,``).replace(/\/+$/,``),a=((await this.dangerouslyListAllObjects({Bucket:this.bucketName,Prefix:i})).Contents||[]).filter(e=>{let t=e.Key||``;return t?n?this.matchesGlob(t,i,n):!0:!1}).filter(e=>!this.maxFileSizeBytes||e.Size===void 0?!0:e.Size<=this.maxFileSizeBytes);return(await Promise.all(a.map(async t=>{let n=t.Key;if(!n)return[];try{let t=await this.s3Client.send(new c.GetObjectCommand({Bucket:this.bucketName,Key:n}));if(!t.Body)return[];let r=(await t.Body.transformToString(`utf-8`)).split(/\r?\n/),i=[];for(let t=0;t<r.length;t++){let a=r[t];a.includes(e)&&i.push({path:`/${n}`,line:t+1,text:a})}return i}catch{return[]}}))).flat()}catch(e){return this.getErrorMessage(e,`Unknown error during grep operation`)}}matchesGlob(e,t,n){let r=t&&e.startsWith(t)?e.slice(t.length).replace(/^\/+/,``):e,i=e.split(`/`).pop()||e;return l.isMatch(r,n,{dot:!0})||l.isMatch(i,n,{dot:!0})||l.isMatch(e,n,{dot:!0})}async globInfo(e,t){try{let n=this.resolvePath(t??`/`),r=n===`/`?``:n.slice(1).replace(/^\/+/,``).replace(/\/+$/,``);return((await this.dangerouslyListAllObjects({Bucket:this.bucketName,Prefix:r})).Contents||[]).filter(t=>{let n=t.Key||``;return!n||n.endsWith(`/`)?!1:this.matchesGlob(n,r,e)}).map(e=>({path:`/${e.Key}`,is_dir:!1,size:e.Size,modified_at:e.LastModified?.toISOString()}))}catch(e){return[{path:this.getErrorMessage(e,`Unknown error during glob operation`),is_dir:!1,size:void 0,modified_at:void 0}]}}async write(e,t){try{let n=this.resolvePath(e);try{await this.s3Client.send(new c.HeadObjectCommand({Bucket:this.bucketName,Key:n.slice(1)}))}catch{}let r=new Date().toISOString();return await this.s3Client.send(new c.PutObjectCommand({Bucket:this.bucketName,Key:n.slice(1),Body:t,Metadata:{CreatedAt:r}})),{path:n,filesUpdate:null}}catch(e){return{error:this.getErrorMessage(e,`Unknown error during write operation`)}}}async edit(e,t,n,r=!1){try{let i=this.resolvePath(e),a=await this.readRaw(i),o=a.content.join(`
|
|
3
3
|
`);if(!o.includes(t))return{error:`The string "${t}" was not found in the file.`};let s=r?o.split(t).length-1:1,l=r?o.split(t).join(n):o.replace(t,n);return await this.s3Client.send(new c.PutObjectCommand({Bucket:this.bucketName,Key:i.slice(1),Body:l,Metadata:{CreatedAt:a.created_at||new Date().toISOString()}})),{path:i,filesUpdate:null,occurrences:s}}catch(e){return{error:this.getErrorMessage(e,`Unknown error during edit operation`)}}}async uploadFiles(e){let t=[];return await Promise.all(e.map(async([e,n])=>{try{let r=this.resolvePath(e);await this.s3Client.send(new c.PutObjectCommand({Bucket:this.bucketName,Key:r.slice(1),Body:n,Metadata:{CreatedAt:new Date().toISOString()}})),t.push({path:r,error:null})}catch(n){t.push({path:e,error:this.mapError(n)})}})),t}async downloadFiles(e){return await Promise.all(e.map(async e=>{try{let t=this.resolvePath(e),n=await this.s3Client.send(new c.GetObjectCommand({Bucket:this.bucketName,Key:t.slice(1)}));if(!n.Body)return{path:e,content:null,error:`file_not_found`};let r=n.Body,i;if(typeof r.transformToByteArray==`function`)i=await r.transformToByteArray();else if(typeof r.transformToString==`function`)i=new TextEncoder().encode(await r.transformToString(`utf-8`));else if(typeof r.transformToWebStream==`function`){let e=r.transformToWebStream().getReader(),t=[],n=0;for(;;){let{value:r,done:i}=await e.read();if(i)break;let a=r??new Uint8Array;t.push(a),n+=a.length}let a=new Uint8Array(n),o=0;for(let e of t)a.set(e,o),o+=e.length;i=a}else return{path:e,content:null,error:`invalid_path`};return{path:e,content:i,error:null}}catch(t){return{path:e,content:null,error:this.mapError(t)}}}))}mapError(e){let t=e,n=t?.code,r=t?.name,i=t?.message;return n===`NoSuchKey`||n===`ENOENT`||r===`NoSuchKey`||i===`NoSuchKey`?`file_not_found`:n===`AccessDenied`||n===`Forbidden`||n===`EACCES`||r===`AccessDenied`||i===`AccessDenied`||i===`Forbidden`?`permission_denied`:n===`EISDIR`||i===`EISDIR`?`is_directory`:`invalid_path`}async dangerouslyListAllObjects(e){let t=!0,n,r=[],i=[],a,o,s,l;for(;t;){let u=await this.s3Client.send(new c.ListObjectsV2Command({...e,ContinuationToken:n}));a===void 0&&u.Name!==void 0&&(a=u.Name),o===void 0&&u.Prefix!==void 0&&(o=u.Prefix),s===void 0&&u.Delimiter!==void 0&&(s=u.Delimiter),l===void 0&&u.MaxKeys!==void 0&&(l=u.MaxKeys),u.Contents?.length&&r.push(...u.Contents),u.CommonPrefixes?.length&&i.push(...u.CommonPrefixes),t=u.IsTruncated===!0,n=u.NextContinuationToken}return{Name:a,Prefix:o??e.Prefix,Delimiter:s??e.Delimiter,MaxKeys:l??e.MaxKeys,Contents:r,CommonPrefixes:i}}};exports.S3Backend=u;
|
|
4
4
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["S3Client","GetObjectCommand","HeadObjectCommand","m","PutObjectCommand","ListObjectsV2Command"],"sources":["../src/index.ts"],"sourcesContent":["import {\n S3Client,\n ListObjectsV2Command,\n ListObjectsV2CommandInput,\n GetObjectCommand,\n HeadObjectCommand,\n _Object,\n ListObjectsV2CommandOutput,\n PutObjectCommand,\n} from \"@aws-sdk/client-s3\";\nimport type { NodeJsRuntimeStreamingBlobPayloadOutputTypes } from \"@smithy/types\";\nimport {\n EditResult,\n FileData,\n FileDownloadResponse,\n FileInfo,\n FileUploadResponse,\n GrepMatch,\n WriteResult,\n BackendProtocol,\n} from \"deepagents\";\nimport * as m from \"micromatch\";\n\nexport class S3Backend implements BackendProtocol {\n private s3Client: S3Client;\n protected bucketName: string;\n protected cwd: string;\n private maxFileSizeBytes: number | undefined;\n\n private getErrorMessage(error: unknown, fallback: string): string {\n if (error instanceof Error && error.message) {\n return error.message;\n }\n\n if (typeof error === \"string\" && error.length > 0) {\n return error;\n }\n\n return fallback;\n }\n\n constructor(\n options: {\n s3ClientConfig?: ConstructorParameters<typeof S3Client>;\n bucketName?: string;\n rootPrefix?: string;\n maxFileSizeMb?: number;\n } = {},\n ) {\n this.s3Client = new S3Client(options?.s3ClientConfig || {});\n this.cwd = options?.rootPrefix || \"/\";\n this.maxFileSizeBytes = options?.maxFileSizeMb\n ? options?.maxFileSizeMb * 1024 * 1024\n : undefined;\n\n if (!options?.bucketName) {\n throw new Error(\"bucketName is required in options\");\n } else {\n this.bucketName = options.bucketName;\n }\n }\n\n /**\n * Resolve a path with security checks. This behaves similarly to the original\n * `resolvePath` in the `FilesystemBackend`, but it always act in virtual mode.\n *\n * @param key - The S3 object key to resolve. It should be an absolute path starting with /, but if not, it will be treated as relative to the root.\n * @returns Resolve the key to a normalized S3 object key.\n * @throws Error if the key contains path traversal patterns or is invalid.\n */\n private resolvePath(key: string): string {\n if (typeof key !== \"string\") {\n throw new Error(\"Path must be a string\");\n }\n\n const normalizedInput = key.replace(/\\\\/g, \"/\");\n if (normalizedInput.includes(\"\\0\")) {\n throw new Error(\"Path contains invalid characters\");\n }\n\n const rootSegments = this.cwd\n .replace(/\\\\/g, \"/\")\n .split(\"/\")\n .filter(Boolean);\n\n for (const segment of rootSegments) {\n if (segment === \".\" || segment === \"..\") {\n throw new Error(\"Invalid rootPrefix; path traversal is not allowed\");\n }\n }\n\n const inputSegments = normalizedInput\n .split(\"/\")\n .filter((segment) => segment.length > 0 && segment !== \".\");\n\n for (const segment of inputSegments) {\n if (segment === \"..\") {\n throw new Error(\"Path traversal is not allowed\");\n }\n\n if (segment === \"~\" || segment.startsWith(\"~\")) {\n throw new Error(\"Home-relative paths are not allowed\");\n }\n\n if (segment.includes(\"\\0\")) {\n throw new Error(\"Path contains invalid characters\");\n }\n }\n\n const fullPathSegments = [...rootSegments, ...inputSegments];\n return `/${fullPathSegments.join(\"/\")}`;\n }\n\n /**\n * List files and directories in the specified directory (non-recursive).\n *\n * @param dirPath - Absolute directory path to list files from\n * @returns List of FileInfo objects for files and directories directly in the directory.\n * Directories have a trailing / in their path and is_dir=true.\n */\n async lsInfo(dirPath: string): Promise<FileInfo[]> {\n try {\n const resolvedPath = this.resolvePath(dirPath);\n const listPrefix =\n resolvedPath === \"/\"\n ? \"\"\n : `${resolvedPath.slice(1).replace(/\\/+$/, \"\")}/`;\n const result = await this.dangerouslyListAllObjects({\n Bucket: this.bucketName,\n Prefix: listPrefix,\n Delimiter: \"/\",\n });\n\n const directFiles = (result.Contents || [])\n .filter((obj) => {\n const key = obj.Key || \"\";\n if (!key.startsWith(listPrefix)) {\n return false;\n }\n\n const relativeKey = key.slice(listPrefix.length);\n return relativeKey.length > 0 && !relativeKey.includes(\"/\");\n })\n .map((obj) => ({\n path: `/${obj.Key}`,\n is_dir: false,\n size: obj.Size,\n modified_at: obj.LastModified?.toISOString(),\n }));\n\n const directDirectories = (result.CommonPrefixes || [])\n .map((prefix) => prefix.Prefix || \"\")\n .filter((prefix) => {\n if (!prefix.startsWith(listPrefix)) {\n return false;\n }\n\n const relativePrefix = prefix\n .slice(listPrefix.length)\n .replace(/\\/+$/, \"\");\n\n return relativePrefix.length > 0 && !relativePrefix.includes(\"/\");\n })\n .map(\n (prefix) =>\n ({\n path: `/${prefix}`,\n is_dir: true,\n size: undefined,\n modified_at: undefined,\n }) as FileInfo,\n );\n\n return [...directFiles, ...directDirectories];\n } catch {\n return [];\n }\n }\n\n /**\n * Read file content with line numbers.\n *\n * @param filePath - Absolute or relative file path\n * @param offset - Line offset to start reading from (0-indexed)\n * @param limit - Maximum number of lines to read\n * @returns Formatted file content with line numbers, or error message\n */\n async read(\n filePath: string,\n offset: number = 0,\n limit: number = 500,\n ): Promise<string> {\n try {\n const fileData = await this.readRaw(filePath);\n const selectedLines = fileData.content.slice(offset, offset + limit);\n return selectedLines\n .map((line, index) => `${offset + index + 1}: ${line}`)\n .join(\"\\n\");\n } catch (error) {\n return this.getErrorMessage(error, \"Unknown error during read operation\");\n }\n }\n\n /**\n * Read file content as raw FileData.\n *\n * @param filePath - Absolute file path\n * @returns Raw file content as FileData\n */\n async readRaw(filePath: string): Promise<FileData> {\n try {\n const resolvedPath = this.resolvePath(filePath);\n\n const [getObjectResult, headObjectResult] = await Promise.all([\n this.s3Client.send(\n new GetObjectCommand({\n Bucket: this.bucketName,\n Key: resolvedPath.slice(1),\n }),\n ),\n this.s3Client.send(\n new HeadObjectCommand({\n Bucket: this.bucketName,\n Key: resolvedPath.slice(1),\n }),\n ),\n ]);\n\n if (!getObjectResult.Body) {\n return { content: [], created_at: \"\", modified_at: \"\" };\n }\n\n const headInfo = {\n size: headObjectResult.ContentLength,\n created_at:\n headObjectResult.Metadata?.CreatedAt ||\n headObjectResult.LastModified?.toISOString() ||\n \"\",\n modified_at: headObjectResult.LastModified?.toISOString() || \"\",\n };\n\n const stream = getObjectResult.Body as ReadableStream;\n const reader = stream.getReader();\n const decoder = new TextDecoder(\"utf-8\");\n let content = \"\";\n let done = false;\n\n while (!done) {\n const { value, done: streamDone } = await reader.read();\n if (value) {\n content += decoder.decode(value, { stream: true });\n }\n done = streamDone;\n }\n\n content += decoder.decode();\n\n const lines = content.split(/\\r?\\n/);\n\n return {\n content: lines,\n created_at: headInfo.created_at,\n modified_at: headInfo.modified_at,\n };\n } catch (error) {\n return {\n content: [\n this.getErrorMessage(error, \"Unknown error during readRaw operation\"),\n ],\n created_at: \"\",\n modified_at: \"\",\n };\n }\n }\n\n /**\n * Search for a literal text pattern in files (recursive).\n *\n * @param pattern - Literal string to search for (NOT regex).\n * @param dirPath - Directory or file path to search in. Defaults to current directory.\n * @param glob - Optional glob pattern to filter which files to search.\n * @returns List of GrepMatch dicts containing path, line number, and matched text.\n */\n async grepRaw(\n pattern: string,\n dirPath?: string | null,\n glob?: string | null,\n ): Promise<GrepMatch[] | string> {\n try {\n const searchPath = dirPath ? this.resolvePath(dirPath) : this.cwd;\n const prefix =\n searchPath === \"/\"\n ? \"\"\n : searchPath.slice(1).replace(/^\\/+/, \"\").replace(/\\/+$/, \"\");\n\n const listedObjects = await this.dangerouslyListAllObjects({\n Bucket: this.bucketName,\n Prefix: prefix,\n });\n\n const matchingObjects = (listedObjects.Contents || [])\n .filter((obj) => {\n const key = obj.Key || \"\";\n if (!key) {\n return false;\n }\n\n if (!glob) {\n return true;\n }\n\n return this.matchesGlob(key, prefix, glob);\n })\n .filter((obj) => {\n if (!this.maxFileSizeBytes || obj.Size === undefined) {\n return true;\n }\n\n return obj.Size <= this.maxFileSizeBytes;\n });\n\n const objectMatches = await Promise.all(\n matchingObjects.map(async (obj) => {\n const key = obj.Key;\n if (!key) {\n return [] as GrepMatch[];\n }\n\n try {\n const getObjectResult = await this.s3Client.send(\n new GetObjectCommand({\n Bucket: this.bucketName,\n Key: key,\n }),\n );\n\n if (!getObjectResult.Body) {\n return [] as GrepMatch[];\n }\n\n const content = await getObjectResult.Body.transformToString();\n const lines = content.split(/\\r?\\n/);\n const fileMatches: GrepMatch[] = [];\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n if (!line.includes(pattern)) {\n continue;\n }\n\n fileMatches.push({\n path: `/${key}`,\n line: i + 1,\n text: line,\n });\n }\n\n return fileMatches;\n } catch {\n return [] as GrepMatch[];\n }\n }),\n );\n\n return objectMatches.flat();\n } catch (error) {\n return this.getErrorMessage(error, \"Unknown error during grep operation\");\n }\n }\n\n private matchesGlob(\n key: string,\n prefix: string,\n globPattern: string,\n ): boolean {\n const relativeToPrefix =\n prefix && key.startsWith(prefix)\n ? key.slice(prefix.length).replace(/^\\/+/, \"\")\n : key;\n const fileName = key.split(\"/\").pop() || key;\n\n return (\n m.isMatch(relativeToPrefix, globPattern, { dot: true }) ||\n m.isMatch(fileName, globPattern, { dot: true }) ||\n m.isMatch(key, globPattern, { dot: true })\n );\n }\n\n /**\n * Structured glob matching returning FileInfo objects.\n *\n * @param pattern - Glob pattern (e.g., `*.py`, `**\\/*.ts`)\n * @param path - Base path to search from (default: \"/\")\n * @returns List of FileInfo objects matching the pattern\n */\n async globInfo(pattern: string, path?: string): Promise<FileInfo[]> {\n try {\n const searchPath = this.resolvePath(path ?? \"/\");\n const prefix =\n searchPath === \"/\"\n ? \"\"\n : searchPath.slice(1).replace(/^\\/+/, \"\").replace(/\\/+$/, \"\");\n\n const listedObjects = await this.dangerouslyListAllObjects({\n Bucket: this.bucketName,\n Prefix: prefix,\n });\n\n return (listedObjects.Contents || [])\n .filter((obj) => {\n const key = obj.Key || \"\";\n if (!key || key.endsWith(\"/\")) {\n return false;\n }\n\n return this.matchesGlob(key, prefix, pattern);\n })\n .map((obj) => ({\n path: `/${obj.Key}`,\n is_dir: false,\n size: obj.Size,\n modified_at: obj.LastModified?.toISOString(),\n }));\n } catch (error) {\n return [\n {\n path: this.getErrorMessage(\n error,\n \"Unknown error during glob operation\",\n ),\n is_dir: false,\n size: undefined,\n modified_at: undefined,\n },\n ];\n }\n }\n\n /**\n * Create a new file.\n *\n * @param filePath - Absolute file path\n * @param content - File content as string\n * @returns WriteResult with error populated on failure\n */\n async write(filePath: string, content: string): Promise<WriteResult> {\n try {\n const resolvedPath = this.resolvePath(filePath);\n\n const getObjectResult = await this.s3Client.send(\n new GetObjectCommand({\n Bucket: this.bucketName,\n Key: resolvedPath.slice(1),\n }),\n );\n\n if (getObjectResult.Body) {\n return {\n error: `File already exists at path: ${resolvedPath}`,\n };\n }\n\n const creationTime = new Date().toISOString();\n await this.s3Client.send(\n new PutObjectCommand({\n Bucket: this.bucketName,\n Key: resolvedPath.slice(1),\n Body: content,\n Metadata: {\n CreatedAt: creationTime,\n },\n }),\n );\n\n return {\n path: resolvedPath,\n filesUpdate: null,\n };\n } catch (error) {\n return {\n error: this.getErrorMessage(\n error,\n \"Unknown error during write operation\",\n ),\n };\n }\n }\n\n /**\n * Edit a file by replacing string occurrences.\n *\n * @param filePath - Absolute file path\n * @param oldString - String to find and replace\n * @param newString - Replacement string\n * @param replaceAll - If true, replace all occurrences (default: false)\n * @returns EditResult with error, path, filesUpdate, and occurrences\n */\n async edit(\n filePath: string,\n oldString: string,\n newString: string,\n replaceAll: boolean = false,\n ): Promise<EditResult> {\n try {\n const resolvedPath = this.resolvePath(filePath);\n const fileData = await this.readRaw(resolvedPath);\n const content = fileData.content.join(\"\\n\");\n\n if (!content.includes(oldString)) {\n return {\n error: `The string \"${oldString}\" was not found in the file.`,\n };\n }\n\n const occurrences = replaceAll ? content.split(oldString).length - 1 : 1;\n\n const newContent = replaceAll\n ? content.split(oldString).join(newString)\n : content.replace(oldString, newString);\n\n await this.s3Client.send(\n new PutObjectCommand({\n Bucket: this.bucketName,\n Key: resolvedPath.slice(1),\n Body: newContent,\n Metadata: {\n CreatedAt: fileData.created_at || new Date().toISOString(),\n },\n }),\n );\n\n return {\n path: resolvedPath,\n filesUpdate: null,\n occurrences,\n };\n } catch (error) {\n return {\n error: this.getErrorMessage(\n error,\n \"Unknown error during edit operation\",\n ),\n };\n }\n }\n\n /**\n * Upload multiple files.\n * Optional - backends that don't support file upload can omit this.\n *\n * @param files - List of [path, content] tuples to upload\n * @returns List of FileUploadResponse objects, one per input file\n */\n async uploadFiles(\n files: Array<[string, Uint8Array]>,\n ): Promise<FileUploadResponse[]> {\n const uploadResults: FileUploadResponse[] = [];\n\n await Promise.all(\n files.map(async ([filePath, content]) => {\n try {\n const resolvedPath = this.resolvePath(filePath);\n await this.s3Client.send(\n new PutObjectCommand({\n Bucket: this.bucketName,\n Key: resolvedPath.slice(1),\n Body: content,\n Metadata: {\n CreatedAt: new Date().toISOString(),\n },\n }),\n );\n\n uploadResults.push({\n path: resolvedPath,\n error: null,\n });\n } catch (error) {\n uploadResults.push({\n path: filePath,\n error: this.mapError(error),\n });\n }\n }),\n );\n\n return uploadResults;\n }\n\n /**\n * Download multiple files.\n * Optional - backends that don't support file download can omit this.\n *\n * @param paths - List of file paths to download\n * @returns List of FileDownloadResponse objects, one per input path\n */\n async downloadFiles(paths: string[]): Promise<FileDownloadResponse[]> {\n const downloadResults = await Promise.all(\n paths.map(async (filePath) => {\n try {\n const resolvedPath = this.resolvePath(filePath);\n const getObjectResult = await this.s3Client.send(\n new GetObjectCommand({\n Bucket: this.bucketName,\n Key: resolvedPath.slice(1),\n }),\n );\n\n if (!getObjectResult.Body) {\n return {\n path: filePath,\n content: null,\n error: \"file_not_found\" as const,\n };\n }\n\n const body =\n getObjectResult.Body as NodeJsRuntimeStreamingBlobPayloadOutputTypes;\n\n let content: Uint8Array;\n\n if (typeof body.transformToByteArray === \"function\") {\n content = await body.transformToByteArray();\n } else if (typeof body.transformToString === \"function\") {\n content = new TextEncoder().encode(\n await body.transformToString(\"utf-8\"),\n );\n } else if (typeof body.transformToWebStream === \"function\") {\n const reader = body.transformToWebStream().getReader();\n const chunks: Uint8Array[] = [];\n let totalLength = 0;\n\n while (true) {\n const { value, done } = await reader.read();\n if (done) {\n break;\n }\n\n const chunk = value ?? new Uint8Array();\n chunks.push(chunk);\n totalLength += chunk.length;\n }\n\n const merged = new Uint8Array(totalLength);\n let offset = 0;\n for (const chunk of chunks) {\n merged.set(chunk, offset);\n offset += chunk.length;\n }\n\n content = merged;\n } else {\n return {\n path: filePath,\n content: null,\n error: \"invalid_path\" as const,\n };\n }\n\n return {\n path: filePath,\n content,\n error: null,\n };\n } catch (error) {\n return {\n path: filePath,\n content: null,\n error: this.mapError(error),\n };\n }\n }),\n );\n\n return downloadResults;\n }\n\n private mapError(\n error: unknown,\n ): FileDownloadResponse[\"error\"] | FileUploadResponse[\"error\"] {\n const candidate = error as {\n name?: string;\n code?: string;\n message?: string;\n };\n const code = candidate?.code;\n const name = candidate?.name;\n const message = candidate?.message;\n\n if (\n code === \"NoSuchKey\" ||\n code === \"ENOENT\" ||\n name === \"NoSuchKey\" ||\n message === \"NoSuchKey\"\n ) {\n return \"file_not_found\";\n }\n\n if (\n code === \"AccessDenied\" ||\n code === \"Forbidden\" ||\n code === \"EACCES\" ||\n name === \"AccessDenied\" ||\n message === \"AccessDenied\" ||\n message === \"Forbidden\"\n ) {\n return \"permission_denied\";\n }\n\n if (code === \"EISDIR\" || message === \"EISDIR\") {\n return \"is_directory\";\n }\n\n return \"invalid_path\";\n }\n\n async dangerouslyListAllObjects(\n param: Omit<ListObjectsV2CommandInput, \"ContinuationToken\">,\n ): Promise<\n Pick<\n ListObjectsV2CommandOutput,\n | \"Name\"\n | \"Contents\"\n | \"CommonPrefixes\"\n | \"Prefix\"\n | \"Delimiter\"\n | \"MaxKeys\"\n >\n > {\n let isTruncated = true;\n let continuationToken: string | undefined;\n\n const allContents: _Object[] = [];\n const allCommonPrefixes: NonNullable<\n ListObjectsV2CommandOutput[\"CommonPrefixes\"]\n > = [];\n\n let name: ListObjectsV2CommandOutput[\"Name\"];\n let prefix: ListObjectsV2CommandOutput[\"Prefix\"];\n let delimiter: ListObjectsV2CommandOutput[\"Delimiter\"];\n let maxKeys: ListObjectsV2CommandOutput[\"MaxKeys\"];\n\n while (isTruncated) {\n const result = await this.s3Client.send(\n new ListObjectsV2Command({\n ...param,\n ContinuationToken: continuationToken,\n }),\n );\n\n if (name === undefined && result.Name !== undefined) {\n name = result.Name;\n }\n\n if (prefix === undefined && result.Prefix !== undefined) {\n prefix = result.Prefix;\n }\n\n if (delimiter === undefined && result.Delimiter !== undefined) {\n delimiter = result.Delimiter;\n }\n\n if (maxKeys === undefined && result.MaxKeys !== undefined) {\n maxKeys = result.MaxKeys;\n }\n\n if (result.Contents?.length) {\n allContents.push(...result.Contents);\n }\n\n if (result.CommonPrefixes?.length) {\n allCommonPrefixes.push(...result.CommonPrefixes);\n }\n\n isTruncated = result.IsTruncated === true;\n continuationToken = result.NextContinuationToken;\n }\n\n return {\n Name: name,\n Prefix: prefix ?? param.Prefix,\n Delimiter: delimiter ?? param.Delimiter,\n MaxKeys: maxKeys ?? param.MaxKeys,\n Contents: allContents,\n CommonPrefixes: allCommonPrefixes,\n };\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["S3Client","GetObjectCommand","HeadObjectCommand","m","PutObjectCommand","ListObjectsV2Command"],"sources":["../src/index.ts"],"sourcesContent":["import {\n S3Client,\n ListObjectsV2Command,\n ListObjectsV2CommandInput,\n GetObjectCommand,\n HeadObjectCommand,\n _Object,\n ListObjectsV2CommandOutput,\n PutObjectCommand,\n S3ClientConfig,\n} from \"@aws-sdk/client-s3\";\nimport type { NodeJsRuntimeStreamingBlobPayloadOutputTypes } from \"@smithy/types\";\nimport {\n EditResult,\n FileData,\n FileDownloadResponse,\n FileInfo,\n FileUploadResponse,\n GrepMatch,\n WriteResult,\n BackendProtocol,\n} from \"deepagents\";\nimport * as m from \"micromatch\";\n\nexport class S3Backend implements BackendProtocol {\n private s3Client: S3Client;\n protected bucketName: string;\n protected cwd: string;\n private maxFileSizeBytes: number | undefined;\n\n private getErrorMessage(error: unknown, fallback: string): string {\n if (error instanceof Error && error.message) {\n return error.message;\n }\n\n if (typeof error === \"string\" && error.length > 0) {\n return error;\n }\n\n return fallback;\n }\n\n constructor(\n options: {\n s3ClientConfig?: S3ClientConfig;\n bucketName?: string;\n rootPrefix?: string;\n maxFileSizeMb?: number;\n } = {},\n ) {\n this.s3Client = new S3Client(options?.s3ClientConfig || {});\n this.cwd = options?.rootPrefix || \"/\";\n this.maxFileSizeBytes = options?.maxFileSizeMb\n ? options?.maxFileSizeMb * 1024 * 1024\n : undefined;\n\n if (!options?.bucketName) {\n throw new Error(\"bucketName is required in options\");\n } else {\n this.bucketName = options.bucketName;\n }\n }\n\n /**\n * Resolve a path with security checks. This behaves similarly to the original\n * `resolvePath` in the `FilesystemBackend`, but it always act in virtual mode.\n *\n * @param key - The S3 object key to resolve. It should be an absolute path starting with /, but if not, it will be treated as relative to the root.\n * @returns Resolve the key to a normalized S3 object key.\n * @throws Error if the key contains path traversal patterns or is invalid.\n */\n private resolvePath(key: string): string {\n if (typeof key !== \"string\") {\n throw new Error(\"Path must be a string\");\n }\n\n const normalizedInput = key.replace(/\\\\/g, \"/\");\n if (normalizedInput.includes(\"\\0\")) {\n throw new Error(\"Path contains invalid characters\");\n }\n\n const rootSegments = this.cwd\n .replace(/\\\\/g, \"/\")\n .split(\"/\")\n .filter(Boolean);\n\n for (const segment of rootSegments) {\n if (segment === \".\" || segment === \"..\") {\n throw new Error(\"Invalid rootPrefix; path traversal is not allowed\");\n }\n }\n\n const inputSegments = normalizedInput\n .split(\"/\")\n .filter((segment) => segment.length > 0 && segment !== \".\");\n\n for (const segment of inputSegments) {\n if (segment === \"..\") {\n throw new Error(\"Path traversal is not allowed\");\n }\n\n if (segment === \"~\" || segment.startsWith(\"~\")) {\n throw new Error(\"Home-relative paths are not allowed\");\n }\n\n if (segment.includes(\"\\0\")) {\n throw new Error(\"Path contains invalid characters\");\n }\n }\n\n const fullPathSegments = [...rootSegments, ...inputSegments];\n return `/${fullPathSegments.join(\"/\")}`;\n }\n\n /**\n * List files and directories in the specified directory (non-recursive).\n *\n * @param dirPath - Absolute directory path to list files from\n * @returns List of FileInfo objects for files and directories directly in the directory.\n * Directories have a trailing / in their path and is_dir=true.\n */\n async lsInfo(dirPath: string): Promise<FileInfo[]> {\n try {\n const resolvedPath = this.resolvePath(dirPath);\n const listPrefix =\n resolvedPath === \"/\"\n ? \"\"\n : `${resolvedPath.slice(1).replace(/\\/+$/, \"\")}/`;\n const result = await this.dangerouslyListAllObjects({\n Bucket: this.bucketName,\n Prefix: listPrefix,\n Delimiter: \"/\",\n });\n\n const directFiles = (result.Contents || [])\n .filter((obj) => {\n const key = obj.Key || \"\";\n if (!key.startsWith(listPrefix)) {\n return false;\n }\n\n const relativeKey = key.slice(listPrefix.length);\n return relativeKey.length > 0 && !relativeKey.includes(\"/\");\n })\n .map((obj) => ({\n path: `/${obj.Key}`,\n is_dir: false,\n size: obj.Size,\n modified_at: obj.LastModified?.toISOString(),\n }));\n\n const directDirectories = (result.CommonPrefixes || [])\n .map((prefix) => prefix.Prefix || \"\")\n .filter((prefix) => {\n if (!prefix.startsWith(listPrefix)) {\n return false;\n }\n\n const relativePrefix = prefix\n .slice(listPrefix.length)\n .replace(/\\/+$/, \"\");\n\n return relativePrefix.length > 0 && !relativePrefix.includes(\"/\");\n })\n .map(\n (prefix) =>\n ({\n path: `/${prefix}`,\n is_dir: true,\n size: undefined,\n modified_at: undefined,\n }) as FileInfo,\n );\n\n return [...directFiles, ...directDirectories];\n } catch {\n return [];\n }\n }\n\n /**\n * Read file content with line numbers.\n *\n * @param filePath - Absolute or relative file path\n * @param offset - Line offset to start reading from (0-indexed)\n * @param limit - Maximum number of lines to read\n * @returns Formatted file content with line numbers, or error message\n */\n async read(\n filePath: string,\n offset: number = 0,\n limit: number = 500,\n ): Promise<string> {\n try {\n const fileData = await this.readRaw(filePath);\n const selectedLines = fileData.content.slice(offset, offset + limit);\n return selectedLines\n .map((line, index) => `${offset + index + 1}: ${line}`)\n .join(\"\\n\");\n } catch (error) {\n return this.getErrorMessage(error, \"Unknown error during read operation\");\n }\n }\n\n /**\n * Read file content as raw FileData.\n *\n * @param filePath - Absolute file path\n * @returns Raw file content as FileData\n */\n async readRaw(filePath: string): Promise<FileData> {\n try {\n const resolvedPath = this.resolvePath(filePath);\n\n const [getObjectResult, headObjectResult] = await Promise.all([\n this.s3Client.send(\n new GetObjectCommand({\n Bucket: this.bucketName,\n Key: resolvedPath.slice(1),\n }),\n ),\n this.s3Client.send(\n new HeadObjectCommand({\n Bucket: this.bucketName,\n Key: resolvedPath.slice(1),\n }),\n ),\n ]);\n\n if (!getObjectResult.Body) {\n return { content: [], created_at: \"\", modified_at: \"\" };\n }\n\n const headInfo = {\n size: headObjectResult.ContentLength,\n created_at:\n headObjectResult.Metadata?.CreatedAt ||\n headObjectResult.LastModified?.toISOString() ||\n \"\",\n modified_at: headObjectResult.LastModified?.toISOString() || \"\",\n };\n\n const stream =\n getObjectResult.Body as NodeJsRuntimeStreamingBlobPayloadOutputTypes;\n const content = await stream.transformToString(\"utf-8\");\n\n const lines = content.split(/\\r?\\n/);\n\n return {\n content: lines,\n created_at: headInfo.created_at,\n modified_at: headInfo.modified_at,\n };\n } catch (error) {\n return {\n content: [\n this.getErrorMessage(error, \"Unknown error during readRaw operation\"),\n ],\n created_at: \"\",\n modified_at: \"\",\n };\n }\n }\n\n /**\n * Search for a literal text pattern in files (recursive).\n *\n * @param pattern - Literal string to search for (NOT regex).\n * @param dirPath - Directory or file path to search in. Defaults to current directory.\n * @param glob - Optional glob pattern to filter which files to search.\n * @returns List of GrepMatch dicts containing path, line number, and matched text.\n */\n async grepRaw(\n pattern: string,\n dirPath?: string | null,\n glob?: string | null,\n ): Promise<GrepMatch[] | string> {\n try {\n const searchPath = dirPath ? this.resolvePath(dirPath) : this.cwd;\n const prefix =\n searchPath === \"/\"\n ? \"\"\n : searchPath.slice(1).replace(/^\\/+/, \"\").replace(/\\/+$/, \"\");\n\n const listedObjects = await this.dangerouslyListAllObjects({\n Bucket: this.bucketName,\n Prefix: prefix,\n });\n\n const matchingObjects = (listedObjects.Contents || [])\n .filter((obj) => {\n const key = obj.Key || \"\";\n if (!key) {\n return false;\n }\n\n if (!glob) {\n return true;\n }\n\n return this.matchesGlob(key, prefix, glob);\n })\n .filter((obj) => {\n if (!this.maxFileSizeBytes || obj.Size === undefined) {\n return true;\n }\n\n return obj.Size <= this.maxFileSizeBytes;\n });\n\n const objectMatches = await Promise.all(\n matchingObjects.map(async (obj) => {\n const key = obj.Key;\n if (!key) {\n return [] as GrepMatch[];\n }\n\n try {\n const getObjectResult = await this.s3Client.send(\n new GetObjectCommand({\n Bucket: this.bucketName,\n Key: key,\n }),\n );\n\n if (!getObjectResult.Body) {\n return [] as GrepMatch[];\n }\n\n const content =\n await getObjectResult.Body.transformToString(\"utf-8\");\n const lines = content.split(/\\r?\\n/);\n const fileMatches: GrepMatch[] = [];\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n if (!line.includes(pattern)) {\n continue;\n }\n\n fileMatches.push({\n path: `/${key}`,\n line: i + 1,\n text: line,\n });\n }\n\n return fileMatches;\n } catch {\n return [] as GrepMatch[];\n }\n }),\n );\n\n return objectMatches.flat();\n } catch (error) {\n return this.getErrorMessage(error, \"Unknown error during grep operation\");\n }\n }\n\n private matchesGlob(\n key: string,\n prefix: string,\n globPattern: string,\n ): boolean {\n const relativeToPrefix =\n prefix && key.startsWith(prefix)\n ? key.slice(prefix.length).replace(/^\\/+/, \"\")\n : key;\n const fileName = key.split(\"/\").pop() || key;\n\n return (\n m.isMatch(relativeToPrefix, globPattern, { dot: true }) ||\n m.isMatch(fileName, globPattern, { dot: true }) ||\n m.isMatch(key, globPattern, { dot: true })\n );\n }\n\n /**\n * Structured glob matching returning FileInfo objects.\n *\n * @param pattern - Glob pattern (e.g., `*.py`, `**\\/*.ts`)\n * @param path - Base path to search from (default: \"/\")\n * @returns List of FileInfo objects matching the pattern\n */\n async globInfo(pattern: string, path?: string): Promise<FileInfo[]> {\n try {\n const searchPath = this.resolvePath(path ?? \"/\");\n const prefix =\n searchPath === \"/\"\n ? \"\"\n : searchPath.slice(1).replace(/^\\/+/, \"\").replace(/\\/+$/, \"\");\n\n const listedObjects = await this.dangerouslyListAllObjects({\n Bucket: this.bucketName,\n Prefix: prefix,\n });\n\n return (listedObjects.Contents || [])\n .filter((obj) => {\n const key = obj.Key || \"\";\n if (!key || key.endsWith(\"/\")) {\n return false;\n }\n\n return this.matchesGlob(key, prefix, pattern);\n })\n .map((obj) => ({\n path: `/${obj.Key}`,\n is_dir: false,\n size: obj.Size,\n modified_at: obj.LastModified?.toISOString(),\n }));\n } catch (error) {\n return [\n {\n path: this.getErrorMessage(\n error,\n \"Unknown error during glob operation\",\n ),\n is_dir: false,\n size: undefined,\n modified_at: undefined,\n },\n ];\n }\n }\n\n /**\n * Create a new file.\n *\n * @param filePath - Absolute file path\n * @param content - File content as string\n * @returns WriteResult with error populated on failure\n */\n async write(filePath: string, content: string): Promise<WriteResult> {\n try {\n const resolvedPath = this.resolvePath(filePath);\n\n try {\n await this.s3Client.send(\n new HeadObjectCommand({\n Bucket: this.bucketName,\n Key: resolvedPath.slice(1),\n }),\n );\n } catch {\n // continue because file does not exists\n }\n\n const creationTime = new Date().toISOString();\n await this.s3Client.send(\n new PutObjectCommand({\n Bucket: this.bucketName,\n Key: resolvedPath.slice(1),\n Body: content,\n Metadata: {\n CreatedAt: creationTime,\n },\n }),\n );\n\n return {\n path: resolvedPath,\n filesUpdate: null,\n };\n } catch (error) {\n return {\n error: this.getErrorMessage(\n error,\n \"Unknown error during write operation\",\n ),\n };\n }\n }\n\n /**\n * Edit a file by replacing string occurrences.\n *\n * @param filePath - Absolute file path\n * @param oldString - String to find and replace\n * @param newString - Replacement string\n * @param replaceAll - If true, replace all occurrences (default: false)\n * @returns EditResult with error, path, filesUpdate, and occurrences\n */\n async edit(\n filePath: string,\n oldString: string,\n newString: string,\n replaceAll: boolean = false,\n ): Promise<EditResult> {\n try {\n const resolvedPath = this.resolvePath(filePath);\n const fileData = await this.readRaw(resolvedPath);\n const content = fileData.content.join(\"\\n\");\n\n if (!content.includes(oldString)) {\n return {\n error: `The string \"${oldString}\" was not found in the file.`,\n };\n }\n\n const occurrences = replaceAll ? content.split(oldString).length - 1 : 1;\n\n const newContent = replaceAll\n ? content.split(oldString).join(newString)\n : content.replace(oldString, newString);\n\n await this.s3Client.send(\n new PutObjectCommand({\n Bucket: this.bucketName,\n Key: resolvedPath.slice(1),\n Body: newContent,\n Metadata: {\n CreatedAt: fileData.created_at || new Date().toISOString(),\n },\n }),\n );\n\n return {\n path: resolvedPath,\n filesUpdate: null,\n occurrences,\n };\n } catch (error) {\n return {\n error: this.getErrorMessage(\n error,\n \"Unknown error during edit operation\",\n ),\n };\n }\n }\n\n /**\n * Upload multiple files.\n * Optional - backends that don't support file upload can omit this.\n *\n * @param files - List of [path, content] tuples to upload\n * @returns List of FileUploadResponse objects, one per input file\n */\n async uploadFiles(\n files: Array<[string, Uint8Array]>,\n ): Promise<FileUploadResponse[]> {\n const uploadResults: FileUploadResponse[] = [];\n\n await Promise.all(\n files.map(async ([filePath, content]) => {\n try {\n const resolvedPath = this.resolvePath(filePath);\n await this.s3Client.send(\n new PutObjectCommand({\n Bucket: this.bucketName,\n Key: resolvedPath.slice(1),\n Body: content,\n Metadata: {\n CreatedAt: new Date().toISOString(),\n },\n }),\n );\n\n uploadResults.push({\n path: resolvedPath,\n error: null,\n });\n } catch (error) {\n uploadResults.push({\n path: filePath,\n error: this.mapError(error),\n });\n }\n }),\n );\n\n return uploadResults;\n }\n\n /**\n * Download multiple files.\n * Optional - backends that don't support file download can omit this.\n *\n * @param paths - List of file paths to download\n * @returns List of FileDownloadResponse objects, one per input path\n */\n async downloadFiles(paths: string[]): Promise<FileDownloadResponse[]> {\n const downloadResults = await Promise.all(\n paths.map(async (filePath) => {\n try {\n const resolvedPath = this.resolvePath(filePath);\n const getObjectResult = await this.s3Client.send(\n new GetObjectCommand({\n Bucket: this.bucketName,\n Key: resolvedPath.slice(1),\n }),\n );\n\n if (!getObjectResult.Body) {\n return {\n path: filePath,\n content: null,\n error: \"file_not_found\" as const,\n };\n }\n\n const body =\n getObjectResult.Body as NodeJsRuntimeStreamingBlobPayloadOutputTypes;\n\n let content: Uint8Array;\n\n if (typeof body.transformToByteArray === \"function\") {\n content = await body.transformToByteArray();\n } else if (typeof body.transformToString === \"function\") {\n content = new TextEncoder().encode(\n await body.transformToString(\"utf-8\"),\n );\n } else if (typeof body.transformToWebStream === \"function\") {\n const reader = body.transformToWebStream().getReader();\n const chunks: Uint8Array[] = [];\n let totalLength = 0;\n\n while (true) {\n const { value, done } = await reader.read();\n if (done) {\n break;\n }\n\n const chunk = value ?? new Uint8Array();\n chunks.push(chunk);\n totalLength += chunk.length;\n }\n\n const merged = new Uint8Array(totalLength);\n let offset = 0;\n for (const chunk of chunks) {\n merged.set(chunk, offset);\n offset += chunk.length;\n }\n\n content = merged;\n } else {\n return {\n path: filePath,\n content: null,\n error: \"invalid_path\" as const,\n };\n }\n\n return {\n path: filePath,\n content,\n error: null,\n };\n } catch (error) {\n return {\n path: filePath,\n content: null,\n error: this.mapError(error),\n };\n }\n }),\n );\n\n return downloadResults;\n }\n\n private mapError(\n error: unknown,\n ): FileDownloadResponse[\"error\"] | FileUploadResponse[\"error\"] {\n const candidate = error as {\n name?: string;\n code?: string;\n message?: string;\n };\n const code = candidate?.code;\n const name = candidate?.name;\n const message = candidate?.message;\n\n if (\n code === \"NoSuchKey\" ||\n code === \"ENOENT\" ||\n name === \"NoSuchKey\" ||\n message === \"NoSuchKey\"\n ) {\n return \"file_not_found\";\n }\n\n if (\n code === \"AccessDenied\" ||\n code === \"Forbidden\" ||\n code === \"EACCES\" ||\n name === \"AccessDenied\" ||\n message === \"AccessDenied\" ||\n message === \"Forbidden\"\n ) {\n return \"permission_denied\";\n }\n\n if (code === \"EISDIR\" || message === \"EISDIR\") {\n return \"is_directory\";\n }\n\n return \"invalid_path\";\n }\n\n async dangerouslyListAllObjects(\n param: Omit<ListObjectsV2CommandInput, \"ContinuationToken\">,\n ): Promise<\n Pick<\n ListObjectsV2CommandOutput,\n | \"Name\"\n | \"Contents\"\n | \"CommonPrefixes\"\n | \"Prefix\"\n | \"Delimiter\"\n | \"MaxKeys\"\n >\n > {\n let isTruncated = true;\n let continuationToken: string | undefined;\n\n const allContents: _Object[] = [];\n const allCommonPrefixes: NonNullable<\n ListObjectsV2CommandOutput[\"CommonPrefixes\"]\n > = [];\n\n let name: ListObjectsV2CommandOutput[\"Name\"];\n let prefix: ListObjectsV2CommandOutput[\"Prefix\"];\n let delimiter: ListObjectsV2CommandOutput[\"Delimiter\"];\n let maxKeys: ListObjectsV2CommandOutput[\"MaxKeys\"];\n\n while (isTruncated) {\n const result = await this.s3Client.send(\n new ListObjectsV2Command({\n ...param,\n ContinuationToken: continuationToken,\n }),\n );\n\n if (name === undefined && result.Name !== undefined) {\n name = result.Name;\n }\n\n if (prefix === undefined && result.Prefix !== undefined) {\n prefix = result.Prefix;\n }\n\n if (delimiter === undefined && result.Delimiter !== undefined) {\n delimiter = result.Delimiter;\n }\n\n if (maxKeys === undefined && result.MaxKeys !== undefined) {\n maxKeys = result.MaxKeys;\n }\n\n if (result.Contents?.length) {\n allContents.push(...result.Contents);\n }\n\n if (result.CommonPrefixes?.length) {\n allCommonPrefixes.push(...result.CommonPrefixes);\n }\n\n isTruncated = result.IsTruncated === true;\n continuationToken = result.NextContinuationToken;\n }\n\n return {\n Name: name,\n Prefix: prefix ?? param.Prefix,\n Delimiter: delimiter ?? param.Delimiter,\n MaxKeys: maxKeys ?? param.MaxKeys,\n Contents: allContents,\n CommonPrefixes: allCommonPrefixes,\n };\n }\n}\n"],"mappings":"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"}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ListObjectsV2CommandInput, ListObjectsV2CommandOutput,
|
|
1
|
+
import { ListObjectsV2CommandInput, ListObjectsV2CommandOutput, S3ClientConfig } from "@aws-sdk/client-s3";
|
|
2
2
|
import { BackendProtocol, EditResult, FileData, FileDownloadResponse, FileInfo, FileUploadResponse, GrepMatch, WriteResult } from "deepagents";
|
|
3
3
|
|
|
4
4
|
//#region src/index.d.ts
|
|
@@ -9,7 +9,7 @@ declare class S3Backend implements BackendProtocol {
|
|
|
9
9
|
private maxFileSizeBytes;
|
|
10
10
|
private getErrorMessage;
|
|
11
11
|
constructor(options?: {
|
|
12
|
-
s3ClientConfig?:
|
|
12
|
+
s3ClientConfig?: S3ClientConfig;
|
|
13
13
|
bucketName?: string;
|
|
14
14
|
rootPrefix?: string;
|
|
15
15
|
maxFileSizeMb?: number;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ListObjectsV2CommandInput, ListObjectsV2CommandOutput,
|
|
1
|
+
import { ListObjectsV2CommandInput, ListObjectsV2CommandOutput, S3ClientConfig } from "@aws-sdk/client-s3";
|
|
2
2
|
import { BackendProtocol, EditResult, FileData, FileDownloadResponse, FileInfo, FileUploadResponse, GrepMatch, WriteResult } from "deepagents";
|
|
3
3
|
|
|
4
4
|
//#region src/index.d.ts
|
|
@@ -9,7 +9,7 @@ declare class S3Backend implements BackendProtocol {
|
|
|
9
9
|
private maxFileSizeBytes;
|
|
10
10
|
private getErrorMessage;
|
|
11
11
|
constructor(options?: {
|
|
12
|
-
s3ClientConfig?:
|
|
12
|
+
s3ClientConfig?: S3ClientConfig;
|
|
13
13
|
bucketName?: string;
|
|
14
14
|
rootPrefix?: string;
|
|
15
15
|
maxFileSizeMb?: number;
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import{GetObjectCommand as e,HeadObjectCommand as t,ListObjectsV2Command as n,PutObjectCommand as r,S3Client as i}from"@aws-sdk/client-s3";import*as a from"micromatch";var o=class{s3Client;bucketName;cwd;maxFileSizeBytes;getErrorMessage(e,t){return e instanceof Error&&e.message?e.message:typeof e==`string`&&e.length>0?e:t}constructor(e={}){if(this.s3Client=new i(e?.s3ClientConfig||{}),this.cwd=e?.rootPrefix||`/`,this.maxFileSizeBytes=e?.maxFileSizeMb?e?.maxFileSizeMb*1024*1024:void 0,e?.bucketName)this.bucketName=e.bucketName;else throw Error(`bucketName is required in options`)}resolvePath(e){if(typeof e!=`string`)throw Error(`Path must be a string`);let t=e.replace(/\\/g,`/`);if(t.includes(`\0`))throw Error(`Path contains invalid characters`);let n=this.cwd.replace(/\\/g,`/`).split(`/`).filter(Boolean);for(let e of n)if(e===`.`||e===`..`)throw Error(`Invalid rootPrefix; path traversal is not allowed`);let r=t.split(`/`).filter(e=>e.length>0&&e!==`.`);for(let e of r){if(e===`..`)throw Error(`Path traversal is not allowed`);if(e===`~`||e.startsWith(`~`))throw Error(`Home-relative paths are not allowed`);if(e.includes(`\0`))throw Error(`Path contains invalid characters`)}return`/${[...n,...r].join(`/`)}`}async lsInfo(e){try{let t=this.resolvePath(e),n=t===`/`?``:`${t.slice(1).replace(/\/+$/,``)}/`,r=await this.dangerouslyListAllObjects({Bucket:this.bucketName,Prefix:n,Delimiter:`/`}),i=(r.Contents||[]).filter(e=>{let t=e.Key||``;if(!t.startsWith(n))return!1;let r=t.slice(n.length);return r.length>0&&!r.includes(`/`)}).map(e=>({path:`/${e.Key}`,is_dir:!1,size:e.Size,modified_at:e.LastModified?.toISOString()})),a=(r.CommonPrefixes||[]).map(e=>e.Prefix||``).filter(e=>{if(!e.startsWith(n))return!1;let t=e.slice(n.length).replace(/\/+$/,``);return t.length>0&&!t.includes(`/`)}).map(e=>({path:`/${e}`,is_dir:!0,size:void 0,modified_at:void 0}));return[...i,...a]}catch{return[]}}async read(e,t=0,n=500){try{return(await this.readRaw(e)).content.slice(t,t+n).map((e,n)=>`${t+n+1}: ${e}`).join(`
|
|
2
|
-
`)}catch(e){return this.getErrorMessage(e,`Unknown error during read operation`)}}async readRaw(n){try{let r=this.resolvePath(n),[i,a]=await Promise.all([this.s3Client.send(new e({Bucket:this.bucketName,Key:r.slice(1)})),this.s3Client.send(new t({Bucket:this.bucketName,Key:r.slice(1)}))]);if(!i.Body)return{content:[],created_at:``,modified_at:``};let o={size:a.ContentLength,created_at:a.Metadata?.CreatedAt||a.LastModified?.toISOString()||``,modified_at:a.LastModified?.toISOString()||``}
|
|
2
|
+
`)}catch(e){return this.getErrorMessage(e,`Unknown error during read operation`)}}async readRaw(n){try{let r=this.resolvePath(n),[i,a]=await Promise.all([this.s3Client.send(new e({Bucket:this.bucketName,Key:r.slice(1)})),this.s3Client.send(new t({Bucket:this.bucketName,Key:r.slice(1)}))]);if(!i.Body)return{content:[],created_at:``,modified_at:``};let o={size:a.ContentLength,created_at:a.Metadata?.CreatedAt||a.LastModified?.toISOString()||``,modified_at:a.LastModified?.toISOString()||``};return{content:(await i.Body.transformToString(`utf-8`)).split(/\r?\n/),created_at:o.created_at,modified_at:o.modified_at}}catch(e){return{content:[this.getErrorMessage(e,`Unknown error during readRaw operation`)],created_at:``,modified_at:``}}}async grepRaw(t,n,r){try{let i=n?this.resolvePath(n):this.cwd,a=i===`/`?``:i.slice(1).replace(/^\/+/,``).replace(/\/+$/,``),o=((await this.dangerouslyListAllObjects({Bucket:this.bucketName,Prefix:a})).Contents||[]).filter(e=>{let t=e.Key||``;return t?r?this.matchesGlob(t,a,r):!0:!1}).filter(e=>!this.maxFileSizeBytes||e.Size===void 0?!0:e.Size<=this.maxFileSizeBytes);return(await Promise.all(o.map(async n=>{let r=n.Key;if(!r)return[];try{let n=await this.s3Client.send(new e({Bucket:this.bucketName,Key:r}));if(!n.Body)return[];let i=(await n.Body.transformToString(`utf-8`)).split(/\r?\n/),a=[];for(let e=0;e<i.length;e++){let n=i[e];n.includes(t)&&a.push({path:`/${r}`,line:e+1,text:n})}return a}catch{return[]}}))).flat()}catch(e){return this.getErrorMessage(e,`Unknown error during grep operation`)}}matchesGlob(e,t,n){let r=t&&e.startsWith(t)?e.slice(t.length).replace(/^\/+/,``):e,i=e.split(`/`).pop()||e;return a.isMatch(r,n,{dot:!0})||a.isMatch(i,n,{dot:!0})||a.isMatch(e,n,{dot:!0})}async globInfo(e,t){try{let n=this.resolvePath(t??`/`),r=n===`/`?``:n.slice(1).replace(/^\/+/,``).replace(/\/+$/,``);return((await this.dangerouslyListAllObjects({Bucket:this.bucketName,Prefix:r})).Contents||[]).filter(t=>{let n=t.Key||``;return!n||n.endsWith(`/`)?!1:this.matchesGlob(n,r,e)}).map(e=>({path:`/${e.Key}`,is_dir:!1,size:e.Size,modified_at:e.LastModified?.toISOString()}))}catch(e){return[{path:this.getErrorMessage(e,`Unknown error during glob operation`),is_dir:!1,size:void 0,modified_at:void 0}]}}async write(e,n){try{let i=this.resolvePath(e);try{await this.s3Client.send(new t({Bucket:this.bucketName,Key:i.slice(1)}))}catch{}let a=new Date().toISOString();return await this.s3Client.send(new r({Bucket:this.bucketName,Key:i.slice(1),Body:n,Metadata:{CreatedAt:a}})),{path:i,filesUpdate:null}}catch(e){return{error:this.getErrorMessage(e,`Unknown error during write operation`)}}}async edit(e,t,n,i=!1){try{let a=this.resolvePath(e),o=await this.readRaw(a),s=o.content.join(`
|
|
3
3
|
`);if(!s.includes(t))return{error:`The string "${t}" was not found in the file.`};let c=i?s.split(t).length-1:1,l=i?s.split(t).join(n):s.replace(t,n);return await this.s3Client.send(new r({Bucket:this.bucketName,Key:a.slice(1),Body:l,Metadata:{CreatedAt:o.created_at||new Date().toISOString()}})),{path:a,filesUpdate:null,occurrences:c}}catch(e){return{error:this.getErrorMessage(e,`Unknown error during edit operation`)}}}async uploadFiles(e){let t=[];return await Promise.all(e.map(async([e,n])=>{try{let i=this.resolvePath(e);await this.s3Client.send(new r({Bucket:this.bucketName,Key:i.slice(1),Body:n,Metadata:{CreatedAt:new Date().toISOString()}})),t.push({path:i,error:null})}catch(n){t.push({path:e,error:this.mapError(n)})}})),t}async downloadFiles(t){return await Promise.all(t.map(async t=>{try{let n=this.resolvePath(t),r=await this.s3Client.send(new e({Bucket:this.bucketName,Key:n.slice(1)}));if(!r.Body)return{path:t,content:null,error:`file_not_found`};let i=r.Body,a;if(typeof i.transformToByteArray==`function`)a=await i.transformToByteArray();else if(typeof i.transformToString==`function`)a=new TextEncoder().encode(await i.transformToString(`utf-8`));else if(typeof i.transformToWebStream==`function`){let e=i.transformToWebStream().getReader(),t=[],n=0;for(;;){let{value:r,done:i}=await e.read();if(i)break;let a=r??new Uint8Array;t.push(a),n+=a.length}let r=new Uint8Array(n),o=0;for(let e of t)r.set(e,o),o+=e.length;a=r}else return{path:t,content:null,error:`invalid_path`};return{path:t,content:a,error:null}}catch(e){return{path:t,content:null,error:this.mapError(e)}}}))}mapError(e){let t=e,n=t?.code,r=t?.name,i=t?.message;return n===`NoSuchKey`||n===`ENOENT`||r===`NoSuchKey`||i===`NoSuchKey`?`file_not_found`:n===`AccessDenied`||n===`Forbidden`||n===`EACCES`||r===`AccessDenied`||i===`AccessDenied`||i===`Forbidden`?`permission_denied`:n===`EISDIR`||i===`EISDIR`?`is_directory`:`invalid_path`}async dangerouslyListAllObjects(e){let t=!0,r,i=[],a=[],o,s,c,l;for(;t;){let u=await this.s3Client.send(new n({...e,ContinuationToken:r}));o===void 0&&u.Name!==void 0&&(o=u.Name),s===void 0&&u.Prefix!==void 0&&(s=u.Prefix),c===void 0&&u.Delimiter!==void 0&&(c=u.Delimiter),l===void 0&&u.MaxKeys!==void 0&&(l=u.MaxKeys),u.Contents?.length&&i.push(...u.Contents),u.CommonPrefixes?.length&&a.push(...u.CommonPrefixes),t=u.IsTruncated===!0,r=u.NextContinuationToken}return{Name:o,Prefix:s??e.Prefix,Delimiter:c??e.Delimiter,MaxKeys:l??e.MaxKeys,Contents:i,CommonPrefixes:a}}};export{o as S3Backend};
|
|
4
4
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import {\n S3Client,\n ListObjectsV2Command,\n ListObjectsV2CommandInput,\n GetObjectCommand,\n HeadObjectCommand,\n _Object,\n ListObjectsV2CommandOutput,\n PutObjectCommand,\n} from \"@aws-sdk/client-s3\";\nimport type { NodeJsRuntimeStreamingBlobPayloadOutputTypes } from \"@smithy/types\";\nimport {\n EditResult,\n FileData,\n FileDownloadResponse,\n FileInfo,\n FileUploadResponse,\n GrepMatch,\n WriteResult,\n BackendProtocol,\n} from \"deepagents\";\nimport * as m from \"micromatch\";\n\nexport class S3Backend implements BackendProtocol {\n private s3Client: S3Client;\n protected bucketName: string;\n protected cwd: string;\n private maxFileSizeBytes: number | undefined;\n\n private getErrorMessage(error: unknown, fallback: string): string {\n if (error instanceof Error && error.message) {\n return error.message;\n }\n\n if (typeof error === \"string\" && error.length > 0) {\n return error;\n }\n\n return fallback;\n }\n\n constructor(\n options: {\n s3ClientConfig?: ConstructorParameters<typeof S3Client>;\n bucketName?: string;\n rootPrefix?: string;\n maxFileSizeMb?: number;\n } = {},\n ) {\n this.s3Client = new S3Client(options?.s3ClientConfig || {});\n this.cwd = options?.rootPrefix || \"/\";\n this.maxFileSizeBytes = options?.maxFileSizeMb\n ? options?.maxFileSizeMb * 1024 * 1024\n : undefined;\n\n if (!options?.bucketName) {\n throw new Error(\"bucketName is required in options\");\n } else {\n this.bucketName = options.bucketName;\n }\n }\n\n /**\n * Resolve a path with security checks. This behaves similarly to the original\n * `resolvePath` in the `FilesystemBackend`, but it always act in virtual mode.\n *\n * @param key - The S3 object key to resolve. It should be an absolute path starting with /, but if not, it will be treated as relative to the root.\n * @returns Resolve the key to a normalized S3 object key.\n * @throws Error if the key contains path traversal patterns or is invalid.\n */\n private resolvePath(key: string): string {\n if (typeof key !== \"string\") {\n throw new Error(\"Path must be a string\");\n }\n\n const normalizedInput = key.replace(/\\\\/g, \"/\");\n if (normalizedInput.includes(\"\\0\")) {\n throw new Error(\"Path contains invalid characters\");\n }\n\n const rootSegments = this.cwd\n .replace(/\\\\/g, \"/\")\n .split(\"/\")\n .filter(Boolean);\n\n for (const segment of rootSegments) {\n if (segment === \".\" || segment === \"..\") {\n throw new Error(\"Invalid rootPrefix; path traversal is not allowed\");\n }\n }\n\n const inputSegments = normalizedInput\n .split(\"/\")\n .filter((segment) => segment.length > 0 && segment !== \".\");\n\n for (const segment of inputSegments) {\n if (segment === \"..\") {\n throw new Error(\"Path traversal is not allowed\");\n }\n\n if (segment === \"~\" || segment.startsWith(\"~\")) {\n throw new Error(\"Home-relative paths are not allowed\");\n }\n\n if (segment.includes(\"\\0\")) {\n throw new Error(\"Path contains invalid characters\");\n }\n }\n\n const fullPathSegments = [...rootSegments, ...inputSegments];\n return `/${fullPathSegments.join(\"/\")}`;\n }\n\n /**\n * List files and directories in the specified directory (non-recursive).\n *\n * @param dirPath - Absolute directory path to list files from\n * @returns List of FileInfo objects for files and directories directly in the directory.\n * Directories have a trailing / in their path and is_dir=true.\n */\n async lsInfo(dirPath: string): Promise<FileInfo[]> {\n try {\n const resolvedPath = this.resolvePath(dirPath);\n const listPrefix =\n resolvedPath === \"/\"\n ? \"\"\n : `${resolvedPath.slice(1).replace(/\\/+$/, \"\")}/`;\n const result = await this.dangerouslyListAllObjects({\n Bucket: this.bucketName,\n Prefix: listPrefix,\n Delimiter: \"/\",\n });\n\n const directFiles = (result.Contents || [])\n .filter((obj) => {\n const key = obj.Key || \"\";\n if (!key.startsWith(listPrefix)) {\n return false;\n }\n\n const relativeKey = key.slice(listPrefix.length);\n return relativeKey.length > 0 && !relativeKey.includes(\"/\");\n })\n .map((obj) => ({\n path: `/${obj.Key}`,\n is_dir: false,\n size: obj.Size,\n modified_at: obj.LastModified?.toISOString(),\n }));\n\n const directDirectories = (result.CommonPrefixes || [])\n .map((prefix) => prefix.Prefix || \"\")\n .filter((prefix) => {\n if (!prefix.startsWith(listPrefix)) {\n return false;\n }\n\n const relativePrefix = prefix\n .slice(listPrefix.length)\n .replace(/\\/+$/, \"\");\n\n return relativePrefix.length > 0 && !relativePrefix.includes(\"/\");\n })\n .map(\n (prefix) =>\n ({\n path: `/${prefix}`,\n is_dir: true,\n size: undefined,\n modified_at: undefined,\n }) as FileInfo,\n );\n\n return [...directFiles, ...directDirectories];\n } catch {\n return [];\n }\n }\n\n /**\n * Read file content with line numbers.\n *\n * @param filePath - Absolute or relative file path\n * @param offset - Line offset to start reading from (0-indexed)\n * @param limit - Maximum number of lines to read\n * @returns Formatted file content with line numbers, or error message\n */\n async read(\n filePath: string,\n offset: number = 0,\n limit: number = 500,\n ): Promise<string> {\n try {\n const fileData = await this.readRaw(filePath);\n const selectedLines = fileData.content.slice(offset, offset + limit);\n return selectedLines\n .map((line, index) => `${offset + index + 1}: ${line}`)\n .join(\"\\n\");\n } catch (error) {\n return this.getErrorMessage(error, \"Unknown error during read operation\");\n }\n }\n\n /**\n * Read file content as raw FileData.\n *\n * @param filePath - Absolute file path\n * @returns Raw file content as FileData\n */\n async readRaw(filePath: string): Promise<FileData> {\n try {\n const resolvedPath = this.resolvePath(filePath);\n\n const [getObjectResult, headObjectResult] = await Promise.all([\n this.s3Client.send(\n new GetObjectCommand({\n Bucket: this.bucketName,\n Key: resolvedPath.slice(1),\n }),\n ),\n this.s3Client.send(\n new HeadObjectCommand({\n Bucket: this.bucketName,\n Key: resolvedPath.slice(1),\n }),\n ),\n ]);\n\n if (!getObjectResult.Body) {\n return { content: [], created_at: \"\", modified_at: \"\" };\n }\n\n const headInfo = {\n size: headObjectResult.ContentLength,\n created_at:\n headObjectResult.Metadata?.CreatedAt ||\n headObjectResult.LastModified?.toISOString() ||\n \"\",\n modified_at: headObjectResult.LastModified?.toISOString() || \"\",\n };\n\n const stream = getObjectResult.Body as ReadableStream;\n const reader = stream.getReader();\n const decoder = new TextDecoder(\"utf-8\");\n let content = \"\";\n let done = false;\n\n while (!done) {\n const { value, done: streamDone } = await reader.read();\n if (value) {\n content += decoder.decode(value, { stream: true });\n }\n done = streamDone;\n }\n\n content += decoder.decode();\n\n const lines = content.split(/\\r?\\n/);\n\n return {\n content: lines,\n created_at: headInfo.created_at,\n modified_at: headInfo.modified_at,\n };\n } catch (error) {\n return {\n content: [\n this.getErrorMessage(error, \"Unknown error during readRaw operation\"),\n ],\n created_at: \"\",\n modified_at: \"\",\n };\n }\n }\n\n /**\n * Search for a literal text pattern in files (recursive).\n *\n * @param pattern - Literal string to search for (NOT regex).\n * @param dirPath - Directory or file path to search in. Defaults to current directory.\n * @param glob - Optional glob pattern to filter which files to search.\n * @returns List of GrepMatch dicts containing path, line number, and matched text.\n */\n async grepRaw(\n pattern: string,\n dirPath?: string | null,\n glob?: string | null,\n ): Promise<GrepMatch[] | string> {\n try {\n const searchPath = dirPath ? this.resolvePath(dirPath) : this.cwd;\n const prefix =\n searchPath === \"/\"\n ? \"\"\n : searchPath.slice(1).replace(/^\\/+/, \"\").replace(/\\/+$/, \"\");\n\n const listedObjects = await this.dangerouslyListAllObjects({\n Bucket: this.bucketName,\n Prefix: prefix,\n });\n\n const matchingObjects = (listedObjects.Contents || [])\n .filter((obj) => {\n const key = obj.Key || \"\";\n if (!key) {\n return false;\n }\n\n if (!glob) {\n return true;\n }\n\n return this.matchesGlob(key, prefix, glob);\n })\n .filter((obj) => {\n if (!this.maxFileSizeBytes || obj.Size === undefined) {\n return true;\n }\n\n return obj.Size <= this.maxFileSizeBytes;\n });\n\n const objectMatches = await Promise.all(\n matchingObjects.map(async (obj) => {\n const key = obj.Key;\n if (!key) {\n return [] as GrepMatch[];\n }\n\n try {\n const getObjectResult = await this.s3Client.send(\n new GetObjectCommand({\n Bucket: this.bucketName,\n Key: key,\n }),\n );\n\n if (!getObjectResult.Body) {\n return [] as GrepMatch[];\n }\n\n const content = await getObjectResult.Body.transformToString();\n const lines = content.split(/\\r?\\n/);\n const fileMatches: GrepMatch[] = [];\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n if (!line.includes(pattern)) {\n continue;\n }\n\n fileMatches.push({\n path: `/${key}`,\n line: i + 1,\n text: line,\n });\n }\n\n return fileMatches;\n } catch {\n return [] as GrepMatch[];\n }\n }),\n );\n\n return objectMatches.flat();\n } catch (error) {\n return this.getErrorMessage(error, \"Unknown error during grep operation\");\n }\n }\n\n private matchesGlob(\n key: string,\n prefix: string,\n globPattern: string,\n ): boolean {\n const relativeToPrefix =\n prefix && key.startsWith(prefix)\n ? key.slice(prefix.length).replace(/^\\/+/, \"\")\n : key;\n const fileName = key.split(\"/\").pop() || key;\n\n return (\n m.isMatch(relativeToPrefix, globPattern, { dot: true }) ||\n m.isMatch(fileName, globPattern, { dot: true }) ||\n m.isMatch(key, globPattern, { dot: true })\n );\n }\n\n /**\n * Structured glob matching returning FileInfo objects.\n *\n * @param pattern - Glob pattern (e.g., `*.py`, `**\\/*.ts`)\n * @param path - Base path to search from (default: \"/\")\n * @returns List of FileInfo objects matching the pattern\n */\n async globInfo(pattern: string, path?: string): Promise<FileInfo[]> {\n try {\n const searchPath = this.resolvePath(path ?? \"/\");\n const prefix =\n searchPath === \"/\"\n ? \"\"\n : searchPath.slice(1).replace(/^\\/+/, \"\").replace(/\\/+$/, \"\");\n\n const listedObjects = await this.dangerouslyListAllObjects({\n Bucket: this.bucketName,\n Prefix: prefix,\n });\n\n return (listedObjects.Contents || [])\n .filter((obj) => {\n const key = obj.Key || \"\";\n if (!key || key.endsWith(\"/\")) {\n return false;\n }\n\n return this.matchesGlob(key, prefix, pattern);\n })\n .map((obj) => ({\n path: `/${obj.Key}`,\n is_dir: false,\n size: obj.Size,\n modified_at: obj.LastModified?.toISOString(),\n }));\n } catch (error) {\n return [\n {\n path: this.getErrorMessage(\n error,\n \"Unknown error during glob operation\",\n ),\n is_dir: false,\n size: undefined,\n modified_at: undefined,\n },\n ];\n }\n }\n\n /**\n * Create a new file.\n *\n * @param filePath - Absolute file path\n * @param content - File content as string\n * @returns WriteResult with error populated on failure\n */\n async write(filePath: string, content: string): Promise<WriteResult> {\n try {\n const resolvedPath = this.resolvePath(filePath);\n\n const getObjectResult = await this.s3Client.send(\n new GetObjectCommand({\n Bucket: this.bucketName,\n Key: resolvedPath.slice(1),\n }),\n );\n\n if (getObjectResult.Body) {\n return {\n error: `File already exists at path: ${resolvedPath}`,\n };\n }\n\n const creationTime = new Date().toISOString();\n await this.s3Client.send(\n new PutObjectCommand({\n Bucket: this.bucketName,\n Key: resolvedPath.slice(1),\n Body: content,\n Metadata: {\n CreatedAt: creationTime,\n },\n }),\n );\n\n return {\n path: resolvedPath,\n filesUpdate: null,\n };\n } catch (error) {\n return {\n error: this.getErrorMessage(\n error,\n \"Unknown error during write operation\",\n ),\n };\n }\n }\n\n /**\n * Edit a file by replacing string occurrences.\n *\n * @param filePath - Absolute file path\n * @param oldString - String to find and replace\n * @param newString - Replacement string\n * @param replaceAll - If true, replace all occurrences (default: false)\n * @returns EditResult with error, path, filesUpdate, and occurrences\n */\n async edit(\n filePath: string,\n oldString: string,\n newString: string,\n replaceAll: boolean = false,\n ): Promise<EditResult> {\n try {\n const resolvedPath = this.resolvePath(filePath);\n const fileData = await this.readRaw(resolvedPath);\n const content = fileData.content.join(\"\\n\");\n\n if (!content.includes(oldString)) {\n return {\n error: `The string \"${oldString}\" was not found in the file.`,\n };\n }\n\n const occurrences = replaceAll ? content.split(oldString).length - 1 : 1;\n\n const newContent = replaceAll\n ? content.split(oldString).join(newString)\n : content.replace(oldString, newString);\n\n await this.s3Client.send(\n new PutObjectCommand({\n Bucket: this.bucketName,\n Key: resolvedPath.slice(1),\n Body: newContent,\n Metadata: {\n CreatedAt: fileData.created_at || new Date().toISOString(),\n },\n }),\n );\n\n return {\n path: resolvedPath,\n filesUpdate: null,\n occurrences,\n };\n } catch (error) {\n return {\n error: this.getErrorMessage(\n error,\n \"Unknown error during edit operation\",\n ),\n };\n }\n }\n\n /**\n * Upload multiple files.\n * Optional - backends that don't support file upload can omit this.\n *\n * @param files - List of [path, content] tuples to upload\n * @returns List of FileUploadResponse objects, one per input file\n */\n async uploadFiles(\n files: Array<[string, Uint8Array]>,\n ): Promise<FileUploadResponse[]> {\n const uploadResults: FileUploadResponse[] = [];\n\n await Promise.all(\n files.map(async ([filePath, content]) => {\n try {\n const resolvedPath = this.resolvePath(filePath);\n await this.s3Client.send(\n new PutObjectCommand({\n Bucket: this.bucketName,\n Key: resolvedPath.slice(1),\n Body: content,\n Metadata: {\n CreatedAt: new Date().toISOString(),\n },\n }),\n );\n\n uploadResults.push({\n path: resolvedPath,\n error: null,\n });\n } catch (error) {\n uploadResults.push({\n path: filePath,\n error: this.mapError(error),\n });\n }\n }),\n );\n\n return uploadResults;\n }\n\n /**\n * Download multiple files.\n * Optional - backends that don't support file download can omit this.\n *\n * @param paths - List of file paths to download\n * @returns List of FileDownloadResponse objects, one per input path\n */\n async downloadFiles(paths: string[]): Promise<FileDownloadResponse[]> {\n const downloadResults = await Promise.all(\n paths.map(async (filePath) => {\n try {\n const resolvedPath = this.resolvePath(filePath);\n const getObjectResult = await this.s3Client.send(\n new GetObjectCommand({\n Bucket: this.bucketName,\n Key: resolvedPath.slice(1),\n }),\n );\n\n if (!getObjectResult.Body) {\n return {\n path: filePath,\n content: null,\n error: \"file_not_found\" as const,\n };\n }\n\n const body =\n getObjectResult.Body as NodeJsRuntimeStreamingBlobPayloadOutputTypes;\n\n let content: Uint8Array;\n\n if (typeof body.transformToByteArray === \"function\") {\n content = await body.transformToByteArray();\n } else if (typeof body.transformToString === \"function\") {\n content = new TextEncoder().encode(\n await body.transformToString(\"utf-8\"),\n );\n } else if (typeof body.transformToWebStream === \"function\") {\n const reader = body.transformToWebStream().getReader();\n const chunks: Uint8Array[] = [];\n let totalLength = 0;\n\n while (true) {\n const { value, done } = await reader.read();\n if (done) {\n break;\n }\n\n const chunk = value ?? new Uint8Array();\n chunks.push(chunk);\n totalLength += chunk.length;\n }\n\n const merged = new Uint8Array(totalLength);\n let offset = 0;\n for (const chunk of chunks) {\n merged.set(chunk, offset);\n offset += chunk.length;\n }\n\n content = merged;\n } else {\n return {\n path: filePath,\n content: null,\n error: \"invalid_path\" as const,\n };\n }\n\n return {\n path: filePath,\n content,\n error: null,\n };\n } catch (error) {\n return {\n path: filePath,\n content: null,\n error: this.mapError(error),\n };\n }\n }),\n );\n\n return downloadResults;\n }\n\n private mapError(\n error: unknown,\n ): FileDownloadResponse[\"error\"] | FileUploadResponse[\"error\"] {\n const candidate = error as {\n name?: string;\n code?: string;\n message?: string;\n };\n const code = candidate?.code;\n const name = candidate?.name;\n const message = candidate?.message;\n\n if (\n code === \"NoSuchKey\" ||\n code === \"ENOENT\" ||\n name === \"NoSuchKey\" ||\n message === \"NoSuchKey\"\n ) {\n return \"file_not_found\";\n }\n\n if (\n code === \"AccessDenied\" ||\n code === \"Forbidden\" ||\n code === \"EACCES\" ||\n name === \"AccessDenied\" ||\n message === \"AccessDenied\" ||\n message === \"Forbidden\"\n ) {\n return \"permission_denied\";\n }\n\n if (code === \"EISDIR\" || message === \"EISDIR\") {\n return \"is_directory\";\n }\n\n return \"invalid_path\";\n }\n\n async dangerouslyListAllObjects(\n param: Omit<ListObjectsV2CommandInput, \"ContinuationToken\">,\n ): Promise<\n Pick<\n ListObjectsV2CommandOutput,\n | \"Name\"\n | \"Contents\"\n | \"CommonPrefixes\"\n | \"Prefix\"\n | \"Delimiter\"\n | \"MaxKeys\"\n >\n > {\n let isTruncated = true;\n let continuationToken: string | undefined;\n\n const allContents: _Object[] = [];\n const allCommonPrefixes: NonNullable<\n ListObjectsV2CommandOutput[\"CommonPrefixes\"]\n > = [];\n\n let name: ListObjectsV2CommandOutput[\"Name\"];\n let prefix: ListObjectsV2CommandOutput[\"Prefix\"];\n let delimiter: ListObjectsV2CommandOutput[\"Delimiter\"];\n let maxKeys: ListObjectsV2CommandOutput[\"MaxKeys\"];\n\n while (isTruncated) {\n const result = await this.s3Client.send(\n new ListObjectsV2Command({\n ...param,\n ContinuationToken: continuationToken,\n }),\n );\n\n if (name === undefined && result.Name !== undefined) {\n name = result.Name;\n }\n\n if (prefix === undefined && result.Prefix !== undefined) {\n prefix = result.Prefix;\n }\n\n if (delimiter === undefined && result.Delimiter !== undefined) {\n delimiter = result.Delimiter;\n }\n\n if (maxKeys === undefined && result.MaxKeys !== undefined) {\n maxKeys = result.MaxKeys;\n }\n\n if (result.Contents?.length) {\n allContents.push(...result.Contents);\n }\n\n if (result.CommonPrefixes?.length) {\n allCommonPrefixes.push(...result.CommonPrefixes);\n }\n\n isTruncated = result.IsTruncated === true;\n continuationToken = result.NextContinuationToken;\n }\n\n return {\n Name: name,\n Prefix: prefix ?? param.Prefix,\n Delimiter: delimiter ?? param.Delimiter,\n MaxKeys: maxKeys ?? param.MaxKeys,\n Contents: allContents,\n CommonPrefixes: allCommonPrefixes,\n };\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import {\n S3Client,\n ListObjectsV2Command,\n ListObjectsV2CommandInput,\n GetObjectCommand,\n HeadObjectCommand,\n _Object,\n ListObjectsV2CommandOutput,\n PutObjectCommand,\n S3ClientConfig,\n} from \"@aws-sdk/client-s3\";\nimport type { NodeJsRuntimeStreamingBlobPayloadOutputTypes } from \"@smithy/types\";\nimport {\n EditResult,\n FileData,\n FileDownloadResponse,\n FileInfo,\n FileUploadResponse,\n GrepMatch,\n WriteResult,\n BackendProtocol,\n} from \"deepagents\";\nimport * as m from \"micromatch\";\n\nexport class S3Backend implements BackendProtocol {\n private s3Client: S3Client;\n protected bucketName: string;\n protected cwd: string;\n private maxFileSizeBytes: number | undefined;\n\n private getErrorMessage(error: unknown, fallback: string): string {\n if (error instanceof Error && error.message) {\n return error.message;\n }\n\n if (typeof error === \"string\" && error.length > 0) {\n return error;\n }\n\n return fallback;\n }\n\n constructor(\n options: {\n s3ClientConfig?: S3ClientConfig;\n bucketName?: string;\n rootPrefix?: string;\n maxFileSizeMb?: number;\n } = {},\n ) {\n this.s3Client = new S3Client(options?.s3ClientConfig || {});\n this.cwd = options?.rootPrefix || \"/\";\n this.maxFileSizeBytes = options?.maxFileSizeMb\n ? options?.maxFileSizeMb * 1024 * 1024\n : undefined;\n\n if (!options?.bucketName) {\n throw new Error(\"bucketName is required in options\");\n } else {\n this.bucketName = options.bucketName;\n }\n }\n\n /**\n * Resolve a path with security checks. This behaves similarly to the original\n * `resolvePath` in the `FilesystemBackend`, but it always act in virtual mode.\n *\n * @param key - The S3 object key to resolve. It should be an absolute path starting with /, but if not, it will be treated as relative to the root.\n * @returns Resolve the key to a normalized S3 object key.\n * @throws Error if the key contains path traversal patterns or is invalid.\n */\n private resolvePath(key: string): string {\n if (typeof key !== \"string\") {\n throw new Error(\"Path must be a string\");\n }\n\n const normalizedInput = key.replace(/\\\\/g, \"/\");\n if (normalizedInput.includes(\"\\0\")) {\n throw new Error(\"Path contains invalid characters\");\n }\n\n const rootSegments = this.cwd\n .replace(/\\\\/g, \"/\")\n .split(\"/\")\n .filter(Boolean);\n\n for (const segment of rootSegments) {\n if (segment === \".\" || segment === \"..\") {\n throw new Error(\"Invalid rootPrefix; path traversal is not allowed\");\n }\n }\n\n const inputSegments = normalizedInput\n .split(\"/\")\n .filter((segment) => segment.length > 0 && segment !== \".\");\n\n for (const segment of inputSegments) {\n if (segment === \"..\") {\n throw new Error(\"Path traversal is not allowed\");\n }\n\n if (segment === \"~\" || segment.startsWith(\"~\")) {\n throw new Error(\"Home-relative paths are not allowed\");\n }\n\n if (segment.includes(\"\\0\")) {\n throw new Error(\"Path contains invalid characters\");\n }\n }\n\n const fullPathSegments = [...rootSegments, ...inputSegments];\n return `/${fullPathSegments.join(\"/\")}`;\n }\n\n /**\n * List files and directories in the specified directory (non-recursive).\n *\n * @param dirPath - Absolute directory path to list files from\n * @returns List of FileInfo objects for files and directories directly in the directory.\n * Directories have a trailing / in their path and is_dir=true.\n */\n async lsInfo(dirPath: string): Promise<FileInfo[]> {\n try {\n const resolvedPath = this.resolvePath(dirPath);\n const listPrefix =\n resolvedPath === \"/\"\n ? \"\"\n : `${resolvedPath.slice(1).replace(/\\/+$/, \"\")}/`;\n const result = await this.dangerouslyListAllObjects({\n Bucket: this.bucketName,\n Prefix: listPrefix,\n Delimiter: \"/\",\n });\n\n const directFiles = (result.Contents || [])\n .filter((obj) => {\n const key = obj.Key || \"\";\n if (!key.startsWith(listPrefix)) {\n return false;\n }\n\n const relativeKey = key.slice(listPrefix.length);\n return relativeKey.length > 0 && !relativeKey.includes(\"/\");\n })\n .map((obj) => ({\n path: `/${obj.Key}`,\n is_dir: false,\n size: obj.Size,\n modified_at: obj.LastModified?.toISOString(),\n }));\n\n const directDirectories = (result.CommonPrefixes || [])\n .map((prefix) => prefix.Prefix || \"\")\n .filter((prefix) => {\n if (!prefix.startsWith(listPrefix)) {\n return false;\n }\n\n const relativePrefix = prefix\n .slice(listPrefix.length)\n .replace(/\\/+$/, \"\");\n\n return relativePrefix.length > 0 && !relativePrefix.includes(\"/\");\n })\n .map(\n (prefix) =>\n ({\n path: `/${prefix}`,\n is_dir: true,\n size: undefined,\n modified_at: undefined,\n }) as FileInfo,\n );\n\n return [...directFiles, ...directDirectories];\n } catch {\n return [];\n }\n }\n\n /**\n * Read file content with line numbers.\n *\n * @param filePath - Absolute or relative file path\n * @param offset - Line offset to start reading from (0-indexed)\n * @param limit - Maximum number of lines to read\n * @returns Formatted file content with line numbers, or error message\n */\n async read(\n filePath: string,\n offset: number = 0,\n limit: number = 500,\n ): Promise<string> {\n try {\n const fileData = await this.readRaw(filePath);\n const selectedLines = fileData.content.slice(offset, offset + limit);\n return selectedLines\n .map((line, index) => `${offset + index + 1}: ${line}`)\n .join(\"\\n\");\n } catch (error) {\n return this.getErrorMessage(error, \"Unknown error during read operation\");\n }\n }\n\n /**\n * Read file content as raw FileData.\n *\n * @param filePath - Absolute file path\n * @returns Raw file content as FileData\n */\n async readRaw(filePath: string): Promise<FileData> {\n try {\n const resolvedPath = this.resolvePath(filePath);\n\n const [getObjectResult, headObjectResult] = await Promise.all([\n this.s3Client.send(\n new GetObjectCommand({\n Bucket: this.bucketName,\n Key: resolvedPath.slice(1),\n }),\n ),\n this.s3Client.send(\n new HeadObjectCommand({\n Bucket: this.bucketName,\n Key: resolvedPath.slice(1),\n }),\n ),\n ]);\n\n if (!getObjectResult.Body) {\n return { content: [], created_at: \"\", modified_at: \"\" };\n }\n\n const headInfo = {\n size: headObjectResult.ContentLength,\n created_at:\n headObjectResult.Metadata?.CreatedAt ||\n headObjectResult.LastModified?.toISOString() ||\n \"\",\n modified_at: headObjectResult.LastModified?.toISOString() || \"\",\n };\n\n const stream =\n getObjectResult.Body as NodeJsRuntimeStreamingBlobPayloadOutputTypes;\n const content = await stream.transformToString(\"utf-8\");\n\n const lines = content.split(/\\r?\\n/);\n\n return {\n content: lines,\n created_at: headInfo.created_at,\n modified_at: headInfo.modified_at,\n };\n } catch (error) {\n return {\n content: [\n this.getErrorMessage(error, \"Unknown error during readRaw operation\"),\n ],\n created_at: \"\",\n modified_at: \"\",\n };\n }\n }\n\n /**\n * Search for a literal text pattern in files (recursive).\n *\n * @param pattern - Literal string to search for (NOT regex).\n * @param dirPath - Directory or file path to search in. Defaults to current directory.\n * @param glob - Optional glob pattern to filter which files to search.\n * @returns List of GrepMatch dicts containing path, line number, and matched text.\n */\n async grepRaw(\n pattern: string,\n dirPath?: string | null,\n glob?: string | null,\n ): Promise<GrepMatch[] | string> {\n try {\n const searchPath = dirPath ? this.resolvePath(dirPath) : this.cwd;\n const prefix =\n searchPath === \"/\"\n ? \"\"\n : searchPath.slice(1).replace(/^\\/+/, \"\").replace(/\\/+$/, \"\");\n\n const listedObjects = await this.dangerouslyListAllObjects({\n Bucket: this.bucketName,\n Prefix: prefix,\n });\n\n const matchingObjects = (listedObjects.Contents || [])\n .filter((obj) => {\n const key = obj.Key || \"\";\n if (!key) {\n return false;\n }\n\n if (!glob) {\n return true;\n }\n\n return this.matchesGlob(key, prefix, glob);\n })\n .filter((obj) => {\n if (!this.maxFileSizeBytes || obj.Size === undefined) {\n return true;\n }\n\n return obj.Size <= this.maxFileSizeBytes;\n });\n\n const objectMatches = await Promise.all(\n matchingObjects.map(async (obj) => {\n const key = obj.Key;\n if (!key) {\n return [] as GrepMatch[];\n }\n\n try {\n const getObjectResult = await this.s3Client.send(\n new GetObjectCommand({\n Bucket: this.bucketName,\n Key: key,\n }),\n );\n\n if (!getObjectResult.Body) {\n return [] as GrepMatch[];\n }\n\n const content =\n await getObjectResult.Body.transformToString(\"utf-8\");\n const lines = content.split(/\\r?\\n/);\n const fileMatches: GrepMatch[] = [];\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n if (!line.includes(pattern)) {\n continue;\n }\n\n fileMatches.push({\n path: `/${key}`,\n line: i + 1,\n text: line,\n });\n }\n\n return fileMatches;\n } catch {\n return [] as GrepMatch[];\n }\n }),\n );\n\n return objectMatches.flat();\n } catch (error) {\n return this.getErrorMessage(error, \"Unknown error during grep operation\");\n }\n }\n\n private matchesGlob(\n key: string,\n prefix: string,\n globPattern: string,\n ): boolean {\n const relativeToPrefix =\n prefix && key.startsWith(prefix)\n ? key.slice(prefix.length).replace(/^\\/+/, \"\")\n : key;\n const fileName = key.split(\"/\").pop() || key;\n\n return (\n m.isMatch(relativeToPrefix, globPattern, { dot: true }) ||\n m.isMatch(fileName, globPattern, { dot: true }) ||\n m.isMatch(key, globPattern, { dot: true })\n );\n }\n\n /**\n * Structured glob matching returning FileInfo objects.\n *\n * @param pattern - Glob pattern (e.g., `*.py`, `**\\/*.ts`)\n * @param path - Base path to search from (default: \"/\")\n * @returns List of FileInfo objects matching the pattern\n */\n async globInfo(pattern: string, path?: string): Promise<FileInfo[]> {\n try {\n const searchPath = this.resolvePath(path ?? \"/\");\n const prefix =\n searchPath === \"/\"\n ? \"\"\n : searchPath.slice(1).replace(/^\\/+/, \"\").replace(/\\/+$/, \"\");\n\n const listedObjects = await this.dangerouslyListAllObjects({\n Bucket: this.bucketName,\n Prefix: prefix,\n });\n\n return (listedObjects.Contents || [])\n .filter((obj) => {\n const key = obj.Key || \"\";\n if (!key || key.endsWith(\"/\")) {\n return false;\n }\n\n return this.matchesGlob(key, prefix, pattern);\n })\n .map((obj) => ({\n path: `/${obj.Key}`,\n is_dir: false,\n size: obj.Size,\n modified_at: obj.LastModified?.toISOString(),\n }));\n } catch (error) {\n return [\n {\n path: this.getErrorMessage(\n error,\n \"Unknown error during glob operation\",\n ),\n is_dir: false,\n size: undefined,\n modified_at: undefined,\n },\n ];\n }\n }\n\n /**\n * Create a new file.\n *\n * @param filePath - Absolute file path\n * @param content - File content as string\n * @returns WriteResult with error populated on failure\n */\n async write(filePath: string, content: string): Promise<WriteResult> {\n try {\n const resolvedPath = this.resolvePath(filePath);\n\n try {\n await this.s3Client.send(\n new HeadObjectCommand({\n Bucket: this.bucketName,\n Key: resolvedPath.slice(1),\n }),\n );\n } catch {\n // continue because file does not exists\n }\n\n const creationTime = new Date().toISOString();\n await this.s3Client.send(\n new PutObjectCommand({\n Bucket: this.bucketName,\n Key: resolvedPath.slice(1),\n Body: content,\n Metadata: {\n CreatedAt: creationTime,\n },\n }),\n );\n\n return {\n path: resolvedPath,\n filesUpdate: null,\n };\n } catch (error) {\n return {\n error: this.getErrorMessage(\n error,\n \"Unknown error during write operation\",\n ),\n };\n }\n }\n\n /**\n * Edit a file by replacing string occurrences.\n *\n * @param filePath - Absolute file path\n * @param oldString - String to find and replace\n * @param newString - Replacement string\n * @param replaceAll - If true, replace all occurrences (default: false)\n * @returns EditResult with error, path, filesUpdate, and occurrences\n */\n async edit(\n filePath: string,\n oldString: string,\n newString: string,\n replaceAll: boolean = false,\n ): Promise<EditResult> {\n try {\n const resolvedPath = this.resolvePath(filePath);\n const fileData = await this.readRaw(resolvedPath);\n const content = fileData.content.join(\"\\n\");\n\n if (!content.includes(oldString)) {\n return {\n error: `The string \"${oldString}\" was not found in the file.`,\n };\n }\n\n const occurrences = replaceAll ? content.split(oldString).length - 1 : 1;\n\n const newContent = replaceAll\n ? content.split(oldString).join(newString)\n : content.replace(oldString, newString);\n\n await this.s3Client.send(\n new PutObjectCommand({\n Bucket: this.bucketName,\n Key: resolvedPath.slice(1),\n Body: newContent,\n Metadata: {\n CreatedAt: fileData.created_at || new Date().toISOString(),\n },\n }),\n );\n\n return {\n path: resolvedPath,\n filesUpdate: null,\n occurrences,\n };\n } catch (error) {\n return {\n error: this.getErrorMessage(\n error,\n \"Unknown error during edit operation\",\n ),\n };\n }\n }\n\n /**\n * Upload multiple files.\n * Optional - backends that don't support file upload can omit this.\n *\n * @param files - List of [path, content] tuples to upload\n * @returns List of FileUploadResponse objects, one per input file\n */\n async uploadFiles(\n files: Array<[string, Uint8Array]>,\n ): Promise<FileUploadResponse[]> {\n const uploadResults: FileUploadResponse[] = [];\n\n await Promise.all(\n files.map(async ([filePath, content]) => {\n try {\n const resolvedPath = this.resolvePath(filePath);\n await this.s3Client.send(\n new PutObjectCommand({\n Bucket: this.bucketName,\n Key: resolvedPath.slice(1),\n Body: content,\n Metadata: {\n CreatedAt: new Date().toISOString(),\n },\n }),\n );\n\n uploadResults.push({\n path: resolvedPath,\n error: null,\n });\n } catch (error) {\n uploadResults.push({\n path: filePath,\n error: this.mapError(error),\n });\n }\n }),\n );\n\n return uploadResults;\n }\n\n /**\n * Download multiple files.\n * Optional - backends that don't support file download can omit this.\n *\n * @param paths - List of file paths to download\n * @returns List of FileDownloadResponse objects, one per input path\n */\n async downloadFiles(paths: string[]): Promise<FileDownloadResponse[]> {\n const downloadResults = await Promise.all(\n paths.map(async (filePath) => {\n try {\n const resolvedPath = this.resolvePath(filePath);\n const getObjectResult = await this.s3Client.send(\n new GetObjectCommand({\n Bucket: this.bucketName,\n Key: resolvedPath.slice(1),\n }),\n );\n\n if (!getObjectResult.Body) {\n return {\n path: filePath,\n content: null,\n error: \"file_not_found\" as const,\n };\n }\n\n const body =\n getObjectResult.Body as NodeJsRuntimeStreamingBlobPayloadOutputTypes;\n\n let content: Uint8Array;\n\n if (typeof body.transformToByteArray === \"function\") {\n content = await body.transformToByteArray();\n } else if (typeof body.transformToString === \"function\") {\n content = new TextEncoder().encode(\n await body.transformToString(\"utf-8\"),\n );\n } else if (typeof body.transformToWebStream === \"function\") {\n const reader = body.transformToWebStream().getReader();\n const chunks: Uint8Array[] = [];\n let totalLength = 0;\n\n while (true) {\n const { value, done } = await reader.read();\n if (done) {\n break;\n }\n\n const chunk = value ?? new Uint8Array();\n chunks.push(chunk);\n totalLength += chunk.length;\n }\n\n const merged = new Uint8Array(totalLength);\n let offset = 0;\n for (const chunk of chunks) {\n merged.set(chunk, offset);\n offset += chunk.length;\n }\n\n content = merged;\n } else {\n return {\n path: filePath,\n content: null,\n error: \"invalid_path\" as const,\n };\n }\n\n return {\n path: filePath,\n content,\n error: null,\n };\n } catch (error) {\n return {\n path: filePath,\n content: null,\n error: this.mapError(error),\n };\n }\n }),\n );\n\n return downloadResults;\n }\n\n private mapError(\n error: unknown,\n ): FileDownloadResponse[\"error\"] | FileUploadResponse[\"error\"] {\n const candidate = error as {\n name?: string;\n code?: string;\n message?: string;\n };\n const code = candidate?.code;\n const name = candidate?.name;\n const message = candidate?.message;\n\n if (\n code === \"NoSuchKey\" ||\n code === \"ENOENT\" ||\n name === \"NoSuchKey\" ||\n message === \"NoSuchKey\"\n ) {\n return \"file_not_found\";\n }\n\n if (\n code === \"AccessDenied\" ||\n code === \"Forbidden\" ||\n code === \"EACCES\" ||\n name === \"AccessDenied\" ||\n message === \"AccessDenied\" ||\n message === \"Forbidden\"\n ) {\n return \"permission_denied\";\n }\n\n if (code === \"EISDIR\" || message === \"EISDIR\") {\n return \"is_directory\";\n }\n\n return \"invalid_path\";\n }\n\n async dangerouslyListAllObjects(\n param: Omit<ListObjectsV2CommandInput, \"ContinuationToken\">,\n ): Promise<\n Pick<\n ListObjectsV2CommandOutput,\n | \"Name\"\n | \"Contents\"\n | \"CommonPrefixes\"\n | \"Prefix\"\n | \"Delimiter\"\n | \"MaxKeys\"\n >\n > {\n let isTruncated = true;\n let continuationToken: string | undefined;\n\n const allContents: _Object[] = [];\n const allCommonPrefixes: NonNullable<\n ListObjectsV2CommandOutput[\"CommonPrefixes\"]\n > = [];\n\n let name: ListObjectsV2CommandOutput[\"Name\"];\n let prefix: ListObjectsV2CommandOutput[\"Prefix\"];\n let delimiter: ListObjectsV2CommandOutput[\"Delimiter\"];\n let maxKeys: ListObjectsV2CommandOutput[\"MaxKeys\"];\n\n while (isTruncated) {\n const result = await this.s3Client.send(\n new ListObjectsV2Command({\n ...param,\n ContinuationToken: continuationToken,\n }),\n );\n\n if (name === undefined && result.Name !== undefined) {\n name = result.Name;\n }\n\n if (prefix === undefined && result.Prefix !== undefined) {\n prefix = result.Prefix;\n }\n\n if (delimiter === undefined && result.Delimiter !== undefined) {\n delimiter = result.Delimiter;\n }\n\n if (maxKeys === undefined && result.MaxKeys !== undefined) {\n maxKeys = result.MaxKeys;\n }\n\n if (result.Contents?.length) {\n allContents.push(...result.Contents);\n }\n\n if (result.CommonPrefixes?.length) {\n allCommonPrefixes.push(...result.CommonPrefixes);\n }\n\n isTruncated = result.IsTruncated === true;\n continuationToken = result.NextContinuationToken;\n }\n\n return {\n Name: name,\n Prefix: prefix ?? param.Prefix,\n Delimiter: delimiter ?? param.Delimiter,\n MaxKeys: maxKeys ?? param.MaxKeys,\n Contents: allContents,\n CommonPrefixes: allCommonPrefixes,\n };\n }\n}\n"],"mappings":"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"}
|