253 lines
7.1 KiB
Python
253 lines
7.1 KiB
Python
"""
|
|
StreamEventEmitter - standardized event format.
|
|
|
|
All events contain a type and associated data dict.
|
|
"""
|
|
|
|
from dataclasses import dataclass
|
|
from typing import Any
|
|
|
|
STREAM_PROTOCOL_CAPABILITIES = frozenset(
|
|
{
|
|
"complete_tool_call_v1",
|
|
"correlated_tool_call_id_v1",
|
|
"final_invalid_tool_call_v1",
|
|
}
|
|
)
|
|
|
|
|
|
@dataclass
|
|
class StreamEvent:
|
|
"""Unified stream event."""
|
|
|
|
type: str
|
|
data: dict[str, Any]
|
|
|
|
|
|
class StreamEventEmitter:
|
|
"""Stream event emitter - creates standardized event dicts."""
|
|
|
|
@staticmethod
|
|
def thinking(content: str, thinking_id: int = 0) -> StreamEvent:
|
|
"""Thinking content event."""
|
|
return StreamEvent(
|
|
"thinking", {"type": "thinking", "content": content, "id": thinking_id}
|
|
)
|
|
|
|
@staticmethod
|
|
def reasoning_summary(summary: str) -> StreamEvent:
|
|
"""Provider-declared user-visible reasoning summary."""
|
|
return StreamEvent(
|
|
"reasoning_summary",
|
|
{
|
|
"type": "reasoning_summary",
|
|
"summary": summary,
|
|
"visibility": "user_visible_summary",
|
|
"source_kind": "provider_summary",
|
|
},
|
|
)
|
|
|
|
@staticmethod
|
|
def text(content: str) -> StreamEvent:
|
|
"""Text content event."""
|
|
return StreamEvent("text", {"type": "text", "content": content})
|
|
|
|
@staticmethod
|
|
def tool_call(name: str, args: dict[str, Any], tool_id: str) -> StreamEvent:
|
|
"""Tool call event."""
|
|
return StreamEvent(
|
|
"tool_call",
|
|
{"type": "tool_call", "name": name, "args": args, "id": tool_id},
|
|
)
|
|
|
|
@staticmethod
|
|
def tool_result(
|
|
name: str,
|
|
content: str,
|
|
success: bool,
|
|
tool_call_id: str,
|
|
) -> StreamEvent:
|
|
"""Tool result event."""
|
|
return StreamEvent(
|
|
"tool_result",
|
|
{
|
|
"type": "tool_result",
|
|
"name": name,
|
|
"content": content,
|
|
"success": success,
|
|
"id": tool_call_id,
|
|
},
|
|
)
|
|
|
|
@staticmethod
|
|
def subagent_start(
|
|
name: str, description: str, instance_id: str, tool_call_id: str
|
|
) -> StreamEvent:
|
|
"""Sub-agent delegation started."""
|
|
return StreamEvent(
|
|
"subagent_start",
|
|
{
|
|
"type": "subagent_start",
|
|
"name": name,
|
|
"description": description,
|
|
"instance_id": instance_id,
|
|
"tool_call_id": tool_call_id,
|
|
},
|
|
)
|
|
|
|
@staticmethod
|
|
def subagent_tool_call(
|
|
subagent: str,
|
|
name: str,
|
|
args: dict[str, Any],
|
|
tool_id: str,
|
|
instance_id: str,
|
|
) -> StreamEvent:
|
|
"""Tool call from inside a sub-agent."""
|
|
return StreamEvent(
|
|
"subagent_tool_call",
|
|
{
|
|
"type": "subagent_tool_call",
|
|
"subagent": subagent,
|
|
"name": name,
|
|
"args": args,
|
|
"id": tool_id,
|
|
"instance_id": instance_id,
|
|
},
|
|
)
|
|
|
|
@staticmethod
|
|
def subagent_tool_result(
|
|
subagent: str,
|
|
name: str,
|
|
content: str,
|
|
success: bool,
|
|
tool_call_id: str,
|
|
instance_id: str,
|
|
) -> StreamEvent:
|
|
"""Tool result from inside a sub-agent."""
|
|
return StreamEvent(
|
|
"subagent_tool_result",
|
|
{
|
|
"type": "subagent_tool_result",
|
|
"subagent": subagent,
|
|
"name": name,
|
|
"content": content,
|
|
"success": success,
|
|
"id": tool_call_id,
|
|
"instance_id": instance_id,
|
|
},
|
|
)
|
|
|
|
@staticmethod
|
|
def subagent_text(subagent: str, content: str, instance_id: str) -> StreamEvent:
|
|
"""Text content from a sub-agent."""
|
|
return StreamEvent(
|
|
"subagent_text",
|
|
{
|
|
"type": "subagent_text",
|
|
"subagent": subagent,
|
|
"content": content,
|
|
"instance_id": instance_id,
|
|
},
|
|
)
|
|
|
|
@staticmethod
|
|
def subagent_end(name: str, instance_id: str) -> StreamEvent:
|
|
"""Sub-agent delegation completed."""
|
|
return StreamEvent(
|
|
"subagent_end",
|
|
{"type": "subagent_end", "name": name, "instance_id": instance_id},
|
|
)
|
|
|
|
@staticmethod
|
|
def done(response: str = "") -> StreamEvent:
|
|
"""Done event."""
|
|
return StreamEvent(
|
|
"done", {"type": "done", "content": response, "response": response}
|
|
)
|
|
|
|
@staticmethod
|
|
def usage_stats(input_tokens: int, output_tokens: int) -> StreamEvent:
|
|
"""Token usage statistics event."""
|
|
return StreamEvent(
|
|
"usage_stats",
|
|
{
|
|
"type": "usage_stats",
|
|
"input_tokens": input_tokens,
|
|
"output_tokens": output_tokens,
|
|
},
|
|
)
|
|
|
|
@staticmethod
|
|
def interrupt(
|
|
interrupt_id: str,
|
|
action_requests: list,
|
|
review_configs: list | None = None,
|
|
) -> StreamEvent:
|
|
"""Human-in-the-loop interrupt event."""
|
|
return StreamEvent(
|
|
"interrupt",
|
|
{
|
|
"type": "interrupt",
|
|
"interrupt_id": interrupt_id,
|
|
"action_requests": action_requests,
|
|
"review_configs": review_configs or [],
|
|
},
|
|
)
|
|
|
|
@staticmethod
|
|
def ask_user_interrupt(
|
|
interrupt_id: str,
|
|
questions: list,
|
|
tool_call_id: str = "",
|
|
) -> StreamEvent:
|
|
"""Agent-initiated ask_user interrupt event."""
|
|
return StreamEvent(
|
|
"ask_user",
|
|
{
|
|
"type": "ask_user",
|
|
"interrupt_id": interrupt_id,
|
|
"questions": questions,
|
|
"tool_call_id": tool_call_id,
|
|
},
|
|
)
|
|
|
|
@staticmethod
|
|
def tool_selection(tools: list[str]) -> StreamEvent:
|
|
"""Tool selection event from LLMToolSelectorMiddleware."""
|
|
return StreamEvent("tool_selection", {"type": "tool_selection", "tools": tools})
|
|
|
|
@staticmethod
|
|
def summarization(content: str) -> StreamEvent:
|
|
"""Context summarization event."""
|
|
return StreamEvent(
|
|
"summarization", {"type": "summarization", "content": content}
|
|
)
|
|
|
|
@staticmethod
|
|
def summarization_start() -> StreamEvent:
|
|
"""Context summarization started."""
|
|
return StreamEvent(
|
|
"summarization_start",
|
|
{"type": "summarization_start"},
|
|
)
|
|
|
|
@staticmethod
|
|
def error(
|
|
message: str,
|
|
*,
|
|
code: str | None = None,
|
|
recoverable: bool | None = None,
|
|
details: dict[str, Any] | None = None,
|
|
) -> StreamEvent:
|
|
"""Error event."""
|
|
data: dict[str, Any] = {"type": "error", "message": message}
|
|
if code is not None:
|
|
data["code"] = code
|
|
if recoverable is not None:
|
|
data["recoverable"] = recoverable
|
|
if details is not None:
|
|
data["details"] = details
|
|
return StreamEvent("error", data)
|