Files
EvoScientist-Multi/EvoScientist/stream/emitter.py
T
jfilipiuk b5b01d50c2 feat: agent-teams part A - TUI panel visibility + DELEGATION_STRATEGY routing (#369)
* feat: document sync / QuickJS panel / async dispatch modes in DELEGATION_STRATEGY

* feat: surface QuickJS panel dispatches in TUI

* fix: finalize running panel dispatches on turn cancellation

* fix: replace panel widget cancel path with public finalize API

* fix: drop dead isinstance guard on panel dispatch duration_ms

* fix: re-arm panel widget when a new dispatch arrives after finalize

* fix: stop panel timer when finalize_running finds zero running rows

* fix: register panel widget in cleanup dict before awaiting mount
2026-08-07 17:07:01 +01:00

286 lines
8.1 KiB
Python

"""
StreamEventEmitter - standardized event format.
All events contain a type and associated data dict.
"""
from dataclasses import dataclass
from typing import Any
@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 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 panel_dispatch_start(
*,
eval_id: str,
dispatch_id: str,
subagent_type: str,
label: str,
description: str,
) -> StreamEvent:
"""A ``task()`` dispatch inside a ``code_interpreter`` eval started.
Sourced from ``langchain_quickjs``'s custom-stream subagent events.
Distinct family from ``subagent_start`` because the semantics differ:
short-lived expert dispatch inside a QuickJS eval, grouped by
``eval_id`` (parent ``code_interpreter`` tool_call_id), not a full
nested subagent turn.
"""
return StreamEvent(
"panel_dispatch_start",
{
"type": "panel_dispatch_start",
"eval_id": eval_id,
"id": dispatch_id,
"subagent_type": subagent_type,
"label": label,
"description": description,
},
)
@staticmethod
def panel_dispatch_complete(
*,
eval_id: str,
dispatch_id: str,
duration_ms: int,
) -> StreamEvent:
"""A ``task()`` dispatch inside a ``code_interpreter`` eval finished OK."""
return StreamEvent(
"panel_dispatch_complete",
{
"type": "panel_dispatch_complete",
"eval_id": eval_id,
"id": dispatch_id,
"duration_ms": duration_ms,
},
)
@staticmethod
def panel_dispatch_error(
*,
eval_id: str,
dispatch_id: str,
duration_ms: int,
error: str,
) -> StreamEvent:
"""A ``task()`` dispatch inside a ``code_interpreter`` eval raised."""
return StreamEvent(
"panel_dispatch_error",
{
"type": "panel_dispatch_error",
"eval_id": eval_id,
"id": dispatch_id,
"duration_ms": duration_ms,
"error": error,
},
)
@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) -> StreamEvent:
"""Error event."""
return StreamEvent("error", {"type": "error", "message": message})