Packages

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

Current section

Files

Jump to
ex_ai lib ex_ai cli json.ex
Raw

lib/ex_ai/cli/json.ex

defmodule ExAI.CLI.JSON do
@moduledoc """
JSON encoding helpers and stable response schema builders for CLI tasks.
"""
alias ExAI.Error
@base_fields [:ok, :run_id, :status, :result, :error, :events, :usage, :metadata]
@spec encode!(term()) :: String.t()
def encode!(value) do
value
|> normalize()
|> Jason.encode!()
end
@spec success(map()) :: map()
def success(attrs \\ %{}) when is_map(attrs) do
attrs
|> Map.put(:ok, true)
|> Map.put_new(:error, nil)
|> response()
end
@spec failure(Error.t(), map()) :: map()
def failure(%Error{} = error, attrs \\ %{}) when is_map(attrs) do
attrs
|> Map.put(:ok, false)
|> Map.put(:error, error)
|> Map.put_new(:status, :error)
|> response()
end
@spec response(map()) :: map()
def response(attrs) when is_map(attrs) do
defaults = %{
ok: false,
run_id: nil,
status: nil,
result: nil,
error: nil,
events: nil,
usage: nil,
metadata: %{}
}
extras = Map.drop(attrs, @base_fields)
defaults
|> Map.merge(Map.take(attrs, @base_fields))
|> Map.update!(:metadata, &normalize_metadata/1)
|> Map.merge(extras)
end
@spec normalize(term()) :: term()
def normalize(value)
def normalize(nil), do: nil
def normalize(true), do: true
def normalize(false), do: false
def normalize(%DateTime{} = value), do: DateTime.to_iso8601(value)
def normalize(%NaiveDateTime{} = value), do: NaiveDateTime.to_iso8601(value)
def normalize(%Date{} = value), do: Date.to_iso8601(value)
def normalize(%Time{} = value), do: Time.to_iso8601(value)
def normalize(%Error{} = error) do
%{
code: error.code,
message: error.message,
details: normalize(error.details),
context: normalize(error.context)
}
end
def normalize(%_module{} = struct) do
struct
|> Map.from_struct()
|> normalize()
end
def normalize(map) when is_map(map) do
map
|> Enum.map(fn {key, value} -> {normalize_key(key), normalize(value)} end)
|> Map.new()
end
def normalize(list) when is_list(list), do: Enum.map(list, &normalize/1)
def normalize(tuple) when is_tuple(tuple),
do: tuple |> Tuple.to_list() |> Enum.map(&normalize/1)
def normalize(atom) when is_atom(atom), do: Atom.to_string(atom)
def normalize(number) when is_number(number), do: number
def normalize(binary) when is_binary(binary), do: binary
def normalize(other), do: inspect(other)
@spec normalize_key(term()) :: String.t()
defp normalize_key(key) when is_atom(key), do: Atom.to_string(key)
defp normalize_key(key) when is_binary(key), do: key
defp normalize_key(key), do: inspect(key)
@spec normalize_metadata(term()) :: map()
defp normalize_metadata(nil), do: %{}
defp normalize_metadata(metadata) when is_map(metadata), do: metadata
defp normalize_metadata(metadata) when is_list(metadata), do: Map.new(metadata)
defp normalize_metadata(_), do: %{}
end