Capítulo 1: Introducción al Claude Code SDK
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:
- v0.0.x (Python inicial): API básica con
query()y opciones limitadas - v0.1.x: Soporte para MCP (Model Context Protocol), hooks de pre/post herramienta
- v0.2.x (TypeScript): Port completo a TypeScript con tipos fuertes
- v0.3.x+: Subagentes, sesiones persistentes,
create_sdk_mcp_server()
Los repositorios oficiales son:
- Python:
https://github.com/anthropics/claude-code-sdk-python - TypeScript:
https://github.com/anthropics/claude-code-sdk(monorepo) - Documentación:
https://docs.anthropic.com/claude-code/sdk
1.4 Tabla comparativa detallada: CLI vs SDK vs Client SDK
| Criterio | Claude Code CLI | Claude Code SDK | Anthropic Client SDK |
|---|---|---|---|
| Interfaz | Terminal interactiva | API programática | API HTTP/REST |
| Loop agentic | Automático | Automático | Manual (tú lo implementas) |
| Herramientas built-in | Sí (Read, Write, Bash, etc.) | Sí (mismas) | No (defines tus propias) |
| Automatizable | No (requiere humano) | Sí | Sí |
| Control granular del flujo | Bajo | Medio | Alto |
| Sesiones persistentes | Sí (interactivo) | Sí (programático) | Manual |
| Acceso al filesystem | Sí | Sí | No (solo texto) |
| Ejecución de comandos | Sí | Sí | No (solo texto) |
| Soporte MCP | Sí | Sí | No nativo |
| Hooks de herramientas | No (es CLI) | Sí | N/A |
| Costo en tokens | Por uso | Por uso | Por uso |
| Lenguajes cliente | N/A | Python, TypeScript | Python, TS, Java, Go, Ruby |
| Caso principal | Desarrollo asistido | Automatización | Chatbots, APIs |
| Complejidad de setup | Baja | Media | Media |
| Curva de aprendizaje | Baja | Media | Alta (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:
- Serializa las opciones (
ClaudeCodeOptions) en argumentos de línea de comandos - Spawns el binario
claudecomo un subproceso (usandosubprocessen Python,child_processen Node) - Pasa el prompt via
stdino argumentos - Lee el stream de salida en formato JSON-lines desde
stdout - Deserializa cada línea en un tipo de mensaje (
AssistantMessage,ResultMessage, etc.) - 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:
- Definir los esquemas JSON de cada herramienta
- Llamar a la API
- Detectar cuando Claude quiere usar una herramienta (
stop_reason === "tool_use") - Ejecutar la herramienta tú mismo en tu código
- Pasar el resultado de vuelta a Claude
- 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
| Capacidad | anthropic (Client) | claude-code-sdk (Agent) |
|---|---|---|
| Chat básico | Sí | Sí (pero sobrediseñado) |
| Streaming de texto | Sí | Sí |
| Tool calling manual | Sí (defines tú las tools) | N/A (automático) |
| Leer archivos del OS | No (solo si defines una tool) | Sí (built-in) |
| Escribir archivos del OS | No (solo si defines una tool) | Sí (built-in) |
| Ejecutar comandos bash | No (solo si defines una tool) | Sí (built-in) |
| Buscar en código (Grep/Glob) | No | Sí |
| Sesiones persistentes | Manual (guardas el historial) | Sí (nativo) |
| Subagentes paralelos | Manual | Sí (nativo) |
| MCP servers | No | Sí |
| Hooks pre/post tool | N/A | Sí |
| Control total del prompt | Sí | Parcial |
| Fine-tuning | No (aún) | No |
| Acceso a modelos no-Claude | No | No |
| Uso en browser | Sí (con fetch) | No (requiere Node/Python proceso) |
| Serverless (Lambda, etc.) | Sí | Limitado (requiere binario claude) |
| Velocidad de setup | Rápido | Requiere 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:
| Modelo | Input (por MTok) | Output (por MTok) | Tarea típica | Costo estimado |
|---|---|---|---|---|
| claude-opus-4-5 | $15 | $75 | Refactor complejo (20 turnos) | $2-8 |
| claude-sonnet-4-5 | $3 | $15 | Code review (10 turnos) | $0.30-1.50 |
| claude-haiku-3-5 | $0.25 | $1.25 | Tarea 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:
- Usa
system_promptpara limitar el alcance al agente - Ejecuta el agente desde el subdirectorio específico (
cwd) - 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ística | LangChain | Claude Code SDK |
|---|---|---|
| Lenguajes | Python, JavaScript | Python, TypeScript |
| Modelos soportados | Multi-modelo (OpenAI, Anthropic, etc.) | Solo Claude |
| Tool loop | Manual (defines chains) | Automático |
| Herramientas de código | Via tools custom | Built-in (optimizadas) |
| Curva de aprendizaje | Alta (muchos conceptos) | Media |
| Abstracción | Alta (muchas capas) | Baja (directa) |
| Performance en tareas de código | Buena | Excelente (optimizado) |
| Ecosistema | Muy grande | Creciendo |
| Versiones estables | Sí | Aún madurando |
| Uso principal | Apps de IA generales | Automatizació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ística | AutoGen | Claude Code SDK |
|---|---|---|
| Multi-agente | Sí (nativo) | Sí (vía subagentes) |
| Human-in-the-loop | Sí | Sí (via hooks) |
| Herramientas de código | Manual | Built-in |
| Conversaciones agente-agente | Sí | Via orquestación manual |
| Facilidad de uso | Media | Alta para casos de código |
| Modelos | Multi-modelo | Solo 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
-
Herramientas optimizadas para código: Las herramientas
Read,Write,Edit,MultiEdit,Bash,Glob,Grepestán específicamente optimizadas para trabajar con repositorios de código. No son wrappers genéricos; son herramientas que el modelo conoce en profundidad. -
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.
-
Permisos granulares: El sistema de
permission_modey hooks permite control fino sobre qué puede hacer el agente sin necesidad de implementar toda la lógica manualmente. -
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):
- Versión estable actual:
0.0.x - Instalación:
pip install claude-code-sdk - Repositorio:
https://github.com/anthropics/claude-code-sdk-python - PyPI:
https://pypi.org/project/claude-code-sdk/
TypeScript (@anthropic-ai/claude-code-sdk):
- Versión estable actual:
0.2.x - Instalación:
npm install @anthropic-ai/claude-code-sdk - Repositorio:
https://github.com/anthropics/claude-code(monorepo) - npm:
https://www.npmjs.com/package/@anthropic-ai/claude-code-sdk
8.2 Funcionalidades añadidas recientemente
- Subagentes nativos: Capacidad de lanzar agentes paralelos desde un agente padre
create_sdk_mcp_server(): Exponer herramientas Python como servidor MCP- Sesiones con
resume: Reanudar sesiones interrumpidas con el mismo contexto - Modo
bypassPermissionsdocumentado: Uso explícito para automatización confiable - Hooks mejorados: Pre y post herramienta con capacidad de modificar inputs/outputs
- Tipos TypeScript mejorados: Mejor inferencia y autocompletado
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
- Python 3.10+ instalado:
python --version - Node.js 18+ instalado:
node --version - npm 9+ o bun instalado:
npm --version - Cuenta en Anthropic Console:
console.anthropic.com - API Key generada: empieza con
sk-ant- - Variable de entorno configurada:
echo $ANTHROPIC_API_KEY
Instalación de Claude Code CLI
- Instalar Claude Code:
npm install -g @anthropic-ai/claude-code - Verificar instalación:
claude --version - Autenticar:
claude(primera vez pedirá la API key) - Test básico:
claude -p "Di hola"debe retornar texto
Setup Python
- Instalar SDK:
pip install claude-code-sdk - Importar sin error:
python -c "from claude_code_sdk import query; print('OK')" - Test básico:
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
- Instalar SDK:
npm install @anthropic-ai/claude-code-sdk - Test básico:
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
-
.enven.gitignore: La API key no debe llegar al repositorio -
CLAUDE.mdcreado con instrucciones de alcance del agente -
cwdconfigurado para limitar el directorio de trabajo -
max_turnsconfigurado para evitar loops infinitos costosos - Logs de herramientas implementados para auditar las acciones del agente
Primer agente real
- El agente corre sin errores de importación
- El agente puede leer archivos del proyecto
- El mensaje
ResultMessagellega con el resultado esperado - El costo por tarea está dentro de lo esperado
- Tienes un mecanismo para detener el agente si hace algo inesperado
Siguiente: Capítulo 2: Instalación y Configuración