Working with Files
PIE provides a file storage API that lets your agent upload, list, and manage files in the user's account. Files are isolated per agent by default, and documents are automatically indexed so the AI can search them.
Quick start
async function handler(input, context) {
// Upload a file
const file = await context.files.upload(
'data.csv',
'text/csv',
btoa('name,value\nAlice,100\nBob,200')
);
return {
success: true,
file, // Display inline in chat if it's an image
message: `Uploaded ${file.filename} (${file.tags.join(', ')})`,
};
}
module.exports = { handler };How it works
When your agent calls context.files.upload():
- Quota check -- PIE verifies the user hasn't exceeded the 250 MB limit for your agent.
- AI metadata -- Gemini generates a descriptive filename, tags, and a one-line description.
- GCS upload -- The file is stored securely in Google Cloud Storage under the user's account.
- File Search indexing -- If the file is a document type (PDF, text, CSV, etc.), it's automatically indexed in the user's knowledge base so the AI can reference it in chat.
- Database record -- A record is created with metadata, tags, and access control.
Supported file types
Automatically indexed (searchable by AI)
Documents uploaded via context.files are indexed if they match these types:
- Documents: PDF, DOCX, DOC, ODT, PPTX
- Text: TXT, MD, JSON, XML, YAML, CSV, TSV
- Code: JS, TS, PY, Java, Go, Rust, C, C++, PHP, Ruby, and more
- Spreadsheets: XLSX, XLS
Stored only (not indexed)
Media files are stored in GCS but not indexed in File Search:
- Images: JPEG, PNG, GIF, WebP, SVG
- Video: MP4, WebM
- Audio: MP3, WAV, OGG, FLAC
Displaying files in chat
Images
Return a file object in your handler result and PIE will display it inline:
async function handler(input, context) {
const imageBase64 = await generateChart(input.data);
const uploaded = await context.files.upload(
'chart.png',
'image/png',
imageBase64
);
return {
success: true,
file: uploaded,
description: uploaded.description,
};
}PIE detects result.file and renders images as  in the message. The URL never expires because it generates a fresh signed URL on each request.
Documents and other files
For non-image files, PIE includes the file info in the AI's response. The AI can then describe what was uploaded. The file will also appear on the user's Files page.
Listing and managing files
// List all files your agent can access
const files = await context.files.list();
// Filter by type
const images = await context.files.list('image/*');
const docs = await context.files.list('application/pdf');
// Get a download URL (valid ~15 minutes)
const url = await context.files.getUrl(fileId);
// Delete a file your agent created
await context.files.delete(fileId);Access control
- Isolation: By default, each agent can only see files it created.
- Cross-agent access: Users can grant other agents access to specific files via the PIE Files page.
- User ownership: Users can always see and delete all agent files from their Files page.
- Cleanup on uninstall: When a user removes your agent, they choose whether to keep or delete the files.
Quotas
- 250 MB per agent per user (configurable by the PIE administrator)
- Check current usage before large uploads
- An error with details is thrown if the quota is exceeded
try {
await context.files.upload('large-file.zip', 'application/zip', base64Data);
} catch (err) {
if (err.message.includes('quota')) {
return { error: true, message: 'Storage limit reached. Please delete some files.' };
}
throw err;
}Native E2B agents
If you're writing a native E2B agent (without the context shim), use the Plugin Bridge HTTP API directly:
const API_BASE = process.env.PIE_API_BASE;
const TOKEN = process.env.PIE_API_TOKEN;
// Upload
const res = await fetch(`${API_BASE}/api/plugin-bridge/files/upload`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
filename: 'data.csv',
mimeType: 'text/csv',
data: btoa('name,value\nAlice,100'),
}),
});
const file = await res.json();
// List files
const listRes = await fetch(`${API_BASE}/api/plugin-bridge/files`, {
headers: { 'Authorization': `Bearer ${TOKEN}` },
});
const { files } = await listRes.json();
// Get signed URL
const urlRes = await fetch(`${API_BASE}/api/plugin-bridge/files/${fileId}/url`, {
headers: { 'Authorization': `Bearer ${TOKEN}` },
});
const { url } = await urlRes.json();
// Delete
await fetch(`${API_BASE}/api/plugin-bridge/files/${fileId}`, {
method: 'DELETE',
headers: { 'Authorization': `Bearer ${TOKEN}` },
});Best practices
- Use descriptive filenames -- Even though PIE renames files with AI, a meaningful original name helps the AI generate better metadata.
- Upload documents for AI access -- If your agent fetches data the user might want to ask about later, upload it as a text/PDF file so it gets indexed.
- Clean up temporary files -- Delete files your agent no longer needs to stay within quota.
- Handle quota errors gracefully -- Always catch upload errors and provide a helpful message.
- Return
filein results for inline display -- For images and charts, return the uploaded file object in your handler result so it displays in chat.