@syfthub/sdk 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.
package/dist/index.cjs ADDED
@@ -0,0 +1,2604 @@
1
+ 'use strict';
2
+
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __esm = (fn, res) => function __init() {
6
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
7
+ };
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+
13
+ // src/errors.ts
14
+ var errors_exports = {};
15
+ __export(errors_exports, {
16
+ APIError: () => exports.APIError,
17
+ AccountingAccountExistsError: () => exports.AccountingAccountExistsError,
18
+ AccountingServiceUnavailableError: () => exports.AccountingServiceUnavailableError,
19
+ AuthenticationError: () => exports.AuthenticationError,
20
+ AuthorizationError: () => exports.AuthorizationError,
21
+ ConfigurationError: () => exports.ConfigurationError,
22
+ InvalidAccountingPasswordError: () => exports.InvalidAccountingPasswordError,
23
+ NetworkError: () => exports.NetworkError,
24
+ NotFoundError: () => exports.NotFoundError,
25
+ SyftHubError: () => exports.SyftHubError,
26
+ UserAlreadyExistsError: () => exports.UserAlreadyExistsError,
27
+ ValidationError: () => exports.ValidationError
28
+ });
29
+ exports.SyftHubError = void 0; exports.APIError = void 0; exports.AuthenticationError = void 0; exports.AuthorizationError = void 0; exports.NotFoundError = void 0; exports.ValidationError = void 0; exports.NetworkError = void 0; exports.ConfigurationError = void 0; exports.UserAlreadyExistsError = void 0; exports.AccountingAccountExistsError = void 0; exports.InvalidAccountingPasswordError = void 0; exports.AccountingServiceUnavailableError = void 0;
30
+ var init_errors = __esm({
31
+ "src/errors.ts"() {
32
+ exports.SyftHubError = class extends Error {
33
+ constructor(message) {
34
+ super(message);
35
+ this.name = "SyftHubError";
36
+ if (Error.captureStackTrace) {
37
+ Error.captureStackTrace(this, this.constructor);
38
+ }
39
+ }
40
+ };
41
+ exports.APIError = class extends exports.SyftHubError {
42
+ constructor(message, status, data) {
43
+ super(message);
44
+ this.status = status;
45
+ this.data = data;
46
+ this.name = "APIError";
47
+ }
48
+ };
49
+ exports.AuthenticationError = class extends exports.SyftHubError {
50
+ constructor(message = "Authentication required") {
51
+ super(message);
52
+ this.name = "AuthenticationError";
53
+ }
54
+ };
55
+ exports.AuthorizationError = class extends exports.SyftHubError {
56
+ constructor(message = "Permission denied") {
57
+ super(message);
58
+ this.name = "AuthorizationError";
59
+ }
60
+ };
61
+ exports.NotFoundError = class extends exports.SyftHubError {
62
+ constructor(message = "Resource not found") {
63
+ super(message);
64
+ this.name = "NotFoundError";
65
+ }
66
+ };
67
+ exports.ValidationError = class extends exports.SyftHubError {
68
+ constructor(message, errors) {
69
+ super(message);
70
+ this.errors = errors;
71
+ this.name = "ValidationError";
72
+ }
73
+ };
74
+ exports.NetworkError = class extends exports.SyftHubError {
75
+ constructor(message = "Network request failed", cause) {
76
+ super(message);
77
+ this.cause = cause;
78
+ this.name = "NetworkError";
79
+ }
80
+ };
81
+ exports.ConfigurationError = class extends exports.SyftHubError {
82
+ constructor(message = "Invalid SDK configuration") {
83
+ super(message);
84
+ this.name = "ConfigurationError";
85
+ }
86
+ };
87
+ exports.UserAlreadyExistsError = class extends exports.SyftHubError {
88
+ constructor(message = "Username or email already exists", detail) {
89
+ super(message);
90
+ this.detail = detail;
91
+ this.name = "UserAlreadyExistsError";
92
+ if (detail && typeof detail === "object" && "field" in detail) {
93
+ this.field = detail.field;
94
+ }
95
+ }
96
+ /** The field that caused the conflict ("username" or "email") */
97
+ field;
98
+ };
99
+ exports.AccountingAccountExistsError = class extends exports.SyftHubError {
100
+ constructor(message = "This email already has an account in the accounting service", detail) {
101
+ super(message);
102
+ this.detail = detail;
103
+ this.name = "AccountingAccountExistsError";
104
+ }
105
+ /** Indicates that the user needs to provide their existing accounting password */
106
+ requiresAccountingPassword = true;
107
+ };
108
+ exports.InvalidAccountingPasswordError = class extends exports.SyftHubError {
109
+ constructor(message = "The provided accounting password is invalid", detail) {
110
+ super(message);
111
+ this.detail = detail;
112
+ this.name = "InvalidAccountingPasswordError";
113
+ }
114
+ };
115
+ exports.AccountingServiceUnavailableError = class extends exports.SyftHubError {
116
+ constructor(message = "Accounting service is unavailable", detail) {
117
+ super(message);
118
+ this.detail = detail;
119
+ this.name = "AccountingServiceUnavailableError";
120
+ }
121
+ };
122
+ }
123
+ });
124
+
125
+ // src/http.ts
126
+ init_errors();
127
+
128
+ // src/utils.ts
129
+ function snakeToCamel(str) {
130
+ return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
131
+ }
132
+ function camelToSnake(str) {
133
+ return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
134
+ }
135
+ var ISO_DATE_REGEX = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/;
136
+ function isISODateString(value) {
137
+ return typeof value === "string" && ISO_DATE_REGEX.test(value);
138
+ }
139
+ function transformKeys(obj, keyTransformer, parseDates = true) {
140
+ if (obj === null || obj === void 0) {
141
+ return obj;
142
+ }
143
+ if (Array.isArray(obj)) {
144
+ return obj.map((item) => transformKeys(item, keyTransformer, parseDates));
145
+ }
146
+ if (parseDates && isISODateString(obj)) {
147
+ return new Date(obj);
148
+ }
149
+ if (typeof obj === "object") {
150
+ const transformed = {};
151
+ for (const [key, value] of Object.entries(obj)) {
152
+ transformed[keyTransformer(key)] = transformKeys(value, keyTransformer, parseDates);
153
+ }
154
+ return transformed;
155
+ }
156
+ return obj;
157
+ }
158
+ function toSnakeCase(obj) {
159
+ return transformKeys(obj, camelToSnake, false);
160
+ }
161
+ function toCamelCase(obj) {
162
+ return transformKeys(obj, snakeToCamel, true);
163
+ }
164
+ function buildSearchParams(params) {
165
+ const searchParams = new URLSearchParams();
166
+ for (const [key, value] of Object.entries(params)) {
167
+ if (value !== void 0 && value !== null) {
168
+ searchParams.append(camelToSnake(key), String(value));
169
+ }
170
+ }
171
+ return searchParams;
172
+ }
173
+
174
+ // src/http.ts
175
+ init_errors();
176
+ var HTTPClient = class {
177
+ /**
178
+ * Create a new HTTP client.
179
+ *
180
+ * @param baseUrl - Base URL for all API requests (without trailing slash)
181
+ * @param timeout - Default timeout in milliseconds (default: 30000)
182
+ */
183
+ constructor(baseUrl, timeout = 3e4) {
184
+ this.baseUrl = baseUrl;
185
+ this.timeout = timeout;
186
+ }
187
+ accessToken = null;
188
+ refreshToken = null;
189
+ isRefreshing = false;
190
+ refreshPromise = null;
191
+ /**
192
+ * Set authentication tokens.
193
+ */
194
+ setTokens(access, refresh) {
195
+ this.accessToken = access;
196
+ this.refreshToken = refresh;
197
+ }
198
+ /**
199
+ * Get current authentication tokens.
200
+ */
201
+ getTokens() {
202
+ if (!this.accessToken || !this.refreshToken) {
203
+ return null;
204
+ }
205
+ return {
206
+ accessToken: this.accessToken,
207
+ refreshToken: this.refreshToken,
208
+ tokenType: "bearer"
209
+ };
210
+ }
211
+ /**
212
+ * Clear authentication tokens.
213
+ */
214
+ clearTokens() {
215
+ this.accessToken = null;
216
+ this.refreshToken = null;
217
+ }
218
+ /**
219
+ * Check if the client has valid tokens.
220
+ */
221
+ hasTokens() {
222
+ return this.accessToken !== null;
223
+ }
224
+ /**
225
+ * Make a GET request.
226
+ */
227
+ async get(path, params, options) {
228
+ return this.request("GET", path, { ...options, params });
229
+ }
230
+ /**
231
+ * Make a POST request.
232
+ */
233
+ async post(path, body, options) {
234
+ return this.request("POST", path, { ...options, body });
235
+ }
236
+ /**
237
+ * Make a PUT request.
238
+ */
239
+ async put(path, body, options) {
240
+ return this.request("PUT", path, { ...options, body });
241
+ }
242
+ /**
243
+ * Make a PATCH request.
244
+ */
245
+ async patch(path, body, options) {
246
+ return this.request("PATCH", path, { ...options, body });
247
+ }
248
+ /**
249
+ * Make a DELETE request.
250
+ */
251
+ async delete(path, options) {
252
+ return this.request("DELETE", path, options);
253
+ }
254
+ /**
255
+ * Make an HTTP request with automatic retry on 401.
256
+ */
257
+ async request(method, path, options = {}) {
258
+ const { includeAuth = true, isFormData = false, timeout, body, params } = options;
259
+ let url = `${this.baseUrl}${path}`;
260
+ if (params) {
261
+ const searchParams = buildSearchParams(params);
262
+ const queryString = searchParams.toString();
263
+ if (queryString) {
264
+ url += `?${queryString}`;
265
+ }
266
+ }
267
+ const headers = {};
268
+ if (includeAuth && this.accessToken) {
269
+ headers["Authorization"] = `Bearer ${this.accessToken}`;
270
+ }
271
+ let requestBody;
272
+ if (body !== void 0) {
273
+ if (isFormData) {
274
+ headers["Content-Type"] = "application/x-www-form-urlencoded";
275
+ const formData = new URLSearchParams();
276
+ for (const [key, value] of Object.entries(body)) {
277
+ if (value !== void 0 && value !== null) {
278
+ formData.append(key, String(value));
279
+ }
280
+ }
281
+ requestBody = formData.toString();
282
+ } else {
283
+ headers["Content-Type"] = "application/json";
284
+ requestBody = JSON.stringify(toSnakeCase(body));
285
+ }
286
+ }
287
+ const controller = new AbortController();
288
+ const timeoutId = setTimeout(() => controller.abort(), timeout ?? this.timeout);
289
+ try {
290
+ const response = await fetch(url, {
291
+ method,
292
+ headers,
293
+ body: requestBody,
294
+ signal: controller.signal
295
+ });
296
+ clearTimeout(timeoutId);
297
+ if (response.status === 401 && includeAuth && this.refreshToken) {
298
+ await this.attemptTokenRefresh();
299
+ return this.request(method, path, {
300
+ ...options,
301
+ // Mark that we shouldn't retry again to prevent infinite loops
302
+ includeAuth: true
303
+ });
304
+ }
305
+ return await this.handleResponse(response);
306
+ } catch (error) {
307
+ clearTimeout(timeoutId);
308
+ if (error instanceof exports.SyftHubError) {
309
+ throw error;
310
+ }
311
+ if (error instanceof Error) {
312
+ if (error.name === "AbortError") {
313
+ throw new exports.NetworkError("Request timed out", error);
314
+ }
315
+ throw new exports.NetworkError(error.message, error);
316
+ }
317
+ throw new exports.NetworkError("Unknown network error");
318
+ }
319
+ }
320
+ /**
321
+ * Handle the HTTP response and convert to the expected type.
322
+ */
323
+ async handleResponse(response) {
324
+ let data;
325
+ const contentType = response.headers.get("content-type");
326
+ if (contentType?.includes("application/json")) {
327
+ try {
328
+ data = await response.json();
329
+ } catch {
330
+ data = null;
331
+ }
332
+ } else {
333
+ const text = await response.text();
334
+ data = text || null;
335
+ }
336
+ if (!response.ok) {
337
+ this.handleErrorResponse(response.status, data);
338
+ }
339
+ return toCamelCase(data);
340
+ }
341
+ /**
342
+ * Handle error responses by throwing appropriate exceptions.
343
+ */
344
+ handleErrorResponse(status, data) {
345
+ const message = this.extractErrorMessage(data);
346
+ const { code, detail } = this.extractErrorCodeAndDetail(data);
347
+ if (code) {
348
+ switch (code) {
349
+ // User registration errors
350
+ case "USER_ALREADY_EXISTS":
351
+ throw new exports.UserAlreadyExistsError(message, detail);
352
+ // Accounting-related errors
353
+ case "ACCOUNTING_ACCOUNT_EXISTS":
354
+ throw new exports.AccountingAccountExistsError(message, detail);
355
+ case "INVALID_ACCOUNTING_PASSWORD":
356
+ throw new exports.InvalidAccountingPasswordError(message, detail);
357
+ case "ACCOUNTING_SERVICE_UNAVAILABLE":
358
+ throw new exports.AccountingServiceUnavailableError(message, detail);
359
+ }
360
+ }
361
+ switch (status) {
362
+ case 401:
363
+ throw new exports.AuthenticationError(message);
364
+ case 403:
365
+ throw new exports.AuthorizationError(message);
366
+ case 404:
367
+ throw new exports.NotFoundError(message);
368
+ case 422:
369
+ throw new exports.ValidationError(message, this.extractValidationErrors(data));
370
+ default:
371
+ throw new exports.APIError(message, status, data);
372
+ }
373
+ }
374
+ /**
375
+ * Extract error code and detail from API response.
376
+ * Used for accounting-specific error handling.
377
+ */
378
+ extractErrorCodeAndDetail(data) {
379
+ if (!data || typeof data !== "object") {
380
+ return {};
381
+ }
382
+ if ("detail" in data) {
383
+ const detail = data.detail;
384
+ if (detail && typeof detail === "object" && "code" in detail) {
385
+ const innerDetail = detail;
386
+ return {
387
+ code: innerDetail.code,
388
+ detail
389
+ };
390
+ }
391
+ }
392
+ return {};
393
+ }
394
+ /**
395
+ * Extract error message from API response.
396
+ */
397
+ extractErrorMessage(data) {
398
+ if (typeof data === "string") {
399
+ return data;
400
+ }
401
+ if (data && typeof data === "object") {
402
+ if ("detail" in data) {
403
+ const detail = data.detail;
404
+ if (typeof detail === "string") {
405
+ return detail;
406
+ }
407
+ if (Array.isArray(detail) && detail.length > 0) {
408
+ const firstError = detail[0];
409
+ if (firstError?.msg) {
410
+ return firstError.msg;
411
+ }
412
+ }
413
+ }
414
+ if ("message" in data && typeof data.message === "string") {
415
+ return data.message;
416
+ }
417
+ if ("error" in data && typeof data.error === "string") {
418
+ return data.error;
419
+ }
420
+ }
421
+ return "An error occurred";
422
+ }
423
+ /**
424
+ * Extract field-level validation errors from API response.
425
+ */
426
+ extractValidationErrors(data) {
427
+ if (!data || typeof data !== "object" || !("detail" in data)) {
428
+ return void 0;
429
+ }
430
+ const detail = data.detail;
431
+ if (!Array.isArray(detail)) {
432
+ return void 0;
433
+ }
434
+ const errors = {};
435
+ for (const error of detail) {
436
+ if (typeof error === "object" && error !== null && "loc" in error && "msg" in error) {
437
+ const { loc, msg } = error;
438
+ const field = String(loc[loc.length - 1] ?? "unknown");
439
+ if (!errors[field]) {
440
+ errors[field] = [];
441
+ }
442
+ errors[field].push(msg);
443
+ }
444
+ }
445
+ return Object.keys(errors).length > 0 ? errors : void 0;
446
+ }
447
+ /**
448
+ * Attempt to refresh the access token using the refresh token.
449
+ */
450
+ async attemptTokenRefresh() {
451
+ if (this.isRefreshing && this.refreshPromise) {
452
+ await this.refreshPromise;
453
+ return;
454
+ }
455
+ this.isRefreshing = true;
456
+ this.refreshPromise = (async () => {
457
+ try {
458
+ const response = await fetch(`${this.baseUrl}/api/v1/auth/refresh`, {
459
+ method: "POST",
460
+ headers: {
461
+ "Content-Type": "application/json"
462
+ },
463
+ body: JSON.stringify({ refresh_token: this.refreshToken })
464
+ });
465
+ if (!response.ok) {
466
+ this.clearTokens();
467
+ throw new exports.AuthenticationError("Token refresh failed");
468
+ }
469
+ const data = await response.json();
470
+ this.accessToken = data.access_token;
471
+ this.refreshToken = data.refresh_token;
472
+ } finally {
473
+ this.isRefreshing = false;
474
+ this.refreshPromise = null;
475
+ }
476
+ })();
477
+ await this.refreshPromise;
478
+ }
479
+ };
480
+
481
+ // src/client.ts
482
+ init_errors();
483
+
484
+ // src/resources/auth.ts
485
+ init_errors();
486
+ var AuthResource = class {
487
+ constructor(http) {
488
+ this.http = http;
489
+ }
490
+ /**
491
+ * Register a new user account.
492
+ *
493
+ * If an accounting service URL is configured (via `accountingServiceUrl` or server default),
494
+ * the backend will handle accounting integration using a "try-create-first" approach:
495
+ *
496
+ * **Accounting Password Behavior:**
497
+ * - **Not provided**: A secure password is auto-generated and a new accounting account is created.
498
+ * - **Provided (new user)**: The account is created with your chosen password.
499
+ * - **Provided (existing user)**: Your password is validated and accounts are linked.
500
+ *
501
+ * This means you can set your own accounting password during registration even if you're
502
+ * a new user - you don't need an existing accounting account first.
503
+ *
504
+ * @param input - Registration details (username, email, password, fullName)
505
+ * @returns The created User
506
+ * @throws {ValidationError} If input validation fails
507
+ * @throws {UserAlreadyExistsError} If username or email already exists in SyftHub
508
+ * @throws {AccountingAccountExistsError} If email already exists in accounting service
509
+ * and no `accountingPassword` was provided. Retry with the password.
510
+ * @throws {InvalidAccountingPasswordError} If the provided accounting password doesn't
511
+ * match an existing accounting account
512
+ * @throws {AccountingServiceUnavailableError} If the accounting service is unreachable
513
+ *
514
+ * @example
515
+ * // Basic registration (auto-generated accounting password)
516
+ * const user = await client.auth.register({
517
+ * username: 'alice',
518
+ * email: 'alice@example.com',
519
+ * password: 'SecurePass123!',
520
+ * fullName: 'Alice'
521
+ * });
522
+ *
523
+ * @example
524
+ * // Registration with custom accounting password (NEW user)
525
+ * const user = await client.auth.register({
526
+ * username: 'bob',
527
+ * email: 'bob@example.com',
528
+ * password: 'SecurePass123!',
529
+ * fullName: 'Bob',
530
+ * accountingPassword: 'MyChosenAccountingPass!' // Creates account with this password
531
+ * });
532
+ *
533
+ * @example
534
+ * // Handle existing accounting account
535
+ * try {
536
+ * await client.auth.register({ username, email, password, fullName });
537
+ * } catch (error) {
538
+ * if (error instanceof AccountingAccountExistsError) {
539
+ * // Prompt user for their existing accounting password
540
+ * const accountingPassword = await promptUser('Enter your existing accounting password:');
541
+ * await client.auth.register({ username, email, password, fullName, accountingPassword });
542
+ * } else {
543
+ * throw error;
544
+ * }
545
+ * }
546
+ */
547
+ async register(input) {
548
+ const response = await this.http.post("/api/v1/auth/register", input, {
549
+ includeAuth: false
550
+ });
551
+ this.http.setTokens(response.accessToken, response.refreshToken);
552
+ return response.user;
553
+ }
554
+ /**
555
+ * Login with username/email and password.
556
+ *
557
+ * Uses OAuth2 password flow (form-urlencoded body).
558
+ *
559
+ * @param username - Username or email
560
+ * @param password - Password
561
+ * @returns The authenticated User
562
+ * @throws {AuthenticationError} If credentials are invalid
563
+ */
564
+ async login(username, password) {
565
+ const response = await this.http.post(
566
+ "/api/v1/auth/login",
567
+ { username, password },
568
+ {
569
+ includeAuth: false,
570
+ isFormData: true
571
+ }
572
+ );
573
+ this.http.setTokens(response.accessToken, response.refreshToken);
574
+ return response.user;
575
+ }
576
+ /**
577
+ * Logout the current user.
578
+ *
579
+ * Invalidates tokens on the server and clears local token storage.
580
+ */
581
+ async logout() {
582
+ try {
583
+ await this.http.post("/api/v1/auth/logout");
584
+ } finally {
585
+ this.http.clearTokens();
586
+ }
587
+ }
588
+ /**
589
+ * Get the current authenticated user.
590
+ *
591
+ * @returns The current User
592
+ * @throws {AuthenticationError} If not authenticated
593
+ */
594
+ async me() {
595
+ return this.http.get("/api/v1/auth/me");
596
+ }
597
+ /**
598
+ * Manually refresh the access token.
599
+ *
600
+ * This is normally handled automatically when a request returns 401.
601
+ *
602
+ * @throws {AuthenticationError} If refresh token is invalid or expired
603
+ */
604
+ async refresh() {
605
+ const tokens = this.http.getTokens();
606
+ if (!tokens) {
607
+ throw new exports.AuthenticationError("No refresh token available");
608
+ }
609
+ const response = await this.http.post(
610
+ "/api/v1/auth/refresh",
611
+ { refreshToken: tokens.refreshToken },
612
+ { includeAuth: false }
613
+ );
614
+ this.http.setTokens(response.accessToken, response.refreshToken);
615
+ }
616
+ /**
617
+ * Change the current user's password.
618
+ *
619
+ * @param currentPassword - Current password for verification
620
+ * @param newPassword - New password to set
621
+ * @throws {AuthenticationError} If current password is incorrect
622
+ * @throws {ValidationError} If new password doesn't meet requirements
623
+ */
624
+ async changePassword(currentPassword, newPassword) {
625
+ await this.http.put("/api/v1/auth/me/password", {
626
+ currentPassword,
627
+ newPassword
628
+ });
629
+ }
630
+ /**
631
+ * Get a satellite token for a specific audience (target service).
632
+ *
633
+ * Satellite tokens are short-lived, RS256-signed JWTs that allow satellite
634
+ * services (like SyftAI-Space) to verify user identity without calling
635
+ * SyftHub for every request.
636
+ *
637
+ * @param audience - Target service identifier (username of the service owner)
638
+ * @returns Satellite token response with token and expiry
639
+ * @throws {AuthenticationError} If not authenticated
640
+ * @throws {ValidationError} If audience is invalid or inactive
641
+ *
642
+ * @example
643
+ * // Get a token for querying alice's SyftAI-Space endpoints
644
+ * const tokenResponse = await client.auth.getSatelliteToken('alice');
645
+ * console.log(`Token expires in ${tokenResponse.expiresIn} seconds`);
646
+ */
647
+ async getSatelliteToken(audience) {
648
+ return this.http.get("/api/v1/token", { aud: audience });
649
+ }
650
+ /**
651
+ * Get satellite tokens for multiple audiences in parallel.
652
+ *
653
+ * This is useful when making requests to endpoints owned by different users.
654
+ * Tokens are cached and reused where possible.
655
+ *
656
+ * @param audiences - Array of unique audience identifiers (usernames)
657
+ * @returns Map of audience to satellite token
658
+ * @throws {AuthenticationError} If not authenticated
659
+ *
660
+ * @example
661
+ * // Get tokens for multiple endpoint owners
662
+ * const tokens = await client.auth.getSatelliteTokens(['alice', 'bob']);
663
+ * console.log(`Got ${tokens.size} tokens`);
664
+ */
665
+ async getSatelliteTokens(audiences) {
666
+ const uniqueAudiences = [...new Set(audiences)];
667
+ const tokenMap = /* @__PURE__ */ new Map();
668
+ const results = await Promise.allSettled(
669
+ uniqueAudiences.map(async (aud) => {
670
+ const response = await this.getSatelliteToken(aud);
671
+ return { audience: aud, token: response.targetToken };
672
+ })
673
+ );
674
+ for (const result of results) {
675
+ if (result.status === "fulfilled") {
676
+ tokenMap.set(result.value.audience, result.value.token);
677
+ }
678
+ }
679
+ return tokenMap;
680
+ }
681
+ /**
682
+ * Get transaction tokens for multiple endpoint owners.
683
+ *
684
+ * Transaction tokens are short-lived JWTs that pre-authorize the endpoint owner
685
+ * (recipient) to charge the current user (sender) for usage. These tokens are
686
+ * created via the accounting service and passed to the aggregator.
687
+ *
688
+ * This is used by the chat flow to enable billing for endpoint usage.
689
+ *
690
+ * @param ownerUsernames - Array of endpoint owner usernames
691
+ * @returns TransactionTokensResponse with tokens map and any errors
692
+ * @throws {AuthenticationError} If not authenticated
693
+ *
694
+ * @example
695
+ * // Get transaction tokens for endpoint owners
696
+ * const response = await client.auth.getTransactionTokens(['alice', 'bob']);
697
+ * console.log(`Got ${Object.keys(response.tokens).length} tokens`);
698
+ * if (Object.keys(response.errors).length > 0) {
699
+ * console.log('Some tokens failed:', response.errors);
700
+ * }
701
+ */
702
+ async getTransactionTokens(ownerUsernames) {
703
+ const uniqueOwners = [...new Set(ownerUsernames)];
704
+ if (uniqueOwners.length === 0) {
705
+ return { tokens: {}, errors: {} };
706
+ }
707
+ try {
708
+ return await this.http.post(
709
+ "/api/v1/accounting/transaction-tokens",
710
+ { owner_usernames: uniqueOwners }
711
+ );
712
+ } catch (error) {
713
+ console.warn("Failed to get transaction tokens:", error);
714
+ return { tokens: {}, errors: {} };
715
+ }
716
+ }
717
+ };
718
+
719
+ // src/resources/users.ts
720
+ var UsersResource = class {
721
+ constructor(http) {
722
+ this.http = http;
723
+ }
724
+ /**
725
+ * Update the current user's profile.
726
+ *
727
+ * Only provided fields will be updated.
728
+ *
729
+ * @param input - Fields to update
730
+ * @returns The updated User
731
+ * @throws {AuthenticationError} If not authenticated
732
+ * @throws {ValidationError} If input validation fails
733
+ */
734
+ async update(input) {
735
+ return this.http.put("/api/v1/users/me", input);
736
+ }
737
+ /**
738
+ * Check if a username is available.
739
+ *
740
+ * @param username - Username to check
741
+ * @returns True if the username is available
742
+ */
743
+ async checkUsername(username) {
744
+ const response = await this.http.get(
745
+ `/api/v1/users/check-username/${encodeURIComponent(username)}`,
746
+ void 0,
747
+ { includeAuth: false }
748
+ );
749
+ return response.available;
750
+ }
751
+ /**
752
+ * Check if an email is available.
753
+ *
754
+ * @param email - Email to check
755
+ * @returns True if the email is available
756
+ */
757
+ async checkEmail(email) {
758
+ const response = await this.http.get(
759
+ `/api/v1/users/check-email/${encodeURIComponent(email)}`,
760
+ void 0,
761
+ { includeAuth: false }
762
+ );
763
+ return response.available;
764
+ }
765
+ /**
766
+ * Get the current user's accounting service credentials.
767
+ *
768
+ * Returns credentials stored in SyftHub for connecting to an external
769
+ * accounting service. The email is always the same as the user's SyftHub email.
770
+ *
771
+ * @returns Accounting credentials (url and password may be null if not configured)
772
+ * @throws {AuthenticationError} If not authenticated
773
+ *
774
+ * @example
775
+ * const credentials = await client.users.getAccountingCredentials();
776
+ * if (credentials.url && credentials.password) {
777
+ * // Use credentials to connect to accounting service
778
+ * }
779
+ */
780
+ async getAccountingCredentials() {
781
+ return this.http.get("/api/v1/users/me/accounting");
782
+ }
783
+ };
784
+
785
+ // src/pagination.ts
786
+ var PageIterator = class {
787
+ /**
788
+ * Create a new PageIterator.
789
+ *
790
+ * @param fetcher - Function that fetches a page of items given skip and limit
791
+ * @param pageSize - Number of items to fetch per page (default: 20)
792
+ */
793
+ constructor(fetcher, pageSize = 20) {
794
+ this.fetcher = fetcher;
795
+ this.pageSize = pageSize;
796
+ }
797
+ items = [];
798
+ index = 0;
799
+ skip = 0;
800
+ exhausted = false;
801
+ initialized = false;
802
+ /**
803
+ * Async iterator implementation for `for await...of` loops.
804
+ */
805
+ async *[Symbol.asyncIterator]() {
806
+ while (true) {
807
+ if (this.index >= this.items.length) {
808
+ if (this.exhausted) break;
809
+ await this.fetchNextPage();
810
+ if (this.items.length === 0) break;
811
+ }
812
+ const item = this.items[this.index];
813
+ if (item === void 0) break;
814
+ this.index++;
815
+ yield item;
816
+ }
817
+ }
818
+ /**
819
+ * Get just the first page of results.
820
+ *
821
+ * @returns Promise resolving to the first page of items
822
+ */
823
+ async firstPage() {
824
+ if (!this.initialized) {
825
+ await this.fetchNextPage();
826
+ }
827
+ return [...this.items];
828
+ }
829
+ /**
830
+ * Get all items across all pages.
831
+ *
832
+ * Warning: This loads all items into memory. For large datasets,
833
+ * consider iterating with `for await...of` instead.
834
+ *
835
+ * @returns Promise resolving to all items
836
+ */
837
+ async all() {
838
+ const results = [];
839
+ for await (const item of this) {
840
+ results.push(item);
841
+ }
842
+ return results;
843
+ }
844
+ /**
845
+ * Get the first N items.
846
+ *
847
+ * @param n - Maximum number of items to return
848
+ * @returns Promise resolving to up to N items
849
+ */
850
+ async take(n) {
851
+ const results = [];
852
+ for await (const item of this) {
853
+ results.push(item);
854
+ if (results.length >= n) break;
855
+ }
856
+ return results;
857
+ }
858
+ /**
859
+ * Fetch the next page of items from the API.
860
+ */
861
+ async fetchNextPage() {
862
+ const page = await this.fetcher(this.skip, this.pageSize);
863
+ this.items = page;
864
+ this.index = 0;
865
+ this.skip += this.pageSize;
866
+ this.exhausted = page.length < this.pageSize;
867
+ this.initialized = true;
868
+ }
869
+ };
870
+
871
+ // src/resources/my-endpoints.ts
872
+ var MyEndpointsResource = class {
873
+ constructor(http) {
874
+ this.http = http;
875
+ }
876
+ /**
877
+ * Parse an endpoint path into owner and slug.
878
+ *
879
+ * @param path - Path in "owner/slug" format
880
+ * @returns Tuple of [owner, slug]
881
+ * @throws {Error} If path format is invalid
882
+ */
883
+ parsePath(path) {
884
+ const parts = path.replace(/^\/|\/$/g, "").split("/");
885
+ if (parts.length !== 2 || !parts[0] || !parts[1]) {
886
+ throw new Error(`Invalid endpoint path: '${path}'. Expected format: 'owner/slug'`);
887
+ }
888
+ return [parts[0], parts[1]];
889
+ }
890
+ /**
891
+ * Resolve an endpoint path to its ID.
892
+ *
893
+ * @param path - Endpoint path in "owner/slug" format
894
+ * @returns The endpoint ID
895
+ */
896
+ async resolveEndpointId(path) {
897
+ const [owner, slug] = this.parsePath(path);
898
+ const response = await this.http.get(`/${owner}/${slug}`);
899
+ if (response.id === void 0) {
900
+ throw new Error(
901
+ `Could not resolve endpoint ID for '${path}'. Make sure you own this endpoint.`
902
+ );
903
+ }
904
+ return response.id;
905
+ }
906
+ /**
907
+ * List the current user's endpoints.
908
+ *
909
+ * @param options - Filtering and pagination options
910
+ * @returns PageIterator that lazily fetches endpoints
911
+ * @throws {AuthenticationError} If not authenticated
912
+ */
913
+ list(options) {
914
+ const pageSize = options?.pageSize ?? 20;
915
+ return new PageIterator(async (skip, limit) => {
916
+ const params = { skip, limit };
917
+ if (options?.visibility) {
918
+ params["visibility"] = options.visibility;
919
+ }
920
+ return this.http.get("/api/v1/endpoints", params);
921
+ }, pageSize);
922
+ }
923
+ /**
924
+ * Create a new endpoint.
925
+ *
926
+ * @param input - Endpoint creation details
927
+ * @param organizationId - Optional organization ID (for org-owned endpoints)
928
+ * @returns The created Endpoint
929
+ * @throws {AuthenticationError} If not authenticated
930
+ * @throws {ValidationError} If input validation fails
931
+ */
932
+ async create(input, organizationId) {
933
+ const body = organizationId !== void 0 ? { ...input, organizationId } : input;
934
+ return this.http.post("/api/v1/endpoints", body);
935
+ }
936
+ /**
937
+ * Get a specific endpoint by path.
938
+ *
939
+ * @param path - Endpoint path in "owner/slug" format (e.g., "alice/my-api")
940
+ * @returns The Endpoint
941
+ * @throws {AuthenticationError} If not authenticated
942
+ * @throws {NotFoundError} If endpoint not found
943
+ * @throws {AuthorizationError} If not authorized to view
944
+ */
945
+ async get(path) {
946
+ const [owner, slug] = this.parsePath(path);
947
+ return this.http.get(`/${owner}/${slug}`);
948
+ }
949
+ /**
950
+ * Update an endpoint.
951
+ *
952
+ * Only provided fields will be updated.
953
+ *
954
+ * @param path - Endpoint path in "owner/slug" format
955
+ * @param input - Fields to update
956
+ * @returns The updated Endpoint
957
+ * @throws {AuthenticationError} If not authenticated
958
+ * @throws {NotFoundError} If endpoint not found
959
+ * @throws {AuthorizationError} If not owner/admin
960
+ */
961
+ async update(path, input) {
962
+ const endpointId = await this.resolveEndpointId(path);
963
+ return this.http.patch(`/api/v1/endpoints/${endpointId}`, input);
964
+ }
965
+ /**
966
+ * Delete an endpoint.
967
+ *
968
+ * @param path - Endpoint path in "owner/slug" format
969
+ * @throws {AuthenticationError} If not authenticated
970
+ * @throws {NotFoundError} If endpoint not found
971
+ * @throws {AuthorizationError} If not owner/admin
972
+ */
973
+ async delete(path) {
974
+ const endpointId = await this.resolveEndpointId(path);
975
+ await this.http.delete(`/api/v1/endpoints/${endpointId}`);
976
+ }
977
+ };
978
+
979
+ // src/resources/hub.ts
980
+ var HubResource = class {
981
+ constructor(http) {
982
+ this.http = http;
983
+ }
984
+ /**
985
+ * Parse an endpoint path into owner and slug.
986
+ *
987
+ * @param path - Path in "owner/slug" format
988
+ * @returns Tuple of [owner, slug]
989
+ */
990
+ parsePath(path) {
991
+ const parts = path.replace(/^\/|\/$/g, "").split("/");
992
+ if (parts.length !== 2 || !parts[0] || !parts[1]) {
993
+ throw new Error(`Invalid endpoint path: '${path}'. Expected format: 'owner/slug'`);
994
+ }
995
+ return [parts[0], parts[1]];
996
+ }
997
+ /**
998
+ * Resolve an endpoint path to its ID.
999
+ *
1000
+ * This searches the user's own endpoints to find the ID.
1001
+ *
1002
+ * @param path - Endpoint path in "owner/slug" format
1003
+ * @returns The endpoint ID
1004
+ */
1005
+ async resolveEndpointId(path) {
1006
+ const [, slug] = this.parsePath(path);
1007
+ const endpoints = await this.http.get(
1008
+ "/api/v1/endpoints",
1009
+ { limit: 100 }
1010
+ );
1011
+ for (const ep of endpoints) {
1012
+ if (ep.slug === slug && ep.id !== void 0) {
1013
+ return ep.id;
1014
+ }
1015
+ }
1016
+ const { NotFoundError: NotFoundError2 } = await Promise.resolve().then(() => (init_errors(), errors_exports));
1017
+ throw new NotFoundError2(
1018
+ `Could not resolve endpoint ID for '${path}'. Endpoint not found or you don't have access to get its ID.`
1019
+ );
1020
+ }
1021
+ /**
1022
+ * Browse all public endpoints.
1023
+ *
1024
+ * @param options - Pagination options
1025
+ * @returns PageIterator that lazily fetches endpoints
1026
+ */
1027
+ browse(options) {
1028
+ const pageSize = options?.pageSize ?? 20;
1029
+ return new PageIterator(async (skip, limit) => {
1030
+ return this.http.get(
1031
+ "/api/v1/endpoints/public",
1032
+ { skip, limit },
1033
+ { includeAuth: false }
1034
+ );
1035
+ }, pageSize);
1036
+ }
1037
+ /**
1038
+ * Get trending endpoints sorted by stars.
1039
+ *
1040
+ * @param options - Filter and pagination options
1041
+ * @returns PageIterator that lazily fetches endpoints
1042
+ */
1043
+ trending(options) {
1044
+ const pageSize = options?.pageSize ?? 20;
1045
+ return new PageIterator(async (skip, limit) => {
1046
+ const params = { skip, limit };
1047
+ if (options?.minStars !== void 0) {
1048
+ params["minStars"] = options.minStars;
1049
+ }
1050
+ return this.http.get(
1051
+ "/api/v1/endpoints/trending",
1052
+ params,
1053
+ { includeAuth: false }
1054
+ );
1055
+ }, pageSize);
1056
+ }
1057
+ /**
1058
+ * Get an endpoint by its path.
1059
+ *
1060
+ * This method searches the public endpoints API to find the endpoint,
1061
+ * which works reliably across all deployment configurations.
1062
+ *
1063
+ * @param path - Endpoint path in "owner/slug" format (e.g., "alice/cool-api")
1064
+ * @returns The EndpointPublic
1065
+ * @throws {NotFoundError} If endpoint not found
1066
+ */
1067
+ async get(path) {
1068
+ const [owner, slug] = this.parsePath(path);
1069
+ for await (const endpoint of this.browse({ pageSize: 100 })) {
1070
+ if (endpoint.ownerUsername === owner && endpoint.slug === slug) {
1071
+ return endpoint;
1072
+ }
1073
+ }
1074
+ const { NotFoundError: NotFoundError2 } = await Promise.resolve().then(() => (init_errors(), errors_exports));
1075
+ throw new NotFoundError2(
1076
+ `Endpoint not found: '${path}'. No public endpoint found with owner '${owner}' and slug '${slug}'.`
1077
+ );
1078
+ }
1079
+ /**
1080
+ * Star an endpoint.
1081
+ *
1082
+ * @param path - Endpoint path in "owner/slug" format
1083
+ * @throws {AuthenticationError} If not authenticated
1084
+ * @throws {NotFoundError} If endpoint not found
1085
+ */
1086
+ async star(path) {
1087
+ const endpointId = await this.resolveEndpointId(path);
1088
+ await this.http.patch(`/api/v1/endpoints/${endpointId}/star`);
1089
+ }
1090
+ /**
1091
+ * Unstar an endpoint.
1092
+ *
1093
+ * @param path - Endpoint path in "owner/slug" format
1094
+ * @throws {AuthenticationError} If not authenticated
1095
+ * @throws {NotFoundError} If endpoint not found
1096
+ */
1097
+ async unstar(path) {
1098
+ const endpointId = await this.resolveEndpointId(path);
1099
+ await this.http.patch(`/api/v1/endpoints/${endpointId}/unstar`);
1100
+ }
1101
+ /**
1102
+ * Check if you have starred an endpoint.
1103
+ *
1104
+ * @param path - Endpoint path in "owner/slug" format
1105
+ * @returns True if starred, False otherwise
1106
+ * @throws {AuthenticationError} If not authenticated
1107
+ * @throws {NotFoundError} If endpoint not found
1108
+ */
1109
+ async isStarred(path) {
1110
+ const endpointId = await this.resolveEndpointId(path);
1111
+ const response = await this.http.get(
1112
+ `/api/v1/endpoints/${endpointId}/starred`
1113
+ );
1114
+ return response.starred ?? false;
1115
+ }
1116
+ };
1117
+
1118
+ // src/models/common.ts
1119
+ var Visibility = {
1120
+ /** Visible to everyone, no authentication required */
1121
+ PUBLIC: "public",
1122
+ /** Only visible to the owner and collaborators */
1123
+ PRIVATE: "private",
1124
+ /** Visible to authenticated users within the organization */
1125
+ INTERNAL: "internal"
1126
+ };
1127
+ var EndpointType = {
1128
+ /** Machine learning model endpoint */
1129
+ MODEL: "model",
1130
+ /** Data source endpoint */
1131
+ DATA_SOURCE: "data_source"
1132
+ };
1133
+ var UserRole = {
1134
+ /** Administrator with full access */
1135
+ ADMIN: "admin",
1136
+ /** Regular user */
1137
+ USER: "user",
1138
+ /** Guest user with limited access */
1139
+ GUEST: "guest"
1140
+ };
1141
+ var OrganizationRole = {
1142
+ /** Organization owner with full control */
1143
+ OWNER: "owner",
1144
+ /** Administrator with management privileges */
1145
+ ADMIN: "admin",
1146
+ /** Regular member */
1147
+ MEMBER: "member"
1148
+ };
1149
+
1150
+ // src/models/endpoint.ts
1151
+ function getEndpointOwnerType(endpoint) {
1152
+ return endpoint.organizationId !== null ? "organization" : "user";
1153
+ }
1154
+ function getEndpointPublicPath(endpoint) {
1155
+ return `${endpoint.ownerUsername}/${endpoint.slug}`;
1156
+ }
1157
+
1158
+ // src/models/accounting.ts
1159
+ var TransactionStatus = {
1160
+ /** Transaction created, awaiting confirmation */
1161
+ PENDING: "pending",
1162
+ /** Transaction confirmed, funds transferred */
1163
+ COMPLETED: "completed",
1164
+ /** Transaction cancelled, no funds transferred */
1165
+ CANCELLED: "cancelled"
1166
+ };
1167
+ var CreatorType = {
1168
+ /** System-initiated transaction */
1169
+ SYSTEM: "system",
1170
+ /** Sender-initiated transaction */
1171
+ SENDER: "sender",
1172
+ /** Recipient-initiated transaction (delegated) */
1173
+ RECIPIENT: "recipient"
1174
+ };
1175
+ function parseTransaction(response) {
1176
+ return {
1177
+ ...response,
1178
+ createdAt: new Date(response.createdAt),
1179
+ resolvedAt: response.resolvedAt ? new Date(response.resolvedAt) : null
1180
+ };
1181
+ }
1182
+ function isTransactionPending(tx) {
1183
+ return tx.status === TransactionStatus.PENDING;
1184
+ }
1185
+ function isTransactionCompleted(tx) {
1186
+ return tx.status === TransactionStatus.COMPLETED;
1187
+ }
1188
+ function isTransactionCancelled(tx) {
1189
+ return tx.status === TransactionStatus.CANCELLED;
1190
+ }
1191
+
1192
+ // src/resources/accounting.ts
1193
+ init_errors();
1194
+ async function handleResponseError(response) {
1195
+ if (response.ok) return;
1196
+ let detail;
1197
+ try {
1198
+ const body = await response.json();
1199
+ detail = body.detail ?? body.message ?? JSON.stringify(body);
1200
+ } catch {
1201
+ detail = await response.text() || `HTTP ${response.status}`;
1202
+ }
1203
+ switch (response.status) {
1204
+ case 401:
1205
+ throw new exports.AuthenticationError(`Authentication failed: ${detail}`);
1206
+ case 403:
1207
+ throw new exports.AuthorizationError(`Permission denied: ${detail}`);
1208
+ case 404:
1209
+ throw new exports.NotFoundError(`Not found: ${detail}`);
1210
+ case 422:
1211
+ throw new exports.ValidationError(`Validation error: ${detail}`);
1212
+ default:
1213
+ throw new exports.APIError(`Accounting API error: ${detail}`, response.status);
1214
+ }
1215
+ }
1216
+ function createBasicAuth(email, password) {
1217
+ const credentials = `${email}:${password}`;
1218
+ const encoded = typeof btoa !== "undefined" ? btoa(credentials) : Buffer.from(credentials).toString("base64");
1219
+ return `Basic ${encoded}`;
1220
+ }
1221
+ var AccountingResource = class {
1222
+ baseUrl;
1223
+ email;
1224
+ password;
1225
+ timeout;
1226
+ authHeader;
1227
+ constructor(options) {
1228
+ this.baseUrl = options.url.replace(/\/$/, "");
1229
+ this.email = options.email;
1230
+ this.password = options.password;
1231
+ this.timeout = options.timeout ?? 3e4;
1232
+ this.authHeader = createBasicAuth(this.email, this.password);
1233
+ }
1234
+ // ===========================================================================
1235
+ // Private HTTP Methods
1236
+ // ===========================================================================
1237
+ /**
1238
+ * Make an authenticated request to the accounting service.
1239
+ */
1240
+ async request(method, path, options) {
1241
+ const url = new URL(path, this.baseUrl);
1242
+ if (options?.params) {
1243
+ for (const [key, value] of Object.entries(options.params)) {
1244
+ url.searchParams.set(key, String(value));
1245
+ }
1246
+ }
1247
+ const controller = new AbortController();
1248
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
1249
+ try {
1250
+ const response = await fetch(url.toString(), {
1251
+ method,
1252
+ headers: {
1253
+ "Authorization": this.authHeader,
1254
+ "Content-Type": "application/json",
1255
+ "Accept": "application/json"
1256
+ },
1257
+ body: options?.body ? JSON.stringify(options.body) : void 0,
1258
+ signal: controller.signal
1259
+ });
1260
+ await handleResponseError(response);
1261
+ if (response.status === 204) {
1262
+ return {};
1263
+ }
1264
+ return await response.json();
1265
+ } catch (error) {
1266
+ if (error instanceof exports.SyftHubError) {
1267
+ throw error;
1268
+ }
1269
+ if (error instanceof Error && error.name === "AbortError") {
1270
+ throw new exports.APIError("Request timeout", 408);
1271
+ }
1272
+ throw new exports.APIError(`Accounting request failed: ${error instanceof Error ? error.message : "Unknown error"}`, 0);
1273
+ } finally {
1274
+ clearTimeout(timeoutId);
1275
+ }
1276
+ }
1277
+ /**
1278
+ * Make a request using Bearer token auth (for delegated transactions).
1279
+ */
1280
+ async requestWithToken(method, path, token, options) {
1281
+ const url = new URL(path, this.baseUrl);
1282
+ const controller = new AbortController();
1283
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
1284
+ try {
1285
+ const response = await fetch(url.toString(), {
1286
+ method,
1287
+ headers: {
1288
+ "Authorization": `Bearer ${token}`,
1289
+ "Content-Type": "application/json",
1290
+ "Accept": "application/json"
1291
+ },
1292
+ body: options?.body ? JSON.stringify(options.body) : void 0,
1293
+ signal: controller.signal
1294
+ });
1295
+ await handleResponseError(response);
1296
+ if (response.status === 204) {
1297
+ return {};
1298
+ }
1299
+ return await response.json();
1300
+ } catch (error) {
1301
+ if (error instanceof exports.SyftHubError) {
1302
+ throw error;
1303
+ }
1304
+ if (error instanceof Error && error.name === "AbortError") {
1305
+ throw new exports.APIError("Request timeout", 408);
1306
+ }
1307
+ throw new exports.APIError(`Accounting request failed: ${error instanceof Error ? error.message : "Unknown error"}`, 0);
1308
+ } finally {
1309
+ clearTimeout(timeoutId);
1310
+ }
1311
+ }
1312
+ // ===========================================================================
1313
+ // User Operations
1314
+ // ===========================================================================
1315
+ /**
1316
+ * Get the current user's account information including balance.
1317
+ *
1318
+ * @returns AccountingUser with id, email, balance, and organization
1319
+ * @throws {AuthenticationError} If authentication fails
1320
+ * @throws {APIError} On other errors
1321
+ *
1322
+ * @example
1323
+ * ```typescript
1324
+ * const user = await accounting.getUser();
1325
+ * console.log(`Balance: ${user.balance}`);
1326
+ * console.log(`Organization: ${user.organization}`);
1327
+ * ```
1328
+ */
1329
+ async getUser() {
1330
+ return this.request("GET", "/user");
1331
+ }
1332
+ /**
1333
+ * Update the user's password.
1334
+ *
1335
+ * @param currentPassword - Current password for verification
1336
+ * @param newPassword - New password to set
1337
+ * @throws {AuthenticationError} If current password is wrong
1338
+ * @throws {ValidationError} If new password doesn't meet requirements
1339
+ *
1340
+ * @example
1341
+ * ```typescript
1342
+ * await accounting.updatePassword('old_secret', 'new_secret');
1343
+ * ```
1344
+ */
1345
+ async updatePassword(currentPassword, newPassword) {
1346
+ await this.request("PUT", "/user/password", {
1347
+ body: {
1348
+ oldPassword: currentPassword,
1349
+ newPassword
1350
+ }
1351
+ });
1352
+ }
1353
+ /**
1354
+ * Update the user's organization.
1355
+ *
1356
+ * @param organization - New organization name
1357
+ * @throws {AuthenticationError} If authentication fails
1358
+ *
1359
+ * @example
1360
+ * ```typescript
1361
+ * await accounting.updateOrganization('OpenMined');
1362
+ * ```
1363
+ */
1364
+ async updateOrganization(organization) {
1365
+ await this.request("PUT", "/user/organization", {
1366
+ body: { organization }
1367
+ });
1368
+ }
1369
+ // ===========================================================================
1370
+ // Transaction Listing
1371
+ // ===========================================================================
1372
+ /**
1373
+ * List account transactions with pagination.
1374
+ *
1375
+ * Returns a lazy iterator that fetches pages on demand.
1376
+ *
1377
+ * @param options - Pagination options
1378
+ * @returns PageIterator that yields Transaction objects
1379
+ *
1380
+ * @example
1381
+ * ```typescript
1382
+ * // Iterate through all transactions
1383
+ * for await (const tx of accounting.getTransactions()) {
1384
+ * console.log(`${tx.createdAt}: ${tx.amount} from ${tx.senderEmail}`);
1385
+ * }
1386
+ *
1387
+ * // Get first page only
1388
+ * const firstPage = await accounting.getTransactions().firstPage();
1389
+ *
1390
+ * // Get all transactions
1391
+ * const allTxs = await accounting.getTransactions().all();
1392
+ * ```
1393
+ */
1394
+ getTransactions(options) {
1395
+ const pageSize = options?.pageSize ?? 20;
1396
+ return new PageIterator(
1397
+ async (skip, limit) => {
1398
+ const response = await this.request("GET", "/transactions", {
1399
+ params: { skip, limit }
1400
+ });
1401
+ return response.map(parseTransaction);
1402
+ },
1403
+ pageSize
1404
+ );
1405
+ }
1406
+ /**
1407
+ * Get a specific transaction by ID.
1408
+ *
1409
+ * @param transactionId - The transaction ID
1410
+ * @returns Transaction object
1411
+ * @throws {NotFoundError} If transaction not found
1412
+ *
1413
+ * @example
1414
+ * ```typescript
1415
+ * const tx = await accounting.getTransaction('tx_123');
1416
+ * console.log(`Status: ${tx.status}`);
1417
+ * ```
1418
+ */
1419
+ async getTransaction(transactionId) {
1420
+ const response = await this.request(
1421
+ "GET",
1422
+ `/transactions/${transactionId}`
1423
+ );
1424
+ return parseTransaction(response);
1425
+ }
1426
+ // ===========================================================================
1427
+ // Direct Transaction Operations
1428
+ // ===========================================================================
1429
+ /**
1430
+ * Create a new transaction (direct transfer).
1431
+ *
1432
+ * Creates a PENDING transaction that must be confirmed or cancelled.
1433
+ * The transaction is created by the sender (current user).
1434
+ *
1435
+ * @param input - Transaction details
1436
+ * @returns Transaction in PENDING status
1437
+ * @throws {ValidationError} If amount <= 0 or insufficient balance
1438
+ *
1439
+ * @example
1440
+ * ```typescript
1441
+ * const tx = await accounting.createTransaction({
1442
+ * recipientEmail: 'bob@example.com',
1443
+ * amount: 10.0,
1444
+ * appName: 'syftai-space',
1445
+ * appEpPath: 'alice/my-model'
1446
+ * });
1447
+ * console.log(`Created transaction ${tx.id}: ${tx.status}`);
1448
+ *
1449
+ * // Later, confirm or cancel
1450
+ * await accounting.confirmTransaction(tx.id);
1451
+ * ```
1452
+ */
1453
+ async createTransaction(input) {
1454
+ if (input.amount <= 0) {
1455
+ throw new exports.ValidationError("Amount must be greater than 0");
1456
+ }
1457
+ const response = await this.request("POST", "/transactions", {
1458
+ body: {
1459
+ recipientEmail: input.recipientEmail,
1460
+ amount: input.amount,
1461
+ ...input.appName && { appName: input.appName },
1462
+ ...input.appEpPath && { appEpPath: input.appEpPath }
1463
+ }
1464
+ });
1465
+ return parseTransaction(response);
1466
+ }
1467
+ /**
1468
+ * Confirm a pending transaction.
1469
+ *
1470
+ * Confirms the transaction, transferring funds from sender to recipient.
1471
+ * Can be called by either the sender or recipient.
1472
+ *
1473
+ * @param transactionId - The transaction ID to confirm
1474
+ * @returns Transaction in COMPLETED status
1475
+ * @throws {NotFoundError} If transaction not found
1476
+ * @throws {ValidationError} If transaction is not in PENDING status
1477
+ *
1478
+ * @example
1479
+ * ```typescript
1480
+ * const tx = await accounting.confirmTransaction('tx_123');
1481
+ * console.log(`Confirmed: ${tx.status}`); // "completed"
1482
+ * ```
1483
+ */
1484
+ async confirmTransaction(transactionId) {
1485
+ const response = await this.request(
1486
+ "POST",
1487
+ `/transactions/${transactionId}/confirm`
1488
+ );
1489
+ return parseTransaction(response);
1490
+ }
1491
+ /**
1492
+ * Cancel a pending transaction.
1493
+ *
1494
+ * Cancels the transaction without transferring funds.
1495
+ * Can be called by either the sender or recipient.
1496
+ *
1497
+ * @param transactionId - The transaction ID to cancel
1498
+ * @returns Transaction in CANCELLED status
1499
+ * @throws {NotFoundError} If transaction not found
1500
+ * @throws {ValidationError} If transaction is not in PENDING status
1501
+ *
1502
+ * @example
1503
+ * ```typescript
1504
+ * const tx = await accounting.cancelTransaction('tx_123');
1505
+ * console.log(`Cancelled: ${tx.status}`); // "cancelled"
1506
+ * ```
1507
+ */
1508
+ async cancelTransaction(transactionId) {
1509
+ const response = await this.request(
1510
+ "POST",
1511
+ `/transactions/${transactionId}/cancel`
1512
+ );
1513
+ return parseTransaction(response);
1514
+ }
1515
+ // ===========================================================================
1516
+ // Delegated Transaction Operations
1517
+ // ===========================================================================
1518
+ /**
1519
+ * Create a transaction token for delegated transfers.
1520
+ *
1521
+ * Creates a JWT token that authorizes the recipient to create a
1522
+ * transaction on behalf of the sender (current user). The token
1523
+ * is short-lived (typically ~5 minutes).
1524
+ *
1525
+ * Use this when you want to pre-authorize a payment that will be
1526
+ * initiated by the recipient (e.g., a service charging for usage).
1527
+ *
1528
+ * @param recipientEmail - Email of the authorized recipient
1529
+ * @returns JWT token string to share with recipient
1530
+ *
1531
+ * @example
1532
+ * ```typescript
1533
+ * // Sender creates token
1534
+ * const token = await accounting.createTransactionToken('service@example.com');
1535
+ *
1536
+ * // Share token with recipient out-of-band
1537
+ * // Recipient uses token to create delegated transaction
1538
+ * ```
1539
+ */
1540
+ async createTransactionToken(recipientEmail) {
1541
+ const response = await this.request("POST", "/token/create", {
1542
+ body: { recipientEmail }
1543
+ });
1544
+ return response.token;
1545
+ }
1546
+ /**
1547
+ * Create a delegated transaction using a pre-authorized token.
1548
+ *
1549
+ * Creates a transaction on behalf of the sender using their token.
1550
+ * This is typically used by services to charge users for usage.
1551
+ *
1552
+ * The token authenticates the request instead of Basic auth.
1553
+ *
1554
+ * @param senderEmail - Email of the sender who created the token
1555
+ * @param amount - Amount to transfer (must be > 0)
1556
+ * @param token - JWT token from sender's createTransactionToken()
1557
+ * @returns Transaction in PENDING status (createdBy=RECIPIENT)
1558
+ * @throws {AuthenticationError} If token is invalid or expired
1559
+ * @throws {ValidationError} If amount <= 0
1560
+ *
1561
+ * @example
1562
+ * ```typescript
1563
+ * // Recipient creates transaction using sender's token
1564
+ * const tx = await accounting.createDelegatedTransaction(
1565
+ * 'alice@example.com',
1566
+ * 5.0,
1567
+ * aliceToken
1568
+ * );
1569
+ *
1570
+ * // Recipient confirms the transaction
1571
+ * await accounting.confirmTransaction(tx.id);
1572
+ * ```
1573
+ */
1574
+ async createDelegatedTransaction(senderEmail, amount, token) {
1575
+ if (amount <= 0) {
1576
+ throw new exports.ValidationError("Amount must be greater than 0");
1577
+ }
1578
+ const response = await this.requestWithToken(
1579
+ "POST",
1580
+ "/transactions",
1581
+ token,
1582
+ {
1583
+ body: {
1584
+ senderEmail,
1585
+ amount
1586
+ }
1587
+ }
1588
+ );
1589
+ return parseTransaction(response);
1590
+ }
1591
+ };
1592
+ function createAccountingResource(options) {
1593
+ return new AccountingResource(options);
1594
+ }
1595
+
1596
+ // src/resources/chat.ts
1597
+ init_errors();
1598
+ var AggregatorError = class extends exports.SyftHubError {
1599
+ constructor(message, status, detail) {
1600
+ super(message);
1601
+ this.status = status;
1602
+ this.detail = detail;
1603
+ this.name = "AggregatorError";
1604
+ }
1605
+ };
1606
+ var EndpointResolutionError = class extends exports.SyftHubError {
1607
+ constructor(message, endpointPath) {
1608
+ super(message);
1609
+ this.endpointPath = endpointPath;
1610
+ this.name = "EndpointResolutionError";
1611
+ }
1612
+ };
1613
+ var ChatResource = class {
1614
+ constructor(hub, auth, aggregatorUrl) {
1615
+ this.hub = hub;
1616
+ this.auth = auth;
1617
+ this.aggregatorUrl = aggregatorUrl;
1618
+ }
1619
+ /**
1620
+ * Convert any endpoint format to EndpointRef with URL and owner info.
1621
+ * The ownerUsername is critical for satellite token authentication.
1622
+ */
1623
+ async resolveEndpointRef(endpoint, expectedType) {
1624
+ if (this.isEndpointRef(endpoint)) {
1625
+ return endpoint;
1626
+ }
1627
+ if (this.isEndpointPublic(endpoint)) {
1628
+ if (expectedType && endpoint.type !== expectedType) {
1629
+ throw new Error(
1630
+ `Expected endpoint type '${expectedType}', got '${endpoint.type}' for '${endpoint.slug}'`
1631
+ );
1632
+ }
1633
+ for (const conn of endpoint.connect) {
1634
+ if (conn.enabled && conn.config["url"]) {
1635
+ return {
1636
+ url: String(conn.config["url"]),
1637
+ slug: endpoint.slug,
1638
+ name: endpoint.name,
1639
+ tenantName: conn.config["tenant_name"],
1640
+ ownerUsername: endpoint.ownerUsername
1641
+ // Capture owner for satellite token
1642
+ };
1643
+ }
1644
+ }
1645
+ throw new EndpointResolutionError(
1646
+ `Endpoint '${endpoint.slug}' has no connection with URL configured`,
1647
+ `${endpoint.ownerUsername}/${endpoint.slug}`
1648
+ );
1649
+ }
1650
+ if (typeof endpoint === "string") {
1651
+ let ep;
1652
+ try {
1653
+ ep = await this.hub.get(endpoint);
1654
+ } catch (error) {
1655
+ throw new EndpointResolutionError(
1656
+ `Failed to fetch endpoint '${endpoint}': ${error instanceof Error ? error.message : String(error)}`,
1657
+ endpoint
1658
+ );
1659
+ }
1660
+ return this.resolveEndpointRef(ep, expectedType);
1661
+ }
1662
+ throw new TypeError(`Cannot resolve endpoint from type: ${typeof endpoint}`);
1663
+ }
1664
+ /**
1665
+ * Collect unique owner usernames from all endpoints.
1666
+ * Used to determine which satellite tokens need to be fetched.
1667
+ */
1668
+ collectUniqueOwners(modelRef, dataSourceRefs) {
1669
+ const owners = /* @__PURE__ */ new Set();
1670
+ if (modelRef.ownerUsername) {
1671
+ owners.add(modelRef.ownerUsername);
1672
+ }
1673
+ for (const ds of dataSourceRefs) {
1674
+ if (ds.ownerUsername) {
1675
+ owners.add(ds.ownerUsername);
1676
+ }
1677
+ }
1678
+ return [...owners];
1679
+ }
1680
+ /**
1681
+ * Get satellite tokens for all unique endpoint owners.
1682
+ * Returns a map of owner username to satellite token.
1683
+ */
1684
+ async getSatelliteTokensForOwners(owners) {
1685
+ if (owners.length === 0) {
1686
+ return {};
1687
+ }
1688
+ const tokenMap = await this.auth.getSatelliteTokens(owners);
1689
+ const result = {};
1690
+ for (const [owner, token] of tokenMap) {
1691
+ result[owner] = token;
1692
+ }
1693
+ return result;
1694
+ }
1695
+ /**
1696
+ * Get transaction tokens for all unique endpoint owners.
1697
+ * Returns a map of owner username to transaction token.
1698
+ *
1699
+ * Transaction tokens are used for billing - they authorize the endpoint
1700
+ * owner to charge the current user for usage.
1701
+ */
1702
+ async getTransactionTokensForOwners(owners) {
1703
+ if (owners.length === 0) {
1704
+ return {};
1705
+ }
1706
+ const response = await this.auth.getTransactionTokens(owners);
1707
+ return response.tokens;
1708
+ }
1709
+ /**
1710
+ * Type guard for EndpointRef.
1711
+ */
1712
+ isEndpointRef(value) {
1713
+ return typeof value === "object" && value !== null && "url" in value && "slug" in value && typeof value.url === "string" && typeof value.slug === "string";
1714
+ }
1715
+ /**
1716
+ * Type guard for EndpointPublic.
1717
+ */
1718
+ isEndpointPublic(value) {
1719
+ return typeof value === "object" && value !== null && "connect" in value && "ownerUsername" in value && Array.isArray(value.connect);
1720
+ }
1721
+ /**
1722
+ * Build the request body for the aggregator.
1723
+ * Includes endpoint_tokens mapping for satellite token authentication.
1724
+ * Includes transaction_tokens mapping for billing authorization.
1725
+ * User identity is derived from satellite tokens, not passed in request body.
1726
+ */
1727
+ buildRequestBody(prompt, modelRef, dataSourceRefs, endpointTokens, transactionTokens, options) {
1728
+ return {
1729
+ prompt,
1730
+ model: {
1731
+ url: modelRef.url,
1732
+ slug: modelRef.slug,
1733
+ name: modelRef.name ?? "",
1734
+ tenant_name: modelRef.tenantName ?? null,
1735
+ owner_username: modelRef.ownerUsername ?? null
1736
+ },
1737
+ data_sources: dataSourceRefs.map((ds) => ({
1738
+ url: ds.url,
1739
+ slug: ds.slug,
1740
+ name: ds.name ?? "",
1741
+ tenant_name: ds.tenantName ?? null,
1742
+ owner_username: ds.ownerUsername ?? null
1743
+ })),
1744
+ endpoint_tokens: endpointTokens,
1745
+ transaction_tokens: transactionTokens,
1746
+ top_k: options.topK ?? 5,
1747
+ max_tokens: options.maxTokens ?? 1024,
1748
+ temperature: options.temperature ?? 0.7,
1749
+ similarity_threshold: options.similarityThreshold ?? 0.5,
1750
+ stream: options.stream ?? false
1751
+ };
1752
+ }
1753
+ /**
1754
+ * Parse a SourceInfo from raw data.
1755
+ */
1756
+ parseSourceInfo(data) {
1757
+ return {
1758
+ path: String(data["path"] ?? ""),
1759
+ documentsRetrieved: Number(data["documents_retrieved"] ?? 0),
1760
+ status: data["status"] ?? "success",
1761
+ errorMessage: data["error_message"]
1762
+ };
1763
+ }
1764
+ /**
1765
+ * Parse ChatMetadata from raw data.
1766
+ */
1767
+ parseMetadata(data) {
1768
+ return {
1769
+ retrievalTimeMs: Number(data["retrieval_time_ms"] ?? 0),
1770
+ generationTimeMs: Number(data["generation_time_ms"] ?? 0),
1771
+ totalTimeMs: Number(data["total_time_ms"] ?? 0)
1772
+ };
1773
+ }
1774
+ /**
1775
+ * Parse TokenUsage from raw data.
1776
+ */
1777
+ parseUsage(data) {
1778
+ return {
1779
+ promptTokens: Number(data["prompt_tokens"] ?? 0),
1780
+ completionTokens: Number(data["completion_tokens"] ?? 0),
1781
+ totalTokens: Number(data["total_tokens"] ?? 0)
1782
+ };
1783
+ }
1784
+ /**
1785
+ * Parse document sources from raw data.
1786
+ * The new format is a dict mapping document title to {slug, content}.
1787
+ */
1788
+ parseDocumentSources(data) {
1789
+ const sources = {};
1790
+ if (!data || typeof data !== "object") {
1791
+ return sources;
1792
+ }
1793
+ for (const [title, value] of Object.entries(data)) {
1794
+ if (value && typeof value === "object") {
1795
+ const source = value;
1796
+ sources[title] = {
1797
+ slug: String(source["slug"] ?? ""),
1798
+ content: String(source["content"] ?? "")
1799
+ };
1800
+ }
1801
+ }
1802
+ return sources;
1803
+ }
1804
+ /**
1805
+ * Parse retrieval info (SourceInfo array) from raw data.
1806
+ */
1807
+ parseRetrievalInfo(data) {
1808
+ const retrievalInfo = [];
1809
+ if (!Array.isArray(data)) {
1810
+ return retrievalInfo;
1811
+ }
1812
+ for (const item of data) {
1813
+ retrievalInfo.push(this.parseSourceInfo(item));
1814
+ }
1815
+ return retrievalInfo;
1816
+ }
1817
+ /**
1818
+ * Send a chat request and get the complete response.
1819
+ *
1820
+ * This method automatically:
1821
+ * 1. Resolves endpoints and extracts owner information
1822
+ * 2. Exchanges Hub tokens for satellite tokens (one per unique owner)
1823
+ * 3. Fetches transaction tokens for billing authorization
1824
+ * 4. Sends tokens to the aggregator for forwarding to SyftAI-Space
1825
+ *
1826
+ * @param options - Chat completion options
1827
+ * @returns ChatResponse with response text, sources, and metadata
1828
+ * @throws {EndpointResolutionError} If endpoint cannot be resolved
1829
+ * @throws {AggregatorError} If aggregator service fails
1830
+ */
1831
+ async complete(options) {
1832
+ const modelRef = await this.resolveEndpointRef(options.model, "model");
1833
+ const dsRefs = [];
1834
+ for (const ds of options.dataSources ?? []) {
1835
+ dsRefs.push(await this.resolveEndpointRef(ds, "data_source"));
1836
+ }
1837
+ const uniqueOwners = this.collectUniqueOwners(modelRef, dsRefs);
1838
+ const endpointTokens = await this.getSatelliteTokensForOwners(uniqueOwners);
1839
+ const transactionTokens = await this.getTransactionTokensForOwners(uniqueOwners);
1840
+ const requestBody = this.buildRequestBody(
1841
+ options.prompt,
1842
+ modelRef,
1843
+ dsRefs,
1844
+ endpointTokens,
1845
+ transactionTokens,
1846
+ {
1847
+ topK: options.topK,
1848
+ maxTokens: options.maxTokens,
1849
+ temperature: options.temperature,
1850
+ similarityThreshold: options.similarityThreshold,
1851
+ stream: false
1852
+ }
1853
+ );
1854
+ const url = `${this.aggregatorUrl}/chat`;
1855
+ const response = await fetch(url, {
1856
+ method: "POST",
1857
+ headers: {
1858
+ "Content-Type": "application/json"
1859
+ },
1860
+ body: JSON.stringify(requestBody)
1861
+ });
1862
+ if (!response.ok) {
1863
+ let message = `HTTP ${response.status}`;
1864
+ try {
1865
+ const data2 = await response.json();
1866
+ message = String(data2["message"] ?? data2["error"] ?? message);
1867
+ } catch {
1868
+ }
1869
+ throw new AggregatorError(`Aggregator error: ${message}`, response.status);
1870
+ }
1871
+ const data = await response.json();
1872
+ const sourcesData = data["sources"];
1873
+ const sources = this.parseDocumentSources(sourcesData);
1874
+ const retrievalInfoData = data["retrieval_info"];
1875
+ const retrievalInfo = this.parseRetrievalInfo(retrievalInfoData);
1876
+ const metadataData = data["metadata"];
1877
+ const metadata = this.parseMetadata(metadataData ?? {});
1878
+ const usageData = data["usage"];
1879
+ const usage = usageData ? this.parseUsage(usageData) : void 0;
1880
+ return {
1881
+ response: String(data["response"] ?? ""),
1882
+ sources,
1883
+ retrievalInfo,
1884
+ metadata,
1885
+ usage
1886
+ };
1887
+ }
1888
+ /**
1889
+ * Send a chat request and stream response events.
1890
+ *
1891
+ * This method automatically:
1892
+ * 1. Resolves endpoints and extracts owner information
1893
+ * 2. Exchanges Hub tokens for satellite tokens (one per unique owner)
1894
+ * 3. Fetches transaction tokens for billing authorization
1895
+ * 4. Sends tokens to the aggregator for forwarding to SyftAI-Space
1896
+ *
1897
+ * @param options - Chat completion options
1898
+ * @yields ChatStreamEvent objects as they arrive
1899
+ */
1900
+ async *stream(options) {
1901
+ const modelRef = await this.resolveEndpointRef(options.model, "model");
1902
+ const dsRefs = [];
1903
+ for (const ds of options.dataSources ?? []) {
1904
+ dsRefs.push(await this.resolveEndpointRef(ds, "data_source"));
1905
+ }
1906
+ const uniqueOwners = this.collectUniqueOwners(modelRef, dsRefs);
1907
+ const endpointTokens = await this.getSatelliteTokensForOwners(uniqueOwners);
1908
+ const transactionTokens = await this.getTransactionTokensForOwners(uniqueOwners);
1909
+ const requestBody = this.buildRequestBody(
1910
+ options.prompt,
1911
+ modelRef,
1912
+ dsRefs,
1913
+ endpointTokens,
1914
+ transactionTokens,
1915
+ {
1916
+ topK: options.topK,
1917
+ maxTokens: options.maxTokens,
1918
+ temperature: options.temperature,
1919
+ similarityThreshold: options.similarityThreshold,
1920
+ stream: true
1921
+ }
1922
+ );
1923
+ const url = `${this.aggregatorUrl}/chat/stream`;
1924
+ const response = await fetch(url, {
1925
+ method: "POST",
1926
+ headers: {
1927
+ "Content-Type": "application/json",
1928
+ Accept: "text/event-stream"
1929
+ },
1930
+ body: JSON.stringify(requestBody),
1931
+ signal: options.signal
1932
+ });
1933
+ if (!response.ok) {
1934
+ let message = `HTTP ${response.status}`;
1935
+ try {
1936
+ const data = await response.json();
1937
+ message = String(data["message"] ?? data["error"] ?? message);
1938
+ } catch {
1939
+ }
1940
+ throw new AggregatorError(`Aggregator error: ${message}`, response.status);
1941
+ }
1942
+ if (!response.body) {
1943
+ throw new AggregatorError("No response body from aggregator");
1944
+ }
1945
+ const reader = response.body.getReader();
1946
+ const decoder = new TextDecoder();
1947
+ let buffer = "";
1948
+ let currentEvent = null;
1949
+ let currentData = "";
1950
+ try {
1951
+ while (true) {
1952
+ const { done, value } = await reader.read();
1953
+ if (done) break;
1954
+ buffer += decoder.decode(value, { stream: true });
1955
+ const lines = buffer.split("\n");
1956
+ buffer = lines.pop() ?? "";
1957
+ for (const line of lines) {
1958
+ const trimmedLine = line.trim();
1959
+ if (!trimmedLine) {
1960
+ if (currentEvent && currentData) {
1961
+ try {
1962
+ const data = JSON.parse(currentData);
1963
+ const event = this.parseSSEEvent(currentEvent, data);
1964
+ if (event) {
1965
+ yield event;
1966
+ }
1967
+ } catch {
1968
+ }
1969
+ }
1970
+ currentEvent = null;
1971
+ currentData = "";
1972
+ continue;
1973
+ }
1974
+ if (trimmedLine.startsWith("event:")) {
1975
+ currentEvent = trimmedLine.slice(6).trim();
1976
+ } else if (trimmedLine.startsWith("data:")) {
1977
+ currentData = trimmedLine.slice(5).trim();
1978
+ }
1979
+ }
1980
+ }
1981
+ } finally {
1982
+ reader.releaseLock();
1983
+ }
1984
+ }
1985
+ /**
1986
+ * Parse an SSE event into a typed event object.
1987
+ */
1988
+ parseSSEEvent(eventType, data) {
1989
+ switch (eventType) {
1990
+ case "retrieval_start":
1991
+ return {
1992
+ type: "retrieval_start",
1993
+ sourceCount: Number(data["sources"] ?? 0)
1994
+ };
1995
+ case "source_complete":
1996
+ return {
1997
+ type: "source_complete",
1998
+ path: String(data["path"] ?? ""),
1999
+ status: String(data["status"] ?? ""),
2000
+ documentsRetrieved: Number(data["documents"] ?? 0)
2001
+ };
2002
+ case "retrieval_complete":
2003
+ return {
2004
+ type: "retrieval_complete",
2005
+ totalDocuments: Number(data["total_documents"] ?? 0),
2006
+ timeMs: Number(data["time_ms"] ?? 0)
2007
+ };
2008
+ case "generation_start":
2009
+ return { type: "generation_start" };
2010
+ case "token":
2011
+ return {
2012
+ type: "token",
2013
+ content: String(data["content"] ?? "")
2014
+ };
2015
+ case "done": {
2016
+ const sourcesData = data["sources"];
2017
+ const sources = this.parseDocumentSources(sourcesData);
2018
+ const retrievalInfoData = data["retrieval_info"];
2019
+ const retrievalInfo = this.parseRetrievalInfo(retrievalInfoData);
2020
+ const metadataData = data["metadata"];
2021
+ const metadata = this.parseMetadata(metadataData ?? {});
2022
+ const usageData = data["usage"];
2023
+ const usage = usageData ? this.parseUsage(usageData) : void 0;
2024
+ return { type: "done", sources, retrievalInfo, metadata, usage };
2025
+ }
2026
+ case "error":
2027
+ return {
2028
+ type: "error",
2029
+ message: String(data["message"] ?? "Unknown error")
2030
+ };
2031
+ default:
2032
+ return {
2033
+ type: "error",
2034
+ message: `Unknown event type: ${eventType}`
2035
+ };
2036
+ }
2037
+ }
2038
+ /**
2039
+ * Get model endpoints that have connection URLs configured.
2040
+ *
2041
+ * @param limit - Maximum number of results (default: 20)
2042
+ * @returns Array of EndpointPublic objects for models with URLs
2043
+ */
2044
+ async getAvailableModels(limit = 20) {
2045
+ const results = [];
2046
+ for await (const endpoint of this.hub.browse()) {
2047
+ if (results.length >= limit) break;
2048
+ if (endpoint.type !== EndpointType.MODEL) continue;
2049
+ const hasUrl = endpoint.connect.some(
2050
+ (conn) => conn.enabled && conn.config["url"]
2051
+ );
2052
+ if (hasUrl) {
2053
+ results.push(endpoint);
2054
+ }
2055
+ }
2056
+ return results;
2057
+ }
2058
+ /**
2059
+ * Get data source endpoints that have connection URLs configured.
2060
+ *
2061
+ * @param limit - Maximum number of results (default: 20)
2062
+ * @returns Array of EndpointPublic objects for data sources with URLs
2063
+ */
2064
+ async getAvailableDataSources(limit = 20) {
2065
+ const results = [];
2066
+ for await (const endpoint of this.hub.browse()) {
2067
+ if (results.length >= limit) break;
2068
+ if (endpoint.type !== EndpointType.DATA_SOURCE) continue;
2069
+ const hasUrl = endpoint.connect.some(
2070
+ (conn) => conn.enabled && conn.config["url"]
2071
+ );
2072
+ if (hasUrl) {
2073
+ results.push(endpoint);
2074
+ }
2075
+ }
2076
+ return results;
2077
+ }
2078
+ };
2079
+
2080
+ // src/resources/syftai.ts
2081
+ init_errors();
2082
+ var RetrievalError = class extends exports.SyftHubError {
2083
+ constructor(message, sourcePath, detail) {
2084
+ super(message);
2085
+ this.sourcePath = sourcePath;
2086
+ this.detail = detail;
2087
+ this.name = "RetrievalError";
2088
+ }
2089
+ };
2090
+ var GenerationError = class extends exports.SyftHubError {
2091
+ constructor(message, modelSlug, detail) {
2092
+ super(message);
2093
+ this.modelSlug = modelSlug;
2094
+ this.detail = detail;
2095
+ this.name = "GenerationError";
2096
+ }
2097
+ };
2098
+ var SyftAIResource = class {
2099
+ // No dependencies - uses direct fetch to SyftAI-Space endpoints
2100
+ /**
2101
+ * Build headers for SyftAI-Space request.
2102
+ */
2103
+ buildHeaders(tenantName) {
2104
+ const headers = {
2105
+ "Content-Type": "application/json"
2106
+ };
2107
+ if (tenantName) {
2108
+ headers["X-Tenant-Name"] = tenantName;
2109
+ }
2110
+ return headers;
2111
+ }
2112
+ /**
2113
+ * Query a data source endpoint directly.
2114
+ *
2115
+ * @param options - Query options
2116
+ * @returns Array of Document objects
2117
+ * @throws {RetrievalError} If the query fails
2118
+ */
2119
+ async queryDataSource(options) {
2120
+ const { endpoint, query, userEmail, topK = 5, similarityThreshold = 0.5 } = options;
2121
+ const url = `${endpoint.url.replace(/\/$/, "")}/api/v1/endpoints/${endpoint.slug}/query`;
2122
+ const requestBody = {
2123
+ user_email: userEmail,
2124
+ messages: query,
2125
+ // SyftAI-Space expects "messages" for query text
2126
+ limit: topK,
2127
+ similarity_threshold: similarityThreshold
2128
+ };
2129
+ let response;
2130
+ try {
2131
+ response = await fetch(url, {
2132
+ method: "POST",
2133
+ headers: this.buildHeaders(endpoint.tenantName),
2134
+ body: JSON.stringify(requestBody)
2135
+ });
2136
+ } catch (error) {
2137
+ throw new RetrievalError(
2138
+ `Failed to connect to data source '${endpoint.slug}': ${error instanceof Error ? error.message : String(error)}`,
2139
+ endpoint.slug,
2140
+ error
2141
+ );
2142
+ }
2143
+ if (!response.ok) {
2144
+ let message = `HTTP ${response.status}`;
2145
+ try {
2146
+ const data2 = await response.json();
2147
+ message = String(data2["detail"] ?? data2["message"] ?? message);
2148
+ } catch {
2149
+ }
2150
+ throw new RetrievalError(`Data source query failed: ${message}`, endpoint.slug);
2151
+ }
2152
+ const data = await response.json();
2153
+ const documents = [];
2154
+ const docsData = data["documents"];
2155
+ if (Array.isArray(docsData)) {
2156
+ for (const doc of docsData) {
2157
+ documents.push({
2158
+ content: String(doc["content"] ?? ""),
2159
+ score: Number(doc["score"] ?? 0),
2160
+ metadata: doc["metadata"] ?? {}
2161
+ });
2162
+ }
2163
+ }
2164
+ return documents;
2165
+ }
2166
+ /**
2167
+ * Query a model endpoint directly.
2168
+ *
2169
+ * @param options - Query options
2170
+ * @returns Generated response text
2171
+ * @throws {GenerationError} If generation fails
2172
+ */
2173
+ async queryModel(options) {
2174
+ const { endpoint, messages, userEmail, maxTokens = 1024, temperature = 0.7 } = options;
2175
+ const url = `${endpoint.url.replace(/\/$/, "")}/api/v1/endpoints/${endpoint.slug}/query`;
2176
+ const requestBody = {
2177
+ user_email: userEmail,
2178
+ messages: messages.map((msg) => ({
2179
+ role: msg.role,
2180
+ content: msg.content
2181
+ })),
2182
+ max_tokens: maxTokens,
2183
+ temperature,
2184
+ stream: false
2185
+ };
2186
+ let response;
2187
+ try {
2188
+ response = await fetch(url, {
2189
+ method: "POST",
2190
+ headers: this.buildHeaders(endpoint.tenantName),
2191
+ body: JSON.stringify(requestBody)
2192
+ });
2193
+ } catch (error) {
2194
+ throw new GenerationError(
2195
+ `Failed to connect to model '${endpoint.slug}': ${error instanceof Error ? error.message : String(error)}`,
2196
+ endpoint.slug,
2197
+ error
2198
+ );
2199
+ }
2200
+ if (!response.ok) {
2201
+ let message = `HTTP ${response.status}`;
2202
+ try {
2203
+ const data2 = await response.json();
2204
+ message = String(data2["detail"] ?? data2["message"] ?? message);
2205
+ } catch {
2206
+ }
2207
+ throw new GenerationError(`Model query failed: ${message}`, endpoint.slug);
2208
+ }
2209
+ const data = await response.json();
2210
+ const messageData = data["message"];
2211
+ return String(messageData?.["content"] ?? "");
2212
+ }
2213
+ /**
2214
+ * Stream a model response directly.
2215
+ *
2216
+ * @param options - Query options
2217
+ * @yields Response text chunks as they arrive
2218
+ * @throws {GenerationError} If generation fails
2219
+ */
2220
+ async *queryModelStream(options) {
2221
+ const { endpoint, messages, userEmail, maxTokens = 1024, temperature = 0.7 } = options;
2222
+ const url = `${endpoint.url.replace(/\/$/, "")}/api/v1/endpoints/${endpoint.slug}/query`;
2223
+ const requestBody = {
2224
+ user_email: userEmail,
2225
+ messages: messages.map((msg) => ({
2226
+ role: msg.role,
2227
+ content: msg.content
2228
+ })),
2229
+ max_tokens: maxTokens,
2230
+ temperature,
2231
+ stream: true
2232
+ };
2233
+ let response;
2234
+ try {
2235
+ response = await fetch(url, {
2236
+ method: "POST",
2237
+ headers: {
2238
+ ...this.buildHeaders(endpoint.tenantName),
2239
+ Accept: "text/event-stream"
2240
+ },
2241
+ body: JSON.stringify(requestBody)
2242
+ });
2243
+ } catch (error) {
2244
+ throw new GenerationError(
2245
+ `Failed to connect to model '${endpoint.slug}': ${error instanceof Error ? error.message : String(error)}`,
2246
+ endpoint.slug,
2247
+ error
2248
+ );
2249
+ }
2250
+ if (!response.ok) {
2251
+ let message = `HTTP ${response.status}`;
2252
+ try {
2253
+ const data = await response.json();
2254
+ message = String(data["detail"] ?? data["message"] ?? message);
2255
+ } catch {
2256
+ }
2257
+ throw new GenerationError(`Model stream failed: ${message}`, endpoint.slug);
2258
+ }
2259
+ if (!response.body) {
2260
+ throw new GenerationError("No response body from model", endpoint.slug);
2261
+ }
2262
+ const reader = response.body.getReader();
2263
+ const decoder = new TextDecoder();
2264
+ let buffer = "";
2265
+ try {
2266
+ while (true) {
2267
+ const { done, value } = await reader.read();
2268
+ if (done) break;
2269
+ buffer += decoder.decode(value, { stream: true });
2270
+ const lines = buffer.split("\n");
2271
+ buffer = lines.pop() ?? "";
2272
+ for (const line of lines) {
2273
+ const trimmedLine = line.trim();
2274
+ if (!trimmedLine || trimmedLine.startsWith("event:")) {
2275
+ continue;
2276
+ }
2277
+ if (trimmedLine.startsWith("data:")) {
2278
+ const dataStr = trimmedLine.slice(5).trim();
2279
+ if (dataStr === "[DONE]") {
2280
+ return;
2281
+ }
2282
+ try {
2283
+ const data = JSON.parse(dataStr);
2284
+ if (typeof data["content"] === "string") {
2285
+ yield data["content"];
2286
+ } else if (Array.isArray(data["choices"])) {
2287
+ for (const choice of data["choices"]) {
2288
+ const delta = choice["delta"];
2289
+ if (delta && typeof delta["content"] === "string") {
2290
+ yield delta["content"];
2291
+ }
2292
+ }
2293
+ }
2294
+ } catch {
2295
+ }
2296
+ }
2297
+ }
2298
+ }
2299
+ } finally {
2300
+ reader.releaseLock();
2301
+ }
2302
+ }
2303
+ };
2304
+
2305
+ // src/client.ts
2306
+ function getEnv(key) {
2307
+ if (typeof process !== "undefined" && process.env) {
2308
+ return process.env[key];
2309
+ }
2310
+ return void 0;
2311
+ }
2312
+ function isBrowser() {
2313
+ return typeof globalThis !== "undefined" && typeof globalThis.window !== "undefined" && typeof globalThis.document !== "undefined";
2314
+ }
2315
+ var SyftHubClient = class {
2316
+ http;
2317
+ options;
2318
+ aggregatorUrl;
2319
+ // Lazy-initialized resources
2320
+ _auth;
2321
+ _users;
2322
+ _myEndpoints;
2323
+ _hub;
2324
+ _accounting;
2325
+ _chat;
2326
+ _syftai;
2327
+ /**
2328
+ * Create a new SyftHub client.
2329
+ *
2330
+ * @param options - Configuration options
2331
+ * @throws {SyftHubError} If baseUrl is not provided and SYFTHUB_URL is not set (in non-browser environments)
2332
+ */
2333
+ constructor(options = {}) {
2334
+ this.options = options;
2335
+ let baseUrl = options.baseUrl ?? getEnv("SYFTHUB_URL");
2336
+ if (!baseUrl && !isBrowser()) {
2337
+ throw new exports.SyftHubError(
2338
+ "baseUrl is required. Provide it in options or set the SYFTHUB_URL environment variable."
2339
+ );
2340
+ }
2341
+ baseUrl = baseUrl ?? "";
2342
+ const normalizedUrl = baseUrl ? baseUrl.replace(/\/+$/, "") : "";
2343
+ this.http = new HTTPClient(normalizedUrl, options.timeout ?? 3e4);
2344
+ this.aggregatorUrl = options.aggregatorUrl ?? getEnv("SYFTHUB_AGGREGATOR_URL") ?? `${normalizedUrl}/aggregator/api/v1`;
2345
+ }
2346
+ /**
2347
+ * Authentication resource for login, register, and session management.
2348
+ *
2349
+ * @example
2350
+ * const user = await client.auth.login('alice', 'password');
2351
+ * await client.auth.logout();
2352
+ */
2353
+ get auth() {
2354
+ if (!this._auth) {
2355
+ this._auth = new AuthResource(this.http);
2356
+ }
2357
+ return this._auth;
2358
+ }
2359
+ /**
2360
+ * Users resource for profile management.
2361
+ *
2362
+ * @example
2363
+ * const user = await client.users.update({ fullName: 'Alice Smith' });
2364
+ * const available = await client.users.checkUsername('newname');
2365
+ */
2366
+ get users() {
2367
+ if (!this._users) {
2368
+ this._users = new UsersResource(this.http);
2369
+ }
2370
+ return this._users;
2371
+ }
2372
+ /**
2373
+ * My Endpoints resource for managing your own endpoints.
2374
+ *
2375
+ * @example
2376
+ * const endpoints = await client.myEndpoints.list().all();
2377
+ * const endpoint = await client.myEndpoints.create({ name: 'My API', type: 'model' });
2378
+ */
2379
+ get myEndpoints() {
2380
+ if (!this._myEndpoints) {
2381
+ this._myEndpoints = new MyEndpointsResource(this.http);
2382
+ }
2383
+ return this._myEndpoints;
2384
+ }
2385
+ /**
2386
+ * Hub resource for browsing public endpoints.
2387
+ *
2388
+ * @example
2389
+ * for await (const endpoint of client.hub.browse()) {
2390
+ * console.log(endpoint.name);
2391
+ * }
2392
+ */
2393
+ get hub() {
2394
+ if (!this._hub) {
2395
+ this._hub = new HubResource(this.http);
2396
+ }
2397
+ return this._hub;
2398
+ }
2399
+ /**
2400
+ * Accounting resource for billing and transactions.
2401
+ *
2402
+ * The accounting service is external and uses separate credentials
2403
+ * (email/password Basic auth) from SyftHub's JWT authentication.
2404
+ *
2405
+ * Credentials can be provided via:
2406
+ * - Constructor options: accountingUrl, accountingEmail, accountingPassword
2407
+ * - Environment variables: SYFTHUB_ACCOUNTING_URL, SYFTHUB_ACCOUNTING_EMAIL, SYFTHUB_ACCOUNTING_PASSWORD
2408
+ *
2409
+ * @throws {SyftHubError} If accounting credentials are not configured
2410
+ *
2411
+ * @example
2412
+ * const user = await client.accounting.getUser();
2413
+ * console.log(`Balance: ${user.balance}`);
2414
+ *
2415
+ * // Create a transaction
2416
+ * const tx = await client.accounting.createTransaction({
2417
+ * recipientEmail: 'bob@example.com',
2418
+ * amount: 10.0
2419
+ * });
2420
+ */
2421
+ get accounting() {
2422
+ if (!this._accounting) {
2423
+ const url = this.options.accountingUrl ?? getEnv("SYFTHUB_ACCOUNTING_URL");
2424
+ const email = this.options.accountingEmail ?? getEnv("SYFTHUB_ACCOUNTING_EMAIL");
2425
+ const password = this.options.accountingPassword ?? getEnv("SYFTHUB_ACCOUNTING_PASSWORD");
2426
+ if (!url || !email || !password) {
2427
+ const missing = [];
2428
+ if (!url) missing.push("SYFTHUB_ACCOUNTING_URL");
2429
+ if (!email) missing.push("SYFTHUB_ACCOUNTING_EMAIL");
2430
+ if (!password) missing.push("SYFTHUB_ACCOUNTING_PASSWORD");
2431
+ throw new exports.ConfigurationError(
2432
+ `Accounting not configured. Missing: ${missing.join(", ")}. Set environment variables or pass credentials to SyftHubClient.`
2433
+ );
2434
+ }
2435
+ this._accounting = new AccountingResource({
2436
+ url,
2437
+ email,
2438
+ password,
2439
+ timeout: this.options.timeout
2440
+ });
2441
+ }
2442
+ return this._accounting;
2443
+ }
2444
+ /**
2445
+ * Chat resource for RAG-augmented conversations via the Aggregator.
2446
+ *
2447
+ * This resource provides high-level chat functionality that integrates
2448
+ * with the SyftHub Aggregator service for RAG workflows.
2449
+ *
2450
+ * @example
2451
+ * // Simple chat completion
2452
+ * const response = await client.chat.complete({
2453
+ * prompt: 'What is machine learning?',
2454
+ * model: 'alice/gpt-model',
2455
+ * dataSources: ['bob/ml-docs'],
2456
+ * });
2457
+ * console.log(response.response);
2458
+ *
2459
+ * // Streaming chat
2460
+ * for await (const event of client.chat.stream(options)) {
2461
+ * if (event.type === 'token') {
2462
+ * process.stdout.write(event.content);
2463
+ * }
2464
+ * }
2465
+ *
2466
+ * // Get available endpoints
2467
+ * const models = await client.chat.getAvailableModels();
2468
+ * const sources = await client.chat.getAvailableDataSources();
2469
+ */
2470
+ get chat() {
2471
+ if (!this._chat) {
2472
+ this._chat = new ChatResource(
2473
+ this.hub,
2474
+ this.auth,
2475
+ this.aggregatorUrl
2476
+ );
2477
+ }
2478
+ return this._chat;
2479
+ }
2480
+ /**
2481
+ * SyftAI-Space resource for direct endpoint queries (low-level API).
2482
+ *
2483
+ * This resource provides direct access to SyftAI-Space endpoints without
2484
+ * going through the aggregator. Use this when you need custom RAG pipelines
2485
+ * or fine-grained control over queries.
2486
+ *
2487
+ * For most use cases, prefer the higher-level `client.chat` API instead.
2488
+ *
2489
+ * @example
2490
+ * // Query a data source directly
2491
+ * const docs = await client.syftai.queryDataSource({
2492
+ * endpoint: { url: 'http://syftai:8080', slug: 'docs' },
2493
+ * query: 'What is Python?',
2494
+ * userEmail: 'alice@example.com',
2495
+ * });
2496
+ *
2497
+ * // Query a model directly
2498
+ * const response = await client.syftai.queryModel({
2499
+ * endpoint: { url: 'http://syftai:8080', slug: 'gpt-model' },
2500
+ * messages: [{ role: 'user', content: 'Hello!' }],
2501
+ * userEmail: 'alice@example.com',
2502
+ * });
2503
+ */
2504
+ get syftai() {
2505
+ if (!this._syftai) {
2506
+ this._syftai = new SyftAIResource();
2507
+ }
2508
+ return this._syftai;
2509
+ }
2510
+ /**
2511
+ * Get current authentication tokens.
2512
+ *
2513
+ * Use this to persist tokens for later sessions.
2514
+ *
2515
+ * @returns Current tokens or null if not authenticated
2516
+ *
2517
+ * @example
2518
+ * const tokens = client.getTokens();
2519
+ * if (tokens) {
2520
+ * localStorage.setItem('tokens', JSON.stringify(tokens));
2521
+ * }
2522
+ */
2523
+ getTokens() {
2524
+ return this.http.getTokens();
2525
+ }
2526
+ /**
2527
+ * Set authentication tokens.
2528
+ *
2529
+ * Use this to restore a session from previously saved tokens.
2530
+ *
2531
+ * @param tokens - Tokens to set
2532
+ *
2533
+ * @example
2534
+ * const saved = JSON.parse(localStorage.getItem('tokens'));
2535
+ * if (saved) {
2536
+ * client.setTokens(saved);
2537
+ * }
2538
+ */
2539
+ setTokens(tokens) {
2540
+ this.http.setTokens(tokens.accessToken, tokens.refreshToken);
2541
+ }
2542
+ /**
2543
+ * Check if the client is currently authenticated.
2544
+ *
2545
+ * @returns True if tokens are present
2546
+ */
2547
+ get isAuthenticated() {
2548
+ return this.http.hasTokens();
2549
+ }
2550
+ /**
2551
+ * Check if accounting service is configured.
2552
+ *
2553
+ * Use this to check if accounting credentials are available before
2554
+ * accessing the `accounting` property, which will throw if not configured.
2555
+ *
2556
+ * @returns True if accounting url, email, and password are all configured
2557
+ *
2558
+ * @example
2559
+ * if (client.isAccountingConfigured) {
2560
+ * const user = await client.accounting.getUser();
2561
+ * }
2562
+ */
2563
+ get isAccountingConfigured() {
2564
+ const url = this.options.accountingUrl ?? getEnv("SYFTHUB_ACCOUNTING_URL");
2565
+ const email = this.options.accountingEmail ?? getEnv("SYFTHUB_ACCOUNTING_EMAIL");
2566
+ const password = this.options.accountingPassword ?? getEnv("SYFTHUB_ACCOUNTING_PASSWORD");
2567
+ return Boolean(url && email && password);
2568
+ }
2569
+ /**
2570
+ * Close the client and clean up resources.
2571
+ *
2572
+ * Currently a no-op, but may be used in future for connection pooling.
2573
+ */
2574
+ close() {
2575
+ }
2576
+ };
2577
+
2578
+ // src/index.ts
2579
+ init_errors();
2580
+
2581
+ exports.AccountingResource = AccountingResource;
2582
+ exports.AggregatorError = AggregatorError;
2583
+ exports.ChatResource = ChatResource;
2584
+ exports.CreatorType = CreatorType;
2585
+ exports.EndpointResolutionError = EndpointResolutionError;
2586
+ exports.EndpointType = EndpointType;
2587
+ exports.GenerationError = GenerationError;
2588
+ exports.OrganizationRole = OrganizationRole;
2589
+ exports.PageIterator = PageIterator;
2590
+ exports.RetrievalError = RetrievalError;
2591
+ exports.SyftAIResource = SyftAIResource;
2592
+ exports.SyftHubClient = SyftHubClient;
2593
+ exports.TransactionStatus = TransactionStatus;
2594
+ exports.UserRole = UserRole;
2595
+ exports.Visibility = Visibility;
2596
+ exports.createAccountingResource = createAccountingResource;
2597
+ exports.getEndpointOwnerType = getEndpointOwnerType;
2598
+ exports.getEndpointPublicPath = getEndpointPublicPath;
2599
+ exports.isTransactionCancelled = isTransactionCancelled;
2600
+ exports.isTransactionCompleted = isTransactionCompleted;
2601
+ exports.isTransactionPending = isTransactionPending;
2602
+ exports.parseTransaction = parseTransaction;
2603
+ //# sourceMappingURL=index.cjs.map
2604
+ //# sourceMappingURL=index.cjs.map