Capítulo 1: Introducción al Claude Code SDK

Por: Artiko
claudeclaude-code-sdkintroduccionagentesarquitectura

Capítulo 1: Introducción al Claude Code SDK


1. ¿Qué es el Claude Code SDK?

El Claude Code SDK es una biblioteca que expone el motor interno de Claude Code como una API programática. Te permite construir agentes de IA autónomos en Python o TypeScript que pueden leer archivos, escribir código, ejecutar comandos de terminal, buscar en repositorios y orquestar flujos de trabajo complejos, todo con el mismo nivel de inteligencia y capacidad que tiene Claude Code el CLI.

1.1 La familia de productos de Anthropic para desarrolladores

Anthropic ofrece tres formas distintas de integrar a Claude en tus aplicaciones, y entender las diferencias es fundamental para elegir la herramienta correcta:

Claude Code CLI: Herramienta de línea de comandos para desarrolladores. Se ejecuta en tu terminal, tiene acceso a tu sistema de archivos y está diseñada para sesiones interactivas de programación. No tiene API pública; es una herramienta para humanos.

Anthropic Client SDK (@anthropic-ai/sdk en npm, anthropic en pip): Acceso directo a la API de mensajes de Anthropic. Te da control total sobre los prompts, pero tú eres responsable de implementar el loop de herramientas, manejar el estado de la conversación, parsear respuestas y ejecutar las acciones que Claude solicita.

Claude Code SDK (@anthropic-ai/claude-code-sdk en npm, claude-code-sdk en pip): El tema de este tutorial. Encapsula a Claude Code completo como una biblioteca. Maneja automáticamente el loop agentic, las herramientas integradas (filesystem, bash, búsqueda), la gestión del contexto y la ejecución de acciones.

1.2 Diagrama arquitectural: los tres niveles

flowchart TD
    subgraph L3["NIVEL 3 — Claude Code CLI (Interactivo)"]
        Human["👤 Desarrollador humano"] -->|escribe en terminal| CLI["Claude Code CLI"]
        CLI -->|ejecuta acciones| FS1["Sistema de archivos / Terminal"]
    end

    subgraph L2["NIVEL 2 — Claude Code SDK (Programático)"]
        App["Tu aplicación Python/TS"] -->|query()| SDK["Claude Code SDK"]
        SDK -->|spawns subprocess| CLIBIN["Claude Code binary"]
        CLIBIN -->|ejecuta acciones| FS2["Sistema de archivos / Terminal"]
        CLIBIN -->|stream mensajes| SDK
        SDK -->|AsyncIterator| App
    end

    subgraph L1["NIVEL 1 — Anthropic Client SDK (API pura)"]
        App2["Tu aplicación"] -->|messages.create()| AAPI["Anthropic Messages API"]
        AAPI -->|response con tool_use| App2
        App2 -->|TÚ implementas el loop| App2
    end

    style L3 fill:#e8f5e9,stroke:#4caf50
    style L2 fill:#e3f2fd,stroke:#2196f3
    style L1 fill:#fff3e0,stroke:#ff9800

1.3 Historia y evolución

El Claude Code SDK nació como respuesta a una necesidad clara: los desarrolladores que usaban Claude Code querían automatizar flujos que antes requerían interacción manual. La primera versión pública apareció en 2024 como parte del ecosistema de herramientas de Anthropic.

La evolución ha sido rápida:

Los repositorios oficiales son:

1.4 Tabla comparativa detallada: CLI vs SDK vs Client SDK

CriterioClaude Code CLIClaude Code SDKAnthropic Client SDK
InterfazTerminal interactivaAPI programáticaAPI HTTP/REST
Loop agenticAutomáticoAutomáticoManual (tú lo implementas)
Herramientas built-inSí (Read, Write, Bash, etc.)Sí (mismas)No (defines tus propias)
AutomatizableNo (requiere humano)
Control granular del flujoBajoMedioAlto
Sesiones persistentesSí (interactivo)Sí (programático)Manual
Acceso al filesystemNo (solo texto)
Ejecución de comandosNo (solo texto)
Soporte MCPNo nativo
Hooks de herramientasNo (es CLI)N/A
Costo en tokensPor usoPor usoPor uso
Lenguajes clienteN/APython, TypeScriptPython, TS, Java, Go, Ruby
Caso principalDesarrollo asistidoAutomatizaciónChatbots, APIs
Complejidad de setupBajaMediaMedia
Curva de aprendizajeBajaMediaAlta (para agentes complejos)

2. Arquitectura Interna del SDK

Entender cómo funciona el SDK internamente te ayuda a usarlo mejor, debuggear problemas y optimizar el rendimiento de tus agentes.

2.1 El loop agentic interno

El SDK implementa un patrón de Observe-Plan-Act-Verify. Claude no simplemente ejecuta una acción y responde; recorre un ciclo donde observa el estado, planifica pasos, ejecuta acciones y verifica los resultados hasta que la tarea está completa o se alcanza el límite de turnos.

stateDiagram-v2
    [*] --> Observe : query(prompt)
    Observe --> Plan : Lee el contexto actual
    Plan --> Act : Decide herramienta a usar
    Act --> Verify : Ejecuta herramienta
    Verify --> Observe : Resultado no satisfactorio
    Verify --> Done : Tarea completada
    Done --> [*] : Emite ResultMessage

    note right of Observe
        Lee archivos relevantes,
        analiza el estado del proyecto,
        carga contexto de CLAUDE.md
    end note

    note right of Act
        Read, Write, Edit, Bash,
        Glob, Grep, WebSearch,
        herramientas MCP custom
    end note

2.2 Cómo el SDK spawna Claude Code

Cuando llamas a query(), el SDK:

  1. Serializa las opciones (ClaudeCodeOptions) en argumentos de línea de comandos
  2. Spawns el binario claude como un subproceso (usando subprocess en Python, child_process en Node)
  3. Pasa el prompt via stdin o argumentos
  4. Lee el stream de salida en formato JSON-lines desde stdout
  5. Deserializa cada línea en un tipo de mensaje (AssistantMessage, ResultMessage, etc.)
  6. Yield/emite cada mensaje al consumidor
sequenceDiagram
    participant App as Tu Aplicación
    participant SDK as Claude Code SDK
    participant Proc as Proceso Claude
    participant API as Anthropic API
    participant FS as Sistema de Archivos

    App->>SDK: query(prompt, options)
    SDK->>Proc: spawn("claude --print --output-format stream-json")
    Proc->>API: POST /messages (con herramientas disponibles)
    API-->>Proc: AssistantMessage (puede incluir tool_use)
    Proc->>FS: Ejecuta herramienta (ej: Read file)
    FS-->>Proc: Contenido del archivo
    Proc->>API: POST /messages (con tool_result)
    API-->>Proc: Siguiente AssistantMessage
    Proc-->>SDK: Stream JSON lines
    SDK-->>App: yield AssistantMessage
    SDK-->>App: yield ResultMessage (final)

2.3 Tipos de mensajes en el stream

El SDK emite tres tipos principales de mensajes:

AssistantMessage: Contiene el texto que Claude “dice” mientras trabaja. Puede incluir el razonamiento, explicaciones y los resultados de las herramientas.

SystemMessage / mensajes de init: Mensajes de sistema que describen el entorno, las herramientas disponibles y el contexto inicial.

ResultMessage: El mensaje final cuando el agente completa la tarea. Contiene el resultado final, estadísticas de uso de tokens, costo estimado y si la tarea fue exitosa.

# Tipos de mensajes en Python
from claude_code_sdk import query, ClaudeCodeOptions

async for message in query(prompt="Lista los archivos del proyecto"):
    # Tipo 1: AssistantMessage - Claude hablando/razonando
    if hasattr(message, 'content'):
        for block in message.content:
            if block.type == 'text':
                print(f"[Assistant]: {block.text}")
            elif block.type == 'tool_use':
                print(f"[Tool call]: {block.name}({block.input})")

    # Tipo 2: ResultMessage - resultado final
    if hasattr(message, 'result'):
        print(f"[Result]: {message.result}")
        print(f"[Tokens]: {message.usage}")
        print(f"[Cost]: ${message.cost_usd}")
        print(f"[Turns]: {message.num_turns}")
// Tipos de mensajes en TypeScript
import { query, ClaudeCodeOptions } from "@anthropic-ai/claude-code-sdk";

for await (const message of query({ prompt: "Lista los archivos del proyecto" })) {
  // AssistantMessage
  if (message.type === "assistant") {
    for (const block of message.message.content) {
      if (block.type === "text") {
        console.log(`[Assistant]: ${block.text}`);
      } else if (block.type === "tool_use") {
        console.log(`[Tool call]: ${block.name}`, block.input);
      }
    }
  }

  // ResultMessage
  if (message.type === "result") {
    console.log(`[Result]: ${message.result}`);
    console.log(`[Cost]: $${message.cost_usd}`);
    console.log(`[Turns]: ${message.num_turns}`);
  }
}

2.4 Componentes clave del SDK

query(): La función principal del SDK. Acepta un prompt y opciones, retorna un AsyncIterator de mensajes. Es el punto de entrada para prácticamente todo lo que haces con el SDK.

ClaudeCodeOptions: Dataclass/interfaz de configuración. Controla el directorio de trabajo, número máximo de turnos, modo de permisos, system prompt personalizado, selección de modelo y configuración de MCP.

create_sdk_mcp_server(): Función avanzada que te permite exponer tus herramientas Python/TS como un servidor MCP que Claude puede usar. Permite extender las capacidades del agente con herramientas completamente custom.

HookMatcher: Clase para definir hooks que interceptan las llamadas a herramientas. Permite ejecutar código antes o después de que Claude use una herramienta.

AgentDefinition (experimental): Define agentes reutilizables con configuración predeterminada encapsulada.


3. Comparación Profunda: Claude Code SDK vs Anthropic Client SDK

Esta es una de las confusiones más comunes. Los dos SDKs tienen nombres similares y ambos vienen de Anthropic, pero son herramientas fundamentalmente diferentes.

3.1 El problema que resuelve cada uno

Anthropic Client SDK (anthropic en pip) resuelve el problema de acceder a la API de mensajes de Anthropic. Te da una interfaz limpia para crear conversaciones, pero si quieres que Claude use herramientas (como leer archivos), debes:

  1. Definir los esquemas JSON de cada herramienta
  2. Llamar a la API
  3. Detectar cuando Claude quiere usar una herramienta (stop_reason === "tool_use")
  4. Ejecutar la herramienta tú mismo en tu código
  5. Pasar el resultado de vuelta a Claude
  6. Repetir hasta que Claude diga que terminó

Esto es el tool loop manual y puede ser 50-200 líneas de código para tareas simples.

Claude Code SDK (claude-code-sdk en pip) resuelve el problema de ejecutar agentes autónomos que manipulan código y sistemas. El tool loop es automático, las herramientas de filesystem y bash están integradas, y tú solo recibes el stream de resultados.

3.2 Tabla de capacidades

Capacidadanthropic (Client)claude-code-sdk (Agent)
Chat básicoSí (pero sobrediseñado)
Streaming de texto
Tool calling manualSí (defines tú las tools)N/A (automático)
Leer archivos del OSNo (solo si defines una tool)Sí (built-in)
Escribir archivos del OSNo (solo si defines una tool)Sí (built-in)
Ejecutar comandos bashNo (solo si defines una tool)Sí (built-in)
Buscar en código (Grep/Glob)No
Sesiones persistentesManual (guardas el historial)Sí (nativo)
Subagentes paralelosManualSí (nativo)
MCP serversNo
Hooks pre/post toolN/A
Control total del promptParcial
Fine-tuningNo (aún)No
Acceso a modelos no-ClaudeNoNo
Uso en browserSí (con fetch)No (requiere Node/Python proceso)
Serverless (Lambda, etc.)Limitado (requiere binario claude)
Velocidad de setupRápidoRequiere instalar Claude Code CLI

3.3 Código lado a lado: la misma tarea con cada SDK

Tarea: “Leer el archivo package.json y reportar las dependencias”

Con Anthropic Client SDK (manual):

import anthropic
import json

client = anthropic.Anthropic()

# Defines la herramienta manualmente
tools = [{
    "name": "read_file",
    "description": "Lee el contenido de un archivo",
    "input_schema": {
        "type": "object",
        "properties": {
            "path": {"type": "string", "description": "Ruta del archivo"}
        },
        "required": ["path"]
    }
}]

messages = [{"role": "user", "content": "Lee package.json y reporta las dependencias"}]

# Primer llamado
response = client.messages.create(
    model="claude-opus-4-5",
    max_tokens=1024,
    tools=tools,
    messages=messages
)

# Loop de herramientas MANUAL
while response.stop_reason == "tool_use":
    # Extraer tool_use block
    tool_use_block = next(b for b in response.content if b.type == "tool_use")

    # Ejecutar la herramienta TÚ MISMO
    if tool_use_block.name == "read_file":
        try:
            with open(tool_use_block.input["path"], "r") as f:
                file_content = f.read()
            tool_result = {"type": "tool_result", "tool_use_id": tool_use_block.id, "content": file_content}
        except FileNotFoundError:
            tool_result = {"type": "tool_result", "tool_use_id": tool_use_block.id, "content": "Archivo no encontrado", "is_error": True}

    # Agregar respuesta al historial
    messages.append({"role": "assistant", "content": response.content})
    messages.append({"role": "user", "content": [tool_result]})

    # Nueva llamada
    response = client.messages.create(
        model="claude-opus-4-5",
        max_tokens=1024,
        tools=tools,
        messages=messages
    )

# Extraer resultado final
final_text = next(b.text for b in response.content if b.type == "text")
print(final_text)
# Son ~50 líneas de código para una tarea simple

Con Claude Code SDK (automático):

import asyncio
from claude_code_sdk import query, ClaudeCodeOptions

async def main():
    options = ClaudeCodeOptions(cwd="/mi/proyecto")
    async for message in query(
        prompt="Lee package.json y reporta las dependencias",
        options=options
    ):
        if hasattr(message, 'result'):
            print(message.result)

asyncio.run(main())
# Son ~10 líneas de código para la misma tarea

La diferencia es dramática cuando la tarea involucra múltiples herramientas, decisiones condicionales o loops de verificación.


4. Casos de Uso Ideales

4.1 Automatización de revisiones de código (Code Review)

Uno de los casos más poderosos: un agente que revisa pull requests automáticamente.

import asyncio
from claude_code_sdk import query, ClaudeCodeOptions
from pathlib import Path

async def revisar_pull_request(repo_path: str, archivos_cambiados: list[str]) -> dict:
    """
    Agente que revisa un conjunto de archivos cambiados y genera un reporte.
    """
    archivos_str = "\n".join(f"- {f}" for f in archivos_cambiados)

    prompt = f"""Eres un revisor de código experto. Revisa los siguientes archivos que fueron
modificados en este pull request:

{archivos_str}

Para cada archivo:
1. Lee su contenido actual
2. Identifica problemas de: seguridad, performance, mantenibilidad, convenciones
3. Sugiere mejoras concretas con ejemplos de código cuando sea relevante

Genera un reporte estructurado en formato Markdown con secciones por archivo.
Termina con un resumen ejecutivo y una recomendación: APROBAR, APROBAR CON CAMBIOS MENORES, o RECHAZAR."""

    options = ClaudeCodeOptions(
        cwd=repo_path,
        max_turns=20,
        permission_mode="bypassPermissions"  # solo lectura, pero necesitamos bypassear para leer
    )

    resultado_final = ""
    async for message in query(prompt=prompt, options=options):
        if hasattr(message, 'result') and message.result:
            resultado_final = message.result

    return {
        "reporte": resultado_final,
        "archivos_revisados": archivos_cambiados
    }

# Uso
asyncio.run(revisar_pull_request(
    repo_path="/home/dev/mi-proyecto",
    archivos_cambiados=["src/auth/login.py", "src/api/users.py", "tests/test_auth.py"]
))
import { query, ClaudeCodeOptions } from "@anthropic-ai/claude-code-sdk";

async function revisarPullRequest(
  repoPath: string,
  archivosModificados: string[]
): Promise<string> {
  const archivosStr = archivosModificados.map(f => `- ${f}`).join("\n");

  const prompt = `Eres un revisor de código experto. Revisa los siguientes archivos:

${archivosStr}

Para cada archivo: lee el contenido, identifica problemas de seguridad, performance y mantenibilidad.
Genera un reporte en Markdown con recomendación final: APROBAR, APROBAR CON CAMBIOS MENORES, o RECHAZAR.`;

  const options: ClaudeCodeOptions = {
    cwd: repoPath,
    maxTurns: 20,
    permissionMode: "bypassPermissions",
  };

  let resultado = "";
  for await (const message of query({ prompt, options })) {
    if (message.type === "result" && message.result) {
      resultado = message.result;
    }
  }

  return resultado;
}

4.2 Pipeline de CI/CD con IA

import asyncio
import subprocess
from claude_code_sdk import query, ClaudeCodeOptions

async def pipeline_ci_con_ia(repo_path: str):
    """
    Pipeline que ejecuta tests y, si fallan, usa IA para intentar corregir los errores.
    """
    # Paso 1: Ejecutar tests
    result = subprocess.run(
        ["python", "-m", "pytest", "--tb=short", "-q"],
        cwd=repo_path,
        capture_output=True,
        text=True
    )

    if result.returncode == 0:
        print("✓ Tests pasaron correctamente")
        return True

    # Paso 2: Si fallan, usar el agente para corregir
    output_tests = result.stdout + result.stderr
    print(f"✗ Tests fallaron. Intentando corrección automática...")

    prompt = f"""Los siguientes tests están fallando en este proyecto Python:

{output_tests}


Analiza el error, encuentra la causa raíz leyendo los archivos relevantes y corrígela.
Asegúrate de que los cambios sean mínimos y no rompan otras funcionalidades.
Después de corregir, verifica que el fix tiene sentido lógico."""

    options = ClaudeCodeOptions(
        cwd=repo_path,
        max_turns=15,
        permission_mode="acceptEdits"
    )

    async for message in query(prompt=prompt, options=options):
        if hasattr(message, 'content'):
            for block in message.content:
                if hasattr(block, 'text'):
                    print(f"Agente: {block.text[:200]}...")
        if hasattr(message, 'result'):
            print(f"Resultado: {message.result}")

    # Paso 3: Verificar si se corrigió
    result2 = subprocess.run(
        ["python", "-m", "pytest", "--tb=short", "-q"],
        cwd=repo_path,
        capture_output=True,
        text=True
    )

    if result2.returncode == 0:
        print("✓ Agente corrigió los tests exitosamente")
        return True
    else:
        print("✗ El agente no pudo corregir automáticamente. Requiere intervención manual.")
        return False

asyncio.run(pipeline_ci_con_ia("/home/dev/mi-proyecto"))

4.3 Generador automático de documentación

import { query, ClaudeCodeOptions } from "@anthropic-ai/claude-code-sdk";
import { writeFileSync } from "fs";
import { join } from "path";

async function generarDocumentacion(proyectoPath: string): Promise<void> {
  const prompt = `Eres un experto en documentación técnica. Analiza este proyecto y genera documentación completa.

Tareas:
1. Lee todos los archivos TypeScript/JavaScript en src/
2. Para cada módulo público, documenta: propósito, parámetros, retorno, ejemplos de uso
3. Genera un README.md actualizado con:
   - Descripción del proyecto
   - Instalación
   - API Reference con todos los métodos públicos
   - Ejemplos de uso completos
   - Arquitectura (en formato Mermaid)
4. Escribe el README.md en la raíz del proyecto

Asegúrate de que el README sea técnicamente preciso y útil para un desarrollador que llega al proyecto por primera vez.`;

  const options: ClaudeCodeOptions = {
    cwd: proyectoPath,
    maxTurns: 30,
    permissionMode: "acceptEdits",
    systemPrompt: "Escribe documentación técnica clara, concisa y con ejemplos reales. Usa Markdown con formato excelente.",
  };

  console.log("Generando documentación...");

  for await (const message of query({ prompt, options })) {
    if (message.type === "assistant") {
      for (const block of message.message.content) {
        if (block.type === "tool_use") {
          console.log(`  → ${block.name}: ${JSON.stringify(block.input).slice(0, 80)}...`);
        }
      }
    }
    if (message.type === "result") {
      console.log("\nDocumentación generada exitosamente.");
      console.log(`Turnos utilizados: ${message.num_turns}`);
      console.log(`Costo estimado: $${message.cost_usd?.toFixed(4)}`);
    }
  }
}

4.4 Agente de migración de código

import asyncio
from claude_code_sdk import query, ClaudeCodeOptions

async def migrar_a_nueva_api(
    proyecto_path: str,
    api_antigua: str,
    api_nueva: str,
    guia_migracion: str
) -> None:
    """
    Migra un proyecto de una versión de API a otra automáticamente.
    Ejemplo: migrar de React 17 a React 18, o de Express 4 a Express 5.
    """
    prompt = f"""Eres un experto en migraciones de código. Tu tarea es migrar este proyecto
de {api_antigua} a {api_nueva}.

Guía de migración a seguir:
{guia_migracion}

Proceso:
1. Primero, usa Grep/Glob para identificar todos los archivos que usan {api_antigua}
2. Crea un plan de migración listando cada cambio necesario
3. Aplica los cambios archivo por archivo
4. Después de cada cambio, verifica que no hay errores de sintaxis obvios
5. Al finalizar, crea un archivo MIGRATION_LOG.md con todos los cambios realizados

Sé conservador: si no estás seguro de un cambio, documéntalo en el log pero no lo hagas.
Prioriza no romper funcionalidad existente."""

    options = ClaudeCodeOptions(
        cwd=proyecto_path,
        max_turns=50,
        permission_mode="acceptEdits",
        system_prompt="Eres un experto en migraciones. Sé meticuloso y documenta todo."
    )

    print(f"Iniciando migración: {api_antigua}{api_nueva}")
    archivos_modificados = []

    async for message in query(prompt=prompt, options=options):
        if hasattr(message, 'content'):
            for block in message.content:
                if hasattr(block, 'name') and block.name in ('Write', 'Edit', 'MultiEdit'):
                    archivo = block.input.get('file_path', block.input.get('path', 'desconocido'))
                    archivos_modificados.append(archivo)
                    print(f"  Modificando: {archivo}")

        if hasattr(message, 'result'):
            print(f"\nMigración completada.")
            print(f"Archivos modificados: {len(set(archivos_modificados))}")

asyncio.run(migrar_a_nueva_api(
    proyecto_path="/home/dev/mi-react-app",
    api_antigua="React 17",
    api_nueva="React 18",
    guia_migracion="Reemplaza ReactDOM.render() con createRoot(). Actualiza las APIs de concurrent mode..."
))

4.5 Asistente de soporte técnico embebido

import { query, ClaudeCodeOptions } from "@anthropic-ai/claude-code-sdk";
import * as readline from "readline";

class AsistenteSoporte {
  private proyectoPath: string;
  private sessionId?: string;

  constructor(proyectoPath: string) {
    this.proyectoPath = proyectoPath;
  }

  async responderPregunta(pregunta: string): Promise<string> {
    const options: ClaudeCodeOptions = {
      cwd: this.proyectoPath,
      maxTurns: 10,
      permissionMode: "bypassPermissions", // Solo lectura para soporte
      systemPrompt: `Eres un asistente de soporte técnico experto en este proyecto.
Cuando el usuario tenga un problema:
1. Lee los logs y archivos de configuración relevantes
2. Identifica la causa del problema
3. Proporciona una solución clara y paso a paso
4. Si necesitas ejemplos de código, muéstralos completos`,
    };

    let respuesta = "";

    for await (const message of query({ prompt: pregunta, options })) {
      if (message.type === "result" && message.result) {
        respuesta = message.result;
      }
    }

    return respuesta;
  }
}

// CLI interactivo del asistente
async function iniciarAsistente() {
  const asistente = new AsistenteSoporte(process.cwd());
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });

  console.log("Asistente de Soporte Técnico activo. Escribe 'salir' para terminar.");

  const preguntar = () => {
    rl.question("\nTu pregunta: ", async (input) => {
      if (input.toLowerCase() === "salir") {
        rl.close();
        return;
      }
      console.log("\nAnalizando...\n");
      const respuesta = await asistente.responderPregunta(input);
      console.log("Asistente:", respuesta);
      preguntar();
    });
  };

  preguntar();
}

iniciarAsistente();

5. Limitaciones y Consideraciones de Producción

Antes de usar el SDK en producción, es crucial entender sus limitaciones y riesgos.

5.1 Costos por token: estimaciones prácticas

El Claude Code SDK llama a la API de Anthropic en cada turno del loop. Los costos pueden escalar rápidamente:

ModeloInput (por MTok)Output (por MTok)Tarea típicaCosto estimado
claude-opus-4-5$15$75Refactor complejo (20 turnos)$2-8
claude-sonnet-4-5$3$15Code review (10 turnos)$0.30-1.50
claude-haiku-3-5$0.25$1.25Tarea simple (5 turnos)$0.01-0.05

Estrategias para controlar costos:

from claude_code_sdk import query, ClaudeCodeOptions

# 1. Limitar el número de turnos
options = ClaudeCodeOptions(
    max_turns=10,  # Máximo 10 iteraciones
    model="claude-haiku-3-5"  # Modelo más económico para tareas simples
)

# 2. Monitorear el costo en tiempo real
costo_total = 0.0

async for message in query(prompt="...", options=options):
    if hasattr(message, 'cost_usd') and message.cost_usd:
        costo_total += message.cost_usd
        if costo_total > 1.00:  # Límite de $1 USD
            print("Límite de costo alcanzado")
            break

# 3. Ser específico en el prompt para reducir turnos
# MAL: "Arregla los problemas del proyecto"
# BIEN: "En src/auth/login.py línea 45, el password no se hashea antes de guardarse. Agrega bcrypt."

5.2 Timeouts y límites de contexto

El SDK no tiene un timeout interno por defecto. Para tareas de larga duración:

import asyncio
from claude_code_sdk import query, ClaudeCodeOptions

async def query_con_timeout(prompt: str, timeout_segundos: int = 120):
    """Wrapper que agrega timeout a query()"""
    try:
        async with asyncio.timeout(timeout_segundos):
            resultados = []
            async for message in query(prompt=prompt):
                resultados.append(message)
            return resultados
    except asyncio.TimeoutError:
        raise TimeoutError(f"El agente excedió el límite de {timeout_segundos} segundos")

El límite de contexto de Claude (200K tokens para Opus/Sonnet) puede alcanzarse en proyectos muy grandes. Estrategias:

  1. Usa system_prompt para limitar el alcance al agente
  2. Ejecuta el agente desde el subdirectorio específico (cwd)
  3. Divide tareas grandes en subtareas con múltiples llamadas a query()

5.3 Seguridad: el SDK ejecuta código real

Este es el punto más crítico. El Claude Code SDK tiene acceso completo a tu sistema de archivos y puede ejecutar comandos bash.

flowchart TD
    A["Usuario provee prompt"] --> B{"¿El prompt es confiable?"}
    B -->|Sí, fuente interna| C["Usar bypassPermissions con precaución"]
    B -->|No, fuente externa/usuario| D["Usar defaultMode o sandboxing"]
    C --> E["Monitorear con hooks"]
    D --> F["Revisar acciones con acceptEdits"]
    F --> G{"¿Acción peligrosa?"}
    G -->|Sí: rm -rf, curl pipe bash| H["Rechazar con hook"]
    G -->|No| I["Permitir"]
    H --> J["Log y alerta"]
    I --> K["Continuar"]

Reglas de seguridad para producción:

from claude_code_sdk import query, ClaudeCodeOptions

# NUNCA hagas esto con input de usuarios externos:
# options = ClaudeCodeOptions(permission_mode="bypassPermissions")

# SIEMPRE usa el modo de permisos apropiado:
options = ClaudeCodeOptions(
    permission_mode="default",  # Pide confirmación para acciones destructivas
    cwd="/mi/proyecto/sandboxed",  # Restringe a un directorio
    system_prompt="""
REGLAS DE SEGURIDAD:
- SOLO trabaja dentro del directorio actual
- NUNCA ejecutes comandos que modifiquen el sistema (apt, brew, pip con sudo)
- NUNCA hagas curl, wget u otras descargas
- NUNCA accedas a archivos fuera del directorio actual
- NUNCA leas archivos .env, .pem, credenciales o secrets
"""
)

5.4 Diagrama: cuándo NO usar el SDK

flowchart TD
    A["¿Quieres usar Claude Code SDK?"] --> B{"¿La tarea es una conversación simple?"}
    B -->|Sí| C["Usa Anthropic Client SDK o Claude API directamente"]
    B -->|No| D{"¿Necesitas ejecutar en browser/serverless?"}
    D -->|Sí, sin proceso Node/Python| E["Usa Anthropic Client SDK con tool calling manual"]
    D -->|No| F{"¿El input viene de usuarios externos no confiables?"}
    F -->|Sí, sin sandboxing| G["PELIGROSO: Necesitas sandboxing o usar Client SDK"]
    F -->|No, o tienes sandboxing| H{"¿Tienes control del sistema donde se ejecuta?"}
    H -->|No (shared hosting, etc.)| I["No uses el SDK: requiere binario Claude Code instalado"]
    H -->|Sí| J["Claude Code SDK es apropiado ✓"]

6. Primer Ejemplo Completo Funcional

Vamos a construir un agente real que analiza un proyecto de software y genera un reporte técnico completo. Este ejemplo muestra las capacidades fundamentales del SDK.

6.1 El agente en Python

#!/usr/bin/env python3
"""
analizador_proyecto.py

Agente que analiza un proyecto de software y genera un reporte técnico.
Uso: python analizador_proyecto.py /ruta/al/proyecto
"""

import asyncio
import sys
import json
from datetime import datetime
from pathlib import Path
from claude_code_sdk import query, ClaudeCodeOptions


PROMPT_ANALISIS = """Eres un arquitecto de software senior. Tu tarea es analizar este proyecto
y generar un reporte técnico detallado. El reporte debe ser útil para:
- Nuevos desarrolladores que llegan al proyecto
- Tech leads que necesitan evaluar la salud del código
- Gestores que necesitan entender la complejidad técnica

ANÁLISIS REQUERIDO:

1. **Visión General**
   - Tipo de proyecto (web app, CLI, biblioteca, API, etc.)
   - Tecnologías y frameworks principales
   - Versión/madurez del proyecto

2. **Estructura del Código**
   - Arquitectura de directorios
   - Patrones de diseño identificados
   - Separación de responsabilidades

3. **Calidad del Código**
   - Cobertura de tests (si hay tests/)
   - Documentación (comentarios, docstrings, README)
   - Consistencia de estilo
   - Deuda técnica identificada

4. **Seguridad**
   - Manejo de credenciales y secrets
   - Validación de inputs
   - Dependencias con vulnerabilidades conocidas

5. **Performance**
   - Potenciales cuellos de botella
   - Uso de caching
   - Queries o operaciones costosas

6. **Métricas Clave**
   - Número de archivos de código
   - Lenguajes/tecnologías usadas
   - Dependencias externas

7. **Recomendaciones Prioritarias**
   Lista las 5 mejoras más importantes ordenadas por impacto.

Formato: Markdown con encabezados claros. Sé específico, cita archivos y líneas cuando sea relevante."""


async def analizar_proyecto(ruta_proyecto: str) -> dict:
    """
    Analiza un proyecto y retorna el reporte con metadatos.

    Args:
        ruta_proyecto: Ruta absoluta al directorio del proyecto

    Returns:
        dict con 'reporte', 'turnos', 'costo_usd', 'timestamp'
    """
    ruta = Path(ruta_proyecto).resolve()

    if not ruta.exists():
        raise ValueError(f"El directorio no existe: {ruta}")

    if not ruta.is_dir():
        raise ValueError(f"La ruta no es un directorio: {ruta}")

    print(f"Analizando proyecto en: {ruta}")
    print("Iniciando agente...")

    options = ClaudeCodeOptions(
        cwd=str(ruta),
        max_turns=25,
        permission_mode="bypassPermissions",  # Solo necesitamos leer
        system_prompt="Eres un analizador de código. Lee todos los archivos necesarios para hacer un análisis completo."
    )

    reporte = ""
    turnos = 0
    costo_total = 0.0
    herramientas_usadas = []

    async for message in query(prompt=PROMPT_ANALISIS, options=options):
        # Mostrar progreso
        if hasattr(message, 'content'):
            for block in message.content:
                # Si Claude está usando una herramienta, mostrar progreso
                if hasattr(block, 'name'):
                    herramienta = block.name
                    entrada = block.input
                    herramientas_usadas.append(herramienta)

                    # Mostrar qué está haciendo el agente
                    if herramienta == 'Read':
                        print(f"  📖 Leyendo: {entrada.get('file_path', '')}")
                    elif herramienta == 'LS':
                        print(f"  📂 Explorando: {entrada.get('path', '.')}")
                    elif herramienta == 'Glob':
                        print(f"  🔍 Buscando: {entrada.get('pattern', '')}")
                    elif herramienta == 'Grep':
                        print(f"  🔎 Grep: {entrada.get('pattern', '')}")
                    elif herramienta == 'Bash':
                        print(f"  ⚙️  Ejecutando: {entrada.get('command', '')[:60]}")

        # Capturar el resultado final
        if hasattr(message, 'result'):
            reporte = message.result or ""
            turnos = getattr(message, 'num_turns', 0)
            costo_total = getattr(message, 'cost_usd', 0.0) or 0.0

    return {
        "reporte": reporte,
        "turnos": turnos,
        "costo_usd": round(costo_total, 6),
        "timestamp": datetime.now().isoformat(),
        "proyecto": str(ruta),
        "herramientas_usadas": len(herramientas_usadas),
        "top_herramientas": _contar_herramientas(herramientas_usadas)
    }


def _contar_herramientas(herramientas: list[str]) -> dict:
    """Cuenta la frecuencia de uso de cada herramienta."""
    conteo = {}
    for h in herramientas:
        conteo[h] = conteo.get(h, 0) + 1
    return dict(sorted(conteo.items(), key=lambda x: x[1], reverse=True))


def guardar_reporte(resultado: dict, archivo_salida: str) -> None:
    """Guarda el reporte en un archivo Markdown."""
    with open(archivo_salida, 'w', encoding='utf-8') as f:
        f.write(f"# Reporte de Análisis Técnico\n\n")
        f.write(f"**Generado**: {resultado['timestamp']}\n")
        f.write(f"**Proyecto**: `{resultado['proyecto']}`\n")
        f.write(f"**Turnos del agente**: {resultado['turnos']}\n")
        f.write(f"**Costo**: ${resultado['costo_usd']:.6f} USD\n\n")
        f.write("---\n\n")
        f.write(resultado['reporte'])

    print(f"\nReporte guardado en: {archivo_salida}")


async def main():
    if len(sys.argv) < 2:
        print("Uso: python analizador_proyecto.py <ruta_proyecto> [archivo_salida]")
        print("Ejemplo: python analizador_proyecto.py /home/dev/mi-app reporte.md")
        sys.exit(1)

    ruta_proyecto = sys.argv[1]
    archivo_salida = sys.argv[2] if len(sys.argv) > 2 else "reporte_analisis.md"

    resultado = await analizar_proyecto(ruta_proyecto)

    print(f"\n{'='*50}")
    print(f"Análisis completado")
    print(f"  Turnos del agente:    {resultado['turnos']}")
    print(f"  Costo estimado:       ${resultado['costo_usd']:.4f} USD")
    print(f"  Herramientas usadas:  {resultado['herramientas_usadas']} veces")
    print(f"  Top herramientas:     {json.dumps(resultado['top_herramientas'], indent=2)}")

    guardar_reporte(resultado, archivo_salida)


if __name__ == "__main__":
    asyncio.run(main())

6.2 El mismo agente en TypeScript

#!/usr/bin/env ts-node
/**
 * analizadorProyecto.ts
 *
 * Agente que analiza un proyecto de software y genera un reporte técnico.
 * Uso: npx ts-node analizadorProyecto.ts /ruta/al/proyecto
 */

import { query, ClaudeCodeOptions } from "@anthropic-ai/claude-code-sdk";
import { writeFileSync, existsSync, statSync } from "fs";
import { resolve } from "path";

const PROMPT_ANALISIS = `Eres un arquitecto de software senior. Analiza este proyecto y genera
un reporte técnico detallado con:

1. **Visión General**: tipo, tecnologías, madurez
2. **Estructura del Código**: arquitectura, patrones de diseño
3. **Calidad**: tests, documentación, deuda técnica
4. **Seguridad**: credenciales, validación de inputs, dependencias
5. **Performance**: cuellos de botella, caching
6. **Métricas**: archivos, lenguajes, dependencias
7. **Recomendaciones Prioritarias**: top 5 mejoras por impacto

Sé específico y cita archivos y líneas. Formato Markdown.`;

interface ResultadoAnalisis {
  reporte: string;
  turnos: number;
  costoUsd: number;
  timestamp: string;
  proyecto: string;
  herramientasUsadas: number;
  topHerramientas: Record<string, number>;
}

async function analizarProyecto(rutaProyecto: string): Promise<ResultadoAnalisis> {
  const ruta = resolve(rutaProyecto);

  if (!existsSync(ruta)) {
    throw new Error(`El directorio no existe: ${ruta}`);
  }

  if (!statSync(ruta).isDirectory()) {
    throw new Error(`La ruta no es un directorio: ${ruta}`);
  }

  console.log(`Analizando proyecto en: ${ruta}`);
  console.log("Iniciando agente...");

  const options: ClaudeCodeOptions = {
    cwd: ruta,
    maxTurns: 25,
    permissionMode: "bypassPermissions",
    systemPrompt: "Eres un analizador de código. Lee todos los archivos necesarios.",
  };

  let reporte = "";
  let turnos = 0;
  let costoTotal = 0;
  const herramientasUsadas: string[] = [];

  for await (const message of query({ prompt: PROMPT_ANALISIS, options })) {
    // Mostrar progreso
    if (message.type === "assistant") {
      for (const block of message.message.content) {
        if (block.type === "tool_use") {
          herramientasUsadas.push(block.name);

          switch (block.name) {
            case "Read":
              console.log(`  Leyendo: ${(block.input as Record<string, string>).file_path}`);
              break;
            case "Glob":
              console.log(`  Buscando: ${(block.input as Record<string, string>).pattern}`);
              break;
            case "Bash":
              const cmd = (block.input as Record<string, string>).command;
              console.log(`  Ejecutando: ${cmd.slice(0, 60)}`);
              break;
          }
        }
      }
    }

    // Capturar resultado final
    if (message.type === "result") {
      reporte = message.result || "";
      turnos = message.num_turns;
      costoTotal = message.cost_usd || 0;
    }
  }

  return {
    reporte,
    turnos,
    costoUsd: costoTotal,
    timestamp: new Date().toISOString(),
    proyecto: ruta,
    herramientasUsadas: herramientasUsadas.length,
    topHerramientas: contarHerramientas(herramientasUsadas),
  };
}

function contarHerramientas(herramientas: string[]): Record<string, number> {
  const conteo: Record<string, number> = {};
  for (const h of herramientas) {
    conteo[h] = (conteo[h] || 0) + 1;
  }
  return Object.fromEntries(
    Object.entries(conteo).sort(([, a], [, b]) => b - a)
  );
}

function guardarReporte(resultado: ResultadoAnalisis, archivoSalida: string): void {
  const contenido = [
    "# Reporte de Análisis Técnico",
    "",
    `**Generado**: ${resultado.timestamp}`,
    `**Proyecto**: \`${resultado.proyecto}\``,
    `**Turnos del agente**: ${resultado.turnos}`,
    `**Costo**: $${resultado.costoUsd.toFixed(6)} USD`,
    "",
    "---",
    "",
    resultado.reporte,
  ].join("\n");

  writeFileSync(archivoSalida, contenido, "utf-8");
  console.log(`\nReporte guardado en: ${archivoSalida}`);
}

async function main() {
  const args = process.argv.slice(2);

  if (args.length === 0) {
    console.log("Uso: ts-node analizadorProyecto.ts <ruta_proyecto> [archivo_salida]");
    console.log("Ejemplo: ts-node analizadorProyecto.ts /home/dev/mi-app reporte.md");
    process.exit(1);
  }

  const rutaProyecto = args[0];
  const archivoSalida = args[1] || "reporte_analisis.md";

  const resultado = await analizarProyecto(rutaProyecto);

  console.log("\n" + "=".repeat(50));
  console.log("Análisis completado");
  console.log(`  Turnos del agente:    ${resultado.turnos}`);
  console.log(`  Costo estimado:       $${resultado.costoUsd.toFixed(4)} USD`);
  console.log(`  Herramientas usadas:  ${resultado.herramientasUsadas} veces`);
  console.log(`  Top herramientas:     ${JSON.stringify(resultado.topHerramientas, null, 2)}`);

  guardarReporte(resultado, archivoSalida);
}

main().catch(console.error);

6.3 Output esperado al ejecutar el agente

Cuando ejecutas python analizador_proyecto.py /mi/proyecto, verás algo como:

Analizando proyecto en: /mi/proyecto
Iniciando agente...
  📂 Explorando: .
  📖 Leyendo: package.json
  📖 Leyendo: src/index.ts
  🔍 Buscando: **/*.ts
  📖 Leyendo: src/auth/login.ts
  📖 Leyendo: src/api/users.ts
  🔎 Grep: TODO|FIXME|HACK
  ⚙️  Ejecutando: find . -name "*.test.ts" | wc -l
  📖 Leyendo: README.md

==================================================
Análisis completado
  Turnos del agente:    8
  Costo estimado:       $0.0234 USD
  Herramientas usadas:  15 veces
  Top herramientas:     {
    "Read": 6,
    "Glob": 3,
    "Grep": 3,
    "Bash": 2,
    "LS": 1
  }

Reporte guardado en: reporte_analisis.md

Y el reporte_analisis.md contendrá el análisis completo con todas las secciones.


7. Comparación con Otros Frameworks de Agentes

7.1 LangChain vs Claude Code SDK

CaracterísticaLangChainClaude Code SDK
LenguajesPython, JavaScriptPython, TypeScript
Modelos soportadosMulti-modelo (OpenAI, Anthropic, etc.)Solo Claude
Tool loopManual (defines chains)Automático
Herramientas de códigoVia tools customBuilt-in (optimizadas)
Curva de aprendizajeAlta (muchos conceptos)Media
AbstracciónAlta (muchas capas)Baja (directa)
Performance en tareas de códigoBuenaExcelente (optimizado)
EcosistemaMuy grandeCreciendo
Versiones establesAún madurando
Uso principalApps de IA generalesAutomatización de código

Cuándo elegir LangChain: Si necesitas soportar múltiples modelos (OpenAI + Anthropic + local), tienes un equipo con experiencia en LangChain, o tu caso de uso no está centrado en manipulación de código.

Cuándo elegir Claude Code SDK: Si tu caso de uso principal es automatización de código, tienes acceso a Claude y quieres la menor cantidad de código posible para tareas agenticas complejas.

7.2 AutoGen vs Claude Code SDK

AutoGen (Microsoft) está diseñado para conversaciones multi-agente donde múltiples instancias de IA se comunican entre sí. El Claude Code SDK está diseñado para un agente único muy capaz con herramientas de código built-in.

CaracterísticaAutoGenClaude Code SDK
Multi-agenteSí (nativo)Sí (vía subagentes)
Human-in-the-loopSí (via hooks)
Herramientas de códigoManualBuilt-in
Conversaciones agente-agenteVia orquestación manual
Facilidad de usoMediaAlta para casos de código
ModelosMulti-modeloSolo Claude

7.3 CrewAI vs Claude Code SDK

CrewAI se enfoca en equipos de agentes con roles definidos. Es más orientado a flujos de trabajo donde distintos agentes tienen especialidades.

flowchart LR
    subgraph CrewAI
        PM["Agente PM"] --> Dev["Agente Dev"]
        Dev --> QA["Agente QA"]
        QA --> PM
    end

    subgraph ClaudeCodeSDK
        OA["Un agente orquestador"] --> S1["Subagente 1"]
        OA --> S2["Subagente 2"]
        OA --> S3["Subagente 3"]
        S1 --> OA
        S2 --> OA
        S3 --> OA
    end

7.4 Por qué el Claude Code SDK tiene ventajas únicas

  1. Herramientas optimizadas para código: Las herramientas Read, Write, Edit, MultiEdit, Bash, Glob, Grep están específicamente optimizadas para trabajar con repositorios de código. No son wrappers genéricos; son herramientas que el modelo conoce en profundidad.

  2. Mismo motor que Claude Code CLI: No es una reimplementación; es literalmente Claude Code empaquetado como SDK. Cuando Anthropic mejora Claude Code, el SDK mejora automáticamente.

  3. Permisos granulares: El sistema de permission_mode y hooks permite control fino sobre qué puede hacer el agente sin necesidad de implementar toda la lógica manualmente.

  4. Integración con MCP: El Model Context Protocol permite extender el agente con herramientas de terceros (Playwright, GitHub, Slack, bases de datos) de forma estándar.


8. Versiones y Roadmap

8.1 Estado actual del SDK

Python (claude-code-sdk):

TypeScript (@anthropic-ai/claude-code-sdk):

8.2 Funcionalidades añadidas recientemente

8.3 Cómo verificar la versión instalada

# Python
import importlib.metadata
version = importlib.metadata.version("claude-code-sdk")
print(f"claude-code-sdk version: {version}")
# npm
npm list @anthropic-ai/claude-code-sdk
# o
node -e "console.log(require('@anthropic-ai/claude-code-sdk/package.json').version)"

9. Setup Completo del Entorno de Desarrollo

9.1 Proyecto Python completo

# Crear el proyecto
mkdir mi-agente-python && cd mi-agente-python

# Crear entorno virtual con uv (recomendado) o venv
uv init mi-agente  # con uv
# o
python -m venv .venv && source .venv/bin/activate  # con venv

# Instalar dependencias
uv add claude-code-sdk python-dotenv
# o
pip install claude-code-sdk python-dotenv

pyproject.toml completo:

[project]
name = "mi-agente"
version = "0.1.0"
description = "Agente de automatización usando Claude Code SDK"
readme = "README.md"
requires-python = ">=3.10"
dependencies = [
    "claude-code-sdk>=0.0.9",
    "python-dotenv>=1.0.0",
]

[project.optional-dependencies]
dev = [
    "pytest>=7.0",
    "pytest-asyncio>=0.23",
    "ruff>=0.3",
    "mypy>=1.8",
]

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.pytest.ini_options]
asyncio_mode = "auto"

[tool.ruff]
line-length = 88
target-version = "py310"

[tool.mypy]
python_version = "3.10"
strict = true

Estructura de directorios:

mi-agente-python/
├── .env                     # Variables de entorno (NO committear)
├── .env.example             # Plantilla de variables (sí committear)
├── .gitignore
├── pyproject.toml
├── CLAUDE.md                # Instrucciones para el agente
├── src/
│   └── mi_agente/
│       ├── __init__.py
│       ├── agente.py        # Lógica principal
│       ├── opciones.py      # Configuraciones reutilizables
│       └── hooks/
│           ├── __init__.py
│           └── auditoria.py # Hooks de seguridad/auditoría
└── tests/
    ├── __init__.py
    └── test_agente.py

.env.example:

# Copia este archivo a .env y completa los valores
ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxx

# Opcional: modelo a usar (default: claude-sonnet-4-5)
CLAUDE_MODEL=claude-sonnet-4-5

# Opcional: número máximo de turnos
MAX_TURNS=20

src/mi_agente/opciones.py:

"""Configuraciones reutilizables de ClaudeCodeOptions."""

import os
from claude_code_sdk import ClaudeCodeOptions


def opciones_lectura(cwd: str) -> ClaudeCodeOptions:
    """Solo lectura: para análisis y reportes."""
    return ClaudeCodeOptions(
        cwd=cwd,
        max_turns=int(os.getenv("MAX_TURNS", "20")),
        permission_mode="bypassPermissions",
        model=os.getenv("CLAUDE_MODEL", "claude-sonnet-4-5"),
    )


def opciones_escritura(cwd: str) -> ClaudeCodeOptions:
    """Lectura y escritura: para modificar archivos."""
    return ClaudeCodeOptions(
        cwd=cwd,
        max_turns=int(os.getenv("MAX_TURNS", "30")),
        permission_mode="acceptEdits",
        model=os.getenv("CLAUDE_MODEL", "claude-sonnet-4-5"),
    )


def opciones_interactivo(cwd: str) -> ClaudeCodeOptions:
    """Modo interactivo: pide confirmación para acciones peligrosas."""
    return ClaudeCodeOptions(
        cwd=cwd,
        max_turns=int(os.getenv("MAX_TURNS", "50")),
        permission_mode="default",
        model=os.getenv("CLAUDE_MODEL", "claude-opus-4-5"),
    )

9.2 Proyecto TypeScript completo

# Crear el proyecto
mkdir mi-agente-ts && cd mi-agente-ts
npm init -y

# Instalar dependencias
npm install @anthropic-ai/claude-code-sdk dotenv
npm install -D typescript @types/node ts-node tsx

package.json completo:

{
  "name": "mi-agente-ts",
  "version": "0.1.0",
  "description": "Agente de automatización usando Claude Code SDK",
  "type": "module",
  "scripts": {
    "start": "node --loader ts-node/esm src/index.ts",
    "dev": "tsx watch src/index.ts",
    "build": "tsc",
    "test": "vitest",
    "lint": "eslint src/",
    "typecheck": "tsc --noEmit"
  },
  "dependencies": {
    "@anthropic-ai/claude-code-sdk": "^0.2.0",
    "dotenv": "^16.0.0"
  },
  "devDependencies": {
    "@types/node": "^20.0.0",
    "tsx": "^4.7.0",
    "typescript": "^5.4.0",
    "vitest": "^1.0.0"
  }
}

tsconfig.json completo:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "lib": ["ES2022"],
    "outDir": "dist",
    "rootDir": "src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist", "**/*.test.ts"]
}

Estructura de directorios:

mi-agente-ts/
├── .env
├── .env.example
├── .gitignore
├── package.json
├── tsconfig.json
├── CLAUDE.md
├── src/
│   ├── index.ts             # Entry point
│   ├── agente.ts            # Lógica principal
│   ├── opciones.ts          # Configuraciones
│   └── hooks/
│       └── auditoria.ts     # Hooks de auditoría
└── tests/
    └── agente.test.ts

src/opciones.ts:

import { ClaudeCodeOptions } from "@anthropic-ai/claude-code-sdk";

const MAX_TURNS = parseInt(process.env.MAX_TURNS || "20");
const MODELO = process.env.CLAUDE_MODEL || "claude-sonnet-4-5";

export const opcionesLectura = (cwd: string): ClaudeCodeOptions => ({
  cwd,
  maxTurns: MAX_TURNS,
  permissionMode: "bypassPermissions",
  model: MODELO,
});

export const opcionesEscritura = (cwd: string): ClaudeCodeOptions => ({
  cwd,
  maxTurns: MAX_TURNS + 10,
  permissionMode: "acceptEdits",
  model: MODELO,
});

export const opcionesInteractivo = (cwd: string): ClaudeCodeOptions => ({
  cwd,
  maxTurns: 50,
  permissionMode: "default",
  model: "claude-opus-4-5",
});

9.3 CLAUDE.md para el agente

El archivo CLAUDE.md en la raíz de tu proyecto le da instrucciones al agente cada vez que empieza una sesión:

# CLAUDE.md — Instrucciones para el Agente

## Sobre este proyecto
Este es un proyecto [describe brevemente]. El código está en TypeScript/Python.

## Reglas de trabajo
- Modifica SOLO los archivos en src/
- NUNCA modifiques archivos en node_modules/, .env, o archivos de configuración
- Antes de hacer cambios importantes, verifica el estado actual del código
- Si encuentras un bug, documenta la causa antes de corregirla

## Convenciones de código
- TypeScript: usa tipos estrictos, no uses `any`
- Funciones: máximo 30 líneas
- Archivos: máximo 150 líneas
- Tests: cada función pública debe tener al menos un test

## Herramientas preferidas
- Para buscar: usa Grep antes que Bash con grep
- Para listar archivos: usa Glob antes que Bash con find
- Para editar: usa Edit para cambios pequeños, Write solo si es necesario reescribir todo

9.4 Variables de entorno necesarias

# REQUERIDAS
ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxxxxxx  # Tu API key de Anthropic

# OPCIONALES - para proveedores alternativos
CLAUDE_CODE_USE_BEDROCK=1           # Si usas Amazon Bedrock
CLAUDE_CODE_USE_VERTEX=1            # Si usas Google Vertex AI
AWS_ACCESS_KEY_ID=xxxx              # Si usas Bedrock
AWS_SECRET_ACCESS_KEY=xxxx          # Si usas Bedrock
AWS_REGION=us-east-1                # Si usas Bedrock

# OPCIONALES - configuración del agente
CLAUDE_MODEL=claude-sonnet-4-5      # Modelo a usar por defecto
MAX_TURNS=20                        # Máximo de turnos por default

10. Checklist de Inicio Rápido

Usa esta lista para verificar que todo está configurado correctamente antes de empezar a construir agentes.

Prerequisitos del sistema

Instalación de Claude Code CLI

Setup Python

import asyncio
from claude_code_sdk import query

async def test():
    async for msg in query(prompt="Responde solo: SDK funcionando"):
        if hasattr(msg, 'result'):
            print(msg.result)

asyncio.run(test())

Setup TypeScript

import { query } from "@anthropic-ai/claude-code-sdk";

for await (const msg of query({ prompt: "Responde solo: SDK funcionando" })) {
  if (msg.type === "result") console.log(msg.result);
}

Verificaciones de seguridad

Primer agente real


Siguiente: Capítulo 2: Instalación y Configuración