@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.
@@ -1,4 +1,19 @@
1
1
  import type { ConnectorContract, RawMeter } from "@openlimiter/core";
2
+ /**
3
+ * The Antigravity quota reader.
4
+ *
5
+ * The shape below is the one a working reader observed against a real Google AI
6
+ * Pro account on 2026-08-07, recorded in
7
+ * `Product Idea/reference-implementation`. Design evidence, not verification
8
+ * evidence: Google publishes nothing about this surface, so the provider stays
9
+ * UNVERIFIED whatever we observe.
10
+ *
11
+ * The response is not a percentage in a field. It is a list of GROUPS, each
12
+ * holding a list of BUCKETS, each bucket carrying `remainingFraction` between
13
+ * zero and one, a window name, and an RFC3339 reset. Usage is one minus that
14
+ * fraction. The reader used to parse `quota.used_percent`, a shape from an
15
+ * early prototype that has never been seen on the wire.
16
+ */
2
17
  export declare const antigravityLabels: {
3
18
  readonly credentialOrigin: "official-local-tool";
4
19
  readonly dataInterfaceStatus: "internal-endpoint";
@@ -10,6 +25,39 @@ export declare const antigravityInput: {
10
25
  readonly pathTemplate: "{providerState}/quota.json";
11
26
  readonly readMode: "read_only";
12
27
  };
28
+ /** What this reader reads. JSON, because the endpoint answers JSON. */
29
+ export declare const antigravityEncoding: "json";
30
+ /**
31
+ * The bucket prefix naming the pool this provider tracks.
32
+ *
33
+ * Matched against the prefix of `bucketId` EXACTLY, never as a substring. A
34
+ * hypothetical `3p-gemini-fallback` bucket would satisfy a naive containment
35
+ * test and let a third party pool overwrite the real one, which is a wrong
36
+ * number rather than a missing one.
37
+ */
38
+ export declare const ANTIGRAVITY_TRACKED_POOL = "gemini";
39
+ /**
40
+ * The window names this build understands, and how long each one is.
41
+ *
42
+ * Google spells them "5h" and "weekly". A window name outside this table is not
43
+ * dropped: the whole payload is refused. Dropping it would remove a window that
44
+ * might be the binding one and leave the meter confidently reporting a LOWER
45
+ * number than the truth, which is the exact failure this product exists to
46
+ * prevent.
47
+ */
48
+ export declare const ANTIGRAVITY_WINDOWS: Readonly<Record<string, number>>;
49
+ /**
50
+ * The tracked pool's binding window, as one meter.
51
+ *
52
+ * One meter rather than one per window, matching what the reference reader's
53
+ * bar shows and what the registry declares: the BINDING window, meaning the one
54
+ * with the highest usage, because that is the one that will stop the work.
55
+ *
56
+ * The third party pool that rides the same subscription is deliberately not
57
+ * modelled. It is a real second wallet, and reporting it under this provider's
58
+ * single meter would conflate two pools into one number. It needs its own
59
+ * identity in the registry before it can have one here.
60
+ */
13
61
  export declare function parseAntigravityPayload(payload: unknown, now: string): RawMeter[] | null;
14
62
  export declare const antigravityConnector: ConnectorContract;
15
63
  //# sourceMappingURL=antigravity.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"antigravity.d.ts","sourceRoot":"","sources":["../src/antigravity.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,iBAAiB,EAGjB,QAAQ,EACT,MAAM,mBAAmB,CAAC;AAG3B,eAAO,MAAM,iBAAiB;;;;;CAKM,CAAC;AAErC,eAAO,MAAM,gBAAgB;;;;CAInB,CAAC;AAEX,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,QAAQ,EAAE,GAAG,IAAI,CAmBxF;AAED,eAAO,MAAM,oBAAoB,EAAE,iBAalC,CAAC"}
1
+ {"version":3,"file":"antigravity.d.ts","sourceRoot":"","sources":["../src/antigravity.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,iBAAiB,EAGjB,QAAQ,EACT,MAAM,mBAAmB,CAAC;AAU3B;;;;;;;;;;;;;;GAcG;AAEH,eAAO,MAAM,iBAAiB;;;;;CAKM,CAAC;AAErC,eAAO,MAAM,gBAAgB;;;;CAInB,CAAC;AAEX,uEAAuE;AACvE,eAAO,MAAM,mBAAmB,EAAG,MAAe,CAAC;AAEnD;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,WAAW,CAAC;AAEjD;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAGhE,CAAC;AAuDF;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,MAAM,GACV,QAAQ,EAAE,GAAG,IAAI,CAsCnB;AAED,eAAO,MAAM,oBAAoB,EAAE,iBAclC,CAAC"}
@@ -1,4 +1,19 @@
1
- import { boundedNumber, futureInstant, rawMeter, record, shortExpiry } from "./shared.js";
1
+ import { boundedFraction, futureInstantFromRfc3339, plausibleResetHorizon, rawMeter, record, shortExpiry } from "./shared.js";
2
+ /**
3
+ * The Antigravity quota reader.
4
+ *
5
+ * The shape below is the one a working reader observed against a real Google AI
6
+ * Pro account on 2026-08-07, recorded in
7
+ * `Product Idea/reference-implementation`. Design evidence, not verification
8
+ * evidence: Google publishes nothing about this surface, so the provider stays
9
+ * UNVERIFIED whatever we observe.
10
+ *
11
+ * The response is not a percentage in a field. It is a list of GROUPS, each
12
+ * holding a list of BUCKETS, each bucket carrying `remainingFraction` between
13
+ * zero and one, a window name, and an RFC3339 reset. Usage is one minus that
14
+ * fraction. The reader used to parse `quota.used_percent`, a shape from an
15
+ * early prototype that has never been seen on the wire.
16
+ */
2
17
  export const antigravityLabels = {
3
18
  credentialOrigin: "official-local-tool",
4
19
  dataInterfaceStatus: "internal-endpoint",
@@ -10,20 +25,133 @@ export const antigravityInput = {
10
25
  pathTemplate: "{providerState}/quota.json",
11
26
  readMode: "read_only"
12
27
  };
28
+ /** What this reader reads. JSON, because the endpoint answers JSON. */
29
+ export const antigravityEncoding = "json";
30
+ /**
31
+ * The bucket prefix naming the pool this provider tracks.
32
+ *
33
+ * Matched against the prefix of `bucketId` EXACTLY, never as a substring. A
34
+ * hypothetical `3p-gemini-fallback` bucket would satisfy a naive containment
35
+ * test and let a third party pool overwrite the real one, which is a wrong
36
+ * number rather than a missing one.
37
+ */
38
+ export const ANTIGRAVITY_TRACKED_POOL = "gemini";
39
+ /**
40
+ * The window names this build understands, and how long each one is.
41
+ *
42
+ * Google spells them "5h" and "weekly". A window name outside this table is not
43
+ * dropped: the whole payload is refused. Dropping it would remove a window that
44
+ * might be the binding one and leave the meter confidently reporting a LOWER
45
+ * number than the truth, which is the exact failure this product exists to
46
+ * prevent.
47
+ */
48
+ export const ANTIGRAVITY_WINDOWS = {
49
+ "5h": 18_000,
50
+ weekly: 604_800
51
+ };
52
+ /**
53
+ * Every bucket of one group, or null if any one of them cannot be believed.
54
+ *
55
+ * Whole rather than per bucket, for the reason above: a partially read group is
56
+ * a group whose worst window may be the one that failed to read.
57
+ */
58
+ function readBuckets(buckets, now) {
59
+ const windows = [];
60
+ for (const entry of buckets) {
61
+ const bucket = record(entry);
62
+ if (bucket === null)
63
+ return null;
64
+ const fraction = boundedFraction(bucket["remainingFraction"]);
65
+ if (fraction === null)
66
+ return null;
67
+ const name = bucket["window"];
68
+ if (typeof name !== "string")
69
+ return null;
70
+ const seconds = ANTIGRAVITY_WINDOWS[name.toLowerCase()];
71
+ if (seconds === undefined)
72
+ return null;
73
+ const resetAt = futureInstantFromRfc3339(bucket["resetTime"], now, plausibleResetHorizon(seconds));
74
+ if (resetAt === null)
75
+ return null;
76
+ /* One decimal, which is the precision the reference reader keeps and more
77
+ than a quota bar can show. Clamped, because floating point subtraction
78
+ of a fraction can land a hair outside the range. */
79
+ const percent = Math.round(Math.max(0, Math.min(100, (1 - fraction) * 100)) * 10) / 10;
80
+ windows.push({ percent, seconds, resetAt });
81
+ }
82
+ return windows.length === 0 ? null : windows;
83
+ }
84
+ /** The prefix of every bucket id in a group, or null if any is unreadable. */
85
+ function poolPrefixes(buckets) {
86
+ const prefixes = new Set();
87
+ for (const entry of buckets) {
88
+ const bucket = record(entry);
89
+ if (bucket === null)
90
+ return null;
91
+ const id = bucket["bucketId"];
92
+ if (typeof id !== "string")
93
+ return null;
94
+ const separator = id.indexOf("-");
95
+ if (separator <= 0)
96
+ return null;
97
+ prefixes.add(id.slice(0, separator));
98
+ }
99
+ return prefixes;
100
+ }
101
+ /**
102
+ * The tracked pool's binding window, as one meter.
103
+ *
104
+ * One meter rather than one per window, matching what the reference reader's
105
+ * bar shows and what the registry declares: the BINDING window, meaning the one
106
+ * with the highest usage, because that is the one that will stop the work.
107
+ *
108
+ * The third party pool that rides the same subscription is deliberately not
109
+ * modelled. It is a real second wallet, and reporting it under this provider's
110
+ * single meter would conflate two pools into one number. It needs its own
111
+ * identity in the registry before it can have one here.
112
+ */
13
113
  export function parseAntigravityPayload(payload, now) {
14
114
  const root = record(payload);
15
- const quota = record(root?.["quota"]);
16
- const percent = boundedNumber(quota?.["used_percent"]);
17
- const resetAt = futureInstant(quota?.["reset_at"], now);
115
+ const groups = root?.["groups"];
116
+ if (!Array.isArray(groups))
117
+ return null;
118
+ let tracked = null;
119
+ for (const entry of groups) {
120
+ const group = record(entry);
121
+ if (group === null)
122
+ return null;
123
+ const buckets = group["buckets"];
124
+ if (!Array.isArray(buckets))
125
+ return null;
126
+ const prefixes = poolPrefixes(buckets);
127
+ if (prefixes === null)
128
+ return null;
129
+ if (!prefixes.has(ANTIGRAVITY_TRACKED_POOL))
130
+ continue;
131
+ /* Two groups claiming the same pool is ambiguous, and picking one would be
132
+ picking at random. */
133
+ if (tracked !== null)
134
+ return null;
135
+ tracked = readBuckets(buckets, now);
136
+ if (tracked === null)
137
+ return null;
138
+ }
139
+ if (tracked === null)
140
+ return null;
18
141
  const expiresAt = shortExpiry(now);
19
- if (percent === null || resetAt === null || expiresAt === null)
142
+ if (expiresAt === null)
20
143
  return null;
144
+ let binding = tracked[0];
145
+ for (const candidate of tracked.slice(1)) {
146
+ if (candidate.percent > binding.percent)
147
+ binding = candidate;
148
+ }
21
149
  return [rawMeter({
22
150
  provider: "ANTIGRAVITY",
23
151
  meter: "PRIMARY",
24
- value: percent,
25
- window: { kind: "fixed" },
26
- resetAt,
152
+ value: binding.percent,
153
+ window: { kind: "rolling", durationSeconds: binding.seconds },
154
+ resetAt: binding.resetAt,
27
155
  source: "internal_payload",
28
156
  precision: "estimated",
29
157
  observedAt: now,
@@ -34,6 +162,7 @@ export function parseAntigravityPayload(payload, now) {
34
162
  export const antigravityConnector = {
35
163
  id: "antigravity",
36
164
  displayName: "Antigravity",
165
+ encoding: "json",
37
166
  labels: antigravityLabels,
38
167
  detect(environment) {
39
168
  return environment["ANTIGRAVITY_USAGE_PAYLOAD"] === "1";
@@ -1 +1 @@
1
- {"version":3,"file":"antigravity.js","sourceRoot":"","sources":["../src/antigravity.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1F,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,gBAAgB,EAAE,qBAAqB;IACvC,mBAAmB,EAAE,mBAAmB;IACxC,cAAc,EAAE,MAAM;IACtB,YAAY,EAAE,YAAY;CACQ,CAAC;AAErC,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,IAAI,EAAE,0BAA0B;IAChC,YAAY,EAAE,4BAA4B;IAC1C,QAAQ,EAAE,WAAW;CACb,CAAC;AAEX,MAAM,UAAU,uBAAuB,CAAC,OAAgB,EAAE,GAAW;IACnE,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IAC7B,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IACtC,MAAM,OAAO,GAAG,aAAa,CAAC,KAAK,EAAE,CAAC,cAAc,CAAC,CAAC,CAAC;IACvD,MAAM,OAAO,GAAG,aAAa,CAAC,KAAK,EAAE,CAAC,UAAU,CAAC,EAAE,GAAG,CAAC,CAAC;IACxD,MAAM,SAAS,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;IACnC,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,KAAK,IAAI,IAAI,SAAS,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC5E,OAAO,CAAC,QAAQ,CAAC;YACf,QAAQ,EAAE,aAAa;YACvB,KAAK,EAAE,SAAS;YAChB,KAAK,EAAE,OAAO;YACd,MAAM,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE;YACzB,OAAO;YACP,MAAM,EAAE,kBAAkB;YAC1B,SAAS,EAAE,WAAW;YACtB,UAAU,EAAE,GAAG;YACf,SAAS;YACT,MAAM,EAAE,iBAAiB;SAC1B,CAAC,CAAC,CAAC;AACN,CAAC;AAED,MAAM,CAAC,MAAM,oBAAoB,GAAsB;IACrD,EAAE,EAAE,aAAa;IACjB,WAAW,EAAE,aAAa;IAC1B,MAAM,EAAE,iBAAiB;IACzB,MAAM,CAAC,WAAW;QAChB,OAAO,WAAW,CAAC,2BAA2B,CAAC,KAAK,GAAG,CAAC;IAC1D,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,OAAO;QAChB,MAAM,MAAM,GAAG,uBAAuB,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;QACrE,OAAO,MAAM,KAAK,IAAI;YACpB,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE;YAClC,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAC3B,CAAC;CACF,CAAC"}
1
+ {"version":3,"file":"antigravity.js","sourceRoot":"","sources":["../src/antigravity.ts"],"names":[],"mappings":"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"}
package/dist/claude.d.ts CHANGED
@@ -13,8 +13,35 @@ export declare const claudeInput: {
13
13
  /**
14
14
  * Parse the rate limit block of a Claude Code statusline payload.
15
15
  *
16
- * Shape: { "rate_limits": { "five_hour": { "utilization": 42,
17
- * "resets_at": "2026-09-01T05:00:00.000Z" }, "seven_day": { ... } } }
16
+ * Documented shape, from https://code.claude.com/docs/en/statusline
17
+ * reviewed 2026-08-10:
18
+ *
19
+ * ```json
20
+ * {
21
+ * "rate_limits": {
22
+ * "five_hour": { "used_percentage": 23.5, "resets_at": 1738425600 },
23
+ * "seven_day": { "used_percentage": 41.2, "resets_at": 1738857600 }
24
+ * }
25
+ * }
26
+ * ```
27
+ *
28
+ * `used_percentage` is the share of the window consumed, from 0 to 100.
29
+ * `resets_at` is Unix epoch SECONDS, a number, not a date string.
30
+ *
31
+ * Three absences the documentation states, and none of them is an error:
32
+ *
33
+ * 1. `rate_limits` appears only for Claude.ai subscribers on Pro or Max, and
34
+ * only after the first API response in the session. A payload without it is
35
+ * an ordinary payload from a free account or a session that has not called
36
+ * the API yet.
37
+ * 2. Each window may be independently absent, so one present window is one
38
+ * meter and is a complete answer.
39
+ * 3. No window present is the honest unknown, not zero.
40
+ *
41
+ * An earlier version of this parser read a field named `utilization` and an ISO
42
+ * date string. No Claude Code release is known to have emitted either, and the
43
+ * only thing that ever agreed with them was our own fixture, so nothing here
44
+ * falls back to that shape.
18
45
  *
19
46
  * A window that fails validation is dropped and the other window still counts,
20
47
  * because one expired window is not a reason to forget the whole session.
@@ -1 +1 @@
1
- {"version":3,"file":"claude.d.ts","sourceRoot":"","sources":["../src/claude.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,iBAAiB,EAGjB,QAAQ,EACT,MAAM,mBAAmB,CAAC;AAG3B,eAAO,MAAM,YAAY;;;;;CAKW,CAAC;AAErC,eAAO,MAAM,WAAW;;;;CAId,CAAC;AAiCX;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,QAAQ,EAAE,GAAG,IAAI,CAgBnF;AAED,eAAO,MAAM,eAAe,EAAE,iBAa7B,CAAC"}
1
+ {"version":3,"file":"claude.d.ts","sourceRoot":"","sources":["../src/claude.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,iBAAiB,EAGjB,QAAQ,EACT,MAAM,mBAAmB,CAAC;AAU3B,eAAO,MAAM,YAAY;;;;;CAKW,CAAC;AAErC,eAAO,MAAM,WAAW;;;;CAId,CAAC;AA0CX;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,QAAQ,EAAE,GAAG,IAAI,CAgBnF;AAED,eAAO,MAAM,eAAe,EAAE,iBAc7B,CAAC"}
package/dist/claude.js CHANGED
@@ -1,4 +1,4 @@
1
- import { boundedNumber, futureInstant, rawMeter, record, shortExpiry } from "./shared.js";
1
+ import { boundedNumber, futureInstantFromEpochSeconds, plausibleResetHorizon, rawMeter, record, shortExpiry } from "./shared.js";
2
2
  export const claudeLabels = {
3
3
  credentialOrigin: "official-local-tool",
4
4
  dataInterfaceStatus: "native-statusline-payload",
@@ -14,8 +14,13 @@ function parseWindow(value, now, meter, durationSeconds) {
14
14
  const input = record(value);
15
15
  if (input === null)
16
16
  return null;
17
- const percent = boundedNumber(input["utilization"]);
18
- const resetAt = futureInstant(input["resets_at"], now);
17
+ const percent = boundedNumber(input["used_percentage"]);
18
+ /*
19
+ * The reset has to belong to the window that named it. A five hour window
20
+ * resetting years from now is a corrupt field, not a long wait, and it is
21
+ * dropped on its own so the other window still reports.
22
+ */
23
+ const resetAt = futureInstantFromEpochSeconds(input["resets_at"], now, plausibleResetHorizon(durationSeconds));
19
24
  const expiresAt = shortExpiry(now);
20
25
  if (percent === null || resetAt === null || expiresAt === null)
21
26
  return null;
@@ -39,8 +44,35 @@ const windows = [
39
44
  /**
40
45
  * Parse the rate limit block of a Claude Code statusline payload.
41
46
  *
42
- * Shape: { "rate_limits": { "five_hour": { "utilization": 42,
43
- * "resets_at": "2026-09-01T05:00:00.000Z" }, "seven_day": { ... } } }
47
+ * Documented shape, from https://code.claude.com/docs/en/statusline
48
+ * reviewed 2026-08-10:
49
+ *
50
+ * ```json
51
+ * {
52
+ * "rate_limits": {
53
+ * "five_hour": { "used_percentage": 23.5, "resets_at": 1738425600 },
54
+ * "seven_day": { "used_percentage": 41.2, "resets_at": 1738857600 }
55
+ * }
56
+ * }
57
+ * ```
58
+ *
59
+ * `used_percentage` is the share of the window consumed, from 0 to 100.
60
+ * `resets_at` is Unix epoch SECONDS, a number, not a date string.
61
+ *
62
+ * Three absences the documentation states, and none of them is an error:
63
+ *
64
+ * 1. `rate_limits` appears only for Claude.ai subscribers on Pro or Max, and
65
+ * only after the first API response in the session. A payload without it is
66
+ * an ordinary payload from a free account or a session that has not called
67
+ * the API yet.
68
+ * 2. Each window may be independently absent, so one present window is one
69
+ * meter and is a complete answer.
70
+ * 3. No window present is the honest unknown, not zero.
71
+ *
72
+ * An earlier version of this parser read a field named `utilization` and an ISO
73
+ * date string. No Claude Code release is known to have emitted either, and the
74
+ * only thing that ever agreed with them was our own fixture, so nothing here
75
+ * falls back to that shape.
44
76
  *
45
77
  * A window that fails validation is dropped and the other window still counts,
46
78
  * because one expired window is not a reason to forget the whole session.
@@ -62,6 +94,7 @@ export function parseClaudePayload(payload, now) {
62
94
  }
63
95
  export const claudeConnector = {
64
96
  id: "claude",
97
+ encoding: "json",
65
98
  displayName: "Claude",
66
99
  labels: claudeLabels,
67
100
  detect(environment) {
@@ -1 +1 @@
1
- {"version":3,"file":"claude.js","sourceRoot":"","sources":["../src/claude.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"claude.js","sourceRoot":"","sources":["../src/claude.ts"],"names":[],"mappings":"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"}
package/dist/codex.d.ts CHANGED
@@ -1,4 +1,22 @@
1
1
  import type { ConnectorContract, RawMeter } from "@openlimiter/core";
2
+ /**
3
+ * The Codex usage reader.
4
+ *
5
+ * The shape below is the one a working reader observed against a real account
6
+ * on 2026-08-07, recorded in `Product Idea/reference-implementation`. OpenAI
7
+ * publishes nothing about it, so it is DESIGN evidence and not verification
8
+ * evidence: it tells us what to parse, and it does not turn an internal
9
+ * endpoint into an official API. The labels below stay as they are, and the
10
+ * provider stays UNVERIFIED, until a sanitized capture lands in the fixture
11
+ * slot and even then the interface is still internal.
12
+ *
13
+ * The reader used to parse `rate_limits` with an s, a shape carried over from
14
+ * an early prototype and never seen on the wire. The real field is
15
+ * `rate_limit`, singular. That one letter is the whole reason the drift
16
+ * machinery exists: both shapes are well formed JSON, both would have arrived
17
+ * with a 200, and the wrong one produces no reading at all rather than a wrong
18
+ * one only because this parser refuses whatever it was not told to expect.
19
+ */
2
20
  export declare const codexLabels: {
3
21
  readonly credentialOrigin: "official-local-tool";
4
22
  readonly dataInterfaceStatus: "internal-endpoint";
@@ -10,6 +28,27 @@ export declare const codexInput: {
10
28
  readonly pathTemplate: "{providerState}/usage.json";
11
29
  readonly readMode: "read_only";
12
30
  };
31
+ /** What this reader reads. JSON, because the endpoint answers JSON. */
32
+ export declare const codexEncoding: "json";
33
+ /**
34
+ * The observed response, reduced to what may be believed.
35
+ *
36
+ * `used_percent` is a percentage and `reset_at` is Unix epoch SECONDS. The
37
+ * encoding was not guessed: the reference reader passes `reset_at` through
38
+ * untouched into a cache whose consumers format it with an epoch formatter,
39
+ * while it explicitly converts the other two providers' RFC3339 stamps first.
40
+ *
41
+ * `limit_window_seconds` is the window's own length, stated by the provider. It
42
+ * is optional, because the reference treats it as optional, and its absence
43
+ * costs only the plausibility bound: a window whose length is unknown cannot be
44
+ * called rolling, so it is reported as an unknown window rather than as a five
45
+ * hour one this build made up.
46
+ *
47
+ * The secondary window is deliberately NOT modelled. Plans differ in whether
48
+ * they have one, the registry declares a single meter, and folding a second
49
+ * window into the same meter would silently report whichever one this build
50
+ * happened to read last.
51
+ */
13
52
  export declare function parseCodexPayload(payload: unknown, now: string): RawMeter[] | null;
14
53
  export declare const codexConnector: ConnectorContract;
15
54
  //# sourceMappingURL=codex.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"codex.d.ts","sourceRoot":"","sources":["../src/codex.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,iBAAiB,EAGjB,QAAQ,EACT,MAAM,mBAAmB,CAAC;AAG3B,eAAO,MAAM,WAAW;;;;;CAKY,CAAC;AAErC,eAAO,MAAM,UAAU;;;;CAIb,CAAC;AAEX,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,QAAQ,EAAE,GAAG,IAAI,CAoBlF;AAED,eAAO,MAAM,cAAc,EAAE,iBAa5B,CAAC"}
1
+ {"version":3,"file":"codex.d.ts","sourceRoot":"","sources":["../src/codex.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,iBAAiB,EAGjB,QAAQ,EACT,MAAM,mBAAmB,CAAC;AAW3B;;;;;;;;;;;;;;;;;GAiBG;AAEH,eAAO,MAAM,WAAW;;;;;CAKY,CAAC;AAErC,eAAO,MAAM,UAAU;;;;CAIb,CAAC;AAEX,uEAAuE;AACvE,eAAO,MAAM,aAAa,EAAG,MAAe,CAAC;AAE7C;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,QAAQ,EAAE,GAAG,IAAI,CA4BlF;AAED,eAAO,MAAM,cAAc,EAAE,iBAc5B,CAAC"}
package/dist/codex.js CHANGED
@@ -1,4 +1,22 @@
1
- import { boundedNumber, futureInstant, rawMeter, record, shortExpiry } from "./shared.js";
1
+ import { boundedNumber, futureInstantFromEpochSeconds, plausibleResetHorizon, rawMeter, record, shortExpiry, windowSeconds } from "./shared.js";
2
+ /**
3
+ * The Codex usage reader.
4
+ *
5
+ * The shape below is the one a working reader observed against a real account
6
+ * on 2026-08-07, recorded in `Product Idea/reference-implementation`. OpenAI
7
+ * publishes nothing about it, so it is DESIGN evidence and not verification
8
+ * evidence: it tells us what to parse, and it does not turn an internal
9
+ * endpoint into an official API. The labels below stay as they are, and the
10
+ * provider stays UNVERIFIED, until a sanitized capture lands in the fixture
11
+ * slot and even then the interface is still internal.
12
+ *
13
+ * The reader used to parse `rate_limits` with an s, a shape carried over from
14
+ * an early prototype and never seen on the wire. The real field is
15
+ * `rate_limit`, singular. That one letter is the whole reason the drift
16
+ * machinery exists: both shapes are well formed JSON, both would have arrived
17
+ * with a 200, and the wrong one produces no reading at all rather than a wrong
18
+ * one only because this parser refuses whatever it was not told to expect.
19
+ */
2
20
  export const codexLabels = {
3
21
  credentialOrigin: "official-local-tool",
4
22
  dataInterfaceStatus: "internal-endpoint",
@@ -10,20 +28,46 @@ export const codexInput = {
10
28
  pathTemplate: "{providerState}/usage.json",
11
29
  readMode: "read_only"
12
30
  };
31
+ /** What this reader reads. JSON, because the endpoint answers JSON. */
32
+ export const codexEncoding = "json";
33
+ /**
34
+ * The observed response, reduced to what may be believed.
35
+ *
36
+ * `used_percent` is a percentage and `reset_at` is Unix epoch SECONDS. The
37
+ * encoding was not guessed: the reference reader passes `reset_at` through
38
+ * untouched into a cache whose consumers format it with an epoch formatter,
39
+ * while it explicitly converts the other two providers' RFC3339 stamps first.
40
+ *
41
+ * `limit_window_seconds` is the window's own length, stated by the provider. It
42
+ * is optional, because the reference treats it as optional, and its absence
43
+ * costs only the plausibility bound: a window whose length is unknown cannot be
44
+ * called rolling, so it is reported as an unknown window rather than as a five
45
+ * hour one this build made up.
46
+ *
47
+ * The secondary window is deliberately NOT modelled. Plans differ in whether
48
+ * they have one, the registry declares a single meter, and folding a second
49
+ * window into the same meter would silently report whichever one this build
50
+ * happened to read last.
51
+ */
13
52
  export function parseCodexPayload(payload, now) {
14
53
  const root = record(payload);
15
- const limits = record(root?.["rate_limits"]);
54
+ const limits = record(root?.["rate_limit"]);
16
55
  const primary = record(limits?.["primary_window"]);
17
56
  const percent = boundedNumber(primary?.["used_percent"]);
18
- const resetAt = futureInstant(primary?.["reset_at"], now);
57
+ if (percent === null)
58
+ return null;
59
+ const length = windowSeconds(primary?.["limit_window_seconds"]);
60
+ const resetAt = futureInstantFromEpochSeconds(primary?.["reset_at"], now, length === null ? undefined : plausibleResetHorizon(length));
19
61
  const expiresAt = shortExpiry(now);
20
- if (percent === null || resetAt === null || expiresAt === null)
62
+ if (resetAt === null || expiresAt === null)
21
63
  return null;
22
64
  return [rawMeter({
23
65
  provider: "CODEX",
24
66
  meter: "PRIMARY",
25
67
  value: percent,
26
- window: { kind: "rolling", durationSeconds: 18_000 },
68
+ window: length === null
69
+ ? { kind: "unknown" }
70
+ : { kind: "rolling", durationSeconds: length },
27
71
  resetAt,
28
72
  source: "internal_payload",
29
73
  precision: "estimated",
@@ -35,6 +79,7 @@ export function parseCodexPayload(payload, now) {
35
79
  export const codexConnector = {
36
80
  id: "codex",
37
81
  displayName: "Codex",
82
+ encoding: "json",
38
83
  labels: codexLabels,
39
84
  detect(environment) {
40
85
  return environment["CODEX_USAGE_PAYLOAD"] === "1";
package/dist/codex.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"codex.js","sourceRoot":"","sources":["../src/codex.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1F,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,gBAAgB,EAAE,qBAAqB;IACvC,mBAAmB,EAAE,mBAAmB;IACxC,cAAc,EAAE,MAAM;IACtB,YAAY,EAAE,YAAY;CACQ,CAAC;AAErC,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,IAAI,EAAE,0BAA0B;IAChC,YAAY,EAAE,4BAA4B;IAC1C,QAAQ,EAAE,WAAW;CACb,CAAC;AAEX,MAAM,UAAU,iBAAiB,CAAC,OAAgB,EAAE,GAAW;IAC7D,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IAC7B,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC;IAC7C,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,gBAAgB,CAAC,CAAC,CAAC;IACnD,MAAM,OAAO,GAAG,aAAa,CAAC,OAAO,EAAE,CAAC,cAAc,CAAC,CAAC,CAAC;IACzD,MAAM,OAAO,GAAG,aAAa,CAAC,OAAO,EAAE,CAAC,UAAU,CAAC,EAAE,GAAG,CAAC,CAAC;IAC1D,MAAM,SAAS,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;IACnC,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,KAAK,IAAI,IAAI,SAAS,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC5E,OAAO,CAAC,QAAQ,CAAC;YACf,QAAQ,EAAE,OAAO;YACjB,KAAK,EAAE,SAAS;YAChB,KAAK,EAAE,OAAO;YACd,MAAM,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,EAAE;YACpD,OAAO;YACP,MAAM,EAAE,kBAAkB;YAC1B,SAAS,EAAE,WAAW;YACtB,UAAU,EAAE,GAAG;YACf,SAAS;YACT,MAAM,EAAE,WAAW;SACpB,CAAC,CAAC,CAAC;AACN,CAAC;AAED,MAAM,CAAC,MAAM,cAAc,GAAsB;IAC/C,EAAE,EAAE,OAAO;IACX,WAAW,EAAE,OAAO;IACpB,MAAM,EAAE,WAAW;IACnB,MAAM,CAAC,WAAW;QAChB,OAAO,WAAW,CAAC,qBAAqB,CAAC,KAAK,GAAG,CAAC;IACpD,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,OAAO;QAChB,MAAM,MAAM,GAAG,iBAAiB,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;QAC/D,OAAO,MAAM,KAAK,IAAI;YACpB,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE;YAClC,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAC3B,CAAC;CACF,CAAC"}
1
+ {"version":3,"file":"codex.js","sourceRoot":"","sources":["../src/codex.ts"],"names":[],"mappings":"AAOA,OAAO,EACL,aAAa,EACb,6BAA6B,EAC7B,qBAAqB,EACrB,QAAQ,EACR,MAAM,EACN,WAAW,EACX,aAAa,EACd,MAAM,aAAa,CAAC;AAErB;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,gBAAgB,EAAE,qBAAqB;IACvC,mBAAmB,EAAE,mBAAmB;IACxC,cAAc,EAAE,MAAM;IACtB,YAAY,EAAE,YAAY;CACQ,CAAC;AAErC,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,IAAI,EAAE,0BAA0B;IAChC,YAAY,EAAE,4BAA4B;IAC1C,QAAQ,EAAE,WAAW;CACb,CAAC;AAEX,uEAAuE;AACvE,MAAM,CAAC,MAAM,aAAa,GAAG,MAAe,CAAC;AAE7C;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAgB,EAAE,GAAW;IAC7D,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IAC7B,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC;IAC5C,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,gBAAgB,CAAC,CAAC,CAAC;IACnD,MAAM,OAAO,GAAG,aAAa,CAAC,OAAO,EAAE,CAAC,cAAc,CAAC,CAAC,CAAC;IACzD,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAClC,MAAM,MAAM,GAAG,aAAa,CAAC,OAAO,EAAE,CAAC,sBAAsB,CAAC,CAAC,CAAC;IAChE,MAAM,OAAO,GAAG,6BAA6B,CAC3C,OAAO,EAAE,CAAC,UAAU,CAAC,EACrB,GAAG,EACH,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,qBAAqB,CAAC,MAAM,CAAC,CAC5D,CAAC;IACF,MAAM,SAAS,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;IACnC,IAAI,OAAO,KAAK,IAAI,IAAI,SAAS,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACxD,OAAO,CAAC,QAAQ,CAAC;YACf,QAAQ,EAAE,OAAO;YACjB,KAAK,EAAE,SAAS;YAChB,KAAK,EAAE,OAAO;YACd,MAAM,EAAE,MAAM,KAAK,IAAI;gBACrB,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE;gBACrB,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,EAAE;YAChD,OAAO;YACP,MAAM,EAAE,kBAAkB;YAC1B,SAAS,EAAE,WAAW;YACtB,UAAU,EAAE,GAAG;YACf,SAAS;YACT,MAAM,EAAE,WAAW;SACpB,CAAC,CAAC,CAAC;AACN,CAAC;AAED,MAAM,CAAC,MAAM,cAAc,GAAsB;IAC/C,EAAE,EAAE,OAAO;IACX,WAAW,EAAE,OAAO;IACpB,QAAQ,EAAE,MAAM;IAChB,MAAM,EAAE,WAAW;IACnB,MAAM,CAAC,WAAW;QAChB,OAAO,WAAW,CAAC,qBAAqB,CAAC,KAAK,GAAG,CAAC;IACpD,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,OAAO;QAChB,MAAM,MAAM,GAAG,iBAAiB,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;QAC/D,OAAO,MAAM,KAAK,IAAI;YACpB,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE;YAClC,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAC3B,CAAC;CACF,CAAC"}