@openlimiter/core 0.1.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.
@@ -0,0 +1,218 @@
1
+ import { mergeSnapshots, snapshotIdentity, MAX_CACHE_ENTRIES } from "./merge.js";
2
+ import { PROVIDER_CODES, ACCOUNT_ID_PATTERN } from "./types.js";
3
+ /**
4
+ * What one collection run achieved, and what the cache does about it.
5
+ *
6
+ * The product's whole honesty problem in one file. A provider answering with a
7
+ * 200 is not a reading, and a provider answering with a well formed body whose
8
+ * meaning has changed is worse than a provider answering with nothing: the
9
+ * first is visibly unknown, the second is confidently wrong. So a run reports
10
+ * what it achieved rather than what it received, and drift is a first class
11
+ * outcome with a first class consequence.
12
+ *
13
+ * That consequence is a SUPPRESSION. Removing the affected rows is not enough
14
+ * on its own, because a cache is shared: the desktop, the command line tool,
15
+ * the adapters and the dashboard all read it, and any one of them could write a
16
+ * stale row back before the others noticed. A suppression is a standing
17
+ * instruction in the document itself, so every reader on the machine reaches
18
+ * the same unknown at the same instant, and a row that predates it can never
19
+ * become visible again.
20
+ *
21
+ * Everything here is pure: no clock, no file system, no network. The caller
22
+ * supplies the instant, so a whole history can be replayed and land on the same
23
+ * answer every time.
24
+ */
25
+ /**
26
+ * Why a collection run produced nothing usable.
27
+ *
28
+ * `drift` is separated from every other reason on purpose, and is the only one
29
+ * that suppresses. The rest are ordinary interruptions: the provider was busy,
30
+ * the machine was offline, a credential lapsed. None of those say anything
31
+ * about whether the reading we already hold is still true, so none of them
32
+ * throws it away; the reading ages out through the ordinary freshness rule
33
+ * instead. Drift is the one that says the previous reading's MEANING is in
34
+ * doubt, and that cannot be waited out.
35
+ */
36
+ export const COLLECTION_FAILURE_REASONS = [
37
+ "drift",
38
+ "network",
39
+ "authentication",
40
+ "rate_limited",
41
+ "remote_error",
42
+ "local_io"
43
+ ];
44
+ /**
45
+ * How many suppressions one cache document may carry.
46
+ *
47
+ * The same order of magnitude as the row bound, because there is at most one
48
+ * suppression per identity and identities are what rows are keyed by. A
49
+ * document claiming more than this is not a cache with a lot of drift, it is a
50
+ * document somebody wrote by hand.
51
+ */
52
+ export const MAX_CACHE_SUPPRESSIONS = MAX_CACHE_ENTRIES;
53
+ /**
54
+ * The one place provider and account identity is decided.
55
+ *
56
+ * A row, a report and a suppression all have to agree on what "the same thing"
57
+ * means, and the way that goes wrong is three near identical comparisons in
58
+ * three files, one of which forgets that an absent account is not the account
59
+ * called "default". So there is one function, everything calls it, and an
60
+ * absent account keys exactly as it always did.
61
+ */
62
+ export function collectionIdentity(provider, accountId) {
63
+ return accountId === undefined ? provider : provider + " " + accountId;
64
+ }
65
+ /** Whether this snapshot belongs to this provider and account. */
66
+ export function snapshotBelongsTo(snapshot, provider, accountId) {
67
+ return (snapshot.provider === provider &&
68
+ collectionIdentity(provider, snapshot.accountId) ===
69
+ collectionIdentity(provider, accountId));
70
+ }
71
+ function parseInstant(value) {
72
+ const parsed = Date.parse(value);
73
+ return Number.isFinite(parsed) ? parsed : null;
74
+ }
75
+ function isRecord(value) {
76
+ return typeof value === "object" && value !== null && !Array.isArray(value);
77
+ }
78
+ function isProviderCode(value) {
79
+ return (typeof value === "string" &&
80
+ PROVIDER_CODES.includes(value));
81
+ }
82
+ /**
83
+ * Read the suppression list out of a cache document.
84
+ *
85
+ * Absent is the ordinary case and reads as an empty list: a document written
86
+ * before suppressions existed has none, which is true. Anything present and
87
+ * unreadable is refused whole rather than partially salvaged, because a
88
+ * half read suppression list would silently un suppress whichever entries
89
+ * failed to parse, which is exactly the value it exists to prevent.
90
+ */
91
+ export function readSuppressions(value) {
92
+ if (value === undefined || value === null)
93
+ return { ok: true, suppressions: [] };
94
+ if (!Array.isArray(value))
95
+ return { ok: false };
96
+ if (value.length > MAX_CACHE_SUPPRESSIONS)
97
+ return { ok: false };
98
+ const suppressions = [];
99
+ for (const entry of value) {
100
+ if (!isRecord(entry))
101
+ return { ok: false };
102
+ if (!isProviderCode(entry["provider"]))
103
+ return { ok: false };
104
+ if (entry["reason"] !== "drift")
105
+ return { ok: false };
106
+ const suppressedAt = entry["suppressedAt"];
107
+ if (typeof suppressedAt !== "string" || parseInstant(suppressedAt) === null) {
108
+ return { ok: false };
109
+ }
110
+ const accountId = entry["accountId"];
111
+ if (accountId !== undefined) {
112
+ if (typeof accountId !== "string" || !ACCOUNT_ID_PATTERN.test(accountId)) {
113
+ return { ok: false };
114
+ }
115
+ }
116
+ suppressions.push({
117
+ provider: entry["provider"],
118
+ reason: "drift",
119
+ suppressedAt,
120
+ ...(accountId === undefined ? {} : { accountId })
121
+ });
122
+ }
123
+ return { ok: true, suppressions };
124
+ }
125
+ /**
126
+ * Fold one collection report into the cache.
127
+ *
128
+ * Four rules, one per kind of outcome, and no fifth:
129
+ *
130
+ * 1. A successful report replaces every row for its identity and clears that
131
+ * identity's suppression. A provider that has started making sense again
132
+ * is trusted again, and only a real parse can say that.
133
+ * 2. A drift report removes those rows and records the suppression, replacing
134
+ * any earlier one, so the instant always reflects the latest drift.
135
+ * 3. Every other failure changes nothing at all. The rows we hold stay, and
136
+ * age out through the ordinary freshness rule.
137
+ * 4. A report whose instant cannot be read is not applied. An unreadable
138
+ * instant cannot be compared with a row's observation, so it could neither
139
+ * suppress honestly nor be cleared honestly.
140
+ */
141
+ export function applyCollectionReport(state, report) {
142
+ if (parseInstant(report.observedAt) === null)
143
+ return state;
144
+ const belongs = (snapshot) => snapshotBelongsTo(snapshot, report.provider, report.accountId);
145
+ const suppressionMatches = (suppression) => suppression.provider === report.provider &&
146
+ collectionIdentity(report.provider, suppression.accountId) ===
147
+ collectionIdentity(report.provider, report.accountId);
148
+ if (report.ok) {
149
+ /* Rows for this identity are dropped before the merge rather than left for
150
+ it, because a successful run that returns FEWER meters than last time
151
+ must not leave the missing ones behind as fresh looking survivors. */
152
+ const kept = state.snapshots.filter((snapshot) => !belongs(snapshot));
153
+ return {
154
+ snapshots: mergeSnapshots(kept, report.snapshots),
155
+ suppressions: state.suppressions.filter((suppression) => !suppressionMatches(suppression))
156
+ };
157
+ }
158
+ if (report.reason !== "drift")
159
+ return state;
160
+ const suppression = {
161
+ provider: report.provider,
162
+ reason: "drift",
163
+ suppressedAt: report.observedAt,
164
+ ...(report.accountId === undefined ? {} : { accountId: report.accountId })
165
+ };
166
+ const others = state.suppressions.filter((existing) => !suppressionMatches(existing));
167
+ return {
168
+ snapshots: state.snapshots.filter((snapshot) => !belongs(snapshot)),
169
+ /* Bounded by dropping the oldest, so a machine that drifts on every
170
+ provider for a month cannot grow the document without limit. */
171
+ suppressions: boundSuppressions([...others, suppression])
172
+ };
173
+ }
174
+ function suppressedMilliseconds(suppression) {
175
+ return parseInstant(suppression.suppressedAt) ?? 0;
176
+ }
177
+ function boundSuppressions(suppressions) {
178
+ if (suppressions.length <= MAX_CACHE_SUPPRESSIONS)
179
+ return [...suppressions];
180
+ return [...suppressions]
181
+ .sort((left, right) => suppressedMilliseconds(right) - suppressedMilliseconds(left))
182
+ .slice(0, MAX_CACHE_SUPPRESSIONS);
183
+ }
184
+ /**
185
+ * The rows a surface may actually see.
186
+ *
187
+ * A row survives only when nothing suppresses its identity, or when it was
188
+ * observed strictly AFTER the suppression that does. Strictly after, not at or
189
+ * after: a row observed in the same millisecond as the drift that suppressed it
190
+ * is the very row the drift was about.
191
+ *
192
+ * Every read goes through here, which is what makes drift instant everywhere.
193
+ * `buildAdvice` is handed the result of this function and never the raw rows,
194
+ * so a suppressed provider is reported unknown by the statusline, the agent
195
+ * context, the dashboard and the tray in the same tick.
196
+ */
197
+ export function visibleSnapshots(state) {
198
+ if (state.suppressions.length === 0)
199
+ return [...state.snapshots];
200
+ return state.snapshots.filter((snapshot) => {
201
+ const observed = parseInstant(snapshot.observedAt);
202
+ for (const suppression of state.suppressions) {
203
+ if (!snapshotBelongsTo(snapshot, suppression.provider, suppression.accountId)) {
204
+ continue;
205
+ }
206
+ /* A row whose own observation cannot be read cannot be proven newer than
207
+ the suppression, so it is not shown. */
208
+ if (observed === null)
209
+ return false;
210
+ if (observed <= suppressedMilliseconds(suppression))
211
+ return false;
212
+ }
213
+ return true;
214
+ });
215
+ }
216
+ /** Identity, exported for the cache writer that has to key rows the same way. */
217
+ export { snapshotIdentity };
218
+ //# sourceMappingURL=collection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collection.js","sourceRoot":"","sources":["../src/collection.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,178 @@
1
+ /**
2
+ * The connection lifecycle, as one pure function and two human tables.
3
+ *
4
+ * A parser is not a connection. The product's central honesty problem is that
5
+ * an implemented parser has been able to look like a live account, so the state
6
+ * a connection is in has to be a value the whole product agrees on rather than
7
+ * a sentence each surface invents. Everything here is data and arithmetic: no
8
+ * clock, no network, no storage, so a surface can replay a connection's whole
9
+ * history and get the same answer every time.
10
+ */
11
+ export declare const CONNECTION_STATES: readonly ["NOT_CONFIGURED", "DETECTED", "NEEDS_AUTH", "READY_TO_ENABLE", "CONNECTING", "CONNECTED", "DEGRADED", "STALE", "AUTH_EXPIRED", "IMPORT_ONLY", "MANUAL", "UNSUPPORTED", "ERROR"];
12
+ export type ConnectionState = (typeof CONNECTION_STATES)[number];
13
+ /**
14
+ * How many consecutive network failures turn a degraded connection into a
15
+ * broken one. One timeout is a bad moment; three in a row is a fault worth
16
+ * showing a person.
17
+ */
18
+ export declare const NETWORK_FAILURE_ERROR_THRESHOLD = 3;
19
+ export type ConnectionEvent =
20
+ /** A supported local tool was found on this machine. */
21
+ {
22
+ kind: "detected";
23
+ }
24
+ /** Setup established that this connection cannot proceed without a secret. */
25
+ | {
26
+ kind: "credential_required";
27
+ }
28
+ /** A secret was accepted into the operating system credential store. */
29
+ | {
30
+ kind: "credential_stored";
31
+ }
32
+ /** The user asked for this connection to start collecting. */
33
+ | {
34
+ kind: "enable_requested";
35
+ }
36
+ /** A remote read finished. `parsed` says whether we understood the body. */
37
+ | {
38
+ kind: "http_response";
39
+ status: number;
40
+ parsed: boolean;
41
+ }
42
+ /** A local read finished, for readers that never speak HTTP. */
43
+ | {
44
+ kind: "local_read";
45
+ parsed: boolean;
46
+ }
47
+ /** A read never reached the provider. `consecutive` counts failures in a row. */
48
+ | {
49
+ kind: "network_failure";
50
+ consecutive: number;
51
+ }
52
+ /** The newest observation aged past its own expiry. */
53
+ | {
54
+ kind: "expiry_passed";
55
+ }
56
+ /** The user removed this connection. */
57
+ | {
58
+ kind: "disconnected";
59
+ }
60
+ /** The product states this source can only be fed by an explicit import. */
61
+ | {
62
+ kind: "declared_import_only";
63
+ }
64
+ /** The product states this source is a user maintained plan. */
65
+ | {
66
+ kind: "declared_manual";
67
+ }
68
+ /** The product states no safe automatic source exists for this product. */
69
+ | {
70
+ kind: "declared_unsupported";
71
+ };
72
+ /**
73
+ * What the transition function cannot work out from the current state alone.
74
+ *
75
+ * The one authentication question is whether this connection has ever worked,
76
+ * and the current state cannot answer it. CONNECTED then a 404 lands in ERROR,
77
+ * and a 401 arriving there would read as "we never had a credential" when what
78
+ * actually happened is that a credential which used to work stopped, which is a
79
+ * different sentence and a different button. So the fact is carried on the
80
+ * connection record, where it belongs, and passed in. It is one way: nothing
81
+ * ever sets it back to false, because a connection that worked once always did.
82
+ */
83
+ export interface ConnectionContext {
84
+ everConnected: boolean;
85
+ }
86
+ /**
87
+ * Advance a connection by one event.
88
+ *
89
+ * Total and pure: every state, event and context triple has an answer, and an
90
+ * event that makes no sense where it arrived leaves the state exactly as it was
91
+ * rather than inventing a transition. The context is required rather than
92
+ * defaulted, because a caller that forgets it would silently get the wrong
93
+ * authentication answer, and a compiler error is cheaper than that.
94
+ */
95
+ export declare function nextConnectionState(state: ConnectionState, event: ConnectionEvent, context: ConnectionContext): ConnectionState;
96
+ /** One sentence per state, written for a person rather than for a log. */
97
+ export declare const connectionSentence: {
98
+ readonly NOT_CONFIGURED: "Not connected.";
99
+ readonly DETECTED: "Found on this machine but not collecting yet.";
100
+ readonly NEEDS_AUTH: "Waiting for a credential.";
101
+ readonly READY_TO_ENABLE: "Ready to start collecting.";
102
+ readonly CONNECTING: "Connecting.";
103
+ readonly CONNECTED: "Connected and collecting.";
104
+ readonly DEGRADED: "The provider is refusing reads for now.";
105
+ readonly STALE: "The last reading is older than it should be.";
106
+ readonly AUTH_EXPIRED: "The stored credential stopped working.";
107
+ readonly IMPORT_ONLY: "Reads only what you import. Nothing is collected automatically.";
108
+ readonly MANUAL: "Shows the plan you entered yourself.";
109
+ readonly UNSUPPORTED: "No safe automatic source exists for this product yet.";
110
+ readonly ERROR: "This connection failed and needs a look.";
111
+ };
112
+ /** The one thing to do next, in the words of the button that does it. */
113
+ export declare const connectionNextAction: {
114
+ readonly NOT_CONFIGURED: "Connect";
115
+ readonly DETECTED: "Enable local integration";
116
+ readonly NEEDS_AUTH: "Add credential";
117
+ readonly READY_TO_ENABLE: "Enable";
118
+ readonly CONNECTING: "Wait";
119
+ readonly CONNECTED: "Refresh now";
120
+ readonly DEGRADED: "Wait for the next retry";
121
+ readonly STALE: "Refresh now";
122
+ readonly AUTH_EXPIRED: "Reconnect";
123
+ readonly IMPORT_ONLY: "Import a payload";
124
+ readonly MANUAL: "Edit plan";
125
+ readonly UNSUPPORTED: "Add manual plan";
126
+ readonly ERROR: "View diagnostics";
127
+ };
128
+ /** The five providers on the Connect and See catalogue surface. */
129
+ export declare const CATALOGUE_PROVIDER_IDS: readonly ["claude", "openrouter", "codex", "antigravity", "opencode"];
130
+ export type CatalogueProviderId = (typeof CATALOGUE_PROVIDER_IDS)[number];
131
+ export type CataloguePlatform = "windows" | "macos" | "linux";
132
+ export type CatalogueCapabilityMode = "automatic" | "manual" | "event_driven";
133
+ export type CatalogueCapabilityMaturity = "supported" | "experimental";
134
+ export type CatalogueCapabilityLabel = "Supported" | "Event driven" | "Experimental" | "Manual" | "Manual experimental";
135
+ export type CatalogueAuthMode = "existing_local_cli" | "api_key" | "manual";
136
+ export interface CataloguePlatformCapability {
137
+ mode: CatalogueCapabilityMode;
138
+ maturity: CatalogueCapabilityMaturity;
139
+ label: CatalogueCapabilityLabel;
140
+ }
141
+ export interface ProviderCatalogueEntry {
142
+ providerId: CatalogueProviderId;
143
+ displayName: string;
144
+ connectionState: ConnectionState;
145
+ capabilities: Readonly<Record<CataloguePlatform, CataloguePlatformCapability>>;
146
+ authMode: CatalogueAuthMode;
147
+ action: string;
148
+ }
149
+ export interface GeneratedProviderDocument {
150
+ providers?: unknown;
151
+ }
152
+ /**
153
+ * Join generated provider facts to the closed connection state model.
154
+ *
155
+ * The generated document supplies identity, display name, auth mode and the
156
+ * platform set. Product capability maturity is the frozen overlay above. No
157
+ * site or local application is inspected at runtime.
158
+ */
159
+ export declare function queryProviderCatalogue(generated: GeneratedProviderDocument, states?: Readonly<Partial<Record<CatalogueProviderId, ConnectionState>>>): readonly ProviderCatalogueEntry[];
160
+ export interface PlannedProviderEntry {
161
+ specId: string;
162
+ displayName: string;
163
+ action: "Planned";
164
+ }
165
+ export type CatalogueRow = ({
166
+ availability: "connectable";
167
+ } & ProviderCatalogueEntry) | ({
168
+ availability: "planned";
169
+ } & PlannedProviderEntry);
170
+ /**
171
+ * The connections surface showing connectable providers and planned products.
172
+ *
173
+ * The product catalogue presents every supported or planned provider in document
174
+ * order after the connectable set. Planned rows carry no connection state because
175
+ * no background collector or credential exists for them.
176
+ */
177
+ export declare function queryCatalogueRows(generated: GeneratedProviderDocument, states?: Readonly<Partial<Record<CatalogueProviderId, ConnectionState>>>): readonly CatalogueRow[];
178
+ //# sourceMappingURL=connection-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection-state.d.ts","sourceRoot":"","sources":["../src/connection-state.ts"],"names":[],"mappings":"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"}