pi-models-discovery 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -3
- package/README.zh-CN.md +21 -3
- package/SKILL.md +1 -1
- package/package.json +1 -1
- package/src/index.ts +17 -8
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# pi-models-discovery
|
|
2
2
|
|
|
3
|
-
Generic model discovery extension: reads providers marked with `"discoverModels": true` in `~/.pi/agent/models.json`, requests `GET {baseUrl}/models
|
|
3
|
+
Generic model discovery extension: reads providers marked with `"discoverModels": true` in `~/.pi/agent/models.json`, requests `GET {baseUrl}/models` (or a configured `modelsUrl`), and registers the discovered models automatically — no handwritten `models` array required. After the first successful discovery the model list is persisted to a local cache, so subsequent startups read the cache and **perform no network requests**.
|
|
4
4
|
|
|
5
5
|
Suitable for local/self-hosted LLM proxies (one gateway exposing many models), Ollama, vLLM, and other OpenAI-compatible services.
|
|
6
6
|
|
|
@@ -40,7 +40,25 @@ Add `"discoverModels": true` to a provider in `~/.pi/agent/models.json`:
|
|
|
40
40
|
}
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
`baseUrl` and `api` are required; `apiKey` is optional (omit for unauthenticated services). Multiple providers sharing the same `
|
|
43
|
+
`baseUrl` and `api` are required; `apiKey` is optional (omit for unauthenticated services). Multiple providers sharing the same model-list URL, `apiKey`, `headers`, and `compat` reuse a single discovery request.
|
|
44
|
+
|
|
45
|
+
If the model catalog uses a different endpoint, set the optional `modelsUrl` in models.json to its **complete URL** (including the path and any query parameters):
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"providers": {
|
|
50
|
+
"foo": {
|
|
51
|
+
"baseUrl": "https://api.example.com/v1",
|
|
52
|
+
"modelsUrl": "https://catalog.example.com/v1/models",
|
|
53
|
+
"apiKey": "$FOO_API_KEY",
|
|
54
|
+
"api": "openai-completions",
|
|
55
|
+
"discoverModels": true
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`modelsUrl` is used as-is for discovery and refresh only; chat requests still use `baseUrl`. When omitted or empty, discovery uses `{baseUrl}/models`. Discovery sends the provider's existing `apiKey` and `headers` to this URL, so use a trusted endpoint. The response format remains unchanged.
|
|
44
62
|
|
|
45
63
|
Hand edits require `/reload` to take effect; changes made through `/config:model-discovery` apply immediately.
|
|
46
64
|
|
|
@@ -54,7 +72,7 @@ Forces a rediscovery of every discovery provider and updates the local cache, no
|
|
|
54
72
|
|
|
55
73
|
## Behavior
|
|
56
74
|
|
|
57
|
-
- **Startup (cache-first)**: when the cache hits, models are registered directly from the persisted list with zero network requests. The cache lives at `~/.pi/agent/extensions/pi-models-discovery/cache.json`. The cache is invalidated automatically when the provider configuration fingerprint (baseUrl+api+apiKey+headers+compat) changes, triggering a fresh network discovery.
|
|
75
|
+
- **Startup (cache-first)**: when the cache hits, models are registered directly from the persisted list with zero network requests. The cache lives at `~/.pi/agent/extensions/pi-models-discovery/cache.json`. The cache is invalidated automatically when the provider configuration fingerprint (baseUrl+api+apiKey+headers+compat+modelsUrl) changes, triggering a fresh network discovery.
|
|
58
76
|
- **Online refresh**: `/config:model-discovery-refresh`, or the `refreshModels` hook triggered when opening `/model`, rediscovers online and updates the cache.
|
|
59
77
|
- **Offline / fetch failure**: handwritten `models` in models.json (if any) are kept as a fallback, and an explicit warning is surfaced via in-session notify — never a silent degradation. One provider failing does not affect the others.
|
|
60
78
|
- **apiKey resolution** (discovery request only): supports literals and `$ENV_VAR` / `${ENV_VAR}` interpolation; `!command` values skip discovery with an explicit warning (chat requests are still resolved by pi itself and are unaffected).
|
package/README.zh-CN.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# pi-models-discovery
|
|
2
2
|
|
|
3
|
-
通用模型发现插件:读取 `~/.pi/agent/models.json` 中带 `"discoverModels": true` 的 provider,请求 `GET {baseUrl}/models`
|
|
3
|
+
通用模型发现插件:读取 `~/.pi/agent/models.json` 中带 `"discoverModels": true` 的 provider,请求 `GET {baseUrl}/models`(或配置的 `modelsUrl`)自动发现模型并注册,无需手写 `models` 数组。首次发现成功后模型列表持久化到本地缓存,之后每次启动直接读缓存,**不再请求网络**。
|
|
4
4
|
|
|
5
5
|
适合本地/自建 LLM 代理(一个网关暴露多个模型)、Ollama、vLLM 等 OpenAI 兼容服务。
|
|
6
6
|
|
|
@@ -40,7 +40,25 @@ pi install npm:pi-models-discovery
|
|
|
40
40
|
}
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
`baseUrl` 和 `api` 必填;`apiKey`
|
|
43
|
+
`baseUrl` 和 `api` 必填;`apiKey` 可选(无鉴权服务可省略)。模型列表 URL、`apiKey`、`headers` 和 `compat` 相同的多个 provider 共享一次发现请求。
|
|
44
|
+
|
|
45
|
+
如果模型列表使用不同的端点,可在 models.json 中设置可选的 `modelsUrl`,值为模型列表的**完整 URL**(包含路径和所需查询参数):
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"providers": {
|
|
50
|
+
"foo": {
|
|
51
|
+
"baseUrl": "https://api.example.com/v1",
|
|
52
|
+
"modelsUrl": "https://catalog.example.com/v1/models",
|
|
53
|
+
"apiKey": "$FOO_API_KEY",
|
|
54
|
+
"api": "openai-completions",
|
|
55
|
+
"discoverModels": true
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`modelsUrl` 仅用于模型发现和刷新,按原值请求;聊天请求仍使用 `baseUrl`。未配置或为空时,沿用 `{baseUrl}/models`。发现请求会将 provider 现有的 `apiKey` 和 `headers` 发送到该 URL,请使用可信端点。响应格式保持不变。
|
|
44
62
|
|
|
45
63
|
手编方式修改配置后需 `/reload` 生效;`/config:model-discovery` 命令的修改立即生效。
|
|
46
64
|
|
|
@@ -54,7 +72,7 @@ pi install npm:pi-models-discovery
|
|
|
54
72
|
|
|
55
73
|
## 行为
|
|
56
74
|
|
|
57
|
-
- **启动(缓存优先)**:缓存命中时直接用持久化的模型列表注册,零网络请求;缓存文件位于 `~/.pi/agent/extensions/pi-models-discovery/cache.json`。provider 配置指纹(baseUrl+api+apiKey+headers+compat)变化时缓存自动失效,重新走网络发现。
|
|
75
|
+
- **启动(缓存优先)**:缓存命中时直接用持久化的模型列表注册,零网络请求;缓存文件位于 `~/.pi/agent/extensions/pi-models-discovery/cache.json`。provider 配置指纹(baseUrl+api+apiKey+headers+compat+modelsUrl)变化时缓存自动失效,重新走网络发现。
|
|
58
76
|
- **在线刷新**:`/config:model-discovery-refresh` 或 `/model` 打开时触发的 `refreshModels` 在线重新发现,并同步更新缓存。
|
|
59
77
|
- **离线 / 拉取失败**:保留 models.json 里手写的 `models`(如有,作为回退),并通过会话内 notify 显式警告,不静默降级;单个 provider 失败不影响其他 provider。
|
|
60
78
|
- **apiKey 解析**(仅发现请求):支持字面量与 `$ENV_VAR` / `${ENV_VAR}` 插值;`!command` 形式跳过发现并显式警告(聊天请求仍由 pi 自身解析执行,不受影响)。
|
package/SKILL.md
CHANGED
|
@@ -13,7 +13,7 @@ description: "配置与排查 models.json 的动态模型发现、缓存刷新
|
|
|
13
13
|
- `extensions/pi-models-discovery/cache.json`:启动缓存;
|
|
14
14
|
- `models.json.discovery-bak`:交互配置写回前的备份。
|
|
15
15
|
|
|
16
|
-
只处理带 `discoverModels: true` 的 provider。确认 `baseUrl`、`api`、可选 `apiKey`、headers/compat
|
|
16
|
+
只处理带 `discoverModels: true` 的 provider。确认 `baseUrl`、`api`、可选 `modelsUrl` / `apiKey`、headers/compat 以及服务的模型列表响应。发现请求使用 `GET modelsUrl`(完整 URL,不追加 `/models`);未配置或为空时使用 `GET {baseUrl}/models`。`modelsUrl` 仅影响发现和刷新,不改变聊天请求的 `baseUrl`;它会接收现有鉴权信息,须使用可信端点。
|
|
17
17
|
|
|
18
18
|
## 修改
|
|
19
19
|
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* 通用模型发现插件
|
|
3
3
|
*
|
|
4
4
|
* 读取 models.json 中带 "discoverModels": true 的 provider,请求
|
|
5
|
-
* GET {baseUrl}/models
|
|
5
|
+
* GET modelsUrl(缺省为 {baseUrl}/models)自动发现模型并注册,无需手写 models 数组。
|
|
6
6
|
*
|
|
7
7
|
* 配置方式(二选一):
|
|
8
8
|
* 1. pi 终端内执行 /config:model-discovery,交互式添加/删除/重新发现 provider(推荐);
|
|
@@ -22,14 +22,14 @@
|
|
|
22
22
|
* 行为约定:
|
|
23
23
|
* - 首次发现成功后,模型列表持久化到 ~/.pi/agent/extensions/pi-models-discovery/cache.json;
|
|
24
24
|
* 之后每次启动直接读缓存注册,不请求网络。配置指纹
|
|
25
|
-
* (baseUrl+api+apiKey+headers+compat)变化时缓存自动失效,重新走网络发现。
|
|
25
|
+
* (baseUrl+api+apiKey+headers+compat+modelsUrl)变化时缓存自动失效,重新走网络发现。
|
|
26
26
|
* - /config:model-discovery-refresh 强制重新拉取所有发现 provider 并更新缓存;
|
|
27
27
|
* /model 打开时触发的在线 refreshModels 同样走网络并同步更新缓存。
|
|
28
28
|
* - baseUrl / api 由扩展显式转发(pi 的 extension 组合层要求),
|
|
29
29
|
* apiKey / name / headers / compat 不写回注册配置,由 pi 的 models.json 层回落生效。
|
|
30
30
|
* - provider 级 compat 会被合并进每个发现的模型(pi 的 models.json provider 级 compat
|
|
31
31
|
* 不作用于 extension 注册的模型,故在此转发)。
|
|
32
|
-
* -
|
|
32
|
+
* - 同一模型列表 URL+apiKey+headers+compat 的多个 provider 共享一次发现请求。
|
|
33
33
|
* - 发现失败:该 provider 保留 models.json 手写 models(如有,作为离线回退),
|
|
34
34
|
* 并通过 notify 显式警告,不静默降级;单个 provider 失败不影响其他 provider 注册。
|
|
35
35
|
* - 注册 refreshModels:打开 /model 触发在线刷新时重新发现;
|
|
@@ -80,6 +80,7 @@ interface DiscoveryProviderEntry {
|
|
|
80
80
|
id: string;
|
|
81
81
|
name?: string;
|
|
82
82
|
baseUrl?: string;
|
|
83
|
+
modelsUrl?: string;
|
|
83
84
|
apiKey?: string;
|
|
84
85
|
api?: string;
|
|
85
86
|
headers?: Record<string, string>;
|
|
@@ -132,6 +133,8 @@ function providerFingerprint(entry: DiscoveryProviderEntry): string {
|
|
|
132
133
|
entry.apiKey ?? "",
|
|
133
134
|
entry.headers ?? {},
|
|
134
135
|
entry.compat ?? {},
|
|
136
|
+
// 未配置 modelsUrl 时保留已有指纹,避免让旧缓存失效。
|
|
137
|
+
...(entry.modelsUrl ? [entry.modelsUrl] : []),
|
|
135
138
|
]);
|
|
136
139
|
}
|
|
137
140
|
|
|
@@ -247,6 +250,7 @@ function pickDiscoveryProviders(data: Record<string, unknown>): DiscoveryProvide
|
|
|
247
250
|
id,
|
|
248
251
|
name: typeof value.name === "string" ? value.name : undefined,
|
|
249
252
|
baseUrl: typeof value.baseUrl === "string" ? value.baseUrl : undefined,
|
|
253
|
+
modelsUrl: typeof value.modelsUrl === "string" ? value.modelsUrl : undefined,
|
|
250
254
|
apiKey: typeof value.apiKey === "string" ? value.apiKey : undefined,
|
|
251
255
|
api: typeof value.api === "string" ? value.api : undefined,
|
|
252
256
|
headers:
|
|
@@ -316,7 +320,12 @@ export function buildModel(
|
|
|
316
320
|
};
|
|
317
321
|
}
|
|
318
322
|
|
|
319
|
-
/**
|
|
323
|
+
/** 模型列表的完整 URL;未配置时沿用 {baseUrl}/models。 */
|
|
324
|
+
function getModelsUrl(entry: DiscoveryProviderEntry): string {
|
|
325
|
+
return entry.modelsUrl || `${(entry.baseUrl ?? "").replace(/\/+$/, "")}/models`;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/** 请求模型列表并解析为模型配置;失败抛错(错误信息带原因) */
|
|
320
329
|
async function fetchModels(
|
|
321
330
|
entry: DiscoveryProviderEntry,
|
|
322
331
|
notices: Notice[],
|
|
@@ -345,7 +354,7 @@ async function fetchModels(
|
|
|
345
354
|
headers.Authorization = `Bearer ${resolved.value}`;
|
|
346
355
|
}
|
|
347
356
|
}
|
|
348
|
-
const url =
|
|
357
|
+
const url = getModelsUrl(entry);
|
|
349
358
|
let response: Response;
|
|
350
359
|
try {
|
|
351
360
|
response = await fetch(url, { headers, signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });
|
|
@@ -374,11 +383,11 @@ async function fetchModels(
|
|
|
374
383
|
return models;
|
|
375
384
|
}
|
|
376
385
|
|
|
377
|
-
/**
|
|
386
|
+
/** 缓存的是含 compat 的模型配置,只有 URL+apiKey+headers+compat 相同才可复用。 */
|
|
378
387
|
type FetchCache = Map<string, Promise<ProviderModelConfig[]>>;
|
|
379
388
|
|
|
380
389
|
function fetchWithCache(cache: FetchCache, entry: DiscoveryProviderEntry, notices: Notice[]): Promise<ProviderModelConfig[]> {
|
|
381
|
-
const cacheKey =
|
|
390
|
+
const cacheKey = JSON.stringify([getModelsUrl(entry), entry.apiKey ?? "", entry.headers ?? {}, entry.compat ?? {}]);
|
|
382
391
|
let pending = cache.get(cacheKey);
|
|
383
392
|
if (!pending) {
|
|
384
393
|
pending = fetchModels(entry, notices);
|
|
@@ -542,7 +551,7 @@ function registerRefreshCommand(pi: ExtensionAPI, sink: NoticeSink) {
|
|
|
542
551
|
sink({ level: "info", message: i18n.t("refreshEmpty") }, ctx);
|
|
543
552
|
return;
|
|
544
553
|
}
|
|
545
|
-
//
|
|
554
|
+
// 每次刷新用新的请求级缓存:同模型列表 URL 的 provider 仍共享请求,但不复用启动期结果
|
|
546
555
|
const fetchCache: FetchCache = new Map();
|
|
547
556
|
let ok = 0;
|
|
548
557
|
for (const entry of providers) {
|