Current section
Files
Jump to
Current section
Files
lib/cmdc_tui/event_router.ex
defmodule CmdcTui.EventRouter do
@moduledoc """
CMDC EventBus 事件路由器。
将 `{:cmdc_event, session_id, event}` 中的 event 映射到 State 更新。
"""
alias CmdcTui.State
@doc "将 CMDC 事件路由到 State 更新。"
@spec route(term(), State.t()) :: State.t()
# -----
# 生命周期
# -----
def route(:agent_start, state) do
%{state | agent_status: :running, current_streaming: "", current_thinking: ""}
end
def route({:agent_end, _messages, token_usage}, state) do
state
|> State.update_tokens(token_usage)
|> Map.put(:agent_status, :idle)
end
def route({:agent_abort, _reason}, state), do: %{state | agent_status: :idle}
def route(:agent_abort, state), do: %{state | agent_status: :idle}
# -----
# 流式文本
# -----
def route(:message_start, state) do
%{state | agent_status: :streaming, current_streaming: ""}
end
def route({:message_delta, %{delta: text}}, state) do
state = %{state | current_streaming: state.current_streaming <> text}
%{state | message_scroll: State.scroll_to_bottom(state)}
end
def route({:response_complete, message}, state) do
content = extract_content(message)
state
|> finalize_streaming()
|> maybe_append_assistant(content)
end
# -----
# 思考链
# -----
def route(:thinking_start, state) do
%{state | show_thinking: true, current_thinking: ""}
end
def route({:thinking_delta, %{delta: text}}, state) do
%{state | current_thinking: state.current_thinking <> text}
end
# -----
# 工具
# -----
def route({:tool_execution_start, name, call_id, args}, state) do
State.upsert_tool(state, call_id, %{
name: name,
args: args,
status: :running,
started_at: now()
})
end
def route({:tool_execution_end, name, call_id, result}, state) do
status = if match?({:ok, _}, result), do: :done, else: :error
State.upsert_tool(state, call_id, %{
name: name,
status: status,
result: result,
finished_at: now()
})
end
def route({:tool_blocked, name, call_id, reason}, state) do
State.upsert_tool(state, call_id, %{
name: name,
status: :blocked,
result: {:error, reason},
started_at: now(),
finished_at: now()
})
end
# -----
# Todo
# -----
def route({:todo_change, _sid, todos}, state) do
%{state | todos: todos}
end
# -----
# HITL 审批
# -----
def route({:approval_required, approval}, state) do
%{state | pending_approval: approval}
end
def route({:approval_resolved, _approval}, state) do
# 审批已由 KeyHandler 处理,此处只做兜底清除
%{state | pending_approval: nil}
end
# -----
# Ask User
# -----
def route({:ask_user, session_id, question, options, ref}, state) do
ask_map = CmdcTui.Dialogs.AskUserDialog.new_ask_map(session_id, question, options, ref)
%{state | pending_ask: ask_map}
end
# -----
# 兜底
# -----
def route(_event, state), do: state
# ==========================================================================
# 私有辅助
# ==========================================================================
defp finalize_streaming(state) do
%{state | current_streaming: "", show_thinking: false, current_thinking: ""}
end
defp maybe_append_assistant(state, "") do
state
end
defp maybe_append_assistant(state, content) do
State.append_message(state, %{
role: :assistant,
content: content,
tool_name: nil,
timestamp: now()
})
end
defp extract_content(%{content: content}) when is_binary(content), do: content
defp extract_content(_), do: ""
defp now, do: System.system_time(:millisecond)
end