@datagrout/conduit 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -2
- package/dist/index.d.mts +161 -3
- package/dist/index.d.ts +161 -3
- package/dist/index.js +343 -1
- package/dist/index.mjs +341 -1
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ Production-ready MCP client with mTLS identity, OAuth 2.1, semantic discovery, a
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npm install @datagrout/conduit
|
|
8
|
+
npm install @datagrout/conduit@0.4.0
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
## Quick Start
|
|
@@ -158,8 +158,58 @@ const client = new Client({ url });
|
|
|
158
158
|
|
|
159
159
|
// JSONRPC — lightweight, stateless, same tools and auth
|
|
160
160
|
const client = new Client({ url, transport: 'jsonrpc' });
|
|
161
|
+
|
|
162
|
+
// WebSocket — bidirectional push
|
|
163
|
+
const client = new Client({
|
|
164
|
+
url: 'wss://gateway.datagrout.ai/servers/{uuid}/ws',
|
|
165
|
+
transport: 'websocket',
|
|
166
|
+
});
|
|
161
167
|
```
|
|
162
168
|
|
|
169
|
+
### WebSocket transport
|
|
170
|
+
|
|
171
|
+
The WebSocket transport uses the `datagrout-jsonrpc.v1` subprotocol over a single persistent `wss://` connection. Concurrent requests are multiplexed; responses correlated by JSON-RPC `id` via `Map<string, { resolve, reject }>`.
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
import { Client } from '@datagrout/conduit';
|
|
175
|
+
|
|
176
|
+
const client = new Client({
|
|
177
|
+
url: 'wss://gateway.datagrout.ai/servers/{uuid}/ws',
|
|
178
|
+
auth: { bearer: 'your-token' },
|
|
179
|
+
transport: 'websocket',
|
|
180
|
+
});
|
|
181
|
+
await client.connect();
|
|
182
|
+
|
|
183
|
+
// Subscribe to server-pushed events
|
|
184
|
+
const sub = await client.subscribe('agents.my-agent-id.events');
|
|
185
|
+
|
|
186
|
+
for await (const event of sub) {
|
|
187
|
+
console.log(event.event, event.data);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
await client.unsubscribe(sub.id);
|
|
191
|
+
await client.disconnect();
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
You can also call `await sub.recv()` for one-event-at-a-time consumption:
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
const event = await sub.recv();
|
|
198
|
+
console.log(event.event, event.data);
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Supported topics:
|
|
202
|
+
|
|
203
|
+
| Topic | Fires when |
|
|
204
|
+
|-------|-----------|
|
|
205
|
+
| `agents.<agent_id>.events` | Agent lifecycle events (plan started, IC completed, grounding failed, …) |
|
|
206
|
+
| `tools.<tool_name>.results` | A specific tool call completes |
|
|
207
|
+
| `tasks.<task_id>.*` | Long-running background task transitions |
|
|
208
|
+
| `flows.<flow_id>.*` | `flow.into` progress and completion |
|
|
209
|
+
| `governor.<server_uuid>` | Governor percept events (file change, schedule, webhook) |
|
|
210
|
+
|
|
211
|
+
**Reconnection**: after a disconnect, calls raise `NotInitializedError`. Re-call `connect()` and re-subscribe — subscriptions do not survive reconnects in v0.4.
|
|
212
|
+
|
|
163
213
|
## API Reference
|
|
164
214
|
|
|
165
215
|
### Client Options
|
|
@@ -168,7 +218,7 @@ const client = new Client({ url, transport: 'jsonrpc' });
|
|
|
168
218
|
new Client(options: {
|
|
169
219
|
url: string;
|
|
170
220
|
auth?: { bearer?: string; apiKey?: string; clientCredentials?: {...} };
|
|
171
|
-
transport?: 'mcp' | 'jsonrpc';
|
|
221
|
+
transport?: 'mcp' | 'jsonrpc' | 'websocket';
|
|
172
222
|
useIntelligentInterface?: boolean;
|
|
173
223
|
identity?: ConduitIdentity;
|
|
174
224
|
identityAuto?: boolean;
|
package/dist/index.d.mts
CHANGED
|
@@ -256,6 +256,7 @@ interface GuideState {
|
|
|
256
256
|
}
|
|
257
257
|
interface AuthConfig {
|
|
258
258
|
bearer?: string;
|
|
259
|
+
apiKey?: string;
|
|
259
260
|
basic?: {
|
|
260
261
|
username: string;
|
|
261
262
|
password: string;
|
|
@@ -321,7 +322,7 @@ interface ClientOptions {
|
|
|
321
322
|
* @default false
|
|
322
323
|
*/
|
|
323
324
|
disableMtls?: boolean;
|
|
324
|
-
transport?: 'mcp' | 'jsonrpc';
|
|
325
|
+
transport?: 'mcp' | 'jsonrpc' | 'websocket';
|
|
325
326
|
timeout?: number;
|
|
326
327
|
/**
|
|
327
328
|
* Maximum number of automatic retries on "server not initialized" errors.
|
|
@@ -461,6 +462,136 @@ interface MCPPrompt {
|
|
|
461
462
|
}>;
|
|
462
463
|
}
|
|
463
464
|
|
|
465
|
+
/**
|
|
466
|
+
* Base transport interface
|
|
467
|
+
*/
|
|
468
|
+
|
|
469
|
+
declare abstract class Transport {
|
|
470
|
+
abstract connect(): Promise<void>;
|
|
471
|
+
abstract disconnect(): Promise<void>;
|
|
472
|
+
abstract listTools(options?: any): Promise<MCPTool[]>;
|
|
473
|
+
abstract callTool(name: string, args: Record<string, any>, options?: any): Promise<any>;
|
|
474
|
+
abstract listResources(options?: any): Promise<MCPResource[]>;
|
|
475
|
+
abstract readResource(uri: string, options?: any): Promise<any>;
|
|
476
|
+
abstract listPrompts(options?: any): Promise<MCPPrompt[]>;
|
|
477
|
+
abstract getPrompt(name: string, args?: Record<string, any>, options?: any): Promise<any>;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* WebSocket transport for `datagrout-jsonrpc.v1`.
|
|
482
|
+
*
|
|
483
|
+
* Recommended for any client that needs bidirectional push (server-initiated
|
|
484
|
+
* notifications) without a polling loop. A single `wss://` connection is
|
|
485
|
+
* multiplexed for all in-flight requests; concurrent calls are correlated by
|
|
486
|
+
* JSON-RPC `id` with no head-of-line blocking.
|
|
487
|
+
*
|
|
488
|
+
* @example
|
|
489
|
+
* ```ts
|
|
490
|
+
* const client = new Client({
|
|
491
|
+
* url: 'wss://gateway.datagrout.ai/servers/<uuid>/ws',
|
|
492
|
+
* transport: 'websocket',
|
|
493
|
+
* auth: { bearer: 'your-token' },
|
|
494
|
+
* });
|
|
495
|
+
* await client.connect();
|
|
496
|
+
*
|
|
497
|
+
* // Push subscription
|
|
498
|
+
* const sub = await client.subscribe('agents.my-agent-id.events');
|
|
499
|
+
* for await (const event of sub) {
|
|
500
|
+
* console.log(event.event, event.data);
|
|
501
|
+
* }
|
|
502
|
+
* await client.unsubscribe(sub.id);
|
|
503
|
+
* ```
|
|
504
|
+
*
|
|
505
|
+
* @module
|
|
506
|
+
*/
|
|
507
|
+
|
|
508
|
+
declare const SUBPROTOCOL = "datagrout-jsonrpc.v1";
|
|
509
|
+
/** A single server-pushed notification. */
|
|
510
|
+
interface SubscriptionEvent {
|
|
511
|
+
/** The subscription id this event belongs to. */
|
|
512
|
+
subscription: string;
|
|
513
|
+
/** Server-named event slug (e.g. `"agent.thought"`). */
|
|
514
|
+
event: string;
|
|
515
|
+
/** Free-form payload from the server. */
|
|
516
|
+
data: unknown;
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Handle for an active server-push subscription.
|
|
520
|
+
*
|
|
521
|
+
* Consume events with {@link Subscription.recv} or an async-for loop:
|
|
522
|
+
* ```ts
|
|
523
|
+
* for await (const event of sub) {
|
|
524
|
+
* handle(event);
|
|
525
|
+
* }
|
|
526
|
+
* ```
|
|
527
|
+
*/
|
|
528
|
+
declare class Subscription {
|
|
529
|
+
readonly id: string;
|
|
530
|
+
readonly topic: string;
|
|
531
|
+
private _queue;
|
|
532
|
+
private _waiters;
|
|
533
|
+
private _rejecters;
|
|
534
|
+
private _closed;
|
|
535
|
+
constructor(id: string, topic: string);
|
|
536
|
+
/**
|
|
537
|
+
* Wait for the next event from this subscription.
|
|
538
|
+
*
|
|
539
|
+
* @throws When the subscription has been closed.
|
|
540
|
+
*/
|
|
541
|
+
recv(): Promise<SubscriptionEvent>;
|
|
542
|
+
[Symbol.asyncIterator](): AsyncGenerator<SubscriptionEvent>;
|
|
543
|
+
_enqueue(event: SubscriptionEvent): void;
|
|
544
|
+
_close(): void;
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* JSON-RPC 2.0 over WebSocket transport (`datagrout-jsonrpc.v1`).
|
|
548
|
+
*
|
|
549
|
+
* Multiplexes any number of in-flight requests on one socket and routes
|
|
550
|
+
* server-pushed notifications back to {@link Subscription} queues.
|
|
551
|
+
*/
|
|
552
|
+
declare class WsTransport extends Transport {
|
|
553
|
+
private readonly _url;
|
|
554
|
+
private readonly _auth?;
|
|
555
|
+
private _ws;
|
|
556
|
+
private _nextId;
|
|
557
|
+
private readonly _pending;
|
|
558
|
+
private readonly _pendingSubscribe;
|
|
559
|
+
private readonly _subscriptions;
|
|
560
|
+
constructor(url: string, auth?: AuthConfig, _timeout?: number, _identity?: ConduitIdentity);
|
|
561
|
+
connect(): Promise<void>;
|
|
562
|
+
disconnect(): Promise<void>;
|
|
563
|
+
/**
|
|
564
|
+
* Open a server-side push subscription for `topic`.
|
|
565
|
+
*
|
|
566
|
+
* @param topic - Dotted namespace topic, e.g.
|
|
567
|
+
* `"agents.my-agent-id.events"` or `"tasks.task-123.*"`.
|
|
568
|
+
* @returns A {@link Subscription} handle whose async-for loop delivers events.
|
|
569
|
+
*/
|
|
570
|
+
subscribe(topic: string): Promise<Subscription>;
|
|
571
|
+
/**
|
|
572
|
+
* Cancel a server-side subscription.
|
|
573
|
+
*
|
|
574
|
+
* The local {@link Subscription} queue is closed immediately.
|
|
575
|
+
*
|
|
576
|
+
* @param subscriptionId - The `id` field from the {@link Subscription}
|
|
577
|
+
* returned by {@link subscribe}.
|
|
578
|
+
*/
|
|
579
|
+
unsubscribe(subscriptionId: string): Promise<void>;
|
|
580
|
+
listTools(options?: any): Promise<MCPTool[]>;
|
|
581
|
+
callTool(name: string, args: Record<string, any>, _options?: any): Promise<any>;
|
|
582
|
+
listResources(_options?: any): Promise<MCPResource[]>;
|
|
583
|
+
readResource(uri: string, _options?: any): Promise<any>;
|
|
584
|
+
listPrompts(_options?: any): Promise<MCPPrompt[]>;
|
|
585
|
+
getPrompt(name: string, args?: Record<string, any>, _options?: any): Promise<any>;
|
|
586
|
+
private _mintId;
|
|
587
|
+
private _requireConnected;
|
|
588
|
+
private _send;
|
|
589
|
+
private _request;
|
|
590
|
+
private _handleMessage;
|
|
591
|
+
private _routeNotification;
|
|
592
|
+
private _failAll;
|
|
593
|
+
}
|
|
594
|
+
|
|
464
595
|
/**
|
|
465
596
|
* Prism namespace — data transformation, charting, rendering, and export.
|
|
466
597
|
*/
|
|
@@ -842,6 +973,33 @@ declare class Client {
|
|
|
842
973
|
* @param args - Template argument values.
|
|
843
974
|
*/
|
|
844
975
|
getPrompt(name: string, args?: Record<string, any>, options?: any): Promise<any>;
|
|
976
|
+
/**
|
|
977
|
+
* Subscribe to a server-push topic (WebSocket transport only).
|
|
978
|
+
*
|
|
979
|
+
* Requires `transport: 'websocket'` when constructing the client.
|
|
980
|
+
*
|
|
981
|
+
* @param topic - Dotted namespace topic, e.g.
|
|
982
|
+
* `"agents.my-agent-id.events"` or `"tasks.task-123.*"`.
|
|
983
|
+
* @returns A {@link Subscription} handle. Consume events with
|
|
984
|
+
* {@link Subscription.recv} or an `for await` loop.
|
|
985
|
+
*
|
|
986
|
+
* @example
|
|
987
|
+
* ```ts
|
|
988
|
+
* const sub = await client.subscribe('agents.my-agent-id.events');
|
|
989
|
+
* for await (const event of sub) {
|
|
990
|
+
* console.log(event.event, event.data);
|
|
991
|
+
* }
|
|
992
|
+
* await client.unsubscribe(sub.id);
|
|
993
|
+
* ```
|
|
994
|
+
*/
|
|
995
|
+
subscribe(topic: string): Promise<Subscription>;
|
|
996
|
+
/**
|
|
997
|
+
* Cancel a server-side push subscription.
|
|
998
|
+
*
|
|
999
|
+
* @param subscriptionId - The `id` from the {@link Subscription} returned
|
|
1000
|
+
* by {@link subscribe}.
|
|
1001
|
+
*/
|
|
1002
|
+
unsubscribe(subscriptionId: string): Promise<void>;
|
|
845
1003
|
private warnIfNotDg;
|
|
846
1004
|
/**
|
|
847
1005
|
* Semantically discover tools relevant to a goal or query.
|
|
@@ -1223,6 +1381,6 @@ declare class InvalidConfigError extends ConduitError {
|
|
|
1223
1381
|
* DataGrout Conduit SDK for TypeScript/JavaScript
|
|
1224
1382
|
*/
|
|
1225
1383
|
|
|
1226
|
-
declare const version = "0.
|
|
1384
|
+
declare const version = "0.4.0";
|
|
1227
1385
|
|
|
1228
|
-
export { type AuthConfig, AuthError, type Byok, type ChartOptions, Client, type ClientOptions, ConduitError, ConduitIdentity, type ConstrainOptions, type CreditEstimate, DEFAULT_IDENTITY_DIR, DG_CA_URL, DG_SUBSTRATE_ENDPOINT, type DiscoverOptions, type DiscoverResult, type FlowOptions, type ForgetOptions, type GuideOptions, type GuideRequestOptions, type GuideState, GuidedSession, InvalidConfigError, type Keypair, type MCPPrompt, type MCPResource, type MCPTool, type MtlsConfig, NetworkError, NotInitializedError, OAuthTokenProvider, type PerformOptions, type PerformResult, type PlanOptions, type PrismFocusOptions, type QueryCellOptions, type RateLimit, RateLimitError, type RateLimitStatus, type Receipt, type ReflectOptions, type RefractOptions, type RegisteredIdentity, type RegistrationOptions, type RememberOptions, type RenewalOptions, type RotationOptions, type SavedPaths, ServerError, type ToolInfo, type ToolMeta, deriveTokenEndpoint, extractMeta, fetchDgCaCert, fetchWithIdentity, generateKeypair, isDgUrl, refreshCaCert, registerIdentity, rotateIdentity, saveIdentity, version };
|
|
1386
|
+
export { type AuthConfig, AuthError, type Byok, type ChartOptions, Client, type ClientOptions, ConduitError, ConduitIdentity, type ConstrainOptions, type CreditEstimate, DEFAULT_IDENTITY_DIR, DG_CA_URL, DG_SUBSTRATE_ENDPOINT, type DiscoverOptions, type DiscoverResult, type FlowOptions, type ForgetOptions, type GuideOptions, type GuideRequestOptions, type GuideState, GuidedSession, InvalidConfigError, type Keypair, type MCPPrompt, type MCPResource, type MCPTool, type MtlsConfig, NetworkError, NotInitializedError, OAuthTokenProvider, type PerformOptions, type PerformResult, type PlanOptions, type PrismFocusOptions, type QueryCellOptions, type RateLimit, RateLimitError, type RateLimitStatus, type Receipt, type ReflectOptions, type RefractOptions, type RegisteredIdentity, type RegistrationOptions, type RememberOptions, type RenewalOptions, type RotationOptions, type SavedPaths, ServerError, Subscription, type SubscriptionEvent, type ToolInfo, type ToolMeta, SUBPROTOCOL as WS_SUBPROTOCOL, WsTransport, deriveTokenEndpoint, extractMeta, fetchDgCaCert, fetchWithIdentity, generateKeypair, isDgUrl, refreshCaCert, registerIdentity, rotateIdentity, saveIdentity, version };
|
package/dist/index.d.ts
CHANGED
|
@@ -256,6 +256,7 @@ interface GuideState {
|
|
|
256
256
|
}
|
|
257
257
|
interface AuthConfig {
|
|
258
258
|
bearer?: string;
|
|
259
|
+
apiKey?: string;
|
|
259
260
|
basic?: {
|
|
260
261
|
username: string;
|
|
261
262
|
password: string;
|
|
@@ -321,7 +322,7 @@ interface ClientOptions {
|
|
|
321
322
|
* @default false
|
|
322
323
|
*/
|
|
323
324
|
disableMtls?: boolean;
|
|
324
|
-
transport?: 'mcp' | 'jsonrpc';
|
|
325
|
+
transport?: 'mcp' | 'jsonrpc' | 'websocket';
|
|
325
326
|
timeout?: number;
|
|
326
327
|
/**
|
|
327
328
|
* Maximum number of automatic retries on "server not initialized" errors.
|
|
@@ -461,6 +462,136 @@ interface MCPPrompt {
|
|
|
461
462
|
}>;
|
|
462
463
|
}
|
|
463
464
|
|
|
465
|
+
/**
|
|
466
|
+
* Base transport interface
|
|
467
|
+
*/
|
|
468
|
+
|
|
469
|
+
declare abstract class Transport {
|
|
470
|
+
abstract connect(): Promise<void>;
|
|
471
|
+
abstract disconnect(): Promise<void>;
|
|
472
|
+
abstract listTools(options?: any): Promise<MCPTool[]>;
|
|
473
|
+
abstract callTool(name: string, args: Record<string, any>, options?: any): Promise<any>;
|
|
474
|
+
abstract listResources(options?: any): Promise<MCPResource[]>;
|
|
475
|
+
abstract readResource(uri: string, options?: any): Promise<any>;
|
|
476
|
+
abstract listPrompts(options?: any): Promise<MCPPrompt[]>;
|
|
477
|
+
abstract getPrompt(name: string, args?: Record<string, any>, options?: any): Promise<any>;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* WebSocket transport for `datagrout-jsonrpc.v1`.
|
|
482
|
+
*
|
|
483
|
+
* Recommended for any client that needs bidirectional push (server-initiated
|
|
484
|
+
* notifications) without a polling loop. A single `wss://` connection is
|
|
485
|
+
* multiplexed for all in-flight requests; concurrent calls are correlated by
|
|
486
|
+
* JSON-RPC `id` with no head-of-line blocking.
|
|
487
|
+
*
|
|
488
|
+
* @example
|
|
489
|
+
* ```ts
|
|
490
|
+
* const client = new Client({
|
|
491
|
+
* url: 'wss://gateway.datagrout.ai/servers/<uuid>/ws',
|
|
492
|
+
* transport: 'websocket',
|
|
493
|
+
* auth: { bearer: 'your-token' },
|
|
494
|
+
* });
|
|
495
|
+
* await client.connect();
|
|
496
|
+
*
|
|
497
|
+
* // Push subscription
|
|
498
|
+
* const sub = await client.subscribe('agents.my-agent-id.events');
|
|
499
|
+
* for await (const event of sub) {
|
|
500
|
+
* console.log(event.event, event.data);
|
|
501
|
+
* }
|
|
502
|
+
* await client.unsubscribe(sub.id);
|
|
503
|
+
* ```
|
|
504
|
+
*
|
|
505
|
+
* @module
|
|
506
|
+
*/
|
|
507
|
+
|
|
508
|
+
declare const SUBPROTOCOL = "datagrout-jsonrpc.v1";
|
|
509
|
+
/** A single server-pushed notification. */
|
|
510
|
+
interface SubscriptionEvent {
|
|
511
|
+
/** The subscription id this event belongs to. */
|
|
512
|
+
subscription: string;
|
|
513
|
+
/** Server-named event slug (e.g. `"agent.thought"`). */
|
|
514
|
+
event: string;
|
|
515
|
+
/** Free-form payload from the server. */
|
|
516
|
+
data: unknown;
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Handle for an active server-push subscription.
|
|
520
|
+
*
|
|
521
|
+
* Consume events with {@link Subscription.recv} or an async-for loop:
|
|
522
|
+
* ```ts
|
|
523
|
+
* for await (const event of sub) {
|
|
524
|
+
* handle(event);
|
|
525
|
+
* }
|
|
526
|
+
* ```
|
|
527
|
+
*/
|
|
528
|
+
declare class Subscription {
|
|
529
|
+
readonly id: string;
|
|
530
|
+
readonly topic: string;
|
|
531
|
+
private _queue;
|
|
532
|
+
private _waiters;
|
|
533
|
+
private _rejecters;
|
|
534
|
+
private _closed;
|
|
535
|
+
constructor(id: string, topic: string);
|
|
536
|
+
/**
|
|
537
|
+
* Wait for the next event from this subscription.
|
|
538
|
+
*
|
|
539
|
+
* @throws When the subscription has been closed.
|
|
540
|
+
*/
|
|
541
|
+
recv(): Promise<SubscriptionEvent>;
|
|
542
|
+
[Symbol.asyncIterator](): AsyncGenerator<SubscriptionEvent>;
|
|
543
|
+
_enqueue(event: SubscriptionEvent): void;
|
|
544
|
+
_close(): void;
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* JSON-RPC 2.0 over WebSocket transport (`datagrout-jsonrpc.v1`).
|
|
548
|
+
*
|
|
549
|
+
* Multiplexes any number of in-flight requests on one socket and routes
|
|
550
|
+
* server-pushed notifications back to {@link Subscription} queues.
|
|
551
|
+
*/
|
|
552
|
+
declare class WsTransport extends Transport {
|
|
553
|
+
private readonly _url;
|
|
554
|
+
private readonly _auth?;
|
|
555
|
+
private _ws;
|
|
556
|
+
private _nextId;
|
|
557
|
+
private readonly _pending;
|
|
558
|
+
private readonly _pendingSubscribe;
|
|
559
|
+
private readonly _subscriptions;
|
|
560
|
+
constructor(url: string, auth?: AuthConfig, _timeout?: number, _identity?: ConduitIdentity);
|
|
561
|
+
connect(): Promise<void>;
|
|
562
|
+
disconnect(): Promise<void>;
|
|
563
|
+
/**
|
|
564
|
+
* Open a server-side push subscription for `topic`.
|
|
565
|
+
*
|
|
566
|
+
* @param topic - Dotted namespace topic, e.g.
|
|
567
|
+
* `"agents.my-agent-id.events"` or `"tasks.task-123.*"`.
|
|
568
|
+
* @returns A {@link Subscription} handle whose async-for loop delivers events.
|
|
569
|
+
*/
|
|
570
|
+
subscribe(topic: string): Promise<Subscription>;
|
|
571
|
+
/**
|
|
572
|
+
* Cancel a server-side subscription.
|
|
573
|
+
*
|
|
574
|
+
* The local {@link Subscription} queue is closed immediately.
|
|
575
|
+
*
|
|
576
|
+
* @param subscriptionId - The `id` field from the {@link Subscription}
|
|
577
|
+
* returned by {@link subscribe}.
|
|
578
|
+
*/
|
|
579
|
+
unsubscribe(subscriptionId: string): Promise<void>;
|
|
580
|
+
listTools(options?: any): Promise<MCPTool[]>;
|
|
581
|
+
callTool(name: string, args: Record<string, any>, _options?: any): Promise<any>;
|
|
582
|
+
listResources(_options?: any): Promise<MCPResource[]>;
|
|
583
|
+
readResource(uri: string, _options?: any): Promise<any>;
|
|
584
|
+
listPrompts(_options?: any): Promise<MCPPrompt[]>;
|
|
585
|
+
getPrompt(name: string, args?: Record<string, any>, _options?: any): Promise<any>;
|
|
586
|
+
private _mintId;
|
|
587
|
+
private _requireConnected;
|
|
588
|
+
private _send;
|
|
589
|
+
private _request;
|
|
590
|
+
private _handleMessage;
|
|
591
|
+
private _routeNotification;
|
|
592
|
+
private _failAll;
|
|
593
|
+
}
|
|
594
|
+
|
|
464
595
|
/**
|
|
465
596
|
* Prism namespace — data transformation, charting, rendering, and export.
|
|
466
597
|
*/
|
|
@@ -842,6 +973,33 @@ declare class Client {
|
|
|
842
973
|
* @param args - Template argument values.
|
|
843
974
|
*/
|
|
844
975
|
getPrompt(name: string, args?: Record<string, any>, options?: any): Promise<any>;
|
|
976
|
+
/**
|
|
977
|
+
* Subscribe to a server-push topic (WebSocket transport only).
|
|
978
|
+
*
|
|
979
|
+
* Requires `transport: 'websocket'` when constructing the client.
|
|
980
|
+
*
|
|
981
|
+
* @param topic - Dotted namespace topic, e.g.
|
|
982
|
+
* `"agents.my-agent-id.events"` or `"tasks.task-123.*"`.
|
|
983
|
+
* @returns A {@link Subscription} handle. Consume events with
|
|
984
|
+
* {@link Subscription.recv} or an `for await` loop.
|
|
985
|
+
*
|
|
986
|
+
* @example
|
|
987
|
+
* ```ts
|
|
988
|
+
* const sub = await client.subscribe('agents.my-agent-id.events');
|
|
989
|
+
* for await (const event of sub) {
|
|
990
|
+
* console.log(event.event, event.data);
|
|
991
|
+
* }
|
|
992
|
+
* await client.unsubscribe(sub.id);
|
|
993
|
+
* ```
|
|
994
|
+
*/
|
|
995
|
+
subscribe(topic: string): Promise<Subscription>;
|
|
996
|
+
/**
|
|
997
|
+
* Cancel a server-side push subscription.
|
|
998
|
+
*
|
|
999
|
+
* @param subscriptionId - The `id` from the {@link Subscription} returned
|
|
1000
|
+
* by {@link subscribe}.
|
|
1001
|
+
*/
|
|
1002
|
+
unsubscribe(subscriptionId: string): Promise<void>;
|
|
845
1003
|
private warnIfNotDg;
|
|
846
1004
|
/**
|
|
847
1005
|
* Semantically discover tools relevant to a goal or query.
|
|
@@ -1223,6 +1381,6 @@ declare class InvalidConfigError extends ConduitError {
|
|
|
1223
1381
|
* DataGrout Conduit SDK for TypeScript/JavaScript
|
|
1224
1382
|
*/
|
|
1225
1383
|
|
|
1226
|
-
declare const version = "0.
|
|
1384
|
+
declare const version = "0.4.0";
|
|
1227
1385
|
|
|
1228
|
-
export { type AuthConfig, AuthError, type Byok, type ChartOptions, Client, type ClientOptions, ConduitError, ConduitIdentity, type ConstrainOptions, type CreditEstimate, DEFAULT_IDENTITY_DIR, DG_CA_URL, DG_SUBSTRATE_ENDPOINT, type DiscoverOptions, type DiscoverResult, type FlowOptions, type ForgetOptions, type GuideOptions, type GuideRequestOptions, type GuideState, GuidedSession, InvalidConfigError, type Keypair, type MCPPrompt, type MCPResource, type MCPTool, type MtlsConfig, NetworkError, NotInitializedError, OAuthTokenProvider, type PerformOptions, type PerformResult, type PlanOptions, type PrismFocusOptions, type QueryCellOptions, type RateLimit, RateLimitError, type RateLimitStatus, type Receipt, type ReflectOptions, type RefractOptions, type RegisteredIdentity, type RegistrationOptions, type RememberOptions, type RenewalOptions, type RotationOptions, type SavedPaths, ServerError, type ToolInfo, type ToolMeta, deriveTokenEndpoint, extractMeta, fetchDgCaCert, fetchWithIdentity, generateKeypair, isDgUrl, refreshCaCert, registerIdentity, rotateIdentity, saveIdentity, version };
|
|
1386
|
+
export { type AuthConfig, AuthError, type Byok, type ChartOptions, Client, type ClientOptions, ConduitError, ConduitIdentity, type ConstrainOptions, type CreditEstimate, DEFAULT_IDENTITY_DIR, DG_CA_URL, DG_SUBSTRATE_ENDPOINT, type DiscoverOptions, type DiscoverResult, type FlowOptions, type ForgetOptions, type GuideOptions, type GuideRequestOptions, type GuideState, GuidedSession, InvalidConfigError, type Keypair, type MCPPrompt, type MCPResource, type MCPTool, type MtlsConfig, NetworkError, NotInitializedError, OAuthTokenProvider, type PerformOptions, type PerformResult, type PlanOptions, type PrismFocusOptions, type QueryCellOptions, type RateLimit, RateLimitError, type RateLimitStatus, type Receipt, type ReflectOptions, type RefractOptions, type RegisteredIdentity, type RegistrationOptions, type RememberOptions, type RenewalOptions, type RotationOptions, type SavedPaths, ServerError, Subscription, type SubscriptionEvent, type ToolInfo, type ToolMeta, SUBPROTOCOL as WS_SUBPROTOCOL, WsTransport, deriveTokenEndpoint, extractMeta, fetchDgCaCert, fetchWithIdentity, generateKeypair, isDgUrl, refreshCaCert, registerIdentity, rotateIdentity, saveIdentity, version };
|
package/dist/index.js
CHANGED
|
@@ -360,6 +360,8 @@ __export(index_exports, {
|
|
|
360
360
|
OAuthTokenProvider: () => OAuthTokenProvider,
|
|
361
361
|
RateLimitError: () => RateLimitError,
|
|
362
362
|
ServerError: () => ServerError,
|
|
363
|
+
WS_SUBPROTOCOL: () => SUBPROTOCOL,
|
|
364
|
+
WsTransport: () => WsTransport,
|
|
363
365
|
deriveTokenEndpoint: () => deriveTokenEndpoint,
|
|
364
366
|
extractMeta: () => extractMeta,
|
|
365
367
|
fetchDgCaCert: () => fetchDgCaCert,
|
|
@@ -727,6 +729,295 @@ var JSONRPCTransport = class extends Transport {
|
|
|
727
729
|
}
|
|
728
730
|
};
|
|
729
731
|
|
|
732
|
+
// src/transports/ws.ts
|
|
733
|
+
var SUBPROTOCOL = "datagrout-jsonrpc.v1";
|
|
734
|
+
var SUBSCRIPTION_BUFFER = 256;
|
|
735
|
+
var Subscription = class {
|
|
736
|
+
id;
|
|
737
|
+
topic;
|
|
738
|
+
_queue = [];
|
|
739
|
+
_waiters = [];
|
|
740
|
+
_rejecters = [];
|
|
741
|
+
_closed = false;
|
|
742
|
+
constructor(id, topic) {
|
|
743
|
+
this.id = id;
|
|
744
|
+
this.topic = topic;
|
|
745
|
+
}
|
|
746
|
+
/**
|
|
747
|
+
* Wait for the next event from this subscription.
|
|
748
|
+
*
|
|
749
|
+
* @throws When the subscription has been closed.
|
|
750
|
+
*/
|
|
751
|
+
recv() {
|
|
752
|
+
if (this._queue.length > 0) {
|
|
753
|
+
return Promise.resolve(this._queue.shift());
|
|
754
|
+
}
|
|
755
|
+
if (this._closed) {
|
|
756
|
+
return Promise.reject(new Error("Subscription closed"));
|
|
757
|
+
}
|
|
758
|
+
return new Promise((resolve, reject) => {
|
|
759
|
+
this._waiters.push(resolve);
|
|
760
|
+
this._rejecters.push(reject);
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
async *[Symbol.asyncIterator]() {
|
|
764
|
+
while (this._queue.length > 0 || !this._closed) {
|
|
765
|
+
try {
|
|
766
|
+
yield await this.recv();
|
|
767
|
+
} catch {
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
// ── Internal ───────────────────────────────────────────────────────────────
|
|
773
|
+
_enqueue(event) {
|
|
774
|
+
if (this._waiters.length > 0) {
|
|
775
|
+
const resolve = this._waiters.shift();
|
|
776
|
+
this._rejecters.shift();
|
|
777
|
+
resolve(event);
|
|
778
|
+
} else if (this._queue.length < SUBSCRIPTION_BUFFER) {
|
|
779
|
+
this._queue.push(event);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
_close() {
|
|
783
|
+
this._closed = true;
|
|
784
|
+
const err = new Error("Subscription closed");
|
|
785
|
+
for (const reject of this._rejecters) {
|
|
786
|
+
reject(err);
|
|
787
|
+
}
|
|
788
|
+
this._waiters.length = 0;
|
|
789
|
+
this._rejecters.length = 0;
|
|
790
|
+
}
|
|
791
|
+
};
|
|
792
|
+
var WsTransport = class extends Transport {
|
|
793
|
+
_url;
|
|
794
|
+
_auth;
|
|
795
|
+
_ws = null;
|
|
796
|
+
_nextId = 0;
|
|
797
|
+
_pending = /* @__PURE__ */ new Map();
|
|
798
|
+
_pendingSubscribe = /* @__PURE__ */ new Map();
|
|
799
|
+
_subscriptions = /* @__PURE__ */ new Map();
|
|
800
|
+
constructor(url, auth, _timeout, _identity) {
|
|
801
|
+
super();
|
|
802
|
+
const scheme = new URL(url).protocol.replace(":", "");
|
|
803
|
+
if (scheme !== "ws" && scheme !== "wss") {
|
|
804
|
+
throw new Error(`WS transport requires a ws:// or wss:// URL, got ${scheme}://`);
|
|
805
|
+
}
|
|
806
|
+
this._url = url;
|
|
807
|
+
this._auth = auth;
|
|
808
|
+
}
|
|
809
|
+
// ── Lifecycle ─────────────────────────────────────────────────────────────
|
|
810
|
+
async connect() {
|
|
811
|
+
if (this._ws !== null) return;
|
|
812
|
+
const WsImpl = await resolveWebSocketImpl();
|
|
813
|
+
const headers = buildUpgradeHeaders(this._auth);
|
|
814
|
+
const ws = new WsImpl(this._url, [SUBPROTOCOL], {
|
|
815
|
+
headers
|
|
816
|
+
});
|
|
817
|
+
await new Promise((resolve, reject) => {
|
|
818
|
+
ws.onopen = () => resolve();
|
|
819
|
+
ws.onerror = (ev) => reject(new Error(`WS connect failed: ${ev.message ?? "unknown"}`));
|
|
820
|
+
});
|
|
821
|
+
ws.onmessage = (ev) => this._handleMessage(ev.data);
|
|
822
|
+
ws.onerror = (_ev) => this._failAll("WS connection error");
|
|
823
|
+
ws.onclose = () => {
|
|
824
|
+
this._failAll("WS connection closed");
|
|
825
|
+
this._ws = null;
|
|
826
|
+
};
|
|
827
|
+
this._ws = ws;
|
|
828
|
+
}
|
|
829
|
+
async disconnect() {
|
|
830
|
+
const ws = this._ws;
|
|
831
|
+
this._ws = null;
|
|
832
|
+
this._failAll("WS connection closed");
|
|
833
|
+
if (ws !== null) {
|
|
834
|
+
try {
|
|
835
|
+
ws.close();
|
|
836
|
+
} catch {
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
// ── Subscriptions ─────────────────────────────────────────────────────────
|
|
841
|
+
/**
|
|
842
|
+
* Open a server-side push subscription for `topic`.
|
|
843
|
+
*
|
|
844
|
+
* @param topic - Dotted namespace topic, e.g.
|
|
845
|
+
* `"agents.my-agent-id.events"` or `"tasks.task-123.*"`.
|
|
846
|
+
* @returns A {@link Subscription} handle whose async-for loop delivers events.
|
|
847
|
+
*/
|
|
848
|
+
async subscribe(topic) {
|
|
849
|
+
this._requireConnected();
|
|
850
|
+
const id = this._mintId();
|
|
851
|
+
return new Promise((resolve, reject) => {
|
|
852
|
+
this._pendingSubscribe.set(id, { topic, resolve, reject });
|
|
853
|
+
this._send({ jsonrpc: "2.0", id, method: "subscribe", params: { topic } });
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
/**
|
|
857
|
+
* Cancel a server-side subscription.
|
|
858
|
+
*
|
|
859
|
+
* The local {@link Subscription} queue is closed immediately.
|
|
860
|
+
*
|
|
861
|
+
* @param subscriptionId - The `id` field from the {@link Subscription}
|
|
862
|
+
* returned by {@link subscribe}.
|
|
863
|
+
*/
|
|
864
|
+
async unsubscribe(subscriptionId) {
|
|
865
|
+
this._requireConnected();
|
|
866
|
+
const sub = this._subscriptions.get(subscriptionId);
|
|
867
|
+
if (sub !== void 0) {
|
|
868
|
+
this._subscriptions.delete(subscriptionId);
|
|
869
|
+
sub._close();
|
|
870
|
+
}
|
|
871
|
+
const id = this._mintId();
|
|
872
|
+
const ackPromise = new Promise((resolve, reject) => {
|
|
873
|
+
this._pending.set(id, { resolve, reject });
|
|
874
|
+
});
|
|
875
|
+
this._send({
|
|
876
|
+
jsonrpc: "2.0",
|
|
877
|
+
id,
|
|
878
|
+
method: "unsubscribe",
|
|
879
|
+
params: { subscription: subscriptionId }
|
|
880
|
+
});
|
|
881
|
+
await Promise.race([
|
|
882
|
+
ackPromise,
|
|
883
|
+
new Promise((resolve) => setTimeout(resolve, 5e3))
|
|
884
|
+
]);
|
|
885
|
+
this._pending.delete(id);
|
|
886
|
+
}
|
|
887
|
+
// ── Transport base implementation ─────────────────────────────────────────
|
|
888
|
+
async listTools(options) {
|
|
889
|
+
return await this._request("tools/list", options);
|
|
890
|
+
}
|
|
891
|
+
async callTool(name, args, _options) {
|
|
892
|
+
return this._request("tools/call", { name, arguments: args });
|
|
893
|
+
}
|
|
894
|
+
async listResources(_options) {
|
|
895
|
+
return await this._request("resources/list");
|
|
896
|
+
}
|
|
897
|
+
async readResource(uri, _options) {
|
|
898
|
+
return this._request("resources/read", { uri });
|
|
899
|
+
}
|
|
900
|
+
async listPrompts(_options) {
|
|
901
|
+
return await this._request("prompts/list");
|
|
902
|
+
}
|
|
903
|
+
async getPrompt(name, args, _options) {
|
|
904
|
+
return this._request("prompts/get", { name, arguments: args });
|
|
905
|
+
}
|
|
906
|
+
// ── Internal ──────────────────────────────────────────────────────────────
|
|
907
|
+
_mintId() {
|
|
908
|
+
return `ws-${++this._nextId}`;
|
|
909
|
+
}
|
|
910
|
+
_requireConnected() {
|
|
911
|
+
if (this._ws === null) {
|
|
912
|
+
throw new Error("WS transport not connected. Call connect() first.");
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
_send(payload) {
|
|
916
|
+
this._ws.send(JSON.stringify(payload));
|
|
917
|
+
}
|
|
918
|
+
async _request(method, params) {
|
|
919
|
+
this._requireConnected();
|
|
920
|
+
const id = this._mintId();
|
|
921
|
+
return new Promise((resolve, reject) => {
|
|
922
|
+
this._pending.set(id, { resolve, reject });
|
|
923
|
+
this._send({ jsonrpc: "2.0", id, method, ...params !== void 0 ? { params } : {} });
|
|
924
|
+
});
|
|
925
|
+
}
|
|
926
|
+
_handleMessage(data) {
|
|
927
|
+
let msg;
|
|
928
|
+
try {
|
|
929
|
+
msg = JSON.parse(data);
|
|
930
|
+
} catch {
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
933
|
+
if (!("id" in msg)) {
|
|
934
|
+
if (msg["method"] === "notification") {
|
|
935
|
+
this._routeNotification(msg["params"]);
|
|
936
|
+
}
|
|
937
|
+
return;
|
|
938
|
+
}
|
|
939
|
+
const msgId = String(msg["id"]);
|
|
940
|
+
const pendingSub = this._pendingSubscribe.get(msgId);
|
|
941
|
+
if (pendingSub !== void 0) {
|
|
942
|
+
this._pendingSubscribe.delete(msgId);
|
|
943
|
+
const err = msg["error"];
|
|
944
|
+
if (err !== void 0) {
|
|
945
|
+
pendingSub.reject(new Error(String(err["message"] ?? "Subscribe failed")));
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
const result = msg["result"] ?? {};
|
|
949
|
+
const subId = String(result["subscription"] ?? msgId);
|
|
950
|
+
const sub = new Subscription(subId, pendingSub.topic);
|
|
951
|
+
this._subscriptions.set(subId, sub);
|
|
952
|
+
pendingSub.resolve(sub);
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
const pending = this._pending.get(msgId);
|
|
956
|
+
if (pending !== void 0) {
|
|
957
|
+
this._pending.delete(msgId);
|
|
958
|
+
const err = msg["error"];
|
|
959
|
+
if (err !== void 0) {
|
|
960
|
+
pending.reject(new Error(String(err["message"] ?? "RPC error")));
|
|
961
|
+
} else {
|
|
962
|
+
pending.resolve(msg["result"]);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
_routeNotification(params) {
|
|
967
|
+
if (params === void 0) return;
|
|
968
|
+
const subId = params["subscription"];
|
|
969
|
+
if (typeof subId !== "string") return;
|
|
970
|
+
const sub = this._subscriptions.get(subId);
|
|
971
|
+
if (sub === void 0) return;
|
|
972
|
+
sub._enqueue({
|
|
973
|
+
subscription: subId,
|
|
974
|
+
event: String(params["event"] ?? ""),
|
|
975
|
+
data: params["data"]
|
|
976
|
+
});
|
|
977
|
+
}
|
|
978
|
+
_failAll(reason) {
|
|
979
|
+
const err = new Error(reason);
|
|
980
|
+
for (const { reject } of this._pending.values()) {
|
|
981
|
+
reject(err);
|
|
982
|
+
}
|
|
983
|
+
this._pending.clear();
|
|
984
|
+
for (const { reject } of this._pendingSubscribe.values()) {
|
|
985
|
+
reject(err);
|
|
986
|
+
}
|
|
987
|
+
this._pendingSubscribe.clear();
|
|
988
|
+
for (const sub of this._subscriptions.values()) {
|
|
989
|
+
sub._close();
|
|
990
|
+
}
|
|
991
|
+
this._subscriptions.clear();
|
|
992
|
+
}
|
|
993
|
+
};
|
|
994
|
+
function buildUpgradeHeaders(auth) {
|
|
995
|
+
const headers = {};
|
|
996
|
+
if (auth === void 0) return headers;
|
|
997
|
+
if ("bearer" in auth && auth.bearer !== void 0) {
|
|
998
|
+
headers["Authorization"] = `Bearer ${auth.bearer}`;
|
|
999
|
+
} else if ("apiKey" in auth && auth.apiKey !== void 0) {
|
|
1000
|
+
headers["X-API-Key"] = auth.apiKey;
|
|
1001
|
+
} else if ("basic" in auth && auth.basic !== void 0) {
|
|
1002
|
+
const encoded = Buffer.from(`${auth.basic.username}:${auth.basic.password}`).toString("base64");
|
|
1003
|
+
headers["Authorization"] = `Basic ${encoded}`;
|
|
1004
|
+
}
|
|
1005
|
+
return headers;
|
|
1006
|
+
}
|
|
1007
|
+
async function resolveWebSocketImpl() {
|
|
1008
|
+
if (typeof globalThis.WebSocket !== "undefined") {
|
|
1009
|
+
return globalThis.WebSocket;
|
|
1010
|
+
}
|
|
1011
|
+
try {
|
|
1012
|
+
const { default: WS } = await import("ws");
|
|
1013
|
+
return WS;
|
|
1014
|
+
} catch {
|
|
1015
|
+
throw new Error(
|
|
1016
|
+
"No WebSocket implementation found. Install the 'ws' package: npm install ws"
|
|
1017
|
+
);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
|
|
730
1021
|
// src/client.ts
|
|
731
1022
|
init_identity();
|
|
732
1023
|
|
|
@@ -1254,6 +1545,11 @@ var Client2 = class _Client {
|
|
|
1254
1545
|
const transportType = options.transport || "mcp";
|
|
1255
1546
|
if (transportType === "mcp") {
|
|
1256
1547
|
this.transport = new MCPTransport(this.url, this.auth, identity);
|
|
1548
|
+
} else if (transportType === "websocket") {
|
|
1549
|
+
let wsUrl = this.url;
|
|
1550
|
+
if (wsUrl.startsWith("https://")) wsUrl = "wss://" + wsUrl.slice(8);
|
|
1551
|
+
else if (wsUrl.startsWith("http://")) wsUrl = "ws://" + wsUrl.slice(7);
|
|
1552
|
+
this.transport = new WsTransport(wsUrl, this.auth, options.timeout, identity);
|
|
1257
1553
|
} else {
|
|
1258
1554
|
const rpcUrl = this.url.endsWith("/mcp") ? this.url.slice(0, -4) + "/rpc" : this.url;
|
|
1259
1555
|
this.transport = new JSONRPCTransport(rpcUrl, this.auth, options.timeout, identity);
|
|
@@ -1488,6 +1784,50 @@ var Client2 = class _Client {
|
|
|
1488
1784
|
this.ensureInitialized();
|
|
1489
1785
|
return this.sendWithRetry(() => this.transport.getPrompt(name, args, options));
|
|
1490
1786
|
}
|
|
1787
|
+
// ===== WebSocket push subscriptions =====
|
|
1788
|
+
/**
|
|
1789
|
+
* Subscribe to a server-push topic (WebSocket transport only).
|
|
1790
|
+
*
|
|
1791
|
+
* Requires `transport: 'websocket'` when constructing the client.
|
|
1792
|
+
*
|
|
1793
|
+
* @param topic - Dotted namespace topic, e.g.
|
|
1794
|
+
* `"agents.my-agent-id.events"` or `"tasks.task-123.*"`.
|
|
1795
|
+
* @returns A {@link Subscription} handle. Consume events with
|
|
1796
|
+
* {@link Subscription.recv} or an `for await` loop.
|
|
1797
|
+
*
|
|
1798
|
+
* @example
|
|
1799
|
+
* ```ts
|
|
1800
|
+
* const sub = await client.subscribe('agents.my-agent-id.events');
|
|
1801
|
+
* for await (const event of sub) {
|
|
1802
|
+
* console.log(event.event, event.data);
|
|
1803
|
+
* }
|
|
1804
|
+
* await client.unsubscribe(sub.id);
|
|
1805
|
+
* ```
|
|
1806
|
+
*/
|
|
1807
|
+
async subscribe(topic) {
|
|
1808
|
+
this.ensureInitialized();
|
|
1809
|
+
if (!(this.transport instanceof WsTransport)) {
|
|
1810
|
+
throw new Error(
|
|
1811
|
+
"subscribe() requires transport: 'websocket'. Reinitialise the client with transport: 'websocket'."
|
|
1812
|
+
);
|
|
1813
|
+
}
|
|
1814
|
+
return this.transport.subscribe(topic);
|
|
1815
|
+
}
|
|
1816
|
+
/**
|
|
1817
|
+
* Cancel a server-side push subscription.
|
|
1818
|
+
*
|
|
1819
|
+
* @param subscriptionId - The `id` from the {@link Subscription} returned
|
|
1820
|
+
* by {@link subscribe}.
|
|
1821
|
+
*/
|
|
1822
|
+
async unsubscribe(subscriptionId) {
|
|
1823
|
+
this.ensureInitialized();
|
|
1824
|
+
if (!(this.transport instanceof WsTransport)) {
|
|
1825
|
+
throw new Error(
|
|
1826
|
+
"unsubscribe() requires transport: 'websocket'. Reinitialise the client with transport: 'websocket'."
|
|
1827
|
+
);
|
|
1828
|
+
}
|
|
1829
|
+
return this.transport.unsubscribe(subscriptionId);
|
|
1830
|
+
}
|
|
1491
1831
|
// ===== DG-awareness helpers =====
|
|
1492
1832
|
warnIfNotDg(method) {
|
|
1493
1833
|
if (!this.isDg && !this.dgWarned) {
|
|
@@ -1778,7 +2118,7 @@ function buildToolMeta(raw) {
|
|
|
1778
2118
|
}
|
|
1779
2119
|
|
|
1780
2120
|
// src/index.ts
|
|
1781
|
-
var version = "0.
|
|
2121
|
+
var version = "0.4.0";
|
|
1782
2122
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1783
2123
|
0 && (module.exports = {
|
|
1784
2124
|
AuthError,
|
|
@@ -1795,6 +2135,8 @@ var version = "0.1.0";
|
|
|
1795
2135
|
OAuthTokenProvider,
|
|
1796
2136
|
RateLimitError,
|
|
1797
2137
|
ServerError,
|
|
2138
|
+
WS_SUBPROTOCOL,
|
|
2139
|
+
WsTransport,
|
|
1798
2140
|
deriveTokenEndpoint,
|
|
1799
2141
|
extractMeta,
|
|
1800
2142
|
fetchDgCaCert,
|
package/dist/index.mjs
CHANGED
|
@@ -589,6 +589,295 @@ var JSONRPCTransport = class extends Transport {
|
|
|
589
589
|
}
|
|
590
590
|
};
|
|
591
591
|
|
|
592
|
+
// src/transports/ws.ts
|
|
593
|
+
var SUBPROTOCOL = "datagrout-jsonrpc.v1";
|
|
594
|
+
var SUBSCRIPTION_BUFFER = 256;
|
|
595
|
+
var Subscription = class {
|
|
596
|
+
id;
|
|
597
|
+
topic;
|
|
598
|
+
_queue = [];
|
|
599
|
+
_waiters = [];
|
|
600
|
+
_rejecters = [];
|
|
601
|
+
_closed = false;
|
|
602
|
+
constructor(id, topic) {
|
|
603
|
+
this.id = id;
|
|
604
|
+
this.topic = topic;
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Wait for the next event from this subscription.
|
|
608
|
+
*
|
|
609
|
+
* @throws When the subscription has been closed.
|
|
610
|
+
*/
|
|
611
|
+
recv() {
|
|
612
|
+
if (this._queue.length > 0) {
|
|
613
|
+
return Promise.resolve(this._queue.shift());
|
|
614
|
+
}
|
|
615
|
+
if (this._closed) {
|
|
616
|
+
return Promise.reject(new Error("Subscription closed"));
|
|
617
|
+
}
|
|
618
|
+
return new Promise((resolve, reject) => {
|
|
619
|
+
this._waiters.push(resolve);
|
|
620
|
+
this._rejecters.push(reject);
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
async *[Symbol.asyncIterator]() {
|
|
624
|
+
while (this._queue.length > 0 || !this._closed) {
|
|
625
|
+
try {
|
|
626
|
+
yield await this.recv();
|
|
627
|
+
} catch {
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
// ── Internal ───────────────────────────────────────────────────────────────
|
|
633
|
+
_enqueue(event) {
|
|
634
|
+
if (this._waiters.length > 0) {
|
|
635
|
+
const resolve = this._waiters.shift();
|
|
636
|
+
this._rejecters.shift();
|
|
637
|
+
resolve(event);
|
|
638
|
+
} else if (this._queue.length < SUBSCRIPTION_BUFFER) {
|
|
639
|
+
this._queue.push(event);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
_close() {
|
|
643
|
+
this._closed = true;
|
|
644
|
+
const err = new Error("Subscription closed");
|
|
645
|
+
for (const reject of this._rejecters) {
|
|
646
|
+
reject(err);
|
|
647
|
+
}
|
|
648
|
+
this._waiters.length = 0;
|
|
649
|
+
this._rejecters.length = 0;
|
|
650
|
+
}
|
|
651
|
+
};
|
|
652
|
+
var WsTransport = class extends Transport {
|
|
653
|
+
_url;
|
|
654
|
+
_auth;
|
|
655
|
+
_ws = null;
|
|
656
|
+
_nextId = 0;
|
|
657
|
+
_pending = /* @__PURE__ */ new Map();
|
|
658
|
+
_pendingSubscribe = /* @__PURE__ */ new Map();
|
|
659
|
+
_subscriptions = /* @__PURE__ */ new Map();
|
|
660
|
+
constructor(url, auth, _timeout, _identity) {
|
|
661
|
+
super();
|
|
662
|
+
const scheme = new URL(url).protocol.replace(":", "");
|
|
663
|
+
if (scheme !== "ws" && scheme !== "wss") {
|
|
664
|
+
throw new Error(`WS transport requires a ws:// or wss:// URL, got ${scheme}://`);
|
|
665
|
+
}
|
|
666
|
+
this._url = url;
|
|
667
|
+
this._auth = auth;
|
|
668
|
+
}
|
|
669
|
+
// ── Lifecycle ─────────────────────────────────────────────────────────────
|
|
670
|
+
async connect() {
|
|
671
|
+
if (this._ws !== null) return;
|
|
672
|
+
const WsImpl = await resolveWebSocketImpl();
|
|
673
|
+
const headers = buildUpgradeHeaders(this._auth);
|
|
674
|
+
const ws = new WsImpl(this._url, [SUBPROTOCOL], {
|
|
675
|
+
headers
|
|
676
|
+
});
|
|
677
|
+
await new Promise((resolve, reject) => {
|
|
678
|
+
ws.onopen = () => resolve();
|
|
679
|
+
ws.onerror = (ev) => reject(new Error(`WS connect failed: ${ev.message ?? "unknown"}`));
|
|
680
|
+
});
|
|
681
|
+
ws.onmessage = (ev) => this._handleMessage(ev.data);
|
|
682
|
+
ws.onerror = (_ev) => this._failAll("WS connection error");
|
|
683
|
+
ws.onclose = () => {
|
|
684
|
+
this._failAll("WS connection closed");
|
|
685
|
+
this._ws = null;
|
|
686
|
+
};
|
|
687
|
+
this._ws = ws;
|
|
688
|
+
}
|
|
689
|
+
async disconnect() {
|
|
690
|
+
const ws = this._ws;
|
|
691
|
+
this._ws = null;
|
|
692
|
+
this._failAll("WS connection closed");
|
|
693
|
+
if (ws !== null) {
|
|
694
|
+
try {
|
|
695
|
+
ws.close();
|
|
696
|
+
} catch {
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
// ── Subscriptions ─────────────────────────────────────────────────────────
|
|
701
|
+
/**
|
|
702
|
+
* Open a server-side push subscription for `topic`.
|
|
703
|
+
*
|
|
704
|
+
* @param topic - Dotted namespace topic, e.g.
|
|
705
|
+
* `"agents.my-agent-id.events"` or `"tasks.task-123.*"`.
|
|
706
|
+
* @returns A {@link Subscription} handle whose async-for loop delivers events.
|
|
707
|
+
*/
|
|
708
|
+
async subscribe(topic) {
|
|
709
|
+
this._requireConnected();
|
|
710
|
+
const id = this._mintId();
|
|
711
|
+
return new Promise((resolve, reject) => {
|
|
712
|
+
this._pendingSubscribe.set(id, { topic, resolve, reject });
|
|
713
|
+
this._send({ jsonrpc: "2.0", id, method: "subscribe", params: { topic } });
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
/**
|
|
717
|
+
* Cancel a server-side subscription.
|
|
718
|
+
*
|
|
719
|
+
* The local {@link Subscription} queue is closed immediately.
|
|
720
|
+
*
|
|
721
|
+
* @param subscriptionId - The `id` field from the {@link Subscription}
|
|
722
|
+
* returned by {@link subscribe}.
|
|
723
|
+
*/
|
|
724
|
+
async unsubscribe(subscriptionId) {
|
|
725
|
+
this._requireConnected();
|
|
726
|
+
const sub = this._subscriptions.get(subscriptionId);
|
|
727
|
+
if (sub !== void 0) {
|
|
728
|
+
this._subscriptions.delete(subscriptionId);
|
|
729
|
+
sub._close();
|
|
730
|
+
}
|
|
731
|
+
const id = this._mintId();
|
|
732
|
+
const ackPromise = new Promise((resolve, reject) => {
|
|
733
|
+
this._pending.set(id, { resolve, reject });
|
|
734
|
+
});
|
|
735
|
+
this._send({
|
|
736
|
+
jsonrpc: "2.0",
|
|
737
|
+
id,
|
|
738
|
+
method: "unsubscribe",
|
|
739
|
+
params: { subscription: subscriptionId }
|
|
740
|
+
});
|
|
741
|
+
await Promise.race([
|
|
742
|
+
ackPromise,
|
|
743
|
+
new Promise((resolve) => setTimeout(resolve, 5e3))
|
|
744
|
+
]);
|
|
745
|
+
this._pending.delete(id);
|
|
746
|
+
}
|
|
747
|
+
// ── Transport base implementation ─────────────────────────────────────────
|
|
748
|
+
async listTools(options) {
|
|
749
|
+
return await this._request("tools/list", options);
|
|
750
|
+
}
|
|
751
|
+
async callTool(name, args, _options) {
|
|
752
|
+
return this._request("tools/call", { name, arguments: args });
|
|
753
|
+
}
|
|
754
|
+
async listResources(_options) {
|
|
755
|
+
return await this._request("resources/list");
|
|
756
|
+
}
|
|
757
|
+
async readResource(uri, _options) {
|
|
758
|
+
return this._request("resources/read", { uri });
|
|
759
|
+
}
|
|
760
|
+
async listPrompts(_options) {
|
|
761
|
+
return await this._request("prompts/list");
|
|
762
|
+
}
|
|
763
|
+
async getPrompt(name, args, _options) {
|
|
764
|
+
return this._request("prompts/get", { name, arguments: args });
|
|
765
|
+
}
|
|
766
|
+
// ── Internal ──────────────────────────────────────────────────────────────
|
|
767
|
+
_mintId() {
|
|
768
|
+
return `ws-${++this._nextId}`;
|
|
769
|
+
}
|
|
770
|
+
_requireConnected() {
|
|
771
|
+
if (this._ws === null) {
|
|
772
|
+
throw new Error("WS transport not connected. Call connect() first.");
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
_send(payload) {
|
|
776
|
+
this._ws.send(JSON.stringify(payload));
|
|
777
|
+
}
|
|
778
|
+
async _request(method, params) {
|
|
779
|
+
this._requireConnected();
|
|
780
|
+
const id = this._mintId();
|
|
781
|
+
return new Promise((resolve, reject) => {
|
|
782
|
+
this._pending.set(id, { resolve, reject });
|
|
783
|
+
this._send({ jsonrpc: "2.0", id, method, ...params !== void 0 ? { params } : {} });
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
_handleMessage(data) {
|
|
787
|
+
let msg;
|
|
788
|
+
try {
|
|
789
|
+
msg = JSON.parse(data);
|
|
790
|
+
} catch {
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
if (!("id" in msg)) {
|
|
794
|
+
if (msg["method"] === "notification") {
|
|
795
|
+
this._routeNotification(msg["params"]);
|
|
796
|
+
}
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
const msgId = String(msg["id"]);
|
|
800
|
+
const pendingSub = this._pendingSubscribe.get(msgId);
|
|
801
|
+
if (pendingSub !== void 0) {
|
|
802
|
+
this._pendingSubscribe.delete(msgId);
|
|
803
|
+
const err = msg["error"];
|
|
804
|
+
if (err !== void 0) {
|
|
805
|
+
pendingSub.reject(new Error(String(err["message"] ?? "Subscribe failed")));
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
808
|
+
const result = msg["result"] ?? {};
|
|
809
|
+
const subId = String(result["subscription"] ?? msgId);
|
|
810
|
+
const sub = new Subscription(subId, pendingSub.topic);
|
|
811
|
+
this._subscriptions.set(subId, sub);
|
|
812
|
+
pendingSub.resolve(sub);
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
const pending = this._pending.get(msgId);
|
|
816
|
+
if (pending !== void 0) {
|
|
817
|
+
this._pending.delete(msgId);
|
|
818
|
+
const err = msg["error"];
|
|
819
|
+
if (err !== void 0) {
|
|
820
|
+
pending.reject(new Error(String(err["message"] ?? "RPC error")));
|
|
821
|
+
} else {
|
|
822
|
+
pending.resolve(msg["result"]);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
_routeNotification(params) {
|
|
827
|
+
if (params === void 0) return;
|
|
828
|
+
const subId = params["subscription"];
|
|
829
|
+
if (typeof subId !== "string") return;
|
|
830
|
+
const sub = this._subscriptions.get(subId);
|
|
831
|
+
if (sub === void 0) return;
|
|
832
|
+
sub._enqueue({
|
|
833
|
+
subscription: subId,
|
|
834
|
+
event: String(params["event"] ?? ""),
|
|
835
|
+
data: params["data"]
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
_failAll(reason) {
|
|
839
|
+
const err = new Error(reason);
|
|
840
|
+
for (const { reject } of this._pending.values()) {
|
|
841
|
+
reject(err);
|
|
842
|
+
}
|
|
843
|
+
this._pending.clear();
|
|
844
|
+
for (const { reject } of this._pendingSubscribe.values()) {
|
|
845
|
+
reject(err);
|
|
846
|
+
}
|
|
847
|
+
this._pendingSubscribe.clear();
|
|
848
|
+
for (const sub of this._subscriptions.values()) {
|
|
849
|
+
sub._close();
|
|
850
|
+
}
|
|
851
|
+
this._subscriptions.clear();
|
|
852
|
+
}
|
|
853
|
+
};
|
|
854
|
+
function buildUpgradeHeaders(auth) {
|
|
855
|
+
const headers = {};
|
|
856
|
+
if (auth === void 0) return headers;
|
|
857
|
+
if ("bearer" in auth && auth.bearer !== void 0) {
|
|
858
|
+
headers["Authorization"] = `Bearer ${auth.bearer}`;
|
|
859
|
+
} else if ("apiKey" in auth && auth.apiKey !== void 0) {
|
|
860
|
+
headers["X-API-Key"] = auth.apiKey;
|
|
861
|
+
} else if ("basic" in auth && auth.basic !== void 0) {
|
|
862
|
+
const encoded = Buffer.from(`${auth.basic.username}:${auth.basic.password}`).toString("base64");
|
|
863
|
+
headers["Authorization"] = `Basic ${encoded}`;
|
|
864
|
+
}
|
|
865
|
+
return headers;
|
|
866
|
+
}
|
|
867
|
+
async function resolveWebSocketImpl() {
|
|
868
|
+
if (typeof globalThis.WebSocket !== "undefined") {
|
|
869
|
+
return globalThis.WebSocket;
|
|
870
|
+
}
|
|
871
|
+
try {
|
|
872
|
+
const { default: WS } = await import("ws");
|
|
873
|
+
return WS;
|
|
874
|
+
} catch {
|
|
875
|
+
throw new Error(
|
|
876
|
+
"No WebSocket implementation found. Install the 'ws' package: npm install ws"
|
|
877
|
+
);
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
|
|
592
881
|
// src/client.ts
|
|
593
882
|
init_identity();
|
|
594
883
|
|
|
@@ -1116,6 +1405,11 @@ var Client2 = class _Client {
|
|
|
1116
1405
|
const transportType = options.transport || "mcp";
|
|
1117
1406
|
if (transportType === "mcp") {
|
|
1118
1407
|
this.transport = new MCPTransport(this.url, this.auth, identity);
|
|
1408
|
+
} else if (transportType === "websocket") {
|
|
1409
|
+
let wsUrl = this.url;
|
|
1410
|
+
if (wsUrl.startsWith("https://")) wsUrl = "wss://" + wsUrl.slice(8);
|
|
1411
|
+
else if (wsUrl.startsWith("http://")) wsUrl = "ws://" + wsUrl.slice(7);
|
|
1412
|
+
this.transport = new WsTransport(wsUrl, this.auth, options.timeout, identity);
|
|
1119
1413
|
} else {
|
|
1120
1414
|
const rpcUrl = this.url.endsWith("/mcp") ? this.url.slice(0, -4) + "/rpc" : this.url;
|
|
1121
1415
|
this.transport = new JSONRPCTransport(rpcUrl, this.auth, options.timeout, identity);
|
|
@@ -1350,6 +1644,50 @@ var Client2 = class _Client {
|
|
|
1350
1644
|
this.ensureInitialized();
|
|
1351
1645
|
return this.sendWithRetry(() => this.transport.getPrompt(name, args, options));
|
|
1352
1646
|
}
|
|
1647
|
+
// ===== WebSocket push subscriptions =====
|
|
1648
|
+
/**
|
|
1649
|
+
* Subscribe to a server-push topic (WebSocket transport only).
|
|
1650
|
+
*
|
|
1651
|
+
* Requires `transport: 'websocket'` when constructing the client.
|
|
1652
|
+
*
|
|
1653
|
+
* @param topic - Dotted namespace topic, e.g.
|
|
1654
|
+
* `"agents.my-agent-id.events"` or `"tasks.task-123.*"`.
|
|
1655
|
+
* @returns A {@link Subscription} handle. Consume events with
|
|
1656
|
+
* {@link Subscription.recv} or an `for await` loop.
|
|
1657
|
+
*
|
|
1658
|
+
* @example
|
|
1659
|
+
* ```ts
|
|
1660
|
+
* const sub = await client.subscribe('agents.my-agent-id.events');
|
|
1661
|
+
* for await (const event of sub) {
|
|
1662
|
+
* console.log(event.event, event.data);
|
|
1663
|
+
* }
|
|
1664
|
+
* await client.unsubscribe(sub.id);
|
|
1665
|
+
* ```
|
|
1666
|
+
*/
|
|
1667
|
+
async subscribe(topic) {
|
|
1668
|
+
this.ensureInitialized();
|
|
1669
|
+
if (!(this.transport instanceof WsTransport)) {
|
|
1670
|
+
throw new Error(
|
|
1671
|
+
"subscribe() requires transport: 'websocket'. Reinitialise the client with transport: 'websocket'."
|
|
1672
|
+
);
|
|
1673
|
+
}
|
|
1674
|
+
return this.transport.subscribe(topic);
|
|
1675
|
+
}
|
|
1676
|
+
/**
|
|
1677
|
+
* Cancel a server-side push subscription.
|
|
1678
|
+
*
|
|
1679
|
+
* @param subscriptionId - The `id` from the {@link Subscription} returned
|
|
1680
|
+
* by {@link subscribe}.
|
|
1681
|
+
*/
|
|
1682
|
+
async unsubscribe(subscriptionId) {
|
|
1683
|
+
this.ensureInitialized();
|
|
1684
|
+
if (!(this.transport instanceof WsTransport)) {
|
|
1685
|
+
throw new Error(
|
|
1686
|
+
"unsubscribe() requires transport: 'websocket'. Reinitialise the client with transport: 'websocket'."
|
|
1687
|
+
);
|
|
1688
|
+
}
|
|
1689
|
+
return this.transport.unsubscribe(subscriptionId);
|
|
1690
|
+
}
|
|
1353
1691
|
// ===== DG-awareness helpers =====
|
|
1354
1692
|
warnIfNotDg(method) {
|
|
1355
1693
|
if (!this.isDg && !this.dgWarned) {
|
|
@@ -1640,7 +1978,7 @@ function buildToolMeta(raw) {
|
|
|
1640
1978
|
}
|
|
1641
1979
|
|
|
1642
1980
|
// src/index.ts
|
|
1643
|
-
var version = "0.
|
|
1981
|
+
var version = "0.4.0";
|
|
1644
1982
|
export {
|
|
1645
1983
|
AuthError,
|
|
1646
1984
|
Client2 as Client,
|
|
@@ -1656,6 +1994,8 @@ export {
|
|
|
1656
1994
|
OAuthTokenProvider,
|
|
1657
1995
|
RateLimitError,
|
|
1658
1996
|
ServerError,
|
|
1997
|
+
SUBPROTOCOL as WS_SUBPROTOCOL,
|
|
1998
|
+
WsTransport,
|
|
1659
1999
|
deriveTokenEndpoint,
|
|
1660
2000
|
extractMeta,
|
|
1661
2001
|
fetchDgCaCert,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@datagrout/conduit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.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",
|
|
@@ -45,10 +45,12 @@
|
|
|
45
45
|
"url": "https://github.com/DataGrout/conduit-sdk/issues"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
48
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
49
|
+
"ws": "^8.18.0"
|
|
49
50
|
},
|
|
50
51
|
"devDependencies": {
|
|
51
52
|
"@types/node": "^20.11.0",
|
|
53
|
+
"@types/ws": "^8.5.10",
|
|
52
54
|
"@typescript-eslint/eslint-plugin": "^6.19.0",
|
|
53
55
|
"@typescript-eslint/parser": "^6.19.0",
|
|
54
56
|
"eslint": "^8.56.0",
|