omnigateway 0.12.12 → 0.13.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 +1 -0
- package/bin/omni.js +414 -25
- package/gateway.js +379 -15
- package/package.json +1 -1
- package/public/assets/{Chip-D4qvhu1D.js → Chip-D0XSbQUr.js} +1 -1
- package/public/assets/{CopyValue-SnsT2LML.js → CopyValue-DAlmkISB.js} +3 -3
- package/public/assets/{Field-BYBri2NV.js → Field-DG1317zr.js} +1 -1
- package/public/assets/Lamp-Dk9XrTCg.js +26 -0
- package/public/assets/{Panel-CWmOTDAP.js → Panel-CsovmtxF.js} +23 -4
- package/public/assets/Rack-CDUOJkS2.js +483 -0
- package/public/assets/{Readout-BTh732hP.js → Readout-DlT5oxGO.js} +1 -1
- package/public/assets/RequestTable-i3TtCB9m.js +93 -0
- package/public/assets/{SummaryDeck-7ivuca39.js → SummaryDeck-DrzTQQo5.js} +2 -2
- package/public/assets/{Toggle-t82IhqEt.js → Toggle-CdMD58sy.js} +1 -1
- package/public/assets/{TokenBreakdown-D1Wtza1y.js → TokenBreakdown-BC6nqo1v.js} +1 -1
- package/public/assets/{WindowChart-BeGPsccS.js → WindowChart-Bqbl-KFO.js} +2 -2
- package/public/assets/_app-CSru2YTE.js +1 -0
- package/public/assets/_app.accounts-2syjcpos.js +70 -0
- package/public/assets/_app.console-Dxkf_zSS.js +24 -0
- package/public/assets/_app.database-CqSoTi2l.js +24 -0
- package/public/assets/_app.index-BV1kJJjA.js +67 -0
- package/public/assets/_app.keys-Dq5O0AG9.js +92 -0
- package/public/assets/_app.logs-PjRK5wqy.js +46 -0
- package/public/assets/_app.models-BfmeeB5e.js +148 -0
- package/public/assets/_app.plugins._pluginId-BSlXzAHP.js +20 -0
- package/public/assets/{_app.settings-C3GzeJ4q.js → _app.settings-5i-vvT-H.js} +7 -7
- package/public/assets/_app.usage--5qFtU1W.js +76 -0
- package/public/assets/chevron-down-Bb5ZGvwk.js +1 -0
- package/public/assets/chevron-right-hwF5mD6-.js +1 -0
- package/public/assets/{client-k1tkFSNH.js → client-DUhmc9Si.js} +4 -4
- package/public/assets/{dist-D03q-60i.js → dist-BBGxva0Y.js} +1 -1
- package/public/assets/index-Bzh27906.js +14 -0
- package/public/assets/{login-DW2Ipp21.js → login-DbXxdOl5.js} +4 -4
- package/public/assets/plus-BgcHxxIr.js +1 -0
- package/public/assets/{preload-helper-BPaHpbO_.js → preload-helper-B7KDmXJ9.js} +1 -1
- package/public/assets/rotate-ccw-Do43Argv.js +1 -0
- package/public/assets/{shared-Dtkw6SE0.js → shared-DDpLUhCp.js} +2 -2
- package/public/assets/{trash-D4QWzEK_.js → trash-00950_yY.js} +1 -1
- package/public/index.html +5 -5
- package/public/assets/Lamp-DkvXKB-B.js +0 -26
- package/public/assets/Rack-CEuCdVXc.js +0 -483
- package/public/assets/RequestTable-D8L6iVJV.js +0 -93
- package/public/assets/_app-Drf03PWk.js +0 -1
- package/public/assets/_app.accounts-W1USUo2Q.js +0 -70
- package/public/assets/_app.console-Co9d2ZiD.js +0 -44
- package/public/assets/_app.database-CtFJLcfE.js +0 -24
- package/public/assets/_app.index-DigYfob3.js +0 -67
- package/public/assets/_app.keys-B1fRMyU-.js +0 -92
- package/public/assets/_app.logs-CFixPsUw.js +0 -60
- package/public/assets/_app.models-Dpl1O2RE.js +0 -148
- package/public/assets/_app.plugins._pluginId-BK0ADugW.js +0 -20
- package/public/assets/_app.usage-D2qlcEp4.js +0 -76
- package/public/assets/chevron-down-Bs0tSOyA.js +0 -1
- package/public/assets/chevron-right-DSaI1kFo.js +0 -1
- package/public/assets/index-Bk9sabex.js +0 -14
- package/public/assets/plus-BCfjC6nk.js +0 -1
package/README.md
CHANGED
|
@@ -251,6 +251,7 @@ Use `--db <path>` to point one command somewhere else.
|
|
|
251
251
|
| `omni usage` | spend and tokens, by provider, model, key, or day |
|
|
252
252
|
| `omni quota` | provider quota per window: use, burn rate, and when it runs out |
|
|
253
253
|
| `omni quota refresh <id> \| --all` | read provider quota now, without waiting for the next poll |
|
|
254
|
+
| `omni quota resets <id>` / `omni quota reset <id>` | list / spend a banked Codex or Claude quota reset (irreversible) |
|
|
254
255
|
| `omni connect <provider>` | authorize an account from the terminal |
|
|
255
256
|
| `omni credentials …` | list, show, enable, disable, `set`, `rm`, refresh, `add-key`, health |
|
|
256
257
|
| `omni models …` | list, show, put, `rm`, `dry-run`, `catalog` |
|
package/bin/omni.js
CHANGED
|
@@ -24739,12 +24739,18 @@ var ANTHROPIC_MODELS = {
|
|
|
24739
24739
|
};
|
|
24740
24740
|
var ONE_M_SUFFIX = "[1m]";
|
|
24741
24741
|
var DATED_SUFFIX = /-\d{8}$/;
|
|
24742
|
-
function
|
|
24742
|
+
function anthropicChoice(model) {
|
|
24743
24743
|
let id = model.trim();
|
|
24744
24744
|
if (id.toLowerCase().endsWith(ONE_M_SUFFIX))
|
|
24745
24745
|
id = id.slice(0, -ONE_M_SUFFIX.length).trim();
|
|
24746
24746
|
id = id.replace(DATED_SUFFIX, "");
|
|
24747
|
-
return ANTHROPIC_MODELS.models.find((m) => m.id === id)
|
|
24747
|
+
return ANTHROPIC_MODELS.models.find((m) => m.id === id);
|
|
24748
|
+
}
|
|
24749
|
+
function anthropicReasoningForm(model) {
|
|
24750
|
+
return anthropicChoice(model)?.reasoningForm ?? "adaptive";
|
|
24751
|
+
}
|
|
24752
|
+
function anthropicMaxOutputTokens(model) {
|
|
24753
|
+
return anthropicChoice(model)?.limits.maxOutputTokens;
|
|
24748
24754
|
}
|
|
24749
24755
|
|
|
24750
24756
|
// packages/providers/src/anthropic/descriptor.ts
|
|
@@ -25635,7 +25641,8 @@ function codecAdapter(id, capabilities2, codec2, origins) {
|
|
|
25635
25641
|
credentials: req.credentials,
|
|
25636
25642
|
fail,
|
|
25637
25643
|
...req.requestId === undefined ? {} : { requestId: req.requestId },
|
|
25638
|
-
...req.autoCache === undefined ? {} : { autoCacheEnabled: req.autoCache }
|
|
25644
|
+
...req.autoCache === undefined ? {} : { autoCacheEnabled: req.autoCache },
|
|
25645
|
+
...req.maxOutputTokens === undefined ? {} : { maxOutputTokens: req.maxOutputTokens }
|
|
25639
25646
|
}));
|
|
25640
25647
|
const described = built?.request ?? {};
|
|
25641
25648
|
const request2 = {
|
|
@@ -27340,6 +27347,12 @@ function addAutoCacheBreakpoints(body, req, note2) {
|
|
|
27340
27347
|
lastHistory.cache_control = { type: "ephemeral" };
|
|
27341
27348
|
note2("anthropic:history-cache-breakpoint-added");
|
|
27342
27349
|
}
|
|
27350
|
+
function defaultMaxTokens(model, saved) {
|
|
27351
|
+
const ceiling = anthropicMaxOutputTokens(model);
|
|
27352
|
+
if (saved === undefined)
|
|
27353
|
+
return ceiling ?? 4096;
|
|
27354
|
+
return ceiling === undefined ? saved : Math.min(saved, ceiling);
|
|
27355
|
+
}
|
|
27343
27356
|
function toWire(req, model, opts) {
|
|
27344
27357
|
const cloak = opts.cloak ?? null;
|
|
27345
27358
|
const degradations = [];
|
|
@@ -27386,7 +27399,7 @@ function toWire(req, model, opts) {
|
|
|
27386
27399
|
}
|
|
27387
27400
|
return [{ role: m.role, content: encodeSystemTurn(m.content, cloak) }];
|
|
27388
27401
|
}),
|
|
27389
|
-
max_tokens: req.maxTokens ??
|
|
27402
|
+
max_tokens: req.maxTokens ?? defaultMaxTokens(model, opts.maxOutputTokens),
|
|
27390
27403
|
stream: req.stream
|
|
27391
27404
|
};
|
|
27392
27405
|
if (systemCache.retarget !== undefined) {
|
|
@@ -27525,7 +27538,8 @@ var anthropicCodec = {
|
|
|
27525
27538
|
const { body, degradations } = toWire(input2.request, input2.model, {
|
|
27526
27539
|
oauth,
|
|
27527
27540
|
cloak,
|
|
27528
|
-
autoCache: input2.autoCacheEnabled === true
|
|
27541
|
+
autoCache: input2.autoCacheEnabled === true,
|
|
27542
|
+
...input2.maxOutputTokens === undefined ? {} : { maxOutputTokens: input2.maxOutputTokens }
|
|
27529
27543
|
});
|
|
27530
27544
|
const firstUser = input2.request.messages.find((m) => m.role === "user");
|
|
27531
27545
|
const firstText = firstUser?.content.find((b) => b.type === "text");
|
|
@@ -27858,8 +27872,125 @@ var anthropicOAuthFlow = {
|
|
|
27858
27872
|
if (!usageReadable(res.status, "anthropic"))
|
|
27859
27873
|
return null;
|
|
27860
27874
|
return parseAnthropicUsage(parsed(res.body), now());
|
|
27875
|
+
},
|
|
27876
|
+
async* resetCredits({ secrets }) {
|
|
27877
|
+
if (secrets.accessToken === null)
|
|
27878
|
+
return null;
|
|
27879
|
+
const res = yield getJsonRequest(RESET_STATUS_URL, anthropicProfile, {
|
|
27880
|
+
accessToken: secrets.accessToken,
|
|
27881
|
+
extraHeaders: SIDE_HEADERS
|
|
27882
|
+
});
|
|
27883
|
+
if (!usageReadable(res.status, "anthropic"))
|
|
27884
|
+
return null;
|
|
27885
|
+
const root = recordOf(parsed(res.body));
|
|
27886
|
+
return root === null ? null : parseResetGrants(root.cedar_ember ?? null);
|
|
27887
|
+
},
|
|
27888
|
+
async* redeemReset({ secrets, creditId, requestId, fail }) {
|
|
27889
|
+
if (secrets.accessToken === null)
|
|
27890
|
+
throw fail("AUTH", "credential holds no access token");
|
|
27891
|
+
const grantId = grantOf(creditId);
|
|
27892
|
+
if (grantId === null)
|
|
27893
|
+
throw fail("CONFLICT", "that reset credit is not available");
|
|
27894
|
+
const profile = yield getJsonRequest(PROFILE_URL, anthropicProfile, {
|
|
27895
|
+
accessToken: secrets.accessToken,
|
|
27896
|
+
extraHeaders: SIDE_HEADERS
|
|
27897
|
+
});
|
|
27898
|
+
if (profile.status < 200 || profile.status >= 300) {
|
|
27899
|
+
throw fail(redeemErrorCode(profile.status), `profile read refused: http_${profile.status}`, {
|
|
27900
|
+
status: profile.status
|
|
27901
|
+
});
|
|
27902
|
+
}
|
|
27903
|
+
const orgId = orgIdOf(parsed(profile.body));
|
|
27904
|
+
if (orgId === null)
|
|
27905
|
+
throw fail("UPSTREAM", "profile names no organization");
|
|
27906
|
+
const res = yield postJsonRequest(`https://api.anthropic.com/api/organizations/${orgId}/reset_rate_limits`, anthropicProfile, {
|
|
27907
|
+
contentType: "application/json",
|
|
27908
|
+
body: JSON.stringify({ program: RESET_PROGRAM, grant_id: grantId, request_id: requestId }),
|
|
27909
|
+
extraHeaders: [["Authorization", `Bearer ${secrets.accessToken}`], ...SIDE_HEADERS]
|
|
27910
|
+
});
|
|
27911
|
+
if (res.status < 200 || res.status >= 300) {
|
|
27912
|
+
throw fail(redeemErrorCode(res.status), `reset claim refused: http_${res.status}`, {
|
|
27913
|
+
status: res.status
|
|
27914
|
+
});
|
|
27915
|
+
}
|
|
27916
|
+
return claimOutcome(parsed(res.body), fail);
|
|
27861
27917
|
}
|
|
27862
27918
|
};
|
|
27919
|
+
var RESET_PROGRAM = "cedar_ember";
|
|
27920
|
+
var RESET_STATUS_URL = `${USAGE_URL}?cedar_ember=1&skip_spend=1`;
|
|
27921
|
+
var PROFILE_URL = "https://api.anthropic.com/api/oauth/profile";
|
|
27922
|
+
var ORG_ID = /^[A-Za-z0-9-]{1,64}$/;
|
|
27923
|
+
var SIDE_HEADERS = [["anthropic-beta", "oauth-2025-04-20"]];
|
|
27924
|
+
var USE_SUFFIX = /-u(\d+)$/;
|
|
27925
|
+
function creditIdOf(grantId, left) {
|
|
27926
|
+
return `${grantId}-u${left}`;
|
|
27927
|
+
}
|
|
27928
|
+
function grantOf(creditId) {
|
|
27929
|
+
const grant = creditId.replace(USE_SUFFIX, "");
|
|
27930
|
+
return grant === creditId || grant === "" ? null : grant;
|
|
27931
|
+
}
|
|
27932
|
+
function orgIdOf(value) {
|
|
27933
|
+
const org = recordOf(recordOf(value)?.organization);
|
|
27934
|
+
const uuid3 = org?.uuid;
|
|
27935
|
+
return typeof uuid3 === "string" && ORG_ID.test(uuid3) ? uuid3 : null;
|
|
27936
|
+
}
|
|
27937
|
+
function parseResetGrants(value) {
|
|
27938
|
+
if (value === null)
|
|
27939
|
+
return { available: 0, credits: [] };
|
|
27940
|
+
const block = recordOf(value);
|
|
27941
|
+
if (block === null || !Array.isArray(block.grants))
|
|
27942
|
+
return null;
|
|
27943
|
+
const next = typeof block.next_grant_id === "string" ? block.next_grant_id : null;
|
|
27944
|
+
const atLimit = block.at_limit === true;
|
|
27945
|
+
let available = 0;
|
|
27946
|
+
const credits = block.grants.flatMap((raw) => {
|
|
27947
|
+
const g = recordOf(raw);
|
|
27948
|
+
if (g === null || typeof g.id !== "string")
|
|
27949
|
+
return [];
|
|
27950
|
+
const left = typeof g.resets_left === "number" ? g.resets_left : 0;
|
|
27951
|
+
const claimable = left > 0 && g.usable_now === true && g.paused !== true && (g.use_requires_limit !== true || atLimit) && (next === null || next === g.id);
|
|
27952
|
+
if (claimable)
|
|
27953
|
+
available += left;
|
|
27954
|
+
return [
|
|
27955
|
+
{
|
|
27956
|
+
id: creditIdOf(g.id, left),
|
|
27957
|
+
status: claimable ? "available" : left > 0 ? "unavailable" : "used",
|
|
27958
|
+
title: typeof g.label === "string" && g.label !== "" ? g.label : null,
|
|
27959
|
+
grantedAt: isoOrNull(g.starts_at),
|
|
27960
|
+
expiresAt: isoOrNull(g.ends_at)
|
|
27961
|
+
}
|
|
27962
|
+
];
|
|
27963
|
+
});
|
|
27964
|
+
return { available, credits };
|
|
27965
|
+
}
|
|
27966
|
+
function claimOutcome(value, fail) {
|
|
27967
|
+
const body = recordOf(value);
|
|
27968
|
+
const result = typeof body?.result === "string" ? body.result : null;
|
|
27969
|
+
if (result === "reset") {
|
|
27970
|
+
return { windowsReset: Array.isArray(body?.cleared) ? body.cleared.length : null };
|
|
27971
|
+
}
|
|
27972
|
+
if (result === "cooldown")
|
|
27973
|
+
throw fail("RATE_LIMIT", "reset claim refused: cooldown");
|
|
27974
|
+
if (result === "already_used" || result === "not_limited" || result === "ineligible") {
|
|
27975
|
+
throw fail("CONFLICT", `reset claim refused: ${result}`);
|
|
27976
|
+
}
|
|
27977
|
+
throw fail("UPSTREAM", "reset claim answered without a reset");
|
|
27978
|
+
}
|
|
27979
|
+
function redeemErrorCode(status) {
|
|
27980
|
+
if (status === 429)
|
|
27981
|
+
return "RATE_LIMIT";
|
|
27982
|
+
if (status === 401 || status === 403)
|
|
27983
|
+
return "AUTH";
|
|
27984
|
+
if (status >= 400 && status < 500)
|
|
27985
|
+
return "CONFLICT";
|
|
27986
|
+
return "UPSTREAM";
|
|
27987
|
+
}
|
|
27988
|
+
function isoOrNull(value) {
|
|
27989
|
+
if (typeof value !== "string")
|
|
27990
|
+
return null;
|
|
27991
|
+
const at = Date.parse(value);
|
|
27992
|
+
return Number.isNaN(at) ? null : at;
|
|
27993
|
+
}
|
|
27863
27994
|
// packages/providers/src/antigravity/codec.ts
|
|
27864
27995
|
import { createHash as createHash2 } from "crypto";
|
|
27865
27996
|
|
|
@@ -29181,7 +29312,7 @@ var grokOAuthFlow = {
|
|
|
29181
29312
|
|
|
29182
29313
|
// packages/providers/src/kilo/oauth.ts
|
|
29183
29314
|
var CODES_URL = "https://api.kilo.ai/api/device-auth/codes";
|
|
29184
|
-
var
|
|
29315
|
+
var PROFILE_URL2 = "https://api.kilo.ai/api/profile";
|
|
29185
29316
|
var FALLBACK_VERIFICATION_URL = "https://kilo.ai";
|
|
29186
29317
|
var DEFAULT_EXPIRES_SECONDS = 300;
|
|
29187
29318
|
var POLL_INTERVAL_SECONDS = 3;
|
|
@@ -29213,7 +29344,7 @@ function deviceCodeFrom(value, fail) {
|
|
|
29213
29344
|
}
|
|
29214
29345
|
async function* orgIdFor(accessToken) {
|
|
29215
29346
|
try {
|
|
29216
|
-
const res = yield getJsonRequest(
|
|
29347
|
+
const res = yield getJsonRequest(PROFILE_URL2, kiloProfile, { accessToken });
|
|
29217
29348
|
if (res.status < 200 || res.status >= 300)
|
|
29218
29349
|
return null;
|
|
29219
29350
|
const record2 = recordFrom(parsed(res.body));
|
|
@@ -29721,6 +29852,7 @@ var AUTHORIZE_URL3 = "https://auth.openai.com/oauth/authorize";
|
|
|
29721
29852
|
var TOKEN_URL5 = "https://auth.openai.com/oauth/token";
|
|
29722
29853
|
var SCOPES4 = "openid profile email offline_access";
|
|
29723
29854
|
var USAGE_URL3 = "https://chatgpt.com/backend-api/wham/usage";
|
|
29855
|
+
var RESET_CREDITS_URL = "https://chatgpt.com/backend-api/wham/rate-limit-reset-credits";
|
|
29724
29856
|
function isRecord7(value) {
|
|
29725
29857
|
return typeof value === "object" && value !== null;
|
|
29726
29858
|
}
|
|
@@ -29874,8 +30006,78 @@ var openaiOAuthFlow = {
|
|
|
29874
30006
|
if (!usageReadable(res.status, "openai"))
|
|
29875
30007
|
return null;
|
|
29876
30008
|
return parseOpenAIUsage(parsed(res.body), now());
|
|
30009
|
+
},
|
|
30010
|
+
async* resetCredits({ secrets, providerData }) {
|
|
30011
|
+
if (secrets.accessToken === null)
|
|
30012
|
+
return null;
|
|
30013
|
+
const res = yield getJsonRequest(RESET_CREDITS_URL, openaiProfile, {
|
|
30014
|
+
accessToken: secrets.accessToken,
|
|
30015
|
+
extraHeaders: accountHeader(providerData)
|
|
30016
|
+
});
|
|
30017
|
+
if (!usageReadable(res.status, "openai"))
|
|
30018
|
+
return null;
|
|
30019
|
+
return parseResetCredits(parsed(res.body));
|
|
30020
|
+
},
|
|
30021
|
+
async* redeemReset({ secrets, providerData, creditId, requestId, fail }) {
|
|
30022
|
+
if (secrets.accessToken === null)
|
|
30023
|
+
throw fail("AUTH", "credential holds no access token");
|
|
30024
|
+
const res = yield postJsonRequest(`${RESET_CREDITS_URL}/consume`, openaiProfile, {
|
|
30025
|
+
contentType: "application/json",
|
|
30026
|
+
body: JSON.stringify({ credit_id: creditId, redeem_request_id: requestId }),
|
|
30027
|
+
extraHeaders: [
|
|
30028
|
+
["Authorization", `Bearer ${secrets.accessToken}`],
|
|
30029
|
+
...accountHeader(providerData)
|
|
30030
|
+
]
|
|
30031
|
+
});
|
|
30032
|
+
if (res.status < 200 || res.status >= 300) {
|
|
30033
|
+
throw fail(redeemErrorCode2(res.status), `reset credit redeem refused: http_${res.status}`, {
|
|
30034
|
+
status: res.status
|
|
30035
|
+
});
|
|
30036
|
+
}
|
|
30037
|
+
const body = recordOf(parsed(res.body));
|
|
30038
|
+
const windowsReset = body === null ? null : numberOf(body, ["windows_reset"]);
|
|
30039
|
+
return { windowsReset };
|
|
29877
30040
|
}
|
|
29878
30041
|
};
|
|
30042
|
+
function accountHeader(providerData) {
|
|
30043
|
+
const accountId = accountIdFromProviderData(providerData);
|
|
30044
|
+
return accountId === null ? [] : [["chatgpt-account-id", accountId]];
|
|
30045
|
+
}
|
|
30046
|
+
function redeemErrorCode2(status) {
|
|
30047
|
+
if (status === 429)
|
|
30048
|
+
return "RATE_LIMIT";
|
|
30049
|
+
if (status === 401 || status === 403)
|
|
30050
|
+
return "AUTH";
|
|
30051
|
+
if (status >= 400 && status < 500)
|
|
30052
|
+
return "CONFLICT";
|
|
30053
|
+
return "UPSTREAM";
|
|
30054
|
+
}
|
|
30055
|
+
function isoOrNull2(value) {
|
|
30056
|
+
if (typeof value !== "string")
|
|
30057
|
+
return null;
|
|
30058
|
+
const at = Date.parse(value);
|
|
30059
|
+
return Number.isFinite(at) ? at : null;
|
|
30060
|
+
}
|
|
30061
|
+
function parseResetCredits(value) {
|
|
30062
|
+
const root = recordOf(value);
|
|
30063
|
+
if (root === null || !Array.isArray(root.credits))
|
|
30064
|
+
return null;
|
|
30065
|
+
const credits = root.credits.flatMap((raw) => {
|
|
30066
|
+
const c = recordOf(raw);
|
|
30067
|
+
if (c === null || typeof c.id !== "string" || typeof c.status !== "string")
|
|
30068
|
+
return [];
|
|
30069
|
+
return [
|
|
30070
|
+
{
|
|
30071
|
+
id: c.id,
|
|
30072
|
+
status: c.status,
|
|
30073
|
+
title: typeof c.title === "string" ? c.title : null,
|
|
30074
|
+
grantedAt: isoOrNull2(c.granted_at),
|
|
30075
|
+
expiresAt: isoOrNull2(c.expires_at)
|
|
30076
|
+
}
|
|
30077
|
+
];
|
|
30078
|
+
});
|
|
30079
|
+
return { available: credits.filter((c) => c.status === "available").length, credits };
|
|
30080
|
+
}
|
|
29879
30081
|
|
|
29880
30082
|
// packages/providers/src/builtinOAuth.ts
|
|
29881
30083
|
function builtinOAuthFlows() {
|
|
@@ -32601,6 +32803,24 @@ function createConnectFlows(deps) {
|
|
|
32601
32803
|
if (provider === undefined)
|
|
32602
32804
|
throw new GatewayError("BAD_REQUEST", "provider does not support OAuth");
|
|
32603
32805
|
const result = await provider.exchange({ code, pending: flow.pending }, { http: deps.http, now: deps.now });
|
|
32806
|
+
if (flow.credentialId !== undefined) {
|
|
32807
|
+
const existing = await deps.store.credentials.get(flow.credentialId);
|
|
32808
|
+
if (existing === null)
|
|
32809
|
+
throw new GatewayError("BAD_REQUEST", "credential not found");
|
|
32810
|
+
await deps.store.credentials.updateSecrets(flow.credentialId, result.secrets, result.expiresAt);
|
|
32811
|
+
await deps.store.credentials.update(flow.credentialId, {
|
|
32812
|
+
enabled: true,
|
|
32813
|
+
disabledReason: null,
|
|
32814
|
+
disabledAt: null,
|
|
32815
|
+
accountEmail: result.accountEmail,
|
|
32816
|
+
providerData: result.providerData
|
|
32817
|
+
});
|
|
32818
|
+
logger2.info("oauth reconnect completed", {
|
|
32819
|
+
provider: flow.provider,
|
|
32820
|
+
credentialId: flow.credentialId
|
|
32821
|
+
});
|
|
32822
|
+
return { id: flow.credentialId };
|
|
32823
|
+
}
|
|
32604
32824
|
const id = crypto.randomUUID();
|
|
32605
32825
|
await deps.store.credentials.create({
|
|
32606
32826
|
id,
|
|
@@ -32637,13 +32857,25 @@ function createConnectFlows(deps) {
|
|
|
32637
32857
|
}
|
|
32638
32858
|
return {
|
|
32639
32859
|
connectableIds,
|
|
32640
|
-
async start(providerInput, labelInput) {
|
|
32860
|
+
async start(providerInput, labelInput, credentialIdInput) {
|
|
32641
32861
|
const unconnectable = () => new GatewayError("BAD_REQUEST", `provider must be one of ${connectableIds().join(", ")}`);
|
|
32642
32862
|
if (!exists(providerInput))
|
|
32643
32863
|
throw unconnectable();
|
|
32644
32864
|
const provider = deps.providers[providerInput];
|
|
32645
32865
|
if (provider === undefined)
|
|
32646
32866
|
throw unconnectable();
|
|
32867
|
+
let credentialId;
|
|
32868
|
+
if (credentialIdInput !== undefined) {
|
|
32869
|
+
if (typeof credentialIdInput !== "string" || credentialIdInput.trim().length === 0) {
|
|
32870
|
+
throw new GatewayError("BAD_REQUEST", "invalid credential for reconnect");
|
|
32871
|
+
}
|
|
32872
|
+
const id = credentialIdInput.trim();
|
|
32873
|
+
const existing = await deps.store.credentials.get(id);
|
|
32874
|
+
if (existing === null || existing.provider !== providerInput || existing.authType !== "oauth") {
|
|
32875
|
+
throw new GatewayError("BAD_REQUEST", "invalid credential for reconnect");
|
|
32876
|
+
}
|
|
32877
|
+
credentialId = id;
|
|
32878
|
+
}
|
|
32647
32879
|
const label3 = typeof labelInput === "string" && labelInput.trim().length > 0 ? labelInput.trim() : providerInput;
|
|
32648
32880
|
const redirectUri = callbackUri(providerInput);
|
|
32649
32881
|
const oauthDeps = { http: deps.http, now: deps.now };
|
|
@@ -32653,6 +32885,7 @@ function createConnectFlows(deps) {
|
|
|
32653
32885
|
provider: providerInput,
|
|
32654
32886
|
label: label3,
|
|
32655
32887
|
pending: start.pending,
|
|
32888
|
+
...credentialId === undefined ? {} : { credentialId },
|
|
32656
32889
|
...start.userCode === undefined ? {} : { userCode: start.userCode }
|
|
32657
32890
|
});
|
|
32658
32891
|
return {
|
|
@@ -32806,6 +33039,11 @@ var quotaRefreshSchema = exports_external.discriminatedUnion("kind", [
|
|
|
32806
33039
|
}).strict(),
|
|
32807
33040
|
exports_external.object({ kind: exports_external.literal("all") }).strict()
|
|
32808
33041
|
]);
|
|
33042
|
+
var accountIdSchema = exports_external.string().trim().min(1).max(64).regex(/^[A-Za-z0-9_-]+$/, "credentialId must be an account id");
|
|
33043
|
+
var redeemResetSchema = exports_external.object({
|
|
33044
|
+
credentialId: accountIdSchema,
|
|
33045
|
+
creditId: exports_external.string().min(1).max(128).regex(/^[A-Za-z0-9_-]+$/).optional()
|
|
33046
|
+
}).strict();
|
|
32809
33047
|
var dimensionSchema = exports_external.enum([
|
|
32810
33048
|
"credential",
|
|
32811
33049
|
"model",
|
|
@@ -35215,6 +35453,29 @@ function oauthAdapter(id, flow, opts = {}) {
|
|
|
35215
35453
|
return Promise.resolve(null);
|
|
35216
35454
|
return runStep(id, "usage", step, deps, origins);
|
|
35217
35455
|
}
|
|
35456
|
+
},
|
|
35457
|
+
...flow.resetCredits === undefined ? {} : {
|
|
35458
|
+
resetCredits: (secrets, deps, providerData) => {
|
|
35459
|
+
const step = flow.resetCredits?.({ secrets, providerData, ...helpers(deps) });
|
|
35460
|
+
if (step === undefined)
|
|
35461
|
+
return Promise.resolve(null);
|
|
35462
|
+
return runStep(id, "resetCredits", step, deps, origins);
|
|
35463
|
+
}
|
|
35464
|
+
},
|
|
35465
|
+
...flow.redeemReset === undefined ? {} : {
|
|
35466
|
+
redeemReset: (secrets, deps, providerData, creditId, requestId) => {
|
|
35467
|
+
const step = flow.redeemReset?.({
|
|
35468
|
+
secrets,
|
|
35469
|
+
providerData,
|
|
35470
|
+
creditId,
|
|
35471
|
+
requestId,
|
|
35472
|
+
...helpers(deps)
|
|
35473
|
+
});
|
|
35474
|
+
if (step === undefined) {
|
|
35475
|
+
return Promise.reject(flowFailure(id, "redeemReset", "has no redeem step"));
|
|
35476
|
+
}
|
|
35477
|
+
return runStep(id, "redeemReset", step, deps, origins);
|
|
35478
|
+
}
|
|
35218
35479
|
}
|
|
35219
35480
|
};
|
|
35220
35481
|
if (flow.kind === "pkce")
|
|
@@ -35250,11 +35511,32 @@ function isAuthorizeStart(value) {
|
|
|
35250
35511
|
const candidate = value;
|
|
35251
35512
|
return typeof candidate.authorizeUrl === "string" && typeof candidate.pending === "object" && candidate.pending !== null;
|
|
35252
35513
|
}
|
|
35514
|
+
function isResetCreditsOrNull(value) {
|
|
35515
|
+
if (value === null)
|
|
35516
|
+
return true;
|
|
35517
|
+
if (typeof value !== "object")
|
|
35518
|
+
return false;
|
|
35519
|
+
const candidate = value;
|
|
35520
|
+
return typeof candidate.available === "number" && Array.isArray(candidate.credits) && candidate.credits.every((c) => {
|
|
35521
|
+
if (typeof c !== "object" || c === null)
|
|
35522
|
+
return false;
|
|
35523
|
+
const row = c;
|
|
35524
|
+
return typeof row.id === "string" && typeof row.status === "string" && (row.title === null || typeof row.title === "string") && (row.grantedAt === null || typeof row.grantedAt === "number") && (row.expiresAt === null || typeof row.expiresAt === "number");
|
|
35525
|
+
});
|
|
35526
|
+
}
|
|
35527
|
+
function isResetRedeemed(value) {
|
|
35528
|
+
if (typeof value !== "object" || value === null)
|
|
35529
|
+
return false;
|
|
35530
|
+
const held = value.windowsReset;
|
|
35531
|
+
return held === null || typeof held === "number";
|
|
35532
|
+
}
|
|
35253
35533
|
var RETURN_SHAPE = {
|
|
35254
35534
|
start: isAuthorizeStart,
|
|
35255
35535
|
begin: isAuthorizeStart,
|
|
35256
35536
|
exchange: isFlowResult,
|
|
35257
|
-
refresh: isFlowResult
|
|
35537
|
+
refresh: isFlowResult,
|
|
35538
|
+
resetCredits: isResetCreditsOrNull,
|
|
35539
|
+
redeemReset: isResetRedeemed
|
|
35258
35540
|
};
|
|
35259
35541
|
|
|
35260
35542
|
// packages/control/src/oauth/index.ts
|
|
@@ -35474,6 +35756,11 @@ function validateOAuthFlow(id, value) {
|
|
|
35474
35756
|
if (value.usage !== undefined) {
|
|
35475
35757
|
checkAt(typeof value.usage === "function", id, "oauth.usage", "a function");
|
|
35476
35758
|
}
|
|
35759
|
+
for (const fn of ["resetCredits", "redeemReset"]) {
|
|
35760
|
+
if (value[fn] !== undefined) {
|
|
35761
|
+
checkAt(typeof value[fn] === "function", id, `oauth.${fn}`, "a function");
|
|
35762
|
+
}
|
|
35763
|
+
}
|
|
35477
35764
|
if (kind === "device") {
|
|
35478
35765
|
checkAt(typeof value.begin === "function", id, "oauth.begin", "a function");
|
|
35479
35766
|
checkAt(typeof value.needsDeviceId === "boolean", id, "oauth.needsDeviceId", "a boolean");
|
|
@@ -35669,6 +35956,52 @@ async function quotaHistory(deps, input2) {
|
|
|
35669
35956
|
|
|
35670
35957
|
// packages/control/src/quota/clientHistory.ts
|
|
35671
35958
|
var MAX_SPAN_MS = 16 * 24 * 60 * 60 * 1000;
|
|
35959
|
+
// packages/control/src/quota/resets.ts
|
|
35960
|
+
async function resettable(deps, credentialId) {
|
|
35961
|
+
const credential = await deps.store.credentials.get(credentialId.trim());
|
|
35962
|
+
if (credential === null)
|
|
35963
|
+
throw new GatewayError("BAD_REQUEST", "no such credential");
|
|
35964
|
+
const provider = deps.providers[credential.provider];
|
|
35965
|
+
const list = provider?.resetCredits;
|
|
35966
|
+
const redeem = provider?.redeemReset;
|
|
35967
|
+
if (credential.authType !== "oauth" || list === undefined || redeem === undefined) {
|
|
35968
|
+
throw new GatewayError("BAD_REQUEST", "this account's provider has no quota resets");
|
|
35969
|
+
}
|
|
35970
|
+
return { credential, list, redeem };
|
|
35971
|
+
}
|
|
35972
|
+
async function listFor(deps, r) {
|
|
35973
|
+
const secrets = await usageSecretsFor(deps, r.credential);
|
|
35974
|
+
const read = await r.list(secrets, { http: deps.http, now: deps.now }, r.credential.providerData);
|
|
35975
|
+
if (read === null) {
|
|
35976
|
+
throw new GatewayError("UPSTREAM", "provider answered the reset list with nothing usable", {
|
|
35977
|
+
gatewayAuthored: true
|
|
35978
|
+
});
|
|
35979
|
+
}
|
|
35980
|
+
return read;
|
|
35981
|
+
}
|
|
35982
|
+
async function listResetCredits(deps, credentialId) {
|
|
35983
|
+
const r = await resettable(deps, credentialId);
|
|
35984
|
+
return { credentialId: r.credential.id, ...await listFor(deps, r) };
|
|
35985
|
+
}
|
|
35986
|
+
async function redeemResetCredit(deps, request2) {
|
|
35987
|
+
const r = await resettable(deps, request2.credentialId);
|
|
35988
|
+
return deps.coord.mutex.withLock(PROBE_LOCK_PREFIX + r.credential.id, PROBE_LOCK_TTL_MS, PROBE_LOCK_WAIT_MS, () => redeemLocked(deps, r, request2));
|
|
35989
|
+
}
|
|
35990
|
+
async function redeemLocked(deps, r, request2) {
|
|
35991
|
+
const available = (await listFor(deps, r)).credits.filter((c) => c.status === "available").sort((a, b) => (a.expiresAt ?? Number.POSITIVE_INFINITY) - (b.expiresAt ?? Number.POSITIVE_INFINITY));
|
|
35992
|
+
const credit = request2.creditId === undefined ? available[0] : available.find((c) => c.id === request2.creditId);
|
|
35993
|
+
if (credit === undefined) {
|
|
35994
|
+
throw new GatewayError("CONFLICT", request2.creditId === undefined ? "no reset credit is available for this account" : "that reset credit is not available");
|
|
35995
|
+
}
|
|
35996
|
+
const secrets = await usageSecretsFor(deps, r.credential);
|
|
35997
|
+
const { windowsReset } = await r.redeem(secrets, { http: deps.http, now: deps.now }, r.credential.providerData, credit.id, crypto.randomUUID());
|
|
35998
|
+
let quotaRefreshed = false;
|
|
35999
|
+
try {
|
|
36000
|
+
quotaRefreshed = await probe(deps, r.credential) !== null;
|
|
36001
|
+
} catch {}
|
|
36002
|
+
return { credentialId: r.credential.id, creditId: credit.id, windowsReset, quotaRefreshed };
|
|
36003
|
+
}
|
|
36004
|
+
|
|
35672
36005
|
// packages/control/src/quota/poll.ts
|
|
35673
36006
|
var CONCURRENCY = 4;
|
|
35674
36007
|
var RATE_LIMIT_COOLDOWN_MS = 180000;
|
|
@@ -35676,14 +36009,17 @@ var COOLDOWN_PREFIX = "quota:cooldown:";
|
|
|
35676
36009
|
var PROBE_LOCK_PREFIX = "quota:probe:";
|
|
35677
36010
|
var PROBE_LOCK_TTL_MS = 60000;
|
|
35678
36011
|
var PROBE_LOCK_WAIT_MS = 30000;
|
|
36012
|
+
async function usageSecretsFor(deps, credential) {
|
|
36013
|
+
const refreshed = credential.hasRefreshToken && credential.expiresAt !== null && credential.expiresAt - SCHEDULER_REFRESH_LEAD_MS <= deps.now() ? await deps.refresh(credential) : null;
|
|
36014
|
+
return refreshed === null ? await credential.openForUsage() : { accessToken: refreshed.accessToken };
|
|
36015
|
+
}
|
|
35679
36016
|
async function probe(deps, credential) {
|
|
35680
36017
|
if (credential.authType !== "oauth")
|
|
35681
36018
|
return null;
|
|
35682
36019
|
const provider = deps.providers[credential.provider];
|
|
35683
36020
|
if (provider?.usage === undefined)
|
|
35684
36021
|
return null;
|
|
35685
|
-
const
|
|
35686
|
-
const secrets = refreshed === null ? await credential.openForUsage() : { accessToken: refreshed.accessToken };
|
|
36022
|
+
const secrets = await usageSecretsFor(deps, credential);
|
|
35687
36023
|
const report = await provider.usage(secrets, { http: deps.http, now: deps.now }, credential.providerData);
|
|
35688
36024
|
if (report === null || report.windows.length === 0)
|
|
35689
36025
|
return null;
|
|
@@ -35846,6 +36182,12 @@ function quotaOps(deps) {
|
|
|
35846
36182
|
outcomes.push(result?.outcome ?? { kind: "failed", credentialId: credential.id, code: "INTERNAL" });
|
|
35847
36183
|
}
|
|
35848
36184
|
return { outcomes };
|
|
36185
|
+
},
|
|
36186
|
+
resetCredits: (credentialId) => listResetCredits(deps, credentialId),
|
|
36187
|
+
redeemReset: (request2) => redeemResetCredit(deps, request2),
|
|
36188
|
+
hasResets: (provider) => {
|
|
36189
|
+
const flow = deps.providers[provider];
|
|
36190
|
+
return flow?.resetCredits !== undefined && flow.redeemReset !== undefined;
|
|
35849
36191
|
}
|
|
35850
36192
|
};
|
|
35851
36193
|
}
|
|
@@ -36541,9 +36883,9 @@ var bodies = {
|
|
|
36541
36883
|
// apps/cli/src/commands/connect.ts
|
|
36542
36884
|
var DEVICE_TIMEOUT_MS = 600000;
|
|
36543
36885
|
var connect = {
|
|
36544
|
-
usage: "connect <provider> [--label L]",
|
|
36886
|
+
usage: "connect <provider> [--label L] [--credential ID]",
|
|
36545
36887
|
summary: "Authorize a provider account from the terminal",
|
|
36546
|
-
options: { label: { type: "string" } },
|
|
36888
|
+
options: { label: { type: "string" }, credential: { type: "string" } },
|
|
36547
36889
|
async run(args, { ctx, writer, prompt, connect: connectFlows, connectable }) {
|
|
36548
36890
|
const providerId = requirePositional(args, 0, "provider");
|
|
36549
36891
|
const { ids, failures } = await connectable();
|
|
@@ -36552,7 +36894,8 @@ var connect = {
|
|
|
36552
36894
|
throw new UsageError(broken === undefined ? `provider must be one of ${ids.join(", ")}` : `provider ${providerId} comes from a plugin that failed to load: ${broken.reason}`);
|
|
36553
36895
|
}
|
|
36554
36896
|
const flows = await connectFlows(await ctx.store());
|
|
36555
|
-
const
|
|
36897
|
+
const credentialId = stringFlag(args.values, "credential");
|
|
36898
|
+
const start = await flows.start(providerId, stringFlag(args.values, "label"), credentialId);
|
|
36556
36899
|
writer.err("");
|
|
36557
36900
|
writer.err(`Open this URL to authorize ${providerId}:`);
|
|
36558
36901
|
writer.err(` ${start.authorizeUrl}`);
|
|
@@ -38030,16 +38373,7 @@ var quotaRefresh = {
|
|
|
38030
38373
|
const store = await ctx.store();
|
|
38031
38374
|
const credentials = await listCredentials(store);
|
|
38032
38375
|
const wanted = id === undefined ? credentials : credentials.filter((c) => c.id === id);
|
|
38033
|
-
const
|
|
38034
|
-
const http = nodeHttpClient();
|
|
38035
|
-
const ops = quotaOps({
|
|
38036
|
-
store,
|
|
38037
|
-
coord: memoryCoord({ now: ctx.now }),
|
|
38038
|
-
providers,
|
|
38039
|
-
http,
|
|
38040
|
-
refresh: createRefresher({ store, providers, http, now: ctx.now }),
|
|
38041
|
-
now: ctx.now
|
|
38042
|
-
});
|
|
38376
|
+
const ops = await opsFor(ctx, wanted);
|
|
38043
38377
|
note(ctx, writer, id === undefined ? "refreshing every account\u2026" : `refreshing ${id}\u2026`);
|
|
38044
38378
|
const result = await ops.refresh(id === undefined ? { kind: "all" } : { kind: "one", credentialId: id });
|
|
38045
38379
|
const labels = new Map(credentials.map((c) => [c.id, c.label]));
|
|
@@ -38065,6 +38399,59 @@ var quotaRefresh = {
|
|
|
38065
38399
|
}
|
|
38066
38400
|
}
|
|
38067
38401
|
};
|
|
38402
|
+
async function opsFor(ctx, wanted) {
|
|
38403
|
+
const store = await ctx.store();
|
|
38404
|
+
const providers = wanted.every((c) => Object.hasOwn(PROVIDER_DESCRIPTORS, c.provider)) ? OAUTH_PROVIDERS : (await connectRegistryFor(ctx.root.root)).providers;
|
|
38405
|
+
const http = nodeHttpClient();
|
|
38406
|
+
return quotaOps({
|
|
38407
|
+
store,
|
|
38408
|
+
coord: memoryCoord({ now: ctx.now }),
|
|
38409
|
+
providers,
|
|
38410
|
+
http,
|
|
38411
|
+
refresh: createRefresher({ store, providers, http, now: ctx.now }),
|
|
38412
|
+
now: ctx.now
|
|
38413
|
+
});
|
|
38414
|
+
}
|
|
38415
|
+
var quotaResets = {
|
|
38416
|
+
usage: "quota resets <id>",
|
|
38417
|
+
summary: "List an account's banked quota resets (Codex, Claude)",
|
|
38418
|
+
async run(args, { ctx, writer }) {
|
|
38419
|
+
const id = requirePositional(args, 0, "credential id");
|
|
38420
|
+
const credentials = (await listCredentials(await ctx.store())).filter((c) => c.id === id);
|
|
38421
|
+
const listed = await (await opsFor(ctx, credentials)).resetCredits(id);
|
|
38422
|
+
emit(ctx, writer, listed, () => [
|
|
38423
|
+
table([{ header: "CREDIT" }, { header: "STATUS" }, { header: "TITLE" }, { header: "EXPIRES" }], listed.credits.map((c) => [c.id, c.status, c.title ?? dash, formatTime(c.expiresAt)])),
|
|
38424
|
+
"",
|
|
38425
|
+
`${listed.available} available`
|
|
38426
|
+
].join(`
|
|
38427
|
+
`));
|
|
38428
|
+
}
|
|
38429
|
+
};
|
|
38430
|
+
var quotaReset = {
|
|
38431
|
+
usage: "quota reset <id> [--credit CREDIT_ID]",
|
|
38432
|
+
summary: "Spend one banked quota reset on an account (irreversible)",
|
|
38433
|
+
options: { credit: { type: "string" } },
|
|
38434
|
+
async run(args, { ctx, writer, prompt }) {
|
|
38435
|
+
const id = requirePositional(args, 0, "credential id");
|
|
38436
|
+
const creditId = stringFlag(args.values, "credit");
|
|
38437
|
+
const credentials = (await listCredentials(await ctx.store())).filter((c) => c.id === id);
|
|
38438
|
+
const label3 = credentials[0]?.label ?? id;
|
|
38439
|
+
const ops = await opsFor(ctx, credentials);
|
|
38440
|
+
const provider2 = credentials[0]?.provider;
|
|
38441
|
+
if (provider2 === undefined)
|
|
38442
|
+
throw new CliError("no such credential");
|
|
38443
|
+
if (!ops.hasResets(provider2))
|
|
38444
|
+
throw new CliError("this account's provider has no quota resets");
|
|
38445
|
+
if (!await prompt.confirm(`spend one banked quota reset on "${label3}" (${id})?`)) {
|
|
38446
|
+
throw new CliError("cancelled");
|
|
38447
|
+
}
|
|
38448
|
+
const result = await ops.redeemReset({
|
|
38449
|
+
credentialId: id,
|
|
38450
|
+
...creditId === undefined ? {} : { creditId }
|
|
38451
|
+
});
|
|
38452
|
+
emit(ctx, writer, result, () => `${label3}: reset ${result.creditId} redeemed` + (result.windowsReset === null ? "" : `, ${result.windowsReset} window(s) reset`) + (result.quotaRefreshed ? "; quota re-read" : "; quota re-read failed, next poll will update it"));
|
|
38453
|
+
}
|
|
38454
|
+
};
|
|
38068
38455
|
|
|
38069
38456
|
// apps/cli/src/commands/service.ts
|
|
38070
38457
|
import { existsSync as existsSync5 } from "fs";
|
|
@@ -38155,7 +38542,7 @@ function createServiceDeps(input2) {
|
|
|
38155
38542
|
}
|
|
38156
38543
|
|
|
38157
38544
|
// apps/cli/src/version.ts
|
|
38158
|
-
var VERSION = "0.
|
|
38545
|
+
var VERSION = "0.13.0";
|
|
38159
38546
|
|
|
38160
38547
|
// apps/cli/src/commands/service.ts
|
|
38161
38548
|
function gatewayArgv(root) {
|
|
@@ -38872,6 +39259,8 @@ var COMMANDS = {
|
|
|
38872
39259
|
"credentials add-key": credentialsAddKey,
|
|
38873
39260
|
"credentials health": credentialsHealth,
|
|
38874
39261
|
"quota refresh": quotaRefresh,
|
|
39262
|
+
"quota resets": quotaResets,
|
|
39263
|
+
"quota reset": quotaReset,
|
|
38875
39264
|
"models list": modelsList,
|
|
38876
39265
|
"models show": modelsShow,
|
|
38877
39266
|
"models put": modelsPut,
|