dsh-connect-minimaxcode 0.6.3 → 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 +12 -0
- package/README.md +12 -0
- package/lib/client.js +23 -2
- package/lib/index.d.ts +74 -6
- package/lib/index.js +137 -15
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -384,6 +384,18 @@ 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
|
+
|
|
393
|
+
### 0.6.4 (2026-11-11)
|
|
394
|
+
|
|
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`.
|
|
396
|
+
- The episode also exposed a process problem: a few anchors in the patch script missed, and its output was covered by another script in the same command, so a partial application was read as a complete one. **Every patch is now checked individually**, and an end-to-end check was added: the report counts as working only once it has travelled from the card through the route and back into the status document.
|
|
397
|
+
- The route-count cases were brought up to date (the host now mounts four routes: status, action, capability, generation).
|
|
398
|
+
|
|
387
399
|
### 0.6.3 (2026-11-11)
|
|
388
400
|
|
|
389
401
|
- **"Could not send it to the conversation" now names the missing piece**: delivering a picture needs several pieces of the host — the session identifier, the sessions service, the session binding, a way to send or to hold a draft, and the input box's attachment channel — and they arrive in different ways. There used to be one blanket failure message; each absence now has its own: the host named no session / offers no sessions service / this session is not reachable / it cannot send / it cannot hold drafts / the input box takes no attachments.
|
package/README.md
CHANGED
|
@@ -395,6 +395,18 @@ 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
|
+
|
|
404
|
+
### 0.6.4(2026-11-11)
|
|
405
|
+
|
|
406
|
+
- **修复「自检报告从未出现」**:上一版把自检能力加在了客户端(卡片上报宿主提供了什么),但**服务端那一半没有落地** —— 状态文档里没有承载报告的字段,也没有接收报告的路由,于是卡片上报到哪里都无人接收。现已补齐:报告由插件自己的路由接收,并出现在状态文档的 `capability` 里。
|
|
407
|
+
- 这次同时暴露了一个流程问题:补丁脚本里有几处锚点未命中,而脚本的输出被同一条命令里的另一个脚本盖住了,于是「部分应用」被当成了「全部应用」。**现在每一处补丁的成败都单独核对**,并且加了端到端验证:报告从卡片出发、经路由回到状态文档,全程贯通才算通过。
|
|
408
|
+
- 顺带补上路由数量的用例(宿主侧现在有四条路由:状态、动作、自检、生成)。
|
|
409
|
+
|
|
398
410
|
### 0.6.3(2026-11-11)
|
|
399
411
|
|
|
400
412
|
- **「无法发送到对话」现在会说明缺哪一步**:把图片送进对话需要宿主的好几块能力(会话标识、会话服务、会话绑定、发送或草稿通道、输入框的附件通道),它们分别以不同方式交到插件手上。原先只有一句笼统的失败提示,现在每种缺失都有各自的话:宿主没给会话标识 / 没有会话服务 / 当前会话不可达 / 会话不支持发送 / 不支持草稿 / 输入框不接受附件。
|
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
|
|
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
|
|
395
|
-
*
|
|
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,8 +1135,17 @@ 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;
|
|
1142
|
+
/** What the host offered the card the last time it looked. */
|
|
1143
|
+
capability?: {
|
|
1144
|
+
/** When the card reported it. */
|
|
1145
|
+
at: number;
|
|
1146
|
+
/** One entry per piece the card looked for. */
|
|
1147
|
+
findings: Record<string, string>;
|
|
1148
|
+
};
|
|
1090
1149
|
}
|
|
1091
1150
|
/** Inputs the document is derived from. */
|
|
1092
1151
|
interface StatusDeps {
|
|
@@ -1117,8 +1176,17 @@ interface StatusDeps {
|
|
|
1117
1176
|
login?: LoginStatus;
|
|
1118
1177
|
/** The account's usage profile, when the account API could be reached. */
|
|
1119
1178
|
usageProfile?: UsageProfileStatus;
|
|
1179
|
+
/** The plan the account subscribes to, when there is one. */
|
|
1180
|
+
plan?: PlanStatus;
|
|
1120
1181
|
/** The choices the generation endpoints offer. */
|
|
1121
1182
|
generation?: GenerationOptions;
|
|
1183
|
+
/** What the host offered the card the last time it looked. */
|
|
1184
|
+
capability?: {
|
|
1185
|
+
/** When the card reported it. */
|
|
1186
|
+
at: number;
|
|
1187
|
+
/** One entry per piece the card looked for. */
|
|
1188
|
+
findings: Record<string, string>;
|
|
1189
|
+
};
|
|
1122
1190
|
}
|
|
1123
1191
|
/**
|
|
1124
1192
|
* Build the card's status document.
|
|
@@ -1201,4 +1269,4 @@ declare const inject: string[];
|
|
|
1201
1269
|
*/
|
|
1202
1270
|
declare function apply(ctx: Context): void;
|
|
1203
1271
|
//#endregion
|
|
1204
|
-
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
|
@@ -746,6 +746,16 @@ const MINIMAX_ACTION_PATH = "/plugins/dsh-connect-minimaxcode/action";
|
|
|
746
746
|
* takes a minute and a half and is polled.
|
|
747
747
|
*/
|
|
748
748
|
const MINIMAX_GENERATE_PATH = "/plugins/dsh-connect-minimaxcode/generate";
|
|
749
|
+
/**
|
|
750
|
+
* Route the card reports what the host offers it on.
|
|
751
|
+
*
|
|
752
|
+
* Delivering a generated picture into a conversation needs several pieces of
|
|
753
|
+
* the host — the session, its face, the input box's attachment channel — and
|
|
754
|
+
* they are handed over in different ways. Reporting what is actually there
|
|
755
|
+
* turns "it did not work" into "this piece is missing", which is the only
|
|
756
|
+
* useful answer when something like that goes wrong.
|
|
757
|
+
*/
|
|
758
|
+
const MINIMAX_CAPABILITY_PATH = "/plugins/dsh-connect-minimaxcode/capability";
|
|
749
759
|
const HINTS = {
|
|
750
760
|
"app-missing": "未找到 MiniMax Code 的登录信息。请先安装并登录 MiniMax Code 桌面 App,然后重启 DSH。",
|
|
751
761
|
"not-signed-in": "MiniMax Code 尚未登录。请在 MiniMax Code 桌面 App 中登录后重启 DSH。",
|
|
@@ -776,6 +786,8 @@ function statusDocument(deps) {
|
|
|
776
786
|
...deps.signin === void 0 ? {} : { signin: deps.signin },
|
|
777
787
|
...deps.login === void 0 ? {} : { login: deps.login },
|
|
778
788
|
...deps.usageProfile === void 0 ? {} : { usageProfile: deps.usageProfile },
|
|
789
|
+
...deps.plan === void 0 ? {} : { plan: deps.plan },
|
|
790
|
+
...deps.capability === void 0 ? {} : { capability: deps.capability },
|
|
779
791
|
...deps.generation === void 0 ? {} : { generation: deps.generation }
|
|
780
792
|
};
|
|
781
793
|
if (auth.state === "expired") return {
|
|
@@ -1363,6 +1375,29 @@ async function ensureOAuthCredential(region, env = process.env, now = Date.now()
|
|
|
1363
1375
|
}
|
|
1364
1376
|
}
|
|
1365
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
|
+
/**
|
|
1366
1401
|
* Read the credit summary out of a membership response.
|
|
1367
1402
|
*
|
|
1368
1403
|
* The app's settings page prefers the summary and falls back to the flat
|
|
@@ -1400,11 +1435,13 @@ function toNumber$1(value) {
|
|
|
1400
1435
|
}
|
|
1401
1436
|
}
|
|
1402
1437
|
/**
|
|
1403
|
-
* Fetch the
|
|
1438
|
+
* Fetch the membership document.
|
|
1404
1439
|
*
|
|
1405
|
-
*
|
|
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.
|
|
1406
1443
|
*/
|
|
1407
|
-
async function
|
|
1444
|
+
async function fetchMembershipDocument(credential, region, fetchImpl = fetch) {
|
|
1408
1445
|
const response = await fetchImpl(`${MINIMAX_ACCOUNT_HOSTS[region]}${MEMBERSHIP_PATH}`, {
|
|
1409
1446
|
method: "POST",
|
|
1410
1447
|
headers: {
|
|
@@ -1414,7 +1451,17 @@ async function fetchCreditSummary(credential, region, fetchImpl = fetch) {
|
|
|
1414
1451
|
body: "{}"
|
|
1415
1452
|
});
|
|
1416
1453
|
if (!response.ok) throw new Error(`membership request failed: HTTP ${response.status}`);
|
|
1417
|
-
|
|
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);
|
|
1418
1465
|
}
|
|
1419
1466
|
/**
|
|
1420
1467
|
* Read the credit lots out of a ledger response.
|
|
@@ -1476,19 +1523,24 @@ async function fetchCreditLots(credential, region, fetchImpl = fetch) {
|
|
|
1476
1523
|
return lots;
|
|
1477
1524
|
}
|
|
1478
1525
|
/**
|
|
1479
|
-
* Read the two rate-limit windows out of a remains
|
|
1526
|
+
* Read the two rate-limit windows out of a remains response.
|
|
1480
1527
|
*
|
|
1481
1528
|
* The upstream answers with one entry per metered group; the general one is
|
|
1482
1529
|
* the only one the plan meters today, and the first entry is used when it is
|
|
1483
|
-
* absent. The
|
|
1484
|
-
*
|
|
1485
|
-
* edges are read. The window length is derived from the entry's own endpoints
|
|
1486
|
-
* 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
|
|
1487
1532
|
* changed before, and the display should follow whatever the server says.
|
|
1488
1533
|
*
|
|
1489
1534
|
* A window the server marks as unlimited (`current_*_status` of 3) carries no
|
|
1490
1535
|
* allowance to be a percentage of, so it produces no window at all: reporting
|
|
1491
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.
|
|
1492
1544
|
*/
|
|
1493
1545
|
function parseRateLimitWindows(document) {
|
|
1494
1546
|
if (typeof document !== "object" || document === null) return void 0;
|
|
@@ -1497,8 +1549,8 @@ function parseRateLimitWindows(document) {
|
|
|
1497
1549
|
const entry = remains.find((item) => typeof item === "object" && item !== null && item.model_name === "general") ?? remains[0];
|
|
1498
1550
|
if (typeof entry !== "object" || entry === null) return void 0;
|
|
1499
1551
|
const record = entry;
|
|
1500
|
-
const interval = readWindow(record.current_interval_used_percent, record.start_time, record.end_time, record.current_interval_status);
|
|
1501
|
-
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);
|
|
1502
1554
|
if (interval === void 0 || weekly === void 0) return void 0;
|
|
1503
1555
|
return {
|
|
1504
1556
|
interval,
|
|
@@ -1511,10 +1563,25 @@ function parseRateLimitWindows(document) {
|
|
|
1511
1563
|
* The desktop app reads it the same way: `isUnlimited: 3 === status`.
|
|
1512
1564
|
*/
|
|
1513
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
|
+
}
|
|
1514
1581
|
/** Assemble one window from its percent and edge fields; all must be present. */
|
|
1515
|
-
function readWindow(percent, startAtMs, resetAtMs, status) {
|
|
1582
|
+
function readWindow(percent, remainingPercent, startAtMs, resetAtMs, status) {
|
|
1516
1583
|
if (status === UNLIMITED_STATUS) return void 0;
|
|
1517
|
-
const usedPercent =
|
|
1584
|
+
const usedPercent = readUsedPercent(percent, remainingPercent);
|
|
1518
1585
|
const start = toNumber$1(startAtMs);
|
|
1519
1586
|
const reset = toNumber$1(resetAtMs);
|
|
1520
1587
|
if (usedPercent === void 0 || start === void 0 || reset === void 0) return void 0;
|
|
@@ -1707,6 +1774,7 @@ var MinimaxCredits = class {
|
|
|
1707
1774
|
#ledgerError;
|
|
1708
1775
|
#rateLimit;
|
|
1709
1776
|
#rateLimitError;
|
|
1777
|
+
#plan;
|
|
1710
1778
|
#rateLimitAbsence;
|
|
1711
1779
|
#rateLimitAtMs;
|
|
1712
1780
|
#usageProfile;
|
|
@@ -1744,6 +1812,10 @@ var MinimaxCredits = class {
|
|
|
1744
1812
|
get rateLimitError() {
|
|
1745
1813
|
return this.#rateLimitError;
|
|
1746
1814
|
}
|
|
1815
|
+
/** The plan the account subscribes to, as the last read found it. */
|
|
1816
|
+
get plan() {
|
|
1817
|
+
return this.#plan;
|
|
1818
|
+
}
|
|
1747
1819
|
/** Why the plan has no windows, when the upstream said so plainly. */
|
|
1748
1820
|
get rateLimitAbsence() {
|
|
1749
1821
|
return this.#rateLimitAbsence;
|
|
@@ -1774,7 +1846,9 @@ var MinimaxCredits = class {
|
|
|
1774
1846
|
async refresh(credential, region, fetchImpl = fetch, now = Date.now()) {
|
|
1775
1847
|
let changed = false;
|
|
1776
1848
|
try {
|
|
1777
|
-
const
|
|
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);
|
|
1778
1852
|
if (summary === void 0) this.#error = "membership response carried no credit summary";
|
|
1779
1853
|
else {
|
|
1780
1854
|
const previous = this.#snapshot;
|
|
@@ -2646,6 +2720,8 @@ function apply(ctx) {
|
|
|
2646
2720
|
let loginPending = false;
|
|
2647
2721
|
/** Whether the server refused the plugin's own credential. */
|
|
2648
2722
|
let revokedByServer = false;
|
|
2723
|
+
/** What the card last reported about what the host offered it. */
|
|
2724
|
+
let capability;
|
|
2649
2725
|
/**
|
|
2650
2726
|
* Give up on the plugin's own credential after the server refused it.
|
|
2651
2727
|
*
|
|
@@ -2943,6 +3019,20 @@ function apply(ctx) {
|
|
|
2943
3019
|
updatedAt: profile.lastUpdateTime
|
|
2944
3020
|
};
|
|
2945
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
|
+
};
|
|
2946
3036
|
/** The plan's rolling usage windows as the card reads them. */
|
|
2947
3037
|
const buildRateLimitStatus = () => {
|
|
2948
3038
|
if (creditState === "signed-out" || creditState === "expired") return { state: creditState };
|
|
@@ -3092,6 +3182,8 @@ function apply(ctx) {
|
|
|
3092
3182
|
signin: buildSigninStatus(),
|
|
3093
3183
|
login: buildLoginStatus(),
|
|
3094
3184
|
usageProfile: buildUsageProfileStatus(),
|
|
3185
|
+
plan: buildPlanStatus(),
|
|
3186
|
+
capability,
|
|
3095
3187
|
generation: {
|
|
3096
3188
|
imageModels: [...IMAGE_MODELS],
|
|
3097
3189
|
imageAspectRatios: [...IMAGE_ASPECT_RATIOS],
|
|
@@ -3407,6 +3499,36 @@ function apply(ctx) {
|
|
|
3407
3499
|
}
|
|
3408
3500
|
}
|
|
3409
3501
|
}));
|
|
3502
|
+
webCtx.effect(() => webCtx.webServer.register({
|
|
3503
|
+
kind: "exact",
|
|
3504
|
+
path: MINIMAX_CAPABILITY_PATH,
|
|
3505
|
+
handler: async (req, res) => {
|
|
3506
|
+
if (!isLoopback(req.headers.host)) {
|
|
3507
|
+
res.writeHead(403, { "Content-Type": "application/json" });
|
|
3508
|
+
res.end(JSON.stringify({ error: "loopback only" }));
|
|
3509
|
+
return;
|
|
3510
|
+
}
|
|
3511
|
+
try {
|
|
3512
|
+
const raw = new URL(req.url ?? "/", "http://127.0.0.1").searchParams.get("report") ?? "";
|
|
3513
|
+
let findings = {};
|
|
3514
|
+
try {
|
|
3515
|
+
const parsed = JSON.parse(raw);
|
|
3516
|
+
if (typeof parsed === "object" && parsed !== null) for (const [key, value] of Object.entries(parsed)) findings[key] = String(value);
|
|
3517
|
+
} catch {
|
|
3518
|
+
findings = { report: "unreadable" };
|
|
3519
|
+
}
|
|
3520
|
+
capability = {
|
|
3521
|
+
at: Date.now(),
|
|
3522
|
+
findings
|
|
3523
|
+
};
|
|
3524
|
+
res.writeHead(204);
|
|
3525
|
+
res.end();
|
|
3526
|
+
} catch (error) {
|
|
3527
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
3528
|
+
res.end(JSON.stringify({ error: error instanceof Error ? error.message : String(error) }));
|
|
3529
|
+
}
|
|
3530
|
+
}
|
|
3531
|
+
}));
|
|
3410
3532
|
webCtx.effect(() => webCtx.webServer.register({
|
|
3411
3533
|
kind: "exact",
|
|
3412
3534
|
path: MINIMAX_GENERATE_PATH,
|
|
@@ -3470,4 +3592,4 @@ async function readRequestBody(req) {
|
|
|
3470
3592
|
return text;
|
|
3471
3593
|
}
|
|
3472
3594
|
//#endregion
|
|
3473
|
-
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.
|
|
5
|
+
"version": "0.6.5",
|
|
6
6
|
"author": "JeffTsuiCUG",
|
|
7
7
|
"contributors": [
|
|
8
8
|
{
|