Packages

Elixir framework for building production-ready AI agents and LLM applications: type-safe schemas with JSON Schema generation, tool calling, streaming, multi-agent coordination, guardrails, and distributed fault-tolerant sessions, with first-class Anthropic Claude support.

Current section

Files

Jump to
normandy lib normandy components content_block cache_control.ex
Raw

lib/normandy/components/content_block/cache_control.ex

defmodule Normandy.Components.ContentBlock.CacheControl do
@moduledoc false
# Internal helper shared by ContentBlock.{Text,Image,Document}.to_claudio/1.
# Anthropic's wire shape uses string keys; callers may pass atom keys for
# ergonomics. We stringify the top level (`:type` -> `"type"`, `:ttl` ->
# `"ttl"`) so the on-the-wire JSON never carries Elixir atoms.
@spec maybe_attach(map(), map() | nil) :: map()
def maybe_attach(block, nil), do: block
def maybe_attach(block, %{} = cache_control) when not is_struct(cache_control) do
Map.put(block, "cache_control", normalize_keys(cache_control))
end
# Structs in Elixir are maps with `:__struct__`, so `%{} = cache_control`
# would otherwise accept e.g. `%DateTime{}` and ship `__struct__` and
# struct fields into the cache_control payload. Reject at the boundary.
def maybe_attach(_block, cache_control) do
raise ArgumentError,
"Normandy.Components.ContentBlock.CacheControl: cache_control must be a " <>
"plain map. Got: #{inspect(cache_control)}"
end
@spec normalize_keys(map()) :: map()
def normalize_keys(%{} = map) when not is_struct(map) do
# Stringifying atom keys can collide with existing string keys
# (`%{type: "x", "type" => "y"}` would silently lose one entry under
# `Map.new/2`). Detect collisions during the reduce and raise — caller
# intent is unrecoverable from a collapsed map.
Enum.reduce(map, %{}, fn {k, v}, acc ->
key = if is_atom(k), do: Atom.to_string(k), else: k
if Map.has_key?(acc, key) do
raise ArgumentError,
"Normandy.Components.ContentBlock.CacheControl: cache_control map " <>
"contains both an atom and string version of the same key after " <>
"normalization (#{inspect(key)}). Pick one form."
end
Map.put(acc, key, v)
end)
end
def normalize_keys(other) do
raise ArgumentError,
"Normandy.Components.ContentBlock.CacheControl: cache_control must be a " <>
"plain map. Got: #{inspect(other)}"
end
end