first commit

This commit is contained in:
Clelia (Astra) Bertelli
2025-12-05 22:54:58 +01:00
commit 96a8060c6d
10 changed files with 321 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
node_modules/
pnpm-lock.yaml
+3
View File
@@ -0,0 +1,3 @@
# Claude + AgentFS + LlamaIndex Workflows
Attempt to run Claude Code within a fully-virtualized file system (AgentFS), orchestrating it with LlamaIndex Workflows.
+9
View File
@@ -0,0 +1,9 @@
import js from "@eslint/js";
import globals from "globals";
import tseslint from "typescript-eslint";
import { defineConfig } from "eslint/config";
export default defineConfig([
{ files: ["**/*.{js,mjs,cjs,ts,mts,cts}"], plugins: { js }, extends: ["js/recommended"], languageOptions: { globals: globals.browser } },
tseslint.configs.recommended,
]);
+33
View File
@@ -0,0 +1,33 @@
{
"name": "agentfs-claude",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"packageManager": "pnpm@10.15.1",
"devDependencies": {
"@eslint/js": "^9.39.1",
"@types/mime-types": "^3.0.1",
"@types/node": "^24.10.1",
"eslint": "^9.39.1",
"globals": "^16.5.0",
"jiti": "^2.6.1",
"prettier": "^3.7.4",
"ts-node": "^10.9.2",
"typescript": "^5.9.3",
"typescript-eslint": "^8.48.1"
},
"dependencies": {
"@anthropic-ai/claude-agent-sdk": "^0.1.59",
"@llamaindex/workflow-core": "^1.3.3",
"@modelcontextprotocol/sdk": "^1.24.3",
"agentfs-sdk": "^0.2.1",
"mime-types": "^3.0.2",
"zod": "3.25.76"
}
}
View File
+106
View File
@@ -0,0 +1,106 @@
import * as fs from 'fs/promises';
import {type FileWithContent} from "./types";
import path from 'path';
import * as mime from 'mime-types';
import { AgentFS } from 'agentfs-sdk';
async function getFilesInDir({dirPath = "./"}: {dirPath?: string}): Promise<FileWithContent[]> {
const files: FileWithContent[] = []
try {
const entries = await fs.readdir(dirPath, { withFileTypes: true })
for (const entry of entries) {
const pt = path.join(dirPath, entry.name)
if (entry.isDirectory()) {
const subFiles = await getFilesInDir({dirPath: pt})
files.push(...subFiles)
} else {
const mimeType = mime.lookup(pt)
if (typeof mimeType === "string" && mimeType.startsWith("text/")) {
const content = await fs.readFile(pt, {encoding: "utf-8"})
files.push({filePath: pt, content: content})
}
}
}
} catch(error) {
console.error(error)
}
return files
}
export async function recordFiles(agentfs: AgentFS): Promise<boolean> {
try {
const files = await getFilesInDir({})
for (const file of files) {
await agentfs.fs.writeFile(file.filePath, file.content)
}
return true
} catch(error) {
console.error(error)
return false
}
}
export async function readFile(filePath: string, agentfs: AgentFS): Promise<string | null> {
let content: string | null = null
try {
content = await agentfs.fs.readFile(filePath, "utf-8") as string
} catch(error) {
console.error(error)
}
return content
}
export async function writeFile(filePath: string, fileContent: string, agentfs: AgentFS): Promise<boolean> {
try {
await agentfs.fs.writeFile(filePath, fileContent)
return true
} catch(error) {
console.error(error)
return false
}
}
export async function editFile(filePath: string, oldString: string, newString: string, agentfs: AgentFS): Promise<string | null> {
let editedContent: string | null = null
try {
const content = await agentfs.fs.readFile(filePath, "utf-8") as string
editedContent = content.replace(oldString, newString)
await agentfs.fs.writeFile(filePath, editedContent)
return editedContent
} catch(error) {
console.error(error)
return editedContent
}
}
export async function fileExists(filePath: string, agentfs: AgentFS): Promise<boolean> {
try {
const dirPath = path.dirname(filePath)
const files = await agentfs.fs.readdir(dirPath)
for (const file of files) {
if (file == filePath || file == path.basename(filePath)) {
return true
}
}
return false
} catch(error) {
console.error(error)
return false
}
}
export async function listFiles(agentfs: AgentFS): Promise<string> {
const dirPath = "./"
let availableFiles: string = ""
try {
const files = await agentfs.fs.readdir(dirPath)
availableFiles += "AVAILABLE FILES:\n"
for (const file of files) {
availableFiles += file + ", "
}
return availableFiles.trim()
} catch(error) {
console.error(error)
return availableFiles
}
}
View File
+142
View File
@@ -0,0 +1,142 @@
import { AgentFS } from "agentfs-sdk";
import * as z from "zod";
import { type CallToolResult } from "@modelcontextprotocol/sdk/types.js";
import { editFile, fileExists, listFiles, readFile, writeFile } from "./filesystem";
import { tool, createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";
const readSchemaShape = {
filePath: z.string().describe("Path of the file to read")
}
const writeSchemaShape = {
filePath: z.string().describe("Path of the file to write"),
fileContent: z.string().describe("Content of the file")
}
const editSchemaShape = {
filePath: z.string().describe("Path of the file to write"),
oldString: z.string().describe("Old string in the file (to replace)"),
newString: z.string().describe("New string with which to replace the old string"),
}
const fileExistsSchemaShape = {
filePath: z.string().describe("Path of the file whose existence has to be checked")
}
const listFilesSchemaShape = {}
// eslint-disable-next-line
const readSchema = z.object(readSchemaShape);
// eslint-disable-next-line
const writeSchema = z.object(writeSchemaShape);
// eslint-disable-next-line
const editSchema = z.object(editSchemaShape);
// eslint-disable-next-line
const fileExistsSchema = z.object(fileExistsSchemaShape)
// eslint-disable-next-line
const listFilesSchema = z.object(listFilesSchemaShape)
async function getAgentFS({filePath = null}: {filePath?: string | null}): Promise<AgentFS> {
if (!filePath) {
filePath = "fs.db"
}
const agentfs = await AgentFS.open({ id: "claude-agentfs", path: filePath})
return agentfs
}
async function readTool(input: z.infer<typeof readSchema>): Promise<CallToolResult> {
const agentfs = await getAgentFS({})
const content = await readFile(input.filePath, agentfs)
if (typeof content == "string") {
return {content: [{type: "text", text: content}]}
} else {
return {content: [{type: "text", text: `Could not read ${input.filePath}. Please check that the file exists and submit the request again.`}], isError: true}
}
}
async function fileExistsTool(input: z.infer<typeof fileExistsSchema>): Promise<CallToolResult> {
const agentfs = await getAgentFS({})
const exists = await fileExists(input.filePath, agentfs)
if (exists) {
return {content: [{type: "text", text: `File ${input.filePath} exists`}]}
} else {
return {content: [{type: "text", text: `File ${input.filePath} does not exist.`}]}
}
}
async function writeFileTool(input: z.infer<typeof writeSchema>): Promise<CallToolResult> {
const agentfs = await getAgentFS({})
const success = await writeFile(input.filePath, input.fileContent, agentfs)
if (success) {
return {content: [{type: "text", text: `File ${input.filePath} successfully written with content:\n\n'''\n${input.fileContent}\n'''`}]}
} else {
return {content: [{type: "text", text: `There was an error while writing file ${input.filePath}`}]}
}
}
async function editFileTool(input: z.infer<typeof editSchema>): Promise<CallToolResult> {
const agentfs = await getAgentFS({})
const editedContent = await editFile(input.filePath, input.oldString, input.newString, agentfs)
if (typeof editedContent == "string") {
return {content: [{type: "text", text: `Successfully edited ${input.filePath}. New content:\n\n'''\n${editedContent}\n'''`}]} as CallToolResult
} else {
return {content: [{type: "text", text: `Could not edit ${input.filePath}. Please check that the file exists and submit the request again.`}], isError: true}
}
}
async function listFilesTool(input: z.infer<typeof listFilesSchema>): Promise<CallToolResult> {
const agentfs = await getAgentFS(input)
const files = await listFiles(agentfs)
if (files != "") {
return {content: [{type: "text", text: files}]}
} else {
return {content: [{type: "text", text: `Could not list files. Please report this failure to the user`}], isError: true}
}
}
const mcpReadTool = tool(
"read_file",
"Read a file by passing its path.",
readSchemaShape,
readTool,
)
const mcpWriteTool = tool(
"write_file",
"Write a file by passing its path and content.",
writeSchemaShape,
writeFileTool,
)
const mcpEditTool = tool(
"edit_file",
"Edit a file by passing its path, the old string and the new string.",
editSchemaShape,
editFileTool
)
const mcpFileExists = tool(
"file_exists",
"Check whether a file exists or not by passing its path.",
fileExistsSchemaShape,
fileExistsTool,
)
const mcplistFiles = tool(
"list_files",
"List all the available files",
listFilesSchemaShape,
listFilesTool,
)
export const fileSystemMCP = createSdkMcpServer({
name: "my-custom-tools",
version: "1.0.0",
tools: [
mcpReadTool,
mcpWriteTool,
mcplistFiles,
mcpEditTool,
mcpFileExists,
]
});
+4
View File
@@ -0,0 +1,4 @@
export type FileWithContent = {
filePath: string
content: string
}
+22
View File
@@ -0,0 +1,22 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ES2022",
"lib": ["ES2022"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"types": ["node"],
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}