@datagrout/conduit 0.5.0 → 0.7.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.
@@ -73,7 +73,9 @@ var init_oauth = __esm({
73
73
  }
74
74
  if (!response.ok) {
75
75
  const text = await response.text().catch(() => "");
76
- throw new Error(`OAuth token endpoint returned ${response.status}: ${text}`);
76
+ throw new Error(
77
+ `OAuth token endpoint returned ${response.status}: ${text}`
78
+ );
77
79
  }
78
80
  const data = await response.json();
79
81
  const expiresIn = data.expires_in ?? 3600;
@@ -22,7 +22,9 @@ async function _doRegister(opts) {
22
22
  });
23
23
  if (!completeResp.ok) {
24
24
  const text = await completeResp.text();
25
- throw new Error(`onramp complete rejected (HTTP ${completeResp.status}): ${text}`);
25
+ throw new Error(
26
+ `onramp complete rejected (HTTP ${completeResp.status}): ${text}`
27
+ );
26
28
  }
27
29
  const data = await completeResp.json();
28
30
  return {
package/dist/index.d.mts CHANGED
@@ -226,7 +226,7 @@ declare function fetchWithIdentity(url: string, init: RequestInit, identity: Con
226
226
  * - `"unlimited"` — authenticated DataGrout users; the gateway never blocks them.
227
227
  * - `{ perHour: number }` — unauthenticated callers hitting a per-hour cap.
228
228
  */
229
- type RateLimit = 'unlimited' | {
229
+ type RateLimit = "unlimited" | {
230
230
  perHour: number;
231
231
  };
232
232
  /**
@@ -414,7 +414,7 @@ interface ClientOptions {
414
414
  * This option is kept for backward compatibility and has no effect.
415
415
  */
416
416
  disableMtls?: boolean;
417
- transport?: 'mcp' | 'jsonrpc' | 'websocket';
417
+ transport?: "mcp" | "jsonrpc" | "websocket";
418
418
  timeout?: number;
419
419
  /**
420
420
  * Maximum number of automatic retries on "server not initialized" errors.
@@ -437,7 +437,7 @@ interface PerformOptions {
437
437
  tool: string;
438
438
  args: Record<string, any>;
439
439
  demux?: boolean;
440
- demuxMode?: 'strict' | 'fuzzy';
440
+ demuxMode?: "strict" | "fuzzy";
441
441
  }
442
442
  interface GuideRequestOptions {
443
443
  goal?: string;
@@ -649,9 +649,31 @@ declare class WsTransport extends Transport {
649
649
  private readonly _pending;
650
650
  private readonly _pendingSubscribe;
651
651
  private readonly _subscriptions;
652
+ /**
653
+ * Handle for the recurring ping timer; non-null only while connected.
654
+ * Cleared on disconnect / close. Set indirectly via {@link _startPingTimer}.
655
+ */
656
+ private _pingTimer;
657
+ /**
658
+ * Interval in ms between client-initiated ping frames. Public-readable so
659
+ * tests can inject a small value via {@link WsTransport.setPingInterval};
660
+ * defaults to {@link PING_INTERVAL_MS}.
661
+ */
662
+ private _pingIntervalMs;
652
663
  constructor(url: string, auth?: AuthConfig, _timeout?: number, _identity?: ConduitIdentity);
653
664
  connect(): Promise<void>;
654
665
  disconnect(): Promise<void>;
666
+ /**
667
+ * Override the ping interval (ms) — used by tests to avoid 25-second waits.
668
+ * Must be called BEFORE {@link connect}; has no effect on an already-running
669
+ * timer. In production code, leave the default ({@link PING_INTERVAL_MS}).
670
+ */
671
+ setPingInterval(intervalMs: number): void;
672
+ /** Tracks how many ping frames this transport has sent — for tests. */
673
+ get pingsSent(): number;
674
+ private _pingsSent;
675
+ private _startPingTimer;
676
+ private _stopPingTimer;
655
677
  /**
656
678
  * Open a server-side push subscription for `topic`.
657
679
  *
package/dist/index.d.ts CHANGED
@@ -226,7 +226,7 @@ declare function fetchWithIdentity(url: string, init: RequestInit, identity: Con
226
226
  * - `"unlimited"` — authenticated DataGrout users; the gateway never blocks them.
227
227
  * - `{ perHour: number }` — unauthenticated callers hitting a per-hour cap.
228
228
  */
229
- type RateLimit = 'unlimited' | {
229
+ type RateLimit = "unlimited" | {
230
230
  perHour: number;
231
231
  };
232
232
  /**
@@ -414,7 +414,7 @@ interface ClientOptions {
414
414
  * This option is kept for backward compatibility and has no effect.
415
415
  */
416
416
  disableMtls?: boolean;
417
- transport?: 'mcp' | 'jsonrpc' | 'websocket';
417
+ transport?: "mcp" | "jsonrpc" | "websocket";
418
418
  timeout?: number;
419
419
  /**
420
420
  * Maximum number of automatic retries on "server not initialized" errors.
@@ -437,7 +437,7 @@ interface PerformOptions {
437
437
  tool: string;
438
438
  args: Record<string, any>;
439
439
  demux?: boolean;
440
- demuxMode?: 'strict' | 'fuzzy';
440
+ demuxMode?: "strict" | "fuzzy";
441
441
  }
442
442
  interface GuideRequestOptions {
443
443
  goal?: string;
@@ -649,9 +649,31 @@ declare class WsTransport extends Transport {
649
649
  private readonly _pending;
650
650
  private readonly _pendingSubscribe;
651
651
  private readonly _subscriptions;
652
+ /**
653
+ * Handle for the recurring ping timer; non-null only while connected.
654
+ * Cleared on disconnect / close. Set indirectly via {@link _startPingTimer}.
655
+ */
656
+ private _pingTimer;
657
+ /**
658
+ * Interval in ms between client-initiated ping frames. Public-readable so
659
+ * tests can inject a small value via {@link WsTransport.setPingInterval};
660
+ * defaults to {@link PING_INTERVAL_MS}.
661
+ */
662
+ private _pingIntervalMs;
652
663
  constructor(url: string, auth?: AuthConfig, _timeout?: number, _identity?: ConduitIdentity);
653
664
  connect(): Promise<void>;
654
665
  disconnect(): Promise<void>;
666
+ /**
667
+ * Override the ping interval (ms) — used by tests to avoid 25-second waits.
668
+ * Must be called BEFORE {@link connect}; has no effect on an already-running
669
+ * timer. In production code, leave the default ({@link PING_INTERVAL_MS}).
670
+ */
671
+ setPingInterval(intervalMs: number): void;
672
+ /** Tracks how many ping frames this transport has sent — for tests. */
673
+ get pingsSent(): number;
674
+ private _pingsSent;
675
+ private _startPingTimer;
676
+ private _stopPingTimer;
655
677
  /**
656
678
  * Open a server-side push subscription for `topic`.
657
679
  *
package/dist/index.js CHANGED
@@ -101,7 +101,9 @@ var init_oauth = __esm({
101
101
  }
102
102
  if (!response.ok) {
103
103
  const text = await response.text().catch(() => "");
104
- throw new Error(`OAuth token endpoint returned ${response.status}: ${text}`);
104
+ throw new Error(
105
+ `OAuth token endpoint returned ${response.status}: ${text}`
106
+ );
105
107
  }
106
108
  const data = await response.json();
107
109
  const expiresIn = data.expires_in ?? 3600;
@@ -136,7 +138,9 @@ async function fetchWithIdentity(url, init, identity) {
136
138
  port: parsedUrl.port || 443,
137
139
  path: parsedUrl.pathname + parsedUrl.search,
138
140
  method: (init.method ?? "GET").toUpperCase(),
139
- headers: flattenHeaders(init.headers),
141
+ headers: flattenHeaders(
142
+ init.headers
143
+ ),
140
144
  cert: identity.certPem,
141
145
  key: identity.keyPem,
142
146
  ...identity.caPem ? { ca: identity.caPem } : {}
@@ -220,7 +224,9 @@ var init_identity = __esm({
220
224
  */
221
225
  static fromPaths(certPath, keyPath, caPath) {
222
226
  if (typeof process === "undefined" || !process.versions?.node) {
223
- throw new Error("ConduitIdentity.fromPaths() is only available in Node.js environments");
227
+ throw new Error(
228
+ "ConduitIdentity.fromPaths() is only available in Node.js environments"
229
+ );
224
230
  }
225
231
  const fs2 = require("fs");
226
232
  const certPem = fs2.readFileSync(certPath, "utf8");
@@ -244,7 +250,9 @@ var init_identity = __esm({
244
250
  if (!certPem) return null;
245
251
  const keyPem = process.env.CONDUIT_MTLS_KEY;
246
252
  if (!keyPem) {
247
- throw new Error("CONDUIT_MTLS_CERT is set but CONDUIT_MTLS_KEY is missing");
253
+ throw new Error(
254
+ "CONDUIT_MTLS_CERT is set but CONDUIT_MTLS_KEY is missing"
255
+ );
248
256
  }
249
257
  const caPem = process.env.CONDUIT_MTLS_CA || void 0;
250
258
  return _ConduitIdentity.fromPem(certPem, keyPem, caPem);
@@ -334,7 +342,11 @@ var init_identity = __esm({
334
342
  const keyPath = `${dir}/identity_key.pem`;
335
343
  if (!fs2.existsSync(certPath) || !fs2.existsSync(keyPath)) return null;
336
344
  const caPath = `${dir}/ca.pem`;
337
- return _ConduitIdentity.fromPaths(certPath, keyPath, fs2.existsSync(caPath) ? caPath : void 0);
345
+ return _ConduitIdentity.fromPaths(
346
+ certPath,
347
+ keyPath,
348
+ fs2.existsSync(caPath) ? caPath : void 0
349
+ );
338
350
  } catch {
339
351
  return null;
340
352
  }
@@ -374,7 +386,9 @@ async function _doRegister(opts) {
374
386
  });
375
387
  if (!completeResp.ok) {
376
388
  const text = await completeResp.text();
377
- throw new Error(`onramp complete rejected (HTTP ${completeResp.status}): ${text}`);
389
+ throw new Error(
390
+ `onramp complete rejected (HTTP ${completeResp.status}): ${text}`
391
+ );
378
392
  }
379
393
  const data = await completeResp.json();
380
394
  return {
@@ -565,6 +579,9 @@ var MCPTransport = class extends Transport {
565
579
  throw new Error("Not connected. Call connect() first.");
566
580
  }
567
581
  const result = await this.client.callTool({ name, arguments: args });
582
+ if (result?.structuredContent !== void 0) {
583
+ return result.structuredContent;
584
+ }
568
585
  const content = result?.content;
569
586
  if (Array.isArray(content) && content.length > 0) {
570
587
  const first = content[0];
@@ -575,6 +592,7 @@ var MCPTransport = class extends Transport {
575
592
  return { text: first.text };
576
593
  }
577
594
  }
595
+ return first;
578
596
  }
579
597
  return result;
580
598
  }
@@ -671,7 +689,11 @@ var InvalidConfigError = class extends ConduitError {
671
689
 
672
690
  // src/transports/jsonrpc.ts
673
691
  function unwrapContent(result) {
674
- if (result && Array.isArray(result.content) && result.content.length > 0) {
692
+ if (!result) return result;
693
+ if (result.structuredContent !== void 0) {
694
+ return result.structuredContent;
695
+ }
696
+ if (Array.isArray(result.content) && result.content.length > 0) {
675
697
  const first = result.content[0];
676
698
  if (first && typeof first.text === "string") {
677
699
  try {
@@ -680,6 +702,7 @@ function unwrapContent(result) {
680
702
  return { text: first.text };
681
703
  }
682
704
  }
705
+ return first;
683
706
  }
684
707
  return result;
685
708
  }
@@ -706,7 +729,9 @@ var JSONRPCTransport = class extends Transport {
706
729
  this.identity = identity;
707
730
  this.timeout = timeout;
708
731
  if (identity?.needsRotation(30)) {
709
- console.warn("[conduit] mTLS certificate expires within 30 days \u2014 consider rotating");
732
+ console.warn(
733
+ "[conduit] mTLS certificate expires within 30 days \u2014 consider rotating"
734
+ );
710
735
  }
711
736
  if (auth?.clientCredentials) {
712
737
  const cc = auth.clientCredentials;
@@ -739,7 +764,9 @@ var JSONRPCTransport = class extends Transport {
739
764
  } else if (this.auth?.bearer) {
740
765
  headers["Authorization"] = `Bearer ${this.auth.bearer}`;
741
766
  } else if (this.auth?.basic) {
742
- const credentials = btoa(`${this.auth.basic.username}:${this.auth.basic.password}`);
767
+ const credentials = btoa(
768
+ `${this.auth.basic.username}:${this.auth.basic.password}`
769
+ );
743
770
  headers["Authorization"] = `Basic ${credentials}`;
744
771
  } else if (this.auth?.custom) {
745
772
  Object.assign(headers, this.auth.custom);
@@ -809,6 +836,7 @@ var JSONRPCTransport = class extends Transport {
809
836
  // src/transports/ws.ts
810
837
  var SUBPROTOCOL = "datagrout-jsonrpc.v1";
811
838
  var SUBSCRIPTION_BUFFER = 256;
839
+ var PING_INTERVAL_MS = 25e3;
812
840
  var Subscription = class {
813
841
  id;
814
842
  topic;
@@ -874,11 +902,24 @@ var WsTransport = class extends Transport {
874
902
  _pending = /* @__PURE__ */ new Map();
875
903
  _pendingSubscribe = /* @__PURE__ */ new Map();
876
904
  _subscriptions = /* @__PURE__ */ new Map();
905
+ /**
906
+ * Handle for the recurring ping timer; non-null only while connected.
907
+ * Cleared on disconnect / close. Set indirectly via {@link _startPingTimer}.
908
+ */
909
+ _pingTimer = null;
910
+ /**
911
+ * Interval in ms between client-initiated ping frames. Public-readable so
912
+ * tests can inject a small value via {@link WsTransport.setPingInterval};
913
+ * defaults to {@link PING_INTERVAL_MS}.
914
+ */
915
+ _pingIntervalMs = PING_INTERVAL_MS;
877
916
  constructor(url, auth, _timeout, _identity) {
878
917
  super();
879
918
  const scheme = new URL(url).protocol.replace(":", "");
880
919
  if (scheme !== "ws" && scheme !== "wss") {
881
- throw new Error(`WS transport requires a ws:// or wss:// URL, got ${scheme}://`);
920
+ throw new Error(
921
+ `WS transport requires a ws:// or wss:// URL, got ${scheme}://`
922
+ );
882
923
  }
883
924
  this._url = url;
884
925
  this._auth = auth;
@@ -893,19 +934,24 @@ var WsTransport = class extends Transport {
893
934
  });
894
935
  await new Promise((resolve, reject) => {
895
936
  ws.onopen = () => resolve();
896
- ws.onerror = (ev) => reject(new Error(`WS connect failed: ${ev.message ?? "unknown"}`));
937
+ ws.onerror = (ev) => reject(
938
+ new Error(`WS connect failed: ${ev.message ?? "unknown"}`)
939
+ );
897
940
  });
898
941
  ws.onmessage = (ev) => this._handleMessage(ev.data);
899
942
  ws.onerror = (_ev) => this._failAll("WS connection error");
900
943
  ws.onclose = () => {
901
944
  this._failAll("WS connection closed");
945
+ this._stopPingTimer();
902
946
  this._ws = null;
903
947
  };
904
948
  this._ws = ws;
949
+ this._startPingTimer();
905
950
  }
906
951
  async disconnect() {
907
952
  const ws = this._ws;
908
953
  this._ws = null;
954
+ this._stopPingTimer();
909
955
  this._failAll("WS connection closed");
910
956
  if (ws !== null) {
911
957
  try {
@@ -914,6 +960,42 @@ var WsTransport = class extends Transport {
914
960
  }
915
961
  }
916
962
  }
963
+ // ── Ping keepalive ────────────────────────────────────────────────────────
964
+ /**
965
+ * Override the ping interval (ms) — used by tests to avoid 25-second waits.
966
+ * Must be called BEFORE {@link connect}; has no effect on an already-running
967
+ * timer. In production code, leave the default ({@link PING_INTERVAL_MS}).
968
+ */
969
+ setPingInterval(intervalMs) {
970
+ this._pingIntervalMs = intervalMs;
971
+ }
972
+ /** Tracks how many ping frames this transport has sent — for tests. */
973
+ get pingsSent() {
974
+ return this._pingsSent;
975
+ }
976
+ _pingsSent = 0;
977
+ _startPingTimer() {
978
+ if (this._pingTimer !== null || this._pingIntervalMs <= 0) return;
979
+ this._pingTimer = setInterval(() => {
980
+ const ws = this._ws;
981
+ if (ws === null) return;
982
+ if (typeof ws.ping === "function") {
983
+ try {
984
+ ws.ping();
985
+ this._pingsSent += 1;
986
+ } catch {
987
+ }
988
+ }
989
+ }, this._pingIntervalMs);
990
+ const t = this._pingTimer;
991
+ if (typeof t.unref === "function") t.unref();
992
+ }
993
+ _stopPingTimer() {
994
+ if (this._pingTimer !== null) {
995
+ clearInterval(this._pingTimer);
996
+ this._pingTimer = null;
997
+ }
998
+ }
917
999
  // ── Subscriptions ─────────────────────────────────────────────────────────
918
1000
  /**
919
1001
  * Open a server-side push subscription for `topic`.
@@ -927,7 +1009,12 @@ var WsTransport = class extends Transport {
927
1009
  const id = this._mintId();
928
1010
  return new Promise((resolve, reject) => {
929
1011
  this._pendingSubscribe.set(id, { topic, resolve, reject });
930
- this._send({ jsonrpc: "2.0", id, method: "subscribe", params: { topic } });
1012
+ this._send({
1013
+ jsonrpc: "2.0",
1014
+ id,
1015
+ method: "subscribe",
1016
+ params: { topic }
1017
+ });
931
1018
  });
932
1019
  }
933
1020
  /**
@@ -997,7 +1084,12 @@ var WsTransport = class extends Transport {
997
1084
  const id = this._mintId();
998
1085
  return new Promise((resolve, reject) => {
999
1086
  this._pending.set(id, { resolve, reject });
1000
- this._send({ jsonrpc: "2.0", id, method, ...params !== void 0 ? { params } : {} });
1087
+ this._send({
1088
+ jsonrpc: "2.0",
1089
+ id,
1090
+ method,
1091
+ ...params !== void 0 ? { params } : {}
1092
+ });
1001
1093
  });
1002
1094
  }
1003
1095
  _handleMessage(data) {
@@ -1009,7 +1101,9 @@ var WsTransport = class extends Transport {
1009
1101
  }
1010
1102
  if (!("id" in msg)) {
1011
1103
  if (msg["method"] === "notification") {
1012
- this._routeNotification(msg["params"]);
1104
+ this._routeNotification(
1105
+ msg["params"]
1106
+ );
1013
1107
  }
1014
1108
  return;
1015
1109
  }
@@ -1019,7 +1113,9 @@ var WsTransport = class extends Transport {
1019
1113
  this._pendingSubscribe.delete(msgId);
1020
1114
  const err = msg["error"];
1021
1115
  if (err !== void 0) {
1022
- pendingSub.reject(new Error(String(err["message"] ?? "Subscribe failed")));
1116
+ pendingSub.reject(
1117
+ new Error(String(err["message"] ?? "Subscribe failed"))
1118
+ );
1023
1119
  return;
1024
1120
  }
1025
1121
  const result = msg["result"] ?? {};
@@ -1076,7 +1172,9 @@ function buildUpgradeHeaders(auth) {
1076
1172
  } else if ("apiKey" in auth && auth.apiKey !== void 0) {
1077
1173
  headers["X-API-Key"] = auth.apiKey;
1078
1174
  } else if ("basic" in auth && auth.basic !== void 0) {
1079
- const encoded = Buffer.from(`${auth.basic.username}:${auth.basic.password}`).toString("base64");
1175
+ const encoded = Buffer.from(
1176
+ `${auth.basic.username}:${auth.basic.password}`
1177
+ ).toString("base64");
1080
1178
  headers["Authorization"] = `Basic ${encoded}`;
1081
1179
  }
1082
1180
  return headers;
@@ -1115,7 +1213,9 @@ async function fetchDgCaCert(url = DG_CA_URL) {
1115
1213
  }
1116
1214
  const pem = await resp.text();
1117
1215
  if (!pem.includes("-----BEGIN CERTIFICATE-----")) {
1118
- throw new Error(`Response from ${url} does not look like a PEM certificate`);
1216
+ throw new Error(
1217
+ `Response from ${url} does not look like a PEM certificate`
1218
+ );
1119
1219
  }
1120
1220
  return pem;
1121
1221
  }
@@ -1131,7 +1231,10 @@ function generateKeypair() {
1131
1231
  namedCurve: "P-256"
1132
1232
  });
1133
1233
  return {
1134
- privateKeyPem: privateKey.export({ type: "pkcs8", format: "pem" }),
1234
+ privateKeyPem: privateKey.export({
1235
+ type: "pkcs8",
1236
+ format: "pem"
1237
+ }),
1135
1238
  publicKeyPem: publicKey.export({ type: "spki", format: "pem" })
1136
1239
  };
1137
1240
  }
@@ -1171,9 +1274,17 @@ async function registerIdentity(keypair, opts) {
1171
1274
  };
1172
1275
  }
1173
1276
  async function rotateIdentity(opts) {
1174
- const { privateKey, publicKey } = crypto.generateKeyPairSync("ec", { namedCurve: "P-256" });
1175
- const publicKeyPem = publicKey.export({ type: "spki", format: "pem" });
1176
- const privateKeyPem = privateKey.export({ type: "pkcs8", format: "pem" });
1277
+ const { privateKey, publicKey } = crypto.generateKeyPairSync("ec", {
1278
+ namedCurve: "P-256"
1279
+ });
1280
+ const publicKeyPem = publicKey.export({
1281
+ type: "spki",
1282
+ format: "pem"
1283
+ });
1284
+ const privateKeyPem = privateKey.export({
1285
+ type: "pkcs8",
1286
+ format: "pem"
1287
+ });
1177
1288
  const url = opts.endpoint.replace(/\/$/, "") + "/rotate";
1178
1289
  const https = await import("https");
1179
1290
  const urlModule = await import("url");
@@ -1188,7 +1299,10 @@ async function rotateIdentity(opts) {
1188
1299
  cert: opts.currentCertPem,
1189
1300
  key: opts.currentKeyPem
1190
1301
  };
1191
- const body = JSON.stringify({ public_key_pem: publicKeyPem, name: opts.name });
1302
+ const body = JSON.stringify({
1303
+ public_key_pem: publicKeyPem,
1304
+ name: opts.name
1305
+ });
1192
1306
  const req = https.request(reqOptions, (res) => {
1193
1307
  let data = "";
1194
1308
  res.on("data", (chunk) => {
@@ -1198,7 +1312,9 @@ async function rotateIdentity(opts) {
1198
1312
  if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
1199
1313
  resolve(data);
1200
1314
  } else {
1201
- reject(new Error(`Rotation failed (HTTP ${res.statusCode}): ${data}`));
1315
+ reject(
1316
+ new Error(`Rotation failed (HTTP ${res.statusCode}): ${data}`)
1317
+ );
1202
1318
  }
1203
1319
  });
1204
1320
  });
@@ -1298,8 +1414,12 @@ var PrismNamespace = class {
1298
1414
  data: options.data,
1299
1415
  source_type: options.sourceType,
1300
1416
  target_type: options.targetType,
1301
- ...options.sourceAnnotations && { source_annotations: options.sourceAnnotations },
1302
- ...options.targetAnnotations && { target_annotations: options.targetAnnotations },
1417
+ ...options.sourceAnnotations && {
1418
+ source_annotations: options.sourceAnnotations
1419
+ },
1420
+ ...options.targetAnnotations && {
1421
+ target_annotations: options.targetAnnotations
1422
+ },
1303
1423
  ...options.context && { context: options.context }
1304
1424
  };
1305
1425
  return this.callDg("prism.focus", params);
@@ -1324,7 +1444,9 @@ var LogicNamespace = class {
1324
1444
  statement = opts.statement;
1325
1445
  }
1326
1446
  if (!statement && !opts?.facts?.length) {
1327
- throw new InvalidConfigError("remember() requires either a statement or facts");
1447
+ throw new InvalidConfigError(
1448
+ "remember() requires either a statement or facts"
1449
+ );
1328
1450
  }
1329
1451
  const params = { tag: opts?.tag ?? "default" };
1330
1452
  if (opts?.facts) {
@@ -1345,7 +1467,9 @@ var LogicNamespace = class {
1345
1467
  question = opts.question;
1346
1468
  }
1347
1469
  if (!question && !opts?.patterns?.length) {
1348
- throw new InvalidConfigError("query() requires either a question or patterns");
1470
+ throw new InvalidConfigError(
1471
+ "query() requires either a question or patterns"
1472
+ );
1349
1473
  }
1350
1474
  const params = { limit: opts?.limit ?? 50 };
1351
1475
  if (opts?.patterns) {
@@ -1358,7 +1482,9 @@ var LogicNamespace = class {
1358
1482
  /** Retract facts from the logic cell (`data-grout/logic.forget`). */
1359
1483
  async forget(options) {
1360
1484
  if (!options.handles?.length && !options.pattern) {
1361
- throw new InvalidConfigError("forget() requires either handles or pattern");
1485
+ throw new InvalidConfigError(
1486
+ "forget() requires either handles or pattern"
1487
+ );
1362
1488
  }
1363
1489
  const params = {};
1364
1490
  if (options.handles) params.handles = options.handles;
@@ -1367,13 +1493,18 @@ var LogicNamespace = class {
1367
1493
  }
1368
1494
  /** Reflect on the logic cell (`data-grout/logic.reflect`). */
1369
1495
  async reflect(options) {
1370
- const params = { summary_only: options?.summaryOnly ?? false };
1496
+ const params = {
1497
+ summary_only: options?.summaryOnly ?? false
1498
+ };
1371
1499
  if (options?.entity) params.entity = options.entity;
1372
1500
  return this.callDg("logic.reflect", params);
1373
1501
  }
1374
1502
  /** Add a constraint rule (`data-grout/logic.constrain`). */
1375
1503
  async constrain(rule, options) {
1376
- const params = { rule, tag: options?.tag ?? "constraint" };
1504
+ const params = {
1505
+ rule,
1506
+ tag: options?.tag ?? "constraint"
1507
+ };
1377
1508
  return this.callDg("logic.constrain", params);
1378
1509
  }
1379
1510
  /** Hydrate the logic cell from external data (`data-grout/logic.hydrate`). */
@@ -1535,7 +1666,9 @@ var FlowNamespace = class {
1535
1666
  /** Get details for a specific execution (`data-grout/inspect.execution-details`). */
1536
1667
  async details(executionId) {
1537
1668
  this.warn("inspect.execution-details");
1538
- return this.callDg("inspect.execution-details", { execution_id: executionId });
1669
+ return this.callDg("inspect.execution-details", {
1670
+ execution_id: executionId
1671
+ });
1539
1672
  }
1540
1673
  };
1541
1674
 
@@ -1623,10 +1756,20 @@ var Client2 = class _Client {
1623
1756
  let wsUrl = this.url;
1624
1757
  if (wsUrl.startsWith("https://")) wsUrl = "wss://" + wsUrl.slice(8);
1625
1758
  else if (wsUrl.startsWith("http://")) wsUrl = "ws://" + wsUrl.slice(7);
1626
- this.transport = new WsTransport(wsUrl, this.auth, options.timeout, identity);
1759
+ this.transport = new WsTransport(
1760
+ wsUrl,
1761
+ this.auth,
1762
+ options.timeout,
1763
+ identity
1764
+ );
1627
1765
  } else {
1628
1766
  const rpcUrl = this.url.endsWith("/mcp") ? this.url.slice(0, -4) + "/rpc" : this.url;
1629
- this.transport = new JSONRPCTransport(rpcUrl, this.auth, options.timeout, identity);
1767
+ this.transport = new JSONRPCTransport(
1768
+ rpcUrl,
1769
+ this.auth,
1770
+ options.timeout,
1771
+ identity
1772
+ );
1630
1773
  }
1631
1774
  }
1632
1775
  // ===== Bootstrap / seamless mTLS =====
@@ -1735,9 +1878,15 @@ var Client2 = class _Client {
1735
1878
  const existing = ConduitIdentity.tryDiscover(dir);
1736
1879
  if (existing && !existing.needsRotation(7)) {
1737
1880
  if (!options.url) {
1738
- throw new Error("'url' must be provided when an existing identity is reused");
1881
+ throw new Error(
1882
+ "'url' must be provided when an existing identity is reused"
1883
+ );
1739
1884
  }
1740
- return new _Client({ url: options.url, identity: existing, identityDir: dir });
1885
+ return new _Client({
1886
+ url: options.url,
1887
+ identity: existing,
1888
+ identityDir: dir
1889
+ });
1741
1890
  }
1742
1891
  const creds = await _doRegister2(options.opts);
1743
1892
  const token = await _exchangeToken2(creds);
@@ -1805,7 +1954,9 @@ var Client2 = class _Client {
1805
1954
  // ===== Namespace Accessors =====
1806
1955
  callDgTool = async (tool, params) => {
1807
1956
  this.ensureInitialized();
1808
- return this.sendWithRetry(() => this.transport.callTool(`data-grout/${tool}`, params));
1957
+ return this.sendWithRetry(
1958
+ () => this.transport.callTool(`data-grout/${tool}`, params)
1959
+ );
1809
1960
  };
1810
1961
  /** Data transformation, charting, rendering, and type bridging. */
1811
1962
  get prism() {
@@ -1821,7 +1972,10 @@ var Client2 = class _Client {
1821
1972
  }
1822
1973
  /** Work product registration, listing, and retrieval. */
1823
1974
  get deliverables() {
1824
- return new DeliverablesNamespace(this.callDgTool, (m) => this.warnIfNotDg(m));
1975
+ return new DeliverablesNamespace(
1976
+ this.callDgTool,
1977
+ (m) => this.warnIfNotDg(m)
1978
+ );
1825
1979
  }
1826
1980
  /** Cache listing and inspection. */
1827
1981
  get ephemerals() {
@@ -1910,7 +2064,9 @@ var Client2 = class _Client {
1910
2064
  */
1911
2065
  async getPrompt(name, args, options) {
1912
2066
  this.ensureInitialized();
1913
- return this.sendWithRetry(() => this.transport.getPrompt(name, args, options));
2067
+ return this.sendWithRetry(
2068
+ () => this.transport.getPrompt(name, args, options)
2069
+ );
1914
2070
  }
1915
2071
  // ===== WebSocket push subscriptions =====
1916
2072
  /**
@@ -2011,7 +2167,7 @@ var Client2 = class _Client {
2011
2167
  outputSchema: r.output_contract || r.output_schema || r.outputSchema
2012
2168
  })),
2013
2169
  total: result.total ?? tools.length,
2014
- limit: result.limit ?? (options.limit ?? 10)
2170
+ limit: result.limit ?? options.limit ?? 10
2015
2171
  };
2016
2172
  }
2017
2173
  /**
@@ -2127,8 +2283,10 @@ var Client2 = class _Client {
2127
2283
  if (options.k !== void 0) params.k = options.k;
2128
2284
  if (options.policy) params.policy = options.policy;
2129
2285
  if (options.have) params.have = options.have;
2130
- if (options.returnCallHandles !== void 0) params.return_call_handles = options.returnCallHandles;
2131
- if (options.exposeVirtualSkills !== void 0) params.expose_virtual_skills = options.exposeVirtualSkills;
2286
+ if (options.returnCallHandles !== void 0)
2287
+ params.return_call_handles = options.returnCallHandles;
2288
+ if (options.exposeVirtualSkills !== void 0)
2289
+ params.expose_virtual_skills = options.exposeVirtualSkills;
2132
2290
  if (options.modelOverrides) params.model_overrides = options.modelOverrides;
2133
2291
  return this.sendWithRetry(
2134
2292
  () => this.transport.callTool("data-grout/discovery.plan", params)
package/dist/index.mjs CHANGED
@@ -3,11 +3,11 @@ import {
3
3
  deriveTokenEndpoint,
4
4
  init_oauth,
5
5
  oauth_exports
6
- } from "./chunk-26DYCD4G.mjs";
6
+ } from "./chunk-LW7VX5SZ.mjs";
7
7
  import {
8
8
  registerAndExchange,
9
9
  registerOnly
10
- } from "./chunk-SWH5Y2U7.mjs";
10
+ } from "./chunk-W56N7X6V.mjs";
11
11
  import {
12
12
  __esm,
13
13
  __export,
@@ -35,7 +35,9 @@ async function fetchWithIdentity(url, init, identity) {
35
35
  port: parsedUrl.port || 443,
36
36
  path: parsedUrl.pathname + parsedUrl.search,
37
37
  method: (init.method ?? "GET").toUpperCase(),
38
- headers: flattenHeaders(init.headers),
38
+ headers: flattenHeaders(
39
+ init.headers
40
+ ),
39
41
  cert: identity.certPem,
40
42
  key: identity.keyPem,
41
43
  ...identity.caPem ? { ca: identity.caPem } : {}
@@ -119,7 +121,9 @@ var init_identity = __esm({
119
121
  */
120
122
  static fromPaths(certPath, keyPath, caPath) {
121
123
  if (typeof process === "undefined" || !process.versions?.node) {
122
- throw new Error("ConduitIdentity.fromPaths() is only available in Node.js environments");
124
+ throw new Error(
125
+ "ConduitIdentity.fromPaths() is only available in Node.js environments"
126
+ );
123
127
  }
124
128
  const fs2 = __require("fs");
125
129
  const certPem = fs2.readFileSync(certPath, "utf8");
@@ -143,7 +147,9 @@ var init_identity = __esm({
143
147
  if (!certPem) return null;
144
148
  const keyPem = process.env.CONDUIT_MTLS_KEY;
145
149
  if (!keyPem) {
146
- throw new Error("CONDUIT_MTLS_CERT is set but CONDUIT_MTLS_KEY is missing");
150
+ throw new Error(
151
+ "CONDUIT_MTLS_CERT is set but CONDUIT_MTLS_KEY is missing"
152
+ );
147
153
  }
148
154
  const caPem = process.env.CONDUIT_MTLS_CA || void 0;
149
155
  return _ConduitIdentity.fromPem(certPem, keyPem, caPem);
@@ -233,7 +239,11 @@ var init_identity = __esm({
233
239
  const keyPath = `${dir}/identity_key.pem`;
234
240
  if (!fs2.existsSync(certPath) || !fs2.existsSync(keyPath)) return null;
235
241
  const caPath = `${dir}/ca.pem`;
236
- return _ConduitIdentity.fromPaths(certPath, keyPath, fs2.existsSync(caPath) ? caPath : void 0);
242
+ return _ConduitIdentity.fromPaths(
243
+ certPath,
244
+ keyPath,
245
+ fs2.existsSync(caPath) ? caPath : void 0
246
+ );
237
247
  } catch {
238
248
  return null;
239
249
  }
@@ -354,6 +364,9 @@ var MCPTransport = class extends Transport {
354
364
  throw new Error("Not connected. Call connect() first.");
355
365
  }
356
366
  const result = await this.client.callTool({ name, arguments: args });
367
+ if (result?.structuredContent !== void 0) {
368
+ return result.structuredContent;
369
+ }
357
370
  const content = result?.content;
358
371
  if (Array.isArray(content) && content.length > 0) {
359
372
  const first = content[0];
@@ -364,6 +377,7 @@ var MCPTransport = class extends Transport {
364
377
  return { text: first.text };
365
378
  }
366
379
  }
380
+ return first;
367
381
  }
368
382
  return result;
369
383
  }
@@ -460,7 +474,11 @@ var InvalidConfigError = class extends ConduitError {
460
474
 
461
475
  // src/transports/jsonrpc.ts
462
476
  function unwrapContent(result) {
463
- if (result && Array.isArray(result.content) && result.content.length > 0) {
477
+ if (!result) return result;
478
+ if (result.structuredContent !== void 0) {
479
+ return result.structuredContent;
480
+ }
481
+ if (Array.isArray(result.content) && result.content.length > 0) {
464
482
  const first = result.content[0];
465
483
  if (first && typeof first.text === "string") {
466
484
  try {
@@ -469,6 +487,7 @@ function unwrapContent(result) {
469
487
  return { text: first.text };
470
488
  }
471
489
  }
490
+ return first;
472
491
  }
473
492
  return result;
474
493
  }
@@ -495,7 +514,9 @@ var JSONRPCTransport = class extends Transport {
495
514
  this.identity = identity;
496
515
  this.timeout = timeout;
497
516
  if (identity?.needsRotation(30)) {
498
- console.warn("[conduit] mTLS certificate expires within 30 days \u2014 consider rotating");
517
+ console.warn(
518
+ "[conduit] mTLS certificate expires within 30 days \u2014 consider rotating"
519
+ );
499
520
  }
500
521
  if (auth?.clientCredentials) {
501
522
  const cc = auth.clientCredentials;
@@ -528,7 +549,9 @@ var JSONRPCTransport = class extends Transport {
528
549
  } else if (this.auth?.bearer) {
529
550
  headers["Authorization"] = `Bearer ${this.auth.bearer}`;
530
551
  } else if (this.auth?.basic) {
531
- const credentials = btoa(`${this.auth.basic.username}:${this.auth.basic.password}`);
552
+ const credentials = btoa(
553
+ `${this.auth.basic.username}:${this.auth.basic.password}`
554
+ );
532
555
  headers["Authorization"] = `Basic ${credentials}`;
533
556
  } else if (this.auth?.custom) {
534
557
  Object.assign(headers, this.auth.custom);
@@ -598,6 +621,7 @@ var JSONRPCTransport = class extends Transport {
598
621
  // src/transports/ws.ts
599
622
  var SUBPROTOCOL = "datagrout-jsonrpc.v1";
600
623
  var SUBSCRIPTION_BUFFER = 256;
624
+ var PING_INTERVAL_MS = 25e3;
601
625
  var Subscription = class {
602
626
  id;
603
627
  topic;
@@ -663,11 +687,24 @@ var WsTransport = class extends Transport {
663
687
  _pending = /* @__PURE__ */ new Map();
664
688
  _pendingSubscribe = /* @__PURE__ */ new Map();
665
689
  _subscriptions = /* @__PURE__ */ new Map();
690
+ /**
691
+ * Handle for the recurring ping timer; non-null only while connected.
692
+ * Cleared on disconnect / close. Set indirectly via {@link _startPingTimer}.
693
+ */
694
+ _pingTimer = null;
695
+ /**
696
+ * Interval in ms between client-initiated ping frames. Public-readable so
697
+ * tests can inject a small value via {@link WsTransport.setPingInterval};
698
+ * defaults to {@link PING_INTERVAL_MS}.
699
+ */
700
+ _pingIntervalMs = PING_INTERVAL_MS;
666
701
  constructor(url, auth, _timeout, _identity) {
667
702
  super();
668
703
  const scheme = new URL(url).protocol.replace(":", "");
669
704
  if (scheme !== "ws" && scheme !== "wss") {
670
- throw new Error(`WS transport requires a ws:// or wss:// URL, got ${scheme}://`);
705
+ throw new Error(
706
+ `WS transport requires a ws:// or wss:// URL, got ${scheme}://`
707
+ );
671
708
  }
672
709
  this._url = url;
673
710
  this._auth = auth;
@@ -682,19 +719,24 @@ var WsTransport = class extends Transport {
682
719
  });
683
720
  await new Promise((resolve, reject) => {
684
721
  ws.onopen = () => resolve();
685
- ws.onerror = (ev) => reject(new Error(`WS connect failed: ${ev.message ?? "unknown"}`));
722
+ ws.onerror = (ev) => reject(
723
+ new Error(`WS connect failed: ${ev.message ?? "unknown"}`)
724
+ );
686
725
  });
687
726
  ws.onmessage = (ev) => this._handleMessage(ev.data);
688
727
  ws.onerror = (_ev) => this._failAll("WS connection error");
689
728
  ws.onclose = () => {
690
729
  this._failAll("WS connection closed");
730
+ this._stopPingTimer();
691
731
  this._ws = null;
692
732
  };
693
733
  this._ws = ws;
734
+ this._startPingTimer();
694
735
  }
695
736
  async disconnect() {
696
737
  const ws = this._ws;
697
738
  this._ws = null;
739
+ this._stopPingTimer();
698
740
  this._failAll("WS connection closed");
699
741
  if (ws !== null) {
700
742
  try {
@@ -703,6 +745,42 @@ var WsTransport = class extends Transport {
703
745
  }
704
746
  }
705
747
  }
748
+ // ── Ping keepalive ────────────────────────────────────────────────────────
749
+ /**
750
+ * Override the ping interval (ms) — used by tests to avoid 25-second waits.
751
+ * Must be called BEFORE {@link connect}; has no effect on an already-running
752
+ * timer. In production code, leave the default ({@link PING_INTERVAL_MS}).
753
+ */
754
+ setPingInterval(intervalMs) {
755
+ this._pingIntervalMs = intervalMs;
756
+ }
757
+ /** Tracks how many ping frames this transport has sent — for tests. */
758
+ get pingsSent() {
759
+ return this._pingsSent;
760
+ }
761
+ _pingsSent = 0;
762
+ _startPingTimer() {
763
+ if (this._pingTimer !== null || this._pingIntervalMs <= 0) return;
764
+ this._pingTimer = setInterval(() => {
765
+ const ws = this._ws;
766
+ if (ws === null) return;
767
+ if (typeof ws.ping === "function") {
768
+ try {
769
+ ws.ping();
770
+ this._pingsSent += 1;
771
+ } catch {
772
+ }
773
+ }
774
+ }, this._pingIntervalMs);
775
+ const t = this._pingTimer;
776
+ if (typeof t.unref === "function") t.unref();
777
+ }
778
+ _stopPingTimer() {
779
+ if (this._pingTimer !== null) {
780
+ clearInterval(this._pingTimer);
781
+ this._pingTimer = null;
782
+ }
783
+ }
706
784
  // ── Subscriptions ─────────────────────────────────────────────────────────
707
785
  /**
708
786
  * Open a server-side push subscription for `topic`.
@@ -716,7 +794,12 @@ var WsTransport = class extends Transport {
716
794
  const id = this._mintId();
717
795
  return new Promise((resolve, reject) => {
718
796
  this._pendingSubscribe.set(id, { topic, resolve, reject });
719
- this._send({ jsonrpc: "2.0", id, method: "subscribe", params: { topic } });
797
+ this._send({
798
+ jsonrpc: "2.0",
799
+ id,
800
+ method: "subscribe",
801
+ params: { topic }
802
+ });
720
803
  });
721
804
  }
722
805
  /**
@@ -786,7 +869,12 @@ var WsTransport = class extends Transport {
786
869
  const id = this._mintId();
787
870
  return new Promise((resolve, reject) => {
788
871
  this._pending.set(id, { resolve, reject });
789
- this._send({ jsonrpc: "2.0", id, method, ...params !== void 0 ? { params } : {} });
872
+ this._send({
873
+ jsonrpc: "2.0",
874
+ id,
875
+ method,
876
+ ...params !== void 0 ? { params } : {}
877
+ });
790
878
  });
791
879
  }
792
880
  _handleMessage(data) {
@@ -798,7 +886,9 @@ var WsTransport = class extends Transport {
798
886
  }
799
887
  if (!("id" in msg)) {
800
888
  if (msg["method"] === "notification") {
801
- this._routeNotification(msg["params"]);
889
+ this._routeNotification(
890
+ msg["params"]
891
+ );
802
892
  }
803
893
  return;
804
894
  }
@@ -808,7 +898,9 @@ var WsTransport = class extends Transport {
808
898
  this._pendingSubscribe.delete(msgId);
809
899
  const err = msg["error"];
810
900
  if (err !== void 0) {
811
- pendingSub.reject(new Error(String(err["message"] ?? "Subscribe failed")));
901
+ pendingSub.reject(
902
+ new Error(String(err["message"] ?? "Subscribe failed"))
903
+ );
812
904
  return;
813
905
  }
814
906
  const result = msg["result"] ?? {};
@@ -865,7 +957,9 @@ function buildUpgradeHeaders(auth) {
865
957
  } else if ("apiKey" in auth && auth.apiKey !== void 0) {
866
958
  headers["X-API-Key"] = auth.apiKey;
867
959
  } else if ("basic" in auth && auth.basic !== void 0) {
868
- const encoded = Buffer.from(`${auth.basic.username}:${auth.basic.password}`).toString("base64");
960
+ const encoded = Buffer.from(
961
+ `${auth.basic.username}:${auth.basic.password}`
962
+ ).toString("base64");
869
963
  headers["Authorization"] = `Basic ${encoded}`;
870
964
  }
871
965
  return headers;
@@ -904,7 +998,9 @@ async function fetchDgCaCert(url = DG_CA_URL) {
904
998
  }
905
999
  const pem = await resp.text();
906
1000
  if (!pem.includes("-----BEGIN CERTIFICATE-----")) {
907
- throw new Error(`Response from ${url} does not look like a PEM certificate`);
1001
+ throw new Error(
1002
+ `Response from ${url} does not look like a PEM certificate`
1003
+ );
908
1004
  }
909
1005
  return pem;
910
1006
  }
@@ -920,7 +1016,10 @@ function generateKeypair() {
920
1016
  namedCurve: "P-256"
921
1017
  });
922
1018
  return {
923
- privateKeyPem: privateKey.export({ type: "pkcs8", format: "pem" }),
1019
+ privateKeyPem: privateKey.export({
1020
+ type: "pkcs8",
1021
+ format: "pem"
1022
+ }),
924
1023
  publicKeyPem: publicKey.export({ type: "spki", format: "pem" })
925
1024
  };
926
1025
  }
@@ -960,9 +1059,17 @@ async function registerIdentity(keypair, opts) {
960
1059
  };
961
1060
  }
962
1061
  async function rotateIdentity(opts) {
963
- const { privateKey, publicKey } = crypto.generateKeyPairSync("ec", { namedCurve: "P-256" });
964
- const publicKeyPem = publicKey.export({ type: "spki", format: "pem" });
965
- const privateKeyPem = privateKey.export({ type: "pkcs8", format: "pem" });
1062
+ const { privateKey, publicKey } = crypto.generateKeyPairSync("ec", {
1063
+ namedCurve: "P-256"
1064
+ });
1065
+ const publicKeyPem = publicKey.export({
1066
+ type: "spki",
1067
+ format: "pem"
1068
+ });
1069
+ const privateKeyPem = privateKey.export({
1070
+ type: "pkcs8",
1071
+ format: "pem"
1072
+ });
966
1073
  const url = opts.endpoint.replace(/\/$/, "") + "/rotate";
967
1074
  const https = await import("https");
968
1075
  const urlModule = await import("url");
@@ -977,7 +1084,10 @@ async function rotateIdentity(opts) {
977
1084
  cert: opts.currentCertPem,
978
1085
  key: opts.currentKeyPem
979
1086
  };
980
- const body = JSON.stringify({ public_key_pem: publicKeyPem, name: opts.name });
1087
+ const body = JSON.stringify({
1088
+ public_key_pem: publicKeyPem,
1089
+ name: opts.name
1090
+ });
981
1091
  const req = https.request(reqOptions, (res) => {
982
1092
  let data = "";
983
1093
  res.on("data", (chunk) => {
@@ -987,7 +1097,9 @@ async function rotateIdentity(opts) {
987
1097
  if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
988
1098
  resolve(data);
989
1099
  } else {
990
- reject(new Error(`Rotation failed (HTTP ${res.statusCode}): ${data}`));
1100
+ reject(
1101
+ new Error(`Rotation failed (HTTP ${res.statusCode}): ${data}`)
1102
+ );
991
1103
  }
992
1104
  });
993
1105
  });
@@ -1087,8 +1199,12 @@ var PrismNamespace = class {
1087
1199
  data: options.data,
1088
1200
  source_type: options.sourceType,
1089
1201
  target_type: options.targetType,
1090
- ...options.sourceAnnotations && { source_annotations: options.sourceAnnotations },
1091
- ...options.targetAnnotations && { target_annotations: options.targetAnnotations },
1202
+ ...options.sourceAnnotations && {
1203
+ source_annotations: options.sourceAnnotations
1204
+ },
1205
+ ...options.targetAnnotations && {
1206
+ target_annotations: options.targetAnnotations
1207
+ },
1092
1208
  ...options.context && { context: options.context }
1093
1209
  };
1094
1210
  return this.callDg("prism.focus", params);
@@ -1113,7 +1229,9 @@ var LogicNamespace = class {
1113
1229
  statement = opts.statement;
1114
1230
  }
1115
1231
  if (!statement && !opts?.facts?.length) {
1116
- throw new InvalidConfigError("remember() requires either a statement or facts");
1232
+ throw new InvalidConfigError(
1233
+ "remember() requires either a statement or facts"
1234
+ );
1117
1235
  }
1118
1236
  const params = { tag: opts?.tag ?? "default" };
1119
1237
  if (opts?.facts) {
@@ -1134,7 +1252,9 @@ var LogicNamespace = class {
1134
1252
  question = opts.question;
1135
1253
  }
1136
1254
  if (!question && !opts?.patterns?.length) {
1137
- throw new InvalidConfigError("query() requires either a question or patterns");
1255
+ throw new InvalidConfigError(
1256
+ "query() requires either a question or patterns"
1257
+ );
1138
1258
  }
1139
1259
  const params = { limit: opts?.limit ?? 50 };
1140
1260
  if (opts?.patterns) {
@@ -1147,7 +1267,9 @@ var LogicNamespace = class {
1147
1267
  /** Retract facts from the logic cell (`data-grout/logic.forget`). */
1148
1268
  async forget(options) {
1149
1269
  if (!options.handles?.length && !options.pattern) {
1150
- throw new InvalidConfigError("forget() requires either handles or pattern");
1270
+ throw new InvalidConfigError(
1271
+ "forget() requires either handles or pattern"
1272
+ );
1151
1273
  }
1152
1274
  const params = {};
1153
1275
  if (options.handles) params.handles = options.handles;
@@ -1156,13 +1278,18 @@ var LogicNamespace = class {
1156
1278
  }
1157
1279
  /** Reflect on the logic cell (`data-grout/logic.reflect`). */
1158
1280
  async reflect(options) {
1159
- const params = { summary_only: options?.summaryOnly ?? false };
1281
+ const params = {
1282
+ summary_only: options?.summaryOnly ?? false
1283
+ };
1160
1284
  if (options?.entity) params.entity = options.entity;
1161
1285
  return this.callDg("logic.reflect", params);
1162
1286
  }
1163
1287
  /** Add a constraint rule (`data-grout/logic.constrain`). */
1164
1288
  async constrain(rule, options) {
1165
- const params = { rule, tag: options?.tag ?? "constraint" };
1289
+ const params = {
1290
+ rule,
1291
+ tag: options?.tag ?? "constraint"
1292
+ };
1166
1293
  return this.callDg("logic.constrain", params);
1167
1294
  }
1168
1295
  /** Hydrate the logic cell from external data (`data-grout/logic.hydrate`). */
@@ -1324,7 +1451,9 @@ var FlowNamespace = class {
1324
1451
  /** Get details for a specific execution (`data-grout/inspect.execution-details`). */
1325
1452
  async details(executionId) {
1326
1453
  this.warn("inspect.execution-details");
1327
- return this.callDg("inspect.execution-details", { execution_id: executionId });
1454
+ return this.callDg("inspect.execution-details", {
1455
+ execution_id: executionId
1456
+ });
1328
1457
  }
1329
1458
  };
1330
1459
 
@@ -1412,10 +1541,20 @@ var Client2 = class _Client {
1412
1541
  let wsUrl = this.url;
1413
1542
  if (wsUrl.startsWith("https://")) wsUrl = "wss://" + wsUrl.slice(8);
1414
1543
  else if (wsUrl.startsWith("http://")) wsUrl = "ws://" + wsUrl.slice(7);
1415
- this.transport = new WsTransport(wsUrl, this.auth, options.timeout, identity);
1544
+ this.transport = new WsTransport(
1545
+ wsUrl,
1546
+ this.auth,
1547
+ options.timeout,
1548
+ identity
1549
+ );
1416
1550
  } else {
1417
1551
  const rpcUrl = this.url.endsWith("/mcp") ? this.url.slice(0, -4) + "/rpc" : this.url;
1418
- this.transport = new JSONRPCTransport(rpcUrl, this.auth, options.timeout, identity);
1552
+ this.transport = new JSONRPCTransport(
1553
+ rpcUrl,
1554
+ this.auth,
1555
+ options.timeout,
1556
+ identity
1557
+ );
1419
1558
  }
1420
1559
  }
1421
1560
  // ===== Bootstrap / seamless mTLS =====
@@ -1473,7 +1612,7 @@ var Client2 = class _Client {
1473
1612
  * @param options.substrateEndpoint - Override the DG Substrate endpoint.
1474
1613
  */
1475
1614
  static async bootstrapIdentityOAuth(options) {
1476
- const { OAuthTokenProvider: OAuthTokenProvider2, deriveTokenEndpoint: deriveTokenEndpoint2 } = await import("./oauth-NSDC2G7W.mjs");
1615
+ const { OAuthTokenProvider: OAuthTokenProvider2, deriveTokenEndpoint: deriveTokenEndpoint2 } = await import("./oauth-T2D3EDCN.mjs");
1477
1616
  const tokenEndpoint = deriveTokenEndpoint2(options.url);
1478
1617
  const provider = new OAuthTokenProvider2({
1479
1618
  clientId: options.clientId,
@@ -1519,14 +1658,20 @@ var Client2 = class _Client {
1519
1658
  * ```
1520
1659
  */
1521
1660
  static async bootstrapOnramp(options) {
1522
- const { _doRegister, _exchangeToken } = await import("./onramp-743RJTNI.mjs");
1661
+ const { _doRegister, _exchangeToken } = await import("./onramp-RDGV3FOI.mjs");
1523
1662
  const dir = options.identityDir || DEFAULT_IDENTITY_DIR;
1524
1663
  const existing = ConduitIdentity.tryDiscover(dir);
1525
1664
  if (existing && !existing.needsRotation(7)) {
1526
1665
  if (!options.url) {
1527
- throw new Error("'url' must be provided when an existing identity is reused");
1666
+ throw new Error(
1667
+ "'url' must be provided when an existing identity is reused"
1668
+ );
1528
1669
  }
1529
- return new _Client({ url: options.url, identity: existing, identityDir: dir });
1670
+ return new _Client({
1671
+ url: options.url,
1672
+ identity: existing,
1673
+ identityDir: dir
1674
+ });
1530
1675
  }
1531
1676
  const creds = await _doRegister(options.opts);
1532
1677
  const token = await _exchangeToken(creds);
@@ -1594,7 +1739,9 @@ var Client2 = class _Client {
1594
1739
  // ===== Namespace Accessors =====
1595
1740
  callDgTool = async (tool, params) => {
1596
1741
  this.ensureInitialized();
1597
- return this.sendWithRetry(() => this.transport.callTool(`data-grout/${tool}`, params));
1742
+ return this.sendWithRetry(
1743
+ () => this.transport.callTool(`data-grout/${tool}`, params)
1744
+ );
1598
1745
  };
1599
1746
  /** Data transformation, charting, rendering, and type bridging. */
1600
1747
  get prism() {
@@ -1610,7 +1757,10 @@ var Client2 = class _Client {
1610
1757
  }
1611
1758
  /** Work product registration, listing, and retrieval. */
1612
1759
  get deliverables() {
1613
- return new DeliverablesNamespace(this.callDgTool, (m) => this.warnIfNotDg(m));
1760
+ return new DeliverablesNamespace(
1761
+ this.callDgTool,
1762
+ (m) => this.warnIfNotDg(m)
1763
+ );
1614
1764
  }
1615
1765
  /** Cache listing and inspection. */
1616
1766
  get ephemerals() {
@@ -1699,7 +1849,9 @@ var Client2 = class _Client {
1699
1849
  */
1700
1850
  async getPrompt(name, args, options) {
1701
1851
  this.ensureInitialized();
1702
- return this.sendWithRetry(() => this.transport.getPrompt(name, args, options));
1852
+ return this.sendWithRetry(
1853
+ () => this.transport.getPrompt(name, args, options)
1854
+ );
1703
1855
  }
1704
1856
  // ===== WebSocket push subscriptions =====
1705
1857
  /**
@@ -1800,7 +1952,7 @@ var Client2 = class _Client {
1800
1952
  outputSchema: r.output_contract || r.output_schema || r.outputSchema
1801
1953
  })),
1802
1954
  total: result.total ?? tools.length,
1803
- limit: result.limit ?? (options.limit ?? 10)
1955
+ limit: result.limit ?? options.limit ?? 10
1804
1956
  };
1805
1957
  }
1806
1958
  /**
@@ -1916,8 +2068,10 @@ var Client2 = class _Client {
1916
2068
  if (options.k !== void 0) params.k = options.k;
1917
2069
  if (options.policy) params.policy = options.policy;
1918
2070
  if (options.have) params.have = options.have;
1919
- if (options.returnCallHandles !== void 0) params.return_call_handles = options.returnCallHandles;
1920
- if (options.exposeVirtualSkills !== void 0) params.expose_virtual_skills = options.exposeVirtualSkills;
2071
+ if (options.returnCallHandles !== void 0)
2072
+ params.return_call_handles = options.returnCallHandles;
2073
+ if (options.exposeVirtualSkills !== void 0)
2074
+ params.expose_virtual_skills = options.exposeVirtualSkills;
1921
2075
  if (options.modelOverrides) params.model_overrides = options.modelOverrides;
1922
2076
  return this.sendWithRetry(
1923
2077
  () => this.transport.callTool("data-grout/discovery.plan", params)
@@ -2,7 +2,7 @@ import {
2
2
  OAuthTokenProvider,
3
3
  deriveTokenEndpoint,
4
4
  init_oauth
5
- } from "./chunk-26DYCD4G.mjs";
5
+ } from "./chunk-LW7VX5SZ.mjs";
6
6
  import "./chunk-CIESM3BP.mjs";
7
7
  init_oauth();
8
8
  export {
@@ -3,7 +3,7 @@ import {
3
3
  _exchangeToken,
4
4
  registerAndExchange,
5
5
  registerOnly
6
- } from "./chunk-SWH5Y2U7.mjs";
6
+ } from "./chunk-W56N7X6V.mjs";
7
7
  import "./chunk-CIESM3BP.mjs";
8
8
  export {
9
9
  _doRegister,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datagrout/conduit",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "Production-ready MCP client with mTLS, OAuth 2.1, and semantic discovery",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",