From f08a4a90c63525401671231e262fd6c45e9c1bff Mon Sep 17 00:00:00 2001 From: weichen Date: Fri, 2 Oct 2026 02:11:42 +0000 Subject: [PATCH] Add streaming events for in-flight tool calls Why: The client only learned about a tool call once execution started, so long argument generation looked like a frozen cursor. Evidence: go test ./... Impact: New tool.call.delta and tool.output events; tool.result may include durationMs. Unknown event types stay ignored. Refs: feature/stream-display-overhaul Co-authored-by: Cursor --- events.go | 39 +++++++++++++++++++++++++++++---------- ts/types.ts | 6 +++++- 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/events.go b/events.go index 73c9f19..4a688bd 100644 --- a/events.go +++ b/events.go @@ -8,13 +8,13 @@ import ( // 事件信封:后端 → 前端的唯一增量通道(SSE),持久化后转发,Seq 在会话内单调。 // 消费方对未知 Type 静默忽略(原则 6 前向兼容)。 // 项目流(D50):项目频道事件以伪会话键 "project:" 复用同一机制 -//(独立 seq、持久化、断线重放),信封以 ProjectID 标识归属(会话流事件为空)。 +// (独立 seq、持久化、断线重放),信封以 ProjectID 标识归属(会话流事件为空)。 type StreamEvent struct { Seq int64 `json:"seq"` RunID ID `json:"runId,omitempty"` ConversationID ID `json:"conversationId"` ProjectID ID `json:"projectID,omitempty"` // 项目流事件的项目归属(D50;只增不删) - TS string `json:"ts"` // RFC3339 + TS string `json:"ts"` // RFC3339 V int `json:"v"` Type StreamEventType `json:"type"` Payload json.RawMessage `json:"payload,omitempty"` @@ -38,7 +38,9 @@ func ProjectFromChannel(key ID) (ID, bool) { const ( EvMessageDelta StreamEventType = "message.delta" EvThinkingDelta StreamEventType = "thinking.delta" + EvToolCallDelta StreamEventType = "tool.call.delta" // 参数增量:工具开始执行前即可上屏 EvToolCall StreamEventType = "tool.call" + EvToolOutput StreamEventType = "tool.output" // 执行中的 stdout/stderr 增量 EvToolResult StreamEventType = "tool.result" EvRunPhase StreamEventType = "run.phase" EvHITLRequest StreamEventType = "hitl.request" @@ -82,11 +84,28 @@ type ToolCallPayload struct { Arguments string `json:"arguments,omitempty"` } +// ToolCallDeltaPayload 是模型正在编写的工具参数增量。Index 标识同轮第几次调用 +// (与供应商 tool_calls 下标一致);Name/CallID 往往只在首个分片出现。 +type ToolCallDeltaPayload struct { + Index int `json:"index"` + CallID string `json:"callId,omitempty"` + Name string `json:"name,omitempty"` + Delta string `json:"delta"` +} + +// ToolOutputPayload 是工具执行过程中的输出增量。Stream 为 stdout 或 stderr。 +type ToolOutputPayload struct { + CallID string `json:"callId"` + Stream string `json:"stream"` + Text string `json:"text"` +} + type ToolResultPayload struct { - CallID string `json:"callId"` - Status string `json:"status"` - Summary string `json:"summary,omitempty"` - Truncated bool `json:"truncated,omitempty"` + CallID string `json:"callId"` + Status string `json:"status"` + Summary string `json:"summary,omitempty"` + Truncated bool `json:"truncated,omitempty"` + DurationMs float64 `json:"durationMs,omitempty"` } type RunPhasePayload struct { @@ -115,15 +134,15 @@ type AgentSpawnPayload struct { } type AgentProgressPayload struct { - AgentRun ID `json:"agentRun"` - Note string `json:"note,omitempty"` + AgentRun ID `json:"agentRun"` + Note string `json:"note,omitempty"` Phase RunPhase `json:"phase,omitempty"` } type AgentResultPayload struct { - AgentRun ID `json:"agentRun"` + AgentRun ID `json:"agentRun"` Status RunStatus `json:"status"` - Summary string `json:"summary,omitempty"` + Summary string `json:"summary,omitempty"` // Text 为子代理完整文本产出(squad 步骤交付物用;截断摘要见 Summary)。 Text string `json:"text,omitempty"` } diff --git a/ts/types.ts b/ts/types.ts index ef43c74..f0b3dd6 100644 --- a/ts/types.ts +++ b/ts/types.ts @@ -79,7 +79,8 @@ export interface TodoList { // ---- 流式事件 ---- export type StreamEventType = - | 'message.delta' | 'thinking.delta' | 'tool.call' | 'tool.result' + | 'message.delta' | 'thinking.delta' + | 'tool.call.delta' | 'tool.call' | 'tool.output' | 'tool.result' | 'run.phase' | 'hitl.request' | 'hitl.resolved' | 'plan.update' | 'todo.update' | 'agent.spawn' | 'agent.progress' | 'agent.result' | 'artifact.created' | 'artifact.updated' @@ -93,6 +94,9 @@ export interface StreamEvent

{ // payload 形状(按 type 判别;未知字段忽略) export interface MessageDeltaPayload { index: number; text: string } +export interface ToolCallDeltaPayload { index: number; callId?: string; name?: string; delta: string } +export interface ToolOutputPayload { callId: string; stream: 'stdout' | 'stderr'; text: string } +export interface ToolResultPayload { callId: string; status: string; summary?: string; truncated?: boolean; durationMs?: number } export interface AgentSpawnPayload { agentRun: ID; name: string; task: string } export interface AgentProgressPayload { agentRun: ID; note?: string; phase?: RunPhase } export interface AgentResultPayload { agentRun: ID; status: RunStatus; summary?: string }