From edgeone-makers-tools
Migrates existing AI agent projects (LangChain, LangGraph, OpenAI Agents SDK, Claude Agent SDK, CrewAI) to EdgeOne Makers platform conventions. Converts Express/Next.js API routes to Makers handlers and adds platform capabilities.
How this skill is triggered — by the user, by Claude, or both
Slash command
/edgeone-makers-tools:makers-migrationThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Migrate existing AI agent projects to the **EdgeOne Makers** platform format. Covers structural conversion, API adaptation, and platform capability injection.
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.
// ❌ 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' },
});
}
# ❌ 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()) |
{
"buildCommand": "",
"outputDirectory": "",
"agents": {
"framework": "crewai"
}
}
crewai>=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 |
{
"agents": {
"framework": "langgraph"
}
}
{
"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 |
{
"agents": {
"framework": "openai-agents-sdk"
}
}
{
"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_callNode: 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 |
{
"agents": {
"framework": "claude-agent-sdk"
}
}
{
"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=/tmpNode: 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) |
// ❌ 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);
}
// ❌ 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 }),
});
// ✅ Always pass conversation_id in body for /stop
await fetch('/stop', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ conversation_id: conversationId }),
});
const 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)npx claudepluginhub tencentedgeone/edgeone-makers-tools --plugin edgeone-makers-toolsGuides building AI agent endpoints on EdgeOne Makers with five framework routes, platform-injected runtime, SSE streaming, and conversation routing.
Deploys websites, apps, AI agents to Agentuity production. Creates new projects, migrates Express/Next.js/Vite apps, restructures code, and manages full deploy workflows.
Guides developers through creating a new AgentCore project: framework selection, scaffolding, first deploy, and first invocation on AWS.