# 03 · 配置契约

## 1. 配置位置与所有权

计划配置文件：

```text
<getAgentDir()>/permission-explain.json
```

默认对应：

```text
~/.pi/agent/permission-explain.json
```

使用 Pi 导出的 `getAgentDir()`，不要硬编码用户名或忽略 `PI_CODING_AGENT_DIR`。

这是本扩展自己的文件。不得修改权限插件配置、`models.json`、`auth.json` 或 Pi 的 `defaultModel` / `defaultProvider`。

首版只支持用户级配置，不接受项目目录覆盖，避免仓库静默改变解释请求的模型或发送目标。

## 2. 最小配置

```json
{
  "shortcut": "alt+e",
  "explanationModel": null,
  "language": "auto"
}
```

缺少文件表示使用默认值，不应在启动时为了补默认值而写文件。

## 3. 完整配置示例

以下为拟定配置，不代表文件已创建或功能已实现。

```json
{
  "enabled": true,
  "shortcut": "alt+e",
  "explanationModel": null,
  "language": "auto",
  "reasoning": "off",
  "maxOutputTokens": 384,
  "timeoutMs": 20000,
  "maxInputChars": 12000,
  "cache": {
    "enabled": true,
    "maxEntries": 16,
    "ttlMs": 300000,
    "maxBytes": 262144
  }
}
```

## 4. 字段定义

| 字段 | 类型 | 默认 | 语义 |
| --- | --- | --- | --- |
| `enabled` | boolean | `true` | 关闭后不拦截解释键、不请求模型 |
| `shortcut` | string | `"alt+e"` | Pi 支持的组合键表示；配置校验必须严格 |
| `explanationModel` | null 或模型引用对象 | `null` | null / 缺省表示跟随触发时当前模型 |
| `language` | `"auto"` 或语言标签 | `"auto"` | 解释语言偏好，同时作为扩展 UI 语言选择依据 |
| `reasoning` | `"off"` 或 `"low"` | `"off"` | 解释请求的独立推理偏好，不继承主会话 max |
| `maxOutputTokens` | integer | `384` | 请求输出预算，建议允许 128–2048 |
| `timeoutMs` | integer | `20000` | 单次解释的本地超时，建议允许 1000–120000 |
| `maxInputChars` | integer | `12000` | 待发送完整解释输入的字符上限，建议允许 512–64000 |
| `cache.enabled` | boolean | `true` | 是否启用会话内缓存 |
| `cache.maxEntries` | integer | `16` | 缓存条目上限，建议允许 0–128；0 等同禁用 |
| `cache.ttlMs` | integer | `300000` | 缓存有效时长，建议允许 0–1800000 |
| `cache.maxBytes` | integer | `262144` | 缓存保留内容的估算字节预算，建议允许 0–2097152 |

缓存字节预算不是对 JavaScript 堆占用的精确测量；实现必须说明估算口径，并同时限制条目数和文本长度。

## 5. 固定模型

TUI 选中模型后写入：

```json
{
  "explanationModel": {
    "provider": "所选提供商的注册ID",
    "modelId": "所选模型的原始ID"
  }
}
```

这只是引用形状示意，不是可直接调用的真实模型名称。

- 保存 `provider` 与 `modelId` 两个独立字段；模型 ID 可以包含 `/`，不可自行按斜线拆分。
- 不保存模型完整对象、API Key、base URL 或 token。
- TUI 显示模型名称与 provider；调用时通过 Pi 注册表重新定位。
- 配置便宜模型后，主会话模型保持不变。
- 指定模型不存在、未认证或请求失败：解释失败，不回退当前模型。
- 用户明确选择“跟随当前模型”才把字段清为 null。
- 注册表中的虚拟模型可由 Pi 路由到实际模型；应在选择器中标记。扩展不自行添加 fallback，但不能覆盖用户已有路由策略。若要求固定价格与数据接收方，建议选具体模型。

## 6. 默认模型的时序

`explanationModel: null` 的精确定义是：

> 每次真正发起解释时读取 `ctx.model`，并为该次请求固定这个模型。

不是启动时记录一个模型，也不是从 `settings.json` 的 defaultModel 取值。

主会话模型在解释进行中改变，不改变已发出的请求；下一次触发使用新的当前模型。

## 7. 语言与推理偏好

### 语言

- `auto` 使用 [多语言设计](05-i18n.md) 的本地解析链。
- 显式标签例如 `zh-CN`、`zh-TW`、`en`、`ja`、`de-DE`。
- 长度上限建议 64，使用 `Intl` 规范化并剥离不相关的区域格式扩展。
- 非法显式语言配置不得默默变成另一种语言；提示修复。
- UI 没有对应字典时可回退英文，但解释仍应请求目标语言。

### 推理

`reasoning` 是项目配置值，不表示直接把 `"off"` 原样塞进所有 provider 的 API 字段。

适配器应将 off 映射为 Pi/provider 支持的关闭或未启用推理方式，low 映射为支持的低强度设置。不能仅假设省略 `reasoning` 就一定关闭推理：部分 provider 可能采用自己的默认值。应按所支持 provider 的公开选项验证适配；没有可靠关闭方式时标注限制，不能声称已经禁用。

不支持关闭推理的模型可能仍产生推理 token；输出预算也可能包含推理部分。到达预算而没有正文应作为失败处理，不自动续写。

不修改 `pi.setThinkingLevel()`，不为了“关闭推理”影响主会话。

## 8. 设置保存与生效

- TUI 保存采用“校验 → 原子写入 → 更新内存 → 显示成功”顺序。
- 写失败时保留原内存配置，不能显示已保存。
- 保存成功后下一次解释使用新配置，不中途改变当前请求。
- 手动编辑配置可在下次打开设置或触发解释时按需检查修改时间并重载。
- 不为此启动常驻文件 watcher，不在每次普通按键时访问磁盘。
- 新文件可通过同目录临时文件加 rename 保存；Windows 上需实测替换与失败恢复。
- 不在启动时把 null 自动改写为某个具体模型。

## 9. 无效配置与失败策略

| 情况 | 行为 |
| --- | --- |
| 文件不存在 | 使用默认值 |
| JSON 损坏或字段非法 | 保留已加载的上一份有效配置；无有效配置则停用解释并提示修复 |
| 固定模型消失 | 保留其引用并显示不可用，不改写为 null |
| 快捷键非法 | 不注册该非法绑定；有上一份有效配置则继续沿用，否则停用解释并提示修复，不能退化成裸字母键 |
| 输入超过上限 | 不发送，提示输入过长；不偷偷截断后给出完整安全结论 |
| 网络失败、超时、空输出 | 仅本次解释失败，原审批照常 |
| 目标语言生成不正确 | 不自动追加翻译请求，允许用户显式重试或更换模型 |

未知配置字段可以保留以支持向前扩展，但已知字段必须严格校验。缺省与非法值不能混为一谈，尤其不能因固定模型字段损坏而调用昂贵的主模型。

## 10. 最小化 TUI 设置

常用项：启用、解释快捷键、解释模型、语言。

高级项：推理偏好、输出预算、超时、缓存开关。其余限制保留为可编辑配置，不必全部挤进首屏。

所有配置字段的序列化名称使用稳定英文；展示标签可以本地化。
