Documentation
¶
Index ¶
- Constants
- Variables
- func BuildIMAgentStreamRaw(parts IMStreamParts, agentInProgress bool) string
- func FormatIMDisplayContent(raw string, phase StreamDisplayPhase) string
- func FormatIMFinalFromParts(parts IMStreamParts) string
- func FormatIMIntermediateFromParts(parts IMStreamParts, agentInProgress bool) string
- func FormatIMRagPipelineLine(step IMToolStep) string
- func FormatIMToolLine(step IMToolStep) string
- func GetBool(creds map[string]any, key string) bool
- func GetString(creds map[string]any, key string) string
- func IsRAGPipelineToolName(name string) bool
- func LooksLikeCommand(content string) bool
- func ParseCredentials(data []byte) (map[string]any, error)
- func ResolveMode(channel *IMChannel, def string) string
- func RunSupervised(ctx context.Context, cfg SupervisorConfig)
- func StripThinkBlocks(content string) string
- func TransformThinkBlocks(content string, style ThinkBlockStyle) string
- func WrapThinkBlock(inner string) string
- type Adapter
- type AdapterFactory
- type ChannelSession
- type ChannelWithAgent
- type ChatType
- type ClearCommand
- type Command
- type CommandAction
- type CommandContext
- type CommandRegistry
- type CommandResult
- type FileDownloader
- type FullOutputProgressSender
- type HelpCommand
- type IMChannel
- type IMChannelSummary
- type IMStreamMode
- type IMStreamParts
- type IMToolStep
- type IncomingMessage
- type InfoCommand
- type MessageType
- type Platform
- type QueueMetrics
- type QuotedMessage
- type ReplyMessage
- type SearchCommand
- type Service
- func (s *Service) CreateChannel(channel *IMChannel) error
- func (s *Service) DeleteChannel(channelID string, tenantID uint64) error
- func (s *Service) DeleteChannelsByAgent(agentID string, tenantID uint64) error
- func (s *Service) EnsureChannelAdapter(channelID string) (Adapter, *IMChannel, error)
- func (s *Service) GetChannelAdapter(channelID string) (Adapter, *IMChannel, bool)
- func (s *Service) GetChannelByID(channelID string) (*IMChannel, error)
- func (s *Service) GetChannelByIDAndTenant(channelID string, tenantID uint64) (*IMChannel, error)
- func (s *Service) HandleMessage(ctx context.Context, msg *IncomingMessage, channelID string) error
- func (s *Service) ListChannelsByAgent(agentID string, tenantID uint64) ([]IMChannel, error)
- func (s *Service) ListChannelsByTenant(ctx context.Context, tenantID uint64) ([]ChannelWithAgent, error)
- func (s *Service) LoadAndStartChannels() error
- func (s *Service) RegisterAdapterFactory(platform string, factory AdapterFactory)
- func (s *Service) SetChannelAgentID(ctx context.Context, channel *IMChannel, agentID string) error
- func (s *Service) SetChannelKnowledgeBaseID(ctx context.Context, channel *IMChannel, kbID string) error
- func (s *Service) StartChannel(channel *IMChannel) error
- func (s *Service) Stop()
- func (s *Service) StopChannel(channelID string)
- func (s *Service) ToggleChannel(channelID string, tenantID uint64) (*IMChannel, error)
- func (s *Service) UpdateChannel(channel *IMChannel) error
- type SessionMode
- type StopCommand
- type StreamDisplayPhase
- type StreamSender
- type SupervisorConfig
- type ThinkBlockStyle
Constants ¶
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 ¶
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.
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.
var RAGThinkingStyle = ThinkBlockStyle{
ThinkingHeader: "> 💭 **思考中...**\n",
ThoughtHeader: "> 💭 **思考**\n",
LinePrefix: "> ",
LineSuffix: "",
Separator: "\n---\n\n",
}
RAGThinkingStyle matches Web RagPipelineProgress thinking row (agent.think).
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 ¶
GetBool reads a boolean from JSON credentials (bool, string "true"/"1", or non-zero number).
func IsRAGPipelineToolName ¶
IsRAGPipelineToolName reports whether a tool is part of the quick-QA progress UI.
func LooksLikeCommand ¶
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 ¶
ParseCredentials parses the JSONB credentials field into a map.
func ResolveMode ¶
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 ¶
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 ¶
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 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 (c *ClearCommand) Execute(_ context.Context, _ *CommandContext, _ []string) (*CommandResult, error)
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) BeforeSave ¶
BeforeSave ensures bot_identity is recomputed and session_mode is validated on every save (create + update).
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 ¶
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 ¶
DeleteChannel soft-deletes a channel and stops it. Only deletes if the channel belongs to the given tenant.
func (*Service) DeleteChannelsByAgent ¶
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 ¶
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 ¶
GetChannelAdapter returns the adapter and channel config for a given channel ID.
func (*Service) GetChannelByID ¶
GetChannelByID loads a channel from the database.
func (*Service) GetChannelByIDAndTenant ¶
GetChannelByIDAndTenant loads a channel from the database, scoped to a specific tenant.
func (*Service) HandleMessage ¶
HandleMessage processes an incoming IM message end-to-end using channel config.
func (*Service) ListChannelsByAgent ¶
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 ¶
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 ¶
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 ¶
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 ¶
StopChannel stops and removes a running channel.
func (*Service) ToggleChannel ¶
ToggleChannel enables or disables a channel. Only toggles if the channel belongs to the given tenant.
func (*Service) UpdateChannel ¶
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.
Source Files
¶
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. |