Skip to content

Commit 4b63cd8

Browse files
Claudeclaude
authored andcommitted
fix: recall and semantic search call RPC directly, not via Edge Function
scarSearch() and semanticSearch() previously routed through the ww-mcp Edge Function which only exists on Orchestra's Supabase. Users with their own Supabase got a 404. Now both functions generate embeddings locally (via OpenRouter/OpenAI) and call the gitmem_scar_search / gitmem_semantic_search RPC functions directly via PostgREST. No Edge Function middleman needed. E2E test: 8/8 tools pass on fresh user Supabase (session_start, create_learning, recall, create_decision, create_thread, search, log, list_threads). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 9a5462d commit 4b63cd8

1 file changed

Lines changed: 77 additions & 19 deletions

File tree

src/services/supabase-client.ts

Lines changed: 77 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -189,27 +189,54 @@ export async function upsertRecord<T = unknown>(
189189

190190
/**
191191
* Semantic search across tables
192+
*
193+
* Generates an embedding for the query, then calls the gitmem_semantic_search
194+
* RPC function directly via PostgREST.
192195
*/
193196
export async function semanticSearch<T = unknown>(
194197
options: SupabaseSearchOptions
195198
): Promise<T[]> {
196-
const { query, tables, match_count = 10, project } = options;
199+
if (!isConfigured()) {
200+
throw new Error("Supabase not configured");
201+
}
197202

198-
const args: Record<string, unknown> = {
199-
query,
200-
match_count,
201-
};
203+
const { query, match_count = 10 } = options;
204+
205+
// Generate embedding for the query
206+
const { embed } = await import("./embedding.js");
207+
const embedding = await embed(query);
202208

203-
if (tables && tables.length > 0) {
204-
args.tables = tables;
209+
if (!embedding) {
210+
console.error("[semantic-search] No embedding provider configured — cannot run semantic search");
211+
return [];
205212
}
206213

207-
if (project) {
208-
args.project = project;
214+
// Call the RPC function directly via PostgREST
215+
const rpcName = `${getTableName("").replace(/_$/, "")}_semantic_search`;
216+
const url = `${SUPABASE_URL}/rest/v1/rpc/${rpcName}`;
217+
218+
const response = await fetch(url, {
219+
method: "POST",
220+
headers: {
221+
"Content-Type": "application/json",
222+
apikey: SUPABASE_KEY,
223+
Authorization: `Bearer ${SUPABASE_KEY}`,
224+
},
225+
body: JSON.stringify({
226+
query_embedding: `[${embedding.join(",")}]`,
227+
match_count,
228+
similarity_threshold: 0.0,
229+
}),
230+
signal: AbortSignal.timeout(15_000),
231+
});
232+
233+
if (!response.ok) {
234+
const text = await response.text();
235+
throw new Error(`Supabase RPC error: ${response.status} - ${text.slice(0, 200)}`);
209236
}
210237

211-
const result = await callMcp<{ results: T[] }>("semantic_search", args);
212-
return result.results || [];
238+
const rows = (await response.json()) as T[];
239+
return rows || [];
213240
}
214241

215242
// ============================================================================
@@ -551,23 +578,54 @@ export async function loadScarsWithEmbeddings<T = unknown>(
551578

552579
/**
553580
* Scar search with severity weighting
581+
*
582+
* Generates an embedding for the query, then calls the gitmem_scar_search
583+
* RPC function directly via PostgREST. No Edge Function required.
554584
*/
555585
export async function scarSearch<T = unknown>(
556586
query: string,
557587
matchCount = 5,
558588
project?: Project
559589
): Promise<T[]> {
560-
const args: Record<string, unknown> = {
561-
query,
562-
match_count: matchCount,
563-
};
590+
if (!isConfigured()) {
591+
throw new Error("Supabase not configured");
592+
}
593+
594+
// Generate embedding for the query
595+
const { embed } = await import("./embedding.js");
596+
const embedding = await embed(query);
597+
598+
if (!embedding) {
599+
console.error("[scar-search] No embedding provider configured — cannot run semantic search");
600+
return [];
601+
}
602+
603+
// Call the RPC function directly via PostgREST
604+
const rpcName = `${getTableName("").replace(/_$/, "")}_scar_search`;
605+
const url = `${SUPABASE_URL}/rest/v1/rpc/${rpcName}`;
606+
607+
const response = await fetch(url, {
608+
method: "POST",
609+
headers: {
610+
"Content-Type": "application/json",
611+
apikey: SUPABASE_KEY,
612+
Authorization: `Bearer ${SUPABASE_KEY}`,
613+
},
614+
body: JSON.stringify({
615+
query_embedding: `[${embedding.join(",")}]`,
616+
match_count: matchCount,
617+
similarity_threshold: 0.0,
618+
}),
619+
signal: AbortSignal.timeout(15_000),
620+
});
564621

565-
if (project) {
566-
args.project = project;
622+
if (!response.ok) {
623+
const text = await response.text();
624+
throw new Error(`Supabase RPC error: ${response.status} - ${text.slice(0, 200)}`);
567625
}
568626

569-
const result = await callMcp<{ results: T[] }>("scar_search", args);
570-
return result.results || [];
627+
const rows = (await response.json()) as T[];
628+
return rows || [];
571629
}
572630

573631
/**

0 commit comments

Comments
 (0)