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