ReadonlyidStable transport tag (e.g. 'baileys', 'telegram') — used to namespace conversation keys if one Bot ever runs multiple providers concurrently.
ReadonlycapabilitiesOptional ReadonlyselfThe bot's own user id once connected (for group mention matching). Optional.
Optional ReadonlyconnectionA single primary connection handle for DEBUGGING ONLY. Not a structural guarantee of exactly one connection (Discord may hold many shard sockets). Core must NOT reach through this for behavior.
Send normalized content. Rejects with a typed error (section 8) on failure.
Optionaloptions: SendOptionsSubscribe to the normalized event stream. Returns an unsubscriber.
OptionalingestPush ingress for webhook transports (Cloud API POST, Telegram/Discord webhook, Discord interaction endpoint). The host HTTP server calls this with the raw body (and, optionally, the request headers for signature checks); the provider normalizes and emits via onEvent. May return a value the host can use as the HTTP status (e.g. 200 accepted / 403 bad signature). Socket-first providers (Baileys, Discord gateway) omit it.
Optionalheaders: Record<string, string | string[] | undefined>OptionaldownloadLazy media retrieval behind one signature for every download model.
OptionalserializeDurable journaling (BotConfig.durability): turn a live MediaRef into a JSON-safe,
versioned reference that rehydrateMediaRef can rebuild in another process, so a
replayed message (crash recovery, background retry) can still download its media.
Synchronous, cheap and side-effect free; a throw counts as "not durable". Return
undefined for a ref that must not be journaled (e.g. view-once media). Never include
credentials: the result is stored at rest with the inbound record.
OptionalrehydrateRebuild the MediaRef of a replayed message from what serializeMediaRef wrote.
ref is the journaled ref without its token. Pure decoding: it must not need the
transport to be connected. stored comes from storage and is UNTRUSTED: reject an
invalid, unknown-version or tampered value with a ProviderError.
OptionalgetRead-back seam for poll aggregation, quote-body resolution, retry decrypt.
Providers that don't need it no-op / omit it. Replaces the constructor-
injected getMessage callback baked into BaileysProvider today.
OptionalmarkOptionaltypingOptionalstate: "typing" | "paused" | "recording"OptionaldeleteOptionaleditOptionalacknowledgeOptionalsendShow (or update) an ephemeral, not-yet-sent preview of a message being
generated. Same draftId → the same draft is updated in place. A draft is
NOT a message: the caller must still send() the final text. Only for
providers with capabilities.streaming === 'draft'; rejects (typed error)
where the transport refuses drafts (e.g. Telegram groups), and the caller
then falls back to edits or chunks.
Optionalopts: DraftOptionsOptionalcanWhether a form OutboundContent for form (and opts.nativeId) can be
rendered natively right now, e.g. whether a WhatsApp Flow is mapped to it.
Only consulted when capabilities.forms === 'native'; when it returns
false, an auto-mode flow().form() asks field by field without sending
(and failing) a form message first. Absent = every form is renderable.
Optionalopts: { nativeId?: string }OptionaltextRich-text dialect of one chat, for providers that serve several channels
with different markup through one instance (e.g. Twilio: whatsapp: chats
render WhatsApp markup, SMS chats render none). Return undefined when the
chat's format is unknown; the core then falls back to
capabilities.textFormat. Exposed as ctx.textFormat. Must be synchronous
and cheap (it runs once per inbound message). Absent = every chat uses
capabilities.textFormat.
Transport-agnostic provider contract.
TRawis the provider's native raw payload (proto.IWebMessageInfo, webhook entry, gateway object); it staysunknownin all shared code and is narrowed only inside provider-specific paths.STABILITY CONTRACT (normative): for the lifetime of a session, the same entity MUST yield the same
senderIdand the same chat the samechatIdon every emitted event. All transport-internal aliasing is resolved inside the provider before emission. The core performs no identity reconciliation.INBOUND ID CONTRACT (normative): a message's
ref.idand an interaction'sinteractionIdidentify ONE logical inbound event within its chat: distinct events get distinct ids, and a redelivery of the same event (webhook retry, reconnect replay) carries the same id. Core relies on this for inbound idempotency (BotConfig.dedupe, keyed bychatId+ id). When the transport provides no such id, synthesize one from per-event data that a redelivery repeats (e.g. kind + event timestamp) or emit'', which opts the event out of deduplication. Never fall back to a per-user constant (sender id,user_ref) or a shared placeholder ('<prefix>_0').