@kadi.build/core 0.3.3 → 0.4.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/dist/client.d.ts CHANGED
@@ -14,7 +14,7 @@
14
14
  * - Explicit over implicit (no proxy magic)
15
15
  * - Readable, traceable code flow
16
16
  */
17
- import type { ClientConfig, BrokerState, ToolDefinition, ZodToolDefinition, ToolHandler, LoadedAbility, RegisterToolOptions, LoadNativeOptions, LoadStdioOptions, LoadBrokerOptions, InvokeRemoteOptions, RequestContext, BrokerEventHandler, PublishOptions, SubscribeOptions, EmitOptions } from './types.js';
17
+ import type { ClientConfig, ClientIdentity, BrokerState, ToolDefinition, ZodToolDefinition, ToolHandler, LoadedAbility, ToolExecutionBridge, RegisterToolOptions, LoadNativeOptions, LoadStdioOptions, LoadBrokerOptions, InvokeRemoteOptions, BrokerEventHandler, PublishOptions, SubscribeOptions, EmitOptions } from './types.js';
18
18
  /**
19
19
  * The main client for building KADI agents.
20
20
  *
@@ -58,7 +58,48 @@ export declare class KadiClient {
58
58
  private eventHandler;
59
59
  /** Whether we're serving via stdio (set by serve('stdio')) */
60
60
  private isServingStdio;
61
+ /** Ed25519 private key (DER format) - used for signing */
62
+ private readonly _privateKey;
63
+ /** Base64-encoded Ed25519 public key (SPKI DER format) */
64
+ private readonly _publicKeyBase64;
65
+ /** Deterministic agent ID: SHA256(publicKey).hex().substring(0, 16) */
66
+ private readonly _agentId;
61
67
  constructor(config: ClientConfig);
68
+ /**
69
+ * Get the client's cryptographic identity.
70
+ *
71
+ * Available immediately after construction (before connect).
72
+ * The same identity is used for all broker connections.
73
+ *
74
+ * @example
75
+ * ```typescript
76
+ * const client = new KadiClient({ name: 'my-agent' });
77
+ * console.log(client.identity);
78
+ * // { publicKey: 'MIIBIjAN...', agentId: 'a1b2c3d4e5f67890' }
79
+ * ```
80
+ */
81
+ get identity(): ClientIdentity;
82
+ /**
83
+ * Get the client's public key (base64-encoded SPKI DER format).
84
+ *
85
+ * This is the key used for Ed25519 authentication with brokers.
86
+ */
87
+ get publicKey(): string;
88
+ /**
89
+ * Get the client's agent ID.
90
+ *
91
+ * Derived deterministically from publicKey: SHA256(publicKey).hex().substring(0, 16)
92
+ * This is the same ID that brokers will assign during authentication.
93
+ */
94
+ get agentId(): string;
95
+ /**
96
+ * Get the agent name (from config).
97
+ */
98
+ get name(): string;
99
+ /**
100
+ * Get the agent version (from config).
101
+ */
102
+ get version(): string;
62
103
  /**
63
104
  * Connect to all configured brokers.
64
105
  * Call this after registering tools.
@@ -74,14 +115,16 @@ export declare class KadiClient {
74
115
  * Connect to a specific broker by name.
75
116
  *
76
117
  * Protocol:
77
- * 1. Generate Ed25519 keypair
78
- * 2. Open WebSocket connection
79
- * 3. Send kadi.session.hello
80
- * 4. Receive nonce from broker
81
- * 5. Send kadi.session.authenticate with signed nonce
82
- * 6. Receive agentId from broker
83
- * 7. Register tools with broker
84
- * 8. Start heartbeat
118
+ * 1. Open WebSocket connection
119
+ * 2. Send kadi.session.hello
120
+ * 3. Receive nonce from broker
121
+ * 4. Send kadi.session.authenticate with signed nonce (using client's identity)
122
+ * 5. Receive agentId from broker (should match client.agentId)
123
+ * 6. Register tools with broker
124
+ * 7. Start heartbeat
125
+ *
126
+ * Note: The keypair is generated once at client construction and shared
127
+ * across all broker connections, ensuring consistent identity.
85
128
  */
86
129
  private connectToBroker;
87
130
  private buildHelloMessage;
@@ -299,8 +342,8 @@ export declare class KadiClient {
299
342
  /**
300
343
  * Attempt to reconnect to the broker.
301
344
  *
302
- * This reuses the existing keypair (agentId stays the same) and
303
- * re-registers tools with the broker.
345
+ * Uses the client's identity (same keypair/agentId for all connections)
346
+ * and re-registers tools with the broker.
304
347
  *
305
348
  * On failure, schedules another attempt with increased delay.
306
349
  */
@@ -377,13 +420,33 @@ export declare class KadiClient {
377
420
  */
378
421
  private getToolDefinitions;
379
422
  /**
380
- * Invoke a local tool by name.
423
+ * Execute a tool registered on this client.
424
+ *
425
+ * This is a PRIVATE method - not for external use.
426
+ * It handles INCOMING tool calls from:
427
+ * - Broker (when another agent calls your tools)
428
+ * - Stdio (when running as an ability)
429
+ * - Native transport (when loaded in-process)
381
430
  *
382
- * @param toolName - Name of the tool to invoke
431
+ * To call tools on OTHER agents, use:
432
+ * - `loadBroker(name).invoke(tool, params)` - for repeated calls to the same agent
433
+ * - `invokeRemote(tool, params, { timeout })` - for one-off calls with custom timeout
434
+ *
435
+ * @param toolName - Name of the registered tool to execute
383
436
  * @param params - Input parameters for the tool
384
- * @param context - Optional request context (provided when invoked via broker)
437
+ * @param context - Request context with caller info (provided by broker/stdio)
438
+ * @internal
439
+ */
440
+ private executeToolHandler;
441
+ /**
442
+ * Create a bridge for internal transport use.
443
+ *
444
+ * This allows native transport to call tools on this client without
445
+ * exposing the full client interface.
446
+ *
447
+ * @internal - Not for external use
385
448
  */
386
- invoke(toolName: string, params: unknown, context?: RequestContext): Promise<unknown>;
449
+ createToolBridge(): ToolExecutionBridge;
387
450
  /**
388
451
  * Load an in-process ability via dynamic import.
389
452
  *
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
package/dist/client.js CHANGED
@@ -26,7 +26,7 @@ import { loadBrokerTransport } from './transports/broker.js';
26
26
  // CONSTANTS
27
27
  // ═══════════════════════════════════════════════════════════════
28
28
  const DEFAULT_HEARTBEAT_INTERVAL = 25000; // 25 seconds
29
- const DEFAULT_REQUEST_TIMEOUT = 30000; // 30 seconds
29
+ const DEFAULT_REQUEST_TIMEOUT = 600000; // 10 minutes
30
30
  const DEFAULT_AUTO_RECONNECT = true;
31
31
  const DEFAULT_MAX_RECONNECT_DELAY = 30000; // 30 seconds cap
32
32
  const RECONNECT_BASE_DELAY = 1000; // 1 second initial delay
@@ -144,6 +144,15 @@ export class KadiClient {
144
144
  /** Whether we're serving via stdio (set by serve('stdio')) */
145
145
  isServingStdio = false;
146
146
  // ─────────────────────────────────────────────────────────────
147
+ // IDENTITY (single keypair shared across all broker connections)
148
+ // ─────────────────────────────────────────────────────────────
149
+ /** Ed25519 private key (DER format) - used for signing */
150
+ _privateKey;
151
+ /** Base64-encoded Ed25519 public key (SPKI DER format) */
152
+ _publicKeyBase64;
153
+ /** Deterministic agent ID: SHA256(publicKey).hex().substring(0, 16) */
154
+ _agentId;
155
+ // ─────────────────────────────────────────────────────────────
147
156
  // CONSTRUCTOR
148
157
  // ─────────────────────────────────────────────────────────────
149
158
  constructor(config) {
@@ -153,6 +162,12 @@ export class KadiClient {
153
162
  hint: 'Provide a name for your agent: new KadiClient({ name: "my-agent" })',
154
163
  });
155
164
  }
165
+ // Generate Ed25519 keypair once for consistent identity across all brokers
166
+ const { publicKey, privateKey } = crypto.generateKeyPairSync('ed25519');
167
+ this._privateKey = privateKey.export({ type: 'pkcs8', format: 'der' });
168
+ this._publicKeyBase64 = publicKey.export({ type: 'spki', format: 'der' }).toString('base64');
169
+ // Derive agentId using same algorithm as broker: SHA256(publicKey).hex().substring(0, 16)
170
+ this._agentId = crypto.createHash('sha256').update(this._publicKeyBase64).digest('hex').substring(0, 16);
156
171
  // Resolve configuration with defaults
157
172
  // Auto-select first broker as default if not specified (matches Python behavior)
158
173
  const brokers = config.brokers ?? {};
@@ -179,6 +194,57 @@ export class KadiClient {
179
194
  }
180
195
  }
181
196
  // ─────────────────────────────────────────────────────────────
197
+ // IDENTITY GETTERS
198
+ // ─────────────────────────────────────────────────────────────
199
+ /**
200
+ * Get the client's cryptographic identity.
201
+ *
202
+ * Available immediately after construction (before connect).
203
+ * The same identity is used for all broker connections.
204
+ *
205
+ * @example
206
+ * ```typescript
207
+ * const client = new KadiClient({ name: 'my-agent' });
208
+ * console.log(client.identity);
209
+ * // { publicKey: 'MIIBIjAN...', agentId: 'a1b2c3d4e5f67890' }
210
+ * ```
211
+ */
212
+ get identity() {
213
+ return {
214
+ publicKey: this._publicKeyBase64,
215
+ agentId: this._agentId,
216
+ };
217
+ }
218
+ /**
219
+ * Get the client's public key (base64-encoded SPKI DER format).
220
+ *
221
+ * This is the key used for Ed25519 authentication with brokers.
222
+ */
223
+ get publicKey() {
224
+ return this._publicKeyBase64;
225
+ }
226
+ /**
227
+ * Get the client's agent ID.
228
+ *
229
+ * Derived deterministically from publicKey: SHA256(publicKey).hex().substring(0, 16)
230
+ * This is the same ID that brokers will assign during authentication.
231
+ */
232
+ get agentId() {
233
+ return this._agentId;
234
+ }
235
+ /**
236
+ * Get the agent name (from config).
237
+ */
238
+ get name() {
239
+ return this.config.name;
240
+ }
241
+ /**
242
+ * Get the agent version (from config).
243
+ */
244
+ get version() {
245
+ return this.config.version;
246
+ }
247
+ // ─────────────────────────────────────────────────────────────
182
248
  // CONNECTION MANAGEMENT
183
249
  // ─────────────────────────────────────────────────────────────
184
250
  /**
@@ -231,14 +297,16 @@ export class KadiClient {
231
297
  * Connect to a specific broker by name.
232
298
  *
233
299
  * Protocol:
234
- * 1. Generate Ed25519 keypair
235
- * 2. Open WebSocket connection
236
- * 3. Send kadi.session.hello
237
- * 4. Receive nonce from broker
238
- * 5. Send kadi.session.authenticate with signed nonce
239
- * 6. Receive agentId from broker
240
- * 7. Register tools with broker
241
- * 8. Start heartbeat
300
+ * 1. Open WebSocket connection
301
+ * 2. Send kadi.session.hello
302
+ * 3. Receive nonce from broker
303
+ * 4. Send kadi.session.authenticate with signed nonce (using client's identity)
304
+ * 5. Receive agentId from broker (should match client.agentId)
305
+ * 6. Register tools with broker
306
+ * 7. Start heartbeat
307
+ *
308
+ * Note: The keypair is generated once at client construction and shared
309
+ * across all broker connections, ensuring consistent identity.
242
310
  */
243
311
  async connectToBroker(brokerName) {
244
312
  const url = this.config.brokers[brokerName];
@@ -256,16 +324,11 @@ export class KadiClient {
256
324
  return;
257
325
  }
258
326
  }
259
- // Generate Ed25519 keypair for authentication
260
- const { publicKey, privateKey } = crypto.generateKeyPairSync('ed25519');
261
- // Create broker state
327
+ // Create broker state (identity is at client level, not per-broker)
262
328
  const state = {
263
329
  name: brokerName,
264
330
  url,
265
331
  ws: null,
266
- agentId: '',
267
- publicKey: publicKey.export({ type: 'spki', format: 'der' }),
268
- privateKey: privateKey.export({ type: 'pkcs8', format: 'der' }),
269
332
  heartbeatTimer: null,
270
333
  pendingRequests: new Map(),
271
334
  pendingInvocations: new Map(),
@@ -302,9 +365,10 @@ export class KadiClient {
302
365
  params: { role: 'agent' },
303
366
  };
304
367
  }
305
- buildAuthMessage(state, nonce) {
368
+ buildAuthMessage(nonce) {
369
+ // Sign the nonce using the client's private key
306
370
  const privateKey = crypto.createPrivateKey({
307
- key: state.privateKey,
371
+ key: this._privateKey,
308
372
  format: 'der',
309
373
  type: 'pkcs8',
310
374
  });
@@ -314,7 +378,7 @@ export class KadiClient {
314
378
  id: this.nextRequestId++,
315
379
  method: 'kadi.session.authenticate',
316
380
  params: {
317
- publicKey: state.publicKey.toString('base64'),
381
+ publicKey: this._publicKeyBase64,
318
382
  signature: signature.toString('base64'),
319
383
  nonce,
320
384
  },
@@ -399,13 +463,18 @@ export class KadiClient {
399
463
  }
400
464
  // Step 2: Sign the nonce and authenticate
401
465
  // This proves we own the private key without revealing it
402
- const authResult = await this.sendRequest(state, this.buildAuthMessage(state, helloResult.nonce));
466
+ const authResult = await this.sendRequest(state, this.buildAuthMessage(helloResult.nonce));
403
467
  if (!authResult.agentId) {
404
468
  throw new KadiError('Auth response missing agentId', 'AUTHENTICATION_FAILED', {
405
469
  broker: state.name,
406
470
  });
407
471
  }
408
- state.agentId = authResult.agentId;
472
+ // Verify broker assigned the expected agentId (sanity check)
473
+ if (authResult.agentId !== this._agentId) {
474
+ console.warn(`[KADI] Broker "${state.name}" returned different agentId: ` +
475
+ `expected ${this._agentId}, got ${authResult.agentId}. ` +
476
+ `This may indicate the broker uses a different ID derivation algorithm.`);
477
+ }
409
478
  }
410
479
  /**
411
480
  * Register with broker after authentication.
@@ -828,7 +897,7 @@ export class KadiClient {
828
897
  */
829
898
  async handleInvokeRequest(state, requestId, toolName, toolInput, context) {
830
899
  try {
831
- const result = await this.invoke(toolName, toolInput, context);
900
+ const result = await this.executeToolHandler(toolName, toolInput, context);
832
901
  // Format response based on caller protocol
833
902
  let responseResult;
834
903
  if (context.callerProtocol === 'mcp') {
@@ -1028,8 +1097,8 @@ export class KadiClient {
1028
1097
  /**
1029
1098
  * Attempt to reconnect to the broker.
1030
1099
  *
1031
- * This reuses the existing keypair (agentId stays the same) and
1032
- * re-registers tools with the broker.
1100
+ * Uses the client's identity (same keypair/agentId for all connections)
1101
+ * and re-registers tools with the broker.
1033
1102
  *
1034
1103
  * On failure, schedules another attempt with increased delay.
1035
1104
  */
@@ -1233,13 +1302,24 @@ export class KadiClient {
1233
1302
  .map((t) => t.definition);
1234
1303
  }
1235
1304
  /**
1236
- * Invoke a local tool by name.
1305
+ * Execute a tool registered on this client.
1237
1306
  *
1238
- * @param toolName - Name of the tool to invoke
1307
+ * This is a PRIVATE method - not for external use.
1308
+ * It handles INCOMING tool calls from:
1309
+ * - Broker (when another agent calls your tools)
1310
+ * - Stdio (when running as an ability)
1311
+ * - Native transport (when loaded in-process)
1312
+ *
1313
+ * To call tools on OTHER agents, use:
1314
+ * - `loadBroker(name).invoke(tool, params)` - for repeated calls to the same agent
1315
+ * - `invokeRemote(tool, params, { timeout })` - for one-off calls with custom timeout
1316
+ *
1317
+ * @param toolName - Name of the registered tool to execute
1239
1318
  * @param params - Input parameters for the tool
1240
- * @param context - Optional request context (provided when invoked via broker)
1319
+ * @param context - Request context with caller info (provided by broker/stdio)
1320
+ * @internal
1241
1321
  */
1242
- async invoke(toolName, params, context) {
1322
+ async executeToolHandler(toolName, params, context) {
1243
1323
  const tool = this.tools.get(toolName);
1244
1324
  if (!tool) {
1245
1325
  throw new KadiError(`Tool "${toolName}" not found`, 'TOOL_NOT_FOUND', {
@@ -1254,6 +1334,20 @@ export class KadiClient {
1254
1334
  // Should call tool.definition.input.safeParse(params) and throw on validation errors.
1255
1335
  return tool.handler(params, context);
1256
1336
  }
1337
+ /**
1338
+ * Create a bridge for internal transport use.
1339
+ *
1340
+ * This allows native transport to call tools on this client without
1341
+ * exposing the full client interface.
1342
+ *
1343
+ * @internal - Not for external use
1344
+ */
1345
+ createToolBridge() {
1346
+ return {
1347
+ executeToolHandler: (toolName, params, context) => this.executeToolHandler(toolName, params, context),
1348
+ getRegisteredTools: () => Array.from(this.tools.values()).map((t) => t.definition),
1349
+ };
1350
+ }
1257
1351
  // ─────────────────────────────────────────────────────────────
1258
1352
  // ABILITY LOADING
1259
1353
  // ─────────────────────────────────────────────────────────────
@@ -1284,7 +1378,9 @@ export class KadiClient {
1284
1378
  path = entry.absolutePath;
1285
1379
  entrypoint = entry.entrypoint;
1286
1380
  }
1287
- return loadNativeTransport(path, entrypoint);
1381
+ return loadNativeTransport(path, entrypoint, {
1382
+ timeout: options.timeout ?? this.config.requestTimeout,
1383
+ });
1288
1384
  }
1289
1385
  /**
1290
1386
  * Load a child process ability via stdio.
@@ -1321,7 +1417,7 @@ export class KadiClient {
1321
1417
  // ─────────────────────────────────────────────────────────────────────────
1322
1418
  if (options.command) {
1323
1419
  return loadStdioTransport(options.command, options.args ?? [], {
1324
- timeoutMs: this.config.requestTimeout,
1420
+ timeoutMs: options.timeout ?? this.config.requestTimeout,
1325
1421
  });
1326
1422
  }
1327
1423
  // ─────────────────────────────────────────────────────────────────────────
@@ -1329,7 +1425,7 @@ export class KadiClient {
1329
1425
  // ─────────────────────────────────────────────────────────────────────────
1330
1426
  const { command, args, cwd } = await resolveAbilityScript(name, options.script ?? 'start', options.projectRoot);
1331
1427
  return loadStdioTransport(command, args, {
1332
- timeoutMs: this.config.requestTimeout,
1428
+ timeoutMs: options.timeout ?? this.config.requestTimeout,
1333
1429
  cwd, // Run in ability's directory so relative paths work
1334
1430
  });
1335
1431
  }
@@ -1352,7 +1448,7 @@ export class KadiClient {
1352
1448
  const { state, brokerName } = this.getConnectedBrokerState(options.broker);
1353
1449
  return loadBrokerTransport(name, {
1354
1450
  broker: state,
1355
- requestTimeout: this.config.requestTimeout,
1451
+ requestTimeout: options.timeout ?? this.config.requestTimeout,
1356
1452
  networks: options.networks,
1357
1453
  // Provide subscribe/unsubscribe for ability.on()/off() support
1358
1454
  subscribe: (pattern, handler) => this.subscribe(pattern, handler, { broker: brokerName }),
@@ -1415,6 +1511,14 @@ export class KadiClient {
1415
1511
  sentAt: new Date(),
1416
1512
  });
1417
1513
  });
1514
+ // Helper to clean up the pending invocation on failure
1515
+ const cleanupPendingInvocation = () => {
1516
+ const pending = state.pendingInvocations.get(requestId);
1517
+ if (pending) {
1518
+ clearTimeout(pending.timeout);
1519
+ state.pendingInvocations.delete(requestId);
1520
+ }
1521
+ };
1418
1522
  // NOW send the request (listener already exists, no race possible)
1419
1523
  try {
1420
1524
  const pendingResult = await this.sendRequest(state, {
@@ -1425,22 +1529,12 @@ export class KadiClient {
1425
1529
  });
1426
1530
  // Validate the broker accepted the request
1427
1531
  if (pendingResult.status !== 'pending') {
1428
- // Clean up listener since request wasn't accepted
1429
- const pending = state.pendingInvocations.get(requestId);
1430
- if (pending) {
1431
- clearTimeout(pending.timeout);
1432
- state.pendingInvocations.delete(requestId);
1433
- }
1532
+ cleanupPendingInvocation();
1434
1533
  throw new KadiError('Unexpected response from broker: expected pending acknowledgment', 'BROKER_ERROR', { broker: brokerName, toolName, response: pendingResult });
1435
1534
  }
1436
1535
  }
1437
1536
  catch (error) {
1438
- // Clean up listener if request failed
1439
- const pending = state.pendingInvocations.get(requestId);
1440
- if (pending) {
1441
- clearTimeout(pending.timeout);
1442
- state.pendingInvocations.delete(requestId);
1443
- }
1537
+ cleanupPendingInvocation();
1444
1538
  throw error;
1445
1539
  }
1446
1540
  return resultPromise;
@@ -1559,7 +1653,7 @@ export class KadiClient {
1559
1653
  }
1560
1654
  else if (message.method === 'invoke') {
1561
1655
  const params = message.params;
1562
- result = await this.invoke(params.toolName, params.toolInput);
1656
+ result = await this.executeToolHandler(params.toolName, params.toolInput);
1563
1657
  }
1564
1658
  else if (message.method === 'shutdown') {
1565
1659
  // Graceful shutdown - send response first, then cleanup