@syfthub/sdk 0.1.1 → 0.2.1

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