# `CMDCTest.MockProvider`
[🔗](https://github.com/tupleyun/cmdc_test/blob/v0.1.0/lib/cmdc_test/mock_provider.ex#L1)

Mock LLM provider — Builder API 构造响应队列，配合 `CMDC.Config.provider_fn` 注入。

替代集成方各自实现的 inline mock，统一所有第三方测试的 mock LLM 行为。

## 设计

内部用 `:persistent_term` 存储响应队列 + 索引（避免持有 GenServer 进程生命周期），
`to_provider_fn/1` 返回一个 `Config.provider_fn` 接口的闭包，每次调用从队头取响应：

- 文本响应 → spawn 一个 stream bridge 进程，分块 emit 给 Agent
- 工具调用响应 → spawn 一个 bridge，emit 单个 tool_call chunk
- 错误响应 → 直接返回 `{:error, reason}`

## Quick Start

    provider =
      CMDCTest.MockProvider.new()
      |> CMDCTest.MockProvider.respond("Hello, world!")
      |> CMDCTest.MockProvider.respond_tool_call("shell", %{"cmd" => "ls"})
      |> CMDCTest.MockProvider.respond("done")

    {:ok, session} =
      CMDC.create_agent(
        model: "mock:test",
        config: %{provider_fn: CMDCTest.MockProvider.to_provider_fn(provider)}
      )

    CMDC.prompt(session, "go")   # 第 1 个 prompt 用第 1 个响应
    CMDC.prompt(session, "tool") # 第 2 个 prompt 用 tool_call 响应
    CMDC.prompt(session, "end")  # 第 3 个 prompt 用 "done"

## 清理

`:persistent_term` 在测试结束后会随 BEAM 节点持续存在；可在 `on_exit` 调用
`cleanup/1` 主动释放。短测试通常无需清理（数据量很小）。

    setup do
      provider = MockProvider.new() |> MockProvider.respond("ok")
      on_exit(fn -> MockProvider.cleanup(provider) end)
      %{provider: provider}
    end

# `response`

```elixir
@type response() ::
  {:text, String.t()} | {:tool_call, String.t(), map()} | {:error, term()}
```

# `t`

```elixir
@type t() :: %CMDCTest.MockProvider{ref: reference(), responses: [response()]}
```

# `cleanup`

```elixir
@spec cleanup(t()) :: :ok
```

释放该 MockProvider 在 :persistent_term 中的存储。

# `new`

```elixir
@spec new() :: t()
```

构造新的 MockProvider，初始响应队列为空。

## 示例

    iex> provider = CMDCTest.MockProvider.new()
    iex> provider.responses
    []

# `respond`

```elixir
@spec respond(t(), String.t()) :: t()
```

追加一条文本响应。

# `respond_error`

```elixir
@spec respond_error(t(), term()) :: t()
```

追加一条错误响应。Agent 收到后走错误恢复路径（可能 retry 或 abort）。

# `respond_tool_call`

```elixir
@spec respond_tool_call(t(), String.t(), map()) :: t()
```

追加一条工具调用响应。

## 参数

- `name` — 工具名（必须与 Agent.tools 列表中某个 `tool.name()` 匹配，否则 Agent 会发
  `:tool_call_unknown` 事件并注入 error tool_result）
- `args` — 工具参数 map（按工具自身的 parameters schema 序列化）

Agent 收到此响应后会:
1. 在当前 turn 调用对应工具
2. 收集结果作为 ToolMessage
3. 进入下一 turn 继续找 mock 队列中的下一个响应

# `to_provider_fn`

```elixir
@spec to_provider_fn(t()) :: (String.t(), list(), list(), keyword() -&gt;
                          {:ok, map()} | {:error, term()})
```

导出 `Config.provider_fn` 兼容的闭包。

接口签名：`fn model, messages, tools, opts -> {:ok, %{bridge_pid: pid}} | {:error, term}`
与 cmdc 主库 Agent.Integration 测试中的 mock 完全一致。

---

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