channels

package
v0.1.509 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 25, 2026 License: AGPL-3.0 Imports: 17 Imported by: 0

Documentation

Overview

Package channels is the channel-adapter registry plus adapters. It ships a FakeAdapter (for tests) and a reference WebhookAdapter (HTTP POST delivery). Platform-specific adapters (Slack, Discord, ...) follow the WebhookAdapter shape and register the same way.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Adapter

type Adapter interface {
	Name() string
	Deliver(ctx context.Context, msg contract.MessageOut) (string, error)
}

Adapter delivers an outbound message to a concrete platform and returns the platform-assigned message ID.

type DiscordAdapter

type DiscordAdapter struct {
	AdapterName string
	Token       string
	// BaseURL defaults to defaultDiscordBaseURL; overridable for tests.
	BaseURL string
	Client  *http.Client
}

DiscordAdapter is a concrete, stdlib-only Adapter that delivers an outbound message via the Discord REST API create-message endpoint. It follows the TelegramAdapter shape: it sits behind the Adapter interface and adds no dependency. The channel is taken from MessageOut.PlatformID; a numeric ThreadID is treated as the id of a message to reply to (message_reference), so replies thread; a non-numeric ThreadID is ignored. The returned platform message id is the Discord message snowflake id.

SECURITY: the bot token is sent in the Authorization header ("Bot <token>"). The adapter NEVER includes the token in returned errors — error strings are redacted first — so a token cannot leak into logs.

func NewDiscordAdapter

func NewDiscordAdapter(name, token string) *DiscordAdapter

NewDiscordAdapter constructs a DiscordAdapter. name defaults to "discord"; the client gets a default 15s timeout.

func (*DiscordAdapter) Deliver

func (a *DiscordAdapter) Deliver(ctx context.Context, msg contract.MessageOut) (string, error)

Deliver sends msg.Content to the channel in msg.PlatformID via create-message and returns the Discord message snowflake id as the platform message id.

func (*DiscordAdapter) Name

func (a *DiscordAdapter) Name() string

Name returns the adapter name.

type EmailAdapter

type EmailAdapter struct {
	AdapterName string
	// Host and Port address the SMTP submission server (e.g. smtp.gmail.com:587).
	Host string
	Port int
	// Username / Password authenticate to the server (PLAIN over STARTTLS).
	Username string
	Password string
	// From is the envelope + header sender address.
	From string
	// contains filtered or unexported fields
}

EmailAdapter is a concrete, stdlib-only Adapter that delivers an outbound message as email over SMTP (net/smtp). It works with any SMTP submission server, including Gmail (smtp.gmail.com:587 with an app password) — satisfying the "Email / Gmail" channel via the universal protocol rather than a provider-specific API.

The recipient is MessageOut.PlatformID (an email address). The subject is derived from the first line of the content; a non-empty MessageOut.ThreadID is echoed into the In-Reply-To / References headers so replies thread in the recipient's client. Deliver returns the generated RFC 5322 Message-ID as the platform message id (SMTP itself returns none).

Inbound ingestion (IMAP / Gmail Pub-Sub) is intentionally out of scope here — the task lists it as optional — so this adapter is send-only, matching the Adapter interface.

SECURITY: the SMTP password is never placed in the message and is redacted from every returned error, so it cannot leak into logs.

func NewEmailAdapter

func NewEmailAdapter(name, host string, port int, username, password, from string) *EmailAdapter

NewEmailAdapter constructs an EmailAdapter. name defaults to "email"; port defaults to 587 (the submission port) when non-positive.

func (*EmailAdapter) Deliver

func (a *EmailAdapter) Deliver(ctx context.Context, msg contract.MessageOut) (string, error)

Deliver sends msg.Content as an email to the address in msg.PlatformID and returns the generated Message-ID as the platform message id.

func (*EmailAdapter) Name

func (a *EmailAdapter) Name() string

Name returns the adapter name.

type FakeAdapter

type FakeAdapter struct {
	AdapterName string
	// contains filtered or unexported fields
}

FakeAdapter records the messages it is asked to deliver. It is exported for use in tests across the host tree.

func NewFakeAdapter

func NewFakeAdapter(name string) *FakeAdapter

NewFakeAdapter constructs a FakeAdapter with the given name (defaults to "fake").

func (*FakeAdapter) Deliver

func (f *FakeAdapter) Deliver(ctx context.Context, msg contract.MessageOut) (string, error)

Deliver records msg and returns a synthetic platform message ID.

func (*FakeAdapter) Delivered

func (f *FakeAdapter) Delivered() []contract.MessageOut

Delivered returns a copy of the messages delivered so far.

func (*FakeAdapter) Name

func (f *FakeAdapter) Name() string

Name returns the adapter name.

type GoogleChatAdapter

type GoogleChatAdapter struct {
	AdapterName string
	// Token is the Google Chat bot/service-account OAuth2 access token (Bearer).
	Token string
	// BaseURL defaults to defaultGoogleChatBaseURL; overridable for tests.
	BaseURL string
	Client  *http.Client
}

GoogleChatAdapter is a concrete, stdlib-only Adapter that delivers an outbound message to a Google Chat space via the REST API `POST {base}/v1/{space}/messages`. It follows the SlackAdapter shape: it sits behind the Adapter interface and adds no dependency.

The space is taken from MessageOut.PlatformID (a space id such as "AAAA1234" or the full resource "spaces/AAAA1234"; a bare id is normalized to the "spaces/" form). A non-empty MessageOut.ThreadID maps to the message `thread.threadKey` so messages with the same key group into one thread. The returned platform message id is the created message resource `name`.

SECURITY: the bot/service-account access token is sent in the Authorization header (never the URL), and the adapter NEVER includes the token in returned errors — error strings are redacted first — so a token cannot leak into logs even if a transport error or upstream payload were ever to echo it.

func NewGoogleChatAdapter

func NewGoogleChatAdapter(name, token string) *GoogleChatAdapter

NewGoogleChatAdapter constructs a GoogleChatAdapter. name defaults to "googlechat"; the client gets a default 15s timeout.

func (*GoogleChatAdapter) Deliver

Deliver sends msg.Content to the space in msg.PlatformID and returns the created message resource name as the platform message id.

func (*GoogleChatAdapter) Name

func (a *GoogleChatAdapter) Name() string

Name returns the adapter name.

type IMessageAdapter

type IMessageAdapter struct {
	AdapterName string
	// contains filtered or unexported fields
}

IMessageAdapter delivers an outbound message via Apple Messages on a macOS host, by driving Messages.app through AppleScript (`osascript`). It is the only adapter that bridges a LOCAL app rather than an HTTP API, so it runs only when the control-plane host is macOS with Messages signed in. The recipient (a phone number or Apple ID) is taken from MessageOut.PlatformID.

There is no network token; the trust surface is the local Messages account.

func NewIMessageAdapter

func NewIMessageAdapter(name string) *IMessageAdapter

NewIMessageAdapter constructs an IMessageAdapter. name defaults to "imessage".

func (*IMessageAdapter) Deliver

func (a *IMessageAdapter) Deliver(ctx context.Context, msg contract.MessageOut) (string, error)

Deliver sends msg.Content to the recipient in msg.PlatformID via Messages.app. It fails fast off macOS — the bridge cannot work without Messages.app.

func (*IMessageAdapter) Name

func (a *IMessageAdapter) Name() string

Name returns the adapter name.

type MatrixAdapter

type MatrixAdapter struct {
	AdapterName string
	// HomeserverURL is the base URL of the Matrix homeserver (e.g.
	// "https://matrix.example.org"). Required. Overridable for tests.
	HomeserverURL string
	// Token is the Matrix access token (Bearer).
	Token  string
	Client *http.Client
}

MatrixAdapter is a concrete, stdlib-only Adapter that delivers an outbound message via the Matrix client-server API `PUT /_matrix/client/v3/rooms/{roomId} /send/m.room.message/{txnId}` endpoint. It follows the SlackAdapter shape: it sits behind the Adapter interface and adds no dependency.

The room is taken from MessageOut.PlatformID (e.g. "!abc:example.org"). A non-empty MessageOut.ThreadID maps to an `m.relates_to` thread relation so the message lands in that thread. Each send uses a fresh transaction id, making the PUT idempotent. The returned platform message id is the Matrix `event_id`.

Unlike the fixed-host platforms, Matrix is self-hosted, so HomeserverURL is required configuration (there is no default homeserver).

SECURITY: the access token is sent in the Authorization header (never the URL), and the adapter NEVER includes the token in returned errors — error strings are redacted first — so a token cannot leak into logs even if a transport error or upstream payload were ever to echo it.

func NewMatrixAdapter

func NewMatrixAdapter(name, homeserverURL, token string) *MatrixAdapter

NewMatrixAdapter constructs a MatrixAdapter. name defaults to "matrix"; the client gets a default 15s timeout.

func (*MatrixAdapter) Deliver

func (a *MatrixAdapter) Deliver(ctx context.Context, msg contract.MessageOut) (string, error)

Deliver sends msg.Content as an m.text message to the room in msg.PlatformID and returns the Matrix event_id as the platform message id.

func (*MatrixAdapter) Name

func (a *MatrixAdapter) Name() string

Name returns the adapter name.

type MattermostAdapter added in v0.1.126

type MattermostAdapter struct {
	AdapterName string
	WebhookURL  string
	Client      *http.Client
}

MattermostAdapter delivers an outbound message to Mattermost via an Incoming Webhook URL. It is stdlib-only and follows the TeamsAdapter shape.

SECURITY: the webhook URL embeds a secret token, so it is sent only in the request line (never logged) and is ALWAYS redacted from returned errors.

func NewMattermostAdapter added in v0.1.126

func NewMattermostAdapter(name, webhookURL string) *MattermostAdapter

func (*MattermostAdapter) Deliver added in v0.1.126

func (*MattermostAdapter) Name added in v0.1.126

func (a *MattermostAdapter) Name() string

type Registry

type Registry struct {
	// contains filtered or unexported fields
}

Registry holds the available channel adapters keyed by Name. It is mutex-guarded and safe for concurrent use.

func NewRegistry

func NewRegistry() *Registry

NewRegistry constructs an empty Registry.

func (*Registry) Get

func (r *Registry) Get(name string) (Adapter, bool)

Get returns the adapter registered under name. The bool is false if none.

func (*Registry) List

func (r *Registry) List() []string

List returns the names of all registered adapters.

func (*Registry) Register

func (r *Registry) Register(a Adapter) error

Register adds an adapter. It errors on a nil adapter, an empty name, or a duplicate name.

type SignalAdapter

type SignalAdapter struct {
	AdapterName string
	// BaseURL is the signal-cli-rest-api base (e.g. http://127.0.0.1:8080).
	BaseURL string
	// Number is the registered sender number (E.164, e.g. +15551234567).
	Number string
	Client *http.Client
}

SignalAdapter delivers an outbound message to Signal via a signal-cli REST bridge (e.g. bbernhard/signal-cli-rest-api) running host-side. Signal has no official bot API, so the host operates a registered signal-cli number and the adapter POSTs to its /v2/send endpoint. It is stdlib-only and follows the SlackAdapter shape. The recipient is taken from MessageOut.PlatformID (a phone number or group id); the sender is the adapter's configured Number.

func NewSignalAdapter

func NewSignalAdapter(name, baseURL, number string) *SignalAdapter

NewSignalAdapter constructs a SignalAdapter. name defaults to "signal"; the client gets a default 15s timeout.

func (*SignalAdapter) Deliver

func (a *SignalAdapter) Deliver(ctx context.Context, msg contract.MessageOut) (string, error)

Deliver sends msg.Content to the recipient in msg.PlatformID via the bridge's /v2/send and returns the message timestamp as the platform message id.

func (*SignalAdapter) Name

func (a *SignalAdapter) Name() string

Name returns the adapter name.

type SlackAdapter

type SlackAdapter struct {
	AdapterName string
	Token       string
	// BaseURL defaults to defaultSlackBaseURL; overridable for tests.
	BaseURL string
	Client  *http.Client
}

SlackAdapter is a concrete, stdlib-only Adapter that delivers an outbound message via the Slack Web API `chat.postMessage` method. It follows the TelegramAdapter shape: it sits behind the Adapter interface and adds no dependency. The channel is taken from MessageOut.PlatformID; a ThreadID maps to Slack's thread_ts so replies thread correctly. The returned platform message id is the Slack message `ts`.

SECURITY: the bot token is sent in the Authorization header (not the URL). The adapter NEVER includes the token in returned errors — error strings are redacted first — so a token cannot leak into logs even if a transport error were ever to echo a header.

func NewSlackAdapter

func NewSlackAdapter(name, token string) *SlackAdapter

NewSlackAdapter constructs a SlackAdapter. name defaults to "slack"; the client gets a default 15s timeout.

func (*SlackAdapter) Deliver

func (a *SlackAdapter) Deliver(ctx context.Context, msg contract.MessageOut) (string, error)

Deliver sends msg.Content to the channel in msg.PlatformID via chat.postMessage and returns the Slack message ts as the platform message id.

func (*SlackAdapter) Name

func (a *SlackAdapter) Name() string

Name returns the adapter name.

type TeamsAdapter

type TeamsAdapter struct {
	AdapterName string
	WebhookURL  string
	Client      *http.Client
}

TeamsAdapter delivers an outbound message to Microsoft Teams via an Incoming Webhook URL. It is stdlib-only and follows the SlackAdapter shape. The webhook URL is per-channel and configured host-side, so a message's PlatformID is advisory only — the destination is the webhook itself. Incoming Webhooks are outbound-only (one-way), which is exactly what a delivery adapter needs.

SECURITY: the webhook URL embeds a secret token, so it is sent only in the request line (never logged) and is ALWAYS redacted from returned errors.

func NewTeamsAdapter

func NewTeamsAdapter(name, webhookURL string) *TeamsAdapter

NewTeamsAdapter constructs a TeamsAdapter. name defaults to "teams"; the client gets a default 15s timeout.

func (*TeamsAdapter) Deliver

func (a *TeamsAdapter) Deliver(ctx context.Context, msg contract.MessageOut) (string, error)

Deliver posts msg.Content to the configured Teams Incoming Webhook. Incoming Webhooks return HTTP 200 with the body "1" on success and do not assign a retrievable message id, so the trimmed response body is returned as the id.

func (*TeamsAdapter) Name

func (a *TeamsAdapter) Name() string

Name returns the adapter name.

type TelegramAdapter

type TelegramAdapter struct {
	AdapterName string
	Token       string
	// BaseURL defaults to defaultTelegramBaseURL; overridable for tests.
	BaseURL string
	Client  *http.Client
}

TelegramAdapter is a concrete, stdlib-only Adapter that delivers an outbound message via the Telegram Bot API `sendMessage` method. It follows the WebhookAdapter shape: it sits behind the Adapter interface and adds no dependency. The chat is taken from MessageOut.PlatformID; a numeric ThreadID maps to a forum-topic message_thread_id. The returned platform message id is the Telegram message_id.

SECURITY: the bot token is part of the request URL (Telegram's design). The adapter NEVER includes the token in returned errors — transport errors (whose url.Error carries the URL) are redacted first — so a token cannot leak into logs.

func NewTelegramAdapter

func NewTelegramAdapter(name, token string) *TelegramAdapter

NewTelegramAdapter constructs a TelegramAdapter. name defaults to "telegram"; the client gets a default 15s timeout.

func (*TelegramAdapter) Deliver

func (a *TelegramAdapter) Deliver(ctx context.Context, msg contract.MessageOut) (string, error)

Deliver sends msg.Content to the chat in msg.PlatformID via sendMessage and returns the Telegram message_id as the platform message id.

func (*TelegramAdapter) Name

func (a *TelegramAdapter) Name() string

Name returns the adapter name.

type WebchatAdapter

type WebchatAdapter struct {
	AdapterName string
	// contains filtered or unexported fields
}

WebchatAdapter is an in-process channel adapter for the web console's chat playground. Unlike the platform adapters, it does not call out to an external service: an agent's outbound message destined for "webchat" is buffered in memory, keyed by conversation (MessageOut.PlatformID), and the browser polls for it via the API. It is the delivery half of feeding the normal router/delivery path; the inbound half is the API handler calling the router.

func NewWebchatAdapter

func NewWebchatAdapter(name string) *WebchatAdapter

NewWebchatAdapter constructs a WebchatAdapter. name defaults to "webchat".

func (*WebchatAdapter) Deliver

Deliver buffers the agent's outbound message for the browser to poll. The conversation is MessageOut.PlatformID; a message without one is dropped (there is no browser to route it to). The returned platform id is the local message id.

func (*WebchatAdapter) Drain

func (a *WebchatAdapter) Drain(conversationID string) []WebchatMessage

Drain returns and clears the buffered replies for a conversation, so the browser polls incrementally. It never returns nil (an empty slice for none).

func (*WebchatAdapter) Name

func (a *WebchatAdapter) Name() string

Name returns the adapter name.

type WebchatMessage

type WebchatMessage struct {
	ID             string `json:"id"`
	ConversationID string `json:"conversationId"`
	ThreadID       string `json:"threadId,omitempty"`
	Content        string `json:"content"`
	Timestamp      string `json:"timestamp"`
}

WebchatMessage is one buffered agent reply, as the browser renders it.

type WebhookAdapter

type WebhookAdapter struct {
	AdapterName string
	URL         string
	Client      *http.Client
}

WebhookAdapter is a concrete, stdlib-only Adapter that delivers an outbound message by POSTing it as JSON to a configured URL. It is the reference HTTP-based channel: platform-specific adapters (Slack, Discord, ...) can follow the same shape. The platform message ID is taken from the response (a JSON {"id": "..."} body, or the X-Message-Id header), falling back to a synthetic id.

func NewWebhookAdapter

func NewWebhookAdapter(name, url string) *WebhookAdapter

NewWebhookAdapter constructs a WebhookAdapter. name defaults to "webhook"; a nil client gets a default with a 15s timeout.

func (*WebhookAdapter) Deliver

func (a *WebhookAdapter) Deliver(ctx context.Context, msg contract.MessageOut) (string, error)

Deliver POSTs msg as JSON to the configured URL.

func (*WebhookAdapter) Name

func (a *WebhookAdapter) Name() string

Name returns the adapter name.

type WhatsAppAdapter

type WhatsAppAdapter struct {
	AdapterName string
	// Token is the WhatsApp Cloud API access token (Bearer).
	Token string
	// PhoneNumberID is the sender's WhatsApp phone-number id; it forms the path
	// segment `{PhoneNumberID}/messages`.
	PhoneNumberID string
	// BaseURL defaults to defaultWhatsAppBaseURL; overridable for tests.
	BaseURL string
	Client  *http.Client
}

WhatsAppAdapter is a concrete, stdlib-only Adapter that delivers an outbound message via the WhatsApp Cloud API (Meta Graph API) `POST {phoneNumberID}/ messages` endpoint. It follows the SlackAdapter/TelegramAdapter shape: it sits behind the Adapter interface and adds no dependency.

The recipient is taken from MessageOut.PlatformID (the wa_id / phone number in E.164 form). A non-empty MessageOut.ThreadID maps to the WhatsApp reply `context.message_id` so replies quote the prior message. The returned platform message id is the WhatsApp `wamid` from `messages[0].id`.

Beyond text, Deliver's sibling SendDocument satisfies the "send a file" capability by posting a `document` message (a hosted link). The interface only requires text Deliver, so file sending is an extra method on the concrete type rather than a contract change.

SECURITY: the access token is sent in the Authorization header (never the URL), and the adapter NEVER includes the token in returned errors — error strings are redacted first — so a token cannot leak into logs even if a transport error or upstream payload were ever to echo it.

func NewWhatsAppAdapter

func NewWhatsAppAdapter(name, token, phoneNumberID string) *WhatsAppAdapter

NewWhatsAppAdapter constructs a WhatsAppAdapter. name defaults to "whatsapp"; the client gets a default 15s timeout.

func (*WhatsAppAdapter) Deliver

func (a *WhatsAppAdapter) Deliver(ctx context.Context, msg contract.MessageOut) (string, error)

Deliver sends msg.Content as a text message to the recipient in msg.PlatformID and returns the WhatsApp message id (wamid) as the platform message id.

func (*WhatsAppAdapter) Name

func (a *WhatsAppAdapter) Name() string

Name returns the adapter name.

func (*WhatsAppAdapter) SendDocument

func (a *WhatsAppAdapter) SendDocument(ctx context.Context, to, link, filename, caption string) (string, error)

SendDocument delivers a hosted file (by link) as a WhatsApp `document` message to `to`, with an optional filename and caption, and returns the wamid. This is the adapter's "send a file" capability; it is not part of the Adapter interface (the queue carries text), so callers use the concrete type.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL