@ekodb/ekodb-client 0.25.0 → 0.26.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.
- package/README.md +5 -2
- package/dist/client.d.ts +79 -6
- package/dist/client.js +104 -55
- package/dist/client.test.js +227 -2
- package/dist/functions.d.ts +19 -16
- package/dist/functions.js +27 -8
- package/dist/functions.test.js +42 -0
- package/dist/index.d.ts +1 -1
- package/dist/search.d.ts +1 -1
- package/dist/websocket.test.js +66 -0
- package/package.json +3 -3
- package/src/client.test.ts +281 -2
- package/src/client.ts +192 -32
- package/src/functions.test.ts +54 -0
- package/src/functions.ts +30 -12
- package/src/index.ts +2 -0
- package/src/search.ts +1 -1
- package/src/websocket.test.ts +90 -0
- package/tsconfig.json +2 -2
package/README.md
CHANGED
|
@@ -308,8 +308,11 @@ const joinResults = await client.find("users", multiQuery);
|
|
|
308
308
|
|
|
309
309
|
#### Chat Models
|
|
310
310
|
|
|
311
|
-
- `getChatModels(): Promise<
|
|
312
|
-
|
|
311
|
+
- `getChatModels(): Promise<ChatModels>` - Get all available chat models by
|
|
312
|
+
provider (`openai`, `anthropic`, `perplexity`, `gemini`), plus a per-provider
|
|
313
|
+
`providers` status map (`ok`, `not_configured`, `auth_failed`,
|
|
314
|
+
`permission_denied`, `billing`, `rate_limited`, `unavailable`, `unreachable`,
|
|
315
|
+
`request_error`) so a rejected key is distinguishable from a missing one
|
|
313
316
|
- `getChatModel(provider: string): Promise<string[]>` - Get models for a
|
|
314
317
|
specific provider
|
|
315
318
|
|
package/dist/client.d.ts
CHANGED
|
@@ -326,12 +326,45 @@ export interface MergeSessionsRequest {
|
|
|
326
326
|
bypass_ripple?: boolean;
|
|
327
327
|
}
|
|
328
328
|
/**
|
|
329
|
-
*
|
|
329
|
+
* A provider's state on `GET /api/chat_models`. The union lists the states
|
|
330
|
+
* this client knows; the `string` escape keeps a newer server's status from
|
|
331
|
+
* failing to type-check.
|
|
332
|
+
*/
|
|
333
|
+
export type ChatProviderState = "ok" | "not_configured" | "auth_failed" | "permission_denied" | "billing" | "rate_limited" | "unavailable" | "unreachable" | "request_error" | (string & {});
|
|
334
|
+
/**
|
|
335
|
+
* One provider's row in `ChatModels.providers`.
|
|
336
|
+
*/
|
|
337
|
+
export interface ChatProviderStatus {
|
|
338
|
+
status: ChatProviderState;
|
|
339
|
+
/**
|
|
340
|
+
* True when the status is the provider's own answer about the configured
|
|
341
|
+
* key. A 5xx, a refused connection, or a missing key says nothing about it.
|
|
342
|
+
*/
|
|
343
|
+
verified: boolean;
|
|
344
|
+
/** The provider's own HTTP status, when it answered. */
|
|
345
|
+
http_status?: number;
|
|
346
|
+
/** The provider's own message, when it answered. */
|
|
347
|
+
message?: string;
|
|
348
|
+
/** How many models were listed, when the status is `ok`. */
|
|
349
|
+
model_count?: number;
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Available chat models by provider, and why each list looks the way it does.
|
|
330
353
|
*/
|
|
331
354
|
export interface ChatModels {
|
|
332
355
|
openai: string[];
|
|
333
356
|
anthropic: string[];
|
|
334
357
|
perplexity: string[];
|
|
358
|
+
/** Google Gemini models. Absent from a server that predates the field. */
|
|
359
|
+
gemini?: string[];
|
|
360
|
+
/**
|
|
361
|
+
* Per-provider status keyed by provider name. A rejected key reports
|
|
362
|
+
* `auth_failed` where a missing one reports `not_configured`, so an empty
|
|
363
|
+
* list is never ambiguous. Absent from a server that predates the map.
|
|
364
|
+
*/
|
|
365
|
+
providers?: {
|
|
366
|
+
[provider: string]: ChatProviderStatus;
|
|
367
|
+
};
|
|
335
368
|
}
|
|
336
369
|
/**
|
|
337
370
|
* Request to compact a chat session's history on demand.
|
|
@@ -1213,9 +1246,21 @@ export declare class EkoDBClient {
|
|
|
1213
1246
|
agentsByDeployment(deploymentId: string): Promise<Record>;
|
|
1214
1247
|
/** Get documents linked to a KV key */
|
|
1215
1248
|
kvGetLinks(key: string): Promise<Record>;
|
|
1216
|
-
/**
|
|
1217
|
-
|
|
1218
|
-
|
|
1249
|
+
/**
|
|
1250
|
+
* Link a document to a KV key.
|
|
1251
|
+
*
|
|
1252
|
+
* The identifying triple goes in the path; the body carries the optional
|
|
1253
|
+
* link payload (`keys`, `field_path`, `metadata`), and an empty object means
|
|
1254
|
+
* "no extra link data".
|
|
1255
|
+
*/
|
|
1256
|
+
kvLink(key: string, collection: string, documentId: string, linkData?: {
|
|
1257
|
+
keys?: string[];
|
|
1258
|
+
field_path?: string;
|
|
1259
|
+
metadata?: {
|
|
1260
|
+
[key: string]: string;
|
|
1261
|
+
};
|
|
1262
|
+
}): Promise<Record>;
|
|
1263
|
+
/** Unlink a document from a KV key. DELETE, with the triple in the path. */
|
|
1219
1264
|
kvUnlink(key: string, collection: string, documentId: string): Promise<Record>;
|
|
1220
1265
|
/** Create a new schedule */
|
|
1221
1266
|
createSchedule(data: Record): Promise<Record>;
|
|
@@ -1227,10 +1272,25 @@ export declare class EkoDBClient {
|
|
|
1227
1272
|
updateSchedule(id: string, data: Record): Promise<Record>;
|
|
1228
1273
|
/** Delete a schedule */
|
|
1229
1274
|
deleteSchedule(id: string): Promise<void>;
|
|
1230
|
-
/**
|
|
1275
|
+
/**
|
|
1276
|
+
* Pause a schedule.
|
|
1277
|
+
*
|
|
1278
|
+
* There is no `/pause` endpoint — pausing is a partial update of the
|
|
1279
|
+
* schedule's `enabled` flag. This previously POSTed to
|
|
1280
|
+
* `/api/schedules/{id}/pause`, which has never existed and always 404'd.
|
|
1281
|
+
*/
|
|
1231
1282
|
pauseSchedule(id: string): Promise<Record>;
|
|
1232
|
-
/**
|
|
1283
|
+
/**
|
|
1284
|
+
* Resume a paused schedule. See {@link pauseSchedule} for why this is an
|
|
1285
|
+
* update rather than its own endpoint.
|
|
1286
|
+
*/
|
|
1233
1287
|
resumeSchedule(id: string): Promise<Record>;
|
|
1288
|
+
/**
|
|
1289
|
+
* Shared implementation for pause/resume: a partial update carrying only
|
|
1290
|
+
* `enabled`. The server recomputes the next execution time when `enabled`
|
|
1291
|
+
* changes, so nothing else needs sending.
|
|
1292
|
+
*/
|
|
1293
|
+
private setScheduleEnabled;
|
|
1234
1294
|
/**
|
|
1235
1295
|
* Check if a collection exists
|
|
1236
1296
|
* @param collection - Collection name to check
|
|
@@ -1389,6 +1449,19 @@ export type ChatStreamEvent = {
|
|
|
1389
1449
|
} | {
|
|
1390
1450
|
type: "error";
|
|
1391
1451
|
error: string;
|
|
1452
|
+
/**
|
|
1453
|
+
* The provider-failure classification (`provider_auth_failed`,
|
|
1454
|
+
* `provider_permission_denied`, `provider_billing`,
|
|
1455
|
+
* `provider_rate_limited`, `provider_unavailable`,
|
|
1456
|
+
* `provider_unreachable`, `provider_not_configured`,
|
|
1457
|
+
* `provider_request_error`), when the failure was the LLM provider's
|
|
1458
|
+
* answer. Absent for a transport failure or a plain server error.
|
|
1459
|
+
*/
|
|
1460
|
+
errorKind?: string;
|
|
1461
|
+
provider?: string;
|
|
1462
|
+
/** The provider's own HTTP status. */
|
|
1463
|
+
providerStatus?: number;
|
|
1464
|
+
retryAfterSecs?: number;
|
|
1392
1465
|
};
|
|
1393
1466
|
/** Definition for a client-side tool the LLM can call. */
|
|
1394
1467
|
export interface ClientToolDefinition {
|
package/dist/client.js
CHANGED
|
@@ -2,39 +2,6 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* ekoDB TypeScript Client
|
|
4
4
|
*/
|
|
5
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
-
if (k2 === undefined) k2 = k;
|
|
7
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
-
}
|
|
11
|
-
Object.defineProperty(o, k2, desc);
|
|
12
|
-
}) : (function(o, m, k, k2) {
|
|
13
|
-
if (k2 === undefined) k2 = k;
|
|
14
|
-
o[k2] = m[k];
|
|
15
|
-
}));
|
|
16
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
17
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
18
|
-
}) : function(o, v) {
|
|
19
|
-
o["default"] = v;
|
|
20
|
-
});
|
|
21
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
22
|
-
var ownKeys = function(o) {
|
|
23
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
24
|
-
var ar = [];
|
|
25
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
26
|
-
return ar;
|
|
27
|
-
};
|
|
28
|
-
return ownKeys(o);
|
|
29
|
-
};
|
|
30
|
-
return function (mod) {
|
|
31
|
-
if (mod && mod.__esModule) return mod;
|
|
32
|
-
var result = {};
|
|
33
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
34
|
-
__setModuleDefault(result, mod);
|
|
35
|
-
return result;
|
|
36
|
-
};
|
|
37
|
-
})();
|
|
38
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
6
|
exports.WebSocketClient = exports.SchemaCache = exports.EventStream = exports.EkoDBClient = exports.MergeStrategy = exports.HealthStatus = exports.HealthUnknown = exports.HealthDegraded = exports.HealthOK = exports.RateLimitError = exports.DEFAULT_REQUEST_TIMEOUT_MS = exports.SerializationFormat = void 0;
|
|
40
7
|
exports.parseHealthStatus = parseHealthStatus;
|
|
@@ -360,11 +327,11 @@ class EkoDBClient {
|
|
|
360
327
|
// ONLY these operations support MessagePack
|
|
361
328
|
const msgpackPaths = [
|
|
362
329
|
"/api/insert/",
|
|
363
|
-
"/api/
|
|
330
|
+
"/api/batch/insert/",
|
|
364
331
|
"/api/update/",
|
|
365
|
-
"/api/
|
|
332
|
+
"/api/batch/update/",
|
|
366
333
|
"/api/delete/",
|
|
367
|
-
"/api/
|
|
334
|
+
"/api/batch/delete/",
|
|
368
335
|
];
|
|
369
336
|
// Check if path starts with any MessagePack-supported operation
|
|
370
337
|
for (const prefix of msgpackPaths) {
|
|
@@ -1454,7 +1421,22 @@ class EkoDBClient {
|
|
|
1454
1421
|
stream.close();
|
|
1455
1422
|
return;
|
|
1456
1423
|
}
|
|
1424
|
+
// The `event:` name applies to the data lines that follow it, until
|
|
1425
|
+
// the blank line that ends the frame. An error frame ends the stream:
|
|
1426
|
+
// nothing after it is surfaced and the body is not read to the end,
|
|
1427
|
+
// so a server or proxy that keeps the connection open after an error
|
|
1428
|
+
// cannot hang the caller (the Rust and Go clients stop the same way).
|
|
1429
|
+
let eventName = "";
|
|
1430
|
+
let stopped = false;
|
|
1457
1431
|
const emitLine = (line) => {
|
|
1432
|
+
if (line.startsWith("event:")) {
|
|
1433
|
+
eventName = line.slice(6).trim();
|
|
1434
|
+
return;
|
|
1435
|
+
}
|
|
1436
|
+
if (line.trim() === "") {
|
|
1437
|
+
eventName = "";
|
|
1438
|
+
return;
|
|
1439
|
+
}
|
|
1458
1440
|
if (!line.startsWith("data:"))
|
|
1459
1441
|
return;
|
|
1460
1442
|
const dataStr = line.slice(5).trim();
|
|
@@ -1462,10 +1444,16 @@ class EkoDBClient {
|
|
|
1462
1444
|
return;
|
|
1463
1445
|
try {
|
|
1464
1446
|
const eventData = JSON.parse(dataStr);
|
|
1465
|
-
|
|
1447
|
+
// An error frame is one the server names `error`, or whose
|
|
1448
|
+
// payload carries an `error`; a `message`-only payload is still
|
|
1449
|
+
// the error rather than a frame to skip, and the text is always a
|
|
1450
|
+
// string (`streamErrorText`).
|
|
1451
|
+
if (eventData.error != null || eventName === "error") {
|
|
1452
|
+
stopped = true;
|
|
1466
1453
|
stream.emit("event", {
|
|
1467
1454
|
type: "error",
|
|
1468
|
-
error: eventData
|
|
1455
|
+
error: streamErrorText(eventData),
|
|
1456
|
+
...providerFailureFields(eventData),
|
|
1469
1457
|
});
|
|
1470
1458
|
}
|
|
1471
1459
|
else if (eventData.content && eventData.message_id) {
|
|
@@ -1501,20 +1489,29 @@ class EkoDBClient {
|
|
|
1501
1489
|
break;
|
|
1502
1490
|
buffer += decoder.decode(value, { stream: true });
|
|
1503
1491
|
let nl;
|
|
1504
|
-
while ((nl = buffer.indexOf("\n")) >= 0) {
|
|
1492
|
+
while (!stopped && (nl = buffer.indexOf("\n")) >= 0) {
|
|
1505
1493
|
emitLine(buffer.slice(0, nl));
|
|
1506
1494
|
buffer = buffer.slice(nl + 1);
|
|
1507
1495
|
}
|
|
1496
|
+
if (stopped) {
|
|
1497
|
+
await reader.cancel?.()?.catch?.(() => { });
|
|
1498
|
+
break;
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
if (!stopped) {
|
|
1502
|
+
buffer += decoder.decode();
|
|
1503
|
+
if (buffer)
|
|
1504
|
+
emitLine(buffer);
|
|
1508
1505
|
}
|
|
1509
|
-
buffer += decoder.decode();
|
|
1510
|
-
if (buffer)
|
|
1511
|
-
emitLine(buffer);
|
|
1512
1506
|
}
|
|
1513
1507
|
else {
|
|
1514
1508
|
// Fallback for environments/tests without a readable body stream.
|
|
1515
1509
|
const body = await response.text();
|
|
1516
|
-
for (const line of body.split("\n"))
|
|
1510
|
+
for (const line of body.split("\n")) {
|
|
1517
1511
|
emitLine(line);
|
|
1512
|
+
if (stopped)
|
|
1513
|
+
break;
|
|
1514
|
+
}
|
|
1518
1515
|
}
|
|
1519
1516
|
stream.close();
|
|
1520
1517
|
}
|
|
@@ -1913,15 +1910,21 @@ class EkoDBClient {
|
|
|
1913
1910
|
// ========================================================================
|
|
1914
1911
|
/** Get documents linked to a KV key */
|
|
1915
1912
|
async kvGetLinks(key) {
|
|
1916
|
-
return this.makeRequest("GET", `/api/kv
|
|
1913
|
+
return this.makeRequest("GET", `/api/kv/${encodeURIComponent(key)}/links`, undefined, 0, true);
|
|
1917
1914
|
}
|
|
1918
|
-
/**
|
|
1919
|
-
|
|
1920
|
-
|
|
1915
|
+
/**
|
|
1916
|
+
* Link a document to a KV key.
|
|
1917
|
+
*
|
|
1918
|
+
* The identifying triple goes in the path; the body carries the optional
|
|
1919
|
+
* link payload (`keys`, `field_path`, `metadata`), and an empty object means
|
|
1920
|
+
* "no extra link data".
|
|
1921
|
+
*/
|
|
1922
|
+
async kvLink(key, collection, documentId, linkData = {}) {
|
|
1923
|
+
return this.makeRequest("POST", `/api/kv/${encodeURIComponent(key)}/links/${encodeURIComponent(collection)}/${encodeURIComponent(documentId)}`, linkData, 0, true);
|
|
1921
1924
|
}
|
|
1922
|
-
/** Unlink a document from a KV key */
|
|
1925
|
+
/** Unlink a document from a KV key. DELETE, with the triple in the path. */
|
|
1923
1926
|
async kvUnlink(key, collection, documentId) {
|
|
1924
|
-
return this.makeRequest("
|
|
1927
|
+
return this.makeRequest("DELETE", `/api/kv/${encodeURIComponent(key)}/links/${encodeURIComponent(collection)}/${encodeURIComponent(documentId)}`, undefined, 0, true);
|
|
1925
1928
|
}
|
|
1926
1929
|
// ========================================================================
|
|
1927
1930
|
// SCHEDULE MANAGEMENT
|
|
@@ -1946,13 +1949,30 @@ class EkoDBClient {
|
|
|
1946
1949
|
async deleteSchedule(id) {
|
|
1947
1950
|
await this.makeRequest("DELETE", `/api/schedules/${encodeURIComponent(id)}`, undefined, 0, true);
|
|
1948
1951
|
}
|
|
1949
|
-
/**
|
|
1952
|
+
/**
|
|
1953
|
+
* Pause a schedule.
|
|
1954
|
+
*
|
|
1955
|
+
* There is no `/pause` endpoint — pausing is a partial update of the
|
|
1956
|
+
* schedule's `enabled` flag. This previously POSTed to
|
|
1957
|
+
* `/api/schedules/{id}/pause`, which has never existed and always 404'd.
|
|
1958
|
+
*/
|
|
1950
1959
|
async pauseSchedule(id) {
|
|
1951
|
-
return this.
|
|
1960
|
+
return this.setScheduleEnabled(id, false);
|
|
1952
1961
|
}
|
|
1953
|
-
/**
|
|
1962
|
+
/**
|
|
1963
|
+
* Resume a paused schedule. See {@link pauseSchedule} for why this is an
|
|
1964
|
+
* update rather than its own endpoint.
|
|
1965
|
+
*/
|
|
1954
1966
|
async resumeSchedule(id) {
|
|
1955
|
-
return this.
|
|
1967
|
+
return this.setScheduleEnabled(id, true);
|
|
1968
|
+
}
|
|
1969
|
+
/**
|
|
1970
|
+
* Shared implementation for pause/resume: a partial update carrying only
|
|
1971
|
+
* `enabled`. The server recomputes the next execution time when `enabled`
|
|
1972
|
+
* changes, so nothing else needs sending.
|
|
1973
|
+
*/
|
|
1974
|
+
async setScheduleEnabled(id, enabled) {
|
|
1975
|
+
return this.makeRequest("PUT", `/api/schedules/${encodeURIComponent(id)}`, { enabled }, 0, true);
|
|
1956
1976
|
}
|
|
1957
1977
|
// ========================================================================
|
|
1958
1978
|
// COLLECTION UTILITIES
|
|
@@ -2216,6 +2236,32 @@ class EkoDBClient {
|
|
|
2216
2236
|
}
|
|
2217
2237
|
}
|
|
2218
2238
|
exports.EkoDBClient = EkoDBClient;
|
|
2239
|
+
/**
|
|
2240
|
+
* The text of a stream error frame: the first of `error` / `message` that is
|
|
2241
|
+
* a non-empty string, else a fixed fallback — a structured `error` object is
|
|
2242
|
+
* still an error, never a non-string `error` on the event. Shared by the SSE
|
|
2243
|
+
* and WebSocket routes so the two cannot drift.
|
|
2244
|
+
*/
|
|
2245
|
+
function streamErrorText(payload) {
|
|
2246
|
+
const text = (value) => typeof value === "string" && value ? value : undefined;
|
|
2247
|
+
return text(payload.error) ?? text(payload.message) ?? "Unknown error";
|
|
2248
|
+
}
|
|
2249
|
+
/**
|
|
2250
|
+
* The classification fields of a stream error frame, only those present, so
|
|
2251
|
+
* a plain error stays `{ type, error }`.
|
|
2252
|
+
*/
|
|
2253
|
+
function providerFailureFields(eventData) {
|
|
2254
|
+
const fields = {};
|
|
2255
|
+
if (typeof eventData.error_kind === "string")
|
|
2256
|
+
fields.errorKind = eventData.error_kind;
|
|
2257
|
+
if (typeof eventData.provider === "string")
|
|
2258
|
+
fields.provider = eventData.provider;
|
|
2259
|
+
if (typeof eventData.provider_status === "number")
|
|
2260
|
+
fields.providerStatus = eventData.provider_status;
|
|
2261
|
+
if (typeof eventData.retry_after_secs === "number")
|
|
2262
|
+
fields.retryAfterSecs = eventData.retry_after_secs;
|
|
2263
|
+
return fields;
|
|
2264
|
+
}
|
|
2219
2265
|
/** EventEmitter-like interface for subscriptions and chat streams. */
|
|
2220
2266
|
class EventStream {
|
|
2221
2267
|
constructor() {
|
|
@@ -2429,7 +2475,7 @@ class WebSocketClient {
|
|
|
2429
2475
|
return this.connectPromise;
|
|
2430
2476
|
}
|
|
2431
2477
|
async openSocket() {
|
|
2432
|
-
const WebSocket = (await
|
|
2478
|
+
const WebSocket = (await import("ws")).default;
|
|
2433
2479
|
let url = this.wsURL;
|
|
2434
2480
|
if (!url.endsWith("/api/ws")) {
|
|
2435
2481
|
url += "/api/ws";
|
|
@@ -2761,9 +2807,12 @@ class WebSocketClient {
|
|
|
2761
2807
|
const chatId = msg.payload?.chat_id || msg.payload?.chatId;
|
|
2762
2808
|
const stream = this.chatStreams.get(chatId);
|
|
2763
2809
|
if (stream) {
|
|
2810
|
+
// The text guard and the classification are the SSE route's,
|
|
2811
|
+
// so the two routes emit the same shape.
|
|
2764
2812
|
stream.emit("event", {
|
|
2765
2813
|
type: "error",
|
|
2766
|
-
error: msg.payload
|
|
2814
|
+
error: streamErrorText(msg.payload),
|
|
2815
|
+
...providerFailureFields(msg.payload),
|
|
2767
2816
|
});
|
|
2768
2817
|
this.chatStreams.delete(chatId);
|
|
2769
2818
|
stream.close();
|