channel

package
v0.60.0 Latest Latest
Warning

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

Go to latest
Published: Jul 19, 2026 License: AGPL-3.0 Imports: 11 Imported by: 0

Documentation

Overview

Package channel defines the public contract for channel plugins. Channel plugins are thin platform adapters (Telegram, QQ, Feishu, Weixin) that normalise incoming messages, delegate business logic to a MessageHandler, and render streamed responses back to the platform.

Index

Constants

View Source
const (
	PlatformTelegram = "telegram"
	PlatformQQ       = "qq"
	PlatformFeishu   = "feishu"
	PlatformWeixin   = "weixin"
	PlatformCLI      = "cli"
	PlatformWebhook  = "webhook"
)

Platform identifiers for each messaging channel.

View Source
const (
	WebhookSessionEphemeral  = "ephemeral"
	WebhookSessionPersistent = "persistent"

	WebhookDefaultWaitTimeoutSeconds   = 60
	WebhookDefaultMaxRunTimeoutSeconds = 300

	// Hard ceilings on the admin-configured timeouts: the wait timeout holds
	// an HTTP connection open, the run timeout holds an in-flight run slot.
	// Raise if a legitimate longer-running webhook workload appears.
	WebhookWaitTimeoutCeilingSeconds = 600
	WebhookRunTimeoutCeilingSeconds  = 3600
)

Webhook session modes.

View Source
const GroupCompactUnsupportedMessage = "⚠️ Group memory is managed automatically from the shared event log, so manual compaction is not available in group chats."

GroupCompactUnsupportedMessage is the shared reply when a user asks to compact a group chat. Group memory is assembled from the shared event log rather than a per-agent LCM conversation, so manual compaction does not apply. Both the web group endpoint and the shared channel command handler use this text.

View Source
const WelcomeMessage = `` /* 1030-byte string literal not displayed */

WelcomeMessage is the shared welcome/help text for all channels. It explains both slash commands and natural-language shortcuts for channels without slash-command menus (like SMS or simple webhooks).

Variables

View Source
var ToolEmoji = map[string]string{
	"bash":    "⚡",
	"read":    "📖",
	"write":   "✏️",
	"edit":    "🔧",
	"search":  "🔍",
	"default": "🔧",
}

ToolEmoji maps known tool names to display emoji.

Functions

func CloneConfigMap

func CloneConfigMap(src map[string]any) map[string]any

func DecodePluginConfig

func DecodePluginConfig[T any](raw map[string]any, label string) (T, error)

func EmojiFor

func EmojiFor(tool string) string

EmojiFor returns the emoji for a tool name, falling back to the default.

func FileReceivedContent

func FileReceivedContent(fileName, assetsDir, savedPath string) []ai.ContentBlock

FileReceivedContent returns the standard content block telling the agent about a file that has been saved to disk, with a kreuzberg extraction hint. assetsDir is the host-side assets directory; savedPath is the host-side absolute path returned by SaveAsset. The hint uses a path relative to the user root (parent of assetsDir) so it resolves correctly inside the bwrap sandbox at /workspace.

func FormatAgentList

func FormatAgentList(agents []IndexedAgent, currentAgentID string) string

FormatAgentList builds a text-based agent list, marking the current agent.

func FormatDuration

func FormatDuration(d time.Duration) string

FormatDuration formats a duration as a human-friendly string.

func FormatModelList

func FormatModelList(models []IndexedModel, query string) string

FormatModelList builds a text-based model list with bullet points.

func FormatNumberedModelList

func FormatNumberedModelList(models []IndexedModel, query string) string

FormatNumberedModelList builds a text-based model list with numbered entries.

func HandleAgentCommand

func HandleAgentCommand(cfg AgentCommandHandler)

HandleAgentCommand runs the shared /agent flow for text-based channels.

func HandleModelCommand

func HandleModelCommand(cfg ModelCommandHandler)

HandleModelCommand runs the shared /model flow for text-based channels.

func NotificationAgentIDFromContext

func NotificationAgentIDFromContext(ctx context.Context) string

NotificationAgentIDFromContext returns the agent ID associated with a notification-producing tool call, if present.

func ParseCommandArgs

func ParseCommandArgs(text, cmd string) string

ParseCommandArgs extracts arguments after the command token. Example: ParseCommandArgs("/agent foo", "/agent") returns "foo".

func ParseModelArgs

func ParseModelArgs(args string) string

ParseModelArgs parses /model arguments as a query string. Returns empty string when no arguments are provided.

func ParseSlashCommand

func ParseSlashCommand(text string) (string, string)

ParseSlashCommand extracts a slash command and its arguments. The returned command is always lowercased.

func RenderToolRecord

func RenderToolRecord(rec ToolRecord) string

RenderToolRecord formats a single completed tool record.

func SplitMessage

func SplitMessage(text string, maxLen int) []string

SplitMessage splits text into chunks that fit within maxLen. It tries to split at newline boundaries and avoids cutting multi-byte UTF-8 characters.

func TextContent

func TextContent(text string) []ai.ContentBlock

TextContent wraps a plain string as a single-element []ai.ContentBlock.

func ToolLine

func ToolLine(t *ToolUseEvent) string

ToolLine returns a short status line for a tool-use event. Used by channels with simple tool display (no full tracker).

func Truncate

func Truncate(s string, maxLen int) string

Truncate shortens s to maxLen bytes, appending "..." if truncated. Cuts at a valid UTF-8 boundary to avoid producing invalid strings.

func WithNotificationAgentID

func WithNotificationAgentID(ctx context.Context, agentID string) context.Context

WithNotificationAgentID annotates tool execution contexts with the agent that produced a notification.

Types

type AgentCommandHandler

type AgentCommandHandler struct {
	Ctx         context.Context
	Incoming    IncomingMessage
	Args        string
	Reply       func(string)
	ListAgents  func(context.Context, IncomingMessage) ([]AgentInfo, string, error)
	SwitchAgent func(context.Context, IncomingMessage, string) error
	FormatList  func([]IndexedAgent, string) string
	OnSwitched  func(string)
}

AgentCommandHandler provides the callbacks needed to serve /agent in text UIs.

type AgentInfo

type AgentInfo struct {
	ID   string
	Name string
}

AgentInfo is agent metadata for display in channel UIs.

type AgentManager

type AgentManager interface {
	// ListAgents returns enabled agents the user can access and the current agent ID.
	ListAgents(ctx context.Context, msg IncomingMessage) ([]AgentInfo, string, error)

	// SwitchAgent switches the active agent for this chat context.
	SwitchAgent(ctx context.Context, msg IncomingMessage, agentSlug string) error
}

AgentManager provides agent listing and switching for channel plugins that support the /agent command.

type AssetSaver added in v0.60.0

type AssetSaver interface {
	// SaveAsset writes data as a new file under assetsDir and returns the local
	// materialized path, durably persisting it in the asset authority. An
	// authority failure returns an error (the asset is never silently local-only).
	SaveAsset(ctx context.Context, assetsDir, fileName string, data []byte) (string, error)
}

AssetSaver is an optional capability that a Handler may implement. Channel plugins assert for it to persist an inbound attachment (file/media) through the deployment's authoritative asset store instead of writing local files that would be lost across replicas. It replaces the former blob process-global: the host owns durable-write semantics, the plugin only supplies the bytes and the resolved per-user assets directory (from UserRootResolver).

type BotRegistrar added in v0.42.0

type BotRegistrar interface {
	RegisterBotIdentity(platform, platformBotID, channelID string)
}

BotRegistrar is an optional capability that a Handler may implement. Channel adapters call RegisterBotIdentity at startup to record their bot's platform identity (e.g., Telegram username), enabling the group dispatcher to resolve @mentions to Stella agents.

type Channel

type Channel interface {
	// Name returns a unique identifier (e.g. "telegram", "qq").
	Name() string

	// Start begins listening for messages. Blocks until ctx is cancelled.
	Start(ctx context.Context) error

	// Stop gracefully shuts down the channel.
	Stop()

	// Notify sends a push notification to a target within this channel.
	Notify(ctx context.Context, n Notification) error
}

Channel is a messaging platform adapter.

type ChatStream

type ChatStream struct {
	Events    <-chan Event
	SessionID string
}

ChatStream holds the event channel and session metadata returned by HandleMessage.

type Event

type Event struct {
	Text       string
	Reasoning  string
	Image      *ImageEvent
	File       *FileEvent
	ToolUse    *ToolUseEvent
	References []renderrefs.Reference
	Err        error
}

Event is a stream event from the agent, consumed by channel plugins to render responses on the platform.

type FeishuConfig

type FeishuConfig struct {
	InstanceID        string                 `json:"-"`
	AppID             string                 `json:"app_id"`
	AppSecret         string                 `json:"app_secret"`
	EncryptKey        string                 `json:"encrypt_key"`
	VerificationToken string                 `json:"verification_token"`
	Groups            map[string]FeishuGroup `json:"groups"`
	EnableNotify      bool                   `json:"enable_notify"`
	TenantKey         string                 `json:"tenant_key"`
	AutoProvision     bool                   `json:"auto_provision"`
}

FeishuConfig is the persisted Feishu channel plugin configuration.

type FeishuGroup

type FeishuGroup struct {
	SystemPrompt string   `json:"system_prompt"`
	ToolAllow    []string `json:"tool_allow"`
	ToolDeny     []string `json:"tool_deny"`
}

FeishuGroup is a per-chat override in the persisted Feishu channel config.

type FileEvent

type FileEvent struct {
	Path string // absolute path on disk
	Name string // display filename (with extension)
}

FileEvent carries a local file path to send to the user.

type Handler

type Handler interface {
	MessageHandler
	ModelManager
	AgentManager
}

Handler combines message routing with model and agent management. Channel plugins typically need all three capabilities.

type ImageEvent

type ImageEvent struct {
	Data     string // base64 encoded
	MimeType string // e.g. "image/jpeg"
}

ImageEvent carries a base64-encoded image.

type IncomingMessage

type IncomingMessage struct {
	Platform   string   // "telegram", "qq", etc.
	ChannelID  string   // configured channel instance ID; defaults to Platform.
	SenderID   string   // preferred platform-specific user ID
	SenderIDs  []string // ordered candidate sender IDs, most stable first
	SenderName string   // display name
	ChatID     string   // group/channel ID (empty for DMs)
	IsGroup    bool
	ThreadID   string // platform sub-thread/topic id within ChatID (e.g. Telegram forum topic); empty if none
	Content    []ai.ContentBlock

	// Group-chat metadata (D3). Adapters fill what they can; empty/zero is allowed.
	MessageID string    // platform-native message ID (stable delivery/update id), empty if unavailable
	Timestamp time.Time // platform-reported send time, zero if unavailable
	ReplyTo   string    // platform message ID this message replies to, empty if none
	Mentions  []Mention // @-mentions, normalized; AgentID is resolved later by the dispatcher
}

IncomingMessage is the normalised input from any platform.

type IndexedAgent

type IndexedAgent struct {
	AgentInfo
	GlobalIdx int
}

IndexedAgent pairs an AgentInfo with its 1-based global index.

func IndexAgents

func IndexAgents(agents []AgentInfo) []IndexedAgent

IndexAgents wraps a full agent list with sequential 1-based indices.

type IndexedModel

type IndexedModel struct {
	ModelOption
	GlobalIdx int
}

IndexedModel pairs a ModelOption with its 1-based global index.

func FilterModels

func FilterModels(models []ModelOption, query string) []IndexedModel

FilterModels returns indexed models matching the query, preserving their 1-based global indices from the full list.

func IndexModels

func IndexModels(models []ModelOption) []IndexedModel

IndexModels wraps a full model list with sequential 1-based indices.

type Mention added in v0.42.0

type Mention struct {
	Raw        string // raw @ text (@username / <at open_id> ...), for audit/fallback
	PlatformID string // platform-side mentioned id (username / open_id / qq number)
	AgentID    string // resolved Stella agent; empty if unresolved
}

Mention is a normalized @-mention. Adapters fill Raw and PlatformID; the dispatcher resolves AgentID by looking up group membership. @-routing honors only mentions whose AgentID is non-empty.

type MessageHandler

type MessageHandler interface {
	// HandleIncoming resolves the user once, tries command handling, and if the
	// command is not handled, streams a chat response.
	// Returns (commandResponse, handled, stream, err).
	// If handled is true, commandResponse contains the reply and stream is nil.
	// If handled is false, stream contains the chat response.
	HandleIncoming(ctx context.Context, msg IncomingMessage, command, args string) (string, bool, *ChatStream, error)
}

MessageHandler is the core coordinator interface injected into channel plugins. It owns user resolution, agent routing, session management, and command handling. The single HandleIncoming entry point resolves the user once, tries command handling, and falls through to chat streaming — eliminating double resolution.

type ModelCommandHandler

type ModelCommandHandler struct {
	Args                string
	Reply               func(string)
	ListModels          func() []ModelOption
	SwitchModel         func(provider, model string) error
	FormatList          func([]IndexedModel, string) string
	AllowIndexSelection bool
	OnSwitched          func(ModelOption)
}

ModelCommandHandler provides the callbacks needed to serve /model in text UIs.

type ModelManager

type ModelManager interface {
	// ListModels returns available models.
	ListModels() []ModelOption

	// SwitchModel switches the active model.
	SwitchModel(provider, model string) error
}

ModelManager provides model listing and switching for channel plugins that support the /model command.

type ModelOption

type ModelOption struct {
	Provider string
	Model    string
}

ModelOption represents a selectable provider/model combination.

func FindModelByName

func FindModelByName(models []ModelOption, name string) (ModelOption, bool)

FindModelByName looks up a model by "provider/model" name (case-insensitive). Returns the model and true if found, zero value and false otherwise.

type Notification

type Notification struct {
	Channel string // optional: route to a specific backend
	ChatID  string // target chat/channel within the backend
	AgentID string // optional: agent that produced the notification
	Text    string // markdown content
	Silent  bool   // send without notification sound
}

Notification is a push message to send to a chat.

type ProvisionRequest

type ProvisionRequest struct {
	Platform   string
	ExternalID string
	Name       string
}

ProvisionRequest carries the information needed to look up a channel user.

type Provisioner

type Provisioner interface {
	ProvisionUser(ctx context.Context, req ProvisionRequest) error
}

Provisioner is an optional capability that a Handler may implement. Channel plugins assert for this interface when they want to auto-provision users on first contact, without adding the method to every channel's Handler.

type QQConfig

type QQConfig struct {
	InstanceID   string `json:"-"`
	AppID        string `json:"app_id"`
	AppSecret    string `json:"app_secret"`
	EnableNotify bool   `json:"enable_notify"`
}

QQConfig is the persisted QQ channel plugin configuration.

type TelegramConfig

type TelegramConfig struct {
	InstanceID   string `json:"-"`
	Token        string `json:"token"`
	ChannelID    string `json:"channel_id"`
	EnableNotify bool   `json:"enable_notify"`
}

TelegramConfig is the persisted Telegram channel plugin configuration.

type ToolRecord

type ToolRecord struct {
	Tool     string
	Input    string
	Status   string // "done" or "error"
	Detail   string
	Duration time.Duration
}

ToolRecord holds a completed tool invocation for summary display.

type ToolTracker

type ToolTracker struct {
	History      []ToolRecord
	ActiveTool   string
	ActiveInput  string
	ActiveStart  time.Time
	DisplayUntil time.Time // minimum time to keep showing the last-finished tool

	// MinDisplayDuration controls how long a finished tool stays visible.
	// Zero means no minimum.
	MinDisplayDuration time.Duration
}

ToolTracker tracks active and completed tool invocations during streaming.

func (*ToolTracker) Finish

func (tt *ToolTracker) Finish(t *ToolUseEvent)

Finish records the active tool as completed.

func (*ToolTracker) Handle

func (tt *ToolTracker) Handle(t *ToolUseEvent) bool

Handle processes a tool event, returning true if a display refresh is needed.

func (*ToolTracker) HasHistory

func (tt *ToolTracker) HasHistory() bool

HasHistory returns true if any tools were tracked.

func (*ToolTracker) IsDisplaying

func (tt *ToolTracker) IsDisplaying() bool

IsDisplaying returns true if there is an active tool or the minimum display duration for the last finished tool has not yet elapsed.

func (*ToolTracker) Render

func (tt *ToolTracker) Render() string

Render builds the tool section for real-time streaming display, showing completed tools and the active tool with elapsed time.

func (*ToolTracker) RenderFinal

func (tt *ToolTracker) RenderFinal() string

RenderFinal builds a compact tool summary for the final message. Shows a one-liner with tool counts and total time, plus individual lines for any failed tool calls.

func (*ToolTracker) Start

func (tt *ToolTracker) Start(t *ToolUseEvent)

Start registers a new tool as running.

type ToolUseEvent

type ToolUseEvent struct {
	ID         string
	Tool       string // tool name, e.g. "bash", "read"
	Status     string // "running", "done", "error"
	Input      string // short summary of the tool input
	Arguments  map[string]any
	Detail     string // error detail or result summary
	Content    string
	References []renderrefs.Reference
}

ToolUseEvent describes a tool invocation in progress or completed.

type UserRootResolver

type UserRootResolver interface {
	ResolveUserRoot(ctx context.Context, msg IncomingMessage) (string, error)
}

UserRootResolver is an optional capability that a Handler may implement. Channel plugins assert for this interface when they need the per-user writable root path (e.g. to store uploaded files) before calling HandleIncoming.

type WebhookConfig added in v0.60.0

type WebhookConfig struct {
	// DefaultWait selects synchronous (true) vs. fire-and-forget (false) when a
	// request does not set the ?wait query parameter.
	DefaultWait bool `json:"default_wait"`
	// WaitTimeoutSeconds bounds how long a synchronous caller waits for the
	// agent's reply before receiving 504 (the run continues in the background).
	WaitTimeoutSeconds int `json:"wait_timeout_seconds"`
	// MaxRunTimeoutSeconds is the hard ceiling on the agent run itself.
	MaxRunTimeoutSeconds int `json:"max_run_timeout_seconds"`
	// SessionMode is "ephemeral" (fresh session per trigger, default) or
	// "persistent" (one stable session per caller per webhook instance).
	SessionMode string `json:"session_mode"`
}

WebhookConfig is the persisted config for the inbound webhook channel. The agent binding lives on the channel row (agent_id), not here; there is no user binding — each run executes as the calling PAT's user. This holds only behavioural knobs.

func (WebhookConfig) EffectiveMaxRunTimeout added in v0.60.0

func (c WebhookConfig) EffectiveMaxRunTimeout() int

EffectiveMaxRunTimeout returns the configured run ceiling or the default.

func (WebhookConfig) EffectiveWaitTimeout added in v0.60.0

func (c WebhookConfig) EffectiveWaitTimeout() int

EffectiveWaitTimeout returns the configured wait timeout or the default.

func (WebhookConfig) Persistent added in v0.60.0

func (c WebhookConfig) Persistent() bool

Persistent reports whether triggers accumulate into one stable session.

type WeixinConfig

type WeixinConfig struct {
	InstanceID   string `json:"-"`
	BotToken     string `json:"bot_token"`
	BaseURL      string `json:"base_url"`
	BotID        string `json:"bot_id"`
	UserID       string `json:"user_id"`
	SKRouteTag   string `json:"sk_route_tag"`
	EnableNotify bool   `json:"enable_notify"`
}

WeixinConfig is the persisted Weixin channel plugin configuration.

Jump to

Keyboard shortcuts

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