@ekodb/ekodb-client 0.15.1 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client.d.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  import { QueryBuilder } from "./query-builder";
5
5
  import { SearchQuery, SearchResponse } from "./search";
6
6
  import { Schema, SchemaBuilder, CollectionMetadata } from "./schema";
7
- import { Script, FunctionResult } from "./functions";
7
+ import { UserFunction, FunctionResult } from "./functions";
8
8
  export interface Record {
9
9
  [key: string]: any;
10
10
  }
@@ -156,6 +156,7 @@ export interface CreateChatSessionRequest {
156
156
  llm_provider: string;
157
157
  llm_model?: string;
158
158
  system_prompt?: string;
159
+ agent_id?: string;
159
160
  bypass_ripple?: boolean;
160
161
  parent_id?: string;
161
162
  branch_point_idx?: number;
@@ -171,6 +172,9 @@ export interface ChatMessageRequest {
171
172
  max_iterations?: number;
172
173
  tool_config?: ToolConfig;
173
174
  llm_model?: string;
175
+ client_tools?: ClientToolDefinition[];
176
+ confirm_tools?: string[];
177
+ exclude_tools?: string[];
174
178
  }
175
179
  export interface TokenUsage {
176
180
  prompt_tokens: number;
@@ -193,6 +197,7 @@ export interface ChatSession {
193
197
  llm_model: string;
194
198
  collections: CollectionConfig[];
195
199
  system_prompt?: string;
200
+ agent_id?: string;
196
201
  title?: string;
197
202
  message_count: number;
198
203
  }
@@ -285,23 +290,6 @@ export interface RawCompletionRequest {
285
290
  export interface RawCompletionResponse {
286
291
  content: string;
287
292
  }
288
- /**
289
- * User function definition - reusable sequence of Functions that can be called by Scripts
290
- */
291
- export interface UserFunction {
292
- label: string;
293
- name: string;
294
- description?: string;
295
- version?: string;
296
- parameters: {
297
- [key: string]: ParameterDefinition;
298
- };
299
- functions: FunctionStageConfig[];
300
- tags?: string[];
301
- id?: string;
302
- created_at?: string;
303
- updated_at?: string;
304
- }
305
293
  /**
306
294
  * Parameter definition for functions
307
295
  */
@@ -821,6 +809,11 @@ export declare class EkoDBClient {
821
809
  * Send a message in an existing chat session
822
810
  */
823
811
  chatMessage(sessionId: string, request: ChatMessageRequest): Promise<ChatResponse>;
812
+ /**
813
+ * Submit a client tool result for an in-flight SSE chat stream.
814
+ * Unblocks ekoDB's tool loop so it can feed the result to the LLM.
815
+ */
816
+ submitChatToolResult(chatId: string, callId: string, success: boolean, result?: any, error?: string): Promise<void>;
824
817
  /**
825
818
  * Send a message in an existing chat session via SSE streaming.
826
819
  *
@@ -902,29 +895,29 @@ export declare class EkoDBClient {
902
895
  */
903
896
  getChatMessage(sessionId: string, messageId: string): Promise<Record>;
904
897
  /**
905
- * Save a new script definition
898
+ * Save a new function definition
906
899
  */
907
- saveScript(script: Script): Promise<string>;
900
+ saveFunction(script: UserFunction): Promise<string>;
908
901
  /**
909
- * Get a script by ID
902
+ * Get a function by ID
910
903
  */
911
- getScript(id: string): Promise<Script>;
904
+ getFunction(id: string): Promise<UserFunction>;
912
905
  /**
913
- * List all scripts, optionally filtered by tags
906
+ * List all functions, optionally filtered by tags
914
907
  */
915
- listScripts(tags?: string[]): Promise<Script[]>;
908
+ listFunctions(tags?: string[]): Promise<UserFunction[]>;
916
909
  /**
917
- * Update an existing script by ID
910
+ * Update an existing function by ID
918
911
  */
919
- updateScript(id: string, script: Script): Promise<void>;
912
+ updateFunction(id: string, script: UserFunction): Promise<void>;
920
913
  /**
921
- * Delete a script by ID
914
+ * Delete a function by ID
922
915
  */
923
- deleteScript(id: string): Promise<void>;
916
+ deleteFunction(id: string): Promise<void>;
924
917
  /**
925
- * Call a saved script by ID or label
918
+ * Call a saved function by ID or label
926
919
  */
927
- callScript(idOrLabel: string, params?: {
920
+ callFunction(idOrLabel: string, params?: {
928
921
  [key: string]: any;
929
922
  }): Promise<FunctionResult>;
930
923
  /**
@@ -1058,6 +1051,17 @@ export declare class EkoDBClient {
1058
1051
  * @returns Number of documents in the collection
1059
1052
  */
1060
1053
  countDocuments(collection: string): Promise<number>;
1054
+ /**
1055
+ * Subscribe to collection mutations via SSE (Server-Sent Events).
1056
+ *
1057
+ * Returns an EventStream that emits MutationNotification events.
1058
+ * Use this when WebSocket connections aren't available (e.g. behind
1059
+ * reverse proxies that block WS upgrades).
1060
+ */
1061
+ subscribeSSE(collection: string, options?: {
1062
+ filterField?: string;
1063
+ filterValue?: string;
1064
+ }): EventStream<MutationNotification>;
1061
1065
  /**
1062
1066
  * Create a WebSocket client
1063
1067
  */
package/dist/client.js CHANGED
@@ -1098,6 +1098,18 @@ class EkoDBClient {
1098
1098
  async chatMessage(sessionId, request) {
1099
1099
  return this.makeRequest("POST", `/api/chat/${sessionId}/messages`, request, 0, true);
1100
1100
  }
1101
+ /**
1102
+ * Submit a client tool result for an in-flight SSE chat stream.
1103
+ * Unblocks ekoDB's tool loop so it can feed the result to the LLM.
1104
+ */
1105
+ async submitChatToolResult(chatId, callId, success, result, error) {
1106
+ await this.makeRequest("POST", `/api/chat/${chatId}/tool-result`, {
1107
+ call_id: callId,
1108
+ success,
1109
+ ...(result !== undefined && { result }),
1110
+ ...(error !== undefined && { error }),
1111
+ }, 0, true);
1112
+ }
1101
1113
  /**
1102
1114
  * Send a message in an existing chat session via SSE streaming.
1103
1115
  *
@@ -1307,41 +1319,41 @@ class EkoDBClient {
1307
1319
  // SCRIPTS API
1308
1320
  // ========================================================================
1309
1321
  /**
1310
- * Save a new script definition
1322
+ * Save a new function definition
1311
1323
  */
1312
- async saveScript(script) {
1324
+ async saveFunction(script) {
1313
1325
  const result = await this.makeRequest("POST", "/api/functions", script);
1314
1326
  return result.id;
1315
1327
  }
1316
1328
  /**
1317
- * Get a script by ID
1329
+ * Get a function by ID
1318
1330
  */
1319
- async getScript(id) {
1331
+ async getFunction(id) {
1320
1332
  return this.makeRequest("GET", `/api/functions/${id}`);
1321
1333
  }
1322
1334
  /**
1323
- * List all scripts, optionally filtered by tags
1335
+ * List all functions, optionally filtered by tags
1324
1336
  */
1325
- async listScripts(tags) {
1337
+ async listFunctions(tags) {
1326
1338
  const params = tags ? `?tags=${tags.join(",")}` : "";
1327
1339
  return this.makeRequest("GET", `/api/functions${params}`);
1328
1340
  }
1329
1341
  /**
1330
- * Update an existing script by ID
1342
+ * Update an existing function by ID
1331
1343
  */
1332
- async updateScript(id, script) {
1344
+ async updateFunction(id, script) {
1333
1345
  await this.makeRequest("PUT", `/api/functions/${id}`, script);
1334
1346
  }
1335
1347
  /**
1336
- * Delete a script by ID
1348
+ * Delete a function by ID
1337
1349
  */
1338
- async deleteScript(id) {
1350
+ async deleteFunction(id) {
1339
1351
  await this.makeRequest("DELETE", `/api/functions/${id}`);
1340
1352
  }
1341
1353
  /**
1342
- * Call a saved script by ID or label
1354
+ * Call a saved function by ID or label
1343
1355
  */
1344
- async callScript(idOrLabel, params) {
1356
+ async callFunction(idOrLabel, params) {
1345
1357
  return this.makeRequest("POST", `/api/functions/${idOrLabel}`, params || {});
1346
1358
  }
1347
1359
  // ========================================================================
@@ -1613,6 +1625,99 @@ class EkoDBClient {
1613
1625
  const records = await this.find(collection, query);
1614
1626
  return records.length;
1615
1627
  }
1628
+ /**
1629
+ * Subscribe to collection mutations via SSE (Server-Sent Events).
1630
+ *
1631
+ * Returns an EventStream that emits MutationNotification events.
1632
+ * Use this when WebSocket connections aren't available (e.g. behind
1633
+ * reverse proxies that block WS upgrades).
1634
+ */
1635
+ subscribeSSE(collection, options) {
1636
+ const stream = new EventStream();
1637
+ (async () => {
1638
+ try {
1639
+ let token = await this.getToken();
1640
+ if (!token) {
1641
+ await this.refreshToken();
1642
+ token = await this.getToken();
1643
+ }
1644
+ let url = `${this.baseURL}/api/subscribe/${encodeURIComponent(collection)}`;
1645
+ const params = [];
1646
+ if (options?.filterField)
1647
+ params.push(`filter_field=${encodeURIComponent(options.filterField)}`);
1648
+ if (options?.filterValue)
1649
+ params.push(`filter_value=${encodeURIComponent(options.filterValue)}`);
1650
+ if (params.length > 0)
1651
+ url += `?${params.join("&")}`;
1652
+ const response = await fetch(url, {
1653
+ method: "GET",
1654
+ headers: {
1655
+ Accept: "text/event-stream",
1656
+ Authorization: `Bearer ${token}`,
1657
+ },
1658
+ });
1659
+ if (!response.ok) {
1660
+ const body = await response.text();
1661
+ stream.emit("error", `SSE subscribe failed (${response.status}): ${body}`);
1662
+ stream.close();
1663
+ return;
1664
+ }
1665
+ const reader = response.body?.getReader();
1666
+ if (!reader) {
1667
+ stream.emit("error", "SSE subscribe failed: streaming not supported");
1668
+ stream.close();
1669
+ return;
1670
+ }
1671
+ const decoder = new TextDecoder("utf-8");
1672
+ let buffer = "";
1673
+ let eventType = "";
1674
+ let dataLines = [];
1675
+ while (!stream.closed) {
1676
+ const { value, done } = await reader.read();
1677
+ if (done)
1678
+ break;
1679
+ buffer += decoder.decode(value, { stream: true });
1680
+ const lines = buffer.split("\n");
1681
+ buffer = lines.pop() ?? "";
1682
+ for (const line of lines) {
1683
+ if (line === "") {
1684
+ // End of event block
1685
+ if (eventType === "mutation" && dataLines.length > 0) {
1686
+ try {
1687
+ const payload = JSON.parse(dataLines.join("\n"));
1688
+ stream.emit("event", {
1689
+ collection: payload.collection,
1690
+ event: payload.event,
1691
+ recordIds: payload.record_ids,
1692
+ records: payload.records,
1693
+ timestamp: payload.timestamp,
1694
+ });
1695
+ }
1696
+ catch {
1697
+ // skip malformed data
1698
+ }
1699
+ }
1700
+ eventType = "";
1701
+ dataLines = [];
1702
+ continue;
1703
+ }
1704
+ if (line.startsWith("event: ")) {
1705
+ eventType = line.slice(7).trim();
1706
+ }
1707
+ else if (line.startsWith("data: ")) {
1708
+ dataLines.push(line.slice(6).trim());
1709
+ }
1710
+ }
1711
+ }
1712
+ stream.close();
1713
+ }
1714
+ catch (err) {
1715
+ stream.emit("error", err.message ?? String(err));
1716
+ stream.close();
1717
+ }
1718
+ })();
1719
+ return stream;
1720
+ }
1616
1721
  /**
1617
1722
  * Create a WebSocket client
1618
1723
  */
@@ -1720,7 +1825,10 @@ class EkoDBClient {
1720
1825
  limit,
1721
1826
  };
1722
1827
  const response = await this.search(collection, searchQuery);
1723
- return response.results.map((r) => r.record);
1828
+ return response.results.map((r) => ({
1829
+ ...r.record,
1830
+ _score: r.score,
1831
+ }));
1724
1832
  }
1725
1833
  /**
1726
1834
  * Find all records in a collection with a limit
@@ -590,14 +590,14 @@ function mockErrorResponse(status, message) {
590
590
  parameters: {},
591
591
  functions: [],
592
592
  };
593
- const result = await client.saveScript(script);
593
+ const result = await client.saveFunction(script);
594
594
  (0, vitest_1.expect)(result).toBeDefined();
595
595
  });
596
596
  (0, vitest_1.it)("gets script by ID", async () => {
597
597
  const client = createTestClient();
598
598
  mockTokenResponse();
599
599
  mockJsonResponse({ id: "func_123", label: "my_function" });
600
- const result = await client.getScript("func_123");
600
+ const result = await client.getFunction("func_123");
601
601
  (0, vitest_1.expect)(result).toBeDefined();
602
602
  });
603
603
  (0, vitest_1.it)("updates script", async () => {
@@ -610,7 +610,7 @@ function mockErrorResponse(status, message) {
610
610
  parameters: {},
611
611
  functions: [],
612
612
  };
613
- await (0, vitest_1.expect)(client.updateScript("func_123", script)).resolves.not.toThrow();
613
+ await (0, vitest_1.expect)(client.updateFunction("func_123", script)).resolves.not.toThrow();
614
614
  });
615
615
  });
616
616
  (0, vitest_1.describe)("EkoDBClient chat advanced", () => {
@@ -1944,6 +1944,7 @@ function mockErrorResponse(status, message) {
1944
1944
  (0, vitest_1.expect)(result).toHaveLength(1);
1945
1945
  (0, vitest_1.expect)(result[0]).toHaveProperty("id", "doc_1");
1946
1946
  (0, vitest_1.expect)(result[0]).toHaveProperty("title", "ML Guide");
1947
+ (0, vitest_1.expect)(result[0]).toHaveProperty("_score", 0.95);
1947
1948
  });
1948
1949
  });
1949
1950
  // ============================================================================
@@ -2022,3 +2023,204 @@ function mockErrorResponse(status, message) {
2022
2023
  (0, vitest_1.expect)(mockFetch).toHaveBeenCalledTimes(2);
2023
2024
  });
2024
2025
  });
2026
+ // ============================================================================
2027
+ // agent_id Tests
2028
+ // ============================================================================
2029
+ (0, vitest_1.describe)("agent_id on chat types", () => {
2030
+ (0, vitest_1.it)("CreateChatSessionRequest includes agent_id", () => {
2031
+ const req = {
2032
+ collections: [{ collection_name: "docs" }],
2033
+ llm_provider: "openai",
2034
+ agent_id: "my-agent",
2035
+ };
2036
+ (0, vitest_1.expect)(req.agent_id).toBe("my-agent");
2037
+ });
2038
+ (0, vitest_1.it)("CreateChatSessionRequest omits agent_id when undefined", () => {
2039
+ const req = {
2040
+ collections: [],
2041
+ llm_provider: "openai",
2042
+ };
2043
+ (0, vitest_1.expect)(req.agent_id).toBeUndefined();
2044
+ });
2045
+ (0, vitest_1.it)("ChatSession includes agent_id", () => {
2046
+ const session = {
2047
+ chat_id: "c1",
2048
+ created_at: "2026-01-01",
2049
+ updated_at: "2026-01-01",
2050
+ llm_provider: "openai",
2051
+ llm_model: "gpt-4",
2052
+ collections: [],
2053
+ agent_id: "bot-1",
2054
+ message_count: 0,
2055
+ };
2056
+ (0, vitest_1.expect)(session.agent_id).toBe("bot-1");
2057
+ });
2058
+ (0, vitest_1.it)("ChatSession allows missing agent_id", () => {
2059
+ const session = {
2060
+ chat_id: "c1",
2061
+ created_at: "2026-01-01",
2062
+ updated_at: "2026-01-01",
2063
+ llm_provider: "openai",
2064
+ llm_model: "gpt-4",
2065
+ collections: [],
2066
+ message_count: 0,
2067
+ };
2068
+ (0, vitest_1.expect)(session.agent_id).toBeUndefined();
2069
+ });
2070
+ });
2071
+ // ============================================================================
2072
+ // client_tools / confirm_tools / exclude_tools Tests
2073
+ // ============================================================================
2074
+ (0, vitest_1.describe)("ChatMessageRequest tool fields", () => {
2075
+ (0, vitest_1.it)("includes client_tools, confirm_tools, exclude_tools", () => {
2076
+ const req = {
2077
+ message: "hello",
2078
+ client_tools: [
2079
+ {
2080
+ name: "weather",
2081
+ description: "Get weather",
2082
+ parameters: { type: "object" },
2083
+ },
2084
+ ],
2085
+ confirm_tools: ["shell_exec"],
2086
+ exclude_tools: ["file_delete"],
2087
+ };
2088
+ (0, vitest_1.expect)(req.client_tools).toHaveLength(1);
2089
+ (0, vitest_1.expect)(req.client_tools[0].name).toBe("weather");
2090
+ (0, vitest_1.expect)(req.confirm_tools).toEqual(["shell_exec"]);
2091
+ (0, vitest_1.expect)(req.exclude_tools).toEqual(["file_delete"]);
2092
+ });
2093
+ (0, vitest_1.it)("tool fields are optional", () => {
2094
+ const req = { message: "hi" };
2095
+ (0, vitest_1.expect)(req.client_tools).toBeUndefined();
2096
+ (0, vitest_1.expect)(req.confirm_tools).toBeUndefined();
2097
+ (0, vitest_1.expect)(req.exclude_tools).toBeUndefined();
2098
+ });
2099
+ (0, vitest_1.it)("ClientToolDefinition has correct shape", () => {
2100
+ const tool = {
2101
+ name: "calc",
2102
+ description: "Calculator",
2103
+ parameters: { type: "object", properties: {} },
2104
+ };
2105
+ (0, vitest_1.expect)(tool.name).toBe("calc");
2106
+ (0, vitest_1.expect)(tool.description).toBe("Calculator");
2107
+ (0, vitest_1.expect)(tool.parameters.type).toBe("object");
2108
+ });
2109
+ });
2110
+ // ============================================================================
2111
+ // submitChatToolResult Tests
2112
+ // ============================================================================
2113
+ (0, vitest_1.describe)("submitChatToolResult", () => {
2114
+ (0, vitest_1.it)("sends tool result to correct endpoint", async () => {
2115
+ const client = createTestClient();
2116
+ mockTokenResponse();
2117
+ mockJsonResponse({});
2118
+ await client.submitChatToolResult("chat-123", "call-456", true, {
2119
+ temp: "72F",
2120
+ });
2121
+ (0, vitest_1.expect)(mockFetch).toHaveBeenCalledTimes(2);
2122
+ const call = mockFetch.mock.calls[1];
2123
+ (0, vitest_1.expect)(call[0]).toContain("/api/chat/chat-123/tool-result");
2124
+ const body = JSON.parse(call[1].body);
2125
+ (0, vitest_1.expect)(body.call_id).toBe("call-456");
2126
+ (0, vitest_1.expect)(body.success).toBe(true);
2127
+ (0, vitest_1.expect)(body.result.temp).toBe("72F");
2128
+ });
2129
+ (0, vitest_1.it)("sends error result", async () => {
2130
+ const client = createTestClient();
2131
+ mockTokenResponse();
2132
+ mockJsonResponse({});
2133
+ await client.submitChatToolResult("chat-123", "call-456", false, undefined, "tool crashed");
2134
+ const call = mockFetch.mock.calls[1];
2135
+ const body = JSON.parse(call[1].body);
2136
+ (0, vitest_1.expect)(body.success).toBe(false);
2137
+ (0, vitest_1.expect)(body.error).toBe("tool crashed");
2138
+ (0, vitest_1.expect)(body.result).toBeUndefined();
2139
+ });
2140
+ });
2141
+ // ============================================================================
2142
+ // subscribeSSE Tests
2143
+ // ============================================================================
2144
+ (0, vitest_1.describe)("subscribeSSE", () => {
2145
+ /** Create a mock ReadableStream from a string */
2146
+ function mockReadableStream(data) {
2147
+ const encoder = new TextEncoder();
2148
+ const bytes = encoder.encode(data);
2149
+ let sent = false;
2150
+ return {
2151
+ getReader: () => ({
2152
+ read: async () => {
2153
+ if (!sent) {
2154
+ sent = true;
2155
+ return { value: bytes, done: false };
2156
+ }
2157
+ return { value: undefined, done: true };
2158
+ },
2159
+ }),
2160
+ };
2161
+ }
2162
+ (0, vitest_1.it)("parses mutation events from SSE stream", async () => {
2163
+ const client = createTestClient();
2164
+ mockTokenResponse();
2165
+ const sseBody = "event: subscribed\ndata: {}\n\n" +
2166
+ 'event: mutation\ndata: {"collection":"orders","event":"insert","record_ids":["r1"],"timestamp":"t1"}\n\n' +
2167
+ 'event: mutation\ndata: {"collection":"orders","event":"update","record_ids":["r2"],"timestamp":"t2"}\n\n';
2168
+ mockFetch.mockResolvedValueOnce({
2169
+ ok: true,
2170
+ status: 200,
2171
+ body: mockReadableStream(sseBody),
2172
+ headers: new Headers({ "content-type": "text/event-stream" }),
2173
+ });
2174
+ const stream = client.subscribeSSE("orders");
2175
+ const events = [];
2176
+ await new Promise((resolve) => {
2177
+ stream.on("event", (e) => events.push(e));
2178
+ stream.on("close", resolve);
2179
+ setTimeout(resolve, 100);
2180
+ });
2181
+ (0, vitest_1.expect)(events).toHaveLength(2);
2182
+ (0, vitest_1.expect)(events[0].event).toBe("insert");
2183
+ (0, vitest_1.expect)(events[0].recordIds).toEqual(["r1"]);
2184
+ (0, vitest_1.expect)(events[1].event).toBe("update");
2185
+ (0, vitest_1.expect)(events[1].recordIds).toEqual(["r2"]);
2186
+ });
2187
+ (0, vitest_1.it)("passes filter params in URL", async () => {
2188
+ const client = createTestClient();
2189
+ mockTokenResponse();
2190
+ mockFetch.mockResolvedValueOnce({
2191
+ ok: true,
2192
+ status: 200,
2193
+ body: mockReadableStream(""),
2194
+ headers: new Headers({ "content-type": "text/event-stream" }),
2195
+ });
2196
+ client.subscribeSSE("orders", {
2197
+ filterField: "status",
2198
+ filterValue: "active",
2199
+ });
2200
+ // Wait for async fetch
2201
+ await new Promise((r) => setTimeout(r, 50));
2202
+ const call = mockFetch.mock.calls[1];
2203
+ (0, vitest_1.expect)(call[0]).toContain("/api/subscribe/orders");
2204
+ (0, vitest_1.expect)(call[0]).toContain("filter_field=status");
2205
+ (0, vitest_1.expect)(call[0]).toContain("filter_value=active");
2206
+ });
2207
+ (0, vitest_1.it)("emits error on HTTP failure", async () => {
2208
+ const client = createTestClient();
2209
+ mockTokenResponse();
2210
+ mockFetch.mockResolvedValueOnce({
2211
+ ok: false,
2212
+ status: 401,
2213
+ text: async () => "Unauthorized",
2214
+ headers: new Headers(),
2215
+ });
2216
+ const stream = client.subscribeSSE("orders");
2217
+ const errors = [];
2218
+ await new Promise((resolve) => {
2219
+ stream.on("error", (e) => errors.push(e));
2220
+ stream.on("close", resolve);
2221
+ setTimeout(resolve, 100);
2222
+ });
2223
+ (0, vitest_1.expect)(errors).toHaveLength(1);
2224
+ (0, vitest_1.expect)(errors[0]).toContain("401");
2225
+ });
2226
+ });
@@ -1,7 +1,9 @@
1
1
  /**
2
- * Scripts API for ekoDB TypeScript client
2
+ * Functions API for ekoDB TypeScript client
3
3
  */
4
- export interface Script {
4
+ /** A reusable sequence of Functions stored in ekoDB. */
5
+ export interface UserFunction {
6
+ id?: string;
5
7
  label: string;
6
8
  name: string;
7
9
  description?: string;
@@ -140,7 +142,7 @@ export type FunctionStageConfig = {
140
142
  value: any;
141
143
  } | {
142
144
  type: "If";
143
- condition: ScriptCondition;
145
+ condition: FunctionCondition;
144
146
  then_functions: FunctionStageConfig[];
145
147
  else_functions?: FunctionStageConfig[];
146
148
  } | {
@@ -224,7 +226,7 @@ export interface SortFieldConfig {
224
226
  field: string;
225
227
  ascending: boolean;
226
228
  }
227
- export type ScriptCondition = {
229
+ export type FunctionCondition = {
228
230
  type: "FieldEquals";
229
231
  value: {
230
232
  field: string;
@@ -255,17 +257,17 @@ export type ScriptCondition = {
255
257
  } | {
256
258
  type: "And";
257
259
  value: {
258
- conditions: ScriptCondition[];
260
+ conditions: FunctionCondition[];
259
261
  };
260
262
  } | {
261
263
  type: "Or";
262
264
  value: {
263
- conditions: ScriptCondition[];
265
+ conditions: FunctionCondition[];
264
266
  };
265
267
  } | {
266
268
  type: "Not";
267
269
  value: {
268
- condition: ScriptCondition;
270
+ condition: FunctionCondition;
269
271
  };
270
272
  };
271
273
  export interface FunctionResult {
@@ -314,7 +316,7 @@ export declare const Stage: {
314
316
  embed: (input_field: string, output_field: string, model?: string) => FunctionStageConfig;
315
317
  findById: (collection: string, record_id: string) => FunctionStageConfig;
316
318
  findOne: (collection: string, key: string, value: any) => FunctionStageConfig;
317
- if: (condition: ScriptCondition, thenFunctions: FunctionStageConfig[], elseFunctions?: FunctionStageConfig[]) => FunctionStageConfig;
319
+ if: (condition: FunctionCondition, thenFunctions: FunctionStageConfig[], elseFunctions?: FunctionStageConfig[]) => FunctionStageConfig;
318
320
  forEach: (functions: FunctionStageConfig[]) => FunctionStageConfig;
319
321
  callFunction: (function_label: string, params?: Record<string, any>) => FunctionStageConfig;
320
322
  findOneAndUpdate: (collection: string, record_id: string, updates: Record<string, any>, bypassRipple?: boolean, ttl?: number) => FunctionStageConfig;
package/dist/functions.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /**
3
- * Scripts API for ekoDB TypeScript client
3
+ * Functions API for ekoDB TypeScript client
4
4
  */
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.Stage = exports.ChatMessage = void 0;
package/dist/index.d.ts CHANGED
@@ -9,6 +9,6 @@ export type { WrappedFieldValue } from "./utils";
9
9
  export type { SearchQuery, SearchResult, SearchResponse } from "./search";
10
10
  export type { Schema, FieldTypeSchema, IndexConfig, CollectionMetadata, } from "./schema";
11
11
  export type { JoinConfig } from "./join";
12
- export type { Script, ParameterDefinition, FunctionStageConfig, GroupFunctionConfig, SortFieldConfig, FunctionResult, FunctionStats, StageStats, } from "./functions";
12
+ export type { UserFunction, ParameterDefinition, FunctionStageConfig, GroupFunctionConfig, SortFieldConfig, FunctionResult, FunctionStats, StageStats, } from "./functions";
13
13
  export type { MutationNotification, ChatStreamEvent, ClientToolDefinition, ChatSendOptions, SubscribeOptions, } from "./client";
14
- export type { Record, Query, BatchOperationResult, ClientConfig, RateLimitInfo, CollectionConfig, ChatRequest, CreateChatSessionRequest, ChatMessageRequest, TokenUsage, ChatResponse, ChatSession, ChatSessionResponse, ListSessionsQuery, ListSessionsResponse, GetMessagesQuery, GetMessagesResponse, UpdateSessionRequest, MergeSessionsRequest, ChatModels, EmbedRequest, EmbedResponse, RawCompletionRequest, RawCompletionResponse, UserFunction, ToolChoice, ToolConfig, } from "./client";
14
+ export type { Record, Query, BatchOperationResult, ClientConfig, RateLimitInfo, CollectionConfig, ChatRequest, CreateChatSessionRequest, ChatMessageRequest, TokenUsage, ChatResponse, ChatSession, ChatSessionResponse, ListSessionsQuery, ListSessionsResponse, GetMessagesQuery, GetMessagesResponse, UpdateSessionRequest, MergeSessionsRequest, ChatModels, EmbedRequest, EmbedResponse, RawCompletionRequest, RawCompletionResponse, ToolChoice, ToolConfig, } from "./client";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ekodb/ekodb-client",
3
- "version": "0.15.1",
3
+ "version": "0.16.0",
4
4
  "description": "Official TypeScript/JavaScript client for ekoDB",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -800,7 +800,7 @@ describe("EkoDBClient scripts advanced", () => {
800
800
  parameters: {},
801
801
  functions: [],
802
802
  };
803
- const result = await client.saveScript(script);
803
+ const result = await client.saveFunction(script);
804
804
 
805
805
  expect(result).toBeDefined();
806
806
  });
@@ -811,7 +811,7 @@ describe("EkoDBClient scripts advanced", () => {
811
811
  mockTokenResponse();
812
812
  mockJsonResponse({ id: "func_123", label: "my_function" });
813
813
 
814
- const result = await client.getScript("func_123");
814
+ const result = await client.getFunction("func_123");
815
815
 
816
816
  expect(result).toBeDefined();
817
817
  });
@@ -829,7 +829,7 @@ describe("EkoDBClient scripts advanced", () => {
829
829
  functions: [],
830
830
  };
831
831
  await expect(
832
- client.updateScript("func_123", script),
832
+ client.updateFunction("func_123", script),
833
833
  ).resolves.not.toThrow();
834
834
  });
835
835
  });
@@ -2584,6 +2584,7 @@ describe("EkoDBClient text and hybrid search", () => {
2584
2584
  expect(result).toHaveLength(1);
2585
2585
  expect(result[0]).toHaveProperty("id", "doc_1");
2586
2586
  expect(result[0]).toHaveProperty("title", "ML Guide");
2587
+ expect(result[0]).toHaveProperty("_score", 0.95);
2587
2588
  });
2588
2589
  });
2589
2590
 
@@ -2673,3 +2674,243 @@ describe("EkoDBClient auth token management", () => {
2673
2674
  expect(mockFetch).toHaveBeenCalledTimes(2);
2674
2675
  });
2675
2676
  });
2677
+
2678
+ // ============================================================================
2679
+ // agent_id Tests
2680
+ // ============================================================================
2681
+
2682
+ describe("agent_id on chat types", () => {
2683
+ it("CreateChatSessionRequest includes agent_id", () => {
2684
+ const req: any = {
2685
+ collections: [{ collection_name: "docs" }],
2686
+ llm_provider: "openai",
2687
+ agent_id: "my-agent",
2688
+ };
2689
+ expect(req.agent_id).toBe("my-agent");
2690
+ });
2691
+
2692
+ it("CreateChatSessionRequest omits agent_id when undefined", () => {
2693
+ const req: any = {
2694
+ collections: [],
2695
+ llm_provider: "openai",
2696
+ };
2697
+ expect(req.agent_id).toBeUndefined();
2698
+ });
2699
+
2700
+ it("ChatSession includes agent_id", () => {
2701
+ const session: any = {
2702
+ chat_id: "c1",
2703
+ created_at: "2026-01-01",
2704
+ updated_at: "2026-01-01",
2705
+ llm_provider: "openai",
2706
+ llm_model: "gpt-4",
2707
+ collections: [],
2708
+ agent_id: "bot-1",
2709
+ message_count: 0,
2710
+ };
2711
+ expect(session.agent_id).toBe("bot-1");
2712
+ });
2713
+
2714
+ it("ChatSession allows missing agent_id", () => {
2715
+ const session: any = {
2716
+ chat_id: "c1",
2717
+ created_at: "2026-01-01",
2718
+ updated_at: "2026-01-01",
2719
+ llm_provider: "openai",
2720
+ llm_model: "gpt-4",
2721
+ collections: [],
2722
+ message_count: 0,
2723
+ };
2724
+ expect(session.agent_id).toBeUndefined();
2725
+ });
2726
+ });
2727
+
2728
+ // ============================================================================
2729
+ // client_tools / confirm_tools / exclude_tools Tests
2730
+ // ============================================================================
2731
+
2732
+ describe("ChatMessageRequest tool fields", () => {
2733
+ it("includes client_tools, confirm_tools, exclude_tools", () => {
2734
+ const req: any = {
2735
+ message: "hello",
2736
+ client_tools: [
2737
+ {
2738
+ name: "weather",
2739
+ description: "Get weather",
2740
+ parameters: { type: "object" },
2741
+ },
2742
+ ],
2743
+ confirm_tools: ["shell_exec"],
2744
+ exclude_tools: ["file_delete"],
2745
+ };
2746
+ expect(req.client_tools).toHaveLength(1);
2747
+ expect(req.client_tools[0].name).toBe("weather");
2748
+ expect(req.confirm_tools).toEqual(["shell_exec"]);
2749
+ expect(req.exclude_tools).toEqual(["file_delete"]);
2750
+ });
2751
+
2752
+ it("tool fields are optional", () => {
2753
+ const req: any = { message: "hi" };
2754
+ expect(req.client_tools).toBeUndefined();
2755
+ expect(req.confirm_tools).toBeUndefined();
2756
+ expect(req.exclude_tools).toBeUndefined();
2757
+ });
2758
+
2759
+ it("ClientToolDefinition has correct shape", () => {
2760
+ const tool: any = {
2761
+ name: "calc",
2762
+ description: "Calculator",
2763
+ parameters: { type: "object", properties: {} },
2764
+ };
2765
+ expect(tool.name).toBe("calc");
2766
+ expect(tool.description).toBe("Calculator");
2767
+ expect(tool.parameters.type).toBe("object");
2768
+ });
2769
+ });
2770
+
2771
+ // ============================================================================
2772
+ // submitChatToolResult Tests
2773
+ // ============================================================================
2774
+
2775
+ describe("submitChatToolResult", () => {
2776
+ it("sends tool result to correct endpoint", async () => {
2777
+ const client = createTestClient();
2778
+ mockTokenResponse();
2779
+ mockJsonResponse({});
2780
+
2781
+ await client.submitChatToolResult("chat-123", "call-456", true, {
2782
+ temp: "72F",
2783
+ });
2784
+
2785
+ expect(mockFetch).toHaveBeenCalledTimes(2);
2786
+ const call = mockFetch.mock.calls[1];
2787
+ expect(call[0]).toContain("/api/chat/chat-123/tool-result");
2788
+ const body = JSON.parse(call[1].body);
2789
+ expect(body.call_id).toBe("call-456");
2790
+ expect(body.success).toBe(true);
2791
+ expect(body.result.temp).toBe("72F");
2792
+ });
2793
+
2794
+ it("sends error result", async () => {
2795
+ const client = createTestClient();
2796
+ mockTokenResponse();
2797
+ mockJsonResponse({});
2798
+
2799
+ await client.submitChatToolResult(
2800
+ "chat-123",
2801
+ "call-456",
2802
+ false,
2803
+ undefined,
2804
+ "tool crashed",
2805
+ );
2806
+
2807
+ const call = mockFetch.mock.calls[1];
2808
+ const body = JSON.parse(call[1].body);
2809
+ expect(body.success).toBe(false);
2810
+ expect(body.error).toBe("tool crashed");
2811
+ expect(body.result).toBeUndefined();
2812
+ });
2813
+ });
2814
+
2815
+ // ============================================================================
2816
+ // subscribeSSE Tests
2817
+ // ============================================================================
2818
+
2819
+ describe("subscribeSSE", () => {
2820
+ /** Create a mock ReadableStream from a string */
2821
+ function mockReadableStream(data: string) {
2822
+ const encoder = new TextEncoder();
2823
+ const bytes = encoder.encode(data);
2824
+ let sent = false;
2825
+ return {
2826
+ getReader: () => ({
2827
+ read: async () => {
2828
+ if (!sent) {
2829
+ sent = true;
2830
+ return { value: bytes, done: false };
2831
+ }
2832
+ return { value: undefined, done: true };
2833
+ },
2834
+ }),
2835
+ };
2836
+ }
2837
+
2838
+ it("parses mutation events from SSE stream", async () => {
2839
+ const client = createTestClient();
2840
+ mockTokenResponse();
2841
+
2842
+ const sseBody =
2843
+ "event: subscribed\ndata: {}\n\n" +
2844
+ 'event: mutation\ndata: {"collection":"orders","event":"insert","record_ids":["r1"],"timestamp":"t1"}\n\n' +
2845
+ 'event: mutation\ndata: {"collection":"orders","event":"update","record_ids":["r2"],"timestamp":"t2"}\n\n';
2846
+
2847
+ mockFetch.mockResolvedValueOnce({
2848
+ ok: true,
2849
+ status: 200,
2850
+ body: mockReadableStream(sseBody),
2851
+ headers: new Headers({ "content-type": "text/event-stream" }),
2852
+ });
2853
+
2854
+ const stream = client.subscribeSSE("orders");
2855
+ const events: any[] = [];
2856
+ await new Promise<void>((resolve) => {
2857
+ stream.on("event", (e: any) => events.push(e));
2858
+ stream.on("close", resolve);
2859
+ setTimeout(resolve, 100);
2860
+ });
2861
+
2862
+ expect(events).toHaveLength(2);
2863
+ expect(events[0].event).toBe("insert");
2864
+ expect(events[0].recordIds).toEqual(["r1"]);
2865
+ expect(events[1].event).toBe("update");
2866
+ expect(events[1].recordIds).toEqual(["r2"]);
2867
+ });
2868
+
2869
+ it("passes filter params in URL", async () => {
2870
+ const client = createTestClient();
2871
+ mockTokenResponse();
2872
+
2873
+ mockFetch.mockResolvedValueOnce({
2874
+ ok: true,
2875
+ status: 200,
2876
+ body: mockReadableStream(""),
2877
+ headers: new Headers({ "content-type": "text/event-stream" }),
2878
+ });
2879
+
2880
+ client.subscribeSSE("orders", {
2881
+ filterField: "status",
2882
+ filterValue: "active",
2883
+ });
2884
+
2885
+ // Wait for async fetch
2886
+ await new Promise((r) => setTimeout(r, 50));
2887
+
2888
+ const call = mockFetch.mock.calls[1];
2889
+ expect(call[0]).toContain("/api/subscribe/orders");
2890
+ expect(call[0]).toContain("filter_field=status");
2891
+ expect(call[0]).toContain("filter_value=active");
2892
+ });
2893
+
2894
+ it("emits error on HTTP failure", async () => {
2895
+ const client = createTestClient();
2896
+ mockTokenResponse();
2897
+
2898
+ mockFetch.mockResolvedValueOnce({
2899
+ ok: false,
2900
+ status: 401,
2901
+ text: async () => "Unauthorized",
2902
+ headers: new Headers(),
2903
+ });
2904
+
2905
+ const stream = client.subscribeSSE("orders");
2906
+ const errors: string[] = [];
2907
+ await new Promise<void>((resolve) => {
2908
+ stream.on("error", (e: string) => errors.push(e));
2909
+ stream.on("close", resolve);
2910
+ setTimeout(resolve, 100);
2911
+ });
2912
+
2913
+ expect(errors).toHaveLength(1);
2914
+ expect(errors[0]).toContain("401");
2915
+ });
2916
+ });
package/src/client.ts CHANGED
@@ -6,7 +6,7 @@ import { encode, decode } from "@msgpack/msgpack";
6
6
  import { QueryBuilder, Query as QueryBuilderQuery } from "./query-builder";
7
7
  import { SearchQuery, SearchResponse } from "./search";
8
8
  import { Schema, SchemaBuilder, CollectionMetadata } from "./schema";
9
- import { Script, FunctionResult } from "./functions";
9
+ import { UserFunction, FunctionResult } from "./functions";
10
10
 
11
11
  export interface Record {
12
12
  [key: string]: any;
@@ -188,6 +188,7 @@ export interface CreateChatSessionRequest {
188
188
  llm_provider: string;
189
189
  llm_model?: string;
190
190
  system_prompt?: string;
191
+ agent_id?: string;
191
192
  bypass_ripple?: boolean;
192
193
  parent_id?: string;
193
194
  branch_point_idx?: number;
@@ -204,6 +205,9 @@ export interface ChatMessageRequest {
204
205
  max_iterations?: number;
205
206
  tool_config?: ToolConfig;
206
207
  llm_model?: string;
208
+ client_tools?: ClientToolDefinition[];
209
+ confirm_tools?: string[];
210
+ exclude_tools?: string[];
207
211
  }
208
212
 
209
213
  export interface TokenUsage {
@@ -229,6 +233,7 @@ export interface ChatSession {
229
233
  llm_model: string;
230
234
  collections: CollectionConfig[];
231
235
  system_prompt?: string;
236
+ agent_id?: string;
232
237
  title?: string;
233
238
  message_count: number;
234
239
  }
@@ -335,21 +340,7 @@ export interface RawCompletionResponse {
335
340
  content: string;
336
341
  }
337
342
 
338
- /**
339
- * User function definition - reusable sequence of Functions that can be called by Scripts
340
- */
341
- export interface UserFunction {
342
- label: string;
343
- name: string;
344
- description?: string;
345
- version?: string;
346
- parameters: { [key: string]: ParameterDefinition };
347
- functions: FunctionStageConfig[];
348
- tags?: string[];
349
- id?: string;
350
- created_at?: string;
351
- updated_at?: string;
352
- }
343
+ // UserFunction is defined in functions.ts and re-exported from there.
353
344
 
354
345
  /**
355
346
  * Parameter definition for functions
@@ -1769,6 +1760,31 @@ export class EkoDBClient {
1769
1760
  );
1770
1761
  }
1771
1762
 
1763
+ /**
1764
+ * Submit a client tool result for an in-flight SSE chat stream.
1765
+ * Unblocks ekoDB's tool loop so it can feed the result to the LLM.
1766
+ */
1767
+ async submitChatToolResult(
1768
+ chatId: string,
1769
+ callId: string,
1770
+ success: boolean,
1771
+ result?: any,
1772
+ error?: string,
1773
+ ): Promise<void> {
1774
+ await this.makeRequest(
1775
+ "POST",
1776
+ `/api/chat/${chatId}/tool-result`,
1777
+ {
1778
+ call_id: callId,
1779
+ success,
1780
+ ...(result !== undefined && { result }),
1781
+ ...(error !== undefined && { error }),
1782
+ },
1783
+ 0,
1784
+ true,
1785
+ );
1786
+ }
1787
+
1772
1788
  /**
1773
1789
  * Send a message in an existing chat session via SSE streaming.
1774
1790
  *
@@ -2106,9 +2122,9 @@ export class EkoDBClient {
2106
2122
  // ========================================================================
2107
2123
 
2108
2124
  /**
2109
- * Save a new script definition
2125
+ * Save a new function definition
2110
2126
  */
2111
- async saveScript(script: Script): Promise<string> {
2127
+ async saveFunction(script: UserFunction): Promise<string> {
2112
2128
  const result = await this.makeRequest<{ id: string }>(
2113
2129
  "POST",
2114
2130
  "/api/functions",
@@ -2118,38 +2134,38 @@ export class EkoDBClient {
2118
2134
  }
2119
2135
 
2120
2136
  /**
2121
- * Get a script by ID
2137
+ * Get a function by ID
2122
2138
  */
2123
- async getScript(id: string): Promise<Script> {
2124
- return this.makeRequest<Script>("GET", `/api/functions/${id}`);
2139
+ async getFunction(id: string): Promise<UserFunction> {
2140
+ return this.makeRequest<UserFunction>("GET", `/api/functions/${id}`);
2125
2141
  }
2126
2142
 
2127
2143
  /**
2128
- * List all scripts, optionally filtered by tags
2144
+ * List all functions, optionally filtered by tags
2129
2145
  */
2130
- async listScripts(tags?: string[]): Promise<Script[]> {
2146
+ async listFunctions(tags?: string[]): Promise<UserFunction[]> {
2131
2147
  const params = tags ? `?tags=${tags.join(",")}` : "";
2132
- return this.makeRequest<Script[]>("GET", `/api/functions${params}`);
2148
+ return this.makeRequest<UserFunction[]>("GET", `/api/functions${params}`);
2133
2149
  }
2134
2150
 
2135
2151
  /**
2136
- * Update an existing script by ID
2152
+ * Update an existing function by ID
2137
2153
  */
2138
- async updateScript(id: string, script: Script): Promise<void> {
2154
+ async updateFunction(id: string, script: UserFunction): Promise<void> {
2139
2155
  await this.makeRequest<void>("PUT", `/api/functions/${id}`, script);
2140
2156
  }
2141
2157
 
2142
2158
  /**
2143
- * Delete a script by ID
2159
+ * Delete a function by ID
2144
2160
  */
2145
- async deleteScript(id: string): Promise<void> {
2161
+ async deleteFunction(id: string): Promise<void> {
2146
2162
  await this.makeRequest<void>("DELETE", `/api/functions/${id}`);
2147
2163
  }
2148
2164
 
2149
2165
  /**
2150
- * Call a saved script by ID or label
2166
+ * Call a saved function by ID or label
2151
2167
  */
2152
- async callScript(
2168
+ async callFunction(
2153
2169
  idOrLabel: string,
2154
2170
  params?: { [key: string]: any },
2155
2171
  ): Promise<FunctionResult> {
@@ -2783,6 +2799,115 @@ export class EkoDBClient {
2783
2799
  return records.length;
2784
2800
  }
2785
2801
 
2802
+ /**
2803
+ * Subscribe to collection mutations via SSE (Server-Sent Events).
2804
+ *
2805
+ * Returns an EventStream that emits MutationNotification events.
2806
+ * Use this when WebSocket connections aren't available (e.g. behind
2807
+ * reverse proxies that block WS upgrades).
2808
+ */
2809
+ subscribeSSE(
2810
+ collection: string,
2811
+ options?: { filterField?: string; filterValue?: string },
2812
+ ): EventStream<MutationNotification> {
2813
+ const stream = new EventStream<MutationNotification>();
2814
+
2815
+ (async () => {
2816
+ try {
2817
+ let token = await this.getToken();
2818
+ if (!token) {
2819
+ await this.refreshToken();
2820
+ token = await this.getToken();
2821
+ }
2822
+
2823
+ let url = `${this.baseURL}/api/subscribe/${encodeURIComponent(collection)}`;
2824
+ const params: string[] = [];
2825
+ if (options?.filterField)
2826
+ params.push(
2827
+ `filter_field=${encodeURIComponent(options.filterField)}`,
2828
+ );
2829
+ if (options?.filterValue)
2830
+ params.push(
2831
+ `filter_value=${encodeURIComponent(options.filterValue)}`,
2832
+ );
2833
+ if (params.length > 0) url += `?${params.join("&")}`;
2834
+
2835
+ const response = await fetch(url, {
2836
+ method: "GET",
2837
+ headers: {
2838
+ Accept: "text/event-stream",
2839
+ Authorization: `Bearer ${token}`,
2840
+ },
2841
+ });
2842
+
2843
+ if (!response.ok) {
2844
+ const body = await response.text();
2845
+ stream.emit(
2846
+ "error",
2847
+ `SSE subscribe failed (${response.status}): ${body}`,
2848
+ );
2849
+ stream.close();
2850
+ return;
2851
+ }
2852
+
2853
+ const reader = response.body?.getReader();
2854
+ if (!reader) {
2855
+ stream.emit("error", "SSE subscribe failed: streaming not supported");
2856
+ stream.close();
2857
+ return;
2858
+ }
2859
+
2860
+ const decoder = new TextDecoder("utf-8");
2861
+ let buffer = "";
2862
+ let eventType = "";
2863
+ let dataLines: string[] = [];
2864
+
2865
+ while (!stream.closed) {
2866
+ const { value, done } = await reader.read();
2867
+ if (done) break;
2868
+
2869
+ buffer += decoder.decode(value, { stream: true });
2870
+ const lines = buffer.split("\n");
2871
+ buffer = lines.pop() ?? "";
2872
+
2873
+ for (const line of lines) {
2874
+ if (line === "") {
2875
+ // End of event block
2876
+ if (eventType === "mutation" && dataLines.length > 0) {
2877
+ try {
2878
+ const payload = JSON.parse(dataLines.join("\n"));
2879
+ stream.emit("event", {
2880
+ collection: payload.collection,
2881
+ event: payload.event,
2882
+ recordIds: payload.record_ids,
2883
+ records: payload.records,
2884
+ timestamp: payload.timestamp,
2885
+ } as MutationNotification);
2886
+ } catch {
2887
+ // skip malformed data
2888
+ }
2889
+ }
2890
+ eventType = "";
2891
+ dataLines = [];
2892
+ continue;
2893
+ }
2894
+ if (line.startsWith("event: ")) {
2895
+ eventType = line.slice(7).trim();
2896
+ } else if (line.startsWith("data: ")) {
2897
+ dataLines.push(line.slice(6).trim());
2898
+ }
2899
+ }
2900
+ }
2901
+ stream.close();
2902
+ } catch (err: any) {
2903
+ stream.emit("error", err.message ?? String(err));
2904
+ stream.close();
2905
+ }
2906
+ })();
2907
+
2908
+ return stream;
2909
+ }
2910
+
2786
2911
  /**
2787
2912
  * Create a WebSocket client
2788
2913
  */
@@ -2912,7 +3037,10 @@ export class EkoDBClient {
2912
3037
  };
2913
3038
 
2914
3039
  const response = await this.search(collection, searchQuery);
2915
- return response.results.map((r) => r.record);
3040
+ return response.results.map((r) => ({
3041
+ ...r.record,
3042
+ _score: r.score,
3043
+ }));
2916
3044
  }
2917
3045
 
2918
3046
  /**
package/src/functions.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  /**
2
- * Scripts API for ekoDB TypeScript client
2
+ * Functions API for ekoDB TypeScript client
3
3
  */
4
4
 
5
- export interface Script {
5
+ /** A reusable sequence of Functions stored in ekoDB. */
6
+ export interface UserFunction {
7
+ id?: string;
6
8
  label: string;
7
9
  name: string;
8
10
  description?: string;
@@ -146,7 +148,7 @@ export type FunctionStageConfig =
146
148
  }
147
149
  | {
148
150
  type: "If";
149
- condition: ScriptCondition;
151
+ condition: FunctionCondition;
150
152
  then_functions: FunctionStageConfig[];
151
153
  else_functions?: FunctionStageConfig[];
152
154
  }
@@ -265,18 +267,18 @@ export interface SortFieldConfig {
265
267
  ascending: boolean;
266
268
  }
267
269
 
268
- // ScriptCondition uses adjacently-tagged format: { type: "...", value: { ...data } }
270
+ // FunctionCondition uses adjacently-tagged format: { type: "...", value: { ...data } }
269
271
  // Unit variants (HasRecords) have no value field
270
- export type ScriptCondition =
272
+ export type FunctionCondition =
271
273
  | { type: "FieldEquals"; value: { field: string; value: any } }
272
274
  | { type: "FieldExists"; value: { field: string } }
273
275
  | { type: "HasRecords" }
274
276
  | { type: "CountEquals"; value: { count: number } }
275
277
  | { type: "CountGreaterThan"; value: { count: number } }
276
278
  | { type: "CountLessThan"; value: { count: number } }
277
- | { type: "And"; value: { conditions: ScriptCondition[] } }
278
- | { type: "Or"; value: { conditions: ScriptCondition[] } }
279
- | { type: "Not"; value: { condition: ScriptCondition } };
279
+ | { type: "And"; value: { conditions: FunctionCondition[] } }
280
+ | { type: "Or"; value: { conditions: FunctionCondition[] } }
281
+ | { type: "Not"; value: { condition: FunctionCondition } };
280
282
 
281
283
  export interface FunctionResult {
282
284
  records: Record<string, any>[];
@@ -541,7 +543,7 @@ export const Stage = {
541
543
  }),
542
544
 
543
545
  if: (
544
- condition: ScriptCondition,
546
+ condition: FunctionCondition,
545
547
  thenFunctions: FunctionStageConfig[],
546
548
  elseFunctions?: FunctionStageConfig[],
547
549
  ): FunctionStageConfig => ({
package/src/index.ts CHANGED
@@ -44,7 +44,7 @@ export type {
44
44
  } from "./schema";
45
45
  export type { JoinConfig } from "./join";
46
46
  export type {
47
- Script,
47
+ UserFunction,
48
48
  ParameterDefinition,
49
49
  FunctionStageConfig,
50
50
  GroupFunctionConfig,
@@ -85,7 +85,6 @@ export type {
85
85
  EmbedResponse,
86
86
  RawCompletionRequest,
87
87
  RawCompletionResponse,
88
- UserFunction,
89
88
  ToolChoice,
90
89
  ToolConfig,
91
90
  } from "./client";