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
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
.Define the Handler Function: Export a default asynchronous function to handle incoming requests. This function receives
req
(HTTP request) andres
(HTTP response) objects.export default async function handler(req, res) { res.status(200).json({ message: 'Hello, Next.js!' }); }
Handling Different HTTP Methods: You can manage multiple HTTP methods by checking
req.method
and implementing logic forGET
,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`); } }
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