# Tool Dispatch

OpenResponses supports two kinds of tools: **external** (implemented in your client) and **hosted** (implemented on the server). Both follow the same request format.

## Defining tools in a request

Tools are defined as JSON Schema function descriptions:

```json
{
  "model": "gpt-4o",
  "input": [{"role": "user", "content": "Search for recent news about Elixir."}],
  "tools": [
    {
      "type": "function",
      "name": "web_search",
      "description": "Search the web for recent information.",
      "parameters": {
        "type": "object",
        "properties": {
          "query": {
            "type": "string",
            "description": "The search query"
          }
        },
        "required": ["query"]
      }
    }
  ]
}
```

## External tool flow

When the model calls a tool that is not registered as a hosted tool, the call appears in the response output and you are responsible for executing it and submitting the result.

### Step 1 — Model calls the tool

The response (or stream) includes a `function_call` item:

```json
{
  "output": [
    {
      "type": "function_call",
      "id": "fc_01",
      "call_id": "call_abc123",
      "name": "web_search",
      "arguments": "{\"query\": \"Elixir news 2026\"}"
    }
  ]
}
```

### Step 2 — Execute the tool in your code

```elixir
arguments = Jason.decode!(function_call["arguments"])
result = MyApp.WebSearch.search(arguments["query"])
```

### Step 3 — Submit the result

Send a follow-up request with the previous response ID and a `function_call_output` item:

```json
{
  "model": "gpt-4o",
  "previous_response_id": "resp_01",
  "input": [
    {
      "type": "function_call_output",
      "call_id": "call_abc123",
      "output": "Top results: ElixirConf 2026 announced..."
    }
  ]
}
```

OpenResponses reconstructs the full conversation context from `previous_response_id` and continues from where it left off.

## Hosted tools

Hosted tools execute on the server. The loop calls them automatically without involving the client. This is ideal for tools that are safe, fast, and don't require user interaction.

### Implementing a hosted tool

Implement the `OpenResponses.Tool` behaviour:

```elixir
defmodule MyApp.Tools.TimeZone do
  @behaviour OpenResponses.Tool

  @impl OpenResponses.Tool
  def execute(%{"timezone" => tz}, _context) do
    case DateTime.now(tz) do
      {:ok, dt} -> {:ok, Calendar.strftime(dt, "%Y-%m-%d %H:%M:%S %Z")}
      {:error, _} -> {:error, "Unknown timezone: #{tz}"}
    end
  end
end
```

`execute/2` receives:
- `arguments` — the parsed JSON arguments from the model (a map with string keys)
- `context` — a map with request context, including `response_id`

Return `{:ok, string_result}` or `{:error, reason}`.

### Registering hosted tools

In `config/config.exs`:

```elixir
config :open_responses, :hosted_tools, %{
  "get_time" => MyApp.Tools.TimeZone,
  "search_docs" => MyApp.Tools.DocSearch,
  "run_sql" => MyApp.Tools.SQL
}
```

The key is the tool name the model uses in `function_call`. When a model emits a call for a registered name, OpenResponses dispatches it internally, appends the result, and continues the agentic loop — the client never sees the intermediate step.

## Controlling tool use

### `tool_choice`

Force or prevent tool use with the `tool_choice` field:

| Value | Behaviour |
|---|---|
| `"auto"` (default) | Model decides whether to call a tool |
| `"required"` | Model must call at least one tool |
| `"none"` | Model may not call any tools |
| `{"type": "function", "function": {"name": "my_tool"}}` | Model must call this specific tool |

```json
{
  "model": "gpt-4o",
  "tool_choice": "required",
  "tools": [...],
  "input": [...]
}
```

### `allowed_tools`

Restrict which tools the model may call, regardless of what is defined in `tools`:

```json
{
  "model": "gpt-4o",
  "tools": [{"name": "search"}, {"name": "calculator"}, {"name": "email"}],
  "allowed_tools": ["search", "calculator"],
  "input": [...]
}
```

Calls to tools not in `allowed_tools` are rejected by `OpenResponses.ToolPolicy` before dispatch.

## Error handling

If a hosted tool returns `{:error, reason}`, the error is formatted as a string and submitted back to the model as a `function_call_output`. The model can then handle the error gracefully in its response.

If a tool raises an exception, the loop catches it, submits an error output, and continues.
