@kadi.build/core 0.0.1-alpha.9 → 0.1.0
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 +362 -1305
- package/dist/client.d.ts +573 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +1673 -0
- package/dist/client.js.map +1 -0
- package/dist/errors.d.ts +107 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +147 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +37 -14
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +40 -23
- package/dist/index.js.map +1 -1
- package/dist/lockfile.d.ts +190 -0
- package/dist/lockfile.d.ts.map +1 -0
- package/dist/lockfile.js +373 -0
- package/dist/lockfile.js.map +1 -0
- package/dist/transports/broker.d.ts +75 -0
- package/dist/transports/broker.d.ts.map +1 -0
- package/dist/transports/broker.js +383 -0
- package/dist/transports/broker.js.map +1 -0
- package/dist/transports/native.d.ts +39 -0
- package/dist/transports/native.d.ts.map +1 -0
- package/dist/transports/native.js +189 -0
- package/dist/transports/native.js.map +1 -0
- package/dist/transports/stdio.d.ts +46 -0
- package/dist/transports/stdio.d.ts.map +1 -0
- package/dist/transports/stdio.js +460 -0
- package/dist/transports/stdio.js.map +1 -0
- package/dist/types.d.ts +664 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +16 -0
- package/dist/types.js.map +1 -0
- package/dist/zod.d.ts +34 -0
- package/dist/zod.d.ts.map +1 -0
- package/dist/zod.js +60 -0
- package/dist/zod.js.map +1 -0
- package/package.json +13 -28
- package/dist/KadiClient.d.ts +0 -470
- package/dist/KadiClient.d.ts.map +0 -1
- package/dist/KadiClient.js +0 -1572
- package/dist/KadiClient.js.map +0 -1
- package/dist/errors/error-codes.d.ts +0 -985
- package/dist/errors/error-codes.d.ts.map +0 -1
- package/dist/errors/error-codes.js +0 -638
- package/dist/errors/error-codes.js.map +0 -1
- package/dist/loadAbility.d.ts +0 -105
- package/dist/loadAbility.d.ts.map +0 -1
- package/dist/loadAbility.js +0 -370
- package/dist/loadAbility.js.map +0 -1
- package/dist/messages/BrokerMessages.d.ts +0 -84
- package/dist/messages/BrokerMessages.d.ts.map +0 -1
- package/dist/messages/BrokerMessages.js +0 -125
- package/dist/messages/BrokerMessages.js.map +0 -1
- package/dist/messages/MessageBuilder.d.ts +0 -83
- package/dist/messages/MessageBuilder.d.ts.map +0 -1
- package/dist/messages/MessageBuilder.js +0 -144
- package/dist/messages/MessageBuilder.js.map +0 -1
- package/dist/schemas/events.schemas.d.ts +0 -177
- package/dist/schemas/events.schemas.d.ts.map +0 -1
- package/dist/schemas/events.schemas.js +0 -265
- package/dist/schemas/events.schemas.js.map +0 -1
- package/dist/schemas/index.d.ts +0 -3
- package/dist/schemas/index.d.ts.map +0 -1
- package/dist/schemas/index.js +0 -4
- package/dist/schemas/index.js.map +0 -1
- package/dist/schemas/kadi.schemas.d.ts +0 -70
- package/dist/schemas/kadi.schemas.d.ts.map +0 -1
- package/dist/schemas/kadi.schemas.js +0 -120
- package/dist/schemas/kadi.schemas.js.map +0 -1
- package/dist/transports/BrokerTransport.d.ts +0 -102
- package/dist/transports/BrokerTransport.d.ts.map +0 -1
- package/dist/transports/BrokerTransport.js +0 -177
- package/dist/transports/BrokerTransport.js.map +0 -1
- package/dist/transports/NativeTransport.d.ts +0 -82
- package/dist/transports/NativeTransport.d.ts.map +0 -1
- package/dist/transports/NativeTransport.js +0 -263
- package/dist/transports/NativeTransport.js.map +0 -1
- package/dist/transports/StdioTransport.d.ts +0 -112
- package/dist/transports/StdioTransport.d.ts.map +0 -1
- package/dist/transports/StdioTransport.js +0 -450
- package/dist/transports/StdioTransport.js.map +0 -1
- package/dist/transports/Transport.d.ts +0 -93
- package/dist/transports/Transport.d.ts.map +0 -1
- package/dist/transports/Transport.js +0 -13
- package/dist/transports/Transport.js.map +0 -1
- package/dist/types/broker.d.ts +0 -31
- package/dist/types/broker.d.ts.map +0 -1
- package/dist/types/broker.js +0 -6
- package/dist/types/broker.js.map +0 -1
- package/dist/types/core.d.ts +0 -139
- package/dist/types/core.d.ts.map +0 -1
- package/dist/types/core.js +0 -26
- package/dist/types/core.js.map +0 -1
- package/dist/types/events.d.ts +0 -186
- package/dist/types/events.d.ts.map +0 -1
- package/dist/types/events.js +0 -16
- package/dist/types/events.js.map +0 -1
- package/dist/types/index.d.ts +0 -9
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js +0 -13
- package/dist/types/index.js.map +0 -1
- package/dist/types/protocol.d.ts +0 -160
- package/dist/types/protocol.d.ts.map +0 -1
- package/dist/types/protocol.js +0 -5
- package/dist/types/protocol.js.map +0 -1
- package/dist/utils/agentUtils.d.ts +0 -187
- package/dist/utils/agentUtils.d.ts.map +0 -1
- package/dist/utils/agentUtils.js +0 -185
- package/dist/utils/agentUtils.js.map +0 -1
- package/dist/utils/commandUtils.d.ts +0 -45
- package/dist/utils/commandUtils.d.ts.map +0 -1
- package/dist/utils/commandUtils.js +0 -145
- package/dist/utils/commandUtils.js.map +0 -1
- package/dist/utils/configUtils.d.ts +0 -55
- package/dist/utils/configUtils.d.ts.map +0 -1
- package/dist/utils/configUtils.js +0 -100
- package/dist/utils/configUtils.js.map +0 -1
- package/dist/utils/logger.d.ts +0 -59
- package/dist/utils/logger.d.ts.map +0 -1
- package/dist/utils/logger.js +0 -122
- package/dist/utils/logger.js.map +0 -1
- package/dist/utils/pathUtils.d.ts +0 -48
- package/dist/utils/pathUtils.d.ts.map +0 -1
- package/dist/utils/pathUtils.js +0 -128
- package/dist/utils/pathUtils.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,1479 +1,536 @@
|
|
|
1
|
-
#
|
|
1
|
+
# kadi-core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
TypeScript SDK for building KADI agents. Register tools, connect to brokers, load abilities, publish events.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
kadi-core lets you:
|
|
6
|
+
- **Expose functions** as callable tools over the network
|
|
7
|
+
- **Discover and invoke** remote services without hardcoding URLs
|
|
8
|
+
- **Communicate via events** across your service mesh
|
|
7
9
|
|
|
8
|
-
##
|
|
9
|
-
|
|
10
|
-
`@kadi.build/core` is the foundational library for creating KADI abilities - modular, protocol-agnostic components that can communicate via multiple transport layers. Whether you're building local tools, distributed systems, or high-performance native modules, this toolkit provides a unified, developer-friendly API that abstracts away transport complexity.
|
|
11
|
-
|
|
12
|
-
## 📚 Table of Contents
|
|
13
|
-
|
|
14
|
-
- [🎯 Overview](#-overview)
|
|
15
|
-
- [📦 Installation](#-installation)
|
|
16
|
-
- [🚀 Quick Start](#-quick-start)
|
|
17
|
-
- [🔌 Transport Protocols](#-transport-protocols)
|
|
18
|
-
- [🛠️ Creating Abilities](#-creating-abilities)
|
|
19
|
-
- [🤖 Creating Agents](#-creating-agents)
|
|
20
|
-
- [🔄 Architecture Deep Dive](#-architecture-deep-dive)
|
|
21
|
-
- [📥 Loading Abilities](#-loading-abilities)
|
|
22
|
-
- [⚙️ Configuration](#-configuration)
|
|
23
|
-
- [🚀 Advanced Usage](#-advanced-usage)
|
|
24
|
-
- [🔧 Development Workflow](#-development-workflow)
|
|
25
|
-
- [📖 API Reference](#-api-reference)
|
|
26
|
-
- [🎮 Running the Examples](#-running-the-examples)
|
|
27
|
-
- [💡 Additional Examples](#-additional-examples)
|
|
28
|
-
- [🐛 Troubleshooting](#-troubleshooting)
|
|
29
|
-
- [🤝 Contributing](#-contributing)
|
|
30
|
-
- [📄 License](#-license)
|
|
31
|
-
- [🔗 Related Projects](#-related-projects)
|
|
32
|
-
- [📚 Resources](#-resources)
|
|
33
|
-
|
|
34
|
-
## 📦 Installation
|
|
10
|
+
## Install
|
|
35
11
|
|
|
36
12
|
```bash
|
|
37
13
|
npm install @kadi.build/core
|
|
38
14
|
```
|
|
39
15
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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:
|
|
16
|
+
**Requirements:**
|
|
17
|
+
- Node.js 18+
|
|
18
|
+
- TypeScript 5.0+ (recommended for full type inference)
|
|
19
|
+
- ESM only (`"type": "module"` in your package.json)
|
|
49
20
|
|
|
50
|
-
|
|
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
|
-
```
|
|
21
|
+
## Quick Start
|
|
74
22
|
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
```
|
|
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
|
-
);
|
|
23
|
+
Create a simple calculator agent:
|
|
127
24
|
|
|
128
|
-
|
|
129
|
-
|
|
25
|
+
```typescript
|
|
26
|
+
// calculator.ts
|
|
27
|
+
import { KadiClient, z } from '@kadi.build/core';
|
|
130
28
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
language: 'spanish'
|
|
29
|
+
const client = new KadiClient({
|
|
30
|
+
name: 'calculator',
|
|
31
|
+
version: '1.0.0',
|
|
135
32
|
});
|
|
136
|
-
console.log(result);
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
## 🔌 Transport Protocols
|
|
140
33
|
|
|
141
|
-
|
|
34
|
+
client.registerTool({
|
|
35
|
+
name: 'add',
|
|
36
|
+
description: 'Add two numbers',
|
|
37
|
+
input: z.object({ a: z.number(), b: z.number() }),
|
|
38
|
+
}, async ({ a, b }) => ({ result: a + b }));
|
|
142
39
|
|
|
143
|
-
|
|
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');
|
|
40
|
+
// Start listening for requests over stdio
|
|
41
|
+
await client.serve('stdio');
|
|
153
42
|
```
|
|
154
43
|
|
|
155
|
-
|
|
44
|
+
Now another agent can load and use it:
|
|
156
45
|
|
|
157
|
-
|
|
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
|
|
46
|
+
---
|
|
161
47
|
|
|
162
|
-
|
|
163
|
-
// Force stdio protocol
|
|
164
|
-
const ability = await loadAbility('my-ability', 'stdio');
|
|
165
|
-
```
|
|
48
|
+
## Table of Contents
|
|
166
49
|
|
|
167
|
-
|
|
50
|
+
- [Core Concepts](#core-concepts)
|
|
51
|
+
- [Registering Tools](#registering-tools)
|
|
52
|
+
- [Connecting to Brokers](#connecting-to-brokers)
|
|
53
|
+
- [Loading Abilities](#loading-abilities)
|
|
54
|
+
- [Events from Abilities](#events-from-abilities)
|
|
55
|
+
- [Broker Events (Pub/Sub)](#broker-events-pubsub)
|
|
56
|
+
- [Serving as an Ability](#serving-as-an-ability)
|
|
57
|
+
- [Error Handling](#error-handling)
|
|
58
|
+
- [API Reference](#api-reference)
|
|
59
|
+
- [Troubleshooting](#troubleshooting)
|
|
168
60
|
|
|
169
|
-
|
|
170
|
-
KADI_PROTOCOL=stdio # Identifies the protocol being used
|
|
171
|
-
# Plus all parent process.env variables
|
|
172
|
-
```
|
|
61
|
+
---
|
|
173
62
|
|
|
174
|
-
|
|
63
|
+
## Core Concepts
|
|
175
64
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
65
|
+
| Concept | Plain English |
|
|
66
|
+
|---------|---------------|
|
|
67
|
+
| **Tool** | A function other services can call over the network (like an API endpoint) |
|
|
68
|
+
| **Agent** | Your service. One codebase = one agent. Agents expose tools and call other agents' tools. |
|
|
69
|
+
| **Broker** | The router/registry. Agents register with brokers so they can find each other (like DNS + API gateway combined) |
|
|
70
|
+
| **Ability** | A tool provider you connect to. Could be in-process, a subprocess, or a remote service. |
|
|
71
|
+
| **Event** | Fire-and-forget notification. Publish events, other agents can subscribe. |
|
|
180
72
|
|
|
181
|
-
|
|
182
|
-
// Use broker for distributed execution
|
|
183
|
-
const ability = await loadAbility('my-ability', 'broker');
|
|
184
|
-
```
|
|
73
|
+
---
|
|
185
74
|
|
|
186
|
-
|
|
75
|
+
## Registering Tools
|
|
187
76
|
|
|
188
|
-
|
|
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
|
-
```
|
|
77
|
+
Tools are functions you expose for other agents to call.
|
|
195
78
|
|
|
196
|
-
|
|
79
|
+
```typescript
|
|
80
|
+
import { KadiClient, z } from '@kadi.build/core';
|
|
197
81
|
|
|
198
|
-
|
|
82
|
+
const client = new KadiClient({ name: 'weather-service' });
|
|
199
83
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
|
84
|
+
// Define input schema with Zod
|
|
85
|
+
const weatherInput = z.object({
|
|
86
|
+
city: z.string().describe('City name'),
|
|
87
|
+
units: z.enum(['celsius', 'fahrenheit']).default('celsius'),
|
|
207
88
|
});
|
|
208
89
|
|
|
209
|
-
//
|
|
210
|
-
|
|
90
|
+
// Register tool
|
|
91
|
+
client.registerTool({
|
|
92
|
+
name: 'getWeather',
|
|
93
|
+
description: 'Get current weather for a city',
|
|
94
|
+
input: weatherInput,
|
|
95
|
+
}, async ({ city, units }) => {
|
|
96
|
+
const temp = await fetchTemperature(city);
|
|
97
|
+
return { city, temperature: temp, units };
|
|
98
|
+
});
|
|
211
99
|
```
|
|
212
100
|
|
|
213
|
-
|
|
101
|
+
**Why Zod?** Type-safe schemas, 70% less code than JSON Schema, automatic TypeScript inference.
|
|
214
102
|
|
|
215
|
-
|
|
103
|
+
### Targeting Specific Brokers
|
|
216
104
|
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
B -->|Yes| C[Use Specified Protocol]
|
|
221
|
-
B -->|No| D[Default to 'native']
|
|
105
|
+
```typescript
|
|
106
|
+
// Register tool only on specific brokers
|
|
107
|
+
client.registerTool(definition, handler, { brokers: ['internal'] });
|
|
222
108
|
```
|
|
223
109
|
|
|
224
|
-
|
|
110
|
+
---
|
|
225
111
|
|
|
226
|
-
|
|
112
|
+
## Connecting to Brokers
|
|
227
113
|
|
|
228
|
-
|
|
229
|
-
import { KadiClient } from '@kadi.build/core';
|
|
114
|
+
Brokers let agents discover and call each other without knowing URLs.
|
|
230
115
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
protocol: 'stdio', // 'native', 'stdio', or 'broker'
|
|
116
|
+
```typescript
|
|
117
|
+
const client = new KadiClient({
|
|
118
|
+
name: 'my-agent',
|
|
235
119
|
brokers: {
|
|
236
|
-
|
|
237
|
-
|
|
120
|
+
default: 'ws://localhost:8080/kadi',
|
|
121
|
+
production: 'wss://broker.example.com/kadi',
|
|
238
122
|
},
|
|
239
|
-
defaultBroker: '
|
|
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() };
|
|
302
|
-
|
|
303
|
-
await ability.publishEvent('echo.completed', { result });
|
|
304
|
-
return result;
|
|
123
|
+
defaultBroker: 'default',
|
|
124
|
+
networks: ['global'], // Which networks to join
|
|
305
125
|
});
|
|
306
126
|
|
|
307
|
-
//
|
|
308
|
-
|
|
309
|
-
name: 'my-client',
|
|
310
|
-
role: 'agent',
|
|
311
|
-
protocol: 'broker'
|
|
312
|
-
});
|
|
127
|
+
// Connect (uses Ed25519 authentication)
|
|
128
|
+
await client.connect();
|
|
313
129
|
|
|
314
|
-
//
|
|
315
|
-
|
|
316
|
-
console.log('Echo event received:', data);
|
|
317
|
-
});
|
|
130
|
+
// Invoke a tool - broker routes to any available provider
|
|
131
|
+
const result = await client.invokeRemote('add', { a: 5, b: 3 });
|
|
318
132
|
|
|
319
|
-
//
|
|
320
|
-
|
|
321
|
-
ability.events.on('echo.completed', (data) => {
|
|
322
|
-
console.log('Completed:', data);
|
|
323
|
-
});
|
|
133
|
+
// Disconnect when done
|
|
134
|
+
await client.disconnect();
|
|
324
135
|
```
|
|
325
136
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
- Use wildcards: `math.*` matches `math.operation`, `math.milestone`
|
|
329
|
-
- Protocol-specific delivery:
|
|
330
|
-
- **Native**: Direct EventEmitter (synchronous)
|
|
331
|
-
- **Stdio**: JSON-RPC notifications over stdout
|
|
332
|
-
- **Broker**: RabbitMQ pub/sub via WebSocket
|
|
333
|
-
|
|
334
|
-
### Ability Configuration (agent.json)
|
|
137
|
+
### Reconnection
|
|
335
138
|
|
|
336
|
-
|
|
139
|
+
Auto-reconnects with exponential backoff (1s → 2s → 4s → ... → 30s max) with jitter:
|
|
337
140
|
|
|
338
|
-
```
|
|
339
|
-
{
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
"description": "Echo ability with multiple transport support",
|
|
345
|
-
"scripts": {
|
|
346
|
-
"setup": "npm install",
|
|
347
|
-
"start": "node ability.js"
|
|
348
|
-
},
|
|
349
|
-
"exports": [
|
|
350
|
-
{
|
|
351
|
-
"name": "echo",
|
|
352
|
-
"description": "Echo the input message",
|
|
353
|
-
"inputSchema": {
|
|
354
|
-
"type": "object",
|
|
355
|
-
"properties": {
|
|
356
|
-
"message": { "type": "string" }
|
|
357
|
-
},
|
|
358
|
-
"required": ["message"]
|
|
359
|
-
},
|
|
360
|
-
"outputSchema": {
|
|
361
|
-
"type": "object",
|
|
362
|
-
"properties": {
|
|
363
|
-
"echo": { "type": "string" },
|
|
364
|
-
"timestamp": { "type": "string" }
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
],
|
|
369
|
-
"brokers": {
|
|
370
|
-
"local": "ws://localhost:8080",
|
|
371
|
-
"remote": "ws://api.example.com:8080"
|
|
372
|
-
},
|
|
373
|
-
"defaultBroker": "local"
|
|
374
|
-
}
|
|
375
|
-
```
|
|
376
|
-
|
|
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: { /* ... */ }
|
|
141
|
+
```typescript
|
|
142
|
+
const client = new KadiClient({
|
|
143
|
+
name: 'resilient-agent',
|
|
144
|
+
brokers: { default: 'ws://localhost:8080/kadi' },
|
|
145
|
+
autoReconnect: true, // Default: true
|
|
146
|
+
maxReconnectDelay: 30000, // Cap at 30 seconds
|
|
404
147
|
});
|
|
405
|
-
|
|
406
|
-
// Note: If both are defined, inline schemas take precedence over exports
|
|
407
148
|
```
|
|
408
149
|
|
|
409
|
-
|
|
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' });
|
|
150
|
+
---
|
|
423
151
|
|
|
424
|
-
|
|
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
|
-
```
|
|
152
|
+
## Loading Abilities
|
|
431
153
|
|
|
432
|
-
|
|
154
|
+
Three ways to connect to other agents:
|
|
433
155
|
|
|
434
|
-
|
|
435
|
-
|
|
156
|
+
| Method | Use When | How It Works |
|
|
157
|
+
|--------|----------|--------------|
|
|
158
|
+
| `loadNative(name)` | Ability is a local TypeScript/JavaScript module | Runs in your process |
|
|
159
|
+
| `loadStdio(name)` | Ability runs as a separate process (any language) | JSON-RPC over stdin/stdout |
|
|
160
|
+
| `loadBroker(name)` | Ability is a remote service | WebSocket via broker |
|
|
436
161
|
|
|
437
|
-
|
|
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
|
-
});
|
|
162
|
+
### loadNative — In-Process
|
|
450
163
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
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
|
-
);
|
|
164
|
+
```typescript
|
|
165
|
+
// Resolves path from agent-lock.json
|
|
166
|
+
const calc = await client.loadNative('calculator');
|
|
466
167
|
|
|
467
|
-
//
|
|
468
|
-
await client.
|
|
168
|
+
// Or specify path directly
|
|
169
|
+
const calc = await client.loadNative('calculator', { path: './abilities/calc' });
|
|
469
170
|
|
|
470
|
-
//
|
|
471
|
-
const result = await
|
|
472
|
-
|
|
473
|
-
to: 'es'
|
|
474
|
-
});
|
|
171
|
+
// Use it
|
|
172
|
+
const result = await calc.invoke('add', { a: 5, b: 3 });
|
|
173
|
+
console.log(result); // { result: 8 }
|
|
475
174
|
|
|
476
|
-
//
|
|
477
|
-
|
|
175
|
+
// Cleanup (no-op for native, but good practice)
|
|
176
|
+
await calc.disconnect();
|
|
478
177
|
```
|
|
479
178
|
|
|
480
|
-
|
|
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
|
-
```
|
|
179
|
+
### loadStdio — Child Process
|
|
549
180
|
|
|
550
|
-
|
|
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
|
-
```
|
|
181
|
+
By default, launches the ability using `scripts.start` from the ability's `agent.json`:
|
|
615
182
|
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
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
|
-
```
|
|
183
|
+
```typescript
|
|
184
|
+
// Runs scripts.start from ability's agent.json
|
|
185
|
+
const processor = await client.loadStdio('image-processor');
|
|
670
186
|
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
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
|
-
```
|
|
187
|
+
// Use it
|
|
188
|
+
const result = await processor.invoke('resize', { width: 800 });
|
|
189
|
+
console.log(result);
|
|
728
190
|
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
How KadiClient handles stdio communication with LSP-style framing:
|
|
732
|
-
|
|
733
|
-
```mermaid
|
|
734
|
-
sequenceDiagram
|
|
735
|
-
participant Parent as Parent Process
|
|
736
|
-
participant SR as StdioFrameReader
|
|
737
|
-
participant SW as StdioFrameWriter
|
|
738
|
-
participant Server as StdioRpcServer
|
|
739
|
-
participant Ability as KadiClient
|
|
740
|
-
|
|
741
|
-
Note over Parent,SR: Incoming Request
|
|
742
|
-
Parent->>SR: Write to stdin: "Content-Length: 52\r\n\r\n{...}"
|
|
743
|
-
SR->>SR: Buffer incoming data
|
|
744
|
-
SR->>SR: Look for Content-Length header
|
|
745
|
-
SR->>SR: Parse header, extract body length
|
|
746
|
-
SR->>SR: Wait for complete body
|
|
747
|
-
SR->>SR: Parse JSON from body
|
|
748
|
-
SR->>Server: onMessage({ success: true, data: request })
|
|
749
|
-
|
|
750
|
-
Server->>Server: Validate JSON-RPC format
|
|
751
|
-
Server->>Ability: getMethodHandler(request.method)
|
|
752
|
-
Ability-->>Server: Return handler
|
|
753
|
-
Server->>Server: Execute handler(params) with timeout
|
|
754
|
-
|
|
755
|
-
Note over Server,Parent: Response
|
|
756
|
-
Server->>SW: write(response)
|
|
757
|
-
SW->>SW: JSON.stringify(response)
|
|
758
|
-
SW->>SW: Calculate byte length
|
|
759
|
-
SW->>SW: Create header: "Content-Length: N\r\n\r\n"
|
|
760
|
-
SW->>Parent: Write header + body to stdout
|
|
761
|
-
|
|
762
|
-
Note over SR: Error Recovery
|
|
763
|
-
alt Frame Corruption
|
|
764
|
-
SR->>SR: Detect invalid frame
|
|
765
|
-
SR->>SR: Search for next Content-Length marker
|
|
766
|
-
SR->>SR: Skip corrupted bytes
|
|
767
|
-
SR->>Server: onMessage({ success: false, error: 'FRAME_CORRUPTION' })
|
|
768
|
-
Server->>Server: Log error, continue processing
|
|
769
|
-
end
|
|
191
|
+
// Cleanup (terminates child process)
|
|
192
|
+
await processor.disconnect();
|
|
770
193
|
```
|
|
771
194
|
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
### Basic Loading
|
|
775
|
-
|
|
776
|
-
```javascript
|
|
777
|
-
import { loadAbility } from '@kadi.build/core';
|
|
195
|
+
You can specify a different script to launch:
|
|
778
196
|
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
// Explicitly specify protocol
|
|
783
|
-
const stdioAbility = await loadAbility('my-ability', 'stdio');
|
|
784
|
-
const brokerAbility = await loadAbility('my-ability', 'broker');
|
|
785
|
-
const nativeAbility = await loadAbility('my-ability', 'native');
|
|
197
|
+
```typescript
|
|
198
|
+
// Run scripts.dev instead of scripts.start
|
|
199
|
+
const processor = await client.loadStdio('image-processor', { script: 'dev' });
|
|
786
200
|
```
|
|
787
201
|
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
```javascript
|
|
791
|
-
// Call methods directly - no need to discover them first
|
|
792
|
-
const result = await ability.echo({ message: 'hello' });
|
|
793
|
-
|
|
794
|
-
// Call methods
|
|
795
|
-
const result = await ability.someMethod({ param: 'value' });
|
|
796
|
-
|
|
797
|
-
// Direct RPC call (bypasses method validation)
|
|
798
|
-
const response = await ability.__call('someMethod', { param: 'value' });
|
|
202
|
+
Or bypass `agent.json` entirely with an explicit command:
|
|
799
203
|
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
204
|
+
```typescript
|
|
205
|
+
// Explicit command (ignores agent.json)
|
|
206
|
+
const processor = await client.loadStdio('processor', {
|
|
207
|
+
command: 'python3',
|
|
208
|
+
args: ['main.py'],
|
|
803
209
|
});
|
|
804
|
-
|
|
805
|
-
// Note: Events work for all protocols (native, stdio, and broker)
|
|
806
|
-
// Subscribe before calling methods that emit events
|
|
807
210
|
```
|
|
808
211
|
|
|
809
|
-
###
|
|
810
|
-
|
|
811
|
-
KADI uses “transport” in two places and they serve different purposes:
|
|
812
|
-
|
|
813
|
-
- KadiClient transport — how this client exposes its own tools after `registerTool(...)` and `serve()`.
|
|
814
|
-
- `transport: 'native'` — in‑process only. There is no network or stdio listener. Code that loads you natively (via `loadAbility('you','native')`) can call your tools directly. `serve()` simply keeps the process alive if you want a long‑running ability.
|
|
815
|
-
- `transport: 'stdio'` — the client runs a stdio server (JSON‑RPC over stdin/stdout) and answers requests with your registered handlers. `serve()` wires up the stdio loop.
|
|
816
|
-
- `transport: 'broker'` — the client connects to the broker, registers your tools, and receives remote invocations over WebSocket. `serve()` (or `connectToBrokers()`) handles connection and registration.
|
|
817
|
-
|
|
818
|
-
- loadAbility transport — how this client connects to a specific ability it wants to use.
|
|
819
|
-
- `'native'` loads a module in‑process
|
|
820
|
-
- `'stdio'` talks to a spawned child process (uses the ability’s `agent.json` `scripts.start`)
|
|
821
|
-
- `'broker'` talks to a remote ability over the broker
|
|
822
|
-
|
|
823
|
-
These two choices are independent: setting the client’s `transport` does not change how you load another ability, and the transport you choose in `loadAbility(...)` does not change how your client serves its own tools.
|
|
824
|
-
|
|
825
|
-
Examples
|
|
826
|
-
|
|
827
|
-
Provide tools over broker
|
|
212
|
+
### loadBroker — Remote via Broker
|
|
828
213
|
|
|
829
|
-
```
|
|
830
|
-
|
|
214
|
+
```typescript
|
|
215
|
+
// Must be connected to broker first
|
|
216
|
+
await client.connect();
|
|
831
217
|
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
transport: 'broker',
|
|
836
|
-
brokers: { local: 'ws://localhost:8080' },
|
|
837
|
-
defaultBroker: 'local'
|
|
218
|
+
// Find and connect to a remote ability
|
|
219
|
+
const gpu = await client.loadBroker('gpu-service', {
|
|
220
|
+
networks: ['gpu-cluster'],
|
|
838
221
|
});
|
|
839
222
|
|
|
840
|
-
|
|
841
|
-
await
|
|
223
|
+
const result = await gpu.invoke('inference', { model: 'llama3' });
|
|
224
|
+
await gpu.disconnect();
|
|
842
225
|
```
|
|
843
226
|
|
|
844
|
-
|
|
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
|
-
});
|
|
227
|
+
---
|
|
866
228
|
|
|
867
|
-
|
|
868
|
-
networks: ['global']
|
|
869
|
-
});
|
|
870
|
-
const r = await echo.say_message({ message: 'hi' });
|
|
871
|
-
```
|
|
229
|
+
## Events from Abilities
|
|
872
230
|
|
|
873
|
-
|
|
231
|
+
Abilities can emit real-time events. Subscribe with `on()`, unsubscribe with `off()`.
|
|
874
232
|
|
|
875
|
-
|
|
233
|
+
```typescript
|
|
234
|
+
const watcher = await client.loadStdio('file-watcher');
|
|
876
235
|
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
236
|
+
// Define handlers (need references to unsubscribe later)
|
|
237
|
+
const onFileChanged = (data) => {
|
|
238
|
+
console.log('File changed:', data.path, data.action);
|
|
239
|
+
};
|
|
880
240
|
|
|
881
|
-
|
|
241
|
+
const onError = (data) => {
|
|
242
|
+
console.error('Watch error:', data.message);
|
|
243
|
+
};
|
|
882
244
|
|
|
883
|
-
|
|
245
|
+
// Subscribe to events
|
|
246
|
+
watcher.on('file.changed', onFileChanged);
|
|
247
|
+
watcher.on('file.error', onError);
|
|
884
248
|
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
export KADI_PROTOCOL=stdio
|
|
249
|
+
// Later, unsubscribe
|
|
250
|
+
watcher.off('file.changed', onFileChanged);
|
|
888
251
|
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
export KADI_ABILITY_NAME=my-ability
|
|
892
|
-
export KADI_AGENT_SCOPE=project-123
|
|
252
|
+
// Cleanup (terminates child process)
|
|
253
|
+
await watcher.disconnect();
|
|
893
254
|
```
|
|
894
255
|
|
|
895
|
-
###
|
|
896
|
-
|
|
897
|
-
When abilities are spawned as child processes (stdio and broker protocols), the parent process passes down important environment variables:
|
|
256
|
+
### Emitting Events (from inside an ability)
|
|
898
257
|
|
|
899
|
-
|
|
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;
|
|
258
|
+
If you're building an ability that emits events:
|
|
905
259
|
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
// Ability automatically uses KADI_PROTOCOL to determine transport
|
|
911
|
-
});
|
|
260
|
+
```typescript
|
|
261
|
+
// Inside your ability code
|
|
262
|
+
client.emit('job.progress', { percent: 50, message: 'Halfway done' });
|
|
263
|
+
client.emit('file.changed', { path: '/tmp/foo.txt', action: 'modified' });
|
|
912
264
|
```
|
|
913
265
|
|
|
914
|
-
|
|
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
|
|
266
|
+
---
|
|
931
267
|
|
|
932
|
-
|
|
268
|
+
## Broker Events (Pub/Sub)
|
|
933
269
|
|
|
934
|
-
|
|
270
|
+
When connected to a broker, agents can publish and subscribe to events across the network.
|
|
935
271
|
|
|
936
|
-
|
|
272
|
+
### Subscribing
|
|
937
273
|
|
|
938
|
-
|
|
274
|
+
```typescript
|
|
275
|
+
await client.connect();
|
|
939
276
|
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
//
|
|
944
|
-
|
|
945
|
-
console.log(`
|
|
277
|
+
// Subscribe to patterns (RabbitMQ topic exchange style)
|
|
278
|
+
client.subscribe('user.*', (event) => {
|
|
279
|
+
console.log(`Channel: ${event.channel}`); // 'user.login'
|
|
280
|
+
console.log(`Data:`, event.data); // { userId: '123' }
|
|
281
|
+
console.log(`Network: ${event.networkId}`); // 'global'
|
|
282
|
+
console.log(`Source: ${event.source}`); // Publisher's session ID
|
|
946
283
|
});
|
|
284
|
+
```
|
|
947
285
|
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
286
|
+
**Pattern matching:**
|
|
287
|
+
- `user.*` → matches `user.login`, `user.logout` (exactly one word after dot)
|
|
288
|
+
- `user.#` → matches `user`, `user.login`, `user.profile.update` (zero or more words)
|
|
289
|
+
- `order.new` → matches exactly `order.new`
|
|
951
290
|
|
|
952
|
-
|
|
953
|
-
client.on('tool:invoked', ({ toolName, params }) => {
|
|
954
|
-
console.log(`Tool ${toolName} invoked with:`, params);
|
|
955
|
-
});
|
|
291
|
+
### Publishing
|
|
956
292
|
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
});
|
|
293
|
+
```typescript
|
|
294
|
+
// Publish to default network
|
|
295
|
+
await client.publish('user.login', { userId: '123', timestamp: Date.now() });
|
|
960
296
|
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
});
|
|
297
|
+
// Publish to specific network
|
|
298
|
+
await client.publish('order.created', orderData, { network: 'internal' });
|
|
964
299
|
```
|
|
965
300
|
|
|
966
|
-
|
|
301
|
+
### Unsubscribing
|
|
967
302
|
|
|
968
|
-
|
|
303
|
+
```typescript
|
|
304
|
+
const handler = (event) => console.log(event);
|
|
305
|
+
client.subscribe('user.*', handler);
|
|
969
306
|
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
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
|
-
});
|
|
307
|
+
// Later, remove this specific handler
|
|
308
|
+
client.unsubscribe('user.*', handler);
|
|
309
|
+
```
|
|
984
310
|
|
|
985
|
-
|
|
986
|
-
const result = await processData(data);
|
|
311
|
+
---
|
|
987
312
|
|
|
988
|
-
|
|
989
|
-
timestamp: Date.now(),
|
|
990
|
-
result
|
|
991
|
-
});
|
|
313
|
+
## Serving as an Ability
|
|
992
314
|
|
|
993
|
-
|
|
994
|
-
});
|
|
315
|
+
To make your agent callable by others:
|
|
995
316
|
|
|
996
|
-
|
|
997
|
-
const subscriber = new KadiClient({
|
|
998
|
-
name: 'event-subscriber',
|
|
999
|
-
role: 'agent',
|
|
1000
|
-
protocol: 'broker'
|
|
1001
|
-
});
|
|
317
|
+
### Stdio Mode (for loadStdio)
|
|
1002
318
|
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
console.log('Process event:', data);
|
|
1006
|
-
});
|
|
319
|
+
```typescript
|
|
320
|
+
const client = new KadiClient({ name: 'my-ability' });
|
|
1007
321
|
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
322
|
+
client.registerTool({
|
|
323
|
+
name: 'process',
|
|
324
|
+
description: 'Process data',
|
|
325
|
+
input: z.object({ data: z.string() }),
|
|
326
|
+
}, async ({ data }) => {
|
|
327
|
+
return { processed: data.toUpperCase() };
|
|
1011
328
|
});
|
|
1012
329
|
|
|
1013
|
-
//
|
|
1014
|
-
|
|
1015
|
-
console.log(`Event from ${pattern}:`, data);
|
|
1016
|
-
});
|
|
330
|
+
// Serve over stdio - blocks until SIGTERM/SIGINT
|
|
331
|
+
await client.serve('stdio');
|
|
1017
332
|
```
|
|
1018
333
|
|
|
1019
|
-
|
|
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
|
|
334
|
+
### Broker Mode (for loadBroker)
|
|
1026
335
|
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
```javascript
|
|
336
|
+
```typescript
|
|
1030
337
|
const client = new KadiClient({
|
|
1031
|
-
name: '
|
|
1032
|
-
|
|
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']
|
|
338
|
+
name: 'my-service',
|
|
339
|
+
brokers: { default: 'ws://localhost:8080/kadi' },
|
|
1041
340
|
});
|
|
1042
341
|
|
|
1043
|
-
|
|
1044
|
-
await client.connectToBrokers();
|
|
342
|
+
client.registerTool({ /* ... */ }, handler);
|
|
1045
343
|
|
|
1046
|
-
//
|
|
1047
|
-
|
|
1048
|
-
// 2. Receive messages from any broker
|
|
1049
|
-
// 3. Send messages to the default broker unless specified
|
|
1050
|
-
// 4. Automatically handle broker failover
|
|
344
|
+
// Connect to broker and wait for requests
|
|
345
|
+
await client.serve('broker');
|
|
1051
346
|
```
|
|
1052
347
|
|
|
1053
|
-
|
|
348
|
+
---
|
|
1054
349
|
|
|
1055
|
-
|
|
350
|
+
## Error Handling
|
|
1056
351
|
|
|
1057
|
-
|
|
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
|
-
}
|
|
352
|
+
All errors are `KadiError` with structured codes and context:
|
|
1066
353
|
|
|
1067
|
-
|
|
1068
|
-
{
|
|
1069
|
-
"name": "ml-processor",
|
|
1070
|
-
"scripts": {
|
|
1071
|
-
"start": "python3 main.py"
|
|
1072
|
-
}
|
|
1073
|
-
}
|
|
354
|
+
```typescript
|
|
355
|
+
import { KadiError } from '@kadi.build/core';
|
|
1074
356
|
|
|
1075
|
-
|
|
1076
|
-
{
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
357
|
+
try {
|
|
358
|
+
await client.invokeRemote('unknown-tool', {});
|
|
359
|
+
} catch (error) {
|
|
360
|
+
if (KadiError.isKadiError(error)) {
|
|
361
|
+
console.log(error.code); // 'TOOL_NOT_FOUND'
|
|
362
|
+
console.log(error.message); // Human-readable message
|
|
363
|
+
console.log(error.context); // { toolName: '...', hint: '...' }
|
|
1081
364
|
}
|
|
1082
365
|
}
|
|
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
366
|
```
|
|
1088
367
|
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
### Testing Local Changes
|
|
368
|
+
### Error Codes
|
|
1092
369
|
|
|
1093
|
-
|
|
370
|
+
| Code | When It Happens |
|
|
371
|
+
|------|-----------------|
|
|
372
|
+
| `TOOL_NOT_FOUND` | `invoke()` or `invokeRemote()` with unknown tool name |
|
|
373
|
+
| `BROKER_NOT_CONNECTED` | Any broker operation before calling `connect()` |
|
|
374
|
+
| `ABILITY_NOT_FOUND` | `loadNative/loadStdio` when ability not in agent-lock.json |
|
|
375
|
+
| `ABILITY_LOAD_FAILED` | Ability exists but failed to start or initialize |
|
|
376
|
+
| `TOOL_INVOCATION_FAILED` | Tool threw an error during execution |
|
|
377
|
+
| `BROKER_TIMEOUT` | Request to broker timed out |
|
|
378
|
+
| `INVALID_CONFIG` | Configuration error (missing required fields, etc.) |
|
|
379
|
+
| `LOCKFILE_NOT_FOUND` | `loadNative/loadStdio` when agent-lock.json doesn't exist |
|
|
1094
380
|
|
|
1095
|
-
|
|
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:
|
|
381
|
+
---
|
|
1117
382
|
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
383
|
+
## API Reference
|
|
384
|
+
|
|
385
|
+
### KadiClient
|
|
386
|
+
|
|
387
|
+
```typescript
|
|
388
|
+
new KadiClient(config: ClientConfig)
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
**Configuration:**
|
|
392
|
+
|
|
393
|
+
| Option | Type | Default | Description |
|
|
394
|
+
|--------|------|---------|-------------|
|
|
395
|
+
| `name` | `string` | required | Agent name |
|
|
396
|
+
| `version` | `string` | `'1.0.0'` | Agent version |
|
|
397
|
+
| `brokers` | `Record<string, string>` | `{}` | Broker name → URL map |
|
|
398
|
+
| `defaultBroker` | `string` | first broker | Default broker to use |
|
|
399
|
+
| `networks` | `string[]` | `['global']` | Networks to join |
|
|
400
|
+
| `autoReconnect` | `boolean` | `true` | Auto-reconnect on disconnect |
|
|
401
|
+
| `maxReconnectDelay` | `number` | `30000` | Max backoff delay (ms) |
|
|
402
|
+
| `requestTimeout` | `number` | `30000` | Default request timeout (ms) |
|
|
403
|
+
|
|
404
|
+
**Methods:**
|
|
405
|
+
|
|
406
|
+
| Method | Description |
|
|
407
|
+
|--------|-------------|
|
|
408
|
+
| `registerTool(def, handler, options?)` | Register a tool. See [Registering Tools](#registering-tools) |
|
|
409
|
+
| `connect(broker?)` | Connect to broker |
|
|
410
|
+
| `disconnect(broker?)` | Disconnect from broker |
|
|
411
|
+
| `invokeRemote(tool, params, options?)` | Invoke tool via broker (broker picks provider) |
|
|
412
|
+
| `loadNative(name, options?)` | Load ability in-process. See [Loading Abilities](#loading-abilities) |
|
|
413
|
+
| `loadStdio(name, options?)` | Load ability as child process |
|
|
414
|
+
| `loadBroker(name, options?)` | Load ability via broker |
|
|
415
|
+
| `subscribe(pattern, handler, options?)` | Subscribe to broker events. See [Broker Events](#broker-events-pubsub) |
|
|
416
|
+
| `unsubscribe(pattern, handler, options?)` | Unsubscribe from events |
|
|
417
|
+
| `publish(channel, data, options?)` | Publish event to broker |
|
|
418
|
+
| `emit(event, data)` | Emit event to consumer (when serving as ability) |
|
|
419
|
+
| `serve(mode)` | Serve as ability (`'stdio'` or `'broker'`) |
|
|
420
|
+
| `getConnectedBrokers()` | List connected broker names |
|
|
421
|
+
|
|
422
|
+
### LoadedAbility
|
|
423
|
+
|
|
424
|
+
Returned by `loadNative()`, `loadStdio()`, `loadBroker()`:
|
|
425
|
+
|
|
426
|
+
| Property/Method | Description |
|
|
427
|
+
|-----------------|-------------|
|
|
428
|
+
| `name` | Ability name |
|
|
429
|
+
| `transport` | `'native'` \| `'stdio'` \| `'broker'` |
|
|
430
|
+
| `invoke(tool, params)` | Invoke a tool |
|
|
431
|
+
| `getTools()` | Get array of tool definitions |
|
|
432
|
+
| `on(event, handler)` | Subscribe to events |
|
|
433
|
+
| `off(event, handler)` | Unsubscribe from events |
|
|
434
|
+
| `disconnect()` | Cleanup resources |
|
|
435
|
+
|
|
436
|
+
### BrokerEvent
|
|
437
|
+
|
|
438
|
+
Received when subscribed to broker events:
|
|
439
|
+
|
|
440
|
+
```typescript
|
|
441
|
+
interface BrokerEvent {
|
|
442
|
+
channel: string; // 'user.login'
|
|
443
|
+
data: unknown; // Event payload
|
|
444
|
+
networkId: string; // Which network
|
|
445
|
+
source: string; // Publisher's session ID
|
|
446
|
+
timestamp: number; // Unix timestamp (ms)
|
|
1123
447
|
}
|
|
1124
448
|
```
|
|
1125
449
|
|
|
1126
|
-
|
|
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)
|
|
1161
|
-
|
|
1162
|
-
**Tool Registration Methods:**
|
|
1163
|
-
|
|
1164
|
-
- `registerTool(name, handler, schema?)` - Register a single tool
|
|
1165
|
-
- `getTools()` - Get list of registered tool names
|
|
1166
|
-
- `getToolNames()` - Get filtered list of tool names
|
|
1167
|
-
- `getToolHandler(name)` - Get a specific tool's handler
|
|
1168
|
-
- `getToolSchema(name)` - Get a specific tool's schema
|
|
1169
|
-
- `hasTool(name)` - Check if a tool is registered
|
|
1170
|
-
|
|
1171
|
-
**Remote Tool Invocation:**
|
|
1172
|
-
|
|
1173
|
-
- `callTool(targetAgent, toolName, params)` - Call a remote tool via broker
|
|
1174
|
-
- `discoverRemoteTools(targetAgent)` - List tools from a remote agent
|
|
1175
|
-
- `loadAbility(name, protocol?, options?)` - Load an ability (compatibility)
|
|
450
|
+
---
|
|
1176
451
|
|
|
1177
|
-
|
|
452
|
+
## Troubleshooting
|
|
1178
453
|
|
|
1179
|
-
|
|
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
|
|
454
|
+
### "Cannot find module" with loadNative/loadStdio
|
|
1184
455
|
|
|
1185
|
-
|
|
456
|
+
The ability isn't in your `agent-lock.json`. Either:
|
|
457
|
+
- Run `kadi install` to install abilities and generate the lock file
|
|
458
|
+
- Use the `path` option: `loadNative('calc', { path: './path/to/ability' })`
|
|
459
|
+
- Use explicit command: `loadStdio('calc', { command: 'node', args: ['calc.js'] })`
|
|
1186
460
|
|
|
1187
|
-
|
|
1188
|
-
- `connectToBrokers()` - Connect to all configured brokers
|
|
1189
|
-
- `disconnect()` - Clean disconnect
|
|
1190
|
-
- `isConnected` - Check connection status
|
|
461
|
+
### Connection timeout to broker
|
|
1191
462
|
|
|
1192
|
-
|
|
463
|
+
1. Check the broker is running
|
|
464
|
+
2. Verify the URL uses `ws://` or `wss://`, not `http://`
|
|
465
|
+
3. Check firewall/network allows WebSocket connections
|
|
1193
466
|
|
|
1194
|
-
|
|
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
|
|
467
|
+
### Events not being received
|
|
1201
468
|
|
|
1202
|
-
|
|
469
|
+
1. Verify both publisher and subscriber are on the same network (check `networks` config)
|
|
470
|
+
2. Check your pattern matches the channel (remember: `*` = one word, `#` = zero or more)
|
|
471
|
+
3. Ensure you're connected before subscribing: `await client.connect()`
|
|
1203
472
|
|
|
1204
|
-
|
|
1205
|
-
import { KadiClient } from '@kadi.build/core';
|
|
473
|
+
### Tool invocation times out
|
|
1206
474
|
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
protocol: 'broker',
|
|
1212
|
-
brokers: {
|
|
1213
|
-
local: 'ws://localhost:8080',
|
|
1214
|
-
cloud: 'wss://api.example.com'
|
|
1215
|
-
},
|
|
1216
|
-
defaultBroker: 'local',
|
|
1217
|
-
networks: ['global']
|
|
1218
|
-
});
|
|
1219
|
-
|
|
1220
|
-
// Register tools with schemas
|
|
1221
|
-
ability.registerTool(
|
|
1222
|
-
'add',
|
|
1223
|
-
async ({ a, b }) => {
|
|
1224
|
-
// Publish event before processing
|
|
1225
|
-
await ability.publishEvent('math.operation', {
|
|
1226
|
-
operation: 'add',
|
|
1227
|
-
inputs: { a, b }
|
|
1228
|
-
});
|
|
1229
|
-
|
|
1230
|
-
const result = a + b;
|
|
1231
|
-
|
|
1232
|
-
// Publish completion event
|
|
1233
|
-
await ability.publishEvent('math.completed', {
|
|
1234
|
-
operation: 'add',
|
|
1235
|
-
result
|
|
1236
|
-
});
|
|
1237
|
-
|
|
1238
|
-
return { result };
|
|
1239
|
-
},
|
|
1240
|
-
{
|
|
1241
|
-
description: 'Add two numbers',
|
|
1242
|
-
inputSchema: {
|
|
1243
|
-
type: 'object',
|
|
1244
|
-
properties: {
|
|
1245
|
-
a: { type: 'number', description: 'First number' },
|
|
1246
|
-
b: { type: 'number', description: 'Second number' }
|
|
1247
|
-
},
|
|
1248
|
-
required: ['a', 'b']
|
|
1249
|
-
},
|
|
1250
|
-
outputSchema: {
|
|
1251
|
-
type: 'object',
|
|
1252
|
-
properties: {
|
|
1253
|
-
result: { type: 'number', description: 'Sum of a and b' }
|
|
1254
|
-
}
|
|
1255
|
-
}
|
|
1256
|
-
}
|
|
1257
|
-
);
|
|
1258
|
-
|
|
1259
|
-
// Subscribe to events from other abilities
|
|
1260
|
-
ability.subscribeToEvent('system.*', (data) => {
|
|
1261
|
-
console.log('System event:', data);
|
|
1262
|
-
});
|
|
475
|
+
Increase the timeout:
|
|
476
|
+
```typescript
|
|
477
|
+
await client.invokeRemote('slow-tool', params, { timeout: 60000 });
|
|
478
|
+
```
|
|
1263
479
|
|
|
1264
|
-
|
|
1265
|
-
await ability.connectToBrokers();
|
|
480
|
+
---
|
|
1266
481
|
|
|
1267
|
-
|
|
1268
|
-
const result = await ability.callTool('translator-ability', 'translate', {
|
|
1269
|
-
text: 'Hello',
|
|
1270
|
-
to: 'es'
|
|
1271
|
-
});
|
|
482
|
+
## Advanced: Building CLI Tools
|
|
1272
483
|
|
|
1273
|
-
|
|
1274
|
-
process.on('SIGTERM', async () => {
|
|
1275
|
-
await ability.disconnect();
|
|
1276
|
-
process.exit(0);
|
|
1277
|
-
});
|
|
1278
|
-
```
|
|
484
|
+
These utilities are exported for building tooling on top of kadi-core.
|
|
1279
485
|
|
|
1280
|
-
|
|
486
|
+
### Zod to JSON Schema
|
|
1281
487
|
|
|
1282
|
-
|
|
488
|
+
Convert Zod schemas to JSON Schema (useful for generating documentation or OpenAPI specs):
|
|
1283
489
|
|
|
1284
|
-
```
|
|
1285
|
-
import {
|
|
490
|
+
```typescript
|
|
491
|
+
import { zodToJsonSchema, z } from '@kadi.build/core';
|
|
1286
492
|
|
|
1287
|
-
const
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
networks: ['global'], // Network segments
|
|
1291
|
-
existingClient: client // Reuse existing KadiClient for broker
|
|
1292
|
-
});
|
|
493
|
+
const schema = z.object({ name: z.string(), age: z.number() });
|
|
494
|
+
const jsonSchema = zodToJsonSchema(schema);
|
|
495
|
+
// { type: 'object', properties: { name: { type: 'string' }, ... } }
|
|
1293
496
|
```
|
|
1294
497
|
|
|
1295
|
-
|
|
1296
|
-
|
|
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
|
|
498
|
+
### Lock File Resolution
|
|
1301
499
|
|
|
1302
|
-
|
|
500
|
+
Utilities for working with `agent-lock.json` (the file that tracks installed abilities):
|
|
1303
501
|
|
|
1304
|
-
```
|
|
502
|
+
```typescript
|
|
1305
503
|
import {
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
getBrokerUrl,
|
|
1311
|
-
runExecCommand
|
|
504
|
+
findProjectRoot,
|
|
505
|
+
readLockFile,
|
|
506
|
+
resolveAbilityPath,
|
|
507
|
+
getInstalledAbilityNames,
|
|
1312
508
|
} from '@kadi.build/core';
|
|
1313
|
-
```
|
|
1314
|
-
|
|
1315
|
-
### Debug Mode
|
|
1316
|
-
|
|
1317
|
-
Enable detailed logging:
|
|
1318
|
-
|
|
1319
|
-
```bash
|
|
1320
|
-
DEBUG=kadi:* node index.js
|
|
1321
|
-
```
|
|
1322
|
-
|
|
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
|
|
1332
|
-
|
|
1333
|
-
```javascript
|
|
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
|
-
});
|
|
1356
|
-
```
|
|
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
509
|
|
|
1414
|
-
|
|
510
|
+
// Find the nearest directory containing agent-lock.json
|
|
511
|
+
const root = findProjectRoot();
|
|
1415
512
|
|
|
1416
|
-
|
|
513
|
+
// Read and parse the lock file
|
|
514
|
+
const lock = readLockFile(root);
|
|
1417
515
|
|
|
1418
|
-
|
|
516
|
+
// List installed ability names
|
|
517
|
+
const abilities = getInstalledAbilityNames(lock);
|
|
518
|
+
// ['calculator', 'image-processor', ...]
|
|
1419
519
|
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
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
|
-
);
|
|
520
|
+
// Get the filesystem path to an ability
|
|
521
|
+
const calcPath = resolveAbilityPath('calculator', root);
|
|
522
|
+
// '/path/to/project/abilities/calculator@1.0.0'
|
|
1464
523
|
```
|
|
1465
524
|
|
|
1466
|
-
|
|
525
|
+
---
|
|
1467
526
|
|
|
1468
|
-
|
|
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.
|
|
527
|
+
## Related
|
|
1472
528
|
|
|
1473
|
-
- [
|
|
1474
|
-
- [
|
|
1475
|
-
- [Examples Repository](https://github.com/kadi-examples) - More examples and patterns
|
|
529
|
+
- [kadi-core-py](https://gitlab.com/humin-game-lab/kadi/kadi-core-py) — Python SDK (mirrors this API)
|
|
530
|
+
- [kadi-broker](https://gitlab.com/humin-game-lab/kadi/kadi-broker) — The broker server
|
|
1476
531
|
|
|
1477
532
|
---
|
|
1478
533
|
|
|
1479
|
-
|
|
534
|
+
## License
|
|
535
|
+
|
|
536
|
+
MIT
|