hypha-rpc 0.20.61 → 0.20.64

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.
@@ -356,20 +356,23 @@ class RPC extends _utils_index_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
356
356
  if (!this._silent && this._connection.manager_id) {
357
357
  console.debug("Connection established, reporting services...");
358
358
  try {
359
- const manager = await this.get_manager_service({
360
- timeout: 10,
361
- case_conversion: "camel",
362
- });
359
+ // Retry getting manager service with exponential backoff
360
+ const manager = await this._get_manager_with_retry();
363
361
  const services = Object.values(this._services);
364
362
  const servicesCount = services.length;
365
363
  let registeredCount = 0;
364
+ const failedServices = [];
366
365
 
367
366
  for (let service of services) {
368
367
  try {
369
368
  const serviceInfo = this._extract_service_info(service);
370
369
  await manager.registerService(serviceInfo);
371
370
  registeredCount++;
371
+ console.debug(
372
+ `Successfully registered service: ${service.id || "unknown"}`,
373
+ );
372
374
  } catch (serviceError) {
375
+ failedServices.push(service.id || "unknown");
373
376
  console.error(
374
377
  `Failed to register service ${service.id || "unknown"}: ${serviceError}`,
375
378
  );
@@ -382,13 +385,25 @@ class RPC extends _utils_index_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
382
385
  );
383
386
  } else {
384
387
  console.warn(
385
- `Only registered ${registeredCount} out of ${servicesCount} services with the server`,
388
+ `Only registered ${registeredCount} out of ${servicesCount} services with the server. Failed services: ${failedServices.join(", ")}`,
386
389
  );
387
390
  }
391
+
392
+ // Fire event with registration status
393
+ this._fire("services_registered", {
394
+ total: servicesCount,
395
+ registered: registeredCount,
396
+ failed: failedServices,
397
+ });
388
398
  } catch (managerError) {
389
399
  console.error(
390
400
  `Failed to get manager service for registering services: ${managerError}`,
391
401
  );
402
+ // Fire event with error status
403
+ this._fire("services_registration_failed", {
404
+ error: managerError.toString(),
405
+ total_services: Object.keys(this._services).length,
406
+ });
392
407
  }
393
408
  } else {
394
409
  // console.debug("Connection established", connectionInfo);
@@ -428,6 +443,10 @@ class RPC extends _utils_index_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
428
443
  }
429
444
 
430
445
  async _ping(msg, context) {
446
+ // Handle the new kwargs format with _rkwargs flag
447
+ if (typeof context === "object" && context && context._rkwargs) {
448
+ context = context.context;
449
+ }
431
450
  (0,_utils_index_js__WEBPACK_IMPORTED_MODULE_0__.assert)(msg == "ping");
432
451
  return "pong";
433
452
  }
@@ -444,6 +463,10 @@ class RPC extends _utils_index_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
444
463
  }
445
464
 
446
465
  _create_message(key, heartbeat, overwrite, context) {
466
+ // Handle the new kwargs format with _rkwargs flag
467
+ if (typeof context === "object" && context && context._rkwargs) {
468
+ context = context.context;
469
+ }
447
470
  if (heartbeat) {
448
471
  if (!this._object_store[key]) {
449
472
  throw new Error(`session does not exist anymore: ${key}`);
@@ -463,6 +486,10 @@ class RPC extends _utils_index_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
463
486
  }
464
487
 
465
488
  _append_message(key, data, heartbeat, context) {
489
+ // Handle the new kwargs format with _rkwargs flag
490
+ if (typeof context === "object" && context && context._rkwargs) {
491
+ context = context.context;
492
+ }
466
493
  if (heartbeat) {
467
494
  if (!this._object_store[key]) {
468
495
  throw new Error(`session does not exist anymore: ${key}`);
@@ -478,6 +505,10 @@ class RPC extends _utils_index_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
478
505
  }
479
506
 
480
507
  _set_message(key, index, data, heartbeat, context) {
508
+ // Handle the new kwargs format with _rkwargs flag
509
+ if (typeof context === "object" && context && context._rkwargs) {
510
+ context = context.context;
511
+ }
481
512
  if (heartbeat) {
482
513
  if (!this._object_store[key]) {
483
514
  throw new Error(`session does not exist anymore: ${key}`);
@@ -493,6 +524,10 @@ class RPC extends _utils_index_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
493
524
  }
494
525
 
495
526
  _remove_message(key, context) {
527
+ // Handle the new kwargs format with _rkwargs flag
528
+ if (typeof context === "object" && context && context._rkwargs) {
529
+ context = context.context;
530
+ }
496
531
  const cache = this._object_store["message_cache"];
497
532
  if (!cache[key]) {
498
533
  throw new Error(`Message with key ${key} does not exists.`);
@@ -501,6 +536,10 @@ class RPC extends _utils_index_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
501
536
  }
502
537
 
503
538
  _process_message(key, heartbeat, context) {
539
+ // Handle the new kwargs format with _rkwargs flag
540
+ if (typeof context === "object" && context && context._rkwargs) {
541
+ context = context.context;
542
+ }
504
543
  if (heartbeat) {
505
544
  if (!this._object_store[key]) {
506
545
  throw new Error(`session does not exist anymore: ${key}`);
@@ -587,6 +626,35 @@ class RPC extends _utils_index_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
587
626
  await this._connection.disconnect();
588
627
  }
589
628
 
629
+ async _get_manager_with_retry(maxRetries = 20) {
630
+ const baseDelay = 500;
631
+ const maxDelay = 10000;
632
+ let lastError = null;
633
+
634
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
635
+ try {
636
+ const svc = await this.get_remote_service(
637
+ `*/${this._connection.manager_id}:default`,
638
+ { timeout: 20, case_conversion: "camel" },
639
+ );
640
+ return svc;
641
+ } catch (e) {
642
+ lastError = e;
643
+ console.warn(
644
+ `Failed to get manager service (attempt ${attempt + 1}/${maxRetries}): ${e.message}`,
645
+ );
646
+ if (attempt < maxRetries - 1) {
647
+ // Exponential backoff with maximum delay
648
+ const delay = Math.min(baseDelay * Math.pow(2, attempt), maxDelay);
649
+ await new Promise((resolve) => setTimeout(resolve, delay));
650
+ }
651
+ }
652
+ }
653
+
654
+ // If we get here, all retries failed
655
+ throw lastError;
656
+ }
657
+
590
658
  async get_manager_service(config) {
591
659
  config = config || {};
592
660
 
@@ -630,6 +698,10 @@ class RPC extends _utils_index_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
630
698
  return this._services;
631
699
  }
632
700
  get_local_service(service_id, context) {
701
+ // Handle the new kwargs format with _rkwargs flag
702
+ if (typeof context === "object" && context && context._rkwargs) {
703
+ context = context.context;
704
+ }
633
705
  (0,_utils_index_js__WEBPACK_IMPORTED_MODULE_0__.assert)(service_id);
634
706
  const [ws, client_id] = context["to"].split("/");
635
707
  (0,_utils_index_js__WEBPACK_IMPORTED_MODULE_0__.assert)(
@@ -1092,9 +1164,9 @@ class RPC extends _utils_index_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
1092
1164
 
1093
1165
  const taskFn = async () => {
1094
1166
  await message_cache.set(message_id, idx, chunk, !!session_id);
1095
- console.debug(
1096
- `Sending chunk ${idx + 1}/${chunk_num} (total=${total_size} bytes)`,
1097
- );
1167
+ // console.debug(
1168
+ // `Sending chunk ${idx + 1}/${chunk_num} (total=${total_size} bytes)`,
1169
+ // );
1098
1170
  };
1099
1171
 
1100
1172
  // Push into an array, each one runs under the semaphore
@@ -1125,14 +1197,14 @@ class RPC extends _utils_index_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
1125
1197
  startByte + this._long_message_chunk_size,
1126
1198
  );
1127
1199
  await message_cache.append(message_id, chunk, !!session_id);
1128
- console.debug(
1129
- `Sending chunk ${idx + 1}/${chunk_num} (total=${total_size} bytes)`,
1130
- );
1200
+ // console.debug(
1201
+ // `Sending chunk ${idx + 1}/${chunk_num} (total=${total_size} bytes)`,
1202
+ // );
1131
1203
  }
1132
1204
  }
1133
1205
  await message_cache.process(message_id, !!session_id);
1134
1206
  const durationSec = ((Date.now() - start_time) / 1000).toFixed(2);
1135
- console.debug(`All chunks (${total_size} bytes) sent in ${durationSec} s`);
1207
+ // console.debug(`All chunks (${total_size} bytes) sent in ${durationSec} s`);
1136
1208
  }
1137
1209
 
1138
1210
  emit(main_message, extra_data) {
@@ -1513,34 +1585,30 @@ class RPC extends _utils_index_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
1513
1585
  } else {
1514
1586
  args = [];
1515
1587
  }
1588
+ let kwargs = {};
1589
+ if (data.with_kwargs) {
1590
+ kwargs = args.pop();
1591
+ }
1592
+
1516
1593
  if (
1517
1594
  this._method_annotations.has(method) &&
1518
1595
  this._method_annotations.get(method).require_context
1519
1596
  ) {
1520
- // Check if this is a remote service (from external clients)
1521
- // Remote services start with a session ID or client ID, not "services."
1522
- const isRemoteService = !data.method.startsWith("services.");
1523
-
1524
- if (isRemoteService) {
1525
- // For remote services (external client services), the method.length reflects
1526
- // the original signature and cannot be modified. We inject context as the last argument
1527
- // and skip the strict argument validation that fails for external services.
1528
- args.push(data.ctx);
1529
- } else {
1530
- // For local services, use the existing logic with padding and validation
1531
- // if args.length + 1 is less than the required number of arguments we will pad with undefined
1532
- // so we make sure the last argument is the context
1533
- if (args.length + 1 < method.length) {
1534
- for (let i = args.length; i < method.length - 1; i++) {
1535
- args.push(undefined);
1536
- }
1537
- }
1538
- args.push(data.ctx);
1539
- (0,_utils_index_js__WEBPACK_IMPORTED_MODULE_0__.assert)(
1540
- args.length === method.length,
1541
- `Runtime Error: Invalid number of arguments for method ${method_name}, expected ${method.length} but got ${args.length}`,
1542
- );
1597
+ // Always use kwargs approach for consistency
1598
+ kwargs.context = data.ctx;
1599
+ kwargs._rkwargs = true;
1600
+
1601
+ // CRITICAL FIX: Ensure kwargs goes to the correct position
1602
+ // For a function like multiply_context(a, b, kwargs), we need to ensure
1603
+ // that kwargs is always the last parameter, even if fewer args are provided
1604
+ const expectedParamCount = method.length;
1605
+
1606
+ // Pad args array to the expected parameter count (minus 1 for kwargs)
1607
+ while (args.length < expectedParamCount - 1) {
1608
+ args.push(undefined);
1543
1609
  }
1610
+
1611
+ args.push(kwargs);
1544
1612
  }
1545
1613
  // console.debug(`Executing method: ${method_name} (${data.method})`);
1546
1614
  if (data.promise) {
@@ -1560,6 +1628,7 @@ class RPC extends _utils_index_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
1560
1628
  clearInterval(heartbeat_task);
1561
1629
  }
1562
1630
  } else {
1631
+ // console.log(`DEBUG: About to call method (no promise) ${data.method} with args:`, args);
1563
1632
  method.apply(null, args);
1564
1633
  clearInterval(heartbeat_task);
1565
1634
  }
@@ -5422,8 +5491,19 @@ class WebsocketRPCConnection {
5422
5491
  event.code,
5423
5492
  event.reason,
5424
5493
  );
5494
+
5425
5495
  let retry = 0;
5496
+ const baseDelay = 1000; // Start with 1 second
5497
+ const maxDelay = 60000; // Maximum delay of 60 seconds
5498
+ const maxJitter = 0.1; // Maximum jitter factor
5499
+
5426
5500
  const reconnect = async () => {
5501
+ // Check if we were explicitly closed
5502
+ if (this._closed) {
5503
+ console.info("Connection was closed, stopping reconnection");
5504
+ return;
5505
+ }
5506
+
5427
5507
  try {
5428
5508
  console.warn(
5429
5509
  `Reconnecting to ${this._server_url.split("?")[0]} (attempt #${retry})`,
@@ -5445,6 +5525,10 @@ class WebsocketRPCConnection {
5445
5525
  console.warn(
5446
5526
  `Successfully reconnected to server ${this._server_url} (services re-registered)`,
5447
5527
  );
5528
+ // Emit reconnection success event
5529
+ if (this._handle_connected) {
5530
+ this._handle_connected(this.connection_info);
5531
+ }
5448
5532
  } catch (e) {
5449
5533
  if (`${e}`.includes("ConnectionAbortedError:")) {
5450
5534
  console.warn("Failed to reconnect, connection aborted:", e);
@@ -5455,26 +5539,74 @@ class WebsocketRPCConnection {
5455
5539
  );
5456
5540
  return;
5457
5541
  }
5542
+
5543
+ // Log specific error types for better debugging
5544
+ if (e.name === "NetworkError" || e.message.includes("network")) {
5545
+ console.error(`Network error during reconnection: ${e.message}`);
5546
+ } else if (
5547
+ e.name === "TimeoutError" ||
5548
+ e.message.includes("timeout")
5549
+ ) {
5550
+ console.error(
5551
+ `Connection timeout during reconnection: ${e.message}`,
5552
+ );
5553
+ } else {
5554
+ console.error(
5555
+ `Unexpected error during reconnection: ${e.message}`,
5556
+ );
5557
+ }
5558
+
5559
+ // Calculate exponential backoff with jitter
5560
+ const delay = Math.min(baseDelay * Math.pow(2, retry), maxDelay);
5561
+ // Add jitter to prevent thundering herd
5562
+ const jitter = (Math.random() * 2 - 1) * maxJitter * delay;
5563
+ const finalDelay = Math.max(100, delay + jitter);
5564
+
5565
+ console.debug(
5566
+ `Waiting ${(finalDelay / 1000).toFixed(2)}s before next reconnection attempt`,
5567
+ );
5568
+
5458
5569
  // Track the reconnection timeout to prevent leaks
5459
5570
  const timeoutId = setTimeout(async () => {
5460
5571
  this._reconnect_timeouts.delete(timeoutId);
5572
+
5573
+ // Check if connection was restored externally
5461
5574
  if (
5462
5575
  this._websocket &&
5463
- this._websocket.readyState === WebSocket.CONNECTED
5576
+ this._websocket.readyState === WebSocket.OPEN
5464
5577
  ) {
5578
+ console.info("Connection restored externally");
5579
+ return;
5580
+ }
5581
+
5582
+ // Check if we were explicitly closed
5583
+ if (this._closed) {
5584
+ console.info("Connection was closed, stopping reconnection");
5465
5585
  return;
5466
5586
  }
5587
+
5467
5588
  retry += 1;
5468
5589
  if (retry < MAX_RETRY) {
5469
5590
  await reconnect();
5470
5591
  } else {
5471
5592
  console.error(
5472
- "Failed to reconnect after",
5473
- MAX_RETRY,
5474
- "attempts",
5593
+ `Failed to reconnect after ${MAX_RETRY} attempts, giving up. Exiting process.`,
5475
5594
  );
5595
+ // Notify about max retry exceeded
5596
+ if (this._handle_disconnected) {
5597
+ this._handle_disconnected(
5598
+ "Max reconnection attempts exceeded",
5599
+ );
5600
+ }
5601
+ // Exit process to prevent stuck event loop
5602
+ if (typeof process !== "undefined" && process.exit) {
5603
+ console.error(
5604
+ "Forcing process exit due to unrecoverable connection failure",
5605
+ );
5606
+ process.exit(1);
5607
+ }
5476
5608
  }
5477
- }, 1000);
5609
+ }, finalDelay);
5478
5610
  this._reconnect_timeouts.add(timeoutId);
5479
5611
  }
5480
5612
  };