@multi-agent-protocol/sdk 0.0.3 → 0.0.4

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 CHANGED
@@ -227,9 +227,6 @@ function isDirectAddress(address) {
227
227
  function isFederatedAddress(address) {
228
228
  return typeof address === "object" && "system" in address && "agent" in address;
229
229
  }
230
- function isScopeAddress(address) {
231
- return typeof address === "object" && "scope" in address;
232
- }
233
230
  function isBroadcastAddress(address) {
234
231
  return typeof address === "object" && "broadcast" in address;
235
232
  }
@@ -719,6 +716,30 @@ function websocketStream(ws) {
719
716
  });
720
717
  return { readable, writable };
721
718
  }
719
+ function waitForOpen(ws, timeoutMs = 1e4) {
720
+ return new Promise((resolve, reject) => {
721
+ if (ws.readyState === WebSocket.OPEN) {
722
+ resolve();
723
+ return;
724
+ }
725
+ const timeout = setTimeout(() => {
726
+ ws.close();
727
+ reject(new Error(`WebSocket connection timeout after ${timeoutMs}ms`));
728
+ }, timeoutMs);
729
+ const onOpen = () => {
730
+ clearTimeout(timeout);
731
+ ws.removeEventListener("error", onError);
732
+ resolve();
733
+ };
734
+ const onError = () => {
735
+ clearTimeout(timeout);
736
+ ws.removeEventListener("open", onOpen);
737
+ reject(new Error("WebSocket connection failed"));
738
+ };
739
+ ws.addEventListener("open", onOpen, { once: true });
740
+ ws.addEventListener("error", onError, { once: true });
741
+ });
742
+ }
722
743
  function createStreamPair() {
723
744
  const clientToServer = [];
724
745
  const serverToClient = [];
@@ -1525,9 +1546,16 @@ var CausalEventBuffer = class {
1525
1546
  this.#options = {
1526
1547
  maxWaitTime: options.maxWaitTime ?? 5e3,
1527
1548
  maxBufferSize: options.maxBufferSize ?? 1e3,
1549
+ multiCauseMode: options.multiCauseMode ?? "all",
1528
1550
  onForcedRelease: options.onForcedRelease
1529
1551
  };
1530
1552
  }
1553
+ /**
1554
+ * Get the current multi-cause mode.
1555
+ */
1556
+ get multiCauseMode() {
1557
+ return this.#options.multiCauseMode;
1558
+ }
1531
1559
  /**
1532
1560
  * Push an event into the buffer.
1533
1561
  *
@@ -1543,13 +1571,14 @@ var CausalEventBuffer = class {
1543
1571
  if (!event.receivedAt) {
1544
1572
  event = { ...event, receivedAt: Date.now() };
1545
1573
  }
1546
- const missingPredecessors = this.#getMissingPredecessors(event);
1547
- if (missingPredecessors.length === 0) {
1574
+ const shouldRelease = this.#shouldReleaseEvent(event);
1575
+ if (shouldRelease) {
1548
1576
  ready.push(event);
1549
1577
  this.#releaseWaiting(event.eventId, ready);
1550
1578
  } else {
1551
1579
  this.#pending.set(event.eventId, event);
1552
- for (const predecessorId of missingPredecessors) {
1580
+ const predecessors = event.causedBy ?? [];
1581
+ for (const predecessorId of predecessors) {
1553
1582
  if (!this.#waitingFor.has(predecessorId)) {
1554
1583
  this.#waitingFor.set(predecessorId, /* @__PURE__ */ new Set());
1555
1584
  }
@@ -1608,6 +1637,20 @@ var CausalEventBuffer = class {
1608
1637
  this.#pending.clear();
1609
1638
  this.#waitingFor.clear();
1610
1639
  }
1640
+ /**
1641
+ * Check if an event should be released based on its predecessors and the multi-cause mode.
1642
+ */
1643
+ #shouldReleaseEvent(event) {
1644
+ if (!event.causedBy || event.causedBy.length === 0) {
1645
+ return true;
1646
+ }
1647
+ const missingPredecessors = this.#getMissingPredecessors(event);
1648
+ if (this.#options.multiCauseMode === "any") {
1649
+ return missingPredecessors.length < event.causedBy.length;
1650
+ } else {
1651
+ return missingPredecessors.length === 0;
1652
+ }
1653
+ }
1611
1654
  /**
1612
1655
  * Get missing predecessors for an event.
1613
1656
  * A predecessor is considered "missing" if it hasn't been released yet
@@ -1631,9 +1674,21 @@ var CausalEventBuffer = class {
1631
1674
  for (const waitingEventId of waitingEventIds) {
1632
1675
  const waitingEvent = this.#pending.get(waitingEventId);
1633
1676
  if (!waitingEvent) continue;
1634
- const stillMissing = this.#getMissingPredecessors(waitingEvent);
1635
- if (stillMissing.length === 0) {
1677
+ if (this.#shouldReleaseEvent(waitingEvent)) {
1636
1678
  this.#pending.delete(waitingEventId);
1679
+ if (waitingEvent.causedBy) {
1680
+ for (const otherPredId of waitingEvent.causedBy) {
1681
+ if (otherPredId !== predecessorId) {
1682
+ const waiting = this.#waitingFor.get(otherPredId);
1683
+ if (waiting) {
1684
+ waiting.delete(waitingEventId);
1685
+ if (waiting.size === 0) {
1686
+ this.#waitingFor.delete(otherPredId);
1687
+ }
1688
+ }
1689
+ }
1690
+ }
1691
+ }
1637
1692
  ready.push(waitingEvent);
1638
1693
  this.#releaseWaiting(waitingEventId, ready);
1639
1694
  }
@@ -1747,7 +1802,7 @@ function sortCausalOrder(events) {
1747
1802
  }
1748
1803
 
1749
1804
  // src/connection/client.ts
1750
- var ClientConnection = class {
1805
+ var ClientConnection = class _ClientConnection {
1751
1806
  #connection;
1752
1807
  #subscriptions = /* @__PURE__ */ new Map();
1753
1808
  #subscriptionStates = /* @__PURE__ */ new Map();
@@ -1771,6 +1826,59 @@ var ClientConnection = class {
1771
1826
  }
1772
1827
  }
1773
1828
  // ===========================================================================
1829
+ // Static Factory Methods
1830
+ // ===========================================================================
1831
+ /**
1832
+ * Connect to a MAP server via WebSocket URL.
1833
+ *
1834
+ * Handles:
1835
+ * - WebSocket creation and connection
1836
+ * - Stream wrapping
1837
+ * - Auto-configuration of createStream for reconnection
1838
+ * - Initial MAP protocol connect handshake
1839
+ *
1840
+ * @param url - WebSocket URL (ws:// or wss://)
1841
+ * @param options - Connection options
1842
+ * @returns Connected ClientConnection instance
1843
+ *
1844
+ * @example
1845
+ * ```typescript
1846
+ * const client = await ClientConnection.connect('ws://localhost:8080', {
1847
+ * name: 'MyClient',
1848
+ * reconnection: true
1849
+ * });
1850
+ *
1851
+ * // Already connected, ready to use
1852
+ * const agents = await client.listAgents();
1853
+ * ```
1854
+ */
1855
+ static async connect(url, options) {
1856
+ const parsedUrl = new URL(url);
1857
+ if (!["ws:", "wss:"].includes(parsedUrl.protocol)) {
1858
+ throw new Error(
1859
+ `Unsupported protocol: ${parsedUrl.protocol}. Use ws: or wss:`
1860
+ );
1861
+ }
1862
+ const timeout = options?.connectTimeout ?? 1e4;
1863
+ const ws = new WebSocket(url);
1864
+ await waitForOpen(ws, timeout);
1865
+ const stream = websocketStream(ws);
1866
+ const createStream = async () => {
1867
+ const newWs = new WebSocket(url);
1868
+ await waitForOpen(newWs, timeout);
1869
+ return websocketStream(newWs);
1870
+ };
1871
+ const reconnection = options?.reconnection === true ? { enabled: true } : typeof options?.reconnection === "object" ? options.reconnection : void 0;
1872
+ const client = new _ClientConnection(stream, {
1873
+ name: options?.name,
1874
+ capabilities: options?.capabilities,
1875
+ createStream,
1876
+ reconnection
1877
+ });
1878
+ await client.connect({ auth: options?.auth });
1879
+ return client;
1880
+ }
1881
+ // ===========================================================================
1774
1882
  // Connection Lifecycle
1775
1883
  // ===========================================================================
1776
1884
  /**
@@ -1783,6 +1891,7 @@ var ClientConnection = class {
1783
1891
  name: this.#options.name,
1784
1892
  capabilities: this.#options.capabilities,
1785
1893
  sessionId: options?.sessionId,
1894
+ resumeToken: options?.resumeToken,
1786
1895
  auth: options?.auth
1787
1896
  };
1788
1897
  const result = await this.#connection.sendRequest(CORE_METHODS.CONNECT, params);
@@ -1795,14 +1904,18 @@ var ClientConnection = class {
1795
1904
  }
1796
1905
  /**
1797
1906
  * Disconnect from the MAP system
1907
+ * @param reason - Optional reason for disconnecting
1908
+ * @returns Resume token that can be used to resume this session later
1798
1909
  */
1799
1910
  async disconnect(reason) {
1800
- if (!this.#connected) return;
1911
+ if (!this.#connected) return void 0;
1912
+ let resumeToken;
1801
1913
  try {
1802
- await this.#connection.sendRequest(
1914
+ const result = await this.#connection.sendRequest(
1803
1915
  CORE_METHODS.DISCONNECT,
1804
1916
  reason ? { reason } : void 0
1805
1917
  );
1918
+ resumeToken = result.resumeToken;
1806
1919
  } finally {
1807
1920
  for (const subscription of this.#subscriptions.values()) {
1808
1921
  subscription._close();
@@ -1811,6 +1924,7 @@ var ClientConnection = class {
1811
1924
  await this.#connection.close();
1812
1925
  this.#connected = false;
1813
1926
  }
1927
+ return resumeToken;
1814
1928
  }
1815
1929
  /**
1816
1930
  * Whether the client is connected
@@ -2346,7 +2460,7 @@ var ClientConnection = class {
2346
2460
  };
2347
2461
 
2348
2462
  // src/connection/agent.ts
2349
- var AgentConnection = class {
2463
+ var AgentConnection = class _AgentConnection {
2350
2464
  #connection;
2351
2465
  #subscriptions = /* @__PURE__ */ new Map();
2352
2466
  #options;
@@ -2373,6 +2487,66 @@ var AgentConnection = class {
2373
2487
  }
2374
2488
  }
2375
2489
  // ===========================================================================
2490
+ // Static Factory Methods
2491
+ // ===========================================================================
2492
+ /**
2493
+ * Connect and register an agent via WebSocket URL.
2494
+ *
2495
+ * Handles:
2496
+ * - WebSocket creation and connection
2497
+ * - Stream wrapping
2498
+ * - Auto-configuration of createStream for reconnection
2499
+ * - Initial MAP protocol connect handshake
2500
+ * - Agent registration
2501
+ *
2502
+ * @param url - WebSocket URL (ws:// or wss://)
2503
+ * @param options - Connection and agent options
2504
+ * @returns Connected and registered AgentConnection instance
2505
+ *
2506
+ * @example
2507
+ * ```typescript
2508
+ * const agent = await AgentConnection.connect('ws://localhost:8080', {
2509
+ * name: 'Worker',
2510
+ * role: 'processor',
2511
+ * reconnection: true
2512
+ * });
2513
+ *
2514
+ * // Already registered, ready to work
2515
+ * agent.onMessage(handleMessage);
2516
+ * await agent.busy();
2517
+ * ```
2518
+ */
2519
+ static async connect(url, options) {
2520
+ const parsedUrl = new URL(url);
2521
+ if (!["ws:", "wss:"].includes(parsedUrl.protocol)) {
2522
+ throw new Error(
2523
+ `Unsupported protocol: ${parsedUrl.protocol}. Use ws: or wss:`
2524
+ );
2525
+ }
2526
+ const timeout = options?.connectTimeout ?? 1e4;
2527
+ const ws = new WebSocket(url);
2528
+ await waitForOpen(ws, timeout);
2529
+ const stream = websocketStream(ws);
2530
+ const createStream = async () => {
2531
+ const newWs = new WebSocket(url);
2532
+ await waitForOpen(newWs, timeout);
2533
+ return websocketStream(newWs);
2534
+ };
2535
+ const reconnection = options?.reconnection === true ? { enabled: true } : typeof options?.reconnection === "object" ? options.reconnection : void 0;
2536
+ const agent = new _AgentConnection(stream, {
2537
+ name: options?.name,
2538
+ role: options?.role,
2539
+ capabilities: options?.capabilities,
2540
+ visibility: options?.visibility,
2541
+ parent: options?.parent,
2542
+ scopes: options?.scopes,
2543
+ createStream,
2544
+ reconnection
2545
+ });
2546
+ await agent.connect({ auth: options?.auth });
2547
+ return agent;
2548
+ }
2549
+ // ===========================================================================
2376
2550
  // Connection Lifecycle
2377
2551
  // ===========================================================================
2378
2552
  /**
@@ -2385,6 +2559,7 @@ var AgentConnection = class {
2385
2559
  participantId: options?.agentId,
2386
2560
  name: this.#options.name,
2387
2561
  capabilities: this.#options.capabilities,
2562
+ resumeToken: options?.resumeToken,
2388
2563
  auth: options?.auth
2389
2564
  };
2390
2565
  const connectResult = await this.#connection.sendRequest(CORE_METHODS.CONNECT, connectParams);
@@ -2409,9 +2584,12 @@ var AgentConnection = class {
2409
2584
  }
2410
2585
  /**
2411
2586
  * Disconnect from the MAP system
2587
+ * @param reason - Optional reason for disconnecting
2588
+ * @returns Resume token that can be used to resume this session later
2412
2589
  */
2413
2590
  async disconnect(reason) {
2414
- if (!this.#connected) return;
2591
+ if (!this.#connected) return void 0;
2592
+ let resumeToken;
2415
2593
  try {
2416
2594
  if (this.#agentId) {
2417
2595
  await this.#connection.sendRequest(LIFECYCLE_METHODS.AGENTS_UNREGISTER, {
@@ -2419,10 +2597,11 @@ var AgentConnection = class {
2419
2597
  reason
2420
2598
  });
2421
2599
  }
2422
- await this.#connection.sendRequest(
2600
+ const result = await this.#connection.sendRequest(
2423
2601
  CORE_METHODS.DISCONNECT,
2424
2602
  reason ? { reason } : void 0
2425
2603
  );
2604
+ resumeToken = result.resumeToken;
2426
2605
  } finally {
2427
2606
  for (const subscription of this.#subscriptions.values()) {
2428
2607
  subscription._close();
@@ -2431,6 +2610,7 @@ var AgentConnection = class {
2431
2610
  await this.#connection.close();
2432
2611
  this.#connected = false;
2433
2612
  }
2613
+ return resumeToken;
2434
2614
  }
2435
2615
  /**
2436
2616
  * Whether the agent is connected
@@ -4350,6 +4530,89 @@ function canAgentMessageAgent(senderAgent, targetAgentId, context, config = DEFA
4350
4530
  return false;
4351
4531
  }
4352
4532
 
4533
+ // src/server/messages/address.ts
4534
+ var SEPARATOR = ":";
4535
+ var VALID_ADDRESS_TYPES = ["agent", "scope"];
4536
+ var InvalidAddressError = class extends Error {
4537
+ constructor(address, reason) {
4538
+ super(`Invalid address "${address}": ${reason}`);
4539
+ this.name = "InvalidAddressError";
4540
+ }
4541
+ };
4542
+ function formatAddress(type, id) {
4543
+ if (!id) {
4544
+ throw new InvalidAddressError("", "ID cannot be empty");
4545
+ }
4546
+ if (id.includes(SEPARATOR)) {
4547
+ throw new InvalidAddressError(id, "ID cannot contain colon separator");
4548
+ }
4549
+ return `${type}${SEPARATOR}${id}`;
4550
+ }
4551
+ function parseAddress(address) {
4552
+ const separatorIndex = address.indexOf(SEPARATOR);
4553
+ if (separatorIndex === -1) {
4554
+ throw new InvalidAddressError(address, "missing type prefix");
4555
+ }
4556
+ const type = address.slice(0, separatorIndex);
4557
+ const id = address.slice(separatorIndex + 1);
4558
+ if (!VALID_ADDRESS_TYPES.includes(type)) {
4559
+ throw new InvalidAddressError(address, `invalid type "${type}", must be agent or scope`);
4560
+ }
4561
+ if (!id) {
4562
+ throw new InvalidAddressError(address, "ID cannot be empty");
4563
+ }
4564
+ return {
4565
+ type,
4566
+ id
4567
+ };
4568
+ }
4569
+ function isAddress(address) {
4570
+ try {
4571
+ parseAddress(address);
4572
+ return true;
4573
+ } catch {
4574
+ return false;
4575
+ }
4576
+ }
4577
+ function isAgentAddress(address) {
4578
+ try {
4579
+ const parsed = parseAddress(address);
4580
+ return parsed.type === "agent";
4581
+ } catch {
4582
+ return false;
4583
+ }
4584
+ }
4585
+ function isScopeAddress(address) {
4586
+ try {
4587
+ const parsed = parseAddress(address);
4588
+ return parsed.type === "scope";
4589
+ } catch {
4590
+ return false;
4591
+ }
4592
+ }
4593
+ function extractId(address) {
4594
+ try {
4595
+ const parsed = parseAddress(address);
4596
+ return parsed.id;
4597
+ } catch {
4598
+ return address;
4599
+ }
4600
+ }
4601
+ function extractType(address) {
4602
+ try {
4603
+ const parsed = parseAddress(address);
4604
+ return parsed.type;
4605
+ } catch {
4606
+ return void 0;
4607
+ }
4608
+ }
4609
+ function toAgent(agentId) {
4610
+ return formatAddress("agent", agentId);
4611
+ }
4612
+ function toScope(scopeId) {
4613
+ return formatAddress("scope", scopeId);
4614
+ }
4615
+
4353
4616
  Object.defineProperty(exports, "monotonicFactory", {
4354
4617
  enumerable: true,
4355
4618
  get: function () { return ulid.monotonicFactory; }
@@ -4392,6 +4655,7 @@ exports.FederatedAddressSchema = FederatedAddressSchema;
4392
4655
  exports.FederationOutageBuffer = FederationOutageBuffer;
4393
4656
  exports.GatewayConnection = GatewayConnection;
4394
4657
  exports.HierarchicalAddressSchema = HierarchicalAddressSchema;
4658
+ exports.InvalidAddressError = InvalidAddressError;
4395
4659
  exports.JSONRPC_VERSION = JSONRPC_VERSION;
4396
4660
  exports.JsonRpcVersionSchema = JsonRpcVersionSchema;
4397
4661
  exports.LIFECYCLE_METHODS = LIFECYCLE_METHODS;
@@ -4485,15 +4749,20 @@ exports.createStreamPair = createStreamPair;
4485
4749
  exports.createSubscription = createSubscription;
4486
4750
  exports.createSuccessResponse = createSuccessResponse;
4487
4751
  exports.deepMergePermissions = deepMergePermissions;
4752
+ exports.extractId = extractId;
4753
+ exports.extractType = extractType;
4488
4754
  exports.filterVisibleAgents = filterVisibleAgents;
4489
4755
  exports.filterVisibleEvents = filterVisibleEvents;
4490
4756
  exports.filterVisibleScopes = filterVisibleScopes;
4757
+ exports.formatAddress = formatAddress;
4491
4758
  exports.getEnvelopeRoutingInfo = getEnvelopeRoutingInfo;
4492
4759
  exports.getMethodInfo = getMethodInfo;
4493
4760
  exports.getMethodsByCategory = getMethodsByCategory;
4494
4761
  exports.getRequiredCapabilities = getRequiredCapabilities;
4495
4762
  exports.hasCapability = hasCapability;
4496
4763
  exports.hasRequiredCapabilities = hasRequiredCapabilities;
4764
+ exports.isAddress = isAddress;
4765
+ exports.isAgentAddress = isAgentAddress;
4497
4766
  exports.isAgentExposed = isAgentExposed;
4498
4767
  exports.isBroadcastAddress = isBroadcastAddress;
4499
4768
  exports.isDirectAddress = isDirectAddress;
@@ -4513,14 +4782,18 @@ exports.isValidEnvelope = isValidEnvelope;
4513
4782
  exports.isValidUlid = isValidUlid;
4514
4783
  exports.mapVisibilityToRule = mapVisibilityToRule;
4515
4784
  exports.ndJsonStream = ndJsonStream;
4785
+ exports.parseAddress = parseAddress;
4516
4786
  exports.processFederationEnvelope = processFederationEnvelope;
4517
4787
  exports.resolveAgentPermissions = resolveAgentPermissions;
4518
4788
  exports.retryable = retryable;
4519
4789
  exports.sleep = sleep;
4520
4790
  exports.sortCausalOrder = sortCausalOrder;
4791
+ exports.toAgent = toAgent;
4792
+ exports.toScope = toScope;
4521
4793
  exports.ulidTimestamp = ulidTimestamp;
4522
4794
  exports.unwrapEnvelope = unwrapEnvelope;
4523
4795
  exports.validateCausalOrder = validateCausalOrder;
4796
+ exports.waitForOpen = waitForOpen;
4524
4797
  exports.websocketStream = websocketStream;
4525
4798
  exports.withPayload = withPayload;
4526
4799
  exports.withRetry = withRetry;