@xenosystem/agent-cli 0.5.106 → 0.5.108

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 (77) hide show
  1. package/dist/cli.js +1 -1
  2. package/dist/index.js +1916 -1903
  3. package/dist/metafile-esm.json +1 -1
  4. package/dist/sdk-build-identity.json +8 -8
  5. package/native/xeno-pty/prebuilds/win32-x64/manifest.json +18 -18
  6. package/node_modules/@xeno-corporation/account/LICENSE +39 -0
  7. package/node_modules/@xeno-corporation/account/README.md +211 -0
  8. package/node_modules/@xeno-corporation/account/dist/chunk-FELZILY4.mjs +60 -0
  9. package/node_modules/@xeno-corporation/account/dist/chunk-FELZILY4.mjs.map +1 -0
  10. package/node_modules/@xeno-corporation/account/dist/index.d.mts +883 -0
  11. package/node_modules/@xeno-corporation/account/dist/index.d.ts +883 -0
  12. package/node_modules/@xeno-corporation/account/dist/index.js +2301 -0
  13. package/node_modules/@xeno-corporation/account/dist/index.js.map +1 -0
  14. package/node_modules/@xeno-corporation/account/dist/index.mjs +2191 -0
  15. package/node_modules/@xeno-corporation/account/dist/index.mjs.map +1 -0
  16. package/node_modules/@xeno-corporation/account/dist/node.d.mts +35 -0
  17. package/node_modules/@xeno-corporation/account/dist/node.d.ts +35 -0
  18. package/node_modules/@xeno-corporation/account/dist/node.js +715 -0
  19. package/node_modules/@xeno-corporation/account/dist/node.js.map +1 -0
  20. package/node_modules/@xeno-corporation/account/dist/node.mjs +645 -0
  21. package/node_modules/@xeno-corporation/account/dist/node.mjs.map +1 -0
  22. package/node_modules/@xeno-corporation/account/dist/tokenStore-CnIo2ILq.d.mts +47 -0
  23. package/node_modules/@xeno-corporation/account/dist/tokenStore-CnIo2ILq.d.ts +47 -0
  24. package/node_modules/@xeno-corporation/account/package.json +60 -0
  25. package/node_modules/zod/LICENSE +21 -0
  26. package/node_modules/zod/README.md +3028 -0
  27. package/node_modules/zod/index.d.ts +2 -0
  28. package/node_modules/zod/lib/ZodError.d.ts +164 -0
  29. package/node_modules/zod/lib/ZodError.js +137 -0
  30. package/node_modules/zod/lib/__tests__/Mocker.d.ts +17 -0
  31. package/node_modules/zod/lib/__tests__/Mocker.js +57 -0
  32. package/node_modules/zod/lib/benchmarks/datetime.d.ts +5 -0
  33. package/node_modules/zod/lib/benchmarks/datetime.js +54 -0
  34. package/node_modules/zod/lib/benchmarks/discriminatedUnion.d.ts +5 -0
  35. package/node_modules/zod/lib/benchmarks/discriminatedUnion.js +79 -0
  36. package/node_modules/zod/lib/benchmarks/index.d.ts +1 -0
  37. package/node_modules/zod/lib/benchmarks/index.js +59 -0
  38. package/node_modules/zod/lib/benchmarks/ipv4.d.ts +5 -0
  39. package/node_modules/zod/lib/benchmarks/ipv4.js +54 -0
  40. package/node_modules/zod/lib/benchmarks/object.d.ts +5 -0
  41. package/node_modules/zod/lib/benchmarks/object.js +70 -0
  42. package/node_modules/zod/lib/benchmarks/primitives.d.ts +5 -0
  43. package/node_modules/zod/lib/benchmarks/primitives.js +170 -0
  44. package/node_modules/zod/lib/benchmarks/realworld.d.ts +5 -0
  45. package/node_modules/zod/lib/benchmarks/realworld.js +56 -0
  46. package/node_modules/zod/lib/benchmarks/string.d.ts +5 -0
  47. package/node_modules/zod/lib/benchmarks/string.js +55 -0
  48. package/node_modules/zod/lib/benchmarks/union.d.ts +5 -0
  49. package/node_modules/zod/lib/benchmarks/union.js +79 -0
  50. package/node_modules/zod/lib/errors.d.ts +5 -0
  51. package/node_modules/zod/lib/errors.js +17 -0
  52. package/node_modules/zod/lib/external.d.ts +6 -0
  53. package/node_modules/zod/lib/external.js +22 -0
  54. package/node_modules/zod/lib/helpers/enumUtil.d.ts +8 -0
  55. package/node_modules/zod/lib/helpers/enumUtil.js +2 -0
  56. package/node_modules/zod/lib/helpers/errorUtil.d.ts +9 -0
  57. package/node_modules/zod/lib/helpers/errorUtil.js +8 -0
  58. package/node_modules/zod/lib/helpers/parseUtil.d.ts +78 -0
  59. package/node_modules/zod/lib/helpers/parseUtil.js +125 -0
  60. package/node_modules/zod/lib/helpers/partialUtil.d.ts +8 -0
  61. package/node_modules/zod/lib/helpers/partialUtil.js +2 -0
  62. package/node_modules/zod/lib/helpers/typeAliases.d.ts +2 -0
  63. package/node_modules/zod/lib/helpers/typeAliases.js +2 -0
  64. package/node_modules/zod/lib/helpers/util.d.ts +82 -0
  65. package/node_modules/zod/lib/helpers/util.js +142 -0
  66. package/node_modules/zod/lib/index.d.ts +4 -0
  67. package/node_modules/zod/lib/index.js +33 -0
  68. package/node_modules/zod/lib/index.mjs +4382 -0
  69. package/node_modules/zod/lib/index.umd.js +4497 -0
  70. package/node_modules/zod/lib/locales/en.d.ts +3 -0
  71. package/node_modules/zod/lib/locales/en.js +129 -0
  72. package/node_modules/zod/lib/standard-schema.d.ts +102 -0
  73. package/node_modules/zod/lib/standard-schema.js +2 -0
  74. package/node_modules/zod/lib/types.d.ts +1062 -0
  75. package/node_modules/zod/lib/types.js +3823 -0
  76. package/node_modules/zod/package.json +122 -0
  77. package/package.json +2 -2
@@ -0,0 +1,2191 @@
1
+ import {
2
+ AccountError,
3
+ __name,
4
+ codeForStatus,
5
+ normalizeDpopHtu
6
+ } from "./chunk-FELZILY4.mjs";
7
+
8
+ // src/types.ts
9
+ import { z } from "zod";
10
+ var MICRO_PER_CREDIT = 1e6;
11
+ var surfaceSchema = z.string().min(2).max(64).regex(/^xeno[_a-z0-9]+$/, "surface must look like xeno_post");
12
+ var accountSchema = z.object({
13
+ id: z.string().min(1),
14
+ email: z.string().email(),
15
+ username: z.string().nullable().default(null),
16
+ displayName: z.string().nullable().default(null),
17
+ avatarUrl: z.string().url().nullable().default(null),
18
+ emailVerified: z.boolean().default(false),
19
+ /** Linked app surfaces for this account (unified-account view). */
20
+ linkedSurfaces: z.array(surfaceSchema).default([])
21
+ });
22
+ var tokenSetSchema = z.object({
23
+ accessToken: z.string().min(1),
24
+ /** Opaque or JWT; the SDK treats it as opaque. */
25
+ tokenType: z.string().default("Bearer"),
26
+ expiresAt: z.coerce.date().nullable().default(null),
27
+ refreshToken: z.string().nullable().default(null),
28
+ /** OIDC ID token (authentication only — never sent to resource APIs). */
29
+ idToken: z.string().nullable().default(null),
30
+ scope: z.array(z.string()).default([]),
31
+ /** Session id claim, the join key for back-channel logout. */
32
+ sid: z.string().nullable().default(null),
33
+ /** Signature-verified claims; never populated by unverified decoding. */
34
+ accessClaims: z.record(z.unknown()).nullable().default(null),
35
+ idClaims: z.record(z.unknown()).nullable().default(null)
36
+ });
37
+ var oauthTokenResponseSchema = z.object({
38
+ access_token: z.string(),
39
+ token_type: z.string().default("Bearer"),
40
+ expires_in: z.number().optional(),
41
+ refresh_token: z.string().optional(),
42
+ id_token: z.string().optional(),
43
+ scope: z.string().optional()
44
+ });
45
+ var balanceSchema = z.object({
46
+ /** Settled balance. */
47
+ postedMicro: z.number().int(),
48
+ /** Settled minus active holds. */
49
+ pendingMicro: z.number().int(),
50
+ /** Most conservative spendable amount — gate approvals on this. */
51
+ availableMicro: z.number().int(),
52
+ currency: z.literal("credits").default("credits"),
53
+ asOf: z.coerce.date().default(() => /* @__PURE__ */ new Date(0))
54
+ });
55
+ var meterAggregationSchema = z.enum([
56
+ "sum",
57
+ "count",
58
+ "max",
59
+ "min",
60
+ "unique_count",
61
+ "latest"
62
+ ]);
63
+ var usageEventSchema = z.object({
64
+ surface: surfaceSchema,
65
+ /** Stable, producer-supplied dedup id. */
66
+ transactionId: z.string().min(8).max(255),
67
+ operation: z.string().min(1),
68
+ model: z.string().nullable().default(null),
69
+ provider: z.string().nullable().default(null),
70
+ /** Pre-computed cost in micro-credits, if the caller priced it. */
71
+ costMicro: z.number().int().nonnegative().nullable().default(null),
72
+ inputTokens: z.number().int().nonnegative().nullable().default(null),
73
+ outputTokens: z.number().int().nonnegative().nullable().default(null),
74
+ /** Arbitrary attribution dimensions (project, workspace, …). */
75
+ dimensions: z.record(z.string()).default({}),
76
+ occurredAt: z.coerce.date().optional()
77
+ });
78
+ var usageResultSchema = z.object({
79
+ accepted: z.boolean(),
80
+ /** True when the event was a duplicate (idempotent no-op). */
81
+ duplicate: z.boolean().default(false),
82
+ costMicro: z.number().int().nonnegative(),
83
+ transactionId: z.string(),
84
+ balance: balanceSchema.nullable().default(null)
85
+ });
86
+ var holdRequestSchema = z.object({
87
+ surface: surfaceSchema,
88
+ /** Idempotency key for the hold itself. */
89
+ holdId: z.string().min(8).max(255),
90
+ amountMicro: z.number().int().positive(),
91
+ operation: z.string().min(1),
92
+ expiresInSeconds: z.number().int().positive().max(86400).default(900),
93
+ dimensions: z.record(z.string()).default({})
94
+ });
95
+ var holdSchema = z.object({
96
+ holdId: z.string(),
97
+ state: z.enum([
98
+ "held",
99
+ "settled",
100
+ "voided",
101
+ "expired"
102
+ ]),
103
+ amountMicro: z.number().int().nonnegative(),
104
+ settledMicro: z.number().int().nonnegative().default(0),
105
+ balance: balanceSchema.nullable().default(null)
106
+ });
107
+ var authzCheckSchema = z.object({
108
+ /** e.g. `user:abc` or `agent:scout`. */
109
+ subject: z.string().min(1),
110
+ relation: z.string().min(1),
111
+ /** e.g. `workspace:acme` or `post:123`. */
112
+ object: z.string().min(1),
113
+ context: z.record(z.unknown()).default({})
114
+ });
115
+ var authzResultSchema = z.object({
116
+ allowed: z.boolean(),
117
+ /** Optional reason/trace for debugging (never authoritative). */
118
+ reason: z.string().nullable().default(null)
119
+ });
120
+ function toMicro(credits) {
121
+ return Math.round(credits * MICRO_PER_CREDIT);
122
+ }
123
+ __name(toMicro, "toMicro");
124
+ function fromMicro(micro) {
125
+ return micro / MICRO_PER_CREDIT;
126
+ }
127
+ __name(fromMicro, "fromMicro");
128
+
129
+ // src/jwt-json.ts
130
+ function parseJwtObject(source) {
131
+ if (source.length > 16384) throw new SyntaxError("JWT JSON bound");
132
+ let offset = 0;
133
+ const whitespace = /* @__PURE__ */ __name(() => {
134
+ while (offset < source.length && /[ \t\r\n]/.test(source[offset] ?? "")) offset++;
135
+ }, "whitespace");
136
+ const invalid2 = /* @__PURE__ */ __name(() => {
137
+ throw new SyntaxError("Ambiguous JWT JSON");
138
+ }, "invalid");
139
+ const string = /* @__PURE__ */ __name(() => {
140
+ const start = offset++;
141
+ while (offset < source.length) {
142
+ const current = source[offset++];
143
+ if (current === '"') return JSON.parse(source.slice(start, offset));
144
+ if (current === "\\") offset++;
145
+ }
146
+ return invalid2();
147
+ }, "string");
148
+ const value = /* @__PURE__ */ __name((depth) => {
149
+ if (depth > 8) invalid2();
150
+ whitespace();
151
+ const opening = source[offset];
152
+ if (opening === '"') {
153
+ string();
154
+ return;
155
+ }
156
+ if (opening === "{" || opening === "[") {
157
+ const close = opening === "{" ? "}" : "]";
158
+ offset++;
159
+ whitespace();
160
+ if (source[offset] === close) {
161
+ offset++;
162
+ return;
163
+ }
164
+ const names = /* @__PURE__ */ new Set();
165
+ let count = 0;
166
+ while (offset < source.length) {
167
+ if (++count > 256) invalid2();
168
+ if (opening === "{") {
169
+ whitespace();
170
+ if (source[offset] !== '"') invalid2();
171
+ const key = string();
172
+ if (names.has(key) || key === "__proto__" || key === "constructor" || key === "prototype") invalid2();
173
+ names.add(key);
174
+ whitespace();
175
+ if (source[offset++] !== ":") invalid2();
176
+ }
177
+ value(depth + 1);
178
+ whitespace();
179
+ if (source[offset] === close) {
180
+ offset++;
181
+ return;
182
+ }
183
+ if (source[offset++] !== ",") invalid2();
184
+ }
185
+ return invalid2();
186
+ }
187
+ const literal = /^(?:true|false|null|-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?)/.exec(source.slice(offset));
188
+ if (!literal) return invalid2();
189
+ offset += literal[0].length;
190
+ }, "value");
191
+ value(0);
192
+ whitespace();
193
+ if (offset !== source.length) invalid2();
194
+ const parsed = JSON.parse(source, (_key, item) => {
195
+ if (typeof item === "number" && !Number.isFinite(item)) invalid2();
196
+ return item;
197
+ });
198
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) invalid2();
199
+ return parsed;
200
+ }
201
+ __name(parseJwtObject, "parseJwtObject");
202
+
203
+ // src/http.ts
204
+ function authorizationArgs(auth) {
205
+ return typeof auth === "function" ? {
206
+ authorization: auth
207
+ } : typeof auth === "string" ? {
208
+ token: auth,
209
+ tokenType: "Bearer"
210
+ } : {
211
+ token: auth.token,
212
+ tokenType: auth.tokenType,
213
+ headers: auth.headers
214
+ };
215
+ }
216
+ __name(authorizationArgs, "authorizationArgs");
217
+ var Transport = class {
218
+ static {
219
+ __name(this, "Transport");
220
+ }
221
+ fetchImpl;
222
+ timeoutMs;
223
+ maxRetries;
224
+ userAgent;
225
+ maxResponseBytes;
226
+ constructor(opts = {}) {
227
+ this.fetchImpl = opts.fetchImpl ?? globalThis.fetch;
228
+ this.timeoutMs = opts.timeoutMs ?? 8e3;
229
+ this.maxRetries = opts.maxRetries ?? 2;
230
+ this.userAgent = opts.userAgent ?? "xeno-account-client/0.1";
231
+ this.maxResponseBytes = opts.maxResponseBytes ?? 1024 * 1024;
232
+ if (!Number.isSafeInteger(this.timeoutMs) || this.timeoutMs < 1 || this.timeoutMs > 12e4 || !Number.isSafeInteger(this.maxResponseBytes) || this.maxResponseBytes < 1 || this.maxResponseBytes > 8 * 1024 * 1024 || !Number.isSafeInteger(this.maxRetries) || this.maxRetries < 0 || this.maxRetries > 10) throw new TypeError("Account transport deadline and response bound are invalid");
233
+ }
234
+ /** Perform a request and return parsed JSON (or null for empty bodies). */
235
+ async request(args) {
236
+ let body = args.body;
237
+ const headers = {
238
+ ...args.headers
239
+ };
240
+ if (body !== void 0 && typeof body !== "string") {
241
+ try {
242
+ body = JSON.stringify(body);
243
+ } catch {
244
+ throw new AccountError({
245
+ code: "BAD_REQUEST",
246
+ message: "Account request body cannot be serialized"
247
+ });
248
+ }
249
+ headers["content-type"] = "application/json";
250
+ }
251
+ args = Object.freeze({
252
+ ...args,
253
+ body,
254
+ headers: Object.freeze(headers)
255
+ });
256
+ const isMutation = args.method !== "GET";
257
+ const staticProof = args.tokenType === "DPoP" || Object.keys(args.headers ?? {}).some((name) => name.toLowerCase() === "dpop");
258
+ const retrySafe = (!isMutation || Boolean(args.idempotencyKey)) && (!staticProof || Boolean(args.authorization));
259
+ const attempts = retrySafe ? this.maxRetries + 1 : 1;
260
+ let lastErr = null;
261
+ for (let attempt = 0; attempt < attempts; attempt += 1) {
262
+ try {
263
+ return await this.once(args);
264
+ } catch (err) {
265
+ const ae = err instanceof AccountError ? err : new AccountError({
266
+ code: "UNREACHABLE",
267
+ message: "Account request failed before a verified response was received"
268
+ });
269
+ lastErr = ae;
270
+ if (!ae.retryable || attempt === attempts - 1) throw ae;
271
+ await sleep(backoffMs(attempt, ae.retryAfterMs), args.signal);
272
+ }
273
+ }
274
+ throw lastErr ?? new AccountError({
275
+ code: "PLATFORM_ERROR",
276
+ message: "request failed"
277
+ });
278
+ }
279
+ async once(args) {
280
+ const controller = new AbortController();
281
+ const callerAborted = /* @__PURE__ */ __name(() => controller.abort(), "callerAborted");
282
+ args.signal?.addEventListener("abort", callerAborted, {
283
+ once: true
284
+ });
285
+ if (args.signal?.aborted) callerAborted();
286
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
287
+ let res;
288
+ try {
289
+ if (args.signal?.aborted) throw cancellationError();
290
+ let auth;
291
+ if (args.authorization) {
292
+ try {
293
+ auth = await beforeAbort(args.authorization(args.method, args.url), controller.signal);
294
+ } catch (error) {
295
+ throw new AccountError({
296
+ code: error instanceof AccountError ? error.code : "UNREACHABLE",
297
+ message: "Account request authorization unavailable",
298
+ ...error instanceof AccountError ? {
299
+ httpStatus: error.httpStatus,
300
+ retryable: error.retryable
301
+ } : {}
302
+ });
303
+ }
304
+ if (!auth || typeof auth.token !== "string" || !auth.token || auth.token.length > 16384 || /[\r\n]/.test(auth.token) || ![
305
+ "Bearer",
306
+ "DPoP"
307
+ ].includes(auth.tokenType)) throw new AccountError({
308
+ code: "INVALID_TOKEN",
309
+ message: "Account request authorization invalid"
310
+ });
311
+ const proofHeaders = Object.entries(auth.headers ?? {}).filter(([name]) => name.toLowerCase() === "dpop");
312
+ if (auth.tokenType === "DPoP" && (proofHeaders.length !== 1 || typeof proofHeaders[0]?.[1] !== "string" || !proofHeaders[0][1] || proofHeaders[0][1].length > 16384 || /[\r\n]/.test(proofHeaders[0][1]))) throw new AccountError({
313
+ code: "INVALID_TOKEN",
314
+ message: "Account request proof invalid"
315
+ });
316
+ }
317
+ const supplied = Object.fromEntries(Object.entries(args.headers ?? {}).map(([key, value]) => [
318
+ key.toLowerCase(),
319
+ value
320
+ ]));
321
+ if (auth) {
322
+ delete supplied.authorization;
323
+ delete supplied.dpop;
324
+ }
325
+ const headers = {
326
+ accept: "application/json",
327
+ "user-agent": this.userAgent,
328
+ ...supplied,
329
+ ...Object.fromEntries(Object.entries(auth?.headers ?? {}).map(([key, value]) => [
330
+ key.toLowerCase(),
331
+ value
332
+ ]))
333
+ };
334
+ const token = auth?.token ?? args.token;
335
+ if (token) headers["authorization"] = `${auth?.tokenType ?? args.tokenType ?? "Bearer"} ${token}`;
336
+ if (args.idempotencyKey) headers["idempotency-key"] = args.idempotencyKey;
337
+ let payload;
338
+ if (typeof args.body === "string") {
339
+ payload = args.body;
340
+ } else if (args.body !== void 0) {
341
+ headers["content-type"] = "application/json";
342
+ try {
343
+ payload = JSON.stringify(args.body);
344
+ } catch {
345
+ throw new AccountError({
346
+ code: "BAD_REQUEST",
347
+ message: "Account request body cannot be serialized"
348
+ });
349
+ }
350
+ }
351
+ if (controller.signal.aborted) throw new Error("Account response deadline");
352
+ res = await fetchBeforeAbort(this.fetchImpl, args.url, {
353
+ method: args.method,
354
+ headers,
355
+ body: payload,
356
+ signal: controller.signal,
357
+ redirect: "error",
358
+ cache: "no-store",
359
+ credentials: "omit"
360
+ }, controller.signal);
361
+ if (!res.ok) {
362
+ const code = codeForStatus(res.status);
363
+ const oauthError = args.oauthErrorResponse ? await readOAuthError(res, controller.signal) : null;
364
+ throw new AccountError({
365
+ code,
366
+ message: `${args.method} ${safeUrl(args.url)} -> ${res.status}`,
367
+ httpStatus: res.status,
368
+ retryAfterMs: parseRetryAfter(res.headers.get("retry-after")),
369
+ oauthError
370
+ });
371
+ }
372
+ if (res.status === 204 || !res.body) return null;
373
+ const text = await readBoundedText(res, controller.signal, this.maxResponseBytes);
374
+ if (!text) return null;
375
+ try {
376
+ return JSON.parse(text);
377
+ } catch {
378
+ throw new AccountError({
379
+ code: "INVALID_RESPONSE",
380
+ message: `non-JSON response from ${safeUrl(args.url)}`
381
+ });
382
+ }
383
+ } catch (err) {
384
+ if (args.signal?.aborted) throw cancellationError();
385
+ if (err instanceof AccountError) throw err;
386
+ throw new AccountError({
387
+ code: "UNREACHABLE",
388
+ message: `request to ${safeUrl(args.url)} failed`,
389
+ retryable: true
390
+ });
391
+ } finally {
392
+ controller.abort();
393
+ args.signal?.removeEventListener("abort", callerAborted);
394
+ clearTimeout(timer);
395
+ if (res) cancelBody(res);
396
+ }
397
+ }
398
+ };
399
+ function cancelBody(response) {
400
+ void response.body?.cancel().catch(() => {
401
+ });
402
+ }
403
+ __name(cancelBody, "cancelBody");
404
+ function beforeAbort(pending, signal) {
405
+ return new Promise((resolve, reject) => {
406
+ const aborted = /* @__PURE__ */ __name(() => {
407
+ signal.removeEventListener("abort", aborted);
408
+ reject(new Error("Account response deadline"));
409
+ }, "aborted");
410
+ if (signal.aborted) {
411
+ void pending.catch(() => {
412
+ });
413
+ aborted();
414
+ return;
415
+ }
416
+ signal.addEventListener("abort", aborted, {
417
+ once: true
418
+ });
419
+ pending.then((value) => {
420
+ signal.removeEventListener("abort", aborted);
421
+ if (signal.aborted) aborted();
422
+ else resolve(value);
423
+ }, (error) => {
424
+ signal.removeEventListener("abort", aborted);
425
+ reject(error);
426
+ });
427
+ });
428
+ }
429
+ __name(beforeAbort, "beforeAbort");
430
+ function fetchBeforeAbort(fetchImpl, url, init, signal) {
431
+ const pending = Promise.resolve(fetchImpl(url, init));
432
+ void pending.then((response) => {
433
+ if (signal.aborted) cancelBody(response);
434
+ }, () => {
435
+ });
436
+ return beforeAbort(pending, signal);
437
+ }
438
+ __name(fetchBeforeAbort, "fetchBeforeAbort");
439
+ async function readBoundedText(response, signal, maxBytes) {
440
+ const reader = response.body?.getReader();
441
+ if (!reader) return "";
442
+ let complete = false;
443
+ try {
444
+ const decoder = new TextDecoder("utf-8", {
445
+ fatal: true
446
+ });
447
+ let text = "", size = 0;
448
+ while (true) {
449
+ const chunk = await beforeAbort(reader.read(), signal);
450
+ if (chunk.done) {
451
+ complete = true;
452
+ break;
453
+ }
454
+ size += chunk.value.byteLength;
455
+ if (size > maxBytes) throw new AccountError({
456
+ code: "INVALID_RESPONSE",
457
+ message: "Account response exceeds configured bound"
458
+ });
459
+ try {
460
+ text += decoder.decode(chunk.value, {
461
+ stream: true
462
+ });
463
+ } catch {
464
+ throw new AccountError({
465
+ code: "INVALID_RESPONSE",
466
+ message: "Account response is not valid UTF-8"
467
+ });
468
+ }
469
+ }
470
+ try {
471
+ return text + decoder.decode();
472
+ } catch {
473
+ throw new AccountError({
474
+ code: "INVALID_RESPONSE",
475
+ message: "Account response is not valid UTF-8"
476
+ });
477
+ }
478
+ } finally {
479
+ if (!complete) void reader.cancel().catch(() => {
480
+ });
481
+ reader.releaseLock();
482
+ }
483
+ }
484
+ __name(readBoundedText, "readBoundedText");
485
+ async function readOAuthError(response, signal) {
486
+ try {
487
+ const body = parseJwtObject(await readBoundedText(response, signal, 4096));
488
+ const code = body.error;
489
+ return typeof code === "string" && [
490
+ "invalid_grant",
491
+ "invalid_client",
492
+ "invalid_scope",
493
+ "invalid_request",
494
+ "unauthorized_client",
495
+ "unsupported_grant_type",
496
+ "authorization_pending",
497
+ "slow_down",
498
+ "access_denied",
499
+ "expired_token",
500
+ "use_dpop_nonce",
501
+ "invalid_dpop_proof"
502
+ ].includes(code) ? code : null;
503
+ } catch {
504
+ return null;
505
+ }
506
+ }
507
+ __name(readOAuthError, "readOAuthError");
508
+ function backoffMs(attempt, retryAfterMs) {
509
+ if (retryAfterMs && retryAfterMs > 0) return Math.min(retryAfterMs, 1e4);
510
+ return Math.min(200 * (2 ** attempt + (attempt === 0 ? 0 : 1)), 5e3);
511
+ }
512
+ __name(backoffMs, "backoffMs");
513
+ function parseRetryAfter(header) {
514
+ if (!header) return null;
515
+ const seconds = Number(header);
516
+ return Number.isFinite(seconds) ? seconds * 1e3 : null;
517
+ }
518
+ __name(parseRetryAfter, "parseRetryAfter");
519
+ function cancellationError() {
520
+ return new AccountError({
521
+ code: "BAD_REQUEST",
522
+ message: "Account request was cancelled; outcome may be unconfirmed",
523
+ retryable: false
524
+ });
525
+ }
526
+ __name(cancellationError, "cancellationError");
527
+ function sleep(ms, signal) {
528
+ return new Promise((resolve, reject) => {
529
+ const timer = setTimeout(done, ms);
530
+ function cleanup() {
531
+ clearTimeout(timer);
532
+ signal?.removeEventListener("abort", aborted);
533
+ }
534
+ __name(cleanup, "cleanup");
535
+ function done() {
536
+ cleanup();
537
+ resolve();
538
+ }
539
+ __name(done, "done");
540
+ function aborted() {
541
+ cleanup();
542
+ reject(cancellationError());
543
+ }
544
+ __name(aborted, "aborted");
545
+ signal?.addEventListener("abort", aborted, {
546
+ once: true
547
+ });
548
+ if (signal?.aborted) aborted();
549
+ });
550
+ }
551
+ __name(sleep, "sleep");
552
+ function safeUrl(url) {
553
+ try {
554
+ const u = new URL(url);
555
+ return u.origin;
556
+ } catch {
557
+ return "[invalid account URL]";
558
+ }
559
+ }
560
+ __name(safeUrl, "safeUrl");
561
+
562
+ // src/account.ts
563
+ var AccountClient = class {
564
+ static {
565
+ __name(this, "AccountClient");
566
+ }
567
+ base;
568
+ transport;
569
+ constructor(base, transport) {
570
+ this.base = base;
571
+ this.transport = transport;
572
+ }
573
+ /** Resolve a token to its account, or throw an AccountError. */
574
+ async me(auth) {
575
+ let raw;
576
+ try {
577
+ raw = await this.transport.request({
578
+ method: "GET",
579
+ url: `${this.base}/api/v2/me`,
580
+ ...authorizationArgs(auth)
581
+ });
582
+ } catch (err) {
583
+ const isDpop = typeof auth === "object" && auth.tokenType === "DPoP";
584
+ if (err instanceof AccountError && err.code === "NOT_FOUND" && !isDpop) {
585
+ raw = await this.transport.request({
586
+ method: "GET",
587
+ url: `${this.base}/api/auth/me`,
588
+ ...authorizationArgs(auth)
589
+ });
590
+ } else {
591
+ throw err;
592
+ }
593
+ }
594
+ const account = normalize(raw);
595
+ if (!account) {
596
+ throw new AccountError({
597
+ code: "INVALID_RESPONSE",
598
+ message: "/me returned an unrecognised shape"
599
+ });
600
+ }
601
+ return account;
602
+ }
603
+ };
604
+ function normalize(body) {
605
+ if (!body || typeof body !== "object") return null;
606
+ const root = body;
607
+ const user = root["user"] ?? root["data"] ?? root;
608
+ if (!user || typeof user !== "object") return null;
609
+ const id = str(user["id"]);
610
+ const email = str(user["email"]);
611
+ if (!id || !email) return null;
612
+ const linked = Array.isArray(user["linkedSurfaces"]) ? [
613
+ ...new Set(user["linkedSurfaces"].map(normalizeSurface).filter(isSurface))
614
+ ] : [];
615
+ const parsed = accountSchema.safeParse({
616
+ id,
617
+ email,
618
+ username: str(user["username"]) ?? null,
619
+ displayName: str(user["display_name"]) ?? str(user["displayName"]) ?? null,
620
+ avatarUrl: str(user["avatar_url"]) ?? str(user["avatarUrl"]) ?? null,
621
+ emailVerified: Boolean(user["email_verified"] ?? user["emailVerified"] ?? false),
622
+ linkedSurfaces: linked
623
+ });
624
+ return parsed.success ? parsed.data : null;
625
+ }
626
+ __name(normalize, "normalize");
627
+ function str(v) {
628
+ return typeof v === "string" && v.length > 0 ? v : void 0;
629
+ }
630
+ __name(str, "str");
631
+ function normalizeSurface(value) {
632
+ if (typeof value !== "string") return void 0;
633
+ const parsed = surfaceSchema.safeParse(value.trim().toLowerCase().replace(/-/g, "_"));
634
+ return parsed.success ? parsed.data : void 0;
635
+ }
636
+ __name(normalizeSurface, "normalizeSurface");
637
+ function isSurface(value) {
638
+ return value !== void 0;
639
+ }
640
+ __name(isSurface, "isSurface");
641
+
642
+ // src/ledger.ts
643
+ var LedgerClient = class {
644
+ static {
645
+ __name(this, "LedgerClient");
646
+ }
647
+ apiBase;
648
+ transport;
649
+ constructor(apiBase, transport) {
650
+ this.apiBase = apiBase;
651
+ this.transport = transport;
652
+ }
653
+ /** Posted / pending / available balance for the token's account. */
654
+ async getBalance(auth) {
655
+ const raw = await this.transport.request({
656
+ method: "GET",
657
+ url: `${this.apiBase}/api/v2/ledger/balance`,
658
+ ...authorizationArgs(auth)
659
+ });
660
+ return parse(balanceSchema, raw);
661
+ }
662
+ /**
663
+ * Record a usage event (the meter). Idempotent on `transactionId`; a duplicate
664
+ * returns `{ duplicate: true }` and does not charge again.
665
+ */
666
+ async recordUsage(auth, event) {
667
+ const e = usageEventSchema.parse(event);
668
+ const raw = await this.transport.request({
669
+ method: "POST",
670
+ url: `${this.apiBase}/api/v2/ledger/usage`,
671
+ ...authorizationArgs(auth),
672
+ body: e,
673
+ idempotencyKey: e.transactionId
674
+ });
675
+ return parse(usageResultSchema, raw);
676
+ }
677
+ /**
678
+ * Reserve credits for a per-use action (phase 1 of two-phase spend). Rejected
679
+ * with `INSUFFICIENT_CREDITS` if `available < amount`. Idempotent on `holdId`.
680
+ */
681
+ async hold(auth, req) {
682
+ const r = holdRequestSchema.parse(req);
683
+ const raw = await this.transport.request({
684
+ method: "POST",
685
+ url: `${this.apiBase}/api/v2/ledger/holds`,
686
+ ...authorizationArgs(auth),
687
+ body: r,
688
+ idempotencyKey: r.holdId
689
+ });
690
+ return parse(holdSchema, raw);
691
+ }
692
+ /**
693
+ * Settle a hold for the actual cost (phase 2). Posting less than the held
694
+ * amount auto-restores the remainder. Idempotent on `holdId`.
695
+ */
696
+ async settle(auth, holdId, actualCostMicro) {
697
+ const raw = await this.transport.request({
698
+ method: "POST",
699
+ url: `${this.apiBase}/api/v2/ledger/holds/${encodeURIComponent(holdId)}/settle`,
700
+ ...authorizationArgs(auth),
701
+ body: {
702
+ actualCostMicro
703
+ },
704
+ idempotencyKey: `${holdId}:settle`
705
+ });
706
+ return parse(holdSchema, raw);
707
+ }
708
+ /** Release a hold without charging (failure/cancel). Idempotent on `holdId`. */
709
+ async void(auth, holdId) {
710
+ const raw = await this.transport.request({
711
+ method: "POST",
712
+ url: `${this.apiBase}/api/v2/ledger/holds/${encodeURIComponent(holdId)}/void`,
713
+ ...authorizationArgs(auth),
714
+ body: {},
715
+ idempotencyKey: `${holdId}:void`
716
+ });
717
+ return parse(holdSchema, raw);
718
+ }
719
+ };
720
+ function parse(schema, raw) {
721
+ const r = schema.safeParse(raw);
722
+ if (!r.success || r.data === void 0) {
723
+ throw new AccountError({
724
+ code: "INVALID_RESPONSE",
725
+ message: "ledger returned an unrecognised shape"
726
+ });
727
+ }
728
+ return r.data;
729
+ }
730
+ __name(parse, "parse");
731
+
732
+ // src/authz.ts
733
+ var AuthzClient = class {
734
+ static {
735
+ __name(this, "AuthzClient");
736
+ }
737
+ apiBase;
738
+ transport;
739
+ constructor(apiBase, transport) {
740
+ this.apiBase = apiBase;
741
+ this.transport = transport;
742
+ }
743
+ async check(auth, check) {
744
+ const raw = await this.transport.request({
745
+ method: "POST",
746
+ url: `${this.apiBase}/api/v2/authz/check`,
747
+ ...authorizationArgs(auth),
748
+ body: check
749
+ });
750
+ const parsed = authzResultSchema.safeParse(raw);
751
+ if (!parsed.success) {
752
+ throw new AccountError({
753
+ code: "INVALID_RESPONSE",
754
+ message: "authz returned an unrecognised shape"
755
+ });
756
+ }
757
+ return parsed.data;
758
+ }
759
+ };
760
+
761
+ // src/verification.ts
762
+ var jwksCache = /* @__PURE__ */ new Map();
763
+ function bytes(value) {
764
+ const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
765
+ const bin = atob(normalized + "=".repeat((4 - normalized.length % 4) % 4));
766
+ return Uint8Array.from(bin, (char) => char.charCodeAt(0));
767
+ }
768
+ __name(bytes, "bytes");
769
+ function decode(token) {
770
+ const parts = token.split(".");
771
+ if (parts.length !== 3 || !parts[0] || !parts[1] || !parts[2]) throw invalid("malformed JWT");
772
+ try {
773
+ return {
774
+ header: JSON.parse(new TextDecoder().decode(bytes(parts[0]))),
775
+ payload: JSON.parse(new TextDecoder().decode(bytes(parts[1]))),
776
+ signingInput: new TextEncoder().encode(`${parts[0]}.${parts[1]}`),
777
+ signature: bytes(parts[2])
778
+ };
779
+ } catch {
780
+ throw invalid("malformed JWT");
781
+ }
782
+ }
783
+ __name(decode, "decode");
784
+ function invalid(message) {
785
+ return new AccountError({
786
+ code: "INVALID_RESPONSE",
787
+ message: `OIDC verification failed: ${message}`
788
+ });
789
+ }
790
+ __name(invalid, "invalid");
791
+ function audience(value, expected) {
792
+ return value === expected || Array.isArray(value) && value.includes(expected);
793
+ }
794
+ __name(audience, "audience");
795
+ async function fetchJwks(transport, issuer, jwksUrl, force = false) {
796
+ const url = jwksUrl ?? `${issuer.replace(/\/+$/, "")}/api/oauth2/jwks`;
797
+ const cached = jwksCache.get(url);
798
+ if (!force && cached && cached.expiresAt > Date.now()) return cached.keys;
799
+ const response = await transport.request({
800
+ method: "GET",
801
+ url
802
+ });
803
+ if (!Array.isArray(response.keys) || response.keys.length === 0) throw invalid("empty JWKS");
804
+ jwksCache.set(url, {
805
+ keys: response.keys,
806
+ expiresAt: Date.now() + 5 * 6e4
807
+ });
808
+ return response.keys;
809
+ }
810
+ __name(fetchJwks, "fetchJwks");
811
+ async function verifySignature(decoded, keys) {
812
+ if (decoded.header.alg !== "ES256" || typeof decoded.header.kid !== "string") return "invalid";
813
+ const jwk = keys.find((candidate) => candidate.kid === decoded.header.kid && candidate.kty === "EC" && candidate.crv === "P-256");
814
+ if (!jwk) return "unknown-kid";
815
+ try {
816
+ const key = await globalThis.crypto.subtle.importKey("jwk", jwk, {
817
+ name: "ECDSA",
818
+ namedCurve: "P-256"
819
+ }, false, [
820
+ "verify"
821
+ ]);
822
+ return await globalThis.crypto.subtle.verify({
823
+ name: "ECDSA",
824
+ hash: "SHA-256"
825
+ }, key, decoded.signature, decoded.signingInput) ? "valid" : "invalid";
826
+ } catch {
827
+ return "invalid";
828
+ }
829
+ }
830
+ __name(verifySignature, "verifySignature");
831
+ async function verifiedPayload(token, transport, issuer, jwksUrl) {
832
+ const decoded = decode(token);
833
+ let keys = await fetchJwks(transport, issuer, jwksUrl);
834
+ let result = await verifySignature(decoded, keys);
835
+ if (result === "unknown-kid") {
836
+ keys = await fetchJwks(transport, issuer, jwksUrl, true);
837
+ result = await verifySignature(decoded, keys);
838
+ }
839
+ if (result !== "valid") throw invalid("signature or algorithm");
840
+ return decoded;
841
+ }
842
+ __name(verifiedPayload, "verifiedPayload");
843
+ function temporal(payload, now) {
844
+ if (!Number.isInteger(payload.exp) || Number(payload.exp) <= now) throw invalid("token expired");
845
+ if (!Number.isInteger(payload.iat) || Number(payload.iat) > now + 60) throw invalid("invalid issued-at time");
846
+ if (payload.nbf !== void 0 && (!Number.isInteger(payload.nbf) || Number(payload.nbf) > now + 60)) throw invalid("token not yet valid");
847
+ }
848
+ __name(temporal, "temporal");
849
+ async function verifyAccessToken(args) {
850
+ const decoded = await verifiedPayload(args.token, args.transport, args.issuer, args.jwksUrl);
851
+ const p = decoded.payload;
852
+ const now = Math.floor(Date.now() / 1e3);
853
+ temporal(p, now);
854
+ if (decoded.header.typ !== "at+jwt" || p.typ !== "at+jwt" || p.iss !== args.issuer || !audience(p.aud, "xeno-api")) throw invalid("access-token profile");
855
+ if (p.client_id !== args.clientId || p.azp !== args.clientId) throw invalid("client binding");
856
+ if (typeof p.sub !== "string" || typeof p.sid !== "string" || !Number.isInteger(p.auth_epoch)) throw invalid("session claims");
857
+ if (!p.cnf || typeof p.cnf !== "object" || p.cnf.jkt !== args.dpopJkt) throw invalid("DPoP binding");
858
+ if (p.act !== void 0) {
859
+ const act = p.act;
860
+ if (act.client_id !== "xeno-hub" || typeof act.installation_id_hash !== "string" || !act.installation_id_hash) throw invalid("broker actor");
861
+ }
862
+ if (args.requireBrokerActor && p.act === void 0) throw invalid("missing broker actor");
863
+ const held = new Set(String(p.scope ?? "").split(/\s+/).filter(Boolean));
864
+ for (const scope of args.expectedScopes ?? []) if (!held.has(scope)) throw invalid(`missing scope ${scope}`);
865
+ return p;
866
+ }
867
+ __name(verifyAccessToken, "verifyAccessToken");
868
+ async function verifyIdToken(args) {
869
+ const decoded = await verifiedPayload(args.token, args.transport, args.issuer, args.jwksUrl);
870
+ const p = decoded.payload;
871
+ temporal(p, Math.floor(Date.now() / 1e3));
872
+ if (p.iss !== args.issuer || !audience(p.aud, args.clientId) || typeof p.sub !== "string" || typeof p.sid !== "string" || !Number.isInteger(p.auth_time)) throw invalid("ID-token profile");
873
+ if (args.expectedNonce !== void 0 && p.nonce !== args.expectedNonce) throw invalid("nonce mismatch");
874
+ if (args.expectedSubject !== void 0 && p.sub !== args.expectedSubject) throw invalid("subject mismatch");
875
+ return p;
876
+ }
877
+ __name(verifyIdToken, "verifyIdToken");
878
+ async function verifyLogoutToken(args) {
879
+ const decoded = await verifiedPayload(args.token, args.transport, args.issuer, args.jwksUrl);
880
+ const p = decoded.payload;
881
+ temporal(p, Math.floor(Date.now() / 1e3));
882
+ const event = "http://schemas.openid.net/event/backchannel-logout";
883
+ if (decoded.header.typ !== "logout+jwt" || p.iss !== args.issuer || !audience(p.aud, args.clientId)) throw invalid("logout-token profile");
884
+ if (typeof p.jti !== "string" || !p.jti || p.nonce !== void 0) throw invalid("logout-token claims");
885
+ if (typeof p.sid !== "string" && typeof p.sub !== "string") throw invalid("logout subject");
886
+ if (!p.events || typeof p.events !== "object" || !(event in p.events)) throw invalid("logout event");
887
+ return p;
888
+ }
889
+ __name(verifyLogoutToken, "verifyLogoutToken");
890
+
891
+ // src/oidc.ts
892
+ async function generatePkce() {
893
+ const verifierBytes = new Uint8Array(32);
894
+ globalThis.crypto.getRandomValues(verifierBytes);
895
+ const verifier = base64url(verifierBytes);
896
+ const digest = await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
897
+ return {
898
+ verifier,
899
+ challenge: base64url(new Uint8Array(digest))
900
+ };
901
+ }
902
+ __name(generatePkce, "generatePkce");
903
+ function generateState() {
904
+ const b = new Uint8Array(24);
905
+ globalThis.crypto.getRandomValues(b);
906
+ return base64url(b);
907
+ }
908
+ __name(generateState, "generateState");
909
+ function base64url(bytes2) {
910
+ let bin = "";
911
+ for (let i = 0; i < bytes2.length; i += 1) bin += String.fromCharCode(bytes2[i]);
912
+ const b64 = typeof btoa === "function" ? btoa(bin) : Buffer.from(bytes2).toString("base64");
913
+ return b64.replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
914
+ }
915
+ __name(base64url, "base64url");
916
+ function endpoints(cfg) {
917
+ const base = cfg.issuer.replace(/\/+$/, "");
918
+ return {
919
+ authorize: cfg.authorizeUrl ?? `${base}/api/oauth2/authorize`,
920
+ token: cfg.tokenUrl ?? `${base}/api/oauth2/token`,
921
+ device: cfg.deviceAuthorizationUrl ?? `${base}/api/oauth2/device_authorization`
922
+ };
923
+ }
924
+ __name(endpoints, "endpoints");
925
+ var DEFAULT_SCOPES = [
926
+ "openid",
927
+ "profile",
928
+ "email",
929
+ "ledger"
930
+ ];
931
+ function buildAuthorizeUrl(cfg, args) {
932
+ const redirectUri = args.redirectUri ?? cfg.redirectUri;
933
+ if (!redirectUri) {
934
+ throw new AccountError({
935
+ code: "BAD_REQUEST",
936
+ message: "redirectUri required for authorize"
937
+ });
938
+ }
939
+ const url = new URL(endpoints(cfg).authorize);
940
+ url.searchParams.set("response_type", "code");
941
+ url.searchParams.set("client_id", cfg.clientId);
942
+ url.searchParams.set("redirect_uri", redirectUri);
943
+ url.searchParams.set("scope", (args.scopes ?? cfg.scopes ?? DEFAULT_SCOPES).join(" "));
944
+ url.searchParams.set("state", args.state);
945
+ url.searchParams.set("code_challenge", args.codeChallenge);
946
+ url.searchParams.set("code_challenge_method", "S256");
947
+ if (args.nonce) url.searchParams.set("nonce", args.nonce);
948
+ if (args.prompt) url.searchParams.set("prompt", args.prompt);
949
+ if (args.maxAge !== void 0) url.searchParams.set("max_age", String(args.maxAge));
950
+ if (args.acrValues) url.searchParams.set("acr_values", args.acrValues);
951
+ return url.toString();
952
+ }
953
+ __name(buildAuthorizeUrl, "buildAuthorizeUrl");
954
+ async function exchangeCode(cfg, transport, args) {
955
+ const redirectUri = args.redirectUri ?? cfg.redirectUri;
956
+ if (!redirectUri) {
957
+ throw new AccountError({
958
+ code: "BAD_REQUEST",
959
+ message: "redirectUri required for code exchange"
960
+ });
961
+ }
962
+ const body = new URLSearchParams({
963
+ grant_type: "authorization_code",
964
+ code: args.code,
965
+ redirect_uri: redirectUri,
966
+ client_id: cfg.clientId,
967
+ code_verifier: args.codeVerifier
968
+ });
969
+ if (cfg.clientSecret) body.set("client_secret", cfg.clientSecret);
970
+ return postToken(cfg, transport, body, {
971
+ requireIdToken: true,
972
+ expectedNonce: args.expectedNonce,
973
+ expectedScopes: args.expectedScopes,
974
+ signal: args.signal
975
+ });
976
+ }
977
+ __name(exchangeCode, "exchangeCode");
978
+ async function refreshTokens(cfg, transport, refreshToken) {
979
+ const body = new URLSearchParams({
980
+ grant_type: "refresh_token",
981
+ refresh_token: refreshToken,
982
+ client_id: cfg.clientId
983
+ });
984
+ if (cfg.clientSecret) body.set("client_secret", cfg.clientSecret);
985
+ return postToken(cfg, transport, body);
986
+ }
987
+ __name(refreshTokens, "refreshTokens");
988
+ async function startDeviceAuthorization(cfg, transport, scopes) {
989
+ const body = new URLSearchParams({
990
+ client_id: cfg.clientId,
991
+ scope: (scopes ?? cfg.scopes ?? DEFAULT_SCOPES).join(" ")
992
+ });
993
+ const raw = await transport.request({
994
+ method: "POST",
995
+ url: endpoints(cfg).device,
996
+ headers: {
997
+ "content-type": "application/x-www-form-urlencoded"
998
+ },
999
+ body: body.toString()
1000
+ });
1001
+ const expiresIn = Number(raw["expires_in"] ?? 600);
1002
+ return {
1003
+ deviceCode: String(raw["device_code"] ?? ""),
1004
+ userCode: String(raw["user_code"] ?? ""),
1005
+ verificationUri: String(raw["verification_uri"] ?? ""),
1006
+ verificationUriComplete: typeof raw["verification_uri_complete"] === "string" ? raw["verification_uri_complete"] : null,
1007
+ expiresAt: new Date(Date.now() + expiresIn * 1e3),
1008
+ intervalSeconds: Number(raw["interval"] ?? 5)
1009
+ };
1010
+ }
1011
+ __name(startDeviceAuthorization, "startDeviceAuthorization");
1012
+ async function pollDeviceToken(cfg, transport, device, opts = {}) {
1013
+ const sleep2 = opts.sleep ?? ((ms) => new Promise((resolve) => {
1014
+ const signal = opts.signal;
1015
+ const timer = setTimeout(done, ms);
1016
+ function done() {
1017
+ clearTimeout(timer);
1018
+ signal?.removeEventListener("abort", done);
1019
+ resolve();
1020
+ }
1021
+ __name(done, "done");
1022
+ signal?.addEventListener("abort", done, {
1023
+ once: true
1024
+ });
1025
+ if (signal?.aborted) done();
1026
+ }));
1027
+ let intervalMs = device.intervalSeconds * 1e3;
1028
+ while (Date.now() < device.expiresAt.getTime()) {
1029
+ if (opts.signal?.aborted) {
1030
+ throw new AccountError({
1031
+ code: "BAD_REQUEST",
1032
+ message: "device authorization aborted"
1033
+ });
1034
+ }
1035
+ await sleep2(intervalMs);
1036
+ if (opts.signal?.aborted) {
1037
+ throw new AccountError({
1038
+ code: "BAD_REQUEST",
1039
+ message: "device authorization aborted"
1040
+ });
1041
+ }
1042
+ if (Date.now() >= device.expiresAt.getTime()) break;
1043
+ const body = new URLSearchParams({
1044
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code",
1045
+ device_code: device.deviceCode,
1046
+ client_id: cfg.clientId
1047
+ });
1048
+ try {
1049
+ const tokens = await postToken(cfg, transport, body, {
1050
+ requireIdToken: true,
1051
+ signal: opts.signal
1052
+ });
1053
+ if (opts.signal?.aborted) throw new AccountError({
1054
+ code: "BAD_REQUEST",
1055
+ message: "device authorization aborted"
1056
+ });
1057
+ return tokens;
1058
+ } catch (err) {
1059
+ if (!(err instanceof AccountError)) throw err;
1060
+ const oauthErr = err.oauthError;
1061
+ if (err.httpStatus === 400 && oauthErr === "authorization_pending") continue;
1062
+ if (err.httpStatus === 400 && oauthErr === "slow_down") {
1063
+ intervalMs += 5e3;
1064
+ continue;
1065
+ }
1066
+ throw err;
1067
+ }
1068
+ }
1069
+ throw new AccountError({
1070
+ code: "BAD_REQUEST",
1071
+ message: "device code expired before approval"
1072
+ });
1073
+ }
1074
+ __name(pollDeviceToken, "pollDeviceToken");
1075
+ async function postToken(cfg, transport, body, verify = {}) {
1076
+ const tokenUrl = endpoints(cfg).token;
1077
+ const dpop = await cfg.dpopSigner.createProof({
1078
+ method: "POST",
1079
+ url: tokenUrl
1080
+ });
1081
+ if (verify.signal?.aborted) throw new AccountError({
1082
+ code: "BAD_REQUEST",
1083
+ message: "Account sign-in was cancelled",
1084
+ retryable: false
1085
+ });
1086
+ const raw = await transport.request({
1087
+ method: "POST",
1088
+ url: tokenUrl,
1089
+ headers: {
1090
+ "content-type": "application/x-www-form-urlencoded",
1091
+ dpop
1092
+ },
1093
+ body: body.toString(),
1094
+ oauthErrorResponse: true,
1095
+ signal: verify.signal
1096
+ });
1097
+ const parsed = oauthTokenResponseSchema.safeParse(raw);
1098
+ if (!parsed.success) {
1099
+ throw new AccountError({
1100
+ code: "INVALID_RESPONSE",
1101
+ message: "token endpoint returned an unrecognised shape"
1102
+ });
1103
+ }
1104
+ const t = parsed.data;
1105
+ if (t.token_type.toLowerCase() !== "dpop") throw new AccountError({
1106
+ code: "INVALID_RESPONSE",
1107
+ message: "token endpoint did not issue a DPoP token"
1108
+ });
1109
+ if (verify.requireIdToken && !t.id_token) throw new AccountError({
1110
+ code: "INVALID_RESPONSE",
1111
+ message: "token endpoint omitted id_token"
1112
+ });
1113
+ const accessClaims = await verifyAccessToken({
1114
+ token: t.access_token,
1115
+ transport,
1116
+ issuer: cfg.issuer,
1117
+ clientId: cfg.clientId,
1118
+ dpopJkt: cfg.dpopSigner.jkt,
1119
+ jwksUrl: cfg.jwksUrl,
1120
+ expectedScopes: verify.expectedScopes
1121
+ });
1122
+ const idClaims = t.id_token ? await verifyIdToken({
1123
+ token: t.id_token,
1124
+ transport,
1125
+ issuer: cfg.issuer,
1126
+ clientId: cfg.clientId,
1127
+ jwksUrl: cfg.jwksUrl,
1128
+ expectedNonce: verify.expectedNonce,
1129
+ expectedSubject: accessClaims.sub
1130
+ }) : null;
1131
+ return tokenSetSchema.parse({
1132
+ accessToken: t.access_token,
1133
+ tokenType: t.token_type,
1134
+ expiresAt: t.expires_in ? new Date(Date.now() + t.expires_in * 1e3) : null,
1135
+ refreshToken: t.refresh_token ?? null,
1136
+ idToken: t.id_token ?? null,
1137
+ scope: t.scope ? t.scope.split(/\s+/).filter(Boolean) : [],
1138
+ sid: idClaims?.sid ?? accessClaims.sid,
1139
+ accessClaims,
1140
+ idClaims
1141
+ });
1142
+ }
1143
+ __name(postToken, "postToken");
1144
+
1145
+ // src/tokenStore.ts
1146
+ function memoryStore() {
1147
+ const m = /* @__PURE__ */ new Map();
1148
+ const locks = /* @__PURE__ */ new Map();
1149
+ return {
1150
+ async withExclusive(key, work) {
1151
+ const prior = locks.get(key) ?? Promise.resolve();
1152
+ let release;
1153
+ const held = new Promise((resolve) => {
1154
+ release = resolve;
1155
+ });
1156
+ const tail = prior.then(() => held);
1157
+ locks.set(key, tail);
1158
+ await prior;
1159
+ try {
1160
+ return await work();
1161
+ } finally {
1162
+ release();
1163
+ if (locks.get(key) === tail) locks.delete(key);
1164
+ }
1165
+ },
1166
+ async get(k) {
1167
+ return m.get(k) ?? null;
1168
+ },
1169
+ async set(k, v) {
1170
+ m.set(k, v);
1171
+ },
1172
+ async delete(k) {
1173
+ m.delete(k);
1174
+ }
1175
+ };
1176
+ }
1177
+ __name(memoryStore, "memoryStore");
1178
+ function keychainStore(kc) {
1179
+ return {
1180
+ ...kc.withExclusive ? {
1181
+ withExclusive: /* @__PURE__ */ __name((key, work) => kc.withExclusive(key, work), "withExclusive")
1182
+ } : {},
1183
+ async get(k) {
1184
+ return await kc.get(k) ?? null;
1185
+ },
1186
+ async set(k, v) {
1187
+ await kc.set(k, v);
1188
+ },
1189
+ async delete(k) {
1190
+ await kc.delete(k);
1191
+ }
1192
+ };
1193
+ }
1194
+ __name(keychainStore, "keychainStore");
1195
+
1196
+ // src/client.ts
1197
+ var XenoAccountClient = class {
1198
+ static {
1199
+ __name(this, "XenoAccountClient");
1200
+ }
1201
+ surface;
1202
+ account;
1203
+ ledger;
1204
+ authz;
1205
+ oidcConfig;
1206
+ transport;
1207
+ store;
1208
+ apiKey;
1209
+ broker;
1210
+ refreshStoreKey;
1211
+ refreshPendingKey;
1212
+ access = null;
1213
+ user = null;
1214
+ interactive;
1215
+ resolutionFlight = null;
1216
+ generation = 0;
1217
+ locallySignedOut = false;
1218
+ listeners = /* @__PURE__ */ new Set();
1219
+ constructor(cfg) {
1220
+ this.surface = surfaceSchema.parse(cfg.surface);
1221
+ this.transport = new Transport(cfg.transport);
1222
+ this.oidcConfig = {
1223
+ issuer: cfg.issuer,
1224
+ ...cfg.oidc
1225
+ };
1226
+ this.refreshStoreKey = `xeno.account/${this.oidcConfig.clientId}/refresh`;
1227
+ this.refreshPendingKey = `${this.refreshStoreKey}/pending`;
1228
+ this.account = new AccountClient(cfg.issuer, this.transport);
1229
+ this.ledger = new LedgerClient(cfg.apiBase, this.transport);
1230
+ this.authz = new AuthzClient(cfg.apiBase, this.transport);
1231
+ this.store = cfg.store ?? memoryStore();
1232
+ this.apiKey = cfg.apiKey ?? (typeof process !== "undefined" ? process.env?.XENO_API_KEY : void 0) ?? void 0;
1233
+ this.broker = cfg.broker;
1234
+ }
1235
+ // ─── Session workhorse (priority chain, XENO AUTH §5) ───────────────────
1236
+ /**
1237
+ * Return a valid Bearer for the gate, walking the chain:
1238
+ * 1 in-memory access token → 2 explicit override (apiKey/XENO_API_KEY) →
1239
+ * 3 [broker — host-wired] → 4 this product's own refresh token →
1240
+ * 5 interactive sign-in (host-provided; skipped when `silent`) → null.
1241
+ */
1242
+ async getAccessToken(opts = {}) {
1243
+ const generation = this.generation;
1244
+ const scopes = opts.scopes ? [
1245
+ ...opts.scopes
1246
+ ] : void 0;
1247
+ if (this.access && this.access.expiresAtMs - 6e4 > Date.now() && containsScopes(this.access.scopes, scopes)) {
1248
+ const held = this.access;
1249
+ if (await this.cacheCurrent(held) && this.generation === generation) return held.token;
1250
+ }
1251
+ if (this.apiKey) return this.apiKey;
1252
+ if (this.locallySignedOut && opts.silent) return null;
1253
+ if (!this.resolutionFlight) {
1254
+ const flight = this.resolveSession({
1255
+ scopes,
1256
+ silent: opts.silent
1257
+ }, generation);
1258
+ this.resolutionFlight = flight;
1259
+ void flight.finally(() => {
1260
+ if (this.resolutionFlight === flight) this.resolutionFlight = null;
1261
+ }).catch(() => {
1262
+ });
1263
+ }
1264
+ await this.resolutionFlight;
1265
+ if (this.generation !== generation || !this.access) return null;
1266
+ if (!await this.cacheCurrent(this.access) || this.generation !== generation || !this.access) return null;
1267
+ if (!containsScopes(this.access.scopes, scopes)) throw new AccountError({
1268
+ code: "INVALID_TOKEN",
1269
+ message: "Current account session lacks required scope",
1270
+ retryable: false
1271
+ });
1272
+ if (this.access.expiresAtMs <= Date.now()) return null;
1273
+ return this.access.token;
1274
+ }
1275
+ async resolveSession(opts, generation) {
1276
+ if (this.locallySignedOut) {
1277
+ if (!opts.silent && this.interactive) await this.resolveInteractive(opts.scopes, generation);
1278
+ return;
1279
+ }
1280
+ const pending = await this.storeRead(this.refreshPendingKey);
1281
+ if (this.generation !== generation) return;
1282
+ if (pending && !/^ready:v1:[A-Za-z0-9_-]{32}$/.test(pending)) {
1283
+ if (!opts.silent && this.interactive) {
1284
+ await this.resolveInteractive(opts.scopes, generation);
1285
+ return;
1286
+ }
1287
+ if (pending === "signed-out:v1") {
1288
+ this.setUnauthenticated();
1289
+ if (!await this.storeRead(this.refreshStoreKey)) return;
1290
+ } else {
1291
+ throw new AccountError({
1292
+ code: "CONFLICT",
1293
+ message: "Previous account transition is unconfirmed; explicit sign-in recovery required",
1294
+ retryable: false
1295
+ });
1296
+ }
1297
+ if (pending === "signed-out:v1") throw transitionConflict();
1298
+ }
1299
+ if (this.broker) {
1300
+ try {
1301
+ const brokerToken = await this.broker.getToken({
1302
+ clientId: this.oidcConfig.clientId,
1303
+ scopes: opts.scopes ?? this.oidcConfig.scopes ?? [],
1304
+ resource: "https://api.xenostudio.ai",
1305
+ childDpopJkt: this.oidcConfig.dpopSigner.jkt
1306
+ });
1307
+ if (brokerToken) {
1308
+ const claims = await verifyAccessToken({
1309
+ token: brokerToken.accessToken,
1310
+ transport: this.transport,
1311
+ issuer: this.oidcConfig.issuer,
1312
+ clientId: this.oidcConfig.clientId,
1313
+ dpopJkt: this.oidcConfig.dpopSigner.jkt,
1314
+ jwksUrl: this.oidcConfig.jwksUrl,
1315
+ expectedScopes: opts.scopes,
1316
+ requireBrokerActor: true
1317
+ });
1318
+ if (brokerToken.refreshToken) throw new AccountError({
1319
+ code: "INVALID_RESPONSE",
1320
+ message: "Broker cannot transfer refresh credentials",
1321
+ retryable: false
1322
+ });
1323
+ await this.applyTokenSet({
1324
+ ...brokerToken,
1325
+ accessClaims: claims,
1326
+ idClaims: null
1327
+ }, generation, pending, true);
1328
+ return;
1329
+ }
1330
+ } catch {
1331
+ }
1332
+ }
1333
+ if (this.generation !== generation) return;
1334
+ const transition = `refresh:v1:${generateState()}`;
1335
+ const rt = await this.exclusiveStore(async () => {
1336
+ if (this.generation !== generation) return null;
1337
+ if (await this.store.get(this.refreshPendingKey) !== pending) throw transitionConflict();
1338
+ const value = await this.store.get(this.refreshStoreKey);
1339
+ if (value) await this.store.set(this.refreshPendingKey, transition);
1340
+ return value;
1341
+ });
1342
+ if (this.generation !== generation) return;
1343
+ if (rt) {
1344
+ try {
1345
+ const tokens = await this.refresh(rt);
1346
+ if (!tokens.refreshToken) throw new AccountError({
1347
+ code: "INVALID_RESPONSE",
1348
+ message: "Rotating refresh response omitted successor credential",
1349
+ retryable: false
1350
+ });
1351
+ await this.applyTokenSet(tokens, generation, transition);
1352
+ return;
1353
+ } catch (error) {
1354
+ if (this.generation !== generation) return;
1355
+ if (!(error instanceof AccountError && error.httpStatus === 400 && error.oauthError === "invalid_grant")) {
1356
+ throw new AccountError({
1357
+ code: error instanceof AccountError ? error.code : "UNREACHABLE",
1358
+ message: "Account refresh outcome is unconfirmed; explicit recovery required",
1359
+ retryable: false,
1360
+ ...error instanceof AccountError ? {
1361
+ httpStatus: error.httpStatus
1362
+ } : {}
1363
+ });
1364
+ }
1365
+ await this.exclusiveStore(async () => {
1366
+ if (this.generation !== generation) return;
1367
+ if (await this.store.get(this.refreshPendingKey) !== transition) throw transitionConflict();
1368
+ await this.store.delete(this.refreshStoreKey);
1369
+ await this.store.set(this.refreshPendingKey, "signed-out:v1");
1370
+ });
1371
+ if (this.generation !== generation) return;
1372
+ this.setUnauthenticated();
1373
+ }
1374
+ }
1375
+ if (!opts.silent && this.interactive) {
1376
+ await this.resolveInteractive(opts.scopes, generation);
1377
+ }
1378
+ return;
1379
+ }
1380
+ /** Register the host's interactive sign-in (loopback PKCE / device grant). */
1381
+ setInteractiveSignIn(fn) {
1382
+ this.interactive = fn;
1383
+ }
1384
+ isAuthenticated() {
1385
+ return !!this.access && this.access.expiresAtMs > Date.now();
1386
+ }
1387
+ currentUser() {
1388
+ return this.user;
1389
+ }
1390
+ /** Subscribe to login/refresh/logout/backchannel. Returns an unsubscribe fn. */
1391
+ onAuthChanged(cb) {
1392
+ this.listeners.add(cb);
1393
+ return () => this.listeners.delete(cb);
1394
+ }
1395
+ /** Local wipe only, used after durable host-side invalidation/reconciliation. */
1396
+ async signOutLocal() {
1397
+ this.generation++;
1398
+ this.locallySignedOut = true;
1399
+ this.resolutionFlight = null;
1400
+ this.setUnauthenticated();
1401
+ await this.exclusiveStore(async () => {
1402
+ await this.store.set(this.refreshPendingKey, "signed-out:v1");
1403
+ await this.store.delete(this.refreshStoreKey);
1404
+ });
1405
+ }
1406
+ /** Revoke this subject-owned session, then always clear local state. */
1407
+ async signOut() {
1408
+ const original = this.access;
1409
+ const local = this.signOutLocal();
1410
+ void local.catch(() => {
1411
+ });
1412
+ let serverRevoked = false;
1413
+ try {
1414
+ if (original?.tokenType === "DPoP") {
1415
+ const url = `${this.oidcConfig.issuer.replace(/\/+$/, "")}/api/oauth2/end_session`;
1416
+ const dpop = await this.createProof({
1417
+ method: "POST",
1418
+ url,
1419
+ accessToken: original.token
1420
+ });
1421
+ await this.transport.request({
1422
+ method: "POST",
1423
+ url,
1424
+ token: original.token,
1425
+ tokenType: "DPoP",
1426
+ headers: {
1427
+ dpop
1428
+ },
1429
+ idempotencyKey: `logout:${original.sid ?? "current"}`
1430
+ });
1431
+ serverRevoked = true;
1432
+ }
1433
+ } finally {
1434
+ await local;
1435
+ }
1436
+ return {
1437
+ serverRevoked
1438
+ };
1439
+ }
1440
+ /** Step-up protected, subject-keyed global logout. Only authorized clients can call it. */
1441
+ async signOutEverywhere() {
1442
+ if (!this.access || this.access.tokenType !== "DPoP") throw new Error("fresh DPoP session required");
1443
+ const original = this.access, generation = this.generation;
1444
+ const url = `${this.oidcConfig.issuer.replace(/\/+$/, "")}/api/oauth2/logout_everywhere`;
1445
+ const dpop = await this.createProof({
1446
+ method: "POST",
1447
+ url,
1448
+ accessToken: original.token
1449
+ });
1450
+ if (this.generation !== generation || this.access !== original) throw new AccountError({
1451
+ code: "CONFLICT",
1452
+ message: "Account session changed before global revocation",
1453
+ retryable: false
1454
+ });
1455
+ await this.transport.request({
1456
+ method: "POST",
1457
+ url,
1458
+ token: original.token,
1459
+ tokenType: "DPoP",
1460
+ headers: {
1461
+ dpop
1462
+ },
1463
+ idempotencyKey: `logout-all:${original.sid ?? "current"}`
1464
+ });
1465
+ try {
1466
+ await this.broker?.logout?.();
1467
+ } finally {
1468
+ if (this.generation === generation) {
1469
+ this.generation++;
1470
+ this.locallySignedOut = true;
1471
+ this.resolutionFlight = null;
1472
+ this.setUnauthenticated();
1473
+ await this.exclusiveStore(async () => {
1474
+ if (await this.store.get(this.refreshPendingKey) !== original.storeTransition) return;
1475
+ await this.store.set(this.refreshPendingKey, "signed-out:v1");
1476
+ await this.store.delete(this.refreshStoreKey);
1477
+ });
1478
+ }
1479
+ }
1480
+ }
1481
+ setUnauthenticated() {
1482
+ this.access = null;
1483
+ this.user = null;
1484
+ this.emit(null);
1485
+ }
1486
+ emit(a) {
1487
+ for (const cb of [
1488
+ ...this.listeners
1489
+ ]) {
1490
+ try {
1491
+ cb(a);
1492
+ } catch {
1493
+ }
1494
+ }
1495
+ }
1496
+ async storeRead(key) {
1497
+ try {
1498
+ return await this.store.get(key);
1499
+ } catch {
1500
+ throw new AccountError({
1501
+ code: "UNREACHABLE",
1502
+ message: "Secure account store is unavailable",
1503
+ retryable: false
1504
+ });
1505
+ }
1506
+ }
1507
+ async cacheCurrent(held) {
1508
+ const transition = await this.storeRead(this.refreshPendingKey);
1509
+ if (this.access === held && transition === held.storeTransition) return true;
1510
+ if (this.access === held) this.setUnauthenticated();
1511
+ return false;
1512
+ }
1513
+ async exclusiveStore(work) {
1514
+ if (!this.store.withExclusive) throw new AccountError({
1515
+ code: "UNREACHABLE",
1516
+ message: "Secure account store exclusion is unavailable",
1517
+ retryable: false
1518
+ });
1519
+ try {
1520
+ return await this.store.withExclusive(this.refreshStoreKey, work);
1521
+ } catch (error) {
1522
+ if (error instanceof AccountError) throw error;
1523
+ throw new AccountError({
1524
+ code: "UNREACHABLE",
1525
+ message: "Secure account state commit is unavailable",
1526
+ retryable: false
1527
+ });
1528
+ }
1529
+ }
1530
+ async reserveSignIn(generation) {
1531
+ const transition = `sign-in:v1:${generateState()}`;
1532
+ await this.exclusiveStore(async () => {
1533
+ if (this.generation !== generation) throw transitionConflict();
1534
+ await this.store.set(this.refreshPendingKey, transition);
1535
+ });
1536
+ return transition;
1537
+ }
1538
+ async resolveInteractive(scopes, generation) {
1539
+ const transition = await this.reserveSignIn(generation);
1540
+ await this.applyTokenSet(await this.interactive(scopes), generation, transition);
1541
+ }
1542
+ /** Cache the access token, persist the rotated refresh token, update the user. */
1543
+ async applyTokenSet(ts, generation, transition, preserveRefresh = false, signal) {
1544
+ if (this.generation !== generation) return;
1545
+ const claims = await verifyAccessToken({
1546
+ token: ts.accessToken,
1547
+ transport: this.transport,
1548
+ issuer: this.oidcConfig.issuer,
1549
+ clientId: this.oidcConfig.clientId,
1550
+ dpopJkt: this.oidcConfig.dpopSigner.jkt,
1551
+ jwksUrl: this.oidcConfig.jwksUrl
1552
+ });
1553
+ const idClaims = ts.idToken ? await verifyIdToken({
1554
+ token: ts.idToken,
1555
+ transport: this.transport,
1556
+ issuer: this.oidcConfig.issuer,
1557
+ clientId: this.oidcConfig.clientId,
1558
+ jwksUrl: this.oidcConfig.jwksUrl,
1559
+ expectedSubject: claims.sub
1560
+ }) : null;
1561
+ if (idClaims && idClaims.sid !== claims.sid) throw new AccountError({
1562
+ code: "INVALID_RESPONSE",
1563
+ message: "ID and access session binding differ",
1564
+ retryable: false
1565
+ });
1566
+ if (ts.tokenType.toLowerCase() !== "dpop") throw new AccountError({
1567
+ code: "INVALID_RESPONSE",
1568
+ message: "Canonical session requires DPoP token type",
1569
+ retryable: false
1570
+ });
1571
+ if (this.generation !== generation) return;
1572
+ const expMs = Math.min(claims.exp * 1e3, ts.expiresAt?.getTime() ?? claims.exp * 1e3);
1573
+ const storeTransition = `ready:v1:${generateState()}`;
1574
+ await this.exclusiveStore(async () => {
1575
+ if (this.generation !== generation) return;
1576
+ if (await this.store.get(this.refreshPendingKey) !== transition) throw transitionConflict();
1577
+ if (ts.refreshToken) {
1578
+ await this.store.set(this.refreshStoreKey, ts.refreshToken);
1579
+ } else if (!preserveRefresh) await this.store.delete(this.refreshStoreKey);
1580
+ if (this.generation !== generation || signal?.aborted) throw transitionConflict();
1581
+ await this.store.set(this.refreshPendingKey, storeTransition);
1582
+ if (signal?.aborted) {
1583
+ if (await this.store.get(this.refreshPendingKey) === storeTransition) {
1584
+ await this.store.set(this.refreshPendingKey, "signed-out:v1");
1585
+ await this.store.delete(this.refreshStoreKey);
1586
+ }
1587
+ throw transitionConflict();
1588
+ }
1589
+ });
1590
+ if (this.generation !== generation) {
1591
+ if (signal?.aborted) await this.cancelCommittedSignIn(storeTransition);
1592
+ return;
1593
+ }
1594
+ this.access = {
1595
+ token: ts.accessToken,
1596
+ tokenType: ts.tokenType.toLowerCase() === "dpop" ? "DPoP" : "Bearer",
1597
+ expiresAtMs: expMs,
1598
+ scopes: claims.scope.split(/\s+/).filter(Boolean),
1599
+ sid: claims.sid,
1600
+ storeTransition
1601
+ };
1602
+ this.locallySignedOut = false;
1603
+ if (idClaims) {
1604
+ const u = accountFromVerifiedClaims(idClaims);
1605
+ if (u) this.user = u;
1606
+ } else if (this.user?.id !== claims.sub) this.user = null;
1607
+ this.emit(this.user);
1608
+ return storeTransition;
1609
+ }
1610
+ async cancelCommittedSignIn(transition) {
1611
+ await this.exclusiveStore(async () => {
1612
+ if (await this.store.get(this.refreshPendingKey) !== transition) return;
1613
+ await this.store.set(this.refreshPendingKey, "signed-out:v1");
1614
+ await this.store.delete(this.refreshStoreKey);
1615
+ });
1616
+ }
1617
+ // ─── OIDC sign-in (Authorization Code + PKCE) ───────────────────────────
1618
+ /**
1619
+ * Begin "Continue with XENO". Returns the authorize URL to redirect to, plus
1620
+ * the `{ state, verifier }` the caller MUST persist (encrypted) to complete
1621
+ * the flow in {@link completeSignIn}.
1622
+ */
1623
+ async beginSignIn(args = {}) {
1624
+ const state = generateState();
1625
+ const nonce = generateState();
1626
+ const pkce = await generatePkce();
1627
+ const url = buildAuthorizeUrl(this.oidcConfig, {
1628
+ state,
1629
+ codeChallenge: pkce.challenge,
1630
+ redirectUri: args.redirectUri,
1631
+ scopes: args.scopes,
1632
+ nonce,
1633
+ ...args.fresh ? {
1634
+ prompt: "login",
1635
+ maxAge: 60,
1636
+ acrValues: "urn:xeno:acr:fresh"
1637
+ } : {}
1638
+ });
1639
+ return {
1640
+ url,
1641
+ state,
1642
+ nonce,
1643
+ pkce
1644
+ };
1645
+ }
1646
+ /** Complete the code flow: exchange `code` (with the saved verifier) for tokens,
1647
+ * then cache the access token + persist the refresh token + set the user. */
1648
+ async completeSignIn(args) {
1649
+ const generation = ++this.generation;
1650
+ this.resolutionFlight = null;
1651
+ const invalidate = this.bindSignInCancellation(generation, args.signal);
1652
+ try {
1653
+ if (args.signal?.aborted) throw transitionConflict();
1654
+ const transition = await this.reserveSignIn(generation);
1655
+ if (args.signal?.aborted || this.generation !== generation) throw transitionConflict();
1656
+ const ts = await exchangeCode(this.oidcConfig, this.transport, args);
1657
+ const committed = await this.applyTokenSet(ts, generation, transition, false, args.signal);
1658
+ if (this.generation !== generation || args.signal?.aborted) {
1659
+ if (committed && args.signal?.aborted) await this.cancelCommittedSignIn(committed);
1660
+ throw transitionConflict();
1661
+ }
1662
+ return ts;
1663
+ } catch (error) {
1664
+ if (args.signal?.aborted) throw transitionConflict();
1665
+ throw error;
1666
+ } finally {
1667
+ invalidate();
1668
+ }
1669
+ }
1670
+ bindSignInCancellation(generation, signal) {
1671
+ const cancelled = /* @__PURE__ */ __name(() => {
1672
+ if (this.generation !== generation) return;
1673
+ this.generation++;
1674
+ this.resolutionFlight = null;
1675
+ this.setUnauthenticated();
1676
+ }, "cancelled");
1677
+ signal?.addEventListener("abort", cancelled, {
1678
+ once: true
1679
+ });
1680
+ if (signal?.aborted) cancelled();
1681
+ return () => signal?.removeEventListener("abort", cancelled);
1682
+ }
1683
+ /** Headers for one sender-constrained resource request. */
1684
+ async authorization(method, url, opts = {}) {
1685
+ const generation = this.generation;
1686
+ const token = await this.getAccessToken(opts);
1687
+ if (!token || this.generation !== generation) return null;
1688
+ if (this.apiKey && token === this.apiKey) return {
1689
+ token,
1690
+ tokenType: "Bearer"
1691
+ };
1692
+ if (!this.access || this.access.token !== token || this.access.tokenType !== "DPoP") return {
1693
+ token,
1694
+ tokenType: "Bearer"
1695
+ };
1696
+ const dpop = await this.createProof({
1697
+ method,
1698
+ url,
1699
+ accessToken: token
1700
+ });
1701
+ if (this.generation !== generation || this.access?.token !== token || this.access.expiresAtMs <= Date.now()) return null;
1702
+ if (!await this.cacheCurrent(this.access) || this.generation !== generation || this.access?.token !== token) return null;
1703
+ return {
1704
+ token,
1705
+ tokenType: "DPoP",
1706
+ headers: {
1707
+ dpop
1708
+ }
1709
+ };
1710
+ }
1711
+ /** Verify and sender-constrain a previously persisted access token. Server
1712
+ * consumers use this for encrypted per-workspace sessions without placing
1713
+ * those tokens into the process-global client cache. */
1714
+ async authorizationForToken(token, method, url, expectedScopes) {
1715
+ await verifyAccessToken({
1716
+ token,
1717
+ transport: this.transport,
1718
+ issuer: this.oidcConfig.issuer,
1719
+ clientId: this.oidcConfig.clientId,
1720
+ dpopJkt: this.oidcConfig.dpopSigner.jkt,
1721
+ jwksUrl: this.oidcConfig.jwksUrl,
1722
+ expectedScopes
1723
+ });
1724
+ const dpop = await this.createProof({
1725
+ method,
1726
+ url,
1727
+ accessToken: token
1728
+ });
1729
+ return {
1730
+ token,
1731
+ tokenType: "DPoP",
1732
+ headers: {
1733
+ dpop
1734
+ }
1735
+ };
1736
+ }
1737
+ /** Verify a provider-originated Back-Channel Logout Token. */
1738
+ verifyBackchannelLogout(token) {
1739
+ return verifyLogoutToken({
1740
+ token,
1741
+ transport: this.transport,
1742
+ issuer: this.oidcConfig.issuer,
1743
+ clientId: this.oidcConfig.clientId,
1744
+ jwksUrl: this.oidcConfig.jwksUrl
1745
+ });
1746
+ }
1747
+ async createProof(args) {
1748
+ try {
1749
+ return await this.oidcConfig.dpopSigner.createProof(args);
1750
+ } catch {
1751
+ throw new AccountError({
1752
+ code: "UNREACHABLE",
1753
+ message: "Host account request signing is unavailable",
1754
+ retryable: false
1755
+ });
1756
+ }
1757
+ }
1758
+ /** Rotate tokens. Persist the returned refreshToken; a failure means re-auth. */
1759
+ refresh(refreshToken) {
1760
+ return refreshTokens(this.oidcConfig, this.transport, refreshToken);
1761
+ }
1762
+ // ─── Device grant (CLI / headless) ──────────────────────────────────────
1763
+ startDeviceLogin(scopes) {
1764
+ return startDeviceAuthorization(this.oidcConfig, this.transport, scopes);
1765
+ }
1766
+ async awaitDeviceLogin(device, opts) {
1767
+ const generation = ++this.generation;
1768
+ this.resolutionFlight = null;
1769
+ const invalidate = this.bindSignInCancellation(generation, opts?.signal);
1770
+ try {
1771
+ if (opts?.signal?.aborted) throw transitionConflict();
1772
+ const transition = await this.reserveSignIn(generation);
1773
+ if (opts?.signal?.aborted || this.generation !== generation) throw transitionConflict();
1774
+ const ts = await pollDeviceToken(this.oidcConfig, this.transport, device, opts);
1775
+ const committed = await this.applyTokenSet(ts, generation, transition, false, opts?.signal);
1776
+ if (this.generation !== generation || opts?.signal?.aborted) {
1777
+ if (committed && opts?.signal?.aborted) await this.cancelCommittedSignIn(committed);
1778
+ throw transitionConflict();
1779
+ }
1780
+ return ts;
1781
+ } catch (error) {
1782
+ if (opts?.signal?.aborted) throw transitionConflict();
1783
+ throw error;
1784
+ } finally {
1785
+ invalidate();
1786
+ }
1787
+ }
1788
+ };
1789
+ function accountFromVerifiedClaims(p) {
1790
+ if (!p.sub) return null;
1791
+ return {
1792
+ id: p.sub,
1793
+ email: p.email ?? "",
1794
+ username: p.preferred_username ?? null,
1795
+ displayName: p.name ?? null,
1796
+ avatarUrl: p.picture ?? null,
1797
+ emailVerified: p.email_verified === true,
1798
+ linkedSurfaces: []
1799
+ };
1800
+ }
1801
+ __name(accountFromVerifiedClaims, "accountFromVerifiedClaims");
1802
+ function containsScopes(held, requested) {
1803
+ if (!requested?.length) return true;
1804
+ const set = new Set(held);
1805
+ return requested.every((scope) => set.has(scope));
1806
+ }
1807
+ __name(containsScopes, "containsScopes");
1808
+ function transitionConflict() {
1809
+ return new AccountError({
1810
+ code: "CONFLICT",
1811
+ message: "Account credential transition changed; explicit recovery required",
1812
+ retryable: false
1813
+ });
1814
+ }
1815
+ __name(transitionConflict, "transitionConflict");
1816
+
1817
+ // src/bff.ts
1818
+ var BffSessionClient = class {
1819
+ static {
1820
+ __name(this, "BffSessionClient");
1821
+ }
1822
+ baseUrl;
1823
+ fetchImpl;
1824
+ csrf;
1825
+ constructor(baseUrl = "/api/session", fetchImpl = globalThis.fetch, csrf) {
1826
+ this.baseUrl = baseUrl;
1827
+ this.fetchImpl = fetchImpl;
1828
+ this.csrf = csrf;
1829
+ }
1830
+ loginUrl(returnUrl) {
1831
+ if (!returnUrl.startsWith("/") || returnUrl.startsWith("//")) throw new AccountError({
1832
+ code: "BAD_REQUEST",
1833
+ message: "returnUrl must be same-origin"
1834
+ });
1835
+ return `${this.baseUrl}/login/start?return_url=${encodeURIComponent(returnUrl)}`;
1836
+ }
1837
+ session() {
1838
+ return this.request("GET", "/session");
1839
+ }
1840
+ refresh(idempotencyKey) {
1841
+ return this.request("POST", "/refresh", idempotencyKey);
1842
+ }
1843
+ async logout() {
1844
+ await this.request("POST", "/logout");
1845
+ }
1846
+ async request(method, path, idempotencyKey) {
1847
+ const headers = {
1848
+ accept: "application/json"
1849
+ };
1850
+ if (method === "POST") {
1851
+ const csrf = this.csrf();
1852
+ if (!csrf) throw new AccountError({
1853
+ code: "BAD_REQUEST",
1854
+ message: "missing BFF CSRF value"
1855
+ });
1856
+ headers["x-xeno-csrf"] = csrf;
1857
+ if (idempotencyKey) headers["idempotency-key"] = idempotencyKey;
1858
+ }
1859
+ const response = await this.fetchImpl(`${this.baseUrl}${path}`, {
1860
+ method,
1861
+ credentials: "include",
1862
+ headers
1863
+ });
1864
+ if (!response.ok) throw new AccountError({
1865
+ code: response.status === 401 ? "INVALID_TOKEN" : "PLATFORM_ERROR",
1866
+ message: `BFF session request failed: ${response.status}`,
1867
+ httpStatus: response.status
1868
+ });
1869
+ if (response.status === 204) return void 0;
1870
+ return response.json();
1871
+ }
1872
+ };
1873
+
1874
+ // src/redaction.ts
1875
+ var TOKEN_RE = /(eyJ[A-Za-z0-9_-]{6,}\.[A-Za-z0-9_-]{6,}\.[A-Za-z0-9_-]{6,})|([A-Za-z0-9_-]{40,})/g;
1876
+ function redact(input) {
1877
+ const s = typeof input === "string" ? input : safeStringify(input);
1878
+ return s.replace(TOKEN_RE, (m) => `${m.slice(0, 6)}\u2026[redacted:${m.length}]`);
1879
+ }
1880
+ __name(redact, "redact");
1881
+ function safeStringify(v) {
1882
+ try {
1883
+ return JSON.stringify(v) ?? String(v);
1884
+ } catch {
1885
+ return String(v);
1886
+ }
1887
+ }
1888
+ __name(safeStringify, "safeStringify");
1889
+
1890
+ // src/resource-authorization.ts
1891
+ var MAX_JWT_BYTES = 16384;
1892
+ var PRIVATE_FIELDS = [
1893
+ "d",
1894
+ "p",
1895
+ "q",
1896
+ "dp",
1897
+ "dq",
1898
+ "qi",
1899
+ "oth",
1900
+ "k"
1901
+ ];
1902
+ function denied() {
1903
+ return new AccountError({
1904
+ code: "INVALID_TOKEN",
1905
+ message: "Resource authorization refused",
1906
+ retryable: false,
1907
+ httpStatus: 401
1908
+ });
1909
+ }
1910
+ __name(denied, "denied");
1911
+ function unavailable() {
1912
+ return new AccountError({
1913
+ code: "UNREACHABLE",
1914
+ message: "Resource authorization authority unavailable",
1915
+ retryable: true,
1916
+ httpStatus: 503
1917
+ });
1918
+ }
1919
+ __name(unavailable, "unavailable");
1920
+ function decodePart(part) {
1921
+ if (!/^[a-zA-Z0-9_-]+$/.test(part) || part.length % 4 === 1) throw denied();
1922
+ const padded = part.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat((4 - part.length % 4) % 4);
1923
+ try {
1924
+ return Uint8Array.from(atob(padded), (value) => value.charCodeAt(0));
1925
+ } catch {
1926
+ throw denied();
1927
+ }
1928
+ }
1929
+ __name(decodePart, "decodePart");
1930
+ function record(bytes2) {
1931
+ try {
1932
+ return parseJwtObject(new TextDecoder("utf-8", {
1933
+ fatal: true
1934
+ }).decode(bytes2));
1935
+ } catch {
1936
+ throw denied();
1937
+ }
1938
+ }
1939
+ __name(record, "record");
1940
+ async function sha(value) {
1941
+ const bytes2 = new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value)));
1942
+ return btoa(String.fromCharCode(...bytes2)).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
1943
+ }
1944
+ __name(sha, "sha");
1945
+ function frozenClaims(claims) {
1946
+ return Object.freeze({
1947
+ iss: claims.iss,
1948
+ sub: claims.sub,
1949
+ aud: claims.aud,
1950
+ exp: claims.exp,
1951
+ iat: claims.iat,
1952
+ sid: claims.sid,
1953
+ client_id: claims.client_id,
1954
+ azp: claims.azp,
1955
+ scope: claims.scope,
1956
+ typ: claims.typ,
1957
+ auth_epoch: claims.auth_epoch,
1958
+ ...claims.auth_time === void 0 ? {} : {
1959
+ auth_time: claims.auth_time
1960
+ },
1961
+ ...claims.acr ? {
1962
+ acr: claims.acr
1963
+ } : {},
1964
+ cnf: Object.freeze({
1965
+ jkt: claims.cnf.jkt
1966
+ }),
1967
+ ...Array.isArray(claims.aud) ? {
1968
+ aud: Object.freeze([
1969
+ ...claims.aud
1970
+ ])
1971
+ } : {},
1972
+ ...claims.act ? {
1973
+ act: Object.freeze({
1974
+ client_id: claims.act.client_id,
1975
+ installation_id_hash: claims.act.installation_id_hash,
1976
+ ...claims.act.sub ? {
1977
+ sub: claims.act.sub
1978
+ } : {}
1979
+ })
1980
+ } : {},
1981
+ ...claims.amr ? {
1982
+ amr: Object.freeze([
1983
+ ...claims.amr
1984
+ ])
1985
+ } : {}
1986
+ });
1987
+ }
1988
+ __name(frozenClaims, "frozenClaims");
1989
+ function createResourceRequestAuthorizer(options) {
1990
+ const issuer = normalizeDpopHtu(options.issuer);
1991
+ if (issuer.replace(/\/$/, "") !== options.issuer || !options.clientIds.length || options.clientIds.length > 128 || options.clientIds.some((id) => !/^[a-zA-Z0-9._-]{1,128}$/.test(id)) || new Set(options.clientIds).size !== options.clientIds.length) throw new TypeError("Explicit canonical issuer and unique clients required");
1992
+ const clients = new Set(options.clientIds);
1993
+ return {
1994
+ async authorize(request) {
1995
+ request = Object.freeze({
1996
+ ...request,
1997
+ ...request.scopes ? {
1998
+ scopes: [
1999
+ ...request.scopes
2000
+ ]
2001
+ } : {}
2002
+ });
2003
+ if (!/^[A-Z]{3,16}$/.test(request.method) || request.url.length > 8192 || request.authorization.length > MAX_JWT_BYTES + 5 || request.proof.length > MAX_JWT_BYTES || !request.proof || (request.scopes?.length ?? 0) > 128) throw denied();
2004
+ let url;
2005
+ try {
2006
+ const parsed = new URL(request.url);
2007
+ if (parsed.username || parsed.password || parsed.hash) throw denied();
2008
+ url = normalizeDpopHtu(request.url);
2009
+ } catch {
2010
+ throw denied();
2011
+ }
2012
+ const match = /^DPoP ([a-zA-Z0-9_.-]+)$/.exec(request.authorization);
2013
+ if (!match) throw denied();
2014
+ const token = match[1];
2015
+ const parts = request.proof.split(".");
2016
+ if (parts.length !== 3) throw denied();
2017
+ const header = record(decodePart(parts[0])), proof = record(decodePart(parts[1]));
2018
+ if (header.typ !== "dpop+jwt" || header.alg !== "ES256" || header.crit !== void 0 || header.b64 !== void 0 || !header.jwk || typeof header.jwk !== "object" || Array.isArray(header.jwk)) throw denied();
2019
+ const supplied = header.jwk;
2020
+ if (PRIVATE_FIELDS.some((field) => supplied[field] !== void 0) || supplied.kty !== "EC" || supplied.crv !== "P-256" || typeof supplied.x !== "string" || typeof supplied.y !== "string" || decodePart(supplied.x).length !== 32 || decodePart(supplied.y).length !== 32) throw denied();
2021
+ const jwk = {
2022
+ kty: "EC",
2023
+ crv: "P-256",
2024
+ x: supplied.x,
2025
+ y: supplied.y
2026
+ };
2027
+ const jkt = await sha(JSON.stringify({
2028
+ crv: jwk.crv,
2029
+ kty: jwk.kty,
2030
+ x: jwk.x,
2031
+ y: jwk.y
2032
+ }));
2033
+ const now = Math.floor(Date.now() / 1e3);
2034
+ if (typeof proof.jti !== "string" || !/^[a-zA-Z0-9._:-]{1,128}$/.test(proof.jti) || !Number.isSafeInteger(proof.iat) || Number(proof.iat) < now - 60 || Number(proof.iat) > now + 60 || proof.htm !== request.method || proof.htu !== url || proof.ath !== await sha(token) || (request.nonce === void 0 ? proof.nonce !== void 0 : proof.nonce !== request.nonce)) throw denied();
2035
+ try {
2036
+ const key = await crypto.subtle.importKey("jwk", jwk, {
2037
+ name: "ECDSA",
2038
+ namedCurve: "P-256"
2039
+ }, false, [
2040
+ "verify"
2041
+ ]);
2042
+ if (!await crypto.subtle.verify({
2043
+ name: "ECDSA",
2044
+ hash: "SHA-256"
2045
+ }, key, decodePart(parts[2]), new TextEncoder().encode(`${parts[0]}.${parts[1]}`))) throw denied();
2046
+ } catch {
2047
+ throw denied();
2048
+ }
2049
+ const tokenParts = token.split(".");
2050
+ if (tokenParts.length !== 3) throw denied();
2051
+ const tokenHeader = record(decodePart(tokenParts[0]));
2052
+ if (tokenHeader.alg !== "ES256" || tokenHeader.typ !== "at+jwt" || typeof tokenHeader.kid !== "string" || tokenHeader.kid.length > 128 || tokenHeader.crit !== void 0 || tokenHeader.b64 !== void 0 || tokenHeader.jku !== void 0 || tokenHeader.jwk !== void 0 || tokenHeader.x5u !== void 0) throw denied();
2053
+ const untrusted = record(decodePart(tokenParts[1]));
2054
+ if (untrusted.auth_time !== void 0 && (!Number.isSafeInteger(untrusted.auth_time) || Number(untrusted.auth_time) < 0 || Number(untrusted.auth_time) > now + 30)) throw denied();
2055
+ if (untrusted.acr !== void 0 && (typeof untrusted.acr !== "string" || untrusted.acr.length > 128)) throw denied();
2056
+ if (untrusted.amr !== void 0 && (!Array.isArray(untrusted.amr) || untrusted.amr.length > 16 || untrusted.amr.some((method) => typeof method !== "string" || !/^[a-zA-Z0-9._:-]{1,64}$/.test(method)))) throw denied();
2057
+ if (typeof untrusted.client_id !== "string" || !clients.has(untrusted.client_id)) throw denied();
2058
+ let verified;
2059
+ try {
2060
+ verified = await verifyAccessToken({
2061
+ token,
2062
+ transport: options.transport,
2063
+ issuer: options.issuer,
2064
+ clientId: untrusted.client_id,
2065
+ dpopJkt: jkt,
2066
+ expectedScopes: [
2067
+ ...request.scopes ?? []
2068
+ ]
2069
+ });
2070
+ } catch (error) {
2071
+ if (error instanceof AccountError && error.retryable) throw unavailable();
2072
+ throw denied();
2073
+ }
2074
+ if (!verified.sub || verified.sub.length > 256 || !verified.sid || verified.sid.length > 256 || !Number.isSafeInteger(verified.auth_epoch) || verified.auth_epoch < 0 || typeof verified.scope !== "string" || verified.scope.length > 8192) throw denied();
2075
+ const claims = frozenClaims(verified);
2076
+ const active = /* @__PURE__ */ __name(async () => {
2077
+ if (claims.exp <= Math.floor(Date.now() / 1e3)) throw denied();
2078
+ let current;
2079
+ try {
2080
+ current = await options.sessionActive(claims, token);
2081
+ } catch {
2082
+ throw unavailable();
2083
+ }
2084
+ if (current !== true) throw denied();
2085
+ }, "active");
2086
+ await active();
2087
+ let consumed;
2088
+ try {
2089
+ consumed = await options.replayStore.consume({
2090
+ keyThumbprint: jkt,
2091
+ proofId: proof.jti,
2092
+ method: request.method,
2093
+ url,
2094
+ expiresAtMs: Math.min(claims.exp * 1e3, (Number(proof.iat) + 61) * 1e3)
2095
+ });
2096
+ } catch {
2097
+ throw unavailable();
2098
+ }
2099
+ if (consumed !== true) throw denied();
2100
+ await active();
2101
+ return Object.freeze({
2102
+ claims,
2103
+ revalidate: active
2104
+ });
2105
+ }
2106
+ };
2107
+ }
2108
+ __name(createResourceRequestAuthorizer, "createResourceRequestAuthorizer");
2109
+ function createOidcIntrospectionSessionAuthority(options) {
2110
+ const base = new URL(options.issuer);
2111
+ const timeoutMs = options.timeoutMs ?? 5e3;
2112
+ if (base.origin !== options.issuer || base.protocol !== "https:" || !Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 3e4) throw new TypeError("Canonical HTTPS issuer and bounded introspection timeout required");
2113
+ const fetchImpl = options.fetchImpl ?? fetch;
2114
+ return async (claims, token) => {
2115
+ if (token.length > MAX_JWT_BYTES || !token) throw unavailable();
2116
+ const controller = new AbortController();
2117
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
2118
+ let response;
2119
+ try {
2120
+ response = await fetchBeforeAbort(fetchImpl, `${base.origin}/api/oauth2/introspect`, {
2121
+ method: "POST",
2122
+ redirect: "error",
2123
+ cache: "no-store",
2124
+ credentials: "omit",
2125
+ signal: controller.signal,
2126
+ headers: {
2127
+ accept: "application/json",
2128
+ "content-type": "application/x-www-form-urlencoded"
2129
+ },
2130
+ body: new URLSearchParams({
2131
+ token
2132
+ }).toString()
2133
+ }, controller.signal);
2134
+ if (!response.ok || !response.body) throw unavailable();
2135
+ const value = parseJwtObject(await readBoundedText(response, controller.signal, 32768));
2136
+ if (value.active === false) return false;
2137
+ const canonicalAudience = Array.isArray(value.aud) ? value.aud.includes("xeno-api") : value.aud === "xeno-api";
2138
+ return value.active === true && value.token_type === "access_token" && value.iss === claims.iss && value.sub === claims.sub && value.sid === claims.sid && value.client_id === claims.client_id && value.exp === claims.exp && value.scope === claims.scope && canonicalAudience;
2139
+ } catch {
2140
+ throw unavailable();
2141
+ } finally {
2142
+ controller.abort();
2143
+ clearTimeout(timer);
2144
+ if (response) cancelBody(response);
2145
+ }
2146
+ };
2147
+ }
2148
+ __name(createOidcIntrospectionSessionAuthority, "createOidcIntrospectionSessionAuthority");
2149
+ export {
2150
+ AccountClient,
2151
+ AccountError,
2152
+ AuthzClient,
2153
+ BffSessionClient,
2154
+ LedgerClient,
2155
+ MICRO_PER_CREDIT,
2156
+ Transport,
2157
+ XenoAccountClient,
2158
+ accountSchema,
2159
+ authzCheckSchema,
2160
+ authzResultSchema,
2161
+ balanceSchema,
2162
+ buildAuthorizeUrl,
2163
+ codeForStatus,
2164
+ createOidcIntrospectionSessionAuthority,
2165
+ createResourceRequestAuthorizer,
2166
+ exchangeCode,
2167
+ fromMicro,
2168
+ generatePkce,
2169
+ generateState,
2170
+ holdRequestSchema,
2171
+ holdSchema,
2172
+ keychainStore,
2173
+ memoryStore,
2174
+ meterAggregationSchema,
2175
+ normalize,
2176
+ normalizeDpopHtu,
2177
+ oauthTokenResponseSchema,
2178
+ pollDeviceToken,
2179
+ redact,
2180
+ refreshTokens,
2181
+ startDeviceAuthorization,
2182
+ surfaceSchema,
2183
+ toMicro,
2184
+ tokenSetSchema,
2185
+ usageEventSchema,
2186
+ usageResultSchema,
2187
+ verifyAccessToken,
2188
+ verifyIdToken,
2189
+ verifyLogoutToken
2190
+ };
2191
+ //# sourceMappingURL=index.mjs.map