pi-credits 0.2.0 → 0.3.1
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 +1 -0
- package/package.json +1 -1
- package/src/manager.ts +11 -3
- package/src/providers/deepseek.ts +2 -22
- package/src/providers/fireworks.ts +1 -1
- package/src/providers/index.ts +4 -1
- package/src/providers/moonshot.ts +39 -0
- package/src/providers/openai-codex.ts +1 -1
- package/src/providers/openrouter.ts +1 -1
- package/src/providers/vercel-ai-gateway.ts +1 -1
- package/src/types.ts +1 -1
- package/src/utils.ts +21 -0
package/README.md
CHANGED
package/package.json
CHANGED
package/src/manager.ts
CHANGED
|
@@ -9,6 +9,7 @@ const REQUEST_TIMEOUT_MS = 30_000;
|
|
|
9
9
|
|
|
10
10
|
export class CreditsManager {
|
|
11
11
|
private inflight: AbortController | undefined = undefined;
|
|
12
|
+
private currentProvider: string | undefined = undefined;
|
|
12
13
|
|
|
13
14
|
async refresh(ctx: ExtensionContext): Promise<void> {
|
|
14
15
|
this.inflight?.abort();
|
|
@@ -16,10 +17,17 @@ export class CreditsManager {
|
|
|
16
17
|
const provider = findProvider(ctx.model?.provider);
|
|
17
18
|
if (!provider) {
|
|
18
19
|
this.inflight = undefined;
|
|
20
|
+
this.currentProvider = undefined;
|
|
19
21
|
ctx.ui.setStatus(STATUS_KEY, undefined);
|
|
20
22
|
return;
|
|
21
23
|
}
|
|
22
24
|
|
|
25
|
+
// Clear stale credits from another provider while the new fetch is in flight.
|
|
26
|
+
if (this.currentProvider !== provider.id) {
|
|
27
|
+
this.currentProvider = provider.id;
|
|
28
|
+
ctx.ui.setStatus(STATUS_KEY, undefined);
|
|
29
|
+
}
|
|
30
|
+
|
|
23
31
|
const controller = new AbortController();
|
|
24
32
|
this.inflight = controller;
|
|
25
33
|
|
|
@@ -30,7 +38,7 @@ export class CreditsManager {
|
|
|
30
38
|
|
|
31
39
|
private async fetch(ctx: ExtensionContext, provider: CreditsProvider, signal: AbortSignal): Promise<void> {
|
|
32
40
|
try {
|
|
33
|
-
const apiKey = await ctx.modelRegistry.getApiKeyForProvider(provider.
|
|
41
|
+
const apiKey = await ctx.modelRegistry.getApiKeyForProvider(provider.id);
|
|
34
42
|
if (!apiKey) {
|
|
35
43
|
ctx.ui.setStatus(STATUS_KEY, undefined);
|
|
36
44
|
return;
|
|
@@ -42,12 +50,12 @@ export class CreditsManager {
|
|
|
42
50
|
const credits = await provider.fetch(ctx, apiKey, AbortSignal.any(signals));
|
|
43
51
|
|
|
44
52
|
// The active model may have changed while the request was in flight.
|
|
45
|
-
if (ctx.model?.provider !== provider.
|
|
53
|
+
if (ctx.model?.provider !== provider.id) return;
|
|
46
54
|
|
|
47
55
|
ctx.ui.setStatus(STATUS_KEY, renderCredits(ctx.ui.theme, provider.label, credits));
|
|
48
56
|
} catch (error) {
|
|
49
57
|
if (signal.aborted || ctx.signal?.aborted) return;
|
|
50
|
-
if (ctx.model?.provider !== provider.
|
|
58
|
+
if (ctx.model?.provider !== provider.id) return;
|
|
51
59
|
|
|
52
60
|
const message = error instanceof Error ? error.message : String(error);
|
|
53
61
|
ctx.ui.setStatus(STATUS_KEY, renderError(ctx.ui.theme, provider.label, message));
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { toNumber } from "../utils";
|
|
1
|
+
import { convertToUSD, toNumber } from "../utils";
|
|
2
2
|
|
|
3
3
|
import type { Credits, CreditsProvider } from "../types";
|
|
4
4
|
|
|
5
5
|
const PROVIDER = "deepseek";
|
|
6
6
|
const URL = "https://api.deepseek.com/user/balance";
|
|
7
|
-
const FRANKFURTER_API = "https://api.frankfurter.dev/v2/rate";
|
|
8
7
|
|
|
9
8
|
interface DeepSeekBalanceResponse {
|
|
10
9
|
balance_infos?: DeepSeekBalanceInfo[] | null;
|
|
@@ -15,27 +14,8 @@ interface DeepSeekBalanceInfo {
|
|
|
15
14
|
total_balance?: string | number;
|
|
16
15
|
}
|
|
17
16
|
|
|
18
|
-
interface FrankfurterRateResponse {
|
|
19
|
-
rate?: string | number;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
async function convertToUSD(amount: number | undefined, currency: string | undefined, signal: AbortSignal): Promise<number | undefined> {
|
|
23
|
-
if (amount === undefined) return undefined;
|
|
24
|
-
if (!currency || currency === "USD") return amount;
|
|
25
|
-
|
|
26
|
-
const url = `${FRANKFURTER_API}/${encodeURIComponent(currency)}/USD`;
|
|
27
|
-
const response = await fetch(url, { headers: { Accept: "application/json" }, signal });
|
|
28
|
-
if (!response.ok) throw new Error("currency conversion failed");
|
|
29
|
-
|
|
30
|
-
const payload = (await response.json()) as FrankfurterRateResponse;
|
|
31
|
-
const rate = toNumber(payload.rate);
|
|
32
|
-
if (rate === undefined) throw new Error("currency conversion failed");
|
|
33
|
-
|
|
34
|
-
return amount * rate;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
17
|
export const deepseekProvider: CreditsProvider = {
|
|
38
|
-
|
|
18
|
+
id: PROVIDER,
|
|
39
19
|
label: "DeepSeek",
|
|
40
20
|
|
|
41
21
|
async fetch(_ctx, apiKey, signal): Promise<Credits> {
|
|
@@ -99,7 +99,7 @@ function moneyToNumber(money: Money | null | undefined): number | undefined {
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
export const fireworksProvider: CreditsProvider = {
|
|
102
|
-
|
|
102
|
+
id: PROVIDER,
|
|
103
103
|
label: "Fireworks",
|
|
104
104
|
|
|
105
105
|
async fetch(_ctx, apiKey, signal): Promise<Credits> {
|
package/src/providers/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { deepseekProvider } from "./deepseek";
|
|
2
2
|
import { fireworksProvider } from "./fireworks";
|
|
3
|
+
import { moonshotProvider, moonshotCnProvider } from "./moonshot";
|
|
3
4
|
import { openaiCodexProvider } from "./openai-codex";
|
|
4
5
|
import { openrouterProvider } from "./openrouter";
|
|
5
6
|
import { vercelAiGatewayProvider } from "./vercel-ai-gateway";
|
|
@@ -9,11 +10,13 @@ import type { CreditsProvider } from "../types";
|
|
|
9
10
|
const PROVIDERS: CreditsProvider[] = [
|
|
10
11
|
deepseekProvider,
|
|
11
12
|
fireworksProvider,
|
|
13
|
+
moonshotProvider,
|
|
14
|
+
moonshotCnProvider,
|
|
12
15
|
openaiCodexProvider,
|
|
13
16
|
openrouterProvider,
|
|
14
17
|
vercelAiGatewayProvider,
|
|
15
18
|
];
|
|
16
19
|
|
|
17
20
|
export function findProvider(provider?: string): CreditsProvider | undefined {
|
|
18
|
-
return PROVIDERS.find((entry) => entry.
|
|
21
|
+
return PROVIDERS.find((entry) => entry.id === provider);
|
|
19
22
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { convertToUSD, toNumber } from "../utils";
|
|
2
|
+
|
|
3
|
+
import type { Credits, CreditsProvider } from "../types";
|
|
4
|
+
|
|
5
|
+
interface MoonshotBalanceResponse {
|
|
6
|
+
data?: {
|
|
7
|
+
available_balance?: string | number;
|
|
8
|
+
} | null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* For Moonshot, the international and China-mainland accounts live on separate hosts and bill in
|
|
13
|
+
* different currencies (USD vs CNY), which the endpoint does not report, so each pi provider ID
|
|
14
|
+
* fixes both host and currency.
|
|
15
|
+
*/
|
|
16
|
+
function createMoonshotProvider(id: string, host: string, currency: string): CreditsProvider {
|
|
17
|
+
return {
|
|
18
|
+
id,
|
|
19
|
+
label: "Moonshot",
|
|
20
|
+
|
|
21
|
+
async fetch(_ctx, apiKey, signal): Promise<Credits> {
|
|
22
|
+
const headers: Record<string, string> = {
|
|
23
|
+
Accept: "application/json",
|
|
24
|
+
Authorization: `Bearer ${apiKey}`,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const response = await fetch(`https://${host}/v1/users/me/balance`, { headers, signal });
|
|
28
|
+
if (!response.ok) throw new Error("request failed");
|
|
29
|
+
|
|
30
|
+
const payload = (await response.json()) as MoonshotBalanceResponse;
|
|
31
|
+
const remaining = await convertToUSD(toNumber(payload.data?.available_balance), currency, signal);
|
|
32
|
+
|
|
33
|
+
return { type: "balance", remaining };
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const moonshotProvider = createMoonshotProvider("moonshotai", "api.moonshot.ai", "USD");
|
|
39
|
+
export const moonshotCnProvider = createMoonshotProvider("moonshotai-cn", "api.moonshot.cn", "CNY");
|
|
@@ -33,7 +33,7 @@ function parseUsedPercent(window?: CodexRateWindow | null): number | undefined {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
export const openaiCodexProvider: CreditsProvider = {
|
|
36
|
-
|
|
36
|
+
id: PROVIDER,
|
|
37
37
|
label: "Codex",
|
|
38
38
|
|
|
39
39
|
async fetch(ctx, apiKey, signal): Promise<Credits> {
|
package/src/types.ts
CHANGED
|
@@ -18,7 +18,7 @@ export interface CreditsLane {
|
|
|
18
18
|
|
|
19
19
|
/** A credits source for a pi provider, shown in the status line while that provider is active. */
|
|
20
20
|
export interface CreditsProvider {
|
|
21
|
-
readonly
|
|
21
|
+
readonly id: Provider;
|
|
22
22
|
readonly label: string;
|
|
23
23
|
fetch(ctx: ExtensionContext, apiKey: string, signal: AbortSignal): Promise<Credits>;
|
|
24
24
|
}
|
package/src/utils.ts
CHANGED
|
@@ -29,3 +29,24 @@ export function toNumber(value?: string | number | null): number | undefined {
|
|
|
29
29
|
const parsed = typeof value === "number" ? value : Number(value);
|
|
30
30
|
return Number.isFinite(parsed) ? parsed : undefined;
|
|
31
31
|
}
|
|
32
|
+
|
|
33
|
+
const FRANKFURTER_API = "https://api.frankfurter.dev/v2/rate";
|
|
34
|
+
|
|
35
|
+
interface FrankfurterRateResponse {
|
|
36
|
+
rate?: string | number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function convertToUSD(amount: number | undefined, currency: string | undefined, signal: AbortSignal): Promise<number | undefined> {
|
|
40
|
+
if (amount === undefined) return undefined;
|
|
41
|
+
if (!currency || currency === "USD") return amount;
|
|
42
|
+
|
|
43
|
+
const url = `${FRANKFURTER_API}/${encodeURIComponent(currency)}/USD`;
|
|
44
|
+
const response = await fetch(url, { headers: { Accept: "application/json" }, signal });
|
|
45
|
+
if (!response.ok) throw new Error("currency conversion failed");
|
|
46
|
+
|
|
47
|
+
const payload = (await response.json()) as FrankfurterRateResponse;
|
|
48
|
+
const rate = toNumber(payload.rate);
|
|
49
|
+
if (rate === undefined) throw new Error("currency conversion failed");
|
|
50
|
+
|
|
51
|
+
return amount * rate;
|
|
52
|
+
}
|