@kadi.build/core 0.0.1-alpha.10 → 0.0.1-alpha.12

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.
Files changed (71) hide show
  1. package/README.md +269 -1311
  2. package/dist/abilities/AbilityLoader.d.ts +26 -0
  3. package/dist/abilities/AbilityLoader.d.ts.map +1 -1
  4. package/dist/abilities/AbilityLoader.js +141 -18
  5. package/dist/abilities/AbilityLoader.js.map +1 -1
  6. package/dist/abilities/AbilityProxy.d.ts +33 -0
  7. package/dist/abilities/AbilityProxy.d.ts.map +1 -1
  8. package/dist/abilities/AbilityProxy.js +40 -0
  9. package/dist/abilities/AbilityProxy.js.map +1 -1
  10. package/dist/abilities/index.d.ts +1 -1
  11. package/dist/abilities/index.d.ts.map +1 -1
  12. package/dist/abilities/types.d.ts +67 -0
  13. package/dist/abilities/types.d.ts.map +1 -1
  14. package/dist/broker/BrokerProtocol.js +11 -11
  15. package/dist/broker/BrokerProtocol.js.map +1 -1
  16. package/dist/client/KadiClient.d.ts +191 -2
  17. package/dist/client/KadiClient.d.ts.map +1 -1
  18. package/dist/client/KadiClient.js +412 -2
  19. package/dist/client/KadiClient.js.map +1 -1
  20. package/dist/index.d.ts +3 -2
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +5 -1
  23. package/dist/index.js.map +1 -1
  24. package/dist/messages/index.d.ts +1 -1
  25. package/dist/messages/index.js +1 -1
  26. package/dist/messages/index.js.map +1 -1
  27. package/dist/schemas/index.d.ts +3 -0
  28. package/dist/schemas/index.d.ts.map +1 -1
  29. package/dist/schemas/index.js +2 -0
  30. package/dist/schemas/index.js.map +1 -1
  31. package/dist/schemas/zod-helpers.d.ts +129 -0
  32. package/dist/schemas/zod-helpers.d.ts.map +1 -0
  33. package/dist/schemas/zod-helpers.js +225 -0
  34. package/dist/schemas/zod-helpers.js.map +1 -0
  35. package/dist/schemas/zod-to-json-schema.d.ts +159 -0
  36. package/dist/schemas/zod-to-json-schema.d.ts.map +1 -0
  37. package/dist/schemas/zod-to-json-schema.js +154 -0
  38. package/dist/schemas/zod-to-json-schema.js.map +1 -0
  39. package/dist/transports/NativeTransport.d.ts +29 -0
  40. package/dist/transports/NativeTransport.d.ts.map +1 -1
  41. package/dist/transports/NativeTransport.js +98 -3
  42. package/dist/transports/NativeTransport.js.map +1 -1
  43. package/dist/transports/StdioTransport.d.ts +141 -63
  44. package/dist/transports/StdioTransport.d.ts.map +1 -1
  45. package/dist/transports/StdioTransport.js +309 -232
  46. package/dist/transports/StdioTransport.js.map +1 -1
  47. package/dist/types/broker.d.ts +0 -22
  48. package/dist/types/broker.d.ts.map +1 -1
  49. package/dist/types/broker.js +0 -27
  50. package/dist/types/broker.js.map +1 -1
  51. package/dist/types/index.d.ts +3 -1
  52. package/dist/types/index.d.ts.map +1 -1
  53. package/dist/types/index.js +1 -1
  54. package/dist/types/index.js.map +1 -1
  55. package/dist/types/zod-tools.d.ts +198 -0
  56. package/dist/types/zod-tools.d.ts.map +1 -0
  57. package/dist/types/zod-tools.js +14 -0
  58. package/dist/types/zod-tools.js.map +1 -0
  59. package/dist/utils/LockfileResolver.d.ts +108 -0
  60. package/dist/utils/LockfileResolver.d.ts.map +1 -0
  61. package/dist/utils/LockfileResolver.js +230 -0
  62. package/dist/utils/LockfileResolver.js.map +1 -0
  63. package/dist/utils/StdioMessageReader.d.ts +122 -0
  64. package/dist/utils/StdioMessageReader.d.ts.map +1 -0
  65. package/dist/utils/StdioMessageReader.js +209 -0
  66. package/dist/utils/StdioMessageReader.js.map +1 -0
  67. package/dist/utils/StdioMessageWriter.d.ts +104 -0
  68. package/dist/utils/StdioMessageWriter.d.ts.map +1 -0
  69. package/dist/utils/StdioMessageWriter.js +162 -0
  70. package/dist/utils/StdioMessageWriter.js.map +1 -0
  71. package/package.json +2 -1
@@ -13,7 +13,7 @@ import { EventEmitter } from 'events';
13
13
  import type { KadiConfig, ResolvedConfig, ToolHandler, KadiTool, EventCallback, UnsubscribeFunction, PublishOptions } from '../types/index.js';
14
14
  import { BrokerConnectionManager } from '../broker/BrokerConnectionManager.js';
15
15
  import { BrokerProtocol } from '../broker/BrokerProtocol.js';
16
- import { type LoadedAbility, type LoadOptions } from '../abilities/index.js';
16
+ import { type LoadedAbility, type LoadOptions, type AgentJson } from '../abilities/index.js';
17
17
  import type { IBrokerClient } from '../transports/BrokerTransport.js';
18
18
  /**
19
19
  * KADI Client
@@ -143,7 +143,58 @@ export declare class KadiClient extends EventEmitter implements IBrokerClient {
143
143
  * });
144
144
  * ```
145
145
  */
146
- registerTool<TInput = unknown, TOutput = unknown>(definition: KadiTool, handler: ToolHandler<TInput, TOutput>): this;
146
+ registerTool<TInput = unknown, TOutput = unknown>(definition: KadiTool | (Omit<KadiTool, 'inputSchema' | 'outputSchema'> & {
147
+ input?: any;
148
+ output?: any;
149
+ }), handler: ToolHandler<TInput, TOutput>): this;
150
+ /**
151
+ * Get a registered tool by name
152
+ *
153
+ * Retrieves a tool that has been registered with this client.
154
+ * Useful for inspecting registered tools or accessing their metadata.
155
+ *
156
+ * @param name - Tool name to retrieve
157
+ * @returns The registered tool with its definition and handler, or undefined if not found
158
+ *
159
+ * @example
160
+ * ```typescript
161
+ * const tool = client.getRegisteredTool('add');
162
+ * if (tool) {
163
+ * console.log('Tool:', tool.definition.name);
164
+ * console.log('Description:', tool.definition.description);
165
+ * }
166
+ * ```
167
+ */
168
+ getRegisteredTool(name: string): import("../schemas/kadi-extensions.js").RegisteredTool<unknown, unknown> | undefined;
169
+ /**
170
+ * Get all registered tools
171
+ *
172
+ * Returns an array of all tools that have been registered with this client.
173
+ * Useful for listing available tools or debugging.
174
+ *
175
+ * @returns Array of all registered tools
176
+ *
177
+ * @example
178
+ * ```typescript
179
+ * const allTools = client.getAllRegisteredTools();
180
+ * console.log('Available tools:', allTools.map(t => t.definition.name));
181
+ * ```
182
+ */
183
+ getAllRegisteredTools(): import("../schemas/kadi-extensions.js").RegisteredTool<unknown, unknown>[];
184
+ /**
185
+ * Check if a tool is registered
186
+ *
187
+ * @param name - Tool name to check
188
+ * @returns true if the tool is registered, false otherwise
189
+ *
190
+ * @example
191
+ * ```typescript
192
+ * if (client.hasRegisteredTool('add')) {
193
+ * // Tool is available
194
+ * }
195
+ * ```
196
+ */
197
+ hasRegisteredTool(name: string): boolean;
147
198
  /**
148
199
  * Load an ability
149
200
  *
@@ -229,6 +280,144 @@ export declare class KadiClient extends EventEmitter implements IBrokerClient {
229
280
  * ```
230
281
  */
231
282
  disconnect(): Promise<void>;
283
+ /**
284
+ * Stdio server instance (when running in stdio mode)
285
+ */
286
+ private stdioServer?;
287
+ /**
288
+ * Start serving this ability in the specified mode
289
+ *
290
+ * **Universal Entry Point**:
291
+ * Starts the appropriate server based on the mode parameter.
292
+ * This enables the same ability code to work across all transports
293
+ *
294
+ * **Modes**:
295
+ * - **native**: No-op (ability already loaded in-process as ES module)
296
+ * - **stdio**: Starts JSON-RPC server over stdin/stdout (for child processes)
297
+ * - **broker**: Connects to WebSocket broker and serves remotely
298
+ *
299
+ * @param mode - Transport mode to serve in ('native' | 'stdio' | 'broker')
300
+ * @returns Promise that resolves when server is ready (never resolves for stdio/broker - they keep running)
301
+ *
302
+ * @example
303
+ * ```typescript
304
+ * // ability.ts
305
+ * const client = new KadiClient({
306
+ * name: 'calculator',
307
+ * version: '1.0.0',
308
+ * brokers: { default: 'ws://localhost:8080' } // For broker mode
309
+ * });
310
+ *
311
+ * client.registerTool({ name: 'add', ... }, async ({ a, b }) => {
312
+ * return { result: a + b };
313
+ * });
314
+ *
315
+ * export default client;
316
+ *
317
+ * // If running standalone, start server
318
+ * if (import.meta.url === `file://${process.argv[1]}`) {
319
+ * await client.serve('stdio'); // or 'broker' for distributed mode
320
+ * }
321
+ * ```
322
+ */
323
+ serve(mode: 'native' | 'stdio' | 'broker'): Promise<void>;
324
+ /**
325
+ * Serve via stdio (JSON-RPC over stdin/stdout)
326
+ *
327
+ * **Steps**:
328
+ * 1. Redirect console.log to stderr (keeps stdout clean for JSON-RPC)
329
+ * 2. Create Content-Length message reader/writer for stdin/stdout
330
+ * 3. Listen for incoming JSON-RPC requests (invoke, readAgentJson, shutdown)
331
+ * 4. Execute tool handlers for 'invoke' requests
332
+ * 5. Send JSON-RPC responses back via stdout
333
+ * 6. Handle graceful shutdown on SIGTERM/SIGINT
334
+ * 7. Keep process alive until shutdown
335
+ *
336
+ * @returns Promise that never resolves (server runs until killed)
337
+ */
338
+ private serveStdio;
339
+ /**
340
+ * Serve via broker (WebSocket-based distributed mode)
341
+ *
342
+ * Connects to the broker, registers this ability's tools, and handles
343
+ * incoming tool invocation requests.
344
+ *
345
+ * **Steps**:
346
+ * 1. Get broker URL from constructor config
347
+ * 2. Create and connect to broker via BrokerConnection
348
+ * 3. Perform handshake and authentication
349
+ * 4. Register all tools from the registry
350
+ * 5. Listen for incoming 'kadi.ability.request' messages
351
+ * 6. Execute tool handlers and send responses
352
+ * 7. Handle graceful shutdown on SIGTERM/SIGINT
353
+ * 8. Keep process alive until shutdown
354
+ *
355
+ * @returns Promise that never resolves (server runs until killed)
356
+ * @throws {KadiError} If broker config missing or connection fails
357
+ * @private
358
+ */
359
+ private serveBroker;
360
+ /**
361
+ * Read agent.json representation
362
+ *
363
+ * Returns the runtime representation of this client's agent.json configuration.
364
+ * This enables KadiClient to function as an ability module that can be loaded
365
+ * natively by other agents.
366
+ *
367
+ * **What this returns:**
368
+ * - Represents what would be in the ability's agent.json file
369
+ * - Runtime `tools` field is mapped from agent.json's `exports` field
370
+ * - Used for discovery, type generation, and validation
371
+ *
372
+ * **Registration Pattern (Option A):**
373
+ * Ability developers write their code once using KadiClient.registerTool(),
374
+ * and it works across all transports (native, stdio, broker).
375
+ *
376
+ * @returns agent.json representation with name, version, and tools
377
+ *
378
+ * @example
379
+ * ```typescript
380
+ * // ability.ts (alongside agent.json file)
381
+ * const client = new KadiClient({ name: 'calculator', version: '1.0.0' });
382
+ * client.registerTool({ name: 'add', ... }, handler);
383
+ * export default client;
384
+ *
385
+ * // When loaded natively:
386
+ * const agentJson = client.readAgentJson();
387
+ * // { name: 'calculator', version: '1.0.0', tools: [...] }
388
+ * // ↑ Represents agent.json's runtime structure
389
+ * ```
390
+ */
391
+ readAgentJson(): AgentJson;
392
+ /**
393
+ * Invoke a tool by name
394
+ *
395
+ * Enables KadiClient to be used as an ability module. When loaded natively,
396
+ * this method is called by NativeTransport to execute registered tools.
397
+ *
398
+ * **Write Once, Run Anywhere:**
399
+ * The same tool handler works whether the ability is loaded natively,
400
+ * via stdio, or through a broker.
401
+ *
402
+ * @template TInput - Tool input type
403
+ * @template TOutput - Tool output type
404
+ * @param toolName - Name of the tool to invoke
405
+ * @param params - Tool input parameters
406
+ * @returns Promise resolving to tool result
407
+ *
408
+ * @throws {KadiError} If tool not found or invocation fails
409
+ *
410
+ * @example
411
+ * ```typescript
412
+ * // Registered tool:
413
+ * client.registerTool({ name: 'add', ... }, ({ a, b }) => ({ result: a + b }));
414
+ *
415
+ * // Invoke via agent.json protocol (native loading):
416
+ * const result = await client.invoke('add', { a: 5, b: 3 });
417
+ * // { result: 8 }
418
+ * ```
419
+ */
420
+ invoke<TInput = unknown, TOutput = unknown>(toolName: string, params: TInput): Promise<TOutput>;
232
421
  /**
233
422
  * Get broker manager (implements IBrokerClient)
234
423
  */
@@ -1 +1 @@
1
- {"version":3,"file":"KadiClient.d.ts","sourceRoot":"","sources":["../../src/client/KadiClient.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAEV,UAAU,EACV,cAAc,EACd,WAAW,EACX,QAAQ,EACR,aAAa,EACb,mBAAmB,EACnB,cAAc,EACf,MAAM,mBAAmB,CAAC;AAM3B,OAAO,EAAE,uBAAuB,EAAE,MAAM,sCAAsC,CAAC;AAC/E,OAAO,EAAE,cAAc,EAAoB,MAAM,6BAA6B,CAAC;AAC/E,OAAO,EAAiB,KAAK,aAAa,EAAE,KAAK,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAC5F,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AAEtE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,qBAAa,UAAW,SAAQ,YAAa,YAAW,aAAa;IACnE;;OAEG;IACH,SAAgB,MAAM,EAAE,cAAc,CAAC;IAEvC;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAe;IAErC;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAW;IAElC;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA0B;IAElD;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAqC;IAE/D;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAgB;IAE9C;;OAEG;IACH,OAAO,CAAC,WAAW,CAAS;IAE5B;;;;OAIG;gBACS,MAAM,EAAE,UAAU;IAoB9B;;;;;;;;;;OAUG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAmB9B;;OAEG;YACW,gBAAgB;IA4C9B;;;;OAIG;YACW,8BAA8B;IAY5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAkCG;IACH,YAAY,CAAC,MAAM,GAAG,OAAO,EAAE,OAAO,GAAG,OAAO,EAC9C,UAAU,EAAE,QAAQ,EACpB,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,GACpC,IAAI;IAwBP;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAsCG;IACG,IAAI,CACR,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,QAAQ,GAAG,OAAO,GAAG,QAAQ,EACxC,OAAO,GAAE,WAAgB,GACxB,OAAO,CAAC,aAAa,CAAC;IAIzB;;;;;;;;;;;;;;;;;OAiBG;IACH,gBAAgB,CAAC,CAAC,GAAG,OAAO,EAC1B,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,GACzB,mBAAmB;IAuBtB;;;;;;;;;;;;;;;OAeG;IACH,YAAY,CAAC,CAAC,GAAG,OAAO,EACtB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,CAAC,EACP,OAAO,CAAC,EAAE,cAAc,GACvB,IAAI;IAuBP;;;;;;;OAOG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAsBjC;;OAEG;IACH,gBAAgB,IAAI,uBAAuB;IAI3C;;;;OAIG;IACH,iBAAiB,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,cAAc;IAuBtD;;OAEG;IACH,IAAI,QAAQ,IAAI,MAAM,EAAE,CAEvB;IAED;;;OAGG;IACH,OAAO,CAAC,mBAAmB;IA4B3B;;OAEG;IACH,OAAO,CAAC,0BAA0B;IAwBlC;;;;;;;;;;;;OAYG;YACW,yBAAyB;CAwCxC"}
1
+ {"version":3,"file":"KadiClient.d.ts","sourceRoot":"","sources":["../../src/client/KadiClient.ts"],"names":[],"mappings":"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"}
@@ -18,6 +18,8 @@ import { EventHub } from '../events/EventHub.js';
18
18
  import { BrokerConnectionManager } from '../broker/BrokerConnectionManager.js';
19
19
  import { BrokerProtocol, PROTOCOL_VERSION } from '../broker/BrokerProtocol.js';
20
20
  import { AbilityLoader } from '../abilities/index.js';
21
+ // Import Zod utilities for automatic schema conversion
22
+ import { zodToJsonSchema, isZodSchema } from '../schemas/zod-to-json-schema.js';
21
23
  /**
22
24
  * KADI Client
23
25
  *
@@ -219,12 +221,44 @@ export class KadiClient extends EventEmitter {
219
221
  * ```
220
222
  */
221
223
  registerTool(definition, handler) {
222
- this.tools.register(definition, handler);
224
+ // Support both JSON Schema and Zod schemas
225
+ // Zod approach: { input: z.object(...), output: z.object(...) }
226
+ // JSON Schema approach: { inputSchema: {...}, outputSchema: {...} }
227
+ let finalDefinition;
228
+ // Check if using Zod schemas (has 'input' field instead of 'inputSchema')
229
+ if ('input' in definition && definition.input !== undefined) {
230
+ const zodDef = definition;
231
+ // Validate that input is actually a Zod schema
232
+ if (!isZodSchema(zodDef.input)) {
233
+ throw new KadiError(`Tool '${definition.name}': 'input' must be a Zod schema. Use 'inputSchema' for JSON Schema.`, ErrorCode.INVALID_INPUT, 400, { toolName: definition.name });
234
+ }
235
+ // Convert Zod schemas to JSON Schema
236
+ finalDefinition = {
237
+ name: zodDef.name,
238
+ description: zodDef.description,
239
+ title: zodDef.title,
240
+ version: zodDef.version,
241
+ tags: zodDef.tags,
242
+ networks: zodDef.networks,
243
+ // Convert input Zod schema → JSON Schema
244
+ inputSchema: zodToJsonSchema(zodDef.input),
245
+ // Convert output Zod schema → JSON Schema (if provided)
246
+ outputSchema: zodDef.output && isZodSchema(zodDef.output)
247
+ ? zodToJsonSchema(zodDef.output)
248
+ : undefined
249
+ };
250
+ }
251
+ else {
252
+ // Traditional JSON Schema approach - use as-is
253
+ finalDefinition = definition;
254
+ }
255
+ // Register the tool with the converted schemas
256
+ this.tools.register(finalDefinition, handler);
223
257
  // If already connected to brokers, register the new tool
224
258
  if (this.initialized && this.protocols.size > 0) {
225
259
  for (const protocol of this.protocols.values()) {
226
260
  protocol.registerCapabilities({
227
- tools: [definition],
261
+ tools: [finalDefinition], // Use converted definition
228
262
  networks: this.config.networks,
229
263
  displayName: this.config.name
230
264
  }).catch(error => {
@@ -234,6 +268,60 @@ export class KadiClient extends EventEmitter {
234
268
  }
235
269
  return this;
236
270
  }
271
+ /**
272
+ * Get a registered tool by name
273
+ *
274
+ * Retrieves a tool that has been registered with this client.
275
+ * Useful for inspecting registered tools or accessing their metadata.
276
+ *
277
+ * @param name - Tool name to retrieve
278
+ * @returns The registered tool with its definition and handler, or undefined if not found
279
+ *
280
+ * @example
281
+ * ```typescript
282
+ * const tool = client.getRegisteredTool('add');
283
+ * if (tool) {
284
+ * console.log('Tool:', tool.definition.name);
285
+ * console.log('Description:', tool.definition.description);
286
+ * }
287
+ * ```
288
+ */
289
+ getRegisteredTool(name) {
290
+ return this.tools.get(name);
291
+ }
292
+ /**
293
+ * Get all registered tools
294
+ *
295
+ * Returns an array of all tools that have been registered with this client.
296
+ * Useful for listing available tools or debugging.
297
+ *
298
+ * @returns Array of all registered tools
299
+ *
300
+ * @example
301
+ * ```typescript
302
+ * const allTools = client.getAllRegisteredTools();
303
+ * console.log('Available tools:', allTools.map(t => t.definition.name));
304
+ * ```
305
+ */
306
+ getAllRegisteredTools() {
307
+ return this.tools.getAll();
308
+ }
309
+ /**
310
+ * Check if a tool is registered
311
+ *
312
+ * @param name - Tool name to check
313
+ * @returns true if the tool is registered, false otherwise
314
+ *
315
+ * @example
316
+ * ```typescript
317
+ * if (client.hasRegisteredTool('add')) {
318
+ * // Tool is available
319
+ * }
320
+ * ```
321
+ */
322
+ hasRegisteredTool(name) {
323
+ return this.tools.has(name);
324
+ }
237
325
  /**
238
326
  * Load an ability
239
327
  *
@@ -364,6 +452,328 @@ export class KadiClient extends EventEmitter {
364
452
  this.protocols.clear();
365
453
  this.initialized = false;
366
454
  this.emit('disconnected');
455
+ // If running in stdio mode, exit process
456
+ if (this.stdioServer) {
457
+ this.stdioServer.shutdown();
458
+ }
459
+ }
460
+ /**
461
+ * Stdio server instance (when running in stdio mode)
462
+ */
463
+ stdioServer;
464
+ /**
465
+ * Start serving this ability in the specified mode
466
+ *
467
+ * **Universal Entry Point**:
468
+ * Starts the appropriate server based on the mode parameter.
469
+ * This enables the same ability code to work across all transports
470
+ *
471
+ * **Modes**:
472
+ * - **native**: No-op (ability already loaded in-process as ES module)
473
+ * - **stdio**: Starts JSON-RPC server over stdin/stdout (for child processes)
474
+ * - **broker**: Connects to WebSocket broker and serves remotely
475
+ *
476
+ * @param mode - Transport mode to serve in ('native' | 'stdio' | 'broker')
477
+ * @returns Promise that resolves when server is ready (never resolves for stdio/broker - they keep running)
478
+ *
479
+ * @example
480
+ * ```typescript
481
+ * // ability.ts
482
+ * const client = new KadiClient({
483
+ * name: 'calculator',
484
+ * version: '1.0.0',
485
+ * brokers: { default: 'ws://localhost:8080' } // For broker mode
486
+ * });
487
+ *
488
+ * client.registerTool({ name: 'add', ... }, async ({ a, b }) => {
489
+ * return { result: a + b };
490
+ * });
491
+ *
492
+ * export default client;
493
+ *
494
+ * // If running standalone, start server
495
+ * if (import.meta.url === `file://${process.argv[1]}`) {
496
+ * await client.serve('stdio'); // or 'broker' for distributed mode
497
+ * }
498
+ * ```
499
+ */
500
+ async serve(mode) {
501
+ if (mode === 'stdio') {
502
+ return this.serveStdio();
503
+ }
504
+ if (mode === 'broker') {
505
+ return this.serveBroker();
506
+ }
507
+ // Native mode - ability already loaded in-process, nothing to serve
508
+ return Promise.resolve();
509
+ }
510
+ /**
511
+ * Serve via stdio (JSON-RPC over stdin/stdout)
512
+ *
513
+ * **Steps**:
514
+ * 1. Redirect console.log to stderr (keeps stdout clean for JSON-RPC)
515
+ * 2. Create Content-Length message reader/writer for stdin/stdout
516
+ * 3. Listen for incoming JSON-RPC requests (invoke, readAgentJson, shutdown)
517
+ * 4. Execute tool handlers for 'invoke' requests
518
+ * 5. Send JSON-RPC responses back via stdout
519
+ * 6. Handle graceful shutdown on SIGTERM/SIGINT
520
+ * 7. Keep process alive until shutdown
521
+ *
522
+ * @returns Promise that never resolves (server runs until killed)
523
+ */
524
+ async serveStdio() {
525
+ const { StdioMessageReader } = await import('../utils/StdioMessageReader.js');
526
+ const { StdioMessageWriter } = await import('../utils/StdioMessageWriter.js');
527
+ // Step 1: Redirect console.log to stderr (keeps stdout clean for JSON-RPC)
528
+ const originalLog = console.log;
529
+ console.log = console.error;
530
+ // Step 2: Create Content-Length message reader/writer for stdin/stdout
531
+ const reader = new StdioMessageReader();
532
+ const writer = new StdioMessageWriter(process.stdout);
533
+ // Step 3: Listen for incoming JSON-RPC requests
534
+ reader.on('message', async (message) => {
535
+ try {
536
+ const { id, method, params } = message;
537
+ if (method === 'invoke') {
538
+ // Step 4: Execute tool handler for 'invoke' request
539
+ const { toolName, toolInput } = params;
540
+ const result = await this.invoke(toolName, toolInput);
541
+ // Step 5: Send JSON-RPC success response
542
+ await writer.write({
543
+ jsonrpc: '2.0',
544
+ id,
545
+ result
546
+ });
547
+ }
548
+ else if (method === 'readAgentJson') {
549
+ // Return agent.json representation (used during discovery)
550
+ const agentJson = this.readAgentJson();
551
+ await writer.write({
552
+ jsonrpc: '2.0',
553
+ id,
554
+ result: agentJson
555
+ });
556
+ }
557
+ else if (method === 'shutdown') {
558
+ // Graceful shutdown request
559
+ await writer.write({
560
+ jsonrpc: '2.0',
561
+ id,
562
+ result: { success: true }
563
+ });
564
+ // Restore console.log
565
+ console.log = originalLog;
566
+ // Exit after short delay to allow response to flush
567
+ setTimeout(() => {
568
+ process.exit(0);
569
+ }, 100);
570
+ }
571
+ else {
572
+ // Unknown method - send error
573
+ await writer.write({
574
+ jsonrpc: '2.0',
575
+ id,
576
+ error: {
577
+ code: -32601,
578
+ message: `Method not found: ${method}`
579
+ }
580
+ });
581
+ }
582
+ }
583
+ catch (error) {
584
+ // Step 5: Send JSON-RPC error response on failure
585
+ const kadiError = error instanceof KadiError ? error : KadiError.from(error);
586
+ await writer.write({
587
+ jsonrpc: '2.0',
588
+ id: message.id,
589
+ error: {
590
+ code: kadiError.statusCode || -32603,
591
+ message: kadiError.message,
592
+ data: kadiError.context
593
+ }
594
+ });
595
+ }
596
+ });
597
+ reader.on('error', (error) => {
598
+ // Log parse errors to stderr
599
+ console.error('[KADI Stdio Server] Parse error:', error.message);
600
+ });
601
+ // Feed stdin data to reader for Content-Length framing
602
+ process.stdin.on('data', (chunk) => {
603
+ reader.onData(chunk);
604
+ });
605
+ // Step 6: Handle graceful shutdown on SIGTERM/SIGINT
606
+ const cleanup = () => {
607
+ console.log = originalLog;
608
+ reader.destroy();
609
+ writer.destroy();
610
+ process.exit(0);
611
+ };
612
+ process.on('SIGTERM', cleanup);
613
+ process.on('SIGINT', cleanup);
614
+ // Store server instance for programmatic shutdown
615
+ this.stdioServer = {
616
+ shutdown: () => {
617
+ console.log = originalLog;
618
+ reader.destroy();
619
+ writer.destroy();
620
+ }
621
+ };
622
+ // Step 7: Keep process alive until shutdown
623
+ return new Promise(() => {
624
+ // Never resolves - server runs until killed
625
+ });
626
+ }
627
+ /**
628
+ * Serve via broker (WebSocket-based distributed mode)
629
+ *
630
+ * Connects to the broker, registers this ability's tools, and handles
631
+ * incoming tool invocation requests.
632
+ *
633
+ * **Steps**:
634
+ * 1. Get broker URL from constructor config
635
+ * 2. Create and connect to broker via BrokerConnection
636
+ * 3. Perform handshake and authentication
637
+ * 4. Register all tools from the registry
638
+ * 5. Listen for incoming 'kadi.ability.request' messages
639
+ * 6. Execute tool handlers and send responses
640
+ * 7. Handle graceful shutdown on SIGTERM/SIGINT
641
+ * 8. Keep process alive until shutdown
642
+ *
643
+ * @returns Promise that never resolves (server runs until killed)
644
+ * @throws {KadiError} If broker config missing or connection fails
645
+ * @private
646
+ */
647
+ async serveBroker() {
648
+ // Step 1: Validate broker configuration exists
649
+ if (!this.brokers) {
650
+ throw new KadiError('Cannot serve in broker mode - no broker configuration provided', ErrorCode.INVALID_CONFIG, 400, {
651
+ suggestion: 'Add brokers configuration to KadiClient constructor',
652
+ example: 'new KadiClient({ brokers: { default: "ws://localhost:8080" } })'
653
+ });
654
+ }
655
+ // Step 2: Connect to broker (this performs handshake and registers capabilities)
656
+ await this.connect();
657
+ // Step 3: Get connection for error/disconnect handling
658
+ const connection = this.brokers.getDefaultConnection();
659
+ // Note: Tool invocation requests are handled by setupBrokerEventForwarding()
660
+ // which was called during initialization. No need to duplicate the listener here.
661
+ // Handle connection errors
662
+ connection.on('error', (error) => {
663
+ console.error('[KADI Broker Server] Connection error:', error);
664
+ });
665
+ // Handle disconnection
666
+ connection.on('disconnected', (code, reason) => {
667
+ console.log(`[KADI Broker Server] Disconnected from broker (code: ${code}, reason: ${reason})`);
668
+ // Broker connection should auto-reconnect if configured
669
+ });
670
+ // Step 7: Handle graceful shutdown on SIGTERM/SIGINT
671
+ const shutdown = async () => {
672
+ console.log('\n[KADI Broker Server] Shutting down...');
673
+ await connection.disconnect();
674
+ console.log('[KADI Broker Server] Disconnected from broker');
675
+ process.exit(0);
676
+ };
677
+ process.on('SIGTERM', shutdown);
678
+ process.on('SIGINT', shutdown);
679
+ // Step 8: Keep process alive until shutdown
680
+ return new Promise(() => {
681
+ // Never resolves - server runs until killed
682
+ });
683
+ }
684
+ /**
685
+ * Read agent.json representation
686
+ *
687
+ * Returns the runtime representation of this client's agent.json configuration.
688
+ * This enables KadiClient to function as an ability module that can be loaded
689
+ * natively by other agents.
690
+ *
691
+ * **What this returns:**
692
+ * - Represents what would be in the ability's agent.json file
693
+ * - Runtime `tools` field is mapped from agent.json's `exports` field
694
+ * - Used for discovery, type generation, and validation
695
+ *
696
+ * **Registration Pattern (Option A):**
697
+ * Ability developers write their code once using KadiClient.registerTool(),
698
+ * and it works across all transports (native, stdio, broker).
699
+ *
700
+ * @returns agent.json representation with name, version, and tools
701
+ *
702
+ * @example
703
+ * ```typescript
704
+ * // ability.ts (alongside agent.json file)
705
+ * const client = new KadiClient({ name: 'calculator', version: '1.0.0' });
706
+ * client.registerTool({ name: 'add', ... }, handler);
707
+ * export default client;
708
+ *
709
+ * // When loaded natively:
710
+ * const agentJson = client.readAgentJson();
711
+ * // { name: 'calculator', version: '1.0.0', tools: [...] }
712
+ * // ↑ Represents agent.json's runtime structure
713
+ * ```
714
+ */
715
+ readAgentJson() {
716
+ return {
717
+ name: this.config.name,
718
+ version: this.config.version,
719
+ description: this.config.description,
720
+ tools: this.tools.extractDefinitions()
721
+ };
722
+ }
723
+ /**
724
+ * Invoke a tool by name
725
+ *
726
+ * Enables KadiClient to be used as an ability module. When loaded natively,
727
+ * this method is called by NativeTransport to execute registered tools.
728
+ *
729
+ * **Write Once, Run Anywhere:**
730
+ * The same tool handler works whether the ability is loaded natively,
731
+ * via stdio, or through a broker.
732
+ *
733
+ * @template TInput - Tool input type
734
+ * @template TOutput - Tool output type
735
+ * @param toolName - Name of the tool to invoke
736
+ * @param params - Tool input parameters
737
+ * @returns Promise resolving to tool result
738
+ *
739
+ * @throws {KadiError} If tool not found or invocation fails
740
+ *
741
+ * @example
742
+ * ```typescript
743
+ * // Registered tool:
744
+ * client.registerTool({ name: 'add', ... }, ({ a, b }) => ({ result: a + b }));
745
+ *
746
+ * // Invoke via agent.json protocol (native loading):
747
+ * const result = await client.invoke('add', { a: 5, b: 3 });
748
+ * // { result: 8 }
749
+ * ```
750
+ */
751
+ async invoke(toolName, params) {
752
+ // Look up the tool handler
753
+ const handler = this.tools.getHandler(toolName);
754
+ if (!handler) {
755
+ throw new KadiError(`Tool '${toolName}' not found`, ErrorCode.TOOL_NOT_FOUND, 404, {
756
+ toolName,
757
+ availableTools: this.tools.extractDefinitions().map(t => t.name),
758
+ suggestion: 'Check tool name or register tool first'
759
+ });
760
+ }
761
+ try {
762
+ // Execute the handler
763
+ const result = await handler(params);
764
+ return result;
765
+ }
766
+ catch (error) {
767
+ // Enhance error with context
768
+ if (error instanceof KadiError) {
769
+ throw error;
770
+ }
771
+ throw new KadiError(`Tool invocation failed: ${error instanceof Error ? error.message : String(error)}`, ErrorCode.TOOL_INVOCATION_FAILED, 500, {
772
+ toolName,
773
+ input: params,
774
+ originalError: error instanceof Error ? error.message : String(error)
775
+ });
776
+ }
367
777
  }
368
778
  /**
369
779
  * Get broker manager (implements IBrokerClient)