Skip to main content

lib/miosa/computer/agent.ex

defmodule Miosa.Computer.Agent do
  @moduledoc """
  Computer-Use Agent (CUA) session management for a computer.

  Wraps:
    * `POST   /computers/:id/cua/sessions`          — run/4
    * `GET    /computers/:id/cua/sessions`          — list/2
    * `GET    /computers/:id/cua/sessions/:sid`     — get/3
    * `DELETE /computers/:id/cua/sessions/:sid`     — cancel/3
  """

  alias Miosa.Client

  @doc """
  Create and start a CUA session for a computer
  (POST `/computers/:computer_id/cua/sessions`).

  ## Options map keys

    * `"model"` — Override the AI model (e.g. `"claude-3-5-sonnet-latest"`).
    * `"max_steps"` — Maximum number of agent steps before stopping.
    * `"tools"` — List of additional tool names to enable.
    * `"timeout"` — Session timeout in seconds.
  """
  @spec run(Client.t(), String.t(), String.t(), map()) :: Client.result(map())
  def run(%Client{} = client, computer_id, goal, opts \\ %{})
      when is_binary(computer_id) and is_binary(goal) do
    body =
      opts
      |> Enum.reduce(%{"goal" => goal}, fn {k, v}, acc ->
        if v != nil, do: Map.put(acc, to_string(k), v), else: acc
      end)

    client
    |> Client.post("/computers/#{computer_id}/cua/sessions", body)
    |> unwrap()
  end

  @doc """
  List all CUA sessions for a computer
  (GET `/computers/:computer_id/cua/sessions`).
  """
  @spec list(Client.t(), String.t()) :: Client.result(list())
  def list(%Client{} = client, computer_id) when is_binary(computer_id) do
    client
    |> Client.get("/computers/#{computer_id}/cua/sessions")
    |> unwrap()
  end

  @doc """
  Retrieve a single CUA session by ID
  (GET `/computers/:computer_id/cua/sessions/:session_id`).
  """
  @spec get(Client.t(), String.t(), String.t()) :: Client.result(map())
  def get(%Client{} = client, computer_id, session_id)
      when is_binary(computer_id) and is_binary(session_id) do
    client
    |> Client.get("/computers/#{computer_id}/cua/sessions/#{session_id}")
    |> unwrap()
  end

  @doc """
  Cancel a running CUA session
  (DELETE `/computers/:computer_id/cua/sessions/:session_id`).
  """
  @spec cancel(Client.t(), String.t(), String.t()) :: :ok | {:error, Miosa.Error.t()}
  def cancel(%Client{} = client, computer_id, session_id)
      when is_binary(computer_id) and is_binary(session_id) do
    case Client.delete(client, "/computers/#{computer_id}/cua/sessions/#{session_id}") do
      {:ok, _} -> :ok
      err -> err
    end
  end

  # ---------------------------------------------------------------------------
  # Private
  # ---------------------------------------------------------------------------

  defp unwrap({:ok, %{"data" => data}}), do: {:ok, data}
  defp unwrap({:ok, body}), do: {:ok, body}
  defp unwrap(err), do: err
end