@omnicross/core 0.1.6 → 0.1.8

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 (137) hide show
  1. package/dist/ApiConverter.cjs +2 -2
  2. package/dist/ApiConverter.js +1 -1
  3. package/dist/{ProviderProxy-C-xqrkKi.d.ts → ProviderProxy--ROec6VI.d.ts} +22 -3
  4. package/dist/{ProviderProxy-CnMQYN59.d.cts → ProviderProxy-QdwNV6l1.d.cts} +22 -3
  5. package/dist/auth/GeminiCodeAssistProjectResolver.cjs +4 -3
  6. package/dist/auth/GeminiCodeAssistProjectResolver.js +3 -2
  7. package/dist/{chunk-3Z5BQZFM.cjs → chunk-235MXAGX.cjs} +3 -1
  8. package/dist/{chunk-SQYVORNP.cjs → chunk-26K6T2YS.cjs} +1520 -523
  9. package/dist/{chunk-DLXZEZWO.cjs → chunk-2E42SAW3.cjs} +42 -4
  10. package/dist/{chunk-7AK7B36B.cjs → chunk-2Z4XM3H5.cjs} +6 -6
  11. package/dist/{chunk-OEWZUX4G.js → chunk-32O35CIF.js} +9 -20
  12. package/dist/chunk-3SCOLIPT.cjs +63 -0
  13. package/dist/{chunk-SYQF7OIR.js → chunk-3WD4HTR2.js} +2 -0
  14. package/dist/{chunk-AT7B7SOZ.js → chunk-5NJLQ6T6.js} +21 -10
  15. package/dist/chunk-5RSZYQJH.cjs +62 -0
  16. package/dist/{chunk-IFXJ2BG7.js → chunk-7C7DET6S.js} +51 -6
  17. package/dist/{chunk-SNYBEXLB.cjs → chunk-EPN67EGP.cjs} +6 -1
  18. package/dist/chunk-FU7EAR73.js +62 -0
  19. package/dist/{chunk-UX7VWA46.js → chunk-FXR6N3SR.js} +39 -1
  20. package/dist/{chunk-WYTEQ4Q5.cjs → chunk-GDIXXKF2.cjs} +21 -10
  21. package/dist/{chunk-WB6PH56J.js → chunk-GFIZ6LQI.js} +12 -8
  22. package/dist/chunk-GL6XM527.js +61 -0
  23. package/dist/{chunk-EJLH6NFT.cjs → chunk-J3QQ6JS5.cjs} +12 -8
  24. package/dist/{chunk-VRFBB5DB.js → chunk-MYYR3HZL.js} +4 -4
  25. package/dist/{chunk-YJVHCHQX.js → chunk-N7OOJI5V.js} +46 -2
  26. package/dist/chunk-NKVZ6Z3Y.cjs +61 -0
  27. package/dist/chunk-OTZC4TNH.cjs +79 -0
  28. package/dist/{chunk-6VUQPOV3.cjs → chunk-OVQBNVSV.cjs} +10 -21
  29. package/dist/{chunk-4VLRHHFE.cjs → chunk-OXHNSZ53.cjs} +9 -11
  30. package/dist/{chunk-6CL5HTPQ.js → chunk-PEGTIPB3.js} +8 -10
  31. package/dist/chunk-PO3XB56Z.js +79 -0
  32. package/dist/{chunk-UDCC7EMR.js → chunk-QYC54PMG.js} +1242 -245
  33. package/dist/{chunk-SIZQQD6T.js → chunk-RJ5ZGNI3.js} +42 -5
  34. package/dist/{chunk-KCVJWJST.cjs → chunk-RJUONLUQ.cjs} +47 -10
  35. package/dist/{chunk-BPKCU575.js → chunk-UAPBLNN2.js} +6 -1
  36. package/dist/{chunk-UNWKG6EI.cjs → chunk-XPIOJQS2.cjs} +51 -6
  37. package/dist/chunk-XXFYLL7R.js +63 -0
  38. package/dist/{chunk-YK57NMIF.cjs → chunk-YWL7YZWS.cjs} +47 -3
  39. package/dist/completion/ApiKeyPoolService.cjs +32 -1
  40. package/dist/completion/ApiKeyPoolService.d.cts +17 -1
  41. package/dist/completion/ApiKeyPoolService.d.ts +17 -1
  42. package/dist/completion/ApiKeyPoolService.js +31 -0
  43. package/dist/completion/CompletionService.cjs +23 -19
  44. package/dist/completion/CompletionService.d.cts +1 -0
  45. package/dist/completion/CompletionService.d.ts +1 -0
  46. package/dist/completion/CompletionService.js +22 -18
  47. package/dist/completion.cjs +23 -19
  48. package/dist/completion.d.cts +1 -0
  49. package/dist/completion.d.ts +1 -0
  50. package/dist/completion.js +22 -18
  51. package/dist/index.cjs +81 -21
  52. package/dist/index.d.cts +5 -5
  53. package/dist/index.d.ts +5 -5
  54. package/dist/index.js +82 -22
  55. package/dist/outbound-api/auditCapture.cjs +2 -2
  56. package/dist/outbound-api/auditCapture.d.cts +3 -4
  57. package/dist/outbound-api/auditCapture.d.ts +3 -4
  58. package/dist/outbound-api/auditCapture.js +1 -1
  59. package/dist/outbound-api/routeResolver.d.cts +4 -3
  60. package/dist/outbound-api/routeResolver.d.ts +4 -3
  61. package/dist/outbound-api/subscriptionRegistryPort.d.cts +1 -0
  62. package/dist/outbound-api/subscriptionRegistryPort.d.ts +1 -0
  63. package/dist/outbound-api/types.d.cts +3 -2
  64. package/dist/outbound-api/types.d.ts +3 -2
  65. package/dist/outbound-api.cjs +23 -19
  66. package/dist/outbound-api.d.cts +8 -6
  67. package/dist/outbound-api.d.ts +8 -6
  68. package/dist/outbound-api.js +22 -18
  69. package/dist/pipeline/AccountAllowanceScheduling.d.cts +3 -2
  70. package/dist/pipeline/AccountAllowanceScheduling.d.ts +3 -2
  71. package/dist/pipeline/AccountRouteActivity.cjs +10 -0
  72. package/dist/pipeline/AccountRouteActivity.d.cts +61 -0
  73. package/dist/pipeline/AccountRouteActivity.d.ts +61 -0
  74. package/dist/pipeline/AccountRouteActivity.js +10 -0
  75. package/dist/pipeline/ServerOverloadCounter.cjs +16 -0
  76. package/dist/pipeline/ServerOverloadCounter.d.cts +77 -0
  77. package/dist/pipeline/ServerOverloadCounter.d.ts +77 -0
  78. package/dist/pipeline/ServerOverloadCounter.js +16 -0
  79. package/dist/pipeline/SubscriptionAccountHealth.cjs +4 -2
  80. package/dist/pipeline/SubscriptionAccountHealth.d.cts +32 -3
  81. package/dist/pipeline/SubscriptionAccountHealth.d.ts +32 -3
  82. package/dist/pipeline/SubscriptionAccountHealth.js +3 -1
  83. package/dist/pipeline/upstreamFetch.cjs +4 -3
  84. package/dist/pipeline/upstreamFetch.d.cts +35 -6
  85. package/dist/pipeline/upstreamFetch.d.ts +35 -6
  86. package/dist/pipeline/upstreamFetch.js +3 -2
  87. package/dist/pipeline/upstreamTrace.cjs +4 -2
  88. package/dist/pipeline/upstreamTrace.d.cts +22 -8
  89. package/dist/pipeline/upstreamTrace.d.ts +22 -8
  90. package/dist/pipeline/upstreamTrace.js +3 -1
  91. package/dist/ports.d.cts +2 -2
  92. package/dist/ports.d.ts +2 -2
  93. package/dist/provider-proxy/ProviderProxy.cjs +23 -19
  94. package/dist/provider-proxy/ProviderProxy.d.cts +2 -1
  95. package/dist/provider-proxy/ProviderProxy.d.ts +2 -1
  96. package/dist/provider-proxy/ProviderProxy.js +22 -18
  97. package/dist/provider-proxy/identity/codexCliHeaders.cjs +10 -0
  98. package/dist/provider-proxy/identity/codexCliHeaders.d.cts +45 -0
  99. package/dist/provider-proxy/identity/codexCliHeaders.d.ts +45 -0
  100. package/dist/provider-proxy/identity/codexCliHeaders.js +10 -0
  101. package/dist/provider-proxy/ingress/providerProxyShared.cjs +23 -19
  102. package/dist/provider-proxy/ingress/providerProxyShared.d.cts +11 -1
  103. package/dist/provider-proxy/ingress/providerProxyShared.d.ts +11 -1
  104. package/dist/provider-proxy/ingress/providerProxyShared.js +22 -18
  105. package/dist/provider-proxy/matchText.cjs +2 -2
  106. package/dist/provider-proxy/matchText.d.cts +1 -3
  107. package/dist/provider-proxy/matchText.d.ts +1 -3
  108. package/dist/provider-proxy/matchText.js +1 -1
  109. package/dist/provider-proxy/types.d.cts +20 -1
  110. package/dist/provider-proxy/types.d.ts +20 -1
  111. package/dist/provider-proxy.cjs +79 -19
  112. package/dist/provider-proxy.d.cts +255 -8
  113. package/dist/provider-proxy.d.ts +255 -8
  114. package/dist/provider-proxy.js +80 -20
  115. package/dist/{routeResolver-CHTv_Bs5.d.ts → routeResolver-C0mkkZaM.d.ts} +1 -1
  116. package/dist/{routeResolver-kepxnfcC.d.cts → routeResolver-CVFE8KKe.d.cts} +1 -1
  117. package/dist/transformer/transformers/AnthropicTransformer.cjs +3 -2
  118. package/dist/transformer/transformers/AnthropicTransformer.js +2 -1
  119. package/dist/transformer/transformers/OpenAIResponseTransformer.cjs +3 -2
  120. package/dist/transformer/transformers/OpenAIResponseTransformer.js +2 -1
  121. package/dist/transformer/transformers.cjs +7 -6
  122. package/dist/transformer/transformers.js +6 -5
  123. package/dist/transformer.cjs +9 -8
  124. package/dist/transformer.js +8 -7
  125. package/dist/{types-hsoKrVQS.d.ts → types-BB8pxk_3.d.ts} +40 -1
  126. package/dist/{types-H3wGZSOo.d.cts → types-C2MwkLah.d.cts} +40 -1
  127. package/dist/usage/pricing-engine.cjs +2 -2
  128. package/dist/usage/pricing-engine.d.cts +20 -0
  129. package/dist/usage/pricing-engine.d.ts +20 -0
  130. package/dist/usage/pricing-engine.js +1 -1
  131. package/dist/usage/usage-recorder.cjs +2 -2
  132. package/dist/usage/usage-recorder.d.cts +11 -1
  133. package/dist/usage/usage-recorder.d.ts +11 -1
  134. package/dist/usage/usage-recorder.js +1 -1
  135. package/dist/usage.cjs +3 -3
  136. package/dist/usage.js +2 -2
  137. package/package.json +1 -1
@@ -1,4 +1,5 @@
1
1
  // src/provider-proxy/matchText.ts
2
+ import { createHash } from "crypto";
2
3
  var MATCH_TEXT_PER_MESSAGE_CAP = 8192;
3
4
  var MATCH_TEXT_RECENT_MESSAGES = 6;
4
5
  function flattenMatchText(value) {
@@ -55,39 +56,42 @@ var SESSION_ID_CAP = 1024;
55
56
  function deriveGatewaySessionKey(body, headers = {}, options = {}) {
56
57
  const sessionHeader = firstHeaderValue(headers, ["session-id", "session_id", "x-session-id"]);
57
58
  if (sessionHeader) {
58
- return { key: fnv1a(sessionHeader), source: "session-header" };
59
+ return { key: stableSessionDigest(sessionHeader), source: "session-header" };
59
60
  }
60
61
  const threadHeader = firstHeaderValue(headers, ["thread-id", "thread_id", "x-thread-id"]);
61
62
  if (threadHeader) {
62
- return { key: fnv1a(threadHeader), source: "thread-header" };
63
+ return { key: stableSessionDigest(threadHeader), source: "thread-header" };
63
64
  }
64
65
  const bodySessionId = scalarSessionValue(body.session_id);
65
66
  if (bodySessionId) {
66
- return { key: fnv1a(bodySessionId), source: "body-session-id" };
67
+ return { key: stableSessionDigest(bodySessionId), source: "body-session-id" };
67
68
  }
68
69
  const bodyThreadId = scalarSessionValue(body.thread_id ?? body.threadId);
69
70
  if (bodyThreadId) {
70
- return { key: fnv1a(bodyThreadId), source: "body-thread-id" };
71
+ return { key: stableSessionDigest(bodyThreadId), source: "body-thread-id" };
71
72
  }
72
73
  const conversationId = scalarSessionValue(body.conversation_id);
73
74
  if (conversationId) {
74
- return { key: fnv1a(conversationId), source: "body-session-id" };
75
+ return { key: stableSessionDigest(conversationId), source: "body-session-id" };
75
76
  }
76
77
  const promptCacheKey = scalarSessionValue(body.prompt_cache_key);
77
78
  if (promptCacheKey) {
78
- return { key: fnv1a(promptCacheKey), source: "prompt-cache-key" };
79
+ return { key: stableSessionDigest(promptCacheKey), source: "prompt-cache-key" };
79
80
  }
80
81
  const fingerprint = deriveResponsesContentFingerprint(body);
81
82
  if (fingerprint) {
82
- return { key: fnv1a(fingerprint), source: "content-fingerprint" };
83
+ return { key: stableSessionDigest(fingerprint), source: "content-fingerprint" };
83
84
  }
84
85
  const endpoint = options.endpoint?.trim() || "responses";
85
86
  const fallback = options.fallbackKey?.trim() || "anonymous";
86
87
  return {
87
- key: fnv1a(`${endpoint}\0${fallback}`),
88
+ key: stableSessionDigest(`${endpoint}\0${fallback}`),
88
89
  source: "api-key-fallback"
89
90
  };
90
91
  }
92
+ function stableSessionDigest(value) {
93
+ return createHash("sha256").update(value, "utf8").digest("hex").slice(0, 32);
94
+ }
91
95
  function firstHeaderValue(headers, names) {
92
96
  const byLower = /* @__PURE__ */ new Map();
93
97
  for (const [name, value] of Object.entries(headers)) {
@@ -0,0 +1,61 @@
1
+ // src/pipeline/AccountRouteActivity.ts
2
+ var ACCOUNT_ROUTE_ACTIVITY_LIMIT = 300;
3
+ function boundedLimit(value) {
4
+ if (!Number.isFinite(value)) return 100;
5
+ return Math.max(1, Math.min(ACCOUNT_ROUTE_ACTIVITY_LIMIT, Math.trunc(value)));
6
+ }
7
+ var AccountRouteActivityStore = class {
8
+ records = [];
9
+ sequence = 0;
10
+ record(input) {
11
+ const ts = input.ts ?? Date.now();
12
+ const sessionKey = input.sessionKey?.trim() || void 0;
13
+ const previous = sessionKey ? this.records.find((record2) => record2.providerId === input.providerId && record2.endpoint === input.endpoint && record2.sessionKey === sessionKey) : void 0;
14
+ const affinity = !sessionKey ? "untracked" : !previous ? "new" : previous.accountId === input.accountId ? "sticky" : "switched";
15
+ const record = {
16
+ ...input,
17
+ id: `${ts.toString(36)}-${(this.sequence++).toString(36)}`,
18
+ ts,
19
+ sessionKey,
20
+ affinity,
21
+ ...affinity === "switched" && previous ? { previousAccountId: previous.accountId } : {}
22
+ };
23
+ this.records.unshift(record);
24
+ if (this.records.length > ACCOUNT_ROUTE_ACTIVITY_LIMIT) {
25
+ this.records.length = ACCOUNT_ROUTE_ACTIVITY_LIMIT;
26
+ }
27
+ return { ...record };
28
+ }
29
+ list(query = {}) {
30
+ const providerId = query.providerId?.trim();
31
+ const accountId = query.accountId?.trim();
32
+ const sessionKey = query.sessionKey?.trim();
33
+ return this.records.filter((record) => !providerId || record.providerId === providerId).filter((record) => !accountId || record.accountId === accountId).filter((record) => !sessionKey || record.sessionKey === sessionKey).slice(0, boundedLimit(query.limit)).map((record) => ({ ...record }));
34
+ }
35
+ clear() {
36
+ this.records.length = 0;
37
+ this.sequence = 0;
38
+ }
39
+ /**
40
+ * Backfill a field on an already-recorded entry. Used to annotate a record
41
+ * AFTER the upstream body streams — e.g. a `200` whose SSE body carries a
42
+ * `response.failed` overload event is only detectable mid-relay, long after
43
+ * `record()` stamped the 200 status. `patch` is intentionally narrow: only
44
+ * post-hoc-observable fields belong here, never the routing-decision fields.
45
+ * No-op when the id has already aged out of the bounded ring.
46
+ */
47
+ amend(id, patch) {
48
+ const record = this.records.find((r) => r.id === id);
49
+ if (record) Object.assign(record, patch);
50
+ }
51
+ };
52
+ var sharedAccountRouteActivity = new AccountRouteActivityStore();
53
+ function getSharedAccountRouteActivity() {
54
+ return sharedAccountRouteActivity;
55
+ }
56
+
57
+ export {
58
+ ACCOUNT_ROUTE_ACTIVITY_LIMIT,
59
+ AccountRouteActivityStore,
60
+ getSharedAccountRouteActivity
61
+ };
@@ -1,4 +1,5 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }// src/provider-proxy/matchText.ts
2
+ var _crypto = require('crypto');
2
3
  var MATCH_TEXT_PER_MESSAGE_CAP = 8192;
3
4
  var MATCH_TEXT_RECENT_MESSAGES = 6;
4
5
  function flattenMatchText(value) {
@@ -55,39 +56,42 @@ var SESSION_ID_CAP = 1024;
55
56
  function deriveGatewaySessionKey(body, headers = {}, options = {}) {
56
57
  const sessionHeader = firstHeaderValue(headers, ["session-id", "session_id", "x-session-id"]);
57
58
  if (sessionHeader) {
58
- return { key: fnv1a(sessionHeader), source: "session-header" };
59
+ return { key: stableSessionDigest(sessionHeader), source: "session-header" };
59
60
  }
60
61
  const threadHeader = firstHeaderValue(headers, ["thread-id", "thread_id", "x-thread-id"]);
61
62
  if (threadHeader) {
62
- return { key: fnv1a(threadHeader), source: "thread-header" };
63
+ return { key: stableSessionDigest(threadHeader), source: "thread-header" };
63
64
  }
64
65
  const bodySessionId = scalarSessionValue(body.session_id);
65
66
  if (bodySessionId) {
66
- return { key: fnv1a(bodySessionId), source: "body-session-id" };
67
+ return { key: stableSessionDigest(bodySessionId), source: "body-session-id" };
67
68
  }
68
69
  const bodyThreadId = scalarSessionValue(_nullishCoalesce(body.thread_id, () => ( body.threadId)));
69
70
  if (bodyThreadId) {
70
- return { key: fnv1a(bodyThreadId), source: "body-thread-id" };
71
+ return { key: stableSessionDigest(bodyThreadId), source: "body-thread-id" };
71
72
  }
72
73
  const conversationId = scalarSessionValue(body.conversation_id);
73
74
  if (conversationId) {
74
- return { key: fnv1a(conversationId), source: "body-session-id" };
75
+ return { key: stableSessionDigest(conversationId), source: "body-session-id" };
75
76
  }
76
77
  const promptCacheKey = scalarSessionValue(body.prompt_cache_key);
77
78
  if (promptCacheKey) {
78
- return { key: fnv1a(promptCacheKey), source: "prompt-cache-key" };
79
+ return { key: stableSessionDigest(promptCacheKey), source: "prompt-cache-key" };
79
80
  }
80
81
  const fingerprint = deriveResponsesContentFingerprint(body);
81
82
  if (fingerprint) {
82
- return { key: fnv1a(fingerprint), source: "content-fingerprint" };
83
+ return { key: stableSessionDigest(fingerprint), source: "content-fingerprint" };
83
84
  }
84
85
  const endpoint = _optionalChain([options, 'access', _ => _.endpoint, 'optionalAccess', _2 => _2.trim, 'call', _3 => _3()]) || "responses";
85
86
  const fallback = _optionalChain([options, 'access', _4 => _4.fallbackKey, 'optionalAccess', _5 => _5.trim, 'call', _6 => _6()]) || "anonymous";
86
87
  return {
87
- key: fnv1a(`${endpoint}\0${fallback}`),
88
+ key: stableSessionDigest(`${endpoint}\0${fallback}`),
88
89
  source: "api-key-fallback"
89
90
  };
90
91
  }
92
+ function stableSessionDigest(value) {
93
+ return _crypto.createHash.call(void 0, "sha256").update(value, "utf8").digest("hex").slice(0, 32);
94
+ }
91
95
  function firstHeaderValue(headers, names) {
92
96
  const byLower = /* @__PURE__ */ new Map();
93
97
  for (const [name, value] of Object.entries(headers)) {
@@ -1,15 +1,15 @@
1
+ import {
2
+ AnthropicTransformer
3
+ } from "./chunk-PEGTIPB3.js";
1
4
  import {
2
5
  GeminiTransformer
3
6
  } from "./chunk-LVUCAWMK.js";
4
7
  import {
5
8
  OpenAIResponseTransformer
6
- } from "./chunk-OEWZUX4G.js";
9
+ } from "./chunk-32O35CIF.js";
7
10
  import {
8
11
  OpenAITransformer
9
12
  } from "./chunk-LB6EBS6Y.js";
10
- import {
11
- AnthropicTransformer
12
- } from "./chunk-6CL5HTPQ.js";
13
13
  import {
14
14
  GeminiCodeAssistTransformer
15
15
  } from "./chunk-N5T4M4GU.js";
@@ -3,6 +3,7 @@ var OVERLOAD_TTL_MS = 10 * 6e4;
3
3
  var OVERLOAD_ENABLED_DEFAULT = true;
4
4
  var AUTH_ERROR_TTL_MS = 30 * 6e4;
5
5
  var SERVER_ERROR_TTL_MS = 5 * 6e4;
6
+ var MAX_QUOTA_COOLDOWN_MS = 7 * 24 * 60 * 6e4;
6
7
  var BAN_BODY_MARKERS = [
7
8
  "this organization has been disabled",
8
9
  "oauth authentication is currently not allowed"
@@ -38,7 +39,7 @@ function isBanBody(bodyText) {
38
39
  return BAN_BODY_MARKERS.some((marker) => lower.includes(marker));
39
40
  }
40
41
  function isRecordEmpty(record) {
41
- return record.rateLimitEndAt === void 0 && record.overloadUntil === void 0 && record.tempUnavailableUntil === void 0 && !record.blocked;
42
+ return record.rateLimitEndAt === void 0 && record.overloadUntil === void 0 && record.tempUnavailableUntil === void 0 && record.quotaExhaustedUntil === void 0 && !record.blocked;
42
43
  }
43
44
  var KEY_SEP = "\0";
44
45
  var SubscriptionAccountHealth = class {
@@ -159,12 +160,47 @@ var SubscriptionAccountHealth = class {
159
160
  this.emitAnomaly(anomaly);
160
161
  }
161
162
  }
163
+ /**
164
+ * Mark an account quota/usage-exhausted until `untilEpochMs` (the Codex weekly
165
+ * usage-limit wall). Unlike the status-driven timers set by
166
+ * {@link recordUpstreamOutcome}, this mark is NOT cleared by a 2xx: in-flight
167
+ * sessions on the same account keep succeeding after the window is exhausted,
168
+ * so a traffic-driven clear would re-strand every NEW session onto this
169
+ * account. It excludes the account from NEW-session selection until the
170
+ * deadline elapses (existing sessions are unaffected — they already hold their
171
+ * token). Self-heals lazily on read and via {@link sweepRecoveries}.
172
+ * `untilEpochMs` is clamped to {@link MAX_QUOTA_COOLDOWN_MS}; an already-elapsed
173
+ * deadline is a no-op. Emits a `quota_exhausted` anomaly on the healthy→
174
+ * unhealthy edge.
175
+ */
176
+ markQuotaExhausted(providerId, accountId, untilEpochMs, now = this.now()) {
177
+ if (!Number.isFinite(untilEpochMs)) return;
178
+ const clamped = Math.min(untilEpochMs, now + MAX_QUOTA_COOLDOWN_MS);
179
+ if (clamped <= now) return;
180
+ const key = this.key(providerId, accountId);
181
+ const preExisting = this.records.get(key);
182
+ const record = preExisting ?? {};
183
+ const wasUnhealthyBefore = preExisting ? this.isUnhealthyAt(preExisting, now) : false;
184
+ record.quotaExhaustedUntil = clamped;
185
+ this.records.set(key, record);
186
+ if (!wasUnhealthyBefore) {
187
+ const anomaly = {
188
+ providerId,
189
+ accountId,
190
+ at: now,
191
+ state: "quota_exhausted"
192
+ };
193
+ this.recordDiagnostic({ kind: "health-anomaly", ...anomaly });
194
+ this.emitAnomaly(anomaly);
195
+ }
196
+ }
162
197
  /** Whether a record is unhealthy at `now`: blocked, or any timer still future. */
163
198
  isUnhealthyAt(record, now) {
164
199
  if (record.blocked) return true;
165
200
  if (record.rateLimitEndAt !== void 0 && now < record.rateLimitEndAt) return true;
166
201
  if (record.overloadUntil !== void 0 && now < record.overloadUntil) return true;
167
202
  if (record.tempUnavailableUntil !== void 0 && now < record.tempUnavailableUntil) return true;
203
+ if (record.quotaExhaustedUntil !== void 0 && now < record.quotaExhaustedUntil) return true;
168
204
  return false;
169
205
  }
170
206
  /**
@@ -201,7 +237,7 @@ var SubscriptionAccountHealth = class {
201
237
  const recovered = [];
202
238
  for (const [key, record] of this.records) {
203
239
  if (record.blocked) continue;
204
- const hadTimer = record.rateLimitEndAt !== void 0 || record.overloadUntil !== void 0 || record.tempUnavailableUntil !== void 0;
240
+ const hadTimer = record.rateLimitEndAt !== void 0 || record.overloadUntil !== void 0 || record.tempUnavailableUntil !== void 0 || record.quotaExhaustedUntil !== void 0;
205
241
  const active = this.clearExpired(record, now);
206
242
  if (hadTimer && !active && isRecordEmpty(record)) {
207
243
  const [pid, aid] = this.parseKey(key);
@@ -225,6 +261,9 @@ var SubscriptionAccountHealth = class {
225
261
  const record = this.records.get(this.key(providerId, accountId));
226
262
  if (!record) return { state: "healthy" };
227
263
  if (record.blocked) return { state: "blocked" };
264
+ if (record.quotaExhaustedUntil !== void 0 && now < record.quotaExhaustedUntil) {
265
+ return { state: "quota_exhausted", cooldownUntil: record.quotaExhaustedUntil };
266
+ }
228
267
  if (record.rateLimitEndAt !== void 0 && now < record.rateLimitEndAt) {
229
268
  return { state: "rate_limited", cooldownUntil: record.rateLimitEndAt };
230
269
  }
@@ -311,6 +350,10 @@ var SubscriptionAccountHealth = class {
311
350
  if (now >= record.tempUnavailableUntil) delete record.tempUnavailableUntil;
312
351
  else active = true;
313
352
  }
353
+ if (record.quotaExhaustedUntil !== void 0) {
354
+ if (now >= record.quotaExhaustedUntil) delete record.quotaExhaustedUntil;
355
+ else active = true;
356
+ }
314
357
  return active;
315
358
  }
316
359
  key(providerId, accountId) {
@@ -338,6 +381,7 @@ export {
338
381
  OVERLOAD_ENABLED_DEFAULT,
339
382
  AUTH_ERROR_TTL_MS,
340
383
  SERVER_ERROR_TTL_MS,
384
+ MAX_QUOTA_COOLDOWN_MS,
341
385
  ACCOUNT_HEALTH_DIAGNOSTIC_LIMIT,
342
386
  resolveResetSeconds,
343
387
  SubscriptionAccountHealth,
@@ -0,0 +1,61 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class;// src/pipeline/AccountRouteActivity.ts
2
+ var ACCOUNT_ROUTE_ACTIVITY_LIMIT = 300;
3
+ function boundedLimit(value) {
4
+ if (!Number.isFinite(value)) return 100;
5
+ return Math.max(1, Math.min(ACCOUNT_ROUTE_ACTIVITY_LIMIT, Math.trunc(value)));
6
+ }
7
+ var AccountRouteActivityStore = (_class = class {constructor() { _class.prototype.__init.call(this);_class.prototype.__init2.call(this); }
8
+ __init() {this.records = []}
9
+ __init2() {this.sequence = 0}
10
+ record(input) {
11
+ const ts = _nullishCoalesce(input.ts, () => ( Date.now()));
12
+ const sessionKey = _optionalChain([input, 'access', _ => _.sessionKey, 'optionalAccess', _2 => _2.trim, 'call', _3 => _3()]) || void 0;
13
+ const previous = sessionKey ? this.records.find((record2) => record2.providerId === input.providerId && record2.endpoint === input.endpoint && record2.sessionKey === sessionKey) : void 0;
14
+ const affinity = !sessionKey ? "untracked" : !previous ? "new" : previous.accountId === input.accountId ? "sticky" : "switched";
15
+ const record = {
16
+ ...input,
17
+ id: `${ts.toString(36)}-${(this.sequence++).toString(36)}`,
18
+ ts,
19
+ sessionKey,
20
+ affinity,
21
+ ...affinity === "switched" && previous ? { previousAccountId: previous.accountId } : {}
22
+ };
23
+ this.records.unshift(record);
24
+ if (this.records.length > ACCOUNT_ROUTE_ACTIVITY_LIMIT) {
25
+ this.records.length = ACCOUNT_ROUTE_ACTIVITY_LIMIT;
26
+ }
27
+ return { ...record };
28
+ }
29
+ list(query = {}) {
30
+ const providerId = _optionalChain([query, 'access', _4 => _4.providerId, 'optionalAccess', _5 => _5.trim, 'call', _6 => _6()]);
31
+ const accountId = _optionalChain([query, 'access', _7 => _7.accountId, 'optionalAccess', _8 => _8.trim, 'call', _9 => _9()]);
32
+ const sessionKey = _optionalChain([query, 'access', _10 => _10.sessionKey, 'optionalAccess', _11 => _11.trim, 'call', _12 => _12()]);
33
+ return this.records.filter((record) => !providerId || record.providerId === providerId).filter((record) => !accountId || record.accountId === accountId).filter((record) => !sessionKey || record.sessionKey === sessionKey).slice(0, boundedLimit(query.limit)).map((record) => ({ ...record }));
34
+ }
35
+ clear() {
36
+ this.records.length = 0;
37
+ this.sequence = 0;
38
+ }
39
+ /**
40
+ * Backfill a field on an already-recorded entry. Used to annotate a record
41
+ * AFTER the upstream body streams — e.g. a `200` whose SSE body carries a
42
+ * `response.failed` overload event is only detectable mid-relay, long after
43
+ * `record()` stamped the 200 status. `patch` is intentionally narrow: only
44
+ * post-hoc-observable fields belong here, never the routing-decision fields.
45
+ * No-op when the id has already aged out of the bounded ring.
46
+ */
47
+ amend(id, patch) {
48
+ const record = this.records.find((r) => r.id === id);
49
+ if (record) Object.assign(record, patch);
50
+ }
51
+ }, _class);
52
+ var sharedAccountRouteActivity = new AccountRouteActivityStore();
53
+ function getSharedAccountRouteActivity() {
54
+ return sharedAccountRouteActivity;
55
+ }
56
+
57
+
58
+
59
+
60
+
61
+ exports.ACCOUNT_ROUTE_ACTIVITY_LIMIT = ACCOUNT_ROUTE_ACTIVITY_LIMIT; exports.AccountRouteActivityStore = AccountRouteActivityStore; exports.getSharedAccountRouteActivity = getSharedAccountRouteActivity;
@@ -0,0 +1,79 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class;// src/pipeline/ServerOverloadCounter.ts
2
+ var OVERLOAD_RECENT_LIMIT = 64;
3
+ var OVERLOAD_ENTRY_TTL_MS = 24 * 60 * 6e4;
4
+ function entryKey(providerId, accountId, endpoint) {
5
+ return `${providerId}\0${accountId}\0${endpoint}`;
6
+ }
7
+ var ServerOverloadCounterStore = (_class = class {constructor() { _class.prototype.__init.call(this); }
8
+ __init() {this.entries = /* @__PURE__ */ new Map()}
9
+ /**
10
+ * Record one overload event. Creates the entry on first sight, then bumps
11
+ * `count`, `lastTs`, and prepends the event to the bounded `recent` ring.
12
+ */
13
+ recordOverload(input, now = Date.now()) {
14
+ const key = entryKey(input.providerId, input.accountId, input.endpoint);
15
+ const existing = this.entries.get(key);
16
+ if (existing) {
17
+ existing.count += 1;
18
+ existing.lastTs = now;
19
+ existing.recent.unshift(now);
20
+ if (existing.recent.length > OVERLOAD_RECENT_LIMIT) {
21
+ existing.recent.length = OVERLOAD_RECENT_LIMIT;
22
+ }
23
+ return { ...existing, recent: [...existing.recent] };
24
+ }
25
+ const entry = {
26
+ providerId: input.providerId,
27
+ accountId: input.accountId,
28
+ endpoint: input.endpoint,
29
+ count: 1,
30
+ firstTs: now,
31
+ lastTs: now,
32
+ recent: [now]
33
+ };
34
+ this.entries.set(key, entry);
35
+ return { ...entry, recent: [...entry.recent] };
36
+ }
37
+ /**
38
+ * Snapshot all (optionally filtered) entries, pruning stale ones first. Returns
39
+ * copies — callers (admin API → JSON) never see the live mutable entries.
40
+ */
41
+ list(query = {}, now = Date.now()) {
42
+ const providerId = _optionalChain([query, 'access', _ => _.providerId, 'optionalAccess', _2 => _2.trim, 'call', _3 => _3()]);
43
+ const accountId = _optionalChain([query, 'access', _4 => _4.accountId, 'optionalAccess', _5 => _5.trim, 'call', _6 => _6()]);
44
+ const out = [];
45
+ for (const [key, entry] of this.entries) {
46
+ if (now - entry.lastTs > OVERLOAD_ENTRY_TTL_MS) {
47
+ this.entries.delete(key);
48
+ continue;
49
+ }
50
+ if (providerId && entry.providerId !== providerId) continue;
51
+ if (accountId && entry.accountId !== accountId) continue;
52
+ out.push({ ...entry, recent: [...entry.recent] });
53
+ }
54
+ return out;
55
+ }
56
+ clear() {
57
+ this.entries.clear();
58
+ }
59
+ }, _class);
60
+ var sharedStore = null;
61
+ function getSharedOverloadCounter() {
62
+ if (!sharedStore) sharedStore = new ServerOverloadCounterStore();
63
+ return sharedStore;
64
+ }
65
+ function setSharedOverloadCounter(instance) {
66
+ sharedStore = instance;
67
+ }
68
+ function __resetSharedOverloadCounterForTests() {
69
+ sharedStore = null;
70
+ }
71
+
72
+
73
+
74
+
75
+
76
+
77
+
78
+
79
+ exports.OVERLOAD_RECENT_LIMIT = OVERLOAD_RECENT_LIMIT; exports.OVERLOAD_ENTRY_TTL_MS = OVERLOAD_ENTRY_TTL_MS; exports.ServerOverloadCounterStore = ServerOverloadCounterStore; exports.getSharedOverloadCounter = getSharedOverloadCounter; exports.setSharedOverloadCounter = setSharedOverloadCounter; exports.__resetSharedOverloadCounterForTests = __resetSharedOverloadCounterForTests;
@@ -1,4 +1,9 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class;// src/transformer/transformers/OpenAIResponseTransformer.ts
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class;
2
+
3
+
4
+ var _chunk5RSZYQJHcjs = require('./chunk-5RSZYQJH.cjs');
5
+
6
+ // src/transformer/transformers/OpenAIResponseTransformer.ts
2
7
  var OpenAIResponseTransformer = (_class = class {constructor() { _class.prototype.__init.call(this);_class.prototype.__init2.call(this); }
3
8
  static __initStatic() {this.TransformerName = "openai-response"}
4
9
  __init() {this.name = "openai-response"}
@@ -433,11 +438,7 @@ function convertResponseApiJsonToOpenAI(data) {
433
438
  finish_reason: toolCalls.length > 0 ? "tool_calls" : "stop"
434
439
  }
435
440
  ],
436
- usage: usage ? {
437
- prompt_tokens: usage.input_tokens || 0,
438
- completion_tokens: usage.output_tokens || 0,
439
- total_tokens: (usage.input_tokens || 0) + (usage.output_tokens || 0)
440
- } : void 0
441
+ usage: _chunk5RSZYQJHcjs.responsesUsageToChatUsage.call(void 0, usage)
441
442
  };
442
443
  }
443
444
  function convertOpenAIJsonToResponseApi(data, codexTools) {
@@ -475,11 +476,7 @@ function convertOpenAIJsonToResponseApi(data, codexTools) {
475
476
  status: "completed",
476
477
  model: data.model || "unknown",
477
478
  output,
478
- usage: usage ? {
479
- input_tokens: usage.prompt_tokens || 0,
480
- output_tokens: usage.completion_tokens || 0,
481
- total_tokens: usage.total_tokens || (usage.prompt_tokens || 0) + (usage.completion_tokens || 0)
482
- } : void 0
479
+ usage: _chunk5RSZYQJHcjs.chatUsageToResponsesUsage.call(void 0, usage)
483
480
  };
484
481
  }
485
482
  function convertResponseApiStreamToOpenAI(responseApiStream) {
@@ -589,11 +586,7 @@ function convertResponseApiStreamToOpenAI(responseApiStream) {
589
586
  case "response.completed": {
590
587
  const resp = event.response;
591
588
  const respUsage = _optionalChain([resp, 'optionalAccess', _32 => _32.usage]);
592
- const usage = respUsage ? {
593
- prompt_tokens: respUsage.input_tokens || 0,
594
- completion_tokens: respUsage.output_tokens || 0,
595
- total_tokens: (respUsage.input_tokens || 0) + (respUsage.output_tokens || 0)
596
- } : void 0;
589
+ const usage = _chunk5RSZYQJHcjs.responsesUsageToChatUsage.call(void 0, respUsage);
597
590
  emitChunk([{ index: 0, delta: {}, finish_reason: hasToolCalls ? "tool_calls" : "stop" }], usage);
598
591
  safeEnqueue("data: [DONE]\n\n");
599
592
  break;
@@ -801,11 +794,7 @@ function convertOpenAIStreamToResponseApi(openaiStream, codexTools) {
801
794
  status: "completed",
802
795
  model,
803
796
  output,
804
- usage: chunk.usage ? {
805
- input_tokens: chunk.usage.prompt_tokens || 0,
806
- output_tokens: chunk.usage.completion_tokens || 0,
807
- total_tokens: chunk.usage.total_tokens || 0
808
- } : void 0
797
+ usage: _chunk5RSZYQJHcjs.chatUsageToResponsesUsage.call(void 0, chunk.usage)
809
798
  }
810
799
  });
811
800
  }
@@ -1,4 +1,8 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class;// src/transformer/transformers/AnthropicToolHandling.ts
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class;
2
+
3
+ var _chunk5RSZYQJHcjs = require('./chunk-5RSZYQJH.cjs');
4
+
5
+ // src/transformer/transformers/AnthropicToolHandling.ts
2
6
  function isServerSideTool(tool) {
3
7
  const type = String(tool.type || "");
4
8
  return type.startsWith("web_search_") || type.startsWith("code_execution_") || type.startsWith("text_editor_") || type.startsWith("memory_") || type.startsWith("web_fetch_") || type.startsWith("search_tool_");
@@ -261,11 +265,9 @@ ${formatted}`);
261
265
  message,
262
266
  finish_reason: stopReasonMapping[anthropicResponse.stop_reason] || "stop"
263
267
  }],
264
- usage: usage ? {
265
- prompt_tokens: usage.input_tokens || 0,
266
- completion_tokens: usage.output_tokens || 0,
267
- total_tokens: (usage.input_tokens || 0) + (usage.output_tokens || 0)
268
- } : void 0
268
+ // Preserve prompt-cache counts (cache-read → prompt_tokens_details.cached_tokens,
269
+ // cache-creation folded into prompt_tokens) instead of dropping them.
270
+ usage: _chunk5RSZYQJHcjs.anthropicUsageToChatUsage.call(void 0, usage)
269
271
  };
270
272
  }
271
273
  function convertOpenAIResponseToAnthropic(openaiResponse) {
@@ -619,11 +621,7 @@ ${formatted}` },
619
621
  }]
620
622
  };
621
623
  if (event.usage) {
622
- chunk.usage = {
623
- prompt_tokens: event.usage.input_tokens || 0,
624
- completion_tokens: event.usage.output_tokens || 0,
625
- total_tokens: (event.usage.input_tokens || 0) + (event.usage.output_tokens || 0)
626
- };
624
+ chunk.usage = _chunk5RSZYQJHcjs.anthropicUsageToChatUsage.call(void 0, event.usage);
627
625
  }
628
626
  safeEnqueue(`data: ${JSON.stringify(chunk)}
629
627
 
@@ -1,3 +1,7 @@
1
+ import {
2
+ anthropicUsageToChatUsage
3
+ } from "./chunk-FU7EAR73.js";
4
+
1
5
  // src/transformer/transformers/AnthropicToolHandling.ts
2
6
  function isServerSideTool(tool) {
3
7
  const type = String(tool.type || "");
@@ -261,11 +265,9 @@ ${formatted}`);
261
265
  message,
262
266
  finish_reason: stopReasonMapping[anthropicResponse.stop_reason] || "stop"
263
267
  }],
264
- usage: usage ? {
265
- prompt_tokens: usage.input_tokens || 0,
266
- completion_tokens: usage.output_tokens || 0,
267
- total_tokens: (usage.input_tokens || 0) + (usage.output_tokens || 0)
268
- } : void 0
268
+ // Preserve prompt-cache counts (cache-read → prompt_tokens_details.cached_tokens,
269
+ // cache-creation folded into prompt_tokens) instead of dropping them.
270
+ usage: anthropicUsageToChatUsage(usage)
269
271
  };
270
272
  }
271
273
  function convertOpenAIResponseToAnthropic(openaiResponse) {
@@ -619,11 +621,7 @@ ${formatted}` },
619
621
  }]
620
622
  };
621
623
  if (event.usage) {
622
- chunk.usage = {
623
- prompt_tokens: event.usage.input_tokens || 0,
624
- completion_tokens: event.usage.output_tokens || 0,
625
- total_tokens: (event.usage.input_tokens || 0) + (event.usage.output_tokens || 0)
626
- };
624
+ chunk.usage = anthropicUsageToChatUsage(event.usage);
627
625
  }
628
626
  safeEnqueue(`data: ${JSON.stringify(chunk)}
629
627
 
@@ -0,0 +1,79 @@
1
+ // src/pipeline/ServerOverloadCounter.ts
2
+ var OVERLOAD_RECENT_LIMIT = 64;
3
+ var OVERLOAD_ENTRY_TTL_MS = 24 * 60 * 6e4;
4
+ function entryKey(providerId, accountId, endpoint) {
5
+ return `${providerId}\0${accountId}\0${endpoint}`;
6
+ }
7
+ var ServerOverloadCounterStore = class {
8
+ entries = /* @__PURE__ */ new Map();
9
+ /**
10
+ * Record one overload event. Creates the entry on first sight, then bumps
11
+ * `count`, `lastTs`, and prepends the event to the bounded `recent` ring.
12
+ */
13
+ recordOverload(input, now = Date.now()) {
14
+ const key = entryKey(input.providerId, input.accountId, input.endpoint);
15
+ const existing = this.entries.get(key);
16
+ if (existing) {
17
+ existing.count += 1;
18
+ existing.lastTs = now;
19
+ existing.recent.unshift(now);
20
+ if (existing.recent.length > OVERLOAD_RECENT_LIMIT) {
21
+ existing.recent.length = OVERLOAD_RECENT_LIMIT;
22
+ }
23
+ return { ...existing, recent: [...existing.recent] };
24
+ }
25
+ const entry = {
26
+ providerId: input.providerId,
27
+ accountId: input.accountId,
28
+ endpoint: input.endpoint,
29
+ count: 1,
30
+ firstTs: now,
31
+ lastTs: now,
32
+ recent: [now]
33
+ };
34
+ this.entries.set(key, entry);
35
+ return { ...entry, recent: [...entry.recent] };
36
+ }
37
+ /**
38
+ * Snapshot all (optionally filtered) entries, pruning stale ones first. Returns
39
+ * copies — callers (admin API → JSON) never see the live mutable entries.
40
+ */
41
+ list(query = {}, now = Date.now()) {
42
+ const providerId = query.providerId?.trim();
43
+ const accountId = query.accountId?.trim();
44
+ const out = [];
45
+ for (const [key, entry] of this.entries) {
46
+ if (now - entry.lastTs > OVERLOAD_ENTRY_TTL_MS) {
47
+ this.entries.delete(key);
48
+ continue;
49
+ }
50
+ if (providerId && entry.providerId !== providerId) continue;
51
+ if (accountId && entry.accountId !== accountId) continue;
52
+ out.push({ ...entry, recent: [...entry.recent] });
53
+ }
54
+ return out;
55
+ }
56
+ clear() {
57
+ this.entries.clear();
58
+ }
59
+ };
60
+ var sharedStore = null;
61
+ function getSharedOverloadCounter() {
62
+ if (!sharedStore) sharedStore = new ServerOverloadCounterStore();
63
+ return sharedStore;
64
+ }
65
+ function setSharedOverloadCounter(instance) {
66
+ sharedStore = instance;
67
+ }
68
+ function __resetSharedOverloadCounterForTests() {
69
+ sharedStore = null;
70
+ }
71
+
72
+ export {
73
+ OVERLOAD_RECENT_LIMIT,
74
+ OVERLOAD_ENTRY_TTL_MS,
75
+ ServerOverloadCounterStore,
76
+ getSharedOverloadCounter,
77
+ setSharedOverloadCounter,
78
+ __resetSharedOverloadCounterForTests
79
+ };