dsh-connect-minimaxcode 0.2.3 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +113 -27
- package/lib/client.js +317 -0
- package/lib/index.d.ts +351 -3
- package/lib/index.js +736 -19
- package/package.json +16 -5
package/lib/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
|
-
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { readFile, rename, writeFile } from "node:fs/promises";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { createProvider } from "@earendil-works/pi-ai";
|
|
6
6
|
import { anthropicMessagesApi } from "@earendil-works/pi-ai/api/anthropic-messages.lazy";
|
|
7
|
+
import { LlmAdapter, resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
|
|
7
8
|
import { PiAiAdapter } from "@deepseek-ai/dsh-llm-pi-ai";
|
|
8
|
-
import { resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
|
|
9
9
|
//#region src/auth.ts
|
|
10
10
|
/**
|
|
11
11
|
* Reading the MiniMax Code desktop app's own sign-in.
|
|
@@ -494,6 +494,65 @@ function toPiModel(model, baseUrl, authorization) {
|
|
|
494
494
|
};
|
|
495
495
|
}
|
|
496
496
|
/**
|
|
497
|
+
* An adapter that reports what each request used.
|
|
498
|
+
*
|
|
499
|
+
* The usage counters ride on the stream the gateway already sends, so
|
|
500
|
+
* measuring costs nothing extra: the chunk is copied out of the stream and
|
|
501
|
+
* handed to the callback, and the chunk itself still reaches the host
|
|
502
|
+
* unchanged. Both entry points are covered — the plain stream and the one a
|
|
503
|
+
* prepared call dispatches through — because the host may use either.
|
|
504
|
+
*/
|
|
505
|
+
var MeteredAdapter = class extends LlmAdapter {
|
|
506
|
+
#inner;
|
|
507
|
+
#onUsage;
|
|
508
|
+
constructor(inner, onUsage) {
|
|
509
|
+
super();
|
|
510
|
+
this.#inner = inner;
|
|
511
|
+
this.#onUsage = onUsage;
|
|
512
|
+
}
|
|
513
|
+
providerInfo(provider) {
|
|
514
|
+
return this.#inner.providerInfo(provider);
|
|
515
|
+
}
|
|
516
|
+
providerRetryPolicy(provider) {
|
|
517
|
+
return this.#inner.providerRetryPolicy(provider);
|
|
518
|
+
}
|
|
519
|
+
listModels(provider) {
|
|
520
|
+
return this.#inner.listModels(provider);
|
|
521
|
+
}
|
|
522
|
+
resolveModel(provider, model, signal) {
|
|
523
|
+
return this.#inner.resolveModel(provider, model, signal);
|
|
524
|
+
}
|
|
525
|
+
async prepareCall(provider, model, signal) {
|
|
526
|
+
const prepared = await this.#inner.prepareCall(provider, model, signal);
|
|
527
|
+
return {
|
|
528
|
+
...prepared,
|
|
529
|
+
stream: (options) => this.#metered(prepared.stream(options), model)
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
stream(options) {
|
|
533
|
+
return this.#metered(this.#inner.stream(options), options.model);
|
|
534
|
+
}
|
|
535
|
+
/** Copy the usage out of a stream, leaving the stream itself untouched. */
|
|
536
|
+
#metered(chunks, model) {
|
|
537
|
+
const onUsage = this.#onUsage;
|
|
538
|
+
return { async *[Symbol.asyncIterator]() {
|
|
539
|
+
for await (const chunk of chunks) {
|
|
540
|
+
if (chunk.type === "usage") {
|
|
541
|
+
const usage = chunk.usage;
|
|
542
|
+
onUsage(model, {
|
|
543
|
+
inputTokens: usage.inputTokens,
|
|
544
|
+
outputTokens: usage.outputTokens,
|
|
545
|
+
cacheReadTokens: usage.cacheReadTokens ?? 0,
|
|
546
|
+
cacheWriteTokens: usage.cacheWriteTokens ?? 0,
|
|
547
|
+
reasoningTokens: usage.reasoningTokens ?? 0
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
yield chunk;
|
|
551
|
+
}
|
|
552
|
+
} };
|
|
553
|
+
}
|
|
554
|
+
};
|
|
555
|
+
/**
|
|
497
556
|
* Build the DSH adapter that serves the catalog.
|
|
498
557
|
*
|
|
499
558
|
* The model list is rebuilt on demand rather than snapshotted, which is what
|
|
@@ -550,12 +609,13 @@ function createMinimaxCodeAdapter(options) {
|
|
|
550
609
|
return /* @__PURE__ */ new Map([[MINIMAXCODE_PROVIDER_ID, route]]);
|
|
551
610
|
};
|
|
552
611
|
let profiles = buildProfiles();
|
|
612
|
+
const inner = new PiAiAdapter({
|
|
613
|
+
profiles: () => profiles,
|
|
614
|
+
resolveApiKey: options.resolveApiKey,
|
|
615
|
+
auth: INERT_AUTH
|
|
616
|
+
});
|
|
553
617
|
return {
|
|
554
|
-
adapter: new
|
|
555
|
-
profiles: () => profiles,
|
|
556
|
-
resolveApiKey: options.resolveApiKey,
|
|
557
|
-
auth: INERT_AUTH
|
|
558
|
-
}),
|
|
618
|
+
adapter: options.onUsage === void 0 ? inner : new MeteredAdapter(inner, options.onUsage),
|
|
559
619
|
invalidate: () => {
|
|
560
620
|
profiles = buildProfiles();
|
|
561
621
|
}
|
|
@@ -586,7 +646,9 @@ function statusDocument(deps) {
|
|
|
586
646
|
catalogSource: deps.catalogSource,
|
|
587
647
|
...deps.catalogError === void 0 ? {} : { catalogError: deps.catalogError },
|
|
588
648
|
models: deps.models,
|
|
589
|
-
refreshing: deps.refreshing
|
|
649
|
+
refreshing: deps.refreshing,
|
|
650
|
+
...deps.credits === void 0 ? {} : { credits: deps.credits },
|
|
651
|
+
...deps.usage === void 0 ? {} : { usage: deps.usage }
|
|
590
652
|
};
|
|
591
653
|
if (auth.state === "expired") return {
|
|
592
654
|
...base,
|
|
@@ -611,6 +673,576 @@ function isLoopback(host) {
|
|
|
611
673
|
return name === "127.0.0.1" || name === "localhost" || name === "[::1]" || name === "::1";
|
|
612
674
|
}
|
|
613
675
|
//#endregion
|
|
676
|
+
//#region src/credits.ts
|
|
677
|
+
/**
|
|
678
|
+
* The MiniMax Code account's credit balance.
|
|
679
|
+
*
|
|
680
|
+
* Two credentials exist on this machine and they are not interchangeable:
|
|
681
|
+
*
|
|
682
|
+
* - `local-runtime.auth.json` holds a JWT the model gateway accepts. It cannot
|
|
683
|
+
* read account data: every credit endpoint answers `401`.
|
|
684
|
+
* - `auth/prod/<region>/mcode-public/auth.json` holds the desktop app's OAuth
|
|
685
|
+
* token (`mmoat_`). That is the credential the account endpoints accept.
|
|
686
|
+
*
|
|
687
|
+
* The balance itself comes from the membership endpoint the desktop app calls
|
|
688
|
+
* itself: `POST /matrix/api/v1/commerce/get_membership_info`, whose response
|
|
689
|
+
* carries `op_credit_summary` — the same three numbers the app's own settings
|
|
690
|
+
* page renders ("remaining credits", free vs purchased).
|
|
691
|
+
*
|
|
692
|
+
* Nothing here performs a login. The plugin may refresh an *expired* token
|
|
693
|
+
* with the refresh token the app already stored, and writes the rotated pair
|
|
694
|
+
* back in the app's own format so the app keeps working; it never starts a
|
|
695
|
+
* sign-in, never revokes, and never touches the app's accounts.
|
|
696
|
+
*
|
|
697
|
+
* @module dsh-connect-minimaxcode/credits
|
|
698
|
+
*/
|
|
699
|
+
/** The desktop app's own OAuth client id. */
|
|
700
|
+
const MINIMAX_OAUTH_CLIENT_ID = "mcode-public";
|
|
701
|
+
/** The scope the app requests. */
|
|
702
|
+
const MINIMAX_OAUTH_SCOPES = ["agent.default"];
|
|
703
|
+
/** The audience the app's tokens are issued for. */
|
|
704
|
+
const MINIMAX_OAUTH_AUDIENCE = "agent-backend";
|
|
705
|
+
/** Authorization server per region. */
|
|
706
|
+
const MINIMAX_AUTH_HOSTS = {
|
|
707
|
+
cn: "https://account.minimax.cn",
|
|
708
|
+
io: "https://account.minimax.io",
|
|
709
|
+
com: "https://account.minimaxi.com"
|
|
710
|
+
};
|
|
711
|
+
/** Gateway serving the account endpoints per region. */
|
|
712
|
+
const MINIMAX_ACCOUNT_HOSTS = {
|
|
713
|
+
cn: "https://agent.minimax.cn",
|
|
714
|
+
io: "https://agent.minimax.io",
|
|
715
|
+
com: "https://agent.minimaxi.com"
|
|
716
|
+
};
|
|
717
|
+
/** Membership endpoint: carries the credit summary. */
|
|
718
|
+
const MEMBERSHIP_PATH = "/matrix/api/v1/commerce/get_membership_info";
|
|
719
|
+
/** Credit ledger endpoint: per-grant amounts and expiry. */
|
|
720
|
+
const CREDIT_DETAILS_PATH = "/minimax-cloud/api/v1/credit/details";
|
|
721
|
+
/** OAuth token endpoint. */
|
|
722
|
+
const TOKEN_PATH = "/oauth2/token";
|
|
723
|
+
/** Absolute path of the app's credential file for a region. */
|
|
724
|
+
function oauthAuthPath(region, env = process.env) {
|
|
725
|
+
return join(dataDir(env), "auth", "prod", region, "mcode-public", "auth.json");
|
|
726
|
+
}
|
|
727
|
+
/** Milliseconds until the credential expires; negative once it has. */
|
|
728
|
+
function expiresInMs$1(credential, now = Date.now()) {
|
|
729
|
+
return credential.expiresAtMs === void 0 ? void 0 : credential.expiresAtMs - now;
|
|
730
|
+
}
|
|
731
|
+
/** Whether the credential is close enough to expiry to be worth refreshing. */
|
|
732
|
+
function needsRefresh(credential, now = Date.now()) {
|
|
733
|
+
const remaining = expiresInMs$1(credential, now);
|
|
734
|
+
return remaining !== void 0 && remaining <= 12e4;
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* Parse the app's credential document.
|
|
738
|
+
*
|
|
739
|
+
* The record key is generated per install, so the record is found by value
|
|
740
|
+
* rather than by a hard-coded name.
|
|
741
|
+
*/
|
|
742
|
+
function parseOAuthDocument(text, now = Date.now()) {
|
|
743
|
+
let parsed;
|
|
744
|
+
try {
|
|
745
|
+
parsed = JSON.parse(text);
|
|
746
|
+
} catch {
|
|
747
|
+
return {
|
|
748
|
+
state: "signed-out",
|
|
749
|
+
reason: "unreadable"
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
const records = parsed.records;
|
|
753
|
+
if (typeof records !== "object" || records === null) return {
|
|
754
|
+
state: "signed-out",
|
|
755
|
+
reason: "not-signed-in"
|
|
756
|
+
};
|
|
757
|
+
const record = Object.values(records)[0];
|
|
758
|
+
if (typeof record !== "object" || record === null) return {
|
|
759
|
+
state: "signed-out",
|
|
760
|
+
reason: "not-signed-in"
|
|
761
|
+
};
|
|
762
|
+
const entry = record;
|
|
763
|
+
const token = entry.accessToken;
|
|
764
|
+
if (typeof token !== "string" || token.length === 0) return {
|
|
765
|
+
state: "signed-out",
|
|
766
|
+
reason: "not-signed-in"
|
|
767
|
+
};
|
|
768
|
+
const expiresAtMs = typeof entry.expiresAtMs === "number" ? entry.expiresAtMs : void 0;
|
|
769
|
+
const credential = {
|
|
770
|
+
token,
|
|
771
|
+
refreshToken: typeof entry.refreshToken === "string" ? entry.refreshToken : void 0,
|
|
772
|
+
expiresAtMs,
|
|
773
|
+
clientId: typeof entry.clientId === "string" ? entry.clientId : MINIMAX_OAUTH_CLIENT_ID,
|
|
774
|
+
scopes: Array.isArray(entry.scopes) ? entry.scopes.filter((s) => typeof s === "string") : [...MINIMAX_OAUTH_SCOPES],
|
|
775
|
+
audience: typeof entry.audience === "string" ? entry.audience : MINIMAX_OAUTH_AUDIENCE,
|
|
776
|
+
generation: typeof entry.generation === "number" ? entry.generation : void 0
|
|
777
|
+
};
|
|
778
|
+
return expiresAtMs !== void 0 && expiresAtMs <= now ? {
|
|
779
|
+
state: "expired",
|
|
780
|
+
credential
|
|
781
|
+
} : {
|
|
782
|
+
state: "signed-in",
|
|
783
|
+
credential
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
/**
|
|
787
|
+
* Read the desktop app's OAuth credential.
|
|
788
|
+
*
|
|
789
|
+
* Every failure is a state, not an exception: an app that is not installed, or
|
|
790
|
+
* not signed in, is a normal condition the UI renders rather than an error the
|
|
791
|
+
* host logs on every request.
|
|
792
|
+
*/
|
|
793
|
+
async function readOAuthCredential(region, env = process.env, now = Date.now()) {
|
|
794
|
+
let text;
|
|
795
|
+
try {
|
|
796
|
+
text = await readFile(oauthAuthPath(region, env), "utf8");
|
|
797
|
+
} catch {
|
|
798
|
+
return {
|
|
799
|
+
state: "signed-out",
|
|
800
|
+
reason: "app-missing"
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
return parseOAuthDocument(text, now);
|
|
804
|
+
}
|
|
805
|
+
/**
|
|
806
|
+
* Exchange the stored refresh token for a fresh access token.
|
|
807
|
+
*
|
|
808
|
+
* The authorization server rotates the refresh token, so the new pair is
|
|
809
|
+
* written back into the app's own file, in the app's own format, with an
|
|
810
|
+
* atomic replace. `generation` is left untouched on purpose: the app uses it
|
|
811
|
+
* to decide whether a stored credential still belongs to its own login, and
|
|
812
|
+
* raising it would make the app believe it had been signed in elsewhere.
|
|
813
|
+
*/
|
|
814
|
+
async function refreshOAuthCredential(region, credential, env = process.env, now = Date.now(), fetchImpl = fetch) {
|
|
815
|
+
if (credential.refreshToken === void 0) return {
|
|
816
|
+
state: "expired",
|
|
817
|
+
credential
|
|
818
|
+
};
|
|
819
|
+
const body = new URLSearchParams({
|
|
820
|
+
grant_type: "refresh_token",
|
|
821
|
+
refresh_token: credential.refreshToken,
|
|
822
|
+
client_id: credential.clientId,
|
|
823
|
+
scope: credential.scopes.join(" "),
|
|
824
|
+
audience: credential.audience
|
|
825
|
+
});
|
|
826
|
+
const response = await fetchImpl(`${MINIMAX_AUTH_HOSTS[region]}${TOKEN_PATH}`, {
|
|
827
|
+
method: "POST",
|
|
828
|
+
headers: {
|
|
829
|
+
accept: "application/json",
|
|
830
|
+
"content-type": "application/x-www-form-urlencoded"
|
|
831
|
+
},
|
|
832
|
+
body
|
|
833
|
+
});
|
|
834
|
+
if (!response.ok) throw new Error(`OAuth refresh failed: HTTP ${response.status}`);
|
|
835
|
+
const document = await response.json();
|
|
836
|
+
const token = document.access_token;
|
|
837
|
+
if (typeof token !== "string" || token.length === 0) throw new Error("OAuth refresh returned no access token");
|
|
838
|
+
const expiresInSec = typeof document.expires_in === "number" ? document.expires_in : void 0;
|
|
839
|
+
const next = {
|
|
840
|
+
...credential,
|
|
841
|
+
token,
|
|
842
|
+
refreshToken: typeof document.refresh_token === "string" ? document.refresh_token : credential.refreshToken,
|
|
843
|
+
expiresAtMs: expiresInSec === void 0 ? void 0 : now + expiresInSec * 1e3
|
|
844
|
+
};
|
|
845
|
+
await writeOAuthCredential(region, next, env);
|
|
846
|
+
return {
|
|
847
|
+
state: "signed-in",
|
|
848
|
+
credential: next
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
/**
|
|
852
|
+
* Write a rotated credential back into the app's store.
|
|
853
|
+
*
|
|
854
|
+
* The document is rewritten as a whole so the app's own reader keeps seeing
|
|
855
|
+
* the shape it wrote, and the replace is atomic so a crash mid-write cannot
|
|
856
|
+
* leave a truncated credential file behind.
|
|
857
|
+
*/
|
|
858
|
+
async function writeOAuthCredential(region, credential, env = process.env) {
|
|
859
|
+
const path = oauthAuthPath(region, env);
|
|
860
|
+
let document;
|
|
861
|
+
try {
|
|
862
|
+
document = JSON.parse(await readFile(path, "utf8"));
|
|
863
|
+
} catch {
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
const records = document.records;
|
|
867
|
+
if (typeof records !== "object" || records === null) return;
|
|
868
|
+
const first = Object.entries(records)[0];
|
|
869
|
+
if (first === void 0) return;
|
|
870
|
+
const [key, record] = first;
|
|
871
|
+
if (typeof record !== "object" || record === null) return;
|
|
872
|
+
const existing = record;
|
|
873
|
+
const updated = {
|
|
874
|
+
...existing,
|
|
875
|
+
accessToken: credential.token,
|
|
876
|
+
refreshToken: credential.refreshToken ?? existing.refreshToken,
|
|
877
|
+
tokenType: "Bearer",
|
|
878
|
+
clientId: credential.clientId,
|
|
879
|
+
scopes: credential.scopes,
|
|
880
|
+
audience: credential.audience,
|
|
881
|
+
expiresAtMs: credential.expiresAtMs
|
|
882
|
+
};
|
|
883
|
+
const payload = {
|
|
884
|
+
...document,
|
|
885
|
+
records: {
|
|
886
|
+
...records,
|
|
887
|
+
[key]: updated
|
|
888
|
+
}
|
|
889
|
+
};
|
|
890
|
+
const temporary = `${path}.dsh-connect-minimaxcode.tmp`;
|
|
891
|
+
await writeFile(temporary, `${JSON.stringify(payload, null, 2)}\n`, { mode: 384 });
|
|
892
|
+
await rename(temporary, path);
|
|
893
|
+
}
|
|
894
|
+
/**
|
|
895
|
+
* Read the credential, refreshing it when it is close to expiry.
|
|
896
|
+
*
|
|
897
|
+
* A refresh is only attempted for a credential the app already stored, and
|
|
898
|
+
* only when it is actually stale: a fresh token is left alone, so the plugin
|
|
899
|
+
* never competes with the app for the same grant.
|
|
900
|
+
*/
|
|
901
|
+
async function ensureOAuthCredential(region, env = process.env, now = Date.now(), fetchImpl = fetch) {
|
|
902
|
+
const state = await readOAuthCredential(region, env, now);
|
|
903
|
+
if (state.state === "signed-in" && !needsRefresh(state.credential, now)) return state;
|
|
904
|
+
if (state.state === "signed-out") return state;
|
|
905
|
+
try {
|
|
906
|
+
return await refreshOAuthCredential(region, state.credential, env, now, fetchImpl);
|
|
907
|
+
} catch {
|
|
908
|
+
return state;
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
/**
|
|
912
|
+
* Read the credit summary out of a membership response.
|
|
913
|
+
*
|
|
914
|
+
* The app's settings page prefers the summary and falls back to the flat
|
|
915
|
+
* balance fields, so the same fallback chain is used here.
|
|
916
|
+
*/
|
|
917
|
+
function parseCreditSummary(document) {
|
|
918
|
+
if (typeof document !== "object" || document === null) return void 0;
|
|
919
|
+
const record = document;
|
|
920
|
+
const summary = record.op_credit_summary;
|
|
921
|
+
if (typeof summary === "object" && summary !== null) {
|
|
922
|
+
const entry = summary;
|
|
923
|
+
const total = toNumber(entry.total_remaining_amount);
|
|
924
|
+
if (total !== void 0) {
|
|
925
|
+
const purchased = toNumber(entry.purchased_remaining_amount) ?? 0;
|
|
926
|
+
return {
|
|
927
|
+
total,
|
|
928
|
+
purchased,
|
|
929
|
+
free: toNumber(entry.free_remaining_amount) ?? total - purchased
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
const flat = toNumber(record.opcredit_balance) ?? toNumber(record.total_remains_credit);
|
|
934
|
+
return flat === void 0 ? void 0 : {
|
|
935
|
+
total: flat,
|
|
936
|
+
purchased: 0,
|
|
937
|
+
free: flat
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
/** Read a numeric field the upstream may send as a string. */
|
|
941
|
+
function toNumber(value) {
|
|
942
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
943
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
944
|
+
const parsed = Number(value);
|
|
945
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
/**
|
|
949
|
+
* Fetch the current credit balance.
|
|
950
|
+
*
|
|
951
|
+
* @returns The balance, or `undefined` when the account has no credit summary.
|
|
952
|
+
*/
|
|
953
|
+
async function fetchCreditSummary(credential, region, fetchImpl = fetch) {
|
|
954
|
+
const response = await fetchImpl(`${MINIMAX_ACCOUNT_HOSTS[region]}${MEMBERSHIP_PATH}`, {
|
|
955
|
+
method: "POST",
|
|
956
|
+
headers: {
|
|
957
|
+
"content-type": "application/json",
|
|
958
|
+
authorization: `Bearer ${credential.token}`
|
|
959
|
+
},
|
|
960
|
+
body: "{}"
|
|
961
|
+
});
|
|
962
|
+
if (!response.ok) throw new Error(`membership request failed: HTTP ${response.status}`);
|
|
963
|
+
return parseCreditSummary(await response.json());
|
|
964
|
+
}
|
|
965
|
+
/**
|
|
966
|
+
* Read the credit lots out of a ledger response.
|
|
967
|
+
*
|
|
968
|
+
* Lots that carry no expiry are ignored: a lot without an expiry date would
|
|
969
|
+
* otherwise be treated as one that never expires.
|
|
970
|
+
*/
|
|
971
|
+
function parseCreditLots(document) {
|
|
972
|
+
if (typeof document !== "object" || document === null) return [];
|
|
973
|
+
const details = document.details;
|
|
974
|
+
if (!Array.isArray(details)) return [];
|
|
975
|
+
const lots = [];
|
|
976
|
+
for (const entry of details) {
|
|
977
|
+
if (typeof entry !== "object" || entry === null) continue;
|
|
978
|
+
const record = entry;
|
|
979
|
+
const expireAtMs = toNumber(record.expire_at_ms);
|
|
980
|
+
if (expireAtMs === void 0) continue;
|
|
981
|
+
const granted = toNumber(record.granted_amount) ?? 0;
|
|
982
|
+
const remaining = toNumber(record.remaining_amount) ?? 0;
|
|
983
|
+
lots.push({
|
|
984
|
+
granted,
|
|
985
|
+
remaining,
|
|
986
|
+
consumed: toNumber(record.consumed_amount) ?? Math.max(0, granted - remaining),
|
|
987
|
+
grantedAtMs: toNumber(record.granted_at_ms) ?? 0,
|
|
988
|
+
expireAtMs,
|
|
989
|
+
creditType: toNumber(record.credit_type)
|
|
990
|
+
});
|
|
991
|
+
}
|
|
992
|
+
return lots.sort((a, b) => a.expireAtMs - b.expireAtMs);
|
|
993
|
+
}
|
|
994
|
+
/**
|
|
995
|
+
* Fetch the credit ledger.
|
|
996
|
+
*
|
|
997
|
+
* The ledger is paginated, so every page is read. The number of pages comes
|
|
998
|
+
* from the account's own `total_count` rather than from a guess, and a lot
|
|
999
|
+
* count that stops growing ends the walk.
|
|
1000
|
+
*/
|
|
1001
|
+
async function fetchCreditLots(credential, region, fetchImpl = fetch) {
|
|
1002
|
+
const pageSize = 100;
|
|
1003
|
+
const lots = [];
|
|
1004
|
+
let page = 1;
|
|
1005
|
+
let total = Number.POSITIVE_INFINITY;
|
|
1006
|
+
while (lots.length < total && page <= 50) {
|
|
1007
|
+
const response = await fetchImpl(`${MINIMAX_ACCOUNT_HOSTS[region]}${CREDIT_DETAILS_PATH}?page=${page}&page_size=${pageSize}`, { headers: { authorization: `Bearer ${credential.token}` } });
|
|
1008
|
+
if (!response.ok) throw new Error(`credit ledger request failed: HTTP ${response.status}`);
|
|
1009
|
+
const document = await response.json();
|
|
1010
|
+
const batch = parseCreditLots(document);
|
|
1011
|
+
lots.push(...batch);
|
|
1012
|
+
const count = toNumber(document.total_count);
|
|
1013
|
+
if (count !== void 0) total = count;
|
|
1014
|
+
if (batch.length < pageSize) break;
|
|
1015
|
+
page += 1;
|
|
1016
|
+
}
|
|
1017
|
+
return lots;
|
|
1018
|
+
}
|
|
1019
|
+
/**
|
|
1020
|
+
* Summarize what is about to expire.
|
|
1021
|
+
*
|
|
1022
|
+
* Only lots that still hold credits count: a lot that was granted and used up
|
|
1023
|
+
* expires with nothing to lose, and counting it would make every account look
|
|
1024
|
+
* like it is about to lose credits.
|
|
1025
|
+
*/
|
|
1026
|
+
function summarizeExpiry(lots, now = Date.now()) {
|
|
1027
|
+
const alive = lots.filter((lot) => lot.remaining > 0 && lot.expireAtMs > now);
|
|
1028
|
+
const within = (days) => alive.filter((lot) => lot.expireAtMs - now <= days * 864e5);
|
|
1029
|
+
const week = within(7);
|
|
1030
|
+
const month = within(30);
|
|
1031
|
+
return {
|
|
1032
|
+
expiring: week.reduce((sum, lot) => sum + lot.remaining, 0),
|
|
1033
|
+
expiringLots: week.length,
|
|
1034
|
+
expiringSoon: month.reduce((sum, lot) => sum + lot.remaining, 0),
|
|
1035
|
+
nextExpiryAtMs: alive[0]?.expireAtMs,
|
|
1036
|
+
upcoming: alive.slice(0, 5).map((lot) => ({
|
|
1037
|
+
amount: lot.remaining,
|
|
1038
|
+
expireAtMs: lot.expireAtMs
|
|
1039
|
+
})),
|
|
1040
|
+
totalRemaining: lots.reduce((sum, lot) => sum + lot.remaining, 0)
|
|
1041
|
+
};
|
|
1042
|
+
}
|
|
1043
|
+
/**
|
|
1044
|
+
* Keeps the current balance and the change between reads.
|
|
1045
|
+
*
|
|
1046
|
+
* The first read has nothing to compare against, so it reports no movement
|
|
1047
|
+
* rather than pretending the whole balance was spent.
|
|
1048
|
+
*/
|
|
1049
|
+
var MinimaxCredits = class {
|
|
1050
|
+
#snapshot;
|
|
1051
|
+
#error;
|
|
1052
|
+
#lots = [];
|
|
1053
|
+
#expiry;
|
|
1054
|
+
#ledgerAtMs;
|
|
1055
|
+
#ledgerError;
|
|
1056
|
+
/** The balance as last read. */
|
|
1057
|
+
get snapshot() {
|
|
1058
|
+
return this.#snapshot;
|
|
1059
|
+
}
|
|
1060
|
+
/** Why the last read failed, when one did. */
|
|
1061
|
+
get error() {
|
|
1062
|
+
return this.#error;
|
|
1063
|
+
}
|
|
1064
|
+
/** The credit lots as last read. */
|
|
1065
|
+
get lots() {
|
|
1066
|
+
return this.#lots;
|
|
1067
|
+
}
|
|
1068
|
+
/** What the lots say about expiry, when the ledger could be read. */
|
|
1069
|
+
get expiry() {
|
|
1070
|
+
return this.#expiry;
|
|
1071
|
+
}
|
|
1072
|
+
/** When the ledger was last read. */
|
|
1073
|
+
get ledgerAtMs() {
|
|
1074
|
+
return this.#ledgerAtMs;
|
|
1075
|
+
}
|
|
1076
|
+
/** Why the ledger could not be read, when it could not. */
|
|
1077
|
+
get ledgerError() {
|
|
1078
|
+
return this.#ledgerError;
|
|
1079
|
+
}
|
|
1080
|
+
/**
|
|
1081
|
+
* Read the balance and record how much was consumed since the last read.
|
|
1082
|
+
*
|
|
1083
|
+
* The credit ledger is read alongside it, but a ledger that cannot be read
|
|
1084
|
+
* never hides the balance: the two come from different endpoints and only
|
|
1085
|
+
* the balance is essential.
|
|
1086
|
+
*
|
|
1087
|
+
* @returns Whether the balance changed.
|
|
1088
|
+
*/
|
|
1089
|
+
async refresh(credential, region, fetchImpl = fetch, now = Date.now()) {
|
|
1090
|
+
let changed = false;
|
|
1091
|
+
try {
|
|
1092
|
+
const summary = await fetchCreditSummary(credential, region, fetchImpl);
|
|
1093
|
+
if (summary === void 0) this.#error = "membership response carried no credit summary";
|
|
1094
|
+
else {
|
|
1095
|
+
const previous = this.#snapshot;
|
|
1096
|
+
this.#snapshot = {
|
|
1097
|
+
summary,
|
|
1098
|
+
fetchedAtMs: now,
|
|
1099
|
+
consumed: previous === void 0 ? 0 : previous.summary.total - summary.total
|
|
1100
|
+
};
|
|
1101
|
+
this.#error = void 0;
|
|
1102
|
+
changed = previous === void 0 || previous.summary.total !== summary.total;
|
|
1103
|
+
}
|
|
1104
|
+
} catch (error) {
|
|
1105
|
+
this.#error = error instanceof Error ? error.message : String(error);
|
|
1106
|
+
}
|
|
1107
|
+
await this.refreshLedger(credential, region, fetchImpl, now);
|
|
1108
|
+
return changed;
|
|
1109
|
+
}
|
|
1110
|
+
/**
|
|
1111
|
+
* Read the credit ledger and work out what is about to expire.
|
|
1112
|
+
*
|
|
1113
|
+
* A failure here is remembered but never thrown: the balance is what the
|
|
1114
|
+
* user needs most, and a ledger that is temporarily unavailable must not
|
|
1115
|
+
* take it down with it.
|
|
1116
|
+
*/
|
|
1117
|
+
async refreshLedger(credential, region, fetchImpl = fetch, now = Date.now()) {
|
|
1118
|
+
try {
|
|
1119
|
+
const lots = await fetchCreditLots(credential, region, fetchImpl);
|
|
1120
|
+
this.#lots = lots;
|
|
1121
|
+
this.#expiry = summarizeExpiry(lots, now);
|
|
1122
|
+
this.#ledgerAtMs = now;
|
|
1123
|
+
this.#ledgerError = void 0;
|
|
1124
|
+
} catch (error) {
|
|
1125
|
+
this.#ledgerError = error instanceof Error ? error.message : String(error);
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
};
|
|
1129
|
+
//#endregion
|
|
1130
|
+
//#region src/usage.ts
|
|
1131
|
+
/** An empty usage record. */
|
|
1132
|
+
const EMPTY_USAGE = {
|
|
1133
|
+
inputTokens: 0,
|
|
1134
|
+
outputTokens: 0,
|
|
1135
|
+
cacheReadTokens: 0,
|
|
1136
|
+
cacheWriteTokens: 0,
|
|
1137
|
+
reasoningTokens: 0
|
|
1138
|
+
};
|
|
1139
|
+
/** The published domestic prices of the models this plugin serves. */
|
|
1140
|
+
const MODEL_PRICES = {
|
|
1141
|
+
"MiniMax-M3.1-Flash": {
|
|
1142
|
+
input: 1,
|
|
1143
|
+
output: 4,
|
|
1144
|
+
cacheRead: .1
|
|
1145
|
+
},
|
|
1146
|
+
"MiniMax-M3": {
|
|
1147
|
+
input: 2.1,
|
|
1148
|
+
output: 8.4,
|
|
1149
|
+
cacheRead: .42
|
|
1150
|
+
}
|
|
1151
|
+
};
|
|
1152
|
+
/**
|
|
1153
|
+
* The price for one model at one input size.
|
|
1154
|
+
*
|
|
1155
|
+
* M3 is billed in two tiers: requests above 512k input tokens cost double.
|
|
1156
|
+
* The tier is decided by the billed input, which is the uncached input plus
|
|
1157
|
+
* the cache read.
|
|
1158
|
+
*/
|
|
1159
|
+
function priceFor(model, billedInputTokens) {
|
|
1160
|
+
const price = MODEL_PRICES[model];
|
|
1161
|
+
if (price === void 0) return void 0;
|
|
1162
|
+
if (model === "MiniMax-M3" && billedInputTokens > 512e3) return {
|
|
1163
|
+
input: price.input * 2,
|
|
1164
|
+
output: price.output * 2,
|
|
1165
|
+
cacheRead: price.cacheRead * 2
|
|
1166
|
+
};
|
|
1167
|
+
return price;
|
|
1168
|
+
}
|
|
1169
|
+
/**
|
|
1170
|
+
* The equivalent amount of one request, in 元.
|
|
1171
|
+
*
|
|
1172
|
+
* Returns `undefined` for a model without a published price, rather than
|
|
1173
|
+
* pretending the request was free.
|
|
1174
|
+
*/
|
|
1175
|
+
function estimateCny(model, usage) {
|
|
1176
|
+
const price = priceFor(model, usage.inputTokens + usage.cacheReadTokens);
|
|
1177
|
+
if (price === void 0) return void 0;
|
|
1178
|
+
const perMillion = (tokens, rate) => tokens / 1e6 * rate;
|
|
1179
|
+
return perMillion(usage.inputTokens, price.input) + perMillion(usage.outputTokens, price.output) + perMillion(usage.cacheReadTokens, price.cacheRead);
|
|
1180
|
+
}
|
|
1181
|
+
/**
|
|
1182
|
+
* Accumulates what the sessions routed through this plugin have used.
|
|
1183
|
+
*
|
|
1184
|
+
* The meter is the plugin's own view: it counts the requests this host made,
|
|
1185
|
+
* with the counters the gateway returned. It is not the account's total, and
|
|
1186
|
+
* it is not the credit balance — the balance comes from the account API.
|
|
1187
|
+
*/
|
|
1188
|
+
var UsageMeter = class {
|
|
1189
|
+
#records = [];
|
|
1190
|
+
#byModel = /* @__PURE__ */ new Map();
|
|
1191
|
+
/** Every recorded request, oldest first. */
|
|
1192
|
+
get records() {
|
|
1193
|
+
return this.#records;
|
|
1194
|
+
}
|
|
1195
|
+
/** The most recent request. */
|
|
1196
|
+
get last() {
|
|
1197
|
+
return this.#records[this.#records.length - 1];
|
|
1198
|
+
}
|
|
1199
|
+
/** Record one request's usage. */
|
|
1200
|
+
record(model, usage) {
|
|
1201
|
+
const entry = {
|
|
1202
|
+
model,
|
|
1203
|
+
usage: { ...usage },
|
|
1204
|
+
cny: estimateCny(model, usage)
|
|
1205
|
+
};
|
|
1206
|
+
this.#records.push(entry);
|
|
1207
|
+
const totals = this.#byModel.get(model) ?? { ...EMPTY_USAGE };
|
|
1208
|
+
this.#byModel.set(model, {
|
|
1209
|
+
inputTokens: totals.inputTokens + usage.inputTokens,
|
|
1210
|
+
outputTokens: totals.outputTokens + usage.outputTokens,
|
|
1211
|
+
cacheReadTokens: totals.cacheReadTokens + usage.cacheReadTokens,
|
|
1212
|
+
cacheWriteTokens: totals.cacheWriteTokens + usage.cacheWriteTokens,
|
|
1213
|
+
reasoningTokens: totals.reasoningTokens + usage.reasoningTokens
|
|
1214
|
+
});
|
|
1215
|
+
return entry;
|
|
1216
|
+
}
|
|
1217
|
+
/** Totals across every recorded request. */
|
|
1218
|
+
totals() {
|
|
1219
|
+
let total = { ...EMPTY_USAGE };
|
|
1220
|
+
for (const usage of this.#byModel.values()) total = {
|
|
1221
|
+
inputTokens: total.inputTokens + usage.inputTokens,
|
|
1222
|
+
outputTokens: total.outputTokens + usage.outputTokens,
|
|
1223
|
+
cacheReadTokens: total.cacheReadTokens + usage.cacheReadTokens,
|
|
1224
|
+
cacheWriteTokens: total.cacheWriteTokens + usage.cacheWriteTokens,
|
|
1225
|
+
reasoningTokens: total.reasoningTokens + usage.reasoningTokens
|
|
1226
|
+
};
|
|
1227
|
+
return total;
|
|
1228
|
+
}
|
|
1229
|
+
/** Totals per model. */
|
|
1230
|
+
byModel() {
|
|
1231
|
+
return this.#byModel;
|
|
1232
|
+
}
|
|
1233
|
+
/** The equivalent amount of everything recorded, in 元. */
|
|
1234
|
+
estimatedCny() {
|
|
1235
|
+
let total = 0;
|
|
1236
|
+
for (const record of this.#records) if (record.cny !== void 0) total += record.cny;
|
|
1237
|
+
return total;
|
|
1238
|
+
}
|
|
1239
|
+
/** Forget everything recorded. */
|
|
1240
|
+
reset() {
|
|
1241
|
+
this.#records = [];
|
|
1242
|
+
this.#byModel.clear();
|
|
1243
|
+
}
|
|
1244
|
+
};
|
|
1245
|
+
//#endregion
|
|
614
1246
|
//#region src/index.ts
|
|
615
1247
|
/** Loader entry name; also the id the bundle patch inserts. */
|
|
616
1248
|
const name = "dsh-connect-minimaxcode";
|
|
@@ -624,6 +1256,13 @@ function pickRegion(preferred) {
|
|
|
624
1256
|
/** How often the card may pull a fresh catalog. */
|
|
625
1257
|
const CATALOG_TTL_MS = 3e5;
|
|
626
1258
|
/**
|
|
1259
|
+
* How often the account's credit balance may be re-read.
|
|
1260
|
+
*
|
|
1261
|
+
* The balance is a live counter upstream, but it is an account-wide value:
|
|
1262
|
+
* reading it too often buys nothing except load on the account API.
|
|
1263
|
+
*/
|
|
1264
|
+
const CREDIT_TTL_MS = 3e4;
|
|
1265
|
+
/**
|
|
627
1266
|
* Compose the plugin.
|
|
628
1267
|
*
|
|
629
1268
|
* The credential is re-read on every request rather than cached at start: the
|
|
@@ -634,10 +1273,14 @@ const CATALOG_TTL_MS = 3e5;
|
|
|
634
1273
|
*/
|
|
635
1274
|
function apply(ctx) {
|
|
636
1275
|
const catalog = new MinimaxCatalog();
|
|
1276
|
+
const credits = new MinimaxCredits();
|
|
1277
|
+
const meter = new UsageMeter();
|
|
637
1278
|
let region = "cn";
|
|
638
1279
|
let credential;
|
|
639
1280
|
let lastCatalogAtMs = 0;
|
|
1281
|
+
let lastCreditAtMs = 0;
|
|
640
1282
|
let refreshing = false;
|
|
1283
|
+
let creditState = "unavailable";
|
|
641
1284
|
const readNow = async () => {
|
|
642
1285
|
const state = await readCredential();
|
|
643
1286
|
if (state.state !== "signed-in") {
|
|
@@ -659,25 +1302,97 @@ function apply(ctx) {
|
|
|
659
1302
|
refreshing = false;
|
|
660
1303
|
}
|
|
661
1304
|
};
|
|
1305
|
+
/**
|
|
1306
|
+
* Read the account's credit balance, at most once per TTL.
|
|
1307
|
+
*
|
|
1308
|
+
* The account API needs the desktop app's own OAuth token, not the model
|
|
1309
|
+
* gateway's JWT, so this reads a second credential file. A stale token is
|
|
1310
|
+
* refreshed through the app's own grant and written back in the app's own
|
|
1311
|
+
* format, which keeps the app signed in.
|
|
1312
|
+
*/
|
|
1313
|
+
const refreshCredits = async () => {
|
|
1314
|
+
const now = Date.now();
|
|
1315
|
+
if (now - lastCreditAtMs < CREDIT_TTL_MS) return;
|
|
1316
|
+
lastCreditAtMs = now;
|
|
1317
|
+
const state = await ensureOAuthCredential(region, process.env, now);
|
|
1318
|
+
if (state.state === "signed-out") {
|
|
1319
|
+
creditState = "signed-out";
|
|
1320
|
+
return;
|
|
1321
|
+
}
|
|
1322
|
+
if (state.state === "expired") {
|
|
1323
|
+
creditState = "expired";
|
|
1324
|
+
return;
|
|
1325
|
+
}
|
|
1326
|
+
await credits.refresh(state.credential, region, fetch, now);
|
|
1327
|
+
creditState = credits.snapshot === void 0 ? "unavailable" : "ok";
|
|
1328
|
+
};
|
|
1329
|
+
/** The credit balance as the card and the browser half read it. */
|
|
1330
|
+
const buildCreditStatus = () => {
|
|
1331
|
+
const snapshot = credits.snapshot;
|
|
1332
|
+
const expiry = credits.expiry;
|
|
1333
|
+
if (snapshot === void 0) return {
|
|
1334
|
+
state: creditState,
|
|
1335
|
+
...credits.error === void 0 ? {} : { error: credits.error }
|
|
1336
|
+
};
|
|
1337
|
+
return {
|
|
1338
|
+
state: "ok",
|
|
1339
|
+
total: snapshot.summary.total,
|
|
1340
|
+
purchased: snapshot.summary.purchased,
|
|
1341
|
+
free: snapshot.summary.free,
|
|
1342
|
+
consumed: snapshot.consumed,
|
|
1343
|
+
updatedAtMs: snapshot.fetchedAtMs,
|
|
1344
|
+
...expiry === void 0 ? {} : {
|
|
1345
|
+
expiring: expiry.expiring,
|
|
1346
|
+
expiringLots: expiry.expiringLots,
|
|
1347
|
+
expiringSoon: expiry.expiringSoon,
|
|
1348
|
+
nextExpiryAtMs: expiry.nextExpiryAtMs,
|
|
1349
|
+
upcoming: expiry.upcoming
|
|
1350
|
+
},
|
|
1351
|
+
...credits.ledgerAtMs === void 0 ? {} : { ledgerAtMs: credits.ledgerAtMs },
|
|
1352
|
+
...credits.ledgerError === void 0 ? {} : { ledgerError: credits.ledgerError }
|
|
1353
|
+
};
|
|
1354
|
+
};
|
|
1355
|
+
/** What this host's own requests have used. */
|
|
1356
|
+
const buildUsageStatus = () => {
|
|
1357
|
+
const totals = meter.totals();
|
|
1358
|
+
const last = meter.last;
|
|
1359
|
+
return {
|
|
1360
|
+
inputTokens: totals.inputTokens,
|
|
1361
|
+
outputTokens: totals.outputTokens,
|
|
1362
|
+
cacheReadTokens: totals.cacheReadTokens,
|
|
1363
|
+
cacheWriteTokens: totals.cacheWriteTokens,
|
|
1364
|
+
reasoningTokens: totals.reasoningTokens,
|
|
1365
|
+
estimatedCny: meter.estimatedCny(),
|
|
1366
|
+
...last?.cny === void 0 ? {} : { lastCny: last.cny },
|
|
1367
|
+
...last === void 0 ? {} : { lastModel: last.model },
|
|
1368
|
+
requests: meter.records.length
|
|
1369
|
+
};
|
|
1370
|
+
};
|
|
662
1371
|
const { adapter, invalidate } = createMinimaxCodeAdapter({
|
|
663
1372
|
catalog,
|
|
664
1373
|
region: () => region,
|
|
665
1374
|
resolveApiKey: async () => (await readNow())?.token,
|
|
666
|
-
bearerToken: () => bearerToken()
|
|
1375
|
+
bearerToken: () => bearerToken(),
|
|
1376
|
+
onUsage: (model, usage) => {
|
|
1377
|
+
meter.record(model, usage);
|
|
1378
|
+
refreshCredits();
|
|
1379
|
+
}
|
|
667
1380
|
});
|
|
668
1381
|
ctx.llm.registerAdapter(["minimaxcode"], adapter);
|
|
669
1382
|
/** Build the document the card renders, without ever exposing the token. */
|
|
670
1383
|
const buildStatus = async () => {
|
|
671
1384
|
const state = await readCredential();
|
|
1385
|
+
const auth = state.state === "signed-in" ? {
|
|
1386
|
+
state: "signed-in",
|
|
1387
|
+
daysRemaining: Math.ceil(expiresInMs(state.credential.claims) / 864e5),
|
|
1388
|
+
expiry: expiryLevel(state.credential.claims)
|
|
1389
|
+
} : state.state === "expired" ? { state: "expired" } : {
|
|
1390
|
+
state: "signed-out",
|
|
1391
|
+
reason: state.reason
|
|
1392
|
+
};
|
|
1393
|
+
await refreshCredits();
|
|
672
1394
|
return statusDocument({
|
|
673
|
-
auth
|
|
674
|
-
state: "signed-in",
|
|
675
|
-
daysRemaining: Math.ceil(expiresInMs(state.credential.claims) / 864e5),
|
|
676
|
-
expiry: expiryLevel(state.credential.claims)
|
|
677
|
-
} : state.state === "expired" ? { state: "expired" } : {
|
|
678
|
-
state: "signed-out",
|
|
679
|
-
reason: state.reason
|
|
680
|
-
},
|
|
1395
|
+
auth,
|
|
681
1396
|
region,
|
|
682
1397
|
catalogSource: catalog.source,
|
|
683
1398
|
catalogError: catalog.error,
|
|
@@ -686,7 +1401,9 @@ function apply(ctx) {
|
|
|
686
1401
|
name: modelName,
|
|
687
1402
|
contextWindow
|
|
688
1403
|
})),
|
|
689
|
-
refreshing
|
|
1404
|
+
refreshing,
|
|
1405
|
+
credits: buildCreditStatus(),
|
|
1406
|
+
usage: buildUsageStatus()
|
|
690
1407
|
});
|
|
691
1408
|
};
|
|
692
1409
|
(async () => {
|
|
@@ -719,4 +1436,4 @@ function apply(ctx) {
|
|
|
719
1436
|
});
|
|
720
1437
|
}
|
|
721
1438
|
//#endregion
|
|
722
|
-
export { FALLBACK_MODELS, MINIMAXCODE_PROVIDER_ID, MINIMAX_AUTH_FILENAME, MINIMAX_DATA_DIR, MINIMAX_GATEWAYS, MINIMAX_REGIONS, MINIMAX_STATUS_PATH, MINIMAX_STREAM_IDLE_TIMEOUT_MS, MinimaxCatalog, apply, authPath, bearerToken, catalogUrl, chatBaseUrl, createMinimaxCodeAdapter, dataDir, daysRemaining, decodeClaims, expiresInMs, expiryLevel, inject, name, parseAuthDocument, parseCatalog, readCredential, statusDocument };
|
|
1439
|
+
export { EMPTY_USAGE, FALLBACK_MODELS, MEMBERSHIP_PATH, MINIMAXCODE_PROVIDER_ID, 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, UsageMeter, apply, authPath, bearerToken, catalogUrl, chatBaseUrl, createMinimaxCodeAdapter, dataDir, daysRemaining, decodeClaims, ensureOAuthCredential, estimateCny, expiresInMs, expiryLevel, fetchCreditSummary, inject, name, parseAuthDocument, parseCatalog, parseCreditSummary, parseOAuthDocument, priceFor, readCredential, readOAuthCredential, refreshOAuthCredential, statusDocument, writeOAuthCredential };
|