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