@kadi.build/core 0.0.1-alpha.10 → 0.0.1-alpha.12

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 (71) hide show
  1. package/README.md +269 -1311
  2. package/dist/abilities/AbilityLoader.d.ts +26 -0
  3. package/dist/abilities/AbilityLoader.d.ts.map +1 -1
  4. package/dist/abilities/AbilityLoader.js +141 -18
  5. package/dist/abilities/AbilityLoader.js.map +1 -1
  6. package/dist/abilities/AbilityProxy.d.ts +33 -0
  7. package/dist/abilities/AbilityProxy.d.ts.map +1 -1
  8. package/dist/abilities/AbilityProxy.js +40 -0
  9. package/dist/abilities/AbilityProxy.js.map +1 -1
  10. package/dist/abilities/index.d.ts +1 -1
  11. package/dist/abilities/index.d.ts.map +1 -1
  12. package/dist/abilities/types.d.ts +67 -0
  13. package/dist/abilities/types.d.ts.map +1 -1
  14. package/dist/broker/BrokerProtocol.js +11 -11
  15. package/dist/broker/BrokerProtocol.js.map +1 -1
  16. package/dist/client/KadiClient.d.ts +191 -2
  17. package/dist/client/KadiClient.d.ts.map +1 -1
  18. package/dist/client/KadiClient.js +412 -2
  19. package/dist/client/KadiClient.js.map +1 -1
  20. package/dist/index.d.ts +3 -2
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +5 -1
  23. package/dist/index.js.map +1 -1
  24. package/dist/messages/index.d.ts +1 -1
  25. package/dist/messages/index.js +1 -1
  26. package/dist/messages/index.js.map +1 -1
  27. package/dist/schemas/index.d.ts +3 -0
  28. package/dist/schemas/index.d.ts.map +1 -1
  29. package/dist/schemas/index.js +2 -0
  30. package/dist/schemas/index.js.map +1 -1
  31. package/dist/schemas/zod-helpers.d.ts +129 -0
  32. package/dist/schemas/zod-helpers.d.ts.map +1 -0
  33. package/dist/schemas/zod-helpers.js +225 -0
  34. package/dist/schemas/zod-helpers.js.map +1 -0
  35. package/dist/schemas/zod-to-json-schema.d.ts +159 -0
  36. package/dist/schemas/zod-to-json-schema.d.ts.map +1 -0
  37. package/dist/schemas/zod-to-json-schema.js +154 -0
  38. package/dist/schemas/zod-to-json-schema.js.map +1 -0
  39. package/dist/transports/NativeTransport.d.ts +29 -0
  40. package/dist/transports/NativeTransport.d.ts.map +1 -1
  41. package/dist/transports/NativeTransport.js +98 -3
  42. package/dist/transports/NativeTransport.js.map +1 -1
  43. package/dist/transports/StdioTransport.d.ts +141 -63
  44. package/dist/transports/StdioTransport.d.ts.map +1 -1
  45. package/dist/transports/StdioTransport.js +309 -232
  46. package/dist/transports/StdioTransport.js.map +1 -1
  47. package/dist/types/broker.d.ts +0 -22
  48. package/dist/types/broker.d.ts.map +1 -1
  49. package/dist/types/broker.js +0 -27
  50. package/dist/types/broker.js.map +1 -1
  51. package/dist/types/index.d.ts +3 -1
  52. package/dist/types/index.d.ts.map +1 -1
  53. package/dist/types/index.js +1 -1
  54. package/dist/types/index.js.map +1 -1
  55. package/dist/types/zod-tools.d.ts +198 -0
  56. package/dist/types/zod-tools.d.ts.map +1 -0
  57. package/dist/types/zod-tools.js +14 -0
  58. package/dist/types/zod-tools.js.map +1 -0
  59. package/dist/utils/LockfileResolver.d.ts +108 -0
  60. package/dist/utils/LockfileResolver.d.ts.map +1 -0
  61. package/dist/utils/LockfileResolver.js +230 -0
  62. package/dist/utils/LockfileResolver.js.map +1 -0
  63. package/dist/utils/StdioMessageReader.d.ts +122 -0
  64. package/dist/utils/StdioMessageReader.d.ts.map +1 -0
  65. package/dist/utils/StdioMessageReader.js +209 -0
  66. package/dist/utils/StdioMessageReader.js.map +1 -0
  67. package/dist/utils/StdioMessageWriter.d.ts +104 -0
  68. package/dist/utils/StdioMessageWriter.d.ts.map +1 -0
  69. package/dist/utils/StdioMessageWriter.js +162 -0
  70. package/dist/utils/StdioMessageWriter.js.map +1 -0
  71. package/package.json +2 -1
package/README.md CHANGED
@@ -1,75 +1,45 @@
1
1
  # @kadi.build/core
2
2
 
3
- > A comprehensive toolkit for building and managing KADI abilities with multiple transport protocols
3
+ > Framework for building distributed abilities with multiple transport protocols
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/@kadi.build/core.svg)](https://www.npmjs.com/package/@kadi.build/core)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
7
 
8
- ## 🎯 Overview
9
-
10
- `@kadi.build/core` is the foundational library for creating KADI abilities - modular, protocol-agnostic components that can communicate via multiple transport layers. Whether you're building local tools, distributed systems, 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
8
+ ## Installation
35
9
 
36
10
  ```bash
37
11
  npm install @kadi.build/core
38
12
  ```
39
13
 
40
- For global CLI tools:
14
+ ## Quick Start
41
15
 
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
-
53
- ### Creating Your First Ability
16
+ ### Creating an Ability
54
17
 
55
18
  ```javascript
56
- #!/usr/bin/env node
57
- import { KadiClient } from '@kadi.build/core';
19
+ import { KadiClient, z } from '@kadi.build/core';
58
20
 
59
- // Create a ability instance
60
- const mathAbility = new KadiClient({
21
+ const ability = new KadiClient({
61
22
  name: 'math-ability',
62
- role: 'ability', // 'agent', 'ability'
63
- protocol: 'stdio' // 'native', 'stdio', or 'broker'
23
+ version: '1.0.0'
64
24
  });
65
25
 
66
- // Register a tool
67
- mathAbility.registerTool('add', async ({ a, b }) => {
26
+ // Register tool with Zod schemas (recommended)
27
+ ability.registerTool({
28
+ name: 'add',
29
+ description: 'Add two numbers',
30
+ input: z.object({
31
+ a: z.number().describe('First number'),
32
+ b: z.number().describe('Second number')
33
+ }),
34
+ output: z.object({
35
+ result: z.number().describe('Sum of a and b')
36
+ })
37
+ }, async ({ a, b }) => {
68
38
  return { result: a + b };
69
39
  });
70
40
 
71
- // Start serving requests
72
- mathAbility.serve().catch(console.error);
41
+ // Start serving (native, stdio, or broker)
42
+ await ability.serve('stdio');
73
43
  ```
74
44
 
75
45
  ### Loading and Using Abilities
@@ -77,1402 +47,390 @@ mathAbility.serve().catch(console.error);
77
47
  ```javascript
78
48
  import { loadAbility } from '@kadi.build/core';
79
49
 
80
- async function main() {
81
- // Load an ability (uses first interface defined in agent.json)
82
- const math = await loadAbility('math-ability');
83
-
84
- // Call methods like regular functions
85
- const result = await math.add({ a: 5, b: 3 });
86
- console.log(result); // { result: 8 }
87
- }
88
-
89
- main().catch(console.error);
90
- ```
91
-
92
- ### Creating a Broker-Connected Agent
93
-
94
- ```javascript
95
- import { KadiClient } from '@kadi.build/core';
96
-
97
- // Create an agent that connects to broker
98
- const agent = new KadiClient({
99
- name: 'my-agent',
100
- role: 'agent',
101
- protocol: 'broker',
102
- brokers: {
103
- prod: 'ws://localhost:8080',
104
- dev: 'ws://localhost:8081'
105
- },
106
- defaultBroker: 'prod',
107
- networks: ['global']
108
- });
109
-
110
- // Register tools that other agents can call
111
- agent.registerTool(
112
- 'greet',
113
- async ({ name }) => {
114
- return { greeting: `Hello, ${name}!` };
115
- },
116
- {
117
- description: 'Greet someone by name',
118
- inputSchema: {
119
- type: 'object',
120
- properties: {
121
- name: { type: 'string', description: 'Name to greet' }
122
- },
123
- required: ['name']
124
- }
125
- }
126
- );
127
-
128
- // Connect to brokers and start serving
129
- await agent.connectToBrokers();
130
-
131
- // Call tools from OTHER agents connected to the same broker
132
- const result = await agent.callTool('translate', 'text-tool', {
133
- text: 'Hello world',
134
- language: 'spanish'
135
- });
136
- console.log(result);
137
- ```
138
-
139
- ## 🔌 Transport Protocols
140
-
141
- KADI abilities support three transport protocols, each optimized for different use cases:
142
-
143
- ### 1. Native Protocol (Fastest)
144
-
145
- - **Use Case**: High-performance, in-process execution
146
- - **How it Works**: Loads abilities as ES modules, enabling direct function calls
147
- - **Performance**: Zero IPC overhead - functions run in the same process
148
- - **Best For**: Utility libraries, data processing, performance-critical operations
149
-
150
- ```javascript
151
- // Loads as native module by default if available
152
- const ability = await loadAbility('my-ability', 'native');
153
- ```
154
-
155
- ### 2. Stdio Protocol (Balanced)
156
-
157
- - **Use Case**: Language-agnostic local execution
158
- - **How it Works**: Spawns abilities as child processes, communicates via LSP-style JSON-RPC over stdin/stdout
159
- - **Performance**: Minimal overhead, reliable local IPC
160
- - **Best For**: Python scripts, Go binaries, Node.js abilities, development/testing
161
-
162
- ```javascript
163
- // Force stdio protocol
164
- const ability = await loadAbility('my-ability', 'stdio');
50
+ const math = await loadAbility('math-ability', 'stdio');
51
+ const result = await math.add({ a: 5, b: 3 });
52
+ console.log(result); // { result: 8 }
165
53
  ```
166
54
 
167
- **Environment Variables Passed to Child Process:**
168
-
169
- ```bash
170
- KADI_PROTOCOL=stdio # Identifies the protocol being used
171
- # Plus all parent process.env variables
172
- ```
55
+ ## Tool Registration (Three Ways)
173
56
 
174
- ### 3. Broker Protocol (Distributed)
57
+ ### 1. With Zod Schemas (Recommended)
175
58
 
176
- - **Use Case**: Distributed systems, containerized deployments
177
- - **How it Works**: Abilities connect to a WebSocket broker for network communication
178
- - **Performance**: Network overhead, but enables scaling and distribution
179
- - **Best For**: Distributed abilities, cloud deployments, load-balanced systems
59
+ Zod provides type-safe schemas with 70% less code than JSON Schema:
180
60
 
181
61
  ```javascript
182
- // Use broker for distributed execution
183
- const ability = await loadAbility('my-ability', 'broker');
184
- ```
185
-
186
- **Environment Variables Passed to Child Process:**
187
-
188
- ```bash
189
- KADI_PROTOCOL=broker # Identifies the protocol
190
- KADI_BROKER_URL=ws://localhost:8080 # Broker connection URL
191
- KADI_ABILITY_NAME=ability.echo.1_0_0 # Ability identifier
192
- KADI_AGENT_SCOPE=uuid-here # Agent's scope/namespace
193
- # Plus all parent process.env variables
194
- ```
195
-
196
- **Network/Namespace Visibility:**
197
-
198
- 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 network:
199
-
200
- ```javascript
201
- // In your ability code:
202
- const echoAbility = new KadiClient({
203
- name: 'echo-js',
204
- version: '0.0.1',
205
- description: 'Echo ability with broker support',
206
- network: process.env.KADI_AGENT_SCOPE // Use agent's network for visibility
62
+ import { KadiClient, z } from '@kadi.build/core';
63
+
64
+ const ability = new KadiClient({ name: 'example' });
65
+
66
+ ability.registerTool({
67
+ name: 'processData',
68
+ description: 'Process user data',
69
+ version: '1.0.0',
70
+ input: z.object({
71
+ name: z.string().min(1).describe('User name'),
72
+ age: z.number().int().positive().optional().describe('User age'),
73
+ tags: z.array(z.string()).describe('User tags')
74
+ }),
75
+ output: z.object({
76
+ processed: z.boolean(),
77
+ data: z.record(z.unknown())
78
+ })
79
+ }, async (params) => {
80
+ // Automatic validation of inputs and outputs
81
+ return {
82
+ processed: true,
83
+ data: params
84
+ };
207
85
  });
208
-
209
- // Without this, the ability would only be visible in the 'global' network
210
- // and the parent agent wouldn't be able to communicate with it
211
- ```
212
-
213
- ### Protocol Selection Strategy
214
-
215
- When no protocol is specified, `loadAbility` defaults to the 'native' protocol. You must explicitly specify the protocol if you want to use 'stdio' or 'broker'.
216
-
217
- ```mermaid
218
- graph TD
219
- A[Load Ability] --> B{Protocol Specified?}
220
- B -->|Yes| C[Use Specified Protocol]
221
- B -->|No| D[Default to 'native']
222
86
  ```
223
87
 
224
- ## 🛠️ Creating Abilities with KadiClient
88
+ ### 2. With createTool() Helper
225
89
 
226
- ### Basic Ability Structure
90
+ For advanced use cases with runtime validation:
227
91
 
228
92
  ```javascript
229
- import { KadiClient } from '@kadi.build/core';
230
-
231
- const ability = new KadiClient({
232
- name: 'echo-ability',
233
- role: 'ability', // 'agent', 'ability',
234
- protocol: 'stdio', // 'native', 'stdio', or 'broker'
235
- brokers: {
236
- local: 'ws://localhost:8080',
237
- prod: 'ws://api.example.com:8080'
238
- },
239
- defaultBroker: 'local'
240
- });
241
-
242
- // Method 1: Simple tool registration
243
- ability.registerTool('echo', async ({ message }) => ({
244
- echo: message,
245
- timestamp: new Date().toISOString()
246
- }));
247
-
248
- // Method 2: Tool with inline schema
249
- ability.registerTool(
250
- 'format',
251
- async ({ text, style }) => ({
252
- formatted: style === 'upper' ? text.toUpperCase() : text.toLowerCase()
93
+ import { createTool, z } from '@kadi.build/core';
94
+
95
+ const { definition, handler } = createTool({
96
+ name: 'convert',
97
+ description: 'Convert units',
98
+ input: z.object({
99
+ value: z.number(),
100
+ from: z.enum(['km', 'miles']),
101
+ to: z.enum(['km', 'miles'])
253
102
  }),
254
- {
255
- description: 'Format text with specified style',
256
- inputSchema: {
257
- type: 'object',
258
- properties: {
259
- text: { type: 'string', description: 'Text to format' },
260
- style: {
261
- type: 'string',
262
- enum: ['upper', 'lower'],
263
- description: 'Formatting style'
264
- }
265
- },
266
- required: ['text', 'style']
267
- },
268
- outputSchema: {
269
- type: 'object',
270
- properties: {
271
- formatted: { type: 'string', description: 'Formatted text' }
272
- }
273
- }
103
+ output: z.object({
104
+ result: z.number(),
105
+ unit: z.string()
106
+ }),
107
+ handler: async ({ value, from, to }) => {
108
+ // Your logic here
109
+ return { result: value * 1.6, unit: to };
274
110
  }
275
- );
276
-
277
- // Start serving
278
- ability.serve().catch(console.error);
279
- ```
280
-
281
- ### Event Publishing and Subscription
282
-
283
- **Full Support**: Events work across all protocols - native, stdio, and broker.
284
-
285
- KadiClient provides a unified event system that works consistently across all transport protocols:
286
-
287
- ```javascript
288
- // In your ability (ability.js)
289
- const ability = new KadiClient({
290
- name: 'my-ability',
291
- role: 'ability',
292
- protocol: 'broker' // Works with all protocols
293
- });
294
-
295
- // Publish events from tool handlers
296
- ability.registerTool('echo', async ({ message }) => {
297
- // Publish events during processing
298
- await ability.publishEvent('echo.processing', { status: 'started' });
299
-
300
- // Do work...
301
- const result = { echo: message, timestamp: new Date().toISOString() };
302
-
303
- await ability.publishEvent('echo.completed', { result });
304
- return result;
305
- });
306
-
307
- // In your agent (agent.js)
308
- const agent = new KadiClient({
309
- name: 'my-client',
310
- role: 'agent',
311
- protocol: 'broker'
312
- });
313
-
314
- // Subscribe to events using patterns
315
- agent.subscribeToEvent('echo.*', (data) => {
316
- console.log('Echo event received:', data);
317
- });
318
-
319
- // For loaded abilities with native/stdio protocols
320
- const ability = await loadAbility('my-ability', 'stdio');
321
- ability.events.on('echo.completed', (data) => {
322
- console.log('Completed:', data);
323
111
  });
324
- ```
325
-
326
- **Event Patterns**:
327
-
328
- - Use wildcards: `math.*` matches `math.operation`, `math.milestone`
329
- - Protocol-specific delivery:
330
- - **Native**: Direct EventEmitter (synchronous)
331
- - **Stdio**: JSON-RPC notifications over stdout
332
- - **Broker**: RabbitMQ pub/sub via WebSocket
333
112
 
334
- ### Ability Configuration (agent.json)
335
-
336
- Every ability should have an `agent.json` file that defines its capabilities:
337
-
338
- ```json
339
- {
340
- "name": "echo-ability",
341
- "kind": "ability",
342
- "version": "1.0.0",
343
- "license": "MIT",
344
- "description": "Echo ability with multiple transport support",
345
- "scripts": {
346
- "setup": "npm install",
347
- "start": "node ability.js"
348
- },
349
- "exports": [
350
- {
351
- "name": "echo",
352
- "description": "Echo the input message",
353
- "inputSchema": {
354
- "type": "object",
355
- "properties": {
356
- "message": { "type": "string" }
357
- },
358
- "required": ["message"]
359
- },
360
- "outputSchema": {
361
- "type": "object",
362
- "properties": {
363
- "echo": { "type": "string" },
364
- "timestamp": { "type": "string" }
365
- }
366
- }
367
- }
368
- ],
369
- "brokers": {
370
- "local": "ws://localhost:8080",
371
- "remote": "ws://api.example.com:8080"
372
- },
373
- "defaultBroker": "local"
374
- }
113
+ ability.registerTool(definition, handler);
375
114
  ```
376
115
 
377
- ### Schema Definition Options
378
-
379
- The ability system provides two ways to define method schemas:
116
+ ### 3. With JSON Schema (Backward Compatible)
380
117
 
381
- 1. **Export Schemas** (defined in `agent.json` exports section)
382
- 2. **Inline Schemas** (passed directly to `registerTool()`)
118
+ Traditional approach still supported:
383
119
 
384
120
  ```javascript
385
- // Option 1: Define in agent.json exports section
386
- // The schema is picked up automatically if the method name matches
387
- // agent.json:
388
- {
389
- "exports": [
390
- {
391
- "name": "process",
392
- "description": "Process data",
393
- "inputSchema": { /* ... */ },
394
- "outputSchema": { /* ... */ }
121
+ ability.registerTool({
122
+ name: 'greet',
123
+ description: 'Greet someone',
124
+ inputSchema: {
125
+ type: 'object',
126
+ properties: {
127
+ name: { type: 'string', description: 'Name to greet' }
128
+ },
129
+ required: ['name']
130
+ },
131
+ outputSchema: {
132
+ type: 'object',
133
+ properties: {
134
+ greeting: { type: 'string' }
395
135
  }
396
- ]
397
- }
398
-
399
- // Option 2: Define inline when registering the method
400
- ability.registerTool('process', handler, {
401
- description: 'Process data',
402
- inputSchema: { /* ... */ },
403
- outputSchema: { /* ... */ }
404
- });
405
-
406
- // Note: If both are defined, inline schemas take precedence over exports
407
- ```
408
-
409
- **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.
410
-
411
- ## 🤖 Working with KadiClient
412
-
413
- 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.
414
-
415
- ### Direct Approach (using loadAbility)
416
-
417
- ```javascript
418
- import { loadAbility } from '@kadi.build/core';
419
-
420
- // Load and call abilities directly
421
- const echoAbility = await loadAbility('echo-js', 'stdio');
422
- const result = await echoAbility.echo({ message: 'Hello world' });
423
-
424
- // Load broker tools directly
425
- const remoteAbility = await loadAbility('remote-ability', 'broker', {
426
- brokerUrl: 'ws://localhost:8080',
427
- networks: ['global']
136
+ }
137
+ }, async ({ name }) => {
138
+ return { greeting: `Hello, ${name}!` };
428
139
  });
429
- const brokerResult = await remoteAbility.process({ data: 'test' });
430
140
  ```
431
141
 
432
- ### Using KadiClient with Named Brokers
142
+ ## Migration from JSON Schema to Zod
433
143
 
144
+ **Before (JSON Schema):**
434
145
  ```javascript
435
- import { KadiClient } from '@kadi.build/core';
436
-
437
- // Create a unified client with named brokers
438
- const client = new KadiClient({
439
- name: 'my-ability',
440
- role: 'ability', // Provider role
441
- protocol: 'broker',
442
- brokers: {
443
- dev: 'ws://localhost:8080',
444
- staging: 'ws://staging.example.com:8080',
445
- prod: 'ws://prod.example.com:8080'
446
- },
447
- defaultBroker: 'dev',
448
- networks: ['global']
449
- });
450
-
451
- // Register tools for others to call
452
- client.registerTool(
453
- 'greet',
454
- async ({ name }) => {
455
- return { greeting: `Hello, ${name}!` };
146
+ // 64 lines of boilerplate
147
+ ability.registerTool({
148
+ name: 'deployToAkash',
149
+ description: 'Deploy to Akash Network',
150
+ inputSchema: {
151
+ type: 'object',
152
+ properties: {
153
+ profile: {
154
+ type: 'string',
155
+ description: 'Deployment profile name'
156
+ },
157
+ dryRun: {
158
+ type: 'boolean',
159
+ description: 'Preview without deploying',
160
+ default: false
161
+ },
162
+ verbose: {
163
+ type: 'boolean',
164
+ description: 'Enable verbose output',
165
+ default: false
166
+ }
167
+ },
168
+ required: ['profile']
456
169
  },
457
- {
458
- description: 'Greet someone by name',
459
- inputSchema: {
460
- type: 'object',
461
- properties: { name: { type: 'string' } },
462
- required: ['name']
170
+ outputSchema: {
171
+ type: 'object',
172
+ properties: {
173
+ success: { type: 'boolean' },
174
+ dseq: { type: 'string' },
175
+ services: {
176
+ type: 'array',
177
+ items: { type: 'string' }
178
+ }
463
179
  }
464
180
  }
465
- );
466
-
467
- // Connect to all configured brokers
468
- await client.connectToBrokers();
469
-
470
- // Call remote tools
471
- const result = await client.callTool('translator', 'translate', {
472
- text: 'Hello',
473
- to: 'es'
474
- });
475
-
476
- // Load abilities for compatibility
477
- const ability = await client.loadAbility('echo-js');
478
- ```
479
-
480
- Use `loadAbility()` for simple consumption, or KadiClient for full-featured ability development with tool registration, event publishing, and remote tool invocation.
481
-
482
- ## 🔄 Architecture Deep Dive
483
-
484
- ### Transport Architecture and Loading Sequence
485
-
486
- The loading process uses modular transports based on the selected protocol:
487
-
488
- ```mermaid
489
- sequenceDiagram
490
- participant Client
491
- participant loadAbility
492
- participant Transport
493
- participant FileSystem
494
- participant ChildProcess
495
- participant Ability
496
- participant Broker
497
-
498
- Client->>loadAbility: loadAbility('echo-js', protocol?)
499
- loadAbility->>FileSystem: Read agent.json manifests
500
- FileSystem-->>loadAbility: Return config & version
501
-
502
- alt protocol === 'native'
503
- loadAbility->>Transport: new NativeTransport(config)
504
- Transport->>FileSystem: import(entry point)
505
- FileSystem-->>Transport: Module with KadiClient instance
506
- Transport->>Ability: Extract tool handlers via getToolNames()
507
- Ability-->>Transport: Return registered tools
508
- Transport->>Transport: Store handlers in Map
509
- else protocol === 'stdio'
510
- loadAbility->>Transport: new StdioTransport(config)
511
- Transport->>ChildProcess: spawn(startCmd, env: {KADI_PROTOCOL})
512
- ChildProcess->>Ability: KadiClient detects stdio mode
513
- Ability->>Ability: Setup FrameReader/Writer
514
- Transport->>Ability: Send discovery request
515
- Ability-->>Transport: Return tool list
516
- Transport->>Transport: Setup JSON-RPC proxy
517
- else protocol === 'broker'
518
- loadAbility->>Transport: new BrokerTransport(config)
519
- Transport->>Transport: Reuse existing KadiClient
520
- Transport->>Broker: WebSocket connect
521
- Transport->>Broker: kadi.session.hello
522
- Broker-->>Transport: { nonce, heartbeatIntervalSec }
523
- Transport->>Broker: kadi.session.authenticate
524
- Broker-->>Transport: { sessionId }
525
- Transport->>Broker: kadi.ability.list
526
- Broker-->>Transport: Available tools
527
- Transport->>Transport: Start heartbeat timer
528
- end
529
-
530
- loadAbility->>loadAbility: Create proxy with tool methods
531
- loadAbility-->>Client: Return ability proxy
532
-
533
- Note over Client,Ability: Tool invocation
534
- Client->>Transport: ability.echo({ message })
535
-
536
- alt native
537
- Transport->>Ability: Direct handler call
538
- else stdio
539
- Transport->>Ability: JSON-RPC over frames
540
- else broker
541
- Transport->>Broker: kadi.ability.invoke
542
- Broker->>Ability: Route to target
543
- Ability->>Broker: kadi.ability.result
544
- Broker-->>Transport: Forward result
545
- end
546
-
547
- Transport-->>Client: Return result
548
- ```
549
-
550
- ### KadiClient Architecture: Tool Registration & Serving
551
-
552
- How KadiClient registers tools and starts serving:
553
-
554
- ```mermaid
555
- sequenceDiagram
556
- participant Dev as Developer
557
- participant KC as KadiClient
558
- participant Transport
559
- participant Network as Network Layer
560
- participant RemoteClient
561
-
562
- Note over Dev,KC: Tool Registration Phase
563
- Dev->>KC: new KadiClient({ name, role, protocol })
564
- KC->>KC: Initialize based on role & protocol
565
-
566
- Dev->>KC: registerTool('echo', handler, schema?)
567
- KC->>KC: Store in toolHandlers Map<name, {handler, schema}>
568
-
569
- Dev->>KC: registerTool('process', handler, schema)
570
- KC->>KC: Add to toolHandlers Map
571
-
572
- Note over KC,Network: Connection & Serving Phase
573
- Dev->>KC: serve() or connectToBrokers()
574
-
575
- alt protocol === 'native'
576
- KC->>KC: Tools available via direct reference
577
- KC->>KC: Emit 'start' event
578
- else protocol === 'stdio'
579
- KC->>Transport: Setup StdioTransport
580
- Transport->>Transport: new FrameReader(stdin)
581
- Transport->>Transport: new FrameWriter(stdout)
582
- Transport->>KC: on('message', handleStdioMessage)
583
- KC->>KC: Process JSON-RPC requests
584
- else protocol === 'broker'
585
- KC->>Network: WebSocket.connect(brokerUrl)
586
- KC->>Network: kadi.session.hello({ role })
587
- Network-->>KC: { nonce, heartbeatIntervalSec: 30 }
588
- KC->>KC: Sign nonce with Ed25519
589
- KC->>Network: kadi.session.authenticate({ signature })
590
- Network-->>KC: { sessionId }
591
- KC->>Network: kadi.agent.register({ tools, networks })
592
- KC->>KC: Start heartbeat timer (30s interval)
593
- end
594
-
595
- Note over RemoteClient,KC: Tool Invocation Flow
596
- RemoteClient->>Network: Request tool invocation
597
-
598
- alt native
599
- RemoteClient->>KC: Direct method call
600
- KC->>KC: toolHandlers.get(name).handler(params)
601
- else stdio
602
- Network->>Transport: JSON-RPC frame
603
- Transport->>KC: Parsed request
604
- KC->>KC: toolHandlers.get(name).handler(params)
605
- KC->>Transport: JSON-RPC response
606
- Transport->>Network: Framed response
607
- else broker
608
- Network->>KC: kadi.ability.invoke message
609
- KC->>KC: toolHandlers.get(toolName).handler(params)
610
- KC->>Network: kadi.ability.result message
611
- end
612
-
613
- Network-->>RemoteClient: Tool result
614
- ```
615
-
616
- ### Event System Architecture
617
-
618
- KadiClient provides a unified event API that works across all transport protocols:
619
-
620
- ```mermaid
621
- sequenceDiagram
622
- participant Publisher as Ability (Publisher)
623
- participant Transport as Transport Layer
624
- participant EventBus as Event Bus
625
- participant Subscriber as Client (Subscriber)
626
-
627
- Note over Subscriber: Setup subscription
628
- Subscriber->>Subscriber: subscribeToEvent('math.*', callback)
629
-
630
- alt protocol === 'broker'
631
- Subscriber->>EventBus: kadi.event.subscribe({channels: ['math.*']})
632
- EventBus-->>Subscriber: {subscribed: ['math.*'], queueName}
633
- else protocol === 'stdio'
634
- Subscriber->>Transport: Setup event listener
635
- Transport->>Subscriber: on('transport:event', dispatcher)
636
- else protocol === 'native'
637
- Subscriber->>Publisher: Direct EventEmitter reference
638
- Publisher->>Subscriber: on('ability:event', dispatcher)
639
- end
640
-
641
- Note over Publisher: Publish event
642
- Publisher->>Publisher: publishEvent('math.operation', data)
643
-
644
- alt protocol === 'native'
645
- Publisher->>Publisher: emit('ability:event', {eventName, data})
646
- Publisher-->>Subscriber: Direct EventEmitter delivery
647
- Subscriber->>Subscriber: Pattern match & invoke callback
648
- else protocol === 'stdio'
649
- Publisher->>Transport: writer.write(__kadi_event notification)
650
- Transport->>Transport: Frame with LSP headers
651
- Transport-->>Subscriber: Framed event over stdout
652
- Subscriber->>Transport: FrameReader parses
653
- Transport->>Subscriber: emit('transport:event', {name, data})
654
- Subscriber->>Subscriber: Pattern match & invoke callback
655
- else protocol === 'broker'
656
- Publisher->>EventBus: kadi.event.publish({channel, data})
657
- EventBus->>EventBus: Route via RabbitMQ
658
- EventBus-->>Subscriber: kadi.event.delivery
659
- Subscriber->>Subscriber: Pattern match & invoke callback
660
- end
661
-
662
- Note over Subscriber: Cleanup
663
- Subscriber->>Subscriber: unsubscribe()
664
- alt protocol === 'broker'
665
- Subscriber->>EventBus: kadi.event.unsubscribe
666
- else
667
- Subscriber->>Subscriber: Remove local listener
668
- end
669
- ```
670
-
671
- ### Broker Protocol Communication Flow
672
-
673
- Detailed view of broker-based communication with heartbeat:
674
-
675
- ```mermaid
676
- sequenceDiagram
677
- participant Client as KadiClient
678
- participant Broker as KADI Broker
679
- participant Target as Target Ability
680
-
681
- Note over Client,Broker: Connection & Authentication
682
- Client->>Broker: WebSocket connect
683
- Client->>Broker: kadi.session.hello({ role })
684
- Broker-->>Client: { nonce, heartbeatIntervalSec: 30 }
685
- Client->>Client: Generate Ed25519 keypair
686
- Client->>Client: Sign nonce
687
- Client->>Broker: kadi.session.authenticate({ signature })
688
- Broker-->>Client: { sessionId }
689
-
690
- Note over Client,Broker: Heartbeat Management
691
- Client->>Client: Start heartbeat timer (30s)
692
- loop Every 30 seconds
693
- Client->>Broker: kadi.session.ping
694
- Note over Broker: Reset connection timeout (90s)
695
- end
696
-
697
- Note over Client,Broker: Tool Registration
698
- Client->>Broker: kadi.agent.register({ tools, networks })
699
- Broker->>Broker: Store in registry
700
- Broker-->>Client: { registered: true }
701
-
702
- Note over Client,Target: Remote Tool Invocation
703
- Client->>Client: callTool('ability-b', 'process', params)
704
- Client->>Broker: kadi.ability.invoke({
705
- targetAgent: 'ability-b',
706
- toolName: 'process',
707
- toolInput: params
708
- })
709
- Broker->>Broker: Lookup ability-b
710
- Broker->>Target: kadi.ability.invoke
711
- Target->>Target: Execute tool handler
712
- Target->>Broker: JSON-RPC response
713
- Broker-->>Client: Forward response
714
- Client->>Client: Resolve promise
715
-
716
- Note over Client,Broker: Event Publishing
717
- Client->>Broker: kadi.event.publish({
718
- channel: 'system.status',
719
- data: { status: 'healthy' }
720
- })
721
- Broker->>Broker: Publish to RabbitMQ exchange
722
-
723
- Note over Client,Broker: Graceful Shutdown
724
- Client->>Client: Stop heartbeat timer
725
- Client->>Broker: kadi.session.goodbye
726
- Client->>Client: Close WebSocket
181
+ }, handler);
727
182
  ```
728
183
 
729
- ### Stdio Protocol: LSP-Style Frame Processing
730
-
731
- How KadiClient handles stdio communication with LSP-style framing:
732
-
733
- ```mermaid
734
- sequenceDiagram
735
- participant Parent as Parent Process
736
- participant SR as StdioFrameReader
737
- participant SW as StdioFrameWriter
738
- participant Server as StdioRpcServer
739
- participant Ability as KadiClient
740
-
741
- Note over Parent,SR: Incoming Request
742
- Parent->>SR: Write to stdin: "Content-Length: 52\r\n\r\n{...}"
743
- SR->>SR: Buffer incoming data
744
- SR->>SR: Look for Content-Length header
745
- SR->>SR: Parse header, extract body length
746
- SR->>SR: Wait for complete body
747
- SR->>SR: Parse JSON from body
748
- SR->>Server: onMessage({ success: true, data: request })
749
-
750
- Server->>Server: Validate JSON-RPC format
751
- Server->>Ability: getMethodHandler(request.method)
752
- Ability-->>Server: Return handler
753
- Server->>Server: Execute handler(params) with timeout
754
-
755
- Note over Server,Parent: Response
756
- Server->>SW: write(response)
757
- SW->>SW: JSON.stringify(response)
758
- SW->>SW: Calculate byte length
759
- SW->>SW: Create header: "Content-Length: N\r\n\r\n"
760
- SW->>Parent: Write header + body to stdout
761
-
762
- Note over SR: Error Recovery
763
- alt Frame Corruption
764
- SR->>SR: Detect invalid frame
765
- SR->>SR: Search for next Content-Length marker
766
- SR->>SR: Skip corrupted bytes
767
- SR->>Server: onMessage({ success: false, error: 'FRAME_CORRUPTION' })
768
- Server->>Server: Log error, continue processing
769
- end
770
- ```
771
-
772
- ## 📥 Loading Abilities
773
-
774
- ### Basic Loading
775
-
776
- ```javascript
777
- import { loadAbility } from '@kadi.build/core';
778
-
779
- // Auto-detect protocol from agent.json
780
- const ability = await loadAbility('my-ability');
781
-
782
- // Explicitly specify protocol
783
- const stdioAbility = await loadAbility('my-ability', 'stdio');
784
- const brokerAbility = await loadAbility('my-ability', 'broker');
785
- const nativeAbility = await loadAbility('my-ability', 'native');
786
- ```
787
-
788
- ### Working with Loaded Abilities
789
-
184
+ **After (Zod):**
790
185
  ```javascript
791
- // Call methods directly - no need to discover them first
792
- const result = await ability.echo({ message: 'hello' });
793
-
794
- // Call methods
795
- const result = await ability.someMethod({ param: 'value' });
796
-
797
- // Direct RPC call (bypasses method validation)
798
- const response = await ability.__call('someMethod', { param: 'value' });
799
-
800
- // Subscribe to events
801
- ability.events.on('custom:event', (data) => {
802
- console.log('Event received:', data);
803
- });
804
-
805
- // Note: Events work for all protocols (native, stdio, and broker)
806
- // Subscribe before calling methods that emit events
807
- ```
808
-
809
- ### KadiClient transport vs loadAbility transport
810
-
811
- KADI uses “transport” in two places and they serve different purposes:
812
-
813
- - KadiClient transport — how this client exposes its own tools after `registerTool(...)` and `serve()`.
814
- - `transport: 'native'` — in‑process only. There is no network or stdio listener. Code that loads you natively (via `loadAbility('you','native')`) can call your tools directly. `serve()` simply keeps the process alive if you want a long‑running ability.
815
- - `transport: 'stdio'` — the client runs a stdio server (JSON‑RPC over stdin/stdout) and answers requests with your registered handlers. `serve()` wires up the stdio loop.
816
- - `transport: 'broker'` — the client connects to the broker, registers your tools, and receives remote invocations over WebSocket. `serve()` (or `connectToBrokers()`) handles connection and registration.
817
-
818
- - loadAbility transport — how this client connects to a specific ability it wants to use.
819
- - `'native'` loads a module in‑process
820
- - `'stdio'` talks to a spawned child process (uses the ability’s `agent.json` `scripts.start`)
821
- - `'broker'` talks to a remote ability over the broker
822
-
823
- These two choices are independent: setting the client’s `transport` does not change how you load another ability, and the transport you choose in `loadAbility(...)` does not change how your client serves its own tools.
824
-
825
- Examples
826
-
827
- Provide tools over broker
828
-
829
- ```ts
830
- import { KadiClient } from '@kadi.build/core';
831
-
832
- const svc = new KadiClient({
833
- name: 'math',
834
- role: 'ability',
835
- transport: 'broker',
836
- brokers: { local: 'ws://localhost:8080' },
837
- defaultBroker: 'local'
838
- });
839
-
840
- svc.registerTool('add', async ({ a, b }) => ({ result: a + b }));
841
- await svc.serve(); // registers with the broker so others can call math.add
842
- ```
843
-
844
- Consume a stdio ability
845
-
846
- ```ts
847
- const agent = new KadiClient({
848
- name: 'client',
849
- role: 'agent',
850
- transport: 'native'
851
- });
852
- const math = await agent.loadAbility('simple-math', 'stdio');
853
- const res = await math.add({ a: 2, b: 3 });
854
- ```
855
-
856
- Consume a broker ability
857
-
858
- ```ts
859
- const agent = new KadiClient({
860
- name: 'client',
861
- role: 'agent',
862
- transport: 'broker',
863
- brokers: { local: 'ws://localhost:8080' },
864
- defaultBroker: 'local'
865
- });
866
-
867
- const echo = await agent.loadAbility('echo-js', 'broker', {
868
- networks: ['global']
869
- });
870
- const r = await echo.say_message({ message: 'hi' });
186
+ // 15 lines - clean and type-safe!
187
+ ability.registerTool({
188
+ name: 'deployToAkash',
189
+ description: 'Deploy to Akash Network',
190
+ input: z.object({
191
+ profile: z.string().describe('Deployment profile name'),
192
+ dryRun: z.boolean().default(false).describe('Preview without deploying'),
193
+ verbose: z.boolean().default(false).describe('Enable verbose output')
194
+ }),
195
+ output: z.object({
196
+ success: z.boolean(),
197
+ dseq: z.string(),
198
+ services: z.array(z.string())
199
+ })
200
+ }, handler);
871
201
  ```
872
202
 
873
- ### Loading Context
874
-
875
- The loader automatically resolves ability versions from:
876
-
877
- 1. **Project context**: Reads from project's `agent.json`
878
- 2. **Nested context**: When loading from within another ability
879
- 3. **Explicit version**: Can be specified in ability name
880
-
881
- ## ⚙️ Configuration
882
-
883
- ### Environment Variables
884
-
885
- ```bash
886
- # Set default protocol
887
- export KADI_PROTOCOL=stdio
888
-
889
- # Configure broker
890
- export KADI_BROKER_URL=ws://localhost:8080
891
- export KADI_ABILITY_NAME=my-ability
892
- export KADI_AGENT_SCOPE=project-123
893
- ```
203
+ **Result: 77% less code!**
894
204
 
895
- ### Environment Variables for Child Processes
205
+ ## Transport Protocols
896
206
 
897
- When abilities are spawned as child processes (stdio and broker protocols), the parent process passes down important environment variables:
207
+ KADI supports three transport protocols for different use cases:
898
208
 
209
+ ### Native (In-Process)
899
210
  ```javascript
900
- // In your ability code, you can access these variables:
901
- const protocol = process.env.KADI_PROTOCOL; // 'stdio' or 'broker'
902
- const brokerUrl = process.env.KADI_BROKER_URL;
903
- const abilityName = process.env.KADI_ABILITY_NAME;
904
- const agentScope = process.env.KADI_AGENT_SCOPE;
905
-
906
- // Use them to configure your ability behavior
907
- const ability = new KadiClient({
908
- name: 'my-ability',
909
- network: process.env.KADI_AGENT_SCOPE || 'global'
910
- // Ability automatically uses KADI_PROTOCOL to determine transport
911
- });
211
+ const ability = await loadAbility('my-ability', 'native');
212
+ // Direct function calls, zero IPC overhead
912
213
  ```
913
214
 
914
- ### Example Project Structure
915
-
916
- ```
917
- my-project/
918
- ├── agent.json # Project configuration
919
- ├── abilities/ # Installed abilities
920
- │ └── echo-ability/
921
- │ └── 1.0.0/
922
- │ ├── agent.json # Ability configuration
923
- │ ├── ability.js # Ability implementation
924
- │ └── package.json
925
- ├── modules/ # Source modules
926
- │ └── echo-ability/ # Development version
927
- └── index.js # Main entry point
215
+ ### Stdio (Child Process)
216
+ ```javascript
217
+ const ability = await loadAbility('my-ability', 'stdio');
218
+ // JSON-RPC over stdin/stdout, language-agnostic
928
219
  ```
929
220
 
930
- ## 🚀 Advanced Usage
931
-
932
- ### Event System
933
-
934
- KadiClient provides a comprehensive event system that works across all protocols:
935
-
936
- #### 1. Lifecycle Events
937
-
938
- Monitor the ability lifecycle:
939
-
221
+ ### Broker (Distributed)
940
222
  ```javascript
941
- const client = new KadiClient({ name: 'my-ability' });
942
-
943
- // Connection events
944
- client.on('connected', ({ broker }) => {
945
- console.log(`Connected to broker: ${broker}`);
946
- });
947
-
948
- client.on('disconnected', () => {
949
- console.log('Disconnected from broker');
950
- });
951
-
952
- // Tool invocation events
953
- client.on('tool:invoked', ({ toolName, params }) => {
954
- console.log(`Tool ${toolName} invoked with:`, params);
955
- });
956
-
957
- client.on('tool:completed', ({ toolName, result }) => {
958
- console.log(`Tool ${toolName} completed:`, result);
959
- });
960
-
961
- client.on('error', (error) => {
962
- console.error('Ability error:', error);
223
+ const ability = await loadAbility('my-ability', 'broker', {
224
+ brokerUrl: 'ws://localhost:8080',
225
+ networks: ['global']
963
226
  });
227
+ // WebSocket-based distributed communication
964
228
  ```
965
229
 
966
- #### 2. Custom Events (All Protocols)
230
+ ## Events
967
231
 
968
- Publish and subscribe to custom events across all transport protocols:
232
+ Publish and subscribe to events across all protocols:
969
233
 
970
234
  ```javascript
971
235
  // Publishing events
972
- const publisher = new KadiClient({
973
- name: 'event-publisher',
974
- role: 'ability',
975
- protocol: 'broker' // Works with all protocols
976
- });
977
-
978
- publisher.registerTool('process', async ({ data }) => {
979
- // Publish events during processing
980
- await publisher.publishEvent('process.started', {
981
- timestamp: Date.now(),
982
- data
983
- });
236
+ ability.registerTool({
237
+ name: 'process',
238
+ input: z.object({ data: z.string() }),
239
+ output: z.object({ result: z.string() })
240
+ }, async (params) => {
241
+ // Publish progress events
242
+ await ability.publishEvent('process.started', { data: params.data });
984
243
 
985
- // Do work...
986
- const result = await processData(data);
244
+ const result = await doWork(params.data);
987
245
 
988
- await publisher.publishEvent('process.completed', {
989
- timestamp: Date.now(),
990
- result
991
- });
992
-
993
- return result;
246
+ await ability.publishEvent('process.completed', { result });
247
+ return { result };
994
248
  });
995
249
 
996
250
  // Subscribing to events
997
- const subscriber = new KadiClient({
998
- name: 'event-subscriber',
999
- role: 'agent',
1000
- protocol: 'broker'
1001
- });
1002
-
1003
- // Subscribe with wildcards
1004
- subscriber.subscribeToEvent('process.*', (data) => {
251
+ ability.subscribeToEvent('process.*', (data) => {
1005
252
  console.log('Process event:', data);
1006
253
  });
1007
-
1008
- // One-time subscription
1009
- subscriber.onceEvent('process.completed', (data) => {
1010
- console.log('First completion:', data);
1011
- });
1012
-
1013
- // Multiple pattern subscription
1014
- subscriber.subscribeToEvents(['process.*', 'system.*'], (pattern, data) => {
1015
- console.log(`Event from ${pattern}:`, data);
1016
- });
1017
- ```
1018
-
1019
- **Protocol-Specific Behavior:**
1020
-
1021
- - **Native**: Direct EventEmitter, synchronous delivery
1022
- - **Stdio**: JSON-RPC notifications with LSP framing
1023
- - **Broker**: RabbitMQ pub/sub via WebSocket, persistent queues available
1024
-
1025
- ### Multi-Broker Configuration
1026
-
1027
- KadiClient supports connecting to multiple brokers simultaneously for redundancy and load distribution:
1028
-
1029
- ```javascript
1030
- const client = new KadiClient({
1031
- name: 'multi-broker-ability',
1032
- role: 'ability',
1033
- protocol: 'broker',
1034
- brokers: {
1035
- primary: 'ws://broker1.example.com:8080',
1036
- secondary: 'ws://broker2.example.com:8080',
1037
- backup: 'ws://broker3.example.com:8080'
1038
- },
1039
- defaultBroker: 'primary', // Used for sending if no specific broker targeted
1040
- networks: ['production']
1041
- });
1042
-
1043
- // Connect to all configured brokers
1044
- await client.connectToBrokers();
1045
-
1046
- // The client will now:
1047
- // 1. Maintain connections to all three brokers
1048
- // 2. Receive messages from any broker
1049
- // 3. Send messages to the default broker unless specified
1050
- // 4. Automatically handle broker failover
1051
254
  ```
1052
255
 
1053
- ### Cross-Language Ability Support
256
+ ## Broker Mode
1054
257
 
1055
- KADI now fully supports abilities written in any language through the stdio protocol:
258
+ Connect to KADI broker for distributed communication:
1056
259
 
1057
260
  ```javascript
1058
- // Go ability with agent.json:
1059
- {
1060
- "name": "hash-go",
1061
- "scripts": {
1062
- "setup": "go build -o bin/hash_ability",
1063
- "start": "./bin/hash_ability" // Binary executable
1064
- }
1065
- }
1066
-
1067
- // Python ability:
1068
- {
1069
- "name": "ml-processor",
1070
- "scripts": {
1071
- "start": "python3 main.py"
1072
- }
1073
- }
1074
-
1075
- // Rust ability:
1076
- {
1077
- "name": "crypto-rust",
1078
- "scripts": {
1079
- "setup": "cargo build --release",
1080
- "start": "./target/release/crypto_ability"
1081
- }
1082
- }
1083
-
1084
- // Load and use regardless of implementation language:
1085
- const hashAbility = await loadAbility('hash-go', 'stdio');
1086
- const result = await hashAbility.sha256({ data: 'hello' });
1087
- ```
1088
-
1089
- ## 🔧 Development Workflow
1090
-
1091
- ### Testing Local Changes
1092
-
1093
- When developing new features or testing changes to `@kadi.build/core` before publishing to NPM:
1094
-
1095
- 0. **Clone the repository**
1096
-
1097
- ```bash
1098
- git clone https://gitlab.com/humin-game-lab/kadi/kadi-core.git
1099
- ```
1100
-
1101
- 1. **Pack the local version** (in the kadi-core directory):
1102
-
1103
- ```bash
1104
- cd /path/to/kadi-core
1105
- npm pack
1106
- # This creates: kadi.build-core-X.Y.Z.tgz
1107
- ```
1108
-
1109
- 2. **Install in your project**:
1110
-
1111
- ```bash
1112
- cd /path/to/your-project
1113
- npm install /path/to/kadi-core/kadi.build-core-X.Y.Z.tgz
1114
- ```
1115
-
1116
- 3. **For ability development**, update the ability's preflight script:
1117
-
1118
- ```json
1119
- {
1120
- "scripts": {
1121
- "preflight": "npm uninstall @kadi.build/core && npm install /path/to/kadi.build-core-X.Y.Z.tgz"
1122
- }
1123
- }
1124
- ```
1125
-
1126
- ## 📖 API Reference
1127
-
1128
- ### Core Classes
1129
-
1130
- #### `KadiClient`
1131
-
1132
- The unified class for all KADI operations - serving tools, calling remote abilities, and managing events.
1133
-
1134
- ```javascript
1135
- import { KadiClient } from '@kadi.build/core';
1136
-
1137
261
  const client = new KadiClient({
1138
- name: 'my-ability',
1139
- role: 'ability', // 'agent' or 'ability'
1140
- protocol: 'broker', // 'native', 'stdio', or 'broker'
262
+ name: 'my-agent',
263
+ role: 'agent',
1141
264
  brokers: {
1142
265
  dev: 'ws://localhost:8080',
1143
266
  prod: 'ws://prod.example.com:8080'
1144
267
  },
1145
268
  defaultBroker: 'dev',
1146
- network: 'global', // Primary network
1147
- networks: ['global', 'custom-network'] // All networks
1148
- });
1149
- ```
1150
-
1151
- **Configuration Options:**
1152
-
1153
- - `name` - Ability/agent name
1154
- - `role` - Operating role: 'agent' (consumer) or 'ability' (provider)
1155
- - `protocol` - Transport protocol to use
1156
- - `brokers` - Named broker configurations (object mapping names to URLs)
1157
- - `defaultBroker` - Default broker name for sending messages
1158
- - `network` - Primary network segment for message routing
1159
- - `networks` - All network namespaces for tool discovery
1160
- - `heartbeatIntervalSec` - Custom heartbeat interval (default: from broker)
1161
-
1162
- **Tool Registration Methods:**
1163
-
1164
- - `registerTool(name, handler, schema?)` - Register a single tool
1165
- - `getTools()` - Get list of registered tool names
1166
- - `getToolNames()` - Get filtered list of tool names
1167
- - `getToolHandler(name)` - Get a specific tool's handler
1168
- - `getToolSchema(name)` - Get a specific tool's schema
1169
- - `hasTool(name)` - Check if a tool is registered
1170
-
1171
- **Remote Tool Invocation:**
1172
-
1173
- - `callTool(targetAgent, toolName, params)` - Call a remote tool via broker
1174
- - `discoverRemoteTools(targetAgent)` - List tools from a remote agent
1175
- - `loadAbility(name, protocol?, options?)` - Load an ability (compatibility)
1176
-
1177
- **Event System:**
1178
-
1179
- - `publishEvent(eventName, data)` - Publish an event
1180
- - `subscribeToEvent(pattern, callback)` - Subscribe with wildcards
1181
- - `subscribeToEvents(patterns[], callback)` - Multiple subscriptions
1182
- - `unsubscribeFromEvent(pattern, callback)` - Remove subscription
1183
- - `onceEvent(pattern, callback)` - One-time subscription
1184
-
1185
- **Connection Management:**
1186
-
1187
- - `serve()` - Start serving (stdio/native modes)
1188
- - `connectToBrokers()` - Connect to all configured brokers
1189
- - `disconnect()` - Clean disconnect
1190
- - `isConnected` - Check connection status
1191
-
1192
- **Properties:**
1193
-
1194
- - `agentId` - Unique agent identifier
1195
- - `name` - Ability name
1196
- - `role` - Current operating role
1197
- - `protocol` - Active protocol
1198
- - `brokers` - Configured broker map
1199
- - `defaultBroker` - Default broker name
1200
- - `currentBroker` - Currently active broker for sending
1201
-
1202
- **Complete Usage Example:**
1203
-
1204
- ```javascript
1205
- import { KadiClient } from '@kadi.build/core';
1206
-
1207
- // Create an ability that can serve tools
1208
- const ability = new KadiClient({
1209
- name: 'math-ability',
1210
- role: 'ability',
1211
- protocol: 'broker',
1212
- brokers: {
1213
- local: 'ws://localhost:8080',
1214
- cloud: 'wss://api.example.com'
1215
- },
1216
- defaultBroker: 'local',
1217
269
  networks: ['global']
1218
270
  });
1219
271
 
1220
- // Register tools with schemas
1221
- ability.registerTool(
1222
- 'add',
1223
- async ({ a, b }) => {
1224
- // Publish event before processing
1225
- await ability.publishEvent('math.operation', {
1226
- operation: 'add',
1227
- inputs: { a, b }
1228
- });
1229
-
1230
- const result = a + b;
1231
-
1232
- // Publish completion event
1233
- await ability.publishEvent('math.completed', {
1234
- operation: 'add',
1235
- result
1236
- });
1237
-
1238
- return { result };
1239
- },
1240
- {
1241
- description: 'Add two numbers',
1242
- inputSchema: {
1243
- type: 'object',
1244
- properties: {
1245
- a: { type: 'number', description: 'First number' },
1246
- b: { type: 'number', description: 'Second number' }
1247
- },
1248
- required: ['a', 'b']
1249
- },
1250
- outputSchema: {
1251
- type: 'object',
1252
- properties: {
1253
- result: { type: 'number', description: 'Sum of a and b' }
1254
- }
1255
- }
1256
- }
1257
- );
1258
-
1259
- // Subscribe to events from other abilities
1260
- ability.subscribeToEvent('system.*', (data) => {
1261
- console.log('System event:', data);
272
+ // Register tools
273
+ client.registerTool({
274
+ name: 'greet',
275
+ input: z.object({ name: z.string() }),
276
+ output: z.object({ greeting: z.string() })
277
+ }, async ({ name }) => {
278
+ return { greeting: `Hello, ${name}!` };
1262
279
  });
1263
280
 
1264
- // Connect to brokers
1265
- await ability.connectToBrokers();
281
+ // Connect to broker
282
+ await client.serve('broker');
1266
283
 
1267
284
  // Call remote tools
1268
- const result = await ability.callTool('translator-ability', 'translate', {
285
+ const result = await client.callTool('translator', 'translate', {
1269
286
  text: 'Hello',
1270
287
  to: 'es'
1271
288
  });
1272
-
1273
- // Graceful shutdown
1274
- process.on('SIGTERM', async () => {
1275
- await ability.disconnect();
1276
- process.exit(0);
1277
- });
1278
289
  ```
1279
290
 
1280
- #### `loadAbility`
291
+ ## API Reference
1281
292
 
1282
- Load an ability by name and protocol.
293
+ ### KadiClient
1283
294
 
1284
- ```javascript
1285
- import { loadAbility } from '@kadi.build/core';
295
+ Main class for all KADI operations:
1286
296
 
1287
- const ability = await loadAbility('ability-name', 'protocol', {
1288
- brokerUrl: 'ws://localhost:8080', // For broker protocol
1289
- brokerName: 'dev', // Named broker to use
1290
- networks: ['global'], // Network segments
1291
- existingClient: client // Reuse existing KadiClient for broker
297
+ ```javascript
298
+ const client = new KadiClient({
299
+ name: 'my-ability',
300
+ version: '1.0.0',
301
+ role: 'ability', // 'agent' or 'ability'
302
+ brokers: { /* broker configs */ },
303
+ defaultBroker: 'dev',
304
+ networks: ['global']
1292
305
  });
1293
306
  ```
1294
307
 
1295
- **Returns:** A proxy object with:
308
+ **Methods:**
309
+ - `registerTool(definition, handler)` - Register a tool
310
+ - `serve(mode)` - Start serving ('native', 'stdio', 'broker')
311
+ - `callTool(agent, tool, params)` - Call remote tool
312
+ - `publishEvent(name, data)` - Publish event
313
+ - `subscribeToEvent(pattern, callback)` - Subscribe to events
314
+ - `loadAbility(name, protocol, options)` - Load another ability
1296
315
 
1297
- - Direct method calls (e.g., `ability.echo({ message: 'hello' })`)
1298
- - `ability.events` - EventEmitter for subscribing to events (all protocols)
1299
- - `ability.__call(method, params)` - Direct RPC call
1300
- - `ability.__disconnect()` - Clean up resources
316
+ ### loadAbility
1301
317
 
1302
- ### Utility Functions
318
+ Load an ability by name and protocol:
1303
319
 
1304
320
  ```javascript
1305
- import {
1306
- createLogger,
1307
- getProjectJSON,
1308
- getAbilityJSON,
1309
- getAgentJSON,
1310
- getBrokerUrl,
1311
- runExecCommand
1312
- } from '@kadi.build/core';
321
+ const ability = await loadAbility('ability-name', 'protocol', {
322
+ brokerUrl: 'ws://localhost:8080',
323
+ networks: ['global']
324
+ });
1313
325
  ```
1314
326
 
1315
- ### Debug Mode
1316
-
1317
- Enable detailed logging:
327
+ ### createTool
1318
328
 
1319
- ```bash
1320
- DEBUG=kadi:* node index.js
1321
- ```
329
+ Helper for creating tools with validation:
1322
330
 
1323
- Check ability logs:
331
+ ```javascript
332
+ import { createTool, z } from '@kadi.build/core';
1324
333
 
1325
- ```bash
1326
- tail -f abilities/my-ability/1.0.0/my-ability.log
334
+ const { definition, handler } = createTool({
335
+ name: 'toolName',
336
+ input: z.object({ /* ... */ }),
337
+ output: z.object({ /* ... */ }),
338
+ handler: async (params) => { /* ... */ }
339
+ });
1327
340
  ```
1328
341
 
1329
- ## 💡 Additional Examples
342
+ ## Common Patterns
1330
343
 
1331
- ### Error Handling Pattern
344
+ ### Error Handling
1332
345
 
1333
346
  ```javascript
1334
- const client = new KadiClient({
1335
- name: 'robust-ability',
1336
- role: 'ability'
1337
- });
1338
-
1339
- client.registerTool('riskyOperation', async (params) => {
347
+ ability.registerTool({
348
+ name: 'riskyOperation',
349
+ input: z.object({ data: z.string() }),
350
+ output: z.object({
351
+ success: z.boolean(),
352
+ result: z.string().optional(),
353
+ error: z.string().optional()
354
+ })
355
+ }, async (params) => {
1340
356
  try {
1341
- const result = await performOperation(params);
1342
- await client.publishEvent('operation.success', { result });
357
+ const result = await performOperation(params.data);
1343
358
  return { success: true, result };
1344
359
  } catch (error) {
1345
- await client.publishEvent('operation.error', {
360
+ await ability.publishEvent('operation.error', {
1346
361
  error: error.message,
1347
362
  params
1348
363
  });
1349
- // Return error in structured format
1350
- return {
1351
- success: false,
1352
- error: error.message
1353
- };
364
+ return { success: false, error: error.message };
1354
365
  }
1355
366
  });
1356
367
  ```
1357
368
 
1358
- ### Health Check Pattern
369
+ ### Health Check
1359
370
 
1360
371
  ```javascript
1361
- // Register a standard health check tool
1362
- client.registerTool('health', async () => {
1363
- const checks = {
1364
- memory: process.memoryUsage(),
1365
- uptime: process.uptime(),
1366
- brokers: client.isConnected ? 'connected' : 'disconnected',
1367
- timestamp: Date.now()
1368
- };
1369
-
372
+ ability.registerTool({
373
+ name: 'health',
374
+ input: z.object({}),
375
+ output: z.object({
376
+ status: z.string(),
377
+ uptime: z.number(),
378
+ memory: z.record(z.number())
379
+ })
380
+ }, async () => {
1370
381
  return {
1371
382
  status: 'healthy',
1372
- checks
383
+ uptime: process.uptime(),
384
+ memory: process.memoryUsage()
1373
385
  };
1374
386
  });
1375
387
  ```
1376
388
 
1377
- ## 🐛 Troubleshooting
389
+ ## Environment Variables
1378
390
 
1379
- ### Common Issues and Solutions
391
+ ```bash
392
+ # Protocol selection
393
+ KADI_PROTOCOL=stdio
1380
394
 
1381
- #### Broker Connection Failures
395
+ # Broker configuration
396
+ KADI_BROKER_URL=ws://localhost:8080
397
+ KADI_AGENT_SCOPE=project-123
398
+ ```
1382
399
 
1383
- **Problem**: "Failed to connect to any brokers"
400
+ ## Debug Mode
1384
401
 
1385
- **Solutions**:
402
+ ```bash
403
+ DEBUG=kadi:* node index.js
404
+ ```
1386
405
 
1387
- - Verify broker URLs are correct
1388
- - Check network connectivity
1389
- - Ensure broker is running
1390
- - Try connecting with lower security (ws:// before wss://)
406
+ ## Troubleshooting
1391
407
 
1392
- #### Events Not Arriving
408
+ ### Broker Connection Failures
1393
409
 
1394
- **Problem**: Subscribed to events but callbacks not triggered
410
+ - Verify broker URL is correct
411
+ - Check network connectivity
412
+ - Ensure broker is running
1395
413
 
1396
- **Solutions**:
414
+ ### Events Not Arriving
1397
415
 
1398
416
  - Subscribe BEFORE triggering events
1399
- - Check pattern matching (use exact match first)
1400
- - Verify publisher and subscriber are on same network
1401
- - Enable debug logging to trace event flow
1402
-
1403
- #### Cross-Language Abilities Not Loading
1404
-
1405
- **Problem**: "Cannot find module 'ability.js'" when loading Go/Python abilities
1406
-
1407
- **Solutions**:
1408
-
1409
- - Ensure `scripts.start` field exists in agent.json
1410
- - Verify the binary/script is executable
1411
- - Check that setup script has been run
1412
- - Use stdio protocol, not native
1413
-
1414
- #### Memory Leaks
1415
-
1416
- **Problem**: Memory usage grows over time
1417
-
1418
- **Solutions**:
1419
-
1420
- - Always call unsubscribe functions
1421
- - Use `onceEvent` for single-use subscriptions
1422
- - Disconnect abilities when done
1423
- - Clear event listeners on cleanup
1424
-
1425
- ## 🤝 Contributing
417
+ - Verify pattern matching
418
+ - Check that publisher and subscriber are on same network
1426
419
 
1427
- We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
420
+ ### Type Errors with Zod
1428
421
 
1429
- 1. Fork the repository
1430
- 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
1431
- 3. Commit your changes (`git commit -m 'Add amazing feature'`)
1432
- 4. Push to the branch (`git push origin feature/amazing-feature`)
1433
- 5. Open a Pull Request
422
+ - Ensure Zod schemas match handler types
423
+ - Use `z.infer<typeof schema>` for TypeScript types
424
+ - Check that all required fields are provided
1434
425
 
1435
- ## 📄 License
426
+ ## License
1436
427
 
1437
- This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
428
+ MIT
1438
429
 
1439
- ## 🔗 Related Projects
430
+ ## Related Projects
1440
431
 
1441
432
  - [@kadi.build/cli](https://gitlab.com/humin-game-lab/kadi/kadi) - Command-line interface
1442
- - [@kadi.build/broker](https://gitlab.com/humin-game-lab/kadi/kadi-broker) - The KADI broker
1443
-
1444
- ## 📚 Resources
1445
-
1446
- ## ⚠️ Error Codes
1447
-
1448
- KADI core exposes a unified, typed error catalog to keep errors consistent across core and the broker.
1449
-
1450
- - CoreErrorCodes: CORE\_\* errors for library/transport concerns
1451
- - BrokerErrorCodes: BROKER\_\* errors mirrored for broker interactions
1452
- - ErrorCodes: merged view of both
1453
-
1454
- Usage:
1455
-
1456
- ```ts
1457
- import { KadiError, CoreErrorCodes } from '@kadi.build/core';
1458
-
1459
- throw new KadiError(
1460
- CoreErrorCodes.CORE_CONFIG_MISSING.code,
1461
- 'scripts.start is required for stdio',
1462
- { transport: 'stdio', abilityName: 'echo' }
1463
- );
1464
- ```
1465
-
1466
- Guidelines:
1467
-
1468
- - Do not use raw string codes like 'CONFIG_MISSING'; always use the exported constants.
1469
- - Prefer KadiError helper factories where available (e.g., KadiError.abilityNotFound()).
1470
- - KadiError automatically tags domain from the code prefix (core/broker) and preserves the original stack.
1471
- - Broker-side codes are fully prefixed: BROKER*SESSION*_, BROKER*AUTH*_, BROKER*AGENT*_, BROKER*NETWORK*_, etc.
1472
-
1473
- - [Event System Deep Dive](docs/event-system.md) - Complete guide to the event architecture
1474
- - [API Documentation](https://docs.kadi.build) - Full API reference
1475
- - [Examples Repository](https://github.com/kadi-examples) - More examples and patterns
433
+ - [@kadi.build/broker](https://gitlab.com/humin-game-lab/kadi/kadi-broker) - KADI broker
1476
434
 
1477
435
  ---
1478
436