@lynn123411/dsh-a6api 1.1.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
  };
@@ -1032,13 +1008,14 @@ async function probeSingleModel(baseURL, apiKey, userId, accessToken, modelName)
1032
1008
  error: requestOk ? void 0 : requestError
1033
1009
  };
1034
1010
  }
1035
- async function getKnownMerchantsFromLogs(userId, accessToken, modelNames = []) {
1011
+ async function getKnownMerchantsFromLogs(userId, accessToken, modelNames = [], logs) {
1036
1012
  if (!userId && !accessToken || modelNames.length === 0) return {};
1037
1013
  const result = {};
1038
1014
  try {
1039
- const logs = await fetchRecentLogs(userId, accessToken, 50);
1015
+ const items = logs !== void 0 ? logs : await fetchRecentLogs(userId, accessToken, 50);
1016
+ const sorted = items.slice().sort((a, b) => (Number(b.created_at) || 0) - (Number(a.created_at) || 0));
1040
1017
  const modelToLog = /* @__PURE__ */ new Map();
1041
- for (const log of logs) {
1018
+ for (const log of sorted) {
1042
1019
  const mName = log.model_name;
1043
1020
  const chId = Number(log.channel);
1044
1021
  if (mName && chId && !modelToLog.has(mName.toLowerCase())) {
@@ -1081,77 +1058,56 @@ async function getKnownMerchantsFromLogs(userId, accessToken, modelNames = []) {
1081
1058
  }
1082
1059
 
1083
1060
  // src/server/sync.ts
1084
- import * as fsp from "node:fs/promises";
1085
- import * as path from "node:path";
1086
- 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";
1087
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";
1088
1072
  async function atomicWriteFile(filePath, content, mode = 384) {
1089
- const dir = path.dirname(filePath);
1090
- await fsp.mkdir(dir, { recursive: true });
1091
- const tmpPath = path.join(dir, `.${path.basename(filePath)}.${process.pid}.${Date.now()}.tmp`);
1092
- await fsp.writeFile(tmpPath, content, { mode });
1093
- 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);
1094
1078
  }
1095
- function dshHome() {
1096
- return process.env.DSH_HOME || path.join(os.homedir(), ".dsh");
1079
+ function dshHome2() {
1080
+ return process.env.DSH_HOME || path2.join(os2.homedir(), ".dsh");
1097
1081
  }
1098
- function configFile() {
1099
- return path.join(dshHome(), "dsh-a6api-config.json");
1082
+ function legacyConfigFile() {
1083
+ return path2.join(dshHome2(), LEGACY_CONFIG_NAME);
1100
1084
  }
1101
1085
  function credentialsFile() {
1102
- return path.join(dshHome(), ".credentials.yaml");
1086
+ return path2.join(dshHome2(), ".credentials.yaml");
1103
1087
  }
1104
1088
  function settingsFile() {
1105
- return path.join(dshHome(), "settings.yaml");
1089
+ return path2.join(dshHome2(), "settings.yaml");
1106
1090
  }
1107
- async function readPluginConfig() {
1108
- const filePath = configFile();
1109
- let raw = "";
1091
+ function getCredentials(ctx) {
1110
1092
  try {
1111
- raw = await fsp.readFile(filePath, "utf8");
1093
+ if (ctx && typeof ctx.get === "function") return ctx.get("credentials");
1112
1094
  } catch {
1113
- const apiKey = await readCredentialKey(A6API_CRED_REF) || "";
1114
- return {
1115
- baseURL: "https://api.a6api.com",
1116
- apiKey,
1117
- userId: "",
1118
- activeModels: []
1119
- };
1120
1095
  }
1096
+ return void 0;
1097
+ }
1098
+ function getSettings(ctx) {
1121
1099
  try {
1122
- const parsed = JSON.parse(raw);
1123
- const apiKey = parsed.apiKey || await readCredentialKey(A6API_CRED_REF) || "";
1124
- const accessToken = parsed.accessToken || parsed.systemAccessToken || parsed.sessionCookie || "";
1125
- return {
1126
- baseURL: parsed.baseURL || "https://api.a6api.com",
1127
- apiKey,
1128
- accessToken,
1129
- userId: parsed.userId || "",
1130
- sessionCookie: accessToken,
1131
- activeModels: Array.isArray(parsed.activeModels) ? parsed.activeModels : [],
1132
- customBaseURL: parsed.customBaseURL
1133
- };
1100
+ if (ctx && typeof ctx.get === "function") return ctx.get("settings");
1134
1101
  } catch {
1135
- return {
1136
- baseURL: "https://api.a6api.com",
1137
- apiKey: "",
1138
- accessToken: "",
1139
- userId: "",
1140
- activeModels: []
1141
- };
1142
1102
  }
1103
+ return void 0;
1143
1104
  }
1144
- async function savePluginConfig(config) {
1145
- const filePath = configFile();
1146
- const { apiKey: _apiKey, ...safeConfig } = config;
1147
- await atomicWriteFile(filePath, JSON.stringify(safeConfig, null, 2));
1148
- if (config.apiKey && config.apiKey.trim()) {
1149
- await writeCredentialKey(A6API_CRED_REF, config.apiKey.trim());
1150
- }
1105
+ function stripV1(baseURL) {
1106
+ return baseURL.replace(/\/v1\/?$/, "");
1151
1107
  }
1152
1108
  async function readCredentialKey(refKey) {
1153
1109
  try {
1154
- const yaml = await fsp.readFile(credentialsFile(), "utf8");
1110
+ const yaml = await fsp2.readFile(credentialsFile(), "utf8");
1155
1111
  let inRefs = false;
1156
1112
  for (const line of yaml.split(/\r?\n/)) {
1157
1113
  const trimmed = line.trim();
@@ -1168,7 +1124,7 @@ async function readCredentialKey(refKey) {
1168
1124
  if (val.startsWith('"') && val.endsWith('"') || val.startsWith("'") && val.endsWith("'")) {
1169
1125
  val = val.slice(1, -1);
1170
1126
  }
1171
- return val;
1127
+ return val || null;
1172
1128
  }
1173
1129
  }
1174
1130
  } catch {
@@ -1179,14 +1135,14 @@ async function writeCredentialKey(refKey, value) {
1179
1135
  const cFile = credentialsFile();
1180
1136
  let yaml = "";
1181
1137
  try {
1182
- yaml = await fsp.readFile(cFile, "utf8");
1138
+ yaml = await fsp2.readFile(cFile, "utf8");
1183
1139
  } catch {
1184
1140
  yaml = "version: 1\nrefs:\n";
1185
1141
  }
1186
1142
  const lines = yaml.split(/\r?\n/);
1187
1143
  let inRefs = false;
1188
1144
  let refsLineIdx = -1;
1189
- let found = false;
1145
+ let foundIdx = -1;
1190
1146
  for (let i = 0; i < lines.length; i++) {
1191
1147
  const line = lines[i];
1192
1148
  const trimmed = line.trim();
@@ -1204,58 +1160,81 @@ async function writeCredentialKey(refKey, value) {
1204
1160
  if (inRefs) {
1205
1161
  const m = /^([A-Za-z0-9_.\-]+):/.exec(trimmed);
1206
1162
  if (m && m[1] === refKey) {
1207
- lines[i] = ` ${refKey}: ${JSON.stringify(value)}`;
1208
- found = true;
1163
+ foundIdx = i;
1209
1164
  break;
1210
1165
  }
1211
1166
  }
1212
1167
  }
1213
- if (!found) {
1214
- if (refsLineIdx >= 0) {
1215
- 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)}`;
1216
1173
  } else {
1217
- 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
+ }
1218
1179
  }
1219
1180
  }
1220
1181
  await atomicWriteFile(cFile, lines.join("\n"), 384);
1221
1182
  }
1222
- async function syncToDshSettings(baseURL, modelIds) {
1223
- const sFile = settingsFile();
1224
- let yaml = "";
1183
+ async function readRawConfiguredModels() {
1225
1184
  try {
1226
- 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;
1227
1212
  } catch {
1228
- yaml = "llm-pi-ai:\n providers:\n";
1213
+ return [];
1229
1214
  }
1230
- const modelEntries = modelIds.map((id) => {
1231
- const meta = resolveModelMeta(id);
1232
- const lines2 = [
1233
- ` - id: ${meta.id}`,
1234
- ` name: ${meta.name}`,
1235
- ` contextWindow: ${meta.contextWindow}`,
1236
- ` maxTokens: ${meta.maxTokens}`,
1237
- ` input:`,
1238
- ...meta.modalities.map((m) => ` - ${m}`)
1239
- ];
1240
- if (meta.reasoningEfforts) {
1241
- lines2.push(` reasoningEfforts:`);
1242
- for (const [k, v] of Object.entries(meta.reasoningEfforts)) {
1243
- 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, "");
1244
1231
  }
1245
1232
  }
1246
- return lines2.join("\n");
1247
- });
1248
- const dshBaseUrl = baseURL.endsWith("/v1") ? baseURL : `${baseURL.replace(/\/+$/, "")}/v1`;
1249
- const a6apiBlockLines = [
1250
- ` a6api:`,
1251
- ` displayName: A6API`,
1252
- ` apiKeyEnv: ${A6API_CRED_REF}`,
1253
- ` api: openai-completions`,
1254
- ` baseURL: ${dshBaseUrl}`,
1255
- ` models:`,
1256
- ...modelEntries.length > 0 ? modelEntries : [` []`]
1257
- ];
1258
- const lines = yaml.split(/\r?\n/);
1233
+ } catch {
1234
+ }
1235
+ return "";
1236
+ }
1237
+ function scanA6apiBlockRange(lines) {
1259
1238
  let inLlm = false;
1260
1239
  let inProviders = false;
1261
1240
  let inA6 = false;
@@ -1301,6 +1280,46 @@ async function syncToDshSettings(baseURL, modelIds) {
1301
1280
  inA6 = false;
1302
1281
  }
1303
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);
1304
1323
  if (a6Start >= 0) {
1305
1324
  lines.splice(a6Start, a6End - a6Start, ...a6apiBlockLines);
1306
1325
  } else if (providersLineIdx >= 0) {
@@ -1312,38 +1331,305 @@ async function syncToDshSettings(baseURL, modelIds) {
1312
1331
  }
1313
1332
  await atomicWriteFile(sFile, lines.join("\n"), 420);
1314
1333
  }
1315
- async function getDshConfiguredModels() {
1334
+ async function removeRawA6apiBlock() {
1335
+ const sFile = settingsFile();
1336
+ let yaml = "";
1316
1337
  try {
1317
- const yaml = await fsp.readFile(settingsFile(), "utf8");
1318
- const lines = yaml.split(/\r?\n/);
1319
- let inA6 = false;
1320
- let inModels = false;
1321
- const modelIds = [];
1322
- for (const line of lines) {
1323
- const trimmed = line.trim();
1324
- const indent = line.match(/^\s*/)?.[0].length ?? 0;
1325
- if (indent === 4 && trimmed.startsWith("a6api:")) {
1326
- inA6 = true;
1327
- inModels = false;
1328
- 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;
1329
1356
  }
1330
- if (inA6 && indent <= 4 && !trimmed.startsWith("a6api:")) {
1331
- inA6 = false;
1332
- 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;
1333
1370
  }
1334
- if (inA6 && indent === 6 && trimmed.startsWith("models:")) {
1335
- inModels = true;
1336
- 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 : "";
1337
1436
  }
1338
- if (inModels && indent === 8 && trimmed.startsWith("- id:")) {
1339
- const id = trimmed.replace(/^- id:\s*/, "").trim();
1340
- 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
+ }
1341
1453
  }
1454
+ } catch (err) {
1455
+ console.warn("[dsh-a6api] settings.get(llm-pi-ai) failed:", err?.message || err);
1342
1456
  }
1343
- return modelIds;
1344
- } catch {
1345
- 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" };
1346
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 };
1347
1633
  }
1348
1634
 
1349
1635
  // src/index.ts
@@ -1355,11 +1641,10 @@ function maskConfig(c) {
1355
1641
  return {
1356
1642
  ...c,
1357
1643
  apiKey: c.apiKey ? MASK : "",
1358
- accessToken: c.accessToken || c.sessionCookie ? MASK : "",
1359
- sessionCookie: "",
1644
+ accessToken: c.accessToken ? MASK : "",
1360
1645
  userId: c.userId ? MASK : "",
1361
1646
  hasApiKey: Boolean(c.apiKey),
1362
- hasToken: Boolean(c.accessToken || c.sessionCookie)
1647
+ hasToken: Boolean(c.accessToken)
1363
1648
  };
1364
1649
  }
1365
1650
  function sendJson(res, status, body) {
@@ -1395,7 +1680,68 @@ async function parseJsonBody(req) {
1395
1680
  }
1396
1681
  var merchantCardCache = /* @__PURE__ */ new Map();
1397
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
+ }
1398
1742
  function apply(ctx) {
1743
+ const configAccess = createConfigAccess(ctx);
1744
+ void configAccess.ensureMigrated();
1399
1745
  const webServer = ctx.webServer || (ctx.get ? ctx.get("webServer") : null);
1400
1746
  if (webServer && typeof webServer.register === "function") {
1401
1747
  ctx.effect(() => {
@@ -1409,24 +1755,29 @@ function apply(ctx) {
1409
1755
  res.writeHead(204);
1410
1756
  return res.end();
1411
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
+ }
1412
1761
  try {
1413
1762
  if (pathname === "/state" && (req.method === "GET" || req.method === "HEAD")) {
1414
- const config = await readPluginConfig();
1415
- const token = config.accessToken || config.sessionCookie || "";
1416
- 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
+ ]);
1417
1772
  if (balance?.userId && String(balance.userId) !== config.userId) {
1773
+ tokenResolveCache = null;
1418
1774
  config.userId = String(balance.userId);
1419
- await savePluginConfig(config);
1420
- }
1421
- const dshConfiguredModels = await getDshConfiguredModels();
1422
- let modelIds = [];
1423
- if (config.apiKey) {
1424
- modelIds = await fetchTokenModels(config.baseURL, config.apiKey);
1775
+ await configAccess.writeConfig({ userId: config.userId });
1425
1776
  }
1777
+ let modelIds = modelIdsRaw;
1426
1778
  if (modelIds.length === 0) {
1427
1779
  modelIds = [
1428
1780
  .../* @__PURE__ */ new Set([
1429
- ...config.activeModels,
1430
1781
  ...dshConfiguredModels,
1431
1782
  "gpt-5.6-sol",
1432
1783
  "gpt-5.6-terra",
@@ -1437,23 +1788,42 @@ function apply(ctx) {
1437
1788
  ])
1438
1789
  ];
1439
1790
  }
1791
+ allLogs.sort((a, b) => (Number(b.created_at) || 0) - (Number(a.created_at) || 0));
1440
1792
  if (config.userId || token) {
1441
1793
  const missing = modelIds.filter((m) => {
1442
1794
  const entry = merchantCardCache.get(m.toLowerCase());
1443
1795
  return !entry || Date.now() - entry.at >= MERCHANT_CARD_TTL_MS;
1444
1796
  });
1445
1797
  if (missing.length > 0) {
1446
- const found = await getKnownMerchantsFromLogs(config.userId, token, missing);
1798
+ let found = {};
1799
+ try {
1800
+ found = await Promise.race([
1801
+ getKnownMerchantsFromLogs(config.userId, token, missing, allLogs),
1802
+ new Promise((resolve) => setTimeout(() => resolve({}), 1e4))
1803
+ ]);
1804
+ } catch {
1805
+ found = {};
1806
+ }
1447
1807
  for (const [mName, card] of Object.entries(found)) {
1448
1808
  merchantCardCache.set(mName.toLowerCase(), { card, at: Date.now() });
1449
1809
  }
1450
1810
  }
1451
1811
  }
1812
+ const lastRoutedMap = /* @__PURE__ */ new Map();
1813
+ for (const log of allLogs) {
1814
+ const mName = log.model_name;
1815
+ const chId = Number(log.channel);
1816
+ const ts = Number(log.created_at) || 0;
1817
+ if (mName && chId > 0 && ts > 0 && !lastRoutedMap.has(mName.toLowerCase())) {
1818
+ lastRoutedMap.set(mName.toLowerCase(), ts);
1819
+ }
1820
+ }
1452
1821
  const dshSet = new Set(dshConfiguredModels);
1453
- const models = modelIds.map((mId) => {
1822
+ let models = modelIds.map((mId) => {
1454
1823
  const meta = resolveModelMeta(mId);
1455
1824
  const cacheEntry = merchantCardCache.get(mId.toLowerCase());
1456
1825
  const cachedCard = cacheEntry && Date.now() - cacheEntry.at < MERCHANT_CARD_TTL_MS ? cacheEntry.card : void 0;
1826
+ const routedAt = lastRoutedMap.get(mId.toLowerCase());
1457
1827
  return {
1458
1828
  model_name: mId,
1459
1829
  brand: meta.brand,
@@ -1463,60 +1833,125 @@ function apply(ctx) {
1463
1833
  hasReasoning: Boolean(meta.reasoningEfforts || meta.thinkingFormat),
1464
1834
  inDsh: dshSet.has(mId),
1465
1835
  merchant: cachedCard,
1466
- probeStatus: cachedCard ? "success" : "idle"
1836
+ probeStatus: cachedCard ? "success" : "idle",
1837
+ lastRoutedAt: routedAt,
1838
+ lastRoutedText: routedAt ? formatRelativeTime(routedAt) : void 0
1467
1839
  };
1468
1840
  });
1469
- const recentLogs = await fetchRecentLogs(config.userId, token, 20);
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
+ }
1895
+ const recentLogs = allLogs.slice(0, 20);
1470
1896
  const response = {
1471
1897
  config: maskConfig(config),
1472
1898
  balance,
1473
1899
  models,
1474
1900
  dshConfiguredModels,
1475
- recentLogs
1901
+ recentLogs,
1902
+ pins
1476
1903
  };
1477
1904
  return sendJson(res, 200, { ok: true, data: response });
1478
1905
  }
1479
1906
  if (pathname === "/config" && req.method === "POST") {
1480
1907
  const body = await parseJsonBody(req);
1481
- const current = await readPluginConfig();
1482
- 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;
1483
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
+ }
1484
1915
  const updated = {
1485
1916
  baseURL: body.baseURL !== void 0 ? body.baseURL : current.baseURL,
1486
1917
  apiKey: newApiKey,
1487
1918
  accessToken: rawToken,
1488
- sessionCookie: rawToken,
1489
- userId: body.userId !== void 0 ? body.userId : current.userId,
1490
- activeModels: Array.isArray(body.activeModels) ? body.activeModels : current.activeModels,
1491
- 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
1492
1921
  };
1493
1922
  const balance = await fetchBalance(updated.baseURL, updated.apiKey, updated.userId, updated.accessToken);
1494
1923
  if (balance?.userId) {
1495
1924
  updated.userId = String(balance.userId);
1496
1925
  }
1497
- 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
+ }
1498
1933
  if (updated.activeModels.length > 0) {
1499
- await syncToDshSettings(updated.baseURL, updated.activeModels);
1934
+ await configAccess.syncModels(updated.baseURL, updated.activeModels);
1500
1935
  }
1501
1936
  return sendJson(res, 200, { ok: true, config: maskConfig(updated), balance });
1502
1937
  }
1503
1938
  if (pathname === "/balance" && (req.method === "GET" || req.method === "HEAD")) {
1504
- const config = await readPluginConfig();
1505
- const token = config.accessToken || config.sessionCookie || "";
1939
+ const config = await configAccess.readConfig();
1940
+ const token = config.accessToken || "";
1506
1941
  const balance = await fetchBalance(config.baseURL, config.apiKey, config.userId, token);
1507
1942
  const recentLogs = await fetchRecentLogs(config.userId, token, 20);
1508
1943
  return sendJson(res, 200, { ok: true, balance, recentLogs });
1509
1944
  }
1510
1945
  if (pathname === "/logs" && (req.method === "GET" || req.method === "HEAD")) {
1511
- const config = await readPluginConfig();
1512
- const token = config.accessToken || config.sessionCookie || "";
1946
+ const config = await configAccess.readConfig();
1947
+ const token = config.accessToken || "";
1513
1948
  const recentLogs = await fetchRecentLogs(config.userId, token, 30);
1514
1949
  return sendJson(res, 200, { ok: true, logs: recentLogs });
1515
1950
  }
1516
1951
  if (pathname === "/probe" && req.method === "POST") {
1517
1952
  const body = await parseJsonBody(req);
1518
- const config = await readPluginConfig();
1519
- const token = config.accessToken || config.sessionCookie || "";
1953
+ const config = await configAccess.readConfig();
1954
+ const token = config.accessToken || "";
1520
1955
  const modelName = body.modelName;
1521
1956
  if (modelName && modelName !== "all") {
1522
1957
  const result = await probeSingleModel(config.baseURL, config.apiKey, config.userId, token, modelName);
@@ -1530,7 +1965,7 @@ function apply(ctx) {
1530
1965
  modelIds = await fetchTokenModels(config.baseURL, config.apiKey);
1531
1966
  }
1532
1967
  if (modelIds.length === 0) {
1533
- modelIds = config.activeModels;
1968
+ modelIds = await configAccess.getDshConfiguredModels();
1534
1969
  }
1535
1970
  const results = [];
1536
1971
  for (const m of modelIds) {
@@ -1544,27 +1979,285 @@ function apply(ctx) {
1544
1979
  }
1545
1980
  if (pathname === "/sync-models" && req.method === "POST") {
1546
1981
  const body = await parseJsonBody(req);
1547
- const config = await readPluginConfig();
1982
+ await configAccess.ensureMigrated();
1983
+ const config = await configAccess.readConfig();
1548
1984
  const modelIds = Array.isArray(body.modelIds) ? body.modelIds : [];
1549
1985
  const baseURL = body.baseURL || config.baseURL;
1550
- config.activeModels = modelIds;
1551
- config.baseURL = baseURL;
1552
- await savePluginConfig(config);
1553
- await syncToDshSettings(baseURL, modelIds);
1554
- const dshConfiguredModels = await getDshConfiguredModels();
1986
+ await configAccess.syncModels(baseURL, modelIds);
1987
+ const dshConfiguredModels = await configAccess.getDshConfiguredModels();
1555
1988
  return sendJson(res, 200, { ok: true, dshConfiguredModels });
1556
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
+ }
1557
2132
  if (pathname === "/price-fluctuation" && (req.method === "GET" || req.method === "HEAD")) {
1558
- const config = await readPluginConfig();
1559
- const token = config.accessToken || config.sessionCookie || "";
2133
+ const config = await configAccess.readConfig();
2134
+ const token = config.accessToken || "";
1560
2135
  if (!token || !config.userId) {
1561
2136
  return sendJson(res, 200, { ok: true, data: { pendingCount: 0, unseenCount: 0, totalCount: 0, hasAuth: false, authError: false, updatedAt: Date.now() } });
1562
2137
  }
1563
- const result = await fetchPriceFluctuation(config.userId, token, token);
2138
+ const result = await fetchPriceFluctuation(config.userId, token);
1564
2139
  const { notices, ...counts } = result;
1565
2140
  const hasAuth = !counts.authError;
1566
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() } });
1567
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
+ }
1568
2261
  return sendJson(res, 404, { ok: false, error: "Not found" });
1569
2262
  } catch (err) {
1570
2263
  console.error("[dsh-a6api] API error:", err);
@@ -1579,19 +2272,27 @@ function apply(ctx) {
1579
2272
  }
1580
2273
  }
1581
2274
  export {
1582
- A6API_CATALOG,
2275
+ A6API_CRED_REF,
2276
+ A6API_TOKEN_REF,
2277
+ A6API_USER_REF,
1583
2278
  apply,
2279
+ clearCatalog,
2280
+ createConfigAccess,
1584
2281
  fetchBalance,
1585
2282
  fetchChannelDetails,
2283
+ fetchMarketplaceModels,
2284
+ fetchMarketplacePins,
1586
2285
  fetchRecentLogs,
1587
2286
  fetchTokenModels,
2287
+ getCatalog,
2288
+ getCatalogEntry,
1588
2289
  getKnownMerchantsFromLogs,
2290
+ inferBrand,
1589
2291
  inject,
1590
2292
  name,
1591
2293
  probeSingleModel,
1592
- readPluginConfig,
2294
+ queryOpenRouter,
1593
2295
  resolveModelMeta,
1594
- savePluginConfig,
1595
- syncToDshSettings
2296
+ updateCatalogEntry
1596
2297
  };
1597
2298
  //# sourceMappingURL=index.js.map