@kadi.build/core 0.0.1-alpha.2 → 0.0.1-alpha.3
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 +1145 -216
- package/examples/example-abilities/echo-js/README.md +131 -0
- package/examples/example-abilities/echo-js/agent.json +63 -0
- package/examples/example-abilities/echo-js/package.json +24 -0
- package/examples/example-abilities/echo-js/service.js +43 -0
- package/examples/example-abilities/hash-go/agent.json +53 -0
- package/examples/example-abilities/hash-go/cmd/hash_ability/main.go +340 -0
- package/examples/example-abilities/hash-go/go.mod +3 -0
- package/examples/example-agent/abilities/echo-js/0.0.1/README.md +131 -0
- package/examples/example-agent/abilities/echo-js/0.0.1/agent.json +63 -0
- package/examples/example-agent/abilities/echo-js/0.0.1/package-lock.json +93 -0
- package/examples/example-agent/abilities/echo-js/0.0.1/package.json +24 -0
- package/examples/example-agent/abilities/echo-js/0.0.1/service.js +41 -0
- package/examples/example-agent/abilities/hash-go/0.0.1/agent.json +53 -0
- package/examples/example-agent/abilities/hash-go/0.0.1/bin/hash_ability +0 -0
- package/examples/example-agent/abilities/hash-go/0.0.1/cmd/hash_ability/main.go +340 -0
- package/examples/example-agent/abilities/hash-go/0.0.1/go.mod +3 -0
- package/examples/example-agent/agent.json +39 -0
- package/examples/example-agent/index.js +102 -0
- package/examples/example-agent/package-lock.json +93 -0
- package/examples/example-agent/package.json +17 -0
- package/package.json +4 -2
- package/src/KadiAbility.js +478 -0
- package/src/index.js +65 -0
- package/src/loadAbility.js +1086 -0
- package/src/servers/BaseRpcServer.js +404 -0
- package/src/servers/BrokerRpcServer.js +776 -0
- package/src/servers/StdioRpcServer.js +360 -0
- package/src/transport/BrokerMessageBuilder.js +377 -0
- package/src/transport/IpcMessageBuilder.js +1229 -0
- package/src/utils/agentUtils.js +137 -0
- package/src/utils/commandUtils.js +64 -0
- package/src/utils/configUtils.js +72 -0
- package/src/utils/logger.js +161 -0
- package/src/utils/pathUtils.js +86 -0
- package/broker.js +0 -214
- package/index.js +0 -382
- package/ipc.js +0 -220
- package/ipcInterfaces/pythonAbilityIPC.py +0 -177
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
// BrokerMessageBuilder.js
|
|
2
|
+
// ESM module
|
|
3
|
+
// A tiny, zero‑dependency helper to build JSON‑RPC messages that "speak"
|
|
4
|
+
// to the broker. Method names default to the current broker handlers but
|
|
5
|
+
// can be reconfigured if/when you rename them.
|
|
6
|
+
//
|
|
7
|
+
// Public API:
|
|
8
|
+
// - IdFactory, newRequestId
|
|
9
|
+
// - toBase64Der (SPKI) + internal signing helper for authenticate
|
|
10
|
+
// - Broker (hello/auth/register/ping/list/callAbility/abilityResult/agentAbility)
|
|
11
|
+
// - MCP (subset; optional for the WS <-> stdio bridge)
|
|
12
|
+
// - configureMethods(overrides) to swap JSON‑RPC method strings
|
|
13
|
+
//
|
|
14
|
+
// Example:
|
|
15
|
+
// import BrokerMessageBuilder, { Broker, IdFactory } from './BrokerMessageBuilder.js';
|
|
16
|
+
// const ids = new IdFactory();
|
|
17
|
+
// ws.send(Broker.hello({ role: 'agent' }).id(ids.next()).toString());
|
|
18
|
+
// ws.send(Broker.authenticate({...}).id(ids.next()).toString());
|
|
19
|
+
// ws.send(Broker.registerCapabilities({...}).id(ids.next()).toString());
|
|
20
|
+
// ws.send(Broker.callAbility({ toolName: 'echoC', args: { message: 'hi' } }).id(ids.next()).toString());
|
|
21
|
+
// ws.send(Broker.abilityResult({ requestId, toSessionId, result: {...} }).toString());
|
|
22
|
+
|
|
23
|
+
import crypto from 'node:crypto';
|
|
24
|
+
import { randomUUID } from 'node:crypto';
|
|
25
|
+
|
|
26
|
+
/** @typedef {import('node:crypto').KeyObject} KeyObject */
|
|
27
|
+
|
|
28
|
+
/** Central method-name map (override via configureMethods). */
|
|
29
|
+
const methodNames = {
|
|
30
|
+
hello: 'kadi.hello',
|
|
31
|
+
authenticate: 'kadi.authenticate',
|
|
32
|
+
registerCapabilities: 'kadi.register_capabilities',
|
|
33
|
+
ping: 'kadi.ping',
|
|
34
|
+
listTools: 'kadi.list_tools',
|
|
35
|
+
callAbility: 'agent.callAbility',
|
|
36
|
+
abilityResult: 'ability.result'
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/** Allow consumers to override one or more JSON‑RPC method strings. */
|
|
40
|
+
export function configureMethods(overrides = {}) {
|
|
41
|
+
for (const [k, v] of Object.entries(overrides)) {
|
|
42
|
+
if (!(k in methodNames)) throw new Error(`Unknown method key: ${k}`);
|
|
43
|
+
if (typeof v !== 'string' || !v.length)
|
|
44
|
+
throw new Error(`Bad method string for ${k}`);
|
|
45
|
+
methodNames[k] = v;
|
|
46
|
+
}
|
|
47
|
+
return { ...methodNames };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Minimal JSON-RPC 2.0 message wrapper with a chainable API. */
|
|
51
|
+
class JsonRpcBuilder {
|
|
52
|
+
/**
|
|
53
|
+
* @param {string} method
|
|
54
|
+
* @param {object|undefined} params
|
|
55
|
+
* @param {boolean} isNotification - when true, no id will be set unless forced via .id()
|
|
56
|
+
*/
|
|
57
|
+
constructor(method, params = undefined, isNotification = false) {
|
|
58
|
+
this._jsonrpc = '2.0';
|
|
59
|
+
this._method = method;
|
|
60
|
+
this._params = params;
|
|
61
|
+
this._id = isNotification ? undefined : 0; // placeholder until .id()
|
|
62
|
+
this._forceNoId = isNotification;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Attach/override params. */
|
|
66
|
+
params(obj) {
|
|
67
|
+
this._params = obj ?? undefined;
|
|
68
|
+
return this;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Set the JSON-RPC id (number|string). If null/undefined, remove id (notification). */
|
|
72
|
+
id(id) {
|
|
73
|
+
this._id = id ?? undefined;
|
|
74
|
+
this._forceNoId = id == null;
|
|
75
|
+
return this;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Build a plain object suitable for JSON.stringify and sending. */
|
|
79
|
+
build() {
|
|
80
|
+
/** @type {any} */
|
|
81
|
+
const msg = { jsonrpc: this._jsonrpc, method: this._method };
|
|
82
|
+
if (this._params !== undefined) msg.params = this._params;
|
|
83
|
+
if (!this._forceNoId) {
|
|
84
|
+
msg.id = this._id === 0 ? IdFactory.default().next() : this._id;
|
|
85
|
+
}
|
|
86
|
+
return msg;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Convenience: JSON string. */
|
|
90
|
+
toString() {
|
|
91
|
+
return JSON.stringify(this.build());
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Monotonic id generator; stable across builders if you reuse the instance. */
|
|
96
|
+
export class IdFactory {
|
|
97
|
+
constructor(prefix = '') {
|
|
98
|
+
this.prefix = prefix;
|
|
99
|
+
this.counter = 1;
|
|
100
|
+
}
|
|
101
|
+
next() {
|
|
102
|
+
const n = this.counter++;
|
|
103
|
+
return this.prefix ? `${this.prefix}${n}` : n;
|
|
104
|
+
}
|
|
105
|
+
static default() {
|
|
106
|
+
if (!globalThis.__brokerIdFactory)
|
|
107
|
+
globalThis.__brokerIdFactory = new IdFactory();
|
|
108
|
+
return globalThis.__brokerIdFactory;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Generate a correlation id suitable for broker requestId fields. */
|
|
113
|
+
export function newRequestId() {
|
|
114
|
+
return randomUUID();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Convert a Node KeyObject public key (Ed25519) to base64 DER (SPKI) string.
|
|
119
|
+
* @param {KeyObject} pubKey
|
|
120
|
+
*/
|
|
121
|
+
export function toBase64Der(pubKey) {
|
|
122
|
+
return pubKey.export({ format: 'der', type: 'spki' }).toString('base64');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Build signature for authenticate. If signatureBase64 is given, returns it as-is.
|
|
127
|
+
* Otherwise, signs the nonce with the provided privateKey using Ed25519.
|
|
128
|
+
*
|
|
129
|
+
* @param {object} opts
|
|
130
|
+
* @param {string} [opts.signatureBase64]
|
|
131
|
+
* @param {KeyObject} [opts.privateKey]
|
|
132
|
+
* @param {string} opts.nonce
|
|
133
|
+
*/
|
|
134
|
+
function ensureSignature({ signatureBase64, privateKey, nonce }) {
|
|
135
|
+
if (signatureBase64) return signatureBase64;
|
|
136
|
+
if (!privateKey)
|
|
137
|
+
throw new Error('Either signatureBase64 or privateKey must be provided');
|
|
138
|
+
return crypto
|
|
139
|
+
.sign(null, Buffer.from(nonce, 'utf8'), privateKey)
|
|
140
|
+
.toString('base64');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Namespace: Broker protocol message builders (outbound). */
|
|
144
|
+
export const Broker = {
|
|
145
|
+
/**
|
|
146
|
+
* hello — specify the role explicitly.
|
|
147
|
+
* @param {object} opts
|
|
148
|
+
* @param {'agent'|'ability'|'observer'} opts.role
|
|
149
|
+
* @param {string} [opts.version='0.1']
|
|
150
|
+
*/
|
|
151
|
+
hello({ role, version = '0.1' } = {}) {
|
|
152
|
+
if (!role)
|
|
153
|
+
throw new Error('role is required ("agent" | "ability" | "observer")');
|
|
154
|
+
return new JsonRpcBuilder(methodNames.hello, { role, version });
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* authenticate
|
|
159
|
+
* @param {object} opts
|
|
160
|
+
* @param {string} opts.publicKeyBase64Der - base64 DER (SPKI) public key
|
|
161
|
+
* @param {string} opts.nonce
|
|
162
|
+
* @param {boolean} [opts.wantNewId=true]
|
|
163
|
+
* @param {KeyObject} [opts.privateKey] - used to compute signature if signatureBase64 not given
|
|
164
|
+
* @param {string} [opts.signatureBase64] - if you prefer to sign externally
|
|
165
|
+
*/
|
|
166
|
+
authenticate({
|
|
167
|
+
publicKeyBase64Der,
|
|
168
|
+
nonce,
|
|
169
|
+
wantNewId = true,
|
|
170
|
+
privateKey,
|
|
171
|
+
signatureBase64
|
|
172
|
+
}) {
|
|
173
|
+
const signature = ensureSignature({ signatureBase64, privateKey, nonce });
|
|
174
|
+
const params = {
|
|
175
|
+
publicKey: publicKeyBase64Der,
|
|
176
|
+
signature,
|
|
177
|
+
nonce,
|
|
178
|
+
wantNewId
|
|
179
|
+
};
|
|
180
|
+
return new JsonRpcBuilder(methodNames.authenticate, params);
|
|
181
|
+
},
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* registerCapabilities
|
|
185
|
+
* @param {object} opts
|
|
186
|
+
* @param {string} opts.displayName
|
|
187
|
+
* @param {('volatile'|'persistent')} [opts.mailboxMode='persistent']
|
|
188
|
+
* @param {Array<object>} [opts.tools=[]] - [{ name, description, inputSchema, outputSchema?, scopes? }]
|
|
189
|
+
* @param {string|string[]} [opts.scopes=['global']]
|
|
190
|
+
* @param {string} [opts.parentAgentId]
|
|
191
|
+
*/
|
|
192
|
+
registerCapabilities({
|
|
193
|
+
displayName,
|
|
194
|
+
mailboxMode = 'persistent',
|
|
195
|
+
tools = [],
|
|
196
|
+
scopes = ['global'],
|
|
197
|
+
parentAgentId
|
|
198
|
+
} = {}) {
|
|
199
|
+
if (!displayName) throw new Error('displayName is required');
|
|
200
|
+
/** @type {any} */
|
|
201
|
+
const params = { displayName, mailboxMode, tools, scopes };
|
|
202
|
+
if (parentAgentId !== undefined) params.parentAgentId = parentAgentId;
|
|
203
|
+
return new JsonRpcBuilder(methodNames.registerCapabilities, params);
|
|
204
|
+
},
|
|
205
|
+
|
|
206
|
+
/** ping (notification) */
|
|
207
|
+
ping() {
|
|
208
|
+
return new JsonRpcBuilder(
|
|
209
|
+
methodNames.ping,
|
|
210
|
+
undefined,
|
|
211
|
+
/* notification */ true
|
|
212
|
+
);
|
|
213
|
+
},
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* listTools
|
|
217
|
+
* @param {object} [opts]
|
|
218
|
+
* @param {string|string[]} [opts.scopes=['global']]
|
|
219
|
+
*/
|
|
220
|
+
listTools({ scopes = ['global'] } = {}) {
|
|
221
|
+
return new JsonRpcBuilder(methodNames.listTools, { scopes });
|
|
222
|
+
},
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* callAbility
|
|
226
|
+
* @param {object} opts
|
|
227
|
+
* @param {string} opts.toolName
|
|
228
|
+
* @param {object} [opts.args]
|
|
229
|
+
* @param {string} [opts.requestId] - correlation ID; generated if omitted
|
|
230
|
+
*/
|
|
231
|
+
callAbility({ toolName, args = {}, requestId = undefined }) {
|
|
232
|
+
if (!toolName) throw new Error('toolName is required');
|
|
233
|
+
const rid = requestId ?? newRequestId();
|
|
234
|
+
return new JsonRpcBuilder(methodNames.callAbility, {
|
|
235
|
+
toolName,
|
|
236
|
+
args,
|
|
237
|
+
requestId: rid
|
|
238
|
+
});
|
|
239
|
+
},
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* abilityResult (notification)
|
|
243
|
+
* @param {object} opts
|
|
244
|
+
* @param {string} opts.requestId
|
|
245
|
+
* @param {string} opts.toSessionId
|
|
246
|
+
* @param {any} [opts.result]
|
|
247
|
+
* @param {string|null} [opts.error]
|
|
248
|
+
*/
|
|
249
|
+
abilityResult({ requestId, toSessionId, result = undefined, error = null }) {
|
|
250
|
+
if (!requestId) throw new Error('requestId is required');
|
|
251
|
+
if (!toSessionId) throw new Error('toSessionId is required');
|
|
252
|
+
const params = { requestId, toSessionId };
|
|
253
|
+
if (result !== undefined) params.result = result;
|
|
254
|
+
if (error !== null) params.error = error;
|
|
255
|
+
return new JsonRpcBuilder(
|
|
256
|
+
methodNames.abilityResult,
|
|
257
|
+
params,
|
|
258
|
+
/* notification */ true
|
|
259
|
+
);
|
|
260
|
+
},
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Build an agent ability definition object.
|
|
264
|
+
* Kept shape compatible with current broker "tools" array.
|
|
265
|
+
* @param {object} opts
|
|
266
|
+
* @param {string} opts.name
|
|
267
|
+
* @param {string} opts.description
|
|
268
|
+
* @param {object} opts.inputSchema
|
|
269
|
+
* @param {object} [opts.outputSchema]
|
|
270
|
+
* @param {string[]} [opts.scopes]
|
|
271
|
+
*/
|
|
272
|
+
agentAbility({ name, description, inputSchema, outputSchema, scopes }) {
|
|
273
|
+
if (!name) throw new Error('ability.name is required');
|
|
274
|
+
if (!description) throw new Error('ability.description is required');
|
|
275
|
+
if (!inputSchema) throw new Error('ability.inputSchema is required');
|
|
276
|
+
/** @type {any} */
|
|
277
|
+
const def = { name, description, inputSchema };
|
|
278
|
+
if (outputSchema) def.outputSchema = outputSchema;
|
|
279
|
+
if (scopes) def.scopes = scopes;
|
|
280
|
+
return def;
|
|
281
|
+
},
|
|
282
|
+
|
|
283
|
+
/** Short alias. */
|
|
284
|
+
ability(opts) {
|
|
285
|
+
return this.agentAbility(opts);
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
/** Namespace: MCP bridge message builders (subset as per README). */
|
|
290
|
+
export const MCP = {
|
|
291
|
+
initialize({
|
|
292
|
+
protocolVersion = '2025-06-18',
|
|
293
|
+
capabilities = {},
|
|
294
|
+
clientInfo = { name: 'mcp-client', version: '1.0.0' }
|
|
295
|
+
} = {}) {
|
|
296
|
+
const params = {
|
|
297
|
+
protocolVersion,
|
|
298
|
+
capabilities: {
|
|
299
|
+
resources: { subscribe: false, ...capabilities.resources },
|
|
300
|
+
tools: true,
|
|
301
|
+
prompts: { subscribe: false, ...capabilities.prompts },
|
|
302
|
+
sampling: false,
|
|
303
|
+
...capabilities
|
|
304
|
+
},
|
|
305
|
+
clientInfo
|
|
306
|
+
};
|
|
307
|
+
return new JsonRpcBuilder('initialize', params);
|
|
308
|
+
},
|
|
309
|
+
initialized() {
|
|
310
|
+
return new JsonRpcBuilder('initialized', undefined, true);
|
|
311
|
+
},
|
|
312
|
+
toolsList() {
|
|
313
|
+
return new JsonRpcBuilder('tools/list', {});
|
|
314
|
+
},
|
|
315
|
+
toolsCall({ name, arguments: args = {} }) {
|
|
316
|
+
if (!name) throw new Error('name is required');
|
|
317
|
+
return new JsonRpcBuilder('tools/call', { name, arguments: args });
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
/** Helpers for common agent handshake flows */
|
|
322
|
+
export const Flows = {
|
|
323
|
+
/**
|
|
324
|
+
* Build all handshake messages for an agent given the broker's hello result and your keys.
|
|
325
|
+
* @param {object} opts
|
|
326
|
+
* @param {KeyObject} opts.publicKey
|
|
327
|
+
* @param {KeyObject} opts.privateKey
|
|
328
|
+
* @param {string} opts.nonce - From hello result
|
|
329
|
+
* @param {string} opts.displayName
|
|
330
|
+
* @param {Array<object>} opts.tools
|
|
331
|
+
* @param {('volatile'|'persistent')} [opts.mailboxMode]
|
|
332
|
+
* @param {string|string[]} [opts.scopes=['global']]
|
|
333
|
+
*/
|
|
334
|
+
completeAgentHandshake({
|
|
335
|
+
publicKey,
|
|
336
|
+
privateKey,
|
|
337
|
+
nonce,
|
|
338
|
+
displayName,
|
|
339
|
+
tools,
|
|
340
|
+
mailboxMode = 'volatile',
|
|
341
|
+
scopes = ['global']
|
|
342
|
+
}) {
|
|
343
|
+
const ids = new IdFactory(); // fresh id sequence for this flow
|
|
344
|
+
const hello = Broker.hello({ role: 'agent' }).id(ids.next()).build();
|
|
345
|
+
const auth = Broker.authenticate({
|
|
346
|
+
publicKeyBase64Der: toBase64Der(publicKey),
|
|
347
|
+
privateKey,
|
|
348
|
+
nonce,
|
|
349
|
+
wantNewId: true
|
|
350
|
+
})
|
|
351
|
+
.id(ids.next())
|
|
352
|
+
.build();
|
|
353
|
+
const reg = Broker.registerCapabilities({
|
|
354
|
+
displayName,
|
|
355
|
+
tools,
|
|
356
|
+
mailboxMode,
|
|
357
|
+
scopes
|
|
358
|
+
})
|
|
359
|
+
.id(ids.next())
|
|
360
|
+
.build();
|
|
361
|
+
return { hello, authenticate: auth, register: reg };
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
const BrokerMessageBuilder = {
|
|
366
|
+
JsonRpcBuilder,
|
|
367
|
+
IdFactory,
|
|
368
|
+
newRequestId,
|
|
369
|
+
toBase64Der,
|
|
370
|
+
Broker,
|
|
371
|
+
MCP,
|
|
372
|
+
Flows,
|
|
373
|
+
configureMethods,
|
|
374
|
+
_methodNames: methodNames // exposed for debugging
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
export default BrokerMessageBuilder;
|