Overview
The ACP SDK provides methods for agents to read and write files in the client’s file system. These operations are only available if the client advertises the corresponding capabilities during initialization.
File system operations require the client to explicitly enable these capabilities. Always check that the client advertised the capability before attempting to use these methods.
Reading Files
The readTextFile method reads content from a text file:
async readTextFile (
params : ReadTextFileRequest
): Promise < ReadTextFileResponse >
The session ID making the request
Absolute path to the file to read
The file contents as a string
Only available if the client advertises the fs.readTextFile capability during initialization.
See protocol docs: Client
Example
class MyAgent implements acp . Agent {
private connection : acp . AgentSideConnection ;
private clientCapabilities : acp . ClientCapabilities ;
async initialize ( params : acp . InitializeRequest ) : Promise < acp . InitializeResponse > {
// Store client capabilities
this . clientCapabilities = params . clientCapabilities ;
return {
protocolVersion: acp . PROTOCOL_VERSION ,
agentCapabilities: {},
};
}
async readFile ( sessionId : string , path : string ) : Promise < string > {
// Check if capability is available
if ( ! this . clientCapabilities . fs ?. readTextFile ) {
throw new Error ( "Client does not support file reading" );
}
const response = await this . connection . readTextFile ({
sessionId ,
path ,
});
return response . content ;
}
}
All paths in the protocol should be absolute paths.
Writing Files
The writeTextFile method writes content to a text file:
async writeTextFile (
params : WriteTextFileRequest
): Promise < WriteTextFileResponse >
The session ID making the request
Absolute path to the file to write
The content to write to the file
Only available if the client advertises the fs.writeTextFile capability during initialization.
Allows the agent to create or modify files within the client’s environment.
See protocol docs: Client
Example
async writeFile (
sessionId : string ,
path : string ,
content : string
): Promise < void > {
// Check if capability is available
if (!this.clientCapabilities.fs?.writeTextFile) {
throw new Error ( "Client does not support file writing" );
}
await this.connection.writeTextFile({
sessionId ,
path ,
content ,
});
}
Error Handling
Handle errors appropriately when working with file system operations:
import * as acp from "@agentprotocol/acp" ;
async readFileWithErrorHandling (
sessionId : string ,
path : string
): Promise < string | null > {
try {
const response = await this . connection . readTextFile ({
sessionId ,
path ,
});
return response . content ;
} catch (error) {
if ( error instanceof acp.RequestError) {
// Handle specific error codes
if ( error . code === - 32002 ) {
// Resource not found
console . error ( `File not found: ${ path } ` );
return null ;
}
}
throw error;
}
}
async writeFileWithErrorHandling (
sessionId : string ,
path : string ,
content : string
): Promise < boolean > {
try {
await this . connection . writeTextFile ({
sessionId ,
path ,
content ,
});
return true ;
} catch (error) {
if ( error instanceof acp.RequestError) {
console . error ( `Failed to write file: ${ error . message } ` );
return false ;
}
throw error;
}
}
Error Codes
Resource not found - the file does not exist
Internal error - an error occurred while reading or writing the file
Security Considerations
File system operations can be sensitive. Always follow these security best practices:
Path Validation
Ensure paths are absolute and within expected boundaries:
import * as path from "node:path" ;
function validatePath ( filePath : string , workingDirectory : string ) : boolean {
// Ensure path is absolute
if ( ! path . isAbsolute ( filePath )) {
return false ;
}
// Ensure path is within working directory
const normalized = path . normalize ( filePath );
const workdir = path . normalize ( workingDirectory );
return normalized . startsWith ( workdir );
}
Permission Requests
Request user permission before writing sensitive files:
async writeSensitiveFile (
sessionId : string ,
filePath : string ,
content : string
): Promise < void > {
// Send tool call for visibility
await this.connection.sessionUpdate({
sessionId ,
update : {
sessionUpdate : "tool_call" ,
toolCallId : "write_1" ,
title : `Writing to ${ path . basename ( filePath ) } ` ,
kind : "edit" ,
status : "pending" ,
locations : [{ path: filePath }],
rawInput : { path : filePath , content },
},
});
// Request permission
const permission = await this . connection . requestPermission ({
sessionId ,
toolCall: {
toolCallId: "write_1" ,
title: `Writing to ${ path . basename ( filePath ) } ` ,
kind: "edit" ,
status: "pending" ,
locations: [{ path: filePath }],
rawInput: { path: filePath , content },
},
options: [
{ kind: "allow_once" , name: "Allow" , optionId: "allow" },
{ kind: "reject_once" , name: "Reject" , optionId: "reject" },
],
});
if (permission.outcome.optionId === "allow" ) {
await this . connection . writeTextFile ({ sessionId , path: filePath , content });
}
}
Capability Checking
Always verify capabilities before attempting operations:
class FileSystemHelper {
constructor (
private connection : acp . AgentSideConnection ,
private capabilities : acp . ClientCapabilities
) {}
canRead () : boolean {
return this . capabilities . fs ?. readTextFile === true ;
}
canWrite () : boolean {
return this . capabilities . fs ?. writeTextFile === true ;
}
async read ( sessionId : string , path : string ) : Promise < string > {
if ( ! this . canRead ()) {
throw new Error ( "Read capability not available" );
}
const response = await this . connection . readTextFile ({ sessionId , path });
return response . content ;
}
async write ( sessionId : string , path : string , content : string ) : Promise < void > {
if ( ! this . canWrite ()) {
throw new Error ( "Write capability not available" );
}
await this . connection . writeTextFile ({ sessionId , path , content });
}
}
Complete Example
Here’s a complete example that demonstrates safe file operations:
import * as acp from "@agentprotocol/acp" ;
import * as path from "node:path" ;
class FileOperationAgent implements acp . Agent {
private connection : acp . AgentSideConnection ;
private capabilities : acp . ClientCapabilities | null = null ;
constructor ( connection : acp . AgentSideConnection ) {
this . connection = connection ;
}
async initialize ( params : acp . InitializeRequest ) : Promise < acp . InitializeResponse > {
this . capabilities = params . clientCapabilities ;
return {
protocolVersion: acp . PROTOCOL_VERSION ,
agentCapabilities: {},
};
}
async readProjectFiles ( sessionId : string , directory : string ) : Promise < Map < string , string >> {
if ( ! this . capabilities ?. fs ?. readTextFile ) {
throw new Error ( "File reading not supported" );
}
const files = new Map < string , string >();
const filePaths = await this . listFiles ( directory );
for ( const filePath of filePaths ) {
try {
const response = await this . connection . readTextFile ({
sessionId ,
path: filePath ,
});
files . set ( filePath , response . content );
} catch ( error ) {
console . error ( `Failed to read ${ filePath } :` , error );
}
}
return files ;
}
async updateFile (
sessionId : string ,
filePath : string ,
newContent : string
) : Promise < boolean > {
if ( ! this . capabilities ?. fs ?. writeTextFile ) {
throw new Error ( "File writing not supported" );
}
// Request permission
const permission = await this . connection . requestPermission ({
sessionId ,
toolCall: {
toolCallId: crypto . randomUUID (),
title: `Update ${ path . basename ( filePath ) } ` ,
kind: "edit" ,
status: "pending" ,
locations: [{ path: filePath }],
rawInput: { path: filePath , content: newContent },
},
options: [
{ kind: "allow_once" , name: "Allow" , optionId: "allow" },
{ kind: "reject_once" , name: "Reject" , optionId: "reject" },
],
});
if ( permission . outcome . optionId !== "allow" ) {
return false ;
}
await this . connection . writeTextFile ({
sessionId ,
path: filePath ,
content: newContent ,
});
return true ;
}
private async listFiles ( directory : string ) : Promise < string []> {
// Implementation depends on your file listing strategy
return [];
}
// ... implement other required Agent methods
}
Best Practices
Check Capabilities Always verify the client supports file operations before attempting them
Use Absolute Paths All paths in the protocol must be absolute, not relative
Handle Errors Gracefully handle file not found and permission errors
Request Permission Request user permission before writing sensitive files