DEV Community

Cover image for วิธีสร้าง Claude Code ด้วยตัวเอง
Thanawat Wongchai
Thanawat Wongchai

Posted on • Originally published at apidog.com

วิธีสร้าง Claude Code ด้วยตัวเอง

TL;DR

การรั่วไหลของซอร์สโค้ด Claude เปิดเผยฐานโค้ด TypeScript ขนาด 512,000 บรรทัดเมื่อวันที่ 31 มีนาคม 2026 โครงสร้างหลักเป็นลูป while ที่เรียก API, ส่ง tool calls, และส่งผลลัพธ์กลับ คุณสามารถสร้างเวอร์ชันของตัวเองด้วย Python, Anthropic SDK และโค้ดสำหรับลูปหลักประมาณ 200 บรรทัด บทความนี้จะอธิบายโครงสร้างแต่ละส่วนและแนะนำวิธีสร้างเองแบบ step-by-step

ลองใช้ Apidog วันนี้

บทนำ

เมื่อวันที่ 31 มีนาคม 2026, Anthropic เผลอปล่อยไฟล์ source map ขนาด 59.8 MB ใน @anthropic-ai/claude-code (npm package) ซึ่งทำให้สามารถกู้คืน TypeScript codebase ฉบับเต็มได้ในทันที

ไม่กี่ชั่วโมงหลังการรั่วไหล โค้ดถูกคัดลอกไปทั่ว GitHub หลายแห่ง และนักพัฒนาเริ่มวิเคราะห์แต่ละโมดูล ตั้งแต่ลูปเอเจนต์หลักจนถึงฟีเจอร์ซ่อน เช่น undercover mode และการฉีดเครื่องมือปลอม

ปฏิกิริยามีทั้งด้านวิจารณ์เรื่องความปลอดภัยและความหลงใหลในสถาปัตยกรรมที่ถูกเปิดเผย นักพัฒนาจำนวนมากตั้งคำถาม: "จะสร้างแบบนี้เองได้ไหม?" คำตอบคือ "ได้" โครงสร้างหลักเรียบง่ายและสามารถสร้างใหม่ได้ด้วย Python + Anthropic SDK โดยบทความนี้จะอธิบายทีละส่วน พร้อมโค้ดตัวอย่างที่นำไปใช้ได้ทันที และยังแนะนำวิธีทดสอบ API interaction ของเอเจนต์แบบ custom ด้วย Apidog (ช่วยให้ debug API conversation หลายรอบได้สะดวกกว่า curl มาก)

สิ่งที่การรั่วไหลเปิดเผยเกี่ยวกับสถาปัตยกรรมของ Claude Code

ภาพรวมของฐานโค้ด

Claude Code (โค้ดเนม "Tengu") มีไฟล์ ~1,900 ไฟล์ โครงสร้างโมดูลแบ่งเป็น:

cli/          - UI เทอร์มินัล (React + Ink)
tools/        - เครื่องมือเฉพาะมากกว่า 40 รายการ
core/         - system prompt, สิทธิ์, ค่าคงที่
assistant/    - จัดระเบียบเอเจนต์
services/     - API, การบีบอัด, OAuth, telemetry
Enter fullscreen mode Exit fullscreen mode

UI CLI คือ React app ที่ใช้ Ink (React for terminal) กับ CSS flexbox (Yoga) และ ANSI สำหรับ formatting ทุก interaction เป็น React component ทั้งหมด

สำหรับการทำ DIY agent คุณไม่จำเป็นต้องมี React CLI – ใช้ REPL ธรรมดาก็เพียงพอ

ลูปเอเจนต์หลัก

แกนหลักของ Claude Code เป็น while loop ที่ Anthropic เรียกว่า "nO" กระบวนการคือ:

  1. ส่งข้อความ (system prompt + tools) ไปยัง Claude API
  2. รับ response ที่อาจมีข้อความหรือ block tool_use
  3. dispatch ไปยังฟังก์ชัน tool handler ตามชื่อ
  4. ผนวกผลลัพธ์เครื่องมือกลับเข้า message list
  5. ถ้ายังมี tool call ให้วนลูปต่อ
  6. ถ้าเป็นข้อความธรรมดา ให้ส่งคืน user

ตัวอย่างโค้ด Python (minimal):

import anthropic

client = anthropic.Anthropic()
MODEL = "claude-sonnet-4-6"

def agent_loop(system_prompt: str, tools: list, messages: list) -> str:
    """The core agent loop - keep calling until no more tool use."""
    while True:
        response = client.messages.create(
            model=MODEL,
            max_tokens=16384,
            system=system_prompt,
            tools=tools,
            messages=messages,
        )

        messages.append({"role": "assistant", "content": response.content})

        if response.stop_reason != "tool_use":
            return "".join(
                block.text for block in response.content
                if hasattr(block, "text")
            )

        tool_results = []
        for block in response.content:
            if block.type == "tool_use":
                result = execute_tool(block.name, block.input)
                tool_results.append({
                    "type": "tool_result",
                    "tool_use_id": block.id,
                    "content": result,
                })

        messages.append({"role": "user", "content": tool_results})
Enter fullscreen mode Exit fullscreen mode

แกนหลักอยู่ที่ ~30 บรรทัด ความซับซ้อนที่เหลือมาจาก tool, สิทธิ์, context, และ memory

การสร้างระบบเครื่องมือ

ทำไมต้องใช้ "เครื่องมือเฉพาะ" แทน bash

Claude Code ใช้ เครื่องมือเฉพาะ เช่น Read, Edit, Grep, Glob แทนที่จะรัน bash ตรง ๆ เนื่องจาก:

  • Structured output: เช่น Grep เฉพาะคืนค่าที่ parse ได้
  • Security: BashTool จะบล็อก pattern อันตราย, เครื่องมือเฉพาะปลอดภัยกว่า
  • ประสิทธิภาพโทเค็น: ตัดทอน output ได้ง่าย, ลด context window เปลือง

ชุดเครื่องมือที่จำเป็น

ตัวอย่างโครงสร้าง TOOLS ที่ควรมี:

TOOLS = [
    {
        "name": "read_file",
        "description": "อ่านไฟล์จากระบบไฟล์ ส่งคืนเนื้อหาพร้อมหมายเลขบรรทัด",
        "input_schema": {
            "type": "object",
            "properties": {
                "file_path": {
                    "type": "string",
                    "description": "พาธสัมบูรณ์ของไฟล์"
                },
                "offset": {
                    "type": "integer",
                    "description": "หมายเลขบรรทัดที่จะเริ่มอ่านจาก (0-indexed)"
                },
                "limit": {
                    "type": "integer",
                    "description": "จำนวนบรรทัดสูงสุดที่จะอ่าน ค่าเริ่มต้นคือ 2000"
                }
            },
            "required": ["file_path"]
        }
    },
    # ... (write_file, edit_file, run_command, search_code)
]
Enter fullscreen mode Exit fullscreen mode

แมปเครื่องมือกับ handler

โค้ดตัวอย่างสำหรับ dispatch ฟังก์ชัน:

import subprocess
import os
import re

def execute_tool(name: str, params: dict) -> str:
    handlers = {
        "read_file": handle_read_file,
        "write_file": handle_write_file,
        "edit_file": handle_edit_file,
        "run_command": handle_run_command,
        "search_code": handle_search_code,
    }
    handler = handlers.get(name)
    if not handler:
        return f"Error: Unknown tool '{name}'"
    try:
        return handler(params)
    except Exception as e:
        return f"Error: {str(e)}"

def handle_read_file(params: dict) -> str:
    path = params["file_path"]
    offset = params.get("offset", 0)
    limit = params.get("limit", 2000)
    with open(path, "r") as f:
        lines = f.readlines()
    selected = lines[offset:offset + limit]
    numbered = [f"{i + offset + 1}\t{line}" for i, line in enumerate(selected)]
    return "".join(numbered)

# ... (write_file, edit_file, run_command, search_code)
Enter fullscreen mode Exit fullscreen mode

การจัดการบริบท: ปัญหาที่ยาก

ทำไม context management สำคัญกว่า prompt engineering

Claude Code ลงทุนกับ context compressor (wU2) มากกว่าตัว prompt เอง สองกลยุทธ์ที่ควรมี:

  • Auto-compaction: เมื่อใช้ context เกิน ~92% ให้สรุปข้อความเดิม
  • CLAUDE.md re-injection: แทรกแนวทางโปรเจกต์ซ้ำทุกเทิร์นเพื่อไม่ให้ context หลุด

ตัวอย่าง compressor แบบง่าย

def maybe_compact(messages: list, system_prompt: str, max_tokens: int = 180000) -> list:
    total_chars = sum(len(str(m.get("content", ""))) for m in messages)
    estimated_tokens = total_chars // 4
    if estimated_tokens < max_tokens * 0.85:
        return messages
    summary_response = client.messages.create(
        model=MODEL,
        max_tokens=4096,
        system="Summarize this conversation. ...",
        messages=messages,
    )
    summary_text = summary_response.content[0].text
    compacted = [
        {"role": "user", "content": f"[สรุปการสนทนา]\n{summary_text}"},
        {"role": "assistant", "content": "ฉันมีบริบทจากการสนทนาก่อนหน้านี้แล้ว ..."},
    ]
    compacted.extend(messages[-4:])
    return compacted
Enter fullscreen mode Exit fullscreen mode

แทรกแนวทางโปรเจกต์ซ้ำ

def build_system_prompt(project_dir: str) -> str:
    base_prompt = """คุณเป็นผู้ช่วยเขียนโค้ด...
ควรอ่านไฟล์ก่อนแก้ไข ..."""
    claude_md_path = os.path.join(project_dir, ".claude", "CLAUDE.md")
    if os.path.exists(claude_md_path):
        with open(claude_md_path, "r") as f:
            project_context = f.read()
        base_prompt += f"\n\n# แนวทางโปรเจกต์\n{project_context}"
    root_md = os.path.join(project_dir, "CLAUDE.md")
    if os.path.exists(root_md):
        with open(root_md, "r") as f:
            root_context = f.read()
        base_prompt += f"\n\n# แนวทางที่เก็บ\n{root_context}"
    return base_prompt
Enter fullscreen mode Exit fullscreen mode

ระบบหน่วยความจำสามชั้น

ชั้นที่ 1: MEMORY.md (โหลดตลอดเวลา)

  • index ขนาดเล็ก (บรรทัดละ <150 ตัวอักษร) โหลดในทุก prompt
  • จำกัดที่ 200 บรรทัด / 25KB
- [การตั้งค่าผู้ใช้](memory/user-prefs.md) - ชอบ TypeScript, ใช้การผูกคีย์ Vim
- [ข้อตกลง API](memory/api-conventions.md) - REST + JSON:API
Enter fullscreen mode Exit fullscreen mode

ชั้นที่ 2: ไฟล์หัวข้อ (on demand)

  • โหลดเมื่อเกี่ยวข้อง (project conventions, decisions, patterns)

ชั้นที่ 3: บันทึกการสนทนา (grep ค้นหา)

  • ไม่โหลดทั้งหมด ใช้ grep หาเฉพาะคีย์

ตัวอย่างระบบ memory

import json

MEMORY_DIR = ".agent/memory"

def load_memory_index() -> str:
    index_path = os.path.join(MEMORY_DIR, "MEMORY.md")
    if os.path.exists(index_path):
        with open(index_path, "r") as f:
            return f.read()
    return ""

def save_memory(key: str, content: str, description: str):
    os.makedirs(MEMORY_DIR, exist_ok=True)
    filename = f"{key.replace(' ', '-').lower()}.md"
    filepath = os.path.join(MEMORY_DIR, filename)
    with open(filepath, "w") as f:
        f.write(f"---\nname: {key}\ndescription: {description}\n---\n\n{content}")
    index_path = os.path.join(MEMORY_DIR, "MEMORY.md")
    index_lines = []
    if os.path.exists(index_path):
        with open(index_path, "r") as f:
            index_lines = f.readlines()
    new_entry = f"- [{key}]({filename}) - {description}\n"
    updated = False
    for i, line in enumerate(index_lines):
        if filename in line:
            index_lines[i] = new_entry
            updated = True
            break
    if not updated:
        index_lines.append(new_entry)
    with open(index_path, "w") as f:
        f.writelines(index_lines)
Enter fullscreen mode Exit fullscreen mode

เพิ่ม save_memory ใน TOOLS เพื่อให้ agent จดจำความรู้ระหว่าง session

การเพิ่มระบบสิทธิ์

การรั่วไหลเปิดเผย permission mode 5 แบบ: default, auto, bypass, yolo, deny และจัดกลุ่ม risk เป็น LOW, MEDIUM, HIGH

สำหรับ DIY agent สามารถใช้ logic แบบนี้:

RISK_LEVELS = {
    "read_file": "low",
    "search_code": "low",
    "edit_file": "medium",
    "write_file": "medium",
    "run_command": "high",
}

def check_permission(tool_name: str, params: dict, auto_approve_low: bool = True) -> bool:
    risk = RISK_LEVELS.get(tool_name, "high")
    if risk == "low" and auto_approve_low:
        return True
    print(f"\n--- ตรวจสอบสิทธิ์ (ความเสี่ยง {risk.upper()}) ---")
    print(f"เครื่องมือ: {tool_name}")
    for key, value in params.items():
        display = str(value)[:200]
        print(f"  {key}: {display}")
    response = input("อนุญาต? [y/n/always]: ").strip().lower()
    if response == "always":
        RISK_LEVELS[tool_name] = "low"
        return True
    return response == "y"
Enter fullscreen mode Exit fullscreen mode

การทดสอบการเรียก API ของเอเจนต์ของคุณด้วย Apidog

การพัฒนา agent ที่โต้ตอบกับ Claude API หลายร้อยครั้ง ต้อง debug รอบ conversation แบบ multi-turn และ tool use ได้สะดวก

Apidog Debug

Apidog ช่วยให้ตรวจสอบ/ทดสอบ API requests ของ agent ได้แบบ visual และ repeatable:

จับภาพและเล่นซ้ำคำขอ API

  1. เปิด Apidog สร้างโปรเจกต์ใหม่
  2. นำเข้า endpoint Anthropic Messages API: POST https://api.anthropic.com/v1/messages
  3. กำหนด request body (system prompt, tools, messages)
  4. ทดสอบแต่ละรอบด้วยการ replay request ที่จับภาพไว้

ปรับ input หรือ payload ได้ทันทีโดยไม่ต้องรันลูปทั้งหมด

ดีบัก multi-turn conversation

  • บันทึก array messages เป็น environment variable หลังแต่ละรอบ
  • เล่นซ้ำจากจุดไหนก็ได้
  • เปรียบเทียบผลลัพธ์ tool ระหว่าง run เพื่อวิเคราะห์พฤติกรรม

ตรวจสอบความถูกต้องของ schema เครื่องมือ

นำเข้า JSON schema ของ tools เข้าตัว validator ของ Apidog เพื่อตรวจสอบ schema error ก่อนยิง API

รวบรวมทั้งหมดเข้าด้วยกัน: REPL ที่สมบูรณ์

ตัวอย่าง main REPL agent ที่รวมทุกอย่างไว้ในไฟล์เดียว:

#!/usr/bin/env python3
"""A minimal Claude Code-style coding agent."""

import anthropic
import os
import sys

client = anthropic.Anthropic()
MODEL = "claude-sonnet-4-6"
PROJECT_DIR = os.getcwd()

def main():
    system_prompt = build_system_prompt(PROJECT_DIR)
    memory = load_memory_index()
    if memory:
        system_prompt += f"\n\n# หน่วยความจำ\n{memory}"

    messages = []
    print("เอเจนต์เขียนโค้ดพร้อมแล้ว พิมพ์ 'quit' เพื่อออก\n")

    while True:
        user_input = input("> ").strip()
        if user_input.lower() in ("quit", "exit"):
            break
        if not user_input:
            continue

        messages.append({"role": "user", "content": user_input})
        messages = maybe_compact(messages, system_prompt)
        current_system = build_system_prompt(PROJECT_DIR)
        memory = load_memory_index()
        if memory:
            current_system += f"\n\n# หน่วยความจำ\n{memory}"
        result = agent_loop(current_system, TOOLS, messages)
        print(f"\n{result}\n")

if __name__ == "__main__":
    main()
Enter fullscreen mode Exit fullscreen mode

สิ่งที่ต้องเพิ่มต่อไป

หลังจากได้ลูปหลักแล้ว ฟีเจอร์สำคัญที่ควรเพิ่ม คือ:

  • เอเจนต์ย่อย (subagent) สำหรับงานขนาน
  • การลบข้อมูลซ้ำ จากการอ่านไฟล์ (cache file hash/mtime)
  • การตัดเอาต์พุต/สุ่มตัวอย่าง เมื่อ tool คืนข้อมูลใหญ่
  • การบีบอัดอัตโนมัติพร้อม reinject ไฟล์ ที่ถูกเข้าถึงล่าสุด

สิ่งที่เราเรียนรู้จากการรั่วไหล

  • ลูปหลักของ agent นั้นเรียบง่าย (~30 บรรทัด)
  • เครื่องมือเฉพาะดีกว่า bash ในแง่ token density และ security
  • Memory และ context management สำคัญมาก
  • Harness (ตัวรับ-ส่ง context/tool/memory) คือ "product" จริง ๆ ไม่ใช่ LLM
  • ถ้าต้องการ debug API conversation agent แบบ multi-turn หรือ tool use ซับซ้อน ลองใช้ Apidog ฟรี จะช่วยให้ workflow เร็วขึ้นมาก

คำถามที่พบบ่อย

ฉันสามารถใช้รูปแบบจากการรั่วไหลของ Claude Code ได้อย่างถูกกฎหมายหรือไม่?

ได้ รูปแบบสถาปัตยกรรม (while loop + tool dispatch) เป็น standard pattern ที่พบได้ในเอกสาร Anthropic API คุณไม่ควรคัดลอกโค้ดตรง ๆ แต่การสร้างสถาปัตยกรรมแบบเดียวกันด้วยโค้ดของตัวเองถือว่าโอเค

ฉันควรใช้โมเดลใดสำหรับเอเจนต์เขียนโค้ด DIY?

Claude Sonnet 4.6 เหมาะสมสำหรับ balance ด้านความเร็วและความสามารถ งาน complex แนะนำ Opus 4.6 (แต่แพงกว่าและช้ากว่า) งานง่าย ๆ ใช้ Haiku 4.5 ก็ประหยัดสุด

การรันเอเจนต์เขียนโค้ดของคุณเองมีค่าใช้จ่ายเท่าไร?

ค่าใช้จ่ายอยู่ที่ ~1-5 ดอลลาร์ ต่อ session (30-50 รอบ) หากใช้ Claude Sonnet 4.6 ปัจจัยหลักคือ window context – การบีบอัดช่วยลดค่าใช้จ่าย

เหตุใด Claude Code จึงใช้ React สำหรับแอปเทอร์มินัล?

Ink (React for terminal) ช่วย reuse component/state management สำหรับ UI ที่ซับซ้อน สำหรับ DIY ใช้ input()/print() ก็เพียงพอ

คุณสมบัติที่สำคัญที่สุดที่ควรสร้างหลังจากลูปหลักคืออะไร?

ระบบสิทธิ์ (permission) เพื่อป้องกัน agent เขียนทับไฟล์หรือรันคำสั่งอันตรายโดยไม่แจ้งเตือน

Claude Code จัดการข้อผิดพลาดจากการเรียกใช้เครื่องมืออย่างไร?

error จาก tool จะถูกส่งกลับในข้อความ tool_result model ตัดสินใจ retry หรือถาม user เอง ไม่มี error handling logic พิเศษ

ฉันสามารถใช้สิ่งนี้กับโมเดลอื่นที่ไม่ใช่ Claude ได้หรือไม่?

ได้ ถ้า LLM รองรับ function/tool call เช่น GPT-4, Gemini, Llama ปรับแค่รูปแบบ API ที่เรียก

ฉันจะป้องกันไม่ให้เอเจนต์รันคำสั่งที่เป็นอันตรายได้อย่างไร?

เพิ่มรายการ pattern ที่บล็อกไว้ (rm -rf /, mkfs, ฯลฯ) และ require approve สำหรับ run_command ทุกครั้ง แบ่งระดับ risk แล้วแจ้งเตือน user ก่อนรันเครื่องมือเสี่ยง


สนใจ debug และพัฒนา agent API อย่างมีประสิทธิภาพ? ลองใช้ Apidog วันนี้ ฟรี!

Top comments (0)