{"name":"AI SDK API","version":"2.1.0","authentication":{"description":"Depende del provider seleccionado","openrouter":{"headers":{"x-openrouter-api-key":"Tu API key de OpenRouter","Authorization":"Alternativa: Bearer <tu-api-key>"}},"bedrock":{"headers":{"x-aws-region":"Región AWS (recomendado para sobreescribir por request)","x-aws-access-key-id":"Access key AWS (opcional)","x-aws-secret-access-key":"Secret key AWS (opcional)","x-aws-session-token":"Session token AWS (opcional)","x-aws-bearer-token-bedrock":"Bearer token Bedrock (opcional)","x-bedrock-api-key":"Alias de x-aws-bearer-token-bedrock (opcional)","Authorization":"Alternativa para bearer: Bearer <token>"},"environment":{"AWS_REGION":"Región AWS (requerido)","AWS_ACCESS_KEY_ID":"Opcional si usas provider chain","AWS_SECRET_ACCESS_KEY":"Opcional si usas provider chain","AWS_SESSION_TOKEN":"Opcional","AWS_BEARER_TOKEN_BEDROCK":"Opcional (Bearer auth alternativa)"}}},"environment":{"REQUEST_TIMEOUT":"number (opcional, segundos) - Timeout total de POST /generate. Default: 1200 (20 minutos)","API_BASE_URL":"string (opcional) - URL base pública para webhooks de respuesta async de tools","REDIS_URL":"string (opcional) - Redis para coordinar esperas async y stops entre réplicas. Ver toolAsyncGuide.redis","DEBUG_MODEL_RESPONSE":"boolean (opcional) - 'true' loguea la respuesta cruda del modelo","LOG_LEVEL":"string (opcional) - debug | info | warn | error. Default: info. Controla verbosidad de logs estructurados"},"endpoints":{"POST /generate":{"description":"Genera texto usando AI SDK con OpenRouter o Bedrock","headers":{"x-openrouter-api-key":"string (requerido solo para provider=openrouter)","x-aws-region":"string (requerido para bedrock si no hay AWS_REGION)","x-aws-access-key-id":"string (opcional, bedrock)","x-aws-secret-access-key":"string (opcional, bedrock)","x-aws-session-token":"string (opcional, bedrock)","x-aws-bearer-token-bedrock":"string (opcional, bedrock)"},"body":{"provider":"string (opcional) - 'openrouter' (default) | 'bedrock'","model":"string | string[] (requerido) - Modelo principal. Array = fallbacks OpenRouter. Con fusion, actua como juez salvo fusion.judge_model","prompt":"string (opcional) - Prompt simple","system":"string (opcional) - Mensaje de sistema","messages":"array (opcional) - Historial de mensajes","config":{"temperature":"number (0-2)","maxTokens":"number","topP":"number (0-1)","frequencyPenalty":"number","presencePenalty":"number","stopSequences":"string[]"},"tools":"array (opcional) - Tools disponibles [{name, description, parameters}]","toolsWebhook":"string (requerido si tools) - URL del webhook para ejecutar tools","messagesWebhook":"string (opcional) - URL que recibe mensajes intermedios de cada step (assistant, tool-calls, tool results)","toolContext":"object (opcional) - Contexto extra que recibirán toolsWebhook y messagesWebhook","maxToolSteps":"number (default: 5) - Máximo de iteraciones de tools","stopId":"string (opcional) - ID para detener esta generación con POST /generate-stop/:stopId. Si se omite y hay messagesWebhook, se genera automáticamente","providerOptions":"object (opcional) - Opciones del proveedor, ej: { openrouter: { parallel_tool_calls: false } }","output":{"type":"string - text | object | array | choice | json","name":"string (opcional)","description":"string (opcional)","schema":"object (requerido para object/array) - JSON Schema","options":"string[] (requerido para choice, mínimo 2)"},"fusion":{"analysis_models":"string[] (opcional, 1-8) - Panel que debate en paralelo. Omítelo para usar el preset Quality de OpenRouter","judge_model":"string (opcional, avanzado) - Juez distinto al model principal. Casi nunca hace falta","max_tool_calls":"number (opcional, 1-16) - Pasos de tool-calling internos","max_completion_tokens":"number (opcional) - Tokens máximos por llamada interna","tool_choice":"string (opcional) - 'required' fuerza la invocación de fusion en cada request"}}},"GET /health":{"description":"Verifica el estado de la API"},"POST /webhook-response/:id":{"description":"Recibe la respuesta async de una tool cuando su webhook devolvió _wait_response: true. También puede interrumpir la espera con _interrupted_mode","body":{"normal":"any - Resultado de la tool (mismo formato que respuesta síncrona del webhook)","interrupt":{"_interrupted_mode":"boolean - true detiene /generate sin que el asistente procese la respuesta","_fallback":"object (opcional) - tool-result a guardar en messages; si se omite, usa el _fallback de la respuesta inicial"}},"errors":{"410":"La espera ya expiró o fue interrumpida"}},"POST /generate-stop/:stopId":{"description":"Detiene una generación activa registrada con stopId en POST /generate. Durante step solo de mensaje: queued hasta la próxima tool. En espera async (_wait_response): interrumpe al instante con _fallback. Con webhook síncrono en curso: applied pero la tool termina con su resultado real; /generate se detiene al cerrar el step (messages = resultados reales + fallbacks de async canceladas)","body":{"_fallback":"object (requerido) - tool-result para tools en espera async; ignorado si la tool ya está ejecutando el webhook síncrono (se conserva el resultado real)"},"response":{"ok":"boolean","stop_id":"string","applied":"boolean - true si había tool en curso o espera async (el efecto depende de la fase)","queued":"boolean - true si el stop quedó pendiente (step de mensaje en curso)"},"errors":{"404":"No hay generación activa con ese stopId"}}},"examples":{"simple":{"provider":"openrouter","model":"openai/gpt-4o-mini","prompt":"Explica qué es TypeScript en una oración"},"withModelFallbacks":{"provider":"openrouter","model":["anthropic/claude-sonnet-4","openai/gpt-4o","google/gemini-2.5-pro-preview"],"prompt":"Explica qué es TypeScript en una oración"},"withFusion":{"provider":"openrouter","model":"anthropic/claude-sonnet-4","fusion":{"analysis_models":["anthropic/claude-opus-4","openai/gpt-4o","google/gemini-2.5-pro-preview"]},"prompt":"Compara los argumentos a favor y en contra de un impuesto al carbono. ¿Dónde discrepan los expertos?"},"withFusionAlias":{"provider":"openrouter","model":"openrouter/fusion","prompt":"Compara los argumentos a favor y en contra de un impuesto al carbono. ¿Dónde discrepan los expertos?"},"withBedrock":{"provider":"bedrock","model":"us.anthropic.claude-3-5-sonnet-20241022-v2:0","prompt":"Explica qué es TypeScript en una oración"},"withTools":{"provider":"openrouter","model":"openai/gpt-4o","system":"Eres un asistente que puede consultar el clima y buscar información.","messages":[{"role":"user","content":"¿Qué clima hace en Madrid?"}],"tools":[{"name":"get_weather","description":"Obtiene el clima actual de una ciudad","parameters":{"type":"object","properties":{"city":{"type":"string","description":"Nombre de la ciudad"},"unit":{"type":"string","enum":["celsius","fahrenheit"],"description":"Unidad de temperatura"}},"required":["city"]}}],"toolsWebhook":"https://tu-servidor.com/webhook/tools","maxToolSteps":3},"multipleTools":{"provider":"openrouter","model":"anthropic/claude-3.5-sonnet","messages":[{"role":"user","content":"Busca hoteles en París y dime el clima"}],"tools":[{"name":"search_hotels","description":"Busca hoteles en una ciudad","parameters":{"type":"object","properties":{"city":{"type":"string"},"checkIn":{"type":"string","description":"Fecha YYYY-MM-DD"},"checkOut":{"type":"string","description":"Fecha YYYY-MM-DD"}},"required":["city"]}},{"name":"get_weather","description":"Obtiene el clima","parameters":{"type":"object","properties":{"city":{"type":"string"}},"required":["city"]}}],"toolsWebhook":"https://tu-servidor.com/webhook/tools"}},"messagesWebhookFormat":{"description":"POST antes de ejecutar toolsWebhook. Recibe cada step del asistente; si incluye tool-calls, la API espera la respuesta del webhook antes de invocar las tools. Errores del webhook no bloquean la generación.","timing":"Se dispara justo después de que el modelo responde y antes de ejecutar las tools de ese step","body":{"step":"number - Índice del step (0-based)","messages":"array - Mensaje assistant del step (texto intermedio + tool-calls, con usage y provider_data)","finish_reason":"string - Razón de fin del step (ej: tool-calls, stop)","stop_id":"string - stopId del request o generado automáticamente si se omitió; usar con POST /generate-stop/:stopId","context":"object (opcional) - Mismo toolContext del request","timestamp":"string - ISO timestamp"},"response":"any - Respuesta del webhook (se consume pero no altera el flujo). Si falla o timeout, se continúa igual","example":{"step":0,"finish_reason":"tool-calls","stop_id":"sess_abc","context":{"sessionId":"sess_abc"},"timestamp":"2024-01-15T10:30:00.000Z","messages":[{"role":"assistant","content":[{"type":"text","text":"Encontré varias opciones..."},{"type":"tool-call","toolCallId":"call_abc123","toolName":"mostrar_opciones","input":{"body":"¿Qué presentación prefiere?","opciones":["a","b"]}}],"usage":{"totalTokens":18240},"provider_data":{"id":"gen-1","modelId":"minimax/minimax-m3"}}]}},"webhookFormat":{"request":{"description":"Lo que tu webhook recibirá","body":{"tool":"string - Nombre de la tool a ejecutar","arguments":"object - Argumentos de la tool","context":"object - Contexto extra enviado en toolContext","timestamp":"string - ISO timestamp","response_id":"string - ID único de la llamada (toolCallId); usar al responder","response_webhook":"string - URL POST de esta API donde enviar el resultado async"}},"asyncResponse":{"description":"Respuesta inmediata del webhook para operaciones que tardan. La API espera el resultado real en response_webhook.","body":{"_wait_response":"boolean - true para indicar que la respuesta llegará después","_timeout":"number (opcional, default 30) - Segundos máximos de espera","_fallback":"object (opcional) - Resultado si expira el timeout. Con _interrupted_mode es obligatorio","_interrupted_mode":"boolean (opcional) - Si true, al expirar el timeout detiene /generate sin que el asistente procese la respuesta. El _fallback se guarda como tool-result en messages. Para continuar, el cliente reenvía messages + un nuevo mensaje user"},"callback":{"method":"POST","url":"{API_BASE_URL}/webhook-response/{response_id}","body":"any - Resultado normal, o { _interrupted_mode: true, _fallback?: {...} } para interrumpir con otro fallback. Rechazado (410) si la espera ya expiró"},"interruptCallback":{"_interrupted_mode":true,"_fallback":{"status":"cancelled","reason":"usuario canceló"}}},"response":{"description":"Lo que tu webhook debe retornar","body":"any - El resultado será pasado al modelo. Opcionalmente puede incluir _metadata (objeto) que no verá el agente pero se devolverá en tools_metadata del endpoint /generate"},"metadata":{"description":"Campo opcional _metadata en la respuesta del webhook","behavior":"Se elimina antes de enviar el resultado al modelo. Si está presente, aparece en tools_metadata de la respuesta de /generate","format":{"tool_name":"string - Nombre de la tool ejecutada","tool_id":"string - ID de la llamada a la tool (toolCallId)","metadata":"object - Valor del campo _metadata devuelto por el webhook"}},"example":{"request":{"tool":"get_weather","arguments":{"city":"Madrid","unit":"celsius"},"context":{"userId":"user_123","sessionId":"sess_abc"},"timestamp":"2024-01-15T10:30:00.000Z","response_id":"call_abc123","response_webhook":"https://tu-api.com/webhook-response/call_abc123"},"immediateResponse":{"_wait_response":true,"_timeout":30,"_interrupted_mode":true,"_fallback":{"status":"pending","timed_out":true}},"interruptedGenerateResponse":{"interrupted":true,"finishReason":"tool-wait-timeout","messages":"array - Solo mensajes nuevos de este /generate (sin el historial enviado). En interrupción: assistant + tool-results del turno; el último suele ser tool","resume":"Concatenar historial previo + messages de la respuesta + nuevo { role: 'user', content: '...' } en el siguiente /generate"},"asyncCallback":{"temperature":15,"condition":"soleado","humidity":45},"response":{"temperature":15,"condition":"soleado","humidity":45,"_metadata":{"source":"openweather","requestId":"req_abc123"}}}},"toolAsyncGuide":{"overview":"Las tools pueden responder de forma síncrona o pedir espera async con _wait_response. En modo async la API bloquea /generate hasta recibir POST /webhook-response/:id o hasta que expire _timeout. El modo interrumpido detiene /generate sin que el asistente procese el resultado, pero guarda el historial completo (incluido tool-result) para reanudar después.","environment":{"API_BASE_URL":"Requerido para async: genera response_webhook en el POST al toolsWebhook","REDIS_URL":"Recomendado con varias instancias o load balancer. Sin esto el estado vive en memoria del proceso (una sola réplica). Ej: redis://localhost:6379"},"redis":{"overview":"Redis guarda el estado efímero de tools async e interrupciones para que cualquier réplica de la API pueda resolver callbacks y stops, no solo la que atendió POST /generate.","stores":["Esperas de POST /webhook-response/:id — abierto/cerrado, respuesta early y resultado del callback","Stops de POST /generate-stop/:stopId — registro activo, fase tool y _fallback pendiente"],"whenRequired":"Obligatorio si despliegas más de una instancia detrás de un balanceador. Si el callback o el stop llegan a otra réplica que la que inició /generate, sin Redis fallan (404/410 o stop not_found).","withoutRedis":"Una sola instancia: funciona igual con memoria local. No hace falta Redis en desarrollo salvo que quieras probar el flujo distribuido.","localDev":"docker compose up levanta redis + api con REDIS_URL=redis://redis:6379. Solo Redis: docker compose up redis -d y REDIS_URL=redis://localhost:6379 en .env"},"fallbackRule":"Un resultado real nunca se descarta. _fallback solo reemplaza tools detenidas antes de tener resultado real: espera async cancelada (stop, timeout interrumpido, callback con _interrupted_mode) o stop en cola aplicado antes de ejecutar el webhook. Webhook síncrono que ya respondió siempre conserva su resultado en messages.","controlFields":{"_wait_response":"true — activa espera async","_timeout":"Segundos máximos de espera (default 30)","_fallback":"Resultado si expira el timeout. Obligatorio si usas _interrupted_mode en la respuesta inicial","_interrupted_mode":"En respuesta inicial: interrumpe al expirar timeout. En POST /webhook-response/:id: interrumpe manualmente"},"fallbackPriorityOnInterrupt":["_fallback del POST /webhook-response/:id (si se envía)","_fallback de la respuesta inicial del toolsWebhook","Resto del body del callback sin campos de control"],"closedWebhook":"Tras timeout, interrupción o callback exitoso, el response_id queda cerrado. POST tardíos reciben HTTP 410.","useCases":[{"id":"sync","title":"1. Respuesta síncrona","description":"El toolsWebhook devuelve el resultado directamente. El agente lo recibe y continúa en el mismo /generate.","webhookResponse":{"temperature":15,"condition":"soleado"}},{"id":"async-normal","title":"2. Async con callback normal","description":"El toolsWebhook responde con _wait_response. Tu backend procesa en background y envía el resultado real a POST /webhook-response/{response_id}. El agente continúa con ese resultado.","webhookResponse":{"_wait_response":true,"_timeout":120},"callback":{"temperature":15,"condition":"soleado"}},{"id":"timeout-fallback","title":"3. Timeout con _fallback (el agente sigue)","description":"Si no llega el callback a tiempo, se usa _fallback como resultado de la tool y el asistente sigue procesándolo en el mismo /generate.","webhookResponse":{"_wait_response":true,"_timeout":30,"_fallback":{"status":"pending","timed_out":true}}},{"id":"timeout-interrupted","title":"4. Timeout interrumpido (_interrupted_mode en respuesta inicial)","description":"Al expirar _timeout, /generate termina con interrupted: true. El _fallback se guarda como tool-result en messages pero el asistente NO lo procesa. El webhook de espera queda cerrado (410).","webhookResponse":{"_wait_response":true,"_timeout":30,"_interrupted_mode":true,"_fallback":{"status":"pending","timed_out":true}},"generateResponse":{"interrupted":true,"finishReason":"tool-wait-timeout","text":"","messages":[{"role":"user","content":"¿Qué clima hace en Madrid?"},{"role":"assistant","content":[{"type":"text","text":"Voy a consultar el clima."},{"type":"tool-call","toolCallId":"call_abc123","toolName":"get_weather","input":{"city":"Madrid"}}]},{"role":"tool","content":[{"type":"tool-result","toolCallId":"call_abc123","toolName":"get_weather","output":{"status":"pending","timed_out":true}}]}]}},{"id":"callback-interrupt","title":"5. Interrupción manual vía POST /webhook-response/:id","description":"Mientras la tool espera, puedes interrumpir enviando _interrupted_mode al callback. Puedes pasar un _fallback distinto al de la respuesta inicial. Mismo efecto que timeout interrumpido: historial completo, asistente detenido, id cerrado.","webhookResponse":{"_wait_response":true,"_timeout":120,"_fallback":{"status":"pending"}},"callback":{"_interrupted_mode":true,"_fallback":{"status":"cancelled","reason":"usuario canceló"}}},{"id":"generate-stop","title":"6. Stop general vía POST /generate-stop/:stopId","description":"Incluye stopId en POST /generate. Step solo de mensaje: queued. Espera async: _fallback al instante. Webhook síncrono en curso: deja terminar con resultado real y detiene /generate al cerrar el step (sin otro turno del asistente).","generateRequest":{"stopId":"sess_abc","model":"openai/gpt-4o","messages":[{"role":"user","content":"Consulta el clima"}],"tools":[{"name":"get_weather","description":"...","parameters":{}}],"toolsWebhook":"https://tu-servidor.com/webhook/tools"},"stopCallback":{"_fallback":{"status":"stopped","reason":"usuario canceló"}},"generateResponse":{"interrupted":true,"finishReason":"generation-stopped","messages":"Historial del step: tool-results reales de webhooks síncronos + _fallback en async canceladas"}},{"id":"resume","title":"7. Reanudar conversación","description":"Tras una interrupción, concatena tu historial previo + messages de la respuesta + un nuevo mensaje user. Puedes incluir usage y provider_data tal cual: la API los quita al llamar al modelo y normaliza tool-result.","generateRequest":{"model":"openai/gpt-4o","messages":[{"role":"user","content":"¿Qué clima hace en Madrid?"},{"role":"assistant","content":[{"type":"tool-call","toolCallId":"call_abc123","toolName":"get_weather","input":{"city":"Madrid"}}]},{"role":"tool","content":[{"type":"tool-result","toolCallId":"call_abc123","toolName":"get_weather","output":{"status":"pending","timed_out":true}}]},{"role":"user","content":"Ya tengo el dato, continúa"}],"tools":[{"name":"get_weather","description":"...","parameters":{}}],"toolsWebhook":"https://tu-servidor.com/webhook/tools"}}]}}