defmodule SyntropyWeb.Api.AgentController do use SyntropyWeb, :controller alias Syntropy.{ClusterInventory, EventRecorder, LatticeSupervisor, TaskScheduler} alias SyntropyWeb.Api.{AgentJSON, AgentRequest, ControllerHelpers, Listing} @spec index(Plug.Conn.t(), map()) :: Plug.Conn.t() def index(conn, params) do case Listing.parse_opts(params, [:q, :perspective]) do {:ok, opts} -> agents = Listing.filter_agents(ClusterInventory.cluster_agents(), opts) limit = Keyword.fetch!(opts, :limit) json(conn, %{ data: agents |> Enum.take(limit) |> Enum.map(&AgentJSON.agent/1), meta: %{has_more: length(agents) > limit, next_cursor: nil} }) {:error, :invalid_cursor} -> ControllerHelpers.invalid_request(conn, "Invalid cursor.", []) end end @spec show(Plug.Conn.t(), map()) :: Plug.Conn.t() def show(conn, %{"id" => agent_id}) do case ClusterInventory.find_agent(agent_id) do nil -> ControllerHelpers.not_found(conn, "Agent", agent_id) agent -> json(conn, %{data: AgentJSON.agent(agent)}) end end @spec create(Plug.Conn.t(), map()) :: Plug.Conn.t() def create(conn, params) do with {:ok, request} <- AgentRequest.parse(params), {:ok, agent_id} <- LatticeSupervisor.add_agent(request.name, request.perspective, request.opts) do agent = LatticeSupervisor.get_agent(agent_id) EventRecorder.record("agent_created", %{ agent_id: agent.id, runtime_id: agent.runtime_id, node_id: agent.node_id, agent_name: agent.name, perspective: agent.perspective, source: "api" }) conn |> put_status(:created) |> json(%{data: AgentJSON.agent(agent)}) else {:error, errors} when is_list(errors) -> ControllerHelpers.invalid_request(conn, "Invalid agent request.", errors) {:error, {:already_started, _pid}} -> ControllerHelpers.conflict( conn, "agent_exists", "An agent with this id is already running." ) {:error, _reason} -> ControllerHelpers.conflict(conn, "agent_not_started", "The agent could not be started.") end end @spec delete(Plug.Conn.t(), map()) :: Plug.Conn.t() def delete(conn, %{"id" => agent_id}) do case LatticeSupervisor.get_agent(agent_id) do nil -> ControllerHelpers.not_found(conn, "Agent", agent_id) agent -> if agent.runtime_id in TaskScheduler.active_agent_ids() do ControllerHelpers.conflict( conn, "agent_busy", "Agent #{agent_id} is executing an active run and cannot be removed." ) else remove_agent(conn, agent) end end end defp remove_agent(conn, agent) do case LatticeSupervisor.remove_agent(agent.id) do :ok -> EventRecorder.record("agent_removed", %{ agent_id: agent.id, runtime_id: agent.runtime_id, node_id: agent.node_id, agent_name: agent.name, perspective: agent.perspective, source: "api" }) json(conn, %{data: %{id: agent.id, removed: true}}) {:error, :not_found} -> ControllerHelpers.not_found(conn, "Agent", agent.id) end end end