@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,789 @@
1
+ import {
2
+ ALLOWED_FAMILIES,
3
+ type AllowedFamily,
4
+ type MultiAccountConfig,
5
+ } from "./config.js";
6
+ import { ModelSupportRegistry } from "./model-support.js";
7
+ import {
8
+ selectExactModelRouteCandidates,
9
+ type SubscriptionManagedAccount,
10
+ } from "./routing.js";
11
+ import {
12
+ isCanonicalManagedProviderId,
13
+ type RuntimeState,
14
+ } from "./runtime-state.js";
15
+
16
+ export const ROUTE_RESOLVER_PURPOSE = "exact-model-routing" as const;
17
+ export const ROUTE_RESOLVER_VERSION = 1 as const;
18
+ export const ROUTE_RESOLVER_REGISTRY_KEY = Symbol.for(
19
+ "@caair/pi-multi-account/route-resolver",
20
+ );
21
+
22
+ const MAX_MODEL_ID_LENGTH = 256;
23
+
24
+ type RouteResolverFamily = AllowedFamily;
25
+
26
+ export interface RouteResolverInput {
27
+ readonly purpose: typeof ROUTE_RESOLVER_PURPOSE;
28
+ readonly version: typeof ROUTE_RESOLVER_VERSION;
29
+ readonly modelId: string;
30
+ readonly family?: RouteResolverFamily;
31
+ readonly preferredProviderId?: string;
32
+ readonly excludedProviderIds?: readonly string[];
33
+ }
34
+
35
+ export interface ExactModelRoute {
36
+ readonly providerId: string;
37
+ readonly modelId: string;
38
+ readonly family: RouteResolverFamily;
39
+ }
40
+
41
+ export type RouteResolverUnresolvedReason =
42
+ | "invalid-input"
43
+ | "unsupported-purpose"
44
+ | "incompatible-version"
45
+ | "unknown-model"
46
+ | "ambiguous-model-family"
47
+ | "family-model-mismatch"
48
+ | "no-eligible-routes";
49
+
50
+ export interface RouteResolverResolvedResult {
51
+ readonly purpose: typeof ROUTE_RESOLVER_PURPOSE;
52
+ readonly version: typeof ROUTE_RESOLVER_VERSION;
53
+ readonly status: "resolved";
54
+ readonly reason: "eligible-routes";
55
+ readonly routes: readonly ExactModelRoute[];
56
+ }
57
+
58
+ export interface RouteResolverUnresolvedResult {
59
+ readonly purpose: typeof ROUTE_RESOLVER_PURPOSE;
60
+ readonly version: typeof ROUTE_RESOLVER_VERSION;
61
+ readonly status: "unresolved";
62
+ readonly reason: RouteResolverUnresolvedReason;
63
+ readonly routes: readonly [];
64
+ }
65
+
66
+ export type RouteResolverResult =
67
+ | RouteResolverResolvedResult
68
+ | RouteResolverUnresolvedResult;
69
+
70
+ export interface ExactModelRouteResolutionContext {
71
+ /** Credentialed managed accounts and their live model catalogs. */
72
+ readonly catalogAccounts: readonly SubscriptionManagedAccount[];
73
+ /** Accounts currently eligible for automatic routing. */
74
+ readonly accounts: readonly SubscriptionManagedAccount[];
75
+ readonly state: RuntimeState;
76
+ readonly config: MultiAccountConfig;
77
+ readonly modelSupport?: ModelSupportRegistry;
78
+ readonly nowMs: number;
79
+ }
80
+
81
+ export type RouteResolver = (input: unknown) => RouteResolverResult;
82
+
83
+ export interface RouteResolverService {
84
+ readonly purpose: typeof ROUTE_RESOLVER_PURPOSE;
85
+ readonly version: typeof ROUTE_RESOLVER_VERSION;
86
+ readonly resolve: RouteResolver;
87
+ }
88
+
89
+ export interface RetainedRouteResolverService {
90
+ readonly purpose: typeof ROUTE_RESOLVER_PURPOSE;
91
+ readonly version: number;
92
+ readonly resolve: (input: unknown) => unknown;
93
+ }
94
+
95
+ export type RouteResolverPublicationResult =
96
+ | { readonly status: "published"; readonly service: RouteResolverService }
97
+ | {
98
+ readonly status: "retained-newer";
99
+ readonly service: RetainedRouteResolverService;
100
+ };
101
+
102
+ export type RouteResolverLookup =
103
+ | { readonly status: "absent" }
104
+ | { readonly status: "incompatible" }
105
+ | { readonly status: "available"; readonly service: RouteResolverService };
106
+
107
+ function unresolved(reason: RouteResolverUnresolvedReason): RouteResolverUnresolvedResult {
108
+ return Object.freeze({
109
+ purpose: ROUTE_RESOLVER_PURPOSE,
110
+ version: ROUTE_RESOLVER_VERSION,
111
+ status: "unresolved" as const,
112
+ reason,
113
+ routes: Object.freeze([]) as readonly [],
114
+ });
115
+ }
116
+
117
+ function routeResult(
118
+ accounts: readonly SubscriptionManagedAccount[],
119
+ modelId: string,
120
+ ): RouteResolverResult {
121
+ const routes = accounts.map((account) =>
122
+ Object.freeze({
123
+ providerId: account.providerId,
124
+ modelId,
125
+ family: account.family,
126
+ }),
127
+ );
128
+ return Object.freeze({
129
+ purpose: ROUTE_RESOLVER_PURPOSE,
130
+ version: ROUTE_RESOLVER_VERSION,
131
+ status: "resolved" as const,
132
+ reason: "eligible-routes" as const,
133
+ routes: Object.freeze(routes),
134
+ });
135
+ }
136
+
137
+ function isRecord(value: unknown): value is Record<string, unknown> {
138
+ return typeof value === "object" && value !== null;
139
+ }
140
+
141
+ function familyForProvider(providerId: string): AllowedFamily | undefined {
142
+ for (const family of ALLOWED_FAMILIES) {
143
+ if (isCanonicalManagedProviderId(providerId, family)) return family;
144
+ }
145
+ return undefined;
146
+ }
147
+
148
+ function validModelId(value: unknown): value is string {
149
+ return (
150
+ typeof value === "string" &&
151
+ value.length > 0 &&
152
+ value.length <= MAX_MODEL_ID_LENGTH &&
153
+ value.trim() === value &&
154
+ !/[\u0000-\u001f\u007f]/u.test(value)
155
+ );
156
+ }
157
+
158
+ function validInput(value: unknown): value is RouteResolverInput {
159
+ if (!isRecord(value)) return false;
160
+ if (!validModelId(value.modelId)) return false;
161
+ if (
162
+ value.family !== undefined &&
163
+ !ALLOWED_FAMILIES.includes(value.family as AllowedFamily)
164
+ ) {
165
+ return false;
166
+ }
167
+ if (
168
+ value.preferredProviderId !== undefined &&
169
+ (typeof value.preferredProviderId !== "string" ||
170
+ familyForProvider(value.preferredProviderId) === undefined)
171
+ ) {
172
+ return false;
173
+ }
174
+ if (value.excludedProviderIds !== undefined) {
175
+ if (!Array.isArray(value.excludedProviderIds)) return false;
176
+ if (
177
+ value.excludedProviderIds.some(
178
+ (providerId) =>
179
+ typeof providerId !== "string" ||
180
+ familyForProvider(providerId) === undefined,
181
+ )
182
+ ) {
183
+ return false;
184
+ }
185
+ }
186
+ return true;
187
+ }
188
+
189
+ function parseQualifiedModel(modelId: string):
190
+ | { readonly providerId: string; readonly modelId: string }
191
+ | undefined {
192
+ const slash = modelId.indexOf("/");
193
+ if (slash <= 0) return undefined;
194
+ const providerId = modelId.slice(0, slash);
195
+ if (familyForProvider(providerId) === undefined) return undefined;
196
+ const exactModelId = modelId.slice(slash + 1);
197
+ return exactModelId.length === 0
198
+ ? undefined
199
+ : { providerId, modelId: exactModelId };
200
+ }
201
+
202
+ function catalogOwners(
203
+ accounts: readonly SubscriptionManagedAccount[],
204
+ modelId: string,
205
+ ): ReadonlySet<AllowedFamily> {
206
+ const owners = new Set<AllowedFamily>();
207
+ for (const account of accounts) {
208
+ if (
209
+ familyForProvider(account.providerId) === account.family &&
210
+ account.modelIds?.includes(modelId)
211
+ ) {
212
+ owners.add(account.family);
213
+ }
214
+ }
215
+ return owners;
216
+ }
217
+
218
+ /**
219
+ * Resolves one exact model against supplied managed-account facts. The context
220
+ * is deliberately explicit so the function has no host, credential, or catalog
221
+ * discovery side effects and can be tested with bounded synthetic facts.
222
+ */
223
+ export function resolveExactModelRoutes(
224
+ input: unknown,
225
+ context: ExactModelRouteResolutionContext,
226
+ ): RouteResolverResult {
227
+ try {
228
+ if (!isRecord(input)) return unresolved("invalid-input");
229
+ if (input.purpose !== ROUTE_RESOLVER_PURPOSE) {
230
+ return unresolved(
231
+ typeof input.purpose === "string"
232
+ ? "unsupported-purpose"
233
+ : "invalid-input",
234
+ );
235
+ }
236
+ if (input.version !== ROUTE_RESOLVER_VERSION) {
237
+ return unresolved(
238
+ typeof input.version === "number"
239
+ ? "incompatible-version"
240
+ : "invalid-input",
241
+ );
242
+ }
243
+ if (!validInput(input)) return unresolved("invalid-input");
244
+
245
+ const slash = input.modelId.indexOf("/");
246
+ if (
247
+ slash > 0 &&
248
+ familyForProvider(input.modelId.slice(0, slash)) !== undefined &&
249
+ input.modelId.slice(slash + 1).length === 0
250
+ ) {
251
+ return unresolved("invalid-input");
252
+ }
253
+ const qualified = parseQualifiedModel(input.modelId);
254
+ const exactModelId = qualified?.modelId ?? input.modelId;
255
+ const qualifiedFamily = qualified
256
+ ? familyForProvider(qualified.providerId)
257
+ : undefined;
258
+ if (qualified !== undefined && qualifiedFamily === undefined) {
259
+ return unresolved("invalid-input");
260
+ }
261
+ if (
262
+ qualifiedFamily !== undefined &&
263
+ input.family !== undefined &&
264
+ input.family !== qualifiedFamily
265
+ ) {
266
+ return unresolved("family-model-mismatch");
267
+ }
268
+
269
+ const owners = catalogOwners(context.catalogAccounts, exactModelId);
270
+ let family = qualifiedFamily;
271
+ if (family === undefined) {
272
+ if (input.family !== undefined) {
273
+ if (!owners.has(input.family)) {
274
+ return unresolved(
275
+ owners.size === 0
276
+ ? "unknown-model"
277
+ : "family-model-mismatch",
278
+ );
279
+ }
280
+ family = input.family;
281
+ } else if (owners.size === 0) {
282
+ return unresolved("unknown-model");
283
+ } else if (owners.size !== 1) {
284
+ return unresolved("ambiguous-model-family");
285
+ } else {
286
+ family = [...owners][0];
287
+ }
288
+ }
289
+
290
+ if (qualified !== undefined) {
291
+ const targetCatalog = context.catalogAccounts.some(
292
+ (account) =>
293
+ account.providerId === qualified.providerId &&
294
+ account.family === family &&
295
+ account.modelIds?.includes(exactModelId),
296
+ );
297
+ if (!targetCatalog) return unresolved("unknown-model");
298
+ }
299
+
300
+ if (family === undefined) return unresolved("invalid-input");
301
+ const excludedProviderIds = new Set(input.excludedProviderIds ?? []);
302
+ const candidates = selectExactModelRouteCandidates({
303
+ accounts: context.accounts,
304
+ state: context.state,
305
+ nowMs: context.nowMs,
306
+ family,
307
+ config: context.config,
308
+ modelId: exactModelId,
309
+ ...(input.preferredProviderId === undefined
310
+ ? {}
311
+ : { preferredProviderId: input.preferredProviderId }),
312
+ ...(excludedProviderIds.size === 0 ? {} : { excludedProviderIds }),
313
+ ...(qualified === undefined
314
+ ? {}
315
+ : { providerId: qualified.providerId }),
316
+ ...(context.modelSupport === undefined
317
+ ? {}
318
+ : { modelSupport: context.modelSupport }),
319
+ });
320
+ if (candidates.length === 0) return unresolved("no-eligible-routes");
321
+ return routeResult(candidates, exactModelId);
322
+ } catch {
323
+ return unresolved("invalid-input");
324
+ }
325
+ }
326
+
327
+ interface OwnServiceFields {
328
+ readonly purpose: unknown;
329
+ readonly version: unknown;
330
+ readonly resolve: unknown;
331
+ }
332
+
333
+ function ownServiceFields(value: unknown): OwnServiceFields | undefined {
334
+ try {
335
+ if (!isRecord(value)) return undefined;
336
+ const purpose = Object.getOwnPropertyDescriptor(value, "purpose");
337
+ const version = Object.getOwnPropertyDescriptor(value, "version");
338
+ const resolve = Object.getOwnPropertyDescriptor(value, "resolve");
339
+ if (
340
+ purpose === undefined ||
341
+ version === undefined ||
342
+ resolve === undefined ||
343
+ !Object.prototype.hasOwnProperty.call(purpose, "value") ||
344
+ !Object.prototype.hasOwnProperty.call(version, "value") ||
345
+ !Object.prototype.hasOwnProperty.call(resolve, "value")
346
+ ) {
347
+ return undefined;
348
+ }
349
+ return {
350
+ purpose: purpose.value,
351
+ version: version.value,
352
+ resolve: resolve.value,
353
+ };
354
+ } catch {
355
+ return undefined;
356
+ }
357
+ }
358
+
359
+ function serviceLike(value: unknown): value is RouteResolverService {
360
+ try {
361
+ const fields = ownServiceFields(value);
362
+ return (
363
+ fields !== undefined &&
364
+ fields.purpose === ROUTE_RESOLVER_PURPOSE &&
365
+ fields.version === ROUTE_RESOLVER_VERSION &&
366
+ typeof fields.resolve === "function"
367
+ );
368
+ } catch {
369
+ return false;
370
+ }
371
+ }
372
+
373
+ function newerServiceLike(value: unknown): value is RetainedRouteResolverService {
374
+ try {
375
+ const fields = ownServiceFields(value);
376
+ return (
377
+ fields !== undefined &&
378
+ fields.purpose === ROUTE_RESOLVER_PURPOSE &&
379
+ typeof fields.version === "number" &&
380
+ Number.isFinite(fields.version) &&
381
+ fields.version > ROUTE_RESOLVER_VERSION &&
382
+ typeof fields.resolve === "function"
383
+ );
384
+ } catch {
385
+ return false;
386
+ }
387
+ }
388
+
389
+ type OwnRegistryValue =
390
+ | { readonly present: false }
391
+ | { readonly present: true; readonly readable: false }
392
+ | { readonly present: true; readonly readable: true; readonly value: unknown };
393
+
394
+ function ownRegistryValue(): OwnRegistryValue {
395
+ try {
396
+ const descriptor = Object.getOwnPropertyDescriptor(
397
+ globalThis,
398
+ ROUTE_RESOLVER_REGISTRY_KEY,
399
+ );
400
+ if (descriptor === undefined) return { present: false };
401
+ if (!("value" in descriptor)) return { present: true, readable: false };
402
+ return { present: true, readable: true, value: descriptor.value };
403
+ } catch {
404
+ return { present: true, readable: false };
405
+ }
406
+ }
407
+
408
+ // ---------------------------------------------------------------------------
409
+ // Reload-safe process-global coordinator.
410
+ //
411
+ // The public service published under `ROUTE_RESOLVER_REGISTRY_KEY` is a frozen
412
+ // facade whose `resolve` captures only the process-global coordinator below. It
413
+ // never captures a session-owned resolver or `ExtensionContext`, so a facade
414
+ // retained across a Pi reload dispatches to the newest live session owner
415
+ // instead of an invalidated one. Session shutdown revokes the owner before Pi
416
+ // invalidates its context; a retained facade with no live owner returns the
417
+ // frozen version-1 unavailable result rather than reaching stale state.
418
+ // ---------------------------------------------------------------------------
419
+
420
+ /** Internal coordinator slot. Not a discovery API and not a package-root export. */
421
+ export const ROUTE_RESOLVER_COORDINATOR_KEY = Symbol.for(
422
+ "@caair/pi-multi-account/route-resolver-coordinator",
423
+ );
424
+ const ROUTE_RESOLVER_COORDINATOR_BRAND = Symbol.for(
425
+ "@caair/pi-multi-account/route-resolver-coordinator/v1",
426
+ );
427
+ const ROUTE_RESOLVER_COORDINATOR_PROTOCOL = 1;
428
+ // Brands an owner frame so a coordinator's `current` value can be validated as a
429
+ // package-created frame rather than a forged object. `Symbol.for` keeps the
430
+ // brand stable across module-cache replacement.
431
+ const ROUTE_RESOLVER_FRAME_BRAND = Symbol.for(
432
+ "@caair/pi-multi-account/route-resolver-frame/v1",
433
+ );
434
+
435
+ /** Frozen version-1 result a facade returns when it has no live owner. */
436
+ const UNAVAILABLE_RESULT: RouteResolverUnresolvedResult =
437
+ unresolved("no-eligible-routes");
438
+
439
+ interface OwnerFrame {
440
+ active: boolean;
441
+ target: RouteResolver | undefined;
442
+ readonly facade: RouteResolverService;
443
+ readonly predecessor: OwnerFrame | undefined;
444
+ }
445
+
446
+ interface RouteResolverCoordinator {
447
+ readonly protocol: number;
448
+ current: OwnerFrame | undefined;
449
+ }
450
+
451
+ export interface RouteResolverOwner {
452
+ dispose(): void;
453
+ }
454
+
455
+ export type RouteResolverSessionPublication = RouteResolverPublicationResult & {
456
+ readonly dispose: () => void;
457
+ };
458
+
459
+ const NOOP_OWNER: RouteResolverOwner = Object.freeze({
460
+ dispose(): void {
461
+ /* A retained-newer publication owns no version-1 frame. */
462
+ },
463
+ });
464
+
465
+ function createCoordinator(): RouteResolverCoordinator {
466
+ const coordinator: Record<PropertyKey, unknown> = {};
467
+ Object.defineProperty(coordinator, ROUTE_RESOLVER_COORDINATOR_BRAND, {
468
+ value: ROUTE_RESOLVER_COORDINATOR_BRAND,
469
+ enumerable: false,
470
+ writable: false,
471
+ configurable: false,
472
+ });
473
+ Object.defineProperty(coordinator, "protocol", {
474
+ value: ROUTE_RESOLVER_COORDINATOR_PROTOCOL,
475
+ enumerable: false,
476
+ writable: false,
477
+ configurable: false,
478
+ });
479
+ Object.defineProperty(coordinator, "current", {
480
+ value: undefined,
481
+ enumerable: false,
482
+ writable: true,
483
+ configurable: false,
484
+ });
485
+ Object.preventExtensions(coordinator);
486
+ return coordinator as unknown as RouteResolverCoordinator;
487
+ }
488
+
489
+ /** Validate a value as a package-created owner frame through own descriptors only. */
490
+ function isBrandedFrame(value: unknown): boolean {
491
+ try {
492
+ if (typeof value !== "object" || value === null) return false;
493
+ const brand = Object.getOwnPropertyDescriptor(
494
+ value,
495
+ ROUTE_RESOLVER_FRAME_BRAND,
496
+ );
497
+ return (
498
+ brand !== undefined &&
499
+ "value" in brand &&
500
+ brand.value === ROUTE_RESOLVER_FRAME_BRAND
501
+ );
502
+ } catch {
503
+ return false;
504
+ }
505
+ }
506
+
507
+ /** Create a branded, active owner frame. */
508
+ function createFrame(
509
+ target: RouteResolver,
510
+ facade: RouteResolverService,
511
+ predecessor: OwnerFrame | undefined,
512
+ ): OwnerFrame {
513
+ const frame: OwnerFrame = { active: true, target, facade, predecessor };
514
+ Object.defineProperty(frame, ROUTE_RESOLVER_FRAME_BRAND, {
515
+ value: ROUTE_RESOLVER_FRAME_BRAND,
516
+ enumerable: false,
517
+ writable: false,
518
+ configurable: false,
519
+ });
520
+ return frame;
521
+ }
522
+
523
+ /** A non-enumerable, non-configurable data descriptor with the expected writability. */
524
+ function isExactDataDescriptor(
525
+ descriptor: PropertyDescriptor | undefined,
526
+ expectedWritable: boolean,
527
+ ): boolean {
528
+ return (
529
+ descriptor !== undefined &&
530
+ "value" in descriptor &&
531
+ descriptor.enumerable === false &&
532
+ descriptor.configurable === false &&
533
+ descriptor.writable === expectedWritable
534
+ );
535
+ }
536
+
537
+ /**
538
+ * Validate a candidate coordinator against the exact protocol through own data
539
+ * descriptors only, never invoking an accessor. Every deviation — wrong
540
+ * prototype, extensibility, an unexpected own key, a mis-attributed field, or a
541
+ * `current` value that is not a package-created frame — fails closed, so a
542
+ * forged or incompatible coordinator can never be reused.
543
+ */
544
+ function coordinatorFromValue(
545
+ value: unknown,
546
+ ): RouteResolverCoordinator | undefined {
547
+ try {
548
+ if (typeof value !== "object" || value === null) return undefined;
549
+ if (Object.getPrototypeOf(value) !== Object.prototype) return undefined;
550
+ if (Object.isExtensible(value)) return undefined;
551
+ const names = Object.getOwnPropertyNames(value);
552
+ const symbols = Object.getOwnPropertySymbols(value);
553
+ if (
554
+ names.length !== 2 ||
555
+ !names.includes("protocol") ||
556
+ !names.includes("current") ||
557
+ symbols.length !== 1 ||
558
+ symbols[0] !== ROUTE_RESOLVER_COORDINATOR_BRAND
559
+ ) {
560
+ return undefined;
561
+ }
562
+ const brand = Object.getOwnPropertyDescriptor(
563
+ value,
564
+ ROUTE_RESOLVER_COORDINATOR_BRAND,
565
+ );
566
+ if (
567
+ !isExactDataDescriptor(brand, false) ||
568
+ brand?.value !== ROUTE_RESOLVER_COORDINATOR_BRAND
569
+ ) {
570
+ return undefined;
571
+ }
572
+ const protocol = Object.getOwnPropertyDescriptor(value, "protocol");
573
+ if (
574
+ !isExactDataDescriptor(protocol, false) ||
575
+ protocol?.value !== ROUTE_RESOLVER_COORDINATOR_PROTOCOL
576
+ ) {
577
+ return undefined;
578
+ }
579
+ const current = Object.getOwnPropertyDescriptor(value, "current");
580
+ if (!isExactDataDescriptor(current, true)) return undefined;
581
+ if (current?.value !== undefined && !isBrandedFrame(current.value)) {
582
+ return undefined;
583
+ }
584
+ return value as unknown as RouteResolverCoordinator;
585
+ } catch {
586
+ return undefined;
587
+ }
588
+ }
589
+
590
+ /**
591
+ * Reuse the valid process-global coordinator or create one. A present value that
592
+ * does not satisfy the exact protocol — including the global slot's own
593
+ * descriptor — fails publication before the public registry or any owner
594
+ * changes; replacing it could strand retained facades, so a Pi process restart
595
+ * is the only recovery.
596
+ */
597
+ function getOrCreateCoordinator(): RouteResolverCoordinator {
598
+ let descriptor: PropertyDescriptor | undefined;
599
+ try {
600
+ descriptor = Object.getOwnPropertyDescriptor(
601
+ globalThis,
602
+ ROUTE_RESOLVER_COORDINATOR_KEY,
603
+ );
604
+ } catch {
605
+ throw new TypeError(
606
+ "route resolver coordinator slot is unreadable; a Pi process restart is required.",
607
+ );
608
+ }
609
+ if (descriptor !== undefined) {
610
+ const validSlot =
611
+ "value" in descriptor &&
612
+ descriptor.enumerable === false &&
613
+ descriptor.writable === false &&
614
+ descriptor.configurable === true;
615
+ const coordinator = validSlot
616
+ ? coordinatorFromValue(descriptor.value)
617
+ : undefined;
618
+ if (coordinator === undefined) {
619
+ throw new TypeError(
620
+ "route resolver coordinator is incompatible; a Pi process restart is required.",
621
+ );
622
+ }
623
+ return coordinator;
624
+ }
625
+ const created = createCoordinator();
626
+ Object.defineProperty(globalThis, ROUTE_RESOLVER_COORDINATOR_KEY, {
627
+ value: created,
628
+ enumerable: false,
629
+ writable: false,
630
+ configurable: true,
631
+ });
632
+ return created;
633
+ }
634
+
635
+ function buildFacade(
636
+ coordinator: RouteResolverCoordinator,
637
+ ): RouteResolverService {
638
+ return Object.freeze({
639
+ purpose: ROUTE_RESOLVER_PURPOSE,
640
+ version: ROUTE_RESOLVER_VERSION,
641
+ resolve: (input: unknown): RouteResolverResult => {
642
+ const frame = coordinator.current;
643
+ if (frame === undefined || !frame.active) return UNAVAILABLE_RESULT;
644
+ const target = frame.target;
645
+ if (typeof target !== "function") return UNAVAILABLE_RESULT;
646
+ return target(input);
647
+ },
648
+ });
649
+ }
650
+
651
+ /**
652
+ * Restore or delete the public slot, but only while it still holds this frame's
653
+ * exact facade. Any failure is contained: the frame is already inactive with a
654
+ * cleared target, so a retained facade fails closed through the coordinator.
655
+ */
656
+ function restorePublicSlotIfHeld(
657
+ ownFacade: RouteResolverService,
658
+ replacement: OwnerFrame | undefined,
659
+ ): void {
660
+ try {
661
+ const slot = ownRegistryValue();
662
+ if (!slot.present || !slot.readable || slot.value !== ownFacade) return;
663
+ if (replacement !== undefined) {
664
+ Reflect.defineProperty(globalThis, ROUTE_RESOLVER_REGISTRY_KEY, {
665
+ configurable: true,
666
+ enumerable: false,
667
+ writable: true,
668
+ value: replacement.facade,
669
+ });
670
+ } else {
671
+ Reflect.deleteProperty(globalThis, ROUTE_RESOLVER_REGISTRY_KEY);
672
+ }
673
+ } catch {
674
+ /* Contained; see the doc comment. */
675
+ }
676
+ }
677
+
678
+ function createOwner(
679
+ coordinator: RouteResolverCoordinator,
680
+ frame: OwnerFrame,
681
+ ): RouteResolverOwner {
682
+ const dispose = (): void => {
683
+ if (!frame.active) return;
684
+ frame.active = false;
685
+ frame.target = undefined;
686
+ if (coordinator.current !== frame) return;
687
+ let candidate = frame.predecessor;
688
+ while (candidate !== undefined && !candidate.active) {
689
+ candidate = candidate.predecessor;
690
+ }
691
+ coordinator.current = candidate;
692
+ restorePublicSlotIfHeld(frame.facade, candidate);
693
+ };
694
+ return { dispose };
695
+ }
696
+
697
+ function ensureResolve(
698
+ publication: Pick<RouteResolverService, "resolve"> | RouteResolverService,
699
+ ): RouteResolver {
700
+ let resolve: unknown;
701
+ try {
702
+ resolve = publication.resolve;
703
+ if (typeof resolve !== "function") {
704
+ throw new TypeError("route resolver publication must provide resolve.");
705
+ }
706
+ } catch (error) {
707
+ if (
708
+ error instanceof TypeError &&
709
+ error.message === "route resolver publication must provide resolve."
710
+ ) {
711
+ throw error;
712
+ }
713
+ throw new TypeError("route resolver publication must provide resolve.");
714
+ }
715
+ return resolve as RouteResolver;
716
+ }
717
+
718
+ function publishRouteResolverCore(
719
+ publication: Pick<RouteResolverService, "resolve"> | RouteResolverService,
720
+ ): {
721
+ readonly result: RouteResolverPublicationResult;
722
+ readonly owner: RouteResolverOwner;
723
+ } {
724
+ const existing = ownRegistryValue();
725
+ if (existing.present && existing.readable && newerServiceLike(existing.value)) {
726
+ return {
727
+ result: { status: "retained-newer", service: existing.value },
728
+ owner: NOOP_OWNER,
729
+ };
730
+ }
731
+ const resolve = ensureResolve(publication);
732
+ const coordinator = getOrCreateCoordinator();
733
+ const priorFrame = coordinator.current;
734
+ const facade = buildFacade(coordinator);
735
+ const frame = createFrame(resolve, facade, priorFrame);
736
+ try {
737
+ coordinator.current = frame;
738
+ } catch (error) {
739
+ frame.active = false;
740
+ frame.target = undefined;
741
+ throw error;
742
+ }
743
+ try {
744
+ Object.defineProperty(globalThis, ROUTE_RESOLVER_REGISTRY_KEY, {
745
+ configurable: true,
746
+ enumerable: false,
747
+ writable: true,
748
+ value: facade,
749
+ });
750
+ } catch (error) {
751
+ coordinator.current = priorFrame;
752
+ frame.active = false;
753
+ frame.target = undefined;
754
+ throw error;
755
+ }
756
+ return {
757
+ result: { status: "published", service: facade },
758
+ owner: createOwner(coordinator, frame),
759
+ };
760
+ }
761
+
762
+ export function publishRouteResolver(
763
+ publication: Pick<RouteResolverService, "resolve"> | RouteResolverService,
764
+ ): RouteResolverPublicationResult {
765
+ return publishRouteResolverCore(publication).result;
766
+ }
767
+
768
+ /**
769
+ * Session-scoped publication used by the extension factory. Returns the same
770
+ * public publication result plus an idempotent, synchronous, non-throwing
771
+ * `dispose()` that revokes this generation's live target on `session_shutdown`,
772
+ * before Pi invalidates the extension context. Not exported from the package
773
+ * root.
774
+ */
775
+ export function publishRouteResolverForSession(
776
+ publication: Pick<RouteResolverService, "resolve"> | RouteResolverService,
777
+ ): RouteResolverSessionPublication {
778
+ const { result, owner } = publishRouteResolverCore(publication);
779
+ return { ...result, dispose: () => owner.dispose() };
780
+ }
781
+
782
+ export function lookupRouteResolver(): RouteResolverLookup {
783
+ const existing = ownRegistryValue();
784
+ if (!existing.present) return { status: "absent" };
785
+ if (!existing.readable || !serviceLike(existing.value)) {
786
+ return { status: "incompatible" };
787
+ }
788
+ return { status: "available", service: existing.value };
789
+ }