@openlimiter/connectors 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/shared.js CHANGED
@@ -20,6 +20,172 @@ export function futureInstant(value, now) {
20
20
  ? new Date(parsed).toISOString()
21
21
  : null;
22
22
  }
23
+ /**
24
+ * Largest epoch value that is still read as seconds.
25
+ *
26
+ * A number this size in seconds is the year 33658, and the same number read as
27
+ * milliseconds is 2001. Anything at or above it is therefore a millisecond
28
+ * stamp handed to a seconds field, which is the single most likely way this
29
+ * conversion gets a reset instant wrong, so it is refused rather than turned
30
+ * into a date thirty thousand years away.
31
+ */
32
+ export const MAX_EPOCH_SECONDS = 1e12;
33
+ /**
34
+ * Slack allowed between a machine's clock and a provider's clock.
35
+ *
36
+ * An hour covers a wrong time zone offset applied to a timestamp, which is the
37
+ * realistic way two correct systems disagree, without widening the plausibility
38
+ * bound below into something that accepts nonsense.
39
+ */
40
+ export const CLOCK_SKEW_SECONDS = 3_600;
41
+ /**
42
+ * The furthest ahead a window of a given length may plausibly reset.
43
+ *
44
+ * A rolling window cannot reset more than one window from now, so twice the
45
+ * window is already generous, and the skew allowance sits on top of that. This
46
+ * is what stops a five hour meter from claiming it resets in 2038: such a value
47
+ * is not a reset, it is a corrupt field, a different unit, or a different
48
+ * meaning, and rendering a twelve year countdown beside a five hour window
49
+ * would be the product lying with a straight face.
50
+ */
51
+ export function plausibleResetHorizon(windowSeconds) {
52
+ return windowSeconds * 2 + CLOCK_SKEW_SECONDS;
53
+ }
54
+ /**
55
+ * Read a reset instant stated as Unix epoch seconds.
56
+ *
57
+ * Claude Code states its reset times this way. The value must be a finite
58
+ * number, must be in seconds rather than milliseconds, must still be in the
59
+ * future against the supplied clock, because a window that has already reset
60
+ * says nothing about the window running now, and must be close enough to now to
61
+ * belong to the window it claims to describe. Nothing is repaired: a value that
62
+ * fails any of these is null and the caller drops that reading alone.
63
+ *
64
+ * `maxAheadSeconds` is the plausibility bound and is optional only so the
65
+ * helper stays usable for a source with no window length to reason from.
66
+ */
67
+ export function futureInstantFromEpochSeconds(value, now, maxAheadSeconds) {
68
+ if (typeof value !== "number" || !Number.isFinite(value))
69
+ return null;
70
+ if (value <= 0 || value >= MAX_EPOCH_SECONDS)
71
+ return null;
72
+ const current = Date.parse(now);
73
+ if (!Number.isFinite(current))
74
+ return null;
75
+ const milliseconds = Math.round(value * 1_000);
76
+ if (milliseconds <= current)
77
+ return null;
78
+ if (typeof maxAheadSeconds === "number" &&
79
+ Number.isFinite(maxAheadSeconds) &&
80
+ maxAheadSeconds > 0 &&
81
+ milliseconds > current + maxAheadSeconds * 1_000)
82
+ return null;
83
+ const instant = new Date(milliseconds);
84
+ return Number.isFinite(instant.getTime()) ? instant.toISOString() : null;
85
+ }
86
+ /**
87
+ * A fraction of a whole, as Google's quota summary states remaining quota.
88
+ *
89
+ * Zero and one are both real answers, so the bound is inclusive. A boolean is
90
+ * refused explicitly: JavaScript will happily do arithmetic on `true`, and a
91
+ * `remainingFraction` of `true` would otherwise become a usage of zero percent,
92
+ * which is the most dangerous wrong answer this product can give.
93
+ */
94
+ export function boundedFraction(value) {
95
+ if (typeof value !== "number" || !Number.isFinite(value))
96
+ return null;
97
+ return value >= 0 && value <= 1 ? value : null;
98
+ }
99
+ /**
100
+ * A whole positive count of seconds, when a provider states a window length.
101
+ *
102
+ * Bounded above by a year, because a rolling window longer than that is not a
103
+ * subscription window, it is a misread field or a different unit.
104
+ */
105
+ export const MAX_WINDOW_SECONDS = 31_536_000;
106
+ export function windowSeconds(value) {
107
+ if (typeof value !== "number" || !Number.isInteger(value))
108
+ return null;
109
+ return value > 0 && value <= MAX_WINDOW_SECONDS ? value : null;
110
+ }
111
+ /**
112
+ * Read a reset instant stated as an RFC3339 timestamp.
113
+ *
114
+ * Google's quota summary states reset times this way, and the Antigravity
115
+ * client writes them with SEVEN fractional digits, which `Date.parse` on some
116
+ * runtimes reads and others do not. The fraction is normalised to three digits
117
+ * before parsing, which is the one repair made anywhere in this file, and it is
118
+ * a repair of ENCODING rather than of value: the instant it names is unchanged.
119
+ * Recorded 2026-08-07 against a real credential and a real response.
120
+ *
121
+ * An offsetless stamp is read as UTC explicitly. Both observed sources carry an
122
+ * offset today, so this is a guard rather than a fix, and it exists because the
123
+ * alternative is silent: a naive stamp read as local time moves a reset by the
124
+ * machine's offset instead of failing.
125
+ */
126
+ export function futureInstantFromRfc3339(value, now, maxAheadSeconds) {
127
+ if (typeof value !== "string" || value.length > 64)
128
+ return null;
129
+ const trimmed = value.trim();
130
+ /* Three digits is what every runtime agrees on; the rest are dropped, not
131
+ rounded, because sub millisecond precision on a quota reset is noise. */
132
+ const normalized = trimmed.replace(/\.(\d{1,9})/u, (_match, digits) => "." + (digits + "000").slice(0, 3));
133
+ const withZone = /(?:Z|[+-]\d{2}:?\d{2})$/u.test(normalized)
134
+ ? normalized
135
+ : normalized + "Z";
136
+ const parsed = Date.parse(withZone);
137
+ const current = Date.parse(now);
138
+ if (!Number.isFinite(parsed) || !Number.isFinite(current))
139
+ return null;
140
+ if (parsed <= current)
141
+ return null;
142
+ if (typeof maxAheadSeconds === "number" &&
143
+ Number.isFinite(maxAheadSeconds) &&
144
+ maxAheadSeconds > 0 &&
145
+ parsed > current + maxAheadSeconds * 1_000)
146
+ return null;
147
+ return new Date(parsed).toISOString();
148
+ }
149
+ /** How many seconds each unit a provider spells out is worth. */
150
+ const DURATION_UNITS = {
151
+ day: 86_400,
152
+ hour: 3_600,
153
+ minute: 60,
154
+ second: 1
155
+ };
156
+ /**
157
+ * Seconds from a duration a page spelled out in words, or null.
158
+ *
159
+ * OpenCode's workspace page prints "Resets in 5 days 20 hours" rather than a
160
+ * timestamp, so the only reset available is a countdown rendered for a human.
161
+ * Only consecutive number and unit pairs are read, and an empty or zero total
162
+ * is null rather than now: a reset of zero seconds would render as a countdown
163
+ * that has already finished.
164
+ */
165
+ export function durationSecondsFromWords(text) {
166
+ let total = 0;
167
+ let seen = false;
168
+ const pattern = /(\d{1,6})\s*(day|hour|minute|second)s?/giu;
169
+ for (const match of text.matchAll(pattern)) {
170
+ const count = Number.parseInt(match[1] ?? "", 10);
171
+ const unit = DURATION_UNITS[(match[2] ?? "").toLowerCase()];
172
+ if (!Number.isFinite(count) || unit === undefined)
173
+ return null;
174
+ total += count * unit;
175
+ seen = true;
176
+ }
177
+ if (!seen || total <= 0 || total > MAX_WINDOW_SECONDS)
178
+ return null;
179
+ return total;
180
+ }
181
+ /** An instant this many seconds after the supplied clock, or null. */
182
+ export function instantAfter(now, seconds) {
183
+ const current = Date.parse(now);
184
+ if (!Number.isFinite(current))
185
+ return null;
186
+ const instant = new Date(current + seconds * 1_000);
187
+ return Number.isFinite(instant.getTime()) ? instant.toISOString() : null;
188
+ }
23
189
  export function shortExpiry(now) {
24
190
  const parsed = Date.parse(now);
25
191
  return Number.isFinite(parsed) ? new Date(parsed + 60_000).toISOString() : null;
@@ -1 +1 @@
1
- {"version":3,"file":"shared.js","sourceRoot":"","sources":["../src/shared.ts"],"names":[],"mappings":"AAUA,MAAM,UAAU,MAAM,CAAC,KAAc;IACnC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QACzE,CAAC,CAAC,KAAgC;QAClC,CAAC,CAAC,IAAI,CAAC;AACX,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAAc,EAAE,OAAO,GAAG,GAAG;IACzD,OAAO,OAAO,KAAK,KAAK,QAAQ;QAC9B,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QACtB,KAAK,IAAI,CAAC;QACV,KAAK,IAAI,OAAO;QAChB,CAAC,CAAC,KAAK;QACP,CAAC,CAAC,IAAI,CAAC;AACX,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAAc,EAAE,GAAW;IACvD,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC3C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACjC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAChC,OAAO,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,MAAM,GAAG,OAAO;QAC5E,CAAC,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE;QAChC,CAAC,CAAC,IAAI,CAAC;AACX,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,GAAW;IACrC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC/B,OAAO,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AAClF,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,KAmBxB;IACC,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,EAAE,GAAG,KAAK,CAAC;IACnC,OAAO,EAAE,GAAG,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;AACjF,CAAC"}
1
+ {"version":3,"file":"shared.js","sourceRoot":"","sources":["../src/shared.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openlimiter/connectors",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Provider connectors that collect quota and usage data for OpenLimiter.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -19,7 +19,7 @@
19
19
  "NOTICE"
20
20
  ],
21
21
  "dependencies": {
22
- "@openlimiter/core": "0.2.0"
22
+ "@openlimiter/core": "0.4.0"
23
23
  },
24
24
  "repository": {
25
25
  "type": "git",