@serviceme/devtools-shared 2.0.0 → 2.0.2
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/dist/index.d.mts +1025 -998
- package/dist/index.d.ts +1025 -998
- package/dist/index.js +2308 -2347
- package/dist/index.mjs +2252 -2266
- package/package.json +5 -5
package/dist/index.mjs
CHANGED
|
@@ -1,2370 +1,2356 @@
|
|
|
1
|
-
|
|
1
|
+
import { createHash, createHmac } from "crypto";
|
|
2
|
+
//#region src/ai/protocol.ts
|
|
2
3
|
function protocolForBaseUrl(baseUrl) {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
try {
|
|
5
|
+
const url = new URL(baseUrl);
|
|
6
|
+
return /(^|\/)anthropic(\/|$)/i.test(url.pathname) ? "anthropic" : "openai";
|
|
7
|
+
} catch {
|
|
8
|
+
return "openai";
|
|
9
|
+
}
|
|
9
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* Resolve the actual `ProviderType` whose adapter should handle a
|
|
13
|
+
* request with the given `configuredType` + `baseUrl`.
|
|
14
|
+
*
|
|
15
|
+
* Most of the time this is just `configuredType`. The exception is
|
|
16
|
+
* the named `zhipu` provider with an Anthropic-protocol baseUrl
|
|
17
|
+
* (`/api/anthropic`); we route it through the `anthropic-compatible`
|
|
18
|
+
* adapter so the URL construction appends `/v1/messages` instead of
|
|
19
|
+
* the OpenAI `/v1/chat/completions`. Without this, the OpenAIAdapter
|
|
20
|
+
* would build `.../api/anthropic/chat/completions` and GLM would
|
|
21
|
+
* 404 (the Anthropic-compatible endpoint only serves
|
|
22
|
+
* `.../api/anthropic/v1/messages`).
|
|
23
|
+
*
|
|
24
|
+
* `minimax` is intentionally NOT in the override list because the
|
|
25
|
+
* curated preset is `https://api.minimaxi.com/anthropic` (already
|
|
26
|
+
* Anthropic-protocol), and the `minimax` case in the adapter
|
|
27
|
+
* factory already wires AnthropicAdapter unconditionally.
|
|
28
|
+
*
|
|
29
|
+
* Everything else (deepseek / kimi / stepfun / agnes / openrouter /
|
|
30
|
+
* novita / openai-compatible / anthropic-compatible) returns
|
|
31
|
+
* `configuredType` unchanged — these vendors don't publish an
|
|
32
|
+
* alternate-protocol endpoint on the same host.
|
|
33
|
+
*/
|
|
10
34
|
function effectiveAdapterType(configuredType, baseUrl) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
return configuredType;
|
|
35
|
+
if (configuredType === "zhipu" && protocolForBaseUrl(baseUrl) === "anthropic") return "anthropic-compatible";
|
|
36
|
+
return configuredType;
|
|
15
37
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region src/constants.ts
|
|
40
|
+
/**
|
|
41
|
+
* Medalsoft-internal infrastructure addresses.
|
|
42
|
+
*
|
|
43
|
+
* These are **private-network only** endpoints — reachable from the
|
|
44
|
+
* company LAN/VPN, NOT from the public internet. External users must
|
|
45
|
+
* use the public equivalents (`nexus.servicemecloud.com`, …). Keeping
|
|
46
|
+
* them as named constants here (instead of inlined literals) gives a
|
|
47
|
+
* single place to update when the internal fleet moves, and makes the
|
|
48
|
+
* "this is an internal address" intent explicit at every use site.
|
|
49
|
+
*
|
|
50
|
+
* NOTE: `packages/serviceme-core` keeps its own copies of the ones it
|
|
51
|
+
* needs — ADL-003 forbids core → shared. Keep those in lock-step.
|
|
52
|
+
*/
|
|
53
|
+
/** Medalsoft 内网 LLM 网关(OpenAI-compatible `/v1`),仅办公网可达。 */
|
|
54
|
+
const MEDALSOFT_PRIVATE_GATEWAY_URL = "http://10.10.10.249:3000/v1";
|
|
55
|
+
/** Medalsoft 私有 NuGet 源,仅办公网可达。 */
|
|
56
|
+
const MEDALSOFT_NUGET_PRIVATE_SOURCE = "http://192.168.20.209:10010/nuget";
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region src/ai/providers.base-url.ts
|
|
59
|
+
/**
|
|
60
|
+
* Provider-type → known baseUrl candidates (e.g. mainland-China vs.
|
|
61
|
+
* global endpoints for the same vendor, like Agnes/MiniMax/DeepSeek).
|
|
62
|
+
* Purely a UI convenience for the Add/Edit form's baseUrl dropdown —
|
|
63
|
+
* no runtime auto-switching reads this (that mechanism was removed;
|
|
64
|
+
* see git history for the retired `autoSwitch` feature).
|
|
65
|
+
*/
|
|
66
|
+
const PROVIDER_BASE_URL_PRESETS = {
|
|
67
|
+
"openai-compatible": [],
|
|
68
|
+
"anthropic-compatible": [],
|
|
69
|
+
minimax: [{
|
|
70
|
+
label: "国内",
|
|
71
|
+
baseUrl: "https://api.minimaxi.com/anthropic"
|
|
72
|
+
}, {
|
|
73
|
+
label: "全球",
|
|
74
|
+
baseUrl: "https://api.minimax.io/anthropic"
|
|
75
|
+
}],
|
|
76
|
+
deepseek: [{
|
|
77
|
+
label: "官方",
|
|
78
|
+
baseUrl: "https://api.deepseek.com/v1"
|
|
79
|
+
}],
|
|
80
|
+
kimi: [{
|
|
81
|
+
label: "国内",
|
|
82
|
+
baseUrl: "https://api.moonshot.cn/v1"
|
|
83
|
+
}, {
|
|
84
|
+
label: "全球",
|
|
85
|
+
baseUrl: "https://api.moonshot.ai/v1"
|
|
86
|
+
}],
|
|
87
|
+
zhipu: [
|
|
88
|
+
{
|
|
89
|
+
label: "国内 · 标准 API",
|
|
90
|
+
baseUrl: "https://open.bigmodel.cn/api/paas/v4"
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
label: "国内 · Coding Plan",
|
|
94
|
+
baseUrl: "https://open.bigmodel.cn/api/coding/paas/v4"
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
label: "国内 · Coding Plan · Anthropic 协议",
|
|
98
|
+
baseUrl: "https://open.bigmodel.cn/api/anthropic"
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
label: "国际 · 标准 API",
|
|
102
|
+
baseUrl: "https://api.z.ai/api/paas/v4"
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
label: "国际 · Coding Plan",
|
|
106
|
+
baseUrl: "https://api.z.ai/api/coding/paas/v4"
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
label: "国际 · Coding Plan · Anthropic 协议",
|
|
110
|
+
baseUrl: "https://api.z.ai/api/anthropic"
|
|
111
|
+
}
|
|
112
|
+
],
|
|
113
|
+
stepfun: [{
|
|
114
|
+
label: "官方",
|
|
115
|
+
baseUrl: "https://api.stepfun.com/v1"
|
|
116
|
+
}],
|
|
117
|
+
siliconflow: [{
|
|
118
|
+
label: "国内",
|
|
119
|
+
baseUrl: "https://api.siliconflow.cn/v1"
|
|
120
|
+
}, {
|
|
121
|
+
label: "全球",
|
|
122
|
+
baseUrl: "https://api.siliconflow.com/v1"
|
|
123
|
+
}],
|
|
124
|
+
openrouter: [{
|
|
125
|
+
label: "官方",
|
|
126
|
+
baseUrl: "https://openrouter.ai/api/v1"
|
|
127
|
+
}],
|
|
128
|
+
novita: [{
|
|
129
|
+
label: "官方",
|
|
130
|
+
baseUrl: "https://api.novita.ai/openai/v1"
|
|
131
|
+
}],
|
|
132
|
+
agnes: [{
|
|
133
|
+
label: "国内",
|
|
134
|
+
baseUrl: "https://api.agnes-ai.cn/v1"
|
|
135
|
+
}, {
|
|
136
|
+
label: "全球",
|
|
137
|
+
baseUrl: "https://apihub.agnes-ai.com/v1"
|
|
138
|
+
}],
|
|
139
|
+
medalsoft: [{
|
|
140
|
+
label: "内网",
|
|
141
|
+
baseUrl: MEDALSOFT_PRIVATE_GATEWAY_URL
|
|
142
|
+
}, {
|
|
143
|
+
label: "外网",
|
|
144
|
+
baseUrl: "https://nexus.servicemecloud.com/v1"
|
|
145
|
+
}],
|
|
146
|
+
sensenova: [{
|
|
147
|
+
label: "官方",
|
|
148
|
+
baseUrl: "https://token.sensenova.cn/v1"
|
|
149
|
+
}],
|
|
150
|
+
"vscode-builtin": []
|
|
115
151
|
};
|
|
152
|
+
/**
|
|
153
|
+
* Resolve the dropdown candidate list for a provider type. Returns
|
|
154
|
+
* an empty array (NEVER throws) for `-compatible` types or provider
|
|
155
|
+
* types the table doesn't cover.
|
|
156
|
+
*/
|
|
116
157
|
function getProviderBaseUrlPresets(type) {
|
|
117
|
-
|
|
158
|
+
return PROVIDER_BASE_URL_PRESETS[type] ?? [];
|
|
118
159
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
// ignores this flag.
|
|
142
|
-
zhipu: {
|
|
143
|
-
supportsCacheControl: true
|
|
144
|
-
},
|
|
145
|
-
stepfun: {},
|
|
146
|
-
siliconflow: {},
|
|
147
|
-
openrouter: {},
|
|
148
|
-
novita: {},
|
|
149
|
-
agnes: {
|
|
150
|
-
supportsCacheControl: true
|
|
151
|
-
},
|
|
152
|
-
// Medalsoft internal gateway — OpenAI-compatible pass-through; the
|
|
153
|
-
// upstream's prompt-cache behaviour is unknown from outside, so we
|
|
154
|
-
// don't declare support (no `prompt_cache_key` header is written).
|
|
155
|
-
// Flip to `supportsCacheControl: true` once the gateway is verified
|
|
156
|
-
// to honour stable prompt caching.
|
|
157
|
-
medalsoft: {},
|
|
158
|
-
// SenseNova (SenseTime) token-plan gateway — the /v1/models payload
|
|
159
|
-
// publishes an `input_cache_read` pricing field (all-zero today), so
|
|
160
|
-
// the upstream is cache-aware in principle, but there is no
|
|
161
|
-
// documented prompt-cache key contract. Off until verified.
|
|
162
|
-
sensenova: {},
|
|
163
|
-
"vscode-builtin": {}
|
|
160
|
+
//#endregion
|
|
161
|
+
//#region src/ai/providers.cache-control.ts
|
|
162
|
+
/**
|
|
163
|
+
* Provider-type → cache_control capability lookup. Read by the
|
|
164
|
+
* OpenAI adapter's `prompt_cache_key` decision (T-04) via
|
|
165
|
+
* {@link isProviderCacheControlAware}.
|
|
166
|
+
*/
|
|
167
|
+
const PROVIDER_CACHE_CONTROL_METADATA = {
|
|
168
|
+
"openai-compatible": {},
|
|
169
|
+
"anthropic-compatible": {},
|
|
170
|
+
minimax: { supportsCacheControl: true },
|
|
171
|
+
deepseek: { supportsCacheControl: true },
|
|
172
|
+
kimi: {},
|
|
173
|
+
zhipu: { supportsCacheControl: true },
|
|
174
|
+
stepfun: {},
|
|
175
|
+
siliconflow: {},
|
|
176
|
+
openrouter: {},
|
|
177
|
+
novita: {},
|
|
178
|
+
agnes: { supportsCacheControl: true },
|
|
179
|
+
medalsoft: {},
|
|
180
|
+
sensenova: {},
|
|
181
|
+
"vscode-builtin": {}
|
|
164
182
|
};
|
|
183
|
+
/**
|
|
184
|
+
* Does this provider type declare "stable prompt cache" support?
|
|
185
|
+
* The OpenAI path reads this flag to decide whether to write
|
|
186
|
+
* `prompt_cache_key`; the Anthropic path uses its own 4-breakpoint
|
|
187
|
+
* logic and ignores this flag.
|
|
188
|
+
*/
|
|
165
189
|
function isProviderCacheControlAware(type) {
|
|
166
|
-
|
|
190
|
+
return Boolean(PROVIDER_CACHE_CONTROL_METADATA[type]?.supportsCacheControl);
|
|
167
191
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
pricingCNY: { input: 0.1, output: 0.4, cacheRead: null },
|
|
999
|
-
priceCategory: "low",
|
|
1000
|
-
maxInputTokens: 32768,
|
|
1001
|
-
maxOutputTokens: 32768
|
|
1002
|
-
},
|
|
1003
|
-
// ── MiniMax M2.5 (2026-02-13) ──────────────────────────────────────
|
|
1004
|
-
// 229B MoE, 80.2% SWE-Bench Verified, the predecessor to M2.7.
|
|
1005
|
-
// Source: https://siliconflow.cn/models?series=qwen (MiniMax card),
|
|
1006
|
-
// platform.minimaxi.com/docs/release-notes/models (M2.5 release note).
|
|
1007
|
-
"MiniMax-M2.5": {
|
|
1008
|
-
detail: "MiniMax M2.5 \u2014 229B MoE, SOTA \u7F16\u7A0B / Agent / \u529E\u516C\u751F\u4EA7\u529B\uFF08192K \u4E0A\u4E0B\u6587\uFF09",
|
|
1009
|
-
imageInput: false,
|
|
1010
|
-
toolCalling: true,
|
|
1011
|
-
// Per https://minimax-ai.chat/pricing (M2.5 legacy line):
|
|
1012
|
-
// ¥2.1 input / ¥8.4 output / ¥0.21 cache hit per 1M tokens;
|
|
1013
|
-
// USD = $0.30 / $1.20 / $0.03 (cloudprice.net 2026-08-13).
|
|
1014
|
-
// Cache hit IS the published rate — the previous comment
|
|
1015
|
-
// "按官方 10% cache 命中率回填" was wrong (it implied we
|
|
1016
|
-
// were estimating, when actually the cache rate is
|
|
1017
|
-
// documented at ¥0.21 / $0.03 per 1M tokens).
|
|
1018
|
-
pricingUSD: { input: 0.3, output: 1.2, cacheRead: 0.03 },
|
|
1019
|
-
pricingCNY: { input: 2.1, output: 8.4, cacheRead: 0.21 },
|
|
1020
|
-
priceCategory: "medium",
|
|
1021
|
-
// 官方 context 192K;output 按 16K 保守估值(M2.7 标 128K,M2.5
|
|
1022
|
-
// 官方未单独发布 max output 数字,按其同代老模型惯例取 16K)。
|
|
1023
|
-
maxInputTokens: 192e3,
|
|
1024
|
-
maxOutputTokens: 16384
|
|
1025
|
-
},
|
|
1026
|
-
// ── Qwen3.6-35B-A3B(2026-04-17 通义千问)────────────────────────────
|
|
1027
|
-
// 35B MoE,激活仅 3B,256K 上下文;2026 年 Qwen3.6 系列首发
|
|
1028
|
-
// small-size open-weight。"激活成本 1/10" 是其与前代 Qwen3.5-27B
|
|
1029
|
-
// 相比的核心卖点。Source:
|
|
1030
|
-
// https://siliconflow.cn/news/z12t3edpv6ypbuja3o65lgh2
|
|
1031
|
-
"Qwen3.6-35B-A3B": {
|
|
1032
|
-
detail: "Qwen3.6-35B-A3B \u2014 35B MoE (3B \u6FC0\u6D3B)\uFF0C\u601D\u8003/\u975E\u601D\u8003\u53CC\u6A21\uFF0C256K \u4E0A\u4E0B\u6587",
|
|
1033
|
-
imageInput: true,
|
|
1034
|
-
toolCalling: true,
|
|
1035
|
-
// SiliconFlow 官方价格页(https://siliconflow.cn/pricing,
|
|
1036
|
-
// re-fetched 2026-08-20):¥1.8 input / ¥10.8 output per 1M
|
|
1037
|
-
// tokens,无缓存价格列。此前 ¥1.6/¥12.8 来自发布新闻稿,
|
|
1038
|
-
// 已过时(输入上调、输出下调)。USD = CNY ÷7 镜像取两位
|
|
1039
|
-
// 小数(0.26 / 1.54)。
|
|
1040
|
-
pricingUSD: { input: 0.26, output: 1.54, cacheRead: null },
|
|
1041
|
-
pricingCNY: { input: 1.8, output: 10.8, cacheRead: null },
|
|
1042
|
-
priceCategory: "low",
|
|
1043
|
-
// Qwen3.6-35B-A3B 官方 256K context;output 上限按同代 27B
|
|
1044
|
-
// 同样 32K 取值(Qwen3.6 系列 max output 未单独公布)。
|
|
1045
|
-
maxInputTokens: 256e3,
|
|
1046
|
-
maxOutputTokens: 32768
|
|
1047
|
-
}
|
|
192
|
+
//#endregion
|
|
193
|
+
//#region src/ai/providers.metadata.ts
|
|
194
|
+
const PRIMARY_METADATA = {
|
|
195
|
+
"MiniMax-M3": {
|
|
196
|
+
detail: "Native multimodal frontier coding model (1M context, 512K effective)",
|
|
197
|
+
imageInput: true,
|
|
198
|
+
toolCalling: true,
|
|
199
|
+
pricingUSD: {
|
|
200
|
+
input: .3,
|
|
201
|
+
output: 1.2,
|
|
202
|
+
cacheRead: .06
|
|
203
|
+
},
|
|
204
|
+
pricingCNY: {
|
|
205
|
+
input: 2.1,
|
|
206
|
+
output: 8.4,
|
|
207
|
+
cacheRead: .42
|
|
208
|
+
},
|
|
209
|
+
priceCategory: "medium",
|
|
210
|
+
thinkingSchema: "thinkingEnabled",
|
|
211
|
+
maxInputTokens: 512e3,
|
|
212
|
+
maxOutputTokens: 512e3
|
|
213
|
+
},
|
|
214
|
+
"MiniMax-M2.7": {
|
|
215
|
+
detail: "Self-iterating coding model (~60 TPS)",
|
|
216
|
+
imageInput: false,
|
|
217
|
+
toolCalling: true,
|
|
218
|
+
pricingUSD: {
|
|
219
|
+
input: .3,
|
|
220
|
+
output: 1.2,
|
|
221
|
+
cacheRead: .06
|
|
222
|
+
},
|
|
223
|
+
pricingCNY: {
|
|
224
|
+
input: 2.1,
|
|
225
|
+
output: 8.4,
|
|
226
|
+
cacheRead: .42
|
|
227
|
+
},
|
|
228
|
+
priceCategory: "low",
|
|
229
|
+
maxInputTokens: 131072,
|
|
230
|
+
maxOutputTokens: 73728
|
|
231
|
+
},
|
|
232
|
+
"MiniMax-M2.7-highspeed": {
|
|
233
|
+
detail: "M2.7 high-speed: same quality, faster (~100 TPS)",
|
|
234
|
+
imageInput: false,
|
|
235
|
+
toolCalling: true,
|
|
236
|
+
pricingUSD: {
|
|
237
|
+
input: .6,
|
|
238
|
+
output: 2.4,
|
|
239
|
+
cacheRead: .06
|
|
240
|
+
},
|
|
241
|
+
pricingCNY: {
|
|
242
|
+
input: 4.2,
|
|
243
|
+
output: 16.8,
|
|
244
|
+
cacheRead: .42
|
|
245
|
+
},
|
|
246
|
+
priceCategory: "low",
|
|
247
|
+
maxInputTokens: 131072,
|
|
248
|
+
maxOutputTokens: 73728
|
|
249
|
+
},
|
|
250
|
+
"sensenova-6.8-flash-lite": {
|
|
251
|
+
detail: "SenseNova 6.8 Flash-Lite — 轻量多模态智能体模型,支持图像理解 / 工具调用(262K 上下文,token-plan 限频)",
|
|
252
|
+
imageInput: true,
|
|
253
|
+
toolCalling: true,
|
|
254
|
+
pricingUSD: {
|
|
255
|
+
input: 0,
|
|
256
|
+
output: 0,
|
|
257
|
+
cacheRead: 0
|
|
258
|
+
},
|
|
259
|
+
pricingCNY: {
|
|
260
|
+
input: 0,
|
|
261
|
+
output: 0,
|
|
262
|
+
cacheRead: 0
|
|
263
|
+
},
|
|
264
|
+
priceCategory: "low",
|
|
265
|
+
thinkingSchema: "reasoningEffort",
|
|
266
|
+
supportsReasoningEffort: true,
|
|
267
|
+
maxInputTokens: 262144,
|
|
268
|
+
maxOutputTokens: 65536
|
|
269
|
+
},
|
|
270
|
+
"deepseek-v4-flash": {
|
|
271
|
+
detail: "Fast, general-purpose model",
|
|
272
|
+
imageInput: true,
|
|
273
|
+
toolCalling: true,
|
|
274
|
+
pricingUSD: {
|
|
275
|
+
input: .44,
|
|
276
|
+
output: 1.32,
|
|
277
|
+
cacheRead: .014
|
|
278
|
+
},
|
|
279
|
+
pricingCNY: {
|
|
280
|
+
input: 3,
|
|
281
|
+
output: 9,
|
|
282
|
+
cacheRead: .1
|
|
283
|
+
},
|
|
284
|
+
priceCategory: "low",
|
|
285
|
+
thinkingSchema: "thinkingEnabled",
|
|
286
|
+
maxInputTokens: 655360,
|
|
287
|
+
maxOutputTokens: 393216
|
|
288
|
+
},
|
|
289
|
+
"deepseek-v4-pro": {
|
|
290
|
+
detail: "Most capable reasoning model",
|
|
291
|
+
imageInput: true,
|
|
292
|
+
toolCalling: true,
|
|
293
|
+
pricingUSD: {
|
|
294
|
+
input: 1.32,
|
|
295
|
+
output: 3.96,
|
|
296
|
+
cacheRead: .044
|
|
297
|
+
},
|
|
298
|
+
pricingCNY: {
|
|
299
|
+
input: 9,
|
|
300
|
+
output: 27,
|
|
301
|
+
cacheRead: .3
|
|
302
|
+
},
|
|
303
|
+
priceCategory: "low",
|
|
304
|
+
thinkingSchema: "thinkingEnabled",
|
|
305
|
+
maxInputTokens: 655360,
|
|
306
|
+
maxOutputTokens: 393216
|
|
307
|
+
},
|
|
308
|
+
"agnes-2.0-flash": {
|
|
309
|
+
detail: "Fast agentic model — tool calling, coding, image understanding (512K context)",
|
|
310
|
+
imageInput: true,
|
|
311
|
+
toolCalling: true,
|
|
312
|
+
pricingUSD: {
|
|
313
|
+
input: .03,
|
|
314
|
+
output: .15,
|
|
315
|
+
cacheRead: null
|
|
316
|
+
},
|
|
317
|
+
pricingCNY: {
|
|
318
|
+
input: .03,
|
|
319
|
+
output: .15,
|
|
320
|
+
cacheRead: null
|
|
321
|
+
},
|
|
322
|
+
priceCategory: "low",
|
|
323
|
+
thinkingSchema: "thinkingEnabled",
|
|
324
|
+
maxInputTokens: 512e3,
|
|
325
|
+
maxOutputTokens: 65536
|
|
326
|
+
},
|
|
327
|
+
"agnes-2.5-pro-alpha": {
|
|
328
|
+
detail: "Agnes 2.5 Pro Alpha — 付费推理模型(高级编码、科学推理、长上下文、多模态)。1M 上下文 / 65K 最大输出。",
|
|
329
|
+
imageInput: true,
|
|
330
|
+
toolCalling: true,
|
|
331
|
+
pricingUSD: {
|
|
332
|
+
input: .45,
|
|
333
|
+
output: .9,
|
|
334
|
+
cacheRead: .0038
|
|
335
|
+
},
|
|
336
|
+
pricingCNY: {
|
|
337
|
+
input: .45,
|
|
338
|
+
output: .9,
|
|
339
|
+
cacheRead: .0038
|
|
340
|
+
},
|
|
341
|
+
priceCategory: "medium",
|
|
342
|
+
thinkingSchema: "thinkingEnabled",
|
|
343
|
+
maxInputTokens: 934976,
|
|
344
|
+
maxOutputTokens: 65536
|
|
345
|
+
},
|
|
346
|
+
"agnes-2.5-pro": {
|
|
347
|
+
detail: "Paid reasoning model — commercial stable of 2.5 Pro Alpha (advanced coding, scientific reasoning, long context, multimodal). 1M context / 65K max output",
|
|
348
|
+
imageInput: true,
|
|
349
|
+
toolCalling: true,
|
|
350
|
+
pricingUSD: {
|
|
351
|
+
input: .45,
|
|
352
|
+
output: .9,
|
|
353
|
+
cacheRead: .0038
|
|
354
|
+
},
|
|
355
|
+
pricingCNY: {
|
|
356
|
+
input: .45,
|
|
357
|
+
output: .9,
|
|
358
|
+
cacheRead: .0038
|
|
359
|
+
},
|
|
360
|
+
priceCategory: "medium",
|
|
361
|
+
thinkingSchema: "thinkingEnabled",
|
|
362
|
+
maxInputTokens: 934976,
|
|
363
|
+
maxOutputTokens: 65536
|
|
364
|
+
},
|
|
365
|
+
"agnes-2.5-flash": {
|
|
366
|
+
detail: "GA upgrade of Agnes 2.0 Flash — stronger coding, agent workflows, tool calling, image understanding (512K context)",
|
|
367
|
+
imageInput: true,
|
|
368
|
+
toolCalling: true,
|
|
369
|
+
pricingUSD: {
|
|
370
|
+
input: .03,
|
|
371
|
+
output: .15,
|
|
372
|
+
cacheRead: null
|
|
373
|
+
},
|
|
374
|
+
pricingCNY: {
|
|
375
|
+
input: .03,
|
|
376
|
+
output: .15,
|
|
377
|
+
cacheRead: null
|
|
378
|
+
},
|
|
379
|
+
priceCategory: "low",
|
|
380
|
+
thinkingSchema: "thinkingEnabled",
|
|
381
|
+
maxInputTokens: 512e3,
|
|
382
|
+
maxOutputTokens: 65536
|
|
383
|
+
},
|
|
384
|
+
"kimi-k3": {
|
|
385
|
+
detail: "2.8万亿参数旗舰开源模型,1M 上下文",
|
|
386
|
+
imageInput: true,
|
|
387
|
+
toolCalling: true,
|
|
388
|
+
pricingUSD: {
|
|
389
|
+
input: 2.8,
|
|
390
|
+
output: 14,
|
|
391
|
+
cacheRead: .28
|
|
392
|
+
},
|
|
393
|
+
pricingCNY: {
|
|
394
|
+
input: 20,
|
|
395
|
+
output: 100,
|
|
396
|
+
cacheRead: 2
|
|
397
|
+
},
|
|
398
|
+
priceCategory: "high",
|
|
399
|
+
thinkingSchema: "reasoningEffort",
|
|
400
|
+
maxInputTokens: 920576,
|
|
401
|
+
maxOutputTokens: 128e3
|
|
402
|
+
},
|
|
403
|
+
"kimi-k2.7-code": {
|
|
404
|
+
detail: "Coding 专用,支持图片+视频输入,工具调用,始终开启思考",
|
|
405
|
+
imageInput: true,
|
|
406
|
+
toolCalling: true,
|
|
407
|
+
pricingUSD: {
|
|
408
|
+
input: .91,
|
|
409
|
+
output: 3.78,
|
|
410
|
+
cacheRead: .182
|
|
411
|
+
},
|
|
412
|
+
pricingCNY: {
|
|
413
|
+
input: 6.5,
|
|
414
|
+
output: 27,
|
|
415
|
+
cacheRead: 1.3
|
|
416
|
+
},
|
|
417
|
+
priceCategory: "medium",
|
|
418
|
+
maxInputTokens: 229376,
|
|
419
|
+
maxOutputTokens: 32768
|
|
420
|
+
},
|
|
421
|
+
"kimi-k2.7-code-highspeed": {
|
|
422
|
+
detail: "K2.7 Code 高速版(双倍价格),与 K2.7 Code 同一模型",
|
|
423
|
+
imageInput: true,
|
|
424
|
+
toolCalling: true,
|
|
425
|
+
pricingUSD: {
|
|
426
|
+
input: 1.82,
|
|
427
|
+
output: 7.57,
|
|
428
|
+
cacheRead: .364
|
|
429
|
+
},
|
|
430
|
+
pricingCNY: {
|
|
431
|
+
input: 13,
|
|
432
|
+
output: 54,
|
|
433
|
+
cacheRead: 2.6
|
|
434
|
+
},
|
|
435
|
+
priceCategory: "high",
|
|
436
|
+
maxInputTokens: 229376,
|
|
437
|
+
maxOutputTokens: 32768
|
|
438
|
+
},
|
|
439
|
+
"kimi-k2.6": {
|
|
440
|
+
detail: "K2.6 — 支持图片+视频、工具调用,思考可开关",
|
|
441
|
+
imageInput: true,
|
|
442
|
+
toolCalling: true,
|
|
443
|
+
pricingUSD: {
|
|
444
|
+
input: .91,
|
|
445
|
+
output: 3.78,
|
|
446
|
+
cacheRead: .154
|
|
447
|
+
},
|
|
448
|
+
pricingCNY: {
|
|
449
|
+
input: 6.5,
|
|
450
|
+
output: 27,
|
|
451
|
+
cacheRead: 1.1
|
|
452
|
+
},
|
|
453
|
+
priceCategory: "medium",
|
|
454
|
+
thinkingSchema: "thinkingEnabled",
|
|
455
|
+
maxInputTokens: 229376,
|
|
456
|
+
maxOutputTokens: 32768
|
|
457
|
+
},
|
|
458
|
+
"glm-5.2": {
|
|
459
|
+
detail: "GLM-5.2 — 1M 上下文,最大输出 128K(单档 pricing)",
|
|
460
|
+
imageInput: false,
|
|
461
|
+
toolCalling: true,
|
|
462
|
+
thinkingSchema: "reasoningEffort",
|
|
463
|
+
supportsReasoningEffort: true,
|
|
464
|
+
pricingUSD: {
|
|
465
|
+
input: 1.12,
|
|
466
|
+
output: 3.92,
|
|
467
|
+
cacheRead: .28
|
|
468
|
+
},
|
|
469
|
+
pricingCNY: {
|
|
470
|
+
input: 8,
|
|
471
|
+
output: 28,
|
|
472
|
+
cacheRead: 2
|
|
473
|
+
},
|
|
474
|
+
priceCategory: "high",
|
|
475
|
+
maxInputTokens: 1e6,
|
|
476
|
+
maxOutputTokens: 128e3
|
|
477
|
+
},
|
|
478
|
+
"glm-5.3": {
|
|
479
|
+
detail: "GLM-5.3 — 1M 上下文,后训练增强代码 / 网络安全",
|
|
480
|
+
imageInput: false,
|
|
481
|
+
toolCalling: true,
|
|
482
|
+
thinkingSchema: "reasoningEffort",
|
|
483
|
+
supportsReasoningEffort: true,
|
|
484
|
+
pricingUSD: {
|
|
485
|
+
input: 1.4,
|
|
486
|
+
output: 4.4,
|
|
487
|
+
cacheRead: .26
|
|
488
|
+
},
|
|
489
|
+
pricingCNY: {
|
|
490
|
+
input: 8,
|
|
491
|
+
output: 28,
|
|
492
|
+
cacheRead: 2
|
|
493
|
+
},
|
|
494
|
+
priceCategory: "high",
|
|
495
|
+
maxInputTokens: 1e6,
|
|
496
|
+
maxOutputTokens: 128e3
|
|
497
|
+
},
|
|
498
|
+
"glm-5.3-flash": {
|
|
499
|
+
detail: "GLM-5.3 Flash — 320B/18B 激活,稀疏+线性注意力混合架构,原生多模态视觉 Coding,1M 上下文",
|
|
500
|
+
imageInput: true,
|
|
501
|
+
toolCalling: true,
|
|
502
|
+
thinkingSchema: "reasoningEffort",
|
|
503
|
+
supportsReasoningEffort: true,
|
|
504
|
+
pricingUSD: {
|
|
505
|
+
input: .057,
|
|
506
|
+
output: .2,
|
|
507
|
+
cacheRead: .0164
|
|
508
|
+
},
|
|
509
|
+
pricingCNY: {
|
|
510
|
+
input: .4,
|
|
511
|
+
output: 1.4,
|
|
512
|
+
cacheRead: .115
|
|
513
|
+
},
|
|
514
|
+
priceCategory: "low",
|
|
515
|
+
maxInputTokens: 1e6,
|
|
516
|
+
maxOutputTokens: 128e3
|
|
517
|
+
},
|
|
518
|
+
"glm-5.1-highspeed": {
|
|
519
|
+
detail: "GLM-5.1 HighSpeed — 400 TPS 高吞吐生产变体([0, 32K) tier 镜像 GLM-5.1)",
|
|
520
|
+
imageInput: false,
|
|
521
|
+
toolCalling: true,
|
|
522
|
+
thinkingSchema: "reasoningEffort",
|
|
523
|
+
pricingUSD: {
|
|
524
|
+
input: .84,
|
|
525
|
+
output: 3.36,
|
|
526
|
+
cacheRead: .182
|
|
527
|
+
},
|
|
528
|
+
pricingCNY: {
|
|
529
|
+
input: 6,
|
|
530
|
+
output: 24,
|
|
531
|
+
cacheRead: 1.3
|
|
532
|
+
},
|
|
533
|
+
priceCategory: "medium",
|
|
534
|
+
maxInputTokens: 2e5,
|
|
535
|
+
maxOutputTokens: 128e3
|
|
536
|
+
},
|
|
537
|
+
"glm-4.7-flash": {
|
|
538
|
+
detail: "GLM-4.7 Flash — 完全免费(200K 上下文)",
|
|
539
|
+
imageInput: false,
|
|
540
|
+
toolCalling: true,
|
|
541
|
+
thinkingSchema: "reasoningEffort",
|
|
542
|
+
pricingUSD: {
|
|
543
|
+
input: 0,
|
|
544
|
+
output: 0,
|
|
545
|
+
cacheRead: 0
|
|
546
|
+
},
|
|
547
|
+
pricingCNY: {
|
|
548
|
+
input: 0,
|
|
549
|
+
output: 0,
|
|
550
|
+
cacheRead: 0
|
|
551
|
+
},
|
|
552
|
+
priceCategory: "low",
|
|
553
|
+
maxInputTokens: 2e5,
|
|
554
|
+
maxOutputTokens: 128e3
|
|
555
|
+
},
|
|
556
|
+
"glm-4.7": {
|
|
557
|
+
detail: "GLM-4.7 — 200K 上下文,工具调用(3-tier pricing)",
|
|
558
|
+
imageInput: false,
|
|
559
|
+
toolCalling: true,
|
|
560
|
+
thinkingSchema: "reasoningEffort",
|
|
561
|
+
pricingUSD: {
|
|
562
|
+
input: .28,
|
|
563
|
+
output: 1.12,
|
|
564
|
+
cacheRead: .056
|
|
565
|
+
},
|
|
566
|
+
pricingCNY: {
|
|
567
|
+
input: 2,
|
|
568
|
+
output: 8,
|
|
569
|
+
cacheRead: .4
|
|
570
|
+
},
|
|
571
|
+
priceCategory: "medium",
|
|
572
|
+
maxInputTokens: 2e5,
|
|
573
|
+
maxOutputTokens: 128e3
|
|
574
|
+
},
|
|
575
|
+
"glm-5.1": {
|
|
576
|
+
detail: "GLM-5.1 — 200K 上下文,最大输出 128K(2-tier pricing)",
|
|
577
|
+
imageInput: false,
|
|
578
|
+
toolCalling: true,
|
|
579
|
+
thinkingSchema: "reasoningEffort",
|
|
580
|
+
pricingUSD: {
|
|
581
|
+
input: .84,
|
|
582
|
+
output: 3.36,
|
|
583
|
+
cacheRead: .182
|
|
584
|
+
},
|
|
585
|
+
pricingCNY: {
|
|
586
|
+
input: 6,
|
|
587
|
+
output: 24,
|
|
588
|
+
cacheRead: 1.3
|
|
589
|
+
},
|
|
590
|
+
priceCategory: "high",
|
|
591
|
+
maxInputTokens: 2e5,
|
|
592
|
+
maxOutputTokens: 128e3
|
|
593
|
+
},
|
|
594
|
+
"glm-5": {
|
|
595
|
+
detail: "GLM-5 — 200K 上下文,Agentic 工具调用,最大输出 128K(2-tier pricing)",
|
|
596
|
+
imageInput: false,
|
|
597
|
+
toolCalling: true,
|
|
598
|
+
thinkingSchema: "reasoningEffort",
|
|
599
|
+
pricingUSD: {
|
|
600
|
+
input: .56,
|
|
601
|
+
output: 2.52,
|
|
602
|
+
cacheRead: .14
|
|
603
|
+
},
|
|
604
|
+
pricingCNY: {
|
|
605
|
+
input: 4,
|
|
606
|
+
output: 18,
|
|
607
|
+
cacheRead: 1
|
|
608
|
+
},
|
|
609
|
+
priceCategory: "high",
|
|
610
|
+
maxInputTokens: 2e5,
|
|
611
|
+
maxOutputTokens: 128e3
|
|
612
|
+
},
|
|
613
|
+
"glm-5-turbo": {
|
|
614
|
+
detail: "GLM-5 Turbo — 200K 上下文,最大输出 128K(2-tier pricing)",
|
|
615
|
+
imageInput: false,
|
|
616
|
+
toolCalling: true,
|
|
617
|
+
thinkingSchema: "reasoningEffort",
|
|
618
|
+
pricingUSD: {
|
|
619
|
+
input: .7,
|
|
620
|
+
output: 3.08,
|
|
621
|
+
cacheRead: .168
|
|
622
|
+
},
|
|
623
|
+
pricingCNY: {
|
|
624
|
+
input: 5,
|
|
625
|
+
output: 22,
|
|
626
|
+
cacheRead: 1.2
|
|
627
|
+
},
|
|
628
|
+
priceCategory: "medium",
|
|
629
|
+
maxInputTokens: 2e5,
|
|
630
|
+
maxOutputTokens: 128e3
|
|
631
|
+
},
|
|
632
|
+
"glm-4.7-flashx": {
|
|
633
|
+
detail: "GLM-4.7 FlashX — 快速版",
|
|
634
|
+
imageInput: false,
|
|
635
|
+
toolCalling: false,
|
|
636
|
+
thinkingSchema: "reasoningEffort",
|
|
637
|
+
pricingUSD: {
|
|
638
|
+
input: .07,
|
|
639
|
+
output: .42,
|
|
640
|
+
cacheRead: .014
|
|
641
|
+
},
|
|
642
|
+
pricingCNY: {
|
|
643
|
+
input: .5,
|
|
644
|
+
output: 3,
|
|
645
|
+
cacheRead: .1
|
|
646
|
+
},
|
|
647
|
+
priceCategory: "low",
|
|
648
|
+
maxInputTokens: 2e5,
|
|
649
|
+
maxOutputTokens: 128e3
|
|
650
|
+
},
|
|
651
|
+
"glm-4.6": {
|
|
652
|
+
detail: "GLM-4.6 — 工具调用",
|
|
653
|
+
imageInput: false,
|
|
654
|
+
toolCalling: true,
|
|
655
|
+
thinkingSchema: "reasoningEffort",
|
|
656
|
+
pricingUSD: {
|
|
657
|
+
input: .6,
|
|
658
|
+
output: 2.2,
|
|
659
|
+
cacheRead: .11
|
|
660
|
+
},
|
|
661
|
+
pricingCNY: {
|
|
662
|
+
input: 4.3,
|
|
663
|
+
output: 15.7,
|
|
664
|
+
cacheRead: .79
|
|
665
|
+
},
|
|
666
|
+
priceCategory: "medium",
|
|
667
|
+
maxInputTokens: 2e5,
|
|
668
|
+
maxOutputTokens: 128e3
|
|
669
|
+
},
|
|
670
|
+
"glm-4.5": {
|
|
671
|
+
detail: "GLM-4.5 — 工具调用(裸名;Zhipu /v1/models 暴露的 legacy alias)",
|
|
672
|
+
imageInput: false,
|
|
673
|
+
toolCalling: true,
|
|
674
|
+
thinkingSchema: "reasoningEffort",
|
|
675
|
+
pricingUSD: {
|
|
676
|
+
input: 0,
|
|
677
|
+
output: 0,
|
|
678
|
+
cacheRead: null
|
|
679
|
+
},
|
|
680
|
+
pricingCNY: {
|
|
681
|
+
input: 0,
|
|
682
|
+
output: 0,
|
|
683
|
+
cacheRead: null
|
|
684
|
+
},
|
|
685
|
+
priceCategory: "low",
|
|
686
|
+
maxInputTokens: 128e3,
|
|
687
|
+
maxOutputTokens: 96e3
|
|
688
|
+
},
|
|
689
|
+
"glm-4.5-air": {
|
|
690
|
+
detail: "GLM-4.5 Air — 工具调用(3-tier pricing)",
|
|
691
|
+
imageInput: false,
|
|
692
|
+
toolCalling: true,
|
|
693
|
+
thinkingSchema: "reasoningEffort",
|
|
694
|
+
pricingUSD: {
|
|
695
|
+
input: .112,
|
|
696
|
+
output: .28,
|
|
697
|
+
cacheRead: .0224
|
|
698
|
+
},
|
|
699
|
+
pricingCNY: {
|
|
700
|
+
input: .8,
|
|
701
|
+
output: 2,
|
|
702
|
+
cacheRead: .16
|
|
703
|
+
},
|
|
704
|
+
priceCategory: "low",
|
|
705
|
+
maxInputTokens: 128e3,
|
|
706
|
+
maxOutputTokens: 96e3
|
|
707
|
+
},
|
|
708
|
+
"glm-4.5-airx": {
|
|
709
|
+
detail: "GLM-4.5 AirX — 快速版(¥10/M 单档)",
|
|
710
|
+
imageInput: false,
|
|
711
|
+
toolCalling: false,
|
|
712
|
+
thinkingSchema: "reasoningEffort",
|
|
713
|
+
pricingUSD: {
|
|
714
|
+
input: 1.4,
|
|
715
|
+
output: 1.4,
|
|
716
|
+
cacheRead: null
|
|
717
|
+
},
|
|
718
|
+
pricingCNY: {
|
|
719
|
+
input: 10,
|
|
720
|
+
output: 10,
|
|
721
|
+
cacheRead: null
|
|
722
|
+
},
|
|
723
|
+
priceCategory: "low",
|
|
724
|
+
maxInputTokens: 8192,
|
|
725
|
+
maxOutputTokens: 96e3
|
|
726
|
+
},
|
|
727
|
+
"glm-4-long": {
|
|
728
|
+
detail: "GLM-4 Long — 1M 上下文,长文档处理",
|
|
729
|
+
imageInput: false,
|
|
730
|
+
toolCalling: false,
|
|
731
|
+
pricingUSD: {
|
|
732
|
+
input: .14,
|
|
733
|
+
output: .14,
|
|
734
|
+
cacheRead: null
|
|
735
|
+
},
|
|
736
|
+
pricingCNY: {
|
|
737
|
+
input: 1,
|
|
738
|
+
output: 1,
|
|
739
|
+
cacheRead: null
|
|
740
|
+
},
|
|
741
|
+
priceCategory: "low",
|
|
742
|
+
maxInputTokens: 1e6,
|
|
743
|
+
maxOutputTokens: 4e3
|
|
744
|
+
},
|
|
745
|
+
"glm-4-flashx": {
|
|
746
|
+
detail: "GLM-4 FlashX — 快速版",
|
|
747
|
+
imageInput: false,
|
|
748
|
+
toolCalling: false,
|
|
749
|
+
pricingUSD: {
|
|
750
|
+
input: .014,
|
|
751
|
+
output: .014,
|
|
752
|
+
cacheRead: null
|
|
753
|
+
},
|
|
754
|
+
pricingCNY: {
|
|
755
|
+
input: .1,
|
|
756
|
+
output: .1,
|
|
757
|
+
cacheRead: null
|
|
758
|
+
},
|
|
759
|
+
priceCategory: "low",
|
|
760
|
+
maxInputTokens: 128e3,
|
|
761
|
+
maxOutputTokens: 16e3
|
|
762
|
+
},
|
|
763
|
+
"glm-4-plus": {
|
|
764
|
+
detail: "GLM-4 Plus — Function Call 支持",
|
|
765
|
+
imageInput: false,
|
|
766
|
+
toolCalling: true,
|
|
767
|
+
pricingUSD: {
|
|
768
|
+
input: .7,
|
|
769
|
+
output: .7,
|
|
770
|
+
cacheRead: null
|
|
771
|
+
},
|
|
772
|
+
pricingCNY: {
|
|
773
|
+
input: 5,
|
|
774
|
+
output: 5,
|
|
775
|
+
cacheRead: null
|
|
776
|
+
},
|
|
777
|
+
priceCategory: "low",
|
|
778
|
+
maxInputTokens: 128e3,
|
|
779
|
+
maxOutputTokens: 4e3
|
|
780
|
+
},
|
|
781
|
+
"glm-4.5v": {
|
|
782
|
+
detail: "GLM-4.5V 视觉推理模型 — 图像/视频/文档/GUI(2-tier pricing)",
|
|
783
|
+
imageInput: true,
|
|
784
|
+
toolCalling: true,
|
|
785
|
+
thinkingSchema: "reasoningEffort",
|
|
786
|
+
pricingUSD: {
|
|
787
|
+
input: .28,
|
|
788
|
+
output: .84,
|
|
789
|
+
cacheRead: .056
|
|
790
|
+
},
|
|
791
|
+
pricingCNY: {
|
|
792
|
+
input: 2,
|
|
793
|
+
output: 6,
|
|
794
|
+
cacheRead: .4
|
|
795
|
+
},
|
|
796
|
+
priceCategory: "medium",
|
|
797
|
+
maxInputTokens: 64e3,
|
|
798
|
+
maxOutputTokens: 8192
|
|
799
|
+
},
|
|
800
|
+
"glm-4.6v": {
|
|
801
|
+
detail: "GLM-4.6V 视觉推理模型 — 图像/视频/文档/GUI(2-tier pricing)",
|
|
802
|
+
imageInput: true,
|
|
803
|
+
toolCalling: true,
|
|
804
|
+
thinkingSchema: "reasoningEffort",
|
|
805
|
+
pricingUSD: {
|
|
806
|
+
input: .14,
|
|
807
|
+
output: .42,
|
|
808
|
+
cacheRead: .028
|
|
809
|
+
},
|
|
810
|
+
pricingCNY: {
|
|
811
|
+
input: 1,
|
|
812
|
+
output: 3,
|
|
813
|
+
cacheRead: .2
|
|
814
|
+
},
|
|
815
|
+
priceCategory: "medium",
|
|
816
|
+
maxInputTokens: 128e3,
|
|
817
|
+
maxOutputTokens: 8192
|
|
818
|
+
},
|
|
819
|
+
"glm-4.6v-flash": {
|
|
820
|
+
detail: "GLM-4.6V Flash — 完全免费视觉理解模型",
|
|
821
|
+
imageInput: true,
|
|
822
|
+
toolCalling: true,
|
|
823
|
+
thinkingSchema: "reasoningEffort",
|
|
824
|
+
pricingUSD: {
|
|
825
|
+
input: 0,
|
|
826
|
+
output: 0,
|
|
827
|
+
cacheRead: 0
|
|
828
|
+
},
|
|
829
|
+
pricingCNY: {
|
|
830
|
+
input: 0,
|
|
831
|
+
output: 0,
|
|
832
|
+
cacheRead: 0
|
|
833
|
+
},
|
|
834
|
+
priceCategory: "low",
|
|
835
|
+
maxInputTokens: 98304,
|
|
836
|
+
maxOutputTokens: 32768
|
|
837
|
+
},
|
|
838
|
+
"glm-4.6v-flashx": {
|
|
839
|
+
detail: "GLM-4.6V FlashX — 快速版视觉理解(2-tier pricing)",
|
|
840
|
+
imageInput: true,
|
|
841
|
+
toolCalling: false,
|
|
842
|
+
thinkingSchema: "reasoningEffort",
|
|
843
|
+
pricingUSD: {
|
|
844
|
+
input: .021,
|
|
845
|
+
output: .21,
|
|
846
|
+
cacheRead: .0042
|
|
847
|
+
},
|
|
848
|
+
pricingCNY: {
|
|
849
|
+
input: .15,
|
|
850
|
+
output: 1.5,
|
|
851
|
+
cacheRead: .03
|
|
852
|
+
},
|
|
853
|
+
priceCategory: "low",
|
|
854
|
+
maxInputTokens: 128e3,
|
|
855
|
+
maxOutputTokens: 8192
|
|
856
|
+
},
|
|
857
|
+
"glm-5v-turbo": {
|
|
858
|
+
detail: "GLM-5V Turbo — 多模态 Coding 模型(2-tier pricing)",
|
|
859
|
+
imageInput: true,
|
|
860
|
+
toolCalling: true,
|
|
861
|
+
thinkingSchema: "reasoningEffort",
|
|
862
|
+
pricingUSD: {
|
|
863
|
+
input: .7,
|
|
864
|
+
output: 3.08,
|
|
865
|
+
cacheRead: .168
|
|
866
|
+
},
|
|
867
|
+
pricingCNY: {
|
|
868
|
+
input: 5,
|
|
869
|
+
output: 22,
|
|
870
|
+
cacheRead: 1.2
|
|
871
|
+
},
|
|
872
|
+
priceCategory: "medium",
|
|
873
|
+
maxInputTokens: 2e5,
|
|
874
|
+
maxOutputTokens: 128e3
|
|
875
|
+
},
|
|
876
|
+
"glm-3-turbo": {
|
|
877
|
+
detail: "GLM-3 Turbo — 入门级(¥1/1M tokens)",
|
|
878
|
+
imageInput: false,
|
|
879
|
+
toolCalling: false,
|
|
880
|
+
pricingUSD: {
|
|
881
|
+
input: .14,
|
|
882
|
+
output: .14,
|
|
883
|
+
cacheRead: null
|
|
884
|
+
},
|
|
885
|
+
pricingCNY: {
|
|
886
|
+
input: 1,
|
|
887
|
+
output: 1,
|
|
888
|
+
cacheRead: null
|
|
889
|
+
},
|
|
890
|
+
priceCategory: "low",
|
|
891
|
+
maxInputTokens: 128e3,
|
|
892
|
+
maxOutputTokens: 8192
|
|
893
|
+
},
|
|
894
|
+
"step-3.7-flash": {
|
|
895
|
+
detail: "Step 3.7 Flash — 多模态推理模型(198B/11B MoE)",
|
|
896
|
+
imageInput: true,
|
|
897
|
+
toolCalling: true,
|
|
898
|
+
pricingUSD: {
|
|
899
|
+
input: .2,
|
|
900
|
+
output: 1.15,
|
|
901
|
+
cacheRead: .04
|
|
902
|
+
},
|
|
903
|
+
pricingCNY: {
|
|
904
|
+
input: 1.35,
|
|
905
|
+
output: 8.1,
|
|
906
|
+
cacheRead: .27
|
|
907
|
+
},
|
|
908
|
+
priceCategory: "medium",
|
|
909
|
+
thinkingSchema: "reasoningEffort",
|
|
910
|
+
maxInputTokens: 262144,
|
|
911
|
+
maxOutputTokens: 256e3
|
|
912
|
+
},
|
|
913
|
+
"step-3.5-flash": {
|
|
914
|
+
detail: "Step 3.5 Flash — 推理模型",
|
|
915
|
+
imageInput: false,
|
|
916
|
+
toolCalling: true,
|
|
917
|
+
pricingUSD: {
|
|
918
|
+
input: .1,
|
|
919
|
+
output: .3,
|
|
920
|
+
cacheRead: .02
|
|
921
|
+
},
|
|
922
|
+
pricingCNY: {
|
|
923
|
+
input: .7,
|
|
924
|
+
output: 2.1,
|
|
925
|
+
cacheRead: .14
|
|
926
|
+
},
|
|
927
|
+
priceCategory: "low",
|
|
928
|
+
thinkingSchema: "reasoningEffort",
|
|
929
|
+
maxInputTokens: 256e3,
|
|
930
|
+
maxOutputTokens: 256e3
|
|
931
|
+
},
|
|
932
|
+
"step-1o-turbo-vision": {
|
|
933
|
+
detail: "Step 1o Turbo Vision — 视觉模型",
|
|
934
|
+
imageInput: true,
|
|
935
|
+
toolCalling: false,
|
|
936
|
+
pricingUSD: {
|
|
937
|
+
input: .357,
|
|
938
|
+
output: 1.143,
|
|
939
|
+
cacheRead: .071
|
|
940
|
+
},
|
|
941
|
+
pricingCNY: {
|
|
942
|
+
input: 2.5,
|
|
943
|
+
output: 8,
|
|
944
|
+
cacheRead: .5
|
|
945
|
+
},
|
|
946
|
+
priceCategory: "low",
|
|
947
|
+
maxInputTokens: 32768,
|
|
948
|
+
maxOutputTokens: 32768
|
|
949
|
+
},
|
|
950
|
+
"openrouter/auto": {
|
|
951
|
+
detail: "OpenRouter Auto — 自动路由到平台最佳模型",
|
|
952
|
+
imageInput: true,
|
|
953
|
+
toolCalling: true,
|
|
954
|
+
pricingUSD: {
|
|
955
|
+
input: 0,
|
|
956
|
+
output: 0,
|
|
957
|
+
cacheRead: null
|
|
958
|
+
},
|
|
959
|
+
pricingCNY: {
|
|
960
|
+
input: 0,
|
|
961
|
+
output: 0,
|
|
962
|
+
cacheRead: null
|
|
963
|
+
},
|
|
964
|
+
priceCategory: "medium",
|
|
965
|
+
maxInputTokens: 0,
|
|
966
|
+
maxOutputTokens: 0
|
|
967
|
+
},
|
|
968
|
+
"novita-ai/novita-3.5-flash": {
|
|
969
|
+
detail: "Novita 3.5 Flash — Novita 平台热门模型(占位,按实际选择调整)",
|
|
970
|
+
imageInput: true,
|
|
971
|
+
toolCalling: true,
|
|
972
|
+
pricingUSD: {
|
|
973
|
+
input: .1,
|
|
974
|
+
output: .4,
|
|
975
|
+
cacheRead: null
|
|
976
|
+
},
|
|
977
|
+
pricingCNY: {
|
|
978
|
+
input: .1,
|
|
979
|
+
output: .4,
|
|
980
|
+
cacheRead: null
|
|
981
|
+
},
|
|
982
|
+
priceCategory: "low",
|
|
983
|
+
maxInputTokens: 32768,
|
|
984
|
+
maxOutputTokens: 32768
|
|
985
|
+
},
|
|
986
|
+
"MiniMax-M2.5": {
|
|
987
|
+
detail: "MiniMax M2.5 — 229B MoE, SOTA 编程 / Agent / 办公生产力(192K 上下文)",
|
|
988
|
+
imageInput: false,
|
|
989
|
+
toolCalling: true,
|
|
990
|
+
pricingUSD: {
|
|
991
|
+
input: .3,
|
|
992
|
+
output: 1.2,
|
|
993
|
+
cacheRead: .03
|
|
994
|
+
},
|
|
995
|
+
pricingCNY: {
|
|
996
|
+
input: 2.1,
|
|
997
|
+
output: 8.4,
|
|
998
|
+
cacheRead: .21
|
|
999
|
+
},
|
|
1000
|
+
priceCategory: "medium",
|
|
1001
|
+
maxInputTokens: 192e3,
|
|
1002
|
+
maxOutputTokens: 16384
|
|
1003
|
+
},
|
|
1004
|
+
"Qwen3.6-35B-A3B": {
|
|
1005
|
+
detail: "Qwen3.6-35B-A3B — 35B MoE (3B 激活),思考/非思考双模,256K 上下文",
|
|
1006
|
+
imageInput: true,
|
|
1007
|
+
toolCalling: true,
|
|
1008
|
+
pricingUSD: {
|
|
1009
|
+
input: .26,
|
|
1010
|
+
output: 1.54,
|
|
1011
|
+
cacheRead: null
|
|
1012
|
+
},
|
|
1013
|
+
pricingCNY: {
|
|
1014
|
+
input: 1.8,
|
|
1015
|
+
output: 10.8,
|
|
1016
|
+
cacheRead: null
|
|
1017
|
+
},
|
|
1018
|
+
priceCategory: "low",
|
|
1019
|
+
maxInputTokens: 256e3,
|
|
1020
|
+
maxOutputTokens: 32768
|
|
1021
|
+
}
|
|
1048
1022
|
};
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1023
|
+
/**
|
|
1024
|
+
* Map of namespaced model ids (used by aggregator platforms like
|
|
1025
|
+
* SiliconFlow and Novita) to their primary entry. **Exported** so
|
|
1026
|
+
* downstream code (e.g. the "Pick from preset" dropdown in
|
|
1027
|
+
* ProvidersTab) can identify which entries in `MODEL_METADATA` are
|
|
1028
|
+
* aliases vs primary curated ids — listing aliases would
|
|
1029
|
+
* duplicate primaries in the UI.
|
|
1030
|
+
*
|
|
1031
|
+
* **Read-only by design.** Don't mutate; if you need to add a new
|
|
1032
|
+
* aggregator alias, do it here so the merged `MODEL_METADATA`
|
|
1033
|
+
* auto-tracks the primary.
|
|
1034
|
+
*/
|
|
1035
|
+
const NAMESPACE_ALIASES = {
|
|
1036
|
+
"deepseek-ai/DeepSeek-V4-Pro": "deepseek-v4-pro",
|
|
1037
|
+
"deepseek-ai/DeepSeek-V4-Flash": "deepseek-v4-flash",
|
|
1038
|
+
"zai-org/GLM-5.2": "glm-5.2",
|
|
1039
|
+
"Qwen/Qwen3.6-35B-A3B": "Qwen3.6-35B-A3B",
|
|
1040
|
+
"moonshotai/Kimi-K2.7-Code": "kimi-k2.7-code",
|
|
1041
|
+
"deepseek/deepseek-v4-pro": "deepseek-v4-pro",
|
|
1042
|
+
"deepseek/deepseek-v4-flash": "deepseek-v4-flash",
|
|
1043
|
+
"zai/glm-5.2": "glm-5.2",
|
|
1044
|
+
"zai/glm-5.1": "glm-5.1",
|
|
1045
|
+
"moonshotai/kimi-k3": "kimi-k3",
|
|
1046
|
+
"glm-4-flashx-250414": "glm-4-flashx"
|
|
1071
1047
|
};
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
"zai/glm-5.1": "Novita (aggregator)",
|
|
1084
|
-
"moonshotai/kimi-k3": "Novita (aggregator)"
|
|
1048
|
+
const NAMESPACE_ALIAS_FAMILY = {
|
|
1049
|
+
"deepseek-ai/DeepSeek-V4-Pro": "SiliconFlow (aggregator)",
|
|
1050
|
+
"deepseek-ai/DeepSeek-V4-Flash": "SiliconFlow (aggregator)",
|
|
1051
|
+
"zai-org/GLM-5.2": "SiliconFlow (aggregator)",
|
|
1052
|
+
"Qwen/Qwen3.6-35B-A3B": "SiliconFlow (aggregator)",
|
|
1053
|
+
"moonshotai/Kimi-K2.7-Code": "SiliconFlow (aggregator)",
|
|
1054
|
+
"deepseek/deepseek-v4-pro": "Novita (aggregator)",
|
|
1055
|
+
"deepseek/deepseek-v4-flash": "Novita (aggregator)",
|
|
1056
|
+
"zai/glm-5.2": "Novita (aggregator)",
|
|
1057
|
+
"zai/glm-5.1": "Novita (aggregator)",
|
|
1058
|
+
"moonshotai/kimi-k3": "Novita (aggregator)"
|
|
1085
1059
|
};
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
merged[alias] = targetEntry;
|
|
1094
|
-
}
|
|
1095
|
-
}
|
|
1096
|
-
return Object.freeze(merged);
|
|
1060
|
+
const MODEL_METADATA = (() => {
|
|
1061
|
+
const merged = { ...PRIMARY_METADATA };
|
|
1062
|
+
for (const [alias, target] of Object.entries(NAMESPACE_ALIASES)) {
|
|
1063
|
+
const targetEntry = PRIMARY_METADATA[target];
|
|
1064
|
+
if (targetEntry !== void 0) merged[alias] = targetEntry;
|
|
1065
|
+
}
|
|
1066
|
+
return Object.freeze(merged);
|
|
1097
1067
|
})();
|
|
1068
|
+
/**
|
|
1069
|
+
* Look up curated metadata for a model by its un-qualified id
|
|
1070
|
+
* (the part after the last `::` in a qualified id, or the raw id
|
|
1071
|
+
* for a non-namespaced provider). Returns `undefined` for
|
|
1072
|
+
* user-added / `-compatible` models that have no curated entry;
|
|
1073
|
+
* callers should then fall back to whatever the user typed into
|
|
1074
|
+
* the ProvidersTab.
|
|
1075
|
+
*/
|
|
1098
1076
|
function lookupModelMetadata(modelId) {
|
|
1099
|
-
|
|
1077
|
+
return MODEL_METADATA[modelId];
|
|
1100
1078
|
}
|
|
1079
|
+
/**
|
|
1080
|
+
* Resolve the currency for a baseUrl. Strict hostname match —
|
|
1081
|
+
* `api.minimaxi.com` and `api.minimaxi.cn` map to CNY (the China
|
|
1082
|
+
* platform), `api.minimax.io` maps to USD (the global platform),
|
|
1083
|
+
* everything else falls back to USD (the global default). The
|
|
1084
|
+
* match is exact-host so a typo in the hostname never silently
|
|
1085
|
+
* flips currency.
|
|
1086
|
+
*
|
|
1087
|
+
* `api.deepseek.com` maps to CNY (2026-08-20 product decision —
|
|
1088
|
+
* user request). History: the 8/18 draft put it in CNY by mistake
|
|
1089
|
+
* (per the $ block on the English pricing page), 8/19 moved it to
|
|
1090
|
+
* the USD catch-all, and 8/20 moved it BACK to CNY deliberately —
|
|
1091
|
+
* our user base bills in ¥ on this endpoint (the official zh-cn
|
|
1092
|
+
* page publishes the ¥ table for it), and the picker should show
|
|
1093
|
+
* the price those users actually pay. The DeepSeek entries pin the
|
|
1094
|
+
* PEAK tier (conservative worst-case; off-peak is exactly half).
|
|
1095
|
+
*/
|
|
1101
1096
|
function currencyForBaseUrl(baseUrl) {
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
// don't surface it in the baseUrl dropdown, but a user
|
|
1109
|
-
// may paste it from a saved settings.json, so the
|
|
1110
|
-
// currency has to match (CNY, same as the new host).
|
|
1111
|
-
hostname === "dev.bigmodel.cn" || // DeepSeek — 2026-08-20 product decision (user request):
|
|
1112
|
-
// surface the domestic ¥ prices. The `.com` endpoint
|
|
1113
|
-
// serves both regions, but our user base bills in CNY on
|
|
1114
|
-
// it (the official zh-cn pricing page publishes the ¥
|
|
1115
|
-
// table for this endpoint), so the picker now shows the
|
|
1116
|
-
// CNY block. This REVERSES the 2026-08-19 mapping that
|
|
1117
|
-
// left it in the USD catch-all ("$ block corresponds to
|
|
1118
|
-
// the .com endpoint") — with a CNY-billed user base, the
|
|
1119
|
-
// ¥ block is the relevant one. The pricingCNY block in
|
|
1120
|
-
// MODEL_METADATA pins the PEAK tier for these entries
|
|
1121
|
-
// (conservative worst-case; see the deepseek-v4-flash
|
|
1122
|
-
// comment).
|
|
1123
|
-
hostname === "api.deepseek.com" || // SenseNova (SenseTime) token-plan gateway — CN-domiciled,
|
|
1124
|
-
// quota-billed (all-zero published pricing, so the currency is
|
|
1125
|
-
// cosmetic today, but pinning it keeps a future per-token ¥ table
|
|
1126
|
-
// from silently rendering as USD).
|
|
1127
|
-
hostname === "token.sensenova.cn") {
|
|
1128
|
-
return "CNY";
|
|
1129
|
-
}
|
|
1130
|
-
if (hostname === "api.minimax.io" || hostname === "api.minimaxi.io" || hostname === "api.siliconflow.cn" || hostname === "api.siliconflow.com" || hostname === "api.stepfun.com" || hostname === "openrouter.ai" || hostname === "api.novita.ai" || // Z.ai / Zhipu international. Billed in USD per the
|
|
1131
|
-
// official `bigmodel.cn/pricing` page (the CNY-billed
|
|
1132
|
-
// list is the China-domiciled `open.bigmodel.cn` only;
|
|
1133
|
-
// the international `api.z.ai` is USD regardless of
|
|
1134
|
-
// which apiMode / protocol path the user picked). The
|
|
1135
|
-
// GLM-for-copilot reference uses the same split
|
|
1136
|
-
// (`docs/references/GLM-for-copilot-main/src/endpoint.ts:160-173`).
|
|
1137
|
-
// Without this explicit entry, `api.z.ai` would still
|
|
1138
|
-
// resolve to USD via the catch-all below — adding it
|
|
1139
|
-
// here makes the intent grep-able and pins the host
|
|
1140
|
-
// list against accidental removal.
|
|
1141
|
-
hostname === "api.z.ai") {
|
|
1142
|
-
return "USD";
|
|
1143
|
-
}
|
|
1144
|
-
} catch {
|
|
1145
|
-
}
|
|
1146
|
-
return "USD";
|
|
1097
|
+
try {
|
|
1098
|
+
const hostname = new URL(baseUrl).hostname.toLowerCase();
|
|
1099
|
+
if (hostname === "api.minimaxi.com" || hostname === "api.minimaxi.cn" || hostname === "api.moonshot.cn" || hostname === "open.bigmodel.cn" || hostname === "dev.bigmodel.cn" || hostname === "api.deepseek.com" || hostname === "token.sensenova.cn") return "CNY";
|
|
1100
|
+
if (hostname === "api.minimax.io" || hostname === "api.minimaxi.io" || hostname === "api.siliconflow.cn" || hostname === "api.siliconflow.com" || hostname === "api.stepfun.com" || hostname === "openrouter.ai" || hostname === "api.novita.ai" || hostname === "api.z.ai") return "USD";
|
|
1101
|
+
} catch {}
|
|
1102
|
+
return "USD";
|
|
1147
1103
|
}
|
|
1148
|
-
|
|
1149
|
-
|
|
1104
|
+
//#endregion
|
|
1105
|
+
//#region src/ai/providers.presets.ts
|
|
1106
|
+
/**
|
|
1107
|
+
* Default configuration snippets shipped with named vendor types.
|
|
1108
|
+
*
|
|
1109
|
+
* When the user picks `minimax` / `deepseek` / `agnes` in the
|
|
1110
|
+
* ProvidersTab, the form pre-fills `displayName` + `baseUrl` + a
|
|
1111
|
+
* starter `models` list from this table. The user still has to
|
|
1112
|
+
* paste their own `apiKey` (always empty by default — secrets
|
|
1113
|
+
* never ship with the extension).
|
|
1114
|
+
*
|
|
1115
|
+
* Keep `baseUrl` here in sync with the upstream vendor docs.
|
|
1116
|
+
*
|
|
1117
|
+
* Adapter routing (see `apps/extension/src/services/providers/adapters/index.ts`):
|
|
1118
|
+
* - minimax: Anthropic-compatible at `https://api.minimaxi.com/anthropic`
|
|
1119
|
+
* → routed to AnthropicAdapter
|
|
1120
|
+
* - deepseek: OpenAI-compatible at `https://api.deepseek.com/v1`
|
|
1121
|
+
* → routed to OpenAIAdapter (the /anthropic surface v3 exposed
|
|
1122
|
+
* is no longer documented for v4)
|
|
1123
|
+
* - agnes: OpenAI-compatible at `https://apihub.agnes-ai.com/v1`
|
|
1124
|
+
* → routed to OpenAIAdapter
|
|
1125
|
+
* - medalsoft: OpenAI-compatible internal gateway at
|
|
1126
|
+
* `https://nexus.servicemecloud.com/v1` → routed to OpenAIAdapter
|
|
1127
|
+
* (empty starter model list — populate via "Fetch from API")
|
|
1128
|
+
*
|
|
1129
|
+
* Earlier iterations of the minimax default 401'd on the team; do
|
|
1130
|
+
* NOT swap minimax back to one of these without checking with the
|
|
1131
|
+
* user first:
|
|
1132
|
+
* - `https://agent.minimaxi.com/mavis/api/v1/llm/v1` (opencode.json
|
|
1133
|
+
* proxy URL — AnthropicAdapter would double-prefix /v1 to it)
|
|
1134
|
+
* - `https://agent.minimaxi.com/v1` (OpenAI-compat
|
|
1135
|
+
* variant — user tried this in commit 8bd867c then asked to
|
|
1136
|
+
* revert in 71faf6e-era because it 401'd as well)
|
|
1137
|
+
*/
|
|
1138
|
+
/**
|
|
1139
|
+
* Build a `ProviderModel` preset entry by joining the static
|
|
1140
|
+
* context-window numbers (token caps are user-visible and depend
|
|
1141
|
+
* on the wire-protocol spec, not the curated metadata) with the
|
|
1142
|
+
* curated metadata in `MODEL_METADATA` (detail / capabilities /
|
|
1143
|
+
* pricing / thinking). The user can override any field in the
|
|
1144
|
+
* ProvidersTab form; the curated values are just the starter
|
|
1145
|
+
* defaults so the picker shows the cost column + thinking
|
|
1146
|
+
* dropdown out of the box for the named vendors.
|
|
1147
|
+
*
|
|
1148
|
+
* Pricing block is selected to match `baseUrl`'s currency (via
|
|
1149
|
+
* `currencyForBaseUrl`): CNY for `open.bigmodel.cn` /
|
|
1150
|
+
* `api.moonshot.cn` / etc., USD for `api.z.ai` / `openrouter.ai` /
|
|
1151
|
+
* etc. Falls back to USD for unrecognised hosts. The caller is
|
|
1152
|
+
* expected to pass a real `baseUrl` — when adding a brand-new
|
|
1153
|
+
* provider the host is already known (came from the
|
|
1154
|
+
* `PROVIDER_BASE_URL_PRESETS` dropdown or user-typed); the empty
|
|
1155
|
+
* `""` default picks USD as a safe fallback.
|
|
1156
|
+
*/
|
|
1150
1157
|
function buildPresetModel(id, displayName, baseUrl) {
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
pricing: meta[`pricing${currency}`],
|
|
1169
|
-
priceCategory: meta.priceCategory,
|
|
1170
|
-
thinkingSchema: meta.thinkingSchema ?? "none"
|
|
1171
|
-
};
|
|
1158
|
+
const meta = MODEL_METADATA[id];
|
|
1159
|
+
if (!meta) throw new Error(`buildPresetModel: no curated MODEL_METADATA entry for '${id}' — add one before referencing it from BUILTIN_PROVIDER_PRESETS.`);
|
|
1160
|
+
const currency = baseUrl ? currencyForBaseUrl(baseUrl) : "USD";
|
|
1161
|
+
return {
|
|
1162
|
+
id,
|
|
1163
|
+
displayName,
|
|
1164
|
+
maxInputTokens: meta.maxInputTokens,
|
|
1165
|
+
maxOutputTokens: meta.maxOutputTokens,
|
|
1166
|
+
detail: meta.detail,
|
|
1167
|
+
capabilities: {
|
|
1168
|
+
supportsImageToText: meta.imageInput,
|
|
1169
|
+
supportsToolCalling: meta.toolCalling
|
|
1170
|
+
},
|
|
1171
|
+
pricing: meta[`pricing${currency}`],
|
|
1172
|
+
priceCategory: meta.priceCategory,
|
|
1173
|
+
thinkingSchema: meta.thinkingSchema ?? "none"
|
|
1174
|
+
};
|
|
1172
1175
|
}
|
|
1176
|
+
/**
|
|
1177
|
+
* Union a model row returned by the "Fetch from API" flow with its
|
|
1178
|
+
* curated preset metadata, when the id matches an entry in
|
|
1179
|
+
* `MODEL_METADATA`. The API typically returns just `id` (sometimes
|
|
1180
|
+
* `displayName`); the user expects the picker's `detail` / `pricing`
|
|
1181
|
+
* / `capabilities` / `maxInputTokens` columns to be filled in for
|
|
1182
|
+
* any model we curate, not blank.
|
|
1183
|
+
*
|
|
1184
|
+
* Precedence (TDD-pinned 2026-08-19, see
|
|
1185
|
+
* `test/unionProviderModelWithPreset.test.mjs`):
|
|
1186
|
+
*
|
|
1187
|
+
* • **Preset wins** for the curated fields: `detail`,
|
|
1188
|
+
* `capabilities`, `pricing`, `priceCategory`, `thinkingSchema`,
|
|
1189
|
+
* `maxInputTokens`, `maxOutputTokens`. These are the values we
|
|
1190
|
+
* maintain by hand and trust more than what the API publishes,
|
|
1191
|
+
* which is often missing or stale (e.g. the OpenAI `/v1/models`
|
|
1192
|
+
* endpoint does not return pricing or token caps; DeepSeek's
|
|
1193
|
+
* `GET /models` likewise returns id + owned_by only).
|
|
1194
|
+
* • **Fetched wins** for `id` (the API is the source of truth for
|
|
1195
|
+
* what the endpoint actually exposes — a stale preset could list
|
|
1196
|
+
* a model the user no longer has access to).
|
|
1197
|
+
* • **`displayName`** — the 2026-08-19 follow-up: fetched wins
|
|
1198
|
+
* when set, the preset's curated `displayName` (from
|
|
1199
|
+
* `BUILTIN_PROVIDER_PRESETS`, see
|
|
1200
|
+
* `getPresetModelDisplayName`) fills in when fetched omits it
|
|
1201
|
+
* or sends an empty string. Without this fallback, the picker
|
|
1202
|
+
* would render the bare id (`deepseek-v4-flash`) as the model
|
|
1203
|
+
* name right after the fetch result lands — the user picked
|
|
1204
|
+
* "Fetch from API" because they wanted the live catalog, but
|
|
1205
|
+
* the friendly label they would have seen if they'd picked
|
|
1206
|
+
* the vendor from the named-vendor `<select>` should also
|
|
1207
|
+
* surface here. Empty string is treated the same as missing
|
|
1208
|
+
* (a blank label in the picker is strictly worse than the
|
|
1209
|
+
* curated friendly name).
|
|
1210
|
+
* • **No preset match** → returns the fetched model unchanged
|
|
1211
|
+
* (custom / aggregator-only models stay bare; the user fills
|
|
1212
|
+
* in detail / pricing by hand).
|
|
1213
|
+
*
|
|
1214
|
+
* `baseUrl` is consulted to pick the right currency for `pricing`
|
|
1215
|
+
* (CNY for `open.bigmodel.cn` / `api.moonshot.cn` / etc., USD
|
|
1216
|
+
* otherwise — see `currencyForBaseUrl`). Pass the form's current
|
|
1217
|
+
* `editing.baseUrl`; that is the same baseUrl the row was just
|
|
1218
|
+
* fetched from, so the resolved currency matches what the user
|
|
1219
|
+
* will see in the picker. Empty / unset baseUrl → USD fallback.
|
|
1220
|
+
*
|
|
1221
|
+
* Namespaced ids (e.g. `deepseek-ai/DeepSeek-V4-Pro` on
|
|
1222
|
+
* SiliconFlow) resolve transparently — `MODEL_METADATA` aliases
|
|
1223
|
+
* the primary entry's metadata reference under every namespace key,
|
|
1224
|
+
* so `lookupModelMetadata("deepseek-ai/DeepSeek-V4-Pro")` returns
|
|
1225
|
+
* the same object as `lookupModelMetadata("deepseek-v4-pro")`.
|
|
1226
|
+
*/
|
|
1173
1227
|
function unionProviderModelWithPreset(fetched, baseUrl) {
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
pricing: meta[`pricing${currency}`],
|
|
1194
|
-
priceCategory: meta.priceCategory,
|
|
1195
|
-
// thinkingSchema: prefer preset (curated), fall back to
|
|
1196
|
-
// fetched (in case the API publishes a schema the
|
|
1197
|
-
// preset doesn't know about), then "none" to match
|
|
1198
|
-
// `buildPresetModel`'s explicit-none convention so the
|
|
1199
|
-
// picker renders identically for preset vs fetched rows.
|
|
1200
|
-
thinkingSchema: meta.thinkingSchema ?? fetched.thinkingSchema ?? "none",
|
|
1201
|
-
// Token caps: preset is curated; if the preset doesn't
|
|
1202
|
-
// publish a cap, keep whatever fetched supplied (the
|
|
1203
|
-
// user may have entered it by hand earlier).
|
|
1204
|
-
maxInputTokens: meta.maxInputTokens ?? fetched.maxInputTokens,
|
|
1205
|
-
maxOutputTokens: meta.maxOutputTokens ?? fetched.maxOutputTokens
|
|
1206
|
-
};
|
|
1228
|
+
const meta = MODEL_METADATA[fetched.id];
|
|
1229
|
+
if (!meta) return fetched;
|
|
1230
|
+
const currency = baseUrl ? currencyForBaseUrl(baseUrl) : "USD";
|
|
1231
|
+
const presetDisplayName = getPresetModelDisplayName(fetched.id);
|
|
1232
|
+
const displayName = typeof fetched.displayName === "string" && fetched.displayName.length > 0 ? fetched.displayName : presetDisplayName;
|
|
1233
|
+
return {
|
|
1234
|
+
...fetched,
|
|
1235
|
+
displayName,
|
|
1236
|
+
detail: meta.detail,
|
|
1237
|
+
capabilities: {
|
|
1238
|
+
supportsImageToText: meta.imageInput,
|
|
1239
|
+
supportsToolCalling: meta.toolCalling
|
|
1240
|
+
},
|
|
1241
|
+
pricing: meta[`pricing${currency}`],
|
|
1242
|
+
priceCategory: meta.priceCategory,
|
|
1243
|
+
thinkingSchema: meta.thinkingSchema ?? fetched.thinkingSchema ?? "none",
|
|
1244
|
+
maxInputTokens: meta.maxInputTokens ?? fetched.maxInputTokens,
|
|
1245
|
+
maxOutputTokens: meta.maxOutputTokens ?? fetched.maxOutputTokens
|
|
1246
|
+
};
|
|
1207
1247
|
}
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1248
|
+
/**
|
|
1249
|
+
* The list of vendor family groups shown in the preset dropdown.
|
|
1250
|
+
* Order is intentional (most common presets first):
|
|
1251
|
+
* 1. GLM (Zhipu / 智谱) — the user explicitly asked us to
|
|
1252
|
+
* support 4-channel endpoints; GLM has the richest preset
|
|
1253
|
+
* list (15+ models) so it gets the top slot.
|
|
1254
|
+
* 2. DeepSeek / Kimi / StepFun / MiniMax — the other named
|
|
1255
|
+
* vendors with curated presets.
|
|
1256
|
+
* 3. Agnes / Qwen — the smaller curated lists.
|
|
1257
|
+
* 4. Aggregators (SiliconFlow / Novita / OpenRouter) — listed
|
|
1258
|
+
* last because users on aggregators usually type the namespaced
|
|
1259
|
+
* id by hand rather than reach for a curated dropdown.
|
|
1260
|
+
*/
|
|
1261
|
+
const PRESET_MODEL_FAMILIES = [
|
|
1262
|
+
"GLM",
|
|
1263
|
+
"DeepSeek",
|
|
1264
|
+
"Kimi",
|
|
1265
|
+
"StepFun",
|
|
1266
|
+
"MiniMax",
|
|
1267
|
+
"Agnes",
|
|
1268
|
+
"SenseNova",
|
|
1269
|
+
"Qwen",
|
|
1270
|
+
"SiliconFlow (aggregator)",
|
|
1271
|
+
"Novita (aggregator)",
|
|
1272
|
+
"OpenRouter (aggregator)"
|
|
1220
1273
|
];
|
|
1274
|
+
/**
|
|
1275
|
+
* Map a `MODEL_METADATA` id to its vendor family for dropdown
|
|
1276
|
+
* grouping. Pure data — keeps the dropdown order in one place.
|
|
1277
|
+
*
|
|
1278
|
+
* Namespaced alias ids (the SiliconFlow `deepseek-ai/…` /
|
|
1279
|
+
* `zai-org/…` / `Qwen/Qwen*` / `moonshotai/…` cluster, the Novita
|
|
1280
|
+
* `zai/…` / `deepseek/…` cluster, and the historical
|
|
1281
|
+
* `moonshotai/kimi-k3`) consult `NAMESPACE_ALIAS_FAMILY` first so
|
|
1282
|
+
* they land in the matching aggregator group instead of being
|
|
1283
|
+
* filtered out as aliases. The primary entries fall through to the
|
|
1284
|
+
* prefix-based rules below.
|
|
1285
|
+
*/
|
|
1221
1286
|
function vendorFamilyForId(id) {
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1287
|
+
const aliasFamily = NAMESPACE_ALIAS_FAMILY[id];
|
|
1288
|
+
if (aliasFamily) return aliasFamily;
|
|
1289
|
+
if (id.startsWith("glm-")) return "GLM";
|
|
1290
|
+
if (id.startsWith("deepseek-")) return "DeepSeek";
|
|
1291
|
+
if (id.startsWith("kimi-")) return "Kimi";
|
|
1292
|
+
if (id.startsWith("step-")) return "StepFun";
|
|
1293
|
+
if (id.startsWith("MiniMax-")) return "MiniMax";
|
|
1294
|
+
if (id.startsWith("agnes-")) return "Agnes";
|
|
1295
|
+
if (id.startsWith("sensenova-")) return "SenseNova";
|
|
1296
|
+
if (id.startsWith("Qwen")) return "Qwen";
|
|
1297
|
+
if (id.startsWith("openrouter/")) return "OpenRouter (aggregator)";
|
|
1298
|
+
return "Other";
|
|
1234
1299
|
}
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1300
|
+
/**
|
|
1301
|
+
* The "Pick from preset" dropdown options — every curated primary
|
|
1302
|
+
* entry in `MODEL_METADATA` PLUS every alias tagged with an
|
|
1303
|
+
* aggregator family in `NAMESPACE_ALIAS_FAMILY`. Grouped by
|
|
1304
|
+
* vendor family. Sorted alphabetically within each family so the
|
|
1305
|
+
* dropdown order is stable across runs.
|
|
1306
|
+
*
|
|
1307
|
+
* Two alias flavours exist in `NAMESPACE_ALIASES`:
|
|
1308
|
+
* - **Aggregator aliases** (SiliconFlow `deepseek-ai/…`,
|
|
1309
|
+
* Novita `zai/…` / `deepseek/…`, etc.) — KEPT in the list so
|
|
1310
|
+
* the dropdown surfaces the namespaced ids aggregator users
|
|
1311
|
+
* actually need to type. They are routed to the matching
|
|
1312
|
+
* aggregator <optgroup> via `NAMESPACE_ALIAS_FAMILY`.
|
|
1313
|
+
* - **Historical / naming aliases** (e.g. the Zhipu
|
|
1314
|
+
* `glm-4-flashx-250414` rebrand of `glm-4-flashx`) — DROPPED
|
|
1315
|
+
* because they're duplicates of an existing primary entry
|
|
1316
|
+
* that already appears in the dropdown. Users with the
|
|
1317
|
+
* historical id already in their settings.json keep
|
|
1318
|
+
* working at the chat-registration layer
|
|
1319
|
+
* (see `MODEL_METADATA`'s alias merge) — the dropdown just
|
|
1320
|
+
* doesn't surface a redundant second option.
|
|
1321
|
+
*
|
|
1322
|
+
* The filter rule is the inverse of the aggregator tag presence:
|
|
1323
|
+
* any alias with a `NAMESPACE_ALIAS_FAMILY` entry is kept, every
|
|
1324
|
+
* other alias is filtered. The pinning test
|
|
1325
|
+
* `test/listPresetModelGroups.test.mjs` asserts this 1:1 mapping
|
|
1326
|
+
* between the two structures.
|
|
1327
|
+
*
|
|
1328
|
+
* Used by `ProvidersTab.tsx` to render the `<select>` next to the
|
|
1329
|
+
* "+ Add model" button. Selecting an option calls
|
|
1330
|
+
* `buildPresetModel(id, displayName, baseUrl)` and appends the
|
|
1331
|
+
* resulting `ProviderModel` to the editing list. The `displayName`
|
|
1332
|
+
* mirrors the model id verbatim (the canonical form is what users
|
|
1333
|
+
* see in /v1/models, what VSCode's chat picker surfaces, and what
|
|
1334
|
+
* the existing `BUILTIN_PROVIDER_PRESETS` pre-fills); the user can
|
|
1335
|
+
* still rename the field after picking — this is just a starter
|
|
1336
|
+
* label.
|
|
1337
|
+
*/
|
|
1338
|
+
const LISTABLE_PRESET_MODELS = (() => {
|
|
1339
|
+
const aggregatorAliasKeys = new Set(Object.keys(NAMESPACE_ALIAS_FAMILY));
|
|
1340
|
+
return Object.keys(MODEL_METADATA).filter((id) => {
|
|
1341
|
+
if (NAMESPACE_ALIASES[id] === void 0) return true;
|
|
1342
|
+
return aggregatorAliasKeys.has(id);
|
|
1343
|
+
}).sort().map((id) => ({
|
|
1344
|
+
id,
|
|
1345
|
+
displayName: id,
|
|
1346
|
+
vendorFamily: vendorFamilyForId(id)
|
|
1347
|
+
}));
|
|
1246
1348
|
})();
|
|
1247
1349
|
function listPresetModelGroups() {
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1350
|
+
const byFamily = /* @__PURE__ */ new Map();
|
|
1351
|
+
for (const entry of LISTABLE_PRESET_MODELS) {
|
|
1352
|
+
const bucket = byFamily.get(entry.vendorFamily) ?? [];
|
|
1353
|
+
bucket.push(entry);
|
|
1354
|
+
byFamily.set(entry.vendorFamily, bucket);
|
|
1355
|
+
}
|
|
1356
|
+
return PRESET_MODEL_FAMILIES.filter((f) => byFamily.has(f)).map((family) => ({
|
|
1357
|
+
family,
|
|
1358
|
+
entries: byFamily.get(family) ?? []
|
|
1359
|
+
}));
|
|
1258
1360
|
}
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
// GLM-5.1 served via TileRT; still
|
|
1378
|
-
// listed in some third-party mirrors
|
|
1379
|
-
// (Alibaba Cloud DashScope) but not
|
|
1380
|
-
// on Zhipu's own /v1/models.
|
|
1381
|
-
// glm-5v-turbo — multimodal coding base; only
|
|
1382
|
-
// reachable via the dedicated
|
|
1383
|
-
// multimodal endpoint, not
|
|
1384
|
-
// /v1/chat/completions.
|
|
1385
|
-
// glm-4.7-flash — free-tier 4.7 lite; advertised on
|
|
1386
|
-
// the docs pricing page but absent
|
|
1387
|
-
// from /v1/models.
|
|
1388
|
-
// glm-4.7-flashx — quick-response 4.7; same situation
|
|
1389
|
-
// as glm-4.7-flash.
|
|
1390
|
-
// glm-4.5v — multimodal 4.5; only on the
|
|
1391
|
-
// dedicated VLM endpoint.
|
|
1392
|
-
// glm-4.5-airx — quick-response 4.5 Air; not in
|
|
1393
|
-
// /v1/models anymore.
|
|
1394
|
-
// glm-4-long — 1M-context 4-Long; the `/long`
|
|
1395
|
-
// path was retired in 2026 H1.
|
|
1396
|
-
// glm-4-flashx — quick-response 4 FlashX; the
|
|
1397
|
-
// `-250414` dated alias (see
|
|
1398
|
-
// `NAMESPACE_ALIASES` in
|
|
1399
|
-
// `providers.metadata.ts`) is the
|
|
1400
|
-
// only spelling still exposed.
|
|
1401
|
-
//
|
|
1402
|
-
// Note: `glm-4.5` (no suffix) IS in the preset now. It is
|
|
1403
|
-
// NOT listed on the public "模型概览" page but it IS
|
|
1404
|
-
// returned by /v1/models — almost certainly a legacy alias
|
|
1405
|
-
// that routes to one of the suffixed 4.5 variants. The
|
|
1406
|
-
// curated metadata entry marks it as such; users on a
|
|
1407
|
-
// private coding-plan endpoint that distinguishes `glm-4.5`
|
|
1408
|
-
// from `glm-4.5-air` should override the model id in the
|
|
1409
|
-
// ProvidersTab.
|
|
1410
|
-
//
|
|
1411
|
-
// Earlier (also 2026-08-18) trim — `glm-4-plus` and
|
|
1412
|
-
// `glm-3-turbo` were removed from the preset on the same
|
|
1413
|
-
// date. Both are no longer listed in Zhipu's public
|
|
1414
|
-
// "模型一览": `GLM-4-0520` is in the "即将弃用模型" list
|
|
1415
|
-
// and `GLM-3-Turbo` has been retired without a formal
|
|
1416
|
-
// redirect. Their API endpoints may still respond for
|
|
1417
|
-
// legacy accounts (the `glm-4-plus` 429 "余额不足" log we
|
|
1418
|
-
// saw on 2026-08-18 is one such case), but they shouldn't
|
|
1419
|
-
// be the default starter pick for a freshly added Zhipu
|
|
1420
|
-
// provider. Users with a paid legacy plan that still works
|
|
1421
|
-
// can add the id back by hand in the ProvidersTab; the
|
|
1422
|
-
// `MODEL_METADATA` entries are kept so the id is still
|
|
1423
|
-
// resolvable for the curated detail / pricing columns.
|
|
1424
|
-
models: [
|
|
1425
|
-
buildPresetModel("glm-5.3", "GLM-5.3", "https://open.bigmodel.cn/api/paas/v4"),
|
|
1426
|
-
// GLM-5.3-Flash (2026-08-26) — GLM-5 系列首个原生多模态
|
|
1427
|
-
// 模型,性价比高(¥0.4/¥1.4 per 1M tokens),接入 GLM Coding Plan
|
|
1428
|
-
// 后额度较 GLM-5.3 翻 3 倍。放在 5.3 后面,符合
|
|
1429
|
-
// 旗舰/快速版成对的展示惯例。
|
|
1430
|
-
buildPresetModel("glm-5.3-flash", "GLM-5.3 Flash", "https://open.bigmodel.cn/api/paas/v4"),
|
|
1431
|
-
buildPresetModel("glm-5.2", "GLM-5.2", "https://open.bigmodel.cn/api/paas/v4"),
|
|
1432
|
-
buildPresetModel("glm-5.1", "GLM-5.1", "https://open.bigmodel.cn/api/paas/v4"),
|
|
1433
|
-
buildPresetModel("glm-5", "GLM-5", "https://open.bigmodel.cn/api/paas/v4"),
|
|
1434
|
-
buildPresetModel("glm-5-turbo", "GLM-5 Turbo", "https://open.bigmodel.cn/api/paas/v4"),
|
|
1435
|
-
buildPresetModel("glm-4.7", "GLM-4.7", "https://open.bigmodel.cn/api/paas/v4"),
|
|
1436
|
-
buildPresetModel("glm-4.6", "GLM-4.6", "https://open.bigmodel.cn/api/paas/v4"),
|
|
1437
|
-
buildPresetModel("glm-4.5-air", "GLM-4.5 Air", "https://open.bigmodel.cn/api/paas/v4"),
|
|
1438
|
-
buildPresetModel("glm-4.5", "GLM-4.5", "https://open.bigmodel.cn/api/paas/v4")
|
|
1439
|
-
]
|
|
1440
|
-
},
|
|
1441
|
-
stepfun: {
|
|
1442
|
-
displayName: "StepFun",
|
|
1443
|
-
baseUrl: "https://api.stepfun.com/v1",
|
|
1444
|
-
models: [
|
|
1445
|
-
buildPresetModel("step-3.7-flash", "Step 3.7 Flash", "https://api.stepfun.com/v1"),
|
|
1446
|
-
buildPresetModel("step-3.5-flash", "Step 3.5 Flash", "https://api.stepfun.com/v1"),
|
|
1447
|
-
buildPresetModel(
|
|
1448
|
-
"step-1o-turbo-vision",
|
|
1449
|
-
"Step 1o Turbo Vision",
|
|
1450
|
-
"https://api.stepfun.com/v1"
|
|
1451
|
-
)
|
|
1452
|
-
]
|
|
1453
|
-
},
|
|
1454
|
-
siliconflow: {
|
|
1455
|
-
displayName: "SiliconFlow",
|
|
1456
|
-
baseUrl: "https://api.siliconflow.cn/v1",
|
|
1457
|
-
// 聚合平台 — 模型列表由平台动态维护(>100 个)。这里列的
|
|
1458
|
-
// 6 个是 2026 年 7-8 月各家最新的旗舰/代表型号,给 ProvidersTab
|
|
1459
|
-
// 一个 "一眼能看到" 的起点;用户添加 provider 后可继续通过
|
|
1460
|
-
// `/v1/models` 拉取完整列表。
|
|
1461
|
-
// 这里的 id 是 SiliconFlow API 用的 namespaced 字符串,必须
|
|
1462
|
-
// 与 MODEL_METADATA 的 alias 严格一致。
|
|
1463
|
-
models: [
|
|
1464
|
-
// DeepSeek V4 系列 (2026-04)
|
|
1465
|
-
buildPresetModel(
|
|
1466
|
-
"deepseek-ai/DeepSeek-V4-Pro",
|
|
1467
|
-
"DeepSeek V4 Pro (via SiliconFlow)",
|
|
1468
|
-
"https://api.siliconflow.cn/v1"
|
|
1469
|
-
),
|
|
1470
|
-
buildPresetModel(
|
|
1471
|
-
"deepseek-ai/DeepSeek-V4-Flash",
|
|
1472
|
-
"DeepSeek V4 Flash (via SiliconFlow)",
|
|
1473
|
-
"https://api.siliconflow.cn/v1"
|
|
1474
|
-
),
|
|
1475
|
-
// GLM-5.2 (2026-06-17) — open-weight 编程旗舰,1M context
|
|
1476
|
-
buildPresetModel(
|
|
1477
|
-
"zai-org/GLM-5.2",
|
|
1478
|
-
"GLM-5.2 (via SiliconFlow)",
|
|
1479
|
-
"https://api.siliconflow.cn/v1"
|
|
1480
|
-
),
|
|
1481
|
-
// Qwen3.6-35B-A3B (2026-04) — 35B MoE, 3B 激活,"小而强"
|
|
1482
|
-
buildPresetModel(
|
|
1483
|
-
"Qwen/Qwen3.6-35B-A3B",
|
|
1484
|
-
"Qwen3.6-35B-A3B (via SiliconFlow)",
|
|
1485
|
-
"https://api.siliconflow.cn/v1"
|
|
1486
|
-
),
|
|
1487
|
-
// Kimi K2.7-Code (2026-06-12) — Moonshot coding 旗舰
|
|
1488
|
-
buildPresetModel(
|
|
1489
|
-
"moonshotai/Kimi-K2.7-Code",
|
|
1490
|
-
"Kimi K2.7 Code (via SiliconFlow)",
|
|
1491
|
-
"https://api.siliconflow.cn/v1"
|
|
1492
|
-
)
|
|
1493
|
-
]
|
|
1494
|
-
},
|
|
1495
|
-
openrouter: {
|
|
1496
|
-
displayName: "OpenRouter",
|
|
1497
|
-
baseUrl: "https://openrouter.ai/api/v1",
|
|
1498
|
-
models: [
|
|
1499
|
-
buildPresetModel("openrouter/auto", "OpenRouter Auto", "https://openrouter.ai/api/v1")
|
|
1500
|
-
]
|
|
1501
|
-
},
|
|
1502
|
-
novita: {
|
|
1503
|
-
displayName: "Novita",
|
|
1504
|
-
baseUrl: "https://api.novita.ai/openai/v1",
|
|
1505
|
-
// 聚合平台:Novita 本身不产出自有模型,只是把 Kimi K3 / GLM 5.x /
|
|
1506
|
-
// DeepSeek V4 等第三方开源或授权模型挂到统一 OpenAI 兼容网关下
|
|
1507
|
-
// (见 https://novita.ai/llm-api)。这里列 5 个 2026 旗舰作为
|
|
1508
|
-
// preset 起点;用户添加 provider 后可继续通过 `/v1/models` 拉取
|
|
1509
|
-
// 完整列表。id 严格匹配 Novita API 的 namespaced 字符串。
|
|
1510
|
-
models: [
|
|
1511
|
-
// DeepSeek V4 系列 (2026-04)
|
|
1512
|
-
buildPresetModel(
|
|
1513
|
-
"deepseek/deepseek-v4-pro",
|
|
1514
|
-
"DeepSeek V4 Pro (via Novita)",
|
|
1515
|
-
"https://api.novita.ai/openai/v1"
|
|
1516
|
-
),
|
|
1517
|
-
buildPresetModel(
|
|
1518
|
-
"deepseek/deepseek-v4-flash",
|
|
1519
|
-
"DeepSeek V4 Flash (via Novita)",
|
|
1520
|
-
"https://api.novita.ai/openai/v1"
|
|
1521
|
-
),
|
|
1522
|
-
// GLM-5 系列 (2026-04/06)
|
|
1523
|
-
buildPresetModel("zai/glm-5.2", "GLM-5.2 (via Novita)", "https://api.novita.ai/openai/v1"),
|
|
1524
|
-
buildPresetModel("zai/glm-5.1", "GLM-5.1 (via Novita)", "https://api.novita.ai/openai/v1"),
|
|
1525
|
-
// Kimi K3 (2026-07-16 API, 2026-07-27 开源) — 1M context, 2.8T MoE
|
|
1526
|
-
buildPresetModel(
|
|
1527
|
-
"moonshotai/kimi-k3",
|
|
1528
|
-
"Kimi K3 (via Novita)",
|
|
1529
|
-
"https://api.novita.ai/openai/v1"
|
|
1530
|
-
)
|
|
1531
|
-
]
|
|
1532
|
-
},
|
|
1533
|
-
medalsoft: {
|
|
1534
|
-
displayName: "Medalsoft",
|
|
1535
|
-
// Medalsoft internal LLM gateway (公司内部代理) — OpenAI-compatible
|
|
1536
|
-
// `/v1/chat/completions`. The gateway forwards to upstream vendors
|
|
1537
|
-
// (GLM / DeepSeek / Kimi / ...), so the model catalogue is dynamic
|
|
1538
|
-
// and NOT curated here: the preset ships an EMPTY starter list and
|
|
1539
|
-
// the user populates it via "Fetch from API" (`GET /v1/models`) in
|
|
1540
|
-
// the ProvidersTab. `buildPresetModel`'s fail-loudly contract is
|
|
1541
|
-
// why we don't guess ids — a curated `MODEL_METADATA` entry only
|
|
1542
|
-
// exists for vendor-native ids, not the gateway's routing table.
|
|
1543
|
-
//
|
|
1544
|
-
// NOTE: models fetched from the gateway carry the upstream model
|
|
1545
|
-
// ids, so the curated metadata (pricing / thinking dropdown /
|
|
1546
|
-
// token caps) still resolves via `MODEL_METADATA` after the fetch.
|
|
1547
|
-
baseUrl: "https://nexus.servicemecloud.com/v1",
|
|
1548
|
-
models: []
|
|
1549
|
-
},
|
|
1550
|
-
sensenova: {
|
|
1551
|
-
displayName: "SenseNova",
|
|
1552
|
-
// SenseTime's SenseNova OpenAI-compatible gateway (see
|
|
1553
|
-
// https://platform.sensenova.cn/docs — `POST /v1/chat/completions`,
|
|
1554
|
-
// `Authorization: Bearer`, `GET /v1/models`). Token-plan quota per
|
|
1555
|
-
// model (e.g. 1500 req / 5h for flash-lite) is enforced upstream.
|
|
1556
|
-
//
|
|
1557
|
-
// The starter list mirrors the live `GET /v1/models` response
|
|
1558
|
-
// (fetched 2026-08-25 with a real key). The image-generation
|
|
1559
|
-
// models (sensenova-u1.5-lite / sensenova-u1-fast) live on
|
|
1560
|
-
// /v1/images/* endpoints and are explicitly documented as NOT
|
|
1561
|
-
// usable as chat Model IDs, so they are not curated here.
|
|
1562
|
-
// `sensenova-6.7-flash-lite` is likewise excluded — the docs
|
|
1563
|
-
// state it is a compat alias whose calls auto-redirect to
|
|
1564
|
-
// `sensenova-6.8-flash-lite` (through 2026-08-31).
|
|
1565
|
-
//
|
|
1566
|
-
// The relayed deepseek-v4-flash / glm-5.2 rows reuse their
|
|
1567
|
-
// MODEL_METADATA entries (keyed on the un-qualified id) and then
|
|
1568
|
-
// OVERRIDE the gateway-published specifics below: SenseNova
|
|
1569
|
-
// serves them with a 1M context window and TEXT-ONLY input
|
|
1570
|
-
// (per the live /v1/models payload), which differs from the
|
|
1571
|
-
// vendor-official curated values (655K input / vision on).
|
|
1572
|
-
// The global MODEL_METADATA stays vendor-official; only this
|
|
1573
|
-
// preset carries the gateway-specific caps so other providers
|
|
1574
|
-
// (DeepSeek official / Zhipu) are unaffected.
|
|
1575
|
-
baseUrl: "https://token.sensenova.cn/v1",
|
|
1576
|
-
models: [
|
|
1577
|
-
buildPresetModel(
|
|
1578
|
-
"sensenova-6.8-flash-lite",
|
|
1579
|
-
"SenseNova 6.8 Flash-Lite",
|
|
1580
|
-
"https://token.sensenova.cn/v1"
|
|
1581
|
-
),
|
|
1582
|
-
{
|
|
1583
|
-
// Live /v1/models: context_length 1048576,
|
|
1584
|
-
// max_output_length 65536, input_modalities ["text"] only.
|
|
1585
|
-
...buildPresetModel(
|
|
1586
|
-
"deepseek-v4-flash",
|
|
1587
|
-
"DeepSeek V4 Flash (via SenseNova)",
|
|
1588
|
-
"https://token.sensenova.cn/v1"
|
|
1589
|
-
),
|
|
1590
|
-
maxInputTokens: 1048576,
|
|
1591
|
-
maxOutputTokens: 65536,
|
|
1592
|
-
capabilities: { supportsImageToText: false, supportsToolCalling: true }
|
|
1593
|
-
},
|
|
1594
|
-
{
|
|
1595
|
-
// Live /v1/models: context_length 1048576,
|
|
1596
|
-
// max_output_length 131072, input_modalities ["text"] only.
|
|
1597
|
-
...buildPresetModel("glm-5.2", "GLM-5.2 (via SenseNova)", "https://token.sensenova.cn/v1"),
|
|
1598
|
-
maxInputTokens: 1048576,
|
|
1599
|
-
maxOutputTokens: 131072,
|
|
1600
|
-
capabilities: { supportsImageToText: false, supportsToolCalling: true }
|
|
1601
|
-
}
|
|
1602
|
-
]
|
|
1603
|
-
}
|
|
1361
|
+
const BUILTIN_PROVIDER_PRESETS = {
|
|
1362
|
+
minimax: {
|
|
1363
|
+
displayName: "MiniMax",
|
|
1364
|
+
baseUrl: "https://api.minimaxi.com/anthropic",
|
|
1365
|
+
models: [
|
|
1366
|
+
buildPresetModel("MiniMax-M3", "MiniMax-M3", "https://api.minimaxi.com/anthropic"),
|
|
1367
|
+
buildPresetModel("MiniMax-M2.7", "MiniMax-M2.7", "https://api.minimaxi.com/anthropic"),
|
|
1368
|
+
buildPresetModel("MiniMax-M2.7-highspeed", "MiniMax-M2.7-highspeed", "https://api.minimaxi.com/anthropic"),
|
|
1369
|
+
buildPresetModel("MiniMax-M2.5", "MiniMax-M2.5", "https://api.minimaxi.com/anthropic")
|
|
1370
|
+
]
|
|
1371
|
+
},
|
|
1372
|
+
deepseek: {
|
|
1373
|
+
displayName: "DeepSeek",
|
|
1374
|
+
baseUrl: "https://api.deepseek.com/v1",
|
|
1375
|
+
models: [buildPresetModel("deepseek-v4-flash", "DeepSeek V4 Flash", "https://api.deepseek.com/v1"), buildPresetModel("deepseek-v4-pro", "DeepSeek V4 Pro", "https://api.deepseek.com/v1")]
|
|
1376
|
+
},
|
|
1377
|
+
agnes: {
|
|
1378
|
+
displayName: "Agnes",
|
|
1379
|
+
baseUrl: "https://apihub.agnes-ai.com/v1",
|
|
1380
|
+
models: [
|
|
1381
|
+
buildPresetModel("agnes-2.5-pro", "Agnes 2.5 Pro", "https://apihub.agnes-ai.com/v1"),
|
|
1382
|
+
buildPresetModel("agnes-2.5-pro-alpha", "Agnes 2.5 Pro Alpha", "https://apihub.agnes-ai.com/v1"),
|
|
1383
|
+
buildPresetModel("agnes-2.5-flash", "Agnes 2.5 Flash", "https://apihub.agnes-ai.com/v1"),
|
|
1384
|
+
buildPresetModel("agnes-2.0-flash", "Agnes 2.0 Flash", "https://apihub.agnes-ai.com/v1")
|
|
1385
|
+
]
|
|
1386
|
+
},
|
|
1387
|
+
kimi: {
|
|
1388
|
+
displayName: "Kimi",
|
|
1389
|
+
baseUrl: "https://api.moonshot.cn/v1",
|
|
1390
|
+
models: [
|
|
1391
|
+
buildPresetModel("kimi-k3", "Kimi K3", "https://api.moonshot.cn/v1"),
|
|
1392
|
+
buildPresetModel("kimi-k2.7-code", "Kimi K2.7 Code", "https://api.moonshot.cn/v1"),
|
|
1393
|
+
buildPresetModel("kimi-k2.7-code-highspeed", "Kimi K2.7 Code HighSpeed", "https://api.moonshot.cn/v1"),
|
|
1394
|
+
buildPresetModel("kimi-k2.6", "Kimi K2.6", "https://api.moonshot.cn/v1")
|
|
1395
|
+
]
|
|
1396
|
+
},
|
|
1397
|
+
zhipu: {
|
|
1398
|
+
displayName: "Zhipu",
|
|
1399
|
+
baseUrl: "https://open.bigmodel.cn/api/paas/v4",
|
|
1400
|
+
models: [
|
|
1401
|
+
buildPresetModel("glm-5.3", "GLM-5.3", "https://open.bigmodel.cn/api/paas/v4"),
|
|
1402
|
+
buildPresetModel("glm-5.3-flash", "GLM-5.3 Flash", "https://open.bigmodel.cn/api/paas/v4"),
|
|
1403
|
+
buildPresetModel("glm-5.2", "GLM-5.2", "https://open.bigmodel.cn/api/paas/v4"),
|
|
1404
|
+
buildPresetModel("glm-5.1", "GLM-5.1", "https://open.bigmodel.cn/api/paas/v4"),
|
|
1405
|
+
buildPresetModel("glm-5", "GLM-5", "https://open.bigmodel.cn/api/paas/v4"),
|
|
1406
|
+
buildPresetModel("glm-5-turbo", "GLM-5 Turbo", "https://open.bigmodel.cn/api/paas/v4"),
|
|
1407
|
+
buildPresetModel("glm-4.7", "GLM-4.7", "https://open.bigmodel.cn/api/paas/v4"),
|
|
1408
|
+
buildPresetModel("glm-4.6", "GLM-4.6", "https://open.bigmodel.cn/api/paas/v4"),
|
|
1409
|
+
buildPresetModel("glm-4.5-air", "GLM-4.5 Air", "https://open.bigmodel.cn/api/paas/v4"),
|
|
1410
|
+
buildPresetModel("glm-4.5", "GLM-4.5", "https://open.bigmodel.cn/api/paas/v4")
|
|
1411
|
+
]
|
|
1412
|
+
},
|
|
1413
|
+
stepfun: {
|
|
1414
|
+
displayName: "StepFun",
|
|
1415
|
+
baseUrl: "https://api.stepfun.com/v1",
|
|
1416
|
+
models: [
|
|
1417
|
+
buildPresetModel("step-3.7-flash", "Step 3.7 Flash", "https://api.stepfun.com/v1"),
|
|
1418
|
+
buildPresetModel("step-3.5-flash", "Step 3.5 Flash", "https://api.stepfun.com/v1"),
|
|
1419
|
+
buildPresetModel("step-1o-turbo-vision", "Step 1o Turbo Vision", "https://api.stepfun.com/v1")
|
|
1420
|
+
]
|
|
1421
|
+
},
|
|
1422
|
+
siliconflow: {
|
|
1423
|
+
displayName: "SiliconFlow",
|
|
1424
|
+
baseUrl: "https://api.siliconflow.cn/v1",
|
|
1425
|
+
models: [
|
|
1426
|
+
buildPresetModel("deepseek-ai/DeepSeek-V4-Pro", "DeepSeek V4 Pro (via SiliconFlow)", "https://api.siliconflow.cn/v1"),
|
|
1427
|
+
buildPresetModel("deepseek-ai/DeepSeek-V4-Flash", "DeepSeek V4 Flash (via SiliconFlow)", "https://api.siliconflow.cn/v1"),
|
|
1428
|
+
buildPresetModel("zai-org/GLM-5.2", "GLM-5.2 (via SiliconFlow)", "https://api.siliconflow.cn/v1"),
|
|
1429
|
+
buildPresetModel("Qwen/Qwen3.6-35B-A3B", "Qwen3.6-35B-A3B (via SiliconFlow)", "https://api.siliconflow.cn/v1"),
|
|
1430
|
+
buildPresetModel("moonshotai/Kimi-K2.7-Code", "Kimi K2.7 Code (via SiliconFlow)", "https://api.siliconflow.cn/v1")
|
|
1431
|
+
]
|
|
1432
|
+
},
|
|
1433
|
+
openrouter: {
|
|
1434
|
+
displayName: "OpenRouter",
|
|
1435
|
+
baseUrl: "https://openrouter.ai/api/v1",
|
|
1436
|
+
models: [buildPresetModel("openrouter/auto", "OpenRouter Auto", "https://openrouter.ai/api/v1")]
|
|
1437
|
+
},
|
|
1438
|
+
novita: {
|
|
1439
|
+
displayName: "Novita",
|
|
1440
|
+
baseUrl: "https://api.novita.ai/openai/v1",
|
|
1441
|
+
models: [
|
|
1442
|
+
buildPresetModel("deepseek/deepseek-v4-pro", "DeepSeek V4 Pro (via Novita)", "https://api.novita.ai/openai/v1"),
|
|
1443
|
+
buildPresetModel("deepseek/deepseek-v4-flash", "DeepSeek V4 Flash (via Novita)", "https://api.novita.ai/openai/v1"),
|
|
1444
|
+
buildPresetModel("zai/glm-5.2", "GLM-5.2 (via Novita)", "https://api.novita.ai/openai/v1"),
|
|
1445
|
+
buildPresetModel("zai/glm-5.1", "GLM-5.1 (via Novita)", "https://api.novita.ai/openai/v1"),
|
|
1446
|
+
buildPresetModel("moonshotai/kimi-k3", "Kimi K3 (via Novita)", "https://api.novita.ai/openai/v1")
|
|
1447
|
+
]
|
|
1448
|
+
},
|
|
1449
|
+
medalsoft: {
|
|
1450
|
+
displayName: "Medalsoft",
|
|
1451
|
+
baseUrl: "https://nexus.servicemecloud.com/v1",
|
|
1452
|
+
models: []
|
|
1453
|
+
},
|
|
1454
|
+
sensenova: {
|
|
1455
|
+
displayName: "SenseNova",
|
|
1456
|
+
baseUrl: "https://token.sensenova.cn/v1",
|
|
1457
|
+
models: [
|
|
1458
|
+
buildPresetModel("sensenova-6.8-flash-lite", "SenseNova 6.8 Flash-Lite", "https://token.sensenova.cn/v1"),
|
|
1459
|
+
{
|
|
1460
|
+
...buildPresetModel("deepseek-v4-flash", "DeepSeek V4 Flash (via SenseNova)", "https://token.sensenova.cn/v1"),
|
|
1461
|
+
maxInputTokens: 1048576,
|
|
1462
|
+
maxOutputTokens: 65536,
|
|
1463
|
+
capabilities: {
|
|
1464
|
+
supportsImageToText: false,
|
|
1465
|
+
supportsToolCalling: true
|
|
1466
|
+
}
|
|
1467
|
+
},
|
|
1468
|
+
{
|
|
1469
|
+
...buildPresetModel("glm-5.2", "GLM-5.2 (via SenseNova)", "https://token.sensenova.cn/v1"),
|
|
1470
|
+
maxInputTokens: 1048576,
|
|
1471
|
+
maxOutputTokens: 131072,
|
|
1472
|
+
capabilities: {
|
|
1473
|
+
supportsImageToText: false,
|
|
1474
|
+
supportsToolCalling: true
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
]
|
|
1478
|
+
}
|
|
1604
1479
|
};
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1480
|
+
/**
|
|
1481
|
+
* Curated `id → displayName` map derived from `BUILTIN_PROVIDER_PRESETS`
|
|
1482
|
+
* at module-load time. Single source of truth for the friendly
|
|
1483
|
+
* model label shown in the picker — both the "Reset to preset
|
|
1484
|
+
* models" / vendor `<select>` flows (which call `buildPresetModel`
|
|
1485
|
+
* and get the displayName as a parameter) and the "Fetch from API"
|
|
1486
|
+
* union (which calls `getPresetModelDisplayName` to fill in the
|
|
1487
|
+
* `displayName` field when the API doesn't return one).
|
|
1488
|
+
*
|
|
1489
|
+
* Built at module load (not lazily) so the lookup is O(1) on the
|
|
1490
|
+
* hot path — `unionProviderModelWithPreset` runs once per fetched
|
|
1491
|
+
* model row, and the union runs every time the user clicks "Fetch
|
|
1492
|
+
* from API". A lazy Map would also work, but the upfront cost is
|
|
1493
|
+
* ~30 entries (the largest preset is Zhipu with ~20 GLM rows) and
|
|
1494
|
+
* pays for itself after the first fetch.
|
|
1495
|
+
*/
|
|
1496
|
+
const PRESET_MODEL_DISPLAY_NAMES = (() => {
|
|
1497
|
+
const map = {};
|
|
1498
|
+
for (const preset of Object.values(BUILTIN_PROVIDER_PRESETS)) for (const model of preset.models) if (model.displayName !== void 0) map[model.id] = model.displayName;
|
|
1499
|
+
return Object.freeze(map);
|
|
1615
1500
|
})();
|
|
1501
|
+
/**
|
|
1502
|
+
* Look up the curated friendly displayName for a model id. Two
|
|
1503
|
+
* sources, in priority order:
|
|
1504
|
+
*
|
|
1505
|
+
* 1. **Explicit map** (`PRESET_MODEL_DISPLAY_NAMES`, derived from
|
|
1506
|
+
* `BUILTIN_PROVIDER_PRESETS` at module load). Curated by hand;
|
|
1507
|
+
* wins when present so a curated prettier name
|
|
1508
|
+
* (e.g. "DeepSeek V4 Flash" for `deepseek-v4-flash`) is
|
|
1509
|
+
* always preferred over whatever the detail's prefix would
|
|
1510
|
+
* produce.
|
|
1511
|
+
* 2. **`MODEL_METADATA.detail` fallback** (2026-08-19 follow-up).
|
|
1512
|
+
* For ids that are in `MODEL_METADATA` (have curated pricing
|
|
1513
|
+
* / capabilities) but NOT in any vendor preset — e.g.
|
|
1514
|
+
* `glm-4.7-flash`, `glm-4.5v`, `glm-5v-turbo`, `glm-4.6v` —
|
|
1515
|
+
* derive the display name from the `detail` field by
|
|
1516
|
+
* splitting on the first ` — ` and keeping the left half.
|
|
1517
|
+
* This restores friendly labels for models the v1 lookup
|
|
1518
|
+
* missed (the user-reported case: `glm-4.7-flash` came
|
|
1519
|
+
* back from "Fetch from API" without a display name
|
|
1520
|
+
* because it was excluded from the Zhipu preset on
|
|
1521
|
+
* 2026-08-18, but its detail field already said
|
|
1522
|
+
* "GLM-4.7 Flash — 完全免费(200K 上下文)").
|
|
1523
|
+
*
|
|
1524
|
+
* Returns `undefined` for:
|
|
1525
|
+
* • ids that aren't in `BUILTIN_PROVIDER_PRESETS` AND aren't in
|
|
1526
|
+
* `MODEL_METADATA` (genuinely custom / aggregator-only
|
|
1527
|
+
* models the user added by hand — the consumer falls back
|
|
1528
|
+
* to the literal fetched id)
|
|
1529
|
+
* • ids in `MODEL_METADATA` whose `detail` is empty /
|
|
1530
|
+
* whitespace, or whose detail has no ` — ` boundary and the
|
|
1531
|
+
* whole string is the qualifier (defensive — every entry
|
|
1532
|
+
* today has a usable detail).
|
|
1533
|
+
*
|
|
1534
|
+
* Used by `unionProviderModelWithPreset` to fill in the
|
|
1535
|
+
* `displayName` field when the API payload omits it — the
|
|
1536
|
+
* picker's model name column then renders "DeepSeek V4 Flash"
|
|
1537
|
+
* or "GLM-4.7 Flash" instead of the bare id right after the
|
|
1538
|
+
* fetch result lands.
|
|
1539
|
+
*/
|
|
1616
1540
|
function getPresetModelDisplayName(id) {
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
return void 0;
|
|
1628
|
-
}
|
|
1629
|
-
const dashIndex = detail.indexOf(" \u2014 ");
|
|
1630
|
-
if (dashIndex === -1) {
|
|
1631
|
-
return detail;
|
|
1632
|
-
}
|
|
1633
|
-
const head = detail.slice(0, dashIndex).trim();
|
|
1634
|
-
return head === "" ? void 0 : head;
|
|
1541
|
+
const explicit = PRESET_MODEL_DISPLAY_NAMES[id];
|
|
1542
|
+
if (explicit !== void 0) return explicit;
|
|
1543
|
+
const meta = MODEL_METADATA[id];
|
|
1544
|
+
if (meta === void 0) return;
|
|
1545
|
+
const detail = meta.detail;
|
|
1546
|
+
if (typeof detail !== "string" || detail.trim() === "") return;
|
|
1547
|
+
const dashIndex = detail.indexOf(" — ");
|
|
1548
|
+
if (dashIndex === -1) return detail;
|
|
1549
|
+
const head = detail.slice(0, dashIndex).trim();
|
|
1550
|
+
return head === "" ? void 0 : head;
|
|
1635
1551
|
}
|
|
1552
|
+
/**
|
|
1553
|
+
* Look up the default config (displayName / baseUrl / models) for a
|
|
1554
|
+
* named vendor type. Returns `null` for the `-compatible` family —
|
|
1555
|
+
* those have no canned defaults; the user enters them by hand.
|
|
1556
|
+
*/
|
|
1636
1557
|
function getBuiltinProviderPreset(type) {
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
return null;
|
|
1652
|
-
}
|
|
1558
|
+
switch (type) {
|
|
1559
|
+
case "minimax":
|
|
1560
|
+
case "deepseek":
|
|
1561
|
+
case "agnes":
|
|
1562
|
+
case "kimi":
|
|
1563
|
+
case "zhipu":
|
|
1564
|
+
case "stepfun":
|
|
1565
|
+
case "siliconflow":
|
|
1566
|
+
case "openrouter":
|
|
1567
|
+
case "novita":
|
|
1568
|
+
case "medalsoft":
|
|
1569
|
+
case "sensenova": return BUILTIN_PROVIDER_PRESETS[type];
|
|
1570
|
+
default: return null;
|
|
1571
|
+
}
|
|
1653
1572
|
}
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1573
|
+
//#endregion
|
|
1574
|
+
//#region src/certificate-bundle.ts
|
|
1575
|
+
const CERTIFICATE_BUNDLE_FORMATS = [
|
|
1576
|
+
{
|
|
1577
|
+
format: "pem",
|
|
1578
|
+
label: "Nginx / Generic PEM",
|
|
1579
|
+
description: "cert.pem + key.pem + fullchain.pem (Nginx, HAProxy, most Unix servers).",
|
|
1580
|
+
icon: "file-lock-2",
|
|
1581
|
+
requiresPassword: false,
|
|
1582
|
+
artifactExtension: ".pem"
|
|
1583
|
+
},
|
|
1584
|
+
{
|
|
1585
|
+
format: "pfx",
|
|
1586
|
+
label: "IIS / Tomcat PFX",
|
|
1587
|
+
description: "PKCS#12 bundle (.pfx) for Windows IIS, Tomcat, ColdFusion. Requires a password.",
|
|
1588
|
+
icon: "shield",
|
|
1589
|
+
requiresPassword: true,
|
|
1590
|
+
artifactExtension: ".pfx"
|
|
1591
|
+
},
|
|
1592
|
+
{
|
|
1593
|
+
format: "crt",
|
|
1594
|
+
label: "Apache CRT",
|
|
1595
|
+
description: "Separate .crt + .key for Apache httpd and other Unix servers that prefer DER/PEM split.",
|
|
1596
|
+
icon: "file-badge",
|
|
1597
|
+
requiresPassword: false,
|
|
1598
|
+
artifactExtension: ".crt"
|
|
1599
|
+
},
|
|
1600
|
+
{
|
|
1601
|
+
format: "jks",
|
|
1602
|
+
label: "Java JKS",
|
|
1603
|
+
description: "Java KeyStore (.jks) for Tomcat and Java applications. Requires a JDK with `keytool` on PATH.",
|
|
1604
|
+
icon: "coffee",
|
|
1605
|
+
requiresPassword: true,
|
|
1606
|
+
artifactExtension: ".jks"
|
|
1607
|
+
}
|
|
1689
1608
|
];
|
|
1690
|
-
|
|
1691
|
-
|
|
1609
|
+
//#endregion
|
|
1610
|
+
//#region src/copilot-customizations.ts
|
|
1611
|
+
/**
|
|
1612
|
+
* Whole-package registrar install marker: the explicit `wholePackage`
|
|
1613
|
+
* flag, or (legacy shape) the synthetic `::package:` artifact. Such
|
|
1614
|
+
* packages are PERSONAL-scope only — registrar-owned, so per-artifact
|
|
1615
|
+
* scope actions (move to workspace) do not apply.
|
|
1616
|
+
*/
|
|
1692
1617
|
function isWholePackageInstall(definition) {
|
|
1693
|
-
|
|
1618
|
+
return definition.wholePackage === true || definition.artifacts?.some((artifact) => artifact.id.includes("::package:")) === true;
|
|
1694
1619
|
}
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1620
|
+
/**
|
|
1621
|
+
* Registrar record id (`repo:plugin`) from a view/package payload id
|
|
1622
|
+
* (`repo::plugin`). Both spellings float around the wire; the registrar
|
|
1623
|
+
* and the settings keys always use the single-colon form.
|
|
1624
|
+
*/
|
|
1625
|
+
function registrationIdFromPackageId(packageId) {
|
|
1626
|
+
return packageId.replace("::", ":");
|
|
1627
|
+
}
|
|
1628
|
+
//#endregion
|
|
1629
|
+
//#region ../../node_modules/.pnpm/js-sha256@1.0.0/node_modules/js-sha256/src/shared.mjs
|
|
1700
1630
|
var INPUT_ERROR = "input is invalid type";
|
|
1701
1631
|
var FINALIZE_ERROR = "finalize already called";
|
|
1702
1632
|
var ARRAY_BUFFER = typeof ArrayBuffer !== "undefined";
|
|
1703
1633
|
var formatMessage = function(message) {
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
if (ARRAY_BUFFER && message) {
|
|
1712
|
-
if (message.constructor === ArrayBuffer) {
|
|
1713
|
-
return [new Uint8Array(message), false];
|
|
1714
|
-
} else if (ArrayBuffer.isView(message)) {
|
|
1715
|
-
return [message, false];
|
|
1716
|
-
}
|
|
1717
|
-
}
|
|
1718
|
-
throw new Error(INPUT_ERROR);
|
|
1634
|
+
if (typeof message === "string") return [message, true];
|
|
1635
|
+
if (Array.isArray(message)) return [message, false];
|
|
1636
|
+
if (ARRAY_BUFFER && message) {
|
|
1637
|
+
if (message.constructor === ArrayBuffer) return [new Uint8Array(message), false];
|
|
1638
|
+
else if (ArrayBuffer.isView(message)) return [message, false];
|
|
1639
|
+
}
|
|
1640
|
+
throw new Error(INPUT_ERROR);
|
|
1719
1641
|
};
|
|
1720
|
-
|
|
1721
|
-
|
|
1642
|
+
//#endregion
|
|
1643
|
+
//#region ../../node_modules/.pnpm/js-sha256@1.0.0/node_modules/js-sha256/src/node.mjs
|
|
1722
1644
|
function toNodeInput(message) {
|
|
1723
|
-
|
|
1724
|
-
|
|
1645
|
+
const [msg, isString] = formatMessage(message);
|
|
1646
|
+
return isString ? Buffer.from(msg, "utf8") : Buffer.from(msg);
|
|
1725
1647
|
}
|
|
1726
1648
|
var NodeHasher = class {
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
return Uint8Array.from(this.array()).buffer;
|
|
1760
|
-
}
|
|
1649
|
+
constructor(hash) {
|
|
1650
|
+
this.hash = hash;
|
|
1651
|
+
this.result = void 0;
|
|
1652
|
+
}
|
|
1653
|
+
update(message) {
|
|
1654
|
+
if (this.result) throw new Error(FINALIZE_ERROR);
|
|
1655
|
+
this.hash.update(toNodeInput(message));
|
|
1656
|
+
return this;
|
|
1657
|
+
}
|
|
1658
|
+
finalize() {
|
|
1659
|
+
if (!this.result) {
|
|
1660
|
+
this.result = this.hash.digest();
|
|
1661
|
+
this.hash = void 0;
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
hex() {
|
|
1665
|
+
this.finalize();
|
|
1666
|
+
return this.result.toString("hex");
|
|
1667
|
+
}
|
|
1668
|
+
toString() {
|
|
1669
|
+
return this.hex();
|
|
1670
|
+
}
|
|
1671
|
+
array() {
|
|
1672
|
+
this.finalize();
|
|
1673
|
+
return Array.from(this.result);
|
|
1674
|
+
}
|
|
1675
|
+
digest() {
|
|
1676
|
+
return this.array();
|
|
1677
|
+
}
|
|
1678
|
+
arrayBuffer() {
|
|
1679
|
+
return Uint8Array.from(this.array()).buffer;
|
|
1680
|
+
}
|
|
1761
1681
|
};
|
|
1762
1682
|
function addOutputMethods(method, createHasher) {
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1683
|
+
method.hex = method;
|
|
1684
|
+
method.array = function(...args) {
|
|
1685
|
+
return createHasher(...args.slice(0, -1)).update(args[args.length - 1]).array();
|
|
1686
|
+
};
|
|
1687
|
+
method.digest = method.array;
|
|
1688
|
+
method.arrayBuffer = function(...args) {
|
|
1689
|
+
return createHasher(...args.slice(0, -1)).update(args[args.length - 1]).arrayBuffer();
|
|
1690
|
+
};
|
|
1691
|
+
return method;
|
|
1772
1692
|
}
|
|
1773
1693
|
function createNodeMethod(algorithm) {
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1694
|
+
const createHasher = () => new NodeHasher(createHash(algorithm));
|
|
1695
|
+
const method = function(message) {
|
|
1696
|
+
return createHasher().update(message).hex();
|
|
1697
|
+
};
|
|
1698
|
+
addOutputMethods(method, createHasher);
|
|
1699
|
+
method.create = createHasher;
|
|
1700
|
+
method.update = function(message) {
|
|
1701
|
+
return method.create().update(message);
|
|
1702
|
+
};
|
|
1703
|
+
return method;
|
|
1784
1704
|
}
|
|
1785
1705
|
function createNodeHmacMethod(algorithm) {
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1706
|
+
const createHasher = (key) => new NodeHasher(createHmac(algorithm, toNodeInput(key)));
|
|
1707
|
+
const method = function(key, message) {
|
|
1708
|
+
return createHasher(key).update(message).hex();
|
|
1709
|
+
};
|
|
1710
|
+
addOutputMethods(method, createHasher);
|
|
1711
|
+
method.create = createHasher;
|
|
1712
|
+
method.update = function(key, message) {
|
|
1713
|
+
return method.create(key).update(message);
|
|
1714
|
+
};
|
|
1715
|
+
return method;
|
|
1796
1716
|
}
|
|
1797
|
-
|
|
1798
|
-
|
|
1717
|
+
const sha256 = createNodeMethod("sha256");
|
|
1718
|
+
const sha224 = createNodeMethod("sha224");
|
|
1799
1719
|
sha256.sha256 = sha256;
|
|
1800
1720
|
sha256.sha224 = sha224;
|
|
1801
1721
|
sha256.hmac = createNodeHmacMethod("sha256");
|
|
1802
1722
|
sha224.hmac = createNodeHmacMethod("sha224");
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1723
|
+
//#endregion
|
|
1724
|
+
//#region src/device-auth.ts
|
|
1725
|
+
/**
|
|
1726
|
+
* Device-auth wire contract shared by the extension (signer) and the
|
|
1727
|
+
* server (verifier).
|
|
1728
|
+
*
|
|
1729
|
+
* Single source of truth for the `x-ms-device-*` header names and the
|
|
1730
|
+
* HMAC-SHA-256 request-signature algorithm. Previously the same
|
|
1731
|
+
* constants + function were copy-pasted in three places
|
|
1732
|
+
* (extension `services/device/deviceAuth.ts`, core `device/deviceAuth.ts`,
|
|
1733
|
+
* server `lib/auth/device-signature-guard.ts`) and kept in sync by
|
|
1734
|
+
* comments alone. Server and extension now import from here.
|
|
1735
|
+
*
|
|
1736
|
+
* NOTE: `packages/serviceme-core/src/device/deviceAuth.ts` keeps its own
|
|
1737
|
+
* copy — ADL-003 forbids core → shared (and shared → core) so the core
|
|
1738
|
+
* copy is a documented boundary exception. Keep it in lock-step with
|
|
1739
|
+
* this file. See `docs/architecture/phase-5-device-header-spec.md` §5
|
|
1740
|
+
* for the wire format.
|
|
1741
|
+
*/
|
|
1742
|
+
/** Canonical header names — MUST match the server's verifier. */
|
|
1743
|
+
const DeviceAuthHeaders = {
|
|
1744
|
+
deviceId: "x-ms-device-id",
|
|
1745
|
+
deviceSecret: "x-ms-device-secret",
|
|
1746
|
+
signature: "x-ms-device-signature",
|
|
1747
|
+
timestamp: "x-ms-device-timestamp",
|
|
1748
|
+
secretVersion: "x-ms-device-secret-version"
|
|
1811
1749
|
};
|
|
1750
|
+
/**
|
|
1751
|
+
* Basis is `METHOD\nPATH\nTIMESTAMP\nBODY\nSECRET` (LF-joined, NOT JSON).
|
|
1752
|
+
* Output is lowercase hex SHA-256.
|
|
1753
|
+
*
|
|
1754
|
+
* Uses `js-sha256` (pure JS, synchronous, browser + Node) instead of
|
|
1755
|
+
* `node:crypto` so this module stays importable from the webview (the
|
|
1756
|
+
* shared barrel is consumed by browser bundles — `node:crypto` breaks
|
|
1757
|
+
* the vite/rollup build).
|
|
1758
|
+
*/
|
|
1812
1759
|
function createDeviceRequestSignature(params) {
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1760
|
+
const basis = [
|
|
1761
|
+
params.method.toUpperCase(),
|
|
1762
|
+
params.path,
|
|
1763
|
+
String(params.timestamp),
|
|
1764
|
+
params.body,
|
|
1765
|
+
params.secret
|
|
1766
|
+
].join("\n");
|
|
1767
|
+
return sha256(basis);
|
|
1821
1768
|
}
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1769
|
+
//#endregion
|
|
1770
|
+
//#region src/git-utils.ts
|
|
1771
|
+
/**
|
|
1772
|
+
* Git URL Utilities
|
|
1773
|
+
*
|
|
1774
|
+
* Pure functions for parsing and validating Git remote URLs into canonical slugs.
|
|
1775
|
+
* No platform-specific logic — suitable for both Node.js and browser environments.
|
|
1776
|
+
*/
|
|
1777
|
+
/**
|
|
1778
|
+
* Built-in hostname aliases for Git remotes.
|
|
1779
|
+
*
|
|
1780
|
+
* Some users/teams configure `~/.ssh/config` `Host` aliases (e.g. to pick a
|
|
1781
|
+
* specific SSH identity for a work GitHub account) so their remotes read
|
|
1782
|
+
* `git@github-msc:owner/repo.git` instead of `git@github.com:owner/repo.git`.
|
|
1783
|
+
* Resolving these here — rather than only on the client — keeps the server's
|
|
1784
|
+
* independently-recomputed canonical slug (see `ensureRemotesMatchCanonicalSlug`
|
|
1785
|
+
* in `apps/server/src/app/api/v1/projects/_validators.ts`) consistent with
|
|
1786
|
+
* whatever the client already resolved and sent as `canonical_slug`.
|
|
1787
|
+
*/
|
|
1788
|
+
const GIT_REMOTE_HOST_ALIASES = { "github-msc": "github.com" };
|
|
1827
1789
|
function normalizeGitRemoteHost(host) {
|
|
1828
|
-
|
|
1829
|
-
|
|
1790
|
+
const normalizedHost = host.trim().toLowerCase();
|
|
1791
|
+
return GIT_REMOTE_HOST_ALIASES[normalizedHost] ?? normalizedHost;
|
|
1830
1792
|
}
|
|
1793
|
+
/**
|
|
1794
|
+
* Apply {@link GIT_REMOTE_HOST_ALIASES} to an already-canonical `host/owner/repo`
|
|
1795
|
+
* slug string (as opposed to a raw Git URL — see `normalizeGitUrl` for that).
|
|
1796
|
+
*/
|
|
1831
1797
|
function normalizeCanonicalSlug(slug) {
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
const path = trimmedSlug.slice(firstSlashIndex + 1);
|
|
1839
|
-
return `${normalizeGitRemoteHost(host)}/${path}`;
|
|
1798
|
+
const trimmedSlug = slug.trim().toLowerCase();
|
|
1799
|
+
const firstSlashIndex = trimmedSlug.indexOf("/");
|
|
1800
|
+
if (firstSlashIndex <= 0) return trimmedSlug;
|
|
1801
|
+
const host = trimmedSlug.slice(0, firstSlashIndex);
|
|
1802
|
+
const path = trimmedSlug.slice(firstSlashIndex + 1);
|
|
1803
|
+
return `${normalizeGitRemoteHost(host)}/${path}`;
|
|
1840
1804
|
}
|
|
1805
|
+
/**
|
|
1806
|
+
* Normalize a Git remote URL to a canonical slug: host/owner/repo
|
|
1807
|
+
* - Supports HTTPS, SSH, and SCP-like syntax
|
|
1808
|
+
* - Strips credentials, ports, and .git suffix
|
|
1809
|
+
* - Lowercases host and every path segment
|
|
1810
|
+
* - Resolves known host aliases (see {@link GIT_REMOTE_HOST_ALIASES})
|
|
1811
|
+
*
|
|
1812
|
+
* @throws Error if the input cannot be parsed into a valid slug
|
|
1813
|
+
*/
|
|
1841
1814
|
function normalizeGitUrl(input) {
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
if (isValidCanonicalSlug(trimmed)) return normalizeCanonicalSlug(trimmed);
|
|
1863
|
-
throw new Error(`Unparseable Git URL: ${input}`);
|
|
1864
|
-
}
|
|
1815
|
+
const trimmed = input.trim();
|
|
1816
|
+
const scpMatch = !trimmed.includes("://") && trimmed.match(/^([^@]+)@([^:]+):(.+)$/);
|
|
1817
|
+
if (scpMatch) {
|
|
1818
|
+
const host = scpMatch[2];
|
|
1819
|
+
const path = scpMatch[3];
|
|
1820
|
+
return toCanonicalSlug(`ssh://${host}/${path}`);
|
|
1821
|
+
}
|
|
1822
|
+
try {
|
|
1823
|
+
const url = new URL(trimmed);
|
|
1824
|
+
const host = normalizeGitRemoteHost(url.hostname);
|
|
1825
|
+
let pathname = url.pathname;
|
|
1826
|
+
if (pathname.startsWith("/")) pathname = pathname.slice(1);
|
|
1827
|
+
if (pathname.endsWith(".git")) pathname = pathname.slice(0, -4);
|
|
1828
|
+
const parts = pathname.split("/").filter(Boolean);
|
|
1829
|
+
if (parts.length < 2) throw new Error("Invalid Git URL: missing owner/repo path");
|
|
1830
|
+
return `${host}/${parts.map((part) => part.toLowerCase()).join("/")}`;
|
|
1831
|
+
} catch {
|
|
1832
|
+
if (isValidCanonicalSlug(trimmed)) return normalizeCanonicalSlug(trimmed);
|
|
1833
|
+
throw new Error(`Unparseable Git URL: ${input}`);
|
|
1834
|
+
}
|
|
1865
1835
|
}
|
|
1836
|
+
/**
|
|
1837
|
+
* Validate whether a string matches the canonical slug format: host/owner/repo
|
|
1838
|
+
* Requires at least three segments (host + two path parts).
|
|
1839
|
+
* Allows percent-encoded characters (e.g. %20) for hosts like Azure DevOps
|
|
1840
|
+
* that permit spaces in project/repo names.
|
|
1841
|
+
*/
|
|
1866
1842
|
function isValidCanonicalSlug(slug) {
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
return canonicalSlugPattern.test(slug.trim());
|
|
1843
|
+
const segment = "(?:[a-zA-Z0-9_.-]|%[0-9A-Fa-f]{2})+";
|
|
1844
|
+
return new RegExp(`^${segment}(?:\\/${segment}){2,}$`).test(slug.trim());
|
|
1870
1845
|
}
|
|
1871
1846
|
function toCanonicalSlug(coerced) {
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1847
|
+
const url = new URL(coerced);
|
|
1848
|
+
const host = normalizeGitRemoteHost(url.hostname);
|
|
1849
|
+
let pathname = url.pathname;
|
|
1850
|
+
if (pathname.startsWith("/")) pathname = pathname.slice(1);
|
|
1851
|
+
if (pathname.endsWith(".git")) pathname = pathname.slice(0, -4);
|
|
1852
|
+
const parts = pathname.split("/").filter(Boolean);
|
|
1853
|
+
if (parts.length < 2) throw new Error("Invalid Git URL: missing owner/repo path");
|
|
1854
|
+
return `${host}/${parts.map((part) => part.toLowerCase()).join("/")}`;
|
|
1880
1855
|
}
|
|
1881
|
-
|
|
1882
|
-
|
|
1856
|
+
//#endregion
|
|
1857
|
+
//#region src/github-api.ts
|
|
1858
|
+
/**
|
|
1859
|
+
* Fetch user info from GitHub API
|
|
1860
|
+
* @param token GitHub Personal Access Token or OAuth Access Token
|
|
1861
|
+
* @returns GitHubUser object
|
|
1862
|
+
* @throws Error if request fails or token is invalid
|
|
1863
|
+
*/
|
|
1883
1864
|
async function fetchGitHubUser(token) {
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
} catch (_e) {
|
|
1904
|
-
body = "[Failed to read response body]";
|
|
1905
|
-
}
|
|
1906
|
-
throw new Error(`GitHub API Error: ${status} ${statusText} - ${body}`);
|
|
1865
|
+
const resp = await fetch("https://api.github.com/user", { headers: {
|
|
1866
|
+
Authorization: `token ${token}`,
|
|
1867
|
+
Accept: "application/vnd.github.v3+json"
|
|
1868
|
+
} });
|
|
1869
|
+
if (resp.ok) {
|
|
1870
|
+
const user = await resp.json();
|
|
1871
|
+
user.email = await resolveGitHubUserEmail(token, user);
|
|
1872
|
+
return user;
|
|
1873
|
+
}
|
|
1874
|
+
if (resp.status === 401) throw new Error("GitHub API Error: 401 Unauthorized");
|
|
1875
|
+
const status = resp.status;
|
|
1876
|
+
const statusText = resp.statusText;
|
|
1877
|
+
let body = "";
|
|
1878
|
+
try {
|
|
1879
|
+
body = await resp.text();
|
|
1880
|
+
} catch (_e) {
|
|
1881
|
+
body = "[Failed to read response body]";
|
|
1882
|
+
}
|
|
1883
|
+
throw new Error(`GitHub API Error: ${status} ${statusText} - ${body}`);
|
|
1907
1884
|
}
|
|
1908
1885
|
async function resolveGitHubUserEmail(token, user) {
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
}
|
|
1913
|
-
const emails = await fetchGitHubUserEmails(token);
|
|
1914
|
-
const preferredEmail = pickPreferredGitHubEmail(emails);
|
|
1915
|
-
return preferredEmail ?? directEmail;
|
|
1886
|
+
const directEmail = sanitizeEmail(user.email);
|
|
1887
|
+
if (directEmail) return directEmail;
|
|
1888
|
+
return pickPreferredGitHubEmail(await fetchGitHubUserEmails(token)) ?? directEmail;
|
|
1916
1889
|
}
|
|
1917
1890
|
async function fetchGitHubUserEmails(token) {
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
}
|
|
1933
|
-
let body = "";
|
|
1934
|
-
try {
|
|
1935
|
-
body = await resp.text();
|
|
1936
|
-
} catch (_e) {
|
|
1937
|
-
body = "[Failed to read response body]";
|
|
1938
|
-
}
|
|
1939
|
-
throw new Error(`GitHub API Error: ${resp.status} ${resp.statusText} - ${body}`);
|
|
1891
|
+
const resp = await fetch("https://api.github.com/user/emails", { headers: {
|
|
1892
|
+
Authorization: `token ${token}`,
|
|
1893
|
+
Accept: "application/vnd.github.v3+json"
|
|
1894
|
+
} });
|
|
1895
|
+
if (resp.ok) return await resp.json();
|
|
1896
|
+
if (resp.status === 404 || resp.status === 403) return [];
|
|
1897
|
+
if (resp.status === 401) throw new Error("GitHub API Error: 401 Unauthorized");
|
|
1898
|
+
let body = "";
|
|
1899
|
+
try {
|
|
1900
|
+
body = await resp.text();
|
|
1901
|
+
} catch (_e) {
|
|
1902
|
+
body = "[Failed to read response body]";
|
|
1903
|
+
}
|
|
1904
|
+
throw new Error(`GitHub API Error: ${resp.status} ${resp.statusText} - ${body}`);
|
|
1940
1905
|
}
|
|
1941
1906
|
function pickPreferredGitHubEmail(emails) {
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
);
|
|
1948
|
-
const preferred = sanitizedEmails.find((emailRecord) => emailRecord.primary && emailRecord.verified) ?? sanitizedEmails.find((emailRecord) => emailRecord.verified) ?? sanitizedEmails.find((emailRecord) => emailRecord.primary) ?? sanitizedEmails[0];
|
|
1949
|
-
return preferred?.email ?? null;
|
|
1907
|
+
const sanitizedEmails = emails.map((emailRecord) => ({
|
|
1908
|
+
...emailRecord,
|
|
1909
|
+
email: sanitizeEmail(emailRecord.email)
|
|
1910
|
+
})).filter((emailRecord) => Boolean(emailRecord.email));
|
|
1911
|
+
return (sanitizedEmails.find((emailRecord) => emailRecord.primary && emailRecord.verified) ?? sanitizedEmails.find((emailRecord) => emailRecord.verified) ?? sanitizedEmails.find((emailRecord) => emailRecord.primary) ?? sanitizedEmails[0])?.email ?? null;
|
|
1950
1912
|
}
|
|
1951
1913
|
function sanitizeEmail(email) {
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
if (!normalizedEmail) {
|
|
1957
|
-
return null;
|
|
1958
|
-
}
|
|
1959
|
-
return normalizedEmail;
|
|
1914
|
+
if (!email) return null;
|
|
1915
|
+
const normalizedEmail = email.trim();
|
|
1916
|
+
if (!normalizedEmail) return null;
|
|
1917
|
+
return normalizedEmail;
|
|
1960
1918
|
}
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1919
|
+
const __internal = {
|
|
1920
|
+
pickPreferredGitHubEmail,
|
|
1921
|
+
sanitizeEmail
|
|
1964
1922
|
};
|
|
1965
1923
|
function createOrgMembershipResult(organization, status, httpStatus, membership) {
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1924
|
+
return {
|
|
1925
|
+
status,
|
|
1926
|
+
httpStatus,
|
|
1927
|
+
organization,
|
|
1928
|
+
role: typeof membership?.role === "string" ? membership.role : null,
|
|
1929
|
+
directMembership: typeof membership?.direct_membership === "boolean" ? membership.direct_membership : null
|
|
1930
|
+
};
|
|
1973
1931
|
}
|
|
1932
|
+
/**
|
|
1933
|
+
* Fetch the authenticated user's membership details for a GitHub organization.
|
|
1934
|
+
* Uses the memberships list endpoint so callers can distinguish active, pending,
|
|
1935
|
+
* and indeterminate states instead of collapsing everything into a boolean.
|
|
1936
|
+
*/
|
|
1974
1937
|
async function getGitHubOrgMembership(token, org) {
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
}
|
|
1998
|
-
const memberships = await resp.json();
|
|
1999
|
-
const membership = memberships.find(
|
|
2000
|
-
(entry) => entry.organization?.login?.toLowerCase() === normalizedOrg
|
|
2001
|
-
);
|
|
2002
|
-
if (!membership) {
|
|
2003
|
-
return createOrgMembershipResult(org, "not_member", resp.status);
|
|
2004
|
-
}
|
|
2005
|
-
if (membership.state === "pending") {
|
|
2006
|
-
return createOrgMembershipResult(org, "pending", resp.status, membership);
|
|
2007
|
-
}
|
|
2008
|
-
if (membership.state === "active") {
|
|
2009
|
-
return createOrgMembershipResult(org, "active", resp.status, membership);
|
|
2010
|
-
}
|
|
2011
|
-
return createOrgMembershipResult(org, "not_member", resp.status, membership);
|
|
1938
|
+
const normalizedOrg = org.trim().toLowerCase();
|
|
1939
|
+
const resp = await fetch("https://api.github.com/user/memberships/orgs", { headers: {
|
|
1940
|
+
Authorization: `token ${token}`,
|
|
1941
|
+
Accept: "application/vnd.github+json",
|
|
1942
|
+
"X-GitHub-Api-Version": "2022-11-28"
|
|
1943
|
+
} });
|
|
1944
|
+
if (resp.status === 401) return createOrgMembershipResult(org, "unauthorized", resp.status);
|
|
1945
|
+
if (resp.status === 403) return createOrgMembershipResult(org, "forbidden", resp.status);
|
|
1946
|
+
if (!resp.ok) {
|
|
1947
|
+
let body = "";
|
|
1948
|
+
try {
|
|
1949
|
+
body = await resp.text();
|
|
1950
|
+
} catch (_e) {
|
|
1951
|
+
body = "[Failed to read response body]";
|
|
1952
|
+
}
|
|
1953
|
+
throw new Error(`GitHub API Error: ${resp.status} ${resp.statusText} - ${body}`);
|
|
1954
|
+
}
|
|
1955
|
+
const membership = (await resp.json()).find((entry) => entry.organization?.login?.toLowerCase() === normalizedOrg);
|
|
1956
|
+
if (!membership) return createOrgMembershipResult(org, "not_member", resp.status);
|
|
1957
|
+
if (membership.state === "pending") return createOrgMembershipResult(org, "pending", resp.status, membership);
|
|
1958
|
+
if (membership.state === "active") return createOrgMembershipResult(org, "active", resp.status, membership);
|
|
1959
|
+
return createOrgMembershipResult(org, "not_member", resp.status, membership);
|
|
2012
1960
|
}
|
|
1961
|
+
/**
|
|
1962
|
+
* Check if the token owner is authorized for a given GitHub organization.
|
|
1963
|
+
* Uses the authenticated user's own token — works for both public and private membership.
|
|
1964
|
+
* Returns true if the user is active or has a pending invitation, false otherwise.
|
|
1965
|
+
* @param token GitHub Personal Access Token or OAuth Access Token
|
|
1966
|
+
* @param org GitHub organization name
|
|
1967
|
+
*/
|
|
2013
1968
|
async function checkGitHubOrgMembership(token, org) {
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
}
|
|
2018
|
-
return membership.status === "active" || membership.status === "pending";
|
|
1969
|
+
const membership = await getGitHubOrgMembership(token, org);
|
|
1970
|
+
if (membership.status === "forbidden" || membership.status === "unauthorized") throw new Error(`GitHub org membership indeterminate: HTTP ${membership.httpStatus}`);
|
|
1971
|
+
return membership.status === "active" || membership.status === "pending";
|
|
2019
1972
|
}
|
|
2020
|
-
|
|
2021
|
-
|
|
1973
|
+
//#endregion
|
|
1974
|
+
//#region src/github-user-email.ts
|
|
2022
1975
|
function isGitHubLocalEmail(email) {
|
|
2023
|
-
|
|
1976
|
+
return typeof email === "string" && email.trim().toLowerCase().endsWith("@github.local");
|
|
2024
1977
|
}
|
|
2025
1978
|
function buildGitHubLocalEmail(login) {
|
|
2026
|
-
|
|
1979
|
+
return `${login}@github.local`;
|
|
2027
1980
|
}
|
|
2028
1981
|
function resolvePrimaryEmail(login, email) {
|
|
2029
|
-
|
|
2030
|
-
return normalizedEmail || buildGitHubLocalEmail(login);
|
|
1982
|
+
return email?.trim() || buildGitHubLocalEmail(login);
|
|
2031
1983
|
}
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
1984
|
+
//#endregion
|
|
1985
|
+
//#region src/logger/index.ts
|
|
1986
|
+
/**
|
|
1987
|
+
* Unified, environment-agnostic logger contract for the SERVICEME monorepo.
|
|
1988
|
+
*
|
|
1989
|
+
* This module intentionally has NO dependency on `vscode`, the extension
|
|
1990
|
+
* runtime, or `@serviceme/devtools-core` so it can be consumed by every
|
|
1991
|
+
* package (shared → protocol → core → cli → webview → extension → server)
|
|
1992
|
+
* without creating cycles or pulling in heavyweight environment-specific
|
|
1993
|
+
* APIs.
|
|
1994
|
+
*
|
|
1995
|
+
* Consumers route their telemetry through an {@link ILogger}:
|
|
1996
|
+
* - the extension uses the OutputChannel + file-backed `Logger` (see
|
|
1997
|
+
* `apps/extension/src/core/logger/Logger.ts`);
|
|
1998
|
+
* - server / cli / webview use the console / postMessage-backed
|
|
1999
|
+
* implementations provided by their own package, or the
|
|
2000
|
+
* {@link createConsoleLogger} fallback defined here.
|
|
2001
|
+
*/
|
|
2002
|
+
/** Severity levels, ordered low → high. */
|
|
2003
|
+
let LogLevel = /* @__PURE__ */ function(LogLevel) {
|
|
2004
|
+
LogLevel[LogLevel["DEBUG"] = 0] = "DEBUG";
|
|
2005
|
+
LogLevel[LogLevel["INFO"] = 1] = "INFO";
|
|
2006
|
+
LogLevel[LogLevel["WARN"] = 2] = "WARN";
|
|
2007
|
+
LogLevel[LogLevel["ERROR"] = 3] = "ERROR";
|
|
2008
|
+
return LogLevel;
|
|
2009
|
+
}({});
|
|
2010
|
+
/**
|
|
2011
|
+
* Normalize any thrown value into a serializable record for logging.
|
|
2012
|
+
*
|
|
2013
|
+
* Handles `Error` (canonical fields + protocol-level extras), strings,
|
|
2014
|
+
* primitives, and plain objects, so log sinks can `JSON.stringify` the
|
|
2015
|
+
* result without throwing on circular refs or dropping context. This is a
|
|
2016
|
+
* pure function with no environment dependencies and was promoted from
|
|
2017
|
+
* `apps/extension/src/core/logger/Logger.ts` so every package shares one
|
|
2018
|
+
* normalization path.
|
|
2019
|
+
*/
|
|
2041
2020
|
function normalizeErrorForLog(error) {
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
}
|
|
2084
|
-
return result;
|
|
2085
|
-
}
|
|
2086
|
-
return { kind: t, value: String(error) };
|
|
2021
|
+
if (error === null || error === void 0) return {
|
|
2022
|
+
kind: "nullish",
|
|
2023
|
+
value: null
|
|
2024
|
+
};
|
|
2025
|
+
const t = typeof error;
|
|
2026
|
+
if (t === "string") return {
|
|
2027
|
+
kind: "string",
|
|
2028
|
+
message: error
|
|
2029
|
+
};
|
|
2030
|
+
if (t === "number" || t === "boolean" || t === "bigint") return {
|
|
2031
|
+
kind: t,
|
|
2032
|
+
value: String(error)
|
|
2033
|
+
};
|
|
2034
|
+
if (t === "object") {
|
|
2035
|
+
const obj = error;
|
|
2036
|
+
if (error instanceof Error) {
|
|
2037
|
+
const result = {
|
|
2038
|
+
kind: "error",
|
|
2039
|
+
name: error.name,
|
|
2040
|
+
message: error.message,
|
|
2041
|
+
stack: error.stack
|
|
2042
|
+
};
|
|
2043
|
+
if (typeof obj.code === "string") result.code = obj.code;
|
|
2044
|
+
if (typeof obj.retryable === "boolean") result.retryable = obj.retryable;
|
|
2045
|
+
if ("details" in obj) result.details = obj.details;
|
|
2046
|
+
if (typeof obj.cause !== "undefined") result.cause = normalizeErrorForLog(obj.cause);
|
|
2047
|
+
return result;
|
|
2048
|
+
}
|
|
2049
|
+
const result = { kind: "object" };
|
|
2050
|
+
let captured = 0;
|
|
2051
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
2052
|
+
result[k] = v;
|
|
2053
|
+
captured += 1;
|
|
2054
|
+
}
|
|
2055
|
+
if (captured === 0) result.message = "(empty object)";
|
|
2056
|
+
return result;
|
|
2057
|
+
}
|
|
2058
|
+
return {
|
|
2059
|
+
kind: t,
|
|
2060
|
+
value: String(error)
|
|
2061
|
+
};
|
|
2087
2062
|
}
|
|
2063
|
+
/**
|
|
2064
|
+
* Best-effort check for `NODE_ENV === "production"`.
|
|
2065
|
+
*
|
|
2066
|
+
* Written against `globalThis` (rather than the `process` global) so this
|
|
2067
|
+
* module stays environment-agnostic and type-checks without `@types/node`.
|
|
2068
|
+
* In a browser / webview `globalThis.process` is undefined, so we treat that
|
|
2069
|
+
* as "not production" (verbose logging on) — which matches the prior
|
|
2070
|
+
* always-on `console.*` behavior of the webview.
|
|
2071
|
+
*/
|
|
2088
2072
|
function isProduction() {
|
|
2089
|
-
|
|
2090
|
-
return proc?.env?.NODE_ENV === "production";
|
|
2073
|
+
return globalThis.process?.env?.NODE_ENV === "production";
|
|
2091
2074
|
}
|
|
2075
|
+
/**
|
|
2076
|
+
* Create a console-backed {@link ILogger}.
|
|
2077
|
+
*
|
|
2078
|
+
* - `debug` is gated to `NODE_ENV !== "production"` (verbose traces only in
|
|
2079
|
+
* dev / test), mirroring the server's legacy `logDebug` behavior.
|
|
2080
|
+
* - `info` / `warn` / `error` always emit to the matching `console` method.
|
|
2081
|
+
* - `error`'s leading `error` argument is run through
|
|
2082
|
+
* {@link normalizeErrorForLog} so serialized errors stay structured and
|
|
2083
|
+
* circular-ref safe.
|
|
2084
|
+
*
|
|
2085
|
+
* This is the canonical fallback for packages without a richer sink (cli,
|
|
2086
|
+
* webview bootstrap, extension logger self-diagnostics).
|
|
2087
|
+
*/
|
|
2092
2088
|
function createConsoleLogger(name) {
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
setLevel(next) {
|
|
2129
|
-
level = next;
|
|
2130
|
-
}
|
|
2131
|
-
};
|
|
2089
|
+
let level = isProduction() ? 1 : 0;
|
|
2090
|
+
const emit = (lvl, method, message, args) => {
|
|
2091
|
+
if (lvl < level) return;
|
|
2092
|
+
const prefixed = `[${name}] ${message}`;
|
|
2093
|
+
switch (method) {
|
|
2094
|
+
case "log":
|
|
2095
|
+
console.log(prefixed, ...args);
|
|
2096
|
+
break;
|
|
2097
|
+
case "info":
|
|
2098
|
+
console.info(prefixed, ...args);
|
|
2099
|
+
break;
|
|
2100
|
+
case "warn":
|
|
2101
|
+
console.warn(prefixed, ...args);
|
|
2102
|
+
break;
|
|
2103
|
+
case "error": console.error(prefixed, ...args);
|
|
2104
|
+
}
|
|
2105
|
+
};
|
|
2106
|
+
return {
|
|
2107
|
+
debug(message, ...args) {
|
|
2108
|
+
emit(0, "log", message, args);
|
|
2109
|
+
},
|
|
2110
|
+
info(message, ...args) {
|
|
2111
|
+
emit(1, "info", message, args);
|
|
2112
|
+
},
|
|
2113
|
+
warn(message, ...args) {
|
|
2114
|
+
emit(2, "warn", message, args);
|
|
2115
|
+
},
|
|
2116
|
+
error(message, error, ...args) {
|
|
2117
|
+
const payload = error === void 0 ? args : [normalizeErrorForLog(error), ...args];
|
|
2118
|
+
emit(3, "error", message, payload);
|
|
2119
|
+
},
|
|
2120
|
+
setLevel(next) {
|
|
2121
|
+
level = next;
|
|
2122
|
+
}
|
|
2123
|
+
};
|
|
2132
2124
|
}
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
}
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2125
|
+
//#endregion
|
|
2126
|
+
//#region src/messages.ts
|
|
2127
|
+
let WebviewMessageType = /* @__PURE__ */ function(WebviewMessageType) {
|
|
2128
|
+
WebviewMessageType["WebviewReady"] = "webviewReady";
|
|
2129
|
+
WebviewMessageType["Ready"] = "ready";
|
|
2130
|
+
WebviewMessageType["Log"] = "log";
|
|
2131
|
+
WebviewMessageType["ExecuteCommand"] = "executeCommand";
|
|
2132
|
+
WebviewMessageType["OpenUrl"] = "openUrl";
|
|
2133
|
+
WebviewMessageType["UsePrompt"] = "usePrompt";
|
|
2134
|
+
WebviewMessageType["UpdateAzureProfiles"] = "updateAzureProfiles";
|
|
2135
|
+
WebviewMessageType["UpdateAzureProfile"] = "updateAzureProfile";
|
|
2136
|
+
WebviewMessageType["GetApiConfig"] = "getApiConfig";
|
|
2137
|
+
WebviewMessageType["UpdateApiConfig"] = "updateApiConfig";
|
|
2138
|
+
WebviewMessageType["SaveApiConfig"] = "saveApiConfig";
|
|
2139
|
+
WebviewMessageType["ApiConfigSaveFailed"] = "apiConfigSaveFailed";
|
|
2140
|
+
WebviewMessageType["ExportApiConfig"] = "exportApiConfig";
|
|
2141
|
+
WebviewMessageType["ImportApiConfig"] = "importApiConfig";
|
|
2142
|
+
WebviewMessageType["ApiConfigImported"] = "apiConfigImported";
|
|
2143
|
+
WebviewMessageType["AddExternalTool"] = "addExternalTool";
|
|
2144
|
+
WebviewMessageType["UpdateExternalTool"] = "updateExternalTool";
|
|
2145
|
+
WebviewMessageType["UpdateExternalTools"] = "updateExternalTools";
|
|
2146
|
+
WebviewMessageType["DeleteExternalTool"] = "deleteExternalTool";
|
|
2147
|
+
WebviewMessageType["ReorderExternalTools"] = "reorderExternalTools";
|
|
2148
|
+
WebviewMessageType["UpdateNgrokStatus"] = "updateNgrokStatus";
|
|
2149
|
+
WebviewMessageType["UpdateServerStatus"] = "updateServerStatus";
|
|
2150
|
+
WebviewMessageType["UpdateOcxStatus"] = "updateOcxStatus";
|
|
2151
|
+
WebviewMessageType["UpdateRtkStatus"] = "updateRtkStatus";
|
|
2152
|
+
WebviewMessageType["UpdateCodegraphStatus"] = "updateCodegraphStatus";
|
|
2153
|
+
WebviewMessageType["GetAuthState"] = "getAuthState";
|
|
2154
|
+
WebviewMessageType["UpdateAuthState"] = "updateAuthState";
|
|
2155
|
+
WebviewMessageType["Login"] = "login";
|
|
2156
|
+
WebviewMessageType["Logout"] = "logout";
|
|
2157
|
+
WebviewMessageType["GetAccounts"] = "getAccounts";
|
|
2158
|
+
WebviewMessageType["UpdateAccounts"] = "updateAccounts";
|
|
2159
|
+
WebviewMessageType["SwitchAccount"] = "switchAccount";
|
|
2160
|
+
WebviewMessageType["GetUserProfile"] = "getUserProfile";
|
|
2161
|
+
WebviewMessageType["UpdateUserProfile"] = "updateUserProfile";
|
|
2162
|
+
WebviewMessageType["UserProfileUpdated"] = "userProfileUpdated";
|
|
2163
|
+
WebviewMessageType["AddExtraEmail"] = "addExtraEmail";
|
|
2164
|
+
WebviewMessageType["DeleteExtraEmail"] = "deleteExtraEmail";
|
|
2165
|
+
WebviewMessageType["GetCalendarHolidays"] = "getCalendarHolidays";
|
|
2166
|
+
WebviewMessageType["UpdateCalendarHolidays"] = "updateCalendarHolidays";
|
|
2167
|
+
WebviewMessageType["GetCalendarLeaves"] = "getCalendarLeaves";
|
|
2168
|
+
WebviewMessageType["UpdateCalendarLeaves"] = "updateCalendarLeaves";
|
|
2169
|
+
WebviewMessageType["CreateCalendarLeave"] = "createCalendarLeave";
|
|
2170
|
+
WebviewMessageType["DeleteCalendarLeave"] = "deleteCalendarLeave";
|
|
2171
|
+
WebviewMessageType["GetCalendarNotes"] = "getCalendarNotes";
|
|
2172
|
+
WebviewMessageType["UpdateCalendarNotes"] = "updateCalendarNotes";
|
|
2173
|
+
WebviewMessageType["SaveCalendarNote"] = "saveCalendarNote";
|
|
2174
|
+
WebviewMessageType["GetCalendarMonthSchedule"] = "getCalendarMonthSchedule";
|
|
2175
|
+
WebviewMessageType["GetScheduledTasks"] = "getScheduledTasks";
|
|
2176
|
+
WebviewMessageType["UpdateScheduledTasks"] = "updateScheduledTasks";
|
|
2177
|
+
WebviewMessageType["CreateScheduledTask"] = "createScheduledTask";
|
|
2178
|
+
WebviewMessageType["EditScheduledTask"] = "editScheduledTask";
|
|
2179
|
+
WebviewMessageType["DeleteScheduledTask"] = "deleteScheduledTask";
|
|
2180
|
+
WebviewMessageType["ToggleScheduledTask"] = "toggleScheduledTask";
|
|
2181
|
+
WebviewMessageType["TriggerScheduledTask"] = "triggerScheduledTask";
|
|
2182
|
+
WebviewMessageType["CancelTaskExecution"] = "cancelTaskExecution";
|
|
2183
|
+
WebviewMessageType["GetTaskExecutionLogs"] = "getTaskExecutionLogs";
|
|
2184
|
+
WebviewMessageType["ClearTaskExecutionLogs"] = "clearTaskExecutionLogs";
|
|
2185
|
+
WebviewMessageType["UpdateTaskExecutionLogs"] = "updateTaskExecutionLogs";
|
|
2186
|
+
WebviewMessageType["UpdateTaskExecutionState"] = "updateTaskExecutionState";
|
|
2187
|
+
WebviewMessageType["GetCurrentWorkspace"] = "getCurrentWorkspace";
|
|
2188
|
+
WebviewMessageType["UpdateCurrentWorkspace"] = "updateCurrentWorkspace";
|
|
2189
|
+
WebviewMessageType["GetProviders"] = "getProviders";
|
|
2190
|
+
WebviewMessageType["ProvidersResponse"] = "providersResponse";
|
|
2191
|
+
WebviewMessageType["AddProvider"] = "addProvider";
|
|
2192
|
+
WebviewMessageType["UpdateProvider"] = "updateProvider";
|
|
2193
|
+
WebviewMessageType["RemoveProvider"] = "removeProvider";
|
|
2194
|
+
WebviewMessageType["SetDefaultProvider"] = "setDefaultProvider";
|
|
2195
|
+
WebviewMessageType["SetProviderEnabled"] = "setProviderEnabled";
|
|
2196
|
+
WebviewMessageType["SetProviderOrder"] = "setProviderOrder";
|
|
2197
|
+
WebviewMessageType["TestProvider"] = "testProvider";
|
|
2198
|
+
WebviewMessageType["ProviderTestResultMessage"] = "providerTestResult";
|
|
2199
|
+
WebviewMessageType["TestProviderModel"] = "testProviderModel";
|
|
2200
|
+
WebviewMessageType["ProviderTestModelResultMessage"] = "providerTestModelResult";
|
|
2201
|
+
WebviewMessageType["DefaultProviderChanged"] = "defaultProviderChanged";
|
|
2202
|
+
WebviewMessageType["FetchProviderModels"] = "fetchProviderModels";
|
|
2203
|
+
WebviewMessageType["FetchProviderModelsResult"] = "fetchProviderModelsResult";
|
|
2204
|
+
WebviewMessageType["GetProviderUsage"] = "getProviderUsage";
|
|
2205
|
+
WebviewMessageType["ProviderUsageResponse"] = "providerUsageResponse";
|
|
2206
|
+
WebviewMessageType["SetCacheControlEnabled"] = "setCacheControlEnabled";
|
|
2207
|
+
WebviewMessageType["GetByomSettings"] = "getByomSettings";
|
|
2208
|
+
WebviewMessageType["ByomSettingsResponse"] = "byomSettingsResponse";
|
|
2209
|
+
WebviewMessageType["ListSkillRepoEntries"] = "listSkillRepoEntries";
|
|
2210
|
+
WebviewMessageType["GetSkillRepoEntry"] = "getSkillRepoEntry";
|
|
2211
|
+
WebviewMessageType["InstallSkillRepoEntry"] = "installSkillRepoEntry";
|
|
2212
|
+
WebviewMessageType["ConvertSkillRepoEntryToSymlink"] = "convertSkillRepoEntryToSymlink";
|
|
2213
|
+
WebviewMessageType["UninstallSkillRepoEntry"] = "uninstallSkillRepoEntry";
|
|
2214
|
+
WebviewMessageType["SetSkillRepoEntryEnabled"] = "setSkillRepoEntryEnabled";
|
|
2215
|
+
WebviewMessageType["ListLinkedSkills"] = "listLinkedSkills";
|
|
2216
|
+
WebviewMessageType["UpdateSkillRepoCatalog"] = "updateSkillRepoCatalog";
|
|
2217
|
+
WebviewMessageType["UpdateSkillRepoInstall"] = "updateSkillRepoInstall";
|
|
2218
|
+
WebviewMessageType["UpdateLinkedSkills"] = "updateLinkedSkills";
|
|
2219
|
+
WebviewMessageType["CreateSkillRepoDraft"] = "createSkillRepoDraft";
|
|
2220
|
+
WebviewMessageType["CommitSkillRepoDraft"] = "commitSkillRepoDraft";
|
|
2221
|
+
WebviewMessageType["ListSkillRepoDrafts"] = "listSkillRepoDrafts";
|
|
2222
|
+
WebviewMessageType["DeleteSkillRepoDraft"] = "deleteSkillRepoDraft";
|
|
2223
|
+
WebviewMessageType["UpdateSkillRepoDraft"] = "updateSkillRepoDraft";
|
|
2224
|
+
WebviewMessageType["ListRepositories"] = "listRepositories";
|
|
2225
|
+
WebviewMessageType["AddRepository"] = "addRepository";
|
|
2226
|
+
WebviewMessageType["UpdateRepository"] = "updateRepository";
|
|
2227
|
+
WebviewMessageType["RemoveRepository"] = "removeRepository";
|
|
2228
|
+
WebviewMessageType["EnableRepository"] = "enableRepository";
|
|
2229
|
+
WebviewMessageType["DisableRepository"] = "disableRepository";
|
|
2230
|
+
WebviewMessageType["SyncRepository"] = "syncRepository";
|
|
2231
|
+
WebviewMessageType["SyncAllRepositories"] = "syncAllRepositories";
|
|
2232
|
+
WebviewMessageType["ResetParseCache"] = "resetParseCache";
|
|
2233
|
+
WebviewMessageType["ListCopilotPlugins"] = "listCopilotPlugins";
|
|
2234
|
+
WebviewMessageType["RegisterCopilotPlugin"] = "registerCopilotPlugin";
|
|
2235
|
+
WebviewMessageType["UnregisterCopilotPlugin"] = "unregisterCopilotPlugin";
|
|
2236
|
+
WebviewMessageType["SetCopilotPluginEnabled"] = "setCopilotPluginEnabled";
|
|
2237
|
+
WebviewMessageType["GetCopilotContentStatus"] = "getCopilotContentStatus";
|
|
2238
|
+
WebviewMessageType["RestoreCopilotContent"] = "restoreCopilotContent";
|
|
2239
|
+
WebviewMessageType["ApproveCopilotContent"] = "approveCopilotContent";
|
|
2240
|
+
WebviewMessageType["UpdateRepositoryList"] = "updateRepositoryList";
|
|
2241
|
+
WebviewMessageType["UpdateRepositoryAdd"] = "updateRepositoryAdd";
|
|
2242
|
+
WebviewMessageType["UpdateRepositoryUpdate"] = "updateRepositoryUpdate";
|
|
2243
|
+
WebviewMessageType["UpdateRepositoryRemove"] = "updateRepositoryRemove";
|
|
2244
|
+
WebviewMessageType["UpdateRepositoryToggle"] = "updateRepositoryToggle";
|
|
2245
|
+
WebviewMessageType["UpdateRepositorySyncResult"] = "updateRepositorySyncResult";
|
|
2246
|
+
WebviewMessageType["UpdateCopilotContentStatus"] = "updateCopilotContentStatus";
|
|
2247
|
+
WebviewMessageType["GetCopilotCustomizations"] = "getCopilotCustomizations";
|
|
2248
|
+
WebviewMessageType["SyncCopilotCustomizations"] = "syncCopilotCustomizations";
|
|
2249
|
+
WebviewMessageType["UpdateCopilotCustomizations"] = "updateCopilotCustomizations";
|
|
2250
|
+
WebviewMessageType["InstallCopilotPackage"] = "installCopilotPackage";
|
|
2251
|
+
WebviewMessageType["MoveCopilotPackage"] = "moveCopilotPackage";
|
|
2252
|
+
WebviewMessageType["UninstallCopilotPackage"] = "uninstallCopilotPackage";
|
|
2253
|
+
WebviewMessageType["ListCopilotSources"] = "listCopilotSources";
|
|
2254
|
+
WebviewMessageType["RemoveCopilotSource"] = "removeCopilotSource";
|
|
2255
|
+
WebviewMessageType["PreviewCopilotUpdate"] = "previewCopilotUpdate";
|
|
2256
|
+
WebviewMessageType["UpdateCopilotPackage"] = "updateCopilotPackage";
|
|
2257
|
+
WebviewMessageType["PreviewCopilotLegacy"] = "previewCopilotLegacy";
|
|
2258
|
+
WebviewMessageType["MigrateCopilotLegacy"] = "migrateCopilotLegacy";
|
|
2259
|
+
WebviewMessageType["UpdateCopilotSources"] = "updateCopilotSources";
|
|
2260
|
+
WebviewMessageType["UpdateCopilotUpdatePreview"] = "updateCopilotUpdatePreview";
|
|
2261
|
+
WebviewMessageType["UpdateCopilotLegacyPreview"] = "updateCopilotLegacyPreview";
|
|
2262
|
+
WebviewMessageType["GetUtilityModels"] = "getUtilityModels";
|
|
2263
|
+
WebviewMessageType["UtilityModelsResponse"] = "utilityModelsResponse";
|
|
2264
|
+
WebviewMessageType["UpdateUtilityModels"] = "updateUtilityModels";
|
|
2265
|
+
WebviewMessageType["GetServerProxyState"] = "getServerProxyState";
|
|
2266
|
+
WebviewMessageType["ServerProxyStateResponse"] = "serverProxyStateResponse";
|
|
2267
|
+
WebviewMessageType["SetServerProxyEnabled"] = "setServerProxyEnabled";
|
|
2268
|
+
WebviewMessageType["SetServerProxyAllowOverride"] = "setServerProxyAllowOverride";
|
|
2269
|
+
WebviewMessageType["GetCachedServerUrl"] = "getCachedServerUrl";
|
|
2270
|
+
WebviewMessageType["CachedServerUrlResponse"] = "cachedServerUrlResponse";
|
|
2271
|
+
return WebviewMessageType;
|
|
2272
|
+
}({});
|
|
2273
|
+
/**
|
|
2274
|
+
* Top-level const aliases for the BYO Utility Models message-type
|
|
2275
|
+
* members above. Re-exported as `export const` (rather than just enum
|
|
2276
|
+
* members) because `@serviceme/devtools-shared` ships as CommonJS — bare
|
|
2277
|
+
* `import { GetUtilityModels } from "@serviceme/devtools-shared"` from an ESM
|
|
2278
|
+
* module resolves to `undefined` unless the binding is also exported
|
|
2279
|
+
* as a top-level const. The webview's vitest tests compare against
|
|
2280
|
+
* these by reference; without the const aliases, every
|
|
2281
|
+
* `c[0] === UpdateUtilityModels` check matches the mount-time
|
|
2282
|
+
* `vscode.post(GetUtilityModels)` call (because `undefined ===
|
|
2283
|
+
* undefined` is true). Keep both the enum members AND the consts in
|
|
2284
|
+
* sync; Task 5 (extension handler) uses the enum members, the webview
|
|
2285
|
+
* component + tests use the consts.
|
|
2286
|
+
*/
|
|
2287
|
+
const GetUtilityModels = "getUtilityModels";
|
|
2288
|
+
const UtilityModelsResponse = "utilityModelsResponse";
|
|
2289
|
+
const UpdateUtilityModels = "updateUtilityModels";
|
|
2290
|
+
const SetCacheControlEnabled = "setCacheControlEnabled";
|
|
2291
|
+
const GetByomSettings = "getByomSettings";
|
|
2292
|
+
const ByomSettingsResponse = "byomSettingsResponse";
|
|
2293
|
+
const GetServerProxyState = "getServerProxyState";
|
|
2294
|
+
const ServerProxyStateResponse = "serverProxyStateResponse";
|
|
2295
|
+
const SetServerProxyEnabled = "setServerProxyEnabled";
|
|
2296
|
+
const SetServerProxyAllowOverride = "setServerProxyAllowOverride";
|
|
2297
|
+
const GetCachedServerUrl = "getCachedServerUrl";
|
|
2298
|
+
const CachedServerUrlResponse = "cachedServerUrlResponse";
|
|
2299
|
+
//#endregion
|
|
2300
|
+
//#region src/utils/safe-cast.ts
|
|
2301
|
+
/**
|
|
2302
|
+
* Environment-agnostic cast helpers.
|
|
2303
|
+
*
|
|
2304
|
+
* These centralize the handful of `as unknown as` / `JSON.parse` fallbacks
|
|
2305
|
+
* that used to be scattered across the codebase. They are intentionally
|
|
2306
|
+
* thin wrappers that preserve the exact runtime behavior of the original
|
|
2307
|
+
* inline casts — they exist for consistency and discoverability, not to
|
|
2308
|
+
* change semantics. No `protocol`-level imports are used here so the helpers
|
|
2309
|
+
* stay usable from any package (extension, server, webview) without pulling
|
|
2310
|
+
* in transport types.
|
|
2311
|
+
*/
|
|
2312
|
+
/**
|
|
2313
|
+
* Narrow an unknown payload into a typed shape.
|
|
2314
|
+
*
|
|
2315
|
+
* Equivalent to `raw as T`. Retains the original "blind cast" semantics used
|
|
2316
|
+
* for scheduled-task payloads: callers own the contract and we do not validate
|
|
2317
|
+
* the runtime shape here. Keeping the cast in one place makes the intent
|
|
2318
|
+
* (and the assumption) explicit and grep-able.
|
|
2319
|
+
*/
|
|
2295
2320
|
function parsePayload(raw) {
|
|
2296
|
-
|
|
2321
|
+
return raw;
|
|
2297
2322
|
}
|
|
2323
|
+
/**
|
|
2324
|
+
* Best-effort extraction of an `AbortSignal` from an inbound request object.
|
|
2325
|
+
*
|
|
2326
|
+
* The original code read `req.signal` via `req as unknown as { signal?: AbortSignal }`,
|
|
2327
|
+
* which would return whatever sat on `.signal` — including a non-`AbortSignal`
|
|
2328
|
+
* value. To avoid leaking an invalid signal into downstream `fetch`/`undici`
|
|
2329
|
+
* calls (where a non-`AbortSignal` signal throws), we only return the value when
|
|
2330
|
+
* it is a genuine `AbortSignal` instance; otherwise we return `undefined`, which
|
|
2331
|
+
* is the same as "no signal". In practice the request signal is always a real
|
|
2332
|
+
* `AbortSignal`, so behavior is unchanged for every production path.
|
|
2333
|
+
*/
|
|
2298
2334
|
function asAbortSignal(input) {
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
}
|
|
2304
|
-
}
|
|
2305
|
-
return void 0;
|
|
2335
|
+
if (input !== null && typeof input === "object") {
|
|
2336
|
+
const candidate = input.signal;
|
|
2337
|
+
if (candidate instanceof AbortSignal) return candidate;
|
|
2338
|
+
}
|
|
2306
2339
|
}
|
|
2340
|
+
/**
|
|
2341
|
+
* Parse a JSON string, returning `fallback` when parsing fails.
|
|
2342
|
+
*
|
|
2343
|
+
* Equivalent to wrapping `JSON.parse(text)` in a try/catch. Used to replace
|
|
2344
|
+
* the previous `response.json().catch(() => ({}))` patterns (callers pair this
|
|
2345
|
+
* with their own `.catch` so that a body-read failure still yields the same
|
|
2346
|
+
* fallback as a malformed-body failure).
|
|
2347
|
+
*/
|
|
2307
2348
|
function safeJson(text, fallback) {
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2349
|
+
try {
|
|
2350
|
+
return JSON.parse(text);
|
|
2351
|
+
} catch {
|
|
2352
|
+
return fallback;
|
|
2353
|
+
}
|
|
2313
2354
|
}
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
ByomSettingsResponse,
|
|
2317
|
-
CERTIFICATE_BUNDLE_FORMATS,
|
|
2318
|
-
CachedServerUrlResponse,
|
|
2319
|
-
DeviceAuthHeaders,
|
|
2320
|
-
GIT_REMOTE_HOST_ALIASES,
|
|
2321
|
-
GetByomSettings,
|
|
2322
|
-
GetCachedServerUrl,
|
|
2323
|
-
GetServerProxyState,
|
|
2324
|
-
GetUtilityModels,
|
|
2325
|
-
LISTABLE_PRESET_MODELS,
|
|
2326
|
-
LogLevel,
|
|
2327
|
-
MEDALSOFT_NUGET_PRIVATE_SOURCE,
|
|
2328
|
-
MEDALSOFT_PRIVATE_GATEWAY_URL,
|
|
2329
|
-
MODEL_METADATA,
|
|
2330
|
-
NAMESPACE_ALIASES,
|
|
2331
|
-
NAMESPACE_ALIAS_FAMILY,
|
|
2332
|
-
PRESET_MODEL_FAMILIES,
|
|
2333
|
-
PROVIDER_BASE_URL_PRESETS,
|
|
2334
|
-
PROVIDER_CACHE_CONTROL_METADATA,
|
|
2335
|
-
ServerProxyStateResponse,
|
|
2336
|
-
SetCacheControlEnabled,
|
|
2337
|
-
SetServerProxyAllowOverride,
|
|
2338
|
-
SetServerProxyEnabled,
|
|
2339
|
-
UpdateUtilityModels,
|
|
2340
|
-
UtilityModelsResponse,
|
|
2341
|
-
WebviewMessageType,
|
|
2342
|
-
__internal,
|
|
2343
|
-
asAbortSignal,
|
|
2344
|
-
buildGitHubLocalEmail,
|
|
2345
|
-
buildPresetModel,
|
|
2346
|
-
checkGitHubOrgMembership,
|
|
2347
|
-
createConsoleLogger,
|
|
2348
|
-
createDeviceRequestSignature,
|
|
2349
|
-
currencyForBaseUrl,
|
|
2350
|
-
effectiveAdapterType,
|
|
2351
|
-
fetchGitHubUser,
|
|
2352
|
-
getBuiltinProviderPreset,
|
|
2353
|
-
getGitHubOrgMembership,
|
|
2354
|
-
getPresetModelDisplayName,
|
|
2355
|
-
getProviderBaseUrlPresets,
|
|
2356
|
-
isGitHubLocalEmail,
|
|
2357
|
-
isProviderCacheControlAware,
|
|
2358
|
-
isValidCanonicalSlug,
|
|
2359
|
-
isWholePackageInstall,
|
|
2360
|
-
listPresetModelGroups,
|
|
2361
|
-
lookupModelMetadata,
|
|
2362
|
-
normalizeCanonicalSlug,
|
|
2363
|
-
normalizeErrorForLog,
|
|
2364
|
-
normalizeGitUrl,
|
|
2365
|
-
parsePayload,
|
|
2366
|
-
protocolForBaseUrl,
|
|
2367
|
-
resolvePrimaryEmail,
|
|
2368
|
-
safeJson,
|
|
2369
|
-
unionProviderModelWithPreset
|
|
2370
|
-
};
|
|
2355
|
+
//#endregion
|
|
2356
|
+
export { BUILTIN_PROVIDER_PRESETS, ByomSettingsResponse, CERTIFICATE_BUNDLE_FORMATS, CachedServerUrlResponse, DeviceAuthHeaders, GIT_REMOTE_HOST_ALIASES, GetByomSettings, GetCachedServerUrl, GetServerProxyState, GetUtilityModels, LISTABLE_PRESET_MODELS, LogLevel, MEDALSOFT_NUGET_PRIVATE_SOURCE, MEDALSOFT_PRIVATE_GATEWAY_URL, MODEL_METADATA, NAMESPACE_ALIASES, NAMESPACE_ALIAS_FAMILY, PRESET_MODEL_FAMILIES, PROVIDER_BASE_URL_PRESETS, PROVIDER_CACHE_CONTROL_METADATA, ServerProxyStateResponse, SetCacheControlEnabled, SetServerProxyAllowOverride, SetServerProxyEnabled, UpdateUtilityModels, UtilityModelsResponse, WebviewMessageType, __internal, asAbortSignal, buildGitHubLocalEmail, buildPresetModel, checkGitHubOrgMembership, createConsoleLogger, createDeviceRequestSignature, currencyForBaseUrl, effectiveAdapterType, fetchGitHubUser, getBuiltinProviderPreset, getGitHubOrgMembership, getPresetModelDisplayName, getProviderBaseUrlPresets, isGitHubLocalEmail, isProviderCacheControlAware, isValidCanonicalSlug, isWholePackageInstall, listPresetModelGroups, lookupModelMetadata, normalizeCanonicalSlug, normalizeErrorForLog, normalizeGitUrl, parsePayload, protocolForBaseUrl, registrationIdFromPackageId, resolvePrimaryEmail, safeJson, unionProviderModelWithPreset };
|