@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,372 @@
1
+ /**
2
+ * Effective-dated USD rate records for canonical managed accounts.
3
+ *
4
+ * The account is the billing boundary: one canonical accountId owns one
5
+ * ordered sequence of rate records. Selecting a catalog preset COPIES its
6
+ * terms -- provider, account type, label, catalog version, and provenance --
7
+ * into the new record, so a later catalog edit never rewrites an existing
8
+ * record or a closed historical report. A rate stays effective until the next
9
+ * record for the same account begins; this module never inserts a default
10
+ * end, renewal, or effective instant, and an explicit `monthlyUsd: 0` is a
11
+ * valid rate, distinct from no rate being recorded at all.
12
+ */
13
+
14
+ import type {
15
+ CatalogAccountType,
16
+ CatalogProvider,
17
+ SubscriptionPlanPreset,
18
+ } from "./subscription-plan-catalog.js";
19
+ import {
20
+ CATALOG_ACCOUNT_TYPES,
21
+ CATALOG_PROVIDERS,
22
+ MAX_PRESET_ID_LENGTH,
23
+ PRESET_ID_PATTERN,
24
+ } from "./subscription-plan-catalog.js";
25
+
26
+ const MAX_ACCOUNT_ID_LENGTH = 128;
27
+ const MAX_LABEL_LENGTH = 64;
28
+ const MAX_PROVENANCE_LENGTH = 256;
29
+
30
+ export class AccountRateHistoryError extends Error {
31
+ constructor(message: string) {
32
+ super(`[account-rate-history] ${message}`);
33
+ this.name = "AccountRateHistoryError";
34
+ }
35
+ }
36
+
37
+ export interface AccountRateRecord {
38
+ readonly accountId: string;
39
+ readonly provider: CatalogProvider;
40
+ readonly accountType: CatalogAccountType;
41
+ readonly monthlyUsd: number;
42
+ /** RFC 3339 instant with an explicit `Z` or numeric UTC offset. */
43
+ readonly effectiveFrom: string;
44
+ readonly presetId: string;
45
+ readonly presetLabel: string;
46
+ readonly catalogVersion: number;
47
+ readonly provenance: string;
48
+ /** Present only when the operator supplied an explicit rate override. */
49
+ readonly overrideProvenance?: string;
50
+ }
51
+
52
+ function isRecord(value: unknown): value is Record<string, unknown> {
53
+ return typeof value === "object" && value !== null && !Array.isArray(value);
54
+ }
55
+
56
+ function isBoundedString(value: unknown, maxLength: number): value is string {
57
+ return (
58
+ typeof value === "string" &&
59
+ value.trim().length > 0 &&
60
+ value.length <= maxLength
61
+ );
62
+ }
63
+
64
+ /**
65
+ * A strict RFC 3339 timestamp: date, `T`, time, and either `Z` or a signed
66
+ * `HH:MM` numeric offset. An offsetless date-time is rejected, matching the
67
+ * product's custom-range timestamp rule.
68
+ */
69
+ const RFC3339_INSTANT_PATTERN =
70
+ /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/u;
71
+
72
+ function parseInstantMs(value: string): number {
73
+ const epochMs = Date.parse(value);
74
+ if (!Number.isFinite(epochMs)) {
75
+ throw new AccountRateHistoryError(
76
+ `"${value}" is not a valid RFC 3339 instant.`,
77
+ );
78
+ }
79
+ return epochMs;
80
+ }
81
+
82
+ /** Validates an RFC 3339 instant string and returns it unchanged. */
83
+ export function assertValidEffectiveFrom(value: unknown, context: string): string {
84
+ if (typeof value !== "string" || !RFC3339_INSTANT_PATTERN.test(value)) {
85
+ throw new AccountRateHistoryError(
86
+ `${context} must be an RFC 3339 timestamp with an explicit "Z" or numeric offset.`,
87
+ );
88
+ }
89
+ parseInstantMs(value);
90
+ return value;
91
+ }
92
+
93
+ function assertValidAccountId(value: unknown, context: string): string {
94
+ if (!isBoundedString(value, MAX_ACCOUNT_ID_LENGTH)) {
95
+ throw new AccountRateHistoryError(
96
+ `${context}.accountId must be a non-empty string of at most ${MAX_ACCOUNT_ID_LENGTH} characters.`,
97
+ );
98
+ }
99
+ return value;
100
+ }
101
+
102
+ function assertValidPresetId(value: unknown, context: string): string {
103
+ if (
104
+ !isBoundedString(value, MAX_PRESET_ID_LENGTH) ||
105
+ !PRESET_ID_PATTERN.test(value)
106
+ ) {
107
+ throw new AccountRateHistoryError(
108
+ `${context}.presetId must be a lowercase, hyphen-separated identifier of at most ${MAX_PRESET_ID_LENGTH} characters.`,
109
+ );
110
+ }
111
+ return value;
112
+ }
113
+
114
+ function assertValidMonthlyUsd(value: unknown, context: string): number {
115
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
116
+ throw new AccountRateHistoryError(
117
+ `${context}.monthlyUsd must be a finite, non-negative USD amount.`,
118
+ );
119
+ }
120
+ return value;
121
+ }
122
+
123
+ function assertValidCatalogVersion(value: unknown, context: string): number {
124
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 1) {
125
+ throw new AccountRateHistoryError(
126
+ `${context}.catalogVersion must be a positive integer.`,
127
+ );
128
+ }
129
+ return value;
130
+ }
131
+
132
+ function assertValidProvenance(
133
+ value: unknown,
134
+ context: string,
135
+ field: string,
136
+ ): string {
137
+ if (!isBoundedString(value, MAX_PROVENANCE_LENGTH)) {
138
+ throw new AccountRateHistoryError(
139
+ `${context}.${field} must be a non-empty string of at most ${MAX_PROVENANCE_LENGTH} characters.`,
140
+ );
141
+ }
142
+ return value;
143
+ }
144
+
145
+ function assertValidPresetLabel(value: unknown, context: string): string {
146
+ if (!isBoundedString(value, MAX_LABEL_LENGTH)) {
147
+ throw new AccountRateHistoryError(
148
+ `${context}.presetLabel must be a non-empty string of at most ${MAX_LABEL_LENGTH} characters.`,
149
+ );
150
+ }
151
+ return value;
152
+ }
153
+
154
+ function assertValidProvider(value: unknown, context: string): CatalogProvider {
155
+ if (
156
+ typeof value !== "string" ||
157
+ !(CATALOG_PROVIDERS as readonly string[]).includes(value)
158
+ ) {
159
+ throw new AccountRateHistoryError(
160
+ `${context}.provider must be one of [${CATALOG_PROVIDERS.join(", ")}].`,
161
+ );
162
+ }
163
+ return value as CatalogProvider;
164
+ }
165
+
166
+ function assertValidAccountType(value: unknown, context: string): CatalogAccountType {
167
+ if (
168
+ typeof value !== "string" ||
169
+ !(CATALOG_ACCOUNT_TYPES as readonly string[]).includes(value)
170
+ ) {
171
+ throw new AccountRateHistoryError(
172
+ `${context}.accountType must be one of [${CATALOG_ACCOUNT_TYPES.join(", ")}].`,
173
+ );
174
+ }
175
+ return value as CatalogAccountType;
176
+ }
177
+
178
+ const RECORD_FIELDS = new Set([
179
+ "accountId",
180
+ "provider",
181
+ "accountType",
182
+ "monthlyUsd",
183
+ "effectiveFrom",
184
+ "presetId",
185
+ "presetLabel",
186
+ "catalogVersion",
187
+ "provenance",
188
+ "overrideProvenance",
189
+ ]);
190
+
191
+ /**
192
+ * Validates one already-persisted rate record, allowlisting exactly the
193
+ * fields above and rejecting any other field. Structural only: never
194
+ * resolves `presetId` against a live catalog, so a stored record stays valid
195
+ * -- and unchanged -- after the catalog that produced it is edited.
196
+ */
197
+ export function parseAccountRateRecord(
198
+ value: unknown,
199
+ context: string,
200
+ ): AccountRateRecord {
201
+ if (!isRecord(value)) {
202
+ throw new AccountRateHistoryError(`${context} must be a JSON object.`);
203
+ }
204
+ const unknownKeys = Object.keys(value).filter((key) => !RECORD_FIELDS.has(key));
205
+ if (unknownKeys.length > 0) {
206
+ throw new AccountRateHistoryError(
207
+ `${context} has unsupported field "${unknownKeys[0]}".`,
208
+ );
209
+ }
210
+ const hasOverrideProvenance = value["overrideProvenance"] !== undefined;
211
+ const record: AccountRateRecord = {
212
+ accountId: assertValidAccountId(value["accountId"], context),
213
+ provider: assertValidProvider(value["provider"], context),
214
+ accountType: assertValidAccountType(value["accountType"], context),
215
+ monthlyUsd: assertValidMonthlyUsd(value["monthlyUsd"], context),
216
+ effectiveFrom: assertValidEffectiveFrom(
217
+ value["effectiveFrom"],
218
+ `${context}.effectiveFrom`,
219
+ ),
220
+ presetId: assertValidPresetId(value["presetId"], context),
221
+ presetLabel: assertValidPresetLabel(value["presetLabel"], context),
222
+ catalogVersion: assertValidCatalogVersion(value["catalogVersion"], context),
223
+ provenance: assertValidProvenance(value["provenance"], context, "provenance"),
224
+ ...(hasOverrideProvenance
225
+ ? {
226
+ overrideProvenance: assertValidProvenance(
227
+ value["overrideProvenance"],
228
+ context,
229
+ "overrideProvenance",
230
+ ),
231
+ }
232
+ : {}),
233
+ };
234
+ return Object.freeze(record);
235
+ }
236
+
237
+ export interface CreateAccountRateRecordInput {
238
+ readonly accountId: string;
239
+ readonly preset: SubscriptionPlanPreset;
240
+ readonly catalogVersion: number;
241
+ readonly effectiveFrom: string;
242
+ readonly monthlyUsdOverride?: number;
243
+ readonly overrideProvenance?: string;
244
+ }
245
+
246
+ /**
247
+ * Builds one immutable rate record by COPYING the preset's terms -- provider,
248
+ * account type, label, provenance -- and the caller-supplied catalog version
249
+ * into plain fields on the record. The record holds no reference back to the
250
+ * preset or catalog, so a later catalog edit or removal cannot change it.
251
+ */
252
+ export function createAccountRateRecord(
253
+ input: CreateAccountRateRecordInput,
254
+ ): AccountRateRecord {
255
+ const context = "accountRateRecord";
256
+ const accountId = assertValidAccountId(input.accountId, context);
257
+ const effectiveFrom = assertValidEffectiveFrom(
258
+ input.effectiveFrom,
259
+ `${context}.effectiveFrom`,
260
+ );
261
+ const catalogVersion = assertValidCatalogVersion(input.catalogVersion, context);
262
+ const hasOverride = input.monthlyUsdOverride !== undefined;
263
+ if (hasOverride !== (input.overrideProvenance !== undefined)) {
264
+ throw new AccountRateHistoryError(
265
+ `${context}.monthlyUsdOverride and ${context}.overrideProvenance must be supplied together.`,
266
+ );
267
+ }
268
+ const monthlyUsd = hasOverride
269
+ ? assertValidMonthlyUsd(input.monthlyUsdOverride, context)
270
+ : input.preset.monthlyUsd;
271
+ const overrideProvenance = hasOverride
272
+ ? assertValidProvenance(input.overrideProvenance, context, "overrideProvenance")
273
+ : undefined;
274
+ const record: AccountRateRecord = {
275
+ accountId,
276
+ provider: input.preset.provider,
277
+ accountType: input.preset.accountType,
278
+ monthlyUsd,
279
+ effectiveFrom,
280
+ presetId: input.preset.id,
281
+ presetLabel: input.preset.label,
282
+ catalogVersion,
283
+ provenance: input.preset.provenance,
284
+ ...(overrideProvenance !== undefined ? { overrideProvenance } : {}),
285
+ };
286
+ return Object.freeze(record);
287
+ }
288
+
289
+ /**
290
+ * One candidate going into {@link normalizeAccountRateHistory}. `effectiveUntil`
291
+ * is an optional, input-only, EXPLICIT upper bound the caller asserts for this
292
+ * record's coverage; it is exclusive, never inferred when omitted, and never
293
+ * copied onto the normalized {@link AccountRateRecord}. It lets a caller
294
+ * reconstructing history from known bounds catch an inconsistent, overlapping
295
+ * claim before it reaches storage.
296
+ */
297
+ export interface AccountRateHistoryCandidate {
298
+ readonly record: AccountRateRecord;
299
+ readonly effectiveUntil?: string;
300
+ }
301
+
302
+ /**
303
+ * Orders one account's candidate rate records ascending by `effectiveFrom`
304
+ * instant, and rejects:
305
+ *
306
+ * - more than one accountId in the input (each call normalizes one account);
307
+ * - two records that resolve to the same effective instant, even when their
308
+ * raw strings differ (e.g. `Z` vs `+00:00`); and
309
+ * - an explicit `effectiveUntil` bound that does not follow its own record's
310
+ * `effectiveFrom`, or that overlaps a later record's `effectiveFrom`.
311
+ *
312
+ * Never inserts a default end, renewal, or effective instant: an omitted
313
+ * `effectiveUntil` places no upper bound on a record other than the next
314
+ * record's `effectiveFrom`, once sorted.
315
+ */
316
+ export function normalizeAccountRateHistory(
317
+ candidates: readonly AccountRateHistoryCandidate[],
318
+ ): readonly AccountRateRecord[] {
319
+ if (candidates.length === 0) return Object.freeze([]);
320
+ const accountId = candidates[0]!.record.accountId;
321
+ for (const candidate of candidates) {
322
+ if (candidate.record.accountId !== accountId) {
323
+ throw new AccountRateHistoryError(
324
+ "normalizeAccountRateHistory received records for more than one accountId.",
325
+ );
326
+ }
327
+ if (candidate.effectiveUntil !== undefined) {
328
+ assertValidEffectiveFrom(
329
+ candidate.effectiveUntil,
330
+ `accountRateHistory.${accountId}.effectiveUntil`,
331
+ );
332
+ if (
333
+ parseInstantMs(candidate.effectiveUntil) <=
334
+ parseInstantMs(candidate.record.effectiveFrom)
335
+ ) {
336
+ throw new AccountRateHistoryError(
337
+ `Account "${accountId}" has an explicit effectiveUntil ("${candidate.effectiveUntil}") that does not follow its own effectiveFrom ("${candidate.record.effectiveFrom}").`,
338
+ );
339
+ }
340
+ }
341
+ }
342
+
343
+ const sorted = [...candidates].sort(
344
+ (a, b) =>
345
+ parseInstantMs(a.record.effectiveFrom) - parseInstantMs(b.record.effectiveFrom),
346
+ );
347
+
348
+ for (let i = 0; i < sorted.length; i += 1) {
349
+ const current = sorted[i]!;
350
+ const currentFrom = parseInstantMs(current.record.effectiveFrom);
351
+ const currentUntil =
352
+ current.effectiveUntil !== undefined
353
+ ? parseInstantMs(current.effectiveUntil)
354
+ : undefined;
355
+ for (let j = i + 1; j < sorted.length; j += 1) {
356
+ const other = sorted[j]!;
357
+ const otherFrom = parseInstantMs(other.record.effectiveFrom);
358
+ if (otherFrom === currentFrom) {
359
+ throw new AccountRateHistoryError(
360
+ `Account "${accountId}" has duplicate effectiveFrom "${current.record.effectiveFrom}".`,
361
+ );
362
+ }
363
+ if (currentUntil !== undefined && otherFrom < currentUntil) {
364
+ throw new AccountRateHistoryError(
365
+ `Account "${accountId}" has overlapping explicit intervals: the record effective "${current.record.effectiveFrom}" claims coverage until "${current.effectiveUntil}", which overlaps the record effective "${other.record.effectiveFrom}".`,
366
+ );
367
+ }
368
+ }
369
+ }
370
+
371
+ return Object.freeze(sorted.map((candidate) => candidate.record));
372
+ }