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