pi-models-discovery 1.3.1 → 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 +35 -4
- package/README.zh-CN.md +35 -4
- package/SKILL.md +5 -1
- package/package.json +2 -2
- package/src/index.ts +27 -12
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,12 +72,25 @@ 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).
|
|
61
79
|
- Default parameters for discovered models: `reasoning: true`, `input: ["text", "image"]`, zero cost, `contextWindow` 1M, `maxTokens` 64K, `compat.supportsDeveloperRole: false`. Provider-level `compat` is merged into every discovered model.
|
|
62
|
-
- Discovered models
|
|
80
|
+
- Discovered models declare `thinkingLevelMap: { xhigh: "xhigh", max: "max" }`, so `xhigh` and `max` appear in `/thinking`; the standard levels stay omitted and keep pi's provider default mapping. `/models` cannot report per-model thinking capability, so this is a blanket default for every discovered model: when a provider rejects those levels, override it per model through `modelOverrides` in models.json (set `xhigh` / `max` to `null` to hide them), or declare the model yourself in a handwritten `models` entry.
|
|
81
|
+
|
|
82
|
+
```json
|
|
83
|
+
{
|
|
84
|
+
"providers": {
|
|
85
|
+
"llm-proxy": {
|
|
86
|
+
"discoverModels": true,
|
|
87
|
+
"modelOverrides": {
|
|
88
|
+
"some-model": { "thinkingLevelMap": { "xhigh": null, "max": null } }
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
63
94
|
- Model metadata may carry `name` / `context_window` (or `contextWindow`) / `max_tokens` (or `maxTokens`); defaults are used when absent.
|
|
64
95
|
|
|
65
96
|
The old `/model-discovery`, `/model-discovery-refresh`, `/pi-model-discovery`, and `/pi-model-discovery-refresh` names remain available as compatibility aliases.
|
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,12 +72,25 @@ 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 自身解析执行,不受影响)。
|
|
61
79
|
- 发现的模型默认参数:`reasoning: true`、`input: ["text", "image"]`、cost 全 0、`contextWindow` 1M、`maxTokens` 64K、`compat.supportsDeveloperRole: false`;provider 级 `compat` 会合并进每个发现的模型。
|
|
62
|
-
-
|
|
80
|
+
- 发现的模型声明 `thinkingLevelMap: { xhigh: "xhigh", max: "max" }`,让 `xhigh` / `max` 出现在 `/thinking`;标准档位保持缺省,仍由 pi 的 provider 默认映射决定。`/models` 无法逐个模型返回思考能力,所以这是对所有发现模型一刀切的默认值:某个 provider 拒绝这些档位时,在 models.json 的 `modelOverrides` 里按 model.id 覆盖(把 `xhigh` / `max` 置 `null` 可隐藏),或改用手写 `models` 条目自行声明。
|
|
81
|
+
|
|
82
|
+
```json
|
|
83
|
+
{
|
|
84
|
+
"providers": {
|
|
85
|
+
"llm-proxy": {
|
|
86
|
+
"discoverModels": true,
|
|
87
|
+
"modelOverrides": {
|
|
88
|
+
"some-model": { "thinkingLevelMap": { "xhigh": null, "max": null } }
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
63
94
|
- 模型元数据可携带 `name` / `context_window`(或 `contextWindow`)/ `max_tokens`(或 `maxTokens`),缺失时用默认值。
|
|
64
95
|
|
|
65
96
|
旧的 `/model-discovery`、`/model-discovery-refresh`、`/pi-model-discovery` 和 `/pi-model-discovery-refresh` 名称仍作为兼容别名保留。
|
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
|
|
|
@@ -21,6 +21,10 @@ description: "配置与排查 models.json 的动态模型发现、缓存刷新
|
|
|
21
21
|
|
|
22
22
|
`apiKey` 可用字面量、`$ENV_VAR` 或 `${ENV_VAR}`;`!command` 不用于发现请求,会被跳过并警告。不要把密钥写入 Skill、仓库或回复。
|
|
23
23
|
|
|
24
|
+
## 思考等级
|
|
25
|
+
|
|
26
|
+
发现的模型默认声明 `thinkingLevelMap: { xhigh: "xhigh", max: "max" }`(对所有模型一刀切)。若某个模型不认 `xhigh` / `max`,用 models.json 的 `modelOverrides` 按 model.id 把它们置 `null`。
|
|
27
|
+
|
|
24
28
|
## 验证
|
|
25
29
|
|
|
26
30
|
- `/config:model-discovery-refresh` 强制联网刷新并更新缓存;旧命令名仅为兼容别名。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-models-discovery",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Pi extension: discover models from {baseUrl}/models for providers marked with discoverModels in models.json",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.ts",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"@earendil-works/pi-coding-agent": ">=0.80.0"
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"pi-extensions-i18n": "^0.
|
|
60
|
+
"pi-extensions-i18n": "^0.8.0"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"@earendil-works/pi-ai": "0.85.1",
|
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,19 +22,22 @@
|
|
|
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 触发在线刷新时重新发现;
|
|
36
36
|
* 离线初始化(allowNetwork=false)返回上次成功列表(含缓存),尚无成功记录时抛错,
|
|
37
37
|
* 以免空列表清掉 models.json 手写 models。
|
|
38
|
+
* - 发现模型默认声明 thinkingLevelMap { xhigh: "xhigh", max: "max" },让 xhigh/max 出现在
|
|
39
|
+
* /thinking;标准档位缺省沿用 pi 的 provider 默认映射。这是对所有发现模型一刀切的默认值,
|
|
40
|
+
* 上游不认这些值时用 models.json 的 provider.modelOverrides 按 model.id 覆盖。
|
|
38
41
|
* - 发现请求的 apiKey 解析仅支持字面量与 $ENV_VAR/${ENV_VAR} 插值;
|
|
39
42
|
* "!command" 形式的 apiKey 跳过发现(显式警告),pi 发起聊天请求时仍由 pi 自身解析。
|
|
40
43
|
* - /config:model-discovery 命令对 models.json 的修改立即生效(registerProvider 运行时可直接调用);
|
|
@@ -48,7 +51,7 @@ import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
|
48
51
|
import type { ExtensionAPI, ProviderModelConfig } from "@earendil-works/pi-coding-agent";
|
|
49
52
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
50
53
|
import { dirname, join } from "node:path";
|
|
51
|
-
import { createTranslator, installNoticeRenderer, loadCatalog, notifyWithSource, type NoticeColor, type NoticeSource } from "pi-extensions-i18n";
|
|
54
|
+
import { NOTICE_TAG_COLOR, createTranslator, installNoticeRenderer, loadCatalog, notifyWithSource, type NoticeColor, type NoticeSource } from "pi-extensions-i18n";
|
|
52
55
|
|
|
53
56
|
const i18n = createTranslator(loadCatalog(new URL("../locales/index.json", import.meta.url)));
|
|
54
57
|
|
|
@@ -57,8 +60,8 @@ const DISCOVERY_MARKER = "discoverModels";
|
|
|
57
60
|
const LOG_PREFIX = "[model-discovery]";
|
|
58
61
|
/** 本扩展的提示标签;短且唯一,便于在会话里定位来源。 */
|
|
59
62
|
const NOTICE_TAG = "models";
|
|
60
|
-
/**
|
|
61
|
-
const NOTICE_COLOR: NoticeColor =
|
|
63
|
+
/** 提示标签颜色:所有扩展统一用弱化色,来源靠 tag 文本区分,不靠颜色。 */
|
|
64
|
+
const NOTICE_COLOR: NoticeColor = NOTICE_TAG_COLOR;
|
|
62
65
|
/** 本扩展的提示来源。 */
|
|
63
66
|
const NOTICE_SOURCE: NoticeSource = { tag: NOTICE_TAG, color: NOTICE_COLOR };
|
|
64
67
|
const API_CHOICES = ["openai-completions", "anthropic-messages", "openai-responses", "google-generative-ai"] as const;
|
|
@@ -77,6 +80,7 @@ interface DiscoveryProviderEntry {
|
|
|
77
80
|
id: string;
|
|
78
81
|
name?: string;
|
|
79
82
|
baseUrl?: string;
|
|
83
|
+
modelsUrl?: string;
|
|
80
84
|
apiKey?: string;
|
|
81
85
|
api?: string;
|
|
82
86
|
headers?: Record<string, string>;
|
|
@@ -129,6 +133,8 @@ function providerFingerprint(entry: DiscoveryProviderEntry): string {
|
|
|
129
133
|
entry.apiKey ?? "",
|
|
130
134
|
entry.headers ?? {},
|
|
131
135
|
entry.compat ?? {},
|
|
136
|
+
// 未配置 modelsUrl 时保留已有指纹,避免让旧缓存失效。
|
|
137
|
+
...(entry.modelsUrl ? [entry.modelsUrl] : []),
|
|
132
138
|
]);
|
|
133
139
|
}
|
|
134
140
|
|
|
@@ -244,6 +250,7 @@ function pickDiscoveryProviders(data: Record<string, unknown>): DiscoveryProvide
|
|
|
244
250
|
id,
|
|
245
251
|
name: typeof value.name === "string" ? value.name : undefined,
|
|
246
252
|
baseUrl: typeof value.baseUrl === "string" ? value.baseUrl : undefined,
|
|
253
|
+
modelsUrl: typeof value.modelsUrl === "string" ? value.modelsUrl : undefined,
|
|
247
254
|
apiKey: typeof value.apiKey === "string" ? value.apiKey : undefined,
|
|
248
255
|
api: typeof value.api === "string" ? value.api : undefined,
|
|
249
256
|
headers:
|
|
@@ -288,7 +295,7 @@ function resolveEnvValue(raw: string): { value: string | null; error: string | n
|
|
|
288
295
|
return { value, error: null };
|
|
289
296
|
}
|
|
290
297
|
|
|
291
|
-
function buildModel(
|
|
298
|
+
export function buildModel(
|
|
292
299
|
id: string,
|
|
293
300
|
name: string | undefined,
|
|
294
301
|
contextWindow: number | undefined,
|
|
@@ -299,6 +306,9 @@ function buildModel(
|
|
|
299
306
|
id,
|
|
300
307
|
name: name ?? id,
|
|
301
308
|
reasoning: true,
|
|
309
|
+
// xhigh/max 必须给非 null 值才会出现在 /thinking(缺省键等同不支持);
|
|
310
|
+
// 标准档位保持缺省,仍走 pi 的 provider 默认映射,语义不变。
|
|
311
|
+
thinkingLevelMap: { xhigh: "xhigh", max: "max" },
|
|
302
312
|
input: ["text", "image"],
|
|
303
313
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
304
314
|
contextWindow: contextWindow ?? 1_000_000,
|
|
@@ -310,7 +320,12 @@ function buildModel(
|
|
|
310
320
|
};
|
|
311
321
|
}
|
|
312
322
|
|
|
313
|
-
/**
|
|
323
|
+
/** 模型列表的完整 URL;未配置时沿用 {baseUrl}/models。 */
|
|
324
|
+
function getModelsUrl(entry: DiscoveryProviderEntry): string {
|
|
325
|
+
return entry.modelsUrl || `${(entry.baseUrl ?? "").replace(/\/+$/, "")}/models`;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/** 请求模型列表并解析为模型配置;失败抛错(错误信息带原因) */
|
|
314
329
|
async function fetchModels(
|
|
315
330
|
entry: DiscoveryProviderEntry,
|
|
316
331
|
notices: Notice[],
|
|
@@ -339,7 +354,7 @@ async function fetchModels(
|
|
|
339
354
|
headers.Authorization = `Bearer ${resolved.value}`;
|
|
340
355
|
}
|
|
341
356
|
}
|
|
342
|
-
const url =
|
|
357
|
+
const url = getModelsUrl(entry);
|
|
343
358
|
let response: Response;
|
|
344
359
|
try {
|
|
345
360
|
response = await fetch(url, { headers, signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });
|
|
@@ -368,11 +383,11 @@ async function fetchModels(
|
|
|
368
383
|
return models;
|
|
369
384
|
}
|
|
370
385
|
|
|
371
|
-
/**
|
|
386
|
+
/** 缓存的是含 compat 的模型配置,只有 URL+apiKey+headers+compat 相同才可复用。 */
|
|
372
387
|
type FetchCache = Map<string, Promise<ProviderModelConfig[]>>;
|
|
373
388
|
|
|
374
389
|
function fetchWithCache(cache: FetchCache, entry: DiscoveryProviderEntry, notices: Notice[]): Promise<ProviderModelConfig[]> {
|
|
375
|
-
const cacheKey =
|
|
390
|
+
const cacheKey = JSON.stringify([getModelsUrl(entry), entry.apiKey ?? "", entry.headers ?? {}, entry.compat ?? {}]);
|
|
376
391
|
let pending = cache.get(cacheKey);
|
|
377
392
|
if (!pending) {
|
|
378
393
|
pending = fetchModels(entry, notices);
|
|
@@ -536,7 +551,7 @@ function registerRefreshCommand(pi: ExtensionAPI, sink: NoticeSink) {
|
|
|
536
551
|
sink({ level: "info", message: i18n.t("refreshEmpty") }, ctx);
|
|
537
552
|
return;
|
|
538
553
|
}
|
|
539
|
-
//
|
|
554
|
+
// 每次刷新用新的请求级缓存:同模型列表 URL 的 provider 仍共享请求,但不复用启动期结果
|
|
540
555
|
const fetchCache: FetchCache = new Map();
|
|
541
556
|
let ok = 0;
|
|
542
557
|
for (const entry of providers) {
|