lib/openagents/conversations/turn.ex

main at 58e6347eeb72 · 1 KB

defmodule OpenAgents.Conversations.Turn do
  @moduledoc false

  use Ecto.Schema
  import Ecto.Changeset

  @statuses ~w(queued streaming completed failed cancelled)

  @primary_key {:id, :binary_id, autogenerate: true}
  @foreign_key_type :binary_id
  @timestamps_opts [type: :utc_datetime_usec]

  schema "turns" do
    belongs_to :conversation, OpenAgents.Conversations.Conversation
    belongs_to :user_message, OpenAgents.Conversations.Message
    belongs_to :assistant_message, OpenAgents.Conversations.Message
    has_one :receipt, OpenAgents.Conversations.TurnReceipt
    has_many :tool_steps, OpenAgents.Conversations.ToolStep
    field :status, :string, default: "queued"
    field :error_message, :string
    # The real machine-readable failure reason (task_exit:…, invalid_provider_event,
    # …), distinct from the user-facing error_message. Populated on failed turns.
    field :error_code, :string
    field :provider_response_id, :string
    field :started_at, :utc_datetime_usec
    field :completed_at, :utc_datetime_usec
    timestamps()
  end

  def changeset(turn, attributes) do
    turn
    |> cast(attributes, [
      :conversation_id,
      :user_message_id,
      :assistant_message_id,
      :status,
      :error_message,
      :error_code,
      :provider_response_id,
      :started_at,
      :completed_at
    ])
    |> validate_required([:conversation_id, :user_message_id, :assistant_message_id, :status])
    |> validate_inclusion(:status, @statuses)
    |> foreign_key_constraint(:conversation_id)
    |> foreign_key_constraint(:user_message_id)
    |> foreign_key_constraint(:assistant_message_id)
    |> unique_constraint(:conversation_id,
      name: :turns_one_active_per_conversation_index,
      message: "already has an active turn"
    )
  end
end