Publicado 23 sept 20269 min de lectura
El AI Connect SDK: da a tus agentes de IA los conectores de cada usuario como herramientas, una línea por framework
El impuesto de integración de los productos de IA está en el cableado, no en el modelo. El AI Connect SDK de Vinkius lo elimina: un cliente TypeScript de cero dependencias, handles con alcance por usuario y nueve adaptadores de framework. Cómo funciona, el código mínimo y un asistente multiusuario funcional, listo para entregar.

Por Renato Marinho
Founder · Vinkius
La pregunta que más recibo sobre un producto de IA no va sobre el modelo. Va sobre el cableado. ¿Cómo lee tu asistente el GitHub del propio usuario? ¿Publica en su Slack? ¿Saca un registro de su CRM? En la mayoría de los productos la respuesta es que un equipo gastó meses construyendo y manteniendo una estantería de integraciones: los apretones de manos OAuth, la rotación de tokens, las formas de error por proveedor, los reintentos, la deriva silenciosa de esquema en el momento en que una API de arriba cambia. Nada de eso hace el producto más inteligente. Está entre el modelo y lo que el usuario de verdad quería, y sigue pidiendo presupuesto.
Ese impuesto es lo que elimina el AI Connect SDK de Vinkius. Es la parte de Vinkius de la que hablo menos en las llamadas de ventas y más con los ingenieros, porque ahí vive la palanca real: un cliente TypeScript pequeño que convierte los servicios conectados de un usuario en herramientas que tu modelo puede llamar, sin que tu equipo posea una sola credencial de arriba.
El SDK en una línea
@vinkius/connect es un cliente TypeScript de cero dependencias en tiempo de ejecución para la plataforma de conectividad de Vinkius. Tu aplicación se identifica con un par de claves que creas una vez en el dashboard de Vinkius: un app id público y una clave de aplicación secreta. Después diriges a tus propios usuarios finales con el identificador que ya les asignas en tu sistema, y para cada uno el SDK te entrega un handle con alcance hacia los conectores que conectó. Vinkius provisiona la conexión, guarda las credenciales y ejecuta las capacidades detrás de un plano de datos gobernado. Tu código no porta ninguna API key de arriba, ningún flujo OAuth, ningún bucle de reintento para una API de terceros. Describes el trabajo en el propio dialecto de herramientas de tu modelo, y un adaptador lo enruta al conector correcto para ese usuario.
Esa frase es toda la propuesta de valor. El resto de este post es la prueba.
El modelo: una app, muchos usuarios, con alcance por usuario
La decisión de diseño que defendería con más fuerza es que la unidad de alcance es el usuario, no el inquilino. Un asistente de chat que puede actuar sobre el calendario de alguien solo es seguro cuando el calendario que puede tocar es el que esa persona conectó, y nada más. El SDK lo codifica directamente: vinkius.user("yourUserId") construye un handle perezoso que hace cero llamadas de red y nunca resuelve ni almacena un id interno de usuario. Dirige a la plataforma con tu id externo, hasta el final de la ejecución.
El conector de cada usuario es una conexión aislada con su propio token. Listar y ejecutar capacidades ocurre en el tiempo de ejecución de esa conexión, por lo que el GitHub conectado por un usuario nunca puede alcanzar el de otro. Cada conexión se mide y puede apagarse de forma independiente, y un token revocado falla cerrado en lugar de volver a acuñarse en silencio. Cuando un usuario descarta un servicio, la capacidad desaparece del conjunto de herramientas de ese usuario en el momento en que la siguiente solicitud la lista. El aislamiento no es una promesa en los documentos. Es la forma del camino de datos.
El código, en tres movimientos
Este es el flujo de conexión y credenciales, tomado de los propios ejemplos del SDK. Conectas el conector GitHub de un usuario, lees el esquema que espera, escribes el token y compruebas la disponibilidad:
import { Vinkius } from '@vinkius/connect';
const vinkius = new Vinkius({
appId: process.env.VINKIUS_APP_ID!,
apiKey: process.env.VINKIUS_APP_KEY!,
});
async function connectGithub(userId: string, githubToken: string) {
const github = vinkius.user(userId).connector('github');
await github.connect();
const schema = await github.credentials.schema();
await github.credentials.set({ GITHUB_TOKEN: githubToken });
const status = await github.status();
console.log(`github status=${status} requires=${Object.keys(schema).join(',')}`);
}
status regresa como una de cuatro palabras: not_connected, needs_credentials, ready o disabled. Las credenciales son solo de escritura. credentials.schema() y credentials.status() te dicen qué claves existen y cuáles están puestas, nunca los valores, y el único camino que recorre un secreto es hacia adentro, una vez, hacia la bóveda.
El segundo movimiento es la agregación. Una llamada devuelve las capacidades ejecutables sobre todos los conectores listos del usuario, ya con espacio de nombres para que dos conectores que traen una herramienta create no choquen:
const caps = await vinkius.user('alice_123').capabilities();
// caps is a CapabilitySet: an array of executable Capability objects
El tercer movimiento es el adaptador. Esta es la línea que paga la plataforma, porque es la misma forma de código no importa qué modelo ejecutes:
import { toOpenAITools, runOpenAIToolCall } from '@vinkius/connect/openai';
const tools = toOpenAITools(caps);
// pass tools to your model, then dispatch each call back:
const result = await runOpenAIToolCall(caps, call, { idempotencyKey: `alice_123:${call.id}` });
Cambia el modelo y cambias el import. Hay nueve adaptadores de subruta para los principales frameworks de modelo, de los SDKs de OpenAI y Anthropic a LangChain, LlamaIndex, el Vercel AI SDK, OpenAI Agents y Cloudflare Workers AI, más un despachador JSON Schema neutral de framework para el resto. Cada uno es un conversor de tipos estructurales de cero dependencias. Convierte capacidades de Vinkius al dialecto de herramientas de tu modelo y de vuelta, y deliberadamente no importa el SDK del proveedor, así que añadir Vinkius a tu proyecto no trae nada que ya no estés usando.
Un asistente multiusuario funcional
Esta es la implementación que entregaría. Es un manejador de chat que responde por un usuario usando los propios conectores de ese usuario, con el bucle completo de herramientas: el modelo pide una herramienta, la ejecutamos en el tiempo de ejecución gobernado, devolvemos el resultado, y ponemos un tope al bucle para que un modelo mal comportado no te facture para siempre.
import OpenAI from 'openai';
import { Vinkius } from '@vinkius/connect';
import { toOpenAITools, runOpenAIToolCall } from '@vinkius/connect/openai';
const openai = new OpenAI();
const vinkius = new Vinkius({
appId: process.env.VINKIUS_APP_ID!,
apiKey: process.env.VINKIUS_APP_KEY!,
});
// Any model your chosen provider serves. The integration is model agnostic:
// nothing below depends on a specific model name.
const MODEL = process.env.OPENAI_MODEL ?? 'your-model-id';
export async function assistantFor(userId: string, question: string) {
// 1. What can this user's connectors actually do, right now?
const caps = await vinkius.user(userId).capabilities();
if (caps.length === 0) {
return 'You have not connected a service yet. Connect one in the app and I can act on it.';
}
// 2. Hand the model this user's capabilities as its own tools.
const messages = [{ role: 'user', content: question }];
const tools = toOpenAITools(caps);
// 3. The tool loop. The model picks, we execute at the runtime, we feed the result back.
let reply;
for (let step = 0; step < 4; step++) {
reply = await openai.chat.completions.create({
model: MODEL,
messages,
tools,
});
const msg = reply.choices[0]?.message;
if (!msg || !msg.tool_calls || msg.tool_calls.length === 0) break;
messages.push(msg);
for (const call of msg.tool_calls) {
// Tool level failures come back as isError results, not throws.
// Transport level failures throw the SDK's typed error classes.
const result = await runOpenAIToolCall(caps, call, {
idempotencyKey: `${userId}:${call.id}`,
});
const text = result.content.map((c) => c.text).join('\n');
messages.push({
role: 'tool',
tool_call_id: call.id,
content: result.isError ? `The tool reported an error: ${text}` : text,
});
}
}
return reply?.choices[0]?.message?.content ?? '';
}
Cuatro cosas en ese bloque merecen señalarse, porque ahí es donde una versión ingenua se equivoca en silencio.
Primero, la lista de capacidades se trae por solicitud, por usuario, y solo de conectores que están listos. Un usuario que conecta un servicio nuevo lo tiene en el siguiente turno sin un despliegue, y un usuario que revoca uno pierde las herramientas correspondientes al mismo tiempo. No hay nada cacheado de forma que sobreviva a la relación de confianza.
Segundo, idempotencyKey en la llamada de ejecución es lo que hace el reintento seguro. El SDK reintenta respuestas transitorias, pero un POST que acuña un efecto secundario solo se reintenta cuando declaras la clave, porque entonces un reenvío se desduplica del lado del servidor en lugar de duplicarse. La clave de arriba lleva espacio de nombres por usuario y por id de llamada de herramienta, así que dos usuarios actuando sobre el mismo conector nunca chocan.
Tercero, el tope del bucle. Un agente que lee un resultado de herramienta, decide que no es suficiente y llama la herramienta siguiente es cómo el producto hace trabajo útil. También es cómo un modelo desconectado puede caminar por mucho tiempo. Poner tope a los pasos acota la factura y el radio de daño, y la respuesta en texto plano del modelo en el último turno es lo que devuelves.
Cuarto, la división de errores. Una capacidad que falla a nivel de herramienta (la API que envuelve devolvió un error) regresa como un resultado con isError: true. El modelo lo lee, y puede recuperarse, reintentar otra herramienta, o explicar el fallo al usuario. Un fallo de transporte o de autenticación lanza una de las clases de error tipadas del SDK. Dos formas de fallo, dos manejadores distintos, y ninguno derriba el bucle entero.
Ejecútalo en otro framework y solo cambia la línea del adaptador. Para Anthropic es toAnthropicTools y runAnthropicToolUse. Para LangChain inyectas la fábrica de tool del framework para que el SDK no traiga ninguna dependencia asociada. El lado de capacidades es idéntico.
Por qué se construyó así
Sigo volviendo a una regla que exijo al equipo: el SDK no puede hacer que tu app posea un secreto que no puede ver ni revocar. Toda la forma sigue de eso. Las credenciales son solo de escritura y cifradas en reposo, y la plataforma no deja que ni sus propios operadores las lean. Los tokens de conexión están acotados a un conector único, autenticados por HMAC, y el texto plano nunca se guarda, así que el peor caso de una fuga es un activo nombrado, revocable, de un solo conector, no un pase fijo al patrimonio entero. Los reintentos están acotados y con ruido, y un timeout por solicitud compite con la lectura del cuerpo para que un flujo atascado no cuelgue un worker. El mismo objeto de transporte maneja el plano de control y el plano de datos, y por eso las semánticas de reintento, timeout y enmascaramiento no pueden divergir entre los dos.
El único lugar donde no ablandaré el lenguaje: mantén las claves de aplicación del lado del servidor. Esto es un cliente de backend. El app id público y la clave secreta pertenecen a tu entorno, no a un bundle, y el diseño asume que el alcance de usuario que pasas es algo que tu servidor puede probar que es el llamador. Si te tienta enviarlo al navegador para ahorrarte un viaje de ida y vuelta, moviste el problema del trabajo de integración a la gestión de secretos, que es un trato peor.
Cómo se entrega
npm install @vinkius/connect. Es dual ESM y CJS con tipos completos, apto para tree shaking, y corre en Node 18 o superior y en cualquier tiempo de ejecución que tenga fetch. Sin dependencias de tiempo de ejecución que auditar, sin hinchazón de lock file, sin cadena de suministro transitiva de discutir con el equipo de seguridad. La licencia es Apache 2.0, así que el código es tuyo para guardarlo y ejecutarlo donde tu app corra.
La capa de capacidades que pilota está cubierta en el post sobre la capa de capacidades y el catálogo, y las garantías de ejecución en que se apoya están en el post sobre ejecutar servidores MCP no fiables en isolates V8. Este post es la superficie de desarrollador de ambos: el cliente contra el que tu equipo escribe.
Entrega la función, no el proyecto de integración. Ese es el punto, y por eso el SDK tiene un solo trabajo: entregar a tu modelo las herramientas que tu usuario conectó, en el dialecto que tu modelo ya habla, y quitarse del medio.
