@centerforagenticai/pi-multi-account 0.1.1

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 (124) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +29 -0
  3. package/README.md +999 -0
  4. package/config/models/pi-multi-account.v1.json +32 -0
  5. package/config/subscription-plans.v1.json +122 -0
  6. package/package.json +76 -0
  7. package/packages/pi-anthropic-oauth/LICENSE +21 -0
  8. package/packages/pi-anthropic-oauth/package.json +54 -0
  9. package/packages/pi-anthropic-oauth/src/auth.ts +396 -0
  10. package/packages/pi-anthropic-oauth/src/context.ts +116 -0
  11. package/packages/pi-anthropic-oauth/src/convert.ts +303 -0
  12. package/packages/pi-anthropic-oauth/src/index.ts +37 -0
  13. package/packages/pi-anthropic-oauth/src/prompt.ts +137 -0
  14. package/packages/pi-anthropic-oauth/src/stream.ts +476 -0
  15. package/packages/pi-antigravity/LICENSE +21 -0
  16. package/packages/pi-antigravity/package.json +77 -0
  17. package/packages/pi-antigravity/src/auth/index.ts +14 -0
  18. package/packages/pi-antigravity/src/auth/oauth.ts +442 -0
  19. package/packages/pi-antigravity/src/client/client.ts +561 -0
  20. package/packages/pi-antigravity/src/client/index.ts +1 -0
  21. package/packages/pi-antigravity/src/context.ts +110 -0
  22. package/packages/pi-antigravity/src/diagnostics/diagnostics.ts +96 -0
  23. package/packages/pi-antigravity/src/diagnostics/index.ts +1 -0
  24. package/packages/pi-antigravity/src/image/image.ts +336 -0
  25. package/packages/pi-antigravity/src/image/index.ts +1 -0
  26. package/packages/pi-antigravity/src/index.ts +280 -0
  27. package/packages/pi-antigravity/src/models/discovery.ts +154 -0
  28. package/packages/pi-antigravity/src/models/grouping.ts +424 -0
  29. package/packages/pi-antigravity/src/models/index.ts +3 -0
  30. package/packages/pi-antigravity/src/models/models.ts +500 -0
  31. package/packages/pi-antigravity/src/stream/index.ts +1 -0
  32. package/packages/pi-antigravity/src/stream/stream.ts +1478 -0
  33. package/packages/pi-antigravity/src/types/enums.ts +42 -0
  34. package/packages/pi-antigravity/src/types/index.ts +2 -0
  35. package/packages/pi-antigravity/src/types/types.ts +292 -0
  36. package/packages/pi-antigravity/src/usage/index.ts +1 -0
  37. package/packages/pi-antigravity/src/usage/usage.ts +416 -0
  38. package/packages/pi-antigravity/src/utils/http.ts +91 -0
  39. package/packages/pi-antigravity/src/utils/index.ts +3 -0
  40. package/packages/pi-antigravity/src/utils/security.ts +73 -0
  41. package/packages/pi-antigravity/src/utils/util.ts +132 -0
  42. package/scripts/multi-account.mjs +44 -0
  43. package/src/account-labels.ts +223 -0
  44. package/src/account-plan-assignment.ts +340 -0
  45. package/src/account-rate-history.ts +372 -0
  46. package/src/anthropic-adaptive-stream.ts +531 -0
  47. package/src/anthropic-alias-stream.ts +140 -0
  48. package/src/anthropic-context-compat.ts +80 -0
  49. package/src/api-pricing.ts +579 -0
  50. package/src/bounded-file-lines.ts +97 -0
  51. package/src/catalog-rebinding.ts +177 -0
  52. package/src/catalog-registration-probe.ts +111 -0
  53. package/src/codex-adapter.ts +345 -0
  54. package/src/codex-model-defaults.ts +785 -0
  55. package/src/command-completions.ts +404 -0
  56. package/src/commands.ts +2000 -0
  57. package/src/compaction.ts +14 -0
  58. package/src/config.ts +1317 -0
  59. package/src/continuation.ts +569 -0
  60. package/src/cooldowns.ts +110 -0
  61. package/src/cost-digest-store.ts +332 -0
  62. package/src/cost-digest.ts +1044 -0
  63. package/src/cost-history.ts +251 -0
  64. package/src/cost-period-closer.ts +160 -0
  65. package/src/cost-report-json.ts +318 -0
  66. package/src/cost-report-reader.ts +368 -0
  67. package/src/cost-report-render.ts +207 -0
  68. package/src/cost-report.ts +1104 -0
  69. package/src/coverage-attestation.ts +397 -0
  70. package/src/credential-lifecycle.ts +169 -0
  71. package/src/credential-refresh.ts +248 -0
  72. package/src/declaration-notice-marker.ts +238 -0
  73. package/src/diagnostic-store.ts +276 -0
  74. package/src/diagnostics.ts +309 -0
  75. package/src/discovery.ts +471 -0
  76. package/src/duration.ts +13 -0
  77. package/src/error-classification.ts +256 -0
  78. package/src/fuzzy.ts +15 -0
  79. package/src/group-policy.ts +81 -0
  80. package/src/history-store.ts +897 -0
  81. package/src/index.ts +5572 -0
  82. package/src/lifecycle.ts +378 -0
  83. package/src/logical-dispatch.ts +279 -0
  84. package/src/logical-model-selector.ts +254 -0
  85. package/src/logical-model-switcher.ts +430 -0
  86. package/src/logical-provider-attribution.ts +544 -0
  87. package/src/logical-provider.ts +1237 -0
  88. package/src/logical-route-indicator.ts +215 -0
  89. package/src/machine-lease.ts +445 -0
  90. package/src/model-support.ts +66 -0
  91. package/src/models-declaration.ts +1091 -0
  92. package/src/openai-adapter.ts +117 -0
  93. package/src/openrouter-budget.ts +304 -0
  94. package/src/openrouter-fallback.ts +146 -0
  95. package/src/period-boundaries.ts +376 -0
  96. package/src/pi-anthropic-oauth.d.ts +6 -0
  97. package/src/preflight.ts +253 -0
  98. package/src/pricing-cache.ts +235 -0
  99. package/src/project-identity.ts +100 -0
  100. package/src/provider-registration.ts +942 -0
  101. package/src/rate-formula.ts +163 -0
  102. package/src/recovery-engine.ts +853 -0
  103. package/src/recovery-output.ts +837 -0
  104. package/src/recovery-plan.ts +239 -0
  105. package/src/report-range.ts +203 -0
  106. package/src/route-resolver.ts +789 -0
  107. package/src/routing-config-transaction.ts +232 -0
  108. package/src/routing.ts +1163 -0
  109. package/src/runtime-state.ts +630 -0
  110. package/src/session-account-groups.ts +284 -0
  111. package/src/session-restore.ts +287 -0
  112. package/src/shared-usage.ts +1392 -0
  113. package/src/standalone-cli.ts +720 -0
  114. package/src/status-view.ts +578 -0
  115. package/src/subscription-plan-catalog.ts +346 -0
  116. package/src/tier-model-resolver.ts +46 -0
  117. package/src/upstream-anthropic.ts +315 -0
  118. package/src/upstream-antigravity.ts +327 -0
  119. package/src/usage-fetch.ts +1634 -0
  120. package/src/usage.ts +1026 -0
  121. package/src/vendor.ts +87 -0
  122. package/src/warmer.ts +231 -0
  123. package/src/watchdog.ts +219 -0
  124. package/src/window-history.ts +270 -0
@@ -0,0 +1,270 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import {
3
+ appendHistory,
4
+ iterateHistory,
5
+ } from "./history-store.js";
6
+ import { isAllowedFamily, type AllowedFamily } from "./config.js";
7
+ import { isCanonicalManagedProviderId } from "./runtime-state.js";
8
+
9
+ /**
10
+ * The subscription family a retained window sample names, or `undefined` for a
11
+ * non-string, unknown, or owning-vendor-api `openai` family. Its fixed
12
+ * `AllowedFamily | undefined` return type is what keeps the retained window
13
+ * schema subscription-only while still narrowing `family` to `AllowedFamily` for
14
+ * the downstream `isCanonicalManagedProviderId` check. The mutation control for
15
+ * this guard changes only this body (to `return family as AllowedFamily`), which
16
+ * still compiles because the signature is unchanged.
17
+ */
18
+ function subscriptionFamilyForWindow(family: unknown): AllowedFamily | undefined {
19
+ return typeof family === "string" && isAllowedFamily(family)
20
+ ? family
21
+ : undefined;
22
+ }
23
+
24
+ const MAX_OBSERVER_ID_LENGTH = 128;
25
+ const DEFAULT_BUILD_PROVENANCE = "pi-multi-account@0.1.0";
26
+ const UNUSABLE_REASON = "reset-time-unavailable";
27
+ const MAX_PROJECT_DIGEST_LENGTH = 64;
28
+ /**
29
+ * Bounded, opaque, non-identifying digest shape (e.g. a truncated sha256 hex
30
+ * digest with a short type prefix, as produced by
31
+ * `usage-fetch.ts#deriveAntigravityProjectDigest`). This module never derives
32
+ * the digest itself and never accepts a raw project id: it only bounds and
33
+ * stores whatever already-projected value a caller supplies.
34
+ */
35
+ function validProjectDigest(value: unknown): value is string {
36
+ return (
37
+ typeof value === "string" &&
38
+ value.length > 0 &&
39
+ value.length <= MAX_PROJECT_DIGEST_LENGTH &&
40
+ /^[a-z0-9-]+$/.test(value)
41
+ );
42
+ }
43
+
44
+ /** A retained sample for one provider-reported rate-limit window. */
45
+ export interface WindowSample {
46
+ readonly providerId: string;
47
+ /** Canonical account identity; deliberately not a human-readable label. */
48
+ readonly accountId: string;
49
+ readonly family: AllowedFamily;
50
+ readonly windowId: string;
51
+ /** Reset epoch used by rate calculation; absent samples are unusable. */
52
+ readonly resetEpoch?: number;
53
+ /** Canonical stored unit: fraction of the provider budget remaining. */
54
+ readonly remainingFraction?: number;
55
+ readonly resetAtMs?: number;
56
+ /**
57
+ * Bounded, non-identifying digest of an upstream account-scoped project id
58
+ * (e.g. Google Antigravity's Cloud project). Absent for a family with no such
59
+ * concept. Never the raw id -- see `validProjectDigest`.
60
+ */
61
+ readonly projectDigest?: string;
62
+ readonly recordedAtMs: number;
63
+ readonly schemaVersion: number;
64
+ readonly buildProvenance: string;
65
+ readonly observerId: string;
66
+ readonly usable: boolean;
67
+ readonly unusableReason?: string;
68
+ }
69
+
70
+ export type WindowHistoryWriteOptions = NonNullable<
71
+ Parameters<typeof appendHistory>[1]
72
+ >;
73
+
74
+ type WindowSampleInput = Omit<
75
+ WindowSample,
76
+ "schemaVersion" | "buildProvenance" | "observerId" | "usable"
77
+ > &
78
+ Partial<
79
+ Pick<
80
+ WindowSample,
81
+ "schemaVersion" | "buildProvenance" | "observerId" | "usable" | "unusableReason"
82
+ >
83
+ >;
84
+
85
+ function validTimestamp(value: unknown): value is number {
86
+ return typeof value === "number" && Number.isFinite(value) && value >= 0;
87
+ }
88
+
89
+ function validFraction(value: unknown): value is number {
90
+ return (
91
+ typeof value === "number" &&
92
+ Number.isFinite(value) &&
93
+ value >= 0 &&
94
+ value <= 1
95
+ );
96
+ }
97
+
98
+ function validWindowSample(value: unknown): value is WindowSample {
99
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
100
+ return false;
101
+ }
102
+ const sample = value as Record<string, unknown>;
103
+ // Retained window schema stays subscription-only: an owning-vendor-api
104
+ // `openai` family yields `undefined` here and is rejected below, even though
105
+ // isCanonicalManagedProviderId now widens. `family` is AllowedFamily when set.
106
+ const family = subscriptionFamilyForWindow(sample.family);
107
+ if (
108
+ typeof sample.providerId !== "string" ||
109
+ typeof sample.accountId !== "string" ||
110
+ family === undefined ||
111
+ !isCanonicalManagedProviderId(sample.providerId, family) ||
112
+ sample.accountId !== sample.providerId ||
113
+ typeof sample.windowId !== "string" ||
114
+ sample.windowId.length === 0 ||
115
+ sample.windowId.length > 128 ||
116
+ !validTimestamp(sample.recordedAtMs) ||
117
+ sample.schemaVersion !== 1 ||
118
+ typeof sample.buildProvenance !== "string" ||
119
+ sample.buildProvenance.length === 0 ||
120
+ sample.buildProvenance.length > 128 ||
121
+ typeof sample.observerId !== "string" ||
122
+ sample.observerId.length === 0 ||
123
+ sample.observerId.length > MAX_OBSERVER_ID_LENGTH ||
124
+ typeof sample.usable !== "boolean"
125
+ ) {
126
+ return false;
127
+ }
128
+ if (sample.remainingFraction !== undefined && !validFraction(sample.remainingFraction)) {
129
+ return false;
130
+ }
131
+ if (sample.resetAtMs !== undefined && !validTimestamp(sample.resetAtMs)) {
132
+ return false;
133
+ }
134
+ if (sample.resetEpoch !== undefined && !validTimestamp(sample.resetEpoch)) {
135
+ return false;
136
+ }
137
+ if (sample.projectDigest !== undefined && !validProjectDigest(sample.projectDigest)) {
138
+ return false;
139
+ }
140
+ if (sample.usable && (!validFraction(sample.remainingFraction) || !validTimestamp(sample.resetAtMs))) {
141
+ return false;
142
+ }
143
+ if (!sample.usable && typeof sample.unusableReason !== "string") {
144
+ return false;
145
+ }
146
+ return true;
147
+ }
148
+
149
+ function normalizeSample(sample: WindowSampleInput): WindowSample {
150
+ const usable =
151
+ sample.usable === true &&
152
+ validFraction(sample.remainingFraction) &&
153
+ validTimestamp(sample.resetAtMs);
154
+ const suppliedObserverId = sample.observerId;
155
+ const observerId =
156
+ typeof suppliedObserverId === "string" &&
157
+ /^process-[a-zA-Z0-9-]+$/.test(suppliedObserverId)
158
+ ? suppliedObserverId
159
+ : `process-${process.pid}`;
160
+ const normalized: WindowSample = {
161
+ providerId: sample.providerId,
162
+ accountId: sample.accountId,
163
+ family: sample.family,
164
+ windowId: sample.windowId,
165
+ ...(sample.resetAtMs === undefined ? {} : { resetAtMs: sample.resetAtMs }),
166
+ ...(sample.resetEpoch === undefined
167
+ ? {}
168
+ : { resetEpoch: sample.resetEpoch }),
169
+ ...(sample.remainingFraction === undefined
170
+ ? {}
171
+ : { remainingFraction: sample.remainingFraction }),
172
+ ...(sample.projectDigest === undefined
173
+ ? {}
174
+ : { projectDigest: sample.projectDigest }),
175
+ recordedAtMs: sample.recordedAtMs,
176
+ schemaVersion: sample.schemaVersion ?? 1,
177
+ buildProvenance: sample.buildProvenance ?? DEFAULT_BUILD_PROVENANCE,
178
+ observerId: observerId.slice(0, MAX_OBSERVER_ID_LENGTH),
179
+ usable,
180
+ ...(usable
181
+ ? {}
182
+ : {
183
+ unusableReason:
184
+ sample.unusableReason ?? UNUSABLE_REASON,
185
+ }),
186
+ };
187
+ return normalized;
188
+ }
189
+
190
+ /** Convert a utilization fraction into the canonical remaining fraction. */
191
+ export function remainingFractionFromUtilization(
192
+ utilization: number,
193
+ ): number | undefined {
194
+ return validFraction(utilization)
195
+ ? Math.round((1 - utilization) * 1_000_000_000_000) / 1_000_000_000_000
196
+ : undefined;
197
+ }
198
+
199
+ /** Convert the stored remaining fraction into utilization at read time. */
200
+ export function utilizationFromRemainingFraction(
201
+ remainingFraction: number,
202
+ ): number | undefined {
203
+ return validFraction(remainingFraction) ? 1 - remainingFraction : undefined;
204
+ }
205
+
206
+ /** Mark a provider window unusable without discarding or inventing its reset. */
207
+ export function markWindowUnusable(sample: WindowSampleInput): WindowSample {
208
+ return normalizeSample({ ...sample, usable: false });
209
+ }
210
+
211
+ /** Persist one immutable window sample through the shared history lock. */
212
+ export async function writeHistoryWindowSample(
213
+ sample: WindowSampleInput,
214
+ options?: WindowHistoryWriteOptions,
215
+ ): Promise<boolean> {
216
+ try {
217
+ const normalized = normalizeSample(sample);
218
+ if (!validWindowSample(normalized)) return false;
219
+ return await appendHistory(
220
+ {
221
+ schemaVersion: 1,
222
+ recordType: "window-sample",
223
+ stableId: randomUUID(),
224
+ observedAtMs: normalized.recordedAtMs,
225
+ recordedAtMs: normalized.recordedAtMs,
226
+ payload: normalized,
227
+ },
228
+ options,
229
+ );
230
+ } catch {
231
+ return false;
232
+ }
233
+ }
234
+
235
+ function* iterateValidWindowHistory(
236
+ options?: WindowHistoryWriteOptions,
237
+ ): Generator<WindowSample> {
238
+ for (const record of iterateHistory("window-sample", options)) {
239
+ if (record.recordType !== "window-sample") continue;
240
+ if (validWindowSample(record.payload)) yield record.payload;
241
+ }
242
+ }
243
+
244
+ /** Read valid window payloads while preserving the history store's append log. */
245
+ export function readWindowHistory(
246
+ options?: WindowHistoryWriteOptions,
247
+ ): readonly WindowSample[] {
248
+ return [...iterateValidWindowHistory(options)];
249
+ }
250
+
251
+ /** Fold status history to one latest sample per provider/window identity. */
252
+ export function readLatestWindowHistory(
253
+ options?: WindowHistoryWriteOptions,
254
+ ): ReadonlyMap<string, readonly WindowSample[]> {
255
+ const latest = new Map<string, WindowSample>();
256
+ for (const sample of iterateValidWindowHistory(options)) {
257
+ const key = JSON.stringify([sample.providerId, sample.windowId]);
258
+ const previous = latest.get(key);
259
+ if (previous === undefined || sample.recordedAtMs > previous.recordedAtMs) {
260
+ latest.set(key, sample);
261
+ }
262
+ }
263
+ const byProvider = new Map<string, WindowSample[]>();
264
+ for (const sample of latest.values()) {
265
+ const samples = byProvider.get(sample.providerId) ?? [];
266
+ samples.push(sample);
267
+ byProvider.set(sample.providerId, samples);
268
+ }
269
+ return byProvider;
270
+ }