@diousk/pi-usage-fast 0.1.0 → 0.3.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 +11 -8
- package/dist/index.ts +160 -120
- package/dist/index.ts.map +3 -3
- package/docs/providers.md +16 -0
- package/package.json +4 -4
- package/src/codex-fast-runtime.ts +2 -2
- package/src/codex-fast.ts +15 -8
- package/src/query.ts +53 -133
- package/src/types.ts +1 -0
- package/src/usage-http.ts +133 -0
package/README.md
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@diousk/pi-usage-fast) [](https://pi.dev) [](./LICENSE)
|
|
4
4
|
|
|
5
|
-
Inspect usage and DeepSeek API balance for Pi's active provider account, query other configured providers, and toggle Fast mode for supported OpenAI Codex models.
|
|
5
|
+
Inspect usage and DeepSeek API balance for Pi's active provider account, query other configured providers, and toggle Fast mode for supported OpenAI and Codex models.
|
|
6
6
|
The extension keeps each provider's native quota, allowance, and spending semantics instead of treating unlike values as equivalent.
|
|
7
7
|
xAI OAuth subscription reporting follows the reviewed Grok Build contract and runs only after an explicit `/usage` action.
|
|
8
8
|
|
|
9
|
-
This fork of [pi-usage](https://github.com/narumiruna/pi-extensions/tree/main/packages/pi-usage) adds Fast mode support for `gpt-6-*` models.
|
|
9
|
+
This fork of [pi-usage](https://github.com/narumiruna/pi-extensions/tree/main/packages/pi-usage) adds Fast mode support for `gpt-6-*` and `gpt-6.<minor>-*` models, including `gpt-6.1-sol`.
|
|
10
10
|
Install it instead of the upstream pi-usage package to avoid duplicate commands and status updates.
|
|
11
11
|
Existing `pi-usage.json` settings remain compatible.
|
|
12
12
|
|
|
@@ -51,19 +51,19 @@ The package declares `dist/index.ts`, so an unbuilt local checkout must run the
|
|
|
51
51
|
|
|
52
52
|
Run `/usage` in TUI or RPC mode to inspect the active provider, refresh its usage, or choose another configured provider.
|
|
53
53
|
When a provider exposes several billing targets, `/usage` asks for one target before querying usage.
|
|
54
|
-
Run `/fast` to toggle Fast mode for a supported active Codex model.
|
|
54
|
+
Run `/fast` to toggle Fast mode for a supported active OpenAI or Codex model.
|
|
55
55
|
|
|
56
56
|
## 💬 Commands
|
|
57
57
|
|
|
58
58
|
| Command | Purpose |
|
|
59
59
|
| --- | --- |
|
|
60
60
|
| `/usage` | Query the active provider's usage, then manage provider queries, preferences, or eligible Codex resets. |
|
|
61
|
-
| `/fast` | Toggle Fast mode for the active supported Codex model. |
|
|
61
|
+
| `/fast` | Toggle Fast mode for the active supported OpenAI or Codex model. |
|
|
62
62
|
|
|
63
63
|
Both commands support TUI and RPC, accept no arguments, and reject print and JSON modes.
|
|
64
64
|
Cross-provider queries require an explicit interactive choice; there are no provider-ID, `--refresh`, or `--all` arguments.
|
|
65
65
|
Requests use the matched provider credentials; see [Security and privacy](#-security-and-privacy).
|
|
66
|
-
Fast mode uses more plan allowance; see [Codex Fast mode](#codex-fast-mode) for eligibility and when changes apply.
|
|
66
|
+
Fast mode uses more plan allowance or API credits; see [Codex Fast mode](#codex-fast-mode) for eligibility and when changes apply.
|
|
67
67
|
|
|
68
68
|
Codex reset redemption requires a freshly matched current OAuth account and explicit confirmation; **after confirmation, its progress view cannot cancel the reset**.
|
|
69
69
|
Read the [query and reset guide](./docs/operations.md) for target selection, cancellation, and safe retry behavior.
|
|
@@ -87,11 +87,12 @@ A successful explicit Fireworks account selection writes the generic field and r
|
|
|
87
87
|
|
|
88
88
|
### Codex Fast mode
|
|
89
89
|
|
|
90
|
-
Run `/fast` without arguments to toggle Fast for the active supported Codex model, or use **Turn Fast mode on/off** in `/usage`.
|
|
91
|
-
Fast
|
|
90
|
+
Run `/fast` without arguments to toggle Fast for the active supported OpenAI or Codex model, or use **Turn Fast mode on/off** in `/usage`.
|
|
91
|
+
Fast requests priority routing and consumes more plan allowance or API credits.
|
|
92
92
|
The `codexFastMode` preference defaults to Off.
|
|
93
93
|
|
|
94
|
-
Fast
|
|
94
|
+
Fast applies to official OpenAI Codex requests at `https://chatgpt.com` and OpenAI Responses requests at `https://api.openai.com`, including the newer ChatGPT sign-in and API-key billing. Supported models are `gpt-5.5`, `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-5.6-luna`, `gpt-6-*`, and `gpt-6.<minor>-*`.
|
|
95
|
+
For example, `gpt-6.1-sol` supports the same toggle, routing, statusline, and priority cost correction as `gpt-6-sol`.
|
|
95
96
|
It sends `service_tier: "priority"` while enabled and explicit `service_tier: "default"` otherwise.
|
|
96
97
|
The statusline adds `fast` only while the preference is effective, for example `codex fast 59% ↻ 2h30m` with the default reset countdown.
|
|
97
98
|
Unsupported models and custom or proxy origins are left unchanged.
|
|
@@ -132,6 +133,7 @@ Currencies and billing targets remain separate.
|
|
|
132
133
|
| Provider | Reported data |
|
|
133
134
|
| --- | --- |
|
|
134
135
|
| OpenAI Codex | Subscription windows, credits, resets, and model buckets |
|
|
136
|
+
| OpenAI | Links to ChatGPT plan usage or API billing; numeric quotas are unavailable through this authentication flow |
|
|
135
137
|
| Kimi For Coding | Plan request windows and a separate booster wallet |
|
|
136
138
|
| Moonshot AI Global/China | Current API balance in USD/CNY |
|
|
137
139
|
| MiniMax Global/China | Token Plan windows or pay-as-you-go API balance |
|
|
@@ -146,6 +148,7 @@ Currencies and billing targets remain separate.
|
|
|
146
148
|
| Z.AI | Coding Plan quota windows, MCP allowance, plan name, and renewal date |
|
|
147
149
|
|
|
148
150
|
Read the [provider reference](./docs/providers.md) for provider IDs, exact endpoints, authentication requirements, normalization rules, statusline examples, limitations, and pinned contract evidence.
|
|
151
|
+
For Pi's newer OpenAI ChatGPT sign-in, `/usage` links to [ChatGPT usage settings](https://chatgpt.com/settings/usage); API-key accounts link to API billing. Numeric quotas are not exposed through this flow, and its token is never sent to legacy Codex endpoints.
|
|
149
152
|
Codex reset redemption requires a freshly matched current OAuth account and explicit confirmation; custom or proxy origins fail before mutation.
|
|
150
153
|
|
|
151
154
|
## 🧭 Current and configured accounts
|
package/dist/index.ts
CHANGED
|
@@ -9,17 +9,18 @@ var CODEX_FAST_MODEL_IDS = /* @__PURE__ */ new Set([
|
|
|
9
9
|
"gpt-5.5",
|
|
10
10
|
"gpt-5.6-luna",
|
|
11
11
|
"gpt-5.6-sol",
|
|
12
|
-
"gpt-5.6-terra"
|
|
12
|
+
"gpt-5.6-terra",
|
|
13
|
+
"gpt-6.1-sol"
|
|
13
14
|
]);
|
|
14
15
|
function supportsCodexFast(modelId) {
|
|
15
|
-
return CODEX_FAST_MODEL_IDS.has(modelId) || /^gpt-6
|
|
16
|
+
return CODEX_FAST_MODEL_IDS.has(modelId) || /^gpt-6(?:\.\d+)?-.+/u.test(modelId);
|
|
16
17
|
}
|
|
17
18
|
function codexFastAvailability(model, enabled) {
|
|
18
|
-
if (model?.provider !== "openai-codex") return { kind: "not-codex" };
|
|
19
|
+
if (model?.provider !== "openai-codex" && model?.provider !== "openai") return { kind: "not-codex" };
|
|
19
20
|
if (!isOfficialCodexModel(model)) {
|
|
20
21
|
return {
|
|
21
22
|
kind: "unavailable",
|
|
22
|
-
reason: "Fast mode requires
|
|
23
|
+
reason: "Fast mode requires an official OpenAI Responses or Codex Responses endpoint."
|
|
23
24
|
};
|
|
24
25
|
}
|
|
25
26
|
if (!supportsCodexFast(model.id)) {
|
|
@@ -64,11 +65,10 @@ function codexFastStatusLabel(status, enabled) {
|
|
|
64
65
|
return status === "codex" ? "codex fast" : `codex fast${status.slice("codex".length)}`;
|
|
65
66
|
}
|
|
66
67
|
function isOfficialCodexModel(model) {
|
|
67
|
-
if (
|
|
68
|
-
return false;
|
|
69
|
-
}
|
|
68
|
+
if (!model) return false;
|
|
70
69
|
try {
|
|
71
|
-
|
|
70
|
+
const origin = new URL(model.baseUrl).origin;
|
|
71
|
+
return model.provider === "openai-codex" && hasApi(model, "openai-codex-responses") && origin === "https://chatgpt.com" || model.provider === "openai" && hasApi(model, "openai-responses") && origin === "https://api.openai.com";
|
|
72
72
|
} catch {
|
|
73
73
|
return false;
|
|
74
74
|
}
|
|
@@ -1969,6 +1969,116 @@ function clampPercent3(value) {
|
|
|
1969
1969
|
return Math.min(100, Math.max(0, value));
|
|
1970
1970
|
}
|
|
1971
1971
|
|
|
1972
|
+
// src/usage-http.ts
|
|
1973
|
+
var MAX_SUCCESS_BODY_BYTES = 64 * 1024;
|
|
1974
|
+
var MAX_ERROR_BODY_BYTES = 4 * 1024;
|
|
1975
|
+
async function fetchProviderJson(url, auth, signal, timeoutMs, description, request = {}) {
|
|
1976
|
+
const controller = new AbortController();
|
|
1977
|
+
let timedOut = false;
|
|
1978
|
+
const abortFromCaller = () => controller.abort();
|
|
1979
|
+
if (signal.aborted) controller.abort();
|
|
1980
|
+
else signal.addEventListener("abort", abortFromCaller, { once: true });
|
|
1981
|
+
const timeout = setTimeout(() => {
|
|
1982
|
+
timedOut = true;
|
|
1983
|
+
controller.abort();
|
|
1984
|
+
}, timeoutMs);
|
|
1985
|
+
try {
|
|
1986
|
+
const headers = { ...auth.headers };
|
|
1987
|
+
if (request.userAgent !== false && !hasHeader(headers, "User-Agent")) {
|
|
1988
|
+
headers["User-Agent"] = "pi-usage";
|
|
1989
|
+
}
|
|
1990
|
+
if (request.body && !hasHeader(headers, "Content-Type")) {
|
|
1991
|
+
headers["Content-Type"] = "application/json";
|
|
1992
|
+
}
|
|
1993
|
+
const response = await fetch(url, {
|
|
1994
|
+
method: request.method ?? "GET",
|
|
1995
|
+
headers,
|
|
1996
|
+
...request.body ? { body: JSON.stringify(request.body) } : {},
|
|
1997
|
+
...request.redirect ? { redirect: request.redirect } : {},
|
|
1998
|
+
signal: controller.signal
|
|
1999
|
+
});
|
|
2000
|
+
if (response.redirected) throw new Error(`${description} refused a redirected response.`);
|
|
2001
|
+
if (controller.signal.aborted) throw Object.assign(new Error("Usage query aborted."), { name: "AbortError" });
|
|
2002
|
+
const text = await readBoundedResponse(
|
|
2003
|
+
response,
|
|
2004
|
+
response.ok ? MAX_SUCCESS_BODY_BYTES : MAX_ERROR_BODY_BYTES,
|
|
2005
|
+
!response.ok,
|
|
2006
|
+
description,
|
|
2007
|
+
controller.signal
|
|
2008
|
+
);
|
|
2009
|
+
if (controller.signal.aborted) throw Object.assign(new Error("Usage query aborted."), { name: "AbortError" });
|
|
2010
|
+
const responseError = request.responseError?.(response.status, text);
|
|
2011
|
+
if (responseError) throw new Error(responseError);
|
|
2012
|
+
if (!response.ok) {
|
|
2013
|
+
throw new Error(
|
|
2014
|
+
`${description} returned ${response.status} ${response.statusText}: ${redactUsageError(text, auth.secrets)}`
|
|
2015
|
+
);
|
|
2016
|
+
}
|
|
2017
|
+
let parsed;
|
|
2018
|
+
try {
|
|
2019
|
+
parsed = JSON.parse(text);
|
|
2020
|
+
} catch (error) {
|
|
2021
|
+
throw new Error(`${description} returned invalid JSON: ${errorMessage(error)}`);
|
|
2022
|
+
}
|
|
2023
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
2024
|
+
throw new Error(`${description} response was not an object.`);
|
|
2025
|
+
}
|
|
2026
|
+
return parsed;
|
|
2027
|
+
} catch (error) {
|
|
2028
|
+
if (timedOut) {
|
|
2029
|
+
throw new Error(`Timed out after ${Math.round(timeoutMs / 1e3)}s while fetching usage.`);
|
|
2030
|
+
}
|
|
2031
|
+
if (signal.aborted) throw Object.assign(new Error("Usage query aborted."), { name: "AbortError" });
|
|
2032
|
+
throw error;
|
|
2033
|
+
} finally {
|
|
2034
|
+
clearTimeout(timeout);
|
|
2035
|
+
signal.removeEventListener("abort", abortFromCaller);
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
async function readBoundedResponse(response, maxBytes, truncateOverflow, description, signal) {
|
|
2039
|
+
if (!response.body) return "";
|
|
2040
|
+
const reader = response.body.getReader();
|
|
2041
|
+
const chunks = [];
|
|
2042
|
+
let total = 0;
|
|
2043
|
+
let truncated = false;
|
|
2044
|
+
const abort = () => void reader.cancel().catch(() => void 0);
|
|
2045
|
+
if (signal.aborted) abort();
|
|
2046
|
+
else signal.addEventListener("abort", abort, { once: true });
|
|
2047
|
+
try {
|
|
2048
|
+
while (true) {
|
|
2049
|
+
const { done, value } = await reader.read();
|
|
2050
|
+
if (done) break;
|
|
2051
|
+
const remaining = maxBytes - total;
|
|
2052
|
+
if (value.byteLength > remaining) {
|
|
2053
|
+
if (remaining > 0) chunks.push(value.subarray(0, remaining));
|
|
2054
|
+
total = maxBytes;
|
|
2055
|
+
truncated = true;
|
|
2056
|
+
await reader.cancel();
|
|
2057
|
+
break;
|
|
2058
|
+
}
|
|
2059
|
+
chunks.push(value);
|
|
2060
|
+
total += value.byteLength;
|
|
2061
|
+
}
|
|
2062
|
+
} finally {
|
|
2063
|
+
signal.removeEventListener("abort", abort);
|
|
2064
|
+
reader.releaseLock();
|
|
2065
|
+
}
|
|
2066
|
+
if (truncated && !truncateOverflow) {
|
|
2067
|
+
throw new Error(`${description} response exceeded ${maxBytes} bytes.`);
|
|
2068
|
+
}
|
|
2069
|
+
const body = new Uint8Array(total);
|
|
2070
|
+
let offset = 0;
|
|
2071
|
+
for (const chunk of chunks) {
|
|
2072
|
+
body.set(chunk, offset);
|
|
2073
|
+
offset += chunk.byteLength;
|
|
2074
|
+
}
|
|
2075
|
+
const text = new TextDecoder().decode(body);
|
|
2076
|
+
return truncated ? `${text}\u2026` : text;
|
|
2077
|
+
}
|
|
2078
|
+
function hasHeader(headers, name) {
|
|
2079
|
+
return Object.keys(headers).some((key) => key.toLowerCase() === name.toLowerCase());
|
|
2080
|
+
}
|
|
2081
|
+
|
|
1972
2082
|
// src/usage-targets.ts
|
|
1973
2083
|
var MAX_TARGETS = 1e3;
|
|
1974
2084
|
var MAX_TARGET_ID_CHARS = 256;
|
|
@@ -2083,10 +2193,30 @@ var XAI_CLIENT_HEADERS = Object.freeze({
|
|
|
2083
2193
|
"x-grok-client-version": "1.0.10",
|
|
2084
2194
|
"x-grok-client-mode": "interactive"
|
|
2085
2195
|
});
|
|
2086
|
-
var MAX_SUCCESS_BODY_BYTES = 64 * 1024;
|
|
2087
|
-
var MAX_ERROR_BODY_BYTES = 4 * 1024;
|
|
2088
2196
|
var AUTH_FINGERPRINT_SALT = randomBytes(32);
|
|
2089
2197
|
var SUPPORTED_ADAPTERS = [
|
|
2198
|
+
{
|
|
2199
|
+
id: "openai",
|
|
2200
|
+
displayName: "OpenAI",
|
|
2201
|
+
semantics: { kind: "consumer-subscription", label: "ChatGPT plan usage" },
|
|
2202
|
+
publishesStatusline: false,
|
|
2203
|
+
async query(auth) {
|
|
2204
|
+
const subscription = auth.chatgptPlan === true;
|
|
2205
|
+
return {
|
|
2206
|
+
providerId: "openai",
|
|
2207
|
+
providerName: subscription ? "OpenAI (ChatGPT)" : "OpenAI API",
|
|
2208
|
+
capturedAt: Date.now(),
|
|
2209
|
+
source: "openai-usage-settings",
|
|
2210
|
+
semantics: subscription ? { kind: "consumer-subscription", label: "ChatGPT plan usage" } : { kind: "api-key", label: "OpenAI API usage" },
|
|
2211
|
+
buckets: [],
|
|
2212
|
+
metrics: [],
|
|
2213
|
+
notes: [
|
|
2214
|
+
subscription ? "Review plan usage and app limits in ChatGPT Settings \u2192 Usage: https://chatgpt.com/settings/usage" : "Review OpenAI API usage and billing: https://platform.openai.com/usage",
|
|
2215
|
+
"Numeric usage is unavailable through this authentication flow; no quota is inferred."
|
|
2216
|
+
]
|
|
2217
|
+
};
|
|
2218
|
+
}
|
|
2219
|
+
},
|
|
2090
2220
|
{
|
|
2091
2221
|
id: "baseten",
|
|
2092
2222
|
displayName: "Baseten",
|
|
@@ -2332,7 +2462,7 @@ async function resolveUsageAuth(ctx, adapter, salt = AUTH_FINGERPRINT_SALT, cred
|
|
|
2332
2462
|
}
|
|
2333
2463
|
const model = candidateModels(ctx, adapter.id).find((candidate) => hasOfficialOrigin(candidate, adapter.id));
|
|
2334
2464
|
if (!model) return void 0;
|
|
2335
|
-
if (["github-copilot", "xai"].includes(adapter.id) && candidateReader?.waitUntilReady && !await candidateReader.waitUntilReady(ctx, adapter.id)) {
|
|
2465
|
+
if (["github-copilot", "xai", "openai"].includes(adapter.id) && candidateReader?.waitUntilReady && !await candidateReader.waitUntilReady(ctx, adapter.id)) {
|
|
2336
2466
|
throw new Error(`${adapter.displayName} OAuth credential readiness failed closed.`);
|
|
2337
2467
|
}
|
|
2338
2468
|
const registry = ctx.modelRegistry;
|
|
@@ -2441,12 +2571,27 @@ async function resolveUsageAuth(ctx, adapter, salt = AUTH_FINGERPRINT_SALT, cred
|
|
|
2441
2571
|
const secrets = [auth.apiKey, headerValue(auth.headers, "Authorization"), authorization].filter(
|
|
2442
2572
|
(value) => Boolean(value)
|
|
2443
2573
|
);
|
|
2574
|
+
let chatgptPlan;
|
|
2575
|
+
if (adapter.id === "openai") {
|
|
2576
|
+
const offered = candidateReader ? await candidateReader(ctx, adapter.id) : fallbackOAuthCredentialCandidates(adapter.id, credentialReader);
|
|
2577
|
+
if (!offered.ok) throw new Error("OpenAI OAuth credential discovery failed closed.");
|
|
2578
|
+
const access = bearerToken(authorization);
|
|
2579
|
+
chatgptPlan = offered.candidates.some(
|
|
2580
|
+
(credential) => credential.access === access && Array.isArray(credential.scopes) && credential.scopes.includes("chatgpt.tokens.use.direct")
|
|
2581
|
+
);
|
|
2582
|
+
if (!chatgptPlan && providerResult?.source === "OAuth") {
|
|
2583
|
+
throw new Error(
|
|
2584
|
+
"The active OpenAI OAuth credential does not match an available ChatGPT plan credential; reload after account activation."
|
|
2585
|
+
);
|
|
2586
|
+
}
|
|
2587
|
+
}
|
|
2444
2588
|
return finalize({
|
|
2445
2589
|
apiKey: auth.apiKey,
|
|
2446
2590
|
headers,
|
|
2447
2591
|
fingerprint: "",
|
|
2448
2592
|
secrets,
|
|
2449
|
-
model
|
|
2593
|
+
model,
|
|
2594
|
+
...chatgptPlan !== void 0 ? { chatgptPlan } : {}
|
|
2450
2595
|
});
|
|
2451
2596
|
}
|
|
2452
2597
|
async function queryProviderUsage(adapter, auth, signal, timeoutMs, guard, targetOrSettings) {
|
|
@@ -2522,109 +2667,6 @@ function candidateModels(ctx, providerId) {
|
|
|
2522
2667
|
for (const model of ctx.modelRegistry.getAll()) add(model);
|
|
2523
2668
|
return candidates;
|
|
2524
2669
|
}
|
|
2525
|
-
async function fetchProviderJson(url, auth, signal, timeoutMs, description, request = {}) {
|
|
2526
|
-
const controller = new AbortController();
|
|
2527
|
-
let timedOut = false;
|
|
2528
|
-
const abortFromCaller = () => controller.abort();
|
|
2529
|
-
if (signal.aborted) controller.abort();
|
|
2530
|
-
else signal.addEventListener("abort", abortFromCaller, { once: true });
|
|
2531
|
-
const timeout = setTimeout(() => {
|
|
2532
|
-
timedOut = true;
|
|
2533
|
-
controller.abort();
|
|
2534
|
-
}, timeoutMs);
|
|
2535
|
-
try {
|
|
2536
|
-
const headers = { ...auth.headers };
|
|
2537
|
-
if (request.userAgent !== false && !hasHeader(headers, "User-Agent")) {
|
|
2538
|
-
headers["User-Agent"] = "pi-usage";
|
|
2539
|
-
}
|
|
2540
|
-
if (request.body && !hasHeader(headers, "Content-Type")) {
|
|
2541
|
-
headers["Content-Type"] = "application/json";
|
|
2542
|
-
}
|
|
2543
|
-
const response = await fetch(url, {
|
|
2544
|
-
method: request.method ?? "GET",
|
|
2545
|
-
headers,
|
|
2546
|
-
...request.body ? { body: JSON.stringify(request.body) } : {},
|
|
2547
|
-
...request.redirect ? { redirect: request.redirect } : {},
|
|
2548
|
-
signal: controller.signal
|
|
2549
|
-
});
|
|
2550
|
-
if (response.redirected) throw new Error(`${description} refused a redirected response.`);
|
|
2551
|
-
if (controller.signal.aborted) throw Object.assign(new Error("Usage query aborted."), { name: "AbortError" });
|
|
2552
|
-
const text = await readBoundedResponse(
|
|
2553
|
-
response,
|
|
2554
|
-
response.ok ? MAX_SUCCESS_BODY_BYTES : MAX_ERROR_BODY_BYTES,
|
|
2555
|
-
!response.ok,
|
|
2556
|
-
description,
|
|
2557
|
-
controller.signal
|
|
2558
|
-
);
|
|
2559
|
-
if (controller.signal.aborted) throw Object.assign(new Error("Usage query aborted."), { name: "AbortError" });
|
|
2560
|
-
const responseError = request.responseError?.(response.status, text);
|
|
2561
|
-
if (responseError) throw new Error(responseError);
|
|
2562
|
-
if (!response.ok) {
|
|
2563
|
-
throw new Error(
|
|
2564
|
-
`${description} returned ${response.status} ${response.statusText}: ${redactUsageError(text, auth.secrets)}`
|
|
2565
|
-
);
|
|
2566
|
-
}
|
|
2567
|
-
let parsed;
|
|
2568
|
-
try {
|
|
2569
|
-
parsed = JSON.parse(text);
|
|
2570
|
-
} catch (error) {
|
|
2571
|
-
throw new Error(`${description} returned invalid JSON: ${errorMessage(error)}`);
|
|
2572
|
-
}
|
|
2573
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
2574
|
-
throw new Error(`${description} response was not an object.`);
|
|
2575
|
-
}
|
|
2576
|
-
return parsed;
|
|
2577
|
-
} catch (error) {
|
|
2578
|
-
if (timedOut) {
|
|
2579
|
-
throw new Error(`Timed out after ${Math.round(timeoutMs / 1e3)}s while fetching usage.`);
|
|
2580
|
-
}
|
|
2581
|
-
if (signal.aborted) throw Object.assign(new Error("Usage query aborted."), { name: "AbortError" });
|
|
2582
|
-
throw error;
|
|
2583
|
-
} finally {
|
|
2584
|
-
clearTimeout(timeout);
|
|
2585
|
-
signal.removeEventListener("abort", abortFromCaller);
|
|
2586
|
-
}
|
|
2587
|
-
}
|
|
2588
|
-
async function readBoundedResponse(response, maxBytes, truncateOverflow, description, signal) {
|
|
2589
|
-
if (!response.body) return "";
|
|
2590
|
-
const reader = response.body.getReader();
|
|
2591
|
-
const chunks = [];
|
|
2592
|
-
let total = 0;
|
|
2593
|
-
let truncated = false;
|
|
2594
|
-
const abort = () => void reader.cancel().catch(() => void 0);
|
|
2595
|
-
if (signal.aborted) abort();
|
|
2596
|
-
else signal.addEventListener("abort", abort, { once: true });
|
|
2597
|
-
try {
|
|
2598
|
-
while (true) {
|
|
2599
|
-
const { done, value } = await reader.read();
|
|
2600
|
-
if (done) break;
|
|
2601
|
-
const remaining = maxBytes - total;
|
|
2602
|
-
if (value.byteLength > remaining) {
|
|
2603
|
-
if (remaining > 0) chunks.push(value.subarray(0, remaining));
|
|
2604
|
-
total = maxBytes;
|
|
2605
|
-
truncated = true;
|
|
2606
|
-
await reader.cancel();
|
|
2607
|
-
break;
|
|
2608
|
-
}
|
|
2609
|
-
chunks.push(value);
|
|
2610
|
-
total += value.byteLength;
|
|
2611
|
-
}
|
|
2612
|
-
} finally {
|
|
2613
|
-
signal.removeEventListener("abort", abort);
|
|
2614
|
-
reader.releaseLock();
|
|
2615
|
-
}
|
|
2616
|
-
if (truncated && !truncateOverflow) {
|
|
2617
|
-
throw new Error(`${description} response exceeded ${maxBytes} bytes.`);
|
|
2618
|
-
}
|
|
2619
|
-
const body = new Uint8Array(total);
|
|
2620
|
-
let offset = 0;
|
|
2621
|
-
for (const chunk of chunks) {
|
|
2622
|
-
body.set(chunk, offset);
|
|
2623
|
-
offset += chunk.byteLength;
|
|
2624
|
-
}
|
|
2625
|
-
const text = new TextDecoder().decode(body);
|
|
2626
|
-
return truncated ? `${text}\u2026` : text;
|
|
2627
|
-
}
|
|
2628
2670
|
function resolveXaiUsageAuth(auth, model, salt, candidates) {
|
|
2629
2671
|
const resolvedAccess = bearerToken(headerValue(auth.headers, "Authorization")) ?? auth.apiKey;
|
|
2630
2672
|
if (!resolvedAccess) throw new Error("xAI runtime authentication was incomplete.");
|
|
@@ -2770,6 +2812,7 @@ function hasOfficialUrlOrigin(value, providerId) {
|
|
|
2770
2812
|
return ["https://inference.baseten.co", "https://api.baseten.co"].includes(url.origin);
|
|
2771
2813
|
}
|
|
2772
2814
|
if (providerId === "openai-codex") return url.origin === "https://chatgpt.com";
|
|
2815
|
+
if (providerId === "openai") return url.origin === "https://api.openai.com";
|
|
2773
2816
|
if (providerId === "deepseek") return url.origin === "https://api.deepseek.com";
|
|
2774
2817
|
if (providerId === "fireworks") return url.origin === "https://api.fireworks.ai";
|
|
2775
2818
|
if (providerId === "openrouter") return url.origin === "https://openrouter.ai";
|
|
@@ -2795,9 +2838,6 @@ function headerValue(headers, name) {
|
|
|
2795
2838
|
const entry = Object.entries(headers ?? {}).find(([candidate]) => candidate.toLowerCase() === name.toLowerCase());
|
|
2796
2839
|
return entry?.[1] ?? void 0;
|
|
2797
2840
|
}
|
|
2798
|
-
function hasHeader(headers, name) {
|
|
2799
|
-
return Object.keys(headers).some((key) => key.toLowerCase() === name.toLowerCase());
|
|
2800
|
-
}
|
|
2801
2841
|
function validatedXaiUserId(value) {
|
|
2802
2842
|
if (typeof value !== "string" || !/^[A-Za-z0-9._~-]{1,128}$/u.test(value)) {
|
|
2803
2843
|
throw new Error("xAI consumer identity returned an unsafe canonical user ID.");
|
|
@@ -3999,7 +4039,7 @@ import { randomUUID as randomUUID2 } from "node:crypto";
|
|
|
3999
4039
|
|
|
4000
4040
|
// src/codex-fast-runtime.ts
|
|
4001
4041
|
var NO_FAST_REQUEST = /* @__PURE__ */ Symbol("no-fast-request");
|
|
4002
|
-
var FAST_USAGE_WARNING = "Fast
|
|
4042
|
+
var FAST_USAGE_WARNING = "Fast uses priority routing and consumes more plan allowance or API credits.";
|
|
4003
4043
|
function registerCodexFastMode(pi, settingsRuntime, refreshStatus, options = {}) {
|
|
4004
4044
|
let sessionController = new AbortController();
|
|
4005
4045
|
let generation = 0;
|
|
@@ -4036,7 +4076,7 @@ function registerCodexFastMode(pi, settingsRuntime, refreshStatus, options = {})
|
|
|
4036
4076
|
if (!ctx.hasUI) throw new Error("/fast requires TUI or RPC mode.");
|
|
4037
4077
|
const availability = codexFastAvailability(ctx.model, settingsRuntime.get().settings.codexFastMode);
|
|
4038
4078
|
if (availability.kind === "not-codex") {
|
|
4039
|
-
ctx.ui.notify("/fast
|
|
4079
|
+
ctx.ui.notify("/fast requires a supported OpenAI or OpenAI Codex model.", "warning");
|
|
4040
4080
|
return;
|
|
4041
4081
|
}
|
|
4042
4082
|
if (availability.kind === "unavailable") {
|