Packages

Graph-first runtime for building agent systems on the BEAM in Elixir

Current section

Files

Jump to
ex_ai lib mix tasks ex_ai.run.get.ex
Raw

lib/mix/tasks/ex_ai.run.get.ex

defmodule Mix.Tasks.ExAi.Run.Get do
@shortdoc "Get persisted metadata for an ExAI run"
@moduledoc """
Loads a persisted run and related inspection data.
mix ex_ai.run.get <run_id>
mix ex_ai.run.get <run_id> --json
"""
use Mix.Task
alias ExAI.CLI.CommandTelemetry
alias ExAI.CLI.JSON
alias ExAI.CLI.Output
alias ExAI.CLI.Runtime
alias ExAI.Error
alias ExAI.Persistence.History
alias ExAI.Persistence.Store
@switches [dir: :string, json: :boolean]
@impl true
def run(argv) do
{opts, args, invalid} = OptionParser.parse(argv, strict: @switches)
opts = Map.new(opts)
persistence_opts = Runtime.persistence_opts(opts)
started_ms = CommandTelemetry.start("ex_ai.run.get")
with :ok <- validate_invalid_options(invalid),
{:ok, run_id} <- parse_run_id(args),
:ok <- Runtime.boot(opts),
{:ok, run} <- Store.load_run(run_id, persistence_opts),
{:ok, messages} <- History.list_messages(run_id, persistence_opts),
inspection <- maybe_inspection(run_id, persistence_opts) do
payload =
JSON.success(%{
run_id: run.run_id,
status: run.status,
result: run.result,
metadata: run.metadata,
usage: usage_from_result(run.result),
message_count: length(messages),
inspection: inspection
})
|> maybe_merge_error_from_inspection(inspection)
CommandTelemetry.stop("ex_ai.run.get", started_ms, %{run_id: run.run_id})
emit_success(payload, opts)
else
{:error, %Error{} = error} ->
CommandTelemetry.error("ex_ai.run.get", started_ms, error)
emit_error(error, opts, %{})
{:error, reason} ->
error = Error.new(:validation_error, reason)
CommandTelemetry.error("ex_ai.run.get", started_ms, error)
emit_error(error, opts, %{})
end
end
@spec parse_run_id([String.t()]) :: {:ok, String.t()} | {:error, String.t()}
defp parse_run_id([run_id | _]) when is_binary(run_id), do: {:ok, run_id}
defp parse_run_id(_), do: {:error, "usage: mix ex_ai.run.get <run_id>"}
@spec maybe_inspection(String.t(), keyword()) :: map() | nil
defp maybe_inspection(run_id, opts) do
case History.inspection(run_id, opts) do
{:ok, inspection} ->
inspection
{:error, %Error{code: :not_found}} ->
nil
{:error, _error} ->
nil
end
end
@spec maybe_merge_error_from_inspection(map(), map() | nil) :: map()
defp maybe_merge_error_from_inspection(payload, nil), do: payload
defp maybe_merge_error_from_inspection(payload, inspection) do
error =
inspection
|> Map.get(:snapshot, %{})
|> Map.get(:error)
Map.put(payload, :error, error)
end
@spec usage_from_result(term()) :: term()
defp usage_from_result(%{} = result), do: Map.get(result, :usage)
defp usage_from_result(_), do: nil
@spec validate_invalid_options([{String.t(), String.t() | nil}]) :: :ok | {:error, String.t()}
defp validate_invalid_options([]), do: :ok
defp validate_invalid_options(invalid) do
{:error, "invalid options: #{inspect(invalid)}"}
end
@spec emit_success(map(), map()) :: :ok
defp emit_success(payload, %{json: true}) do
Output.emit_json(payload)
end
defp emit_success(payload, _opts) do
Output.emit_info("run #{payload.run_id} status=#{payload.status}")
:ok
end
@spec emit_error(ExAI.Error.t(), map(), map()) :: no_return()
defp emit_error(%Error{} = error, opts, payload_overrides) do
Output.halt_error(error, opts, payload_overrides)
end
end