This doc is designed for your coding agent. Share it with your agent so it learns how to send images back to Discord.
Example prompt:
Read docs/sendimages.md from OpenAB GitHub and send the image back to my Discord thread.💡 Tip: If it works the first time, ask your agent to save this as a SKILL so it remembers how to do it next time without re-reading the doc.
OpenAB does not relay images from the agent to Discord — it only streams text. To send an image back to the user, the agent must call the Discord API directly.
For sending non-image files (PDF, CSV, logs, etc.), see sendfiles.md.
┌──────────┐ text only ┌──────────┐ ACP stdio ┌──────────────┐
│ Discord │◄────────────│ OpenAB │◄────────────│ Agent (CLI) │
│ Thread │ └──────────┘ └──────┬───────┘
│ │ │
│ │ Discord REST API │
│ │◄─────────────────────────────────────────────┘
│ │ POST /channels/{thread_id}/messages
│ │ + multipart file attachment
└──────────┘
OpenAB only streams text via ACP. To send an image, the agent calls the
Discord API directly using the thread_id from sender_context.
Every message from OpenAB includes a <sender_context> JSON block:
{
"schema": "openab.sender.v1",
"sender_id": "845835116920307722",
"sender_name": "pahud.hsieh",
"display_name": "pahud.hsieh",
"channel": "discord",
"channel_id": "1490282656913559673",
"thread_id": "1499442140172910654",
"is_bot": false
}Use thread_id as the target channel. If thread_id is absent, fall back to channel_id.
The agent needs a Discord Bot Token to call the API. Pass it via [agent] env in config.toml:
[agent]
env = { DISCORD_BOT_TOKEN = "${DISCORD_BOT_TOKEN}" }
⚠️ The token in[discord] bot_tokenis consumed by OpenAB itself and is not automatically forwarded to the agent subprocess. You must explicitly pass it via[agent] env.
For production, consider creating a dedicated "File Deliverer" bot with minimal permissions instead of sharing the main OAB bot token:
┌─────────────────────────────────────────────────────────────────┐
│ Discord Server │
│ │
│ ┌─────────────────┐ ┌──────────────────────────────┐ │
│ │ OAB Bot │ │ File Deliverer Bot │ │
│ │ (main bot) │ │ (dedicated, minimal perms) │ │
│ │ │ │ │ │
│ │ ✅ Read Messages │ │ ✅ Send Messages │ │
│ │ ✅ Manage Threads│ │ ✅ Send Messages in Threads │ │
│ │ ✅ Add Reactions │ │ ✅ Attach Files │ │
│ │ ✅ Send Messages │ │ ❌ Read Messages │ │
│ │ ✅ Attach Files │ │ ❌ Manage Threads │ │
│ └────────┬────────┘ └──────────────┬───────────────┘ │
│ │ │ │
│ │ ACP stdio │ REST API │
│ ▼ ▼ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Agent (kiro-cli / claude / codex) │ │
│ │ │ │
│ │ DISCORD_FILE_BOT_TOKEN ──► POST /channels/{id}/messages │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
What users see in the thread:
超渡法師: 好的,我幫你生成報告...
File Deliverer: 📎 report.pdf
| Same token (simple) | Dedicated bot (recommended) | |
|---|---|---|
| Setup | One bot, one token | Create a second bot in Discord Developer Portal |
| Agent permissions | Everything the OAB bot can do | Only Send Messages + Attach Files |
| Token leak impact | Full bot access exposed | Limited to file uploads |
| Audit trail | Cannot distinguish OAB vs agent messages | Separate bot identity in logs |
# Production: dedicated file-upload bot (e.g. "File Deliverer")
[agent]
env = { DISCORD_FILE_BOT_TOKEN = "${DISCORD_FILE_BOT_TOKEN}" }For personal use or small teams, sharing the same token is fine — you already trust your agent.
Use the Discord Create Message endpoint with a multipart/form-data body:
POST https://discord.com/api/v10/channels/{thread_id}/messages
Authorization: Bot {DISCORD_BOT_TOKEN}
Content-Type: multipart/form-data
curl -X POST "https://discord.com/api/v10/channels/${THREAD_ID}/messages" \
-H "Authorization: Bot ${DISCORD_BOT_TOKEN}" \
-F "content=Here is the generated image" \
-F "files[0]=@/path/to/image.png"import os, requests
def send_image(thread_id: str, image_path: str, message: str = ""):
url = f"https://discord.com/api/v10/channels/{thread_id}/messages"
headers = {"Authorization": f"Bot {os.environ['DISCORD_BOT_TOKEN']}"}
with open(image_path, "rb") as f:
requests.post(url, headers=headers,
data={"content": message},
files={"files[0]": (os.path.basename(image_path), f)})const fs = require("fs");
const FormData = require("form-data");
async function sendImage(threadId, imagePath, message = "") {
const form = new FormData();
form.append("content", message);
form.append("files[0]", fs.createReadStream(imagePath));
await fetch(`https://discord.com/api/v10/channels/${threadId}/messages`, {
method: "POST",
headers: { Authorization: `Bot ${process.env.DISCORD_BOT_TOKEN}` },
body: form,
});
}If your agent generates images to a known directory (e.g. Codex writes to
~/.codex/generated_images/), you can run a file-watcher sidecar that
automatically uploads new images:
- Watch the output directory for new files.
- Read the session metadata to find the originating
thread_id. - Upload via the Discord API.
- Track uploaded files in a state file to avoid duplicates.
This is the pattern used by the community discord-image-uploader sidecar.
- Never hardcode the bot token. Read it from
$DISCORD_BOT_TOKENor a mounted secret. - Scope permissions. The bot only needs
Send MessagesandAttach Filesin the target channels. - Validate file paths. If the agent constructs paths dynamically, sanitize them to prevent path traversal.
- Rate limits. Discord enforces rate limits on message creation. Space uploads if sending multiple images.
In the Discord Developer Portal, ensure your bot has:
-
Send Messages -
Send Messages in Threads -
Attach Files
These are typically already granted if your bot works with OpenAB.
Q: Can OpenAB relay images natively? A: Not currently. OpenAB streams text via ACP JSON-RPC. Image/file sending is done out-of-band by the agent.
Q: Does this work with Slack / Telegram / LINE?
A: The same concept applies — call the platform's file upload API using the channel ID from sender_context. The API details differ per platform.
Q: What image formats are supported? A: Discord supports PNG, JPEG, GIF, and WebP. Max file size is 25 MB (or higher with Nitro boost).