@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 +78 -15
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +138 -44
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/transports/broker.d.ts +1 -1
- package/dist/transports/broker.d.ts.map +1 -1
- package/dist/transports/broker.js +64 -36
- package/dist/transports/broker.js.map +1 -1
- package/dist/transports/native.d.ts +6 -1
- package/dist/transports/native.d.ts.map +1 -1
- package/dist/transports/native.js +37 -7
- package/dist/transports/native.js.map +1 -1
- package/dist/transports/stdio.d.ts.map +1 -1
- package/dist/transports/stdio.js +14 -9
- package/dist/transports/stdio.js.map +1 -1
- package/dist/types.d.ts +135 -26
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
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,
|
|
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.
|
|
78
|
-
* 2.
|
|
79
|
-
* 3.
|
|
80
|
-
* 4.
|
|
81
|
-
* 5.
|
|
82
|
-
* 6.
|
|
83
|
-
* 7.
|
|
84
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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 -
|
|
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
|
-
|
|
449
|
+
createToolBridge(): ToolExecutionBridge;
|
|
387
450
|
/**
|
|
388
451
|
* Load an in-process ability via dynamic import.
|
|
389
452
|
*
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,OAAO,KAAK,EACV,YAAY,EAEZ,WAAW,EAEX,cAAc,EACd,iBAAiB,EACjB,WAAW,EACX,aAAa,EACb,mBAAmB,EACnB,iBAAiB,EACjB,gBAAgB,EAChB,iBAAiB,EACjB,mBAAmB,
|
|
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 =
|
|
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.
|
|
235
|
-
* 2.
|
|
236
|
-
* 3.
|
|
237
|
-
* 4.
|
|
238
|
-
* 5.
|
|
239
|
-
* 6.
|
|
240
|
-
* 7.
|
|
241
|
-
*
|
|
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
|
-
//
|
|
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(
|
|
368
|
+
buildAuthMessage(nonce) {
|
|
369
|
+
// Sign the nonce using the client's private key
|
|
306
370
|
const privateKey = crypto.createPrivateKey({
|
|
307
|
-
key:
|
|
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:
|
|
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(
|
|
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
|
-
|
|
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.
|
|
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
|
-
*
|
|
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
|
-
*
|
|
1305
|
+
* Execute a tool registered on this client.
|
|
1237
1306
|
*
|
|
1238
|
-
*
|
|
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 -
|
|
1319
|
+
* @param context - Request context with caller info (provided by broker/stdio)
|
|
1320
|
+
* @internal
|
|
1241
1321
|
*/
|
|
1242
|
-
async
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|