Packages
Secure BEAM sandbox runtime for LLM code mode and MCP aggregation. Run concurrent LLM/tool clients safely while agents orchestrate approved tools, call upstream MCP/OpenAPI servers, and transform data.
Current section
Files
Jump to
Current section
Files
lib/ptc_runner/sub_agent/namespace/data.ex
defmodule PtcRunner.SubAgent.Namespace.Data do
@moduledoc "Renders the data/ namespace section."
alias PtcRunner.SubAgent.Namespace.SampleFormatter
alias PtcRunner.SubAgent.Namespace.TypeVocabulary
alias PtcRunner.SubAgent.Signature.Renderer
@name_width 30
@doc """
Render data/ namespace section for USER message.
Returns `nil` for empty data maps, otherwise a formatted string with header
and entries showing type label and truncated sample.
## Options
- `:field_descriptions` - Map of field names to description strings
- `:context_signature` - Parsed signature for type information
- `:sample_limit` - Max items to show in collections (default: 3)
- `:sample_printable_limit` - Max chars for strings (default: 80)
## Examples
iex> PtcRunner.SubAgent.Namespace.Data.render(%{})
nil
iex> PtcRunner.SubAgent.Namespace.Data.render(%{count: 42})
";; === data/ ===\\ndata/count ; integer, sample: 42"
iex> PtcRunner.SubAgent.Namespace.Data.render(%{_token: "secret"})
";; === data/ ===\\ndata/_token ; string, sample: \\"secret\\""
iex> PtcRunner.SubAgent.Namespace.Data.render(%{x: 5}, field_descriptions: %{x: "Input value"})
";; === data/ ===\\ndata/x ; integer, sample: 5 -- Input value"
"""
@spec render(map(), keyword()) :: String.t() | nil
def render(data, opts \\ [])
def render(data, _opts) when map_size(data) == 0, do: nil
def render(data, opts) do
field_descriptions = Keyword.get(opts, :field_descriptions, %{}) || %{}
context_signature = Keyword.get(opts, :context_signature)
# Extract param types from signature if available
param_types = extract_param_types(context_signature)
lines =
data
|> Enum.sort_by(fn {name, _} -> to_string(name) end)
|> Enum.map(fn {name, value} ->
format_entry(name, value, param_types, field_descriptions, opts)
end)
[";; === data/ ===" | lines] |> Enum.join("\n")
end
defp extract_param_types({:signature, params, _return_type}) do
params
|> Enum.map(fn {name, type} -> {to_string(name), type} end)
|> Map.new()
end
defp extract_param_types(_), do: %{}
defp format_entry(name, value, param_types, field_descriptions, opts) do
name_str = to_string(name)
# Get type - prefer signature type, fall back to runtime inference
type_label = get_type_label(name_str, value, param_types)
# Build the line parts
padded_name = String.pad_trailing("data/#{name_str}", @name_width)
sample_part = "sample: #{SampleFormatter.format(value, opts)}"
desc_part =
case get_field_description(name, field_descriptions) do
nil -> ""
desc -> " -- #{desc}"
end
"#{padded_name}; #{type_label}, #{sample_part}#{desc_part}"
end
defp get_type_label(name_str, value, param_types) do
case Map.get(param_types, name_str) do
nil ->
# Use runtime type inference
TypeVocabulary.type_of(value)
type ->
# Use signature type, render without colons
Renderer.render_type(type, key_style: :lisp_prompt) |> String.replace(":", "")
end
end
defp get_field_description(_key, nil), do: nil
defp get_field_description(_key, descriptions) when map_size(descriptions) == 0, do: nil
defp get_field_description(key, descriptions) do
key_atom = if is_atom(key), do: key, else: String.to_existing_atom(to_string(key))
key_str = to_string(key)
Map.get(descriptions, key_atom) || Map.get(descriptions, key_str)
rescue
ArgumentError -> Map.get(descriptions, to_string(key))
end
end