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