Tanstack Start

AI agents

Give an agent file tools in one line with s3delivery/ai.

s3delivery/ai turns an S3Delivery client into a set of tools an AI agent can call — uploadFile, listFiles, getFileUrl, deleteFile, and getUsage. Spread them into your agent's tools: and it can store, find, link, and clean up files on its own.

The tools are shaped to drop straight into the Vercel AI SDK (tool()-compatible: { description, inputSchema, execute }) and work with any framework that accepts that shape. ai is an optional peer dependency — this module never imports it, so the core SDK stays dependency-free apart from zod.

import { S3Delivery } from "s3delivery";
import { createFileTools } from "s3delivery/ai";
import { generateText } from "ai";

const tools = createFileTools(new S3Delivery());

await generateText({
  model,
  tools, // uploadFile, listFiles, getFileUrl, deleteFile, getUsage
  prompt: "Save these notes as notes.txt and give me a shareable link.",
});

Server-side only

createFileTools wraps a client that holds your secret API key. Run it on the server (your API route, agent backend, or job runner) — never in the browser.

The tools

Each tool carries an LLM-friendly description and a zod-validated inputSchema, and returns a compact, JSON-serializable result.

ToolInputResult
uploadFilename, text or base64, contentType?, visibility?The stored file (id, name, url, …)
listFileslimit?, offset?{ files: [...] } with ids and urls
getFileUrlfileId, download?{ url, file } — a fresh shareable URL
deleteFilefileId{ id, deleted: true }
getUsage(none)Bytes used, file count, plan, and limits

uploadFile takes the bytes as text (plain UTF-8, for text files) or base64 (for binary). The result objects are summarized — id, name, content type, size, status, visibility, and url — dropping timestamps the model rarely needs.

Safety knobs

Two built-in controls let you decide how much an agent can do.

readOnly

Expose only the read tools (listFiles, getFileUrl, getUsage). The mutating tools (uploadFile, deleteFile) are omitted entirely. A safe default for untrusted agents.

const safe = createFileTools(s3, { readOnly: true });

requireApproval + onApproval

Gate mutating tools behind human approval. Pass true to gate all mutating tools, or a per-tool map to gate specific ones.

const tools = createFileTools(s3, {
  requireApproval: { deleteFile: true },
  onApproval: async ({ tool, input }) =>
    confirm(`Allow agent to ${tool} ${JSON.stringify(input)}?`),
});

When a gated tool is invoked, onApproval runs first:

  • Return true → the action runs and its real result is returned.
  • Return false (or throw) → the tool returns { status: "denied", tool, message } and does not act.

If you do not supply onApproval, a gated tool returns { status: "approval_required", tool, message, input } instead of acting. The model surfaces that to your app, which can prompt the human out-of-band and re-run the action on approval — useful when approval is asynchronous (a Slack message, a UI confirmation) rather than a synchronous confirm.

OptionTypeNotes
readOnlybooleanOmit the mutating tools entirely.
requireApprovalboolean | { uploadFile?: boolean; deleteFile?: boolean }Gate mutating tools behind approval.
onApproval({ tool, input }) => boolean | Promise<boolean>Resolve a gated call: true runs it, false/throw denies.

Wiring into the Vercel AI SDK

createFileTools returns a record of tool name → tool, so spread it straight into tools:. It composes with your own tools.

import { S3Delivery } from "s3delivery";
import { createFileTools } from "s3delivery/ai";
import { generateText, tool } from "ai";
import { z } from "zod";

const s3 = new S3Delivery();

const result = await generateText({
  model,
  tools: {
    ...createFileTools(s3, { requireApproval: { deleteFile: true } }),
    // your own tools alongside the file tools
    getWeather: tool({
      description: "Get the weather for a city.",
      inputSchema: z.object({ city: z.string() }),
      execute: async ({ city }) => fetchWeather(city),
    }),
  },
  prompt: "Generate a CSV of last week's sales and give me a download link.",
});

The tools match the object the AI SDK's tool() helper returns, so you do not need to wrap them. They also work with any other framework that accepts the same { description, inputSchema, execute } shape.

Use the tools directly

The tools are plain objects, so you can call execute yourself — handy for tests or non-LLM automation.

const { listFiles, getFileUrl } = createFileTools(s3, { readOnly: true });

const { files } = await listFiles.execute({ limit: 5 });
const { url } = await getFileUrl.execute({ fileId: files[0].id });

A read-only retrieval agent

A common, safe pattern: let an agent find and link files, but never write.

const tools = createFileTools(s3, { readOnly: true });

await generateText({
  model,
  tools, // listFiles, getFileUrl, getUsage
  prompt: "Find the latest invoice PDF and give me a shareable link.",
});

The agent can discover ids with listFiles, then mint a fresh URL with getFileUrl — with no way to upload or delete.

On this page