agenthooksprotocol 0.0.0-stage → 0.1.0

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 (151) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +28 -2
  3. package/dist/src/client/auth.d.ts +147 -0
  4. package/dist/src/client/auth.d.ts.map +1 -0
  5. package/dist/src/client/auth.js +629 -0
  6. package/dist/src/client/auth.js.map +1 -0
  7. package/dist/src/client/composition.d.ts +42 -0
  8. package/dist/src/client/composition.d.ts.map +1 -0
  9. package/dist/src/client/composition.js +523 -0
  10. package/dist/src/client/composition.js.map +1 -0
  11. package/dist/src/client/content.d.ts +73 -0
  12. package/dist/src/client/content.d.ts.map +1 -0
  13. package/dist/src/client/content.js +404 -0
  14. package/dist/src/client/content.js.map +1 -0
  15. package/dist/src/client/hooks.d.ts +62 -0
  16. package/dist/src/client/hooks.d.ts.map +1 -0
  17. package/dist/src/client/hooks.js +997 -0
  18. package/dist/src/client/hooks.js.map +1 -0
  19. package/dist/src/client/index.d.ts +11 -0
  20. package/dist/src/client/index.d.ts.map +1 -0
  21. package/dist/src/client/index.js +9 -0
  22. package/dist/src/client/index.js.map +1 -0
  23. package/dist/src/client/transport.d.ts +12 -0
  24. package/dist/src/client/transport.d.ts.map +1 -0
  25. package/dist/src/client/transport.js +347 -0
  26. package/dist/src/client/transport.js.map +1 -0
  27. package/dist/src/client/types.d.ts +117 -0
  28. package/dist/src/client/types.d.ts.map +1 -0
  29. package/dist/src/client/types.js +10 -0
  30. package/dist/src/client/types.js.map +1 -0
  31. package/dist/src/client/validation.d.ts +3 -0
  32. package/dist/src/client/validation.d.ts.map +1 -0
  33. package/dist/src/client/validation.js +25 -0
  34. package/dist/src/client/validation.js.map +1 -0
  35. package/dist/src/compaction.d.ts +27 -0
  36. package/dist/src/compaction.d.ts.map +1 -0
  37. package/dist/src/compaction.js +155 -0
  38. package/dist/src/compaction.js.map +1 -0
  39. package/dist/src/content-upload.d.ts +60 -0
  40. package/dist/src/content-upload.d.ts.map +1 -0
  41. package/dist/src/content-upload.js +129 -0
  42. package/dist/src/content-upload.js.map +1 -0
  43. package/dist/src/draft/generated.d.ts +3085 -0
  44. package/dist/src/draft/generated.d.ts.map +1 -0
  45. package/dist/src/draft/generated.js +15372 -0
  46. package/dist/src/draft/generated.js.map +1 -0
  47. package/dist/src/draft/index.d.ts +31 -0
  48. package/dist/src/draft/index.d.ts.map +1 -0
  49. package/dist/src/draft/index.js +129 -0
  50. package/dist/src/draft/index.js.map +1 -0
  51. package/dist/src/draft/schemas.d.ts +8848 -0
  52. package/dist/src/draft/schemas.d.ts.map +1 -0
  53. package/dist/src/draft/schemas.js +9219 -0
  54. package/dist/src/draft/schemas.js.map +1 -0
  55. package/dist/src/draft-atomic.d.ts +18 -0
  56. package/dist/src/draft-atomic.d.ts.map +1 -0
  57. package/dist/src/draft-atomic.js +102 -0
  58. package/dist/src/draft-atomic.js.map +1 -0
  59. package/dist/src/draft-runtime.d.ts +89 -0
  60. package/dist/src/draft-runtime.d.ts.map +1 -0
  61. package/dist/src/draft-runtime.js +242 -0
  62. package/dist/src/draft-runtime.js.map +1 -0
  63. package/dist/src/elicitation.d.ts +18 -0
  64. package/dist/src/elicitation.d.ts.map +1 -0
  65. package/dist/src/elicitation.js +191 -0
  66. package/dist/src/elicitation.js.map +1 -0
  67. package/dist/src/errors.d.ts +7 -0
  68. package/dist/src/errors.d.ts.map +1 -0
  69. package/dist/src/errors.js +15 -0
  70. package/dist/src/errors.js.map +1 -0
  71. package/dist/src/framing.d.ts +7 -0
  72. package/dist/src/framing.d.ts.map +1 -0
  73. package/dist/src/framing.js +39 -0
  74. package/dist/src/framing.js.map +1 -0
  75. package/dist/src/generated.d.ts +304 -0
  76. package/dist/src/generated.d.ts.map +1 -0
  77. package/dist/src/generated.js +1885 -0
  78. package/dist/src/generated.js.map +1 -0
  79. package/dist/src/hook-runner.d.ts +11 -0
  80. package/dist/src/hook-runner.d.ts.map +1 -0
  81. package/dist/src/hook-runner.js +223 -0
  82. package/dist/src/hook-runner.js.map +1 -0
  83. package/dist/src/index.d.ts +8 -0
  84. package/dist/src/index.d.ts.map +1 -0
  85. package/dist/src/index.js +8 -0
  86. package/dist/src/index.js.map +1 -0
  87. package/dist/src/observation.d.ts +26 -0
  88. package/dist/src/observation.d.ts.map +1 -0
  89. package/dist/src/observation.js +28 -0
  90. package/dist/src/observation.js.map +1 -0
  91. package/dist/src/server/attachments.d.ts +29 -0
  92. package/dist/src/server/attachments.d.ts.map +1 -0
  93. package/dist/src/server/attachments.js +112 -0
  94. package/dist/src/server/attachments.js.map +1 -0
  95. package/dist/src/server/hooks.d.ts +20 -0
  96. package/dist/src/server/hooks.d.ts.map +1 -0
  97. package/dist/src/server/hooks.js +129 -0
  98. package/dist/src/server/hooks.js.map +1 -0
  99. package/dist/src/server/index.d.ts +7 -0
  100. package/dist/src/server/index.d.ts.map +1 -0
  101. package/dist/src/server/index.js +4 -0
  102. package/dist/src/server/index.js.map +1 -0
  103. package/dist/src/server/stdio.d.ts +15 -0
  104. package/dist/src/server/stdio.d.ts.map +1 -0
  105. package/dist/src/server/stdio.js +69 -0
  106. package/dist/src/server/stdio.js.map +1 -0
  107. package/dist/src/transport.d.ts +7 -0
  108. package/dist/src/transport.d.ts.map +1 -0
  109. package/dist/src/transport.js +195 -0
  110. package/dist/src/transport.js.map +1 -0
  111. package/dist/src/types.d.ts +116 -0
  112. package/dist/src/types.d.ts.map +1 -0
  113. package/dist/src/types.js +14 -0
  114. package/dist/src/types.js.map +1 -0
  115. package/dist/src/validation.d.ts +7 -0
  116. package/dist/src/validation.d.ts.map +1 -0
  117. package/dist/src/validation.js +209 -0
  118. package/dist/src/validation.js.map +1 -0
  119. package/package.json +62 -4
  120. package/src/client/auth.ts +1076 -0
  121. package/src/client/composition.ts +708 -0
  122. package/src/client/content.ts +510 -0
  123. package/src/client/hooks.ts +1368 -0
  124. package/src/client/index.ts +28 -0
  125. package/src/client/transport.ts +408 -0
  126. package/src/client/types.ts +173 -0
  127. package/src/client/validation.ts +24 -0
  128. package/src/compaction.ts +196 -0
  129. package/src/content-upload.ts +205 -0
  130. package/src/draft/README.md +16 -0
  131. package/src/draft/ahp-codegen.lock.json +123 -0
  132. package/src/draft/generated.ts +19143 -0
  133. package/src/draft/index.ts +246 -0
  134. package/src/draft/schemas.ts +9413 -0
  135. package/src/draft-atomic.ts +119 -0
  136. package/src/draft-runtime.ts +365 -0
  137. package/src/elicitation.ts +254 -0
  138. package/src/errors.ts +30 -0
  139. package/src/framing.ts +56 -0
  140. package/src/generated.ts +2408 -0
  141. package/src/hook-runner.ts +310 -0
  142. package/src/index.ts +8 -0
  143. package/src/observation.ts +65 -0
  144. package/src/server/attachments.ts +128 -0
  145. package/src/server/hooks.ts +205 -0
  146. package/src/server/index.ts +18 -0
  147. package/src/server/node-crypto.d.ts +7 -0
  148. package/src/server/stdio.ts +81 -0
  149. package/src/transport.ts +273 -0
  150. package/src/types.ts +124 -0
  151. package/src/validation.ts +372 -0
@@ -0,0 +1,1076 @@
1
+ import type { Authentication } from "../draft/generated.js";
2
+
3
+ export interface Credential {
4
+ token: string;
5
+ /** Absolute expiry in milliseconds since the Unix epoch. */
6
+ expiresAt?: number;
7
+ refreshToken?: string;
8
+ scopes?: string[];
9
+ }
10
+ export type AuthCredential = Credential;
11
+
12
+ /** Context for a selected delivery binding, or an optional standalone auth helper. */
13
+ export interface AuthContext {
14
+ /** Selected registration backend. Omitted for standalone authentication calls. */
15
+ backendId?: string;
16
+ /** Independent delivery binding. Omitted for standalone authentication calls. */
17
+ purpose?: "event" | "upload";
18
+ authentication?: Authentication;
19
+ url: string;
20
+ signal?: AbortSignal;
21
+ challenge?: string;
22
+ /** Set on a 401 even when WWW-Authenticate is absent. */
23
+ challenged?: boolean;
24
+ cache?: Map<string, Credential>;
25
+ /** Optional endpoint discovery state; factory also scopes this by cache identity. */
26
+ discoveryCache?: Map<
27
+ string,
28
+ { client: OAuthClient; metadata: OAuthMetadata }
29
+ >;
30
+ /** Installed by the factory so exported defaults retain composed overrides. */
31
+ provider?: AuthProvider;
32
+ authOptions?: AuthOptions;
33
+ }
34
+ /** The delivery mechanisms currently supported by AHP all use bearer tokens. */
35
+ export interface DeliveryCredential {
36
+ type: "bearer";
37
+ token: string;
38
+ /** Provider-owned identity, returned unchanged on rejection; never log it. */
39
+ attempt?: unknown;
40
+ }
41
+
42
+ /** Hooks always supplies backend identity and the independently selected binding. */
43
+ export interface DeliveryAuthContext extends AuthContext {
44
+ backendId: string;
45
+ purpose: "event" | "upload";
46
+ }
47
+
48
+ export interface AuthChallengeContext extends DeliveryAuthContext {
49
+ /** Actual endpoint response, including status and WWW-Authenticate headers. */
50
+ response: Response;
51
+ /** Exact credential used for this attempt, including its opaque attempt identity. */
52
+ credential: DeliveryCredential | undefined;
53
+ }
54
+
55
+ /**
56
+ * Caller-owned authentication infrastructure. Hooks neither closes this provider
57
+ * nor manages its token lifecycle. Both callbacks must honor the operation signal.
58
+ * Credentials and attempt identities must not be included in diagnostics.
59
+ */
60
+ export interface DeliveryAuthProvider {
61
+ /** Undefined means no credential; configured authentication must fail closed. */
62
+ credential(
63
+ context: DeliveryAuthContext,
64
+ ): DeliveryCredential | undefined | Promise<DeliveryCredential | undefined>;
65
+ /**
66
+ * Record a rejection before credentials are requested again. This does not
67
+ * authorize replay: Hooks decides whether a bounded retry is protocol-safe.
68
+ */
69
+ challenge(context: AuthChallengeContext): void | Promise<void>;
70
+ }
71
+
72
+ export interface OAuthClient {
73
+ issuer: string;
74
+ resource: string;
75
+ clientId: string;
76
+ flow: "authorization_code_pkce" | "client_credentials";
77
+ scopes?: string[];
78
+ clientSecretRef?: string;
79
+ redirectUri?: string;
80
+ }
81
+ export interface AuthorizationRequest {
82
+ url: string;
83
+ redirectUri: string;
84
+ state: string;
85
+ signal?: AbortSignal;
86
+ }
87
+ export interface OAuthMetadata {
88
+ issuer: string;
89
+ token_endpoint: string;
90
+ authorization_endpoint?: string;
91
+ grant_types_supported?: string[];
92
+ code_challenge_methods_supported?: string[];
93
+ token_endpoint_auth_methods_supported?: string[];
94
+ }
95
+ export interface TokenRequest {
96
+ client: OAuthClient;
97
+ metadata: OAuthMetadata;
98
+ parameters: URLSearchParams;
99
+ }
100
+ export interface AuthorizationCodeRequest extends TokenRequest {
101
+ code: string;
102
+ verifier: string;
103
+ redirectUri: string;
104
+ }
105
+ export interface AuthOptions extends Partial<AuthProvider> {
106
+ fetch?: typeof globalThis.fetch;
107
+ /** Trusted DNS adapter; production adapters must enforce the checked addresses. */
108
+ resolveHostname?: (hostname: string) => Promise<readonly string[]>;
109
+ /** Exact origins permitted for explicitly trusted local test/process boundaries. */
110
+ allowedLoopbackOrigins?: readonly string[];
111
+ /** Optional additional host restrictions, applied after built-in DNS/private-network checks. */
112
+ validateDestination?: (
113
+ url: URL,
114
+ purpose: "metadata" | "authorization" | "token",
115
+ context: AuthContext,
116
+ ) => void | Promise<void>;
117
+ /** Automatic discovery never selects a client identity or trusts an issuer itself. */
118
+ selectClient?: (
119
+ issuers: readonly string[],
120
+ resource: string,
121
+ context: AuthContext,
122
+ ) => OAuthClient | Promise<OAuthClient>;
123
+ /** Receives the PKCE URL and returns the complete callback URL, not just a code. */
124
+ authorize?: (
125
+ request: AuthorizationRequest,
126
+ context: AuthContext,
127
+ ) => string | URL | Promise<string | URL>;
128
+ redirectUri?: string;
129
+ now?: () => number;
130
+ expirySkewMs?: number;
131
+ }
132
+ export interface AuthProvider {
133
+ authenticate(
134
+ context: AuthContext,
135
+ options?: AuthOptions,
136
+ ): Promise<Credential | undefined>;
137
+ resolveEnvironmentVariable(
138
+ name: string,
139
+ context?: AuthContext,
140
+ ): string | undefined | Promise<string | undefined>;
141
+ resolveCredentialReference(
142
+ reference: string,
143
+ context: AuthContext,
144
+ ): string | undefined | Promise<string | undefined>;
145
+ discover(
146
+ context: AuthContext,
147
+ options?: AuthOptions,
148
+ ): Promise<{ client: OAuthClient; metadata: OAuthMetadata }>;
149
+ exchangeAuthorizationCode(
150
+ request: AuthorizationCodeRequest,
151
+ context: AuthContext,
152
+ options?: AuthOptions,
153
+ ): Promise<Credential>;
154
+ getToken(
155
+ key: string,
156
+ context: AuthContext,
157
+ ): Credential | undefined | Promise<Credential | undefined>;
158
+ setToken(
159
+ key: string,
160
+ credential: Credential,
161
+ context: AuthContext,
162
+ ): void | Promise<void>;
163
+ deleteToken(key: string, context: AuthContext): void | Promise<void>;
164
+ refresh(
165
+ context: AuthContext,
166
+ request: TokenRequest,
167
+ refreshToken: string,
168
+ options?: AuthOptions,
169
+ ): Promise<Credential>;
170
+ clientCredentials(
171
+ context: AuthContext,
172
+ request: TokenRequest,
173
+ options?: AuthOptions,
174
+ ): Promise<Credential>;
175
+ requestToken(
176
+ context: AuthContext,
177
+ request: TokenRequest,
178
+ options?: AuthOptions,
179
+ ): Promise<Credential>;
180
+ }
181
+ export type AuthOverrides = AuthOptions;
182
+
183
+ function error(message: string): never {
184
+ // Never include credential values, callback URLs, or token endpoint bodies.
185
+ throw new Error(`Authentication failed: ${message}`);
186
+ }
187
+ function record(value: unknown): Record<string, unknown> {
188
+ if (!value || typeof value !== "object" || Array.isArray(value))
189
+ error("invalid metadata");
190
+ return value as Record<string, unknown>;
191
+ }
192
+ function string(value: unknown, name: string): string {
193
+ if (typeof value !== "string" || !value) error(`missing ${name}`);
194
+ return value;
195
+ }
196
+ function strings(value: unknown): string[] {
197
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string"))
198
+ error("invalid metadata list");
199
+ return value as string[];
200
+ }
201
+ function loopback(host: string): boolean {
202
+ return (
203
+ host === "localhost" ||
204
+ host.endsWith(".localhost") ||
205
+ host === "[::1]" ||
206
+ host === "::1" ||
207
+ /^127\./.test(host)
208
+ );
209
+ }
210
+ function publicAddress(address: string): boolean {
211
+ const host = address.toLowerCase().replace(/^\[|\]$/g, "");
212
+ if (host.includes(":"))
213
+ return (
214
+ /^[23][\da-f]{3}:/.test(host) && !/^(?:2001:(?:db8|0):|2002:)/.test(host)
215
+ );
216
+ const octets = host.split(".").map(Number);
217
+ if (
218
+ octets.length !== 4 ||
219
+ octets.some((n) => !Number.isInteger(n) || n < 0 || n > 255)
220
+ )
221
+ return false;
222
+ const [a = 0, b = 0, c = 0] = octets;
223
+ return !(
224
+ a === 0 ||
225
+ a === 10 ||
226
+ a === 127 ||
227
+ a >= 224 ||
228
+ (a === 100 && b >= 64 && b <= 127) ||
229
+ (a === 169 && b === 254) ||
230
+ (a === 172 && b >= 16 && b <= 31) ||
231
+ (a === 192 && (b === 168 || b === 0 || (b === 88 && c === 99))) ||
232
+ (a === 198 && (b === 18 || b === 19 || (b === 51 && c === 100))) ||
233
+ (a === 203 && b === 0 && c === 113)
234
+ );
235
+ }
236
+ function secureUrl(value: string, options: AuthOptions = {}): URL {
237
+ const url = new URL(value);
238
+ const local =
239
+ url.protocol === "http:" &&
240
+ loopback(url.hostname) &&
241
+ options.allowedLoopbackOrigins?.includes(url.origin);
242
+ if (
243
+ (url.protocol !== "https:" && !local) ||
244
+ url.username ||
245
+ url.password ||
246
+ url.hash
247
+ )
248
+ error("HTTPS URL required");
249
+ return url;
250
+ }
251
+ type CheckedURL = URL & { resolvedAddresses: readonly string[] };
252
+ async function destination(
253
+ value: string,
254
+ purpose: "metadata" | "authorization" | "token",
255
+ context: AuthContext,
256
+ options: AuthOptions,
257
+ ): Promise<CheckedURL> {
258
+ const url = secureUrl(value, options);
259
+ const host = url.hostname.toLowerCase();
260
+ const local =
261
+ loopback(host) && options.allowedLoopbackOrigins?.includes(url.origin);
262
+ if (!local) {
263
+ if (
264
+ loopback(host) ||
265
+ host.endsWith(".local") ||
266
+ (!host.includes(".") && !host.includes(":"))
267
+ )
268
+ error("forbidden destination");
269
+ if ((/^[\d.]+$/.test(host) || host.includes(":")) && !publicAddress(host))
270
+ error("forbidden destination");
271
+ }
272
+ let addresses: readonly string[] = host.includes(":")
273
+ ? ["::1"]
274
+ : ["127.0.0.1"];
275
+ if (!local) {
276
+ addresses = await withinBudget(
277
+ (async () => {
278
+ if (options.resolveHostname) return options.resolveHostname(host);
279
+ // Keep the Node-only resolver lazy: browser hosts must provide a resolver
280
+ // and a fetch adapter that enforces the checked destination addresses.
281
+ const moduleName = "node:dns/promises";
282
+ const dns = (await import(moduleName)) as {
283
+ lookup(
284
+ host: string,
285
+ options: { all: true },
286
+ ): Promise<{ address: string }[]>;
287
+ };
288
+ return (
289
+ await dns.lookup(host.replace(/^\[|\]$/g, ""), { all: true })
290
+ ).map((entry) => entry.address);
291
+ })(),
292
+ context.signal,
293
+ );
294
+ if (
295
+ !addresses.length ||
296
+ addresses.some((address) => !publicAddress(address))
297
+ )
298
+ error("forbidden resolved destination");
299
+ }
300
+ if (options.validateDestination)
301
+ await withinBudget(
302
+ Promise.resolve(options.validateDestination(url, purpose, context)),
303
+ context.signal,
304
+ );
305
+ return Object.assign(url, { resolvedAddresses: addresses });
306
+ }
307
+
308
+ /** Node's default transport pins the checked addresses, preventing DNS rebinding.
309
+ * A custom fetch is a trusted host adapter and receives resolvedAddresses on URL.
310
+ * Redirects are never followed, and token/metadata response bodies are bounded.
311
+ */
312
+ async function fetchChecked(
313
+ url: CheckedURL,
314
+ init: RequestInit,
315
+ options: AuthOptions,
316
+ ): Promise<Response> {
317
+ if (options.fetch) return options.fetch(url, init);
318
+ interface Incoming extends AsyncIterable<Uint8Array> {
319
+ statusCode?: number;
320
+ headers: Record<string, string | string[] | undefined>;
321
+ }
322
+ interface Outgoing {
323
+ on(event: "error", listener: (error: Error) => void): void;
324
+ end(body?: string): void;
325
+ destroy(error?: Error): void;
326
+ }
327
+ const moduleName = url.protocol === "http:" ? "node:http" : "node:https";
328
+ const http = (await import(moduleName)) as {
329
+ request(
330
+ url: URL,
331
+ options: Record<string, unknown>,
332
+ callback: (response: Incoming) => void,
333
+ ): Outgoing;
334
+ };
335
+ return new Promise<Response>((resolve, reject) => {
336
+ const request = http.request(
337
+ url,
338
+ {
339
+ method: init.method ?? "GET",
340
+ headers: Object.fromEntries(new Headers(init.headers)),
341
+ signal: init.signal,
342
+ lookup(
343
+ _hostname: string,
344
+ lookupOptions: { all?: boolean },
345
+ callback: (...args: unknown[]) => void,
346
+ ) {
347
+ const addresses = url.resolvedAddresses.map((address) => ({
348
+ address,
349
+ family: address.includes(":") ? 6 : 4,
350
+ }));
351
+ if (lookupOptions.all) callback(null, addresses);
352
+ else callback(null, addresses[0]?.address, addresses[0]?.family);
353
+ },
354
+ },
355
+ (incoming) => {
356
+ void (async () => {
357
+ const headers = new Headers();
358
+ for (const [key, value] of Object.entries(incoming.headers)) {
359
+ if (Array.isArray(value))
360
+ for (const item of value) headers.append(key, item);
361
+ else if (value !== undefined) headers.set(key, value);
362
+ }
363
+ const chunks: Uint8Array[] = [];
364
+ let size = 0;
365
+ for await (const chunk of incoming) {
366
+ size += chunk.length;
367
+ if (size > 1_048_576) {
368
+ request.destroy();
369
+ error("metadata or token response too large");
370
+ }
371
+ chunks.push(chunk);
372
+ }
373
+ const body = new Uint8Array(size);
374
+ let offset = 0;
375
+ for (const chunk of chunks) {
376
+ body.set(chunk, offset);
377
+ offset += chunk.length;
378
+ }
379
+ const status = incoming.statusCode ?? 500;
380
+ resolve(
381
+ new Response([204, 205, 304].includes(status) ? null : body, {
382
+ status,
383
+ headers,
384
+ }),
385
+ );
386
+ })().catch(reject);
387
+ },
388
+ );
389
+ request.on("error", () =>
390
+ reject(new Error("Authentication failed: network request failed")),
391
+ );
392
+ request.end(init.body?.toString());
393
+ });
394
+ }
395
+ async function withinBudget<T>(
396
+ promise: Promise<T>,
397
+ signal?: AbortSignal,
398
+ ): Promise<T> {
399
+ if (!signal) return promise;
400
+ if (signal.aborted) {
401
+ // The host callback may have aborted synchronously after returning work.
402
+ void promise.catch(() => {});
403
+ signal.throwIfAborted();
404
+ }
405
+ return new Promise<T>((resolve, reject) => {
406
+ const abort = () =>
407
+ reject(signal.reason ?? new Error("Authentication cancelled"));
408
+ signal.addEventListener("abort", abort, { once: true });
409
+ promise
410
+ .then(resolve, reject)
411
+ .finally(() => signal.removeEventListener("abort", abort));
412
+ });
413
+ }
414
+ function validateCredential(
415
+ credential: Credential,
416
+ options: AuthOptions,
417
+ ): Credential {
418
+ if (!credential.token || /[\s\x00-\x1f\x7f]/.test(credential.token))
419
+ error("invalid bearer token");
420
+ if (
421
+ credential.expiresAt !== undefined &&
422
+ (!Number.isFinite(credential.expiresAt) ||
423
+ credential.expiresAt <= (options.now ?? Date.now)())
424
+ )
425
+ error("expired token");
426
+ return credential;
427
+ }
428
+ function cacheKey(context: AuthContext, client: OAuthClient): string {
429
+ return JSON.stringify([
430
+ context.url,
431
+ client.issuer,
432
+ client.resource,
433
+ client.clientId,
434
+ client.flow,
435
+ client.clientSecretRef,
436
+ [...(client.scopes ?? [])].sort(),
437
+ ]);
438
+ }
439
+ function base64url(bytes: Uint8Array): string {
440
+ return btoa(Array.from(bytes, (byte) => String.fromCharCode(byte)).join(""))
441
+ .replace(/\+/g, "-")
442
+ .replace(/\//g, "_")
443
+ .replace(/=+$/, "");
444
+ }
445
+ function challengeParameter(
446
+ challenge: string | undefined,
447
+ name: string,
448
+ ): string | undefined {
449
+ if (!challenge) return undefined;
450
+ const bearer = /(?:^|,)\s*Bearer\s+/i.exec(challenge);
451
+ if (!bearer) return undefined;
452
+ const tail = challenge.slice(bearer.index + bearer[0].length);
453
+ // Tokenize quoted parameters (commas inside quoted strings are not separators).
454
+ const parameters = tail.match(/(?:[^,"]|"(?:\\.|[^"\\])*")+/g) ?? [];
455
+ for (const parameter of parameters) {
456
+ const match =
457
+ /^\s*([\w-]+)\s*=\s*(?:"((?:\\.|[^"\\])*)"|([^,\s]+))\s*$/.exec(
458
+ parameter,
459
+ );
460
+ if (!match) break; // Another authentication scheme begins here.
461
+ if (match[1]?.toLowerCase() === name.toLowerCase())
462
+ return (match[2] ?? match[3] ?? "").replace(/\\(.)/g, "$1");
463
+ }
464
+ return undefined;
465
+ }
466
+ async function metadataAt(
467
+ urls: string[],
468
+ context: AuthContext,
469
+ options: AuthOptions,
470
+ ): Promise<Record<string, unknown>> {
471
+ for (const value of [...new Set(urls)]) {
472
+ const url = await destination(value, "metadata", context, options);
473
+ const response = await withinBudget(
474
+ fetchChecked(
475
+ url,
476
+ {
477
+ redirect: "error",
478
+ ...(context.signal ? { signal: context.signal } : {}),
479
+ },
480
+ options,
481
+ ),
482
+ context.signal,
483
+ );
484
+ if (response.status === 404) continue;
485
+ if (!response.ok) error("metadata request rejected");
486
+ return record(await withinBudget(response.json(), context.signal));
487
+ }
488
+ return error("metadata unavailable");
489
+ }
490
+ function validateResource(
491
+ value: string,
492
+ endpoint: string,
493
+ options: AuthOptions,
494
+ ): void {
495
+ const resource = secureUrl(value, options),
496
+ target = secureUrl(endpoint, options);
497
+ if (
498
+ resource.search ||
499
+ resource.origin !== target.origin ||
500
+ !(
501
+ target.pathname === resource.pathname ||
502
+ target.pathname.startsWith(resource.pathname.replace(/\/$/, "") + "/")
503
+ )
504
+ )
505
+ error("resource does not cover endpoint");
506
+ }
507
+
508
+ const defaults: AuthProvider = {
509
+ getToken(key, context) {
510
+ return context.cache?.get(key);
511
+ },
512
+ setToken(key, credential, context) {
513
+ (context.cache ?? (context.cache = new Map())).set(key, credential);
514
+ },
515
+ deleteToken(key, context) {
516
+ context.cache?.delete(key);
517
+ },
518
+ resolveEnvironmentVariable(name) {
519
+ return (
520
+ globalThis as { process?: { env?: Record<string, string | undefined> } }
521
+ ).process?.env?.[name];
522
+ },
523
+ resolveCredentialReference() {
524
+ return undefined;
525
+ },
526
+ async discover(context, options = {}) {
527
+ const binding = context.authentication as
528
+ | Record<string, unknown>
529
+ | undefined;
530
+ const endpoint = secureUrl(context.url, options);
531
+ let client: OAuthClient;
532
+ let resource: string;
533
+ if (binding?.type === "oauth") {
534
+ const flow = binding.flow;
535
+ if (flow !== "authorization_code_pkce" && flow !== "client_credentials")
536
+ error("unsupported OAuth flow");
537
+ client = {
538
+ issuer: string(binding.issuer, "issuer"),
539
+ resource: string(binding.resource, "resource"),
540
+ clientId: string(binding.clientId, "clientId"),
541
+ flow,
542
+ ...(binding.scopes !== undefined
543
+ ? { scopes: strings(binding.scopes) }
544
+ : {}),
545
+ ...(binding.clientSecretRef !== undefined
546
+ ? {
547
+ clientSecretRef: string(
548
+ binding.clientSecretRef,
549
+ "clientSecretRef",
550
+ ),
551
+ }
552
+ : {}),
553
+ };
554
+ resource = client.resource;
555
+ const advertised = challengeParameter(
556
+ context.challenge,
557
+ "resource_metadata",
558
+ );
559
+ const metadata = await metadataAt(
560
+ advertised
561
+ ? [advertised]
562
+ : [
563
+ `${endpoint.origin}/.well-known/oauth-protected-resource${endpoint.pathname === "/" ? "" : endpoint.pathname}`,
564
+ `${endpoint.origin}/.well-known/oauth-protected-resource`,
565
+ ],
566
+ context,
567
+ options,
568
+ );
569
+ if (
570
+ metadata.resource !== resource ||
571
+ !strings(metadata.authorization_servers).includes(client.issuer)
572
+ )
573
+ error("preset constraints violated");
574
+ } else {
575
+ const advertised = challengeParameter(
576
+ context.challenge,
577
+ "resource_metadata",
578
+ );
579
+ const metadata = await metadataAt(
580
+ advertised
581
+ ? [advertised]
582
+ : [
583
+ `${endpoint.origin}/.well-known/oauth-protected-resource${endpoint.pathname === "/" ? "" : endpoint.pathname}`,
584
+ `${endpoint.origin}/.well-known/oauth-protected-resource`,
585
+ ],
586
+ context,
587
+ options,
588
+ );
589
+ resource = string(metadata.resource, "resource");
590
+ validateResource(resource, context.url, options);
591
+ const issuers = strings(metadata.authorization_servers);
592
+ if (issuers.length === 0) error("authorization server missing");
593
+ for (const issuer of issuers) secureUrl(issuer, options);
594
+ if (!options.selectClient) error("OAuth client selection required");
595
+ client = await withinBudget(
596
+ Promise.resolve(options.selectClient(issuers, resource, context)),
597
+ context.signal,
598
+ );
599
+ if (!issuers.includes(client.issuer) || client.resource !== resource)
600
+ error("untrusted issuer or resource");
601
+ }
602
+ validateResource(resource, context.url, options);
603
+ const issuer = secureUrl(client.issuer, options);
604
+ if (issuer.search) error("invalid issuer");
605
+ if (
606
+ !client.clientId ||
607
+ !["authorization_code_pkce", "client_credentials"].includes(client.flow)
608
+ )
609
+ error("invalid client configuration");
610
+ const requiredScopes =
611
+ challengeParameter(context.challenge, "scope")
612
+ ?.split(/\s+/)
613
+ .filter(Boolean) ?? [];
614
+ if (requiredScopes.some((scope) => !(client.scopes ?? []).includes(scope)))
615
+ error("challenge exceeds permitted scopes");
616
+ const path =
617
+ issuer.pathname === "/" ? "" : issuer.pathname.replace(/\/$/, "");
618
+ const discovered = await metadataAt(
619
+ [
620
+ `${issuer.origin}/.well-known/oauth-authorization-server${path}`,
621
+ `${issuer.origin}/.well-known/openid-configuration${path}`,
622
+ `${issuer.origin}${path}/.well-known/openid-configuration`,
623
+ ],
624
+ context,
625
+ options,
626
+ );
627
+ if (discovered.issuer !== client.issuer) error("issuer mismatch");
628
+ const metadata: OAuthMetadata = {
629
+ issuer: client.issuer,
630
+ token_endpoint: string(discovered.token_endpoint, "token endpoint"),
631
+ ...(discovered.authorization_endpoint !== undefined
632
+ ? {
633
+ authorization_endpoint: string(
634
+ discovered.authorization_endpoint,
635
+ "authorization endpoint",
636
+ ),
637
+ }
638
+ : {}),
639
+ ...(discovered.grant_types_supported !== undefined
640
+ ? { grant_types_supported: strings(discovered.grant_types_supported) }
641
+ : {}),
642
+ ...(discovered.code_challenge_methods_supported !== undefined
643
+ ? {
644
+ code_challenge_methods_supported: strings(
645
+ discovered.code_challenge_methods_supported,
646
+ ),
647
+ }
648
+ : {}),
649
+ ...(discovered.token_endpoint_auth_methods_supported !== undefined
650
+ ? {
651
+ token_endpoint_auth_methods_supported: strings(
652
+ discovered.token_endpoint_auth_methods_supported,
653
+ ),
654
+ }
655
+ : {}),
656
+ };
657
+ await destination(metadata.token_endpoint, "token", context, options);
658
+ return { client, metadata };
659
+ },
660
+ async requestToken(context, request, options = {}) {
661
+ const { client, metadata } = request;
662
+ const parameters = new URLSearchParams(request.parameters);
663
+ parameters.set("client_id", client.clientId);
664
+ parameters.set("resource", client.resource);
665
+ if (client.scopes?.length) parameters.set("scope", client.scopes.join(" "));
666
+ const url = await destination(
667
+ metadata.token_endpoint,
668
+ "token",
669
+ context,
670
+ options,
671
+ );
672
+ const headers: Record<string, string> = {
673
+ "content-type": "application/x-www-form-urlencoded",
674
+ accept: "application/json",
675
+ };
676
+ if (client.clientSecretRef) {
677
+ const secret = await withinBudget(
678
+ Promise.resolve(
679
+ this.resolveCredentialReference(client.clientSecretRef, context),
680
+ ),
681
+ context.signal,
682
+ );
683
+ if (!secret) error("client secret unavailable");
684
+ const methods = metadata.token_endpoint_auth_methods_supported ?? [
685
+ "client_secret_basic",
686
+ ];
687
+ if (methods.includes("client_secret_basic")) {
688
+ const encode = (value: string) =>
689
+ new URLSearchParams({ v: value }).toString().slice(2);
690
+ headers.authorization = `Basic ${btoa(`${encode(client.clientId)}:${encode(secret)}`)}`;
691
+ } else if (methods.includes("client_secret_post"))
692
+ parameters.set("client_secret", secret);
693
+ else error("unsupported client authentication");
694
+ } else if (
695
+ metadata.token_endpoint_auth_methods_supported &&
696
+ !metadata.token_endpoint_auth_methods_supported.includes("none")
697
+ )
698
+ error("client secret required");
699
+ const response = await withinBudget(
700
+ fetchChecked(
701
+ url,
702
+ {
703
+ method: "POST",
704
+ headers,
705
+ body: parameters,
706
+ redirect: "error",
707
+ ...(context.signal ? { signal: context.signal } : {}),
708
+ },
709
+ options,
710
+ ),
711
+ context.signal,
712
+ );
713
+ if (!response.ok) error("token request rejected");
714
+ const body = record(await withinBudget(response.json(), context.signal));
715
+ if (
716
+ typeof body.token_type !== "string" ||
717
+ body.token_type.toLowerCase() !== "bearer"
718
+ )
719
+ error("unsupported token type");
720
+ if (body.resource !== undefined && body.resource !== client.resource)
721
+ error("token resource mismatch");
722
+ if (
723
+ body.expires_in !== undefined &&
724
+ (typeof body.expires_in !== "number" ||
725
+ !Number.isFinite(body.expires_in) ||
726
+ body.expires_in <= 0)
727
+ )
728
+ error("invalid token expiry");
729
+ const scopes =
730
+ body.scope === undefined
731
+ ? client.scopes
732
+ : string(body.scope, "scope").split(/\s+/);
733
+ if (
734
+ scopes?.some((scope) => !(client.scopes ?? []).includes(scope)) ||
735
+ client.scopes?.some((scope) => !scopes?.includes(scope))
736
+ )
737
+ error("token scope mismatch");
738
+ return validateCredential(
739
+ {
740
+ token: string(body.access_token, "access token"),
741
+ ...(typeof body.expires_in === "number"
742
+ ? { expiresAt: (options.now ?? Date.now)() + body.expires_in * 1000 }
743
+ : {}),
744
+ ...(body.refresh_token !== undefined
745
+ ? { refreshToken: string(body.refresh_token, "refresh token") }
746
+ : {}),
747
+ ...(scopes ? { scopes } : {}),
748
+ },
749
+ options,
750
+ );
751
+ },
752
+ async exchangeAuthorizationCode(request, context, options) {
753
+ const parameters = new URLSearchParams(request.parameters);
754
+ parameters.set("grant_type", "authorization_code");
755
+ parameters.set("code", request.code);
756
+ parameters.set("code_verifier", request.verifier);
757
+ parameters.set("redirect_uri", request.redirectUri);
758
+ return this.requestToken(context, { ...request, parameters }, options);
759
+ },
760
+ async refresh(context, request, refreshToken, options) {
761
+ const parameters = new URLSearchParams(request.parameters);
762
+ parameters.set("grant_type", "refresh_token");
763
+ parameters.set("refresh_token", refreshToken);
764
+ const credential = await this.requestToken(
765
+ context,
766
+ { ...request, parameters },
767
+ options,
768
+ );
769
+ return {
770
+ ...credential,
771
+ refreshToken: credential.refreshToken ?? refreshToken,
772
+ };
773
+ },
774
+ async clientCredentials(context, request, options) {
775
+ if (!request.metadata.grant_types_supported?.includes("client_credentials"))
776
+ error("client credentials not advertised");
777
+ const parameters = new URLSearchParams(request.parameters);
778
+ parameters.set("grant_type", "client_credentials");
779
+ return this.requestToken(context, { ...request, parameters }, options);
780
+ },
781
+ async authenticate(context, options = {}) {
782
+ context.signal?.throwIfAborted();
783
+ const binding = context.authentication as
784
+ | Record<string, unknown>
785
+ | undefined;
786
+ if (binding?.type === "bearer") {
787
+ const env = binding.tokenEnv,
788
+ ref = binding.tokenRef;
789
+ if ((env !== undefined) === (ref !== undefined))
790
+ error("bearer requires exactly one credential source");
791
+ const token = await withinBudget(
792
+ Promise.resolve(
793
+ env !== undefined
794
+ ? this.resolveEnvironmentVariable(string(env, "tokenEnv"), context)
795
+ : this.resolveCredentialReference(string(ref, "tokenRef"), context),
796
+ ),
797
+ context.signal,
798
+ );
799
+ if (!token) error("bearer credential unavailable");
800
+ return validateCredential({ token }, options);
801
+ }
802
+ if (binding && binding.type !== "oauth")
803
+ error("unsupported authentication binding");
804
+ const discoveryCache =
805
+ context.discoveryCache ?? (context.discoveryCache = new Map());
806
+ const discoveryKey = JSON.stringify([
807
+ context.url,
808
+ context.authentication ?? null,
809
+ ]);
810
+ const known = discoveryCache.get(discoveryKey);
811
+ const challenged = context.challenged || context.challenge !== undefined;
812
+ if (!binding && !challenged && !known) return undefined;
813
+ const discovered =
814
+ known && !challenged ? known : await this.discover(context, options);
815
+ const { client, metadata } = discovered;
816
+ if (known && cacheKey(context, known.client) !== cacheKey(context, client))
817
+ error("OAuth identity changed");
818
+ const key = cacheKey(context, client);
819
+ const cached = await withinBudget(
820
+ Promise.resolve(this.getToken(key, context)),
821
+ context.signal,
822
+ );
823
+ const now = (options.now ?? Date.now)();
824
+ if (
825
+ cached &&
826
+ !context.challenged &&
827
+ context.challenge === undefined &&
828
+ (cached.expiresAt === undefined ||
829
+ cached.expiresAt > now + (options.expirySkewMs ?? 30_000))
830
+ )
831
+ return validateCredential(cached, options);
832
+ const request: TokenRequest = {
833
+ client,
834
+ metadata,
835
+ parameters: new URLSearchParams(),
836
+ };
837
+ let credential: Credential;
838
+ if (cached?.refreshToken) {
839
+ await withinBudget(
840
+ Promise.resolve(this.deleteToken(key, context)),
841
+ context.signal,
842
+ );
843
+ credential = await this.refresh(
844
+ context,
845
+ request,
846
+ cached.refreshToken,
847
+ options,
848
+ );
849
+ } else if (client.flow === "client_credentials") {
850
+ credential = await this.clientCredentials(context, request, options);
851
+ } else {
852
+ if (!options.authorize) error("interactive authorization unavailable");
853
+ if (!metadata.code_challenge_methods_supported?.includes("S256"))
854
+ error("PKCE S256 unavailable");
855
+ if (
856
+ metadata.grant_types_supported &&
857
+ !metadata.grant_types_supported.includes("authorization_code")
858
+ )
859
+ error("authorization code not supported");
860
+ const redirectUri = client.redirectUri ?? options.redirectUri;
861
+ if (!redirectUri) error("redirect URI required");
862
+ const redirect = new URL(redirectUri);
863
+ if (
864
+ redirect.username ||
865
+ redirect.password ||
866
+ redirect.hash ||
867
+ !(
868
+ redirect.protocol === "https:" ||
869
+ (redirect.protocol === "http:" &&
870
+ ["127.0.0.1", "[::1]"].includes(redirect.hostname))
871
+ )
872
+ )
873
+ error("invalid redirect URI");
874
+ if (
875
+ redirect.searchParams.has("code") ||
876
+ redirect.searchParams.has("state") ||
877
+ redirect.searchParams.has("error")
878
+ )
879
+ error("reserved redirect parameter");
880
+ const authorization = await destination(
881
+ string(metadata.authorization_endpoint, "authorization endpoint"),
882
+ "authorization",
883
+ context,
884
+ options,
885
+ );
886
+ const verifier = base64url(
887
+ globalThis.crypto.getRandomValues(new Uint8Array(32)),
888
+ );
889
+ const state = base64url(
890
+ globalThis.crypto.getRandomValues(new Uint8Array(32)),
891
+ );
892
+ const challenge = base64url(
893
+ new Uint8Array(
894
+ await globalThis.crypto.subtle.digest(
895
+ "SHA-256",
896
+ new TextEncoder().encode(verifier),
897
+ ),
898
+ ),
899
+ );
900
+ for (const [name, value] of Object.entries({
901
+ response_type: "code",
902
+ client_id: client.clientId,
903
+ redirect_uri: redirectUri,
904
+ resource: client.resource,
905
+ state,
906
+ code_challenge: challenge,
907
+ code_challenge_method: "S256",
908
+ }))
909
+ authorization.searchParams.set(name, value);
910
+ if (client.scopes?.length)
911
+ authorization.searchParams.set("scope", client.scopes.join(" "));
912
+ const callback = new URL(
913
+ await withinBudget(
914
+ Promise.resolve(
915
+ options.authorize(
916
+ {
917
+ url: authorization.href,
918
+ redirectUri,
919
+ state,
920
+ ...(context.signal ? { signal: context.signal } : {}),
921
+ },
922
+ context,
923
+ ),
924
+ ),
925
+ context.signal,
926
+ ),
927
+ );
928
+ if (
929
+ callback.origin !== redirect.origin ||
930
+ callback.pathname !== redirect.pathname ||
931
+ callback.hash ||
932
+ callback.username ||
933
+ callback.password
934
+ )
935
+ error("redirect mismatch");
936
+ for (const [name, value] of redirect.searchParams)
937
+ if (callback.searchParams.get(name) !== value)
938
+ error("redirect query mismatch");
939
+ if (
940
+ callback.searchParams.getAll("state").length !== 1 ||
941
+ callback.searchParams.get("state") !== state
942
+ )
943
+ error("state mismatch");
944
+ if (callback.searchParams.has("error")) error("authorization denied");
945
+ if (
946
+ callback.searchParams.has("iss") &&
947
+ callback.searchParams.get("iss") !== client.issuer
948
+ )
949
+ error("authorization issuer mismatch");
950
+ if (callback.searchParams.getAll("code").length !== 1)
951
+ error("authorization code missing");
952
+ credential = await this.exchangeAuthorizationCode(
953
+ {
954
+ ...request,
955
+ code: string(callback.searchParams.get("code"), "authorization code"),
956
+ verifier,
957
+ redirectUri,
958
+ },
959
+ context,
960
+ options,
961
+ );
962
+ }
963
+ validateCredential(credential, options);
964
+ await withinBudget(
965
+ Promise.resolve(this.setToken(key, credential, context)),
966
+ context.signal,
967
+ );
968
+ discoveryCache.set(discoveryKey, discovered);
969
+ return credential;
970
+ },
971
+ };
972
+
973
+ /** Compose host-specific policy with SDK defaults; defaults remain callable for delegation. */
974
+ export const auth = Object.assign(
975
+ function auth(overrides: AuthOverrides = {}): AuthProvider {
976
+ const provider = { ...defaults, ...overrides } as AuthProvider;
977
+ // Each composed provider owns its fallback cache. Callers can supply endpoint-
978
+ // scoped caches when sharing a provider; keys also include the full endpoint.
979
+ const cache = new Map<string, Credential>();
980
+ // Hooks can spread each context per request while retaining its token Map.
981
+ // A WeakMap keyed by that Map isolates discovery across Hooks instances even
982
+ // when they deliberately share this composed provider.
983
+ const discoveryCaches = new WeakMap<
984
+ Map<string, Credential>,
985
+ NonNullable<AuthContext["discoveryCache"]>
986
+ >();
987
+ const authenticate = provider.authenticate;
988
+ provider.authenticate = (context, options) => {
989
+ const configured = { ...overrides, ...options };
990
+ const tokens = context.cache ?? cache;
991
+ let discoveryCache =
992
+ context.discoveryCache ?? discoveryCaches.get(tokens);
993
+ if (!discoveryCache) {
994
+ discoveryCache = new Map();
995
+ discoveryCaches.set(tokens, discoveryCache);
996
+ }
997
+ return authenticate.call(
998
+ provider,
999
+ {
1000
+ ...context,
1001
+ cache: tokens,
1002
+ discoveryCache,
1003
+ provider,
1004
+ authOptions: configured,
1005
+ },
1006
+ configured,
1007
+ );
1008
+ };
1009
+ return provider;
1010
+ },
1011
+ {
1012
+ ...defaults,
1013
+ authenticate(context: AuthContext, options?: AuthOptions) {
1014
+ return defaults.authenticate.call(context.provider ?? defaults, context, {
1015
+ ...context.authOptions,
1016
+ ...options,
1017
+ });
1018
+ },
1019
+ exchangeAuthorizationCode(
1020
+ request: AuthorizationCodeRequest,
1021
+ context: AuthContext,
1022
+ options?: AuthOptions,
1023
+ ) {
1024
+ return defaults.exchangeAuthorizationCode.call(
1025
+ context.provider ?? defaults,
1026
+ request,
1027
+ context,
1028
+ { ...context.authOptions, ...options },
1029
+ );
1030
+ },
1031
+ discover(context: AuthContext, options?: AuthOptions) {
1032
+ return defaults.discover.call(context.provider ?? defaults, context, {
1033
+ ...context.authOptions,
1034
+ ...options,
1035
+ });
1036
+ },
1037
+ requestToken(
1038
+ context: AuthContext,
1039
+ request: TokenRequest,
1040
+ options?: AuthOptions,
1041
+ ) {
1042
+ return defaults.requestToken.call(
1043
+ context.provider ?? defaults,
1044
+ context,
1045
+ request,
1046
+ { ...context.authOptions, ...options },
1047
+ );
1048
+ },
1049
+ refresh(
1050
+ context: AuthContext,
1051
+ request: TokenRequest,
1052
+ refreshToken: string,
1053
+ options?: AuthOptions,
1054
+ ) {
1055
+ return defaults.refresh.call(
1056
+ context.provider ?? defaults,
1057
+ context,
1058
+ request,
1059
+ refreshToken,
1060
+ { ...context.authOptions, ...options },
1061
+ );
1062
+ },
1063
+ clientCredentials(
1064
+ context: AuthContext,
1065
+ request: TokenRequest,
1066
+ options?: AuthOptions,
1067
+ ) {
1068
+ return defaults.clientCredentials.call(
1069
+ context.provider ?? defaults,
1070
+ context,
1071
+ request,
1072
+ { ...context.authOptions, ...options },
1073
+ );
1074
+ },
1075
+ },
1076
+ );