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