@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.
- package/README.md +269 -1311
- package/dist/abilities/AbilityLoader.d.ts +26 -0
- package/dist/abilities/AbilityLoader.d.ts.map +1 -1
- package/dist/abilities/AbilityLoader.js +141 -18
- package/dist/abilities/AbilityLoader.js.map +1 -1
- package/dist/abilities/AbilityProxy.d.ts +33 -0
- package/dist/abilities/AbilityProxy.d.ts.map +1 -1
- package/dist/abilities/AbilityProxy.js +40 -0
- package/dist/abilities/AbilityProxy.js.map +1 -1
- package/dist/abilities/index.d.ts +1 -1
- package/dist/abilities/index.d.ts.map +1 -1
- package/dist/abilities/types.d.ts +67 -0
- package/dist/abilities/types.d.ts.map +1 -1
- package/dist/broker/BrokerProtocol.js +11 -11
- package/dist/broker/BrokerProtocol.js.map +1 -1
- package/dist/client/KadiClient.d.ts +191 -2
- package/dist/client/KadiClient.d.ts.map +1 -1
- package/dist/client/KadiClient.js +412 -2
- package/dist/client/KadiClient.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/messages/index.d.ts +1 -1
- package/dist/messages/index.js +1 -1
- package/dist/messages/index.js.map +1 -1
- package/dist/schemas/index.d.ts +3 -0
- package/dist/schemas/index.d.ts.map +1 -1
- package/dist/schemas/index.js +2 -0
- package/dist/schemas/index.js.map +1 -1
- package/dist/schemas/zod-helpers.d.ts +129 -0
- package/dist/schemas/zod-helpers.d.ts.map +1 -0
- package/dist/schemas/zod-helpers.js +225 -0
- package/dist/schemas/zod-helpers.js.map +1 -0
- package/dist/schemas/zod-to-json-schema.d.ts +159 -0
- package/dist/schemas/zod-to-json-schema.d.ts.map +1 -0
- package/dist/schemas/zod-to-json-schema.js +154 -0
- package/dist/schemas/zod-to-json-schema.js.map +1 -0
- package/dist/transports/NativeTransport.d.ts +29 -0
- package/dist/transports/NativeTransport.d.ts.map +1 -1
- package/dist/transports/NativeTransport.js +98 -3
- package/dist/transports/NativeTransport.js.map +1 -1
- package/dist/transports/StdioTransport.d.ts +141 -63
- package/dist/transports/StdioTransport.d.ts.map +1 -1
- package/dist/transports/StdioTransport.js +309 -232
- package/dist/transports/StdioTransport.js.map +1 -1
- package/dist/types/broker.d.ts +0 -22
- package/dist/types/broker.d.ts.map +1 -1
- package/dist/types/broker.js +0 -27
- package/dist/types/broker.js.map +1 -1
- package/dist/types/index.d.ts +3 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/zod-tools.d.ts +198 -0
- package/dist/types/zod-tools.d.ts.map +1 -0
- package/dist/types/zod-tools.js +14 -0
- package/dist/types/zod-tools.js.map +1 -0
- package/dist/utils/LockfileResolver.d.ts +108 -0
- package/dist/utils/LockfileResolver.d.ts.map +1 -0
- package/dist/utils/LockfileResolver.js +230 -0
- package/dist/utils/LockfileResolver.js.map +1 -0
- package/dist/utils/StdioMessageReader.d.ts +122 -0
- package/dist/utils/StdioMessageReader.d.ts.map +1 -0
- package/dist/utils/StdioMessageReader.js +209 -0
- package/dist/utils/StdioMessageReader.js.map +1 -0
- package/dist/utils/StdioMessageWriter.d.ts +104 -0
- package/dist/utils/StdioMessageWriter.d.ts.map +1 -0
- package/dist/utils/StdioMessageWriter.js +162 -0
- package/dist/utils/StdioMessageWriter.js.map +1 -0
- 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
|
|
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;
|
|
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
|
-
|
|
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: [
|
|
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)
|