diff --git a/package-lock.json b/package-lock.json index 021faee..69245a5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "shiki": "^4.0.2", + "simple-icons": "^16.30.0", "tailwindcss": "^4.2.2", "unified": "^11.0.5" }, @@ -9072,6 +9073,25 @@ "kolorist": "^1.6.0" } }, + "node_modules/simple-icons": { + "version": "16.30.0", + "resolved": "https://registry.npmjs.org/simple-icons/-/simple-icons-16.30.0.tgz", + "integrity": "sha512-YX/HmgEqjpn5Zs3myzKp8ek5Ti+TdrkvNWn9skGLetg9tOg0cTjYizj9ESAYJn4xwc0GQYVGzQxBELDfbxq7WQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/simple-icons" + }, + { + "type": "github", + "url": "https://github.com/sponsors/simple-icons" + } + ], + "license": "CC0-1.0", + "engines": { + "node": ">=0.12.18" + } + }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", diff --git a/package.json b/package.json index d459bba..79ad95b 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "shiki": "^4.0.2", + "simple-icons": "^16.30.0", "tailwindcss": "^4.2.2", "unified": "^11.0.5" }, diff --git a/public/docs/gentle-ai/rose.webp b/public/docs/gentle-ai/rose.webp new file mode 100644 index 0000000..cf6aa91 Binary files /dev/null and b/public/docs/gentle-ai/rose.webp differ diff --git a/src/components/docs/AgentGrid.astro b/src/components/docs/AgentGrid.astro new file mode 100644 index 0000000..39dac3b --- /dev/null +++ b/src/components/docs/AgentGrid.astro @@ -0,0 +1,89 @@ +--- +/** + * The "pick your agent" grid of /docs/agent-setup. + * + * It replaced a table. A table is the right shape for comparing numbers, and + * the wrong one for choosing: a reader arrives knowing which tool they use and + * wants to find it, which is a recognition task. Marks and cards are scanned + * in one pass; a column of names is read line by line. + * + * `note` carries the one thing that changes the decision, not a description. + * For most tools that is nothing at all, and an empty note is better than a + * filler sentence repeated nine times. + */ +import AgentLogo from './AgentLogo.astro'; + +interface Item { + name: string; + href: string; + /** Terminal, IDE, extension… shown as the card's eyebrow. */ + kind: string; + /** simple-icons key; omitted when the tool has no mark in the set. */ + logo?: string; + /** The caveat worth knowing before clicking. */ + note?: string; + /** Marks the tool that does not connect straight to the cluster. */ + indirect?: boolean; + /** Marks the recommendation, so the eye lands on it first. */ + featured?: boolean; +} + +interface Props { + items: Item[]; +} + +const { items } = Astro.props; +--- + +
+ { + items.map((item) => ( + + + + {item.name} + + + + + {item.kind} + + {item.indirect && ( + + via pasarela + + )} + {item.featured && ( + + recomendado + + )} + + + {item.note && {item.note}} + + )) + } +
+ + diff --git a/src/components/docs/AgentLogo.astro b/src/components/docs/AgentLogo.astro new file mode 100644 index 0000000..000c03f --- /dev/null +++ b/src/components/docs/AgentLogo.astro @@ -0,0 +1,120 @@ +--- +/** + * The mark of a coding agent, for the picker on /docs/agent-setup. + * + * Paths come from `simple-icons` rather than from SVG files committed here: + * the package is maintained, the marks stay current, and nothing in this repo + * has to be redrawn when a tool rebrands. Everything is resolved at build time, + * so the reader downloads a handful of inline paths and no icon library. + * + * Not every tool has one. Continue, Aider, Codex, VS Code and our own CLI are + * missing from the set, so they fall back to a monogram tile instead of to an + * empty square: a deliberate letterform reads as a choice, a blank does not. + * + * The marks render in the current text colour rather than in each brand's + * colour. That is the monoaccent rule of the brand, and it is also what keeps + * a grid of nine logos from looking like a sponsor wall. + */ +/* + * Named imports, one per mark, and never `import * as icons`: the package + * carries a few thousand of them, and a namespace import defeats tree-shaking, + * so all of them would be bundled into a Worker that has a size limit. + * + * Adding a tool here is one import plus one line in MARKS. + */ +import { + siClaudecode, + siCline, + siCursor, + siGithubcopilot, + siOpencode, + siPi, + siZedindustries, +} from 'simple-icons'; + +/* + * One mark that shares a name with something else is NOT here on purpose: + * `siHermes` in that package is myhermes.de, a German parcel courier, and not + * the agent by Nous Research. A wrong logo is worse than no logo, because it + * makes the page look like it knows something it does not. + * + * `siPi` is the right one, checked against its source: pi.dev, the terminal + * agent that configures itself under `~/.pi/agent/`. + */ + +/* + * Marks that simple-icons does not carry. + * + * Same shape as the ones it does: a single path on a 24x24 viewBox, drawn in + * `currentColor`. Taken from lobehub/lobe-icons (MIT), which is where the + * OpenAI family lives now that simple-icons no longer publishes it. + */ +const LOCAL_MARKS: Record = { + codex: { + path: 'M8.086.457a6.105 6.105 0 013.046-.415c1.333.153 2.521.72 3.564 1.7a.117.117 0 00.107.029c1.408-.346 2.762-.224 4.061.366l.063.03.154.076c1.357.703 2.33 1.77 2.918 3.198.278.679.418 1.388.421 2.126a5.655 5.655 0 01-.18 1.631.167.167 0 00.04.155 5.982 5.982 0 011.578 2.891c.385 1.901-.01 3.615-1.183 5.14l-.182.22a6.063 6.063 0 01-2.934 1.851.162.162 0 00-.108.102c-.255.736-.511 1.364-.987 1.992-1.199 1.582-2.962 2.462-4.948 2.451-1.583-.008-2.986-.587-4.21-1.736a.145.145 0 00-.14-.032c-.518.167-1.04.191-1.604.185a5.924 5.924 0 01-2.595-.622 6.058 6.058 0 01-2.146-1.781c-.203-.269-.404-.522-.551-.821a7.74 7.74 0 01-.495-1.283 6.11 6.11 0 01-.017-3.064.166.166 0 00.008-.074.115.115 0 00-.037-.064 5.958 5.958 0 01-1.38-2.202 5.196 5.196 0 01-.333-1.589 6.915 6.915 0 01.188-2.132c.45-1.484 1.309-2.648 2.577-3.493.282-.188.55-.334.802-.438.286-.12.573-.22.861-.304a.129.129 0 00.087-.087A6.016 6.016 0 015.635 2.31C6.315 1.464 7.132.846 8.086.457zm-.804 7.85a.848.848 0 00-1.473.842l1.694 2.965-1.688 2.848a.849.849 0 001.46.864l1.94-3.272a.849.849 0 00.007-.854l-1.94-3.393zm5.446 6.24a.849.849 0 000 1.695h4.848a.849.849 0 000-1.696h-4.848z', + }, +}; + +const MARKS: Record = { + ...LOCAL_MARKS, + claudecode: siClaudecode, + cline: siCline, + cursor: siCursor, + githubcopilot: siGithubcopilot, + opencode: siOpencode, + pi: siPi, + zedindustries: siZedindustries, +}; + +interface Props { + /** Key into MARKS. Omitted, or unknown, falls back to a monogram. */ + logo?: string; + /** Used for the monogram fallback and for the accessible name. */ + name: string; + size?: number; +} + +const { logo, name, size = 22 } = Astro.props; + +const icon = logo ? MARKS[logo.toLowerCase()] : undefined; + +/** + * One or two letters, skipping the words that every tool shares. "GitHub + * Copilot" reads as GC, but "Visual Studio Code" as VS rather than VSC, which + * does not fit the tile at this size. + */ +const monogram = name + .replace(/\b(code|cli|ai|studio)\b/gi, '') + .split(/[\s.-]+/) + .filter(Boolean) + .slice(0, 2) + .map((w) => w[0]) + .join('') + .toUpperCase() + .slice(0, 2); +--- + +{ + icon ? ( + + + + ) : ( + + {monogram} + + ) +} diff --git a/src/components/docs/BrandIntro.astro b/src/components/docs/BrandIntro.astro new file mode 100644 index 0000000..4ecc5f3 --- /dev/null +++ b/src/components/docs/BrandIntro.astro @@ -0,0 +1,58 @@ +--- +/** + * A third-party project's own mark, beside the paragraph that introduces it. + * + * It exists so that raw `
`, `` and ` diff --git a/src/components/docs/Details.astro b/src/components/docs/Details.astro new file mode 100644 index 0000000..7be2e89 --- /dev/null +++ b/src/components/docs/Details.astro @@ -0,0 +1,95 @@ +--- +/** + * A collapsed block: the summary is always visible, the body opens on click. + * + * For the material that is true but not needed on the first read: the caveat + * for one platform, the troubleshooting list, the long config nobody copies + * until something breaks. Before this, that material either pushed the steps + * off the screen or was cut, and cutting it is how a guide ends up answering + * the easy half of the question. + * + * Native `
` on purpose. It prints, it is searchable with the + * browser's own find, it works without JavaScript, and a screen reader + * announces its state without any of it being wired by hand. + */ + +interface Props { + /** The always-visible line. Written as a question when it answers one. */ + summary: string; + /** Starts open. For the block a reader should see without asking. */ + open?: boolean; +} + +const { summary, open = false } = Astro.props; +--- + +
+ + + {summary} + +
+ +
+
+ + diff --git a/src/components/docs/Steps.astro b/src/components/docs/Steps.astro new file mode 100644 index 0000000..ebd1528 --- /dev/null +++ b/src/components/docs/Steps.astro @@ -0,0 +1,92 @@ +--- +/** + * A numbered procedure: a rail down the left, one circle per step. + * + * The steps are the `h3`s inside the slot, and the numbers come from a CSS + * counter rather than from the text. Written by hand, the numbers go stale the + * first time a step is inserted in the middle, and a guide that says "4. Apunta + * Claude Code" right after step 4 is the kind of error nobody reports. + * + * Everything here is presentation. The extractor in mdxToText.ts serves the + * children unchanged, so /api/docs keeps the same markdown headings it had + * before the rail existed. + */ +--- + +
+ +
+ + diff --git a/src/content/docs-es/agent-setup.mdx b/src/content/docs-es/agent-setup.mdx new file mode 100644 index 0000000..760f203 --- /dev/null +++ b/src/content/docs-es/agent-setup.mdx @@ -0,0 +1,157 @@ +--- +title: Configurar tu agente +description: Conecta Claude Code, Codex, Cursor, VS Code, Cline, OpenCode, Zed, Pi o Hermes al clúster de NaN. +order: 3 +group: Configurar tu agente +--- + +import AgentGrid from '../../components/docs/AgentGrid.astro'; +import Callout from '../../components/docs/Callout.astro'; +import Details from '../../components/docs/Details.astro'; + +# Configurar tu agente. + +Casi todas las herramientas de código con IA se conectan a NaN con los mismos dos datos: la **base URL** del clúster y tu **API key**. Lo que cambia de una a otra es dónde se escriben. + +| Campo | Valor | +|---|---| +| Base URL | `https://api.nan.builders/v1` | +| API key | la tuya, empieza por `sk-` | + +Cada página de esta sección tiene la misma forma: qué necesitas, la configuración exacta con la ruta del fichero, cómo comprobar que funciona y los problemas conocidos. + +## Elige tu herramienta + + + +¿No está la tuya? Si acepta una base URL y una API key de OpenAI, funciona. Copia los dos valores de la tabla de arriba y pega el id del modelo tal y como aparece en [Elige tu modelo](/es/docs/choose-a-model). + +## Atajos y extras + + + +## Por qué casi todas funcionan + +NaN expone una API compatible con la de OpenAI. Eso significa que las rutas, los campos de la petición y la forma de la respuesta son los que ya esperan estas herramientas, así que para ellas el clúster es indistinguible de OpenAI salvo por la dirección a la que llaman. + +La excepción es **Claude Code**, que no habla ese formato sino el de la API de Anthropic. No es un problema de credenciales: son dos protocolos distintos. Por eso su página explica los dos caminos que hay. + +## Qué modelo poner + +Casi todas estas herramientas te piden un id de modelo además de la base URL y la clave. Para trabajo de agente, el orden razonable es: + +1. `glm5.3` si tienes el tier premium. Es el que está pensado para sesiones largas de código. +2. `glm5.3-flash` si no lo tienes. Mismo contexto de 1M de tokens y cuota amplia. +3. `deepseek-v4-flash` para todo lo demás. + +La lista completa, con cuotas y modalidades, está en [Elige tu modelo](/es/docs/choose-a-model). + + +Varias de estas herramientas te dejan escribir a mano la ventana de contexto del modelo. Si pones un número mayor del real, el agente llenará la conversación hasta un punto en el que el modelo empieza a rechazar peticiones. Si pones uno menor, solo compactará antes de tiempo. Ante la duda, quédate corto. + + +
+ +Cualquier página de esta documentación se puede leer sin el HTML de alrededor, añadiendo el prefijo de la API: + +- `https://nan.builders/api/docs/getting-started.md` +- `https://nan.builders/api/docs/choose-a-model.md` +- `https://nan.builders/api/docs/manifest.json` lista todas las páginas con su hash de contenido + +Es lo que conviene darle a un agente cuando quieres que lea la documentación en vez de adivinar. + +
diff --git a/src/content/docs-es/agents.md b/src/content/docs-es/agents.md index 4c301c4..54655ce 100644 --- a/src/content/docs-es/agents.md +++ b/src/content/docs-es/agents.md @@ -1,7 +1,7 @@ --- title: Agentes description: "Despliega agentes de IA en una microVM aislada con QEMU: Hermes, terminal web, subida de ficheros y observabilidad." -order: 5 +order: 20 group: Guías --- diff --git a/src/content/docs-es/apps.md b/src/content/docs-es/apps.md index 138e56c..5140472 100644 --- a/src/content/docs-es/apps.md +++ b/src/content/docs-es/apps.md @@ -1,7 +1,7 @@ --- title: Apps description: Despliega tus apps desde GitHub a NaN Cloud en minutos. -order: 6 +order: 21 group: Guías --- diff --git a/src/content/docs-es/choose-a-model.md b/src/content/docs-es/choose-a-model.md new file mode 100644 index 0000000..fd9d179 --- /dev/null +++ b/src/content/docs-es/choose-a-model.md @@ -0,0 +1,94 @@ +--- +title: Elige tu modelo +description: Qué modelo pedir para cada tarea y cómo se escribe exactamente su id. +order: 2 +group: Primeros pasos +--- + +# Elige tu modelo. + +En NaN cambiar de modelo es cambiar una palabra. Todos se llaman igual, por el mismo endpoint y con el mismo formato de petición: lo único que cambia es el valor del campo `model`. + +```json +{ + "model": "deepseek-v4-flash", + "messages": [{ "role": "user", "content": "Hola" }] +} +``` + +Ese valor es el **id del modelo**, y tiene que escribirse exacto. Un punto de más o un guion de menos y la API responde `404` con `model_not_found`. Esta página es la lista buena. + +## Empieza por aquí + +Si no sabes cuál coger, busca en la primera columna lo que quieres hacer. + +| Quiero | Pide | Por qué | +|---|---|---| +| Chatear o razonar sobre algo, sin más | `deepseek-v4-flash` | Es el mejor de propósito general del clúster y lee imágenes | +| Mover un agente de código en sesiones largas | `glm5.3` | Está pensado para eso. Necesita el tier premium | +| Lo mismo, pero sin el tier premium | `glm5.3-flash` | Mismo contexto de 1M y cuota generosa | +| Que conteste rápido | `qwen3.8-flash` | Menos profundidad, mucha menos espera | +| Pasarle un audio al modelo directamente | `mimo-v2.5` | Es el único que oye | +| Describir o analizar una imagen | `deepseek-v4-flash` | Cualquiera menos `glm5.3` sirve; este es el mejor | +| Probar cosas sin gastar cuota | `gemma4` | No tiene contador de tokens | +| Montar un buscador o un RAG | `qwen3-embedding` y después `rerank` | Primero recuperas por similitud, luego reordenas por relevancia | +| Convertir texto en audio | `kokoro` | 67 voces, dos de ellas en español | +| Transcribir audio | `whisper` | Más de 99 idiomas, con detección automática | +| Generar o editar una imagen | `flux-2-klein` | Texto a imagen e imagen a imagen | + +## Todos los modelos + +| id | Para qué | Contexto | Acepta | Cuota | +|---|---|---|---|---| +| `deepseek-v4-flash` | Chat y razonamiento general | 1M | texto · imagen | 3B tokens/mes | +| `glm5.3` | Agentes de código y tareas largas | 1M | texto | 3B tokens/periodo de facturación | +| `glm5.3-flash` | Agentes de código, sin premium | 1M | texto · imagen | 2B tokens/mes | +| `qwen3.8-flash` | Respuestas rápidas | 262K | texto · imagen | 500M tokens/mes | +| `mimo-v2.5` | Audio de entrada, omnimodal | 1M | texto · imagen · audio | 1.0B tokens/mes | +| `gemma4` | Tareas cortas y pruebas | 262K | texto · imagen | sin contador | +| `qwen3.6` | Generación anterior | 262K | texto · imagen | sin contador | +| `qwen3-embedding` | Vectores de 4096 dimensiones | - | texto | sin contador | +| `rerank` | Reordenar por relevancia | - | texto | sin contador | +| `kokoro` | Texto a voz | - | texto | sin contador | +| `whisper` | Voz a texto | - | audio | sin contador | +| `flux-2-klein` | Generar y editar imágenes | - | texto · imagen | 100 peticiones/mes | + +Las fichas completas, con parámetros, licencias y modos de razonamiento, están en [Modelos](/es/docs/models). + +> **`glm5.3` es el único que no entra con la suscripción normal** +> Necesita una clave en el tier premium. Si lo pides sin él, la respuesta es `403` con `tier_restricted`. Todos los demás los puede llamar cualquier miembro. + +## Cómo se leen los ids + +- **El id no es el nombre comercial.** El modelo que en su casa se llama "GLM 5.3 Flash" aquí es `glm5.3-flash`, en minúsculas, sin espacios y con el punto de la versión. +- **`-flash` significa rápido**, no pequeño ni peor: son variantes optimizadas para latencia. +- **El punto de la versión cuenta.** `qwen3.6` y `qwen3.8-flash` son modelos distintos, y `mimo-v2.5` lleva el punto donde lo lleva. +- **Los ids no cambian de significado.** Cuando servimos una variante nueva de un modelo mantenemos su id si la API es la misma. `deepseek-v4-flash`, por ejemplo, pasó a leer imágenes sin cambiar de nombre. +- **Los ids viejos no se apagan de golpe.** `qwen3.6` sigue respondiendo para que las configuraciones que ya lo nombran no se rompan, pero no es lo que te conviene si empiezas hoy. + +## Qué significa la cuota + +La columna de cuota cuenta tres cosas distintas: + +- **`/mes`** es un contador de tokens que vuelve a cero con el mes natural. +- **`/periodo de facturación`** vuelve a cero cuando arranca tu periodo en Stripe, que casi nunca es el día 1. Solo `glm5.3` va así, y además tiene un tope aparte de tokens por cada ventana móvil de 4 horas, que es con el que topa antes una sesión intensiva de agente. +- **`sin contador`** significa que no hay contador de tokens asociado, no que sea infinito: los límites de peticiones por minuto se aplican igual a todos. + +Cuando agotas una cuota, la API responde `402` o `429` y no se arregla reintentando. Las cifras vigentes de límites están al final de [Modelos](/es/docs/models). + +## La lista que puede usar tu clave + +Esta página se escribe a mano y el clúster se mueve. La respuesta definitiva, y además filtrada por lo que tu clave puede llamar de verdad, te la da la propia API: + +```bash +curl https://api.nan.builders/v1/models \ + -H "Authorization: Bearer $NAN_API_KEY" +``` + +Si un id aparece ahí, funciona. Si no aparece, no lo tienes disponible, aunque lo leas en otro sitio. + +## Siguientes pasos + +- [Configurar tu agente](/es/docs/agent-setup): dónde poner el id en Cursor, Claude Code, Codex, Cline, OpenCode o Zed. +- [Ejemplos](/es/docs/examples): una llamada completa por cada tipo de modelo. +- [Modelos](/es/docs/models): las fichas técnicas, modelo a modelo. diff --git a/src/content/docs-es/claude-code.mdx b/src/content/docs-es/claude-code.mdx new file mode 100644 index 0000000..48699d3 --- /dev/null +++ b/src/content/docs-es/claude-code.mdx @@ -0,0 +1,204 @@ +--- +title: Claude Code +description: "Dos formas de usar los modelos de NaN desde Claude Code: delegando en OpenCode o con una pasarela local." +order: 6 +group: Configurar tu agente +--- + +import Steps from '../../components/docs/Steps.astro'; +import Details from '../../components/docs/Details.astro'; +import Callout from '../../components/docs/Callout.astro'; + +# Claude Code. + +Claude Code es el agente de terminal de Anthropic. Es la única herramienta de esta sección que **no se conecta directamente** al clúster, y conviene entender por qué antes de pelearse con la configuración. + + +Claude Code habla el formato de la API de Anthropic. NaN habla el formato de OpenAI. Son dos protocolos distintos, así que apuntar `ANTHROPIC_BASE_URL` a `https://api.nan.builders/v1` no funciona: la petición llega, pero con una forma que el clúster no entiende. + + +Hay dos caminos, y hacen cosas distintas: + +| | Qué consigues | Qué cuesta | +|---|---|---| +| **Delegar en OpenCode** | Claude Code dirige, y el trabajo con modelos lo hace OpenCode contra NaN | Instalar OpenCode. Nada más | +| **Pasarela local** | Claude Code usa los modelos de NaN como si fueran suyos | Un proceso extra corriendo en tu máquina | + +Empieza por el primero. Es más simple y no monta infraestructura. + +## Camino 1: delegar en OpenCode + +Claude Code puede ejecutar comandos en tu terminal, y [OpenCode](/es/docs/opencode) sabe trabajar sin abrir su interfaz. Con eso basta: le pides a Claude Code, con palabras normales, que use OpenCode para una tarea, y quien hace el trabajo con el modelo es NaN. + +No hay nada que montar más allá de tener OpenCode configurado. Ni pasarelas, ni variables de entorno, ni ficheros nuevos. + +Sigues usando tu suscripción de Claude Code para lo que hace bien, dirigir la sesión y conocer tu repositorio, y el trabajo que consume tokens a manos llenas se va al clúster. + + + +### Configura OpenCode + +Una sola vez, como explica [su página](/es/docs/opencode), o dejando que lo haga el [CLI de NaN](/es/docs/nan-cli). + +### Comprueba que responde sin interfaz + +```bash +opencode run --agent plan -m nan/deepseek-v4-flash "Resume en tres líneas qué hace este repositorio." +``` + +Tres cosas de ese comando: + +- **`run`** es el modo sin interfaz: hace la tarea, escribe la respuesta y termina. +- **`-m`** toma el modelo como `proveedor/modelo`, donde el proveedor es el nombre que le hayas puesto en tu `opencode.json`, `nan` si has seguido nuestra página. +- **`--agent plan`** deja a OpenCode en modo lectura. Sin eso arranca con su agente por defecto, que **puede editar ficheros y ejecutar comandos**. Para delegar una revisión o un resumen no quieres eso. + +La respuesta llega por la salida estándar, precedida de las herramientas que ha usado: + +```text +> plan · deepseek-v4-flash +→ Read src/lib/modelCatalog.ts +Es la fuente única de verdad del catálogo de modelos... +``` + +### Pídeselo a Claude Code + +Dentro de una sesión de Claude Code, díselo tal cual: + +```text +Usa `opencode run --agent plan -m nan/deepseek-v4-flash` para revisar +src/parser.ts y dime qué casos no está cubriendo. +``` + +Claude Code lanza el comando, lee lo que responde OpenCode y sigue trabajando a partir de ahí. La primera vez te pedirá permiso para ejecutarlo. + +Funciona bien para lo que es largo de leer y barato de resumir: revisar un fichero grande, hacer un primer borrador, resumir documentación, comparar dos versiones. Claude Code se queda con la coordinación y con los cambios finos. + +Dos opciones que ayudan cuando le coges el gusto: + +- **`-f fichero`** le adjunta ficheros concretos, en vez de que los busque él. +- **`-c`** continúa la última conversación de OpenCode, para repreguntar sin volver a explicárselo todo. + + + + +Escribe una línea en el `CLAUDE.md` de tu proyecto, del tipo "para revisar ficheros largos, usa `opencode run --agent plan -m nan/deepseek-v4-flash`". Así no tienes que repetir el comando en cada sesión. + + + +Lo que hace Claude Code sale de tu suscripción de Anthropic. Lo que hace `opencode run` sale de tu cuota de NaN. Es justo lo que buscas si quieres estirar la suscripción, pero conviene tenerlo claro cuando mires las cifras de consumo. + + +## Camino 2: una pasarela local + +Si lo que quieres es que **Claude Code mismo** use los modelos de NaN, hay que poner en medio algo que traduzca entre los dos formatos. + +Necesitas Claude Code instalado, Python 3.10 o superior para la pasarela, y tu API key de NaN en `NAN_API_KEY`. + + + +### Instala la pasarela + +[LiteLLM](https://docs.litellm.ai/docs/anthropic_unified/) expone un endpoint `/v1/messages` con el formato de Anthropic y lo traduce al de OpenAI antes de reenviarlo. + +```bash +pip install "litellm[proxy]" +``` + + +Las versiones 1.82.7 y 1.82.8 de LiteLLM se publicaron con código que robaba credenciales. Instala una versión posterior y conocida, y no uses esas dos. Si las llegaste a instalar, rota tus claves. + + +### Configura la pasarela + +Crea un `litellm.config.yaml` donde te venga bien: + +```yaml +model_list: + - model_name: nan-coder + litellm_params: + model: openai/glm5.3-flash + api_base: https://api.nan.builders/v1 + api_key: os.environ/NAN_API_KEY + + - model_name: nan-general + litellm_params: + model: openai/deepseek-v4-flash + api_base: https://api.nan.builders/v1 + api_key: os.environ/NAN_API_KEY + +general_settings: + master_key: sk-local-cambia-esto +``` + +El prefijo `openai/` le dice a LiteLLM con qué formato hablar con NaN. Lo que va después del prefijo es el id del modelo tal cual, y `model_name` es el nombre con el que lo verás desde Claude Code. + +El `master_key` es una clave que te inventas tú para tu pasarela local. No es tu clave de NaN, y no debe serlo: la de NaN solo la conoce el proceso de LiteLLM. + +### Arranca la pasarela + +```bash +litellm --config litellm.config.yaml --port 4000 +``` + +Déjala corriendo en su propia terminal. + +### Apunta Claude Code a la pasarela + +```bash +export ANTHROPIC_BASE_URL="http://localhost:4000" +export ANTHROPIC_AUTH_TOKEN="sk-local-cambia-esto" +export ANTHROPIC_MODEL="nan-coder" + +claude +``` + +Usa `ANTHROPIC_AUTH_TOKEN` y no `ANTHROPIC_API_KEY`: es la variable que Claude Code envía como cabecera `Authorization` cuando la base URL no es la de Anthropic. + +Si prefieres elegir el modelo en cada sesión en lugar de fijarlo por entorno, deja fuera `ANTHROPIC_MODEL` y arranca con `claude --model nan-coder`. + +### Comprueba que funciona + +Con la pasarela levantada: + +```bash +curl http://localhost:4000/v1/messages \ + -H "Authorization: Bearer sk-local-cambia-esto" \ + -H "Content-Type: application/json" \ + -d '{ + "model": "nan-coder", + "max_tokens": 64, + "messages": [{ "role": "user", "content": "Di hola." }] + }' +``` + +Si eso responde, Claude Code también lo hará. Si devuelve un error de autenticación, el que está mal es el `master_key`; si devuelve `404 model_not_found`, lo que está mal es el id de NaN dentro de `litellm_params`. + + + +
+ +Hay pasarelas más pequeñas dedicadas solo a esto, como [claude-code-proxy](https://github.com/fuergaosi233/claude-code-proxy). La idea es la misma: un proceso local que recibe en formato Anthropic y reenvía a `https://api.nan.builders/v1`. + +
+ +## Modelo recomendado + +Depende del camino, porque el modelo hace cosas distintas en cada uno. + +**Delegando en OpenCode**, el modelo recibe encargos sueltos y acotados, así que `deepseek-v4-flash` va sobrado y tiene la cuota más amplia del clúster. Si lo que le pasas es un repositorio entero, `glm5.3-flash` aguanta más contexto. + +**Con la pasarela**, el modelo lleva la sesión entera: `glm5.3-flash`, o `glm5.3` si tienes el tier premium. Los dos tienen 1M de tokens de contexto, que es lo que pide una sesión larga de agente. + +En cualquiera de los dos, evita `glm5.3` para tareas que impliquen leer capturas de pantalla: no acepta imágenes. + +## Problemas conocidos + +- **Delegando, la salida de OpenCode entra en tu conversación.** Si le pides un resumen de algo enorme, lo que devuelva ocupa contexto en Claude Code. Pídele resúmenes, no volcados. +- **Con la pasarela, toda la sesión pasa por tu máquina.** Si apagas el proceso, Claude Code deja de responder. No es un servicio, es algo tuyo que tiene que estar encendido. +- **El comportamiento del agente depende del modelo.** Claude Code está afinado contra los modelos de Anthropic, y un modelo abierto puede usar peor sus herramientas. Es esperable, y no es un fallo del clúster. Esto solo afecta al camino de la pasarela: delegando, Claude Code sigue siendo Claude Code. +- **Las funciones atadas a la cuenta de Anthropic no viajan.** Lo que dependa de la infraestructura de Anthropic no funciona contra otra base URL. +- **El contexto se llena rápido.** Una sesión de agente consume tokens mucho más deprisa que un chat. Si usas `glm5.3`, vigila la ventana móvil de 4 horas descrita en [Elige tu modelo](/es/docs/choose-a-model). + +## Las herramientas de NaN, aparte + +Independientemente del camino que elijas, puedes darle a Claude Code la búsqueda web del clúster con una sola orden. Está en [Servidor MCP](/es/docs/mcp). diff --git a/src/content/docs-es/cline.mdx b/src/content/docs-es/cline.mdx new file mode 100644 index 0000000..5c62502 --- /dev/null +++ b/src/content/docs-es/cline.mdx @@ -0,0 +1,69 @@ +--- +title: Cline +description: Conecta Cline, la extensión de VS Code, al clúster de NaN. +order: 10 +group: Configurar tu agente +--- + +import Steps from '../../components/docs/Steps.astro'; +import Details from '../../components/docs/Details.astro'; +import Callout from '../../components/docs/Callout.astro'; + +# Cline. + +Cline es una extensión de VS Code que edita ficheros y ejecuta comandos por ti. Trae un proveedor genérico llamado **OpenAI Compatible**, que es justo lo que necesita NaN. + +## Configuración + + + +### Abre los ajustes de Cline + +En VS Code, abre el panel de Cline y entra en sus ajustes con el icono del engranaje. + +### Elige el proveedor + +En **API Provider**, selecciona **OpenAI Compatible**. + +### Rellena los tres campos + +| Campo | Valor | +|---|---| +| Base URL | `https://api.nan.builders/v1` | +| API Key | tu clave, la que empieza por `sk-` | +| Model ID | `glm5.3-flash` | + + +Es el error más habitual de este proveedor: pegar `https://api.nan.builders/v1/chat/completions` en lugar de solo la base. Cline añade por su cuenta la parte del endpoint, así que con la ruta completa acabarías llamando a una dirección que no existe. + + +### Declara qué sabe hacer el modelo + +Cline lo pregunta porque con un proveedor genérico no tiene forma de averiguarlo: + +| Modelo | Contexto | Imágenes | Herramientas | +|---|---|---|---| +| `glm5.3-flash` | 1000000 | sí | sí | +| `deepseek-v4-flash` | 1000000 | sí | sí | +| `qwen3.8-flash` | 262144 | sí | sí | +| `glm5.3` | 1000000 | no | sí | + +Si marcas imágenes en un modelo que no las acepta, Cline intentará mandarle capturas y la petición fallará. + +### Pruébalo + +Abre una carpeta con código y pídele algo pequeño y observable, por ejemplo que lea el README y te resuma qué hace el proyecto. Si te contesta y te pide permiso para tocar ficheros, está conectado. + + + +## Modelo recomendado + +`glm5.3-flash`. Es el que mejor aguanta las tandas largas de leer, editar y volver a leer que hace Cline, y su cuota da para trabajar. + +
+ +- **Cline consume mucho contexto.** Manda el contenido de los ficheros en cada vuelta, así que gasta tokens mucho más rápido que un chat. Vigila tu cuota en [cloud.nan.builders](https://cloud.nan.builders/). +- **Los cuatro campos tienen que ser del mismo sitio.** Proveedor, base URL, clave y modelo, todo de NaN. Mezclar una clave de un sitio con la base URL de otro da siempre un `401`. +- **Roo Code y los demás derivados se configuran igual.** Son forks de Cline y tienen el mismo proveedor genérico, con los mismos cuatro campos. + +
diff --git a/src/content/docs-es/codex.mdx b/src/content/docs-es/codex.mdx new file mode 100644 index 0000000..f810cd3 --- /dev/null +++ b/src/content/docs-es/codex.mdx @@ -0,0 +1,80 @@ +--- +title: Codex +description: Configura Codex CLI para que use los modelos de NaN con un proveedor propio. +order: 7 +group: Configurar tu agente +--- + +import Details from '../../components/docs/Details.astro'; + +# Codex. + +Codex CLI es el agente de terminal de OpenAI. Habla el formato de OpenAI, que es el mismo que habla NaN, así que se conecta directamente: solo hay que declarar un proveedor nuevo. + +> **Puedes saltarte todo esto** +> El [CLI de NaN](/es/docs/nan-cli) escribe esta configuración por ti: pestaña Setup, `e` para pegar tu clave, `espacio` para marcar Codex, `c` para aplicar. + +## Qué necesitas + +- Codex CLI instalado. +- Tu API key de NaN en una variable de entorno. + +```bash +export NAN_API_KEY="sk-tu-clave" +``` + +Codex lee la clave de la variable de entorno que le indiques, no del fichero de configuración. Así no acaba escrita en un fichero que puedas subir a un repositorio sin querer. + +## Configuración + +Edita `~/.codex/config.toml`: + +```toml +model_provider = "nan" +model = "glm5.3-flash" + +[model_providers.nan] +name = "NaN" +base_url = "https://api.nan.builders/v1" +env_key = "NAN_API_KEY" +wire_api = "chat" +``` + +Tres detalles que importan: + +- **`wire_api = "chat"`** hace que Codex use `/chat/completions`. Es lo que quieres: el endpoint `/responses` del clúster contesta de una sola vez en lugar de ir emitiendo la respuesta, así que con `"responses"` verías la respuesta aparecer de golpe al final. +- **El identificador del proveedor no puede ser `openai`, `ollama` ni `lmstudio`**, que están reservados. Por eso se llama `nan`. +- **`base_url` termina en `/v1`** y nada más. No añadas la ruta del endpoint. + +## Comprueba que funciona + +```bash +codex +``` + +Pídele algo corto, por ejemplo que liste los ficheros del directorio y resuma qué hace el proyecto. Si contesta, ya está funcionando contra el clúster. + +Si ves un error de autenticación, lo más habitual es que la variable `NAN_API_KEY` no esté exportada en la terminal desde la que lanzas `codex`. + +## Cambiar de modelo + +Puedes cambiarlo sin tocar el fichero: + +```bash +codex --model deepseek-v4-flash +``` + +O dejar varios proveedores declarados y elegir con `--profile` si prefieres perfiles separados por tarea. + +## Modelo recomendado + +`glm5.3-flash` para trabajo de código. Si tienes el tier premium, `glm5.3`. Para preguntas sueltas que no tocan ficheros, `deepseek-v4-flash` va sobrado. + +
+ +- **Las funciones en la nube de Codex no aplican.** Al declarar un proveedor propio, todo va contra NaN desde tu máquina. +- **El razonamiento se ve distinto según el modelo.** Los modelos del clúster emiten su traza de razonamiento a su manera, y Codex no siempre la presenta como con los modelos de OpenAI. +- **Si cambias `wire_api` a `"responses"`**, la respuesta deja de aparecer poco a poco. No es un cuelgue: es que ese endpoint todavía no emite la respuesta por partes. + +
+ diff --git a/src/content/docs-es/cursor.mdx b/src/content/docs-es/cursor.mdx new file mode 100644 index 0000000..34f8e34 --- /dev/null +++ b/src/content/docs-es/cursor.mdx @@ -0,0 +1,71 @@ +--- +title: Cursor +description: Apunta Cursor al clúster de NaN sobrescribiendo la base URL de OpenAI. +order: 8 +group: Configurar tu agente +--- + +import Steps from '../../components/docs/Steps.astro'; +import Details from '../../components/docs/Details.astro'; +import Callout from '../../components/docs/Callout.astro'; + +# Cursor. + +Cursor permite sustituir la dirección a la que envía sus peticiones de OpenAI. Como NaN habla ese mismo formato, basta con cambiar la dirección y la clave. + + +La sobrescritura de la base URL no cubre todo Cursor. El autocompletado mientras escribes usa modelos propios de Cursor y seguirá haciéndolo pase lo que pase. Y en versiones recientes la opción ha dejado de estar visible en todos los planes: si no la encuentras en tus ajustes, no es que la hayas perdido, es que tu plan no la trae. + + +## Configuración + + + +### Abre los ajustes de modelos + +Entra en **Settings**, luego **Cursor Settings**, y abre la sección **Models**. + +### Pega tu clave + +En la sección de claves de OpenAI, pon tu clave de NaN, la que empieza por `sk-`. + +### Cambia la base URL + +Activa **Override OpenAI Base URL** y escribe: + +```text +https://api.nan.builders/v1 +``` + +### Añade los modelos + +Con **Add Model**, escribe el id exactamente como aparece en [Elige tu modelo](/es/docs/choose-a-model): + +```text +glm5.3-flash +deepseek-v4-flash +qwen3.8-flash +``` + +Los nombres que Cursor trae de fábrica no existen en NaN, así que desactívalos o ignóralos. Si dejas seleccionado uno de ellos, la petición se va al clúster con un id que no reconoce y vuelve un `404`. + +### Verifica + +Pulsa **Verify** para que Cursor compruebe la conexión, y después abre el panel de chat, elige uno de los modelos que has añadido y pregúntale algo. + +Para estar seguro del todo, mira el consumo en [cloud.nan.builders](https://cloud.nan.builders/): si los tokens suben, las peticiones están llegando. + + + +## Modelo recomendado + +`glm5.3-flash` para el modo agente y `deepseek-v4-flash` para preguntas de chat. `qwen3.8-flash` si notas la respuesta lenta y prefieres rapidez a profundidad. + +
+ +- **El autocompletado en línea no usa NaN.** Es de Cursor y no se puede redirigir. +- **La opción no está en todos los planes.** En algunas versiones recientes solo aparece en ciertos planes. La alternativa que queda entonces es la configuración de tipo Azure, si tu versión la trae. +- **Es una sobrescritura global.** Al cambiar la base URL, todo lo que Cursor enviaba a OpenAI pasa a ir a NaN. No es una selección por modelo. +- **Las funciones que corren en los servidores de Cursor no cambian de sitio.** Lo que Cursor procesa en su propia infraestructura sigue igual. + +
diff --git a/src/content/docs-es/examples.md b/src/content/docs-es/examples.md index e4d9a0b..249414f 100644 --- a/src/content/docs-es/examples.md +++ b/src/content/docs-es/examples.md @@ -1,7 +1,7 @@ --- title: Ejemplos description: Fragmentos de código para conectarte a la API de NaN con Python, Node.js, curl y más. -order: 4 +order: 19 group: Guías --- @@ -9,9 +9,9 @@ group: Guías Ejemplos para conectarte a la API desde distintos lenguajes y herramientas. Usa `https://api.nan.builders/v1` como base URL y tu API key personal. -## model: qwen3.6 +## model: deepseek-v4-flash -generación de texto y chat +generación de texto, chat y visión ### curl @@ -20,7 +20,7 @@ curl https://api.nan.builders/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer sk-your-key-here" \ -d '{ - "model": "qwen3.6", + "model": "deepseek-v4-flash", "messages": [{"role": "user", "content": "Hello, how are you?"}], "max_tokens": 500 }' @@ -37,7 +37,7 @@ client = OpenAI( ) response = client.chat.completions.create( - model="qwen3.6", + model="deepseek-v4-flash", messages=[{"role": "user", "content": "Write a hello world in Rust"}], max_tokens=500, stream=True @@ -62,7 +62,7 @@ const client = new OpenAI({ }); const stream = await client.chat.completions.create({ - model: "qwen3.6", + model: "deepseek-v4-flash", messages: [{ role: "user", content: "Write a hello world in Zig" }], max_tokens: 500, stream: true, @@ -76,182 +76,6 @@ for await (const chunk of stream) { Instalación: `npm install openai` -### opencode.json (config) - -```json -{ - "$schema": "https://opencode.ai/config.json", - "provider": { - "nan": { - "npm": "@ai-sdk/openai-compatible", - "name": "NaN", - "options": { - "baseURL": "https://api.nan.builders/v1", - "apiKey": "sk-your-key-here" - }, - "models": { - "qwen3.6": { - "name": "Qwen 3.6", - "contextWindow": 262144, - "modalities": { - "input": ["text", "image"], - "output": ["text"] - } - }, - "gemma4": { - "name": "Gemma 4", - "contextWindow": 262144, - "modalities": { - "input": ["text", "image"], - "output": ["text"] - } - }, - "deepseek-v4-flash": { - "name": "DeepSeek V4 Flash", - "contextWindow": 500000, - "modalities": { - "input": ["text", "image"], - "output": ["text"] - } - }, - "mimo-v2.5": { - "name": "Xiaomi MiMo V2.5", - "contextWindow": 500000, - "modalities": { - "input": ["text", "image", "audio"], - "output": ["text"] - } - } - } - } - }, - "compaction": { - "auto": true, - "prune": true, - "reserved": 50000 - } -} -``` - -Esta es la configuración para conectar IDEs (Cursor, OpenCode) con los 4 modelos LLM disponibles: `qwen3.6`, `gemma4`, `deepseek-v4-flash` y `mimo-v2.5`. - -### .pi/agent/models.json (config) - -```json -{ - "providers": { - "nan": { - "baseUrl": "https://api.nan.builders/v1", - "api": "openai-completions", - "apiKey": "", - "compat": { - "supportsDeveloperRole": true - }, - "models": [ - { - "id": "qwen3.6", - "name": "Qwen 3.6", - "reasoning": true, - "input": ["text", "image"], - "contextWindow": 262144, - "maxTokens": 16384 - }, - { - "id": "gemma4", - "name": "Gemma 4", - "reasoning": true, - "input": ["text", "image"], - "contextWindow": 262144, - "maxTokens": 16384 - } - ] - } - } -} -``` - -Configuración para `~/.pi/agent/models.json` - -### .pi/agent/settings.json (config) - -```json -{ - "defaultProvider": "nan", - "defaultModel": "qwen3.6" -} -``` - -Configuración para `~/.pi/agent/settings.json`. Sin `defaultProvider` ni `defaultModel`, Pi usa su proveedor por defecto y devuelve un error de autenticación (401). - -### openclaw.json (config) - -```json -{ - "models": { - "providers": { - "nan": { - "baseUrl": "https://api.nan.builders/v1", - "apiKey": "sk-...", - "api": "openai-completions", - "models": [ - { - "id": "qwen3.6", - "name": "Qwen 3.6", - "reasoning": true, - "input": ["text", "image"], - "contextWindow": 262144, - "maxTokens": 65536 - } - ] - } - } - }, - "agents": { - "defaults": { - "model": { "primary": "nan/qwen3.6" }, - "models": { - "nan/qwen3.6": { - "params": { - "maxTokens": 16000 - } - } - } - } - } -} -``` - -Configuración para `~/.openclaw/openclaw.json` - -`maxTokens: 65536` es el máximo que admite el modelo. `params.maxTokens: 16000` es lo que se manda en cada petición. 16K es un buen equilibrio para la mayoría de tareas. Si necesitas respuestas más largas, súbelo, pero ten en cuenta que el razonamiento también consume de ese presupuesto. - -

settings.json (Zed)

- -```json -{ - "language_models": { - "openai": { - "api_url": "https://api.nan.builders/v1", - "available_models": [ - { - "name": "qwen3.6", - "display_name": "NaN", - "max_tokens": 262144 - } - ] - } - }, - "edit_predictions": { - "open_ai_compatible_api": { - "api_url": "https://api.nan.builders/v1", - "model": "qwen3.6" - } - } -} -``` - -Configuración para `~/.config/zed/settings.json`. Incluye las predicciones en línea. - ## model: qwen3-embedding embeddings vectoriales @@ -631,13 +455,6 @@ for r in response["results"]: También funciona con `requests` a pelo o con cualquier cliente HTTP: manda el cuerpo JSON con tu key en el Bearer. -## Integración con IDEs +## Conectar tu editor o tu agente -- **Cursor**: Settings → OpenAI API → Base URL: `https://api.nan.builders/v1`, API Key: tu key -- **Zed**: Settings → `settings.json` → mira la [configuración completa de arriba](#qwen36-zed) -- **Cline / Continue / Aider**: define las variables de entorno: - -```bash -export OPENAI_BASE_URL="https://api.nan.builders/v1" -export OPENAI_API_KEY="sk-your-key-here" -``` +Las configuraciones de Cursor, Claude Code, Codex, Cline, OpenCode, Zed y el resto están en [Configurar tu agente](/es/docs/agent-setup), con una página por herramienta. diff --git a/src/content/docs-es/gentle-ai.mdx b/src/content/docs-es/gentle-ai.mdx new file mode 100644 index 0000000..640e652 --- /dev/null +++ b/src/content/docs-es/gentle-ai.mdx @@ -0,0 +1,135 @@ +--- +title: Gentle-AI +description: "La configuración que NaN recomienda por defecto: memoria, skills y guardarraíles sobre el agente que ya usas." +order: 5 +group: Configurar tu agente +--- + +import Steps from '../../components/docs/Steps.astro'; +import Details from '../../components/docs/Details.astro'; +import Callout from '../../components/docs/Callout.astro'; +import BrandIntro from '../../components/docs/BrandIntro.astro'; + +# Gentle-AI. + + + +[Gentle-AI](https://github.com/Gentleman-Programming/gentle-ai) no instala agentes ni los conecta a ningún sitio: **configura el que ya usas**. Le pone memoria entre sesiones, una biblioteca de skills, un flujo de trabajo para lo que es grande, un servidor MCP de documentación y unos guardarraíles de permisos. + + + +Es la configuración que recomendamos por defecto en NaN, y es lo que se hace **después** de conectar tu agente al clúster, no en vez de eso. + + +Primero conecta tu agente siguiendo [su página](/es/docs/agent-setup). Después pasa Gentle-AI. No toca tu proveedor ni tus modelos: lo que hayas configurado para NaN sigue exactamente igual. + + +## Por qué lo recomendamos + +Un agente de código, tal y como viene, empieza cada sesión en blanco. No recuerda lo que decidisteis ayer, no conoce las convenciones del proyecto y vuelve a preguntar lo mismo. Eso se paga en tokens y en paciencia. + +En NaN la cuota da para sesiones largas, así que el cuello de botella no es cuánto puedes gastar sino cuánto contexto se pierde por el camino. Gentle-AI ataca justo eso: + +- **Memoria entre sesiones**, para que las decisiones no haya que repetirlas. +- **Skills**, una biblioteca de criterios que se cargan según lo que estés haciendo. +- **SDD**, un flujo para lo que es lo bastante grande como para merecer un plan antes del código. +- **Context7**, un servidor MCP que le da documentación actualizada de librerías en vez de lo que recuerde el modelo. +- **Permisos**, listas de denegación para que el agente no lea ni toque lo que no debe. +- **Persona**, el tono con el que te habla. + +Puedes cogerlo todo o solo lo que quieras: trae presets, desde "solo memoria" hasta el paquete completo. + +## Instalación + + + +### Comprueba los requisitos + +Necesitas **Node.js 18 o superior con npm**, y **Git 2.38 o superior**. En Windows, además, **Go 1.25.10 o superior**. + +Y el agente que vayas a configurar tiene que estar ya instalado y accesible desde la terminal. Gentle-AI lo configura, no lo instala. + +### Instala el binario + +En macOS y Linux: + +```bash +curl -fsSL https://raw.githubusercontent.com/Gentleman-Programming/gentle-ai/main/scripts/install.sh | bash +``` + +En Windows, desde PowerShell: + +```powershell +go install github.com/gentleman-programming/gentle-ai/v2/cmd/gentle-ai@latest +``` + +En Windows la instalación desde fuente es la vía soportada: no hay distribución oficial de binarios y el instalador está hecho para fallar antes que descargarte un ejecutable sin firmar. + +Comprueba que ha ido bien: + +```bash +gentle-ai version +``` + +### Configura tu agente + +```bash +gentle-ai +``` + +Se abre un panel donde eliges qué agentes configurar, qué componentes quieres (o un preset) y qué persona. + +Al aplicar, escribe en el directorio de configuración de cada agente que hayas marcado. Antes hace una copia de seguridad de lo que tenías. + +### Verifica + +```bash +gentle-ai doctor +``` + +Te da un informe de salud sin tocar nada: binarios, estado, memoria accesible, espacio en disco y configuraciones rotas. Es lo primero que hay que ejecutar cuando algo va raro. + + + +## Con qué agentes funciona + +De los que documentamos aquí, Gentle-AI configura **Claude Code, OpenCode, Cursor, VS Code con Copilot, Codex y Pi**. Además soporta Kilo Code, Gemini CLI, Antigravity, Kimi Code, Kiro IDE, Qwen Code, Hermes, Windsurf, OpenClaw y Trae. + +Usa el mecanismo de configuración propio de cada agente, no una capa por encima, así que lo que escribe se puede leer y revertir. + +
+ +Gentle-AI hace una copia de seguridad de lo que había antes de escribir nada, comprimida y con rotación automática. Si no te convence: + +```bash +gentle-ai uninstall +``` + +Eso quita lo que él gestiona y deja el resto. Y `gentle-ai restore` devuelve una copia anterior. + +Aun así, si tienes una configuración muy tuya, míratela antes y después. Escribe en el directorio global del agente, que es donde también vive lo que hayas puesto a mano. + +
+ +
+ +No. Gentle-AI configura cómo trabaja el agente, no a qué proveedor llama. Tu `opencode.json`, tu `config.toml` de Codex o lo que hayas configurado para NaN sigue igual. + +Trae un componente opcional de cambio de proveedor, pero es eso, opcional, y no altera lo que ya tengas puesto si no se lo pides. + +
+ +## Mantenerlo al día + +```bash +gentle-ai upgrade # actualiza el binario +gentle-ai sync # pone al día lo que ha escrito en tus agentes +``` + +Conviene pasar `sync` después de cada `upgrade`: lo primero actualiza el programa, lo segundo lo que ese programa dejó escrito en tus agentes. + +## Siguientes pasos + +- [Configurar tu agente](/es/docs/agent-setup): conecta primero tu herramienta al clúster. +- [Servidor MCP](/es/docs/mcp): añade además la búsqueda web de NaN a tu agente. +- [Elige tu modelo](/es/docs/choose-a-model): qué modelo pedir para cada tarea. diff --git a/src/content/docs-es/getting-started.md b/src/content/docs-es/getting-started.md deleted file mode 100644 index 4408838..0000000 --- a/src/content/docs-es/getting-started.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -title: Primeros pasos -description: Configura tu IDE o herramienta favorita para conectarte a los modelos de NaN. -order: 1 -group: Primeros pasos ---- - -# Primeros pasos. - -El acceso es vía LiteLLM con una API compatible con OpenAI. Funciona con cualquier herramienta que acepte una `base URL` y una `API key`: Cursor, Cline, Continue, Aider, Open Code, Open WebUI o cualquier SDK compatible con OpenAI. - -## Consigue tu API Key - -Tienes que ser miembro de la comunidad de NaN. Si ya estás suscrito, genera tu API Key desde los ajustes de usuario, en el apartado "API Keys" de la [plataforma](https://cloud.nan.builders/). La key es personal e intransferible. - -> **Nota** -> El soporte es solo para incidencias técnicas. - -## Configura tu herramienta - -| Campo | Valor | -|---|---| -| base URL | `https://api.nan.builders/v1` | -| API Key | `sk-your-key-here` | -| Modelo | `qwen3.6` | - -Ejemplo de configuración compatible con OpenAI: - -```json -provider: { - openai: { - npm: "@ai-sdk/openai", - name: "NaN", - apiKey: "sk-your-key-here", - baseURL: "https://api.nan.builders/v1", - model: "qwen3.6" - } -} -``` diff --git a/src/content/docs-es/getting-started.mdx b/src/content/docs-es/getting-started.mdx new file mode 100644 index 0000000..039ecca --- /dev/null +++ b/src/content/docs-es/getting-started.mdx @@ -0,0 +1,167 @@ +--- +title: Primeros pasos +description: De cero a tu primera respuesta del clúster, y de ahí a tu editor. +order: 1 +group: Primeros pasos +--- + +import Steps from '../../components/docs/Steps.astro'; +import Details from '../../components/docs/Details.astro'; +import Callout from '../../components/docs/Callout.astro'; + +# Primeros pasos. + +La API de NaN es compatible con OpenAI. Solo necesitas dos datos, una **base URL** y una **API key**, y cualquier herramienta o SDK que acepte esos dos campos funciona con NaN sin tocar nada más. + +| Campo | Valor | +|---|---| +| Base URL | `https://api.nan.builders/v1` | +| API key | la tuya, empieza por `sk-` | +| Modelo para empezar | `deepseek-v4-flash` | + + + +### Consigue tu API key + +Tienes que ser miembro de la comunidad NaN. Entra en [cloud.nan.builders](https://cloud.nan.builders/), abre los ajustes de usuario y ve a la sección **API Keys** para generar la tuya. + +La clave es personal e intransferible, y se muestra una sola vez: cópiala en cuanto la generes. Si la pierdes, no pasa nada, genera otra y borra la vieja desde el mismo panel. + + +Una clave subida a un repositorio la encuentra un bot en cuestión de minutos. Guárdala en una variable de entorno y léela desde ahí, como en los ejemplos de esta página. + + +### Guárdala en una variable de entorno + +```bash +# macOS y Linux +export NAN_API_KEY="sk-tu-clave" +``` + +```powershell +# Windows, PowerShell +$env:NAN_API_KEY = "sk-tu-clave" +``` + +Así solo dura lo que dure esa terminal. Para que sobreviva a cerrarla, añade la línea a tu `~/.zshrc`, a tu `~/.bashrc` o a tu perfil de PowerShell. + +### Haz tu primera llamada + +```bash +curl https://api.nan.builders/v1/chat/completions \ + -H "Authorization: Bearer $NAN_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "model": "deepseek-v4-flash", + "messages": [{ "role": "user", "content": "Preséntate en una línea." }] + }' +``` + +Si todo va bien recibes un JSON y el texto del modelo está en `choices[0].message.content`: + +```json +{ + "id": "chatcmpl-...", + "model": "deepseek-v4-flash", + "choices": [ + { "index": 0, "message": { "role": "assistant", "content": "Soy un modelo abierto..." } } + ], + "usage": { "prompt_tokens": 14, "completion_tokens": 23, "total_tokens": 37 } +} +``` + +Ya tienes acceso al clúster. Todo lo demás son variaciones de esta misma llamada. + +### Lo mismo desde tu código + +Usa el SDK oficial de OpenAI y cámbiale la `base_url`. No hay librería de NaN que instalar. + +```python +# pip install openai +import os +from openai import OpenAI + +client = OpenAI( + api_key=os.environ["NAN_API_KEY"], + base_url="https://api.nan.builders/v1", +) + +resp = client.chat.completions.create( + model="deepseek-v4-flash", + messages=[{"role": "user", "content": "Preséntate en una línea."}], +) +print(resp.choices[0].message.content) +``` + +```javascript +// npm install openai +import OpenAI from 'openai'; + +const client = new OpenAI({ + apiKey: process.env.NAN_API_KEY, + baseURL: 'https://api.nan.builders/v1', +}); + +const resp = await client.chat.completions.create({ + model: 'deepseek-v4-flash', + messages: [{ role: 'user', content: 'Preséntate en una línea.' }], +}); +console.log(resp.choices[0].message.content); +``` + +Tienes más ejemplos, incluidos embeddings, voz, imágenes y búsqueda web, en [Ejemplos](/es/docs/examples). + +### Elige el modelo + +`deepseek-v4-flash` es un buen punto de partida, pero no es el único ni el mejor para todo. La lista completa, con cuál conviene para cada cosa, está en [Elige tu modelo](/es/docs/choose-a-model). + +Y la lista que de verdad puede usar **tu** clave siempre te la da la API: + +```bash +curl https://api.nan.builders/v1/models \ + -H "Authorization: Bearer $NAN_API_KEY" +``` + +### Conecta tu editor o tu agente + +Cursor, Claude Code, Codex, VS Code, Cline, OpenCode, Zed y compañía se configuran con esos mismos dos datos. Cada uno los pide en un sitio distinto, y eso es lo que recoge [Configurar tu agente](/es/docs/agent-setup), con una página por herramienta. + +Si usas OpenCode, Codex, Pi o droid, el [CLI de NaN](/es/docs/nan-cli) te los configura sin que tengas que editar nada. + + + +## Si algo falla + +Todos los errores llegan con el mismo formato que los de OpenAI: un JSON con `error.message`, `error.type` y un `error.code` corto con el que puedes ramificar en tu código. + +| Código | Qué ha pasado | Qué hacer | +|---|---|---| +| `401` | La clave falta, está mal escrita o ya no existe | Revisa la cabecera `Authorization: Bearer ...` | +| `402` | Has gastado la cuota de tokens de ese modelo | Cambia de modelo o espera a que empiece tu siguiente periodo | +| `403` | Tu plan no llega a ese modelo o a ese endpoint | `glm5.3` necesita el tier premium | +| `404` | Ese id de modelo no existe | Comprueba cómo se escribe en [Elige tu modelo](/es/docs/choose-a-model) o con `GET /v1/models` | +| `429` | Demasiadas peticiones a la vez, o cuota agotada | Reintenta esperando cada vez un poco más | +| `5xx` | El fallo es nuestro | Reintenta; si insiste, cuéntalo en `#support` | + +Reintentar solo tiene sentido con `429` y con los `5xx`. Un `401`, un `403` o un `404` van a fallar exactamente igual hasta que cambies la petición, y el `402` no se arregla insistiendo: el contador vuelve a cero cuando empieza tu periodo de facturación. + +El detalle completo, endpoint a endpoint, está en la [referencia de la API](/es/docs/api). + +
+ +Los límites van por API key, no por modelo: un tope de peticiones por minuto y un máximo de peticiones a la vez. + +`glm5.3` es el único que además se rige por una ventana móvil de tokens, y la búsqueda web tiene su propio presupuesto, separado del de los modelos. + +Las cifras vigentes están al final de [Modelos](/es/docs/models), que es donde se publican para que no haya dos versiones de la misma cifra dando vueltas. + +
+ +## Siguientes pasos + +- [Elige tu modelo](/es/docs/choose-a-model): qué modelo pedir para cada tarea, y cómo se escribe su id. +- [Configurar tu agente](/es/docs/agent-setup): Cursor, Claude Code, Codex, VS Code, Cline, OpenCode, Zed y el resto. +- [Gentle-AI](/es/docs/gentle-ai): lo que recomendamos configurar encima, una vez conectado. +- [Ejemplos](/es/docs/examples): fragmentos listos para copiar en Python, Node.js y curl. +- [Referencia de la API](/es/docs/api): todos los endpoints, campos y errores. +- Soporte: `#support` en Discord, solo para cuestiones técnicas. diff --git a/src/content/docs-es/hermes.mdx b/src/content/docs-es/hermes.mdx new file mode 100644 index 0000000..9feda22 --- /dev/null +++ b/src/content/docs-es/hermes.mdx @@ -0,0 +1,90 @@ +--- +title: Hermes +description: El agente de Nous Research, con los modelos de NaN. En tu máquina o desplegado en NaN Cloud. +order: 14 +group: Configurar tu agente +--- + +import Steps from '../../components/docs/Steps.astro'; +import Details from '../../components/docs/Details.astro'; +import Callout from '../../components/docs/Callout.astro'; + +# Hermes. + +[Hermes](https://hermes-agent.nousresearch.com/) es el agente de Nous Research, de código abierto y con licencia MIT. No es un agente de código como el resto de esta sección: vive en tus canales de mensajería (Telegram, Discord, Slack, WhatsApp, Signal, correo) además de en la terminal, recuerda lo que hablasteis, se agenda tareas y delega en subagentes. + +Hay dos formas de tenerlo, y la primera no requiere configurar nada. + +## Camino 1: desplegado en NaN Cloud + +NaN Cloud despliega Hermes por ti en una **microVM propia**, y sale conectado al clúster de fábrica: la plataforma le inyecta tu clave y no tienes que tocar ninguna configuración de proveedor. + + +Creas el agente desde el panel, eliges modelo, le das el token de tu bot de Telegram y ya está hablando. Está explicado paso a paso en [Agentes](/es/docs/agents), con capturas. + + +Además tienes terminal web, subida de ficheros, logs y métricas, y puedes exponerlo en una URL pública. Todo eso solo lo tienes por este camino. + +## Camino 2: en tu propia máquina + +Si prefieres correrlo tú, Hermes trae un proveedor llamado **`custom`**, pensado para cualquier endpoint compatible con OpenAI. Es el que usa NaN. + + + +### Instálalo + +En macOS y Linux: + +```bash +curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash +``` + +Para macOS 12 o superior y para Windows 10 y 11 hay además instaladores de escritorio en su web. + +### Configúralo + +```bash +hermes setup +``` + +Elige el proveedor **`custom`** (aparece también como `local`, `ollama` o `vllm`: es el mismo, el de endpoints compatibles con OpenAI) y dale los tres datos: + +| Campo | Valor | +|---|---| +| Base URL | `https://api.nan.builders/v1` | +| API key | tu clave, la que empieza por `sk-` | +| Modelo | `glm5.3-flash` | + +A diferencia de los proveedores que Hermes trae preconfigurados, el `custom` **no tiene variable de entorno fija**: la dirección y la clave se las das tú aquí, y las guarda en `~/.hermes/config.yaml`. + +### Comprueba que funciona + +```bash +hermes doctor +``` + +Te dice si el proveedor responde. Después arranca el agente y mándale un mensaje por el canal que hayas configurado. + + + +## Cambiar de modelo + +El modelo por defecto vive en `~/.hermes/config.yaml`, en `model.default`. No se toca a mano: + +```bash +hermes model +``` + +## Modelo recomendado + +`glm5.3-flash` si vas a pedirle tareas largas o que delegue en subagentes, por su contexto de 1M de tokens. `deepseek-v4-flash` para conversación y tareas normales, que además tiene la cuota más amplia del clúster. + +Si lo vas a usar para mandarle fotos por Telegram, cualquiera menos `glm5.3`, que es el único que no acepta imágenes. + +
+ +- **Los dos caminos no se mezclan.** El Hermes de NaN Cloud se configura desde el panel y el tuyo desde `hermes setup`. Si tienes los dos, son dos agentes distintos con dos memorias distintas. +- **El proveedor `custom` no autodescubre los modelos.** Como no hay una lista fija detrás, escribe el id exactamente como aparece en [Elige tu modelo](/es/docs/choose-a-model). +- **Un agente que vive en tus mensajes consume a su ritmo.** No es una sesión que abres y cierras: si le das tareas agendadas, gasta cuota mientras tú no miras. Revísala en [cloud.nan.builders](https://cloud.nan.builders/). + +
diff --git a/src/content/docs-es/intro.md b/src/content/docs-es/intro.md index 5594671..8ed7e03 100644 --- a/src/content/docs-es/intro.md +++ b/src/content/docs-es/intro.md @@ -12,16 +12,24 @@ Esta documentación explica cómo conectar tus herramientas a nuestras GPUs. El > **Para conseguir tu API Key** > Tienes que ser miembro de la comunidad de NaN. Puedes generar tu API Key desde los ajustes de usuario, en el apartado "API Keys" de la [plataforma](https://cloud.nan.builders/). La key es personal e intransferible. -## Rate limits +## Lo esencial -| Métrica | Valor | +| Campo | Valor | |---|---| -| Peticiones por minuto | 60 rpm | -| Máximo en paralelo | 5 concurrentes | +| Base URL | `https://api.nan.builders/v1` | +| Autenticación | `Authorization: Bearer sk-tu-clave` | +| Formato | Compatible con OpenAI | + +Los límites van por API key: un tope de peticiones por minuto y un máximo de peticiones a la vez. Las cifras vigentes están al final de [Modelos](/es/docs/models), que es donde se publican para que no haya dos versiones de la misma cifra. ## Por dónde seguir -- [Primeros pasos](/es/docs/getting-started): endpoint, autenticación y configuración paso a paso. -- [Modelos](/es/docs/models): capacidades y límites de los modelos. +- [Primeros pasos](/es/docs/getting-started): de cero a tu primera respuesta, con curl, Python y Node. +- [Elige tu modelo](/es/docs/choose-a-model): qué modelo pedir para cada tarea y cómo se escribe su id. +- [Configurar tu agente](/es/docs/agent-setup): Claude Code, Codex, Cursor, Cline, OpenCode, Zed y compañía. +- [CLI de NaN](/es/docs/nan-cli): la herramienta oficial de terminal, que además configura varias de ellas por ti. +- [Servidor MCP](/es/docs/mcp): la búsqueda web de NaN dentro de tu agente. +- [Referencia de la API](/es/docs/api): todos los endpoints, campo a campo. +- [Modelos](/es/docs/models): las fichas técnicas y los límites. - [Ejemplos](/es/docs/examples): fragmentos en Python, Node.js y curl. - Soporte: reporta incidencias en `#support` de Discord. diff --git a/src/content/docs-es/mcp.mdx b/src/content/docs-es/mcp.mdx new file mode 100644 index 0000000..e8f3a39 --- /dev/null +++ b/src/content/docs-es/mcp.mdx @@ -0,0 +1,124 @@ +--- +title: Servidor MCP +description: Enchufa la búsqueda web de NaN en cualquier agente compatible con MCP. +order: 16 +group: Configurar tu agente +--- + +import Details from '../../components/docs/Details.astro'; + +# Servidor MCP. + +Además de los modelos, NaN expone sus propias herramientas a través de un servidor **MCP** ([Model Context Protocol](https://modelcontextprotocol.io)). Sirve para lo contrario que el resto de esta sección: aquí no le das modelos a tu agente, le das **capacidades**. + +Hoy la herramienta disponible es la **búsqueda web**. El registro irá creciendo, así que pregúntale al servidor qué tiene en vez de fiarte de esta frase. + +| Campo | Valor | +|---|---| +| URL | `https://api.nan.builders/mcp` | +| Autenticación | `Authorization: Bearer sk-tu-clave` | +| Transporte | HTTP, sin estado | + +> **Esta URL no lleva `/v1`** +> El servidor MCP vive en la raíz del dominio, no debajo de `/v1` como el resto de la API. Es `https://api.nan.builders/mcp`, sin nada más. + +La clave es la misma que usas para los modelos. No hay que generar otra. + +## Configuración genérica + +Casi todos los clientes MCP usan este mismo fichero, con este mismo bloque: + +```json +{ + "mcpServers": { + "nan": { + "url": "https://api.nan.builders/mcp", + "headers": { + "Authorization": "Bearer sk-tu-clave" + } + } + } +} +``` + +Dónde va ese fichero depende del cliente: + +| Cliente | Dónde | +|---|---| +| Cursor | `.cursor/mcp.json` en el proyecto, o `~/.cursor/mcp.json` | +| Cline | El panel de MCP Servers dentro de la extensión | +| Zed | El bloque `context_servers` de `~/.config/zed/settings.json` | +| OpenCode | El bloque `mcp` de tu `opencode.json` | + +## Claude Code + +Claude Code lo añade por línea de comandos: + +```bash +claude mcp add --transport http nan https://api.nan.builders/mcp \ + --header "Authorization: Bearer sk-tu-clave" +``` + +Añade `--scope user` si lo quieres disponible en todos tus proyectos y no solo en el actual. Dentro de una sesión, `/mcp` te enseña los servidores conectados y las herramientas que ofrecen. + +Esto es independiente de los modelos: puedes usar la búsqueda web de NaN desde Claude Code aunque los modelos te los sirva otro sitio. + +## Comprueba que funciona + +Sin cliente de por medio, preguntándole al servidor directamente qué herramientas tiene: + +```bash +curl https://api.nan.builders/mcp \ + -H "Authorization: Bearer $NAN_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ "jsonrpc": "2.0", "id": 1, "method": "tools/list" }' +``` + +La respuesta trae la lista de herramientas con sus argumentos. Ahí verás siempre el conjunto real, que es más fiable que cualquier lista escrita a mano. + +Y una búsqueda de verdad: + +```bash +curl https://api.nan.builders/mcp \ + -H "Authorization: Bearer $NAN_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "id": 2, + "method": "tools/call", + "params": { + "name": "web_search", + "arguments": { "query": "kubernetes 1.34 release", "count": 5 } + } + }' +``` + +## La herramienta `web_search` + +Acepta los mismos argumentos que el endpoint [`POST /v1/search`](/es/docs/api): + +| Argumento | Qué hace | +|---|---| +| `query` | La búsqueda. Es el único obligatorio | +| `count` | Cuántos resultados, de 1 a 20. Por defecto 5 | +| `freshness` | Filtro de antigüedad: `pd` día, `pw` semana, `pm` mes, `py` año | +| `fetch_content` | Con `true`, además del resumen trae el texto de las páginas. Tarda más | + +Las búsquedas salen por NaN, así que tu clave nunca habla con un buscador externo y no necesitas darte de alta en ninguno. + +## Límites + +La búsqueda web tiene su propio presupuesto, separado del de los modelos: **20 peticiones por minuto, 3 a la vez y 500 búsquedas al día** por clave. Buscar no gasta tu cuota de chat ni al revés. + +Da igual si la llamada entra por MCP o por `POST /v1/search`: cuenta lo mismo en el mismo contador. Una búsqueda repetida en los 15 minutos siguientes se sirve de una caché corta y llega marcada con `cached: true`, pero sigue contando. + +Si te pasas, la respuesta es un `429` con una cabecera `Retry-After` que te dice cuánto esperar. + +
+ +- **El servidor no guarda estado.** Cada petición es independiente y lleva su propia autenticación. No hay sesión que mantener abierta. +- **Algunos clientes no reenvían las cabeceras que declaras** en todas las fases de la conexión. Si el cliente se conecta pero luego falla al llamar a una herramienta con un error de autenticación, suele ser eso, y no tu clave. +- **La lista de herramientas cambia.** Usa `tools/list` antes de dar por hecho que una herramienta existe. + +
+ diff --git a/src/content/docs-es/models.mdx b/src/content/docs-es/models.mdx index 9538ffb..096d1ed 100644 --- a/src/content/docs-es/models.mdx +++ b/src/content/docs-es/models.mdx @@ -1,7 +1,7 @@ --- title: Modelos description: Especificaciones técnicas, capacidades y parámetros de los modelos del clúster compartido. -order: 3 +order: 18 group: Referencia --- @@ -15,79 +15,78 @@ Modelos de la comunidad. A todos se accede con la misma API compatible con OpenAI y la misma `base URL`. max_tokens ≥ 300)', + 'Tool calling', + 'Modo razonamiento', 'Visión (entrada de imagen)', - 'Audio (entrada de audio)', 'Contexto de 1M tokens', 'Generación en streaming (SSE)', ]} /> @@ -116,25 +115,26 @@ OpenAI y la misma `base URL`. /> max_tokens ≥ 300)', 'Visión (entrada de imagen)', + 'Audio (entrada de audio)', 'Contexto de 1M tokens', 'Generación en streaming (SSE)', ]} diff --git a/src/content/docs-es/nan-cli.mdx b/src/content/docs-es/nan-cli.mdx new file mode 100644 index 0000000..a2269cb --- /dev/null +++ b/src/content/docs-es/nan-cli.mdx @@ -0,0 +1,161 @@ +--- +title: CLI de NaN +description: "La herramienta oficial de terminal: consumo, costes y configuración automática de OpenCode, Codex, Pi y droid." +order: 4 +group: Configurar tu agente +--- + +import Steps from '../../components/docs/Steps.astro'; +import Details from '../../components/docs/Details.astro'; +import Callout from '../../components/docs/Callout.astro'; + +# CLI de NaN. + +NaN tiene una herramienta de terminal oficial, [`helmcode/nan-cli`](https://github.com/helmcode/nan-cli). Hace dos cosas: te enseña tu consumo real y **configura tus herramientas de código por ti**, sin que tengas que editar ficheros de configuración a mano. + +Es un atajo, no un requisito. Todo lo que hace se puede hacer a mano siguiendo la página de tu herramienta en esta misma sección. + +## Qué configura sola + +| Herramienta | Configuración automática | +|---|---| +| [OpenCode](/es/docs/opencode) | Sí | +| [Codex](/es/docs/codex) | Sí | +| Pi | Sí | +| Factory AI (`droid`) | Sí | + +Para Claude Code, Cursor, VS Code, Cline y Zed sigue haciendo falta la configuración manual de sus páginas. + +## Qué más te da + +El CLI abre un panel con pestañas que se mueve con las flechas: + +| Pestaña | Para qué | +|---|---| +| Profile | Los datos de tu cuenta | +| Usage | Tokens consumidos en 24 horas, 30 días y desde siempre | +| Models | Los modelos disponibles y cuánto has gastado en cada uno | +| Costs | Lo que te habría costado lo mismo en otros proveedores | +| Setup | Tu API key y la configuración automática de herramientas | +| About | Versión y enlaces | + +La pestaña **Usage** es la forma más rápida de saber cuánta cuota te queda antes de arrancar una sesión larga de agente. + +## Instalación + + + +### Instala el binario + +En macOS y Linux, una línea: + +```bash +curl -fsSL https://nan.builders/install | bash +``` + +El instalador mira tu sistema y tu arquitectura, se baja el binario que toca de la última versión publicada, **comprueba su checksum** y lo deja en `/usr/local/bin/nan`. Si esa carpeta necesita permisos de administrador, te pedirá la contraseña por `sudo`. + +Para ponerlo en otro sitio, dale la carpeta tú: + +```bash +INSTALL_DIR="$HOME/.local/bin" curl -fsSL https://nan.builders/install | bash +``` + +Si la carpeta que elijas no está en tu `PATH`, el propio instalador te lo dice y te da la línea que hay que añadir a tu `~/.zshrc` o a tu `~/.bashrc`. + +Hay binarios para macOS y Linux, en Intel y en ARM. En Windows, de momento, hay que compilar desde el código, y el bloque de abajo explica cómo. + +### Comprueba que ha ido bien + +```bash +nan --version +``` + +Tiene que responder con la versión y con el nombre de la herramienta. Si la terminal dice que no encuentra la orden, el binario está instalado pero su carpeta no está en tu `PATH`. + +### Consigue tu API key + +En [cloud.nan.builders](https://cloud.nan.builders/), en los ajustes de usuario, apartado **API Keys**. + + +Ese comando apunta a una dirección de acceso por Discord que ya no existe y acaba en un `404` en el navegador. No es culpa de tu máquina. El camino bueno es sacar la clave del panel y pegarla en la pestaña Setup. + + +### Configura tus herramientas + +```bash +nan +``` + +Dentro del panel, ve a la pestaña **Setup** con las flechas. Pulsa `e` y pega tu API key. Con `espacio`, marca las herramientas que quieres configurar. Pulsa `c` para aplicar. + +El CLI escribe la configuración de cada herramienta marcada. Sal con `q` y arranca tu agente: + +```bash +opencode +``` + +Dentro de OpenCode, `/models` te enseña los modelos de NaN ya disponibles. + + + +
+ +Es la vía para Windows, y la que quieres si vas a tocar el CLI. Hace falta [Go](https://go.dev) 1.26 o superior: + +```bash +git clone https://github.com/helmcode/nan-cli +cd nan-cli +go build -o nan . +``` + +Eso deja el binario en la carpeta del repositorio. Para tenerlo a mano desde cualquier sitio, muévelo a una carpeta de tu `PATH`: + +```bash +mkdir -p ~/.local/bin +mv ./nan ~/.local/bin/nan +``` + +Si esa carpeta no está aún en tu `PATH`, añádela al fichero de arranque de tu terminal. En macOS la terminal por defecto es **zsh**, así que el fichero es `~/.zshrc` y no `~/.bashrc`: + +```bash +echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc +source ~/.zshrc +``` + +En Linux con bash, el mismo comando pero contra `~/.bashrc`. + +
+ +## Actualizar + +Vuelve a pasar el instalador. Siempre trae la última versión publicada y sobrescribe la que tengas: + +```bash +curl -fsSL https://nan.builders/install | bash +``` + +Para quitarlo, borra el binario: `rm /usr/local/bin/nan`, o la ruta que le hayas dado con `INSTALL_DIR`. No deja nada más en tu sistema. + +## Si prefieres no instalar nada + +Es completamente razonable. Ve directamente a la página de tu herramienta y copia el bloque de configuración: + +- [OpenCode](/es/docs/opencode) +- [Codex](/es/docs/codex) +- [Claude Code](/es/docs/claude-code) +- [Cursor](/es/docs/cursor) +- [VS Code y Copilot](/es/docs/vscode) +- [Cline](/es/docs/cline) +- [Zed](/es/docs/zed) +- [Otras herramientas](/es/docs/other-tools) + +El resultado es el mismo. Lo único que te pierdes son las pestañas de consumo y costes. + +
+ +- **Profile, Usage y Costs necesitan una sesión que hoy no se puede crear.** El acceso por Discord del CLI apunta a un endpoint retirado. La pestaña Setup, que es la que configura tus herramientas, funciona solo con la API key y no se ve afectada. +- **En Windows hay que compilar.** Las versiones publicadas traen binarios de macOS y Linux; para Windows, de momento, la vía es `go build`. +- **El instalador pide la lista de versiones a GitHub.** Si estás detrás de un proxy que bloquea `api.github.com`, no encontrará nada que descargar. En ese caso, baja el `.tar.gz` de tu sistema a mano desde la [página de releases](https://github.com/helmcode/nan-cli/releases). + +
diff --git a/src/content/docs-es/opencode.mdx b/src/content/docs-es/opencode.mdx new file mode 100644 index 0000000..ba12612 --- /dev/null +++ b/src/content/docs-es/opencode.mdx @@ -0,0 +1,132 @@ +--- +title: OpenCode +description: Declara NaN como proveedor en OpenCode y usa todos los modelos del clúster. +order: 11 +group: Configurar tu agente +--- + +import Details from '../../components/docs/Details.astro'; + +# OpenCode. + +OpenCode es un agente de terminal de código abierto. Se le declara NaN como un proveedor más y a partir de ahí puedes cambiar de modelo desde el propio agente. + +> **Puedes saltarte todo esto** +> El [CLI de NaN](/es/docs/nan-cli) escribe esta configuración por ti: pestaña Setup, `e` para pegar tu clave, `espacio` para marcar OpenCode, `c` para aplicar. + +## Configuración + +Escribe esto en `~/.config/opencode/opencode.json` para tenerlo en todos tus proyectos, o en un `opencode.json` en la raíz del proyecto si lo quieres solo ahí: + +```json +{ + "$schema": "https://opencode.ai/config.json", + "provider": { + "nan": { + "npm": "@ai-sdk/openai-compatible", + "name": "NaN", + "options": { + "baseURL": "https://api.nan.builders/v1", + "apiKey": "sk-tu-clave" + }, + "models": { + "deepseek-v4-flash": { + "name": "DeepSeek V4 Flash", + "limit": { "context": 1048575, "output": 32768 }, + "modalities": { "input": ["text", "image"], "output": ["text"] } + }, + "glm5.3-flash": { + "name": "GLM 5.3 Flash", + "limit": { "context": 1048576, "output": 32768 }, + "modalities": { "input": ["text", "image"], "output": ["text"] } + }, + "qwen3.8-flash": { + "name": "Qwen 3.8 Flash", + "limit": { "context": 262144, "output": 32768 }, + "modalities": { "input": ["text", "image"], "output": ["text"] } + }, + "mimo-v2.5": { + "name": "Xiaomi MiMo V2.5", + "limit": { "context": 1048576, "output": 32768 }, + "modalities": { "input": ["text", "image", "audio"], "output": ["text"] } + }, + "glm5.3": { + "name": "GLM 5.3 (premium)", + "limit": { "context": 1048576, "output": 32768 }, + "modalities": { "input": ["text"], "output": ["text"] } + } + } + } + }, + "compaction": { + "auto": true, + "prune": true, + "reserved": 50000 + } +} +``` + +`@ai-sdk/openai-compatible` es el adaptador genérico, el que habla con cualquier API con forma de OpenAI. No uses `@ai-sdk/openai` a secas: ese espera la API de OpenAI de verdad. + +De todo lo que hay dentro de cada modelo, lo único obligatorio es su clave, que es el id. `name`, `limit` y `modalities` son opcionales: sin ellos OpenCode funciona igual, solo que en el selector verás el id pelado y la compactación automática trabajará con sus valores por defecto. Si prefieres un fichero corto, quítalos. + +`glm5.3` solo responderá si tu clave está en el tier premium. Si no lo está, déjalo fuera de la lista para no seleccionarlo por error. + +## Dónde poner la clave + +En el ejemplo de arriba la clave va escrita dentro del fichero, que es lo más directo pero no lo más cómodo si ese fichero acaba en un repositorio. + +La alternativa es dejar `apiKey` fuera del `opencode.json` y guardarla con el comando `/connect` de OpenCode, que la escribe en su propio almacén de credenciales: + +```text +/connect +``` + +Te pedirá un **Provider id**. Escribe exactamente: + +```text +nan +``` + +> **El Provider id tiene que coincidir con la clave del JSON** +> OpenCode empareja la credencial con el proveedor por ese identificador. Si en `opencode.json` tu proveedor se llama `nan` y en `/connect` escribes `NaN`, `nan-builders` o cualquier otra cosa, OpenCode guarda la credencial pero no la asocia a ningún proveedor, y las peticiones salen sin clave. Es el fallo más habitual al conectar OpenCode con NaN y no da un mensaje que lo explique. + +Después pega tu API key y pulsa Enter. + +## Los límites de contexto + +```json +"limit": { "context": 1048575, "output": 32768 } +``` + +`limit.context` y `limit.output` son los campos que OpenCode lee. Una versión anterior de esta documentación publicaba `contextWindow`, que no existe en [el esquema de OpenCode](https://opencode.ai/config.json): una clave desconocida no da ningún error que nadie vea, OpenCode se queda con su propia suposición sobre la ventana, y el síntoma es una sesión que compacta demasiado pronto en los modelos de contexto largo. + +`limit.context` es la ventana que acepta el proxy, que no siempre es aquella con la que se entrenó el modelo: `qwen3.8-flash` se sirve en sus 262K nativos, no en el 1M extendido con YaRN. `limit.output` es un presupuesto del cliente, no un tope del servidor, así que súbelo si necesitas respuestas más largas. + +## El bloque de compactación + +```json +"compaction": { "auto": true, "prune": true, "reserved": 50000 } +``` + +OpenCode resume la conversación por su cuenta cuando se acerca al límite de contexto, y `reserved` es el margen de tokens que se guarda para hacerlo. Con ventanas de 1M de tokens no vas a rozar el límite en una sesión normal, pero dejarlo activado evita que una sesión muy larga se corte de golpe. + +## Comprueba que funciona + +```bash +opencode +``` + +Dentro del agente, elige el modelo con `/models` y pídele algo corto. Si responde, ya está saliendo por el clúster. + +## Modelo recomendado + +`glm5.3-flash` para código, `deepseek-v4-flash` para lo demás. Si tienes el tier premium, `glm5.3` para las sesiones largas. + +
+ +- **La clave va escrita en el fichero.** Si guardas el `opencode.json` dentro del repositorio, la clave se va con él. Tenlo en el fichero de tu carpeta personal, o añade el fichero a `.gitignore`. +- **Las ventanas son las de verdad.** Están medidas contra el proxy, y son las mismas cifras que publica [Ejemplos](/es/docs/examples). Si las subes a mano, OpenCode llenará la conversación hasta un punto en el que el modelo empieza a rechazar peticiones. + +
+ diff --git a/src/content/docs-es/other-tools.md b/src/content/docs-es/other-tools.md new file mode 100644 index 0000000..8455e03 --- /dev/null +++ b/src/content/docs-es/other-tools.md @@ -0,0 +1,122 @@ +--- +title: Otras herramientas +description: Continue, Aider, OpenClaw y cualquier interfaz compatible con OpenAI. +order: 15 +group: Configurar tu agente +--- + +# Otras herramientas. + +Todo lo que acepte una base URL y una API key de OpenAI funciona con NaN. Aquí están las configuraciones de las herramientas que no tienen página propia. + +En todas, los dos datos son los mismos: + +| Campo | Valor | +|---|---| +| Base URL | `https://api.nan.builders/v1` | +| API key | tu clave, la que empieza por `sk-` | + +## Continue + +Extensión para VS Code y JetBrains. Edita `~/.continue/config.yaml`: + +```yaml +name: NaN +version: 1.0.0 +schema: v1 +models: + - name: GLM 5.3 Flash + provider: openai + model: glm5.3-flash + apiBase: https://api.nan.builders/v1 + apiKey: sk-tu-clave + roles: + - chat + - edit + - name: DeepSeek V4 Flash + provider: openai + model: deepseek-v4-flash + apiBase: https://api.nan.builders/v1 + apiKey: sk-tu-clave + roles: + - chat +``` + +`provider: openai` no significa que llame a OpenAI: es el nombre del adaptador que habla ese formato. Lo que decide a dónde va la petición es `apiBase`. + +## Aider + +Agente de terminal que trabaja sobre tu repositorio de git. Aider enruta por el prefijo del modelo, así que el id tiene que ir precedido de `openai/`: + +```bash +export OPENAI_API_BASE="https://api.nan.builders/v1" +export OPENAI_API_KEY="sk-tu-clave" + +aider --model openai/glm5.3-flash +``` + +Para no repetirlo cada vez, déjalo en `~/.aider.conf.yml`: + +```yaml +openai-api-base: https://api.nan.builders/v1 +model: openai/glm5.3-flash +``` + +Sin el prefijo `openai/`, Aider intenta adivinar el proveedor a partir del nombre, no lo reconoce y falla antes de llegar a hacer la petición. + +## Pi + +Tiene página propia: [Pi](/es/docs/pi). + +## OpenClaw + +Configura `~/.openclaw/openclaw.json`: + +```json +{ + "models": { + "providers": { + "nan": { + "baseUrl": "https://api.nan.builders/v1", + "apiKey": "sk-tu-clave", + "api": "openai-completions", + "models": [ + { + "id": "glm5.3-flash", + "name": "GLM 5.3 Flash", + "reasoning": true, + "input": ["text", "image"], + "contextWindow": 1000000, + "maxTokens": 65536 + } + ] + } + } + }, + "agents": { + "defaults": { + "model": { "primary": "nan/glm5.3-flash" }, + "models": { + "nan/glm5.3-flash": { + "params": { "maxTokens": 16000 } + } + } + } + } +} +``` + +`maxTokens: 65536` es el máximo que admite el modelo. `params.maxTokens: 16000` es lo que se envía en cada petición, que es un buen equilibrio para la mayoría de tareas. Si necesitas respuestas más largas, súbelo, pero ten en cuenta que el razonamiento también sale de ese presupuesto. + +## Open WebUI, LM Studio y demás interfaces de chat + +Todas piden lo mismo, con nombres distintos según la aplicación: una dirección de API de OpenAI y una clave. + +- **Open WebUI**: Settings, Connections, OpenAI API. Pon la base URL y la clave, y los modelos aparecen solos en el selector. +- **LM Studio**: en la pestaña de proveedores remotos, añade un proveedor compatible con OpenAI con esos mismos dos datos. + +Si la aplicación te pide "OpenAI API Base", "API Endpoint" o "Custom base URL", todas son el mismo campo y todas quieren `https://api.nan.builders/v1`. + +## Tu propio código + +No hace falta ninguna herramienta: el SDK oficial de OpenAI, en Python o en JavaScript, funciona cambiándole la base URL. Está en [Primeros pasos](/es/docs/getting-started) y con más ejemplos en [Ejemplos](/es/docs/examples). diff --git a/src/content/docs-es/pi.mdx b/src/content/docs-es/pi.mdx new file mode 100644 index 0000000..d44d5ab --- /dev/null +++ b/src/content/docs-es/pi.mdx @@ -0,0 +1,96 @@ +--- +title: Pi +description: Declara NaN como proveedor en Pi y déjalo como el que usa por defecto. +order: 13 +group: Configurar tu agente +--- + +import Steps from '../../components/docs/Steps.astro'; +import Details from '../../components/docs/Details.astro'; +import Callout from '../../components/docs/Callout.astro'; + +# Pi. + +Pi es un agente de terminal deliberadamente pequeño: un armazón mínimo que se adapta a tu forma de trabajar en vez de imponerte la suya. Habla el formato de OpenAI, así que NaN entra como un proveedor más. + + +El [CLI de NaN](/es/docs/nan-cli) escribe esta configuración por ti: pestaña Setup, `e` para pegar tu clave, `espacio` para marcar Pi, `c` para aplicar. + + +## Configuración + + + +### Declara el proveedor + +En `~/.pi/agent/models.json`: + +```json +{ + "providers": { + "nan": { + "baseUrl": "https://api.nan.builders/v1", + "api": "openai-completions", + "apiKey": "sk-tu-clave", + "compat": { "supportsDeveloperRole": true }, + "models": [ + { + "id": "glm5.3-flash", + "name": "GLM 5.3 Flash", + "reasoning": true, + "input": ["text", "image"], + "contextWindow": 1000000, + "maxTokens": 16384 + }, + { + "id": "deepseek-v4-flash", + "name": "DeepSeek V4 Flash", + "reasoning": true, + "input": ["text", "image"], + "contextWindow": 1000000, + "maxTokens": 16384 + } + ] + } + } +} +``` + +`api: "openai-completions"` es lo que le dice a Pi con qué formato hablar. `maxTokens` es el techo de la respuesta en cada petición, no el contexto. + +### Ponlo por defecto + +En `~/.pi/agent/settings.json`: + +```json +{ + "defaultProvider": "nan", + "defaultModel": "glm5.3-flash" +} +``` + + +Sin `defaultProvider` y `defaultModel`, Pi sigue usando su proveedor de fábrica y lo que recibes es un `401`. Es el fallo más habitual al conectarlo, y el mensaje de error no dice que el problema sea este fichero. + + +### Pruébalo + +```bash +pi +``` + +Pídele algo corto. Si contesta, está saliendo por el clúster. Puedes cambiar de modelo a mitad de sesión sin salir del agente. + + + +## Modelo recomendado + +`glm5.3-flash` para trabajo de código. `deepseek-v4-flash` para lo demás, que además tiene la cuota más amplia del clúster. + +
+ +- **La clave va escrita en el fichero.** `models.json` vive en tu carpeta personal, así que no suele acabar en un repositorio, pero tenlo en cuenta si sincronizas tu configuración entre máquinas. +- **`maxTokens` no es el contexto.** Es el techo de cada respuesta. El razonamiento sale de ese mismo presupuesto, así que si pides respuestas largas y razonadas, súbelo. +- **Los modelos que declares son los que verás.** Pi no le pregunta al clúster qué hay disponible: muestra lo que haya en la lista. + +
diff --git a/src/content/docs-es/vscode.mdx b/src/content/docs-es/vscode.mdx new file mode 100644 index 0000000..86476ce --- /dev/null +++ b/src/content/docs-es/vscode.mdx @@ -0,0 +1,121 @@ +--- +title: VS Code y Copilot +description: Añade los modelos de NaN al chat de Copilot en VS Code con un endpoint propio. +order: 9 +group: Configurar tu agente +--- + +import Steps from '../../components/docs/Steps.astro'; +import Details from '../../components/docs/Details.astro'; +import Callout from '../../components/docs/Callout.astro'; + +# VS Code y Copilot. + +En VS Code los modelos entran por **Copilot Chat**, así que configurar VS Code y configurar Copilot son la misma cosa. Copilot admite modelos propios a través de su proveedor **Custom Endpoint**, que habla el formato de OpenAI. Es justo lo que sirve NaN. + + +Esto afecta al **chat y al modo agente**. El autocompletado en línea, esas sugerencias grises mientras escribes, **sigue siendo de Copilot** y no se puede redirigir. Lo mismo con la búsqueda semántica y lo que depende de los embeddings de GitHub: siguen necesitando tu cuenta. + + +## Qué necesitas + +Una versión de VS Code con el proveedor **Custom Endpoint** (llegó en la 1.122; antes existía un ajuste `github.copilot.chat.customOAIModels` que está obsoleto), la extensión de Copilot Chat instalada y tu API key de NaN. + +## Configuración + + + +### Abre el editor de modelos + +En el selector de modelos del chat, pulsa el engranaje. O abre la paleta de comandos y busca **Chat: Manage Language Models**. + +### Añade un proveedor + +Elige **Add Models** y después **Custom Endpoint**. + +Te pedirá un nombre de grupo, un nombre visible y la API key. Pon `NaN` como nombre y pega tu clave, la que empieza por `sk-`. + +Cuando pregunte el tipo de API, elige **Chat Completions**. + +### Declara los modelos + +Se abre un fichero `chatLanguageModels.json`. Déjalo así: + +```json +[ + { + "name": "NaN", + "vendor": "customendpoint", + "apiKey": "${input:nanApiKey}", + "apiType": "chat-completions", + "models": [ + { + "id": "glm5.3-flash", + "name": "GLM 5.3 Flash", + "url": "https://api.nan.builders/v1/chat/completions", + "toolCalling": true, + "vision": true, + "maxInputTokens": 900000, + "maxOutputTokens": 32000 + }, + { + "id": "deepseek-v4-flash", + "name": "DeepSeek V4 Flash", + "url": "https://api.nan.builders/v1/chat/completions", + "toolCalling": true, + "vision": true, + "maxInputTokens": 900000, + "maxOutputTokens": 32000 + }, + { + "id": "qwen3.8-flash", + "name": "Qwen 3.8 Flash", + "url": "https://api.nan.builders/v1/chat/completions", + "toolCalling": true, + "vision": true, + "maxInputTokens": 240000, + "maxOutputTokens": 32000 + } + ] + } +] +``` + +### Elige el modelo y prueba + +Abre el chat, despliega el selector de modelos y elige uno de los que acabas de añadir. Pídele algo corto. Si responde, está saliendo por el clúster. + + + +## Tres detalles que importan + +- **La `url` va completa, con `/chat/completions` al final.** No es como en el resto de herramientas, donde se pone solo la base. VS Code añade la ruta por su cuenta según el tipo de API, y dársela entera evita que se equivoque. +- **`toolCalling: true` es lo que habilita el modo agente.** Sin eso, el modelo aparece en el chat pero no puede usar herramientas ni editar ficheros. +- **`maxInputTokens` va por debajo del contexto real a propósito.** Es el presupuesto de entrada, y si lo dejas al ras de la ventana no queda sitio para la respuesta. Con 900.000 sobre un millón vas sobrado. + +## La clave + +`"apiKey": "${input:nanApiKey}"` hace que VS Code te la pida y la guarde en su almacén de secretos, en vez de dejarla escrita en el fichero. Es lo que quieres: ese fichero es fácil que acabe en un repositorio o en una copia de seguridad. + +## Modelo recomendado + +`glm5.3-flash` para el modo agente, que es donde importa el contexto largo. `deepseek-v4-flash` para preguntas de chat. `qwen3.8-flash` si prefieres que conteste rápido. + +`glm5.3` solo si tienes el tier premium, y en ese caso pon `"vision": false`: no acepta imágenes. + +
+ +- **El autocompletado no cambia.** Es de Copilot y no se puede redirigir a otro proveedor. Si lo que quieres es que las sugerencias en línea salgan por NaN, la herramienta es [Zed](/es/docs/zed), que sí lo permite. +- **Algunas funciones siguen pidiendo cuenta de GitHub**, como la búsqueda semántica del repositorio. No es un problema de tu clave. +- **El proveedor llegó en la 1.122.** En versiones anteriores había un ajuste `github.copilot.chat.customOAIModels` que ya está obsoleto: si lo tienes puesto, quítalo y usa el proveedor. +- **Si el modelo aparece pero no edita ficheros**, casi siempre es `toolCalling` sin poner o a `false`. + +
+ +
+ +Esta página cubre Copilot **dentro de VS Code**, que es donde vive el proveedor Custom Endpoint. Copilot en JetBrains, en Visual Studio o en su CLI no ofrece el mismo mecanismo de endpoint propio. + +Si trabajas en JetBrains, la vía para usar NaN es otro plugin que acepte base URL de OpenAI, o [Continue](/es/docs/other-tools), que sí tiene versión para JetBrains. + +
diff --git a/src/content/docs-es/zed.mdx b/src/content/docs-es/zed.mdx new file mode 100644 index 0000000..e8f8e45 --- /dev/null +++ b/src/content/docs-es/zed.mdx @@ -0,0 +1,66 @@ +--- +title: Zed +description: Usa los modelos de NaN en el asistente de Zed y en las predicciones en línea. +order: 12 +group: Configurar tu agente +--- + +import Details from '../../components/docs/Details.astro'; + +# Zed. + +Zed acepta proveedores compatibles con OpenAI tanto para el asistente como para las predicciones mientras escribes. + +## Configuración + +Abre `~/.config/zed/settings.json` y añade: + +```json +{ + "language_models": { + "openai": { + "api_url": "https://api.nan.builders/v1", + "available_models": [ + { + "name": "glm5.3-flash", + "display_name": "NaN GLM 5.3 Flash", + "max_tokens": 1000000 + }, + { + "name": "deepseek-v4-flash", + "display_name": "NaN DeepSeek V4 Flash", + "max_tokens": 1000000 + } + ] + } + }, + "edit_predictions": { + "open_ai_compatible_api": { + "api_url": "https://api.nan.builders/v1", + "model": "qwen3.8-flash" + } + } +} +``` + +`name` es el id que viaja en la petición y tiene que escribirse exacto. `display_name` es solo lo que verás tú en el selector, así que ponle lo que quieras. + +## La clave + +Zed no guarda la clave en el `settings.json`. Te la pide desde la interfaz: abre el panel del asistente, entra en la configuración del proveedor OpenAI y pega ahí tu clave de NaN. + +## Comprueba que funciona + +Abre el panel del asistente, elige uno de los modelos que has declarado y pregúntale cualquier cosa. + +## Modelo recomendado + +`glm5.3-flash` para el asistente. Para las predicciones en línea, `qwen3.8-flash`: ahí lo que importa es la latencia, porque el resultado tiene que aparecer mientras escribes. + +
+ +- **No pongas un modelo lento en `edit_predictions`.** Las predicciones se piden muy a menudo y con un modelo grande van a llegar tarde y a gastar cuota para nada. +- **Solo aparecen los modelos que declares.** Zed no pregunta al clúster qué hay disponible: muestra exactamente lo que hayas escrito en `available_models`. + +
+ diff --git a/src/content/docs/agent-setup.mdx b/src/content/docs/agent-setup.mdx new file mode 100644 index 0000000..85b2d37 --- /dev/null +++ b/src/content/docs/agent-setup.mdx @@ -0,0 +1,157 @@ +--- +title: Set up your agent +description: Connect Claude Code, Codex, Cursor, VS Code, Cline, OpenCode, Zed, Pi or Hermes to the NaN cluster. +order: 3 +group: Set up your agent +--- + +import AgentGrid from '../../components/docs/AgentGrid.astro'; +import Callout from '../../components/docs/Callout.astro'; +import Details from '../../components/docs/Details.astro'; + +# Set up your agent. + +Almost every AI coding tool connects to NaN with the same two values: the cluster's **base URL** and your **API key**. What changes from one to the next is where you write them. + +| Field | Value | +|---|---| +| Base URL | `https://api.nan.builders/v1` | +| API key | yours, it starts with `sk-` | + +Every page in this section has the same shape: what you need, the exact configuration with the file path, how to check that it works, and the known issues. + +## Pick your tool + + + +Yours is not here? If it accepts an OpenAI base URL and an API key, it works. Copy the two values from the table above and paste the model id exactly as it appears in [Choose your model](/docs/choose-a-model). + +## Shortcuts and extras + + + +## Why almost all of them work + +NaN exposes an OpenAI-compatible API. That means the routes, the request fields and the shape of the response are the ones these tools already expect, so as far as they are concerned the cluster is indistinguishable from OpenAI except for the address they call. + +The exception is **Claude Code**, which does not speak that format but Anthropic's. It is not a credentials problem: they are two different protocols. That is why its page explains the two paths there are. + +## Which model to put + +Almost all of these tools ask you for a model id on top of the base URL and the key. For agent work, the sensible order is: + +1. `glm5.3` if you have the premium tier. It is the one built for long coding sessions. +2. `glm5.3-flash` if you do not. Same 1M token context and a wide quota. +3. `deepseek-v4-flash` for everything else. + +The full list, with quotas and modalities, is in [Choose your model](/docs/choose-a-model). + + +Several of these tools let you type the model's context window by hand. Set a number higher than the real one and the agent will fill the conversation up to a point where the model starts rejecting requests. Set a lower one and it will only compact earlier than needed. When in doubt, stay short. + + +
+ +Any page of this documentation can be read without the HTML around it, by adding the API prefix: + +- `https://nan.builders/api/docs/getting-started.md` +- `https://nan.builders/api/docs/choose-a-model.md` +- `https://nan.builders/api/docs/manifest.json` lists every page with its content hash + +That is what to hand an agent when you want it to read the documentation instead of guessing. + +
diff --git a/src/content/docs/agents.md b/src/content/docs/agents.md index bbcf284..cc3f17d 100644 --- a/src/content/docs/agents.md +++ b/src/content/docs/agents.md @@ -1,7 +1,7 @@ --- title: Agents description: "Deploy AI agents in an isolated microVM with QEMU: Hermes, web terminal, file uploads, and observability." -order: 5 +order: 20 group: Guides --- diff --git a/src/content/docs/apps.md b/src/content/docs/apps.md index b02db3c..f5f4d36 100644 --- a/src/content/docs/apps.md +++ b/src/content/docs/apps.md @@ -1,7 +1,7 @@ --- title: Apps description: Deploy your apps from GitHub to NaN Cloud in minutes. -order: 6 +order: 21 group: Guides --- diff --git a/src/content/docs/choose-a-model.md b/src/content/docs/choose-a-model.md new file mode 100644 index 0000000..d729b2c --- /dev/null +++ b/src/content/docs/choose-a-model.md @@ -0,0 +1,94 @@ +--- +title: Choose your model +description: Which model to ask for each task, and exactly how its id is spelled. +order: 2 +group: Get started +--- + +# Choose your model. + +On NaN, switching models means changing one word. They are all called the same way, through the same endpoint and with the same request format: the only thing that changes is the value of the `model` field. + +```json +{ + "model": "deepseek-v4-flash", + "messages": [{ "role": "user", "content": "Hello" }] +} +``` + +That value is the **model id**, and it has to be spelled exactly. One dot too many or one hyphen too few and the API answers `404` with `model_not_found`. This page is the good list. + +## Start here + +If you do not know which one to pick, look for what you want to do in the first column. + +| I want to | Ask for | Why | +|---|---|---| +| Chat or reason about something, plainly | `deepseek-v4-flash` | It is the best general-purpose model on the cluster, and it reads images | +| Drive a coding agent through long sessions | `glm5.3` | It is built for that. Needs the premium tier | +| The same, but without the premium tier | `glm5.3-flash` | Same 1M context and a generous quota | +| Get an answer fast | `qwen3.8-flash` | Less depth, much less waiting | +| Hand the model an audio file directly | `mimo-v2.5` | It is the only one that hears | +| Describe or analyze an image | `deepseek-v4-flash` | Any of them except `glm5.3` will do; this is the best | +| Try things without spending quota | `gemma4` | It has no token counter | +| Build a search engine or a RAG | `qwen3-embedding` and then `rerank` | First you retrieve by similarity, then you reorder by relevance | +| Turn text into audio | `kokoro` | 67 voices, two of them Spanish | +| Transcribe audio | `whisper` | More than 99 languages, with automatic detection | +| Generate or edit an image | `flux-2-klein` | Text to image and image to image | + +## Every model + +| id | What for | Context | Accepts | Quota | +|---|---|---|---|---| +| `deepseek-v4-flash` | General chat and reasoning | 1M | text · image | 3B tokens/month | +| `glm5.3` | Coding agents and long tasks | 1M | text | 3B tokens/billing period | +| `glm5.3-flash` | Coding agents, without premium | 1M | text · image | 2B tokens/month | +| `qwen3.8-flash` | Fast answers | 262K | text · image | 500M tokens/month | +| `mimo-v2.5` | Audio input, omnimodal | 1M | text · image · audio | 1.0B tokens/month | +| `gemma4` | Short tasks and testing | 262K | text · image | no counter | +| `qwen3.6` | Previous generation | 262K | text · image | no counter | +| `qwen3-embedding` | 4096-dimension vectors | - | text | no counter | +| `rerank` | Reorder by relevance | - | text | no counter | +| `kokoro` | Text to speech | - | text | no counter | +| `whisper` | Speech to text | - | audio | no counter | +| `flux-2-klein` | Generate and edit images | - | text · image | 100 requests/month | + +The full spec sheets, with parameters, licenses and reasoning modes, are in [Models](/docs/models). + +> **`glm5.3` is the only one the normal subscription does not cover** +> It needs a key on the premium tier. If you ask for it without one, the answer is `403` with `tier_restricted`. Every other model can be called by any member. + +## How to read the ids + +- **The id is not the commercial name.** The model its makers call "GLM 5.3 Flash" is `glm5.3-flash` here, lowercase, no spaces, and with the version dot. +- **`-flash` means fast**, not small or worse: these are variants optimized for latency. +- **The version dot counts.** `qwen3.6` and `qwen3.8-flash` are different models, and `mimo-v2.5` carries its dot where it carries it. +- **Ids do not change meaning.** When we serve a new variant of a model we keep its id if the API is the same. `deepseek-v4-flash`, for instance, started reading images without changing its name. +- **Old ids are not switched off overnight.** `qwen3.6` still answers so that configurations already naming it do not break, but it is not what you want if you are starting today. + +## What the quota means + +The quota column counts three different things: + +- **`/month`** is a token counter that goes back to zero with the calendar month. +- **`/billing period`** goes back to zero when your period starts in Stripe, which is almost never the 1st. Only `glm5.3` works that way, and it also has a separate token cap for each rolling 4-hour window, which is the one an intensive agent session hits first. +- **`no counter`** means there is no token counter attached, not that it is infinite: the requests-per-minute limits apply to all of them just the same. + +When you exhaust a quota, the API answers `402` or `429` and retrying does not fix it. The current limit figures are at the end of [Models](/docs/models). + +## The list your key can use + +This page is written by hand and the cluster moves. The definitive answer, filtered by what your key can actually call, comes from the API itself: + +```bash +curl https://api.nan.builders/v1/models \ + -H "Authorization: Bearer $NAN_API_KEY" +``` + +If an id shows up there, it works. If it does not, you do not have it available, whatever you read somewhere else. + +## Next steps + +- [Set up your agent](/docs/agent-setup): where to put the id in Cursor, Claude Code, Codex, Cline, OpenCode or Zed. +- [Examples](/docs/examples): one complete call for each kind of model. +- [Models](/docs/models): the spec sheets, model by model. diff --git a/src/content/docs/claude-code.mdx b/src/content/docs/claude-code.mdx new file mode 100644 index 0000000..da5f3ee --- /dev/null +++ b/src/content/docs/claude-code.mdx @@ -0,0 +1,204 @@ +--- +title: Claude Code +description: "Two ways to use NaN models from Claude Code: delegating to OpenCode, or through a local gateway." +order: 6 +group: Set up your agent +--- + +import Steps from '../../components/docs/Steps.astro'; +import Details from '../../components/docs/Details.astro'; +import Callout from '../../components/docs/Callout.astro'; + +# Claude Code. + +Claude Code is Anthropic's terminal agent. It is the only tool in this section that **does not connect directly** to the cluster, and it is worth understanding why before wrestling with the configuration. + + +Claude Code speaks the Anthropic API format. NaN speaks the OpenAI format. They are two different protocols, so pointing `ANTHROPIC_BASE_URL` at `https://api.nan.builders/v1` does not work: the request arrives, but in a shape the cluster does not understand. + + +There are two paths, and they do different things: + +| | What you get | What it costs | +|---|---|---| +| **Delegating to OpenCode** | Claude Code directs, and the model work is done by OpenCode against NaN | Installing OpenCode. Nothing else | +| **Local gateway** | Claude Code uses NaN models as if they were its own | An extra process running on your machine | + +Start with the first. It is simpler and it builds no infrastructure. + +## Path 1: delegate to OpenCode + +Claude Code can run commands in your terminal, and [OpenCode](/docs/opencode) knows how to work without opening its interface. That is enough: you ask Claude Code, in plain words, to use OpenCode for a task, and the one doing the model work is NaN. + +There is nothing to set up beyond having OpenCode configured. No gateways, no environment variables, no new files. + +You keep using your Claude Code subscription for what it does well, directing the session and knowing your repository, and the work that eats tokens by the handful goes to the cluster. + + + +### Configure OpenCode + +Once, as [its page](/docs/opencode) explains, or by letting the [NaN CLI](/docs/nan-cli) do it. + +### Check that it answers without an interface + +```bash +opencode run --agent plan -m nan/deepseek-v4-flash "Summarize in three lines what this repository does." +``` + +Three things about that command: + +- **`run`** is the headless mode: it does the task, writes the answer and exits. +- **`-m`** takes the model as `provider/model`, where the provider is the name you gave it in your `opencode.json`, `nan` if you followed our page. +- **`--agent plan`** leaves OpenCode in read-only mode. Without it, it starts with its default agent, which **can edit files and run commands**. For delegating a review or a summary, that is not what you want. + +The answer comes back on standard output, preceded by the tools it used: + +```text +> plan · deepseek-v4-flash +→ Read src/lib/modelCatalog.ts +It is the single source of truth for the model catalog... +``` + +### Ask Claude Code for it + +Inside a Claude Code session, say it plainly: + +```text +Use `opencode run --agent plan -m nan/deepseek-v4-flash` to review +src/parser.ts and tell me which cases it is not covering. +``` + +Claude Code runs the command, reads what OpenCode answers and carries on from there. The first time, it will ask for permission to run it. + +It works well for what is long to read and cheap to summarize: reviewing a big file, making a first draft, summarizing documentation, comparing two versions. Claude Code keeps the coordination and the fine edits. + +Two options that help once you get a taste for it: + +- **`-f file`** attaches specific files, instead of making it look for them. +- **`-c`** continues OpenCode's last conversation, so you can follow up without explaining everything again. + + + + +Write a line in your project's `CLAUDE.md`, along the lines of "to review long files, use `opencode run --agent plan -m nan/deepseek-v4-flash`". That way you do not have to repeat the command every session. + + + +What Claude Code does comes out of your Anthropic subscription. What `opencode run` does comes out of your NaN quota. That is exactly what you want if you are stretching the subscription, but it is worth being clear about when you look at usage figures. + + +## Path 2: a local gateway + +If what you want is for **Claude Code itself** to use NaN models, you have to put something in the middle that translates between the two formats. + +You need Claude Code installed, Python 3.10 or newer for the gateway, and your NaN API key in `NAN_API_KEY`. + + + +### Install the gateway + +[LiteLLM](https://docs.litellm.ai/docs/anthropic_unified/) exposes a `/v1/messages` endpoint in Anthropic's format and translates it into OpenAI's before forwarding it. + +```bash +pip install "litellm[proxy]" +``` + + +LiteLLM versions 1.82.7 and 1.82.8 were published with credential-stealing code. Install a later, known version, and do not use those two. If you did install them, rotate your keys. + + +### Configure the gateway + +Create a `litellm.config.yaml` wherever suits you: + +```yaml +model_list: + - model_name: nan-coder + litellm_params: + model: openai/glm5.3-flash + api_base: https://api.nan.builders/v1 + api_key: os.environ/NAN_API_KEY + + - model_name: nan-general + litellm_params: + model: openai/deepseek-v4-flash + api_base: https://api.nan.builders/v1 + api_key: os.environ/NAN_API_KEY + +general_settings: + master_key: sk-local-change-this +``` + +The `openai/` prefix tells LiteLLM which format to speak to NaN in. What comes after the prefix is the model id as it is, and `model_name` is the name you will see it under from Claude Code. + +The `master_key` is a key you make up for your local gateway. It is not your NaN key, and it must not be: only the LiteLLM process knows that one. + +### Start the gateway + +```bash +litellm --config litellm.config.yaml --port 4000 +``` + +Leave it running in its own terminal. + +### Point Claude Code at the gateway + +```bash +export ANTHROPIC_BASE_URL="http://localhost:4000" +export ANTHROPIC_AUTH_TOKEN="sk-local-change-this" +export ANTHROPIC_MODEL="nan-coder" + +claude +``` + +Use `ANTHROPIC_AUTH_TOKEN` and not `ANTHROPIC_API_KEY`: it is the variable Claude Code sends as the `Authorization` header when the base URL is not Anthropic's. + +If you would rather pick the model per session than fix it through the environment, leave `ANTHROPIC_MODEL` out and start with `claude --model nan-coder`. + +### Check that it works + +With the gateway up: + +```bash +curl http://localhost:4000/v1/messages \ + -H "Authorization: Bearer sk-local-change-this" \ + -H "Content-Type: application/json" \ + -d '{ + "model": "nan-coder", + "max_tokens": 64, + "messages": [{ "role": "user", "content": "Say hello." }] + }' +``` + +If that answers, so will Claude Code. If it returns an authentication error, the thing that is wrong is the `master_key`; if it returns `404 model_not_found`, what is wrong is the NaN id inside `litellm_params`. + + + +
+ +There are smaller gateways dedicated to just this, such as [claude-code-proxy](https://github.com/fuergaosi233/claude-code-proxy). The idea is the same: a local process that receives in Anthropic format and forwards to `https://api.nan.builders/v1`. + +
+ +## Recommended model + +It depends on the path, because the model does different jobs in each. + +**Delegating to OpenCode**, the model receives separate, bounded errands, so `deepseek-v4-flash` is more than enough and has the widest quota on the cluster. If what you hand it is a whole repository, `glm5.3-flash` holds more context. + +**With the gateway**, the model runs the entire session: `glm5.3-flash`, or `glm5.3` if you have the premium tier. Both have 1M tokens of context, which is what a long agent session asks for. + +On either path, avoid `glm5.3` for tasks that involve reading screenshots: it does not accept images. + +## Known issues + +- **When delegating, OpenCode's output lands in your conversation.** If you ask it to summarize something enormous, whatever it returns takes up context in Claude Code. Ask it for summaries, not dumps. +- **With the gateway, the whole session goes through your machine.** If you kill the process, Claude Code stops answering. It is not a service, it is something of yours that has to be switched on. +- **Agent behavior depends on the model.** Claude Code is tuned against Anthropic's models, and an open model may use its tools less well. That is to be expected, and it is not a cluster failure. This only affects the gateway path: when delegating, Claude Code is still Claude Code. +- **Features tied to the Anthropic account do not travel.** Anything that depends on Anthropic's infrastructure does not work against another base URL. +- **Context fills up fast.** An agent session eats tokens far quicker than a chat. If you use `glm5.3`, watch the rolling 4-hour window described in [Choose your model](/docs/choose-a-model). + +## NaN's tools, separately + +Whichever path you choose, you can give Claude Code the cluster's web search with a single command. It is in [MCP server](/docs/mcp). diff --git a/src/content/docs/cline.mdx b/src/content/docs/cline.mdx new file mode 100644 index 0000000..9f22b58 --- /dev/null +++ b/src/content/docs/cline.mdx @@ -0,0 +1,69 @@ +--- +title: Cline +description: Connect Cline, the VS Code extension, to the NaN cluster. +order: 10 +group: Set up your agent +--- + +import Steps from '../../components/docs/Steps.astro'; +import Details from '../../components/docs/Details.astro'; +import Callout from '../../components/docs/Callout.astro'; + +# Cline. + +Cline is a VS Code extension that edits files and runs commands for you. It ships a generic provider called **OpenAI Compatible**, which is exactly what NaN needs. + +## Configuration + + + +### Open Cline's settings + +In VS Code, open the Cline panel and go into its settings with the gear icon. + +### Choose the provider + +In **API Provider**, select **OpenAI Compatible**. + +### Fill in the three fields + +| Field | Value | +|---|---| +| Base URL | `https://api.nan.builders/v1` | +| API Key | your key, the one that starts with `sk-` | +| Model ID | `glm5.3-flash` | + + +This is the most common mistake with this provider: pasting `https://api.nan.builders/v1/chat/completions` instead of just the base. Cline appends the endpoint part on its own, so with the full path you would end up calling an address that does not exist. + + +### Declare what the model can do + +Cline asks because with a generic provider it has no way of finding out: + +| Model | Context | Images | Tools | +|---|---|---|---| +| `glm5.3-flash` | 1000000 | yes | yes | +| `deepseek-v4-flash` | 1000000 | yes | yes | +| `qwen3.8-flash` | 262144 | yes | yes | +| `glm5.3` | 1000000 | no | yes | + +If you tick images on a model that does not accept them, Cline will try to send it screenshots and the request will fail. + +### Try it + +Open a folder with code and ask it for something small and observable, for instance to read the README and summarize what the project does. If it answers and asks for permission to touch files, it is connected. + + + +## Recommended model + +`glm5.3-flash`. It is the one that best withstands the long rounds of reading, editing and reading again that Cline does, and its quota is enough to work with. + +
+ +- **Cline eats a lot of context.** It sends file contents on every turn, so it spends tokens far faster than a chat does. Watch your quota at [cloud.nan.builders](https://cloud.nan.builders/). +- **The four fields have to come from the same place.** Provider, base URL, key and model, all NaN. Mixing a key from one place with a base URL from another always gives a `401`. +- **Roo Code and the other derivatives are configured the same way.** They are Cline forks and have the same generic provider, with the same four fields. + +
diff --git a/src/content/docs/codex.mdx b/src/content/docs/codex.mdx new file mode 100644 index 0000000..afd379b --- /dev/null +++ b/src/content/docs/codex.mdx @@ -0,0 +1,79 @@ +--- +title: Codex +description: Set up Codex CLI to use NaN models through a provider of your own. +order: 7 +group: Set up your agent +--- + +import Details from '../../components/docs/Details.astro'; + +# Codex. + +Codex CLI is OpenAI's terminal agent. It speaks the OpenAI format, which is the same one NaN speaks, so it connects directly: all you have to do is declare a new provider. + +> **You can skip all of this** +> The [NaN CLI](/docs/nan-cli) writes this configuration for you: Setup tab, `e` to paste your key, `space` to mark Codex, `c` to apply. + +## What you need + +- Codex CLI installed. +- Your NaN API key in an environment variable. + +```bash +export NAN_API_KEY="sk-your-key" +``` + +Codex reads the key from the environment variable you point it at, not from the configuration file. That way it does not end up written in a file you might push to a repository by accident. + +## Configuration + +Edit `~/.codex/config.toml`: + +```toml +model_provider = "nan" +model = "glm5.3-flash" + +[model_providers.nan] +name = "NaN" +base_url = "https://api.nan.builders/v1" +env_key = "NAN_API_KEY" +wire_api = "chat" +``` + +Three details that matter: + +- **`wire_api = "chat"`** makes Codex use `/chat/completions`. That is what you want: the cluster's `/responses` endpoint answers in one go instead of streaming, so with `"responses"` you would see the answer appear all at once at the end. +- **The provider identifier cannot be `openai`, `ollama` or `lmstudio`**, which are reserved. That is why it is called `nan`. +- **`base_url` ends at `/v1`** and nothing more. Do not add the endpoint path. + +## Check that it works + +```bash +codex +``` + +Ask it for something short, for example to list the files in the directory and summarize what the project does. If it answers, it is already working against the cluster. + +If you see an authentication error, the usual cause is that `NAN_API_KEY` is not exported in the terminal you are launching `codex` from. + +## Switching models + +You can change it without touching the file: + +```bash +codex --model deepseek-v4-flash +``` + +Or leave several providers declared and pick with `--profile` if you prefer separate profiles per task. + +## Recommended model + +`glm5.3-flash` for coding work. If you have the premium tier, `glm5.3`. For one-off questions that do not touch files, `deepseek-v4-flash` is more than enough. + +
+ +- **Codex's cloud features do not apply.** Once you declare a provider of your own, everything goes to NaN from your machine. +- **Reasoning looks different depending on the model.** The cluster's models emit their reasoning trace their own way, and Codex does not always present it the way it does with OpenAI's models. +- **If you change `wire_api` to `"responses"`**, the answer stops appearing gradually. It is not a hang: that endpoint does not stream yet. + +
diff --git a/src/content/docs/cursor.mdx b/src/content/docs/cursor.mdx new file mode 100644 index 0000000..d36fc82 --- /dev/null +++ b/src/content/docs/cursor.mdx @@ -0,0 +1,71 @@ +--- +title: Cursor +description: Point Cursor at the NaN cluster by overriding the OpenAI base URL. +order: 8 +group: Set up your agent +--- + +import Steps from '../../components/docs/Steps.astro'; +import Details from '../../components/docs/Details.astro'; +import Callout from '../../components/docs/Callout.astro'; + +# Cursor. + +Cursor lets you replace the address it sends its OpenAI requests to. Since NaN speaks that same format, changing the address and the key is enough. + + +The base URL override does not cover all of Cursor. Autocomplete as you type uses Cursor's own models and will keep doing so whatever you set. And in recent versions the option has stopped being visible on every plan: if you cannot find it in your settings, you have not lost it, your plan does not include it. + + +## Configuration + + + +### Open the model settings + +Go to **Settings**, then **Cursor Settings**, and open the **Models** section. + +### Paste your key + +In the OpenAI keys section, put your NaN key, the one that starts with `sk-`. + +### Change the base URL + +Turn on **Override OpenAI Base URL** and write: + +```text +https://api.nan.builders/v1 +``` + +### Add the models + +With **Add Model**, write the id exactly as it appears in [Choose your model](/docs/choose-a-model): + +```text +glm5.3-flash +deepseek-v4-flash +qwen3.8-flash +``` + +The names Cursor ships with do not exist on NaN, so turn them off or ignore them. If you leave one of them selected, the request goes to the cluster with an id it does not recognize and comes back a `404`. + +### Verify + +Press **Verify** so Cursor checks the connection, then open the chat panel, pick one of the models you added and ask it something. + +To be completely sure, look at your usage on [cloud.nan.builders](https://cloud.nan.builders/): if the tokens go up, the requests are arriving. + + + +## Recommended model + +`glm5.3-flash` for agent mode and `deepseek-v4-flash` for chat questions. `qwen3.8-flash` if answers feel slow and you would rather have speed than depth. + +
+ +- **Inline autocomplete does not use NaN.** It is Cursor's own and cannot be redirected. +- **The option is not on every plan.** In some recent versions it only shows up on certain plans. The remaining alternative is then the Azure-style configuration, if your version has it. +- **It is a global override.** Once you change the base URL, everything Cursor used to send to OpenAI goes to NaN. It is not a per-model selection. +- **Features that run on Cursor's servers do not move.** Whatever Cursor processes on its own infrastructure stays as it is. + +
diff --git a/src/content/docs/examples.md b/src/content/docs/examples.md index 6f3f249..375867b 100644 --- a/src/content/docs/examples.md +++ b/src/content/docs/examples.md @@ -1,7 +1,7 @@ --- title: Examples description: Code snippets to connect to the NaN API with Python, Node.js, curl, and more. -order: 4 +order: 19 group: Guides --- diff --git a/src/content/docs/gentle-ai.mdx b/src/content/docs/gentle-ai.mdx new file mode 100644 index 0000000..052576a --- /dev/null +++ b/src/content/docs/gentle-ai.mdx @@ -0,0 +1,135 @@ +--- +title: Gentle-AI +description: "The setup NaN recommends by default: memory, skills and guardrails on top of the agent you already use." +order: 5 +group: Set up your agent +--- + +import Steps from '../../components/docs/Steps.astro'; +import Details from '../../components/docs/Details.astro'; +import Callout from '../../components/docs/Callout.astro'; +import BrandIntro from '../../components/docs/BrandIntro.astro'; + +# Gentle-AI. + + + +[Gentle-AI](https://github.com/Gentleman-Programming/gentle-ai) does not install agents and does not connect them anywhere: it **configures the one you already use**. It gives it memory between sessions, a library of skills, a workflow for the big stuff, an MCP documentation server and a set of permission guardrails. + + + +It is the setup we recommend by default on NaN, and it is what you do **after** connecting your agent to the cluster, not instead of that. + + +First connect your agent by following [its page](/docs/agent-setup). Then run Gentle-AI. It does not touch your provider or your models: whatever you configured for NaN stays exactly as it is. + + +## Why we recommend it + +A coding agent, as it comes, starts every session blank. It does not remember what you decided yesterday, it does not know the project's conventions, and it asks the same questions again. That is paid for in tokens and in patience. + +On NaN the quota is enough for long sessions, so the bottleneck is not how much you can spend but how much context gets lost along the way. Gentle-AI goes straight at that: + +- **Memory between sessions**, so decisions do not have to be repeated. +- **Skills**, a library of criteria loaded according to what you are doing. +- **SDD**, a workflow for work big enough to deserve a plan before the code. +- **Context7**, an MCP server that gives it up-to-date library documentation instead of whatever the model remembers. +- **Permissions**, deny lists so the agent does not read or touch what it should not. +- **Persona**, the tone it speaks to you in. + +You can take all of it or only the parts you want: it ships presets, from "memory only" to the full package. + +## Installation + + + +### Check the requirements + +You need **Node.js 18 or newer with npm**, and **Git 2.38 or newer**. On Windows, also **Go 1.25.10 or newer**. + +And the agent you are going to configure has to be installed already and reachable from your terminal. Gentle-AI configures it, it does not install it. + +### Install the binary + +On macOS and Linux: + +```bash +curl -fsSL https://raw.githubusercontent.com/Gentleman-Programming/gentle-ai/main/scripts/install.sh | bash +``` + +On Windows, from PowerShell: + +```powershell +go install github.com/gentleman-programming/gentle-ai/v2/cmd/gentle-ai@latest +``` + +On Windows, installing from source is the supported path: there is no official binary distribution, and the installer is built to fail rather than hand you an unsigned executable. + +Check that it went well: + +```bash +gentle-ai version +``` + +### Configure your agent + +```bash +gentle-ai +``` + +A panel opens where you choose which agents to configure, which components you want (or a preset), and which persona. + +When you apply, it writes into the configuration directory of every agent you marked. It backs up what you had first. + +### Verify + +```bash +gentle-ai doctor +``` + +It gives you a health report without touching anything: binaries, status, memory reachable, disk space and broken configurations. It is the first thing to run when something behaves oddly. + + + +## Which agents it works with + +Of the ones we document here, Gentle-AI configures **Claude Code, OpenCode, Cursor, VS Code with Copilot, Codex and Pi**. It also supports Kilo Code, Gemini CLI, Antigravity, Kimi Code, Kiro IDE, Qwen Code, Hermes, Windsurf, OpenClaw and Trae. + +It uses each agent's own configuration mechanism rather than a layer on top, so what it writes can be read and reverted. + +
+ +Gentle-AI backs up what was there before writing anything, compressed and with automatic rotation. If you are not convinced: + +```bash +gentle-ai uninstall +``` + +That removes what it manages and leaves the rest. And `gentle-ai restore` brings back an earlier backup. + +Even so, if you have a setup very much your own, look at it before and after. It writes into the agent's global directory, which is also where whatever you put there by hand lives. + +
+ +
+ +No. Gentle-AI configures how the agent works, not which provider it calls. Your `opencode.json`, your Codex `config.toml` or whatever you configured for NaN stays as it is. + +It ships an optional provider-switching component, but that is what it is, optional, and it does not alter what you already have set up unless you ask it to. + +
+ +## Keeping it up to date + +```bash +gentle-ai upgrade # updates the binary +gentle-ai sync # brings what it wrote into your agents up to date +``` + +It is worth running `sync` after every `upgrade`: the first updates the program, the second updates what that program left written in your agents. + +## Next steps + +- [Set up your agent](/docs/agent-setup): connect your tool to the cluster first. +- [MCP server](/docs/mcp): add NaN's web search to your agent as well. +- [Choose your model](/docs/choose-a-model): which model to ask for each task. diff --git a/src/content/docs/getting-started.md b/src/content/docs/getting-started.md deleted file mode 100644 index 2305c66..0000000 --- a/src/content/docs/getting-started.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -title: Getting Started -description: Configure your favorite IDE or tool to connect to NaN models. -order: 1 -group: Get started ---- - -# Getting Started. - -Access is via LiteLLM with an OpenAI-compatible API. Works with any tool that accepts a `base URL` + `API key`: Cursor, Cline, Continue, Aider, Open Code, Open WebUI, or any OpenAI-compatible SDK. - -## Get your API Key - -You must be a NaN community member. If you're already subscribed, generate your API Key from the user settings section under "API Keys" on the [platform](https://cloud.nan.builders/). The key is personal and non-transferable. - -> **Note** -> Support is for technical issues only. - -## Configure your tool - -| Field | Value | -|---|---| -| base URL | `https://api.nan.builders/v1` | -| API Key | `sk-your-key-here` | -| Model | `qwen3.6` | - -OpenAI-compatible configuration example: - -```json -provider: { - openai: { - npm: "@ai-sdk/openai", - name: "NaN", - apiKey: "sk-your-key-here", - baseURL: "https://api.nan.builders/v1", - model: "qwen3.6" - } -} -``` diff --git a/src/content/docs/getting-started.mdx b/src/content/docs/getting-started.mdx new file mode 100644 index 0000000..de8fbe7 --- /dev/null +++ b/src/content/docs/getting-started.mdx @@ -0,0 +1,167 @@ +--- +title: Getting started +description: From zero to your first response from the cluster, and from there to your editor. +order: 1 +group: Get started +--- + +import Steps from '../../components/docs/Steps.astro'; +import Details from '../../components/docs/Details.astro'; +import Callout from '../../components/docs/Callout.astro'; + +# Getting started. + +The NaN API is OpenAI-compatible. You only need two things, a **base URL** and an **API key**, and any tool or SDK that accepts those two fields works with NaN without touching anything else. + +| Field | Value | +|---|---| +| Base URL | `https://api.nan.builders/v1` | +| API key | yours, it starts with `sk-` | +| Model to start with | `deepseek-v4-flash` | + + + +### Get your API key + +You have to be a member of the NaN community. Go to [cloud.nan.builders](https://cloud.nan.builders/), open your user settings and go to the **API Keys** section to generate yours. + +The key is personal, non-transferable, and shown only once: copy it as soon as you generate it. If you lose it, nothing breaks, generate another one and delete the old one from the same panel. + + +A key pushed to a repository is found by a bot within minutes. Keep it in an environment variable and read it from there, like the examples on this page do. + + +### Keep it in an environment variable + +```bash +# macOS and Linux +export NAN_API_KEY="sk-your-key" +``` + +```powershell +# Windows, PowerShell +$env:NAN_API_KEY = "sk-your-key" +``` + +That only lasts as long as the terminal does. To make it survive closing it, add the line to your `~/.zshrc`, your `~/.bashrc` or your PowerShell profile. + +### Make your first call + +```bash +curl https://api.nan.builders/v1/chat/completions \ + -H "Authorization: Bearer $NAN_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "model": "deepseek-v4-flash", + "messages": [{ "role": "user", "content": "Introduce yourself in one line." }] + }' +``` + +If all goes well you get back a JSON and the model's text is in `choices[0].message.content`: + +```json +{ + "id": "chatcmpl-...", + "model": "deepseek-v4-flash", + "choices": [ + { "index": 0, "message": { "role": "assistant", "content": "I am an open model..." } } + ], + "usage": { "prompt_tokens": 14, "completion_tokens": 23, "total_tokens": 37 } +} +``` + +You now have access to the cluster. Everything else is a variation on that same call. + +### The same thing from your code + +Use the official OpenAI SDK and change its `base_url`. There is no NaN library to install. + +```python +# pip install openai +import os +from openai import OpenAI + +client = OpenAI( + api_key=os.environ["NAN_API_KEY"], + base_url="https://api.nan.builders/v1", +) + +resp = client.chat.completions.create( + model="deepseek-v4-flash", + messages=[{"role": "user", "content": "Introduce yourself in one line."}], +) +print(resp.choices[0].message.content) +``` + +```javascript +// npm install openai +import OpenAI from 'openai'; + +const client = new OpenAI({ + apiKey: process.env.NAN_API_KEY, + baseURL: 'https://api.nan.builders/v1', +}); + +const resp = await client.chat.completions.create({ + model: 'deepseek-v4-flash', + messages: [{ role: 'user', content: 'Introduce yourself in one line.' }], +}); +console.log(resp.choices[0].message.content); +``` + +There are more examples, including embeddings, speech, images and web search, in [Examples](/docs/examples). + +### Choose your model + +`deepseek-v4-flash` is a good starting point, but it is neither the only one nor the best at everything. The full list, with what each one is good for, is in [Choose your model](/docs/choose-a-model). + +And the list **your** key can actually use is always the one the API gives you: + +```bash +curl https://api.nan.builders/v1/models \ + -H "Authorization: Bearer $NAN_API_KEY" +``` + +### Connect your editor or your agent + +Cursor, Claude Code, Codex, VS Code, Cline, OpenCode, Zed and company are configured with those same two values. Each one asks for them somewhere different, and that is what [Set up your agent](/docs/agent-setup) collects, with one page per tool. + +If you use OpenCode, Codex, Pi or droid, the [NaN CLI](/docs/nan-cli) configures them for you without editing anything. + + + +## If something fails + +Every error arrives in the same shape as OpenAI's: a JSON with `error.message`, `error.type` and a short `error.code` you can branch on in your code. + +| Code | What happened | What to do | +|---|---|---| +| `401` | The key is missing, misspelled or gone | Check the `Authorization: Bearer ...` header | +| `402` | You have spent that model's token quota | Switch models or wait for your next period to start | +| `403` | Your plan does not reach that model or that endpoint | `glm5.3` needs the premium tier | +| `404` | That model id does not exist | Check the spelling in [Choose your model](/docs/choose-a-model) or with `GET /v1/models` | +| `429` | Too many requests at once, or quota exhausted | Retry, waiting a little longer each time | +| `5xx` | The failure is ours | Retry; if it persists, say so in `#support` | + +Retrying only makes sense for `429` and for `5xx`. A `401`, a `403` or a `404` will fail in exactly the same way until you change the request, and a `402` is not fixed by insisting: the counter goes back to zero when your billing period starts. + +The full detail, endpoint by endpoint, is in the [API reference](/docs/api). + +
+ +Limits are per API key, not per model: a cap on requests per minute and a maximum number of requests at once. + +`glm5.3` is the only one that additionally follows a rolling token window, and web search has its own budget, separate from the models'. + +The current figures are at the end of [Models](/docs/models), which is where they are published so that no two versions of the same number go around. + +
+ +## Next steps + +- [Choose your model](/docs/choose-a-model): which model to ask for each task, and how its id is spelled. +- [Set up your agent](/docs/agent-setup): Cursor, Claude Code, Codex, VS Code, Cline, OpenCode, Zed and the rest. +- [Gentle-AI](/docs/gentle-ai): what we recommend configuring on top, once you are connected. +- [Examples](/docs/examples): snippets ready to copy in Python, Node.js and curl. +- [API reference](/docs/api): every endpoint, field and error. +- Support: `#support` on Discord, technical questions only. diff --git a/src/content/docs/hermes.mdx b/src/content/docs/hermes.mdx new file mode 100644 index 0000000..f68c841 --- /dev/null +++ b/src/content/docs/hermes.mdx @@ -0,0 +1,90 @@ +--- +title: Hermes +description: The Nous Research agent, with NaN models. On your own machine or deployed on NaN Cloud. +order: 14 +group: Set up your agent +--- + +import Steps from '../../components/docs/Steps.astro'; +import Details from '../../components/docs/Details.astro'; +import Callout from '../../components/docs/Callout.astro'; + +# Hermes. + +[Hermes](https://hermes-agent.nousresearch.com/) is the Nous Research agent, open source and MIT licensed. It is not a coding agent like the rest of this section: it lives in your messaging channels (Telegram, Discord, Slack, WhatsApp, Signal, email) as well as in the terminal, it remembers what you talked about, it schedules tasks for itself and it delegates to subagents. + +There are two ways to have it, and the first one requires configuring nothing. + +## Path 1: deployed on NaN Cloud + +NaN Cloud deploys Hermes for you in **its own microVM**, and it comes connected to the cluster out of the box: the platform injects your key and you do not have to touch any provider configuration. + + +You create the agent from the panel, pick a model, give it your Telegram bot token, and it is already talking. It is explained step by step in [Agents](/docs/agents), with screenshots. + + +You also get a web terminal, file uploads, logs and metrics, and you can expose it on a public URL. All of that only exists on this path. + +## Path 2: on your own machine + +If you would rather run it yourself, Hermes ships a provider called **`custom`**, meant for any OpenAI-compatible endpoint. That is the one NaN uses. + + + +### Install it + +On macOS and Linux: + +```bash +curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash +``` + +For macOS 12 or newer and for Windows 10 and 11 there are desktop installers on their site as well. + +### Configure it + +```bash +hermes setup +``` + +Choose the **`custom`** provider (it also shows up as `local`, `ollama` or `vllm`: it is the same one, the OpenAI-compatible endpoint provider) and give it the three values: + +| Field | Value | +|---|---| +| Base URL | `https://api.nan.builders/v1` | +| API key | your key, the one that starts with `sk-` | +| Model | `glm5.3-flash` | + +Unlike the providers Hermes ships preconfigured, `custom` **has no fixed environment variable**: you give it the address and the key here, and it keeps them in `~/.hermes/config.yaml`. + +### Check that it works + +```bash +hermes doctor +``` + +It tells you whether the provider answers. Then start the agent and send it a message through whichever channel you configured. + + + +## Switching models + +The default model lives in `~/.hermes/config.yaml`, under `model.default`. You do not edit it by hand: + +```bash +hermes model +``` + +## Recommended model + +`glm5.3-flash` if you are going to give it long tasks or have it delegate to subagents, for its 1M token context. `deepseek-v4-flash` for conversation and normal tasks, which also has the widest quota on the cluster. + +If you are going to send it photos over Telegram, any of them except `glm5.3`, which is the only one that does not accept images. + +
+ +- **The two paths do not mix.** The NaN Cloud Hermes is configured from the panel and yours from `hermes setup`. If you have both, they are two different agents with two different memories. +- **The `custom` provider does not discover models.** Since there is no fixed list behind it, write the id exactly as it appears in [Choose your model](/docs/choose-a-model). +- **An agent that lives in your messages spends at its own pace.** It is not a session you open and close: if you give it scheduled tasks, it spends quota while you are not looking. Check it at [cloud.nan.builders](https://cloud.nan.builders/). + +
diff --git a/src/content/docs/intro.md b/src/content/docs/intro.md index cbddd14..1c76573 100644 --- a/src/content/docs/intro.md +++ b/src/content/docs/intro.md @@ -7,21 +7,29 @@ group: Get started # Welcome to NaN. -This doc explains how to connect your tools to our GPUs. The cluster runs open models with an OpenAI-compatible API. If something accepts a `base URL` + `API key`, it works with NaN. +This documentation explains how to connect your tools to our GPUs. The cluster runs open models behind an OpenAI-compatible API. If something accepts a `base URL` and an `API key`, it works with NaN. > **To get your API Key** -> You must be a NaN community member. You can generate your API Key from the user settings section under "API Keys" on the [platform](https://cloud.nan.builders/). The key is personal and non-transferable. +> You have to be a member of the NaN community. You can generate your API Key from the user settings, under "API Keys" on the [platform](https://cloud.nan.builders/). The key is personal and non-transferable. -## Rate limits +## The essentials -| Metric | Value | +| Field | Value | |---|---| -| Requests per minute | 60 rpm | -| Max parallel | 5 concurrent | +| Base URL | `https://api.nan.builders/v1` | +| Authentication | `Authorization: Bearer sk-your-key` | +| Format | OpenAI-compatible | -## What to do next +Limits are per API key: a cap on requests per minute and a maximum number of requests at once. The current figures are at the end of [Models](/docs/models), which is where they are published so that no two versions of the same number go around. -- [Getting Started](/docs/getting-started): endpoint, auth, and step-by-step setup. -- [Models](/docs/models): capabilities and limits of the models. -- [Examples](/docs/examples): snippets in Python, Node.js, and curl. -- Support: report issues via `#support` on Discord. +## Where to go next + +- [Getting started](/docs/getting-started): from zero to your first response, with curl, Python and Node. +- [Choose your model](/docs/choose-a-model): which model to ask for each task, and how its id is spelled. +- [Set up your agent](/docs/agent-setup): Claude Code, Codex, Cursor, Cline, OpenCode, Zed and company. +- [NaN CLI](/docs/nan-cli): the official terminal tool, which also configures several of them for you. +- [MCP server](/docs/mcp): NaN's web search inside your agent. +- [API reference](/docs/api): every endpoint, field by field. +- [Models](/docs/models): the spec sheets and the limits. +- [Examples](/docs/examples): snippets in Python, Node.js and curl. +- Support: report issues in `#support` on Discord. diff --git a/src/content/docs/mcp.mdx b/src/content/docs/mcp.mdx new file mode 100644 index 0000000..924be65 --- /dev/null +++ b/src/content/docs/mcp.mdx @@ -0,0 +1,123 @@ +--- +title: MCP server +description: Plug NaN's web search into any MCP-compatible agent. +order: 16 +group: Set up your agent +--- + +import Details from '../../components/docs/Details.astro'; + +# MCP server. + +On top of the models, NaN exposes its own tools through an **MCP** server ([Model Context Protocol](https://modelcontextprotocol.io)). It is for the opposite of the rest of this section: here you are not giving your agent models, you are giving it **capabilities**. + +Today the available tool is **web search**. The registry will grow, so ask the server what it has rather than trusting this sentence. + +| Field | Value | +|---|---| +| URL | `https://api.nan.builders/mcp` | +| Authentication | `Authorization: Bearer sk-your-key` | +| Transport | HTTP, stateless | + +> **This URL has no `/v1`** +> The MCP server lives at the root of the domain, not under `/v1` like the rest of the API. It is `https://api.nan.builders/mcp`, nothing else. + +The key is the same one you use for the models. There is no second key to generate. + +## Generic configuration + +Almost every MCP client uses this same file, with this same block: + +```json +{ + "mcpServers": { + "nan": { + "url": "https://api.nan.builders/mcp", + "headers": { + "Authorization": "Bearer sk-your-key" + } + } + } +} +``` + +Where that file goes depends on the client: + +| Client | Where | +|---|---| +| Cursor | `.cursor/mcp.json` in the project, or `~/.cursor/mcp.json` | +| Cline | The MCP Servers panel inside the extension | +| Zed | The `context_servers` block of `~/.config/zed/settings.json` | +| OpenCode | The `mcp` block of your `opencode.json` | + +## Claude Code + +Claude Code adds it from the command line: + +```bash +claude mcp add --transport http nan https://api.nan.builders/mcp \ + --header "Authorization: Bearer sk-your-key" +``` + +Add `--scope user` if you want it available in every project and not only the current one. Inside a session, `/mcp` shows you the connected servers and the tools they offer. + +This is independent of the models: you can use NaN's web search from Claude Code even if your models come from somewhere else. + +## Check that it works + +With no client in between, by asking the server directly which tools it has: + +```bash +curl https://api.nan.builders/mcp \ + -H "Authorization: Bearer $NAN_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ "jsonrpc": "2.0", "id": 1, "method": "tools/list" }' +``` + +The answer carries the list of tools with their arguments. That is where you always see the real set, which is more reliable than any hand-written list. + +And a real search: + +```bash +curl https://api.nan.builders/mcp \ + -H "Authorization: Bearer $NAN_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "id": 2, + "method": "tools/call", + "params": { + "name": "web_search", + "arguments": { "query": "kubernetes 1.34 release", "count": 5 } + } + }' +``` + +## The `web_search` tool + +It takes the same arguments as the [`POST /v1/search`](/docs/api) endpoint: + +| Argument | What it does | +|---|---| +| `query` | The search. The only mandatory one | +| `count` | How many results, from 1 to 20. Default 5 | +| `freshness` | Age filter: `pd` day, `pw` week, `pm` month, `py` year | +| `fetch_content` | With `true`, it brings the text of the pages as well as the summary. It takes longer | + +Searches go out through NaN, so your key never talks to an external search engine and you do not need to sign up for one. + +## Limits + +Web search has its own budget, separate from the models': **20 requests per minute, 3 at once and 500 searches a day** per key. Searching does not spend your chat quota, nor the other way around. + +It makes no difference whether the call comes in through MCP or through `POST /v1/search`: it counts the same, on the same counter. A search repeated within the next 15 minutes is served from a short cache and arrives marked `cached: true`, but it still counts. + +If you go over, the answer is a `429` with a `Retry-After` header telling you how long to wait. + +
+ +- **The server keeps no state.** Every request is independent and carries its own authentication. There is no session to keep open. +- **Some clients do not forward the headers you declare** at every phase of the connection. If the client connects but then fails to call a tool with an authentication error, that is usually why, and not your key. +- **The tool list changes.** Use `tools/list` before assuming a tool exists. + +
diff --git a/src/content/docs/models.mdx b/src/content/docs/models.mdx index 1d3755b..8ce1346 100644 --- a/src/content/docs/models.mdx +++ b/src/content/docs/models.mdx @@ -1,7 +1,7 @@ --- title: Models description: Technical specifications, capabilities, and parameters of the shared cluster models. -order: 3 +order: 18 group: Reference --- @@ -15,79 +15,78 @@ Community models. All are accessed via the same OpenAI-compatible API with the same `base URL`. max_tokens ≥ 300)', + 'Tool calling', + 'Reasoning mode', 'Vision (image input)', - 'Audio (audio input)', '1M token context', 'Streaming generation (SSE)', ]} /> @@ -116,25 +115,26 @@ with the same `base URL`. /> max_tokens ≥ 300)', 'Vision (image input)', + 'Audio (audio input)', '1M token context', 'Streaming generation (SSE)', ]} diff --git a/src/content/docs/nan-cli.mdx b/src/content/docs/nan-cli.mdx new file mode 100644 index 0000000..9c5c29a --- /dev/null +++ b/src/content/docs/nan-cli.mdx @@ -0,0 +1,161 @@ +--- +title: NaN CLI +description: "The official terminal tool: usage, costs and automatic setup for OpenCode, Codex, Pi and droid." +order: 4 +group: Set up your agent +--- + +import Steps from '../../components/docs/Steps.astro'; +import Details from '../../components/docs/Details.astro'; +import Callout from '../../components/docs/Callout.astro'; + +# NaN CLI. + +NaN has an official terminal tool, [`helmcode/nan-cli`](https://github.com/helmcode/nan-cli). It does two things: it shows you your real usage, and it **configures your coding tools for you**, without you having to edit configuration files by hand. + +It is a shortcut, not a requirement. Everything it does can be done by hand by following your tool's page in this same section. + +## What it configures on its own + +| Tool | Automatic setup | +|---|---| +| [OpenCode](/docs/opencode) | Yes | +| [Codex](/docs/codex) | Yes | +| Pi | Yes | +| Factory AI (`droid`) | Yes | + +Claude Code, Cursor, VS Code, Cline and Zed still need the manual configuration from their pages. + +## What else you get + +The CLI opens a panel with tabs you move through with the arrow keys: + +| Tab | What for | +|---|---| +| Profile | Your account details | +| Usage | Tokens spent over 24 hours, 30 days and all time | +| Models | The models available and how much you have spent on each | +| Costs | What the same thing would have cost you at other providers | +| Setup | Your API key and the automatic setup of tools | +| About | Version and links | + +The **Usage** tab is the fastest way to know how much quota you have left before starting a long agent session. + +## Installation + + + +### Install the binary + +On macOS and Linux, one line: + +```bash +curl -fsSL https://nan.builders/install | bash +``` + +The installer looks at your system and architecture, downloads the right binary from the latest published release, **verifies its checksum** and puts it in `/usr/local/bin/nan`. If that directory needs administrator rights, it will ask for your password through `sudo`. + +To put it somewhere else, give it the directory yourself: + +```bash +INSTALL_DIR="$HOME/.local/bin" curl -fsSL https://nan.builders/install | bash +``` + +If the directory you choose is not in your `PATH`, the installer says so and hands you the line to add to your `~/.zshrc` or your `~/.bashrc`. + +There are binaries for macOS and Linux, on Intel and on ARM. On Windows you have to build from source for now, and the block below explains how. + +### Check that it worked + +```bash +nan --version +``` + +It has to answer with the version and the name of the tool. If your terminal says the command is not found, the binary is installed but its directory is not in your `PATH`. + +### Get your API key + +At [cloud.nan.builders](https://cloud.nan.builders/), in your user settings, under **API Keys**. + + +That command points to a Discord sign-in address that no longer exists and ends in a `404` in your browser. It is not your machine's fault. The good path is to take the key from the panel and paste it into the Setup tab. + + +### Configure your tools + +```bash +nan +``` + +Inside the panel, go to the **Setup** tab with the arrow keys. Press `e` and paste your API key. With `space`, mark the tools you want configured. Press `c` to apply. + +The CLI writes the configuration for each tool you marked. Quit with `q` and start your agent: + +```bash +opencode +``` + +Inside OpenCode, `/models` shows you the NaN models, already available. + + + +
+ +This is the path on Windows, and the one you want if you are going to work on the CLI itself. You need [Go](https://go.dev) 1.26 or newer: + +```bash +git clone https://github.com/helmcode/nan-cli +cd nan-cli +go build -o nan . +``` + +That leaves the binary in the repository directory. To have it at hand from anywhere, move it to a directory in your `PATH`: + +```bash +mkdir -p ~/.local/bin +mv ./nan ~/.local/bin/nan +``` + +If that directory is not in your `PATH` yet, add it to your shell's startup file. On macOS the default shell is **zsh**, so the file is `~/.zshrc` and not `~/.bashrc`: + +```bash +echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc +source ~/.zshrc +``` + +On Linux with bash, the same command but against `~/.bashrc`. + +
+ +## Updating + +Run the installer again. It always brings the latest published release and overwrites the one you have: + +```bash +curl -fsSL https://nan.builders/install | bash +``` + +To remove it, delete the binary: `rm /usr/local/bin/nan`, or whatever path you gave it with `INSTALL_DIR`. It leaves nothing else behind on your system. + +## If you would rather not install anything + +That is perfectly reasonable. Go straight to your tool's page and copy the configuration block: + +- [OpenCode](/docs/opencode) +- [Codex](/docs/codex) +- [Claude Code](/docs/claude-code) +- [Cursor](/docs/cursor) +- [VS Code and Copilot](/docs/vscode) +- [Cline](/docs/cline) +- [Zed](/docs/zed) +- [Other tools](/docs/other-tools) + +The result is the same. The only thing you miss out on are the usage and cost tabs. + +
+ +- **Profile, Usage and Costs need a session that cannot be created today.** The CLI's Discord sign-in points to a retired endpoint. The Setup tab, which is the one that configures your tools, works with the API key alone and is unaffected. +- **On Windows you have to build.** The published releases carry macOS and Linux binaries; for Windows, the path for now is `go build`. +- **The installer asks GitHub for the list of releases.** If you are behind a proxy that blocks `api.github.com`, it will find nothing to download. In that case, grab the `.tar.gz` for your system by hand from the [releases page](https://github.com/helmcode/nan-cli/releases). + +
diff --git a/src/content/docs/opencode.mdx b/src/content/docs/opencode.mdx new file mode 100644 index 0000000..f781d15 --- /dev/null +++ b/src/content/docs/opencode.mdx @@ -0,0 +1,131 @@ +--- +title: OpenCode +description: Declare NaN as a provider in OpenCode and use every model on the cluster. +order: 11 +group: Set up your agent +--- + +import Details from '../../components/docs/Details.astro'; + +# OpenCode. + +OpenCode is an open source terminal agent. You declare NaN as one more provider and from then on you can switch models from inside the agent itself. + +> **You can skip all of this** +> The [NaN CLI](/docs/nan-cli) writes this configuration for you: Setup tab, `e` to paste your key, `space` to mark OpenCode, `c` to apply. + +## Configuration + +Write this into `~/.config/opencode/opencode.json` to have it in every project, or into an `opencode.json` at the root of the project if you only want it there: + +```json +{ + "$schema": "https://opencode.ai/config.json", + "provider": { + "nan": { + "npm": "@ai-sdk/openai-compatible", + "name": "NaN", + "options": { + "baseURL": "https://api.nan.builders/v1", + "apiKey": "sk-your-key" + }, + "models": { + "deepseek-v4-flash": { + "name": "DeepSeek V4 Flash", + "limit": { "context": 1048575, "output": 32768 }, + "modalities": { "input": ["text", "image"], "output": ["text"] } + }, + "glm5.3-flash": { + "name": "GLM 5.3 Flash", + "limit": { "context": 1048576, "output": 32768 }, + "modalities": { "input": ["text", "image"], "output": ["text"] } + }, + "qwen3.8-flash": { + "name": "Qwen 3.8 Flash", + "limit": { "context": 262144, "output": 32768 }, + "modalities": { "input": ["text", "image"], "output": ["text"] } + }, + "mimo-v2.5": { + "name": "Xiaomi MiMo V2.5", + "limit": { "context": 1048576, "output": 32768 }, + "modalities": { "input": ["text", "image", "audio"], "output": ["text"] } + }, + "glm5.3": { + "name": "GLM 5.3 (premium)", + "limit": { "context": 1048576, "output": 32768 }, + "modalities": { "input": ["text"], "output": ["text"] } + } + } + } + }, + "compaction": { + "auto": true, + "prune": true, + "reserved": 50000 + } +} +``` + +`@ai-sdk/openai-compatible` is the generic adapter, the one that speaks to any API shaped like OpenAI's. Do not use plain `@ai-sdk/openai`: that one expects the real OpenAI API. + +Of everything inside each model, the only mandatory part is its key, which is the id. `name`, `limit` and `modalities` are optional: without them OpenCode works the same, you just see the bare id in the picker and automatic compaction runs on its own defaults. If you would rather have a short file, take them out. + +`glm5.3` will only answer if your key is on the premium tier. If it is not, leave it off the list so you do not pick it by mistake. + +## Where to put the key + +In the example above the key is written inside the file, which is the most direct thing but not the most comfortable if that file ends up in a repository. + +The alternative is to leave `apiKey` out of the `opencode.json` and store it with OpenCode's `/connect` command, which writes it into its own credential store: + +```text +/connect +``` + +It will ask for a **Provider id**. Write exactly: + +```text +nan +``` + +> **The Provider id has to match the key in the JSON** +> OpenCode pairs the credential with the provider by that identifier. If your provider is called `nan` in `opencode.json` and you write `NaN`, `nan-builders` or anything else in `/connect`, OpenCode saves the credential but associates it with no provider, and the requests go out with no key. It is the most common failure when connecting OpenCode to NaN, and it gives no message that explains it. + +Then paste your API key and press Enter. + +## The context limits + +```json +"limit": { "context": 1048575, "output": 32768 } +``` + +`limit.context` and `limit.output` are the fields OpenCode reads. An older version of these docs published `contextWindow`, which is not part of [OpenCode's schema](https://opencode.ai/config.json): an unknown key raises nothing anyone sees, OpenCode simply falls back to its own assumption about the window, and the symptom is a session that compacts far too early on the long-context models. + +`limit.context` is the window the proxy accepts, which is not always the window the model was trained with: `qwen3.8-flash` is served at its native 262K, not at the YaRN-extended 1M. `limit.output` is a client-side budget rather than a server cap, so raise it if you need longer answers. + +## The compaction block + +```json +"compaction": { "auto": true, "prune": true, "reserved": 50000 } +``` + +OpenCode summarizes the conversation on its own when it gets close to the context limit, and `reserved` is the token margin it keeps to do that. With 1M token windows you are not going to touch the limit in a normal session, but leaving it on stops a very long session from being cut off abruptly. + +## Check that it works + +```bash +opencode +``` + +Inside the agent, pick the model with `/models` and ask it for something short. If it answers, it is already going out through the cluster. + +## Recommended model + +`glm5.3-flash` for code, `deepseek-v4-flash` for everything else. If you have the premium tier, `glm5.3` for long sessions. + +
+ +- **The key is written in the file.** If you keep the `opencode.json` inside the repository, the key goes with it. Keep it in the file in your home directory, or add the file to `.gitignore`. +- **The windows are the real ones.** They are measured against the proxy, and they are the same numbers [Examples](/docs/examples) publishes. If you raise them by hand, OpenCode will fill the conversation up to a point where the model starts rejecting requests. + +
diff --git a/src/content/docs/other-tools.md b/src/content/docs/other-tools.md new file mode 100644 index 0000000..771c522 --- /dev/null +++ b/src/content/docs/other-tools.md @@ -0,0 +1,122 @@ +--- +title: Other tools +description: Continue, Aider, OpenClaw and any OpenAI-compatible interface. +order: 15 +group: Set up your agent +--- + +# Other tools. + +Anything that accepts an OpenAI base URL and an API key works with NaN. Here are the configurations for the tools that do not have a page of their own. + +In all of them, the two values are the same: + +| Field | Value | +|---|---| +| Base URL | `https://api.nan.builders/v1` | +| API key | your key, the one that starts with `sk-` | + +## Continue + +An extension for VS Code and JetBrains. Edit `~/.continue/config.yaml`: + +```yaml +name: NaN +version: 1.0.0 +schema: v1 +models: + - name: GLM 5.3 Flash + provider: openai + model: glm5.3-flash + apiBase: https://api.nan.builders/v1 + apiKey: sk-your-key + roles: + - chat + - edit + - name: DeepSeek V4 Flash + provider: openai + model: deepseek-v4-flash + apiBase: https://api.nan.builders/v1 + apiKey: sk-your-key + roles: + - chat +``` + +`provider: openai` does not mean it calls OpenAI: it is the name of the adapter that speaks that format. What decides where the request goes is `apiBase`. + +## Aider + +A terminal agent that works on your git repository. Aider routes by the model prefix, so the id has to be preceded by `openai/`: + +```bash +export OPENAI_API_BASE="https://api.nan.builders/v1" +export OPENAI_API_KEY="sk-your-key" + +aider --model openai/glm5.3-flash +``` + +To avoid repeating it every time, leave it in `~/.aider.conf.yml`: + +```yaml +openai-api-base: https://api.nan.builders/v1 +model: openai/glm5.3-flash +``` + +Without the `openai/` prefix, Aider tries to guess the provider from the name, does not recognize it, and fails before even making the request. + +## Pi + +It has a page of its own: [Pi](/docs/pi). + +## OpenClaw + +Configure `~/.openclaw/openclaw.json`: + +```json +{ + "models": { + "providers": { + "nan": { + "baseUrl": "https://api.nan.builders/v1", + "apiKey": "sk-your-key", + "api": "openai-completions", + "models": [ + { + "id": "glm5.3-flash", + "name": "GLM 5.3 Flash", + "reasoning": true, + "input": ["text", "image"], + "contextWindow": 1000000, + "maxTokens": 65536 + } + ] + } + } + }, + "agents": { + "defaults": { + "model": { "primary": "nan/glm5.3-flash" }, + "models": { + "nan/glm5.3-flash": { + "params": { "maxTokens": 16000 } + } + } + } + } +} +``` + +`maxTokens: 65536` is the maximum the model takes. `params.maxTokens: 16000` is what gets sent on each request, which is a good balance for most tasks. If you need longer answers, raise it, but bear in mind that reasoning comes out of that budget too. + +## Open WebUI, LM Studio and other chat interfaces + +They all ask for the same thing, under different names depending on the application: an OpenAI API address and a key. + +- **Open WebUI**: Settings, Connections, OpenAI API. Put the base URL and the key, and the models show up in the picker on their own. +- **LM Studio**: in the remote providers tab, add an OpenAI-compatible provider with those same two values. + +If the application asks you for "OpenAI API Base", "API Endpoint" or "Custom base URL", they are all the same field and they all want `https://api.nan.builders/v1`. + +## Your own code + +You do not need any tool at all: the official OpenAI SDK, in Python or in JavaScript, works by changing its base URL. It is in [Getting started](/docs/getting-started), with more examples in [Examples](/docs/examples). diff --git a/src/content/docs/pi.mdx b/src/content/docs/pi.mdx new file mode 100644 index 0000000..43175a5 --- /dev/null +++ b/src/content/docs/pi.mdx @@ -0,0 +1,96 @@ +--- +title: Pi +description: Declare NaN as a provider in Pi and leave it as the default. +order: 13 +group: Set up your agent +--- + +import Steps from '../../components/docs/Steps.astro'; +import Details from '../../components/docs/Details.astro'; +import Callout from '../../components/docs/Callout.astro'; + +# Pi. + +Pi is a deliberately small terminal agent: a minimal shell that adapts to how you work instead of imposing how it works. It speaks the OpenAI format, so NaN goes in as one more provider. + + +The [NaN CLI](/docs/nan-cli) writes this configuration for you: Setup tab, `e` to paste your key, `space` to mark Pi, `c` to apply. + + +## Configuration + + + +### Declare the provider + +In `~/.pi/agent/models.json`: + +```json +{ + "providers": { + "nan": { + "baseUrl": "https://api.nan.builders/v1", + "api": "openai-completions", + "apiKey": "sk-your-key", + "compat": { "supportsDeveloperRole": true }, + "models": [ + { + "id": "glm5.3-flash", + "name": "GLM 5.3 Flash", + "reasoning": true, + "input": ["text", "image"], + "contextWindow": 1000000, + "maxTokens": 16384 + }, + { + "id": "deepseek-v4-flash", + "name": "DeepSeek V4 Flash", + "reasoning": true, + "input": ["text", "image"], + "contextWindow": 1000000, + "maxTokens": 16384 + } + ] + } + } +} +``` + +`api: "openai-completions"` is what tells Pi which format to speak. `maxTokens` is the ceiling for the answer on each request, not the context. + +### Make it the default + +In `~/.pi/agent/settings.json`: + +```json +{ + "defaultProvider": "nan", + "defaultModel": "glm5.3-flash" +} +``` + + +Without `defaultProvider` and `defaultModel`, Pi keeps using its factory provider and what you get is a `401`. It is the most common failure when connecting it, and the error message does not say this file is the problem. + + +### Try it + +```bash +pi +``` + +Ask it for something short. If it answers, it is going out through the cluster. You can switch models mid-session without leaving the agent. + + + +## Recommended model + +`glm5.3-flash` for coding work. `deepseek-v4-flash` for everything else, which also has the widest quota on the cluster. + +
+ +- **The key is written in the file.** `models.json` lives in your home directory, so it does not usually end up in a repository, but keep it in mind if you sync your configuration between machines. +- **`maxTokens` is not the context.** It is the ceiling for each answer. Reasoning comes out of that same budget, so if you ask for long reasoned answers, raise it. +- **The models you declare are the ones you get.** Pi does not ask the cluster what is available: it shows whatever is on the list. + +
diff --git a/src/content/docs/vscode.mdx b/src/content/docs/vscode.mdx new file mode 100644 index 0000000..26e6a8c --- /dev/null +++ b/src/content/docs/vscode.mdx @@ -0,0 +1,121 @@ +--- +title: VS Code and Copilot +description: Add NaN models to Copilot Chat in VS Code through a custom endpoint. +order: 9 +group: Set up your agent +--- + +import Steps from '../../components/docs/Steps.astro'; +import Details from '../../components/docs/Details.astro'; +import Callout from '../../components/docs/Callout.astro'; + +# VS Code and Copilot. + +In VS Code, models come in through **Copilot Chat**, so configuring VS Code and configuring Copilot are the same thing. Copilot accepts models of your own through its **Custom Endpoint** provider, which speaks the OpenAI format. That is exactly what NaN serves. + + +This affects **chat and agent mode**. Inline completion, those grey suggestions as you type, **stays Copilot's** and cannot be redirected. Same with semantic search and anything that depends on GitHub's embeddings: they still need your account. + + +## What you need + +A version of VS Code with the **Custom Endpoint** provider (it arrived in 1.122; before that there was a `github.copilot.chat.customOAIModels` setting which is now deprecated), the Copilot Chat extension installed, and your NaN API key. + +## Configuration + + + +### Open the model editor + +In the chat's model picker, click the gear. Or open the command palette and look for **Chat: Manage Language Models**. + +### Add a provider + +Choose **Add Models** and then **Custom Endpoint**. + +It will ask for a group name, a display name and the API key. Put `NaN` as the name and paste your key, the one that starts with `sk-`. + +When it asks for the API type, choose **Chat Completions**. + +### Declare the models + +A `chatLanguageModels.json` file opens. Leave it like this: + +```json +[ + { + "name": "NaN", + "vendor": "customendpoint", + "apiKey": "${input:nanApiKey}", + "apiType": "chat-completions", + "models": [ + { + "id": "glm5.3-flash", + "name": "GLM 5.3 Flash", + "url": "https://api.nan.builders/v1/chat/completions", + "toolCalling": true, + "vision": true, + "maxInputTokens": 900000, + "maxOutputTokens": 32000 + }, + { + "id": "deepseek-v4-flash", + "name": "DeepSeek V4 Flash", + "url": "https://api.nan.builders/v1/chat/completions", + "toolCalling": true, + "vision": true, + "maxInputTokens": 900000, + "maxOutputTokens": 32000 + }, + { + "id": "qwen3.8-flash", + "name": "Qwen 3.8 Flash", + "url": "https://api.nan.builders/v1/chat/completions", + "toolCalling": true, + "vision": true, + "maxInputTokens": 240000, + "maxOutputTokens": 32000 + } + ] + } +] +``` + +### Pick the model and try it + +Open the chat, drop down the model picker and choose one of the ones you just added. Ask it for something short. If it answers, it is going out through the cluster. + + + +## Three details that matter + +- **The `url` is the full one, with `/chat/completions` at the end.** This is unlike the other tools, where you only put the base. VS Code appends the path on its own depending on the API type, and giving it the whole thing keeps it from getting that wrong. +- **`toolCalling: true` is what enables agent mode.** Without it, the model shows up in chat but cannot use tools or edit files. +- **`maxInputTokens` sits below the real context on purpose.** It is the input budget, and if you leave it flush with the window there is no room left for the answer. At 900,000 out of a million you have plenty of margin. + +## The key + +`"apiKey": "${input:nanApiKey}"` makes VS Code ask you for it and keep it in its secret store, instead of leaving it written in the file. That is what you want: that file easily ends up in a repository or a backup. + +## Recommended model + +`glm5.3-flash` for agent mode, which is where long context matters. `deepseek-v4-flash` for chat questions. `qwen3.8-flash` if you prefer fast answers. + +`glm5.3` only if you have the premium tier, and in that case set `"vision": false`: it does not accept images. + +
+ +- **Autocomplete does not change.** It is Copilot's and cannot be redirected to another provider. If what you want is for inline suggestions to come out of NaN, the tool is [Zed](/docs/zed), which does allow it. +- **Some features still ask for a GitHub account**, such as semantic search over the repository. It is not a problem with your key. +- **The provider arrived in 1.122.** Earlier versions had a `github.copilot.chat.customOAIModels` setting which is now deprecated: if you have it set, remove it and use the provider. +- **If the model shows up but does not edit files**, it is almost always `toolCalling` missing or set to `false`. + +
+ +
+ +This page covers Copilot **inside VS Code**, which is where the Custom Endpoint provider lives. Copilot in JetBrains, in Visual Studio or in its CLI does not offer the same custom endpoint mechanism. + +If you work in JetBrains, the way to use NaN is another plugin that accepts an OpenAI base URL, or [Continue](/docs/other-tools), which does have a JetBrains version. + +
diff --git a/src/content/docs/zed.mdx b/src/content/docs/zed.mdx new file mode 100644 index 0000000..e00a61e --- /dev/null +++ b/src/content/docs/zed.mdx @@ -0,0 +1,65 @@ +--- +title: Zed +description: Use NaN models in Zed's assistant and in its inline edit predictions. +order: 12 +group: Set up your agent +--- + +import Details from '../../components/docs/Details.astro'; + +# Zed. + +Zed accepts OpenAI-compatible providers both for the assistant and for the predictions it makes as you type. + +## Configuration + +Open `~/.config/zed/settings.json` and add: + +```json +{ + "language_models": { + "openai": { + "api_url": "https://api.nan.builders/v1", + "available_models": [ + { + "name": "glm5.3-flash", + "display_name": "NaN GLM 5.3 Flash", + "max_tokens": 1000000 + }, + { + "name": "deepseek-v4-flash", + "display_name": "NaN DeepSeek V4 Flash", + "max_tokens": 1000000 + } + ] + } + }, + "edit_predictions": { + "open_ai_compatible_api": { + "api_url": "https://api.nan.builders/v1", + "model": "qwen3.8-flash" + } + } +} +``` + +`name` is the id that travels in the request and has to be spelled exactly. `display_name` is only what you will see in the picker, so call it whatever you like. + +## The key + +Zed does not keep the key in `settings.json`. It asks you for it through the interface: open the assistant panel, go into the OpenAI provider settings and paste your NaN key there. + +## Check that it works + +Open the assistant panel, pick one of the models you declared and ask it anything. + +## Recommended model + +`glm5.3-flash` for the assistant. For inline predictions, `qwen3.8-flash`: what matters there is latency, because the result has to appear while you type. + +
+ +- **Do not put a slow model in `edit_predictions`.** Predictions are requested very often, and with a big model they will arrive late and spend quota for nothing. +- **Only the models you declare show up.** Zed does not ask the cluster what is available: it shows exactly what you wrote in `available_models`. + +
diff --git a/src/data/modelos.json b/src/data/modelos.json index 4818698..9640bab 100644 --- a/src/data/modelos.json +++ b/src/data/modelos.json @@ -84,9 +84,9 @@ "label": "rerank", "modelos": [ { - "id": "qwen3-reranker", + "id": "rerank", "by": "Alibaba", - "specs": "8B · BF16 · 100+ languages · cross-lingual", + "specs": "Qwen3-Reranker-8B · BF16 · 100+ languages · cross-lingual", "cuota": "sin contador", "frontier": false } @@ -111,6 +111,19 @@ "frontier": false } ] + }, + { + "id": "imagen", + "label": "image", + "modelos": [ + { + "id": "flux-2-klein", + "by": "Black Forest Labs", + "specs": "FLUX diffusion · text→image · image→image · 256-1536 px · 1-4 per request", + "cuota": "100 req/mes", + "frontier": false + } + ] } ] } diff --git a/src/lib/__fixtures__/agentgrid.expected.md b/src/lib/__fixtures__/agentgrid.expected.md new file mode 100644 index 0000000..c1ad62b --- /dev/null +++ b/src/lib/__fixtures__/agentgrid.expected.md @@ -0,0 +1,4 @@ +## Pick your agent + +- [Claude Code](/docs/claude-code) - Terminal. needs a local gateway. Speaks the Anthropic format. +- [Zed](/docs/zed) - Editor \ No newline at end of file diff --git a/src/lib/__fixtures__/agentgrid.mdx b/src/lib/__fixtures__/agentgrid.mdx new file mode 100644 index 0000000..422c13c --- /dev/null +++ b/src/lib/__fixtures__/agentgrid.mdx @@ -0,0 +1,10 @@ +import AgentGrid from '../../components/docs/AgentGrid.astro'; + +## Pick your agent + + diff --git a/src/lib/__fixtures__/details.expected.md b/src/lib/__fixtures__/details.expected.md new file mode 100644 index 0000000..19bdecc --- /dev/null +++ b/src/lib/__fixtures__/details.expected.md @@ -0,0 +1,4 @@ +**Known issues** + +- The first one. +- The second one. \ No newline at end of file diff --git a/src/lib/__fixtures__/details.mdx b/src/lib/__fixtures__/details.mdx new file mode 100644 index 0000000..c9309cf --- /dev/null +++ b/src/lib/__fixtures__/details.mdx @@ -0,0 +1,8 @@ +import Details from '../../components/docs/Details.astro'; + +
+ +- The first one. +- The second one. + +
diff --git a/src/lib/__fixtures__/steps.expected.md b/src/lib/__fixtures__/steps.expected.md new file mode 100644 index 0000000..1549d47 --- /dev/null +++ b/src/lib/__fixtures__/steps.expected.md @@ -0,0 +1,11 @@ +## Setup + +### Get your key + +Generate it on the platform. + +### Call the API + +```bash +curl https://api.nan.builders/v1/models +``` \ No newline at end of file diff --git a/src/lib/__fixtures__/steps.mdx b/src/lib/__fixtures__/steps.mdx new file mode 100644 index 0000000..258ff47 --- /dev/null +++ b/src/lib/__fixtures__/steps.mdx @@ -0,0 +1,17 @@ +import Steps from '../../components/docs/Steps.astro'; + +## Setup + + + +### Get your key + +Generate it on the platform. + +### Call the API + +```bash +curl https://api.nan.builders/v1/models +``` + + diff --git a/src/lib/apiDoc.ts b/src/lib/apiDoc.ts index 0767d89..a66f896 100644 --- a/src/lib/apiDoc.ts +++ b/src/lib/apiDoc.ts @@ -12,16 +12,19 @@ import { rateLimitsToSpecMarkdown, type RateLimitsConfig } from './rateLimits'; * had as `api.mdx`. The manifest contract does not change; only where the * text comes from does. * - * The metadata mirrors the frontmatter `api.mdx` used to carry (`order: 2` - * included) so that neither the manifest order nor the docs navigation moves - * with the migration. + * The metadata mirrors the frontmatter `api.mdx` used to carry, so that + * neither the manifest order nor the docs navigation moved with the + * migration. `order` is the one field that has changed since: the agent setup + * guides were inserted before the reference, and the shell requires the orders + * to be contiguous from zero (see DocsShell.test.ts), so every page after it + * shifted too. */ export const API_DOC_SLUG = 'api'; export const API_DOC_META = { title: 'API', description: 'Public API endpoint reference. OpenAI-compatible.', - order: 2, + order: 17, /** * The nav group, per locale. * diff --git a/src/lib/mdxToText.test.ts b/src/lib/mdxToText.test.ts index f39b5d3..c963639 100644 --- a/src/lib/mdxToText.test.ts +++ b/src/lib/mdxToText.test.ts @@ -15,6 +15,10 @@ const KNOWN_COMPONENTS = [ 'FieldList', 'Callout', 'RateLimits', + 'AgentGrid', + 'Steps', + 'Details', + 'BrandIntro', ]; const FIXTURES = [ @@ -24,6 +28,9 @@ const FIXTURES = [ 'fieldlist', 'callout', 'ratelimits', + 'agentgrid', + 'steps', + 'details', 'raw-html-heading', 'raw-html-inline', 'composite', diff --git a/src/lib/mdxToText.ts b/src/lib/mdxToText.ts index 7fa70bf..433661c 100644 --- a/src/lib/mdxToText.ts +++ b/src/lib/mdxToText.ts @@ -17,6 +17,10 @@ const KNOWN_COMPONENTS = new Set([ 'FieldList', 'Callout', 'RateLimits', + 'AgentGrid', + 'Steps', + 'Details', + 'BrandIntro', ]); const AUTHOR_HTML_BLOCK_TAGS = new Set(['h1', 'h2', 'h3', 'h4']); @@ -253,6 +257,14 @@ function componentToBlockMd(node: MdxNode, rateLimits: RateLimitsConfig): string return calloutToMd(node); case 'RateLimits': return rateLimitsToMd(rateLimits); + case 'AgentGrid': + return agentGridToMd(node); + case 'Steps': + return stepsToMd(node); + case 'Details': + return detailsToMd(node); + case 'BrandIntro': + return brandIntroToMd(node); default: throw new Error(`Unknown MDX block component: <${name || '?'}>`); } @@ -344,6 +356,65 @@ function calloutToMd(node: MdxNode): string { return `> [!${variant}] ${title}\n>\n${quoted}\n`; } +/** + * The agent picker as a list of links. + * + * The grid is a layout, not content: what a text consumer needs is which tools + * are covered and where each one lives. `indirect` survives because it is the + * one fact that changes what a reader has to do. + */ +function agentGridToMd(node: MdxNode): string { + const items = + (getAttr(node, 'items') as + | Array<{ name?: string; href?: string; kind?: string; note?: string; indirect?: boolean }> + | undefined) || []; + const lines: string[] = []; + for (const it of items) { + const name = String(it.name ?? ''); + const href = String(it.href ?? ''); + const parts = [String(it.kind ?? '')]; + if (it.indirect) parts.push('needs a local gateway'); + if (it.note) parts.push(stripInlineHtml(String(it.note))); + lines.push(`- [${name}](${href}) - ${parts.filter(Boolean).join('. ')}`); + } + lines.push(''); + return lines.join('\n'); +} + +/** + * Steps are numbered by CSS, so their markdown is already complete: the + * children carry the headings and the prose. Serving them unchanged is what + * keeps /api/docs identical whether a guide is wrapped in the rail or not. + */ +function stepsToMd(node: MdxNode): string { + return `${stringifyBlockChildren(node.children || []).trim()}\n`; +} + +/** + * A collapsed block flattens to its summary plus its body. + * + * Whether it was open or closed is a property of a browser, and the reader on + * the other side of /api/docs does not have one. + */ +function detailsToMd(node: MdxNode): string { + const summary = String(getAttr(node, 'summary') ?? ''); + const inner = stringifyBlockChildren(node.children || []).trim(); + return `**${summary}**\n\n${inner}\n`; +} + +/** + * The mark plus the paragraph beside it. + * + * The image survives as markdown rather than being dropped: a consumer that + * renders the text gets the logo, and one that does not still reads the alt. + */ +function brandIntroToMd(node: MdxNode): string { + const src = String(getAttr(node, 'src') ?? ''); + const alt = String(getAttr(node, 'alt') ?? ''); + const inner = stringifyBlockChildren(node.children || []).trim(); + return `![${alt}](${src})` + String.fromCharCode(10, 10) + inner + String.fromCharCode(10); +} + function rateLimitsToMd(config: RateLimitsConfig): string { const lines = [ '**rate limits per API key**', diff --git a/src/lib/modelCatalog.ts b/src/lib/modelCatalog.ts new file mode 100644 index 0000000..a6b6d2c --- /dev/null +++ b/src/lib/modelCatalog.ts @@ -0,0 +1,256 @@ +/** + * The one list of model ids NaN serves. + * + * The ids were written by hand on five surfaces with no relation to each + * other: the home table (src/data/modelos.json), the model cards of + * /docs/models, the snippets of /docs/examples, the catalog table inside + * src/data/openapi.json, and the quickstart. They drifted, and a drift here is + * not cosmetic: the home published the reranker as `qwen3-reranker` while the + * API answers to `rerank`, so anyone who copied the id off the landing page + * got a 404 `model_not_found`. `flux-2-klein` had the opposite problem, served + * by the API and absent from the table, which makes a model invisible. + * + * This module holds the facts a reader needs in order to CHOOSE (what it is + * for, how much context, what it accepts, what it spends), not the editorial + * prose of each card. The cards stay hand-written, because what they say about + * a model is a judgement, not data. What cannot be a judgement is the spelling + * of an id, and that is what modelCatalog.test.ts checks across every surface. + * + * glm5.3's window and allowance are NOT repeated here: they already live in + * rateLimits.ts, which is env-driven and feeds both the card and /api/docs. + */ + +export type ModelKind = 'chat' | 'embedding' | 'rerank' | 'tts' | 'stt' | 'image'; + +export type Modality = 'text' | 'image' | 'audio'; + +/** + * How a model counts against what you pay for. + * + * `unmetered` is not "unlimited": the per-minute limits in rateLimits.ts still + * apply. It means no token counter is attached to the model. `billingPeriod` + * exists because glm5.3's counter returns to zero when the Stripe period + * starts, not on the first of the month, and writing it as monthly is the + * mistake every surface made at least once. + */ +export type QuotaKind = 'unmetered' | 'monthly' | 'billingPeriod'; + +export interface ModelSpec { + /** Exactly what goes in the `model` field of a request. */ + id: string; + /** Who trained it, so a reader can map the id to a model they know. */ + by: string; + kind: ModelKind; + /** Context window in tokens. Chat models only. */ + contextTokens?: number; + /** What it accepts as input. Output is text on every chat model. */ + inputs?: Modality[]; + quota: { kind: QuotaKind; label?: { en: string; es: string } }; + /** Only callable with a key on the premium tier. */ + premium?: boolean; + /** + * A previous generation, still served so that configurations naming it keep + * working. Not what a newcomer should be pointed at, and it was: every + * snippet in the docs used `qwen3.6`. + */ + legacy?: boolean; + /** The endpoint the id is called on. */ + endpoint: string; + /** The one-line answer to "when do I pick this one?". */ + bestFor: { en: string; es: string }; +} + +export const MODELS: ModelSpec[] = [ + { + id: 'deepseek-v4-flash', + by: 'DeepSeek', + kind: 'chat', + contextTokens: 1_000_000, + inputs: ['text', 'image'], + quota: { kind: 'monthly', label: { en: '3B tokens / mo', es: '3B tokens/mes' } }, + endpoint: '/chat/completions', + bestFor: { + en: 'General chat and reasoning, the best quality on the cluster', + es: 'Chat y razonamiento general, la mejor calidad del clúster', + }, + }, + { + id: 'glm5.3', + by: 'Z.ai', + kind: 'chat', + contextTokens: 1_000_000, + inputs: ['text'], + quota: { + kind: 'billingPeriod', + label: { en: '3B tokens / billing period', es: '3B tokens/periodo de facturación' }, + }, + premium: true, + endpoint: '/chat/completions', + bestFor: { + en: 'Coding agents and long-horizon tasks', + es: 'Agentes de código y tareas largas', + }, + }, + { + id: 'glm5.3-flash', + by: 'Z.ai', + kind: 'chat', + contextTokens: 1_000_000, + inputs: ['text', 'image'], + quota: { kind: 'monthly', label: { en: '2B tokens / mo', es: '2B tokens/mes' } }, + endpoint: '/chat/completions', + bestFor: { + en: 'Coding agents without the premium tier', + es: 'Agentes de código sin el tier premium', + }, + }, + { + id: 'qwen3.8-flash', + by: 'Alibaba', + kind: 'chat', + contextTokens: 262_144, + inputs: ['text', 'image'], + quota: { kind: 'monthly', label: { en: '500M tokens / mo', es: '500M tokens/mes' } }, + endpoint: '/chat/completions', + bestFor: { + en: 'Fast answers, when latency matters more than depth', + es: 'Respuestas rápidas, cuando importa más la latencia que la profundidad', + }, + }, + { + id: 'mimo-v2.5', + by: 'Xiaomi', + kind: 'chat', + contextTokens: 1_000_000, + inputs: ['text', 'image', 'audio'], + quota: { kind: 'monthly', label: { en: '1.0B tokens / mo', es: '1.0B tokens/mes' } }, + endpoint: '/chat/completions', + bestFor: { + en: 'Passing audio straight to the model. The only omnimodal one', + es: 'Pasarle audio directamente al modelo. El único omnimodal', + }, + }, + { + id: 'gemma4', + by: 'Google', + kind: 'chat', + contextTokens: 262_144, + inputs: ['text', 'image'], + quota: { kind: 'unmetered' }, + endpoint: '/chat/completions', + bestFor: { + en: 'Short tasks and testing, with no token counter attached', + es: 'Tareas cortas y pruebas, sin contador de tokens', + }, + }, + { + id: 'qwen3.6', + by: 'Alibaba', + kind: 'chat', + contextTokens: 262_144, + inputs: ['text', 'image'], + quota: { kind: 'unmetered' }, + legacy: true, + endpoint: '/chat/completions', + bestFor: { + en: 'Previous generation. Still served, so existing configs keep working', + es: 'Generación anterior. Se sigue sirviendo, así que las configuraciones que ya lo nombran no se rompen', + }, + }, + { + id: 'qwen3-embedding', + by: 'Alibaba', + kind: 'embedding', + inputs: ['text'], + quota: { kind: 'unmetered' }, + endpoint: '/embeddings', + bestFor: { + en: 'Turning text into 4096-dimension vectors. First half of a RAG stack', + es: 'Convertir texto en vectores de 4096 dimensiones. Primera mitad de un RAG', + }, + }, + { + id: 'rerank', + by: 'Alibaba', + kind: 'rerank', + inputs: ['text'], + quota: { kind: 'unmetered' }, + endpoint: '/rerank', + bestFor: { + en: 'Reordering what you retrieved by relevance. Second half of a RAG stack', + es: 'Reordenar por relevancia lo que has recuperado. Segunda mitad de un RAG', + }, + }, + { + id: 'kokoro', + by: 'Hexgrad', + kind: 'tts', + inputs: ['text'], + quota: { kind: 'unmetered' }, + endpoint: '/audio/speech', + bestFor: { en: 'Text to speech, 67 voices', es: 'Texto a voz, 67 voces' }, + }, + { + id: 'whisper', + by: 'OpenAI', + kind: 'stt', + inputs: ['audio'], + quota: { kind: 'unmetered' }, + endpoint: '/audio/transcriptions', + bestFor: { en: 'Speech to text, 99+ languages', es: 'Voz a texto, 99+ idiomas' }, + }, + { + id: 'flux-2-klein', + by: 'Black Forest Labs', + kind: 'image', + inputs: ['text', 'image'], + quota: { kind: 'monthly', label: { en: '100 requests / mo', es: '100 peticiones/mes' } }, + endpoint: '/images/generations', + bestFor: { + en: 'Generating and editing images', + es: 'Generar y editar imágenes', + }, + }, +]; + +/** Every id the cluster answers to, for validating what the docs write. */ +export const MODEL_IDS: readonly string[] = MODELS.map((m) => m.id); + +export function getModel(id: string): ModelSpec | undefined { + return MODELS.find((m) => m.id === id); +} + +/** The chat models, in the order a reader should consider them. */ +export const CHAT_MODELS = MODELS.filter((m) => m.kind === 'chat'); + +/** + * The context window as the docs write it: 1M, 262K. + * + * Decimal, not binary. 262144 tokens was published as "262K" on the + * qwen3.8-flash card and as "256K" on the qwen3.6 and gemma4 ones, which is + * the same number written two ways and reads as two different windows. One + * function settles it for every surface. + * + * Deliberately not Intl-formatted: these are round figures that read the same + * in both languages, and the exact token count belongs in the model card, not + * in a decision table. + */ +export function formatContext(tokens: number): string { + if (tokens >= 1_000_000) return `${tokens / 1_000_000}M`; + return `${Math.round(tokens / 1000)}K`; +} + +/** What the quota column shows, including the unmetered case. */ +export function quotaLabel(m: ModelSpec, lang: 'en' | 'es'): string { + if (m.quota.kind === 'unmetered') return lang === 'es' ? 'sin contador' : 'unmetered'; + return m.quota.label![lang]; +} + +/** The input modalities as a short list: "text · image". */ +export function inputsLabel(m: ModelSpec, lang: 'en' | 'es'): string { + const T = { + en: { text: 'text', image: 'image', audio: 'audio' }, + es: { text: 'texto', image: 'imagen', audio: 'audio' }, + }[lang]; + return (m.inputs ?? ['text']).map((i) => T[i]).join(' · '); +} diff --git a/src/pages/install.ts b/src/pages/install.ts new file mode 100644 index 0000000..2f03605 --- /dev/null +++ b/src/pages/install.ts @@ -0,0 +1,91 @@ +import type { APIRoute } from 'astro'; + +/** + * `https://nan.builders/install`, the URL the CLI pipes into bash. + * + * The README of helmcode/nan-cli has always told people to run + * `curl -fsSL https://nan.builders/install | bash`, and this site answered 404. + * A member hit exactly that, fell back to cloning the repo and building it with + * Go, and only got there after a long detour. The installer is the first thing + * a new member touches, so a 404 here costs more than it looks. + * + * The script itself is NOT copied into this repo. It lives next to the code it + * installs (scripts/install.sh in nan-cli), and a second copy here would go + * stale the first time the CLI changes how it lays itself out. This route + * fetches that one and serves it. + * + * It is served rather than redirected to because the whole point is a command + * that pipes into a shell: a redirect only works if the caller passes `-L`, and + * a 302 with an empty body piped into bash is a silent no-op, which is worse + * than an error. + */ + +export const prerender = false; + +/** + * Pinned to a RELEASED TAG, not to `main`. + * + * Whatever this URL returns runs on a member's machine, under sudo in the + * default install path. Off `main`, any push to the CLI repo changes that + * instantly and with no review step in between; a tag is immutable, so what + * ships here is a revision somebody decided to publish. + * + * The cost is that this constant has to be bumped when the CLI changes how it + * installs itself. That is not the same as bumping it on every release: the + * script asks the GitHub API for the LATEST release at run time, so a member + * running an older installer still gets the newest binary. Only a change to + * `scripts/install.sh` itself needs a bump here. + */ +const SCRIPT_URL = + 'https://raw.githubusercontent.com/helmcode/nan-cli/v0.1.1/scripts/install.sh'; + +export const GET: APIRoute = async () => { + let upstream: Response; + try { + upstream = await fetch(SCRIPT_URL); + } catch { + return scriptError('could not reach the install script upstream'); + } + + if (!upstream.ok) { + return scriptError(`install script unavailable (upstream ${upstream.status})`); + } + + const body = await upstream.text(); + + // A shell script that does not start with a shebang is a sign the upstream + // answered with something else (an HTML error page, a redirect notice). Piped + // into bash that executes as commands, so it is refused here instead. + if (!body.startsWith('#!')) { + return scriptError('install script upstream returned something that is not a script'); + } + + return new Response(body, { + status: 200, + headers: { + 'Content-Type': 'text/x-shellscript; charset=utf-8', + // Short: the point of the one-liner is that it installs the current + // version, and an edge cache holding a stale installer for a day is the + // kind of thing nobody thinks to check. + 'Cache-Control': 'public, max-age=300', + }, + }); +}; + +/** + * Failure as a script that exits non-zero, not as an HTTP error page. + * + * The caller is a shell. An HTML body with a 500 would either be executed line + * by line or swallowed; a script that echoes the reason and exits 1 is the only + * form that reaches the person running the command. + */ +function scriptError(message: string): Response { + const body = `#!/usr/bin/env bash\necho "nan install: ${message}" >&2\necho "Install from source instead: https://github.com/helmcode/nan-cli" >&2\nexit 1\n`; + return new Response(body, { + status: 503, + headers: { + 'Content-Type': 'text/x-shellscript; charset=utf-8', + 'Cache-Control': 'no-store', + }, + }); +} diff --git a/src/tests/api/install.test.ts b/src/tests/api/install.test.ts new file mode 100644 index 0000000..482013e --- /dev/null +++ b/src/tests/api/install.test.ts @@ -0,0 +1,113 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { GET } from '../../pages/install'; + +/** + * GET /install, the URL `curl -fsSL https://nan.builders/install | bash` hits. + * + * The caller is a shell, not a browser, which is what these asserts are really + * about: whatever comes back gets executed. A 404 page, an HTML error, or an + * empty body from a redirect the caller did not follow are all failures that + * look fine in a browser and do nothing (or worse) in a terminal. + * + * The upstream fetch is stubbed so the suite does not depend on GitHub being + * reachable, and so the failure paths can actually be exercised. + */ + +const SCRIPT = '#!/usr/bin/env bash\nset -euo pipefail\necho installing\n'; + +const call = () => GET({} as never); + +afterEach(() => { + vi.unstubAllGlobals(); +}); + +describe('GET /install', () => { + it('serves the upstream script as a shell script', async () => { + vi.stubGlobal('fetch', vi.fn(async () => new Response(SCRIPT, { status: 200 }))); + + const res = await call(); + + expect(res.status).toBe(200); + expect(res.headers.get('content-type')).toMatch(/text\/x-shellscript/); + expect(await res.text()).toBe(SCRIPT); + }); + + it('reads the script from the CLI repo instead of keeping a copy here', async () => { + const fetchMock = vi.fn(async () => new Response(SCRIPT, { status: 200 })); + vi.stubGlobal('fetch', fetchMock); + + await call(); + + expect(fetchMock).toHaveBeenCalledTimes(1); + expect(String(fetchMock.mock.calls[0][0])).toContain('helmcode/nan-cli'); + }); + + /** + * What this route returns is piped into a shell, under sudo in the default + * install path. Off a branch, anyone who can push to the CLI repo changes + * that with no review step in between, and nothing here would notice. A tag + * cannot move, so the ref is asserted rather than trusted. + */ + it('pins the script to a tag, not to a moving branch', async () => { + const fetchMock = vi.fn(async () => new Response(SCRIPT, { status: 200 })); + vi.stubGlobal('fetch', fetchMock); + + await call(); + + const url = String(fetchMock.mock.calls[0][0]); + expect(url, url).toMatch(/helmcode\/nan-cli\/v\d+\.\d+\.\d+\//); + }); + + /** + * A body without a shebang means the upstream answered with something that + * is not the installer. Piped into bash it runs line by line, so it is + * refused rather than passed on. + */ + it('refuses a body that is not a script', async () => { + vi.stubGlobal( + 'fetch', + vi.fn(async () => new Response('404', { status: 200 })), + ); + + const res = await call(); + + expect(res.status).toBe(503); + expect(await res.text()).not.toContain(' { + vi.stubGlobal('fetch', vi.fn(async () => new Response('not found', { status: 404 }))); + + const res = await call(); + const body = await res.text(); + + expect(res.status).toBe(503); + expect(body.startsWith('#!')).toBe(true); + expect(body).toContain('exit 1'); + // The person running the one-liner needs somewhere to go next. + expect(body).toContain('github.com/helmcode/nan-cli'); + }); + + it('answers a network failure the same way', async () => { + vi.stubGlobal( + 'fetch', + vi.fn(async () => { + throw new Error('ECONNREFUSED'); + }), + ); + + const res = await call(); + + expect(res.status).toBe(503); + expect((await res.text()).startsWith('#!')).toBe(true); + }); + + /** An edge cache holding a stale installer is the kind of thing nobody checks. */ + it('does not let the installer be cached for long', async () => { + vi.stubGlobal('fetch', vi.fn(async () => new Response(SCRIPT, { status: 200 }))); + + const res = await call(); + + expect(res.headers.get('cache-control')).toBe('public, max-age=300'); + }); +}); diff --git a/src/tests/layouts/DocsShell.test.ts b/src/tests/layouts/DocsShell.test.ts index a32419f..f594e0b 100644 --- a/src/tests/layouts/DocsShell.test.ts +++ b/src/tests/layouts/DocsShell.test.ts @@ -168,8 +168,13 @@ describe('frontmatter of the docs collection', () => { expect(metas.length).toBeGreaterThan(0); }); + /** + * The list is closed on purpose: a typo in `group` does not fail a build, it + * silently opens a fourth nav section holding one page. Adding a section is + * a deliberate act, so it is written here too. + */ it('declares a known group on every guide', () => { - const known = new Set(['Get started', 'Reference', 'Guides']); + const known = new Set(['Get started', 'Set up your agent', 'Reference', 'Guides']); for (const m of metas) { expect(known.has(m.data.group ?? ''), `${m.file}: group=${m.data.group}`).toBe(true); } diff --git a/src/tests/lib/modelCatalog.test.ts b/src/tests/lib/modelCatalog.test.ts new file mode 100644 index 0000000..2dfc236 --- /dev/null +++ b/src/tests/lib/modelCatalog.test.ts @@ -0,0 +1,184 @@ +import { describe, expect, it } from 'vitest'; +import { readFileSync, readdirSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { dirname, resolve } from 'node:path'; +import modelos from '../../data/modelos.json'; +import spec from '../../data/openapi.json'; +import { MODEL_IDS, MODELS, formatContext, quotaLabel } from '../../lib/modelCatalog'; + +/** + * The model ids, checked across every surface that writes one. + * + * This exists because they had already drifted in both directions and neither + * failed a build: the home table published the reranker as `qwen3-reranker` + * while the API answers to `rerank` (copy that id and you get a 404), and + * `flux-2-klein` was served by the API while being absent from the table. + * Reviewing a table of ids by eye does not catch either, so it is done here. + * + * What is asserted is the SPELLING and the SET, never the prose: the model + * cards say what they like about a model, but they cannot name one that does + * not exist. + */ + +const here = dirname(fileURLToPath(import.meta.url)); +const enDir = resolve(here, '../../content/docs'); +const esDir = resolve(here, '../../content/docs-es'); + +const docFiles = [enDir, esDir].flatMap((dir) => + readdirSync(dir) + .filter((f) => /\.(md|mdx)$/.test(f)) + .map((f) => [`${dir.endsWith('docs') ? 'en' : 'es'}/${f}`, readFileSync(resolve(dir, f), 'utf-8')] as const), +); + +describe('the catalog itself', () => { + it('has no duplicate ids', () => { + expect(new Set(MODEL_IDS).size).toBe(MODEL_IDS.length); + }); + + it('gives every chat model a context window and its inputs', () => { + for (const m of MODELS.filter((m) => m.kind === 'chat')) { + expect(m.contextTokens, m.id).toBeGreaterThan(0); + expect(m.inputs?.length, m.id).toBeGreaterThan(0); + } + }); + + /** An unmetered model has no label to show, a metered one must have both. */ + it('labels every quota that is not unmetered, in both languages', () => { + for (const m of MODELS) { + if (m.quota.kind === 'unmetered') continue; + expect(m.quota.label?.en, m.id).toBeTruthy(); + expect(m.quota.label?.es, m.id).toBeTruthy(); + } + }); + + /** + * glm5.3's counter follows the Stripe billing period, not the calendar + * month. Writing it as monthly is the mistake every surface made once. + */ + it('does not publish the premium allowance as monthly', () => { + const glm = MODELS.find((m) => m.id === 'glm5.3')!; + expect(glm.quota.kind).toBe('billingPeriod'); + expect(quotaLabel(glm, 'es')).not.toContain('/mes'); + expect(quotaLabel(glm, 'en')).not.toMatch(/\/ mo\b/); + }); + + /** 262144 was published as both "262K" and "256K". One number, one label. */ + it('writes a context window the same way everywhere', () => { + expect(formatContext(262_144)).toBe('262K'); + expect(formatContext(1_000_000)).toBe('1M'); + }); +}); + +describe('the home table (src/data/modelos.json)', () => { + const homeIds = modelos.categorias.flatMap((c) => c.modelos.map((m) => m.id)).sort(); + + it('lists exactly the models in the catalog', () => { + expect(homeIds).toEqual([...MODEL_IDS].sort()); + }); + + it('marks as premium exactly the models the catalog does', () => { + const homePremium = modelos.categorias + .flatMap((c) => c.modelos) + .filter((m) => 'premium' in m && m.premium) + .map((m) => m.id) + .sort(); + expect(homePremium).toEqual(MODELS.filter((m) => m.premium).map((m) => m.id).sort()); + }); +}); + +describe('the model cards of /docs/models', () => { + /** `id=` on the card is an HTML anchor; `name=` is the API id. */ + const cardIds = (source: string) => + [...source.matchAll(/ m[1]); + + for (const dir of [enDir, esDir]) { + const label = dir.endsWith('docs') ? 'en' : 'es'; + it(`names only real models (${label})`, () => { + const names = cardIds(readFileSync(resolve(dir, 'models.mdx'), 'utf-8')); + expect(names.length).toBeGreaterThan(0); + for (const name of names) { + expect(MODEL_IDS, `${label}: ${name}`).toContain(name); + } + }); + + it(`leaves no model out of the reference (${label})`, () => { + const names = new Set(cardIds(readFileSync(resolve(dir, 'models.mdx'), 'utf-8'))); + for (const id of MODEL_IDS) { + expect([...names], `${label}: ${id} has no card`).toContain(id); + } + }); + } +}); + +describe('the snippets in the guides', () => { + /** + * Names that appear in a `model` field without being ids of ours. + * + * The Claude Code guide sets up a local LiteLLM gateway, and a gateway + * renames what it proxies: `nan-coder` is a label the reader invents in + * their own config, and the request that reaches us carries the real id from + * `litellm_params`. They are listed one by one rather than skipping the file + * so that a genuine typo inside that same guide still fails. + */ + const GATEWAY_ALIASES = new Set(['nan-coder', 'nan-general']); + + /** + * Every id a reader can copy out of a code block and paste into a request. + * Both the JSON field and the keyword-argument form the SDK snippets use. + */ + const inSnippets = (source: string) => [ + ...[...source.matchAll(/"model"\s*:\s*"([^"]+)"/g)].map((m) => m[1]), + ...[...source.matchAll(/\bmodel\s*=\s*"([^"]+)"/g)].map((m) => m[1]), + ]; + + for (const [label, source] of docFiles) { + it(`only name models that exist (${label})`, () => { + for (const id of inSnippets(source)) { + if (GATEWAY_ALIASES.has(id)) continue; + expect(MODEL_IDS, `${label}: "${id}"`).toContain(id); + } + }); + } +}); + +describe('the decision page', () => { + /** + * The flat table of /docs/choose-a-model, which is the page a newcomer is + * sent to when they do not know which id to write. A model missing from it + * is a model nobody picks; an id spelled wrong there is a 404 the reader + * copies by hand. Only rows whose FIRST cell is a backticked id count, so + * the "what do I want to do" table above it is left alone. + */ + for (const [label, dir] of [ + ['en', enDir], + ['es', esDir], + ] as const) { + it(`lists exactly the models in the catalog (${label})`, () => { + const source = readFileSync(resolve(dir, 'choose-a-model.md'), 'utf-8'); + const listed = [...source.matchAll(/^\|\s*`([^`]+)`\s*\|/gm)].map((m) => m[1]); + expect([...listed].sort()).toEqual([...MODEL_IDS].sort()); + }); + } +}); + +describe('the model catalog of the API reference', () => { + /** + * The table under "## Model catalog" inside `info.description`. It is the + * copy the Discord bot serves, so an id missing there is an id nobody + * building a client finds. + */ + const section = (() => { + const text = spec.info.description; + const start = text.indexOf('## Model catalog'); + expect(start).toBeGreaterThan(-1); + const rest = text.slice(start); + const end = rest.indexOf('\n## ', 1); + return end === -1 ? rest : rest.slice(0, end); + })(); + + const listed = [...section.matchAll(/^\|\s*`([^`]+)`\s*\|/gm)].map((m) => m[1]); + + it('lists exactly the models in the catalog', () => { + expect([...listed].sort()).toEqual([...MODEL_IDS].sort()); + }); +});