Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Migrate existing AI agent projects (LangChain, LangGraph, OpenAI Agents SDK, Claude Agent SDK, CrewAI) to EdgeOne Makers platform conventions. Use when the user wants to adapt a standard agent project to run on EdgeOne Makers, convert Express/Next.js API routes to Makers handlers, or add platform capabilities (context.tools, context.sandbox, context.store). Do NOT trigger for new agent projects (use makers-agents instead).
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 122% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 136% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 129% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 333% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 139% | 0% |
Migrate existing AI agent projects to the EdgeOne Makers platform format. Covers structural conversion, API adaptation, and platform capability injection.
What type of project are you migrating?
├── Python project
│ ├── Using CrewAI → See §2 CrewAI
│ ├── Using LangChain/LangGraph/DeepAgents → See §3 LangGraph (Python)
│ ├── Using OpenAI Agents SDK → See §4 OpenAI Agents (Python)
│ └── Using Claude Agent SDK → See §5 Claude SDK (Python)
└── Node/TS project
├── Using Express/Next.js API routes → See §6 Express → Makers
├── Using LangGraph/DeepAgents → See §3 LangGraph (Node)
├── Using OpenAI Agents SDK → See §4 OpenAI Agents (Node)
└── Using Claude Agent SDK → See §5 Claude SDK (Node)Before starting framework-specific changes, check these global items:
edgeone.json with correct agents.framework and buildCommand/outputDirectoryagents/ directoryprocess.env / os.environ with context.env / ctx.envreq.headers.get('x') with context.request.headers['x'] (Node) or plain dict access (Python)await req.json() with context.request.body (already parsed)AI_GATEWAY_* env varsres.json() / return {"data": ...})makers-conversation-id header to frontend fetch callscontext.tools instead of custom tool implementationscontext.store instead of in-memory or custom DBWSA_API_KEY env var and use context.tools.get("web_search")edgeone makers dev for local developmentThis is the most common migration pattern. Applies to Express/Next.js API routes, plain HTTP handlers, etc.
typescript// ❌ Before: Next.js API route (app/api/chat/route.ts) export async function POST(req: Request) { const body = await req.json(); const headers = req.headers; const apiKey = process.env.OPENAI_API_KEY; // ... LLM call ... return Response.json({ data: result }); } // ✅ After: Makers agent handler (agents/chat/index.ts) export async function onRequest(context: any) { const body = context.request.body; // already parsed const conversationId = context.conversation_id; // auto-injected from header const env = context.env; // context.env, never process.env // ... LLM call via AI_GATEWAY_* ... return new Response(JSON.stringify({ data: result }), { headers: { 'Content-Type': 'application/json' }, }); }
python# ❌ Before: Flask route @app.route('/chat', methods=['POST']) def chat(): body = request.get_json() api_key = os.environ.get('OPENAI_API_KEY') # ... LLM call ... return jsonify({'data': result}) # ✅ After: Makers agent handler (agents/chat/index.py) async def handler(ctx): body = ctx.request.body conversation_id = ctx.conversation_id api_key = ctx.env.get("AI_GATEWAY_API_KEY") # ... LLM call via AI_GATEWAY_* ... return {"data": result}
| Before | After | |--------|-------| | os.environ.get("OPENAI_API_KEY") | ctx.env.get("AI_GATEWAY_API_KEY") | | LLM(provider="openai", ...) — LiteLLM dispatch | LLM(provider="openai", base_url=ctx.env["AI_GATEWAY_BASE_URL"], ...) — bypass LiteLLM | | memory=True on Crew | memory=False + use ctx.store | | verbose=True | verbose=False (events go through crewai_event_bus) | | crew.kickoff() (blocking) | await asyncio.to_thread(crew.kickoff) | | Custom search tools | Use ctx.tools.to_crewai_tools(BaseTool) | | Flask/FastAPI handler | async def handler(ctx): → ctx.utils.stream_sse(gen()) |
json{ "buildCommand": "", "outputDirectory": "", "agents": { "framework": "crewai" } }
txtcrewai>=1.14.5 openai>=1.50.0
async def handler(ctx):ctx.env, never os.environLLM(provider="openai", api_key=ctx.env["AI_GATEWAY_API_KEY"], base_url=ctx.env["AI_GATEWAY_BASE_URL"])Crew(memory=False, verbose=False)crew.kickoff() in asyncio.to_thread()ctx.tools.to_crewai_tools(BaseTool)ctx.utils.stream_sse(gen())> See makers-agents/skills/python-frameworks/crewai.md for the complete pattern. > Detailed before/after: references/crewai-to-makers.md
| Before | After | |--------|-------| | Direct model creation (new ChatOpenAI(...)) | Use AI_GATEWAY_* for apiKey/baseURL | | MemorySaver (in-memory checkpointer) | context.store.langgraphCheckpointer (persistent) | | Custom tool functions | context.tools.toLangChainTools(tool) | | agent.stream() | SSE via createSSEResponse(gen, signal) (Node) or ctx.utils.stream_sse(gen()) (Python) | | thread_id manual management | thread_id = context.conversation_id |
json{ "agents": { "framework": "langgraph" } }
json{ "buildCommand": "", "outputDirectory": "", "agents": { "framework": "langgraph" } }
agents/<name>/index.ts (or .py)AI_GATEWAY_API_KEY + AI_GATEWAY_BASE_URLcontext.store.langgraphCheckpointer instead of MemorySavercontext.store.langgraphStorecontext.tools.toLangChainTools(tool) instead of custom tool functionsthread_id: { configurable: { thread_id: context.conversation_id } }> Node: makers-agents/skills/node-frameworks/langgraph.md > Python: makers-agents/skills/python-frameworks/langgraph.md > DeepAgents: makers-agents/skills/node-frameworks/deepagents.md > Detailed before/after: references/langgraph-to-makers.md, references/deepagents-to-makers.md
| Before | After | |--------|-------| | new OpenAI({ apiKey, baseURL }) | Read AI_GATEWAY_* from context.env / ctx.env | | Runner.run(agent, input, { tools }) | Tools from context.tools.all() (already OpenAI function format) | | Session management | context.store.openaiSession(convId) (Node) | | Express route response | SSE via createSSEResponse(gen, signal) (Node) or ctx.utils.stream_sse(gen()) (Python) | | Model name hardcoded | ctx.env.AI_GATEWAY_MODEL || DEFAULT_MODEL |
json{ "agents": { "framework": "openai-agents-sdk" } }
json{ "buildCommand": "", "outputDirectory": "", "agents": { "framework": "openai-agents-sdk" } }
agents/<name>/index.ts (or .py)context.env (not process.env)context.tools.all() (returns OpenAI function tools)context.store.openaiSession(conversationId) for session (Node)output_text_delta → ai_response, tool_called → tool_call> Node: makers-agents/skills/node-frameworks/openai-agents.md > Python: makers-agents/skills/python-frameworks/openai-agents.md > Detailed before/after: references/openai-agents-to-makers.md
| Before | After | |--------|-------| | ANTHROPIC_API_KEY env var | Mapped from AI_GATEWAY_* via collectGatewayEnv() | | process.env | context.env injected into query().options.env | | Custom MCP tools | context.tools.toClaudeMcpServer() | | Session | context.store.claudeSessionStore() (Node) | | Stdout EPIPE crash | Swallow EPIPE on process.stdout (Node) | | No writable config dir | Set CLAUDE_CONFIG_DIR=/tmp/claude-agent-sdk, CLAUDE_CODE_TMPDIR=/tmp |
json{ "agents": { "framework": "claude-agent-sdk" } }
json{ "buildCommand": "", "outputDirectory": "", "agents": { "framework": "claude-agent-sdk" } }
agents/<name>/index.ts (or .py)AI_GATEWAY_* → ANTHROPIC_* via collectGatewayEnv(context.env)query({ options: { env: collectGatewayEnv(...) } })context.tools.toClaudeMcpServer('edgeone', { alwaysLoad: true })EPIPE on process.stdoutCLAUDE_CONFIG_DIR=/tmp/claude-agent-sdk, CLAUDE_CODE_TMPDIR=/tmp> Node: makers-agents/skills/node-frameworks/claude-sdk.md > Python: makers-agents/skills/python-frameworks/claude-sdk.md > Detailed before/after: references/claude-agent-sdk-to-makers.md
General migration for any Express-based or Next.js API route agent.
| Step | Before | After | |------|--------|-------| | 1. File location | app/api/chat/route.ts or server/routes/chat.ts | agents/chat/index.ts | | 2. Entry signature | export async function POST(req) or app.post('/chat', handler) | export async function onRequest(context) | | 3. Body parsing | await req.json() | context.request.body (already parsed) | | 4. Headers | req.headers.get('x-foo') | context.request.headers['x-foo'] | | 5. Abort signal | req.signal | context.request.signal (AbortSignal) | | 6. Model access | process.env.OPENAI_API_KEY → direct call | context.env.AI_GATEWAY_* → AI Gateway | | 7. Response | res.json() or return Response.json() | SSE stream via createSSEResponse(gen, signal) |
typescript// ❌ Before: Next.js (app/api/chat/route.ts) import { NextRequest } from 'next/server'; import OpenAI from 'openai'; export async function POST(req: NextRequest) { const { message } = await req.json(); const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); const response = await client.chat.completions.create({ model: 'gpt-4o', messages: [{ role: 'user', content: message }], stream: true, }); // ... stream back as Response } // ✅ After: EdgeOne Makers (agents/chat/index.ts) import { createLogger, sseEvent, createSSEResponse } from '../_shared'; export async function onRequest(context: any) { const { message } = context.request.body ?? {}; if (!message) return new Response('Missing message', { status: 400 }); const signal = context.request.signal as AbortSignal; return createSSEResponse(async function* (sig) { const response = await fetch(context.env.AI_GATEWAY_BASE_URL + '/v1/chat/completions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${context.env.AI_GATEWAY_API_KEY}`, }, body: JSON.stringify({ model: context.env.AI_GATEWAY_MODEL || '@makers/deepseek-v4-flash', messages: [{ role: 'user', content: message }], stream: true, }), signal: sig, }); // ... proxy SSE chunks ... yield 'data: [DONE]\n\n'; }, signal); }
typescript// ❌ Before: plain fetch without conversation-id const response = await fetch('/api/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message }), }); // ✅ After: with makers-conversation-id header const conversationId = getOrCreateConversationId(); // crypto.randomUUID() + localStorage const response = await fetch('/chat', { method: 'POST', headers: { 'Content-Type': 'application/json', 'makers-conversation-id': conversationId, // ⭐ required for all AI endpoints }, body: JSON.stringify({ message }), });
typescript// ✅ Always pass conversation_id in body for /stop await fetch('/stop', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ conversation_id: conversationId }), });
typescriptconst reader = response.body!.getReader(); const decoder = new TextDecoder(); let buffer = ''; while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split('\n\n'); buffer = lines.pop() || ''; for (const line of lines) { if (line.startsWith('data: ')) { const data = line.slice(6); if (data === '[DONE]') return; try { const event = JSON.parse(data); if (event.type === 'ai_response') { /* display text */ } if (event.type === 'tool_call') { /* show tool call */ } if (event.type === 'ping') { /* ignore heartbeat */ } } catch { /* skip non-JSON */ } } } }
After migration, verify these items before deploying:
edgeone makers dev starts without errors/chat endpoint returns SSE stream (not JSON)context.env is used everywhere (grep for process.env / os.environ — none should remain)edgeone.json has correct agents.frameworkcontext.tools) work in at least one frameworkcontext.store)/stop endpoint cancels active runsmakers-conversation-id headeronRequest/handler)Other measured skills in the registry, with their headline benchmark lift.