aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFuwn <[email protected]>2026-02-03 20:51:41 -0800
committerFuwn <[email protected]>2026-02-03 20:51:41 -0800
commit0af9376ddabc50548a2d06710a071f40b864816f (patch)
treeb45363f2b411e656af461180dd1deff9965c8575
parentfeat(web): Initialise T3 Stack (diff)
downloadarchived-imemio-0af9376ddabc50548a2d06710a071f40b864816f.tar.xz
archived-imemio-0af9376ddabc50548a2d06710a071f40b864816f.zip
feat(sdk): Add semantic search and embedding support
-rw-r--r--packages/sdk/package.json3
-rw-r--r--packages/sdk/src/embedding-service.ts38
-rw-r--r--packages/sdk/src/index.ts7
-rw-r--r--packages/sdk/src/supabase-store.ts73
-rw-r--r--packages/sdk/src/types.ts21
5 files changed, 133 insertions, 9 deletions
diff --git a/packages/sdk/package.json b/packages/sdk/package.json
index 2f830f9..21e7e26 100644
--- a/packages/sdk/package.json
+++ b/packages/sdk/package.json
@@ -27,6 +27,7 @@
"vitest": "^3.0.5"
},
"dependencies": {
- "@supabase/supabase-js": "^2.94.0"
+ "@supabase/supabase-js": "^2.94.0",
+ "openai": "^6.17.0"
}
}
diff --git a/packages/sdk/src/embedding-service.ts b/packages/sdk/src/embedding-service.ts
new file mode 100644
index 0000000..9d3367f
--- /dev/null
+++ b/packages/sdk/src/embedding-service.ts
@@ -0,0 +1,38 @@
+import OpenAI from "openai";
+
+export type EmbeddingModel =
+ | "text-embedding-3-small"
+ | "text-embedding-3-large";
+
+export type EmbeddingServiceConfiguration = {
+ apiKey: string;
+ model?: EmbeddingModel;
+};
+
+export class EmbeddingService {
+ private client: OpenAI;
+ private model: EmbeddingModel;
+
+ constructor(configuration: EmbeddingServiceConfiguration) {
+ this.client = new OpenAI({ apiKey: configuration.apiKey });
+ this.model = configuration.model ?? "text-embedding-3-small";
+ }
+
+ async generate(text: string): Promise<number[]> {
+ const response = await this.client.embeddings.create({
+ model: this.model,
+ input: text,
+ });
+
+ return response.data[0]?.embedding ?? [];
+ }
+
+ async generateBatch(texts: string[]): Promise<number[][]> {
+ const response = await this.client.embeddings.create({
+ model: this.model,
+ input: texts,
+ });
+
+ return response.data.map((item) => item.embedding);
+ }
+}
diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts
index b61164e..e02192b 100644
--- a/packages/sdk/src/index.ts
+++ b/packages/sdk/src/index.ts
@@ -9,6 +9,8 @@ export type {
MemoryCreateInput,
MemoryUpdateInput,
MemoryFilter,
+ SearchOptions,
+ SearchResult,
} from "./types.js";
export type { Result, Success, Failure } from "./result.js";
export type {
@@ -36,3 +38,8 @@ export {
} from "./supabase-client.js";
export { SupabaseStore } from "./supabase-store.js";
export { SupabaseProjectStore } from "./supabase-project-store.js";
+export {
+ EmbeddingService,
+ type EmbeddingModel,
+ type EmbeddingServiceConfiguration,
+} from "./embedding-service.js";
diff --git a/packages/sdk/src/supabase-store.ts b/packages/sdk/src/supabase-store.ts
index acb9364..c8465c4 100644
--- a/packages/sdk/src/supabase-store.ts
+++ b/packages/sdk/src/supabase-store.ts
@@ -6,6 +6,8 @@ import type {
MemoryCreateInput,
MemoryFilter,
MemoryUpdateInput,
+ SearchOptions,
+ SearchResult,
Tag,
} from "./types.js";
@@ -22,6 +24,18 @@ type MemoryRow = {
updated_at: string;
};
+type SearchResultRow = {
+ id: string;
+ content: string;
+ project_id: string;
+ folder_id: string | null;
+ tags: Tag[];
+ metadata: Record<string, unknown>;
+ similarity: number;
+ created_at: string;
+ updated_at: string;
+};
+
function memoryNotFoundError(memoryId: string): MemoryNotFoundError {
return { type: "MEMORY_NOT_FOUND", memoryId };
}
@@ -39,6 +53,20 @@ function rowToMemory(row: MemoryRow): Memory {
};
}
+function rowToSearchResult(row: SearchResultRow): SearchResult {
+ return {
+ id: row.id,
+ content: row.content,
+ projectId: row.project_id,
+ folderId: row.folder_id,
+ tags: row.tags ?? [],
+ metadata: row.metadata ?? {},
+ similarity: row.similarity,
+ createdAt: new Date(row.created_at),
+ updatedAt: new Date(row.updated_at),
+ };
+}
+
export class SupabaseStore implements MemoryStore {
private client: SupabaseClient;
private userId: string;
@@ -49,16 +77,22 @@ export class SupabaseStore implements MemoryStore {
}
async create(input: MemoryCreateInput): Promise<Memory> {
+ const insertData: Record<string, unknown> = {
+ user_id: this.userId,
+ project_id: input.projectId,
+ folder_id: input.folderId ?? null,
+ content: input.content,
+ tags: input.tags ?? [],
+ metadata: input.metadata ?? {},
+ };
+
+ if (input.embedding) {
+ insertData.embedding = input.embedding;
+ }
+
const { data, error } = await this.client
.from("memories")
- .insert({
- user_id: this.userId,
- project_id: input.projectId,
- folder_id: input.folderId ?? null,
- content: input.content,
- tags: input.tags ?? [],
- metadata: input.metadata ?? {},
- })
+ .insert(insertData)
.select()
.single();
@@ -106,6 +140,10 @@ export class SupabaseStore implements MemoryStore {
updates.metadata = input.metadata;
}
+ if (input.embedding !== undefined) {
+ updates.embedding = input.embedding;
+ }
+
const { data, error } = await this.client
.from("memories")
.update(updates)
@@ -164,4 +202,23 @@ export class SupabaseStore implements MemoryStore {
return (data as MemoryRow[]).map(rowToMemory);
}
+
+ async search(
+ embedding: number[],
+ options?: SearchOptions,
+ ): Promise<SearchResult[]> {
+ const { data, error } = await this.client.rpc("search_memories", {
+ query_embedding: embedding,
+ match_threshold: options?.threshold ?? 0.7,
+ match_count: options?.limit ?? 10,
+ filter_project_id: options?.projectId ?? null,
+ filter_folder_id: options?.folderId ?? null,
+ });
+
+ if (error) {
+ throw new Error(`Failed to search memories: ${error.message}`);
+ }
+
+ return (data as SearchResultRow[]).map(rowToSearchResult);
+ }
}
diff --git a/packages/sdk/src/types.ts b/packages/sdk/src/types.ts
index 306da64..76a63de 100644
--- a/packages/sdk/src/types.ts
+++ b/packages/sdk/src/types.ts
@@ -41,6 +41,7 @@ export type MemoryCreateInput = {
folderId?: string;
tags?: Tag[];
metadata?: MemoryMetadata;
+ embedding?: number[];
};
export type MemoryUpdateInput = {
@@ -48,6 +49,7 @@ export type MemoryUpdateInput = {
folderId?: string | null;
tags?: Tag[];
metadata?: MemoryMetadata;
+ embedding?: number[];
};
export type MemoryFilter = {
@@ -55,3 +57,22 @@ export type MemoryFilter = {
folderId?: string;
tags?: string[];
};
+
+export type SearchOptions = {
+ threshold?: number;
+ limit?: number;
+ projectId?: string;
+ folderId?: string;
+};
+
+export type SearchResult = {
+ id: string;
+ content: string;
+ projectId: string;
+ folderId: string | null;
+ tags: Tag[];
+ metadata: MemoryMetadata;
+ similarity: number;
+ createdAt: Date;
+ updatedAt: Date;
+};