omnigateway 0.12.12 → 0.12.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.
Files changed (56) hide show
  1. package/README.md +1 -0
  2. package/bin/omni.js +378 -21
  3. package/gateway.js +346 -13
  4. package/package.json +1 -1
  5. package/public/assets/{Chip-D4qvhu1D.js → Chip-D0XSbQUr.js} +1 -1
  6. package/public/assets/{CopyValue-SnsT2LML.js → CopyValue-DAlmkISB.js} +3 -3
  7. package/public/assets/{Field-BYBri2NV.js → Field-DG1317zr.js} +1 -1
  8. package/public/assets/Lamp-Dk9XrTCg.js +26 -0
  9. package/public/assets/{Panel-CWmOTDAP.js → Panel-CsovmtxF.js} +23 -4
  10. package/public/assets/Rack-CDUOJkS2.js +483 -0
  11. package/public/assets/{Readout-BTh732hP.js → Readout-DlT5oxGO.js} +1 -1
  12. package/public/assets/RequestTable-i3TtCB9m.js +93 -0
  13. package/public/assets/{SummaryDeck-7ivuca39.js → SummaryDeck-DrzTQQo5.js} +2 -2
  14. package/public/assets/{Toggle-t82IhqEt.js → Toggle-CdMD58sy.js} +1 -1
  15. package/public/assets/{TokenBreakdown-D1Wtza1y.js → TokenBreakdown-BC6nqo1v.js} +1 -1
  16. package/public/assets/{WindowChart-BeGPsccS.js → WindowChart-Bqbl-KFO.js} +2 -2
  17. package/public/assets/_app-CSru2YTE.js +1 -0
  18. package/public/assets/_app.accounts-B2lQkMb0.js +70 -0
  19. package/public/assets/_app.console-a2TJDoBC.js +24 -0
  20. package/public/assets/_app.database-CqSoTi2l.js +24 -0
  21. package/public/assets/_app.index-BV1kJJjA.js +67 -0
  22. package/public/assets/_app.keys-Dq5O0AG9.js +92 -0
  23. package/public/assets/_app.logs-PjRK5wqy.js +46 -0
  24. package/public/assets/_app.models-BfmeeB5e.js +148 -0
  25. package/public/assets/_app.plugins._pluginId-Owb05aFv.js +20 -0
  26. package/public/assets/{_app.settings-C3GzeJ4q.js → _app.settings-5i-vvT-H.js} +7 -7
  27. package/public/assets/_app.usage--5qFtU1W.js +76 -0
  28. package/public/assets/chevron-down-Bb5ZGvwk.js +1 -0
  29. package/public/assets/chevron-right-hwF5mD6-.js +1 -0
  30. package/public/assets/{client-k1tkFSNH.js → client-BcXc3lek.js} +4 -4
  31. package/public/assets/{dist-D03q-60i.js → dist-BBGxva0Y.js} +1 -1
  32. package/public/assets/index-D1RDaq3l.js +14 -0
  33. package/public/assets/{login-DW2Ipp21.js → login-GB0ZGUvA.js} +4 -4
  34. package/public/assets/plus-BgcHxxIr.js +1 -0
  35. package/public/assets/{preload-helper-BPaHpbO_.js → preload-helper-B7KDmXJ9.js} +1 -1
  36. package/public/assets/rotate-ccw-Do43Argv.js +1 -0
  37. package/public/assets/{shared-Dtkw6SE0.js → shared-DDpLUhCp.js} +2 -2
  38. package/public/assets/{trash-D4QWzEK_.js → trash-00950_yY.js} +1 -1
  39. package/public/index.html +5 -5
  40. package/public/assets/Lamp-DkvXKB-B.js +0 -26
  41. package/public/assets/Rack-CEuCdVXc.js +0 -483
  42. package/public/assets/RequestTable-D8L6iVJV.js +0 -93
  43. package/public/assets/_app-Drf03PWk.js +0 -1
  44. package/public/assets/_app.accounts-W1USUo2Q.js +0 -70
  45. package/public/assets/_app.console-Co9d2ZiD.js +0 -44
  46. package/public/assets/_app.database-CtFJLcfE.js +0 -24
  47. package/public/assets/_app.index-DigYfob3.js +0 -67
  48. package/public/assets/_app.keys-B1fRMyU-.js +0 -92
  49. package/public/assets/_app.logs-CFixPsUw.js +0 -60
  50. package/public/assets/_app.models-Dpl1O2RE.js +0 -148
  51. package/public/assets/_app.plugins._pluginId-BK0ADugW.js +0 -20
  52. package/public/assets/_app.usage-D2qlcEp4.js +0 -76
  53. package/public/assets/chevron-down-Bs0tSOyA.js +0 -1
  54. package/public/assets/chevron-right-DSaI1kFo.js +0 -1
  55. package/public/assets/index-Bk9sabex.js +0 -14
  56. 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 anthropicReasoningForm(model) {
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)?.reasoningForm ?? "adaptive";
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 ?? 4096,
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 PROFILE_URL = "https://api.kilo.ai/api/profile";
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(PROFILE_URL, kiloProfile, { accessToken });
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() {
@@ -32806,6 +33008,11 @@ var quotaRefreshSchema = exports_external.discriminatedUnion("kind", [
32806
33008
  }).strict(),
32807
33009
  exports_external.object({ kind: exports_external.literal("all") }).strict()
32808
33010
  ]);
33011
+ var accountIdSchema = exports_external.string().trim().min(1).max(64).regex(/^[A-Za-z0-9_-]+$/, "credentialId must be an account id");
33012
+ var redeemResetSchema = exports_external.object({
33013
+ credentialId: accountIdSchema,
33014
+ creditId: exports_external.string().min(1).max(128).regex(/^[A-Za-z0-9_-]+$/).optional()
33015
+ }).strict();
32809
33016
  var dimensionSchema = exports_external.enum([
32810
33017
  "credential",
32811
33018
  "model",
@@ -35215,6 +35422,29 @@ function oauthAdapter(id, flow, opts = {}) {
35215
35422
  return Promise.resolve(null);
35216
35423
  return runStep(id, "usage", step, deps, origins);
35217
35424
  }
35425
+ },
35426
+ ...flow.resetCredits === undefined ? {} : {
35427
+ resetCredits: (secrets, deps, providerData) => {
35428
+ const step = flow.resetCredits?.({ secrets, providerData, ...helpers(deps) });
35429
+ if (step === undefined)
35430
+ return Promise.resolve(null);
35431
+ return runStep(id, "resetCredits", step, deps, origins);
35432
+ }
35433
+ },
35434
+ ...flow.redeemReset === undefined ? {} : {
35435
+ redeemReset: (secrets, deps, providerData, creditId, requestId) => {
35436
+ const step = flow.redeemReset?.({
35437
+ secrets,
35438
+ providerData,
35439
+ creditId,
35440
+ requestId,
35441
+ ...helpers(deps)
35442
+ });
35443
+ if (step === undefined) {
35444
+ return Promise.reject(flowFailure(id, "redeemReset", "has no redeem step"));
35445
+ }
35446
+ return runStep(id, "redeemReset", step, deps, origins);
35447
+ }
35218
35448
  }
35219
35449
  };
35220
35450
  if (flow.kind === "pkce")
@@ -35250,11 +35480,32 @@ function isAuthorizeStart(value) {
35250
35480
  const candidate = value;
35251
35481
  return typeof candidate.authorizeUrl === "string" && typeof candidate.pending === "object" && candidate.pending !== null;
35252
35482
  }
35483
+ function isResetCreditsOrNull(value) {
35484
+ if (value === null)
35485
+ return true;
35486
+ if (typeof value !== "object")
35487
+ return false;
35488
+ const candidate = value;
35489
+ return typeof candidate.available === "number" && Array.isArray(candidate.credits) && candidate.credits.every((c) => {
35490
+ if (typeof c !== "object" || c === null)
35491
+ return false;
35492
+ const row = c;
35493
+ 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");
35494
+ });
35495
+ }
35496
+ function isResetRedeemed(value) {
35497
+ if (typeof value !== "object" || value === null)
35498
+ return false;
35499
+ const held = value.windowsReset;
35500
+ return held === null || typeof held === "number";
35501
+ }
35253
35502
  var RETURN_SHAPE = {
35254
35503
  start: isAuthorizeStart,
35255
35504
  begin: isAuthorizeStart,
35256
35505
  exchange: isFlowResult,
35257
- refresh: isFlowResult
35506
+ refresh: isFlowResult,
35507
+ resetCredits: isResetCreditsOrNull,
35508
+ redeemReset: isResetRedeemed
35258
35509
  };
35259
35510
 
35260
35511
  // packages/control/src/oauth/index.ts
@@ -35474,6 +35725,11 @@ function validateOAuthFlow(id, value) {
35474
35725
  if (value.usage !== undefined) {
35475
35726
  checkAt(typeof value.usage === "function", id, "oauth.usage", "a function");
35476
35727
  }
35728
+ for (const fn of ["resetCredits", "redeemReset"]) {
35729
+ if (value[fn] !== undefined) {
35730
+ checkAt(typeof value[fn] === "function", id, `oauth.${fn}`, "a function");
35731
+ }
35732
+ }
35477
35733
  if (kind === "device") {
35478
35734
  checkAt(typeof value.begin === "function", id, "oauth.begin", "a function");
35479
35735
  checkAt(typeof value.needsDeviceId === "boolean", id, "oauth.needsDeviceId", "a boolean");
@@ -35669,6 +35925,52 @@ async function quotaHistory(deps, input2) {
35669
35925
 
35670
35926
  // packages/control/src/quota/clientHistory.ts
35671
35927
  var MAX_SPAN_MS = 16 * 24 * 60 * 60 * 1000;
35928
+ // packages/control/src/quota/resets.ts
35929
+ async function resettable(deps, credentialId) {
35930
+ const credential = await deps.store.credentials.get(credentialId.trim());
35931
+ if (credential === null)
35932
+ throw new GatewayError("BAD_REQUEST", "no such credential");
35933
+ const provider = deps.providers[credential.provider];
35934
+ const list = provider?.resetCredits;
35935
+ const redeem = provider?.redeemReset;
35936
+ if (credential.authType !== "oauth" || list === undefined || redeem === undefined) {
35937
+ throw new GatewayError("BAD_REQUEST", "this account's provider has no quota resets");
35938
+ }
35939
+ return { credential, list, redeem };
35940
+ }
35941
+ async function listFor(deps, r) {
35942
+ const secrets = await usageSecretsFor(deps, r.credential);
35943
+ const read = await r.list(secrets, { http: deps.http, now: deps.now }, r.credential.providerData);
35944
+ if (read === null) {
35945
+ throw new GatewayError("UPSTREAM", "provider answered the reset list with nothing usable", {
35946
+ gatewayAuthored: true
35947
+ });
35948
+ }
35949
+ return read;
35950
+ }
35951
+ async function listResetCredits(deps, credentialId) {
35952
+ const r = await resettable(deps, credentialId);
35953
+ return { credentialId: r.credential.id, ...await listFor(deps, r) };
35954
+ }
35955
+ async function redeemResetCredit(deps, request2) {
35956
+ const r = await resettable(deps, request2.credentialId);
35957
+ return deps.coord.mutex.withLock(PROBE_LOCK_PREFIX + r.credential.id, PROBE_LOCK_TTL_MS, PROBE_LOCK_WAIT_MS, () => redeemLocked(deps, r, request2));
35958
+ }
35959
+ async function redeemLocked(deps, r, request2) {
35960
+ 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));
35961
+ const credit = request2.creditId === undefined ? available[0] : available.find((c) => c.id === request2.creditId);
35962
+ if (credit === undefined) {
35963
+ throw new GatewayError("CONFLICT", request2.creditId === undefined ? "no reset credit is available for this account" : "that reset credit is not available");
35964
+ }
35965
+ const secrets = await usageSecretsFor(deps, r.credential);
35966
+ const { windowsReset } = await r.redeem(secrets, { http: deps.http, now: deps.now }, r.credential.providerData, credit.id, crypto.randomUUID());
35967
+ let quotaRefreshed = false;
35968
+ try {
35969
+ quotaRefreshed = await probe(deps, r.credential) !== null;
35970
+ } catch {}
35971
+ return { credentialId: r.credential.id, creditId: credit.id, windowsReset, quotaRefreshed };
35972
+ }
35973
+
35672
35974
  // packages/control/src/quota/poll.ts
35673
35975
  var CONCURRENCY = 4;
35674
35976
  var RATE_LIMIT_COOLDOWN_MS = 180000;
@@ -35676,14 +35978,17 @@ var COOLDOWN_PREFIX = "quota:cooldown:";
35676
35978
  var PROBE_LOCK_PREFIX = "quota:probe:";
35677
35979
  var PROBE_LOCK_TTL_MS = 60000;
35678
35980
  var PROBE_LOCK_WAIT_MS = 30000;
35981
+ async function usageSecretsFor(deps, credential) {
35982
+ const refreshed = credential.hasRefreshToken && credential.expiresAt !== null && credential.expiresAt - SCHEDULER_REFRESH_LEAD_MS <= deps.now() ? await deps.refresh(credential) : null;
35983
+ return refreshed === null ? await credential.openForUsage() : { accessToken: refreshed.accessToken };
35984
+ }
35679
35985
  async function probe(deps, credential) {
35680
35986
  if (credential.authType !== "oauth")
35681
35987
  return null;
35682
35988
  const provider = deps.providers[credential.provider];
35683
35989
  if (provider?.usage === undefined)
35684
35990
  return null;
35685
- const refreshed = credential.hasRefreshToken && credential.expiresAt !== null && credential.expiresAt - SCHEDULER_REFRESH_LEAD_MS <= deps.now() ? await deps.refresh(credential) : null;
35686
- const secrets = refreshed === null ? await credential.openForUsage() : { accessToken: refreshed.accessToken };
35991
+ const secrets = await usageSecretsFor(deps, credential);
35687
35992
  const report = await provider.usage(secrets, { http: deps.http, now: deps.now }, credential.providerData);
35688
35993
  if (report === null || report.windows.length === 0)
35689
35994
  return null;
@@ -35846,6 +36151,12 @@ function quotaOps(deps) {
35846
36151
  outcomes.push(result?.outcome ?? { kind: "failed", credentialId: credential.id, code: "INTERNAL" });
35847
36152
  }
35848
36153
  return { outcomes };
36154
+ },
36155
+ resetCredits: (credentialId) => listResetCredits(deps, credentialId),
36156
+ redeemReset: (request2) => redeemResetCredit(deps, request2),
36157
+ hasResets: (provider) => {
36158
+ const flow = deps.providers[provider];
36159
+ return flow?.resetCredits !== undefined && flow.redeemReset !== undefined;
35849
36160
  }
35850
36161
  };
35851
36162
  }
@@ -38030,16 +38341,7 @@ var quotaRefresh = {
38030
38341
  const store = await ctx.store();
38031
38342
  const credentials = await listCredentials(store);
38032
38343
  const wanted = id === undefined ? credentials : credentials.filter((c) => c.id === id);
38033
- const providers = wanted.every((c) => Object.hasOwn(PROVIDER_DESCRIPTORS, c.provider)) ? OAUTH_PROVIDERS : (await connectRegistryFor(ctx.root.root)).providers;
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
- });
38344
+ const ops = await opsFor(ctx, wanted);
38043
38345
  note(ctx, writer, id === undefined ? "refreshing every account\u2026" : `refreshing ${id}\u2026`);
38044
38346
  const result = await ops.refresh(id === undefined ? { kind: "all" } : { kind: "one", credentialId: id });
38045
38347
  const labels = new Map(credentials.map((c) => [c.id, c.label]));
@@ -38065,6 +38367,59 @@ var quotaRefresh = {
38065
38367
  }
38066
38368
  }
38067
38369
  };
38370
+ async function opsFor(ctx, wanted) {
38371
+ const store = await ctx.store();
38372
+ const providers = wanted.every((c) => Object.hasOwn(PROVIDER_DESCRIPTORS, c.provider)) ? OAUTH_PROVIDERS : (await connectRegistryFor(ctx.root.root)).providers;
38373
+ const http = nodeHttpClient();
38374
+ return quotaOps({
38375
+ store,
38376
+ coord: memoryCoord({ now: ctx.now }),
38377
+ providers,
38378
+ http,
38379
+ refresh: createRefresher({ store, providers, http, now: ctx.now }),
38380
+ now: ctx.now
38381
+ });
38382
+ }
38383
+ var quotaResets = {
38384
+ usage: "quota resets <id>",
38385
+ summary: "List an account's banked quota resets (Codex, Claude)",
38386
+ async run(args, { ctx, writer }) {
38387
+ const id = requirePositional(args, 0, "credential id");
38388
+ const credentials = (await listCredentials(await ctx.store())).filter((c) => c.id === id);
38389
+ const listed = await (await opsFor(ctx, credentials)).resetCredits(id);
38390
+ emit(ctx, writer, listed, () => [
38391
+ table([{ header: "CREDIT" }, { header: "STATUS" }, { header: "TITLE" }, { header: "EXPIRES" }], listed.credits.map((c) => [c.id, c.status, c.title ?? dash, formatTime(c.expiresAt)])),
38392
+ "",
38393
+ `${listed.available} available`
38394
+ ].join(`
38395
+ `));
38396
+ }
38397
+ };
38398
+ var quotaReset = {
38399
+ usage: "quota reset <id> [--credit CREDIT_ID]",
38400
+ summary: "Spend one banked quota reset on an account (irreversible)",
38401
+ options: { credit: { type: "string" } },
38402
+ async run(args, { ctx, writer, prompt }) {
38403
+ const id = requirePositional(args, 0, "credential id");
38404
+ const creditId = stringFlag(args.values, "credit");
38405
+ const credentials = (await listCredentials(await ctx.store())).filter((c) => c.id === id);
38406
+ const label3 = credentials[0]?.label ?? id;
38407
+ const ops = await opsFor(ctx, credentials);
38408
+ const provider2 = credentials[0]?.provider;
38409
+ if (provider2 === undefined)
38410
+ throw new CliError("no such credential");
38411
+ if (!ops.hasResets(provider2))
38412
+ throw new CliError("this account's provider has no quota resets");
38413
+ if (!await prompt.confirm(`spend one banked quota reset on "${label3}" (${id})?`)) {
38414
+ throw new CliError("cancelled");
38415
+ }
38416
+ const result = await ops.redeemReset({
38417
+ credentialId: id,
38418
+ ...creditId === undefined ? {} : { creditId }
38419
+ });
38420
+ 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"));
38421
+ }
38422
+ };
38068
38423
 
38069
38424
  // apps/cli/src/commands/service.ts
38070
38425
  import { existsSync as existsSync5 } from "fs";
@@ -38155,7 +38510,7 @@ function createServiceDeps(input2) {
38155
38510
  }
38156
38511
 
38157
38512
  // apps/cli/src/version.ts
38158
- var VERSION = "0.12.12";
38513
+ var VERSION = "0.12.13";
38159
38514
 
38160
38515
  // apps/cli/src/commands/service.ts
38161
38516
  function gatewayArgv(root) {
@@ -38872,6 +39227,8 @@ var COMMANDS = {
38872
39227
  "credentials add-key": credentialsAddKey,
38873
39228
  "credentials health": credentialsHealth,
38874
39229
  "quota refresh": quotaRefresh,
39230
+ "quota resets": quotaResets,
39231
+ "quota reset": quotaReset,
38875
39232
  "models list": modelsList,
38876
39233
  "models show": modelsShow,
38877
39234
  "models put": modelsPut,