DEV Community

Cover image for Cara Membuat Kode Claude Sendiri
Walse
Walse

Posted on • Originally published at apidog.com

Cara Membuat Kode Claude Sendiri

Intisari

Kebocoran kode sumber Claude Kode mengekspos basis kode TypeScript 512.000 baris pada 31 Maret 2026. Arsitekturnya berbasis pada perulangan while yang memanggil API Claude, menjalankan alat, dan mengumpan balik hasilnya. Anda bisa membangun versi sendiri dengan Python, Anthropic SDK, dan kurang dari 200 baris kode untuk loop inti. Artikel ini memecah setiap komponen dan memperlihatkan cara implementasinya.

Coba Apidog hari ini

Pendahuluan

Pada 31 Maret 2026, Anthropic mengirimkan file source map 59,8 MB dalam paket npm @anthropic-ai/claude-code versi 2.1.88. Source map adalah artefak debugging yang merekonstruksi JavaScript yang diminify ke kode asli. Tool build Anthropic (Bun's bundler) menghasilkan ini secara default, sehingga seluruh basis kode TypeScript bisa dipulihkan.

Dalam hitungan jam, pengembang sudah me-mirror kode tersebut ke puluhan repo GitHub. Komunitas menganalisis setiap modul, dari agent loop hingga fitur tersembunyi seperti “mode penyamaran” dan injeksi alat palsu.

Reaksi terpecah; ada yang mengkritik keamanan Anthropic, ada yang kagum pada arsitekturnya. Tapi respons paling berguna datang dari developer yang bertanya: “Bisakah saya bikin ini sendiri?”

Jawabannya: ya. Pola intinya sederhana. Artikel ini membedah tiap lapisan arsitektur, menjelaskan alasan desain, serta memberi contoh kode siap pakai. Anda juga akan belajar cara menguji interaksi API agen Anda dengan Apidog, sehingga debugging percakapan API multi-giliran jauh lebih mudah dibandingkan pakai curl.

Apa yang Diungkapkan Kebocoran tentang Arsitektur Claude Code

Struktur Basis Kode

Claude Code (internal codename: Tengu) terdiri dari ~1.900 file, terorganisir sebagai berikut:

cli/          - UI Terminal (React + Ink)
tools/        - 40+ implementasi alat
core/         - Prompt sistem, izin, konstanta
assistant/    - Orkestrasi agen
services/     - Panggilan API, kompresi, OAuth, telemetri
Enter fullscreen mode Exit fullscreen mode

CLI-nya adalah aplikasi React yang dirender dengan Ink (React untuk terminal). Menggunakan Yoga (engine flexbox CSS) dan ANSI escape code untuk styling. Setiap tampilan adalah komponen React.

Untuk proyek DIY, Anda tidak perlu UI berbasis React. REPL sederhana sudah cukup.

Loop Agen Utama

Inti Claude Code adalah perulangan while. Anthropic menyebutnya “nO”. Prosesnya:

  1. Kirim pesan ke API Claude (prompt sistem + definisi alat)
  2. Terima respons teks/blok tool_use
  3. Jalankan alat yang diminta (berdasarkan nama)
  4. Tambah hasil alat ke daftar pesan
  5. Jika ada panggilan alat lain, ulang ke langkah 1
  6. Jika hanya teks biasa, kembalikan ke user

Satu “giliran” = satu round trip. Berikut versi Python minimalnya:

import anthropic

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

def agent_loop(system_prompt: str, tools: list, messages: list) -> str:
    """Loop inti agen - ulangi sampai tidak ada 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

Sisa kompleksitas berasal dari alat, sistem izin, manajemen konteks, dan memori.

Membangun Sistem Alat

Kenapa Alat Khusus Lebih Baik dari Bash

Claude Code memakai alat khusus untuk operasi file, bukan sekadar menjalankan perintah bash. Contohnya:

  • Read (bukan cat)
  • Edit (bukan sed)
  • Grep (bukan grep)
  • Glob (bukan find)

Keunggulan alat khusus:

  • Output terstruktur: Lebih mudah di-parse model; output bash tidak terprediksi.
  • Keamanan: BashTool menolak backtick/subshell ($()) untuk cegah injeksi.
  • Efisiensi token: Output alat bisa dipotong/disampling; output bash mudah menghabiskan token.

Daftar Alat Minimal

Untuk agen DIY, cukup 5 alat berikut:

TOOLS = [
    {
        "name": "read_file",
        "description": "Read a file from the filesystem. Returns contents with line numbers.",
        "input_schema": {
            "type": "object",
            "properties": {
                "file_path": {
                    "type": "string",
                    "description": "Absolute path to the file"
                },
                "offset": {
                    "type": "integer",
                    "description": "Line number to start reading from (0-indexed)"
                },
                "limit": {
                    "type": "integer",
                    "description": "Max lines to read. Defaults to 2000."
                }
            },
            "required": ["file_path"]
        }
    },
    {
        "name": "write_file",
        "description": "Write content to a file. Creates the file if it doesn't exist.",
        "input_schema": {
            "type": "object",
            "properties": {
                "file_path": {"type": "string", "description": "Absolute path"},
                "content": {"type": "string", "description": "File content to write"}
            },
            "required": ["file_path", "content"]
        }
    },
    {
        "name": "edit_file",
        "description": "Replace a specific string in a file. The old_string must be unique.",
        "input_schema": {
            "type": "object",
            "properties": {
                "file_path": {"type": "string", "description": "Absolute path"},
                "old_string": {"type": "string", "description": "Text to find"},
                "new_string": {"type": "string", "description": "Replacement text"}
            },
            "required": ["file_path", "old_string", "new_string"]
        }
    },
    {
        "name": "run_command",
        "description": "Execute a shell command and return stdout/stderr.",
        "input_schema": {
            "type": "object",
            "properties": {
                "command": {"type": "string", "description": "Shell command to run"},
                "timeout": {"type": "integer", "description": "Timeout in seconds. Default 120."}
            },
            "required": ["command"]
        }
    },
    {
        "name": "search_code",
        "description": "Search for a regex pattern across files in a directory.",
        "input_schema": {
            "type": "object",
            "properties": {
                "pattern": {"type": "string", "description": "Regex pattern"},
                "path": {"type": "string", "description": "Directory to search"},
                "file_glob": {"type": "string", "description": "File pattern filter, e.g. '*.py'"}
            },
            "required": ["pattern"]
        }
    }
]
Enter fullscreen mode Exit fullscreen mode

Eksekusi dan Pengiriman Alat

Implementasi dispatch alat:

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)

def handle_write_file(params: dict) -> str:
    path = params["file_path"]
    os.makedirs(os.path.dirname(path), exist_ok=True)
    with open(path, "w") as f:
        f.write(params["content"])
    return f"Successfully wrote to {path}"

def handle_edit_file(params: dict) -> str:
    path = params["file_path"]
    with open(path, "r") as f:
        content = f.read()

    old = params["old_string"]
    if content.count(old) == 0:
        return f"Error: '{old[:50]}...' not found in {path}"
    if content.count(old) > 1:
        return f"Error: '{old[:50]}...' matches {content.count(old)} locations. Be more specific."

    new_content = content.replace(old, params["new_string"], 1)
    with open(path, "w") as f:
        f.write(new_content)
    return f"Successfully edited {path}"

def handle_run_command(params: dict) -> str:
    cmd = params["command"]
    timeout = params.get("timeout", 120)

    # Block dangerous patterns
    blocked = ["rm -rf /", "mkfs", "> /dev/"]
    for pattern in blocked:
        if pattern in cmd:
            return f"Error: Blocked dangerous command pattern: {pattern}"

    result = subprocess.run(
        cmd, shell=True, capture_output=True, text=True,
        timeout=timeout, cwd=os.getcwd()
    )

    output = ""
    if result.stdout:
        output += result.stdout
    if result.stderr:
        output += f"\nSTDERR:\n{result.stderr}"
    if not output.strip():
        output = f"Command completed with exit code {result.returncode}"

    if len(output) > 30000:
        output = output[:15000] + "\n\n... [truncated] ...\n\n" + output[-15000:]

    return output

def handle_search_code(params: dict) -> str:
    pattern = params["pattern"]
    path = params.get("path", os.getcwd())
    file_glob = params.get("file_glob", "")

    cmd = ["grep", "-rn", "--include", file_glob, pattern, path] if file_glob else \
          ["grep", "-rn", pattern, path]

    result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)

    if not result.stdout.strip():
        return f"No matches found for pattern: {pattern}"

    lines = result.stdout.strip().split("\n")
    if len(lines) > 50:
        return "\n".join(lines[:50]) + f"\n\n... ({len(lines) - 50} more matches)"
    return result.stdout
Enter fullscreen mode Exit fullscreen mode

Manajemen Konteks: Tantangan Utama

Kenapa Manajemen Konteks Lebih Kritis dari Prompt Engineering

Kode Claude memprioritaskan manajemen konteks dibanding prompt. Kompresor konteks (“wU2”) punya lima strategi.

Untuk DIY, cukup dua:

  • Kompresi otomatis: Ketika percakapan mendekati limit, Claude Code memicu ringkasan (buffer 13.000 token).
  • Injeksi ulang CLAUDE.md: Guidelines proyek diinjeksikan ulang di setiap giliran untuk menjaga konsistensi.

Kompresor Sederhana

def maybe_compact(messages: list, system_prompt: str, max_tokens: int = 180000) -> list:
    """Compact conversation ketika terlalu panjang."""
    # Estimasi: 4 karakter per token
    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

    # Ringkas percakapan dengan model
    summary_response = client.messages.create(
        model=MODEL,
        max_tokens=4096,
        system="Summarize this conversation. Keep all file paths, decisions made, errors encountered, and current task state. Be specific about what was changed and why.",
        messages=messages,
    )

    summary_text = summary_response.content[0].text

    compacted = [
        {"role": "user", "content": f"[Conversation summary]\n{summary_text}"},
        {"role": "assistant", "content": "I have the context from our previous conversation. What should I work on next?"},
    ]

    compacted.extend(messages[-4:])

    return compacted
Enter fullscreen mode Exit fullscreen mode

Injeksi Ulang Konteks Proyek

def build_system_prompt(project_dir: str) -> str:
    """Bangun system prompt dengan re-injeksi konteks proyek."""
    base_prompt = """You are a coding assistant that helps with software engineering tasks.
You have access to tools for reading, writing, editing files, running commands, and searching code.
Always read files before modifying them. Prefer edit_file over write_file for existing files.
Keep responses concise. Focus on the code, not explanations."""

    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# Project guidelines\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# Repository guidelines\n{root_context}"

    return base_prompt
Enter fullscreen mode Exit fullscreen mode

Sistem Memori Tiga Lapis

Lapisan 1: MEMORY.md (selalu dimuat)

Indeks ringan, satu baris per entri (<150 karakter), max 200 baris/25KB. Contoh:

- [User preferences](memory/user-prefs.md) - prefers TypeScript, uses Vim keybindings
- [API conventions](memory/api-conventions.md) - REST with JSON:API spec, snake_case
- [Deploy process](memory/deploy.md) - uses GitHub Actions, deploys to AWS EKS
Enter fullscreen mode Exit fullscreen mode

Lapisan 2: File Topik (on-demand)

File detail yang dimuat jika relevan.

Lapisan 3: Transkrip Sesi (dicari, tidak dibaca penuh)

Log sesi lengkap, hanya dicari untuk ID tertentu, tidak pernah dimuat penuh.

Sistem Memori Minimal

import json

MEMORY_DIR = ".agent/memory"

def load_memory_index() -> str:
    """Load memory index untuk system prompt."""
    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):
    """Simpan entri memori dan update index."""
    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

Tambahkan alat save_memory ke daftar tools agar agen bisa menyimpan pengetahuan antar sesi.

Sistem Izin Sederhana

Claude Code membedakan lima mode izin: default, auto, bypass, yolo, deny. Setiap alat diklasifikasi RENDAH, SEDANG, TINGGI.

Untuk DIY, cukup tiga level:

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:
    """Cek izin user untuk tool call."""
    risk = RISK_LEVELS.get(tool_name, "high")

    if risk == "low" and auto_approve_low:
        return True

    print(f"\n--- Permission check ({risk.upper()} risk) ---")
    print(f"Tool: {tool_name}")
    for key, value in params.items():
        display = str(value)[:200]
        print(f"  {key}: {display}")

    response = input("Allow? [y/n/always]: ").strip().lower()
    if response == "always":
        RISK_LEVELS[tool_name] = "low"
        return True
    return response == "y"
Enter fullscreen mode Exit fullscreen mode

Menguji Panggilan API Agen dengan Apidog

Membangun agen pengode berarti ratusan panggilan API ke Claude. Debugging multi-turn dengan log mentah sangat merepotkan.

Debugging API multi-giliran dengan Apidog

Apidog membantu memeriksa dan menguji request API agen Anda. Cara penggunaannya:

Menangkap & Memutar Ulang Request API

  1. Buka Apidog, buat project baru untuk agen Anda
  2. Impor endpoint API Anthropic: POST https://api.anthropic.com/v1/messages
  3. Siapkan body request dengan sistem prompt, array tools, dan pesan Anda
  4. Uji giliran individual dengan replay permintaan dan edit parameter

Ini memungkinkan Anda mengisolasi alat tertentu tanpa menjalankan seluruh loop. Jika model mengembalikan panggilan alat tak terduga atau parameter aneh, modifikasi request di Apidog dan replay.

Debugging Percakapan Multi-Giliran

Variabel lingkungan Apidog bisa menyimpan snapshot percakapan:

  • Simpan array messages setelah tiap giliran
  • Replay dari titik mana pun
  • Bandingkan hasil alat antar eksekusi

Validasi Skema Alat

Skema JSON alat Anda menentukan perilaku model. Skema salah bentuk = alat gagal tanpa pesan. Import skema ke Apidog dan gunakan validator Skema JSON-nya untuk menangkap error sebelum request dikirim.

Menyatukan Segalanya: Contoh REPL Lengkap

Berikut REPL coding agent sederhana:

#!/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# Memory\n{memory}"

    messages = []
    print("Coding agent ready. Type 'quit' to exit.\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})

        # Compact jika perlu
        messages = maybe_compact(messages, system_prompt)

        # Re-inject context setiap giliran
        current_system = build_system_prompt(PROJECT_DIR)
        memory = load_memory_index()
        if memory:
            current_system += f"\n\n# Memory\n{memory}"

        result = agent_loop(current_system, TOOLS, messages)
        print(f"\n{result}\n")

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

Dengan kurang dari 300 baris Python, Anda sudah punya agen pengodean: baca/edit file, jalankan perintah, cari kode, kelola konteks, dan simpan memori.

Apa yang Bisa Ditambahkan Selanjutnya

  • Sub-agen untuk pekerjaan paralel: Munculkan agent_loop() baru untuk task independen, lalu gabungkan hasilnya.
  • Deduplikasi pembacaan file: Track file yang sudah dibaca, skip jika tidak berubah.
  • Pemotongan output alat: Untuk output besar, potong dan beri tahu model jumlah baris yang di-truncate.
  • Kompresi otomatis dengan injeksi ulang file: Setelah kompresi, injeksi ulang isi file yang diakses (maks 5.000 token/file).

Pelajaran dari Kebocoran

  • Loop inti agen sangat sederhana (30 baris)
  • Alat khusus jauh lebih efektif dari bash
  • Memori perlu sistem berlapis
  • Manajemen konteks adalah kunci
  • Produk utamanya adalah harness, bukan model

Jika ingin menguji dan debug interaksi API multi-giliran, skema kompleks, dan validasi respons, coba Apidog secara gratis. Debugging API jadi mudah, Anda bisa fokus pada logika agen.

FAQ

Bisakah saya secara legal menggunakan pola dari kebocoran Kode Claude?

Kebocoran ini mengungkap pola arsitektur, bukan algoritma proprietary. Loop while dengan dispatch alat adalah pola standar (lihat dokumentasi API Anthropic). Jangan salin kode verbatim, tapi reimplementasi arsitektur sendiri adalah praktik wajar.

Model apa yang cocok untuk agen pengode DIY?

Claude Sonnet 4.6 seimbang (kecepatan & kemampuan). Untuk tugas kompleks, Opus 4.6 lebih baik (tapi mahal/lambat). Untuk editing/cari file sederhana, Haiku 4.5 cukup dan biaya murah.

Berapa biaya menjalankan agen pengode sendiri?

Sesi 30-50 giliran (Claude Sonnet 4.6) sekitar $1-5. Biaya utama dari ukuran konteks; kompresi agresif menjaga biaya rendah. Claude Code memicu kompresi di 92% penggunaan konteks.

Mengapa Claude Code memakai React untuk aplikasi terminal?

Ink (React untuk terminal) memungkinkan reuse komponen dan state management React. Untuk DIY, REPL sederhana (input() / print()) sudah cukup.

Fitur apa yang paling penting ditambahkan setelah loop inti?

Sistem izin. Tanpa izin, model bisa overwrite file/jalankan perintah arbitrer. Konfirmasi sederhana sebelum tulis/eksekusi mencegah kerusakan.

Bagaimana Claude Code menangani error dari tool call?

Error alat dikembalikan sebagai konten teks di pesan tool_result. Model memutuskan retry/cara lain/tanya user. Tidak ada penanganan error khusus; model yang memutuskan.

Bisakah pola ini dipakai di model lain selain Claude?

Bisa. Pattern function calling didukung GPT-4, Gemini, Llama, dll. Format API perlu adaptasi, tapi arsitektur loop/alat/memori tetap.

Bagaimana cara mencegah agen menjalankan perintah berbahaya?

Gunakan daftar blokir (rm -rf /, mkfs, dll) dan konfirmasi eksplisit pada semua run_command. Klasifikasikan risiko (RENDAH, SEDANG, TINGGI), lalu blokir/tanya user sesuai level. Terapkan pola yang sama untuk alat Anda.

Top comments (0)