@ekodb/ekodb-client 0.13.0 → 0.15.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 +157 -4
- package/dist/client.js +523 -7
- package/dist/client.test.js +861 -0
- package/dist/websocket.test.js +89 -0
- package/package.json +1 -1
- package/src/client.test.ts +1090 -0
- package/src/client.ts +872 -7
- package/src/websocket.test.ts +137 -0
package/src/client.ts
CHANGED
|
@@ -203,6 +203,7 @@ export interface ChatMessageRequest {
|
|
|
203
203
|
force_summarize?: boolean;
|
|
204
204
|
max_iterations?: number;
|
|
205
205
|
tool_config?: ToolConfig;
|
|
206
|
+
llm_model?: string;
|
|
206
207
|
}
|
|
207
208
|
|
|
208
209
|
export interface TokenUsage {
|
|
@@ -371,6 +372,7 @@ export class EkoDBClient {
|
|
|
371
372
|
private baseURL: string;
|
|
372
373
|
private apiKey: string;
|
|
373
374
|
private token: string | null = null;
|
|
375
|
+
private tokenExpiry: number = 0;
|
|
374
376
|
private shouldRetry: boolean;
|
|
375
377
|
private maxRetries: number;
|
|
376
378
|
private format: SerializationFormat;
|
|
@@ -441,22 +443,74 @@ export class EkoDBClient {
|
|
|
441
443
|
|
|
442
444
|
const result = (await response.json()) as { token: string };
|
|
443
445
|
this.token = result.token;
|
|
446
|
+
|
|
447
|
+
// Extract and cache JWT expiry for proactive refresh
|
|
448
|
+
const expiry = this.extractJWTExpiry(result.token);
|
|
449
|
+
this.tokenExpiry = expiry ?? Math.floor(Date.now() / 1000) + 3600; // fallback: 1 hour
|
|
444
450
|
}
|
|
445
451
|
|
|
446
452
|
/**
|
|
447
|
-
* Get
|
|
448
|
-
*
|
|
449
|
-
|
|
450
|
-
|
|
453
|
+
* Get a valid authentication token.
|
|
454
|
+
*
|
|
455
|
+
* Returns a cached token if it has more than 60s of validity remaining.
|
|
456
|
+
* Otherwise fetches a new one via refreshToken(). This means callers
|
|
457
|
+
* never need to handle token refresh themselves — every getToken() call
|
|
458
|
+
* returns a token that's valid for at least 60 more seconds.
|
|
459
|
+
*/
|
|
460
|
+
async getToken(): Promise<string | null> {
|
|
461
|
+
if (this.token) {
|
|
462
|
+
const now = Math.floor(Date.now() / 1000);
|
|
463
|
+
if (now + 60 >= this.tokenExpiry) {
|
|
464
|
+
// Token is about to expire or already expired — refresh proactively
|
|
465
|
+
await this.refreshToken();
|
|
466
|
+
}
|
|
467
|
+
} else {
|
|
468
|
+
// No token yet — fetch one
|
|
469
|
+
await this.refreshToken();
|
|
470
|
+
}
|
|
451
471
|
return this.token;
|
|
452
472
|
}
|
|
453
473
|
|
|
454
474
|
/**
|
|
455
|
-
* Clear the cached authentication token.
|
|
475
|
+
* Clear the cached authentication token and expiry.
|
|
456
476
|
* The next request will trigger a fresh token exchange.
|
|
457
477
|
*/
|
|
458
478
|
clearTokenCache(): void {
|
|
459
479
|
this.token = null;
|
|
480
|
+
this.tokenExpiry = 0;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Extract the `exp` claim from a JWT without verifying the signature.
|
|
485
|
+
* Returns the Unix timestamp (seconds) of expiry, or null if parsing fails.
|
|
486
|
+
*/
|
|
487
|
+
private extractJWTExpiry(token: string): number | null {
|
|
488
|
+
try {
|
|
489
|
+
const parts = token.split(".");
|
|
490
|
+
if (parts.length !== 3) {
|
|
491
|
+
return null;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Convert base64url to standard base64
|
|
495
|
+
let payload = parts[1];
|
|
496
|
+
payload = payload.replace(/-/g, "+").replace(/_/g, "/");
|
|
497
|
+
|
|
498
|
+
// Pad to multiple of 4
|
|
499
|
+
const pad = payload.length % 4;
|
|
500
|
+
if (pad) {
|
|
501
|
+
payload += "=".repeat(4 - pad);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const decoded = atob(payload);
|
|
505
|
+
const claims = JSON.parse(decoded);
|
|
506
|
+
|
|
507
|
+
if (typeof claims.exp === "number") {
|
|
508
|
+
return claims.exp;
|
|
509
|
+
}
|
|
510
|
+
return null;
|
|
511
|
+
} catch {
|
|
512
|
+
return null;
|
|
513
|
+
}
|
|
460
514
|
}
|
|
461
515
|
|
|
462
516
|
/**
|
|
@@ -1499,6 +1553,54 @@ export class EkoDBClient {
|
|
|
1499
1553
|
|
|
1500
1554
|
// ========== Chat Methods ==========
|
|
1501
1555
|
|
|
1556
|
+
/**
|
|
1557
|
+
* Execute a tool via ekoDB's server-side tool pipeline.
|
|
1558
|
+
*
|
|
1559
|
+
* Calls POST /api/chat/tools/execute which goes through the same
|
|
1560
|
+
* execute_tool function as the LLM tool-calling loop — with all
|
|
1561
|
+
* collection filtering, permission enforcement, and internal collection
|
|
1562
|
+
* blocking. No LLM round-trip.
|
|
1563
|
+
*
|
|
1564
|
+
* @returns The tool result if executed, or null if the server doesn't
|
|
1565
|
+
* support the endpoint (older ekoDB versions).
|
|
1566
|
+
*/
|
|
1567
|
+
async executeTool(
|
|
1568
|
+
toolName: string,
|
|
1569
|
+
params: { [key: string]: any },
|
|
1570
|
+
chatId?: string,
|
|
1571
|
+
): Promise<any | null> {
|
|
1572
|
+
const body: { [key: string]: any } = { tool: toolName, params };
|
|
1573
|
+
if (chatId) {
|
|
1574
|
+
body.chat_id = chatId;
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
try {
|
|
1578
|
+
const result = await this.makeRequest<{ [key: string]: any }>(
|
|
1579
|
+
"POST",
|
|
1580
|
+
"/api/chat/tools/execute",
|
|
1581
|
+
body,
|
|
1582
|
+
0,
|
|
1583
|
+
true, // Force JSON for chat operations
|
|
1584
|
+
);
|
|
1585
|
+
|
|
1586
|
+
if (result.success) {
|
|
1587
|
+
return result.result;
|
|
1588
|
+
} else {
|
|
1589
|
+
throw new Error(result.error || "tool execution failed");
|
|
1590
|
+
}
|
|
1591
|
+
} catch (err: any) {
|
|
1592
|
+
// Server doesn't have the endpoint (404) or route mismatch (405)
|
|
1593
|
+
// Parse status from makeRequest error format: "Request failed with status NNN: ..."
|
|
1594
|
+
const message = String(err?.message ?? "");
|
|
1595
|
+
const match = message.match(/Request failed with status (\d+):/);
|
|
1596
|
+
const status = match ? parseInt(match[1], 10) : undefined;
|
|
1597
|
+
if (status === 404 || status === 405) {
|
|
1598
|
+
return null;
|
|
1599
|
+
}
|
|
1600
|
+
throw err;
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1502
1604
|
/**
|
|
1503
1605
|
* Create a new chat session
|
|
1504
1606
|
*/
|
|
@@ -1542,6 +1644,115 @@ export class EkoDBClient {
|
|
|
1542
1644
|
);
|
|
1543
1645
|
}
|
|
1544
1646
|
|
|
1647
|
+
/**
|
|
1648
|
+
* Stateless raw LLM completion via SSE streaming.
|
|
1649
|
+
*
|
|
1650
|
+
* Same as rawCompletion() but uses Server-Sent Events to keep the
|
|
1651
|
+
* connection alive. Preferred for deployed instances where reverse proxies
|
|
1652
|
+
* may kill idle HTTP connections before the LLM responds.
|
|
1653
|
+
*/
|
|
1654
|
+
async rawCompletionStream(
|
|
1655
|
+
request: RawCompletionRequest,
|
|
1656
|
+
): Promise<RawCompletionResponse> {
|
|
1657
|
+
let token = await this.getToken();
|
|
1658
|
+
const url = `${this.baseURL}/api/chat/complete/stream`;
|
|
1659
|
+
|
|
1660
|
+
const response = await fetch(url, {
|
|
1661
|
+
method: "POST",
|
|
1662
|
+
headers: {
|
|
1663
|
+
"Content-Type": "application/json",
|
|
1664
|
+
Accept: "text/event-stream",
|
|
1665
|
+
Authorization: `Bearer ${token}`,
|
|
1666
|
+
},
|
|
1667
|
+
body: JSON.stringify(request),
|
|
1668
|
+
});
|
|
1669
|
+
|
|
1670
|
+
if (!response.ok) {
|
|
1671
|
+
const body = await response.text();
|
|
1672
|
+
throw new Error(
|
|
1673
|
+
`SSE raw completion failed (${response.status}): ${body}`,
|
|
1674
|
+
);
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
const body = await response.text();
|
|
1678
|
+
let content = "";
|
|
1679
|
+
let lastError: string | null = null;
|
|
1680
|
+
|
|
1681
|
+
for (const line of body.split("\n")) {
|
|
1682
|
+
if (line.startsWith("data:")) {
|
|
1683
|
+
const dataStr = line.slice(5).trim();
|
|
1684
|
+
if (!dataStr) continue;
|
|
1685
|
+
try {
|
|
1686
|
+
const eventData = JSON.parse(dataStr);
|
|
1687
|
+
if (eventData.token) content += eventData.token;
|
|
1688
|
+
if (eventData.content) content = eventData.content;
|
|
1689
|
+
if (eventData.error) lastError = eventData.error;
|
|
1690
|
+
} catch {
|
|
1691
|
+
// skip malformed SSE data
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
if (lastError) throw new Error(lastError);
|
|
1697
|
+
return { content };
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
/**
|
|
1701
|
+
* Stateless raw LLM completion via SSE streaming with token-level progress.
|
|
1702
|
+
*
|
|
1703
|
+
* Same as rawCompletionStream() but invokes `onToken` with each token as it
|
|
1704
|
+
* arrives, allowing callers to show real-time progress.
|
|
1705
|
+
*/
|
|
1706
|
+
async rawCompletionStreamWithProgress(
|
|
1707
|
+
request: RawCompletionRequest,
|
|
1708
|
+
onToken: (token: string) => void,
|
|
1709
|
+
): Promise<RawCompletionResponse> {
|
|
1710
|
+
let token = await this.getToken();
|
|
1711
|
+
const url = `${this.baseURL}/api/chat/complete/stream`;
|
|
1712
|
+
|
|
1713
|
+
const response = await fetch(url, {
|
|
1714
|
+
method: "POST",
|
|
1715
|
+
headers: {
|
|
1716
|
+
"Content-Type": "application/json",
|
|
1717
|
+
Accept: "text/event-stream",
|
|
1718
|
+
Authorization: `Bearer ${token}`,
|
|
1719
|
+
},
|
|
1720
|
+
body: JSON.stringify(request),
|
|
1721
|
+
});
|
|
1722
|
+
|
|
1723
|
+
if (!response.ok) {
|
|
1724
|
+
const body = await response.text();
|
|
1725
|
+
throw new Error(
|
|
1726
|
+
`SSE raw completion failed (${response.status}): ${body}`,
|
|
1727
|
+
);
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
const body = await response.text();
|
|
1731
|
+
let content = "";
|
|
1732
|
+
let lastError: string | null = null;
|
|
1733
|
+
|
|
1734
|
+
for (const line of body.split("\n")) {
|
|
1735
|
+
if (line.startsWith("data:")) {
|
|
1736
|
+
const dataStr = line.slice(5).trim();
|
|
1737
|
+
if (!dataStr) continue;
|
|
1738
|
+
try {
|
|
1739
|
+
const eventData = JSON.parse(dataStr);
|
|
1740
|
+
if (eventData.token) {
|
|
1741
|
+
content += eventData.token;
|
|
1742
|
+
onToken(eventData.token);
|
|
1743
|
+
}
|
|
1744
|
+
if (eventData.content) content = eventData.content;
|
|
1745
|
+
if (eventData.error) lastError = eventData.error;
|
|
1746
|
+
} catch {
|
|
1747
|
+
// skip malformed SSE data
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
if (lastError) throw new Error(lastError);
|
|
1753
|
+
return { content };
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1545
1756
|
/**
|
|
1546
1757
|
* Send a message in an existing chat session
|
|
1547
1758
|
*/
|
|
@@ -1558,6 +1769,96 @@ export class EkoDBClient {
|
|
|
1558
1769
|
);
|
|
1559
1770
|
}
|
|
1560
1771
|
|
|
1772
|
+
/**
|
|
1773
|
+
* Send a message in an existing chat session via SSE streaming.
|
|
1774
|
+
*
|
|
1775
|
+
* Returns an EventStream that emits ChatStreamEvent objects as they arrive:
|
|
1776
|
+
* - `{ type: "chunk", content: "..." }` for each token
|
|
1777
|
+
* - `{ type: "end", messageId, executionTimeMs, tokenUsage?, contextWindow? }` when complete
|
|
1778
|
+
* - `{ type: "error", error: "..." }` on failure
|
|
1779
|
+
*
|
|
1780
|
+
* Preferred over chatMessage() for long-running responses where reverse
|
|
1781
|
+
* proxies may kill idle HTTP connections before the LLM responds.
|
|
1782
|
+
*/
|
|
1783
|
+
chatMessageStream(
|
|
1784
|
+
chatId: string,
|
|
1785
|
+
request: ChatMessageRequest,
|
|
1786
|
+
): EventStream<ChatStreamEvent> {
|
|
1787
|
+
const stream = new EventStream<ChatStreamEvent>();
|
|
1788
|
+
|
|
1789
|
+
(async () => {
|
|
1790
|
+
try {
|
|
1791
|
+
let token = this.getToken();
|
|
1792
|
+
if (!token) {
|
|
1793
|
+
await this.refreshToken();
|
|
1794
|
+
token = this.getToken();
|
|
1795
|
+
}
|
|
1796
|
+
const url = `${this.baseURL}/api/chat/${chatId}/messages/stream`;
|
|
1797
|
+
|
|
1798
|
+
const response = await fetch(url, {
|
|
1799
|
+
method: "POST",
|
|
1800
|
+
headers: {
|
|
1801
|
+
"Content-Type": "application/json",
|
|
1802
|
+
Accept: "text/event-stream",
|
|
1803
|
+
Authorization: `Bearer ${token}`,
|
|
1804
|
+
},
|
|
1805
|
+
body: JSON.stringify(request),
|
|
1806
|
+
});
|
|
1807
|
+
|
|
1808
|
+
if (!response.ok) {
|
|
1809
|
+
const body = await response.text();
|
|
1810
|
+
stream.emit("event", {
|
|
1811
|
+
type: "error",
|
|
1812
|
+
error: `SSE chat message stream failed (${response.status}): ${body}`,
|
|
1813
|
+
} as ChatStreamEvent);
|
|
1814
|
+
stream.close();
|
|
1815
|
+
return;
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
const body = await response.text();
|
|
1819
|
+
for (const line of body.split("\n")) {
|
|
1820
|
+
if (!line.startsWith("data:")) continue;
|
|
1821
|
+
const dataStr = line.slice(5).trim();
|
|
1822
|
+
if (!dataStr) continue;
|
|
1823
|
+
try {
|
|
1824
|
+
const eventData = JSON.parse(dataStr);
|
|
1825
|
+
if (eventData.error) {
|
|
1826
|
+
stream.emit("event", {
|
|
1827
|
+
type: "error",
|
|
1828
|
+
error: eventData.error,
|
|
1829
|
+
} as ChatStreamEvent);
|
|
1830
|
+
} else if (eventData.content && eventData.message_id) {
|
|
1831
|
+
// Done event — has full content + message_id
|
|
1832
|
+
stream.emit("event", {
|
|
1833
|
+
type: "end",
|
|
1834
|
+
messageId: eventData.message_id,
|
|
1835
|
+
executionTimeMs: eventData.execution_time_ms ?? 0,
|
|
1836
|
+
tokenUsage: eventData.token_usage,
|
|
1837
|
+
contextWindow: eventData.context_window,
|
|
1838
|
+
} as ChatStreamEvent);
|
|
1839
|
+
} else if (eventData.token) {
|
|
1840
|
+
stream.emit("event", {
|
|
1841
|
+
type: "chunk",
|
|
1842
|
+
content: eventData.token,
|
|
1843
|
+
} as ChatStreamEvent);
|
|
1844
|
+
}
|
|
1845
|
+
} catch {
|
|
1846
|
+
// skip malformed SSE data
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
stream.close();
|
|
1850
|
+
} catch (err: any) {
|
|
1851
|
+
stream.emit("event", {
|
|
1852
|
+
type: "error",
|
|
1853
|
+
error: err.message ?? String(err),
|
|
1854
|
+
} as ChatStreamEvent);
|
|
1855
|
+
stream.close();
|
|
1856
|
+
}
|
|
1857
|
+
})();
|
|
1858
|
+
|
|
1859
|
+
return stream;
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1561
1862
|
/**
|
|
1562
1863
|
* Get a chat session by ID
|
|
1563
1864
|
*/
|
|
@@ -1942,6 +2243,517 @@ export class EkoDBClient {
|
|
|
1942
2243
|
);
|
|
1943
2244
|
}
|
|
1944
2245
|
|
|
2246
|
+
// ========================================================================
|
|
2247
|
+
// GOAL API
|
|
2248
|
+
// ========================================================================
|
|
2249
|
+
|
|
2250
|
+
/** Create a new goal */
|
|
2251
|
+
async goalCreate(data: Record): Promise<Record> {
|
|
2252
|
+
return this.makeRequest<Record>("POST", "/api/chat/goals", data, 0, true);
|
|
2253
|
+
}
|
|
2254
|
+
|
|
2255
|
+
/** List all goals */
|
|
2256
|
+
async goalList(): Promise<Record> {
|
|
2257
|
+
return this.makeRequest<Record>(
|
|
2258
|
+
"GET",
|
|
2259
|
+
"/api/chat/goals",
|
|
2260
|
+
undefined,
|
|
2261
|
+
0,
|
|
2262
|
+
true,
|
|
2263
|
+
);
|
|
2264
|
+
}
|
|
2265
|
+
|
|
2266
|
+
/** Get a goal by ID */
|
|
2267
|
+
async goalGet(id: string): Promise<Record> {
|
|
2268
|
+
return this.makeRequest<Record>(
|
|
2269
|
+
"GET",
|
|
2270
|
+
`/api/chat/goals/${encodeURIComponent(id)}`,
|
|
2271
|
+
undefined,
|
|
2272
|
+
0,
|
|
2273
|
+
true,
|
|
2274
|
+
);
|
|
2275
|
+
}
|
|
2276
|
+
|
|
2277
|
+
/** Update a goal by ID */
|
|
2278
|
+
async goalUpdate(id: string, data: Record): Promise<Record> {
|
|
2279
|
+
return this.makeRequest<Record>(
|
|
2280
|
+
"PUT",
|
|
2281
|
+
`/api/chat/goals/${encodeURIComponent(id)}`,
|
|
2282
|
+
data,
|
|
2283
|
+
0,
|
|
2284
|
+
true,
|
|
2285
|
+
);
|
|
2286
|
+
}
|
|
2287
|
+
|
|
2288
|
+
/** Delete a goal by ID */
|
|
2289
|
+
async goalDelete(id: string): Promise<void> {
|
|
2290
|
+
await this.makeRequest<void>(
|
|
2291
|
+
"DELETE",
|
|
2292
|
+
`/api/chat/goals/${encodeURIComponent(id)}`,
|
|
2293
|
+
undefined,
|
|
2294
|
+
0,
|
|
2295
|
+
true,
|
|
2296
|
+
);
|
|
2297
|
+
}
|
|
2298
|
+
|
|
2299
|
+
// ── Goal Templates ──
|
|
2300
|
+
|
|
2301
|
+
/** Create a new goal template */
|
|
2302
|
+
async goalTemplateCreate(data: Record): Promise<Record> {
|
|
2303
|
+
return this.makeRequest<Record>(
|
|
2304
|
+
"POST",
|
|
2305
|
+
"/api/chat/goal-templates",
|
|
2306
|
+
data,
|
|
2307
|
+
0,
|
|
2308
|
+
true,
|
|
2309
|
+
);
|
|
2310
|
+
}
|
|
2311
|
+
|
|
2312
|
+
/** List all goal templates */
|
|
2313
|
+
async goalTemplateList(): Promise<Record> {
|
|
2314
|
+
return this.makeRequest<Record>(
|
|
2315
|
+
"GET",
|
|
2316
|
+
"/api/chat/goal-templates",
|
|
2317
|
+
undefined,
|
|
2318
|
+
0,
|
|
2319
|
+
true,
|
|
2320
|
+
);
|
|
2321
|
+
}
|
|
2322
|
+
|
|
2323
|
+
/** Get a goal template by ID */
|
|
2324
|
+
async goalTemplateGet(id: string): Promise<Record> {
|
|
2325
|
+
return this.makeRequest<Record>(
|
|
2326
|
+
"GET",
|
|
2327
|
+
`/api/chat/goal-templates/${encodeURIComponent(id)}`,
|
|
2328
|
+
undefined,
|
|
2329
|
+
0,
|
|
2330
|
+
true,
|
|
2331
|
+
);
|
|
2332
|
+
}
|
|
2333
|
+
|
|
2334
|
+
/** Update a goal template by ID */
|
|
2335
|
+
async goalTemplateUpdate(id: string, data: Record): Promise<Record> {
|
|
2336
|
+
return this.makeRequest<Record>(
|
|
2337
|
+
"PUT",
|
|
2338
|
+
`/api/chat/goal-templates/${encodeURIComponent(id)}`,
|
|
2339
|
+
data,
|
|
2340
|
+
0,
|
|
2341
|
+
true,
|
|
2342
|
+
);
|
|
2343
|
+
}
|
|
2344
|
+
|
|
2345
|
+
/** Delete a goal template by ID */
|
|
2346
|
+
async goalTemplateDelete(id: string): Promise<void> {
|
|
2347
|
+
await this.makeRequest<void>(
|
|
2348
|
+
"DELETE",
|
|
2349
|
+
`/api/chat/goal-templates/${encodeURIComponent(id)}`,
|
|
2350
|
+
undefined,
|
|
2351
|
+
0,
|
|
2352
|
+
true,
|
|
2353
|
+
);
|
|
2354
|
+
}
|
|
2355
|
+
|
|
2356
|
+
/** Search goals */
|
|
2357
|
+
async goalSearch(query: string): Promise<Record> {
|
|
2358
|
+
const params = new URLSearchParams({ q: query });
|
|
2359
|
+
return this.makeRequest<Record>(
|
|
2360
|
+
"GET",
|
|
2361
|
+
`/api/chat/goals/search?${params}`,
|
|
2362
|
+
undefined,
|
|
2363
|
+
0,
|
|
2364
|
+
true,
|
|
2365
|
+
);
|
|
2366
|
+
}
|
|
2367
|
+
|
|
2368
|
+
/** Mark a goal as complete (status -> pending_review) */
|
|
2369
|
+
async goalComplete(id: string, data: Record): Promise<Record> {
|
|
2370
|
+
return this.makeRequest<Record>(
|
|
2371
|
+
"POST",
|
|
2372
|
+
`/api/chat/goals/${encodeURIComponent(id)}/complete`,
|
|
2373
|
+
data,
|
|
2374
|
+
0,
|
|
2375
|
+
true,
|
|
2376
|
+
);
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2379
|
+
/** Approve a goal (status -> in_progress) */
|
|
2380
|
+
async goalApprove(id: string): Promise<Record> {
|
|
2381
|
+
return this.makeRequest<Record>(
|
|
2382
|
+
"POST",
|
|
2383
|
+
`/api/chat/goals/${encodeURIComponent(id)}/approve`,
|
|
2384
|
+
undefined,
|
|
2385
|
+
0,
|
|
2386
|
+
true,
|
|
2387
|
+
);
|
|
2388
|
+
}
|
|
2389
|
+
|
|
2390
|
+
/** Reject a goal (status -> failed) */
|
|
2391
|
+
async goalReject(id: string, data: Record): Promise<Record> {
|
|
2392
|
+
return this.makeRequest<Record>(
|
|
2393
|
+
"POST",
|
|
2394
|
+
`/api/chat/goals/${encodeURIComponent(id)}/reject`,
|
|
2395
|
+
data,
|
|
2396
|
+
0,
|
|
2397
|
+
true,
|
|
2398
|
+
);
|
|
2399
|
+
}
|
|
2400
|
+
|
|
2401
|
+
/** Start a goal step (status -> in_progress) */
|
|
2402
|
+
async goalStepStart(id: string, stepIndex: number): Promise<Record> {
|
|
2403
|
+
return this.makeRequest<Record>(
|
|
2404
|
+
"POST",
|
|
2405
|
+
`/api/chat/goals/${encodeURIComponent(id)}/steps/${stepIndex}/start`,
|
|
2406
|
+
undefined,
|
|
2407
|
+
0,
|
|
2408
|
+
true,
|
|
2409
|
+
);
|
|
2410
|
+
}
|
|
2411
|
+
|
|
2412
|
+
/** Complete a goal step with result */
|
|
2413
|
+
async goalStepComplete(
|
|
2414
|
+
id: string,
|
|
2415
|
+
stepIndex: number,
|
|
2416
|
+
data: Record,
|
|
2417
|
+
): Promise<Record> {
|
|
2418
|
+
return this.makeRequest<Record>(
|
|
2419
|
+
"POST",
|
|
2420
|
+
`/api/chat/goals/${encodeURIComponent(id)}/steps/${stepIndex}/complete`,
|
|
2421
|
+
data,
|
|
2422
|
+
0,
|
|
2423
|
+
true,
|
|
2424
|
+
);
|
|
2425
|
+
}
|
|
2426
|
+
|
|
2427
|
+
/** Fail a goal step with error */
|
|
2428
|
+
async goalStepFail(
|
|
2429
|
+
id: string,
|
|
2430
|
+
stepIndex: number,
|
|
2431
|
+
data: Record,
|
|
2432
|
+
): Promise<Record> {
|
|
2433
|
+
return this.makeRequest<Record>(
|
|
2434
|
+
"POST",
|
|
2435
|
+
`/api/chat/goals/${encodeURIComponent(id)}/steps/${stepIndex}/fail`,
|
|
2436
|
+
data,
|
|
2437
|
+
0,
|
|
2438
|
+
true,
|
|
2439
|
+
);
|
|
2440
|
+
}
|
|
2441
|
+
|
|
2442
|
+
// ========================================================================
|
|
2443
|
+
// TASK API
|
|
2444
|
+
// ========================================================================
|
|
2445
|
+
|
|
2446
|
+
/** Create a new scheduled task */
|
|
2447
|
+
async taskCreate(data: Record): Promise<Record> {
|
|
2448
|
+
return this.makeRequest<Record>("POST", "/api/chat/tasks", data, 0, true);
|
|
2449
|
+
}
|
|
2450
|
+
|
|
2451
|
+
/** List all scheduled tasks */
|
|
2452
|
+
async taskList(): Promise<Record> {
|
|
2453
|
+
return this.makeRequest<Record>(
|
|
2454
|
+
"GET",
|
|
2455
|
+
"/api/chat/tasks",
|
|
2456
|
+
undefined,
|
|
2457
|
+
0,
|
|
2458
|
+
true,
|
|
2459
|
+
);
|
|
2460
|
+
}
|
|
2461
|
+
|
|
2462
|
+
/** Get a task by ID */
|
|
2463
|
+
async taskGet(id: string): Promise<Record> {
|
|
2464
|
+
return this.makeRequest<Record>(
|
|
2465
|
+
"GET",
|
|
2466
|
+
`/api/chat/tasks/${encodeURIComponent(id)}`,
|
|
2467
|
+
undefined,
|
|
2468
|
+
0,
|
|
2469
|
+
true,
|
|
2470
|
+
);
|
|
2471
|
+
}
|
|
2472
|
+
|
|
2473
|
+
/** Update a task by ID */
|
|
2474
|
+
async taskUpdate(id: string, data: Record): Promise<Record> {
|
|
2475
|
+
return this.makeRequest<Record>(
|
|
2476
|
+
"PUT",
|
|
2477
|
+
`/api/chat/tasks/${encodeURIComponent(id)}`,
|
|
2478
|
+
data,
|
|
2479
|
+
0,
|
|
2480
|
+
true,
|
|
2481
|
+
);
|
|
2482
|
+
}
|
|
2483
|
+
|
|
2484
|
+
/** Delete a task by ID */
|
|
2485
|
+
async taskDelete(id: string): Promise<void> {
|
|
2486
|
+
await this.makeRequest<void>(
|
|
2487
|
+
"DELETE",
|
|
2488
|
+
`/api/chat/tasks/${encodeURIComponent(id)}`,
|
|
2489
|
+
undefined,
|
|
2490
|
+
0,
|
|
2491
|
+
true,
|
|
2492
|
+
);
|
|
2493
|
+
}
|
|
2494
|
+
|
|
2495
|
+
/** Get tasks that are due at the given time */
|
|
2496
|
+
async taskDue(now: string): Promise<Record> {
|
|
2497
|
+
const params = new URLSearchParams({ now });
|
|
2498
|
+
return this.makeRequest<Record>(
|
|
2499
|
+
"GET",
|
|
2500
|
+
`/api/chat/tasks/due?${params}`,
|
|
2501
|
+
undefined,
|
|
2502
|
+
0,
|
|
2503
|
+
true,
|
|
2504
|
+
);
|
|
2505
|
+
}
|
|
2506
|
+
|
|
2507
|
+
/** Start a task (status -> running) */
|
|
2508
|
+
async taskStart(id: string): Promise<Record> {
|
|
2509
|
+
return this.makeRequest<Record>(
|
|
2510
|
+
"POST",
|
|
2511
|
+
`/api/chat/tasks/${encodeURIComponent(id)}/start`,
|
|
2512
|
+
undefined,
|
|
2513
|
+
0,
|
|
2514
|
+
true,
|
|
2515
|
+
);
|
|
2516
|
+
}
|
|
2517
|
+
|
|
2518
|
+
/** Mark a task as succeeded */
|
|
2519
|
+
async taskSucceed(id: string, data: Record): Promise<Record> {
|
|
2520
|
+
return this.makeRequest<Record>(
|
|
2521
|
+
"POST",
|
|
2522
|
+
`/api/chat/tasks/${encodeURIComponent(id)}/succeed`,
|
|
2523
|
+
data,
|
|
2524
|
+
0,
|
|
2525
|
+
true,
|
|
2526
|
+
);
|
|
2527
|
+
}
|
|
2528
|
+
|
|
2529
|
+
/** Mark a task as failed */
|
|
2530
|
+
async taskFail(id: string, data: Record): Promise<Record> {
|
|
2531
|
+
return this.makeRequest<Record>(
|
|
2532
|
+
"POST",
|
|
2533
|
+
`/api/chat/tasks/${encodeURIComponent(id)}/fail`,
|
|
2534
|
+
data,
|
|
2535
|
+
0,
|
|
2536
|
+
true,
|
|
2537
|
+
);
|
|
2538
|
+
}
|
|
2539
|
+
|
|
2540
|
+
/** Pause a task */
|
|
2541
|
+
async taskPause(id: string): Promise<Record> {
|
|
2542
|
+
return this.makeRequest<Record>(
|
|
2543
|
+
"POST",
|
|
2544
|
+
`/api/chat/tasks/${encodeURIComponent(id)}/pause`,
|
|
2545
|
+
undefined,
|
|
2546
|
+
0,
|
|
2547
|
+
true,
|
|
2548
|
+
);
|
|
2549
|
+
}
|
|
2550
|
+
|
|
2551
|
+
/** Resume a paused task */
|
|
2552
|
+
async taskResume(id: string, data: Record): Promise<Record> {
|
|
2553
|
+
return this.makeRequest<Record>(
|
|
2554
|
+
"POST",
|
|
2555
|
+
`/api/chat/tasks/${encodeURIComponent(id)}/resume`,
|
|
2556
|
+
data,
|
|
2557
|
+
0,
|
|
2558
|
+
true,
|
|
2559
|
+
);
|
|
2560
|
+
}
|
|
2561
|
+
|
|
2562
|
+
// ========================================================================
|
|
2563
|
+
// AGENT API
|
|
2564
|
+
// ========================================================================
|
|
2565
|
+
|
|
2566
|
+
/** Create a new agent */
|
|
2567
|
+
async agentCreate(data: Record): Promise<Record> {
|
|
2568
|
+
return this.makeRequest<Record>("POST", "/api/chat/agents", data, 0, true);
|
|
2569
|
+
}
|
|
2570
|
+
|
|
2571
|
+
/** List all agents */
|
|
2572
|
+
async agentList(): Promise<Record> {
|
|
2573
|
+
return this.makeRequest<Record>(
|
|
2574
|
+
"GET",
|
|
2575
|
+
"/api/chat/agents",
|
|
2576
|
+
undefined,
|
|
2577
|
+
0,
|
|
2578
|
+
true,
|
|
2579
|
+
);
|
|
2580
|
+
}
|
|
2581
|
+
|
|
2582
|
+
/** Get an agent by ID */
|
|
2583
|
+
async agentGet(id: string): Promise<Record> {
|
|
2584
|
+
return this.makeRequest<Record>(
|
|
2585
|
+
"GET",
|
|
2586
|
+
`/api/chat/agents/${encodeURIComponent(id)}`,
|
|
2587
|
+
undefined,
|
|
2588
|
+
0,
|
|
2589
|
+
true,
|
|
2590
|
+
);
|
|
2591
|
+
}
|
|
2592
|
+
|
|
2593
|
+
/** Get an agent by name */
|
|
2594
|
+
async agentGetByName(name: string): Promise<Record> {
|
|
2595
|
+
return this.makeRequest<Record>(
|
|
2596
|
+
"GET",
|
|
2597
|
+
`/api/chat/agents/by-name/${encodeURIComponent(name)}`,
|
|
2598
|
+
undefined,
|
|
2599
|
+
0,
|
|
2600
|
+
true,
|
|
2601
|
+
);
|
|
2602
|
+
}
|
|
2603
|
+
|
|
2604
|
+
/** Update an agent by ID */
|
|
2605
|
+
async agentUpdate(id: string, data: Record): Promise<Record> {
|
|
2606
|
+
return this.makeRequest<Record>(
|
|
2607
|
+
"PUT",
|
|
2608
|
+
`/api/chat/agents/${encodeURIComponent(id)}`,
|
|
2609
|
+
data,
|
|
2610
|
+
0,
|
|
2611
|
+
true,
|
|
2612
|
+
);
|
|
2613
|
+
}
|
|
2614
|
+
|
|
2615
|
+
/** Delete an agent by ID */
|
|
2616
|
+
async agentDelete(id: string): Promise<void> {
|
|
2617
|
+
await this.makeRequest<void>(
|
|
2618
|
+
"DELETE",
|
|
2619
|
+
`/api/chat/agents/${encodeURIComponent(id)}`,
|
|
2620
|
+
undefined,
|
|
2621
|
+
0,
|
|
2622
|
+
true,
|
|
2623
|
+
);
|
|
2624
|
+
}
|
|
2625
|
+
|
|
2626
|
+
/** Get agents by deployment ID */
|
|
2627
|
+
async agentsByDeployment(deploymentId: string): Promise<Record> {
|
|
2628
|
+
return this.makeRequest<Record>(
|
|
2629
|
+
"GET",
|
|
2630
|
+
`/api/chat/agents/by-deployment/${encodeURIComponent(deploymentId)}`,
|
|
2631
|
+
undefined,
|
|
2632
|
+
0,
|
|
2633
|
+
true,
|
|
2634
|
+
);
|
|
2635
|
+
}
|
|
2636
|
+
|
|
2637
|
+
// ========================================================================
|
|
2638
|
+
// KV DOCUMENT LINKING
|
|
2639
|
+
// ========================================================================
|
|
2640
|
+
|
|
2641
|
+
/** Get documents linked to a KV key */
|
|
2642
|
+
async kvGetLinks(key: string): Promise<Record> {
|
|
2643
|
+
return this.makeRequest<Record>(
|
|
2644
|
+
"GET",
|
|
2645
|
+
`/api/kv/links/${encodeURIComponent(key)}`,
|
|
2646
|
+
undefined,
|
|
2647
|
+
0,
|
|
2648
|
+
true,
|
|
2649
|
+
);
|
|
2650
|
+
}
|
|
2651
|
+
|
|
2652
|
+
/** Link a document to a KV key */
|
|
2653
|
+
async kvLink(
|
|
2654
|
+
key: string,
|
|
2655
|
+
collection: string,
|
|
2656
|
+
documentId: string,
|
|
2657
|
+
): Promise<Record> {
|
|
2658
|
+
return this.makeRequest<Record>(
|
|
2659
|
+
"POST",
|
|
2660
|
+
`/api/kv/link`,
|
|
2661
|
+
{ key, collection, document_id: documentId },
|
|
2662
|
+
0,
|
|
2663
|
+
true,
|
|
2664
|
+
);
|
|
2665
|
+
}
|
|
2666
|
+
|
|
2667
|
+
/** Unlink a document from a KV key */
|
|
2668
|
+
async kvUnlink(
|
|
2669
|
+
key: string,
|
|
2670
|
+
collection: string,
|
|
2671
|
+
documentId: string,
|
|
2672
|
+
): Promise<Record> {
|
|
2673
|
+
return this.makeRequest<Record>(
|
|
2674
|
+
"POST",
|
|
2675
|
+
`/api/kv/unlink`,
|
|
2676
|
+
{ key, collection, document_id: documentId },
|
|
2677
|
+
0,
|
|
2678
|
+
true,
|
|
2679
|
+
);
|
|
2680
|
+
}
|
|
2681
|
+
|
|
2682
|
+
// ========================================================================
|
|
2683
|
+
// SCHEDULE MANAGEMENT
|
|
2684
|
+
// ========================================================================
|
|
2685
|
+
|
|
2686
|
+
/** Create a new schedule */
|
|
2687
|
+
async createSchedule(data: Record): Promise<Record> {
|
|
2688
|
+
return this.makeRequest<Record>("POST", `/api/schedules`, data, 0, true);
|
|
2689
|
+
}
|
|
2690
|
+
|
|
2691
|
+
/** List all schedules */
|
|
2692
|
+
async listSchedules(): Promise<Record> {
|
|
2693
|
+
return this.makeRequest<Record>(
|
|
2694
|
+
"GET",
|
|
2695
|
+
`/api/schedules`,
|
|
2696
|
+
undefined,
|
|
2697
|
+
0,
|
|
2698
|
+
true,
|
|
2699
|
+
);
|
|
2700
|
+
}
|
|
2701
|
+
|
|
2702
|
+
/** Get a schedule by ID */
|
|
2703
|
+
async getSchedule(id: string): Promise<Record> {
|
|
2704
|
+
return this.makeRequest<Record>(
|
|
2705
|
+
"GET",
|
|
2706
|
+
`/api/schedules/${encodeURIComponent(id)}`,
|
|
2707
|
+
undefined,
|
|
2708
|
+
0,
|
|
2709
|
+
true,
|
|
2710
|
+
);
|
|
2711
|
+
}
|
|
2712
|
+
|
|
2713
|
+
/** Update a schedule */
|
|
2714
|
+
async updateSchedule(id: string, data: Record): Promise<Record> {
|
|
2715
|
+
return this.makeRequest<Record>(
|
|
2716
|
+
"PUT",
|
|
2717
|
+
`/api/schedules/${encodeURIComponent(id)}`,
|
|
2718
|
+
data,
|
|
2719
|
+
0,
|
|
2720
|
+
true,
|
|
2721
|
+
);
|
|
2722
|
+
}
|
|
2723
|
+
|
|
2724
|
+
/** Delete a schedule */
|
|
2725
|
+
async deleteSchedule(id: string): Promise<void> {
|
|
2726
|
+
await this.makeRequest<void>(
|
|
2727
|
+
"DELETE",
|
|
2728
|
+
`/api/schedules/${encodeURIComponent(id)}`,
|
|
2729
|
+
undefined,
|
|
2730
|
+
0,
|
|
2731
|
+
true,
|
|
2732
|
+
);
|
|
2733
|
+
}
|
|
2734
|
+
|
|
2735
|
+
/** Pause a schedule */
|
|
2736
|
+
async pauseSchedule(id: string): Promise<Record> {
|
|
2737
|
+
return this.makeRequest<Record>(
|
|
2738
|
+
"POST",
|
|
2739
|
+
`/api/schedules/${encodeURIComponent(id)}/pause`,
|
|
2740
|
+
undefined,
|
|
2741
|
+
0,
|
|
2742
|
+
true,
|
|
2743
|
+
);
|
|
2744
|
+
}
|
|
2745
|
+
|
|
2746
|
+
/** Resume a schedule */
|
|
2747
|
+
async resumeSchedule(id: string): Promise<Record> {
|
|
2748
|
+
return this.makeRequest<Record>(
|
|
2749
|
+
"POST",
|
|
2750
|
+
`/api/schedules/${encodeURIComponent(id)}/resume`,
|
|
2751
|
+
undefined,
|
|
2752
|
+
0,
|
|
2753
|
+
true,
|
|
2754
|
+
);
|
|
2755
|
+
}
|
|
2756
|
+
|
|
1945
2757
|
// ========================================================================
|
|
1946
2758
|
// COLLECTION UTILITIES
|
|
1947
2759
|
// ========================================================================
|
|
@@ -2143,6 +2955,8 @@ export type ChatStreamEvent =
|
|
|
2143
2955
|
tokenUsage?: any;
|
|
2144
2956
|
toolCallHistory?: any;
|
|
2145
2957
|
executionTimeMs: number;
|
|
2958
|
+
/** Model's context window size in tokens. */
|
|
2959
|
+
contextWindow?: number;
|
|
2146
2960
|
}
|
|
2147
2961
|
| {
|
|
2148
2962
|
type: "toolCall";
|
|
@@ -2309,7 +3123,13 @@ export class WebSocketClient {
|
|
|
2309
3123
|
switch (msg.type) {
|
|
2310
3124
|
case "Success":
|
|
2311
3125
|
case "Error": {
|
|
2312
|
-
|
|
3126
|
+
// Try messageId from top-level, then from payload
|
|
3127
|
+
const messageId =
|
|
3128
|
+
msg.messageId ||
|
|
3129
|
+
msg.message_id ||
|
|
3130
|
+
msg.payload?.message_id ||
|
|
3131
|
+
msg.payload?.messageId;
|
|
3132
|
+
let matched = false;
|
|
2313
3133
|
if (messageId && this.pendingRequests.has(messageId)) {
|
|
2314
3134
|
const pending = this.pendingRequests.get(messageId)!;
|
|
2315
3135
|
this.pendingRequests.delete(messageId);
|
|
@@ -2318,7 +3138,9 @@ export class WebSocketClient {
|
|
|
2318
3138
|
} else {
|
|
2319
3139
|
pending.resolve(msg.payload);
|
|
2320
3140
|
}
|
|
2321
|
-
|
|
3141
|
+
matched = true;
|
|
3142
|
+
}
|
|
3143
|
+
if (!matched && this.registerToolsAck) {
|
|
2322
3144
|
const ack = this.registerToolsAck;
|
|
2323
3145
|
this.registerToolsAck = null;
|
|
2324
3146
|
if (msg.type === "Error") {
|
|
@@ -2326,6 +3148,20 @@ export class WebSocketClient {
|
|
|
2326
3148
|
} else {
|
|
2327
3149
|
ack.resolve(msg.payload);
|
|
2328
3150
|
}
|
|
3151
|
+
matched = true;
|
|
3152
|
+
}
|
|
3153
|
+
// Server doesn't echo messageId — if there's exactly one pending
|
|
3154
|
+
// request, deliver the response to it (sequential request/response).
|
|
3155
|
+
if (!matched && this.pendingRequests.size === 1) {
|
|
3156
|
+
const entry = this.pendingRequests.entries().next().value!;
|
|
3157
|
+
const key = entry[0];
|
|
3158
|
+
const pending = entry[1];
|
|
3159
|
+
this.pendingRequests.delete(key);
|
|
3160
|
+
if (msg.type === "Error") {
|
|
3161
|
+
pending.reject(new Error(msg.message || "Unknown error"));
|
|
3162
|
+
} else {
|
|
3163
|
+
pending.resolve(msg.payload);
|
|
3164
|
+
}
|
|
2329
3165
|
}
|
|
2330
3166
|
break;
|
|
2331
3167
|
}
|
|
@@ -2371,6 +3207,8 @@ export class WebSocketClient {
|
|
|
2371
3207
|
msg.payload.tool_call_history || msg.payload.toolCallHistory,
|
|
2372
3208
|
executionTimeMs:
|
|
2373
3209
|
msg.payload.execution_time_ms || msg.payload.executionTimeMs || 0,
|
|
3210
|
+
contextWindow:
|
|
3211
|
+
msg.payload.context_window || msg.payload.contextWindow,
|
|
2374
3212
|
} as ChatStreamEvent);
|
|
2375
3213
|
this.chatStreams.delete(chatId);
|
|
2376
3214
|
stream.close();
|
|
@@ -2566,6 +3404,33 @@ export class WebSocketClient {
|
|
|
2566
3404
|
this.ws.send(JSON.stringify(request));
|
|
2567
3405
|
}
|
|
2568
3406
|
|
|
3407
|
+
/**
|
|
3408
|
+
* Stateless raw LLM completion via WebSocket.
|
|
3409
|
+
*
|
|
3410
|
+
* Sends a RawComplete message and waits for the Success response.
|
|
3411
|
+
* Preferred over HTTP for deployed instances: the persistent WSS
|
|
3412
|
+
* connection is already authenticated and won't be killed by reverse
|
|
3413
|
+
* proxy timeouts.
|
|
3414
|
+
*/
|
|
3415
|
+
async rawCompletion(
|
|
3416
|
+
request: RawCompletionRequest,
|
|
3417
|
+
): Promise<RawCompletionResponse> {
|
|
3418
|
+
await this.ensureConnected();
|
|
3419
|
+
const messageId = this.genMessageId();
|
|
3420
|
+
const payload = await this.sendRequest({
|
|
3421
|
+
type: "RawComplete",
|
|
3422
|
+
messageId,
|
|
3423
|
+
payload: {
|
|
3424
|
+
system_prompt: request.system_prompt,
|
|
3425
|
+
message: request.message,
|
|
3426
|
+
...(request.provider && { provider: request.provider }),
|
|
3427
|
+
...(request.model && { model: request.model }),
|
|
3428
|
+
...(request.max_tokens != null && { max_tokens: request.max_tokens }),
|
|
3429
|
+
},
|
|
3430
|
+
});
|
|
3431
|
+
return { content: payload?.data?.content || "" };
|
|
3432
|
+
}
|
|
3433
|
+
|
|
2569
3434
|
/**
|
|
2570
3435
|
* Close the WebSocket connection.
|
|
2571
3436
|
*/
|