Saltar al contenido principal

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ónObligatoriaNotas
getBootstrapAssertionDevuelve la aserción, de forma síncrona o asíncrona.
externalAgentRefRef pública del agente al que vincularse.
apiBase / apiBaseUrlURL base del runtime. Se aceptan ambas formas.
storageAdaptador de persistencia de sesión. Por defecto, en memoria. Ver Sesiones y almacenamiento.
storageKeyClave bajo la que se guarda el registro de sesión.
transport / fetchSustituye la capa HTTP, p. ej. para añadir cabeceras de trazado.
onErrorSe 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,
}
Solo primitivos — los valores anidados se rechazan

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:

ReglaFallo
El contexto de host debe estar habilitado (maxTotalBytes > 0)403 host_context_not_allowed
Cada clave debe figurar en acceptedFields400 host_context_field_not_allowed
Los valores deben ser primitivos, y las cadenas no deben parecer refs internas400 host_context_field_not_public
El tamaño JSON total debe caber en maxTotalBytes400 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);

Ver Políticas y capacidades.

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.