Skip to content

Commit 19e77d2

Browse files
authored
fix: select quota endpoint by credential (#232)
1 parent 11251ab commit 19e77d2

6 files changed

Lines changed: 283 additions & 63 deletions

File tree

SDK.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,10 +148,24 @@ for (const item of results.organic) {
148148
### Quota
149149

150150
```typescript
151+
import {
152+
isAccountBalanceResponse,
153+
isQuotaResponse,
154+
} from 'mmx-cli/sdk';
155+
151156
const quota = await sdk.quota.info();
152-
console.log(quota);
157+
158+
if (isQuotaResponse(quota)) {
159+
console.log(quota.model_remains);
160+
} else if (isAccountBalanceResponse(quota)) {
161+
console.log(quota.available_amount);
162+
}
153163
```
154164

165+
Token Plan credentials and OAuth return `QuotaResponse`. API secret keys with
166+
the `sk-api-` prefix return `AccountBalanceResponse`. The type guards above
167+
narrow the `QuotaInfoResponse` union without changing the raw API response.
168+
155169
## Custom Base URL
156170

157171
```typescript

src/client/endpoints.ts

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,37 @@ export function isSecretApiKey(apiKey: string): boolean {
5454
return apiKey.startsWith('sk-api-');
5555
}
5656

57+
export type UsageEndpointKind = 'quota' | 'account-balance';
58+
59+
export interface UsageCredential {
60+
token: string;
61+
method: 'api-key' | 'oauth';
62+
}
63+
64+
export interface UsageEndpointSelection {
65+
url: string;
66+
kind: UsageEndpointKind;
67+
}
68+
69+
export function selectUsageEndpoint(
70+
baseUrl: string,
71+
credential: UsageCredential,
72+
): UsageEndpointSelection {
73+
if (credential.method === 'api-key' && isSecretApiKey(credential.token)) {
74+
return {
75+
url: accountBalanceEndpoint(baseUrl),
76+
kind: 'account-balance',
77+
};
78+
}
79+
80+
return {
81+
url: quotaEndpoint(baseUrl),
82+
kind: 'quota',
83+
};
84+
}
85+
5786
export function usageEndpoint(baseUrl: string, apiKey: string): string {
58-
return isSecretApiKey(apiKey)
59-
? accountBalanceEndpoint(baseUrl)
60-
: quotaEndpoint(baseUrl);
87+
return selectUsageEndpoint(baseUrl, { token: apiKey, method: 'api-key' }).url;
6188
}
6289

6390
export function fileUploadEndpoint(baseUrl: string): string {

src/commands/quota/show.ts

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
import { defineCommand } from '../../command';
22
import { requestJson } from '../../client/http';
3-
import { quotaEndpoint, usageEndpoint } from '../../client/endpoints';
3+
import { selectUsageEndpoint } from '../../client/endpoints';
4+
import { resolveCredential } from '../../auth/resolver';
45
import { formatOutput, detectOutputFormat } from '../../output/formatter';
56
import { renderUsage } from '../../output/usage';
67
import type { Config } from '../../config/schema';
78
import type { GlobalFlags } from '../../types/flags';
8-
import type { AccountBalanceResponse, QuotaModelRemain } from '../../types/api';
9-
10-
interface QuotaApiResponse {
11-
model_remains: QuotaModelRemain[];
12-
}
9+
import type { AccountBalanceResponse, QuotaResponse } from '../../types/api';
1310

1411
export default defineCommand({
1512
name: 'quota show',
@@ -26,21 +23,20 @@ export default defineCommand({
2623
}
2724

2825
const format = detectOutputFormat(flags.output as string | undefined);
29-
const apiKey = config.apiKey || config.fileApiKey;
26+
const credential = await resolveCredential(config);
27+
const endpoint = selectUsageEndpoint(config.baseUrl, credential);
3028

31-
if (apiKey && apiKey.startsWith('sk-api-')) {
32-
const url = usageEndpoint(config.baseUrl, apiKey);
33-
const response = await requestJson<AccountBalanceResponse>(config, { url });
29+
if (endpoint.kind === 'account-balance') {
30+
const response = await requestJson<AccountBalanceResponse>(config, { url: endpoint.url });
3431
if (format !== 'text') {
3532
console.log(formatOutput(response, format));
3633
return;
3734
}
38-
renderUsage(response, config, apiKey);
35+
renderUsage(response, config, credential.token);
3936
return;
4037
}
4138

42-
const url = quotaEndpoint(config.baseUrl);
43-
const response = await requestJson<QuotaApiResponse>(config, { url });
39+
const response = await requestJson<QuotaResponse>(config, { url: endpoint.url });
4440
const models = response.model_remains || [];
4541

4642
if (format !== 'text') {

src/sdk/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,16 @@ import { FileSDK } from "./file";
99
import { Client } from "./client";
1010
import { MiniMaxSDKOptions } from "./types";
1111

12+
export {
13+
isAccountBalanceResponse,
14+
isQuotaResponse,
15+
} from "./quota";
16+
export type {
17+
AccountBalanceResponse,
18+
QuotaInfoResponse,
19+
QuotaResponse,
20+
} from "./quota";
21+
1222
export class MiniMaxSDK extends Client {
1323
readonly text: TextSDK;
1424
readonly speech: SpeechSDK;

src/sdk/quota/index.ts

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,31 @@
11
import { Client } from "../client";
2-
import { quotaEndpoint } from "../../client/endpoints";
3-
import type { QuotaResponse } from "../../types/api";
2+
import { selectUsageEndpoint } from "../../client/endpoints";
3+
import { resolveCredential } from "../../auth/resolver";
4+
import type { AccountBalanceResponse, QuotaResponse } from "../../types/api";
5+
6+
export type QuotaInfoResponse = QuotaResponse | AccountBalanceResponse;
7+
8+
export function isQuotaResponse(response: QuotaInfoResponse): response is QuotaResponse {
9+
return 'model_remains' in response;
10+
}
11+
12+
export function isAccountBalanceResponse(
13+
response: QuotaInfoResponse,
14+
): response is AccountBalanceResponse {
15+
return 'available_amount' in response;
16+
}
417

518
export class QuotaSDK extends Client {
6-
async info(): Promise<QuotaResponse> {
7-
const url = quotaEndpoint(this.config.baseUrl);
8-
const res = await this.requestJson<QuotaResponse>({ url });
19+
async info(): Promise<QuotaInfoResponse> {
20+
const credential = await resolveCredential(this.config);
21+
const endpoint = selectUsageEndpoint(this.config.baseUrl, credential);
22+
23+
if (endpoint.kind === 'account-balance') {
24+
return this.requestJson<AccountBalanceResponse>({ url: endpoint.url });
25+
}
926

10-
return res;
27+
return this.requestJson<QuotaResponse>({ url: endpoint.url });
1128
}
1229
}
30+
31+
export type { AccountBalanceResponse, QuotaResponse } from "../../types/api";

0 commit comments

Comments
 (0)