package flyto // events.go - 引擎→消费者的事件流类型定义. // // 这套事件是引擎与所有消费层之间的唯一通信协议: // - TUI 消费层用它渲染终端 UI // - SDK 消费层用它输出 JSON // - HTTP 消费层用它推送 SSE // - ModelProvider 实现用它上报流式结果 // // 消费者通过 type switch 分发: // // for evt := range engine.Run(ctx, sid, msgs) { // switch e := evt.(type) { // case *flyto.TextDeltaEvent: // fmt.Print(e.Text) // case *flyto.DoneEvent: // fmt.Printf("总花费: $%.4f\n", e.TotalCostUSD) // } // } // Event is the interface all engine output events implement. Consumed via // the `<-chan flyto.Event` channel returned by Engine.Run / Session.Send; // consumers type-switch on concrete types (*TextDeltaEvent / *DoneEvent / // *CheckpointSuggestedEvent / *InboxMessageEvent / *TeammateMessageReceivedEvent // etc.) to dispatch. // // Shape: push (stream). The channel delivers events asynchronously; the // consumer ranges and does not reply. // // Event 是所有引擎输出事件的接口. 消费端从 Engine.Run / Session.Send 返回的 // `<-chan flyto.Event` channel 取事件, type-switch 到具体类型 // (*TextDeltaEvent / *DoneEvent / *CheckpointSuggestedEvent / *InboxMessageEvent // / *TeammateMessageReceivedEvent 等) 分发处理. // // 形态: 订阅 (stream). channel 异步推事件, 消费者 range 即可, 不回应. type Event interface { EventType() string } // --- 文本输出事件 --- // TextDeltaEvent 是流式文本增量(最频繁的事件). type TextDeltaEvent struct { Text string } func (e *TextDeltaEvent) EventType() string { return "text_delta" } // TextEvent 是完整的文本块(一个 content block 结束时发出). type TextEvent struct { Text string } func (e *TextEvent) EventType() string { return "text" } // --- 思考事件(仅支持 extended thinking 的 provider 会发出)--- // ThinkingDeltaEvent 是流式思考增量. type ThinkingDeltaEvent struct { Text string } func (e *ThinkingDeltaEvent) EventType() string { return "thinking_delta" } // ThinkingEvent 是完整的思考块. type ThinkingEvent struct { Text string } func (e *ThinkingEvent) EventType() string { return "thinking" } // --- 工具事件 --- // ToolUseEvent 表示模型请求调用工具. type ToolUseEvent struct { ID string // 工具调用 ID ToolName string // 工具名称 Input map[string]any // 工具输入参数 } func (e *ToolUseEvent) EventType() string { return "tool_use" } // ToolResultEvent 表示工具执行完成. // // Truncated / StoredPath surface the orchestrator's large-result handling // to consumers: when a tool emits an oversized output, the orchestrator // replaces Output with a short summary and persists the full content via // the consumer-installed ResultProcessor. Without these fields on the // event, UIs / SSE bridges see only the summary and cannot render a // "click to open full content" affordance or audit the full result. // // Truncated / StoredPath 把 orchestrator 的大结果处理透传给消费层: 工具产 // 生超大输出时, orchestrator 用短摘要替换 Output 并经消费层装的 // ResultProcessor 把完整内容落盘. 事件不带这两个字段时, UI / SSE bridge // 只看得到摘要, 无法渲染 "点击打开完整内容" 入口, 也无法审计完整结果. type ToolResultEvent struct { ID string // 对应 ToolUseEvent.ID ToolName string Output string IsError bool // Truncated marks that Output is a short summary and the full tool // result has been persisted to StoredPath. Consumer UIs use this to // render "result truncated -- click to open full content". // // Truncated 标记 Output 是短摘要, 完整结果已经落盘到 StoredPath. 消费层 // UI 据此渲染 "结果已截断 -- 点击打开完整内容". Truncated bool // StoredPath is where the full tool result was persisted (by the // orchestrator's pluggable ResultProcessor). Empty when Truncated is // false. Path shape is consumer-layer defined (local filesystem path, // sandbox-internal path, object-storage key, ...); callers parse per // the ResultProcessor they installed. // // SECURITY: the path may carry sandbox-internal structure or caller- // specific details. Downstream observers / SSE bridges MUST treat it // as untrusted data (e.g. avoid logging to shared destinations without // review). // // StoredPath 是完整工具结果的持久化位置 (由 orchestrator 的可插拔 // ResultProcessor 决定). Truncated=false 时为空串. Path shape 由消费层 // 定义 (本地文件路径 / sandbox 内部路径 / 对象存储 key / ...); 调用方按 // 自己装的 ResultProcessor 解析. // // 安全: path 可能携带 sandbox 内部结构或调用方特定细节. 下游 observer / // SSE bridge 必须把它当作不可信数据 (例如未审查前不写入共享目的地). StoredPath string } func (e *ToolResultEvent) EventType() string { return "tool_result" } // ToolProgressEvent 表示工具执行中的进度更新(长时间运行的工具). type ToolProgressEvent struct { ID string ToolName string Progress float64 // 0.0-1.0 Detail string } func (e *ToolProgressEvent) EventType() string { return "tool_progress" } // --- 权限事件 --- // PermissionRequestEvent 表示工具需要用户批准. // 消费层收到此事件后展示权限 UI,通过 Session.ResolvePermission() 回复. type PermissionRequestEvent struct { ID string ToolName string Input map[string]any Message string } func (e *PermissionRequestEvent) EventType() string { return "permission_request" } // --- 会话事件 --- // TurnStartEvent 表示新对话轮次开始. type TurnStartEvent struct { TurnNumber int Model string ContextWindowTokens int // 由 provider.Models() 提供,消费层无需硬编码 } func (e *TurnStartEvent) EventType() string { return "turn_start" } // TurnEndEvent 表示对话轮次结束(含本轮 token 用量). type TurnEndEvent struct { TurnNumber int InputTokens int OutputTokens int CostUSD float64 MaxTokens int } func (e *TurnEndEvent) EventType() string { return "turn_end" } // CompactEvent 表示上下文被压缩. type CompactEvent struct { Summary string TokensBefore int TokensAfter int } func (e *CompactEvent) EventType() string { return "compact" } // --- 终止事件 --- // DoneEvent 表示 Agent 运行结束. type DoneEvent struct { TotalInputTokens int TotalOutputTokens int TotalCostUSD float64 TurnCount int // Reason 非空表示受控终止(如 "pre_sampling_blocked"),空字符串表示正常结束. Reason string } func (e *DoneEvent) EventType() string { return "done" } // ErrorEvent 表示运行出错. type ErrorEvent struct { Err error Code string Suggestion string Retryable bool } func (e *ErrorEvent) EventType() string { return "error" } func (e *ErrorEvent) Error() string { return e.Err.Error() } // --- 会话信息事件 --- // SessionInfoEvent 通知消费层更新会话统计(token 用量,花费等). type SessionInfoEvent struct { SessionID string Title string TurnCount int InputTokens int OutputTokens int CostUSD float64 } func (e *SessionInfoEvent) EventType() string { return "session_info" } // --- 权限学习事件 --- // PermissionLearnEvent 建议添加永久权限规则(用户反复批准同类操作时触发). type PermissionLearnEvent struct { Rules []PermissionLearnRule } // PermissionLearnRule 是一条建议的权限规则. type PermissionLearnRule struct { RuleString string Description string } func (e *PermissionLearnEvent) EventType() string { return "permission_learn" } // --- 警告事件 --- // WarningEvent 表示非致命警告(token 接近上限,花费接近预算等). type WarningEvent struct { Code string Message string Detail string } func (e *WarningEvent) EventType() string { return "warning" } const ( WarnTokenUsageHigh = "token_usage_high" WarnBudgetNearLimit = "budget_near_limit" WarnSessionExpiring = "session_expiring" ) // --- 工具摘要事件 --- // ToolSummaryEvent 表示工具执行摘要已异步生成(用于 UI 展示). type ToolSummaryEvent struct { ID string ToolName string Summary string // 5-20 字 } func (e *ToolSummaryEvent) EventType() string { return "tool_summary" } // --- 斜杠命令事件 --- // SlashCommandEvent 表示检测到斜杠命令(引擎不处理,通知消费层). type SlashCommandEvent struct { Name string Args string } func (e *SlashCommandEvent) EventType() string { return "slash_command" } // --- Checkpoint 事件 --- // CheckpointEvent 表示引擎在执行不可逆操作前暂停,等待外部确认. type CheckpointEvent struct { ToolCallID string ToolName string Input map[string]any Message string } func (e *CheckpointEvent) EventType() string { return "checkpoint" } // CheckpointSuggestedEvent 是引擎的主动建议--检测到高风险操作模式时推送, // 但不阻塞执行(与 CheckpointEvent 不同:CheckpointEvent 是工具声明的强制拦截, // CheckpointSuggestedEvent 是引擎的静态分析建议,最终决定权仍在消费层). // // 升华改进(ELEVATED): 早期设计 "Human checkpoint" 仅靠提示词引导模型自主提示用户, // 依赖模型遵从,无法覆盖所有 Provider. // 我们在引擎层做静态模式匹配:无论底层是哪个模型,只要检测到高风险命令就主动通知消费层. // 消费层(TUI/SDK)可选择展示警告,弹出确认框,或忽略(如果用户已在 FLYTO.md 中授权). // // 替代方案:<在权限系统中拦截> - 否决:权限系统面向"是否允许执行该类工具"(二元决策), // 这里是"建议消费层展示额外确认"(建议而非强制),语义不同,不应混入权限系统. type CheckpointSuggestedEvent struct { ToolCallID string // 触发建议的工具调用 ID ToolName string // 工具名("Bash" / "FileEdit" / "FileWrite" 等) Input map[string]any // 工具输入(用于消费层展示) RiskReason string // 高风险原因(英文,方便日志分析) RiskPattern string // 匹配到的风险模式(如 "rm -rf" / "drop table" / "overwrite") } func (e *CheckpointSuggestedEvent) EventType() string { return "checkpoint_suggested" } // --- Inbox 事件 --- // InboxMessageEvent 表示工具子进程通过 UDS 发来的消息. type InboxMessageEvent struct { Type string // "progress" / "log" / "result" ToolUseID string Data string Meta string // JSON 格式的额外元数据 } func (e *InboxMessageEvent) EventType() string { return "inbox_message" } // --- Agent Teams 事件 (peer-to-peer 通讯) --- // TeammateMessageReceivedEvent 表示 Agent Teams 通讯中收到来自同伴的消息. // // 升华改进(ELEVATED): 复用 flyto.EventObserver 统一事件总线, 不再新建 hook 系统. // 对标 Anthropic Claude Code Agent Teams v2.1.32 的 TeammateIdle/TaskCreated/TaskCompleted // 三个独立 shell hook -- 我们用单一事件总线 (类型字段区分) 覆盖其超集: // - 可编程性: Observer 实现任意 Go 逻辑 (调 DB / 发 Slack / 更新 UI), 不限于 shell // - 跨进程: SaaS 场景订阅 Event 的不必是本机, shell hook 做不到 // - 结构化: Data 字段是 struct, 解析无歧义; shell 拿到字符串参数 // 替代方案: <仿 Anthropic 三个独立 shell hook> - 否决: L1326 刚把 hooks/context/evolve // 三包 Observer 收敛到 flyto.EventObserver, 再拆回去是架构回退. type TeammateMessageReceivedEvent struct { From string // 发送方 agent 名称 To string // 接收方 agent 名称 (自己) Type string // Message.Type (task_assignment / idle_notification 等) MessageID string // Message.ID PayloadSize int // payload 字节数 } func (e *TeammateMessageReceivedEvent) EventType() string { return "teammate_message_received" } // --- Provider 内部用量事件 --- // UsageEvent 由 ModelProvider 推送,携带本轮原始 token 用量. // // 精妙之处(CLEVER): provider 只汇报原始数字,不计算费用-- // 费用计算需要模型定价表,而定价表属于引擎层(通过 Models() 获取). // 分层清晰:provider 知道 token 数,引擎知道单价,两者在引擎层合并. // 如果 provider 自己计费,定价表就得维护两份,数据来源单一原则被破坏. // // 消费者不应依赖此事件--引擎会将其转换为 TurnEndEvent. // 直接使用 provider(不经过引擎)时才需要读取此事件. type UsageEvent struct { InputTokens int OutputTokens int CacheReadTokens int // prompt caching 命中的 token 数(仅支持缓存的 provider) CacheCreationTokens int // prompt caching 写入的 token 数(仅支持缓存的 provider) StopReason string // "end_turn"/"max_tokens"/"tool_use"/"stop_sequence"/"length" } func (e *UsageEvent) EventType() string { return "usage" }