@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.
- package/dist/{chunk-26DYCD4G.mjs → chunk-LW7VX5SZ.mjs} +3 -1
- package/dist/{chunk-SWH5Y2U7.mjs → chunk-W56N7X6V.mjs} +3 -1
- package/dist/index.d.mts +25 -3
- package/dist/index.d.ts +25 -3
- package/dist/index.js +199 -41
- package/dist/index.mjs +197 -43
- package/dist/{oauth-NSDC2G7W.mjs → oauth-T2D3EDCN.mjs} +1 -1
- package/dist/{onramp-743RJTNI.mjs → onramp-RDGV3FOI.mjs} +1 -1
- package/package.json +1 -1
|
@@ -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(
|
|
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(
|
|
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 =
|
|
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?:
|
|
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?:
|
|
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 =
|
|
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?:
|
|
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?:
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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({
|
|
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({
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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({
|
|
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", {
|
|
1175
|
-
|
|
1176
|
-
|
|
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({
|
|
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(
|
|
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 && {
|
|
1302
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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 = {
|
|
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 = {
|
|
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", {
|
|
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(
|
|
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(
|
|
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(
|
|
1881
|
+
throw new Error(
|
|
1882
|
+
"'url' must be provided when an existing identity is reused"
|
|
1883
|
+
);
|
|
1739
1884
|
}
|
|
1740
|
-
return new _Client({
|
|
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(
|
|
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(
|
|
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(
|
|
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 ??
|
|
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)
|
|
2131
|
-
|
|
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-
|
|
6
|
+
} from "./chunk-LW7VX5SZ.mjs";
|
|
7
7
|
import {
|
|
8
8
|
registerAndExchange,
|
|
9
9
|
registerOnly
|
|
10
|
-
} from "./chunk-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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({
|
|
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({
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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({
|
|
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", {
|
|
964
|
-
|
|
965
|
-
|
|
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({
|
|
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(
|
|
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 && {
|
|
1091
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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 = {
|
|
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 = {
|
|
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", {
|
|
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(
|
|
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(
|
|
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-
|
|
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-
|
|
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(
|
|
1666
|
+
throw new Error(
|
|
1667
|
+
"'url' must be provided when an existing identity is reused"
|
|
1668
|
+
);
|
|
1528
1669
|
}
|
|
1529
|
-
return new _Client({
|
|
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(
|
|
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(
|
|
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(
|
|
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 ??
|
|
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)
|
|
1920
|
-
|
|
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)
|