@lynn123411/dsh-a6api 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -1,457 +1,110 @@
1
1
  // src/server/catalog.ts
2
- var A6API_CATALOG = {
3
- // --- OpenAI Series ---
4
- "gpt-5.6-sol": {
5
- id: "gpt-5.6-sol",
6
- name: "GPT-5.6 Sol",
7
- brand: "OpenAI",
8
- contextWindow: 1048576,
9
- maxTokens: 65536,
10
- modalities: ["text", "image"],
11
- reasoningEfforts: { off: "", low: "low", medium: "medium", high: "high" },
12
- officialPriceMicros: { input: 4e6, output: 2e7, cacheRead: 4e5, cacheWrite: 5e6 }
13
- },
14
- "gpt-5.6-terra": {
15
- id: "gpt-5.6-terra",
16
- name: "GPT-5.6 Terra",
17
- brand: "OpenAI",
18
- contextWindow: 1048576,
19
- maxTokens: 65536,
20
- modalities: ["text", "image"],
21
- reasoningEfforts: { off: "", low: "low", medium: "medium", high: "high" },
22
- officialPriceMicros: { input: 4e6, output: 2e7, cacheRead: 4e5, cacheWrite: 5e6 }
23
- },
24
- "gpt-5.6-luna": {
25
- id: "gpt-5.6-luna",
26
- name: "GPT-5.6 Luna",
27
- brand: "OpenAI",
28
- contextWindow: 524288,
29
- maxTokens: 32768,
30
- modalities: ["text", "image"],
31
- reasoningEfforts: { off: "", low: "low", medium: "medium", high: "high" },
32
- officialPriceMicros: { input: 2e6, output: 1e7, cacheRead: 2e5, cacheWrite: 25e5 }
33
- },
34
- "gpt-5.6": {
35
- id: "gpt-5.6",
36
- name: "GPT-5.6",
37
- brand: "OpenAI",
38
- contextWindow: 1048576,
39
- maxTokens: 65536,
40
- modalities: ["text", "image"],
41
- reasoningEfforts: { off: "", low: "low", medium: "medium", high: "high" },
42
- officialPriceMicros: { input: 4e6, output: 2e7, cacheRead: 4e5, cacheWrite: 5e6 }
43
- },
44
- "gpt-5.5": {
45
- id: "gpt-5.5",
46
- name: "GPT-5.5",
47
- brand: "OpenAI",
48
- contextWindow: 1048576,
49
- maxTokens: 65536,
50
- modalities: ["text", "image"],
51
- reasoningEfforts: { off: "", low: "low", medium: "medium", high: "high" },
52
- officialPriceMicros: { input: 3e6, output: 15e6, cacheRead: 3e5, cacheWrite: 375e4 }
53
- },
54
- "gpt-5.4": {
55
- id: "gpt-5.4",
56
- name: "GPT-5.4",
57
- brand: "OpenAI",
58
- contextWindow: 524288,
59
- maxTokens: 32768,
60
- modalities: ["text", "image"],
61
- reasoningEfforts: { off: "", low: "low", medium: "medium", high: "high" },
62
- officialPriceMicros: { input: 25e5, output: 1e7, cacheRead: 25e4, cacheWrite: 3e6 }
63
- },
64
- "gpt-5.4-mini": {
65
- id: "gpt-5.4-mini",
66
- name: "GPT-5.4 Mini",
67
- brand: "OpenAI",
68
- contextWindow: 262144,
69
- maxTokens: 32768,
70
- modalities: ["text", "image"],
71
- reasoningEfforts: { off: "", low: "low", medium: "medium", high: "high" },
72
- officialPriceMicros: { input: 15e4, output: 6e5, cacheRead: 75e3, cacheWrite: 15e4 }
73
- },
74
- "gpt-5.3-codex": {
75
- id: "gpt-5.3-codex",
76
- name: "GPT-5.3 Codex",
77
- brand: "OpenAI",
78
- contextWindow: 262144,
79
- maxTokens: 32768,
80
- modalities: ["text"],
81
- officialPriceMicros: { input: 2e6, output: 8e6, cacheRead: 2e5, cacheWrite: 2e6 }
82
- },
83
- "gpt-5.3-codex-spark": {
84
- id: "gpt-5.3-codex-spark",
85
- name: "GPT-5.3 Codex Spark",
86
- brand: "OpenAI",
87
- contextWindow: 262144,
88
- maxTokens: 16384,
89
- modalities: ["text"],
90
- officialPriceMicros: { input: 1e6, output: 4e6, cacheRead: 1e5, cacheWrite: 1e6 }
91
- },
92
- "gpt-5.2": {
93
- id: "gpt-5.2",
94
- name: "GPT-5.2",
95
- brand: "OpenAI",
96
- contextWindow: 262144,
97
- maxTokens: 32768,
98
- modalities: ["text", "image"],
99
- officialPriceMicros: { input: 2e6, output: 8e6, cacheRead: 2e5, cacheWrite: 2e6 }
100
- },
101
- "gpt-4o": {
102
- id: "gpt-4o",
103
- name: "GPT-4o",
104
- brand: "OpenAI",
105
- contextWindow: 128e3,
106
- maxTokens: 16384,
107
- modalities: ["text", "image"],
108
- officialPriceMicros: { input: 25e5, output: 1e7, cacheRead: 125e4, cacheWrite: 25e5 }
109
- },
110
- "gpt-4o-mini": {
111
- id: "gpt-4o-mini",
112
- name: "GPT-4o Mini",
113
- brand: "OpenAI",
114
- contextWindow: 128e3,
115
- maxTokens: 16384,
116
- modalities: ["text", "image"],
117
- officialPriceMicros: { input: 15e4, output: 6e5, cacheRead: 75e3, cacheWrite: 15e4 }
118
- },
119
- // --- Anthropic Claude Series ---
120
- "claude-opus-5": {
121
- id: "claude-opus-5",
122
- name: "Claude Opus 5",
123
- brand: "Anthropic",
124
- contextWindow: 2e5,
125
- maxTokens: 32768,
126
- modalities: ["text", "image"],
127
- thinkingFormat: "anthropic",
128
- officialPriceMicros: { input: 15e6, output: 75e6, cacheRead: 15e5, cacheWrite: 1875e4 }
129
- },
130
- "claude-sonnet-5": {
131
- id: "claude-sonnet-5",
132
- name: "Claude Sonnet 5",
133
- brand: "Anthropic",
134
- contextWindow: 2e5,
135
- maxTokens: 32768,
136
- modalities: ["text", "image"],
137
- thinkingFormat: "anthropic",
138
- officialPriceMicros: { input: 3e6, output: 15e6, cacheRead: 3e5, cacheWrite: 375e4 }
139
- },
140
- "claude-fable-5": {
141
- id: "claude-fable-5",
142
- name: "Claude Fable 5",
143
- brand: "Anthropic",
144
- contextWindow: 2e5,
145
- maxTokens: 32768,
146
- modalities: ["text", "image"],
147
- officialPriceMicros: { input: 3e6, output: 15e6, cacheRead: 3e5, cacheWrite: 375e4 }
148
- },
149
- "claude-opus-4-8": {
150
- id: "claude-opus-4-8",
151
- name: "Claude Opus 4.8",
152
- brand: "Anthropic",
153
- contextWindow: 2e5,
154
- maxTokens: 16384,
155
- modalities: ["text", "image"],
156
- officialPriceMicros: { input: 15e6, output: 75e6, cacheRead: 15e5, cacheWrite: 1875e4 }
157
- },
158
- "claude-opus-4-7": {
159
- id: "claude-opus-4-7",
160
- name: "Claude Opus 4.7",
161
- brand: "Anthropic",
162
- contextWindow: 2e5,
163
- maxTokens: 16384,
164
- modalities: ["text", "image"],
165
- officialPriceMicros: { input: 15e6, output: 75e6, cacheRead: 15e5, cacheWrite: 1875e4 }
166
- },
167
- "claude-opus-4-6": {
168
- id: "claude-opus-4-6",
169
- name: "Claude Opus 4.6",
170
- brand: "Anthropic",
171
- contextWindow: 2e5,
172
- maxTokens: 16384,
173
- modalities: ["text", "image"],
174
- officialPriceMicros: { input: 15e6, output: 75e6, cacheRead: 15e5, cacheWrite: 1875e4 }
175
- },
176
- "claude-sonnet-4-6": {
177
- id: "claude-sonnet-4-6",
178
- name: "Claude Sonnet 4.6",
179
- brand: "Anthropic",
180
- contextWindow: 2e5,
181
- maxTokens: 16384,
182
- modalities: ["text", "image"],
183
- officialPriceMicros: { input: 3e6, output: 15e6, cacheRead: 3e5, cacheWrite: 375e4 }
184
- },
185
- "claude-haiku-4-5": {
186
- id: "claude-haiku-4-5",
187
- name: "Claude Haiku 4.5",
188
- brand: "Anthropic",
189
- contextWindow: 2e5,
190
- maxTokens: 8192,
191
- modalities: ["text", "image"],
192
- officialPriceMicros: { input: 25e4, output: 125e4, cacheRead: 25e3, cacheWrite: 312500 }
193
- },
194
- // --- DeepSeek Series ---
195
- "deepseek-v4-pro": {
196
- id: "deepseek-v4-pro",
197
- name: "DeepSeek V4 Pro",
198
- brand: "DeepSeek",
199
- contextWindow: 131072,
200
- maxTokens: 32768,
201
- modalities: ["text"],
202
- thinkingFormat: "deepseek",
203
- officialPriceMicros: { input: 55e4, output: 219e4, cacheRead: 14e4, cacheWrite: 55e4 }
204
- },
205
- "deepseek-v4-flash": {
206
- id: "deepseek-v4-flash",
207
- name: "DeepSeek V4 Flash",
208
- brand: "DeepSeek",
209
- contextWindow: 131072,
210
- maxTokens: 16384,
211
- modalities: ["text"],
212
- officialPriceMicros: { input: 14e4, output: 28e4, cacheRead: 14e3, cacheWrite: 14e4 }
213
- },
214
- "DeepSeek-V4-Flash-0731": {
215
- id: "DeepSeek-V4-Flash-0731",
216
- name: "DeepSeek V4 Flash (0731)",
217
- brand: "DeepSeek",
218
- contextWindow: 131072,
219
- maxTokens: 16384,
220
- modalities: ["text"],
221
- officialPriceMicros: { input: 14e4, output: 28e4, cacheRead: 14e3, cacheWrite: 14e4 }
222
- },
223
- "deepseek-v4-pro-0813": {
224
- id: "deepseek-v4-pro-0813",
225
- name: "DeepSeek V4 Pro (0813)",
226
- brand: "DeepSeek",
227
- contextWindow: 131072,
228
- maxTokens: 32768,
229
- modalities: ["text"],
230
- thinkingFormat: "deepseek",
231
- officialPriceMicros: { input: 55e4, output: 219e4, cacheRead: 14e4, cacheWrite: 55e4 }
232
- },
233
- "deepseek-v4-flash-vision-exp": {
234
- id: "deepseek-v4-flash-vision-exp",
235
- name: "DeepSeek V4 Flash Vision Exp",
236
- brand: "DeepSeek",
237
- contextWindow: 131072,
238
- maxTokens: 16384,
239
- modalities: ["text", "image"],
240
- officialPriceMicros: { input: 14e4, output: 28e4, cacheRead: 14e3, cacheWrite: 14e4 }
241
- },
242
- "deepseek-chat": {
243
- id: "deepseek-chat",
244
- name: "DeepSeek Chat",
245
- brand: "DeepSeek",
246
- contextWindow: 65536,
247
- maxTokens: 8192,
248
- modalities: ["text"],
249
- officialPriceMicros: { input: 14e4, output: 28e4, cacheRead: 14e3, cacheWrite: 14e4 }
250
- },
251
- "deepseek-reasoner": {
252
- id: "deepseek-reasoner",
253
- name: "DeepSeek Reasoner",
254
- brand: "DeepSeek",
255
- contextWindow: 65536,
256
- maxTokens: 16384,
257
- modalities: ["text"],
258
- thinkingFormat: "deepseek",
259
- officialPriceMicros: { input: 55e4, output: 219e4, cacheRead: 14e4, cacheWrite: 55e4 }
260
- },
261
- // --- Google Gemini Series ---
262
- "gemini-3.7-flash": {
263
- id: "gemini-3.7-flash",
264
- name: "Gemini 3.7 Flash",
265
- brand: "Google",
266
- contextWindow: 1048576,
267
- maxTokens: 65536,
268
- modalities: ["text", "image"],
269
- reasoningEfforts: { off: "", low: "low", medium: "medium", high: "high" },
270
- officialPriceMicros: { input: 1e5, output: 4e5, cacheRead: 25e3, cacheWrite: 1e5 }
271
- },
272
- "gemini-3.6-flash": {
273
- id: "gemini-3.6-flash",
274
- name: "Gemini 3.6 Flash",
275
- brand: "Google",
276
- contextWindow: 1048576,
277
- maxTokens: 65536,
278
- modalities: ["text", "image"],
279
- officialPriceMicros: { input: 1e5, output: 4e5, cacheRead: 25e3, cacheWrite: 1e5 }
280
- },
281
- "gemini-3.5-flash": {
282
- id: "gemini-3.5-flash",
283
- name: "Gemini 3.5 Flash",
284
- brand: "Google",
285
- contextWindow: 1048576,
286
- maxTokens: 65536,
287
- modalities: ["text", "image"],
288
- officialPriceMicros: { input: 75e3, output: 3e5, cacheRead: 18750, cacheWrite: 75e3 }
289
- },
290
- "gemini-3.5-flash-high": {
291
- id: "gemini-3.5-flash-high",
292
- name: "Gemini 3.5 Flash High",
293
- brand: "Google",
294
- contextWindow: 1048576,
295
- maxTokens: 65536,
296
- modalities: ["text", "image"],
297
- reasoningEfforts: { high: "high" },
298
- officialPriceMicros: { input: 75e3, output: 3e5, cacheRead: 18750, cacheWrite: 75e3 }
299
- },
300
- "gemini-3.1-pro-preview": {
301
- id: "gemini-3.1-pro-preview",
302
- name: "Gemini 3.1 Pro Preview",
303
- brand: "Google",
304
- contextWindow: 2097152,
305
- maxTokens: 65536,
306
- modalities: ["text", "image"],
307
- officialPriceMicros: { input: 125e4, output: 5e6, cacheRead: 312500, cacheWrite: 125e4 }
308
- },
309
- "gemini-3.1-flash-lite-preview": {
310
- id: "gemini-3.1-flash-lite-preview",
311
- name: "Gemini 3.1 Flash Lite Preview",
312
- brand: "Google",
313
- contextWindow: 1048576,
314
- maxTokens: 32768,
315
- modalities: ["text", "image"],
316
- officialPriceMicros: { input: 75e3, output: 3e5, cacheRead: 18750, cacheWrite: 75e3 }
317
- },
318
- "gemini-2.5-pro": {
319
- id: "gemini-2.5-pro",
320
- name: "Gemini 2.5 Pro",
321
- brand: "Google",
322
- contextWindow: 2097152,
323
- maxTokens: 65536,
324
- modalities: ["text", "image"],
325
- officialPriceMicros: { input: 125e4, output: 5e6, cacheRead: 312500, cacheWrite: 125e4 }
326
- },
327
- "gemini-2.5-flash": {
328
- id: "gemini-2.5-flash",
329
- name: "Gemini 2.5 Flash",
330
- brand: "Google",
331
- contextWindow: 1048576,
332
- maxTokens: 32768,
333
- modalities: ["text", "image"],
334
- officialPriceMicros: { input: 75e3, output: 3e5, cacheRead: 18750, cacheWrite: 75e3 }
335
- },
336
- // --- xAI Grok Series ---
337
- "grok-4.6": {
338
- id: "grok-4.6",
339
- name: "Grok 4.6",
340
- brand: "xAI",
341
- contextWindow: 262144,
342
- maxTokens: 32768,
343
- modalities: ["text", "image"],
344
- reasoningEfforts: { off: "", high: "high" },
345
- officialPriceMicros: { input: 2e6, output: 1e7, cacheRead: 2e5, cacheWrite: 25e5 }
346
- },
347
- "grok-4.5": {
348
- id: "grok-4.5",
349
- name: "Grok 4.5",
350
- brand: "xAI",
351
- contextWindow: 131072,
352
- maxTokens: 16384,
353
- modalities: ["text", "image"],
354
- officialPriceMicros: { input: 2e6, output: 1e7, cacheRead: 2e5, cacheWrite: 25e5 }
355
- },
356
- "grok-4.3": {
357
- id: "grok-4.3",
358
- name: "Grok 4.3",
359
- brand: "xAI",
360
- contextWindow: 131072,
361
- maxTokens: 16384,
362
- modalities: ["text"],
363
- officialPriceMicros: { input: 2e6, output: 1e7, cacheRead: 2e5, cacheWrite: 25e5 }
364
- },
365
- // --- Zhipu GLM Series ---
366
- "glm-5.3": {
367
- id: "glm-5.3",
368
- name: "GLM 5.3",
369
- brand: "Zhipu",
370
- contextWindow: 131072,
371
- maxTokens: 32768,
372
- modalities: ["text", "image"],
373
- thinkingFormat: "deepseek",
374
- officialPriceMicros: { input: 1e6, output: 4e6, cacheRead: 2e5, cacheWrite: 1e6 }
375
- },
376
- "glm-5.3-flash": {
377
- id: "glm-5.3-flash",
378
- name: "GLM 5.3 Flash",
379
- brand: "Zhipu",
380
- contextWindow: 131072,
381
- maxTokens: 16384,
382
- modalities: ["text"],
383
- officialPriceMicros: { input: 1e5, output: 2e5, cacheRead: 1e4, cacheWrite: 1e5 }
384
- },
385
- "glm-5.2": {
386
- id: "glm-5.2",
387
- name: "GLM 5.2",
388
- brand: "Zhipu",
389
- contextWindow: 131072,
390
- maxTokens: 16384,
391
- modalities: ["text"],
392
- officialPriceMicros: { input: 1e6, output: 4e6, cacheRead: 2e5, cacheWrite: 1e6 }
393
- },
394
- // --- Moonshot Kimi Series ---
395
- "kimi-k3": {
396
- id: "kimi-k3",
397
- name: "Kimi K3",
398
- brand: "Moonshot",
399
- contextWindow: 262144,
400
- maxTokens: 32768,
401
- modalities: ["text", "image"],
402
- thinkingFormat: "deepseek",
403
- officialPriceMicros: { input: 15e5, output: 6e6, cacheRead: 3e5, cacheWrite: 15e5 }
404
- },
405
- "kimi-k2.7-code": {
406
- id: "kimi-k2.7-code",
407
- name: "Kimi K2.7 Code",
408
- brand: "Moonshot",
409
- contextWindow: 262144,
410
- maxTokens: 32768,
411
- modalities: ["text"],
412
- officialPriceMicros: { input: 1e6, output: 4e6, cacheRead: 2e5, cacheWrite: 1e6 }
413
- },
414
- // --- Alibaba Qwen Series ---
415
- "qwen3.8-max": {
416
- id: "qwen3.8-max",
417
- name: "Qwen 3.8 Max",
418
- brand: "Alibaba",
419
- contextWindow: 1048576,
420
- maxTokens: 32768,
421
- modalities: ["text", "image"],
422
- thinkingFormat: "deepseek",
423
- officialPriceMicros: { input: 2e6, output: 8e6, cacheRead: 4e5, cacheWrite: 2e6 }
424
- },
425
- "qwen3.8-flash": {
426
- id: "qwen3.8-flash",
427
- name: "Qwen 3.8 Flash",
428
- brand: "Alibaba",
429
- contextWindow: 1048576,
430
- maxTokens: 16384,
431
- modalities: ["text"],
432
- officialPriceMicros: { input: 1e5, output: 3e5, cacheRead: 2e4, cacheWrite: 1e5 }
433
- },
434
- "qwen3.7-max": {
435
- id: "qwen3.7-max",
436
- name: "Qwen 3.7 Max",
437
- brand: "Alibaba",
438
- contextWindow: 1048576,
439
- maxTokens: 32768,
440
- modalities: ["text", "image"],
441
- thinkingFormat: "deepseek",
442
- officialPriceMicros: { input: 2e6, output: 8e6, cacheRead: 4e5, cacheWrite: 2e6 }
443
- },
444
- // --- MiniMax Series ---
445
- "minimax-m3": {
446
- id: "minimax-m3",
447
- name: "MiniMax M3",
448
- brand: "MiniMax",
449
- contextWindow: 1048576,
450
- maxTokens: 32768,
451
- modalities: ["text"],
452
- officialPriceMicros: { input: 1e6, output: 4e6, cacheRead: 2e5, cacheWrite: 1e6 }
2
+ import fs from "node:fs";
3
+ import fsp from "node:fs/promises";
4
+ import path from "node:path";
5
+ import os from "node:os";
6
+ var CATALOG_VERSION = 1;
7
+ function dshHome() {
8
+ return process.env.DSH_HOME || path.join(os.homedir(), ".dsh");
9
+ }
10
+ function catalogFile() {
11
+ return path.join(dshHome(), "dsh-a6api-catalog.json");
12
+ }
13
+ var catalogCache = null;
14
+ function ensureLoaded() {
15
+ if (catalogCache) return catalogCache;
16
+ try {
17
+ const raw = fs.readFileSync(catalogFile(), "utf8");
18
+ const j = JSON.parse(raw);
19
+ catalogCache = Array.isArray(j?.entries) ? j.entries.filter((e) => e && typeof e.id === "string") : [];
20
+ } catch {
21
+ catalogCache = [];
453
22
  }
23
+ const loaded = catalogCache;
24
+ return loaded;
25
+ }
26
+ function getCatalog() {
27
+ return ensureLoaded();
28
+ }
29
+ function getCatalogEntry(id) {
30
+ const t = id.toLowerCase();
31
+ return ensureLoaded().find((e) => e.id.toLowerCase() === t);
32
+ }
33
+ var writeChain = Promise.resolve();
34
+ function enqueueWrite(fn) {
35
+ const next = writeChain.then(fn, fn);
36
+ writeChain = next.then(
37
+ () => {
38
+ },
39
+ () => {
40
+ }
41
+ );
42
+ return next;
43
+ }
44
+ async function writeCatalog(entries) {
45
+ const file = catalogFile();
46
+ await fsp.mkdir(path.dirname(file), { recursive: true });
47
+ const tmp = `${file}.tmp`;
48
+ await fsp.writeFile(tmp, JSON.stringify({ version: CATALOG_VERSION, entries }, null, 2), {
49
+ encoding: "utf8",
50
+ mode: 420
51
+ });
52
+ await fsp.rename(tmp, file);
53
+ catalogCache = entries;
54
+ }
55
+ async function upsertCatalogEntries(entries) {
56
+ await enqueueWrite(async () => {
57
+ const cur = ensureLoaded();
58
+ const map = /* @__PURE__ */ new Map();
59
+ for (const e of cur) map.set(e.id.toLowerCase(), e);
60
+ for (const e of entries) {
61
+ const clean = Object.fromEntries(Object.entries(e).filter(([, v]) => v !== void 0));
62
+ const key = clean.id.toLowerCase();
63
+ const prev = map.get(key);
64
+ map.set(key, prev ? { ...prev, ...clean } : clean);
65
+ }
66
+ await writeCatalog([...map.values()]);
67
+ });
68
+ }
69
+ async function clearCatalog() {
70
+ await enqueueWrite(async () => {
71
+ await writeCatalog([]);
72
+ });
73
+ }
74
+ async function updateCatalogEntry(id, patch) {
75
+ let result = null;
76
+ await enqueueWrite(async () => {
77
+ const cur = ensureLoaded();
78
+ const idx = cur.findIndex((e) => e.id.toLowerCase() === id.toLowerCase());
79
+ if (idx < 0) return;
80
+ const next = { ...cur[idx], ...patch, id: cur[idx].id, updatedAt: Date.now() };
81
+ for (const k of Object.keys(patch)) {
82
+ if (patch[k] === null) delete next[k];
83
+ }
84
+ const list = cur.slice();
85
+ list[idx] = next;
86
+ await writeCatalog(list);
87
+ result = next;
88
+ });
89
+ return result;
90
+ }
91
+ var BRAND_NORMALIZE = {
92
+ meituan: "MeiTuan",
93
+ tencent: "Tencent",
94
+ xiaomi: "Xiaomi",
95
+ openai: "OpenAI",
96
+ anthropic: "Anthropic",
97
+ google: "Google",
98
+ deepseek: "DeepSeek",
99
+ zhipu: "Zhipu",
100
+ moonshot: "Moonshot",
101
+ alibaba: "Alibaba",
102
+ minimax: "MiniMax"
454
103
  };
104
+ function normalizeBrand(raw) {
105
+ const k = String(raw || "").toLowerCase();
106
+ return BRAND_NORMALIZE[k] || raw || "Other";
107
+ }
455
108
  function inferBrand(modelId) {
456
109
  const m = modelId.toLowerCase();
457
110
  if (m.startsWith("gpt") || m.startsWith("o1") || m.startsWith("o3") || m.startsWith("chatgpt")) return "OpenAI";
@@ -467,29 +120,194 @@ function inferBrand(modelId) {
467
120
  if (m.startsWith("hunyuan") || m.startsWith("tencent") || m.startsWith("hy")) return "Tencent";
468
121
  return "Other";
469
122
  }
123
+ var DEFAULT_REASONING_EFFORTS = {
124
+ off: null,
125
+ minimal: "minimal",
126
+ low: "low",
127
+ medium: "medium",
128
+ high: "high",
129
+ xhigh: "xhigh",
130
+ max: "max"
131
+ };
470
132
  function resolveModelMeta(modelId) {
471
- if (A6API_CATALOG[modelId]) {
472
- return A6API_CATALOG[modelId];
133
+ const entry = getCatalogEntry(modelId);
134
+ if (entry) {
135
+ return {
136
+ id: entry.id,
137
+ name: entry.name || entry.id,
138
+ brand: entry.brand || inferBrand(entry.id),
139
+ contextWindow: entry.contextWindow ?? 262144,
140
+ maxTokens: entry.maxTokens ?? 32768,
141
+ modalities: entry.input && entry.input.length > 0 ? [...entry.input] : ["text"],
142
+ ...entry.reasoningEfforts && typeof entry.reasoningEfforts === "object" && Object.keys(entry.reasoningEfforts).length > 0 ? {
143
+ reasoningEfforts: Object.fromEntries(
144
+ Object.entries(entry.reasoningEfforts).filter(([, v]) => v !== null && v !== void 0)
145
+ )
146
+ } : {}
147
+ };
473
148
  }
474
149
  const lowerId = modelId.toLowerCase();
475
- for (const [k, v] of Object.entries(A6API_CATALOG)) {
476
- if (k.toLowerCase() === lowerId) {
477
- return v;
478
- }
479
- }
480
- const brand = inferBrand(modelId);
481
150
  const isVision = lowerId.includes("vision") || lowerId.includes("vl") || lowerId.includes("image");
482
151
  const hasReasoning = lowerId.includes("think") || lowerId.includes("reason") || lowerId.includes("pro") || lowerId.includes("sol");
483
152
  return {
484
153
  id: modelId,
485
154
  name: modelId,
486
- brand,
155
+ brand: inferBrand(modelId),
487
156
  contextWindow: 262144,
488
157
  maxTokens: 32768,
489
158
  modalities: isVision ? ["text", "image"] : ["text"],
490
159
  ...hasReasoning ? { thinkingFormat: "deepseek" } : {}
491
160
  };
492
161
  }
162
+ var MARKET_SEARCH = "https://a6api.com/api/marketplace/channels/search";
163
+ var PAGE_SIZE = 500;
164
+ var CONCURRENCY = 6;
165
+ function buildWebHeaders(userId, accessToken) {
166
+ const headers = {
167
+ Accept: "application/json",
168
+ "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36"
169
+ };
170
+ const uid = userId ? String(userId).trim() : "";
171
+ const token = accessToken ? String(accessToken).trim() : "";
172
+ if (uid) headers["New-Api-User"] = uid;
173
+ if (token) {
174
+ headers["Authorization"] = token;
175
+ headers["Cookie"] = `session=${token}`;
176
+ }
177
+ return headers;
178
+ }
179
+ async function fetchMarketplaceModels(userId, accessToken) {
180
+ if (!userId && !accessToken) {
181
+ throw new Error("\u9700\u5148\u914D\u7F6E\u7CFB\u7EDF\u8BBF\u95EE\u4EE4\u724C\u624D\u80FD\u83B7\u53D6\u5E02\u573A\u6A21\u578B");
182
+ }
183
+ const headers = buildWebHeaders(userId, accessToken);
184
+ const first = await (async () => {
185
+ const res = await fetch(`${MARKET_SEARCH}?view=list&page=1&page_size=${PAGE_SIZE}`, {
186
+ headers,
187
+ signal: AbortSignal.timeout(15e3)
188
+ });
189
+ if (!res.ok) throw new Error(`A6API \u5E02\u573A\u63A5\u53E3 HTTP ${res.status}`);
190
+ return res.json();
191
+ })();
192
+ const total = Number(first?.data?.total || 0);
193
+ const pages = Math.max(1, Math.ceil(total / PAGE_SIZE));
194
+ const all = [...first?.data?.items || []];
195
+ let failedPages = 0;
196
+ let idx = 2;
197
+ const worker = async () => {
198
+ while (idx <= pages) {
199
+ const p = idx++;
200
+ try {
201
+ const res = await fetch(`${MARKET_SEARCH}?view=list&page=${p}&page_size=${PAGE_SIZE}`, {
202
+ headers,
203
+ signal: AbortSignal.timeout(15e3)
204
+ });
205
+ const j = await res.json();
206
+ all.push(...j?.data?.items || []);
207
+ } catch (err) {
208
+ failedPages++;
209
+ console.warn("[dsh-a6api] fetchMarketplaceModels page", p, "failed:", err?.message || err);
210
+ }
211
+ }
212
+ };
213
+ await Promise.all(Array.from({ length: CONCURRENCY }, () => worker()));
214
+ const byModel = /* @__PURE__ */ new Map();
215
+ for (const it of all) {
216
+ const name2 = it?.model_name;
217
+ if (!name2) continue;
218
+ if (!byModel.has(name2)) {
219
+ byModel.set(name2, {
220
+ id: String(name2),
221
+ brand: normalizeBrand(it?.brand),
222
+ // 默认声明 DSH 全部思考档位(用户可修改;upsert 仅补缺失字段,已有自定义不受影响)
223
+ reasoningEfforts: { ...DEFAULT_REASONING_EFFORTS }
224
+ });
225
+ }
226
+ }
227
+ const models = [...byModel.values()].sort((a, b) => a.id.localeCompare(b.id));
228
+ return { models, failedPages };
229
+ }
230
+ var OPENROUTER_URL = "https://openrouter.ai/api/v1/models";
231
+ var OR_TTL_MS = 60 * 60 * 1e3;
232
+ var orCache = null;
233
+ async function getOpenRouterModels() {
234
+ if (orCache && Date.now() - orCache.at < OR_TTL_MS) return orCache.models;
235
+ try {
236
+ const res = await fetch(OPENROUTER_URL, {
237
+ headers: { Accept: "application/json", "User-Agent": "Mozilla/5.0" },
238
+ signal: AbortSignal.timeout(2e4)
239
+ });
240
+ if (!res.ok) throw new Error(`OpenRouter HTTP ${res.status}`);
241
+ const j = await res.json();
242
+ if (!Array.isArray(j?.data)) throw new Error("OpenRouter \u8FD4\u56DE\u683C\u5F0F\u5F02\u5E38");
243
+ orCache = { at: Date.now(), models: j.data };
244
+ return orCache.models;
245
+ } catch (err) {
246
+ if (orCache) return orCache.models;
247
+ throw err;
248
+ }
249
+ }
250
+ var tailOf = (s) => String(s).split("/").pop() || "";
251
+ function matchOpenRouter(models, target) {
252
+ const t = target.toLowerCase();
253
+ const tTail = tailOf(t);
254
+ const norm = (s) => s.toLowerCase().replace(/[\s_.]+/g, "-");
255
+ const byTail = models.find((m) => tailOf(String(m.id)).toLowerCase() === tTail);
256
+ if (byTail) return byTail;
257
+ const nTail = norm(tTail);
258
+ const byNorm = models.find((m) => norm(tailOf(String(m.id))) === nTail);
259
+ if (byNorm) return byNorm;
260
+ const stripped = tTail.replace(/-(?:\d{4}-\d{2}-\d{2}|\d{8}|-\d{2,4}|\d\.\d+)$/, "");
261
+ if (stripped && stripped !== tTail) {
262
+ const byStripped = models.find((m) => tailOf(String(m.id)).toLowerCase() === stripped);
263
+ if (byStripped) return byStripped;
264
+ }
265
+ const tSeg = t.split("/").pop() || t;
266
+ return models.find((m) => {
267
+ const id = String(m.id).toLowerCase();
268
+ if (!id.includes(t)) return false;
269
+ const tailSeg = id.split("/").pop() || "";
270
+ return tailSeg === t || tailSeg.split("-").includes(tSeg);
271
+ }) || null;
272
+ }
273
+ function orModalities(m) {
274
+ const set = /* @__PURE__ */ new Set();
275
+ const input = m?.architecture?.input_modalities || [];
276
+ for (const mod of input) {
277
+ const k = String(mod).toLowerCase();
278
+ if (k === "text") set.add("text");
279
+ else if (k === "image") set.add("image");
280
+ }
281
+ return [...set];
282
+ }
283
+ async function queryOpenRouter(ids) {
284
+ const uniq = [...new Set(ids.map((s) => String(s).trim()).filter(Boolean))];
285
+ if (uniq.length === 0) return { updated: [], notFound: [] };
286
+ const models = await getOpenRouterModels();
287
+ const updated = [];
288
+ const notFound = [];
289
+ for (const id of uniq) {
290
+ const hit = matchOpenRouter(models, id);
291
+ if (!hit) {
292
+ notFound.push(id);
293
+ continue;
294
+ }
295
+ const patch = {
296
+ updatedAt: Date.now()
297
+ };
298
+ const ctx = hit.context_length ?? hit.top_provider?.context_length;
299
+ if (ctx != null && Number(ctx) > 0) patch.contextWindow = Number(ctx);
300
+ const maxOut = hit.top_provider?.max_completion_tokens ?? hit.max_completion_tokens;
301
+ if (maxOut != null && Number(maxOut) > 0) patch.maxTokens = Number(maxOut);
302
+ const mods = orModalities(hit);
303
+ if (mods.length > 0) patch.input = mods;
304
+ updated.push({ id, ...patch });
305
+ }
306
+ if (updated.length > 0) {
307
+ await upsertCatalogEntries(updated);
308
+ }
309
+ return { updated, notFound };
310
+ }
493
311
 
494
312
  // src/server/a6api-client.ts
495
313
  function cleanBaseUrl(url) {
@@ -516,7 +334,7 @@ function formatCnyPrice(micros, exchangeRate = 6.7209) {
516
334
  if (cny < 1) return `\xA5${cny.toFixed(4)}`;
517
335
  return `\xA5${cny.toFixed(3)}`;
518
336
  }
519
- function buildWebHeaders(userId, sessionCookie) {
337
+ function buildWebHeaders2(userId, accessToken) {
520
338
  const headers = {
521
339
  Accept: "application/json",
522
340
  "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36"
@@ -525,8 +343,8 @@ function buildWebHeaders(userId, sessionCookie) {
525
343
  if (uid) {
526
344
  headers["New-Api-User"] = uid;
527
345
  }
528
- if (sessionCookie && sessionCookie.trim()) {
529
- const raw = sessionCookie.trim();
346
+ if (accessToken && accessToken.trim()) {
347
+ const raw = accessToken.trim();
530
348
  if (raw.startsWith("session=")) {
531
349
  headers["Cookie"] = raw;
532
350
  } else if (raw.includes(";")) {
@@ -538,7 +356,7 @@ function buildWebHeaders(userId, sessionCookie) {
538
356
  }
539
357
  return headers;
540
358
  }
541
- async function fetchBalance(baseURL, apiKey, userId, sessionCookie) {
359
+ async function fetchBalance(baseURL, apiKey, userId, accessToken) {
542
360
  const cleanUrl = cleanBaseUrl(baseURL);
543
361
  let hasAccountAuth = false;
544
362
  let accountBalanceUsd = 0;
@@ -548,13 +366,13 @@ async function fetchBalance(baseURL, apiKey, userId, sessionCookie) {
548
366
  let responseUserId = userId;
549
367
  let usedUsd = 0;
550
368
  let requestCount = 0;
551
- if (userId || sessionCookie) {
369
+ if (userId || accessToken) {
552
370
  const candidates = ["https://a6api.com/api/user/self", `${cleanUrl}/api/user/self`];
553
371
  const uniqueCandidates = [...new Set(candidates)];
554
372
  for (const url of uniqueCandidates) {
555
373
  try {
556
374
  const res = await fetch(url, {
557
- headers: buildWebHeaders(userId, sessionCookie),
375
+ headers: buildWebHeaders2(userId, accessToken),
558
376
  signal: AbortSignal.timeout(6e3)
559
377
  });
560
378
  if (res.ok) {
@@ -642,11 +460,11 @@ async function fetchTokenModels(baseURL, apiKey) {
642
460
  }
643
461
  return [];
644
462
  }
645
- async function fetchRecentLogs(userId, sessionCookie, limit = 30) {
646
- if (!userId && !sessionCookie) return [];
463
+ async function fetchRecentLogs(userId, accessToken, limit = 30) {
464
+ if (!userId && !accessToken) return [];
647
465
  try {
648
466
  const res = await fetch(`https://a6api.com/api/log/self?p=1&page_size=${limit}&type=0`, {
649
- headers: buildWebHeaders(userId, sessionCookie),
467
+ headers: buildWebHeaders2(userId, accessToken),
650
468
  signal: AbortSignal.timeout(8e3)
651
469
  });
652
470
  if (res.ok) {
@@ -681,6 +499,7 @@ async function fetchRecentLogs(userId, sessionCookie, limit = 30) {
681
499
  model_name: it.model_name || it.marketplace_model_name || "",
682
500
  channel: channelId,
683
501
  channel_name: it.channel_name || (channelId ? `\u5546\u6237 #${channelId}` : void 0),
502
+ token_id: it.token_id !== void 0 && it.token_id !== null ? Number(it.token_id) : void 0,
684
503
  prompt_tokens: Number(it.prompt_tokens || 0),
685
504
  completion_tokens: Number(it.completion_tokens || 0),
686
505
  use_time: Number(it.use_time || 0),
@@ -700,7 +519,7 @@ async function fetchRecentLogs(userId, sessionCookie, limit = 30) {
700
519
  }
701
520
  return [];
702
521
  }
703
- async function fetchChannelDetails(channelId, userId, sessionCookie, targetModelName, logSnapshot) {
522
+ async function fetchChannelDetails(channelId, userId, accessToken, targetModelName, logSnapshot) {
704
523
  if (!channelId) return null;
705
524
  const targetName = targetModelName || "";
706
525
  const meta = resolveModelMeta(targetName);
@@ -708,7 +527,7 @@ async function fetchChannelDetails(channelId, userId, sessionCookie, targetModel
708
527
  const res = await fetch(
709
528
  `https://a6api.com/api/marketplace/channels/search?channel_id=${channelId}&view=list&page=1&page_size=20`,
710
529
  {
711
- headers: buildWebHeaders(userId, sessionCookie),
530
+ headers: buildWebHeaders2(userId, accessToken),
712
531
  signal: AbortSignal.timeout(8e3)
713
532
  }
714
533
  );
@@ -781,7 +600,8 @@ async function fetchChannelDetails(channelId, userId, sessionCookie, targetModel
781
600
  const ratioFormatted = ratioCny.toFixed(4);
782
601
  return {
783
602
  listing_id: item.listing_id,
784
- channel_id: item.channel_id,
603
+ // 归一化为数字,避免官方返回字符串 channel_id 导致严格相等比较失效(接管/固定判定依赖)
604
+ channel_id: item.channel_id !== void 0 && item.channel_id !== null ? Number(item.channel_id) : channelId,
785
605
  channel_name: item.channel_name || `\u5546\u6237 #${channelId}`,
786
606
  supplier_name: item.supplier_name || item.supplier_nickname || "GPT\u4F4E\u4EF7",
787
607
  supplier_id: item.supplier_id || 290,
@@ -819,8 +639,10 @@ async function fetchChannelDetails(channelId, userId, sessionCookie, targetModel
819
639
  last_success_text: formatRelativeTime(lastSuccessAt),
820
640
  authenticity_guaranteed: Boolean(item.authenticity_guaranteed),
821
641
  authenticity_badge: item.authenticity_guarantee_badge_key,
822
- is_pinned: Boolean(item.pin_status === "pinned" || item.is_pinned),
823
- user_channel_disabled: Boolean(item.user_channel_disabled),
642
+ // 官方固定状态值是 pin_here / pin_elsewhere(早期代码误用 'pinned',已修正)
643
+ is_pinned: item.pin_status === "pin_here" || item.route_status === "pin_here",
644
+ pin_status: typeof item.pin_status === "string" ? item.pin_status : void 0,
645
+ user_channel_disabled: Boolean(item.user_channel_disabled || item.route_status === "user_disabled"),
824
646
  supplier_channel_disabled: Boolean(item.supplier_channel_disabled),
825
647
  raw: item
826
648
  };
@@ -892,13 +714,13 @@ async function fetchChannelDetails(channelId, userId, sessionCookie, targetModel
892
714
  }
893
715
  return null;
894
716
  }
895
- async function fetchPriceFluctuation(userId, sessionCookie, accessToken) {
896
- const token = (accessToken || sessionCookie || "").trim();
717
+ async function fetchPriceFluctuation(userId, accessToken) {
718
+ const token = (accessToken || "").trim();
897
719
  const uid = (userId || "").trim();
898
720
  if (!uid && !token) {
899
721
  return { pendingCount: 0, unseenCount: 0, totalCount: 0, authError: false };
900
722
  }
901
- const headers = buildWebHeaders(uid || void 0, token || void 0);
723
+ const headers = buildWebHeaders2(uid || void 0, token || void 0);
902
724
  const url = "https://a6api.com/api/marketplace/price-notices";
903
725
  try {
904
726
  const res = await fetch(url, { headers, signal: AbortSignal.timeout(8e3) });
@@ -952,6 +774,158 @@ async function fetchPriceFluctuation(userId, sessionCookie, accessToken) {
952
774
  return { pendingCount: 0, unseenCount: 0, totalCount: 0 };
953
775
  }
954
776
  }
777
+ function parseMarketplaceResult(json) {
778
+ if (!json) return { ok: false, message: "\u7A7A\u54CD\u5E94" };
779
+ const top = json.success === false ? json : null;
780
+ const inner = json.data && json.data.success === false ? json.data : null;
781
+ if (top) return { ok: false, message: top.message || "\u64CD\u4F5C\u5931\u8D25" };
782
+ if (inner) return { ok: false, message: inner.message || "\u64CD\u4F5C\u5931\u8D25" };
783
+ return { ok: true, data: json.data };
784
+ }
785
+ function extractArray(json) {
786
+ if (!json) return [];
787
+ if (Array.isArray(json)) return json;
788
+ if (Array.isArray(json.data)) return json.data;
789
+ if (json.data && Array.isArray(json.data.data)) return json.data.data;
790
+ if (json.data && Array.isArray(json.data.items)) return json.data.items;
791
+ if (json.data && json.data.data && Array.isArray(json.data.data.items)) return json.data.data.items;
792
+ return [];
793
+ }
794
+ async function fetchMarketplacePins(userId, accessToken) {
795
+ if (!userId && !accessToken) return [];
796
+ try {
797
+ const res = await fetch("https://a6api.com/api/marketplace/pins", {
798
+ headers: buildWebHeaders2(userId, accessToken),
799
+ signal: AbortSignal.timeout(8e3)
800
+ });
801
+ if (!res.ok) {
802
+ console.warn("[dsh-a6api] fetchMarketplacePins HTTP", res.status);
803
+ return [];
804
+ }
805
+ const json = await res.json().catch(() => null);
806
+ if (!json || json.success === false) return [];
807
+ return extractArray(json).filter((it) => it && it.model_name).map((it) => ({
808
+ id: it.id !== void 0 ? Number(it.id) : void 0,
809
+ token_id: Number(it.token_id || 0),
810
+ token_name: it.token_name || void 0,
811
+ model_name: String(it.model_name),
812
+ channel_id: it.channel_id !== void 0 && Number(it.channel_id) > 0 ? Number(it.channel_id) : void 0,
813
+ channel_name: it.channel_name || void 0,
814
+ supplier_name: it.supplier_name || void 0,
815
+ supplier_nickname: it.supplier_nickname || void 0,
816
+ fallback_to_smart_routing: it.fallback_to_smart_routing !== void 0 ? Boolean(it.fallback_to_smart_routing) : void 0,
817
+ created_at: it.created_at !== void 0 ? Number(it.created_at) : void 0
818
+ // 不下发 raw:客户端 60s 轮询用 JSON 对比判断变化,raw 含易变字段会导致误判整页刷新
819
+ }));
820
+ } catch (err) {
821
+ console.warn("[dsh-a6api] fetchMarketplacePins error:", err);
822
+ return [];
823
+ }
824
+ }
825
+ async function fetchTokens(userId, accessToken) {
826
+ if (!userId && !accessToken) return [];
827
+ try {
828
+ const res = await fetch("https://a6api.com/api/token/?p=1&size=100", {
829
+ headers: buildWebHeaders2(userId, accessToken),
830
+ signal: AbortSignal.timeout(8e3)
831
+ });
832
+ if (!res.ok) return [];
833
+ const json = await res.json().catch(() => null);
834
+ if (!json || json.success === false) return [];
835
+ return extractArray(json).filter((it) => it && Number(it.id) > 0).map((it) => ({
836
+ id: Number(it.id),
837
+ name: it.name || void 0,
838
+ key: typeof it.key === "string" ? it.key : void 0,
839
+ status: it.status !== void 0 ? Number(it.status) : void 0,
840
+ raw: it
841
+ }));
842
+ } catch (err) {
843
+ console.warn("[dsh-a6api] fetchTokens error:", err);
844
+ return [];
845
+ }
846
+ }
847
+ function friendlyActionError(err) {
848
+ const raw = err?.message || String(err);
849
+ if (raw.includes("aborted due to timeout") || err?.name === "TimeoutError") {
850
+ return "\u8BF7\u6C42\u8D85\u65F6\uFF0C\u8BF7\u91CD\u8BD5";
851
+ }
852
+ return raw;
853
+ }
854
+ async function marketplacePin(userId, accessToken, payload) {
855
+ try {
856
+ const res = await fetch("https://a6api.com/api/marketplace/pin", {
857
+ method: "POST",
858
+ headers: {
859
+ "Content-Type": "application/json",
860
+ ...buildWebHeaders2(userId, accessToken)
861
+ },
862
+ body: JSON.stringify(payload),
863
+ signal: AbortSignal.timeout(1e4)
864
+ });
865
+ const json = await res.json().catch(() => null);
866
+ const result = parseMarketplaceResult(json);
867
+ if (!res.ok && !json) return { ok: false, message: `HTTP ${res.status}` };
868
+ return result;
869
+ } catch (err) {
870
+ console.warn("[dsh-a6api] marketplacePin error:", err);
871
+ return { ok: false, message: friendlyActionError(err) };
872
+ }
873
+ }
874
+ async function marketplaceUnpin(userId, accessToken, payload) {
875
+ try {
876
+ const res = await fetch("https://a6api.com/api/marketplace/unpin", {
877
+ method: "POST",
878
+ headers: {
879
+ "Content-Type": "application/json",
880
+ ...buildWebHeaders2(userId, accessToken)
881
+ },
882
+ body: JSON.stringify(payload),
883
+ signal: AbortSignal.timeout(1e4)
884
+ });
885
+ const json = await res.json().catch(() => null);
886
+ if (!res.ok && !json) return { ok: false, message: `HTTP ${res.status}` };
887
+ return parseMarketplaceResult(json);
888
+ } catch (err) {
889
+ console.warn("[dsh-a6api] marketplaceUnpin error:", err);
890
+ return { ok: false, message: friendlyActionError(err) };
891
+ }
892
+ }
893
+ async function marketplaceDisableChannel(userId, accessToken, channelId, model) {
894
+ try {
895
+ const res = await fetch(
896
+ `https://a6api.com/api/marketplace/channels/${channelId}/disable?model=${encodeURIComponent(model)}`,
897
+ {
898
+ method: "POST",
899
+ headers: buildWebHeaders2(userId, accessToken),
900
+ signal: AbortSignal.timeout(1e4)
901
+ }
902
+ );
903
+ const json = await res.json().catch(() => null);
904
+ if (!res.ok && !json) return { ok: false, message: `HTTP ${res.status}` };
905
+ return parseMarketplaceResult(json);
906
+ } catch (err) {
907
+ console.warn("[dsh-a6api] marketplaceDisableChannel error:", err);
908
+ return { ok: false, message: friendlyActionError(err) };
909
+ }
910
+ }
911
+ async function marketplaceRestoreChannel(userId, accessToken, channelId, model) {
912
+ try {
913
+ const res = await fetch(
914
+ `https://a6api.com/api/marketplace/channels/${channelId}/restore?model=${encodeURIComponent(model)}`,
915
+ {
916
+ method: "POST",
917
+ headers: buildWebHeaders2(userId, accessToken),
918
+ signal: AbortSignal.timeout(1e4)
919
+ }
920
+ );
921
+ const json = await res.json().catch(() => null);
922
+ if (!res.ok && !json) return { ok: false, message: `HTTP ${res.status}` };
923
+ return parseMarketplaceResult(json);
924
+ } catch (err) {
925
+ console.warn("[dsh-a6api] marketplaceRestoreChannel error:", err);
926
+ return { ok: false, message: friendlyActionError(err) };
927
+ }
928
+ }
955
929
 
956
930
  // src/server/probe.ts
957
931
  var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
@@ -1017,6 +991,8 @@ async function probeSingleModel(baseURL, apiKey, userId, accessToken, modelName)
1017
991
  success: true,
1018
992
  channelId,
1019
993
  channelName: log.channel_name,
994
+ // 该次请求由 API Key 发起,日志中记录的 token_id 即该 Key 的令牌 ID
995
+ tokenId: log.token_id !== void 0 && Number(log.token_id) > 0 ? Number(log.token_id) : void 0,
1020
996
  merchant,
1021
997
  durationMs
1022
998
  };
@@ -1082,77 +1058,56 @@ async function getKnownMerchantsFromLogs(userId, accessToken, modelNames = [], l
1082
1058
  }
1083
1059
 
1084
1060
  // src/server/sync.ts
1085
- import * as fsp from "node:fs/promises";
1086
- import * as path from "node:path";
1087
- import * as os from "node:os";
1061
+ import * as fs2 from "node:fs";
1062
+ import * as fsp2 from "node:fs/promises";
1063
+ import * as path2 from "node:path";
1064
+ import * as os2 from "node:os";
1088
1065
  var A6API_CRED_REF = "A6API_API_KEY";
1066
+ var A6API_TOKEN_REF = "A6API_ACCESS_TOKEN";
1067
+ var A6API_USER_REF = "A6API_USER_ID";
1068
+ var SETTINGS_NS = "llm-pi-ai";
1069
+ var PROVIDER_KEY = "a6api";
1070
+ var DEFAULT_BASE_URL = "https://api.a6api.com";
1071
+ var LEGACY_CONFIG_NAME = "dsh-a6api-config.json";
1089
1072
  async function atomicWriteFile(filePath, content, mode = 384) {
1090
- const dir = path.dirname(filePath);
1091
- await fsp.mkdir(dir, { recursive: true });
1092
- const tmpPath = path.join(dir, `.${path.basename(filePath)}.${process.pid}.${Date.now()}.tmp`);
1093
- await fsp.writeFile(tmpPath, content, { mode });
1094
- await fsp.rename(tmpPath, filePath);
1073
+ const dir = path2.dirname(filePath);
1074
+ await fsp2.mkdir(dir, { recursive: true });
1075
+ const tmpPath = path2.join(dir, `.${path2.basename(filePath)}.${process.pid}.${Date.now()}.tmp`);
1076
+ await fsp2.writeFile(tmpPath, content, { mode });
1077
+ await fsp2.rename(tmpPath, filePath);
1095
1078
  }
1096
- function dshHome() {
1097
- return process.env.DSH_HOME || path.join(os.homedir(), ".dsh");
1079
+ function dshHome2() {
1080
+ return process.env.DSH_HOME || path2.join(os2.homedir(), ".dsh");
1098
1081
  }
1099
- function configFile() {
1100
- return path.join(dshHome(), "dsh-a6api-config.json");
1082
+ function legacyConfigFile() {
1083
+ return path2.join(dshHome2(), LEGACY_CONFIG_NAME);
1101
1084
  }
1102
1085
  function credentialsFile() {
1103
- return path.join(dshHome(), ".credentials.yaml");
1086
+ return path2.join(dshHome2(), ".credentials.yaml");
1104
1087
  }
1105
1088
  function settingsFile() {
1106
- return path.join(dshHome(), "settings.yaml");
1089
+ return path2.join(dshHome2(), "settings.yaml");
1107
1090
  }
1108
- async function readPluginConfig() {
1109
- const filePath = configFile();
1110
- let raw = "";
1091
+ function getCredentials(ctx) {
1111
1092
  try {
1112
- raw = await fsp.readFile(filePath, "utf8");
1093
+ if (ctx && typeof ctx.get === "function") return ctx.get("credentials");
1113
1094
  } catch {
1114
- const apiKey = await readCredentialKey(A6API_CRED_REF) || "";
1115
- return {
1116
- baseURL: "https://api.a6api.com",
1117
- apiKey,
1118
- userId: "",
1119
- activeModels: []
1120
- };
1121
1095
  }
1096
+ return void 0;
1097
+ }
1098
+ function getSettings(ctx) {
1122
1099
  try {
1123
- const parsed = JSON.parse(raw);
1124
- const apiKey = parsed.apiKey || await readCredentialKey(A6API_CRED_REF) || "";
1125
- const accessToken = parsed.accessToken || parsed.systemAccessToken || parsed.sessionCookie || "";
1126
- return {
1127
- baseURL: parsed.baseURL || "https://api.a6api.com",
1128
- apiKey,
1129
- accessToken,
1130
- userId: parsed.userId || "",
1131
- sessionCookie: accessToken,
1132
- activeModels: Array.isArray(parsed.activeModels) ? parsed.activeModels : [],
1133
- customBaseURL: parsed.customBaseURL
1134
- };
1100
+ if (ctx && typeof ctx.get === "function") return ctx.get("settings");
1135
1101
  } catch {
1136
- return {
1137
- baseURL: "https://api.a6api.com",
1138
- apiKey: "",
1139
- accessToken: "",
1140
- userId: "",
1141
- activeModels: []
1142
- };
1143
1102
  }
1103
+ return void 0;
1144
1104
  }
1145
- async function savePluginConfig(config) {
1146
- const filePath = configFile();
1147
- const { apiKey: _apiKey, ...safeConfig } = config;
1148
- await atomicWriteFile(filePath, JSON.stringify(safeConfig, null, 2));
1149
- if (config.apiKey && config.apiKey.trim()) {
1150
- await writeCredentialKey(A6API_CRED_REF, config.apiKey.trim());
1151
- }
1105
+ function stripV1(baseURL) {
1106
+ return baseURL.replace(/\/v1\/?$/, "");
1152
1107
  }
1153
1108
  async function readCredentialKey(refKey) {
1154
1109
  try {
1155
- const yaml = await fsp.readFile(credentialsFile(), "utf8");
1110
+ const yaml = await fsp2.readFile(credentialsFile(), "utf8");
1156
1111
  let inRefs = false;
1157
1112
  for (const line of yaml.split(/\r?\n/)) {
1158
1113
  const trimmed = line.trim();
@@ -1169,7 +1124,7 @@ async function readCredentialKey(refKey) {
1169
1124
  if (val.startsWith('"') && val.endsWith('"') || val.startsWith("'") && val.endsWith("'")) {
1170
1125
  val = val.slice(1, -1);
1171
1126
  }
1172
- return val;
1127
+ return val || null;
1173
1128
  }
1174
1129
  }
1175
1130
  } catch {
@@ -1180,14 +1135,14 @@ async function writeCredentialKey(refKey, value) {
1180
1135
  const cFile = credentialsFile();
1181
1136
  let yaml = "";
1182
1137
  try {
1183
- yaml = await fsp.readFile(cFile, "utf8");
1138
+ yaml = await fsp2.readFile(cFile, "utf8");
1184
1139
  } catch {
1185
1140
  yaml = "version: 1\nrefs:\n";
1186
1141
  }
1187
1142
  const lines = yaml.split(/\r?\n/);
1188
1143
  let inRefs = false;
1189
1144
  let refsLineIdx = -1;
1190
- let found = false;
1145
+ let foundIdx = -1;
1191
1146
  for (let i = 0; i < lines.length; i++) {
1192
1147
  const line = lines[i];
1193
1148
  const trimmed = line.trim();
@@ -1205,58 +1160,81 @@ async function writeCredentialKey(refKey, value) {
1205
1160
  if (inRefs) {
1206
1161
  const m = /^([A-Za-z0-9_.\-]+):/.exec(trimmed);
1207
1162
  if (m && m[1] === refKey) {
1208
- lines[i] = ` ${refKey}: ${JSON.stringify(value)}`;
1209
- found = true;
1163
+ foundIdx = i;
1210
1164
  break;
1211
1165
  }
1212
1166
  }
1213
1167
  }
1214
- if (!found) {
1215
- if (refsLineIdx >= 0) {
1216
- lines.splice(refsLineIdx + 1, 0, ` ${refKey}: ${JSON.stringify(value)}`);
1168
+ if (value === "") {
1169
+ if (foundIdx >= 0) lines.splice(foundIdx, 1);
1170
+ } else {
1171
+ if (foundIdx >= 0) {
1172
+ lines[foundIdx] = ` ${refKey}: ${JSON.stringify(value)}`;
1217
1173
  } else {
1218
- lines.push("refs:", ` ${refKey}: ${JSON.stringify(value)}`);
1174
+ if (refsLineIdx >= 0) {
1175
+ lines.splice(refsLineIdx + 1, 0, ` ${refKey}: ${JSON.stringify(value)}`);
1176
+ } else {
1177
+ lines.push("refs:", ` ${refKey}: ${JSON.stringify(value)}`);
1178
+ }
1219
1179
  }
1220
1180
  }
1221
1181
  await atomicWriteFile(cFile, lines.join("\n"), 384);
1222
1182
  }
1223
- async function syncToDshSettings(baseURL, modelIds) {
1224
- const sFile = settingsFile();
1225
- let yaml = "";
1183
+ async function readRawConfiguredModels() {
1226
1184
  try {
1227
- yaml = await fsp.readFile(sFile, "utf8");
1185
+ const yaml = await fsp2.readFile(settingsFile(), "utf8");
1186
+ const lines = yaml.split(/\r?\n/);
1187
+ let inA6 = false;
1188
+ let inModels = false;
1189
+ const modelIds = [];
1190
+ for (const line of lines) {
1191
+ const trimmed = line.trim();
1192
+ const indent = line.match(/^\s*/)?.[0].length ?? 0;
1193
+ if (indent === 4 && trimmed.startsWith("a6api:")) {
1194
+ inA6 = true;
1195
+ inModels = false;
1196
+ continue;
1197
+ }
1198
+ if (inA6 && indent <= 4 && !trimmed.startsWith("a6api:")) {
1199
+ inA6 = false;
1200
+ inModels = false;
1201
+ }
1202
+ if (inA6 && indent === 6 && trimmed.startsWith("models:")) {
1203
+ inModels = true;
1204
+ continue;
1205
+ }
1206
+ if (inModels && indent === 8 && trimmed.startsWith("- id:")) {
1207
+ const id = trimmed.replace(/^- id:\s*/, "").trim();
1208
+ if (id) modelIds.push(id);
1209
+ }
1210
+ }
1211
+ return modelIds;
1228
1212
  } catch {
1229
- yaml = "llm-pi-ai:\n providers:\n";
1213
+ return [];
1230
1214
  }
1231
- const modelEntries = modelIds.map((id) => {
1232
- const meta = resolveModelMeta(id);
1233
- const lines2 = [
1234
- ` - id: ${meta.id}`,
1235
- ` name: ${meta.name}`,
1236
- ` contextWindow: ${meta.contextWindow}`,
1237
- ` maxTokens: ${meta.maxTokens}`,
1238
- ` input:`,
1239
- ...meta.modalities.map((m) => ` - ${m}`)
1240
- ];
1241
- if (meta.reasoningEfforts) {
1242
- lines2.push(` reasoningEfforts:`);
1243
- for (const [k, v] of Object.entries(meta.reasoningEfforts)) {
1244
- if (v) lines2.push(` ${k}: ${v}`);
1215
+ }
1216
+ async function readRawA6apiBaseURL() {
1217
+ try {
1218
+ const yaml = await fsp2.readFile(settingsFile(), "utf8");
1219
+ let inA6 = false;
1220
+ for (const line of yaml.split(/\r?\n/)) {
1221
+ const trimmed = line.trim();
1222
+ if (!trimmed || trimmed.startsWith("#")) continue;
1223
+ const indent = line.match(/^\s*/)?.[0].length ?? 0;
1224
+ if (indent === 4 && trimmed.startsWith("a6api:")) {
1225
+ inA6 = true;
1226
+ continue;
1227
+ }
1228
+ if (inA6 && indent <= 4 && !trimmed.startsWith("a6api:")) inA6 = false;
1229
+ if (inA6 && indent === 6 && trimmed.startsWith("baseURL:")) {
1230
+ return trimmed.replace(/^baseURL:\s*/, "").trim().replace(/^["']|["']$/g, "");
1245
1231
  }
1246
1232
  }
1247
- return lines2.join("\n");
1248
- });
1249
- const dshBaseUrl = baseURL.endsWith("/v1") ? baseURL : `${baseURL.replace(/\/+$/, "")}/v1`;
1250
- const a6apiBlockLines = [
1251
- ` a6api:`,
1252
- ` displayName: A6API`,
1253
- ` apiKeyEnv: ${A6API_CRED_REF}`,
1254
- ` api: openai-completions`,
1255
- ` baseURL: ${dshBaseUrl}`,
1256
- ` models:`,
1257
- ...modelEntries.length > 0 ? modelEntries : [` []`]
1258
- ];
1259
- const lines = yaml.split(/\r?\n/);
1233
+ } catch {
1234
+ }
1235
+ return "";
1236
+ }
1237
+ function scanA6apiBlockRange(lines) {
1260
1238
  let inLlm = false;
1261
1239
  let inProviders = false;
1262
1240
  let inA6 = false;
@@ -1302,6 +1280,46 @@ async function syncToDshSettings(baseURL, modelIds) {
1302
1280
  inA6 = false;
1303
1281
  }
1304
1282
  }
1283
+ return { a6Start, a6End, providersLineIdx, llmLineIdx };
1284
+ }
1285
+ async function writeRawA6apiBlock(baseURL, modelIds) {
1286
+ const sFile = settingsFile();
1287
+ let yaml = "";
1288
+ try {
1289
+ yaml = await fsp2.readFile(sFile, "utf8");
1290
+ } catch {
1291
+ yaml = "llm-pi-ai:\n providers:\n";
1292
+ }
1293
+ const modelEntries = modelIds.map((id) => {
1294
+ const entry = getCatalogEntry(id);
1295
+ const lines2 = [` - id: ${id}`];
1296
+ if (entry?.name) lines2.push(` name: ${JSON.stringify(String(entry.name).replace(/\r?\n/g, " "))}`);
1297
+ if (entry?.contextWindow != null) lines2.push(` contextWindow: ${entry.contextWindow}`);
1298
+ if (entry?.maxTokens != null) lines2.push(` maxTokens: ${entry.maxTokens}`);
1299
+ if (entry?.input && entry.input.length > 0) {
1300
+ lines2.push(` input:`);
1301
+ for (const m of entry.input) lines2.push(` - ${m}`);
1302
+ }
1303
+ if (entry?.reasoningEfforts && typeof entry.reasoningEfforts === "object") {
1304
+ lines2.push(` reasoningEfforts:`);
1305
+ for (const [k, v] of Object.entries(entry.reasoningEfforts)) {
1306
+ lines2.push(v ? ` ${k}: ${v}` : ` ${k}: `);
1307
+ }
1308
+ }
1309
+ return lines2.join("\n");
1310
+ });
1311
+ const dshBaseUrl = baseURL.endsWith("/v1") ? baseURL : `${baseURL.replace(/\/+$/, "")}/v1`;
1312
+ const a6apiBlockLines = [
1313
+ ` a6api:`,
1314
+ ` displayName: A6API`,
1315
+ ` apiKeyEnv: ${A6API_CRED_REF}`,
1316
+ ` api: openai-completions`,
1317
+ ` baseURL: ${dshBaseUrl}`,
1318
+ ` models:`,
1319
+ ...modelEntries
1320
+ ];
1321
+ const lines = yaml.split(/\r?\n/);
1322
+ const { a6Start, a6End, providersLineIdx, llmLineIdx } = scanA6apiBlockRange(lines);
1305
1323
  if (a6Start >= 0) {
1306
1324
  lines.splice(a6Start, a6End - a6Start, ...a6apiBlockLines);
1307
1325
  } else if (providersLineIdx >= 0) {
@@ -1313,38 +1331,305 @@ async function syncToDshSettings(baseURL, modelIds) {
1313
1331
  }
1314
1332
  await atomicWriteFile(sFile, lines.join("\n"), 420);
1315
1333
  }
1316
- async function getDshConfiguredModels() {
1334
+ async function removeRawA6apiBlock() {
1335
+ const sFile = settingsFile();
1336
+ let yaml = "";
1317
1337
  try {
1318
- const yaml = await fsp.readFile(settingsFile(), "utf8");
1319
- const lines = yaml.split(/\r?\n/);
1320
- let inA6 = false;
1321
- let inModels = false;
1322
- const modelIds = [];
1323
- for (const line of lines) {
1324
- const trimmed = line.trim();
1325
- const indent = line.match(/^\s*/)?.[0].length ?? 0;
1326
- if (indent === 4 && trimmed.startsWith("a6api:")) {
1327
- inA6 = true;
1328
- inModels = false;
1329
- continue;
1338
+ yaml = await fsp2.readFile(sFile, "utf8");
1339
+ } catch {
1340
+ return;
1341
+ }
1342
+ const lines = yaml.split(/\r?\n/);
1343
+ const { a6Start, a6End, providersLineIdx, llmLineIdx } = scanA6apiBlockRange(lines);
1344
+ if (a6Start < 0) return;
1345
+ lines.splice(a6Start, a6End - a6Start);
1346
+ if (providersLineIdx >= 0) {
1347
+ let hasProvider = false;
1348
+ for (let i = providersLineIdx + 1; i < lines.length; i++) {
1349
+ const trimmed = lines[i].trim();
1350
+ if (!trimmed || trimmed.startsWith("#")) continue;
1351
+ const indent = lines[i].match(/^\s*/)?.[0].length ?? 0;
1352
+ if (indent <= 2) break;
1353
+ if (indent === 4) {
1354
+ hasProvider = true;
1355
+ break;
1330
1356
  }
1331
- if (inA6 && indent <= 4 && !trimmed.startsWith("a6api:")) {
1332
- inA6 = false;
1333
- inModels = false;
1357
+ }
1358
+ if (!hasProvider) lines.splice(providersLineIdx, 1);
1359
+ }
1360
+ if (llmLineIdx >= 0) {
1361
+ let hasLlmKey = false;
1362
+ for (let i = llmLineIdx + 1; i < lines.length; i++) {
1363
+ const trimmed = lines[i].trim();
1364
+ if (!trimmed || trimmed.startsWith("#")) continue;
1365
+ const indent = lines[i].match(/^\s*/)?.[0].length ?? 0;
1366
+ if (indent === 0) break;
1367
+ if (indent === 2) {
1368
+ hasLlmKey = true;
1369
+ break;
1334
1370
  }
1335
- if (inA6 && indent === 6 && trimmed.startsWith("models:")) {
1336
- inModels = true;
1337
- continue;
1371
+ }
1372
+ if (!hasLlmKey) lines.splice(llmLineIdx, 1);
1373
+ }
1374
+ await atomicWriteFile(sFile, lines.join("\n"), 420);
1375
+ }
1376
+ function normalizeLegacy(parsed) {
1377
+ return {
1378
+ apiKey: typeof parsed?.apiKey === "string" ? parsed.apiKey : "",
1379
+ accessToken: String(parsed?.accessToken || parsed?.systemAccessToken || parsed?.sessionCookie || ""),
1380
+ userId: String(parsed?.userId || ""),
1381
+ baseURL: typeof parsed?.baseURL === "string" && parsed.baseURL ? parsed.baseURL : DEFAULT_BASE_URL,
1382
+ activeModels: Array.isArray(parsed?.activeModels) ? parsed.activeModels.filter((m) => typeof m === "string") : []
1383
+ };
1384
+ }
1385
+ async function readLegacyConfig() {
1386
+ try {
1387
+ const raw = await fsp2.readFile(legacyConfigFile(), "utf8");
1388
+ const legacy = normalizeLegacy(JSON.parse(raw));
1389
+ return {
1390
+ baseURL: legacy.baseURL,
1391
+ apiKey: legacy.apiKey,
1392
+ accessToken: legacy.accessToken || void 0,
1393
+ userId: legacy.userId || void 0,
1394
+ activeModels: legacy.activeModels
1395
+ };
1396
+ } catch {
1397
+ return null;
1398
+ }
1399
+ }
1400
+ function buildA6apiBlock(baseURL, modelIds) {
1401
+ const models = modelIds.map((id) => {
1402
+ const entry = getCatalogEntry(id);
1403
+ const m = { id };
1404
+ if (entry?.name) m.name = entry.name;
1405
+ if (entry?.contextWindow != null) m.contextWindow = entry.contextWindow;
1406
+ if (entry?.maxTokens != null) m.maxTokens = entry.maxTokens;
1407
+ if (entry?.input && entry.input.length > 0) m.input = [...entry.input];
1408
+ if (entry?.reasoningEfforts !== void 0 && entry.reasoningEfforts !== null) {
1409
+ m.reasoningEfforts = entry.reasoningEfforts;
1410
+ }
1411
+ return m;
1412
+ });
1413
+ return {
1414
+ displayName: "A6API",
1415
+ apiKeyEnv: A6API_CRED_REF,
1416
+ api: "openai-completions",
1417
+ baseURL: baseURL.endsWith("/v1") ? baseURL : `${baseURL.replace(/\/+$/, "")}/v1`,
1418
+ models
1419
+ };
1420
+ }
1421
+ function createConfigAccess(ctx) {
1422
+ let migration = null;
1423
+ const ensureMigrated = () => {
1424
+ if (!migration) {
1425
+ migration = doMigrate().catch((err) => {
1426
+ console.warn("[dsh-a6api] \u65E7\u914D\u7F6E\u8FC1\u79FB\u5931\u8D25\uFF08\u4FDD\u7559\u65E7\u6587\u4EF6\u8BFB\u53D6\u515C\u5E95\uFF09:", err?.message || err);
1427
+ });
1428
+ }
1429
+ return migration;
1430
+ };
1431
+ const resolveRef = async (creds, ref) => {
1432
+ try {
1433
+ if (creds && typeof creds.resolve === "function") {
1434
+ const r = await creds.resolve(ref);
1435
+ return r && typeof r.value === "string" ? r.value : "";
1338
1436
  }
1339
- if (inModels && indent === 8 && trimmed.startsWith("- id:")) {
1340
- const id = trimmed.replace(/^- id:\s*/, "").trim();
1341
- if (id) modelIds.push(id);
1437
+ } catch (err) {
1438
+ console.warn(`[dsh-a6api] credentials.resolve(${ref}) failed:`, err?.message || err);
1439
+ }
1440
+ return await readCredentialKey(ref) || "";
1441
+ };
1442
+ const readA6apiBlock = async (settings) => {
1443
+ try {
1444
+ if (settings && typeof settings.get === "function") {
1445
+ const llm = settings.get(SETTINGS_NS);
1446
+ const block = llm && llm.providers ? llm.providers[PROVIDER_KEY] : void 0;
1447
+ if (block && typeof block === "object") {
1448
+ return {
1449
+ baseURL: typeof block.baseURL === "string" ? block.baseURL : void 0,
1450
+ models: Array.isArray(block.models) ? block.models.map((m) => typeof m === "string" ? m : m && typeof m.id === "string" ? m.id : "").filter(Boolean) : []
1451
+ };
1452
+ }
1342
1453
  }
1454
+ } catch (err) {
1455
+ console.warn("[dsh-a6api] settings.get(llm-pi-ai) failed:", err?.message || err);
1343
1456
  }
1344
- return modelIds;
1345
- } catch {
1346
- return [];
1457
+ return null;
1458
+ };
1459
+ const readConfig = async () => {
1460
+ await ensureMigrated();
1461
+ const creds = getCredentials(ctx);
1462
+ const settings = getSettings(ctx);
1463
+ const apiKey = await resolveRef(creds, A6API_CRED_REF);
1464
+ const accessToken = await resolveRef(creds, A6API_TOKEN_REF);
1465
+ const userId = await resolveRef(creds, A6API_USER_REF);
1466
+ let baseURL = DEFAULT_BASE_URL;
1467
+ let activeModels = [];
1468
+ const block = await readA6apiBlock(settings);
1469
+ if (block) {
1470
+ if (block.baseURL) baseURL = stripV1(block.baseURL) || DEFAULT_BASE_URL;
1471
+ activeModels = block.models;
1472
+ } else {
1473
+ const rawBase = await readRawA6apiBaseURL();
1474
+ if (rawBase) baseURL = stripV1(rawBase) || DEFAULT_BASE_URL;
1475
+ activeModels = await readRawConfiguredModels();
1476
+ }
1477
+ if (!apiKey && !accessToken && fs2.existsSync(legacyConfigFile())) {
1478
+ const legacy = await readLegacyConfig();
1479
+ if (legacy) {
1480
+ return {
1481
+ baseURL: baseURL || legacy.baseURL,
1482
+ apiKey: apiKey || legacy.apiKey,
1483
+ accessToken: accessToken || legacy.accessToken,
1484
+ userId: userId || legacy.userId,
1485
+ activeModels: activeModels.length > 0 ? activeModels : legacy.activeModels
1486
+ };
1487
+ }
1488
+ }
1489
+ return { baseURL, apiKey, accessToken, userId, activeModels };
1490
+ };
1491
+ const writeConfig = async (parts) => {
1492
+ await ensureMigrated();
1493
+ const creds = getCredentials(ctx);
1494
+ const entries = [
1495
+ [A6API_CRED_REF, parts.apiKey],
1496
+ [A6API_TOKEN_REF, parts.accessToken],
1497
+ [A6API_USER_REF, parts.userId]
1498
+ ];
1499
+ for (const [ref, value] of entries) {
1500
+ if (value === void 0) continue;
1501
+ const v = value.trim();
1502
+ try {
1503
+ if (creds && typeof creds.set === "function" && typeof creds.unset === "function") {
1504
+ if (v) await creds.set(ref, v);
1505
+ else await creds.unset(ref);
1506
+ } else {
1507
+ await writeCredentialKey(ref, v);
1508
+ }
1509
+ } catch (err) {
1510
+ console.warn(`[dsh-a6api] \u5199\u5165\u51ED\u636E ${ref} \u5931\u8D25\uFF08\u5DF2\u8DF3\u8FC7\uFF09:`, err?.message || err);
1511
+ }
1512
+ }
1513
+ };
1514
+ const syncModels = async (baseURL, modelIds) => {
1515
+ const settings = getSettings(ctx);
1516
+ if (modelIds.length === 0) {
1517
+ if (settings && typeof settings.mutate === "function") {
1518
+ try {
1519
+ await settings.mutate(SETTINGS_NS, [{ op: "unset", path: ["providers", PROVIDER_KEY] }]);
1520
+ return;
1521
+ } catch (err) {
1522
+ console.warn("[dsh-a6api] settings.mutate(llm-pi-ai) \u79FB\u9664 a6api \u5757\u5931\u8D25\uFF0C\u56DE\u9000\u88F8\u5199 settings.yaml:", err?.message || err);
1523
+ }
1524
+ }
1525
+ await removeRawA6apiBlock();
1526
+ return;
1527
+ }
1528
+ const block = buildA6apiBlock(baseURL, modelIds);
1529
+ if (settings && typeof settings.update === "function") {
1530
+ try {
1531
+ await settings.update(SETTINGS_NS, { providers: { [PROVIDER_KEY]: block } });
1532
+ return;
1533
+ } catch (err) {
1534
+ console.warn("[dsh-a6api] settings.update(llm-pi-ai) \u5931\u8D25\uFF0C\u56DE\u9000\u88F8\u5199 settings.yaml:", err?.message || err);
1535
+ }
1536
+ }
1537
+ await writeRawA6apiBlock(baseURL, modelIds);
1538
+ };
1539
+ const getDshConfiguredModels = async () => {
1540
+ const block = await readA6apiBlock(getSettings(ctx));
1541
+ if (block) return block.models;
1542
+ return readRawConfiguredModels();
1543
+ };
1544
+ const fillRef = async (creds, ref, value) => {
1545
+ if (!value) return true;
1546
+ try {
1547
+ const current = await resolveRef(creds, ref);
1548
+ if (current) return true;
1549
+ if (creds && typeof creds.set === "function") await creds.set(ref, value);
1550
+ else await writeCredentialKey(ref, value);
1551
+ return true;
1552
+ } catch (err) {
1553
+ console.warn(`[dsh-a6api] \u8FC1\u79FB ${ref} \u5931\u8D25\uFF08\u8DF3\u8FC7\uFF0C\u4FDD\u7559\u65E7\u6587\u4EF6\uFF09:`, err?.message || err);
1554
+ return false;
1555
+ }
1556
+ };
1557
+ const archiveLegacy = async (filePath) => {
1558
+ try {
1559
+ await fsp2.rename(filePath, `${filePath}.bak`);
1560
+ console.log("[dsh-a6api] \u65E7\u914D\u7F6E\u5DF2\u8FC1\u79FB\u81F3 DSH \u539F\u751F\u914D\u7F6E\u5E76\u5F52\u6863: dsh-a6api-config.json.bak");
1561
+ } catch (err) {
1562
+ console.warn("[dsh-a6api] \u65E7\u914D\u7F6E\u5F52\u6863\u5931\u8D25\uFF08\u8FC1\u79FB\u503C\u5DF2\u5199\u5165\uFF0C\u65E7\u6587\u4EF6\u4FDD\u7559\uFF0C\u4E0B\u6B21\u542F\u52A8\u91CD\u8BD5\uFF09:", err?.message || err);
1563
+ }
1564
+ };
1565
+ const doMigrate = async () => {
1566
+ const filePath = legacyConfigFile();
1567
+ let raw = "";
1568
+ try {
1569
+ raw = await fsp2.readFile(filePath, "utf8");
1570
+ } catch {
1571
+ return;
1572
+ }
1573
+ let parsed;
1574
+ try {
1575
+ parsed = JSON.parse(raw);
1576
+ } catch (err) {
1577
+ console.warn("[dsh-a6api] \u65E7\u914D\u7F6E\u6587\u4EF6\u635F\u574F\uFF0C\u8DF3\u8FC7\u8FC1\u79FB\u5E76\u5F52\u6863:", err?.message || err);
1578
+ await archiveLegacy(filePath);
1579
+ return;
1580
+ }
1581
+ const legacy = normalizeLegacy(parsed);
1582
+ const creds = getCredentials(ctx);
1583
+ const fillResults = [
1584
+ await fillRef(creds, A6API_CRED_REF, legacy.apiKey),
1585
+ await fillRef(creds, A6API_TOKEN_REF, legacy.accessToken),
1586
+ await fillRef(creds, A6API_USER_REF, legacy.userId)
1587
+ ];
1588
+ if (fillResults.some((ok) => !ok)) {
1589
+ console.warn("[dsh-a6api] \u51ED\u636E\u8FC1\u79FB\u672A\u5168\u90E8\u6210\u529F\uFF0C\u8DF3\u8FC7\u5F52\u6863\uFF0C\u4FDD\u7559\u65E7\u6587\u4EF6\u8BFB\u53D6\u515C\u5E95\uFF08\u4E0B\u6B21\u542F\u52A8\u91CD\u8BD5\uFF09");
1590
+ return;
1591
+ }
1592
+ const block = await readA6apiBlock(getSettings(ctx));
1593
+ const blockExists = Boolean(block) || (await readRawConfiguredModels()).length > 0;
1594
+ if (!blockExists && legacy.activeModels.length > 0) {
1595
+ await syncModels(legacy.baseURL, legacy.activeModels);
1596
+ }
1597
+ await archiveLegacy(filePath);
1598
+ };
1599
+ return { ensureMigrated, readConfig, writeConfig, syncModels, getDshConfiguredModels };
1600
+ }
1601
+
1602
+ // src/types.ts
1603
+ var THINKING_LEVEL_KEYS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
1604
+ function validateReasoningEfforts(value) {
1605
+ if (value === false) return { ok: true, value: false };
1606
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
1607
+ return { ok: false, error: "reasoningEfforts \u5FC5\u987B\u662F\u6863\u4F4D\u5B57\u5178\u6216 false" };
1347
1608
  }
1609
+ const dict = value;
1610
+ const keys = Object.keys(dict);
1611
+ if (keys.length === 0) return { ok: false, error: "reasoningEfforts \u4E0D\u80FD\u4E3A\u7A7A\u5B57\u5178" };
1612
+ const out = {};
1613
+ for (const k of keys) {
1614
+ if (!THINKING_LEVEL_KEYS.includes(k)) {
1615
+ return { ok: false, error: `reasoningEfforts \u952E "${k}" \u4E0D\u662F DSH \u652F\u6301\u7684\u6863\u4F4D\uFF08off/minimal/low/medium/high/xhigh/max\uFF09` };
1616
+ }
1617
+ const v = dict[k];
1618
+ if (v === null) {
1619
+ if (k !== "off") {
1620
+ return { ok: false, error: `reasoningEfforts.${k} \u5FC5\u987B\u63D0\u4F9B wire \u503C\uFF0C\u4EC5 off \u5141\u8BB8\u7559\u7A7A` };
1621
+ }
1622
+ out[k] = null;
1623
+ } else if (typeof v === "string" && v.length > 0) {
1624
+ out[k] = v;
1625
+ } else {
1626
+ return { ok: false, error: `reasoningEfforts.${k} \u7684\u503C\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32\u6216 null` };
1627
+ }
1628
+ }
1629
+ if (!keys.some((k) => k !== "off")) {
1630
+ return { ok: false, error: "reasoningEfforts \u9664 off \u5916\u81F3\u5C11\u9700\u8981\u4E00\u4E2A\u601D\u8003\u6863\u4F4D\uFF08\u975E\u63A8\u7406\u6A21\u578B\u8BF7\u7528 false\uFF09" };
1631
+ }
1632
+ return { ok: true, value: out };
1348
1633
  }
1349
1634
 
1350
1635
  // src/index.ts
@@ -1356,11 +1641,10 @@ function maskConfig(c) {
1356
1641
  return {
1357
1642
  ...c,
1358
1643
  apiKey: c.apiKey ? MASK : "",
1359
- accessToken: c.accessToken || c.sessionCookie ? MASK : "",
1360
- sessionCookie: "",
1644
+ accessToken: c.accessToken ? MASK : "",
1361
1645
  userId: c.userId ? MASK : "",
1362
1646
  hasApiKey: Boolean(c.apiKey),
1363
- hasToken: Boolean(c.accessToken || c.sessionCookie)
1647
+ hasToken: Boolean(c.accessToken)
1364
1648
  };
1365
1649
  }
1366
1650
  function sendJson(res, status, body) {
@@ -1396,7 +1680,68 @@ async function parseJsonBody(req) {
1396
1680
  }
1397
1681
  var merchantCardCache = /* @__PURE__ */ new Map();
1398
1682
  var MERCHANT_CARD_TTL_MS = 15 * 60 * 1e3;
1683
+ var tokenResolveCache = null;
1684
+ var TOKEN_RESOLVE_TTL_MS = 10 * 60 * 1e3;
1685
+ async function resolveTokenId(config) {
1686
+ if (tokenResolveCache && Date.now() - tokenResolveCache.at < TOKEN_RESOLVE_TTL_MS) {
1687
+ return tokenResolveCache.tokenId;
1688
+ }
1689
+ const token = config.accessToken || "";
1690
+ if (!config.userId || !token) return null;
1691
+ try {
1692
+ const tokens = await fetchTokens(config.userId, token);
1693
+ let tokenId = null;
1694
+ const key = (config.apiKey || "").trim();
1695
+ if (key) {
1696
+ const hit = tokens.find((t) => t.key && t.key === key);
1697
+ if (hit) tokenId = hit.id;
1698
+ }
1699
+ if (!tokenId && tokens.length === 1) tokenId = tokens[0].id;
1700
+ if (tokenId && tokenId > 0) {
1701
+ tokenResolveCache = { tokenId, at: Date.now() };
1702
+ return tokenId;
1703
+ }
1704
+ } catch (err) {
1705
+ console.warn("[dsh-a6api] resolveTokenId error:", err);
1706
+ }
1707
+ return null;
1708
+ }
1709
+ function webAuthOf(config) {
1710
+ return { userId: config.userId || void 0, token: config.accessToken || void 0 };
1711
+ }
1712
+ function cachedMerchantOf(modelName) {
1713
+ const entry = merchantCardCache.get(modelName.toLowerCase());
1714
+ return entry && Date.now() - entry.at < MERCHANT_CARD_TTL_MS ? entry.card : void 0;
1715
+ }
1716
+ function overlayPinsOnModels(models, pins, tokenId) {
1717
+ const byModel = /* @__PURE__ */ new Map();
1718
+ for (const p of pins) {
1719
+ const key = (p.model_name || "").toLowerCase();
1720
+ if (!key) continue;
1721
+ const list = byModel.get(key);
1722
+ if (list) list.push(p);
1723
+ else byModel.set(key, [p]);
1724
+ }
1725
+ return models.map((m) => {
1726
+ const list = byModel.get(m.model_name.toLowerCase());
1727
+ if (!list || list.length === 0) return m;
1728
+ const pick = (tokenId ? list.find((p) => Number(p.token_id) === tokenId) : void 0) || list[0];
1729
+ const cardChannel = m.merchant?.channel_id;
1730
+ const pinChannel = pick.channel_id;
1731
+ return {
1732
+ ...m,
1733
+ // Number() 归一化:官方接口可能返回字符串渠道 ID,严格相等会误判
1734
+ pinStatus: pinChannel ? cardChannel && Number(cardChannel) === Number(pinChannel) ? "pin_here" : "pin_elsewhere" : void 0,
1735
+ pinnedChannelId: pinChannel,
1736
+ pinnedSupplierName: pick.supplier_nickname || pick.supplier_name,
1737
+ pinnedFallback: pick.fallback_to_smart_routing,
1738
+ pinTokenMatched: Boolean(tokenId && Number(pick.token_id) === tokenId)
1739
+ };
1740
+ });
1741
+ }
1399
1742
  function apply(ctx) {
1743
+ const configAccess = createConfigAccess(ctx);
1744
+ void configAccess.ensureMigrated();
1400
1745
  const webServer = ctx.webServer || (ctx.get ? ctx.get("webServer") : null);
1401
1746
  if (webServer && typeof webServer.register === "function") {
1402
1747
  ctx.effect(() => {
@@ -1410,24 +1755,29 @@ function apply(ctx) {
1410
1755
  res.writeHead(204);
1411
1756
  return res.end();
1412
1757
  }
1758
+ if (req.method === "POST" && !String(req.headers["content-type"] || "").toLowerCase().includes("application/json")) {
1759
+ return sendJson(res, 415, { ok: false, error: "Content-Type must be application/json" });
1760
+ }
1413
1761
  try {
1414
1762
  if (pathname === "/state" && (req.method === "GET" || req.method === "HEAD")) {
1415
- const config = await readPluginConfig();
1416
- const token = config.accessToken || config.sessionCookie || "";
1417
- const balance = await fetchBalance(config.baseURL, config.apiKey, config.userId, token);
1763
+ const config = await configAccess.readConfig();
1764
+ const token = config.accessToken || "";
1765
+ const [balance, dshConfiguredModels, modelIdsRaw, allLogs, pins] = await Promise.all([
1766
+ fetchBalance(config.baseURL, config.apiKey, config.userId, token),
1767
+ configAccess.getDshConfiguredModels(),
1768
+ config.apiKey ? fetchTokenModels(config.baseURL, config.apiKey) : Promise.resolve([]),
1769
+ fetchRecentLogs(config.userId, token, 100),
1770
+ config.userId && token ? fetchMarketplacePins(config.userId, token).catch(() => []) : Promise.resolve([])
1771
+ ]);
1418
1772
  if (balance?.userId && String(balance.userId) !== config.userId) {
1773
+ tokenResolveCache = null;
1419
1774
  config.userId = String(balance.userId);
1420
- await savePluginConfig(config);
1421
- }
1422
- const dshConfiguredModels = await getDshConfiguredModels();
1423
- let modelIds = [];
1424
- if (config.apiKey) {
1425
- modelIds = await fetchTokenModels(config.baseURL, config.apiKey);
1775
+ await configAccess.writeConfig({ userId: config.userId });
1426
1776
  }
1777
+ let modelIds = modelIdsRaw;
1427
1778
  if (modelIds.length === 0) {
1428
1779
  modelIds = [
1429
1780
  .../* @__PURE__ */ new Set([
1430
- ...config.activeModels,
1431
1781
  ...dshConfiguredModels,
1432
1782
  "gpt-5.6-sol",
1433
1783
  "gpt-5.6-terra",
@@ -1438,7 +1788,6 @@ function apply(ctx) {
1438
1788
  ])
1439
1789
  ];
1440
1790
  }
1441
- const allLogs = await fetchRecentLogs(config.userId, token, 100);
1442
1791
  allLogs.sort((a, b) => (Number(b.created_at) || 0) - (Number(a.created_at) || 0));
1443
1792
  if (config.userId || token) {
1444
1793
  const missing = modelIds.filter((m) => {
@@ -1470,7 +1819,7 @@ function apply(ctx) {
1470
1819
  }
1471
1820
  }
1472
1821
  const dshSet = new Set(dshConfiguredModels);
1473
- const models = modelIds.map((mId) => {
1822
+ let models = modelIds.map((mId) => {
1474
1823
  const meta = resolveModelMeta(mId);
1475
1824
  const cacheEntry = merchantCardCache.get(mId.toLowerCase());
1476
1825
  const cachedCard = cacheEntry && Date.now() - cacheEntry.at < MERCHANT_CARD_TTL_MS ? cacheEntry.card : void 0;
@@ -1489,57 +1838,120 @@ function apply(ctx) {
1489
1838
  lastRoutedText: routedAt ? formatRelativeTime(routedAt) : void 0
1490
1839
  };
1491
1840
  });
1841
+ const resolvedTokenId = pins.length > 0 ? await resolveTokenId(config) : null;
1842
+ models = overlayPinsOnModels(models, pins, resolvedTokenId);
1843
+ const rePointTargets = models.filter(
1844
+ (m) => m.pinStatus === "pin_elsewhere" && m.pinTokenMatched === true && m.pinnedChannelId && m.pinnedChannelId > 0
1845
+ ).map((m) => ({ modelName: m.model_name, channelId: m.pinnedChannelId }));
1846
+ if (rePointTargets.length > 0 && config.userId && token) {
1847
+ try {
1848
+ await Promise.race([
1849
+ (async () => {
1850
+ for (let i = 0; i < rePointTargets.length; i += 4) {
1851
+ const batch = rePointTargets.slice(i, i + 4);
1852
+ await Promise.all(
1853
+ batch.map(async ({ modelName, channelId }) => {
1854
+ try {
1855
+ const pinnedCard = await fetchChannelDetails(
1856
+ channelId,
1857
+ config.userId,
1858
+ token,
1859
+ modelName
1860
+ );
1861
+ if (pinnedCard && Number(pinnedCard.channel_id) === Number(channelId)) {
1862
+ merchantCardCache.set(modelName.toLowerCase(), { card: pinnedCard, at: Date.now() });
1863
+ }
1864
+ } catch {
1865
+ }
1866
+ })
1867
+ );
1868
+ }
1869
+ })(),
1870
+ new Promise((resolve) => setTimeout(() => resolve(), 1e4))
1871
+ ]);
1872
+ } catch {
1873
+ }
1874
+ models = models.map((m) => {
1875
+ if (m.pinStatus !== "pin_elsewhere" || m.pinTokenMatched !== true) return m;
1876
+ const entry = merchantCardCache.get(m.model_name.toLowerCase());
1877
+ const card = entry && Date.now() - entry.at < MERCHANT_CARD_TTL_MS ? entry.card : void 0;
1878
+ if (card && Number(card.channel_id) === Number(m.pinnedChannelId)) {
1879
+ const pinnedLog = allLogs.find(
1880
+ (l) => l.model_name?.toLowerCase() === m.model_name.toLowerCase() && Number(l.channel) === m.pinnedChannelId
1881
+ );
1882
+ const pinnedAt = pinnedLog ? Number(pinnedLog.created_at) || 0 : void 0;
1883
+ return {
1884
+ ...m,
1885
+ merchant: card,
1886
+ pinStatus: "pin_here",
1887
+ probeStatus: "success",
1888
+ lastRoutedAt: pinnedAt,
1889
+ lastRoutedText: pinnedAt ? formatRelativeTime(pinnedAt) : void 0
1890
+ };
1891
+ }
1892
+ return m;
1893
+ });
1894
+ }
1492
1895
  const recentLogs = allLogs.slice(0, 20);
1493
1896
  const response = {
1494
1897
  config: maskConfig(config),
1495
1898
  balance,
1496
1899
  models,
1497
1900
  dshConfiguredModels,
1498
- recentLogs
1901
+ recentLogs,
1902
+ pins
1499
1903
  };
1500
1904
  return sendJson(res, 200, { ok: true, data: response });
1501
1905
  }
1502
1906
  if (pathname === "/config" && req.method === "POST") {
1503
1907
  const body = await parseJsonBody(req);
1504
- const current = await readPluginConfig();
1505
- const rawToken = body.accessToken !== void 0 && body.accessToken !== MASK ? body.accessToken : body.sessionCookie !== void 0 && body.sessionCookie !== MASK ? body.sessionCookie : current.accessToken || current.sessionCookie || "";
1908
+ const current = await configAccess.readConfig();
1909
+ const rawToken = body.accessToken !== void 0 && body.accessToken !== MASK ? body.accessToken : body.sessionCookie !== void 0 && body.sessionCookie !== MASK ? body.sessionCookie : current.accessToken;
1506
1910
  const newApiKey = body.apiKey !== void 0 && body.apiKey !== MASK ? body.apiKey : current.apiKey;
1911
+ const credChanged = newApiKey !== current.apiKey || rawToken !== (current.accessToken || "") || body.userId !== void 0 && body.userId !== MASK && body.userId !== current.userId;
1912
+ if (credChanged) {
1913
+ tokenResolveCache = null;
1914
+ }
1507
1915
  const updated = {
1508
1916
  baseURL: body.baseURL !== void 0 ? body.baseURL : current.baseURL,
1509
1917
  apiKey: newApiKey,
1510
1918
  accessToken: rawToken,
1511
- sessionCookie: rawToken,
1512
- userId: body.userId !== void 0 ? body.userId : current.userId,
1513
- activeModels: Array.isArray(body.activeModels) ? body.activeModels : current.activeModels,
1514
- customBaseURL: body.customBaseURL !== void 0 ? body.customBaseURL : current.customBaseURL
1919
+ userId: body.userId !== void 0 && body.userId !== MASK ? body.userId : current.userId,
1920
+ activeModels: Array.isArray(body.activeModels) ? body.activeModels : current.activeModels
1515
1921
  };
1516
1922
  const balance = await fetchBalance(updated.baseURL, updated.apiKey, updated.userId, updated.accessToken);
1517
1923
  if (balance?.userId) {
1518
1924
  updated.userId = String(balance.userId);
1519
1925
  }
1520
- await savePluginConfig(updated);
1926
+ const credWrites = {};
1927
+ if (updated.apiKey !== current.apiKey) credWrites.apiKey = updated.apiKey;
1928
+ if ((updated.accessToken || "") !== (current.accessToken || "")) credWrites.accessToken = updated.accessToken;
1929
+ if ((updated.userId || "") !== (current.userId || "")) credWrites.userId = updated.userId;
1930
+ if (Object.keys(credWrites).length > 0) {
1931
+ await configAccess.writeConfig(credWrites);
1932
+ }
1521
1933
  if (updated.activeModels.length > 0) {
1522
- await syncToDshSettings(updated.baseURL, updated.activeModels);
1934
+ await configAccess.syncModels(updated.baseURL, updated.activeModels);
1523
1935
  }
1524
1936
  return sendJson(res, 200, { ok: true, config: maskConfig(updated), balance });
1525
1937
  }
1526
1938
  if (pathname === "/balance" && (req.method === "GET" || req.method === "HEAD")) {
1527
- const config = await readPluginConfig();
1528
- const token = config.accessToken || config.sessionCookie || "";
1939
+ const config = await configAccess.readConfig();
1940
+ const token = config.accessToken || "";
1529
1941
  const balance = await fetchBalance(config.baseURL, config.apiKey, config.userId, token);
1530
1942
  const recentLogs = await fetchRecentLogs(config.userId, token, 20);
1531
1943
  return sendJson(res, 200, { ok: true, balance, recentLogs });
1532
1944
  }
1533
1945
  if (pathname === "/logs" && (req.method === "GET" || req.method === "HEAD")) {
1534
- const config = await readPluginConfig();
1535
- const token = config.accessToken || config.sessionCookie || "";
1946
+ const config = await configAccess.readConfig();
1947
+ const token = config.accessToken || "";
1536
1948
  const recentLogs = await fetchRecentLogs(config.userId, token, 30);
1537
1949
  return sendJson(res, 200, { ok: true, logs: recentLogs });
1538
1950
  }
1539
1951
  if (pathname === "/probe" && req.method === "POST") {
1540
1952
  const body = await parseJsonBody(req);
1541
- const config = await readPluginConfig();
1542
- const token = config.accessToken || config.sessionCookie || "";
1953
+ const config = await configAccess.readConfig();
1954
+ const token = config.accessToken || "";
1543
1955
  const modelName = body.modelName;
1544
1956
  if (modelName && modelName !== "all") {
1545
1957
  const result = await probeSingleModel(config.baseURL, config.apiKey, config.userId, token, modelName);
@@ -1553,7 +1965,7 @@ function apply(ctx) {
1553
1965
  modelIds = await fetchTokenModels(config.baseURL, config.apiKey);
1554
1966
  }
1555
1967
  if (modelIds.length === 0) {
1556
- modelIds = config.activeModels;
1968
+ modelIds = await configAccess.getDshConfiguredModels();
1557
1969
  }
1558
1970
  const results = [];
1559
1971
  for (const m of modelIds) {
@@ -1567,27 +1979,285 @@ function apply(ctx) {
1567
1979
  }
1568
1980
  if (pathname === "/sync-models" && req.method === "POST") {
1569
1981
  const body = await parseJsonBody(req);
1570
- const config = await readPluginConfig();
1982
+ await configAccess.ensureMigrated();
1983
+ const config = await configAccess.readConfig();
1571
1984
  const modelIds = Array.isArray(body.modelIds) ? body.modelIds : [];
1572
1985
  const baseURL = body.baseURL || config.baseURL;
1573
- config.activeModels = modelIds;
1574
- config.baseURL = baseURL;
1575
- await savePluginConfig(config);
1576
- await syncToDshSettings(baseURL, modelIds);
1577
- const dshConfiguredModels = await getDshConfiguredModels();
1986
+ await configAccess.syncModels(baseURL, modelIds);
1987
+ const dshConfiguredModels = await configAccess.getDshConfiguredModels();
1578
1988
  return sendJson(res, 200, { ok: true, dshConfiguredModels });
1579
1989
  }
1990
+ if (pathname === "/pin" && req.method === "POST") {
1991
+ const body = await parseJsonBody(req);
1992
+ const config = await configAccess.readConfig();
1993
+ const modelName = String(body.modelName || "").trim();
1994
+ if (!modelName) return sendJson(res, 400, { ok: false, error: "\u7F3A\u5C11\u6A21\u578B\u540D\u79F0" });
1995
+ const { userId, token } = webAuthOf(config);
1996
+ if (!userId || !token) {
1997
+ return sendJson(res, 400, { ok: false, error: "\u9700\u5728\u300C\u57FA\u7840\u914D\u7F6E\u300D\u586B\u5199\u7CFB\u7EDF\u8BBF\u95EE\u4EE4\u724C/\u4F1A\u8BDD\u540E\u624D\u80FD\u56FA\u5B9A\u5546\u5BB6" });
1998
+ }
1999
+ let card = cachedMerchantOf(modelName);
2000
+ let tokenId = await resolveTokenId(config);
2001
+ if ((!card || !tokenId) && config.apiKey) {
2002
+ try {
2003
+ const probe = await probeSingleModel(config.baseURL, config.apiKey, userId, token, modelName);
2004
+ if (!tokenId && probe.tokenId && Number(probe.tokenId) > 0) tokenId = Number(probe.tokenId);
2005
+ if (!card && probe.merchant) {
2006
+ card = probe.merchant;
2007
+ merchantCardCache.set(modelName.toLowerCase(), { card, at: Date.now() });
2008
+ }
2009
+ } catch {
2010
+ }
2011
+ }
2012
+ if (!card) {
2013
+ return sendJson(res, 400, { ok: false, error: "\u8BE5\u6A21\u578B\u6682\u65E0\u5546\u5BB6\u6570\u636E\uFF0C\u8BF7\u5148\u300C\u63A2\u6D4B\u5546\u5BB6\u300D" });
2014
+ }
2015
+ if (!tokenId) {
2016
+ return sendJson(res, 400, { ok: false, error: "\u65E0\u6CD5\u81EA\u52A8\u89E3\u6790 API Key \u5BF9\u5E94\u7684\u4EE4\u724C ID\uFF0C\u8BF7\u68C0\u67E5\u7CFB\u7EDF\u8BBF\u95EE\u4EE4\u724C\u662F\u5426\u6709\u6548\uFF0C\u6216\u5230\u5B98\u7F51\u300C\u4EE4\u724C\u300D\u9875\u624B\u52A8\u56FA\u5B9A" });
2017
+ }
2018
+ if (!card.channel_id) {
2019
+ return sendJson(res, 400, { ok: false, error: "\u5546\u5BB6\u5361\u7247\u7F3A\u5C11\u6E20\u9053 ID\uFF0C\u8BF7\u91CD\u65B0\u63A2\u6D4B" });
2020
+ }
2021
+ const pinResult = await marketplacePin(userId, token, {
2022
+ token_id: tokenId,
2023
+ channel_id: card.channel_id,
2024
+ model_name: modelName,
2025
+ // 平台默认兜底:渠道异常时自动切换智能优选(不暴露 UI 开关)
2026
+ fallback_to_smart_routing: true
2027
+ });
2028
+ if (!pinResult.ok) {
2029
+ return sendJson(res, 400, { ok: false, error: pinResult.message || "\u56FA\u5B9A\u5931\u8D25" });
2030
+ }
2031
+ tokenResolveCache = { tokenId, at: Date.now() };
2032
+ merchantCardCache.set(modelName.toLowerCase(), {
2033
+ card: { ...card, is_pinned: true, pin_status: "pin_here" },
2034
+ at: Date.now()
2035
+ });
2036
+ const pinList = await fetchMarketplacePins(userId, token);
2037
+ return sendJson(res, 200, { ok: true, message: `\u5DF2\u56FA\u5B9A ${modelName} \u81F3\u5546\u6237 #${card.channel_id}`, pins: pinList, tokenId });
2038
+ }
2039
+ if (pathname === "/unpin" && req.method === "POST") {
2040
+ const body = await parseJsonBody(req);
2041
+ const config = await configAccess.readConfig();
2042
+ const modelName = String(body.modelName || "").trim();
2043
+ if (!modelName) return sendJson(res, 400, { ok: false, error: "\u7F3A\u5C11\u6A21\u578B\u540D\u79F0" });
2044
+ const { userId, token } = webAuthOf(config);
2045
+ if (!userId || !token) {
2046
+ return sendJson(res, 400, { ok: false, error: "\u9700\u5728\u300C\u57FA\u7840\u914D\u7F6E\u300D\u586B\u5199\u7CFB\u7EDF\u8BBF\u95EE\u4EE4\u724C/\u4F1A\u8BDD\u540E\u624D\u80FD\u53D6\u6D88\u56FA\u5B9A" });
2047
+ }
2048
+ const tokenId = await resolveTokenId(config);
2049
+ if (!tokenId) {
2050
+ return sendJson(res, 400, { ok: false, error: "\u65E0\u6CD5\u89E3\u6790 API Key \u5BF9\u5E94\u7684\u4EE4\u724C ID\uFF0C\u8BF7\u68C0\u67E5\u7CFB\u7EDF\u8BBF\u95EE\u4EE4\u724C\u662F\u5426\u6709\u6548" });
2051
+ }
2052
+ const unpinResult = await marketplaceUnpin(userId, token, { token_id: tokenId, model_name: modelName });
2053
+ if (!unpinResult.ok) {
2054
+ return sendJson(res, 400, { ok: false, error: unpinResult.message || "\u53D6\u6D88\u56FA\u5B9A\u5931\u8D25" });
2055
+ }
2056
+ const card = cachedMerchantOf(modelName);
2057
+ if (card) {
2058
+ merchantCardCache.set(modelName.toLowerCase(), {
2059
+ card: { ...card, is_pinned: false, pin_status: void 0 },
2060
+ at: Date.now()
2061
+ });
2062
+ }
2063
+ const pinList = await fetchMarketplacePins(userId, token);
2064
+ return sendJson(res, 200, { ok: true, message: `\u5DF2\u53D6\u6D88\u56FA\u5B9A ${modelName}`, pins: pinList, tokenId });
2065
+ }
2066
+ if (pathname === "/disable" && req.method === "POST") {
2067
+ const body = await parseJsonBody(req);
2068
+ const config = await configAccess.readConfig();
2069
+ const modelName = String(body.modelName || "").trim();
2070
+ if (!modelName) return sendJson(res, 400, { ok: false, error: "\u7F3A\u5C11\u6A21\u578B\u540D\u79F0" });
2071
+ let card = cachedMerchantOf(modelName);
2072
+ if (!card && config.apiKey) {
2073
+ try {
2074
+ const probe = await probeSingleModel(config.baseURL, config.apiKey, config.userId, config.accessToken || "", modelName);
2075
+ if (probe.merchant) {
2076
+ card = probe.merchant;
2077
+ merchantCardCache.set(modelName.toLowerCase(), { card, at: Date.now() });
2078
+ }
2079
+ } catch {
2080
+ }
2081
+ }
2082
+ if (!card || !card.channel_id) {
2083
+ return sendJson(res, 400, { ok: false, error: "\u8BE5\u6A21\u578B\u6682\u65E0\u5546\u5BB6\u6570\u636E\uFF0C\u8BF7\u5148\u300C\u63A2\u6D4B\u5546\u5BB6\u300D" });
2084
+ }
2085
+ const { userId, token } = webAuthOf(config);
2086
+ if (!userId || !token) {
2087
+ return sendJson(res, 400, { ok: false, error: "\u9700\u5728\u300C\u57FA\u7840\u914D\u7F6E\u300D\u586B\u5199\u7CFB\u7EDF\u8BBF\u95EE\u4EE4\u724C/\u4F1A\u8BDD\u540E\u624D\u80FD\u7981\u7528\u5546\u5BB6" });
2088
+ }
2089
+ const disableResult = await marketplaceDisableChannel(userId, token, card.channel_id, modelName);
2090
+ if (!disableResult.ok) {
2091
+ return sendJson(res, 400, { ok: false, error: disableResult.message || "\u7981\u7528\u5931\u8D25" });
2092
+ }
2093
+ merchantCardCache.set(modelName.toLowerCase(), {
2094
+ card: { ...card, user_channel_disabled: true },
2095
+ at: Date.now()
2096
+ });
2097
+ return sendJson(res, 200, { ok: true, message: `\u5DF2\u7981\u7528\u5546\u6237 #${card.channel_id} \u5BF9\u8BE5\u6A21\u578B\u7684\u670D\u52A1` });
2098
+ }
2099
+ if (pathname === "/restore" && req.method === "POST") {
2100
+ const body = await parseJsonBody(req);
2101
+ const config = await configAccess.readConfig();
2102
+ const modelName = String(body.modelName || "").trim();
2103
+ if (!modelName) return sendJson(res, 400, { ok: false, error: "\u7F3A\u5C11\u6A21\u578B\u540D\u79F0" });
2104
+ let card = cachedMerchantOf(modelName);
2105
+ if (!card && config.apiKey) {
2106
+ try {
2107
+ const probe = await probeSingleModel(config.baseURL, config.apiKey, config.userId, config.accessToken || "", modelName);
2108
+ if (probe.merchant) {
2109
+ card = probe.merchant;
2110
+ merchantCardCache.set(modelName.toLowerCase(), { card, at: Date.now() });
2111
+ }
2112
+ } catch {
2113
+ }
2114
+ }
2115
+ if (!card || !card.channel_id) {
2116
+ return sendJson(res, 400, { ok: false, error: "\u8BE5\u6A21\u578B\u6682\u65E0\u5546\u5BB6\u6570\u636E\uFF0C\u8BF7\u5148\u300C\u63A2\u6D4B\u5546\u5BB6\u300D" });
2117
+ }
2118
+ const { userId, token } = webAuthOf(config);
2119
+ if (!userId || !token) {
2120
+ return sendJson(res, 400, { ok: false, error: "\u9700\u5728\u300C\u57FA\u7840\u914D\u7F6E\u300D\u586B\u5199\u7CFB\u7EDF\u8BBF\u95EE\u4EE4\u724C/\u4F1A\u8BDD\u540E\u624D\u80FD\u6062\u590D\u5546\u5BB6" });
2121
+ }
2122
+ const restoreResult = await marketplaceRestoreChannel(userId, token, card.channel_id, modelName);
2123
+ if (!restoreResult.ok) {
2124
+ return sendJson(res, 400, { ok: false, error: restoreResult.message || "\u6062\u590D\u5931\u8D25" });
2125
+ }
2126
+ merchantCardCache.set(modelName.toLowerCase(), {
2127
+ card: { ...card, user_channel_disabled: false },
2128
+ at: Date.now()
2129
+ });
2130
+ return sendJson(res, 200, { ok: true, message: `\u5DF2\u6062\u590D\u5546\u6237 #${card.channel_id} \u5BF9\u8BE5\u6A21\u578B\u7684\u670D\u52A1` });
2131
+ }
1580
2132
  if (pathname === "/price-fluctuation" && (req.method === "GET" || req.method === "HEAD")) {
1581
- const config = await readPluginConfig();
1582
- const token = config.accessToken || config.sessionCookie || "";
2133
+ const config = await configAccess.readConfig();
2134
+ const token = config.accessToken || "";
1583
2135
  if (!token || !config.userId) {
1584
2136
  return sendJson(res, 200, { ok: true, data: { pendingCount: 0, unseenCount: 0, totalCount: 0, hasAuth: false, authError: false, updatedAt: Date.now() } });
1585
2137
  }
1586
- const result = await fetchPriceFluctuation(config.userId, token, token);
2138
+ const result = await fetchPriceFluctuation(config.userId, token);
1587
2139
  const { notices, ...counts } = result;
1588
2140
  const hasAuth = !counts.authError;
1589
2141
  return sendJson(res, 200, { ok: true, data: { pendingCount: counts.pendingCount, unseenCount: counts.unseenCount, totalCount: counts.totalCount, hasAuth, authError: Boolean(counts.authError), updatedAt: Date.now() } });
1590
2142
  }
2143
+ if (pathname === "/pins" && (req.method === "GET" || req.method === "HEAD")) {
2144
+ const config = await configAccess.readConfig();
2145
+ const { userId, token } = webAuthOf(config);
2146
+ if (!userId || !token) {
2147
+ return sendJson(res, 200, { ok: true, pins: [] });
2148
+ }
2149
+ try {
2150
+ const pins = await fetchMarketplacePins(userId, token);
2151
+ return sendJson(res, 200, { ok: true, pins });
2152
+ } catch (err) {
2153
+ console.warn("[dsh-a6api] GET /pins error:", err);
2154
+ return sendJson(res, 200, { ok: true, pins: [] });
2155
+ }
2156
+ }
2157
+ if (pathname === "/catalog" && (req.method === "GET" || req.method === "HEAD")) {
2158
+ return sendJson(res, 200, { ok: true, catalog: getCatalog() });
2159
+ }
2160
+ if (pathname === "/catalog/clear" && req.method === "POST") {
2161
+ await clearCatalog();
2162
+ return sendJson(res, 200, { ok: true });
2163
+ }
2164
+ if (pathname === "/catalog/fetch-models" && req.method === "POST") {
2165
+ const config = await configAccess.readConfig();
2166
+ const { userId, token } = webAuthOf(config);
2167
+ let result;
2168
+ try {
2169
+ result = await fetchMarketplaceModels(userId, token);
2170
+ } catch (err) {
2171
+ return sendJson(res, 400, { ok: false, error: err?.message || "\u83B7\u53D6\u5E02\u573A\u6A21\u578B\u5931\u8D25" });
2172
+ }
2173
+ const models = result.models;
2174
+ const before = new Set(getCatalog().map((e) => e.id.toLowerCase()));
2175
+ let added = 0;
2176
+ for (const m of models) {
2177
+ if (!before.has(m.id.toLowerCase())) added++;
2178
+ }
2179
+ await upsertCatalogEntries(
2180
+ models.map((m) => ({ id: m.id, brand: m.brand, reasoningEfforts: m.reasoningEfforts }))
2181
+ );
2182
+ return sendJson(res, 200, {
2183
+ ok: true,
2184
+ total: models.length,
2185
+ added,
2186
+ failedPages: result.failedPages
2187
+ });
2188
+ }
2189
+ if (pathname === "/catalog/query-openrouter" && req.method === "POST") {
2190
+ const body = await parseJsonBody(req);
2191
+ const catalog = getCatalog();
2192
+ const modelIds = Array.isArray(body.modelIds) && body.modelIds.length > 0 ? body.modelIds.map((s) => String(s)) : catalog.map((e) => e.id);
2193
+ if (modelIds.length === 0) {
2194
+ return sendJson(res, 400, { ok: false, error: "\u76EE\u5F55\u4E3A\u7A7A\uFF0C\u8BF7\u5148\u300C\u4ECE A6API \u83B7\u53D6\u5E02\u573A\u6A21\u578B\u300D" });
2195
+ }
2196
+ const result = await queryOpenRouter(modelIds);
2197
+ return sendJson(res, 200, {
2198
+ ok: true,
2199
+ updated: result.updated.length,
2200
+ notFound: result.notFound
2201
+ });
2202
+ }
2203
+ if (pathname === "/catalog/update" && req.method === "POST") {
2204
+ const body = await parseJsonBody(req);
2205
+ const id = String(body.id || "").trim();
2206
+ if (!id) return sendJson(res, 400, { ok: false, error: "\u7F3A\u5C11\u6A21\u578B ID" });
2207
+ const patch = {};
2208
+ if (body.name !== void 0) {
2209
+ if (body.name === null) {
2210
+ patch.name = null;
2211
+ } else if (typeof body.name !== "string") {
2212
+ return sendJson(res, 400, { ok: false, error: "name \u5FC5\u987B\u662F\u5B57\u7B26\u4E32" });
2213
+ } else {
2214
+ const name2 = body.name.trim();
2215
+ patch.name = name2 || null;
2216
+ }
2217
+ }
2218
+ for (const key of ["contextWindow", "maxTokens"]) {
2219
+ if (body[key] === null) {
2220
+ patch[key] = null;
2221
+ } else if (body[key] !== void 0) {
2222
+ const n = Number(body[key]);
2223
+ if (!Number.isInteger(n) || n < 1) {
2224
+ return sendJson(res, 400, { ok: false, error: `${key} \u5FC5\u987B\u662F\u6B63\u6574\u6570` });
2225
+ }
2226
+ patch[key] = n;
2227
+ }
2228
+ }
2229
+ if (body.input !== void 0) {
2230
+ if (body.input === null) {
2231
+ patch.input = null;
2232
+ } else if (!Array.isArray(body.input)) {
2233
+ return sendJson(res, 400, { ok: false, error: "input \u5FC5\u987B\u662F\u6570\u7EC4" });
2234
+ } else {
2235
+ const mods = body.input.filter((m) => m === "text" || m === "image");
2236
+ patch.input = mods.length > 0 ? mods : null;
2237
+ }
2238
+ }
2239
+ if (body.reasoningEfforts !== void 0) {
2240
+ if (body.reasoningEfforts === null) {
2241
+ patch.reasoningEfforts = null;
2242
+ } else {
2243
+ const v = validateReasoningEfforts(body.reasoningEfforts);
2244
+ if (!v.ok) return sendJson(res, 400, { ok: false, error: v.error });
2245
+ patch.reasoningEfforts = v.value;
2246
+ }
2247
+ }
2248
+ const entry = await updateCatalogEntry(id, patch);
2249
+ if (!entry) return sendJson(res, 404, { ok: false, error: "\u76EE\u5F55\u4E2D\u4E0D\u5B58\u5728\u8BE5\u6A21\u578B" });
2250
+ try {
2251
+ const config = await configAccess.readConfig();
2252
+ const dshModels = await configAccess.getDshConfiguredModels();
2253
+ if (dshModels.some((m) => m.toLowerCase() === entry.id.toLowerCase()) && config.activeModels.length > 0) {
2254
+ await configAccess.syncModels(config.baseURL, config.activeModels);
2255
+ }
2256
+ } catch (err) {
2257
+ console.warn("[dsh-a6api] catalog update: resync settings failed:", err?.message || err);
2258
+ }
2259
+ return sendJson(res, 200, { ok: true, entry });
2260
+ }
1591
2261
  return sendJson(res, 404, { ok: false, error: "Not found" });
1592
2262
  } catch (err) {
1593
2263
  console.error("[dsh-a6api] API error:", err);
@@ -1602,19 +2272,27 @@ function apply(ctx) {
1602
2272
  }
1603
2273
  }
1604
2274
  export {
1605
- A6API_CATALOG,
2275
+ A6API_CRED_REF,
2276
+ A6API_TOKEN_REF,
2277
+ A6API_USER_REF,
1606
2278
  apply,
2279
+ clearCatalog,
2280
+ createConfigAccess,
1607
2281
  fetchBalance,
1608
2282
  fetchChannelDetails,
2283
+ fetchMarketplaceModels,
2284
+ fetchMarketplacePins,
1609
2285
  fetchRecentLogs,
1610
2286
  fetchTokenModels,
2287
+ getCatalog,
2288
+ getCatalogEntry,
1611
2289
  getKnownMerchantsFromLogs,
2290
+ inferBrand,
1612
2291
  inject,
1613
2292
  name,
1614
2293
  probeSingleModel,
1615
- readPluginConfig,
2294
+ queryOpenRouter,
1616
2295
  resolveModelMeta,
1617
- savePluginConfig,
1618
- syncToDshSettings
2296
+ updateCatalogEntry
1619
2297
  };
1620
2298
  //# sourceMappingURL=index.js.map