From 4a214605838e61b07e16f0fad59426801102f061 Mon Sep 17 00:00:00 2001 From: barckcode Date: Tue, 13 Oct 2026 22:02:37 +0200 Subject: [PATCH 1/2] docs(byok): use your own provider key in runs and automations New "Use your own provider key (BYOK)" sections in /docs/runs and /docs/automations (EN+ES): providers, saving a Provider key and the base URL rules, choosing it per run (portal, nan run --byok-secret, API) and per automation, who can use it (no NaN plan needed with an active workspace; ad-hoc runs need a session), billing (your provider, not metered by NaN), limits (8 BYOK runs at once), security notes, errors, and the Hermes + Anthropic limitation. Workspaces, webhooks, PR gatekeeper and NaN CLI pages no longer say runs always need an inference plan. OpenAPI: the inference object (request oneOf and rendered shape), usage.source byok, BYOK errors on POST /v1/runs (400 inference*, 403 session_required, 404 inference.secret), automations run on your key, and the run error object (cloud-api #344) so cloud-api's own goldens validate. Tests validate cloud-api 4c69270 BYOK goldens against the schema and check the guide. Co-Authored-By: Claude Opus 5.5 --- src/content/docs-es/automations.mdx | 27 +- src/content/docs-es/nan-cli.mdx | 1 + src/content/docs-es/pr-gatekeeper.mdx | 2 +- src/content/docs-es/runs.mdx | 128 ++++++++- src/content/docs-es/webhooks.mdx | 4 +- src/content/docs-es/workspaces.mdx | 4 +- src/content/docs/automations.mdx | 27 +- src/content/docs/nan-cli.mdx | 1 + src/content/docs/pr-gatekeeper.mdx | 2 +- src/content/docs/runs.mdx | 128 ++++++++- src/content/docs/webhooks.mdx | 4 +- src/content/docs/workspaces.mdx | 4 +- src/data/openapi.json | 268 ++++++++++++++---- src/lib/openapiByok.test.ts | 155 ++++++++++ src/lib/openapiRuns.test.ts | 4 +- .../fixtures/phase3/automation_get_byok.json | 42 +++ src/tests/fixtures/phase3/run_get_byok.json | 59 ++++ src/tests/fixtures/phase3/run_list_byok.json | 119 ++++++++ src/tests/layouts/automationsDocs.test.ts | 17 +- src/tests/layouts/runsDocs.test.ts | 6 +- src/tests/lib/modelCatalog.test.ts | 22 +- 21 files changed, 933 insertions(+), 91 deletions(-) create mode 100644 src/lib/openapiByok.test.ts create mode 100644 src/tests/fixtures/phase3/automation_get_byok.json create mode 100644 src/tests/fixtures/phase3/run_get_byok.json create mode 100644 src/tests/fixtures/phase3/run_list_byok.json diff --git a/src/content/docs-es/automations.mdx b/src/content/docs-es/automations.mdx index 86966b27..9db2778c 100644 --- a/src/content/docs-es/automations.mdx +++ b/src/content/docs-es/automations.mdx @@ -24,7 +24,7 @@ El agente de una automatización no hace push ni publica nada por su cuenta. **P ## Antes de empezar - **Un workspace encendido**, con el agente que quieras instalado (Pi, Pi + Gentle Shell o Hermes), como para cualquier [run](/es/docs/runs#elige-el-agente). -- **Un plan con inferencia.** Los runs de una automatización usan la clave de inferencia de tu workspace y cuentan para el uso de tu plan como cualquier otro run. +- **Un plan con inferencia, o tu propia clave de proveedor.** Con la inferencia de NaN, los runs de una automatización usan la clave de inferencia de tu workspace y cuentan para el uso de tu plan como cualquier otro run. Con [tu propia clave de proveedor](#usa-tu-propia-clave-de-proveedor-byok), los factura tu proveedor y basta con un workspace propio activo. - **Una sesión en el navegador.** Crear o cambiar automatizaciones, escribir secretos y aprobar acciones exige que hayas iniciado sesión en [cloud.nan.builders](https://cloud.nan.builders/) desde tu navegador, con el enlace por correo. Una sesión de la CLI, una API key o un token pueden listar automatizaciones y lanzarlas, pero nunca cambiarlas. Consulta [Por qué algunas acciones necesitan el navegador](#por-qué-algunas-acciones-necesitan-el-navegador). - **Para las plantillas de GitHub, un token de GitHub guardado como secreto.** Sin él la automatización no se puede guardar, así que créalo antes: consulta [Un token de GitHub para las acciones](#un-token-de-github-para-las-acciones). @@ -62,6 +62,7 @@ Las tres plantillas de GitHub actúan sobre un repositorio, así que necesitan u | **Folder** | Dónde trabaja el agente, dentro de `/home/nan`. No se usa con **Fresh clone per run**. | | **Separate branch** | Rama aparte: **On** (por defecto), **Auto** u **Off (work in the folder directly)** (trabajar directamente en la carpeta), como en un [run](/es/docs/runs#dónde-trabaja-el-agente). No se usa con **Fresh clone per run**. | | **Time limit (minutes)** | El tiempo máximo, de 1 a 120. 30 por defecto. | +| **Inference** | La inferencia: **NaN (included in your plan)**, la predeterminada, o **My own key**, una **Provider key** tuya y el **Model**. Consulta [Usa tu propia clave de proveedor](#usa-tu-propia-clave-de-proveedor-byok). | | **GitHub repository (optional)** | `owner/name`, por ejemplo `acme/api`. El repositorio sobre el que actúan las acciones. | | **Checkout** | **Workspace folder** (por defecto): el agente trabaja en **Folder**, en tu workspace. **Fresh clone per run** (clon nuevo por run): NaN clona el **GitHub repository** en cada run, en el commit del que trata el evento, y borra el clon cuando termina el run. Consulta [Clon nuevo por run](#clon-nuevo-por-run). | | **Run pull requests from forks and outside contributors** | Aparece con **Fresh clone per run**. Desactivado por defecto. Consulta [Pull requests de forks y de colaboradores externos](#pull-requests-de-forks-y-de-colaboradores-externos). | @@ -89,7 +90,7 @@ Los secretos son los tokens y claves que usan tus automatizaciones: un token de ### Añade un secreto -Abre tus ajustes en el portal, **Secrets**, debajo de **Tokens**, y pulsa **Add secret**: +Abre tus ajustes en el portal, **Secrets**, debajo de **Tokens**, y pulsa **Add secret**. En **Kind**, **Secret** es para los tokens y claves que reciben tus automatizaciones; **Provider key** es para tu propia clave con un proveedor de IA, que se usa como inferencia de runs y automatizaciones: consulta [Usa tu propia clave de proveedor](#usa-tu-propia-clave-de-proveedor-byok). - **Name**: el nombre de la variable que ven tus automatizaciones, en mayúsculas, dígitos y `_`, empezando por una letra. Por ejemplo `GITHUB_TOKEN`. Hasta 64 caracteres. - **Value**: se guarda tal cual lo pegas, hasta 16 KiB. Los saltos de línea dentro del valor están bien (una clave PEM, por ejemplo), pero no al principio ni al final. @@ -99,7 +100,7 @@ Los secretos son de **solo escritura**: una vez guardados, nadie puede volver a - **Replace value**: el valor nuevo lo usan todos los runs que arranquen a partir de ese momento, incluidos los que ya están en cola. El anterior no se puede recuperar. - Cambiar su **Description**. -- **Delete**. El borrado se rechaza mientras una automatización lo use: quítalo antes de esas automatizaciones. +- **Delete**. El borrado se rechaza mientras una automatización lo use, como uno de sus secretos o como su provider key: quítalo antes de esas automatizaciones. Puedes guardar hasta 100 secretos. Mejor tokens de vida corta, limitados a lo que necesita la automatización. @@ -139,6 +140,24 @@ Si un push puede cambiar archivos dentro de `.github/workflows/` (la plantilla * Si el agente también necesita leer el repositorio (uno privado, o para traer logs de CI), crea un segundo token solo con permisos de lectura (**Contents**, **Pull requests**, **Issues**, y **Actions** para los logs de CI) y asígnalo como `GITHUB_TOKEN`, **Agent**. +## Usa tu propia clave de proveedor (BYOK) + +Una automatización puede usar **tu propia clave** con un proveedor de IA en lugar de la inferencia de NaN, igual que [un run suelto](/es/docs/runs#usa-tu-propia-clave-de-proveedor-byok). Guarda antes la clave como **Provider key** en **Secrets** (Anthropic, OpenAI, Google Gemini o cualquier endpoint OpenAI-compatible): [Guarda tu clave como provider key](/es/docs/runs#guarda-tu-clave-como-provider-key) explica el formulario y las reglas de la URL base. + +En los ajustes de la automatización, pon **Inference** en **My own key**, elige la **Provider key** y escribe el **Model**, tal como lo llama tu proveedor. A partir de ahí todos los runs de la automatización usan esa clave y ese modelo, los lance quien los lance: un webhook, una programación, **Run now**, la CLI o la API. Si vuelves a poner **Inference** en **NaN (included in your plan)**, vuelve a la inferencia de NaN, con el modelo de NaN que elijas o, si no eliges ninguno, el predeterminado del agente. + +- **Quién puede usarla.** Sin un plan con inferencia de NaN, una automatización con tu propia clave funciona con un workspace propio activo. Crearla y cambiarla necesitan el navegador, como cualquier automatización. +- **Cualquier credencial puede lanzarla.** A diferencia de [un run suelto con tu clave](/es/docs/runs#quién-puede-usarla), una automatización con tu clave se puede lanzar con una API key o con un token de plataforma con `automations:run`, por ejemplo desde CI: sus instrucciones son las que tú guardaste, y quien la lanza solo añade la entrada. +- **Facturación.** Sus runs los factura tu proveedor; NaN no los mide. La clave cuenta para los 48 KiB de secretos que puede recibir un run. +- **Límites.** Hasta 8 runs con tus propias claves a la vez entre todos tus workspaces, además de los límites de todos los runs. Consulta [Límites](/es/docs/runs#límites). +- **La clave está en uso.** No puedes borrar una provider key mientras una automatización la use: cambia antes esas automatizaciones. `nan secrets ls` y la lista de secretos muestran qué automatizaciones usan cada clave. `nan automations show` muestra la elección en su línea **inference**. + + +Lo que trae una entrega de webhook o un `input` lo escribe otra persona, y el agente lo lee mientras tiene tu clave en su entorno. Una pull request o una issue preparadas para ello podrían intentar que la imprima o la envíe. Dale a esas automatizaciones una clave limitada a lo que necesitan, con un límite de gasto en tu proveedor, y deja fuera a los emisores no fiables (forks, colaboradores externos) con los filtros del trigger. `nan automations show` te lo recuerda cuando un webhook lanza una automatización con tu clave. + + +Hermes todavía no puede usar una clave de **Anthropic** en los workspaces: sus runs fallan con `config_error`. Usa **Pi** o **Pi + Gentle Shell** con ella, o un endpoint **OpenAI-compatible**. + ## Acciones y aprobación ### Cómo actúa una automatización @@ -375,7 +394,7 @@ Para CI, dale al token `automations:run` y `runs` (para seguir el run y conocer | Instrucciones | 16 KiB | | Input de un run | Un objeto JSON de hasta 48 KiB, con 8 niveles de anidamiento como mucho | -Los runs de las automatizaciones comparten los límites de todos los runs: cuántos trabajan a la vez en cada workspace, como mucho 5 a la vez entre todos tus workspaces (8 con Premium), y la cola. Consulta [Cuántos runs a la vez](/es/docs/runs#cuántos-runs-a-la-vez). Las automatizaciones necesitan un plan con inferencia: sin él, el portal te enseña las opciones para mejorarlo y los webhooks se responden sin lanzar ningún run. +Los runs de las automatizaciones comparten los límites de todos los runs: cuántos trabajan a la vez en cada workspace, como mucho 5 a la vez entre todos tus workspaces (8 con Premium), y la cola. Consulta [Cuántos runs a la vez](/es/docs/runs#cuántos-runs-a-la-vez). Las automatizaciones con la inferencia de NaN necesitan un plan con inferencia: sin él, el portal te ofrece **My own key** (consulta [Usa tu propia clave de proveedor](#usa-tu-propia-clave-de-proveedor-byok)), y los webhooks de una automatización con la inferencia de NaN se responden sin lanzar ningún run. Los runs con tus propias claves tienen su propio límite: hasta 8 a la vez entre todos tus workspaces. ## Cambia o borra una automatización diff --git a/src/content/docs-es/nan-cli.mdx b/src/content/docs-es/nan-cli.mdx index f23d0e4b..f4afbe3b 100644 --- a/src/content/docs-es/nan-cli.mdx +++ b/src/content/docs-es/nan-cli.mdx @@ -164,6 +164,7 @@ Desde la v0.1.25 la CLI también pone a trabajar agentes en tareas dentro de tus nan run "añade tests al parser de fechas y haz que pasen" nan runs ls nan runs logs -f +nan run --byok-secret ANTHROPIC_KEY --model claude-sonnet-4-5 "arregla el test inestable" # v0.1.32+, tu propia clave de proveedor ``` El run se ejecuta en el workspace, no en tu máquina, así que sigue aunque cierres la terminal. Los comandos de runs usan tu sesión de `nan auth login`, o un token de plataforma desde `NAN_TOKEN`, `--token-file` o `nan auth login --api-token` para scripts, CI y servidores. Todos los flags, el orden de autenticación, los códigos de salida y la API están en [Runs de agentes](/es/docs/runs). diff --git a/src/content/docs-es/pr-gatekeeper.mdx b/src/content/docs-es/pr-gatekeeper.mdx index afc40864..3e41a571 100644 --- a/src/content/docs-es/pr-gatekeeper.mdx +++ b/src/content/docs-es/pr-gatekeeper.mdx @@ -91,7 +91,7 @@ Y cómo se comporta: - **Un workspace encendido con las herramientas de tu repositorio.** El agente ejecuta los tests en uno de tus [workspaces](/es/docs/workspaces), así que necesita lo mismo que tu CI: Go, Node, Python... Instálalas una vez en el workspace, como lo harías en tu portátil, y mantenlo encendido: un workspace parado no puede lanzar runs, y los eventos que llegan mientras tanto se pierden. El ejemplo usa un workspace llamado `work` con Go instalado. - **El agente instalado en ese workspace**: Pi, Pi + Gentle Shell o Hermes, desde su [panel de Software](/es/docs/workspaces#agentes-y-el-panel-de-software). La automatización solo puede usar un agente que el workspace tenga (consulta [Elige el agente](/es/docs/runs#elige-el-agente)). El ejemplo usa Pi. -- **Un plan con inferencia.** Cada review es un [run de agente](/es/docs/runs) y usa tu plan como cualquier otra petición. +- **Un plan con inferencia, o tu propia clave de proveedor.** Cada review es un [run de agente](/es/docs/runs) y usa tu plan como cualquier otra petición, o [tu propia clave de proveedor](/es/docs/automations#usa-tu-propia-clave-de-proveedor-byok), que factura tu proveedor. - **Acceso de administrador al repositorio en GitHub**, para añadir el webhook y crear los tokens. - **Una sesión en el navegador en [cloud.nan.builders](https://cloud.nan.builders/)**, con el enlace del correo: la necesitas para crear automatizaciones y guardar secretos. diff --git a/src/content/docs-es/runs.mdx b/src/content/docs-es/runs.mdx index 8a588358..ca3f1e4e 100644 --- a/src/content/docs-es/runs.mdx +++ b/src/content/docs-es/runs.mdx @@ -27,7 +27,7 @@ Para lanzar la misma tarea una y otra vez, desde un webhook, con una programaci ## Antes de empezar - **Un workspace encendido**, con al menos un agente instalado: Pi, Pi + Gentle Shell o Hermes. Se instalan desde el [panel de Software](/es/docs/workspaces#agentes-y-el-panel-de-software) del workspace. Consulta [Elige el agente](#elige-el-agente). -- **Un plan con inferencia.** Un run usa la clave de inferencia del propio workspace, y lo que gasta el agente cuenta para el uso de tu plan como cualquier otra petición. Si tu plan no incluye inferencia no puedes crear runs: el portal te enseña las opciones para mejorarlo. +- **Un plan con inferencia, o tu propia clave de proveedor.** Con la inferencia de NaN (lo predeterminado), un run usa la clave de inferencia del propio workspace, y lo que gasta el agente cuenta para el uso de tu plan como cualquier otra petición. Con [tu propia clave de proveedor](#usa-tu-propia-clave-de-proveedor-byok), la factura tu proveedor y no hace falta un plan con inferencia: basta con un workspace propio activo. ## Elige el agente @@ -68,6 +68,7 @@ Pulsa **New run** y rellena el formulario (el portal está en inglés): | **Folder** | La carpeta donde trabaja el agente. Por defecto es `/home/nan` y tiene que estar dentro de ella. | | **Separate branch** | Rama aparte: **Auto** (por defecto), **On** u **Off**. En un repositorio git, el agente trabaja en su propia rama y tu checkout se queda como está. Consulta [Dónde trabaja el agente](#dónde-trabaja-el-agente). | | **Time limit** | El tiempo máximo: **30 min** por defecto, igual que en la CLI y la API. Las opciones son 15 min, 30 min, 1 hour y 2 hours (1 hora y 2 horas). El run se para al llegar a él. | +| **Inference** | La inferencia: **NaN (included in your plan)**, la predeterminada, o **My own key**, una **Provider key** tuya y el **Model** que usar. Consulta [Usa tu propia clave de proveedor](#usa-tu-propia-clave-de-proveedor-byok). | Pulsa **Start run**, o `Ctrl+Enter` (`⌘+Enter` en un Mac) desde la caja de la tarea. @@ -138,7 +139,7 @@ Cuántos runs trabajan a la vez depende del tamaño del workspace: | Medium | 5 | | Large | 8 | -Además, los runs comparten tu concurrencia de inferencia, así que puedes tener como mucho **5 runs trabajando a la vez entre todos tus workspaces** (**8 con Premium**). +Además, los runs con la inferencia de NaN comparten tu concurrencia de inferencia, así que puedes tener como mucho **5 de ellos trabajando a la vez entre todos tus workspaces** (**8 con Premium**). Los runs con [tu propia clave de proveedor](#usa-tu-propia-clave-de-proveedor-byok) tienen su propio límite: hasta **8 a la vez** entre todos tus workspaces. Los runs que pasan de esos límites no se rechazan: esperan en una cola y arrancan por orden de llegada. En la cola caben hasta **20 runs por workspace** y **50 en total**, y puedes crear hasta **60 runs por hora**. @@ -146,12 +147,122 @@ Cada run trabaja en su propio proceso dentro del workspace, con su parte de CPU ## Inferencia y secretos -Los runs usan la clave de inferencia del workspace, la misma que usan sus agentes cuando trabajas con ellos a mano. Sus peticiones cuentan para el uso de tu plan como cualquier otra. +Los runs con la inferencia de NaN usan la clave de inferencia del workspace, la misma que usan sus agentes cuando trabajas con ellos a mano. Sus peticiones cuentan para el uso de tu plan como cualquier otra. Los runs con [tu propia clave de proveedor](#usa-tu-propia-clave-de-proveedor-byok) usan tu clave en su lugar, y nunca reciben la del workspace. Los secretos del archivo de entorno de tu workspace, como esa clave de inferencia o un token de bot de Telegram, aparecen como `***` en los logs y resultados de los runs, así que un log no los filtra. Los secretos que guardas en tus ajustes no se les dan a estos runs: solo llegan a los runs de las [automatizaciones](/es/docs/automations#secretos) a las que los asignas. +## Usa tu propia clave de proveedor (BYOK) + +Un run puede usar **tu propia clave** con un proveedor de IA en lugar de la inferencia de NaN: trae tu propia clave (BYOK, *bring your own key*). El agente trabaja exactamente igual, pero sus llamadas al modelo salen de tu workspace directamente hacia tu proveedor, con tu clave, y es tu proveedor quien las factura. + +Lo eliges en cada run y en cada [automatización](/es/docs/automations#usa-tu-propia-clave-de-proveedor-byok): **NaN** sigue siendo lo predeterminado. + +### Proveedores + +| Proveedor | `provider` | Id de modelo, por ejemplo | +|---|---|---| +| **Anthropic** | `anthropic` | `claude-sonnet-4-5` | +| **OpenAI** | `openai` | `gpt-5` | +| **Google Gemini** | `gemini` | `gemini-2.5-pro` | +| **OpenAI-compatible** | `openai_compatible` | El que sirva el endpoint, por ejemplo `meta-llama/Llama-3.3-70B-Instruct` | + +**OpenAI-compatible** es cualquier endpoint que hable la API de OpenAI en una URL base que tú indicas: un proveedor como Together, Groq u OpenRouter, o tu propio servidor, siempre que se pueda alcanzar desde internet por `https`. + +Escribe el id del modelo tal como lo llama tu proveedor. NaN no lo comprueba con tu proveedor: un id equivocado, o una clave equivocada, aparece como un run fallido con el error de tu proveedor en su log (con la clave enmascarada). + +### Guarda tu clave como provider key + +Abre tus ajustes en el portal, **Secrets**, y pulsa **Add secret** (guardar un secreto necesita que hayas iniciado sesión en el navegador). Rellena (el portal está en inglés): + +- **Kind**: **Provider key**. +- **Provider**: **Anthropic**, **OpenAI**, **Google Gemini** u **OpenAI-compatible**. No se puede cambiar después: para cambiar de proveedor, añade otra clave. +- **Base URL**, solo para **OpenAI-compatible**: la raíz del endpoint, que suele acabar en `/v1`, por ejemplo `https://api.together.xyz/v1`. Mira las reglas más abajo. +- **Value**: la clave, una sola línea sin espacios, de 8 a 4096 caracteres. +- **Name**: cómo la nombran los runs y las automatizaciones, por ejemplo `ANTHROPIC_KEY`. Mayúsculas, dígitos y `_`, empezando por una letra. +- **Description (optional)**. + +Como todo secreto, una provider key es de **solo escritura**: nadie puede volver a leerla, ni siquiera tú. La lista la muestra como **Provider key · Anthropic** (o su proveedor) con los 4 últimos caracteres y, en una clave OpenAI-compatible, su URL base, que puedes cambiar con **Base URL**. **Replace value** cambia la clave para todos los runs que arranquen a partir de ese momento. `nan secrets ls` la muestra con **KIND** `provider_key`, su **PROVIDER** y su **BASE URL**. + +**Reglas de la URL base.** NaN comprueba la URL base al guardarla y nunca se conecta a ella: solo lo hace el agente de tu workspace. + +- Solo `https`, hasta 512 caracteres. Un puerto vale (`https://llm.example.com:8443/v1`). +- Un nombre de host público (con al menos un punto) o una dirección IP pública. Se rechazan `localhost`, las direcciones privadas y de enlace local, y los nombres que acaban en `.local`, `.internal`, `.lan`, `.home.arpa` o `.svc`. +- No valen los hosts de NaN (`nan.builders`, `helmcode.com` y sus subdominios): para los modelos de NaN, elige **NaN** como inferencia. +- Sin `usuario:contraseña@`, sin parámetros (`?...`) y sin fragmento (`#...`). + + +La clave va en **Value**, donde se guarda cifrada y enmascarada. La URL base no es un secreto: el portal, `nan secrets ls` y la API la muestran tal cual. Una URL con un token dentro se rechaza si lleva `usuario:contraseña@` o parámetros, pero cualquier otra forma de esconder un token en ella quedaría a la vista de quien lea tus ajustes. + + +### Elígela para un run + +**En el portal**, en el formulario **New run**, pon **Inference** en **My own key**, elige la **Provider key** y escribe el **Model**. + +**Desde la terminal** (CLI **0.1.32** o posterior): + +```bash +nan run --byok-secret ANTHROPIC_KEY --model claude-sonnet-4-5 "arregla el test inestable del parser de fechas" +``` + +`--byok-secret` recibe el **nombre** del secreto, nunca la clave, y necesita `--model`. `nan runs show ` muestra la elección en su línea **inference**, y `nan runs ls` en su columna **INFERENCE**. + +**Desde la API**, añade un objeto `inference` y no mandes el `model` de primer nivel: + +```json +{ + "workspace": "develop", + "agent": "pi", + "prompt": "arregla el test inestable del parser de fechas", + "inference": { "source": "byok", "secret": "ANTHROPIC_KEY", "model": "claude-sonnet-4-5" } +} +``` + +El run que recibes dice lo mismo en su `inference` (`"source": "byok"`, el `provider`, el `secret` y el `model`), y su `usage.source` es `byok`. Sin `inference`, o con `{"source": "nan"}`, el run usa la inferencia de NaN como siempre. + +### Quién puede usarla + +- **Sin un plan con inferencia de NaN**, los runs y las automatizaciones con tu propia clave funcionan si tienes **un workspace propio activo**. Con un plan con inferencia, puedes elegir una u otra en cada run. +- **Un run que lanzas tú con tu clave necesita una sesión iniciada**: el portal, o la CLI después de `nan auth login`. Una API key (`sk-...`) o un token de plataforma (`nan_pat_...`) recibe `403 session_required`, y `nan run` rechaza `NAN_TOKEN` y `--token-file` para estos runs. El motivo: el prompt de un run puede hacer que el agente imprima la clave que tiene, y esas credenciales no pueden leer tus secretos, así que tampoco deben poder conseguir que un agente revele uno. Sí pueden lanzar una [automatización que usa tu clave](/es/docs/automations#usa-tu-propia-clave-de-proveedor-byok): sus instrucciones son las que tú guardaste. +- Si **My own key** aparece desactivado con "isn't available for your account yet", todavía no está abierto para tu cuenta. + +### Facturación y uso + +Tu proveedor factura todos los tokens de estos runs, en tu cuenta con él. NaN no los mide: nada cuenta para tu plan de NaN ni para su uso. Los tokens que muestra un run son los que ha informado el agente (Pi los informa, Hermes no); la referencia es la factura de tu proveedor. + +### Límites + +- Hasta **8 runs con tus propias claves trabajando a la vez** entre todos tus workspaces. No ocupan plazas de tu límite de inferencia de NaN (5, u 8 con Premium). +- El tamaño de cada workspace sigue limitando **todos** sus runs juntos, sean de NaN o no: consulta [Cuántos runs a la vez](#cuántos-runs-a-la-vez). Igual que la cola (20 por workspace, 50 en total) y los 60 runs por hora. + +### Seguridad + +- Tu clave se guarda como cualquier [secreto](/es/docs/automations#secretos): cifrada, de solo escritura y enmascarada en logs, resultados y propuestas. +- Solo llega al agente del run, dentro de tu propio workspace, como variable de entorno: nunca en la línea de comandos, en un archivo ni en los logs de NaN. El run no recibe la clave de inferencia de NaN de tu workspace. +- **NaN nunca llama a tu URL base ni a tu proveedor.** Lo hace el agente de tu workspace, por la red de tu workspace. +- El run lee los ajustes de tu agente para conservar tus skills y preferencias, pero usa su propia copia de la configuración, con solo el proveedor de tu clave: tus archivos de configuración no cambian. +- **El agente tiene la clave.** Un prompt, o unos datos que lea, que lo convenzan de imprimirla o enviarla pueden filtrarla. Usa una clave limitada a lo que necesita el run, con un límite de gasto en tu proveedor, y cámbiala si se filtra. +- Una [automatización](/es/docs/automations#usa-tu-propia-clave-de-proveedor-byok) que lanza un webhook le da al agente un texto que ha escrito otra persona, mientras tiene tu clave. Tenlo en cuenta antes de conectar un webhook público a una automatización con tu clave. + +### Si algo sale mal + +| Lo que ves | Por qué, y qué hacer | +|---|---| +| `404 not_found`, `param: inference.secret` | No tienes ningún secreto con ese nombre. Comprueba los nombres con `nan secrets ls`. | +| `400 invalid_request`, `param: inference.secret` | Ese secreto es un secreto genérico, no una **Provider key**. Añade tu clave como provider key. | +| `400 invalid_request`, `param: inference.model` | Falta el modelo, o no es un id de modelo (letras, dígitos y `. _ : / -`, hasta 128, sin empezar por `-`). | +| `400 invalid_request`, `param: inference` | Tu propia clave todavía no está disponible para tu cuenta, o el objeto `inference` está mal formado. | +| `403 session_required` | Un run con tu clave lanzado con una API key o un token. Lánzalo con la sesión iniciada, o lanza una automatización. | +| `403 tier_restricted` | Ni plan con inferencia de NaN ni un workspace propio activo. | +| El run falla con `secret_missing` | La provider key se borró después de que el run entrara en la cola. | +| El run falla con `config_error` | No se ha podido preparar el agente para tu clave, por ejemplo Hermes con una clave de Anthropic (abajo). | +| El run falla con el error de tu proveedor en el log | Una clave equivocada, un id de modelo equivocado, sin saldo en tu proveedor, o una URL base que no responde. | + +### Limitación conocida: Hermes con una clave de Anthropic + +Hermes todavía no puede usar una clave de **Anthropic** en los workspaces: sus runs fallan con `config_error` hasta que la imagen del workspace incluya soporte para Anthropic. Usa **Pi** o **Pi + Gentle Shell** con una clave de Anthropic, o apunta Hermes a un endpoint **OpenAI-compatible**. + ## Desde la terminal: `nan run` La [CLI de NaN](/es/docs/nan-cli) lanza y sigue runs desde tu propia terminal. Necesitas la versión **0.1.25** o posterior, y la **0.1.29** o posterior para `--agent pi_gentle`: `nan --version` imprime `nan `. @@ -203,7 +314,8 @@ nan run --detach "actualiza las dependencias" |---|---|---| | `--ws NAME` | tu único workspace | El workspace donde se ejecuta. Si tienes más de uno y no lo pones, la CLI te los lista y sale con código 64. | | `--agent pi\|pi_gentle\|hermes` | `pi` | El agente: `pi` (Pi), `pi_gentle` (Pi + Gentle Shell) o `hermes` (Hermes). Tiene que estar instalado en el workspace. Consulta [Elige el agente](#elige-el-agente). | -| `--model M` | el del agente | El modelo que usa el agente. | +| `--model M` | el del agente | El modelo que usa el agente. Obligatorio con `--byok-secret`. | +| `--byok-secret NAME` | inferencia de NaN | Ejecuta el run con tu propia clave de proveedor: el nombre de un secreto de tipo provider key. Necesita `--model` y tu sesión de `nan auth login`. Consulta [Usa tu propia clave de proveedor](#usa-tu-propia-clave-de-proveedor-byok). | | `--cwd PATH` | `/home/nan` | La carpeta en el workspace. | | `--worktree` / `--no-worktree` | auto | Fuerza la rama aparte a on u off. Consulta [Dónde trabaja el agente](#dónde-trabaja-el-agente). | | `--timeout D` | `30m` | El tiempo máximo, de `1m` a `2h`. | @@ -253,7 +365,7 @@ nan runs cancel | 4 | el workspace no está listo: agente no instalado, sin clave de inferencia o configuración incorrecta | | 10 | el run espera tu aprobación (solo runs de [automatizaciones](/es/docs/automations#aprueba-o-rechaza-una-propuesta)) | | 64 | error de uso, también tener más de un workspace y no pasar `--ws` | -| 65 | sin sesión, sesión caducada o sin permiso | +| 65 | sin sesión, sesión caducada o sin permiso (también un token o una API key para un run con tu propia clave) | | 69 | nan.builders no disponible, o se ha perdido el stream (el run sigue, y se imprime el id) | | 75 | el run se ha aceptado y sigue en marcha (`--detach`, o Ctrl-C una vez) | @@ -282,6 +394,8 @@ nan auth login --api-token < token.txt # o pásalo por stdin No pongas nunca un token en la propia línea de comandos: acabaría en el historial de tu shell y en `ps`. +Un run con [tu propia clave de proveedor](#usa-tu-propia-clave-de-proveedor-byok) (`--byok-secret`) usa solo tu sesión de `nan auth login`: la CLI rechaza `NAN_TOKEN` y `--token-file` para él, y se salta un token o una API key guardados. + En la CLI, un token de plataforma solo sirve para `nan run`, `nan runs`, `nan automations` y `nan approvals` (consulta [API keys y tokens](#api-keys-y-tokens)). `nan me`, `nan metrics usage` y el panel siguen necesitando una sesión de `nan auth login`. ### Usa la CLI en CI o en un servidor @@ -332,7 +446,7 @@ Un **token** es lo que conviene darle a un script que solo gestiona runs. No sir - Puedes revocar cualquiera en cualquier momento. - Lleva **scopes**, lo que puede hacer: `runs` y `workspaces:read` por defecto. Para listar o lanzar automatizaciones, añade `automations:read` o `automations:run`. Consulta [Tokens para automatizaciones](/es/docs/automations#tokens-para-automatizaciones). -Para crear runs sigue haciendo falta un plan con inferencia, uses la credencial que uses. La [referencia de la API](/es/docs/api#description/platform-tokens) indica qué credencial acepta cada endpoint. +Para crear runs con la inferencia de NaN sigue haciendo falta un plan con inferencia, uses la credencial que uses. Los runs con [tu propia clave de proveedor](#usa-tu-propia-clave-de-proveedor-byok) necesitan una sesión iniciada en lugar de una API key o un token. La [referencia de la API](/es/docs/api#description/platform-tokens) indica qué credencial acepta cada endpoint. La clave que hay dentro de un workspace, la que usan sus agentes para la inferencia, no puede gestionar runs: la API le responde con un `403`. Un agente que trabaja en tu workspace no puede lanzar runs nuevos por su cuenta, salvo que le des una de tus propias API keys o tokens. @@ -399,7 +513,7 @@ Nada: el run sigue en el workspace. Abre su página en la pestaña **Runs**, o e
-No. Los runs usan los modelos a través de la clave de inferencia del workspace, así que para crearlos necesitas un plan con inferencia. Sí puedes crear un token y listar tus workspaces y tus runs. +Sí, con [tu propia clave de proveedor](#usa-tu-propia-clave-de-proveedor-byok) y un workspace propio activo: los tokens los factura tu proveedor. Los runs con la inferencia de NaN necesitan un plan con inferencia.
diff --git a/src/content/docs-es/webhooks.mdx b/src/content/docs-es/webhooks.mdx index e7b0fecf..a71cbbb8 100644 --- a/src/content/docs-es/webhooks.mdx +++ b/src/content/docs-es/webhooks.mdx @@ -237,7 +237,7 @@ La pestaña **Deliveries** lista los últimos 50 webhooks que ha recibido la aut | **Ignored by a filter** | El filtro de eventos o de autores no ha coincidido o, con **Fresh clone per run**, la pull request venía de un fork o de un autor sin permiso de escritura. | | **Stopped: caused by one of its own runs** | Se ha parado un [bucle](#bucles). | | **Skipped: a run for the same subject is active** | Consulta [Cuando ya hay un run activo](/es/docs/automations#cuando-ya-hay-un-run-activo). | -| **Not accepted: your membership does not include runs** | No se ha podido crear el run: tu plan no tiene inferencia, o el workspace está parado o borrado, su agente no está instalado o no tiene clave de inferencia. El emisor no lo reintenta, así que el evento se pierde: mantén el workspace encendido. | +| **Not accepted: your membership does not include runs** | No se ha podido crear el run: tu plan no tiene inferencia (o, en una automatización con [tu propia clave de proveedor](/es/docs/automations#usa-tu-propia-clave-de-proveedor-byok), no tienes un workspace activo), o el workspace está parado o borrado, su agente no está instalado o no tiene clave de inferencia. El emisor no lo reintenta, así que el evento se pierde: mantén el workspace encendido. | | **Too large** | El evento no cabía en lo que puede recibir el agente. | No se guarda ningún payload ni ninguna cabecera. Las entregas con una firma incorrecta nunca se guardan, y las limitadas (límite de peticiones, cola llena, no disponible temporalmente) se responden pidiendo un reintento y no se listan. @@ -293,7 +293,7 @@ Cada programación muestra su próxima ejecución y cómo fue la última: | **Skipped: too many runs waiting** | Tu cola de runs está llena. | | **Skipped: automation disabled** | La automatización no está activada. | | **Skipped: workspace deleted** | Su workspace ya no existe. | -| **Not accepted: your membership does not include runs** | No se ha podido crear el run: tu plan no tiene inferencia, o el workspace está parado o no está listo para el agente. | +| **Not accepted: your membership does not include runs** | No se ha podido crear el run: tu plan no tiene inferencia (o, en una automatización con [tu propia clave de proveedor](/es/docs/automations#usa-tu-propia-clave-de-proveedor-byok), no tienes un workspace activo), o el workspace está parado o no está listo para el agente. | | **Missed (more than 15 minutes late)** | No pudo arrancar a tiempo y se ha saltado. | | **Error** | Algo ha fallado en nuestro lado. | diff --git a/src/content/docs-es/workspaces.mdx b/src/content/docs-es/workspaces.mdx index f4178225..de05edf2 100644 --- a/src/content/docs-es/workspaces.mdx +++ b/src/content/docs-es/workspaces.mdx @@ -20,7 +20,7 @@ Todo se gestiona desde [cloud.nan.builders/workspaces](https://cloud.nan.builder Cualquier **miembro de pago** de NaN puede comprar slots de workspace. El plan Premium incluye **un workspace gratis** (Micro: 1 vCPU, 2 GiB de RAM, 10 GiB de disco). -Un slot paga **solo la máquina**. No incluye inferencia: los agentes de tu workspace llaman a los modelos de NaN solo si tu membresía incluye inferencia. Sin ella puedes usar el workspace igualmente como máquina de desarrollo y apuntar los agentes a cualquier proveedor que ya tengas. +Un slot paga **solo la máquina**. No incluye inferencia: los agentes de tu workspace llaman a los modelos de NaN solo si tu membresía incluye inferencia. Sin ella puedes usar el workspace igualmente como máquina de desarrollo, apuntar los agentes a cualquier proveedor que ya tengas y lanzar [runs](/es/docs/runs#usa-tu-propia-clave-de-proveedor-byok) y [automatizaciones](/es/docs/automations#usa-tu-propia-clave-de-proveedor-byok) con tu propia clave de proveedor. ## Tamaños y precios @@ -272,7 +272,7 @@ No. Cada workspace es una máquina dedicada y aislada, con su CPU, su memoria y
-No. Un slot paga la máquina. Los agentes usan los modelos de NaN solo si tu membresía incluye inferencia. Sin ella puedes instalar los agentes igualmente y conectarlos a otro proveedor. +No. Un slot paga la máquina. Los agentes usan los modelos de NaN solo si tu membresía incluye inferencia. Sin ella puedes instalar los agentes igualmente y conectarlos a otro proveedor, y usar [runs](/es/docs/runs#usa-tu-propia-clave-de-proveedor-byok) y [automatizaciones](/es/docs/automations#usa-tu-propia-clave-de-proveedor-byok) con tu propia clave de proveedor.
diff --git a/src/content/docs/automations.mdx b/src/content/docs/automations.mdx index 3f9671ed..c6e1029f 100644 --- a/src/content/docs/automations.mdx +++ b/src/content/docs/automations.mdx @@ -24,7 +24,7 @@ The agent of an automation does not push or post on its own. It **proposes actio ## Before you start - **A workspace that is running**, with the agent you want installed (Pi, Pi + Gentle Shell or Hermes), as for any [run](/docs/runs#pick-the-agent). -- **A plan with inference.** Automation runs use your workspace's inference key and count against your plan's usage like any other run. +- **A plan with inference, or your own provider key.** On NaN inference, automation runs use your workspace's inference key and count against your plan's usage like any other run. On [your own provider key](#use-your-own-provider-key-byok), your provider bills them and an active workspace of your own is enough. - **A browser sign-in.** Creating or changing automations, writing secrets and approving actions need you signed in at [cloud.nan.builders](https://cloud.nan.builders/) in your browser, with the email link. A CLI session, an API key or a token can list automations and run them, but never change them. See [Why some actions need the browser](#why-some-actions-need-the-browser). - **For the GitHub templates, a GitHub token saved as a secret.** The automation cannot be saved without it, so create it first: see [A GitHub token for actions](#a-github-token-for-actions). @@ -62,6 +62,7 @@ The three GitHub templates act on a repository, so they need a **GitHub reposito | **Folder** | Where the agent works, inside `/home/nan`. Not used with **Fresh clone per run**. | | **Separate branch** | **On** (the default), **Auto** or **Off (work in the folder directly)**, as in a [run](/docs/runs#where-the-agent-works). Not used with **Fresh clone per run**. | | **Time limit (minutes)** | From 1 to 120. 30 by default. | +| **Inference** | **NaN (included in your plan)**, the default, or **My own key**: a **Provider key** of yours and the **Model**. See [Use your own provider key](#use-your-own-provider-key-byok). | | **GitHub repository (optional)** | `owner/name`, for example `acme/api`. The repository the actions act on. | | **Checkout** | **Workspace folder** (the default): the agent works in **Folder**, in your workspace. **Fresh clone per run**: NaN clones the **GitHub repository** for each run, at the commit the event is about, and deletes the clone when the run ends. See [Fresh clone per run](#fresh-clone-per-run). | | **Run pull requests from forks and outside contributors** | Shown with **Fresh clone per run**. Off by default. See [Pull requests from forks and outside contributors](#pull-requests-from-forks-and-outside-contributors). | @@ -89,7 +90,7 @@ Secrets are the tokens and keys your automations use: a GitHub token, an API key ### Add a secret -Open your settings on the portal, **Secrets**, below **Tokens**, and press **Add secret**: +Open your settings on the portal, **Secrets**, below **Tokens**, and press **Add secret**. **Kind** is **Secret** for the tokens and keys your automations receive; **Provider key** is for your own key with an AI provider, used as the inference of runs and automations: see [Use your own provider key](#use-your-own-provider-key-byok). - **Name**: the variable name your automations see, in capitals, digits and `_`, starting with a letter. For example `GITHUB_TOKEN`. Up to 64 characters. - **Value**: saved exactly as you paste it, up to 16 KiB. Line breaks inside the value are fine (a PEM key, for example), but not at its start or end. @@ -99,7 +100,7 @@ Secrets are **write-only**: once saved, nobody can read a value back, not even y - **Replace value**: the new value is used by every run that starts from then on, including runs already queued. The old one cannot be recovered. - Change its **Description**. -- **Delete** it. Deleting is refused while an automation uses it: remove it from those automations first. +- **Delete** it. Deleting is refused while an automation uses it, as one of its secrets or as its provider key: remove it from those automations first. You can keep up to 100 secrets. Prefer short-lived tokens limited to what the automation needs. @@ -139,6 +140,24 @@ If a push may change files under `.github/workflows/` (the **CI failure** templa If the agent also needs to read the repository (a private one, or to fetch CI logs), create a second token with read permissions only (**Contents**, **Pull requests**, **Issues**, and **Actions** for CI logs) and assign it as `GITHUB_TOKEN`, **Agent**. +## Use your own provider key (BYOK) + +An automation can run on **your own key** with an AI provider instead of NaN inference, as an [ad-hoc run can](/docs/runs#use-your-own-provider-key-byok). Save the key first as a **Provider key** in **Secrets** (Anthropic, OpenAI, Google Gemini or any OpenAI-compatible endpoint): [Save your key as a provider key](/docs/runs#save-your-key-as-a-provider-key) has the form and the base URL rules. + +In the automation's settings, set **Inference** to **My own key**, pick the **Provider key** and type the **Model**, exactly as your provider names it. Every run of the automation then uses that key and model, whatever starts it: a webhook, a schedule, **Run now**, the CLI or the API. Setting **Inference** back to **NaN (included in your plan)** returns it to NaN inference, with the NaN model you pick or, if you pick none, the agent's default. + +- **Who can use it.** Without a NaN inference plan, an automation on your own key works with an active workspace of your own. Creating it and changing it need the browser, like any automation. +- **Any credential can start it.** Unlike an [ad-hoc run on your key](/docs/runs#who-can-use-it), an automation on your key can be started with an API key or a platform token with `automations:run`, for example from CI: its instructions are the ones you saved, and the caller only adds input. +- **Billing.** Your provider bills its runs; NaN does not meter them. The key counts towards the 48 KiB of secrets a run can receive. +- **Limits.** Up to 8 runs on your own keys at once across all your workspaces, plus the limits of every run. See [Limits](/docs/runs#limits). +- **The key is in use.** You cannot delete a provider key while an automation uses it: change those automations first. `nan secrets ls` and the secrets list show which automations use each key. `nan automations show` shows the choice on its **inference** line. + + +What a webhook delivery or an `input` carries is written by someone else, and the agent reads it while your key is in its environment. A crafted pull request or issue could try to make it print or send the key. Give such automations a key limited to what they need, with a spending limit at your provider, and keep untrusted senders (forks, outside contributors) out with the trigger's filters. `nan automations show` reminds you when a webhook starts an automation on your key. + + +Hermes cannot use an **Anthropic** key in workspaces yet: its runs fail with `config_error`. Use **Pi** or **Pi + Gentle Shell** with it, or an **OpenAI-compatible** endpoint. + ## Actions and approval ### How an automation acts @@ -375,7 +394,7 @@ For CI, give the token `automations:run` and `runs` (to follow the run and get i | Instructions | 16 KiB | | Input of a run | A JSON object of up to 48 KiB, nested at most 8 levels | -Automation runs share the limits of every run: how many work at once in each workspace, at most 5 at once across all your workspaces (8 on Premium), and the queue. See [How many runs at once](/docs/runs#how-many-runs-at-once). Automations need a plan with inference: without one, the portal shows you the upgrade options and webhooks are answered without starting a run. +Automation runs share the limits of every run: how many work at once in each workspace, at most 5 at once across all your workspaces (8 on Premium), and the queue. See [How many runs at once](/docs/runs#how-many-runs-at-once). Automations on NaN inference need a plan with inference: without one, the portal offers **My own key** (see [Use your own provider key](#use-your-own-provider-key-byok)), and webhooks of an automation on NaN inference are answered without starting a run. Runs on your own keys have their own limit: up to 8 at once across all your workspaces. ## Change or delete an automation diff --git a/src/content/docs/nan-cli.mdx b/src/content/docs/nan-cli.mdx index e53b54fb..1b100857 100644 --- a/src/content/docs/nan-cli.mdx +++ b/src/content/docs/nan-cli.mdx @@ -164,6 +164,7 @@ From v0.1.25 the CLI also starts agents on tasks inside your [workspaces](/docs/ nan run "add tests for the date parser and make them pass" nan runs ls nan runs logs -f +nan run --byok-secret ANTHROPIC_KEY --model claude-sonnet-4-5 "fix the flaky test" # v0.1.32+, your own provider key ``` The run happens in the workspace, not on your machine, so it keeps going if you close the terminal. The runs commands use your `nan auth login` session, or a platform token from `NAN_TOKEN`, `--token-file` or `nan auth login --api-token` for scripts, CI and servers. Every flag, the authentication order, the exit codes and the API are in [Agent runs](/docs/runs). diff --git a/src/content/docs/pr-gatekeeper.mdx b/src/content/docs/pr-gatekeeper.mdx index 92ff9e14..bb00454b 100644 --- a/src/content/docs/pr-gatekeeper.mdx +++ b/src/content/docs/pr-gatekeeper.mdx @@ -91,7 +91,7 @@ And how it behaves: - **A running workspace with your repository's toolchain.** The agent runs the tests in one of your [workspaces](/docs/workspaces), so it needs what your CI needs: Go, Node, Python... Install them once in the workspace, as you would on your laptop, and keep it running: a stopped workspace cannot start runs, and the events that arrive meanwhile are lost. The example uses a workspace called `work` with Go installed. - **The agent installed in that workspace**: Pi, Pi + Gentle Shell or Hermes, from its [Software panel](/docs/workspaces#agents-and-the-software-panel). The automation can only use an agent the workspace has (see [Pick the agent](/docs/runs#pick-the-agent)). The example uses Pi. -- **A plan with inference.** Each review is an [agent run](/docs/runs) and uses your plan like any other request. +- **A plan with inference, or your own provider key.** Each review is an [agent run](/docs/runs) and uses your plan like any other request, or [your own provider key](/docs/automations#use-your-own-provider-key-byok), billed by your provider. - **Admin access to the repository on GitHub**, to add the webhook and create the tokens. - **A browser sign-in at [cloud.nan.builders](https://cloud.nan.builders/)**, with the email link: creating automations and saving secrets need it. diff --git a/src/content/docs/runs.mdx b/src/content/docs/runs.mdx index 68ee6020..ff3250c0 100644 --- a/src/content/docs/runs.mdx +++ b/src/content/docs/runs.mdx @@ -27,7 +27,7 @@ To run the same task again and again, from a webhook, on a schedule or with acti ## Before you start - **A workspace that is running**, with at least one agent installed: Pi, Pi + Gentle Shell or Hermes. You install them from the [Software panel](/docs/workspaces#agents-and-the-software-panel) of the workspace. See [Pick the agent](#pick-the-agent). -- **A plan with inference.** A run uses your workspace's own inference key, and what the agent spends counts against your plan's usage like any other request. Without inference in your plan you cannot create runs: the portal shows you the upgrade options instead. +- **A plan with inference, or your own provider key.** With NaN inference (the default), a run uses your workspace's own inference key, and what the agent spends counts against your plan's usage like any other request. With [your own provider key](#use-your-own-provider-key-byok), your provider bills it and a plan with inference is not needed: an active workspace of your own is enough. ## Pick the agent @@ -68,6 +68,7 @@ Press **New run** and fill in the form: | **Folder** | Where the agent works. It defaults to `/home/nan` and must be inside it. | | **Separate branch** | **Auto** (the default), **On** or **Off**. In a git repository, the agent works on its own branch and your checkout stays as it is. See [Where the agent works](#where-the-agent-works). | | **Time limit** | **30 min** by default, the same as the CLI and the API. The options are 15 min, 30 min, 1 hour and 2 hours. The run is stopped when it reaches it. | +| **Inference** | **NaN (included in your plan)**, the default, or **My own key**: a **Provider key** of yours and the **Model** to use. See [Use your own provider key](#use-your-own-provider-key-byok). | Press **Start run**, or `Ctrl+Enter` (`⌘+Enter` on a Mac) from the task box. @@ -138,7 +139,7 @@ How many runs work at the same time depends on the size of the workspace: | Medium | 5 | | Large | 8 | -On top of that, runs share your inference concurrency, so you can have at most **5 runs working at once across all your workspaces** (**8 on Premium**). +On top of that, runs on NaN inference share your inference concurrency, so you can have at most **5 of them working at once across all your workspaces** (**8 on Premium**). Runs on [your own provider key](#use-your-own-provider-key-byok) have their own limit: up to **8 at once** across all your workspaces. Runs beyond those limits are not refused: they wait in a queue and start in order, first in, first out. The queue holds up to **20 runs per workspace** and **50 in total**, and you can create up to **60 runs per hour**. @@ -146,12 +147,122 @@ Each run works in its own process inside the workspace, with its own share of CP ## Inference and secrets -Runs use the inference key of the workspace, the same one its agents use when you work with them by hand. Their requests count against your plan's usage like any other. +Runs on NaN inference use the inference key of the workspace, the same one its agents use when you work with them by hand. Their requests count against your plan's usage like any other. Runs on [your own provider key](#use-your-own-provider-key-byok) use your key instead, and never get the workspace's. Secrets in your workspace's environment file, such as that inference key or a Telegram bot token, are shown as `***` in run logs and results, so a log does not leak them. The secrets you keep in your settings are not given to these runs: they go only to the runs of the [automations](/docs/automations#secrets) you assign them to. +## Use your own provider key (BYOK) + +A run can use **your own key** with an AI provider instead of NaN inference: bring your own key (BYOK). The agent works exactly the same way, but its model calls go from your workspace straight to your provider, with your key, and your provider bills them. + +You choose it per run, and per [automation](/docs/automations#use-your-own-provider-key-byok): **NaN** stays the default. + +### Providers + +| Provider | `provider` | Model id, for example | +|---|---|---| +| **Anthropic** | `anthropic` | `claude-sonnet-4-5` | +| **OpenAI** | `openai` | `gpt-5` | +| **Google Gemini** | `gemini` | `gemini-2.5-pro` | +| **OpenAI-compatible** | `openai_compatible` | Whatever the endpoint serves, for example `meta-llama/Llama-3.3-70B-Instruct` | + +**OpenAI-compatible** is any endpoint that speaks the OpenAI API at a base URL you give: a hosted provider such as Together, Groq or OpenRouter, or your own server, as long as it is reachable on the internet over `https`. + +Type the model id exactly as your provider names it. NaN does not check it against your provider: a wrong id, or a wrong key, shows up as a failed run with your provider's error in its log (with the key masked). + +### Save your key as a provider key + +Open your settings on the portal, **Secrets**, and press **Add secret** (saving a secret needs you signed in through the browser). Fill in: + +- **Kind**: **Provider key**. +- **Provider**: **Anthropic**, **OpenAI**, **Google Gemini** or **OpenAI-compatible**. It cannot be changed later: to switch providers, add another key. +- **Base URL**, only for **OpenAI-compatible**: the root of the endpoint, usually ending in `/v1`, for example `https://api.together.xyz/v1`. See the rules below. +- **Value**: the key, one line without spaces, 8 to 4096 characters. +- **Name**: how runs and automations refer to it, for example `ANTHROPIC_KEY`. Capitals, digits and `_`, starting with a letter. +- **Description (optional)**. + +Like every secret, a provider key is **write-only**: nobody can read it back, not even you. The list shows it as **Provider key · Anthropic** (or its provider) with the last 4 characters, and, for an OpenAI-compatible key, its base URL, which you can change with **Base URL**. **Replace value** changes the key for every run that starts from then on. `nan secrets ls` shows it with **KIND** `provider_key`, its **PROVIDER** and its **BASE URL**. + +**Base URL rules.** NaN checks the base URL when you save it, and never connects to it: only the agent in your workspace does. + +- `https` only, up to 512 characters. A port is fine (`https://llm.example.com:8443/v1`). +- A public host name (with at least one dot) or a public IP address. `localhost`, private and link-local addresses, and names ending in `.local`, `.internal`, `.lan`, `.home.arpa` or `.svc` are refused. +- Not NaN's own hosts (`nan.builders`, `helmcode.com` and their subdomains): for NaN models, choose **NaN** as the inference. +- No `user:password@`, no query string (`?...`) and no fragment (`#...`). + + +The key goes in **Value**, where it is stored encrypted and masked. The base URL is not a secret: the portal, `nan secrets ls` and the API show it as it is. A URL with a token inside is refused when it has `user:password@` or a query string, but any other way of hiding a token in it would be shown to whoever reads your settings. + + +### Choose it for a run + +**In the portal**, in the **New run** form, set **Inference** to **My own key**, pick the **Provider key** and type the **Model**. + +**From the terminal** (CLI **0.1.32** or newer): + +```bash +nan run --byok-secret ANTHROPIC_KEY --model claude-sonnet-4-5 "fix the flaky test in the date parser" +``` + +`--byok-secret` takes the **name** of the secret, never the key itself, and needs `--model`. `nan runs show ` shows the choice on its **inference** line, and `nan runs ls` in its **INFERENCE** column. + +**From the API**, add an `inference` object and leave the top-level `model` out: + +```json +{ + "workspace": "develop", + "agent": "pi", + "prompt": "fix the flaky test in the date parser", + "inference": { "source": "byok", "secret": "ANTHROPIC_KEY", "model": "claude-sonnet-4-5" } +} +``` + +The run you get back says the same in its `inference` (`"source": "byok"`, the `provider`, the `secret` and the `model`), and its `usage.source` is `byok`. Without `inference`, or with `{"source": "nan"}`, the run uses NaN inference as always. + +### Who can use it + +- **Without a NaN inference plan**, runs and automations on your own key work if you have **a workspace of your own that is active**. With a NaN inference plan, you can choose either for each run. +- **A run you start yourself on your key needs a signed-in session**: the portal, or the CLI after `nan auth login`. An API key (`sk-...`) or a platform token (`nan_pat_...`) gets `403 session_required`, and `nan run` refuses `NAN_TOKEN` and `--token-file` for it. The reason: the prompt of a run can make the agent print the key it holds, and those credentials cannot read your secrets, so they must not be able to make an agent reveal one. They can still start an [automation that uses your key](/docs/automations#use-your-own-provider-key-byok): its instructions are the ones you saved. +- If **My own key** is disabled with "isn't available for your account yet", it is not open to your account yet. + +### Billing and usage + +Your provider bills every token of these runs, under your account with them. NaN does not meter them: nothing counts against your NaN plan or its usage. The tokens a run shows are the ones the agent reported (Pi reports them, Hermes does not); your provider's bill is the reference. + +### Limits + +- Up to **8 runs on your own keys working at once** across all your workspaces. They do not take places from your NaN inference limit (5, or 8 on Premium). +- The size of each workspace still limits **all** its runs together, NaN or not: see [How many runs at once](#how-many-runs-at-once). So do the queue (20 per workspace, 50 in total) and the 60 runs per hour. + +### Security + +- Your key is stored like any [secret](/docs/automations#secrets): encrypted, write-only and masked in logs, results and proposals. +- It reaches only the agent of the run, inside your own workspace, as an environment variable: never on the command line, in a file or in NaN's logs. The run does not get your workspace's NaN inference key. +- **NaN never calls your base URL or your provider.** The agent in your workspace does, over your workspace's network. +- The run reads your agent's settings to keep your skills and preferences, but it uses its own copy of the configuration, with only your key's provider in it: your own configuration files are not changed. +- **The agent holds the key.** A prompt, or data it reads, that convinces it to print or send the key can leak it. Use a key limited to what the run needs, with a spending limit at your provider, and replace it if it leaks. +- An [automation](/docs/automations#use-your-own-provider-key-byok) started by a webhook gives the agent text that someone else wrote, while it holds your key. Keep that in mind before you connect a public webhook to an automation on your key. + +### If something goes wrong + +| What you see | Why, and what to do | +|---|---| +| `404 not_found`, `param: inference.secret` | You have no secret with that name. Check the names with `nan secrets ls`. | +| `400 invalid_request`, `param: inference.secret` | That secret is a generic secret, not a **Provider key**. Add your key as a provider key. | +| `400 invalid_request`, `param: inference.model` | The model is missing, or it is not a model id (letters, digits and `. _ : / -`, up to 128, not starting with `-`). | +| `400 invalid_request`, `param: inference` | Your own key is not available for your account yet, or the `inference` object is malformed. | +| `403 session_required` | A run on your key with an API key or a token. Start it signed in, or run an automation instead. | +| `403 tier_restricted` | No NaN inference plan and no active workspace of your own. | +| The run fails with `secret_missing` | The provider key was deleted after the run was queued. | +| The run fails with `config_error` | The agent could not be set up for your key, for example Hermes with an Anthropic key (below). | +| The run fails with your provider's error in the log | A wrong key, a wrong model id, no credit at your provider, or a base URL that does not answer. | + +### Known limitation: Hermes with an Anthropic key + +Hermes cannot use an **Anthropic** key in workspaces yet: its runs fail with `config_error` until the workspace image includes Anthropic support. Use **Pi** or **Pi + Gentle Shell** with an Anthropic key, or point Hermes at an **OpenAI-compatible** endpoint. + ## From the terminal: `nan run` The [NaN CLI](/docs/nan-cli) starts and follows runs from your own terminal. You need version **0.1.25** or newer, and **0.1.29** or newer for `--agent pi_gentle`: `nan --version` prints `nan `. @@ -203,7 +314,8 @@ nan run --detach "upgrade the dependencies" |---|---|---| | `--ws NAME` | your only workspace | The workspace to run in. If you have more than one and leave it out, the CLI lists them and exits with code 64. | | `--agent pi\|pi_gentle\|hermes` | `pi` | The agent: `pi` (Pi), `pi_gentle` (Pi + Gentle Shell) or `hermes` (Hermes). It must be installed in the workspace. See [Pick the agent](#pick-the-agent). | -| `--model M` | the agent's own | The model the agent uses. | +| `--model M` | the agent's own | The model the agent uses. Required with `--byok-secret`. | +| `--byok-secret NAME` | NaN inference | Run on your own provider key: the name of a provider key secret. Needs `--model` and your `nan auth login` session. See [Use your own provider key](#use-your-own-provider-key-byok). | | `--cwd PATH` | `/home/nan` | The folder in the workspace. | | `--worktree` / `--no-worktree` | auto | Force a separate branch on or off. See [Where the agent works](#where-the-agent-works). | | `--timeout D` | `30m` | The time limit, from `1m` to `2h`. | @@ -253,7 +365,7 @@ nan runs cancel | 4 | the workspace is not ready for it: agent not installed, no inference key or a bad configuration | | 10 | the run is waiting for your approval (only [automation](/docs/automations#approve-or-reject-a-proposal) runs) | | 64 | usage error, including more than one workspace and no `--ws` | -| 65 | not signed in, session expired or not allowed | +| 65 | not signed in, session expired or not allowed (also a token or an API key for a run on your own key) | | 69 | nan.builders unavailable, or the stream was lost (the run goes on, and the id is printed) | | 75 | the run was accepted and is still going (`--detach`, or Ctrl-C once) | @@ -282,6 +394,8 @@ nan auth login --api-token < token.txt # or pipe it in Never put a token on the command line itself: it would end up in your shell history and in `ps`. +A run on [your own provider key](#use-your-own-provider-key-byok) (`--byok-secret`) uses only your session from `nan auth login`: the CLI refuses `NAN_TOKEN` and `--token-file` for it, and skips a saved token or API key. + In the CLI, a platform token only works for `nan run`, `nan runs`, `nan automations` and `nan approvals` (see [API keys and tokens](#api-keys-and-tokens)). `nan me`, `nan metrics usage` and the dashboard still need a session from `nan auth login`. ### Use the CLI in CI or on a server @@ -332,7 +446,7 @@ A **token** is the one to give a script that only manages runs. It cannot call t - You can revoke any of them at any time. - It carries **scopes**, what it may do: `runs` and `workspaces:read` by default. To list or run automations, add `automations:read` or `automations:run`. See [Tokens for automations](/docs/automations#tokens-for-automations). -Creating runs still needs a plan with inference, whatever credential you use. The [API reference](/docs/api#description/platform-tokens) lists which credential each endpoint accepts. +Creating runs on NaN inference still needs a plan with inference, whatever credential you use. Runs on [your own provider key](#use-your-own-provider-key-byok) need a signed-in session instead of a key or a token. The [API reference](/docs/api#description/platform-tokens) lists which credential each endpoint accepts. The key inside a workspace, the one its agents use for inference, cannot manage runs: the API answers it with `403`. An agent working in your workspace cannot start new runs on its own, unless you give it one of your own API keys or tokens. @@ -399,7 +513,7 @@ Nothing: the run keeps going in the workspace. Open its page in the **Runs** tab
-No. Runs use the models through your workspace's inference key, so creating them needs a plan with inference. You can still create a token and list your workspaces and runs. +Yes, with [your own provider key](#use-your-own-provider-key-byok) and an active workspace of your own: your provider bills the tokens. Runs on NaN inference need a plan with inference.
diff --git a/src/content/docs/webhooks.mdx b/src/content/docs/webhooks.mdx index eeae6db6..2567e678 100644 --- a/src/content/docs/webhooks.mdx +++ b/src/content/docs/webhooks.mdx @@ -237,7 +237,7 @@ The **Deliveries** tab lists the last 50 webhooks the automation received in the | **Ignored by a filter** | The event or actor filter did not match, or, with **Fresh clone per run**, the pull request came from a fork or an author without write access. | | **Stopped: caused by one of its own runs** | A [loop](#loops) was stopped. | | **Skipped: a run for the same subject is active** | See [When a run is already active](/docs/automations#when-a-run-is-already-active). | -| **Not accepted: your membership does not include runs** | The run could not be created: your plan has no inference, or the workspace is stopped or deleted, its agent is not installed or it has no inference key. The sender does not retry it, so the event is lost: keep the workspace running. | +| **Not accepted: your membership does not include runs** | The run could not be created: your plan has no inference (or, for an automation on [your own provider key](/docs/automations#use-your-own-provider-key-byok), you have no active workspace), or the workspace is stopped or deleted, its agent is not installed or it has no inference key. The sender does not retry it, so the event is lost: keep the workspace running. | | **Too large** | The event did not fit in what the agent can receive. | No payload and no headers are kept. Deliveries with a bad signature are never stored, and throttled ones (rate limit, full queue, temporarily unavailable) are answered with a retry and not listed. @@ -293,7 +293,7 @@ Each schedule shows its next run and how the last one went: | **Skipped: too many runs waiting** | Your run queue is full. | | **Skipped: automation disabled** | The automation is not enabled. | | **Skipped: workspace deleted** | Its workspace no longer exists. | -| **Not accepted: your membership does not include runs** | The run could not be created: your plan has no inference, or the workspace is stopped or not ready for the agent. | +| **Not accepted: your membership does not include runs** | The run could not be created: your plan has no inference (or, for an automation on [your own provider key](/docs/automations#use-your-own-provider-key-byok), you have no active workspace), or the workspace is stopped or not ready for the agent. | | **Missed (more than 15 minutes late)** | It could not start in time and was skipped. | | **Error** | Something failed on our side. | diff --git a/src/content/docs/workspaces.mdx b/src/content/docs/workspaces.mdx index c29cbeb6..02921630 100644 --- a/src/content/docs/workspaces.mdx +++ b/src/content/docs/workspaces.mdx @@ -20,7 +20,7 @@ Everything is managed from [cloud.nan.builders/workspaces](https://cloud.nan.bui Any **paying member** of NaN can buy workspace slots. The Premium plan includes **one free workspace** (Micro: 1 vCPU, 2 GiB RAM, 10 GiB disk). -A slot pays for the **machine only**. It does not include inference: the agents in your workspace call NaN models only if your membership includes inference. Without it you can still use the workspace as a dev box, and point the agents at any provider you already have. +A slot pays for the **machine only**. It does not include inference: the agents in your workspace call NaN models only if your membership includes inference. Without it you can still use the workspace as a dev box, point the agents at any provider you already have, and start [runs](/docs/runs#use-your-own-provider-key-byok) and [automations](/docs/automations#use-your-own-provider-key-byok) on your own provider key. ## Sizes and prices @@ -272,7 +272,7 @@ No. Each workspace is a dedicated, isolated machine with its own CPU, memory and
-No. A slot pays for the machine. The agents use NaN models only if your membership includes inference. Without it you can still install the agents and connect them to another provider. +No. A slot pays for the machine. The agents use NaN models only if your membership includes inference. Without it you can still install the agents and connect them to another provider, and use [runs](/docs/runs#use-your-own-provider-key-byok) and [automations](/docs/automations#use-your-own-provider-key-byok) with your own provider key.
diff --git a/src/data/openapi.json b/src/data/openapi.json index 26511a9d..e98293d6 100644 --- a/src/data/openapi.json +++ b/src/data/openapi.json @@ -60,7 +60,7 @@ }, { "name": "Runs", - "description": "Give an agent (Pi, Pi + Gentle Shell or Hermes) a task inside one of your workspaces and follow it. A run keeps going if the client disconnects. Accepts a member API key or a platform token with the `runs` scope. Guide: [Agent runs](https://nan.builders/docs/runs)." + "description": "Give an agent (Pi, Pi + Gentle Shell or Hermes) a task inside one of your workspaces and follow it. A run keeps going if the client disconnects. Accepts a member API key or a platform token with the `runs` scope. Guide: [Agent runs](https://nan.builders/docs/runs). Runs can use NaN inference or your own provider key: [Use your own provider key](https://nan.builders/docs/runs#use-your-own-provider-key-byok)." }, { "name": "Automations", @@ -1614,7 +1614,7 @@ "platformToken": [] } ], - "description": "Gives an agent a task in one of your workspaces. The run is queued and starts as soon as there is a free place: a workspace runs 1 (Micro and the free workspace), 2 (Nano), 3 (Basic), 5 (Medium) or 8 (Large) runs at once, never more than your per-member total: at most 5 runs working across all your workspaces (8 on Premium). Extra runs wait, first in, first out.\n\nLimits: up to 20 queued runs per workspace and 50 in total (`429 run_queue_full`), and 60 new runs per rolling hour (`429 rate_limited`).\n\nCreating runs needs a plan with inference (`403 tier_restricted`): the run uses the workspace's inference key and counts against your plan's usage. The workspace must be `running` and have the agent installed.\n\n**Idempotency.** Send an `Idempotency-Key` header to make retries safe: the same key with the same body returns the run already created (`200`), the same key with a different body answers `409 idempotency_conflict`.\n\nAuthentication: a member API key (`sk-...`) or a platform token (`nan_pat_...`) with the `runs` scope.", + "description": "Gives an agent a task in one of your workspaces. The run is queued and starts as soon as there is a free place: a workspace runs 1 (Micro and the free workspace), 2 (Nano), 3 (Basic), 5 (Medium) or 8 (Large) runs at once, never more than your per-member total: at most 5 runs working across all your workspaces (8 on Premium). Extra runs wait, first in, first out.\n\nLimits: up to 20 queued runs per workspace and 50 in total (`429 run_queue_full`), and 60 new runs per rolling hour (`429 rate_limited`).\n\nWith NaN inference (the default), creating runs needs a plan with inference (`403 tier_restricted`): the run uses the workspace's inference key and counts against your plan's usage. With your own provider key (`inference.source: byok`) a plan with inference is not needed, an active workspace of yours is enough; your provider bills the run, NaN does not meter it, and these runs need a signed-in session (`403 session_required` for API keys and platform tokens). Up to 8 runs on your own keys work at once across your workspaces, on top of the workspace's own limit. The workspace must be `running` and have the agent installed. Guide: [Use your own provider key](https://nan.builders/docs/runs#use-your-own-provider-key-byok).\n\n**Idempotency.** Send an `Idempotency-Key` header to make retries safe: the same key with the same body returns the run already created (`200`), the same key with a different body answers `409 idempotency_conflict`.\n\nAuthentication: a member API key (`sk-...`) or a platform token (`nan_pat_...`) with the `runs` scope.", "parameters": [ { "name": "Idempotency-Key", @@ -1634,13 +1634,31 @@ "schema": { "$ref": "#/components/schemas/CreateRunRequest" }, - "example": { - "workspace": "develop", - "agent": "pi", - "prompt": "add tests for the date parser and make them pass", - "cwd": "/home/nan/projects/api", - "git_isolation": null, - "timeout_seconds": 1800 + "examples": { + "nan": { + "summary": "NaN inference (the default).", + "value": { + "workspace": "develop", + "agent": "pi", + "prompt": "add tests for the date parser and make them pass", + "cwd": "/home/nan/projects/api", + "git_isolation": null, + "timeout_seconds": 1800 + } + }, + "byok": { + "summary": "Your own provider key (needs a signed-in session).", + "value": { + "workspace": "develop", + "agent": "pi", + "prompt": "add tests for the date parser and make them pass", + "inference": { + "source": "byok", + "secret": "TOGETHER_KEY", + "model": "meta-llama/Llama-3.3-70B-Instruct" + } + } + } } } } @@ -1727,18 +1745,45 @@ } }, "400": { - "description": "A field is invalid; `param` names it (`workspace`, `agent`, `prompt`, `cwd`, `timeout_seconds`, `model`, `trigger`, `Idempotency-Key`, or an unknown field). The body is limited to 128 KiB, and must be one JSON object.", + "description": "A field is invalid; `param` names it (`workspace`, `agent`, `prompt`, `cwd`, `timeout_seconds`, `model`, `trigger`, `inference`, `inference.secret`, `inference.model`, `Idempotency-Key`, or an unknown field). The body is limited to 128 KiB, and must be one JSON object. With your own key: `inference` when the object is malformed or your account cannot use your own key yet; `inference.secret` when the secret is not of kind `provider_key`; `inference.model` when the model is missing or invalid; `model` when a top-level `model` is sent with `byok`.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, - "example": { - "error": { - "message": "must be between 60 and 7200", - "type": "invalid_request_error", - "param": "timeout_seconds", - "code": "invalid_request" + "examples": { + "invalid_field": { + "summary": "An invalid field.", + "value": { + "error": { + "message": "must be between 60 and 7200", + "type": "invalid_request_error", + "param": "timeout_seconds", + "code": "invalid_request" + } + } + }, + "not_a_provider_key": { + "summary": "The secret is not a provider key.", + "value": { + "error": { + "message": "the secret is not a provider key", + "type": "invalid_request_error", + "param": "inference.secret", + "code": "invalid_request" + } + } + }, + "byok_unavailable": { + "summary": "Your own key is not available for your account.", + "value": { + "error": { + "message": "bring-your-own-key inference is not available for your account", + "type": "invalid_request_error", + "param": "inference", + "code": "invalid_request" + } + } } } } @@ -1748,7 +1793,7 @@ "$ref": "#/components/responses/RunsUnauthorized" }, "403": { - "description": "`tier_restricted`: your plan does not include inference. `workspace_key_not_allowed`: the bearer is the key inside a workspace.", + "description": "`tier_restricted`: with NaN inference, your plan does not include inference; with your own key, you have no active workspace either. `session_required` (`param: \"inference\"`): a run on your own key with an API key or a platform token; create it signed in (portal or `nan auth login`), or run an automation that uses your key. `workspace_key_not_allowed`: the bearer is the key inside a workspace.", "content": { "application/json": { "schema": { @@ -1774,24 +1819,50 @@ "code": "workspace_key_not_allowed" } } + }, + "session_required": { + "value": { + "error": { + "message": "runs with your own key need a signed-in session (portal or nan CLI); API keys and platform tokens can trigger an automation that uses your key", + "type": "permission_error", + "param": "inference", + "code": "session_required" + } + } } } } } }, "404": { - "description": "No workspace with that name or id among yours.", + "description": "No workspace with that name or id among yours, or (`param: \"inference.secret\"`) no secret with that name among yours.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, - "example": { - "error": { - "message": "workspace not found", - "type": "invalid_request_error", - "param": null, - "code": "not_found" + "examples": { + "workspace": { + "summary": "No such workspace.", + "value": { + "error": { + "message": "workspace not found", + "type": "invalid_request_error", + "param": null, + "code": "not_found" + } + } + }, + "secret": { + "summary": "No such provider key secret.", + "value": { + "error": { + "message": "secret not found", + "type": "invalid_request_error", + "param": "inference.secret", + "code": "not_found" + } + } } } } @@ -3259,7 +3330,7 @@ "platformToken": [] } ], - "description": "Starts a run of the automation now, with its saved instructions, secrets and approval settings: the same run a webhook or a schedule would start. Its agent proposes actions, which apply automatically or wait for your approval as the automation says. Follow it like any run (`GET /v1/runs/{id}/events`). Runs you start this way are never skipped by `skip_if_active` or cancelled by `supersede`, but they count towards the automation's `max_runs_per_hour` and the limits of every run.\n\nAuthentication: a member API key (`sk-...`) or a platform token (`nan_pat_...`) with the `automations:run` scope. Runs need a plan with inference.", + "description": "Starts a run of the automation now, with its saved instructions, secrets and approval settings: the same run a webhook or a schedule would start. Its agent proposes actions, which apply automatically or wait for your approval as the automation says. Follow it like any run (`GET /v1/runs/{id}/events`). Runs you start this way are never skipped by `skip_if_active` or cancelled by `supersede`, but they count towards the automation's `max_runs_per_hour` and the limits of every run.\n\nAuthentication: a member API key (`sk-...`) or a platform token (`nan_pat_...`) with the `automations:run` scope. Runs on NaN inference need a plan with inference; an automation that uses your own provider key needs an active workspace of yours instead. Any of these credentials may run an automation that uses your key: its instructions are fixed in the portal, and what you send as `input` reaches an agent that holds your key, so treat it as untrusted. Guide: [Use your own provider key](https://nan.builders/docs/runs#use-your-own-provider-key-byok).", "parameters": [ { "name": "id", @@ -3457,7 +3528,7 @@ "$ref": "#/components/responses/RunsUnauthorized" }, "403": { - "description": "A platform token without the `automations:run` scope (`token_scope_denied`), or the key that lives inside a workspace (`workspace_key_not_allowed`): agents cannot manage automations. A membership without runs: `tier_restricted`.", + "description": "A platform token without the `automations:run` scope (`token_scope_denied`), or the key that lives inside a workspace (`workspace_key_not_allowed`): agents cannot manage automations. A membership without runs (no plan with inference, or, for an automation on your own key, no active workspace): `tier_restricted`.", "content": { "application/json": { "schema": { @@ -5430,7 +5501,7 @@ ], "pattern": "^[A-Za-z0-9._:/-]{1,128}$", "default": null, - "description": "Model for the agent. `null` uses the agent's own default." + "description": "Model for the agent on NaN inference. `null` uses the agent's own default. With `inference.source: byok` leave it out: the model is `inference.model` (`400` with `param: \"model\"` otherwise)." }, "trigger": { "type": "string", @@ -5441,6 +5512,9 @@ ], "default": "api", "description": "Who started the run. Informational only." + }, + "inference": { + "$ref": "#/components/schemas/CreateRunInference" } } }, @@ -5575,7 +5649,8 @@ "type": [ "string", "null" - ] + ], + "description": "The model the run asked for, or `null` for the agent's default. With `byok` inference it is `inference.model`." }, "trigger": { "type": "string", @@ -5710,9 +5785,10 @@ "enum": [ "litellm", "agent", + "byok", null ], - "description": "Where the counts come from: `litellm` (measured by the NaN gateway) or `agent` (reported by the agent)." + "description": "Where the counts come from: `litellm` (measured by the NaN gateway), `agent` (reported by the agent) or `byok` (a run on your own provider key: the counts are what the agent reported, `null` when it reports none, and nothing is charged against your NaN plan; your provider's bill is the reference)." } } }, @@ -5748,16 +5824,7 @@ "description": "`run`: a run you start with `POST /v1/runs`. `propose`: an automation run, whose agent proposes actions instead of acting. `apply`: the step that applies a proposal's actions, with no agent." }, "inference": { - "type": "object", - "description": "Where the run's inference comes from. Always NaN.", - "properties": { - "source": { - "type": "string", - "enum": [ - "nan" - ] - } - } + "$ref": "#/components/schemas/RunInference" }, "automation_id": { "type": [ @@ -5865,6 +5932,29 @@ "format": "date-time", "description": "When the agent of an automation run finished, before any approval." }, + "error": { + "type": [ + "object", + "null" + ], + "description": "Why a finished run failed, or `null`: `code` is its `error_code`, and on `GET /v1/runs/{id}` `message` is the text of its last `error` event (masked; render it as plain text; `null` when there is none). On a run on your own provider key, a provider error (a wrong key, an unknown model) shows up here.", + "required": [ + "code", + "message" + ], + "additionalProperties": false, + "properties": { + "code": { + "type": "string" + }, + "message": { + "type": [ + "string", + "null" + ] + } + } + }, "checkout": { "oneOf": [ { @@ -6863,7 +6953,8 @@ "type": [ "string", "null" - ] + ], + "description": "The model of its runs, or `null` for the agent's default. With `byok` inference it is `inference.model` and is never `null`." }, "instructions": { "type": "string", @@ -6906,15 +6997,7 @@ "description": "How long a proposal waits for you. 259200 (72 hours) by default." }, "inference": { - "type": "object", - "properties": { - "source": { - "type": "string", - "enum": [ - "nan" - ] - } - } + "$ref": "#/components/schemas/RunInference" }, "github_repo": { "type": [ @@ -7491,6 +7574,93 @@ "description": "For display, for example `fresh @ 0123456789ab` or `fresh @ default branch`." } } + }, + "RunInference": { + "type": "object", + "description": "Where the model calls of a run or an automation go. `{\"source\":\"nan\"}`: NaN inference, through the workspace's inference key, counted against your plan. `{\"source\":\"byok\"}`: your own provider key (bring your own key): a secret of kind `provider_key` you saved in the portal (Settings > Secrets > Provider key). Its provider bills the run and NaN does not meter it. The key itself never appears here. Guide: [Use your own provider key](https://nan.builders/docs/runs#use-your-own-provider-key-byok).", + "required": [ + "source" + ], + "additionalProperties": false, + "properties": { + "source": { + "type": "string", + "enum": [ + "nan", + "byok" + ] + }, + "provider": { + "type": "string", + "enum": [ + "openai_compatible", + "anthropic", + "openai", + "gemini" + ], + "description": "Only with `byok`: the provider of the key (`openai_compatible` is any endpoint that speaks the OpenAI API, at the base URL saved with the key)." + }, + "secret": { + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]{0,63}$", + "description": "Only with `byok`: the name of the provider key secret. On a run it is the name when the run was created, and it stays after the secret is deleted." + }, + "model": { + "type": "string", + "description": "Only with `byok`: the model id, as your provider names it." + } + } + }, + "CreateRunInference": { + "description": "The inference of the run. Leave it out (or send `{\"source\":\"nan\"}`) for NaN inference. `{\"source\":\"byok\",\"secret\":\"\",\"model\":\"\"}` uses your own provider key: `secret` is the name of a secret of kind `provider_key` (created in the portal; the secrets API is portal-only) and `model` the model id your provider serves. With `byok`, leave the top-level `model` out. A run with `byok` needs a signed-in session: an API key or a platform token gets `403 session_required` (they can run an automation that uses your key). Guide: [Use your own provider key](https://nan.builders/docs/runs#use-your-own-provider-key-byok).", + "oneOf": [ + { + "type": "object", + "title": "NaN inference", + "required": [ + "source" + ], + "additionalProperties": false, + "properties": { + "source": { + "type": "string", + "enum": [ + "nan" + ] + } + } + }, + { + "type": "object", + "title": "Your own provider key", + "required": [ + "source", + "secret", + "model" + ], + "additionalProperties": false, + "properties": { + "source": { + "type": "string", + "enum": [ + "byok" + ] + }, + "secret": { + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]{0,63}$", + "description": "Name of one of your secrets of kind `provider_key`.", + "example": "TOGETHER_KEY" + }, + "model": { + "type": "string", + "pattern": "^[A-Za-z0-9._:/-]{1,128}$", + "description": "The model id, as your provider names it. It cannot start with `-`.", + "example": "meta-llama/Llama-3.3-70B-Instruct" + } + } + } + ] } }, "responses": { diff --git a/src/lib/openapiByok.test.ts b/src/lib/openapiByok.test.ts new file mode 100644 index 00000000..7ea33460 --- /dev/null +++ b/src/lib/openapiByok.test.ts @@ -0,0 +1,155 @@ +import { describe, expect, it } from 'vitest'; +import { readFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { dirname, resolve } from 'node:path'; +import spec from '../data/openapi.json'; +import { validate, type Schema } from '../tests/helpers/openapiValidate'; + +/** + * BYOK (bring your own key) in the API reference: Agent Automations Phase 3 + * (devops AGENT-AUTOMATIONS-PHASE3-BYOK.md §2, cloud-api #347, 0.8.260). + * + * The fixtures in src/tests/fixtures/phase3 are rendered by cloud-api + * origin/main's own handlers (renderRun, renderAutomation) at 4c69270: every + * one must validate against the schema this reference publishes. + */ + +const s = spec as any; +const op = (path: string, method: string) => s.paths[path][method]; +const ref = (name: string): Schema => ({ $ref: `#/components/schemas/${name}` }); +const here = dirname(fileURLToPath(import.meta.url)); +const fixture = (name: string) => + JSON.parse(readFileSync(resolve(here, '../tests/fixtures/phase3', `${name}.json`), 'utf-8')); +const GUIDE = 'https://nan.builders/docs/runs#use-your-own-provider-key-byok'; +const PROVIDERS = ['openai_compatible', 'anthropic', 'openai', 'gemini']; + +describe('openapi.json: BYOK inference', () => { + it('the rendered inference object: source, provider, secret, model', () => { + const inf = s.components.schemas.RunInference; + expect(inf.additionalProperties).toBe(false); + expect(inf.properties.source.enum).toEqual(['nan', 'byok']); + expect(inf.properties.provider.enum).toEqual(PROVIDERS); + expect(inf.properties.secret.pattern).toBe('^[A-Z][A-Z0-9_]{0,63}$'); + expect(inf.description).toContain(GUIDE); + expect(s.components.schemas.Run.properties.inference).toEqual(ref('RunInference')); + expect(s.components.schemas.Automation.properties.inference).toEqual(ref('RunInference')); + }); + + it('the request: {"source":"nan"} or {"source":"byok","secret","model"}, nothing else', () => { + const body = s.components.schemas.CreateRunRequest.properties.inference; + expect(body).toEqual(ref('CreateRunInference')); + const req = ref('CreateRunInference'); + const ok = [ + { source: 'nan' }, + { source: 'byok', secret: 'TOGETHER_KEY', model: 'meta-llama/Llama-3.3-70B-Instruct' }, + ]; + const bad = [ + {}, + { source: 'own' }, + { source: 'nan', secret: 'K' }, + { source: 'byok', model: 'm' }, + { source: 'byok', secret: 'K' }, + { source: 'byok', secret: 'lower', model: 'm' }, + { source: 'byok', secret: 'K', model: 'm', provider: 'openai' }, + ]; + for (const v of ok) expect(validate(v, req), JSON.stringify(v)).toEqual([]); + for (const v of bad) expect(validate(v, req).length, JSON.stringify(v)).toBeGreaterThan(0); + const desc = s.components.schemas.CreateRunInference.description; + expect(desc).toContain('`403 session_required`'); + expect(desc).toContain('portal-only'); + expect(s.components.schemas.CreateRunRequest.properties.model.description).toContain('inference.model'); + }); + + it('usage.source can be byok', () => { + const src = s.components.schemas.Run.properties.usage.properties.source; + expect(src.enum).toEqual(['litellm', 'agent', 'byok', null]); + expect(src.description).toMatch(/nothing is charged against your NaN plan/); + }); + + it('POST /runs documents who may use it and every BYOK error', () => { + const create = op('/runs', 'post'); + expect(create.description).toMatch(/an active workspace of yours is enough/); + expect(create.description).toMatch(/Up to 8 runs on your own keys/); + expect(create.description).toContain(GUIDE); + const r = create.responses; + expect(r['400'].description).toContain('`inference.secret`'); + expect(r['400'].description).toContain('`inference.model`'); + const ex400 = r['400'].content['application/json'].examples; + expect(ex400.not_a_provider_key.value.error.param).toBe('inference.secret'); + expect(ex400.byok_unavailable.value.error.param).toBe('inference'); + const ex403 = r['403'].content['application/json'].examples.session_required.value.error; + expect(ex403).toMatchObject({ code: 'session_required', param: 'inference', type: 'permission_error' }); + expect(r['404'].content['application/json'].examples.secret.value.error).toMatchObject({ + code: 'not_found', + param: 'inference.secret', + }); + const req = create.requestBody.content['application/json'].examples; + expect(req.byok.value.inference.source).toBe('byok'); + expect(req.byok.value.model).toBeUndefined(); + }); + + it('running an automation on your key: any credential, untrusted input', () => { + const run = op('/automations/{id}/runs', 'post'); + expect(run.description).toMatch(/needs an active workspace of yours instead/); + expect(run.description).toMatch(/untrusted/); + expect(run.responses['403'].description).toMatch(/own key/); + }); + + it('secrets stay out of the public API (they are written in the portal only)', () => { + expect(Object.keys(s.paths).filter((p) => p.includes('secret'))).toEqual([]); + }); + + it('every request and response example of POST /runs matches its schema', () => { + const create = op('/runs', 'post'); + const req = create.requestBody.content['application/json']; + for (const [name, ex] of Object.entries(req.examples) as Array<[string, any]>) { + expect(validate(ex.value, req.schema), `request ${name}`).toEqual([]); + } + for (const [status, r] of Object.entries(create.responses) as Array<[string, any]>) { + const json = r.content?.['application/json']; + for (const [name, ex] of Object.entries(json?.examples ?? {}) as Array<[string, any]>) { + expect(validate(ex.value, json.schema), `${status} ${name}`).toEqual([]); + } + } + }); + + it.each([ + ['run_get_byok', 'Run'], + ['run_list_byok', 'RunList'], + ['automation_get_byok', 'Automation'], + ])('cloud-api golden %s validates against %s', (name, schema) => { + expect(validate(fixture(name), ref(schema))).toEqual([]); + }); + + it('the goldens carry the BYOK shape', () => { + const run = fixture('run_get_byok'); + expect(run.inference).toEqual({ source: 'byok', provider: 'anthropic', secret: 'ANTHROPIC_KEY', model: 'claude-sonnet-4-5' }); + expect(run.model).toBe(run.inference.model); + expect(run.usage.source).toBe('byok'); + expect(fixture('run_list_byok').data[1].inference).toEqual({ source: 'nan' }); + expect(fixture('automation_get_byok').inference.provider).toBe('openai_compatible'); + }); +}); + +describe('docs: the BYOK guide exists where the reference links it', () => { + const doc = (lang: string, name: string) => + readFileSync(resolve(here, `../content/${lang}/${name}.mdx`), 'utf-8'); + + it.each(['docs', 'docs-es'])('%s: runs and automations have the BYOK section', (lang) => { + expect(doc(lang, 'runs')).toMatch(/^## .*\(BYOK\)$/m); + expect(doc(lang, 'automations')).toMatch(/^## .*\(BYOK\)$/m); + }); + + it('the English heading gives the anchor the reference and the CLI link', () => { + expect(doc('docs', 'runs')).toMatch(/^## Use your own provider key \(BYOK\)$/m); + }); + + it.each(['docs', 'docs-es'])('%s: the guide covers the contract', (lang) => { + const runs = doc(lang, 'runs'); + for (const term of ['provider_key', 'openai_compatible', '--byok-secret', 'session_required', '"source": "byok"', 'https', '8']) { + expect(runs, term).toContain(term); + } + expect(runs).not.toMatch(/—/); + expect(doc(lang, 'automations')).not.toMatch(/—/); + }); +}); diff --git a/src/lib/openapiRuns.test.ts b/src/lib/openapiRuns.test.ts index 7a2dcd51..6e10055b 100644 --- a/src/lib/openapiRuns.test.ts +++ b/src/lib/openapiRuns.test.ts @@ -95,7 +95,7 @@ describe('openapi.json: the Runs contract (cloud-api)', () => { expect(body.required).toEqual(['workspace', 'agent', 'prompt']); expect(body.additionalProperties).toBe(false); expect(Object.keys(body.properties)).toEqual([ - 'workspace', 'agent', 'prompt', 'cwd', 'git_isolation', 'timeout_seconds', 'model', 'trigger', + 'workspace', 'agent', 'prompt', 'cwd', 'git_isolation', 'timeout_seconds', 'model', 'trigger', 'inference', ]); expect(body.properties.agent.enum).toEqual(['pi', 'pi_gentle', 'hermes']); expect(body.properties.prompt.description).toContain('32768 bytes'); @@ -169,7 +169,7 @@ describe('openapi.json: the Runs contract (cloud-api)', () => { 'result', 'usage', 'last_seq', 'created_at', 'started_at', 'finished_at', // Phase 2 (handlers/runs.go runJSON), same order. 'phase', 'inference', 'automation_id', 'parent_run_id', 'apply_run_ids', 'approval', 'instructions', 'input', - 'approval_kinds', 'github_repo', 'trigger_depth', 'secrets', 'agent_finished_at', + 'approval_kinds', 'github_repo', 'trigger_depth', 'secrets', 'agent_finished_at', 'error', // Migration 110 (PR gatekeeper), same order. 'checkout', 'superseded_by', 'sticky_pr_comment', ]); diff --git a/src/tests/fixtures/phase3/automation_get_byok.json b/src/tests/fixtures/phase3/automation_get_byok.json new file mode 100644 index 00000000..42726901 --- /dev/null +++ b/src/tests/fixtures/phase3/automation_get_byok.json @@ -0,0 +1,42 @@ +{ + "agent": "pi", + "approval_kinds": [ + "github_pr_review" + ], + "approval_ttl_seconds": 259200, + "checkout": "workspace", + "checkout_untrusted_heads": false, + "concurrency_policy": "skip_if_active", + "created_at": "2026-10-11T09:00:00Z", + "cwd": "/home/nan/projects/api", + "description": "reviews", + "disabled_reason": null, + "enabled": true, + "git_isolation": true, + "github_repo": "acme/api", + "id": "a0000000-0000-4000-8000-000000000001", + "inference": { + "model": "qwen/qwen3-coder", + "provider": "openai_compatible", + "secret": "TOGETHER_KEY", + "source": "byok" + }, + "instructions": "Review for correctness.", + "max_runs_per_hour": 30, + "max_trigger_depth": 2, + "model": "qwen/qwen3-coder", + "name": "PR review api", + "object": "automation", + "secrets": [ + { + "env": "GITHUB_TOKEN", + "exposure": "actions", + "secret": "GITHUB_WRITE_TOKEN" + } + ], + "sticky_pr_comment": false, + "template": "pr_review", + "timeout_seconds": 1800, + "updated_at": "2026-10-11T09:00:00Z", + "workspace": "11111111-2222-4333-8444-555555555555" +} diff --git a/src/tests/fixtures/phase3/run_get_byok.json b/src/tests/fixtures/phase3/run_get_byok.json new file mode 100644 index 00000000..46a27be9 --- /dev/null +++ b/src/tests/fixtures/phase3/run_get_byok.json @@ -0,0 +1,59 @@ +{ + "agent": "pi", + "agent_finished_at": null, + "apply_run_ids": [], + "approval": null, + "approval_kinds": null, + "automation_id": null, + "cancel_requested": false, + "checkout": null, + "created_at": "2026-10-10T09:00:00Z", + "cwd": "/home/nan/projects/api", + "error": null, + "error_code": null, + "exit_code": null, + "finished_at": null, + "git_isolation": null, + "github_repo": null, + "id": "6f1c2a9b-0000-4000-8000-000000000001", + "inference": { + "model": "claude-sonnet-4-5", + "provider": "anthropic", + "secret": "ANTHROPIC_KEY", + "source": "byok" + }, + "last_seq": 42, + "model": "claude-sonnet-4-5", + "object": "run", + "parent_run_id": null, + "phase": "run", + "prompt": "review PR 42 in full", + "prompt_preview": "review PR 42", + "queue_position": null, + "result": { + "actions_applied": [], + "artifacts": [], + "branch": "nan-run/6f1c2a9b", + "changed_files": 3, + "commit": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "status": "succeeded", + "summary": "done" + }, + "secrets": [], + "started_at": "2026-10-10T09:00:01Z", + "state": "succeeded", + "sticky_pr_comment": false, + "superseded_by": null, + "timeout_seconds": 1800, + "trigger": "cli", + "trigger_depth": 0, + "usage": { + "source": "byok", + "tokens_in": 12840, + "tokens_out": 1630 + }, + "workspace": { + "id": "11111111-2222-4333-8444-555555555555", + "name": "develop" + } +} diff --git a/src/tests/fixtures/phase3/run_list_byok.json b/src/tests/fixtures/phase3/run_list_byok.json new file mode 100644 index 00000000..013df2c9 --- /dev/null +++ b/src/tests/fixtures/phase3/run_list_byok.json @@ -0,0 +1,119 @@ +{ + "data": [ + { + "agent": "pi", + "agent_finished_at": null, + "apply_run_ids": [], + "approval": null, + "approval_kinds": null, + "automation_id": null, + "cancel_requested": false, + "checkout": null, + "created_at": "2026-10-10T09:00:00Z", + "cwd": "/home/nan/projects/api", + "error": null, + "error_code": null, + "exit_code": null, + "finished_at": null, + "git_isolation": null, + "github_repo": null, + "id": "6f1c2a9b-0000-4000-8000-000000000001", + "inference": { + "model": "claude-sonnet-4-5", + "provider": "anthropic", + "secret": "ANTHROPIC_KEY", + "source": "byok" + }, + "last_seq": 42, + "model": "claude-sonnet-4-5", + "object": "run", + "parent_run_id": null, + "phase": "run", + "prompt_preview": "review PR 42", + "queue_position": null, + "result": { + "actions_applied": [], + "artifacts": [], + "branch": "nan-run/6f1c2a9b", + "changed_files": 3, + "commit": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "status": "succeeded", + "summary": "done" + }, + "secrets": [], + "started_at": "2026-10-10T09:00:01Z", + "state": "succeeded", + "sticky_pr_comment": false, + "superseded_by": null, + "timeout_seconds": 1800, + "trigger": "cli", + "trigger_depth": 0, + "usage": { + "source": "byok", + "tokens_in": 12840, + "tokens_out": 1630 + }, + "workspace": { + "id": "11111111-2222-4333-8444-555555555555", + "name": "develop" + } + }, + { + "agent": "pi", + "agent_finished_at": null, + "apply_run_ids": [], + "approval": null, + "approval_kinds": null, + "automation_id": null, + "cancel_requested": false, + "checkout": null, + "created_at": "2026-10-10T09:00:00Z", + "cwd": "/home/nan/projects/api", + "error": null, + "error_code": null, + "exit_code": null, + "finished_at": null, + "git_isolation": null, + "github_repo": null, + "id": "6f1c2a9b-0000-4000-8000-000000000002", + "inference": { + "source": "nan" + }, + "last_seq": 42, + "model": null, + "object": "run", + "parent_run_id": null, + "phase": "run", + "prompt_preview": "review PR 42", + "queue_position": null, + "result": { + "actions_applied": [], + "artifacts": [], + "branch": "nan-run/6f1c2a9b", + "changed_files": 3, + "commit": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "status": "succeeded", + "summary": "done" + }, + "secrets": [], + "started_at": "2026-10-10T09:00:01Z", + "state": "succeeded", + "sticky_pr_comment": false, + "superseded_by": null, + "timeout_seconds": 1800, + "trigger": "cli", + "trigger_depth": 0, + "usage": { + "source": null, + "tokens_in": null, + "tokens_out": null + }, + "workspace": { + "id": "11111111-2222-4333-8444-555555555555", + "name": "develop" + } + } + ], + "next_cursor": null, + "object": "list" +} diff --git a/src/tests/layouts/automationsDocs.test.ts b/src/tests/layouts/automationsDocs.test.ts index 6b053f15..8adf8ade 100644 --- a/src/tests/layouts/automationsDocs.test.ts +++ b/src/tests/layouts/automationsDocs.test.ts @@ -278,8 +278,21 @@ describe.each(Object.entries(pages))('automations guides (%s)', (_locale, page) } }); - test('ephemeral executors and BYOK are not documented', () => { - for (const body of [autoBody, hooksBody]) expect(body).not.toMatch(/ephemeral|ef[ií]mer|byok|bring your own key/i); + test('ephemeral executors are not documented', () => { + for (const body of [autoBody, hooksBody]) expect(body).not.toMatch(/ephemeral|ef[ií]mer/i); + }); + + // BYOK (AGENT-AUTOMATIONS-PHASE3-BYOK.md): portal labels from the cloud-ui + // automation editor and Settings > Secrets. + test('documents BYOK with the portal labels, the untrusted-input warning and the limits', () => { + expect(autoBody).toMatch(/^## .*\(BYOK\)$/m); + for (const label of ['**Inference**', '**My own key**', '**NaN (included in your plan)**', '**Provider key**', '**Model**']) { + expect(autoBody, label).toContain(label); + } + expect(autoBody).toMatch(/automations:run/); + expect(autoBody).toMatch(/48 KiB/); + expect(autoBody).toMatch(/8 (runs with|runs con|a la vez|at once)/); + expect(autoBody).toContain('config_error'); }); }); diff --git a/src/tests/layouts/runsDocs.test.ts b/src/tests/layouts/runsDocs.test.ts index b2a5a494..361bd60a 100644 --- a/src/tests/layouts/runsDocs.test.ts +++ b/src/tests/layouts/runsDocs.test.ts @@ -167,7 +167,9 @@ describe.each(Object.entries(pages))('agent runs guide (%s)', (locale, page) => for (const [size, n] of CONCURRENCY) { expect(body, size).toMatch(new RegExp(`^\\| ${size}\\b[^|]*\\| ${n} \\|$`, 'm')); } - expect(flat).toMatch(/\*\*5 runs/); + expect(flat).toMatch(/\*\*5 (of them|de ellos)/); + // BYOK runs: their own per-member cap (MemberBYOKCap = 8). + expect(flat).toMatch(/\*\*8 (at once|a la vez)\*\*/); expect(flat).toMatch(/\*\*8 (on|con) Premium\*\*/); expect(flat).toMatch(/\*\*20 runs (per|por) workspace\*\*/); expect(flat).toMatch(/\*\*50 (in total|en total)\*\*/); @@ -175,7 +177,7 @@ describe.each(Object.entries(pages))('agent runs guide (%s)', (locale, page) => }); test('says runs need inference, count as usage and mask secrets', () => { - expect(flat).toMatch(/\*\*(A plan with inference|Un plan con inferencia)\.\*\*/); + expect(flat).toMatch(/\*\*(A plan with inference, or your own provider key|Un plan con inferencia, o tu propia clave de proveedor)\.\*\*/); expect(body).toContain('`***`'); }); diff --git a/src/tests/lib/modelCatalog.test.ts b/src/tests/lib/modelCatalog.test.ts index 9da736d2..0a2139ce 100644 --- a/src/tests/lib/modelCatalog.test.ts +++ b/src/tests/lib/modelCatalog.test.ts @@ -126,10 +126,24 @@ describe('the snippets in the guides', () => { * 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]), - ]; + const inSnippets = (source: string) => { + // A BYOK inference object names a model of the member's own provider + // (/docs/runs#use-your-own-provider-key-byok), not one of ours: its line + // carries "source": "byok" and is left out. Nothing else is. + const ours = source + .split('\n') + .filter((line) => !/"source"\s*:\s*"byok"/.test(line)) + .join('\n'); + return [ + ...[...ours.matchAll(/"model"\s*:\s*"([^"]+)"/g)].map((m) => m[1]), + ...[...ours.matchAll(/\bmodel\s*=\s*"([^"]+)"/g)].map((m) => m[1]), + ]; + }; + + it('a BYOK inference line is the only exemption', () => { + expect(inSnippets('{ "inference": { "source": "byok", "secret": "K", "model": "claude-sonnet-4-5" } }')).toEqual([]); + expect(inSnippets('{ "source": "nan", "model": "claude-sonnet-4-5" }')).toEqual(['claude-sonnet-4-5']); + }); for (const [label, source] of docFiles) { it(`only name models that exist (${label})`, () => { From 125dccf73e0033a0689610d9d1881d0914dc59ef Mon Sep 17 00:00:00 2001 From: barckcode Date: Tue, 13 Oct 2026 22:07:29 +0200 Subject: [PATCH 2/2] docs(byok): review nits (labels, field order, param model row, billing notes) Co-Authored-By: Claude Opus 5.5 --- src/content/docs-es/automations.mdx | 2 +- src/content/docs-es/pr-gatekeeper.mdx | 2 +- src/content/docs-es/runs.mdx | 13 +++++++------ src/content/docs-es/webhooks.mdx | 2 +- src/content/docs/automations.mdx | 2 +- src/content/docs/pr-gatekeeper.mdx | 2 +- src/content/docs/runs.mdx | 13 +++++++------ src/content/docs/webhooks.mdx | 2 +- src/data/openapi.json | 4 ++-- 9 files changed, 22 insertions(+), 20 deletions(-) diff --git a/src/content/docs-es/automations.mdx b/src/content/docs-es/automations.mdx index 9db2778c..59dd5ba5 100644 --- a/src/content/docs-es/automations.mdx +++ b/src/content/docs-es/automations.mdx @@ -62,7 +62,7 @@ Las tres plantillas de GitHub actúan sobre un repositorio, así que necesitan u | **Folder** | Dónde trabaja el agente, dentro de `/home/nan`. No se usa con **Fresh clone per run**. | | **Separate branch** | Rama aparte: **On** (por defecto), **Auto** u **Off (work in the folder directly)** (trabajar directamente en la carpeta), como en un [run](/es/docs/runs#dónde-trabaja-el-agente). No se usa con **Fresh clone per run**. | | **Time limit (minutes)** | El tiempo máximo, de 1 a 120. 30 por defecto. | -| **Inference** | La inferencia: **NaN (included in your plan)**, la predeterminada, o **My own key**, una **Provider key** tuya y el **Model**. Consulta [Usa tu propia clave de proveedor](#usa-tu-propia-clave-de-proveedor-byok). | +| **Inference** | La inferencia: **NaN (included in your plan)**, la predeterminada (**NaN (not in your plan)** si tu plan no incluye inferencia), o **My own key**, una **Provider key** tuya y el **Model**. Consulta [Usa tu propia clave de proveedor](#usa-tu-propia-clave-de-proveedor-byok). | | **GitHub repository (optional)** | `owner/name`, por ejemplo `acme/api`. El repositorio sobre el que actúan las acciones. | | **Checkout** | **Workspace folder** (por defecto): el agente trabaja en **Folder**, en tu workspace. **Fresh clone per run** (clon nuevo por run): NaN clona el **GitHub repository** en cada run, en el commit del que trata el evento, y borra el clon cuando termina el run. Consulta [Clon nuevo por run](#clon-nuevo-por-run). | | **Run pull requests from forks and outside contributors** | Aparece con **Fresh clone per run**. Desactivado por defecto. Consulta [Pull requests de forks y de colaboradores externos](#pull-requests-de-forks-y-de-colaboradores-externos). | diff --git a/src/content/docs-es/pr-gatekeeper.mdx b/src/content/docs-es/pr-gatekeeper.mdx index 3e41a571..92265020 100644 --- a/src/content/docs-es/pr-gatekeeper.mdx +++ b/src/content/docs-es/pr-gatekeeper.mdx @@ -331,7 +331,7 @@ Desde la terminal, `nan automations deliveries ` muestra la misma li ## Lo que cuesta -Cada review es un run en tu workspace, y los tokens que usa su agente cuentan para tu plan como cualquier otra petición. Nuestras reviews de un repositorio de Go de tamaño medio tardan de 8 a 20 minutos; la de las capturas usó 430K tokens de entrada y 5K de salida. Casi todo es el agente leyendo código y la salida de los tests, así que un repositorio pequeño cuesta mucho menos. El run de aplicación no usa inferencia. +Cada review es un run en tu workspace, y los tokens que usa su agente cuentan para tu plan como cualquier otra petición (con tu propia clave de proveedor, los factura tu proveedor). Nuestras reviews de un repositorio de Go de tamaño medio tardan de 8 a 20 minutos; la de las capturas usó 430K tokens de entrada y 5K de salida. Casi todo es el agente leyendo código y la salida de los tests, así que un repositorio pequeño cuesta mucho menos. El run de aplicación no usa inferencia. Algunas cosas lo mantienen a raya: diff --git a/src/content/docs-es/runs.mdx b/src/content/docs-es/runs.mdx index ca3f1e4e..5babe05e 100644 --- a/src/content/docs-es/runs.mdx +++ b/src/content/docs-es/runs.mdx @@ -68,7 +68,7 @@ Pulsa **New run** y rellena el formulario (el portal está en inglés): | **Folder** | La carpeta donde trabaja el agente. Por defecto es `/home/nan` y tiene que estar dentro de ella. | | **Separate branch** | Rama aparte: **Auto** (por defecto), **On** u **Off**. En un repositorio git, el agente trabaja en su propia rama y tu checkout se queda como está. Consulta [Dónde trabaja el agente](#dónde-trabaja-el-agente). | | **Time limit** | El tiempo máximo: **30 min** por defecto, igual que en la CLI y la API. Las opciones son 15 min, 30 min, 1 hour y 2 hours (1 hora y 2 horas). El run se para al llegar a él. | -| **Inference** | La inferencia: **NaN (included in your plan)**, la predeterminada, o **My own key**, una **Provider key** tuya y el **Model** que usar. Consulta [Usa tu propia clave de proveedor](#usa-tu-propia-clave-de-proveedor-byok). | +| **Inference** | La inferencia: **NaN (included in your plan)**, la predeterminada (aparece como **NaN (not in your plan)** si tu plan no incluye inferencia), o **My own key**, una **Provider key** tuya y el **Model** que usar. Consulta [Usa tu propia clave de proveedor](#usa-tu-propia-clave-de-proveedor-byok). | Pulsa **Start run**, o `Ctrl+Enter` (`⌘+Enter` en un Mac) desde la caja de la tarea. @@ -176,14 +176,14 @@ Escribe el id del modelo tal como lo llama tu proveedor. NaN no lo comprueba con Abre tus ajustes en el portal, **Secrets**, y pulsa **Add secret** (guardar un secreto necesita que hayas iniciado sesión en el navegador). Rellena (el portal está en inglés): +- **Name**: cómo la nombran los runs y las automatizaciones, por ejemplo `ANTHROPIC_KEY`. Mayúsculas, dígitos y `_`, empezando por una letra. - **Kind**: **Provider key**. - **Provider**: **Anthropic**, **OpenAI**, **Google Gemini** u **OpenAI-compatible**. No se puede cambiar después: para cambiar de proveedor, añade otra clave. - **Base URL**, solo para **OpenAI-compatible**: la raíz del endpoint, que suele acabar en `/v1`, por ejemplo `https://api.together.xyz/v1`. Mira las reglas más abajo. - **Value**: la clave, una sola línea sin espacios, de 8 a 4096 caracteres. -- **Name**: cómo la nombran los runs y las automatizaciones, por ejemplo `ANTHROPIC_KEY`. Mayúsculas, dígitos y `_`, empezando por una letra. - **Description (optional)**. -Como todo secreto, una provider key es de **solo escritura**: nadie puede volver a leerla, ni siquiera tú. La lista la muestra como **Provider key · Anthropic** (o su proveedor) con los 4 últimos caracteres y, en una clave OpenAI-compatible, su URL base, que puedes cambiar con **Base URL**. **Replace value** cambia la clave para todos los runs que arranquen a partir de ese momento. `nan secrets ls` la muestra con **KIND** `provider_key`, su **PROVIDER** y su **BASE URL**. +Como todo secreto, una provider key es de **solo escritura**: nadie puede volver a leerla, ni siquiera tú. La lista la muestra como **Provider key · Anthropic** (o su proveedor) normalmente con sus 4 últimos caracteres y, en una clave OpenAI-compatible, su URL base, que puedes cambiar con **Base URL**. **Replace value** cambia la clave para todos los runs que arranquen a partir de ese momento. `nan secrets ls` la muestra con **KIND** `provider_key`, su **PROVIDER** y su **BASE URL**. **Reglas de la URL base.** NaN comprueba la URL base al guardarla y nunca se conecta a ella: solo lo hace el agente de tu workspace. @@ -208,7 +208,7 @@ nan run --byok-secret ANTHROPIC_KEY --model claude-sonnet-4-5 "arregla el test i `--byok-secret` recibe el **nombre** del secreto, nunca la clave, y necesita `--model`. `nan runs show ` muestra la elección en su línea **inference**, y `nan runs ls` en su columna **INFERENCE**. -**Desde la API**, añade un objeto `inference` y no mandes el `model` de primer nivel: +**Desde la API**, con una sesión iniciada (consulta [Quién puede usarla](#quién-puede-usarla)), añade un objeto `inference` y no mandes el `model` de primer nivel: ```json { @@ -225,11 +225,11 @@ El run que recibes dice lo mismo en su `inference` (`"source": "byok"`, el `prov - **Sin un plan con inferencia de NaN**, los runs y las automatizaciones con tu propia clave funcionan si tienes **un workspace propio activo**. Con un plan con inferencia, puedes elegir una u otra en cada run. - **Un run que lanzas tú con tu clave necesita una sesión iniciada**: el portal, o la CLI después de `nan auth login`. Una API key (`sk-...`) o un token de plataforma (`nan_pat_...`) recibe `403 session_required`, y `nan run` rechaza `NAN_TOKEN` y `--token-file` para estos runs. El motivo: el prompt de un run puede hacer que el agente imprima la clave que tiene, y esas credenciales no pueden leer tus secretos, así que tampoco deben poder conseguir que un agente revele uno. Sí pueden lanzar una [automatización que usa tu clave](/es/docs/automations#usa-tu-propia-clave-de-proveedor-byok): sus instrucciones son las que tú guardaste. -- Si **My own key** aparece desactivado con "isn't available for your account yet", todavía no está abierto para tu cuenta. +- Si **My own key** aparece desactivado con "isn't available for your account yet", se está abriendo poco a poco y tu cuenta todavía no lo tiene: mientras tanto usa la inferencia de NaN, o pregunta en `#support` en Discord. ### Facturación y uso -Tu proveedor factura todos los tokens de estos runs, en tu cuenta con él. NaN no los mide: nada cuenta para tu plan de NaN ni para su uso. Los tokens que muestra un run son los que ha informado el agente (Pi los informa, Hermes no); la referencia es la factura de tu proveedor. +Tu proveedor factura todos los tokens de estos runs, en tu cuenta con él. NaN no los mide: nada cuenta para tu plan de NaN ni para su uso. Los tokens que muestra un run son los que ha informado el agente (Pi y Pi + Gentle Shell los informan, Hermes no); la referencia es la factura de tu proveedor. ### Límites @@ -252,6 +252,7 @@ Tu proveedor factura todos los tokens de estos runs, en tu cuenta con él. NaN n | `404 not_found`, `param: inference.secret` | No tienes ningún secreto con ese nombre. Comprueba los nombres con `nan secrets ls`. | | `400 invalid_request`, `param: inference.secret` | Ese secreto es un secreto genérico, no una **Provider key**. Añade tu clave como provider key. | | `400 invalid_request`, `param: inference.model` | Falta el modelo, o no es un id de modelo (letras, dígitos y `. _ : / -`, hasta 128, sin empezar por `-`). | +| `400 invalid_request`, `param: model` | Un `model` de primer nivel enviado con `"source": "byok"`. Muévelo a `inference.model`. | | `400 invalid_request`, `param: inference` | Tu propia clave todavía no está disponible para tu cuenta, o el objeto `inference` está mal formado. | | `403 session_required` | Un run con tu clave lanzado con una API key o un token. Lánzalo con la sesión iniciada, o lanza una automatización. | | `403 tier_restricted` | Ni plan con inferencia de NaN ni un workspace propio activo. | diff --git a/src/content/docs-es/webhooks.mdx b/src/content/docs-es/webhooks.mdx index a71cbbb8..1967dce4 100644 --- a/src/content/docs-es/webhooks.mdx +++ b/src/content/docs-es/webhooks.mdx @@ -237,7 +237,7 @@ La pestaña **Deliveries** lista los últimos 50 webhooks que ha recibido la aut | **Ignored by a filter** | El filtro de eventos o de autores no ha coincidido o, con **Fresh clone per run**, la pull request venía de un fork o de un autor sin permiso de escritura. | | **Stopped: caused by one of its own runs** | Se ha parado un [bucle](#bucles). | | **Skipped: a run for the same subject is active** | Consulta [Cuando ya hay un run activo](/es/docs/automations#cuando-ya-hay-un-run-activo). | -| **Not accepted: your membership does not include runs** | No se ha podido crear el run: tu plan no tiene inferencia (o, en una automatización con [tu propia clave de proveedor](/es/docs/automations#usa-tu-propia-clave-de-proveedor-byok), no tienes un workspace activo), o el workspace está parado o borrado, su agente no está instalado o no tiene clave de inferencia. El emisor no lo reintenta, así que el evento se pierde: mantén el workspace encendido. | +| **Not accepted: your membership does not include runs** | No se ha podido crear el run: tu plan no tiene inferencia (o, en una automatización con [tu propia clave de proveedor](/es/docs/automations#usa-tu-propia-clave-de-proveedor-byok), no tienes un workspace activo), o el workspace está parado o borrado, su agente no está instalado o no tiene clave de inferencia (solo con la inferencia de NaN). El emisor no lo reintenta, así que el evento se pierde: mantén el workspace encendido. | | **Too large** | El evento no cabía en lo que puede recibir el agente. | No se guarda ningún payload ni ninguna cabecera. Las entregas con una firma incorrecta nunca se guardan, y las limitadas (límite de peticiones, cola llena, no disponible temporalmente) se responden pidiendo un reintento y no se listan. diff --git a/src/content/docs/automations.mdx b/src/content/docs/automations.mdx index c6e1029f..30fd1a59 100644 --- a/src/content/docs/automations.mdx +++ b/src/content/docs/automations.mdx @@ -62,7 +62,7 @@ The three GitHub templates act on a repository, so they need a **GitHub reposito | **Folder** | Where the agent works, inside `/home/nan`. Not used with **Fresh clone per run**. | | **Separate branch** | **On** (the default), **Auto** or **Off (work in the folder directly)**, as in a [run](/docs/runs#where-the-agent-works). Not used with **Fresh clone per run**. | | **Time limit (minutes)** | From 1 to 120. 30 by default. | -| **Inference** | **NaN (included in your plan)**, the default, or **My own key**: a **Provider key** of yours and the **Model**. See [Use your own provider key](#use-your-own-provider-key-byok). | +| **Inference** | **NaN (included in your plan)**, the default (**NaN (not in your plan)** when your plan has no inference), or **My own key**: a **Provider key** of yours and the **Model**. See [Use your own provider key](#use-your-own-provider-key-byok). | | **GitHub repository (optional)** | `owner/name`, for example `acme/api`. The repository the actions act on. | | **Checkout** | **Workspace folder** (the default): the agent works in **Folder**, in your workspace. **Fresh clone per run**: NaN clones the **GitHub repository** for each run, at the commit the event is about, and deletes the clone when the run ends. See [Fresh clone per run](#fresh-clone-per-run). | | **Run pull requests from forks and outside contributors** | Shown with **Fresh clone per run**. Off by default. See [Pull requests from forks and outside contributors](#pull-requests-from-forks-and-outside-contributors). | diff --git a/src/content/docs/pr-gatekeeper.mdx b/src/content/docs/pr-gatekeeper.mdx index bb00454b..9a5b1607 100644 --- a/src/content/docs/pr-gatekeeper.mdx +++ b/src/content/docs/pr-gatekeeper.mdx @@ -331,7 +331,7 @@ From the terminal, `nan automations deliveries ` shows the same list ## What it costs -Each review is a run in your workspace, and the tokens its agent uses count against your plan like any other request. Our reviews of a mid-sized Go repository take 8 to 20 minutes; the one in the screenshots used 430K input tokens and 5K output tokens. Most of it is the agent reading code and test output, so a small repository costs far less. The apply run uses no inference. +Each review is a run in your workspace, and the tokens its agent uses count against your plan like any other request (on your own provider key, your provider bills them instead). Our reviews of a mid-sized Go repository take 8 to 20 minutes; the one in the screenshots used 430K input tokens and 5K output tokens. Most of it is the agent reading code and test output, so a small repository costs far less. The apply run uses no inference. Some things keep it in check: diff --git a/src/content/docs/runs.mdx b/src/content/docs/runs.mdx index ff3250c0..0303447c 100644 --- a/src/content/docs/runs.mdx +++ b/src/content/docs/runs.mdx @@ -68,7 +68,7 @@ Press **New run** and fill in the form: | **Folder** | Where the agent works. It defaults to `/home/nan` and must be inside it. | | **Separate branch** | **Auto** (the default), **On** or **Off**. In a git repository, the agent works on its own branch and your checkout stays as it is. See [Where the agent works](#where-the-agent-works). | | **Time limit** | **30 min** by default, the same as the CLI and the API. The options are 15 min, 30 min, 1 hour and 2 hours. The run is stopped when it reaches it. | -| **Inference** | **NaN (included in your plan)**, the default, or **My own key**: a **Provider key** of yours and the **Model** to use. See [Use your own provider key](#use-your-own-provider-key-byok). | +| **Inference** | **NaN (included in your plan)**, the default (shown as **NaN (not in your plan)** when your plan has no inference), or **My own key**: a **Provider key** of yours and the **Model** to use. See [Use your own provider key](#use-your-own-provider-key-byok). | Press **Start run**, or `Ctrl+Enter` (`⌘+Enter` on a Mac) from the task box. @@ -176,14 +176,14 @@ Type the model id exactly as your provider names it. NaN does not check it again Open your settings on the portal, **Secrets**, and press **Add secret** (saving a secret needs you signed in through the browser). Fill in: +- **Name**: how runs and automations refer to it, for example `ANTHROPIC_KEY`. Capitals, digits and `_`, starting with a letter. - **Kind**: **Provider key**. - **Provider**: **Anthropic**, **OpenAI**, **Google Gemini** or **OpenAI-compatible**. It cannot be changed later: to switch providers, add another key. - **Base URL**, only for **OpenAI-compatible**: the root of the endpoint, usually ending in `/v1`, for example `https://api.together.xyz/v1`. See the rules below. - **Value**: the key, one line without spaces, 8 to 4096 characters. -- **Name**: how runs and automations refer to it, for example `ANTHROPIC_KEY`. Capitals, digits and `_`, starting with a letter. - **Description (optional)**. -Like every secret, a provider key is **write-only**: nobody can read it back, not even you. The list shows it as **Provider key · Anthropic** (or its provider) with the last 4 characters, and, for an OpenAI-compatible key, its base URL, which you can change with **Base URL**. **Replace value** changes the key for every run that starts from then on. `nan secrets ls` shows it with **KIND** `provider_key`, its **PROVIDER** and its **BASE URL**. +Like every secret, a provider key is **write-only**: nobody can read it back, not even you. The list shows it as **Provider key · Anthropic** (or its provider) usually with its last 4 characters, and, for an OpenAI-compatible key, its base URL, which you can change with **Base URL**. **Replace value** changes the key for every run that starts from then on. `nan secrets ls` shows it with **KIND** `provider_key`, its **PROVIDER** and its **BASE URL**. **Base URL rules.** NaN checks the base URL when you save it, and never connects to it: only the agent in your workspace does. @@ -208,7 +208,7 @@ nan run --byok-secret ANTHROPIC_KEY --model claude-sonnet-4-5 "fix the flaky tes `--byok-secret` takes the **name** of the secret, never the key itself, and needs `--model`. `nan runs show ` shows the choice on its **inference** line, and `nan runs ls` in its **INFERENCE** column. -**From the API**, add an `inference` object and leave the top-level `model` out: +**From the API**, with a signed-in session (see [Who can use it](#who-can-use-it)), add an `inference` object and leave the top-level `model` out: ```json { @@ -225,11 +225,11 @@ The run you get back says the same in its `inference` (`"source": "byok"`, the ` - **Without a NaN inference plan**, runs and automations on your own key work if you have **a workspace of your own that is active**. With a NaN inference plan, you can choose either for each run. - **A run you start yourself on your key needs a signed-in session**: the portal, or the CLI after `nan auth login`. An API key (`sk-...`) or a platform token (`nan_pat_...`) gets `403 session_required`, and `nan run` refuses `NAN_TOKEN` and `--token-file` for it. The reason: the prompt of a run can make the agent print the key it holds, and those credentials cannot read your secrets, so they must not be able to make an agent reveal one. They can still start an [automation that uses your key](/docs/automations#use-your-own-provider-key-byok): its instructions are the ones you saved. -- If **My own key** is disabled with "isn't available for your account yet", it is not open to your account yet. +- If **My own key** is disabled with "isn't available for your account yet", it is being opened gradually and your account does not have it yet: use NaN inference meanwhile, or ask in `#support` on Discord. ### Billing and usage -Your provider bills every token of these runs, under your account with them. NaN does not meter them: nothing counts against your NaN plan or its usage. The tokens a run shows are the ones the agent reported (Pi reports them, Hermes does not); your provider's bill is the reference. +Your provider bills every token of these runs, under your account with them. NaN does not meter them: nothing counts against your NaN plan or its usage. The tokens a run shows are the ones the agent reported (Pi and Pi + Gentle Shell report them, Hermes does not); your provider's bill is the reference. ### Limits @@ -252,6 +252,7 @@ Your provider bills every token of these runs, under your account with them. NaN | `404 not_found`, `param: inference.secret` | You have no secret with that name. Check the names with `nan secrets ls`. | | `400 invalid_request`, `param: inference.secret` | That secret is a generic secret, not a **Provider key**. Add your key as a provider key. | | `400 invalid_request`, `param: inference.model` | The model is missing, or it is not a model id (letters, digits and `. _ : / -`, up to 128, not starting with `-`). | +| `400 invalid_request`, `param: model` | A top-level `model` sent with `"source": "byok"`. Move it into `inference.model`. | | `400 invalid_request`, `param: inference` | Your own key is not available for your account yet, or the `inference` object is malformed. | | `403 session_required` | A run on your key with an API key or a token. Start it signed in, or run an automation instead. | | `403 tier_restricted` | No NaN inference plan and no active workspace of your own. | diff --git a/src/content/docs/webhooks.mdx b/src/content/docs/webhooks.mdx index 2567e678..47b0f5cd 100644 --- a/src/content/docs/webhooks.mdx +++ b/src/content/docs/webhooks.mdx @@ -237,7 +237,7 @@ The **Deliveries** tab lists the last 50 webhooks the automation received in the | **Ignored by a filter** | The event or actor filter did not match, or, with **Fresh clone per run**, the pull request came from a fork or an author without write access. | | **Stopped: caused by one of its own runs** | A [loop](#loops) was stopped. | | **Skipped: a run for the same subject is active** | See [When a run is already active](/docs/automations#when-a-run-is-already-active). | -| **Not accepted: your membership does not include runs** | The run could not be created: your plan has no inference (or, for an automation on [your own provider key](/docs/automations#use-your-own-provider-key-byok), you have no active workspace), or the workspace is stopped or deleted, its agent is not installed or it has no inference key. The sender does not retry it, so the event is lost: keep the workspace running. | +| **Not accepted: your membership does not include runs** | The run could not be created: your plan has no inference (or, for an automation on [your own provider key](/docs/automations#use-your-own-provider-key-byok), you have no active workspace), or the workspace is stopped or deleted, its agent is not installed or it has no inference key (NaN inference only). The sender does not retry it, so the event is lost: keep the workspace running. | | **Too large** | The event did not fit in what the agent can receive. | No payload and no headers are kept. Deliveries with a bad signature are never stored, and throttled ones (rate limit, full queue, temporarily unavailable) are answered with a retry and not listed. diff --git a/src/data/openapi.json b/src/data/openapi.json index e98293d6..d2e8807c 100644 --- a/src/data/openapi.json +++ b/src/data/openapi.json @@ -1614,7 +1614,7 @@ "platformToken": [] } ], - "description": "Gives an agent a task in one of your workspaces. The run is queued and starts as soon as there is a free place: a workspace runs 1 (Micro and the free workspace), 2 (Nano), 3 (Basic), 5 (Medium) or 8 (Large) runs at once, never more than your per-member total: at most 5 runs working across all your workspaces (8 on Premium). Extra runs wait, first in, first out.\n\nLimits: up to 20 queued runs per workspace and 50 in total (`429 run_queue_full`), and 60 new runs per rolling hour (`429 rate_limited`).\n\nWith NaN inference (the default), creating runs needs a plan with inference (`403 tier_restricted`): the run uses the workspace's inference key and counts against your plan's usage. With your own provider key (`inference.source: byok`) a plan with inference is not needed, an active workspace of yours is enough; your provider bills the run, NaN does not meter it, and these runs need a signed-in session (`403 session_required` for API keys and platform tokens). Up to 8 runs on your own keys work at once across your workspaces, on top of the workspace's own limit. The workspace must be `running` and have the agent installed. Guide: [Use your own provider key](https://nan.builders/docs/runs#use-your-own-provider-key-byok).\n\n**Idempotency.** Send an `Idempotency-Key` header to make retries safe: the same key with the same body returns the run already created (`200`), the same key with a different body answers `409 idempotency_conflict`.\n\nAuthentication: a member API key (`sk-...`) or a platform token (`nan_pat_...`) with the `runs` scope.", + "description": "Gives an agent a task in one of your workspaces. The run is queued and starts as soon as there is a free place: a workspace runs 1 (Micro and the free workspace), 2 (Nano), 3 (Basic), 5 (Medium) or 8 (Large) runs at once, never more than your per-member total: at most 5 runs working across all your workspaces (8 on Premium). Extra runs wait, first in, first out.\n\nLimits: up to 20 queued runs per workspace and 50 in total (`429 run_queue_full`), and 60 new runs per rolling hour (`429 rate_limited`).\n\nWith NaN inference (the default), creating runs needs a plan with inference (`403 tier_restricted`): the run uses the workspace's inference key and counts against your plan's usage. With your own provider key (`inference.source: byok`) a plan with inference is not needed, an active workspace of yours is enough; your provider bills the run, NaN does not meter it, and these runs need a signed-in session (`403 session_required` for API keys and platform tokens). Up to 8 runs on your own keys work at once across your workspaces, on top of the workspace's own limit. The workspace must be `running` and have the agent installed. Guide: [Use your own provider key](https://nan.builders/docs/runs#use-your-own-provider-key-byok).\n\n**Idempotency.** Send an `Idempotency-Key` header to make retries safe: the same key with the same body returns the run already created (`200`), the same key with a different body answers `409 idempotency_conflict`.\n\nAuthentication: a member API key (`sk-...`) or a platform token (`nan_pat_...`) with the `runs` scope (not for `inference.source: byok`, which needs a signed-in session).", "parameters": [ { "name": "Idempotency-Key", @@ -3507,7 +3507,7 @@ } }, "400": { - "description": "Invalid body: `input` not a JSON object, too large (48 KiB) or nested too deep (8 levels); unknown fields; a bad `Idempotency-Key`.", + "description": "Invalid body: `input` not a JSON object, too large (48 KiB) or nested too deep (8 levels); unknown fields; a bad `Idempotency-Key`. On an automation that uses your own key, `invalid_request` with `param: \"inference\"` when your own key is not available for your account.", "content": { "application/json": { "schema": {