@awarevue/agent-sdk 2.0.75 → 2.0.76

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.
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,257 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const vitest_1 = require("vitest");
4
+ const rxjs_1 = require("rxjs");
5
+ const agent_server_1 = require("../agent-server");
6
+ const in_memory_1 = require("../hubs/in-memory");
7
+ const constants_1 = require("../constants");
8
+ const in_memory_transport_1 = require("./in-memory-transport");
9
+ let msgSeq = 0;
10
+ const envelope = (from, payload) => ({
11
+ ...payload,
12
+ id: `msg-${++msgSeq}`,
13
+ from,
14
+ version: constants_1.AGENT_PROTOCOL_VERSION,
15
+ on: Date.now(),
16
+ });
17
+ const registerPayload = (agentId) => envelope(agentId, {
18
+ kind: 'register',
19
+ providers: {
20
+ 'test-provider': {
21
+ title: 'Test Provider',
22
+ configSchema: {},
23
+ configDefault: {},
24
+ },
25
+ },
26
+ });
27
+ const startRsPayload = (agentId, requestId) => envelope(agentId, { kind: 'start-rs', requestId });
28
+ const stopRsPayload = (agentId, requestId) => envelope(agentId, { kind: 'stop-rs', requestId });
29
+ /**
30
+ * Connects a fake agent to the hub and returns the agent's side of the transport.
31
+ */
32
+ function connectAgent(hub, peerId) {
33
+ const [agentSide, serverSide] = (0, in_memory_transport_1.createTransportPair)();
34
+ hub.addPeer(peerId, serverSide);
35
+ return agentSide;
36
+ }
37
+ /**
38
+ * Subscribe BEFORE sending — in-memory transports deliver synchronously,
39
+ * so the reply arrives during send() and is missed if subscribed after.
40
+ */
41
+ function sendAndAwait(agent, msg, replyKind) {
42
+ const p = (0, rxjs_1.firstValueFrom)(agent.messages$.pipe((0, rxjs_1.filter)((m) => m.kind === replyKind), (0, rxjs_1.timeout)(1000)));
43
+ agent.send(msg);
44
+ return p;
45
+ }
46
+ /* ------------------------------------------------------------------ */
47
+ /* Tests */
48
+ /* ------------------------------------------------------------------ */
49
+ (0, vitest_1.describe)('AgentServer', () => {
50
+ let hub;
51
+ let server;
52
+ (0, vitest_1.beforeEach)(() => {
53
+ hub = new in_memory_1.InMemoryHub();
54
+ msgSeq = 0;
55
+ });
56
+ (0, vitest_1.afterEach)(() => {
57
+ server === null || server === void 0 ? void 0 : server.finalize();
58
+ hub === null || hub === void 0 ? void 0 : hub.close();
59
+ });
60
+ // ── Registration ──────────────────────────────────────────────────
61
+ (0, vitest_1.describe)('registration', () => {
62
+ (0, vitest_1.it)('should resolve agentId from msg.from on register', async () => {
63
+ const registered = [];
64
+ server = new agent_server_1.AgentServer(hub, {
65
+ onRegister: ({ agentId, accept }) => {
66
+ registered.push(agentId);
67
+ accept();
68
+ },
69
+ });
70
+ server.init();
71
+ const agent = connectAgent(hub, 1);
72
+ const reply = await sendAndAwait(agent, registerPayload('my-agent'), 'register-rs');
73
+ (0, vitest_1.expect)(reply.kind).toBe('register-rs');
74
+ (0, vitest_1.expect)(registered).toEqual(['my-agent']);
75
+ });
76
+ (0, vitest_1.it)('should allow rejecting registration', async () => {
77
+ server = new agent_server_1.AgentServer(hub, {
78
+ onRegister: ({ reject }) => {
79
+ reject('not allowed');
80
+ },
81
+ });
82
+ server.init();
83
+ const agent = connectAgent(hub, 1);
84
+ const reply = await sendAndAwait(agent, registerPayload('bad-agent'), 'error-rs');
85
+ (0, vitest_1.expect)(reply.kind).toBe('error-rs');
86
+ (0, vitest_1.expect)(reply.error).toBe('not allowed');
87
+ });
88
+ (0, vitest_1.it)('should handle multiple agents with different peer IDs', async () => {
89
+ const registered = [];
90
+ server = new agent_server_1.AgentServer(hub, {
91
+ onRegister: ({ agentId, accept }) => {
92
+ registered.push(agentId);
93
+ accept();
94
+ },
95
+ });
96
+ server.init();
97
+ const agentA = connectAgent(hub, 1);
98
+ const agentB = connectAgent(hub, 2);
99
+ await sendAndAwait(agentA, registerPayload('agent-alpha'), 'register-rs');
100
+ await sendAndAwait(agentB, registerPayload('agent-beta'), 'register-rs');
101
+ (0, vitest_1.expect)(registered).toContain('agent-alpha');
102
+ (0, vitest_1.expect)(registered).toContain('agent-beta');
103
+ });
104
+ });
105
+ // ── Start / Stop ──────────────────────────────────────────────────
106
+ (0, vitest_1.describe)('start and stop', () => {
107
+ (0, vitest_1.it)('should route startAgent by agentId to correct peer', async () => {
108
+ server = new agent_server_1.AgentServer(hub, {
109
+ onRegister: ({ accept }) => accept(),
110
+ });
111
+ server.init();
112
+ const agentA = connectAgent(hub, 1);
113
+ const agentB = connectAgent(hub, 2);
114
+ await sendAndAwait(agentA, registerPayload('agent-alpha'), 'register-rs');
115
+ await sendAndAwait(agentB, registerPayload('agent-beta'), 'register-rs');
116
+ // subscribe before sending
117
+ const startPromise = (0, rxjs_1.firstValueFrom)(agentB.messages$.pipe((0, rxjs_1.filter)((m) => m.kind === 'start'), (0, rxjs_1.timeout)(1000)));
118
+ server.startAgent({
119
+ agentId: 'agent-beta',
120
+ provider: 'test-provider',
121
+ config: { url: 'http://test' },
122
+ lastEventForeignRef: null,
123
+ lastEventTimestamp: null,
124
+ });
125
+ const startMsg = await startPromise;
126
+ (0, vitest_1.expect)(startMsg.kind).toBe('start');
127
+ (0, vitest_1.expect)(startMsg.provider).toBe('test-provider');
128
+ });
129
+ (0, vitest_1.it)('should fire onStarted when agent replies with start-rs', async () => {
130
+ const started = [];
131
+ server = new agent_server_1.AgentServer(hub, {
132
+ onRegister: ({ accept }) => accept(),
133
+ onStarted: ({ agentId }) => started.push(agentId),
134
+ });
135
+ server.init();
136
+ const agent = connectAgent(hub, 1);
137
+ await sendAndAwait(agent, registerPayload('my-agent'), 'register-rs');
138
+ agent.send(startRsPayload('my-agent', 'req-1'));
139
+ (0, vitest_1.expect)(started).toEqual(['my-agent']);
140
+ });
141
+ (0, vitest_1.it)('should fire onStopped when agent replies with stop-rs', async () => {
142
+ const stopped = [];
143
+ server = new agent_server_1.AgentServer(hub, {
144
+ onRegister: ({ accept }) => accept(),
145
+ onStopped: ({ agentId }) => stopped.push(agentId),
146
+ });
147
+ server.init();
148
+ const agent = connectAgent(hub, 1);
149
+ await sendAndAwait(agent, registerPayload('my-agent'), 'register-rs');
150
+ agent.send(stopRsPayload('my-agent', 'req-2'));
151
+ (0, vitest_1.expect)(stopped).toEqual(['my-agent']);
152
+ });
153
+ (0, vitest_1.it)('should send stop to the correct agent', async () => {
154
+ server = new agent_server_1.AgentServer(hub, {
155
+ onRegister: ({ accept }) => accept(),
156
+ });
157
+ server.init();
158
+ const agent = connectAgent(hub, 1);
159
+ await sendAndAwait(agent, registerPayload('my-agent'), 'register-rs');
160
+ const stopPromise = (0, rxjs_1.firstValueFrom)(agent.messages$.pipe((0, rxjs_1.filter)((m) => m.kind === 'stop'), (0, rxjs_1.timeout)(1000)));
161
+ server.stopAgent('my-agent', 'test-provider');
162
+ const stopMsg = await stopPromise;
163
+ (0, vitest_1.expect)(stopMsg.kind).toBe('stop');
164
+ (0, vitest_1.expect)(stopMsg.provider).toBe('test-provider');
165
+ });
166
+ });
167
+ // ── Disconnect ────────────────────────────────────────────────────
168
+ (0, vitest_1.describe)('disconnect', () => {
169
+ (0, vitest_1.it)('should fire onUnregistered with agentId when peer disconnects after registration', async () => {
170
+ const unregistered = [];
171
+ server = new agent_server_1.AgentServer(hub, {
172
+ onRegister: ({ accept }) => accept(),
173
+ onUnregistered: ({ agentId }) => unregistered.push(agentId),
174
+ });
175
+ server.init();
176
+ const agent = connectAgent(hub, 1);
177
+ await sendAndAwait(agent, registerPayload('my-agent'), 'register-rs');
178
+ agent.close();
179
+ (0, vitest_1.expect)(unregistered).toEqual(['my-agent']);
180
+ });
181
+ (0, vitest_1.it)('should not fire onUnregistered for peers that never registered', async () => {
182
+ const unregistered = [];
183
+ server = new agent_server_1.AgentServer(hub, {
184
+ onUnregistered: ({ agentId }) => unregistered.push(agentId),
185
+ });
186
+ server.init();
187
+ // connect and immediately disconnect without registering
188
+ const agent = connectAgent(hub, 1);
189
+ agent.close();
190
+ await new Promise((r) => setTimeout(r, 10));
191
+ (0, vitest_1.expect)(unregistered).toEqual([]);
192
+ });
193
+ (0, vitest_1.it)('should clean up maps after disconnect', async () => {
194
+ server = new agent_server_1.AgentServer(hub, {
195
+ onRegister: ({ accept }) => accept(),
196
+ });
197
+ server.init();
198
+ const agent = connectAgent(hub, 1);
199
+ await sendAndAwait(agent, registerPayload('my-agent'), 'register-rs');
200
+ (0, vitest_1.expect)(server.getAgentSender('my-agent')).not.toBeNull();
201
+ agent.close();
202
+ (0, vitest_1.expect)(server.getAgentSender('my-agent')).toBeNull();
203
+ });
204
+ });
205
+ // ── Protocol validation ───────────────────────────────────────────
206
+ (0, vitest_1.describe)('protocol validation', () => {
207
+ (0, vitest_1.it)('should reject messages with wrong protocol version', async () => {
208
+ server = new agent_server_1.AgentServer(hub);
209
+ server.init();
210
+ const notifications = [];
211
+ server.notifications$.subscribe((n) => notifications.push(n));
212
+ const agent = connectAgent(hub, 1);
213
+ const badMsg = {
214
+ kind: 'register',
215
+ providers: {},
216
+ id: 'bad-1',
217
+ from: 'bad-agent',
218
+ version: 999,
219
+ on: Date.now(),
220
+ };
221
+ const reply = await sendAndAwait(agent, badMsg, 'error-rs');
222
+ (0, vitest_1.expect)(reply.kind).toBe('error-rs');
223
+ (0, vitest_1.expect)(reply.error).toContain('Incompatible protocol version');
224
+ (0, vitest_1.expect)(notifications.some((n) => n.includes('incompatible'))).toBe(true);
225
+ });
226
+ });
227
+ // ── messages$ stream ──────────────────────────────────────────────
228
+ (0, vitest_1.describe)('messages$ stream', () => {
229
+ (0, vitest_1.it)('should include agentId alongside peer in emitted messages', async () => {
230
+ server = new agent_server_1.AgentServer(hub, {
231
+ onRegister: ({ accept }) => accept(),
232
+ });
233
+ server.init();
234
+ const emittedPromise = (0, rxjs_1.firstValueFrom)(server.messages$.pipe((0, rxjs_1.timeout)(1000)));
235
+ const agent = connectAgent(hub, 42);
236
+ agent.send(registerPayload('stream-agent'));
237
+ const emitted = await emittedPromise;
238
+ (0, vitest_1.expect)(emitted.peer).toBe(42);
239
+ (0, vitest_1.expect)(emitted.agentId).toBe('stream-agent');
240
+ (0, vitest_1.expect)(emitted.msg.kind).toBe('register');
241
+ });
242
+ });
243
+ // ── finalize ──────────────────────────────────────────────────────
244
+ (0, vitest_1.describe)('finalize', () => {
245
+ (0, vitest_1.it)('should clear internal maps on finalize', async () => {
246
+ server = new agent_server_1.AgentServer(hub, {
247
+ onRegister: ({ accept }) => accept(),
248
+ });
249
+ server.init();
250
+ const agent = connectAgent(hub, 1);
251
+ await sendAndAwait(agent, registerPayload('my-agent'), 'register-rs');
252
+ (0, vitest_1.expect)(server.getAgentSender('my-agent')).not.toBeNull();
253
+ server.finalize();
254
+ (0, vitest_1.expect)(server.getAgentSender('my-agent')).toBeNull();
255
+ });
256
+ });
257
+ });
@@ -0,0 +1,10 @@
1
+ import { DuplexTransport } from '../transport_types';
2
+ /**
3
+ * Creates a linked pair of in-process DuplexTransports.
4
+ * Writing to one side is immediately readable on the other.
5
+ * No serialization, no network — pure in-memory for testing.
6
+ */
7
+ export declare function createTransportPair<TA, TB>(): [
8
+ DuplexTransport<TA, TB>,
9
+ DuplexTransport<TB, TA>
10
+ ];
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createTransportPair = createTransportPair;
4
+ const rxjs_1 = require("rxjs");
5
+ /**
6
+ * Creates a linked pair of in-process DuplexTransports.
7
+ * Writing to one side is immediately readable on the other.
8
+ * No serialization, no network — pure in-memory for testing.
9
+ */
10
+ function createTransportPair() {
11
+ const aToB = new rxjs_1.Subject();
12
+ const bToA = new rxjs_1.Subject();
13
+ const aConnected = new rxjs_1.BehaviorSubject(true);
14
+ const bConnected = new rxjs_1.BehaviorSubject(true);
15
+ const close = () => {
16
+ aConnected.next(false);
17
+ bConnected.next(false);
18
+ aConnected.complete();
19
+ bConnected.complete();
20
+ aToB.complete();
21
+ bToA.complete();
22
+ };
23
+ const sideA = {
24
+ connected$: aConnected.asObservable(),
25
+ messages$: bToA.asObservable(),
26
+ send: (msg) => {
27
+ if (aConnected.value)
28
+ aToB.next(msg);
29
+ },
30
+ close,
31
+ };
32
+ const sideB = {
33
+ connected$: bConnected.asObservable(),
34
+ messages$: aToB.asObservable(),
35
+ send: (msg) => {
36
+ if (bConnected.value)
37
+ bToA.next(msg);
38
+ },
39
+ close,
40
+ };
41
+ return [sideA, sideB];
42
+ }
@@ -4,6 +4,7 @@ exports.createAgentApp = createAgentApp;
4
4
  const agent_app_1 = require("./agent-app");
5
5
  const logging_1 = require("./transports/logging");
6
6
  const ws_1 = require("./transports/ws");
7
+ const ws_json_encoder_1 = require("./transports/ws-json-encoder");
7
8
  /**
8
9
  * Creates an AgentApp instance with default transport settings (WS transport wrapped in a logging decorator) if no custom transport is provided.
9
10
  * @param agent The agent instance to use.
@@ -12,11 +13,11 @@ const ws_1 = require("./transports/ws");
12
13
  */
13
14
  function createAgentApp(agent, options) {
14
15
  const { url, apiKey, transport, ...rest } = options;
15
- const finalTransport = transport !== null && transport !== void 0 ? transport : new logging_1.LoggingDuplexTransport(new ws_1.WsDuplexTransport({
16
+ const finalTransport = transport !== null && transport !== void 0 ? transport : new logging_1.LoggingDuplexTransport(new ws_json_encoder_1.WsJsonEncoder(new ws_1.WsDuplexTransport({
16
17
  url,
17
18
  headers: {
18
19
  Authorization: `APIKey ${apiKey}`,
19
20
  },
20
- }));
21
+ })));
21
22
  return new agent_app_1.AgentApp(agent, { ...rest, transport: finalTransport });
22
23
  }
@@ -0,0 +1,50 @@
1
+ import { FromAgent, FromServer, Message, RegisterRq, StartServiceRq } from '@awarevue/api-types';
2
+ import { HubTransport, PeerId } from './transport_types';
3
+ import { AgentProtocol } from './agent-protocol';
4
+ import { Subject } from 'rxjs';
5
+ export interface OnUnregisteredEventArgs {
6
+ agentId: string;
7
+ reason?: string;
8
+ }
9
+ export type OnRegisterEventArgs = {
10
+ agentId: string;
11
+ accept: () => void;
12
+ reject: (reason: string) => void;
13
+ } & Omit<RegisterRq, 'kind'>;
14
+ export type OnStoppedEventArgs = {
15
+ agentId: string;
16
+ };
17
+ export type OnStartedEventArgs = {
18
+ agentId: string;
19
+ };
20
+ export type StartAgentArgs = {
21
+ agentId: string;
22
+ } & Omit<StartServiceRq, 'kind'>;
23
+ export type AgentServerOptions = {
24
+ /** Timeout for awaiting replies to sent messages (default 10s) */
25
+ replyTimeout?: number;
26
+ onRegister?: (args: OnRegisterEventArgs) => void;
27
+ onUnregistered?: (args: OnUnregisteredEventArgs) => void;
28
+ onStarted?: (args: OnStartedEventArgs) => void;
29
+ onStopped?: (args: OnStoppedEventArgs) => void;
30
+ };
31
+ export declare class AgentServer<TPeer extends PeerId = string> {
32
+ private readonly hub;
33
+ private readonly options;
34
+ readonly notifications$: Subject<string>;
35
+ readonly messages$: Subject<{
36
+ msg: Message<FromAgent>;
37
+ peer: TPeer;
38
+ agentId: string;
39
+ }>;
40
+ private sub;
41
+ private readonly _agentToPeer;
42
+ private readonly _peerToAgent;
43
+ constructor(hub: HubTransport<Message<FromAgent>, Message<FromServer>, TPeer>, options?: AgentServerOptions);
44
+ init(): void;
45
+ startAgent({ agentId, ...rest }: StartAgentArgs): void;
46
+ stopAgent(agentId: string, provider: string): void;
47
+ finalize(): void;
48
+ getAgentSender(agentId: string): AgentProtocol<'server'> | null;
49
+ private getPeerSender;
50
+ }
@@ -0,0 +1,142 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AgentServer = void 0;
4
+ const api_types_1 = require("@awarevue/api-types");
5
+ const agent_protocol_1 = require("./agent-protocol");
6
+ const rxjs_1 = require("rxjs");
7
+ const constants_1 = require("./constants");
8
+ class AgentServer {
9
+ constructor(hub, options = {}) {
10
+ this.hub = hub;
11
+ this.options = options;
12
+ this.notifications$ = new rxjs_1.Subject();
13
+ this.messages$ = new rxjs_1.Subject();
14
+ this.sub = null;
15
+ this._agentToPeer = new Map();
16
+ this._peerToAgent = new Map();
17
+ }
18
+ init() {
19
+ if (this.sub) {
20
+ return;
21
+ }
22
+ const processMessages$ = this.hub.messages$.pipe(
23
+ // validate
24
+ (0, rxjs_1.map)(({ msg, peer }) => ({ msg, peer, isValid: (0, api_types_1.isMessageFromAgent)(msg) })), (0, rxjs_1.tap)(({ msg, peer, isValid }) => {
25
+ var _a;
26
+ const id = (_a = msg.id) !== null && _a !== void 0 ? _a : 'unknown';
27
+ if (msg.version !== constants_1.AGENT_PROTOCOL_VERSION) {
28
+ this.notifications$.next(`Agent ${msg.from} has incompatible protocol version: ${msg.version}`);
29
+ const protocol = this.getPeerSender(peer);
30
+ protocol === null || protocol === void 0 ? void 0 : protocol.send({
31
+ kind: 'error-rs',
32
+ requestId: id,
33
+ error: `Incompatible protocol version. Expected ${constants_1.AGENT_PROTOCOL_VERSION} but got ${msg.version}`,
34
+ });
35
+ }
36
+ else if (!isValid) {
37
+ // log
38
+ const issues = (0, api_types_1.getAgentMessageIssues)(msg).join(', ');
39
+ this.notifications$.next(`Received invalid message from agent ${peer}: ${issues}`);
40
+ const sender = this.getPeerSender(peer);
41
+ sender === null || sender === void 0 ? void 0 : sender.send({
42
+ kind: 'error-rs',
43
+ requestId: id,
44
+ error: issues,
45
+ });
46
+ return;
47
+ }
48
+ }),
49
+ // dismiss invalid protocol messages
50
+ (0, rxjs_1.filter)(({ msg, isValid }) => msg.version === constants_1.AGENT_PROTOCOL_VERSION && isValid),
51
+ // forward valid messages to the messages$ stream
52
+ (0, rxjs_1.tap)((m) => {
53
+ var _a, _b, _c, _d, _e, _f;
54
+ const agentId = m.msg.from;
55
+ // if this is a registration message, establish the agentId ↔ peer mapping
56
+ if (m.msg.kind === 'register') {
57
+ this._agentToPeer.set(agentId, m.peer);
58
+ this._peerToAgent.set(m.peer, agentId);
59
+ this.messages$.next({ ...m, agentId });
60
+ (_b = (_a = this.options).onRegister) === null || _b === void 0 ? void 0 : _b.call(_a, {
61
+ ...m.msg,
62
+ agentId,
63
+ reject: (reason) => {
64
+ const protocol = this.getPeerSender(m.peer);
65
+ protocol === null || protocol === void 0 ? void 0 : protocol.send({
66
+ kind: 'error-rs',
67
+ requestId: m.msg.id,
68
+ error: reason,
69
+ });
70
+ this._agentToPeer.delete(agentId);
71
+ this._peerToAgent.delete(m.peer);
72
+ },
73
+ accept: () => {
74
+ const protocol = this.getPeerSender(m.peer);
75
+ protocol === null || protocol === void 0 ? void 0 : protocol.send({
76
+ kind: 'register-rs',
77
+ requestId: m.msg.id,
78
+ });
79
+ },
80
+ });
81
+ }
82
+ else {
83
+ this.messages$.next({ ...m, agentId });
84
+ if (m.msg.kind === 'start-rs') {
85
+ (_d = (_c = this.options).onStarted) === null || _d === void 0 ? void 0 : _d.call(_c, { agentId });
86
+ }
87
+ else if (m.msg.kind === 'stop-rs') {
88
+ (_f = (_e = this.options).onStopped) === null || _f === void 0 ? void 0 : _f.call(_e, { agentId });
89
+ }
90
+ }
91
+ }));
92
+ const processConnections$ = this.hub.peerEvents$.pipe((0, rxjs_1.filter)((event) => event.type === 'leave'), (0, rxjs_1.tap)(({ peer, reason }) => {
93
+ var _a, _b;
94
+ const agentId = this._peerToAgent.get(peer);
95
+ this.notifications$.next(`Peer ${peer}${agentId ? ` (agent: ${agentId})` : ''} left: ${reason !== null && reason !== void 0 ? reason : 'unknown reason'}`);
96
+ // clean up maps
97
+ if (agentId) {
98
+ this._peerToAgent.delete(peer);
99
+ this._agentToPeer.delete(agentId);
100
+ (_b = (_a = this.options).onUnregistered) === null || _b === void 0 ? void 0 : _b.call(_a, {
101
+ agentId,
102
+ reason: reason,
103
+ });
104
+ }
105
+ }));
106
+ this.sub = (0, rxjs_1.merge)(processMessages$, processConnections$).subscribe();
107
+ }
108
+ startAgent({ agentId, ...rest }) {
109
+ const protocol = this.getAgentSender(agentId);
110
+ protocol === null || protocol === void 0 ? void 0 : protocol.send({ kind: 'start', ...rest });
111
+ }
112
+ stopAgent(agentId, provider) {
113
+ const protocol = this.getAgentSender(agentId);
114
+ protocol === null || protocol === void 0 ? void 0 : protocol.send({ kind: 'stop', provider });
115
+ }
116
+ finalize() {
117
+ var _a;
118
+ (_a = this.sub) === null || _a === void 0 ? void 0 : _a.unsubscribe();
119
+ this.sub = null;
120
+ this._agentToPeer.clear();
121
+ this._peerToAgent.clear();
122
+ }
123
+ getAgentSender(agentId) {
124
+ const peer = this._agentToPeer.get(agentId);
125
+ if (peer === undefined)
126
+ return null;
127
+ return this.getPeerSender(peer);
128
+ }
129
+ getPeerSender(peer) {
130
+ var _a;
131
+ const connection = this.hub.connection(peer);
132
+ if (!connection) {
133
+ return null;
134
+ }
135
+ const agentId = (_a = this._peerToAgent.get(peer)) !== null && _a !== void 0 ? _a : `${peer}`;
136
+ return new agent_protocol_1.AgentProtocol(connection, {
137
+ replyTimeout: this.options.replyTimeout,
138
+ id: agentId,
139
+ });
140
+ }
141
+ }
142
+ exports.AgentServer = AgentServer;
@@ -2,15 +2,14 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.InMemoryHub = void 0;
4
4
  const rxjs_1 = require("rxjs");
5
- const operators_1 = require("rxjs/operators");
6
5
  class InMemoryHub {
7
6
  constructor() {
8
7
  this.peerEventsSubject = new rxjs_1.Subject();
9
8
  this.messagesSubject = new rxjs_1.Subject();
10
9
  this.peers = new Map();
11
10
  this.peerSubs = new Map();
12
- this.peerEvents$ = this.peerEventsSubject.asObservable().pipe((0, operators_1.share)());
13
- this.messages$ = this.messagesSubject.asObservable().pipe((0, operators_1.share)());
11
+ this.peerEvents$ = this.peerEventsSubject.asObservable();
12
+ this.messages$ = this.messagesSubject.asObservable();
14
13
  }
15
14
  addPeer(peerId, conn) {
16
15
  // defensive: if peer already exists, clean it first
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from './agent-app';
2
2
  export * from './agent-app-with-defaults';
3
+ export * from './agent-server';
3
4
  export * from './agent';
4
5
  export * from './constants';
5
6
  export * from './agent-protocol';
package/dist/index.js CHANGED
@@ -16,6 +16,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./agent-app"), exports);
18
18
  __exportStar(require("./agent-app-with-defaults"), exports);
19
+ __exportStar(require("./agent-server"), exports);
19
20
  __exportStar(require("./agent"), exports);
20
21
  __exportStar(require("./constants"), exports);
21
22
  __exportStar(require("./agent-protocol"), exports);
package/dist/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "type": "git",
5
5
  "url": "git+https://github.com/Linc-Security-Systems/aware-essentials.git"
6
6
  },
7
- "version": "2.0.75",
7
+ "version": "2.0.76",
8
8
  "description": "SDK for building Agent implementations that speak the agent protocol.",
9
9
  "author": "Yaser Awajan",
10
10
  "license": "MIT",
@@ -26,17 +26,17 @@
26
26
  "scripts": {
27
27
  "build": "tsc -p tsconfig.json && cp package.json dist/",
28
28
  "prepublishOnly": "yarn build",
29
- "test": "echo \"add unit tests here\"",
29
+ "test": "vitest run",
30
30
  "lint": "eslint \"src/**/*.{ts,tsx}\"",
31
31
  "lint:fix": "yarn lint --fix"
32
32
  },
33
33
  "peerDependencies": {
34
- "@awarevue/api-types": "2.0.75",
34
+ "@awarevue/api-types": "2.0.76",
35
35
  "rxjs": "^7.8.2",
36
36
  "ws": "^8"
37
37
  },
38
38
  "devDependencies": {
39
- "@awarevue/api-types": "2.0.75",
39
+ "@awarevue/api-types": "2.0.76",
40
40
  "@types/node": "^20.12.7",
41
41
  "@types/ws": "^8.18.1",
42
42
  "@typescript-eslint/eslint-plugin": "^8.31.1",
@@ -46,6 +46,7 @@
46
46
  "eslint-plugin-import": "^2.31.0",
47
47
  "rxjs": "7.8.2",
48
48
  "typescript": "^5.8.3",
49
+ "vitest": "^4.1.4",
49
50
  "ws": "^8",
50
51
  "zod": "3.25.76"
51
52
  },
@@ -1,2 +1,3 @@
1
1
  export * from './ws';
2
+ export * from './ws-json-encoder';
2
3
  export * from './logging';
@@ -15,4 +15,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./ws"), exports);
18
+ __exportStar(require("./ws-json-encoder"), exports);
18
19
  __exportStar(require("./logging"), exports);
@@ -0,0 +1,12 @@
1
+ import { Observable } from 'rxjs';
2
+ import { DuplexTransport } from '../transport_types';
3
+ import { FromAgent, FromServer, Message } from '@awarevue/api-types';
4
+ export declare class WsJsonEncoder implements DuplexTransport<Message<FromServer | FromAgent>, Message<FromAgent | FromServer>> {
5
+ private defaultDeserializer;
6
+ private defaultSerializer;
7
+ readonly connected$: Observable<boolean>;
8
+ readonly messages$: Observable<Message<FromServer | FromAgent>>;
9
+ readonly send: (msg: Message<FromAgent | FromServer>) => void;
10
+ readonly close: () => void;
11
+ constructor(decoratee: DuplexTransport<string, string>);
12
+ }
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WsJsonEncoder = void 0;
4
+ const rxjs_1 = require("rxjs");
5
+ class WsJsonEncoder {
6
+ constructor(decoratee) {
7
+ this.defaultDeserializer = (raw) => {
8
+ const wsShape = JSON.parse(raw);
9
+ return {
10
+ kind: wsShape.event,
11
+ ...wsShape.data,
12
+ };
13
+ };
14
+ this.defaultSerializer = (msg) => {
15
+ const { kind, ...data } = msg;
16
+ const wsShape = {
17
+ event: kind,
18
+ data,
19
+ };
20
+ return JSON.stringify(wsShape);
21
+ };
22
+ this.connected$ = decoratee.connected$;
23
+ this.messages$ = decoratee.messages$.pipe((0, rxjs_1.map)((raw) => this.defaultDeserializer(raw)), (0, rxjs_1.catchError)(() => rxjs_1.EMPTY));
24
+ this.send = (msg) => decoratee.send(this.defaultSerializer(msg));
25
+ this.close = () => decoratee.close();
26
+ }
27
+ }
28
+ exports.WsJsonEncoder = WsJsonEncoder;
@@ -8,16 +8,6 @@ export interface WsDuplexTransportOptions {
8
8
  * (e.g. Authorization, User-Agent).
9
9
  */
10
10
  headers?: Record<string, string>;
11
- /**
12
- * Serialise an outbound message to a string or Buffer.
13
- * Defaults to `JSON.stringify`.
14
- */
15
- serialise?: (msg: unknown) => string;
16
- /**
17
- * Deserialise an inbound raw payload to a message object.
18
- * Defaults to `JSON.parse`.
19
- */
20
- deserialise?: (raw: string) => unknown;
21
11
  /** Initial reconnect delay in ms (default 1 000). */
22
12
  reconnectDelay?: number;
23
13
  /** Maximum reconnect delay in ms (default 30 000). */
@@ -25,10 +15,10 @@ export interface WsDuplexTransportOptions {
25
15
  /** When false, no automatic reconnect is attempted (default true). */
26
16
  autoReconnect?: boolean;
27
17
  }
28
- export declare class WsDuplexTransport<TIn, TOut> implements DuplexTransport<TIn, TOut> {
18
+ export declare class WsDuplexTransport implements DuplexTransport<string, string> {
29
19
  private readonly opts;
30
20
  readonly connected$: Observable<boolean>;
31
- readonly messages$: Observable<TIn>;
21
+ readonly messages$: Observable<string>;
32
22
  readonly errors$: Observable<Error>;
33
23
  private readonly _connected$;
34
24
  private readonly _messages$;
@@ -40,12 +30,8 @@ export declare class WsDuplexTransport<TIn, TOut> implements DuplexTransport<TIn
40
30
  private readonly maxDelay;
41
31
  private readonly autoReconnect;
42
32
  private destroyed;
43
- private readonly serialise;
44
- private readonly deserialise;
45
- private defaultDeserializer;
46
- private defaultSerializer;
47
33
  constructor(opts: WsDuplexTransportOptions);
48
- send(msg: TOut): void;
34
+ send(msg: string): void;
49
35
  close(): void;
50
36
  /** Single subscription that drains the outbound queue when connected. */
51
37
  private setupSender;
@@ -14,7 +14,7 @@ const rxjs_1 = require("rxjs");
14
14
  /* ---------------------------------------------------------------- */
15
15
  class WsDuplexTransport {
16
16
  constructor(opts) {
17
- var _a, _b, _c, _d, _e;
17
+ var _a, _b, _c;
18
18
  this.opts = opts;
19
19
  // ---- internal subjects ----
20
20
  this._connected$ = new rxjs_1.BehaviorSubject(false);
@@ -23,28 +23,12 @@ class WsDuplexTransport {
23
23
  this.outbound$ = new rxjs_1.Subject();
24
24
  this.destroy$ = new rxjs_1.Subject();
25
25
  this.destroyed = false;
26
- this.defaultDeserializer = (raw) => {
27
- const env = JSON.parse(raw);
28
- return {
29
- kind: env.event,
30
- ...env.data,
31
- };
32
- };
33
- this.defaultSerializer = (msg) => {
34
- if (typeof msg === 'object' && 'kind' in (msg || {})) {
35
- const { kind, ...data } = msg;
36
- return JSON.stringify({ event: kind, data });
37
- }
38
- return JSON.stringify(msg);
39
- };
40
26
  this.connected$ = this._connected$.asObservable();
41
27
  this.messages$ = this._messages$.asObservable();
42
28
  this.errors$ = this._errors$.asObservable();
43
29
  this.reconnectDelay = (_a = opts.reconnectDelay) !== null && _a !== void 0 ? _a : 1000;
44
30
  this.maxDelay = (_b = opts.maxReconnectDelay) !== null && _b !== void 0 ? _b : 30000;
45
31
  this.autoReconnect = (_c = opts.autoReconnect) !== null && _c !== void 0 ? _c : true;
46
- this.serialise = ((_d = opts.serialise) !== null && _d !== void 0 ? _d : this.defaultSerializer);
47
- this.deserialise = ((_e = opts.deserialise) !== null && _e !== void 0 ? _e : this.defaultDeserializer);
48
32
  this.connect();
49
33
  this.setupSender();
50
34
  }
@@ -52,7 +36,7 @@ class WsDuplexTransport {
52
36
  /* Public API */
53
37
  /* -------------------------------------------------------------- */
54
38
  send(msg) {
55
- this.outbound$.next(this.serialise(msg));
39
+ this.outbound$.next(msg);
56
40
  }
57
41
  close() {
58
42
  var _a;
@@ -89,7 +73,7 @@ class WsDuplexTransport {
89
73
  });
90
74
  this.ws.on('message', (data) => {
91
75
  try {
92
- const msg = this.deserialise(data.toString());
76
+ const msg = data.toString();
93
77
  this._messages$.next(msg);
94
78
  }
95
79
  catch (err) {
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "type": "git",
5
5
  "url": "git+https://github.com/Linc-Security-Systems/aware-essentials.git"
6
6
  },
7
- "version": "2.0.75",
7
+ "version": "2.0.76",
8
8
  "description": "SDK for building Agent implementations that speak the agent protocol.",
9
9
  "author": "Yaser Awajan",
10
10
  "license": "MIT",
@@ -26,17 +26,17 @@
26
26
  "scripts": {
27
27
  "build": "tsc -p tsconfig.json && cp package.json dist/",
28
28
  "prepublishOnly": "yarn build",
29
- "test": "echo \"add unit tests here\"",
29
+ "test": "vitest run",
30
30
  "lint": "eslint \"src/**/*.{ts,tsx}\"",
31
31
  "lint:fix": "yarn lint --fix"
32
32
  },
33
33
  "peerDependencies": {
34
- "@awarevue/api-types": "2.0.75",
34
+ "@awarevue/api-types": "2.0.76",
35
35
  "rxjs": "^7.8.2",
36
36
  "ws": "^8"
37
37
  },
38
38
  "devDependencies": {
39
- "@awarevue/api-types": "2.0.75",
39
+ "@awarevue/api-types": "2.0.76",
40
40
  "@types/node": "^20.12.7",
41
41
  "@types/ws": "^8.18.1",
42
42
  "@typescript-eslint/eslint-plugin": "^8.31.1",
@@ -46,6 +46,7 @@
46
46
  "eslint-plugin-import": "^2.31.0",
47
47
  "rxjs": "7.8.2",
48
48
  "typescript": "^5.8.3",
49
+ "vitest": "^4.1.4",
49
50
  "ws": "^8",
50
51
  "zod": "3.25.76"
51
52
  },