defmodule Codex.Events do @moduledoc """ Typed event structs emitted during Codex turn execution. Provides helpers to parse JSON-decoded maps into strongly typed structs and to convert structs back into protocol maps for encoding. """ alias Codex.Items alias Codex.Protocol.CollabAgentRef alias Codex.Protocol.CollabAgentState alias Codex.Protocol.CollabAgentStatusEntry alias Codex.Protocol.RateLimit.Snapshot, as: RateLimitSnapshot alias Codex.Protocol.RequestPermissions alias Codex.Protocol.RequestUserInput.Question, as: RequestUserInputQuestion defmodule ThreadStarted do @moduledoc """ Event emitted when a thread is first created. """ @enforce_keys [:thread_id] defstruct thread_id: nil, metadata: %{} @type t :: %__MODULE__{ thread_id: String.t(), metadata: map() } end defmodule ThreadStatusChanged do @moduledoc """ Event emitted when thread load or activity status changes. """ @enforce_keys [:thread_id] defstruct thread_id: nil, status: nil @type t :: %__MODULE__{ thread_id: String.t(), status: map() | String.t() | atom() | nil } end defmodule ThreadArchived do @moduledoc """ Event emitted when a thread is archived. """ @enforce_keys [:thread_id] defstruct thread_id: nil @type t :: %__MODULE__{ thread_id: String.t() } end defmodule ThreadUnarchived do @moduledoc """ Event emitted when a thread is restored from the archive. """ @enforce_keys [:thread_id] defstruct thread_id: nil @type t :: %__MODULE__{ thread_id: String.t() } end defmodule SkillsChanged do @moduledoc """ Invalidation signal emitted when watched skill metadata changes. """ defstruct [] @type t :: %__MODULE__{} end defmodule ThreadNameUpdated do @moduledoc """ Event emitted when the thread display name changes. """ @enforce_keys [:thread_id] defstruct thread_id: nil, thread_name: nil @type t :: %__MODULE__{ thread_id: String.t(), thread_name: String.t() | nil } end defmodule TurnStarted do @moduledoc """ Event emitted when a new turn starts. """ defstruct turn_id: nil, thread_id: nil @type t :: %__MODULE__{ turn_id: String.t() | nil, thread_id: String.t() | nil } end defmodule TurnContinuation do @moduledoc """ Signals that a continuation token is available for resuming the turn. """ @enforce_keys [:thread_id, :turn_id, :continuation_token] defstruct thread_id: nil, turn_id: nil, continuation_token: nil, retryable: true, reason: nil @type t :: %__MODULE__{ thread_id: String.t(), turn_id: String.t(), continuation_token: String.t(), retryable: boolean(), reason: String.t() | nil } end defmodule TurnCompleted do @moduledoc """ Final event for a turn, optionally carrying final response and usage data. """ defstruct thread_id: nil, turn_id: nil, response_id: nil, final_response: nil, usage: nil, status: nil, error: nil @type t :: %__MODULE__{ thread_id: String.t() | nil, turn_id: String.t() | nil, response_id: String.t() | nil, final_response: Items.AgentMessage.t() | map() | nil, usage: map() | nil, status: String.t() | nil, error: map() | nil } end defmodule ThreadTokenUsageUpdated do @moduledoc """ Incremental token usage update emitted while a turn is in flight. """ @enforce_keys [:usage] defstruct thread_id: nil, turn_id: nil, usage: %{}, delta: nil, rate_limits: nil @type t :: %__MODULE__{ thread_id: String.t() | nil, turn_id: String.t() | nil, usage: map(), delta: map() | nil, rate_limits: Codex.Protocol.RateLimit.Snapshot.t() | map() | nil } end defmodule TurnDiffUpdated do @moduledoc """ Event emitted when the app-server publishes a turn diff update. """ @enforce_keys [:diff] defstruct thread_id: nil, turn_id: nil, diff: "" @type t :: %__MODULE__{ thread_id: String.t() | nil, turn_id: String.t() | nil, diff: String.t() | map() } end defmodule TurnPlanUpdated do @moduledoc """ Event emitted when the app-server publishes an updated plan for the current turn. """ @enforce_keys [:plan] defstruct thread_id: nil, turn_id: nil, explanation: nil, plan: [] @type plan_step_status :: :pending | :in_progress | :completed @type plan_step :: %{step: String.t(), status: plan_step_status()} @type t :: %__MODULE__{ thread_id: String.t() | nil, turn_id: String.t() | nil, explanation: String.t() | nil, plan: [plan_step()] } end defmodule CommandOutputDelta do @moduledoc """ Event delta emitted while a command execution is producing output. """ @enforce_keys [:item_id, :delta] defstruct thread_id: nil, turn_id: nil, item_id: nil, delta: "" @type t :: %__MODULE__{ thread_id: String.t() | nil, turn_id: String.t() | nil, item_id: String.t(), delta: String.t() } end defmodule FileChangeOutputDelta do @moduledoc """ Event delta emitted while a file change stream is producing output. """ @enforce_keys [:item_id, :delta] defstruct thread_id: nil, turn_id: nil, item_id: nil, delta: "" @type t :: %__MODULE__{ thread_id: String.t() | nil, turn_id: String.t() | nil, item_id: String.t(), delta: String.t() } end defmodule TerminalInteraction do @moduledoc """ Event emitted when stdin is written to an interactive command execution. """ @enforce_keys [:item_id] defstruct thread_id: nil, turn_id: nil, item_id: nil, process_id: nil, stdin: "" @type t :: %__MODULE__{ thread_id: String.t() | nil, turn_id: String.t() | nil, item_id: String.t(), process_id: String.t() | nil, stdin: String.t() } end defmodule ReasoningDelta do @moduledoc """ Event delta emitted while reasoning content is streaming. """ @enforce_keys [:item_id, :delta] defstruct thread_id: nil, turn_id: nil, item_id: nil, delta: "", content_index: nil @type t :: %__MODULE__{ thread_id: String.t() | nil, turn_id: String.t() | nil, item_id: String.t(), delta: String.t(), content_index: integer() | nil } end defmodule ReasoningSummaryDelta do @moduledoc """ Event delta emitted while reasoning summary text is streaming. """ @enforce_keys [:item_id, :delta] defstruct thread_id: nil, turn_id: nil, item_id: nil, delta: "", summary_index: nil @type t :: %__MODULE__{ thread_id: String.t() | nil, turn_id: String.t() | nil, item_id: String.t(), delta: String.t(), summary_index: integer() | nil } end defmodule ReasoningSummaryPartAdded do @moduledoc """ Event emitted when a new reasoning summary part is added. """ @enforce_keys [:item_id, :summary_index] defstruct thread_id: nil, turn_id: nil, item_id: nil, summary_index: nil @type t :: %__MODULE__{ thread_id: String.t() | nil, turn_id: String.t() | nil, item_id: String.t(), summary_index: integer() } end defmodule McpToolCallProgress do @moduledoc """ Progress message emitted while an MCP tool call is running. """ @enforce_keys [:item_id, :message] defstruct thread_id: nil, turn_id: nil, item_id: nil, message: "" @type t :: %__MODULE__{ thread_id: String.t() | nil, turn_id: String.t() | nil, item_id: String.t(), message: String.t() } end defmodule McpServerOauthLoginCompleted do @moduledoc """ Event emitted when an MCP server OAuth login completes. """ @enforce_keys [:name, :success] defstruct name: nil, success: false, error: nil @type t :: %__MODULE__{ name: String.t(), success: boolean(), error: String.t() | nil } end defmodule McpServerStartupStatusUpdated do @moduledoc """ Event emitted when app-server observes an MCP server startup state transition. """ @enforce_keys [:name, :status] defstruct name: nil, status: nil, error: nil @type startup_status :: :starting | :ready | :failed | :cancelled | String.t() @type t :: %__MODULE__{ name: String.t(), status: startup_status(), error: String.t() | nil } end defmodule HookStarted do @moduledoc """ Event emitted when an app-server hook run starts. """ @enforce_keys [:thread_id] defstruct thread_id: nil, turn_id: nil, run: %{} @type t :: %__MODULE__{ thread_id: String.t(), turn_id: String.t() | nil, run: map() } end defmodule HookCompleted do @moduledoc """ Event emitted when an app-server hook run completes. """ @enforce_keys [:thread_id] defstruct thread_id: nil, turn_id: nil, run: %{} @type t :: %__MODULE__{ thread_id: String.t(), turn_id: String.t() | nil, run: map() } end defmodule AccountUpdated do @moduledoc """ Event emitted when account authentication state changes. """ defstruct auth_mode: nil, plan_type: nil @type t :: %__MODULE__{ auth_mode: String.t() | nil, plan_type: atom() | String.t() | nil } end defmodule AccountRateLimitsUpdated do @moduledoc """ Event emitted when account rate limits are updated. Contains current rate limit information from the API, including limits, remaining quota, and reset times. """ @enforce_keys [:rate_limits] defstruct rate_limits: %{}, thread_id: nil, turn_id: nil @type t :: %__MODULE__{ rate_limits: Codex.Protocol.RateLimit.Snapshot.t() | map(), thread_id: String.t() | nil, turn_id: String.t() | nil } end defmodule AccountLoginCompleted do @moduledoc """ Event emitted when account login completes. """ @enforce_keys [:success] defstruct login_id: nil, success: false, error: nil @type t :: %__MODULE__{ login_id: String.t() | nil, success: boolean(), error: String.t() | nil } end defmodule WindowsWorldWritableWarning do @moduledoc """ Event emitted when world-writable Windows paths are detected. """ @enforce_keys [:sample_paths, :extra_count, :failed_scan] defstruct sample_paths: [], extra_count: 0, failed_scan: false @type t :: %__MODULE__{ sample_paths: [String.t()], extra_count: non_neg_integer(), failed_scan: boolean() } end defmodule DeprecationNotice do @moduledoc """ Event emitted when the server reports a deprecated feature or behavior. """ @enforce_keys [:summary] defstruct summary: nil, details: nil @type t :: %__MODULE__{ summary: String.t(), details: String.t() | nil } end defmodule AppListUpdated do @moduledoc """ Event emitted when the app catalog changes. """ @enforce_keys [:data] defstruct data: [] @type t :: %__MODULE__{ data: [map()] } end defmodule ModelRerouted do @moduledoc """ Event emitted when Codex reroutes a turn to a different model. """ @enforce_keys [:thread_id, :turn_id, :from_model, :to_model, :reason] defstruct thread_id: nil, turn_id: nil, from_model: nil, to_model: nil, reason: nil @type t :: %__MODULE__{ thread_id: String.t(), turn_id: String.t(), from_model: String.t(), to_model: String.t(), reason: String.t() | atom() } end defmodule FuzzyFileSearchSessionUpdated do @moduledoc """ Event emitted when a fuzzy file search session publishes updated matches. """ @enforce_keys [:session_id, :query, :files] defstruct session_id: nil, query: nil, files: [] @type t :: %__MODULE__{ session_id: String.t(), query: String.t(), files: [map()] } end defmodule FuzzyFileSearchSessionCompleted do @moduledoc """ Event emitted when a fuzzy file search session finishes. """ @enforce_keys [:session_id] defstruct session_id: nil @type t :: %__MODULE__{ session_id: String.t() } end defmodule ThreadRealtimeStarted do @moduledoc """ Event emitted when thread realtime startup is accepted. """ @enforce_keys [:thread_id] defstruct thread_id: nil, session_id: nil @type t :: %__MODULE__{ thread_id: String.t(), session_id: String.t() | nil } end defmodule ThreadRealtimeItemAdded do @moduledoc """ Event emitted when thread realtime adds a non-audio item. """ @enforce_keys [:thread_id, :item] defstruct thread_id: nil, item: nil @type t :: %__MODULE__{ thread_id: String.t(), item: map() | list() | String.t() } end defmodule ThreadRealtimeOutputAudioDelta do @moduledoc """ Event emitted when thread realtime streams output audio. """ @enforce_keys [:thread_id, :audio] defstruct thread_id: nil, audio: %{} @type t :: %__MODULE__{ thread_id: String.t(), audio: map() } end defmodule ThreadRealtimeError do @moduledoc """ Event emitted when thread realtime encounters an error. """ @enforce_keys [:thread_id, :message] defstruct thread_id: nil, message: nil @type t :: %__MODULE__{ thread_id: String.t(), message: String.t() } end defmodule ThreadRealtimeClosed do @moduledoc """ Event emitted when thread realtime closes. """ @enforce_keys [:thread_id] defstruct thread_id: nil, reason: nil @type t :: %__MODULE__{ thread_id: String.t(), reason: String.t() | nil } end defmodule RawResponseItemCompleted do @moduledoc """ Event emitted when a raw response item completes on the app-server stream. """ @enforce_keys [:item] defstruct thread_id: nil, turn_id: nil, item: nil @type t :: %__MODULE__{ thread_id: String.t() | nil, turn_id: String.t() | nil, item: Items.t() | map() } end defmodule AppServerNotification do @moduledoc """ Lossless wrapper for an app-server notification that is not yet mapped into a typed event. """ @enforce_keys [:method] defstruct method: nil, params: %{} @type t :: %__MODULE__{ method: String.t(), params: map() } end defmodule TurnCompaction do @moduledoc """ Signals that Codex compacted a turn's history. """ @enforce_keys [:compaction, :stage] defstruct thread_id: nil, turn_id: nil, compaction: %{}, stage: nil @type stage :: :started | :completed | :failed | :unknown | String.t() @type t :: %__MODULE__{ thread_id: String.t() | nil, turn_id: String.t() | nil, compaction: map(), stage: stage() } end defmodule ItemAgentMessageDelta do @moduledoc """ Event delta emitted when the agent produces message content. """ @enforce_keys [:item] defstruct item: %{}, thread_id: nil, turn_id: nil @type t :: %__MODULE__{ item: map(), thread_id: String.t() | nil, turn_id: String.t() | nil } end defmodule ItemInputTextDelta do @moduledoc """ Event delta emitted for user input text items. """ @enforce_keys [:item] defstruct item: %{}, thread_id: nil, turn_id: nil @type t :: %__MODULE__{ item: map(), thread_id: String.t() | nil, turn_id: String.t() | nil } end defmodule ItemCompleted do @moduledoc """ Event emitted when an item completes. """ @enforce_keys [:item] defstruct item: nil, thread_id: nil, turn_id: nil @type t :: %__MODULE__{ item: Items.t(), thread_id: String.t() | nil, turn_id: String.t() | nil } end defmodule ItemStarted do @moduledoc """ Event emitted when an item begins processing. """ @enforce_keys [:item] defstruct item: nil, thread_id: nil, turn_id: nil @type t :: %__MODULE__{ item: Items.t(), thread_id: String.t() | nil, turn_id: String.t() | nil } end defmodule ItemUpdated do @moduledoc """ Event emitted when an in-progress item receives an update. """ @enforce_keys [:item] defstruct item: nil, thread_id: nil, turn_id: nil @type t :: %__MODULE__{ item: Items.t(), thread_id: String.t() | nil, turn_id: String.t() | nil } end defmodule Error do @moduledoc """ General error event emitted by the CLI. """ @enforce_keys [:message] defstruct message: nil, thread_id: nil, turn_id: nil, additional_details: nil, codex_error_info: nil, will_retry: nil @type t :: %__MODULE__{ message: String.t(), thread_id: String.t() | nil, turn_id: String.t() | nil, additional_details: String.t() | nil, codex_error_info: map() | nil, will_retry: boolean() | nil } end defmodule TurnFailed do @moduledoc """ Event emitted when a turn fails. """ @enforce_keys [:error] defstruct error: %{}, thread_id: nil, turn_id: nil @type t :: %__MODULE__{ error: map(), thread_id: String.t() | nil, turn_id: String.t() | nil } end defmodule ToolCallRequested do @moduledoc """ Indicates Codex requires a tool invocation to continue auto-run. """ @enforce_keys [:thread_id, :turn_id, :call_id, :tool_name, :arguments] defstruct thread_id: nil, turn_id: nil, call_id: nil, tool_name: nil, arguments: %{}, requires_approval: false, approved: nil, approved_by_policy: nil, sandbox_warnings: nil, capabilities: nil @type t :: %__MODULE__{ thread_id: String.t(), turn_id: String.t(), call_id: String.t(), tool_name: String.t(), arguments: map() | list() | String.t(), requires_approval: boolean(), approved: boolean() | nil, approved_by_policy: boolean() | nil, sandbox_warnings: [String.t()] | nil, capabilities: map() | nil } end defmodule ToolCallCompleted do @moduledoc """ Event emitted when a tool call has completed and returned output. """ @enforce_keys [:thread_id, :turn_id, :call_id, :tool_name, :output] defstruct thread_id: nil, turn_id: nil, call_id: nil, tool_name: nil, output: %{} @type t :: %__MODULE__{ thread_id: String.t(), turn_id: String.t(), call_id: String.t(), tool_name: String.t(), output: map() } end defmodule SessionConfigured do @moduledoc """ Event emitted when a session is configured. """ defstruct session_id: nil, forked_from_id: nil, model: nil, model_provider_id: nil, approval_policy: nil, approvals_reviewer: nil, sandbox_policy: nil, cwd: nil, reasoning_effort: nil, history_log_id: nil, history_entry_count: nil, initial_messages: nil, rollout_path: nil @type t :: %__MODULE__{ session_id: String.t() | nil, forked_from_id: String.t() | nil, model: String.t() | nil, model_provider_id: String.t() | nil, approval_policy: term(), approvals_reviewer: :user | :guardian_subagent | String.t() | nil, sandbox_policy: term(), cwd: String.t() | nil, reasoning_effort: String.t() | atom() | nil, history_log_id: non_neg_integer() | nil, history_entry_count: non_neg_integer() | nil, initial_messages: list() | nil, rollout_path: String.t() | nil } end defmodule Warning do @moduledoc """ Warning event emitted during a turn. """ @enforce_keys [:message] defstruct message: nil @type t :: %__MODULE__{ message: String.t() } end defmodule ContextCompacted do @moduledoc """ Indicates that the conversation context was compacted. """ defstruct removed_turns: nil, remaining_turns: nil @type t :: %__MODULE__{ removed_turns: non_neg_integer() | nil, remaining_turns: non_neg_integer() | nil } end defmodule ThreadRolledBack do @moduledoc """ Indicates that recent user turns were removed from context. """ defstruct num_turns: nil @type t :: %__MODULE__{ num_turns: non_neg_integer() | nil } end defmodule RequestUserInput do @moduledoc """ Event emitted when the agent requests user input. """ defstruct id: nil, thread_id: nil, turn_id: nil, item_id: nil, questions: [] @type t :: %__MODULE__{ id: String.t() | integer() | nil, thread_id: String.t() | nil, turn_id: String.t() | nil, item_id: String.t() | nil, questions: list() } end defmodule McpElicitationRequested do @moduledoc """ Event emitted when the app-server requests an MCP elicitation response from the client. """ @enforce_keys [:id, :thread_id, :server_name, :request] defstruct id: nil, thread_id: nil, turn_id: nil, server_name: nil, request_mode: nil, message: nil, request: %{} @type t :: %__MODULE__{ id: String.t() | integer(), thread_id: String.t(), turn_id: String.t() | nil, server_name: String.t(), request_mode: String.t() | nil, message: String.t() | nil, request: map() } end defmodule PermissionsApprovalRequested do @moduledoc """ Event emitted when the app-server requests additional permissions from the client. """ @enforce_keys [:id, :thread_id, :turn_id, :item_id, :permissions] defstruct id: nil, thread_id: nil, turn_id: nil, item_id: nil, reason: nil, permissions: nil @type t :: %__MODULE__{ id: String.t() | integer(), thread_id: String.t(), turn_id: String.t(), item_id: String.t(), reason: String.t() | nil, permissions: RequestPermissions.RequestPermissionProfile.t() | map() } end defmodule CommandApprovalRequested do @moduledoc """ Event emitted when the app-server requests approval for a command execution. """ @enforce_keys [:id, :thread_id, :turn_id, :item_id] defstruct id: nil, thread_id: nil, turn_id: nil, item_id: nil, approval_id: nil, reason: nil, command: nil, cwd: nil, command_actions: nil, network_approval_context: nil, additional_permissions: nil, skill_metadata: nil, proposed_execpolicy_amendment: nil, proposed_network_policy_amendments: nil, available_decisions: nil @type t :: %__MODULE__{ id: String.t() | integer(), thread_id: String.t(), turn_id: String.t(), item_id: String.t(), approval_id: String.t() | nil, reason: String.t() | nil, command: String.t() | nil, cwd: String.t() | nil, command_actions: [map()] | nil, network_approval_context: map() | nil, additional_permissions: RequestPermissions.RequestPermissionProfile.t() | map() | nil, skill_metadata: map() | nil, proposed_execpolicy_amendment: [String.t()] | nil, proposed_network_policy_amendments: [map()] | nil, available_decisions: [String.t() | map()] | nil } end defmodule FileApprovalRequested do @moduledoc """ Event emitted when the app-server requests approval for a file change. """ @enforce_keys [:id, :thread_id, :turn_id, :item_id] defstruct id: nil, thread_id: nil, turn_id: nil, item_id: nil, reason: nil, grant_root: nil @type t :: %__MODULE__{ id: String.t() | integer(), thread_id: String.t(), turn_id: String.t(), item_id: String.t(), reason: String.t() | nil, grant_root: String.t() | nil } end defmodule GuardianApprovalReview do @moduledoc """ Review payload emitted by guardian auto-approval review notifications. """ @type status :: :in_progress | :approved | :denied | :aborted @type risk_level :: :low | :medium | :high @enforce_keys [:status] defstruct status: nil, risk_score: nil, risk_level: nil, rationale: nil @type t :: %__MODULE__{ status: status(), risk_score: non_neg_integer() | nil, risk_level: risk_level() | nil, rationale: String.t() | nil } end defmodule GuardianApprovalReviewStarted do @moduledoc """ Event emitted when guardian review begins for an approval request. """ @enforce_keys [:thread_id, :turn_id, :target_item_id, :review] defstruct thread_id: nil, turn_id: nil, target_item_id: nil, review: nil, action: nil @type t :: %__MODULE__{ thread_id: String.t(), turn_id: String.t(), target_item_id: String.t(), review: GuardianApprovalReview.t(), action: map() | String.t() | nil } end defmodule GuardianApprovalReviewCompleted do @moduledoc """ Event emitted when guardian review completes for an approval request. """ @enforce_keys [:thread_id, :turn_id, :target_item_id, :review] defstruct thread_id: nil, turn_id: nil, target_item_id: nil, review: nil, action: nil @type t :: %__MODULE__{ thread_id: String.t(), turn_id: String.t(), target_item_id: String.t(), review: GuardianApprovalReview.t(), action: map() | String.t() | nil } end defmodule ServerRequestResolved do @moduledoc """ Event emitted when the app-server resolves a prior server request. """ @enforce_keys [:thread_id, :request_id] defstruct thread_id: nil, request_id: nil @type t :: %__MODULE__{ thread_id: String.t(), request_id: String.t() | integer() } end defmodule DynamicToolCallRequested do @moduledoc """ Event emitted when the app-server asks the client to execute a dynamic tool call. """ @enforce_keys [:id, :thread_id, :turn_id, :call_id, :tool_name] defstruct id: nil, thread_id: nil, turn_id: nil, call_id: nil, tool_name: nil, arguments: %{} @type t :: %__MODULE__{ id: String.t() | integer(), thread_id: String.t(), turn_id: String.t(), call_id: String.t(), tool_name: String.t(), arguments: map() | list() | String.t() | nil } end defmodule ChatgptAuthTokensRefreshRequested do @moduledoc """ Event emitted when the app-server asks the client to refresh ChatGPT auth tokens. """ @enforce_keys [:id, :reason] defstruct id: nil, reason: nil, previous_account_id: nil @type t :: %__MODULE__{ id: String.t() | integer(), reason: String.t(), previous_account_id: String.t() | nil } end defmodule McpStartupUpdate do @moduledoc """ Incremental status update for MCP server startup. """ defstruct server_name: nil, status: nil, message: nil @type t :: %__MODULE__{ server_name: String.t() | nil, status: String.t() | map() | atom() | nil, message: String.t() | nil } end defmodule McpStartupComplete do @moduledoc """ Summary of MCP server startup completion. """ defstruct servers: nil @type t :: %__MODULE__{ servers: map() | list() | nil } end defmodule ElicitationRequest do @moduledoc """ Event emitted for MCP elicitation requests. """ defstruct server_name: nil, id: nil, message: nil @type t :: %__MODULE__{ server_name: String.t() | nil, id: String.t() | nil, message: String.t() | nil } end defmodule UndoStarted do @moduledoc """ Event emitted when an undo operation begins. """ defstruct turn_id: nil, message: nil @type t :: %__MODULE__{ turn_id: String.t() | nil, message: String.t() | nil } end defmodule UndoCompleted do @moduledoc """ Event emitted when an undo operation completes. """ defstruct turn_id: nil, success: nil, message: nil @type t :: %__MODULE__{ turn_id: String.t() | nil, success: boolean() | nil, message: String.t() | nil } end defmodule TurnAborted do @moduledoc """ Event emitted when a turn is aborted. """ defstruct turn_id: nil, reason: nil @type t :: %__MODULE__{ turn_id: String.t() | nil, reason: String.t() | atom() | map() | nil } end defmodule ShutdownComplete do @moduledoc """ Event emitted when the agent shuts down. """ defstruct [] @type t :: %__MODULE__{} end defmodule EnteredReviewMode do @moduledoc """ Event emitted when a review session starts. """ defstruct review_request: nil @type t :: %__MODULE__{ review_request: map() | nil } end defmodule ExitedReviewMode do @moduledoc """ Event emitted when a review session ends. """ defstruct result: nil @type t :: %__MODULE__{ result: map() | nil } end defmodule ConfigWarning do @moduledoc """ Event emitted when configuration warnings are reported. """ @enforce_keys [:summary] defstruct summary: nil, details: nil @type t :: %__MODULE__{ summary: String.t(), details: String.t() | nil } end defmodule CollabAgentSpawnBegin do @moduledoc """ Collab event emitted when an agent spawn starts. """ defstruct call_id: nil, sender_thread_id: nil, prompt: nil, model: nil, reasoning_effort: nil @type t :: %__MODULE__{ call_id: String.t() | nil, sender_thread_id: String.t() | nil, prompt: String.t() | nil, model: String.t() | nil, reasoning_effort: String.t() | atom() | nil } end defmodule CollabAgentSpawnEnd do @moduledoc """ Collab event emitted when an agent spawn completes. """ defstruct call_id: nil, sender_thread_id: nil, new_thread_id: nil, new_agent_nickname: nil, new_agent_role: nil, prompt: nil, model: nil, reasoning_effort: nil, status: nil @type t :: %__MODULE__{ call_id: String.t() | nil, sender_thread_id: String.t() | nil, new_thread_id: String.t() | nil, new_agent_nickname: String.t() | nil, new_agent_role: String.t() | nil, prompt: String.t() | nil, model: String.t() | nil, reasoning_effort: String.t() | atom() | nil, status: CollabAgentState.t() | nil } end defmodule CollabAgentInteractionBegin do @moduledoc """ Collab event emitted when an agent interaction starts. """ defstruct call_id: nil, sender_thread_id: nil, receiver_thread_id: nil, prompt: nil @type t :: %__MODULE__{ call_id: String.t() | nil, sender_thread_id: String.t() | nil, receiver_thread_id: String.t() | nil, prompt: String.t() | nil } end defmodule CollabAgentInteractionEnd do @moduledoc """ Collab event emitted when an agent interaction completes. """ defstruct call_id: nil, sender_thread_id: nil, receiver_thread_id: nil, receiver_agent_nickname: nil, receiver_agent_role: nil, prompt: nil, status: nil @type t :: %__MODULE__{ call_id: String.t() | nil, sender_thread_id: String.t() | nil, receiver_thread_id: String.t() | nil, receiver_agent_nickname: String.t() | nil, receiver_agent_role: String.t() | nil, prompt: String.t() | nil, status: CollabAgentState.t() | nil } end defmodule CollabWaitingBegin do @moduledoc """ Collab event emitted when an agent begins waiting. """ defstruct sender_thread_id: nil, receiver_thread_ids: [], receiver_agents: [], call_id: nil @type t :: %__MODULE__{ sender_thread_id: String.t() | nil, receiver_thread_ids: [String.t()], receiver_agents: [CollabAgentRef.t()], call_id: String.t() | nil } end defmodule CollabWaitingEnd do @moduledoc """ Collab event emitted when an agent stops waiting. """ defstruct sender_thread_id: nil, call_id: nil, agent_statuses: [], statuses: nil @type t :: %__MODULE__{ sender_thread_id: String.t() | nil, call_id: String.t() | nil, agent_statuses: [CollabAgentStatusEntry.t()], statuses: %{optional(String.t()) => CollabAgentState.t()} | nil } end defmodule CollabCloseBegin do @moduledoc """ Collab event emitted when a collab session begins closing. """ defstruct call_id: nil, sender_thread_id: nil, receiver_thread_id: nil @type t :: %__MODULE__{ call_id: String.t() | nil, sender_thread_id: String.t() | nil, receiver_thread_id: String.t() | nil } end defmodule CollabCloseEnd do @moduledoc """ Collab event emitted when a collab session closes. """ defstruct call_id: nil, sender_thread_id: nil, receiver_thread_id: nil, receiver_agent_nickname: nil, receiver_agent_role: nil, status: nil @type t :: %__MODULE__{ call_id: String.t() | nil, sender_thread_id: String.t() | nil, receiver_thread_id: String.t() | nil, receiver_agent_nickname: String.t() | nil, receiver_agent_role: String.t() | nil, status: CollabAgentState.t() | nil } end defmodule CollabResumeBegin do @moduledoc """ Collab event emitted when an agent resume begins. """ defstruct call_id: nil, sender_thread_id: nil, receiver_thread_id: nil, receiver_agent_nickname: nil, receiver_agent_role: nil @type t :: %__MODULE__{ call_id: String.t() | nil, sender_thread_id: String.t() | nil, receiver_thread_id: String.t() | nil, receiver_agent_nickname: String.t() | nil, receiver_agent_role: String.t() | nil } end defmodule CollabResumeEnd do @moduledoc """ Collab event emitted when an agent resume completes. """ defstruct call_id: nil, sender_thread_id: nil, receiver_thread_id: nil, receiver_agent_nickname: nil, receiver_agent_role: nil, status: nil @type t :: %__MODULE__{ call_id: String.t() | nil, sender_thread_id: String.t() | nil, receiver_thread_id: String.t() | nil, receiver_agent_nickname: String.t() | nil, receiver_agent_role: String.t() | nil, status: CollabAgentState.t() | nil } end alias __MODULE__.{ ItemAgentMessageDelta, ItemInputTextDelta, ThreadArchived, ThreadUnarchived, ThreadStatusChanged, SkillsChanged, ThreadNameUpdated, ThreadStarted, TurnCompleted, ThreadTokenUsageUpdated, TurnCompaction, TurnContinuation, TurnDiffUpdated, TurnPlanUpdated, TurnStarted, ToolCallCompleted, ToolCallRequested, ItemCompleted, ItemStarted, ItemUpdated, CommandOutputDelta, FileChangeOutputDelta, TerminalInteraction, ReasoningDelta, ReasoningSummaryDelta, ReasoningSummaryPartAdded, AppServerNotification, McpToolCallProgress, McpServerOauthLoginCompleted, McpServerStartupStatusUpdated, HookStarted, HookCompleted, AccountUpdated, AccountRateLimitsUpdated, AccountLoginCompleted, WindowsWorldWritableWarning, DeprecationNotice, AppListUpdated, ModelRerouted, FuzzyFileSearchSessionUpdated, FuzzyFileSearchSessionCompleted, ThreadRealtimeStarted, ThreadRealtimeItemAdded, ThreadRealtimeOutputAudioDelta, ThreadRealtimeError, ThreadRealtimeClosed, RawResponseItemCompleted, Error, TurnFailed, SessionConfigured, Warning, ContextCompacted, ThreadRolledBack, RequestUserInput, McpElicitationRequested, CommandApprovalRequested, FileApprovalRequested, PermissionsApprovalRequested, GuardianApprovalReviewStarted, GuardianApprovalReviewCompleted, ServerRequestResolved, DynamicToolCallRequested, ChatgptAuthTokensRefreshRequested, McpStartupUpdate, McpStartupComplete, ElicitationRequest, UndoStarted, UndoCompleted, TurnAborted, ShutdownComplete, EnteredReviewMode, ExitedReviewMode, ConfigWarning, CollabAgentSpawnBegin, CollabAgentSpawnEnd, CollabAgentInteractionBegin, CollabAgentInteractionEnd, CollabWaitingBegin, CollabWaitingEnd, CollabCloseBegin, CollabCloseEnd, CollabResumeBegin, CollabResumeEnd } @type t :: ThreadStarted.t() | ThreadStatusChanged.t() | ThreadArchived.t() | ThreadUnarchived.t() | SkillsChanged.t() | ThreadNameUpdated.t() | TurnStarted.t() | TurnContinuation.t() | TurnCompleted.t() | ThreadTokenUsageUpdated.t() | TurnDiffUpdated.t() | TurnPlanUpdated.t() | TurnCompaction.t() | ItemAgentMessageDelta.t() | ItemInputTextDelta.t() | ItemCompleted.t() | ItemStarted.t() | ItemUpdated.t() | CommandOutputDelta.t() | FileChangeOutputDelta.t() | TerminalInteraction.t() | ReasoningDelta.t() | ReasoningSummaryDelta.t() | ReasoningSummaryPartAdded.t() | AppServerNotification.t() | McpToolCallProgress.t() | McpServerOauthLoginCompleted.t() | McpServerStartupStatusUpdated.t() | HookStarted.t() | HookCompleted.t() | AccountUpdated.t() | AccountRateLimitsUpdated.t() | AccountLoginCompleted.t() | WindowsWorldWritableWarning.t() | DeprecationNotice.t() | AppListUpdated.t() | ModelRerouted.t() | FuzzyFileSearchSessionUpdated.t() | FuzzyFileSearchSessionCompleted.t() | ThreadRealtimeStarted.t() | ThreadRealtimeItemAdded.t() | ThreadRealtimeOutputAudioDelta.t() | ThreadRealtimeError.t() | ThreadRealtimeClosed.t() | RawResponseItemCompleted.t() | Error.t() | TurnFailed.t() | ToolCallRequested.t() | ToolCallCompleted.t() | SessionConfigured.t() | Warning.t() | ContextCompacted.t() | ThreadRolledBack.t() | RequestUserInput.t() | McpElicitationRequested.t() | CommandApprovalRequested.t() | FileApprovalRequested.t() | PermissionsApprovalRequested.t() | GuardianApprovalReviewStarted.t() | GuardianApprovalReviewCompleted.t() | ServerRequestResolved.t() | DynamicToolCallRequested.t() | ChatgptAuthTokensRefreshRequested.t() | McpStartupUpdate.t() | McpStartupComplete.t() | ElicitationRequest.t() | UndoStarted.t() | UndoCompleted.t() | TurnAborted.t() | ShutdownComplete.t() | EnteredReviewMode.t() | ExitedReviewMode.t() | ConfigWarning.t() | CollabAgentSpawnBegin.t() | CollabAgentSpawnEnd.t() | CollabAgentInteractionBegin.t() | CollabAgentInteractionEnd.t() | CollabWaitingBegin.t() | CollabWaitingEnd.t() | CollabCloseBegin.t() | CollabCloseEnd.t() | CollabResumeBegin.t() | CollabResumeEnd.t() @compaction_stage_map %{ "started" => :started, "completed" => :completed, "failed" => :failed } @doc """ Parses a JSON-decoded map into a typed event struct, raising on unknown event types. """ @spec parse!(map()) :: t() def parse!(%{"type" => "thread.started"} = map) do %ThreadStarted{ thread_id: Map.get(map, "thread_id"), metadata: Map.get(map, "metadata", %{}) } end def parse!(%{"type" => "thread/status/changed"} = map) do %ThreadStatusChanged{ thread_id: Map.fetch!(map, "thread_id"), status: parse_thread_status(Map.get(map, "status")) } end def parse!(%{"type" => "thread/archived"} = map) do %ThreadArchived{thread_id: Map.fetch!(map, "thread_id")} end def parse!(%{"type" => "thread/unarchived"} = map) do %ThreadUnarchived{thread_id: Map.fetch!(map, "thread_id")} end def parse!(%{"type" => "skills/changed"}) do %SkillsChanged{} end def parse!(%{"type" => "thread/name/updated"} = map) do %ThreadNameUpdated{ thread_id: Map.fetch!(map, "thread_id"), thread_name: Map.get(map, "thread_name") || Map.get(map, "threadName") } end def parse!(%{"type" => type} = map) when type in ["session_configured", "sessionConfigured"] do parse_session_configured(map) end def parse!(%{"type" => type} = map) when type in ["warning", "Warning"] do %Warning{ message: Map.get(map, "message") || "" } end def parse!(%{"type" => type} = map) when type in ["context_compacted", "contextCompacted"] do %ContextCompacted{ removed_turns: Map.get(map, "removed_turns") || Map.get(map, "removedTurns"), remaining_turns: Map.get(map, "remaining_turns") || Map.get(map, "remainingTurns") } end def parse!(%{"type" => type} = map) when type in ["thread_rolled_back", "threadRolledBack"] do %ThreadRolledBack{ num_turns: Map.get(map, "num_turns") || Map.get(map, "numTurns") } end def parse!(%{"type" => "turn.started"} = map) do %TurnStarted{ turn_id: Map.get(map, "turn_id"), thread_id: Map.get(map, "thread_id") } end def parse!(%{"type" => "hook/started"} = map) do %HookStarted{ thread_id: Map.fetch!(map, "thread_id"), turn_id: Map.get(map, "turn_id"), run: Map.get(map, "run") || %{} } end def parse!(%{"type" => "turn.continuation"} = map) do %TurnContinuation{ thread_id: Map.fetch!(map, "thread_id"), turn_id: Map.fetch!(map, "turn_id"), continuation_token: Map.fetch!(map, "continuation_token"), retryable: Map.get(map, "retryable", false), reason: Map.get(map, "reason") } end def parse!(%{"type" => "turn.completed"} = map) do %TurnCompleted{ thread_id: Map.get(map, "thread_id"), turn_id: Map.get(map, "turn_id"), response_id: Map.get(map, "response_id"), final_response: Map.get(map, "final_response"), usage: Map.get(map, "usage"), status: Map.get(map, "status"), error: Map.get(map, "error") } end def parse!(%{"type" => "hook/completed"} = map) do %HookCompleted{ thread_id: Map.fetch!(map, "thread_id"), turn_id: Map.get(map, "turn_id"), run: Map.get(map, "run") || %{} } end def parse!(%{"type" => type} = map) when type in ["thread.tokenUsage.updated", "thread/tokenUsage/updated"] do rate_limits = map |> Map.get("rate_limits") |> case do nil -> Map.get(map, "rateLimits") value -> value end |> parse_rate_limits() %ThreadTokenUsageUpdated{ thread_id: Map.get(map, "thread_id"), turn_id: Map.get(map, "turn_id"), usage: Map.get(map, "usage") || Map.get(map, "token_usage") || %{}, delta: Map.get(map, "delta") || Map.get(map, "usage_delta"), rate_limits: rate_limits } end def parse!(%{"type" => type} = map) when type in ["turn.diff.updated", "turn/diff/updated"] do %TurnDiffUpdated{ thread_id: Map.get(map, "thread_id"), turn_id: Map.get(map, "turn_id"), diff: Map.get(map, "diff") || Map.get(map, "delta") || "" } end def parse!(%{"type" => type} = map) when type in ["turn.plan.updated", "turn/plan/updated"] do %TurnPlanUpdated{ thread_id: Map.get(map, "thread_id"), turn_id: Map.get(map, "turn_id"), explanation: Map.get(map, "explanation"), plan: Map.get(map, "plan") || [] } end def parse!(%{"type" => type} = map) when type in ["request_user_input", "requestUserInput"] do %RequestUserInput{ id: Map.get(map, "id") || Map.get(map, "call_id") || Map.get(map, "callId"), thread_id: Map.get(map, "thread_id") || Map.get(map, "threadId"), turn_id: Map.get(map, "turn_id") || Map.get(map, "turnId"), item_id: Map.get(map, "item_id") || Map.get(map, "itemId"), questions: map |> Map.get("questions") |> parse_request_user_input_questions() } end def parse!(%{"type" => "mcp_elicitation_requested"} = map) do %McpElicitationRequested{ id: Map.fetch!(map, "id"), thread_id: Map.fetch!(map, "thread_id"), turn_id: Map.get(map, "turn_id"), server_name: Map.fetch!(map, "server_name"), request_mode: Map.get(map, "request_mode"), message: Map.get(map, "message"), request: Map.get(map, "request") || %{} } end def parse!(%{"type" => "permissions_approval_requested"} = map) do %PermissionsApprovalRequested{ id: Map.fetch!(map, "id"), thread_id: Map.fetch!(map, "thread_id"), turn_id: Map.fetch!(map, "turn_id"), item_id: Map.fetch!(map, "item_id"), reason: Map.get(map, "reason"), permissions: map |> Map.get("permissions") |> RequestPermissions.RequestPermissionProfile.from_map() } end def parse!(%{"type" => "command_approval_requested"} = map) do %CommandApprovalRequested{ id: Map.fetch!(map, "id"), thread_id: Map.fetch!(map, "thread_id"), turn_id: Map.fetch!(map, "turn_id"), item_id: Map.fetch!(map, "item_id"), approval_id: Map.get(map, "approval_id"), reason: Map.get(map, "reason"), command: Map.get(map, "command"), cwd: Map.get(map, "cwd"), command_actions: Map.get(map, "command_actions"), network_approval_context: Map.get(map, "network_approval_context"), additional_permissions: map |> Map.get("additional_permissions") |> RequestPermissions.RequestPermissionProfile.from_map(), skill_metadata: Map.get(map, "skill_metadata"), proposed_execpolicy_amendment: Map.get(map, "proposed_execpolicy_amendment"), proposed_network_policy_amendments: Map.get(map, "proposed_network_policy_amendments"), available_decisions: Map.get(map, "available_decisions") } end def parse!(%{"type" => "file_approval_requested"} = map) do %FileApprovalRequested{ id: Map.fetch!(map, "id"), thread_id: Map.fetch!(map, "thread_id"), turn_id: Map.fetch!(map, "turn_id"), item_id: Map.fetch!(map, "item_id"), reason: Map.get(map, "reason"), grant_root: Map.get(map, "grant_root") } end def parse!(%{"type" => "guardian_approval_review_started"} = map) do %GuardianApprovalReviewStarted{ thread_id: Map.fetch!(map, "thread_id"), turn_id: Map.fetch!(map, "turn_id"), target_item_id: Map.fetch!(map, "target_item_id"), review: parse_guardian_review(Map.get(map, "review") || %{}), action: Map.get(map, "action") } end def parse!(%{"type" => "guardian_approval_review_completed"} = map) do %GuardianApprovalReviewCompleted{ thread_id: Map.fetch!(map, "thread_id"), turn_id: Map.fetch!(map, "turn_id"), target_item_id: Map.fetch!(map, "target_item_id"), review: parse_guardian_review(Map.get(map, "review") || %{}), action: Map.get(map, "action") } end def parse!(%{"type" => "server_request_resolved"} = map) do %ServerRequestResolved{ thread_id: Map.fetch!(map, "thread_id"), request_id: Map.fetch!(map, "request_id") } end def parse!(%{"type" => "dynamic_tool_call_requested"} = map) do %DynamicToolCallRequested{ id: Map.fetch!(map, "id"), thread_id: Map.fetch!(map, "thread_id"), turn_id: Map.fetch!(map, "turn_id"), call_id: Map.fetch!(map, "call_id"), tool_name: Map.fetch!(map, "tool_name"), arguments: Map.get(map, "arguments") } end def parse!(%{"type" => "chatgpt_auth_tokens_refresh_requested"} = map) do %ChatgptAuthTokensRefreshRequested{ id: Map.fetch!(map, "id"), reason: Map.fetch!(map, "reason"), previous_account_id: Map.get(map, "previous_account_id") } end def parse!(%{"type" => type} = map) when type in [ "mcpServer/startupStatus/updated", "mcp_server_startup_status_updated", "mcpServerStartupStatusUpdated" ] do %McpServerStartupStatusUpdated{ name: Map.fetch!(map, "name"), status: map |> Map.get("status") |> normalize_mcp_server_startup_status(), error: Map.get(map, "error") } end def parse!(%{"type" => type} = map) when type in ["mcp_startup_update", "mcpStartupUpdate"] do {status, message} = map |> Map.get("status") |> normalize_mcp_startup_status() %McpStartupUpdate{ server_name: Map.get(map, "server") || Map.get(map, "server_name") || Map.get(map, "serverName"), status: status, message: message || Map.get(map, "message") } end def parse!(%{"type" => type} = map) when type in ["mcp_startup_complete", "mcpStartupComplete"] do %McpStartupComplete{ servers: normalize_mcp_startup_complete(map) } end def parse!(%{"type" => type} = map) when type in ["elicitation_request", "elicitationRequest"] do %ElicitationRequest{ server_name: Map.get(map, "server_name") || Map.get(map, "serverName") || Map.get(map, "server"), id: Map.get(map, "id"), message: Map.get(map, "message") } end def parse!(%{"type" => type} = map) when type in ["undo_started", "undoStarted"] do %UndoStarted{ turn_id: Map.get(map, "turn_id") || Map.get(map, "turnId"), message: Map.get(map, "message") } end def parse!(%{"type" => type} = map) when type in ["undo_completed", "undoCompleted"] do %UndoCompleted{ turn_id: Map.get(map, "turn_id") || Map.get(map, "turnId"), success: Map.get(map, "success"), message: Map.get(map, "message") } end def parse!(%{"type" => type} = map) when type in ["turn_aborted", "turnAborted", "turn.aborted"] do %TurnAborted{ turn_id: Map.get(map, "turn_id") || Map.get(map, "turnId"), reason: Map.get(map, "reason") } end def parse!(%{"type" => type}) when type in ["shutdown_complete", "shutdownComplete"] do %ShutdownComplete{} end def parse!(%{"type" => type} = map) when type in ["entered_review_mode", "enteredReviewMode"] do review_request = Map.get(map, "review_request") || Map.get(map, "reviewRequest") || Map.drop(map, ["type"]) %EnteredReviewMode{review_request: review_request} end def parse!(%{"type" => type} = map) when type in ["exited_review_mode", "exitedReviewMode"] do result = Map.get(map, "review_output") || Map.get(map, "reviewOutput") || Map.get(map, "result") || Map.drop(map, ["type"]) %ExitedReviewMode{result: result} end def parse!(%{"type" => type} = map) when type in ["config_warning", "configWarning"] do %ConfigWarning{ summary: Map.get(map, "summary") || "", details: Map.get(map, "details") } end def parse!(%{"type" => "collab_agent_spawn_begin"} = map) do %CollabAgentSpawnBegin{ call_id: Map.get(map, "call_id") || Map.get(map, "callId") || Map.get(map, "id"), sender_thread_id: Map.get(map, "sender_thread_id") || Map.get(map, "senderThreadId"), prompt: Map.get(map, "prompt"), model: Map.get(map, "model"), reasoning_effort: normalize_reasoning_effort( Map.get(map, "reasoning_effort") || Map.get(map, "reasoningEffort") ) } end def parse!(%{"type" => "collab_agent_spawn_end"} = map) do %CollabAgentSpawnEnd{ call_id: Map.get(map, "call_id") || Map.get(map, "callId") || Map.get(map, "id"), sender_thread_id: Map.get(map, "sender_thread_id") || Map.get(map, "senderThreadId"), new_thread_id: Map.get(map, "new_thread_id") || Map.get(map, "newThreadId"), new_agent_nickname: Map.get(map, "new_agent_nickname") || Map.get(map, "newAgentNickname"), new_agent_role: Map.get(map, "new_agent_role") || Map.get(map, "newAgentRole"), prompt: Map.get(map, "prompt"), model: Map.get(map, "model"), reasoning_effort: normalize_reasoning_effort( Map.get(map, "reasoning_effort") || Map.get(map, "reasoningEffort") ), status: normalize_collab_agent_state(Map.get(map, "status")) } end def parse!(%{"type" => "collab_agent_interaction_begin"} = map) do %CollabAgentInteractionBegin{ call_id: Map.get(map, "call_id") || Map.get(map, "callId") || Map.get(map, "id"), sender_thread_id: Map.get(map, "sender_thread_id") || Map.get(map, "senderThreadId"), receiver_thread_id: Map.get(map, "receiver_thread_id") || Map.get(map, "receiverThreadId"), prompt: Map.get(map, "prompt") } end def parse!(%{"type" => "collab_agent_interaction_end"} = map) do %CollabAgentInteractionEnd{ call_id: Map.get(map, "call_id") || Map.get(map, "callId") || Map.get(map, "id"), sender_thread_id: Map.get(map, "sender_thread_id") || Map.get(map, "senderThreadId"), receiver_thread_id: Map.get(map, "receiver_thread_id") || Map.get(map, "receiverThreadId"), receiver_agent_nickname: Map.get(map, "receiver_agent_nickname") || Map.get(map, "receiverAgentNickname"), receiver_agent_role: Map.get(map, "receiver_agent_role") || Map.get(map, "receiverAgentRole"), prompt: Map.get(map, "prompt"), status: normalize_collab_agent_state(Map.get(map, "status")) } end def parse!(%{"type" => "collab_waiting_begin"} = map) do %CollabWaitingBegin{ sender_thread_id: Map.get(map, "sender_thread_id") || Map.get(map, "senderThreadId"), receiver_thread_ids: Map.get(map, "receiver_thread_ids") || Map.get(map, "receiverThreadIds") || [], receiver_agents: map |> Map.get("receiver_agents", Map.get(map, "receiverAgents", [])) |> normalize_collab_agent_refs(), call_id: Map.get(map, "call_id") || Map.get(map, "callId") || Map.get(map, "id") } end def parse!(%{"type" => "collab_waiting_end"} = map) do %CollabWaitingEnd{ sender_thread_id: Map.get(map, "sender_thread_id") || Map.get(map, "senderThreadId"), call_id: Map.get(map, "call_id") || Map.get(map, "callId") || Map.get(map, "id"), agent_statuses: map |> Map.get("agent_statuses", Map.get(map, "agentStatuses", [])) |> normalize_collab_agent_status_entries(), statuses: normalize_collab_agent_states_map(Map.get(map, "statuses")) } end def parse!(%{"type" => "collab_close_begin"} = map) do %CollabCloseBegin{ call_id: Map.get(map, "call_id") || Map.get(map, "callId") || Map.get(map, "id"), sender_thread_id: Map.get(map, "sender_thread_id") || Map.get(map, "senderThreadId"), receiver_thread_id: Map.get(map, "receiver_thread_id") || Map.get(map, "receiverThreadId") } end def parse!(%{"type" => "collab_close_end"} = map) do %CollabCloseEnd{ call_id: Map.get(map, "call_id") || Map.get(map, "callId") || Map.get(map, "id"), sender_thread_id: Map.get(map, "sender_thread_id") || Map.get(map, "senderThreadId"), receiver_thread_id: Map.get(map, "receiver_thread_id") || Map.get(map, "receiverThreadId"), receiver_agent_nickname: Map.get(map, "receiver_agent_nickname") || Map.get(map, "receiverAgentNickname"), receiver_agent_role: Map.get(map, "receiver_agent_role") || Map.get(map, "receiverAgentRole"), status: normalize_collab_agent_state(Map.get(map, "status")) } end def parse!(%{"type" => "collab_resume_begin"} = map) do %CollabResumeBegin{ call_id: Map.get(map, "call_id") || Map.get(map, "callId") || Map.get(map, "id"), sender_thread_id: Map.get(map, "sender_thread_id") || Map.get(map, "senderThreadId"), receiver_thread_id: Map.get(map, "receiver_thread_id") || Map.get(map, "receiverThreadId"), receiver_agent_nickname: Map.get(map, "receiver_agent_nickname") || Map.get(map, "receiverAgentNickname"), receiver_agent_role: Map.get(map, "receiver_agent_role") || Map.get(map, "receiverAgentRole") } end def parse!(%{"type" => "collab_resume_end"} = map) do %CollabResumeEnd{ call_id: Map.get(map, "call_id") || Map.get(map, "callId") || Map.get(map, "id"), sender_thread_id: Map.get(map, "sender_thread_id") || Map.get(map, "senderThreadId"), receiver_thread_id: Map.get(map, "receiver_thread_id") || Map.get(map, "receiverThreadId"), receiver_agent_nickname: Map.get(map, "receiver_agent_nickname") || Map.get(map, "receiverAgentNickname"), receiver_agent_role: Map.get(map, "receiver_agent_role") || Map.get(map, "receiverAgentRole"), status: normalize_collab_agent_state(Map.get(map, "status")) } end def parse!(%{"type" => <<"turn.compaction", _rest::binary>>} = map), do: parse_compaction(map) def parse!(%{"type" => <<"turn/compaction", _rest::binary>>} = map), do: parse_compaction(map) def parse!(%{"type" => "item.agent_message.delta"} = map) do %ItemAgentMessageDelta{ item: Map.fetch!(map, "item"), thread_id: Map.get(map, "thread_id"), turn_id: Map.get(map, "turn_id") } end def parse!(%{"type" => "item.input_text.delta"} = map) do %ItemInputTextDelta{ item: Map.fetch!(map, "item"), thread_id: Map.get(map, "thread_id"), turn_id: Map.get(map, "turn_id") } end def parse!(%{"type" => "item.completed"} = map) do %ItemCompleted{ item: map |> Map.fetch!("item") |> Items.parse!(), thread_id: Map.get(map, "thread_id"), turn_id: Map.get(map, "turn_id") } end def parse!(%{"type" => "item.started"} = map) do %ItemStarted{ item: map |> Map.fetch!("item") |> Items.parse!(), thread_id: Map.get(map, "thread_id"), turn_id: Map.get(map, "turn_id") } end def parse!(%{"type" => "item.updated"} = map) do %ItemUpdated{ item: map |> Map.fetch!("item") |> Items.parse!(), thread_id: Map.get(map, "thread_id"), turn_id: Map.get(map, "turn_id") } end def parse!(%{"type" => "deprecationNotice"} = map) do %DeprecationNotice{ summary: Map.get(map, "summary") || "", details: Map.get(map, "details") } end def parse!(%{"type" => "app/list/updated"} = map) do %AppListUpdated{ data: Map.get(map, "data") || [] } end def parse!(%{"type" => "model/rerouted"} = map) do %ModelRerouted{ thread_id: Map.fetch!(map, "thread_id"), turn_id: Map.fetch!(map, "turn_id"), from_model: Map.fetch!(map, "from_model"), to_model: Map.fetch!(map, "to_model"), reason: normalize_model_reroute_reason(Map.get(map, "reason")) } end def parse!(%{"type" => "fuzzyFileSearch/sessionUpdated"} = map) do %FuzzyFileSearchSessionUpdated{ session_id: Map.fetch!(map, "session_id"), query: Map.fetch!(map, "query"), files: Map.get(map, "files") || [] } end def parse!(%{"type" => "fuzzyFileSearch/sessionCompleted"} = map) do %FuzzyFileSearchSessionCompleted{ session_id: Map.fetch!(map, "session_id") } end def parse!(%{"type" => "thread/realtime/started"} = map) do %ThreadRealtimeStarted{ thread_id: Map.fetch!(map, "thread_id"), session_id: Map.get(map, "session_id") } end def parse!(%{"type" => "thread/realtime/itemAdded"} = map) do %ThreadRealtimeItemAdded{ thread_id: Map.fetch!(map, "thread_id"), item: Map.get(map, "item") } end def parse!(%{"type" => "thread/realtime/outputAudio/delta"} = map) do %ThreadRealtimeOutputAudioDelta{ thread_id: Map.fetch!(map, "thread_id"), audio: Map.get(map, "audio") || %{} } end def parse!(%{"type" => "thread/realtime/error"} = map) do %ThreadRealtimeError{ thread_id: Map.fetch!(map, "thread_id"), message: Map.get(map, "message") || "" } end def parse!(%{"type" => "thread/realtime/closed"} = map) do %ThreadRealtimeClosed{ thread_id: Map.fetch!(map, "thread_id"), reason: Map.get(map, "reason") } end def parse!(%{"type" => "account/updated"} = map) do %AccountUpdated{ auth_mode: Map.get(map, "auth_mode") || Map.get(map, "authMode"), plan_type: map |> Map.get("plan_type") |> case do nil -> Map.get(map, "planType") value -> value end |> normalize_plan_type() } end def parse!(%{"type" => "account/login/completed"} = map) do %AccountLoginCompleted{ login_id: Map.get(map, "login_id") || Map.get(map, "loginId"), success: Map.get(map, "success") || false, error: Map.get(map, "error") } end def parse!(%{"type" => "account/rateLimits/updated"} = map) do rate_limits = map |> Map.get("rate_limits") |> case do nil -> Map.get(map, "rateLimits") value -> value end |> parse_rate_limits() %AccountRateLimitsUpdated{ rate_limits: rate_limits || %{}, thread_id: Map.get(map, "thread_id") || Map.get(map, "threadId"), turn_id: Map.get(map, "turn_id") || Map.get(map, "turnId") } end def parse!(%{"type" => type} = map) when type in ["rawResponseItem/completed", "rawResponseItem.completed"] do item_map = Map.get(map, "item") || %{} item = case Items.parse_raw_response_item(item_map) do {:ok, parsed} -> parsed {:error, _} -> item_map end %RawResponseItemCompleted{ thread_id: Map.get(map, "thread_id") || Map.get(map, "threadId"), turn_id: Map.get(map, "turn_id") || Map.get(map, "turnId"), item: item } end def parse!(%{"type" => "error"} = map) do %Error{ message: Map.get(map, "message"), thread_id: Map.get(map, "thread_id"), turn_id: Map.get(map, "turn_id"), additional_details: Map.get(map, "additional_details") || Map.get(map, "additionalDetails"), codex_error_info: Map.get(map, "codex_error_info") || Map.get(map, "codexErrorInfo"), will_retry: Map.get(map, "will_retry") || Map.get(map, "willRetry") } end def parse!(%{"type" => "turn.failed"} = map) do %TurnFailed{ error: Map.get(map, "error", %{}), thread_id: Map.get(map, "thread_id"), turn_id: Map.get(map, "turn_id") } end def parse!(%{"type" => "tool.call.required"} = map) do %ToolCallRequested{ thread_id: Map.fetch!(map, "thread_id"), turn_id: Map.fetch!(map, "turn_id"), call_id: Map.fetch!(map, "call_id"), tool_name: Map.fetch!(map, "tool_name"), arguments: Map.get(map, "arguments", %{}), requires_approval: Map.get(map, "requires_approval", false), approved: Map.get(map, "approved"), approved_by_policy: Map.get(map, "approved_by_policy"), sandbox_warnings: Map.get(map, "sandbox_warnings") || Map.get(map, "warnings"), capabilities: Map.get(map, "capabilities") } end def parse!(%{"type" => "tool.call.completed"} = map) do %ToolCallCompleted{ thread_id: Map.fetch!(map, "thread_id"), turn_id: Map.fetch!(map, "turn_id"), call_id: Map.fetch!(map, "call_id"), tool_name: Map.fetch!(map, "tool_name"), output: Map.get(map, "output", %{}) } end def parse!(%{"type" => unknown}) do raise ArgumentError, "unsupported codex event #{inspect(unknown)}" end def parse!(other) do raise ArgumentError, "expected codex event map, got: #{inspect(other)}" end @doc """ Converts a typed event struct back into the JSON-serializable map representation. """ @spec to_map(t()) :: map() def to_map(%ThreadStarted{} = event) do %{ "type" => "thread.started", "thread_id" => event.thread_id } |> put_optional("metadata", event.metadata) end def to_map(%ThreadStatusChanged{} = event) do %{ "type" => "thread/status/changed", "thread_id" => event.thread_id } |> put_optional("status", encode_thread_status(event.status)) end def to_map(%ThreadArchived{} = event) do %{ "type" => "thread/archived", "thread_id" => event.thread_id } end def to_map(%ThreadUnarchived{} = event) do %{ "type" => "thread/unarchived", "thread_id" => event.thread_id } end def to_map(%SkillsChanged{}) do %{"type" => "skills/changed"} end def to_map(%ThreadNameUpdated{} = event) do %{ "type" => "thread/name/updated", "thread_id" => event.thread_id } |> put_optional("thread_name", event.thread_name) end def to_map(%TurnStarted{} = event) do %{ "type" => "turn.started", "turn_id" => event.turn_id, "thread_id" => event.thread_id } end def to_map(%HookStarted{} = event) do %{ "type" => "hook/started", "thread_id" => event.thread_id, "run" => event.run || %{} } |> put_optional("turn_id", event.turn_id) end def to_map(%TurnContinuation{} = event) do %{ "type" => "turn.continuation", "thread_id" => event.thread_id, "turn_id" => event.turn_id, "continuation_token" => event.continuation_token, "retryable" => event.retryable } |> put_optional("reason", event.reason) end def to_map(%TurnCompleted{} = event) do %{ "type" => "turn.completed", "thread_id" => event.thread_id, "turn_id" => event.turn_id } |> put_optional("response_id", event.response_id) |> put_optional("final_response", encode_final_response(event.final_response)) |> put_optional("usage", event.usage) |> put_optional("status", event.status) |> put_optional("error", event.error) end def to_map(%HookCompleted{} = event) do %{ "type" => "hook/completed", "thread_id" => event.thread_id, "run" => event.run || %{} } |> put_optional("turn_id", event.turn_id) end def to_map(%ThreadTokenUsageUpdated{} = event) do %{ "type" => "thread/tokenUsage/updated" } |> put_optional("thread_id", event.thread_id) |> put_optional("turn_id", event.turn_id) |> put_optional("usage", event.usage) |> put_optional("delta", event.delta) |> put_optional("rate_limits", encode_rate_limits(event.rate_limits)) end def to_map(%TurnDiffUpdated{} = event) do %{ "type" => "turn/diff/updated" } |> put_optional("thread_id", event.thread_id) |> put_optional("turn_id", event.turn_id) |> put_optional("diff", event.diff) end def to_map(%TurnPlanUpdated{} = event) do %{ "type" => "turn/plan/updated" } |> put_optional("thread_id", event.thread_id) |> put_optional("turn_id", event.turn_id) |> put_optional("explanation", event.explanation) |> put_optional("plan", event.plan) end def to_map(%TurnCompaction{} = event) do %{ "type" => build_compaction_type(event.stage) } |> put_optional("thread_id", event.thread_id) |> put_optional("turn_id", event.turn_id) |> put_optional("compaction", event.compaction) end def to_map(%ItemAgentMessageDelta{} = event) do %{ "type" => "item.agent_message.delta", "item" => event.item } |> put_optional("thread_id", event.thread_id) |> put_optional("turn_id", event.turn_id) end def to_map(%ItemInputTextDelta{} = event) do %{ "type" => "item.input_text.delta", "item" => event.item } |> put_optional("thread_id", event.thread_id) |> put_optional("turn_id", event.turn_id) end def to_map(%ItemCompleted{} = event) do %{ "type" => "item.completed", "item" => Items.to_map(event.item) } |> put_optional("thread_id", event.thread_id) |> put_optional("turn_id", event.turn_id) end def to_map(%ItemStarted{} = event) do %{ "type" => "item.started", "item" => Items.to_map(event.item) } |> put_optional("thread_id", event.thread_id) |> put_optional("turn_id", event.turn_id) end def to_map(%ItemUpdated{} = event) do %{ "type" => "item.updated", "item" => Items.to_map(event.item) } |> put_optional("thread_id", event.thread_id) |> put_optional("turn_id", event.turn_id) end def to_map(%CommandOutputDelta{} = event) do %{ "type" => "item/commandExecution/outputDelta", "item_id" => event.item_id, "delta" => event.delta } |> put_optional("thread_id", event.thread_id) |> put_optional("turn_id", event.turn_id) end def to_map(%FileChangeOutputDelta{} = event) do %{ "type" => "item/fileChange/outputDelta", "item_id" => event.item_id, "delta" => event.delta } |> put_optional("thread_id", event.thread_id) |> put_optional("turn_id", event.turn_id) end def to_map(%TerminalInteraction{} = event) do %{ "type" => "item/commandExecution/terminalInteraction", "item_id" => event.item_id, "process_id" => event.process_id, "stdin" => event.stdin } |> put_optional("thread_id", event.thread_id) |> put_optional("turn_id", event.turn_id) end def to_map(%ReasoningDelta{} = event) do %{ "type" => "item/reasoning/textDelta", "item_id" => event.item_id, "delta" => event.delta } |> put_optional("thread_id", event.thread_id) |> put_optional("turn_id", event.turn_id) |> put_optional("content_index", event.content_index) end def to_map(%ReasoningSummaryDelta{} = event) do %{ "type" => "item/reasoning/summaryTextDelta", "item_id" => event.item_id, "delta" => event.delta } |> put_optional("thread_id", event.thread_id) |> put_optional("turn_id", event.turn_id) |> put_optional("summary_index", event.summary_index) end def to_map(%ReasoningSummaryPartAdded{} = event) do %{ "type" => "item/reasoning/summaryPartAdded", "item_id" => event.item_id } |> put_optional("thread_id", event.thread_id) |> put_optional("turn_id", event.turn_id) |> put_optional("summary_index", event.summary_index) end def to_map(%McpToolCallProgress{} = event) do %{ "type" => "item/mcpToolCall/progress", "item_id" => event.item_id, "message" => event.message } |> put_optional("thread_id", event.thread_id) |> put_optional("turn_id", event.turn_id) end def to_map(%McpServerOauthLoginCompleted{} = event) do %{ "type" => "mcpServer/oauthLogin/completed", "name" => event.name, "success" => event.success } |> put_optional("error", event.error) end def to_map(%AccountUpdated{} = event) do %{ "type" => "account/updated" } |> put_optional("auth_mode", event.auth_mode) |> put_optional("plan_type", encode_plan_type(event.plan_type)) end def to_map(%AccountRateLimitsUpdated{} = event) do %{ "type" => "account/rateLimits/updated", "rate_limits" => encode_rate_limits(event.rate_limits) || %{} } |> put_optional("thread_id", event.thread_id) |> put_optional("turn_id", event.turn_id) end def to_map(%AccountLoginCompleted{} = event) do %{ "type" => "account/login/completed", "success" => event.success } |> put_optional("login_id", event.login_id) |> put_optional("error", event.error) end def to_map(%WindowsWorldWritableWarning{} = event) do %{ "type" => "windows/worldWritableWarning", "sample_paths" => event.sample_paths, "extra_count" => event.extra_count, "failed_scan" => event.failed_scan } end def to_map(%DeprecationNotice{} = event) do %{ "type" => "deprecationNotice", "summary" => event.summary } |> put_optional("details", event.details) end def to_map(%AppListUpdated{} = event) do %{ "type" => "app/list/updated", "data" => event.data } end def to_map(%ModelRerouted{} = event) do %{ "type" => "model/rerouted", "thread_id" => event.thread_id, "turn_id" => event.turn_id, "from_model" => event.from_model, "to_model" => event.to_model, "reason" => encode_model_reroute_reason(event.reason) } end def to_map(%FuzzyFileSearchSessionUpdated{} = event) do %{ "type" => "fuzzyFileSearch/sessionUpdated", "session_id" => event.session_id, "query" => event.query, "files" => event.files } end def to_map(%FuzzyFileSearchSessionCompleted{} = event) do %{ "type" => "fuzzyFileSearch/sessionCompleted", "session_id" => event.session_id } end def to_map(%ThreadRealtimeStarted{} = event) do %{ "type" => "thread/realtime/started", "thread_id" => event.thread_id } |> put_optional("session_id", event.session_id) end def to_map(%ThreadRealtimeItemAdded{} = event) do %{ "type" => "thread/realtime/itemAdded", "thread_id" => event.thread_id, "item" => event.item } end def to_map(%ThreadRealtimeOutputAudioDelta{} = event) do %{ "type" => "thread/realtime/outputAudio/delta", "thread_id" => event.thread_id, "audio" => event.audio } end def to_map(%ThreadRealtimeError{} = event) do %{ "type" => "thread/realtime/error", "thread_id" => event.thread_id, "message" => event.message } end def to_map(%ThreadRealtimeClosed{} = event) do %{ "type" => "thread/realtime/closed", "thread_id" => event.thread_id } |> put_optional("reason", event.reason) end def to_map(%RawResponseItemCompleted{} = event) do %{ "type" => "rawResponseItem/completed", "item" => encode_raw_item(event.item) } |> put_optional("thread_id", event.thread_id) |> put_optional("turn_id", event.turn_id) end def to_map(%Error{} = event) do %{ "type" => "error", "message" => event.message } |> put_optional("thread_id", event.thread_id) |> put_optional("turn_id", event.turn_id) |> put_optional("additional_details", event.additional_details) |> put_optional("codex_error_info", event.codex_error_info) |> put_optional("will_retry", event.will_retry) end def to_map(%TurnFailed{} = event) do %{ "type" => "turn.failed", "error" => event.error } |> put_optional("thread_id", event.thread_id) |> put_optional("turn_id", event.turn_id) end def to_map(%ToolCallRequested{} = event) do %{ "type" => "tool.call.required", "thread_id" => event.thread_id, "turn_id" => event.turn_id, "call_id" => event.call_id, "tool_name" => event.tool_name, "arguments" => event.arguments, "requires_approval" => event.requires_approval } |> put_optional("approved", event.approved) |> put_optional("approved_by_policy", event.approved_by_policy) |> put_optional("sandbox_warnings", event.sandbox_warnings) |> put_optional("capabilities", event.capabilities) end def to_map(%ToolCallCompleted{} = event) do %{ "type" => "tool.call.completed", "thread_id" => event.thread_id, "turn_id" => event.turn_id, "call_id" => event.call_id, "tool_name" => event.tool_name, "output" => event.output } end def to_map(%SessionConfigured{} = event) do %{ "type" => "session_configured" } |> put_optional("session_id", event.session_id) |> put_optional("forked_from_id", event.forked_from_id) |> put_optional("model", event.model) |> put_optional("model_provider_id", event.model_provider_id) |> put_optional("approval_policy", event.approval_policy) |> put_optional("approvals_reviewer", event.approvals_reviewer) |> put_optional("sandbox_policy", event.sandbox_policy) |> put_optional("cwd", event.cwd) |> put_optional("reasoning_effort", event.reasoning_effort) |> put_optional("history_log_id", event.history_log_id) |> put_optional("history_entry_count", event.history_entry_count) |> put_optional("initial_messages", encode_initial_messages(event.initial_messages)) |> put_optional("rollout_path", event.rollout_path) end def to_map(%Warning{} = event) do %{ "type" => "warning", "message" => event.message } end def to_map(%ContextCompacted{} = event) do %{ "type" => "context_compacted" } |> put_optional("removed_turns", event.removed_turns) |> put_optional("remaining_turns", event.remaining_turns) end def to_map(%ThreadRolledBack{} = event) do %{ "type" => "thread_rolled_back" } |> put_optional("num_turns", event.num_turns) end def to_map(%RequestUserInput{} = event) do %{ "type" => "request_user_input" } |> put_optional("id", event.id) |> put_optional("thread_id", event.thread_id) |> put_optional("turn_id", event.turn_id) |> put_optional("item_id", event.item_id) |> put_optional("questions", encode_request_user_input_questions(event.questions)) end def to_map(%McpElicitationRequested{} = event) do %{ "type" => "mcp_elicitation_requested", "id" => event.id, "thread_id" => event.thread_id, "server_name" => event.server_name, "request" => event.request } |> put_optional("turn_id", event.turn_id) |> put_optional("request_mode", event.request_mode) |> put_optional("message", event.message) end def to_map(%PermissionsApprovalRequested{} = event) do %{ "type" => "permissions_approval_requested", "id" => event.id, "thread_id" => event.thread_id, "turn_id" => event.turn_id, "item_id" => event.item_id, "permissions" => event.permissions |> RequestPermissions.RequestPermissionProfile.from_map() |> RequestPermissions.RequestPermissionProfile.to_map() } |> put_optional("reason", event.reason) end def to_map(%CommandApprovalRequested{} = event) do %{ "type" => "command_approval_requested", "id" => event.id, "thread_id" => event.thread_id, "turn_id" => event.turn_id, "item_id" => event.item_id } |> put_optional("approval_id", event.approval_id) |> put_optional("reason", event.reason) |> put_optional("command", event.command) |> put_optional("cwd", event.cwd) |> put_optional("command_actions", event.command_actions) |> put_optional("network_approval_context", event.network_approval_context) |> put_optional( "additional_permissions", event.additional_permissions && event.additional_permissions |> RequestPermissions.RequestPermissionProfile.from_map() |> RequestPermissions.RequestPermissionProfile.to_map() ) |> put_optional("skill_metadata", event.skill_metadata) |> put_optional("proposed_execpolicy_amendment", event.proposed_execpolicy_amendment) |> put_optional( "proposed_network_policy_amendments", event.proposed_network_policy_amendments ) |> put_optional("available_decisions", event.available_decisions) end def to_map(%FileApprovalRequested{} = event) do %{ "type" => "file_approval_requested", "id" => event.id, "thread_id" => event.thread_id, "turn_id" => event.turn_id, "item_id" => event.item_id } |> put_optional("reason", event.reason) |> put_optional("grant_root", event.grant_root) end def to_map(%GuardianApprovalReviewStarted{} = event) do %{ "type" => "guardian_approval_review_started", "thread_id" => event.thread_id, "turn_id" => event.turn_id, "target_item_id" => event.target_item_id, "review" => guardian_review_to_map(event.review) } |> put_optional("action", event.action) end def to_map(%GuardianApprovalReviewCompleted{} = event) do %{ "type" => "guardian_approval_review_completed", "thread_id" => event.thread_id, "turn_id" => event.turn_id, "target_item_id" => event.target_item_id, "review" => guardian_review_to_map(event.review) } |> put_optional("action", event.action) end def to_map(%ServerRequestResolved{} = event) do %{ "type" => "server_request_resolved", "thread_id" => event.thread_id, "request_id" => event.request_id } end def to_map(%DynamicToolCallRequested{} = event) do %{ "type" => "dynamic_tool_call_requested", "id" => event.id, "thread_id" => event.thread_id, "turn_id" => event.turn_id, "call_id" => event.call_id, "tool_name" => event.tool_name } |> put_optional("arguments", event.arguments) end def to_map(%ChatgptAuthTokensRefreshRequested{} = event) do %{ "type" => "chatgpt_auth_tokens_refresh_requested", "id" => event.id, "reason" => event.reason } |> put_optional("previous_account_id", event.previous_account_id) end def to_map(%McpStartupUpdate{} = event) do %{ "type" => "mcp_startup_update", "server" => event.server_name } |> put_optional("status", event.status) |> put_optional("message", event.message) end def to_map(%McpServerStartupStatusUpdated{} = event) do %{ "type" => "mcpServer/startupStatus/updated", "name" => event.name, "status" => encode_mcp_server_startup_status(event.status) } |> put_optional("error", event.error) end def to_map(%McpStartupComplete{} = event) do %{"type" => "mcp_startup_complete"} |> Map.merge(encode_mcp_startup_complete(event.servers)) end def to_map(%ElicitationRequest{} = event) do %{ "type" => "elicitation_request", "server_name" => event.server_name, "id" => event.id, "message" => event.message } end def to_map(%UndoStarted{} = event) do %{ "type" => "undo_started" } |> put_optional("turn_id", event.turn_id) |> put_optional("message", event.message) end def to_map(%UndoCompleted{} = event) do %{ "type" => "undo_completed" } |> put_optional("turn_id", event.turn_id) |> put_optional("success", event.success) |> put_optional("message", event.message) end def to_map(%TurnAborted{} = event) do %{ "type" => "turn_aborted" } |> put_optional("turn_id", event.turn_id) |> put_optional("reason", event.reason) end def to_map(%ShutdownComplete{}) do %{"type" => "shutdown_complete"} end def to_map(%EnteredReviewMode{} = event) do base = %{"type" => "entered_review_mode"} case event.review_request do %{} = request -> Map.merge(base, request) nil -> base other -> Map.put(base, "review_request", other) end end def to_map(%ExitedReviewMode{} = event) do %{"type" => "exited_review_mode"} |> put_optional("review_output", event.result) end def to_map(%ConfigWarning{} = event) do %{ "type" => "configWarning", "summary" => event.summary } |> put_optional("details", event.details) end def to_map(%CollabAgentSpawnBegin{} = event) do %{ "type" => "collab_agent_spawn_begin", "call_id" => event.call_id, "sender_thread_id" => event.sender_thread_id } |> put_optional("prompt", event.prompt) |> put_optional("model", event.model) |> put_optional("reasoning_effort", event.reasoning_effort) end def to_map(%CollabAgentSpawnEnd{} = event) do %{ "type" => "collab_agent_spawn_end", "call_id" => event.call_id, "sender_thread_id" => event.sender_thread_id } |> put_optional("new_thread_id", event.new_thread_id) |> put_optional("new_agent_nickname", event.new_agent_nickname) |> put_optional("new_agent_role", event.new_agent_role) |> put_optional("prompt", event.prompt) |> put_optional("model", event.model) |> put_optional("reasoning_effort", event.reasoning_effort) |> put_optional("status", collab_agent_state_to_event_value(event.status)) end def to_map(%CollabAgentInteractionBegin{} = event) do %{ "type" => "collab_agent_interaction_begin", "call_id" => event.call_id, "sender_thread_id" => event.sender_thread_id, "receiver_thread_id" => event.receiver_thread_id } |> put_optional("prompt", event.prompt) end def to_map(%CollabAgentInteractionEnd{} = event) do %{ "type" => "collab_agent_interaction_end", "call_id" => event.call_id, "sender_thread_id" => event.sender_thread_id, "receiver_thread_id" => event.receiver_thread_id } |> put_optional("receiver_agent_nickname", event.receiver_agent_nickname) |> put_optional("receiver_agent_role", event.receiver_agent_role) |> put_optional("prompt", event.prompt) |> put_optional("status", collab_agent_state_to_event_value(event.status)) end def to_map(%CollabWaitingBegin{} = event) do %{ "type" => "collab_waiting_begin", "sender_thread_id" => event.sender_thread_id, "receiver_thread_ids" => event.receiver_thread_ids, "call_id" => event.call_id } |> put_optional("receiver_agents", encode_collab_agent_refs(event.receiver_agents)) end def to_map(%CollabWaitingEnd{} = event) do %{ "type" => "collab_waiting_end", "sender_thread_id" => event.sender_thread_id, "call_id" => event.call_id } |> put_optional("agent_statuses", encode_collab_agent_status_entries(event.agent_statuses)) |> put_optional("statuses", encode_collab_agent_states_map(event.statuses)) end def to_map(%CollabCloseBegin{} = event) do %{ "type" => "collab_close_begin", "call_id" => event.call_id, "sender_thread_id" => event.sender_thread_id, "receiver_thread_id" => event.receiver_thread_id } end def to_map(%CollabCloseEnd{} = event) do %{ "type" => "collab_close_end", "call_id" => event.call_id, "sender_thread_id" => event.sender_thread_id, "receiver_thread_id" => event.receiver_thread_id } |> put_optional("receiver_agent_nickname", event.receiver_agent_nickname) |> put_optional("receiver_agent_role", event.receiver_agent_role) |> put_optional("status", collab_agent_state_to_event_value(event.status)) end def to_map(%CollabResumeBegin{} = event) do %{ "type" => "collab_resume_begin", "call_id" => event.call_id, "sender_thread_id" => event.sender_thread_id, "receiver_thread_id" => event.receiver_thread_id } |> put_optional("receiver_agent_nickname", event.receiver_agent_nickname) |> put_optional("receiver_agent_role", event.receiver_agent_role) end def to_map(%CollabResumeEnd{} = event) do %{ "type" => "collab_resume_end", "call_id" => event.call_id, "sender_thread_id" => event.sender_thread_id, "receiver_thread_id" => event.receiver_thread_id } |> put_optional("receiver_agent_nickname", event.receiver_agent_nickname) |> put_optional("receiver_agent_role", event.receiver_agent_role) |> put_optional("status", collab_agent_state_to_event_value(event.status)) end defp parse_compaction(map) do %TurnCompaction{ thread_id: Map.get(map, "thread_id"), turn_id: Map.get(map, "turn_id"), compaction: Map.get(map, "compaction", %{}), stage: map |> Map.fetch!("type") |> parse_compaction_stage() } end defp parse_compaction_stage(type) do type |> String.split([".", "/"], trim: true) |> List.last() |> then(&Map.get(@compaction_stage_map, &1, &1 || :unknown)) end defp build_compaction_type(stage) do case stage_to_string(stage) do nil -> "turn/compaction" stage_string -> "turn/compaction/#{stage_string}" end end defp stage_to_string(value) when is_atom(value), do: Atom.to_string(value) defp stage_to_string(value) when is_binary(value), do: value defp stage_to_string(_), do: nil defp parse_initial_messages(nil), do: nil defp parse_initial_messages(messages) when is_list(messages) do Enum.map(messages, &parse_initial_message/1) end defp parse_initial_messages(_), do: nil defp parse_session_configured(%{} = map) do %SessionConfigured{ session_id: fetch_any(map, ["session_id", "sessionId"]), forked_from_id: fetch_any(map, ["forked_from_id", "forkedFromId"]), model: Map.get(map, "model"), model_provider_id: fetch_any(map, ["model_provider_id", "modelProviderId"]), approval_policy: fetch_any(map, ["approval_policy", "approvalPolicy"]), approvals_reviewer: fetch_any(map, ["approvals_reviewer", "approvalsReviewer"]), sandbox_policy: fetch_any(map, ["sandbox_policy", "sandboxPolicy"]), cwd: Map.get(map, "cwd"), reasoning_effort: fetch_any(map, ["reasoning_effort", "reasoningEffort"]), history_log_id: fetch_any(map, ["history_log_id", "historyLogId"]), history_entry_count: fetch_any(map, ["history_entry_count", "historyEntryCount"]), initial_messages: map |> fetch_initial_messages() |> parse_initial_messages(), rollout_path: fetch_any(map, ["rollout_path", "rolloutPath"]) } end defp fetch_initial_messages(%{} = map) do Map.get(map, "initial_messages") || Map.get(map, "initialMessages") end defp fetch_any(%{} = map, keys) when is_list(keys) do Enum.reduce_while(keys, nil, fn key, _acc -> if Map.has_key?(map, key) do {:halt, Map.get(map, key)} else {:cont, nil} end end) end defp parse_initial_message(%{} = message) do parse!(message) rescue _ -> message end defp parse_initial_message(other), do: other defp encode_initial_messages(nil), do: nil defp encode_initial_messages(messages) when is_list(messages) do Enum.map(messages, &encode_initial_message/1) end defp encode_initial_messages(other), do: other defp encode_initial_message(%{__struct__: _} = message), do: to_map(message) defp encode_initial_message(other), do: other defp parse_request_user_input_questions(nil), do: [] defp parse_request_user_input_questions(questions) when is_list(questions) do Enum.map(questions, fn %RequestUserInputQuestion{} = question -> question %{} = question -> RequestUserInputQuestion.from_map(question) other -> other end) end defp parse_request_user_input_questions(_), do: [] defp encode_request_user_input_questions(nil), do: nil defp encode_request_user_input_questions(questions) when is_list(questions) do Enum.map(questions, fn %RequestUserInputQuestion{} = question -> RequestUserInputQuestion.to_map(question) %{} = question -> question other -> other end) end defp encode_request_user_input_questions(other), do: other defp normalize_reasoning_effort(nil), do: nil defp normalize_reasoning_effort(value) do case Codex.Models.normalize_reasoning_effort(value) do {:ok, nil} -> nil {:ok, effort} -> effort _ -> value end end defp normalize_collab_agent_state(nil), do: nil defp normalize_collab_agent_state(state), do: CollabAgentState.from_map(state) defp normalize_collab_agent_refs(list) when is_list(list), do: Enum.map(list, &CollabAgentRef.from_map/1) defp normalize_collab_agent_refs(_), do: [] defp normalize_collab_agent_status_entries(list) when is_list(list), do: Enum.map(list, &CollabAgentStatusEntry.from_map/1) defp normalize_collab_agent_status_entries(_), do: [] defp normalize_collab_agent_states_map(%{} = states) do states |> Enum.map(fn {thread_id, state} -> {to_string(thread_id), CollabAgentState.from_map(state)} end) |> Map.new() end defp normalize_collab_agent_states_map(_), do: nil defp encode_collab_agent_refs(list) when is_list(list) do Enum.map(list, fn %CollabAgentRef{} = ref -> CollabAgentRef.to_map(ref) ref -> ref end) end defp encode_collab_agent_refs(_), do: [] defp encode_collab_agent_status_entries(list) when is_list(list) do Enum.map(list, fn %CollabAgentStatusEntry{} = entry -> CollabAgentStatusEntry.to_map(entry) entry -> entry end) end defp encode_collab_agent_status_entries(_), do: [] defp encode_collab_agent_states_map(%{} = states) do states |> Enum.map(fn {thread_id, state} -> {to_string(thread_id), collab_agent_state_to_event_value(state)} end) |> Map.new() end defp encode_collab_agent_states_map(_), do: nil defp collab_agent_state_to_event_value(nil), do: nil defp collab_agent_state_to_event_value(%CollabAgentState{} = state), do: CollabAgentState.to_event_value(state) defp collab_agent_state_to_event_value(state), do: state defp parse_guardian_review(%{} = review) do %GuardianApprovalReview{ status: review |> Map.get("status") |> parse_guardian_review_status(), risk_score: Map.get(review, "risk_score") || Map.get(review, "riskScore"), risk_level: review |> Map.get("risk_level", Map.get(review, "riskLevel")) |> parse_guardian_risk_level(), rationale: Map.get(review, "rationale") } end defp parse_guardian_review(_review) do %GuardianApprovalReview{status: :in_progress} end defp guardian_review_to_map(%GuardianApprovalReview{} = review) do %{ "status" => guardian_review_status_to_string(review.status) } |> put_optional("risk_score", review.risk_score) |> put_optional("risk_level", guardian_risk_level_to_string(review.risk_level)) |> put_optional("rationale", review.rationale) end defp guardian_review_status_to_string(:in_progress), do: "in_progress" defp guardian_review_status_to_string(:approved), do: "approved" defp guardian_review_status_to_string(:denied), do: "denied" defp guardian_review_status_to_string(:aborted), do: "aborted" defp guardian_review_status_to_string(value) when is_binary(value), do: value defp guardian_review_status_to_string(_), do: "in_progress" defp parse_guardian_review_status("inProgress"), do: :in_progress defp parse_guardian_review_status("in_progress"), do: :in_progress defp parse_guardian_review_status("approved"), do: :approved defp parse_guardian_review_status("denied"), do: :denied defp parse_guardian_review_status("aborted"), do: :aborted defp parse_guardian_review_status(:in_progress), do: :in_progress defp parse_guardian_review_status(:approved), do: :approved defp parse_guardian_review_status(:denied), do: :denied defp parse_guardian_review_status(:aborted), do: :aborted defp parse_guardian_review_status(_), do: :in_progress defp guardian_risk_level_to_string(:low), do: "low" defp guardian_risk_level_to_string(:medium), do: "medium" defp guardian_risk_level_to_string(:high), do: "high" defp guardian_risk_level_to_string(value) when is_binary(value), do: value defp guardian_risk_level_to_string(_), do: nil defp parse_guardian_risk_level("low"), do: :low defp parse_guardian_risk_level("medium"), do: :medium defp parse_guardian_risk_level("high"), do: :high defp parse_guardian_risk_level(:low), do: :low defp parse_guardian_risk_level(:medium), do: :medium defp parse_guardian_risk_level(:high), do: :high defp parse_guardian_risk_level(_), do: nil defp normalize_mcp_startup_status(nil), do: {nil, nil} defp normalize_mcp_startup_status(%{"state" => state} = status) do {state, Map.get(status, "error")} end defp normalize_mcp_startup_status(%{state: state} = status) do {state, Map.get(status, :error)} end defp normalize_mcp_startup_status(value) when is_atom(value) do {Atom.to_string(value), nil} end defp normalize_mcp_startup_status(value) when is_binary(value), do: {value, nil} defp normalize_mcp_startup_status(_), do: {nil, nil} defp normalize_mcp_server_startup_status(nil), do: nil defp normalize_mcp_server_startup_status("starting"), do: :starting defp normalize_mcp_server_startup_status("ready"), do: :ready defp normalize_mcp_server_startup_status("failed"), do: :failed defp normalize_mcp_server_startup_status("cancelled"), do: :cancelled defp normalize_mcp_server_startup_status(value) when is_atom(value), do: value defp normalize_mcp_server_startup_status(value) when is_binary(value), do: value defp normalize_mcp_server_startup_status(_), do: nil defp encode_mcp_server_startup_status(:starting), do: "starting" defp encode_mcp_server_startup_status(:ready), do: "ready" defp encode_mcp_server_startup_status(:failed), do: "failed" defp encode_mcp_server_startup_status(:cancelled), do: "cancelled" defp encode_mcp_server_startup_status(value) when is_binary(value), do: value defp encode_mcp_server_startup_status(value) when is_atom(value), do: Atom.to_string(value) defp encode_mcp_server_startup_status(_), do: nil defp normalize_mcp_startup_complete(%{} = map) do servers = Map.get(map, "servers") || Map.get(map, :servers) if servers != nil do servers else data = %{} |> put_optional("ready", Map.get(map, "ready") || Map.get(map, :ready)) |> put_optional("failed", Map.get(map, "failed") || Map.get(map, :failed)) |> put_optional( "cancelled", Map.get(map, "cancelled") || Map.get(map, :cancelled) || Map.get(map, "canceled") ) if data == %{}, do: nil, else: data end end defp encode_mcp_startup_complete(nil), do: %{} defp encode_mcp_startup_complete(%{} = servers) do ready = Map.get(servers, "ready") || Map.get(servers, :ready) failed = Map.get(servers, "failed") || Map.get(servers, :failed) cancelled = Map.get(servers, "cancelled") || Map.get(servers, :cancelled) if ready != nil or failed != nil or cancelled != nil do %{} |> put_optional("ready", ready) |> put_optional("failed", failed) |> put_optional("cancelled", cancelled) else %{"servers" => servers} end end defp encode_mcp_startup_complete(servers) when is_list(servers) do %{"servers" => servers} end defp encode_mcp_startup_complete(servers) do %{"servers" => servers} end defp parse_rate_limits(nil), do: nil defp parse_rate_limits(%RateLimitSnapshot{} = snapshot), do: snapshot defp parse_rate_limits(%{} = snapshot) do RateLimitSnapshot.from_map(snapshot) rescue _ -> snapshot end defp parse_rate_limits(_), do: nil defp encode_rate_limits(nil), do: nil defp encode_rate_limits(%RateLimitSnapshot{} = snapshot) do %{} |> put_optional("primary", encode_rate_limit_window(snapshot.primary)) |> put_optional("secondary", encode_rate_limit_window(snapshot.secondary)) |> put_optional("credits", encode_rate_limit_credits(snapshot.credits)) |> put_optional("plan_type", encode_rate_limit_plan(snapshot.plan_type)) end defp encode_rate_limits(%{} = snapshot), do: snapshot defp encode_rate_limits(other), do: other defp encode_rate_limit_window(nil), do: nil defp encode_rate_limit_window(%Codex.Protocol.RateLimit.Window{} = window) do %{} |> put_optional("used_percent", window.used_percent) |> put_optional("window_minutes", window.window_minutes) |> put_optional("resets_at", window.resets_at) end defp encode_rate_limit_window(%{} = window), do: window defp encode_rate_limit_window(other), do: other defp encode_rate_limit_credits(nil), do: nil defp encode_rate_limit_credits(%Codex.Protocol.RateLimit.CreditsSnapshot{} = credits) do %{} |> put_optional("has_credits", credits.has_credits) |> put_optional("unlimited", credits.unlimited) |> put_optional("balance", credits.balance) end defp encode_rate_limit_credits(%{} = credits), do: credits defp encode_rate_limit_credits(other), do: other defp encode_rate_limit_plan(nil), do: nil defp encode_rate_limit_plan(plan) when is_atom(plan), do: Atom.to_string(plan) defp encode_rate_limit_plan(plan) when is_binary(plan), do: plan defp encode_rate_limit_plan(plan), do: to_string(plan) defp parse_thread_status(nil), do: nil defp parse_thread_status(%{} = status) do type = fetch_any(status, ["type"]) case type do "active" -> %{ type: :active, active_flags: status |> fetch_any(["active_flags", "activeFlags"]) |> List.wrap() |> Enum.map(&normalize_thread_active_flag/1) } "notLoaded" -> :not_loaded "systemError" -> :system_error other when is_binary(other) -> normalize_thread_status(other) _ -> status end end defp parse_thread_status(status), do: normalize_thread_status(status) defp normalize_thread_status("notLoaded"), do: :not_loaded defp normalize_thread_status("not_loaded"), do: :not_loaded defp normalize_thread_status("idle"), do: :idle defp normalize_thread_status("systemError"), do: :system_error defp normalize_thread_status("system_error"), do: :system_error defp normalize_thread_status("active"), do: %{type: :active, active_flags: []} defp normalize_thread_status(status) when is_atom(status), do: status defp normalize_thread_status(status), do: status defp normalize_thread_active_flag("waitingOnApproval"), do: :waiting_on_approval defp normalize_thread_active_flag("waiting_on_approval"), do: :waiting_on_approval defp normalize_thread_active_flag("waitingOnUserInput"), do: :waiting_on_user_input defp normalize_thread_active_flag("waiting_on_user_input"), do: :waiting_on_user_input defp normalize_thread_active_flag(flag) when is_atom(flag), do: flag defp normalize_thread_active_flag(flag), do: flag defp encode_thread_status(nil), do: nil defp encode_thread_status(%{type: :active} = status) do %{ "type" => "active", "active_flags" => status |> Map.get(:active_flags, []) |> Enum.map(&encode_thread_active_flag/1) } end defp encode_thread_status(%{"type" => _} = status), do: status defp encode_thread_status(status) when is_atom(status), do: encode_thread_status(Atom.to_string(status)) defp encode_thread_status("not_loaded"), do: "notLoaded" defp encode_thread_status("system_error"), do: "systemError" defp encode_thread_status(status) when is_binary(status), do: status defp encode_thread_status(status), do: status defp encode_thread_active_flag(:waiting_on_approval), do: "waitingOnApproval" defp encode_thread_active_flag(:waiting_on_user_input), do: "waitingOnUserInput" defp encode_thread_active_flag(flag) when is_atom(flag), do: Atom.to_string(flag) defp encode_thread_active_flag(flag), do: flag defp normalize_plan_type(nil), do: nil defp normalize_plan_type("plus"), do: :plus defp normalize_plan_type("pro"), do: :pro defp normalize_plan_type("team"), do: :team defp normalize_plan_type("enterprise"), do: :enterprise defp normalize_plan_type("api"), do: :api defp normalize_plan_type(type) when is_atom(type), do: type defp normalize_plan_type(type), do: type defp encode_plan_type(nil), do: nil defp encode_plan_type(type) when is_atom(type), do: Atom.to_string(type) defp encode_plan_type(type), do: type defp normalize_model_reroute_reason(nil), do: nil defp normalize_model_reroute_reason("highRiskCyberActivity"), do: :high_risk_cyber_activity defp normalize_model_reroute_reason("high_risk_cyber_activity"), do: :high_risk_cyber_activity defp normalize_model_reroute_reason(reason) when is_atom(reason), do: reason defp normalize_model_reroute_reason(reason), do: reason defp encode_model_reroute_reason(nil), do: nil defp encode_model_reroute_reason(:high_risk_cyber_activity), do: "highRiskCyberActivity" defp encode_model_reroute_reason(reason) when is_atom(reason), do: Atom.to_string(reason) defp encode_model_reroute_reason(reason), do: reason defp put_optional(map, _key, nil), do: map defp put_optional(map, key, value), do: Map.put(map, key, value) defp encode_raw_item(%{__struct__: _} = item), do: Items.to_map(item) defp encode_raw_item(%{} = item), do: item defp encode_raw_item(other), do: other defp encode_final_response(%Items.AgentMessage{text: text}) when is_binary(text) do %{"type" => "text", "text" => text} end defp encode_final_response(%Items.AgentMessage{}), do: %{"type" => "text"} defp encode_final_response(other), do: other end