@zeyos/client 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 (110) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/LICENSE +21 -0
  3. package/README.md +458 -0
  4. package/agents/README.md +66 -0
  5. package/agents/shared/business-app-benchmarks.md +111 -0
  6. package/agents/shared/zeyos-entity-map.md +142 -0
  7. package/agents/shared/zeyos-entity-reference.md +570 -0
  8. package/agents/shared/zeyos-query-patterns.md +89 -0
  9. package/agents/zeyos-account-intelligence/SKILL.md +34 -0
  10. package/agents/zeyos-account-intelligence/agents/openai.yaml +4 -0
  11. package/agents/zeyos-account-intelligence/references/workflows.md +84 -0
  12. package/agents/zeyos-billing-insights/SKILL.md +41 -0
  13. package/agents/zeyos-billing-insights/agents/openai.yaml +4 -0
  14. package/agents/zeyos-billing-insights/references/workflows.md +106 -0
  15. package/agents/zeyos-campaign-and-outreach/SKILL.md +44 -0
  16. package/agents/zeyos-campaign-and-outreach/agents/openai.yaml +4 -0
  17. package/agents/zeyos-campaign-and-outreach/references/workflows.md +100 -0
  18. package/agents/zeyos-collaboration-and-activity/SKILL.md +37 -0
  19. package/agents/zeyos-collaboration-and-activity/agents/openai.yaml +4 -0
  20. package/agents/zeyos-collaboration-and-activity/references/workflows.md +104 -0
  21. package/agents/zeyos-collections-and-dunning/SKILL.md +46 -0
  22. package/agents/zeyos-collections-and-dunning/agents/openai.yaml +4 -0
  23. package/agents/zeyos-collections-and-dunning/references/workflows.md +132 -0
  24. package/agents/zeyos-commerce-and-inventory/SKILL.md +38 -0
  25. package/agents/zeyos-commerce-and-inventory/agents/openai.yaml +4 -0
  26. package/agents/zeyos-commerce-and-inventory/references/workflows.md +101 -0
  27. package/agents/zeyos-mail-operations/SKILL.md +35 -0
  28. package/agents/zeyos-mail-operations/agents/openai.yaml +4 -0
  29. package/agents/zeyos-mail-operations/references/workflows.md +110 -0
  30. package/agents/zeyos-notes-and-sops/SKILL.md +31 -0
  31. package/agents/zeyos-notes-and-sops/agents/openai.yaml +4 -0
  32. package/agents/zeyos-notes-and-sops/references/workflows.md +85 -0
  33. package/agents/zeyos-platform-and-schema/SKILL.md +37 -0
  34. package/agents/zeyos-platform-and-schema/agents/openai.yaml +4 -0
  35. package/agents/zeyos-platform-and-schema/references/workflows.md +97 -0
  36. package/agents/zeyos-work-management/SKILL.md +45 -0
  37. package/agents/zeyos-work-management/agents/openai.yaml +4 -0
  38. package/agents/zeyos-work-management/references/workflows.md +148 -0
  39. package/docs/01-api-reference/01-data-retrieval.md +601 -0
  40. package/docs/01-api-reference/02-authentication.md +288 -0
  41. package/docs/01-api-reference/03-resources.md +270 -0
  42. package/docs/01-api-reference/04-schema.md +539 -0
  43. package/docs/01-api-reference/_category_.json +9 -0
  44. package/docs/02-javascript-client/01-getting-started.md +146 -0
  45. package/docs/02-javascript-client/02-authentication.md +287 -0
  46. package/docs/02-javascript-client/03-making-requests.md +572 -0
  47. package/docs/02-javascript-client/04-practical-guide.md +348 -0
  48. package/docs/02-javascript-client/_category_.json +9 -0
  49. package/docs/03-cli/01-getting-started.md +219 -0
  50. package/docs/03-cli/02-commands.md +407 -0
  51. package/docs/03-cli/03-configuration.md +220 -0
  52. package/docs/03-cli/_category_.json +9 -0
  53. package/docs/04-agent-workflows/00-coding-agents.md +35 -0
  54. package/docs/04-agent-workflows/01-agent-quickstart.md +147 -0
  55. package/docs/04-agent-workflows/02-agent-recipes.md +109 -0
  56. package/docs/04-agent-workflows/03-cli-coverage-and-escalation.md +65 -0
  57. package/docs/04-agent-workflows/_category_.json +9 -0
  58. package/docs/04-sample-apps/01-kanban.md +89 -0
  59. package/docs/04-sample-apps/02-crm.md +81 -0
  60. package/docs/04-sample-apps/03-dashboard.md +80 -0
  61. package/docs/04-sample-apps/_category_.json +9 -0
  62. package/docs/05-tutorials/00-application-developers.md +43 -0
  63. package/docs/05-tutorials/01-integration-architecture.md +60 -0
  64. package/docs/05-tutorials/02-build-your-own-zeyos-frontend.md +517 -0
  65. package/docs/05-tutorials/03-server-side-integrations.md +185 -0
  66. package/docs/05-tutorials/_category_.json +9 -0
  67. package/docs/intro.md +197 -0
  68. package/openapi/api.json +24308 -0
  69. package/openapi/auth.json +415 -0
  70. package/openapi/dbref.json +56223 -0
  71. package/openapi/oauth2.json +781 -0
  72. package/openapi/sdk.json +949 -0
  73. package/openapi/views.txt +642 -0
  74. package/package.json +49 -0
  75. package/samples/crm/README.md +28 -0
  76. package/samples/crm/index.html +327 -0
  77. package/samples/crm/js/api.js +208 -0
  78. package/samples/crm/js/auth.js +61 -0
  79. package/samples/crm/js/main.js +545 -0
  80. package/samples/crm/js/state.js +90 -0
  81. package/samples/crm/js/ui.js +51 -0
  82. package/samples/dashboard/README.md +28 -0
  83. package/samples/dashboard/index.html +280 -0
  84. package/samples/dashboard/js/api.js +197 -0
  85. package/samples/dashboard/js/auth.js +59 -0
  86. package/samples/dashboard/js/main.js +382 -0
  87. package/samples/dashboard/js/state.js +81 -0
  88. package/samples/dashboard/js/ui.js +48 -0
  89. package/samples/kanban/README.md +28 -0
  90. package/samples/kanban/index.html +263 -0
  91. package/samples/kanban/js/api.js +152 -0
  92. package/samples/kanban/js/auth.js +59 -0
  93. package/samples/kanban/js/constants.js +40 -0
  94. package/samples/kanban/js/kanban.js +246 -0
  95. package/samples/kanban/js/main.js +362 -0
  96. package/samples/kanban/js/modals.js +474 -0
  97. package/samples/kanban/js/settings.js +82 -0
  98. package/samples/kanban/js/state.js +118 -0
  99. package/samples/kanban/js/ui.js +49 -0
  100. package/scripts/generate-client.mjs +344 -0
  101. package/src/generated/operations.js +9772 -0
  102. package/src/generated/schema.js +8982 -0
  103. package/src/index.js +85 -0
  104. package/src/runtime/client.js +1208 -0
  105. package/src/runtime/error.js +29 -0
  106. package/src/runtime/http.js +174 -0
  107. package/src/runtime/request-shape.js +35 -0
  108. package/src/runtime/schema.js +206 -0
  109. package/src/runtime/suggest.js +74 -0
  110. package/src/runtime/token-store.js +105 -0
@@ -0,0 +1,1208 @@
1
+ import { GENERATED, SERVICES, SERVICE_KEYS } from '../generated/operations.js';
2
+ import { SCHEMA } from '../generated/schema.js';
3
+ import { ZeyosApiError, ZeyosValidationError } from './error.js';
4
+ import { buildUrl, httpRequest } from './http.js';
5
+ import {
6
+ OBJECT_CONTROL_KEYS as OBJECT_CONTROL_KEY_LIST,
7
+ REQUEST_CONTROL_KEYS
8
+ } from './request-shape.js';
9
+ import { createSchema } from './schema.js';
10
+ import { suggestClosest } from './suggest.js';
11
+ import { MemoryTokenStore, normalizeTokenSet, tokenResponseToTokenSet } from './token-store.js';
12
+
13
+ const DEFAULT_RETRY = Object.freeze({
14
+ maxRetries: 2,
15
+ retryOn: Object.freeze([429, 503]),
16
+ baseDelayMs: 300,
17
+ maxDelayMs: 10000
18
+ });
19
+
20
+ function normalizeRetry(retry) {
21
+ if (retry === false || retry === null) {
22
+ return { maxRetries: 0, retryOn: new Set(), baseDelayMs: 0, maxDelayMs: 0 };
23
+ }
24
+ const cfg = retry && typeof retry === 'object' ? retry : {};
25
+ const retryOn = Array.isArray(cfg.retryOn) ? cfg.retryOn : DEFAULT_RETRY.retryOn;
26
+ return {
27
+ maxRetries: Number.isInteger(cfg.maxRetries) && cfg.maxRetries >= 0 ? cfg.maxRetries : DEFAULT_RETRY.maxRetries,
28
+ retryOn: new Set(retryOn),
29
+ baseDelayMs: Number(cfg.baseDelayMs) > 0 ? Number(cfg.baseDelayMs) : DEFAULT_RETRY.baseDelayMs,
30
+ maxDelayMs: Number(cfg.maxDelayMs) > 0 ? Number(cfg.maxDelayMs) : DEFAULT_RETRY.maxDelayMs
31
+ };
32
+ }
33
+
34
+ function abortableDelay(ms, signal) {
35
+ // Respect an already-aborted signal even when there is no delay to wait on,
36
+ // so a zero-delay retry (e.g. `Retry-After: 0`) does not fire another request
37
+ // after the caller has aborted.
38
+ if (signal?.aborted) {
39
+ return Promise.reject(signal.reason ?? new Error('Aborted'));
40
+ }
41
+ if (!(ms > 0)) return Promise.resolve();
42
+ return new Promise((resolve, reject) => {
43
+ if (signal?.aborted) {
44
+ reject(signal.reason ?? new Error('Aborted'));
45
+ return;
46
+ }
47
+ const timer = setTimeout(() => {
48
+ signal?.removeEventListener?.('abort', onAbort);
49
+ resolve();
50
+ }, ms);
51
+ function onAbort() {
52
+ clearTimeout(timer);
53
+ reject(signal.reason ?? new Error('Aborted'));
54
+ }
55
+ signal?.addEventListener?.('abort', onAbort, { once: true });
56
+ });
57
+ }
58
+
59
+ // Honor a Retry-After header (seconds or HTTP-date), else exponential backoff
60
+ // with jitter, capped at maxDelayMs.
61
+ function computeRetryDelay(response, attempt, retryConfig) {
62
+ const header = response.headers?.['retry-after'];
63
+ // An empty or whitespace-only header carries no delay directive — `Number('')`
64
+ // is 0, which would otherwise short-circuit to a zero delay instead of the
65
+ // exponential backoff below.
66
+ const trimmedHeader = typeof header === 'string' ? header.trim() : header;
67
+ if (trimmedHeader != null && trimmedHeader !== '') {
68
+ const seconds = Number(trimmedHeader);
69
+ if (Number.isFinite(seconds)) {
70
+ return Math.min(retryConfig.maxDelayMs, Math.max(0, seconds * 1000));
71
+ }
72
+ const dateMs = Date.parse(trimmedHeader);
73
+ if (Number.isFinite(dateMs)) {
74
+ return Math.min(retryConfig.maxDelayMs, Math.max(0, dateMs - Date.now()));
75
+ }
76
+ }
77
+ const exp = retryConfig.baseDelayMs * Math.pow(2, attempt);
78
+ const jitter = Math.random() * retryConfig.baseDelayMs;
79
+ return Math.min(retryConfig.maxDelayMs, exp + jitter);
80
+ }
81
+
82
+ const AUTH_SCHEME_MAP = Object.freeze({
83
+ oauth: 'bearer',
84
+ token: 'bearer',
85
+ session: 'session',
86
+ basic: 'basic'
87
+ });
88
+ const PLATFORM_PRESETS = Object.freeze({
89
+ live: 'https://cloud.zeyos.com'
90
+ });
91
+
92
+ const VALID_AUTH_MODES = new Set(['auto', 'oauth', 'session', 'none']);
93
+ const RESERVED_INPUT_KEYS = new Set(REQUEST_CONTROL_KEYS);
94
+
95
+ // Reserved keys that act as control *containers* and are only meaningful when
96
+ // object-valued. A scalar value for one of these (most commonly `query: 'term'`
97
+ // for ZeyOS full-text search) is a payload field, not a control directive, so it
98
+ // must not disable body inference or be excluded from the inferred body.
99
+ const OBJECT_CONTROL_KEYS = new Set(OBJECT_CONTROL_KEY_LIST);
100
+
101
+ function isObject(value) {
102
+ return value != null && typeof value === 'object' && !Array.isArray(value);
103
+ }
104
+
105
+ function isPlainObject(value) {
106
+ return Object.prototype.toString.call(value) === '[object Object]';
107
+ }
108
+
109
+ function cloneValue(value) {
110
+ if (!Array.isArray(value) && !isPlainObject(value)) {
111
+ return value;
112
+ }
113
+ return structuredClone(value);
114
+ }
115
+
116
+ function trimTrailingSlash(value) {
117
+ return String(value).replace(/\/+$/, '');
118
+ }
119
+
120
+ function toBase64(value) {
121
+ if (typeof Buffer !== 'undefined') {
122
+ return Buffer.from(value, 'utf8').toString('base64');
123
+ }
124
+
125
+ if (typeof btoa === 'function') {
126
+ return btoa(value);
127
+ }
128
+
129
+ throw new Error('No base64 encoder available in this runtime.');
130
+ }
131
+
132
+ function normalizeAuthMode(value, fallback = 'auto') {
133
+ if (value && VALID_AUTH_MODES.has(value)) {
134
+ return value;
135
+ }
136
+ return fallback;
137
+ }
138
+
139
+ function parsePlatformUrl(value) {
140
+ try {
141
+ const parsed = new URL(value);
142
+ const segments = parsed.pathname
143
+ .split('/')
144
+ .map((part) => part.trim())
145
+ .filter(Boolean);
146
+
147
+ const instance = segments.length === 1 ? decodeURIComponent(segments[0]) : null;
148
+ return {
149
+ origin: parsed.origin,
150
+ instance
151
+ };
152
+ } catch {
153
+ return null;
154
+ }
155
+ }
156
+
157
+ function normalizePlatform(platform) {
158
+ if (!platform) {
159
+ return null;
160
+ }
161
+
162
+ if (typeof platform === 'string') {
163
+ if (PLATFORM_PRESETS[platform]) {
164
+ return {
165
+ origin: PLATFORM_PRESETS[platform],
166
+ instance: null
167
+ };
168
+ }
169
+
170
+ const parsed = parsePlatformUrl(platform);
171
+ if (parsed) {
172
+ return parsed;
173
+ }
174
+
175
+ return {
176
+ origin: platform,
177
+ instance: null
178
+ };
179
+ }
180
+
181
+ if (!isObject(platform)) {
182
+ return null;
183
+ }
184
+
185
+ const preset = typeof platform.preset === 'string' ? platform.preset : null;
186
+ const directOrigin = typeof platform.origin === 'string' ? platform.origin : null;
187
+ const directUrl = typeof platform.url === 'string' ? platform.url : null;
188
+ const parsedUrl = directUrl ? parsePlatformUrl(directUrl) : null;
189
+
190
+ return {
191
+ origin: directOrigin ?? parsedUrl?.origin ?? (preset && PLATFORM_PRESETS[preset] ? PLATFORM_PRESETS[preset] : null),
192
+ instance: typeof platform.instance === 'string' ? platform.instance : parsedUrl?.instance ?? null
193
+ };
194
+ }
195
+
196
+ function mergeHeaders(...sources) {
197
+ const merged = new Headers();
198
+ for (const source of sources) {
199
+ if (!source) {
200
+ continue;
201
+ }
202
+ const headers = source instanceof Headers ? source : new Headers(source);
203
+ for (const [key, value] of headers.entries()) {
204
+ merged.set(key, value);
205
+ }
206
+ }
207
+ return merged;
208
+ }
209
+
210
+ function isSuccessfulHttpStatus(status) {
211
+ return Number.isInteger(status) && status >= 200 && status < 400;
212
+ }
213
+
214
+ function securitySchemesFromOperation(operation) {
215
+ const security = Array.isArray(operation.security) ? operation.security : [];
216
+ if (security.length === 0) {
217
+ return ['none'];
218
+ }
219
+
220
+ const schemes = [];
221
+ for (const requirement of security) {
222
+ const keys = Object.keys(requirement || {});
223
+ if (keys.length === 0) {
224
+ if (!schemes.includes('none')) {
225
+ schemes.push('none');
226
+ }
227
+ continue;
228
+ }
229
+
230
+ for (const key of keys) {
231
+ const mapped = AUTH_SCHEME_MAP[key];
232
+ if (mapped && !schemes.includes(mapped)) {
233
+ schemes.push(mapped);
234
+ }
235
+ }
236
+ }
237
+
238
+ return schemes.length > 0 ? schemes : ['none'];
239
+ }
240
+
241
+ function shouldInferBody(operation, input) {
242
+ if (!Array.isArray(operation.requestContentTypes) || operation.requestContentTypes.length === 0) {
243
+ return false;
244
+ }
245
+
246
+ for (const key of RESERVED_INPUT_KEYS) {
247
+ if (!Object.prototype.hasOwnProperty.call(input, key)) {
248
+ continue;
249
+ }
250
+ // Object-container control keys only disable inference when actually
251
+ // object-valued; a scalar (e.g. query: 'acme') is a payload field.
252
+ if (OBJECT_CONTROL_KEYS.has(key) && !isObject(input[key])) {
253
+ continue;
254
+ }
255
+ return false;
256
+ }
257
+
258
+ return true;
259
+ }
260
+
261
+ function prepareOperationInput(operation, inputValue) {
262
+ const input = isObject(inputValue) ? inputValue : {};
263
+ const pathParams = isObject(input.path) ? { ...input.path } : {};
264
+ const query = isObject(input.query) ? { ...input.query } : {};
265
+ const headers = isObject(input.headers) ? { ...input.headers } : {};
266
+ const consumedInputKeys = new Set(RESERVED_INPUT_KEYS);
267
+ // Scalar object-container keys (e.g. query: 'acme') are payload fields, so do
268
+ // not pre-exclude them from the inferred body. A declared path/query/header
269
+ // parameter of the same name is still routed correctly by the loops below.
270
+ for (const key of OBJECT_CONTROL_KEYS) {
271
+ if (Object.prototype.hasOwnProperty.call(input, key) && !isObject(input[key])) {
272
+ consumedInputKeys.delete(key);
273
+ }
274
+ }
275
+
276
+ for (const name of operation.parameterNames.path) {
277
+ if (!Object.prototype.hasOwnProperty.call(pathParams, name) && Object.prototype.hasOwnProperty.call(input, name)) {
278
+ pathParams[name] = input[name];
279
+ }
280
+ if (Object.prototype.hasOwnProperty.call(input, name)) {
281
+ consumedInputKeys.add(name);
282
+ }
283
+ }
284
+
285
+ for (const name of operation.parameterNames.query) {
286
+ if (!Object.prototype.hasOwnProperty.call(query, name) && Object.prototype.hasOwnProperty.call(input, name)) {
287
+ query[name] = input[name];
288
+ }
289
+ if (Object.prototype.hasOwnProperty.call(input, name)) {
290
+ consumedInputKeys.add(name);
291
+ }
292
+ }
293
+
294
+ for (const name of operation.parameterNames.header) {
295
+ if (!Object.prototype.hasOwnProperty.call(headers, name) && Object.prototype.hasOwnProperty.call(input, name)) {
296
+ headers[name] = input[name];
297
+ }
298
+ if (Object.prototype.hasOwnProperty.call(input, name)) {
299
+ consumedInputKeys.add(name);
300
+ }
301
+ }
302
+
303
+ let body;
304
+ if (Object.prototype.hasOwnProperty.call(input, 'body')) {
305
+ body = input.body;
306
+ } else if (Object.prototype.hasOwnProperty.call(input, 'data')) {
307
+ body = input.data;
308
+ } else if (shouldInferBody(operation, input)) {
309
+ body = {};
310
+ for (const [key, value] of Object.entries(input)) {
311
+ if (!consumedInputKeys.has(key)) {
312
+ body[key] = value;
313
+ }
314
+ }
315
+ if (Object.keys(body).length === 0) {
316
+ body = undefined;
317
+ }
318
+ } else if (Array.isArray(operation.requestContentTypes) && operation.requestContentTypes.length > 0) {
319
+ // Body inference was skipped because a reserved control key is present in the
320
+ // input (shouldInferBody returned false). Any remaining input fields that are
321
+ // not reserved control keys and not path/query/header parameters would have
322
+ // become the request body, but are now silently dropped. Surface that clearly
323
+ // instead of sending a request that omits the caller's payload.
324
+ const collidingReservedKeys = [];
325
+ for (const key of RESERVED_INPUT_KEYS) {
326
+ if (Object.prototype.hasOwnProperty.call(input, key)) {
327
+ collidingReservedKeys.push(key);
328
+ }
329
+ }
330
+
331
+ const orphanedFields = Object.keys(input).filter((key) => !consumedInputKeys.has(key));
332
+
333
+ if (orphanedFields.length > 0) {
334
+ const operationLabel = operation.operationId || `${operation.method} ${operation.path}`;
335
+ throw new ZeyosApiError(
336
+ `${operationLabel}: payload field(s) ${orphanedFields.map((field) => `"${field}"`).join(', ')} ` +
337
+ `would be dropped because the reserved key(s) ${collidingReservedKeys
338
+ .map((key) => `"${key}"`)
339
+ .join(', ')} disabled body inference. ` +
340
+ 'Wrap payload fields in an explicit `body: { ... }` (or `data: { ... }`).',
341
+ {
342
+ operationId: operation.operationId,
343
+ method: operation.method,
344
+ url: operation.path
345
+ }
346
+ );
347
+ }
348
+ }
349
+
350
+ return {
351
+ pathParams,
352
+ query,
353
+ headers,
354
+ body,
355
+ bodyType: input.bodyType,
356
+ auth: input.auth,
357
+ signal: input.signal,
358
+ raw: input.raw,
359
+ baseUrl: input.baseUrl
360
+ };
361
+ }
362
+
363
+ function chooseBodyType(serviceKey, operation, prepared, fallbackBodyType) {
364
+ const body = prepared.body;
365
+ if (body == null) {
366
+ return undefined;
367
+ }
368
+
369
+ const explicitType = prepared.bodyType ?? fallbackBodyType;
370
+ if (explicitType) {
371
+ return explicitType;
372
+ }
373
+
374
+ const contentTypes = operation.requestContentTypes || [];
375
+
376
+ if ((serviceKey === 'oauth2' || serviceKey === 'legacyAuth') && contentTypes.includes('application/x-www-form-urlencoded')) {
377
+ return 'form';
378
+ }
379
+
380
+ if (contentTypes.includes('application/json')) {
381
+ return 'json';
382
+ }
383
+
384
+ if (contentTypes.includes('application/x-www-form-urlencoded')) {
385
+ return 'form';
386
+ }
387
+
388
+ return undefined;
389
+ }
390
+
391
+ function createApiError(response, { serviceKey, operation, method, url }) {
392
+ const operationDescription = operation.operationId ? `${serviceKey}.${operation.operationId}` : `${serviceKey} request`;
393
+ const message = `${operationDescription} failed with HTTP ${response.status}`;
394
+
395
+ return new ZeyosApiError(message, {
396
+ status: response.status,
397
+ statusText: response.statusText,
398
+ headers: response.headers,
399
+ body: response.data,
400
+ method,
401
+ url,
402
+ operationId: operation.operationId,
403
+ service: serviceKey
404
+ });
405
+ }
406
+
407
+ function normalizeRequestAuth(auth) {
408
+ if (!auth) {
409
+ return {};
410
+ }
411
+ if (typeof auth === 'string') {
412
+ return { mode: auth };
413
+ }
414
+ return auth;
415
+ }
416
+
417
+ function getBasicCredentials({ body, requestAuth, oauthConfig }) {
418
+ const bodyObject = isObject(body) ? body : {};
419
+
420
+ const clientId =
421
+ requestAuth.clientId ??
422
+ requestAuth.client_id ??
423
+ bodyObject.client_id ??
424
+ bodyObject.clientId ??
425
+ oauthConfig.clientId ??
426
+ oauthConfig.client_id;
427
+
428
+ const clientSecret =
429
+ requestAuth.clientSecret ??
430
+ requestAuth.client_secret ??
431
+ bodyObject.client_secret ??
432
+ bodyObject.clientSecret ??
433
+ oauthConfig.clientSecret ??
434
+ oauthConfig.client_secret;
435
+
436
+ if (!clientId || !clientSecret) {
437
+ return null;
438
+ }
439
+
440
+ return {
441
+ clientId,
442
+ clientSecret
443
+ };
444
+ }
445
+
446
+ function resolveBaseUrl({ services, serviceKey, config, explicitBaseUrl }) {
447
+ if (explicitBaseUrl) {
448
+ return trimTrailingSlash(explicitBaseUrl);
449
+ }
450
+
451
+ if (isObject(config.baseUrls) && typeof config.baseUrls[serviceKey] === 'string') {
452
+ return trimTrailingSlash(config.baseUrls[serviceKey]);
453
+ }
454
+
455
+ const service = services[serviceKey];
456
+ if (!service) {
457
+ throw new Error(`Unknown service key: ${serviceKey}`);
458
+ }
459
+
460
+ const template = service.server?.urlTemplate || '';
461
+ const defaults = isObject(service.server?.defaultVariables) ? service.server.defaultVariables : {};
462
+ const platform = normalizePlatform(config.platform);
463
+ const platformInstance = platform?.instance ?? config.instance ?? defaults.INSTANCE;
464
+
465
+ if (platform?.origin) {
466
+ const pathTemplate = service.server?.basePathTemplate || '';
467
+ const pathVariables = {
468
+ ...defaults,
469
+ INSTANCE: platformInstance
470
+ };
471
+
472
+ const resolvedPath = pathTemplate.replace(/\{([^}]+)\}/g, (_, token) => {
473
+ if (!Object.prototype.hasOwnProperty.call(pathVariables, token)) {
474
+ return `{${token}}`;
475
+ }
476
+ return encodeURIComponent(String(pathVariables[token]));
477
+ });
478
+
479
+ const normalizedOrigin = trimTrailingSlash(platform.origin);
480
+ const normalizedPath = resolvedPath.startsWith('/') ? resolvedPath : `/${resolvedPath}`;
481
+ return trimTrailingSlash(`${normalizedOrigin}${normalizedPath}`);
482
+ }
483
+
484
+ const variables = {
485
+ ...defaults,
486
+ INSTANCE: platformInstance
487
+ };
488
+
489
+ const resolved = template.replace(/\{([^}]+)\}/g, (_, token) => {
490
+ if (!Object.prototype.hasOwnProperty.call(variables, token)) {
491
+ return `{${token}}`;
492
+ }
493
+ return encodeURIComponent(String(variables[token]));
494
+ });
495
+
496
+ return trimTrailingSlash(resolved);
497
+ }
498
+
499
+ function resolveAuthCandidates({ mode, schemes, tokenSet, sessionEnabled }) {
500
+ const has = (scheme) => schemes.includes(scheme);
501
+
502
+ if (mode === 'none') {
503
+ return [{ type: 'none' }];
504
+ }
505
+
506
+ if (mode === 'oauth') {
507
+ if (has('basic')) {
508
+ return [{ type: 'basic' }];
509
+ }
510
+ if (has('bearer')) {
511
+ return [{ type: 'bearer' }];
512
+ }
513
+ if (has('none')) {
514
+ return [{ type: 'none' }];
515
+ }
516
+ throw new Error('OAuth mode cannot satisfy the operation security requirements.');
517
+ }
518
+
519
+ if (mode === 'session') {
520
+ if (has('session')) {
521
+ return [{ type: 'session' }];
522
+ }
523
+ if (has('none')) {
524
+ return [{ type: 'none' }];
525
+ }
526
+ throw new Error('Session mode cannot satisfy the operation security requirements.');
527
+ }
528
+
529
+ const candidates = [];
530
+ if (has('basic')) {
531
+ candidates.push({ type: 'basic' });
532
+ }
533
+ if (has('bearer') && tokenSet?.accessToken) {
534
+ candidates.push({ type: 'bearer' });
535
+ }
536
+ if (has('session') && sessionEnabled) {
537
+ candidates.push({ type: 'session' });
538
+ }
539
+
540
+ if (candidates.length === 0) {
541
+ if (has('bearer')) {
542
+ candidates.push({ type: 'bearer' });
543
+ } else if (has('session') && sessionEnabled) {
544
+ candidates.push({ type: 'session' });
545
+ } else {
546
+ candidates.push({ type: 'none' });
547
+ }
548
+ }
549
+
550
+ return candidates;
551
+ }
552
+
553
+ function canRefreshAccessToken({ mode, operation, tokenSet, oauthConfig }) {
554
+ if (mode !== 'auto' && mode !== 'oauth') {
555
+ return false;
556
+ }
557
+
558
+ if (oauthConfig.autoRefresh === false) {
559
+ return false;
560
+ }
561
+
562
+ if (!tokenSet?.refreshToken) {
563
+ return false;
564
+ }
565
+
566
+ if (operation.operationId === 'getToken') {
567
+ return false;
568
+ }
569
+
570
+ return Boolean(oauthConfig.clientId && oauthConfig.clientSecret);
571
+ }
572
+
573
+ function isAccessTokenExpired(tokenSet, skewSeconds = 60) {
574
+ if (!tokenSet?.accessToken || tokenSet.expiresAt == null) {
575
+ return false;
576
+ }
577
+ const expiresAt = Number(tokenSet.expiresAt);
578
+ if (!Number.isFinite(expiresAt)) {
579
+ return false;
580
+ }
581
+ const now = Math.floor(Date.now() / 1000);
582
+ return expiresAt <= now + skewSeconds;
583
+ }
584
+
585
+ export function createZeyosClient(rawConfig = {}) {
586
+ const config = isObject(rawConfig) ? rawConfig : {};
587
+ const fetchImpl = config.fetch ?? globalThis.fetch;
588
+
589
+ if (typeof fetchImpl !== 'function') {
590
+ throw new Error('Fetch implementation is required (pass config.fetch or run in an environment with global fetch).');
591
+ }
592
+
593
+ const authConfig = isObject(config.auth) ? config.auth : {};
594
+ const oauthConfig = isObject(authConfig.oauth) ? authConfig.oauth : {};
595
+ const sessionConfig = isObject(authConfig.session) ? authConfig.session : {};
596
+
597
+ const defaultMode = normalizeAuthMode(authConfig.mode, 'auto');
598
+ const sessionEnabled = sessionConfig.enabled !== false;
599
+ const sessionCredentials = sessionConfig.credentials ?? 'include';
600
+
601
+ const providedTokenStore = oauthConfig.tokenStore;
602
+ const tokenStore =
603
+ providedTokenStore && typeof providedTokenStore.get === 'function' && typeof providedTokenStore.set === 'function'
604
+ ? providedTokenStore
605
+ : new MemoryTokenStore(oauthConfig.token ?? null);
606
+
607
+ const defaultHeaders = isObject(config.headers) ? config.headers : {};
608
+ const retryConfig = normalizeRetry(config.retry);
609
+ const schemaApi = createSchema({ services: SERVICES, schema: SCHEMA });
610
+ const validateByDefault = config.validate === true;
611
+ const operationLookup = new Map();
612
+
613
+ for (const [serviceKey, service] of Object.entries(SERVICES)) {
614
+ for (const operation of service.operations) {
615
+ operationLookup.set(`${serviceKey}.${operation.operationId}`, operation);
616
+ }
617
+ }
618
+
619
+ async function getTokenSet() {
620
+ return normalizeTokenSet(await tokenStore.get());
621
+ }
622
+
623
+ async function setTokenSet(tokenSet) {
624
+ await tokenStore.set(normalizeTokenSet(tokenSet));
625
+ }
626
+
627
+ async function clearTokenSet() {
628
+ await tokenStore.set(null);
629
+ }
630
+
631
+ async function getSessionCookieHeader() {
632
+ const cookieSource = sessionConfig.cookie;
633
+ const rawCookie = typeof cookieSource === 'function' ? await cookieSource() : cookieSource;
634
+
635
+ if (!rawCookie) {
636
+ return null;
637
+ }
638
+
639
+ const cookieValue = String(rawCookie);
640
+ if (cookieValue.includes('=')) {
641
+ return cookieValue;
642
+ }
643
+
644
+ return `ZEYOSID=${cookieValue}`;
645
+ }
646
+
647
+ async function sendRequestOnce({ serviceKey, operation, prepared, requestAuth, tokenSet, candidate, requestOptions }) {
648
+ const body = cloneValue(prepared.body);
649
+ const authHeaders = {};
650
+ let credentials;
651
+
652
+ if (candidate.type === 'bearer') {
653
+ const accessToken = requestAuth.accessToken ?? requestAuth.access_token ?? tokenSet?.accessToken;
654
+ if (!accessToken) {
655
+ throw new Error('Missing access token for bearer-authenticated request.');
656
+ }
657
+ authHeaders.authorization = `Bearer ${accessToken}`;
658
+ }
659
+
660
+ if (candidate.type === 'basic') {
661
+ const credentialsPair = getBasicCredentials({ body, requestAuth, oauthConfig });
662
+ if (!credentialsPair) {
663
+ throw new Error('Missing client_id/client_secret for basic-authenticated request.');
664
+ }
665
+
666
+ authHeaders.authorization = `Basic ${toBase64(`${credentialsPair.clientId}:${credentialsPair.clientSecret}`)}`;
667
+
668
+ if (isObject(body)) {
669
+ if (!Object.prototype.hasOwnProperty.call(body, 'client_id')) {
670
+ body.client_id = credentialsPair.clientId;
671
+ }
672
+ if (!Object.prototype.hasOwnProperty.call(body, 'client_secret')) {
673
+ body.client_secret = credentialsPair.clientSecret;
674
+ }
675
+ }
676
+ }
677
+
678
+ if (candidate.type === 'session') {
679
+ credentials = sessionCredentials;
680
+ const cookieHeader = await getSessionCookieHeader();
681
+ if (cookieHeader) {
682
+ authHeaders.cookie = cookieHeader;
683
+ }
684
+ }
685
+
686
+ const bodyType = chooseBodyType(serviceKey, operation, { ...prepared, body }, requestOptions?.bodyType);
687
+ const headers = mergeHeaders(defaultHeaders, prepared.headers, authHeaders);
688
+
689
+ if (!headers.has('accept')) {
690
+ headers.set('accept', 'application/json, text/plain;q=0.9, */*;q=0.8');
691
+ }
692
+
693
+ const url = buildUrl(
694
+ resolveBaseUrl({ services: SERVICES, serviceKey, config, explicitBaseUrl: prepared.baseUrl ?? requestOptions?.baseUrl }),
695
+ operation.path,
696
+ prepared.pathParams,
697
+ prepared.query
698
+ );
699
+
700
+ const signal = prepared.signal ?? requestOptions?.signal;
701
+
702
+ let response;
703
+ for (let attempt = 0; ; attempt++) {
704
+ response = await httpRequest({
705
+ fetchImpl,
706
+ url,
707
+ method: operation.method,
708
+ headers,
709
+ body,
710
+ bodyType,
711
+ signal,
712
+ credentials
713
+ });
714
+
715
+ if (attempt >= retryConfig.maxRetries || !retryConfig.retryOn.has(response.status)) {
716
+ break;
717
+ }
718
+ await abortableDelay(computeRetryDelay(response, attempt, retryConfig), signal);
719
+ }
720
+
721
+ if (!isSuccessfulHttpStatus(response.status)) {
722
+ throw createApiError(response, {
723
+ serviceKey,
724
+ operation,
725
+ method: operation.method,
726
+ url
727
+ });
728
+ }
729
+
730
+ return {
731
+ ...response,
732
+ data: operation.method === 'HEAD' ? true : response.data
733
+ };
734
+ }
735
+
736
+ async function refreshAccessToken(currentTokenSet, requestAuth = {}, requestOptions = {}) {
737
+ const refreshToken = requestAuth.refreshToken ?? requestAuth.refresh_token ?? currentTokenSet?.refreshToken;
738
+ if (!refreshToken) {
739
+ return null;
740
+ }
741
+
742
+ const credentials = getBasicCredentials({ body: {}, requestAuth, oauthConfig });
743
+ if (!credentials) {
744
+ return null;
745
+ }
746
+
747
+ const tokenOperation = operationLookup.get('oauth2.getToken');
748
+ if (!tokenOperation) {
749
+ return null;
750
+ }
751
+
752
+ const prepared = {
753
+ pathParams: {},
754
+ query: {},
755
+ headers: {},
756
+ body: {
757
+ grant_type: 'refresh_token',
758
+ refresh_token: refreshToken,
759
+ client_id: credentials.clientId,
760
+ client_secret: credentials.clientSecret
761
+ },
762
+ bodyType: 'form',
763
+ signal: requestOptions.signal,
764
+ baseUrl: requestOptions.baseUrl
765
+ };
766
+
767
+ const response = await sendRequestOnce({
768
+ serviceKey: 'oauth2',
769
+ operation: tokenOperation,
770
+ prepared,
771
+ requestAuth,
772
+ tokenSet: currentTokenSet,
773
+ candidate: { type: 'basic' },
774
+ requestOptions: { ...requestOptions, bodyType: 'form' }
775
+ });
776
+
777
+ const nextTokenSet = tokenResponseToTokenSet(response.data);
778
+ if (!nextTokenSet) {
779
+ return null;
780
+ }
781
+
782
+ if (!nextTokenSet.refreshToken && currentTokenSet?.refreshToken) {
783
+ nextTokenSet.refreshToken = currentTokenSet.refreshToken;
784
+ }
785
+
786
+ await tokenStore.set(nextTokenSet);
787
+ return nextTokenSet;
788
+ }
789
+
790
+ async function executeOperation({ serviceKey, operation, prepared, requestOptions = {} }) {
791
+ const requestAuth = normalizeRequestAuth(prepared.auth ?? requestOptions.auth);
792
+ const mode = normalizeAuthMode(requestAuth.mode, defaultMode);
793
+ const schemes = securitySchemesFromOperation(operation);
794
+ let tokenSet = await getTokenSet();
795
+
796
+ if (
797
+ schemes.includes('bearer') &&
798
+ isAccessTokenExpired(tokenSet) &&
799
+ canRefreshAccessToken({ mode, operation, tokenSet, oauthConfig })
800
+ ) {
801
+ try {
802
+ const refreshed = await refreshAccessToken(tokenSet, requestAuth, requestOptions);
803
+ if (refreshed?.accessToken) {
804
+ tokenSet = refreshed;
805
+ }
806
+ } catch {
807
+ // Fall back to the normal request path; a 401 can still trigger refresh.
808
+ }
809
+ }
810
+
811
+ const candidates = resolveAuthCandidates({
812
+ mode,
813
+ schemes,
814
+ tokenSet,
815
+ sessionEnabled
816
+ });
817
+
818
+ const raw = requestOptions.raw ?? prepared.raw ?? false;
819
+ let lastError;
820
+
821
+ for (const candidate of candidates) {
822
+ try {
823
+ const response = await sendRequestOnce({
824
+ serviceKey,
825
+ operation,
826
+ prepared,
827
+ requestAuth,
828
+ tokenSet,
829
+ candidate,
830
+ requestOptions
831
+ });
832
+
833
+ return raw ? response : response.data;
834
+ } catch (error) {
835
+ if (!(error instanceof ZeyosApiError) || error.status !== 401) {
836
+ throw error;
837
+ }
838
+
839
+ if (candidate.type === 'bearer' && canRefreshAccessToken({ mode, operation, tokenSet, oauthConfig })) {
840
+ try {
841
+ const refreshed = await refreshAccessToken(tokenSet, requestAuth, requestOptions);
842
+ if (refreshed?.accessToken) {
843
+ tokenSet = refreshed;
844
+ const retryResponse = await sendRequestOnce({
845
+ serviceKey,
846
+ operation,
847
+ prepared,
848
+ requestAuth,
849
+ tokenSet,
850
+ candidate,
851
+ requestOptions
852
+ });
853
+ return raw ? retryResponse : retryResponse.data;
854
+ }
855
+ } catch (refreshError) {
856
+ lastError = refreshError;
857
+ continue;
858
+ }
859
+ }
860
+
861
+ lastError = error;
862
+ }
863
+ }
864
+
865
+ if (lastError) {
866
+ throw lastError;
867
+ }
868
+
869
+ throw new Error('Unable to execute request due to missing authentication candidates.');
870
+ }
871
+
872
+ function bindService(serviceKey) {
873
+ const service = SERVICES[serviceKey];
874
+ if (!service) {
875
+ return Object.freeze({});
876
+ }
877
+
878
+ const namespace = {};
879
+ const operationIds = service.operations.map((operation) => operation.operationId);
880
+
881
+ for (const operation of service.operations) {
882
+ namespace[operation.operationId] = async (input, requestOptions) => {
883
+ if (validateByDefault || requestOptions?.validate === true) {
884
+ const result = schemaApi.validate(operation.operationId, input);
885
+ if (!result.valid) {
886
+ throw new ZeyosValidationError(
887
+ `${operation.operationId}: ${result.errors.map((entry) => entry.message).join(' ')}`,
888
+ { operationId: operation.operationId, errors: result.errors }
889
+ );
890
+ }
891
+ }
892
+ const prepared = prepareOperationInput(operation, input);
893
+ return executeOperation({ serviceKey, operation, prepared, requestOptions });
894
+ };
895
+ }
896
+
897
+ // Return a helpful "did you mean ...?" error when an agent calls an
898
+ // operation that does not exist (e.g. listDunning vs listDunningNotices),
899
+ // instead of an opaque "x is not a function" TypeError.
900
+ return new Proxy(Object.freeze(namespace), {
901
+ get(target, prop, receiver) {
902
+ if (typeof prop !== 'string' || prop === 'then' || prop in target) {
903
+ return Reflect.get(target, prop, receiver);
904
+ }
905
+ // Async so an unknown operation rejects like a real operation call
906
+ // would, rather than throwing synchronously before `.catch()`/`await`.
907
+ return async () => {
908
+ const suggestion = suggestClosest(prop, operationIds);
909
+ throw new ZeyosApiError(
910
+ `Unknown operation '${serviceKey}.${prop}'.` +
911
+ (suggestion
912
+ ? ` Did you mean '${suggestion}'?`
913
+ : ' Use client.schema.operationIds() to list valid operations.'),
914
+ { operationId: prop, service: serviceKey }
915
+ );
916
+ };
917
+ }
918
+ });
919
+ }
920
+
921
+ async function request(input = {}, requestOptions = {}) {
922
+ if (!isObject(input)) {
923
+ throw new Error('client.request input must be an object.');
924
+ }
925
+
926
+ const serviceKey = input.service;
927
+ if (!serviceKey || typeof serviceKey !== 'string') {
928
+ throw new Error('client.request requires a service key.');
929
+ }
930
+
931
+ if (input.operationId) {
932
+ const operation = operationLookup.get(`${serviceKey}.${input.operationId}`);
933
+ if (!operation) {
934
+ const candidates = (SERVICES[serviceKey]?.operations ?? []).map((entry) => entry.operationId);
935
+ const suggestion = suggestClosest(input.operationId, candidates);
936
+ throw new ZeyosApiError(
937
+ `Unknown operation: ${serviceKey}.${input.operationId}.` + (suggestion ? ` Did you mean '${suggestion}'?` : ''),
938
+ { operationId: input.operationId, service: serviceKey }
939
+ );
940
+ }
941
+
942
+ const prepared = {
943
+ pathParams: isObject(input.pathParams) ? input.pathParams : {},
944
+ query: isObject(input.query) ? input.query : {},
945
+ headers: isObject(input.headers) ? input.headers : {},
946
+ body: input.body,
947
+ bodyType: input.bodyType,
948
+ auth: input.auth,
949
+ signal: input.signal,
950
+ raw: input.raw,
951
+ baseUrl: input.baseUrl
952
+ };
953
+
954
+ return executeOperation({ serviceKey, operation, prepared, requestOptions });
955
+ }
956
+
957
+ if (!input.path || !input.method) {
958
+ throw new Error('client.request requires method and path when operationId is not provided.');
959
+ }
960
+
961
+ const operation = {
962
+ operationId: 'request',
963
+ method: String(input.method).toUpperCase(),
964
+ path: String(input.path),
965
+ security: Array.isArray(input.security) ? input.security : [],
966
+ requestContentTypes: Array.isArray(input.requestContentTypes) ? input.requestContentTypes : ['application/json'],
967
+ parameterNames: {
968
+ path: [],
969
+ query: [],
970
+ header: []
971
+ }
972
+ };
973
+
974
+ const prepared = {
975
+ pathParams: isObject(input.pathParams) ? input.pathParams : {},
976
+ query: isObject(input.query) ? input.query : {},
977
+ headers: isObject(input.headers) ? input.headers : {},
978
+ body: input.body,
979
+ bodyType: input.bodyType,
980
+ auth: input.auth,
981
+ signal: input.signal,
982
+ raw: input.raw,
983
+ baseUrl: input.baseUrl
984
+ };
985
+
986
+ return executeOperation({ serviceKey, operation, prepared, requestOptions });
987
+ }
988
+
989
+ const api = bindService('api');
990
+ const oauth2Operations = bindService('oauth2');
991
+ const legacyAuth = bindService('legacyAuth');
992
+
993
+ function buildAuthorizationUrl(options = {}) {
994
+ const clientId = options.clientId ?? options.client_id ?? oauthConfig.clientId;
995
+ const redirectUri = options.redirectUri ?? options.redirect_uri;
996
+
997
+ if (!clientId) {
998
+ throw new Error('buildAuthorizationUrl requires clientId (or auth.oauth.clientId in client config).');
999
+ }
1000
+ if (!redirectUri) {
1001
+ throw new Error('buildAuthorizationUrl requires redirectUri.');
1002
+ }
1003
+
1004
+ const query = {
1005
+ client_id: clientId,
1006
+ redirect_uri: redirectUri,
1007
+ response_type: 'code',
1008
+ scope: options.scope ?? options.options?.scope,
1009
+ response_mode: options.responseMode ?? options.response_mode,
1010
+ code_challenge: options.codeChallenge ?? options.code_challenge,
1011
+ code_challenge_method: options.codeChallengeMethod ?? options.code_challenge_method,
1012
+ state: options.state
1013
+ };
1014
+
1015
+ if (query.code_challenge && !query.code_challenge_method) {
1016
+ query.code_challenge_method = 'S256';
1017
+ }
1018
+
1019
+ return buildUrl(
1020
+ resolveBaseUrl({ services: SERVICES, serviceKey: 'oauth2', config, explicitBaseUrl: options.baseUrl }),
1021
+ '/authorize',
1022
+ {},
1023
+ query
1024
+ );
1025
+ }
1026
+
1027
+ function parseAuthorizationCallback(callbackUrl) {
1028
+ const url =
1029
+ callbackUrl instanceof URL
1030
+ ? callbackUrl
1031
+ : (() => {
1032
+ try {
1033
+ return new URL(String(callbackUrl));
1034
+ } catch {
1035
+ return new URL(String(callbackUrl), 'http://localhost');
1036
+ }
1037
+ })();
1038
+
1039
+ const params = url.searchParams;
1040
+
1041
+ return {
1042
+ code: params.get('code'),
1043
+ state: params.get('state'),
1044
+ error: params.get('error'),
1045
+ errorDescription: params.get('error_description'),
1046
+ errorUri: params.get('error_uri'),
1047
+ isError: params.has('error')
1048
+ };
1049
+ }
1050
+
1051
+ async function storeTokenResponse(tokenResponse, store = true) {
1052
+ const tokenSet = tokenResponseToTokenSet(tokenResponse);
1053
+ if (store && tokenSet) {
1054
+ await tokenStore.set(tokenSet);
1055
+ }
1056
+ return tokenSet || tokenResponse;
1057
+ }
1058
+
1059
+ async function exchangeAuthorizationCode(options = {}, requestOptions = {}) {
1060
+ const clientId = options.clientId ?? options.client_id ?? oauthConfig.clientId;
1061
+ const clientSecret = options.clientSecret ?? options.client_secret ?? oauthConfig.clientSecret;
1062
+ const code = options.code;
1063
+
1064
+ if (!code) {
1065
+ throw new Error('exchangeAuthorizationCode requires code.');
1066
+ }
1067
+
1068
+ const tokenResponse = await request(
1069
+ {
1070
+ service: 'oauth2',
1071
+ operationId: 'getToken',
1072
+ body: {
1073
+ grant_type: 'authorization_code',
1074
+ code,
1075
+ code_verifier: options.codeVerifier ?? options.code_verifier,
1076
+ redirect_uri: options.redirectUri ?? options.redirect_uri,
1077
+ client_id: clientId,
1078
+ client_secret: clientSecret
1079
+ },
1080
+ auth: {
1081
+ mode: 'oauth',
1082
+ clientId,
1083
+ clientSecret
1084
+ },
1085
+ bodyType: 'form',
1086
+ raw: false,
1087
+ baseUrl: options.baseUrl
1088
+ },
1089
+ requestOptions
1090
+ );
1091
+
1092
+ return storeTokenResponse(tokenResponse, options.store !== false);
1093
+ }
1094
+
1095
+ async function refreshToken(options = {}, requestOptions = {}) {
1096
+ const clientId = options.clientId ?? options.client_id ?? oauthConfig.clientId;
1097
+ const clientSecret = options.clientSecret ?? options.client_secret ?? oauthConfig.clientSecret;
1098
+
1099
+ const tokenSet = await getTokenSet();
1100
+ const refreshTokenValue = options.refreshToken ?? options.refresh_token ?? tokenSet?.refreshToken;
1101
+
1102
+ if (!refreshTokenValue) {
1103
+ throw new Error('refreshToken requires refreshToken or a stored token with refreshToken.');
1104
+ }
1105
+
1106
+ const tokenResponse = await request(
1107
+ {
1108
+ service: 'oauth2',
1109
+ operationId: 'getToken',
1110
+ body: {
1111
+ grant_type: 'refresh_token',
1112
+ refresh_token: refreshTokenValue,
1113
+ client_id: clientId,
1114
+ client_secret: clientSecret
1115
+ },
1116
+ auth: {
1117
+ mode: 'oauth',
1118
+ clientId,
1119
+ clientSecret
1120
+ },
1121
+ bodyType: 'form',
1122
+ baseUrl: options.baseUrl
1123
+ },
1124
+ requestOptions
1125
+ );
1126
+
1127
+ return storeTokenResponse(tokenResponse, options.store !== false);
1128
+ }
1129
+
1130
+ async function revokeToken(options = {}, requestOptions = {}) {
1131
+ const clientId = options.clientId ?? options.client_id ?? oauthConfig.clientId;
1132
+ const clientSecret = options.clientSecret ?? options.client_secret ?? oauthConfig.clientSecret;
1133
+
1134
+ return request(
1135
+ {
1136
+ service: 'oauth2',
1137
+ operationId: 'revokeToken',
1138
+ body: {
1139
+ token: options.token,
1140
+ client_id: clientId,
1141
+ client_secret: clientSecret
1142
+ },
1143
+ auth: {
1144
+ mode: 'oauth',
1145
+ clientId,
1146
+ clientSecret
1147
+ },
1148
+ bodyType: 'form',
1149
+ baseUrl: options.baseUrl
1150
+ },
1151
+ requestOptions
1152
+ );
1153
+ }
1154
+
1155
+ async function introspectToken(options = {}, requestOptions = {}) {
1156
+ const clientId = options.clientId ?? options.client_id ?? oauthConfig.clientId;
1157
+ const clientSecret = options.clientSecret ?? options.client_secret ?? oauthConfig.clientSecret;
1158
+
1159
+ return request(
1160
+ {
1161
+ service: 'oauth2',
1162
+ operationId: 'introspectToken',
1163
+ body: {
1164
+ token: options.token,
1165
+ client_id: clientId,
1166
+ client_secret: clientSecret
1167
+ },
1168
+ auth: {
1169
+ mode: 'oauth',
1170
+ clientId,
1171
+ clientSecret
1172
+ },
1173
+ bodyType: 'form',
1174
+ baseUrl: options.baseUrl
1175
+ },
1176
+ requestOptions
1177
+ );
1178
+ }
1179
+
1180
+ const oauth2 = Object.freeze({
1181
+ ...oauth2Operations,
1182
+ buildAuthorizationUrl,
1183
+ parseAuthorizationCallback,
1184
+ exchangeAuthorizationCode,
1185
+ refreshToken,
1186
+ revokeToken,
1187
+ introspectToken
1188
+ });
1189
+
1190
+ const client = {
1191
+ api,
1192
+ oauth2,
1193
+ legacyAuth,
1194
+ request,
1195
+ schema: schemaApi,
1196
+ auth: {
1197
+ getTokenSet,
1198
+ setTokenSet,
1199
+ clearTokenSet
1200
+ },
1201
+ metadata: {
1202
+ generatedAt: GENERATED.generatedAt,
1203
+ services: SERVICE_KEYS
1204
+ }
1205
+ };
1206
+
1207
+ return Object.freeze(client);
1208
+ }