pi-provider-sicau-pmt 0.1.0 → 0.2.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 +19 -11
- package/package.json +2 -2
- package/src/index.ts +46 -72
- package/src/models.ts +224 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# pi-provider-sicau-pmt
|
|
2
2
|
|
|
3
|
-
[Pi](https://pi.dev) provider for a self-hosted
|
|
3
|
+
[Pi](https://pi.dev) provider for a self-hosted `oh-pmt-api` gateway (OpenAI-compatible). Adds the gateway's models to Pi with correct context windows, output limits and thinking-effort tables, and asks for the API key through `/login` instead of hand-editing config files.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -24,24 +24,32 @@ pi -e npm:pi-provider-sicau-pmt
|
|
|
24
24
|
|
|
25
25
|
## What it registers
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|---|---|---|
|
|
29
|
-
| DeepSeek-Flash | 1M | low / high / max, off |
|
|
30
|
-
| GLM-5.3-Flash | 1M | high / max |
|
|
31
|
-
| Qwen-3.8-Flash | 180K | low / medium / xhigh, off |
|
|
27
|
+
The model list comes from the gateway's `/v1/models` at startup. Models added to the gateway show up in Pi without editing this package; the built-in table below is only a fallback for offline startup and the first run after install.
|
|
32
28
|
|
|
33
|
-
|
|
29
|
+
| Model | Context | Output | Thinking effort | Image input |
|
|
30
|
+
|---|---|---|---|---|
|
|
31
|
+
| Qwen-3.8-Flash | 180K | 131072 | low / medium / xhigh, off | no |
|
|
32
|
+
| DeepSeek-Flash | 1M | 131072 | low / high / max, off | no |
|
|
33
|
+
| GLM-5.3-Flash | 1M | 131072 | low / high / max | yes |
|
|
34
|
+
| MiniMax-M3.1-Flash | 1M | 128000 | low / medium / high / xhigh / max | yes |
|
|
35
|
+
| DeepSeek-V4.1-Flash | 1M | 128000 | low / high / max, off | yes |
|
|
36
|
+
| Space-Bunny | 1M | 128000 | low / medium / high / xhigh / max | yes |
|
|
37
|
+
| Step-5-Preview | 1M | 64000 | low / medium / high | yes |
|
|
34
38
|
|
|
35
|
-
|
|
39
|
+
The gateway's `/v1/models` publishes no capability metadata, so capabilities live in `src/models.ts`, keyed by upstream key. Context windows and output limits come from Qoder's official catalog and WorkBuddy's product config; effort tables come from the vendors' declared effort lists.
|
|
40
|
+
|
|
41
|
+
Duplicate display ids are collapsed: the gateway lists `GLM-5.3-Flash` and `MiniMax-M3.1-Flash` twice, once per upstream, and the id-form request always resolves to the same backend, so one entry is enough.
|
|
42
|
+
|
|
43
|
+
`compat.supportsDeveloperRole` is disabled on every model. Pi switches the system prompt to the `developer` role on any model with `reasoning: true`, and the Qoder upstream validates message roles against a serde enum that accepts only `system`, `assistant`, `user` and `tool` — a `developer` message rejects the whole request.
|
|
36
44
|
|
|
37
45
|
## Point it at your own gateway
|
|
38
46
|
|
|
39
|
-
`BASE_URL` in `src/index.ts` is pinned to `https://api.hhy.homes/v1`. To use a different
|
|
47
|
+
`BASE_URL` in `src/index.ts` is pinned to `https://api.hhy.homes/v1`. To use a different deployment, fork this package, change `BASE_URL`, and rewrite the capability table in `src/models.ts` for your models.
|
|
40
48
|
|
|
41
49
|
## Requirements
|
|
42
50
|
|
|
43
|
-
- Pi 1.4.0 or newer.
|
|
44
|
-
- A
|
|
51
|
+
- Pi 1.4.0 or newer — the model list is refreshed through `ProviderConfig.refreshModels`.
|
|
52
|
+
- A gateway that forwards `reasoning_effort` and reads `max_completion_tokens`. Gateways without that plumbing still work, but the thinking selector has no effect and the output limit falls back to the server default.
|
|
45
53
|
|
|
46
54
|
## License
|
|
47
55
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-provider-sicau-pmt",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Pi provider for a self-hosted
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Pi provider for a self-hosted oh-pmt-api gateway (OpenAI-compatible), with dynamic model discovery, thinking-effort support and a /login API-key prompt",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"publishConfig": {
|
package/src/index.ts
CHANGED
|
@@ -11,89 +11,63 @@
|
|
|
11
11
|
* 没给 apiKey 时自动补一个默认的 API key 登录,/login 里弹 secret 输入框,
|
|
12
12
|
* 存成 {"type":"api_key","key":...},正是我们要的。
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
14
|
+
* 模型列表走 refreshModels 动态拉取:中转站的 /v1/models 决定有哪几个模型,
|
|
15
|
+
* 能力(上下文窗口、输出上限、思考档位)由 models.ts 里的表按 upstream_key 补。
|
|
16
16
|
*/
|
|
17
17
|
import type { ExtensionAPI, ProviderModelConfig } from "@earendil-works/pi-coding-agent";
|
|
18
|
+
import { SEED_MODELS, modelsFromCatalog, type GatewayModel } from "./models.ts";
|
|
18
19
|
|
|
19
20
|
const PROVIDER_ID = "sicau-pmt";
|
|
20
21
|
const BASE_URL = "https://api.hhy.homes/v1";
|
|
21
22
|
|
|
22
|
-
/**
|
|
23
|
-
*
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
{
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
48
|
-
contextWindow: 180000,
|
|
49
|
-
maxTokens: 8192,
|
|
50
|
-
compat: { supportsDeveloperRole: false },
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
id: "GLM-5.3-Flash",
|
|
54
|
-
name: "GLM 5.3 Flash",
|
|
55
|
-
reasoning: true,
|
|
56
|
-
thinkingLevelMap: {
|
|
57
|
-
off: null,
|
|
58
|
-
minimal: null,
|
|
59
|
-
low: null,
|
|
60
|
-
medium: null,
|
|
61
|
-
high: "high",
|
|
62
|
-
xhigh: null,
|
|
63
|
-
max: "max",
|
|
64
|
-
},
|
|
65
|
-
input: ["text"],
|
|
66
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
67
|
-
contextWindow: 1000000,
|
|
68
|
-
maxTokens: 8192,
|
|
69
|
-
compat: { supportsDeveloperRole: false },
|
|
70
|
-
},
|
|
71
|
-
{
|
|
72
|
-
id: "DeepSeek-Flash",
|
|
73
|
-
name: "DeepSeek Flash",
|
|
74
|
-
reasoning: true,
|
|
75
|
-
thinkingLevelMap: {
|
|
76
|
-
off: "disabled",
|
|
77
|
-
minimal: null,
|
|
78
|
-
low: "low",
|
|
79
|
-
medium: null,
|
|
80
|
-
high: "high",
|
|
81
|
-
xhigh: null,
|
|
82
|
-
max: "max",
|
|
83
|
-
},
|
|
84
|
-
input: ["text"],
|
|
85
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
86
|
-
contextWindow: 1000000,
|
|
87
|
-
maxTokens: 8192,
|
|
88
|
-
compat: { supportsDeveloperRole: false },
|
|
89
|
-
},
|
|
90
|
-
];
|
|
23
|
+
/** 目录缓存时间。同一个会话里 pi 会多次触发刷新,没必要每次都打网关。 */
|
|
24
|
+
const CATALOG_TTL_MS = 5 * 60 * 1000;
|
|
25
|
+
/** 网关的 /v1/models 只是个静态列表,不需要等太久。 */
|
|
26
|
+
const CATALOG_TIMEOUT_MS = 8 * 1000;
|
|
27
|
+
|
|
28
|
+
let cached: ProviderModelConfig[] = SEED_MODELS;
|
|
29
|
+
let cachedAt = 0;
|
|
30
|
+
|
|
31
|
+
/** 从网关拉一次目录。失败返回 null,由调用方回落到缓存或内置表。 */
|
|
32
|
+
async function fetchCatalog(apiKey: string, signal: AbortSignal): Promise<ProviderModelConfig[] | null> {
|
|
33
|
+
const timeout = AbortSignal.timeout(CATALOG_TIMEOUT_MS);
|
|
34
|
+
try {
|
|
35
|
+
const response = await fetch(`${BASE_URL}/models`, {
|
|
36
|
+
headers: apiKey ? { authorization: `Bearer ${apiKey}` } : {},
|
|
37
|
+
signal: AbortSignal.any([signal, timeout]),
|
|
38
|
+
});
|
|
39
|
+
if (!response.ok) return null;
|
|
40
|
+
const payload = (await response.json()) as { data?: GatewayModel[] };
|
|
41
|
+
const entries = Array.isArray(payload?.data) ? payload.data : [];
|
|
42
|
+
const models = modelsFromCatalog(entries);
|
|
43
|
+
return models.length > 0 ? models : null;
|
|
44
|
+
} catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
91
48
|
|
|
92
49
|
export default function sicauPmt(pi: ExtensionAPI) {
|
|
93
50
|
pi.registerProvider(PROVIDER_ID, {
|
|
94
51
|
name: "SICAU-PMT",
|
|
95
52
|
baseUrl: BASE_URL,
|
|
96
53
|
api: "openai-completions",
|
|
97
|
-
models:
|
|
54
|
+
models: SEED_MODELS,
|
|
55
|
+
/**
|
|
56
|
+
* pi 在启动时先用 allowNetwork=false 调一次,从缓存恢复;随后在交互模式下用
|
|
57
|
+
* allowNetwork=true 再调一次,这时才真的访问网关。返回值会替换掉上面注册的
|
|
58
|
+
* 模型列表,所以任何路径都必须返回非空列表。
|
|
59
|
+
*/
|
|
60
|
+
async refreshModels(context) {
|
|
61
|
+
if (!context.allowNetwork) return cached;
|
|
62
|
+
if (!context.force && Date.now() - cachedAt < CATALOG_TTL_MS) return cached;
|
|
63
|
+
|
|
64
|
+
const key = context.credential?.type === "api_key" ? (context.credential.key ?? "") : "";
|
|
65
|
+
const models = await fetchCatalog(key, context.signal);
|
|
66
|
+
if (!models) return cached;
|
|
67
|
+
|
|
68
|
+
cached = models;
|
|
69
|
+
cachedAt = Date.now();
|
|
70
|
+
return models;
|
|
71
|
+
},
|
|
98
72
|
});
|
|
99
73
|
}
|
package/src/models.ts
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 模型能力表 + 目录合并规则。
|
|
3
|
+
*
|
|
4
|
+
* 中转站的 /v1/models 只给出 id、upstream_key 和 owned_by,没有上下文窗口、输出
|
|
5
|
+
* 上限、思考档位这些能力数据。所以能力写在这张表里,按 upstream_key 索引——
|
|
6
|
+
* upstream_key 在上游唯一,而展示用的 id 会撞车(GLM-5.3-Flash 和
|
|
7
|
+
* MiniMax-M3.1-Flash 在 Qoder 与 WorkBuddy 各有一条,id 相同)。
|
|
8
|
+
*
|
|
9
|
+
* 合并规则:/v1/models 决定有哪几个模型,这张表只补能力。表里没有的模型用保守
|
|
10
|
+
* 默认值,仍然可用,网关将来新增模型也不会从列表里消失。
|
|
11
|
+
*/
|
|
12
|
+
import type { ProviderModelConfig } from "@earendil-works/pi-coding-agent";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 档位表类型。ProviderChatModelConfig 没有从包入口导出,所以从联合类型里按
|
|
16
|
+
* 「有 reasoning 字段」把 chat 分支取出来。
|
|
17
|
+
*/
|
|
18
|
+
type LevelMap = NonNullable<Extract<ProviderModelConfig, { reasoning: boolean }>["thinkingLevelMap"]>;
|
|
19
|
+
|
|
20
|
+
/** 一条模型的能力描述,合并时按 upstream_key 查。 */
|
|
21
|
+
interface Capability {
|
|
22
|
+
name: string;
|
|
23
|
+
contextWindow: number;
|
|
24
|
+
maxTokens: number;
|
|
25
|
+
/** 是否按思考模型注册。false 时 pi 只给「关」,也不下发 reasoning_effort。 */
|
|
26
|
+
reasoning: boolean;
|
|
27
|
+
thinkingLevelMap?: LevelMap;
|
|
28
|
+
input: ("text" | "image")[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** 上游把 none / off / disabled / disable / false / 0 / no 都当关闭,这里统一用 disabled。 */
|
|
32
|
+
const OFF = "disabled";
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 由厂商声明的档位列表生成 thinkingLevelMap。
|
|
36
|
+
*
|
|
37
|
+
* pi 的规则:到 high 为止的标准档位,没写等于「用上游默认值」;xhigh 与 max 必须
|
|
38
|
+
* 显式写非 null 才可选。所以没声明的档位一律写 null,避免选了 max 却静默退回默认档。
|
|
39
|
+
* off 传 null 表示该模型关不掉思考,pi 就不显示关闭选项。
|
|
40
|
+
*/
|
|
41
|
+
function levelMap(efforts: string[], off: string | null): LevelMap {
|
|
42
|
+
const supported = new Set(efforts);
|
|
43
|
+
const map: LevelMap = { off };
|
|
44
|
+
for (const level of ["minimal", "low", "medium", "high", "xhigh", "max"] as const) {
|
|
45
|
+
map[level] = supported.has(level) ? level : null;
|
|
46
|
+
}
|
|
47
|
+
return map;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 该模型关不掉思考:off 写 null,pi 就不显示关闭选项,也不下发 reasoning_effort。
|
|
52
|
+
*
|
|
53
|
+
* 档位仍按厂商声明的写。网关这条链路眼下不会把 reasoning_effort 转给上游,选了也
|
|
54
|
+
* 只是回到默认档;但 pi 需要至少一个可选档位(全是 null 会被 clampThinkingLevel
|
|
55
|
+
* 退成 off,反而把思考模型当成关闭思考用)。等网关补上转发,这张表不用改。
|
|
56
|
+
*/
|
|
57
|
+
function alwaysThinking(efforts: string[]): LevelMap {
|
|
58
|
+
return levelMap(efforts, null);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* 能力表。上下文窗口与输出上限取自 Qoder 官方目录
|
|
63
|
+
* (~/.pi/agent/qoder*-models-cache.json)和 WorkBuddy 产品配置
|
|
64
|
+
* (~/.workbuddy/cache/acc-product-config-v3.json)。
|
|
65
|
+
*
|
|
66
|
+
* key 是网关实际路由到的上游 key,不是 owned_by。网关的 owned_by 是
|
|
67
|
+
* `qoder-<region>` 硬编码,看不出真实厂商:请求进来时 resolve_model 先按上游 key
|
|
68
|
+
* 精确匹配、再按去掉区域后缀的小写形式匹配,所以 id 为 GLM-5.3-Flash 的请求会命中
|
|
69
|
+
* WorkBuddy 的 glm-5.3-flash,而不是 Qoder 的 gfmodel(后者已因缺订阅返回 403)。
|
|
70
|
+
*/
|
|
71
|
+
const CAPABILITIES: Record<string, Capability> = {
|
|
72
|
+
// ---- 走 Qoder 适配器:网关会把 reasoning_effort 传下去,档位真实生效 ----
|
|
73
|
+
qfmodel: {
|
|
74
|
+
name: "Qwen 3.8 Flash",
|
|
75
|
+
// Qoder 目录里 max_input_tokens 是 180000,另有 200K/400K/1M 可选档,默认 200K
|
|
76
|
+
contextWindow: 180000,
|
|
77
|
+
maxTokens: 131072,
|
|
78
|
+
reasoning: true,
|
|
79
|
+
thinkingLevelMap: levelMap(["low", "medium", "xhigh"], OFF),
|
|
80
|
+
input: ["text"],
|
|
81
|
+
},
|
|
82
|
+
dfmodel: {
|
|
83
|
+
name: "DeepSeek Flash",
|
|
84
|
+
contextWindow: 1000000,
|
|
85
|
+
maxTokens: 131072,
|
|
86
|
+
reasoning: true,
|
|
87
|
+
thinkingLevelMap: levelMap(["low", "high", "max"], OFF),
|
|
88
|
+
input: ["text"],
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
// ---- 走 WorkBuddy 适配器 ----
|
|
92
|
+
// 该适配器只转发温度、top_p 这类采样字段,reasoning_effort 不在白名单里,所以
|
|
93
|
+
// 这几个模型眼下的档位调不动(实测任何档位都没有变化)。档位仍按厂商声明的写,
|
|
94
|
+
// 界面上跟厂商本身一致;等网关补上转发就自然生效。
|
|
95
|
+
"glm-5.3-flash": {
|
|
96
|
+
name: "GLM 5.3 Flash",
|
|
97
|
+
contextWindow: 1000000,
|
|
98
|
+
maxTokens: 131072,
|
|
99
|
+
reasoning: true,
|
|
100
|
+
thinkingLevelMap: alwaysThinking(["low", "high", "max"]),
|
|
101
|
+
input: ["text", "image"],
|
|
102
|
+
},
|
|
103
|
+
"minimax-m3.1-flash": {
|
|
104
|
+
name: "MiniMax M3.1 Flash",
|
|
105
|
+
contextWindow: 1000000,
|
|
106
|
+
maxTokens: 128000,
|
|
107
|
+
reasoning: true,
|
|
108
|
+
thinkingLevelMap: alwaysThinking(["low", "medium", "high", "xhigh", "max"]),
|
|
109
|
+
input: ["text", "image"],
|
|
110
|
+
},
|
|
111
|
+
"deepseek-v4.1-flash": {
|
|
112
|
+
name: "DeepSeek V4.1 Flash",
|
|
113
|
+
contextWindow: 1000000,
|
|
114
|
+
maxTokens: 128000,
|
|
115
|
+
reasoning: true,
|
|
116
|
+
thinkingLevelMap: alwaysThinking(["low", "high", "max"]),
|
|
117
|
+
input: ["text", "image"],
|
|
118
|
+
},
|
|
119
|
+
"space-bunny": {
|
|
120
|
+
name: "Space Bunny",
|
|
121
|
+
contextWindow: 1000000,
|
|
122
|
+
maxTokens: 128000,
|
|
123
|
+
reasoning: true,
|
|
124
|
+
thinkingLevelMap: alwaysThinking(["low", "medium", "high", "xhigh", "max"]),
|
|
125
|
+
input: ["text", "image"],
|
|
126
|
+
},
|
|
127
|
+
"step-5-preview": {
|
|
128
|
+
name: "Step 5 Preview",
|
|
129
|
+
contextWindow: 1000000,
|
|
130
|
+
maxTokens: 64000,
|
|
131
|
+
reasoning: true,
|
|
132
|
+
thinkingLevelMap: alwaysThinking(["low", "medium", "high"]),
|
|
133
|
+
input: ["text", "image"],
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* 表里查不到时用的默认值。中转站不校验 max_tokens,设大了不会报错,但真按 1M
|
|
139
|
+
* 输出截断会很难排查,所以给一个大多数模型都吃得下的值。
|
|
140
|
+
*/
|
|
141
|
+
const FALLBACK: Capability = {
|
|
142
|
+
name: "",
|
|
143
|
+
contextWindow: 200000,
|
|
144
|
+
maxTokens: 32000,
|
|
145
|
+
reasoning: false,
|
|
146
|
+
input: ["text"],
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* supportsDeveloperRole 保持关闭:pi 在 reasoning 模型上会把系统提示词的角色换成
|
|
151
|
+
* developer,而 Qoder 那条链路用 serde 枚举校验 role,只认 system / assistant /
|
|
152
|
+
* user / tool,多一个 developer 整条请求会被拒。网关现在会把 developer 转成 system,
|
|
153
|
+
* 但关掉这个开关没有代价,留着当保险。
|
|
154
|
+
*/
|
|
155
|
+
const COMPAT = { supportsDeveloperRole: false } as const;
|
|
156
|
+
|
|
157
|
+
/** /v1/models 里我们用到的那几个字段。 */
|
|
158
|
+
export interface GatewayModel {
|
|
159
|
+
id: string;
|
|
160
|
+
upstream_key?: string;
|
|
161
|
+
owned_by?: string;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* 查出这条目录项真实对应的能力。
|
|
166
|
+
*
|
|
167
|
+
* 先按 upstream_key 查,查不到再按小写 id 查:网关的 resolve_model 是「先按上游 key
|
|
168
|
+
* 精确匹配,再按去掉区域后缀的小写名字匹配」,所以 id 为 GLM-5.3-Flash 的请求实际
|
|
169
|
+
* 落到 WorkBuddy 的 glm-5.3-flash,而不是列表里排在前面的 Qoder 那条 gfmodel
|
|
170
|
+
* (它已经因为缺订阅返回 403)。按同样的顺序查表,才能跟路由结果对上。
|
|
171
|
+
*/
|
|
172
|
+
function capabilityFor(entry: GatewayModel): Capability {
|
|
173
|
+
const byKey = CAPABILITIES[entry.upstream_key || ""];
|
|
174
|
+
if (byKey) return byKey;
|
|
175
|
+
return CAPABILITIES[entry.id.toLowerCase()] ?? FALLBACK;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** 把网关返回的一条目录项补成 pi 的模型定义。 */
|
|
179
|
+
export function toProviderModel(entry: GatewayModel): ProviderModelConfig {
|
|
180
|
+
const cap = capabilityFor(entry);
|
|
181
|
+
return {
|
|
182
|
+
id: entry.id,
|
|
183
|
+
name: cap.name || entry.id,
|
|
184
|
+
reasoning: cap.reasoning,
|
|
185
|
+
...(cap.thinkingLevelMap ? { thinkingLevelMap: cap.thinkingLevelMap } : {}),
|
|
186
|
+
input: cap.input,
|
|
187
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
188
|
+
contextWindow: cap.contextWindow,
|
|
189
|
+
maxTokens: cap.maxTokens,
|
|
190
|
+
compat: COMPAT,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* 把网关目录去重成 pi 模型列表。
|
|
196
|
+
*
|
|
197
|
+
* 同一展示 id 可能出现两次(GLM-5.3-Flash、MiniMax-M3.1-Flash 在 Qoder 与 WorkBuddy
|
|
198
|
+
* 各有一条)。id 形式的请求总是走 resolve_model 的同一条匹配路径、落到同一个上游,
|
|
199
|
+
* 所以按 id 保留第一条即可。
|
|
200
|
+
*/
|
|
201
|
+
export function modelsFromCatalog(entries: GatewayModel[]): ProviderModelConfig[] {
|
|
202
|
+
const seen = new Set<string>();
|
|
203
|
+
const models: ProviderModelConfig[] = [];
|
|
204
|
+
for (const entry of entries) {
|
|
205
|
+
if (!entry.id || seen.has(entry.id)) continue;
|
|
206
|
+
seen.add(entry.id);
|
|
207
|
+
models.push(toProviderModel(entry));
|
|
208
|
+
}
|
|
209
|
+
return models;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* 内置目录,服务启动、离线、或网关暂时不可用时先用它,保证模型始终可选。
|
|
214
|
+
* 与 2026-10-25 的 /v1/models 一致(重复 id 已按网关路由结果归并)。
|
|
215
|
+
*/
|
|
216
|
+
export const SEED_MODELS: ProviderModelConfig[] = modelsFromCatalog([
|
|
217
|
+
{ id: "Qwen-3.8-Flash", upstream_key: "qfmodel" },
|
|
218
|
+
{ id: "GLM-5.3-Flash", upstream_key: "glm-5.3-flash" },
|
|
219
|
+
{ id: "DeepSeek-Flash", upstream_key: "dfmodel" },
|
|
220
|
+
{ id: "MiniMax-M3.1-Flash", upstream_key: "minimax-m3.1-flash" },
|
|
221
|
+
{ id: "DeepSeek-V4.1-Flash", upstream_key: "deepseek-v4.1-flash" },
|
|
222
|
+
{ id: "Space-Bunny", upstream_key: "space-bunny" },
|
|
223
|
+
{ id: "Step-5-Preview", upstream_key: "step-5-preview" },
|
|
224
|
+
]);
|