# `CMDC.Provider`
[🔗](https://github.com/tuplehq/cmdc/blob/v0.4.0/lib/cmdc/provider.ex#L1)

req_llm 的薄封装层，负责发起流式 LLM 请求。

CMDC 不自建 Provider 体系，而是直接依赖 req_llm（18 个 Provider 开箱即用），
本模块只做：

1. 将 `CMDC.Message` 列表转为 req_llm `Context` 格式
2. 将 `CMDC.Tool` 模块列表转为 req_llm tool schema
3. 调用 `ReqLLM.stream_text/3` 发起流式请求
4. 启动 `CMDC.Provider.StreamBridge` 将流式 chunk 转为 gen_statem 消息

## 流式消息协议（StreamBridge → Agent）

| 消息 | 含义 |
|------|------|
| `{:cmdc_stream_chunk, StreamChunk.t()}` | 流式数据块（文本/推理/工具调用/元数据） |
| `:cmdc_stream_done` | 流正常结束 |
| `{:cmdc_stream_error, reason}` | 流出错 |

## 使用示例

    {:ok, %{bridge_pid: _pid}} = CMDC.Provider.stream(
      "anthropic:claude-sonnet-4-5",
      messages,
      tools,
      agent_pid: self(),
      api_key: "sk-...",
      system_prompt: "你是一个专业助手。"
    )

## 已知约束

使用自定义 base_url 时，**必须同时在 model map 和 opts 里传 `base_url`**。
原因：req_llm 的 OpenAI provider 流式路径从 `opts[:base_url]` 读取，
而非从 model struct 读取。若只传 model map，会 fallback 到默认 OpenAI 地址。

# `model`

```elixir
@type model() :: String.t() | map()
```

# `convert_messages`

```elixir
@spec convert_messages([CMDC.Message.t()], atom()) :: [ReqLLM.Message.t()]
```

将 `CMDC.Message` 列表转为 `ReqLLM.Message` struct 列表。

根据 provider 类型自动切换消息格式。

# `convert_tools`

```elixir
@spec convert_tools([module()]) :: [map()]
```

将工具模块列表转为 req_llm tool schema 格式。

# `stream`

```elixir
@spec stream(model(), [CMDC.Message.t()], [module()], keyword()) ::
  {:ok, %{bridge_pid: pid()}} | {:error, term()}
```

发起流式 LLM 请求，在独立进程中消费 StreamResponse 并推送给 Agent。

## 参数

- `model` — 模型标识（`"anthropic:claude-sonnet-4-5"` 或 map）
- `messages` — `CMDC.Message.t()` 列表
- `tools` — 工具模块列表（实现 `CMDC.Tool` behaviour）
- `opts` — 选项
  - `:agent_pid`（必需）— 接收流式消息的 gen_statem 进程
  - `:system_prompt` — 系统提示词
  - `:temperature` — 温度参数
  - `:max_tokens` — 最大生成 token 数
  - `:api_key` — API Key（覆盖环境变量）
  - `:base_url` — 自定义 API base URL
  - `:receive_timeout` — 接收超时毫秒数

## 返回

- `{:ok, %{bridge_pid: pid()}}` — StreamBridge 进程已启动
- `{:error, reason}` — 请求失败

---

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