Files
Upload attachments, send them on a message, show them in the UI.
Files are user-supplied inputs. PAI stores an immutable attachment on the transcript and asks a file provider for the bytes when the model needs them.
Upload first, then send. That is what makes progress, thumbnails, and "which file failed" possible.
const client = paiClient.agent("main");
const thread = client.thread(threadId);
const uploaded = await client.files.upload({
body: file,
mediaType: file.type,
filename: file.name,
});
await thread.send({
text: "Describe this image.",
attachments: [uploaded],
});Only fileId crosses the wire. Filename, type, and size are snapshotted from the provider when the part is stored.
Configure a provider
import { createPai } from "@pai/core";
import { createInMemoryFileProvider } from "@pai/files-inmemory";
import { createMongoFileProvider } from "@pai/files-mongo";
import { assistantAgent } from "./assistant-agent";
const files = process.env.MONGO_URL
? createMongoFileProvider({ url: process.env.MONGO_URL })
: createInMemoryFileProvider();
if ("initialize" in files) await files.initialize();
export const pai = createPai({
agents: { main: assistantAgent },
scopeKey: (identity) => identity.workspaceId,
userKey: (identity) => identity.userId,
files,
});Without a provider, uploads fail. In-memory is for tests and the first app. Mongo GridFS is the production default next to thread storage.
Render an attachment
Attachment parts sit in message.parts like text and tools:
if (part.type === "attachment") {
return <AttachmentCard key={part.id} part={part} />;
}URLs are not stored on the message. Ask for one when you need a preview:
const preview = await client.files.url({
fileId: part.fileId,
intent: "view",
});
const href = preview?.url;In a tool card or transcript, prefer useFileUrl():
const preview = Assistant.useFileUrl(part.fileId, { intent: "view" });
const href = preview?.url;Or stream bytes with client.files.read({ fileId }).
Composer
function Composer() {
const composer = Assistant.useComposer();
return (
<input
type="file"
onChange={(event) => {
if (event.target.files) void composer.attachFiles(event.target.files);
}}
/>
);
}attachFiles uploads through client.files.upload and stages the result. Image files also get a local previewUrl for a thumbnail.
Next
- Sandbox when the agent should write files itself
- Serve over HTTP for the Mongo provider