From 626d4ba7b2d0e742f3479747253cc8ec2ae462b6 Mon Sep 17 00:00:00 2001
From: Till <till.friesewinkel@googlemail.com>
Date: Fri, 14 Mar 2025 15:29:03 +0000
Subject: [PATCH 1/2] Move to SQL database

---
 .prettierrc             |  3 ++
 app/api/agents/route.ts | 18 ++++-----
 lib/firestore.ts        | 85 ++++++++++++++++++++++++-----------------
 package.json            |  1 +
 pnpm-lock.yaml          | 33 +++++++++++++++-
 5 files changed, 92 insertions(+), 48 deletions(-)
 create mode 100644 .prettierrc

diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..1ca87ab
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,3 @@
+{
+  "singleQuote": false
+}
diff --git a/app/api/agents/route.ts b/app/api/agents/route.ts
index e1a6acb..ec879be 100644
--- a/app/api/agents/route.ts
+++ b/app/api/agents/route.ts
@@ -1,8 +1,7 @@
-import { Agent } from "@/lib/types";
 import { NextRequest, NextResponse } from "next/server";
-import { write, queryAgents } from "@/lib/firestore";
-import { COLLECTIONS } from "@/lib/constants";
+import { queryAgents } from "@/lib/firestore";
 import { kv } from "@vercel/kv";
+import { listAiAgents, createAiAgent, ai_agent } from "@bitte-ai/data";
 
 export async function GET(request: NextRequest) {
   try {
@@ -13,13 +12,16 @@ export async function GET(request: NextRequest) {
     const verifiedOnly = searchParams.get("verifiedOnly") !== "false";
     const category = searchParams.get("category") || undefined;
 
-    const agents = await queryAgents<Agent>({
+    // TODO: remove firestore after migration
+    const firestoreAgents = await queryAgents({
       verified: verifiedOnly,
       chainIds,
       offset,
       limit,
       category: category === "" ? undefined : category,
     });
+    const sqlAgents = await listAiAgents();
+    const agents = [...firestoreAgents, ...sqlAgents];
 
     const agentIds = agents.map((agent) => agent.id);
     const pingsByAgent = await getTotalPingsByAgentIds(agentIds);
@@ -47,7 +49,7 @@ export async function POST(request: NextRequest) {
   try {
     const body = await request.json();
 
-    const newAgent: Agent = {
+    const newAgent: ai_agent = {
       ...body,
       verified: false,
       id: crypto.randomUUID(),
@@ -73,11 +75,7 @@ export async function POST(request: NextRequest) {
       );
     }
 
-    const result = await write(COLLECTIONS.AGENTS, newAgent.id, newAgent);
-
-    if (!result.success) {
-      throw result.error;
-    }
+    await createAiAgent(newAgent);
 
     return NextResponse.json(newAgent, { status: 201 });
   } catch (error) {
diff --git a/lib/firestore.ts b/lib/firestore.ts
index a90b337..e0a9316 100644
--- a/lib/firestore.ts
+++ b/lib/firestore.ts
@@ -4,7 +4,7 @@ import {
   WithFieldValue,
 } from "@google-cloud/firestore";
 import { COLLECTIONS } from "./constants";
-import { Tool } from "./types";
+import { Agent, Tool } from "./types";
 
 export type FirestoreOperationResult = {
   success: boolean;
@@ -135,26 +135,32 @@ export const catchDocumentNotFound = (err: Error): null => {
   throw err;
 };
 
-export const queryAgents = async <T>(options: {
-  verified?: boolean;
-  withTools?: boolean;
-  chainIds?: string[];
-  offset?: number;
-  limit?: number;
-  category?: string | null;
-} = {}): Promise<T[]> => {
+export const queryAgents = async (
+  options: {
+    verified?: boolean;
+    withTools?: boolean;
+    chainIds?: string[];
+    offset?: number;
+    limit?: number;
+    category?: string | null;
+  } = {}
+): Promise<Agent[]> => {
   let query: FirebaseFirestore.Query = db.collection(COLLECTIONS.AGENTS);
-  
+
   if (options.verified) {
-    query = query.where('verified', '==', true);
+    query = query.where("verified", "==", true);
   }
 
   if (options.chainIds?.length) {
-    query = query.where('chainIds', 'array-contains-any', options.chainIds.map(id => parseInt(id)));
+    query = query.where(
+      "chainIds",
+      "array-contains-any",
+      options.chainIds.map((id) => parseInt(id))
+    );
   }
 
   if (options.category) {
-    query = query.where('category', '==', options.category);
+    query = query.where("category", "==", options.category);
   }
 
   if (options.limit) {
@@ -166,33 +172,36 @@ export const queryAgents = async <T>(options: {
   }
 
   const snapshot = await query.get();
-  const agents = snapshot.docs.map(doc => doc.data());
-  
+  const agents = snapshot.docs.map((doc) => doc.data());
+
   if (!options.withTools) {
-    return agents.map(agent => {
+    return agents.map((agent) => {
       const { ...rest } = agent;
-      return rest as T;
+      return rest as Agent;
     });
   }
-  
-  return agents as T[];
+
+  return agents as Agent[];
 };
 
-export const queryTools = async <T>(options: {
-  verified?: boolean;
-  functionName?: string;
-  offset?: number;
-  chainId?: string;
-} = {}): Promise<T[]> => {
-  let query: FirebaseFirestore.Query = db.collection(COLLECTIONS.AGENTS)
-    .select('tools', 'image', 'chainIds');
-  
+export const queryTools = async <T>(
+  options: {
+    verified?: boolean;
+    functionName?: string;
+    offset?: number;
+    chainId?: string;
+  } = {}
+): Promise<T[]> => {
+  let query: FirebaseFirestore.Query = db
+    .collection(COLLECTIONS.AGENTS)
+    .select("tools", "image", "chainIds");
+
   if (options.verified) {
-    query = query.where('verified', '==', true);
+    query = query.where("verified", "==", true);
   }
 
   if (options.chainId) {
-    query = query.where('chainIds', 'array-contains', options.chainId);
+    query = query.where("chainIds", "array-contains", options.chainId);
   }
 
   const limit = 100;
@@ -200,31 +209,35 @@ export const queryTools = async <T>(options: {
 
   const snapshot = await query.get();
   const tools: Tool[] = [];
-  
+
   for (const doc of snapshot.docs) {
     const agent = doc.data();
     if (!agent.tools?.length) continue;
 
     const baseToolData = {
       image: agent.image,
-      chainIds: agent.chainIds || []
+      chainIds: agent.chainIds || [],
     };
 
     for (const tool of agent.tools) {
-      if (options.functionName && 
-          !tool.function.name.toLowerCase().includes(options.functionName.toLowerCase())) {
+      if (
+        options.functionName &&
+        !tool.function.name
+          .toLowerCase()
+          .includes(options.functionName.toLowerCase())
+      ) {
         continue;
       }
 
       tools.push({
         ...tool,
-        ...baseToolData
+        ...baseToolData,
       });
     }
   }
 
   const uniqueTools = Array.from(
-    new Map(tools.map(tool => [tool.function.name, tool])).values()
+    new Map(tools.map((tool) => [tool.function.name, tool])).values()
   );
 
   if (options.offset) {
diff --git a/package.json b/package.json
index 87f2ce5..58d6da5 100644
--- a/package.json
+++ b/package.json
@@ -12,6 +12,7 @@
   },
   "dependencies": {
     "@ai-sdk/openai": "^1.1.11",
+    "@bitte-ai/data": "0.1.0-fix-cicd2-460c6c4",
     "@google-cloud/firestore": "^7.11.0",
     "@jest/globals": "^29.7.0",
     "@radix-ui/react-dialog": "^1.1.6",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 57bc750..650dc91 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -11,6 +11,9 @@ importers:
       '@ai-sdk/openai':
         specifier: ^1.1.11
         version: 1.1.11(zod@3.24.2)
+      '@bitte-ai/data':
+        specifier: 0.1.0-fix-cicd2-460c6c4
+        version: 0.1.0-fix-cicd2-460c6c4(typescript@5.7.3)
       '@google-cloud/firestore':
         specifier: ^7.11.0
         version: 7.11.0
@@ -342,6 +345,9 @@ packages:
   '@bcoe/v8-coverage@0.2.3':
     resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
 
+  '@bitte-ai/data@0.1.0-fix-cicd2-460c6c4':
+    resolution: {integrity: sha512-b3hmiSbimQMHaDD2ZmahVX6zTBxs2rsRnMEJDTuPA/I6Docu706xHfSAycLy5jkXz+Z2ukdjV7p8Ap/FnLLvvQ==}
+
   '@emnapi/runtime@1.3.1':
     resolution: {integrity: sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==}
 
@@ -698,6 +704,18 @@ packages:
     resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
     engines: {node: '>=14'}
 
+  '@prisma/client@6.4.1':
+    resolution: {integrity: sha512-A7Mwx44+GVZVexT5e2GF/WcKkEkNNKbgr059xpr5mn+oUm2ZW1svhe+0TRNBwCdzhfIZ+q23jEgsNPvKD9u+6g==}
+    engines: {node: '>=18.18'}
+    peerDependencies:
+      prisma: '*'
+      typescript: '>=5.1.0'
+    peerDependenciesMeta:
+      prisma:
+        optional: true
+      typescript:
+        optional: true
+
   '@protobufjs/aspromise@1.1.2':
     resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==}
 
@@ -3778,6 +3796,13 @@ snapshots:
 
   '@bcoe/v8-coverage@0.2.3': {}
 
+  '@bitte-ai/data@0.1.0-fix-cicd2-460c6c4(typescript@5.7.3)':
+    dependencies:
+      '@prisma/client': 6.4.1(typescript@5.7.3)
+    transitivePeerDependencies:
+      - prisma
+      - typescript
+
   '@emnapi/runtime@1.3.1':
     dependencies:
       tslib: 2.8.1
@@ -4189,6 +4214,10 @@ snapshots:
   '@pkgjs/parseargs@0.11.0':
     optional: true
 
+  '@prisma/client@6.4.1(typescript@5.7.3)':
+    optionalDependencies:
+      typescript: 5.7.3
+
   '@protobufjs/aspromise@1.1.2': {}
 
   '@protobufjs/base64@1.1.2': {}
@@ -5383,7 +5412,7 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  eslint-module-utils@2.12.0(@typescript-eslint/parser@8.24.0(eslint@9.20.0(jiti@1.21.7))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0)(eslint@9.20.0(jiti@1.21.7)):
+  eslint-module-utils@2.12.0(@typescript-eslint/parser@8.24.0(eslint@9.20.0(jiti@1.21.7))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0)(eslint@9.20.0(jiti@1.21.7)))(eslint@9.20.0(jiti@1.21.7)):
     dependencies:
       debug: 3.2.7
     optionalDependencies:
@@ -5405,7 +5434,7 @@ snapshots:
       doctrine: 2.1.0
       eslint: 9.20.0(jiti@1.21.7)
       eslint-import-resolver-node: 0.3.9
-      eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.24.0(eslint@9.20.0(jiti@1.21.7))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0)(eslint@9.20.0(jiti@1.21.7))
+      eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.24.0(eslint@9.20.0(jiti@1.21.7))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0)(eslint@9.20.0(jiti@1.21.7)))(eslint@9.20.0(jiti@1.21.7))
       hasown: 2.0.2
       is-core-module: 2.16.1
       is-glob: 4.0.3

From 8eb2d02e5d8a18e618a6e7c021be89bae45e2800 Mon Sep 17 00:00:00 2001
From: Till <till.friesewinkel@googlemail.com>
Date: Fri, 14 Mar 2025 15:30:42 +0000
Subject: [PATCH 2/2] Important FIXME note, do not merge until resolved

---
 app/api/agents/route.ts | 1 +
 1 file changed, 1 insertion(+)

diff --git a/app/api/agents/route.ts b/app/api/agents/route.ts
index ec879be..a66034a 100644
--- a/app/api/agents/route.ts
+++ b/app/api/agents/route.ts
@@ -20,6 +20,7 @@ export async function GET(request: NextRequest) {
       limit,
       category: category === "" ? undefined : category,
     });
+    // FIXME: wait for version that is camel case
     const sqlAgents = await listAiAgents();
     const agents = [...firestoreAgents, ...sqlAgents];