Session Files
Files uploaded to a session are read by the agent in the turns that follow. The files live in a Document Store bucket chosen by the agent's administrator; client.agents.sessions.files uploads, lists, downloads and deletes them.
New in 3.1.0
sessions.files.upload / list / delete, files.content / contentStream, Agent.attachments on agents.get(), and the bucket_key / path fields on every file. Files need a Console release that ships session files on buckets.
When to use
- A user hands the agent a PDF, spreadsheet, image or text file and asks about it.
- You want to upload first, ask later — the session exists before any message does.
- You need to show or download what the agent was given, including the text it actually read.
The flow: create → upload → ask
import { readFile } from 'node:fs/promises';
const agent = await client.agents.get('support-bot');
if (!agent.attachments?.enabled) throw new Error('This agent takes no files');
const session = await client.agents.sessions.create({ agent: 'support-bot' });
const file = await client.agents.sessions.files.upload(session.id, await readFile('invoice.pdf'), {
name: 'invoice.pdf',
mimeType: 'application/pdf',
});
file.bucket_key; // "session-docs": the Document Store bucket it lives in
file.path; // "agent-sessions/support-bot/<sessionId>/<unique name>": its path inside that bucket
// A file nobody has sent yet goes with the session's next message...
const reply = await client.agents.responses.create({
model: 'support-bot',
session_id: session.id,
input: 'What is the total on this invoice?',
});
// ...or name it in the message.
await client.agents.responses.create({
model: 'support-bot',
session_id: session.id,
input: [{ role: 'user', content: [
{ type: 'input_text', text: 'And the due date?' },
{ type: 'input_file', file_id: file.id },
] }],
});A file for input_file / input_image is named with session_id, conversation or previous_response_id. A file nobody has sent yet goes with the session's next message anyway, unless the agent's Send new uploads with the next message setting is off.
Check what the agent accepts
agents.get(key).attachments (not in agents.list()) says whether and how much, before you upload anything:
| Field | Meaning |
|---|---|
enabled | Files are on for the agent and its bucket can take a file right now. When false, uploads are refused (403 attachments_disabled, or 409 storage_unavailable when the bucket cannot take a file at the moment). |
max_files | Files one session holds (0 while files are switched off) |
max_file_size_mb | Largest file in MB (0 while files are switched off) |
allowed_mime_types | Accepted MIME types; image/* style wildcards allowed. Empty = any |
accepts_images | The agent's model reads images. When false, an attached image is replaced by a note saying so; the model never sees it |
When files are on but the bucket cannot take a file at the moment, enabled is false and the real limits are still reported. attachments is absent on a Console that predates session files.
Methods
| Call | What it does | Route |
|---|---|---|
files.upload(sessionId, bytes, { name, mimeType?, signal? }) | Upload (201): the raw bytes as the request body, not base64 | POST …/files?name=<name> |
files.list(sessionId) | The session's files, oldest first | GET …/files |
files.content(sessionId, fileId, { variant?, signal? }) | Download the bytes: { data, contentType, fileName? } | GET …/files/{fileId}/content |
files.contentStream(sessionId, fileId, { variant?, signal? }) | The same as a stream ({ body, contentType, fileName?, size? }) for large files | GET …/files/{fileId}/content |
files.delete(sessionId, fileId) | Delete the file (object, converted text and record) → { id, object: 'agent.session.file', deleted: true } | DELETE …/files/{fileId} |
(… is /api/client/v1/agents/sessions/{sessionId}.)
Upload
const file = await client.agents.sessions.files.upload(session.id, bytes, {
name: 'invoice.pdf', // required for raw bytes; taken from a File when omitted
mimeType: 'application/pdf', // taken from a Blob/File when omitted, else application/octet-stream
});| Option | Description |
|---|---|
bytes | Blob, File, Uint8Array or ArrayBuffer |
name | File name. Required for raw bytes; read from a File. Without one the SDK throws CognipeerError before sending anything |
mimeType | MIME type. Read from a Blob / File when omitted, else application/octet-stream |
signal | Aborts the request |
JSON files are sent as application/octet-stream
The upload route also accepts a JSON { name, content_type, data: <base64> } body for small files, so it reads any JSON content type as that envelope. The SDK always sends the raw form, and therefore sends a JSON mimeType (application/json, *+json) as application/octet-stream. The file name still decides how a .json file is read.
The SessionFile object
interface SessionFile {
id: string; // pass as file_id in an input_file / input_image part
object: 'agent.session.file';
session_id: string;
name: string;
mime_type: string;
size: number; // bytes
conversion_status: 'pending' | 'succeeded' | 'failed' | 'skipped' | (string & {});
conversion_error?: string; // why a conversion failed
bucket_key: string; // the Document Store bucket the file lives in
path: string; // <folder>/<agentKey>/<sessionId>/<unique name>
content_url: string; // Console path that serves the bytes
created_at?: string; // ISO
expires_at?: string; // ISO — after this the Console deletes the file; absent = kept
}conversion_status | Meaning |
|---|---|
succeeded | The agent can read the text (documents become markdown) |
pending | Still converting |
failed | Only the original exists (conversion_error says why) |
skipped | Needs no conversion (an image) |
bucket_key is the bucket the file was stored in when it was uploaded; changing the agent's bucket later affects new files only. content_url is a Console path (/api/client/v1/agents/sessions/{id}/files/{fileId}/content); prefer content() / contentStream(), which add the base URL and the token.
Download
import { writeFile } from 'node:fs/promises';
// The bytes as uploaded.
const { data, contentType, fileName } = await client.agents.sessions.files.content(session.id, file.id);
await writeFile(fileName ?? 'download', data);
// The text the agent reads (documents become markdown), when the conversion succeeded.
const { data: md } = await client.agents.sessions.files.content(session.id, file.id, { variant: 'markdown' });
console.log(new TextDecoder().decode(md));
// A large file: stream it instead of buffering.
const { body, size } = await client.agents.sessions.files.contentStream(session.id, file.id);variant is original (default) or markdown. A file without a text version answers 409 markdown_unavailable (a picture, or a conversion that did not succeed — read the original instead). fileName is the base name from Content-Disposition. For contentStream() the client timeout covers the wait for the response headers only (a transfer that keeps producing bytes is not cut off); cancel body if you stop reading early.
The content call needs a recent Console
content() / contentStream() need a Console that serves the session-file content route (GET /agents/sessions/{id}/files/{fileId}/content). On an older one it answers 404.
Console settings that control files
Where files live, and the limits, are the agent's Configure → Files setting in the Console, not something a caller chooses. Defaults when a field is unset:
| Setting | Default | Effect |
|---|---|---|
| Let people attach files | off | Off: uploads are refused (403 attachments_disabled). Needs a bucket |
| Bucket | none | The Document Store bucket the files are saved in. Only a bucket of the agent's project whose storage provider is active can be chosen |
| Folder | agent-sessions | The folder inside the bucket. Letters, digits and . _ - / only; no . or .. segments; at most 200 characters |
| Files per session | 10 | A further upload is 409 too_many_files |
| Largest file (MB) | 20 | Larger is 413 file_too_large |
| Keep files for (days) | 30 | After this the file is deleted from the bucket (expires_at); changing it affects files uploaded from then on |
| Allowed file types | any | MIME types; image/* and text/* match a whole family. Others are 415 unsupported_file_type |
| Put in the message up to (tokens) | 30,000 | The text of all files sent with one message is added up: under this it is put in the message; over it, none is, and the agent reads the files page by page with tools |
| Page size when reading (characters) | 20,000 | How much of a file the agent gets per read when it reads page by page |
| Send new uploads with the next message | on | A file uploaded to a session that no message has carried yet goes along with the next message. Off: only files a message names are sent |
| Images per model request | 6 | Past this number, the oldest images become a note naming the file, which the agent can look at again |
| Image size per request (MB) | 12 | The most image data one model request carries, before encoding; the oldest images are left out first |
| Show recent images again (messages) | 2 | How many of the latest image-bearing messages are shown to the agent again when they fell out of its context; 0 = never |
The agent reads session files with built-in read-only tools (session_files_list, session_files_read, and session_files_view_image while the session holds an image and the model takes images).
An image reaches the model only when the agent's model reads images (agent.attachments.accepts_images); otherwise the turn records a note that the image was not sent.
Files are ordinary bucket objects
The files are ordinary objects in the bucket, so the Files API sees them too. They carry purpose: 'conversation', conversationId, agentKey, expiresAt and uploadedBy:
const { files, folders } = await client.files.list(file.bucket_key, {
prefix: 'agent-sessions/support-bot/',
purpose: 'conversation', // only files attached to agent sessions; 'other' = only the rest
});A bucket an agent keeps session files in cannot be deleted or disabled until the agent uses another one: the Files management API answers 409 bucket_in_use and lists the agents (error.response.agents). No call of this SDK raises it.
Limits
| Limit | Value |
|---|---|
| Files per session | the agent's setting (default 10; attachments.max_files) |
| File size | the agent's setting (default 20 MB; attachments.max_file_size_mb), capped by the Console's overall upload ceiling |
| Retention | the agent's setting (default 30 days) |
| Quota | the tenant's file quotas apply: 429 quota_exceeded |
Errors
| Status | errorCode | When |
|---|---|---|
| 403 | attachments_disabled | The agent takes no files: they are off for it, or on with no bucket chosen. Also raised by responses.create() with input_file / input_image parts |
| 404 | session_not_found | No such session in the token's project |
| 404 | file_not_found | The file is not on this session (or was deleted or expired) |
| 409 | storage_unavailable | The agent's bucket cannot take a file now (deleted, disabled, or its provider is gone, inactive or not assigned to the project); the message names the bucket |
| 409 | storage_not_configured | Files are on but no bucket was chosen (older data); pick one in the agent's Files settings |
| 409 | too_many_files | The session already holds the agent's maximum |
| 409 | markdown_unavailable | variant: 'markdown' on a file without a text version |
| 413 | file_too_large | Over the agent's file size limit |
| 415 | unsupported_file_type | The type is not in the agent's allowed list |
| 429 | quota_exceeded | Rate or storage quota of the tenant |
See Agent error codes for how to branch on them.
Security notes
- Files are scoped to the token's project: a session — and so its files — of another project answers
404, even with its id. - A session file's bytes are served with
Cache-Control: private,X-Content-Type-Options: nosniffand a sanitisedContent-Disposition: pictures open in place, everything else downloads, and SVG and HTML always download. Do not re-serve a downloaded file under a content type you did not check. - Treat a file's name and content as untrusted input: the agent reads them, and a document can carry instructions aimed at the model.
- Files expire. Do not use a session file as long-term storage; copy what you need to your own storage.
Troubleshooting
403 attachments_disabled. Files are off for the agent, or on with no bucket. Check agents.get(key).attachments.enabled and ask the agent's administrator to turn files on and pick a bucket in Configure → Files.
409 storage_unavailable. The bucket was deleted or disabled, or its storage provider is unusable; the error message names the bucket. An administrator fixes the bucket or picks another one.
The agent answers as if it never saw my file. Check file.conversion_status: pending means the text is not ready; failed means only the original exists. Then check the file reached a turn — name it with an input_file part, or make sure Send new uploads with the next message is on. For an image, check attachments.accepts_images.
markdown_unavailable on a PDF. The conversion did not succeed (conversion_status: 'failed'). Download the original with the default variant.
content() answers 404. Either the file is not on this session, or the Console is too old to serve the content route.
Related
- Agent sessions · Agents → Input parts
- Files API — the bucket objects behind session files
- Example: create a session, upload a file, ask

