@mantyx/sdk 0.9.1 → 0.10.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.
package/dist/index.d.cts CHANGED
@@ -1,96 +1,6 @@
1
- export { A as A2AToolRef, a as AgentSession, b as AgentSpecBase, c as AssistantDeltaEvent, d as AssistantMessageEvent, C as CancelledEvent, D as DEFAULT_BASE_URL, e as DefineLocalA2AOptions, f as DefineLocalMcpOptions, g as DefineLocalToolOptions, E as ErrorEvent, L as LocalA2ATool, h as LocalHandlers, i as LocalMcpHttpTransport, j as LocalMcpServer, k as LocalMcpStdioTransport, l as LocalTool, m as LocalToolCallEvent, n as LocalToolResultInEvent, o as LoopDetectedEvent, p as LoopDetection, q as MantyxA2AOptions, M as MantyxClient, r as MantyxClientOptions, s as MantyxMcpOptions, t as MantyxPluginToolRef, u as MantyxToolRef, v as McpToolRef, w as ModelCatalog, x as ModelInfo, O as OutputSchema, R as ReasoningLevel, y as ResultEvent, z as RunEvent, B as RunEventBase, F as RunResult, G as RunSpec, S as ServerToolResultEvent, H as SessionInfo, I as SessionSpec, J as ThinkingDeltaEvent, K as ToolBudget, N as ToolBudgetExceededEvent, P as ToolBudgets, T as ToolRef, Z as ZodLikeObject, Q as defineLocalA2A, U as defineLocalMcp, V as defineLocalTool, W as isLocalA2ATool, X as isLocalMcpServer, Y as isLocalTool, _ as mantyxA2A, $ as mantyxMcp, a0 as mantyxPluginTool, a1 as mantyxTool, a2 as parseRunOutput } from './client-BB6cjfsz.cjs';
1
+ export { A as A2AToolRef, a as AgentSession, b as AgentSpecBase, c as AssistantDeltaEvent, d as AssistantMessageEvent, C as CancelledEvent, e as ClientCredentialsOptions, f as ClientCredentialsTokenSourceOptions, D as DEFAULT_BASE_URL, g as DEFAULT_OAUTH_BASE_URL, h as DEFAULT_REFRESH_SKEW_MS, i as DefineLocalA2AOptions, j as DefineLocalMcpOptions, k as DefineLocalToolOptions, E as ErrorEvent, l as ExchangeAuthorizationCodeOptions, L as LocalA2ATool, m as LocalHandlers, n as LocalMcpHttpTransport, o as LocalMcpServer, p as LocalMcpStdioTransport, q as LocalTool, r as LocalToolCallEvent, s as LocalToolResultInEvent, t as LoopDetectedEvent, u as LoopDetection, v as MantyxA2AOptions, w as MantyxAuthError, M as MantyxClient, x as MantyxClientOptions, y as MantyxError, z as MantyxMcpOptions, B as MantyxNetworkError, F as MantyxOAuthClient, G as MantyxOAuthClientOptions, H as MantyxOAuthError, I as MantyxParseError, J as MantyxPluginToolRef, K as MantyxRunError, N as MantyxRunErrorInit, O as MantyxScopeError, P as MantyxToolError, Q as MantyxToolRef, S as McpToolRef, U as ModelCatalog, V as ModelInfo, W as OAuthToken, X as OutputSchema, R as ReasoningLevel, Y as RefreshOptions, Z as RefreshTokenSourceOptions, _ as ResultEvent, $ as RevokeOptions, a0 as RunEvent, a1 as RunEventBase, a2 as RunResult, a3 as RunSpec, a4 as ServerToolResultEvent, a5 as SessionInfo, a6 as SessionSpec, a7 as ThinkingDeltaEvent, a8 as TokenRequestReason, a9 as TokenSource, aa as ToolBudget, ab as ToolBudgetExceededEvent, ac as ToolBudgets, T as ToolRef, ad as ZodLikeObject, ae as defineLocalA2A, af as defineLocalMcp, ag as defineLocalTool, ah as generatePkceVerifier, ai as isLocalA2ATool, aj as isLocalMcpServer, ak as isLocalTool, al as mantyxA2A, am as mantyxMcp, an as mantyxPluginTool, ao as mantyxTool, ap as parseRunOutput, aq as pkceChallenge } from './client-DHwh8MPj.cjs';
2
2
  import { z } from 'zod';
3
3
 
4
- /**
5
- * Error types raised by the MANTYX SDK.
6
- */
7
- declare class MantyxError extends Error {
8
- readonly code: string;
9
- readonly status: number | undefined;
10
- readonly hint: string | undefined;
11
- constructor(message: string, opts?: {
12
- code?: string;
13
- status?: number;
14
- hint?: string;
15
- });
16
- }
17
- declare class MantyxNetworkError extends MantyxError {
18
- constructor(message: string, opts?: {
19
- cause?: unknown;
20
- });
21
- }
22
- declare class MantyxAuthError extends MantyxError {
23
- constructor(message?: string);
24
- }
25
- declare class MantyxToolError extends MantyxError {
26
- readonly toolName: string;
27
- constructor(toolName: string, message: string);
28
- }
29
- /**
30
- * Optional triage attributes the runner attaches to terminal `error`
31
- * events. Mirrors the wire fields described in
32
- * `docs/agent-runs-protocol.md` §7 ("error event payload fields") so SDK
33
- * callers can render structured UI status notes ("model truncated — JSON
34
- * likely incomplete") and drive retry policy without re-parsing the
35
- * human-readable `message`.
36
- */
37
- interface MantyxRunErrorInit {
38
- /**
39
- * Canonical category of failure. One of `"rate_limit"`, `"overloaded"`,
40
- * `"server"`, `"context_window"`, `"truncation"`, `"invalid_request"`,
41
- * `"auth"`, `"timeout"`, `"local_timeout"`, `"upstream_deadline"`,
42
- * `"unknown"`. New categories may land additively — callers should
43
- * default-branch to `"unknown"` for unrecognized values.
44
- */
45
- errorClass?: string;
46
- /**
47
- * Canonical lowercase stop reason normalized across providers
48
- * (`"max_tokens"`, `"refusal"`, `"malformed_function_call"`, …). When
49
- * present, mirrors the value carried on the last `assistant_message`
50
- * event preceding the failure.
51
- */
52
- finishReason?: string | null;
53
- /**
54
- * **Best-effort raw bytes** the model emitted before the failure. For
55
- * `outputSchema` runs this is likely **incomplete JSON** that will
56
- * fail `JSON.parse` — treat it as diagnostic data, never as a
57
- * schema-conformant reply.
58
- */
59
- partialText?: string;
60
- /**
61
- * Coarse retry hint inherited from the pipeline's error classifier.
62
- * Informational; the SDK still owns the actual retry decision.
63
- */
64
- retryable?: boolean;
65
- }
66
- declare class MantyxRunError extends MantyxError {
67
- readonly runId: string;
68
- readonly subtype: string;
69
- /** See {@link MantyxRunErrorInit.errorClass}. */
70
- readonly errorClass: string | undefined;
71
- /** See {@link MantyxRunErrorInit.finishReason}. */
72
- readonly finishReason: string | null | undefined;
73
- /** See {@link MantyxRunErrorInit.partialText}. */
74
- readonly partialText: string | undefined;
75
- /** See {@link MantyxRunErrorInit.retryable}. */
76
- readonly retryable: boolean | undefined;
77
- constructor(runId: string, subtype: string, message: string, init?: MantyxRunErrorInit);
78
- }
79
- /**
80
- * Thrown by {@link parseRunOutput} when the run's terminal text was supposed
81
- * to be a JSON document (because `outputSchema` was set on the spec) but
82
- * either failed to JSON.parse or failed the user-supplied validator.
83
- *
84
- * The original `text` is preserved on the `text` field so callers can log
85
- * the raw model output for debugging.
86
- */
87
- declare class MantyxParseError extends MantyxError {
88
- readonly text: string;
89
- constructor(message: string, text: string, opts?: {
90
- cause?: unknown;
91
- });
92
- }
93
-
94
4
  /**
95
5
  * Lightweight Zod → JSON Schema converter for tool parameter definitions.
96
6
  *
@@ -142,6 +52,6 @@ declare function readSseStream(body: ReadableStream<Uint8Array> | null, opts?: S
142
52
  /**
143
53
  * Release version — synced from repo root VERSION (`npm run sync-version`).
144
54
  */
145
- declare const SDK_VERSION = "0.9.1";
55
+ declare const SDK_VERSION = "0.10.0";
146
56
 
147
- export { MantyxAuthError, MantyxError, MantyxNetworkError, MantyxParseError, MantyxRunError, type MantyxRunErrorInit, MantyxToolError, SDK_VERSION, type SseEvent, type SseStreamOptions, readSseStream, toToolParametersWire, zodToJsonSchema };
57
+ export { SDK_VERSION, type SseEvent, type SseStreamOptions, readSseStream, toToolParametersWire, zodToJsonSchema };
package/dist/index.d.ts CHANGED
@@ -1,96 +1,6 @@
1
- export { A as A2AToolRef, a as AgentSession, b as AgentSpecBase, c as AssistantDeltaEvent, d as AssistantMessageEvent, C as CancelledEvent, D as DEFAULT_BASE_URL, e as DefineLocalA2AOptions, f as DefineLocalMcpOptions, g as DefineLocalToolOptions, E as ErrorEvent, L as LocalA2ATool, h as LocalHandlers, i as LocalMcpHttpTransport, j as LocalMcpServer, k as LocalMcpStdioTransport, l as LocalTool, m as LocalToolCallEvent, n as LocalToolResultInEvent, o as LoopDetectedEvent, p as LoopDetection, q as MantyxA2AOptions, M as MantyxClient, r as MantyxClientOptions, s as MantyxMcpOptions, t as MantyxPluginToolRef, u as MantyxToolRef, v as McpToolRef, w as ModelCatalog, x as ModelInfo, O as OutputSchema, R as ReasoningLevel, y as ResultEvent, z as RunEvent, B as RunEventBase, F as RunResult, G as RunSpec, S as ServerToolResultEvent, H as SessionInfo, I as SessionSpec, J as ThinkingDeltaEvent, K as ToolBudget, N as ToolBudgetExceededEvent, P as ToolBudgets, T as ToolRef, Z as ZodLikeObject, Q as defineLocalA2A, U as defineLocalMcp, V as defineLocalTool, W as isLocalA2ATool, X as isLocalMcpServer, Y as isLocalTool, _ as mantyxA2A, $ as mantyxMcp, a0 as mantyxPluginTool, a1 as mantyxTool, a2 as parseRunOutput } from './client-BB6cjfsz.js';
1
+ export { A as A2AToolRef, a as AgentSession, b as AgentSpecBase, c as AssistantDeltaEvent, d as AssistantMessageEvent, C as CancelledEvent, e as ClientCredentialsOptions, f as ClientCredentialsTokenSourceOptions, D as DEFAULT_BASE_URL, g as DEFAULT_OAUTH_BASE_URL, h as DEFAULT_REFRESH_SKEW_MS, i as DefineLocalA2AOptions, j as DefineLocalMcpOptions, k as DefineLocalToolOptions, E as ErrorEvent, l as ExchangeAuthorizationCodeOptions, L as LocalA2ATool, m as LocalHandlers, n as LocalMcpHttpTransport, o as LocalMcpServer, p as LocalMcpStdioTransport, q as LocalTool, r as LocalToolCallEvent, s as LocalToolResultInEvent, t as LoopDetectedEvent, u as LoopDetection, v as MantyxA2AOptions, w as MantyxAuthError, M as MantyxClient, x as MantyxClientOptions, y as MantyxError, z as MantyxMcpOptions, B as MantyxNetworkError, F as MantyxOAuthClient, G as MantyxOAuthClientOptions, H as MantyxOAuthError, I as MantyxParseError, J as MantyxPluginToolRef, K as MantyxRunError, N as MantyxRunErrorInit, O as MantyxScopeError, P as MantyxToolError, Q as MantyxToolRef, S as McpToolRef, U as ModelCatalog, V as ModelInfo, W as OAuthToken, X as OutputSchema, R as ReasoningLevel, Y as RefreshOptions, Z as RefreshTokenSourceOptions, _ as ResultEvent, $ as RevokeOptions, a0 as RunEvent, a1 as RunEventBase, a2 as RunResult, a3 as RunSpec, a4 as ServerToolResultEvent, a5 as SessionInfo, a6 as SessionSpec, a7 as ThinkingDeltaEvent, a8 as TokenRequestReason, a9 as TokenSource, aa as ToolBudget, ab as ToolBudgetExceededEvent, ac as ToolBudgets, T as ToolRef, ad as ZodLikeObject, ae as defineLocalA2A, af as defineLocalMcp, ag as defineLocalTool, ah as generatePkceVerifier, ai as isLocalA2ATool, aj as isLocalMcpServer, ak as isLocalTool, al as mantyxA2A, am as mantyxMcp, an as mantyxPluginTool, ao as mantyxTool, ap as parseRunOutput, aq as pkceChallenge } from './client-DHwh8MPj.js';
2
2
  import { z } from 'zod';
3
3
 
4
- /**
5
- * Error types raised by the MANTYX SDK.
6
- */
7
- declare class MantyxError extends Error {
8
- readonly code: string;
9
- readonly status: number | undefined;
10
- readonly hint: string | undefined;
11
- constructor(message: string, opts?: {
12
- code?: string;
13
- status?: number;
14
- hint?: string;
15
- });
16
- }
17
- declare class MantyxNetworkError extends MantyxError {
18
- constructor(message: string, opts?: {
19
- cause?: unknown;
20
- });
21
- }
22
- declare class MantyxAuthError extends MantyxError {
23
- constructor(message?: string);
24
- }
25
- declare class MantyxToolError extends MantyxError {
26
- readonly toolName: string;
27
- constructor(toolName: string, message: string);
28
- }
29
- /**
30
- * Optional triage attributes the runner attaches to terminal `error`
31
- * events. Mirrors the wire fields described in
32
- * `docs/agent-runs-protocol.md` §7 ("error event payload fields") so SDK
33
- * callers can render structured UI status notes ("model truncated — JSON
34
- * likely incomplete") and drive retry policy without re-parsing the
35
- * human-readable `message`.
36
- */
37
- interface MantyxRunErrorInit {
38
- /**
39
- * Canonical category of failure. One of `"rate_limit"`, `"overloaded"`,
40
- * `"server"`, `"context_window"`, `"truncation"`, `"invalid_request"`,
41
- * `"auth"`, `"timeout"`, `"local_timeout"`, `"upstream_deadline"`,
42
- * `"unknown"`. New categories may land additively — callers should
43
- * default-branch to `"unknown"` for unrecognized values.
44
- */
45
- errorClass?: string;
46
- /**
47
- * Canonical lowercase stop reason normalized across providers
48
- * (`"max_tokens"`, `"refusal"`, `"malformed_function_call"`, …). When
49
- * present, mirrors the value carried on the last `assistant_message`
50
- * event preceding the failure.
51
- */
52
- finishReason?: string | null;
53
- /**
54
- * **Best-effort raw bytes** the model emitted before the failure. For
55
- * `outputSchema` runs this is likely **incomplete JSON** that will
56
- * fail `JSON.parse` — treat it as diagnostic data, never as a
57
- * schema-conformant reply.
58
- */
59
- partialText?: string;
60
- /**
61
- * Coarse retry hint inherited from the pipeline's error classifier.
62
- * Informational; the SDK still owns the actual retry decision.
63
- */
64
- retryable?: boolean;
65
- }
66
- declare class MantyxRunError extends MantyxError {
67
- readonly runId: string;
68
- readonly subtype: string;
69
- /** See {@link MantyxRunErrorInit.errorClass}. */
70
- readonly errorClass: string | undefined;
71
- /** See {@link MantyxRunErrorInit.finishReason}. */
72
- readonly finishReason: string | null | undefined;
73
- /** See {@link MantyxRunErrorInit.partialText}. */
74
- readonly partialText: string | undefined;
75
- /** See {@link MantyxRunErrorInit.retryable}. */
76
- readonly retryable: boolean | undefined;
77
- constructor(runId: string, subtype: string, message: string, init?: MantyxRunErrorInit);
78
- }
79
- /**
80
- * Thrown by {@link parseRunOutput} when the run's terminal text was supposed
81
- * to be a JSON document (because `outputSchema` was set on the spec) but
82
- * either failed to JSON.parse or failed the user-supplied validator.
83
- *
84
- * The original `text` is preserved on the `text` field so callers can log
85
- * the raw model output for debugging.
86
- */
87
- declare class MantyxParseError extends MantyxError {
88
- readonly text: string;
89
- constructor(message: string, text: string, opts?: {
90
- cause?: unknown;
91
- });
92
- }
93
-
94
4
  /**
95
5
  * Lightweight Zod → JSON Schema converter for tool parameter definitions.
96
6
  *
@@ -142,6 +52,6 @@ declare function readSseStream(body: ReadableStream<Uint8Array> | null, opts?: S
142
52
  /**
143
53
  * Release version — synced from repo root VERSION (`npm run sync-version`).
144
54
  */
145
- declare const SDK_VERSION = "0.9.1";
55
+ declare const SDK_VERSION = "0.10.0";
146
56
 
147
- export { MantyxAuthError, MantyxError, MantyxNetworkError, MantyxParseError, MantyxRunError, type MantyxRunErrorInit, MantyxToolError, SDK_VERSION, type SseEvent, type SseStreamOptions, readSseStream, toToolParametersWire, zodToJsonSchema };
57
+ export { SDK_VERSION, type SseEvent, type SseStreamOptions, readSseStream, toToolParametersWire, zodToJsonSchema };
package/dist/index.js CHANGED
@@ -7,6 +7,7 @@ import {
7
7
  MantyxNetworkError,
8
8
  MantyxParseError,
9
9
  MantyxRunError,
10
+ MantyxScopeError,
10
11
  MantyxToolError,
11
12
  defineLocalA2A,
12
13
  defineLocalMcp,
@@ -22,24 +23,307 @@ import {
22
23
  readSseStream,
23
24
  toToolParametersWire,
24
25
  zodToJsonSchema
25
- } from "./chunk-AE7ZSLBH.js";
26
+ } from "./chunk-XMUCELMH.js";
27
+
28
+ // src/oauth.ts
29
+ import { Buffer } from "buffer";
30
+ import { createHash, randomBytes } from "crypto";
31
+ var DEFAULT_OAUTH_BASE_URL = "https://app.mantyx.io";
32
+ var DEFAULT_REFRESH_SKEW_MS = 6e4;
33
+ var MantyxOAuthError = class extends MantyxError {
34
+ oauthError;
35
+ oauthErrorDescription;
36
+ constructor(oauthError, oauthErrorDescription, status) {
37
+ const message = oauthErrorDescription ? `OAuth ${oauthError}: ${oauthErrorDescription}` : `OAuth ${oauthError}`;
38
+ super(message, { code: oauthError, status });
39
+ this.name = "MantyxOAuthError";
40
+ this.oauthError = oauthError;
41
+ this.oauthErrorDescription = oauthErrorDescription;
42
+ }
43
+ };
44
+ var MantyxOAuthClient = class {
45
+ clientId;
46
+ baseUrl;
47
+ clientSecret;
48
+ fetchImpl;
49
+ timeoutMs;
50
+ constructor(opts) {
51
+ if (!opts.clientId) {
52
+ throw new MantyxError("`clientId` is required for MantyxOAuthClient");
53
+ }
54
+ if (!opts.clientSecret) {
55
+ throw new MantyxError("`clientSecret` is required for MantyxOAuthClient");
56
+ }
57
+ const f = opts.fetch ?? globalThis.fetch;
58
+ if (typeof f !== "function") {
59
+ throw new MantyxError(
60
+ "Global fetch is not available; pass a custom `fetch` implementation in MantyxOAuthClientOptions."
61
+ );
62
+ }
63
+ this.clientId = opts.clientId;
64
+ this.clientSecret = opts.clientSecret;
65
+ this.baseUrl = (opts.baseUrl ?? DEFAULT_OAUTH_BASE_URL).replace(/\/+$/, "");
66
+ this.fetchImpl = f;
67
+ this.timeoutMs = opts.timeoutMs ?? 3e4;
68
+ }
69
+ /**
70
+ * Swap an authorization-code + PKCE verifier for the initial
71
+ * `{access_token, refresh_token}` pair. Call this exactly once per
72
+ * sign-in after the browser/native redirect lands back on your
73
+ * `redirectUri` with a `code` parameter. Persist the returned
74
+ * `refreshToken` against the user record — it is long-lived and
75
+ * non-rotating per `docs/oauth.md` §"Token lifetimes & lifecycle".
76
+ */
77
+ async exchangeAuthorizationCode(opts) {
78
+ return this.token({
79
+ grant_type: "authorization_code",
80
+ code: opts.code,
81
+ redirect_uri: opts.redirectUri,
82
+ code_verifier: opts.codeVerifier
83
+ });
84
+ }
85
+ /**
86
+ * Mint a fresh access token from a stored refresh token. The
87
+ * returned `refreshToken` is identical to the input — the field is
88
+ * surfaced for symmetry with {@link exchangeAuthorizationCode} only.
89
+ *
90
+ * On `400 invalid_grant` the refresh token has been revoked (or its
91
+ * grant / app was deleted); the SDK surfaces a
92
+ * {@link MantyxOAuthError} and callers must drive a fresh sign-in.
93
+ */
94
+ async refresh(opts) {
95
+ if (!opts.refreshToken) {
96
+ throw new MantyxError("`refreshToken` is required for MantyxOAuthClient.refresh");
97
+ }
98
+ const body = {
99
+ grant_type: "refresh_token",
100
+ refresh_token: opts.refreshToken
101
+ };
102
+ const scope = normalizeScope(opts.scope);
103
+ if (scope !== void 0) body.scope = scope;
104
+ return this.token(body);
105
+ }
106
+ /**
107
+ * Request a workspace-scoped access token without a user via the
108
+ * `client_credentials` grant. Available only on private OAuth apps
109
+ * that were registered with `allowsClientCredentials: true`. No
110
+ * refresh token is issued; re-call this method whenever a new
111
+ * access token is needed.
112
+ */
113
+ async clientCredentials(opts = {}) {
114
+ const body = {
115
+ grant_type: "client_credentials"
116
+ };
117
+ const scope = normalizeScope(opts.scope);
118
+ if (scope !== void 0) body.scope = scope;
119
+ return this.token(body);
120
+ }
121
+ /**
122
+ * Revoke an access or refresh token (RFC 7009). The server always
123
+ * returns 200, even for unknown tokens. Revoking a **refresh**
124
+ * token kills the refresh and every live access token tied to its
125
+ * grant; revoking an **access** token kills only that one.
126
+ */
127
+ async revoke(opts) {
128
+ if (!opts.token) {
129
+ throw new MantyxError("`token` is required for MantyxOAuthClient.revoke");
130
+ }
131
+ await this.formPost("/api/oauth/revoke", {
132
+ token: opts.token
133
+ });
134
+ }
135
+ /**
136
+ * Build a long-lived {@link TokenSource} that re-mints access
137
+ * tokens from the supplied refresh token. Pass the returned source
138
+ * to `new MantyxClient({ tokenSource, workspaceSlug, ... })`. The
139
+ * source caches the access token in-memory and refreshes
140
+ * proactively when the cached value is within `refreshSkewMs` of
141
+ * `expiresAt`, or eagerly when `MantyxClient` reports a 401.
142
+ */
143
+ refreshTokenSource(opts) {
144
+ if (!opts.refreshToken) {
145
+ throw new MantyxError("`refreshToken` is required for MantyxOAuthClient.refreshTokenSource");
146
+ }
147
+ const skew = opts.refreshSkewMs ?? DEFAULT_REFRESH_SKEW_MS;
148
+ const cache = { token: opts.initialToken, inflight: null };
149
+ const refreshToken = opts.refreshToken;
150
+ return makeTokenSource(cache, skew, async () => {
151
+ return this.refresh({ refreshToken, scope: opts.scope });
152
+ });
153
+ }
154
+ /**
155
+ * Build a long-lived {@link TokenSource} backed by the
156
+ * `client_credentials` grant. On every refresh the source re-mints
157
+ * a workspace-scoped access token by calling the token endpoint
158
+ * with `grant_type=client_credentials`. Available only on private
159
+ * apps with `allowsClientCredentials: true`.
160
+ */
161
+ clientCredentialsTokenSource(opts = {}) {
162
+ const skew = opts.refreshSkewMs ?? DEFAULT_REFRESH_SKEW_MS;
163
+ const cache = { token: void 0, inflight: null };
164
+ return makeTokenSource(cache, skew, async () => {
165
+ return this.clientCredentials({ scope: opts.scope });
166
+ });
167
+ }
168
+ // -------------------------------------------------------------- internals
169
+ /**
170
+ * POST `application/x-www-form-urlencoded` to `/api/oauth/token` and
171
+ * decode the {@link OAuthToken} response. Always injects `client_id`
172
+ * + `client_secret` from the constructor.
173
+ */
174
+ async token(body) {
175
+ const res = await this.formPost("/api/oauth/token", body);
176
+ let parsed = {};
177
+ try {
178
+ parsed = await res.json();
179
+ } catch {
180
+ throw new MantyxOAuthError(
181
+ "invalid_response",
182
+ "Token endpoint returned a non-JSON response",
183
+ res.status
184
+ );
185
+ }
186
+ const accessToken = typeof parsed.access_token === "string" ? parsed.access_token : "";
187
+ if (!accessToken) {
188
+ throw new MantyxOAuthError(
189
+ "invalid_response",
190
+ "Token endpoint response is missing `access_token`",
191
+ res.status
192
+ );
193
+ }
194
+ const expiresIn = typeof parsed.expires_in === "number" ? parsed.expires_in : 3600;
195
+ return {
196
+ accessToken,
197
+ refreshToken: typeof parsed.refresh_token === "string" ? parsed.refresh_token : void 0,
198
+ tokenType: typeof parsed.token_type === "string" ? parsed.token_type : "Bearer",
199
+ expiresIn,
200
+ expiresAt: Date.now() + expiresIn * 1e3,
201
+ scope: typeof parsed.scope === "string" ? parsed.scope : void 0
202
+ };
203
+ }
204
+ async formPost(path, body) {
205
+ const url = `${this.baseUrl}${path}`;
206
+ const params = new URLSearchParams({
207
+ ...body,
208
+ client_id: this.clientId,
209
+ client_secret: this.clientSecret
210
+ });
211
+ const ctrl = new AbortController();
212
+ const t = setTimeout(() => ctrl.abort(), this.timeoutMs);
213
+ let res;
214
+ try {
215
+ res = await this.fetchImpl(url, {
216
+ method: "POST",
217
+ headers: {
218
+ "Content-Type": "application/x-www-form-urlencoded",
219
+ Accept: "application/json"
220
+ },
221
+ body: params.toString(),
222
+ signal: ctrl.signal
223
+ });
224
+ } catch (err) {
225
+ if (ctrl.signal.aborted) {
226
+ throw new MantyxNetworkError(`OAuth request timed out after ${this.timeoutMs}ms`);
227
+ }
228
+ throw new MantyxNetworkError(`OAuth network error: ${err.message}`, {
229
+ cause: err
230
+ });
231
+ } finally {
232
+ clearTimeout(t);
233
+ }
234
+ if (!res.ok) {
235
+ let errBody = {};
236
+ try {
237
+ errBody = await res.json();
238
+ } catch {
239
+ }
240
+ const oauthError = typeof errBody.error === "string" ? errBody.error : `http_${res.status}`;
241
+ const desc = typeof errBody.error_description === "string" ? errBody.error_description : void 0;
242
+ throw new MantyxOAuthError(oauthError, desc, res.status);
243
+ }
244
+ return res;
245
+ }
246
+ };
247
+ function generatePkceVerifier(length = 64) {
248
+ if (length < 43 || length > 128) {
249
+ throw new MantyxError("PKCE code_verifier length must be in [43, 128]");
250
+ }
251
+ const ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
252
+ const bytes = randomBytes(length);
253
+ let out = "";
254
+ for (let i = 0; i < length; i++) {
255
+ out += ALPHABET[bytes[i] % ALPHABET.length];
256
+ }
257
+ return out;
258
+ }
259
+ function pkceChallenge(verifier) {
260
+ const hash = createHash("sha256").update(verifier, "utf8").digest();
261
+ return Buffer.from(hash).toString("base64").replace(/=+$/, "").replace(/\+/g, "-").replace(/\//g, "_");
262
+ }
263
+ function makeTokenSource(cache, skewMs, mint) {
264
+ return async (reason = "initial") => {
265
+ if (reason !== "unauthorized" && cache.token && !isExpiring(cache.token, skewMs)) {
266
+ return cache.token.accessToken;
267
+ }
268
+ if (cache.inflight) {
269
+ const t = await cache.inflight;
270
+ if (reason === "unauthorized" && t === cache.token) {
271
+ } else {
272
+ return t.accessToken;
273
+ }
274
+ }
275
+ cache.inflight = mint().then(
276
+ (t) => {
277
+ cache.token = t;
278
+ return t;
279
+ },
280
+ (err) => {
281
+ throw err;
282
+ }
283
+ );
284
+ try {
285
+ const t = await cache.inflight;
286
+ return t.accessToken;
287
+ } finally {
288
+ cache.inflight = null;
289
+ }
290
+ };
291
+ }
292
+ function isExpiring(token, skewMs) {
293
+ return token.expiresAt - Date.now() <= skewMs;
294
+ }
295
+ function normalizeScope(scope) {
296
+ if (scope === void 0) return void 0;
297
+ if (typeof scope === "string") {
298
+ const trimmed = scope.trim();
299
+ return trimmed.length > 0 ? trimmed : void 0;
300
+ }
301
+ const joined = scope.filter((s) => typeof s === "string" && s.length > 0).join(" ");
302
+ return joined.length > 0 ? joined : void 0;
303
+ }
26
304
 
27
305
  // src/version.ts
28
- var SDK_VERSION = "0.9.1";
306
+ var SDK_VERSION = "0.10.0";
29
307
  export {
30
308
  AgentSession,
31
309
  DEFAULT_BASE_URL,
310
+ DEFAULT_OAUTH_BASE_URL,
311
+ DEFAULT_REFRESH_SKEW_MS,
32
312
  MantyxAuthError,
33
313
  MantyxClient,
34
314
  MantyxError,
35
315
  MantyxNetworkError,
316
+ MantyxOAuthClient,
317
+ MantyxOAuthError,
36
318
  MantyxParseError,
37
319
  MantyxRunError,
320
+ MantyxScopeError,
38
321
  MantyxToolError,
39
322
  SDK_VERSION,
40
323
  defineLocalA2A,
41
324
  defineLocalMcp,
42
325
  defineLocalTool,
326
+ generatePkceVerifier,
43
327
  isLocalA2ATool,
44
328
  isLocalMcpServer,
45
329
  isLocalTool,
@@ -48,6 +332,7 @@ export {
48
332
  mantyxPluginTool,
49
333
  mantyxTool,
50
334
  parseRunOutput,
335
+ pkceChallenge,
51
336
  readSseStream,
52
337
  toToolParametersWire,
53
338
  zodToJsonSchema
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/version.ts"],"sourcesContent":["/**\n * Release version — synced from repo root VERSION (`npm run sync-version`).\n */\nexport const SDK_VERSION = \"0.9.1\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAGO,IAAM,cAAc;","names":[]}
1
+ {"version":3,"sources":["../src/oauth.ts","../src/version.ts"],"sourcesContent":["/**\n * MANTYX OAuth 2.0 client: authorization-code exchange, refresh-token\n * minting, client-credentials grant, and token revocation, plus typed\n * {@link TokenSource}s that {@link MantyxClient} can consume to refresh\n * access tokens transparently before they expire (and again on 401).\n *\n * The wire contract this implements is `docs/oauth.md` in the SDK monorepo:\n *\n * - Token endpoint: `POST <baseUrl>/api/oauth/token`, form-encoded.\n * - Revoke endpoint: `POST <baseUrl>/api/oauth/revoke`, form-encoded.\n * - Access tokens (`mantyx_at_…`) live 1 hour (`expires_in: 3600`).\n * - Refresh tokens (`mantyx_rt_…`) are **persistent and non-rotating**:\n * `grant_type=refresh_token` echoes back the same value the client\n * sent. The caller persists the refresh token once at first sign-in\n * (encrypted at rest) and the SDK re-mints access tokens from it on\n * demand.\n *\n * See also `docs/oauth.md` for the authorization-code + PKCE consent\n * flow (which the SDK does **not** drive — the calling app owns the\n * redirect dance; once it has the auth code, `exchangeAuthorizationCode`\n * swaps it for the initial `{access_token, refresh_token}` pair).\n */\n\nimport { Buffer } from \"node:buffer\";\nimport { createHash, randomBytes } from \"node:crypto\";\n\nimport { MantyxError, MantyxNetworkError } from \"./errors.js\";\n\nexport const DEFAULT_OAUTH_BASE_URL = \"https://app.mantyx.io\";\n\n/** Skew (ms) before `expiresAt` at which a TokenSource will pre-emptively refresh. Default 60s. */\nexport const DEFAULT_REFRESH_SKEW_MS = 60_000;\n\n/**\n * Raised on a non-2xx response from `POST /api/oauth/token` or\n * `POST /api/oauth/revoke`. Carries the RFC 6749 `error` discriminator\n * (`\"invalid_grant\"`, `\"invalid_client\"`, `\"unsupported_grant_type\"`,\n * …) and the optional `error_description` so callers can branch on\n * machine-readable values without parsing the human message.\n *\n * `invalid_grant` from the refresh path specifically signals that the\n * refresh token has been revoked (or the OAuth grant / application\n * was deleted). The SDK never loops on this — callers should route\n * the user back to a fresh sign-in.\n */\nexport class MantyxOAuthError extends MantyxError {\n readonly oauthError: string;\n readonly oauthErrorDescription: string | undefined;\n\n constructor(\n oauthError: string,\n oauthErrorDescription: string | undefined,\n status: number,\n ) {\n const message = oauthErrorDescription\n ? `OAuth ${oauthError}: ${oauthErrorDescription}`\n : `OAuth ${oauthError}`;\n super(message, { code: oauthError, status });\n this.name = \"MantyxOAuthError\";\n this.oauthError = oauthError;\n this.oauthErrorDescription = oauthErrorDescription;\n }\n}\n\n/**\n * Decoded `POST /api/oauth/token` response, augmented with an absolute\n * `expiresAt` timestamp the SDK can use to decide when to refresh.\n *\n * `refreshToken` is present on the initial `authorization_code` exchange\n * and on subsequent `refresh_token` calls (where it is identical to the\n * value the client just sent — refresh tokens never rotate). The\n * `client_credentials` grant never returns one.\n */\nexport interface OAuthToken {\n readonly accessToken: string;\n readonly refreshToken: string | undefined;\n readonly tokenType: string;\n readonly expiresIn: number;\n /** Absolute Unix-ms timestamp set when the SDK parsed the response. */\n readonly expiresAt: number;\n readonly scope: string | undefined;\n}\n\n/** Why the SDK asked the {@link TokenSource} for the current access token. */\nexport type TokenRequestReason = \"initial\" | \"expired\" | \"unauthorized\";\n\n/**\n * A `TokenSource` produces the current access token on demand. The\n * {@link MantyxClient} HTTP layer calls it before every request. When\n * called with `reason: \"unauthorized\"` the source MUST force a refresh\n * (do not return a cached value); this is how the SDK recovers from\n * 401s caused by a token that the server already invalidated.\n *\n * Implementations should be safe to call from many concurrent requests.\n */\nexport type TokenSource = (reason?: TokenRequestReason) => Promise<string>;\n\n/** Caller-supplied options for `MantyxOAuthClient`. */\nexport interface MantyxOAuthClientOptions {\n /**\n * OAuth `client_id` issued at app registration (token prefix\n * `mantyx_oa_`).\n */\n clientId: string;\n /**\n * OAuth `client_secret` issued at app registration (token prefix\n * `mantyx_oas_`). Every MANTYX OAuth app is a confidential client,\n * so this is always required for token + revoke calls. Treat as a\n * deployment secret — do not bundle into browser builds.\n */\n clientSecret: string;\n /**\n * Origin of the MANTYX deployment. Defaults to `https://app.mantyx.io`.\n * The OAuth endpoints are mounted at `<baseUrl>/api/oauth/...`.\n */\n baseUrl?: string;\n /** Optional `fetch` override (e.g. node-fetch wrapper). Default: global `fetch`. */\n fetch?: typeof fetch;\n /** Default per-request timeout in milliseconds. Default: 30s. */\n timeoutMs?: number;\n}\n\nexport interface ExchangeAuthorizationCodeOptions {\n code: string;\n redirectUri: string;\n codeVerifier: string;\n}\n\nexport interface RefreshOptions {\n refreshToken: string;\n /**\n * Optional scope narrowing. Must be a subset of the scopes already\n * granted to the refresh token (server enforces this). Useful when\n * an SDK consumer wants a short-scope access token for a specific\n * sub-operation.\n */\n scope?: string | readonly string[];\n}\n\nexport interface ClientCredentialsOptions {\n scope?: string | readonly string[];\n}\n\nexport interface RevokeOptions {\n token: string;\n}\n\nexport interface RefreshTokenSourceOptions {\n refreshToken: string;\n /** Optional scope narrowing applied on every refresh. */\n scope?: string | readonly string[];\n /**\n * How many ms before `expiresAt` the source proactively refreshes.\n * Defaults to {@link DEFAULT_REFRESH_SKEW_MS} (60s).\n */\n refreshSkewMs?: number;\n /**\n * Optional initial access token + expiry to seed the source's cache\n * with (e.g. the token already in hand from the authorization-code\n * exchange). When omitted, the source mints one on the first call.\n */\n initialToken?: OAuthToken;\n}\n\nexport interface ClientCredentialsTokenSourceOptions {\n scope?: string | readonly string[];\n refreshSkewMs?: number;\n}\n\n/**\n * Wraps the MANTYX OAuth 2.0 authorization-server endpoints. App-scoped\n * (one per `{clientId, clientSecret}` pair); construct independently of\n * {@link MantyxClient}, then either call its grant helpers directly or\n * hand a `TokenSource` it produces to `MantyxClient` for fully\n * transparent refresh.\n */\nexport class MantyxOAuthClient {\n readonly clientId: string;\n readonly baseUrl: string;\n private readonly clientSecret: string;\n private readonly fetchImpl: typeof fetch;\n private readonly timeoutMs: number;\n\n constructor(opts: MantyxOAuthClientOptions) {\n if (!opts.clientId) {\n throw new MantyxError(\"`clientId` is required for MantyxOAuthClient\");\n }\n if (!opts.clientSecret) {\n throw new MantyxError(\"`clientSecret` is required for MantyxOAuthClient\");\n }\n const f = opts.fetch ?? globalThis.fetch;\n if (typeof f !== \"function\") {\n throw new MantyxError(\n \"Global fetch is not available; pass a custom `fetch` implementation in MantyxOAuthClientOptions.\",\n );\n }\n this.clientId = opts.clientId;\n this.clientSecret = opts.clientSecret;\n this.baseUrl = (opts.baseUrl ?? DEFAULT_OAUTH_BASE_URL).replace(/\\/+$/, \"\");\n this.fetchImpl = f;\n this.timeoutMs = opts.timeoutMs ?? 30_000;\n }\n\n /**\n * Swap an authorization-code + PKCE verifier for the initial\n * `{access_token, refresh_token}` pair. Call this exactly once per\n * sign-in after the browser/native redirect lands back on your\n * `redirectUri` with a `code` parameter. Persist the returned\n * `refreshToken` against the user record — it is long-lived and\n * non-rotating per `docs/oauth.md` §\"Token lifetimes & lifecycle\".\n */\n async exchangeAuthorizationCode(opts: ExchangeAuthorizationCodeOptions): Promise<OAuthToken> {\n return this.token({\n grant_type: \"authorization_code\",\n code: opts.code,\n redirect_uri: opts.redirectUri,\n code_verifier: opts.codeVerifier,\n });\n }\n\n /**\n * Mint a fresh access token from a stored refresh token. The\n * returned `refreshToken` is identical to the input — the field is\n * surfaced for symmetry with {@link exchangeAuthorizationCode} only.\n *\n * On `400 invalid_grant` the refresh token has been revoked (or its\n * grant / app was deleted); the SDK surfaces a\n * {@link MantyxOAuthError} and callers must drive a fresh sign-in.\n */\n async refresh(opts: RefreshOptions): Promise<OAuthToken> {\n if (!opts.refreshToken) {\n throw new MantyxError(\"`refreshToken` is required for MantyxOAuthClient.refresh\");\n }\n const body: Record<string, string> = {\n grant_type: \"refresh_token\",\n refresh_token: opts.refreshToken,\n };\n const scope = normalizeScope(opts.scope);\n if (scope !== undefined) body.scope = scope;\n return this.token(body);\n }\n\n /**\n * Request a workspace-scoped access token without a user via the\n * `client_credentials` grant. Available only on private OAuth apps\n * that were registered with `allowsClientCredentials: true`. No\n * refresh token is issued; re-call this method whenever a new\n * access token is needed.\n */\n async clientCredentials(opts: ClientCredentialsOptions = {}): Promise<OAuthToken> {\n const body: Record<string, string> = {\n grant_type: \"client_credentials\",\n };\n const scope = normalizeScope(opts.scope);\n if (scope !== undefined) body.scope = scope;\n return this.token(body);\n }\n\n /**\n * Revoke an access or refresh token (RFC 7009). The server always\n * returns 200, even for unknown tokens. Revoking a **refresh**\n * token kills the refresh and every live access token tied to its\n * grant; revoking an **access** token kills only that one.\n */\n async revoke(opts: RevokeOptions): Promise<void> {\n if (!opts.token) {\n throw new MantyxError(\"`token` is required for MantyxOAuthClient.revoke\");\n }\n await this.formPost(\"/api/oauth/revoke\", {\n token: opts.token,\n });\n }\n\n /**\n * Build a long-lived {@link TokenSource} that re-mints access\n * tokens from the supplied refresh token. Pass the returned source\n * to `new MantyxClient({ tokenSource, workspaceSlug, ... })`. The\n * source caches the access token in-memory and refreshes\n * proactively when the cached value is within `refreshSkewMs` of\n * `expiresAt`, or eagerly when `MantyxClient` reports a 401.\n */\n refreshTokenSource(opts: RefreshTokenSourceOptions): TokenSource {\n if (!opts.refreshToken) {\n throw new MantyxError(\"`refreshToken` is required for MantyxOAuthClient.refreshTokenSource\");\n }\n const skew = opts.refreshSkewMs ?? DEFAULT_REFRESH_SKEW_MS;\n const cache: TokenCache = { token: opts.initialToken, inflight: null };\n const refreshToken = opts.refreshToken;\n return makeTokenSource(cache, skew, async () => {\n return this.refresh({ refreshToken, scope: opts.scope });\n });\n }\n\n /**\n * Build a long-lived {@link TokenSource} backed by the\n * `client_credentials` grant. On every refresh the source re-mints\n * a workspace-scoped access token by calling the token endpoint\n * with `grant_type=client_credentials`. Available only on private\n * apps with `allowsClientCredentials: true`.\n */\n clientCredentialsTokenSource(opts: ClientCredentialsTokenSourceOptions = {}): TokenSource {\n const skew = opts.refreshSkewMs ?? DEFAULT_REFRESH_SKEW_MS;\n const cache: TokenCache = { token: undefined, inflight: null };\n return makeTokenSource(cache, skew, async () => {\n return this.clientCredentials({ scope: opts.scope });\n });\n }\n\n // -------------------------------------------------------------- internals\n\n /**\n * POST `application/x-www-form-urlencoded` to `/api/oauth/token` and\n * decode the {@link OAuthToken} response. Always injects `client_id`\n * + `client_secret` from the constructor.\n */\n private async token(body: Record<string, string>): Promise<OAuthToken> {\n const res = await this.formPost(\"/api/oauth/token\", body);\n let parsed: Record<string, unknown> = {};\n try {\n parsed = (await res.json()) as Record<string, unknown>;\n } catch {\n throw new MantyxOAuthError(\n \"invalid_response\",\n \"Token endpoint returned a non-JSON response\",\n res.status,\n );\n }\n const accessToken = typeof parsed.access_token === \"string\" ? parsed.access_token : \"\";\n if (!accessToken) {\n throw new MantyxOAuthError(\n \"invalid_response\",\n \"Token endpoint response is missing `access_token`\",\n res.status,\n );\n }\n const expiresIn = typeof parsed.expires_in === \"number\" ? parsed.expires_in : 3600;\n return {\n accessToken,\n refreshToken: typeof parsed.refresh_token === \"string\" ? parsed.refresh_token : undefined,\n tokenType: typeof parsed.token_type === \"string\" ? parsed.token_type : \"Bearer\",\n expiresIn,\n expiresAt: Date.now() + expiresIn * 1000,\n scope: typeof parsed.scope === \"string\" ? parsed.scope : undefined,\n };\n }\n\n private async formPost(path: string, body: Record<string, string>): Promise<Response> {\n const url = `${this.baseUrl}${path}`;\n const params = new URLSearchParams({\n ...body,\n client_id: this.clientId,\n client_secret: this.clientSecret,\n });\n const ctrl = new AbortController();\n const t = setTimeout(() => ctrl.abort(), this.timeoutMs);\n let res: Response;\n try {\n res = await this.fetchImpl(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n Accept: \"application/json\",\n },\n body: params.toString(),\n signal: ctrl.signal,\n });\n } catch (err) {\n if (ctrl.signal.aborted) {\n throw new MantyxNetworkError(`OAuth request timed out after ${this.timeoutMs}ms`);\n }\n throw new MantyxNetworkError(`OAuth network error: ${(err as Error).message}`, {\n cause: err,\n });\n } finally {\n clearTimeout(t);\n }\n if (!res.ok) {\n let errBody: { error?: unknown; error_description?: unknown } = {};\n try {\n errBody = (await res.json()) as typeof errBody;\n } catch {\n // ignore\n }\n const oauthError = typeof errBody.error === \"string\" ? errBody.error : `http_${res.status}`;\n const desc =\n typeof errBody.error_description === \"string\" ? errBody.error_description : undefined;\n throw new MantyxOAuthError(oauthError, desc, res.status);\n }\n return res;\n }\n}\n\n// -------------------------------------------------------------- PKCE helpers\n\n/**\n * Generate a high-entropy PKCE `code_verifier` (RFC 7636 §4.1). The\n * verifier is the raw secret you keep across the redirect; the\n * `code_challenge` you send on `/api/oauth/authorize` is derived from\n * it via {@link pkceChallenge}.\n *\n * Default length is 64 characters (≈ 384 bits of entropy after\n * base64url-encoding the 32 random bytes). Pass `length` to clamp to\n * the RFC's 43..128 inclusive range.\n */\nexport function generatePkceVerifier(length = 64): string {\n if (length < 43 || length > 128) {\n throw new MantyxError(\"PKCE code_verifier length must be in [43, 128]\");\n }\n // 32 random bytes -> 43 base64url chars; we then slice / pad up to the\n // requested length using the unreserved RFC 7636 alphabet.\n const ALPHABET = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~\";\n const bytes = randomBytes(length);\n let out = \"\";\n for (let i = 0; i < length; i++) {\n out += ALPHABET[bytes[i]! % ALPHABET.length];\n }\n return out;\n}\n\n/**\n * Compute the PKCE `S256` `code_challenge` for a given verifier:\n * `base64url(sha256(verifier))` with no padding (RFC 7636 §4.2).\n */\nexport function pkceChallenge(verifier: string): string {\n const hash = createHash(\"sha256\").update(verifier, \"utf8\").digest();\n return Buffer.from(hash)\n .toString(\"base64\")\n .replace(/=+$/, \"\")\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\");\n}\n\n// -------------------------------------------------------------- internals\n\ninterface TokenCache {\n token: OAuthToken | undefined;\n inflight: Promise<OAuthToken> | null;\n}\n\n/**\n * Wrap a `mintToken` thunk into a single-flight {@link TokenSource}\n * with a cache + proactive-refresh skew. The cache is overwritten\n * atomically on every successful mint; the in-flight promise\n * collapses N concurrent expired-token observers into one mint call.\n *\n * Single-flight is an efficiency, not a correctness requirement —\n * `docs/oauth.md` explicitly allows multiple concurrent refreshes\n * against the same refresh token — but it keeps the token-endpoint\n * QPS reasonable when an SDK consumer fans out work in parallel.\n */\nfunction makeTokenSource(\n cache: TokenCache,\n skewMs: number,\n mint: () => Promise<OAuthToken>,\n): TokenSource {\n return async (reason: TokenRequestReason = \"initial\"): Promise<string> => {\n if (reason !== \"unauthorized\" && cache.token && !isExpiring(cache.token, skewMs)) {\n return cache.token.accessToken;\n }\n if (cache.inflight) {\n const t = await cache.inflight;\n // If the inflight refresh was triggered by a benign cache miss\n // and we observed an unauthorized hint after it started, retry\n // once with a forced mint so the caller never gets a stale token.\n if (reason === \"unauthorized\" && t === cache.token) {\n // fallthrough to fresh mint below\n } else {\n return t.accessToken;\n }\n }\n cache.inflight = mint().then(\n (t) => {\n cache.token = t;\n return t;\n },\n (err: unknown) => {\n throw err;\n },\n );\n try {\n const t = await cache.inflight;\n return t.accessToken;\n } finally {\n cache.inflight = null;\n }\n };\n}\n\nfunction isExpiring(token: OAuthToken, skewMs: number): boolean {\n return token.expiresAt - Date.now() <= skewMs;\n}\n\nfunction normalizeScope(scope: string | readonly string[] | undefined): string | undefined {\n if (scope === undefined) return undefined;\n if (typeof scope === \"string\") {\n const trimmed = scope.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n }\n const joined = scope.filter((s) => typeof s === \"string\" && s.length > 0).join(\" \");\n return joined.length > 0 ? joined : undefined;\n}\n","/**\n * Release version — synced from repo root VERSION (`npm run sync-version`).\n */\nexport const SDK_VERSION = \"0.10.0\";\n"],"mappings":"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","names":[]}