@kadi.build/core 0.7.2 → 0.8.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/README.md CHANGED
@@ -107,6 +107,59 @@ client.registerTool({
107
107
  client.registerTool(definition, handler, { brokers: ['internal'] });
108
108
  ```
109
109
 
110
+ ### Targeting Specific Networks
111
+
112
+ By default, a tool is visible on **all networks** the agent has joined. You can restrict a tool to specific networks using the `networks` option — the tool will only be discoverable and invocable by agents on those networks.
113
+
114
+ ```typescript
115
+ const client = new KadiClient({
116
+ name: 'multi-network-agent',
117
+ brokers: { default: 'ws://localhost:8080/kadi' },
118
+ networks: ['global', 'internal', 'gpu-cluster'],
119
+ });
120
+
121
+ // Visible on ALL agent networks (global, internal, gpu-cluster)
122
+ client.registerTool({
123
+ name: 'echo',
124
+ description: 'Echo back the input',
125
+ input: z.object({ message: z.string() }),
126
+ }, async ({ message }) => ({ echo: message }));
127
+
128
+ // Visible ONLY on gpu-cluster
129
+ client.registerTool({
130
+ name: 'gpu-inference',
131
+ description: 'Run ML inference on GPU',
132
+ input: z.object({ model: z.string(), prompt: z.string() }),
133
+ }, async ({ model, prompt }) => {
134
+ return { result: await runInference(model, prompt) };
135
+ }, { networks: ['gpu-cluster'] });
136
+
137
+ // Visible on internal AND global only (not gpu-cluster)
138
+ client.registerTool({
139
+ name: 'audit-log',
140
+ description: 'Retrieve audit logs',
141
+ input: z.object({ limit: z.number() }),
142
+ }, async ({ limit }) => {
143
+ return { logs: await getAuditLogs(limit) };
144
+ }, { networks: ['internal', 'global'] });
145
+ ```
146
+
147
+ Per-tool networks must be a **subset** of the client's `networks`. Attempting to scope a tool to a network the agent hasn't joined throws an `INVALID_CONFIG` error:
148
+
149
+ ```typescript
150
+ // Throws KadiError — 'finance' is not in client networks
151
+ client.registerTool(definition, handler, { networks: ['finance'] });
152
+ ```
153
+
154
+ You can combine `brokers` and `networks` to control both which brokers a tool registers with and which networks it's visible on:
155
+
156
+ ```typescript
157
+ client.registerTool(definition, handler, {
158
+ brokers: ['production'],
159
+ networks: ['internal'],
160
+ });
161
+ ```
162
+
110
163
  ---
111
164
 
112
165
  ## Connecting to Brokers
package/dist/client.d.ts CHANGED
@@ -15,7 +15,7 @@
15
15
  * - Readable, traceable code flow
16
16
  */
17
17
  import { type EncryptionKeyPair } from './crypto.js';
18
- import type { ClientConfig, ClientIdentity, BrokerState, ToolDefinition, ZodToolDefinition, ToolHandler, LoadedAbility, ToolExecutionBridge, RegisterToolOptions, LoadNativeOptions, LoadStdioOptions, LoadBrokerOptions, InvokeRemoteOptions, BrokerEventHandler, PublishOptions, SubscribeOptions, EmitOptions } from './types.js';
18
+ import type { ClientConfig, ClientIdentity, BrokerState, BrokerToolDefinition, ZodToolDefinition, ToolHandler, LoadedAbility, ToolExecutionBridge, RegisterToolOptions, LoadNativeOptions, LoadStdioOptions, LoadBrokerOptions, InvokeRemoteOptions, BrokerEventHandler, PublishOptions, SubscribeOptions, EmitOptions } from './types.js';
19
19
  /**
20
20
  * The main client for building KADI agents.
21
21
  *
@@ -25,8 +25,8 @@ import type { ClientConfig, ClientIdentity, BrokerState, ToolDefinition, ZodTool
25
25
  * const client = new KadiClient({
26
26
  * name: 'my-agent',
27
27
  * brokers: {
28
- * production: 'ws://broker-prod:8080',
29
- * internal: 'ws://broker-internal:8080',
28
+ * production: { url: 'ws://broker-prod:8080/kadi' },
29
+ * internal: { url: 'ws://broker-internal:8080/kadi', networks: ['private'] },
30
30
  * },
31
31
  * defaultBroker: 'production',
32
32
  * });
@@ -404,6 +404,11 @@ export declare class KadiClient {
404
404
  * Handle response to a pending request.
405
405
  */
406
406
  private handleBrokerResponse;
407
+ /**
408
+ * Finalize a broker connection: start heartbeat and mark as connected.
409
+ * Shared by initial connect and reconnect paths.
410
+ */
411
+ private finalizeConnection;
407
412
  /**
408
413
  * Send heartbeat to keep connection alive.
409
414
  */
@@ -526,6 +531,11 @@ export declare class KadiClient {
526
531
  * ```
527
532
  */
528
533
  registerTool<TInput, TOutput>(definition: ZodToolDefinition<TInput, TOutput>, handler: ToolHandler<TInput, TOutput>, options?: RegisterToolOptions): void;
534
+ /**
535
+ * Get the union of all networks configured across all brokers.
536
+ * Used by registerTool() to validate per-tool network targeting.
537
+ */
538
+ private getAllConfiguredNetworks;
529
539
  /**
530
540
  * Get tool definitions, optionally filtered for a specific broker.
531
541
  *
@@ -697,7 +707,7 @@ export declare class KadiClient {
697
707
  readAgentJson(): {
698
708
  name: string;
699
709
  version: string;
700
- tools: ToolDefinition[];
710
+ tools: BrokerToolDefinition[];
701
711
  };
702
712
  /**
703
713
  * Get broker connection state (for broker transport).
@@ -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
@@ -20,6 +20,7 @@ import { convertToEncryptionKey, convertToEncryptionKeyPair, } from './crypto.js
20
20
  // @ts-expect-error - tweetnacl-sealedbox-js doesn't have type definitions
21
21
  import sealedbox from 'tweetnacl-sealedbox-js';
22
22
  import { KadiError } from './errors.js';
23
+ import * as protocol from './protocol.js';
23
24
  import { zodToJsonSchema, isZodSchema } from './zod.js';
24
25
  import { resolveAbilityEntry, resolveAbilityScript } from './lockfile.js';
25
26
  import { loadNativeTransport } from './transports/native.js';
@@ -112,8 +113,8 @@ function isMcpCallToolResult(result) {
112
113
  * const client = new KadiClient({
113
114
  * name: 'my-agent',
114
115
  * brokers: {
115
- * production: 'ws://broker-prod:8080',
116
- * internal: 'ws://broker-internal:8080',
116
+ * production: { url: 'ws://broker-prod:8080/kadi' },
117
+ * internal: { url: 'ws://broker-internal:8080/kadi', networks: ['private'] },
117
118
  * },
118
119
  * defaultBroker: 'production',
119
120
  * });
@@ -182,16 +183,21 @@ export class KadiClient {
182
183
  // Derive agentId using same algorithm as broker: SHA256(publicKey).hex().substring(0, 16)
183
184
  this._agentId = crypto.createHash('sha256').update(this._publicKeyBase64).digest('hex').substring(0, 16);
184
185
  // Resolve configuration with defaults
185
- // Auto-select first broker as default if not specified (matches Python behavior)
186
- const brokers = config.brokers ?? {};
187
- const firstBrokerName = Object.keys(brokers)[0];
186
+ const rawBrokers = config.brokers ?? {};
187
+ const normalizedBrokers = {};
188
+ for (const [name, entry] of Object.entries(rawBrokers)) {
189
+ normalizedBrokers[name] = {
190
+ url: entry.url,
191
+ networks: entry.networks ?? ['global'],
192
+ };
193
+ }
194
+ const firstBrokerName = Object.keys(normalizedBrokers)[0];
188
195
  this.config = {
189
196
  name: config.name,
190
197
  version: config.version ?? '1.0.0',
191
198
  description: config.description ?? '',
192
- brokers,
199
+ brokers: normalizedBrokers,
193
200
  defaultBroker: config.defaultBroker ?? firstBrokerName,
194
- networks: config.networks ?? ['global'],
195
201
  heartbeatInterval: config.heartbeatInterval ?? DEFAULT_HEARTBEAT_INTERVAL,
196
202
  requestTimeout: config.requestTimeout ?? DEFAULT_REQUEST_TIMEOUT,
197
203
  autoReconnect: config.autoReconnect ?? DEFAULT_AUTO_RECONNECT,
@@ -364,8 +370,6 @@ export class KadiClient {
364
370
  for (const [i, result] of results.entries()) {
365
371
  if (result.status === 'rejected') {
366
372
  const failedBroker = brokerNames[i];
367
- if (failedBroker === undefined)
368
- continue;
369
373
  const reason = result.reason instanceof Error ? result.reason.message : String(result.reason);
370
374
  console.error(`[KADI] Failed to connect to broker "${failedBroker}": ${reason}`);
371
375
  failures.push(failedBroker);
@@ -397,14 +401,15 @@ export class KadiClient {
397
401
  * across all broker connections, ensuring consistent identity.
398
402
  */
399
403
  async connectToBroker(brokerName) {
400
- const url = this.config.brokers[brokerName];
401
- if (!url) {
404
+ const brokerConfig = this.config.brokers[brokerName];
405
+ if (!brokerConfig) {
402
406
  throw new KadiError(`Broker "${brokerName}" not found in configuration`, 'UNKNOWN_BROKER', {
403
407
  broker: brokerName,
404
408
  available: Object.keys(this.config.brokers),
405
409
  hint: 'Check your brokers configuration',
406
410
  });
407
411
  }
412
+ const url = brokerConfig.url;
408
413
  // Check if already connected
409
414
  if (this.brokers.has(brokerName)) {
410
415
  const existing = this.brokers.get(brokerName);
@@ -416,6 +421,7 @@ export class KadiClient {
416
421
  const state = {
417
422
  name: brokerName,
418
423
  url,
424
+ networks: brokerConfig.networks,
419
425
  ws: null,
420
426
  heartbeatTimer: null,
421
427
  pendingRequests: new Map(),
@@ -436,22 +442,14 @@ export class KadiClient {
436
442
  await this.performHandshake(state);
437
443
  // Register with broker (transitions to "ready" state)
438
444
  await this.registerWithBroker(state);
439
- // Start heartbeat
440
- state.heartbeatTimer = setInterval(() => {
441
- this.sendHeartbeat(state);
442
- }, this.config.heartbeatInterval);
443
- state.status = 'connected';
445
+ // Finalize connection (heartbeat + status)
446
+ this.finalizeConnection(state);
444
447
  }
445
448
  // ─────────────────────────────────────────────────────────────
446
449
  // MESSAGE BUILDERS
447
450
  // ─────────────────────────────────────────────────────────────
448
451
  buildHelloMessage() {
449
- return {
450
- jsonrpc: '2.0',
451
- id: this.nextRequestId++,
452
- method: 'kadi.session.hello',
453
- params: { role: 'agent' },
454
- };
452
+ return protocol.hello(this.nextRequestId++);
455
453
  }
456
454
  buildAuthMessage(nonce) {
457
455
  // Sign the nonce using the client's private key
@@ -461,36 +459,13 @@ export class KadiClient {
461
459
  type: 'pkcs8',
462
460
  });
463
461
  const signature = crypto.sign(null, Buffer.from(nonce, 'utf8'), privateKey);
464
- return {
465
- jsonrpc: '2.0',
466
- id: this.nextRequestId++,
467
- method: 'kadi.session.authenticate',
468
- params: {
469
- publicKey: this._publicKeyBase64,
470
- signature: signature.toString('base64'),
471
- nonce,
472
- },
473
- };
462
+ return protocol.authenticate(this.nextRequestId++, this._publicKeyBase64, signature.toString('base64'), nonce);
474
463
  }
475
464
  buildRegisterMessage(tools, networks) {
476
- return {
477
- jsonrpc: '2.0',
478
- id: this.nextRequestId++,
479
- method: 'kadi.agent.register',
480
- params: {
481
- tools,
482
- networks,
483
- displayName: this.config.name,
484
- },
485
- };
465
+ return protocol.register(this.nextRequestId++, tools, networks, this.config.name);
486
466
  }
487
467
  buildHeartbeatMessage() {
488
- return {
489
- jsonrpc: '2.0',
490
- id: this.nextRequestId++,
491
- method: 'kadi.session.heartbeat',
492
- params: { timestamp: Date.now() },
493
- };
468
+ return protocol.heartbeat(this.nextRequestId++);
494
469
  }
495
470
  // ─────────────────────────────────────────────────────────────
496
471
  // CONNECTION HELPERS
@@ -593,7 +568,11 @@ export class KadiClient {
593
568
  // Note: If registration fails, sendRequest will reject with the error.
594
569
  // We don't need to check response.error here - errors are already
595
570
  // handled via Promise rejection in handleBrokerResponse.
596
- await this.sendRequest(state, this.buildRegisterMessage(tools, this.config.networks));
571
+ const { droppedNetworks } = await this.sendRequest(state, this.buildRegisterMessage(tools, state.networks));
572
+ if (droppedNetworks && droppedNetworks.length > 0) {
573
+ console.warn(`[KADI] Broker "${state.name}" dropped networks ${JSON.stringify(droppedNetworks)} ` +
574
+ `during registration. Tools scoped to those networks will not be discoverable.`);
575
+ }
597
576
  }
598
577
  /**
599
578
  * Send a JSON-RPC request and wait for the response.
@@ -862,12 +841,7 @@ export class KadiClient {
862
841
  handlers.add(handler);
863
842
  // If this is the first handler for this pattern, subscribe on broker
864
843
  if (!state.subscribedPatterns.has(pattern)) {
865
- await this.sendRequest(state, {
866
- jsonrpc: '2.0',
867
- id: this.nextRequestId++,
868
- method: 'kadi.event.subscribe',
869
- params: { pattern },
870
- });
844
+ await this.sendRequest(state, protocol.eventSubscribe(this.nextRequestId++, pattern));
871
845
  state.subscribedPatterns.add(pattern);
872
846
  }
873
847
  }
@@ -913,12 +887,7 @@ export class KadiClient {
913
887
  // Unsubscribe from broker if we were subscribed
914
888
  if (state.subscribedPatterns.has(pattern) && state.status === 'connected') {
915
889
  try {
916
- await this.sendRequest(state, {
917
- jsonrpc: '2.0',
918
- id: this.nextRequestId++,
919
- method: 'kadi.event.unsubscribe',
920
- params: { pattern },
921
- });
890
+ await this.sendRequest(state, protocol.eventUnsubscribe(this.nextRequestId++, pattern));
922
891
  }
923
892
  catch {
924
893
  // Ignore errors during unsubscribe (broker might be disconnecting)
@@ -949,19 +918,10 @@ export class KadiClient {
949
918
  */
950
919
  async publish(channel, data, options = {}) {
951
920
  const { state } = this.getConnectedBrokerState(options.broker);
952
- // Resolve which network to publish to
953
- const networkId = options.network ?? this.config.networks[0] ?? 'global';
921
+ // Resolve which network to publish to (prefer broker-specific, fallback to global)
922
+ const networkId = options.network ?? state.networks[0] ?? '';
954
923
  // Send publish request to broker
955
- await this.sendRequest(state, {
956
- jsonrpc: '2.0',
957
- id: this.nextRequestId++,
958
- method: 'kadi.event.publish',
959
- params: {
960
- channel,
961
- data,
962
- networkId,
963
- },
964
- });
924
+ await this.sendRequest(state, protocol.eventPublish(this.nextRequestId++, channel, data, networkId));
965
925
  }
966
926
  // ─────────────────────────────────────────────────────────────
967
927
  // DEPLOYMENT SECRETS (Trusted Introducer Pattern)
@@ -1191,35 +1151,18 @@ export class KadiClient {
1191
1151
  // KADI clients receive raw structured data
1192
1152
  responseResult = result;
1193
1153
  }
1194
- const response = {
1195
- jsonrpc: '2.0',
1196
- id: requestId,
1197
- result: responseResult,
1198
- };
1199
- state.ws?.send(JSON.stringify(response));
1154
+ state.ws?.send(JSON.stringify(protocol.resultResponse(requestId, responseResult)));
1200
1155
  }
1201
1156
  catch (error) {
1202
1157
  const errorMessage = error instanceof Error ? error.message : String(error);
1203
1158
  // For MCP clients, wrap errors in CallToolResult format too
1204
1159
  if (context.callerProtocol === 'mcp') {
1205
- const response = {
1206
- jsonrpc: '2.0',
1207
- id: requestId,
1208
- result: { content: [{ type: 'text', text: errorMessage }], isError: true },
1209
- };
1210
- state.ws?.send(JSON.stringify(response));
1160
+ const mcpResult = { content: [{ type: 'text', text: errorMessage }], isError: true };
1161
+ state.ws?.send(JSON.stringify(protocol.resultResponse(requestId, mcpResult)));
1211
1162
  }
1212
1163
  else {
1213
1164
  // KADI clients receive JSON-RPC error
1214
- const response = {
1215
- jsonrpc: '2.0',
1216
- id: requestId,
1217
- error: {
1218
- code: -32000,
1219
- message: errorMessage,
1220
- },
1221
- };
1222
- state.ws?.send(JSON.stringify(response));
1165
+ state.ws?.send(JSON.stringify(protocol.errorResponse(requestId, -32000, errorMessage)));
1223
1166
  }
1224
1167
  }
1225
1168
  }
@@ -1244,6 +1187,16 @@ export class KadiClient {
1244
1187
  pending.resolve(response.result);
1245
1188
  }
1246
1189
  }
1190
+ /**
1191
+ * Finalize a broker connection: start heartbeat and mark as connected.
1192
+ * Shared by initial connect and reconnect paths.
1193
+ */
1194
+ finalizeConnection(state) {
1195
+ state.heartbeatTimer = setInterval(() => {
1196
+ this.sendHeartbeat(state);
1197
+ }, this.config.heartbeatInterval);
1198
+ state.status = 'connected';
1199
+ }
1247
1200
  /**
1248
1201
  * Send heartbeat to keep connection alive.
1249
1202
  */
@@ -1283,6 +1236,9 @@ export class KadiClient {
1283
1236
  }));
1284
1237
  }
1285
1238
  state.pendingInvocations.clear();
1239
+ // Clear broker-side subscription tracking — subscriptions are lost when
1240
+ // the connection drops and must be re-established after reconnection.
1241
+ state.subscribedPatterns.clear();
1286
1242
  }
1287
1243
  // ─────────────────────────────────────────────────────────────
1288
1244
  // RECONNECTION LOGIC
@@ -1395,14 +1351,11 @@ export class KadiClient {
1395
1351
  await this.performHandshake(state);
1396
1352
  // Re-register tools
1397
1353
  await this.registerWithBroker(state);
1398
- // Restart heartbeat
1399
- state.heartbeatTimer = setInterval(() => {
1400
- this.sendHeartbeat(state);
1401
- }, this.config.heartbeatInterval);
1354
+ // Finalize connection (heartbeat + status)
1355
+ this.finalizeConnection(state);
1402
1356
  // Success!
1403
1357
  console.error(`[KADI] Reconnected to broker "${state.name}" after ${state.reconnectAttempts} attempts`);
1404
1358
  state.reconnectAttempts = 0;
1405
- state.status = 'connected';
1406
1359
  }
1407
1360
  catch (error) {
1408
1361
  // Log the error and try again
@@ -1509,12 +1462,7 @@ export class KadiClient {
1509
1462
  }
1510
1463
  else if (this.isServingStdio) {
1511
1464
  // Stdio transport: write notification to stdout
1512
- const notification = {
1513
- jsonrpc: '2.0',
1514
- method: 'event',
1515
- params: { name: event, data },
1516
- };
1517
- const json = JSON.stringify(notification);
1465
+ const json = JSON.stringify(protocol.eventNotification(event, data));
1518
1466
  process.stdout.write(`Content-Length: ${Buffer.byteLength(json)}\r\n\r\n${json}`);
1519
1467
  }
1520
1468
  // Broadcast: also publish to broker (if specified and connected)
@@ -1569,15 +1517,40 @@ export class KadiClient {
1569
1517
  ? zodToJsonSchema(definition.output)
1570
1518
  : undefined,
1571
1519
  };
1520
+ // Validate per-tool networks against broker networks (skip when no brokers)
1521
+ const targetNetworks = options.networks ?? [];
1522
+ if (targetNetworks.length > 0 && Object.keys(this.config.brokers).length > 0) {
1523
+ const allNetworks = this.getAllConfiguredNetworks();
1524
+ const invalid = targetNetworks.filter((n) => !allNetworks.includes(n));
1525
+ if (invalid.length > 0) {
1526
+ throw new KadiError(`Tool "${definition.name}" has networks ${JSON.stringify(invalid)} not present in client networks ${JSON.stringify(allNetworks)}. Per-tool networks must be a subset of the client's networks — a tool can only be visible on networks the agent has joined.`, 'INVALID_CONFIG', {
1527
+ toolName: definition.name,
1528
+ invalidNetworks: invalid,
1529
+ clientNetworks: allNetworks,
1530
+ });
1531
+ }
1532
+ }
1572
1533
  // Store registration
1573
1534
  const registered = {
1574
1535
  definition: jsonDefinition,
1575
1536
  handler: handler,
1576
- registeredAt: new Date(),
1577
1537
  targetBrokers: options.brokers ?? [],
1538
+ targetNetworks,
1578
1539
  };
1579
1540
  this.tools.set(definition.name, registered);
1580
1541
  }
1542
+ /**
1543
+ * Get the union of all networks configured across all brokers.
1544
+ * Used by registerTool() to validate per-tool network targeting.
1545
+ */
1546
+ getAllConfiguredNetworks() {
1547
+ const all = new Set();
1548
+ for (const broker of Object.values(this.config.brokers)) {
1549
+ for (const n of broker.networks)
1550
+ all.add(n);
1551
+ }
1552
+ return [...all];
1553
+ }
1581
1554
  /**
1582
1555
  * Get tool definitions, optionally filtered for a specific broker.
1583
1556
  *
@@ -1596,7 +1569,10 @@ export class KadiClient {
1596
1569
  // Otherwise, only include if this broker is in the target list
1597
1570
  return t.targetBrokers.includes(forBroker);
1598
1571
  })
1599
- .map((t) => t.definition);
1572
+ .map((t) => ({
1573
+ ...t.definition,
1574
+ ...(t.targetNetworks.length > 0 && { networks: t.targetNetworks }),
1575
+ }));
1600
1576
  }
1601
1577
  /**
1602
1578
  * Execute a tool registered on this client.
@@ -1752,7 +1728,7 @@ export class KadiClient {
1752
1728
  const ability = await loadBrokerTransport(name, {
1753
1729
  broker: state,
1754
1730
  requestTimeout: options.timeout ?? this.config.requestTimeout,
1755
- networks: options.networks,
1731
+ networks: options.networks ?? state.networks,
1756
1732
  // Provide subscribe/unsubscribe for ability.on()/off() support
1757
1733
  subscribe: (pattern, handler) => this.subscribe(pattern, handler, { broker: brokerName }),
1758
1734
  unsubscribe: (pattern, handler) => this.unsubscribe(pattern, handler, { broker: brokerName }),
@@ -1831,13 +1807,9 @@ export class KadiClient {
1831
1807
  };
1832
1808
  // NOW send the request (listener already exists, no race possible)
1833
1809
  try {
1834
- const pendingResult = await this.sendRequest(state, {
1835
- jsonrpc: '2.0',
1836
- id: ++this.nextRequestId,
1837
- method: 'kadi.ability.request',
1838
- // timeout sent so the broker can enforce a matching server-side deadline
1839
- params: { toolName, toolInput: params, requestId, timeout },
1840
- });
1810
+ const pendingResult = await this.sendRequest(state,
1811
+ // timeout sent so the broker can enforce a matching server-side deadline
1812
+ protocol.abilityRequest(++this.nextRequestId, toolName, params, requestId, timeout));
1841
1813
  // Validate the broker accepted the request
1842
1814
  if (pendingResult.status !== 'pending') {
1843
1815
  cleanupPendingInvocation();
@@ -1990,24 +1962,14 @@ export class KadiClient {
1990
1962
  * Send a response via stdio.
1991
1963
  */
1992
1964
  sendStdioResponse(id, result) {
1993
- const response = {
1994
- jsonrpc: '2.0',
1995
- id,
1996
- result,
1997
- };
1998
- const json = JSON.stringify(response);
1965
+ const json = JSON.stringify(protocol.resultResponse(id, result));
1999
1966
  process.stdout.write(`Content-Length: ${Buffer.byteLength(json)}\r\n\r\n${json}`);
2000
1967
  }
2001
1968
  /**
2002
1969
  * Send an error response via stdio.
2003
1970
  */
2004
1971
  sendStdioError(id, error) {
2005
- const response = {
2006
- jsonrpc: '2.0',
2007
- id,
2008
- error,
2009
- };
2010
- const json = JSON.stringify(response);
1972
+ const json = JSON.stringify(protocol.errorResponse(id, error.code, error.message));
2011
1973
  process.stdout.write(`Content-Length: ${Buffer.byteLength(json)}\r\n\r\n${json}`);
2012
1974
  }
2013
1975
  /**