Skip to main content

lib/pi/session/state.ex

defmodule Pi.Session.State do
  @moduledoc "Semantic state owned by a Pi session process."

  alias Pi.Agent.Messages
  alias Pi.Protocol.LLM.Message
  alias Pi.Session.Event

  @enforce_keys [:id]
  defstruct [
    :id,
    :parent_id,
    :name,
    :system,
    :status,
    :result,
    :error,
    started_at: nil,
    updated_at: nil,
    messages: [],
    events: [],
    metadata: %{}
  ]

  @type status :: :idle | :running | :done | :failed | :cancelled
  @type t :: %__MODULE__{
          id: String.t(),
          parent_id: String.t() | nil,
          name: atom() | String.t() | nil,
          system: String.t() | nil,
          status: status(),
          result: term(),
          error: term(),
          started_at: DateTime.t() | nil,
          updated_at: DateTime.t() | nil,
          messages: [Message.t()],
          events: [Event.t()],
          metadata: map()
        }

  def new(opts \\ []) do
    now = DateTime.utc_now()

    %__MODULE__{
      id: Keyword.get_lazy(opts, :id, &id/0),
      parent_id: Keyword.get(opts, :parent_id),
      name: Keyword.get(opts, :name),
      system: Keyword.get(opts, :system),
      status: Keyword.get(opts, :status, :idle),
      started_at: now,
      updated_at: now,
      messages: opts |> Keyword.get(:messages, []) |> Enum.map(&Messages.normalize/1),
      metadata: Keyword.get(opts, :metadata, %{})
    }
  end

  def child(%__MODULE__{} = parent, opts \\ []) do
    opts
    |> Keyword.put_new(:parent_id, parent.id)
    |> new()
  end

  defp id, do: "session_#{System.unique_integer([:positive])}"
end