packfs-core
Version:
Semantic filesystem operations for LLM agent frameworks with natural language understanding. See LLM_AGENT_GUIDE.md for copy-paste examples.
502 lines • 21.3 kB
JavaScript
/**
* LangChain.js integration for PackFS semantic filesystem
* LangChain JavaScript/TypeScript - https://js.langchain.com/
*/
/**
* PackFS semantic filesystem tool for LangChain.js
* Compatible with LangChain's tool calling and agent frameworks
*/
export class LangChainSemanticFilesystemTool {
createTool(config) {
return {
name: 'semantic_filesystem',
description: this.getToolDescription().description,
schema: this.getLangChainSchema(),
func: async (input) => {
try {
const result = await this.executeOperation(config, input);
return this.formatLangChainResponse(result);
}
catch (error) {
const errorMsg = error instanceof Error ? error.message : 'Unknown error';
return `Error: ${errorMsg}`;
}
},
};
}
getToolDescription() {
return {
name: 'semantic_filesystem',
description: 'Perform intelligent file operations using semantic understanding and natural language. Can read, write, search, organize files with AI-powered understanding.',
parameters: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'Natural language description of the file operation (e.g., "read the config file", "create a notes file with my thoughts", "find all documentation")',
},
operation: {
type: 'string',
description: 'Specific operation type',
enum: ['read', 'write', 'search', 'list', 'organize', 'delete'],
},
path: {
type: 'string',
description: 'File path when operation requires specific file',
},
content: {
type: 'string',
description: 'Content to write when creating/updating files',
},
pattern: {
type: 'string',
description: 'Search pattern or glob for finding files',
},
options: {
type: 'object',
description: 'Additional options for the operation',
},
},
required: ['query'],
},
examples: [
{
input: '{"query": "read the README file"}',
description: 'Read a specific file using natural language',
},
{
input: '{"query": "create a todo list file with my daily tasks"}',
description: 'Create a new file with content',
},
{
input: '{"query": "find all JavaScript files in the project"}',
description: 'Search for files by type',
},
],
};
}
validateParameters(params) {
const errors = [];
if (!params.query && !params.operation) {
errors.push('Must provide either query or operation');
}
return {
valid: errors.length === 0,
errors: errors.length > 0 ? errors : undefined,
};
}
getLangChainSchema() {
// LangChain.js schema format
return {
type: 'object',
properties: {
query: {
type: 'string',
description: 'Natural language description of the file operation to perform',
},
operation: {
type: 'string',
description: 'Specific operation type (optional, can be inferred from query)',
enum: ['read', 'write', 'search', 'list', 'organize', 'delete'],
},
path: {
type: 'string',
description: 'File path when operation requires specific file',
},
content: {
type: 'string',
description: 'Content to write when creating/updating files',
},
pattern: {
type: 'string',
description: 'Search pattern or glob for finding files',
},
options: {
type: 'object',
description: 'Additional options for the operation',
properties: {
maxResults: { type: 'number' },
includeContent: { type: 'boolean' },
recursive: { type: 'boolean' },
},
},
},
required: ['query'],
};
}
async executeOperation(config, input) {
const startTime = Date.now();
// Parse input - LangChain might pass as string or object
let params;
if (typeof input === 'string') {
try {
params = JSON.parse(input);
}
catch {
// If not JSON, treat as natural language query
params = { query: input };
}
}
else {
params = input;
}
// Validate parameters
const validation = this.validateParameters(params);
if (!validation.valid) {
return {
success: false,
error: `Invalid parameters: ${validation.errors?.join(', ')}`,
};
}
try {
let result;
// If we have a natural language query, use NL processing
if (params.query && !params.operation) {
// Ensure filesystem is initialized
if (!config.filesystem) {
throw new Error('Filesystem is not initialized. Please provide a valid filesystem or workingDirectory.');
}
const nlResult = await config.filesystem.interpretNaturalLanguage({
query: params.query,
context: {
workingDirectory: config.workingDirectory,
},
});
if (!nlResult.success) {
throw new Error(`Failed to interpret query: ${nlResult.message}`);
}
// Execute the interpreted intent
result = await this.executeSemanticIntent(config, nlResult.interpretedIntent);
}
else {
// Handle structured operations
result = await this.executeStructuredOperation(config, params);
}
return {
success: true,
data: result,
metadata: {
executionTime: Date.now() - startTime,
operationType: params.operation || 'natural_language',
},
};
}
catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error',
};
}
}
async executeSemanticIntent(config, intent) {
// Ensure filesystem is initialized
if (!config.filesystem) {
throw new Error('Filesystem is not initialized. Please provide a valid filesystem or workingDirectory.');
}
if ('purpose' in intent) {
switch (intent.purpose) {
case 'read':
case 'preview':
case 'metadata':
case 'verify_exists':
case 'create_or_get':
return await config.filesystem.accessFile(intent);
case 'create':
case 'append':
case 'overwrite':
case 'merge':
case 'patch':
// Ensure filesystem is initialized
if (!config.filesystem) {
throw new Error('Filesystem is not initialized. Please provide a valid filesystem or workingDirectory.');
}
return await config.filesystem.updateContent(intent);
case 'create_directory':
case 'move':
case 'copy':
case 'group_semantic':
case 'group_keywords':
// Ensure filesystem is initialized
if (!config.filesystem) {
throw new Error('Filesystem is not initialized. Please provide a valid filesystem or workingDirectory.');
}
return await config.filesystem.organizeFiles(intent);
case 'list':
case 'find':
case 'search_content':
case 'search_semantic':
case 'search_integrated':
// Ensure filesystem is initialized
if (!config.filesystem) {
throw new Error('Filesystem is not initialized. Please provide a valid filesystem or workingDirectory.');
}
return await config.filesystem.discoverFiles(intent);
case 'delete_file':
case 'delete_directory':
case 'delete_by_criteria':
// Ensure filesystem is initialized
if (!config.filesystem) {
throw new Error('Filesystem is not initialized. Please provide a valid filesystem or workingDirectory.');
}
return await config.filesystem.removeFiles(intent);
}
}
// Handle workflow intents
// Ensure filesystem is initialized
if (!config.filesystem) {
throw new Error('Filesystem is not initialized. Please provide a valid filesystem or workingDirectory.');
}
return await config.filesystem.executeWorkflow(intent);
}
async executeStructuredOperation(config, params) {
// Ensure filesystem is initialized
if (!config.filesystem) {
throw new Error('Filesystem is not initialized. Please provide a valid filesystem or workingDirectory.');
}
switch (params.operation) {
case 'read':
return await config.filesystem.accessFile({
purpose: 'read',
target: { path: params.path },
preferences: params.options,
});
case 'write':
return await config.filesystem.updateContent({
purpose: params.append ? 'append' : 'create',
target: { path: params.path },
content: params.content,
options: params.options,
});
case 'search':
const target = {};
if (params.pattern && params.pattern !== '*') {
target.pattern = params.pattern;
}
if (params.query) {
target.semanticQuery = params.query;
}
if (!target.pattern && !target.semanticQuery) {
target.semanticQuery = 'all files';
}
return await config.filesystem.discoverFiles({
purpose: 'search_semantic',
target,
options: params.options,
});
case 'list':
return await config.filesystem.discoverFiles({
purpose: 'list',
target: { path: params.path || '.' },
options: params.options,
});
case 'organize':
return await config.filesystem.organizeFiles({
purpose: params.mode || 'move',
source: { path: params.source },
destination: { path: params.destination },
options: params.options,
});
case 'delete':
return await config.filesystem.removeFiles({
purpose: 'delete_file',
target: { path: params.path },
options: params.options,
});
default:
throw new Error(`Unknown operation: ${params.operation}`);
}
}
formatLangChainResponse(result) {
if (!result.success) {
return `Error: ${result.error}`;
}
const data = result.data;
// Format different types of results for LangChain text output
if (data.content !== undefined) {
// File content result
return typeof data.content === 'string' ? data.content : '[Binary content]';
}
if (data.files && Array.isArray(data.files)) {
// File discovery result
const fileList = data.files
.map((f) => {
let info = f.path;
if (f.metadata?.size) {
info += ` (${f.metadata.size} bytes)`;
}
if (f.relevanceScore) {
info += ` [relevance: ${f.relevanceScore.toFixed(2)}]`;
}
return info;
})
.join('\n');
return `Found ${data.files.length} files:\n${fileList}`;
}
if (data.bytesWritten !== undefined) {
// Write operation result
return `Successfully ${data.created ? 'created' : 'updated'} file (${data.bytesWritten} bytes)`;
}
if (data.filesAffected !== undefined) {
// Organization result
return `Operation completed: ${data.filesAffected} files affected`;
}
if (data.filesDeleted !== undefined) {
// Deletion result
return `Deleted ${data.filesDeleted} files, freed ${data.freedSpace} bytes`;
}
if (data.exists !== undefined) {
// Existence check - but if this was a read operation and file doesn't exist, treat as error
if (!data.exists && data.content === undefined) {
return 'Error: File not found';
}
return data.exists ? 'File exists' : 'File does not exist';
}
// Generic success response
return 'Operation completed successfully';
}
}
/**
* Create a LangChain.js compatible semantic filesystem tool
*/
export function createLangChainSemanticFilesystemTool(config) {
const adapter = new LangChainSemanticFilesystemTool();
return adapter.createTool(config);
}
/**
* Create multiple specialized LangChain tools for different operations
* Useful for agents that need granular control over file operations
*/
export function createLangChainSemanticToolSet(config) {
return {
fileReader: {
name: 'read_file',
description: 'Read file content using path or natural language description',
func: async (input) => {
// Ensure filesystem is initialized
if (!config.filesystem) {
throw new Error('Filesystem is not initialized. Please provide a valid filesystem or workingDirectory.');
}
const params = typeof input === 'string' ? { query: input } : input;
const result = await config.filesystem.accessFile({
purpose: 'read',
target: params.path ? { path: params.path } : { semanticQuery: params.query },
});
return result.success ? result.content : `Error: ${result.message}`;
},
},
fileWriter: {
name: 'write_file',
description: 'Create or update files with content',
schema: {
type: 'object',
properties: {
path: { type: 'string', description: 'File path to write to' },
content: { type: 'string', description: 'Content to write' },
mode: {
type: 'string',
enum: ['create', 'append', 'overwrite'],
description: 'Write mode',
},
},
required: ['path', 'content'],
},
func: async (input) => {
// Ensure filesystem is initialized
if (!config.filesystem) {
throw new Error('Filesystem is not initialized. Please provide a valid filesystem or workingDirectory.');
}
const params = typeof input === 'string' ? JSON.parse(input) : input;
const result = await config.filesystem.updateContent({
purpose: params.mode || 'create',
target: { path: params.path },
content: params.content,
});
return result.success
? `File ${result.created ? 'created' : 'updated'} successfully (${result.bytesWritten} bytes)`
: `Error: ${result.message}`;
},
},
fileSearcher: {
name: 'search_files',
description: 'Search for files using patterns or semantic queries',
schema: {
type: 'object',
properties: {
query: { type: 'string', description: 'Search query or pattern' },
maxResults: { type: 'number', description: 'Maximum results to return' },
},
required: ['query'],
},
func: async (input) => {
// Ensure filesystem is initialized
if (!config.filesystem) {
throw new Error('Filesystem is not initialized. Please provide a valid filesystem or workingDirectory.');
}
const params = typeof input === 'string' ? { query: input } : input;
const result = await config.filesystem.discoverFiles({
purpose: 'search_semantic',
target: { semanticQuery: params.query },
options: { maxResults: params.maxResults },
});
if (!result.success) {
return `Error: ${result.message}`;
}
const fileList = result.files.map((f) => f.path).join('\n');
return `Found ${result.files.length} files:\n${fileList}`;
},
},
fileManager: {
name: 'manage_files',
description: 'Move, copy, delete, and organize files',
schema: {
type: 'object',
properties: {
action: {
type: 'string',
enum: ['move', 'copy', 'delete', 'mkdir'],
description: 'Management action',
},
source: { type: 'string', description: 'Source path' },
destination: { type: 'string', description: 'Destination path (for move/copy)' },
},
required: ['action', 'source'],
},
func: async (input) => {
const params = typeof input === 'string' ? JSON.parse(input) : input;
let result;
// Ensure filesystem is initialized
if (!config.filesystem) {
throw new Error('Filesystem is not initialized. Please provide a valid filesystem or workingDirectory.');
}
switch (params.action) {
case 'move':
case 'copy':
result = await config.filesystem.organizeFiles({
purpose: params.action,
source: { path: params.source },
destination: { path: params.destination },
});
break;
case 'delete':
result = await config.filesystem.removeFiles({
purpose: 'delete_file',
target: { path: params.source },
});
break;
case 'mkdir':
result = await config.filesystem.organizeFiles({
purpose: 'create_directory',
destination: { path: params.source },
});
break;
default:
return `Error: Unknown action ${params.action}`;
}
return result.success
? `${params.action} operation completed successfully`
: `Error: ${result.message}`;
},
},
};
}
//# sourceMappingURL=langchain-js.js.map