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.
Files changed (56) hide show
  1. package/README.md +1 -0
  2. package/bin/omni.js +414 -25
  3. package/gateway.js +379 -15
  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-2syjcpos.js +70 -0
  19. package/public/assets/_app.console-Dxkf_zSS.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-BSlXzAHP.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-DUhmc9Si.js} +4 -4
  31. package/public/assets/{dist-D03q-60i.js → dist-BBGxva0Y.js} +1 -1
  32. package/public/assets/index-Bzh27906.js +14 -0
  33. package/public/assets/{login-DW2Ipp21.js → login-DbXxdOl5.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() {
@@ -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 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 };
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 start = await flows.start(providerId, stringFlag(args.values, "label"));
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 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
- });
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.12.12";
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,