diff --git a/apps/mesh/package.json b/apps/mesh/package.json index 256ba09878..a554d560b8 100644 --- a/apps/mesh/package.json +++ b/apps/mesh/package.json @@ -58,6 +58,7 @@ "@ai-sdk/provider": "^3.0.8", "@electric-sql/pglite": "^0.3.15", "@ai-sdk/react": "^3.0.118", + "@aws-sdk/client-s3": "^3.1010.0", "@better-auth/sso": "1.4.1", "@daveyplate/better-auth-ui": "^3.2.7", "@deco/ui": "workspace:*", diff --git a/apps/mesh/src/api/app.ts b/apps/mesh/src/api/app.ts index da82324534..7b5abfa552 100644 --- a/apps/mesh/src/api/app.ts +++ b/apps/mesh/src/api/app.ts @@ -12,7 +12,7 @@ import { env } from "../env"; import { DECO_STORE_URL, isDecoHostedMcp } from "@/core/deco-constants"; import { WellKnownOrgMCPId } from "@decocms/mesh-sdk"; import { PrometheusSerializer } from "@opentelemetry/exporter-prometheus"; -import { Hono } from "hono"; +import { type Context, Hono } from "hono"; import { getCookie } from "hono/cookie"; import { cors } from "hono/cors"; import { logger } from "hono/logger"; @@ -956,6 +956,47 @@ export async function createApp(options: CreateAppOptions = {}) { mountDevRoutes(app, mcpAuth); } + // Filesystem MCP routes (S3-backed filesystem for agents) + // Handle /mcp/filesystem alias + app.use("/mcp/filesystem", mcpAuth); + app.route( + "/mcp/filesystem", + (await import("./routes/filesystem-mcp")).default, + ); + + // Handle {org_id}_filesystem connection ID pattern + app.all( + "/mcp/:connectionId{.*_filesystem$}", + mcpAuth, + async (c: Context) => { + const ctx = c.get("meshContext") as MeshContext; + const { handleFilesystemMcpRequest } = await import( + "./routes/filesystem-mcp" + ); + return handleFilesystemMcpRequest(c.req.raw, ctx); + }, + ); + + // Handle call-tool endpoint for filesystem connections + app.all( + "/mcp/:connectionId{.*_filesystem$}/call-tool/:toolName", + mcpAuth, + async (c: Context) => { + const ctx = c.get("meshContext") as MeshContext; + const toolName = c.req.param("toolName"); + if (!toolName) { + return c.json({ error: "Missing tool name" }, 400); + } + const args = (await c.req.json()) as Record; + const { callFilesystemTool } = await import("./routes/filesystem-mcp"); + const result = await callFilesystemTool(toolName, args, ctx); + if (result.isError) { + return c.json(result.content, 500); + } + return c.json(result.content); + }, + ); + // Virtual MCP / Agent routes (must be before proxy to match /mcp/gateway and /mcp/virtual-mcp before /mcp/:connectionId) // /mcp/gateway/:virtualMcpId (backward compat) or /mcp/virtual-mcp/:virtualMcpId app.route("/mcp", virtualMcpRoutes); diff --git a/apps/mesh/src/api/routes/filesystem-mcp.ts b/apps/mesh/src/api/routes/filesystem-mcp.ts new file mode 100644 index 0000000000..e48e74740b --- /dev/null +++ b/apps/mesh/src/api/routes/filesystem-mcp.ts @@ -0,0 +1,306 @@ +/** + * Filesystem MCP Server + * + * An MCP server that implements the FILESYSTEM_BINDING interface + * using S3-compatible storage as the backing store. + * + * Provides inline content access for AI agents (read/write file content + * directly in tool calls), unlike OBJECT_STORAGE_BINDING which uses presigned URLs. + * + * Route: POST /mcp/filesystem + * Also handles org-scoped connection ID pattern: /mcp/{orgId}_filesystem + */ + +import type { + FsDeleteInput, + FsDeleteOutput, + FsListInput, + FsListOutput, + FsMetadataInput, + FsMetadataOutput, + FsReadInput, + FsReadOutput, + FsWriteInput, + FsWriteOutput, +} from "@decocms/bindings/filesystem"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js"; +import { Hono } from "hono"; +import { z } from "zod"; +import type { MeshContext } from "../../core/mesh-context"; +import { requireOrganization } from "../../core/mesh-context"; +import { S3Service } from "../../filesystem/s3-service"; +import { + FsDeleteInputSchema, + FsDeleteOutputSchema, + FsListInputSchema, + FsListOutputSchema, + FsMetadataInputSchema, + FsMetadataOutputSchema, + FsReadInputSchema, + FsReadOutputSchema, + FsWriteInputSchema, + FsWriteOutputSchema, +} from "../../tools/filesystem/schema"; +import { getFilesystemS3Service } from "../../filesystem/factory"; + +// Local tool definition type +interface ToolDefinition { + name: string; + description?: string; + inputSchema: z.ZodTypeAny; + outputSchema?: z.ZodTypeAny; + handler: (args: Record) => Promise; + annotations?: { + title?: string; + readOnlyHint?: boolean; + destructiveHint?: boolean; + idempotentHint?: boolean; + openWorldHint?: boolean; + }; +} + +// Define Hono variables type +type Variables = { + meshContext: MeshContext; +}; + +const app = new Hono<{ Variables: Variables }>(); + +function createFilesystemTools(s3: S3Service, orgId: string): ToolDefinition[] { + return [ + { + name: "FS_READ", + description: + "Read file content. Returns content inline for text files and small binary files (as base64). Returns an error for files exceeding the size limit.", + inputSchema: FsReadInputSchema, + outputSchema: FsReadOutputSchema, + annotations: { + title: "Read File", + readOnlyHint: true, + destructiveHint: false, + }, + handler: async (args): Promise => { + const input = args as FsReadInput; + return s3.readFile(orgId, input.path, input.offset, input.limit); + }, + }, + { + name: "FS_WRITE", + description: + "Write content to a file. Creates the file if it doesn't exist, overwrites if it does.", + inputSchema: FsWriteInputSchema, + outputSchema: FsWriteOutputSchema, + annotations: { + title: "Write File", + readOnlyHint: false, + destructiveHint: false, + idempotentHint: true, + }, + handler: async (args): Promise => { + const input = args as FsWriteInput; + return s3.writeFile( + orgId, + input.path, + input.content, + input.encoding, + input.contentType, + ); + }, + }, + { + name: "FS_LIST", + description: + "List files and directories at a given path. Supports pagination and glob pattern filtering on file names.", + inputSchema: FsListInputSchema, + outputSchema: FsListOutputSchema, + annotations: { + title: "List Files", + readOnlyHint: true, + destructiveHint: false, + }, + handler: async (args): Promise => { + const input = args as FsListInput; + return s3.listFiles(orgId, input); + }, + }, + { + name: "FS_DELETE", + description: "Delete a single file.", + inputSchema: FsDeleteInputSchema, + outputSchema: FsDeleteOutputSchema, + annotations: { + title: "Delete File", + readOnlyHint: false, + destructiveHint: true, + idempotentHint: true, + }, + handler: async (args): Promise => { + const input = args as FsDeleteInput; + return s3.deleteFile(orgId, input.path); + }, + }, + { + name: "FS_METADATA", + description: + "Get file metadata including size, content type, last modified time, and ETag.", + inputSchema: FsMetadataInputSchema, + outputSchema: FsMetadataOutputSchema, + annotations: { + title: "File Metadata", + readOnlyHint: true, + destructiveHint: false, + }, + handler: async (args): Promise => { + const input = args as FsMetadataInput; + return s3.getMetadata(orgId, input.path); + }, + }, + ]; +} + +/** + * Handle a filesystem MCP request with a given context + */ +export async function handleFilesystemMcpRequest( + req: Request, + ctx: MeshContext, +): Promise { + const org = requireOrganization(ctx); + const s3 = getFilesystemS3Service(); + + if (!s3) { + return new Response( + JSON.stringify({ + error: "Filesystem not configured. Set S3_* environment variables.", + }), + { status: 503, headers: { "Content-Type": "application/json" } }, + ); + } + + const tools = createFilesystemTools(s3, org.id); + + const server = new McpServer( + { name: "filesystem-mcp", version: "1.0.0" }, + { capabilities: { tools: {} } }, + ); + + for (const tool of tools) { + const inputShape = + "shape" in tool.inputSchema + ? (tool.inputSchema.shape as z.ZodRawShape) + : z.object({}).shape; + const outputShape = + tool.outputSchema && "shape" in tool.outputSchema + ? (tool.outputSchema.shape as z.ZodRawShape) + : z.object({}).shape; + + server.registerTool( + tool.name, + { + description: tool.description ?? "", + inputSchema: inputShape, + outputSchema: outputShape, + annotations: tool.annotations, + }, + async (args) => { + try { + const result = await tool.handler(args); + return { + content: [{ type: "text" as const, text: JSON.stringify(result) }], + structuredContent: result as { [x: string]: unknown }, + }; + } catch (error) { + const err = error as Error; + return { + content: [{ type: "text" as const, text: `Error: ${err.message}` }], + isError: true, + }; + } + }, + ); + } + + const transport = new WebStandardStreamableHTTPServerTransport({ + enableJsonResponse: + req.headers.get("Accept")?.includes("application/json") ?? false, + }); + await server.connect(transport); + return transport.handleRequest(req); +} + +/** + * Call a filesystem tool directly + */ +export async function callFilesystemTool( + toolName: string, + args: Record, + ctx: MeshContext, +): Promise<{ content: unknown; isError?: boolean }> { + const org = requireOrganization(ctx); + const s3 = getFilesystemS3Service(); + + if (!s3) { + return { + content: [ + { + type: "text", + text: "Filesystem not configured. Set S3_* environment variables.", + }, + ], + isError: true, + }; + } + + const tools = createFilesystemTools(s3, org.id); + const tool = tools.find((t) => t.name === toolName); + + if (!tool) { + return { + content: [{ type: "text", text: `Tool not found: ${toolName}` }], + isError: true, + }; + } + + const parsed = tool.inputSchema.safeParse(args); + if (!parsed.success) { + return { + content: [ + { + type: "text", + text: `Invalid input: ${parsed.error.message}`, + }, + ], + isError: true, + }; + } + + try { + const result = await tool.handler(parsed.data as Record); + return { + content: [{ type: "text", text: JSON.stringify(result) }], + }; + } catch (error) { + return { + content: [ + { + type: "text", + text: error instanceof Error ? error.message : String(error), + }, + ], + isError: true, + }; + } +} + +/** + * Filesystem MCP endpoint + * + * Route: POST /mcp/filesystem + */ +app.all("/", async (c) => { + const ctx = c.get("meshContext"); + return handleFilesystemMcpRequest(c.req.raw, ctx); +}); + +export default app; diff --git a/apps/mesh/src/env.ts b/apps/mesh/src/env.ts index a0663bd30d..c753eaa844 100644 --- a/apps/mesh/src/env.ts +++ b/apps/mesh/src/env.ts @@ -43,6 +43,14 @@ const envSchema = z // Transport UNSAFE_ALLOW_STDIO_TRANSPORT: zBooleanString, + // Object Storage (S3-compatible) + S3_ENDPOINT: z.string().optional(), + S3_BUCKET: z.string().optional(), + S3_REGION: z.string().default("auto"), + S3_ACCESS_KEY_ID: z.string().optional(), + S3_SECRET_ACCESS_KEY: z.string().optional(), + S3_FORCE_PATH_STYLE: z.enum(["true", "false"]).optional().default("true"), + // AI Gateway DECO_AI_GATEWAY_ENABLED: zBooleanString, DECO_AI_GATEWAY_URL: z.string().default("https://ai-site.decocache.com"), @@ -97,6 +105,8 @@ const SECRET_KEYS = new Set([ "BETTER_AUTH_SECRET", "ENCRYPTION_KEY", "MESH_JWT_SECRET", + "S3_ACCESS_KEY_ID", + "S3_SECRET_ACCESS_KEY", ]); const URL_KEYS = new Set(["DATABASE_URL", "CLICKHOUSE_URL", "NATS_URL"]); @@ -166,6 +176,14 @@ function logConfiguration(e: Env) { sect("Transport"); r("UNSAFE_ALLOW_STDIO_TRANSPORT", e.UNSAFE_ALLOW_STDIO_TRANSPORT); + sect("Object Storage"); + r("S3_ENDPOINT", e.S3_ENDPOINT); + r("S3_BUCKET", e.S3_BUCKET); + r("S3_REGION", e.S3_REGION); + r("S3_ACCESS_KEY_ID", e.S3_ACCESS_KEY_ID); + r("S3_SECRET_ACCESS_KEY", e.S3_SECRET_ACCESS_KEY); + r("S3_FORCE_PATH_STYLE", e.S3_FORCE_PATH_STYLE); + sect("AI Gateway"); r("DECO_AI_GATEWAY_ENABLED", e.DECO_AI_GATEWAY_ENABLED); r("DECO_AI_GATEWAY_URL", e.DECO_AI_GATEWAY_URL); diff --git a/apps/mesh/src/filesystem/factory.ts b/apps/mesh/src/filesystem/factory.ts new file mode 100644 index 0000000000..595b7b6c85 --- /dev/null +++ b/apps/mesh/src/filesystem/factory.ts @@ -0,0 +1,58 @@ +/** + * Filesystem S3 Service Factory + * + * Creates and caches a singleton S3Service instance from environment variables. + * For v1, only mesh-level S3 config via env vars is supported. + */ + +import { S3Service } from "./s3-service"; + +let cachedService: S3Service | null = null; +let initialized = false; + +/** + * Get the mesh-level filesystem S3 service. + * Returns null if S3 is not configured (S3_* env vars not set). + * + * The service is created once and cached for the lifetime of the process. + */ +export function getFilesystemS3Service(): S3Service | null { + if (initialized) { + return cachedService; + } + + initialized = true; + + const endpoint = process.env.S3_ENDPOINT; + const bucket = process.env.S3_BUCKET; + const region = process.env.S3_REGION ?? "auto"; + const accessKeyId = process.env.S3_ACCESS_KEY_ID; + const secretAccessKey = process.env.S3_SECRET_ACCESS_KEY; + + if (!endpoint || !bucket || !accessKeyId || !secretAccessKey) { + return null; + } + + cachedService = new S3Service({ + endpoint, + bucket, + region, + accessKeyId, + secretAccessKey, + forcePathStyle: process.env.S3_FORCE_PATH_STYLE !== "false", + }); + + return cachedService; +} + +/** + * Check if filesystem is configured (S3 env vars are set). + */ +export function isFilesystemConfigured(): boolean { + return !!( + process.env.S3_ENDPOINT && + process.env.S3_BUCKET && + process.env.S3_ACCESS_KEY_ID && + process.env.S3_SECRET_ACCESS_KEY + ); +} diff --git a/apps/mesh/src/filesystem/path-utils.test.ts b/apps/mesh/src/filesystem/path-utils.test.ts new file mode 100644 index 0000000000..9f953515fa --- /dev/null +++ b/apps/mesh/src/filesystem/path-utils.test.ts @@ -0,0 +1,173 @@ +import { describe, expect, it } from "bun:test"; +import { + buildS3Key, + buildS3Prefix, + detectContentType, + isTextContentType, + sanitizePath, + stripOrgPrefix, +} from "./path-utils"; + +describe("sanitizePath", () => { + it("strips leading slashes", () => { + expect(sanitizePath("/foo/bar")).toBe("foo/bar"); + expect(sanitizePath("///foo")).toBe("foo"); + }); + + it("strips trailing slashes", () => { + expect(sanitizePath("foo/bar/")).toBe("foo/bar"); + }); + + it("removes .. segments", () => { + expect(sanitizePath("../../../etc/passwd")).toBe("etc/passwd"); + expect(sanitizePath("foo/../bar")).toBe("foo/bar"); + expect(sanitizePath("foo/../../bar")).toBe("foo/bar"); + }); + + it("removes . segments", () => { + expect(sanitizePath("./foo/./bar")).toBe("foo/bar"); + }); + + it("removes null bytes", () => { + expect(sanitizePath("file\0.txt")).toBe("file.txt"); + }); + + it("removes non-printable characters", () => { + expect(sanitizePath("file\x01\x02.txt")).toBe("file.txt"); + }); + + it("normalizes backslashes", () => { + expect(sanitizePath("foo\\bar\\baz")).toBe("foo/bar/baz"); + }); + + it("normalizes multiple slashes", () => { + expect(sanitizePath("foo//bar///baz")).toBe("foo/bar/baz"); + }); + + it("handles percent-encoded traversal", () => { + expect(sanitizePath("%2e%2e%2ffoo")).toBe("foo"); + }); + + it("strips percent-encoded null bytes", () => { + expect(sanitizePath("file%00.txt")).toBe("file.txt"); + }); + + it("handles malformed percent encoding gracefully", () => { + expect(sanitizePath("file%ZZ.txt")).toBe("file%ZZ.txt"); + }); + + it("handles empty string", () => { + expect(sanitizePath("")).toBe(""); + }); + + it("handles just dots", () => { + expect(sanitizePath("..")).toBe(""); + expect(sanitizePath(".")).toBe(""); + }); + + it("preserves normal paths", () => { + expect(sanitizePath("docs/readme.md")).toBe("docs/readme.md"); + expect(sanitizePath("src/components/Button.tsx")).toBe( + "src/components/Button.tsx", + ); + }); + + it("handles paths with spaces", () => { + expect(sanitizePath("my folder/my file.txt")).toBe("my folder/my file.txt"); + }); +}); + +describe("buildS3Key", () => { + it("prefixes with org ID", () => { + expect(buildS3Key("org_123", "docs/readme.md")).toBe( + "org_123/docs/readme.md", + ); + }); + + it("sanitizes the path", () => { + expect(buildS3Key("org_123", "../../../etc/passwd")).toBe( + "org_123/etc/passwd", + ); + }); + + it("throws for empty path", () => { + expect(() => buildS3Key("org_123", "")).toThrow("Path cannot be empty"); + expect(() => buildS3Key("org_123", "..")).toThrow("Path cannot be empty"); + }); + + it("prevents escaping org prefix via traversal", () => { + const key = buildS3Key("org_a", "../../org_b/secret.txt"); + expect(key).toBe("org_a/org_b/secret.txt"); + expect(key.startsWith("org_a/")).toBe(true); + }); +}); + +describe("buildS3Prefix", () => { + it("returns org root when no path given", () => { + expect(buildS3Prefix("org_123")).toBe("org_123/"); + expect(buildS3Prefix("org_123", undefined)).toBe("org_123/"); + expect(buildS3Prefix("org_123", "")).toBe("org_123/"); + }); + + it("appends trailing slash to directory path", () => { + expect(buildS3Prefix("org_123", "docs")).toBe("org_123/docs/"); + }); + + it("preserves trailing slash", () => { + expect(buildS3Prefix("org_123", "docs/")).toBe("org_123/docs/"); + }); +}); + +describe("stripOrgPrefix", () => { + it("strips the org prefix", () => { + expect(stripOrgPrefix("org_123", "org_123/docs/readme.md")).toBe( + "docs/readme.md", + ); + }); + + it("returns key unchanged if prefix doesn't match", () => { + expect(stripOrgPrefix("org_123", "other/readme.md")).toBe( + "other/readme.md", + ); + }); +}); + +describe("detectContentType", () => { + it("detects text types", () => { + expect(detectContentType("readme.md")).toBe("text/markdown"); + expect(detectContentType("style.css")).toBe("text/css"); + expect(detectContentType("index.html")).toBe("text/html"); + }); + + it("detects code types", () => { + expect(detectContentType("app.ts")).toBe("text/typescript"); + expect(detectContentType("config.json")).toBe("application/json"); + expect(detectContentType("script.js")).toBe("application/javascript"); + }); + + it("detects image types", () => { + expect(detectContentType("photo.png")).toBe("image/png"); + expect(detectContentType("photo.jpg")).toBe("image/jpeg"); + }); + + it("defaults to octet-stream for unknown", () => { + expect(detectContentType("file.xyz")).toBe("application/octet-stream"); + expect(detectContentType("noextension")).toBe("application/octet-stream"); + }); +}); + +describe("isTextContentType", () => { + it("returns true for text types", () => { + expect(isTextContentType("text/plain")).toBe(true); + expect(isTextContentType("text/markdown")).toBe(true); + expect(isTextContentType("application/json")).toBe(true); + expect(isTextContentType("application/javascript")).toBe(true); + expect(isTextContentType("image/svg+xml")).toBe(true); + }); + + it("returns false for binary types", () => { + expect(isTextContentType("image/png")).toBe(false); + expect(isTextContentType("application/octet-stream")).toBe(false); + expect(isTextContentType("application/pdf")).toBe(false); + }); +}); diff --git a/apps/mesh/src/filesystem/path-utils.ts b/apps/mesh/src/filesystem/path-utils.ts new file mode 100644 index 0000000000..1f7a37e9ad --- /dev/null +++ b/apps/mesh/src/filesystem/path-utils.ts @@ -0,0 +1,160 @@ +/** + * Path Utilities for Filesystem Operations + * + * Provides path sanitization and S3 key construction with org-level isolation. + * All user-provided paths are sanitized to prevent directory traversal attacks. + */ + +/** + * Sanitize a user-provided file path to prevent directory traversal and injection attacks. + * + * - Strips leading/trailing slashes + * - Removes `..` path segments + * - Removes null bytes + * - Removes non-printable characters + * - Normalizes multiple consecutive slashes + * - Rejects empty paths after sanitization + */ +export function sanitizePath(userPath: string): string { + let path = userPath; + + // Decode percent-encoded sequences first so encoded null bytes / control chars + // don't survive past the stripping step below. + try { + path = decodeURIComponent(path); + } catch { + // If decoding fails (malformed %), continue with the raw string + } + + // Remove null bytes + path = path.replace(/\0/g, ""); + + // Remove non-printable characters (control chars) + path = path.replace(/[\x00-\x1f\x7f]/g, ""); + + // Normalize backslashes to forward slashes + path = path.replace(/\\/g, "/"); + + // Remove leading/trailing slashes + path = path.replace(/^\/+|\/+$/g, ""); + + // Split into segments, remove '..' and '.' segments + const segments = path + .split("/") + .filter((s) => s !== ".." && s !== "." && s !== ""); + + // Rejoin and normalize multiple slashes + path = segments.join("/"); + + return path; +} + +/** + * Build an S3 key from an org ID and user-provided path. + * For the mesh-default shared bucket, the key is prefixed with the org ID. + * + * @param orgId - Organization ID (immutable, used as prefix) + * @param userPath - User-provided file path + * @returns The full S3 key with org prefix + */ +export function buildS3Key(orgId: string, userPath: string): string { + const sanitized = sanitizePath(userPath); + if (!sanitized) { + throw new Error("Path cannot be empty"); + } + return `${orgId}/${sanitized}`; +} + +/** + * Build an S3 prefix for listing operations. + * If no path is provided, returns the org root prefix. + * + * @param orgId - Organization ID + * @param userPath - Optional directory path + * @returns The S3 prefix for listing + */ +export function buildS3Prefix(orgId: string, userPath?: string): string { + if (!userPath) { + return `${orgId}/`; + } + const sanitized = sanitizePath(userPath); + if (!sanitized) { + return `${orgId}/`; + } + // Ensure prefix ends with / + return `${orgId}/${sanitized}${sanitized.endsWith("/") ? "" : "/"}`; +} + +/** + * Strip the org prefix from an S3 key to get the user-visible path. + * + * @param orgId - Organization ID + * @param s3Key - Full S3 key + * @returns The user-visible path without org prefix + */ +export function stripOrgPrefix(orgId: string, s3Key: string): string { + const prefix = `${orgId}/`; + if (s3Key.startsWith(prefix)) { + return s3Key.slice(prefix.length); + } + return s3Key; +} + +/** + * Detect content type from file extension. + * Returns a reasonable default for common file types. + */ +export function detectContentType(path: string): string { + const ext = path.split(".").pop()?.toLowerCase(); + const types: Record = { + // Text + txt: "text/plain", + md: "text/markdown", + html: "text/html", + css: "text/css", + csv: "text/csv", + xml: "text/xml", + // Code + js: "application/javascript", + mjs: "application/javascript", + ts: "text/typescript", + tsx: "text/typescript", + jsx: "application/javascript", + json: "application/json", + yaml: "application/yaml", + yml: "application/yaml", + toml: "application/toml", + // Images + png: "image/png", + jpg: "image/jpeg", + jpeg: "image/jpeg", + gif: "image/gif", + svg: "image/svg+xml", + webp: "image/webp", + ico: "image/x-icon", + // Documents + pdf: "application/pdf", + // Archives + zip: "application/zip", + gz: "application/gzip", + tar: "application/x-tar", + // Data + wasm: "application/wasm", + }; + return types[ext ?? ""] ?? "application/octet-stream"; +} + +/** + * Check if a content type is text-based (content can be returned as utf-8). + */ +export function isTextContentType(contentType: string): boolean { + return ( + contentType.startsWith("text/") || + contentType === "application/json" || + contentType === "application/javascript" || + contentType === "application/yaml" || + contentType === "application/toml" || + contentType === "application/xml" || + contentType === "image/svg+xml" + ); +} diff --git a/apps/mesh/src/filesystem/s3-service.ts b/apps/mesh/src/filesystem/s3-service.ts new file mode 100644 index 0000000000..fbaa5cf625 --- /dev/null +++ b/apps/mesh/src/filesystem/s3-service.ts @@ -0,0 +1,269 @@ +/** + * S3 Service + * + * Wraps @aws-sdk/client-s3 with org-scoped path isolation. + * Provides filesystem-like operations (read, write, list, delete, metadata) + * backed by any S3-compatible storage (AWS S3, Cloudflare R2, MinIO, Backblaze B2). + */ + +import { + DeleteObjectCommand, + GetObjectCommand, + HeadObjectCommand, + ListObjectsV2Command, + PutObjectCommand, + S3Client, +} from "@aws-sdk/client-s3"; +import { + buildS3Key, + buildS3Prefix, + detectContentType, + isTextContentType, + stripOrgPrefix, +} from "./path-utils"; +import type { + FsDeleteOutput, + FsListInput, + FsListOutput, + FsMetadataOutput, + FsReadOutput, + FsWriteOutput, +} from "@decocms/bindings/filesystem"; + +/** Maximum file size for inline content reads (1 MB) */ +const MAX_INLINE_READ_SIZE = 1 * 1024 * 1024; + +export interface S3Config { + endpoint: string; + bucket: string; + region: string; + accessKeyId: string; + secretAccessKey: string; + forcePathStyle?: boolean; +} + +export class S3Service { + private client: S3Client; + private bucket: string; + + constructor(config: S3Config) { + this.bucket = config.bucket; + this.client = new S3Client({ + endpoint: config.endpoint, + region: config.region, + credentials: { + accessKeyId: config.accessKeyId, + secretAccessKey: config.secretAccessKey, + }, + forcePathStyle: config.forcePathStyle ?? true, + }); + } + + async readFile( + orgId: string, + path: string, + offset?: number, + limit?: number, + ): Promise { + const key = buildS3Key(orgId, path); + + // First, HEAD to get metadata + const head = await this.client + .send(new HeadObjectCommand({ Bucket: this.bucket, Key: key })) + .catch( + (err: { name?: string; $metadata?: { httpStatusCode?: number } }) => { + if ( + err.name === "NotFound" || + err.$metadata?.httpStatusCode === 404 + ) { + return null; + } + throw err; + }, + ); + + if (!head) { + return { size: 0, error: "FILE_NOT_FOUND" }; + } + + const size = head.ContentLength ?? 0; + const contentType = head.ContentType ?? detectContentType(path); + + // Check size limit for full reads (partial reads are always allowed) + if (!offset && !limit && size > MAX_INLINE_READ_SIZE) { + return { size, contentType, error: "FILE_TOO_LARGE" }; + } + + // Build range header for partial reads + let range: string | undefined; + if (offset !== undefined || limit !== undefined) { + const start = offset ?? 0; + const end = limit !== undefined ? start + limit - 1 : ""; + range = `bytes=${start}-${end}`; + } + + const response = await this.client.send( + new GetObjectCommand({ + Bucket: this.bucket, + Key: key, + Range: range, + }), + ); + + if (!response.Body) { + return { size, contentType, error: "FILE_NOT_FOUND" }; + } + + const isText = isTextContentType(contentType); + + if (isText) { + const content = await response.Body.transformToString("utf-8"); + return { content, encoding: "utf-8", contentType, size }; + } + + // Binary content — return as base64 + const bytes = await response.Body.transformToByteArray(); + const content = Buffer.from(bytes).toString("base64"); + return { content, encoding: "base64", contentType, size }; + } + + async writeFile( + orgId: string, + path: string, + content: string, + encoding: "utf-8" | "base64" = "utf-8", + contentType?: string, + ): Promise { + const key = buildS3Key(orgId, path); + const resolvedContentType = contentType ?? detectContentType(path); + + let body: Buffer; + if (encoding === "base64") { + body = Buffer.from(content, "base64"); + } else { + body = Buffer.from(content, "utf-8"); + } + + await this.client.send( + new PutObjectCommand({ + Bucket: this.bucket, + Key: key, + Body: body, + ContentType: resolvedContentType, + }), + ); + + return { path, size: body.length }; + } + + async listFiles(orgId: string, input: FsListInput): Promise { + const prefix = buildS3Prefix(orgId, input.path); + const maxKeys = Math.min(input.maxKeys ?? 100, 1000); + + const response = await this.client.send( + new ListObjectsV2Command({ + Bucket: this.bucket, + Prefix: prefix, + Delimiter: "/", + MaxKeys: maxKeys, + ContinuationToken: input.continuationToken, + }), + ); + + const entries: FsListOutput["entries"] = []; + + // Add directories (common prefixes) + if (response.CommonPrefixes) { + for (const cp of response.CommonPrefixes) { + if (cp.Prefix) { + const dirPath = stripOrgPrefix(orgId, cp.Prefix); + // Filter by pattern if provided + if (input.pattern && !matchGlob(dirPath, input.pattern)) { + continue; + } + entries.push({ path: dirPath, type: "directory" }); + } + } + } + + // Add files + if (response.Contents) { + for (const obj of response.Contents) { + if (!obj.Key) continue; + // Skip the prefix itself (S3 can return the directory marker) + if (obj.Key === prefix) continue; + + const filePath = stripOrgPrefix(orgId, obj.Key); + // Filter by pattern if provided + if (input.pattern && !matchGlob(filePath, input.pattern)) { + continue; + } + entries.push({ + path: filePath, + type: "file", + size: obj.Size, + lastModified: obj.LastModified?.toISOString(), + }); + } + } + + return { + entries, + isTruncated: response.IsTruncated ?? false, + nextContinuationToken: response.NextContinuationToken, + }; + } + + async deleteFile(orgId: string, path: string): Promise { + const key = buildS3Key(orgId, path); + + await this.client.send( + new DeleteObjectCommand({ Bucket: this.bucket, Key: key }), + ); + + // S3 DeleteObject is idempotent (returns 204 even for nonexistent keys) + return { success: true, path }; + } + + async getMetadata(orgId: string, path: string): Promise { + const key = buildS3Key(orgId, path); + + const head = await this.client.send( + new HeadObjectCommand({ Bucket: this.bucket, Key: key }), + ); + + return { + size: head.ContentLength ?? 0, + contentType: head.ContentType ?? detectContentType(path), + lastModified: + head.LastModified?.toISOString() ?? new Date().toISOString(), + etag: head.ETag ?? "", + }; + } + + destroy(): void { + this.client.destroy(); + } +} + +/** + * Simple glob pattern matching for filtering file paths. + * Supports * (any chars except /) and ** (any chars including /). + */ +function matchGlob(path: string, pattern: string): boolean { + // Strip trailing slash so directory entries (e.g. "docs/") match by name + const normalized = path.endsWith("/") ? path.slice(0, -1) : path; + // Get just the filename for patterns without / + const target = pattern.includes("/") + ? normalized + : (normalized.split("/").pop() ?? normalized); + + const regexStr = pattern + .replace(/[.+^${}()|[\]\\]/g, "\\$&") + .replace(/\*\*/g, "{{GLOBSTAR}}") + .replace(/\*/g, "[^/]*") + .replace(/{{GLOBSTAR}}/g, ".*") + .replace(/\?/g, "[^/]"); + + return new RegExp(`^${regexStr}$`).test(target); +} diff --git a/apps/mesh/src/services/ensure-services.ts b/apps/mesh/src/services/ensure-services.ts index 43bc4926c6..fa44d398a5 100644 --- a/apps/mesh/src/services/ensure-services.ts +++ b/apps/mesh/src/services/ensure-services.ts @@ -8,14 +8,14 @@ import { existsSync, mkdirSync, readFileSync, + readdirSync, symlinkSync, writeFileSync, } from "fs"; -import { chmod, unlink } from "fs/promises"; -import { createRequire } from "module"; +import { chmod, rm, unlink } from "fs/promises"; import { createConnection } from "net"; import { arch, homedir, platform } from "os"; -import { dirname, join } from "path"; +import { join } from "path"; // --------------------------------------------------------------------------- // Constants @@ -162,52 +162,53 @@ interface ServiceInfo { // --------------------------------------------------------------------------- /** - * Fix missing .dylib symlinks in the embedded-postgres platform package. - * - * The npm package ships a `pg-symlinks.json` manifest listing symlinks that - * must exist (e.g. libicudata.77.1.dylib → libicudata.77.dylib). These are - * created by a postinstall script, but bun doesn't always run postinstall for - * optional platform packages, so we re-hydrate them at startup. - * - * We locate the platform package by using createRequire scoped to the - * embedded-postgres module, which can resolve its optional dependencies - * regardless of directory layout (.bun cache, flat node_modules, or bunx). + * Fix missing .dylib symlinks in the embedded-postgres package. + * The npm package ships versioned libs (e.g. libicudata.77.1.dylib) but + * binaries reference the short name (libicudata.77.dylib). Symlinks are + * lost during npm packaging, so we recreate them at startup. */ function fixEmbeddedPostgresLibSymlinks() { - try { - // Resolve the platform-specific package from embedded-postgres's own - // module context using createRequire. This works regardless of directory - // layout (.bun cache, flat node_modules, bunx temporary installs). - const epPath = require.resolve("embedded-postgres"); - const requireFromEp = createRequire(epPath); - const platformPkgName = `@embedded-postgres/${platform()}-${arch()}`; - const resolved = requireFromEp.resolve(platformPkgName); - - // resolved = /dist/index.js — navigate up to package root - const pkgRoot = join(dirname(resolved), ".."); - const symlinksFile = join(pkgRoot, "native", "pg-symlinks.json"); - - if (!existsSync(symlinksFile)) return; - - const symlinks: { source: string; target: string }[] = JSON.parse( - readFileSync(symlinksFile, "utf-8"), - ); + if (platform() !== "darwin") return; - for (const { source, target } of symlinks) { - const absTarget = join(pkgRoot, target); - if (existsSync(absTarget)) continue; - - const targetDir = join(absTarget, ".."); - const sourceName = source.split("/").pop()!; - const targetName = target.split("/").pop()!; - const cwd = process.cwd(); - try { - process.chdir(targetDir); - symlinkSync(sourceName, targetName); - } catch { - // Symlink may already exist from a concurrent run - } finally { - process.chdir(cwd); + try { + // Locate the platform-specific package lib directory. + // Bun stores it at node_modules/.bun/@embedded-postgres+-@/ + const epDir = require.resolve("embedded-postgres"); + // Walk up from .bun/embedded-postgres@ver/node_modules/embedded-postgres/dist/index.js + // to the .bun/ directory + const bunCacheDir = join(epDir, "..", "..", "..", "..", ".."); + const platformPkg = `@embedded-postgres+${platform()}-${arch()}`; + // Find the versioned directory + const candidates = existsSync(bunCacheDir) + ? readdirSync(bunCacheDir).filter((d) => d.startsWith(platformPkg)) + : []; + + for (const candidate of candidates) { + const libDir = join( + bunCacheDir, + candidate, + "node_modules", + "@embedded-postgres", + `${platform()}-${arch()}`, + "native", + "lib", + ); + if (!existsSync(libDir)) continue; + + for (const file of readdirSync(libDir)) { + // Create symlinks for versioned dylibs, e.g.: + // libicudata.77.1.dylib → libicudata.77.dylib → libicudata.dylib + const match = file.match(/^(.+)\.(\d+)\.(\d+)\.dylib$/); + if (!match) continue; + const [, base, major] = match; + const midName = `${base}.${major}.dylib`; + const shortName = `${base}.dylib`; + for (const name of [midName, shortName]) { + const target = join(libDir, name); + if (!existsSync(target)) { + symlinkSync(file, target); + } + } } } } catch { @@ -261,29 +262,11 @@ async function ensurePostgres(): Promise { const pgVersionFile = join(dataDir, "PG_VERSION"); if (!existsSync(pgVersionFile)) { - try { - await pg.initialise(); - } catch (initErr) { - // initdb may have been killed by a signal (exit code null) due to a race - // with another process initializing the same data directory. Log the - // error for debugging — do NOT remove the data dir as it may contain - // important data from a prior run. - console.error( - `[ensurePostgres] pg.initialise() failed. dataDir=${dataDir}`, - initErr, - ); - - // Another process (e.g. another workspace) may have won the race and - // already started postgres on our port. - if (await probePort(PG_PORT)) { - info.state = "running"; - info.pid = findPidOnPort(PG_PORT); - info.owner = "managed"; - return info; - } - - throw initErr; + // Clean up empty/broken data dir — initdb fails if the directory exists + if (existsSync(dataDir) && readdirSync(dataDir).length === 0) { + await rm(dataDir, { recursive: true, force: true }); } + await pg.initialise(); } await pg.start(); diff --git a/apps/mesh/src/tools/connection/filesystem.ts b/apps/mesh/src/tools/connection/filesystem.ts new file mode 100644 index 0000000000..e016214c5a --- /dev/null +++ b/apps/mesh/src/tools/connection/filesystem.ts @@ -0,0 +1,82 @@ +/** + * Filesystem Connection Utilities + * + * Shared utilities for the S3-backed filesystem connection. + * This connection is injected when S3 is configured to provide + * filesystem functionality for AI agents. + */ + +import { FILESYSTEM_BINDING } from "@decocms/bindings/filesystem"; +import { + getWellKnownFilesystemConnection, + WellKnownOrgMCPId, +} from "@decocms/mesh-sdk"; +import { z } from "zod"; +import { isFilesystemConfigured } from "../../filesystem/factory"; +import { type ConnectionEntity, type ToolDefinition } from "./schema"; + +/** + * Cached tool definitions for filesystem connection. + * Computed once at module load time to avoid repeated z.toJSONSchema conversions. + */ +const FILESYSTEM_TOOLS: ToolDefinition[] = FILESYSTEM_BINDING.map( + (binding: (typeof FILESYSTEM_BINDING)[number]) => ({ + name: binding.name, + description: `${binding.name} operation for S3-backed filesystem`, + inputSchema: z.toJSONSchema(binding.inputSchema) as Record, + outputSchema: z.toJSONSchema(binding.outputSchema) as Record< + string, + unknown + >, + }), +); + +/** + * Check if a connection ID is the filesystem connection for an organization + */ +export function isFilesystemConnection( + connectionId: string, + organizationId: string, +): boolean { + return connectionId === WellKnownOrgMCPId.FILESYSTEM(organizationId); +} + +/** + * Create a filesystem connection entity for S3-backed file storage. + * This is injected when S3 is configured to provide filesystem + * functionality for AI agents. + */ +export function createFilesystemConnectionEntity( + orgId: string, + baseUrl: string, +): ConnectionEntity { + const connectionData = getWellKnownFilesystemConnection(baseUrl, orgId); + + const now = new Date().toISOString(); + + return { + id: connectionData.id ?? WellKnownOrgMCPId.FILESYSTEM(orgId), + title: connectionData.title, + description: connectionData.description ?? null, + icon: connectionData.icon ?? null, + app_name: connectionData.app_name ?? null, + app_id: connectionData.app_id ?? null, + organization_id: orgId, + created_by: "system", + created_at: now, + updated_at: now, + connection_type: connectionData.connection_type, + connection_url: connectionData.connection_url ?? null, + connection_token: null, + connection_headers: null, + oauth_config: null, + configuration_state: null, + configuration_scopes: null, + metadata: connectionData.metadata ?? null, + tools: FILESYSTEM_TOOLS, + bindings: ["FILESYSTEM"], + status: "active", + }; +} + +export { isFilesystemConfigured }; diff --git a/apps/mesh/src/tools/connection/get.ts b/apps/mesh/src/tools/connection/get.ts index e03f40fd36..77d97ed6a1 100644 --- a/apps/mesh/src/tools/connection/get.ts +++ b/apps/mesh/src/tools/connection/get.ts @@ -16,6 +16,11 @@ import { isDevAssetsConnection, isDevMode, } from "./dev-assets"; +import { + createFilesystemConnectionEntity, + isFilesystemConfigured, + isFilesystemConnection, +} from "./filesystem"; import { ConnectionEntitySchema } from "./schema"; /** @@ -52,6 +57,16 @@ export const COLLECTION_CONNECTIONS_GET = defineTool({ }; } + // When S3 is configured, check if this is the filesystem connection + if ( + isFilesystemConfigured() && + isFilesystemConnection(input.id, organization.id) + ) { + return { + item: createFilesystemConnectionEntity(organization.id, getBaseUrl()), + }; + } + // Get connection from database const connection = await ctx.storage.connections.findById(input.id); diff --git a/apps/mesh/src/tools/connection/list.ts b/apps/mesh/src/tools/connection/list.ts index 986c48773f..3e6b61a5b1 100644 --- a/apps/mesh/src/tools/connection/list.ts +++ b/apps/mesh/src/tools/connection/list.ts @@ -14,6 +14,7 @@ import { type WhereExpression, } from "@decocms/bindings/collections"; import { LANGUAGE_MODEL_BINDING } from "@decocms/bindings/llm"; +import { FILESYSTEM_BINDING } from "@decocms/bindings/filesystem"; import { OBJECT_STORAGE_BINDING } from "@decocms/bindings/object-storage"; import { WellKnownOrgMCPId } from "@decocms/mesh-sdk"; import { z } from "zod"; @@ -21,12 +22,17 @@ import { defineTool } from "../../core/define-tool"; import { getBaseUrl } from "../../core/server-constants"; import { requireOrganization } from "../../core/mesh-context"; import { createDevAssetsConnectionEntity, isDevMode } from "./dev-assets"; +import { + createFilesystemConnectionEntity, + isFilesystemConfigured, +} from "./filesystem"; import { type ConnectionEntity, ConnectionEntitySchema } from "./schema"; const BUILTIN_BINDING_CHECKERS: Record = { LLM: LANGUAGE_MODEL_BINDING, ASSISTANTS: ASSISTANTS_BINDING, OBJECT_STORAGE: OBJECT_STORAGE_BINDING, + FILESYSTEM: FILESYSTEM_BINDING, }; /** @@ -267,6 +273,20 @@ export const COLLECTION_CONNECTIONS_LIST = defineTool({ } } + // When S3 is configured, inject the filesystem connection + if (isFilesystemConfigured()) { + const baseUrl = getBaseUrl(); + const filesystemId = WellKnownOrgMCPId.FILESYSTEM(organization.id); + + if (!connections.some((c) => c.id === filesystemId)) { + const filesystemConnection = createFilesystemConnectionEntity( + organization.id, + baseUrl, + ); + connections.unshift(filesystemConnection); + } + } + // Filter connections by binding if specified (tools are pre-populated at create/update time) let filteredConnections = bindingChecker ? await Promise.all( diff --git a/apps/mesh/src/tools/filesystem/schema.ts b/apps/mesh/src/tools/filesystem/schema.ts new file mode 100644 index 0000000000..075422dc3f --- /dev/null +++ b/apps/mesh/src/tools/filesystem/schema.ts @@ -0,0 +1,29 @@ +/** + * Filesystem Schemas + * + * Re-exports schemas from @decocms/bindings for use in MCP tools. + * The bindings package is the source of truth for these schemas. + */ + +export { + FsReadInputSchema, + type FsReadInput, + FsReadOutputSchema, + type FsReadOutput, + FsWriteInputSchema, + type FsWriteInput, + FsWriteOutputSchema, + type FsWriteOutput, + FsListInputSchema, + type FsListInput, + FsListOutputSchema, + type FsListOutput, + FsDeleteInputSchema, + type FsDeleteInput, + FsDeleteOutputSchema, + type FsDeleteOutput, + FsMetadataInputSchema, + type FsMetadataInput, + FsMetadataOutputSchema, + type FsMetadataOutput, +} from "@decocms/bindings"; diff --git a/apps/mesh/src/web/components/details/virtual-mcp/index.tsx b/apps/mesh/src/web/components/details/virtual-mcp/index.tsx index 8f9b0608a0..ef2c184779 100644 --- a/apps/mesh/src/web/components/details/virtual-mcp/index.tsx +++ b/apps/mesh/src/web/components/details/virtual-mcp/index.tsx @@ -21,6 +21,7 @@ import { } from "@deco/ui/components/tooltip.tsx"; import { ORG_ADMIN_PROJECT_SLUG, + WellKnownOrgMCPId, useConnection, useProjectContext, useVirtualMCP, @@ -33,6 +34,7 @@ import { ChevronUp, CubeOutline, File02, + HardDrive, Loading01, Play, Plus, @@ -182,6 +184,69 @@ SkillItem.Fallback = function SkillItemFallback() { ); }; +/** + * Filesystem Access Toggle + * + * Shows a toggle to enable/disable filesystem access for the agent. + * Only visible when the filesystem connection exists (S3 is configured). + * Adding filesystem access creates a connection aggregation with all tools selected. + */ +function FilesystemAccessToggle({ + orgId, + connections, + form, +}: { + orgId: string; + connections: VirtualMCPConnection[]; + form: ReturnType>; +}) { + const filesystemId = WellKnownOrgMCPId.FILESYSTEM(orgId); + const filesystemConnection = useConnection(filesystemId); + + // Don't render if filesystem is not configured (connection doesn't exist) + if (!filesystemConnection) return null; + + const isEnabled = connections.some((c) => c.connection_id === filesystemId); + + const handleToggle = (checked: boolean) => { + if (checked) { + // Add filesystem connection with all tools selected + form.setValue( + "connections", + [ + ...connections, + { + connection_id: filesystemId, + selected_tools: null, + selected_resources: null, + selected_prompts: null, + }, + ], + { shouldDirty: true }, + ); + } else { + // Remove filesystem connection + form.setValue( + "connections", + connections.filter((c) => c.connection_id !== filesystemId), + { shouldDirty: true }, + ); + } + }; + + return ( +
+
+ +

+ Filesystem Access +

+
+ +
+ ); +} + function VirtualMcpDetailViewWithData({ virtualMcp, }: { @@ -535,6 +600,15 @@ function VirtualMcpDetailViewWithData({ + {/* Filesystem Access section */} + + + + {/* Instructions section */}

diff --git a/bun.lock b/bun.lock index 175d7a21af..475c57f9ee 100644 --- a/bun.lock +++ b/bun.lock @@ -55,7 +55,7 @@ }, "apps/mesh": { "name": "decocms", - "version": "2.175.6", + "version": "2.178.1", "bin": { "deco": "./dist/server/cli.js", }, @@ -78,6 +78,7 @@ "devDependencies": { "@ai-sdk/provider": "^3.0.8", "@ai-sdk/react": "^3.0.118", + "@aws-sdk/client-s3": "^3.1010.0", "@better-auth/sso": "1.4.1", "@daveyplate/better-auth-ui": "^3.2.7", "@deco/ui": "workspace:*", @@ -494,6 +495,86 @@ "@authenio/xml-encryption": ["@authenio/xml-encryption@2.0.2", "", { "dependencies": { "@xmldom/xmldom": "^0.8.6", "escape-html": "^1.0.3", "xpath": "0.0.32" } }, "sha512-cTlrKttbrRHEw3W+0/I609A2Matj5JQaRvfLtEIGZvlN0RaPi+3ANsMeqAyCAVlH/lUIW2tmtBlSMni74lcXeg=="], + "@aws-crypto/crc32": ["@aws-crypto/crc32@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg=="], + + "@aws-crypto/crc32c": ["@aws-crypto/crc32c@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag=="], + + "@aws-crypto/sha1-browser": ["@aws-crypto/sha1-browser@5.2.0", "", { "dependencies": { "@aws-crypto/supports-web-crypto": "^5.2.0", "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg=="], + + "@aws-crypto/sha256-browser": ["@aws-crypto/sha256-browser@5.2.0", "", { "dependencies": { "@aws-crypto/sha256-js": "^5.2.0", "@aws-crypto/supports-web-crypto": "^5.2.0", "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw=="], + + "@aws-crypto/sha256-js": ["@aws-crypto/sha256-js@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA=="], + + "@aws-crypto/supports-web-crypto": ["@aws-crypto/supports-web-crypto@5.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg=="], + + "@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="], + + "@aws-sdk/client-s3": ["@aws-sdk/client-s3@3.1011.0", "", { "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.20", "@aws-sdk/credential-provider-node": "^3.972.21", "@aws-sdk/middleware-bucket-endpoint": "^3.972.8", "@aws-sdk/middleware-expect-continue": "^3.972.8", "@aws-sdk/middleware-flexible-checksums": "^3.974.0", "@aws-sdk/middleware-host-header": "^3.972.8", "@aws-sdk/middleware-location-constraint": "^3.972.8", "@aws-sdk/middleware-logger": "^3.972.8", "@aws-sdk/middleware-recursion-detection": "^3.972.8", "@aws-sdk/middleware-sdk-s3": "^3.972.20", "@aws-sdk/middleware-ssec": "^3.972.8", "@aws-sdk/middleware-user-agent": "^3.972.21", "@aws-sdk/region-config-resolver": "^3.972.8", "@aws-sdk/signature-v4-multi-region": "^3.996.8", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@aws-sdk/util-user-agent-browser": "^3.972.8", "@aws-sdk/util-user-agent-node": "^3.973.7", "@smithy/config-resolver": "^4.4.11", "@smithy/core": "^3.23.11", "@smithy/eventstream-serde-browser": "^4.2.12", "@smithy/eventstream-serde-config-resolver": "^4.3.12", "@smithy/eventstream-serde-node": "^4.2.12", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/hash-blob-browser": "^4.2.13", "@smithy/hash-node": "^4.2.12", "@smithy/hash-stream-node": "^4.2.12", "@smithy/invalid-dependency": "^4.2.12", "@smithy/md5-js": "^4.2.12", "@smithy/middleware-content-length": "^4.2.12", "@smithy/middleware-endpoint": "^4.4.25", "@smithy/middleware-retry": "^4.4.42", "@smithy/middleware-serde": "^4.2.14", "@smithy/middleware-stack": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/node-http-handler": "^4.4.16", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.5", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.41", "@smithy/util-defaults-mode-node": "^4.2.44", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.12", "@smithy/util-stream": "^4.5.19", "@smithy/util-utf8": "^4.2.2", "@smithy/util-waiter": "^4.2.13", "tslib": "^2.6.2" } }, "sha512-jY7CGX+vfM/DSi4K8UwaZKoXnhqchmAbKFB1kIuHMfPPqW7l3jC/fUVDb95/njMsB2ymYOTusZEzoCTeUB/4qA=="], + + "@aws-sdk/core": ["@aws-sdk/core@3.973.20", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@aws-sdk/xml-builder": "^3.972.11", "@smithy/core": "^3.23.11", "@smithy/node-config-provider": "^4.3.12", "@smithy/property-provider": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/signature-v4": "^5.3.12", "@smithy/smithy-client": "^4.12.5", "@smithy/types": "^4.13.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-i3GuX+lowD892F3IuJf8o6AbyDupMTdyTxQrCJGcn71ni5hTZ82L4nQhcdumxZ7XPJRJJVHS/CR3uYOIIs0PVA=="], + + "@aws-sdk/crc64-nvme": ["@aws-sdk/crc64-nvme@3.972.5", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-2VbTstbjKdT+yKi8m7b3a9CiVac+pL/IY2PHJwsaGkkHmuuqkJZIErPck1h6P3T9ghQMLSdMPyW6Qp7Di5swFg=="], + + "@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.18", "", { "dependencies": { "@aws-sdk/core": "^3.973.20", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-X0B8AlQY507i5DwjLByeU2Af4ARsl9Vr84koDcXCbAkplmU+1xBFWxEPrWRAoh56waBne/yJqEloSwvRf4x6XA=="], + + "@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.20", "", { "dependencies": { "@aws-sdk/core": "^3.973.20", "@aws-sdk/types": "^3.973.6", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/node-http-handler": "^4.4.16", "@smithy/property-provider": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.5", "@smithy/types": "^4.13.1", "@smithy/util-stream": "^4.5.19", "tslib": "^2.6.2" } }, "sha512-ey9Lelj001+oOfrbKmS6R2CJAiXX7QKY4Vj9VJv6L2eE6/VjD8DocHIoYqztTm70xDLR4E1jYPTKfIui+eRNDA=="], + + "@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.20", "", { "dependencies": { "@aws-sdk/core": "^3.973.20", "@aws-sdk/credential-provider-env": "^3.972.18", "@aws-sdk/credential-provider-http": "^3.972.20", "@aws-sdk/credential-provider-login": "^3.972.20", "@aws-sdk/credential-provider-process": "^3.972.18", "@aws-sdk/credential-provider-sso": "^3.972.20", "@aws-sdk/credential-provider-web-identity": "^3.972.20", "@aws-sdk/nested-clients": "^3.996.10", "@aws-sdk/types": "^3.973.6", "@smithy/credential-provider-imds": "^4.2.12", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-5flXSnKHMloObNF+9N0cupKegnH1Z37cdVlpETVgx8/rAhCe+VNlkcZH3HDg2SDn9bI765S+rhNPXGDJJPfbtA=="], + + "@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.20", "", { "dependencies": { "@aws-sdk/core": "^3.973.20", "@aws-sdk/nested-clients": "^3.996.10", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-gEWo54nfqp2jABMu6HNsjVC4hDLpg9HC8IKSJnp0kqWtxIJYHTmiLSsIfI4ScQjxEwpB+jOOH8dOLax1+hy/Hw=="], + + "@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.21", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.18", "@aws-sdk/credential-provider-http": "^3.972.20", "@aws-sdk/credential-provider-ini": "^3.972.20", "@aws-sdk/credential-provider-process": "^3.972.18", "@aws-sdk/credential-provider-sso": "^3.972.20", "@aws-sdk/credential-provider-web-identity": "^3.972.20", "@aws-sdk/types": "^3.973.6", "@smithy/credential-provider-imds": "^4.2.12", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-hah8if3/B/Q+LBYN5FukyQ1Mym6PLPDsBOBsIgNEYD6wLyZg0UmUF/OKIVC3nX9XH8TfTPuITK+7N/jenVACWA=="], + + "@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.18", "", { "dependencies": { "@aws-sdk/core": "^3.973.20", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Tpl7SRaPoOLT32jbTWchPsn52hYYgJ0kpiFgnwk8pxTANQdUymVSZkzFvv1+oOgZm1CrbQUP9MBeoMZ9IzLZjA=="], + + "@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.20", "", { "dependencies": { "@aws-sdk/core": "^3.973.20", "@aws-sdk/nested-clients": "^3.996.10", "@aws-sdk/token-providers": "3.1009.0", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-p+R+PYR5Z7Gjqf/6pvbCnzEHcqPCpLzR7Yf127HjJ6EAb4hUcD+qsNRnuww1sB/RmSeCLxyay8FMyqREw4p1RA=="], + + "@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.20", "", { "dependencies": { "@aws-sdk/core": "^3.973.20", "@aws-sdk/nested-clients": "^3.996.10", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-rWCmh8o7QY4CsUj63qopzMzkDq/yPpkrpb+CnjBEFSOg/02T/we7sSTVg4QsDiVS9uwZ8VyONhq98qt+pIh3KA=="], + + "@aws-sdk/middleware-bucket-endpoint": ["@aws-sdk/middleware-bucket-endpoint@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-arn-parser": "^3.972.3", "@smithy/node-config-provider": "^4.3.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-config-provider": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-WR525Rr2QJSETa9a050isktyWi/4yIGcmY3BQ1kpHqb0LqUglQHCS8R27dTJxxWNZvQ0RVGtEZjTCbZJpyF3Aw=="], + + "@aws-sdk/middleware-expect-continue": ["@aws-sdk/middleware-expect-continue@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-5DTBTiotEES1e2jOHAq//zyzCjeMB78lEHd35u15qnrid4Nxm7diqIf9fQQ3Ov0ChH1V3Vvt13thOnrACmfGVQ=="], + + "@aws-sdk/middleware-flexible-checksums": ["@aws-sdk/middleware-flexible-checksums@3.974.0", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", "@aws-crypto/util": "5.2.0", "@aws-sdk/core": "^3.973.20", "@aws-sdk/crc64-nvme": "^3.972.5", "@aws-sdk/types": "^3.973.6", "@smithy/is-array-buffer": "^4.2.2", "@smithy/node-config-provider": "^4.3.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-middleware": "^4.2.12", "@smithy/util-stream": "^4.5.19", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-BmdDjqvnuYaC4SY7ypHLXfCSsGYGUZkjCLSZyUAAYn1YT28vbNMJNDwhlfkvvE+hQHG5RJDlEmYuvBxcB9jX1g=="], + + "@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-wAr2REfKsqoKQ+OkNqvOShnBoh+nkPurDKW7uAeVSu6kUECnWlSJiPvnoqxGlfousEY/v9LfS9sNc46hjSYDIQ=="], + + "@aws-sdk/middleware-location-constraint": ["@aws-sdk/middleware-location-constraint@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-KaUoFuoFPziIa98DSQsTPeke1gvGXlc5ZGMhy+b+nLxZ4A7jmJgLzjEF95l8aOQN2T/qlPP3MrAyELm8ExXucw=="], + + "@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-CWl5UCM57WUFaFi5kB7IBY1UmOeLvNZAZ2/OZ5l20ldiJ3TiIz1pC65gYj8X0BCPWkeR1E32mpsCk1L1I4n+lA=="], + + "@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-BnnvYs2ZEpdlmZ2PNlV2ZyQ8j8AEkMTjN79y/YA475ER1ByFYrkVR85qmhni8oeTaJcDqbx364wDpitDAA/wCA=="], + + "@aws-sdk/middleware-sdk-s3": ["@aws-sdk/middleware-sdk-s3@3.972.20", "", { "dependencies": { "@aws-sdk/core": "^3.973.20", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-arn-parser": "^3.972.3", "@smithy/core": "^3.23.11", "@smithy/node-config-provider": "^4.3.12", "@smithy/protocol-http": "^5.3.12", "@smithy/signature-v4": "^5.3.12", "@smithy/smithy-client": "^4.12.5", "@smithy/types": "^4.13.1", "@smithy/util-config-provider": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-stream": "^4.5.19", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-yhva/xL5H4tWQgsBjwV+RRD0ByCzg0TcByDCLp3GXdn/wlyRNfy8zsswDtCvr1WSKQkSQYlyEzPuWkJG0f5HvQ=="], + + "@aws-sdk/middleware-ssec": ["@aws-sdk/middleware-ssec@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-wqlK0yO/TxEC2UsY9wIlqeeutF6jjLe0f96Pbm40XscTo57nImUk9lBcw0dPgsm0sppFtAkSlDrfpK+pC30Wqw=="], + + "@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.21", "", { "dependencies": { "@aws-sdk/core": "^3.973.20", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@smithy/core": "^3.23.11", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-retry": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-62XRl1GDYPpkt7cx1AX1SPy9wgNE9Iw/NPuurJu4lmhCWS7sGKO+kS53TQ8eRmIxy3skmvNInnk0ZbWrU5Dpyg=="], + + "@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.10", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.20", "@aws-sdk/middleware-host-header": "^3.972.8", "@aws-sdk/middleware-logger": "^3.972.8", "@aws-sdk/middleware-recursion-detection": "^3.972.8", "@aws-sdk/middleware-user-agent": "^3.972.21", "@aws-sdk/region-config-resolver": "^3.972.8", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@aws-sdk/util-user-agent-browser": "^3.972.8", "@aws-sdk/util-user-agent-node": "^3.973.7", "@smithy/config-resolver": "^4.4.11", "@smithy/core": "^3.23.11", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/hash-node": "^4.2.12", "@smithy/invalid-dependency": "^4.2.12", "@smithy/middleware-content-length": "^4.2.12", "@smithy/middleware-endpoint": "^4.4.25", "@smithy/middleware-retry": "^4.4.42", "@smithy/middleware-serde": "^4.2.14", "@smithy/middleware-stack": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/node-http-handler": "^4.4.16", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.5", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.41", "@smithy/util-defaults-mode-node": "^4.2.44", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.12", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-SlDol5Z+C7Ivnc2rKGqiqfSUmUZzY1qHfVs9myt/nxVwswgfpjdKahyTzLTx802Zfq0NFRs7AejwKzzzl5Co2w=="], + + "@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/config-resolver": "^4.4.11", "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-1eD4uhTDeambO/PNIDVG19A6+v4NdD7xzwLHDutHsUqz0B+i661MwQB2eYO4/crcCvCiQG4SRm1k81k54FEIvw=="], + + "@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.8", "", { "dependencies": { "@aws-sdk/middleware-sdk-s3": "^3.972.20", "@aws-sdk/types": "^3.973.6", "@smithy/protocol-http": "^5.3.12", "@smithy/signature-v4": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-n1qYFD+tbqZuyskVaxUE+t10AUz9g3qzDw3Tp6QZDKmqsjfDmZBd4GIk2EKJJNtcCBtE5YiUjDYA+3djFAFBBg=="], + + "@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1009.0", "", { "dependencies": { "@aws-sdk/core": "^3.973.20", "@aws-sdk/nested-clients": "^3.996.10", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-KCPLuTqN9u0Rr38Arln78fRG9KXpzsPWmof+PZzfAHMMQq2QED6YjQrkrfiH7PDefLWEposY1o4/eGwrmKA4JA=="], + + "@aws-sdk/types": ["@aws-sdk/types@3.973.6", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Atfcy4E++beKtwJHiDln2Nby8W/mam64opFPTiHEqgsthqeydFS1pY+OUlN1ouNOmf8ArPU/6cDS65anOP3KQw=="], + + "@aws-sdk/util-arn-parser": ["@aws-sdk/util-arn-parser@3.972.3", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-HzSD8PMFrvgi2Kserxuff5VitNq2sgf3w9qxmskKDiDTThWfVteJxuCS9JXiPIPtmCrp+7N9asfIaVhBFORllA=="], + + "@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.996.5", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-endpoints": "^3.3.3", "tslib": "^2.6.2" } }, "sha512-Uh93L5sXFNbyR5sEPMzUU8tJ++Ku97EY4udmC01nB8Zu+xfBPwpIwJ6F7snqQeq8h2pf+8SGN5/NoytfKgYPIw=="], + + "@aws-sdk/util-locate-window": ["@aws-sdk/util-locate-window@3.965.5", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ=="], + + "@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/types": "^4.13.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-B3KGXJviV2u6Cdw2SDY2aDhoJkVfY/Q/Trwk2CMSkikE1Oi6gRzxhvhIfiRpHfmIsAhV4EA54TVEX8K6CbHbkA=="], + + "@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.973.7", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.21", "@aws-sdk/types": "^3.973.6", "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "@smithy/util-config-provider": "^4.2.2", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-Hz6EZMUAEzqUd7e+vZ9LE7mn+5gMbxltXy18v+YSFY+9LBJz15wkNZvw5JqfX3z0FS9n3bgUtz3L5rAsfh4YlA=="], + + "@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.11", "", { "dependencies": { "@smithy/types": "^4.13.1", "fast-xml-parser": "5.4.1", "tslib": "^2.6.2" } }, "sha512-iitV/gZKQMvY9d7ovmyFnFuTHbBAtrmLnvaSb/3X8vOKyevwtpmEtyc8AdhVWZe0pI/1GsHxlEvQeOePFzy7KQ=="], + + "@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.4", "", {}, "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ=="], + "@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="], "@babel/compat-data": ["@babel/compat-data@7.29.0", "", {}, "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg=="], @@ -1298,6 +1379,108 @@ "@sindresorhus/merge-streams": ["@sindresorhus/merge-streams@2.3.0", "", {}, "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg=="], + "@smithy/abort-controller": ["@smithy/abort-controller@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-xolrFw6b+2iYGl6EcOL7IJY71vvyZ0DJ3mcKtpykqPe2uscwtzDZJa1uVQXyP7w9Dd+kGwYnPbMsJrGISKiY/Q=="], + + "@smithy/chunked-blob-reader": ["@smithy/chunked-blob-reader@5.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-St+kVicSyayWQca+I1rGitaOEH6uKgE8IUWoYnnEX26SWdWQcL6LvMSD19Lg+vYHKdT9B2Zuu7rd3i6Wnyb/iw=="], + + "@smithy/chunked-blob-reader-native": ["@smithy/chunked-blob-reader-native@4.2.3", "", { "dependencies": { "@smithy/util-base64": "^4.3.2", "tslib": "^2.6.2" } }, "sha512-jA5k5Udn7Y5717L86h4EIv06wIr3xn8GM1qHRi/Nf31annXcXHJjBKvgztnbn2TxH3xWrPBfgwHsOwZf0UmQWw=="], + + "@smithy/config-resolver": ["@smithy/config-resolver@4.4.11", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "@smithy/util-config-provider": "^4.2.2", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-YxFiiG4YDAtX7WMN7RuhHZLeTmRRAOyCbr+zB8e3AQzHPnUhS8zXjB1+cniPVQI3xbWsQPM0X2aaIkO/ME0ymw=="], + + "@smithy/core": ["@smithy/core@3.23.12", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-stream": "^4.5.20", "@smithy/util-utf8": "^4.2.2", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-o9VycsYNtgC+Dy3I0yrwCqv9CWicDnke0L7EVOrZtJpjb2t0EjaEofmMrYc0T1Kn3yk32zm6cspxF9u9Bj7e5w=="], + + "@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.12", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/property-provider": "^4.2.12", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-cr2lR792vNZcYMriSIj+Um3x9KWrjcu98kn234xA6reOAFMmbRpQMOv8KPgEmLLtx3eldU6c5wALKFqNOhugmg=="], + + "@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.12", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.13.1", "@smithy/util-hex-encoding": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-FE3bZdEl62ojmy8x4FHqxq2+BuOHlcxiH5vaZ6aqHJr3AIZzwF5jfx8dEiU/X0a8RboyNDjmXjlbr8AdEyLgiA=="], + + "@smithy/eventstream-serde-browser": ["@smithy/eventstream-serde-browser@4.2.12", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-XUSuMxlTxV5pp4VpqZf6Sa3vT/Q75FVkLSpSSE3KkWBvAQWeuWt1msTv8fJfgA4/jcJhrbrbMzN1AC/hvPmm5A=="], + + "@smithy/eventstream-serde-config-resolver": ["@smithy/eventstream-serde-config-resolver@4.3.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-7epsAZ3QvfHkngz6RXQYseyZYHlmWXSTPOfPmXkiS+zA6TBNo1awUaMFL9vxyXlGdoELmCZyZe1nQE+imbmV+Q=="], + + "@smithy/eventstream-serde-node": ["@smithy/eventstream-serde-node@4.2.12", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-D1pFuExo31854eAvg89KMn9Oab/wEeJR6Buy32B49A9Ogdtx5fwZPqBHUlDzaCDpycTFk2+fSQgX689Qsk7UGA=="], + + "@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@4.2.12", "", { "dependencies": { "@smithy/eventstream-codec": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-+yNuTiyBACxOJUTvbsNsSOfH9G9oKbaJE1lNL3YHpGcuucl6rPZMi3nrpehpVOVR2E07YqFFmtwpImtpzlouHQ=="], + + "@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.15", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/querystring-builder": "^4.2.12", "@smithy/types": "^4.13.1", "@smithy/util-base64": "^4.3.2", "tslib": "^2.6.2" } }, "sha512-T4jFU5N/yiIfrtrsb9uOQn7RdELdM/7HbyLNr6uO/mpkj1ctiVs7CihVr51w4LyQlXWDpXFn4BElf1WmQvZu/A=="], + + "@smithy/hash-blob-browser": ["@smithy/hash-blob-browser@4.2.13", "", { "dependencies": { "@smithy/chunked-blob-reader": "^5.2.2", "@smithy/chunked-blob-reader-native": "^4.2.3", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-YrF4zWKh+ghLuquldj6e/RzE3xZYL8wIPfkt0MqCRphVICjyyjH8OwKD7LLlKpVEbk4FLizFfC1+gwK6XQdR3g=="], + + "@smithy/hash-node": ["@smithy/hash-node@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-QhBYbGrbxTkZ43QoTPrK72DoYviDeg6YKDrHTMJbbC+A0sml3kSjzFtXP7BtbyJnXojLfTQldGdUR0RGD8dA3w=="], + + "@smithy/hash-stream-node": ["@smithy/hash-stream-node@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-O3YbmGExeafuM/kP7Y8r6+1y0hIh3/zn6GROx0uNlB54K9oihAL75Qtc+jFfLNliTi6pxOAYZrRKD9A7iA6UFw=="], + + "@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-/4F1zb7Z8LOu1PalTdESFHR0RbPwHd3FcaG1sI3UEIriQTWakysgJr65lc1jj6QY5ye7aFsisajotH6UhWfm/g=="], + + "@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow=="], + + "@smithy/md5-js": ["@smithy/md5-js@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-W/oIpHCpWU2+iAkfZYyGWE+qkpuf3vEXHLxQQDx9FPNZTTdnul0dZ2d/gUFrtQ5je1G2kp4cjG0/24YueG2LbQ=="], + + "@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.12", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-YE58Yz+cvFInWI/wOTrB+DbvUVz/pLn5mC5MvOV4fdRUc6qGwygyngcucRQjAhiCEbmfLOXX0gntSIcgMvAjmA=="], + + "@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.26", "", { "dependencies": { "@smithy/core": "^3.23.12", "@smithy/middleware-serde": "^4.2.15", "@smithy/node-config-provider": "^4.3.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-middleware": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-8Qfikvd2GVKSm8S6IbjfwFlRY9VlMrj0Dp4vTwAuhqbX7NhJKE5DQc2bnfJIcY0B+2YKMDBWfvexbSZeejDgeg=="], + + "@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.43", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/protocol-http": "^5.3.12", "@smithy/service-error-classification": "^4.2.12", "@smithy/smithy-client": "^4.12.6", "@smithy/types": "^4.13.1", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.12", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-ZwsifBdyuNHrFGmbc7bAfP2b54+kt9J2rhFd18ilQGAB+GDiP4SrawqyExbB7v455QVR7Psyhb2kjULvBPIhvA=="], + + "@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.15", "", { "dependencies": { "@smithy/core": "^3.23.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-ExYhcltZSli0pgAKOpQQe1DLFBLryeZ22605y/YS+mQpdNWekum9Ujb/jMKfJKgjtz1AZldtwA/wCYuKJgjjlg=="], + + "@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-kruC5gRHwsCOuyCd4ouQxYjgRAym2uDlCvQ5acuMtRrcdfg7mFBg6blaxcJ09STpt3ziEkis6bhg1uwrWU7txw=="], + + "@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.12", "", { "dependencies": { "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-tr2oKX2xMcO+rBOjobSwVAkV05SIfUKz8iI53rzxEmgW3GOOPOv0UioSDk+J8OpRQnpnhsO3Af6IEBabQBVmiw=="], + + "@smithy/node-http-handler": ["@smithy/node-http-handler@4.5.0", "", { "dependencies": { "@smithy/abort-controller": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/querystring-builder": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Rnq9vQWiR1+/I6NZZMNzJHV6pZYyEHt2ZnuV3MG8z2NNenC4i/8Kzttz7CjZiHSmsN5frhXhg17z3Zqjjhmz1A=="], + + "@smithy/property-provider": ["@smithy/property-provider@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A=="], + + "@smithy/protocol-http": ["@smithy/protocol-http@5.3.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw=="], + + "@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-6wTZjGABQufekycfDGMEB84BgtdOE/rCVTov+EDXQ8NHKTUNIp/j27IliwP7tjIU9LR+sSzyGBOXjeEtVgzCHg=="], + + "@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-P2OdvrgiAKpkPNKlKUtWbNZKB1XjPxM086NeVhK+W+wI46pIKdWBe5QyXvhUm3MEcyS/rkLvY8rZzyUdmyDZBw=="], + + "@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1" } }, "sha512-LlP29oSQN0Tw0b6D0Xo6BIikBswuIiGYbRACy5ujw/JgWSzTdYj46U83ssf6Ux0GyNJVivs2uReU8pt7Eu9okQ=="], + + "@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.7", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-HrOKWsUb+otTeo1HxVWeEb99t5ER1XrBi/xka2Wv6NVmTbuCUC1dvlrksdvxFtODLBjsC+PHK+fuy2x/7Ynyiw=="], + + "@smithy/signature-v4": ["@smithy/signature-v4@5.3.12", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-uri-escape": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-B/FBwO3MVOL00DaRSXfXfa/TRXRheagt/q5A2NM13u7q+sHS59EOVGQNfG7DkmVtdQm5m3vOosoKAXSqn/OEgw=="], + + "@smithy/smithy-client": ["@smithy/smithy-client@4.12.6", "", { "dependencies": { "@smithy/core": "^3.23.12", "@smithy/middleware-endpoint": "^4.4.26", "@smithy/middleware-stack": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-stream": "^4.5.20", "tslib": "^2.6.2" } }, "sha512-aib3f0jiMsJ6+cvDnXipBsGDL7ztknYSVqJs1FdN9P+u9tr/VzOR7iygSh6EUOdaBeMCMSh3N0VdyYsG4o91DQ=="], + + "@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/url-parser": ["@smithy/url-parser@4.2.12", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-wOPKPEpso+doCZGIlr+e1lVI6+9VAKfL4kZWFgzVgGWY2hZxshNKod4l2LXS3PRC9otH/JRSjtEHqQ/7eLciRA=="], + + "@smithy/util-base64": ["@smithy/util-base64@4.3.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ=="], + + "@smithy/util-body-length-browser": ["@smithy/util-body-length-browser@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ=="], + + "@smithy/util-body-length-node": ["@smithy/util-body-length-node@4.2.3", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g=="], + + "@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.2", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q=="], + + "@smithy/util-config-provider": ["@smithy/util-config-provider@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ=="], + + "@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.42", "", { "dependencies": { "@smithy/property-provider": "^4.2.12", "@smithy/smithy-client": "^4.12.6", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-0vjwmcvkWAUtikXnWIUOyV6IFHTEeQUYh3JUZcDgcszF+hD/StAsQ3rCZNZEPHgI9kVNcbnyc8P2CBHnwgmcwg=="], + + "@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.45", "", { "dependencies": { "@smithy/config-resolver": "^4.4.11", "@smithy/credential-provider-imds": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/property-provider": "^4.2.12", "@smithy/smithy-client": "^4.12.6", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-q5dOqqfTgUcLe38TAGiFn9srToKj2YCHJ34QGOLzM+xYLLA+qRZv7N+33kl1MERVusue36ZHnlNaNEvY/PzSrw=="], + + "@smithy/util-endpoints": ["@smithy/util-endpoints@3.3.3", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-VACQVe50j0HZPjpwWcjyT51KUQ4AnsvEaQ2lKHOSL4mNLD0G9BjEniQ+yCt1qqfKfiAHRAts26ud7hBjamrwig=="], + + "@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg=="], + + "@smithy/util-middleware": ["@smithy/util-middleware@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ=="], + + "@smithy/util-retry": ["@smithy/util-retry@4.2.12", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-1zopLDUEOwumjcHdJ1mwBHddubYF8GMQvstVCLC54Y46rqoHwlIU+8ZzUeaBcD+WCJHyDGSeZ2ml9YSe9aqcoQ=="], + + "@smithy/util-stream": ["@smithy/util-stream@4.5.20", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.15", "@smithy/node-http-handler": "^4.5.0", "@smithy/types": "^4.13.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-4yXLm5n/B5SRBR2p8cZ90Sbv4zL4NKsgxdzCzp/83cXw2KxLEumt5p+GAVyRNZgQOSrzXn9ARpO0lUe8XSlSDw=="], + + "@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw=="], + + "@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], + + "@smithy/util-waiter": ["@smithy/util-waiter@4.2.13", "", { "dependencies": { "@smithy/abort-controller": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-2zdZ9DTHngRtcYxJK1GUDxruNr53kv5W2Lupe0LMU+Imr6ohQg8M2T14MNkj1Y0wS3FFwpgpGQyvuaMF7CiTmQ=="], + + "@smithy/uuid": ["@smithy/uuid@1.1.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g=="], + "@speed-highlight/core": ["@speed-highlight/core@1.2.14", "", {}, "sha512-G4ewlBNhUtlLvrJTb88d2mdy2KRijzs4UhnlrOSRT4bmjh/IqNElZa3zkrZ+TC47TwtlDWzVLFADljF1Ijp5hA=="], "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], @@ -1630,6 +1813,8 @@ "boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="], + "bowser": ["bowser@2.14.1", "", {}, "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg=="], + "boxen": ["boxen@8.0.1", "", { "dependencies": { "ansi-align": "^3.0.1", "camelcase": "^8.0.0", "chalk": "^5.3.0", "cli-boxes": "^3.0.0", "string-width": "^7.2.0", "type-fest": "^4.21.0", "widest-line": "^5.0.0", "wrap-ansi": "^9.0.0" } }, "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw=="], "brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], @@ -3174,6 +3359,12 @@ "@authenio/xml-encryption/xpath": ["xpath@0.0.32", "", {}, "sha512-rxMJhSIoiO8vXcWvSifKqhvV96GjiD5wYb8/QHdoRyQvraTpp4IEv944nhGausZZ3u7dhQXteZuZbaqfpB7uYw=="], + "@aws-crypto/sha1-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], + + "@aws-crypto/sha256-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], + + "@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], + "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], "@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], @@ -3612,6 +3803,12 @@ "@astrojs/react/vite/esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], + "@aws-crypto/sha1-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], + + "@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], + + "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], + "@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], "@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], @@ -3868,6 +4065,12 @@ "@astrojs/react/vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], + "@aws-crypto/sha1-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], + + "@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], + + "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], + "@oclif/core/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], "@opentelemetry/sdk-node/@opentelemetry/exporter-logs-otlp-proto/@opentelemetry/otlp-transformer/protobufjs": ["protobufjs@7.5.4", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg=="], diff --git a/packages/bindings/package.json b/packages/bindings/package.json index b33bd53d71..53d2b76de2 100644 --- a/packages/bindings/package.json +++ b/packages/bindings/package.json @@ -22,6 +22,7 @@ "./collections": "./src/well-known/collections.ts", "./llm": "./src/well-known/language-model.ts", "./object-storage": "./src/well-known/object-storage.ts", + "./filesystem": "./src/well-known/filesystem.ts", "./connection": "./src/core/connection.ts", "./client": "./src/core/client/index.ts", "./mcp": "./src/well-known/mcp.ts", diff --git a/packages/bindings/src/index.ts b/packages/bindings/src/index.ts index 3a07b16df2..0c3651e344 100644 --- a/packages/bindings/src/index.ts +++ b/packages/bindings/src/index.ts @@ -121,5 +121,31 @@ export { type DeleteObjectsOutput, } from "./well-known/object-storage"; +// Re-export filesystem binding types +export { + FILESYSTEM_BINDING, + type FilesystemBinding, + type FsReadInput, + type FsReadOutput, + FsReadInputSchema, + FsReadOutputSchema, + type FsWriteInput, + type FsWriteOutput, + FsWriteInputSchema, + FsWriteOutputSchema, + type FsListInput, + type FsListOutput, + FsListInputSchema, + FsListOutputSchema, + type FsDeleteInput, + type FsDeleteOutput, + FsDeleteInputSchema, + FsDeleteOutputSchema, + type FsMetadataInput, + type FsMetadataOutput, + FsMetadataInputSchema, + FsMetadataOutputSchema, +} from "./well-known/filesystem"; + // Re-export workflow binding types export { WORKFLOWS_COLLECTION_BINDING } from "./well-known/workflow"; diff --git a/packages/bindings/src/well-known/filesystem.ts b/packages/bindings/src/well-known/filesystem.ts new file mode 100644 index 0000000000..0093f81c40 --- /dev/null +++ b/packages/bindings/src/well-known/filesystem.ts @@ -0,0 +1,234 @@ +/** + * Filesystem Well-Known Binding + * + * Defines the interface for agent-oriented filesystem operations backed by S3-compatible storage. + * Unlike OBJECT_STORAGE_BINDING (which uses presigned URLs for browser/UI use), + * this binding provides inline content access designed for AI agents. + * + * This binding includes: + * - FS_READ: Read file content inline (text or base64 for binary) + * - FS_WRITE: Write file content inline + * - FS_LIST: List files and directories with pattern filtering + * - FS_DELETE: Delete a single file + * - FS_METADATA: Get file metadata (size, content type, etc.) + */ + +import { z } from "zod"; +import type { Binder, ToolBinder } from "../core/binder"; + +// ============================================================================ +// Tool Schemas +// ============================================================================ + +/** + * FS_READ - Read file content inline + * + * Returns content directly for text files and small binary files (as base64). + * For files exceeding the size limit, returns an error with file metadata. + */ +const FsReadInputSchema = z.object({ + path: z.string().describe("File path to read (e.g., 'docs/readme.md')"), + offset: z + .number() + .optional() + .describe( + "Byte offset to start reading from (for partial reads of large files)", + ), + limit: z + .number() + .optional() + .describe("Maximum number of bytes to read (for partial reads)"), +}); + +const FsReadOutputSchema = z.object({ + content: z + .string() + .optional() + .describe( + "File content as text (for text files) or base64 (for small binary files)", + ), + encoding: z + .enum(["utf-8", "base64"]) + .optional() + .describe("Content encoding: utf-8 for text, base64 for binary"), + contentType: z.string().optional().describe("MIME type of the file"), + size: z.number().describe("Total file size in bytes"), + error: z + .enum(["FILE_NOT_FOUND", "FILE_TOO_LARGE"]) + .optional() + .describe("Error code if file cannot be read inline"), +}); + +export type FsReadInput = z.infer; +export type FsReadOutput = z.infer; + +export { FsReadInputSchema, FsReadOutputSchema }; + +/** + * FS_WRITE - Write file content inline + * + * Creates or overwrites a file with the provided content. + */ +const FsWriteInputSchema = z.object({ + path: z.string().describe("File path to write to (e.g., 'docs/readme.md')"), + content: z.string().describe("File content to write"), + encoding: z + .enum(["utf-8", "base64"]) + .optional() + .default("utf-8") + .describe("Content encoding: utf-8 for text (default), base64 for binary"), + contentType: z + .string() + .optional() + .describe( + "MIME type for the file (auto-detected from extension if omitted)", + ), +}); + +const FsWriteOutputSchema = z.object({ + path: z.string().describe("Path of the written file"), + size: z.number().describe("Size of the written file in bytes"), +}); + +export type FsWriteInput = z.infer; +export type FsWriteOutput = z.infer; + +export { FsWriteInputSchema, FsWriteOutputSchema }; + +/** + * FS_LIST - List files and directories + * + * Lists entries at a given path with optional pattern filtering. + * Uses S3 prefix/delimiter semantics to simulate directory listing. + */ +const FsListInputSchema = z.object({ + path: z + .string() + .optional() + .describe("Directory path to list (e.g., 'docs/'). Defaults to root."), + pattern: z + .string() + .optional() + .describe( + "Glob pattern to filter results by key name (e.g., '*.md'). Applied to key names only, not content.", + ), + continuationToken: z + .string() + .optional() + .describe("Token for pagination from previous response"), + maxKeys: z + .number() + .optional() + .default(100) + .describe("Maximum number of entries to return (default: 100, max: 1000)"), +}); + +const FsListOutputSchema = z.object({ + entries: z + .array( + z.object({ + path: z.string().describe("File or directory path"), + type: z.enum(["file", "directory"]).describe("Entry type"), + size: z.number().optional().describe("File size in bytes (files only)"), + lastModified: z + .string() + .optional() + .describe("Last modified timestamp (files only)"), + }), + ) + .describe("List of file and directory entries"), + nextContinuationToken: z + .string() + .optional() + .describe("Token for fetching next page of results"), + isTruncated: z.boolean().describe("Whether there are more results available"), +}); + +export type FsListInput = z.infer; +export type FsListOutput = z.infer; + +export { FsListInputSchema, FsListOutputSchema }; + +/** + * FS_DELETE - Delete a single file + */ +const FsDeleteInputSchema = z.object({ + path: z.string().describe("File path to delete"), +}); + +const FsDeleteOutputSchema = z.object({ + success: z.boolean().describe("Whether the deletion was successful"), + path: z.string().describe("Path of the deleted file"), +}); + +export type FsDeleteInput = z.infer; +export type FsDeleteOutput = z.infer; + +export { FsDeleteInputSchema, FsDeleteOutputSchema }; + +/** + * FS_METADATA - Get file metadata + */ +const FsMetadataInputSchema = z.object({ + path: z.string().describe("File path to get metadata for"), +}); + +const FsMetadataOutputSchema = z.object({ + size: z.number().describe("File size in bytes"), + contentType: z.string().optional().describe("MIME type of the file"), + lastModified: z.string().describe("Last modified timestamp"), + etag: z.string().describe("Entity tag for the file"), +}); + +export type FsMetadataInput = z.infer; +export type FsMetadataOutput = z.infer; + +export { FsMetadataInputSchema, FsMetadataOutputSchema }; + +// ============================================================================ +// Binding Definition +// ============================================================================ + +/** + * Filesystem Binding + * + * Agent-oriented filesystem interface backed by S3-compatible storage. + * Provides inline content access (read/write file content directly in tool calls) + * as opposed to OBJECT_STORAGE_BINDING which uses presigned URLs. + * + * Required tools: + * - FS_READ: Read file content inline + * - FS_WRITE: Write file content inline + * - FS_LIST: List files and directories + * - FS_DELETE: Delete a file + * - FS_METADATA: Get file metadata + */ +export const FILESYSTEM_BINDING = [ + { + name: "FS_READ" as const, + inputSchema: FsReadInputSchema, + outputSchema: FsReadOutputSchema, + } satisfies ToolBinder<"FS_READ", FsReadInput, FsReadOutput>, + { + name: "FS_WRITE" as const, + inputSchema: FsWriteInputSchema, + outputSchema: FsWriteOutputSchema, + } satisfies ToolBinder<"FS_WRITE", FsWriteInput, FsWriteOutput>, + { + name: "FS_LIST" as const, + inputSchema: FsListInputSchema, + outputSchema: FsListOutputSchema, + } satisfies ToolBinder<"FS_LIST", FsListInput, FsListOutput>, + { + name: "FS_DELETE" as const, + inputSchema: FsDeleteInputSchema, + outputSchema: FsDeleteOutputSchema, + } satisfies ToolBinder<"FS_DELETE", FsDeleteInput, FsDeleteOutput>, + { + name: "FS_METADATA" as const, + inputSchema: FsMetadataInputSchema, + outputSchema: FsMetadataOutputSchema, + } satisfies ToolBinder<"FS_METADATA", FsMetadataInput, FsMetadataOutput>, +] as const satisfies Binder; + +export type FilesystemBinding = typeof FILESYSTEM_BINDING; diff --git a/packages/mesh-sdk/src/index.ts b/packages/mesh-sdk/src/index.ts index b36d0b0d38..5d289fe85e 100644 --- a/packages/mesh-sdk/src/index.ts +++ b/packages/mesh-sdk/src/index.ts @@ -198,6 +198,8 @@ export { getWellKnownCommunityRegistryConnection, getWellKnownSelfConnection, getWellKnownDevAssetsConnection, + getWellKnownFilesystemConnection, + FILESYSTEM_MCP_ALIAS_ID, getWellKnownOpenRouterConnection, getWellKnownMcpStudioConnection, // Virtual MCP factory functions diff --git a/packages/mesh-sdk/src/lib/constants.ts b/packages/mesh-sdk/src/lib/constants.ts index 0a66cfb0fb..22969bd30b 100644 --- a/packages/mesh-sdk/src/lib/constants.ts +++ b/packages/mesh-sdk/src/lib/constants.ts @@ -26,6 +26,8 @@ export const WellKnownOrgMCPId = { COMMUNITY_REGISTRY: (org: string) => `${org}_community-registry`, /** Dev Assets MCP - local file storage for development */ DEV_ASSETS: (org: string) => `${org}_dev-assets`, + /** Filesystem MCP - S3-backed filesystem for agents */ + FILESYSTEM: (org: string) => `${org}_filesystem`, }; /** @@ -35,6 +37,13 @@ export const WellKnownOrgMCPId = { */ export const SELF_MCP_ALIAS_ID = "self"; +/** + * Frontend connection ID for the filesystem MCP endpoint. + * Use this constant when calling filesystem tools from the frontend. + * The endpoint is exposed at /mcp/filesystem. + */ +export const FILESYSTEM_MCP_ALIAS_ID = "filesystem"; + /** * Frontend connection ID for the dev-assets MCP endpoint. * Use this constant when calling object storage tools from the frontend in dev mode. @@ -169,6 +178,41 @@ export function getWellKnownDevAssetsConnection( }; } +/** + * Get well-known connection definition for the Filesystem MCP. + * S3-backed filesystem for AI agents with inline content access. + * Implements FILESYSTEM_BINDING. + * + * @param baseUrl - The base URL for the MCP server + * @param orgId - The organization ID + * @returns ConnectionCreateData for the Filesystem MCP + */ +export function getWellKnownFilesystemConnection( + baseUrl: string, + orgId: string, +): ConnectionCreateData { + return { + id: WellKnownOrgMCPId.FILESYSTEM(orgId), + title: "Filesystem", + description: + "S3-backed filesystem for AI agents. Read and write files directly.", + connection_type: "HTTP", + connection_url: `${baseUrl}/mcp/${FILESYSTEM_MCP_ALIAS_ID}`, + icon: "https://api.iconify.design/lucide:hard-drive.svg?color=%23888", + app_name: "@deco/filesystem-mcp", + app_id: null, + connection_token: null, + connection_headers: null, + oauth_config: null, + configuration_state: null, + configuration_scopes: null, + metadata: { + isFixed: true, + type: "filesystem", + }, + }; +} + /** * Get well-known connection definition for OpenRouter. * Used by the chat UI to offer a one-click install when no model provider is connected.