Capitulo 17: LSP y Formatters
Capitulo 17: LSP y Formatters
< Volver al Indice del Tutorial
OpenCode puede conectarse a Language Servers y ejecutar formatters automáticamente. Esto permite que el agente reciba diagnósticos del código en tiempo real y que todo el código generado siga las reglas de formato del proyecto. En este capítulo exploraremos en profundidad cómo configurar, personalizar y aprovechar al máximo ambas integraciones para obtener un flujo de trabajo donde el agente AI produce código que compila correctamente y cumple con los estándares de estilo desde la primera iteración.
LSP Integration (Experimental)
La integración con Language Server Protocol le da a OpenCode la misma inteligencia de código que tu IDE: errores de tipo, warnings, autocompletado y más. Los diagnósticos del LSP se pasan directamente al LLM como contexto adicional, lo que significa que el modelo no trabaja a ciegas sino con información real del compilador o intérprete del lenguaje.
Cómo Funciona
El flujo de comunicación entre OpenCode y el Language Server sigue un patrón bien definido:
- OpenCode detecta los Language Servers configurados en
opencode.json - Inicia el proceso del Language Server correspondiente al lenguaje del archivo
- Al editar o crear archivos, consulta al LSP por diagnósticos
- Los errores y warnings se incluyen automáticamente en el contexto del agente
- El LLM puede corregir problemas basándose en los diagnósticos reales del compilador
- El ciclo se repite hasta que no hay diagnósticos pendientes
sequenceDiagram
participant Agent as OpenCode Agent
participant LSP as Language Server
participant File as Archivo
Agent->>File: Escribe código
Agent->>LSP: textDocument/didChange
LSP->>LSP: Analiza código
LSP-->>Agent: Diagnósticos (errores, warnings)
Agent->>Agent: Incluye diagnósticos en contexto LLM
Agent->>File: Corrige basándose en diagnósticos
Agent->>LSP: textDocument/didChange
LSP-->>Agent: Sin diagnósticos
Agent->>Agent: Código correcto, continúa
Esta retroalimentación continua es lo que diferencia a OpenCode de otros asistentes que simplemente generan código y esperan que funcione. Con LSP activo, el agente tiene un ciclo de verificación automático que reduce drásticamente los errores en el código generado.
Lenguajes Soportados
La integración funciona con cualquier Language Server que implemente el protocolo LSP estándar. Esto significa que prácticamente todos los lenguajes modernos son compatibles:
| Lenguaje | Language Server | Paquete |
|---|---|---|
| TypeScript/JavaScript | typescript-language-server | typescript-language-server |
| Python | pylsp | python-lsp-server |
| Go | gopls | golang.org/x/tools/gopls |
| Rust | rust-analyzer | rust-analyzer |
| Java | jdtls | Eclipse JDT Language Server |
| Julia | LanguageServer.jl | LanguageServer |
| C/C++ | clangd | clangd |
| Kotlin | kotlin-language-server | kotlin-language-server |
| Ruby | solargraph | solargraph |
| PHP | intelephense | intelephense |
No estás limitado a esta lista. Cualquier ejecutable que implemente el protocolo LSP sobre stdio puede integrarse con OpenCode. Si tu lenguaje favorito tiene un Language Server, es compatible.
Configuración
Agrega los Language Servers en tu archivo de configuración opencode.json. Cada entrada necesita al menos el comando que inicia el servidor:
{
"lsp": {
"typescript": {
"command": "typescript-language-server",
"args": ["--stdio"]
},
"python": {
"command": "pylsp"
},
"go": {
"command": "gopls",
"args": ["serve"]
},
"rust": {
"command": "rust-analyzer"
}
}
}
Cada clave bajo "lsp" es un identificador libre que tú eliges. Lo importante es que el command apunte al ejecutable correcto del Language Server y que los args incluyan los flags necesarios para que el servidor funcione en modo stdio.
Asegúrate de que el Language Server esté instalado y disponible en tu PATH antes de configurarlo. OpenCode intentará iniciar el proceso cuando detecte archivos del lenguaje correspondiente y reportará un error si no puede encontrar el ejecutable.
Herramienta LSP
El agente tiene acceso a una herramienta lsp que le permite interactuar directamente con el Language Server. Esta herramienta es experimental pero ya ofrece capacidades poderosas. El agente puede:
- Obtener diagnósticos: solicitar la lista completa de errores, warnings e información de un archivo específico. Los diagnósticos incluyen la línea, columna, severidad y mensaje descriptivo del problema.
- Consultar definiciones: navegar a la definición de un símbolo. Esto permite al agente entender dónde se declara una función, tipo o variable antes de modificarla, evitando romper contratos implícitos.
- Encontrar referencias: buscar todos los lugares donde se usa un símbolo. Fundamental para refactorizaciones seguras donde el agente necesita actualizar múltiples archivos de forma consistente.
- Información de hover: obtener la documentación y tipo de un símbolo. El agente puede consultar la firma de una función o el tipo de una variable sin necesidad de leer el archivo completo donde se define.
- Code actions: solicitar acciones sugeridas por el LSP, como importaciones automáticas, correcciones rápidas o refactorizaciones propuestas por el propio Language Server.
Esto significa que el agente no solo recibe errores pasivamente, sino que puede consultar activamente al Language Server para tomar mejores decisiones. Cuando el agente necesita refactorizar una función, primero busca todas las referencias, entiende el impacto del cambio y luego procede con la modificación.
Diagnósticos en el Contexto
Cuando el LSP detecta problemas, estos se inyectan automáticamente en el prompt del LLM. El formato es claro y estructurado para que el modelo pueda interpretarlo correctamente:
[LSP Diagnostics for src/utils/math.ts]
Error (line 15, col 3): Type 'string' is not assignable to type 'number'.
Warning (line 22, col 1): Variable 'result' is declared but never used.
El modelo recibe esta información junto con el código fuente, lo que le permite generar correcciones precisas en lugar de hacer suposiciones sobre qué podría estar mal. La combinación de código + diagnósticos reales produce correcciones mucho más acertadas que pedirle al modelo que “busque errores” en el código.
Formatters
OpenCode puede ejecutar formatters automáticamente después de editar archivos. Esto garantiza que el código generado por el agente respete las mismas reglas de formato que el resto del proyecto, eliminando la necesidad de correcciones manuales de estilo.
Nueva Configuración (v1.3.x)
A partir de las versiones recientes, la configuración de formatters cambió para soportar formatters nombrados con mayor flexibilidad. Ahora puedes definir formatters personalizados, desactivar los built-in y especificar extensiones de archivo por formatter:
{
"formatter": {
"prettier": {
"disabled": true
},
"custom-prettier": {
"command": ["npx", "prettier", "--write", "$FILE"],
"extensions": [".js", ".ts", ".jsx", ".tsx"]
},
"gofmt": {
"command": ["gofmt", "-w", "$FILE"],
"extensions": [".go"]
},
"black": {
"command": ["black", "$FILE"],
"extensions": [".py"]
}
}
}
Cada formatter tiene un nombre identificador como clave. Las propiedades disponibles son:
command: array con el ejecutable y sus argumentos. Usa$FILEcomo placeholder para la ruta del archivo que se formateará.extensions: array de extensiones de archivo que este formatter maneja.disabled: booleano para desactivar un formatter built-in sin eliminarlo de la configuración.
Esta estructura permite tener múltiples formatters para diferentes lenguajes y desactivar selectivamente los que no necesitas. Por ejemplo, puedes desactivar el prettier built-in y reemplazarlo con tu propia configuración que usa npx para garantizar la versión correcta.
Formatters Populares
| Formatter | Lenguajes | Comando |
|---|---|---|
| Prettier | JS, TS, CSS, HTML, JSON, MD | ["npx", "prettier", "--write", "$FILE"] |
| Biome | JS, TS, JSON | ["npx", "biome", "format", "--write", "$FILE"] |
| Black | Python | ["black", "$FILE"] |
| gofmt | Go | ["gofmt", "-w", "$FILE"] |
| rustfmt | Rust | ["rustfmt", "$FILE"] |
| dfmt | D | ["dfmt", "$FILE"] |
| clang-format | C, C++ | ["clang-format", "-i", "$FILE"] |
| ruff | Python | ["ruff", "format", "$FILE"] |
| stylua | Lua | ["stylua", "$FILE"] |
Nota: el soporte para
dfmtfue agregado en la versión v1.2.6.
Flujo de Trabajo Automático
Cuando el agente edita un archivo, el formateo ocurre de forma transparente:
- El agente escribe los cambios en el archivo
- OpenCode detecta la extensión del archivo modificado
- Busca un formatter configurado para esa extensión
- Ejecuta el formatter sobre el archivo con
$FILEreemplazado por la ruta real - El resultado final es código correcto y bien formateado
flowchart LR
A[Agente escribe código] --> B[Detecta extensión]
B --> C{Formatter configurado?}
C -->|Sí| D[Ejecuta formatter]
C -->|No| E[Archivo sin cambios de formato]
D --> F[Código formateado]
F --> G[LSP analiza resultado]
E --> G
El agente no necesita saber qué formatter se usa ni cómo configurarlo. OpenCode se encarga de todo el proceso automáticamente. Esto significa que puedes cambiar de Prettier a Biome o de Black a Ruff sin modificar nada en tu flujo de trabajo con el agente.
Múltiples Formatters por Extensión
Si configuras más de un formatter para la misma extensión, OpenCode ejecutará todos en el orden en que aparecen en la configuración. Esto es útil cuando quieres ejecutar un linter con fix automático antes del formatter:
{
"formatter": {
"eslint-fix": {
"command": ["npx", "eslint", "--fix", "$FILE"],
"extensions": [".ts", ".tsx"]
},
"prettier-ts": {
"command": ["npx", "prettier", "--write", "$FILE"],
"extensions": [".ts", ".tsx"]
}
}
}
En este ejemplo, primero se ejecuta ESLint para corregir problemas de linting y luego Prettier para el formateo final. El orden de ejecución sigue el orden de definición en el JSON.
Combinando LSP y Formatters
La combinación de ambas features crea un ciclo de retroalimentación poderoso que replica el flujo de trabajo de un desarrollador experimentado usando un IDE:
flowchart TD
A[Agente genera código] --> B[Formatter formatea automáticamente]
B --> C[LSP analiza código formateado]
C --> D{Hay diagnósticos?}
D -->|Sí| E[Diagnósticos se envían al LLM]
E --> F[Agente corrige basándose en diagnósticos]
F --> B
D -->|No| G[Código correcto y formateado]
Este ciclo se ejecuta automáticamente sin intervención del usuario. El resultado es código que:
- Compila correctamente gracias a los diagnósticos del LSP
- Sigue las convenciones de estilo gracias al formatter
- No tiene errores de tipo porque el LSP reporta incompatibilidades
- Mantiene imports organizados si el formatter o linter lo soporta
Ejemplo Práctico: Proyecto TypeScript
Para un proyecto TypeScript típico, la configuración completa sería:
{
"lsp": {
"typescript": {
"command": "typescript-language-server",
"args": ["--stdio"]
}
},
"formatter": {
"prettier": {
"command": ["npx", "prettier", "--write", "$FILE"],
"extensions": [".ts", ".tsx", ".js", ".jsx", ".css", ".json"]
}
}
}
Con esta configuración, cuando el agente crea un componente React:
- Escribe el archivo
.tsx - Prettier formatea el código automáticamente
- El typescript-language-server analiza el código
- Si hay errores de tipo, el agente los recibe y corrige
- El ciclo continúa hasta que el código está limpio
Ejemplo Práctico: Proyecto Go
Para Go, la configuración es aún más simple porque gofmt es el estándar universal:
{
"lsp": {
"go": {
"command": "gopls",
"args": ["serve"]
}
},
"formatter": {
"gofmt": {
"command": ["gofmt", "-w", "$FILE"],
"extensions": [".go"]
}
}
}
gopls es extremadamente preciso con los diagnósticos y gofmt no tiene configuración, así que el resultado es siempre código Go idiomático.
Ejemplo Práctico: Proyecto Python
Python se beneficia enormemente de la combinación LSP + formatter porque los errores de tipo son frecuentes en código dinámico:
{
"lsp": {
"python": {
"command": "pylsp"
}
},
"formatter": {
"black": {
"command": ["black", "$FILE"],
"extensions": [".py"]
},
"isort": {
"command": ["isort", "$FILE"],
"extensions": [".py"]
}
}
}
Aquí usamos dos formatters para Python: Black para el estilo de código e isort para organizar los imports. Ambos se ejecutan en secuencia después de cada edición.
Troubleshooting
El LSP no detecta errores
Verifica que el Language Server está instalado y accesible:
which typescript-language-server # Debe devolver una ruta
which gopls # Debe devolver una ruta
Si el comando no se encuentra, instala el Language Server primero. OpenCode no puede iniciar un proceso que no existe en el PATH.
El formatter no se ejecuta
Revisa que la extensión del archivo coincide exactamente con las extensiones configuradas. La comparación incluye el punto: .ts, no ts. También verifica que el comando del formatter está disponible en el PATH o usa npx para ejecutar binarios de npm locales.
Diagnósticos desactualizados
Si los diagnósticos parecen no actualizarse, puede ser que el Language Server necesita tiempo para re-analizar el proyecto después de cambios grandes. Los Language Servers procesan cambios de forma asíncrona y proyectos grandes pueden tardar varios segundos en producir diagnósticos actualizados.
Beneficios Clave
- Consistencia: el código generado por AI sigue las mismas reglas del proyecto
- Menos revisiones: no necesitas corregir indentación o estilo manualmente
- Diagnósticos reales: el agente trabaja con errores del compilador, no con suposiciones
- Multi-lenguaje: funciona con cualquier lenguaje que tenga LSP y formatter
- Ciclo cerrado: la retroalimentación automática reduce iteraciones manuales
- Configuración declarativa: todo se define en
opencode.json, sin código adicional
Siguiente: Capitulo 18: IDE y ACP —>