# `PtcRunner.SubAgent.LLMResolver`
[🔗](https://github.com/andreasronge/ptc_runner/blob/main/lib/ptc_runner/sub_agent/llm_resolver.ex#L1)

LLM resolution and invocation for SubAgents.

Handles calling LLMs that can be:
- Strings (model aliases like `"haiku"` or full IDs like `"openrouter:anthropic/claude-haiku-4.5"`)
- Functions (direct callback functions)
- Atoms (registry lookups for atom-based LLM references like `:haiku`)

LLM responses are normalized to a consistent format:
- Plain string responses become `%{content: string, tokens: nil}`
- Map responses with `:content` key preserve tokens if present

# `normalized_response`

```elixir
@type normalized_response() ::
  %{content: String.t() | nil, tokens: map() | nil}
  | %{content: String.t() | nil, tokens: map() | nil, tool_calls: [map()]}
```

Normalized LLM response with content, optional token counts, and optional tool calls.

For tool calling mode, the response may include `tool_calls` instead of or in addition to `content`.

# `normalize_response`

```elixir
@spec normalize_response(String.t() | map()) :: normalized_response()
```

Normalize an LLM response to a consistent format.

## Examples

    iex> PtcRunner.SubAgent.LLMResolver.normalize_response("hello")
    %{content: "hello", tokens: nil}

    iex> PtcRunner.SubAgent.LLMResolver.normalize_response(%{content: "hello"})
    %{content: "hello", tokens: nil}

    iex> PtcRunner.SubAgent.LLMResolver.normalize_response(%{content: "hello", tokens: %{input: 10, output: 5}})
    %{content: "hello", tokens: %{input: 10, output: 5}}

# `resolve`

```elixir
@spec resolve(
  String.t() | atom() | (map() -&gt; {:ok, term()} | {:error, term()}),
  map(),
  map()
) ::
  {:ok, normalized_response()} | {:error, term()}
```

Resolve and invoke an LLM, handling strings, functions, and atom references.

Normalizes the LLM response to always return a map with `:content` and `:tokens` keys.
This provides a consistent interface for callers regardless of whether the LLM
callback returns a plain string or a map with token information.

## Parameters

- `llm` - A model string (alias or full ID), function/1, or atom referencing the registry
- `input` - The LLM input map to pass to the callback
- `registry` - Map of atom to LLM callback for atom-based LLM references

## Returns

- `{:ok, %{content: String.t(), tokens: map() | nil}}` - Normalized response on success
- `{:error, reason}` - Error tuple with reason on failure

## Examples

    # String model reference (resolved via PtcRunner.LLM.callback)
    # PtcRunner.SubAgent.LLMResolver.resolve("haiku", %{...}, %{})

    iex> llm = fn %{messages: [%{content: _}]} -> {:ok, "result"} end
    iex> PtcRunner.SubAgent.LLMResolver.resolve(llm, %{messages: [%{content: "test"}]}, %{})
    {:ok, %{content: "result", tokens: nil}}

    iex> llm = fn _ -> {:ok, %{content: "result", tokens: %{input: 10, output: 5}}} end
    iex> PtcRunner.SubAgent.LLMResolver.resolve(llm, %{messages: []}, %{})
    {:ok, %{content: "result", tokens: %{input: 10, output: 5}}}

    iex> registry = %{haiku: fn %{messages: _} -> {:ok, "response"} end}
    iex> PtcRunner.SubAgent.LLMResolver.resolve(:haiku, %{messages: [%{content: "test"}]}, registry)
    {:ok, %{content: "response", tokens: nil}}

# `total_tokens`

```elixir
@spec total_tokens(map()) :: non_neg_integer()
```

Calculate total tokens from input and output token counts.

## Examples

    iex> PtcRunner.SubAgent.LLMResolver.total_tokens(%{input: 10, output: 5})
    15

    iex> PtcRunner.SubAgent.LLMResolver.total_tokens(%{input: 0, output: 0})
    0

    iex> PtcRunner.SubAgent.LLMResolver.total_tokens(%{})
    0

---

*Consult [api-reference.md](api-reference.md) for complete listing*
