dsh-connect-minimaxcode 0.6.4 → 0.6.5

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/README.en.md CHANGED
@@ -384,6 +384,12 @@ The OAuth token is valid for about an hour and is refreshed automatically by the
384
384
 
385
385
  ## Changelog
386
386
 
387
+ ### 0.6.5 (2026-11-12)
388
+
389
+ - **Fixed "the limit windows never showed up"**: once a plan is held, the upstream changed the quota endpoint's **field names and its meaning** — from the share *used* (the string `"19%"`) to the share *remaining* (the number `90`). The plugin still read the old names, so the windows either could not be read at all or read as the opposite. Both shapes are accepted now, and what leaves the plugin is always the share *used*: 92% remaining is 8% used. The window length is still derived from the answer's own edges (measured: five hours and seven days) rather than assumed.
390
+ - **Added the plan to the card**: the membership answer now names the tier and the day it ends (`token_plan_tier`, `token_plan_expires_at`), and the plugin shows one more line in the pill detail and on the plugin page — "Go · until 2026-12-11". It comes from the same answer as the balance, so it costs no extra request.
391
+ - **An account without a plan sees exactly what it saw before**: the plan fields are simply absent there, so the plugin shows nothing about a plan; the windows are still absent, and the card still says "No M Plan subscription (credits only), so there are no usage windows". The new `tests/free-account.spec.ts` pins that whole path for a free account (balance, plan, windows), and `tests/plan.spec.ts` covers the new field shape, the old one and the free account.
392
+
387
393
  ### 0.6.4 (2026-11-11)
388
394
 
389
395
  - **Fixed "the self-check report never appeared"**: the previous version put the self-check in the client half (the card reports what the host offered), but **the server half was never landed** — the status document had no field to carry the report and no route to receive it, so the card reported into nowhere. It is complete now: the report is received by the plugin's own route and appears in the status document as `capability`.
package/README.md CHANGED
@@ -395,6 +395,12 @@ OAuth 令牌有效期约 1 小时,由客户端自动续期。插件**只在自
395
395
 
396
396
  ## 更新记录
397
397
 
398
+ ### 0.6.5(2026-11-12)
399
+
400
+ - **修复「限额窗口一直显示不出来」**:上游在有了套餐之后改了额度接口的**字段名和语义** —— 从「已用百分比」(字符串 `"19%"`)改成「**剩余**百分比」(数字 `90`)。插件仍按旧字段名读,于是窗口读不到,或者读成相反的意思。现在两种形态都认,且对外一律是「已用百分比」:读到剩余 92% 就是已用 8%。窗口长度仍从响应自身的起止时刻推导(实测 5 小时与 7 天),不写死。
401
+ - **新增套餐显示**:会员接口现在会报出档位与到期日(`token_plan_tier`、`token_plan_expires_at`),插件在胶囊详情与插件页多显示一行,例如「Go · 2026-12-11 到期」。这与余额读的是同一个响应,不额外发请求。
402
+ - **免费用户看到的完全不变**:没有套餐的账户,套餐字段根本不存在,插件也就不显示任何套餐信息;窗口依然读不到,卡片上那句「未订阅 M Plan(仅积分包),因此没有限额窗口」照旧。新增 `tests/free-account.spec.ts` 把免费账户的整条路径钉住(余额、套餐、窗口三样),另加 `tests/plan.spec.ts` 覆盖新字段形态、旧字段形态与免费账户三种情况。
403
+
398
404
  ### 0.6.4(2026-11-11)
399
405
 
400
406
  - **修复「自检报告从未出现」**:上一版把自检能力加在了客户端(卡片上报宿主提供了什么),但**服务端那一半没有落地** —— 状态文档里没有承载报告的字段,也没有接收报告的路由,于是卡片上报到哪里都无人接收。现已补齐:报告由插件自己的路由接收,并出现在状态文档的 `capability` 里。
package/lib/client.js CHANGED
@@ -77,6 +77,8 @@
77
77
  usageRanking: '用量排名前 {percent}%',
78
78
  usageBestDay: '最活跃的一天 {date}',
79
79
  planNoSubscription: '未订阅 M Plan(仅积分包),因此没有限额窗口',
80
+ planGeneric: 'M Plan 订阅',
81
+ planExpires: '{date} 到期',
80
82
  generateTitle: '生成',
81
83
  generateImage: '图片',
82
84
  generateVideo: '视频',
@@ -155,6 +157,8 @@
155
157
  usageRanking: 'Top {percent}% by usage',
156
158
  usageBestDay: 'Busiest day {date}',
157
159
  planNoSubscription: 'No M Plan subscription (credits only), so there are no usage windows',
160
+ planGeneric: 'M Plan subscription',
161
+ planExpires: 'until {date}',
158
162
  generateTitle: 'Generate',
159
163
  generateImage: 'Image',
160
164
  generateVideo: 'Video',
@@ -383,6 +387,23 @@
383
387
  return null;
384
388
  return h('div', { style: mutedStyle, 'data-minimax-plan-notice': true }, t('planNoSubscription'));
385
389
  }
390
+ /**
391
+ * The plan the account holds, when it holds one.
392
+ *
393
+ * An account without a plan has nothing to say here, and the card shows
394
+ * it nothing: the rolling windows it does not have are already
395
+ * explained by the notice beside them. What this adds is for the
396
+ * accounts that do hold one — the tier's name and the day it ends.
397
+ */
398
+ function planLine(t, plan) {
399
+ if (plan === undefined || !plan.active)
400
+ return null;
401
+ const tier = plan.tier ?? t('planGeneric');
402
+ const expiry = plan.expiresAtMs === undefined
403
+ ? null
404
+ : t('planExpires', { date: day(plan.expiresAtMs) });
405
+ return h('div', { style: rowStyle, 'data-minimax-plan': true }, h('span', null, tier), h('span', null, expiry ?? ''));
406
+ }
386
407
  /** The pill itself; the gate decides whether it is drawn at all. */
387
408
  function CreditPill(props) {
388
409
  const [status] = useStatus();
@@ -415,7 +436,7 @@
415
436
  const detail = available
416
437
  ? h('div', { style: detailStyle, 'data-minimax-credits-detail': true }, row(t('free'), credits(credits_.free, 3)), row(t('purchased'), credits(credits_.purchased, 3)), credits_.consumed !== undefined && credits_.consumed > 0
417
438
  ? row(t('consumed'), credits(credits_.consumed, 3))
418
- : null, expiryBlock(t, credits_), rateLimitBlock(t, status?.rateLimit), planNotice(t, status?.rateLimit), usageProfileBlock(t, status?.usageProfile), usage !== undefined
439
+ : null, expiryBlock(t, credits_), rateLimitBlock(t, status?.rateLimit), planNotice(t, status?.rateLimit), planLine(t, status?.plan), usageProfileBlock(t, status?.usageProfile), usage !== undefined
419
440
  ? h('div', { style: rowStyle }, h('span', null, t('tokens', { count: tokens(usage.inputTokens + usage.outputTokens + usage.cacheReadTokens) })), h('span', null, t('cost', { amount: money(usage.estimatedCny) })))
420
441
  : null, credits_.updatedAtMs !== undefined
421
442
  ? row(t('updated', { time: new Date(credits_.updatedAtMs).toLocaleTimeString() }), '')
@@ -913,7 +934,7 @@
913
934
  // usable even when the balance cannot be read.
914
935
  h(GenerationPanel, props));
915
936
  }
916
- return h('div', { style: pageStyle, 'data-minimax-page': true }, h('div', { style: cardStyle, 'data-minimax-credits-card': true }, h('div', { style: cardTitleStyle }, t('title')), h('div', { style: bigNumberStyle }, t('balance', { total: credits(credits_.total, 2) })), row(t('free'), credits(credits_.free, 3)), row(t('purchased'), credits(credits_.purchased, 3)), expiryBlock(t, credits_), rateLimitBlock(t, status?.rateLimit), planNotice(t, status?.rateLimit), signinBlock(t, signin, refresh), h('div', { style: sectionTitleStyle }, t('lots')), (credits_.upcoming ?? []).length === 0
937
+ return h('div', { style: pageStyle, 'data-minimax-page': true }, h('div', { style: cardStyle, 'data-minimax-credits-card': true }, h('div', { style: cardTitleStyle }, t('title')), h('div', { style: bigNumberStyle }, t('balance', { total: credits(credits_.total, 2) })), row(t('free'), credits(credits_.free, 3)), row(t('purchased'), credits(credits_.purchased, 3)), expiryBlock(t, credits_), rateLimitBlock(t, status?.rateLimit), planNotice(t, status?.rateLimit), planLine(t, status?.plan), signinBlock(t, signin, refresh), h('div', { style: sectionTitleStyle }, t('lots')), (credits_.upcoming ?? []).length === 0
917
938
  ? h('div', { style: mutedStyle }, t('noLots'))
918
939
  : h('ul', { style: listStyle }, ...(credits_.upcoming ?? []).map((lot, index) => h('li', { key: `${lot.expireAtMs}-${index}`, style: rowStyle }, h('span', null, day(lot.expireAtMs)), h('span', null, credits(lot.amount, 2))))), usage !== undefined
919
940
  ? h('div', { style: usageBoxStyle }, row(t('tokens', { count: tokens(usage.inputTokens + usage.outputTokens + usage.cacheReadTokens) }), ''), row(t('cost', { amount: money(usage.estimatedCny) }), ''), usage.requests > 0 ? row(t('requests', { count: usage.requests }), usage.lastModel ?? '') : null)
package/lib/index.d.ts CHANGED
@@ -320,6 +320,32 @@ interface CreditSummary {
320
320
  /** Remaining free (granted) credits. */
321
321
  free: number;
322
322
  }
323
+ /**
324
+ * The plan the account subscribes to.
325
+ *
326
+ * An account without one still reads a balance — it simply has no rolling
327
+ * windows, which is what the limit readout used to report as missing. With a
328
+ * plan the upstream names the tier and the day it ends, and both are worth
329
+ * showing: the tier says what the account has, the day says how long it has.
330
+ */
331
+ interface PlanSummary {
332
+ /** The tier's name, as the upstream spells it ("Go"). */
333
+ tier?: string;
334
+ /** When the plan ends, in milliseconds since the epoch. */
335
+ expiresAtMs?: number;
336
+ /** Whether the upstream reports an active plan at all. */
337
+ active: boolean;
338
+ }
339
+ /**
340
+ * Read the plan out of a membership response.
341
+ *
342
+ * The upstream only fills these in for an account that subscribes: without a
343
+ * plan `has_token_plan` is false and the tier is absent. `subscription_type`
344
+ * says so too — "mplan" once a plan is held, "none" before.
345
+ *
346
+ * @returns The plan, or `undefined` when the response carries no plan fields.
347
+ */
348
+ declare function parsePlanSummary(document: unknown): PlanSummary | undefined;
323
349
  /**
324
350
  * Read the credit summary out of a membership response.
325
351
  *
@@ -327,6 +353,14 @@ interface CreditSummary {
327
353
  * balance fields, so the same fallback chain is used here.
328
354
  */
329
355
  declare function parseCreditSummary(document: unknown): CreditSummary | undefined;
356
+ /**
357
+ * Fetch the membership document.
358
+ *
359
+ * One answer carries several things: the credit balance, the plan the
360
+ * account subscribes to, and when that plan ends. They are read together so
361
+ * that the account is asked once.
362
+ */
363
+ declare function fetchMembershipDocument(credential: OAuthCredential, region: MinimaxRegion, fetchImpl?: typeof fetch): Promise<Record<string, unknown> | undefined>;
330
364
  /**
331
365
  * Fetch the current credit balance.
332
366
  *
@@ -387,19 +421,24 @@ interface RateLimitWindows {
387
421
  weekly: UsageWindow;
388
422
  }
389
423
  /**
390
- * Read the two rate-limit windows out of a remains-percent response.
424
+ * Read the two rate-limit windows out of a remains response.
391
425
  *
392
426
  * The upstream answers with one entry per metered group; the general one is
393
427
  * the only one the plan meters today, and the first entry is used when it is
394
- * absent. The percents arrive as strings ("19%"), and the counts arrive as -1
395
- * when the plan does not meter by count, so only the percents and the window
396
- * edges are read. The window length is derived from the entry's own endpoints
397
- * rather than assumed to be five hours or seven days — the plan's terms have
428
+ * absent. The window length is derived from the entry's own endpoints rather
429
+ * than assumed to be five hours or seven days — the plan's terms have
398
430
  * changed before, and the display should follow whatever the server says.
399
431
  *
400
432
  * A window the server marks as unlimited (`current_*_status` of 3) carries no
401
433
  * allowance to be a percentage of, so it produces no window at all: reporting
402
434
  * "0%" of an unlimited quota would be a number nobody can act on.
435
+ *
436
+ * **Which share the upstream reports changed.** It used to answer with the
437
+ * share already used, as a string ("19%"); with a plan subscribed it answers
438
+ * with the share still *remaining*, as a plain number (90). Both are
439
+ * accepted, and the two are opposites, so what leaves this function is
440
+ * always the share used — otherwise the same window would read 90% used on
441
+ * a plan that has barely spent anything.
403
442
  */
404
443
  declare function parseRateLimitWindows(document: unknown): RateLimitWindows | undefined;
405
444
  /**
@@ -493,6 +532,8 @@ declare class MinimaxCredits {
493
532
  get rateLimit(): RateLimitWindows | undefined;
494
533
  /** Why the windows could not be read, when they could not. */
495
534
  get rateLimitError(): string | undefined;
535
+ /** The plan the account subscribes to, as the last read found it. */
536
+ get plan(): PlanSummary | undefined;
496
537
  /** Why the plan has no windows, when the upstream said so plainly. */
497
538
  get rateLimitAbsence(): 'no-subscription' | undefined;
498
539
  /** When the windows were last read. */
@@ -985,6 +1026,15 @@ interface UsageStatus {
985
1026
  /** How many requests were measured. */
986
1027
  requests: number;
987
1028
  }
1029
+ /** The plan the account subscribes to, as the card reads it. */
1030
+ interface PlanStatus {
1031
+ /** Whether the account holds a plan. */
1032
+ active: boolean;
1033
+ /** The tier's name, as the upstream spells it ("Go"). */
1034
+ tier?: string;
1035
+ /** When the plan ends, in milliseconds since the epoch. */
1036
+ expiresAtMs?: number;
1037
+ }
988
1038
  /** The plan's two rolling usage windows, as the card reads them. */
989
1039
  interface RateLimitStatus {
990
1040
  /** Whether the windows could be read. */
@@ -1085,6 +1135,8 @@ interface StatusDocument {
1085
1135
  login?: LoginStatus;
1086
1136
  /** The account's usage profile, when it could be read. */
1087
1137
  usageProfile?: UsageProfileStatus;
1138
+ /** The plan the account subscribes to, when there is one. */
1139
+ plan?: PlanStatus;
1088
1140
  /** The choices the generation endpoints offer. */
1089
1141
  generation?: GenerationOptions;
1090
1142
  /** What the host offered the card the last time it looked. */
@@ -1124,6 +1176,8 @@ interface StatusDeps {
1124
1176
  login?: LoginStatus;
1125
1177
  /** The account's usage profile, when the account API could be reached. */
1126
1178
  usageProfile?: UsageProfileStatus;
1179
+ /** The plan the account subscribes to, when there is one. */
1180
+ plan?: PlanStatus;
1127
1181
  /** The choices the generation endpoints offer. */
1128
1182
  generation?: GenerationOptions;
1129
1183
  /** What the host offered the card the last time it looked. */
@@ -1215,4 +1269,4 @@ declare const inject: string[];
1215
1269
  */
1216
1270
  declare function apply(ctx: Context): void;
1217
1271
  //#endregion
1218
- export { type AuthState, type CatalogModel, type CatalogSource, type Credential, type CreditSummary, DEVICE_CODE_GRANT, DEVICE_CODE_PATH, type DeviceLoginState, EMPTY_USAGE, FALLBACK_MODELS, MEMBERSHIP_PATH, MINIMAXCODE_PROVIDER_ID, MINIMAX_ACTION_PATH, MINIMAX_AUTH_FILENAME, MINIMAX_DATA_DIR, MINIMAX_GATEWAYS, MINIMAX_OAUTH_CLIENT_ID, MINIMAX_REGIONS, MINIMAX_STATUS_PATH, MINIMAX_STREAM_IDLE_TIMEOUT_MS, MODEL_PRICES, MinimaxCatalog, MinimaxCredits, type MinimaxRegion, type ModelUsage, type OAuthCredential, type OAuthState, PLUGIN_CREDENTIAL_FILENAME, PLUGIN_STATE_FILENAME, type PluginCredential, type PluginState, REMAINS_PERCENT_PATH, type RateLimitWindows, SIGNIN_CLAIM_PATH, SIGNIN_CLAIM_RESULT, SIGNIN_CYCLE_DAYS, SIGNIN_DAY_STATUS, SIGNIN_PANEL_PATH, type SigninClaim, type SigninDay, type SigninPanel, type StatusDocument, type TokenClaims, UsageMeter, apply, authPath, bearerToken, catalogUrl, chatBaseUrl, claimSignin, createMinimaxCodeAdapter, dataDir, daysRemaining, decodeClaims, deletePluginCredential, dshHome, ensureOAuthCredential, estimateCny, expiresInMs, expiryLevel, fetchCreditSummary, fetchRateLimitWindows, fetchSigninPanel, inject, localDay, makePkce, name, parseAuthDocument, parseCatalog, parseCreditSummary, parseOAuthDocument, parseRateLimitWindows, parseSigninClaim, parseSigninPanel, pluginCredentialPath, pluginStatePath, pollDeviceLogin, priceFor, readCredential, readOAuthCredential, readPluginCredential, readPluginState, refreshOAuthCredential, refreshPluginCredential, startDeviceLogin, statusDocument, timezoneId, writeOAuthCredential, writePluginCredential, writePluginState };
1272
+ export { type AuthState, type CatalogModel, type CatalogSource, type Credential, type CreditSummary, DEVICE_CODE_GRANT, DEVICE_CODE_PATH, type DeviceLoginState, EMPTY_USAGE, FALLBACK_MODELS, MEMBERSHIP_PATH, MINIMAXCODE_PROVIDER_ID, MINIMAX_ACTION_PATH, MINIMAX_AUTH_FILENAME, MINIMAX_DATA_DIR, MINIMAX_GATEWAYS, MINIMAX_OAUTH_CLIENT_ID, MINIMAX_REGIONS, MINIMAX_STATUS_PATH, MINIMAX_STREAM_IDLE_TIMEOUT_MS, MODEL_PRICES, MinimaxCatalog, MinimaxCredits, type MinimaxRegion, type ModelUsage, type OAuthCredential, type OAuthState, PLUGIN_CREDENTIAL_FILENAME, PLUGIN_STATE_FILENAME, type PlanSummary, type PluginCredential, type PluginState, REMAINS_PERCENT_PATH, type RateLimitWindows, SIGNIN_CLAIM_PATH, SIGNIN_CLAIM_RESULT, SIGNIN_CYCLE_DAYS, SIGNIN_DAY_STATUS, SIGNIN_PANEL_PATH, type SigninClaim, type SigninDay, type SigninPanel, type StatusDocument, type TokenClaims, UsageMeter, apply, authPath, bearerToken, catalogUrl, chatBaseUrl, claimSignin, createMinimaxCodeAdapter, dataDir, daysRemaining, decodeClaims, deletePluginCredential, dshHome, ensureOAuthCredential, estimateCny, expiresInMs, expiryLevel, fetchCreditSummary, fetchMembershipDocument, fetchRateLimitWindows, fetchSigninPanel, inject, localDay, makePkce, name, parseAuthDocument, parseCatalog, parseCreditSummary, parseOAuthDocument, parsePlanSummary, parseRateLimitWindows, parseSigninClaim, parseSigninPanel, pluginCredentialPath, pluginStatePath, pollDeviceLogin, priceFor, readCredential, readOAuthCredential, readPluginCredential, readPluginState, refreshOAuthCredential, refreshPluginCredential, startDeviceLogin, statusDocument, timezoneId, writeOAuthCredential, writePluginCredential, writePluginState };
package/lib/index.js CHANGED
@@ -786,6 +786,7 @@ function statusDocument(deps) {
786
786
  ...deps.signin === void 0 ? {} : { signin: deps.signin },
787
787
  ...deps.login === void 0 ? {} : { login: deps.login },
788
788
  ...deps.usageProfile === void 0 ? {} : { usageProfile: deps.usageProfile },
789
+ ...deps.plan === void 0 ? {} : { plan: deps.plan },
789
790
  ...deps.capability === void 0 ? {} : { capability: deps.capability },
790
791
  ...deps.generation === void 0 ? {} : { generation: deps.generation }
791
792
  };
@@ -1374,6 +1375,29 @@ async function ensureOAuthCredential(region, env = process.env, now = Date.now()
1374
1375
  }
1375
1376
  }
1376
1377
  /**
1378
+ * Read the plan out of a membership response.
1379
+ *
1380
+ * The upstream only fills these in for an account that subscribes: without a
1381
+ * plan `has_token_plan` is false and the tier is absent. `subscription_type`
1382
+ * says so too — "mplan" once a plan is held, "none" before.
1383
+ *
1384
+ * @returns The plan, or `undefined` when the response carries no plan fields.
1385
+ */
1386
+ function parsePlanSummary(document) {
1387
+ if (typeof document !== "object" || document === null) return void 0;
1388
+ const record = document;
1389
+ const hasTokenPlan = record.has_token_plan;
1390
+ const tier = typeof record.token_plan_tier === "string" && record.token_plan_tier.length > 0 ? record.token_plan_tier : void 0;
1391
+ const expires = toNumber$1(record.token_plan_expires_at);
1392
+ const expiresAtMs = expires === void 0 || expires <= 0 ? void 0 : expires;
1393
+ if (hasTokenPlan !== true && tier === void 0 && expiresAtMs === void 0) return void 0;
1394
+ return {
1395
+ tier,
1396
+ expiresAtMs,
1397
+ active: hasTokenPlan === true || tier !== void 0
1398
+ };
1399
+ }
1400
+ /**
1377
1401
  * Read the credit summary out of a membership response.
1378
1402
  *
1379
1403
  * The app's settings page prefers the summary and falls back to the flat
@@ -1411,11 +1435,13 @@ function toNumber$1(value) {
1411
1435
  }
1412
1436
  }
1413
1437
  /**
1414
- * Fetch the current credit balance.
1438
+ * Fetch the membership document.
1415
1439
  *
1416
- * @returns The balance, or `undefined` when the account has no credit summary.
1440
+ * One answer carries several things: the credit balance, the plan the
1441
+ * account subscribes to, and when that plan ends. They are read together so
1442
+ * that the account is asked once.
1417
1443
  */
1418
- async function fetchCreditSummary(credential, region, fetchImpl = fetch) {
1444
+ async function fetchMembershipDocument(credential, region, fetchImpl = fetch) {
1419
1445
  const response = await fetchImpl(`${MINIMAX_ACCOUNT_HOSTS[region]}${MEMBERSHIP_PATH}`, {
1420
1446
  method: "POST",
1421
1447
  headers: {
@@ -1425,7 +1451,17 @@ async function fetchCreditSummary(credential, region, fetchImpl = fetch) {
1425
1451
  body: "{}"
1426
1452
  });
1427
1453
  if (!response.ok) throw new Error(`membership request failed: HTTP ${response.status}`);
1428
- return parseCreditSummary(await response.json());
1454
+ const document = await response.json();
1455
+ return typeof document === "object" && document !== null ? document : void 0;
1456
+ }
1457
+ /**
1458
+ * Fetch the current credit balance.
1459
+ *
1460
+ * @returns The balance, or `undefined` when the account has no credit summary.
1461
+ */
1462
+ async function fetchCreditSummary(credential, region, fetchImpl = fetch) {
1463
+ const document = await fetchMembershipDocument(credential, region, fetchImpl);
1464
+ return document === void 0 ? void 0 : parseCreditSummary(document);
1429
1465
  }
1430
1466
  /**
1431
1467
  * Read the credit lots out of a ledger response.
@@ -1487,19 +1523,24 @@ async function fetchCreditLots(credential, region, fetchImpl = fetch) {
1487
1523
  return lots;
1488
1524
  }
1489
1525
  /**
1490
- * Read the two rate-limit windows out of a remains-percent response.
1526
+ * Read the two rate-limit windows out of a remains response.
1491
1527
  *
1492
1528
  * The upstream answers with one entry per metered group; the general one is
1493
1529
  * the only one the plan meters today, and the first entry is used when it is
1494
- * absent. The percents arrive as strings ("19%"), and the counts arrive as -1
1495
- * when the plan does not meter by count, so only the percents and the window
1496
- * edges are read. The window length is derived from the entry's own endpoints
1497
- * rather than assumed to be five hours or seven days — the plan's terms have
1530
+ * absent. The window length is derived from the entry's own endpoints rather
1531
+ * than assumed to be five hours or seven days — the plan's terms have
1498
1532
  * changed before, and the display should follow whatever the server says.
1499
1533
  *
1500
1534
  * A window the server marks as unlimited (`current_*_status` of 3) carries no
1501
1535
  * allowance to be a percentage of, so it produces no window at all: reporting
1502
1536
  * "0%" of an unlimited quota would be a number nobody can act on.
1537
+ *
1538
+ * **Which share the upstream reports changed.** It used to answer with the
1539
+ * share already used, as a string ("19%"); with a plan subscribed it answers
1540
+ * with the share still *remaining*, as a plain number (90). Both are
1541
+ * accepted, and the two are opposites, so what leaves this function is
1542
+ * always the share used — otherwise the same window would read 90% used on
1543
+ * a plan that has barely spent anything.
1503
1544
  */
1504
1545
  function parseRateLimitWindows(document) {
1505
1546
  if (typeof document !== "object" || document === null) return void 0;
@@ -1508,8 +1549,8 @@ function parseRateLimitWindows(document) {
1508
1549
  const entry = remains.find((item) => typeof item === "object" && item !== null && item.model_name === "general") ?? remains[0];
1509
1550
  if (typeof entry !== "object" || entry === null) return void 0;
1510
1551
  const record = entry;
1511
- const interval = readWindow(record.current_interval_used_percent, record.start_time, record.end_time, record.current_interval_status);
1512
- const weekly = readWindow(record.current_weekly_used_percent, record.weekly_start_time, record.weekly_end_time, record.current_weekly_status);
1552
+ const interval = readWindow(record.current_interval_used_percent, record.current_interval_remaining_percent, record.start_time, record.end_time, record.current_interval_status);
1553
+ const weekly = readWindow(record.current_weekly_used_percent, record.current_weekly_remaining_percent, record.weekly_start_time, record.weekly_end_time, record.current_weekly_status);
1513
1554
  if (interval === void 0 || weekly === void 0) return void 0;
1514
1555
  return {
1515
1556
  interval,
@@ -1522,10 +1563,25 @@ function parseRateLimitWindows(document) {
1522
1563
  * The desktop app reads it the same way: `isUnlimited: 3 === status`.
1523
1564
  */
1524
1565
  const UNLIMITED_STATUS = 3;
1566
+ /**
1567
+ * The share of a window's allowance already used, whichever way it is reported.
1568
+ *
1569
+ * The endpoint named the used share `current_*_used_percent` and then
1570
+ * renamed it to `current_*_remaining_percent` while inverting its meaning.
1571
+ * The used share is what a person needs, so it is what this returns; the
1572
+ * remaining share is subtracted from a hundred to get there.
1573
+ */
1574
+ function readUsedPercent(used, remaining) {
1575
+ const usedPercent = parsePercent(used);
1576
+ if (usedPercent !== void 0) return usedPercent;
1577
+ const remainingPercent = parsePercent(remaining);
1578
+ if (remainingPercent === void 0) return void 0;
1579
+ return 100 - remainingPercent;
1580
+ }
1525
1581
  /** Assemble one window from its percent and edge fields; all must be present. */
1526
- function readWindow(percent, startAtMs, resetAtMs, status) {
1582
+ function readWindow(percent, remainingPercent, startAtMs, resetAtMs, status) {
1527
1583
  if (status === UNLIMITED_STATUS) return void 0;
1528
- const usedPercent = parsePercent(percent);
1584
+ const usedPercent = readUsedPercent(percent, remainingPercent);
1529
1585
  const start = toNumber$1(startAtMs);
1530
1586
  const reset = toNumber$1(resetAtMs);
1531
1587
  if (usedPercent === void 0 || start === void 0 || reset === void 0) return void 0;
@@ -1718,6 +1774,7 @@ var MinimaxCredits = class {
1718
1774
  #ledgerError;
1719
1775
  #rateLimit;
1720
1776
  #rateLimitError;
1777
+ #plan;
1721
1778
  #rateLimitAbsence;
1722
1779
  #rateLimitAtMs;
1723
1780
  #usageProfile;
@@ -1755,6 +1812,10 @@ var MinimaxCredits = class {
1755
1812
  get rateLimitError() {
1756
1813
  return this.#rateLimitError;
1757
1814
  }
1815
+ /** The plan the account subscribes to, as the last read found it. */
1816
+ get plan() {
1817
+ return this.#plan;
1818
+ }
1758
1819
  /** Why the plan has no windows, when the upstream said so plainly. */
1759
1820
  get rateLimitAbsence() {
1760
1821
  return this.#rateLimitAbsence;
@@ -1785,7 +1846,9 @@ var MinimaxCredits = class {
1785
1846
  async refresh(credential, region, fetchImpl = fetch, now = Date.now()) {
1786
1847
  let changed = false;
1787
1848
  try {
1788
- const summary = await fetchCreditSummary(credential, region, fetchImpl);
1849
+ const document = await fetchMembershipDocument(credential, region, fetchImpl);
1850
+ if (document !== void 0) this.#plan = parsePlanSummary(document);
1851
+ const summary = document === void 0 ? void 0 : parseCreditSummary(document);
1789
1852
  if (summary === void 0) this.#error = "membership response carried no credit summary";
1790
1853
  else {
1791
1854
  const previous = this.#snapshot;
@@ -2956,6 +3019,20 @@ function apply(ctx) {
2956
3019
  updatedAt: profile.lastUpdateTime
2957
3020
  };
2958
3021
  };
3022
+ /**
3023
+ * The plan the account holds, as the card reads it.
3024
+ *
3025
+ * It comes from the same membership answer as the balance, so it is free.
3026
+ */
3027
+ const buildPlanStatus = () => {
3028
+ const held = credits.plan;
3029
+ if (held === void 0) return void 0;
3030
+ return {
3031
+ active: held.active,
3032
+ tier: held.tier,
3033
+ expiresAtMs: held.expiresAtMs
3034
+ };
3035
+ };
2959
3036
  /** The plan's rolling usage windows as the card reads them. */
2960
3037
  const buildRateLimitStatus = () => {
2961
3038
  if (creditState === "signed-out" || creditState === "expired") return { state: creditState };
@@ -3105,6 +3182,7 @@ function apply(ctx) {
3105
3182
  signin: buildSigninStatus(),
3106
3183
  login: buildLoginStatus(),
3107
3184
  usageProfile: buildUsageProfileStatus(),
3185
+ plan: buildPlanStatus(),
3108
3186
  capability,
3109
3187
  generation: {
3110
3188
  imageModels: [...IMAGE_MODELS],
@@ -3514,4 +3592,4 @@ async function readRequestBody(req) {
3514
3592
  return text;
3515
3593
  }
3516
3594
  //#endregion
3517
- export { DEVICE_CODE_GRANT, DEVICE_CODE_PATH, EMPTY_USAGE, FALLBACK_MODELS, MEMBERSHIP_PATH, MINIMAXCODE_PROVIDER_ID, MINIMAX_ACTION_PATH, MINIMAX_AUTH_FILENAME, MINIMAX_DATA_DIR, MINIMAX_GATEWAYS, MINIMAX_OAUTH_CLIENT_ID, MINIMAX_REGIONS, MINIMAX_STATUS_PATH, MINIMAX_STREAM_IDLE_TIMEOUT_MS, MODEL_PRICES, MinimaxCatalog, MinimaxCredits, PLUGIN_CREDENTIAL_FILENAME, PLUGIN_STATE_FILENAME, REMAINS_PERCENT_PATH, SIGNIN_CLAIM_PATH, SIGNIN_CLAIM_RESULT, SIGNIN_CYCLE_DAYS, SIGNIN_DAY_STATUS, SIGNIN_PANEL_PATH, UsageMeter, apply, authPath, bearerToken, catalogUrl, chatBaseUrl, claimSignin, createMinimaxCodeAdapter, dataDir, daysRemaining, decodeClaims, deletePluginCredential, dshHome, ensureOAuthCredential, estimateCny, expiresInMs, expiryLevel, fetchCreditSummary, fetchRateLimitWindows, fetchSigninPanel, inject, localDay, makePkce, name, parseAuthDocument, parseCatalog, parseCreditSummary, parseOAuthDocument, parseRateLimitWindows, parseSigninClaim, parseSigninPanel, pluginCredentialPath, pluginStatePath, pollDeviceLogin, priceFor, readCredential, readOAuthCredential, readPluginCredential, readPluginState, refreshOAuthCredential, refreshPluginCredential, startDeviceLogin, statusDocument, timezoneId, writeOAuthCredential, writePluginCredential, writePluginState };
3595
+ export { DEVICE_CODE_GRANT, DEVICE_CODE_PATH, EMPTY_USAGE, FALLBACK_MODELS, MEMBERSHIP_PATH, MINIMAXCODE_PROVIDER_ID, MINIMAX_ACTION_PATH, MINIMAX_AUTH_FILENAME, MINIMAX_DATA_DIR, MINIMAX_GATEWAYS, MINIMAX_OAUTH_CLIENT_ID, MINIMAX_REGIONS, MINIMAX_STATUS_PATH, MINIMAX_STREAM_IDLE_TIMEOUT_MS, MODEL_PRICES, MinimaxCatalog, MinimaxCredits, PLUGIN_CREDENTIAL_FILENAME, PLUGIN_STATE_FILENAME, REMAINS_PERCENT_PATH, SIGNIN_CLAIM_PATH, SIGNIN_CLAIM_RESULT, SIGNIN_CYCLE_DAYS, SIGNIN_DAY_STATUS, SIGNIN_PANEL_PATH, UsageMeter, apply, authPath, bearerToken, catalogUrl, chatBaseUrl, claimSignin, createMinimaxCodeAdapter, dataDir, daysRemaining, decodeClaims, deletePluginCredential, dshHome, ensureOAuthCredential, estimateCny, expiresInMs, expiryLevel, fetchCreditSummary, fetchMembershipDocument, fetchRateLimitWindows, fetchSigninPanel, inject, localDay, makePkce, name, parseAuthDocument, parseCatalog, parseCreditSummary, parseOAuthDocument, parsePlanSummary, parseRateLimitWindows, parseSigninClaim, parseSigninPanel, pluginCredentialPath, pluginStatePath, pollDeviceLogin, priceFor, readCredential, readOAuthCredential, readPluginCredential, readPluginState, refreshOAuthCredential, refreshPluginCredential, startDeviceLogin, statusDocument, timezoneId, writeOAuthCredential, writePluginCredential, writePluginState };
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "dsh-connect-minimaxcode",
3
3
  "displayName": "DSH Connect MiniMax Code",
4
4
  "description": "把本机已登录的 MiniMax Code 桌面 App 模型接入 DeepSeek Harness:零配置、Anthropic Messages 兼容、动态模型目录,并显示积分余额与到期 — bring MiniMax Code desktop app models into DSH with a dynamic model catalog and a credit balance with expiry details.",
5
- "version": "0.6.4",
5
+ "version": "0.6.5",
6
6
  "author": "JeffTsuiCUG",
7
7
  "contributors": [
8
8
  {