@awarevue/agent-sdk 1.0.1

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,25 @@
1
+ import { Transport } from './transport';
2
+ import { ProviderSpecs, PushEventRq, PushStateUpdateRq } from '@awarevue/api-types';
3
+ import { Agent } from './agent';
4
+ export type DeviceActivity = Omit<PushStateUpdateRq, 'provider'> | Omit<PushEventRq, 'provider'>;
5
+ export type AgentOptions = {
6
+ version: number;
7
+ providers: Record<string, ProviderSpecs>;
8
+ agentId: string;
9
+ replyTimeout?: number;
10
+ transport: Transport;
11
+ };
12
+ export declare class AgentApp {
13
+ private readonly agent;
14
+ private readonly options;
15
+ private static id;
16
+ private static nextId;
17
+ private sub;
18
+ private getReply$;
19
+ private addEnvelope;
20
+ private runProvider$;
21
+ private process$;
22
+ constructor(agent: Agent, options: AgentOptions);
23
+ start(): void;
24
+ stop(): void;
25
+ }
@@ -0,0 +1,143 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AgentApp = void 0;
4
+ const rxjs_1 = require("rxjs");
5
+ class AgentApp {
6
+ constructor(agent, options) {
7
+ this.agent = agent;
8
+ this.options = options;
9
+ this.sub = null;
10
+ // getReply$ is a generic function that sends a message to server and waits for a reply.
11
+ this.getReply$ = (responseKind, payload) => {
12
+ const reply$ = (id) => this.options.transport.messages$.pipe((0, rxjs_1.mergeMap)((message) => {
13
+ if (message.kind === 'error-rs' && message.requestId === id) {
14
+ const error = message.error;
15
+ return (0, rxjs_1.throwError)(() => new Error(`Server failed to process message ${message.kind}: ${error}`));
16
+ }
17
+ return (0, rxjs_1.of)(message);
18
+ }), (0, rxjs_1.filter)((message) => message.kind === responseKind &&
19
+ 'requestId' in message &&
20
+ message.requestId === id), (0, rxjs_1.take)(1), (0, rxjs_1.timeout)(this.options.replyTimeout || 10000));
21
+ return (0, rxjs_1.of)(this.addEnvelope({ ...payload, id: AgentApp.nextId() })).pipe(
22
+ // send the message to the agent
23
+ (0, rxjs_1.tap)((p) => this.options.transport.send(p)),
24
+ // wait for the agent to reply
25
+ (0, rxjs_1.mergeMap)(({ id }) => reply$(id)));
26
+ };
27
+ this.addEnvelope = (payload) => ({
28
+ ...payload,
29
+ id: AgentApp.nextId(),
30
+ from: this.options.agentId,
31
+ version: this.options.version,
32
+ on: Date.now(),
33
+ });
34
+ this.runProvider$ = (context) => {
35
+ return (0, rxjs_1.merge)(
36
+ // run the agent
37
+ this.agent
38
+ .run$(context)
39
+ .pipe((0, rxjs_1.tap)((message) => this.options.transport.send(this.addEnvelope({ ...message, provider: context.provider })))), this.options.transport.messages$.pipe((0, rxjs_1.mergeMap)((message) => {
40
+ switch (message.kind) {
41
+ // handle commands
42
+ case 'command':
43
+ return this.agent.runCommand$(context, message).pipe(
44
+ // success
45
+ (0, rxjs_1.map)(() => ({
46
+ kind: 'command-rs',
47
+ requestId: message.id,
48
+ })),
49
+ // error
50
+ (0, rxjs_1.catchError)((error) => {
51
+ var _a;
52
+ return (0, rxjs_1.of)({
53
+ kind: 'error-rs',
54
+ requestId: message.id,
55
+ error: (_a = error.message) !== null && _a !== void 0 ? _a : 'Unknown error',
56
+ });
57
+ }),
58
+ // send the response
59
+ (0, rxjs_1.tap)((rs) => this.options.transport.send(this.addEnvelope(rs))));
60
+ case 'get-available-devices':
61
+ // get available devices
62
+ return this.agent.getDevicesAndRelations$(context).pipe(
63
+ // success
64
+ (0, rxjs_1.map)((rs) => ({
65
+ kind: 'get-available-devices-rs',
66
+ ...rs,
67
+ requestId: message.id,
68
+ })),
69
+ // error
70
+ (0, rxjs_1.catchError)((error) => {
71
+ var _a;
72
+ return (0, rxjs_1.of)({
73
+ kind: 'error-rs',
74
+ requestId: message.id,
75
+ error: (_a = error.message) !== null && _a !== void 0 ? _a : 'Unknown error',
76
+ });
77
+ }), (0, rxjs_1.tap)((rs) => this.options.transport.send(this.addEnvelope(rs))));
78
+ default:
79
+ return rxjs_1.EMPTY;
80
+ }
81
+ })));
82
+ };
83
+ this.process$ = () => {
84
+ const registration$ = this.options.transport.connected$.pipe((0, rxjs_1.switchMap)((connected) => connected
85
+ ? this.getReply$('register-rs', {
86
+ kind: 'register',
87
+ providers: this.options.providers,
88
+ }).pipe((0, rxjs_1.retry)({ delay: 3000 }))
89
+ : rxjs_1.EMPTY));
90
+ const startStop$ = this.options.transport.messages$.pipe((0, rxjs_1.filter)((message) => message.kind === 'start' || message.kind === 'stop'), (0, rxjs_1.switchMap)((message) => message.kind === 'start'
91
+ ? (0, rxjs_1.merge)((0, rxjs_1.of)({
92
+ provider: message.provider,
93
+ config: message.config,
94
+ lastEventForeignRef: message.lastEventForeignRef,
95
+ lastEventTimestamp: message.lastEventTimestamp,
96
+ }).pipe((0, rxjs_1.mergeMap)((context) => this.runProvider$(context))), (0, rxjs_1.of)({
97
+ kind: 'start-rs',
98
+ requestId: message.id,
99
+ }).pipe((0, rxjs_1.tap)((reply) => this.options.transport.send(this.addEnvelope(reply)))))
100
+ : rxjs_1.EMPTY));
101
+ const validateConfig$ = this.options.transport.messages$.pipe((0, rxjs_1.filter)((message) => message.kind === 'validate-config'), (0, rxjs_1.mergeMap)((message) => {
102
+ const provider = message.provider;
103
+ const config = message.config;
104
+ return this.agent
105
+ .getConfigIssues$({
106
+ provider,
107
+ config,
108
+ })
109
+ .pipe(
110
+ // success
111
+ (0, rxjs_1.map)((issues) => ({
112
+ kind: 'validate-config-rs',
113
+ requestId: message.id,
114
+ issues,
115
+ })),
116
+ // error
117
+ (0, rxjs_1.catchError)((error) => {
118
+ var _a;
119
+ return (0, rxjs_1.of)({
120
+ kind: 'error-rs',
121
+ requestId: message.id,
122
+ error: (_a = error.message) !== null && _a !== void 0 ? _a : 'Unknown error',
123
+ });
124
+ }),
125
+ // send the response
126
+ (0, rxjs_1.tap)((rs) => this.options.transport.send(this.addEnvelope(rs))));
127
+ }));
128
+ return (0, rxjs_1.merge)(registration$, startStop$, validateConfig$);
129
+ };
130
+ }
131
+ start() {
132
+ this.sub = this.process$().subscribe();
133
+ }
134
+ stop() {
135
+ var _a;
136
+ (_a = this.sub) === null || _a === void 0 ? void 0 : _a.unsubscribe();
137
+ this.sub = null;
138
+ this.options.transport.close();
139
+ }
140
+ }
141
+ exports.AgentApp = AgentApp;
142
+ AgentApp.id = 0;
143
+ AgentApp.nextId = () => `${++AgentApp.id}`;
@@ -0,0 +1,17 @@
1
+ import { ValidateProviderConfigRs, DeviceDiscoveryDto, RunCommandRq } from '@awarevue/api-types';
2
+ import { Observable } from 'rxjs';
3
+ import { DeviceActivity } from './agent-app';
4
+ export type Context = {
5
+ provider: string;
6
+ config: Record<string, unknown>;
7
+ };
8
+ export type RunContext = Context & {
9
+ lastEventForeignRef: string | null;
10
+ lastEventTimestamp: number | null;
11
+ };
12
+ export interface Agent {
13
+ getConfigIssues$: (context: Context) => Observable<ValidateProviderConfigRs['issues']>;
14
+ getDevicesAndRelations$: (context: Context) => Observable<DeviceDiscoveryDto>;
15
+ run$: (context: RunContext) => Observable<DeviceActivity>;
16
+ runCommand$: (context: Context, command: RunCommandRq) => Observable<unknown>;
17
+ }
package/dist/agent.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,5 @@
1
+ export * from './agent-app';
2
+ export * from './agent';
3
+ export * from './ws-agent-transport';
4
+ export * from './transport-logger-decorator';
5
+ export * from './transport';
package/dist/index.js ADDED
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./agent-app"), exports);
18
+ __exportStar(require("./agent"), exports);
19
+ __exportStar(require("./ws-agent-transport"), exports);
20
+ __exportStar(require("./transport-logger-decorator"), exports);
21
+ __exportStar(require("./transport"), exports);
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@awarevue/agent-sdk",
3
+ "version": "1.0.1",
4
+ "description": "SDK for building Agent implementations that speak the Aware protocol.",
5
+ "author": "Yaser Awajan",
6
+ "license": "MIT",
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "main": "dist/index.js",
11
+ "types": "dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "import": "./dist/index.js",
15
+ "require": "./dist/index.js",
16
+ "types": "./dist/index.d.ts"
17
+ }
18
+ },
19
+ "engines": {
20
+ "node": ">=18.0.0"
21
+ },
22
+ "scripts": {
23
+ "build": "tsc -p tsconfig.json && cp package.json dist/",
24
+ "prepublishOnly": "yarn build",
25
+ "test": "echo \"add unit tests here\"",
26
+ "lint": "eslint \"src/**/*.{ts,tsx}\"",
27
+ "lint:fix": "yarn lint --fix"
28
+ },
29
+ "dependencies": {
30
+ "@awarevue/api-types": "^1.0.0",
31
+ "rxjs": "^7.8.2",
32
+ "ws": "^8",
33
+ "zod": "3.24.2"
34
+ },
35
+ "devDependencies": {
36
+ "@types/node": "^20.12.7",
37
+ "@typescript-eslint/eslint-plugin": "^8.31.1",
38
+ "@typescript-eslint/parser": "^8.31.1",
39
+ "eslint": "^9.25.1",
40
+ "eslint-config-prettier": "^10.1.2",
41
+ "eslint-plugin-import": "^2.31.0",
42
+ "typescript": "^5.8.3"
43
+ },
44
+ "publishConfig": {
45
+ "access": "public",
46
+ "registry": "https://registry.npmjs.org/"
47
+ }
48
+ }
@@ -0,0 +1,11 @@
1
+ import { Message, FromAgent } from '@awarevue/api-types';
2
+ import { Transport } from './transport';
3
+ export declare class TransportWithLogs implements Transport {
4
+ private readonly decoratee;
5
+ constructor(decoratee: Transport);
6
+ get connected$(): import("rxjs").Observable<boolean>;
7
+ get messages$(): import("rxjs").Observable<Message<import("@awarevue/api-types").FromServer>>;
8
+ get errors$(): import("rxjs").Observable<Error>;
9
+ close(): void;
10
+ send(msg: Message<FromAgent>): void;
11
+ }
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TransportWithLogs = void 0;
4
+ class TransportWithLogs {
5
+ constructor(decoratee) {
6
+ this.decoratee = decoratee;
7
+ this.decoratee.connected$.subscribe((connected) => {
8
+ console.log(`[${new Date()}] - Transport connected: ${connected}`);
9
+ });
10
+ this.decoratee.messages$.subscribe((msg) => {
11
+ console.log(`[${new Date()}] - Transport message: ${JSON.stringify(msg)}`);
12
+ });
13
+ this.decoratee.errors$.subscribe((err) => {
14
+ console.error(`[${new Date()}] - Transport error: ${err}`);
15
+ });
16
+ }
17
+ get connected$() {
18
+ return this.decoratee.connected$;
19
+ }
20
+ get messages$() {
21
+ return this.decoratee.messages$;
22
+ }
23
+ get errors$() {
24
+ return this.decoratee.errors$;
25
+ }
26
+ close() {
27
+ console.log(`Closing transport`);
28
+ this.decoratee.close();
29
+ }
30
+ send(msg) {
31
+ console.log(`[${new Date()}] - Sending message: ${JSON.stringify(msg)}`);
32
+ this.decoratee.send(msg);
33
+ }
34
+ }
35
+ exports.TransportWithLogs = TransportWithLogs;
@@ -0,0 +1,10 @@
1
+ import { FromAgent, FromServer, Message } from '@awarevue/api-types';
2
+ import { Observable } from 'rxjs';
3
+ /** What the SDK expects from a transport */
4
+ export interface Transport {
5
+ readonly connected$: Observable<boolean>;
6
+ readonly messages$: Observable<Message<FromServer>>;
7
+ readonly errors$: Observable<Error>;
8
+ send(msg: Message<FromAgent>): void;
9
+ close(): void;
10
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,28 @@
1
+ import { Observable } from 'rxjs';
2
+ import { FromAgent, FromServer, Message } from '@awarevue/api-types';
3
+ import { Transport } from './transport';
4
+ export declare class WsAgentTransport implements Transport {
5
+ private readonly url;
6
+ readonly connected$: Observable<boolean>;
7
+ readonly messages$: Observable<Message<FromServer>>;
8
+ readonly errors$: Observable<Error>;
9
+ private ws;
10
+ private readonly _connected$;
11
+ private readonly _messages$;
12
+ private readonly _errors$;
13
+ private readonly outbound$;
14
+ private readonly destroy$;
15
+ private reconnectDelay;
16
+ private readonly maxDelay;
17
+ private destroyed;
18
+ constructor(url: string);
19
+ send(msg: Message<FromAgent>): void;
20
+ /** Call once when your container/module shuts down. */
21
+ close(): void;
22
+ /** One subscription for all outbound traffic. */
23
+ private setupSender;
24
+ /** Establish or re-establish a WebSocket connection. */
25
+ private connect;
26
+ /** Exponential back-off reconnect, cancelled via destroy$. */
27
+ private scheduleReconnect;
28
+ }
@@ -0,0 +1,100 @@
1
+ "use strict";
2
+ // ws-agent-transport.ts
3
+ //--------------------------------------------------------------
4
+ // A robust, RxJS-friendly WebSocket transport for the Aware SDK
5
+ // • automatic exponential back-off reconnect
6
+ // • single outbound queue, no per-call subscriptions
7
+ // • clean shutdown (no leaks)
8
+ //--------------------------------------------------------------
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.WsAgentTransport = void 0;
11
+ const ws_1 = require("ws");
12
+ const rxjs_1 = require("rxjs");
13
+ class WsAgentTransport {
14
+ constructor(url) {
15
+ this.url = url;
16
+ this._connected$ = new rxjs_1.BehaviorSubject(false);
17
+ this._messages$ = new rxjs_1.Subject();
18
+ this._errors$ = new rxjs_1.Subject();
19
+ this.outbound$ = new rxjs_1.Subject();
20
+ this.destroy$ = new rxjs_1.Subject();
21
+ this.reconnectDelay = 1000; // start at 1 s
22
+ this.maxDelay = 30000; // cap at 30 s
23
+ this.destroyed = false;
24
+ this.connected$ = this._connected$.asObservable();
25
+ this.messages$ = this._messages$.asObservable();
26
+ this.errors$ = this._errors$.asObservable();
27
+ this.connect(); // first connection
28
+ this.setupSender(); // single outbound subscription
29
+ }
30
+ //----------------------------------------
31
+ // Public API
32
+ //----------------------------------------
33
+ send(msg) {
34
+ const { kind, ...data } = msg;
35
+ const payload = JSON.stringify({ event: kind, data });
36
+ this.outbound$.next(payload);
37
+ }
38
+ /** Call once when your container/module shuts down. */
39
+ close() {
40
+ var _a;
41
+ if (this.destroyed)
42
+ return;
43
+ this.destroyed = true;
44
+ this.destroy$.next();
45
+ this.destroy$.complete();
46
+ this._connected$.complete();
47
+ this._messages$.complete();
48
+ this._errors$.complete();
49
+ /** terminate() is immediate; skip the close handshake intentionally */
50
+ (_a = this.ws) === null || _a === void 0 ? void 0 : _a.terminate();
51
+ }
52
+ //----------------------------------------
53
+ // Private helpers
54
+ //----------------------------------------
55
+ /** One subscription for all outbound traffic. */
56
+ setupSender() {
57
+ this.outbound$
58
+ .pipe((0, rxjs_1.concatMap)((payload) => this._connected$.pipe((0, rxjs_1.filter)(Boolean), // wait until we’re actually connected
59
+ (0, rxjs_1.take)(1), // just the next open
60
+ (0, rxjs_1.takeUntil)(this.destroy$), (0, rxjs_1.map)(() => payload))), (0, rxjs_1.takeUntil)(this.destroy$))
61
+ .subscribe((payload) => {
62
+ /* readyState **must** be OPEN here by construction */
63
+ this.ws.send(payload);
64
+ });
65
+ }
66
+ /** Establish or re-establish a WebSocket connection. */
67
+ connect() {
68
+ this.ws = new ws_1.WebSocket(this.url, {
69
+ headers: { 'User-Agent': 'Aware Agent SDK' },
70
+ });
71
+ this.ws.on('open', () => {
72
+ this.reconnectDelay = 1000; // reset back-off
73
+ this._connected$.next(true);
74
+ });
75
+ this.ws.on('message', (data) => {
76
+ const raw = JSON.parse(data.toString());
77
+ this._messages$.next({
78
+ kind: raw.event,
79
+ ...raw.data,
80
+ });
81
+ });
82
+ this.ws.on('error', (err) => {
83
+ this._errors$.next(err);
84
+ // the 'close' handler will schedule reconnect
85
+ });
86
+ this.ws.on('close', () => {
87
+ this._connected$.next(false);
88
+ if (!this.destroyed)
89
+ this.scheduleReconnect();
90
+ });
91
+ }
92
+ /** Exponential back-off reconnect, cancelled via destroy$. */
93
+ scheduleReconnect() {
94
+ (0, rxjs_1.timer)(this.reconnectDelay)
95
+ .pipe((0, rxjs_1.takeUntil)(this.destroy$))
96
+ .subscribe(() => this.connect());
97
+ this.reconnectDelay = Math.min(this.reconnectDelay * 2, this.maxDelay);
98
+ }
99
+ }
100
+ exports.WsAgentTransport = WsAgentTransport;
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@awarevue/agent-sdk",
3
+ "version": "1.0.1",
4
+ "description": "SDK for building Agent implementations that speak the Aware protocol.",
5
+ "author": "Yaser Awajan",
6
+ "license": "MIT",
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "main": "dist/index.js",
11
+ "types": "dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "import": "./dist/index.js",
15
+ "require": "./dist/index.js",
16
+ "types": "./dist/index.d.ts"
17
+ }
18
+ },
19
+ "engines": {
20
+ "node": ">=18.0.0"
21
+ },
22
+ "scripts": {
23
+ "build": "tsc -p tsconfig.json && cp package.json dist/",
24
+ "prepublishOnly": "yarn build",
25
+ "test": "echo \"add unit tests here\"",
26
+ "lint": "eslint \"src/**/*.{ts,tsx}\"",
27
+ "lint:fix": "yarn lint --fix"
28
+ },
29
+ "dependencies": {
30
+ "@awarevue/api-types": "^1.0.0",
31
+ "rxjs": "^7.8.2",
32
+ "ws": "^8",
33
+ "zod": "3.24.2"
34
+ },
35
+ "devDependencies": {
36
+ "@types/node": "^20.12.7",
37
+ "@typescript-eslint/eslint-plugin": "^8.31.1",
38
+ "@typescript-eslint/parser": "^8.31.1",
39
+ "eslint": "^9.25.1",
40
+ "eslint-config-prettier": "^10.1.2",
41
+ "eslint-plugin-import": "^2.31.0",
42
+ "typescript": "^5.8.3"
43
+ },
44
+ "publishConfig": {
45
+ "access": "public",
46
+ "registry": "https://registry.npmjs.org/"
47
+ }
48
+ }