# FireRedASR 本地接口适配

`@speclip/pi-speech` 的默认 ASR 模型为 `fireredasr2-ctc-int8`，provider ID 为 `fireredasr-local`。npm 包不包含模型或推理运行时，而是调用已运行的 FireRedASR macOS 桌面整合包。

## 前置条件

- 打开 `FireRedASR.app`。
- 桌面面板显示服务已启动、模型已下载。
- 服务使用默认回环地址 `http://127.0.0.1:8734`。
- 不设置 `FIREREDASR_API_KEY`。默认回环服务关闭鉴权，pi-speech 不读取或发送任何 API Key。

## 请求契约

adapter 使用 `multipart/form-data` 调用：

```text
POST http://127.0.0.1:8734/v1/audio/transcriptions
```

字段固定为：

- `file`：工作区音频快照。
- `model`：`fireredasr2-ctc-int8`。
- `language`：`auto`、`zh` 或 `en`；对应 `speech_transcribe.language`。云端专用的 `languageHints` 不适用于本地模型。
- `response_format`：`verbose_json`。
- `timestamp_granularities[]`：同时请求 `word` 和 `segment`。

Node 原生 `fetch` 会为已知长度的 Blob multipart 请求生成 `Content-Length`，满足 FireRedASR 在临时落盘前检查上传大小的要求。请求不设置 `Authorization`，也不手工设置 multipart `Content-Type`，避免破坏自动生成的 boundary。

## 响应映射

- 响应头 `X-Request-ID` → generation attempt 的 `providerRequestId`。
- `text` → transcript `text`。
- `duration`（秒）→ task 返回值的 `durationSeconds`，并写入 `usage: { unit: "audio-second", quantity: duration }`。
- `segments[].start/end`（秒）→ `sentences[].beginMs/endMs`（毫秒）。
- `words[].word/start/end` → 每个 sentence 内的 `words[]`；按 word 起点归入对应 segment。

输出仍遵守 pi-speech 的工作区边界：只写新的相对 `.json` 路径，不覆盖既有文件，并在 `.speech/generations/` 保存输入哈希、请求 ID、时长、0 美元本地 API 费用记录及输出哈希。

## 错误映射

- 无法连接本机服务 → `LOCAL_SERVICE_UNAVAILABLE`。请启动 FireRedASR.app。
- `model_not_installed` → 在 FireRedASR 桌面面板完成模型下载。
- `engine_busy` / HTTP 429、408、5xx → 作为服务明确拒绝的可重试错误；本地 task 等待 1 秒后只重试一次。
- 401 `invalid_api_key` → FireRedASR 服务被用户改成了鉴权模式；当前 local adapter 按无 Key 规范不会发送凭证。
- 非法 JSON、缺少请求 ID 或非法时间戳 → `INVALID_PROVIDER_RESPONSE`，不会伪造时间戳。

## 当前边界

FireRedASR 原生接口可处理更大的音视频上传，但 pi-speech 当前所有 ASR provider 共用 10 MiB 工作区音频快照边界，只接受 `wav`、`mp3`、`opus`、`aac`、`m4a`。本次适配没有扩展为视频、流式 partial、说话人分离或 TTS，也没有把模型管理复制到 npm 包中。
