@dexto/client-sdk 1.1.5
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/LICENSE +44 -0
- package/README.md +108 -0
- package/dist/index.cjs +849 -0
- package/dist/index.d.cts +232 -0
- package/dist/index.d.ts +232 -0
- package/dist/index.js +828 -0
- package/package.json +36 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,849 @@
|
|
|
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 __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
21
|
+
|
|
22
|
+
// src/index.ts
|
|
23
|
+
var index_exports = {};
|
|
24
|
+
__export(index_exports, {
|
|
25
|
+
ClientError: () => ClientError,
|
|
26
|
+
DextoClient: () => DextoClient
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(index_exports);
|
|
29
|
+
|
|
30
|
+
// src/errors.ts
|
|
31
|
+
var ClientError = class {
|
|
32
|
+
/**
|
|
33
|
+
* Connection and Network Errors
|
|
34
|
+
*/
|
|
35
|
+
static connectionFailed(baseUrl, originalError) {
|
|
36
|
+
const error = new Error(`Failed to connect to Dexto server at ${baseUrl}`);
|
|
37
|
+
error.name = "ConnectionError";
|
|
38
|
+
error.baseUrl = baseUrl;
|
|
39
|
+
error.originalError = originalError == null ? void 0 : originalError.message;
|
|
40
|
+
return error;
|
|
41
|
+
}
|
|
42
|
+
static networkError(message, originalError) {
|
|
43
|
+
const error = new Error(`Network error: ${message}`);
|
|
44
|
+
error.name = "NetworkError";
|
|
45
|
+
error.originalError = originalError == null ? void 0 : originalError.message;
|
|
46
|
+
return error;
|
|
47
|
+
}
|
|
48
|
+
static httpError(status, statusText, endpoint, details) {
|
|
49
|
+
const error = new Error(`HTTP ${status}: ${statusText}${endpoint ? ` (${endpoint})` : ""}`);
|
|
50
|
+
error.name = "HttpError";
|
|
51
|
+
error.status = status;
|
|
52
|
+
error.statusText = statusText;
|
|
53
|
+
error.endpoint = endpoint;
|
|
54
|
+
error.details = details;
|
|
55
|
+
return error;
|
|
56
|
+
}
|
|
57
|
+
static timeoutError(operation, timeout) {
|
|
58
|
+
const error = new Error(`Operation '${operation}' timed out after ${timeout}ms`);
|
|
59
|
+
error.name = "TimeoutError";
|
|
60
|
+
error.operation = operation;
|
|
61
|
+
error.timeout = timeout;
|
|
62
|
+
return error;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* WebSocket Errors
|
|
66
|
+
*/
|
|
67
|
+
static websocketConnectionFailed(url, originalError) {
|
|
68
|
+
const error = new Error(`Failed to connect WebSocket to ${url}`);
|
|
69
|
+
error.name = "WebSocketConnectionError";
|
|
70
|
+
error.url = url;
|
|
71
|
+
error.originalError = originalError == null ? void 0 : originalError.message;
|
|
72
|
+
return error;
|
|
73
|
+
}
|
|
74
|
+
static websocketSendFailed(originalError) {
|
|
75
|
+
const error = new Error("Failed to send WebSocket message");
|
|
76
|
+
error.name = "WebSocketSendError";
|
|
77
|
+
error.originalError = originalError == null ? void 0 : originalError.message;
|
|
78
|
+
return error;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Configuration Errors
|
|
82
|
+
*/
|
|
83
|
+
static invalidConfig(field, value, reason) {
|
|
84
|
+
const error = new Error(`Invalid configuration for ${field}: ${reason}`);
|
|
85
|
+
error.name = "InvalidConfigError";
|
|
86
|
+
error.field = field;
|
|
87
|
+
error.value = value;
|
|
88
|
+
error.reason = reason;
|
|
89
|
+
return error;
|
|
90
|
+
}
|
|
91
|
+
static responseParseError(originalError) {
|
|
92
|
+
const error = new Error("Failed to parse server response");
|
|
93
|
+
error.name = "ResponseParseError";
|
|
94
|
+
error.originalError = originalError == null ? void 0 : originalError.message;
|
|
95
|
+
return error;
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// src/http-client.ts
|
|
100
|
+
var HttpClient = class {
|
|
101
|
+
constructor(config) {
|
|
102
|
+
__publicField(this, "config");
|
|
103
|
+
__publicField(this, "fetchFn");
|
|
104
|
+
this.config = config;
|
|
105
|
+
if (typeof window !== "undefined" && window.fetch) {
|
|
106
|
+
this.fetchFn = window.fetch.bind(window);
|
|
107
|
+
} else if (typeof globalThis !== "undefined" && globalThis.fetch) {
|
|
108
|
+
this.fetchFn = globalThis.fetch;
|
|
109
|
+
} else {
|
|
110
|
+
throw ClientError.invalidConfig(
|
|
111
|
+
"fetch",
|
|
112
|
+
"unavailable",
|
|
113
|
+
"No fetch implementation available. Use Node.js 18+ or a browser."
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async request(endpoint, options = {}) {
|
|
118
|
+
var _a;
|
|
119
|
+
const { method = "GET", body, headers = {}, timeout = this.config.timeout } = options;
|
|
120
|
+
const url = new URL(endpoint, this.config.baseUrl).toString();
|
|
121
|
+
const requestHeaders = { ...headers };
|
|
122
|
+
const hasContentType = Object.keys(requestHeaders).some(
|
|
123
|
+
(k) => k.toLowerCase() === "content-type"
|
|
124
|
+
);
|
|
125
|
+
let payload = void 0;
|
|
126
|
+
const isBodyInit = (b) => {
|
|
127
|
+
if (b == null) return false;
|
|
128
|
+
return typeof b === "string" || typeof globalThis.Blob !== "undefined" && b instanceof globalThis.Blob || typeof globalThis.FormData !== "undefined" && b instanceof globalThis.FormData || typeof globalThis.URLSearchParams !== "undefined" && b instanceof globalThis.URLSearchParams || typeof globalThis.ReadableStream !== "undefined" && b instanceof globalThis.ReadableStream || typeof ArrayBuffer !== "undefined" && b instanceof ArrayBuffer;
|
|
129
|
+
};
|
|
130
|
+
if (body !== void 0) {
|
|
131
|
+
if (isBodyInit(body)) {
|
|
132
|
+
payload = body;
|
|
133
|
+
} else {
|
|
134
|
+
payload = JSON.stringify(body);
|
|
135
|
+
if (!hasContentType) {
|
|
136
|
+
requestHeaders["Content-Type"] = "application/json";
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (this.config.apiKey) {
|
|
141
|
+
requestHeaders["Authorization"] = `Bearer ${this.config.apiKey}`;
|
|
142
|
+
}
|
|
143
|
+
const requestInit = {
|
|
144
|
+
method,
|
|
145
|
+
headers: requestHeaders,
|
|
146
|
+
...payload !== void 0 && { body: payload }
|
|
147
|
+
};
|
|
148
|
+
const controller = new AbortController();
|
|
149
|
+
const resolvedTimeout = (_a = timeout != null ? timeout : this.config.timeout) != null ? _a : 3e4;
|
|
150
|
+
const timeoutId = setTimeout(() => controller.abort(), resolvedTimeout);
|
|
151
|
+
requestInit.signal = controller.signal;
|
|
152
|
+
try {
|
|
153
|
+
const response = await this.fetchWithRetry(url, requestInit);
|
|
154
|
+
clearTimeout(timeoutId);
|
|
155
|
+
if (!response.ok) {
|
|
156
|
+
const errorData = await this.safeParseJson(response);
|
|
157
|
+
throw ClientError.httpError(
|
|
158
|
+
response.status,
|
|
159
|
+
response.statusText,
|
|
160
|
+
endpoint,
|
|
161
|
+
errorData
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
if (response.status === 204 || response.headers.get("content-length") === "0") {
|
|
165
|
+
return {};
|
|
166
|
+
}
|
|
167
|
+
const contentType = response.headers.get("content-type") || "";
|
|
168
|
+
if (contentType.includes("application/json")) {
|
|
169
|
+
const data = await response.json();
|
|
170
|
+
return data;
|
|
171
|
+
}
|
|
172
|
+
const text = await response.text();
|
|
173
|
+
return text ? text : {};
|
|
174
|
+
} catch (error) {
|
|
175
|
+
clearTimeout(timeoutId);
|
|
176
|
+
if (error instanceof Error && error.name.startsWith("ClientError")) {
|
|
177
|
+
throw error;
|
|
178
|
+
}
|
|
179
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
180
|
+
throw ClientError.timeoutError(endpoint, resolvedTimeout);
|
|
181
|
+
}
|
|
182
|
+
throw ClientError.networkError(
|
|
183
|
+
error instanceof Error ? error.message : "Unknown network error",
|
|
184
|
+
error instanceof Error ? error : void 0
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
async fetchWithRetry(url, init) {
|
|
189
|
+
var _a;
|
|
190
|
+
let lastError = null;
|
|
191
|
+
const method = (init.method || "GET").toUpperCase();
|
|
192
|
+
const canRetry = ["GET", "HEAD", "PUT", "DELETE"].includes(method);
|
|
193
|
+
const maxRetries = (_a = this.config.retries) != null ? _a : 3;
|
|
194
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
195
|
+
try {
|
|
196
|
+
const response = await this.fetchFn(url, init);
|
|
197
|
+
const transientStatus = [429, 502, 503, 504];
|
|
198
|
+
if (canRetry && transientStatus.includes(response.status) && attempt < maxRetries) {
|
|
199
|
+
const retryAfter = response.headers.get("retry-after");
|
|
200
|
+
let delay = Math.min(1e3 * Math.pow(2, attempt), 1e4);
|
|
201
|
+
if (retryAfter) {
|
|
202
|
+
const secs = Number(retryAfter);
|
|
203
|
+
if (Number.isFinite(secs) && secs >= 0) {
|
|
204
|
+
delay = secs * 1e3;
|
|
205
|
+
} else {
|
|
206
|
+
const retryTime = Date.parse(retryAfter);
|
|
207
|
+
if (retryTime > Date.now()) {
|
|
208
|
+
delay = Math.max(0, retryTime - Date.now());
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
delay = Math.min(delay, 3e4);
|
|
213
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
return response;
|
|
217
|
+
} catch (error) {
|
|
218
|
+
lastError = error instanceof Error ? error : ClientError.networkError(
|
|
219
|
+
error instanceof Error ? error.message : String(error),
|
|
220
|
+
error instanceof Error ? error : void 0
|
|
221
|
+
);
|
|
222
|
+
if (attempt === maxRetries || !canRetry || error instanceof Error && error.name === "AbortError") {
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
const delay = Math.min(1e3 * Math.pow(2, attempt), 1e4);
|
|
226
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
throw lastError;
|
|
230
|
+
}
|
|
231
|
+
async safeParseJson(response) {
|
|
232
|
+
try {
|
|
233
|
+
return await response.json();
|
|
234
|
+
} catch (e) {
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
// Convenience methods
|
|
239
|
+
async get(endpoint, headers) {
|
|
240
|
+
const options = { method: "GET" };
|
|
241
|
+
if (headers) options.headers = headers;
|
|
242
|
+
return this.request(endpoint, options);
|
|
243
|
+
}
|
|
244
|
+
async post(endpoint, body, headers) {
|
|
245
|
+
const options = { method: "POST" };
|
|
246
|
+
if (body !== void 0) options.body = body;
|
|
247
|
+
if (headers) options.headers = headers;
|
|
248
|
+
return this.request(endpoint, options);
|
|
249
|
+
}
|
|
250
|
+
async put(endpoint, body, headers) {
|
|
251
|
+
const options = { method: "PUT" };
|
|
252
|
+
if (body !== void 0) options.body = body;
|
|
253
|
+
if (headers) options.headers = headers;
|
|
254
|
+
return this.request(endpoint, options);
|
|
255
|
+
}
|
|
256
|
+
async delete(endpoint, headers) {
|
|
257
|
+
const options = { method: "DELETE" };
|
|
258
|
+
if (headers) options.headers = headers;
|
|
259
|
+
return this.request(endpoint, options);
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
// src/websocket-client.ts
|
|
264
|
+
var WebSocketClient = class {
|
|
265
|
+
constructor(url, options = {}) {
|
|
266
|
+
__publicField(this, "ws", null);
|
|
267
|
+
__publicField(this, "url");
|
|
268
|
+
__publicField(this, "eventHandlers", /* @__PURE__ */ new Map());
|
|
269
|
+
__publicField(this, "stateHandlers", /* @__PURE__ */ new Set());
|
|
270
|
+
__publicField(this, "reconnectEnabled", true);
|
|
271
|
+
__publicField(this, "reconnectInterval", 5e3);
|
|
272
|
+
__publicField(this, "maxReconnectAttempts", 10);
|
|
273
|
+
__publicField(this, "reconnectAttempts", 0);
|
|
274
|
+
__publicField(this, "isIntentionallyClosed", false);
|
|
275
|
+
var _a, _b, _c;
|
|
276
|
+
this.url = url;
|
|
277
|
+
this.reconnectEnabled = (_a = options.reconnect) != null ? _a : true;
|
|
278
|
+
this.reconnectInterval = (_b = options.reconnectInterval) != null ? _b : 5e3;
|
|
279
|
+
this.maxReconnectAttempts = (_c = options.maxReconnectAttempts) != null ? _c : 10;
|
|
280
|
+
}
|
|
281
|
+
connect() {
|
|
282
|
+
return new Promise((resolve, reject) => {
|
|
283
|
+
try {
|
|
284
|
+
this.isIntentionallyClosed = false;
|
|
285
|
+
this.emitState("connecting");
|
|
286
|
+
if (typeof WebSocket !== "undefined") {
|
|
287
|
+
this.ws = new WebSocket(this.url);
|
|
288
|
+
} else {
|
|
289
|
+
reject(
|
|
290
|
+
ClientError.websocketConnectionFailed(
|
|
291
|
+
this.url,
|
|
292
|
+
new Error(
|
|
293
|
+
"WebSocket not available in this environment. Use HTTP methods or run in a browser."
|
|
294
|
+
)
|
|
295
|
+
)
|
|
296
|
+
);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
this.setupEventHandlers(resolve, reject);
|
|
300
|
+
} catch (error) {
|
|
301
|
+
reject(
|
|
302
|
+
ClientError.websocketConnectionFailed(
|
|
303
|
+
this.url,
|
|
304
|
+
error instanceof Error ? error : new Error("Failed to create WebSocket connection")
|
|
305
|
+
)
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
setupEventHandlers(resolve, reject) {
|
|
311
|
+
if (!this.ws) return;
|
|
312
|
+
this.ws.onopen = () => {
|
|
313
|
+
this.reconnectAttempts = 0;
|
|
314
|
+
this.emitState("open");
|
|
315
|
+
resolve();
|
|
316
|
+
};
|
|
317
|
+
this.ws.onmessage = (event) => {
|
|
318
|
+
const raw = event.data;
|
|
319
|
+
const tryDispatch = (text) => {
|
|
320
|
+
try {
|
|
321
|
+
this.handleIncomingMessage(JSON.parse(text));
|
|
322
|
+
} catch (err) {
|
|
323
|
+
console.warn(
|
|
324
|
+
`[WebSocketClient] Failed to parse WebSocket message: ${err instanceof Error ? err.message : String(err)}`
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
if (typeof raw === "string") {
|
|
329
|
+
tryDispatch(raw);
|
|
330
|
+
} else if (typeof globalThis.Blob !== "undefined" && raw instanceof globalThis.Blob) {
|
|
331
|
+
raw.text().then(tryDispatch).catch((err) => {
|
|
332
|
+
console.warn(
|
|
333
|
+
`[WebSocketClient] Failed to read Blob message: ${err instanceof Error ? err.message : String(err)}`
|
|
334
|
+
);
|
|
335
|
+
});
|
|
336
|
+
} else if (typeof ArrayBuffer !== "undefined" && raw instanceof ArrayBuffer) {
|
|
337
|
+
try {
|
|
338
|
+
const text = new globalThis.TextDecoder().decode(raw);
|
|
339
|
+
tryDispatch(text);
|
|
340
|
+
} catch (err) {
|
|
341
|
+
console.warn(
|
|
342
|
+
`[WebSocketClient] Failed to decode ArrayBuffer message: ${err instanceof Error ? err.message : String(err)}`
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
} else {
|
|
346
|
+
console.warn(
|
|
347
|
+
`[WebSocketClient] Ignoring non-text WebSocket message of type: ${Object.prototype.toString.call(raw)}`
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
this.ws.onclose = (_event) => {
|
|
352
|
+
this.emitState("closed");
|
|
353
|
+
const shouldReconnect = !this.isIntentionallyClosed && this.reconnectEnabled && this.reconnectAttempts < this.maxReconnectAttempts;
|
|
354
|
+
if (shouldReconnect) {
|
|
355
|
+
this.scheduleReconnect(resolve, reject);
|
|
356
|
+
} else if (!this.isIntentionallyClosed && this.reconnectAttempts >= this.maxReconnectAttempts) {
|
|
357
|
+
reject(
|
|
358
|
+
ClientError.websocketConnectionFailed(
|
|
359
|
+
this.url,
|
|
360
|
+
new Error(`Max reconnect attempts reached (${this.maxReconnectAttempts})`)
|
|
361
|
+
)
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
this.ws.onerror = (_error) => {
|
|
366
|
+
this.emitState("error");
|
|
367
|
+
if (this.reconnectAttempts === 0) {
|
|
368
|
+
reject(ClientError.websocketConnectionFailed(this.url));
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
scheduleReconnect(resolve, reject) {
|
|
373
|
+
this.reconnectAttempts++;
|
|
374
|
+
setTimeout(() => {
|
|
375
|
+
if (!this.isIntentionallyClosed) {
|
|
376
|
+
this.emitState("connecting");
|
|
377
|
+
if (resolve && reject) {
|
|
378
|
+
this.connect().then(resolve).catch(reject);
|
|
379
|
+
} else {
|
|
380
|
+
this.connect().catch(() => {
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}, this.reconnectInterval);
|
|
385
|
+
}
|
|
386
|
+
handleIncomingMessage(data) {
|
|
387
|
+
var _a;
|
|
388
|
+
const msgData = data;
|
|
389
|
+
const eventType = msgData.type || msgData.event;
|
|
390
|
+
if (!eventType) {
|
|
391
|
+
console.warn("Received WebSocket message without event type:", msgData);
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
const event = {
|
|
395
|
+
type: eventType,
|
|
396
|
+
data: msgData.data || msgData,
|
|
397
|
+
sessionId: msgData.sessionId || ((_a = msgData.data) == null ? void 0 : _a.sessionId)
|
|
398
|
+
};
|
|
399
|
+
const handlers = this.eventHandlers.get(event.type);
|
|
400
|
+
if (handlers) {
|
|
401
|
+
handlers.forEach((handler) => {
|
|
402
|
+
try {
|
|
403
|
+
handler(event);
|
|
404
|
+
} catch (error) {
|
|
405
|
+
console.error(
|
|
406
|
+
`Error in event handler for ${event.type}: ${error instanceof Error ? error.message : String(error)}`
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
const wildcardHandlers = this.eventHandlers.get("*");
|
|
412
|
+
if (wildcardHandlers) {
|
|
413
|
+
wildcardHandlers.forEach((handler) => {
|
|
414
|
+
try {
|
|
415
|
+
handler(event);
|
|
416
|
+
} catch (error) {
|
|
417
|
+
console.error(
|
|
418
|
+
`Error in wildcard event handler: ${error instanceof Error ? error.message : String(error)}`
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
// Send a message through the WebSocket
|
|
425
|
+
send(message) {
|
|
426
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
427
|
+
return false;
|
|
428
|
+
}
|
|
429
|
+
try {
|
|
430
|
+
this.ws.send(JSON.stringify(message));
|
|
431
|
+
return true;
|
|
432
|
+
} catch (error) {
|
|
433
|
+
console.error(
|
|
434
|
+
`Failed to send WebSocket message: ${error instanceof Error ? error.message : String(error)}`
|
|
435
|
+
);
|
|
436
|
+
return false;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
// Subscribe to specific event types
|
|
440
|
+
on(eventType, handler) {
|
|
441
|
+
if (!this.eventHandlers.has(eventType)) {
|
|
442
|
+
this.eventHandlers.set(eventType, /* @__PURE__ */ new Set());
|
|
443
|
+
}
|
|
444
|
+
this.eventHandlers.get(eventType).add(handler);
|
|
445
|
+
return () => {
|
|
446
|
+
const handlers = this.eventHandlers.get(eventType);
|
|
447
|
+
if (handlers) {
|
|
448
|
+
handlers.delete(handler);
|
|
449
|
+
if (handlers.size === 0) {
|
|
450
|
+
this.eventHandlers.delete(eventType);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
// Subscribe to connection state changes
|
|
456
|
+
onConnectionState(handler) {
|
|
457
|
+
this.stateHandlers.add(handler);
|
|
458
|
+
return () => {
|
|
459
|
+
this.stateHandlers.delete(handler);
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
emitState(state) {
|
|
463
|
+
this.stateHandlers.forEach((handler) => {
|
|
464
|
+
try {
|
|
465
|
+
handler(state);
|
|
466
|
+
} catch (error) {
|
|
467
|
+
console.error(
|
|
468
|
+
`Error in connection state handler: ${error instanceof Error ? error.message : String(error)}`
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
// Get current connection state
|
|
474
|
+
get state() {
|
|
475
|
+
if (!this.ws) return "closed";
|
|
476
|
+
switch (this.ws.readyState) {
|
|
477
|
+
case WebSocket.CONNECTING:
|
|
478
|
+
return "connecting";
|
|
479
|
+
case WebSocket.OPEN:
|
|
480
|
+
return "open";
|
|
481
|
+
case WebSocket.CLOSING:
|
|
482
|
+
case WebSocket.CLOSED:
|
|
483
|
+
return "closed";
|
|
484
|
+
default:
|
|
485
|
+
return "closed";
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
// Close the connection
|
|
489
|
+
close() {
|
|
490
|
+
this.isIntentionallyClosed = true;
|
|
491
|
+
this.reconnectEnabled = false;
|
|
492
|
+
if (this.ws) {
|
|
493
|
+
this.ws.close();
|
|
494
|
+
this.ws = null;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
// Enable/disable automatic reconnection
|
|
498
|
+
setReconnectEnabled(enabled) {
|
|
499
|
+
this.reconnectEnabled = enabled;
|
|
500
|
+
}
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
// src/schemas.ts
|
|
504
|
+
function isValidUrl(url) {
|
|
505
|
+
try {
|
|
506
|
+
new URL(url);
|
|
507
|
+
return /^https?:\/\//i.test(url);
|
|
508
|
+
} catch (e) {
|
|
509
|
+
return false;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// src/client.ts
|
|
514
|
+
var DextoClient = class {
|
|
515
|
+
constructor(config, options = {}) {
|
|
516
|
+
__publicField(this, "http");
|
|
517
|
+
__publicField(this, "ws", null);
|
|
518
|
+
__publicField(this, "config");
|
|
519
|
+
__publicField(this, "options");
|
|
520
|
+
var _a, _b, _c, _d, _e, _f;
|
|
521
|
+
if (!isValidUrl(config.baseUrl)) {
|
|
522
|
+
throw ClientError.invalidConfig(
|
|
523
|
+
"baseUrl",
|
|
524
|
+
config.baseUrl,
|
|
525
|
+
"Must be a valid HTTP/HTTPS URL"
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
this.config = {
|
|
529
|
+
baseUrl: config.baseUrl,
|
|
530
|
+
apiKey: config.apiKey,
|
|
531
|
+
timeout: (_a = config.timeout) != null ? _a : 3e4,
|
|
532
|
+
retries: (_b = config.retries) != null ? _b : 3
|
|
533
|
+
};
|
|
534
|
+
this.options = {
|
|
535
|
+
enableWebSocket: (_c = options.enableWebSocket) != null ? _c : true,
|
|
536
|
+
reconnect: (_d = options.reconnect) != null ? _d : true,
|
|
537
|
+
reconnectInterval: (_e = options.reconnectInterval) != null ? _e : 5e3,
|
|
538
|
+
debug: (_f = options.debug) != null ? _f : false
|
|
539
|
+
};
|
|
540
|
+
this.http = new HttpClient(this.config);
|
|
541
|
+
if (this.options.enableWebSocket) {
|
|
542
|
+
this.initializeWebSocket();
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
initializeWebSocket() {
|
|
546
|
+
var _a, _b;
|
|
547
|
+
const wsUrl = this.config.baseUrl.replace(/^https/, "wss").replace(/^http/, "ws");
|
|
548
|
+
this.ws = new WebSocketClient(wsUrl, {
|
|
549
|
+
reconnect: (_a = this.options.reconnect) != null ? _a : true,
|
|
550
|
+
reconnectInterval: (_b = this.options.reconnectInterval) != null ? _b : 5e3
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
// ============= CONNECTION MANAGEMENT =============
|
|
554
|
+
/**
|
|
555
|
+
* Establish connection to Dexto server (including WebSocket if enabled)
|
|
556
|
+
*/
|
|
557
|
+
async connect() {
|
|
558
|
+
try {
|
|
559
|
+
await this.http.get("/health");
|
|
560
|
+
} catch (error) {
|
|
561
|
+
throw ClientError.connectionFailed(
|
|
562
|
+
this.config.baseUrl,
|
|
563
|
+
error instanceof Error ? error : void 0
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
if (this.options.enableWebSocket && this.ws) {
|
|
567
|
+
try {
|
|
568
|
+
await this.ws.connect();
|
|
569
|
+
} catch (error) {
|
|
570
|
+
if (this.options.debug) {
|
|
571
|
+
console.warn(
|
|
572
|
+
`WebSocket connection failed, continuing with HTTP-only mode: ${error instanceof Error ? error.message : String(error)}`
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
this.ws = null;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Disconnect from Dexto server
|
|
581
|
+
*/
|
|
582
|
+
disconnect() {
|
|
583
|
+
if (this.ws) {
|
|
584
|
+
this.ws.close();
|
|
585
|
+
this.ws = null;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
// ============= MESSAGING =============
|
|
589
|
+
/**
|
|
590
|
+
* Send a message to the Dexto agent
|
|
591
|
+
*/
|
|
592
|
+
async sendMessage(input) {
|
|
593
|
+
if (input.stream === true) {
|
|
594
|
+
throw ClientError.invalidConfig(
|
|
595
|
+
"input.stream",
|
|
596
|
+
input.stream,
|
|
597
|
+
"Use sendMessageStream() for streaming responses"
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
const endpoint = "/api/message-sync";
|
|
601
|
+
const requestBody = {
|
|
602
|
+
message: input.content,
|
|
603
|
+
...input.sessionId && { sessionId: input.sessionId },
|
|
604
|
+
...input.imageData && { imageData: input.imageData },
|
|
605
|
+
...input.fileData && { fileData: input.fileData }
|
|
606
|
+
};
|
|
607
|
+
return this.http.post(endpoint, requestBody);
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* Send a message via WebSocket for streaming responses
|
|
611
|
+
*/
|
|
612
|
+
sendMessageStream(input) {
|
|
613
|
+
if (!this.ws || this.ws.state !== "open") {
|
|
614
|
+
throw ClientError.connectionFailed(
|
|
615
|
+
"WebSocket endpoint",
|
|
616
|
+
new Error("WebSocket connection not available for streaming")
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
return this.ws.send({
|
|
620
|
+
type: "message",
|
|
621
|
+
content: input.content,
|
|
622
|
+
...input.sessionId && { sessionId: input.sessionId },
|
|
623
|
+
...input.imageData && { imageData: input.imageData },
|
|
624
|
+
...input.fileData && { fileData: input.fileData },
|
|
625
|
+
stream: true
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
// ============= SESSION MANAGEMENT =============
|
|
629
|
+
/**
|
|
630
|
+
* List all sessions
|
|
631
|
+
*/
|
|
632
|
+
async listSessions() {
|
|
633
|
+
const response = await this.http.get("/api/sessions");
|
|
634
|
+
return response.sessions;
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* Create a new session
|
|
638
|
+
*/
|
|
639
|
+
async createSession(sessionId) {
|
|
640
|
+
const response = await this.http.post("/api/sessions", {
|
|
641
|
+
...sessionId && { sessionId }
|
|
642
|
+
});
|
|
643
|
+
return response.session;
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Get session details
|
|
647
|
+
*/
|
|
648
|
+
async getSession(sessionId) {
|
|
649
|
+
const response = await this.http.get(
|
|
650
|
+
`/api/sessions/${encodeURIComponent(sessionId)}`
|
|
651
|
+
);
|
|
652
|
+
return response.session;
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* Get session conversation history
|
|
656
|
+
*/
|
|
657
|
+
async getSessionHistory(sessionId) {
|
|
658
|
+
const response = await this.http.get(
|
|
659
|
+
`/api/sessions/${encodeURIComponent(sessionId)}/history`
|
|
660
|
+
);
|
|
661
|
+
return response.history;
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* Delete a session permanently
|
|
665
|
+
*/
|
|
666
|
+
async deleteSession(sessionId) {
|
|
667
|
+
await this.http.delete(`/api/sessions/${encodeURIComponent(sessionId)}`);
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* Load a session as the current working session
|
|
671
|
+
*/
|
|
672
|
+
async loadSession(sessionId) {
|
|
673
|
+
const id = sessionId === null ? "null" : sessionId;
|
|
674
|
+
await this.http.post(`/api/sessions/${encodeURIComponent(id)}/load`);
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
677
|
+
* Get the current working session
|
|
678
|
+
*/
|
|
679
|
+
async getCurrentSession() {
|
|
680
|
+
const response = await this.http.get("/api/sessions/current");
|
|
681
|
+
return response.currentSessionId;
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* Reset conversation (clear history while keeping session alive)
|
|
685
|
+
*/
|
|
686
|
+
async resetConversation(sessionId) {
|
|
687
|
+
await this.http.post("/api/reset", {
|
|
688
|
+
...sessionId && { sessionId }
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
// ============= LLM MANAGEMENT =============
|
|
692
|
+
/**
|
|
693
|
+
* Get current LLM configuration
|
|
694
|
+
*/
|
|
695
|
+
async getCurrentLLMConfig(sessionId) {
|
|
696
|
+
const params = sessionId ? `?sessionId=${encodeURIComponent(sessionId)}` : "";
|
|
697
|
+
const response = await this.http.get(`/api/llm/current${params}`);
|
|
698
|
+
return response.config;
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* Switch LLM configuration
|
|
702
|
+
*/
|
|
703
|
+
async switchLLM(config, sessionId) {
|
|
704
|
+
const requestBody = {
|
|
705
|
+
...config,
|
|
706
|
+
...sessionId && { sessionId }
|
|
707
|
+
};
|
|
708
|
+
const response = await this.http.post(
|
|
709
|
+
"/api/llm/switch",
|
|
710
|
+
requestBody
|
|
711
|
+
);
|
|
712
|
+
return response.config;
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* Get available LLM providers and models
|
|
716
|
+
*/
|
|
717
|
+
async getLLMProviders() {
|
|
718
|
+
const response = await this.http.get(
|
|
719
|
+
"/api/llm/providers"
|
|
720
|
+
);
|
|
721
|
+
return response.providers;
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* Get LLM catalog with filtering options
|
|
725
|
+
*/
|
|
726
|
+
async getLLMCatalog(options = {}) {
|
|
727
|
+
const params = new globalThis.URLSearchParams();
|
|
728
|
+
if (options.provider) params.set("provider", options.provider);
|
|
729
|
+
if (options.hasKey !== void 0) params.set("hasKey", options.hasKey.toString());
|
|
730
|
+
if (options.router) params.set("router", options.router);
|
|
731
|
+
if (options.fileType) params.set("fileType", options.fileType);
|
|
732
|
+
if (options.defaultOnly) params.set("defaultOnly", "true");
|
|
733
|
+
if (options.mode) params.set("mode", options.mode);
|
|
734
|
+
const queryString = params.toString();
|
|
735
|
+
const endpoint = queryString ? `/api/llm/catalog?${queryString}` : "/api/llm/catalog";
|
|
736
|
+
return this.http.get(endpoint);
|
|
737
|
+
}
|
|
738
|
+
// ============= MCP SERVER MANAGEMENT =============
|
|
739
|
+
/**
|
|
740
|
+
* List connected MCP servers
|
|
741
|
+
*/
|
|
742
|
+
async listMCPServers() {
|
|
743
|
+
const response = await this.http.get("/api/mcp/servers");
|
|
744
|
+
return response.servers;
|
|
745
|
+
}
|
|
746
|
+
/**
|
|
747
|
+
* Connect to a new MCP server
|
|
748
|
+
*/
|
|
749
|
+
async connectMCPServer(name, config) {
|
|
750
|
+
await this.http.post("/api/mcp/servers", { name, config });
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* Disconnect from an MCP server
|
|
754
|
+
*/
|
|
755
|
+
async disconnectMCPServer(serverId) {
|
|
756
|
+
await this.http.delete(`/api/mcp/servers/${encodeURIComponent(serverId)}`);
|
|
757
|
+
}
|
|
758
|
+
/**
|
|
759
|
+
* Get tools from a specific MCP server
|
|
760
|
+
*/
|
|
761
|
+
async getMCPServerTools(serverId) {
|
|
762
|
+
const response = await this.http.get(
|
|
763
|
+
`/api/mcp/servers/${encodeURIComponent(serverId)}/tools`
|
|
764
|
+
);
|
|
765
|
+
return response.tools;
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
768
|
+
* Execute a tool from an MCP server
|
|
769
|
+
*/
|
|
770
|
+
async executeMCPTool(serverId, toolName, args) {
|
|
771
|
+
const response = await this.http.post(
|
|
772
|
+
`/api/mcp/servers/${encodeURIComponent(serverId)}/tools/${encodeURIComponent(toolName)}/execute`,
|
|
773
|
+
args
|
|
774
|
+
);
|
|
775
|
+
return response.data;
|
|
776
|
+
}
|
|
777
|
+
// ============= SEARCH =============
|
|
778
|
+
/**
|
|
779
|
+
* Search messages across sessions
|
|
780
|
+
*/
|
|
781
|
+
async searchMessages(query, options = {}) {
|
|
782
|
+
const params = new globalThis.URLSearchParams();
|
|
783
|
+
params.append("q", query);
|
|
784
|
+
if (options.limit !== void 0) params.append("limit", String(options.limit));
|
|
785
|
+
if (options.offset !== void 0) params.append("offset", String(options.offset));
|
|
786
|
+
if (options.sessionId) params.append("sessionId", options.sessionId);
|
|
787
|
+
if (options.role) params.append("role", options.role);
|
|
788
|
+
return this.http.get(`/api/search/messages?${params}`);
|
|
789
|
+
}
|
|
790
|
+
/**
|
|
791
|
+
* Search sessions that contain the query
|
|
792
|
+
*/
|
|
793
|
+
async searchSessions(query) {
|
|
794
|
+
const params = new globalThis.URLSearchParams({ q: query });
|
|
795
|
+
return this.http.get(`/api/search/sessions?${params}`);
|
|
796
|
+
}
|
|
797
|
+
// ============= EVENT HANDLING =============
|
|
798
|
+
/**
|
|
799
|
+
* Subscribe to real-time events
|
|
800
|
+
*/
|
|
801
|
+
on(eventType, handler) {
|
|
802
|
+
if (!this.ws) {
|
|
803
|
+
if (this.options.debug) {
|
|
804
|
+
console.warn("WebSocket not available, events will not be received");
|
|
805
|
+
}
|
|
806
|
+
return () => {
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
return this.ws.on(eventType, handler);
|
|
810
|
+
}
|
|
811
|
+
/**
|
|
812
|
+
* Subscribe to connection state changes
|
|
813
|
+
*/
|
|
814
|
+
onConnectionState(handler) {
|
|
815
|
+
if (!this.ws) {
|
|
816
|
+
return () => {
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
return this.ws.onConnectionState(handler);
|
|
820
|
+
}
|
|
821
|
+
// ============= GREETING =============
|
|
822
|
+
/**
|
|
823
|
+
* Get agent greeting message
|
|
824
|
+
*/
|
|
825
|
+
async getGreeting(sessionId) {
|
|
826
|
+
const params = sessionId ? `?sessionId=${encodeURIComponent(sessionId)}` : "";
|
|
827
|
+
const response = await this.http.get(`/api/greeting${params}`);
|
|
828
|
+
return response.greeting;
|
|
829
|
+
}
|
|
830
|
+
// ============= UTILITY METHODS =============
|
|
831
|
+
/**
|
|
832
|
+
* Get connection status
|
|
833
|
+
*/
|
|
834
|
+
get connectionState() {
|
|
835
|
+
return this.ws ? this.ws.state : "closed";
|
|
836
|
+
}
|
|
837
|
+
/**
|
|
838
|
+
* Check if client is connected
|
|
839
|
+
*/
|
|
840
|
+
get isConnected() {
|
|
841
|
+
return this.connectionState === "open";
|
|
842
|
+
}
|
|
843
|
+
/**
|
|
844
|
+
* Get client configuration
|
|
845
|
+
*/
|
|
846
|
+
get clientConfig() {
|
|
847
|
+
return { ...this.config };
|
|
848
|
+
}
|
|
849
|
+
};
|