@ekodb/ekodb-client 0.10.0 → 0.12.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 CHANGED
@@ -299,6 +299,28 @@ const joinResults = await client.find("users", multiQuery);
299
299
 
300
300
  - `listCollections(): Promise<string[]>`
301
301
  - `deleteCollection(collection: string): Promise<void>`
302
+ - `collectionExists(collection: string): Promise<boolean>` - Check if collection
303
+ exists
304
+ - `countDocuments(collection: string): Promise<number>` - Count documents in
305
+ collection
306
+
307
+ #### Chat Models
308
+
309
+ - `getChatModels(): Promise<Record<string, string[]>>` - Get all available chat
310
+ models by provider
311
+ - `getChatModel(provider: string): Promise<string[]>` - Get models for a
312
+ specific provider
313
+
314
+ #### User Functions
315
+
316
+ - `saveUserFunction(userFunction: object): Promise<string>` - Create a new user
317
+ function
318
+ - `getUserFunction(label: string): Promise<object>` - Get user function by label
319
+ - `listUserFunctions(tags?: string[]): Promise<object[]>` - List all user
320
+ functions (optionally filter by tags)
321
+ - `updateUserFunction(label: string, userFunction: object): Promise<void>` -
322
+ Update existing user function
323
+ - `deleteUserFunction(label: string): Promise<void>` - Delete user function
302
324
 
303
325
  #### WebSocket
304
326
 
@@ -322,6 +344,8 @@ for complete working examples:
322
344
  - `client_joins.ts` - Join operations
323
345
  - `client_batch_operations.ts` - Batch operations
324
346
  - `client_kv_operations.ts` - Key-value operations
347
+ - `client_chat_models.ts` - Chat models API
348
+ - `client_user_functions.ts` - User functions API
325
349
  - And more...
326
350
 
327
351
  ## License
package/dist/client.d.ts CHANGED
@@ -123,6 +123,18 @@ export interface ChatRequest {
123
123
  system_prompt?: string;
124
124
  bypass_ripple?: boolean;
125
125
  }
126
+ export interface ToolChoice {
127
+ type: "auto" | "none" | "required" | "tool";
128
+ name?: string;
129
+ }
130
+ export interface ToolConfig {
131
+ enabled: boolean;
132
+ allowed_tools?: string[];
133
+ allowed_collections?: string[];
134
+ max_iterations?: number;
135
+ allow_write_operations?: boolean;
136
+ tool_choice?: ToolChoice;
137
+ }
126
138
  export interface CreateChatSessionRequest {
127
139
  collections: CollectionConfig[];
128
140
  llm_provider: string;
@@ -132,11 +144,16 @@ export interface CreateChatSessionRequest {
132
144
  parent_id?: string;
133
145
  branch_point_idx?: number;
134
146
  max_context_messages?: number;
147
+ max_tokens?: number;
148
+ temperature?: number;
149
+ tool_config?: ToolConfig;
135
150
  }
136
151
  export interface ChatMessageRequest {
137
152
  message: string;
138
153
  bypass_ripple?: boolean;
139
154
  force_summarize?: boolean;
155
+ max_iterations?: number;
156
+ tool_config?: ToolConfig;
140
157
  }
141
158
  export interface TokenUsage {
142
159
  prompt_tokens: number;
@@ -195,16 +212,77 @@ export interface UpdateSessionRequest {
195
212
  llm_model?: string;
196
213
  collections?: CollectionConfig[];
197
214
  max_context_messages?: number;
215
+ bypass_ripple?: boolean;
216
+ title?: string;
217
+ memory?: any;
198
218
  }
199
219
  export declare enum MergeStrategy {
200
220
  Chronological = "Chronological",
201
221
  Summarized = "Summarized",
202
- LatestOnly = "LatestOnly"
222
+ LatestOnly = "LatestOnly",
223
+ Interleaved = "Interleaved"
203
224
  }
204
225
  export interface MergeSessionsRequest {
205
226
  source_chat_ids: string[];
206
227
  target_chat_id: string;
207
228
  merge_strategy: MergeStrategy;
229
+ bypass_ripple?: boolean;
230
+ }
231
+ /**
232
+ * Available chat models by provider
233
+ */
234
+ export interface ChatModels {
235
+ openai: string[];
236
+ anthropic: string[];
237
+ perplexity: string[];
238
+ }
239
+ /**
240
+ * Request to generate embeddings
241
+ */
242
+ export interface EmbedRequest {
243
+ text?: string;
244
+ texts?: string[];
245
+ model?: string;
246
+ }
247
+ /**
248
+ * Response from embedding generation
249
+ */
250
+ export interface EmbedResponse {
251
+ embeddings: number[][];
252
+ model: string;
253
+ dimensions: number;
254
+ }
255
+ /**
256
+ * User function definition - reusable sequence of Functions that can be called by Scripts
257
+ */
258
+ export interface UserFunction {
259
+ label: string;
260
+ name: string;
261
+ description?: string;
262
+ version?: string;
263
+ parameters: {
264
+ [key: string]: ParameterDefinition;
265
+ };
266
+ functions: FunctionStageConfig[];
267
+ tags?: string[];
268
+ id?: string;
269
+ created_at?: string;
270
+ updated_at?: string;
271
+ }
272
+ /**
273
+ * Parameter definition for functions
274
+ */
275
+ export interface ParameterDefinition {
276
+ required: boolean;
277
+ default?: any;
278
+ description?: string;
279
+ }
280
+ /**
281
+ * Function stage configuration for pipelines
282
+ */
283
+ export interface FunctionStageConfig {
284
+ type: string;
285
+ [key: string]: any;
208
286
  }
209
287
  export declare class EkoDBClient {
210
288
  private baseURL;
@@ -635,6 +713,24 @@ export declare class EkoDBClient {
635
713
  * Merge multiple chat sessions into one
636
714
  */
637
715
  mergeChatSessions(request: MergeSessionsRequest): Promise<ChatSessionResponse>;
716
+ /**
717
+ * Get all available chat models from all providers
718
+ * @returns ChatModels object with models organized by provider
719
+ */
720
+ getChatModels(): Promise<ChatModels>;
721
+ /**
722
+ * Get available models for a specific provider
723
+ * @param provider - Provider name (e.g., "openai", "anthropic", "perplexity")
724
+ * @returns Array of model names for the provider
725
+ */
726
+ getChatModel(provider: string): Promise<string[]>;
727
+ /**
728
+ * Get a specific chat message by ID
729
+ * @param sessionId - Chat session ID
730
+ * @param messageId - Message ID
731
+ * @returns The chat message record
732
+ */
733
+ getChatMessage(sessionId: string, messageId: string): Promise<Record>;
638
734
  /**
639
735
  * Save a new script definition
640
736
  */
@@ -661,18 +757,53 @@ export declare class EkoDBClient {
661
757
  callScript(idOrLabel: string, params?: {
662
758
  [key: string]: any;
663
759
  }): Promise<FunctionResult>;
760
+ /**
761
+ * Save a new user function
762
+ * @param userFunction - The user function definition
763
+ * @returns The ID of the created user function
764
+ */
765
+ saveUserFunction(userFunction: UserFunction): Promise<string>;
766
+ /**
767
+ * Get a user function by label
768
+ * @param label - The user function label
769
+ * @returns The user function definition
770
+ */
771
+ getUserFunction(label: string): Promise<UserFunction>;
772
+ /**
773
+ * List all user functions, optionally filtered by tags
774
+ * @param tags - Optional array of tags to filter by
775
+ * @returns Array of user functions
776
+ */
777
+ listUserFunctions(tags?: string[]): Promise<UserFunction[]>;
778
+ /**
779
+ * Update an existing user function by label
780
+ * @param label - The user function label
781
+ * @param userFunction - The updated user function definition
782
+ */
783
+ updateUserFunction(label: string, userFunction: UserFunction): Promise<void>;
784
+ /**
785
+ * Delete a user function by label
786
+ * @param label - The user function label
787
+ */
788
+ deleteUserFunction(label: string): Promise<void>;
789
+ /**
790
+ * Check if a collection exists
791
+ * @param collection - Collection name to check
792
+ * @returns true if the collection exists, false otherwise
793
+ */
794
+ collectionExists(collection: string): Promise<boolean>;
795
+ /**
796
+ * Count documents in a collection
797
+ * @param collection - Collection name
798
+ * @returns Number of documents in the collection
799
+ */
800
+ countDocuments(collection: string): Promise<number>;
664
801
  /**
665
802
  * Create a WebSocket client
666
803
  */
667
804
  websocket(wsURL: string): WebSocketClient;
668
805
  /**
669
- * Generate embeddings for text using ekoDB's native Functions
670
- *
671
- * This helper simplifies embedding generation by:
672
- * 1. Creating a temporary collection with the text
673
- * 2. Running a Script with FindAll + Embed Functions
674
- * 3. Extracting and returning the embedding vector
675
- * 4. Cleaning up temporary resources
806
+ * Generate embeddings for a single text
676
807
  *
677
808
  * @param text - The text to generate embeddings for
678
809
  * @param model - The embedding model to use (e.g., "text-embedding-3-small")
@@ -688,6 +819,18 @@ export declare class EkoDBClient {
688
819
  * ```
689
820
  */
690
821
  embed(text: string, model: string): Promise<number[]>;
822
+ /**
823
+ * Generate embeddings for multiple texts in a single batch request
824
+ *
825
+ * @param texts - Array of texts to generate embeddings for
826
+ * @param model - The embedding model to use
827
+ * @returns Array of embedding vectors
828
+ */
829
+ embedBatch(texts: string[], model: string): Promise<number[][]>;
830
+ /**
831
+ * Internal: make embed API request
832
+ */
833
+ private embedRequest;
691
834
  /**
692
835
  * Perform text search without embeddings
693
836
  *
package/dist/client.js CHANGED
@@ -66,6 +66,7 @@ var MergeStrategy;
66
66
  MergeStrategy["Chronological"] = "Chronological";
67
67
  MergeStrategy["Summarized"] = "Summarized";
68
68
  MergeStrategy["LatestOnly"] = "LatestOnly";
69
+ MergeStrategy["Interleaved"] = "Interleaved";
69
70
  })(MergeStrategy || (exports.MergeStrategy = MergeStrategy = {}));
70
71
  class EkoDBClient {
71
72
  constructor(config, apiKey) {
@@ -882,6 +883,30 @@ class EkoDBClient {
882
883
  async mergeChatSessions(request) {
883
884
  return this.makeRequest("POST", "/api/chat/merge", request, 0, true);
884
885
  }
886
+ /**
887
+ * Get all available chat models from all providers
888
+ * @returns ChatModels object with models organized by provider
889
+ */
890
+ async getChatModels() {
891
+ return this.makeRequest("GET", "/api/chat_models", undefined, 0, true);
892
+ }
893
+ /**
894
+ * Get available models for a specific provider
895
+ * @param provider - Provider name (e.g., "openai", "anthropic", "perplexity")
896
+ * @returns Array of model names for the provider
897
+ */
898
+ async getChatModel(provider) {
899
+ return this.makeRequest("GET", `/api/chat_models/${encodeURIComponent(provider)}`, undefined, 0, true);
900
+ }
901
+ /**
902
+ * Get a specific chat message by ID
903
+ * @param sessionId - Chat session ID
904
+ * @param messageId - Message ID
905
+ * @returns The chat message record
906
+ */
907
+ async getChatMessage(sessionId, messageId) {
908
+ return this.makeRequest("GET", `/api/chat/${sessionId}/messages/${messageId}`, undefined, 0, true);
909
+ }
885
910
  // ========================================================================
886
911
  // SCRIPTS API
887
912
  // ========================================================================
@@ -923,6 +948,77 @@ class EkoDBClient {
923
948
  async callScript(idOrLabel, params) {
924
949
  return this.makeRequest("POST", `/api/functions/${idOrLabel}`, params || {});
925
950
  }
951
+ // ========================================================================
952
+ // USER FUNCTIONS API
953
+ // ========================================================================
954
+ /**
955
+ * Save a new user function
956
+ * @param userFunction - The user function definition
957
+ * @returns The ID of the created user function
958
+ */
959
+ async saveUserFunction(userFunction) {
960
+ const result = await this.makeRequest("POST", "/api/functions", userFunction, 0, true);
961
+ return result.id;
962
+ }
963
+ /**
964
+ * Get a user function by label
965
+ * @param label - The user function label
966
+ * @returns The user function definition
967
+ */
968
+ async getUserFunction(label) {
969
+ return this.makeRequest("GET", `/api/functions/${encodeURIComponent(label)}`, undefined, 0, true);
970
+ }
971
+ /**
972
+ * List all user functions, optionally filtered by tags
973
+ * @param tags - Optional array of tags to filter by
974
+ * @returns Array of user functions
975
+ */
976
+ async listUserFunctions(tags) {
977
+ const params = tags ? `?tags=${tags.join(",")}` : "";
978
+ return this.makeRequest("GET", `/api/functions${params}`, undefined, 0, true);
979
+ }
980
+ /**
981
+ * Update an existing user function by label
982
+ * @param label - The user function label
983
+ * @param userFunction - The updated user function definition
984
+ */
985
+ async updateUserFunction(label, userFunction) {
986
+ await this.makeRequest("PUT", `/api/functions/${encodeURIComponent(label)}`, userFunction, 0, true);
987
+ }
988
+ /**
989
+ * Delete a user function by label
990
+ * @param label - The user function label
991
+ */
992
+ async deleteUserFunction(label) {
993
+ await this.makeRequest("DELETE", `/api/functions/${encodeURIComponent(label)}`, undefined, 0, true);
994
+ }
995
+ // ========================================================================
996
+ // COLLECTION UTILITIES
997
+ // ========================================================================
998
+ /**
999
+ * Check if a collection exists
1000
+ * @param collection - Collection name to check
1001
+ * @returns true if the collection exists, false otherwise
1002
+ */
1003
+ async collectionExists(collection) {
1004
+ try {
1005
+ const collections = await this.listCollections();
1006
+ return collections.includes(collection);
1007
+ }
1008
+ catch {
1009
+ return false;
1010
+ }
1011
+ }
1012
+ /**
1013
+ * Count documents in a collection
1014
+ * @param collection - Collection name
1015
+ * @returns Number of documents in the collection
1016
+ */
1017
+ async countDocuments(collection) {
1018
+ const query = new query_builder_1.QueryBuilder().limit(100000).build();
1019
+ const records = await this.find(collection, query);
1020
+ return records.length;
1021
+ }
926
1022
  /**
927
1023
  * Create a WebSocket client
928
1024
  */
@@ -931,13 +1027,7 @@ class EkoDBClient {
931
1027
  }
932
1028
  // ========== RAG Helper Methods ==========
933
1029
  /**
934
- * Generate embeddings for text using ekoDB's native Functions
935
- *
936
- * This helper simplifies embedding generation by:
937
- * 1. Creating a temporary collection with the text
938
- * 2. Running a Script with FindAll + Embed Functions
939
- * 3. Extracting and returning the embedding vector
940
- * 4. Cleaning up temporary resources
1030
+ * Generate embeddings for a single text
941
1031
  *
942
1032
  * @param text - The text to generate embeddings for
943
1033
  * @param model - The embedding model to use (e.g., "text-embedding-3-small")
@@ -953,52 +1043,28 @@ class EkoDBClient {
953
1043
  * ```
954
1044
  */
955
1045
  async embed(text, model) {
956
- const tempCollection = `embed_temp_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
957
- try {
958
- // Insert temporary record with the text
959
- await this.insert(tempCollection, { text }, undefined);
960
- // Create Script with FindAll + Embed Functions
961
- const tempLabel = `embed_script_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
962
- const script = {
963
- label: tempLabel,
964
- name: "Generate Embedding",
965
- description: "Temporary script for embedding generation",
966
- version: "1.0",
967
- parameters: {},
968
- functions: [
969
- {
970
- type: "FindAll",
971
- collection: tempCollection,
972
- },
973
- {
974
- type: "Embed",
975
- input_field: "text",
976
- output_field: "embedding",
977
- model: model,
978
- },
979
- ],
980
- tags: [],
981
- };
982
- // Save and execute the script
983
- const scriptId = await this.saveScript(script);
984
- const result = await this.callScript(scriptId, undefined);
985
- // Clean up
986
- await this.deleteScript(scriptId).catch(() => { });
987
- await this.deleteCollection(tempCollection).catch(() => { });
988
- // Extract embedding from result
989
- if (result.records && result.records.length > 0) {
990
- const record = result.records[0];
991
- if (record.embedding && Array.isArray(record.embedding)) {
992
- return record.embedding;
993
- }
994
- }
995
- throw new Error("Failed to extract embedding from result");
996
- }
997
- catch (error) {
998
- // Ensure cleanup even on error
999
- await this.deleteCollection(tempCollection).catch(() => { });
1000
- throw error;
1046
+ const response = await this.embedRequest({ text, model });
1047
+ if (response.embeddings.length === 0) {
1048
+ throw new Error("No embedding returned");
1001
1049
  }
1050
+ return response.embeddings[0];
1051
+ }
1052
+ /**
1053
+ * Generate embeddings for multiple texts in a single batch request
1054
+ *
1055
+ * @param texts - Array of texts to generate embeddings for
1056
+ * @param model - The embedding model to use
1057
+ * @returns Array of embedding vectors
1058
+ */
1059
+ async embedBatch(texts, model) {
1060
+ const response = await this.embedRequest({ texts, model });
1061
+ return response.embeddings;
1062
+ }
1063
+ /**
1064
+ * Internal: make embed API request
1065
+ */
1066
+ async embedRequest(request) {
1067
+ return this.makeRequest("POST", "/api/embed", request, 0, true);
1002
1068
  }
1003
1069
  /**
1004
1070
  * Perform text search without embeddings
@@ -765,3 +765,154 @@ function mockErrorResponse(status, message) {
765
765
  });
766
766
  });
767
767
  });
768
+ // ============================================================================
769
+ // Chat Models Tests
770
+ // ============================================================================
771
+ (0, vitest_1.describe)("EkoDBClient chat models", () => {
772
+ (0, vitest_1.it)("gets all chat models", async () => {
773
+ const client = createTestClient();
774
+ mockTokenResponse();
775
+ mockJsonResponse({
776
+ openai: ["gpt-4", "gpt-3.5-turbo"],
777
+ anthropic: ["claude-3-opus", "claude-3-sonnet"],
778
+ perplexity: ["llama-3.1-sonar-small"],
779
+ });
780
+ const result = await client.getChatModels();
781
+ (0, vitest_1.expect)(result.openai).toHaveLength(2);
782
+ (0, vitest_1.expect)(result.anthropic).toHaveLength(2);
783
+ (0, vitest_1.expect)(result.perplexity).toHaveLength(1);
784
+ });
785
+ (0, vitest_1.it)("gets models for specific provider", async () => {
786
+ const client = createTestClient();
787
+ mockTokenResponse();
788
+ mockJsonResponse(["gpt-4", "gpt-3.5-turbo", "gpt-4-turbo"]);
789
+ const result = await client.getChatModel("openai");
790
+ (0, vitest_1.expect)(result).toHaveLength(3);
791
+ (0, vitest_1.expect)(result).toContain("gpt-4");
792
+ });
793
+ (0, vitest_1.it)("gets specific chat message", async () => {
794
+ const client = createTestClient();
795
+ mockTokenResponse();
796
+ mockJsonResponse({
797
+ id: "msg_123",
798
+ role: "user",
799
+ content: "Hello there",
800
+ created_at: "2024-01-01T00:00:00Z",
801
+ });
802
+ const result = await client.getChatMessage("chat_123", "msg_123");
803
+ (0, vitest_1.expect)(result).toHaveProperty("id", "msg_123");
804
+ (0, vitest_1.expect)(result).toHaveProperty("content", "Hello there");
805
+ });
806
+ });
807
+ // ============================================================================
808
+ // User Functions Tests
809
+ // ============================================================================
810
+ (0, vitest_1.describe)("EkoDBClient user functions", () => {
811
+ (0, vitest_1.it)("saves user function", async () => {
812
+ const client = createTestClient();
813
+ mockTokenResponse();
814
+ mockJsonResponse({ id: "uf_123" });
815
+ const userFunction = {
816
+ label: "my_function",
817
+ name: "My Function",
818
+ parameters: {},
819
+ functions: [],
820
+ };
821
+ const result = await client.saveUserFunction(userFunction);
822
+ (0, vitest_1.expect)(result).toBe("uf_123");
823
+ });
824
+ (0, vitest_1.it)("gets user function by label", async () => {
825
+ const client = createTestClient();
826
+ mockTokenResponse();
827
+ mockJsonResponse({
828
+ label: "my_function",
829
+ name: "My Function",
830
+ parameters: {},
831
+ functions: [],
832
+ id: "uf_123",
833
+ });
834
+ const result = await client.getUserFunction("my_function");
835
+ (0, vitest_1.expect)(result).toHaveProperty("label", "my_function");
836
+ (0, vitest_1.expect)(result).toHaveProperty("id", "uf_123");
837
+ });
838
+ (0, vitest_1.it)("lists user functions", async () => {
839
+ const client = createTestClient();
840
+ mockTokenResponse();
841
+ mockJsonResponse([
842
+ { label: "func_1", name: "Function 1" },
843
+ { label: "func_2", name: "Function 2" },
844
+ ]);
845
+ const result = await client.listUserFunctions();
846
+ (0, vitest_1.expect)(result).toHaveLength(2);
847
+ (0, vitest_1.expect)(result[0]).toHaveProperty("label", "func_1");
848
+ });
849
+ (0, vitest_1.it)("lists user functions filtered by tags", async () => {
850
+ const client = createTestClient();
851
+ mockTokenResponse();
852
+ mockJsonResponse([{ label: "func_1", name: "Function 1", tags: ["data"] }]);
853
+ const result = await client.listUserFunctions(["data"]);
854
+ (0, vitest_1.expect)(result).toHaveLength(1);
855
+ });
856
+ (0, vitest_1.it)("updates user function", async () => {
857
+ const client = createTestClient();
858
+ mockTokenResponse();
859
+ mockJsonResponse({ status: "updated" });
860
+ const userFunction = {
861
+ label: "my_function",
862
+ name: "Updated Function",
863
+ parameters: {},
864
+ functions: [],
865
+ };
866
+ await (0, vitest_1.expect)(client.updateUserFunction("my_function", userFunction)).resolves.not.toThrow();
867
+ });
868
+ (0, vitest_1.it)("deletes user function", async () => {
869
+ const client = createTestClient();
870
+ mockTokenResponse();
871
+ mockJsonResponse({ status: "deleted" });
872
+ await (0, vitest_1.expect)(client.deleteUserFunction("my_function")).resolves.not.toThrow();
873
+ });
874
+ });
875
+ // ============================================================================
876
+ // Collection Utility Tests
877
+ // ============================================================================
878
+ (0, vitest_1.describe)("EkoDBClient collection utilities", () => {
879
+ (0, vitest_1.it)("collectionExists returns true for existing collection", async () => {
880
+ const client = createTestClient();
881
+ mockTokenResponse();
882
+ mockJsonResponse({ collections: ["users", "posts", "comments"] });
883
+ const result = await client.collectionExists("users");
884
+ (0, vitest_1.expect)(result).toBe(true);
885
+ });
886
+ (0, vitest_1.it)("collectionExists returns false for non-existing collection", async () => {
887
+ const client = createTestClient();
888
+ mockTokenResponse();
889
+ mockJsonResponse({ collections: ["users", "posts", "comments"] });
890
+ const result = await client.collectionExists("nonexistent");
891
+ (0, vitest_1.expect)(result).toBe(false);
892
+ });
893
+ (0, vitest_1.it)("collectionExists returns false on error", async () => {
894
+ const client = createTestClient();
895
+ mockTokenResponse();
896
+ mockErrorResponse(500, "Server error");
897
+ const result = await client.collectionExists("users");
898
+ (0, vitest_1.expect)(result).toBe(false);
899
+ });
900
+ (0, vitest_1.it)("countDocuments returns correct count", async () => {
901
+ const client = createTestClient();
902
+ mockTokenResponse();
903
+ mockJsonResponse([
904
+ { id: "1", name: "A" },
905
+ { id: "2", name: "B" },
906
+ { id: "3", name: "C" },
907
+ ]);
908
+ const result = await client.countDocuments("users");
909
+ (0, vitest_1.expect)(result).toBe(3);
910
+ });
911
+ (0, vitest_1.it)("countDocuments returns zero for empty collection", async () => {
912
+ const client = createTestClient();
913
+ mockTokenResponse();
914
+ mockJsonResponse([]);
915
+ const result = await client.countDocuments("empty_collection");
916
+ (0, vitest_1.expect)(result).toBe(0);
917
+ });
918
+ });
package/dist/index.d.ts CHANGED
@@ -10,4 +10,4 @@ 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
12
  export type { Script, ParameterDefinition, FunctionStageConfig, GroupFunctionConfig, SortFieldConfig, FunctionResult, FunctionStats, StageStats, } from "./functions";
13
- export type { Record, Query, BatchOperationResult, ClientConfig, RateLimitInfo, CollectionConfig, ChatRequest, CreateChatSessionRequest, ChatMessageRequest, TokenUsage, ChatResponse, ChatSession, ChatSessionResponse, ListSessionsQuery, ListSessionsResponse, GetMessagesQuery, GetMessagesResponse, UpdateSessionRequest, MergeSessionsRequest, } from "./client";
13
+ export type { Record, Query, BatchOperationResult, ClientConfig, RateLimitInfo, CollectionConfig, ChatRequest, CreateChatSessionRequest, ChatMessageRequest, TokenUsage, ChatResponse, ChatSession, ChatSessionResponse, ListSessionsQuery, ListSessionsResponse, GetMessagesQuery, GetMessagesResponse, UpdateSessionRequest, MergeSessionsRequest, ChatModels, EmbedRequest, EmbedResponse, UserFunction, ToolChoice, ToolConfig, } from "./client";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ekodb/ekodb-client",
3
- "version": "0.10.0",
3
+ "version": "0.12.0",
4
4
  "description": "Official TypeScript/JavaScript client for ekoDB",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -19,13 +19,13 @@
19
19
  "author": "ekoDB",
20
20
  "license": "MIT",
21
21
  "devDependencies": {
22
- "@types/node": "^25.0.3",
23
- "@types/ws": "^8.5.10",
24
- "typescript": "^5.3.0",
25
- "vitest": "^3.2.0"
22
+ "@types/node": "^25.3.5",
23
+ "@types/ws": "^8.18.1",
24
+ "typescript": "^5.9.3",
25
+ "vitest": "^4.0.18"
26
26
  },
27
27
  "dependencies": {
28
- "@msgpack/msgpack": "^3.0.0",
29
- "ws": "^8.16.0"
28
+ "@msgpack/msgpack": "^3.1.3",
29
+ "ws": "^8.19.0"
30
30
  }
31
31
  }
@@ -1031,3 +1031,215 @@ describe("Convenience methods", () => {
1031
1031
  });
1032
1032
  });
1033
1033
  });
1034
+
1035
+ // ============================================================================
1036
+ // Chat Models Tests
1037
+ // ============================================================================
1038
+
1039
+ describe("EkoDBClient chat models", () => {
1040
+ it("gets all chat models", async () => {
1041
+ const client = createTestClient();
1042
+
1043
+ mockTokenResponse();
1044
+ mockJsonResponse({
1045
+ openai: ["gpt-4", "gpt-3.5-turbo"],
1046
+ anthropic: ["claude-3-opus", "claude-3-sonnet"],
1047
+ perplexity: ["llama-3.1-sonar-small"],
1048
+ });
1049
+
1050
+ const result = await client.getChatModels();
1051
+
1052
+ expect(result.openai).toHaveLength(2);
1053
+ expect(result.anthropic).toHaveLength(2);
1054
+ expect(result.perplexity).toHaveLength(1);
1055
+ });
1056
+
1057
+ it("gets models for specific provider", async () => {
1058
+ const client = createTestClient();
1059
+
1060
+ mockTokenResponse();
1061
+ mockJsonResponse(["gpt-4", "gpt-3.5-turbo", "gpt-4-turbo"]);
1062
+
1063
+ const result = await client.getChatModel("openai");
1064
+
1065
+ expect(result).toHaveLength(3);
1066
+ expect(result).toContain("gpt-4");
1067
+ });
1068
+
1069
+ it("gets specific chat message", async () => {
1070
+ const client = createTestClient();
1071
+
1072
+ mockTokenResponse();
1073
+ mockJsonResponse({
1074
+ id: "msg_123",
1075
+ role: "user",
1076
+ content: "Hello there",
1077
+ created_at: "2024-01-01T00:00:00Z",
1078
+ });
1079
+
1080
+ const result = await client.getChatMessage("chat_123", "msg_123");
1081
+
1082
+ expect(result).toHaveProperty("id", "msg_123");
1083
+ expect(result).toHaveProperty("content", "Hello there");
1084
+ });
1085
+ });
1086
+
1087
+ // ============================================================================
1088
+ // User Functions Tests
1089
+ // ============================================================================
1090
+
1091
+ describe("EkoDBClient user functions", () => {
1092
+ it("saves user function", async () => {
1093
+ const client = createTestClient();
1094
+
1095
+ mockTokenResponse();
1096
+ mockJsonResponse({ id: "uf_123" });
1097
+
1098
+ const userFunction = {
1099
+ label: "my_function",
1100
+ name: "My Function",
1101
+ parameters: {},
1102
+ functions: [],
1103
+ };
1104
+ const result = await client.saveUserFunction(userFunction);
1105
+
1106
+ expect(result).toBe("uf_123");
1107
+ });
1108
+
1109
+ it("gets user function by label", async () => {
1110
+ const client = createTestClient();
1111
+
1112
+ mockTokenResponse();
1113
+ mockJsonResponse({
1114
+ label: "my_function",
1115
+ name: "My Function",
1116
+ parameters: {},
1117
+ functions: [],
1118
+ id: "uf_123",
1119
+ });
1120
+
1121
+ const result = await client.getUserFunction("my_function");
1122
+
1123
+ expect(result).toHaveProperty("label", "my_function");
1124
+ expect(result).toHaveProperty("id", "uf_123");
1125
+ });
1126
+
1127
+ it("lists user functions", async () => {
1128
+ const client = createTestClient();
1129
+
1130
+ mockTokenResponse();
1131
+ mockJsonResponse([
1132
+ { label: "func_1", name: "Function 1" },
1133
+ { label: "func_2", name: "Function 2" },
1134
+ ]);
1135
+
1136
+ const result = await client.listUserFunctions();
1137
+
1138
+ expect(result).toHaveLength(2);
1139
+ expect(result[0]).toHaveProperty("label", "func_1");
1140
+ });
1141
+
1142
+ it("lists user functions filtered by tags", async () => {
1143
+ const client = createTestClient();
1144
+
1145
+ mockTokenResponse();
1146
+ mockJsonResponse([{ label: "func_1", name: "Function 1", tags: ["data"] }]);
1147
+
1148
+ const result = await client.listUserFunctions(["data"]);
1149
+
1150
+ expect(result).toHaveLength(1);
1151
+ });
1152
+
1153
+ it("updates user function", async () => {
1154
+ const client = createTestClient();
1155
+
1156
+ mockTokenResponse();
1157
+ mockJsonResponse({ status: "updated" });
1158
+
1159
+ const userFunction = {
1160
+ label: "my_function",
1161
+ name: "Updated Function",
1162
+ parameters: {},
1163
+ functions: [],
1164
+ };
1165
+ await expect(
1166
+ client.updateUserFunction("my_function", userFunction),
1167
+ ).resolves.not.toThrow();
1168
+ });
1169
+
1170
+ it("deletes user function", async () => {
1171
+ const client = createTestClient();
1172
+
1173
+ mockTokenResponse();
1174
+ mockJsonResponse({ status: "deleted" });
1175
+
1176
+ await expect(
1177
+ client.deleteUserFunction("my_function"),
1178
+ ).resolves.not.toThrow();
1179
+ });
1180
+ });
1181
+
1182
+ // ============================================================================
1183
+ // Collection Utility Tests
1184
+ // ============================================================================
1185
+
1186
+ describe("EkoDBClient collection utilities", () => {
1187
+ it("collectionExists returns true for existing collection", async () => {
1188
+ const client = createTestClient();
1189
+
1190
+ mockTokenResponse();
1191
+ mockJsonResponse({ collections: ["users", "posts", "comments"] });
1192
+
1193
+ const result = await client.collectionExists("users");
1194
+
1195
+ expect(result).toBe(true);
1196
+ });
1197
+
1198
+ it("collectionExists returns false for non-existing collection", async () => {
1199
+ const client = createTestClient();
1200
+
1201
+ mockTokenResponse();
1202
+ mockJsonResponse({ collections: ["users", "posts", "comments"] });
1203
+
1204
+ const result = await client.collectionExists("nonexistent");
1205
+
1206
+ expect(result).toBe(false);
1207
+ });
1208
+
1209
+ it("collectionExists returns false on error", async () => {
1210
+ const client = createTestClient();
1211
+
1212
+ mockTokenResponse();
1213
+ mockErrorResponse(500, "Server error");
1214
+
1215
+ const result = await client.collectionExists("users");
1216
+
1217
+ expect(result).toBe(false);
1218
+ });
1219
+
1220
+ it("countDocuments returns correct count", async () => {
1221
+ const client = createTestClient();
1222
+
1223
+ mockTokenResponse();
1224
+ mockJsonResponse([
1225
+ { id: "1", name: "A" },
1226
+ { id: "2", name: "B" },
1227
+ { id: "3", name: "C" },
1228
+ ]);
1229
+
1230
+ const result = await client.countDocuments("users");
1231
+
1232
+ expect(result).toBe(3);
1233
+ });
1234
+
1235
+ it("countDocuments returns zero for empty collection", async () => {
1236
+ const client = createTestClient();
1237
+
1238
+ mockTokenResponse();
1239
+ mockJsonResponse([]);
1240
+
1241
+ const result = await client.countDocuments("empty_collection");
1242
+
1243
+ expect(result).toBe(0);
1244
+ });
1245
+ });
package/src/client.ts CHANGED
@@ -151,6 +151,20 @@ export interface ChatRequest {
151
151
  bypass_ripple?: boolean;
152
152
  }
153
153
 
154
+ export interface ToolChoice {
155
+ type: "auto" | "none" | "required" | "tool";
156
+ name?: string;
157
+ }
158
+
159
+ export interface ToolConfig {
160
+ enabled: boolean;
161
+ allowed_tools?: string[];
162
+ allowed_collections?: string[];
163
+ max_iterations?: number;
164
+ allow_write_operations?: boolean;
165
+ tool_choice?: ToolChoice;
166
+ }
167
+
154
168
  export interface CreateChatSessionRequest {
155
169
  collections: CollectionConfig[];
156
170
  llm_provider: string;
@@ -160,12 +174,17 @@ export interface CreateChatSessionRequest {
160
174
  parent_id?: string;
161
175
  branch_point_idx?: number;
162
176
  max_context_messages?: number;
177
+ max_tokens?: number;
178
+ temperature?: number;
179
+ tool_config?: ToolConfig;
163
180
  }
164
181
 
165
182
  export interface ChatMessageRequest {
166
183
  message: string;
167
184
  bypass_ripple?: boolean;
168
185
  force_summarize?: boolean;
186
+ max_iterations?: number;
187
+ tool_config?: ToolConfig;
169
188
  }
170
189
 
171
190
  export interface TokenUsage {
@@ -233,18 +252,83 @@ export interface UpdateSessionRequest {
233
252
  llm_model?: string;
234
253
  collections?: CollectionConfig[];
235
254
  max_context_messages?: number;
255
+ bypass_ripple?: boolean;
256
+ title?: string;
257
+ memory?: any;
236
258
  }
237
259
 
238
260
  export enum MergeStrategy {
239
261
  Chronological = "Chronological",
240
262
  Summarized = "Summarized",
241
263
  LatestOnly = "LatestOnly",
264
+ Interleaved = "Interleaved",
242
265
  }
243
266
 
244
267
  export interface MergeSessionsRequest {
245
268
  source_chat_ids: string[];
246
269
  target_chat_id: string;
247
270
  merge_strategy: MergeStrategy;
271
+ bypass_ripple?: boolean;
272
+ }
273
+
274
+ /**
275
+ * Available chat models by provider
276
+ */
277
+ export interface ChatModels {
278
+ openai: string[];
279
+ anthropic: string[];
280
+ perplexity: string[];
281
+ }
282
+
283
+ /**
284
+ * Request to generate embeddings
285
+ */
286
+ export interface EmbedRequest {
287
+ text?: string;
288
+ texts?: string[];
289
+ model?: string;
290
+ }
291
+
292
+ /**
293
+ * Response from embedding generation
294
+ */
295
+ export interface EmbedResponse {
296
+ embeddings: number[][];
297
+ model: string;
298
+ dimensions: number;
299
+ }
300
+
301
+ /**
302
+ * User function definition - reusable sequence of Functions that can be called by Scripts
303
+ */
304
+ export interface UserFunction {
305
+ label: string;
306
+ name: string;
307
+ description?: string;
308
+ version?: string;
309
+ parameters: { [key: string]: ParameterDefinition };
310
+ functions: FunctionStageConfig[];
311
+ tags?: string[];
312
+ id?: string;
313
+ created_at?: string;
314
+ updated_at?: string;
315
+ }
316
+
317
+ /**
318
+ * Parameter definition for functions
319
+ */
320
+ export interface ParameterDefinition {
321
+ required: boolean;
322
+ default?: any;
323
+ description?: string;
324
+ }
325
+
326
+ /**
327
+ * Function stage configuration for pipelines
328
+ */
329
+ export interface FunctionStageConfig {
330
+ type: string;
331
+ [key: string]: any;
248
332
  }
249
333
 
250
334
  export class EkoDBClient {
@@ -1462,6 +1546,51 @@ export class EkoDBClient {
1462
1546
  );
1463
1547
  }
1464
1548
 
1549
+ /**
1550
+ * Get all available chat models from all providers
1551
+ * @returns ChatModels object with models organized by provider
1552
+ */
1553
+ async getChatModels(): Promise<ChatModels> {
1554
+ return this.makeRequest<ChatModels>(
1555
+ "GET",
1556
+ "/api/chat_models",
1557
+ undefined,
1558
+ 0,
1559
+ true, // Force JSON for chat operations
1560
+ );
1561
+ }
1562
+
1563
+ /**
1564
+ * Get available models for a specific provider
1565
+ * @param provider - Provider name (e.g., "openai", "anthropic", "perplexity")
1566
+ * @returns Array of model names for the provider
1567
+ */
1568
+ async getChatModel(provider: string): Promise<string[]> {
1569
+ return this.makeRequest<string[]>(
1570
+ "GET",
1571
+ `/api/chat_models/${encodeURIComponent(provider)}`,
1572
+ undefined,
1573
+ 0,
1574
+ true, // Force JSON for chat operations
1575
+ );
1576
+ }
1577
+
1578
+ /**
1579
+ * Get a specific chat message by ID
1580
+ * @param sessionId - Chat session ID
1581
+ * @param messageId - Message ID
1582
+ * @returns The chat message record
1583
+ */
1584
+ async getChatMessage(sessionId: string, messageId: string): Promise<Record> {
1585
+ return this.makeRequest<Record>(
1586
+ "GET",
1587
+ `/api/chat/${sessionId}/messages/${messageId}`,
1588
+ undefined,
1589
+ 0,
1590
+ true, // Force JSON for chat operations
1591
+ );
1592
+ }
1593
+
1465
1594
  // ========================================================================
1466
1595
  // SCRIPTS API
1467
1596
  // ========================================================================
@@ -1521,6 +1650,118 @@ export class EkoDBClient {
1521
1650
  );
1522
1651
  }
1523
1652
 
1653
+ // ========================================================================
1654
+ // USER FUNCTIONS API
1655
+ // ========================================================================
1656
+
1657
+ /**
1658
+ * Save a new user function
1659
+ * @param userFunction - The user function definition
1660
+ * @returns The ID of the created user function
1661
+ */
1662
+ async saveUserFunction(userFunction: UserFunction): Promise<string> {
1663
+ const result = await this.makeRequest<{ id: string }>(
1664
+ "POST",
1665
+ "/api/functions",
1666
+ userFunction,
1667
+ 0,
1668
+ true, // Force JSON
1669
+ );
1670
+ return result.id;
1671
+ }
1672
+
1673
+ /**
1674
+ * Get a user function by label
1675
+ * @param label - The user function label
1676
+ * @returns The user function definition
1677
+ */
1678
+ async getUserFunction(label: string): Promise<UserFunction> {
1679
+ return this.makeRequest<UserFunction>(
1680
+ "GET",
1681
+ `/api/functions/${encodeURIComponent(label)}`,
1682
+ undefined,
1683
+ 0,
1684
+ true, // Force JSON
1685
+ );
1686
+ }
1687
+
1688
+ /**
1689
+ * List all user functions, optionally filtered by tags
1690
+ * @param tags - Optional array of tags to filter by
1691
+ * @returns Array of user functions
1692
+ */
1693
+ async listUserFunctions(tags?: string[]): Promise<UserFunction[]> {
1694
+ const params = tags ? `?tags=${tags.join(",")}` : "";
1695
+ return this.makeRequest<UserFunction[]>(
1696
+ "GET",
1697
+ `/api/functions${params}`,
1698
+ undefined,
1699
+ 0,
1700
+ true, // Force JSON
1701
+ );
1702
+ }
1703
+
1704
+ /**
1705
+ * Update an existing user function by label
1706
+ * @param label - The user function label
1707
+ * @param userFunction - The updated user function definition
1708
+ */
1709
+ async updateUserFunction(
1710
+ label: string,
1711
+ userFunction: UserFunction,
1712
+ ): Promise<void> {
1713
+ await this.makeRequest<void>(
1714
+ "PUT",
1715
+ `/api/functions/${encodeURIComponent(label)}`,
1716
+ userFunction,
1717
+ 0,
1718
+ true, // Force JSON
1719
+ );
1720
+ }
1721
+
1722
+ /**
1723
+ * Delete a user function by label
1724
+ * @param label - The user function label
1725
+ */
1726
+ async deleteUserFunction(label: string): Promise<void> {
1727
+ await this.makeRequest<void>(
1728
+ "DELETE",
1729
+ `/api/functions/${encodeURIComponent(label)}`,
1730
+ undefined,
1731
+ 0,
1732
+ true, // Force JSON
1733
+ );
1734
+ }
1735
+
1736
+ // ========================================================================
1737
+ // COLLECTION UTILITIES
1738
+ // ========================================================================
1739
+
1740
+ /**
1741
+ * Check if a collection exists
1742
+ * @param collection - Collection name to check
1743
+ * @returns true if the collection exists, false otherwise
1744
+ */
1745
+ async collectionExists(collection: string): Promise<boolean> {
1746
+ try {
1747
+ const collections = await this.listCollections();
1748
+ return collections.includes(collection);
1749
+ } catch {
1750
+ return false;
1751
+ }
1752
+ }
1753
+
1754
+ /**
1755
+ * Count documents in a collection
1756
+ * @param collection - Collection name
1757
+ * @returns Number of documents in the collection
1758
+ */
1759
+ async countDocuments(collection: string): Promise<number> {
1760
+ const query = new QueryBuilder().limit(100000).build();
1761
+ const records = await this.find(collection, query);
1762
+ return records.length;
1763
+ }
1764
+
1524
1765
  /**
1525
1766
  * Create a WebSocket client
1526
1767
  */
@@ -1531,13 +1772,7 @@ export class EkoDBClient {
1531
1772
  // ========== RAG Helper Methods ==========
1532
1773
 
1533
1774
  /**
1534
- * Generate embeddings for text using ekoDB's native Functions
1535
- *
1536
- * This helper simplifies embedding generation by:
1537
- * 1. Creating a temporary collection with the text
1538
- * 2. Running a Script with FindAll + Embed Functions
1539
- * 3. Extracting and returning the embedding vector
1540
- * 4. Cleaning up temporary resources
1775
+ * Generate embeddings for a single text
1541
1776
  *
1542
1777
  * @param text - The text to generate embeddings for
1543
1778
  * @param model - The embedding model to use (e.g., "text-embedding-3-small")
@@ -1553,57 +1788,36 @@ export class EkoDBClient {
1553
1788
  * ```
1554
1789
  */
1555
1790
  async embed(text: string, model: string): Promise<number[]> {
1556
- const tempCollection = `embed_temp_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
1557
-
1558
- try {
1559
- // Insert temporary record with the text
1560
- await this.insert(tempCollection, { text }, undefined);
1561
-
1562
- // Create Script with FindAll + Embed Functions
1563
- const tempLabel = `embed_script_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
1564
- const script: Script = {
1565
- label: tempLabel,
1566
- name: "Generate Embedding",
1567
- description: "Temporary script for embedding generation",
1568
- version: "1.0",
1569
- parameters: {},
1570
- functions: [
1571
- {
1572
- type: "FindAll",
1573
- collection: tempCollection,
1574
- },
1575
- {
1576
- type: "Embed",
1577
- input_field: "text",
1578
- output_field: "embedding",
1579
- model: model,
1580
- } as any,
1581
- ],
1582
- tags: [],
1583
- };
1584
-
1585
- // Save and execute the script
1586
- const scriptId = await this.saveScript(script);
1587
- const result = await this.callScript(scriptId, undefined);
1588
-
1589
- // Clean up
1590
- await this.deleteScript(scriptId).catch(() => {});
1591
- await this.deleteCollection(tempCollection).catch(() => {});
1791
+ const response = await this.embedRequest({ text, model });
1792
+ if (response.embeddings.length === 0) {
1793
+ throw new Error("No embedding returned");
1794
+ }
1795
+ return response.embeddings[0];
1796
+ }
1592
1797
 
1593
- // Extract embedding from result
1594
- if (result.records && result.records.length > 0) {
1595
- const record = result.records[0];
1596
- if (record.embedding && Array.isArray(record.embedding)) {
1597
- return record.embedding as number[];
1598
- }
1599
- }
1798
+ /**
1799
+ * Generate embeddings for multiple texts in a single batch request
1800
+ *
1801
+ * @param texts - Array of texts to generate embeddings for
1802
+ * @param model - The embedding model to use
1803
+ * @returns Array of embedding vectors
1804
+ */
1805
+ async embedBatch(texts: string[], model: string): Promise<number[][]> {
1806
+ const response = await this.embedRequest({ texts, model });
1807
+ return response.embeddings;
1808
+ }
1600
1809
 
1601
- throw new Error("Failed to extract embedding from result");
1602
- } catch (error) {
1603
- // Ensure cleanup even on error
1604
- await this.deleteCollection(tempCollection).catch(() => {});
1605
- throw error;
1606
- }
1810
+ /**
1811
+ * Internal: make embed API request
1812
+ */
1813
+ private async embedRequest(request: EmbedRequest): Promise<EmbedResponse> {
1814
+ return this.makeRequest<EmbedResponse>(
1815
+ "POST",
1816
+ "/api/embed",
1817
+ request,
1818
+ 0,
1819
+ true, // Force JSON
1820
+ );
1607
1821
  }
1608
1822
 
1609
1823
  /**
package/src/index.ts CHANGED
@@ -70,4 +70,10 @@ export type {
70
70
  GetMessagesResponse,
71
71
  UpdateSessionRequest,
72
72
  MergeSessionsRequest,
73
+ ChatModels,
74
+ EmbedRequest,
75
+ EmbedResponse,
76
+ UserFunction,
77
+ ToolChoice,
78
+ ToolConfig,
73
79
  } from "./client";
@@ -0,0 +1,7 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ exclude: ["dist/**", "node_modules/**"],
6
+ },
7
+ });