@elelem/answer-engine-client 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,265 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ AnswerEngineClient: () => AnswerEngineClient
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+ var import_socket = require("socket.io-client");
27
+ var AnswerEngineClient = class {
28
+ constructor(config) {
29
+ this.config = config;
30
+ this.socket = null;
31
+ this.lastReceivedAt = 0;
32
+ this.status = "disconnected";
33
+ this.activeMessageID = null;
34
+ this.launchMessageID = null;
35
+ this.actionQueue = [];
36
+ this.isProcessingQueue = false;
37
+ // Callback stores
38
+ this.tokenCallbacks = [];
39
+ this.recommendationCallbacks = [];
40
+ this.statusCallbacks = [];
41
+ this.errorCallbacks = [];
42
+ if (!config.projectID) {
43
+ throw new Error("projectID is required");
44
+ }
45
+ if (!config.environmentID) {
46
+ throw new Error("environmentID is required");
47
+ }
48
+ this.userID = config.userID || "ae-sess-" + Date.now() + "-" + Math.random().toString(36).slice(2);
49
+ }
50
+ /**
51
+ * Initialize the Socket.IO connection and handshakes.
52
+ */
53
+ connect() {
54
+ if (this.socket) {
55
+ return Promise.resolve();
56
+ }
57
+ const socketUrl = this.config.socketUrl || "wss://general-runtime.answer-engine.elelem.ai";
58
+ const socketPath = this.config.socketPath || "/v3/interact/socket/";
59
+ this.updateStatus("connecting");
60
+ this.socket = (0, import_socket.io)(socketUrl, {
61
+ path: socketPath,
62
+ transports: ["websocket"],
63
+ reconnection: true
64
+ });
65
+ return new Promise((resolve, reject) => {
66
+ let isInit = true;
67
+ this.socket.on("connect", () => {
68
+ this.updateStatus("connected");
69
+ this.socket.emit("start", {
70
+ userID: this.userID,
71
+ projectID: this.config.projectID,
72
+ environmentID: this.config.environmentID,
73
+ lastReceivedAt: this.lastReceivedAt,
74
+ config: {
75
+ completionEvents: true,
76
+ audioEvents: false,
77
+ audioEncoding: "audio/pcm",
78
+ modality: "chat",
79
+ guidedNavigation: false,
80
+ userTimezone: this.config.userTimezone || Intl.DateTimeFormat().resolvedOptions().timeZone,
81
+ endAutomatically: false,
82
+ isTestSession: false
83
+ }
84
+ });
85
+ });
86
+ this.socket.on("start", (data) => {
87
+ this.updateStatus("ready");
88
+ if (isInit) {
89
+ isInit = false;
90
+ resolve();
91
+ }
92
+ });
93
+ this.socket.on("connect_error", (error) => {
94
+ this.handleError(error);
95
+ if (isInit) {
96
+ isInit = false;
97
+ reject(error);
98
+ }
99
+ });
100
+ this.socket.on("disconnect", (reason) => {
101
+ this.updateStatus("disconnected");
102
+ this.activeMessageID = null;
103
+ this.launchMessageID = null;
104
+ });
105
+ this.socket.on("action", (data) => {
106
+ if (data.status === "accepted") {
107
+ this.activeMessageID = data.messageID;
108
+ } else if (data.status === "completed" && data.messageID === this.activeMessageID) {
109
+ this.activeMessageID = null;
110
+ this.processNextInQueue();
111
+ }
112
+ });
113
+ this.socket.on("trace", (data) => {
114
+ const { trace } = data;
115
+ if (trace.time) {
116
+ this.lastReceivedAt = trace.time;
117
+ }
118
+ if (trace.type === "completion") {
119
+ if (trace.payload.state === "content") {
120
+ this.triggerToken(trace.payload.content);
121
+ }
122
+ } else if (trace.type === "Emit recommendations") {
123
+ this.triggerRecommendation({
124
+ doc_title: trace.payload.doc_title,
125
+ chunks: trace.payload.chunks,
126
+ url: trace.payload.url,
127
+ doc_title2: trace.payload.doc_title2,
128
+ chunks2: trace.payload.chunks2,
129
+ url2: trace.payload.url2
130
+ });
131
+ }
132
+ });
133
+ });
134
+ }
135
+ /**
136
+ * Terminate the connection.
137
+ */
138
+ disconnect() {
139
+ if (this.socket) {
140
+ this.socket.disconnect();
141
+ this.socket = null;
142
+ }
143
+ this.updateStatus("disconnected");
144
+ this.actionQueue = [];
145
+ this.isProcessingQueue = false;
146
+ }
147
+ /**
148
+ * Launch/initialize the conversational flow.
149
+ */
150
+ launch() {
151
+ return this.queueAction("launch");
152
+ }
153
+ /**
154
+ * Send a text message turn.
155
+ */
156
+ sendMessage(text) {
157
+ return this.queueAction("text", text);
158
+ }
159
+ /**
160
+ * Trigger a named flow intent event turn.
161
+ */
162
+ sendIntent(intentName, data = {}) {
163
+ return this.queueAction("intent", { name: intentName, data });
164
+ }
165
+ // --- Subscriptions ---
166
+ onToken(callback) {
167
+ this.tokenCallbacks.push(callback);
168
+ return () => {
169
+ this.tokenCallbacks = this.tokenCallbacks.filter((cb) => cb !== callback);
170
+ };
171
+ }
172
+ onRecommendations(callback) {
173
+ this.recommendationCallbacks.push(callback);
174
+ return () => {
175
+ this.recommendationCallbacks = this.recommendationCallbacks.filter((cb) => cb !== callback);
176
+ };
177
+ }
178
+ onStatusChange(callback) {
179
+ this.statusCallbacks.push(callback);
180
+ callback(this.status);
181
+ return () => {
182
+ this.statusCallbacks = this.statusCallbacks.filter((cb) => cb !== callback);
183
+ };
184
+ }
185
+ onError(callback) {
186
+ this.errorCallbacks.push(callback);
187
+ return () => {
188
+ this.errorCallbacks = this.errorCallbacks.filter((cb) => cb !== callback);
189
+ };
190
+ }
191
+ // --- Internals ---
192
+ queueAction(type, payload) {
193
+ return new Promise((resolve, reject) => {
194
+ this.actionQueue.push({ type, payload, resolve, reject });
195
+ this.processNextInQueue();
196
+ });
197
+ }
198
+ processNextInQueue() {
199
+ if (this.isProcessingQueue) {
200
+ return;
201
+ }
202
+ if (this.actionQueue.length === 0) {
203
+ return;
204
+ }
205
+ if (this.status !== "ready" || this.activeMessageID !== null) {
206
+ return;
207
+ }
208
+ this.isProcessingQueue = true;
209
+ const item = this.actionQueue.shift();
210
+ try {
211
+ if (!this.socket) {
212
+ throw new Error("Client is not connected. Call .connect() first.");
213
+ }
214
+ let payloadBlock = {};
215
+ if (item.type === "launch") {
216
+ payloadBlock = { type: "launch" };
217
+ } else if (item.type === "text") {
218
+ payloadBlock = { type: "text", payload: item.payload };
219
+ } else if (item.type === "intent") {
220
+ payloadBlock = {
221
+ type: "intent",
222
+ payload: {
223
+ intent: { name: item.payload.name },
224
+ data: item.payload.data,
225
+ label: `Intent: ${item.payload.name}`,
226
+ query: `Intent: ${item.payload.name}`
227
+ }
228
+ };
229
+ }
230
+ this.socket.emit("action", {
231
+ action: payloadBlock,
232
+ config: {
233
+ stateful: false,
234
+ audioEvents: false,
235
+ guidedNavigation: false
236
+ }
237
+ });
238
+ item.resolve();
239
+ } catch (err) {
240
+ item.reject(err);
241
+ this.handleError(err);
242
+ } finally {
243
+ this.isProcessingQueue = false;
244
+ setTimeout(() => this.processNextInQueue(), 0);
245
+ }
246
+ }
247
+ updateStatus(newStatus) {
248
+ this.status = newStatus;
249
+ this.statusCallbacks.forEach((cb) => cb(newStatus));
250
+ }
251
+ handleError(err) {
252
+ const errorObj = err instanceof Error ? err : new Error(String(err));
253
+ this.errorCallbacks.forEach((cb) => cb(errorObj));
254
+ }
255
+ triggerToken(token) {
256
+ this.tokenCallbacks.forEach((cb) => cb(token));
257
+ }
258
+ triggerRecommendation(rec) {
259
+ this.recommendationCallbacks.forEach((cb) => cb(rec));
260
+ }
261
+ };
262
+ // Annotate the CommonJS export names for ESM import in node:
263
+ 0 && (module.exports = {
264
+ AnswerEngineClient
265
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,240 @@
1
+ // src/index.ts
2
+ import { io } from "socket.io-client";
3
+ var AnswerEngineClient = class {
4
+ constructor(config) {
5
+ this.config = config;
6
+ this.socket = null;
7
+ this.lastReceivedAt = 0;
8
+ this.status = "disconnected";
9
+ this.activeMessageID = null;
10
+ this.launchMessageID = null;
11
+ this.actionQueue = [];
12
+ this.isProcessingQueue = false;
13
+ // Callback stores
14
+ this.tokenCallbacks = [];
15
+ this.recommendationCallbacks = [];
16
+ this.statusCallbacks = [];
17
+ this.errorCallbacks = [];
18
+ if (!config.projectID) {
19
+ throw new Error("projectID is required");
20
+ }
21
+ if (!config.environmentID) {
22
+ throw new Error("environmentID is required");
23
+ }
24
+ this.userID = config.userID || "ae-sess-" + Date.now() + "-" + Math.random().toString(36).slice(2);
25
+ }
26
+ /**
27
+ * Initialize the Socket.IO connection and handshakes.
28
+ */
29
+ connect() {
30
+ if (this.socket) {
31
+ return Promise.resolve();
32
+ }
33
+ const socketUrl = this.config.socketUrl || "wss://general-runtime.answer-engine.elelem.ai";
34
+ const socketPath = this.config.socketPath || "/v3/interact/socket/";
35
+ this.updateStatus("connecting");
36
+ this.socket = io(socketUrl, {
37
+ path: socketPath,
38
+ transports: ["websocket"],
39
+ reconnection: true
40
+ });
41
+ return new Promise((resolve, reject) => {
42
+ let isInit = true;
43
+ this.socket.on("connect", () => {
44
+ this.updateStatus("connected");
45
+ this.socket.emit("start", {
46
+ userID: this.userID,
47
+ projectID: this.config.projectID,
48
+ environmentID: this.config.environmentID,
49
+ lastReceivedAt: this.lastReceivedAt,
50
+ config: {
51
+ completionEvents: true,
52
+ audioEvents: false,
53
+ audioEncoding: "audio/pcm",
54
+ modality: "chat",
55
+ guidedNavigation: false,
56
+ userTimezone: this.config.userTimezone || Intl.DateTimeFormat().resolvedOptions().timeZone,
57
+ endAutomatically: false,
58
+ isTestSession: false
59
+ }
60
+ });
61
+ });
62
+ this.socket.on("start", (data) => {
63
+ this.updateStatus("ready");
64
+ if (isInit) {
65
+ isInit = false;
66
+ resolve();
67
+ }
68
+ });
69
+ this.socket.on("connect_error", (error) => {
70
+ this.handleError(error);
71
+ if (isInit) {
72
+ isInit = false;
73
+ reject(error);
74
+ }
75
+ });
76
+ this.socket.on("disconnect", (reason) => {
77
+ this.updateStatus("disconnected");
78
+ this.activeMessageID = null;
79
+ this.launchMessageID = null;
80
+ });
81
+ this.socket.on("action", (data) => {
82
+ if (data.status === "accepted") {
83
+ this.activeMessageID = data.messageID;
84
+ } else if (data.status === "completed" && data.messageID === this.activeMessageID) {
85
+ this.activeMessageID = null;
86
+ this.processNextInQueue();
87
+ }
88
+ });
89
+ this.socket.on("trace", (data) => {
90
+ const { trace } = data;
91
+ if (trace.time) {
92
+ this.lastReceivedAt = trace.time;
93
+ }
94
+ if (trace.type === "completion") {
95
+ if (trace.payload.state === "content") {
96
+ this.triggerToken(trace.payload.content);
97
+ }
98
+ } else if (trace.type === "Emit recommendations") {
99
+ this.triggerRecommendation({
100
+ doc_title: trace.payload.doc_title,
101
+ chunks: trace.payload.chunks,
102
+ url: trace.payload.url,
103
+ doc_title2: trace.payload.doc_title2,
104
+ chunks2: trace.payload.chunks2,
105
+ url2: trace.payload.url2
106
+ });
107
+ }
108
+ });
109
+ });
110
+ }
111
+ /**
112
+ * Terminate the connection.
113
+ */
114
+ disconnect() {
115
+ if (this.socket) {
116
+ this.socket.disconnect();
117
+ this.socket = null;
118
+ }
119
+ this.updateStatus("disconnected");
120
+ this.actionQueue = [];
121
+ this.isProcessingQueue = false;
122
+ }
123
+ /**
124
+ * Launch/initialize the conversational flow.
125
+ */
126
+ launch() {
127
+ return this.queueAction("launch");
128
+ }
129
+ /**
130
+ * Send a text message turn.
131
+ */
132
+ sendMessage(text) {
133
+ return this.queueAction("text", text);
134
+ }
135
+ /**
136
+ * Trigger a named flow intent event turn.
137
+ */
138
+ sendIntent(intentName, data = {}) {
139
+ return this.queueAction("intent", { name: intentName, data });
140
+ }
141
+ // --- Subscriptions ---
142
+ onToken(callback) {
143
+ this.tokenCallbacks.push(callback);
144
+ return () => {
145
+ this.tokenCallbacks = this.tokenCallbacks.filter((cb) => cb !== callback);
146
+ };
147
+ }
148
+ onRecommendations(callback) {
149
+ this.recommendationCallbacks.push(callback);
150
+ return () => {
151
+ this.recommendationCallbacks = this.recommendationCallbacks.filter((cb) => cb !== callback);
152
+ };
153
+ }
154
+ onStatusChange(callback) {
155
+ this.statusCallbacks.push(callback);
156
+ callback(this.status);
157
+ return () => {
158
+ this.statusCallbacks = this.statusCallbacks.filter((cb) => cb !== callback);
159
+ };
160
+ }
161
+ onError(callback) {
162
+ this.errorCallbacks.push(callback);
163
+ return () => {
164
+ this.errorCallbacks = this.errorCallbacks.filter((cb) => cb !== callback);
165
+ };
166
+ }
167
+ // --- Internals ---
168
+ queueAction(type, payload) {
169
+ return new Promise((resolve, reject) => {
170
+ this.actionQueue.push({ type, payload, resolve, reject });
171
+ this.processNextInQueue();
172
+ });
173
+ }
174
+ processNextInQueue() {
175
+ if (this.isProcessingQueue) {
176
+ return;
177
+ }
178
+ if (this.actionQueue.length === 0) {
179
+ return;
180
+ }
181
+ if (this.status !== "ready" || this.activeMessageID !== null) {
182
+ return;
183
+ }
184
+ this.isProcessingQueue = true;
185
+ const item = this.actionQueue.shift();
186
+ try {
187
+ if (!this.socket) {
188
+ throw new Error("Client is not connected. Call .connect() first.");
189
+ }
190
+ let payloadBlock = {};
191
+ if (item.type === "launch") {
192
+ payloadBlock = { type: "launch" };
193
+ } else if (item.type === "text") {
194
+ payloadBlock = { type: "text", payload: item.payload };
195
+ } else if (item.type === "intent") {
196
+ payloadBlock = {
197
+ type: "intent",
198
+ payload: {
199
+ intent: { name: item.payload.name },
200
+ data: item.payload.data,
201
+ label: `Intent: ${item.payload.name}`,
202
+ query: `Intent: ${item.payload.name}`
203
+ }
204
+ };
205
+ }
206
+ this.socket.emit("action", {
207
+ action: payloadBlock,
208
+ config: {
209
+ stateful: false,
210
+ audioEvents: false,
211
+ guidedNavigation: false
212
+ }
213
+ });
214
+ item.resolve();
215
+ } catch (err) {
216
+ item.reject(err);
217
+ this.handleError(err);
218
+ } finally {
219
+ this.isProcessingQueue = false;
220
+ setTimeout(() => this.processNextInQueue(), 0);
221
+ }
222
+ }
223
+ updateStatus(newStatus) {
224
+ this.status = newStatus;
225
+ this.statusCallbacks.forEach((cb) => cb(newStatus));
226
+ }
227
+ handleError(err) {
228
+ const errorObj = err instanceof Error ? err : new Error(String(err));
229
+ this.errorCallbacks.forEach((cb) => cb(errorObj));
230
+ }
231
+ triggerToken(token) {
232
+ this.tokenCallbacks.forEach((cb) => cb(token));
233
+ }
234
+ triggerRecommendation(rec) {
235
+ this.recommendationCallbacks.forEach((cb) => cb(rec));
236
+ }
237
+ };
238
+ export {
239
+ AnswerEngineClient
240
+ };
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@elelem/answer-engine-client",
3
+ "version": "1.0.0",
4
+ "description": "Zero-dependency client SDK for the Elelem Answer Engine Realtime API",
5
+ "main": "./dist/index.cjs.js",
6
+ "module": "./dist/index.esm.js",
7
+ "types": "./dist/index.d.ts",
8
+ "browser": "./dist/index.global.js",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.esm.js",
13
+ "require": "./dist/index.cjs.js"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsup src/index.ts --format cjs,esm,iife --global-name AnswerEngine --dts --clean"
21
+ },
22
+ "peerDependencies": {
23
+ "socket.io-client": "^4.0.0"
24
+ },
25
+ "devDependencies": {
26
+ "typescript": "^5.4.5",
27
+ "tsup": "^8.0.2"
28
+ },
29
+ "publishConfig": {
30
+ "access": "public"
31
+ },
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/elelem/answer-engine-client.git"
35
+ },
36
+ "keywords": [
37
+ "answer-engine",
38
+ "realtime",
39
+ "socket.io",
40
+ "sdk",
41
+ "elelem"
42
+ ],
43
+ "author": "Elelem AI",
44
+ "license": "MIT"
45
+ }