@kadi.build/core 0.0.1-alpha.2 → 0.0.1-alpha.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.
Files changed (99) hide show
  1. package/README.md +1122 -216
  2. package/dist/KadiClient.d.ts +303 -0
  3. package/dist/KadiClient.d.ts.map +1 -0
  4. package/dist/KadiClient.js +1162 -0
  5. package/dist/KadiClient.js.map +1 -0
  6. package/dist/errors/error-codes.d.ts +215 -0
  7. package/dist/errors/error-codes.d.ts.map +1 -0
  8. package/dist/errors/error-codes.js +295 -0
  9. package/dist/errors/error-codes.js.map +1 -0
  10. package/dist/index.d.ts +15 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +24 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/loadAbility.d.ts +65 -0
  15. package/dist/loadAbility.d.ts.map +1 -0
  16. package/dist/loadAbility.js +335 -0
  17. package/dist/loadAbility.js.map +1 -0
  18. package/dist/messages/BrokerMessages.d.ts +84 -0
  19. package/dist/messages/BrokerMessages.d.ts.map +1 -0
  20. package/dist/messages/BrokerMessages.js +127 -0
  21. package/dist/messages/BrokerMessages.js.map +1 -0
  22. package/dist/messages/MessageBuilder.d.ts +83 -0
  23. package/dist/messages/MessageBuilder.d.ts.map +1 -0
  24. package/dist/messages/MessageBuilder.js +144 -0
  25. package/dist/messages/MessageBuilder.js.map +1 -0
  26. package/dist/schemas/events.schemas.d.ts +177 -0
  27. package/dist/schemas/events.schemas.d.ts.map +1 -0
  28. package/dist/schemas/events.schemas.js +265 -0
  29. package/dist/schemas/events.schemas.js.map +1 -0
  30. package/dist/schemas/index.d.ts +3 -0
  31. package/dist/schemas/index.d.ts.map +1 -0
  32. package/dist/schemas/index.js +4 -0
  33. package/dist/schemas/index.js.map +1 -0
  34. package/dist/schemas/kadi.schemas.d.ts +70 -0
  35. package/dist/schemas/kadi.schemas.d.ts.map +1 -0
  36. package/dist/schemas/kadi.schemas.js +120 -0
  37. package/dist/schemas/kadi.schemas.js.map +1 -0
  38. package/dist/transports/BrokerTransport.d.ts +96 -0
  39. package/dist/transports/BrokerTransport.d.ts.map +1 -0
  40. package/dist/transports/BrokerTransport.js +145 -0
  41. package/dist/transports/BrokerTransport.js.map +1 -0
  42. package/dist/transports/NativeTransport.d.ts +92 -0
  43. package/dist/transports/NativeTransport.d.ts.map +1 -0
  44. package/dist/transports/NativeTransport.js +221 -0
  45. package/dist/transports/NativeTransport.js.map +1 -0
  46. package/dist/transports/StdioTransport.d.ts +112 -0
  47. package/dist/transports/StdioTransport.d.ts.map +1 -0
  48. package/dist/transports/StdioTransport.js +440 -0
  49. package/dist/transports/StdioTransport.js.map +1 -0
  50. package/dist/transports/Transport.d.ts +93 -0
  51. package/dist/transports/Transport.d.ts.map +1 -0
  52. package/dist/transports/Transport.js +13 -0
  53. package/dist/transports/Transport.js.map +1 -0
  54. package/dist/types/broker.d.ts +31 -0
  55. package/dist/types/broker.d.ts.map +1 -0
  56. package/dist/types/broker.js +6 -0
  57. package/dist/types/broker.js.map +1 -0
  58. package/dist/types/core.d.ts +138 -0
  59. package/dist/types/core.d.ts.map +1 -0
  60. package/dist/types/core.js +26 -0
  61. package/dist/types/core.js.map +1 -0
  62. package/dist/types/events.d.ts +186 -0
  63. package/dist/types/events.d.ts.map +1 -0
  64. package/dist/types/events.js +16 -0
  65. package/dist/types/events.js.map +1 -0
  66. package/dist/types/index.d.ts +9 -0
  67. package/dist/types/index.d.ts.map +1 -0
  68. package/dist/types/index.js +13 -0
  69. package/dist/types/index.js.map +1 -0
  70. package/dist/types/protocol.d.ts +160 -0
  71. package/dist/types/protocol.d.ts.map +1 -0
  72. package/dist/types/protocol.js +5 -0
  73. package/dist/types/protocol.js.map +1 -0
  74. package/dist/utils/agentUtils.d.ts +102 -0
  75. package/dist/utils/agentUtils.d.ts.map +1 -0
  76. package/dist/utils/agentUtils.js +166 -0
  77. package/dist/utils/agentUtils.js.map +1 -0
  78. package/dist/utils/commandUtils.d.ts +45 -0
  79. package/dist/utils/commandUtils.d.ts.map +1 -0
  80. package/dist/utils/commandUtils.js +145 -0
  81. package/dist/utils/commandUtils.js.map +1 -0
  82. package/dist/utils/configUtils.d.ts +55 -0
  83. package/dist/utils/configUtils.d.ts.map +1 -0
  84. package/dist/utils/configUtils.js +100 -0
  85. package/dist/utils/configUtils.js.map +1 -0
  86. package/dist/utils/logger.d.ts +59 -0
  87. package/dist/utils/logger.d.ts.map +1 -0
  88. package/dist/utils/logger.js +122 -0
  89. package/dist/utils/logger.js.map +1 -0
  90. package/dist/utils/pathUtils.d.ts +48 -0
  91. package/dist/utils/pathUtils.d.ts.map +1 -0
  92. package/dist/utils/pathUtils.js +128 -0
  93. package/dist/utils/pathUtils.js.map +1 -0
  94. package/package.json +58 -5
  95. package/agent.json +0 -18
  96. package/broker.js +0 -214
  97. package/index.js +0 -382
  98. package/ipc.js +0 -220
  99. package/ipcInterfaces/pythonAbilityIPC.py +0 -177
package/README.md CHANGED
@@ -1,306 +1,1212 @@
1
1
  # @kadi.build/core
2
2
 
3
- ---
3
+ > A comprehensive toolkit for building and managing KADI abilities with multiple transport protocols
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@kadi.build/core.svg)](https://www.npmjs.com/package/@kadi.build/core)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ ## 🎯 Overview
9
+
10
+ `@kadi.build/core` is the foundational library for creating KADI abilities - modular, protocol-agnostic services that can communicate via multiple transport layers. Whether you're building local tools, distributed microservices, or high-performance native modules, this toolkit provides a unified, developer-friendly API that abstracts away transport complexity.
11
+
12
+ ## 📚 Table of Contents
13
+
14
+ - [🎯 Overview](#-overview)
15
+ - [📦 Installation](#-installation)
16
+ - [🚀 Quick Start](#-quick-start)
17
+ - [🔌 Transport Protocols](#-transport-protocols)
18
+ - [🛠️ Creating Abilities](#-creating-abilities)
19
+ - [🤖 Creating Agents](#-creating-agents)
20
+ - [🔄 Architecture Deep Dive](#-architecture-deep-dive)
21
+ - [📥 Loading Abilities](#-loading-abilities)
22
+ - [⚙️ Configuration](#-configuration)
23
+ - [🚀 Advanced Usage](#-advanced-usage)
24
+ - [🔧 Development Workflow](#-development-workflow)
25
+ - [📖 API Reference](#-api-reference)
26
+ - [🎮 Running the Examples](#-running-the-examples)
27
+ - [💡 Additional Examples](#-additional-examples)
28
+ - [🐛 Troubleshooting](#-troubleshooting)
29
+ - [🤝 Contributing](#-contributing)
30
+ - [📄 License](#-license)
31
+ - [🔗 Related Projects](#-related-projects)
32
+ - [📚 Resources](#-resources)
33
+
34
+ ## 📦 Installation
35
+
36
+ ```bash
37
+ npm install @kadi.build/core
38
+ ```
39
+
40
+ For global CLI tools:
41
+
42
+ ```bash
43
+ npm install -g @kadi.build/cli
44
+ ```
45
+
46
+ ## 🚀 Quick Start
47
+
48
+ The library provides a unified way to work with KADI through the **KadiClient** class, which can operate in multiple roles:
49
+
50
+ - **As an Ability**: Serving tools/methods that others can call
51
+ - **As an Agent**: Calling remote tools via broker protocol
52
+ - **As a Service**: Combining both roles - serving and consuming tools
53
+
54
+ ### Creating Your First Service
55
+
56
+ ```javascript
57
+ #!/usr/bin/env node
58
+ import { KadiClient } from '@kadi.build/core';
59
+
60
+ // Create a service instance
61
+ const mathService = new KadiClient({
62
+ name: 'math-service',
63
+ role: 'ability', // 'agent', 'ability', or 'service'
64
+ protocol: 'stdio' // 'native', 'stdio', or 'broker'
65
+ });
66
+
67
+ // Register a tool
68
+ mathService.registerTool('add', async ({ a, b }) => {
69
+ return { result: a + b };
70
+ });
71
+
72
+ // Start serving requests
73
+ mathService.serve().catch(console.error);
74
+ ```
75
+
76
+ ### Loading and Using Abilities
77
+
78
+ ```javascript
79
+ import { loadAbility } from '@kadi.build/core';
80
+
81
+ async function main() {
82
+ // Load an ability (uses first interface defined in agent.json)
83
+ const math = await loadAbility('math-ability');
84
+
85
+ // Call methods like regular functions
86
+ const result = await math.add({ a: 5, b: 3 });
87
+ console.log(result); // { result: 8 }
88
+ }
89
+
90
+ main().catch(console.error);
91
+ ```
92
+
93
+ ### Creating a Broker-Connected Agent
94
+
95
+ ```javascript
96
+ import { KadiClient } from '@kadi.build/core';
97
+
98
+ // Create an agent that connects to broker
99
+ const agent = new KadiClient({
100
+ name: 'my-agent',
101
+ role: 'agent',
102
+ protocol: 'broker',
103
+ brokerUrls: ['ws://localhost:8080'],
104
+ networks: ['global']
105
+ });
106
+
107
+ // Register tools that other agents can call
108
+ agent.registerTool(
109
+ 'greet',
110
+ async ({ name }) => {
111
+ return { greeting: `Hello, ${name}!` };
112
+ },
113
+ {
114
+ description: 'Greet someone by name',
115
+ inputSchema: {
116
+ type: 'object',
117
+ properties: {
118
+ name: { type: 'string', description: 'Name to greet' }
119
+ },
120
+ required: ['name']
121
+ }
122
+ }
123
+ );
124
+
125
+ // Connect to broker and start serving
126
+ await agent.connectToBrokers();
127
+
128
+ // Call tools from OTHER agents connected to the same broker
129
+ const result = await agent.callTool('translate', 'text-tool', {
130
+ text: 'Hello world',
131
+ language: 'spanish'
132
+ });
133
+ console.log(result);
134
+ ```
135
+
136
+ ## 🔌 Transport Protocols
137
+
138
+ KADI abilities support three transport protocols, each optimized for different use cases:
139
+
140
+ ### 1. Native Protocol (Fastest)
141
+
142
+ - **Use Case**: High-performance, in-process execution
143
+ - **How it Works**: Loads abilities as ES modules, enabling direct function calls
144
+ - **Performance**: Zero IPC overhead - functions run in the same process
145
+ - **Best For**: Utility libraries, data processing, performance-critical operations
146
+
147
+ ```javascript
148
+ // Loads as native module by default if available
149
+ const ability = await loadAbility('my-ability', 'native');
150
+ ```
151
+
152
+ ### 2. Stdio Protocol (Balanced)
4
153
 
5
- The `@kadi.build/core` module is a comprehensive toolkit for developers integrating with the KADI infrastructure. This module simplifies tasks such as managing `agent.json` files, spawning processes, interacting with brokers, and handling interprocess communication (IPC).
154
+ - **Use Case**: Language-agnostic local execution
155
+ - **How it Works**: Spawns abilities as child processes, communicates via LSP-style JSON-RPC over stdin/stdout
156
+ - **Performance**: Minimal overhead, reliable local IPC
157
+ - **Best For**: Python scripts, Go binaries, Node.js services, development/testing
158
+
159
+ ```javascript
160
+ // Force stdio protocol
161
+ const ability = await loadAbility('my-ability', 'stdio');
162
+ ```
6
163
 
7
- ## Features
164
+ **Environment Variables Passed to Child Process:**
8
165
 
9
- - **Agent JSON Management**: Manage configurations for tools, agents, or systems using KADI.
10
- - **Process Management**: Support launching and managing subprocesses.
11
- - **Multi-Broker Support**: Dynamic broker selection and management with environment/runtime configuration.
12
- - **Broker Interaction**: Facilitate communications with WebSocket brokers.
13
- - **IPC Support**: Tools for interprocess communication across various programming languages.
166
+ ```bash
167
+ KADI_PROTOCOL=stdio # Identifies the protocol being used
168
+ # Plus all parent process.env variables
169
+ ```
14
170
 
15
- ## Installation
171
+ ### 3. Broker Protocol (Distributed)
16
172
 
17
- Install `@kadi.build/core` using npm:
173
+ - **Use Case**: Distributed services, containerized deployments
174
+ - **How it Works**: Abilities connect to a WebSocket broker for network communication
175
+ - **Performance**: Network overhead, but enables scaling and distribution
176
+ - **Best For**: Microservices, cloud deployments, load-balanced services
177
+
178
+ ```javascript
179
+ // Use broker for distributed execution
180
+ const ability = await loadAbility('my-ability', 'broker');
181
+ ```
182
+
183
+ **Environment Variables Passed to Child Process:**
18
184
 
19
185
  ```bash
20
- npm install @kadi.build/core
186
+ KADI_PROTOCOL=broker # Identifies the protocol
187
+ KADI_BROKER_URL=ws://localhost:8080 # Broker connection URL
188
+ KADI_SERVICE_NAME=ability.echo.1_0_0 # Service identifier
189
+ KADI_AGENT_SCOPE=uuid-here # Agent's scope/namespace
190
+ # Plus all parent process.env variables
21
191
  ```
22
192
 
23
- ## Documentation
193
+ **Scope/Namespace Visibility:**
24
194
 
25
- ### Agent JSON Functions
195
+ The `KADI_AGENT_SCOPE` environment variable represents the agent's namespace. For the ability to be visible to its parent agent via the broker, it must register with the same scope:
26
196
 
27
- - **`getAbilityJSON(abilityName, abilityVersion)`**: Retrieves the `agent.json` for a specified ability.
28
- - **`getAbilityJSONPath(abilityName, abilityVersion)`**: Provides the file path to the `agent.json` for a specific ability.
29
- - **`getAbilityVersionFromArray(abilities, name)`**: Searches ability array provided, and returns the version number for name provided.
30
- - **`getAbilitiesDir()`**: Returns the directory path where abilities are stored.
31
- - **`getProjectJSON()`**: Fetches the `agent.json` for the current project.
32
- - **`getProjectJSONPath()`**: Gets the file path for the project's `agent.json`.
33
- - **`getKadiCoreJSON()`**: Retrieves the `agent.json` for the Kadi core.
34
- - **`getKadiCoreJSONPath()`**: Provides the file path to the Kadi core's `agent.json`.
35
- - **`getKadiJSON()`**: Fetches the `agent.json` for the Kadi system.
36
- - **`getKadiJSONPath()`**: Returns the file path for the Kadi system's `agent.json`.
197
+ ```javascript
198
+ // In your ability code:
199
+ const echoAbility = new KadiAbility({
200
+ name: 'echo-js',
201
+ version: '0.0.1',
202
+ description: 'Echo ability with broker support',
203
+ scope: process.env.KADI_AGENT_SCOPE // Use agent's scope for visibility
204
+ });
37
205
 
38
- ### Broker Management Functions
206
+ // Without this, the ability would only be visible in the 'global' scope
207
+ // and the parent agent wouldn't be able to communicate with it
208
+ ```
39
209
 
40
- The broker management system allows dynamic selection from multiple configured brokers defined in `agent.json`:
210
+ ### Protocol Selection Strategy
211
+
212
+ When no protocol is specified, `loadAbility` uses the **first interface** defined in the ability's `agent.json`. The KADI team recommends ordering interfaces from fastest to slowest (native → stdio → broker) for optimal default performance.
213
+
214
+ ```mermaid
215
+ graph TD
216
+ A[Load Ability] --> B{Protocol Specified?}
217
+ B -->|Yes| C[Use Specified Protocol]
218
+ B -->|No| D{Check agent.json interfaces}
219
+ D --> E[Use First Interface Listed]
220
+ E --> F[native/stdio/broker]
221
+ ```
222
+
223
+ **Recommended Interface Order in agent.json:**
41
224
 
42
225
  ```json
43
226
  {
44
- "brokers": {
45
- "local": "ws://127.0.0.1:8080",
46
- "remote": "ws://production.example.com:8080",
47
- "staging": "ws://staging.example.com:8080"
227
+ "interfaces": {
228
+ "native": { "entry": "service.js" }, // 1st choice: Fastest
229
+ "stdio": { "discover": true }, // 2nd choice: Balanced
230
+ "broker": { "discover": true } // 3rd choice: Distributed
48
231
  }
49
232
  }
50
233
  ```
51
234
 
52
- Available functions:
235
+ ## 🛠️ Creating Services with KadiClient
236
+
237
+ ### Basic Service Structure
238
+
239
+ ```javascript
240
+ import { KadiClient } from '@kadi.build/core';
53
241
 
54
- - **`KADI_BROKERS`**: Object containing all configured brokers with their parsed URLs.
55
- - **`KADI_BROKER_URL`**: Default broker URL (first one defined, for backward compatibility).
56
- - **`getBrokerUrl(brokerName)`**: Get URL for a specific broker by name. Returns `null` if not found.
57
- - **`getBrokerNames()`**: Get array of all available broker names.
58
- - **`setActiveBroker(brokerName)`**: Set the active broker for the session. Returns `true` if successful.
59
- - **`getActiveBrokerName()`**: Get the name of the currently active broker.
60
- - **`getActiveBrokerUrl()`**: Get the URL of the currently active broker.
61
- - **`getDefaultBrokerName()`**: Get the name of the default broker (first one defined).
62
- - **`selectBrokerFromEnv()`**: Set active broker from `KADI_BROKER` environment variable.
242
+ const service = new KadiClient({
243
+ name: 'echo-service',
244
+ role: 'ability', // 'agent', 'ability', or 'service'
245
+ protocol: 'stdio', // 'native', 'stdio', or 'broker'
246
+ brokerUrls: ['ws://localhost:8080']
247
+ });
248
+
249
+ // Method 1: Simple tool registration
250
+ service.registerTool('echo', async ({ message }) => ({
251
+ echo: message,
252
+ timestamp: new Date().toISOString()
253
+ }));
254
+
255
+ // Method 2: Tool with inline schema
256
+ service.registerTool(
257
+ 'format',
258
+ async ({ text, style }) => ({
259
+ formatted: style === 'upper' ? text.toUpperCase() : text.toLowerCase()
260
+ }),
261
+ {
262
+ description: 'Format text with specified style',
263
+ inputSchema: {
264
+ type: 'object',
265
+ properties: {
266
+ text: { type: 'string', description: 'Text to format' },
267
+ style: {
268
+ type: 'string',
269
+ enum: ['upper', 'lower'],
270
+ description: 'Formatting style'
271
+ }
272
+ },
273
+ required: ['text', 'style']
274
+ },
275
+ outputSchema: {
276
+ type: 'object',
277
+ properties: {
278
+ formatted: { type: 'string', description: 'Formatted text' }
279
+ }
280
+ }
281
+ }
282
+ );
283
+
284
+ // Start serving
285
+ service.serve().catch(console.error);
286
+ ```
63
287
 
64
- ### Process Management Functions
288
+ ### Event Publishing and Subscription
65
289
 
66
- - **`runExecCommand(name, version, command)`**: Executes a command for initializing abilities.
67
- - **`runSpawnCommand(name, version, command)`**: Uses `spawn` to execute commands for subprocesses.
290
+ **Full Support**: Events work across all protocols - native, stdio, and broker.
68
291
 
69
- ## Usage Examples
292
+ KadiClient provides a unified event system that works consistently across all transport protocols:
70
293
 
71
294
  ```javascript
72
- import {
73
- getProjectJSON,
74
- runExecCommand,
75
- IPCManager,
76
- Broker
77
- } from '@kadi.build/core';
295
+ // In your service (service.js)
296
+ const service = new KadiClient({
297
+ name: 'my-service',
298
+ role: 'ability',
299
+ protocol: 'broker' // Works with all protocols
300
+ });
301
+
302
+ // Publish events from tool handlers
303
+ service.registerTool('echo', async ({ message }) => {
304
+ // Publish events during processing
305
+ await service.publishEvent('echo.processing', { status: 'started' });
306
+
307
+ // Do work...
308
+ const result = { echo: message, timestamp: new Date().toISOString() };
309
+
310
+ await service.publishEvent('echo.completed', { result });
311
+ return result;
312
+ });
313
+
314
+ // In your client (index.js)
315
+ const client = new KadiClient({
316
+ name: 'my-client',
317
+ role: 'agent',
318
+ protocol: 'broker'
319
+ });
320
+
321
+ // Subscribe to events using patterns
322
+ client.subscribeToEvent('echo.*', (data) => {
323
+ console.log('Echo event received:', data);
324
+ });
325
+
326
+ // For loaded abilities with native/stdio protocols
327
+ const ability = await loadAbility('my-service', 'stdio');
328
+ ability.events.on('echo.completed', (data) => {
329
+ console.log('Completed:', data);
330
+ });
331
+ ```
332
+
333
+ **Event Patterns**:
334
+ - Use wildcards: `math.*` matches `math.operation`, `math.milestone`
335
+ - Protocol-specific delivery:
336
+ - **Native**: Direct EventEmitter (synchronous)
337
+ - **Stdio**: JSON-RPC notifications over stdout
338
+ - **Broker**: RabbitMQ pub/sub via WebSocket
339
+
340
+ ### Ability Configuration (agent.json)
78
341
 
79
- async function setupProject() {
80
- const projectConfig = getProjectJSON();
81
- console.log(projectConfig);
82
-
83
- await runExecCommand('example', '1.0', 'npm install');
84
-
85
- // Broker management examples
86
- console.log('Available brokers:', getBrokerNames());
87
- console.log('All brokers:', KADI_BROKERS);
88
-
89
- // Set active broker
90
- setActiveBroker('remote');
91
- console.log('Active broker:', getActiveBrokerUrl());
92
-
93
- // IPC setup
94
- const ipc = new IPCManager();
95
- ipc.createInstance('python', 'pythonScript.py', 'pythonInstance');
96
-
97
- // Traditional broker setup
98
- Broker.addBroker('ws://example.com', 'exampleBroker');
99
- let broker = Broker.getBroker('default');
100
- console.log(`Connected to ${broker.url}`);
101
- broker.send(
102
- BrokerMessageBuilder.setup(
103
- 'TestAgent',
104
- 'A Broker Testing Agent',
105
- null,
106
- null
107
- )
108
- );
342
+ Every ability should have an `agent.json` file that defines its capabilities and interfaces:
343
+
344
+ ```json
345
+ {
346
+ "name": "echo-ability",
347
+ "kind": "ability",
348
+ "version": "1.0.0",
349
+ "license": "MIT",
350
+ "description": "Echo service with multiple transport support",
351
+ "scripts": {
352
+ "setup": "npm install",
353
+ "start": "node service.js"
354
+ },
355
+ "interfaces": {
356
+ "native": {
357
+ "entry": "service.js"
358
+ },
359
+ "stdio": {
360
+ "discover": true,
361
+ "timeoutMs": 10000
362
+ },
363
+ "broker": {
364
+ "discover": true,
365
+ "timeoutMs": 15000,
366
+ "serviceName": "echo-service"
367
+ }
368
+ },
369
+ "exports": [
370
+ {
371
+ "name": "echo",
372
+ "description": "Echo the input message",
373
+ "inputSchema": {
374
+ "type": "object",
375
+ "properties": {
376
+ "message": { "type": "string" }
377
+ },
378
+ "required": ["message"]
379
+ },
380
+ "outputSchema": {
381
+ "type": "object",
382
+ "properties": {
383
+ "echo": { "type": "string" },
384
+ "timestamp": { "type": "string" }
385
+ }
386
+ }
387
+ }
388
+ ],
389
+ "brokers": {
390
+ "local": "ws://localhost:8080",
391
+ "remote": "ws://api.example.com:8080"
392
+ }
109
393
  }
110
394
  ```
111
395
 
112
- ### Broker Functions
396
+ ### Schema Definition Options
113
397
 
114
- - **`Broker`**: Manages interactions with broker systems.
115
- - **`IBroker`**: Broker instance interface with methods to manage its lifecycle and communications.
116
- - **`BrokerMessageBuilder`**: Assists in constructing messages for broker communication.
398
+ The ability system provides two ways to define method schemas:
117
399
 
118
- ## Broker Functions
400
+ 1. **Export Schemas** (defined in `agent.json` exports section)
401
+ 2. **Inline Schemas** (passed directly to `.method()`)
119
402
 
120
- The Broker system facilitates interaction between different agents and services within the Kadi environment.
403
+ ```javascript
404
+ // Option 1: Define in agent.json exports section
405
+ // The schema is picked up automatically if the method name matches
406
+ // agent.json:
407
+ {
408
+ "exports": [
409
+ {
410
+ "name": "process",
411
+ "description": "Process data",
412
+ "inputSchema": { /* ... */ },
413
+ "outputSchema": { /* ... */ }
414
+ }
415
+ ]
416
+ }
121
417
 
122
- ### `Broker` Interface
418
+ // Option 2: Define inline when registering the method
419
+ ability.method('process', handler, {
420
+ description: 'Process data',
421
+ inputSchema: { /* ... */ },
422
+ outputSchema: { /* ... */ }
423
+ });
123
424
 
124
- - **`addBroker(url, name = 'default')`**: Adds a new broker connection. If a broker with the same name exists, it will be reused.
125
- - **`disconnect(name = 'default')`**: Disconnects the broker specified by the name.
126
- - **`deleteBroker(name = 'default')`**: Deletes the broker specified by the name and closes its connection.
127
- - **`send(message, brokerName = 'default')`**: Sends a message through the broker specified by the name.
128
- - **`addEventListener(event, listener, brokerName = 'default')`**: Adds an event listener to the specified broker.
129
- - **`removeEventListener(event, listener, brokerName = 'default')`**: Removes an event listener from the specified broker.
130
- - **`removeAllListeners(brokerName = 'default')`**: Removes all event listeners from the specified broker.
131
- - **`getBroker(brokerName = 'default')`**: Retrieves a broker instance by name.
425
+ // Note: If both are defined, inline schemas take precedence over exports
426
+ ```
132
427
 
133
- ### `IBroker` Instance
428
+ **Important**: Specifying MCP schemas for ability handlers is particularly important for `broker` communication. If you only need `native` and `stdio` protocols, schemas can be omitted.
134
429
 
135
- This class extends `EventEmitter` and manages individual broker connections:
430
+ ## 🤖 Working with KadiClient
136
431
 
137
- - **`constructor(url, name)`**: Initializes a new broker connection.
138
- - **`send(message)`**: Sends a message through the WebSocket connection.
139
- - **`getConnectedAgents()`**: Retrieves a list of currently connected agents.
432
+ KadiClient is the unified API for all KADI operations. It replaces the previous separate KadiAbility and KadiAgent classes with a single, powerful interface that can operate in multiple roles.
140
433
 
141
- ### `BrokerMessageBuilder`
434
+ ### Direct Approach (using loadAbility)
142
435
 
143
- Utility class for constructing broker messages:
436
+ ```javascript
437
+ import { loadAbility } from '@kadi.build/core';
144
438
 
145
- - **`create_message(type, data)`**: Creates a JSON string with the specified type and data.
146
- - **`message(to, content)`**: Constructs a message directed to a specific peer.
147
- - **`setup(name, description, limit, uuid)`**: Creates a setup message for registering the broker.
148
- - **`suspend()`**: Constructs a suspend message.
149
- - **`finish()`**: Constructs a finish message.
150
- - **`list()`**: Constructs a list message to request a list of connected agents.
439
+ // Load and call abilities directly
440
+ const echoAbility = await loadAbility('echo-js', 'stdio');
441
+ const result = await echoAbility.echo({ message: 'Hello world' });
151
442
 
152
- ### IPC Functions
443
+ // Load broker tools directly
444
+ const remoteService = await loadAbility('remote-service', 'broker', {
445
+ brokerUrl: 'ws://localhost:8080',
446
+ networks: ['global']
447
+ });
448
+ const brokerResult = await remoteService.process({ data: 'test' });
449
+ ```
153
450
 
154
- - **`IPCMessageBuilder`**: Constructs messages for IPC interactions.
155
- - **`IPCManager`**: Manages setup and lifecycle of IPC connections.
156
- - **`IAbilityIPC`**: Represents an IPC ability instance with methods to manage its lifecycle and communications.
451
+ ### Using KadiClient
157
452
 
158
- ### `IPCManager`
453
+ ```javascript
454
+ import { KadiClient } from '@kadi.build/core';
455
+
456
+ // Create a unified client
457
+ const client = new KadiClient({
458
+ name: 'my-service',
459
+ role: 'service', // Can both serve and consume
460
+ protocol: 'broker',
461
+ brokerUrls: ['ws://localhost:8080'],
462
+ networks: ['global']
463
+ });
159
464
 
160
- Manages the lifecycle and setup of interprocess communications.
465
+ // Register tools for others to call
466
+ client.registerTool(
467
+ 'greet',
468
+ async ({ name }) => {
469
+ return { greeting: `Hello, ${name}!` };
470
+ },
471
+ {
472
+ description: 'Greet someone by name',
473
+ inputSchema: {
474
+ type: 'object',
475
+ properties: { name: { type: 'string' } },
476
+ required: ['name']
477
+ }
478
+ }
479
+ );
161
480
 
162
- - **`createInstance(language, commandString, name = 'default')`**: Creates and manages a new IPC instance for the specified language.
163
- - **`getInstance(name)`**: Retrieves an existing IPC instance by name.
164
- - **`shutdownInstance(name)`**: Shuts down an existing IPC instance by name.
481
+ // Connect and serve
482
+ await client.connectToBrokers();
165
483
 
166
- ### `IAbilityIPC` Instance
484
+ // Call remote tools
485
+ const result = await client.callTool('translator', 'translate', {
486
+ text: 'Hello',
487
+ to: 'es'
488
+ });
167
489
 
168
- Manages an individual interprocess communication instance:
490
+ // Load abilities for compatibility
491
+ const ability = await client.loadAbility('echo-js');
492
+ ```
169
493
 
170
- - **`start()`**: Starts the child process associated with this IPC instance.
171
- - **`launch()`**: Sends the launch command to the associated process.
172
- - **`sendMessage(message)`**: Sends a JSON-formatted message to the child process.
173
- - **`shutdown()`**: Sends a shutdown command to the child process and terminates it.
494
+ Use `loadAbility()` for simple consumption, or KadiClient for full-featured service development with tool registration, event publishing, and remote tool invocation.
495
+
496
+ ## 🔄 Architecture Deep Dive
497
+
498
+ ### Transport Architecture and Loading Sequence
499
+
500
+ The loading process uses modular transports based on the selected protocol:
501
+
502
+ ```mermaid
503
+ sequenceDiagram
504
+ participant Client
505
+ participant loadAbility
506
+ participant Transport
507
+ participant FileSystem
508
+ participant ChildProcess
509
+ participant Service
510
+ participant Broker
511
+
512
+ Client->>loadAbility: loadAbility('echo-js', protocol?)
513
+ loadAbility->>FileSystem: Read agent.json manifests
514
+ FileSystem-->>loadAbility: Return config & version
515
+
516
+ alt protocol === 'native'
517
+ loadAbility->>Transport: new NativeTransport(config)
518
+ Transport->>FileSystem: import(entry point)
519
+ FileSystem-->>Transport: Module with KadiClient instance
520
+ Transport->>Service: Extract tool handlers via getToolNames()
521
+ Service-->>Transport: Return registered tools
522
+ Transport->>Transport: Store handlers in Map
523
+ else protocol === 'stdio'
524
+ loadAbility->>Transport: new StdioTransport(config)
525
+ Transport->>ChildProcess: spawn(startCmd, env: {KADI_PROTOCOL})
526
+ ChildProcess->>Service: KadiClient detects stdio mode
527
+ Service->>Service: Setup FrameReader/Writer
528
+ Transport->>Service: Send discovery request
529
+ Service-->>Transport: Return tool list
530
+ Transport->>Transport: Setup JSON-RPC proxy
531
+ else protocol === 'broker'
532
+ loadAbility->>Transport: new BrokerTransport(config)
533
+ Transport->>Transport: Create KadiClient wrapper
534
+ Transport->>Broker: WebSocket connect
535
+ Transport->>Broker: kadi.session.hello
536
+ Broker-->>Transport: { nonce, heartbeatIntervalSec }
537
+ Transport->>Broker: kadi.session.authenticate
538
+ Broker-->>Transport: { sessionId }
539
+ Transport->>Broker: kadi.ability.list
540
+ Broker-->>Transport: Available tools
541
+ Transport->>Transport: Start heartbeat timer
542
+ end
543
+
544
+ loadAbility->>loadAbility: Create proxy with tool methods
545
+ loadAbility-->>Client: Return ability proxy
546
+
547
+ Note over Client,Service: Tool invocation
548
+ Client->>Transport: ability.echo({ message })
549
+
550
+ alt native
551
+ Transport->>Service: Direct handler call
552
+ else stdio
553
+ Transport->>Service: JSON-RPC over frames
554
+ else broker
555
+ Transport->>Broker: kadi.ability.invoke
556
+ Broker->>Service: Route to target
557
+ Service->>Broker: kadi.ability.result
558
+ Broker-->>Transport: Forward result
559
+ end
560
+
561
+ Transport-->>Client: Return result
562
+ ```
174
563
 
175
- ### `IPCMessageBuilder`
564
+ ### KadiClient Architecture: Tool Registration & Serving
565
+
566
+ How KadiClient registers tools and starts serving:
567
+
568
+ ```mermaid
569
+ sequenceDiagram
570
+ participant Dev as Developer
571
+ participant KC as KadiClient
572
+ participant Transport
573
+ participant Network as Network Layer
574
+ participant RemoteClient
575
+
576
+ Note over Dev,KC: Tool Registration Phase
577
+ Dev->>KC: new KadiClient({ name, role, protocol })
578
+ KC->>KC: Initialize based on role & protocol
579
+
580
+ Dev->>KC: registerTool('echo', handler, schema?)
581
+ KC->>KC: Store in toolHandlers Map<name, {handler, schema}>
582
+
583
+ Dev->>KC: registerTool('process', handler, schema)
584
+ KC->>KC: Add to toolHandlers Map
585
+
586
+ Note over KC,Network: Connection & Serving Phase
587
+ Dev->>KC: serve() or connectToBrokers()
588
+
589
+ alt protocol === 'native'
590
+ KC->>KC: Tools available via direct reference
591
+ KC->>KC: Emit 'start' event
592
+ else protocol === 'stdio'
593
+ KC->>Transport: Setup StdioTransport
594
+ Transport->>Transport: new FrameReader(stdin)
595
+ Transport->>Transport: new FrameWriter(stdout)
596
+ Transport->>KC: on('message', handleStdioMessage)
597
+ KC->>KC: Process JSON-RPC requests
598
+ else protocol === 'broker'
599
+ KC->>Network: WebSocket.connect(brokerUrl)
600
+ KC->>Network: kadi.session.hello({ role })
601
+ Network-->>KC: { nonce, heartbeatIntervalSec: 30 }
602
+ KC->>KC: Sign nonce with Ed25519
603
+ KC->>Network: kadi.session.authenticate({ signature })
604
+ Network-->>KC: { sessionId }
605
+ KC->>Network: kadi.agent.register({ tools, networks })
606
+ KC->>KC: Start heartbeat timer (30s interval)
607
+ end
608
+
609
+ Note over RemoteClient,KC: Tool Invocation Flow
610
+ RemoteClient->>Network: Request tool invocation
611
+
612
+ alt native
613
+ RemoteClient->>KC: Direct method call
614
+ KC->>KC: toolHandlers.get(name).handler(params)
615
+ else stdio
616
+ Network->>Transport: JSON-RPC frame
617
+ Transport->>KC: Parsed request
618
+ KC->>KC: toolHandlers.get(name).handler(params)
619
+ KC->>Transport: JSON-RPC response
620
+ Transport->>Network: Framed response
621
+ else broker
622
+ Network->>KC: kadi.ability.invoke message
623
+ KC->>KC: toolHandlers.get(toolName).handler(params)
624
+ KC->>Network: kadi.ability.result message
625
+ end
626
+
627
+ Network-->>RemoteClient: Tool result
628
+ ```
176
629
 
177
- Utility class for constructing IPC messages:
630
+ ### Event System Architecture
631
+
632
+ KadiClient provides a unified event API that works across all transport protocols:
633
+
634
+ ```mermaid
635
+ sequenceDiagram
636
+ participant Publisher as Service (Publisher)
637
+ participant Transport as Transport Layer
638
+ participant EventBus as Event Bus
639
+ participant Subscriber as Client (Subscriber)
640
+
641
+ Note over Subscriber: Setup subscription
642
+ Subscriber->>Subscriber: subscribeToEvent('math.*', callback)
643
+
644
+ alt protocol === 'broker'
645
+ Subscriber->>EventBus: kadi.event.subscribe({channels: ['math.*']})
646
+ EventBus-->>Subscriber: {subscribed: ['math.*'], queueName}
647
+ else protocol === 'stdio'
648
+ Subscriber->>Transport: Setup event listener
649
+ Transport->>Subscriber: on('transport:event', dispatcher)
650
+ else protocol === 'native'
651
+ Subscriber->>Publisher: Direct EventEmitter reference
652
+ Publisher->>Subscriber: on('ability:event', dispatcher)
653
+ end
654
+
655
+ Note over Publisher: Publish event
656
+ Publisher->>Publisher: publishEvent('math.operation', data)
657
+
658
+ alt protocol === 'native'
659
+ Publisher->>Publisher: emit('ability:event', {eventName, data})
660
+ Publisher-->>Subscriber: Direct EventEmitter delivery
661
+ Subscriber->>Subscriber: Pattern match & invoke callback
662
+ else protocol === 'stdio'
663
+ Publisher->>Transport: writer.write(__kadi_event notification)
664
+ Transport->>Transport: Frame with LSP headers
665
+ Transport-->>Subscriber: Framed event over stdout
666
+ Subscriber->>Transport: FrameReader parses
667
+ Transport->>Subscriber: emit('transport:event', {name, data})
668
+ Subscriber->>Subscriber: Pattern match & invoke callback
669
+ else protocol === 'broker'
670
+ Publisher->>EventBus: kadi.event.publish({channel, data})
671
+ EventBus->>EventBus: Route via RabbitMQ
672
+ EventBus-->>Subscriber: kadi.event.delivery
673
+ Subscriber->>Subscriber: Pattern match & invoke callback
674
+ end
675
+
676
+ Note over Subscriber: Cleanup
677
+ Subscriber->>Subscriber: unsubscribe()
678
+ alt protocol === 'broker'
679
+ Subscriber->>EventBus: kadi.event.unsubscribe
680
+ else
681
+ Subscriber->>Subscriber: Remove local listener
682
+ end
683
+ ```
178
684
 
179
- - **`createMessage(type, contents)`**: Creates a JSON string with the specified type and contents.
180
- - **`launch(commandString)`**: Constructs a launch message with the specified command string.
181
- - **`shutdown()`**: Constructs a shutdown message.
182
- - **`message(content)`**: Constructs a general message with the specified content.
685
+ ### Broker Protocol Communication Flow
686
+
687
+ Detailed view of broker-based communication with heartbeat:
688
+
689
+ ```mermaid
690
+ sequenceDiagram
691
+ participant Client as KadiClient
692
+ participant Broker as KADI Broker
693
+ participant Target as Target Service
694
+
695
+ Note over Client,Broker: Connection & Authentication
696
+ Client->>Broker: WebSocket connect
697
+ Client->>Broker: kadi.session.hello({ role })
698
+ Broker-->>Client: { nonce, heartbeatIntervalSec: 30 }
699
+ Client->>Client: Generate Ed25519 keypair
700
+ Client->>Client: Sign nonce
701
+ Client->>Broker: kadi.session.authenticate({ signature })
702
+ Broker-->>Client: { sessionId }
703
+
704
+ Note over Client,Broker: Heartbeat Management
705
+ Client->>Client: Start heartbeat timer (30s)
706
+ loop Every 30 seconds
707
+ Client->>Broker: kadi.session.ping
708
+ Note over Broker: Reset connection timeout (90s)
709
+ end
710
+
711
+ Note over Client,Broker: Tool Registration
712
+ Client->>Broker: kadi.agent.register({ tools, networks })
713
+ Broker->>Broker: Store in registry
714
+ Broker-->>Client: { registered: true }
715
+
716
+ Note over Client,Target: Remote Tool Invocation
717
+ Client->>Client: callTool('service-b', 'process', params)
718
+ Client->>Broker: kadi.ability.invoke({
719
+ targetAgent: 'service-b',
720
+ toolName: 'process',
721
+ toolInput: params
722
+ })
723
+ Broker->>Broker: Lookup service-b
724
+ Broker->>Target: kadi.ability.invoke
725
+ Target->>Target: Execute tool handler
726
+ Target->>Broker: JSON-RPC response
727
+ Broker-->>Client: Forward response
728
+ Client->>Client: Resolve promise
729
+
730
+ Note over Client,Broker: Event Publishing
731
+ Client->>Broker: kadi.event.publish({
732
+ channel: 'system.status',
733
+ data: { status: 'healthy' }
734
+ })
735
+ Broker->>Broker: Publish to RabbitMQ exchange
736
+
737
+ Note over Client,Broker: Graceful Shutdown
738
+ Client->>Client: Stop heartbeat timer
739
+ Client->>Broker: kadi.session.goodbye
740
+ Client->>Client: Close WebSocket
741
+ ```
183
742
 
184
- ## Usage Examples
743
+ ### Stdio Protocol: LSP-Style Frame Processing
744
+
745
+ How KadiClient handles stdio communication with LSP-style framing:
746
+
747
+ ```mermaid
748
+ sequenceDiagram
749
+ participant Parent as Parent Process
750
+ participant SR as StdioFrameReader
751
+ participant SW as StdioFrameWriter
752
+ participant Server as StdioRpcServer
753
+ participant Ability as KadiAbility
754
+
755
+ Note over Parent,SR: Incoming Request
756
+ Parent->>SR: Write to stdin: "Kadi-Content-Length: 52\r\n\r\n{...}"
757
+ SR->>SR: Buffer incoming data
758
+ SR->>SR: Look for Content-Length header
759
+ SR->>SR: Parse header, extract body length
760
+ SR->>SR: Wait for complete body
761
+ SR->>SR: Parse JSON from body
762
+ SR->>Server: onMessage({ success: true, data: request })
763
+
764
+ Server->>Server: Validate JSON-RPC format
765
+ Server->>Ability: getMethodHandler(request.method)
766
+ Ability-->>Server: Return handler
767
+ Server->>Server: Execute handler(params) with timeout
768
+
769
+ Note over Server,Parent: Response
770
+ Server->>SW: write(response)
771
+ SW->>SW: JSON.stringify(response)
772
+ SW->>SW: Calculate byte length
773
+ SW->>SW: Create header: "Kadi-Content-Length: N\r\n\r\n"
774
+ SW->>Parent: Write header + body to stdout
775
+
776
+ Note over SR: Error Recovery
777
+ alt Frame Corruption
778
+ SR->>SR: Detect invalid frame
779
+ SR->>SR: Search for next Content-Length marker
780
+ SR->>SR: Skip corrupted bytes
781
+ SR->>Server: onMessage({ success: false, error: 'FRAME_CORRUPTION' })
782
+ Server->>Server: Log error, continue processing
783
+ end
784
+ ```
185
785
 
186
- Here's how you might use the IPC and Broker functionalities:
786
+ ## 📥 Loading Abilities
187
787
 
188
- ### Interacting with IPC
788
+ ### Basic Loading
189
789
 
190
790
  ```javascript
191
- console.log('Starting IPC Ability Interface test...');
791
+ import { loadAbility } from '@kadi.build/core';
192
792
 
193
- // Create Python IPC instance
194
- const pythonIPC = IPCManager.createInstance(
195
- 'python',
196
- 'echo "Hello, World!"',
197
- 'pythonTest'
198
- );
793
+ // Auto-detect protocol from agent.json
794
+ const ability = await loadAbility('my-ability');
795
+
796
+ // Explicitly specify protocol
797
+ const stdioAbility = await loadAbility('my-ability', 'stdio');
798
+ const brokerAbility = await loadAbility('my-ability', 'broker');
799
+ const nativeAbility = await loadAbility('my-ability', 'native');
800
+ ```
801
+
802
+ ### Working with Loaded Abilities
199
803
 
200
- pythonIPC.on('error', (error) => {
201
- console.error('Handled Python Error:', error);
202
- // Consider appropriate error handling or recovery actions here
804
+ ```javascript
805
+ // Call methods directly - no need to discover them first
806
+ const result = await ability.echo({ message: 'hello' });
807
+
808
+ // Call methods
809
+ const result = await ability.someMethod({ param: 'value' });
810
+
811
+ // Direct RPC call (bypasses method validation)
812
+ const response = await ability.call('someMethod', { param: 'value' });
813
+
814
+ // Subscribe to events
815
+ ability.events.on('custom:event', (data) => {
816
+ console.log('Event received:', data);
817
+ });
818
+
819
+ // Note: Events work for native and stdio protocols
820
+ // Subscribe before calling methods that emit events
821
+ ```
822
+
823
+ ### Loading Context
824
+
825
+ The loader automatically resolves ability versions from:
826
+
827
+ 1. **Project context**: Reads from project's `agent.json`
828
+ 2. **Nested context**: When loading from within another ability
829
+ 3. **Explicit version**: Can be specified in ability name
830
+
831
+ ## ⚙️ Configuration
832
+
833
+ ### Environment Variables
834
+
835
+ ```bash
836
+ # Set default protocol
837
+ export KADI_PROTOCOL=stdio
838
+
839
+ # Configure broker
840
+ export KADI_BROKER_URL=ws://localhost:8080
841
+ export KADI_SERVICE_NAME=my-service
842
+ export KADI_AGENT_SCOPE=project-123
843
+ ```
844
+
845
+ ### Environment Variables for Child Processes
846
+
847
+ When abilities are spawned as child processes (stdio and broker protocols), the parent process passes down important environment variables:
848
+
849
+ ```javascript
850
+ // In your ability code, you can access these variables:
851
+ const protocol = process.env.KADI_PROTOCOL; // 'stdio' or 'broker'
852
+ const brokerUrl = process.env.KADI_BROKER_URL;
853
+ const serviceName = process.env.KADI_SERVICE_NAME;
854
+ const agentScope = process.env.KADI_AGENT_SCOPE;
855
+
856
+ // Use them to configure your ability behavior
857
+ const ability = new KadiAbility({
858
+ name: 'my-ability',
859
+ scope: process.env.KADI_AGENT_SCOPE || 'global'
860
+ // Ability automatically uses KADI_PROTOCOL to determine transport
861
+ });
862
+ ```
863
+
864
+ ### Example Project Structure
865
+
866
+ ```
867
+ my-project/
868
+ ├── agent.json # Project configuration
869
+ ├── abilities/ # Installed abilities
870
+ │ └── echo-ability/
871
+ │ └── 1.0.0/
872
+ │ ├── agent.json # Ability configuration
873
+ │ ├── service.js # Ability implementation
874
+ │ └── package.json
875
+ ├── modules/ # Source modules
876
+ │ └── echo-ability/ # Development version
877
+ └── index.js # Main entry point
878
+ ```
879
+
880
+ ## 🚀 Advanced Usage
881
+
882
+ ### Event System
883
+
884
+ KadiClient provides a comprehensive event system that works across all protocols:
885
+
886
+ #### 1. Lifecycle Events
887
+
888
+ Monitor the service lifecycle:
889
+
890
+ ```javascript
891
+ const client = new KadiClient({ name: 'my-service' });
892
+
893
+ // Connection events
894
+ client.on('connected', ({ broker }) => {
895
+ console.log(`Connected to broker: ${broker}`);
203
896
  });
204
897
 
205
- pythonIPC.on('critical-error', (error) => {
206
- console.error('Critical Error from Python:', error);
207
- // Handle critical errors, potentially restarting the process or alerting administrators
898
+ client.on('disconnected', () => {
899
+ console.log('Disconnected from broker');
208
900
  });
209
901
 
210
- pythonIPC.on('message', (msg) => {
211
- console.log('Message from Python:', msg);
902
+ // Tool invocation events
903
+ client.on('tool:invoked', ({ toolName, params }) => {
904
+ console.log(`Tool ${toolName} invoked with:`, params);
212
905
  });
213
906
 
214
- pythonIPC.on('broker', (msg) => {
215
- console.log('Broker message from Python:', msg);
907
+ client.on('tool:completed', ({ toolName, result }) => {
908
+ console.log(`Tool ${toolName} completed:`, result);
216
909
  });
217
910
 
218
- pythonIPC.on('shutdown', () => {
219
- console.log('Python IPC has shut down.');
911
+ client.on('error', (error) => {
912
+ console.error('Service error:', error);
220
913
  });
914
+ ```
915
+
916
+ #### 2. Custom Events (All Protocols)
221
917
 
222
- Broker.addBroker('http://your.broker.com', 'default');
223
- let brk = Broker.getBroker('default');
224
- broker = brk;
225
-
226
- pythonIPC.attachBroker(brk);
227
-
228
- brk.on('open', function open() {
229
- console.log(`Connected to ${brk.url}`);
230
- const jsonObject = BrokerMessageBuilder.setup(
231
- 'TestAgent',
232
- 'A Broker Testing Agent',
233
- null,
234
- null
235
- );
236
- brk.send(jsonObject);
918
+ Publish and subscribe to custom events across all transport protocols:
919
+
920
+ ```javascript
921
+ // Publishing events
922
+ const publisher = new KadiClient({
923
+ name: 'event-publisher',
924
+ role: 'service',
925
+ protocol: 'broker' // Works with all protocols
237
926
  });
238
927
 
239
- brk.on('message', function incoming(data) {
240
- let msg = JSON.parse(data);
241
- if (msg.type == 'setup') {
242
- (async () => {
243
- try {
244
- await GetLLMProxy();
245
- } catch (error) {
246
- console.error('Error during GetLLMProxy:', error.message);
247
- }
248
- })();
928
+ publisher.registerTool('process', async ({ data }) => {
929
+ // Publish events during processing
930
+ await publisher.publishEvent('process.started', {
931
+ timestamp: Date.now(),
932
+ data
933
+ });
934
+
935
+ // Do work...
936
+ const result = await processData(data);
937
+
938
+ await publisher.publishEvent('process.completed', {
939
+ timestamp: Date.now(),
940
+ result
941
+ });
942
+
943
+ return result;
944
+ });
945
+
946
+ // Subscribing to events
947
+ const subscriber = new KadiClient({
948
+ name: 'event-subscriber',
949
+ role: 'agent',
950
+ protocol: 'broker'
951
+ });
952
+
953
+ // Subscribe with wildcards
954
+ subscriber.subscribeToEvent('process.*', (data) => {
955
+ console.log('Process event:', data);
956
+ });
957
+
958
+ // One-time subscription
959
+ subscriber.onceEvent('process.completed', (data) => {
960
+ console.log('First completion:', data);
961
+ });
962
+
963
+ // Multiple pattern subscription
964
+ subscriber.subscribeToEvents(
965
+ ['process.*', 'system.*'],
966
+ (pattern, data) => {
967
+ console.log(`Event from ${pattern}:`, data);
249
968
  }
250
- // console.log('Received:', formatJSON(data));
969
+ );
970
+ ```
971
+
972
+ **Protocol-Specific Behavior:**
973
+
974
+ - **Native**: Direct EventEmitter, synchronous delivery
975
+ - **Stdio**: JSON-RPC notifications with LSP framing
976
+ - **Broker**: RabbitMQ pub/sub via WebSocket, persistent queues available
977
+
978
+
979
+ ## 🔧 Development Workflow
980
+
981
+ ### Testing Local Changes
982
+
983
+ When developing new features or testing changes to `@kadi.build/core` before publishing to NPM:
984
+
985
+ 0. **clone the repository**
986
+
987
+ ```bash
988
+ git clone https://gitlab.com/humin-game-lab/kadi/kadi-core.git
989
+ ```
990
+
991
+ 1. **Pack the local version** (in the kadi-core directory):
992
+
993
+ ```bash
994
+ cd /path/to/kadi-core
995
+ npm pack
996
+ # This creates: kadi.build-core-X.Y.Z.tgz
997
+ ```
998
+
999
+ 1. **Install in your project**:
1000
+
1001
+ ```bash
1002
+ cd /path/to/your-project
1003
+ npm install /path/to/kadi-core/kadi.build-core-X.Y.Z.tgz
1004
+ ```
1005
+
1006
+ 1. **For ability development**, update the ability's preflight script:
1007
+
1008
+ ```json
1009
+ {
1010
+ "scripts": {
1011
+ "preflight": "npm uninstall @kadi.build/core && npm install /path/to/kadi.build-core-X.Y.Z.tgz"
1012
+ }
1013
+ }
1014
+ ```
1015
+
1016
+ ## 📖 API Reference
1017
+
1018
+ ### Core Classes
1019
+
1020
+ #### `KadiClient`
1021
+
1022
+ The unified class for all KADI operations - serving tools, calling remote services, and managing events.
1023
+
1024
+ ```javascript
1025
+ import { KadiClient } from '@kadi.build/core';
1026
+
1027
+ const client = new KadiClient({
1028
+ name: 'my-service',
1029
+ role: 'service', // 'agent', 'ability', or 'service'
1030
+ protocol: 'broker', // 'native', 'stdio', or 'broker'
1031
+ brokerUrls: ['ws://localhost:8080'],
1032
+ networks: ['global', 'custom-network']
251
1033
  });
1034
+ ```
1035
+
1036
+ **Configuration Options:**
1037
+ - `name` - Service/agent name
1038
+ - `role` - Operating role: 'agent' (consumer), 'ability' (provider), 'service' (both)
1039
+ - `protocol` - Transport protocol to use
1040
+ - `brokerUrls` - Array of broker WebSocket URLs (for broker protocol)
1041
+ - `networks` - Network namespaces for tool discovery
1042
+ - `heartbeatIntervalSec` - Custom heartbeat interval (default: from broker)
1043
+
1044
+ **Tool Registration Methods:**
1045
+ - `registerTool(name, handler, schema?)` - Register a single tool
1046
+ - `getTools()` - Get list of registered tool names
1047
+ - `getToolNames()` - Get filtered list of tool names
1048
+ - `getToolHandler(name)` - Get a specific tool's handler
1049
+ - `getToolSchema(name)` - Get a specific tool's schema
1050
+ - `hasTool(name)` - Check if a tool is registered
1051
+
1052
+ **Remote Tool Invocation:**
1053
+ - `callTool(targetAgent, toolName, params)` - Call a remote tool via broker
1054
+ - `discoverRemoteTools(targetAgent)` - List tools from a remote agent
1055
+ - `loadAbility(name, protocol?, options?)` - Load an ability (compatibility)
1056
+
1057
+ **Event System:**
1058
+ - `publishEvent(eventName, data)` - Publish an event
1059
+ - `subscribeToEvent(pattern, callback)` - Subscribe with wildcards
1060
+ - `subscribeToEvents(patterns[], callback)` - Multiple subscriptions
1061
+ - `unsubscribeFromEvent(pattern, callback)` - Remove subscription
1062
+ - `onceEvent(pattern, callback)` - One-time subscription
1063
+
1064
+ **Connection Management:**
1065
+ - `serve()` - Start serving (stdio/native modes)
1066
+ - `connectToBrokers(urls?)` - Connect to broker(s)
1067
+ - `disconnect()` - Clean disconnect
1068
+ - `isConnected` - Check connection status
1069
+
1070
+ **Properties:**
1071
+ - `agentId` - Unique agent identifier
1072
+ - `name` - Service name
1073
+ - `role` - Current operating role
1074
+ - `protocol` - Active protocol
1075
+
1076
+ **Complete Usage Example:**
252
1077
 
253
- brk.on('error', function error(error) {
254
- console.error('WebSocket error:', error);
1078
+ ```javascript
1079
+ import { KadiClient } from '@kadi.build/core';
1080
+
1081
+ // Create a service that can both serve and consume
1082
+ const service = new KadiClient({
1083
+ name: 'math-service',
1084
+ role: 'service',
1085
+ protocol: 'broker',
1086
+ brokerUrls: ['ws://localhost:8080'],
1087
+ networks: ['global']
255
1088
  });
256
1089
 
257
- brk.on('close', function close() {
258
- console.log('Disconnected from the server');
259
- process.exit(0); // Exit the process when the WebSocket connection is closed
1090
+ // Register tools with schemas
1091
+ service.registerTool(
1092
+ 'add',
1093
+ async ({ a, b }) => {
1094
+ // Publish event before processing
1095
+ await service.publishEvent('math.operation', {
1096
+ operation: 'add',
1097
+ inputs: { a, b }
1098
+ });
1099
+
1100
+ const result = a + b;
1101
+
1102
+ // Publish completion event
1103
+ await service.publishEvent('math.completed', {
1104
+ operation: 'add',
1105
+ result
1106
+ });
1107
+
1108
+ return { result };
1109
+ },
1110
+ {
1111
+ description: 'Add two numbers',
1112
+ inputSchema: {
1113
+ type: 'object',
1114
+ properties: {
1115
+ a: { type: 'number', description: 'First number' },
1116
+ b: { type: 'number', description: 'Second number' }
1117
+ },
1118
+ required: ['a', 'b']
1119
+ },
1120
+ outputSchema: {
1121
+ type: 'object',
1122
+ properties: {
1123
+ result: { type: 'number', description: 'Sum of a and b' }
1124
+ }
1125
+ }
1126
+ }
1127
+ );
1128
+
1129
+ // Subscribe to events from other services
1130
+ service.subscribeToEvent('system.*', (data) => {
1131
+ console.log('System event:', data);
260
1132
  });
261
1133
 
262
- // Send launch command
1134
+ // Connect to broker
1135
+ await service.connectToBrokers();
263
1136
 
264
- //Utilize log files
265
- // pythonIPC.sendMessage(IPCMessageBuilder.launch(pythonIPC.commandString, process.cwd() + "/logs"));
1137
+ // Call remote tools
1138
+ const result = await service.callTool(
1139
+ 'translator-service',
1140
+ 'translate',
1141
+ { text: 'Hello', to: 'es' }
1142
+ );
266
1143
 
267
- //No log files created
268
- pythonIPC.sendMessage(IPCMessageBuilder.launch(pythonIPC.commandString));
1144
+ // Graceful shutdown
1145
+ process.on('SIGTERM', async () => {
1146
+ await service.disconnect();
1147
+ process.exit(0);
1148
+ });
269
1149
  ```
270
1150
 
271
- ### Handling IPC
1151
+ #### `loadAbility`
1152
+
1153
+ Load an ability by name and protocol.
272
1154
 
273
1155
  ```javascript
274
- const ipcManager = new IPCManager();
275
- const ipcInstance = ipcManager.createInstance('node', 'app.js', 'nodeApp');
276
- ipcInstance.on('message', (message) => console.log(message));
1156
+ import { loadAbility } from '@kadi.build/core';
1157
+
1158
+ const ability = await loadAbility('ability-name', 'protocol');
277
1159
  ```
278
1160
 
1161
+ **Returns:** A proxy object with:
1162
+
1163
+ - Direct method calls (e.g., `ability.echo({ message: 'hello' })`)
1164
+ - `ability.events` - EventEmitter for subscribing to events (native/stdio protocols only)
1165
+ - `ability.__call(method, params)` - Direct RPC call
1166
+
1167
+ ### Utility Functions
1168
+
1169
+ ```javascript
1170
+ import {
1171
+ createLogger,
1172
+ getProjectJSON,
1173
+ getAbilityJSON,
1174
+ getBrokerUrl,
1175
+ runExecCommand
1176
+ } from '@kadi.build/core';
1177
+ ```
1178
+
1179
+ ### Debug Mode
1180
+
1181
+ Enable detailed logging:
1182
+
1183
+ ```bash
1184
+ DEBUG=kadi:* node index.js
1185
+ ```
1186
+
1187
+ Check ability logs:
1188
+
1189
+ ```bash
1190
+ tail -f abilities/my-ability/1.0.0/my-ability.log
1191
+ ```
1192
+
1193
+ ## 🤝 Contributing
1194
+
1195
+ We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
1196
+
1197
+ 1. Fork the repository
1198
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
1199
+ 3. Commit your changes (`git commit -m 'Add amazing feature'`)
1200
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
1201
+ 5. Open a Pull Request
1202
+
1203
+
1204
+
1205
+ ## 🔗 Related Projects
1206
+
1207
+ - [@kadi.build/cli](https://gitlab.com/humin-game-lab/kadi/kadi) - Command-line interface
1208
+ - [@kadi.build/broker](https://gitlab.com/humin-game-lab/kadi/kadi-broker) - The KADI broker
1209
+
279
1210
  ---
280
1211
 
281
- ## TODO:
282
-
283
- - Once @kadi.build/core and dependencies are done, @kadi.build/cli (and all commands), need to be rewritten, using the @kadi.build/core module. this will be v1.0 release
284
-
285
- * [ ] add getConnectedAgents to the Broker, it should call the IBroker method
286
-
287
- * [x] add key to IBroker object
288
- * [ ] IBroker on 'message' should listen for setup event, and update teh uuid and key for the IBroker object
289
-
290
- * [ ] Extract Broker and IPC to their own node module and host on npm
291
- * [x] Publish @kadi.build/core to npm
292
- * [x] Setup of python environment needs to happen once an IPC Inteface is created
293
- This include setting up the virtual environment and installing the required packages
294
- * [x] Add method to IPCManger to set IPCInterfaces from agent.json. If IPCManger has empty list, it thorws an error that no IAbilityIPC's can be created. IPCManger passes the proper interface command to IAbilityIPC on creation from this list, IAbilityIPC does not need access directly to agent.json. Controlling app can update this list anytime a new IAbilityInterface is created, allowing the agent.json to be updated during runtime.
295
- * [x] Need to adapt ipc.js to read from the @kadi.build/core folder when trying to open IPC instances, instead of project folder. @kadi.build/core should be in the npm_modules folder (when deployed)
296
- * [x] The test code needs to fully test the IPCManger interface, and not just the IPCAbility
297
- * [x] If node fails, we need to make sure python IPC interface and child processes also close out.
298
- * [x] Add buffer to error handeling in IAbilityIPC, mimic same process used in std input
299
- * [x] Verify if Python IAbilityIPC needs to utilize buffering, and node passing '\n' at end of jso
300
- * [x] Setup passthrough for broker messages to/from Language IPC interface
301
- * [x] Add broker message builder/parser in python IAbilityIPC
302
- * [x] Update BrokerMessageBuilder to use same format at IPCMessageBuilder
303
- * [x] Update Broker so it extends EventEmitter, rather than have a property of event emitter (similar to IAbilityIPC)
304
- * [x] update python IAbility so that it creates a timestamped command_output.log file, new file for each run, in the project root directory... not the @kadi.build/core directory
305
- * [x] Comment out debug messages
306
- * [x] Add ability to allow for launched process to be piped to event handler (launch command should default to process, but allow user to set flag that will save all outputs to file in the event handler)
1212
+ Built with ❤️ by the KADI team