Attachments and idempotency keys
Sending a file, and sending a message exactly once.
Attachments
Upload the bytes, then send the reference. References rather than bytes, because an event row is JSON in Postgres and a large one would not fit.
const ref = await client.blobs.upload({
filename: "receipt.pdf",
contentType: "application/pdf",
data: base64, // 8 MiB cap on the decoded bytes
sessionId // optional: scopes it to one session
})
await client.sessions.send({
id: sessionId,
message: "here is the receipt — does this match order A-1001?",
attachments: [ref]
})Images and text-shaped documents (PDF, plain text, CSV, JSON, Markdown) are accepted. An image or a PDF is rendered to the model as a native file part; a text document is rendered inline.
A type no provider will read is refused at upload, rather than best-efforted. It would otherwise reach the model as the sentence "there is a file", which looks like it worked and did not.
A reference the turn cannot read renders as a visible marker, for the same reason: a rendering that silently drops a file the user attached is worse than one that says so.
Idempotency keys
await client.sessions.send({ id, message, idempotencyKey: requestId })A duplicate returns the original event and runs no second turn. The check and the write are one atomic decision under the lock that already serialises a session's writes, so the loser of a race blocks until the winner commits and then finds its row — rather than reading, finding nothing, and inserting a second message.
The key is unique per session, which is the grain the log is already keyed and locked by.
Use it wherever a retry is possible and a second turn would be wrong: a webhook handler, a queue consumer, a mobile client with an unreliable connection.