Skip to content

Referencia API

La lista completa de todo lo que el sandbox expone: globales, componentes, cada campo y método en useYumina(), definiciones de tipos y los reemplazos para las APIs de navegador bloqueadas.

Esta es documentación de referencia, no un tutorial. Lee primero la Guía de UI personalizada para obtener la imagen general; ven aquí para buscar firmas específicas.

Todo en esta página se deriva de la implementación real en packages/app/sandbox/, por lo que coincide con la versión del sandbox enviada con el editor.


Globales del sandbox

Estos nombres están disponibles en todas partes de tu árbol de root component sin sentencia de import:

NombreTipoQué es
ReactmóduloReact completo (useState, useEffect, useRef, useMemo, useCallback, useLayoutEffect, Fragment, ...)
useYuminahookSDK de la plataforma — consulta SDK useYumina()
useAssetFonthookCarga una fuente personalizada desde la biblioteca de Recursos — consulta useAssetFont()
IconsobjetoMás de 1400 componentes de iconos Lucide: <Icons.Heart />, <Icons.Sword />. Catálogo completo: https://lucide.dev/icons
ChatcomponenteBloque de construcción completo de chat — consulta <Chat>
MessageListcomponenteMensajes sin entrada — consulta <MessageList>
MessageInputcomponenteSolo barra de entrada — consulta <MessageInput>
ChatCanvascomponenteAlias heredado de <Chat /> — consulta <ChatCanvas>
exports, moduleobjetoFallback de exportación estilo CJS; normalmente los ignoras

NO importes React ni ninguno de los nombres anteriores: son inyectados por el sandbox. Escribir import React from "react" se elimina silenciosamente en tiempo de compilación pero es redundante.

Tus propios archivos SÍ pueden importarse: los root components multi-archivo usan sintaxis de módulo ES: import StatBar from "./stat-bar". Las extensiones .tsx, .ts, .jsx, .js pueden omitirse.


SDK useYumina()

Llámalo dentro de tu función de componente:

tsx
function MyWorld() {
  const api = useYumina()
  // api.variables, api.sendMessage(...), ...
}

Superficie completa, agrupada por propósito:

Lecturas de estado (síncronas)

Lee el último estado del juego. El componente se re-renderiza cada vez que cualquiera de estos cambia.

CampoTipoSignificado
variablesRecord<string, unknown>Variables del juego con alcance de Sesión. Ejemplo: { health: 80, gold: 150 }
globalVariablesRecord<string, unknown>Variables globales compartidas entre todas las Sesiones
worldNamestringNombre del Mundo actual
worldIdstringUUID del Mundo actual
sessionIdstringUUID de la Sesión de juego actual
currentUser{ id, name?, image? } | nullCuenta cruda: id, nombre visible, avatar de cuenta. null cuando no hay sesión iniciada. Úsalo para UI a nivel de cuenta como "ver perfil". Para renderizado de roleplay dentro del Mundo, usa user en su lugar
user{ name: string; avatar: string | null }El jugador interpretado: misma ramificación persona-vs-cuenta que la macro . Cuando el jugador tiene una persona activa, user.name es el nombre de la persona y user.avatar es el avatar de la persona; de lo contrario recurre a la cuenta. Esto es lo que quieres para burbujas de chat dentro del Mundo, Tarjetas de personaje, paneles de perfil
mode"session" | "guest-preview""session" es una Sesión de juego real. "guest-preview" es una vista previa de hub sin sesión iniciada: las acciones que mutan el estado son no-ops y muestran un prompt de inicio de sesión al padre
capabilities{ canSendMessage, canPersistSession, canUseSessionApis, requiresAuth }Lo que permite el mode actual. Léelo para deshabilitar botones que serían no-ops (por ejemplo, el botón Enviar en vista previa de invitado), o para renderizar un CTA inline "Inicia sesión para continuar"
languagestringCódigo de idioma i18n activo del host ("en", "zh", ...). Úsalo para elegir traducciones dentro de la Tarjeta sin depender de la instancia i18next del host
messagesArray<Record<string, unknown>>Historial completo de mensajes — consulta SandboxMessage
isStreamingbooleantrue mientras la IA está generando una respuesta
streamingContentstringTexto de streaming en vivo de la IA (se actualiza frecuentemente)
streamingReasoningstringTexto en vivo de "pensamiento" / razonamiento de la IA (solo para Modelos de razonamiento)
pendingChoicesstring[]Etiquetas de botones de elección emitidas por reglas
errorstring | nullMensaje de error actual (fallo de API, error de generación) o null
readOnlybooleantrue al ver la Sesión de otra persona — <Chat /> oculta la entrada automáticamente
checkpointsArray<Checkpoint>Puntos de control guardados — consulta Checkpoint
greetingContentstring | nullTexto de saludo calculado a partir de las Entradas del Mundo (usado por <Chat /> como contenido de estado vacío)
canvasMode"chat" | "custom" | "fullscreen"Modo de canvas actual
selectedModelstringID del Modelo de IA actualmente seleccionado
userPlanstringPlan de suscripción del usuario ("free", "go", "plus", "pro", "ultra", "internal")
preferredProvider"official" | "private"API oficial vs. clave propia del usuario
entriesReadonlyArray<SandboxEntry>Entradas del Lorebook del Mundo: solo las habilitadas, ordenadas por position. Consulta Búsquedas del Lorebook y SandboxEntry

Acciones de juego (fire-and-forget)

Estos métodos no devuelven nada; solo envían la intención a la aplicación padre.

MétodoQué hace
sendMessage(text)Envía un mensaje como jugador, activando una respuesta de IA
setVariable(id, value, options?)Establece una Variable. options: { scope?: string; targetUserId?: string }. scope elige el alcance de la Variable (para global/personal), targetUserId te permite escribir Variables para un jugador específico en multijugador
executeAction(actionId)Dispara una acción nombrada definida por el motor de reglas (ej. "attackBoss")
switchGreeting(index)Cambia a una variante de saludo diferente por índice
clearPendingChoices()Descarta los botones de elección pendientes sin elegir uno
setComposerDraft(text)Suelta text en el compositor de chat y lo enfoca. No envía. Úsalo cuando quieras que el jugador revise o edite el mensaje antes de presionar Enviar (ej. un botón de interacción con NPC que prepara un iniciador de conversación). Local al sandbox — sin ida y vuelta con el padre — así que solo funciona junto con los componentes <MessageInput> / <Chat> incluidos

Control de chat

Todo lo que puede hacer la barra de chat por defecto, expuesto para que tu UI personalizada también pueda hacerlo.

MétodoQué hace
editMessage(messageId, content)Edita un mensaje existente. Devuelve Promise<boolean>; true en éxito
deleteMessage(messageId)Elimina un mensaje. Devuelve Promise<boolean>
regenerateMessage(messageId)Pide a la IA que regenere la respuesta dada (fire-and-forget)
continueLastMessage()Continúa generando desde el último mensaje de IA (fire-and-forget)
stopGeneration()Interrumpe el stream actual (fire-and-forget)
restartChat()Limpia todos los mensajes, restablece el estado, empieza de nuevo
swipeMessage(messageId, "left" | "right")Cambia entre alternativas de IA (swipes) para un mensaje. Devuelve Promise<Record<string, unknown>>

Sesiones y ramificación

MétodoQué hace
revertToMessage(messageId)Rebobina la conversación a justo antes de messageId. Devuelve Promise<void>
branchFromMessage(messageId)Bifurca una nueva Sesión en el mensaje dado (clona los mensajes hasta él inclusive más la instantánea de estado). Devuelve Promise<string | null> — nuevo ID de Sesión, o null en caso de fallo (mientras se hace streaming, salas multijugador, mensajes faltantes, todo falla)
getBranchContext()Obtiene el slice de rama actual (self, parent, siblings, children). Devuelve Promise<BranchContext>. Se vuelve a obtener cada llamada; sin caché de cliente. Consulta BranchContext
createSession(worldId)Inicia una nueva Sesión para un Mundo. Devuelve Promise<string> con el nuevo ID de Sesión
deleteSession(sessionId)Elimina una Sesión. Devuelve Promise<void>
listSessions(worldId)Lista todas las Sesiones de un Mundo. Devuelve Promise<Array<Record<string, unknown>>>

Puntos de control

Un punto de control es una instantánea con nombre dentro de la Sesión actual a la que puedes rebobinar.

MétodoQué hace
saveCheckpoint()Guarda el estado actual de la Sesión como un nuevo punto de control. Devuelve Promise<void> (el campo checkpoints se actualiza después)
loadCheckpoints()Pide al padre que refresque el array checkpoints. Devuelve Promise<void>
restoreCheckpoint(checkpointId)Restaura la Sesión a un punto de control guardado. Devuelve Promise<void>
deleteCheckpoint(checkpointId)Elimina un punto de control. Devuelve Promise<void>

Audio

MétodoQué hace
playAudio(trackId, opts?)Reproduce una pista de audio definida en las Entradas. opts: { volume?, fadeDuration?, chainTo?, maxDuration?, duckBgm?, loop? } — las duraciones están en segundos; chainTo elige el siguiente trackId a reproducir; duckBgm baja el BGM durante la reproducción; loop sobrescribe el ajuste loop de la pista solo para esta reproducción
stopAudio(trackId?, fadeDuration?)Detiene una pista (omite trackId para detener todo). fadeDuration en segundos. Destruye el elemento — usa pauseAudio si quieres reanudar desde la misma posición
pauseAudio(trackId)Pausa una pista en el sitio, conservando su posición de reproducción
resumeAudio(trackId)Reanuda una pista pausada con pauseAudio
onAudioEnded(cb)Suscríbete a "una pista no en bucle terminó de reproducirse" — cb(trackId). Devuelve una función para cancelar la suscripción. Úsalo para avanzar automáticamente una lista de reproducción personalizada
setAudioVolume(type, volume)type es "bgm" o "sfx", volume es 0-1
getAudioVolume(type)Devuelve síncronamente el volumen actual (0-1)

UI / navegación

MétodoQué hace
toggleImmersive()Alterna el modo inmersivo (pantalla completa)
openPersonaManager()Abre el gestor de personas del jugador — cambiar / crear / editar personas sin salir del mundo (el mismo panel que abre el menú "+" del compositor). La persona activa es global de la cuenta; un cambio surte efecto en el siguiente mensaje
copyToClipboard(text)Copia al portapapeles (reemplaza navigator.clipboard.writeText)
navigate(path)Pide al padre que rute a una ruta como "/app/hub" (reemplaza window.location = ...)
showToast(message, type?)Muestra un toast en la UI del padre. type: "success", "error", "info" (por defecto)

Almacenamiento persistente (por Mundo)

Reemplazo de localStorage. Con alcance por worldId; los Mundos no pueden leer las claves de los demás.

MétodoQué hace
storage.get(key)Lee. Devuelve Promise<string | null>
storage.set(key, value)Escribe (solo strings). Devuelve Promise<void>
storage.remove(key)Elimina. Devuelve Promise<void>

¿Necesitas datos complejos? JSON.stringify / JSON.parse al entrar/salir.

Búsquedas del Lorebook

Acceso de solo lectura al Lorebook del Mundo desde dentro de tu Tarjeta. Útil para inspeccionar o seleccionar manualmente Entradas al ensamblar un prompt de LLM lateral, construir un visor de diario dentro del juego o conectar un panel de depuración.

Campo / métodoQué hace
entriesReadonlyArray<SandboxEntry> — cada Entrada habilitada, ya ordenada por position. Consulta SandboxEntry
getEntry(name)Busca una Entrada por nombre exacto. Devuelve SandboxEntry | null. En localhost, una búsqueda fallida registra una advertencia única con los nombres disponibles: útil cuando renombras una Entrada y olvidas actualizar la Tarjeta

Para la mayoría de los casos no necesitas tocar estos directamente: pasa includeLorebook: "matched" a ai.complete() y el servidor ensambla el lore por ti (consulta más abajo). Recurre a entries / getEntry cuando necesites control quirúrgico — por ejemplo, "este NPC solo conoce Entradas etiquetadas con tavern".

Completaciones crudas de IA

Llama al LLM fuera de la tubería principal del chat. Úsalo para "monólogo interno de NPC en un panel lateral", "descripciones de elementos generadas por IA", "chats telefónicos dentro de la Tarjeta", etc. No escribe en el historial de mensajes, no activa actualizaciones de estado, no consume saludos.

tsx
const api = useYumina()
const text = await api.ai.complete({
  messages: [
    { role: "system", content: "You are a surly merchant." },
    { role: "user", content: "Price me an iron sword." },
  ],
  onDelta: (chunk) => setStreaming((s) => s + chunk),  // optional, per-token
  model: "claude-sonnet-4-6",                           // optional, defaults to selectedModel
  maxTokens: 500,                                       // optional, default 2048, max 8192
  temperature: 0.7,                                     // optional
  includeLorebook: "matched",                           // optional — see below
})

Devuelve Promise<string> con la respuesta completa. Timeout del lado del cliente de 120 segundos.

Límites y costos

LímiteValorFuente
Máx. mensajes por llamada50El servidor rechaza con HTTP 400
Máx. contenido total50.000 caracteres entre todos los mensajesEl servidor rechaza con HTTP 400
maxTokens por defecto2048Por defecto cuando se omite
maxTokens techo8192Los valores mayores se recortan silenciosamente
Rango temperature0-2, por defecto 1.0Los valores fuera de rango se recortan
Modelo por defectoEl selectedModel del jugador, recurriendo a anthropic/claude-sonnet-4.6 si ni model ni selectedModel están establecidos
Límite de tasaCompartido con el chat principal — las llamadas laterales y los turnos del chat principal cuentan contra el mismo presupuesto por minutoDevuelve HTTP 429 + código estilo INSUFFICIENT_CREDITS en desbordamiento
CréditosMisma facturación por token que el chat principal. Los usuarios BYOK omiten la deducción de créditos del servidor pero aún pagan a su propio proveedorRegistrado con endpoint "side-completion"
AutenticaciónLa Sesión debe pertenecer al jugador actual; de lo contrario, la llamada falla con HTTP 404

includeLorebook — auto-inyectar lore del Mundo

Las llamadas laterales eluden el ensamblaje del prompt del chat principal, por lo que el Modelo no tiene idea de quiénes son tus personajes a menos que le des el Lorebook. Pasa includeLorebook y el servidor antepone un mensaje del sistema construido a partir de las Entradas del Mundo:

ValorComportamiento
omitido / falseSin inyección (por defecto). Úsalo para traducciones, resúmenes, clasificación: cualquier cosa que no necesite contexto del Mundo
true / "all"Inyecta cada Entrada habilitada no-greeting, ordenada por position. Predecible, mayor costo de tokens
"matched"Ejecuta el mismo matcher de palabras clave que usa el chat principal contra el último mensaje del usuario en messages. Las Entradas alwaysSend siempre se incluyen; las Entradas activadas por palabras clave se añaden solo cuando son relevantes. Recomendado para llamadas laterales en personaje

Sin esto, una llamada lateral "en personaje" tiene al Modelo fabricando personalidades solo a partir de los nombres: la persona dentro de la Tarjeta se aleja del chat principal. Con "matched", un chat telefónico con un NPC ve el mismo lore del Mundo + perfil de personaje que ve el chat principal.

tsx
// A phone chat that stays in canon
api.ai.complete({
  messages: [
    { role: "system", content: "Stay strictly in character as Balder. Reply in one or two short lines." },
    ...history,
    { role: "user", content: userText },
  ],
  includeLorebook: "matched",  // server pulls Balder's profile + relevant world lore
})

Si necesitas control más fino —inyectar una Entrada específica por nombre, o solo Entradas con una Etiqueta específica— itera sobre api.entries y ensambla el mensaje del sistema tú mismo en lugar de usar includeLorebook:

tsx
const tavernLore = api.entries
  .filter((e) => e.tags?.includes("tavern"))
  .map((e) => `【${e.name}】\n${e.content}`)
  .join("\n\n")

api.ai.complete({
  messages: [
    { role: "system", content: `You are the tavern keeper.\n\n${tavernLore}` },
    { role: "user", content: userText },
  ],
})

Advertencias del modo "matched": solo escanea el último mensaje del usuario en busca de palabras clave (no todo el historial), y las Entradas con compuerta condicional que dependen de Variables del juego no se disparan en llamadas laterales (el matcher ve un stub de estado vacío). Usa true para forzar la inclusión de todo si la precisión importa más que los tokens.

Inyección de contexto

Inyecta un mensaje de contexto de un solo uso en el próximo turno de IA del chat principal. Se consume después de un uso; no se crea ningún mensaje de chat visible. Genial para "mensajes telefónicos", "diálogo de NPC fuera de escena", "cambios ambientales": cosas que la IA principal debería saber pero que el jugador no debería ver como una burbuja de chat.

tsx
api.injectContext("You just received a cryptic text: 'Tonight, 9pm, usual place.'", { role: "system" })
// En el próximo mensaje del jugador, la IA principal verá esto como un mensaje de sistema.

options: { role?: "system" \| "user" } (por defecto "system").

Selector de modelo

Campo / métodoQué hace
selectedModelID del Modelo actual
userPlanNivel de plan del usuario
preferredProvider"official" o "private"
setModel(modelId)Cambia de Modelo (fire-and-forget)
getModels()Devuelve Promise<{ models, pinnedModels, recentlyUsed }> donde models es Array<{ id, name, provider, contextLength }>
pinModel(modelId) / unpinModel(modelId)Fija / desfija un Modelo

Recursos

MétodoQué hace
resolveAssetUrl(ref)Convierte una referencia @asset:xxx en una URL de CDN. Transformación pura de string, sin red. Las URLs HTTP/HTTPS pasan sin cambios

Markdown

MétodoQué hace
renderMarkdown(text)Convierte markdown en HTML seguro (entidades HTML escapadas, etiquetas peligrosas eliminadas, formato preservado). Pasa el resultado a dangerouslySetInnerHTML dentro de una burbuja personalizada y estás seguro — consulta el ejemplo a continuación
tsx
<div dangerouslySetInnerHTML={{ __html: api.renderMarkdown(msg.rawContent) }} />

Componentes

<Chat>

La experiencia completa de chat de la plataforma. Este es el bloque de construcción cotidiano: cero props te da el chat por defecto.

Incluye: lista de mensajes, auto-scroll, cursor de streaming, controles de swipe, acciones de mensaje (editar/eliminar/regenerar), barra de entrada, botones de elección, selector de Modelo, modo de solo lectura, placeholder de saludo.

tsx
<Chat renderBubble={(msg) => <MyBubble {...msg} />} />

Props

PropTipoDescripción
renderBubble?(props: BubbleProps) => ReactNodePersonaliza cómo se ve cada burbuja de mensaje. Recurre al renderizado de markdown por defecto si se omite
className?stringClase CSS extra en el contenedor exterior
children?ReactNodeContenido renderizado arriba de la lista de mensajes (ej. una cabecera HUD fija)

BubbleProps

El objeto msg que tu callback renderBubble recibe:

CampoTipoSignificado
contentHtmlstringHTML seguro pre-renderizado (markdown ya convertido). Normalmente canalizado a dangerouslySetInnerHTML
rawContentstringTexto markdown crudo antes del renderizado (texto de Directiva incluido)
role"user" | "assistant" | "system"Origen del mensaje
messageIndexnumberPosición en la lista (0 = primero, normalmente el saludo)
isStreamingbooleantrue mientras este mensaje se está streameando
stateSnapshotRecord<string, unknown> | nullEstado del juego en el momento en que se generó este mensaje (útil para "cuáles eran HP/location entonces")
variablesRecord<string, unknown>Variables actuales (más recientes) del juego
renderMarkdown(text) => stringHelper: convierte cualquier texto markdown en HTML seguro

<MessageList>

Solo el stream de mensajes (con scroll, cursor de streaming, controles de swipe). Sin barra de entrada.

tsx
<MessageList />

No acepta renderBubble: para personalizar burbujas usa <Chat renderBubble={...} />, o salta <MessageList> por completo y lee api.messages directamente (el patrón de novela visual).

<MessageInput>

Solo la barra de entrada (con selector de Modelo, botones de elección, menú continuar/reiniciar, estado de streaming).

tsx
<MessageInput />

Se oculta automáticamente cuando api.readOnly es true.

<ChatCanvas>

Alias heredado: idéntico a <Chat />. Los Mundos antiguos siguen funcionando; el código nuevo debería preferir <Chat />.


useAssetFont()

Carga un Recurso de fuente subido como un @font-face y obtén de vuelta un string listo para soltar en un valor font-family de CSS.

tsx
const fontFamily = useAssetFont("@asset:my-font-id", {
  family: "Cinzel",
  fallback: "serif",
})
return <div style={{ fontFamily }}>Ancient runes</div>

Firma

ts
useAssetFont(
  assetRef: string | null | undefined,
  options?: AssetFontOptions
): string

La fuente se carga asíncronamente. Mientras carga, el hook devuelve options.fallback (por defecto "serif"); cuando está lista, se dispara un re-renderizado con el string completo de family (con sufijo para evitar choques de nombres).

AssetFontOptions

CampoTipoDescripción
family?stringNombre de la familia de fuentes. Inferido del nombre de archivo o assetRef si se omite
fallback?stringFuente de respaldo mostrada durante la carga. Por defecto "serif"
filename?string | nullNombre de archivo original, usado para adivinar el formato
mimeType?string | nullTipo MIME, usado para adivinar el formato
format?"opentype" | "truetype" | "woff" | "woff2" | nullAnulación explícita del formato
weight?string | numberfont-weight
style?stringfont-style (ej. "italic")
stretch?stringfont-stretch
display?FontDisplayfont-display (por defecto "swap")

Tipos

SandboxMessage

Forma de cada Entrada en api.messages:

ts
interface SandboxMessage {
  id: string
  sessionId: string
  role: "user" | "assistant" | "system"
  content: string
  status?: "complete" | "streaming" | "failed"
  errorMessage?: string | null
  authorUserId?: string | null          // quién lo envió (multijugador)
  authorNameSnapshot?: string | null    // su nombre visible en el momento del envío
  stateChanges?: Record<string, unknown> | null   // diff de actualizaciones de variables de este mensaje
  stateSnapshot?: Record<string, unknown> | null  // estado completo en el momento de la generación del mensaje
  swipes?: Array<{ content, stateSnapshot }>      // respuestas alternativas de la IA
  activeSwipeIndex?: number
  model?: string | null
  tokenCount?: number | null
  generationTimeMs?: number | null
  compacted?: boolean                   // oculto en la sección "older messages"
  attachments?: Array<{ type, mimeType, name, url }> | null
  createdAt: string                     // ISO-8601
}

Checkpoint

ts
interface Checkpoint {
  id: string
  name: string
  messageCount: number
  createdAt: string   // ISO-8601
}

SandboxEntry

Una sola Entrada de Lorebook de solo lectura, expuesta mediante api.entries y api.getEntry():

ts
interface SandboxEntry {
  id: string
  name: string
  content: string
  keywords: string[]
  position: number
  section: "system-presets" | "examples" | "chat-history" | "post-history"
  enabled: boolean
  role: string                            // "system" | "character" | "lore" | etc.
  tags?: string[]
}

Esta es una vista delgada del WorldEntry interno del motor: solo los campos que una Tarjeta necesita para el ensamblaje del prompt. El tiempo de ejecución pre-filtra las Entradas deshabilitadas y pre-ordena por position, así que las Tarjetas nunca necesitan hacer ninguna de las dos cosas ellas mismas.

BranchContext

ts
interface BranchNode {
  id: string
  name: string | null
  parentSessionId: string | null
  branchedFromMessageId: string | null
  messageCount: number
  updatedAt: string   // ISO-8601
  createdAt: string   // ISO-8601
}

interface BranchContext {
  current: BranchNode          // la sesión en la que estás
  parent: BranchNode | null    // la rama de la que te separaste, o null en la raíz
  siblings: BranchNode[]       // otras ramas separadas del mismo padre, las más antiguas primero
  children: BranchNode[]       // ramas separadas de `current`, las más antiguas primero
}

APIs de navegador bloqueadas

Tu código se ejecuta dentro de un iframe sandbox="allow-scripts" de origen cruzado sin allow-same-origin. Eso significa:

  • Sin acceso a cookies / localStorage de la aplicación padre
  • Sin peticiones de red con credenciales
  • Sin manipulación directa de window.parent

Las siguientes APIs están completamente bloqueadas o redirigidas de forma transparente mediante el puente del SDK.

Redirecciones (el código heredado sigue funcionando)

Lo que escribisteLo que realmente sucede
fetch('/api/...')Proxied mediante el fetch autenticado del padre
fetch('/cdn/...')Permitido (la CSP lo permite)
fetch('any other URL')Rechazado (lanza error)
localStorage.getItem/setItem/removeItem/clearEnrutado mediante api.storage, con alcance por Mundo
sessionStorage.*Igual
navigator.clipboard.writeText()Equivalente a api.copyToClipboard()
navigator.clipboard.readText() / read() / write()Rechazado (lanza error)
window.location.pathname / href / assign / replaceObjeto sintético; pathname siempre es /app/chat/{sessionId}; asignar / llamar assign / replace activa la navegación
window.location.reload()Enlazado para recargar la Sesión
window.__yuminaToggleImmersive()Equivalente a api.toggleImmersive()

Uso preferido

Al escribir código nuevo, usa el SDK directamente: las redirecciones existen para Mundos antiguos, pero el SDK es más limpio y estable:

No escribasEscribe
fetch('/api/sessions', { method: 'POST' })api.createSession(worldId)
fetch('/api/sessions/' + sid, { method: 'DELETE' })api.deleteSession(sid)
localStorage.getItem("k")await api.storage.get("k")
window.location = "/app/hub"api.navigate("/app/hub")
navigator.clipboard.writeText(t)api.copyToClipboard(t)

APIs de navegador que SÍ están disponibles

El sandbox es permisivo con cualquier cosa que no alcance la red o el origen compartido. Las siguientes funcionan como en cualquier navegador, sin necesidad de wrapper del SDK:

APIUso típico en Tarjetas
<input type="file"> + FileReader.readAsDataURL / readAsTextDeja que el jugador elija un archivo de imagen/audio/texto → almacénalo como URL de datos o string en una Variable. Consulta Receta: Imágenes subidas por el jugador
URL.createObjectURL / revokeObjectURLGenera una URL temporal en memoria para un Blob (ej. vista previa antes de guardar)
<canvas> + getContext("2d") + toDataURL / toBlobRedimensiona, recorta o compone imágenes antes de guardarlas en una Variable
<img>, <audio>, <video>Renderiza URLs de origen local, URLs @asset:... resueltas, URLs data:/blob:
IntersectionObserver, ResizeObserver, matchMedia, requestAnimationFramePrimitivas estándar de diseño / animación
crypto.randomUUID, crypto.subtleHashing y generación de IDs para estado del lado del cliente
WebAudio (AudioContext)Síntesis o análisis de audio ligero
Notification, navigator.vibrate, screen.orientationLimitado por permisos a nivel del navegador, no por el sandbox en sí

De un vistazo: toda la API

Una tabla, escanea una vez.

useYumina()
├── State reads
│   ├── variables, globalVariables, personalVariables, roomPersonalVariables
│   ├── worldName, worldId, sessionId
│   ├── currentUser (account), user (persona-aware)
│   ├── room, mode, capabilities, language
│   ├── messages, permissions, entries
│   ├── isStreaming, streamingContent, streamingReasoning
│   ├── pendingChoices, error, readOnly, greetingContent, canvasMode
│   ├── checkpoints
│   └── selectedModel, userPlan, preferredProvider
├── Game actions
│   ├── sendMessage(text)
│   ├── setVariable(id, value, options?)
│   ├── executeAction(actionId)
│   ├── switchGreeting(index)
│   ├── clearPendingChoices()
│   └── setComposerDraft(text)              // prefill, no send
├── Chat control
│   ├── editMessage(id, content) → Promise<boolean>
│   ├── deleteMessage(id) → Promise<boolean>
│   ├── regenerateMessage(id)
│   ├── continueLastMessage()
│   ├── stopGeneration()
│   ├── restartChat()
│   └── swipeMessage(id, direction) → Promise
├── Sessions / branching
│   ├── revertToMessage(id) → Promise<void>
│   ├── branchFromMessage(id) → Promise<string | null>
│   ├── getBranchContext() → Promise<BranchContext>
│   ├── createSession(worldId) → Promise<string>
│   ├── deleteSession(id) → Promise<void>
│   └── listSessions(worldId) → Promise<Array>
├── Checkpoints
│   ├── saveCheckpoint() → Promise<void>
│   ├── loadCheckpoints() → Promise<void>
│   ├── restoreCheckpoint(id) → Promise<void>
│   └── deleteCheckpoint(id) → Promise<void>
├── Audio
│   ├── playAudio(trackId, opts?)
│   ├── stopAudio(trackId?, fadeDuration?)
│   ├── pauseAudio(trackId)
│   ├── resumeAudio(trackId)
│   ├── onAudioEnded(cb) → unsubscribe
│   ├── setAudioVolume(type, volume)
│   └── getAudioVolume(type) → number
├── UI / navigation
│   ├── toggleImmersive()
│   ├── openPersonaManager()
│   ├── copyToClipboard(text)
│   ├── navigate(path)
│   └── showToast(message, type?)
├── Storage
│   ├── storage.get(key) → Promise<string | null>
│   ├── storage.set(key, value) → Promise<void>
│   └── storage.remove(key) → Promise<void>
├── Lorebook
│   ├── entries (ReadonlyArray<SandboxEntry>)  // sorted by position, enabled only
│   └── getEntry(name) → SandboxEntry | null
├── AI
│   └── ai.complete({ messages, onDelta?, model?, maxTokens?, temperature?, includeLorebook? }) → Promise<string>
│        // includeLorebook: true | "all" | "matched" — auto-inject world lore
├── Context injection
│   └── injectContext(message, { role? })
├── Model picker
│   ├── setModel(modelId)
│   ├── getModels() → Promise<{ models, pinnedModels, recentlyUsed }>
│   ├── pinModel(id), unpinModel(id)
├── Assets
│   └── resolveAssetUrl(ref) → string
└── Markdown
    └── renderMarkdown(text) → string   // safe HTML

Sandbox globals (no import)
├── React
├── useYumina, useAssetFont
├── Icons  (1400+ Lucide icons)
├── Chat, MessageList, MessageInput, ChatCanvas (legacy alias)
└── Tailwind utility classes (CSS-level)

Blocked / redirected
├── fetch('/api/...') → proxied
├── localStorage / sessionStorage → api.storage
├── window.location → synthetic + navigate
└── navigator.clipboard → copyToClipboard

Browser APIs that work as-is
├── <input type="file"> + FileReader      // player file uploads → data URL
├── <canvas>, URL.createObjectURL          // image processing
├── IntersectionObserver, ResizeObserver, matchMedia, rAF
├── crypto.randomUUID, crypto.subtle
└── WebAudio (AudioContext)

Siguiente: vuelve a la Guía de UI personalizada para ejemplos elaborados, o navega las Recetas para plantillas más cercanas a lo que estás construyendo.