Primeros pasos
Instalación
npm install @agentshelf/external-agents-sdk
El paquete incluye ESM (import), CJS (require) y declaraciones de TypeScript.
Depende de @agentshelf/agent-runtime-sdk-core, que aporta las primitivas
compartidas de transporte, streaming y errores.
La aserción de arranque
Antes de que el SDK pueda abrir una sesión necesita una aserción de arranque:
una cadena corta, generada y firmada por tu backend, que da fe del usuario final
actual. El SDK no la crea ni la firma — solo la solicita, mediante el callback
getBootstrapAssertion que tú proporcionas.
navegador ──▶ tu backend ──▶ aserción firmada
│ │
└───── SDK ──▶ AgentShelf ◀───┘
El endpoint de tu backend es la frontera de confianza. Autentica al usuario allí y genera la aserción. Nunca envíes al cliente la credencial que la firma.
// Tu servidor, p. ej. GET /api/agentshelf/bootstrap-assertion
app.get('/api/agentshelf/bootstrap-assertion', requireLogin, async (req, res) => {
res.type('text/plain').send(await mintAgentShelfAssertion(req.user));
});
Crear un cliente
import { createExternalAgentClient } from '@agentshelf/external-agents-sdk';
const client = createExternalAgentClient({
externalAgentRef: 'ext_agent_public_12345678',
apiBase: 'https://api.agentshelf.ai',
getBootstrapAssertion: async () => {
const response = await fetch('/api/agentshelf/bootstrap-assertion');
if (!response.ok) {
throw new Error('No se pudo obtener una aserción de arranque');
}
return response.text();
},
});
Opciones
| Opción | Obligatoria | Notas |
|---|---|---|
getBootstrapAssertion | sí | Devuelve la aserción, de forma síncrona o asíncrona. |
externalAgentRef | — | Ref pública del agente al que vincularse. |
apiBase / apiBaseUrl | — | URL base del runtime. Se aceptan ambas formas. |
storage | — | Adaptador de persistencia de sesión. Por defecto, en memoria. Ver Sesiones y almacenamiento. |
storageKey | — | Clave bajo la que se guarda el registro de sesión. |
transport / fetch | — | Sustituye la capa HTTP, p. ej. para añadir cabeceras de trazado. |
onError | — | Se invoca con un RuntimeSdkError en cada llamada fallida. |
Tipo completo: ExternalAgentClientOptions.
Abrir una sesión
ensure() obtiene una aserción, crea una sesión pública y guarda el token de
sesión. Si ya existe una sesión válida almacenada, la reutiliza y nunca llama a
getBootstrapAssertion.
const result = await client.ensure({
hostContext: {
page: 'Checkout',
},
});
if (result.kind === 'created') {
console.log(result.policy.defaultStreamProfile);
}
console.log(result.session.sessionRef);
ensure() devuelve una unión discriminada — kind: 'stored' solo trae la
sesión; kind: 'created' trae además la política recién negociada. Discrimina
por kind antes de acceder a policy. Pasa forceRefresh: true para descartar
la sesión almacenada y generar una nueva.
Contexto del host
hostContext le indica al agente dónde se está usando. Para agentes externos es
un mapa plano de primitivos: cadena, número o booleano.
hostContext: {
page: 'Checkout',
cartTotal: 129.5,
isReturningCustomer: true,
}
La firma de TypeScript es RuntimeHostContext | JsonObject, y la variante
RuntimeHostContext describe una forma por campo más rica
({ type, value, sourceTrustLevel, persistencePolicy, modelVisibilityPolicy })
que usan otros runtimes de AgentShelf. La API pública de agentes externos no
la acepta.
El servidor conserva únicamente valores string | number | boolean y rechaza la
petición si se descartó algo, así que un objeto anidado { type, value } falla
con 400 host_context_field_not_public aunque compile. Envía primitivos planos.
Cuatro reglas que aplica el servidor, todas contra policy.hostContextPolicy:
| Regla | Fallo |
|---|---|
El contexto de host debe estar habilitado (maxTotalBytes > 0) | 403 host_context_not_allowed |
Cada clave debe figurar en acceptedFields | 400 host_context_field_not_allowed |
| Los valores deben ser primitivos, y las cadenas no deben parecer refs internas | 400 host_context_field_not_public |
El tamaño JSON total debe caber en maxTotalBytes | 400 host_context_too_large |
Los nombres de clave deben cumplir ^[a-zA-Z0-9_.:-]{1,80}$. Las cadenas también
se filtran por contenido de aspecto interno — UUID en crudo, URIs gs:// o
s3://, y subcadenas como bucket o /workspaces/ — y un valor detectado por
ese filtro provoca el mismo error host_context_field_not_public.
Consulta la lista de campos aceptados antes de enviar nada:
const { hostContextPolicy } = await client.getEffectivePolicy();
console.log(hostContextPolicy.acceptedFields, hostContextPolicy.maxTotalBytes);
Enviar un primer mensaje
const conversation = await client.createConversation({ title: 'Soporte de checkout' });
const stream = await client.streamMessage({
conversationRef: conversation.conversationRef,
content: '¿Por qué se rechazó mi tarjeta?',
});
for await (const event of stream) {
if (event.type === 'message:delta') {
process.stdout.write(event.delta);
}
}
Consulta Conversaciones y streaming para el
conjunto completo de eventos y para collectAssistantMessage, que reduce un
stream a un único mensaje cuando no necesitas renderizado token a token.
Manejo de errores
Todo fallo se expone como un RuntimeSdkError con un code estable, un status
HTTP opcional y un indicador retryable:
import { RuntimeSdkError } from '@agentshelf/agent-runtime-sdk-core';
try {
await client.ensure();
} catch (error) {
if (error instanceof RuntimeSdkError && error.code === 'session_expired') {
await client.clearStoredSession();
}
}
Entre los códigos están unauthorized, forbidden, not_found,
rate_limited, timeout, aborted, conflict, session_expired,
capability_unsupported, policy_denied, stream_interrupted,
network_error y server_error. El detalle del mensaje depende de la política
publicErrorDetail del agente.