@anthropic-ai/sdk 0.92.0 → 0.94.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 (97) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/client.d.mts +73 -0
  3. package/client.d.mts.map +1 -1
  4. package/client.d.ts +73 -0
  5. package/client.d.ts.map +1 -1
  6. package/client.js +258 -9
  7. package/client.js.map +1 -1
  8. package/client.mjs +258 -9
  9. package/client.mjs.map +1 -1
  10. package/core/credentials.d.mts +131 -0
  11. package/core/credentials.d.mts.map +1 -0
  12. package/core/credentials.d.ts +131 -0
  13. package/core/credentials.d.ts.map +1 -0
  14. package/core/credentials.js +317 -0
  15. package/core/credentials.js.map +1 -0
  16. package/core/credentials.mjs +277 -0
  17. package/core/credentials.mjs.map +1 -0
  18. package/internal/utils/time.d.mts +3 -0
  19. package/internal/utils/time.d.mts.map +1 -0
  20. package/internal/utils/time.d.ts +3 -0
  21. package/internal/utils/time.d.ts.map +1 -0
  22. package/internal/utils/time.js +8 -0
  23. package/internal/utils/time.js.map +1 -0
  24. package/internal/utils/time.mjs +5 -0
  25. package/internal/utils/time.mjs.map +1 -0
  26. package/lib/credentials/credential-chain.d.mts +39 -0
  27. package/lib/credentials/credential-chain.d.mts.map +1 -0
  28. package/lib/credentials/credential-chain.d.ts +39 -0
  29. package/lib/credentials/credential-chain.d.ts.map +1 -0
  30. package/lib/credentials/credential-chain.js +245 -0
  31. package/lib/credentials/credential-chain.js.map +1 -0
  32. package/lib/credentials/credential-chain.mjs +208 -0
  33. package/lib/credentials/credential-chain.mjs.map +1 -0
  34. package/lib/credentials/identity-token.d.mts +11 -0
  35. package/lib/credentials/identity-token.d.mts.map +1 -0
  36. package/lib/credentials/identity-token.d.ts +11 -0
  37. package/lib/credentials/identity-token.d.ts.map +1 -0
  38. package/lib/credentials/identity-token.js +72 -0
  39. package/lib/credentials/identity-token.js.map +1 -0
  40. package/lib/credentials/identity-token.mjs +35 -0
  41. package/lib/credentials/identity-token.mjs.map +1 -0
  42. package/lib/credentials/oidc-federation.d.mts +40 -0
  43. package/lib/credentials/oidc-federation.d.mts.map +1 -0
  44. package/lib/credentials/oidc-federation.d.ts +40 -0
  45. package/lib/credentials/oidc-federation.d.ts.map +1 -0
  46. package/lib/credentials/oidc-federation.js +82 -0
  47. package/lib/credentials/oidc-federation.js.map +1 -0
  48. package/lib/credentials/oidc-federation.mjs +79 -0
  49. package/lib/credentials/oidc-federation.mjs.map +1 -0
  50. package/lib/credentials/token-cache.d.mts +54 -0
  51. package/lib/credentials/token-cache.d.mts.map +1 -0
  52. package/lib/credentials/token-cache.d.ts +54 -0
  53. package/lib/credentials/token-cache.d.ts.map +1 -0
  54. package/lib/credentials/token-cache.js +112 -0
  55. package/lib/credentials/token-cache.js.map +1 -0
  56. package/lib/credentials/token-cache.mjs +108 -0
  57. package/lib/credentials/token-cache.mjs.map +1 -0
  58. package/lib/credentials/types.d.mts +96 -0
  59. package/lib/credentials/types.d.mts.map +1 -0
  60. package/lib/credentials/types.d.ts +96 -0
  61. package/lib/credentials/types.d.ts.map +1 -0
  62. package/lib/credentials/types.js +266 -0
  63. package/lib/credentials/types.js.map +1 -0
  64. package/lib/credentials/types.mjs +224 -0
  65. package/lib/credentials/types.mjs.map +1 -0
  66. package/lib/credentials/user-oauth.d.mts +21 -0
  67. package/lib/credentials/user-oauth.d.mts.map +1 -0
  68. package/lib/credentials/user-oauth.d.ts +21 -0
  69. package/lib/credentials/user-oauth.d.ts.map +1 -0
  70. package/lib/credentials/user-oauth.js +130 -0
  71. package/lib/credentials/user-oauth.js.map +1 -0
  72. package/lib/credentials/user-oauth.mjs +94 -0
  73. package/lib/credentials/user-oauth.mjs.map +1 -0
  74. package/lib/credentials.d.mts +4 -0
  75. package/lib/credentials.d.mts.map +1 -0
  76. package/lib/credentials.d.ts +4 -0
  77. package/lib/credentials.d.ts.map +1 -0
  78. package/lib/credentials.js +8 -0
  79. package/lib/credentials.js.map +1 -0
  80. package/lib/credentials.mjs +3 -0
  81. package/lib/credentials.mjs.map +1 -0
  82. package/package.json +1 -1
  83. package/src/client.ts +342 -14
  84. package/src/core/credentials.ts +379 -0
  85. package/src/internal/utils/time.ts +4 -0
  86. package/src/lib/credentials/credential-chain.ts +292 -0
  87. package/src/lib/credentials/identity-token.ts +37 -0
  88. package/src/lib/credentials/oidc-federation.ts +140 -0
  89. package/src/lib/credentials/token-cache.ts +130 -0
  90. package/src/lib/credentials/types.ts +295 -0
  91. package/src/lib/credentials/user-oauth.ts +144 -0
  92. package/src/lib/credentials.ts +3 -0
  93. package/src/version.ts +1 -1
  94. package/version.d.mts +1 -1
  95. package/version.d.ts +1 -1
  96. package/version.js +1 -1
  97. package/version.mjs +1 -1
@@ -0,0 +1,40 @@
1
+ import type { Fetch } from "../../internal/builtin-types.mjs";
2
+ import type { AccessTokenProvider, IdentityTokenProvider } from "./types.mjs";
3
+ export type OIDCFederationConfig = {
4
+ identityTokenProvider: IdentityTokenProvider;
5
+ federationRuleId: string;
6
+ organizationId: string;
7
+ serviceAccountId?: string | undefined;
8
+ /**
9
+ * Optional `wrkspc_*` tagged ID, or the literal `"default"` to scope the
10
+ * token to the organization's default workspace. When omitted the server
11
+ * picks the rule's sole enabled workspace, else the org default if the rule
12
+ * covers it. Required when the rule enables more than one non-default
13
+ * workspace, or to target a specific workspace other than the one the
14
+ * server would pick. The minted token is workspace-scoped: per-request
15
+ * workspace selection (the `anthropic-workspace-id` header) is not supported
16
+ * for federation tokens — switching workspaces requires a new token exchange
17
+ * with a different `workspaceId`.
18
+ */
19
+ workspaceId?: string | undefined;
20
+ baseURL: string;
21
+ fetch: Fetch;
22
+ /**
23
+ * Overrides the outgoing User-Agent header on the token exchange. When
24
+ * empty, sends an SDK-identified UA so the token endpoint's access logs
25
+ * identify the caller.
26
+ */
27
+ userAgent?: string | undefined;
28
+ };
29
+ /**
30
+ * Exchanges an external OIDC JWT for an Anthropic access token via the
31
+ * RFC 7523 jwt-bearer grant.
32
+ *
33
+ * Each invocation performs a fresh token exchange. Wrap in a
34
+ * {@link TokenCache} to avoid exchanging on every request.
35
+ *
36
+ * Federation grants do not return a refresh token — callers re-exchange
37
+ * their assertion on expiry.
38
+ */
39
+ export declare function oidcFederationProvider(config: OIDCFederationConfig): AccessTokenProvider;
40
+ //# sourceMappingURL=oidc-federation.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oidc-federation.d.mts","sourceRoot":"","sources":["../../src/lib/credentials/oidc-federation.ts"],"names":[],"mappings":"OAAO,KAAK,EAAE,KAAK,EAAE;OACd,KAAK,EAAE,mBAAmB,EAAE,qBAAqB,EAAE;AAc1D,MAAM,MAAM,oBAAoB,GAAG;IACjC,qBAAqB,EAAE,qBAAqB,CAAC;IAC7C,gBAAgB,EAAE,MAAM,CAAC;IACzB,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC;;;;;;;;;;OAUG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,KAAK,CAAC;IACb;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC,CAAC;AAEF;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,oBAAoB,GAAG,mBAAmB,CAuFxF"}
@@ -0,0 +1,40 @@
1
+ import type { Fetch } from "../../internal/builtin-types.js";
2
+ import type { AccessTokenProvider, IdentityTokenProvider } from "./types.js";
3
+ export type OIDCFederationConfig = {
4
+ identityTokenProvider: IdentityTokenProvider;
5
+ federationRuleId: string;
6
+ organizationId: string;
7
+ serviceAccountId?: string | undefined;
8
+ /**
9
+ * Optional `wrkspc_*` tagged ID, or the literal `"default"` to scope the
10
+ * token to the organization's default workspace. When omitted the server
11
+ * picks the rule's sole enabled workspace, else the org default if the rule
12
+ * covers it. Required when the rule enables more than one non-default
13
+ * workspace, or to target a specific workspace other than the one the
14
+ * server would pick. The minted token is workspace-scoped: per-request
15
+ * workspace selection (the `anthropic-workspace-id` header) is not supported
16
+ * for federation tokens — switching workspaces requires a new token exchange
17
+ * with a different `workspaceId`.
18
+ */
19
+ workspaceId?: string | undefined;
20
+ baseURL: string;
21
+ fetch: Fetch;
22
+ /**
23
+ * Overrides the outgoing User-Agent header on the token exchange. When
24
+ * empty, sends an SDK-identified UA so the token endpoint's access logs
25
+ * identify the caller.
26
+ */
27
+ userAgent?: string | undefined;
28
+ };
29
+ /**
30
+ * Exchanges an external OIDC JWT for an Anthropic access token via the
31
+ * RFC 7523 jwt-bearer grant.
32
+ *
33
+ * Each invocation performs a fresh token exchange. Wrap in a
34
+ * {@link TokenCache} to avoid exchanging on every request.
35
+ *
36
+ * Federation grants do not return a refresh token — callers re-exchange
37
+ * their assertion on expiry.
38
+ */
39
+ export declare function oidcFederationProvider(config: OIDCFederationConfig): AccessTokenProvider;
40
+ //# sourceMappingURL=oidc-federation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oidc-federation.d.ts","sourceRoot":"","sources":["../../src/lib/credentials/oidc-federation.ts"],"names":[],"mappings":"OAAO,KAAK,EAAE,KAAK,EAAE;OACd,KAAK,EAAE,mBAAmB,EAAE,qBAAqB,EAAE;AAc1D,MAAM,MAAM,oBAAoB,GAAG;IACjC,qBAAqB,EAAE,qBAAqB,CAAC;IAC7C,gBAAgB,EAAE,MAAM,CAAC;IACzB,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC;;;;;;;;;;OAUG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,KAAK,CAAC;IACb;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC,CAAC;AAEF;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,oBAAoB,GAAG,mBAAmB,CAuFxF"}
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.oidcFederationProvider = oidcFederationProvider;
4
+ const types_1 = require("./types.js");
5
+ const time_1 = require("../../internal/utils/time.js");
6
+ const version_1 = require("../../version.js");
7
+ /**
8
+ * Exchanges an external OIDC JWT for an Anthropic access token via the
9
+ * RFC 7523 jwt-bearer grant.
10
+ *
11
+ * Each invocation performs a fresh token exchange. Wrap in a
12
+ * {@link TokenCache} to avoid exchanging on every request.
13
+ *
14
+ * Federation grants do not return a refresh token — callers re-exchange
15
+ * their assertion on expiry.
16
+ */
17
+ function oidcFederationProvider(config) {
18
+ return async () => {
19
+ (0, types_1.requireSecureTokenEndpoint)(config.baseURL);
20
+ const jwt = await config.identityTokenProvider();
21
+ // The token endpoint enforces a 16 KiB assertion limit; surface a clear
22
+ // client-side error so misconfigured projected-token sources are
23
+ // diagnosable without a server round-trip.
24
+ if (jwt.length > 16 * 1024) {
25
+ throw new types_1.WorkloadIdentityError(`Identity token is ${Math.ceil(jwt.length / 1024)} KiB, exceeds the 16 KiB assertion limit`);
26
+ }
27
+ const body = {
28
+ grant_type: types_1.GRANT_TYPE_JWT_BEARER,
29
+ assertion: jwt,
30
+ federation_rule_id: config.federationRuleId,
31
+ organization_id: config.organizationId,
32
+ };
33
+ if (config.serviceAccountId) {
34
+ body['service_account_id'] = config.serviceAccountId;
35
+ }
36
+ if (config.workspaceId) {
37
+ body['workspace_id'] = config.workspaceId;
38
+ }
39
+ const url = `${config.baseURL}${types_1.TOKEN_ENDPOINT}`;
40
+ let resp;
41
+ try {
42
+ resp = await config.fetch(url, {
43
+ method: 'POST',
44
+ headers: {
45
+ 'Content-Type': 'application/json',
46
+ 'anthropic-beta': `${types_1.OAUTH_API_BETA_HEADER},${types_1.FEDERATION_BETA_HEADER}`,
47
+ 'User-Agent': config.userAgent || `anthropic-sdk-typescript/${version_1.VERSION} oidcFederationProvider`,
48
+ },
49
+ body: JSON.stringify(body),
50
+ });
51
+ }
52
+ catch (err) {
53
+ throw new types_1.WorkloadIdentityError(`Failed to reach token endpoint ${url}: ${err}`);
54
+ }
55
+ const requestId = resp.headers.get('Request-Id');
56
+ if (!resp.ok) {
57
+ const text = await resp.text().catch(() => '');
58
+ const redacted = (0, types_1.redactSensitive)(text);
59
+ // A 401 is hard to debug from the status code alone, so surface
60
+ // guidance: check the federation rule, optionally set a workspace ID
61
+ // (the most common fix when no workspaceId is configured), and point at
62
+ // the Workload identity page in Claude Console for the server-side
63
+ // authentication event log. Other statuses (5xx, 400, ...) get no hint.
64
+ let hint = '';
65
+ if (resp.status === 401) {
66
+ const hintMiddle = config.workspaceId ? '' : ("If your federation rule is scoped to multiple workspaces, set the ANTHROPIC_WORKSPACE_ID environment variable, the 'workspace_id' config key, or the `workspaceId` option. ");
67
+ hint = ` Ensure your federation rule matches your identity token. ${hintMiddle}View your authentication events in the Workload identity page of Claude Console for more details.`;
68
+ }
69
+ throw new types_1.WorkloadIdentityError(`Token exchange failed with status ${resp.status}${requestId ? ` (request-id ${requestId})` : ''}: ${redacted}${hint}`, resp.status, redacted, requestId);
70
+ }
71
+ const data = await (0, types_1.parseTokenResponse)(resp, requestId);
72
+ const expiresIn = Number(data.expires_in);
73
+ if (!Number.isFinite(expiresIn)) {
74
+ throw new types_1.WorkloadIdentityError(`Token endpoint response missing required fields: ${JSON.stringify((0, types_1.redactSensitive)(data))}`, resp.status, (0, types_1.redactSensitive)(data), requestId);
75
+ }
76
+ return {
77
+ token: data.access_token,
78
+ expiresAt: (0, time_1.nowAsSeconds)() + expiresIn,
79
+ };
80
+ };
81
+ }
82
+ //# sourceMappingURL=oidc-federation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oidc-federation.js","sourceRoot":"","sources":["../../src/lib/credentials/oidc-federation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,79 @@
1
+ import { FEDERATION_BETA_HEADER, GRANT_TYPE_JWT_BEARER, OAUTH_API_BETA_HEADER, TOKEN_ENDPOINT, WorkloadIdentityError, parseTokenResponse, redactSensitive, requireSecureTokenEndpoint, } from "./types.mjs";
2
+ import { nowAsSeconds } from "../../internal/utils/time.mjs";
3
+ import { VERSION } from "../../version.mjs";
4
+ /**
5
+ * Exchanges an external OIDC JWT for an Anthropic access token via the
6
+ * RFC 7523 jwt-bearer grant.
7
+ *
8
+ * Each invocation performs a fresh token exchange. Wrap in a
9
+ * {@link TokenCache} to avoid exchanging on every request.
10
+ *
11
+ * Federation grants do not return a refresh token — callers re-exchange
12
+ * their assertion on expiry.
13
+ */
14
+ export function oidcFederationProvider(config) {
15
+ return async () => {
16
+ requireSecureTokenEndpoint(config.baseURL);
17
+ const jwt = await config.identityTokenProvider();
18
+ // The token endpoint enforces a 16 KiB assertion limit; surface a clear
19
+ // client-side error so misconfigured projected-token sources are
20
+ // diagnosable without a server round-trip.
21
+ if (jwt.length > 16 * 1024) {
22
+ throw new WorkloadIdentityError(`Identity token is ${Math.ceil(jwt.length / 1024)} KiB, exceeds the 16 KiB assertion limit`);
23
+ }
24
+ const body = {
25
+ grant_type: GRANT_TYPE_JWT_BEARER,
26
+ assertion: jwt,
27
+ federation_rule_id: config.federationRuleId,
28
+ organization_id: config.organizationId,
29
+ };
30
+ if (config.serviceAccountId) {
31
+ body['service_account_id'] = config.serviceAccountId;
32
+ }
33
+ if (config.workspaceId) {
34
+ body['workspace_id'] = config.workspaceId;
35
+ }
36
+ const url = `${config.baseURL}${TOKEN_ENDPOINT}`;
37
+ let resp;
38
+ try {
39
+ resp = await config.fetch(url, {
40
+ method: 'POST',
41
+ headers: {
42
+ 'Content-Type': 'application/json',
43
+ 'anthropic-beta': `${OAUTH_API_BETA_HEADER},${FEDERATION_BETA_HEADER}`,
44
+ 'User-Agent': config.userAgent || `anthropic-sdk-typescript/${VERSION} oidcFederationProvider`,
45
+ },
46
+ body: JSON.stringify(body),
47
+ });
48
+ }
49
+ catch (err) {
50
+ throw new WorkloadIdentityError(`Failed to reach token endpoint ${url}: ${err}`);
51
+ }
52
+ const requestId = resp.headers.get('Request-Id');
53
+ if (!resp.ok) {
54
+ const text = await resp.text().catch(() => '');
55
+ const redacted = redactSensitive(text);
56
+ // A 401 is hard to debug from the status code alone, so surface
57
+ // guidance: check the federation rule, optionally set a workspace ID
58
+ // (the most common fix when no workspaceId is configured), and point at
59
+ // the Workload identity page in Claude Console for the server-side
60
+ // authentication event log. Other statuses (5xx, 400, ...) get no hint.
61
+ let hint = '';
62
+ if (resp.status === 401) {
63
+ const hintMiddle = config.workspaceId ? '' : ("If your federation rule is scoped to multiple workspaces, set the ANTHROPIC_WORKSPACE_ID environment variable, the 'workspace_id' config key, or the `workspaceId` option. ");
64
+ hint = ` Ensure your federation rule matches your identity token. ${hintMiddle}View your authentication events in the Workload identity page of Claude Console for more details.`;
65
+ }
66
+ throw new WorkloadIdentityError(`Token exchange failed with status ${resp.status}${requestId ? ` (request-id ${requestId})` : ''}: ${redacted}${hint}`, resp.status, redacted, requestId);
67
+ }
68
+ const data = await parseTokenResponse(resp, requestId);
69
+ const expiresIn = Number(data.expires_in);
70
+ if (!Number.isFinite(expiresIn)) {
71
+ throw new WorkloadIdentityError(`Token endpoint response missing required fields: ${JSON.stringify(redactSensitive(data))}`, resp.status, redactSensitive(data), requestId);
72
+ }
73
+ return {
74
+ token: data.access_token,
75
+ expiresAt: nowAsSeconds() + expiresIn,
76
+ };
77
+ };
78
+ }
79
+ //# sourceMappingURL=oidc-federation.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oidc-federation.mjs","sourceRoot":"","sources":["../../src/lib/credentials/oidc-federation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ import type { AccessTokenProvider } from "./types.mjs";
2
+ /**
3
+ * Wraps an {@link AccessTokenProvider} with two-tier proactive refresh
4
+ * and concurrent deduplication.
5
+ *
6
+ * Refresh policy on each {@link getToken} call:
7
+ *
8
+ * - No cached token → call provider (blocking), cache, return.
9
+ * - Cached with `expiresAt == null` → return cached forever.
10
+ * - More than 120s remaining → return cached.
11
+ * - 30–120s remaining (advisory window) → return stale token immediately,
12
+ * kick off background refresh. On failure, log and keep stale.
13
+ * - Less than 30s remaining or expired (mandatory) → block and refresh.
14
+ * On failure, throw.
15
+ *
16
+ * Concurrent mandatory callers coalesce into a single provider call.
17
+ */
18
+ export declare class TokenCache {
19
+ private provider;
20
+ private cached;
21
+ private pendingRefresh;
22
+ private nextForce;
23
+ private lastAdvisoryError;
24
+ private onAdvisoryRefreshError;
25
+ constructor(provider: AccessTokenProvider, onAdvisoryRefreshError?: (err: unknown) => void);
26
+ getToken(): Promise<string>;
27
+ /**
28
+ * Clears the cached token and marks the next {@link getToken} as a forced
29
+ * refresh, so the underlying provider bypasses any on-disk freshness check.
30
+ * Called after a 401 — the server has just told us the token is bad even
31
+ * if its `expires_at` still looks fresh.
32
+ */
33
+ invalidate(): void;
34
+ /**
35
+ * Mandatory refresh. Joins any in-flight refresh unless forced — a forced
36
+ * refresh must not coalesce into a non-forced one that may re-serve the
37
+ * same stale disk token.
38
+ */
39
+ private refresh;
40
+ /**
41
+ * Advisory background refresh. Shares the same in-flight promise as
42
+ * mandatory refreshes for deduplication, but swallows errors so the
43
+ * stale cached token keeps being served. Backs off for
44
+ * {@link ADVISORY_REFRESH_BACKOFF_IN_SECONDS} after a failure so an
45
+ * outage during the advisory window doesn't hammer the token endpoint.
46
+ */
47
+ private backgroundRefresh;
48
+ /**
49
+ * Core refresh. Sets {@link pendingRefresh} so concurrent callers
50
+ * (both advisory and mandatory) coalesce into a single provider call.
51
+ */
52
+ private doRefresh;
53
+ }
54
+ //# sourceMappingURL=token-cache.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-cache.d.mts","sourceRoot":"","sources":["../../src/lib/credentials/token-cache.ts"],"names":[],"mappings":"OAAO,KAAK,EAAe,mBAAmB,EAAE;AAQhD;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAsB;IACtC,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,cAAc,CAAqC;IAC3D,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,sBAAsB,CAAuC;gBAEzD,QAAQ,EAAE,mBAAmB,EAAE,sBAAsB,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI;IAKpF,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC;IA6BjC;;;;;OAKG;IACH,UAAU,IAAI,IAAI;IAKlB;;;;OAIG;IACH,OAAO,CAAC,OAAO;IAOf;;;;;;OAMG;IACH,OAAO,CAAC,iBAAiB;IAezB;;;OAGG;IACH,OAAO,CAAC,SAAS;CAclB"}
@@ -0,0 +1,54 @@
1
+ import type { AccessTokenProvider } from "./types.js";
2
+ /**
3
+ * Wraps an {@link AccessTokenProvider} with two-tier proactive refresh
4
+ * and concurrent deduplication.
5
+ *
6
+ * Refresh policy on each {@link getToken} call:
7
+ *
8
+ * - No cached token → call provider (blocking), cache, return.
9
+ * - Cached with `expiresAt == null` → return cached forever.
10
+ * - More than 120s remaining → return cached.
11
+ * - 30–120s remaining (advisory window) → return stale token immediately,
12
+ * kick off background refresh. On failure, log and keep stale.
13
+ * - Less than 30s remaining or expired (mandatory) → block and refresh.
14
+ * On failure, throw.
15
+ *
16
+ * Concurrent mandatory callers coalesce into a single provider call.
17
+ */
18
+ export declare class TokenCache {
19
+ private provider;
20
+ private cached;
21
+ private pendingRefresh;
22
+ private nextForce;
23
+ private lastAdvisoryError;
24
+ private onAdvisoryRefreshError;
25
+ constructor(provider: AccessTokenProvider, onAdvisoryRefreshError?: (err: unknown) => void);
26
+ getToken(): Promise<string>;
27
+ /**
28
+ * Clears the cached token and marks the next {@link getToken} as a forced
29
+ * refresh, so the underlying provider bypasses any on-disk freshness check.
30
+ * Called after a 401 — the server has just told us the token is bad even
31
+ * if its `expires_at` still looks fresh.
32
+ */
33
+ invalidate(): void;
34
+ /**
35
+ * Mandatory refresh. Joins any in-flight refresh unless forced — a forced
36
+ * refresh must not coalesce into a non-forced one that may re-serve the
37
+ * same stale disk token.
38
+ */
39
+ private refresh;
40
+ /**
41
+ * Advisory background refresh. Shares the same in-flight promise as
42
+ * mandatory refreshes for deduplication, but swallows errors so the
43
+ * stale cached token keeps being served. Backs off for
44
+ * {@link ADVISORY_REFRESH_BACKOFF_IN_SECONDS} after a failure so an
45
+ * outage during the advisory window doesn't hammer the token endpoint.
46
+ */
47
+ private backgroundRefresh;
48
+ /**
49
+ * Core refresh. Sets {@link pendingRefresh} so concurrent callers
50
+ * (both advisory and mandatory) coalesce into a single provider call.
51
+ */
52
+ private doRefresh;
53
+ }
54
+ //# sourceMappingURL=token-cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-cache.d.ts","sourceRoot":"","sources":["../../src/lib/credentials/token-cache.ts"],"names":[],"mappings":"OAAO,KAAK,EAAe,mBAAmB,EAAE;AAQhD;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAsB;IACtC,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,cAAc,CAAqC;IAC3D,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,sBAAsB,CAAuC;gBAEzD,QAAQ,EAAE,mBAAmB,EAAE,sBAAsB,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI;IAKpF,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC;IA6BjC;;;;;OAKG;IACH,UAAU,IAAI,IAAI;IAKlB;;;;OAIG;IACH,OAAO,CAAC,OAAO;IAOf;;;;;;OAMG;IACH,OAAO,CAAC,iBAAiB;IAezB;;;OAGG;IACH,OAAO,CAAC,SAAS;CAclB"}
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TokenCache = void 0;
4
+ const types_1 = require("./types.js");
5
+ const time_1 = require("../../internal/utils/time.js");
6
+ /**
7
+ * Wraps an {@link AccessTokenProvider} with two-tier proactive refresh
8
+ * and concurrent deduplication.
9
+ *
10
+ * Refresh policy on each {@link getToken} call:
11
+ *
12
+ * - No cached token → call provider (blocking), cache, return.
13
+ * - Cached with `expiresAt == null` → return cached forever.
14
+ * - More than 120s remaining → return cached.
15
+ * - 30–120s remaining (advisory window) → return stale token immediately,
16
+ * kick off background refresh. On failure, log and keep stale.
17
+ * - Less than 30s remaining or expired (mandatory) → block and refresh.
18
+ * On failure, throw.
19
+ *
20
+ * Concurrent mandatory callers coalesce into a single provider call.
21
+ */
22
+ class TokenCache {
23
+ constructor(provider, onAdvisoryRefreshError) {
24
+ this.cached = null;
25
+ this.pendingRefresh = null;
26
+ this.nextForce = false;
27
+ this.lastAdvisoryError = 0;
28
+ this.provider = provider;
29
+ this.onAdvisoryRefreshError = onAdvisoryRefreshError;
30
+ }
31
+ async getToken() {
32
+ const force = this.nextForce;
33
+ this.nextForce = false;
34
+ const cached = this.cached;
35
+ if (force || cached == null) {
36
+ const token = await this.refresh(force);
37
+ return token.token;
38
+ }
39
+ if (cached.expiresAt == null) {
40
+ return cached.token;
41
+ }
42
+ const remaining = cached.expiresAt - (0, time_1.nowAsSeconds)();
43
+ if (remaining > types_1.ADVISORY_REFRESH_THRESHOLD_IN_SECONDS) {
44
+ return cached.token;
45
+ }
46
+ if (remaining > types_1.MANDATORY_REFRESH_THRESHOLD_IN_SECONDS) {
47
+ this.backgroundRefresh();
48
+ return cached.token;
49
+ }
50
+ const token = await this.refresh();
51
+ return token.token;
52
+ }
53
+ /**
54
+ * Clears the cached token and marks the next {@link getToken} as a forced
55
+ * refresh, so the underlying provider bypasses any on-disk freshness check.
56
+ * Called after a 401 — the server has just told us the token is bad even
57
+ * if its `expires_at` still looks fresh.
58
+ */
59
+ invalidate() {
60
+ this.cached = null;
61
+ this.nextForce = true;
62
+ }
63
+ /**
64
+ * Mandatory refresh. Joins any in-flight refresh unless forced — a forced
65
+ * refresh must not coalesce into a non-forced one that may re-serve the
66
+ * same stale disk token.
67
+ */
68
+ refresh(force = false) {
69
+ if (this.pendingRefresh && !force) {
70
+ return this.pendingRefresh;
71
+ }
72
+ return this.doRefresh(force);
73
+ }
74
+ /**
75
+ * Advisory background refresh. Shares the same in-flight promise as
76
+ * mandatory refreshes for deduplication, but swallows errors so the
77
+ * stale cached token keeps being served. Backs off for
78
+ * {@link ADVISORY_REFRESH_BACKOFF_IN_SECONDS} after a failure so an
79
+ * outage during the advisory window doesn't hammer the token endpoint.
80
+ */
81
+ backgroundRefresh() {
82
+ if (this.pendingRefresh) {
83
+ return;
84
+ }
85
+ if ((0, time_1.nowAsSeconds)() - this.lastAdvisoryError < types_1.ADVISORY_REFRESH_BACKOFF_IN_SECONDS) {
86
+ return;
87
+ }
88
+ this.doRefresh().catch((err) => {
89
+ this.lastAdvisoryError = (0, time_1.nowAsSeconds)();
90
+ // Advisory failure: keep serving the stale cached token, but surface
91
+ // the error to the caller-provided hook so it can be logged.
92
+ this.onAdvisoryRefreshError?.(err);
93
+ });
94
+ }
95
+ /**
96
+ * Core refresh. Sets {@link pendingRefresh} so concurrent callers
97
+ * (both advisory and mandatory) coalesce into a single provider call.
98
+ */
99
+ doRefresh(force = false) {
100
+ this.pendingRefresh = this.provider(force ? { forceRefresh: true } : undefined).then((token) => {
101
+ this.cached = token;
102
+ this.pendingRefresh = null;
103
+ return token;
104
+ }, (err) => {
105
+ this.pendingRefresh = null;
106
+ throw err;
107
+ });
108
+ return this.pendingRefresh;
109
+ }
110
+ }
111
+ exports.TokenCache = TokenCache;
112
+ //# sourceMappingURL=token-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-cache.js","sourceRoot":"","sources":["../../src/lib/credentials/token-cache.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,108 @@
1
+ import { ADVISORY_REFRESH_BACKOFF_IN_SECONDS, ADVISORY_REFRESH_THRESHOLD_IN_SECONDS, MANDATORY_REFRESH_THRESHOLD_IN_SECONDS, } from "./types.mjs";
2
+ import { nowAsSeconds } from "../../internal/utils/time.mjs";
3
+ /**
4
+ * Wraps an {@link AccessTokenProvider} with two-tier proactive refresh
5
+ * and concurrent deduplication.
6
+ *
7
+ * Refresh policy on each {@link getToken} call:
8
+ *
9
+ * - No cached token → call provider (blocking), cache, return.
10
+ * - Cached with `expiresAt == null` → return cached forever.
11
+ * - More than 120s remaining → return cached.
12
+ * - 30–120s remaining (advisory window) → return stale token immediately,
13
+ * kick off background refresh. On failure, log and keep stale.
14
+ * - Less than 30s remaining or expired (mandatory) → block and refresh.
15
+ * On failure, throw.
16
+ *
17
+ * Concurrent mandatory callers coalesce into a single provider call.
18
+ */
19
+ export class TokenCache {
20
+ constructor(provider, onAdvisoryRefreshError) {
21
+ this.cached = null;
22
+ this.pendingRefresh = null;
23
+ this.nextForce = false;
24
+ this.lastAdvisoryError = 0;
25
+ this.provider = provider;
26
+ this.onAdvisoryRefreshError = onAdvisoryRefreshError;
27
+ }
28
+ async getToken() {
29
+ const force = this.nextForce;
30
+ this.nextForce = false;
31
+ const cached = this.cached;
32
+ if (force || cached == null) {
33
+ const token = await this.refresh(force);
34
+ return token.token;
35
+ }
36
+ if (cached.expiresAt == null) {
37
+ return cached.token;
38
+ }
39
+ const remaining = cached.expiresAt - nowAsSeconds();
40
+ if (remaining > ADVISORY_REFRESH_THRESHOLD_IN_SECONDS) {
41
+ return cached.token;
42
+ }
43
+ if (remaining > MANDATORY_REFRESH_THRESHOLD_IN_SECONDS) {
44
+ this.backgroundRefresh();
45
+ return cached.token;
46
+ }
47
+ const token = await this.refresh();
48
+ return token.token;
49
+ }
50
+ /**
51
+ * Clears the cached token and marks the next {@link getToken} as a forced
52
+ * refresh, so the underlying provider bypasses any on-disk freshness check.
53
+ * Called after a 401 — the server has just told us the token is bad even
54
+ * if its `expires_at` still looks fresh.
55
+ */
56
+ invalidate() {
57
+ this.cached = null;
58
+ this.nextForce = true;
59
+ }
60
+ /**
61
+ * Mandatory refresh. Joins any in-flight refresh unless forced — a forced
62
+ * refresh must not coalesce into a non-forced one that may re-serve the
63
+ * same stale disk token.
64
+ */
65
+ refresh(force = false) {
66
+ if (this.pendingRefresh && !force) {
67
+ return this.pendingRefresh;
68
+ }
69
+ return this.doRefresh(force);
70
+ }
71
+ /**
72
+ * Advisory background refresh. Shares the same in-flight promise as
73
+ * mandatory refreshes for deduplication, but swallows errors so the
74
+ * stale cached token keeps being served. Backs off for
75
+ * {@link ADVISORY_REFRESH_BACKOFF_IN_SECONDS} after a failure so an
76
+ * outage during the advisory window doesn't hammer the token endpoint.
77
+ */
78
+ backgroundRefresh() {
79
+ if (this.pendingRefresh) {
80
+ return;
81
+ }
82
+ if (nowAsSeconds() - this.lastAdvisoryError < ADVISORY_REFRESH_BACKOFF_IN_SECONDS) {
83
+ return;
84
+ }
85
+ this.doRefresh().catch((err) => {
86
+ this.lastAdvisoryError = nowAsSeconds();
87
+ // Advisory failure: keep serving the stale cached token, but surface
88
+ // the error to the caller-provided hook so it can be logged.
89
+ this.onAdvisoryRefreshError?.(err);
90
+ });
91
+ }
92
+ /**
93
+ * Core refresh. Sets {@link pendingRefresh} so concurrent callers
94
+ * (both advisory and mandatory) coalesce into a single provider call.
95
+ */
96
+ doRefresh(force = false) {
97
+ this.pendingRefresh = this.provider(force ? { forceRefresh: true } : undefined).then((token) => {
98
+ this.cached = token;
99
+ this.pendingRefresh = null;
100
+ return token;
101
+ }, (err) => {
102
+ this.pendingRefresh = null;
103
+ throw err;
104
+ });
105
+ return this.pendingRefresh;
106
+ }
107
+ }
108
+ //# sourceMappingURL=token-cache.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-cache.mjs","sourceRoot":"","sources":["../../src/lib/credentials/token-cache.ts"],"names":[],"mappings":"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"}