@kadi.build/core 0.0.1-alpha.0 → 0.0.1-alpha.10
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 +1387 -214
- package/dist/abilities/AbilityCache.d.ts +242 -0
- package/dist/abilities/AbilityCache.d.ts.map +1 -0
- package/dist/abilities/AbilityCache.js +285 -0
- package/dist/abilities/AbilityCache.js.map +1 -0
- package/dist/abilities/AbilityContext.d.ts +215 -0
- package/dist/abilities/AbilityContext.d.ts.map +1 -0
- package/dist/abilities/AbilityContext.js +36 -0
- package/dist/abilities/AbilityContext.js.map +1 -0
- package/dist/abilities/AbilityLoader.d.ts +177 -0
- package/dist/abilities/AbilityLoader.d.ts.map +1 -0
- package/dist/abilities/AbilityLoader.js +277 -0
- package/dist/abilities/AbilityLoader.js.map +1 -0
- package/dist/abilities/AbilityProxy.d.ts +463 -0
- package/dist/abilities/AbilityProxy.d.ts.map +1 -0
- package/dist/abilities/AbilityProxy.js +511 -0
- package/dist/abilities/AbilityProxy.js.map +1 -0
- package/dist/abilities/AbilityValidator.d.ts +172 -0
- package/dist/abilities/AbilityValidator.d.ts.map +1 -0
- package/dist/abilities/AbilityValidator.js +253 -0
- package/dist/abilities/AbilityValidator.js.map +1 -0
- package/dist/abilities/index.d.ts +26 -0
- package/dist/abilities/index.d.ts.map +1 -0
- package/dist/abilities/index.js +23 -0
- package/dist/abilities/index.js.map +1 -0
- package/dist/abilities/types.d.ts +156 -0
- package/dist/abilities/types.d.ts.map +1 -0
- package/dist/abilities/types.js +10 -0
- package/dist/abilities/types.js.map +1 -0
- package/dist/api/index.d.ts +92 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +124 -0
- package/dist/api/index.js.map +1 -0
- package/dist/broker/BrokerConnection.d.ts +253 -0
- package/dist/broker/BrokerConnection.d.ts.map +1 -0
- package/dist/broker/BrokerConnection.js +434 -0
- package/dist/broker/BrokerConnection.js.map +1 -0
- package/dist/broker/BrokerConnectionManager.d.ts +216 -0
- package/dist/broker/BrokerConnectionManager.d.ts.map +1 -0
- package/dist/broker/BrokerConnectionManager.js +305 -0
- package/dist/broker/BrokerConnectionManager.js.map +1 -0
- package/dist/broker/BrokerProtocol.d.ts +280 -0
- package/dist/broker/BrokerProtocol.d.ts.map +1 -0
- package/dist/broker/BrokerProtocol.js +466 -0
- package/dist/broker/BrokerProtocol.js.map +1 -0
- package/dist/broker/index.d.ts +9 -0
- package/dist/broker/index.d.ts.map +1 -0
- package/dist/broker/index.js +9 -0
- package/dist/broker/index.js.map +1 -0
- package/dist/client/KadiClient.d.ts +270 -0
- package/dist/client/KadiClient.d.ts.map +1 -0
- package/dist/client/KadiClient.js +492 -0
- package/dist/client/KadiClient.js.map +1 -0
- package/dist/client/index.d.ts +7 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +7 -0
- package/dist/client/index.js.map +1 -0
- package/dist/config/ConfigLoader.d.ts +138 -0
- package/dist/config/ConfigLoader.d.ts.map +1 -0
- package/dist/config/ConfigLoader.js +226 -0
- package/dist/config/ConfigLoader.js.map +1 -0
- package/dist/config/ConfigResolver.d.ts +135 -0
- package/dist/config/ConfigResolver.d.ts.map +1 -0
- package/dist/config/ConfigResolver.js +282 -0
- package/dist/config/ConfigResolver.js.map +1 -0
- package/dist/config/index.d.ts +8 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +8 -0
- package/dist/config/index.js.map +1 -0
- package/dist/errors/index.d.ts +9 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +8 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/events/EventHub.d.ts +172 -0
- package/dist/events/EventHub.d.ts.map +1 -0
- package/dist/events/EventHub.js +333 -0
- package/dist/events/EventHub.js.map +1 -0
- package/dist/events/index.d.ts +7 -0
- package/dist/events/index.d.ts.map +1 -0
- package/dist/events/index.js +7 -0
- package/dist/events/index.js.map +1 -0
- package/dist/index.d.ts +49 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +63 -0
- package/dist/index.js.map +1 -0
- package/dist/messages/index.d.ts +33 -0
- package/dist/messages/index.d.ts.map +1 -0
- package/dist/messages/index.js +33 -0
- package/dist/messages/index.js.map +1 -0
- package/dist/schemas/index.d.ts +19 -0
- package/dist/schemas/index.d.ts.map +1 -0
- package/dist/schemas/index.js +25 -0
- package/dist/schemas/index.js.map +1 -0
- package/dist/schemas/kadi-extensions.d.ts +231 -0
- package/dist/schemas/kadi-extensions.d.ts.map +1 -0
- package/dist/schemas/kadi-extensions.js +14 -0
- package/dist/schemas/kadi-extensions.js.map +1 -0
- package/dist/schemas/mcp/schema.d.ts +1399 -0
- package/dist/schemas/mcp/schema.d.ts.map +1 -0
- package/dist/schemas/mcp/schema.js +53 -0
- package/dist/schemas/mcp/schema.js.map +1 -0
- package/dist/schemas/mcp/version.d.ts +37 -0
- package/dist/schemas/mcp/version.d.ts.map +1 -0
- package/dist/schemas/mcp/version.js +39 -0
- package/dist/schemas/mcp/version.js.map +1 -0
- package/dist/schemas/schema-builders.d.ts +178 -0
- package/dist/schemas/schema-builders.d.ts.map +1 -0
- package/dist/schemas/schema-builders.js +258 -0
- package/dist/schemas/schema-builders.js.map +1 -0
- package/dist/tools/ToolRegistry.d.ts +256 -0
- package/dist/tools/ToolRegistry.d.ts.map +1 -0
- package/dist/tools/ToolRegistry.js +340 -0
- package/dist/tools/ToolRegistry.js.map +1 -0
- package/dist/tools/index.d.ts +7 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +7 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/transports/BrokerTransport.d.ts +151 -0
- package/dist/transports/BrokerTransport.d.ts.map +1 -0
- package/dist/transports/BrokerTransport.js +261 -0
- package/dist/transports/BrokerTransport.js.map +1 -0
- package/dist/transports/NativeTransport.d.ts +149 -0
- package/dist/transports/NativeTransport.d.ts.map +1 -0
- package/dist/transports/NativeTransport.js +302 -0
- package/dist/transports/NativeTransport.js.map +1 -0
- package/dist/transports/StdioTransport.d.ts +172 -0
- package/dist/transports/StdioTransport.d.ts.map +1 -0
- package/dist/transports/StdioTransport.js +410 -0
- package/dist/transports/StdioTransport.js.map +1 -0
- package/dist/transports/index.d.ts +10 -0
- package/dist/transports/index.d.ts.map +1 -0
- package/dist/transports/index.js +9 -0
- package/dist/transports/index.js.map +1 -0
- package/dist/types/broker.d.ts +301 -0
- package/dist/types/broker.d.ts.map +1 -0
- package/dist/types/broker.js +46 -0
- package/dist/types/broker.js.map +1 -0
- package/dist/types/config.d.ts +325 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +17 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/errors.d.ts +178 -0
- package/dist/types/errors.d.ts.map +1 -0
- package/dist/types/errors.js +165 -0
- package/dist/types/errors.js.map +1 -0
- package/dist/types/events.d.ts +210 -0
- package/dist/types/events.d.ts.map +1 -0
- package/dist/types/events.js +8 -0
- package/dist/types/events.js.map +1 -0
- package/dist/types/index.d.ts +32 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +21 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/protocol.d.ts +48 -0
- package/dist/types/protocol.d.ts.map +1 -0
- package/dist/types/protocol.js +11 -0
- package/dist/types/protocol.js.map +1 -0
- package/dist/types/tools.d.ts +67 -0
- package/dist/types/tools.d.ts.map +1 -0
- package/dist/types/tools.js +16 -0
- package/dist/types/tools.js.map +1 -0
- package/dist/types/transport.d.ts +250 -0
- package/dist/types/transport.d.ts.map +1 -0
- package/dist/types/transport.js +18 -0
- package/dist/types/transport.js.map +1 -0
- package/dist/validation/SchemaValidator.d.ts +208 -0
- package/dist/validation/SchemaValidator.d.ts.map +1 -0
- package/dist/validation/SchemaValidator.js +411 -0
- package/dist/validation/SchemaValidator.js.map +1 -0
- package/dist/validation/index.d.ts +11 -0
- package/dist/validation/index.d.ts.map +1 -0
- package/dist/validation/index.js +10 -0
- package/dist/validation/index.js.map +1 -0
- package/package.json +69 -5
- package/.prettierrc +0 -6
- package/agent.json +0 -18
- package/broker.js +0 -214
- package/index.js +0 -370
- package/ipc.js +0 -220
- package/ipcInterfaces/pythonAbilityIPC.py +0 -177
package/README.md
CHANGED
|
@@ -1,306 +1,1479 @@
|
|
|
1
1
|
# @kadi.build/core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> A comprehensive toolkit for building and managing KADI abilities with multiple transport protocols
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@kadi.build/core)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
## 🎯 Overview
|
|
9
|
+
|
|
10
|
+
`@kadi.build/core` is the foundational library for creating KADI abilities - modular, protocol-agnostic components that can communicate via multiple transport layers. Whether you're building local tools, distributed systems, or high-performance native modules, this toolkit provides a unified, developer-friendly API that abstracts away transport complexity.
|
|
11
|
+
|
|
12
|
+
## 📚 Table of Contents
|
|
13
|
+
|
|
14
|
+
- [🎯 Overview](#-overview)
|
|
15
|
+
- [📦 Installation](#-installation)
|
|
16
|
+
- [🚀 Quick Start](#-quick-start)
|
|
17
|
+
- [🔌 Transport Protocols](#-transport-protocols)
|
|
18
|
+
- [🛠️ Creating Abilities](#-creating-abilities)
|
|
19
|
+
- [🤖 Creating Agents](#-creating-agents)
|
|
20
|
+
- [🔄 Architecture Deep Dive](#-architecture-deep-dive)
|
|
21
|
+
- [📥 Loading Abilities](#-loading-abilities)
|
|
22
|
+
- [⚙️ Configuration](#-configuration)
|
|
23
|
+
- [🚀 Advanced Usage](#-advanced-usage)
|
|
24
|
+
- [🔧 Development Workflow](#-development-workflow)
|
|
25
|
+
- [📖 API Reference](#-api-reference)
|
|
26
|
+
- [🎮 Running the Examples](#-running-the-examples)
|
|
27
|
+
- [💡 Additional Examples](#-additional-examples)
|
|
28
|
+
- [🐛 Troubleshooting](#-troubleshooting)
|
|
29
|
+
- [🤝 Contributing](#-contributing)
|
|
30
|
+
- [📄 License](#-license)
|
|
31
|
+
- [🔗 Related Projects](#-related-projects)
|
|
32
|
+
- [📚 Resources](#-resources)
|
|
33
|
+
|
|
34
|
+
## 📦 Installation
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npm install @kadi.build/core
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
For global CLI tools:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npm install -g @kadi.build/cli
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## 🚀 Quick Start
|
|
47
|
+
|
|
48
|
+
The library provides a unified way to work with KADI through the **KadiClient** class, which can operate in multiple roles:
|
|
49
|
+
|
|
50
|
+
- **As an Ability**: Serving tools/methods that others can call
|
|
51
|
+
- **As an Agent**: Calling remote tools via broker protocol
|
|
52
|
+
|
|
53
|
+
### Creating Your First Ability
|
|
54
|
+
|
|
55
|
+
```javascript
|
|
56
|
+
#!/usr/bin/env node
|
|
57
|
+
import { KadiClient } from '@kadi.build/core';
|
|
58
|
+
|
|
59
|
+
// Create a ability instance
|
|
60
|
+
const mathAbility = new KadiClient({
|
|
61
|
+
name: 'math-ability',
|
|
62
|
+
role: 'ability', // 'agent', 'ability'
|
|
63
|
+
protocol: 'stdio' // 'native', 'stdio', or 'broker'
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Register a tool
|
|
67
|
+
mathAbility.registerTool('add', async ({ a, b }) => {
|
|
68
|
+
return { result: a + b };
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Start serving requests
|
|
72
|
+
mathAbility.serve().catch(console.error);
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Loading and Using Abilities
|
|
4
76
|
|
|
5
|
-
|
|
77
|
+
```javascript
|
|
78
|
+
import { loadAbility } from '@kadi.build/core';
|
|
79
|
+
|
|
80
|
+
async function main() {
|
|
81
|
+
// Load an ability (uses first interface defined in agent.json)
|
|
82
|
+
const math = await loadAbility('math-ability');
|
|
83
|
+
|
|
84
|
+
// Call methods like regular functions
|
|
85
|
+
const result = await math.add({ a: 5, b: 3 });
|
|
86
|
+
console.log(result); // { result: 8 }
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
main().catch(console.error);
|
|
90
|
+
```
|
|
6
91
|
|
|
7
|
-
|
|
92
|
+
### Creating a Broker-Connected Agent
|
|
8
93
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
+
);
|
|
14
127
|
|
|
15
|
-
|
|
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
|
+
```
|
|
16
154
|
|
|
17
|
-
|
|
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');
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**Environment Variables Passed to Child Process:**
|
|
18
168
|
|
|
19
169
|
```bash
|
|
20
|
-
|
|
170
|
+
KADI_PROTOCOL=stdio # Identifies the protocol being used
|
|
171
|
+
# Plus all parent process.env variables
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### 3. Broker Protocol (Distributed)
|
|
175
|
+
|
|
176
|
+
- **Use Case**: Distributed systems, containerized deployments
|
|
177
|
+
- **How it Works**: Abilities connect to a WebSocket broker for network communication
|
|
178
|
+
- **Performance**: Network overhead, but enables scaling and distribution
|
|
179
|
+
- **Best For**: Distributed abilities, cloud deployments, load-balanced systems
|
|
180
|
+
|
|
181
|
+
```javascript
|
|
182
|
+
// Use broker for distributed execution
|
|
183
|
+
const ability = await loadAbility('my-ability', 'broker');
|
|
21
184
|
```
|
|
22
185
|
|
|
23
|
-
|
|
186
|
+
**Environment Variables Passed to Child Process:**
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
KADI_PROTOCOL=broker # Identifies the protocol
|
|
190
|
+
KADI_BROKER_URL=ws://localhost:8080 # Broker connection URL
|
|
191
|
+
KADI_ABILITY_NAME=ability.echo.1_0_0 # Ability identifier
|
|
192
|
+
KADI_AGENT_SCOPE=uuid-here # Agent's scope/namespace
|
|
193
|
+
# Plus all parent process.env variables
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**Network/Namespace Visibility:**
|
|
197
|
+
|
|
198
|
+
The `KADI_AGENT_SCOPE` environment variable represents the agent's namespace. For the ability to be visible to its parent agent via the broker, it must register with the same network:
|
|
199
|
+
|
|
200
|
+
```javascript
|
|
201
|
+
// In your ability code:
|
|
202
|
+
const echoAbility = new KadiClient({
|
|
203
|
+
name: 'echo-js',
|
|
204
|
+
version: '0.0.1',
|
|
205
|
+
description: 'Echo ability with broker support',
|
|
206
|
+
network: process.env.KADI_AGENT_SCOPE // Use agent's network for visibility
|
|
207
|
+
});
|
|
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
|
+
```
|
|
223
|
+
|
|
224
|
+
## 🛠️ Creating Abilities with KadiClient
|
|
225
|
+
|
|
226
|
+
### Basic Ability Structure
|
|
227
|
+
|
|
228
|
+
```javascript
|
|
229
|
+
import { KadiClient } from '@kadi.build/core';
|
|
230
|
+
|
|
231
|
+
const ability = new KadiClient({
|
|
232
|
+
name: 'echo-ability',
|
|
233
|
+
role: 'ability', // 'agent', 'ability',
|
|
234
|
+
protocol: 'stdio', // 'native', 'stdio', or 'broker'
|
|
235
|
+
brokers: {
|
|
236
|
+
local: 'ws://localhost:8080',
|
|
237
|
+
prod: 'ws://api.example.com:8080'
|
|
238
|
+
},
|
|
239
|
+
defaultBroker: 'local'
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
// Method 1: Simple tool registration
|
|
243
|
+
ability.registerTool('echo', async ({ message }) => ({
|
|
244
|
+
echo: message,
|
|
245
|
+
timestamp: new Date().toISOString()
|
|
246
|
+
}));
|
|
247
|
+
|
|
248
|
+
// Method 2: Tool with inline schema
|
|
249
|
+
ability.registerTool(
|
|
250
|
+
'format',
|
|
251
|
+
async ({ text, style }) => ({
|
|
252
|
+
formatted: style === 'upper' ? text.toUpperCase() : text.toLowerCase()
|
|
253
|
+
}),
|
|
254
|
+
{
|
|
255
|
+
description: 'Format text with specified style',
|
|
256
|
+
inputSchema: {
|
|
257
|
+
type: 'object',
|
|
258
|
+
properties: {
|
|
259
|
+
text: { type: 'string', description: 'Text to format' },
|
|
260
|
+
style: {
|
|
261
|
+
type: 'string',
|
|
262
|
+
enum: ['upper', 'lower'],
|
|
263
|
+
description: 'Formatting style'
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
required: ['text', 'style']
|
|
267
|
+
},
|
|
268
|
+
outputSchema: {
|
|
269
|
+
type: 'object',
|
|
270
|
+
properties: {
|
|
271
|
+
formatted: { type: 'string', description: 'Formatted text' }
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
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() };
|
|
24
302
|
|
|
25
|
-
|
|
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
|
+
});
|
|
324
|
+
```
|
|
26
325
|
|
|
27
|
-
|
|
28
|
-
- **`getAbilityJSONPath(abilityName, abilityVersion)`**: Provides the file path to the `agent.json` for a specific ability.
|
|
29
|
-
- **`getAbilityVersionFromArray(abilities, name)`**: Searches ability array provided, and returns the version number for name provided.
|
|
30
|
-
- **`getAbilitiesDir()`**: Returns the directory path where abilities are stored.
|
|
31
|
-
- **`getProjectJSON()`**: Fetches the `agent.json` for the current project.
|
|
32
|
-
- **`getProjectJSONPath()`**: Gets the file path for the project's `agent.json`.
|
|
33
|
-
- **`getKadiCoreJSON()`**: Retrieves the `agent.json` for the Kadi core.
|
|
34
|
-
- **`getKadiCoreJSONPath()`**: Provides the file path to the Kadi core's `agent.json`.
|
|
35
|
-
- **`getKadiJSON()`**: Fetches the `agent.json` for the Kadi system.
|
|
36
|
-
- **`getKadiJSONPath()`**: Returns the file path for the Kadi system's `agent.json`.
|
|
326
|
+
**Event Patterns**:
|
|
37
327
|
|
|
38
|
-
|
|
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
|
|
39
333
|
|
|
40
|
-
|
|
334
|
+
### Ability Configuration (agent.json)
|
|
335
|
+
|
|
336
|
+
Every ability should have an `agent.json` file that defines its capabilities:
|
|
41
337
|
|
|
42
338
|
```json
|
|
43
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
|
+
],
|
|
44
369
|
"brokers": {
|
|
45
|
-
"local": "ws://
|
|
46
|
-
"remote": "ws://
|
|
47
|
-
|
|
48
|
-
|
|
370
|
+
"local": "ws://localhost:8080",
|
|
371
|
+
"remote": "ws://api.example.com:8080"
|
|
372
|
+
},
|
|
373
|
+
"defaultBroker": "local"
|
|
49
374
|
}
|
|
50
375
|
```
|
|
51
376
|
|
|
52
|
-
|
|
377
|
+
### Schema Definition Options
|
|
378
|
+
|
|
379
|
+
The ability system provides two ways to define method schemas:
|
|
380
|
+
|
|
381
|
+
1. **Export Schemas** (defined in `agent.json` exports section)
|
|
382
|
+
2. **Inline Schemas** (passed directly to `registerTool()`)
|
|
383
|
+
|
|
384
|
+
```javascript
|
|
385
|
+
// Option 1: Define in agent.json exports section
|
|
386
|
+
// The schema is picked up automatically if the method name matches
|
|
387
|
+
// agent.json:
|
|
388
|
+
{
|
|
389
|
+
"exports": [
|
|
390
|
+
{
|
|
391
|
+
"name": "process",
|
|
392
|
+
"description": "Process data",
|
|
393
|
+
"inputSchema": { /* ... */ },
|
|
394
|
+
"outputSchema": { /* ... */ }
|
|
395
|
+
}
|
|
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
|
+
```
|
|
53
408
|
|
|
54
|
-
|
|
55
|
-
- **`KADI_BROKER_URL`**: Default broker URL (first one defined, for backward compatibility).
|
|
56
|
-
- **`getBrokerUrl(brokerName)`**: Get URL for a specific broker by name. Returns `null` if not found.
|
|
57
|
-
- **`getBrokerNames()`**: Get array of all available broker names.
|
|
58
|
-
- **`setActiveBroker(brokerName)`**: Set the active broker for the session. Returns `true` if successful.
|
|
59
|
-
- **`getActiveBrokerName()`**: Get the name of the currently active broker.
|
|
60
|
-
- **`getActiveBrokerUrl()`**: Get the URL of the currently active broker.
|
|
61
|
-
- **`getDefaultBrokerName()`**: Get the name of the default broker (first one defined).
|
|
62
|
-
- **`selectBrokerFromEnv()`**: Set active broker from `KADI_BROKER` environment variable.
|
|
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.
|
|
63
410
|
|
|
64
|
-
|
|
411
|
+
## 🤖 Working with KadiClient
|
|
65
412
|
|
|
66
|
-
|
|
67
|
-
- **`runSpawnCommand(name, version, command)`**: Uses `spawn` to execute commands for subprocesses.
|
|
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.
|
|
68
414
|
|
|
69
|
-
|
|
415
|
+
### Direct Approach (using loadAbility)
|
|
70
416
|
|
|
71
417
|
```javascript
|
|
72
|
-
import {
|
|
73
|
-
getProjectJSON,
|
|
74
|
-
runExecCommand,
|
|
75
|
-
IPCManager,
|
|
76
|
-
Broker
|
|
77
|
-
} from '@kadi.build/core';
|
|
418
|
+
import { loadAbility } from '@kadi.build/core';
|
|
78
419
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
// Set active broker
|
|
90
|
-
setActiveBroker('remote');
|
|
91
|
-
console.log('Active broker:', getActiveBrokerUrl());
|
|
92
|
-
|
|
93
|
-
// IPC setup
|
|
94
|
-
const ipc = new IPCManager();
|
|
95
|
-
ipc.createInstance('python', 'pythonScript.py', 'pythonInstance');
|
|
96
|
-
|
|
97
|
-
// Traditional broker setup
|
|
98
|
-
Broker.addBroker('ws://example.com', 'exampleBroker');
|
|
99
|
-
let broker = Broker.getBroker('default');
|
|
100
|
-
console.log(`Connected to ${broker.url}`);
|
|
101
|
-
broker.send(
|
|
102
|
-
BrokerMessageBuilder.setup(
|
|
103
|
-
'TestAgent',
|
|
104
|
-
'A Broker Testing Agent',
|
|
105
|
-
null,
|
|
106
|
-
null
|
|
107
|
-
)
|
|
108
|
-
);
|
|
109
|
-
}
|
|
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' });
|
|
110
430
|
```
|
|
111
431
|
|
|
112
|
-
###
|
|
432
|
+
### Using KadiClient with Named Brokers
|
|
113
433
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
+
});
|
|
117
450
|
|
|
118
|
-
|
|
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
|
+
);
|
|
119
466
|
|
|
120
|
-
|
|
467
|
+
// Connect to all configured brokers
|
|
468
|
+
await client.connectToBrokers();
|
|
121
469
|
|
|
122
|
-
|
|
470
|
+
// Call remote tools
|
|
471
|
+
const result = await client.callTool('translator', 'translate', {
|
|
472
|
+
text: 'Hello',
|
|
473
|
+
to: 'es'
|
|
474
|
+
});
|
|
123
475
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
- **`send(message, brokerName = 'default')`**: Sends a message through the broker specified by the name.
|
|
128
|
-
- **`addEventListener(event, listener, brokerName = 'default')`**: Adds an event listener to the specified broker.
|
|
129
|
-
- **`removeEventListener(event, listener, brokerName = 'default')`**: Removes an event listener from the specified broker.
|
|
130
|
-
- **`removeAllListeners(brokerName = 'default')`**: Removes all event listeners from the specified broker.
|
|
131
|
-
- **`getBroker(brokerName = 'default')`**: Retrieves a broker instance by name.
|
|
476
|
+
// Load abilities for compatibility
|
|
477
|
+
const ability = await client.loadAbility('echo-js');
|
|
478
|
+
```
|
|
132
479
|
|
|
133
|
-
|
|
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
|
+
```
|
|
134
549
|
|
|
135
|
-
|
|
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
|
+
```
|
|
136
615
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
+
```
|
|
140
670
|
|
|
141
|
-
###
|
|
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
|
|
727
|
+
```
|
|
142
728
|
|
|
143
|
-
|
|
729
|
+
### Stdio Protocol: LSP-Style Frame Processing
|
|
730
|
+
|
|
731
|
+
How KadiClient handles stdio communication with LSP-style framing:
|
|
732
|
+
|
|
733
|
+
```mermaid
|
|
734
|
+
sequenceDiagram
|
|
735
|
+
participant Parent as Parent Process
|
|
736
|
+
participant SR as StdioFrameReader
|
|
737
|
+
participant SW as StdioFrameWriter
|
|
738
|
+
participant Server as StdioRpcServer
|
|
739
|
+
participant Ability as KadiClient
|
|
740
|
+
|
|
741
|
+
Note over Parent,SR: Incoming Request
|
|
742
|
+
Parent->>SR: Write to stdin: "Content-Length: 52\r\n\r\n{...}"
|
|
743
|
+
SR->>SR: Buffer incoming data
|
|
744
|
+
SR->>SR: Look for Content-Length header
|
|
745
|
+
SR->>SR: Parse header, extract body length
|
|
746
|
+
SR->>SR: Wait for complete body
|
|
747
|
+
SR->>SR: Parse JSON from body
|
|
748
|
+
SR->>Server: onMessage({ success: true, data: request })
|
|
749
|
+
|
|
750
|
+
Server->>Server: Validate JSON-RPC format
|
|
751
|
+
Server->>Ability: getMethodHandler(request.method)
|
|
752
|
+
Ability-->>Server: Return handler
|
|
753
|
+
Server->>Server: Execute handler(params) with timeout
|
|
754
|
+
|
|
755
|
+
Note over Server,Parent: Response
|
|
756
|
+
Server->>SW: write(response)
|
|
757
|
+
SW->>SW: JSON.stringify(response)
|
|
758
|
+
SW->>SW: Calculate byte length
|
|
759
|
+
SW->>SW: Create header: "Content-Length: N\r\n\r\n"
|
|
760
|
+
SW->>Parent: Write header + body to stdout
|
|
761
|
+
|
|
762
|
+
Note over SR: Error Recovery
|
|
763
|
+
alt Frame Corruption
|
|
764
|
+
SR->>SR: Detect invalid frame
|
|
765
|
+
SR->>SR: Search for next Content-Length marker
|
|
766
|
+
SR->>SR: Skip corrupted bytes
|
|
767
|
+
SR->>Server: onMessage({ success: false, error: 'FRAME_CORRUPTION' })
|
|
768
|
+
Server->>Server: Log error, continue processing
|
|
769
|
+
end
|
|
770
|
+
```
|
|
144
771
|
|
|
145
|
-
|
|
146
|
-
- **`message(to, content)`**: Constructs a message directed to a specific peer.
|
|
147
|
-
- **`setup(name, description, limit, uuid)`**: Creates a setup message for registering the broker.
|
|
148
|
-
- **`suspend()`**: Constructs a suspend message.
|
|
149
|
-
- **`finish()`**: Constructs a finish message.
|
|
150
|
-
- **`list()`**: Constructs a list message to request a list of connected agents.
|
|
772
|
+
## 📥 Loading Abilities
|
|
151
773
|
|
|
152
|
-
###
|
|
774
|
+
### Basic Loading
|
|
153
775
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
- **`IAbilityIPC`**: Represents an IPC ability instance with methods to manage its lifecycle and communications.
|
|
776
|
+
```javascript
|
|
777
|
+
import { loadAbility } from '@kadi.build/core';
|
|
157
778
|
|
|
158
|
-
|
|
779
|
+
// Auto-detect protocol from agent.json
|
|
780
|
+
const ability = await loadAbility('my-ability');
|
|
159
781
|
|
|
160
|
-
|
|
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
|
+
|
|
790
|
+
```javascript
|
|
791
|
+
// Call methods directly - no need to discover them first
|
|
792
|
+
const result = await ability.echo({ message: 'hello' });
|
|
161
793
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
- **`shutdownInstance(name)`**: Shuts down an existing IPC instance by name.
|
|
794
|
+
// Call methods
|
|
795
|
+
const result = await ability.someMethod({ param: 'value' });
|
|
165
796
|
|
|
166
|
-
|
|
797
|
+
// Direct RPC call (bypasses method validation)
|
|
798
|
+
const response = await ability.__call('someMethod', { param: 'value' });
|
|
167
799
|
|
|
168
|
-
|
|
800
|
+
// Subscribe to events
|
|
801
|
+
ability.events.on('custom:event', (data) => {
|
|
802
|
+
console.log('Event received:', data);
|
|
803
|
+
});
|
|
169
804
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
- **`shutdown()`**: Sends a shutdown command to the child process and terminates it.
|
|
805
|
+
// Note: Events work for all protocols (native, stdio, and broker)
|
|
806
|
+
// Subscribe before calling methods that emit events
|
|
807
|
+
```
|
|
174
808
|
|
|
175
|
-
###
|
|
809
|
+
### KadiClient transport vs loadAbility transport
|
|
176
810
|
|
|
177
|
-
|
|
811
|
+
KADI uses “transport” in two places and they serve different purposes:
|
|
178
812
|
|
|
179
|
-
-
|
|
180
|
-
-
|
|
181
|
-
-
|
|
182
|
-
-
|
|
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.
|
|
183
817
|
|
|
184
|
-
|
|
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
|
|
185
822
|
|
|
186
|
-
|
|
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.
|
|
187
824
|
|
|
188
|
-
|
|
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' });
|
|
871
|
+
```
|
|
872
|
+
|
|
873
|
+
### Loading Context
|
|
874
|
+
|
|
875
|
+
The loader automatically resolves ability versions from:
|
|
876
|
+
|
|
877
|
+
1. **Project context**: Reads from project's `agent.json`
|
|
878
|
+
2. **Nested context**: When loading from within another ability
|
|
879
|
+
3. **Explicit version**: Can be specified in ability name
|
|
880
|
+
|
|
881
|
+
## ⚙️ Configuration
|
|
882
|
+
|
|
883
|
+
### Environment Variables
|
|
884
|
+
|
|
885
|
+
```bash
|
|
886
|
+
# Set default protocol
|
|
887
|
+
export KADI_PROTOCOL=stdio
|
|
888
|
+
|
|
889
|
+
# Configure broker
|
|
890
|
+
export KADI_BROKER_URL=ws://localhost:8080
|
|
891
|
+
export KADI_ABILITY_NAME=my-ability
|
|
892
|
+
export KADI_AGENT_SCOPE=project-123
|
|
893
|
+
```
|
|
894
|
+
|
|
895
|
+
### Environment Variables for Child Processes
|
|
896
|
+
|
|
897
|
+
When abilities are spawned as child processes (stdio and broker protocols), the parent process passes down important environment variables:
|
|
189
898
|
|
|
190
899
|
```javascript
|
|
191
|
-
|
|
900
|
+
// In your ability code, you can access these variables:
|
|
901
|
+
const protocol = process.env.KADI_PROTOCOL; // 'stdio' or 'broker'
|
|
902
|
+
const brokerUrl = process.env.KADI_BROKER_URL;
|
|
903
|
+
const abilityName = process.env.KADI_ABILITY_NAME;
|
|
904
|
+
const agentScope = process.env.KADI_AGENT_SCOPE;
|
|
905
|
+
|
|
906
|
+
// Use them to configure your ability behavior
|
|
907
|
+
const ability = new KadiClient({
|
|
908
|
+
name: 'my-ability',
|
|
909
|
+
network: process.env.KADI_AGENT_SCOPE || 'global'
|
|
910
|
+
// Ability automatically uses KADI_PROTOCOL to determine transport
|
|
911
|
+
});
|
|
912
|
+
```
|
|
192
913
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
914
|
+
### Example Project Structure
|
|
915
|
+
|
|
916
|
+
```
|
|
917
|
+
my-project/
|
|
918
|
+
├── agent.json # Project configuration
|
|
919
|
+
├── abilities/ # Installed abilities
|
|
920
|
+
│ └── echo-ability/
|
|
921
|
+
│ └── 1.0.0/
|
|
922
|
+
│ ├── agent.json # Ability configuration
|
|
923
|
+
│ ├── ability.js # Ability implementation
|
|
924
|
+
│ └── package.json
|
|
925
|
+
├── modules/ # Source modules
|
|
926
|
+
│ └── echo-ability/ # Development version
|
|
927
|
+
└── index.js # Main entry point
|
|
928
|
+
```
|
|
929
|
+
|
|
930
|
+
## 🚀 Advanced Usage
|
|
931
|
+
|
|
932
|
+
### Event System
|
|
199
933
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
934
|
+
KadiClient provides a comprehensive event system that works across all protocols:
|
|
935
|
+
|
|
936
|
+
#### 1. Lifecycle Events
|
|
937
|
+
|
|
938
|
+
Monitor the ability lifecycle:
|
|
939
|
+
|
|
940
|
+
```javascript
|
|
941
|
+
const client = new KadiClient({ name: 'my-ability' });
|
|
942
|
+
|
|
943
|
+
// Connection events
|
|
944
|
+
client.on('connected', ({ broker }) => {
|
|
945
|
+
console.log(`Connected to broker: ${broker}`);
|
|
946
|
+
});
|
|
947
|
+
|
|
948
|
+
client.on('disconnected', () => {
|
|
949
|
+
console.log('Disconnected from broker');
|
|
950
|
+
});
|
|
951
|
+
|
|
952
|
+
// Tool invocation events
|
|
953
|
+
client.on('tool:invoked', ({ toolName, params }) => {
|
|
954
|
+
console.log(`Tool ${toolName} invoked with:`, params);
|
|
955
|
+
});
|
|
956
|
+
|
|
957
|
+
client.on('tool:completed', ({ toolName, result }) => {
|
|
958
|
+
console.log(`Tool ${toolName} completed:`, result);
|
|
959
|
+
});
|
|
960
|
+
|
|
961
|
+
client.on('error', (error) => {
|
|
962
|
+
console.error('Ability error:', error);
|
|
963
|
+
});
|
|
964
|
+
```
|
|
965
|
+
|
|
966
|
+
#### 2. Custom Events (All Protocols)
|
|
967
|
+
|
|
968
|
+
Publish and subscribe to custom events across all transport protocols:
|
|
969
|
+
|
|
970
|
+
```javascript
|
|
971
|
+
// Publishing events
|
|
972
|
+
const publisher = new KadiClient({
|
|
973
|
+
name: 'event-publisher',
|
|
974
|
+
role: 'ability',
|
|
975
|
+
protocol: 'broker' // Works with all protocols
|
|
976
|
+
});
|
|
977
|
+
|
|
978
|
+
publisher.registerTool('process', async ({ data }) => {
|
|
979
|
+
// Publish events during processing
|
|
980
|
+
await publisher.publishEvent('process.started', {
|
|
981
|
+
timestamp: Date.now(),
|
|
982
|
+
data
|
|
983
|
+
});
|
|
984
|
+
|
|
985
|
+
// Do work...
|
|
986
|
+
const result = await processData(data);
|
|
987
|
+
|
|
988
|
+
await publisher.publishEvent('process.completed', {
|
|
989
|
+
timestamp: Date.now(),
|
|
990
|
+
result
|
|
991
|
+
});
|
|
992
|
+
|
|
993
|
+
return result;
|
|
203
994
|
});
|
|
204
995
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
996
|
+
// Subscribing to events
|
|
997
|
+
const subscriber = new KadiClient({
|
|
998
|
+
name: 'event-subscriber',
|
|
999
|
+
role: 'agent',
|
|
1000
|
+
protocol: 'broker'
|
|
208
1001
|
});
|
|
209
1002
|
|
|
210
|
-
|
|
211
|
-
|
|
1003
|
+
// Subscribe with wildcards
|
|
1004
|
+
subscriber.subscribeToEvent('process.*', (data) => {
|
|
1005
|
+
console.log('Process event:', data);
|
|
212
1006
|
});
|
|
213
1007
|
|
|
214
|
-
|
|
215
|
-
|
|
1008
|
+
// One-time subscription
|
|
1009
|
+
subscriber.onceEvent('process.completed', (data) => {
|
|
1010
|
+
console.log('First completion:', data);
|
|
216
1011
|
});
|
|
217
1012
|
|
|
218
|
-
|
|
219
|
-
|
|
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']
|
|
220
1041
|
});
|
|
221
1042
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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
|
+
```
|
|
1052
|
+
|
|
1053
|
+
### Cross-Language Ability Support
|
|
1054
|
+
|
|
1055
|
+
KADI now fully supports abilities written in any language through the stdio protocol:
|
|
1056
|
+
|
|
1057
|
+
```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
|
+
const client = new KadiClient({
|
|
1138
|
+
name: 'my-ability',
|
|
1139
|
+
role: 'ability', // 'agent' or 'ability'
|
|
1140
|
+
protocol: 'broker', // 'native', 'stdio', or 'broker'
|
|
1141
|
+
brokers: {
|
|
1142
|
+
dev: 'ws://localhost:8080',
|
|
1143
|
+
prod: 'ws://prod.example.com:8080'
|
|
1144
|
+
},
|
|
1145
|
+
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)
|
|
225
1161
|
|
|
226
|
-
|
|
1162
|
+
**Tool Registration Methods:**
|
|
227
1163
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
|
+
networks: ['global']
|
|
237
1218
|
});
|
|
238
1219
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
1220
|
+
// Register tools with schemas
|
|
1221
|
+
ability.registerTool(
|
|
1222
|
+
'add',
|
|
1223
|
+
async ({ a, b }) => {
|
|
1224
|
+
// Publish event before processing
|
|
1225
|
+
await ability.publishEvent('math.operation', {
|
|
1226
|
+
operation: 'add',
|
|
1227
|
+
inputs: { a, b }
|
|
1228
|
+
});
|
|
1229
|
+
|
|
1230
|
+
const result = a + b;
|
|
1231
|
+
|
|
1232
|
+
// Publish completion event
|
|
1233
|
+
await ability.publishEvent('math.completed', {
|
|
1234
|
+
operation: 'add',
|
|
1235
|
+
result
|
|
1236
|
+
});
|
|
1237
|
+
|
|
1238
|
+
return { result };
|
|
1239
|
+
},
|
|
1240
|
+
{
|
|
1241
|
+
description: 'Add two numbers',
|
|
1242
|
+
inputSchema: {
|
|
1243
|
+
type: 'object',
|
|
1244
|
+
properties: {
|
|
1245
|
+
a: { type: 'number', description: 'First number' },
|
|
1246
|
+
b: { type: 'number', description: 'Second number' }
|
|
1247
|
+
},
|
|
1248
|
+
required: ['a', 'b']
|
|
1249
|
+
},
|
|
1250
|
+
outputSchema: {
|
|
1251
|
+
type: 'object',
|
|
1252
|
+
properties: {
|
|
1253
|
+
result: { type: 'number', description: 'Sum of a and b' }
|
|
247
1254
|
}
|
|
248
|
-
}
|
|
1255
|
+
}
|
|
249
1256
|
}
|
|
250
|
-
|
|
1257
|
+
);
|
|
1258
|
+
|
|
1259
|
+
// Subscribe to events from other abilities
|
|
1260
|
+
ability.subscribeToEvent('system.*', (data) => {
|
|
1261
|
+
console.log('System event:', data);
|
|
1262
|
+
});
|
|
1263
|
+
|
|
1264
|
+
// Connect to brokers
|
|
1265
|
+
await ability.connectToBrokers();
|
|
1266
|
+
|
|
1267
|
+
// Call remote tools
|
|
1268
|
+
const result = await ability.callTool('translator-ability', 'translate', {
|
|
1269
|
+
text: 'Hello',
|
|
1270
|
+
to: 'es'
|
|
251
1271
|
});
|
|
252
1272
|
|
|
253
|
-
|
|
254
|
-
|
|
1273
|
+
// Graceful shutdown
|
|
1274
|
+
process.on('SIGTERM', async () => {
|
|
1275
|
+
await ability.disconnect();
|
|
1276
|
+
process.exit(0);
|
|
255
1277
|
});
|
|
1278
|
+
```
|
|
1279
|
+
|
|
1280
|
+
#### `loadAbility`
|
|
1281
|
+
|
|
1282
|
+
Load an ability by name and protocol.
|
|
1283
|
+
|
|
1284
|
+
```javascript
|
|
1285
|
+
import { loadAbility } from '@kadi.build/core';
|
|
256
1286
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
1287
|
+
const ability = await loadAbility('ability-name', 'protocol', {
|
|
1288
|
+
brokerUrl: 'ws://localhost:8080', // For broker protocol
|
|
1289
|
+
brokerName: 'dev', // Named broker to use
|
|
1290
|
+
networks: ['global'], // Network segments
|
|
1291
|
+
existingClient: client // Reuse existing KadiClient for broker
|
|
260
1292
|
});
|
|
1293
|
+
```
|
|
1294
|
+
|
|
1295
|
+
**Returns:** A proxy object with:
|
|
261
1296
|
|
|
262
|
-
|
|
1297
|
+
- Direct method calls (e.g., `ability.echo({ message: 'hello' })`)
|
|
1298
|
+
- `ability.events` - EventEmitter for subscribing to events (all protocols)
|
|
1299
|
+
- `ability.__call(method, params)` - Direct RPC call
|
|
1300
|
+
- `ability.__disconnect()` - Clean up resources
|
|
263
1301
|
|
|
264
|
-
|
|
265
|
-
// pythonIPC.sendMessage(IPCMessageBuilder.launch(pythonIPC.commandString, process.cwd() + "/logs"));
|
|
1302
|
+
### Utility Functions
|
|
266
1303
|
|
|
267
|
-
|
|
268
|
-
|
|
1304
|
+
```javascript
|
|
1305
|
+
import {
|
|
1306
|
+
createLogger,
|
|
1307
|
+
getProjectJSON,
|
|
1308
|
+
getAbilityJSON,
|
|
1309
|
+
getAgentJSON,
|
|
1310
|
+
getBrokerUrl,
|
|
1311
|
+
runExecCommand
|
|
1312
|
+
} from '@kadi.build/core';
|
|
1313
|
+
```
|
|
1314
|
+
|
|
1315
|
+
### Debug Mode
|
|
1316
|
+
|
|
1317
|
+
Enable detailed logging:
|
|
1318
|
+
|
|
1319
|
+
```bash
|
|
1320
|
+
DEBUG=kadi:* node index.js
|
|
269
1321
|
```
|
|
270
1322
|
|
|
271
|
-
|
|
1323
|
+
Check ability logs:
|
|
1324
|
+
|
|
1325
|
+
```bash
|
|
1326
|
+
tail -f abilities/my-ability/1.0.0/my-ability.log
|
|
1327
|
+
```
|
|
1328
|
+
|
|
1329
|
+
## 💡 Additional Examples
|
|
1330
|
+
|
|
1331
|
+
### Error Handling Pattern
|
|
272
1332
|
|
|
273
1333
|
```javascript
|
|
274
|
-
const
|
|
275
|
-
|
|
276
|
-
|
|
1334
|
+
const client = new KadiClient({
|
|
1335
|
+
name: 'robust-ability',
|
|
1336
|
+
role: 'ability'
|
|
1337
|
+
});
|
|
1338
|
+
|
|
1339
|
+
client.registerTool('riskyOperation', async (params) => {
|
|
1340
|
+
try {
|
|
1341
|
+
const result = await performOperation(params);
|
|
1342
|
+
await client.publishEvent('operation.success', { result });
|
|
1343
|
+
return { success: true, result };
|
|
1344
|
+
} catch (error) {
|
|
1345
|
+
await client.publishEvent('operation.error', {
|
|
1346
|
+
error: error.message,
|
|
1347
|
+
params
|
|
1348
|
+
});
|
|
1349
|
+
// Return error in structured format
|
|
1350
|
+
return {
|
|
1351
|
+
success: false,
|
|
1352
|
+
error: error.message
|
|
1353
|
+
};
|
|
1354
|
+
}
|
|
1355
|
+
});
|
|
277
1356
|
```
|
|
278
1357
|
|
|
1358
|
+
### Health Check Pattern
|
|
1359
|
+
|
|
1360
|
+
```javascript
|
|
1361
|
+
// Register a standard health check tool
|
|
1362
|
+
client.registerTool('health', async () => {
|
|
1363
|
+
const checks = {
|
|
1364
|
+
memory: process.memoryUsage(),
|
|
1365
|
+
uptime: process.uptime(),
|
|
1366
|
+
brokers: client.isConnected ? 'connected' : 'disconnected',
|
|
1367
|
+
timestamp: Date.now()
|
|
1368
|
+
};
|
|
1369
|
+
|
|
1370
|
+
return {
|
|
1371
|
+
status: 'healthy',
|
|
1372
|
+
checks
|
|
1373
|
+
};
|
|
1374
|
+
});
|
|
1375
|
+
```
|
|
1376
|
+
|
|
1377
|
+
## 🐛 Troubleshooting
|
|
1378
|
+
|
|
1379
|
+
### Common Issues and Solutions
|
|
1380
|
+
|
|
1381
|
+
#### Broker Connection Failures
|
|
1382
|
+
|
|
1383
|
+
**Problem**: "Failed to connect to any brokers"
|
|
1384
|
+
|
|
1385
|
+
**Solutions**:
|
|
1386
|
+
|
|
1387
|
+
- Verify broker URLs are correct
|
|
1388
|
+
- Check network connectivity
|
|
1389
|
+
- Ensure broker is running
|
|
1390
|
+
- Try connecting with lower security (ws:// before wss://)
|
|
1391
|
+
|
|
1392
|
+
#### Events Not Arriving
|
|
1393
|
+
|
|
1394
|
+
**Problem**: Subscribed to events but callbacks not triggered
|
|
1395
|
+
|
|
1396
|
+
**Solutions**:
|
|
1397
|
+
|
|
1398
|
+
- Subscribe BEFORE triggering events
|
|
1399
|
+
- Check pattern matching (use exact match first)
|
|
1400
|
+
- Verify publisher and subscriber are on same network
|
|
1401
|
+
- Enable debug logging to trace event flow
|
|
1402
|
+
|
|
1403
|
+
#### Cross-Language Abilities Not Loading
|
|
1404
|
+
|
|
1405
|
+
**Problem**: "Cannot find module 'ability.js'" when loading Go/Python abilities
|
|
1406
|
+
|
|
1407
|
+
**Solutions**:
|
|
1408
|
+
|
|
1409
|
+
- Ensure `scripts.start` field exists in agent.json
|
|
1410
|
+
- Verify the binary/script is executable
|
|
1411
|
+
- Check that setup script has been run
|
|
1412
|
+
- Use stdio protocol, not native
|
|
1413
|
+
|
|
1414
|
+
#### Memory Leaks
|
|
1415
|
+
|
|
1416
|
+
**Problem**: Memory usage grows over time
|
|
1417
|
+
|
|
1418
|
+
**Solutions**:
|
|
1419
|
+
|
|
1420
|
+
- Always call unsubscribe functions
|
|
1421
|
+
- Use `onceEvent` for single-use subscriptions
|
|
1422
|
+
- Disconnect abilities when done
|
|
1423
|
+
- Clear event listeners on cleanup
|
|
1424
|
+
|
|
1425
|
+
## 🤝 Contributing
|
|
1426
|
+
|
|
1427
|
+
We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
|
|
1428
|
+
|
|
1429
|
+
1. Fork the repository
|
|
1430
|
+
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
1431
|
+
3. Commit your changes (`git commit -m 'Add amazing feature'`)
|
|
1432
|
+
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
1433
|
+
5. Open a Pull Request
|
|
1434
|
+
|
|
1435
|
+
## 📄 License
|
|
1436
|
+
|
|
1437
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
1438
|
+
|
|
1439
|
+
## 🔗 Related Projects
|
|
1440
|
+
|
|
1441
|
+
- [@kadi.build/cli](https://gitlab.com/humin-game-lab/kadi/kadi) - Command-line interface
|
|
1442
|
+
- [@kadi.build/broker](https://gitlab.com/humin-game-lab/kadi/kadi-broker) - The KADI broker
|
|
1443
|
+
|
|
1444
|
+
## 📚 Resources
|
|
1445
|
+
|
|
1446
|
+
## ⚠️ Error Codes
|
|
1447
|
+
|
|
1448
|
+
KADI core exposes a unified, typed error catalog to keep errors consistent across core and the broker.
|
|
1449
|
+
|
|
1450
|
+
- CoreErrorCodes: CORE\_\* errors for library/transport concerns
|
|
1451
|
+
- BrokerErrorCodes: BROKER\_\* errors mirrored for broker interactions
|
|
1452
|
+
- ErrorCodes: merged view of both
|
|
1453
|
+
|
|
1454
|
+
Usage:
|
|
1455
|
+
|
|
1456
|
+
```ts
|
|
1457
|
+
import { KadiError, CoreErrorCodes } from '@kadi.build/core';
|
|
1458
|
+
|
|
1459
|
+
throw new KadiError(
|
|
1460
|
+
CoreErrorCodes.CORE_CONFIG_MISSING.code,
|
|
1461
|
+
'scripts.start is required for stdio',
|
|
1462
|
+
{ transport: 'stdio', abilityName: 'echo' }
|
|
1463
|
+
);
|
|
1464
|
+
```
|
|
1465
|
+
|
|
1466
|
+
Guidelines:
|
|
1467
|
+
|
|
1468
|
+
- Do not use raw string codes like 'CONFIG_MISSING'; always use the exported constants.
|
|
1469
|
+
- Prefer KadiError helper factories where available (e.g., KadiError.abilityNotFound()).
|
|
1470
|
+
- KadiError automatically tags domain from the code prefix (core/broker) and preserves the original stack.
|
|
1471
|
+
- Broker-side codes are fully prefixed: BROKER*SESSION*_, BROKER*AUTH*_, BROKER*AGENT*_, BROKER*NETWORK*_, etc.
|
|
1472
|
+
|
|
1473
|
+
- [Event System Deep Dive](docs/event-system.md) - Complete guide to the event architecture
|
|
1474
|
+
- [API Documentation](https://docs.kadi.build) - Full API reference
|
|
1475
|
+
- [Examples Repository](https://github.com/kadi-examples) - More examples and patterns
|
|
1476
|
+
|
|
279
1477
|
---
|
|
280
1478
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
- Once @kadi.build/core and dependencies are done, @kadi.build/cli (and all commands), need to be rewritten, using the @kadi.build/core module. this will be v1.0 release
|
|
284
|
-
|
|
285
|
-
* [ ] add getConnectedAgents to the Broker, it should call the IBroker method
|
|
286
|
-
|
|
287
|
-
* [x] add key to IBroker object
|
|
288
|
-
* [ ] IBroker on 'message' should listen for setup event, and update teh uuid and key for the IBroker object
|
|
289
|
-
|
|
290
|
-
* [ ] Extract Broker and IPC to their own node module and host on npm
|
|
291
|
-
* [x] Publish @kadi.build/core to npm
|
|
292
|
-
* [x] Setup of python environment needs to happen once an IPC Inteface is created
|
|
293
|
-
This include setting up the virtual environment and installing the required packages
|
|
294
|
-
* [x] Add method to IPCManger to set IPCInterfaces from agent.json. If IPCManger has empty list, it thorws an error that no IAbilityIPC's can be created. IPCManger passes the proper interface command to IAbilityIPC on creation from this list, IAbilityIPC does not need access directly to agent.json. Controlling app can update this list anytime a new IAbilityInterface is created, allowing the agent.json to be updated during runtime.
|
|
295
|
-
* [x] Need to adapt ipc.js to read from the @kadi.build/core folder when trying to open IPC instances, instead of project folder. @kadi.build/core should be in the npm_modules folder (when deployed)
|
|
296
|
-
* [x] The test code needs to fully test the IPCManger interface, and not just the IPCAbility
|
|
297
|
-
* [x] If node fails, we need to make sure python IPC interface and child processes also close out.
|
|
298
|
-
* [x] Add buffer to error handeling in IAbilityIPC, mimic same process used in std input
|
|
299
|
-
* [x] Verify if Python IAbilityIPC needs to utilize buffering, and node passing '\n' at end of jso
|
|
300
|
-
* [x] Setup passthrough for broker messages to/from Language IPC interface
|
|
301
|
-
* [x] Add broker message builder/parser in python IAbilityIPC
|
|
302
|
-
* [x] Update BrokerMessageBuilder to use same format at IPCMessageBuilder
|
|
303
|
-
* [x] Update Broker so it extends EventEmitter, rather than have a property of event emitter (similar to IAbilityIPC)
|
|
304
|
-
* [x] update python IAbility so that it creates a timestamped command_output.log file, new file for each run, in the project root directory... not the @kadi.build/core directory
|
|
305
|
-
* [x] Comment out debug messages
|
|
306
|
-
* [x] Add ability to allow for launched process to be piped to event handler (launch command should default to process, but allow user to set flag that will save all outputs to file in the event handler)
|
|
1479
|
+
Built with ❤️ by the KADI team
|