From 31084777c7d235253b5aafb3dc7953947937d695 Mon Sep 17 00:00:00 2001 From: Avaer Kazmer Date: Thu, 26 Dec 2024 17:07:02 -0800 Subject: [PATCH 1/8] Use pglite storage adapter by default in the browser --- apps/chat/app/new/editor.tsx | 5 ++-- .../react-agents-browser/browser-runtime.ts | 3 ++ .../packages/react-agents-browser/worker.tsx | 3 +- .../packages/react-agents/root.ts | 28 ++++++++++++++++--- .../react-agents/storage/pglite-storage.mjs | 5 +++- 5 files changed, 36 insertions(+), 8 deletions(-) diff --git a/apps/chat/app/new/editor.tsx b/apps/chat/app/new/editor.tsx index bb30e715e..ff2c2c2d0 100644 --- a/apps/chat/app/new/editor.tsx +++ b/apps/chat/app/new/editor.tsx @@ -6,7 +6,8 @@ import Editor, { useMonaco } from '@monaco-editor/react'; import { Button } from '@/components/ui/button'; import { deployEndpointUrl, r2EndpointUrl } from '@/utils/const/endpoints'; import { getJWT } from '@/lib/jwt'; -import { getUserIdForJwt, getUserForJwt } from '@/utils/supabase/supabase-client'; +import { getUserForJwt } from '@/utils/supabase/supabase-client'; +import { PGliteStorage } from 'react-agents/storage/pglite-storage.mjs' import type { StoreItem, SubscriptionProps, @@ -42,7 +43,6 @@ import { currencies, intervals } from 'react-agents/constants.mjs'; import { buildAgentSrc } from 'react-agents-builder'; import { ReactAgentsWorker } from 'react-agents-browser'; import type { FetchableWorker } from 'react-agents-browser/types'; -// import { IconButton } from 'ucom'; import { BackButton } from '@/components/back'; // @@ -355,6 +355,7 @@ export default function AgentEditor({ agentJson, agentModuleSrc, env, + storageAdapter: 'pglite', }); setWorker(newWorker); diff --git a/packages/usdk/packages/upstreet-agent/packages/react-agents-browser/browser-runtime.ts b/packages/usdk/packages/upstreet-agent/packages/react-agents-browser/browser-runtime.ts index 30a4a6ff0..112e9dab0 100644 --- a/packages/usdk/packages/upstreet-agent/packages/react-agents-browser/browser-runtime.ts +++ b/packages/usdk/packages/upstreet-agent/packages/react-agents-browser/browser-runtime.ts @@ -8,10 +8,12 @@ export class ReactAgentsWorker { agentJson, agentModuleSrc, env, + storageAdapter, }: { agentJson: any, agentModuleSrc: string, env: any, + storageAdapter?: string, }) { if ( !agentJson || @@ -34,6 +36,7 @@ export class ReactAgentsWorker { agentJson, env, agentModuleSrc, + storageAdapter, }, }); this.worker.addEventListener('error', e => { diff --git a/packages/usdk/packages/upstreet-agent/packages/react-agents-browser/worker.tsx b/packages/usdk/packages/upstreet-agent/packages/react-agents-browser/worker.tsx index d60db39c8..4e18da0c9 100644 --- a/packages/usdk/packages/upstreet-agent/packages/react-agents-browser/worker.tsx +++ b/packages/usdk/packages/upstreet-agent/packages/react-agents-browser/worker.tsx @@ -25,7 +25,7 @@ globalThis.onmessage = (event: any) => { if (!rootPromise) { rootPromise = (async () => { const { args } = event.data; - const { agentJson, env, agentModuleSrc } = args; + const { agentJson, env, agentModuleSrc, storageAdapter } = args; if (typeof agentModuleSrc !== 'string') { throw new Error('agent worker: missing agentModuleSrc'); } @@ -38,6 +38,7 @@ globalThis.onmessage = (event: any) => { agentJson, codecs, env, + storageAdapter, }; const root = createRoot(rootOpts); root.render(); diff --git a/packages/usdk/packages/upstreet-agent/packages/react-agents/root.ts b/packages/usdk/packages/upstreet-agent/packages/react-agents/root.ts index b399ac03a..ac1eb82e3 100644 --- a/packages/usdk/packages/upstreet-agent/packages/react-agents/root.ts +++ b/packages/usdk/packages/upstreet-agent/packages/react-agents/root.ts @@ -1,6 +1,7 @@ -import React, { ReactNode } from 'react'; +import { ReactNode } from 'react'; import { headers } from './constants.mjs'; import { SupabaseStorage } from './storage/supabase-storage.mjs'; +import { PGliteStorage } from './storage/pglite-storage.mjs'; import { AgentRenderer } from './classes/agent-renderer.tsx'; import { ChatsSpecification } from './classes/chats-specification.ts'; import { multiplayerEndpointUrl } from './util/endpoints.mjs'; @@ -32,11 +33,30 @@ export class Root extends EventTarget { const { agentJson = {}, env = {}, - storageAdapter = new SupabaseStorage({ - jwt: env.AGENT_TOKEN, - }), codecs = {}, } = opts; + const storageAdapter = (() => { + const _storageAdapter = opts.storageAdapter ?? 'supabase'; + if (typeof _storageAdapter === 'string') { + switch (_storageAdapter) { + case 'supabase': { + return new SupabaseStorage({ + jwt: env.AGENT_TOKEN, + }); + } + case 'pglite': { + return new PGliteStorage({ + path: env.PGLITE_PATH, + }); + } + default: { + throw new Error('unknown storage adapter type: ' + _storageAdapter); + } + } + } else { + return _storageAdapter; + } + })(); this.chatsSpecification = new ChatsSpecification({ agentId: agentJson.id, diff --git a/packages/usdk/packages/upstreet-agent/packages/react-agents/storage/pglite-storage.mjs b/packages/usdk/packages/upstreet-agent/packages/react-agents/storage/pglite-storage.mjs index 698f67668..30a2d70d9 100644 --- a/packages/usdk/packages/upstreet-agent/packages/react-agents/storage/pglite-storage.mjs +++ b/packages/usdk/packages/upstreet-agent/packages/react-agents/storage/pglite-storage.mjs @@ -4,9 +4,12 @@ import { PGlite } from '@electric-sql/pglite'; const defaultSchema = 'public'; export class PGliteStorage { + pglite; + postgrestClient; + constructor({ path, - }) { + } = {}) { const pglite = new PGlite(path); this.pglite = pglite; // await p.waitReady; From aed0f3a6ae1923d09794766b05df4bf721336544 Mon Sep 17 00:00:00 2001 From: Avaer Kazmer Date: Thu, 26 Dec 2024 18:15:40 -0800 Subject: [PATCH 2/8] Add pglite initialization --- .../react-agents/storage/pglite-storage.mjs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/packages/usdk/packages/upstreet-agent/packages/react-agents/storage/pglite-storage.mjs b/packages/usdk/packages/upstreet-agent/packages/react-agents/storage/pglite-storage.mjs index 30a2d70d9..4b99eaf5b 100644 --- a/packages/usdk/packages/upstreet-agent/packages/react-agents/storage/pglite-storage.mjs +++ b/packages/usdk/packages/upstreet-agent/packages/react-agents/storage/pglite-storage.mjs @@ -1,8 +1,24 @@ import { PostgrestClient } from '@supabase/postgrest-js'; import { PGlite } from '@electric-sql/pglite'; +import dedent from 'dedent'; const defaultSchema = 'public'; +const initQuery = [ + dedent`\ + create table + chat_specifications ( + id text not null, + created_at timestamp with time zone not null default now(), + user_id uuid not null, + data jsonb null, + uid uuid not null default gen_random_uuid (), + constraint chat_specifications_pkey primary key (uid), + constraint chat_specifications_uid_key unique (uid) + ); + `, +].join('\n'); + export class PGliteStorage { pglite; postgrestClient; @@ -121,6 +137,15 @@ export class PGliteStorage { fetch, schema: defaultSchema, }); + + (async () => { + await this.#init(); + })(); + } + async #init() { + console.log('init 1'); + const initResult = await this.pglite.query(initQuery); + console.log('init 2', initResult); } query(query) { return this.pglite.query(query); From 1cd2e43abec33803d951ad3b59933153d41ee304 Mon Sep 17 00:00:00 2001 From: Avaer Kazmer Date: Thu, 26 Dec 2024 18:15:59 -0800 Subject: [PATCH 3/8] Fix pglite parsing --- .../react-agents/storage/pglite-storage.mjs | 87 +++++++++---------- 1 file changed, 42 insertions(+), 45 deletions(-) diff --git a/packages/usdk/packages/upstreet-agent/packages/react-agents/storage/pglite-storage.mjs b/packages/usdk/packages/upstreet-agent/packages/react-agents/storage/pglite-storage.mjs index 4b99eaf5b..3fa759d00 100644 --- a/packages/usdk/packages/upstreet-agent/packages/react-agents/storage/pglite-storage.mjs +++ b/packages/usdk/packages/upstreet-agent/packages/react-agents/storage/pglite-storage.mjs @@ -32,19 +32,39 @@ export class PGliteStorage { // Hook up PostgrestQueryBuilder to PGlite by implementing fetch const fetch = async (url, init = {}) => { - // console.log('fetch', url, init); - // Parse the SQL query from the URL and body const urlObj = new URL(url); const path = urlObj.pathname; const searchParams = urlObj.searchParams; const method = init.method || 'GET'; - - // // Get table name from headers - // const tableName = (init.headers?.['Accept-Profile'] || init.headers?.['Content-Profile'] || '').replace(/[^a-zA-Z0-9_]/g, ''); + // Get table name from the basename of the path const tableName = path.split('/').pop(); + const parseConditions = (params) => { + return Array.from(params) + .map(([key, value]) => { + const [operator, filterValue] = value.split('.'); + switch(operator) { + case 'eq': + return `${key} = '${filterValue}'`; + case 'gt': + return `${key} > '${filterValue}'`; + case 'lt': + return `${key} < '${filterValue}'`; + case 'gte': + return `${key} >= '${filterValue}'`; + case 'lte': + return `${key} <= '${filterValue}'`; + case 'neq': + return `${key} != '${filterValue}'`; + default: + return `${key} = '${value}'`; + } + }) + .join(' AND '); + }; + // Convert Postgrest request to SQL query let query; if (method === 'GET') { @@ -54,27 +74,20 @@ export class PGliteStorage { delete filter.select; query = `SELECT ${select} FROM ${tableName}`; + + // Handle PostgREST filter operators if (Object.keys(filter).length > 0) { - const conditions = Object.entries(filter) - .map(([key, value]) => `${key} = '${value}'`) - .join(' AND '); + const conditions = parseConditions(Object.entries(filter)); query += ` WHERE ${conditions}`; } } else if (method === 'POST') { - // Handle INSERT or CREATE DATABASE - // if (path === '/rpc') { - // // read the query from the request body - // const body = JSON.parse(init.body); - // query = body.query; - // } else { - // Regular INSERT - const body = JSON.parse(init.body); - const columns = Object.keys(body).join(', '); - const values = Object.values(body) - .map(v => typeof v === 'string' ? `'${v}'` : v) - .join(', '); - query = `INSERT INTO ${tableName} (${columns}) VALUES (${values})`; - // } + // Handle INSERT + const body = JSON.parse(init.body); + const columns = Object.keys(body).join(', '); + const values = Object.values(body) + .map(v => typeof v === 'string' ? `'${v}'` : v) + .join(', '); + query = `INSERT INTO ${tableName} (${columns}) VALUES (${values})`; } else if (method === 'PATCH') { // Handle UPDATE const body = JSON.parse(init.body); @@ -82,45 +95,29 @@ export class PGliteStorage { .map(([key, value]) => `${key} = '${value}'`) .join(', '); query = `UPDATE ${tableName} SET ${updates}`; - // Add WHERE clause from search params + + // Add WHERE clause from search params with operators if (searchParams.toString()) { - const conditions = Array.from(searchParams) - .map(([key, value]) => `${key} = '${value}'`) - .join(' AND '); + const conditions = parseConditions(searchParams); query += ` WHERE ${conditions}`; } } else if (method === 'DELETE') { // Handle DELETE - if (path.startsWith('/database/')) { - // Handle DROP DATABASE - const dbName = path.slice('/database/'.length).replace(/[^a-zA-Z0-9_]/g, ''); - query = `DROP DATABASE ${dbName}`; - } else { - // Regular DELETE - query = `DELETE FROM ${tableName}`; - if (searchParams.toString()) { - const conditions = Array.from(searchParams) - .map(([key, value]) => `${key} = '${value}'`) - .join(' AND '); - query += ` WHERE ${conditions}`; - } + query = `DELETE FROM ${tableName}`; + if (searchParams.toString()) { + const conditions = parseConditions(searchParams); + query += ` WHERE ${conditions}`; } } try { - // Execute the protocol message - // console.log('executing query', query); const result = await pglite.query(query); - // console.log('result', result); // , new TextDecoder().decode(result.data)); - - // Parse result and convert to JSON response return new Response(JSON.stringify(result?.rows ?? null), { status: 200, headers: { 'Content-Type': 'application/vnd.pgrst.object+json', } }); - } catch (err) { console.warn('error', err); return new Response(JSON.stringify({ From 5e434f145f38a68d3e0ae870c42d488470efe152 Mon Sep 17 00:00:00 2001 From: Avaer Kazmer Date: Thu, 26 Dec 2024 19:14:12 -0800 Subject: [PATCH 4/8] Add pgvector support to pglite-storage --- .../react-agents/storage/pglite-storage.mjs | 301 ++++++++++++------ 1 file changed, 201 insertions(+), 100 deletions(-) diff --git a/packages/usdk/packages/upstreet-agent/packages/react-agents/storage/pglite-storage.mjs b/packages/usdk/packages/upstreet-agent/packages/react-agents/storage/pglite-storage.mjs index 3fa759d00..ea66252bb 100644 --- a/packages/usdk/packages/upstreet-agent/packages/react-agents/storage/pglite-storage.mjs +++ b/packages/usdk/packages/upstreet-agent/packages/react-agents/storage/pglite-storage.mjs @@ -1,10 +1,15 @@ import { PostgrestClient } from '@supabase/postgrest-js'; import { PGlite } from '@electric-sql/pglite'; +import { vector } from '@electric-sql/pglite/vector'; import dedent from 'dedent'; +import { QueueManager } from 'queue-manager'; const defaultSchema = 'public'; -const initQuery = [ +const initQueries = [ + dedent`\ + CREATE EXTENSION vector; + `, dedent`\ create table chat_specifications ( @@ -17,118 +22,201 @@ const initQuery = [ constraint chat_specifications_uid_key unique (uid) ); `, -].join('\n'); + dedent`\ + create table + keys_values ( + created_at timestamp with time zone not null default now(), + value jsonb null, + agent_id uuid null, + key text not null, + constraint keys_values_pkey primary key (key) + ); + `, + dedent`\ + create table + agent_messages ( + user_id uuid not null default gen_random_uuid (), + created_at timestamp with time zone not null default now(), + method text not null, + src_user_id uuid null, + src_name text null, + text text null, + args jsonb not null, + id uuid not null default gen_random_uuid (), + embedding vector(3072) not null, + conversation_id text null, + attachments jsonb null, + constraint agent_messages_pkey primary key (id) + ); + `, + dedent`\ + create table + webhooks ( + id uuid not null default gen_random_uuid (), + user_id uuid null, + type text not null, + data jsonb not null, + created_at timestamp with time zone not null default now(), + dev boolean null, + constraint stripe_connect_payments_pkey primary key (id) + ); + `, +]; export class PGliteStorage { pglite; postgrestClient; + queueManager = new QueueManager(); constructor({ path, } = {}) { - const pglite = new PGlite(path); + // console.log('vector extension', vector); + // const vector2 = { + // name: 'vector', + // setup: (...args) => { + // console.log('vector setup', args, new Error().stack); + // return vector.setup(...args); + // }, + // }; + const pglite = new PGlite({ + dataDir: path, + extensions: { + vector, + // vector: vector2, + }, + }); this.pglite = pglite; - // await p.waitReady; + // (async () => { + // this.queueManager.waitForTurn(async () => { + // await pglite.waitReady; + // }); + // })(); // Hook up PostgrestQueryBuilder to PGlite by implementing fetch const fetch = async (url, init = {}) => { - // Parse the SQL query from the URL and body - const urlObj = new URL(url); - const path = urlObj.pathname; - const searchParams = urlObj.searchParams; - const method = init.method || 'GET'; + return await this.queueManager.waitForTurn(async () => { + // Parse the SQL query from the URL and body + const urlObj = new URL(url); + const path = urlObj.pathname; + const searchParams = urlObj.searchParams; + const method = init.method || 'GET'; - // Get table name from the basename of the path - const tableName = path.split('/').pop(); + // Get table name from the basename of the path + const tableName = path.split('/').pop(); - const parseConditions = (params) => { - return Array.from(params) - .map(([key, value]) => { + const parseConditions = (params) => { + const conditions = []; + for (const [key, value] of params) { + if (key === 'select' || key === 'order' || key === 'limit') { + continue; + } const [operator, filterValue] = value.split('.'); switch(operator) { case 'eq': - return `${key} = '${filterValue}'`; + conditions.push(`${key} = '${filterValue}'`); + break; case 'gt': - return `${key} > '${filterValue}'`; - case 'lt': - return `${key} < '${filterValue}'`; + conditions.push(`${key} > '${filterValue}'`); + break; + case 'lt': + conditions.push(`${key} < '${filterValue}'`); + break; case 'gte': - return `${key} >= '${filterValue}'`; + conditions.push(`${key} >= '${filterValue}'`); + break; case 'lte': - return `${key} <= '${filterValue}'`; + conditions.push(`${key} <= '${filterValue}'`); + break; case 'neq': - return `${key} != '${filterValue}'`; + conditions.push(`${key} != '${filterValue}'`); + break; default: - return `${key} = '${value}'`; + conditions.push(`${key} = '${value}'`); } - }) - .join(' AND '); - }; + } + return conditions.join(' AND '); + }; - // Convert Postgrest request to SQL query - let query; - if (method === 'GET') { - // Handle SELECT - const select = searchParams.get('select') || '*'; - const filter = Object.fromEntries(searchParams); - delete filter.select; - - query = `SELECT ${select} FROM ${tableName}`; - - // Handle PostgREST filter operators - if (Object.keys(filter).length > 0) { - const conditions = parseConditions(Object.entries(filter)); - query += ` WHERE ${conditions}`; - } - } else if (method === 'POST') { - // Handle INSERT - const body = JSON.parse(init.body); - const columns = Object.keys(body).join(', '); - const values = Object.values(body) - .map(v => typeof v === 'string' ? `'${v}'` : v) - .join(', '); - query = `INSERT INTO ${tableName} (${columns}) VALUES (${values})`; - } else if (method === 'PATCH') { - // Handle UPDATE - const body = JSON.parse(init.body); - const updates = Object.entries(body) - .map(([key, value]) => `${key} = '${value}'`) - .join(', '); - query = `UPDATE ${tableName} SET ${updates}`; - - // Add WHERE clause from search params with operators - if (searchParams.toString()) { - const conditions = parseConditions(searchParams); - query += ` WHERE ${conditions}`; - } - } else if (method === 'DELETE') { - // Handle DELETE - query = `DELETE FROM ${tableName}`; - if (searchParams.toString()) { + // Convert Postgrest request to SQL query + let query; + if (method === 'GET') { + // Handle SELECT + const select = searchParams.get('select') || '*'; + const filter = Object.fromEntries(searchParams); + + query = `SELECT ${select} FROM ${tableName}`; + + // Handle PostgREST filter operators const conditions = parseConditions(searchParams); - query += ` WHERE ${conditions}`; - } - } + if (conditions) { + query += ` WHERE ${conditions}`; + } + + // Handle order + const order = searchParams.get('order'); + if (order) { + const [column, direction] = order.split('.'); + query += ` ORDER BY ${column} ${direction.toUpperCase()}`; + } - try { - const result = await pglite.query(query); - return new Response(JSON.stringify(result?.rows ?? null), { - status: 200, - headers: { - 'Content-Type': 'application/vnd.pgrst.object+json', + // Handle limit + const limit = searchParams.get('limit'); + if (limit) { + query += ` LIMIT ${limit}`; } - }); - } catch (err) { - console.warn('error', err); - return new Response(JSON.stringify({ - error: err.message - }), { - status: 500, - headers: { - 'Content-Type': 'application/json' + + } else if (method === 'POST') { + // Handle INSERT + const body = JSON.parse(init.body); + const columns = Object.keys(body).join(', '); + const values = Object.values(body) + .map(v => typeof v === 'string' ? `'${v}'` : v) + .join(', '); + query = `INSERT INTO ${tableName} (${columns}) VALUES (${values})`; + } else if (method === 'PATCH') { + // Handle UPDATE + const body = JSON.parse(init.body); + const updates = Object.entries(body) + .map(([key, value]) => `${key} = '${value}'`) + .join(', '); + query = `UPDATE ${tableName} SET ${updates}`; + + // Add WHERE clause from search params with operators + const conditions = parseConditions(searchParams); + if (conditions) { + query += ` WHERE ${conditions}`; } - }); - } + } else if (method === 'DELETE') { + // Handle DELETE + query = `DELETE FROM ${tableName}`; + const conditions = parseConditions(searchParams); + if (conditions) { + query += ` WHERE ${conditions}`; + } + } + + try { + console.log('execute query', query); + const result = await pglite.query(query); + return new Response(JSON.stringify(result?.rows ?? null), { + status: 200, + headers: { + 'Content-Type': 'application/vnd.pgrst.object+json', + } + }); + } catch (err) { + console.warn('error', err); + return new Response(JSON.stringify({ + error: err.message + }), { + status: 500, + headers: { + 'Content-Type': 'application/json' + } + }); + } + }); }; this.postgrestClient = new PostgrestClient(new URL('http://localhost'), { fetch, @@ -140,23 +228,36 @@ export class PGliteStorage { })(); } async #init() { - console.log('init 1'); - const initResult = await this.pglite.query(initQuery); - console.log('init 2', initResult); + // console.log('init 1'); + const initPromises = []; + for (const initQuery of initQueries) { + const p = this.query(initQuery); + initPromises.push(p); + } + const initResults = await Promise.all(initPromises); + // console.log('init 2', initResults); } - query(query) { - return this.pglite.query(query); - } - sql(strings, ...values) { - const query = strings.reduce((acc, str, i) => { - acc.push(str); - if (i < values.length) { - acc.push(values[i]); - } - return acc; - }, []).join(''); - return this.query(query); + async query(query) { + return await this.queueManager.waitForTurn(async () => { + // console.log('init query', query); + const result = await this.pglite.query(query); + // console.log('init result', { + // query, + // result, + // }); + return result; + }); } + // sql(strings, ...values) { + // const query = strings.reduce((acc, str, i) => { + // acc.push(str); + // if (i < values.length) { + // acc.push(values[i]); + // } + // return acc; + // }, []).join(''); + // return this.query(query); + // } from(...args) { return this.postgrestClient.from(...args); } From d9fdb0669eb779e9f9946198365acfb3e1ec1ea1 Mon Sep 17 00:00:00 2001 From: Avaer Kazmer Date: Thu, 26 Dec 2024 19:18:51 -0800 Subject: [PATCH 5/8] Better insert handling in pglite storage --- .../packages/react-agents/storage/pglite-storage.mjs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/usdk/packages/upstreet-agent/packages/react-agents/storage/pglite-storage.mjs b/packages/usdk/packages/upstreet-agent/packages/react-agents/storage/pglite-storage.mjs index ea66252bb..8c867d5ae 100644 --- a/packages/usdk/packages/upstreet-agent/packages/react-agents/storage/pglite-storage.mjs +++ b/packages/usdk/packages/upstreet-agent/packages/react-agents/storage/pglite-storage.mjs @@ -170,8 +170,14 @@ export class PGliteStorage { // Handle INSERT const body = JSON.parse(init.body); const columns = Object.keys(body).join(', '); + console.log('got body', body); const values = Object.values(body) - .map(v => typeof v === 'string' ? `'${v}'` : v) + .map(v => { + if (v === null) return 'NULL'; + if (typeof v === 'string') return `'${v.replace(/'/g, "''")}'`; + if (typeof v === 'object') return `'${JSON.stringify(v).replace(/'/g, "''")}'`; + return v; + }) .join(', '); query = `INSERT INTO ${tableName} (${columns}) VALUES (${values})`; } else if (method === 'PATCH') { From 1dbdb618c2e67243dac65bf4065575cb75f83a96 Mon Sep 17 00:00:00 2001 From: Avaer Kazmer Date: Thu, 26 Dec 2024 19:24:06 -0800 Subject: [PATCH 6/8] Handle on_conflict in pglite-storage --- .../react-agents/storage/pglite-storage.mjs | 43 ++++++++++++++----- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/packages/usdk/packages/upstreet-agent/packages/react-agents/storage/pglite-storage.mjs b/packages/usdk/packages/upstreet-agent/packages/react-agents/storage/pglite-storage.mjs index 8c867d5ae..833c6f662 100644 --- a/packages/usdk/packages/upstreet-agent/packages/react-agents/storage/pglite-storage.mjs +++ b/packages/usdk/packages/upstreet-agent/packages/react-agents/storage/pglite-storage.mjs @@ -61,6 +61,14 @@ const initQueries = [ constraint stripe_connect_payments_pkey primary key (id) ); `, + dedent`\ + create table + pings ( + user_id uuid not null default gen_random_uuid (), + timestamp timestamp with time zone not null default now(), + constraint pings_pkey primary key (user_id) + ); + `, ]; export class PGliteStorage { @@ -108,7 +116,7 @@ export class PGliteStorage { const parseConditions = (params) => { const conditions = []; for (const [key, value] of params) { - if (key === 'select' || key === 'order' || key === 'limit') { + if (key === 'select' || key === 'order' || key === 'limit' || key === 'on_conflict') { continue; } const [operator, filterValue] = value.split('.'); @@ -138,6 +146,13 @@ export class PGliteStorage { return conditions.join(' AND '); }; + const stringifyValue = (value) => { + if (value === null) return 'NULL'; + if (typeof value === 'string') return `'${value.replace(/'/g, "''")}'`; + if (typeof value === 'object') return `'${JSON.stringify(value).replace(/'/g, "''")}'`; + return value; + }; + // Convert Postgrest request to SQL query let query; if (method === 'GET') { @@ -170,21 +185,26 @@ export class PGliteStorage { // Handle INSERT const body = JSON.parse(init.body); const columns = Object.keys(body).join(', '); - console.log('got body', body); const values = Object.values(body) - .map(v => { - if (v === null) return 'NULL'; - if (typeof v === 'string') return `'${v.replace(/'/g, "''")}'`; - if (typeof v === 'object') return `'${JSON.stringify(v).replace(/'/g, "''")}'`; - return v; - }) + .map(stringifyValue) .join(', '); query = `INSERT INTO ${tableName} (${columns}) VALUES (${values})`; + + // Handle ON CONFLICT + const onConflict = searchParams.get('on_conflict'); + if (onConflict) { + query += ` ON CONFLICT (${onConflict}) DO UPDATE SET `; + const updates = Object.entries(body) + .filter(([key]) => key !== onConflict) // Exclude the conflict column + .map(([key, value]) => `${key} = ${stringifyValue(value)}`) + .join(', '); + query += updates; + } } else if (method === 'PATCH') { // Handle UPDATE const body = JSON.parse(init.body); const updates = Object.entries(body) - .map(([key, value]) => `${key} = '${value}'`) + .map(([key, value]) => `${key} = ${stringifyValue(value)}`) .join(', '); query = `UPDATE ${tableName} SET ${updates}`; @@ -203,7 +223,10 @@ export class PGliteStorage { } try { - console.log('execute query', query); + // console.log('execute query', query, { + // searchParams: Object.fromEntries(searchParams), + // init, + // }); const result = await pglite.query(query); return new Response(JSON.stringify(result?.rows ?? null), { status: 200, From 6ec686176bd35fe99f7df8bd302a270a1e16c0d8 Mon Sep 17 00:00:00 2001 From: Avaer Kazmer Date: Thu, 26 Dec 2024 19:24:37 -0800 Subject: [PATCH 7/8] Pass in storage adapter to agent renderer --- .../packages/react-agents/classes/agent-renderer.tsx | 8 +++++--- .../packages/upstreet-agent/packages/react-agents/root.ts | 1 + 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/usdk/packages/upstreet-agent/packages/react-agents/classes/agent-renderer.tsx b/packages/usdk/packages/upstreet-agent/packages/react-agents/classes/agent-renderer.tsx index 2816b51de..85ce3e5cf 100644 --- a/packages/usdk/packages/upstreet-agent/packages/react-agents/classes/agent-renderer.tsx +++ b/packages/usdk/packages/upstreet-agent/packages/react-agents/classes/agent-renderer.tsx @@ -95,6 +95,7 @@ export class AgentRenderer { env: any; config: any; chatsSpecification: ChatsSpecification; + supabase: any; codecs: any; registry: RenderRegistry; @@ -114,17 +115,20 @@ export class AgentRenderer { env, config, chatsSpecification, + supabase, codecs, }: { env: any; config: any; chatsSpecification: ChatsSpecification; + supabase: any; codecs: any; }) { // latch arguments this.env = env; this.config = config; this.chatsSpecification = chatsSpecification; + this.supabase = supabase; this.codecs = codecs; // create the app context @@ -151,9 +155,7 @@ export class AgentRenderer { return this.env.AGENT_TOKEN; }; const useSupabase = () => { - const jwt = useAuthToken(); - const supabase = new SupabaseStorage({ jwt }); - return supabase; + return this.supabase; }; const useConversationManager = () => { return this.conversationManager; diff --git a/packages/usdk/packages/upstreet-agent/packages/react-agents/root.ts b/packages/usdk/packages/upstreet-agent/packages/react-agents/root.ts index ac1eb82e3..df43bf94c 100644 --- a/packages/usdk/packages/upstreet-agent/packages/react-agents/root.ts +++ b/packages/usdk/packages/upstreet-agent/packages/react-agents/root.ts @@ -66,6 +66,7 @@ export class Root extends EventTarget { env, config: agentJson, codecs, + supabase: storageAdapter, chatsSpecification: this.chatsSpecification, }); // const bindAlarm = () => { From 81dadb33be8b5ed3f2ed599975016cb094c78b4c Mon Sep 17 00:00:00 2001 From: Avaer Kazmer Date: Thu, 26 Dec 2024 19:24:50 -0800 Subject: [PATCH 8/8] Remove realtime webhooks updates --- .../packages/react-agents/hooks.ts | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/packages/usdk/packages/upstreet-agent/packages/react-agents/hooks.ts b/packages/usdk/packages/upstreet-agent/packages/react-agents/hooks.ts index 7eadc2243..ab477a21b 100644 --- a/packages/usdk/packages/upstreet-agent/packages/react-agents/hooks.ts +++ b/packages/usdk/packages/upstreet-agent/packages/react-agents/hooks.ts @@ -27,9 +27,9 @@ import { ConversationContext, } from './context'; import { ExtendableMessageEvent } from './util/extendable-message-event'; -import { - supabaseSubscribe, -} from './util/supabase-utils.mjs'; +// import { +// supabaseSubscribe, +// } from './util/supabase-utils.mjs'; import { QueueManager, } from 'queue-manager'; @@ -404,23 +404,23 @@ export const usePurchases = () => { live = false; }; }, []); - // subscribe to webhooks - useEffect(() => { - const channel = supabaseSubscribe({ - supabase, - table: 'webhooks', - userId: ownerId, - }, (payload: any) => { - // console.log('subscription payload', payload); - const webhook = payload.new; - queueManager.waitForTurn(async () => { - await handleWebhook(webhook); - }); - }); - return () => { - supabase.removeChannel(channel); - }; - }, []); + // // subscribe to webhooks + // useEffect(() => { + // const channel = supabaseSubscribe({ + // supabase, + // table: 'webhooks', + // userId: ownerId, + // }, (payload: any) => { + // // console.log('subscription payload', payload); + // const webhook = payload.new; + // queueManager.waitForTurn(async () => { + // await handleWebhook(webhook); + // }); + // }); + // return () => { + // supabase.removeChannel(channel); + // }; + // }, []); const purchases = agentWebhooksState.webhooks.map((webhook) => { const {