@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.
- package/README.md +269 -1311
- package/dist/abilities/AbilityLoader.d.ts +26 -0
- package/dist/abilities/AbilityLoader.d.ts.map +1 -1
- package/dist/abilities/AbilityLoader.js +141 -18
- package/dist/abilities/AbilityLoader.js.map +1 -1
- package/dist/abilities/AbilityProxy.d.ts +33 -0
- package/dist/abilities/AbilityProxy.d.ts.map +1 -1
- package/dist/abilities/AbilityProxy.js +40 -0
- package/dist/abilities/AbilityProxy.js.map +1 -1
- package/dist/abilities/index.d.ts +1 -1
- package/dist/abilities/index.d.ts.map +1 -1
- package/dist/abilities/types.d.ts +67 -0
- package/dist/abilities/types.d.ts.map +1 -1
- package/dist/broker/BrokerProtocol.js +11 -11
- package/dist/broker/BrokerProtocol.js.map +1 -1
- package/dist/client/KadiClient.d.ts +191 -2
- package/dist/client/KadiClient.d.ts.map +1 -1
- package/dist/client/KadiClient.js +412 -2
- package/dist/client/KadiClient.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/messages/index.d.ts +1 -1
- package/dist/messages/index.js +1 -1
- package/dist/messages/index.js.map +1 -1
- package/dist/schemas/index.d.ts +3 -0
- package/dist/schemas/index.d.ts.map +1 -1
- package/dist/schemas/index.js +2 -0
- package/dist/schemas/index.js.map +1 -1
- package/dist/schemas/zod-helpers.d.ts +129 -0
- package/dist/schemas/zod-helpers.d.ts.map +1 -0
- package/dist/schemas/zod-helpers.js +225 -0
- package/dist/schemas/zod-helpers.js.map +1 -0
- package/dist/schemas/zod-to-json-schema.d.ts +159 -0
- package/dist/schemas/zod-to-json-schema.d.ts.map +1 -0
- package/dist/schemas/zod-to-json-schema.js +154 -0
- package/dist/schemas/zod-to-json-schema.js.map +1 -0
- package/dist/transports/NativeTransport.d.ts +29 -0
- package/dist/transports/NativeTransport.d.ts.map +1 -1
- package/dist/transports/NativeTransport.js +98 -3
- package/dist/transports/NativeTransport.js.map +1 -1
- package/dist/transports/StdioTransport.d.ts +141 -63
- package/dist/transports/StdioTransport.d.ts.map +1 -1
- package/dist/transports/StdioTransport.js +309 -232
- package/dist/transports/StdioTransport.js.map +1 -1
- package/dist/types/broker.d.ts +0 -22
- package/dist/types/broker.d.ts.map +1 -1
- package/dist/types/broker.js +0 -27
- package/dist/types/broker.js.map +1 -1
- package/dist/types/index.d.ts +3 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/zod-tools.d.ts +198 -0
- package/dist/types/zod-tools.d.ts.map +1 -0
- package/dist/types/zod-tools.js +14 -0
- package/dist/types/zod-tools.js.map +1 -0
- package/dist/utils/LockfileResolver.d.ts +108 -0
- package/dist/utils/LockfileResolver.d.ts.map +1 -0
- package/dist/utils/LockfileResolver.js +230 -0
- package/dist/utils/LockfileResolver.js.map +1 -0
- package/dist/utils/StdioMessageReader.d.ts +122 -0
- package/dist/utils/StdioMessageReader.d.ts.map +1 -0
- package/dist/utils/StdioMessageReader.js +209 -0
- package/dist/utils/StdioMessageReader.js.map +1 -0
- package/dist/utils/StdioMessageWriter.d.ts +104 -0
- package/dist/utils/StdioMessageWriter.d.ts.map +1 -0
- package/dist/utils/StdioMessageWriter.js +162 -0
- package/dist/utils/StdioMessageWriter.js.map +1 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,75 +1,45 @@
|
|
|
1
1
|
# @kadi.build/core
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> Framework for building distributed abilities with multiple transport protocols
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@kadi.build/core)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
7
|
|
|
8
|
-
##
|
|
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
|
-
|
|
14
|
+
## Quick Start
|
|
41
15
|
|
|
42
|
-
|
|
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
|
-
|
|
57
|
-
import { KadiClient } from '@kadi.build/core';
|
|
19
|
+
import { KadiClient, z } from '@kadi.build/core';
|
|
58
20
|
|
|
59
|
-
|
|
60
|
-
const mathAbility = new KadiClient({
|
|
21
|
+
const ability = new KadiClient({
|
|
61
22
|
name: 'math-ability',
|
|
62
|
-
|
|
63
|
-
protocol: 'stdio' // 'native', 'stdio', or 'broker'
|
|
23
|
+
version: '1.0.0'
|
|
64
24
|
});
|
|
65
25
|
|
|
66
|
-
// Register
|
|
67
|
-
|
|
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
|
|
72
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
57
|
+
### 1. With Zod Schemas (Recommended)
|
|
175
58
|
|
|
176
|
-
-
|
|
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
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
-
|
|
88
|
+
### 2. With createTool() Helper
|
|
225
89
|
|
|
226
|
-
|
|
90
|
+
For advanced use cases with runtime validation:
|
|
227
91
|
|
|
228
92
|
```javascript
|
|
229
|
-
import {
|
|
230
|
-
|
|
231
|
-
const
|
|
232
|
-
name: '
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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
|
-
|
|
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
|
|
378
|
-
|
|
379
|
-
The ability system provides two ways to define method schemas:
|
|
116
|
+
### 3. With JSON Schema (Backward Compatible)
|
|
380
117
|
|
|
381
|
-
|
|
382
|
-
2. **Inline Schemas** (passed directly to `registerTool()`)
|
|
118
|
+
Traditional approach still supported:
|
|
383
119
|
|
|
384
120
|
```javascript
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
{
|
|
389
|
-
|
|
390
|
-
{
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
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
|
-
|
|
142
|
+
## Migration from JSON Schema to Zod
|
|
433
143
|
|
|
144
|
+
**Before (JSON Schema):**
|
|
434
145
|
```javascript
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
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
|
-
|
|
459
|
-
|
|
460
|
-
type: '
|
|
461
|
-
|
|
462
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
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
|
-
|
|
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
|
-
|
|
205
|
+
## Transport Protocols
|
|
896
206
|
|
|
897
|
-
|
|
207
|
+
KADI supports three transport protocols for different use cases:
|
|
898
208
|
|
|
209
|
+
### Native (In-Process)
|
|
899
210
|
```javascript
|
|
900
|
-
|
|
901
|
-
|
|
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
|
-
###
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
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
|
-
|
|
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
|
|
942
|
-
|
|
943
|
-
|
|
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
|
-
|
|
230
|
+
## Events
|
|
967
231
|
|
|
968
|
-
Publish and subscribe to
|
|
232
|
+
Publish and subscribe to events across all protocols:
|
|
969
233
|
|
|
970
234
|
```javascript
|
|
971
235
|
// Publishing events
|
|
972
|
-
|
|
973
|
-
name: '
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
})
|
|
977
|
-
|
|
978
|
-
|
|
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
|
-
|
|
986
|
-
const result = await processData(data);
|
|
244
|
+
const result = await doWork(params.data);
|
|
987
245
|
|
|
988
|
-
await
|
|
989
|
-
|
|
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
|
-
|
|
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
|
-
|
|
256
|
+
## Broker Mode
|
|
1054
257
|
|
|
1055
|
-
|
|
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-
|
|
1139
|
-
role: '
|
|
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
|
|
1221
|
-
|
|
1222
|
-
'
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
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
|
|
1265
|
-
await
|
|
281
|
+
// Connect to broker
|
|
282
|
+
await client.serve('broker');
|
|
1266
283
|
|
|
1267
284
|
// Call remote tools
|
|
1268
|
-
const result = await
|
|
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
|
-
|
|
291
|
+
## API Reference
|
|
1281
292
|
|
|
1282
|
-
|
|
293
|
+
### KadiClient
|
|
1283
294
|
|
|
1284
|
-
|
|
1285
|
-
import { loadAbility } from '@kadi.build/core';
|
|
295
|
+
Main class for all KADI operations:
|
|
1286
296
|
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
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
|
-
**
|
|
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
|
-
|
|
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
|
-
|
|
318
|
+
Load an ability by name and protocol:
|
|
1303
319
|
|
|
1304
320
|
```javascript
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
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
|
-
###
|
|
1316
|
-
|
|
1317
|
-
Enable detailed logging:
|
|
327
|
+
### createTool
|
|
1318
328
|
|
|
1319
|
-
|
|
1320
|
-
DEBUG=kadi:* node index.js
|
|
1321
|
-
```
|
|
329
|
+
Helper for creating tools with validation:
|
|
1322
330
|
|
|
1323
|
-
|
|
331
|
+
```javascript
|
|
332
|
+
import { createTool, z } from '@kadi.build/core';
|
|
1324
333
|
|
|
1325
|
-
|
|
1326
|
-
|
|
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
|
-
##
|
|
342
|
+
## Common Patterns
|
|
1330
343
|
|
|
1331
|
-
### Error Handling
|
|
344
|
+
### Error Handling
|
|
1332
345
|
|
|
1333
346
|
```javascript
|
|
1334
|
-
|
|
1335
|
-
name: '
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
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
|
|
360
|
+
await ability.publishEvent('operation.error', {
|
|
1346
361
|
error: error.message,
|
|
1347
362
|
params
|
|
1348
363
|
});
|
|
1349
|
-
|
|
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
|
|
369
|
+
### Health Check
|
|
1359
370
|
|
|
1360
371
|
```javascript
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
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
|
-
|
|
383
|
+
uptime: process.uptime(),
|
|
384
|
+
memory: process.memoryUsage()
|
|
1373
385
|
};
|
|
1374
386
|
});
|
|
1375
387
|
```
|
|
1376
388
|
|
|
1377
|
-
##
|
|
389
|
+
## Environment Variables
|
|
1378
390
|
|
|
1379
|
-
|
|
391
|
+
```bash
|
|
392
|
+
# Protocol selection
|
|
393
|
+
KADI_PROTOCOL=stdio
|
|
1380
394
|
|
|
1381
|
-
|
|
395
|
+
# Broker configuration
|
|
396
|
+
KADI_BROKER_URL=ws://localhost:8080
|
|
397
|
+
KADI_AGENT_SCOPE=project-123
|
|
398
|
+
```
|
|
1382
399
|
|
|
1383
|
-
|
|
400
|
+
## Debug Mode
|
|
1384
401
|
|
|
1385
|
-
|
|
402
|
+
```bash
|
|
403
|
+
DEBUG=kadi:* node index.js
|
|
404
|
+
```
|
|
1386
405
|
|
|
1387
|
-
|
|
1388
|
-
- Check network connectivity
|
|
1389
|
-
- Ensure broker is running
|
|
1390
|
-
- Try connecting with lower security (ws:// before wss://)
|
|
406
|
+
## Troubleshooting
|
|
1391
407
|
|
|
1392
|
-
|
|
408
|
+
### Broker Connection Failures
|
|
1393
409
|
|
|
1394
|
-
|
|
410
|
+
- Verify broker URL is correct
|
|
411
|
+
- Check network connectivity
|
|
412
|
+
- Ensure broker is running
|
|
1395
413
|
|
|
1396
|
-
|
|
414
|
+
### Events Not Arriving
|
|
1397
415
|
|
|
1398
416
|
- Subscribe BEFORE triggering events
|
|
1399
|
-
-
|
|
1400
|
-
-
|
|
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
|
-
|
|
420
|
+
### Type Errors with Zod
|
|
1428
421
|
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
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
|
-
##
|
|
426
|
+
## License
|
|
1436
427
|
|
|
1437
|
-
|
|
428
|
+
MIT
|
|
1438
429
|
|
|
1439
|
-
##
|
|
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) -
|
|
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
|
|