Loading skill
Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Handles file uploads, image attachments, and media processing in agent conversations. Use this when agents analyze images, process documents, or generate files.
.claude/skills/majiayu000-convex-agents-files/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 49% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 2% | 0% |
| case-03 | ✗→✓ | ▲ Improved | -2% | 0% |
| case-04 | ✗→✓ | ▲ Improved | -15% | 0% |
| case-05 | ✗→✓ | ▲ Improved | -40% | 0% |
Files and images let agents understand and generate visual content. Covers uploading, storing, attaching to messages, and managing file lifecycle.
typescriptimport { storeFile } from "@convex-dev/agent"; export const uploadFile = action({ args: { fileData: v.string(), filename: v.string(), mimeType: v.string() }, handler: async (ctx, { fileData, filename, mimeType }) => { const bytes = Buffer.from(fileData, "base64"); const { file } = await storeFile( ctx, components.agent, new Blob([bytes], { type: mimeType }), { filename, sha256: "hash" } ); return { fileId: file.fileId, url: file.url, storageId: file.storageId, }; }, });
Upload first, then send message with attachment:
typescriptimport { saveMessage, getFile } from "@convex-dev/agent"; // Step 1: Save message with file export const submitFileQuestion = mutation({ args: { threadId: v.string(), fileId: v.string(), question: v.string() }, handler: async (ctx, { threadId, fileId, question }) => { const { imagePart, filePart } = await getFile(ctx, components.agent, fileId); const { messageId } = await saveMessage(ctx, components.agent, { threadId, message: { role: "user", content: [ imagePart ?? filePart, { type: "text", text: question }, ], }, metadata: { fileIds: [fileId] }, }); return { messageId }; }, }); // Step 2: Generate response export const generateFileResponse = action({ args: { threadId: v.string(), promptMessageId: v.string() }, handler: async (ctx, { threadId, promptMessageId }) => { const { thread } = await myAgent.continueThread(ctx, { threadId }); await thread.generateText({ promptMessageId }); }, });
Pass file directly in generation:
typescriptexport const analyzeImageInline = action({ args: { threadId: v.string(), imageData: v.string(), question: v.string() }, handler: async (ctx, { threadId, imageData, question }) => { const { thread } = await myAgent.continueThread(ctx, { threadId }); await thread.generateText({ message: { role: "user", content: [ { type: "image", image: Buffer.from(imageData, "base64"), mimeType: "image/png", }, { type: "text", text: question }, ], }, }); }, });
typescriptexport const generateAndSaveImage = action({ args: { threadId: v.string(), prompt: v.string() }, handler: async (ctx, { threadId, prompt }) => { const { image } = await generateImage({ model: openai.image("dall-e-2"), prompt, }); const { file } = await storeFile(ctx, components.agent, image, { filename: `generated-${Date.now()}.png`, }); return { fileId: file.fileId }; }, });
Other measured skills in the registry, with their headline benchmark lift.