# Tool calls

ExAthena supports two tool-calling protocols and falls back between them
automatically when a model misbehaves.

## Native tool calls

The OpenAI / Anthropic / Ollama native shape. Providers declaring
`native_tool_calls: true` in their capabilities use this by default.

### OpenAI / Ollama

```json
{
  "role": "assistant",
  "content": null,
  "tool_calls": [
    {
      "id": "call_abc",
      "type": "function",
      "function": {
        "name": "read_file",
        "arguments": "{\"path\": \"/tmp/foo\"}"
      }
    }
  ]
}
```

### Anthropic Claude

```json
{
  "role": "assistant",
  "content": [
    {
      "type": "tool_use",
      "id": "toolu_abc",
      "name": "read_file",
      "input": {"path": "/tmp/foo"}
    }
  ]
}
```

`ExAthena.ToolCalls.Native.parse/1` handles both shapes and returns
canonical `%ExAthena.Messages.ToolCall{}` structs.

## Text-tagged tool calls

For models without native tool-call support, ExAthena supports a
prompt-engineered protocol:

````
~~~tool_call
{"name": "read_file", "arguments": {"path": "/tmp/foo"}}
~~~
````

Rules:

- One block per call; multiple blocks in a single response are allowed.
- Both fences must be on their own lines.
- `id` is optional; missing ids are generated server-side.
- Malformed JSON in a block returns an error.

`ExAthena.ToolCalls.augment_system_prompt/2` appends instructions for
this protocol to the system prompt, along with each tool's schema:

```elixir
ExAthena.ToolCalls.augment_system_prompt(
  "Be helpful.",
  [
    %{name: "read_file", description: "read a file", schema: %{type: "object", properties: %{path: %{type: "string"}}}}
  ]
)
```

## Auto-fallback

`ExAthena.ToolCalls.extract/2` picks the protocol based on provider
capabilities AND the response shape:

| Provider says | Response has | Parser used |
|---|---|---|
| `native: true` | `tool_calls` array | Native |
| `native: true` | empty `tool_calls`, text contains `~~~tool_call` | TextTagged (fallback) |
| `native: true` | empty, no fences | returns `{:ok, []}` |
| `native: false` | any | TextTagged |

The agent loop (Phase 2) uses this to recover when a provider declares
native support but the model actually emits text-tagged blocks — common
with smaller Ollama models.

## Arguments

Both parsers accept:

- A decoded map (most common from Claude's `input`).
- A JSON-encoded string (OpenAI's `arguments`).
- An empty string (treated as `{}`).

Anything else returns an error. Never trust arbitrary tool-call payloads
without schema validation at the tool-execution layer.
