@ekodb/ekodb-client 0.13.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +70 -0
- package/dist/client.d.ts +142 -4
- package/dist/client.js +486 -7
- package/dist/client.test.js +810 -0
- package/dist/websocket.test.js +89 -0
- package/package.json +1 -1
- package/src/client.test.ts +1016 -0
- package/src/client.ts +823 -7
- package/src/websocket.test.ts +137 -0
package/dist/client.js
CHANGED
|
@@ -71,6 +71,7 @@ var MergeStrategy;
|
|
|
71
71
|
class EkoDBClient {
|
|
72
72
|
constructor(config, apiKey) {
|
|
73
73
|
this.token = null;
|
|
74
|
+
this.tokenExpiry = 0;
|
|
74
75
|
this.rateLimitInfo = null;
|
|
75
76
|
// Support both old (baseURL, apiKey) and new (config object) signatures
|
|
76
77
|
if (typeof config === "string") {
|
|
@@ -133,20 +134,68 @@ class EkoDBClient {
|
|
|
133
134
|
}
|
|
134
135
|
const result = (await response.json());
|
|
135
136
|
this.token = result.token;
|
|
137
|
+
// Extract and cache JWT expiry for proactive refresh
|
|
138
|
+
const expiry = this.extractJWTExpiry(result.token);
|
|
139
|
+
this.tokenExpiry = expiry ?? Math.floor(Date.now() / 1000) + 3600; // fallback: 1 hour
|
|
136
140
|
}
|
|
137
141
|
/**
|
|
138
|
-
* Get
|
|
139
|
-
*
|
|
140
|
-
|
|
141
|
-
|
|
142
|
+
* Get a valid authentication token.
|
|
143
|
+
*
|
|
144
|
+
* Returns a cached token if it has more than 60s of validity remaining.
|
|
145
|
+
* Otherwise fetches a new one via refreshToken(). This means callers
|
|
146
|
+
* never need to handle token refresh themselves — every getToken() call
|
|
147
|
+
* returns a token that's valid for at least 60 more seconds.
|
|
148
|
+
*/
|
|
149
|
+
async getToken() {
|
|
150
|
+
if (this.token) {
|
|
151
|
+
const now = Math.floor(Date.now() / 1000);
|
|
152
|
+
if (now + 60 >= this.tokenExpiry) {
|
|
153
|
+
// Token is about to expire or already expired — refresh proactively
|
|
154
|
+
await this.refreshToken();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
// No token yet — fetch one
|
|
159
|
+
await this.refreshToken();
|
|
160
|
+
}
|
|
142
161
|
return this.token;
|
|
143
162
|
}
|
|
144
163
|
/**
|
|
145
|
-
* Clear the cached authentication token.
|
|
164
|
+
* Clear the cached authentication token and expiry.
|
|
146
165
|
* The next request will trigger a fresh token exchange.
|
|
147
166
|
*/
|
|
148
167
|
clearTokenCache() {
|
|
149
168
|
this.token = null;
|
|
169
|
+
this.tokenExpiry = 0;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Extract the `exp` claim from a JWT without verifying the signature.
|
|
173
|
+
* Returns the Unix timestamp (seconds) of expiry, or null if parsing fails.
|
|
174
|
+
*/
|
|
175
|
+
extractJWTExpiry(token) {
|
|
176
|
+
try {
|
|
177
|
+
const parts = token.split(".");
|
|
178
|
+
if (parts.length !== 3) {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
// Convert base64url to standard base64
|
|
182
|
+
let payload = parts[1];
|
|
183
|
+
payload = payload.replace(/-/g, "+").replace(/_/g, "/");
|
|
184
|
+
// Pad to multiple of 4
|
|
185
|
+
const pad = payload.length % 4;
|
|
186
|
+
if (pad) {
|
|
187
|
+
payload += "=".repeat(4 - pad);
|
|
188
|
+
}
|
|
189
|
+
const decoded = atob(payload);
|
|
190
|
+
const claims = JSON.parse(decoded);
|
|
191
|
+
if (typeof claims.exp === "number") {
|
|
192
|
+
return claims.exp;
|
|
193
|
+
}
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
150
199
|
}
|
|
151
200
|
/**
|
|
152
201
|
* Extract rate limit information from response headers
|
|
@@ -906,12 +955,198 @@ class EkoDBClient {
|
|
|
906
955
|
async rawCompletion(request) {
|
|
907
956
|
return this.makeRequest("POST", "/api/chat/complete", request, 0, true);
|
|
908
957
|
}
|
|
958
|
+
/**
|
|
959
|
+
* Stateless raw LLM completion via SSE streaming.
|
|
960
|
+
*
|
|
961
|
+
* Same as rawCompletion() but uses Server-Sent Events to keep the
|
|
962
|
+
* connection alive. Preferred for deployed instances where reverse proxies
|
|
963
|
+
* may kill idle HTTP connections before the LLM responds.
|
|
964
|
+
*/
|
|
965
|
+
async rawCompletionStream(request) {
|
|
966
|
+
let token = await this.getToken();
|
|
967
|
+
const url = `${this.baseURL}/api/chat/complete/stream`;
|
|
968
|
+
const response = await fetch(url, {
|
|
969
|
+
method: "POST",
|
|
970
|
+
headers: {
|
|
971
|
+
"Content-Type": "application/json",
|
|
972
|
+
Accept: "text/event-stream",
|
|
973
|
+
Authorization: `Bearer ${token}`,
|
|
974
|
+
},
|
|
975
|
+
body: JSON.stringify(request),
|
|
976
|
+
});
|
|
977
|
+
if (!response.ok) {
|
|
978
|
+
const body = await response.text();
|
|
979
|
+
throw new Error(`SSE raw completion failed (${response.status}): ${body}`);
|
|
980
|
+
}
|
|
981
|
+
const body = await response.text();
|
|
982
|
+
let content = "";
|
|
983
|
+
let lastError = null;
|
|
984
|
+
for (const line of body.split("\n")) {
|
|
985
|
+
if (line.startsWith("data:")) {
|
|
986
|
+
const dataStr = line.slice(5).trim();
|
|
987
|
+
if (!dataStr)
|
|
988
|
+
continue;
|
|
989
|
+
try {
|
|
990
|
+
const eventData = JSON.parse(dataStr);
|
|
991
|
+
if (eventData.token)
|
|
992
|
+
content += eventData.token;
|
|
993
|
+
if (eventData.content)
|
|
994
|
+
content = eventData.content;
|
|
995
|
+
if (eventData.error)
|
|
996
|
+
lastError = eventData.error;
|
|
997
|
+
}
|
|
998
|
+
catch {
|
|
999
|
+
// skip malformed SSE data
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
if (lastError)
|
|
1004
|
+
throw new Error(lastError);
|
|
1005
|
+
return { content };
|
|
1006
|
+
}
|
|
1007
|
+
/**
|
|
1008
|
+
* Stateless raw LLM completion via SSE streaming with token-level progress.
|
|
1009
|
+
*
|
|
1010
|
+
* Same as rawCompletionStream() but invokes `onToken` with each token as it
|
|
1011
|
+
* arrives, allowing callers to show real-time progress.
|
|
1012
|
+
*/
|
|
1013
|
+
async rawCompletionStreamWithProgress(request, onToken) {
|
|
1014
|
+
let token = await this.getToken();
|
|
1015
|
+
const url = `${this.baseURL}/api/chat/complete/stream`;
|
|
1016
|
+
const response = await fetch(url, {
|
|
1017
|
+
method: "POST",
|
|
1018
|
+
headers: {
|
|
1019
|
+
"Content-Type": "application/json",
|
|
1020
|
+
Accept: "text/event-stream",
|
|
1021
|
+
Authorization: `Bearer ${token}`,
|
|
1022
|
+
},
|
|
1023
|
+
body: JSON.stringify(request),
|
|
1024
|
+
});
|
|
1025
|
+
if (!response.ok) {
|
|
1026
|
+
const body = await response.text();
|
|
1027
|
+
throw new Error(`SSE raw completion failed (${response.status}): ${body}`);
|
|
1028
|
+
}
|
|
1029
|
+
const body = await response.text();
|
|
1030
|
+
let content = "";
|
|
1031
|
+
let lastError = null;
|
|
1032
|
+
for (const line of body.split("\n")) {
|
|
1033
|
+
if (line.startsWith("data:")) {
|
|
1034
|
+
const dataStr = line.slice(5).trim();
|
|
1035
|
+
if (!dataStr)
|
|
1036
|
+
continue;
|
|
1037
|
+
try {
|
|
1038
|
+
const eventData = JSON.parse(dataStr);
|
|
1039
|
+
if (eventData.token) {
|
|
1040
|
+
content += eventData.token;
|
|
1041
|
+
onToken(eventData.token);
|
|
1042
|
+
}
|
|
1043
|
+
if (eventData.content)
|
|
1044
|
+
content = eventData.content;
|
|
1045
|
+
if (eventData.error)
|
|
1046
|
+
lastError = eventData.error;
|
|
1047
|
+
}
|
|
1048
|
+
catch {
|
|
1049
|
+
// skip malformed SSE data
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
if (lastError)
|
|
1054
|
+
throw new Error(lastError);
|
|
1055
|
+
return { content };
|
|
1056
|
+
}
|
|
909
1057
|
/**
|
|
910
1058
|
* Send a message in an existing chat session
|
|
911
1059
|
*/
|
|
912
1060
|
async chatMessage(sessionId, request) {
|
|
913
1061
|
return this.makeRequest("POST", `/api/chat/${sessionId}/messages`, request, 0, true);
|
|
914
1062
|
}
|
|
1063
|
+
/**
|
|
1064
|
+
* Send a message in an existing chat session via SSE streaming.
|
|
1065
|
+
*
|
|
1066
|
+
* Returns an EventStream that emits ChatStreamEvent objects as they arrive:
|
|
1067
|
+
* - `{ type: "chunk", content: "..." }` for each token
|
|
1068
|
+
* - `{ type: "end", messageId, executionTimeMs, tokenUsage?, contextWindow? }` when complete
|
|
1069
|
+
* - `{ type: "error", error: "..." }` on failure
|
|
1070
|
+
*
|
|
1071
|
+
* Preferred over chatMessage() for long-running responses where reverse
|
|
1072
|
+
* proxies may kill idle HTTP connections before the LLM responds.
|
|
1073
|
+
*/
|
|
1074
|
+
chatMessageStream(chatId, request) {
|
|
1075
|
+
const stream = new EventStream();
|
|
1076
|
+
(async () => {
|
|
1077
|
+
try {
|
|
1078
|
+
let token = this.getToken();
|
|
1079
|
+
if (!token) {
|
|
1080
|
+
await this.refreshToken();
|
|
1081
|
+
token = this.getToken();
|
|
1082
|
+
}
|
|
1083
|
+
const url = `${this.baseURL}/api/chat/${chatId}/messages/stream`;
|
|
1084
|
+
const response = await fetch(url, {
|
|
1085
|
+
method: "POST",
|
|
1086
|
+
headers: {
|
|
1087
|
+
"Content-Type": "application/json",
|
|
1088
|
+
Accept: "text/event-stream",
|
|
1089
|
+
Authorization: `Bearer ${token}`,
|
|
1090
|
+
},
|
|
1091
|
+
body: JSON.stringify(request),
|
|
1092
|
+
});
|
|
1093
|
+
if (!response.ok) {
|
|
1094
|
+
const body = await response.text();
|
|
1095
|
+
stream.emit("event", {
|
|
1096
|
+
type: "error",
|
|
1097
|
+
error: `SSE chat message stream failed (${response.status}): ${body}`,
|
|
1098
|
+
});
|
|
1099
|
+
stream.close();
|
|
1100
|
+
return;
|
|
1101
|
+
}
|
|
1102
|
+
const body = await response.text();
|
|
1103
|
+
for (const line of body.split("\n")) {
|
|
1104
|
+
if (!line.startsWith("data:"))
|
|
1105
|
+
continue;
|
|
1106
|
+
const dataStr = line.slice(5).trim();
|
|
1107
|
+
if (!dataStr)
|
|
1108
|
+
continue;
|
|
1109
|
+
try {
|
|
1110
|
+
const eventData = JSON.parse(dataStr);
|
|
1111
|
+
if (eventData.error) {
|
|
1112
|
+
stream.emit("event", {
|
|
1113
|
+
type: "error",
|
|
1114
|
+
error: eventData.error,
|
|
1115
|
+
});
|
|
1116
|
+
}
|
|
1117
|
+
else if (eventData.content && eventData.message_id) {
|
|
1118
|
+
// Done event — has full content + message_id
|
|
1119
|
+
stream.emit("event", {
|
|
1120
|
+
type: "end",
|
|
1121
|
+
messageId: eventData.message_id,
|
|
1122
|
+
executionTimeMs: eventData.execution_time_ms ?? 0,
|
|
1123
|
+
tokenUsage: eventData.token_usage,
|
|
1124
|
+
contextWindow: eventData.context_window,
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
1127
|
+
else if (eventData.token) {
|
|
1128
|
+
stream.emit("event", {
|
|
1129
|
+
type: "chunk",
|
|
1130
|
+
content: eventData.token,
|
|
1131
|
+
});
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
catch {
|
|
1135
|
+
// skip malformed SSE data
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
stream.close();
|
|
1139
|
+
}
|
|
1140
|
+
catch (err) {
|
|
1141
|
+
stream.emit("event", {
|
|
1142
|
+
type: "error",
|
|
1143
|
+
error: err.message ?? String(err),
|
|
1144
|
+
});
|
|
1145
|
+
stream.close();
|
|
1146
|
+
}
|
|
1147
|
+
})();
|
|
1148
|
+
return stream;
|
|
1149
|
+
}
|
|
915
1150
|
/**
|
|
916
1151
|
* Get a chat session by ID
|
|
917
1152
|
*/
|
|
@@ -1116,6 +1351,204 @@ class EkoDBClient {
|
|
|
1116
1351
|
await this.makeRequest("DELETE", `/api/functions/${encodeURIComponent(label)}`, undefined, 0, true);
|
|
1117
1352
|
}
|
|
1118
1353
|
// ========================================================================
|
|
1354
|
+
// GOAL API
|
|
1355
|
+
// ========================================================================
|
|
1356
|
+
/** Create a new goal */
|
|
1357
|
+
async goalCreate(data) {
|
|
1358
|
+
return this.makeRequest("POST", "/api/chat/goals", data, 0, true);
|
|
1359
|
+
}
|
|
1360
|
+
/** List all goals */
|
|
1361
|
+
async goalList() {
|
|
1362
|
+
return this.makeRequest("GET", "/api/chat/goals", undefined, 0, true);
|
|
1363
|
+
}
|
|
1364
|
+
/** Get a goal by ID */
|
|
1365
|
+
async goalGet(id) {
|
|
1366
|
+
return this.makeRequest("GET", `/api/chat/goals/${encodeURIComponent(id)}`, undefined, 0, true);
|
|
1367
|
+
}
|
|
1368
|
+
/** Update a goal by ID */
|
|
1369
|
+
async goalUpdate(id, data) {
|
|
1370
|
+
return this.makeRequest("PUT", `/api/chat/goals/${encodeURIComponent(id)}`, data, 0, true);
|
|
1371
|
+
}
|
|
1372
|
+
/** Delete a goal by ID */
|
|
1373
|
+
async goalDelete(id) {
|
|
1374
|
+
await this.makeRequest("DELETE", `/api/chat/goals/${encodeURIComponent(id)}`, undefined, 0, true);
|
|
1375
|
+
}
|
|
1376
|
+
// ── Goal Templates ──
|
|
1377
|
+
/** Create a new goal template */
|
|
1378
|
+
async goalTemplateCreate(data) {
|
|
1379
|
+
return this.makeRequest("POST", "/api/chat/goal-templates", data, 0, true);
|
|
1380
|
+
}
|
|
1381
|
+
/** List all goal templates */
|
|
1382
|
+
async goalTemplateList() {
|
|
1383
|
+
return this.makeRequest("GET", "/api/chat/goal-templates", undefined, 0, true);
|
|
1384
|
+
}
|
|
1385
|
+
/** Get a goal template by ID */
|
|
1386
|
+
async goalTemplateGet(id) {
|
|
1387
|
+
return this.makeRequest("GET", `/api/chat/goal-templates/${encodeURIComponent(id)}`, undefined, 0, true);
|
|
1388
|
+
}
|
|
1389
|
+
/** Update a goal template by ID */
|
|
1390
|
+
async goalTemplateUpdate(id, data) {
|
|
1391
|
+
return this.makeRequest("PUT", `/api/chat/goal-templates/${encodeURIComponent(id)}`, data, 0, true);
|
|
1392
|
+
}
|
|
1393
|
+
/** Delete a goal template by ID */
|
|
1394
|
+
async goalTemplateDelete(id) {
|
|
1395
|
+
await this.makeRequest("DELETE", `/api/chat/goal-templates/${encodeURIComponent(id)}`, undefined, 0, true);
|
|
1396
|
+
}
|
|
1397
|
+
/** Search goals */
|
|
1398
|
+
async goalSearch(query) {
|
|
1399
|
+
const params = new URLSearchParams({ q: query });
|
|
1400
|
+
return this.makeRequest("GET", `/api/chat/goals/search?${params}`, undefined, 0, true);
|
|
1401
|
+
}
|
|
1402
|
+
/** Mark a goal as complete (status -> pending_review) */
|
|
1403
|
+
async goalComplete(id, data) {
|
|
1404
|
+
return this.makeRequest("POST", `/api/chat/goals/${encodeURIComponent(id)}/complete`, data, 0, true);
|
|
1405
|
+
}
|
|
1406
|
+
/** Approve a goal (status -> in_progress) */
|
|
1407
|
+
async goalApprove(id) {
|
|
1408
|
+
return this.makeRequest("POST", `/api/chat/goals/${encodeURIComponent(id)}/approve`, undefined, 0, true);
|
|
1409
|
+
}
|
|
1410
|
+
/** Reject a goal (status -> failed) */
|
|
1411
|
+
async goalReject(id, data) {
|
|
1412
|
+
return this.makeRequest("POST", `/api/chat/goals/${encodeURIComponent(id)}/reject`, data, 0, true);
|
|
1413
|
+
}
|
|
1414
|
+
/** Start a goal step (status -> in_progress) */
|
|
1415
|
+
async goalStepStart(id, stepIndex) {
|
|
1416
|
+
return this.makeRequest("POST", `/api/chat/goals/${encodeURIComponent(id)}/steps/${stepIndex}/start`, undefined, 0, true);
|
|
1417
|
+
}
|
|
1418
|
+
/** Complete a goal step with result */
|
|
1419
|
+
async goalStepComplete(id, stepIndex, data) {
|
|
1420
|
+
return this.makeRequest("POST", `/api/chat/goals/${encodeURIComponent(id)}/steps/${stepIndex}/complete`, data, 0, true);
|
|
1421
|
+
}
|
|
1422
|
+
/** Fail a goal step with error */
|
|
1423
|
+
async goalStepFail(id, stepIndex, data) {
|
|
1424
|
+
return this.makeRequest("POST", `/api/chat/goals/${encodeURIComponent(id)}/steps/${stepIndex}/fail`, data, 0, true);
|
|
1425
|
+
}
|
|
1426
|
+
// ========================================================================
|
|
1427
|
+
// TASK API
|
|
1428
|
+
// ========================================================================
|
|
1429
|
+
/** Create a new scheduled task */
|
|
1430
|
+
async taskCreate(data) {
|
|
1431
|
+
return this.makeRequest("POST", "/api/chat/tasks", data, 0, true);
|
|
1432
|
+
}
|
|
1433
|
+
/** List all scheduled tasks */
|
|
1434
|
+
async taskList() {
|
|
1435
|
+
return this.makeRequest("GET", "/api/chat/tasks", undefined, 0, true);
|
|
1436
|
+
}
|
|
1437
|
+
/** Get a task by ID */
|
|
1438
|
+
async taskGet(id) {
|
|
1439
|
+
return this.makeRequest("GET", `/api/chat/tasks/${encodeURIComponent(id)}`, undefined, 0, true);
|
|
1440
|
+
}
|
|
1441
|
+
/** Update a task by ID */
|
|
1442
|
+
async taskUpdate(id, data) {
|
|
1443
|
+
return this.makeRequest("PUT", `/api/chat/tasks/${encodeURIComponent(id)}`, data, 0, true);
|
|
1444
|
+
}
|
|
1445
|
+
/** Delete a task by ID */
|
|
1446
|
+
async taskDelete(id) {
|
|
1447
|
+
await this.makeRequest("DELETE", `/api/chat/tasks/${encodeURIComponent(id)}`, undefined, 0, true);
|
|
1448
|
+
}
|
|
1449
|
+
/** Get tasks that are due at the given time */
|
|
1450
|
+
async taskDue(now) {
|
|
1451
|
+
const params = new URLSearchParams({ now });
|
|
1452
|
+
return this.makeRequest("GET", `/api/chat/tasks/due?${params}`, undefined, 0, true);
|
|
1453
|
+
}
|
|
1454
|
+
/** Start a task (status -> running) */
|
|
1455
|
+
async taskStart(id) {
|
|
1456
|
+
return this.makeRequest("POST", `/api/chat/tasks/${encodeURIComponent(id)}/start`, undefined, 0, true);
|
|
1457
|
+
}
|
|
1458
|
+
/** Mark a task as succeeded */
|
|
1459
|
+
async taskSucceed(id, data) {
|
|
1460
|
+
return this.makeRequest("POST", `/api/chat/tasks/${encodeURIComponent(id)}/succeed`, data, 0, true);
|
|
1461
|
+
}
|
|
1462
|
+
/** Mark a task as failed */
|
|
1463
|
+
async taskFail(id, data) {
|
|
1464
|
+
return this.makeRequest("POST", `/api/chat/tasks/${encodeURIComponent(id)}/fail`, data, 0, true);
|
|
1465
|
+
}
|
|
1466
|
+
/** Pause a task */
|
|
1467
|
+
async taskPause(id) {
|
|
1468
|
+
return this.makeRequest("POST", `/api/chat/tasks/${encodeURIComponent(id)}/pause`, undefined, 0, true);
|
|
1469
|
+
}
|
|
1470
|
+
/** Resume a paused task */
|
|
1471
|
+
async taskResume(id, data) {
|
|
1472
|
+
return this.makeRequest("POST", `/api/chat/tasks/${encodeURIComponent(id)}/resume`, data, 0, true);
|
|
1473
|
+
}
|
|
1474
|
+
// ========================================================================
|
|
1475
|
+
// AGENT API
|
|
1476
|
+
// ========================================================================
|
|
1477
|
+
/** Create a new agent */
|
|
1478
|
+
async agentCreate(data) {
|
|
1479
|
+
return this.makeRequest("POST", "/api/chat/agents", data, 0, true);
|
|
1480
|
+
}
|
|
1481
|
+
/** List all agents */
|
|
1482
|
+
async agentList() {
|
|
1483
|
+
return this.makeRequest("GET", "/api/chat/agents", undefined, 0, true);
|
|
1484
|
+
}
|
|
1485
|
+
/** Get an agent by ID */
|
|
1486
|
+
async agentGet(id) {
|
|
1487
|
+
return this.makeRequest("GET", `/api/chat/agents/${encodeURIComponent(id)}`, undefined, 0, true);
|
|
1488
|
+
}
|
|
1489
|
+
/** Get an agent by name */
|
|
1490
|
+
async agentGetByName(name) {
|
|
1491
|
+
return this.makeRequest("GET", `/api/chat/agents/by-name/${encodeURIComponent(name)}`, undefined, 0, true);
|
|
1492
|
+
}
|
|
1493
|
+
/** Update an agent by ID */
|
|
1494
|
+
async agentUpdate(id, data) {
|
|
1495
|
+
return this.makeRequest("PUT", `/api/chat/agents/${encodeURIComponent(id)}`, data, 0, true);
|
|
1496
|
+
}
|
|
1497
|
+
/** Delete an agent by ID */
|
|
1498
|
+
async agentDelete(id) {
|
|
1499
|
+
await this.makeRequest("DELETE", `/api/chat/agents/${encodeURIComponent(id)}`, undefined, 0, true);
|
|
1500
|
+
}
|
|
1501
|
+
/** Get agents by deployment ID */
|
|
1502
|
+
async agentsByDeployment(deploymentId) {
|
|
1503
|
+
return this.makeRequest("GET", `/api/chat/agents/by-deployment/${encodeURIComponent(deploymentId)}`, undefined, 0, true);
|
|
1504
|
+
}
|
|
1505
|
+
// ========================================================================
|
|
1506
|
+
// KV DOCUMENT LINKING
|
|
1507
|
+
// ========================================================================
|
|
1508
|
+
/** Get documents linked to a KV key */
|
|
1509
|
+
async kvGetLinks(key) {
|
|
1510
|
+
return this.makeRequest("GET", `/api/kv/links/${encodeURIComponent(key)}`, undefined, 0, true);
|
|
1511
|
+
}
|
|
1512
|
+
/** Link a document to a KV key */
|
|
1513
|
+
async kvLink(key, collection, documentId) {
|
|
1514
|
+
return this.makeRequest("POST", `/api/kv/link`, { key, collection, document_id: documentId }, 0, true);
|
|
1515
|
+
}
|
|
1516
|
+
/** Unlink a document from a KV key */
|
|
1517
|
+
async kvUnlink(key, collection, documentId) {
|
|
1518
|
+
return this.makeRequest("POST", `/api/kv/unlink`, { key, collection, document_id: documentId }, 0, true);
|
|
1519
|
+
}
|
|
1520
|
+
// ========================================================================
|
|
1521
|
+
// SCHEDULE MANAGEMENT
|
|
1522
|
+
// ========================================================================
|
|
1523
|
+
/** Create a new schedule */
|
|
1524
|
+
async createSchedule(data) {
|
|
1525
|
+
return this.makeRequest("POST", `/api/schedules`, data, 0, true);
|
|
1526
|
+
}
|
|
1527
|
+
/** List all schedules */
|
|
1528
|
+
async listSchedules() {
|
|
1529
|
+
return this.makeRequest("GET", `/api/schedules`, undefined, 0, true);
|
|
1530
|
+
}
|
|
1531
|
+
/** Get a schedule by ID */
|
|
1532
|
+
async getSchedule(id) {
|
|
1533
|
+
return this.makeRequest("GET", `/api/schedules/${encodeURIComponent(id)}`, undefined, 0, true);
|
|
1534
|
+
}
|
|
1535
|
+
/** Update a schedule */
|
|
1536
|
+
async updateSchedule(id, data) {
|
|
1537
|
+
return this.makeRequest("PUT", `/api/schedules/${encodeURIComponent(id)}`, data, 0, true);
|
|
1538
|
+
}
|
|
1539
|
+
/** Delete a schedule */
|
|
1540
|
+
async deleteSchedule(id) {
|
|
1541
|
+
await this.makeRequest("DELETE", `/api/schedules/${encodeURIComponent(id)}`, undefined, 0, true);
|
|
1542
|
+
}
|
|
1543
|
+
/** Pause a schedule */
|
|
1544
|
+
async pauseSchedule(id) {
|
|
1545
|
+
return this.makeRequest("POST", `/api/schedules/${encodeURIComponent(id)}/pause`, undefined, 0, true);
|
|
1546
|
+
}
|
|
1547
|
+
/** Resume a schedule */
|
|
1548
|
+
async resumeSchedule(id) {
|
|
1549
|
+
return this.makeRequest("POST", `/api/schedules/${encodeURIComponent(id)}/resume`, undefined, 0, true);
|
|
1550
|
+
}
|
|
1551
|
+
// ========================================================================
|
|
1119
1552
|
// COLLECTION UTILITIES
|
|
1120
1553
|
// ========================================================================
|
|
1121
1554
|
/**
|
|
@@ -1385,7 +1818,12 @@ class WebSocketClient {
|
|
|
1385
1818
|
switch (msg.type) {
|
|
1386
1819
|
case "Success":
|
|
1387
1820
|
case "Error": {
|
|
1388
|
-
|
|
1821
|
+
// Try messageId from top-level, then from payload
|
|
1822
|
+
const messageId = msg.messageId ||
|
|
1823
|
+
msg.message_id ||
|
|
1824
|
+
msg.payload?.message_id ||
|
|
1825
|
+
msg.payload?.messageId;
|
|
1826
|
+
let matched = false;
|
|
1389
1827
|
if (messageId && this.pendingRequests.has(messageId)) {
|
|
1390
1828
|
const pending = this.pendingRequests.get(messageId);
|
|
1391
1829
|
this.pendingRequests.delete(messageId);
|
|
@@ -1395,8 +1833,9 @@ class WebSocketClient {
|
|
|
1395
1833
|
else {
|
|
1396
1834
|
pending.resolve(msg.payload);
|
|
1397
1835
|
}
|
|
1836
|
+
matched = true;
|
|
1398
1837
|
}
|
|
1399
|
-
|
|
1838
|
+
if (!matched && this.registerToolsAck) {
|
|
1400
1839
|
const ack = this.registerToolsAck;
|
|
1401
1840
|
this.registerToolsAck = null;
|
|
1402
1841
|
if (msg.type === "Error") {
|
|
@@ -1405,6 +1844,21 @@ class WebSocketClient {
|
|
|
1405
1844
|
else {
|
|
1406
1845
|
ack.resolve(msg.payload);
|
|
1407
1846
|
}
|
|
1847
|
+
matched = true;
|
|
1848
|
+
}
|
|
1849
|
+
// Server doesn't echo messageId — if there's exactly one pending
|
|
1850
|
+
// request, deliver the response to it (sequential request/response).
|
|
1851
|
+
if (!matched && this.pendingRequests.size === 1) {
|
|
1852
|
+
const entry = this.pendingRequests.entries().next().value;
|
|
1853
|
+
const key = entry[0];
|
|
1854
|
+
const pending = entry[1];
|
|
1855
|
+
this.pendingRequests.delete(key);
|
|
1856
|
+
if (msg.type === "Error") {
|
|
1857
|
+
pending.reject(new Error(msg.message || "Unknown error"));
|
|
1858
|
+
}
|
|
1859
|
+
else {
|
|
1860
|
+
pending.resolve(msg.payload);
|
|
1861
|
+
}
|
|
1408
1862
|
}
|
|
1409
1863
|
break;
|
|
1410
1864
|
}
|
|
@@ -1445,6 +1899,7 @@ class WebSocketClient {
|
|
|
1445
1899
|
tokenUsage: msg.payload.token_usage || msg.payload.tokenUsage,
|
|
1446
1900
|
toolCallHistory: msg.payload.tool_call_history || msg.payload.toolCallHistory,
|
|
1447
1901
|
executionTimeMs: msg.payload.execution_time_ms || msg.payload.executionTimeMs || 0,
|
|
1902
|
+
contextWindow: msg.payload.context_window || msg.payload.contextWindow,
|
|
1448
1903
|
});
|
|
1449
1904
|
this.chatStreams.delete(chatId);
|
|
1450
1905
|
stream.close();
|
|
@@ -1604,6 +2059,30 @@ class WebSocketClient {
|
|
|
1604
2059
|
};
|
|
1605
2060
|
this.ws.send(JSON.stringify(request));
|
|
1606
2061
|
}
|
|
2062
|
+
/**
|
|
2063
|
+
* Stateless raw LLM completion via WebSocket.
|
|
2064
|
+
*
|
|
2065
|
+
* Sends a RawComplete message and waits for the Success response.
|
|
2066
|
+
* Preferred over HTTP for deployed instances: the persistent WSS
|
|
2067
|
+
* connection is already authenticated and won't be killed by reverse
|
|
2068
|
+
* proxy timeouts.
|
|
2069
|
+
*/
|
|
2070
|
+
async rawCompletion(request) {
|
|
2071
|
+
await this.ensureConnected();
|
|
2072
|
+
const messageId = this.genMessageId();
|
|
2073
|
+
const payload = await this.sendRequest({
|
|
2074
|
+
type: "RawComplete",
|
|
2075
|
+
messageId,
|
|
2076
|
+
payload: {
|
|
2077
|
+
system_prompt: request.system_prompt,
|
|
2078
|
+
message: request.message,
|
|
2079
|
+
...(request.provider && { provider: request.provider }),
|
|
2080
|
+
...(request.model && { model: request.model }),
|
|
2081
|
+
...(request.max_tokens != null && { max_tokens: request.max_tokens }),
|
|
2082
|
+
},
|
|
2083
|
+
});
|
|
2084
|
+
return { content: payload?.data?.content || "" };
|
|
2085
|
+
}
|
|
1607
2086
|
/**
|
|
1608
2087
|
* Close the WebSocket connection.
|
|
1609
2088
|
*/
|