CtrlK
BlogDocsLog inGet started
Tessl Logo

makers-migration

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).

79

Quality

100%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Low

Low-risk findings worth noting

SKILL.md
Quality
Evals
Security

EdgeOne Makers Migration Guide

Migrate existing AI agent projects to the EdgeOne Makers platform format. Covers structural conversion, API adaptation, and platform capability injection.


Migration Decision Tree

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)

⚠️ Migration Checklist (common to all frameworks)

Before starting framework-specific changes, check these global items:

  • Create edgeone.json with correct agents.framework and buildCommand/outputDirectory
  • Move backend code from Express routes / Next.js API routes into agents/ directory
  • Replace process.env / os.environ with context.env / ctx.env
  • Replace req.headers.get('x') with context.request.headers['x'] (Node) or plain dict access (Python)
  • Replace await req.json() with context.request.body (already parsed)
  • Replace direct model API calls (OpenAI, Anthropic) with AI_GATEWAY_* env vars
  • Add SSE streaming for AI endpoints (replace res.json() / return {"data": ...})
  • Add makers-conversation-id header to frontend fetch calls
  • Wire platform tools through context.tools instead of custom tool implementations
  • Wire conversation history through context.store instead of in-memory or custom DB
  • If using web_search, set WSA_API_KEY env var and use context.tools.get("web_search")
  • Set up edgeone makers dev for local development

§1. Standard API Route → Makers Handler

This is the most common migration pattern. Applies to Express/Next.js API routes, plain HTTP handlers, etc.

Node (Express/Next.js → Makers)

// ❌ 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 (Flask/FastAPI → Makers)

# ❌ 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}

§2. CrewAI (Python)

Key changes

BeforeAfter
os.environ.get("OPENAI_API_KEY")ctx.env.get("AI_GATEWAY_API_KEY")
LLM(provider="openai", ...) — LiteLLM dispatchLLM(provider="openai", base_url=ctx.env["AI_GATEWAY_BASE_URL"], ...) — bypass LiteLLM
memory=True on Crewmemory=False + use ctx.store
verbose=Trueverbose=False (events go through crewai_event_bus)
crew.kickoff() (blocking)await asyncio.to_thread(crew.kickoff)
Custom search toolsUse ctx.tools.to_crewai_tools(BaseTool)
Flask/FastAPI handlerasync def handler(ctx):ctx.utils.stream_sse(gen())

edgeone.json

{
  "buildCommand": "",
  "outputDirectory": "",
  "agents": {
    "framework": "crewai"
  }
}

Requirements

crewai>=1.14.5
openai>=1.50.0

Migration steps

  1. Replace Flask/FastAPI entry with async def handler(ctx):
  2. Read env from ctx.env, never os.environ
  3. Use LLM(provider="openai", api_key=ctx.env["AI_GATEWAY_API_KEY"], base_url=ctx.env["AI_GATEWAY_BASE_URL"])
  4. Set Crew(memory=False, verbose=False)
  5. Wrap crew.kickoff() in asyncio.to_thread()
  6. Replace custom tools with ctx.tools.to_crewai_tools(BaseTool)
  7. Return SSE via 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


§3. LangGraph / DeepAgents (Node + Python)

Key changes

BeforeAfter
Direct model creation (new ChatOpenAI(...))Use AI_GATEWAY_* for apiKey/baseURL
MemorySaver (in-memory checkpointer)context.store.langgraphCheckpointer (persistent)
Custom tool functionscontext.tools.toLangChainTools(tool)
agent.stream()SSE via createSSEResponse(gen, signal) (Node) or ctx.utils.stream_sse(gen()) (Python)
thread_id manual managementthread_id = context.conversation_id

Node — edgeone.json

{
  "agents": {
    "framework": "langgraph"
  }
}

Python — edgeone.json

{
  "buildCommand": "",
  "outputDirectory": "",
  "agents": {
    "framework": "langgraph"
  }
}

Migration steps

  1. Move handler into agents/<name>/index.ts (or .py)
  2. Replace model initialization: use AI_GATEWAY_API_KEY + AI_GATEWAY_BASE_URL
  3. Replace checkpointer: context.store.langgraphCheckpointer instead of MemorySaver
  4. Replace store: context.store.langgraphStore
  5. Replace tools: context.tools.toLangChainTools(tool) instead of custom tool functions
  6. Set thread_id: { configurable: { thread_id: context.conversation_id } }
  7. Replace response with SSE streaming pattern

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


§4. OpenAI Agents SDK (Node + Python)

Key changes

BeforeAfter
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 managementcontext.store.openaiSession(convId) (Node)
Express route responseSSE via createSSEResponse(gen, signal) (Node) or ctx.utils.stream_sse(gen()) (Python)
Model name hardcoded`ctx.env.AI_GATEWAY_MODEL

Node — edgeone.json

{
  "agents": {
    "framework": "openai-agents-sdk"
  }
}

Python — edgeone.json

{
  "buildCommand": "",
  "outputDirectory": "",
  "agents": {
    "framework": "openai-agents-sdk"
  }
}

Migration steps

  1. Move handler into agents/<name>/index.ts (or .py)
  2. Create OpenAI client from context.env (not process.env)
  3. Replace tools with context.tools.all() (returns OpenAI function tools)
  4. Use context.store.openaiSession(conversationId) for session (Node)
  5. Map stream events to SSE: output_text_deltaai_response, tool_calledtool_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


§5. Claude Agent SDK (Node + Python)

Key changes

BeforeAfter
ANTHROPIC_API_KEY env varMapped from AI_GATEWAY_* via collectGatewayEnv()
process.envcontext.env injected into query().options.env
Custom MCP toolscontext.tools.toClaudeMcpServer()
Sessioncontext.store.claudeSessionStore() (Node)
Stdout EPIPE crashSwallow EPIPE on process.stdout (Node)
No writable config dirSet CLAUDE_CONFIG_DIR=/tmp/claude-agent-sdk, CLAUDE_CODE_TMPDIR=/tmp

Node — edgeone.json

{
  "agents": {
    "framework": "claude-agent-sdk"
  }
}

Python — edgeone.json

{
  "buildCommand": "",
  "outputDirectory": "",
  "agents": {
    "framework": "claude-agent-sdk"
  }
}

Migration steps

  1. Move handler into agents/<name>/index.ts (or .py)
  2. Map AI_GATEWAY_*ANTHROPIC_* via collectGatewayEnv(context.env)
  3. Inject env into query({ options: { env: collectGatewayEnv(...) } })
  4. Replace MCP tools: context.tools.toClaudeMcpServer('edgeone', { alwaysLoad: true })
  5. Node only: swallow EPIPE on process.stdout
  6. Set writable config dirs: CLAUDE_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


§6. Express / Next.js API Routes (Node)

General migration for any Express-based or Next.js API route agent.

The 7-step conversion

StepBeforeAfter
1. File locationapp/api/chat/route.ts or server/routes/chat.tsagents/chat/index.ts
2. Entry signatureexport async function POST(req) or app.post('/chat', handler)export async function onRequest(context)
3. Body parsingawait req.json()context.request.body (already parsed)
4. Headersreq.headers.get('x-foo')context.request.headers['x-foo']
5. Abort signalreq.signalcontext.request.signal (AbortSignal)
6. Model accessprocess.env.OPENAI_API_KEY → direct callcontext.env.AI_GATEWAY_* → AI Gateway
7. Responseres.json() or return Response.json()SSE stream via createSSEResponse(gen, signal)

Example: Next.js API route → Makers

// ❌ 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);
}

§7. Client-Side Migration

Frontend fetch calls

// ❌ 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 }),
});

/stop endpoint

// ✅ Always pass conversation_id in body for /stop
await fetch('/stop', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ conversation_id: conversationId }),
});

SSE parsing

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 */ }
    }
  }
}

§8. Post-Migration Verification

After migration, verify these items before deploying:

  • edgeone makers dev starts without errors
  • /chat endpoint returns SSE stream (not JSON)
  • AI responses work end-to-end (frontend → agent → model → frontend)
  • context.env is used everywhere (grep for process.env / os.environ — none should remain)
  • edgeone.json has correct agents.framework
  • Platform tools (context.tools) work in at least one framework
  • Conversation history persists across requests (via context.store)
  • /stop endpoint cancels active runs
  • Frontend sends makers-conversation-id header

See Also

Detailed before/after reference files

Repository
TencentEdgeOne/edgeone-makers-tools
Last updated
First committed

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.