framework
Version:
The (AI) Framework: turnkey, zero-config AI orchestration that wraps a coding-agent CLI (Claude Code) as a black box and takes you from an idea to a running app. Vite for AI.
137 lines • 5.67 kB
TypeScript
/**
* A minimal Discord gateway client (#680): the inbound half of the Discord integration, which
* until now was outbound-only — a webhook `POST` can notify, but it cannot read a reply (#627).
*
* Deliberately hand-rolled over the global `WebSocket` rather than pulling in `discord.js`. The
* package has three runtime dependencies and builds everything else on node builtins behind
* injectable seams; a client library for the handful of opcodes below would be the largest
* dependency in the package by an order of magnitude.
*
* Implements only what a chat bot needs: identify, heartbeat, resume, and message events.
*/
/** Gateway opcodes we act on. */
export declare const OP: {
readonly dispatch: 0;
readonly heartbeat: 1;
readonly identify: 2;
readonly resume: 6;
readonly reconnect: 7;
readonly invalidSession: 9;
readonly hello: 10;
readonly heartbeatAck: 11;
};
/**
* Gateway intents. `MESSAGE_CONTENT` is privileged: it must be enabled on the application in
* Discord's developer portal, or the gateway connects and every message arrives with an empty
* `content`. That failure is silent, which is why {@link DiscordGateway} logs it explicitly.
*/
export declare const INTENTS: {
readonly guildMessages: number;
readonly directMessages: number;
readonly messageContent: number;
};
/** The intents a chat bot needs: messages in channels and DMs, plus their text. */
export declare const CHAT_INTENTS: number;
/**
* The socket seam. Mirrors the slice of `WebSocket` this uses, so a test drives the protocol
* with a fake and no network — the same shape the watchers use for `fetch`.
*/
export interface GatewaySocket {
send(data: string): void;
close(): void;
onMessage(handler: (data: string) => void): void;
onClose(handler: () => void): void;
onError(handler: (err: unknown) => void): void;
}
/** Opens a {@link GatewaySocket} for a url. */
export type SocketFactory = (url: string) => GatewaySocket;
/** A cancellable timer, so heartbeats are deterministic under test. */
export interface Timer {
stop(): void;
}
/** Schedules `fn` every `ms`. Default wraps `setInterval` and unrefs it. */
export type IntervalFactory = (fn: () => void, ms: number) => Timer;
/** Schedules `fn` once after `ms`. Default wraps `setTimeout` and unrefs it. */
export type DelayFactory = (fn: () => void, ms: number) => Timer;
/** One inbound chat message, narrowed to what routing needs. */
export interface DiscordMessage {
id: string;
channelId: string;
/** Message text. Empty when the privileged MESSAGE_CONTENT intent is not enabled. */
content: string;
authorId: string;
authorName: string;
/** Whether the author is a bot (including us): never act on these, or two bots loop forever. */
fromBot: boolean;
/** Set when the message replies to another, used to thread an answer back to its gate. */
replyToId?: string;
}
/** What {@link DiscordGateway} reports to its owner. */
export interface GatewayHandlers {
onMessage(message: DiscordMessage): void;
/** Connected and identified; carries our own user id so we can ignore our own messages. */
onReady?(selfId: string): void;
/** Non-fatal diagnostics (a failed resume, a missing intent). */
onLog?(message: string): void;
}
/** Injectable seams for {@link DiscordGateway}. */
export interface GatewayDeps {
socket?: SocketFactory;
interval?: IntervalFactory;
delay?: DelayFactory;
url?: string;
}
/**
* A connected Discord bot session. Owns the socket, the heartbeat, and the resume state; hands
* every chat message to its {@link GatewayHandlers}.
*
* Errors are swallowed into `onLog` rather than thrown: a notifier must never take the daemon
* down, which is the same contract the intervention/activity watchers follow.
*/
export declare class DiscordGateway {
private readonly token;
private readonly handlers;
private readonly deps;
private socket;
private heartbeat;
private sequence;
private sessionId;
private resumeUrl;
private acked;
private selfId;
/** Set by {@link stop}, so a socket closing on our own terms never reconnects. */
private stopped;
/** Consecutive reconnects, for the backoff. Reset once a connection actually works. */
private attempts;
private pendingReconnect;
constructor(token: string, handlers: GatewayHandlers, deps?: GatewayDeps);
/** Open the connection and identify. Safe to call once; use {@link stop} to end it. */
connect(): void;
/**
* Close the connection for good. This is what takes the bot offline on `Ctrl+C` (#680): the
* daemon calls it from its shutdown block, and no reconnect follows.
*/
stop(): void;
/** Our own user id once READY has landed. */
get userId(): string | undefined;
private open;
/**
* A closed socket we did not close ourselves: resume if we can, else identify fresh.
*
* Backed off, and that is not a nicety: a connection that fails immediately (offline, a bad
* token) closes as fast as it opens, so reconnecting inline is a tight loop that pins a core
* and gets the bot rate-limited. Doubles to a cap, and resets once a connection works.
*/
private reopen;
private receive;
private onHello;
private identify;
private resume;
private sendHeartbeat;
private onDispatch;
private send;
private log;
}
/** Narrow a MESSAGE_CREATE payload; `undefined` when it is not a shape we can use. */
export declare function parseMessage(data: unknown): DiscordMessage | undefined;
//# sourceMappingURL=gateway.d.ts.map