AI Connect/How to create/Automatisations et comptes de service
Automatisations et comptes de service
Faites en sorte que les cron jobs, les webhooks, les pipelines CI et les traitements nocturnes par lots possèdent leur propre utilisateur dans l'AI Connect SDK, connectent des systèmes sans interface avec des identifiants statiques et agissent avec un isolement et un budget par job, sans aucun navigateur. Les processus deviennent des acteurs gouvernés, pas des clés muettes dans un fichier de configuration.
L'IA la plus précieuse de votre entreprise s'exécute sans intervention humaine. Un job nocturne qui rapproche le grand livre, un webhook qui qualifie un nouveau lead dans le CRM, une étape de CI qui ouvre une issue après un déploiement raté. Jusqu'ici, l'infrastructure n'avait qu'une seule forme pour ce travail : une god-key dans un fichier d'environnement, sans identité, sans mesure, sans révocation, et une revue de sécurité qui se termine en haussement d'épaules.
L'AI Connect SDK remplace cette forme par ce qu'aucune plateforme de connectivité n'a offert avant : un processus est un utilisateur, avec son propre external_id, ses propres connecteurs, ses propres identifiants statiques, adossé à des milliers de connexions IA dès le premier jour. Donnez à chaque automatisation sa propre identité, connectez-la une fois et laissez-la appeler des capacités indéfiniment, isolée, mesurée et auditable. Chaque cron job devient un employé redevable, avec son badge, son budget et son propre départ. C'est une phrase que vous ne pouviez écrire à propos d'aucune plateforme d'intégration du marché jusqu'à celle-ci.
vinkius.user('alice_123').capabilities({ include: ['github'] })GET /apps/vk_app_xxx/users/alice_123/tools?connector=githubconst capabilities = await vinkius
.user('alice_123')
.capabilities({ include: ['github'] });CapabilitySet (6)
github__list_issues read-only
github__create_issue POST /repos/{owner}/{repo}/issues
github__list_pull_requests read-only
github__search_code read-only
...Ce qu'un processus-utilisateur vous apporte
| Propriété | Pourquoi c'est important pour les jobs sans intervention |
|---|---|
| Headless par conception | Les connecteurs à jeton statique (api_key, token) n'exigent aucun consentement interactif, credentials.set() constitue tout le flux. |
| Aucun navigateur | Un conteneur cron avec seulement fetch et votre Application Key exécute tout le SDK. |
| Rayon d'impact par job | La connexion propre à chaque automatisation fait qu'une clé exposée ne compromet qu'un job, pas tout le parc. |
| Métrage indépendant | Les jetons par connexion montrent exactement ce que coûte le job nocturne de rapprochement. |
| Réessais déterministes | Une clé d'idempotence garantit qu'un appel répété est appliqué exactement une fois. |
Ces jobs détiennent de vrais identifiants sans humain dans la boucle. Gardez chaque extrait côté serveur, sourcez les jetons statiques depuis votre secret manager (jamais depuis un dépôt ni une invite du modèle) et donnez à chaque automatisation le plus petit ensemble de connecteurs avec lequel elle peut fonctionner.
1. Une automatisation, un id, un ensemble de connecteurs
Nommez le job d'après ce qu'il fait et traitez ce nom comme l'acteur propriétaire des connexions.
const JOB = 'svc-nightly-reconcile'; // stable, compatible URL, moins de 255 caractères, sans / \ ni espaces
const JOB_CONNECTORS = ['netsuite', 'stripe', 'sheets'];2. Provisionnez un compte de service (une fois, à la configuration)
Cela s'exécute une seule fois lors de l'onboarding, un opérateur ou un script d'amorçage fournit les jetons statiques. Ensuite, le job utilise seulement la connexion.
// scripts/bootstrap-reconcile.ts
import { Vinkius } from '@vinkius/connect';
const vinkius = new Vinkius({
appId: process.env.VINKIUS_APP_ID!,
apiKey: process.env.VINKIUS_APP_KEY!, // depuis le secret manager
});
async function provision(jobId: string, tokens: Record<string, Record<string, string>>) {
const user = vinkius.user(jobId);
await user.ensure({ kind: 'service-account', job: 'nightly-reconcile' });
for (const [slug, values] of Object.entries(tokens)) {
const connector = user.connector(slug);
await connector.connect(); // obtenir-ou-créer, idempotent
await connector.credentials.set(values); // écriture seule ; p. ex. { API_KEY: … }
}
return Promise.all(
Object.keys(tokens).map(async (slug) => ({
slug,
status: await user.connector(slug).status(), // attendez "ready"
})),
);
}
await provision('svc-nightly-reconcile', {
netsuite: await secrets.read('netsuite.reconcile'),
stripe: await secrets.read('stripe.reconcile'),
sheets: await secrets.read('sheets.reconcile'),
});credentials.set() ne renvoie jamais les valeurs stockées, et status() n'indique que les clés configurées. Un processus peut vérifier que ses connecteurs sont prêts sans jamais pouvoir exfiltrer ce qu'il a reçu, l'identifiant est utilisable, pas lisible.
3. Le job sans intervention lui-même
Le processus planifié n'a besoin d'aucun navigateur, d'aucun consentement, d'aucun utilisateur présent. Il charge ses capacités et agit.
// jobs/nightly-reconcile.ts
import { Vinkius } from '@vinkius/connect';
const vinkius = new Vinkius({
appId: process.env.VINKIUS_APP_ID!,
apiKey: process.env.VINKIUS_APP_KEY!,
});
async function run() {
const job = vinkius.user('svc-nightly-reconcile');
const capabilities = await job.capabilities({ include: ['netsuite', 'stripe'] });
const fetchOpen = capabilities.findCapability('stripe__list_invoices');
const postEntry = capabilities.findCapability('netsuite__create_journal_entry');
if (!fetchOpen || !postEntry) {
await alertOps('reconcile : une capacité est indisponible, un identifiant a-t-il expiré ?');
return;
}
const invoices = await fetchOpen.execute({ status: 'open', limit: 200 });
if (invoices.isError) throw new Error('échec de la liste stripe : ' + invoices.content[0]?.text);
const entry = await postEntry.execute(
{ lines: toJournalLines(invoices) },
{ idempotencyKey: `reconcile:${runDate()}` }, // une exécution logique = une écriture
);
if (entry.isError) await alertOps('écriture du reconcile rejetée : ' + entry.content[0]?.text);
}
run().catch(async (error) => {
await alertOps(`reconcile a planté : ${error.message}`);
});idempotencyKey est la fonctionnalité d'automatisation. Un réessai, un déclenchement dupliqué du cron, un redéploiement en cours d'exécution, aucun ne publie deux fois l'écriture comptable, car le serveur déduplique les rejeux portant la même clé. Dérivez la clé de l'événement métier (la date d'exécution, l'id du ticket, l'id de livraison du webhook), jamais de Date.now().
4. Webhooks : un acteur, une livraison par clé
Pour les automatisations pilotées par événements, vous gardez généralement un seul utilisateur de compte de service, mais chaque appel mutatif s'appuie sur l'id de livraison afin qu'un webhook réessayé soit appliqué exactement une fois.
// POST /webhooks/lead (vérifié)
async function handleLeadWebhook(payload: { id: string; email: string }) {
const user = vinkius.user('svc-lead-intake');
if ((await user.connector('hubspot').status()) !== 'ready') {
await alertOps('CRM de lead-intake non prêt');
return;
}
const caps = await user.capabilities({ include: ['hubspot'] });
await caps.findCapability('hubspot__create_contact')?.execute(
{ email: payload.email },
{ idempotencyKey: `lead:${payload.id}` }, // livraison réessayée -> pas de doublon
);
}5. Pipelines CI et exécuteurs éphémères
Un job CI s'authentifie exactement comme un cron : même Application Key, son propre external_id, identifiants statiques provisionnés dans l'environnement. La différence tient à la durée de vie, vous appelez disconnect() sur les exécuteurs éphémères quand le pipeline est détruit.
async function openIssueOnFailedDeploy(runId: string, repo: string) {
const caps = await vinkius.user('ci-deploy-bot').capabilities({ include: ['github'] });
await caps.findCapability('github__create_issue')?.execute(
{ owner: 'acme', repo, title: `Deploy ${runId} a échoué` },
{ idempotencyKey: `deploy:${runId}` },
);
}6. Faites tourner et retirez proprement
Comme tout est rattaché à un unique external_id, la mise hors service d'une automatisation est déterministe.
async function decommission(jobId: string) {
const user = vinkius.user(jobId);
for (const conn of await user.connectors()) {
await user.connector(conn.slug).disconnect();
}
}Pour faire tourner un identifiant, rappelez credentials.set() avec la nouvelle valeur, la connexion reste la même et aucune référence de capacité n'est rompue.
Fiabilité et sortie structurée pour les processus sans supervision
Un processus sans supervision est là où la robustesse intégrée du SDK compte le plus. Trois garanties sont héritées automatiquement :
Réessai automatique avec protection contre l'effet de troupeau. Les lectures idempotentes (et toute écriture portant un idempotencyKey) sont réessayées automatiquement : uniquement pour les erreurs transitoires 429/502/503/504 et réseau, avec un backoff à jitter complet qui respecte un Retry-After du serveur. Votre cron n'a pas besoin de sa propre boucle de réessai.
L'idempotence est le mécanisme de sécurité. Dans un job par lots, une panne et une réexécution sont banales. Un idempotencyKey stable supprime l'ambiguïté de savoir si la réexécution a dupliqué l'effet, car le serveur déduplique les rejeux sur cette clé. Fournissez la clé sur un appel mutatif et le SDK réessayera en toute sécurité même un POST non idempotent.
Consommez les résultats comme des objets, pas du texte. Quand un connecteur renvoie des données structurées, result.structuredContent les fournit déjà analysées, sans extraction de texte fragile dans le pipeline :
const invoices = await fetchOpen.execute({ status: 'open', limit: 200 });
const list = invoices.structuredContent as { invoices: Array<{ id: string; amount: number }> };
const total = list.invoices.reduce((sum, i) => sum + i.amount, 0);Associez les exécutions longues de lots à un timeoutMs par appel approprié et à une signal interrompue à l'arrêt du processus, et le job se termine de façon prévisible pendant un déploiement progressif.
Liste de contrôle de production
- [ ] Donnez à chaque automatisation un
external_iddistinct et lisible (pas de compte partagé surdimensionné). - [ ] Sourcez chaque jeton statique depuis votre secret manager ; ne le versionnez jamais et ne le placez jamais dans une invite.
- [ ] Provisionnez une fois à la configuration ; les runtimes lisent seulement
status(), ils ne réenregistrent jamais de secrets. - [ ] Définissez un
idempotencyKeystable dérivé de l'événement métier pour tout job mutatif. - [ ] Alertez quand
status() !== 'ready'et surisError, afin qu'une panne silencieuse ne soit pas une panne invisible. - [ ] Appelez
disconnect()sur les comptes de service éphémères lors de la destruction.
Vous disposez désormais d'automatisations qui possèdent leur propre identité, connectent de vrais systèmes sans interface et agissent selon un agenda ou un événement, sans toute la mécanique OAuth et sans la dérive du « qui possède ce jeton ? » d'un compte partagé surdimensionné. Votre parc sans intervention est passé du bout du système dont les audits se plaignent à celui avec la meilleure histoire de gouvernance, parce que chacun de ses processus a enfin un nom.
What you just got
Not a pitch: the properties this build inherits automatically.
Connections and capabilities resolve only inside one external_id. No cross-actor leakage is possible, and you wrote none of that enforcement.
Your server stores secrets and can read back which fields are configured, never the values. Not your code, the model, or a dashboard can exfiltrate them.
Every connection owns a vk_live_* token, so cost and revocation are per connection. One call to disconnect() is a complete, auditable stop.
One CapabilitySet converts to OpenAI, Anthropic, Gemini, Vercel AI SDK, LangChain, LlamaIndex, Workers AI or neutral JSON Schema. Only the last line changes.
idempotencyKey, timeoutMs and AbortSignal per call; automatic full-jitter retries on transient failures; typed VinkiusError branches. No bespoke harness.
A cron container with only fetch and your key runs it. An idempotencyKey from the business event makes a re-run a non-event.
Give it to your AI agent
An Agent Skill (SKILL.md) for this build. Preview the first lines below, then copy or download it into your repo under .claude/skills/: Claude Code, Cursor or any Agent-Skills-compatible agent follows it to implement this pattern correctly.
