The OpenAI Agents SDK for JavaScript provides a framework for building AI in TypeScript and JavaScript. Arcade’s @arcadeai/arcadejs library converts Arcade to the format OpenAI Agents expects.
Outcomes
Build an that uses Arcade to help with Gmail and Slack
You will Learn
How to retrieve Arcade tools and convert them to OpenAI format
How to use the factory pattern for
How to handle authorization with executeOrAuthorizeZodTool
The OpenAI Agents JS SDK uses Zod schemas for tool definitions. Arcade’s toZod() function converts Arcade tool definitions to Zod schemas, and the executeFactory parameter determines how tools execute.
TypeScript uses a factory pattern to bind context to tool functions. executeFactory receives tool context (client, userId, tool definition) and returns an execute function with that context “baked in”.
# Arcade API key from https://app.arcade.dev/api-keysARCADE_API_KEY=YOUR_ARCADE_API_KEY# Your Arcade user ID (the email you used to sign up)ARCADE_USER_ID={arcade_user_id}# OpenAI API keyOPENAI_API_KEY=YOUR_OPENAI_API_KEY
Create agent file
Create index.ts (or index.mjs for plain JavaScript):
TypeScript
index.ts
import Arcade from "@arcadeai/arcadejs";import { executeOrAuthorizeZodTool, toZod } from "@arcadeai/arcadejs/lib/index";import { Agent, run, tool } from "@openai/agents";import "dotenv/config";import readline from "node:readline/promises";// Configurationconst ARCADE_USER_ID = process.env.ARCADE_USER_ID || "default-user";const MCP_SERVERS = ["Slack"];const INDIVIDUAL_TOOLS = ["Gmail_ListEmails", "Gmail_SendEmail", "Gmail_WhoAmI"];const SYSTEM_PROMPT = "You are a helpful assistant that can assist with Gmail and Slack.";const MODEL = "gpt-4o-mini";
Retrieve and convert Arcade tools
Use toZod() to convert Arcade tools to Zod schemas, then tool() to convert them to OpenAI Agents format:
TypeScript
index.ts
async function getArcadeTools(client: Arcade, userId: string) { // Fetch tools from MCP servers const mcpServerTools = await Promise.all( MCP_SERVERS.map(async (serverName) => { const response = await client.tools.list({ toolkit: serverName, limit: 30, }); return response.items; }) ); // Fetch individual tools by name const individualToolDefs = await Promise.all( INDIVIDUAL_TOOLS.map((toolName) => client.tools.get(toolName)) ); // Combine and deduplicate const allTools = [...mcpServerTools.flat(), ...individualToolDefs]; const uniqueTools = Array.from( new Map(allTools.map((t) => [t.qualified_name, t])).values() ); // Convert to Zod format with the execute factory // This is the TypeScript equivalent of Python's functools.partial - // the factory receives context and returns an execute function const zodTools = toZod({ tools: uniqueTools, client, userId, executeFactory: executeOrAuthorizeZodTool, }); // Convert Zod tools to OpenAI Agents format return zodTools.map(tool);}
Understanding the factory pattern: In TypeScript, executeFactory binds context to a function. It receives the tool context (client, userId, tool definition) and returns an execute function with that context already bound.
Create and run the agent
TypeScript
index.ts
async function main() { // Initialize Arcade client const client = new Arcade(); // Get tools converted to OpenAI Agents format const tools = await getArcadeTools(client, ARCADE_USER_ID); // Create the agent const agent = new Agent({ name: "Inbox Assistant", instructions: SYSTEM_PROMPT, model: MODEL, tools, }); // Set up interactive chat const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }); console.log("Hello! I'm your helpful OpenAI Agent! I use Arcade Tools to access your Gmail and Slack. Try asking me to summarize your recent emails or DM you on Slack!\n\nType 'exit' to quit.\n"); // Track conversation history for multi-turn context let conversationHistory: any[] = []; while (true) { const input = await rl.question("> "); if (input.toLowerCase() === "exit") { break; } try { // Pass conversation history for context, add new user message const result = await run( agent, conversationHistory.concat({ role: "user", content: input }) ); // Update history with full conversation conversationHistory = result.history; console.log(`\n${result.finalOutput}\n`); } catch (error) { console.error("Error:", error); } } console.log("Goodbye!"); rl.close(); process.exit(0);}main().catch(console.error);
Handle authorization
The executeOrAuthorizeZodTool factory automatically handles authorization. When a tool needs OAuth authorization (like Gmail), instead of throwing an error, it returns a response with the authorization URL. The agent’s output will include something like the following: