Backend - Building an API Router to handle agent actions

Building an API Router in Next.js

Next.js provides robust capabilities for creating API routes, making it a versatile framework for both frontend and backend development. With API routes, you can handle requests in a serverless function that can perform CRUD operations, fetch data, or integrate with third-party services.

Steps to Create an API Route in Next.js

  1. Create a File: Inside the /pages/api/ directory, create a new file. The file name represents the endpoint path. For instance, pages/api/hello.js would map to /api/hello.

  2. Define the Handler Function: Export a default asynchronous function to handle incoming requests. This function receives req (HTTP request) and res (HTTP response) objects.

    export default async function handler(req, res) {
        res.status(200).json({ message: 'Hello, Next.js!' });
    }
  3. Handling Different HTTP Methods: You can manage multiple HTTP methods by checking req.method and implementing logic for GET, POST, PUT, DELETE`, etc.

    export default async function handler(req, res) {
        switch (req.method) {
            case 'GET':
                // Code for handling GET request
                res.status(200).json({ message: 'GET method success' });
                break;
            case 'POST':
                // Code for handling POST request
                res.status(201).json({ message: 'POST method success' });
                break;
            // Handle other methods as needed
            default:
                res.setHeader('Allow', ['GET', 'POST']);
                res.status(405).end(`Method ${req.method} Not Allowed`);
        }
    }
  4. Testing Your API Route: Utilize tools like Postman or your browser to test the API endpoint. For example, visit http://localhost:3000/api/hello from your browser to verify the output.

Benefits of Next.js API Routes

  • Serverless Functions: No need to manage servers; your functions run in isolated environments.

  • Scalability: Next.js optimizes endpoint handling and can scale with demand.

  • Full-Stack Development: Easily integrate frontend and backend logic within the same project.

Next.js' API routes simplify integrating backend logic into your web applications, making development more efficient and organized. Implement the logic based on the example below.

import { Actions, _createSolanaTools, OobeCore, messageModifier, IOfficialEndpoint, ISolanaEndpoint, IUnofficialEndpoints, createSolanaTools, IConfiguration } from "oobe-protocol";
import { ConfigManager } from "oobe-protocol";
import type { NextApiRequest, NextApiResponse } from "next";
import { MemorySaver } from "@langchain/langgraph-checkpoint";
import { createReactAgent } from "@langchain/langgraph/prebuilt";
import bs58 from "bs58";
import { ResponseMessage } from "oobe-protocol/packages/core/dist/types/agent.interface";
import SolanaHelper from "src/helper/SolanaHelper";

export async function getOobeInstance(privateKey: string, openAiKey: string, oobeKey: string,) {
    const configManager = new ConfigManager();
    configManager.createDefaultConfig(
        privateKey,
        openAiKey,
        oobeKey,
        "https://api.mainnet-beta.solana.com" as unknown as IOfficialEndpoint,
        ["https://api.mainnet-beta.solana.com"] as unknown as IUnofficialEndpoints[],
        "https://explorer.solana.com",
    );

    const oobeInstance = new OobeCore({
        solanaEndpoint: "https://api.mainnet-beta.solana.com" as unknown as IOfficialEndpoint,
        solanaUnofficialEndpoints: ["https://api.mainnet-beta.solana.com"] as unknown as IUnofficialEndpoints[],
        solanaExplorer: "https://explorer.solana.com",
        private_key: privateKey,
        openAiKey: openAiKey,
        oobeKey: oobeKey,
    } as IConfiguration);

    oobeInstance.start();

    const agent = oobeInstance.getAgent();

    await agent.registerActions(Actions.map((action) => action.action));

    const tools = await createSolanaTools(agent);

    const memory = oobeInstance.AccessMemory() ?? new MemorySaver();

    const ag = createReactAgent({ llm: agent.genAi(), tools: tools as any, checkpointSaver: memory,
        messageModifier: `
              You are a person with this personality "${JSON.stringify( await agent.getDefaultPersonality())}" that can interact onchain using the OOBE AGENT BUILDER. You can switch and create personality from S-A-CREATE_PERSONALITY_TOOL. You are
              empowered to interact onchain using your tools. If you ever need funds, you can request them from the
              faucet. If not, you can provide your wallet details and request funds from the user. If there is a 5XX
              (internal) HTTP error code, ask the user to try again later. If someone asks you to do something you
              can't do with your currently available tools, you must say so, and encourage them to implement it
              themselves using the OOBE AGENT BUILDER, recommend they go to https://oobe.me/ for more information. Be
              concise and helpful with your responses. Refrain from restating your tools' descriptions unless it is explicitly requested.
            `, });

    return { agent, oobeAgentInstance: ag };
}

async function sleep(ms: number) {
    return new Promise((resolve) => setTimeout(resolve, ms));
}

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
    if (req.method !== "POST") {
        return res.status(405).json({ error: "Method Not Allowed" });
    }

    try {
        const { messages, pvtKey, openKey, oobeKey, personality } = req.body;

        console.log("Received messages:", messages);

        if (!pvtKey) {
            return res.status(400).json({ error: "Missing required parameter: privateKey." });
        }

        if (!openKey) {
            return res.status(400).json({ error: "Missing required parameter: openAiKey." });
        }

        if (!oobeKey) {
            return res.status(400).json({ error: "Missing required parameter: oobeKey." });
        }

        if (!personality) {
            return res.status(400).json({ error: "Missing required parameter: personality." });
        }

        const decryptedPvtKey = new SolanaHelper().decrypt_(pvtKey);

        const _pvtToBase58 = (pvtKey: string) => {
            const base58 = bs58.encode(Buffer.from(pvtKey, "hex"));
            return base58;
        };

        const { agent, oobeAgentInstance } = await getOobeInstance(_pvtToBase58(decryptedPvtKey), openKey, oobeKey);


        const eventStream = oobeAgentInstance.streamEvents(
            { messages },
            {
                version: "v2",
                configurable: {
                    thread_id: "OOBE AGENT BUILDER!",
                },
            }
        );

        res.setHeader("Content-Type", "text/event-stream");
        res.setHeader("Cache-Control", "no-cache");
        res.setHeader("Connection", "keep-alive");

        const textEncoder = new TextEncoder();
        let toolsRes: ResponseMessage[] = [];
        let agentRes: string = "";

        for await (const { event, data } of eventStream) {
            if (event === "on_chat_model_stream" && data.chunk.content) {
                const encodedData = textEncoder.encode(data.chunk.content);
                agentRes = data.chunk.content;
                res.write(encodedData);
            }

            if (event === "on_chain_end" && data.output.messages) {
                res.setHeader("X-OOBE-Event", event);
                const base64Data = Buffer.from(JSON.stringify(data)).toString('base64');
                res.setHeader("X-OOBE-Data", base64Data);
               
            }

            if (toolsRes.length > 0 && agentRes) {
                if (toolsRes.find((x) => x.name === "get_all_kamino_strategies")) {
                  continue;
                } else {
                  const data_merkle = agent.merkleValidate(toolsRes, agentRes as unknown as Record<string, any>);
                  setImmediate(async () => {
                    try {
                      agent.merkle.onChainMerkleInscription(data_merkle)
                      await sleep(2000)
                    } catch (err) {
                      await agent.merkle.onChainMerkleInscription(data_merkle);
                    }
                  });
                }
              }
        }

        res.end();
    } catch (e: any) {
        console.error(e);
        return res.status(e.status ?? 500).json({ error: e.message });
    }
}

Last updated