yao/agent/robot/events/events.go
Max ea9e070f29 Enhance robot integration with Telegram and improve event handling
- Add Telegram integration support by introducing a dispatcher for handling Telegram events and messages.
- Implement event notifications for robot configuration changes (creation, update, deletion) to facilitate integration with external services.
- Refactor the robot initialization process to load robots into cache and start the dispatcher, improving the overall system setup.
- Update the delivery event structure to include additional metadata for better context during message handling.
- Enhance logging capabilities for better observability during robot execution and event processing.
2026-03-01 22:03:25 +08:00

194 lines
6.4 KiB
Go

package events
import (
"context"
"strings"
"sync"
agentcontext "github.com/yaoapp/yao/agent/context"
robottypes "github.com/yaoapp/yao/agent/robot/types"
)
// TriggerFunc is the callback for triggering a robot execution.
// Injected by the api package at startup to break the import cycle.
// Returns (executionID, accepted, error).
type TriggerFunc func(ctx *robottypes.Context, memberID string, triggerType robottypes.TriggerType, data interface{}) (string, bool, error)
// ReplyFunc is the callback for replying to the originating channel.
// Injected by the dispatcher at startup. The implementation routes the
// reply to the correct adapter based on metadata.Channel.
// msg is the standard assistant message (text, Content Parts, media, etc.).
type ReplyFunc func(ctx context.Context, msg *agentcontext.Message, metadata *MessageMetadata) error
var (
triggerFn TriggerFunc
triggerFnMu sync.RWMutex
replyFn ReplyFunc
replyFnMu sync.RWMutex
)
// RegisterTriggerFunc sets the function used by handleMessage to trigger
// robot execution when a confirmed action is detected.
func RegisterTriggerFunc(fn TriggerFunc) {
triggerFnMu.Lock()
defer triggerFnMu.Unlock()
triggerFn = fn
}
func getTriggerFunc() TriggerFunc {
triggerFnMu.RLock()
defer triggerFnMu.RUnlock()
return triggerFn
}
// RegisterReplyFunc sets the function used by handleMessage to reply
// to the originating channel after processing.
func RegisterReplyFunc(fn ReplyFunc) {
replyFnMu.Lock()
defer replyFnMu.Unlock()
replyFn = fn
}
func getReplyFunc() ReplyFunc {
replyFnMu.RLock()
defer replyFnMu.RUnlock()
return replyFn
}
// Robot event type constants for event.Push integration.
// Events are fire-and-forget; handlers are registered via event.Register().
const (
TaskNeedInput = "robot.task.need_input"
TaskFailed = "robot.task.failed"
TaskCompleted = "robot.task.completed"
ExecWaiting = "robot.exec.waiting"
ExecResumed = "robot.exec.resumed"
ExecCompleted = "robot.exec.completed"
ExecFailed = "robot.exec.failed"
ExecCancelled = "robot.exec.cancelled"
Delivery = "robot.delivery"
Message = "robot.message"
)
// Robot configuration change events (used by integrations Receiver).
const (
RobotConfigCreated = "robot.config.created"
RobotConfigUpdated = "robot.config.updated"
RobotConfigDeleted = "robot.config.deleted"
)
// NeedInputPayload is the event payload for TaskNeedInput / ExecWaiting events.
type NeedInputPayload struct {
ExecutionID string `json:"execution_id"`
MemberID string `json:"member_id"`
TeamID string `json:"team_id"`
TaskID string `json:"task_id"`
Question string `json:"question"`
ChatID string `json:"chat_id,omitempty"`
}
// ExecPayload is a generic execution event payload.
type ExecPayload struct {
ExecutionID string `json:"execution_id"`
MemberID string `json:"member_id"`
TeamID string `json:"team_id"`
Status string `json:"status,omitempty"`
Error string `json:"error,omitempty"`
ChatID string `json:"chat_id,omitempty"`
}
// TaskPayload is the event payload for TaskFailed / TaskCompleted events.
type TaskPayload struct {
ExecutionID string `json:"execution_id"`
MemberID string `json:"member_id"`
TeamID string `json:"team_id"`
TaskID string `json:"task_id"`
Error string `json:"error,omitempty"`
ChatID string `json:"chat_id,omitempty"`
}
// DeliveryPayload is the event payload for Delivery events.
type DeliveryPayload struct {
ExecutionID string `json:"execution_id"`
MemberID string `json:"member_id"`
TeamID string `json:"team_id"`
ChatID string `json:"chat_id,omitempty"`
Content *robottypes.DeliveryContent `json:"content,omitempty"`
Preferences *robottypes.DeliveryPreferences `json:"preferences,omitempty"`
Extra map[string]any `json:"extra,omitempty"`
}
// MessagePayload is the event payload for Message events (external channel messages).
type MessagePayload struct {
RobotID string `json:"robot_id"`
Messages []agentcontext.Message `json:"messages"`
Metadata *MessageMetadata `json:"metadata"`
}
// MessageMetadata carries channel-specific information for routing and deduplication.
type MessageMetadata struct {
Channel string `json:"channel"`
MessageID string `json:"message_id,omitempty"`
AppID string `json:"app_id,omitempty"`
ChatID string `json:"chat_id,omitempty"`
SenderID string `json:"sender_id,omitempty"`
SenderName string `json:"sender_name,omitempty"`
Locale string `json:"locale,omitempty"`
ReplyTo string `json:"reply_to,omitempty"`
Extra map[string]any `json:"extra,omitempty"`
}
// MessageResult is the result returned from handleMessage via event.Call.
type MessageResult struct {
Message *agentcontext.Message `json:"message,omitempty"`
Action *ActionResult `json:"action,omitempty"`
ExecutionID string `json:"execution_id,omitempty"`
Metadata *MessageMetadata `json:"metadata,omitempty"`
}
// ActionResult describes a detected action from the Host Agent's Next hook.
type ActionResult struct {
Name string `json:"name"`
Payload any `json:"payload,omitempty"`
}
// RobotConfigPayload is the event payload for robot.config.* events.
type RobotConfigPayload struct {
MemberID string `json:"member_id"`
TeamID string `json:"team_id"`
}
// NormalizeLocale converts various language code formats (IETF BCP 47, etc.)
// into the lowercase hyphenated form used by agentcontext (e.g. "zh-cn", "en-us").
//
// Mapping rules:
//
// "zh-hans", "zh-cn" → "zh-cn"
// "zh-hant", "zh-tw", "zh-hk" → "zh-tw"
// "zh" → "zh-cn"
// "en-us" → "en-us"
// "en-gb" → "en-gb"
// "en" → "en"
// "" → "en" (default)
// other → lowercased as-is
func NormalizeLocale(raw string) string {
code := strings.ToLower(strings.TrimSpace(raw))
if code == "" {
return "en"
}
// Normalize underscore to hyphen (e.g. zh_CN → zh-cn)
code = strings.ReplaceAll(code, "_", "-")
switch code {
case "zh-hans", "zh-cn":
return "zh-cn"
case "zh-hant", "zh-tw", "zh-hk":
return "zh-tw"
case "zh":
return "zh-cn"
default:
return code
}
}