Reference
@chloejs/ core/ channels
The ways an agent is reached: Telegram, Slack and the API, bound in its agent.ts.
The ways an agent is reached. Each is a function an agent calls in the
channels list of its agent.ts.
import { telegramChannel, apiChannel, webChannel } from "@chloejs/core/channels";A channel chloe does not ship is written in the agent's own channels/
folder, and hands each message to receive. Same rule as index.ts:
adding a name here is publishing it.
apiChannelButtoncommandsemailChannelEmailOptionsHandledIncominginPiecesopenEmailreceiveRulesslackChannelSlackOptionsStartedtelegramChannelTelegramOptionswebChannelWebLimitsWebOptionswhatsappChannelWhatsAppOptionsWhile
apiChannelfunction
export function apiChannel(options: { chatHistory?: ChatHistory } = {}): ChannelchatHistory is how much of a caller's thread a turn is shown.
Buttoninterface
export interface Button {
label: string;
sends: string;
}One button under a reply.
commandsfunction
export function commands(agent: Agent): { command: string; description: string }[]The commands a channel can offer in its own menu: each job, with "_" for "-", then /models and /clear.
emailChannelfunction
export function emailChannel(options: EmailOptions): ChannelAn agent on email, as a channel its own agent.ts names.
EmailOptionsinterface
export interface EmailOptions {
/** "email" unless the agent has two. The first half of an address a job asks, "email:someone@example.com". */
name?: string;
/** The people it may write to and hear from, by address. Nobody else is ever sent anything. */
allowFrom: string[];
/** Tools a turn on this channel is not given, by name, like the ones that read the owner's own mail. */
withoutTools?: string[];
/** How much of a conversation a turn is shown: `{ messages, days }`. */
chatHistory?: ChatHistory;
/** Where the dashboard is, instead of `dashboard.remote.url` in settings. Only the tests change it. */
dashboard?: string;
/** The workspace key, instead of `dashboard.remote.api_key`. Only the tests change it. */
key?: string;
/** How DNS is asked for a DKIM key. Only the tests change it. */
lookUp?: LookUp;
}How an agent is put on email: who may write to it, and what its turns may not use.
Handledinterface
export interface Handled {
/** What to send back. Empty when there is nothing to send, because a question already went out. */
text: string;
runId?: string;
steps: number;
cost: number;
/** The job that took it, when one did. */
job?: string;
/** Choices to show under the text, on a channel that can. Pressing one sends `sends` as if the person had written it. */
buttons?: Button[];
}What came of a message. Nothing at all means it was not for the agent.
Incominginterface
export interface Incoming {
/** The channel's name, like "telegram". What the log shows, and the first half of an address. */
channel: string;
/** Where it was said, as the channel names it. `${channel}:${chat}` is how a job asks back here. */
chat: string;
/** The conversation it belongs to: one per chat, or per topic in a forum. Empty for none, so nothing is remembered. */
thread: string;
from: { id: string; name: string };
text: string;
/** A one-to-one chat, rather than a group. */
private: boolean;
/** In a group: it mentions the agent or replies to it. */
addressed?: boolean;
chatTitle?: string;
/** The message this one replies to, when it is one. */
replyTo?: string;
/**
* Facts about where it was said, handed to the model ahead of the message
* with who sent it: "chat_type", "chat_title". Without them the model is
* handed the message alone, which is right for a caller that is a program.
*/
context?: Record<string, string>;
/** The files on it, fetched only when a turn is going to read them. */
files?: () => Promise<{ attachments?: Attachment[]; text?: string; notes?: string[] }>;
/** A model for this one turn, when the channel lets its caller pick. */
model?: string;
/** Tools the turn is not given, by name, though the agent has them. */
withoutTools?: string[];
/** Stops the turn: whoever asked has gone. */
signal?: AbortSignal;
}One message, in the words every channel shares.
inPiecesfunction
export function inPieces(text: string, max: number): string[]A long reply cut into pieces a channel will take, at a line break where there is one, so a tag or a line is never cut in half.
openEmailfunction
export async function openEmail(agent: string, to: string, subject: string, text: string, channel = "email"): Promise<Started>Emails one of the people an agent's email channel allows, from a new address made for that conversation, and keeps the words in it so a reply is read with them. Refused for anybody not in allowFrom, and when the channel is not running.
receivefunction
export async function receive(agent: Agent, message: Incoming, rules: Rules = {}, whileWorking: While = {}): Promise<Handled | undefined>Decides what a message is, does it, and says what to send back. See While for what a channel can hand over to be used on the way.
Rulesinterface
export interface Rules {
/** Ids that may reach the agent. Unset, anybody who got this far may, which is right only behind a login. */
allowFrom?: (string | number)[];
/** In a group, "when-addressed" (the default) answers a command, a mention or a reply. "always" answers everything. */
inGroups?: "when-addressed" | "always";
/** How much of a conversation on this channel a turn is shown. */
chatHistory?: ChatHistory;
/**
* Send what the model writes on its way to an answer (a "let me check" line,
* or a draft it goes on to improve) as it writes it, rather than only the
* answer it ends on. Off unless true. Needs a channel that can send more
* than one reply, so it does nothing on the API.
*/
sendWhileWorking?: boolean;
/**
* Whoever writes is a stranger, like a visitor on a web page. Their message
* is a turn, /clear or the answer to a job that asked them, and nothing
* else: never a /command, a model pick or the answer to a sign-in, and a
* sign-in a tool needs is never started for them.
*/
strangers?: boolean;
}Who a channel answers. Each channel takes these as options and hands them over.
slackChannelfunction
export function slackChannel(options: SlackOptions = {}): ChannelAn agent on Slack, as a channel its own agent.ts names.
SlackOptionsinterface
export interface SlackOptions {
/**
* "slack" unless the agent is in two workspaces. It is what the log shows a
* run came in on, and the start of every address on this app, like "slack:U0123ABCD".
*/
name?: string;
/** Instead of the tokens in settings. */
credentials?: { botToken?: string; appToken?: string };
/** Slack member ids that may reach the agent. */
allowFrom?: string[];
/**
* In a channel, "when-addressed" (the default) answers only a slash
* command, a mention, or a reply in a thread the bot started. "always"
* answers every message from someone in allowFrom.
*/
inGroups?: "when-addressed" | "always";
/** How much of a conversation a turn is shown: `{ messages, days }`. */
chatHistory?: ChatHistory;
/** Send what the model writes on its way to an answer as it writes it, not only the answer. Off unless true. */
sendWhileWorking?: boolean;
/** Which files are taken, and how big. Anything else is named to the agent but not handed over. */
uploadPolicy?: { allowedMediaTypes?: string[]; maxBytes?: number };
/** Where Slack is. Only the tests change it. */
api?: string;
}How an agent is put on Slack: who may reach it, and how it behaves in a channel.
Startedinterface
export interface Started {
/** The address the person replies to. */
address: string;
/** The conversation it is, as every channel names one. */
thread: string;
}What starting a conversation gives back.
telegramChannelfunction
export function telegramChannel(options: TelegramOptions = {}): ChannelAn agent on Telegram, as a channel its own agent.ts names.
TelegramOptionsinterface
export interface TelegramOptions {
/**
* "telegram" unless the agent has two bots. It is what the log shows a run
* came in on, and the start of every address on this bot, like "telegram:123".
*/
name?: string;
/** For spotting a mention in a group. Asked of Telegram when left out. */
botUsername?: string;
/** Instead of the token in settings. Without a secret, webhook mode makes a new one each start. */
credentials?: { botToken?: string; webhookSecretToken?: string };
/** Telegram user ids that may reach the agent. */
allowFrom?: number[];
/**
* In a group, "when-addressed" (the default) answers only a command, a
* mention or a reply to the bot. "always" answers every message from
* someone in allowFrom.
*/
inGroups?: "when-addressed" | "always";
/** How much of a chat's conversation a turn is shown: `{ messages, days }`. */
chatHistory?: ChatHistory;
/** Send what the model writes on its way to an answer as it writes it, not only the answer. Off unless true. */
sendWhileWorking?: boolean;
/**
* Seconds to wait before handling a text message, so that anything else sent
* in the same chat inside that time is handled as one message, joined by a
* blank line in the order it arrived. A share that arrives as two messages (a
* quote and a comment) is what this is for, and so is a person who writes a
* sentence, sends it, and then adds the rest.
*
* One second by default, which is long enough to catch a second message
* somebody was already typing and short enough that nobody waits on it. Zero
* handles each message on its own. A message carrying a file is never held.
*/
stackWithin?: number;
mode?: "polling" | "webhook";
/** Where this server is reachable from outside, for mode "webhook", like "https://agents.example.com". */
publicUrl?: string;
/** Which files are taken, and how big. Anything else is named to the agent but not handed over. */
uploadPolicy?: { allowedMediaTypes?: string[]; maxBytes?: number };
/** Where Telegram is. Only the tests change it. */
api?: string;
}How an agent is put on Telegram: who may reach it, and whether messages are fetched or posted.
webChannelfunction
export function webChannel(options: WebOptions): Channel & { web: Web }The web channel. Throws as it is made when origins is empty or holds
something that is not a site, and as the agent loads when tools names one
of its memory or self tools. A tool the agent does not have is said in the
log and left out, the same as a connection that did not answer.
WebLimitsinterface
export interface WebLimits {
/** For one visitor: 30 messages and $0.50 when unsaid. */
perVisitor?: { messages?: number; dollars?: number };
/** For every visitor together: $5 when unsaid. */
perDay?: { dollars?: number };
}What visitors may spend, each over the last 24 hours. A turn that would start past one is refused, politely.
WebOptionsinterface
export interface WebOptions {
/** The sites that may show the chat box, each a scheme and a host: "https://myshop.com". A page anywhere else is refused. */
origins: string[];
/**
* The only tools a web turn has, by the names the agent gives them. None when
* unsaid. Its memory and self tools are refused, apart from
* `memoryWriteUserNotes`, which a visitor's turn has without it being named
* when the agent keeps `memoryPerUser`.
*/
tools?: string[];
/** What the box shows before anybody has written. */
greeting?: string;
limits?: WebLimits;
/** How much of a visitor's conversation a turn is shown. */
chatHistory?: ChatHistory;
/** The model web turns use, when it is not the agent's own. */
model?: string | SdkModel;
/** Whether a visitor may send pictures. Off unless true. */
pictures?: boolean;
}What a web channel is made with: the sites that may show it, the tools a visitor's turn gets and what visitors may spend.
whatsappChannelfunction
export function whatsappChannel(options: WhatsAppOptions = {}): ChannelAn agent on WhatsApp's own API, as a channel its own agent.ts names.
WhatsAppOptionsinterface
export interface WhatsAppOptions {
/**
* "whatsapp" unless the agent is on two numbers. It is what the log shows
* a run came in on, the end of the address Meta sends to, and the start of
* every address on this number, like "whatsapp:+447700900123".
*/
name?: string;
/** Instead of the three in settings. `verifyToken` is the word Meta is told to check the address with. */
credentials?: { phoneNumberId?: string; token?: string; appSecret?: string; verifyToken?: string };
/** Numbers that may reach the agent, in full international form. */
allowFrom?: string[];
/**
* The post box to collect messages from: a service that takes Meta's
* delivery, because Meta pushes and never lets anything fetch, and holds it
* sealed until this runtime asks. `dashboard.remote.url` in settings unless this says
* otherwise, and "" to collect from nowhere, which leaves only the route
* below for somebody who has opened an address of their own.
*/
postBox?: string;
/** Where this server is reachable from outside, like "https://agents.example.com". Only used to say the address to register. */
publicUrl?: string;
/** How much of a conversation a turn is shown: `{ messages, days }`. */
chatHistory?: ChatHistory;
/** Send what the model writes on its way to an answer as it writes it, not only the answer. Off unless true. */
sendWhileWorking?: boolean;
/** Which files are taken, and how big. Anything else is named to the agent but not handed over. */
uploadPolicy?: { allowedMediaTypes?: string[]; maxBytes?: number };
/** Which version of the API to call. */
version?: string;
/** Where the API is. Only the tests change it. */
api?: string;
}How an agent is put on WhatsApp's own API: who may reach it, and where its messages arrive.
Whileinterface
export interface While {
/** Called once there is work to do, for "typing..."; what it returns is called when the work is over. */
working?: () => () => void;
/** Sends one message to the chat. What sendWhileWorking uses. */
send?: (text: string) => Promise<void>;
/** Called as each tool starts, with its name and its title when it has one. */
calling?: (tool: { name: string; title?: string }) => void;
/**
* Handed the answer's words as they are written, on a model route that
* streams them. Words written before a tool call come this way too, and are
* then handed to `send` whole.
*/
writing?: (delta: string) => void;
}What a channel can do while a message is being dealt with.