lib/foundry/chat/session.ex

defmodule Foundry.Chat.Session do
  @moduledoc """
  Chat session resource backed by Mnesia disc_copies.

  Each session has a unique ID, a list of messages (role + content), and
  timestamps. Mnesia ensures persistence across restarts without requiring
  a separate database.

  ## Mnesia Setup

  The Mnesia table is created and initialized at application startup in
  `Foundry.Application.init_mnesia/0`. The table name is `:foundry_chat_sessions`.
  """

  use Ash.Resource,
    domain: Foundry.Chat,
    data_layer: Ash.DataLayer.Mnesia

  mnesia do
    table(:foundry_chat_sessions)
  end

  attributes do
    uuid_primary_key(:id)

    attribute :session_id, :string do
      description("Unique session identifier for grouping conversations.")
      allow_nil?(false)
      public?(true)
    end

    attribute :messages, {:array, :map} do
      description(
        "Message history. Each message is a map with :role (user|assistant|system), :content, and :timestamp."
      )

      default([])
      public?(true)
    end

    attribute :session_digest, :map do
      description(
        "Compact session memory for Studio copilot runs: selected nodes, recent files, proposal ids, and condensed conclusions."
      )

      default(%{})
      public?(true)
    end

    attribute :title, :string do
      description("Human-readable session title, auto-generated from first message.")
      allow_nil?(true)
      public?(true)
    end

    attribute :model, :string do
      description("LLM model used for the session.")
      allow_nil?(true)
      public?(true)
    end

    create_timestamp(:created_at)
    update_timestamp(:updated_at)
  end

  actions do
    defaults([:read, :update, :destroy])

    create :create do
      primary?(true)
      accept([:session_id, :messages, :session_digest, :title, :model])
    end

    update :persist_messages do
      description("Replace the stored message history for the session.")
      accept([:messages, :session_digest])
    end

    update :add_message do
      description("Append a message to the session's message history.")
      accept([])

      change(
        after_action(fn changeset, result, _context ->
          new_message = changeset.context[:message]

          if new_message do
            updated_messages = (result.messages || []) ++ [new_message]

            Ash.Changeset.change_attribute(result, :messages, updated_messages)
          else
            {:ok, result}
          end
        end)
      )
    end
  end

  calculations do
    calculate :message_count, :integer, expr(length(messages)) do
      description("Number of messages in this session.")
    end
  end
end