Learn how to integrate Pickaxe agents into your Next.js application for seamless AI-powered functionality.

Installation

First, install the Pickaxe SDK:

npm install @hatchet-dev/pickaxe

Basic Setup

Create your agent in a separate file (e.g., lib/agents/my-agent.ts):

import { pickaxe } from "@hatchet-dev/pickaxe";
import z from "zod";

const MyAgentInput = z.object({
  message: z.string(),
});

const MyAgentOutput = z.object({
  response: z.string(),
});

export const myAgent = pickaxe.agent({
  name: "my-nextjs-agent",
  description: "Agent for Next.js application",
  inputSchema: MyAgentInput,
  outputSchema: MyAgentOutput,
  fn: async (input, ctx) => {
    // Your agent logic here
    return {
      response: `Processed: ${input.message}`,
    };
  },
});

API Routes Integration

Create an API route to handle agent execution (pages/api/agent.ts or app/api/agent/route.ts):

Pages Router

// pages/api/agent.ts
import { NextApiRequest, NextApiResponse } from 'next';
import { myAgent } from '../../lib/agents/my-agent';

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

  try {
    const { message } = req.body;
    
    const result = await myAgent.run({
      message,
    });

    res.status(200).json(result);
  } catch (error) {
    console.error('Agent execution failed:', error);
    res.status(500).json({ error: 'Agent execution failed' });
  }
}

App Router

// app/api/agent/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { myAgent } from '@/agents/my-agent';

export async function POST(request: NextRequest) {
  try {
    const { message } = await request.json();
    
    const result = await myAgent.run({
      message,
    });

    return NextResponse.json(result);
  } catch (error) {
    console.error('Agent execution failed:', error);
    return NextResponse.json(
      { error: 'Agent execution failed' },
      { status: 500 }
    );
  }
}

Client-Side Usage

Use the agent from your React components:

'use client'; // For App Router

import { useState } from 'react';

export default function AgentDemo() {
  const [message, setMessage] = useState('');
  const [response, setResponse] = useState('');
  const [loading, setLoading] = useState(false);

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    setLoading(true);

    try {
      const res = await fetch('/api/agent', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ message }),
      });

      const data = await res.json();
      setResponse(data.response);
    } catch (error) {
      console.error('Error:', error);
    } finally {
      setLoading(false);
    }
  };

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <input
          type="text"
          value={message}
          onChange={(e) => setMessage(e.target.value)}
          placeholder="Enter your message"
          disabled={loading}
        />
        <button type="submit" disabled={loading}>
          {loading ? 'Processing...' : 'Send'}
        </button>
      </form>
      {response && (
        <div>
          <h3>Response:</h3>
          <p>{response}</p>
        </div>
      )}
    </div>
  );
}

Server Actions (App Router)

For App Router, you can also use Server Actions:

// app/actions.ts
'use server';

import { myAgent } from '../lib/agents/my-agent';

export async function runAgent(message: string) {
  try {
    const result = await myAgent.run({
      message,
    });
    return { success: true, data: result };
  } catch (error) {
    return { success: false, error: 'Agent execution failed' };
  }
}

Then use it in your component:

// app/components/AgentForm.tsx
'use client';

import { runAgent } from '../actions';
import { useState, useTransition } from 'react';

export default function AgentForm() {
  const [isPending, startTransition] = useTransition();
  const [result, setResult] = useState<string>('');

  const handleSubmit = async (formData: FormData) => {
    const message = formData.get('message') as string;
    
    startTransition(async () => {
      const response = await runAgent(message);
      if (response.success) {
        setResult(response.data.response);
      } else {
        console.error(response.error);
      }
    });
  };

  return (
    <form action={handleSubmit}>
      <input name="message" placeholder="Enter your message" required />
      <button type="submit" disabled={isPending}>
        {isPending ? 'Processing...' : 'Send'}
      </button>
      {result && <p>Response: {result}</p>}
    </form>
  );
}

Environment Configuration

Add your Hatchet configuration to .env.local:

HATCHET_CLIENT_TOKEN=your_token_here
HATCHET_SERVER_URL=https://app.hatchet.run

Initialize Hatchet in your application:

// lib/hatchet.ts
import { Hatchet } from "@hatchet-dev/typescript-sdk";
import { myAgent } from "./agents/my-agent";

const hatchet = Hatchet.init({
  token: process.env.HATCHET_CLIENT_TOKEN!,
  serverUrl: process.env.HATCHET_SERVER_URL,
});

hatchet.registerWorkflows([myAgent]);

export { hatchet };

Deployment

When deploying to Vercel or other platforms, ensure your environment variables are configured and your agent workflows are properly registered.

For serverless deployments, consider using runNoWait() for long-running agents and add a get endpoint to subscribe to the result to avoid timeout issues.