Newer
Older
navi-1 / navi / core / events.py
"""Agent event dataclasses — emitted during run_stream() and forwarded to WebSocket clients."""

from dataclasses import dataclass, field


@dataclass
class ToolStarted:
    """Emitted immediately when a tool call begins, before execution completes."""

    tool_name: str
    arguments: dict
    is_subagent: bool = False   # True when emitted from inside run_ephemeral


@dataclass
class ToolEvent:
    """Emitted when a tool call finishes — carries the result."""

    tool_name: str
    arguments: dict
    result: str
    success: bool
    is_subagent: bool = False   # True when emitted from inside run_ephemeral
    metadata: dict = field(default_factory=dict)  # Extra data for client rendering


@dataclass
class TextDelta:
    """A chunk of text from the streaming LLM response."""

    delta: str


@dataclass
class ThinkingDelta:
    """A chunk of thinking/reasoning text from the streaming LLM response."""

    delta: str


@dataclass
class ThinkingEnd:
    """Marks the end of the thinking phase."""


@dataclass
class StreamEnd:
    """Marks the end of the streaming response."""

    full_content: str
    context_tokens: int | None = None   # total tokens used in this turn
    max_context_tokens: int = 0         # ollama_num_ctx from config
    elapsed_seconds: float | None = None
    tool_call_count: int = 0
    token_count: int | None = None      # same as context_tokens; kept separate for clarity


@dataclass
class StreamStopped:
    """Emitted when the user stops generation mid-stream (cooperative stop)."""


@dataclass
class ContextCompressed:
    """Emitted after context compression runs successfully."""

    messages_before: int
    messages_after: int
    summary: str = ""  # the actual summary text produced by the LLM


@dataclass
class ProfileSwitched:
    """Emitted by switch_profile tool when it successfully changes the session profile."""

    profile_id: str
    profile_name: str


@dataclass
class PlanningStatus:
    """Emitted at the start of each planning phase to show progress in the UI.

    phase: 1 = Analysis, 2 = Execution plan, 3 = Plan review (AIHelper critic).
    label: short human-readable description shown next to the spinner.
    is_subagent: True when emitted from inside run_ephemeral (subagent planning).
    """

    phase: int
    label: str
    is_subagent: bool = False


@dataclass
class PlanReady:
    """Emitted before the main agent loop when profile.planning_enabled is True.

    The plan text has already been injected into session.context as an assistant
    message so the LLM will see it and follow it during execution.
    is_subagent: True when emitted from inside run_ephemeral (subagent planning).
    """

    plan: str
    is_subagent: bool = False


@dataclass
class TurnThinking:
    """Full thinking/reasoning block from a tool-calling turn (complete() response).

    Unlike ThinkingDelta (which streams chunks), this carries the full text at once
    because complete() is non-streaming. Emitted before tool calls for that turn.
    is_subagent=True when emitted from run_ephemeral().
    """

    thinking: str
    is_subagent: bool = False


@dataclass
class SubagentComplete:
    """Internal: emitted by run_ephemeral into the parent sink to report metrics.
    Never forwarded to WebSocket clients."""

    token_count: int = 0
    tool_call_count: int = 0


@dataclass
class PlanningDebugData:
    """Internal: raw outputs from all planning phases, for debug storage in session.
    Never forwarded to WebSocket clients. Only emitted for the main agent (not subagents)."""

    log: dict  # {timestamp, result, phases: {1: {output, prompt_tokens, completion_tokens}, ...}}


@dataclass
class AIHelperTokensUsed:
    """Internal: emitted by AIHelper after each LLM call to report token usage.
    Never forwarded to WebSocket clients."""

    prompt_tokens: int = 0
    completion_tokens: int = 0

    @property
    def total(self) -> int:
        return self.prompt_tokens + self.completion_tokens


AgentEvent = (
    ToolStarted | ToolEvent | TextDelta | ThinkingDelta | ThinkingEnd
    | StreamEnd | StreamStopped | ContextCompressed | TurnThinking | ProfileSwitched
    | PlanningStatus | PlanReady | SubagentComplete | AIHelperTokensUsed | PlanningDebugData
)