discord
v0.1.0 · compiled with katari 0.1.0
Overview
from the package READMEdiscord — the Discord gateway, as Katari agents
A single module, discord, plus its FFI sidecar src/discord.ts: a
discord.js gateway client behind a provider, with watch / send agents on
top — the Discord twin of the Slack package. The connection is owned by the provider — log in
once, and every call in its scope shares the same live gateway client. Independent of any AI layer:
an app reacts to messages with whatever agent it hands to watch_messages as deliver_to.
discord.provider(token = ...)— logs in once and serves the connection for the extent of the continuation.discord.watch_messages(channel_id, deliver_to)— serve a channel forever, delivering each incoming message (channel_id/text/files) to your agent. Bot posts (this bot's own replies included) are not delivered, so replying cannot loop. Never resolves; composes underparallel [ … ].discord.send_message(channel_id, text, files)— post to a channel; pass[]for a plain text post.discord.send_files(channel_id, files, caption)— the tool shape ofsend_message, for an AI loop's tool list.
Files are first-class in both directions: an incoming message's attachments arrive as file values
(the sidecar downloads each from Discord's CDN and uploads it over the blob side channel), and
send_message posts file values back as Discord attachments.
The low-level externals (create_discord_client, discord_send, discord_watch) are implemented
in the sidecar, which keeps the live clients in a module-level map keyed by opaque handle.
Secrets / env
DISCORD_TOKEN— your bot token. Store it in the runtime:katari env set DISCORD_TOKEN --secret. It is astring of private, passed straight to the sidecar's login and never surfaced elsewhere.
To get a token: create an application in the
Discord Developer Portal, add a Bot, and copy its
token. The sidecar requests the Guilds, GuildMessages and MessageContent gateway intents, so
enable the MESSAGE CONTENT intent on the Bot page (the other two are unprivileged). Then invite
the bot to your server with permission to read and send messages in the channel you watch.
Sidecar dependencies
src/discord.ts imports discord.js and @katari-lang/port. They are declared in package.json;
run pnpm install (or npm install) in this package so katari apply can bundle the sidecar. (A
pure-Katari consumer that never applies this package does not need them.)
Usage
import discord
// Echo every message back to the channel it came from, attachments included.
agent echo(channel_id: string, text: string, files: array[file]) -> null {
discord.send_message(channel_id = channel_id, text = f"echo: ${text}", files = files)
}
agent echo_bot(channel_id: string) -> never {
use discord.provider(token = env.get_secret(key = "DISCORD_TOKEN"))
discord.watch_messages(channel_id = channel_id, deliver_to = echo)
}Hand discord.send_files to an AI loop's tool list to let the model post images and other files to
the channel on its own.
discord
12 declarationsget_discord_client#
requestrequest get_discord_client() -> stringThe live Discord gateway connection (the JS client lives in the sidecar; the handle is opaque).
Returns
Wire view (JSON Schema)
provider#
agentagent provider[R, effect E](token: string of private, continuation: agent(value: null) -> R with {...E, get_discord_client}) -> R with E | io | prelude.throw[discord_error]Provide the Discord gateway connection for the extent of the continuation: log in once, then serve get_discord_client to everything downstream. A failed login (a bad token, a transient network fault) raises discord_error (auth_error | api_error) — the connection is fixed at start, so a bad credential surfaces here rather than on the first send.
Generics
Parameters
The bot token; it stays in the provider, passed straight to the sidecar login.
Runs with get_discord_client served; its result is the provider's result.
Returns
Effects
Inferred type
agent(continuation: agent(value: null) -> T0 with get_discord_client | E1, token: string of private) -> T0 with throw[api_error | auth_error] | E1 | io
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
create_discord_client#
external agentexternal agent create_discord_client(token: string of private) -> string with prelude.throw[discord_error]Connect to Discord and log in; returns an opaque client handle. Raises discord_error (auth_error | api_error) when the login fails — an invalid token / missing permission is auth_error, a transient network fault api_error. Prefer the provider.
Parameters
The bot token to log in with.
Returns
Effects
Wire view (JSON Schema)
discord_close#
external agentexternal agent discord_close(client: string) -> nullClose a Discord connection: destroy the gateway client and drop the handle. The provider arms this as a finally, so the connection dies with the run — a gateway client left alive stays logged in and keeps receiving events long after its run is gone, a leaked session that still handles messages. Idempotent: closing an unknown or already-closed handle is a no-op, since a finalizer may run more than once.
Parameters
The opaque client handle create_discord_client returned.
Returns
Wire view (JSON Schema)
auth_error#
datadata auth_error(message: string)An authentication / authorization failure: the bot token is invalid or expired (HTTP 401), or the bot lacks permission in the channel (HTTP 403); message carries Discord's error. The bot cannot recover on its own — the operator must fix the token or the bot's permissions and restart the run (the connection is fixed at start). Let it surface so the bot stops loudly rather than silently dropping every reply.
Parameters
Wire view (JSON Schema)
api_error#
datadata api_error(message: string)A Discord API call failed for a non-auth reason: a rate limit (HTTP 429), a channel that is not a sendable text channel, a transient network fault; message carries Discord's error. Usually per-message or transient, so a bot catches it to drop just that reply and keep serving.
Parameters
Wire view (JSON Schema)
discord_error#
typetype discord_error = auth_error | api_errorDefinition
discord_send#
external agentexternal agent discord_send(client: string, channel_id: string, text: string, files: array[file]) -> null with prelude.throw[discord_error]Low-level: post to a channel on a given client, with any files as attachments. Raises auth_error or api_error (discord_error) when the Discord API call fails. Prefer the send_message agent.
Parameters
The opaque client handle to post through.
The channel to post into.
The message text; empty means an attachments-only post.
Files to attach to the post.
Returns
Effects
Wire view (JSON Schema)
discord_watch#
external agentexternal agent discord_watch[effect E](client: string, channel_id: string, deliver_to: agent(channel_id: string, text: string, files: array[file]) -> null with E) -> never with ELow-level: listen on a channel and deliver each incoming message (channel_id / text / attachments as files) to a callback. Prefer the watch_messages agent.
Generics
Parameters
The opaque client handle to listen through.
The channel to listen on.
Called once per incoming message; its effects E flow to the caller's handlers.
Returns
Effects
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
send_message#
agentagent send_message(channel_id: string, text: string, files: array[file]) -> nullPost a message to a channel using the ambient Discord client; files attach to it (pass [] for a plain text post — parameter defaults only take literals, so the empty array cannot default). Raises discord_error (auth_error | api_error) when the send fails — catch api_error to keep a bot serving past a transient failure, and let auth_error surface so a bad token stops the bot loudly.
Parameters
The channel to post into.
The message text; empty means an attachments-only post.
Files to attach; pass [] for a plain text post.
Returns
Inferred type
agent(channel_id: string, files: array[file], text: string) -> null with get_discord_client | throw[api_error | auth_error] | io
Wire view (JSON Schema)
send_files#
agentagent send_files(channel_id: string, files: array[file], caption: string) -> nullTool: post files (images) to a Discord channel, with a caption. Use the channel id given in the conversation context; pass each file as its handle object — a bare {"$katari_ref": "<id>"} suffices.
Parameters
The channel to post into (given in the conversation context).
The files to post; pass each as its handle object — a bare {"$katari_ref": "<id>"} suffices.
The text posted alongside the files.
Returns
Inferred type
agent(caption: string, channel_id: string, files: array[file]) -> null with get_discord_client | throw[api_error | auth_error] | io
Wire view (JSON Schema)
watch_messages#
agentagent watch_messages[effect E](channel_id: string, deliver_to: agent(channel_id: string, text: string, files: array[file]) -> null with E) -> never with E | get_discord_client | ioServe a channel forever, delivering each incoming message to deliver_to — shaped like time.watch / google_calendar.watch so it composes under parallel [ … ]. Bot posts (this bot's own replies included) are not delivered, so replying from deliver_to cannot loop.
Generics
Parameters
The channel to serve.
Called once per incoming message; its effects E flow to the caller's handlers.
Returns
Effects
Inferred type
agent(channel_id: string, deliver_to: agent(channel_id: string, files: array[file], text: string) -> null with E3) -> never with get_discord_client | E3 | io
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.