@kadi.build/core 0.7.2 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/client.ts CHANGED
@@ -31,6 +31,7 @@ import type {
31
31
  BrokerState,
32
32
  RegisteredTool,
33
33
  ToolDefinition,
34
+ BrokerToolDefinition,
34
35
  ZodToolDefinition,
35
36
  ToolHandler,
36
37
  LoadedAbility,
@@ -52,6 +53,7 @@ import type {
52
53
  EmitOptions,
53
54
  } from './types.js';
54
55
  import { KadiError } from './errors.js';
56
+ import * as protocol from './protocol.js';
55
57
  import { zodToJsonSchema, isZodSchema } from './zod.js';
56
58
  import { resolveAbilityEntry, resolveAbilityScript } from './lockfile.js';
57
59
  import { loadNativeTransport } from './transports/native.js';
@@ -156,8 +158,8 @@ function isMcpCallToolResult(result: unknown): boolean {
156
158
  * const client = new KadiClient({
157
159
  * name: 'my-agent',
158
160
  * brokers: {
159
- * production: 'ws://broker-prod:8080',
160
- * internal: 'ws://broker-internal:8080',
161
+ * production: { url: 'ws://broker-prod:8080/kadi' },
162
+ * internal: { url: 'ws://broker-internal:8080/kadi', networks: ['private'] },
161
163
  * },
162
164
  * defaultBroker: 'production',
163
165
  * });
@@ -241,17 +243,24 @@ export class KadiClient {
241
243
  this._agentId = crypto.createHash('sha256').update(this._publicKeyBase64).digest('hex').substring(0, 16);
242
244
 
243
245
  // Resolve configuration with defaults
244
- // Auto-select first broker as default if not specified (matches Python behavior)
245
- const brokers = config.brokers ?? {};
246
- const firstBrokerName = Object.keys(brokers)[0];
246
+ const rawBrokers = config.brokers ?? {};
247
+ const normalizedBrokers: Record<string, { url: string; networks: string[] }> = {};
248
+
249
+ for (const [name, entry] of Object.entries(rawBrokers)) {
250
+ normalizedBrokers[name] = {
251
+ url: entry.url,
252
+ networks: entry.networks ?? ['global'],
253
+ };
254
+ }
255
+
256
+ const firstBrokerName = Object.keys(normalizedBrokers)[0];
247
257
 
248
258
  this.config = {
249
259
  name: config.name,
250
260
  version: config.version ?? '1.0.0',
251
261
  description: config.description ?? '',
252
- brokers,
262
+ brokers: normalizedBrokers,
253
263
  defaultBroker: config.defaultBroker ?? firstBrokerName,
254
- networks: config.networks ?? ['global'],
255
264
  heartbeatInterval: config.heartbeatInterval ?? DEFAULT_HEARTBEAT_INTERVAL,
256
265
  requestTimeout: config.requestTimeout ?? DEFAULT_REQUEST_TIMEOUT,
257
266
  autoReconnect: config.autoReconnect ?? DEFAULT_AUTO_RECONNECT,
@@ -445,8 +454,7 @@ export class KadiClient {
445
454
  const failures: string[] = [];
446
455
  for (const [i, result] of results.entries()) {
447
456
  if (result.status === 'rejected') {
448
- const failedBroker = brokerNames[i];
449
- if (failedBroker === undefined) continue;
457
+ const failedBroker = brokerNames[i]!;
450
458
  const reason = result.reason instanceof Error ? result.reason.message : String(result.reason);
451
459
  console.error(`[KADI] Failed to connect to broker "${failedBroker}": ${reason}`);
452
460
  failures.push(failedBroker);
@@ -485,8 +493,8 @@ export class KadiClient {
485
493
  * across all broker connections, ensuring consistent identity.
486
494
  */
487
495
  private async connectToBroker(brokerName: string): Promise<void> {
488
- const url = this.config.brokers[brokerName];
489
- if (!url) {
496
+ const brokerConfig = this.config.brokers[brokerName];
497
+ if (!brokerConfig) {
490
498
  throw new KadiError(`Broker "${brokerName}" not found in configuration`, 'UNKNOWN_BROKER', {
491
499
  broker: brokerName,
492
500
  available: Object.keys(this.config.brokers),
@@ -494,6 +502,8 @@ export class KadiClient {
494
502
  });
495
503
  }
496
504
 
505
+ const url = brokerConfig.url;
506
+
497
507
  // Check if already connected
498
508
  if (this.brokers.has(brokerName)) {
499
509
  const existing = this.brokers.get(brokerName)!;
@@ -506,6 +516,7 @@ export class KadiClient {
506
516
  const state: BrokerState = {
507
517
  name: brokerName,
508
518
  url,
519
+ networks: brokerConfig.networks,
509
520
  ws: null,
510
521
  heartbeatTimer: null,
511
522
  pendingRequests: new Map(),
@@ -530,12 +541,8 @@ export class KadiClient {
530
541
  // Register with broker (transitions to "ready" state)
531
542
  await this.registerWithBroker(state);
532
543
 
533
- // Start heartbeat
534
- state.heartbeatTimer = setInterval(() => {
535
- this.sendHeartbeat(state);
536
- }, this.config.heartbeatInterval);
537
-
538
- state.status = 'connected';
544
+ // Finalize connection (heartbeat + status)
545
+ this.finalizeConnection(state);
539
546
  }
540
547
 
541
548
  // ─────────────────────────────────────────────────────────────
@@ -543,12 +550,7 @@ export class KadiClient {
543
550
  // ─────────────────────────────────────────────────────────────
544
551
 
545
552
  private buildHelloMessage(): JsonRpcRequest {
546
- return {
547
- jsonrpc: '2.0',
548
- id: this.nextRequestId++,
549
- method: 'kadi.session.hello',
550
- params: { role: 'agent' },
551
- };
553
+ return protocol.hello(this.nextRequestId++);
552
554
  }
553
555
 
554
556
  private buildAuthMessage(nonce: string): JsonRpcRequest {
@@ -560,38 +562,20 @@ export class KadiClient {
560
562
  });
561
563
  const signature = crypto.sign(null, Buffer.from(nonce, 'utf8'), privateKey);
562
564
 
563
- return {
564
- jsonrpc: '2.0',
565
- id: this.nextRequestId++,
566
- method: 'kadi.session.authenticate',
567
- params: {
568
- publicKey: this._publicKeyBase64,
569
- signature: signature.toString('base64'),
570
- nonce,
571
- },
572
- };
565
+ return protocol.authenticate(
566
+ this.nextRequestId++,
567
+ this._publicKeyBase64,
568
+ signature.toString('base64'),
569
+ nonce,
570
+ );
573
571
  }
574
572
 
575
- private buildRegisterMessage(tools: ToolDefinition[], networks: string[]): JsonRpcRequest {
576
- return {
577
- jsonrpc: '2.0',
578
- id: this.nextRequestId++,
579
- method: 'kadi.agent.register',
580
- params: {
581
- tools,
582
- networks,
583
- displayName: this.config.name,
584
- },
585
- };
573
+ private buildRegisterMessage(tools: BrokerToolDefinition[], networks: string[]): JsonRpcRequest {
574
+ return protocol.register(this.nextRequestId++, tools, networks, this.config.name);
586
575
  }
587
576
 
588
577
  private buildHeartbeatMessage(): JsonRpcRequest {
589
- return {
590
- jsonrpc: '2.0',
591
- id: this.nextRequestId++,
592
- method: 'kadi.session.heartbeat',
593
- params: { timestamp: Date.now() },
594
- };
578
+ return protocol.heartbeat(this.nextRequestId++);
595
579
  }
596
580
 
597
581
  // ─────────────────────────────────────────────────────────────
@@ -718,10 +702,17 @@ export class KadiClient {
718
702
  // Note: If registration fails, sendRequest will reject with the error.
719
703
  // We don't need to check response.error here - errors are already
720
704
  // handled via Promise rejection in handleBrokerResponse.
721
- await this.sendRequest<{ status: string }>(
705
+ const { droppedNetworks } = await this.sendRequest<{ status: string; droppedNetworks?: string[] }>(
722
706
  state,
723
- this.buildRegisterMessage(tools, this.config.networks)
707
+ this.buildRegisterMessage(tools, state.networks)
724
708
  );
709
+
710
+ if (droppedNetworks && droppedNetworks.length > 0) {
711
+ console.warn(
712
+ `[KADI] Broker "${state.name}" dropped networks ${JSON.stringify(droppedNetworks)} ` +
713
+ `during registration. Tools scoped to those networks will not be discoverable.`
714
+ );
715
+ }
725
716
  }
726
717
 
727
718
  /**
@@ -1037,12 +1028,7 @@ export class KadiClient {
1037
1028
 
1038
1029
  // If this is the first handler for this pattern, subscribe on broker
1039
1030
  if (!state.subscribedPatterns.has(pattern)) {
1040
- await this.sendRequest(state, {
1041
- jsonrpc: '2.0',
1042
- id: this.nextRequestId++,
1043
- method: 'kadi.event.subscribe',
1044
- params: { pattern },
1045
- });
1031
+ await this.sendRequest(state, protocol.eventSubscribe(this.nextRequestId++, pattern));
1046
1032
  state.subscribedPatterns.add(pattern);
1047
1033
  }
1048
1034
  }
@@ -1097,12 +1083,7 @@ export class KadiClient {
1097
1083
  // Unsubscribe from broker if we were subscribed
1098
1084
  if (state.subscribedPatterns.has(pattern) && state.status === 'connected') {
1099
1085
  try {
1100
- await this.sendRequest(state, {
1101
- jsonrpc: '2.0',
1102
- id: this.nextRequestId++,
1103
- method: 'kadi.event.unsubscribe',
1104
- params: { pattern },
1105
- });
1086
+ await this.sendRequest(state, protocol.eventUnsubscribe(this.nextRequestId++, pattern));
1106
1087
  } catch {
1107
1088
  // Ignore errors during unsubscribe (broker might be disconnecting)
1108
1089
  }
@@ -1134,20 +1115,11 @@ export class KadiClient {
1134
1115
  async publish(channel: string, data: unknown, options: PublishOptions = {}): Promise<void> {
1135
1116
  const { state } = this.getConnectedBrokerState(options.broker);
1136
1117
 
1137
- // Resolve which network to publish to
1138
- const networkId = options.network ?? this.config.networks[0] ?? 'global';
1118
+ // Resolve which network to publish to (prefer broker-specific, fallback to global)
1119
+ const networkId = options.network ?? state.networks[0] ?? '';
1139
1120
 
1140
1121
  // Send publish request to broker
1141
- await this.sendRequest(state, {
1142
- jsonrpc: '2.0',
1143
- id: this.nextRequestId++,
1144
- method: 'kadi.event.publish',
1145
- params: {
1146
- channel,
1147
- data,
1148
- networkId,
1149
- },
1150
- });
1122
+ await this.sendRequest(state, protocol.eventPublish(this.nextRequestId++, channel, data, networkId));
1151
1123
  }
1152
1124
 
1153
1125
  // ─────────────────────────────────────────────────────────────
@@ -1463,34 +1435,17 @@ export class KadiClient {
1463
1435
  responseResult = result;
1464
1436
  }
1465
1437
 
1466
- const response: JsonRpcResponse = {
1467
- jsonrpc: '2.0',
1468
- id: requestId,
1469
- result: responseResult,
1470
- };
1471
- state.ws?.send(JSON.stringify(response));
1438
+ state.ws?.send(JSON.stringify(protocol.resultResponse(requestId, responseResult)));
1472
1439
  } catch (error) {
1473
1440
  const errorMessage = error instanceof Error ? error.message : String(error);
1474
1441
 
1475
1442
  // For MCP clients, wrap errors in CallToolResult format too
1476
1443
  if (context.callerProtocol === 'mcp') {
1477
- const response: JsonRpcResponse = {
1478
- jsonrpc: '2.0',
1479
- id: requestId,
1480
- result: { content: [{ type: 'text', text: errorMessage }], isError: true },
1481
- };
1482
- state.ws?.send(JSON.stringify(response));
1444
+ const mcpResult = { content: [{ type: 'text', text: errorMessage }], isError: true };
1445
+ state.ws?.send(JSON.stringify(protocol.resultResponse(requestId, mcpResult)));
1483
1446
  } else {
1484
1447
  // KADI clients receive JSON-RPC error
1485
- const response: JsonRpcResponse = {
1486
- jsonrpc: '2.0',
1487
- id: requestId,
1488
- error: {
1489
- code: -32000,
1490
- message: errorMessage,
1491
- },
1492
- };
1493
- state.ws?.send(JSON.stringify(response));
1448
+ state.ws?.send(JSON.stringify(protocol.errorResponse(requestId, -32000, errorMessage)));
1494
1449
  }
1495
1450
  }
1496
1451
  }
@@ -1520,6 +1475,17 @@ export class KadiClient {
1520
1475
  }
1521
1476
  }
1522
1477
 
1478
+ /**
1479
+ * Finalize a broker connection: start heartbeat and mark as connected.
1480
+ * Shared by initial connect and reconnect paths.
1481
+ */
1482
+ private finalizeConnection(state: BrokerState): void {
1483
+ state.heartbeatTimer = setInterval(() => {
1484
+ this.sendHeartbeat(state);
1485
+ }, this.config.heartbeatInterval);
1486
+ state.status = 'connected';
1487
+ }
1488
+
1523
1489
  /**
1524
1490
  * Send heartbeat to keep connection alive.
1525
1491
  */
@@ -1565,6 +1531,10 @@ export class KadiClient {
1565
1531
  );
1566
1532
  }
1567
1533
  state.pendingInvocations.clear();
1534
+
1535
+ // Clear broker-side subscription tracking — subscriptions are lost when
1536
+ // the connection drops and must be re-established after reconnection.
1537
+ state.subscribedPatterns.clear();
1568
1538
  }
1569
1539
 
1570
1540
  // ─────────────────────────────────────────────────────────────
@@ -1701,15 +1671,12 @@ export class KadiClient {
1701
1671
  // Re-register tools
1702
1672
  await this.registerWithBroker(state);
1703
1673
 
1704
- // Restart heartbeat
1705
- state.heartbeatTimer = setInterval(() => {
1706
- this.sendHeartbeat(state);
1707
- }, this.config.heartbeatInterval);
1674
+ // Finalize connection (heartbeat + status)
1675
+ this.finalizeConnection(state);
1708
1676
 
1709
1677
  // Success!
1710
1678
  console.error(`[KADI] Reconnected to broker "${state.name}" after ${state.reconnectAttempts} attempts`);
1711
1679
  state.reconnectAttempts = 0;
1712
- state.status = 'connected';
1713
1680
  } catch (error) {
1714
1681
  // Log the error and try again
1715
1682
  const message = error instanceof Error ? error.message : String(error);
@@ -1819,12 +1786,7 @@ export class KadiClient {
1819
1786
  this.eventHandler(event, data);
1820
1787
  } else if (this.isServingStdio) {
1821
1788
  // Stdio transport: write notification to stdout
1822
- const notification = {
1823
- jsonrpc: '2.0',
1824
- method: 'event',
1825
- params: { name: event, data },
1826
- };
1827
- const json = JSON.stringify(notification);
1789
+ const json = JSON.stringify(protocol.eventNotification(event, data));
1828
1790
  process.stdout.write(`Content-Length: ${Buffer.byteLength(json)}\r\n\r\n${json}`);
1829
1791
  }
1830
1792
 
@@ -1861,8 +1823,12 @@ export class KadiClient {
1861
1823
  * output: z.object({ result: z.number() }),
1862
1824
  * }, async ({ a, b }) => ({ result: a + b }));
1863
1825
  *
1864
- * // Register only on specific broker
1865
- * client.registerTool(def, handler, { brokers: ['internal'] });
1826
+ * // Register on specific brokers with per-broker networks
1827
+ * client.registerTool(def, handler, {
1828
+ * brokers: {
1829
+ * internal: { networks: ['private'] },
1830
+ * },
1831
+ * });
1866
1832
  * ```
1867
1833
  */
1868
1834
  registerTool<TInput, TOutput>(
@@ -1888,12 +1854,44 @@ export class KadiClient {
1888
1854
  : undefined,
1889
1855
  };
1890
1856
 
1857
+ // Validate per-broker network scoping (skip when no brokers configured)
1858
+ const brokerScopes = options.brokers ?? {};
1859
+ if (Object.keys(brokerScopes).length > 0 && Object.keys(this.config.brokers).length > 0) {
1860
+ for (const [brokerName, scope] of Object.entries(brokerScopes)) {
1861
+ const brokerConfig = this.config.brokers[brokerName];
1862
+ if (!brokerConfig) {
1863
+ throw new KadiError(
1864
+ `Tool "${definition.name}" references broker "${brokerName}" which is not configured. Available brokers: ${JSON.stringify(Object.keys(this.config.brokers))}`,
1865
+ 'INVALID_CONFIG',
1866
+ {
1867
+ toolName: definition.name,
1868
+ broker: brokerName,
1869
+ availableBrokers: Object.keys(this.config.brokers),
1870
+ }
1871
+ );
1872
+ }
1873
+ const brokerNetworks = brokerConfig.networks;
1874
+ const invalid = scope.networks.filter((n) => !brokerNetworks.includes(n));
1875
+ if (invalid.length > 0) {
1876
+ throw new KadiError(
1877
+ `Tool "${definition.name}" has networks ${JSON.stringify(invalid)} not present on broker "${brokerName}" networks ${JSON.stringify(brokerNetworks)}. Per-tool networks must be a subset of the broker's networks — a tool can only be visible on networks the agent has joined.`,
1878
+ 'INVALID_CONFIG',
1879
+ {
1880
+ toolName: definition.name,
1881
+ broker: brokerName,
1882
+ invalidNetworks: invalid,
1883
+ brokerNetworks,
1884
+ }
1885
+ );
1886
+ }
1887
+ }
1888
+ }
1889
+
1891
1890
  // Store registration
1892
1891
  const registered: RegisteredTool = {
1893
1892
  definition: jsonDefinition,
1894
1893
  handler: handler as ToolHandler,
1895
- registeredAt: new Date(),
1896
- targetBrokers: options.brokers ?? [],
1894
+ brokerNetworks: brokerScopes,
1897
1895
  };
1898
1896
  this.tools.set(definition.name, registered);
1899
1897
  }
@@ -1902,21 +1900,36 @@ export class KadiClient {
1902
1900
  * Get tool definitions, optionally filtered for a specific broker.
1903
1901
  *
1904
1902
  * @param forBroker - If provided, only return tools targeted for this broker.
1905
- * Tools with empty targetBrokers are included for all brokers.
1903
+ * Tools with empty brokerNetworks are included for all brokers.
1906
1904
  */
1907
- private getToolDefinitions(forBroker?: string): ToolDefinition[] {
1908
- return Array.from(this.tools.values())
1909
- .filter((t) => {
1910
- // If no broker specified, return all tools (e.g., for readAgentJson)
1911
- if (!forBroker) return true;
1905
+ private getToolDefinitions(forBroker?: string): BrokerToolDefinition[] {
1906
+ const results: BrokerToolDefinition[] = [];
1907
+
1908
+ for (const t of this.tools.values()) {
1909
+ const hasBrokerNetworks = Object.keys(t.brokerNetworks).length > 0;
1910
+
1911
+ if (!forBroker) {
1912
+ // No broker filter — return all tools (e.g., for readAgentJson)
1913
+ results.push({ ...t.definition });
1914
+ continue;
1915
+ }
1916
+
1917
+ if (!hasBrokerNetworks) {
1918
+ // Empty brokerNetworks = register with all brokers, no network field
1919
+ results.push({ ...t.definition });
1920
+ continue;
1921
+ }
1912
1922
 
1913
- // Empty targetBrokers means "register with all brokers"
1914
- if (t.targetBrokers.length === 0) return true;
1923
+ const scope = t.brokerNetworks[forBroker];
1924
+ if (!scope) continue; // Tool not registered on this broker
1915
1925
 
1916
- // Otherwise, only include if this broker is in the target list
1917
- return t.targetBrokers.includes(forBroker);
1918
- })
1919
- .map((t) => t.definition);
1926
+ results.push({
1927
+ ...t.definition,
1928
+ ...(scope.networks.length > 0 && { networks: scope.networks }),
1929
+ });
1930
+ }
1931
+
1932
+ return results;
1920
1933
  }
1921
1934
 
1922
1935
  /**
@@ -2090,7 +2103,7 @@ export class KadiClient {
2090
2103
  const ability = await loadBrokerTransport(name, {
2091
2104
  broker: state,
2092
2105
  requestTimeout: options.timeout ?? this.config.requestTimeout,
2093
- networks: options.networks,
2106
+ networks: options.networks ?? state.networks,
2094
2107
  // Provide subscribe/unsubscribe for ability.on()/off() support
2095
2108
  subscribe: (pattern, handler) => this.subscribe(pattern, handler, { broker: brokerName }),
2096
2109
  unsubscribe: (pattern, handler) => this.unsubscribe(pattern, handler, { broker: brokerName }),
@@ -2184,13 +2197,8 @@ export class KadiClient {
2184
2197
  try {
2185
2198
  const pendingResult = await this.sendRequest<{ status: string; requestId: string }>(
2186
2199
  state,
2187
- {
2188
- jsonrpc: '2.0',
2189
- id: ++this.nextRequestId,
2190
- method: 'kadi.ability.request',
2191
- // timeout sent so the broker can enforce a matching server-side deadline
2192
- params: { toolName, toolInput: params, requestId, timeout },
2193
- }
2200
+ // timeout sent so the broker can enforce a matching server-side deadline
2201
+ protocol.abilityRequest(++this.nextRequestId, toolName, params, requestId, timeout)
2194
2202
  );
2195
2203
 
2196
2204
  // Validate the broker accepted the request
@@ -2365,12 +2373,7 @@ export class KadiClient {
2365
2373
  * Send a response via stdio.
2366
2374
  */
2367
2375
  private sendStdioResponse(id: string | number, result: unknown): void {
2368
- const response: JsonRpcResponse = {
2369
- jsonrpc: '2.0',
2370
- id,
2371
- result,
2372
- };
2373
- const json = JSON.stringify(response);
2376
+ const json = JSON.stringify(protocol.resultResponse(id, result));
2374
2377
  process.stdout.write(`Content-Length: ${Buffer.byteLength(json)}\r\n\r\n${json}`);
2375
2378
  }
2376
2379
 
@@ -2378,12 +2381,7 @@ export class KadiClient {
2378
2381
  * Send an error response via stdio.
2379
2382
  */
2380
2383
  private sendStdioError(id: string | number, error: { code: number; message: string }): void {
2381
- const response: JsonRpcResponse = {
2382
- jsonrpc: '2.0',
2383
- id,
2384
- error,
2385
- };
2386
- const json = JSON.stringify(response);
2384
+ const json = JSON.stringify(protocol.errorResponse(id, error.code, error.message));
2387
2385
  process.stdout.write(`Content-Length: ${Buffer.byteLength(json)}\r\n\r\n${json}`);
2388
2386
  }
2389
2387
 
@@ -2418,7 +2416,7 @@ export class KadiClient {
2418
2416
  /**
2419
2417
  * Get agent information (for readAgentJson protocol).
2420
2418
  */
2421
- readAgentJson(): { name: string; version: string; tools: ToolDefinition[] } {
2419
+ readAgentJson(): { name: string; version: string; tools: BrokerToolDefinition[] } {
2422
2420
  return {
2423
2421
  name: this.config.name,
2424
2422
  version: this.config.version,
package/src/index.ts CHANGED
@@ -10,7 +10,7 @@
10
10
  * const client = new KadiClient({
11
11
  * name: 'my-agent',
12
12
  * brokers: {
13
- * production: 'ws://broker:8080',
13
+ * production: { url: 'ws://broker:8080/kadi' },
14
14
  * },
15
15
  * defaultBroker: 'production',
16
16
  * });
@@ -39,10 +39,12 @@ export type { ErrorCode, ErrorContext } from './errors.js';
39
39
  export type {
40
40
  // Configuration
41
41
  ClientConfig,
42
+ BrokerEntry,
42
43
  ResolvedConfig,
43
44
 
44
45
  // Tools
45
46
  ToolDefinition,
47
+ BrokerToolDefinition,
46
48
  ZodToolDefinition,
47
49
  ToolHandler,
48
50
  RegisteredTool,
@@ -67,6 +69,7 @@ export type {
67
69
  BrokerState,
68
70
  BrokerStatus,
69
71
  PendingRequest,
72
+ PendingInvocation,
70
73
 
71
74
  // Broker Events (Pub/Sub)
72
75
  BrokerEvent,
@@ -109,6 +112,9 @@ export { loadStdioTransport } from './transports/stdio.js';
109
112
  export { loadBrokerTransport } from './transports/broker.js';
110
113
  export type { BrokerTransportOptions } from './transports/broker.js';
111
114
 
115
+ // Protocol message builders
116
+ export * as protocol from './protocol.js';
117
+
112
118
  // Crypto utilities (Ed25519 to X25519 conversion)
113
119
  export { convertToEncryptionKey, convertToEncryptionKeyPair } from './crypto.js';
114
120
  export type { EncryptionKeyPair } from './crypto.js';