@oh-my-pi/pi-ai 17.2.11 → 17.2.13
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/CHANGELOG.md +25 -0
- package/dist/types/auth-retry.d.ts +3 -3
- package/dist/types/auth-storage.d.ts +2 -0
- package/dist/types/error/auth-classify.d.ts +5 -5
- package/dist/types/error/aws.d.ts +5 -1
- package/dist/types/error/flags.d.ts +4 -0
- package/dist/types/providers/aws-credentials.d.ts +4 -3
- package/dist/types/providers/cursor/exec-modern.d.ts +1 -1
- package/dist/types/providers/cursor-pi-args.d.ts +14 -0
- package/dist/types/providers/openai-shared.d.ts +9 -1
- package/dist/types/registry/api-key-login.d.ts +6 -4
- package/dist/types/registry/cloudflare-ai-gateway.d.ts +2 -2
- package/dist/types/registry/kagi.d.ts +2 -2
- package/dist/types/registry/litellm.d.ts +2 -2
- package/dist/types/registry/llama-cpp.d.ts +2 -2
- package/dist/types/registry/lm-studio.d.ts +2 -2
- package/dist/types/registry/parallel.d.ts +2 -2
- package/dist/types/registry/tavily.d.ts +1 -1
- package/dist/types/registry/vercel-ai-gateway.d.ts +2 -2
- package/dist/types/registry/vllm.d.ts +2 -2
- package/dist/types/types.d.ts +7 -0
- package/dist/types/usage/cursor.d.ts +11 -0
- package/dist/types/usage/openai-codex-reset.d.ts +0 -20
- package/dist/types/usage/shared.d.ts +13 -1
- package/dist/types/utils/block-symbols.d.ts +12 -0
- package/package.json +5 -5
- package/src/auth-retry.ts +5 -4
- package/src/auth-storage.ts +77 -33
- package/src/dialect/owned-stream.ts +3 -0
- package/src/error/auth-classify.ts +7 -6
- package/src/error/aws.ts +5 -1
- package/src/error/flags.ts +14 -0
- package/src/providers/amazon-bedrock.ts +38 -0
- package/src/providers/aws-credentials.ts +222 -29
- package/src/providers/cursor/exec-modern.ts +1 -0
- package/src/providers/cursor-pi-args.ts +22 -0
- package/src/providers/cursor.ts +81 -1
- package/src/providers/google-gemini-cli.ts +49 -15
- package/src/providers/google-shared.ts +7 -1
- package/src/providers/openai-codex/request-transformer.ts +38 -17
- package/src/providers/openai-codex-responses.ts +2 -3
- package/src/providers/openai-responses.ts +4 -0
- package/src/providers/openai-shared.ts +55 -1
- package/src/providers/pi-native-server.ts +1 -0
- package/src/providers/register-builtins.ts +18 -14
- package/src/registry/api-key-login.ts +26 -12
- package/src/registry/aws.ts +13 -6
- package/src/registry/cloudflare-ai-gateway.ts +10 -29
- package/src/registry/kagi.ts +11 -29
- package/src/registry/litellm.ts +11 -29
- package/src/registry/llama-cpp.ts +11 -21
- package/src/registry/lm-studio.ts +9 -20
- package/src/registry/oauth/callback-server.ts +93 -5
- package/src/registry/parallel.ts +10 -28
- package/src/registry/tavily.ts +9 -27
- package/src/registry/vercel-ai-gateway.ts +10 -28
- package/src/registry/vllm.ts +11 -21
- package/src/stream.ts +10 -7
- package/src/types.ts +7 -0
- package/src/usage/alibaba-token-plan.ts +8 -15
- package/src/usage/claude.ts +12 -19
- package/src/usage/cursor.ts +179 -59
- package/src/usage/gemini.ts +3 -2
- package/src/usage/github-copilot.ts +3 -2
- package/src/usage/google-antigravity.ts +10 -17
- package/src/usage/kimi.ts +35 -17
- package/src/usage/minimax-code.ts +7 -27
- package/src/usage/openai-codex-reset.ts +3 -2
- package/src/usage/openai-codex.ts +5 -5
- package/src/usage/opencode-go.ts +1 -2
- package/src/usage/shared.ts +33 -10
- package/src/usage/synthetic.ts +4 -11
- package/src/usage/umans.ts +2 -2
- package/src/usage/xai-oauth.ts +13 -26
- package/src/usage/zai.ts +4 -6
- package/src/utils/aws-profile.ts +39 -1
- package/src/utils/block-symbols.ts +18 -0
- package/src/utils/leaked-thinking-stream.ts +3 -0
- package/src/utils/openrouter-headers.ts +3 -3
|
@@ -1,18 +1,11 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
UsageFetchParams,
|
|
4
|
-
UsageLimit,
|
|
5
|
-
UsageProvider,
|
|
6
|
-
UsageReport,
|
|
7
|
-
UsageStatus,
|
|
8
|
-
} from "../usage";
|
|
1
|
+
import { toNumber } from "@oh-my-pi/pi-catalog/utils";
|
|
2
|
+
import type { UsageFetchContext, UsageFetchParams, UsageLimit, UsageProvider, UsageReport } from "../usage";
|
|
9
3
|
import { isRecord } from "../utils";
|
|
10
|
-
import {
|
|
4
|
+
import { HOUR_MS, parsePositiveTimestamp, usageStatus } from "./shared";
|
|
11
5
|
|
|
12
6
|
const INTL_PROVIDER = "minimax-code";
|
|
13
7
|
const INTL_BASE_URL = "https://api.minimax.io";
|
|
14
8
|
const REMAINS_PATH = "/v1/token_plan/remains";
|
|
15
|
-
const HOUR_MS = 60 * 60 * 1000;
|
|
16
9
|
/** `current_*_status` enum reported per window: 1 normal, 2 exhausted, 3 unlimited. */
|
|
17
10
|
const STATUS_EXHAUSTED = 2;
|
|
18
11
|
const STATUS_UNLIMITED = 3;
|
|
@@ -42,13 +35,6 @@ interface TokenPlanBucket {
|
|
|
42
35
|
weeklyStatus?: number;
|
|
43
36
|
}
|
|
44
37
|
|
|
45
|
-
/** MiniMax reports epoch milliseconds; tolerate seconds in case a deployment differs. */
|
|
46
|
-
function parseTimestamp(value: unknown): number | undefined {
|
|
47
|
-
const parsed = toNumber(value);
|
|
48
|
-
if (parsed === undefined || parsed <= 0) return undefined;
|
|
49
|
-
return parsed < 1_000_000_000_000 ? parsed * 1000 : parsed;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
38
|
/** `current_*_remaining_percent` is 0..100 remaining; usage fractions are 0..1 used. */
|
|
53
39
|
function usedFractionFromRemainingPercent(value: unknown): number | undefined {
|
|
54
40
|
const parsed = toNumber(value);
|
|
@@ -57,26 +43,20 @@ function usedFractionFromRemainingPercent(value: unknown): number | undefined {
|
|
|
57
43
|
return Math.min(1, Math.max(0, (100 - parsed) / 100));
|
|
58
44
|
}
|
|
59
45
|
|
|
60
|
-
function usageStatus(usedFraction: number): UsageStatus {
|
|
61
|
-
if (usedFraction >= 1) return "exhausted";
|
|
62
|
-
if (usedFraction >= 0.9) return "warning";
|
|
63
|
-
return "ok";
|
|
64
|
-
}
|
|
65
|
-
|
|
66
46
|
function parseBucket(value: unknown): TokenPlanBucket | null {
|
|
67
47
|
if (!isRecord(value)) return null;
|
|
68
48
|
const modelName = typeof value.model_name === "string" ? value.model_name.trim() : "";
|
|
69
49
|
if (!modelName) return null;
|
|
70
50
|
return {
|
|
71
51
|
modelName,
|
|
72
|
-
intervalStart:
|
|
73
|
-
intervalEnd:
|
|
52
|
+
intervalStart: parsePositiveTimestamp(value.start_time),
|
|
53
|
+
intervalEnd: parsePositiveTimestamp(value.end_time),
|
|
74
54
|
intervalRemainingPercent: toNumber(value.current_interval_remaining_percent),
|
|
75
55
|
intervalTotalCount: toNumber(value.current_interval_total_count),
|
|
76
56
|
intervalUsageCount: toNumber(value.current_interval_usage_count),
|
|
77
57
|
intervalStatus: toNumber(value.current_interval_status),
|
|
78
|
-
weeklyStart:
|
|
79
|
-
weeklyEnd:
|
|
58
|
+
weeklyStart: parsePositiveTimestamp(value.weekly_start_time),
|
|
59
|
+
weeklyEnd: parsePositiveTimestamp(value.weekly_end_time),
|
|
80
60
|
weeklyRemainingPercent: toNumber(value.current_weekly_remaining_percent),
|
|
81
61
|
weeklyTotalCount: toNumber(value.current_weekly_total_count),
|
|
82
62
|
weeklyUsageCount: toNumber(value.current_weekly_usage_count),
|
|
@@ -18,10 +18,11 @@
|
|
|
18
18
|
* (the `/usage reset` command + auto-redeem) and any out-of-band tooling can
|
|
19
19
|
* share one wire contract.
|
|
20
20
|
*/
|
|
21
|
+
import { toNumber } from "@oh-my-pi/pi-catalog/utils";
|
|
22
|
+
import { USER_AGENT } from "@oh-my-pi/pi-utils";
|
|
21
23
|
import type { FetchImpl } from "../types";
|
|
22
24
|
import { isRecord } from "../utils";
|
|
23
25
|
import { normalizeCodexBaseUrl } from "./openai-codex-base-url";
|
|
24
|
-
import { toNumber } from "./shared";
|
|
25
26
|
|
|
26
27
|
const RESET_CREDITS_PATH = "wham/rate-limit-reset-credits";
|
|
27
28
|
const RESET_CREDITS_CONSUME_PATH = "wham/rate-limit-reset-credits/consume";
|
|
@@ -89,7 +90,7 @@ function buildUrl(baseUrl: string | undefined, routePath: string): string {
|
|
|
89
90
|
function buildHeaders(auth: CodexResetAuth, json: boolean): Record<string, string> {
|
|
90
91
|
const headers: Record<string, string> = {
|
|
91
92
|
Authorization: `Bearer ${auth.accessToken}`,
|
|
92
|
-
"User-Agent":
|
|
93
|
+
"User-Agent": USER_AGENT,
|
|
93
94
|
};
|
|
94
95
|
if (auth.accountId) headers["ChatGPT-Account-Id"] = auth.accountId;
|
|
95
96
|
if (json) headers["Content-Type"] = "application/json";
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Buffer } from "node:buffer";
|
|
2
|
+
import { toNumber } from "@oh-my-pi/pi-catalog/utils";
|
|
3
|
+
import { USER_AGENT } from "@oh-my-pi/pi-utils";
|
|
2
4
|
import type {
|
|
3
5
|
CredentialRankingContext,
|
|
4
6
|
CredentialRankingStrategy,
|
|
@@ -14,7 +16,7 @@ import type {
|
|
|
14
16
|
import { isRecord } from "../utils";
|
|
15
17
|
import { normalizeCodexBaseUrl } from "./openai-codex-base-url";
|
|
16
18
|
import { listCodexResetCredits } from "./openai-codex-reset";
|
|
17
|
-
import {
|
|
19
|
+
import { HOUR_MS } from "./shared";
|
|
18
20
|
|
|
19
21
|
const CODEX_USAGE_PATH = "wham/usage";
|
|
20
22
|
const JWT_AUTH_CLAIM = "https://api.openai.com/auth";
|
|
@@ -416,7 +418,7 @@ export const openaiCodexUsageProvider: UsageProvider = {
|
|
|
416
418
|
|
|
417
419
|
const headers: Record<string, string> = {
|
|
418
420
|
Authorization: `Bearer ${accessToken}`,
|
|
419
|
-
"User-Agent":
|
|
421
|
+
"User-Agent": USER_AGENT,
|
|
420
422
|
};
|
|
421
423
|
if (accountId) {
|
|
422
424
|
headers["ChatGPT-Account-Id"] = accountId;
|
|
@@ -558,8 +560,6 @@ export const openaiCodexUsageProvider: UsageProvider = {
|
|
|
558
560
|
},
|
|
559
561
|
};
|
|
560
562
|
|
|
561
|
-
const FIVE_HOUR_MS = 5 * 60 * 60 * 1000;
|
|
562
|
-
|
|
563
563
|
// A Codex request gates only on the chat windows it actually consumes. A
|
|
564
564
|
// "-spark" model spends the separate Spark meter; every other Codex model spends
|
|
565
565
|
// the 5h/weekly chat windows. Scoping the gating set this way keeps an exhausted
|
|
@@ -619,7 +619,7 @@ export const codexRankingStrategy: CredentialRankingStrategy = {
|
|
|
619
619
|
windowId === "5h" ||
|
|
620
620
|
(typeof durationMs === "number" &&
|
|
621
621
|
Number.isFinite(durationMs) &&
|
|
622
|
-
Math.abs(durationMs -
|
|
622
|
+
Math.abs(durationMs - 5 * HOUR_MS) <= 60_000);
|
|
623
623
|
if (!isFiveHourWindow) return false;
|
|
624
624
|
const usedFraction = primary.amount.usedFraction;
|
|
625
625
|
return typeof usedFraction === "number" && Number.isFinite(usedFraction) && usedFraction === 0;
|
package/src/usage/opencode-go.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import type { UsageCostHistoryEntry, UsageLimit, UsageProvider, UsageWindow } from "../usage";
|
|
2
|
+
import { DAY_MS, HOUR_MS } from "./shared";
|
|
2
3
|
|
|
3
4
|
const OPENCODE_GO_PROVIDER = "opencode-go";
|
|
4
|
-
const HOUR_MS = 60 * 60 * 1000;
|
|
5
|
-
const DAY_MS = 24 * HOUR_MS;
|
|
6
5
|
const OPENCODE_GO_LIMITS = [
|
|
7
6
|
{ id: "rolling-5h", label: "5 Hour", durationMs: 5 * HOUR_MS, limitUsd: 12 },
|
|
8
7
|
{ id: "weekly", label: "Weekly", durationMs: 7 * DAY_MS, limitUsd: 30 },
|
package/src/usage/shared.ts
CHANGED
|
@@ -1,10 +1,33 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
import { toNumber } from "@oh-my-pi/pi-catalog/utils";
|
|
2
|
+
import type { UsageStatus } from "../usage";
|
|
3
|
+
|
|
4
|
+
/** Milliseconds in one hour. */
|
|
5
|
+
export const HOUR_MS = 60 * 60 * 1000;
|
|
6
|
+
|
|
7
|
+
/** Milliseconds in one day. */
|
|
8
|
+
export const DAY_MS = 24 * HOUR_MS;
|
|
9
|
+
|
|
10
|
+
/** Milliseconds in one seven-day week. */
|
|
11
|
+
export const WEEK_MS = 7 * DAY_MS;
|
|
12
|
+
|
|
13
|
+
/** Parses a finite positive epoch timestamp, tolerating seconds or milliseconds. */
|
|
14
|
+
export function parsePositiveTimestamp(value: unknown): number | undefined {
|
|
15
|
+
const parsed = toNumber(value);
|
|
16
|
+
if (parsed === undefined || parsed <= 0) return undefined;
|
|
17
|
+
return parsed < 1_000_000_000_000 ? parsed * 1000 : parsed;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Parses an ISO timestamp into epoch milliseconds. */
|
|
21
|
+
export function parseIsoTimestamp(value: unknown): number | undefined {
|
|
22
|
+
if (typeof value !== "string" || !value) return undefined;
|
|
23
|
+
const parsed = Date.parse(value);
|
|
24
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Maps a used fraction to the standard quota status thresholds. */
|
|
28
|
+
export function usageStatus(usedFraction: number | undefined): UsageStatus {
|
|
29
|
+
if (usedFraction === undefined) return "unknown";
|
|
30
|
+
if (usedFraction >= 1) return "exhausted";
|
|
31
|
+
if (usedFraction >= 0.9) return "warning";
|
|
32
|
+
return "ok";
|
|
33
|
+
}
|
package/src/usage/synthetic.ts
CHANGED
|
@@ -9,10 +9,9 @@ import type {
|
|
|
9
9
|
UsageStatus,
|
|
10
10
|
UsageWindow,
|
|
11
11
|
} from "../usage";
|
|
12
|
+
import { HOUR_MS, parseIsoTimestamp, WEEK_MS } from "./shared";
|
|
12
13
|
|
|
13
14
|
const QUOTAS_URL = "https://api.synthetic.new/v2/quotas";
|
|
14
|
-
const FIVE_HOUR_MS = 5 * 60 * 60 * 1000;
|
|
15
|
-
const WEEK_MS = 7 * 24 * 60 * 60 * 1000;
|
|
16
15
|
|
|
17
16
|
function parseDollarAmount(value: unknown): number | undefined {
|
|
18
17
|
if (typeof value !== "string") return undefined;
|
|
@@ -21,12 +20,6 @@ function parseDollarAmount(value: unknown): number | undefined {
|
|
|
21
20
|
return Number.isFinite(parsed) ? parsed : undefined;
|
|
22
21
|
}
|
|
23
22
|
|
|
24
|
-
function parseIsoMs(value: unknown): number | undefined {
|
|
25
|
-
if (typeof value !== "string" || !value) return undefined;
|
|
26
|
-
const ms = Date.parse(value);
|
|
27
|
-
return Number.isFinite(ms) ? ms : undefined;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
23
|
function buildUsageAmount(args: {
|
|
31
24
|
used: number | undefined;
|
|
32
25
|
limit: number | undefined;
|
|
@@ -61,7 +54,7 @@ function parseRollingFiveHourLimit(raw: unknown, provider: UsageFetchParams["pro
|
|
|
61
54
|
const remaining = typeof raw.remaining === "number" ? raw.remaining : undefined;
|
|
62
55
|
const max = typeof raw.max === "number" ? raw.max : undefined;
|
|
63
56
|
const limited = raw.limited === true;
|
|
64
|
-
const nextTickAt =
|
|
57
|
+
const nextTickAt = parseIsoTimestamp(raw.nextTickAt);
|
|
65
58
|
const tickPercent = typeof raw.tickPercent === "number" ? raw.tickPercent : undefined;
|
|
66
59
|
|
|
67
60
|
if (remaining === undefined && max === undefined) return null;
|
|
@@ -71,7 +64,7 @@ function parseRollingFiveHourLimit(raw: unknown, provider: UsageFetchParams["pro
|
|
|
71
64
|
const window: UsageWindow = {
|
|
72
65
|
id: "5h",
|
|
73
66
|
label: regenPercent !== undefined ? `5h · regen ${regenPercent}%/tick` : "5h",
|
|
74
|
-
durationMs:
|
|
67
|
+
durationMs: 5 * HOUR_MS,
|
|
75
68
|
...(nextTickAt !== undefined ? { resetsAt: nextTickAt, resetLabel: "tick" } : {}),
|
|
76
69
|
};
|
|
77
70
|
const amount = buildUsageAmount({
|
|
@@ -97,7 +90,7 @@ function parseWeeklyTokenLimit(raw: unknown, provider: UsageFetchParams["provide
|
|
|
97
90
|
const remainingCredits = parseDollarAmount(raw.remainingCredits);
|
|
98
91
|
const maxCredits = parseDollarAmount(raw.maxCredits);
|
|
99
92
|
const percentRemaining = typeof raw.percentRemaining === "number" ? raw.percentRemaining : undefined;
|
|
100
|
-
const nextRegenAt =
|
|
93
|
+
const nextRegenAt = parseIsoTimestamp(raw.nextRegenAt);
|
|
101
94
|
|
|
102
95
|
if (remainingCredits === undefined && maxCredits === undefined) return null;
|
|
103
96
|
|
package/src/usage/umans.ts
CHANGED
|
@@ -10,11 +10,11 @@ import type {
|
|
|
10
10
|
UsageWindow,
|
|
11
11
|
} from "../usage";
|
|
12
12
|
import { isRecord } from "../utils";
|
|
13
|
+
import { HOUR_MS } from "./shared";
|
|
13
14
|
|
|
14
15
|
const UMANS_PROVIDER = "umans";
|
|
15
16
|
const DEFAULT_ENDPOINT = "https://api.code.umans.ai";
|
|
16
17
|
const USAGE_PATH = "/v1/usage";
|
|
17
|
-
const FIVE_HOURS_MS = 5 * 60 * 60 * 1000;
|
|
18
18
|
|
|
19
19
|
/** Umans `GET /v1/usage` response (subset; extras ignored). */
|
|
20
20
|
interface UmansUsagePayload {
|
|
@@ -89,7 +89,7 @@ function buildRequestsLimit(payload: UmansUsagePayload, provider: string): Usage
|
|
|
89
89
|
const window: UsageWindow = {
|
|
90
90
|
id: "5h",
|
|
91
91
|
label: "rolling 5h",
|
|
92
|
-
durationMs: windowSeconds ? windowSeconds * 1000 :
|
|
92
|
+
durationMs: windowSeconds ? windowSeconds * 1000 : 5 * HOUR_MS,
|
|
93
93
|
};
|
|
94
94
|
return {
|
|
95
95
|
id: "umans:requests",
|
package/src/usage/xai-oauth.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* product and must never be sent here.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
import { toNumber } from "@oh-my-pi/pi-catalog/utils";
|
|
12
13
|
import {
|
|
13
14
|
buildXAICliBillingUrl,
|
|
14
15
|
extractXAIAccessTokenSubject,
|
|
@@ -22,15 +23,12 @@ import type {
|
|
|
22
23
|
UsageLimit,
|
|
23
24
|
UsageProvider,
|
|
24
25
|
UsageReport,
|
|
25
|
-
UsageStatus,
|
|
26
26
|
UsageWindow,
|
|
27
27
|
} from "../usage";
|
|
28
28
|
import { isRecord } from "../utils";
|
|
29
|
-
import {
|
|
29
|
+
import { DAY_MS, parseIsoTimestamp, usageStatus, WEEK_MS } from "./shared";
|
|
30
30
|
|
|
31
31
|
const PROVIDER_ID = "xai-oauth";
|
|
32
|
-
const WEEK_MS = 7 * 24 * 60 * 60 * 1000;
|
|
33
|
-
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
34
32
|
const BILLING_SOURCE = "cli-chat-proxy.grok.com/v1/billing";
|
|
35
33
|
|
|
36
34
|
interface XaiBillingPeriod {
|
|
@@ -71,11 +69,6 @@ interface XaiMonthlyBillingConfig {
|
|
|
71
69
|
|
|
72
70
|
type XaiBillingConfig = XaiWeeklyBillingConfig | XaiMonthlyBillingConfig;
|
|
73
71
|
|
|
74
|
-
function parseIsoMs(value: string): number | undefined {
|
|
75
|
-
const parsed = Date.parse(value);
|
|
76
|
-
return Number.isFinite(parsed) ? parsed : undefined;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
72
|
function parsePercent(value: unknown): number | undefined {
|
|
80
73
|
const percent = toNumber(value);
|
|
81
74
|
return percent !== undefined && percent >= 0 && percent <= 100 ? percent : undefined;
|
|
@@ -99,12 +92,6 @@ function buildPercentAmount(usagePercent: number): UsageAmount {
|
|
|
99
92
|
};
|
|
100
93
|
}
|
|
101
94
|
|
|
102
|
-
function buildUsageStatus(usedFraction: number): UsageStatus {
|
|
103
|
-
if (usedFraction >= 1) return "exhausted";
|
|
104
|
-
if (usedFraction >= 0.9) return "warning";
|
|
105
|
-
return "ok";
|
|
106
|
-
}
|
|
107
|
-
|
|
108
95
|
function slugifyProduct(product: string): string {
|
|
109
96
|
return product
|
|
110
97
|
.trim()
|
|
@@ -118,13 +105,13 @@ function buildPeriodWindow(period: XaiBillingPeriod): UsageWindow {
|
|
|
118
105
|
id: "1w",
|
|
119
106
|
label: "Weekly",
|
|
120
107
|
durationMs: WEEK_MS,
|
|
121
|
-
resetsAt:
|
|
108
|
+
resetsAt: parseIsoTimestamp(period.end),
|
|
122
109
|
};
|
|
123
110
|
}
|
|
124
111
|
|
|
125
112
|
function buildMonthlyWindow(periodStart: string, periodEnd: string): UsageWindow | undefined {
|
|
126
|
-
const startMs =
|
|
127
|
-
const endMs =
|
|
113
|
+
const startMs = parseIsoTimestamp(periodStart);
|
|
114
|
+
const endMs = parseIsoTimestamp(periodEnd);
|
|
128
115
|
if (startMs === undefined || endMs === undefined || endMs <= startMs) return undefined;
|
|
129
116
|
// Real calendar months vary; use the observed period length from the API.
|
|
130
117
|
const durationMs = endMs - startMs;
|
|
@@ -140,8 +127,8 @@ function buildMonthlyWindow(periodStart: string, periodEnd: string): UsageWindow
|
|
|
140
127
|
function parseWeeklyBillingConfig(raw: Record<string, unknown>): XaiWeeklyBillingConfig | null {
|
|
141
128
|
if (!isRecord(raw.currentPeriod)) return null;
|
|
142
129
|
|
|
143
|
-
const start = typeof raw.currentPeriod.start === "string" ?
|
|
144
|
-
const end = typeof raw.currentPeriod.end === "string" ?
|
|
130
|
+
const start = typeof raw.currentPeriod.start === "string" ? parseIsoTimestamp(raw.currentPeriod.start) : undefined;
|
|
131
|
+
const end = typeof raw.currentPeriod.end === "string" ? parseIsoTimestamp(raw.currentPeriod.end) : undefined;
|
|
145
132
|
const type = typeof raw.currentPeriod.type === "string" ? raw.currentPeriod.type : "";
|
|
146
133
|
// Keep recently-ended weekly windows so /usage still renders across period
|
|
147
134
|
// rollover while the billing API is mid-refresh. Reject only inverted ranges
|
|
@@ -182,8 +169,8 @@ function parseWeeklyBillingConfig(raw: Record<string, unknown>): XaiWeeklyBillin
|
|
|
182
169
|
function parseMonthlyBillingConfig(raw: Record<string, unknown>): XaiMonthlyBillingConfig | null {
|
|
183
170
|
const periodStart = typeof raw.billingPeriodStart === "string" ? raw.billingPeriodStart : "";
|
|
184
171
|
const periodEnd = typeof raw.billingPeriodEnd === "string" ? raw.billingPeriodEnd : "";
|
|
185
|
-
const startMs =
|
|
186
|
-
const endMs =
|
|
172
|
+
const startMs = parseIsoTimestamp(periodStart);
|
|
173
|
+
const endMs = parseIsoTimestamp(periodEnd);
|
|
187
174
|
if (!periodStart || !periodEnd || startMs === undefined || endMs === undefined || endMs <= startMs) {
|
|
188
175
|
return null;
|
|
189
176
|
}
|
|
@@ -227,7 +214,7 @@ function buildOnDemandLimit(
|
|
|
227
214
|
remainingFraction: 1 - usedFraction,
|
|
228
215
|
unit: "unknown",
|
|
229
216
|
},
|
|
230
|
-
status:
|
|
217
|
+
status: usageStatus(usedFraction),
|
|
231
218
|
};
|
|
232
219
|
}
|
|
233
220
|
|
|
@@ -248,7 +235,7 @@ function buildLimits(config: XaiBillingConfig, accountId: string | undefined): U
|
|
|
248
235
|
scope,
|
|
249
236
|
window,
|
|
250
237
|
amount: overall,
|
|
251
|
-
status:
|
|
238
|
+
status: usageStatus(overall.usedFraction ?? 0),
|
|
252
239
|
},
|
|
253
240
|
];
|
|
254
241
|
|
|
@@ -262,7 +249,7 @@ function buildLimits(config: XaiBillingConfig, accountId: string | undefined): U
|
|
|
262
249
|
scope,
|
|
263
250
|
window,
|
|
264
251
|
amount,
|
|
265
|
-
status:
|
|
252
|
+
status: usageStatus(amount.usedFraction ?? 0),
|
|
266
253
|
});
|
|
267
254
|
}
|
|
268
255
|
const onDemand = buildOnDemandLimit(config.onDemandCap, config.onDemandUsed, accountId);
|
|
@@ -293,7 +280,7 @@ function buildLimits(config: XaiBillingConfig, accountId: string | undefined): U
|
|
|
293
280
|
// xAI does not label the unit; amounts match the dashboard quota points.
|
|
294
281
|
unit: "unknown",
|
|
295
282
|
},
|
|
296
|
-
status:
|
|
283
|
+
status: usageStatus(usedFraction),
|
|
297
284
|
},
|
|
298
285
|
];
|
|
299
286
|
const onDemand = buildOnDemandLimit(config.onDemandCap, config.onDemandUsed, accountId);
|
package/src/usage/zai.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { toNumber } from "@oh-my-pi/pi-catalog/utils";
|
|
2
|
+
import { USER_AGENT } from "@oh-my-pi/pi-utils";
|
|
2
3
|
import type {
|
|
3
4
|
CredentialRankingStrategy,
|
|
4
5
|
UsageAmount,
|
|
@@ -11,14 +12,11 @@ import type {
|
|
|
11
12
|
UsageWindow,
|
|
12
13
|
} from "../usage";
|
|
13
14
|
import { isRecord } from "../utils";
|
|
15
|
+
import { DAY_MS, HOUR_MS, WEEK_MS } from "./shared";
|
|
14
16
|
|
|
15
17
|
const DEFAULT_ENDPOINT = "https://api.z.ai";
|
|
16
18
|
const QUOTA_PATH = "/api/monitor/usage/quota/limit";
|
|
17
19
|
const MODEL_USAGE_PATH = "/api/monitor/usage/model-usage";
|
|
18
|
-
const SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000;
|
|
19
|
-
const HOUR_MS = 60 * 60 * 1000;
|
|
20
|
-
const DAY_MS = 24 * HOUR_MS;
|
|
21
|
-
const WEEK_MS = 7 * DAY_MS;
|
|
22
20
|
const MONTH_MS = 30 * DAY_MS;
|
|
23
21
|
|
|
24
22
|
interface ZaiUsageDetail {
|
|
@@ -188,7 +186,7 @@ function requestQuotaLabel(parsed: ZaiUsageLimitItem): string {
|
|
|
188
186
|
}
|
|
189
187
|
|
|
190
188
|
function buildModelUsageUrl(baseUrl: string, now: Date): string {
|
|
191
|
-
const start = new Date(now.getTime() -
|
|
189
|
+
const start = new Date(now.getTime() - WEEK_MS);
|
|
192
190
|
const startTime = formatDate(start);
|
|
193
191
|
const endTime = formatDate(now);
|
|
194
192
|
return `${baseUrl}${MODEL_USAGE_PATH}?startTime=${encodeURIComponent(startTime)}&endTime=${encodeURIComponent(endTime)}`;
|
|
@@ -231,7 +229,7 @@ async function fetchZaiUsage(params: UsageFetchParams, ctx: UsageFetchContext):
|
|
|
231
229
|
const headers: Record<string, string> = {
|
|
232
230
|
Authorization: token,
|
|
233
231
|
"Content-Type": "application/json",
|
|
234
|
-
"User-Agent":
|
|
232
|
+
"User-Agent": USER_AGENT,
|
|
235
233
|
};
|
|
236
234
|
|
|
237
235
|
let payload: ZaiQuotaPayload | null = null;
|
package/src/utils/aws-profile.ts
CHANGED
|
@@ -78,7 +78,45 @@ export function hasConfiguredAwsProfile(profile?: string): boolean {
|
|
|
78
78
|
const configPath = $env.AWS_CONFIG_FILE || path.join(os.homedir(), ".aws", "config");
|
|
79
79
|
const credentialsIni = readAwsIniSync(credentialsPath);
|
|
80
80
|
const configIni = shouldLoadAwsSharedConfig(profile) ? readAwsIniSync(configPath) : undefined;
|
|
81
|
-
|
|
81
|
+
return profileHasCredentialSource(selectedProfile, credentialsIni, configIni, new Set());
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Whether a profile terminates in a usable credential source. Mirrors the
|
|
86
|
+
* resolver's per-profile dispatch (static keys, SSO, `credential_process`,
|
|
87
|
+
* `role_arn` chaining) so the availability probe never diverges from what
|
|
88
|
+
* `resolveAwsCredentials` can actually resolve. `role_arn` chains follow
|
|
89
|
+
* `source_profile` recursively (cycle-guarded by `seen`); MFA-gated roles are
|
|
90
|
+
* treated as unusable because non-interactive resolution cannot supply a token.
|
|
91
|
+
*/
|
|
92
|
+
function profileHasCredentialSource(
|
|
93
|
+
profile: string,
|
|
94
|
+
credentialsIni: AwsIniFile | undefined,
|
|
95
|
+
configIni: AwsIniFile | undefined,
|
|
96
|
+
seen: Set<string>,
|
|
97
|
+
): boolean {
|
|
98
|
+
if (seen.has(profile)) return false;
|
|
99
|
+
seen.add(profile);
|
|
100
|
+
const merged = { ...(configIni?.[profile] ?? {}), ...(credentialsIni?.[profile] ?? {}) };
|
|
101
|
+
if (merged.role_arn) {
|
|
102
|
+
if (merged.web_identity_token_file) return true;
|
|
103
|
+
if (merged.mfa_serial) return false;
|
|
104
|
+
if (merged.credential_source) {
|
|
105
|
+
switch (merged.credential_source) {
|
|
106
|
+
case "Environment":
|
|
107
|
+
return !!($env.AWS_ACCESS_KEY_ID && $env.AWS_SECRET_ACCESS_KEY);
|
|
108
|
+
case "EcsContainer":
|
|
109
|
+
return !!($env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI || $env.AWS_CONTAINER_CREDENTIALS_FULL_URI);
|
|
110
|
+
case "Ec2InstanceMetadata":
|
|
111
|
+
return $env.AWS_EC2_METADATA_DISABLED?.toLowerCase() !== "true";
|
|
112
|
+
default:
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (merged.source_profile)
|
|
117
|
+
return profileHasCredentialSource(merged.source_profile, credentialsIni, configIni, seen);
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
82
120
|
if (merged.aws_access_key_id && merged.aws_secret_access_key) return true;
|
|
83
121
|
if (merged.credential_process) return true;
|
|
84
122
|
if (!merged.sso_account_id || !merged.sso_role_name) return false;
|
|
@@ -59,6 +59,24 @@ export const kCursorExecResolved = Symbol("provider.block.cursorExecResolved");
|
|
|
59
59
|
/** Carries the resolved marker without exposing a string-keyed property. */
|
|
60
60
|
export type CursorExecResolvedCarrier = object & { [kCursorExecResolved]?: true };
|
|
61
61
|
|
|
62
|
+
/** True when a toolCall block was already executed by Cursor's exec channel. */
|
|
63
|
+
export function isCursorExecResolved(block: CursorExecResolvedCarrier | null | undefined): boolean {
|
|
64
|
+
return block?.[kCursorExecResolved] === true;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Copy {@link kCursorExecResolved} onto a cloned/projected toolCall block.
|
|
69
|
+
*
|
|
70
|
+
* Stream projectors (owned/in-band dialect, leaked-thinking heal) rebuild
|
|
71
|
+
* toolCall objects field-by-field. Dropping this marker lets `agent-loop.ts`
|
|
72
|
+
* re-execute a call Cursor already settled — duplicate toolResults and a
|
|
73
|
+
* second bash/write/delete. Partial-JSON is already copied explicitly; this
|
|
74
|
+
* marker is the other load-bearing symbol that must survive the same way.
|
|
75
|
+
*/
|
|
76
|
+
export function copyCursorExecResolved(target: CursorExecResolvedCarrier, source: CursorExecResolvedCarrier): void {
|
|
77
|
+
if (source[kCursorExecResolved] === true) target[kCursorExecResolved] = true;
|
|
78
|
+
}
|
|
79
|
+
|
|
62
80
|
/**
|
|
63
81
|
* Marks a text block synthesized by cross-model thinking demotion in
|
|
64
82
|
* `transformMessages`. Converters that flatten adjacent text blocks into one
|
|
@@ -36,6 +36,7 @@ import type {
|
|
|
36
36
|
} from "../types";
|
|
37
37
|
import {
|
|
38
38
|
clearStreamingPartialJson,
|
|
39
|
+
copyCursorExecResolved,
|
|
39
40
|
getStreamingPartialJson,
|
|
40
41
|
type StreamingPartialJsonCarrier,
|
|
41
42
|
setStreamingPartialJson,
|
|
@@ -49,6 +50,7 @@ function cloneToolCall(source: StreamingToolCall): StreamingToolCall {
|
|
|
49
50
|
const block: StreamingToolCall = { ...source, arguments: source.arguments };
|
|
50
51
|
const partialJson = getStreamingPartialJson(source);
|
|
51
52
|
if (partialJson !== undefined) setStreamingPartialJson(block, partialJson);
|
|
53
|
+
copyCursorExecResolved(block, source);
|
|
52
54
|
return block;
|
|
53
55
|
}
|
|
54
56
|
|
|
@@ -57,6 +59,7 @@ function syncToolCall(target: StreamingToolCall, source: StreamingToolCall): voi
|
|
|
57
59
|
const partialJson = getStreamingPartialJson(source);
|
|
58
60
|
if (partialJson === undefined) clearStreamingPartialJson(target);
|
|
59
61
|
else setStreamingPartialJson(target, partialJson);
|
|
62
|
+
copyCursorExecResolved(target, source);
|
|
60
63
|
}
|
|
61
64
|
|
|
62
65
|
/**
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { USER_AGENT } from "@oh-my-pi/pi-utils";
|
|
2
2
|
|
|
3
3
|
export function getOpenRouterHeaders(): Record<string, string> {
|
|
4
4
|
return {
|
|
5
|
-
"User-Agent":
|
|
5
|
+
"User-Agent": USER_AGENT,
|
|
6
6
|
"HTTP-Referer": "https://omp.sh/",
|
|
7
|
-
"X-OpenRouter-Title": "
|
|
7
|
+
"X-OpenRouter-Title": "omp",
|
|
8
8
|
"X-OpenRouter-Categories": "cli-agent",
|
|
9
9
|
"X-OpenRouter-Cache": "true",
|
|
10
10
|
"X-OpenRouter-Cache-TTL": "3600",
|