@syfthub/sdk 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -604,11 +604,7 @@ var AuthResource = class {
604
604
  if (!tokens) {
605
605
  throw new AuthenticationError("No refresh token available");
606
606
  }
607
- const response = await this.http.post(
608
- "/api/v1/auth/refresh",
609
- { refreshToken: tokens.refreshToken },
610
- { includeAuth: false }
611
- );
607
+ const response = await this.http.post("/api/v1/auth/refresh", { refreshToken: tokens.refreshToken }, { includeAuth: false });
612
608
  this.http.setTokens(response.accessToken, response.refreshToken);
613
609
  }
614
610
  /**
@@ -885,22 +881,6 @@ var MyEndpointsResource = class {
885
881
  }
886
882
  return [parts[0], parts[1]];
887
883
  }
888
- /**
889
- * Resolve an endpoint path to its ID.
890
- *
891
- * @param path - Endpoint path in "owner/slug" format
892
- * @returns The endpoint ID
893
- */
894
- async resolveEndpointId(path) {
895
- const [owner, slug] = this.parsePath(path);
896
- const response = await this.http.get(`/${owner}/${slug}`);
897
- if (response.id === void 0) {
898
- throw new Error(
899
- `Could not resolve endpoint ID for '${path}'. Make sure you own this endpoint.`
900
- );
901
- }
902
- return response.id;
903
- }
904
884
  /**
905
885
  * List the current user's endpoints.
906
886
  *
@@ -941,8 +921,17 @@ var MyEndpointsResource = class {
941
921
  * @throws {AuthorizationError} If not authorized to view
942
922
  */
943
923
  async get(path) {
944
- const [owner, slug] = this.parsePath(path);
945
- return this.http.get(`/${owner}/${slug}`);
924
+ const [, slug] = this.parsePath(path);
925
+ const endpoints = await this.http.get("/api/v1/endpoints", { limit: 100 });
926
+ for (const ep of endpoints) {
927
+ if (ep.slug === slug) {
928
+ return ep;
929
+ }
930
+ }
931
+ const { NotFoundError: NotFoundError2 } = await Promise.resolve().then(() => (init_errors(), errors_exports));
932
+ throw new NotFoundError2(
933
+ `Endpoint not found: '${path}'. No endpoint found with slug '${slug}' in your endpoints.`
934
+ );
946
935
  }
947
936
  /**
948
937
  * Update an endpoint.
@@ -957,8 +946,8 @@ var MyEndpointsResource = class {
957
946
  * @throws {AuthorizationError} If not owner/admin
958
947
  */
959
948
  async update(path, input) {
960
- const endpointId = await this.resolveEndpointId(path);
961
- return this.http.patch(`/api/v1/endpoints/${endpointId}`, input);
949
+ const [, slug] = this.parsePath(path);
950
+ return this.http.patch(`/api/v1/endpoints/slug/${slug}`, input);
962
951
  }
963
952
  /**
964
953
  * Delete an endpoint.
@@ -969,8 +958,44 @@ var MyEndpointsResource = class {
969
958
  * @throws {AuthorizationError} If not owner/admin
970
959
  */
971
960
  async delete(path) {
972
- const endpointId = await this.resolveEndpointId(path);
973
- await this.http.delete(`/api/v1/endpoints/${endpointId}`);
961
+ const [, slug] = this.parsePath(path);
962
+ await this.http.delete(`/api/v1/endpoints/slug/${slug}`);
963
+ }
964
+ /**
965
+ * Synchronize user's endpoints with provided list.
966
+ *
967
+ * This is a DESTRUCTIVE operation that:
968
+ * 1. Deletes ALL existing endpoints owned by the current user
969
+ * 2. Creates ALL endpoints from the provided list
970
+ * 3. Is ATOMIC: either all endpoints sync successfully, or none do
971
+ *
972
+ * Important Notes:
973
+ * - Organization endpoints are NOT affected
974
+ * - Stars on existing endpoints will be lost (reset to 0)
975
+ * - Endpoint IDs will change (new IDs assigned)
976
+ * - Maximum 100 endpoints per sync request
977
+ *
978
+ * @param endpoints - List of endpoint specifications to sync.
979
+ * Pass an empty array to delete ALL user endpoints.
980
+ * @returns SyncEndpointsResponse with synced count, deleted count, and created endpoints
981
+ * @throws {AuthenticationError} If not authenticated
982
+ * @throws {ValidationError} If any endpoint fails validation (entire batch rejected)
983
+ *
984
+ * @example
985
+ * // Sync with new endpoints
986
+ * const result = await client.myEndpoints.sync([
987
+ * { name: 'Model A', type: 'model', visibility: 'public' },
988
+ * { name: 'Data Source B', type: 'data_source', visibility: 'private' },
989
+ * ]);
990
+ * console.log(`Deleted ${result.deleted}, created ${result.synced} endpoints`);
991
+ *
992
+ * @example
993
+ * // Clear all endpoints
994
+ * const result = await client.myEndpoints.sync([]);
995
+ * console.log(`Deleted ${result.deleted} endpoints`);
996
+ */
997
+ async sync(endpoints = []) {
998
+ return this.http.post("/api/v1/endpoints/sync", { endpoints });
974
999
  }
975
1000
  };
976
1001
 
@@ -1045,12 +1070,61 @@ var HubResource = class {
1045
1070
  if (options?.minStars !== void 0) {
1046
1071
  params["minStars"] = options.minStars;
1047
1072
  }
1048
- return this.http.get(
1049
- "/api/v1/endpoints/trending",
1050
- params,
1073
+ return this.http.get("/api/v1/endpoints/trending", params, {
1074
+ includeAuth: false
1075
+ });
1076
+ }, pageSize);
1077
+ }
1078
+ /**
1079
+ * Search for endpoints using semantic search.
1080
+ *
1081
+ * Uses RAG-powered semantic search to find endpoints that match the
1082
+ * natural language query. Returns results sorted by relevance score.
1083
+ *
1084
+ * Note: If RAG is not configured on the server (no OpenAI API key),
1085
+ * this method returns an empty array.
1086
+ *
1087
+ * @param query - Natural language search query (e.g., "machine learning models for image classification")
1088
+ * @param options - Search options (topK, type filter, minScore)
1089
+ * @returns Promise resolving to array of EndpointSearchResult with relevance scores.
1090
+ * Returns empty array if query is too short (<3 chars) or no matches found.
1091
+ *
1092
+ * @example
1093
+ * // Search for machine learning models
1094
+ * const results = await client.hub.search('image classification models');
1095
+ * for (const result of results) {
1096
+ * console.log(`${result.ownerUsername}/${result.slug}: ${result.relevanceScore.toFixed(2)}`);
1097
+ * }
1098
+ *
1099
+ * @example
1100
+ * // Filter by type and minimum score
1101
+ * const dataSources = await client.hub.search('customer data', {
1102
+ * type: EndpointType.DATA_SOURCE,
1103
+ * minScore: 0.5,
1104
+ * });
1105
+ */
1106
+ async search(query, options = {}) {
1107
+ const { topK = 10, type, minScore = 0 } = options;
1108
+ if (!query || query.trim().length < 3) {
1109
+ return [];
1110
+ }
1111
+ const body = {
1112
+ query: query.trim(),
1113
+ top_k: topK
1114
+ };
1115
+ if (type !== void 0) {
1116
+ body["type"] = type;
1117
+ }
1118
+ try {
1119
+ const response = await this.http.post(
1120
+ "/api/v1/endpoints/search",
1121
+ body,
1051
1122
  { includeAuth: false }
1052
1123
  );
1053
- }, pageSize);
1124
+ return (response.results ?? []).filter((result) => result.relevanceScore >= minScore);
1125
+ } catch {
1126
+ return [];
1127
+ }
1054
1128
  }
1055
1129
  /**
1056
1130
  * Get an endpoint by its path.
@@ -1083,7 +1157,7 @@ var HubResource = class {
1083
1157
  */
1084
1158
  async star(path) {
1085
1159
  const endpointId = await this.resolveEndpointId(path);
1086
- await this.http.patch(`/api/v1/endpoints/${endpointId}/star`);
1160
+ await this.http.post(`/api/v1/endpoints/${endpointId}/star`);
1087
1161
  }
1088
1162
  /**
1089
1163
  * Unstar an endpoint.
@@ -1094,7 +1168,7 @@ var HubResource = class {
1094
1168
  */
1095
1169
  async unstar(path) {
1096
1170
  const endpointId = await this.resolveEndpointId(path);
1097
- await this.http.patch(`/api/v1/endpoints/${endpointId}/unstar`);
1171
+ await this.http.delete(`/api/v1/endpoints/${endpointId}/star`);
1098
1172
  }
1099
1173
  /**
1100
1174
  * Check if you have starred an endpoint.
@@ -1248,9 +1322,9 @@ var AccountingResource = class {
1248
1322
  const response = await fetch(url.toString(), {
1249
1323
  method,
1250
1324
  headers: {
1251
- "Authorization": this.authHeader,
1325
+ Authorization: this.authHeader,
1252
1326
  "Content-Type": "application/json",
1253
- "Accept": "application/json"
1327
+ Accept: "application/json"
1254
1328
  },
1255
1329
  body: options?.body ? JSON.stringify(options.body) : void 0,
1256
1330
  signal: controller.signal
@@ -1267,7 +1341,10 @@ var AccountingResource = class {
1267
1341
  if (error instanceof Error && error.name === "AbortError") {
1268
1342
  throw new APIError("Request timeout", 408);
1269
1343
  }
1270
- throw new APIError(`Accounting request failed: ${error instanceof Error ? error.message : "Unknown error"}`, 0);
1344
+ throw new APIError(
1345
+ `Accounting request failed: ${error instanceof Error ? error.message : "Unknown error"}`,
1346
+ 0
1347
+ );
1271
1348
  } finally {
1272
1349
  clearTimeout(timeoutId);
1273
1350
  }
@@ -1283,9 +1360,9 @@ var AccountingResource = class {
1283
1360
  const response = await fetch(url.toString(), {
1284
1361
  method,
1285
1362
  headers: {
1286
- "Authorization": `Bearer ${token}`,
1363
+ Authorization: `Bearer ${token}`,
1287
1364
  "Content-Type": "application/json",
1288
- "Accept": "application/json"
1365
+ Accept: "application/json"
1289
1366
  },
1290
1367
  body: options?.body ? JSON.stringify(options.body) : void 0,
1291
1368
  signal: controller.signal
@@ -1302,7 +1379,10 @@ var AccountingResource = class {
1302
1379
  if (error instanceof Error && error.name === "AbortError") {
1303
1380
  throw new APIError("Request timeout", 408);
1304
1381
  }
1305
- throw new APIError(`Accounting request failed: ${error instanceof Error ? error.message : "Unknown error"}`, 0);
1382
+ throw new APIError(
1383
+ `Accounting request failed: ${error instanceof Error ? error.message : "Unknown error"}`,
1384
+ 0
1385
+ );
1306
1386
  } finally {
1307
1387
  clearTimeout(timeoutId);
1308
1388
  }
@@ -1391,15 +1471,12 @@ var AccountingResource = class {
1391
1471
  */
1392
1472
  getTransactions(options) {
1393
1473
  const pageSize = options?.pageSize ?? 20;
1394
- return new PageIterator(
1395
- async (skip, limit) => {
1396
- const response = await this.request("GET", "/transactions", {
1397
- params: { skip, limit }
1398
- });
1399
- return response.map(parseTransaction);
1400
- },
1401
- pageSize
1402
- );
1474
+ return new PageIterator(async (skip, limit) => {
1475
+ const response = await this.request("GET", "/transactions", {
1476
+ params: { skip, limit }
1477
+ });
1478
+ return response.map(parseTransaction);
1479
+ }, pageSize);
1403
1480
  }
1404
1481
  /**
1405
1482
  * Get a specific transaction by ID.
@@ -1849,7 +1926,11 @@ var ChatResource = class {
1849
1926
  stream: false
1850
1927
  }
1851
1928
  );
1852
- const url = `${this.aggregatorUrl}/chat`;
1929
+ const effectiveAggregatorUrl = (options.aggregatorUrl ?? this.aggregatorUrl).replace(
1930
+ /\/+$/,
1931
+ ""
1932
+ );
1933
+ const url = `${effectiveAggregatorUrl}/chat`;
1853
1934
  const response = await fetch(url, {
1854
1935
  method: "POST",
1855
1936
  headers: {
@@ -1918,7 +1999,11 @@ var ChatResource = class {
1918
1999
  stream: true
1919
2000
  }
1920
2001
  );
1921
- const url = `${this.aggregatorUrl}/chat/stream`;
2002
+ const effectiveAggregatorUrl = (options.aggregatorUrl ?? this.aggregatorUrl).replace(
2003
+ /\/+$/,
2004
+ ""
2005
+ );
2006
+ const url = `${effectiveAggregatorUrl}/chat/stream`;
1922
2007
  const response = await fetch(url, {
1923
2008
  method: "POST",
1924
2009
  headers: {
@@ -2044,9 +2129,7 @@ var ChatResource = class {
2044
2129
  for await (const endpoint of this.hub.browse()) {
2045
2130
  if (results.length >= limit) break;
2046
2131
  if (endpoint.type !== EndpointType.MODEL) continue;
2047
- const hasUrl = endpoint.connect.some(
2048
- (conn) => conn.enabled && conn.config["url"]
2049
- );
2132
+ const hasUrl = endpoint.connect.some((conn) => conn.enabled && conn.config["url"]);
2050
2133
  if (hasUrl) {
2051
2134
  results.push(endpoint);
2052
2135
  }
@@ -2064,9 +2147,7 @@ var ChatResource = class {
2064
2147
  for await (const endpoint of this.hub.browse()) {
2065
2148
  if (results.length >= limit) break;
2066
2149
  if (endpoint.type !== EndpointType.DATA_SOURCE) continue;
2067
- const hasUrl = endpoint.connect.some(
2068
- (conn) => conn.enabled && conn.config["url"]
2069
- );
2150
+ const hasUrl = endpoint.connect.some((conn) => conn.enabled && conn.config["url"]);
2070
2151
  if (hasUrl) {
2071
2152
  results.push(endpoint);
2072
2153
  }
@@ -2320,6 +2401,7 @@ var SyftHubClient = class {
2320
2401
  _myEndpoints;
2321
2402
  _hub;
2322
2403
  _accounting;
2404
+ _accountingInitPromise = null;
2323
2405
  _chat;
2324
2406
  _syftai;
2325
2407
  /**
@@ -2400,44 +2482,86 @@ var SyftHubClient = class {
2400
2482
  * The accounting service is external and uses separate credentials
2401
2483
  * (email/password Basic auth) from SyftHub's JWT authentication.
2402
2484
  *
2403
- * Credentials can be provided via:
2404
- * - Constructor options: accountingUrl, accountingEmail, accountingPassword
2405
- * - Environment variables: SYFTHUB_ACCOUNTING_URL, SYFTHUB_ACCOUNTING_EMAIL, SYFTHUB_ACCOUNTING_PASSWORD
2485
+ * Credentials are automatically retrieved from the backend after login.
2486
+ * You must call `initAccounting()` after login to initialize this resource.
2406
2487
  *
2407
- * @throws {SyftHubError} If accounting credentials are not configured
2488
+ * @throws {AuthenticationError} If not initialized
2408
2489
  *
2409
2490
  * @example
2410
- * const user = await client.accounting.getUser();
2411
- * console.log(`Balance: ${user.balance}`);
2491
+ * // Login first, then initialize accounting
2492
+ * await client.auth.login('alice', 'password');
2493
+ * await client.initAccounting();
2412
2494
  *
2413
- * // Create a transaction
2414
- * const tx = await client.accounting.createTransaction({
2415
- * recipientEmail: 'bob@example.com',
2416
- * amount: 10.0
2417
- * });
2495
+ * // Now accounting is available
2496
+ * const user = await client.accounting.getUser();
2418
2497
  */
2419
2498
  get accounting() {
2420
- if (!this._accounting) {
2421
- const url = this.options.accountingUrl ?? getEnv("SYFTHUB_ACCOUNTING_URL");
2422
- const email = this.options.accountingEmail ?? getEnv("SYFTHUB_ACCOUNTING_EMAIL");
2423
- const password = this.options.accountingPassword ?? getEnv("SYFTHUB_ACCOUNTING_PASSWORD");
2424
- if (!url || !email || !password) {
2425
- const missing = [];
2426
- if (!url) missing.push("SYFTHUB_ACCOUNTING_URL");
2427
- if (!email) missing.push("SYFTHUB_ACCOUNTING_EMAIL");
2428
- if (!password) missing.push("SYFTHUB_ACCOUNTING_PASSWORD");
2429
- throw new ConfigurationError(
2430
- `Accounting not configured. Missing: ${missing.join(", ")}. Set environment variables or pass credentials to SyftHubClient.`
2431
- );
2432
- }
2433
- this._accounting = new AccountingResource({
2434
- url,
2435
- email,
2436
- password,
2437
- timeout: this.options.timeout
2438
- });
2499
+ if (this._accounting) {
2500
+ return this._accounting;
2501
+ }
2502
+ throw new AuthenticationError(
2503
+ "Accounting not initialized. Call `await client.initAccounting()` after login."
2504
+ );
2505
+ }
2506
+ /**
2507
+ * Initialize accounting resource by fetching credentials from the backend.
2508
+ *
2509
+ * This method retrieves accounting credentials from the SyftHub backend
2510
+ * and initializes the accounting resource. Requires authentication.
2511
+ *
2512
+ * @returns The initialized AccountingResource
2513
+ * @throws {AuthenticationError} If not authenticated
2514
+ * @throws {ConfigurationError} If user has no accounting service configured
2515
+ *
2516
+ * @example
2517
+ * // Login first, then initialize accounting
2518
+ * await client.auth.login('alice', 'password');
2519
+ * await client.initAccounting();
2520
+ *
2521
+ * // Now accounting is available
2522
+ * const user = await client.accounting.getUser();
2523
+ */
2524
+ async initAccounting() {
2525
+ if (this._accounting) {
2526
+ return this._accounting;
2527
+ }
2528
+ if (this._accountingInitPromise) {
2529
+ return this._accountingInitPromise;
2530
+ }
2531
+ this._accountingInitPromise = this._doInitAccounting();
2532
+ try {
2533
+ this._accounting = await this._accountingInitPromise;
2534
+ return this._accounting;
2535
+ } finally {
2536
+ this._accountingInitPromise = null;
2439
2537
  }
2440
- return this._accounting;
2538
+ }
2539
+ /**
2540
+ * Internal method to perform accounting initialization.
2541
+ */
2542
+ async _doInitAccounting() {
2543
+ if (!this.isAuthenticated) {
2544
+ throw new AuthenticationError(
2545
+ "Must be logged in to use accounting. Call client.auth.login() first."
2546
+ );
2547
+ }
2548
+ const creds = await this.users.getAccountingCredentials();
2549
+ if (!creds.url) {
2550
+ throw new ConfigurationError(
2551
+ "No accounting service configured for this user. Contact your administrator to set up accounting."
2552
+ );
2553
+ }
2554
+ if (!creds.password) {
2555
+ throw new ConfigurationError(
2556
+ "Accounting password not available. This may indicate an issue with your account setup."
2557
+ );
2558
+ }
2559
+ return new AccountingResource({
2560
+ url: creds.url,
2561
+ email: creds.email,
2562
+ password: creds.password,
2563
+ timeout: this.options.timeout
2564
+ });
2441
2565
  }
2442
2566
  /**
2443
2567
  * Chat resource for RAG-augmented conversations via the Aggregator.
@@ -2467,11 +2591,7 @@ var SyftHubClient = class {
2467
2591
  */
2468
2592
  get chat() {
2469
2593
  if (!this._chat) {
2470
- this._chat = new ChatResource(
2471
- this.hub,
2472
- this.auth,
2473
- this.aggregatorUrl
2474
- );
2594
+ this._chat = new ChatResource(this.hub, this.auth, this.aggregatorUrl);
2475
2595
  }
2476
2596
  return this._chat;
2477
2597
  }
@@ -2546,23 +2666,20 @@ var SyftHubClient = class {
2546
2666
  return this.http.hasTokens();
2547
2667
  }
2548
2668
  /**
2549
- * Check if accounting service is configured.
2669
+ * Check if accounting has been initialized.
2550
2670
  *
2551
- * Use this to check if accounting credentials are available before
2552
- * accessing the `accounting` property, which will throw if not configured.
2671
+ * Use this to check if accounting is available before accessing
2672
+ * the `accounting` property, which will throw if not initialized.
2553
2673
  *
2554
- * @returns True if accounting url, email, and password are all configured
2674
+ * @returns True if accounting has been initialized via `initAccounting()`
2555
2675
  *
2556
2676
  * @example
2557
- * if (client.isAccountingConfigured) {
2677
+ * if (client.isAccountingInitialized) {
2558
2678
  * const user = await client.accounting.getUser();
2559
2679
  * }
2560
2680
  */
2561
- get isAccountingConfigured() {
2562
- const url = this.options.accountingUrl ?? getEnv("SYFTHUB_ACCOUNTING_URL");
2563
- const email = this.options.accountingEmail ?? getEnv("SYFTHUB_ACCOUNTING_EMAIL");
2564
- const password = this.options.accountingPassword ?? getEnv("SYFTHUB_ACCOUNTING_PASSWORD");
2565
- return Boolean(url && email && password);
2681
+ get isAccountingInitialized() {
2682
+ return this._accounting !== void 0 && this._accounting !== null;
2566
2683
  }
2567
2684
  /**
2568
2685
  * Close the client and clean up resources.