@kadi.build/core 0.0.1-alpha.3 → 0.0.1-alpha.5
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 +754 -606
- package/dist/KadiClient.d.ts +440 -0
- package/dist/KadiClient.d.ts.map +1 -0
- package/dist/KadiClient.js +1518 -0
- package/dist/KadiClient.js.map +1 -0
- package/dist/errors/error-codes.d.ts +215 -0
- package/dist/errors/error-codes.d.ts.map +1 -0
- package/dist/errors/error-codes.js +295 -0
- package/dist/errors/error-codes.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/loadAbility.d.ts +106 -0
- package/dist/loadAbility.d.ts.map +1 -0
- package/dist/loadAbility.js +376 -0
- package/dist/loadAbility.js.map +1 -0
- package/dist/messages/BrokerMessages.d.ts +84 -0
- package/dist/messages/BrokerMessages.d.ts.map +1 -0
- package/dist/messages/BrokerMessages.js +125 -0
- package/dist/messages/BrokerMessages.js.map +1 -0
- package/dist/messages/MessageBuilder.d.ts +83 -0
- package/dist/messages/MessageBuilder.d.ts.map +1 -0
- package/dist/messages/MessageBuilder.js +144 -0
- package/dist/messages/MessageBuilder.js.map +1 -0
- package/dist/schemas/events.schemas.d.ts +177 -0
- package/dist/schemas/events.schemas.d.ts.map +1 -0
- package/dist/schemas/events.schemas.js +265 -0
- package/dist/schemas/events.schemas.js.map +1 -0
- package/dist/schemas/index.d.ts +3 -0
- package/dist/schemas/index.d.ts.map +1 -0
- package/dist/schemas/index.js +4 -0
- package/dist/schemas/index.js.map +1 -0
- package/dist/schemas/kadi.schemas.d.ts +70 -0
- package/dist/schemas/kadi.schemas.d.ts.map +1 -0
- package/dist/schemas/kadi.schemas.js +120 -0
- package/dist/schemas/kadi.schemas.js.map +1 -0
- package/dist/transports/BrokerTransport.d.ts +106 -0
- package/dist/transports/BrokerTransport.d.ts.map +1 -0
- package/dist/transports/BrokerTransport.js +177 -0
- package/dist/transports/BrokerTransport.js.map +1 -0
- package/dist/transports/NativeTransport.d.ts +82 -0
- package/dist/transports/NativeTransport.d.ts.map +1 -0
- package/dist/transports/NativeTransport.js +263 -0
- package/dist/transports/NativeTransport.js.map +1 -0
- package/dist/transports/StdioTransport.d.ts +112 -0
- package/dist/transports/StdioTransport.d.ts.map +1 -0
- package/dist/transports/StdioTransport.js +445 -0
- package/dist/transports/StdioTransport.js.map +1 -0
- package/dist/transports/Transport.d.ts +93 -0
- package/dist/transports/Transport.d.ts.map +1 -0
- package/dist/transports/Transport.js +13 -0
- package/dist/transports/Transport.js.map +1 -0
- package/dist/types/broker.d.ts +31 -0
- package/dist/types/broker.d.ts.map +1 -0
- package/dist/types/broker.js +6 -0
- package/dist/types/broker.js.map +1 -0
- package/dist/types/core.d.ts +139 -0
- package/dist/types/core.d.ts.map +1 -0
- package/dist/types/core.js +26 -0
- package/dist/types/core.js.map +1 -0
- package/dist/types/events.d.ts +186 -0
- package/dist/types/events.d.ts.map +1 -0
- package/dist/types/events.js +16 -0
- package/dist/types/events.js.map +1 -0
- package/dist/types/index.d.ts +9 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +13 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/protocol.d.ts +160 -0
- package/dist/types/protocol.d.ts.map +1 -0
- package/dist/types/protocol.js +5 -0
- package/dist/types/protocol.js.map +1 -0
- package/dist/utils/agentUtils.d.ts +187 -0
- package/dist/utils/agentUtils.d.ts.map +1 -0
- package/dist/utils/agentUtils.js +185 -0
- package/dist/utils/agentUtils.js.map +1 -0
- package/dist/utils/commandUtils.d.ts +45 -0
- package/dist/utils/commandUtils.d.ts.map +1 -0
- package/dist/utils/commandUtils.js +145 -0
- package/dist/utils/commandUtils.js.map +1 -0
- package/dist/utils/configUtils.d.ts +55 -0
- package/dist/utils/configUtils.d.ts.map +1 -0
- package/dist/utils/configUtils.js +100 -0
- package/dist/utils/configUtils.js.map +1 -0
- package/dist/utils/logger.d.ts +59 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +122 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/pathUtils.d.ts +48 -0
- package/dist/utils/pathUtils.d.ts.map +1 -0
- package/dist/utils/pathUtils.js +128 -0
- package/dist/utils/pathUtils.js.map +1 -0
- package/package.json +56 -5
- package/agent.json +0 -18
- package/examples/example-abilities/echo-js/README.md +0 -131
- package/examples/example-abilities/echo-js/agent.json +0 -63
- package/examples/example-abilities/echo-js/package.json +0 -24
- package/examples/example-abilities/echo-js/service.js +0 -43
- package/examples/example-abilities/hash-go/agent.json +0 -53
- package/examples/example-abilities/hash-go/cmd/hash_ability/main.go +0 -340
- package/examples/example-abilities/hash-go/go.mod +0 -3
- package/examples/example-agent/abilities/echo-js/0.0.1/README.md +0 -131
- package/examples/example-agent/abilities/echo-js/0.0.1/agent.json +0 -63
- package/examples/example-agent/abilities/echo-js/0.0.1/package-lock.json +0 -93
- package/examples/example-agent/abilities/echo-js/0.0.1/package.json +0 -24
- package/examples/example-agent/abilities/echo-js/0.0.1/service.js +0 -41
- package/examples/example-agent/abilities/hash-go/0.0.1/agent.json +0 -53
- package/examples/example-agent/abilities/hash-go/0.0.1/bin/hash_ability +0 -0
- package/examples/example-agent/abilities/hash-go/0.0.1/cmd/hash_ability/main.go +0 -340
- package/examples/example-agent/abilities/hash-go/0.0.1/go.mod +0 -3
- package/examples/example-agent/agent.json +0 -39
- package/examples/example-agent/index.js +0 -102
- package/examples/example-agent/package-lock.json +0 -93
- package/examples/example-agent/package.json +0 -17
- package/src/KadiAbility.js +0 -478
- package/src/index.js +0 -65
- package/src/loadAbility.js +0 -1086
- package/src/servers/BaseRpcServer.js +0 -404
- package/src/servers/BrokerRpcServer.js +0 -776
- package/src/servers/StdioRpcServer.js +0 -360
- package/src/transport/BrokerMessageBuilder.js +0 -377
- package/src/transport/IpcMessageBuilder.js +0 -1229
- package/src/utils/agentUtils.js +0 -137
- package/src/utils/commandUtils.js +0 -64
- package/src/utils/configUtils.js +0 -72
- package/src/utils/logger.js +0 -161
- package/src/utils/pathUtils.js +0 -86
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
## 🎯 Overview
|
|
9
9
|
|
|
10
|
-
`@kadi.build/core` is the foundational library for creating KADI abilities - modular, protocol-agnostic
|
|
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
11
|
|
|
12
12
|
## 📚 Table of Contents
|
|
13
13
|
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
- [🚀 Quick Start](#-quick-start)
|
|
17
17
|
- [🔌 Transport Protocols](#-transport-protocols)
|
|
18
18
|
- [🛠️ Creating Abilities](#-creating-abilities)
|
|
19
|
+
- [🤖 Creating Agents](#-creating-agents)
|
|
19
20
|
- [🔄 Architecture Deep Dive](#-architecture-deep-dive)
|
|
20
21
|
- [📥 Loading Abilities](#-loading-abilities)
|
|
21
22
|
- [⚙️ Configuration](#-configuration)
|
|
@@ -44,21 +45,26 @@ npm install -g @kadi.build/cli
|
|
|
44
45
|
|
|
45
46
|
## 🚀 Quick Start
|
|
46
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
|
+
|
|
47
53
|
### Creating Your First Ability
|
|
48
54
|
|
|
49
55
|
```javascript
|
|
50
56
|
#!/usr/bin/env node
|
|
51
|
-
import {
|
|
57
|
+
import { KadiClient } from '@kadi.build/core';
|
|
52
58
|
|
|
53
|
-
// Create
|
|
54
|
-
const mathAbility = new
|
|
59
|
+
// Create a ability instance
|
|
60
|
+
const mathAbility = new KadiClient({
|
|
55
61
|
name: 'math-ability',
|
|
56
|
-
|
|
57
|
-
|
|
62
|
+
role: 'ability', // 'agent', 'ability'
|
|
63
|
+
protocol: 'stdio' // 'native', 'stdio', or 'broker'
|
|
58
64
|
});
|
|
59
65
|
|
|
60
|
-
// Register a
|
|
61
|
-
mathAbility.
|
|
66
|
+
// Register a tool
|
|
67
|
+
mathAbility.registerTool('add', async ({ a, b }) => {
|
|
62
68
|
return { result: a + b };
|
|
63
69
|
});
|
|
64
70
|
|
|
@@ -78,14 +84,58 @@ async function main() {
|
|
|
78
84
|
// Call methods like regular functions
|
|
79
85
|
const result = await math.add({ a: 5, b: 3 });
|
|
80
86
|
console.log(result); // { result: 8 }
|
|
81
|
-
|
|
82
|
-
// List available methods
|
|
83
|
-
console.log(math.__list()); // ['add']
|
|
84
87
|
}
|
|
85
88
|
|
|
86
89
|
main().catch(console.error);
|
|
87
90
|
```
|
|
88
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
|
+
|
|
89
139
|
## 🔌 Transport Protocols
|
|
90
140
|
|
|
91
141
|
KADI abilities support three transport protocols, each optimized for different use cases:
|
|
@@ -107,7 +157,7 @@ const ability = await loadAbility('my-ability', 'native');
|
|
|
107
157
|
- **Use Case**: Language-agnostic local execution
|
|
108
158
|
- **How it Works**: Spawns abilities as child processes, communicates via LSP-style JSON-RPC over stdin/stdout
|
|
109
159
|
- **Performance**: Minimal overhead, reliable local IPC
|
|
110
|
-
- **Best For**: Python scripts, Go binaries, Node.js
|
|
160
|
+
- **Best For**: Python scripts, Go binaries, Node.js abilities, development/testing
|
|
111
161
|
|
|
112
162
|
```javascript
|
|
113
163
|
// Force stdio protocol
|
|
@@ -123,10 +173,10 @@ KADI_PROTOCOL=stdio # Identifies the protocol being used
|
|
|
123
173
|
|
|
124
174
|
### 3. Broker Protocol (Distributed)
|
|
125
175
|
|
|
126
|
-
- **Use Case**: Distributed
|
|
176
|
+
- **Use Case**: Distributed systems, containerized deployments
|
|
127
177
|
- **How it Works**: Abilities connect to a WebSocket broker for network communication
|
|
128
178
|
- **Performance**: Network overhead, but enables scaling and distribution
|
|
129
|
-
- **Best For**:
|
|
179
|
+
- **Best For**: Distributed abilities, cloud deployments, load-balanced systems
|
|
130
180
|
|
|
131
181
|
```javascript
|
|
132
182
|
// Use broker for distributed execution
|
|
@@ -136,77 +186,67 @@ const ability = await loadAbility('my-ability', 'broker');
|
|
|
136
186
|
**Environment Variables Passed to Child Process:**
|
|
137
187
|
|
|
138
188
|
```bash
|
|
139
|
-
KADI_PROTOCOL=broker
|
|
189
|
+
KADI_PROTOCOL=broker # Identifies the protocol
|
|
140
190
|
KADI_BROKER_URL=ws://localhost:8080 # Broker connection URL
|
|
141
|
-
|
|
191
|
+
KADI_ABILITY_NAME=ability.echo.1_0_0 # Ability identifier
|
|
142
192
|
KADI_AGENT_SCOPE=uuid-here # Agent's scope/namespace
|
|
143
193
|
# Plus all parent process.env variables
|
|
144
194
|
```
|
|
145
195
|
|
|
146
|
-
**
|
|
196
|
+
**Network/Namespace Visibility:**
|
|
147
197
|
|
|
148
|
-
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
|
|
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:
|
|
149
199
|
|
|
150
200
|
```javascript
|
|
151
201
|
// In your ability code:
|
|
152
|
-
const echoAbility = new
|
|
202
|
+
const echoAbility = new KadiClient({
|
|
153
203
|
name: 'echo-js',
|
|
154
204
|
version: '0.0.1',
|
|
155
205
|
description: 'Echo ability with broker support',
|
|
156
|
-
|
|
206
|
+
network: process.env.KADI_AGENT_SCOPE // Use agent's network for visibility
|
|
157
207
|
});
|
|
158
208
|
|
|
159
|
-
// Without this, the ability would only be visible in the 'global'
|
|
209
|
+
// Without this, the ability would only be visible in the 'global' network
|
|
160
210
|
// and the parent agent wouldn't be able to communicate with it
|
|
161
211
|
```
|
|
162
212
|
|
|
163
213
|
### Protocol Selection Strategy
|
|
164
214
|
|
|
165
|
-
When no protocol is specified, `loadAbility`
|
|
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'.
|
|
166
216
|
|
|
167
217
|
```mermaid
|
|
168
218
|
graph TD
|
|
169
219
|
A[Load Ability] --> B{Protocol Specified?}
|
|
170
220
|
B -->|Yes| C[Use Specified Protocol]
|
|
171
|
-
B -->|No| D
|
|
172
|
-
D --> E[Use First Interface Listed]
|
|
173
|
-
E --> F[native/stdio/broker]
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
**Recommended Interface Order in agent.json:**
|
|
177
|
-
|
|
178
|
-
```json
|
|
179
|
-
{
|
|
180
|
-
"interfaces": {
|
|
181
|
-
"native": { "entry": "service.js" }, // 1st choice: Fastest
|
|
182
|
-
"stdio": { "discover": true }, // 2nd choice: Balanced
|
|
183
|
-
"broker": { "discover": true } // 3rd choice: Distributed
|
|
184
|
-
}
|
|
185
|
-
}
|
|
221
|
+
B -->|No| D[Default to 'native']
|
|
186
222
|
```
|
|
187
223
|
|
|
188
|
-
## 🛠️ Creating Abilities
|
|
224
|
+
## 🛠️ Creating Abilities with KadiClient
|
|
189
225
|
|
|
190
226
|
### Basic Ability Structure
|
|
191
227
|
|
|
192
228
|
```javascript
|
|
193
|
-
import {
|
|
229
|
+
import { KadiClient } from '@kadi.build/core';
|
|
194
230
|
|
|
195
|
-
const ability = new
|
|
231
|
+
const ability = new KadiClient({
|
|
196
232
|
name: 'echo-ability',
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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'
|
|
200
240
|
});
|
|
201
241
|
|
|
202
|
-
// Method 1: Simple
|
|
203
|
-
ability.
|
|
242
|
+
// Method 1: Simple tool registration
|
|
243
|
+
ability.registerTool('echo', async ({ message }) => ({
|
|
204
244
|
echo: message,
|
|
205
245
|
timestamp: new Date().toISOString()
|
|
206
246
|
}));
|
|
207
247
|
|
|
208
|
-
// Method 2:
|
|
209
|
-
ability.
|
|
248
|
+
// Method 2: Tool with inline schema
|
|
249
|
+
ability.registerTool(
|
|
210
250
|
'format',
|
|
211
251
|
async ({ text, style }) => ({
|
|
212
252
|
formatted: style === 'upper' ? text.toUpperCase() : text.toLowerCase()
|
|
@@ -240,40 +280,60 @@ ability.serve().catch(console.error);
|
|
|
240
280
|
|
|
241
281
|
### Event Publishing and Subscription
|
|
242
282
|
|
|
243
|
-
**
|
|
283
|
+
**Full Support**: Events work across all protocols - native, stdio, and broker.
|
|
244
284
|
|
|
245
|
-
|
|
285
|
+
KadiClient provides a unified event system that works consistently across all transport protocols:
|
|
246
286
|
|
|
247
287
|
```javascript
|
|
248
|
-
// In your ability (
|
|
249
|
-
const ability = new
|
|
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
|
+
});
|
|
250
294
|
|
|
251
|
-
// Publish events from
|
|
252
|
-
ability.
|
|
253
|
-
// Publish
|
|
254
|
-
ability.publishEvent('echo
|
|
255
|
-
|
|
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() };
|
|
256
302
|
|
|
257
|
-
|
|
303
|
+
await ability.publishEvent('echo.completed', { result });
|
|
304
|
+
return result;
|
|
258
305
|
});
|
|
259
306
|
|
|
260
|
-
// In your agent (
|
|
261
|
-
const
|
|
307
|
+
// In your agent (agent.js)
|
|
308
|
+
const agent = new KadiClient({
|
|
309
|
+
name: 'my-client',
|
|
310
|
+
role: 'agent',
|
|
311
|
+
protocol: 'broker'
|
|
312
|
+
});
|
|
262
313
|
|
|
263
|
-
// Subscribe to events
|
|
264
|
-
|
|
265
|
-
console.log(
|
|
314
|
+
// Subscribe to events using patterns
|
|
315
|
+
agent.subscribeToEvent('echo.*', (data) => {
|
|
316
|
+
console.log('Echo event received:', data);
|
|
266
317
|
});
|
|
267
318
|
|
|
268
|
-
//
|
|
269
|
-
const
|
|
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
|
+
});
|
|
270
324
|
```
|
|
271
325
|
|
|
272
|
-
**
|
|
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
|
|
273
333
|
|
|
274
334
|
### Ability Configuration (agent.json)
|
|
275
335
|
|
|
276
|
-
Every ability should have an `agent.json` file that defines its capabilities
|
|
336
|
+
Every ability should have an `agent.json` file that defines its capabilities:
|
|
277
337
|
|
|
278
338
|
```json
|
|
279
339
|
{
|
|
@@ -281,24 +341,10 @@ Every ability should have an `agent.json` file that defines its capabilities and
|
|
|
281
341
|
"kind": "ability",
|
|
282
342
|
"version": "1.0.0",
|
|
283
343
|
"license": "MIT",
|
|
284
|
-
"description": "Echo
|
|
344
|
+
"description": "Echo ability with multiple transport support",
|
|
285
345
|
"scripts": {
|
|
286
346
|
"setup": "npm install",
|
|
287
|
-
"start": "node
|
|
288
|
-
},
|
|
289
|
-
"interfaces": {
|
|
290
|
-
"native": {
|
|
291
|
-
"entry": "service.js"
|
|
292
|
-
},
|
|
293
|
-
"stdio": {
|
|
294
|
-
"discover": true,
|
|
295
|
-
"timeoutMs": 10000
|
|
296
|
-
},
|
|
297
|
-
"broker": {
|
|
298
|
-
"discover": true,
|
|
299
|
-
"timeoutMs": 15000,
|
|
300
|
-
"serviceName": "echo-service"
|
|
301
|
-
}
|
|
347
|
+
"start": "node ability.js"
|
|
302
348
|
},
|
|
303
349
|
"exports": [
|
|
304
350
|
{
|
|
@@ -323,7 +369,8 @@ Every ability should have an `agent.json` file that defines its capabilities and
|
|
|
323
369
|
"brokers": {
|
|
324
370
|
"local": "ws://localhost:8080",
|
|
325
371
|
"remote": "ws://api.example.com:8080"
|
|
326
|
-
}
|
|
372
|
+
},
|
|
373
|
+
"defaultBroker": "local"
|
|
327
374
|
}
|
|
328
375
|
```
|
|
329
376
|
|
|
@@ -332,7 +379,7 @@ Every ability should have an `agent.json` file that defines its capabilities and
|
|
|
332
379
|
The ability system provides two ways to define method schemas:
|
|
333
380
|
|
|
334
381
|
1. **Export Schemas** (defined in `agent.json` exports section)
|
|
335
|
-
2. **Inline Schemas** (passed directly to
|
|
382
|
+
2. **Inline Schemas** (passed directly to `registerTool()`)
|
|
336
383
|
|
|
337
384
|
```javascript
|
|
338
385
|
// Option 1: Define in agent.json exports section
|
|
@@ -350,7 +397,7 @@ The ability system provides two ways to define method schemas:
|
|
|
350
397
|
}
|
|
351
398
|
|
|
352
399
|
// Option 2: Define inline when registering the method
|
|
353
|
-
ability.
|
|
400
|
+
ability.registerTool('process', handler, {
|
|
354
401
|
description: 'Process data',
|
|
355
402
|
inputSchema: { /* ... */ },
|
|
356
403
|
outputSchema: { /* ... */ }
|
|
@@ -361,176 +408,327 @@ ability.method('process', handler, {
|
|
|
361
408
|
|
|
362
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.
|
|
363
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']
|
|
428
|
+
});
|
|
429
|
+
const brokerResult = await remoteAbility.process({ data: 'test' });
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
### Using KadiClient with Named Brokers
|
|
433
|
+
|
|
434
|
+
```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}!` };
|
|
456
|
+
},
|
|
457
|
+
{
|
|
458
|
+
description: 'Greet someone by name',
|
|
459
|
+
inputSchema: {
|
|
460
|
+
type: 'object',
|
|
461
|
+
properties: { name: { type: 'string' } },
|
|
462
|
+
required: ['name']
|
|
463
|
+
}
|
|
464
|
+
}
|
|
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
|
+
|
|
364
482
|
## 🔄 Architecture Deep Dive
|
|
365
483
|
|
|
366
|
-
###
|
|
484
|
+
### Transport Architecture and Loading Sequence
|
|
367
485
|
|
|
368
|
-
The loading process
|
|
486
|
+
The loading process uses modular transports based on the selected protocol:
|
|
369
487
|
|
|
370
488
|
```mermaid
|
|
371
489
|
sequenceDiagram
|
|
372
490
|
participant Client
|
|
373
491
|
participant loadAbility
|
|
492
|
+
participant Transport
|
|
374
493
|
participant FileSystem
|
|
375
494
|
participant ChildProcess
|
|
376
495
|
participant Ability
|
|
377
496
|
participant Broker
|
|
378
497
|
|
|
379
498
|
Client->>loadAbility: loadAbility('echo-js', protocol?)
|
|
380
|
-
loadAbility->>FileSystem: Read
|
|
381
|
-
FileSystem-->>loadAbility: Return
|
|
382
|
-
loadAbility->>FileSystem: Check ability directory exists
|
|
383
|
-
loadAbility->>FileSystem: Read ability's agent.json manifest
|
|
499
|
+
loadAbility->>FileSystem: Read agent.json manifests
|
|
500
|
+
FileSystem-->>loadAbility: Return config & version
|
|
384
501
|
|
|
385
502
|
alt protocol === 'native'
|
|
386
|
-
loadAbility->>
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
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
|
|
391
509
|
else protocol === 'stdio'
|
|
392
|
-
loadAbility->>
|
|
393
|
-
ChildProcess
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
Ability
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
loadAbility->>Ability: Send __kadi_discover
|
|
400
|
-
Ability-->>loadAbility: Return { functions: {...} }
|
|
401
|
-
end
|
|
402
|
-
|
|
403
|
-
loadAbility->>loadAbility: Merge static exports + discovered functions
|
|
404
|
-
loadAbility->>loadAbility: Build proxy with RPC calls
|
|
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
|
|
405
517
|
else protocol === 'broker'
|
|
406
|
-
loadAbility->>
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
Broker
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
Ability->>Broker: registerCapabilities({ tools, scopes: [KADI_AGENT_SCOPE] })
|
|
417
|
-
|
|
418
|
-
Note over loadAbility,Broker: Parent connects to broker
|
|
419
|
-
loadAbility->>Broker: WebSocket connect
|
|
420
|
-
loadAbility->>Broker: hello/auth/register sequence
|
|
421
|
-
|
|
422
|
-
opt if discover enabled
|
|
423
|
-
loadAbility->>Broker: listTools({ scopes: [scope] })
|
|
424
|
-
Broker-->>loadAbility: Return available tools
|
|
425
|
-
end
|
|
426
|
-
|
|
427
|
-
loadAbility->>loadAbility: Merge static exports + broker tools
|
|
428
|
-
loadAbility->>loadAbility: Build proxy with broker RPC
|
|
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
|
|
429
528
|
end
|
|
430
529
|
|
|
431
|
-
loadAbility->>
|
|
432
|
-
Client
|
|
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
|
|
433
548
|
```
|
|
434
549
|
|
|
435
|
-
###
|
|
550
|
+
### KadiClient Architecture: Tool Registration & Serving
|
|
436
551
|
|
|
437
|
-
How
|
|
552
|
+
How KadiClient registers tools and starts serving:
|
|
438
553
|
|
|
439
554
|
```mermaid
|
|
440
555
|
sequenceDiagram
|
|
441
556
|
participant Dev as Developer
|
|
442
|
-
participant
|
|
443
|
-
participant Server as RpcServer
|
|
557
|
+
participant KC as KadiClient
|
|
444
558
|
participant Transport
|
|
445
|
-
participant
|
|
559
|
+
participant Network as Network Layer
|
|
560
|
+
participant RemoteClient
|
|
446
561
|
|
|
447
|
-
Note over Dev,
|
|
448
|
-
Dev->>
|
|
449
|
-
|
|
450
|
-
KA->>KA: Store in methodHandlers Map
|
|
562
|
+
Note over Dev,KC: Tool Registration Phase
|
|
563
|
+
Dev->>KC: new KadiClient({ name, role, protocol })
|
|
564
|
+
KC->>KC: Initialize based on role & protocol
|
|
451
565
|
|
|
452
|
-
Dev->>
|
|
453
|
-
|
|
454
|
-
KA->>KA: Store schema in methodMCPSchemas
|
|
566
|
+
Dev->>KC: registerTool('echo', handler, schema?)
|
|
567
|
+
KC->>KC: Store in toolHandlers Map<name, {handler, schema}>
|
|
455
568
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
KA->>KA: Detect protocol from env/options
|
|
569
|
+
Dev->>KC: registerTool('process', handler, schema)
|
|
570
|
+
KC->>KC: Add to toolHandlers Map
|
|
459
571
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
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
|
|
465
584
|
else protocol === 'broker'
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
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)
|
|
472
593
|
end
|
|
473
594
|
|
|
474
|
-
|
|
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
|
|
475
612
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
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
|
|
484
669
|
```
|
|
485
670
|
|
|
486
671
|
### Broker Protocol Communication Flow
|
|
487
672
|
|
|
488
|
-
Detailed view of broker-based communication:
|
|
673
|
+
Detailed view of broker-based communication with heartbeat:
|
|
489
674
|
|
|
490
675
|
```mermaid
|
|
491
676
|
sequenceDiagram
|
|
492
|
-
participant
|
|
493
|
-
participant Ability as Ability (Child)
|
|
677
|
+
participant Client as KadiClient
|
|
494
678
|
participant Broker as KADI Broker
|
|
495
|
-
participant
|
|
496
|
-
|
|
497
|
-
Note over
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
Broker-->>
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
Broker
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
Broker
|
|
527
|
-
|
|
528
|
-
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
|
|
529
727
|
```
|
|
530
728
|
|
|
531
|
-
### Stdio Protocol Frame Processing
|
|
729
|
+
### Stdio Protocol: LSP-Style Frame Processing
|
|
532
730
|
|
|
533
|
-
How LSP-style
|
|
731
|
+
How KadiClient handles stdio communication with LSP-style framing:
|
|
534
732
|
|
|
535
733
|
```mermaid
|
|
536
734
|
sequenceDiagram
|
|
@@ -538,10 +736,10 @@ sequenceDiagram
|
|
|
538
736
|
participant SR as StdioFrameReader
|
|
539
737
|
participant SW as StdioFrameWriter
|
|
540
738
|
participant Server as StdioRpcServer
|
|
541
|
-
participant Ability as
|
|
739
|
+
participant Ability as KadiClient
|
|
542
740
|
|
|
543
741
|
Note over Parent,SR: Incoming Request
|
|
544
|
-
Parent->>SR: Write to stdin: "
|
|
742
|
+
Parent->>SR: Write to stdin: "Content-Length: 52\r\n\r\n{...}"
|
|
545
743
|
SR->>SR: Buffer incoming data
|
|
546
744
|
SR->>SR: Look for Content-Length header
|
|
547
745
|
SR->>SR: Parse header, extract body length
|
|
@@ -558,7 +756,7 @@ sequenceDiagram
|
|
|
558
756
|
Server->>SW: write(response)
|
|
559
757
|
SW->>SW: JSON.stringify(response)
|
|
560
758
|
SW->>SW: Calculate byte length
|
|
561
|
-
SW->>SW: Create header: "
|
|
759
|
+
SW->>SW: Create header: "Content-Length: N\r\n\r\n"
|
|
562
760
|
SW->>Parent: Write header + body to stdout
|
|
563
761
|
|
|
564
762
|
Note over SR: Error Recovery
|
|
@@ -590,22 +788,21 @@ const nativeAbility = await loadAbility('my-ability', 'native');
|
|
|
590
788
|
### Working with Loaded Abilities
|
|
591
789
|
|
|
592
790
|
```javascript
|
|
593
|
-
//
|
|
594
|
-
const
|
|
595
|
-
console.log('Available methods:', methods);
|
|
791
|
+
// Call methods directly - no need to discover them first
|
|
792
|
+
const result = await ability.echo({ message: 'hello' });
|
|
596
793
|
|
|
597
794
|
// Call methods
|
|
598
795
|
const result = await ability.someMethod({ param: 'value' });
|
|
599
796
|
|
|
600
797
|
// Direct RPC call (bypasses method validation)
|
|
601
|
-
const response = await ability.
|
|
798
|
+
const response = await ability.__call('someMethod', { param: 'value' });
|
|
602
799
|
|
|
603
800
|
// Subscribe to events
|
|
604
801
|
ability.events.on('custom:event', (data) => {
|
|
605
802
|
console.log('Event received:', data);
|
|
606
803
|
});
|
|
607
804
|
|
|
608
|
-
// Note: Events work for native and
|
|
805
|
+
// Note: Events work for all protocols (native, stdio, and broker)
|
|
609
806
|
// Subscribe before calling methods that emit events
|
|
610
807
|
```
|
|
611
808
|
|
|
@@ -627,7 +824,7 @@ export KADI_PROTOCOL=stdio
|
|
|
627
824
|
|
|
628
825
|
# Configure broker
|
|
629
826
|
export KADI_BROKER_URL=ws://localhost:8080
|
|
630
|
-
export
|
|
827
|
+
export KADI_ABILITY_NAME=my-ability
|
|
631
828
|
export KADI_AGENT_SCOPE=project-123
|
|
632
829
|
```
|
|
633
830
|
|
|
@@ -638,19 +835,19 @@ When abilities are spawned as child processes (stdio and broker protocols), the
|
|
|
638
835
|
```javascript
|
|
639
836
|
// In your ability code, you can access these variables:
|
|
640
837
|
const protocol = process.env.KADI_PROTOCOL; // 'stdio' or 'broker'
|
|
641
|
-
const brokerUrl = process.env.KADI_BROKER_URL;
|
|
642
|
-
const
|
|
643
|
-
const agentScope = process.env.KADI_AGENT_SCOPE;
|
|
838
|
+
const brokerUrl = process.env.KADI_BROKER_URL;
|
|
839
|
+
const abilityName = process.env.KADI_ABILITY_NAME;
|
|
840
|
+
const agentScope = process.env.KADI_AGENT_SCOPE;
|
|
644
841
|
|
|
645
842
|
// Use them to configure your ability behavior
|
|
646
|
-
const ability = new
|
|
843
|
+
const ability = new KadiClient({
|
|
647
844
|
name: 'my-ability',
|
|
648
|
-
|
|
845
|
+
network: process.env.KADI_AGENT_SCOPE || 'global'
|
|
649
846
|
// Ability automatically uses KADI_PROTOCOL to determine transport
|
|
650
847
|
});
|
|
651
848
|
```
|
|
652
849
|
|
|
653
|
-
### Project Structure
|
|
850
|
+
### Example Project Structure
|
|
654
851
|
|
|
655
852
|
```
|
|
656
853
|
my-project/
|
|
@@ -659,7 +856,7 @@ my-project/
|
|
|
659
856
|
│ └── echo-ability/
|
|
660
857
|
│ └── 1.0.0/
|
|
661
858
|
│ ├── agent.json # Ability configuration
|
|
662
|
-
│ ├──
|
|
859
|
+
│ ├── ability.js # Ability implementation
|
|
663
860
|
│ └── package.json
|
|
664
861
|
├── modules/ # Source modules
|
|
665
862
|
│ └── echo-ability/ # Development version
|
|
@@ -670,100 +867,159 @@ my-project/
|
|
|
670
867
|
|
|
671
868
|
### Event System
|
|
672
869
|
|
|
673
|
-
|
|
870
|
+
KadiClient provides a comprehensive event system that works across all protocols:
|
|
674
871
|
|
|
675
|
-
#### 1. Lifecycle Events
|
|
872
|
+
#### 1. Lifecycle Events
|
|
676
873
|
|
|
677
|
-
|
|
874
|
+
Monitor the ability lifecycle:
|
|
678
875
|
|
|
679
876
|
```javascript
|
|
680
|
-
const
|
|
877
|
+
const client = new KadiClient({ name: 'my-ability' });
|
|
681
878
|
|
|
682
|
-
//
|
|
683
|
-
|
|
684
|
-
console.log(`
|
|
879
|
+
// Connection events
|
|
880
|
+
client.on('connected', ({ broker }) => {
|
|
881
|
+
console.log(`Connected to broker: ${broker}`);
|
|
685
882
|
});
|
|
686
883
|
|
|
687
|
-
|
|
688
|
-
console.log(
|
|
884
|
+
client.on('disconnected', () => {
|
|
885
|
+
console.log('Disconnected from broker');
|
|
689
886
|
});
|
|
690
887
|
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
888
|
+
// Tool invocation events
|
|
889
|
+
client.on('tool:invoked', ({ toolName, params }) => {
|
|
890
|
+
console.log(`Tool ${toolName} invoked with:`, params);
|
|
694
891
|
});
|
|
695
892
|
|
|
696
|
-
|
|
893
|
+
client.on('tool:completed', ({ toolName, result }) => {
|
|
894
|
+
console.log(`Tool ${toolName} completed:`, result);
|
|
895
|
+
});
|
|
896
|
+
|
|
897
|
+
client.on('error', (error) => {
|
|
697
898
|
console.error('Ability error:', error);
|
|
698
899
|
});
|
|
699
900
|
```
|
|
700
901
|
|
|
701
|
-
#### 2. Custom Events (
|
|
902
|
+
#### 2. Custom Events (All Protocols)
|
|
702
903
|
|
|
703
|
-
|
|
904
|
+
Publish and subscribe to custom events across all transport protocols:
|
|
704
905
|
|
|
705
906
|
```javascript
|
|
706
|
-
//
|
|
707
|
-
const
|
|
907
|
+
// Publishing events
|
|
908
|
+
const publisher = new KadiClient({
|
|
909
|
+
name: 'event-publisher',
|
|
910
|
+
role: 'ability',
|
|
911
|
+
protocol: 'broker' // Works with all protocols
|
|
912
|
+
});
|
|
708
913
|
|
|
709
|
-
|
|
914
|
+
publisher.registerTool('process', async ({ data }) => {
|
|
710
915
|
// Publish events during processing
|
|
711
|
-
|
|
712
|
-
timestamp:
|
|
916
|
+
await publisher.publishEvent('process.started', {
|
|
917
|
+
timestamp: Date.now(),
|
|
918
|
+
data
|
|
713
919
|
});
|
|
714
920
|
|
|
715
|
-
//
|
|
921
|
+
// Do work...
|
|
922
|
+
const result = await processData(data);
|
|
716
923
|
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
924
|
+
await publisher.publishEvent('process.completed', {
|
|
925
|
+
timestamp: Date.now(),
|
|
926
|
+
result
|
|
720
927
|
});
|
|
721
928
|
|
|
722
|
-
return
|
|
929
|
+
return result;
|
|
723
930
|
});
|
|
724
931
|
|
|
725
|
-
//
|
|
726
|
-
const
|
|
932
|
+
// Subscribing to events
|
|
933
|
+
const subscriber = new KadiClient({
|
|
934
|
+
name: 'event-subscriber',
|
|
935
|
+
role: 'agent',
|
|
936
|
+
protocol: 'broker'
|
|
937
|
+
});
|
|
727
938
|
|
|
728
|
-
// Subscribe
|
|
729
|
-
|
|
730
|
-
console.log(
|
|
939
|
+
// Subscribe with wildcards
|
|
940
|
+
subscriber.subscribeToEvent('process.*', (data) => {
|
|
941
|
+
console.log('Process event:', data);
|
|
731
942
|
});
|
|
732
943
|
|
|
733
|
-
|
|
734
|
-
|
|
944
|
+
// One-time subscription
|
|
945
|
+
subscriber.onceEvent('process.completed', (data) => {
|
|
946
|
+
console.log('First completion:', data);
|
|
735
947
|
});
|
|
736
948
|
|
|
737
|
-
//
|
|
738
|
-
|
|
949
|
+
// Multiple pattern subscription
|
|
950
|
+
subscriber.subscribeToEvents(['process.*', 'system.*'], (pattern, data) => {
|
|
951
|
+
console.log(`Event from ${pattern}:`, data);
|
|
952
|
+
});
|
|
739
953
|
```
|
|
740
954
|
|
|
741
|
-
**
|
|
955
|
+
**Protocol-Specific Behavior:**
|
|
956
|
+
|
|
957
|
+
- **Native**: Direct EventEmitter, synchronous delivery
|
|
958
|
+
- **Stdio**: JSON-RPC notifications with LSP framing
|
|
959
|
+
- **Broker**: RabbitMQ pub/sub via WebSocket, persistent queues available
|
|
960
|
+
|
|
961
|
+
### Multi-Broker Configuration
|
|
962
|
+
|
|
963
|
+
KadiClient supports connecting to multiple brokers simultaneously for redundancy and load distribution:
|
|
964
|
+
|
|
965
|
+
```javascript
|
|
966
|
+
const client = new KadiClient({
|
|
967
|
+
name: 'multi-broker-ability',
|
|
968
|
+
role: 'ability',
|
|
969
|
+
protocol: 'broker',
|
|
970
|
+
brokers: {
|
|
971
|
+
'primary': 'ws://broker1.example.com:8080',
|
|
972
|
+
'secondary': 'ws://broker2.example.com:8080',
|
|
973
|
+
'backup': 'ws://broker3.example.com:8080'
|
|
974
|
+
},
|
|
975
|
+
defaultBroker: 'primary', // Used for sending if no specific broker targeted
|
|
976
|
+
networks: ['production']
|
|
977
|
+
});
|
|
978
|
+
|
|
979
|
+
// Connect to all configured brokers
|
|
980
|
+
await client.connectToBrokers();
|
|
742
981
|
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
982
|
+
// The client will now:
|
|
983
|
+
// 1. Maintain connections to all three brokers
|
|
984
|
+
// 2. Receive messages from any broker
|
|
985
|
+
// 3. Send messages to the default broker unless specified
|
|
986
|
+
// 4. Automatically handle broker failover
|
|
987
|
+
```
|
|
988
|
+
|
|
989
|
+
### Cross-Language Ability Support
|
|
747
990
|
|
|
748
|
-
|
|
991
|
+
KADI now fully supports abilities written in any language through the stdio protocol:
|
|
749
992
|
|
|
750
993
|
```javascript
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
994
|
+
// Go ability with agent.json:
|
|
995
|
+
{
|
|
996
|
+
"name": "hash-go",
|
|
997
|
+
"scripts": {
|
|
998
|
+
"setup": "go build -o bin/hash_ability",
|
|
999
|
+
"start": "./bin/hash_ability" // Binary executable
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
// Python ability:
|
|
1004
|
+
{
|
|
1005
|
+
"name": "ml-processor",
|
|
1006
|
+
"scripts": {
|
|
1007
|
+
"start": "python3 main.py"
|
|
765
1008
|
}
|
|
766
1009
|
}
|
|
1010
|
+
|
|
1011
|
+
// Rust ability:
|
|
1012
|
+
{
|
|
1013
|
+
"name": "crypto-rust",
|
|
1014
|
+
"scripts": {
|
|
1015
|
+
"setup": "cargo build --release",
|
|
1016
|
+
"start": "./target/release/crypto_ability"
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
// Load and use regardless of implementation language:
|
|
1021
|
+
const hashAbility = await loadAbility('hash-go', 'stdio');
|
|
1022
|
+
const result = await hashAbility.sha256({ data: 'hello' });
|
|
767
1023
|
```
|
|
768
1024
|
|
|
769
1025
|
## 🔧 Development Workflow
|
|
@@ -772,7 +1028,7 @@ try {
|
|
|
772
1028
|
|
|
773
1029
|
When developing new features or testing changes to `@kadi.build/core` before publishing to NPM:
|
|
774
1030
|
|
|
775
|
-
0. **
|
|
1031
|
+
0. **Clone the repository**
|
|
776
1032
|
|
|
777
1033
|
```bash
|
|
778
1034
|
git clone https://gitlab.com/humin-game-lab/kadi/kadi-core.git
|
|
@@ -786,14 +1042,14 @@ npm pack
|
|
|
786
1042
|
# This creates: kadi.build-core-X.Y.Z.tgz
|
|
787
1043
|
```
|
|
788
1044
|
|
|
789
|
-
|
|
1045
|
+
2. **Install in your project**:
|
|
790
1046
|
|
|
791
1047
|
```bash
|
|
792
1048
|
cd /path/to/your-project
|
|
793
1049
|
npm install /path/to/kadi-core/kadi.build-core-X.Y.Z.tgz
|
|
794
1050
|
```
|
|
795
1051
|
|
|
796
|
-
|
|
1052
|
+
3. **For ability development**, update the ability's preflight script:
|
|
797
1053
|
|
|
798
1054
|
```json
|
|
799
1055
|
{
|
|
@@ -807,402 +1063,295 @@ npm install /path/to/kadi-core/kadi.build-core-X.Y.Z.tgz
|
|
|
807
1063
|
|
|
808
1064
|
### Core Classes
|
|
809
1065
|
|
|
810
|
-
#### `
|
|
1066
|
+
#### `KadiClient`
|
|
811
1067
|
|
|
812
|
-
The
|
|
1068
|
+
The unified class for all KADI operations - serving tools, calling remote abilities, and managing events.
|
|
813
1069
|
|
|
814
1070
|
```javascript
|
|
815
|
-
import {
|
|
1071
|
+
import { KadiClient } from '@kadi.build/core';
|
|
816
1072
|
|
|
817
|
-
const
|
|
1073
|
+
const client = new KadiClient({
|
|
818
1074
|
name: 'my-ability',
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
1075
|
+
role: 'ability', // 'agent' or 'ability'
|
|
1076
|
+
protocol: 'broker', // 'native', 'stdio', or 'broker'
|
|
1077
|
+
brokers: {
|
|
1078
|
+
'dev': 'ws://localhost:8080',
|
|
1079
|
+
'prod': 'ws://prod.example.com:8080'
|
|
1080
|
+
},
|
|
1081
|
+
defaultBroker: 'dev',
|
|
1082
|
+
network: 'global', // Primary network
|
|
1083
|
+
networks: ['global', 'custom-network'] // All networks
|
|
823
1084
|
});
|
|
824
1085
|
```
|
|
825
1086
|
|
|
826
|
-
**
|
|
827
|
-
|
|
828
|
-
- `ability.method(name, handler, schema?)` - Register a method
|
|
829
|
-
- `ability.serve()` - Start serving requests
|
|
830
|
-
- `ability.publishEvent(eventName, data)` - Publish an event
|
|
831
|
-
- `ability.on(event, handler)` - Listen to lifecycle events
|
|
832
|
-
|
|
833
|
-
#### `loadAbility`
|
|
834
|
-
|
|
835
|
-
Load an ability by name and protocol.
|
|
836
|
-
|
|
837
|
-
```javascript
|
|
838
|
-
import { loadAbility } from '@kadi.build/core';
|
|
839
|
-
|
|
840
|
-
const ability = await loadAbility('ability-name', 'protocol');
|
|
841
|
-
```
|
|
842
|
-
|
|
843
|
-
**Returns:** A proxy object with:
|
|
1087
|
+
**Configuration Options:**
|
|
844
1088
|
|
|
845
|
-
-
|
|
846
|
-
- `
|
|
847
|
-
- `
|
|
848
|
-
- `
|
|
1089
|
+
- `name` - Ability/agent name
|
|
1090
|
+
- `role` - Operating role: 'agent' (consumer) or 'ability' (provider)
|
|
1091
|
+
- `protocol` - Transport protocol to use
|
|
1092
|
+
- `brokers` - Named broker configurations (object mapping names to URLs)
|
|
1093
|
+
- `defaultBroker` - Default broker name for sending messages
|
|
1094
|
+
- `network` - Primary network segment for message routing
|
|
1095
|
+
- `networks` - All network namespaces for tool discovery
|
|
1096
|
+
- `heartbeatIntervalSec` - Custom heartbeat interval (default: from broker)
|
|
849
1097
|
|
|
850
|
-
|
|
1098
|
+
**Tool Registration Methods:**
|
|
851
1099
|
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
runExecCommand
|
|
859
|
-
} from '@kadi.build/core';
|
|
860
|
-
```
|
|
1100
|
+
- `registerTool(name, handler, schema?)` - Register a single tool
|
|
1101
|
+
- `getTools()` - Get list of registered tool names
|
|
1102
|
+
- `getToolNames()` - Get filtered list of tool names
|
|
1103
|
+
- `getToolHandler(name)` - Get a specific tool's handler
|
|
1104
|
+
- `getToolSchema(name)` - Get a specific tool's schema
|
|
1105
|
+
- `hasTool(name)` - Check if a tool is registered
|
|
861
1106
|
|
|
862
|
-
|
|
1107
|
+
**Remote Tool Invocation:**
|
|
863
1108
|
|
|
864
|
-
|
|
1109
|
+
- `callTool(targetAgent, toolName, params)` - Call a remote tool via broker
|
|
1110
|
+
- `discoverRemoteTools(targetAgent)` - List tools from a remote agent
|
|
1111
|
+
- `loadAbility(name, protocol?, options?)` - Load an ability (compatibility)
|
|
865
1112
|
|
|
866
|
-
|
|
1113
|
+
**Event System:**
|
|
867
1114
|
|
|
868
|
-
|
|
1115
|
+
- `publishEvent(eventName, data)` - Publish an event
|
|
1116
|
+
- `subscribeToEvent(pattern, callback)` - Subscribe with wildcards
|
|
1117
|
+
- `subscribeToEvents(patterns[], callback)` - Multiple subscriptions
|
|
1118
|
+
- `unsubscribeFromEvent(pattern, callback)` - Remove subscription
|
|
1119
|
+
- `onceEvent(pattern, callback)` - One-time subscription
|
|
869
1120
|
|
|
870
|
-
|
|
871
|
-
examples/
|
|
872
|
-
├── example-abilities/ # Sample abilities in different languages
|
|
873
|
-
│ ├── echo-js/ # JavaScript ability using KadiAbility
|
|
874
|
-
│ │ ├── agent.json # Ability configuration
|
|
875
|
-
│ │ ├── service.js # Implementation
|
|
876
|
-
│ │ └── package.json
|
|
877
|
-
│ └── hash-go/ # Go ability demonstrating polyglot support
|
|
878
|
-
│ ├── agent.json
|
|
879
|
-
│ ├── go.mod
|
|
880
|
-
│ └── cmd/hash_ability/main.go
|
|
881
|
-
└── example-agent/ # Sample agent using the abilities
|
|
882
|
-
├── agent.json # Declares dependencies on abilities
|
|
883
|
-
├── index.js # Demonstrates all loading patterns
|
|
884
|
-
└── package.json
|
|
885
|
-
```
|
|
1121
|
+
**Connection Management:**
|
|
886
1122
|
|
|
887
|
-
|
|
1123
|
+
- `serve()` - Start serving (stdio/native modes)
|
|
1124
|
+
- `connectToBrokers()` - Connect to all configured brokers
|
|
1125
|
+
- `disconnect()` - Clean disconnect
|
|
1126
|
+
- `isConnected` - Check connection status
|
|
888
1127
|
|
|
889
|
-
|
|
890
|
-
kadi broker up
|
|
891
|
-
```
|
|
1128
|
+
**Properties:**
|
|
892
1129
|
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
```bash
|
|
902
|
-
# Inside 'example-agent' folder
|
|
903
|
-
kadi install
|
|
904
|
-
```
|
|
905
|
-
|
|
906
|
-
This installs the `echo-js` (JavaScript) and `hash-go` (Go) abilities defined in the agent.json.
|
|
907
|
-
|
|
908
|
-
#### Step 3: Run the agent
|
|
909
|
-
|
|
910
|
-
```bash
|
|
911
|
-
kadi run
|
|
912
|
-
```
|
|
913
|
-
|
|
914
|
-
#### Expected Output
|
|
915
|
-
|
|
916
|
-
You should see the agent testing various abilities and protocols:
|
|
917
|
-
|
|
918
|
-
```
|
|
919
|
-
=== WORKING ABILITIES ===
|
|
920
|
-
|
|
921
|
-
🔧 Loading echo-js ability using native (Javascript)...
|
|
922
|
-
Available methods: [ 'echo', 'say_message' ]
|
|
923
|
-
Echo-js echo result: {
|
|
924
|
-
echo: 'I am calling echo-js echo method from Javascript',
|
|
925
|
-
timestamp: '2025-08-15T00:20:23.276Z',
|
|
926
|
-
length: 48
|
|
927
|
-
}
|
|
928
|
-
|
|
929
|
-
🔧 Loading echo-js ability using stdio (Javascript)...
|
|
930
|
-
Available methods: [ 'echo', 'say_message' ]
|
|
931
|
-
Echo-js echo result: {
|
|
932
|
-
echo: 'I am calling echo-js echo method from Javascript',
|
|
933
|
-
timestamp: '2025-08-15T00:20:23.339Z',
|
|
934
|
-
length: 48
|
|
935
|
-
}
|
|
936
|
-
```
|
|
1130
|
+
- `agentId` - Unique agent identifier
|
|
1131
|
+
- `name` - Ability name
|
|
1132
|
+
- `role` - Current operating role
|
|
1133
|
+
- `protocol` - Active protocol
|
|
1134
|
+
- `brokers` - Configured broker map
|
|
1135
|
+
- `defaultBroker` - Default broker name
|
|
1136
|
+
- `currentBroker` - Currently active broker for sending
|
|
937
1137
|
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
```bash
|
|
941
|
-
# Stop the agent
|
|
942
|
-
Ctrl + C
|
|
943
|
-
|
|
944
|
-
# Clean up installed abilities and node_modules
|
|
945
|
-
kadi run clean
|
|
946
|
-
```
|
|
947
|
-
|
|
948
|
-
### What the Example Demonstrates
|
|
949
|
-
|
|
950
|
-
1. **Polyglot Abilities**: Go (`hash-go`) and JavaScript (`echo-js`) abilities working together
|
|
951
|
-
2. **Protocol Flexibility**: Same ability (`echo-js`) loaded via native, stdio, and broker
|
|
952
|
-
3. **Error Handling**: Graceful handling when calling non-existent methods
|
|
953
|
-
4. **Method Discovery**: Using `__list()` to discover available methods
|
|
954
|
-
5. **Schema Support**: Both inline and export-based schema definitions
|
|
955
|
-
6. **Event System**: Publishing and subscribing to events across protocols
|
|
956
|
-
7. **Event Timing**: Demonstrates the importance of subscribing to events before calling methods
|
|
957
|
-
|
|
958
|
-
### Exploring the Code
|
|
959
|
-
|
|
960
|
-
**echo-js ability** (`examples/example-abilities/echo-js/service.js`):
|
|
961
|
-
|
|
962
|
-
- Shows method registration with and without schemas
|
|
963
|
-
- Demonstrates scope configuration for broker visibility
|
|
964
|
-
- Uses environment variables for protocol adaptation
|
|
965
|
-
- Includes event publishing examples using `publishEvent()`
|
|
966
|
-
- Demonstrates how to emit events from method handlers
|
|
967
|
-
|
|
968
|
-
**hash-go ability** (`examples/example-abilities/hash-go/`):
|
|
969
|
-
|
|
970
|
-
- Demonstrates Go SDK integration
|
|
971
|
-
- Shows stdio protocol with a compiled language
|
|
972
|
-
- Implements SHA256 hashing as a service
|
|
973
|
-
|
|
974
|
-
**example-agent** (`examples/example-agent/index.js`):
|
|
975
|
-
|
|
976
|
-
- Shows all three loading patterns
|
|
977
|
-
- Demonstrates error handling
|
|
978
|
-
- Tests cross-language ability communication
|
|
979
|
-
- Includes event subscription examples using `ability.events.on()`
|
|
980
|
-
- Shows how to subscribe to events before calling methods
|
|
981
|
-
- Demonstrates event handling across different protocols (native/stdio)
|
|
982
|
-
|
|
983
|
-
## 💡 Additional Examples
|
|
984
|
-
|
|
985
|
-
### Complete Echo Service Example
|
|
986
|
-
|
|
987
|
-
**service.js**:
|
|
1138
|
+
**Complete Usage Example:**
|
|
988
1139
|
|
|
989
1140
|
```javascript
|
|
990
|
-
|
|
991
|
-
import { KadiAbility } from '@kadi.build/core';
|
|
1141
|
+
import { KadiClient } from '@kadi.build/core';
|
|
992
1142
|
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
echoAbility.publishEvent('echo:test-event', { from: 'message echo 2' });
|
|
1005
|
-
|
|
1006
|
-
return {
|
|
1007
|
-
echo: message,
|
|
1008
|
-
timestamp: new Date().toISOString(),
|
|
1009
|
-
length: message?.length || 0
|
|
1010
|
-
};
|
|
1143
|
+
// Create an ability that can serve tools
|
|
1144
|
+
const ability = new KadiClient({
|
|
1145
|
+
name: 'math-ability',
|
|
1146
|
+
role: 'ability',
|
|
1147
|
+
protocol: 'broker',
|
|
1148
|
+
brokers: {
|
|
1149
|
+
'local': 'ws://localhost:8080',
|
|
1150
|
+
'cloud': 'wss://api.example.com'
|
|
1151
|
+
},
|
|
1152
|
+
defaultBroker: 'local',
|
|
1153
|
+
networks: ['global']
|
|
1011
1154
|
});
|
|
1012
1155
|
|
|
1013
|
-
//
|
|
1014
|
-
|
|
1015
|
-
'
|
|
1016
|
-
async ({
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
return {
|
|
1156
|
+
// Register tools with schemas
|
|
1157
|
+
ability.registerTool(
|
|
1158
|
+
'add',
|
|
1159
|
+
async ({ a, b }) => {
|
|
1160
|
+
// Publish event before processing
|
|
1161
|
+
await ability.publishEvent('math.operation', {
|
|
1162
|
+
operation: 'add',
|
|
1163
|
+
inputs: { a, b }
|
|
1164
|
+
});
|
|
1165
|
+
|
|
1166
|
+
const result = a + b;
|
|
1167
|
+
|
|
1168
|
+
// Publish completion event
|
|
1169
|
+
await ability.publishEvent('math.completed', {
|
|
1170
|
+
operation: 'add',
|
|
1171
|
+
result
|
|
1172
|
+
});
|
|
1173
|
+
|
|
1174
|
+
return { result };
|
|
1032
1175
|
},
|
|
1033
1176
|
{
|
|
1034
|
-
description: '
|
|
1177
|
+
description: 'Add two numbers',
|
|
1035
1178
|
inputSchema: {
|
|
1036
1179
|
type: 'object',
|
|
1037
1180
|
properties: {
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
type: 'array',
|
|
1041
|
-
items: { enum: ['upper', 'lower', 'reverse'] }
|
|
1042
|
-
}
|
|
1181
|
+
a: { type: 'number', description: 'First number' },
|
|
1182
|
+
b: { type: 'number', description: 'Second number' }
|
|
1043
1183
|
},
|
|
1044
|
-
required: ['
|
|
1184
|
+
required: ['a', 'b']
|
|
1185
|
+
},
|
|
1186
|
+
outputSchema: {
|
|
1187
|
+
type: 'object',
|
|
1188
|
+
properties: {
|
|
1189
|
+
result: { type: 'number', description: 'Sum of a and b' }
|
|
1190
|
+
}
|
|
1045
1191
|
}
|
|
1046
1192
|
}
|
|
1047
1193
|
);
|
|
1048
1194
|
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1195
|
+
// Subscribe to events from other abilities
|
|
1196
|
+
ability.subscribeToEvent('system.*', (data) => {
|
|
1197
|
+
console.log('System event:', data);
|
|
1198
|
+
});
|
|
1053
1199
|
|
|
1054
|
-
|
|
1055
|
-
|
|
1200
|
+
// Connect to brokers
|
|
1201
|
+
await ability.connectToBrokers();
|
|
1056
1202
|
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
try {
|
|
1063
|
-
const echo = await loadAbility('echo-service', protocol);
|
|
1064
|
-
|
|
1065
|
-
// Subscribe to events (works for native and stdio protocols)
|
|
1066
|
-
echo.events.on('echo:test-event', (data) => {
|
|
1067
|
-
console.log(`Echo event received: ${data.from}`);
|
|
1068
|
-
});
|
|
1069
|
-
|
|
1070
|
-
// Simple echo
|
|
1071
|
-
const result1 = await echo.echo({
|
|
1072
|
-
message: `Hello from ${protocol}!`
|
|
1073
|
-
});
|
|
1074
|
-
console.log('Echo:', result1);
|
|
1075
|
-
|
|
1076
|
-
// Transform
|
|
1077
|
-
const result2 = await echo.transform({
|
|
1078
|
-
text: 'Hello World',
|
|
1079
|
-
operations: ['lower', 'reverse']
|
|
1080
|
-
});
|
|
1081
|
-
console.log('Transform:', result2);
|
|
1082
|
-
} catch (error) {
|
|
1083
|
-
console.error(`${protocol} failed:`, error.message);
|
|
1084
|
-
}
|
|
1085
|
-
}
|
|
1086
|
-
}
|
|
1203
|
+
// Call remote tools
|
|
1204
|
+
const result = await ability.callTool('translator-ability', 'translate', {
|
|
1205
|
+
text: 'Hello',
|
|
1206
|
+
to: 'es'
|
|
1207
|
+
});
|
|
1087
1208
|
|
|
1088
|
-
|
|
1209
|
+
// Graceful shutdown
|
|
1210
|
+
process.on('SIGTERM', async () => {
|
|
1211
|
+
await ability.disconnect();
|
|
1212
|
+
process.exit(0);
|
|
1213
|
+
});
|
|
1089
1214
|
```
|
|
1090
1215
|
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
Here's the exact event pattern used in the example code:
|
|
1216
|
+
#### `loadAbility`
|
|
1094
1217
|
|
|
1095
|
-
|
|
1218
|
+
Load an ability by name and protocol.
|
|
1096
1219
|
|
|
1097
1220
|
```javascript
|
|
1098
|
-
|
|
1099
|
-
name: 'echo-js',
|
|
1100
|
-
version: '0.0.1',
|
|
1101
|
-
scope: process.env.KADI_AGENT_SCOPE
|
|
1102
|
-
});
|
|
1103
|
-
|
|
1104
|
-
async function echo(message) {
|
|
1105
|
-
const timestamp = new Date().toISOString();
|
|
1106
|
-
const messageText = message?.message ?? message ?? '';
|
|
1107
|
-
const length = messageText.length;
|
|
1221
|
+
import { loadAbility } from '@kadi.build/core';
|
|
1108
1222
|
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1223
|
+
const ability = await loadAbility('ability-name', 'protocol', {
|
|
1224
|
+
brokerUrl: 'ws://localhost:8080', // For broker protocol
|
|
1225
|
+
brokerName: 'dev', // Named broker to use
|
|
1226
|
+
networks: ['global'], // Network segments
|
|
1227
|
+
existingClient: client // Reuse existing KadiClient for broker
|
|
1228
|
+
});
|
|
1229
|
+
```
|
|
1112
1230
|
|
|
1113
|
-
|
|
1114
|
-
}
|
|
1231
|
+
**Returns:** A proxy object with:
|
|
1115
1232
|
|
|
1116
|
-
|
|
1117
|
-
|
|
1233
|
+
- Direct method calls (e.g., `ability.echo({ message: 'hello' })`)
|
|
1234
|
+
- `ability.events` - EventEmitter for subscribing to events (all protocols)
|
|
1235
|
+
- `ability.__call(method, params)` - Direct RPC call
|
|
1236
|
+
- `ability.__disconnect()` - Clean up resources
|
|
1118
1237
|
|
|
1119
|
-
|
|
1238
|
+
### Utility Functions
|
|
1120
1239
|
|
|
1121
1240
|
```javascript
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
});
|
|
1241
|
+
import {
|
|
1242
|
+
createLogger,
|
|
1243
|
+
getProjectJSON,
|
|
1244
|
+
getAbilityJSON,
|
|
1245
|
+
getAgentJSON,
|
|
1246
|
+
getBrokerUrl,
|
|
1247
|
+
runExecCommand
|
|
1248
|
+
} from '@kadi.build/core';
|
|
1131
1249
|
```
|
|
1132
1250
|
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
- Events work for `native` and `stdio` protocols only
|
|
1136
|
-
- Always subscribe to events BEFORE calling methods that emit them
|
|
1137
|
-
- Events are emitted immediately when `publishEvent()` is called
|
|
1138
|
-
- Use `ability.events.on()` to subscribe to custom events
|
|
1139
|
-
- Use `ability.on()` to listen to lifecycle events
|
|
1251
|
+
### Debug Mode
|
|
1140
1252
|
|
|
1141
|
-
|
|
1253
|
+
Enable detailed logging:
|
|
1142
1254
|
|
|
1143
|
-
|
|
1255
|
+
```bash
|
|
1256
|
+
DEBUG=kadi:* node index.js
|
|
1257
|
+
```
|
|
1144
1258
|
|
|
1145
|
-
|
|
1259
|
+
Check ability logs:
|
|
1146
1260
|
|
|
1261
|
+
```bash
|
|
1262
|
+
tail -f abilities/my-ability/1.0.0/my-ability.log
|
|
1147
1263
|
```
|
|
1148
|
-
|
|
1149
|
-
```
|
|
1264
|
+
## 💡 Additional Examples
|
|
1150
1265
|
|
|
1151
|
-
|
|
1266
|
+
### Error Handling Pattern
|
|
1152
1267
|
|
|
1153
|
-
|
|
1268
|
+
```javascript
|
|
1269
|
+
const client = new KadiClient({
|
|
1270
|
+
name: 'robust-ability',
|
|
1271
|
+
role: 'ability'
|
|
1272
|
+
});
|
|
1154
1273
|
|
|
1155
|
-
|
|
1156
|
-
|
|
1274
|
+
client.registerTool('riskyOperation', async (params) => {
|
|
1275
|
+
try {
|
|
1276
|
+
const result = await performOperation(params);
|
|
1277
|
+
await client.publishEvent('operation.success', { result });
|
|
1278
|
+
return { success: true, result };
|
|
1279
|
+
} catch (error) {
|
|
1280
|
+
await client.publishEvent('operation.error', {
|
|
1281
|
+
error: error.message,
|
|
1282
|
+
params
|
|
1283
|
+
});
|
|
1284
|
+
// Return error in structured format
|
|
1285
|
+
return {
|
|
1286
|
+
success: false,
|
|
1287
|
+
error: error.message
|
|
1288
|
+
};
|
|
1289
|
+
}
|
|
1290
|
+
});
|
|
1157
1291
|
```
|
|
1158
1292
|
|
|
1159
|
-
|
|
1293
|
+
### Health Check Pattern
|
|
1160
1294
|
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1295
|
+
```javascript
|
|
1296
|
+
// Register a standard health check tool
|
|
1297
|
+
client.registerTool('health', async () => {
|
|
1298
|
+
const checks = {
|
|
1299
|
+
memory: process.memoryUsage(),
|
|
1300
|
+
uptime: process.uptime(),
|
|
1301
|
+
brokers: client.isConnected ? 'connected' : 'disconnected',
|
|
1302
|
+
timestamp: Date.now()
|
|
1303
|
+
};
|
|
1304
|
+
|
|
1305
|
+
return {
|
|
1306
|
+
status: 'healthy',
|
|
1307
|
+
checks
|
|
1308
|
+
};
|
|
1309
|
+
});
|
|
1165
1310
|
```
|
|
1166
1311
|
|
|
1167
|
-
|
|
1312
|
+
## 🐛 Troubleshooting
|
|
1168
1313
|
|
|
1169
|
-
|
|
1314
|
+
### Common Issues and Solutions
|
|
1170
1315
|
|
|
1171
|
-
|
|
1172
|
-
Error: Unsupported ability interface protocol: custom
|
|
1173
|
-
```
|
|
1316
|
+
#### Broker Connection Failures
|
|
1174
1317
|
|
|
1175
|
-
**
|
|
1318
|
+
**Problem**: "Failed to connect to any brokers"
|
|
1176
1319
|
|
|
1177
|
-
**
|
|
1320
|
+
**Solutions**:
|
|
1321
|
+
- Verify broker URLs are correct
|
|
1322
|
+
- Check network connectivity
|
|
1323
|
+
- Ensure broker is running
|
|
1324
|
+
- Try connecting with lower security (ws:// before wss://)
|
|
1178
1325
|
|
|
1179
|
-
|
|
1180
|
-
TypeError: Cannot read property 'on' of undefined
|
|
1181
|
-
```
|
|
1182
|
-
|
|
1183
|
-
**Solution**: Make sure you're accessing `ability.events.on()` not `ability.on()` for custom events
|
|
1326
|
+
#### Events Not Arriving
|
|
1184
1327
|
|
|
1185
|
-
**
|
|
1328
|
+
**Problem**: Subscribed to events but callbacks not triggered
|
|
1186
1329
|
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1330
|
+
**Solutions**:
|
|
1331
|
+
- Subscribe BEFORE triggering events
|
|
1332
|
+
- Check pattern matching (use exact match first)
|
|
1333
|
+
- Verify publisher and subscriber are on same network
|
|
1334
|
+
- Enable debug logging to trace event flow
|
|
1190
1335
|
|
|
1191
|
-
|
|
1336
|
+
#### Cross-Language Abilities Not Loading
|
|
1192
1337
|
|
|
1193
|
-
|
|
1338
|
+
**Problem**: "Cannot find module 'ability.js'" when loading Go/Python abilities
|
|
1194
1339
|
|
|
1195
|
-
|
|
1340
|
+
**Solutions**:
|
|
1341
|
+
- Ensure `scripts.start` field exists in agent.json
|
|
1342
|
+
- Verify the binary/script is executable
|
|
1343
|
+
- Check that setup script has been run
|
|
1344
|
+
- Use stdio protocol, not native
|
|
1196
1345
|
|
|
1197
|
-
|
|
1198
|
-
DEBUG=kadi:* node index.js
|
|
1199
|
-
```
|
|
1346
|
+
#### Memory Leaks
|
|
1200
1347
|
|
|
1201
|
-
|
|
1348
|
+
**Problem**: Memory usage grows over time
|
|
1202
1349
|
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1350
|
+
**Solutions**:
|
|
1351
|
+
- Always call unsubscribe functions
|
|
1352
|
+
- Use `onceEvent` for single-use subscriptions
|
|
1353
|
+
- Disconnect abilities when done
|
|
1354
|
+
- Clear event listeners on cleanup
|
|
1206
1355
|
|
|
1207
1356
|
## 🤝 Contributing
|
|
1208
1357
|
|
|
@@ -1216,7 +1365,7 @@ We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) f
|
|
|
1216
1365
|
|
|
1217
1366
|
## 📄 License
|
|
1218
1367
|
|
|
1219
|
-
MIT License - see [LICENSE](LICENSE) file for details
|
|
1368
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
1220
1369
|
|
|
1221
1370
|
## 🔗 Related Projects
|
|
1222
1371
|
|
|
@@ -1225,11 +1374,10 @@ MIT License - see [LICENSE](LICENSE) file for details
|
|
|
1225
1374
|
|
|
1226
1375
|
## 📚 Resources
|
|
1227
1376
|
|
|
1228
|
-
- [
|
|
1229
|
-
- [API
|
|
1230
|
-
- [
|
|
1231
|
-
- [Architecture Deep Dive](https://docs.kadi.build/architecture)
|
|
1377
|
+
- [Event System Deep Dive](docs/event-system.md) - Complete guide to the event architecture
|
|
1378
|
+
- [API Documentation](https://docs.kadi.build) - Full API reference
|
|
1379
|
+
- [Examples Repository](https://github.com/kadi-examples) - More examples and patterns
|
|
1232
1380
|
|
|
1233
1381
|
---
|
|
1234
1382
|
|
|
1235
|
-
Built with ❤️ by the KADI team
|
|
1383
|
+
Built with ❤️ by the KADI team
|