Documentation
Écrire un plugin d'outil d'assistance
Créez un outil de lecture autonome, validez son manifeste et ses arguments, activez-le délibérément et testez la limite de travail que vous recevez réellement.
Traduit automatiquement de l'anglais. Voir l'original en anglais
Choisissez le format de l'extension de l'assistant
Le chargeur d'extension de l'assistant de production accepte un répertoire contenant plugin.json et une entrée JavaScript fournie. Il importe le module dans un travailleur Bun et expose les fonctions déclarées de son enregistrement aiTools à l'assistant. La fonctionnalité actuellement prise en charge est ai:tool. Ce guide crée un calculateur de cercle de boulons en lecture seule qui renvoie les coordonnées ; il n'insère pas de géométrie dans un dessin.
Il existe également une API ZIP de plug-in de navigateur sous /api/plugins avec manifest.json, main, autorisations de navigateur et ressources d'amorçage. Ce programme d'installation n'active pas le plugin de travail décrit ici. Le référence HTTP du plugin documents qui séparent le format ; Plugins explique les deux surfaces. Ne traduisez pas entre eux en renommant simplement un manifeste.
Un outil assistant est un code exécuté à l’intérieur des limites d’exécution de l’application. Les vérifications de capacité de l'hôte limitent l'accès via le RPC hôte, mais le travailleur actuel n'est pas un bac à sable du système d'exploitation. Utilisez un code d'extension dont l'exécution est appropriée pour cet environnement et faites en sorte que son comportement de lecture/écriture déclaré soit conforme à ce que fait réellement la fonction.
Créer le répertoire et l'enregistrement de consentement
Utilisez le répertoire de données d'application exact du sidecar que vous souhaitez étendre. Créez cette structure, en gardant l'ID de répertoire identique à l'ID DNS inversé du manifeste :
<dataDir>/plugins/
enabled.json
com.example.bolt-circle/
plugin.json
index.js Le chargeur de production lit activé.json comme un tableau JSON d'identifiants sécurisés. Il ignore les entrées en double/invalides, et un JSON mal formé ou manquant signifie qu'aucun plugin n'est activé. Un dossier non répertorié n'est pas chargé. Le chargeur rejette également une incompatibilité d’ID de dossier/manifeste et manifeste qui demande des fonctionnalités non prises en charge.
Le chargement se produit lorsque le service du plugin assistant s'initialise. Il ne configure pas d'observateur de système de fichiers pour le rechargement à chaud. Préparez et validez d’abord les fichiers, puis activez/redémarrez cette instance. Un plugin défaillant enregistre un avertissement pendant que le démarrage des autres plugins et applications se poursuit ; inspectez ces avertissements au lieu de supposer qu’une entrée de fichier activée garantit un outil en cours d’exécution.
Déclarer un contrat d'outillage précis
Ce manifeste complet nomme un seul outil de lecture. L'ID de contribution est l'identifiant local du modèle ; L'entrée nomme la fonction exportée par le module. Ces chaînes peuvent différer, comme le font bolt_circle et boltCircle ici.
{
"id": "com.example.bolt-circle",
"name": "Bolt circle calculator",
"version": "1.0.0",
"apiVersion": "1",
"entry": "index.js",
"capabilities": ["ai:tool"],
"contributes": {
"aiTools": [{
"id": "bolt_circle",
"description": "Calculate equally spaced XY hole centers around the origin for a bolt circle. Returns coordinates without changing a drawing.",
"kind": "read",
"entry": "boltCircle",
"parameters": {
"type": "object",
"properties": {
"count": { "type": "integer", "minimum": 1, "maximum": 64 },
"radius": { "type": "number", "exclusiveMinimum": 0 }
},
"required": ["count", "radius"],
"additionalProperties": false
}
}]
}
} Le SDK nécessite des ID DNS inversés de 3 à 128 caractères, une version sémantique major.minor.patch avec une version préliminaire facultative, une version d'API "1", un nom/une entrée non vide et des fonctionnalités reconnues. Les ID d'outil commencent par une lettre minuscule et utilisent des lettres minuscules, des chiffres ou des traits de soulignement jusqu'à 40 caractères. Les descriptions contiennent 10–2,000 caractères. Le type d'outil est en lecture ou en écriture ; il n’existe pas de type d’extension destructeur.
Gardez le schéma de paramètres petit et descriptif. Son rôle est d'aider le modèle à produire des arguments valides et de permettre à l'adaptateur de rejeter les erreurs ordinaires avant l'exécution. Cela ne remplace pas la validation dans le code.
Implémenter le module autonome
L'entrée exporte aiTools avec la fonction d'entrée nommée dans le manifeste. Il valide à nouveau les arguments d'exécution et renvoie un petit résultat compatible JSON. L'arithmétique à coordonnées finies et le nombre limité empêchent les entrées mal formées de produire une liste illimitée ou une sortie dénuée de sens.
export const aiTools = {
boltCircle: async (args) => {
if (!args || typeof args !== "object") {
return { ok: false, error: "Expected count and radius" }
}
const { count, radius } = args
if (!Number.isInteger(count) || count < 1 || count > 64) {
return { ok: false, error: "count must be an integer from 1 to 64" }
}
if (typeof radius !== "number" || !Number.isFinite(radius) || radius <= 0) {
return { ok: false, error: "radius must be a positive finite number" }
}
const holes = Array.from({ length: count }, (_, index) => {
const angle = 2 * Math.PI * index / count
return {
x: Number((radius * Math.cos(angle)).toFixed(4)),
y: Number((radius * Math.sin(angle)).toFixed(4))
}
})
return { ok: true, units: "mm", holes }
}
} Count 4 and radius 10 return centers at approximately (10,0), (0,10), (−10,0), and (0,−10), in counter-clockwise order around the origin. The result deliberately contains no database mutation or drawing identifier. A client wanting to insert holes must use a separate supported application action with its own access and permission behavior.
Regroupez les dépendances dans l'entrée plutôt que de supposer que les importations relatives de style nœud sont résolues. L'hôte lit le texte de l'entrée et l'importe via un module blob. Le code de niveau supérieur du module s'exécute lors de l'importation, évitez donc les effets secondaires liés au simple chargement d'une calculatrice.
Valider le manifeste et les deux chemins d'argument
Validez avant d'ajouter le plugin à activé.json. Cette vérification de développement utilise le même analyseur de manifeste SDK et le même adaptateur de schéma AI que l'application. Placez le script dans un espace de travail avec ces dépendances, telles que apps/sidecar, et transmettez le répertoire absolu du plugin comme argument.
import { join } from "node:path"
import { Effect } from "effect"
import { parseManifest } from "@cadcamfun/plugin-sdk"
import { pluginParameters } from "@cadcamfun/ai"
const directory = process.argv[2]
if (!directory) throw new Error("Pass the absolute plugin directory")
const manifest = await Effect.runPromise(parseManifest(
await Bun.file(join(directory, "plugin.json")).text()))
if (manifest.capabilities.some((capability) => capability !== "ai:tool")) {
throw new Error("This production loader accepts only ai:tool")
}
const contribution = manifest.contributes?.aiTools?.[0]
if (!contribution) throw new Error("No tool contribution")
const parameters = pluginParameters(contribution.parameters)
console.log(parameters.safeParse({ count: 4, radius: 10 }).success) // true
console.log(parameters.safeParse({ count: 0, radius: 10 }).success) // false
const module = await import(join(directory, manifest.entry))
console.log(await module.aiTools[contribution.entry]({ count: 4, radius: 10 }))
console.log(await module.aiTools[contribution.entry]({ count: 0, radius: 10 })) Les deux derniers appels testent le comportement au niveau de la fonction indépendamment du rejet de l’adaptateur. Le SDK valide la structure du manifeste, mais le chargeur de production ajoute ses propres règles de fonctionnalité et d'ID de dossier. Testez les trois limites : déclaration, adaptateur d’argument et fonction réelle du module.
Activer et découvrir l'identifiant réel de l'outil
Après validation, activez l'ID dans le répertoire des données de l'application :
["com.example.bolt-circle"] Redémarrez/réinitialisez le service sidecar prévu et inspectez les journaux pour détecter les avertissements de charge. Puis liste GET /api/ai/actions. Les outils du plugin de lecture y sont exposés ainsi qu'à l'assistant intégré ; la politique peut réduire le catalogue. L'identifiant de l'outil est généré à partir de l'ID du plugin et de l'ID de l'outil local, normalisé et limité à la taille de l'identifiant de l'hôte.
Pour ce manifeste, l'ID dérivé actuel est plugin_com_example_bolt_circle_bolt_circle. Découvrez-le plutôt que de réimplémenter la règle de dénomination chez chaque client. Un long identifiant de plugin peut être tronqué pour laisser de la place au suffixe de l'outil. La description du catalogue inclut le nom du plugin afin que l'utilisateur puisse identifier la source.
Un outil d'écriture est marqué pour approbation humaine dans l'assistant intégré, quel que soit le mode d'autorisation ordinaire. Le pont extérieur expose uniquement les outils de plug-in de lecture et refuse les appels d'extension d'écriture même avec un jeton de lecture/écriture. Gardez le type précis ; l'application ne peut pas déduire des effets secondaires de module arbitraires à partir d'un schéma JSON.
Appeler un outil de lecture via un accès extérieur limité
Utilisez un jeton de pont émis par l'application pour un projet réel. Son porteur ouvre uniquement GET /api/ai/actions et POST /api/ai/actions/invoke. Il ne permet pas aux appelants d'installer des plugins, de modifier leurs informations d'identification ou d'énumérer des projets sans rapport. Le projet/mode du jeton est vérifié par le gestionnaire d'action ; un jeton à l’échelle de l’application n’est pas nécessaire pour un client de calculatrice autonome.
{
"projectId": "replace-with-your-project-id",
"tool": "plugin_com_example_bolt_circle_bolt_circle",
"input": { "count": 4, "radius": 10 }
} curl --fail-with-body \
-H "Authorization: Bearer $CADCAMFUN_BRIDGE_TOKEN" \
-H "Content-Type: application/json" \
--data-binary @bridge-call.json \
"$CADCAMFUN_URL/api/ai/actions/invoke" Les entrées du plugin sont l'objet argument déclaré ; contrairement aux outils groupés intégrés, ce plugin de lecture ne nécessite pas de discriminateur d'action dans l'entrée. Le pont enveloppe la valeur renvoyée avec des métadonnées d'outil/action ; l'action est exécutée. Au niveau de la fonction ok=false reste un résultat lisible plutôt qu'un succès de transport HTTP prouvant une liste de coordonnées utile.
La politique de pont vérifie tool.use et bridge.use. Un jeton manquant, expiré, révoqué ou de mauvais type entraîne un échec d'autorisation. Un jeton lié à un autre projet n'est pas un moyen d'élargir la portée de cet appel.
Comprendre la conversion de schéma et les limites des capacités
L'adaptateur convertit le schéma JSON de la contribution en zod lorsqu'il est représentable. Si la conversion échoue, que le schéma dépasse 20,000 caractères sérialisés ou qu'il ne décrit pas d'objet, il revient à un schéma d'objet arbitraire. C'est pourquoi l'exemple de fonction vérifie lui-même le nombre et le rayon. Le passage négatif d'un schéma pris en charge localement ne prouve pas que toutes les fonctionnalités du schéma resteront représentables.
Le SDK reconnaît draw:read, draw:write, toolpath:read, storage, ui:panel et ai:tool ; dessiner écrire implique dessiner lire. Le chargeur de l'assistant de production n'accepte actuellement que ai:tool. L’ajout de draw:write à ce manifeste entraîne son saut et ne lui accorde pas de contexte d’éditeur de dessin.
Le bootstrap général du travailleur contient des gestionnaires de dessin/chemin d'outil/interface utilisateur d'espace réservé. Ces gestionnaires ne constituent pas une intégration de géométrie de production. De même, déclarer des contributions de post-traitement ou d'exploitation n'oblige pas l'assistant chargeur à les câbler dans les registres de fabrication. Choisissez une action d'application prise en charge lorsque la fonctionnalité doit modifier des données CAO/FAO réelles.
Concevoir autour du cycle de vie des travailleurs implémentés
Le SDK décrit un module avec des hooks de manifeste, d’activation, de nettoyage facultatif et de désactivation. Le chemin IA de production actuel importe l’entrée et appelle les fonctions aiTools exportées. Ses gestionnaires d'activation/désactivation RPC accusent réception des messages de cycle de vie mais n'invoquent pas les hooks de module exportés. Une calculatrice peut donc utiliser le module minimal présenté ici sans dépendre des rappels d'activation.
L'hôte démarre un travailleur par plugin activé, met en corrélation les réponses avec les ID RPC et suit les appels en attente. Une exception de fonction fait échouer cet appel ; des tests ciblés sur des travailleurs réels montrent que les appels ultérieurs peuvent toujours réussir. Renvoie les refus structurés pour les entrées invalides attendues et réserve les échecs rejetés pour les erreurs de calcul inattendues.
Un délai d'attente d'appel met fin à son travailleur et met immédiatement en défaut le plugin. Les crashs de Worker et les échecs de publication suppriment également l'instance en cours d'exécution et la marquent comme étant défaillante. L'historique des crashs a une fenêtre de dix minutes, mais le gestionnaire actuel échoue lors d'un premier crash ; il n'attend pas trois tentatives de redémarrage réussies. Corrigez la source et rechargez/réactivez délibérément au lieu de supposer un redémarrage automatique.
Gardez les garanties d’exécution proportionnelles
Le chargeur de production fournit un budget hôte de 10 secondes pour un RPC de cycle de vie/commande ordinaire. Les appels aux outils d'IA ont un délai distinct de 30 secondes. L'hébergeur limite un plugin à 2,000 appels par minute et 64 appels en attente. Il transmet un paramètre de limite de ressources de 256 Mo à Bun lors de la création du travailleur. Il s’agit de contrôles d’exécution et non d’une stratégie de confinement de processus ou de système d’exploitation distincte.
Le bootstrap démarre avec un environnement vide et tente de supprimer les globaux de processus/réseau sélectionnés. La suppression s'effectue au mieux et certains globaux Bun restent accessibles. La vérification des capacités est donc significative pour les API fournies par l'hôte, mais ne peut pas être présentée comme une isolation complète du JavaScript arbitraire.
Des champs de signature et de développement facultatifs existent dans le schéma manifeste. Le chemin d'installation actuel du travailleur enregistre le texte de la signature sans vérification cryptographique. N'étiquetez pas un éditeur comme vérifié car ce champ est présent. Une politique d'examen des sources et d'installation autonome reste une décision distincte de l'acceptation du schéma.
Les plugins qui plantent trois fois en dix minutes sont automatiquement désactivés et un appel à l'outil assistant est arrêté après 30 secondes. Les deux valeurs sont constantes dans apps/sidecar/src/plugins/host.ts.
Mettre à jour, supprimer et diagnostiquer un outil
Pour une mise à jour, modifiez et validez le module/manifeste fourni avant de redémarrer le service cible. Gardez son ID stable lorsqu'il s'agit de la même extension et augmentez la version pour identifier ce qui a été chargé. Pour arrêter de le charger lors d'une initialisation future, supprimez son ID de activé.json et redémarrez/réinitialisez. La seule modification du fichier ne supprime pas un travailleur déjà en cours d'exécution.
Si l'outil est manquant, vérifiez le tableau activé, l'égalité exacte de l'ID dossier/manifeste, la liste des fonctionnalités prises en charge et l'erreur de décodage du manifeste dans les journaux. NoSuchTool signifie généralement que l'entrée ne nomme pas une fonction aiTools exportée. Un refus d’argument pointe vers le schéma ou la fonction guard. Un délai d'attente indique un blocage du travail ou une promesse non résolue. Les points de terminaison d'activation/configuration ZIP du navigateur ne gèrent pas ce fichier de consentement des travailleurs.
Une couverture ciblée est en place apps/sidecar/test/plugins-ai-tools.test.ts pour les travailleurs réels et ai-plugin-tools.test.ts pour le chargement de la production, la validation des arguments, l'exposition en lecture/écriture et le comportement du pont. Les sources de mise en œuvre sont packages/plugin-sdk/src/manifest.ts, packages/ai/src/tools/plugin-tool.ts, apps/sidecar/src/plugins/host.ts, et services/ai-plugins.ts. Utilisez ces limites pour déterminer ce qu’un test réussi a réellement prouvé.
Les règles du contrat en un coup d'oeil
| Article | Contrainte | Que se passe-t-il en cas de violation |
|---|---|---|
Outil id | ^[a-z][a-z0-9_]{0,39}$ | Le décodage du manifeste échoue ; le plugin n'est pas chargé. |
description | 10–2,000 caractères | Le décodage du manifeste échoue. |
kind | read ou write; il n'y a pas de type destructeur | Le décodage du manifeste échoue. |
parameters | Objet de schéma JSON, au maximum 20,000 caractères une fois sérialisé | Les schémas surdimensionnés ou non convertibles acceptent n’importe quel objet. |
| Capacités | Seulement ai:tool dans l'hôte assistant | Le plugin est ignoré avec un avertissement. |
| Nom du dossier | Égal au manifeste id | Le plugin est ignoré avec un avertissement nommant les deux valeurs. |
| Budget des appels ordinaires | 5 s par appel par défaut | Le travailleur est arrêté comme n'importe quel plugin qui ne répond pas. |
| Budget des appels pour l'outil Assistant | 30 s | L'appel renvoie un résultat d'échec. |
| Mémoire | 256 Mo de tas | L'ouvrier est tué. |
| Taux | 2,000 appels par plugin et par minute, 64 appels en attente | Les appels dépassant le tarif sont refusés ; une file d'attente complète échoue avec plugin RPC queue is full. |
| Crashs et délais d'attente | Compté sur 10 minutes, limite 3 | Chaque crash ou délai d'attente met fin au travailleur et marque le plugin en panne. |
Écrivez la description du modèle. Indiquez ce que l'outil calcule, ses unités et quand l'utiliser ; par exemple, "Renvoie les centres des trous d'un cercle de boulons en millimètres. À utiliser lorsque l'utilisateur demande des trous uniformément espacés sur un diamètre." Des descriptions vagues conduisent à ce que les outils soient appelés au mauvais moment, voire pas du tout.
Liste de contrôle avant la publication
- Regroupez l’entrée dans un seul module autonome. Le travailleur ne peut pas résoudre les importations d’espace de travail au moment de l’exécution.
- Courir
parseManifestsur la finaleplugin.jsonet corrigez chaque problème signalé. - Appelez directement chaque fonction d'outil exportée avec des arguments valides, limites et non valides, et confirmez qu'une entrée non valide produit un résultat d'erreur lisible plutôt qu'une exception qui s'échappe.
- Gardez les résultats petits et compatibles JSON. L'assistant les lit et les résultats volumineux encombrent le contexte.
- Choisir
readà moins que l'outil change quelque chose. UNwritel'outil invite l'utilisateur avant chaque appel. - Placez le dossier sous
<data dir>/plugins/<id>/, ajoutez l'ID àenabled.json, redémarrez le sidecar et confirmez l'ID de l'outil dérivé à partir de la liste des outils de l'assistant. - Bosse
versionpour chaque monnaie que vous expédiez et conservezidstable afin que l’ID de l’outil dérivé ne change pas.
Linter un manifeste avant l'installation
import { decodeManifest, requestedCapabilities } from "@cadcamfun/plugin-sdk"
import { Effect } from "effect"
const raw = JSON.parse(await Bun.file(process.argv[2] ?? "plugin.json").text())
const manifest = await Effect.runPromise(decodeManifest(raw)) // throws a SchemaError naming the bad field
console.log(manifest.id, manifest.version, requestedCapabilities(manifest))
for (const tool of manifest.contributes?.aiTools ?? [])
console.log(`plugin_${manifest.id}_${tool.id}`, tool.kind) | Erreur courante | Réparer |
|---|---|
"version": "1.0" | Utilisez trois nombres : 1.0.0 |
"apiVersion": 1 | C'est une chaîne : "1" |
Identifiant du plugin gear ou My.Plugin | DNS inversé, minuscules : com.example.gear |
Identifiant de l'outil Lookup-Thread | Minuscules, chiffres et traits de soulignement, première lettre, ≤ 40 caractères |
| Description de l'outil inférieure à 10 caractères | Décrire ce qu'il fait et quand l'utiliser |
Déclarer les deux drawing:read et drawing:write | Autorisé, mais drawing:write seul implique déjà lire |
Clé de stockage commençant par / ou contenant .. | Utilisez des clés relatives telles que presets/m2 |