im

package
v0.8.3 Latest Latest
Warning

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

Go to latest
Published: Oct 14, 2026 License: Apache-2.0, MIT Imports: 37 Imported by: 0

Documentation

Index

Constants

View Source
const (
	RedisKeyLeader     = "im:ws:leader:"    // + channelID — WebSocket leader election
	RedisKeyDedup      = "im:dedup:"        // + messageID — message deduplication
	RedisKeyStop       = "im:stop:"         // + userKey   — cross-instance /stop marker (pre-execution)
	RedisKeyInflight   = "im:inflight:"     // + userKey   — maps userKey → sessionID:messageID for cross-instance /stop
	RedisKeyQueueUser  = "im:queue:user:"   // + userKey   — global per-user queue counter
	RedisKeyRateLimit  = "im:ratelimit:"    // + key       — sliding-window rate limiting
	RedisKeyGlobalGate = "im:global:active" // global concurrent worker counter
	// RedisChannelConfig broadcasts durable im_channels mutations so every
	// application replica invalidates its local adapter/config snapshot.
	RedisChannelConfig = "im:channel:config"
)

── Redis key prefixes ────────────────────────────────────────────────────── All IM-related Redis keys are defined here for discoverability and to avoid scattered string literals across multiple files.

Variables

View Source
var ErrChannelDisabled = errors.New("channel is disabled")

ErrChannelDisabled reports that a channel row exists but is disabled, so callers can tell it apart from a missing channel or a transient failure.

View Source
var MarkdownThinkStyle = ThinkBlockStyle{
	ThinkingHeader: "> 💭 **思考中...**\n",
	ThoughtHeader:  "> 💭 **思考过程**\n",
	LinePrefix:     "> ",
	LineSuffix:     "",
	Separator:      "\n---\n\n",
}

MarkdownThinkStyle renders think blocks as markdown blockquotes. Used by DingTalk and Feishu/Lark.

Known limitation: the headers below are Chinese for every platform, including ones whose users are typically not Chinese speakers (Lark, Slack, Telegram, Mattermost). Localizing them means threading a locale into FormatIMDisplayContent, which is called from the shared streaming path with no platform context — a cross-platform change rather than a per-platform patch.

View Source
var RAGThinkingStyle = ThinkBlockStyle{
	ThinkingHeader: "> 💭 **思考中...**\n",
	ThoughtHeader:  "> 💭 **思考**\n",
	LinePrefix:     "> ",
	LineSuffix:     "",
	Separator:      "\n---\n\n",
}

RAGThinkingStyle matches Web RagPipelineProgress thinking row (agent.think).

View Source
var TelegramThinkStyle = ThinkBlockStyle{
	ThinkingHeader: "> 💭 *思考中...*\n",
	ThoughtHeader:  "> 💭 *思考过程*\n",
	LinePrefix:     "> ",
	LineSuffix:     "",
	Separator:      "\n---\n\n",
}

TelegramThinkStyle renders think blocks as blockquotes for Telegram. Uses the same blockquote format as other platforms for reliable rendering during streaming (where incomplete markdown can cause API failures).

Functions

func BuildIMAgentStreamRaw

func BuildIMAgentStreamRaw(parts IMStreamParts, agentInProgress bool) string

BuildIMAgentStreamRaw assembles agent-mode raw content.

func FormatIMDisplayContent

func FormatIMDisplayContent(raw string, phase StreamDisplayPhase) string

FormatIMDisplayContent formats raw stream content for the given display phase.

func FormatIMFinalFromParts

func FormatIMFinalFromParts(parts IMStreamParts) string

FormatIMFinalFromParts returns the final replace frame (answer-only for all modes).

func FormatIMIntermediateFromParts

func FormatIMIntermediateFromParts(parts IMStreamParts, agentInProgress bool) string

FormatIMIntermediateFromParts formats in-progress IM stream display.

func FormatIMRagPipelineLine

func FormatIMRagPipelineLine(step IMToolStep) string

FormatIMRagPipelineLine formats quick-QA RAG pipeline steps (Web RagPipelineProgress).

func FormatIMToolLine

func FormatIMToolLine(step IMToolStep) string

FormatIMToolLine formats one agent tool step (no emoji; aligned with Web getToolTitle).

func GetBool

func GetBool(creds map[string]any, key string) bool

GetBool reads a boolean from JSON credentials (bool, string "true"/"1", or non-zero number).

func GetString

func GetString(creds map[string]any, key string) string

GetString safely extracts a string value from a credentials map.

func IsRAGPipelineToolName

func IsRAGPipelineToolName(name string) bool

IsRAGPipelineToolName reports whether a tool is part of the quick-QA progress UI.

func LooksLikeCommand

func LooksLikeCommand(content string) bool

LooksLikeCommand returns true when content appears to be a command attempt— it starts with "/" and the first token contains no further "/" separators.

This distinguishes "/help" (command attempt) from "/api/v2/users" (URL path that should fall through to the QA pipeline).

func ParseCredentials

func ParseCredentials(data []byte) (map[string]any, error)

ParseCredentials parses the JSONB credentials field into a map.

func ResolveMode

func ResolveMode(channel *IMChannel, def string) string

ResolveMode returns channel.Mode, falling back to def when it is empty.

func RunSupervised

func RunSupervised(ctx context.Context, cfg SupervisorConfig)

RunSupervised manages the lifecycle of a long-lived IM connection.

It keeps a connection alive by (re)establishing it via cfg.Connect, then proactively recycling it every cfg.MaxConnAge. The underlying SDK is still expected to handle transient drops via its own auto-reconnect; the periodic recycle is a safety net that eliminates stuck/zombie connections that the SDK fails to recover on its own.

RunSupervised blocks until ctx is cancelled. On cancellation it tears down the active connection via the stop function before returning, so callers can rely on cancelling ctx to fully stop the connection.

func StripThinkBlocks

func StripThinkBlocks(content string) string

StripThinkBlocks removes <think> blocks for final IM display.

func TransformThinkBlocks

func TransformThinkBlocks(content string, style ThinkBlockStyle) string

TransformThinkBlocks converts <think>...</think> blocks using the given style. Handles both complete blocks and in-progress blocks (where </think> has not yet arrived during streaming).

func WrapThinkBlock

func WrapThinkBlock(inner string) string

WrapThinkBlock wraps inner content with think tags for formatting.

Types

type Adapter

type Adapter interface {
	// Platform returns the platform identifier.
	Platform() Platform

	// VerifyCallback verifies the signature/token of an incoming callback request.
	// Returns nil if verification passes.
	VerifyCallback(c *gin.Context) error

	// ParseCallback parses the raw IM callback request into a unified IncomingMessage.
	// Returns nil message for non-message events (e.g., URL verification).
	ParseCallback(c *gin.Context) (*IncomingMessage, error)

	// SendReply sends a reply back to the IM platform.
	SendReply(ctx context.Context, incoming *IncomingMessage, reply *ReplyMessage) error

	// HandleURLVerification handles the initial URL verification challenge from the IM platform.
	// Returns true if this request is a verification request and has been handled.
	HandleURLVerification(c *gin.Context) bool
}

Adapter is the interface every IM platform must implement.

type AdapterFactory

type AdapterFactory func(ctx context.Context, channel *IMChannel, msgHandler func(ctx context.Context, msg *IncomingMessage) error) (Adapter, context.CancelFunc, error)

AdapterFactory creates an Adapter from an IMChannel configuration. The second return value is an optional cleanup function (e.g., for stopping websocket connections).

type ChannelSession

type ChannelSession struct {
	ID          string         `json:"id"            gorm:"type:varchar(36);primaryKey;default:uuid_generate_v4()"`
	Platform    string         `json:"platform"      gorm:"type:varchar(20);not null"`
	UserID      string         `json:"user_id"       gorm:"type:varchar(128);not null"`
	ChatID      string         `json:"chat_id"       gorm:"type:varchar(128);not null;default:''"`
	ThreadID    string         `json:"thread_id"     gorm:"type:varchar(128);not null;default:''"`
	SessionID   string         `json:"session_id"    gorm:"type:varchar(36);not null;index"`
	TenantID    uint64         `json:"tenant_id"     gorm:"not null;index"`
	AgentID     string         `json:"agent_id"      gorm:"type:varchar(36);default:''"`
	IMChannelID string         `json:"im_channel_id" gorm:"type:varchar(36);default:''"`
	Status      string         `json:"status"        gorm:"type:varchar(20);not null;default:'active'"`
	Metadata    types.JSON     `json:"metadata"      gorm:"type:jsonb;default:'{}'"`
	CreatedAt   time.Time      `json:"created_at"`
	UpdatedAt   time.Time      `json:"updated_at"`
	DeletedAt   gorm.DeletedAt `json:"deleted_at"    gorm:"index"`
}

ChannelSession maps an IM channel (user+chat combination) to a WeKnora session. This allows the IM integration to maintain conversation continuity.

func (*ChannelSession) BeforeCreate

func (cs *ChannelSession) BeforeCreate(tx *gorm.DB) error

func (ChannelSession) TableName

func (ChannelSession) TableName() string

type ChannelWithAgent

type ChannelWithAgent struct {
	ID          string    `json:"id"`
	TenantID    uint64    `json:"tenant_id"`
	AgentID     string    `json:"agent_id"`
	AgentName   string    `json:"agent_name"`
	Platform    string    `json:"platform"`
	Name        string    `json:"name"`
	Enabled     bool      `json:"enabled"`
	Mode        string    `json:"mode"`
	OutputMode  string    `json:"output_mode"`
	Locale      string    `json:"locale"`
	SessionMode string    `json:"session_mode"`
	BotIdentity string    `json:"bot_identity"`
	CreatedAt   time.Time `json:"created_at"`
	UpdatedAt   time.Time `json:"updated_at"`
}

ChannelWithAgent augments an IMChannel summary with its owning agent's display name. Credentials are intentionally omitted so this type is safe to return from a tenant-scoped list endpoint; callers that need credentials must use the per-agent endpoint which enforces the same tenant scope anyway.

type ChatType

type ChatType string

ChatType represents the IM chat type.

const (
	ChatTypeDirect ChatType = "direct"
	ChatTypeGroup  ChatType = "group"
)

type ClearCommand

type ClearCommand struct{}

ClearCommand implements /clear. It soft-deletes the current ChannelSession and clears the LLM context so the next message starts a completely fresh conversation.

func (*ClearCommand) Description

func (c *ClearCommand) Description() string

func (*ClearCommand) Execute

func (*ClearCommand) Name

func (c *ClearCommand) Name() string

type Command

type Command interface {
	// Name is the primary token used after "/" (e.g. "kb", "mode").
	Name() string
	// Description is the one-line summary shown in /help output.
	Description() string
	// Execute runs the command and returns a reply to send to the user.
	Execute(ctx context.Context, cmdCtx *CommandContext, args []string) (*CommandResult, error)
}

Command is the interface every IM slash-command must implement.

Design rules:

  • Dependencies (DB, services) are injected at construction time.
  • Validation errors (bad args, entity not found) are returned as a CommandResult with a helpful message, NOT as an error.
  • error is reserved for infrastructure failures (DB errors, network, …).

type CommandAction

type CommandAction int

CommandAction represents a service-level side effect that a command requests. Using an enum keeps commands free of service/DB dependencies—they declare intent, the Service executes it.

const (
	// ActionNone means no side effect beyond sending the reply.
	ActionNone CommandAction = iota
	// ActionClear soft-deletes the current ChannelSession and clears the LLM
	// context so the next message creates a completely fresh conversation.
	ActionClear
	// ActionStop cancels the in-flight QA request for this user+chat.
	ActionStop
)

type CommandContext

type CommandContext struct {
	// Incoming is the raw IM message that triggered the command.
	Incoming *IncomingMessage
	// Session is the IM channel session for this user×chat combination.
	Session *ChannelSession
	// TenantID is the tenant that owns this bot deployment.
	TenantID uint64
	// AgentName is the display name of the bound agent (empty if none).
	AgentName string
	// CustomAgent is the bound agent configuration. Commands that need to
	// inspect agent-level settings (e.g. /search reading KBSelectionMode)
	// can access it directly. May be nil when no agent is bound.
	CustomAgent *types.CustomAgent
	// ChannelOutputMode is the channel-level output mode configured by the admin
	// ("stream" or "full").
	ChannelOutputMode string
}

CommandContext carries all runtime data a command needs during execution. Services are NOT here; inject them into command structs at construction time.

type CommandRegistry

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

CommandRegistry maps slash-command names to their handlers.

func NewCommandRegistry

func NewCommandRegistry() *CommandRegistry

NewCommandRegistry returns an empty registry.

func (*CommandRegistry) All

func (r *CommandRegistry) All() []Command

All returns every registered command.

func (*CommandRegistry) IsRegistered

func (r *CommandRegistry) IsRegistered(content string) bool

IsRegistered returns true when content starts with a registered command name. It is cheaper than Parse because it does not allocate a result.

func (*CommandRegistry) Parse

func (r *CommandRegistry) Parse(content string) (Command, []string, bool)

Parse checks whether content is a slash-command and, if so, returns the matching Command and the remaining tokens as args.

It returns (nil, nil, false) when:

  • content does not start with "/"
  • the first token after "/" has no registered handler

Note: unrecognised slash-words are deliberately NOT matched here so that the caller can decide whether to treat them as unknown commands (show help) or pass them through to the QA pipeline (e.g. "/api/v2/users" paths). Use LooksLikeCommand to distinguish the two cases.

func (*CommandRegistry) Register

func (r *CommandRegistry) Register(cmd Command)

Register adds cmd to the registry under its Name(). Panics on duplicate names to surface misconfiguration at startup rather than silently ignoring it.

type CommandResult

type CommandResult struct {
	// Content is the Markdown reply sent back to the user.
	Content string
	// Action requests a service-level side effect (reset, clear, …).
	Action CommandAction
}

CommandResult is the output produced by a Command.Execute call.

type FileDownloader

type FileDownloader interface {
	// DownloadFile downloads a file resource from the IM platform.
	// Returns the file content reader, the resolved file name, and any error.
	DownloadFile(ctx context.Context, msg *IncomingMessage) (io.ReadCloser, string, error)
}

FileDownloader is an optional interface that adapters can implement to support downloading file attachments from the IM platform. It allows file/image messages to be supplied to QA as attachments; when a knowledge_base_id is configured, the same capability also enables asynchronous knowledge-base save.

type FullOutputProgressSender

type FullOutputProgressSender interface {
	StreamSender
	SupportsFullOutputProgress() bool
}

FullOutputProgressSender is an optional capability for adapters whose stream starts with a visible placeholder that can safely be replaced once with the completed answer. Full output never calls UpdateStreamContent.

type HelpCommand

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

HelpCommand implements /help [command].

func (*HelpCommand) Description

func (c *HelpCommand) Description() string

func (*HelpCommand) Execute

func (c *HelpCommand) Execute(_ context.Context, _ *CommandContext, args []string) (*CommandResult, error)

func (*HelpCommand) Name

func (c *HelpCommand) Name() string

type IMChannel

type IMChannel struct {
	ID              string         `json:"id"          gorm:"type:varchar(36);primaryKey;default:uuid_generate_v4()"`
	TenantID        uint64         `json:"tenant_id"   gorm:"not null;index:idx_im_channels_tenant"`
	AgentID         string         `json:"agent_id"    gorm:"type:varchar(36);not null;index:idx_im_channels_agent"`
	Platform        string         `json:"platform"    gorm:"type:varchar(20);not null"`
	Name            string         `json:"name"        gorm:"type:varchar(255);not null;default:''"`
	Enabled         bool           `json:"enabled"     gorm:"not null;default:true"`
	Mode            string         `json:"mode"        gorm:"type:varchar(20);not null;default:'websocket'"`
	OutputMode      string         `json:"output_mode"       gorm:"type:varchar(20);not null;default:'stream'"`
	Locale          string         `json:"locale"            gorm:"type:varchar(16);not null;default:''"`
	KnowledgeBaseID string         `json:"knowledge_base_id" gorm:"type:varchar(36);default:''"`
	BotIdentity     string         `` /* 158-byte string literal not displayed */
	SessionMode     string         `json:"session_mode"      gorm:"type:varchar(20);not null;default:'user'"`
	Credentials     types.JSON     `json:"credentials"       gorm:"type:jsonb;not null;default:'{}'"`
	CreatedAt       time.Time      `json:"created_at"`
	UpdatedAt       time.Time      `json:"updated_at"`
	DeletedAt       gorm.DeletedAt `json:"deleted_at"  gorm:"index"`
}

IMChannel represents an IM channel configuration stored in the database. Each channel binds to an agent and contains platform-specific credentials.

func (*IMChannel) BeforeCreate

func (ch *IMChannel) BeforeCreate(tx *gorm.DB) error

func (*IMChannel) BeforeSave

func (ch *IMChannel) BeforeSave(tx *gorm.DB) error

BeforeSave ensures bot_identity is recomputed and session_mode is validated on every save (create + update).

func (IMChannel) TableName

func (IMChannel) TableName() string

type IMChannelSummary

type IMChannelSummary struct {
	ID                    string    `json:"id"`
	TenantID              uint64    `json:"tenant_id"`
	AgentID               string    `json:"agent_id"`
	Platform              string    `json:"platform"`
	Name                  string    `json:"name"`
	Enabled               bool      `json:"enabled"`
	Mode                  string    `json:"mode"`
	OutputMode            string    `json:"output_mode"`
	Locale                string    `json:"locale"`
	KnowledgeBaseID       string    `json:"knowledge_base_id"`
	BotIdentity           string    `json:"bot_identity"`
	SessionMode           string    `json:"session_mode"`
	CredentialsConfigured bool      `json:"credentials_configured"`
	CreatedAt             time.Time `json:"created_at"`
	UpdatedAt             time.Time `json:"updated_at"`
}

IMChannelSummary is the HTTP-safe list shape for IM channels. Credentials are never included — use Admin+ Create/Update responses to read back values immediately after a mutation.

func SummarizeIMChannel

func SummarizeIMChannel(ch IMChannel) IMChannelSummary

SummarizeIMChannel converts a stored channel into its list response shape.

func SummarizeIMChannels

func SummarizeIMChannels(channels []IMChannel) []IMChannelSummary

SummarizeIMChannels converts stored channels into list response shapes.

type IMStreamMode

type IMStreamMode int

IMStreamMode distinguishes agent reasoning from quick-QA RAG pipeline display.

const (
	IMStreamModeAgent IMStreamMode = iota
	IMStreamModeQuickQA
)

type IMStreamParts

type IMStreamParts struct {
	Mode IMStreamMode

	// Quick-QA (RagPipelineProgress on Web):
	PipelineToolSteps []IMToolStep // query_understand / knowledge_search rows
	ReasoningInner    string       // model reasoning_content — separate "思考" section

	// Agent (AgentStreamDisplay on Web):
	AgentInner     string       // retracted preambles + thoughts ("思考过程")
	AgentToolSteps []IMToolStep // tool progress lines (one row per tool_call_id)
	LiveAnswer     string       // optimistic answer stream before tools retract it

	Answer string // final answer (complete / knowledge QA)
}

IMStreamParts separates stream content for IM display.

type IMToolStep

type IMToolStep struct {
	ToolCallID string
	ToolName   string
	Pending    bool
	Success    bool
	Arguments  map[string]any
	Data       map[string]interface{}
	Output     string
}

IMToolStep tracks one tool invocation for IM display (mirrors Web agent stream events).

type IncomingMessage

type IncomingMessage struct {
	// Platform identifies which IM platform the message comes from.
	Platform Platform
	// MessageType is "text" (default) or "file".
	MessageType MessageType
	// UserID is the IM-platform user identifier.
	UserID string
	// UserName is the display name of the user (optional).
	UserName string
	// ChatID is the group/channel ID (empty for direct messages).
	ChatID string
	// ChatType distinguishes direct message from group chat.
	ChatType ChatType
	// Content is the text content of the message (empty for file messages).
	Content string
	// MessageID is the IM-platform message identifier (for dedup).
	MessageID string
	// FileKey is the platform file identifier (for file messages).
	FileKey string
	// FileName is the original file name (for file messages).
	FileName string
	// FileSize is the file size in bytes (for file messages, optional).
	FileSize int64
	// ThreadID is the platform-specific thread identifier.
	// - Slack: thread_ts (top-level message uses its own timestamp)
	// - Mattermost: root_id, or post_id if top-level
	// - Feishu/Lark: root_id, or message_id if top-level
	// - Telegram: message_thread_id (Forum Topics only)
	// Empty for platforms without thread support (WeCom, DingTalk).
	// In thread mode, top-level messages use their own ID as ThreadID,
	// effectively creating a new session per top-level message.
	ThreadID string
	// Quote is the quoted/replied message, if any.
	// Populated by adapters on platforms that support quote-reply.
	Quote *QuotedMessage
	// Extra holds platform-specific fields (e.g., WeCom stream ID).
	Extra map[string]string
}

IncomingMessage is the unified message parsed from an IM callback.

type InfoCommand

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

InfoCommand implements /info. It shows the bound agent's profile and capabilities so IM users can understand what the bot can do without leaving the chat.

func (*InfoCommand) Description

func (c *InfoCommand) Description() string

func (*InfoCommand) Execute

func (c *InfoCommand) Execute(ctx context.Context, cmdCtx *CommandContext, _ []string) (*CommandResult, error)

func (*InfoCommand) Name

func (c *InfoCommand) Name() string

type MessageType

type MessageType string

MessageType identifies the kind of IM message.

const (
	MessageTypeText  MessageType = "text"
	MessageTypeFile  MessageType = "file"
	MessageTypeImage MessageType = "image"
)

type Platform

type Platform string

Platform identifies an IM platform.

const (
	PlatformWeCom  Platform = "wecom"
	PlatformFeishu Platform = "feishu"
	// PlatformLark is Feishu's international edition (open.larksuite.com).
	// It shares the Feishu adapter; only the API host and tenant differ.
	PlatformLark       Platform = "lark"
	PlatformSlack      Platform = "slack"
	PlatformTelegram   Platform = "telegram"
	PlatformDingtalk   Platform = "dingtalk"
	PlatformMattermost Platform = "mattermost"
	PlatformWeChat     Platform = "wechat"
	PlatformQQBot      Platform = "qqbot"
	PlatformYunzhijia  Platform = "yunzhijia"
)

type QueueMetrics

type QueueMetrics struct {
	// Depth is the current number of requests waiting in the queue.
	Depth int
	// ActiveWorkers is the number of workers currently executing a QA request.
	ActiveWorkers int64
	// TotalEnqueued is the cumulative number of requests enqueued.
	TotalEnqueued int64
	// TotalProcessed is the cumulative number of requests dequeued and executed.
	TotalProcessed int64
	// TotalRejected is the cumulative number of requests rejected (queue full / per-user limit).
	TotalRejected int64
	// TotalTimeout is the cumulative number of requests discarded due to queue timeout.
	TotalTimeout int64
}

QueueMetrics exposes observable queue state.

type QuotedMessage

type QuotedMessage struct {
	// MessageID is the platform message ID of the quoted message.
	MessageID string
	// Content is the text content. Empty for non-text message types.
	Content string
	// SenderID is the platform user ID of the quoted message's author.
	SenderID string
	// IsBotMessage indicates whether the quoted message was from the bot.
	IsBotMessage bool
	// NonTextType records the original message type when the quoted message
	// has no extractable text (e.g. "image", "file", "video").
	// Empty when Content is populated. Used to generate LLM instructions
	// instead of content placeholders that cause hallucination.
	NonTextType string
}

QuotedMessage holds the content and metadata of a quoted/replied message. Populated by platform adapters that support quote-reply (e.g. WeCom long-connection).

type ReplyMessage

type ReplyMessage struct {
	// Content is the text content (Markdown).
	Content string
	// IsStreaming indicates whether this is a streaming chunk.
	IsStreaming bool
	// IsFinal marks the last chunk of a streaming reply.
	IsFinal bool
	// Extra holds platform-specific fields.
	Extra map[string]string
}

ReplyMessage is what WeKnora sends back to the IM platform.

type SearchCommand

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

SearchCommand implements /search <query>.

It runs a hybrid search (vector + keywords) against the user's selected knowledge bases—or the bot-level defaults when no override is active—and returns the raw matching passages without AI summarisation. This is useful when the user needs to inspect source text directly.

func (*SearchCommand) Description

func (c *SearchCommand) Description() string

func (*SearchCommand) Execute

func (c *SearchCommand) Execute(ctx context.Context, cmdCtx *CommandContext, args []string) (*CommandResult, error)

func (*SearchCommand) Name

func (c *SearchCommand) Name() string

type Service

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

Service orchestrates IM message handling: 1. Receives a unified IncomingMessage from an Adapter 2. Resolves or creates a WeKnora session for the IM channel 3. Dispatches slash-commands (/help, /kb, /clear, etc.) without entering QA 4. Calls the WeKnora QA pipeline for normal messages 5. Collects the streaming answer and sends it back via the Adapter

func NewService

func NewService(
	db *gorm.DB,
	sessionService interfaces.SessionService,
	messageService interfaces.MessageService,
	tenantService interfaces.TenantService,
	agentService interfaces.CustomAgentService,
	knowledgeService interfaces.KnowledgeService,
	kbService interfaces.KnowledgeBaseService,
	streamManager interfaces.StreamManager,
	defaultFileSvc interfaces.FileService,
	documentReader interfaces.DocumentReader,
	oauthManager *mcppkg.OAuthManager,
	redisClient *redis.Client,
	appCfg *config.Config,
	storageResolver interfaces.StorageBackendResolver,
) *Service

NewService creates a new IM service. redisClient may be nil — in that case the service falls back to local in-memory state (Lite / single-instance mode). cfg may be nil — in that case built-in defaults are used.

func (*Service) CreateChannel

func (s *Service) CreateChannel(channel *IMChannel) error

CreateChannel creates a new IM channel and optionally starts it. Returns a duplicate_bot error if the bot identity is already used by another channel.

func (*Service) DeleteChannel

func (s *Service) DeleteChannel(channelID string, tenantID uint64) error

DeleteChannel soft-deletes a channel and stops it. Only deletes if the channel belongs to the given tenant.

func (*Service) DeleteChannelsByAgent

func (s *Service) DeleteChannelsByAgent(agentID string, tenantID uint64) error

DeleteChannelsByAgent stops and soft-deletes every IM channel bound to the given agent within the tenant. Used when a custom agent is removed so overview lists and running adapters do not outlive the agent.

func (*Service) EnsureChannelAdapter

func (s *Service) EnsureChannelAdapter(channelID string) (Adapter, *IMChannel, error)

EnsureChannelAdapter loads the durable channel row before serving a webhook callback. This prevents a replica that missed an invalidation event from verifying or processing the callback with stale credentials/configuration.

func (*Service) GetChannelAdapter

func (s *Service) GetChannelAdapter(channelID string) (Adapter, *IMChannel, bool)

GetChannelAdapter returns the adapter and channel config for a given channel ID.

func (*Service) GetChannelByID

func (s *Service) GetChannelByID(channelID string) (*IMChannel, error)

GetChannelByID loads a channel from the database.

func (*Service) GetChannelByIDAndTenant

func (s *Service) GetChannelByIDAndTenant(channelID string, tenantID uint64) (*IMChannel, error)

GetChannelByIDAndTenant loads a channel from the database, scoped to a specific tenant.

func (*Service) HandleMessage

func (s *Service) HandleMessage(ctx context.Context, msg *IncomingMessage, channelID string) error

HandleMessage processes an incoming IM message end-to-end using channel config.

func (*Service) ListChannelsByAgent

func (s *Service) ListChannelsByAgent(agentID string, tenantID uint64) ([]IMChannel, error)

ListChannelsByAgent returns all channels for a given agent within a tenant.

func (*Service) ListChannelsByTenant

func (s *Service) ListChannelsByTenant(ctx context.Context, tenantID uint64) ([]ChannelWithAgent, error)

ListChannelsByTenant returns all non-deleted IM channels in the given tenant, joined with custom_agents.name. Built-in agent names are re-localized from YAML i18n using ctx (the DB column may be empty or frozen in the writer's language). Channels whose custom agent was soft-deleted are excluded so overview lists stay consistent after agent removal.

func (*Service) LoadAndStartChannels

func (s *Service) LoadAndStartChannels() error

LoadAndStartChannels loads all enabled channels from the database and starts them.

func (*Service) RegisterAdapterFactory

func (s *Service) RegisterAdapterFactory(platform string, factory AdapterFactory)

RegisterAdapterFactory registers a factory for creating adapters for a given platform.

func (*Service) SetChannelAgentID

func (s *Service) SetChannelAgentID(ctx context.Context, channel *IMChannel, agentID string) error

SetChannelAgentID validates and assigns a new agent for an existing channel.

func (*Service) SetChannelKnowledgeBaseID

func (s *Service) SetChannelKnowledgeBaseID(ctx context.Context, channel *IMChannel, kbID string) error

SetChannelKnowledgeBaseID binds the KB that IM files are saved into. It must belong to the channel's workspace, which writes into it, and to a KB-restricted API key's allow-list; a foreign ID would create records in (and read the configuration of) another workspace's KB. An empty ID clears the binding.

func (*Service) StartChannel

func (s *Service) StartChannel(channel *IMChannel) error

StartChannel creates and registers an adapter for the given channel. For WebSocket channels with Redis available, only one instance acquires the leader lock and opens the connection; other instances periodically retry so they can take over if the leader dies.

func (*Service) Stop

func (s *Service) Stop()

Stop gracefully shuts down the service, stopping all channels and background goroutines.

func (*Service) StopChannel

func (s *Service) StopChannel(channelID string)

StopChannel stops and removes a running channel.

func (*Service) ToggleChannel

func (s *Service) ToggleChannel(channelID string, tenantID uint64) (*IMChannel, error)

ToggleChannel enables or disables a channel. Only toggles if the channel belongs to the given tenant.

func (*Service) UpdateChannel

func (s *Service) UpdateChannel(channel *IMChannel) error

UpdateChannel updates a channel and restarts it if needed. Returns a duplicate_bot error if the bot identity is already used by another channel.

type SessionMode

type SessionMode string

SessionMode determines how IM sessions are resolved.

const (
	// SessionModeUser resolves sessions by (platform, user_id, chat_id, tenant_id).
	SessionModeUser SessionMode = "user"
	// SessionModeThread resolves sessions by (platform, thread_id, chat_id, tenant_id).
	SessionModeThread SessionMode = "thread"
)

type StopCommand

type StopCommand struct{}

StopCommand implements /stop. It cancels the in-flight QA request for the current user+chat, allowing the user to abort a long-running ReAct reasoning chain without waiting for it to complete. If no request is in progress the command simply acknowledges.

func (*StopCommand) Description

func (c *StopCommand) Description() string

func (*StopCommand) Execute

func (c *StopCommand) Execute(_ context.Context, _ *CommandContext, _ []string) (*CommandResult, error)

func (*StopCommand) Name

func (c *StopCommand) Name() string

type StreamDisplayPhase

type StreamDisplayPhase int

StreamDisplayPhase controls how raw IM stream content is rendered.

const (
	// StreamDisplayIntermediate shows in-progress thinking/tools (Web: expanded progress).
	StreamDisplayIntermediate StreamDisplayPhase = iota
	// StreamDisplayFinal shows answer-only text (Web: collapsed/hidden intermediate steps).
	StreamDisplayFinal
)

type StreamSender

type StreamSender interface {
	// StartStream initializes a streaming reply session (e.g., creates a streaming card).
	// Returns a platform-specific stream ID for subsequent chunk/end calls.
	StartStream(ctx context.Context, incoming *IncomingMessage) (string, error)

	// UpdateStreamContent replaces the user-visible stream text with fullContent so far.
	// Platforms with replace semantics (WeCom, Telegram edit, etc.) show this as the entire message.
	UpdateStreamContent(ctx context.Context, incoming *IncomingMessage, streamID string, fullContent string) error

	// FinalizeStream performs the final replace with answer-only content (thinking/tools stripped).
	FinalizeStream(ctx context.Context, incoming *IncomingMessage, streamID string, finalContent string) error

	// EndStream finalizes a streaming reply.
	EndStream(ctx context.Context, incoming *IncomingMessage, streamID string) error
}

StreamSender is an optional interface that adapters can implement to support streaming replies. In stream output mode the IM service pushes answer chunks in real time. In full output mode it may use the same replaceable message only as a progress placeholder, then replace it once with the completed answer.

type SupervisorConfig

type SupervisorConfig struct {
	// Name identifies the supervised connection in logs (e.g. "DingTalk channel xxx").
	Name string

	// MaxConnAge is the interval between proactive reconnections.
	// Defaults to defaultRecycleInterval when <= 0.
	MaxConnAge time.Duration

	// RetryDelay is the backoff after a failed connect attempt.
	// Defaults to defaultSupervisorRetryDelay when <= 0.
	RetryDelay time.Duration

	// Connect establishes a fresh connection and returns a stop function that
	// tears it down cleanly (preventing any further internal reconnects).
	// It may wrap either a blocking or a non-blocking SDK Start: the only
	// contract is that it returns once the connection has been established
	// (or the attempt has failed), along with a stop function.
	Connect func(ctx context.Context) (stop func(), err error)
}

SupervisorConfig configures RunSupervised.

type ThinkBlockStyle

type ThinkBlockStyle struct {
	// ThinkingHeader is shown when the think block is still in-progress (no closing tag yet).
	ThinkingHeader string // e.g. "> 💭 **思考中...**\n" or "💭 _思考中..._\n"
	// ThoughtHeader is shown before the think block content.
	ThoughtHeader string // e.g. "> 💭 **思考过程**\n" or "💭 *思考过程*\n"
	// LinePrefix is prepended to each line of think content.
	LinePrefix string // e.g. "> " or "> _"
	// LineSuffix is appended to each line of think content (before newline).
	LineSuffix string // e.g. "" or "_"
	// Separator is inserted between the think block and the rest of the content.
	Separator string // e.g. "\n---\n\n" or "\n"
}

ThinkBlockStyle controls how <think>...</think> blocks are rendered.

Directories

Path Synopsis
Package feishu implements the Feishu (飞书) IM adapter for WeKnora, and with it the Lark adapter: Feishu and Lark are the same product on two isolated clouds (open.feishu.cn and open.larksuite.com) sharing one API surface, so a single implementation serves both.
Package feishu implements the Feishu (飞书) IM adapter for WeKnora, and with it the Lark adapter: Feishu and Lark are the same product on two isolated clouds (open.feishu.cn and open.larksuite.com) sharing one API surface, so a single implementation serves both.
Adapter implements im.Adapter and im.FileDownloader for WeChat personal accounts via the Tencent iLink Bot API.
Adapter implements im.Adapter and im.FileDownloader for WeChat personal accounts via the Tencent iLink Bot API.
WeCom Intelligent Bot long connection client.
WeCom Intelligent Bot long connection client.

Jump to

Keyboard shortcuts

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