# `LlmToolkit.Composition`
[🔗](https://github.com/fosferon/llm_toolkit/blob/v0.1.0/{path}#L{line})

Compose multiple tool resolvers into one.

Allows consumers to merge base tools (from LlmToolkit.CodeTools) with
their own domain-specific tools into a single resolver.

## Example

    # Base tools + domain tools
    resolver = LlmToolkit.Composition.new([
      {LlmToolkit.CodeTools, "/project"},
      MyApp.DomainTools
    ])

    {:ok, tools} = resolver.available_tools()
    # Returns all base tools + domain tools

    {:ok, result} = resolver.resolve(%Call{name: "read_file", arguments: %{...}})
    # Dispatches to CodeTools

    {:ok, result} = resolver.resolve(%Call{name: "search", arguments: %{...}})
    # Dispatches to MyApp.DomainTools

## Priority

Resolvers are tried in order. First match wins. This means consumer-specific
tools can override base tools if needed (e.g., a custom bash tool with
sandboxing).

## Resolver formats

Each element in the list can be:
- A module implementing `LlmToolkit.ToolResolver` (cwd = ".")
- A `{module, cwd}` tuple for cwd-aware resolvers like CodeTools

# `t`

```elixir
@type t() :: %LlmToolkit.Composition{resolvers: [{module(), String.t()}]}
```

# `available_tools`

```elixir
@spec available_tools(t()) :: [LlmToolkit.Tool.t()]
```

Returns all available tools from all composed resolvers.

Tools from earlier resolvers appear first. Duplicate tool names are kept
(the first resolver to match a tool name wins during dispatch).

# `dispatch_recipe`

```elixir
@spec dispatch_recipe(t(), String.t()) :: (map() -&gt; map()) | nil
```

Returns the dispatch recipe for a tool name by checking each resolver.

First non-nil recipe wins.

# `new`

```elixir
@spec new([module() | {module(), String.t()}]) :: t()
```

Creates a composed resolver from a list of resolver specs.

Each spec is either a module (uses "." as cwd) or a `{module, cwd}` tuple.

# `resolve`

```elixir
@spec resolve(t(), LlmToolkit.Tool.Call.t()) ::
  {:ok, String.t()} | {:error, String.t()}
```

Resolves a tool call by trying each resolver in order.

The first resolver to return something other than "Unknown tool" wins.

---

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