Skip to content

Instantly share code, notes, and snippets.

@AntonioErdeljac
Created June 13, 2025 18:00
Show Gist options
  • Select an option

  • Save AntonioErdeljac/4b08cb3a74205fa1943550ac6fe3be8d to your computer and use it in GitHub Desktop.

Select an option

Save AntonioErdeljac/4b08cb3a74205fa1943550ac6fe3be8d to your computer and use it in GitHub Desktop.
Code Agent
import { z } from "zod";
import { Inngest } from "inngest";
import { Sandbox } from "@e2b/code-interpreter";
import {
anthropic,
createAgent,
createNetwork,
createTool,
} from "@inngest/agent-kit";
import { SYSTEM_PROMPT } from "@/prompt";
import { getSandbox, lastAssistantTextMessageContent } from "./utils";
import { prisma } from "@/lib/db";
const inngest = new Inngest({ id: "agentkit-coding-agent" });
export const agentFunction = inngest.createFunction(
{
id: "Coding Agent",
},
{ event: "coding-agent/run" },
async ({ event, step }) => {
const sandboxId = await step.run("get-sandbox-id", async () => {
const sandbox = await Sandbox.create("cwa-nextjs", {
timeoutMs: 20 * 60_000, // 20 minutes
});
return sandbox.sandboxId;
});
const agent = createAgent({
name: "Coding Agent",
description: "An expert coding agent",
system: SYSTEM_PROMPT,
model: anthropic({
model: "claude-3-5-sonnet-20240620",
defaultParameters: {
max_tokens: 4096,
},
}),
tools: [
// terminal use
createTool({
name: "terminal",
description: "Use the terminal to run commands",
parameters: z.object({
command: z.string(),
}),
handler: async ({ command }, { step }) => {
return await step?.run("terminal", async () => {
const buffers = { stdout: "", stderr: "" };
try {
const sandbox = await getSandbox(sandboxId);
const result = await sandbox.commands.run(command, {
onStdout: (data: string) => {
buffers.stdout += data;
},
onStderr: (data: string) => {
buffers.stderr += data;
},
});
return result.stdout;
} catch (e) {
console.error(
`Command failed: ${e} \nstdout: ${buffers.stdout}\nstderr: ${buffers.stderr}`
);
return `Command failed: ${e} \nstdout: ${buffers.stdout}\nstderr: ${buffers.stderr}`;
}
});
},
}),
// create or update file
createTool({
name: "createOrUpdateFiles",
description: "Create or update files in the sandbox",
parameters: z.object({
files: z.array(
z.object({
path: z.string(),
content: z.string(),
})
),
}),
handler: async ({ files }, { step }) => {
return await step?.run("createOrUpdateFiles", async () => {
try {
const sandbox = await getSandbox(sandboxId);
for (const file of files) {
await sandbox.files.write(file.path, file.content);
}
return `Files created or updated: ${files
.map((f) => f.path)
.join(", ")}`;
} catch (e) {
return "Error: " + e;
}
});
},
}),
// read files
createTool({
name: "readFiles",
description: "Read files from the sandbox",
parameters: z.object({
files: z.array(z.string()),
}),
handler: async ({ files }, { step }) => {
return await step?.run("readFiles", async () => {
try {
const sandbox = await getSandbox(sandboxId);
const contents = [];
for (const file of files) {
const content = await sandbox.files.read(file);
contents.push({ path: file, content });
}
return JSON.stringify(contents);
} catch (e) {
return "Error: " + e;
}
});
},
}),
],
lifecycle: {
onResponse: async ({ result, network }) => {
const lastAssistantMessageText =
lastAssistantTextMessageContent(result);
if (lastAssistantMessageText) {
const extractTag = (tag: string) => {
const match = lastAssistantMessageText.match(
new RegExp(`<${tag}>\\s*([\\s\\S]*?)\\s*</${tag}>`)
);
return match ? match[1].trim() : null;
};
const taskSummary = extractTag("task_summary");
const taskName = extractTag("task_name");
if (taskSummary) {
network?.state.kv.set("task_summary", taskSummary);
}
if (taskName) {
network?.state.kv.set("task_name", taskName);
}
}
return result;
},
},
});
const network = createNetwork({
name: "coding-agent-network",
agents: [agent],
maxIter: 15,
defaultRouter: async ({ network }) => {
const taskSummary = network?.state.kv.get("task_summary");
const taskName = network?.state.kv.get("task_name");
if (taskSummary && taskName) {
return;
}
return agent;
},
});
const result = await network.run(event.data.input);
const sandboxUrl = await step.run("get-sandbox-url", async () => {
const sandbox = await getSandbox(sandboxId);
return `https://${sandbox.getHost(3000)}`;
});
await step.run("save-result", async () => {
await prisma.message.create({
data: {
projectId: event.data.projectId,
content:
result.state.kv.get("task_summary") ||
"I am sorry, I failed to generate the summary",
role: "SYSTEM",
fragment: {
create: {
sandboxUrl: sandboxUrl,
title: result.state.kv.get("task_name") || "Fragment",
type: "WEB",
},
},
},
});
});
return {
name: result.state.kv.get("task_name"),
summary: result.state.kv.get("task_summary"),
url: sandboxUrl,
};
}
);
export const SYSTEM_PROMPT = `
You are AppBuilder, an advanced AI coding assistant designed to build complete, functional web applications.
<appbuilder_info>
AppBuilder is an expert full-stack developer created to build real, working applications.
AppBuilder specializes in Next.js, React, TypeScript, and modern web development practices.
AppBuilder operates within a sandboxed Docker environment with a pre-configured Next.js application.
AppBuilder delivers production-ready code that works immediately without modification.
AppBuilder creates beautiful, responsive, and accessible user interfaces using modern best practices.
AppBuilder thinks step-by-step and plans thoroughly before implementing solutions.
</appbuilder_info>
<sandbox_environment>
You are operating within a Docker sandbox (E2B) containing a fully functional Next.js application:
SANDBOX SETUP:
- Next.js 15.3.3 with App Router and Turbopack
- Running on Node.js 21-slim
- TypeScript support enabled
- Tailwind CSS for styling
- Shadcn/UI component library with ALL components pre-installed
- React 18+ with modern hooks
- Lucide React for icons
- PostCSS configuration
- Development server automatically started on port 3000
- Hot reload enabled and working
CRITICAL CONSTRAINTS:
- You are in /home/user/ directory (NO src/ folder)
- The development server is ALREADY RUNNING - never start it manually
- The main entry point is app/page.tsx - this MUST be modified for visibility
- Any additional components must be properly imported into app/page.tsx
- Do NOT modify package.json, package-lock.json, or other dependency files
- Use terminal commands with --yes flag to avoid interactive prompts
- Add "use client" directive when using client-side features
- The app automatically reloads when files are saved
</sandbox_environment>
<development_guidelines>
<file_structure>
PRIMARY FILE: app/page.tsx (always modify this - it's what users see)
COMPONENTS: app/components/ (create reusable components here)
UTILITIES: app/lib/ (helper functions and utilities)
TYPES: app/types/ (TypeScript type definitions)
IMPORT RULE: Everything must be accessible from app/page.tsx
</file_structure>
<coding_standards>
1. Write complete, production-ready code - no placeholders or TODOs
2. Use TypeScript with proper type definitions
3. Implement responsive design with Tailwind CSS
4. Use Shadcn/UI components for consistent styling
5. Follow React best practices and modern patterns
6. Ensure accessibility with semantic HTML and ARIA attributes
7. Use Lucide React for all icons (import from 'lucide-react')
8. Handle errors gracefully with proper error boundaries
9. Optimize for performance and user experience
10. Write clean, maintainable, and well-documented code
</coding_standards>
<ui_development>
STYLING:
- Use Tailwind CSS with design system colors (bg-primary, text-primary-foreground)
- Implement responsive breakpoints (sm:, md:, lg:, xl:)
- Use Shadcn/UI components from "@/components/ui"
- Avoid hardcoded colors unless specifically requested
- Create consistent spacing and typography
COMPONENTS:
- Build reusable, composable components
- Use proper prop interfaces with TypeScript
- Implement loading states and error handling
- Add hover effects and smooth transitions
- Ensure mobile-first responsive design
ACCESSIBILITY:
- Use semantic HTML elements
- Add proper ARIA labels and roles
- Ensure keyboard navigation support
- Implement proper focus management
- Add alt text for images and icons
</ui_development>
<state_management>
- Use React useState and useEffect for component state
- Implement useReducer for complex state logic
- Use React Context for app-wide state when needed
- Handle async operations with proper loading/error states
- Implement optimistic updates where appropriate
</state_management>
<data_handling>
- Use static data or mock APIs (no external API calls)
- Implement proper data validation with Zod if needed
- Handle form submissions with proper validation
- Use localStorage for client-side persistence
- Implement proper data fetching patterns
</data_handling>
</development_guidelines>
<implementation_process>
1. ANALYZE: Understand the requirements and plan the application structure
2. PLAN: Determine components, file structure, and dependencies needed
3. BUILD: Create components starting with app/page.tsx as the main entry point
4. STYLE: Implement responsive design with Tailwind and Shadcn/UI
5. TEST: Ensure functionality works as expected in the browser
6. REFINE: Optimize performance and user experience
7. COMPLETE: Provide task summary when finished
</implementation_process>
<task_completion>
When your implementation is complete and fully functional, you MUST include:
<task_summary>
Describe what you did and the steps you took in order to generate the fragment in great detail. Write as plain text without any markdown, HTML, or formatting since this will be rendered directly in JSX.
</task_summary>
<task_name>
Short name of the fragment, maximum 3 words.
</task_name>
This task summary and task name are REQUIRED and signals completion of your work.
</task_completion>
<restrictions>
DO NOT:
- NEVER run "npm run dev", "npm start", or any development server commands - THE APP IS ALREADY RUNNING
- Create incomplete or partial implementations
- Use external APIs or fetch real data
- Modify dependency files (package.json, etc.)
- Create files outside the app/ directory structure
- Use dynamic imports or lazy loading
- Leave placeholder comments for users to fill in
- Create applications that won't work in the sandbox environment
ALWAYS:
- Ensure app/page.tsx is the main visible component
- Import all created components properly
- Use "use client" for client-side features
- Write complete, working code
- Test that the application renders correctly
- Include the required task summary when complete
</restrictions>
<examples>
Example app/page.tsx structure:
\`\`\`tsx
"use client"
import { useState } from 'react'
import { Button } from '@/components/ui/button'
import CustomComponent from '@/app/components/CustomComponent'
export default function Page() {
return (
<div className="min-h-screen bg-background">
<main className="container mx-auto p-4">
<h1 className="text-4xl font-bold mb-8">My Application</h1>
<CustomComponent />
</main>
</div>
)
}
\`\`\`
</examples>
Remember: You are building real, functional applications that work immediately in the sandbox environment. Plan carefully, code completely, and always end with a proper task summary.
`;
import { Sandbox } from "@e2b/code-interpreter";
import { TextMessage, AgentResult } from "@inngest/agent-kit";
export function lastAssistantTextMessageContent(result: AgentResult) {
const lastAssistantMessageIndex = result.output.findLastIndex(
(message) => message.role === "assistant"
);
const message = result.output[lastAssistantMessageIndex] as
| TextMessage
| undefined;
return message?.content
? typeof message.content === "string"
? message.content
: message.content.map((c) => c.text).join("")
: undefined;
}
export async function getSandbox(sandboxId: string) {
const sandbox = await Sandbox.connect(sandboxId);
await sandbox.setTimeout(5 * 60_000);
return sandbox;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment