@syfthub/sdk 0.1.1 → 0.2.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 CHANGED
@@ -126,11 +126,21 @@ var init_errors = __esm({
126
126
  init_errors();
127
127
 
128
128
  // src/utils.ts
129
+ var snakeToCamelCache = /* @__PURE__ */ new Map();
130
+ var camelToSnakeCache = /* @__PURE__ */ new Map();
129
131
  function snakeToCamel(str) {
130
- return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
132
+ const cached = snakeToCamelCache.get(str);
133
+ if (cached !== void 0) return cached;
134
+ const result = str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
135
+ snakeToCamelCache.set(str, result);
136
+ return result;
131
137
  }
132
138
  function camelToSnake(str) {
133
- return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
139
+ const cached = camelToSnakeCache.get(str);
140
+ if (cached !== void 0) return cached;
141
+ const result = str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
142
+ camelToSnakeCache.set(str, result);
143
+ return result;
134
144
  }
135
145
  var ISO_DATE_REGEX = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/;
136
146
  function isISODateString(value) {
@@ -143,6 +153,9 @@ function transformKeys(obj, keyTransformer, parseDates = true) {
143
153
  if (Array.isArray(obj)) {
144
154
  return obj.map((item) => transformKeys(item, keyTransformer, parseDates));
145
155
  }
156
+ if (obj instanceof Date) {
157
+ return obj.toISOString();
158
+ }
146
159
  if (parseDates && isISODateString(obj)) {
147
160
  return new Date(obj);
148
161
  }
@@ -170,9 +183,61 @@ function buildSearchParams(params) {
170
183
  }
171
184
  return searchParams;
172
185
  }
186
+ async function* readSSEEvents(response) {
187
+ if (!response.body) return;
188
+ const reader = response.body.getReader();
189
+ const decoder = new TextDecoder();
190
+ let buffer = "";
191
+ let currentEvent = null;
192
+ let currentData = "";
193
+ const flush = function* () {
194
+ if (currentData) {
195
+ yield { event: currentEvent ?? "message", data: currentData };
196
+ }
197
+ currentEvent = null;
198
+ currentData = "";
199
+ };
200
+ try {
201
+ while (true) {
202
+ const { done, value } = await reader.read();
203
+ if (done) break;
204
+ buffer += decoder.decode(value, { stream: true });
205
+ const lines = buffer.split("\n");
206
+ buffer = lines.pop() ?? "";
207
+ for (const line of lines) {
208
+ const trimmed = line.trim();
209
+ if (!trimmed) {
210
+ yield* flush();
211
+ continue;
212
+ }
213
+ if (trimmed.startsWith("event:")) {
214
+ currentEvent = trimmed.slice(6).trim();
215
+ } else if (trimmed.startsWith("data:")) {
216
+ if (currentData && currentEvent === null) {
217
+ yield* flush();
218
+ }
219
+ currentData = trimmed.slice(5).trim();
220
+ }
221
+ }
222
+ }
223
+ const trailing = buffer.trim();
224
+ if (trailing) {
225
+ if (trailing.startsWith("event:")) {
226
+ currentEvent = trailing.slice(6).trim();
227
+ } else if (trailing.startsWith("data:")) {
228
+ if (currentData && currentEvent === null) {
229
+ yield* flush();
230
+ }
231
+ currentData = trailing.slice(5).trim();
232
+ }
233
+ }
234
+ yield* flush();
235
+ } finally {
236
+ reader.releaseLock();
237
+ }
238
+ }
173
239
 
174
240
  // src/http.ts
175
- init_errors();
176
241
  var HTTPClient = class {
177
242
  /**
178
243
  * Create a new HTTP client.
@@ -186,17 +251,32 @@ var HTTPClient = class {
186
251
  }
187
252
  accessToken = null;
188
253
  refreshToken = null;
254
+ apiToken = null;
189
255
  isRefreshing = false;
190
256
  refreshPromise = null;
191
257
  /**
192
- * Set authentication tokens.
258
+ * Set JWT authentication tokens.
259
+ * Clears any API token if set.
193
260
  */
194
261
  setTokens(access, refresh) {
195
262
  this.accessToken = access;
196
263
  this.refreshToken = refresh;
264
+ this.apiToken = null;
197
265
  }
198
266
  /**
199
- * Get current authentication tokens.
267
+ * Set API token for authentication.
268
+ * Clears any JWT tokens if set.
269
+ *
270
+ * @param token - The API token (starts with "syft_")
271
+ */
272
+ setApiToken(token) {
273
+ this.apiToken = token;
274
+ this.accessToken = null;
275
+ this.refreshToken = null;
276
+ }
277
+ /**
278
+ * Get current JWT authentication tokens.
279
+ * Returns null if using API token authentication.
200
280
  */
201
281
  getTokens() {
202
282
  if (!this.accessToken || !this.refreshToken) {
@@ -209,17 +289,30 @@ var HTTPClient = class {
209
289
  };
210
290
  }
211
291
  /**
212
- * Clear authentication tokens.
292
+ * Check if using API token authentication.
293
+ */
294
+ isUsingApiToken() {
295
+ return this.apiToken !== null;
296
+ }
297
+ /**
298
+ * Clear all authentication (JWT and API tokens).
213
299
  */
214
300
  clearTokens() {
215
301
  this.accessToken = null;
216
302
  this.refreshToken = null;
303
+ this.apiToken = null;
217
304
  }
218
305
  /**
219
- * Check if the client has valid tokens.
306
+ * Check if the client has valid authentication (JWT or API token).
220
307
  */
221
308
  hasTokens() {
222
- return this.accessToken !== null;
309
+ return this.accessToken !== null || this.apiToken !== null;
310
+ }
311
+ /**
312
+ * Get the current bearer token (API token or JWT access token).
313
+ */
314
+ getBearerToken() {
315
+ return this.apiToken ?? this.accessToken;
223
316
  }
224
317
  /**
225
318
  * Make a GET request.
@@ -265,8 +358,9 @@ var HTTPClient = class {
265
358
  }
266
359
  }
267
360
  const headers = {};
268
- if (includeAuth && this.accessToken) {
269
- headers["Authorization"] = `Bearer ${this.accessToken}`;
361
+ const bearerToken = this.getBearerToken();
362
+ if (includeAuth && bearerToken) {
363
+ headers["Authorization"] = `Bearer ${bearerToken}`;
270
364
  }
271
365
  let requestBody;
272
366
  if (body !== void 0) {
@@ -294,7 +388,7 @@ var HTTPClient = class {
294
388
  signal: controller.signal
295
389
  });
296
390
  clearTimeout(timeoutId);
297
- if (response.status === 401 && includeAuth && this.refreshToken) {
391
+ if (response.status === 401 && includeAuth && this.refreshToken && !this.apiToken) {
298
392
  await this.attemptTokenRefresh();
299
393
  return this.request(method, path, {
300
394
  ...options,
@@ -481,6 +575,110 @@ var HTTPClient = class {
481
575
  // src/client.ts
482
576
  init_errors();
483
577
 
578
+ // src/resources/api-tokens.ts
579
+ var APITokensResource = class {
580
+ constructor(http) {
581
+ this.http = http;
582
+ }
583
+ /**
584
+ * Create a new API token.
585
+ *
586
+ * IMPORTANT: The returned token is only shown ONCE!
587
+ * Make sure to save it immediately - it cannot be retrieved later.
588
+ *
589
+ * @param input - Token creation options
590
+ * @returns The created token with the full token value
591
+ *
592
+ * @example
593
+ * const result = await client.apiTokens.create({
594
+ * name: 'CI/CD Pipeline',
595
+ * scopes: ['write'],
596
+ * expiresAt: new Date('2025-12-31'),
597
+ * });
598
+ *
599
+ * // SAVE THIS TOKEN - it will not be shown again!
600
+ * console.log(result.token);
601
+ */
602
+ async create(input) {
603
+ return this.http.post("/api/v1/auth/tokens", input);
604
+ }
605
+ /**
606
+ * List all API tokens for the current user.
607
+ *
608
+ * By default, only active tokens are returned.
609
+ * Note: The full token value is never returned - only the prefix.
610
+ *
611
+ * @param options - List options
612
+ * @returns List of tokens and total count
613
+ *
614
+ * @example
615
+ * // List active tokens
616
+ * const { tokens, total } = await client.apiTokens.list();
617
+ *
618
+ * // Include revoked tokens
619
+ * const all = await client.apiTokens.list({ includeInactive: true });
620
+ */
621
+ async list(options = {}) {
622
+ const params = {};
623
+ if (options.includeInactive !== void 0) {
624
+ params.include_inactive = options.includeInactive;
625
+ }
626
+ if (options.skip !== void 0) {
627
+ params.skip = options.skip;
628
+ }
629
+ if (options.limit !== void 0) {
630
+ params.limit = options.limit;
631
+ }
632
+ return this.http.get("/api/v1/auth/tokens", params);
633
+ }
634
+ /**
635
+ * Get a single API token by ID.
636
+ *
637
+ * Note: The full token value is never returned - only the prefix.
638
+ *
639
+ * @param tokenId - The token ID
640
+ * @returns The token details
641
+ *
642
+ * @example
643
+ * const token = await client.apiTokens.get(123);
644
+ * console.log(token.name, token.lastUsedAt);
645
+ */
646
+ async get(tokenId) {
647
+ return this.http.get(`/api/v1/auth/tokens/${tokenId}`);
648
+ }
649
+ /**
650
+ * Update an API token's name.
651
+ *
652
+ * Only the name can be updated. Scopes and expiration cannot be
653
+ * changed after creation.
654
+ *
655
+ * @param tokenId - The token ID
656
+ * @param input - Update options
657
+ * @returns The updated token
658
+ *
659
+ * @example
660
+ * const updated = await client.apiTokens.update(123, {
661
+ * name: 'New Name',
662
+ * });
663
+ */
664
+ async update(tokenId, input) {
665
+ return this.http.patch(`/api/v1/auth/tokens/${tokenId}`, input);
666
+ }
667
+ /**
668
+ * Revoke an API token.
669
+ *
670
+ * The token becomes immediately unusable. This action cannot be undone.
671
+ *
672
+ * @param tokenId - The token ID to revoke
673
+ *
674
+ * @example
675
+ * await client.apiTokens.revoke(123);
676
+ */
677
+ async revoke(tokenId) {
678
+ await this.http.delete(`/api/v1/auth/tokens/${tokenId}`);
679
+ }
680
+ };
681
+
484
682
  // src/resources/auth.ts
485
683
  init_errors();
486
684
  var AuthResource = class {
@@ -548,8 +746,13 @@ var AuthResource = class {
548
746
  const response = await this.http.post("/api/v1/auth/register", input, {
549
747
  includeAuth: false
550
748
  });
551
- this.http.setTokens(response.accessToken, response.refreshToken);
552
- return response.user;
749
+ if (response.accessToken && response.refreshToken) {
750
+ this.http.setTokens(response.accessToken, response.refreshToken);
751
+ }
752
+ return {
753
+ user: response.user,
754
+ requiresEmailVerification: response.requiresEmailVerification
755
+ };
553
756
  }
554
757
  /**
555
758
  * Login with username/email and password.
@@ -623,6 +826,115 @@ var AuthResource = class {
623
826
  newPassword
624
827
  });
625
828
  }
829
+ /**
830
+ * Get the platform's authentication configuration.
831
+ *
832
+ * No authentication required. Use this to determine whether email
833
+ * verification or password reset is available.
834
+ *
835
+ * @returns AuthConfig with feature flags
836
+ */
837
+ async getAuthConfig() {
838
+ return this.http.get("/api/v1/auth/config", void 0, { includeAuth: false });
839
+ }
840
+ /**
841
+ * Verify a registration OTP and receive auth tokens.
842
+ *
843
+ * After registering when email verification is required, call this with
844
+ * the 6-digit code sent to the user's email.
845
+ *
846
+ * Idempotent: if the user is already verified, tokens are issued immediately.
847
+ *
848
+ * @param input - Email and 6-digit code
849
+ * @returns The authenticated User
850
+ * @throws {APIError} If the code is invalid or max attempts exceeded
851
+ */
852
+ async verifyOtp(input) {
853
+ const response = await this.http.post("/api/v1/auth/register/verify-otp", input, {
854
+ includeAuth: false
855
+ });
856
+ this.http.setTokens(response.accessToken, response.refreshToken);
857
+ return response.user;
858
+ }
859
+ /**
860
+ * Resend the registration OTP code.
861
+ *
862
+ * Rate-limited. Always returns successfully to prevent email enumeration.
863
+ *
864
+ * @param email - Email address to resend the OTP to
865
+ */
866
+ async resendOtp(email) {
867
+ await this.http.post(
868
+ "/api/v1/auth/register/resend-otp",
869
+ { email },
870
+ {
871
+ includeAuth: false
872
+ }
873
+ );
874
+ }
875
+ /**
876
+ * Request a password reset OTP.
877
+ *
878
+ * Always returns successfully to prevent email enumeration.
879
+ * If SMTP is not configured on the server, this is a no-op.
880
+ *
881
+ * @param input - Email address for password reset
882
+ */
883
+ async requestPasswordReset(input) {
884
+ await this.http.post("/api/v1/auth/password-reset/request", input, {
885
+ includeAuth: false
886
+ });
887
+ }
888
+ /**
889
+ * Confirm a password reset with OTP and set a new password.
890
+ *
891
+ * @param input - Email, 6-digit code, and new password
892
+ * @throws {APIError} If the code is invalid or max attempts exceeded
893
+ */
894
+ async confirmPasswordReset(input) {
895
+ await this.http.post("/api/v1/auth/password-reset/confirm", input, {
896
+ includeAuth: false
897
+ });
898
+ }
899
+ /**
900
+ * Get a peer token for NATS communication with tunneling spaces.
901
+ *
902
+ * Peer tokens are short-lived credentials that allow the aggregator to
903
+ * communicate with tunneling SyftAI Spaces via NATS pub/sub.
904
+ *
905
+ * @param targetUsernames - Usernames of the tunneling spaces to communicate with
906
+ * @returns PeerTokenResponse with token, channel, expiry, and NATS URL
907
+ * @throws {AuthenticationError} If not authenticated
908
+ *
909
+ * @example
910
+ * const peer = await client.auth.getPeerToken(['alice', 'bob']);
911
+ * console.log(`Peer channel: ${peer.peerChannel}, expires in ${peer.expiresIn}s`);
912
+ */
913
+ async getPeerToken(targetUsernames) {
914
+ return this.http.post("/api/v1/peer-token", {
915
+ target_usernames: targetUsernames
916
+ });
917
+ }
918
+ /**
919
+ * Get a guest peer token for NATS communication without authentication.
920
+ *
921
+ * Guest peer tokens are rate-limited by IP address. They use the same
922
+ * response format as authenticated peer tokens.
923
+ *
924
+ * @param targetUsernames - Usernames of the tunneling spaces to communicate with
925
+ * @returns PeerTokenResponse with token, channel, expiry, and NATS URL
926
+ *
927
+ * @example
928
+ * const peer = await client.auth.getGuestPeerToken(['alice']);
929
+ * console.log(`Guest peer channel: ${peer.peerChannel}`);
930
+ */
931
+ async getGuestPeerToken(targetUsernames) {
932
+ return this.http.post(
933
+ "/api/v1/nats/guest-peer-token",
934
+ { target_usernames: targetUsernames },
935
+ { includeAuth: false }
936
+ );
937
+ }
626
938
  /**
627
939
  * Get a satellite token for a specific audience (target service).
628
940
  *
@@ -667,6 +979,59 @@ var AuthResource = class {
667
979
  return { audience: aud, token: response.targetToken };
668
980
  })
669
981
  );
982
+ for (const [i, result] of results.entries()) {
983
+ if (result.status === "fulfilled") {
984
+ tokenMap.set(result.value.audience, result.value.token);
985
+ } else {
986
+ console.warn(
987
+ `[SyftHub] Failed to fetch satellite token for "${uniqueAudiences[i]}":`,
988
+ result.reason
989
+ );
990
+ }
991
+ }
992
+ return tokenMap;
993
+ }
994
+ /**
995
+ * Get a guest satellite token for a specific audience (target service).
996
+ *
997
+ * Guest tokens allow unauthenticated users to access policy-free endpoints.
998
+ * No authentication is required to call this method.
999
+ *
1000
+ * @param audience - Target service identifier (username of the service owner)
1001
+ * @returns Satellite token response with token and expiry
1002
+ * @throws {ValidationError} If audience is invalid or inactive
1003
+ *
1004
+ * @example
1005
+ * // Get a guest token for querying alice's policy-free endpoints
1006
+ * const tokenResponse = await client.auth.getGuestSatelliteToken('alice');
1007
+ */
1008
+ async getGuestSatelliteToken(audience) {
1009
+ return this.http.get(
1010
+ "/api/v1/token/guest",
1011
+ { aud: audience },
1012
+ { includeAuth: false }
1013
+ );
1014
+ }
1015
+ /**
1016
+ * Get guest satellite tokens for multiple audiences in parallel.
1017
+ *
1018
+ * No authentication is required to call this method.
1019
+ *
1020
+ * @param audiences - Array of unique audience identifiers (usernames)
1021
+ * @returns Map of audience to satellite token
1022
+ *
1023
+ * @example
1024
+ * const tokens = await client.auth.getGuestSatelliteTokens(['alice', 'bob']);
1025
+ */
1026
+ async getGuestSatelliteTokens(audiences) {
1027
+ const uniqueAudiences = [...new Set(audiences)];
1028
+ const tokenMap = /* @__PURE__ */ new Map();
1029
+ const results = await Promise.allSettled(
1030
+ uniqueAudiences.map(async (aud) => {
1031
+ const response = await this.getGuestSatelliteToken(aud);
1032
+ return { audience: aud, token: response.targetToken };
1033
+ })
1034
+ );
670
1035
  for (const result of results) {
671
1036
  if (result.status === "fulfilled") {
672
1037
  tokenMap.set(result.value.audience, result.value.token);
@@ -677,38 +1042,137 @@ var AuthResource = class {
677
1042
  /**
678
1043
  * Get transaction tokens for multiple endpoint owners.
679
1044
  *
680
- * Transaction tokens are short-lived JWTs that pre-authorize the endpoint owner
681
- * (recipient) to charge the current user (sender) for usage. These tokens are
682
- * created via the accounting service and passed to the aggregator.
1045
+ * @deprecated Transaction tokens are no longer needed. Payments are handled
1046
+ * via the MPP 402 flow. This method is kept for backward compatibility and
1047
+ * always returns empty tokens/errors.
683
1048
  *
684
- * This is used by the chat flow to enable billing for endpoint usage.
1049
+ * @param ownerUsernames - Array of endpoint owner usernames (ignored)
1050
+ * @returns TransactionTokensResponse with empty tokens and errors
1051
+ */
1052
+ async getTransactionTokens(ownerUsernames) {
1053
+ return { tokens: {}, errors: {} };
1054
+ }
1055
+ /**
1056
+ * Get the current access token (JWT or API token).
685
1057
  *
686
- * @param ownerUsernames - Array of endpoint owner usernames
687
- * @returns TransactionTokensResponse with tokens map and any errors
1058
+ * This is used by the chat flow to pass the user's Hub token to the
1059
+ * aggregator for the MPP payment callback.
1060
+ *
1061
+ * @returns The current access token, or null if not authenticated
1062
+ */
1063
+ getAccessToken() {
1064
+ const tokens = this.http.getTokens();
1065
+ return tokens?.accessToken ?? null;
1066
+ }
1067
+ };
1068
+
1069
+ // src/resources/aggregators.ts
1070
+ var AggregatorsResource = class {
1071
+ constructor(http) {
1072
+ this.http = http;
1073
+ }
1074
+ /**
1075
+ * List all aggregator configurations for the current user.
1076
+ *
1077
+ * @returns Array of UserAggregator objects
688
1078
  * @throws {AuthenticationError} If not authenticated
689
1079
  *
690
1080
  * @example
691
- * // Get transaction tokens for endpoint owners
692
- * const response = await client.auth.getTransactionTokens(['alice', 'bob']);
693
- * console.log(`Got ${Object.keys(response.tokens).length} tokens`);
694
- * if (Object.keys(response.errors).length > 0) {
695
- * console.log('Some tokens failed:', response.errors);
1081
+ * const aggregators = await client.users.aggregators.list();
1082
+ * for (const agg of aggregators) {
1083
+ * if (agg.isDefault) {
1084
+ * console.log(`Default: ${agg.name}`);
1085
+ * }
696
1086
  * }
697
1087
  */
698
- async getTransactionTokens(ownerUsernames) {
699
- const uniqueOwners = [...new Set(ownerUsernames)];
700
- if (uniqueOwners.length === 0) {
701
- return { tokens: {}, errors: {} };
702
- }
703
- try {
704
- return await this.http.post(
705
- "/api/v1/accounting/transaction-tokens",
706
- { owner_usernames: uniqueOwners }
707
- );
708
- } catch (error) {
709
- console.warn("Failed to get transaction tokens:", error);
710
- return { tokens: {}, errors: {} };
711
- }
1088
+ async list() {
1089
+ return this.http.get("/api/v1/users/me/aggregators");
1090
+ }
1091
+ /**
1092
+ * Get a specific aggregator configuration by ID.
1093
+ *
1094
+ * @param aggregatorId - The aggregator ID
1095
+ * @returns The UserAggregator object
1096
+ * @throws {AuthenticationError} If not authenticated
1097
+ * @throws {NotFoundError} If aggregator not found
1098
+ *
1099
+ * @example
1100
+ * const agg = await client.users.aggregators.get(1);
1101
+ * console.log(`${agg.name}: ${agg.url}`);
1102
+ */
1103
+ async get(aggregatorId) {
1104
+ return this.http.get(`/api/v1/users/me/aggregators/${aggregatorId}`);
1105
+ }
1106
+ /**
1107
+ * Create a new aggregator configuration.
1108
+ *
1109
+ * The first aggregator created is automatically set as the default.
1110
+ *
1111
+ * @param input - Aggregator creation input
1112
+ * @returns The created UserAggregator object
1113
+ * @throws {AuthenticationError} If not authenticated
1114
+ * @throws {ValidationError} If input is invalid
1115
+ *
1116
+ * @example
1117
+ * const agg = await client.users.aggregators.create({
1118
+ * name: 'My Custom Aggregator',
1119
+ * url: 'https://my-aggregator.example.com'
1120
+ * });
1121
+ * console.log(`Created: ${agg.id}`);
1122
+ */
1123
+ async create(input) {
1124
+ return this.http.post("/api/v1/users/me/aggregators", input);
1125
+ }
1126
+ /**
1127
+ * Update an aggregator configuration.
1128
+ *
1129
+ * Only provided fields will be updated.
1130
+ *
1131
+ * @param aggregatorId - The aggregator ID to update
1132
+ * @param input - Fields to update
1133
+ * @returns The updated UserAggregator object
1134
+ * @throws {AuthenticationError} If not authenticated
1135
+ * @throws {NotFoundError} If aggregator not found
1136
+ * @throws {ValidationError} If input is invalid
1137
+ *
1138
+ * @example
1139
+ * const agg = await client.users.aggregators.update(1, {
1140
+ * name: 'Updated Name'
1141
+ * });
1142
+ */
1143
+ async update(aggregatorId, input) {
1144
+ return this.http.put(`/api/v1/users/me/aggregators/${aggregatorId}`, input);
1145
+ }
1146
+ /**
1147
+ * Delete an aggregator configuration.
1148
+ *
1149
+ * @param aggregatorId - The aggregator ID to delete
1150
+ * @throws {AuthenticationError} If not authenticated
1151
+ * @throws {NotFoundError} If aggregator not found
1152
+ *
1153
+ * @example
1154
+ * await client.users.aggregators.delete(1);
1155
+ */
1156
+ async delete(aggregatorId) {
1157
+ await this.http.delete(`/api/v1/users/me/aggregators/${aggregatorId}`);
1158
+ }
1159
+ /**
1160
+ * Set an aggregator as the default.
1161
+ *
1162
+ * Only one aggregator can be the default at a time. Setting a new default
1163
+ * automatically unsets the previous one.
1164
+ *
1165
+ * @param aggregatorId - The aggregator ID to set as default
1166
+ * @returns The updated UserAggregator object with isDefault=true
1167
+ * @throws {AuthenticationError} If not authenticated
1168
+ * @throws {NotFoundError} If aggregator not found
1169
+ *
1170
+ * @example
1171
+ * const agg = await client.users.aggregators.setDefault(2);
1172
+ * console.log(`${agg.name} is now the default`);
1173
+ */
1174
+ async setDefault(aggregatorId) {
1175
+ return this.http.patch(`/api/v1/users/me/aggregators/${aggregatorId}/default`);
712
1176
  }
713
1177
  };
714
1178
 
@@ -717,6 +1181,34 @@ var UsersResource = class {
717
1181
  constructor(http) {
718
1182
  this.http = http;
719
1183
  }
1184
+ _aggregators;
1185
+ /**
1186
+ * Access aggregator management operations.
1187
+ *
1188
+ * @returns AggregatorsResource for managing user's aggregator configurations
1189
+ *
1190
+ * @example
1191
+ * // List aggregators
1192
+ * const aggregators = await client.users.aggregators.list();
1193
+ * for (const agg of aggregators) {
1194
+ * console.log(`${agg.name}: ${agg.url}`);
1195
+ * }
1196
+ *
1197
+ * // Create aggregator
1198
+ * const agg = await client.users.aggregators.create({
1199
+ * name: 'My Aggregator',
1200
+ * url: 'https://my-aggregator.example.com'
1201
+ * });
1202
+ *
1203
+ * // Set as default
1204
+ * await client.users.aggregators.setDefault(agg.id);
1205
+ */
1206
+ get aggregators() {
1207
+ if (!this._aggregators) {
1208
+ this._aggregators = new AggregatorsResource(this.http);
1209
+ }
1210
+ return this._aggregators;
1211
+ }
720
1212
  /**
721
1213
  * Update the current user's profile.
722
1214
  *
@@ -776,6 +1268,50 @@ var UsersResource = class {
776
1268
  async getAccountingCredentials() {
777
1269
  return this.http.get("/api/v1/users/me/accounting");
778
1270
  }
1271
+ /**
1272
+ * Send a heartbeat to indicate this SyftAI Space is alive.
1273
+ *
1274
+ * The heartbeat mechanism allows SyftAI Spaces to signal their availability
1275
+ * to SyftHub. This should be called periodically (before the TTL expires)
1276
+ * to maintain the "active" status.
1277
+ *
1278
+ * @param input - Heartbeat parameters
1279
+ * @param input.url - Full URL of this space (e.g., "https://myspace.example.com").
1280
+ * The server extracts the domain from this URL.
1281
+ * @param input.ttlSeconds - Time-to-live in seconds (1-3600). The server caps this
1282
+ * at a maximum of 600 seconds (10 minutes). Default is 300
1283
+ * seconds (5 minutes).
1284
+ * @returns HeartbeatResponse containing status, expiry time, domain, and effective TTL
1285
+ * @throws {AuthenticationError} If not authenticated
1286
+ * @throws {ValidationError} If URL or TTL is invalid
1287
+ *
1288
+ * @example
1289
+ * // Send heartbeat with default TTL (300 seconds)
1290
+ * const response = await client.users.sendHeartbeat({
1291
+ * url: 'https://myspace.example.com'
1292
+ * });
1293
+ * console.log(`Next heartbeat before: ${response.expiresAt}`);
1294
+ *
1295
+ * @example
1296
+ * // Send heartbeat with custom TTL
1297
+ * const response = await client.users.sendHeartbeat({
1298
+ * url: 'https://myspace.example.com',
1299
+ * ttlSeconds: 600 // Maximum allowed
1300
+ * });
1301
+ */
1302
+ async sendHeartbeat(input) {
1303
+ const response = await this.http.post("/api/v1/users/me/heartbeat", {
1304
+ url: input.url,
1305
+ ttl_seconds: input.ttlSeconds ?? 300
1306
+ });
1307
+ return {
1308
+ status: response.status,
1309
+ receivedAt: new Date(response.received_at),
1310
+ expiresAt: new Date(response.expires_at),
1311
+ domain: response.domain,
1312
+ ttlSeconds: response.ttl_seconds
1313
+ };
1314
+ }
779
1315
  };
780
1316
 
781
1317
  // src/pagination.ts
@@ -904,14 +1440,12 @@ var MyEndpointsResource = class {
904
1440
  * Create a new endpoint.
905
1441
  *
906
1442
  * @param input - Endpoint creation details
907
- * @param organizationId - Optional organization ID (for org-owned endpoints)
908
1443
  * @returns The created Endpoint
909
1444
  * @throws {AuthenticationError} If not authenticated
910
1445
  * @throws {ValidationError} If input validation fails
911
1446
  */
912
- async create(input, organizationId) {
913
- const body = organizationId !== void 0 ? { ...input, organizationId } : input;
914
- return this.http.post("/api/v1/endpoints", body);
1447
+ async create(input) {
1448
+ return this.http.post("/api/v1/endpoints", input);
915
1449
  }
916
1450
  /**
917
1451
  * Get a specific endpoint by path.
@@ -972,7 +1506,6 @@ var MyEndpointsResource = class {
972
1506
  * 3. Is ATOMIC: either all endpoints sync successfully, or none do
973
1507
  *
974
1508
  * Important Notes:
975
- * - Organization endpoints are NOT affected
976
1509
  * - Stars on existing endpoints will be lost (reset to 0)
977
1510
  * - Endpoint IDs will change (new IDs assigned)
978
1511
  * - Maximum 100 endpoints per sync request
@@ -1046,17 +1579,23 @@ var HubResource = class {
1046
1579
  /**
1047
1580
  * Browse all public endpoints.
1048
1581
  *
1049
- * @param options - Pagination options
1582
+ * @param options - Filter and pagination options
1050
1583
  * @returns PageIterator that lazily fetches endpoints
1584
+ *
1585
+ * @example
1586
+ * // Browse only model endpoints
1587
+ * const models = await client.hub.browse({ endpointType: 'model' }).firstPage();
1051
1588
  */
1052
1589
  browse(options) {
1053
1590
  const pageSize = options?.pageSize ?? 20;
1054
1591
  return new PageIterator(async (skip, limit) => {
1055
- return this.http.get(
1056
- "/api/v1/endpoints/public",
1057
- { skip, limit },
1058
- { includeAuth: false }
1059
- );
1592
+ const params = { skip, limit };
1593
+ if (options?.endpointType !== void 0) {
1594
+ params["endpoint_type"] = options.endpointType;
1595
+ }
1596
+ return this.http.get("/api/v1/endpoints/public", params, {
1597
+ includeAuth: false
1598
+ });
1060
1599
  }, pageSize);
1061
1600
  }
1062
1601
  /**
@@ -1064,19 +1603,57 @@ var HubResource = class {
1064
1603
  *
1065
1604
  * @param options - Filter and pagination options
1066
1605
  * @returns PageIterator that lazily fetches endpoints
1606
+ *
1607
+ * @example
1608
+ * // Get trending models only
1609
+ * const models = await client.hub.trending({ endpointType: 'model' }).firstPage();
1067
1610
  */
1068
1611
  trending(options) {
1069
1612
  const pageSize = options?.pageSize ?? 20;
1070
1613
  return new PageIterator(async (skip, limit) => {
1071
1614
  const params = { skip, limit };
1615
+ if (options?.endpointType !== void 0) {
1616
+ params["endpoint_type"] = options.endpointType;
1617
+ }
1072
1618
  if (options?.minStars !== void 0) {
1073
- params["minStars"] = options.minStars;
1619
+ params["min_stars"] = options.minStars;
1074
1620
  }
1075
1621
  return this.http.get("/api/v1/endpoints/trending", params, {
1076
1622
  includeAuth: false
1077
1623
  });
1078
1624
  }, pageSize);
1079
1625
  }
1626
+ /**
1627
+ * List endpoints accessible to unauthenticated (guest) users.
1628
+ *
1629
+ * Guest-accessible endpoints are public, active, and have no policies attached.
1630
+ * No authentication is required to call this method.
1631
+ *
1632
+ * @param options - Filter and pagination options
1633
+ * @returns PageIterator that lazily fetches guest-accessible endpoints
1634
+ *
1635
+ * @example
1636
+ * // List all guest-accessible endpoints
1637
+ * for await (const endpoint of client.hub.guestAccessible()) {
1638
+ * console.log(`${endpoint.ownerUsername}/${endpoint.slug}: ${endpoint.name}`);
1639
+ * }
1640
+ *
1641
+ * @example
1642
+ * // List only guest-accessible models
1643
+ * const models = await client.hub.guestAccessible({ endpointType: 'model' }).firstPage();
1644
+ */
1645
+ guestAccessible(options) {
1646
+ const pageSize = options?.pageSize ?? 20;
1647
+ return new PageIterator(async (skip, limit) => {
1648
+ const params = { skip, limit };
1649
+ if (options?.endpointType !== void 0) {
1650
+ params["endpoint_type"] = options.endpointType;
1651
+ }
1652
+ return this.http.get("/api/v1/endpoints/guest-accessible", params, {
1653
+ includeAuth: false
1654
+ });
1655
+ }, pageSize);
1656
+ }
1080
1657
  /**
1081
1658
  * Search for endpoints using semantic search.
1082
1659
  *
@@ -1172,6 +1749,26 @@ var HubResource = class {
1172
1749
  const endpointId = await this.resolveEndpointId(path);
1173
1750
  await this.http.delete(`/api/v1/endpoints/${endpointId}/star`);
1174
1751
  }
1752
+ /**
1753
+ * Get the owner/slug paths of a collective's approved member endpoints,
1754
+ * optionally narrowed to a single shared-endpoint subset.
1755
+ *
1756
+ * Used by the chat resource to expand both `collective/<slug>` and
1757
+ * `collective/<slug>/<shared-slug>` data-source references into the
1758
+ * individual endpoint paths before building the aggregator request.
1759
+ *
1760
+ * @param slug - The collective slug (e.g. "genomics-research")
1761
+ * @param sharedSlug - Optional curated-subset slug. When provided, only the
1762
+ * intersection of the subset's configured endpoints with the collective's
1763
+ * currently approved members is returned. Omit for "all approved members".
1764
+ * @returns Array of "owner/slug" path strings
1765
+ * @throws {NotFoundError} If the collective (or shared endpoint) does not exist
1766
+ */
1767
+ async getCollectiveEndpointPaths(slug, sharedSlug) {
1768
+ const base = `/api/v1/collectives/by-slug/${encodeURIComponent(slug)}`;
1769
+ const path = sharedSlug ? `${base}/shared-endpoints/${encodeURIComponent(sharedSlug)}/endpoint-paths` : `${base}/endpoint-paths`;
1770
+ return this.http.get(path, {}, { includeAuth: false });
1771
+ }
1175
1772
  /**
1176
1773
  * Check if you have starred an endpoint.
1177
1774
  *
@@ -1189,489 +1786,408 @@ var HubResource = class {
1189
1786
  }
1190
1787
  };
1191
1788
 
1192
- // src/models/common.ts
1193
- var Visibility = {
1194
- /** Visible to everyone, no authentication required */
1195
- PUBLIC: "public",
1196
- /** Only visible to the owner and collaborators */
1197
- PRIVATE: "private",
1198
- /** Visible to authenticated users within the organization */
1199
- INTERNAL: "internal"
1200
- };
1201
- var EndpointType = {
1202
- /** Machine learning model endpoint */
1203
- MODEL: "model",
1204
- /** Data source endpoint */
1205
- DATA_SOURCE: "data_source"
1206
- };
1207
- var UserRole = {
1208
- /** Administrator with full access */
1209
- ADMIN: "admin",
1210
- /** Regular user */
1211
- USER: "user",
1212
- /** Guest user with limited access */
1213
- GUEST: "guest"
1214
- };
1215
- var OrganizationRole = {
1216
- /** Organization owner with full control */
1217
- OWNER: "owner",
1218
- /** Administrator with management privileges */
1219
- ADMIN: "admin",
1220
- /** Regular member */
1221
- MEMBER: "member"
1222
- };
1223
-
1224
- // src/models/endpoint.ts
1225
- function getEndpointOwnerType(endpoint) {
1226
- return endpoint.organizationId !== null ? "organization" : "user";
1227
- }
1228
- function getEndpointPublicPath(endpoint) {
1229
- return `${endpoint.ownerUsername}/${endpoint.slug}`;
1230
- }
1231
-
1232
- // src/models/accounting.ts
1233
- var TransactionStatus = {
1234
- /** Transaction created, awaiting confirmation */
1235
- PENDING: "pending",
1236
- /** Transaction confirmed, funds transferred */
1237
- COMPLETED: "completed",
1238
- /** Transaction cancelled, no funds transferred */
1239
- CANCELLED: "cancelled"
1240
- };
1241
- var CreatorType = {
1242
- /** System-initiated transaction */
1243
- SYSTEM: "system",
1244
- /** Sender-initiated transaction */
1245
- SENDER: "sender",
1246
- /** Recipient-initiated transaction (delegated) */
1247
- RECIPIENT: "recipient"
1248
- };
1249
- function parseTransaction(response) {
1250
- return {
1251
- ...response,
1252
- createdAt: new Date(response.createdAt),
1253
- resolvedAt: response.resolvedAt ? new Date(response.resolvedAt) : null
1254
- };
1255
- }
1256
- function isTransactionPending(tx) {
1257
- return tx.status === TransactionStatus.PENDING;
1258
- }
1259
- function isTransactionCompleted(tx) {
1260
- return tx.status === TransactionStatus.COMPLETED;
1261
- }
1262
- function isTransactionCancelled(tx) {
1263
- return tx.status === TransactionStatus.CANCELLED;
1264
- }
1265
-
1266
1789
  // src/resources/accounting.ts
1267
- init_errors();
1268
- async function handleResponseError(response) {
1269
- if (response.ok) return;
1270
- let detail;
1271
- try {
1272
- const body = await response.json();
1273
- detail = body.detail ?? body.message ?? JSON.stringify(body);
1274
- } catch {
1275
- detail = await response.text() || `HTTP ${response.status}`;
1276
- }
1277
- switch (response.status) {
1278
- case 401:
1279
- throw new exports.AuthenticationError(`Authentication failed: ${detail}`);
1280
- case 403:
1281
- throw new exports.AuthorizationError(`Permission denied: ${detail}`);
1282
- case 404:
1283
- throw new exports.NotFoundError(`Not found: ${detail}`);
1284
- case 422:
1285
- throw new exports.ValidationError(`Validation error: ${detail}`);
1286
- default:
1287
- throw new exports.APIError(`Accounting API error: ${detail}`, response.status);
1288
- }
1289
- }
1290
- function createBasicAuth(email, password) {
1291
- const credentials = `${email}:${password}`;
1292
- const encoded = typeof btoa !== "undefined" ? btoa(credentials) : Buffer.from(credentials).toString("base64");
1293
- return `Basic ${encoded}`;
1294
- }
1295
1790
  var AccountingResource = class {
1296
- baseUrl;
1297
- email;
1298
- password;
1299
- timeout;
1300
- authHeader;
1301
- constructor(options) {
1302
- this.baseUrl = options.url.replace(/\/$/, "");
1303
- this.email = options.email;
1304
- this.password = options.password;
1305
- this.timeout = options.timeout ?? 3e4;
1306
- this.authHeader = createBasicAuth(this.email, this.password);
1307
- }
1308
- // ===========================================================================
1309
- // Private HTTP Methods
1310
- // ===========================================================================
1311
- /**
1312
- * Make an authenticated request to the accounting service.
1313
- */
1314
- async request(method, path, options) {
1315
- const url = new URL(path, this.baseUrl);
1316
- if (options?.params) {
1317
- for (const [key, value] of Object.entries(options.params)) {
1318
- url.searchParams.set(key, String(value));
1319
- }
1320
- }
1321
- const controller = new AbortController();
1322
- const timeoutId = setTimeout(() => controller.abort(), this.timeout);
1323
- try {
1324
- const response = await fetch(url.toString(), {
1325
- method,
1326
- headers: {
1327
- Authorization: this.authHeader,
1328
- "Content-Type": "application/json",
1329
- Accept: "application/json"
1330
- },
1331
- body: options?.body ? JSON.stringify(options.body) : void 0,
1332
- signal: controller.signal
1333
- });
1334
- await handleResponseError(response);
1335
- if (response.status === 204) {
1336
- return {};
1337
- }
1338
- return await response.json();
1339
- } catch (error) {
1340
- if (error instanceof exports.SyftHubError) {
1341
- throw error;
1342
- }
1343
- if (error instanceof Error && error.name === "AbortError") {
1344
- throw new exports.APIError("Request timeout", 408);
1345
- }
1346
- throw new exports.APIError(
1347
- `Accounting request failed: ${error instanceof Error ? error.message : "Unknown error"}`,
1348
- 0
1349
- );
1350
- } finally {
1351
- clearTimeout(timeoutId);
1352
- }
1353
- }
1354
- /**
1355
- * Make a request using Bearer token auth (for delegated transactions).
1356
- */
1357
- async requestWithToken(method, path, token, options) {
1358
- const url = new URL(path, this.baseUrl);
1359
- const controller = new AbortController();
1360
- const timeoutId = setTimeout(() => controller.abort(), this.timeout);
1361
- try {
1362
- const response = await fetch(url.toString(), {
1363
- method,
1364
- headers: {
1365
- Authorization: `Bearer ${token}`,
1366
- "Content-Type": "application/json",
1367
- Accept: "application/json"
1368
- },
1369
- body: options?.body ? JSON.stringify(options.body) : void 0,
1370
- signal: controller.signal
1371
- });
1372
- await handleResponseError(response);
1373
- if (response.status === 204) {
1374
- return {};
1375
- }
1376
- return await response.json();
1377
- } catch (error) {
1378
- if (error instanceof exports.SyftHubError) {
1379
- throw error;
1380
- }
1381
- if (error instanceof Error && error.name === "AbortError") {
1382
- throw new exports.APIError("Request timeout", 408);
1383
- }
1384
- throw new exports.APIError(
1385
- `Accounting request failed: ${error instanceof Error ? error.message : "Unknown error"}`,
1386
- 0
1387
- );
1388
- } finally {
1389
- clearTimeout(timeoutId);
1390
- }
1791
+ constructor(http) {
1792
+ this.http = http;
1391
1793
  }
1392
1794
  // ===========================================================================
1393
- // User Operations
1795
+ // Wallet Operations
1394
1796
  // ===========================================================================
1395
1797
  /**
1396
- * Get the current user's account information including balance.
1798
+ * Get the current user's wallet information.
1397
1799
  *
1398
- * @returns AccountingUser with id, email, balance, and organization
1399
- * @throws {AuthenticationError} If authentication fails
1400
- * @throws {APIError} On other errors
1800
+ * @returns WalletInfo with address and existence status
1801
+ * @throws {AuthenticationError} If not authenticated
1401
1802
  *
1402
1803
  * @example
1403
1804
  * ```typescript
1404
- * const user = await accounting.getUser();
1405
- * console.log(`Balance: ${user.balance}`);
1406
- * console.log(`Organization: ${user.organization}`);
1805
+ * const wallet = await client.accounting.getWallet();
1806
+ * if (wallet.exists) {
1807
+ * console.log(`Wallet address: ${wallet.address}`);
1808
+ * } else {
1809
+ * console.log('No wallet configured');
1810
+ * }
1407
1811
  * ```
1408
1812
  */
1409
- async getUser() {
1410
- return this.request("GET", "/user");
1813
+ async getWallet() {
1814
+ return this.http.get("/api/v1/wallet/");
1411
1815
  }
1412
1816
  /**
1413
- * Update the user's password.
1817
+ * Get the current user's wallet balance and recent transactions.
1414
1818
  *
1415
- * @param currentPassword - Current password for verification
1416
- * @param newPassword - New password to set
1417
- * @throws {AuthenticationError} If current password is wrong
1418
- * @throws {ValidationError} If new password doesn't meet requirements
1819
+ * @returns WalletBalance with balance, currency, and recent transactions
1820
+ * @throws {AuthenticationError} If not authenticated
1419
1821
  *
1420
1822
  * @example
1421
1823
  * ```typescript
1422
- * await accounting.updatePassword('old_secret', 'new_secret');
1824
+ * const balance = await client.accounting.getBalance();
1825
+ * console.log(`Balance: ${balance.balance} ${balance.currency}`);
1826
+ * console.log(`Wallet configured: ${balance.wallet_configured}`);
1423
1827
  * ```
1424
1828
  */
1425
- async updatePassword(currentPassword, newPassword) {
1426
- await this.request("PUT", "/user/password", {
1427
- body: {
1428
- oldPassword: currentPassword,
1429
- newPassword
1430
- }
1431
- });
1829
+ async getBalance() {
1830
+ return this.http.get("/api/v1/wallet/balance");
1432
1831
  }
1433
1832
  /**
1434
- * Update the user's organization.
1833
+ * Get the current user's wallet transactions.
1435
1834
  *
1436
- * @param organization - New organization name
1437
- * @throws {AuthenticationError} If authentication fails
1835
+ * @returns Array of WalletTransaction objects
1836
+ * @throws {AuthenticationError} If not authenticated
1438
1837
  *
1439
1838
  * @example
1440
1839
  * ```typescript
1441
- * await accounting.updateOrganization('OpenMined');
1840
+ * const transactions = await client.accounting.getTransactions();
1841
+ * for (const tx of transactions) {
1842
+ * console.log(`${tx.created_at}: ${tx.amount} ${tx.status}`);
1843
+ * }
1442
1844
  * ```
1443
1845
  */
1444
- async updateOrganization(organization) {
1445
- await this.request("PUT", "/user/organization", {
1446
- body: { organization }
1447
- });
1846
+ async getTransactions() {
1847
+ return this.http.get("/api/v1/wallet/transactions");
1448
1848
  }
1449
- // ===========================================================================
1450
- // Transaction Listing
1451
- // ===========================================================================
1452
1849
  /**
1453
- * List account transactions with pagination.
1850
+ * Create a new wallet for the current user.
1454
1851
  *
1455
- * Returns a lazy iterator that fetches pages on demand.
1852
+ * Generates a new wallet with a fresh keypair. The wallet address
1853
+ * is returned and stored on the server.
1456
1854
  *
1457
- * @param options - Pagination options
1458
- * @returns PageIterator that yields Transaction objects
1855
+ * @returns Object with the new wallet address
1856
+ * @throws {AuthenticationError} If not authenticated
1857
+ * @throws {ValidationError} If user already has a wallet
1459
1858
  *
1460
1859
  * @example
1461
1860
  * ```typescript
1462
- * // Iterate through all transactions
1463
- * for await (const tx of accounting.getTransactions()) {
1464
- * console.log(`${tx.createdAt}: ${tx.amount} from ${tx.senderEmail}`);
1465
- * }
1466
- *
1467
- * // Get first page only
1468
- * const firstPage = await accounting.getTransactions().firstPage();
1469
- *
1470
- * // Get all transactions
1471
- * const allTxs = await accounting.getTransactions().all();
1861
+ * const result = await client.accounting.createWallet();
1862
+ * console.log(`New wallet address: ${result.address}`);
1472
1863
  * ```
1473
1864
  */
1474
- getTransactions(options) {
1475
- const pageSize = options?.pageSize ?? 20;
1476
- return new PageIterator(async (skip, limit) => {
1477
- const response = await this.request("GET", "/transactions", {
1478
- params: { skip, limit }
1479
- });
1480
- return response.map(parseTransaction);
1481
- }, pageSize);
1865
+ async createWallet() {
1866
+ return this.http.post("/api/v1/wallet/create", {});
1482
1867
  }
1483
1868
  /**
1484
- * Get a specific transaction by ID.
1869
+ * Import an existing wallet using a private key.
1485
1870
  *
1486
- * @param transactionId - The transaction ID
1487
- * @returns Transaction object
1488
- * @throws {NotFoundError} If transaction not found
1871
+ * @param privateKey - The private key to import
1872
+ * @returns Object with the imported wallet address
1873
+ * @throws {AuthenticationError} If not authenticated
1874
+ * @throws {ValidationError} If the private key is invalid
1489
1875
  *
1490
1876
  * @example
1491
1877
  * ```typescript
1492
- * const tx = await accounting.getTransaction('tx_123');
1493
- * console.log(`Status: ${tx.status}`);
1878
+ * const result = await client.accounting.importWallet('0x...');
1879
+ * console.log(`Imported wallet address: ${result.address}`);
1494
1880
  * ```
1495
1881
  */
1496
- async getTransaction(transactionId) {
1497
- const response = await this.request(
1498
- "GET",
1499
- `/transactions/${transactionId}`
1500
- );
1501
- return parseTransaction(response);
1882
+ async importWallet(privateKey) {
1883
+ return this.http.post("/api/v1/wallet/import", {
1884
+ private_key: privateKey
1885
+ });
1886
+ }
1887
+ };
1888
+ function createAccountingResource(options) {
1889
+ throw new Error(
1890
+ "createAccountingResource() is deprecated. The wallet API is now accessed through the SyftHubClient. Use client.initAccounting() after login instead."
1891
+ );
1892
+ }
1893
+
1894
+ // src/resources/agent.ts
1895
+ init_errors();
1896
+ var AgentSessionError = class extends exports.SyftHubError {
1897
+ constructor(message, code) {
1898
+ super(message);
1899
+ this.code = code;
1900
+ this.name = "AgentSessionError";
1901
+ }
1902
+ };
1903
+ var AgentResource = class {
1904
+ constructor(auth, aggregatorUrl) {
1905
+ this.auth = auth;
1906
+ this.aggregatorUrl = aggregatorUrl;
1502
1907
  }
1503
- // ===========================================================================
1504
- // Direct Transaction Operations
1505
- // ===========================================================================
1506
1908
  /**
1507
- * Create a new transaction (direct transfer).
1909
+ * Start a new agent session.
1508
1910
  *
1509
- * Creates a PENDING transaction that must be confirmed or cancelled.
1510
- * The transaction is created by the sender (current user).
1511
- *
1512
- * @param input - Transaction details
1513
- * @returns Transaction in PENDING status
1514
- * @throws {ValidationError} If amount <= 0 or insufficient balance
1515
- *
1516
- * @example
1517
- * ```typescript
1518
- * const tx = await accounting.createTransaction({
1519
- * recipientEmail: 'bob@example.com',
1520
- * amount: 10.0,
1521
- * appName: 'syftai-space',
1522
- * appEpPath: 'alice/my-model'
1523
- * });
1524
- * console.log(`Created transaction ${tx.id}: ${tx.status}`);
1525
- *
1526
- * // Later, confirm or cancel
1527
- * await accounting.confirmTransaction(tx.id);
1528
- * ```
1911
+ * @param options - Session options including prompt, endpoint, and config
1912
+ * @returns An AgentSessionClient for interacting with the session
1529
1913
  */
1530
- async createTransaction(input) {
1531
- if (input.amount <= 0) {
1532
- throw new exports.ValidationError("Amount must be greater than 0");
1533
- }
1534
- const response = await this.request("POST", "/transactions", {
1535
- body: {
1536
- recipientEmail: input.recipientEmail,
1537
- amount: input.amount,
1538
- ...input.appName && { appName: input.appName },
1539
- ...input.appEpPath && { appEpPath: input.appEpPath }
1914
+ async startSession(options) {
1915
+ let owner;
1916
+ let slug;
1917
+ if (typeof options.endpoint === "string") {
1918
+ const parts = options.endpoint.split("/");
1919
+ if (parts.length !== 2) {
1920
+ throw new AgentSessionError(
1921
+ `Endpoint must be in 'owner/slug' format, got: ${options.endpoint}`
1922
+ );
1923
+ }
1924
+ owner = parts[0];
1925
+ slug = parts[1];
1926
+ } else {
1927
+ owner = options.endpoint.owner;
1928
+ slug = options.endpoint.slug;
1929
+ }
1930
+ const satResponse = await this.auth.getSatelliteToken(owner);
1931
+ const peerResponse = await this.auth.getPeerToken([owner]);
1932
+ const wsUrl = this.aggregatorUrl.replace(/^http/, "ws") + "/agent/session";
1933
+ const ws = new WebSocket(wsUrl);
1934
+ await new Promise((resolve, reject) => {
1935
+ const onOpen = () => {
1936
+ ws.removeEventListener("error", onError);
1937
+ resolve();
1938
+ };
1939
+ const onError = (_e) => {
1940
+ ws.removeEventListener("open", onOpen);
1941
+ reject(new AgentSessionError("Failed to connect to agent WebSocket"));
1942
+ };
1943
+ ws.addEventListener("open", onOpen, { once: true });
1944
+ ws.addEventListener("error", onError, { once: true });
1945
+ if (options.signal) {
1946
+ options.signal.addEventListener(
1947
+ "abort",
1948
+ () => {
1949
+ ws.close();
1950
+ reject(new AgentSessionError("Session start aborted"));
1951
+ },
1952
+ { once: true }
1953
+ );
1540
1954
  }
1541
1955
  });
1542
- return parseTransaction(response);
1543
- }
1544
- /**
1545
- * Confirm a pending transaction.
1546
- *
1547
- * Confirms the transaction, transferring funds from sender to recipient.
1548
- * Can be called by either the sender or recipient.
1549
- *
1550
- * @param transactionId - The transaction ID to confirm
1551
- * @returns Transaction in COMPLETED status
1552
- * @throws {NotFoundError} If transaction not found
1553
- * @throws {ValidationError} If transaction is not in PENDING status
1554
- *
1555
- * @example
1556
- * ```typescript
1557
- * const tx = await accounting.confirmTransaction('tx_123');
1558
- * console.log(`Confirmed: ${tx.status}`); // "completed"
1559
- * ```
1560
- */
1561
- async confirmTransaction(transactionId) {
1562
- const response = await this.request(
1563
- "POST",
1564
- `/transactions/${transactionId}/confirm`
1956
+ const startPayload = {
1957
+ prompt: options.prompt,
1958
+ endpoint: { owner, slug },
1959
+ satellite_token: satResponse.targetToken,
1960
+ peer_token: peerResponse.peerToken,
1961
+ peer_channel: peerResponse.peerChannel
1962
+ };
1963
+ if (options.config) {
1964
+ startPayload.config = options.config;
1965
+ }
1966
+ if (options.messages) {
1967
+ startPayload.messages = options.messages;
1968
+ }
1969
+ ws.send(
1970
+ JSON.stringify({
1971
+ type: "session.start",
1972
+ payload: startPayload
1973
+ })
1565
1974
  );
1566
- return parseTransaction(response);
1975
+ const response = await new Promise((resolve, reject) => {
1976
+ const onMessage = (event) => {
1977
+ try {
1978
+ const data = JSON.parse(event.data);
1979
+ if (data.type === "session.created") {
1980
+ ws.removeEventListener("message", onMessage);
1981
+ resolve({
1982
+ session_id: data.session_id || data.payload?.session_id
1983
+ });
1984
+ } else if (data.type === "agent.error") {
1985
+ ws.removeEventListener("message", onMessage);
1986
+ reject(
1987
+ new AgentSessionError(
1988
+ data.payload?.message || "Session start failed",
1989
+ data.payload?.code
1990
+ )
1991
+ );
1992
+ }
1993
+ } catch {
1994
+ reject(new AgentSessionError("Failed to parse session response"));
1995
+ }
1996
+ };
1997
+ ws.addEventListener("message", onMessage);
1998
+ });
1999
+ return new AgentSessionClient(ws, response.session_id);
2000
+ }
2001
+ };
2002
+ var AgentSessionClient = class {
2003
+ constructor(ws, sessionId) {
2004
+ this.ws = ws;
2005
+ this.sessionId = sessionId;
2006
+ this.ws.addEventListener("message", (event) => {
2007
+ this._handleMessage(event);
2008
+ });
2009
+ this.ws.addEventListener("close", () => {
2010
+ this._handleClose();
2011
+ });
2012
+ this.ws.addEventListener("error", () => {
2013
+ this._state = "error";
2014
+ this._handleClose();
2015
+ });
2016
+ }
2017
+ _state = "running";
2018
+ _sequenceCounter = 0;
2019
+ _messageQueue = [];
2020
+ _messageResolvers = [];
2021
+ _closed = false;
2022
+ /** Current session state */
2023
+ get state() {
2024
+ return this._state;
1567
2025
  }
1568
2026
  /**
1569
- * Cancel a pending transaction.
1570
- *
1571
- * Cancels the transaction without transferring funds.
1572
- * Can be called by either the sender or recipient.
1573
- *
1574
- * @param transactionId - The transaction ID to cancel
1575
- * @returns Transaction in CANCELLED status
1576
- * @throws {NotFoundError} If transaction not found
1577
- * @throws {ValidationError} If transaction is not in PENDING status
2027
+ * Async generator yielding agent events.
1578
2028
  *
1579
2029
  * @example
1580
- * ```typescript
1581
- * const tx = await accounting.cancelTransaction('tx_123');
1582
- * console.log(`Cancelled: ${tx.status}`); // "cancelled"
1583
- * ```
2030
+ * for await (const event of session.events()) {
2031
+ * console.log(event.type, event.payload);
2032
+ * }
1584
2033
  */
1585
- async cancelTransaction(transactionId) {
1586
- const response = await this.request(
1587
- "POST",
1588
- `/transactions/${transactionId}/cancel`
1589
- );
1590
- return parseTransaction(response);
2034
+ async *events() {
2035
+ while (!this._closed) {
2036
+ const event = await this._nextEvent();
2037
+ if (event === null) break;
2038
+ yield event;
2039
+ }
1591
2040
  }
1592
- // ===========================================================================
1593
- // Delegated Transaction Operations
1594
- // ===========================================================================
1595
2041
  /**
1596
- * Create a transaction token for delegated transfers.
1597
- *
1598
- * Creates a JWT token that authorizes the recipient to create a
1599
- * transaction on behalf of the sender (current user). The token
1600
- * is short-lived (typically ~5 minutes).
1601
- *
1602
- * Use this when you want to pre-authorize a payment that will be
1603
- * initiated by the recipient (e.g., a service charging for usage).
1604
- *
1605
- * @param recipientEmail - Email of the authorized recipient
1606
- * @returns JWT token string to share with recipient
1607
- *
1608
- * @example
1609
- * ```typescript
1610
- * // Sender creates token
1611
- * const token = await accounting.createTransactionToken('service@example.com');
2042
+ * Register an event handler.
1612
2043
  *
1613
- * // Share token with recipient out-of-band
1614
- * // Recipient uses token to create delegated transaction
1615
- * ```
2044
+ * @param eventType - The event type to listen for, or '*' for all events
2045
+ * @param handler - Callback function
1616
2046
  */
1617
- async createTransactionToken(recipientEmail) {
1618
- const response = await this.request("POST", "/token/create", {
1619
- body: { recipientEmail }
2047
+ on(eventType, handler) {
2048
+ this.ws.addEventListener("message", (msgEvent) => {
2049
+ try {
2050
+ const data = JSON.parse(msgEvent.data);
2051
+ if (eventType === "*" || data.type === eventType) {
2052
+ handler(data);
2053
+ }
2054
+ } catch {
2055
+ }
1620
2056
  });
1621
- return response.token;
1622
2057
  }
1623
- /**
1624
- * Create a delegated transaction using a pre-authorized token.
1625
- *
1626
- * Creates a transaction on behalf of the sender using their token.
1627
- * This is typically used by services to charge users for usage.
1628
- *
1629
- * The token authenticates the request instead of Basic auth.
1630
- *
1631
- * @param senderEmail - Email of the sender who created the token
1632
- * @param amount - Amount to transfer (must be > 0)
1633
- * @param token - JWT token from sender's createTransactionToken()
1634
- * @returns Transaction in PENDING status (createdBy=RECIPIENT)
1635
- * @throws {AuthenticationError} If token is invalid or expired
1636
- * @throws {ValidationError} If amount <= 0
1637
- *
1638
- * @example
1639
- * ```typescript
1640
- * // Recipient creates transaction using sender's token
1641
- * const tx = await accounting.createDelegatedTransaction(
1642
- * 'alice@example.com',
1643
- * 5.0,
1644
- * aliceToken
1645
- * );
1646
- *
1647
- * // Recipient confirms the transaction
1648
- * await accounting.confirmTransaction(tx.id);
1649
- * ```
1650
- */
1651
- async createDelegatedTransaction(senderEmail, amount, token) {
1652
- if (amount <= 0) {
1653
- throw new exports.ValidationError("Amount must be greater than 0");
2058
+ /** Send a user message to the agent */
2059
+ sendMessage(content) {
2060
+ this._send({
2061
+ type: "user.message",
2062
+ payload: { content }
2063
+ });
2064
+ }
2065
+ /** Confirm a tool call */
2066
+ confirm(toolCallId) {
2067
+ this._send({
2068
+ type: "user.confirm",
2069
+ payload: { tool_call_id: toolCallId }
2070
+ });
2071
+ }
2072
+ /** Deny a tool call */
2073
+ deny(toolCallId, reason) {
2074
+ this._send({
2075
+ type: "user.deny",
2076
+ payload: { tool_call_id: toolCallId, reason }
2077
+ });
2078
+ }
2079
+ /** Cancel the session */
2080
+ cancel() {
2081
+ this._state = "cancelled";
2082
+ this._send({ type: "user.cancel" });
2083
+ }
2084
+ /** Close the session and WebSocket */
2085
+ close() {
2086
+ if (this._closed) return;
2087
+ this._send({ type: "session.close" });
2088
+ this.ws.close();
2089
+ this._handleClose();
2090
+ }
2091
+ // ---- Internal ----
2092
+ _send(msg) {
2093
+ if (this.ws.readyState === WebSocket.OPEN) {
2094
+ this.ws.send(JSON.stringify(msg));
1654
2095
  }
1655
- const response = await this.requestWithToken(
1656
- "POST",
1657
- "/transactions",
1658
- token,
1659
- {
1660
- body: {
1661
- senderEmail,
1662
- amount
1663
- }
2096
+ }
2097
+ _handleMessage(event) {
2098
+ try {
2099
+ const data = JSON.parse(event.data);
2100
+ this._sequenceCounter++;
2101
+ switch (data.type) {
2102
+ case "agent.request_input":
2103
+ this._state = "awaiting_input";
2104
+ break;
2105
+ case "session.completed":
2106
+ this._state = "completed";
2107
+ break;
2108
+ case "session.failed":
2109
+ this._state = "failed";
2110
+ break;
2111
+ case "agent.error":
2112
+ if (!data.payload.recoverable) {
2113
+ this._state = "error";
2114
+ }
2115
+ break;
2116
+ default:
2117
+ if (this._state === "awaiting_input" || this._state === "connecting") {
2118
+ this._state = "running";
2119
+ }
1664
2120
  }
1665
- );
1666
- return parseTransaction(response);
2121
+ if (this._messageResolvers.length > 0) {
2122
+ const resolve = this._messageResolvers.shift();
2123
+ resolve(data);
2124
+ } else {
2125
+ this._messageQueue.push(data);
2126
+ }
2127
+ if (data.type === "session.completed" || data.type === "session.failed") {
2128
+ this._handleClose();
2129
+ }
2130
+ } catch {
2131
+ }
2132
+ }
2133
+ _handleClose() {
2134
+ if (this._closed) return;
2135
+ this._closed = true;
2136
+ for (const resolve of this._messageResolvers) {
2137
+ resolve(null);
2138
+ }
2139
+ this._messageResolvers = [];
2140
+ }
2141
+ _nextEvent() {
2142
+ if (this._messageQueue.length > 0) {
2143
+ return Promise.resolve(this._messageQueue.shift());
2144
+ }
2145
+ if (this._closed) {
2146
+ return Promise.resolve(null);
2147
+ }
2148
+ return new Promise((resolve) => {
2149
+ this._messageResolvers.push(resolve);
2150
+ });
1667
2151
  }
1668
2152
  };
1669
- function createAccountingResource(options) {
1670
- return new AccountingResource(options);
1671
- }
1672
2153
 
1673
2154
  // src/resources/chat.ts
1674
2155
  init_errors();
2156
+
2157
+ // src/models/common.ts
2158
+ var Visibility = {
2159
+ /** Visible to everyone, no authentication required */
2160
+ PUBLIC: "public",
2161
+ /** Only visible to the owner and collaborators */
2162
+ PRIVATE: "private",
2163
+ /** Behaves like private — only visible to the owner */
2164
+ INTERNAL: "internal"
2165
+ };
2166
+ var EndpointType = {
2167
+ /** Machine learning model endpoint */
2168
+ MODEL: "model",
2169
+ /** Data source endpoint */
2170
+ DATA_SOURCE: "data_source",
2171
+ /** Both model and data source endpoint */
2172
+ MODEL_DATA_SOURCE: "model_data_source",
2173
+ /** Agent endpoint with session-based interaction */
2174
+ AGENT: "agent"
2175
+ };
2176
+ var UserRole = {
2177
+ /** Administrator with full access */
2178
+ ADMIN: "admin",
2179
+ /** Regular user */
2180
+ USER: "user",
2181
+ /** Guest user with limited access */
2182
+ GUEST: "guest"
2183
+ };
2184
+
2185
+ // src/models/endpoint.ts
2186
+ function getEndpointPublicPath(endpoint) {
2187
+ return `${endpoint.ownerUsername}/${endpoint.slug}`;
2188
+ }
2189
+
2190
+ // src/resources/chat.ts
1675
2191
  var AggregatorError = class extends exports.SyftHubError {
1676
2192
  constructor(message, status, detail) {
1677
2193
  super(message);
@@ -1687,12 +2203,26 @@ var EndpointResolutionError = class extends exports.SyftHubError {
1687
2203
  this.name = "EndpointResolutionError";
1688
2204
  }
1689
2205
  };
1690
- var ChatResource = class {
2206
+ var ChatResource = class _ChatResource {
1691
2207
  constructor(hub, auth, aggregatorUrl) {
1692
2208
  this.hub = hub;
1693
2209
  this.auth = auth;
1694
2210
  this.aggregatorUrl = aggregatorUrl;
1695
2211
  }
2212
+ /**
2213
+ * Check if an endpoint type matches the expected type.
2214
+ * A model_data_source endpoint matches both 'model' and 'data_source'.
2215
+ */
2216
+ static typeMatches(actualType, expectedType) {
2217
+ if (actualType === expectedType) return true;
2218
+ if (actualType === EndpointType.MODEL_DATA_SOURCE) {
2219
+ return expectedType === EndpointType.MODEL || expectedType === EndpointType.DATA_SOURCE;
2220
+ }
2221
+ if (actualType === EndpointType.AGENT && expectedType === EndpointType.MODEL) {
2222
+ return true;
2223
+ }
2224
+ return false;
2225
+ }
1696
2226
  /**
1697
2227
  * Convert any endpoint format to EndpointRef with URL and owner info.
1698
2228
  * The ownerUsername is critical for satellite token authentication.
@@ -1702,7 +2232,7 @@ var ChatResource = class {
1702
2232
  return endpoint;
1703
2233
  }
1704
2234
  if (this.isEndpointPublic(endpoint)) {
1705
- if (expectedType && endpoint.type !== expectedType) {
2235
+ if (expectedType && !_ChatResource.typeMatches(endpoint.type, expectedType)) {
1706
2236
  throw new Error(
1707
2237
  `Expected endpoint type '${expectedType}', got '${endpoint.type}' for '${endpoint.slug}'`
1708
2238
  );
@@ -1757,12 +2287,15 @@ var ChatResource = class {
1757
2287
  /**
1758
2288
  * Get satellite tokens for all unique endpoint owners.
1759
2289
  * Returns a map of owner username to satellite token.
2290
+ *
2291
+ * @param owners - Array of unique owner usernames
2292
+ * @param guestMode - If true, fetch guest tokens (no auth required)
1760
2293
  */
1761
- async getSatelliteTokensForOwners(owners) {
2294
+ async getSatelliteTokensForOwners(owners, guestMode = false) {
1762
2295
  if (owners.length === 0) {
1763
2296
  return {};
1764
2297
  }
1765
- const tokenMap = await this.auth.getSatelliteTokens(owners);
2298
+ const tokenMap = guestMode ? await this.auth.getGuestSatelliteTokens(owners) : await this.auth.getSatelliteTokens(owners);
1766
2299
  const result = {};
1767
2300
  for (const [owner, token] of tokenMap) {
1768
2301
  result[owner] = token;
@@ -1770,18 +2303,11 @@ var ChatResource = class {
1770
2303
  return result;
1771
2304
  }
1772
2305
  /**
1773
- * Get transaction tokens for all unique endpoint owners.
1774
- * Returns a map of owner username to transaction token.
1775
- *
1776
- * Transaction tokens are used for billing - they authorize the endpoint
1777
- * owner to charge the current user for usage.
2306
+ * Get the user's Hub access token for MPP payment flow.
2307
+ * Returns null if in guest mode or not authenticated.
1778
2308
  */
1779
- async getTransactionTokensForOwners(owners) {
1780
- if (owners.length === 0) {
1781
- return {};
1782
- }
1783
- const response = await this.auth.getTransactionTokens(owners);
1784
- return response.tokens;
2309
+ getUserToken() {
2310
+ return this.auth.getAccessToken();
1785
2311
  }
1786
2312
  /**
1787
2313
  * Type guard for EndpointRef.
@@ -1795,14 +2321,147 @@ var ChatResource = class {
1795
2321
  isEndpointPublic(value) {
1796
2322
  return typeof value === "object" && value !== null && "connect" in value && "ownerUsername" in value && Array.isArray(value.connect);
1797
2323
  }
2324
+ static COLLECTIVE_PREFIX = "collective/";
2325
+ static TUNNELING_PREFIX = "tunneling:";
2326
+ /**
2327
+ * Expand any `collective/<slug>` (or `collective/<slug>/<shared-slug>`)
2328
+ * entries in the data-sources list into the individual `owner/slug` paths
2329
+ * of the collective's approved members.
2330
+ *
2331
+ * Path forms recognised:
2332
+ * - `collective/<slug>` → every approved member (backward-compatible)
2333
+ * - `collective/<slug>/all` → equivalent alias of the above
2334
+ * - `collective/<slug>/<shared-slug>` → the named subset, intersected with
2335
+ * the collective's currently approved members
2336
+ *
2337
+ * Non-collective entries pass through unchanged. String paths are
2338
+ * deduplicated so a regular endpoint that also belongs to a selected
2339
+ * collective is not queried twice.
2340
+ */
2341
+ async expandCollectivePaths(dataSources) {
2342
+ const expanded = [];
2343
+ const seenPaths = /* @__PURE__ */ new Set();
2344
+ for (const ds of dataSources) {
2345
+ if (typeof ds === "string" && ds.startsWith(_ChatResource.COLLECTIVE_PREFIX)) {
2346
+ const rest = ds.slice(_ChatResource.COLLECTIVE_PREFIX.length);
2347
+ const slashAt = rest.indexOf("/");
2348
+ const collectiveSlug = slashAt < 0 ? rest : rest.slice(0, slashAt);
2349
+ const rawShared = slashAt < 0 ? void 0 : rest.slice(slashAt + 1);
2350
+ const sharedSlug = rawShared && rawShared !== "all" ? rawShared : void 0;
2351
+ if (!collectiveSlug) {
2352
+ throw new EndpointResolutionError(`Malformed collective path: ${ds}`, ds);
2353
+ }
2354
+ let memberPaths;
2355
+ try {
2356
+ memberPaths = await this.hub.getCollectiveEndpointPaths(collectiveSlug, sharedSlug);
2357
+ } catch (error) {
2358
+ const target = sharedSlug ? `${collectiveSlug}/${sharedSlug}` : collectiveSlug;
2359
+ throw new EndpointResolutionError(
2360
+ `Failed to resolve collective '${target}': ${error instanceof Error ? error.message : String(error)}`,
2361
+ ds
2362
+ );
2363
+ }
2364
+ for (const path of memberPaths) {
2365
+ if (!seenPaths.has(path)) {
2366
+ seenPaths.add(path);
2367
+ expanded.push(path);
2368
+ }
2369
+ }
2370
+ } else if (typeof ds === "string") {
2371
+ if (!seenPaths.has(ds)) {
2372
+ seenPaths.add(ds);
2373
+ expanded.push(ds);
2374
+ }
2375
+ } else {
2376
+ expanded.push(ds);
2377
+ }
2378
+ }
2379
+ return expanded;
2380
+ }
2381
+ /**
2382
+ * Check if any endpoints use tunneling URLs and extract target usernames.
2383
+ */
2384
+ collectTunnelingUsernames(modelRef, dataSourceRefs) {
2385
+ const usernames = /* @__PURE__ */ new Set();
2386
+ if (modelRef.url.startsWith(_ChatResource.TUNNELING_PREFIX)) {
2387
+ usernames.add(modelRef.url.slice(_ChatResource.TUNNELING_PREFIX.length));
2388
+ }
2389
+ for (const ds of dataSourceRefs) {
2390
+ if (ds.url.startsWith(_ChatResource.TUNNELING_PREFIX)) {
2391
+ usernames.add(ds.url.slice(_ChatResource.TUNNELING_PREFIX.length));
2392
+ }
2393
+ }
2394
+ return [...usernames];
2395
+ }
2396
+ /**
2397
+ * Shared request preparation for complete() and stream().
2398
+ * Resolves endpoints, fetches tokens, and builds the aggregator request body.
2399
+ * Returns the request body and the resolved aggregator URL.
2400
+ */
2401
+ async prepareRequest(options, stream) {
2402
+ const modelRef = await this.resolveEndpointRef(options.model, "model");
2403
+ const expandedDataSources = await this.expandCollectivePaths(options.dataSources ?? []);
2404
+ const dsRefs = [];
2405
+ for (const ds of expandedDataSources) {
2406
+ dsRefs.push(await this.resolveEndpointRef(ds, "data_source"));
2407
+ }
2408
+ const uniqueOwners = this.collectUniqueOwners(modelRef, dsRefs);
2409
+ const guestMode = options.guestMode ?? false;
2410
+ const endpointTokens = await this.getSatelliteTokensForOwners(uniqueOwners, guestMode);
2411
+ const userToken = guestMode ? null : this.getUserToken();
2412
+ let peerToken = options.peerToken;
2413
+ let peerChannel = options.peerChannel;
2414
+ if (!peerToken) {
2415
+ const tunnelingUsernames = this.collectTunnelingUsernames(modelRef, dsRefs);
2416
+ if (tunnelingUsernames.length > 0) {
2417
+ const peerResponse = guestMode ? await this.auth.getGuestPeerToken(tunnelingUsernames) : await this.auth.getPeerToken(tunnelingUsernames);
2418
+ peerToken = peerResponse.peerToken;
2419
+ peerChannel = peerResponse.peerChannel;
2420
+ }
2421
+ }
2422
+ const requestBody = this.buildRequestBody(
2423
+ options.prompt,
2424
+ modelRef,
2425
+ dsRefs,
2426
+ endpointTokens,
2427
+ userToken,
2428
+ {
2429
+ topK: options.topK,
2430
+ maxTokens: options.maxTokens,
2431
+ temperature: options.temperature,
2432
+ similarityThreshold: options.similarityThreshold,
2433
+ stream,
2434
+ messages: options.messages,
2435
+ peerToken,
2436
+ peerChannel
2437
+ }
2438
+ );
2439
+ const effectiveAggregatorUrl = (options.aggregatorUrl ?? this.aggregatorUrl).replace(
2440
+ /\/+$/,
2441
+ ""
2442
+ );
2443
+ return { requestBody, effectiveAggregatorUrl };
2444
+ }
2445
+ /**
2446
+ * Parse an error response from the aggregator into an AggregatorError.
2447
+ */
2448
+ async handleAggregatorErrorResponse(response) {
2449
+ let message = `HTTP ${response.status}`;
2450
+ try {
2451
+ const data = await response.json();
2452
+ message = String(data["message"] ?? data["error"] ?? message);
2453
+ } catch {
2454
+ }
2455
+ throw new AggregatorError(`Aggregator error: ${message}`, response.status);
2456
+ }
1798
2457
  /**
1799
2458
  * Build the request body for the aggregator.
1800
2459
  * Includes endpoint_tokens mapping for satellite token authentication.
1801
- * Includes transaction_tokens mapping for billing authorization.
2460
+ * Includes user_token for MPP payment callback authorization.
1802
2461
  * User identity is derived from satellite tokens, not passed in request body.
1803
2462
  */
1804
- buildRequestBody(prompt, modelRef, dataSourceRefs, endpointTokens, transactionTokens, options) {
1805
- return {
2463
+ buildRequestBody(prompt, modelRef, dataSourceRefs, endpointTokens, userToken, options) {
2464
+ const body = {
1806
2465
  prompt,
1807
2466
  model: {
1808
2467
  url: modelRef.url,
@@ -1819,13 +2478,25 @@ var ChatResource = class {
1819
2478
  owner_username: ds.ownerUsername ?? null
1820
2479
  })),
1821
2480
  endpoint_tokens: endpointTokens,
1822
- transaction_tokens: transactionTokens,
1823
2481
  top_k: options.topK ?? 5,
1824
2482
  max_tokens: options.maxTokens ?? 1024,
1825
2483
  temperature: options.temperature ?? 0.7,
1826
2484
  similarity_threshold: options.similarityThreshold ?? 0.5,
1827
2485
  stream: options.stream ?? false
1828
2486
  };
2487
+ if (userToken) {
2488
+ body["user_token"] = userToken;
2489
+ }
2490
+ if (options.messages && options.messages.length > 0) {
2491
+ body.messages = options.messages.map((m) => ({ role: m.role, content: m.content }));
2492
+ }
2493
+ if (options.peerToken) {
2494
+ body["peer_token"] = options.peerToken;
2495
+ }
2496
+ if (options.peerChannel) {
2497
+ body["peer_channel"] = options.peerChannel;
2498
+ }
2499
+ return body;
1829
2500
  }
1830
2501
  /**
1831
2502
  * Parse a SourceInfo from raw data.
@@ -1897,7 +2568,7 @@ var ChatResource = class {
1897
2568
  * This method automatically:
1898
2569
  * 1. Resolves endpoints and extracts owner information
1899
2570
  * 2. Exchanges Hub tokens for satellite tokens (one per unique owner)
1900
- * 3. Fetches transaction tokens for billing authorization
2571
+ * 3. Passes the user's Hub access token for MPP payment authorization
1901
2572
  * 4. Sends tokens to the aggregator for forwarding to SyftAI-Space
1902
2573
  *
1903
2574
  * @param options - Chat completion options
@@ -1906,48 +2577,14 @@ var ChatResource = class {
1906
2577
  * @throws {AggregatorError} If aggregator service fails
1907
2578
  */
1908
2579
  async complete(options) {
1909
- const modelRef = await this.resolveEndpointRef(options.model, "model");
1910
- const dsRefs = [];
1911
- for (const ds of options.dataSources ?? []) {
1912
- dsRefs.push(await this.resolveEndpointRef(ds, "data_source"));
1913
- }
1914
- const uniqueOwners = this.collectUniqueOwners(modelRef, dsRefs);
1915
- const endpointTokens = await this.getSatelliteTokensForOwners(uniqueOwners);
1916
- const transactionTokens = await this.getTransactionTokensForOwners(uniqueOwners);
1917
- const requestBody = this.buildRequestBody(
1918
- options.prompt,
1919
- modelRef,
1920
- dsRefs,
1921
- endpointTokens,
1922
- transactionTokens,
1923
- {
1924
- topK: options.topK,
1925
- maxTokens: options.maxTokens,
1926
- temperature: options.temperature,
1927
- similarityThreshold: options.similarityThreshold,
1928
- stream: false
1929
- }
1930
- );
1931
- const effectiveAggregatorUrl = (options.aggregatorUrl ?? this.aggregatorUrl).replace(
1932
- /\/+$/,
1933
- ""
1934
- );
1935
- const url = `${effectiveAggregatorUrl}/chat`;
1936
- const response = await fetch(url, {
2580
+ const { requestBody, effectiveAggregatorUrl } = await this.prepareRequest(options, false);
2581
+ const response = await fetch(`${effectiveAggregatorUrl}/chat`, {
1937
2582
  method: "POST",
1938
- headers: {
1939
- "Content-Type": "application/json"
1940
- },
2583
+ headers: { "Content-Type": "application/json" },
1941
2584
  body: JSON.stringify(requestBody)
1942
2585
  });
1943
2586
  if (!response.ok) {
1944
- let message = `HTTP ${response.status}`;
1945
- try {
1946
- const data2 = await response.json();
1947
- message = String(data2["message"] ?? data2["error"] ?? message);
1948
- } catch {
1949
- }
1950
- throw new AggregatorError(`Aggregator error: ${message}`, response.status);
2587
+ return this.handleAggregatorErrorResponse(response);
1951
2588
  }
1952
2589
  const data = await response.json();
1953
2590
  const sourcesData = data["sources"];
@@ -1958,12 +2595,14 @@ var ChatResource = class {
1958
2595
  const metadata = this.parseMetadata(metadataData ?? {});
1959
2596
  const usageData = data["usage"];
1960
2597
  const usage = usageData ? this.parseUsage(usageData) : void 0;
2598
+ const profitShare = data["profit_share"];
1961
2599
  return {
1962
2600
  response: String(data["response"] ?? ""),
1963
2601
  sources,
1964
2602
  retrievalInfo,
1965
2603
  metadata,
1966
- usage
2604
+ usage,
2605
+ profitShare
1967
2606
  };
1968
2607
  }
1969
2608
  /**
@@ -1972,41 +2611,15 @@ var ChatResource = class {
1972
2611
  * This method automatically:
1973
2612
  * 1. Resolves endpoints and extracts owner information
1974
2613
  * 2. Exchanges Hub tokens for satellite tokens (one per unique owner)
1975
- * 3. Fetches transaction tokens for billing authorization
2614
+ * 3. Passes the user's Hub access token for MPP payment authorization
1976
2615
  * 4. Sends tokens to the aggregator for forwarding to SyftAI-Space
1977
2616
  *
1978
2617
  * @param options - Chat completion options
1979
2618
  * @yields ChatStreamEvent objects as they arrive
1980
2619
  */
1981
2620
  async *stream(options) {
1982
- const modelRef = await this.resolveEndpointRef(options.model, "model");
1983
- const dsRefs = [];
1984
- for (const ds of options.dataSources ?? []) {
1985
- dsRefs.push(await this.resolveEndpointRef(ds, "data_source"));
1986
- }
1987
- const uniqueOwners = this.collectUniqueOwners(modelRef, dsRefs);
1988
- const endpointTokens = await this.getSatelliteTokensForOwners(uniqueOwners);
1989
- const transactionTokens = await this.getTransactionTokensForOwners(uniqueOwners);
1990
- const requestBody = this.buildRequestBody(
1991
- options.prompt,
1992
- modelRef,
1993
- dsRefs,
1994
- endpointTokens,
1995
- transactionTokens,
1996
- {
1997
- topK: options.topK,
1998
- maxTokens: options.maxTokens,
1999
- temperature: options.temperature,
2000
- similarityThreshold: options.similarityThreshold,
2001
- stream: true
2002
- }
2003
- );
2004
- const effectiveAggregatorUrl = (options.aggregatorUrl ?? this.aggregatorUrl).replace(
2005
- /\/+$/,
2006
- ""
2007
- );
2008
- const url = `${effectiveAggregatorUrl}/chat/stream`;
2009
- const response = await fetch(url, {
2621
+ const { requestBody, effectiveAggregatorUrl } = await this.prepareRequest(options, true);
2622
+ const response = await fetch(`${effectiveAggregatorUrl}/chat/stream`, {
2010
2623
  method: "POST",
2011
2624
  headers: {
2012
2625
  "Content-Type": "application/json",
@@ -2016,55 +2629,22 @@ var ChatResource = class {
2016
2629
  signal: options.signal
2017
2630
  });
2018
2631
  if (!response.ok) {
2019
- let message = `HTTP ${response.status}`;
2020
- try {
2021
- const data = await response.json();
2022
- message = String(data["message"] ?? data["error"] ?? message);
2023
- } catch {
2024
- }
2025
- throw new AggregatorError(`Aggregator error: ${message}`, response.status);
2632
+ return this.handleAggregatorErrorResponse(response);
2026
2633
  }
2027
2634
  if (!response.body) {
2028
2635
  throw new AggregatorError("No response body from aggregator");
2029
2636
  }
2030
- const reader = response.body.getReader();
2031
- const decoder = new TextDecoder();
2032
- let buffer = "";
2033
- let currentEvent = null;
2034
- let currentData = "";
2035
- try {
2036
- while (true) {
2037
- const { done, value } = await reader.read();
2038
- if (done) break;
2039
- buffer += decoder.decode(value, { stream: true });
2040
- const lines = buffer.split("\n");
2041
- buffer = lines.pop() ?? "";
2042
- for (const line of lines) {
2043
- const trimmedLine = line.trim();
2044
- if (!trimmedLine) {
2045
- if (currentEvent && currentData) {
2046
- try {
2047
- const data = JSON.parse(currentData);
2048
- const event = this.parseSSEEvent(currentEvent, data);
2049
- if (event) {
2050
- yield event;
2051
- }
2052
- } catch {
2053
- }
2054
- }
2055
- currentEvent = null;
2056
- currentData = "";
2057
- continue;
2058
- }
2059
- if (trimmedLine.startsWith("event:")) {
2060
- currentEvent = trimmedLine.slice(6).trim();
2061
- } else if (trimmedLine.startsWith("data:")) {
2062
- currentData = trimmedLine.slice(5).trim();
2063
- }
2637
+ for await (const { event: eventName, data: dataStr } of readSSEEvents(response)) {
2638
+ if (eventName === "message") continue;
2639
+ try {
2640
+ const data = JSON.parse(dataStr);
2641
+ const event = this.parseSSEEvent(eventName, data);
2642
+ if (event) {
2643
+ yield event;
2064
2644
  }
2645
+ } catch {
2646
+ yield { type: "error", message: `Failed to parse SSE data: ${dataStr}` };
2065
2647
  }
2066
- } finally {
2067
- reader.releaseLock();
2068
2648
  }
2069
2649
  }
2070
2650
  /**
@@ -2090,8 +2670,24 @@ var ChatResource = class {
2090
2670
  totalDocuments: Number(data["total_documents"] ?? 0),
2091
2671
  timeMs: Number(data["time_ms"] ?? 0)
2092
2672
  };
2673
+ case "reranking_start":
2674
+ return {
2675
+ type: "reranking_start",
2676
+ documents: Number(data["documents"] ?? 0)
2677
+ };
2678
+ case "reranking_complete":
2679
+ return {
2680
+ type: "reranking_complete",
2681
+ documents: Number(data["documents"] ?? 0),
2682
+ timeMs: Number(data["time_ms"] ?? 0)
2683
+ };
2093
2684
  case "generation_start":
2094
2685
  return { type: "generation_start" };
2686
+ case "generation_heartbeat":
2687
+ return {
2688
+ type: "generation_heartbeat",
2689
+ elapsedMs: Number(data["elapsed_ms"] ?? 0)
2690
+ };
2095
2691
  case "token":
2096
2692
  return {
2097
2693
  type: "token",
@@ -2106,7 +2702,9 @@ var ChatResource = class {
2106
2702
  const metadata = this.parseMetadata(metadataData ?? {});
2107
2703
  const usageData = data["usage"];
2108
2704
  const usage = usageData ? this.parseUsage(usageData) : void 0;
2109
- return { type: "done", sources, retrievalInfo, metadata, usage };
2705
+ const profitShare = data["profit_share"];
2706
+ const response = data["response"];
2707
+ return { type: "done", sources, retrievalInfo, metadata, usage, profitShare, response };
2110
2708
  }
2111
2709
  case "error":
2112
2710
  return {
@@ -2114,30 +2712,33 @@ var ChatResource = class {
2114
2712
  message: String(data["message"] ?? "Unknown error")
2115
2713
  };
2116
2714
  default:
2715
+ console.warn(`[SyftHub] Unknown SSE event type received from aggregator: ${eventType}`);
2117
2716
  return {
2118
2717
  type: "error",
2119
2718
  message: `Unknown event type: ${eventType}`
2120
2719
  };
2121
2720
  }
2122
2721
  }
2123
- /**
2124
- * Get model endpoints that have connection URLs configured.
2125
- *
2126
- * @param limit - Maximum number of results (default: 20)
2127
- * @returns Array of EndpointPublic objects for models with URLs
2128
- */
2129
- async getAvailableModels(limit = 20) {
2722
+ async getAvailableEndpoints(endpointType, limit) {
2130
2723
  const results = [];
2131
2724
  for await (const endpoint of this.hub.browse()) {
2132
2725
  if (results.length >= limit) break;
2133
- if (endpoint.type !== EndpointType.MODEL) continue;
2134
- const hasUrl = endpoint.connect.some((conn) => conn.enabled && conn.config["url"]);
2135
- if (hasUrl) {
2726
+ if (endpoint.type !== endpointType) continue;
2727
+ if (endpoint.connect.some((conn) => conn.enabled && conn.config["url"])) {
2136
2728
  results.push(endpoint);
2137
2729
  }
2138
2730
  }
2139
2731
  return results;
2140
2732
  }
2733
+ /**
2734
+ * Get model endpoints that have connection URLs configured.
2735
+ *
2736
+ * @param limit - Maximum number of results (default: 20)
2737
+ * @returns Array of EndpointPublic objects for models with URLs
2738
+ */
2739
+ async getAvailableModels(limit = 20) {
2740
+ return this.getAvailableEndpoints(EndpointType.MODEL, limit);
2741
+ }
2141
2742
  /**
2142
2743
  * Get data source endpoints that have connection URLs configured.
2143
2744
  *
@@ -2145,16 +2746,7 @@ var ChatResource = class {
2145
2746
  * @returns Array of EndpointPublic objects for data sources with URLs
2146
2747
  */
2147
2748
  async getAvailableDataSources(limit = 20) {
2148
- const results = [];
2149
- for await (const endpoint of this.hub.browse()) {
2150
- if (results.length >= limit) break;
2151
- if (endpoint.type !== EndpointType.DATA_SOURCE) continue;
2152
- const hasUrl = endpoint.connect.some((conn) => conn.enabled && conn.config["url"]);
2153
- if (hasUrl) {
2154
- results.push(endpoint);
2155
- }
2156
- }
2157
- return results;
2749
+ return this.getAvailableEndpoints(EndpointType.DATA_SOURCE, limit);
2158
2750
  }
2159
2751
  };
2160
2752
 
@@ -2340,45 +2932,22 @@ var SyftAIResource = class {
2340
2932
  if (!response.body) {
2341
2933
  throw new GenerationError("No response body from model", endpoint.slug);
2342
2934
  }
2343
- const reader = response.body.getReader();
2344
- const decoder = new TextDecoder();
2345
- let buffer = "";
2346
- try {
2347
- while (true) {
2348
- const { done, value } = await reader.read();
2349
- if (done) break;
2350
- buffer += decoder.decode(value, { stream: true });
2351
- const lines = buffer.split("\n");
2352
- buffer = lines.pop() ?? "";
2353
- for (const line of lines) {
2354
- const trimmedLine = line.trim();
2355
- if (!trimmedLine || trimmedLine.startsWith("event:")) {
2356
- continue;
2357
- }
2358
- if (trimmedLine.startsWith("data:")) {
2359
- const dataStr = trimmedLine.slice(5).trim();
2360
- if (dataStr === "[DONE]") {
2361
- return;
2362
- }
2363
- try {
2364
- const data = JSON.parse(dataStr);
2365
- if (typeof data["content"] === "string") {
2366
- yield data["content"];
2367
- } else if (Array.isArray(data["choices"])) {
2368
- for (const choice of data["choices"]) {
2369
- const delta = choice["delta"];
2370
- if (delta && typeof delta["content"] === "string") {
2371
- yield delta["content"];
2372
- }
2373
- }
2374
- }
2375
- } catch {
2935
+ for await (const { data: dataStr } of readSSEEvents(response)) {
2936
+ if (dataStr === "[DONE]") return;
2937
+ try {
2938
+ const data = JSON.parse(dataStr);
2939
+ if (typeof data["content"] === "string") {
2940
+ yield data["content"];
2941
+ } else if (Array.isArray(data["choices"])) {
2942
+ for (const choice of data["choices"]) {
2943
+ const delta = choice["delta"];
2944
+ if (delta && typeof delta["content"] === "string") {
2945
+ yield delta["content"];
2376
2946
  }
2377
2947
  }
2378
2948
  }
2949
+ } catch {
2379
2950
  }
2380
- } finally {
2381
- reader.releaseLock();
2382
2951
  }
2383
2952
  }
2384
2953
  };
@@ -2395,7 +2964,6 @@ function isBrowser() {
2395
2964
  }
2396
2965
  var SyftHubClient = class {
2397
2966
  http;
2398
- options;
2399
2967
  aggregatorUrl;
2400
2968
  // Lazy-initialized resources
2401
2969
  _auth;
@@ -2403,9 +2971,10 @@ var SyftHubClient = class {
2403
2971
  _myEndpoints;
2404
2972
  _hub;
2405
2973
  _accounting;
2406
- _accountingInitPromise = null;
2974
+ _agent;
2407
2975
  _chat;
2408
2976
  _syftai;
2977
+ _apiTokens;
2409
2978
  /**
2410
2979
  * Create a new SyftHub client.
2411
2980
  *
@@ -2413,7 +2982,6 @@ var SyftHubClient = class {
2413
2982
  * @throws {SyftHubError} If baseUrl is not provided and SYFTHUB_URL is not set (in non-browser environments)
2414
2983
  */
2415
2984
  constructor(options = {}) {
2416
- this.options = options;
2417
2985
  let baseUrl = options.baseUrl ?? getEnv("SYFTHUB_URL");
2418
2986
  if (!baseUrl && !isBrowser()) {
2419
2987
  throw new exports.SyftHubError(
@@ -2424,6 +2992,10 @@ var SyftHubClient = class {
2424
2992
  const normalizedUrl = baseUrl ? baseUrl.replace(/\/+$/, "") : "";
2425
2993
  this.http = new HTTPClient(normalizedUrl, options.timeout ?? 3e4);
2426
2994
  this.aggregatorUrl = options.aggregatorUrl ?? getEnv("SYFTHUB_AGGREGATOR_URL") ?? `${normalizedUrl}/aggregator/api/v1`;
2995
+ const apiToken = options.apiToken ?? getEnv("SYFTHUB_API_TOKEN");
2996
+ if (apiToken) {
2997
+ this.http.setApiToken(apiToken);
2998
+ }
2427
2999
  }
2428
3000
  /**
2429
3001
  * Authentication resource for login, register, and session management.
@@ -2479,13 +3051,33 @@ var SyftHubClient = class {
2479
3051
  return this._hub;
2480
3052
  }
2481
3053
  /**
2482
- * Accounting resource for billing and transactions.
3054
+ * Agent resource for bidirectional agent sessions via WebSocket.
3055
+ *
3056
+ * @example
3057
+ * const session = await client.agent.startSession({
3058
+ * prompt: 'Help me refactor this code',
3059
+ * endpoint: 'alice/code-assistant',
3060
+ * });
3061
+ *
3062
+ * for await (const event of session.events()) {
3063
+ * console.log(event.type, event.payload);
3064
+ * }
3065
+ */
3066
+ get agent() {
3067
+ if (!this._agent) {
3068
+ this._agent = new AgentResource(this.auth, this.aggregatorUrl);
3069
+ }
3070
+ return this._agent;
3071
+ }
3072
+ /**
3073
+ * Accounting resource for wallet and payment operations.
2483
3074
  *
2484
- * The accounting service is external and uses separate credentials
2485
- * (email/password Basic auth) from SyftHub's JWT authentication.
3075
+ * Provides access to MPP wallet management (balance, transactions,
3076
+ * wallet creation/import). Uses the same SyftHub JWT authentication
3077
+ * as other resources.
2486
3078
  *
2487
- * Credentials are automatically retrieved from the backend after login.
2488
- * You must call `initAccounting()` after login to initialize this resource.
3079
+ * You must call `initAccounting()` after login to initialize this resource,
3080
+ * or access it directly if already initialized.
2489
3081
  *
2490
3082
  * @throws {AuthenticationError} If not initialized
2491
3083
  *
@@ -2495,7 +3087,8 @@ var SyftHubClient = class {
2495
3087
  * await client.initAccounting();
2496
3088
  *
2497
3089
  * // Now accounting is available
2498
- * const user = await client.accounting.getUser();
3090
+ * const wallet = await client.accounting.getWallet();
3091
+ * const balance = await client.accounting.getBalance();
2499
3092
  */
2500
3093
  get accounting() {
2501
3094
  if (this._accounting) {
@@ -2506,14 +3099,13 @@ var SyftHubClient = class {
2506
3099
  );
2507
3100
  }
2508
3101
  /**
2509
- * Initialize accounting resource by fetching credentials from the backend.
3102
+ * Initialize the accounting (wallet) resource.
2510
3103
  *
2511
- * This method retrieves accounting credentials from the SyftHub backend
2512
- * and initializes the accounting resource. Requires authentication.
3104
+ * The wallet API uses the same SyftHub authentication as other resources.
3105
+ * This method simply verifies authentication and creates the resource.
2513
3106
  *
2514
3107
  * @returns The initialized AccountingResource
2515
3108
  * @throws {AuthenticationError} If not authenticated
2516
- * @throws {ConfigurationError} If user has no accounting service configured
2517
3109
  *
2518
3110
  * @example
2519
3111
  * // Login first, then initialize accounting
@@ -2521,49 +3113,20 @@ var SyftHubClient = class {
2521
3113
  * await client.initAccounting();
2522
3114
  *
2523
3115
  * // Now accounting is available
2524
- * const user = await client.accounting.getUser();
3116
+ * const wallet = await client.accounting.getWallet();
3117
+ * const balance = await client.accounting.getBalance();
2525
3118
  */
2526
3119
  async initAccounting() {
2527
3120
  if (this._accounting) {
2528
3121
  return this._accounting;
2529
3122
  }
2530
- if (this._accountingInitPromise) {
2531
- return this._accountingInitPromise;
2532
- }
2533
- this._accountingInitPromise = this._doInitAccounting();
2534
- try {
2535
- this._accounting = await this._accountingInitPromise;
2536
- return this._accounting;
2537
- } finally {
2538
- this._accountingInitPromise = null;
2539
- }
2540
- }
2541
- /**
2542
- * Internal method to perform accounting initialization.
2543
- */
2544
- async _doInitAccounting() {
2545
3123
  if (!this.isAuthenticated) {
2546
3124
  throw new exports.AuthenticationError(
2547
3125
  "Must be logged in to use accounting. Call client.auth.login() first."
2548
3126
  );
2549
3127
  }
2550
- const creds = await this.users.getAccountingCredentials();
2551
- if (!creds.url) {
2552
- throw new exports.ConfigurationError(
2553
- "No accounting service configured for this user. Contact your administrator to set up accounting."
2554
- );
2555
- }
2556
- if (!creds.password) {
2557
- throw new exports.ConfigurationError(
2558
- "Accounting password not available. This may indicate an issue with your account setup."
2559
- );
2560
- }
2561
- return new AccountingResource({
2562
- url: creds.url,
2563
- email: creds.email,
2564
- password: creds.password,
2565
- timeout: this.options.timeout
2566
- });
3128
+ this._accounting = new AccountingResource(this.http);
3129
+ return this._accounting;
2567
3130
  }
2568
3131
  /**
2569
3132
  * Chat resource for RAG-augmented conversations via the Aggregator.
@@ -2627,6 +3190,40 @@ var SyftHubClient = class {
2627
3190
  }
2628
3191
  return this._syftai;
2629
3192
  }
3193
+ /**
3194
+ * API Tokens resource for managing personal access tokens.
3195
+ *
3196
+ * API tokens provide an alternative to username/password authentication.
3197
+ * They are ideal for CI/CD pipelines, scripts, and programmatic access.
3198
+ *
3199
+ * @example
3200
+ * // Create a new token
3201
+ * const result = await client.apiTokens.create({
3202
+ * name: 'CI/CD Pipeline',
3203
+ * scopes: ['write'],
3204
+ * });
3205
+ * console.log('Save this token:', result.token);
3206
+ *
3207
+ * // List all tokens
3208
+ * const { tokens } = await client.apiTokens.list();
3209
+ *
3210
+ * // Revoke a token
3211
+ * await client.apiTokens.revoke(tokenId);
3212
+ */
3213
+ get apiTokens() {
3214
+ if (!this._apiTokens) {
3215
+ this._apiTokens = new APITokensResource(this.http);
3216
+ }
3217
+ return this._apiTokens;
3218
+ }
3219
+ /**
3220
+ * Check if the client is using API token authentication.
3221
+ *
3222
+ * @returns True if authenticated with an API token (vs JWT)
3223
+ */
3224
+ get isUsingApiToken() {
3225
+ return this.http.isUsingApiToken();
3226
+ }
2630
3227
  /**
2631
3228
  * Get current authentication tokens.
2632
3229
  *
@@ -2668,7 +3265,7 @@ var SyftHubClient = class {
2668
3265
  return this.http.hasTokens();
2669
3266
  }
2670
3267
  /**
2671
- * Check if accounting has been initialized.
3268
+ * Check if the accounting (wallet) resource has been initialized.
2672
3269
  *
2673
3270
  * Use this to check if accounting is available before accessing
2674
3271
  * the `accounting` property, which will throw if not initialized.
@@ -2677,7 +3274,7 @@ var SyftHubClient = class {
2677
3274
  *
2678
3275
  * @example
2679
3276
  * if (client.isAccountingInitialized) {
2680
- * const user = await client.accounting.getUser();
3277
+ * const wallet = await client.accounting.getWallet();
2681
3278
  * }
2682
3279
  */
2683
3280
  get isAccountingInitialized() {
@@ -2695,27 +3292,24 @@ var SyftHubClient = class {
2695
3292
  // src/index.ts
2696
3293
  init_errors();
2697
3294
 
3295
+ exports.APITokensResource = APITokensResource;
2698
3296
  exports.AccountingResource = AccountingResource;
3297
+ exports.AgentResource = AgentResource;
3298
+ exports.AgentSessionClient = AgentSessionClient;
3299
+ exports.AgentSessionError = AgentSessionError;
2699
3300
  exports.AggregatorError = AggregatorError;
3301
+ exports.AggregatorsResource = AggregatorsResource;
2700
3302
  exports.ChatResource = ChatResource;
2701
- exports.CreatorType = CreatorType;
2702
3303
  exports.EndpointResolutionError = EndpointResolutionError;
2703
3304
  exports.EndpointType = EndpointType;
2704
3305
  exports.GenerationError = GenerationError;
2705
- exports.OrganizationRole = OrganizationRole;
2706
3306
  exports.PageIterator = PageIterator;
2707
3307
  exports.RetrievalError = RetrievalError;
2708
3308
  exports.SyftAIResource = SyftAIResource;
2709
3309
  exports.SyftHubClient = SyftHubClient;
2710
- exports.TransactionStatus = TransactionStatus;
2711
3310
  exports.UserRole = UserRole;
2712
3311
  exports.Visibility = Visibility;
2713
3312
  exports.createAccountingResource = createAccountingResource;
2714
- exports.getEndpointOwnerType = getEndpointOwnerType;
2715
3313
  exports.getEndpointPublicPath = getEndpointPublicPath;
2716
- exports.isTransactionCancelled = isTransactionCancelled;
2717
- exports.isTransactionCompleted = isTransactionCompleted;
2718
- exports.isTransactionPending = isTransactionPending;
2719
- exports.parseTransaction = parseTransaction;
2720
3314
  //# sourceMappingURL=index.cjs.map
2721
3315
  //# sourceMappingURL=index.cjs.map