@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.
@@ -0,0 +1,161 @@
1
+ /**
2
+ * KADI Protocol Message Builders
3
+ * ==============================
4
+ *
5
+ * Pure functions that construct JSON-RPC 2.0 message objects for the KADI protocol.
6
+ *
7
+ * All functions return typed objects ready for `JSON.stringify()`.
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * import * as protocol from './protocol.js';
12
+ *
13
+ * const msg = protocol.heartbeat(1);
14
+ * ws.send(JSON.stringify(msg));
15
+ * ```
16
+ *
17
+ * Python Equivalent:
18
+ * kadi-core-py/src/kadi/protocol.py
19
+ */
20
+
21
+ import type { JsonRpcNotification, JsonRpcRequest, JsonRpcResponse } from './types.js';
22
+
23
+ // ---------------------------------------------------------------------------
24
+ // Generic request builder
25
+ // ---------------------------------------------------------------------------
26
+
27
+ /** Build a standard JSON-RPC 2.0 request. */
28
+ export function request(id: string | number, method: string, params: unknown): JsonRpcRequest {
29
+ return { jsonrpc: '2.0', id, method, params };
30
+ }
31
+
32
+ // ===========================================================================
33
+ // Session
34
+ // ===========================================================================
35
+
36
+ /** Build a `kadi.session.hello` request. */
37
+ export function hello(id: number): JsonRpcRequest {
38
+ return request(id, 'kadi.session.hello', { role: 'agent' });
39
+ }
40
+
41
+ /** Build a `kadi.session.authenticate` request. */
42
+ export function authenticate(
43
+ id: number,
44
+ publicKey: string,
45
+ signature: string,
46
+ nonce: string,
47
+ ): JsonRpcRequest {
48
+ return request(id, 'kadi.session.authenticate', { publicKey, signature, nonce });
49
+ }
50
+
51
+ /** Build a `kadi.session.heartbeat` request. */
52
+ export function heartbeat(id: number): JsonRpcRequest {
53
+ return request(id, 'kadi.session.heartbeat', { timestamp: Date.now() });
54
+ }
55
+
56
+ // ===========================================================================
57
+ // Agent
58
+ // ===========================================================================
59
+
60
+ /** Build a `kadi.agent.register` request. */
61
+ export function register(
62
+ id: number,
63
+ tools: unknown[],
64
+ networks: string[],
65
+ displayName?: string,
66
+ ): JsonRpcRequest {
67
+ const params: Record<string, unknown> = { tools, networks };
68
+ if (displayName !== undefined) {
69
+ params.displayName = displayName;
70
+ }
71
+ return request(id, 'kadi.agent.register', params);
72
+ }
73
+
74
+ // ===========================================================================
75
+ // Ability
76
+ // ===========================================================================
77
+
78
+ /** Build a `kadi.ability.request` request. */
79
+ export function abilityRequest(
80
+ id: number,
81
+ toolName: string,
82
+ toolInput: unknown,
83
+ requestId: string,
84
+ timeout?: number,
85
+ ): JsonRpcRequest {
86
+ const params: Record<string, unknown> = { toolName, toolInput, requestId };
87
+ if (timeout !== undefined) {
88
+ params.timeout = timeout;
89
+ }
90
+ return request(id, 'kadi.ability.request', params);
91
+ }
92
+
93
+ /** Build a `kadi.ability.list` request. */
94
+ export function abilityList(
95
+ id: number,
96
+ networks: string[],
97
+ includeProviders = true,
98
+ ): JsonRpcRequest {
99
+ return request(id, 'kadi.ability.list', { networks, includeProviders });
100
+ }
101
+
102
+ // ===========================================================================
103
+ // Event
104
+ // ===========================================================================
105
+
106
+ /** Build a `kadi.event.subscribe` request. */
107
+ export function eventSubscribe(id: number, pattern: string): JsonRpcRequest {
108
+ return request(id, 'kadi.event.subscribe', { pattern });
109
+ }
110
+
111
+ /** Build a `kadi.event.unsubscribe` request. */
112
+ export function eventUnsubscribe(id: number, pattern: string): JsonRpcRequest {
113
+ return request(id, 'kadi.event.unsubscribe', { pattern });
114
+ }
115
+
116
+ /** Build a `kadi.event.publish` request. */
117
+ export function eventPublish(
118
+ id: number,
119
+ channel: string,
120
+ data: unknown,
121
+ networkId: string,
122
+ ): JsonRpcRequest {
123
+ return request(id, 'kadi.event.publish', { channel, data, networkId });
124
+ }
125
+
126
+ // ===========================================================================
127
+ // Response (JSON-RPC result/error envelopes)
128
+ // ===========================================================================
129
+
130
+ /** Build a JSON-RPC 2.0 success response. */
131
+ export function resultResponse(id: string | number, result: unknown): JsonRpcResponse {
132
+ return { jsonrpc: '2.0', id, result };
133
+ }
134
+
135
+ /** Build a JSON-RPC 2.0 error response. */
136
+ export function errorResponse(
137
+ id: string | number,
138
+ code: number,
139
+ message: string,
140
+ data?: unknown,
141
+ ): JsonRpcResponse {
142
+ const error: { code: number; message: string; data?: unknown } = { code, message };
143
+ if (data !== undefined) {
144
+ error.data = data;
145
+ }
146
+ return { jsonrpc: '2.0', id, error };
147
+ }
148
+
149
+ // ===========================================================================
150
+ // Stdio
151
+ // ===========================================================================
152
+
153
+ /** Build a `readAgentJson` request for stdio transport. */
154
+ export function readAgentJson(id: number): JsonRpcRequest {
155
+ return request(id, 'readAgentJson', {});
156
+ }
157
+
158
+ /** Build an `event` notification (no `id` field). */
159
+ export function eventNotification(name: string, data: unknown): JsonRpcNotification {
160
+ return { jsonrpc: '2.0', method: 'event', params: { name, data } };
161
+ }
@@ -23,6 +23,7 @@ import type { Readable, Writable } from 'stream';
23
23
  import { z } from 'zod';
24
24
  import type { LoadedAbility, InvokeOptions, ToolDefinition, JsonRpcRequest, JsonRpcResponse, EventHandler } from '../types.js';
25
25
  import { KadiError } from '../errors.js';
26
+ import * as protocol from '../protocol.js';
26
27
 
27
28
  /** Schema for event notification params */
28
29
  const EventNotificationParams = z.object({
@@ -393,12 +394,7 @@ export async function loadStdioTransport(
393
394
  }
394
395
 
395
396
  const id = idCounter++;
396
- const request: JsonRpcRequest = {
397
- jsonrpc: '2.0',
398
- id,
399
- method,
400
- params,
401
- };
397
+ const request = protocol.request(id, method, params);
402
398
 
403
399
  writer.write(request);
404
400
  const response = await reader.waitForResponse(id, timeoutMs);
package/src/types.ts CHANGED
@@ -27,6 +27,30 @@ export type JSONSchema = JSONSchemaTypes.JSONSchema;
27
27
  // 1. CONFIGURATION
28
28
  // ═══════════════════════════════════════════════════════════════════════
29
29
 
30
+ /**
31
+ * Per-broker connection configuration.
32
+ *
33
+ * Use this when different brokers should join different networks.
34
+ *
35
+ * @example
36
+ * ```typescript
37
+ * brokers: {
38
+ * local: { url: 'ws://localhost:8080/kadi', networks: ['private', 'public'] },
39
+ * remote: { url: 'wss://remote/kadi', networks: ['public'] },
40
+ * }
41
+ * ```
42
+ */
43
+ export interface BrokerEntry {
44
+ /** WebSocket URL for this broker */
45
+ url: string;
46
+
47
+ /**
48
+ * Networks to join on this broker.
49
+ * Defaults to `['global']` if omitted.
50
+ */
51
+ networks?: string[];
52
+ }
53
+
30
54
  /**
31
55
  * Configuration options for creating a KadiClient.
32
56
  *
@@ -36,8 +60,8 @@ export type JSONSchema = JSONSchemaTypes.JSONSchema;
36
60
  * name: 'my-agent',
37
61
  * version: '1.0.0',
38
62
  * brokers: {
39
- * prod: 'ws://broker-prod:8080',
40
- * internal: 'ws://broker-internal:8080',
63
+ * prod: { url: 'ws://broker-prod:8080/kadi', networks: ['public'] },
64
+ * internal: { url: 'ws://broker-internal:8080/kadi', networks: ['private'] },
41
65
  * },
42
66
  * defaultBroker: 'prod',
43
67
  * });
@@ -55,17 +79,17 @@ export interface ClientConfig {
55
79
 
56
80
  /**
57
81
  * Named brokers to connect to.
58
- * Keys are friendly names, values are WebSocket URLs.
82
+ * Keys are friendly names, values are BrokerEntry objects.
59
83
  *
60
84
  * @example
61
85
  * ```typescript
62
86
  * brokers: {
63
- * prod: 'ws://broker-prod:8080',
64
- * staging: 'ws://broker-staging:8080',
87
+ * local: { url: 'ws://localhost:8080/kadi', networks: ['private'] },
88
+ * remote: { url: 'wss://remote/kadi', networks: ['public'] },
65
89
  * }
66
90
  * ```
67
91
  */
68
- brokers?: Record<string, string>;
92
+ brokers?: Record<string, BrokerEntry>;
69
93
 
70
94
  /**
71
95
  * Which broker to use by default for invokeRemote() and loadBroker().
@@ -73,13 +97,6 @@ export interface ClientConfig {
73
97
  */
74
98
  defaultBroker?: string;
75
99
 
76
- /**
77
- * Networks this agent belongs to.
78
- * Used for filtering when discovering abilities.
79
- * Default: ['global']
80
- */
81
- networks?: string[];
82
-
83
100
  /**
84
101
  * Heartbeat interval in milliseconds.
85
102
  * Broker expects heartbeats to stay connected.
@@ -147,9 +164,9 @@ export interface ResolvedConfig {
147
164
  name: string;
148
165
  version: string;
149
166
  description: string;
150
- brokers: Record<string, string>;
167
+ /** Normalized broker configs. Each entry has a url and resolved networks array. */
168
+ brokers: Record<string, { url: string; networks: string[] }>;
151
169
  defaultBroker: string | undefined;
152
- networks: string[];
153
170
  heartbeatInterval: number;
154
171
  requestTimeout: number;
155
172
  autoReconnect: boolean;
@@ -178,6 +195,15 @@ export interface ToolDefinition {
178
195
  outputSchema?: JSONSchema;
179
196
  }
180
197
 
198
+ /**
199
+ * Tool definition extended with optional per-tool network scoping.
200
+ * Used when sending tool definitions to brokers and in readAgentJson().
201
+ */
202
+ export interface BrokerToolDefinition extends ToolDefinition {
203
+ /** Networks this tool is visible on (omitted if visible on all client networks) */
204
+ networks?: string[];
205
+ }
206
+
181
207
  /**
182
208
  * Tool definition using Zod schemas.
183
209
  * Recommended approach - provides type inference.
@@ -280,11 +306,8 @@ export interface RegisteredTool {
280
306
  /** The handler function */
281
307
  handler: ToolHandler;
282
308
 
283
- /** When this tool was registered */
284
- registeredAt: Date;
285
-
286
- /** Which brokers this tool is registered with (empty = all) */
287
- targetBrokers: string[];
309
+ /** Per-broker network targeting (empty = all brokers, all networks) */
310
+ brokerNetworks: Record<string, { networks: string[] }>;
288
311
  }
289
312
 
290
313
  /**
@@ -476,6 +499,9 @@ export interface BrokerState {
476
499
  /** WebSocket URL */
477
500
  url: string;
478
501
 
502
+ /** Networks this broker connection has joined */
503
+ networks: string[];
504
+
479
505
  /** WebSocket connection (null if disconnected) */
480
506
  ws: WebSocket | null;
481
507
 
@@ -721,16 +747,23 @@ export interface AbilityLockEntry {
721
747
  */
722
748
  export interface RegisterToolOptions {
723
749
  /**
724
- * Which brokers to register this tool with.
725
- * If not specified, registers with ALL connected brokers.
750
+ * Per-broker network scoping.
751
+ * Keys are broker names, values specify which networks to register on for that broker.
752
+ * Each broker name must exist in client's configured brokers.
753
+ * Each network list must be a subset of that specific broker's networks.
754
+ * If not specified, registers with ALL brokers on ALL their networks.
726
755
  *
727
756
  * @example
728
757
  * ```typescript
729
- * // Register only on internal broker
730
- * client.registerTool(def, handler, { brokers: ['internal'] });
758
+ * client.registerTool(def, handler, {
759
+ * brokers: {
760
+ * local: { networks: ['private'] },
761
+ * remote: { networks: ['sensitive'] },
762
+ * },
763
+ * });
731
764
  * ```
732
765
  */
733
- brokers?: string[];
766
+ brokers?: Record<string, { networks: string[] }>;
734
767
  }
735
768
 
736
769
  /**