[English](README.md) | 简体中文

# pi-autofill-model-metadata

一个 [Pi](https://github.com/badlogic/pi-mono) 扩展：根据用户提供的**显式映射**，从 [models.dev](https://models.dev) 或独立的 Codex 规范目录取得自定义模型元数据，并在 Pi 启动时通过 `pi.registerProvider()` 补全 Provider 配置。

适用于使用第三方网关、自建代理或 OpenAI/Anthropic 兼容接口的场景。只需在 Pi 的 `models.json` 中声明连接信息和模型 ID，再在 `auto-models.jsonc` 中明确指定每个模型应采用哪一条 models.dev 元数据。

## 特性

- 从 models.dev 填充模型名称、上下文窗口、最大输出、推理能力、输入模态和价格。
- 只接受显式的 `models.dev-provider/models.dev-model` 映射，不进行全局猜测。
- 映射必须完整覆盖目标 Provider，避免重新注册时静默丢失模型。
- 保留 `models.json` 中用户明确设置的模型字段。
- 缓存 models.dev 数据，默认有效期为 24 小时。
- 支持通过一行代理 URL 配置 HTTP(S) 或 SOCKS/SOCKS5 网络代理，可填写用户名和密码。
- 下载失败时可回退到结构有效的过期缓存。
- 使用原子缓存写入，避免进程中断留下半写入文件。
- 可输出经过凭据脱敏的调试快照。
- 不修改 `models.json` 或 `auto-models.jsonc`；所有增强只对当前 Pi 运行时生效。
- 配置、缓存、解析和 Provider 注册均有自动化测试覆盖。

## 工作原理

Pi 加载扩展时，本扩展会：

1. 读取 `~/.pi/agent/models.json`，取得 Provider 的地址、API 类型、凭据、Headers 和模型列表。
2. 读取 `~/.pi/agent/auto-models.jsonc`，取得代理设置、每个 Pi 模型对应的显式元数据来源和缓存/调试选项。
3. 检查映射是否完整、是否包含多余模型，以及配置字段类型是否正确。
4. 按来源静态分派到 models.dev 或 Codex 适配器；仅在首次遇到 models.dev 来源时读取缓存或请求 API。
5. 仅解析显式选中的来源模型，校验 models.dev 必要字段，并统一为来源无关的元数据与溯源记录。
6. 由字段映射器一次性构造完整 Pi 模型配置，并合并 `models.json` 中用户明确设置的覆盖值。
7. 所有来源均成功解析后，通过 `pi.registerProvider()` 在内存中原子注册或覆盖 Provider。

```text
~/.pi/agent/models.json
          │
          ├── Provider 地址、API、凭据、模型 ID
          │
          ▼
~/.pi/agent/auto-models.jsonc ──► 显式元数据来源
          │
          ├── models.dev 适配器 ──► API / 本地缓存（按需一次）
          └── Codex 适配器 ───────► 独立规范目录
          │
          ▼
来源无关规范化 + 字段转换 + 用户覆盖
          │
          ▼
pi.registerProvider()（仅当前运行时）
```

## 要求

- 已安装 Pi。
- Node.js 具备原生 `fetch` 支持；建议使用当前 Node.js LTS。
- 已在 `~/.pi/agent/models.json` 中配置自定义 Provider。
- 使用 models.dev 来源时，可以访问 models.dev，或者本地已有有效缓存。
- 如当前网络无法直接访问 models.dev，可在 `auto-models.jsonc` 中配置 HTTP(S) 或 SOCKS/SOCKS5 代理。

> [!IMPORTANT]
> Pi 扩展以当前用户权限运行。安装任何第三方扩展前都应检查其源码。

## 安装

### 从 npm 安装

```bash
pi install npm:pi-autofill-model-metadata
```

### 从 GitHub monorepo 安装

```bash
pi install git:github.com/peach0x33a/pi-extensions
pi config
```

Git package 会下载整个 monorepo。运行 `pi config` 后，只启用 `packages/autofill-model-metadata/index.ts` 即可。

安装后可查看 Pi 已登记的包：

```bash
pi list
```

### 从本地源码安装

```bash
git clone https://github.com/peach0x33a/pi-extensions.git
cd pi-extensions
bun install
pi install "$PWD/packages/autofill-model-metadata"
```

Pi 对本地包只记录路径，不会复制源码。修改本地源码后，重新启动 Pi 或执行 `/reload` 即可加载新版本。

### 不安装直接试用

```bash
pi -e /absolute/path/to/pi-extensions/packages/autofill-model-metadata --list-models
```

## 配置

扩展同时读取两个文件：

| 文件                            | 作用                                                            |
| ------------------------------- | --------------------------------------------------------------- |
| `~/.pi/agent/models.json`       | Pi Provider、连接信息、凭据和模型 ID                            |
| `~/.pi/agent/auto-models.jsonc` | Pi 模型到 models.dev 或 Codex 模型的显式映射，以及缓存/调试选项 |

### 1. 配置 `models.json`

示例：

```json
{
  "providers": {
    "my-proxy": {
      "name": "My Proxy",
      "baseUrl": "https://proxy.example.com/v1",
      "apiKey": "$MY_PROXY_API_KEY",
      "api": "openai-responses",
      "models": [{ "id": "gpt-main" }, { "id": "claude-main" }]
    }
  }
}
```

Provider 名称和模型 ID 是本地标识，可以与 models.dev 中的名称不同。`apiKey` 推荐使用环境变量引用，不要把真实密钥提交到版本控制。

扩展会读取以下 Provider 字段：

- `name`
- `baseUrl`
- `apiKey`
- `api`
- `authHeader`
- `headers`
- `models`

`models` 中的条目既可以是字符串，也可以是含 `id` 的对象：

```json
{
  "models": [
    "gpt-main",
    {
      "id": "claude-main",
      "name": "内部 Claude",
      "maxTokens": 8192
    }
  ]
}
```

### 2. 配置 `auto-models.jsonc`

创建 `~/.pi/agent/auto-models.jsonc`：

```jsonc
{
  // 可选；支持 http(s)://、socks:// 和 socks5://，可包含 user:password@
  "proxy": "socks5://username:password@127.0.0.1:1080",

  // 单位：秒；默认 86400（24 小时）
  "cacheTTL": 86400,

  // 可选；默认 ~/.pi/agent/models-dev-cache.json
  "cachePath": "~/.pi/agent/models-dev-cache.json",

  "mapping": {
    "my-proxy": {
      "gpt-main": "openai/gpt-5.4",
      "claude-main": "anthropic/claude-sonnet-4-6",
    },
  },
}
```

`proxy` 只代理本扩展对 models.dev API 的请求，不会修改 Pi 其他 Provider 的网络请求。代理地址、用户名和密码全部放在同一行 URL 中；密码包含特殊字符时请按 URL 规则进行百分号编码（例如 `@` 写成 `%40`）。不需要代理时省略该字段。

映射结构为：

```text
Pi Provider 名称
  └── Pi 模型 ID: 元数据源/模型 ID
```

例如：

```jsonc
{
  "mapping": {
    "company-gateway": {
      "fast-model": "google/gemini-2.5-flash",
      "reasoning-model": "openai/o3",
    },
  },
}
```

models.dev 来源可以在 <https://models.dev> 或 <https://models.dev/api.json> 中查询。

对于 models.dev 没有收录的 Codex GPT 模型，可以直接使用 `codex/<model-id>`：

```jsonc
{
  "mapping": {
    "my-responses-provider": {
      "gpt-5.6-sol": "codex/gpt-5.6-sol",
    },
  },
}
```

Codex 来源来自独立的 `pi-codex-gpt-metadata` 规范目录包。该包只维护纯数据目录，不注册 Pi Provider；本扩展的 Codex 适配器负责克隆并规范化目录值。如果所有映射都以 `codex/` 开头，初始化时不会取得或下载 models.dev 数据。`pi-autofill-model-metadata` 不依赖 `pi-gpt-enhance`，单独安装时 models.dev 和 Codex 元数据填充均可使用。

`pi-codex-gpt-metadata` 不会出现在 Pi package gallery 中，这是有意的：它是普通 npm 依赖，而不是可单独安装的 Pi 扩展；它没有 `pi.extensions` 入口，也没有 `pi-package` keyword。请在 Pi 中搜索并安装 `pi-autofill-model-metadata`，npm 会通过本扩展的直接依赖自动安装 Codex 目录包。

如需 OpenAI Responses 服务端压缩，可另外安装 [`pi-gpt-enhance`](../gpt-enhance)。两个扩展分别注册模型元数据和请求流，Pi 会合并 Provider 配置，安装及加载顺序不影响最终结果。

## 严格映射策略

本扩展采用 fail-closed 策略。对于 `mapping` 中出现的每个 Provider：

- `models.json` 中的每个模型都必须有映射。
- 映射中不能出现 `models.json` 未声明的模型。
- 每个映射来源都必须能在指定的 models.dev Provider 或共享 Codex 目录内解析。
- 不会跨 Provider 搜索同名模型。
- 不会选择“第一个匹配项”：大小写不敏感匹配出现多个候选时，该来源视为解析失败。
- 被映射的 Provider 及其模型字段必须通过类型校验，校验失败会中止注册。

如果任一检查失败，本次扩展注册会整体中止，不会提交部分 Provider 配置。这可以避免因为拼写错误、映射缺失或远端数据变化而让部分模型静默消失。

未写入 `mapping` 的其他 Pi Provider 不受本扩展影响；即使这些 Provider 在 `models.json` 中存在格式问题，也只会被本扩展忽略，不影响已映射 Provider 的注册。

## 填充字段与覆盖规则

扩展从 models.dev 或共享 Codex 目录填充：

| models.dev         | Pi                                |
| ------------------ | --------------------------------- |
| `name`             | `name`                            |
| `limit.context`    | `contextWindow`                   |
| `limit.output`     | `maxTokens`                       |
| `reasoning`        | `reasoning`                       |
| `modalities.input` | `input`，仅保留 `text` 和 `image` |
| `cost.input`       | `cost.input`                      |
| `cost.output`      | `cost.output`                     |
| `cost.cache_read`  | `cost.cacheRead`                  |
| `cost.cache_write` | `cost.cacheWrite`                 |

models.dev 的 `reasoning_options` 中 `effort` 值会填充 `thinkingLevelMap`：支持的等级映射为同名 Pi 等级，`none` 映射到 `off`，不支持的等级为 `null`（Pi 只显示 `[off]`）。没有 `effort`（仅 `toggle` 或缺失）的模型得到全 `null` map，Pi 不会再用捏造的默认等级列表兜底。

Codex 来源还会填充对应模型的 `thinkingLevelMap`、Responses `compat` 能力和分级价格。

如果 `models.json` 的模型对象明确设置了以下字段，用户值将覆盖 models.dev 的默认值：

- `name`
- `contextWindow`
- `maxTokens`
- `reasoning`
- `input`
- `cost` 中的单独字段
- `api`
- `baseUrl`
- `headers`
- `compat`
- `thinkingLevelMap`

因此可以使用 models.dev 作为默认元数据来源，同时针对代理服务的真实能力进行修正。

用户覆盖值在加载时会进行类型校验（例如 `maxTokens` 必须是正数、`cost` 字段必须是非负数、`input` 只能包含 `text`/`image`）。类型不合法时，该 Provider 会被标记为无效；若它同时被映射，注册会整体中止并输出原因。

## 缓存

默认缓存文件：

```text
~/.pi/agent/models-dev-cache.json
```

默认有效期：

```text
86400 秒（24 小时）
```

行为：

1. 缓存仍然新鲜时，不访问网络。
2. 缓存过期或不存在时，从 models.dev 下载最新数据；配置了 `proxy` 时，该请求通过指定代理发出。
3. 下载成功后，使用临时文件和原子重命名更新缓存。
4. 下载失败且存在结构有效的过期缓存时，使用过期缓存并打印警告。
5. 下载失败且没有有效缓存时，中止注册。

强制刷新可删除缓存后重新运行 Pi：

```bash
rm ~/.pi/agent/models-dev-cache.json
pi --list-models
```

也可以将 `cacheTTL` 设置为 `0`，使每次加载都尝试刷新；网络失败时仍会回退到有效旧缓存。

## 调试输出

在 `auto-models.jsonc` 中启用：

```jsonc
{
  "mapping": {
    "my-proxy": {
      "gpt-main": "openai/gpt-5.4",
    },
  },
  "debug": {
    "enabled": true,
    "dumpPath": "~/.pi/agent/expanded-models.json",
    "diffOnly": true,
  },
}
```

选项：

| 选项             |                             默认值 | 说明                                                 |
| ---------------- | ---------------------------------: | ---------------------------------------------------- |
| `debug.enabled`  |                            `false` | 是否生成调试文件                                     |
| `debug.dumpPath` | `~/.pi/agent/expanded-models.json` | 输出路径，支持 `~/`                                  |
| `debug.diffOnly` |                             `true` | 只输出发生变化的 Provider；设为 `false` 输出完整快照 |

调试输出包含：

- 生成时间；
- models.dev 缓存年龄；
- 已填充和失败的模型摘要，含每个模型来自 models.dev 的字段（`filledFields`）与被用户覆盖的字段（`overriddenFields`）；
- Provider 差异或最终快照。

Provider API Key、Provider Headers 和模型级 Headers 会被替换为 `<redacted>`。调试文件仍可能包含内部 Provider 地址、模型名称等信息，请勿在未检查内容前公开提交。

## 验证安装

运行：

```bash
pi --list-models
```

扩展正常初始化时保持静默，避免在 subagent、SDK 会话或其他资源重载场景中绕过 Pi 的渲染层输出到终端。验证时请直接检查模型列表：模型的上下文窗口、最大输出、推理和图像能力应使用补全后的数据。

只有配置无效、映射无法解析、下载失败且无可用缓存等异常情况才会写入错误或警告。

## 常见错误与排查

### 找不到配置文件

扩展未找到映射文件时会静默跳过注册（视为未启用）。确认文件位于：

```text
~/.pi/agent/auto-models.jsonc
```

如果映射文件存在但 `models.json` 缺失或无法解析，会输出：

```text
auto-models.jsonc is configured but models.json could not be loaded; registration skipped
```

此时请检查 `~/.pi/agent/models.json` 是否存在且为合法 JSON。

### 映射的 Provider 校验失败

```text
Mapped provider "my-proxy" is invalid in models.json: providers.my-proxy.models[0].maxTokens must be a finite positive number
```

按提示修正 `models.json` 中对应 Provider 的字段类型。未被映射的 Provider 即使校验失败也不会影响注册。

### 缺少显式映射

```text
Missing explicit mapping for my-proxy/model-id
```

为 `models.json` 中该 Provider 的每个模型补充映射。目标 Provider 必须完整映射，不能只映射其中一部分。

### 映射了不存在的本地模型

```text
my-proxy/model-id is mapped but absent from models.json
```

删除多余映射，或在 `models.json` 的对应 Provider 中声明该模型。

### models.dev Provider 或模型不存在

```text
Source provider "..." not found
Source model "..." not found
```

检查映射右侧是否使用 models.dev 的真实 Provider ID 和模型 ID。扩展不会跨 Provider 猜测来源。

### models.dev 数据加载失败

```text
Failed to load models.dev data: ...
```

检查网络连接、`proxy` URL（必须是 `http://`、`https://`、`socks://` 或 `socks5://`）和缓存文件。若缓存损坏，可删除：

```bash
rm ~/.pi/agent/models-dev-cache.json
```

然后重新运行 Pi。

### 修改配置后没有变化

重新启动 Pi，或在交互会话中执行：

```text
/reload
```

扩展不会把补全结果写回 `models.json`，因此只检查文件内容无法看到运行时增强结果；请使用 `pi --list-models` 或 Debug 输出确认。

## 更新与卸载

更新所有已安装扩展：

```bash
pi update --extensions
```

如果使用 npm 安装：

```bash
pi remove npm:pi-autofill-model-metadata
```

如果使用 GitHub monorepo 安装：

```bash
pi remove git:github.com/peach0x33a/pi-extensions
```

如果使用本地路径安装：

```bash
pi remove /absolute/path/to/pi-extensions/packages/autofill-model-metadata
```

卸载扩展不会删除以下用户文件：

- `~/.pi/agent/models.json`
- `~/.pi/agent/auto-models.jsonc`
- `~/.pi/agent/models-dev-cache.json`
- 自定义 Debug 输出文件

如不再需要，请自行删除。

## 开发

```bash
git clone https://github.com/peach0x33a/pi-extensions.git
cd pi-extensions
bun install
bun run check
```

可用命令：

| 命令                                                     | 说明                         |
| -------------------------------------------------------- | ---------------------------- |
| `bun run --filter pi-autofill-model-metadata test`       | 运行 Vitest 测试             |
| `bun run --filter pi-autofill-model-metadata typecheck`  | 运行 TypeScript 类型检查     |
| `bun run --filter pi-autofill-model-metadata check`      | 依次运行类型检查和测试       |
| `pi -e ./packages/autofill-model-metadata --list-models` | 使用当前源码进行真实 Pi 验收 |

项目模块：

| 文件                    | 职责                                               |
| ----------------------- | -------------------------------------------------- |
| `index.ts`              | 扩展入口、映射覆盖检查、原子 Provider 注册         |
| `config.ts`             | 读取并验证 Pi 配置和映射配置                       |
| `cache.ts`              | models.dev 下载、代理、校验、缓存与回退            |
| `resolver.ts`           | 解析来源、静态分派，并延迟/复用 models.dev 获取    |
| `models-dev-adapter.ts` | models.dev 选择、校验、推理选项解释和规范化        |
| `codex-adapter.ts`      | 克隆并规范化独立包中的 Codex 规范目录值            |
| `field-mapper.ts`       | 完整 Pi 模型构造、用户覆盖、字段溯源与不可变性边界 |
| `debug.ts`              | 生成脱敏调试快照                                   |
| `types.ts`              | 来源无关契约、models.dev、配置和 Pi 输入数据类型   |
| `types-ext.ts`          | 本扩展使用的最小 Pi Provider 类型                  |
| `util.ts`               | 共享工具（路径展开、日志前缀、类型判定）           |
| `test/`                 | 单元和集成测试                                     |

## 设计原则

- **显式优于猜测：** 同名模型在不同 Provider 下可能具有不同限制、价格或能力。
- **失败关闭：** 不完整配置不应产生看似成功的部分注册。
- **用户配置优先：** models.dev 提供默认值，用户可以按代理实际能力覆盖。
- **不写回配置：** 扩展只注册运行时状态，避免意外修改凭据或用户文件。
- **外部输入不可信：** 配置、网络响应和缓存均在使用前验证。

## 致谢

本项目的设计和实现参考了 [opencode-auto-model-config](https://github.com/chisaato/opencode-auto-model-config) —— 一个为 OpenCode 提供类似模型元数据自动填充功能的插件。感谢原作者 chisaato 的出色工作与启发。

## 数据来源

模型元数据来自 [models.dev](https://models.dev)。数据准确性和更新频率由 models.dev 提供；代理服务的实际限制可能不同，应以服务商文档为准，并在 `models.json` 中使用用户覆盖字段进行修正。

## 许可证

本项目基于 [MIT 许可证](LICENSE) 发布。
