Browse modules

discord

v0.1.0 · compiled with katari 0.1.0

Overview

from the package README

discord — 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 under parallel [ … ].
  • 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 of send_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 a string 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 declarations

get_discord_client#

request
request get_discord_client() -> string

The live Discord gateway connection (the JS client lives in the sidecar; the handle is opaque).

Returns

string
Wire view (JSON Schema)

provider#

agent
agent 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

Rtype
Eeffect

Parameters

token

The bot token; it stays in the provider, passed straight to the sidecar login.

of private
string
continuation

Runs with get_discord_client served; its result is the provider's result.

agent
input
object
value
null
output
R
effects
override
base
E

Returns

R

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 agent
external 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

token

The bot token to log in with.

of private
string

Returns

string
Wire view (JSON Schema)

discord_close#

external agent
external agent discord_close(client: string) -> null

Close 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

client

The opaque client handle create_discord_client returned.

string

Returns

null
Wire view (JSON Schema)

auth_error#

data
data 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

message
string
Wire view (JSON Schema)

api_error#

data
data 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

message
string
Wire view (JSON Schema)

discord_error#

type
type discord_error = auth_error | api_error

Definition

discord_send#

external agent
external 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

client

The opaque client handle to post through.

string
channel_id

The channel to post into.

string
text

The message text; empty means an attachments-only post.

string
files

Files to attach to the post.

array
file

Returns

null
Wire view (JSON Schema)

discord_watch#

external agent
external 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 E

Low-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

Eeffect

Parameters

client

The opaque client handle to listen through.

string
channel_id

The channel to listen on.

string
deliver_to

Called once per incoming message; its effects E flow to the caller's handlers.

agent
input
object
channel_id
string
text
string
files
array
file
output
null
effects
E

Returns

never

Effects

E

Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.

send_message#

agent
agent send_message(channel_id: string, text: string, files: array[file]) -> null

Post 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

channel_id

The channel to post into.

string
text

The message text; empty means an attachments-only post.

string
files

Files to attach; pass [] for a plain text post.

array
file

Returns

null

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#

agent
agent send_files(channel_id: string, files: array[file], caption: string) -> null

Tool: 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

channel_id

The channel to post into (given in the conversation context).

string
files

The files to post; pass each as its handle object — a bare {"$katari_ref": "<id>"} suffices.

array
file
caption

The text posted alongside the files.

string

Returns

null

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#

agent
agent 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 | io

Serve 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

Eeffect

Parameters

channel_id

The channel to serve.

string
deliver_to

Called once per incoming message; its effects E flow to the caller's handlers.

agent
input
object
channel_id
string
text
string
files
array
file
output
null
effects
E

Returns

never

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.