# `ToolCall`

Derive LLM tool-call schemas from `@spec` and `@doc`.

    defmodule MyApp.Tools.Weather do
      use ToolCall

      @doc """
      Get the forecast for a city.

      ## Parameters
        * `city` - city name
        * `days` - how many days ahead
      """
      @spec get_weather(city :: String.t(), days :: pos_integer() | nil) ::
              {:ok, %{temp: float()}} | {:error, String.t()}
      def get_weather(city, days \\ 3)
    end

    ToolCall.emit(MyApp.Tools.Weather, :openai)
    ToolCall.run(MyApp.Tools.Weather, :get_weather, %{"city" => "Shanghai"})

Everything is generated at compile time from module attributes. No runtime
path reads the `Docs` or `Dbgi` chunks, because release builds strip them.
Visible local `@typedoc` text is recovered in a second compile phase
(`@after_compile`) and embedded in a generated companion module; runtime code
never calls `Code.fetch_docs/1`. The companion is ordinary compiled code,
loaded on first use like any other module.

## Supported typespec subset

See `docs/design.md`. `@type`, `@typep` and `@opaque` declarations in the same
module are resolved before mapping, including parameterised ones. Unknown
types degrade to `{}` (any value) rather than failing compilation unless a
`ToolCall.TypeProvider` registered with `use ToolCall, types: [...]` resolves
them; types that JSON cannot express at all (functions, pids, refs) are
flagged with a compile-time warning.

# `describe`

```elixir
@spec describe(module(), keyword()) :: [
  %{
    module: module(),
    name: String.t(),
    description: String.t(),
    parameters: map()
  }
]
```

Returns a provider-neutral description of every tool in `module`.

Each entry has `:module`, `:name`, `:description` and `:parameters`, where
`:parameters` is the JSON Schema object for the arguments with no provider
envelope around it. Use this when your client owns the request format; use
`emit/3` when you want the OpenAI or Anthropic envelope.

## Options

  * `:strict` - render the strict JSON Schema subset (see `emit/3`).

# `emit`

```elixir
@spec emit(
  module() | [ToolCall.IR.Tool.t()] | ToolCall.IR.Tool.t(),
  :openai | :anthropic,
  keyword()
) ::
  map() | [map()]
```

Emits schemas for `target` (`:openai` or `:anthropic`).

For a module the supported variants are rendered at **compile time** and
embedded as a literal, so this is a direct or guarded map lookup and `strict`
may be a runtime boolean. A module returns a list with one schema per tool; a
single `ToolCall.IR.Tool` returns one schema map and a list of them a list,
rendered on demand, which is what dynamically built tools need. Runtime IR is
validated before rendering; invalid types raise `ArgumentError`.

## Options

  * `:strict` - OpenAI strict mode. The tool object carries
    `"strict": true`, every property becomes required, optional values
    become nullable and every object is closed, which is the only shape the
    API accepts when `strict: true` is set. Ignored for `:anthropic`.

# `run`

```elixir
@spec run(module(), atom() | String.t(), term()) :: {:ok, term()} | {:error, term()}
```

Runs a tool by name. `args` uses string keys, as decoded from JSON.

# `tool_names`

```elixir
@spec tool_names(module()) :: [String.t()]
```

Returns the tool names derived from a module, in declaration order.

# `tools`

```elixir
@spec tools(module()) :: [ToolCall.IR.Tool.t()]
```

Returns the tools derived from a module.

The embedded IR keeps `Type.source`, which charlist handling needs at
runtime, and drops the spec and parameter ASTs, which are compile-time
provenance only.

---

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