coding-tool-x 3.6.3 → 3.6.4

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.
@@ -10,8 +10,12 @@ const MAX_RESPONSE_BYTES = 1024 * 1024;
10
10
  const QUOTA_UNIT = 500000;
11
11
  const VELOERA_QUOTA_UNIT = 1000000;
12
12
  const VALID_SOURCES = new Set(['claude', 'codex', 'gemini', 'opencode']);
13
+ const GATEWAY_PATH_SEGMENTS = new Set(['openai', 'anthropic', 'claude', 'gemini', 'codex']);
14
+ const HUB_PLATFORMS = new Set(['new-api', 'one-api', 'one-hub', 'done-hub', 'veloera', 'anyrouter', 'aihubmix']);
15
+ const UNSUPPORTED_BALANCE_PLATFORMS = new Set(['dashscope', 'modelscope']);
13
16
 
14
17
  const balanceCache = new Map();
18
+ const balanceStrategyCache = new Map();
15
19
 
16
20
  function nowIso(now = Date.now()) {
17
21
  return new Date(now).toISOString();
@@ -22,6 +26,18 @@ function stripBearer(value) {
22
26
  return token.toLowerCase().startsWith('bearer ') ? token.slice(7).trim() : token;
23
27
  }
24
28
 
29
+ function resolveBalanceToken(channel = {}) {
30
+ return stripBearer(
31
+ channel.balanceToken
32
+ || channel.balanceApiKey
33
+ || channel.balanceAuthToken
34
+ || channel.apiKey
35
+ || channel.token
36
+ || channel.authToken
37
+ || ''
38
+ );
39
+ }
40
+
25
41
  function buildAuthHeaders(token, extra = {}) {
26
42
  return {
27
43
  Accept: 'application/json',
@@ -30,6 +46,14 @@ function buildAuthHeaders(token, extra = {}) {
30
46
  };
31
47
  }
32
48
 
49
+ function isLikelyModelApiKey(token) {
50
+ return /^sk-[a-z0-9]/i.test(stripBearer(token));
51
+ }
52
+
53
+ function isLikelyOpenRouterApiKey(token) {
54
+ return /^sk-or-/i.test(stripBearer(token));
55
+ }
56
+
33
57
  function parseFiniteNumber(value) {
34
58
  if (typeof value === 'number' && Number.isFinite(value)) return value;
35
59
  if (typeof value === 'string') {
@@ -44,6 +68,11 @@ function roundMoney(value) {
44
68
  return Math.round(value * 1000000) / 1000000;
45
69
  }
46
70
 
71
+ function normalizeQuotaUnit(value, fallback = QUOTA_UNIT) {
72
+ const parsed = parseFiniteNumber(value);
73
+ return parsed != null && parsed > 0 ? parsed : fallback;
74
+ }
75
+
47
76
  function formatMoney(value) {
48
77
  if (!Number.isFinite(value)) return '';
49
78
  return `$${value.toFixed(2)}`;
@@ -144,7 +173,13 @@ function joinUrl(baseUrl, endpoint) {
144
173
  return `${base}${path}`;
145
174
  }
146
175
 
147
- function requestJson(url, { headers = {}, timeoutMs = REQUEST_TIMEOUT_MS } = {}) {
176
+ function requestJson(url, {
177
+ method = 'GET',
178
+ headers = {},
179
+ body,
180
+ timeoutMs = REQUEST_TIMEOUT_MS,
181
+ redirectCount = 0
182
+ } = {}) {
148
183
  return new Promise((resolve, reject) => {
149
184
  let parsed;
150
185
  try {
@@ -155,13 +190,21 @@ function requestJson(url, { headers = {}, timeoutMs = REQUEST_TIMEOUT_MS } = {})
155
190
  }
156
191
 
157
192
  const client = parsed.protocol === 'http:' ? http : https;
193
+ const requestBody = body === undefined
194
+ ? null
195
+ : (typeof body === 'string' || Buffer.isBuffer(body) ? body : JSON.stringify(body));
196
+ const requestHeaders = { ...headers };
197
+ if (requestBody != null && !Object.keys(requestHeaders).some(key => key.toLowerCase() === 'content-length')) {
198
+ requestHeaders['Content-Length'] = Buffer.byteLength(requestBody);
199
+ }
200
+
158
201
  const req = client.request({
159
202
  protocol: parsed.protocol,
160
203
  hostname: parsed.hostname,
161
204
  port: parsed.port,
162
205
  path: `${parsed.pathname}${parsed.search}`,
163
- method: 'GET',
164
- headers
206
+ method,
207
+ headers: requestHeaders
165
208
  }, (res) => {
166
209
  const chunks = [];
167
210
  let total = 0;
@@ -177,17 +220,42 @@ function requestJson(url, { headers = {}, timeoutMs = REQUEST_TIMEOUT_MS } = {})
177
220
 
178
221
  res.on('end', () => {
179
222
  const raw = Buffer.concat(chunks).toString('utf8');
223
+ let payload = null;
224
+ let parseError = null;
225
+ if (raw) {
226
+ try {
227
+ payload = JSON.parse(raw);
228
+ } catch (error) {
229
+ parseError = error;
230
+ }
231
+ }
232
+ if ([301, 302, 303, 307, 308].includes(res.statusCode) && res.headers.location && redirectCount < 3) {
233
+ try {
234
+ const nextUrl = new URL(res.headers.location, parsed).toString();
235
+ resolve(requestJson(nextUrl, {
236
+ method: res.statusCode === 303 ? 'GET' : method,
237
+ headers,
238
+ body: res.statusCode === 303 ? undefined : body,
239
+ timeoutMs,
240
+ redirectCount: redirectCount + 1
241
+ }));
242
+ } catch (error) {
243
+ reject(error);
244
+ }
245
+ return;
246
+ }
180
247
  if (res.statusCode < 200 || res.statusCode >= 300) {
181
248
  const error = new Error(`HTTP ${res.statusCode}`);
182
249
  error.statusCode = res.statusCode;
250
+ error.payload = payload;
183
251
  reject(error);
184
252
  return;
185
253
  }
186
- try {
187
- resolve(raw ? JSON.parse(raw) : null);
188
- } catch (error) {
189
- reject(error);
254
+ if (parseError) {
255
+ reject(parseError);
256
+ return;
190
257
  }
258
+ resolve(payload);
191
259
  });
192
260
  });
193
261
 
@@ -195,6 +263,9 @@ function requestJson(url, { headers = {}, timeoutMs = REQUEST_TIMEOUT_MS } = {})
195
263
  req.destroy(new Error('request timeout'));
196
264
  });
197
265
  req.on('error', reject);
266
+ if (requestBody != null) {
267
+ req.write(requestBody);
268
+ }
198
269
  req.end();
199
270
  });
200
271
  }
@@ -202,13 +273,22 @@ function requestJson(url, { headers = {}, timeoutMs = REQUEST_TIMEOUT_MS } = {})
202
273
  async function tryRequestJson(url, options) {
203
274
  try {
204
275
  return await requestJson(url, options);
205
- } catch {
276
+ } catch (error) {
277
+ if (error?.payload && typeof error.payload === 'object') {
278
+ return error.payload;
279
+ }
206
280
  return null;
207
281
  }
208
282
  }
209
283
 
210
- function detectPlatformByUrlHint(baseUrl) {
211
- const value = String(baseUrl || '').toLowerCase();
284
+ function detectPlatformByUrlHint(...values) {
285
+ const value = values.map(item => String(item || '')).join(' ').toLowerCase();
286
+ if (value.includes('88code') || value.includes('88-code') || value.includes('rainapp.top')) return '88code';
287
+ if (value.includes('openrouter.ai') || value.includes('openrouter')) return 'openrouter';
288
+ if (value.includes('aihubmix')) return 'aihubmix';
289
+ if (value.includes('dashscope') || value.includes('dashscope.aliyuncs.com') || value.includes('compatible-mode')) return 'dashscope';
290
+ if (value.includes('modelscope') || value.includes('api-inference.modelscope.cn')) return 'modelscope';
291
+ if (value.includes('siliconflow') || value.includes('siliconflow.cn')) return 'siliconflow';
212
292
  if (value.includes('sub2api')) return 'sub2api';
213
293
  if (value.includes('anyrouter')) return 'anyrouter';
214
294
  if (value.includes('donehub') || value.includes('done-hub')) return 'done-hub';
@@ -220,14 +300,17 @@ function detectPlatformByUrlHint(baseUrl) {
220
300
  }
221
301
 
222
302
  function resolveStatusPlatform(payload, hintedPlatform = null) {
223
- if (!payload || payload.success !== true) return null;
224
- const systemName = typeof payload?.data?.system_name === 'string'
225
- ? payload.data.system_name.toLowerCase()
303
+ if (!payload || typeof payload !== 'object') return null;
304
+ const data = payload.data && typeof payload.data === 'object' ? payload.data : payload;
305
+ const systemName = typeof data?.system_name === 'string'
306
+ ? data.system_name.toLowerCase()
226
307
  : '';
227
- const version = typeof payload?.data?.version === 'string'
228
- ? payload.data.version.toLowerCase()
308
+ const version = typeof data?.version === 'string'
309
+ ? data.version.toLowerCase()
229
310
  : '';
311
+ if (payload.success !== true && !systemName && !version) return null;
230
312
 
313
+ if (hintedPlatform === 'aihubmix' || systemName.includes('aihubmix')) return 'aihubmix';
231
314
  if (hintedPlatform === 'anyrouter') return 'anyrouter';
232
315
  if (hintedPlatform === 'done-hub') return 'done-hub';
233
316
  if (hintedPlatform === 'one-hub') return 'one-hub';
@@ -246,24 +329,15 @@ function extractSub2ApiData(payload) {
246
329
  return payload;
247
330
  }
248
331
 
249
- async function detectPlatform(baseUrl, token, hintedPlatform = null) {
250
- const headers = buildAuthHeaders(token);
251
-
252
- if (hintedPlatform === 'sub2api') {
253
- return 'sub2api';
254
- }
255
-
256
- const status = await tryRequestJson(joinUrl(baseUrl, '/api/status'), { headers: { Accept: 'application/json' } });
257
- const statusPlatform = resolveStatusPlatform(status, hintedPlatform);
258
- if (statusPlatform) return statusPlatform;
259
-
260
- const sub2Payload = await tryRequestJson(joinUrl(baseUrl, '/api/v1/auth/me'), { headers });
261
- const sub2Data = extractSub2ApiData(sub2Payload);
262
- if (sub2Data && Object.prototype.hasOwnProperty.call(sub2Data, 'balance')) {
263
- return 'sub2api';
264
- }
265
-
266
- return hintedPlatform;
332
+ function isSub2ApiUsagePayload(payload) {
333
+ if (!payload || typeof payload !== 'object') return false;
334
+ const mode = typeof payload.mode === 'string' ? payload.mode.trim().toLowerCase() : '';
335
+ if (!['quota_limited', 'unrestricted'].includes(mode)) return false;
336
+ return Object.prototype.hasOwnProperty.call(payload, 'remaining')
337
+ || Object.prototype.hasOwnProperty.call(payload, 'balance')
338
+ || (payload.quota && typeof payload.quota === 'object')
339
+ || (payload.subscription && typeof payload.subscription === 'object')
340
+ || (payload.usage && typeof payload.usage === 'object');
267
341
  }
268
342
 
269
343
  function extractUserSelfData(payload) {
@@ -278,7 +352,9 @@ function buildHubBalanceSnapshot(platform, payload) {
278
352
  const data = extractUserSelfData(payload);
279
353
  if (!data) return makeHiddenSnapshot(platform);
280
354
 
281
- const unit = platform === 'veloera' ? VELOERA_QUOTA_UNIT : QUOTA_UNIT;
355
+ const unit = platform === 'veloera'
356
+ ? VELOERA_QUOTA_UNIT
357
+ : normalizeQuotaUnit(data.quota_per_unit || data.quotaPerUnit);
282
358
  const quotaRaw = parseFiniteNumber(data.quota);
283
359
  const usedRaw = parseFiniteNumber(data.used_quota);
284
360
  if (quotaRaw == null) return makeHiddenSnapshot(platform);
@@ -288,7 +364,7 @@ function buildHubBalanceSnapshot(platform, payload) {
288
364
  let remaining;
289
365
  let total;
290
366
 
291
- if (platform === 'new-api' || platform === 'anyrouter' || platform === 'done-hub') {
367
+ if (platform === 'new-api' || platform === 'anyrouter' || platform === 'done-hub' || platform === 'aihubmix') {
292
368
  remaining = quota;
293
369
  total = quota + used;
294
370
  } else {
@@ -303,6 +379,102 @@ function buildHubBalanceSnapshot(platform, payload) {
303
379
  });
304
380
  }
305
381
 
382
+ function extractNewApiTokenUsageData(payload) {
383
+ if (!payload || typeof payload !== 'object') return null;
384
+ const data = payload.data && typeof payload.data === 'object' ? payload.data : payload;
385
+ if (!data || typeof data !== 'object') return null;
386
+ if (!Object.prototype.hasOwnProperty.call(data, 'total_available')) return null;
387
+ return data;
388
+ }
389
+
390
+ function buildNewApiTokenUsageSnapshot(payload, { quotaUnit = QUOTA_UNIT } = {}) {
391
+ const data = extractNewApiTokenUsageData(payload);
392
+ if (!data) return makeHiddenSnapshot('new-api');
393
+
394
+ const availableRaw = parseFiniteNumber(data.total_available);
395
+ if (availableRaw == null) return makeHiddenSnapshot('new-api');
396
+ const totalRaw = parseFiniteNumber(data.total_granted);
397
+ const usedRaw = parseFiniteNumber(data.total_used);
398
+ const unit = normalizeQuotaUnit(data.quota_per_unit || data.quotaPerUnit || quotaUnit);
399
+
400
+ const remaining = Math.max(0, availableRaw / unit);
401
+ return makeVisibleSnapshot('new-api', {
402
+ remaining,
403
+ used: usedRaw != null ? usedRaw / unit : undefined,
404
+ total: totalRaw != null ? totalRaw / unit : undefined
405
+ });
406
+ }
407
+
408
+ function extractOpenRouterData(payload) {
409
+ if (!payload || typeof payload !== 'object') return null;
410
+ const data = payload.data && typeof payload.data === 'object' ? payload.data : payload;
411
+ return data && typeof data === 'object' ? data : null;
412
+ }
413
+
414
+ function buildOpenRouterKeySnapshot(payload) {
415
+ const data = extractOpenRouterData(payload);
416
+ if (!data) return makeHiddenSnapshot('openrouter');
417
+
418
+ const limitRemaining = parseFiniteNumber(data.limit_remaining ?? data.limitRemaining);
419
+ const limit = parseFiniteNumber(data.limit);
420
+ const usage = parseFiniteNumber(data.usage);
421
+ if (limitRemaining != null) {
422
+ return makeVisibleSnapshot('openrouter', {
423
+ remaining: Math.max(0, limitRemaining),
424
+ used: usage,
425
+ total: limit
426
+ });
427
+ }
428
+
429
+ if (limit != null && usage != null) {
430
+ return makeVisibleSnapshot('openrouter', {
431
+ remaining: Math.max(0, limit - usage),
432
+ used: usage,
433
+ total: limit
434
+ });
435
+ }
436
+
437
+ return makeHiddenSnapshot('openrouter');
438
+ }
439
+
440
+ function buildOpenRouterCreditsSnapshot(payload) {
441
+ const data = extractOpenRouterData(payload);
442
+ if (!data) return makeHiddenSnapshot('openrouter');
443
+
444
+ const totalCredits = parseFiniteNumber(data.total_credits ?? data.totalCredits);
445
+ const totalUsage = parseFiniteNumber(data.total_usage ?? data.totalUsage);
446
+ if (totalCredits == null || totalUsage == null) {
447
+ return makeHiddenSnapshot('openrouter');
448
+ }
449
+
450
+ return makeVisibleSnapshot('openrouter', {
451
+ remaining: Math.max(0, totalCredits - totalUsage),
452
+ used: totalUsage,
453
+ total: totalCredits
454
+ });
455
+ }
456
+
457
+ function buildSiliconFlowUserInfoSnapshot(payload) {
458
+ const data = payload?.data && typeof payload.data === 'object' ? payload.data : payload;
459
+ if (!data || typeof data !== 'object') return makeHiddenSnapshot('siliconflow');
460
+
461
+ const remaining = parseFiniteNumber(data.balance);
462
+ if (remaining == null) return makeHiddenSnapshot('siliconflow');
463
+ const chargeBalance = parseFiniteNumber(data.chargeBalance);
464
+ const totalBalance = parseFiniteNumber(data.totalBalance);
465
+ return makeVisibleSnapshot('siliconflow', {
466
+ remaining,
467
+ used: totalBalance != null ? Math.max(0, totalBalance - remaining) : undefined,
468
+ total: totalBalance ?? chargeBalance ?? remaining
469
+ });
470
+ }
471
+
472
+ function extractQuotaUnitFromStatus(payload) {
473
+ if (!payload || typeof payload !== 'object') return null;
474
+ const data = payload.data && typeof payload.data === 'object' ? payload.data : payload;
475
+ return normalizeQuotaUnit(data?.quota_per_unit || data?.quotaPerUnit, null);
476
+ }
477
+
306
478
  function parseSubscriptionItems(payload) {
307
479
  const data = extractSub2ApiData(payload);
308
480
  const candidates = [
@@ -352,14 +524,458 @@ async function fetchSub2ApiSubscriptionSummary(baseUrl, headers) {
352
524
  return {};
353
525
  }
354
526
 
355
- async function fetchSub2ApiBalance(baseUrl, token) {
527
+ function readSub2ApiMonthlyRemaining(subscription) {
528
+ if (!subscription || typeof subscription !== 'object') return null;
529
+ const used = readMoneyField(subscription, 'monthlyUsedUsd', 'monthly_usage_usd', 'monthly_used_usd', 'monthly_used', 'used_monthly_usd') || 0;
530
+ const limit = readMoneyField(subscription, 'monthlyLimitUsd', 'monthly_limit_usd', 'monthly_limit', 'limit_monthly_usd');
531
+ if (limit == null) return null;
532
+ return Math.max(0, limit - used);
533
+ }
534
+
535
+ function nonNegativeMoney(value) {
536
+ const parsed = parseFiniteNumber(value);
537
+ return parsed != null && parsed >= 0 ? parsed : null;
538
+ }
539
+
540
+ function buildSub2ApiUsageSnapshot(payload) {
541
+ if (!isSub2ApiUsagePayload(payload)) return makeHiddenSnapshot('sub2api');
542
+ if (payload.isValid === false) return makeHiddenSnapshot('sub2api');
543
+
544
+ const quota = payload.quota && typeof payload.quota === 'object' ? payload.quota : null;
545
+ if (quota) {
546
+ const remaining = nonNegativeMoney(quota.remaining) ?? nonNegativeMoney(payload.remaining);
547
+ if (remaining != null) {
548
+ return makeVisibleSnapshot('sub2api', {
549
+ remaining,
550
+ used: readMoneyField(quota, 'used', 'quota_used', 'used_usd'),
551
+ total: readMoneyField(quota, 'limit', 'quota', 'total')
552
+ });
553
+ }
554
+ }
555
+
556
+ const remaining = nonNegativeMoney(payload.balance) ?? nonNegativeMoney(payload.remaining);
557
+ const monthlyRemaining = readSub2ApiMonthlyRemaining(payload.subscription);
558
+ return makeVisibleSnapshot('sub2api', {
559
+ remaining,
560
+ monthlyRemaining
561
+ });
562
+ }
563
+
564
+ function addUnique(list, value) {
565
+ if (value && !list.includes(value)) {
566
+ list.push(value);
567
+ }
568
+ }
569
+
570
+ function toOriginApiBase(value) {
571
+ try {
572
+ const parsed = new URL(value);
573
+ return `${parsed.origin}/api`;
574
+ } catch {
575
+ return '';
576
+ }
577
+ }
578
+
579
+ function toPathApiBase(value) {
580
+ try {
581
+ const normalized = normalizeBaseUrl(value);
582
+ if (!normalized) return '';
583
+ return normalized.toLowerCase().endsWith('/api') ? normalized : joinUrl(normalized, '/api');
584
+ } catch {
585
+ return '';
586
+ }
587
+ }
588
+
589
+ function toOpenRouterApiBase(value) {
590
+ try {
591
+ const parsed = new URL(String(value || '').trim());
592
+ parsed.hash = '';
593
+ parsed.search = '';
594
+ const origin = parsed.origin;
595
+ const host = parsed.hostname.toLowerCase();
596
+ const pathname = (parsed.pathname || '').replace(/\/+$/, '');
597
+ const lower = pathname.toLowerCase();
598
+
599
+ if (!pathname || pathname === '/') {
600
+ return `${origin}/api/v1`;
601
+ }
602
+ if (lower.endsWith('/api/v1')) {
603
+ return `${origin}${pathname}`;
604
+ }
605
+ if (lower.endsWith('/api')) {
606
+ return `${origin}${pathname}/v1`;
607
+ }
608
+ if (lower.endsWith('/v1')) {
609
+ const parent = pathname.slice(0, -3).replace(/\/+$/, '');
610
+ if (!parent || parent === '') return `${origin}/api/v1`;
611
+ if (parent.toLowerCase().endsWith('/api')) return `${origin}${parent}/v1`;
612
+ return `${origin}${parent}/api/v1`;
613
+ }
614
+ if (host === 'openrouter.ai' || host.endsWith('.openrouter.ai')) {
615
+ return `${origin}/api/v1`;
616
+ }
617
+ return `${origin}${pathname}/api/v1`;
618
+ } catch {
619
+ return '';
620
+ }
621
+ }
622
+
623
+ function buildOpenRouterApiBaseCandidates(baseUrl, channel = {}) {
624
+ const candidates = [];
625
+ const inputs = [
626
+ channel.balanceBaseUrl,
627
+ channel.websiteUrl,
628
+ channel.baseUrl,
629
+ baseUrl
630
+ ].filter(Boolean);
631
+
632
+ for (const input of inputs) {
633
+ addUnique(candidates, toOpenRouterApiBase(input));
634
+ }
635
+ return candidates;
636
+ }
637
+
638
+ function buildSiliconFlowApiBaseCandidates(baseUrl, channel = {}) {
639
+ const candidates = [];
640
+ const inputs = [
641
+ channel.balanceBaseUrl,
642
+ channel.websiteUrl,
643
+ channel.baseUrl,
644
+ baseUrl
645
+ ].filter(Boolean);
646
+
647
+ for (const input of inputs) {
648
+ try {
649
+ const parsed = new URL(input);
650
+ const origin = parsed.origin;
651
+ const pathname = (parsed.pathname || '').replace(/\/+$/, '');
652
+ const lower = pathname.toLowerCase();
653
+ if (lower.endsWith('/v1')) {
654
+ addUnique(candidates, `${origin}${pathname}`);
655
+ } else {
656
+ addUnique(candidates, `${origin}/v1`);
657
+ }
658
+ } catch {}
659
+ }
660
+ return candidates;
661
+ }
662
+
663
+ function hasGatewayPathSegment(value) {
664
+ try {
665
+ const parsed = new URL(normalizeBaseUrl(value));
666
+ return parsed.pathname
667
+ .split('/')
668
+ .filter(Boolean)
669
+ .some(segment => GATEWAY_PATH_SEGMENTS.has(segment.toLowerCase()));
670
+ } catch {
671
+ return false;
672
+ }
673
+ }
674
+
675
+ function build88CodeApiBaseCandidates(baseUrl, channel = {}) {
676
+ const candidates = [];
677
+ const inputs = [
678
+ channel.balanceBaseUrl,
679
+ channel.websiteUrl,
680
+ channel.baseUrl,
681
+ baseUrl
682
+ ].filter(Boolean);
683
+
684
+ for (const input of inputs) {
685
+ const originApiBase = toOriginApiBase(input);
686
+ const pathApiBase = toPathApiBase(input);
687
+ if (hasGatewayPathSegment(input)) {
688
+ addUnique(candidates, originApiBase);
689
+ addUnique(candidates, pathApiBase);
690
+ } else {
691
+ addUnique(candidates, pathApiBase);
692
+ addUnique(candidates, originApiBase);
693
+ }
694
+ }
695
+ return candidates;
696
+ }
697
+
698
+ function get88CodeApiBaseFromStrategy(strategy = {}) {
699
+ return strategy.apiBase || strategy.baseUrl;
700
+ }
701
+
702
+ function get88CodeRequestBody(channel = {}) {
703
+ const model = String(
704
+ channel.model
705
+ || channel.speedTestModel
706
+ || channel.defaultModel
707
+ || ''
708
+ ).trim();
709
+ return model ? { model } : {};
710
+ }
711
+
712
+ function extract88CodeData(payload) {
713
+ if (!payload || typeof payload !== 'object') return null;
714
+ if (!Object.prototype.hasOwnProperty.call(payload, 'data')) return null;
715
+ return payload.data;
716
+ }
717
+
718
+ function normalize88CodeSubscription(subscription = {}) {
719
+ const plan = subscription.subscriptionPlan && typeof subscription.subscriptionPlan === 'object'
720
+ ? subscription.subscriptionPlan
721
+ : {};
722
+ const creditLimit = parseFiniteNumber(subscription.creditLimit)
723
+ ?? parseFiniteNumber(plan.creditLimit)
724
+ ?? 0;
725
+ return {
726
+ id: parseFiniteNumber(subscription.id) ?? 0,
727
+ planName: String(subscription.subscriptionPlanName || subscription.subscriptionName || plan.subscriptionName || ''),
728
+ status: String(subscription.subscriptionStatus || subscription.status || ''),
729
+ isActive: subscription.isActive === true,
730
+ remainingDays: parseFiniteNumber(subscription.remainingDays) ?? 0,
731
+ creditLimit,
732
+ currentCredits: parseFiniteNumber(subscription.currentCredits) ?? 0
733
+ };
734
+ }
735
+
736
+ function get88CodeBillingPriority(planName) {
737
+ const normalized = String(planName || '').trim().toUpperCase();
738
+ if (normalized === 'FREE') return 3;
739
+ if (normalized === 'PAYGO') return 2;
740
+ return 1;
741
+ }
742
+
743
+ function select88CodeSubscription(subscriptions = []) {
744
+ const active = subscriptions
745
+ .map(normalize88CodeSubscription)
746
+ .filter(subscription => subscription.isActive)
747
+ .filter(subscription => !subscription.status || subscription.status === '活跃中' || subscription.remainingDays > 0)
748
+ .sort((left, right) => {
749
+ const priorityDiff = get88CodeBillingPriority(left.planName) - get88CodeBillingPriority(right.planName);
750
+ if (priorityDiff !== 0) return priorityDiff;
751
+ return left.id - right.id;
752
+ });
753
+
754
+ return active.find(subscription => (
755
+ subscription.planName.toUpperCase() !== 'FREE'
756
+ && subscription.creditLimit > 0
757
+ && subscription.currentCredits < subscription.creditLimit
758
+ ))
759
+ || active.find(subscription => (
760
+ subscription.planName.toUpperCase() !== 'FREE'
761
+ && subscription.currentCredits > 0
762
+ ))
763
+ || null;
764
+ }
765
+
766
+ function hasOnly88CodeFreeSubscription(entities = []) {
767
+ const active = Array.isArray(entities)
768
+ ? entities.filter(item => item?.isActive === true)
769
+ : [];
770
+ if (!active.length) return false;
771
+ return !active.some((item) => {
772
+ const name = String(item.subscriptionName || item.subscriptionPlanName || '').toUpperCase();
773
+ return name && name !== 'FREE' && name !== 'PAYGO';
774
+ });
775
+ }
776
+
777
+ function build88CodeBalanceSnapshotFromValues(values = {}) {
778
+ const remainingRaw = parseFiniteNumber(values.currentCredits);
779
+ const totalRaw = parseFiniteNumber(values.creditLimit);
780
+ if (remainingRaw == null && totalRaw == null) {
781
+ return makeHiddenSnapshot('88code');
782
+ }
783
+
784
+ const remaining = Math.max(0, remainingRaw ?? 0);
785
+ const total = totalRaw != null && totalRaw > 0 ? totalRaw : undefined;
786
+ const used = total != null ? Math.max(0, total - remaining) : undefined;
787
+ return makeVisibleSnapshot('88code', {
788
+ remaining,
789
+ used,
790
+ total
791
+ });
792
+ }
793
+
794
+ function build88CodeUsageSnapshot(data) {
795
+ if (!data || typeof data !== 'object') return makeHiddenSnapshot('88code');
796
+ const entities = Array.isArray(data.subscriptionEntityList) ? data.subscriptionEntityList : [];
797
+ const activeEntity = entities
798
+ .filter(item => item?.isActive === true)
799
+ .filter(item => String(item.subscriptionName || '').toUpperCase() !== 'FREE')
800
+ .find((item) => {
801
+ const total = parseFiniteNumber(item.creditLimit);
802
+ const remaining = parseFiniteNumber(item.currentCredits);
803
+ return total != null && remaining != null && remaining < total;
804
+ });
805
+
806
+ const selected = activeEntity || data;
807
+ return build88CodeBalanceSnapshotFromValues({
808
+ creditLimit: selected.creditLimit,
809
+ currentCredits: selected.currentCredits
810
+ });
811
+ }
812
+
813
+ function is88CodeUsageDataValid(data) {
814
+ if (!data || typeof data !== 'object') return false;
815
+ const creditLimit = parseFiniteNumber(data.creditLimit) ?? 0;
816
+ const entities = Array.isArray(data.subscriptionEntityList) ? data.subscriptionEntityList : [];
817
+ return creditLimit > 0 || entities.length > 0;
818
+ }
819
+
820
+ function build88CodeSubscriptionSnapshot(payload) {
821
+ const data = extract88CodeData(payload);
822
+ const subscriptions = Array.isArray(data) ? data : [];
823
+ const selected = select88CodeSubscription(subscriptions);
824
+ if (!selected) return makeHiddenSnapshot('88code');
825
+ return build88CodeBalanceSnapshotFromValues({
826
+ creditLimit: selected.creditLimit,
827
+ currentCredits: selected.currentCredits
828
+ });
829
+ }
830
+
831
+ async function post88CodeJson(url, token, channel) {
832
+ return requestJson(url, {
833
+ method: 'POST',
834
+ headers: buildAuthHeaders(token, { 'Content-Type': 'application/json' }),
835
+ body: get88CodeRequestBody(channel)
836
+ });
837
+ }
838
+
839
+ async function run88CodeUsageStrategy(strategy, token, channel = {}) {
840
+ const apiBase = get88CodeApiBaseFromStrategy(strategy);
841
+ const payload = await post88CodeJson(joinUrl(apiBase, '/usage'), token, channel);
842
+ const usageData = extract88CodeData(payload);
843
+ if (!is88CodeUsageDataValid(usageData)) return makeHiddenSnapshot('88code');
844
+ const snapshot = build88CodeUsageSnapshot(usageData);
845
+ if (
846
+ snapshot.visible
847
+ && !hasOnly88CodeFreeSubscription(usageData.subscriptionEntityList)
848
+ && Number(snapshot.remaining || 0) > 0
849
+ ) {
850
+ return snapshot;
851
+ }
852
+ return makeHiddenSnapshot('88code');
853
+ }
854
+
855
+ async function run88CodeSubscriptionStrategy(strategy, token, channel = {}) {
856
+ const apiBase = get88CodeApiBaseFromStrategy(strategy);
857
+ const payload = await post88CodeJson(joinUrl(apiBase, '/subscription'), token, channel);
858
+ return build88CodeSubscriptionSnapshot(payload);
859
+ }
860
+
861
+ function buildBalanceProbeStrategies(baseUrl, channel = {}, token = '') {
862
+ const hintedPlatform = detectPlatformByUrlHint(
863
+ channel.providerKey,
864
+ channel.name,
865
+ channel.websiteUrl,
866
+ channel.baseUrl || baseUrl
867
+ );
868
+ const strategies = [];
869
+ const seen = new Set();
870
+ const add = (strategy) => {
871
+ const key = JSON.stringify(strategy);
872
+ if (!seen.has(key)) {
873
+ seen.add(key);
874
+ strategies.push(strategy);
875
+ }
876
+ };
877
+ const addSub2ApiStrategies = () => {
878
+ if (isLikelyModelApiKey(token)) {
879
+ add({ type: 'sub2api-usage', baseUrl });
880
+ } else {
881
+ add({ type: 'sub2api-auth', baseUrl });
882
+ add({ type: 'sub2api-usage', baseUrl });
883
+ }
884
+ };
885
+ const addHubStrategy = () => {
886
+ add({
887
+ type: 'hub-user-self',
888
+ baseUrl,
889
+ platform: HUB_PLATFORMS.has(hintedPlatform) ? hintedPlatform : null,
890
+ hintedPlatform
891
+ });
892
+ };
893
+ const addNewApiTokenUsageStrategy = () => {
894
+ add({ type: 'new-api-token-usage', baseUrl });
895
+ };
896
+ const addNewApiTokenUsageWithStatusStrategy = () => {
897
+ add({ type: 'new-api-token-usage-status', baseUrl });
898
+ };
899
+ const add88CodeStrategies = () => {
900
+ for (const apiBase of build88CodeApiBaseCandidates(baseUrl, channel)) {
901
+ add({ type: '88code-usage', baseUrl, apiBase });
902
+ add({ type: '88code-subscription', baseUrl, apiBase });
903
+ }
904
+ };
905
+ const addOpenRouterStrategies = () => {
906
+ for (const apiBase of buildOpenRouterApiBaseCandidates(baseUrl, channel)) {
907
+ add({ type: 'openrouter-key', baseUrl, apiBase, endpoint: '/key' });
908
+ add({ type: 'openrouter-key', baseUrl, apiBase, endpoint: '/auth/key' });
909
+ add({ type: 'openrouter-credits', baseUrl, apiBase });
910
+ }
911
+ };
912
+ const addSiliconFlowStrategies = () => {
913
+ for (const apiBase of buildSiliconFlowApiBaseCandidates(baseUrl, channel)) {
914
+ add({ type: 'siliconflow-user-info', baseUrl, apiBase });
915
+ }
916
+ };
917
+
918
+ if (UNSUPPORTED_BALANCE_PLATFORMS.has(hintedPlatform)) {
919
+ add({ type: 'unsupported-balance', platform: hintedPlatform, baseUrl });
920
+ return strategies;
921
+ }
922
+
923
+ if (hintedPlatform === 'siliconflow') {
924
+ addSiliconFlowStrategies();
925
+ return strategies;
926
+ }
927
+
928
+ if (hintedPlatform === '88code') {
929
+ add88CodeStrategies();
930
+ addSub2ApiStrategies();
931
+ addHubStrategy();
932
+ return strategies;
933
+ }
934
+
935
+ if (hintedPlatform === 'openrouter' || isLikelyOpenRouterApiKey(token)) {
936
+ addOpenRouterStrategies();
937
+ addSub2ApiStrategies();
938
+ addNewApiTokenUsageWithStatusStrategy();
939
+ addHubStrategy();
940
+ return strategies;
941
+ }
942
+
943
+ if (hintedPlatform === 'sub2api') {
944
+ addSub2ApiStrategies();
945
+ addNewApiTokenUsageWithStatusStrategy();
946
+ addHubStrategy();
947
+ return strategies;
948
+ }
949
+
950
+ if (HUB_PLATFORMS.has(hintedPlatform)) {
951
+ if (hintedPlatform === 'new-api') {
952
+ addNewApiTokenUsageWithStatusStrategy();
953
+ }
954
+ addHubStrategy();
955
+ addSub2ApiStrategies();
956
+ return strategies;
957
+ }
958
+
959
+ if (isLikelyModelApiKey(token)) {
960
+ addSub2ApiStrategies();
961
+ addNewApiTokenUsageWithStatusStrategy();
962
+ addHubStrategy();
963
+ } else {
964
+ addHubStrategy();
965
+ addNewApiTokenUsageWithStatusStrategy();
966
+ addSub2ApiStrategies();
967
+ }
968
+ return strategies;
969
+ }
970
+
971
+ async function runSub2ApiAuthStrategy(strategy, token) {
356
972
  const headers = buildAuthHeaders(token);
357
- const payload = await requestJson(joinUrl(baseUrl, '/api/v1/auth/me'), { headers });
973
+ const payload = await tryRequestJson(joinUrl(strategy.baseUrl, '/api/v1/auth/me'), { headers });
358
974
  const data = extractSub2ApiData(payload);
359
975
  const balance = parseFiniteNumber(data?.balance);
360
976
  if (balance == null) return makeHiddenSnapshot('sub2api');
361
977
 
362
- const summary = await fetchSub2ApiSubscriptionSummary(baseUrl, headers);
978
+ const summary = await fetchSub2ApiSubscriptionSummary(strategy.baseUrl, headers);
363
979
  return makeVisibleSnapshot('sub2api', {
364
980
  remaining: balance,
365
981
  used: 0,
@@ -368,33 +984,183 @@ async function fetchSub2ApiBalance(baseUrl, token) {
368
984
  });
369
985
  }
370
986
 
371
- async function fetchHubBalance(baseUrl, token, platform) {
987
+ async function runSub2ApiUsageStrategy(strategy, token) {
988
+ const payload = await tryRequestJson(joinUrl(strategy.baseUrl, '/v1/usage'), {
989
+ headers: buildAuthHeaders(token)
990
+ });
991
+ return buildSub2ApiUsageSnapshot(payload);
992
+ }
993
+
994
+ async function runHubUserSelfStrategy(strategy, token) {
372
995
  const headers = buildAuthHeaders(token);
373
- const payload = await requestJson(joinUrl(baseUrl, '/api/user/self'), { headers });
374
- return buildHubBalanceSnapshot(platform, payload);
996
+ let platform = strategy.platform;
997
+ let quotaUnit = null;
998
+ if (!HUB_PLATFORMS.has(platform)) {
999
+ const status = await tryRequestJson(joinUrl(strategy.baseUrl, '/api/status'), { headers: { Accept: 'application/json' } });
1000
+ platform = resolveStatusPlatform(status, strategy.hintedPlatform);
1001
+ quotaUnit = extractQuotaUnitFromStatus(status);
1002
+ }
1003
+ if (!HUB_PLATFORMS.has(platform)) return { snapshot: makeHiddenSnapshot(platform), strategy };
1004
+
1005
+ const payload = await requestJson(joinUrl(strategy.baseUrl, '/api/user/self'), { headers });
1006
+ const snapshot = buildHubBalanceSnapshot(platform, {
1007
+ ...payload,
1008
+ data: payload?.data && typeof payload.data === 'object'
1009
+ ? { ...payload.data, quota_per_unit: quotaUnit || payload.data.quota_per_unit }
1010
+ : payload?.data
1011
+ });
1012
+ return {
1013
+ snapshot,
1014
+ strategy: {
1015
+ ...strategy,
1016
+ platform,
1017
+ quotaUnit: quotaUnit || strategy.quotaUnit
1018
+ }
1019
+ };
1020
+ }
1021
+
1022
+ async function runNewApiTokenUsageStrategy(strategy, token) {
1023
+ const payload = await requestJson(joinUrl(strategy.baseUrl, '/api/usage/token'), {
1024
+ headers: buildAuthHeaders(token)
1025
+ });
1026
+ return buildNewApiTokenUsageSnapshot(payload, { quotaUnit: strategy.quotaUnit || QUOTA_UNIT });
375
1027
  }
376
1028
 
377
- async function fetchBalanceFromBase(baseUrl, channel) {
378
- const token = stripBearer(channel.apiKey || channel.token || channel.authToken || '');
379
- if (!baseUrl || !token) return makeHiddenSnapshot();
1029
+ async function runNewApiTokenUsageWithStatusStrategy(strategy, token) {
1030
+ const status = await tryRequestJson(joinUrl(strategy.baseUrl, '/api/status'), {
1031
+ headers: { Accept: 'application/json' }
1032
+ });
1033
+ const quotaUnit = extractQuotaUnitFromStatus(status) || QUOTA_UNIT;
1034
+ const snapshot = await runNewApiTokenUsageStrategy({ ...strategy, quotaUnit }, token);
1035
+ return {
1036
+ snapshot,
1037
+ strategy: {
1038
+ ...strategy,
1039
+ quotaUnit
1040
+ }
1041
+ };
1042
+ }
380
1043
 
381
- const hintedPlatform = detectPlatformByUrlHint(channel.baseUrl || baseUrl);
382
- const platform = await detectPlatform(baseUrl, token, hintedPlatform);
383
- if (!platform) return makeHiddenSnapshot();
1044
+ async function runOpenRouterKeyStrategy(strategy, token) {
1045
+ const payload = await requestJson(joinUrl(strategy.apiBase || strategy.baseUrl, strategy.endpoint || '/key'), {
1046
+ headers: buildAuthHeaders(token)
1047
+ });
1048
+ return buildOpenRouterKeySnapshot(payload);
1049
+ }
384
1050
 
385
- if (platform === 'sub2api') {
386
- return fetchSub2ApiBalance(baseUrl, token);
1051
+ async function runOpenRouterCreditsStrategy(strategy, token) {
1052
+ const payload = await requestJson(joinUrl(strategy.apiBase || strategy.baseUrl, '/credits'), {
1053
+ headers: buildAuthHeaders(token)
1054
+ });
1055
+ return buildOpenRouterCreditsSnapshot(payload);
1056
+ }
1057
+
1058
+ async function runSiliconFlowUserInfoStrategy(strategy, token) {
1059
+ const payload = await requestJson(joinUrl(strategy.apiBase || strategy.baseUrl, '/user/info'), {
1060
+ headers: buildAuthHeaders(token)
1061
+ });
1062
+ return buildSiliconFlowUserInfoSnapshot(payload);
1063
+ }
1064
+
1065
+ async function runBalanceStrategy(strategy, token, channel = {}) {
1066
+ if (!strategy || !strategy.type) {
1067
+ return { snapshot: makeHiddenSnapshot(), strategy };
387
1068
  }
388
1069
 
389
- if (['new-api', 'one-api', 'one-hub', 'done-hub', 'veloera', 'anyrouter'].includes(platform)) {
390
- return fetchHubBalance(baseUrl, token, platform);
1070
+ if (strategy.type === '88code-usage') {
1071
+ return {
1072
+ snapshot: await run88CodeUsageStrategy(strategy, token, channel),
1073
+ strategy
1074
+ };
1075
+ }
1076
+ if (strategy.type === '88code-subscription') {
1077
+ return {
1078
+ snapshot: await run88CodeSubscriptionStrategy(strategy, token, channel),
1079
+ strategy
1080
+ };
1081
+ }
1082
+ if (strategy.type === 'sub2api-auth') {
1083
+ return {
1084
+ snapshot: await runSub2ApiAuthStrategy(strategy, token),
1085
+ strategy
1086
+ };
1087
+ }
1088
+ if (strategy.type === 'sub2api-usage') {
1089
+ return {
1090
+ snapshot: await runSub2ApiUsageStrategy(strategy, token),
1091
+ strategy
1092
+ };
1093
+ }
1094
+ if (strategy.type === 'hub-user-self') {
1095
+ return runHubUserSelfStrategy(strategy, token);
1096
+ }
1097
+ if (strategy.type === 'new-api-token-usage') {
1098
+ return {
1099
+ snapshot: await runNewApiTokenUsageStrategy(strategy, token),
1100
+ strategy
1101
+ };
1102
+ }
1103
+ if (strategy.type === 'new-api-token-usage-status') {
1104
+ return runNewApiTokenUsageWithStatusStrategy(strategy, token);
1105
+ }
1106
+ if (strategy.type === 'openrouter-key') {
1107
+ return {
1108
+ snapshot: await runOpenRouterKeyStrategy(strategy, token),
1109
+ strategy
1110
+ };
1111
+ }
1112
+ if (strategy.type === 'openrouter-credits') {
1113
+ return {
1114
+ snapshot: await runOpenRouterCreditsStrategy(strategy, token),
1115
+ strategy
1116
+ };
1117
+ }
1118
+ if (strategy.type === 'unsupported-balance') {
1119
+ return {
1120
+ snapshot: makeHiddenSnapshot(strategy.platform),
1121
+ strategy
1122
+ };
1123
+ }
1124
+ if (strategy.type === 'siliconflow-user-info') {
1125
+ return {
1126
+ snapshot: await runSiliconFlowUserInfoStrategy(strategy, token),
1127
+ strategy
1128
+ };
391
1129
  }
392
1130
 
393
- return makeHiddenSnapshot(platform);
1131
+ return { snapshot: makeHiddenSnapshot(), strategy };
1132
+ }
1133
+
1134
+ async function tryBalanceStrategy(strategy, token, channel = {}) {
1135
+ try {
1136
+ const result = await runBalanceStrategy(strategy, token, channel);
1137
+ if (result?.snapshot?.visible) return result;
1138
+ } catch {
1139
+ // Probe errors are intentionally silent; unsupported balances stay hidden.
1140
+ }
1141
+ return null;
1142
+ }
1143
+
1144
+ async function probeBalanceFromBase(baseUrl, channel, token) {
1145
+ let lastSnapshot = null;
1146
+ for (const strategy of buildBalanceProbeStrategies(baseUrl, channel, token)) {
1147
+ const result = await tryBalanceStrategy(strategy, token, channel);
1148
+ if (result) return result;
1149
+ if (!lastSnapshot) {
1150
+ lastSnapshot = makeHiddenSnapshot(
1151
+ strategy.type?.startsWith('88code') ? '88code'
1152
+ : strategy.type?.startsWith('openrouter') ? 'openrouter'
1153
+ : strategy.type?.startsWith('siliconflow') ? 'siliconflow'
1154
+ : strategy.type?.startsWith('sub2api') ? 'sub2api'
1155
+ : strategy.platform || null
1156
+ );
1157
+ }
1158
+ }
1159
+ return lastSnapshot ? { snapshot: lastSnapshot, strategy: null } : null;
394
1160
  }
395
1161
 
396
1162
  function buildCacheKey(source, channel) {
397
- const token = stripBearer(channel.apiKey || channel.token || channel.authToken || '');
1163
+ const token = resolveBalanceToken(channel);
398
1164
  const tokenHash = token
399
1165
  ? crypto.createHash('sha256').update(token).digest('hex').slice(0, 16)
400
1166
  : 'no-token';
@@ -417,25 +1183,37 @@ async function refreshChannelBalanceSnapshot(source, channel, options = {}) {
417
1183
  }
418
1184
 
419
1185
  const bases = buildBaseCandidates(channel.baseUrl);
420
- if (!bases.length || !stripBearer(channel.apiKey || channel.token || channel.authToken || '')) {
1186
+ if (!bases.length || !resolveBalanceToken(channel)) {
421
1187
  return makeHiddenSnapshot(null, { updatedAt: nowIso(currentTime) });
422
1188
  }
423
1189
 
424
1190
  let lastSnapshot = null;
1191
+ const token = resolveBalanceToken(channel);
1192
+ const cachedStrategy = balanceStrategyCache.get(key);
1193
+ if (cachedStrategy) {
1194
+ const strategyResult = await tryBalanceStrategy(cachedStrategy, token, channel);
1195
+ if (strategyResult?.snapshot?.visible) {
1196
+ balanceStrategyCache.set(key, strategyResult.strategy);
1197
+ balanceCache.set(key, {
1198
+ snapshot: strategyResult.snapshot,
1199
+ expiresAt: currentTime + CACHE_TTL_MS
1200
+ });
1201
+ return strategyResult.snapshot;
1202
+ }
1203
+ balanceStrategyCache.delete(key);
1204
+ }
1205
+
425
1206
  for (const baseUrl of bases) {
426
- try {
427
- const snapshot = await fetchBalanceFromBase(baseUrl, channel);
428
- if (snapshot?.visible) {
429
- balanceCache.set(key, {
430
- snapshot,
431
- expiresAt: currentTime + CACHE_TTL_MS
432
- });
433
- return snapshot;
434
- }
435
- lastSnapshot = snapshot || lastSnapshot;
436
- } catch {
437
- // Try the next normalized base candidate.
1207
+ const result = await probeBalanceFromBase(baseUrl, channel, token);
1208
+ if (result?.snapshot?.visible) {
1209
+ balanceStrategyCache.set(key, result.strategy);
1210
+ balanceCache.set(key, {
1211
+ snapshot: result.snapshot,
1212
+ expiresAt: currentTime + CACHE_TTL_MS
1213
+ });
1214
+ return result.snapshot;
438
1215
  }
1216
+ lastSnapshot = result?.snapshot || makeHiddenSnapshot(null, { updatedAt: nowIso(currentTime) });
439
1217
  }
440
1218
 
441
1219
  if (cached?.snapshot?.visible) {
@@ -543,6 +1321,7 @@ async function refreshChannelBalance(source, channelId) {
543
1321
 
544
1322
  function clearBalanceCache() {
545
1323
  balanceCache.clear();
1324
+ balanceStrategyCache.clear();
546
1325
  }
547
1326
 
548
1327
  module.exports = {
@@ -553,8 +1332,22 @@ module.exports = {
553
1332
  normalizeBaseUrl,
554
1333
  buildBaseCandidates,
555
1334
  buildHubBalanceSnapshot,
1335
+ buildNewApiTokenUsageSnapshot,
1336
+ buildOpenRouterKeySnapshot,
1337
+ buildOpenRouterCreditsSnapshot,
1338
+ buildSiliconFlowUserInfoSnapshot,
556
1339
  detectPlatformByUrlHint,
1340
+ isSub2ApiUsagePayload,
1341
+ buildSub2ApiUsageSnapshot,
557
1342
  resolveStatusPlatform,
1343
+ resolveBalanceToken,
1344
+ build88CodeApiBaseCandidates,
1345
+ buildOpenRouterApiBaseCandidates,
1346
+ buildSiliconFlowApiBaseCandidates,
1347
+ build88CodeUsageSnapshot,
1348
+ build88CodeSubscriptionSnapshot,
1349
+ buildBalanceProbeStrategies,
1350
+ runBalanceStrategy,
558
1351
  refreshChannelBalanceSnapshot,
559
1352
  makeVisibleSnapshot,
560
1353
  requestJson