Premiers pas
Créez votre première intégration personnalisée en 5 minutes
Ce guide vous accompagne dans la création d'une intégration minimale « Hello World » qui journalise un message et transmet des données. À la fin, vous disposerez d'un fichier .archive.tar.gz prêt à être importé.
Prérequis
- Bun v1.1+ (ou Node.js 20+)
- TypeScript 5.7+
Mise en place du projet
Chemin le plus rapide : générez l'intégralité du projet avec la CLI au lieu des étapes manuelles ci-dessous —
bunx @scrydon/sdk-authoring extension init my-vendor --outDir ./extensions/authoring
cd extensions/my-vendorLes invites interactives permettent de choisir le type d'authentification (OAuth, API key ou none), les capacités à générer et une couleur de marque, puis adaptent le fichier src/index.ts généré à vos choix. Passez --yes pour ignorer les invites et accepter les valeurs par défaut (auth = none, capacités = blocks & tools) — utile pour la CI. Dans tous les cas, vous obtenez package.json, tsconfig.json, un squelette defineExtension() et un exemple de test live dans src/__tests__/.
mkdir my-extension && cd my-extensionbun init -yMettez à jour package.json :
{
"name": "my-extension",
"version": "1.0.0",
"private": true,
"type": "module",
"main": "src/index.ts"
}bun add -d @scrydon/sdk-authoring zodÉcrire l'intégration
Créez src/index.ts :
import {
defineExtension,
defineToolkit,
defineTool,
defineBlock,
} from "@scrydon/sdk-authoring/extensions/authoring/define";
import type { ToolResponse } from "@scrydon/sdk-authoring/extensions/authoring/context";
import { z } from "zod";
// 1. Define a tool — the runtime logic
const sayHelloTool = defineTool({
id: "say-hello",
name: "Say Hello",
version: "1.0.0",
description: "Logs a greeting and passes the message through.",
input: z.object({
message: z.string().describe("The message to pass through"),
}),
output: z.object({
message: z.string().describe("The original message, unchanged"),
}),
params: {
message: {
type: "string",
required: true,
visibility: "user-or-llm",
description: "Message to pass through the block",
},
},
async execute(input, ctx): Promise<ToolResponse<{ message: string }>> {
ctx.logger.info(`Hello World! Received: "${input.message}"`);
return {
success: true,
output: { message: input.message },
};
},
});
// 2. Define a block — the workflow editor UI
const helloBlock = defineBlock({
type: "hello_world",
name: "Hello World",
description: "Logs a greeting and passes input to output.",
category: "extension",
bgColor: "#22C55E",
authMode: "none",
subBlocks: [
{
id: "message",
title: "Message",
type: "short-input",
placeholder: "Enter a message…",
required: true,
},
],
tools: {
access: ["say-hello"],
},
inputs: {
message: { type: "string", description: "Incoming message" },
},
outputs: {
message: { type: "string", description: "Outgoing message" },
},
});
// 3. Define a toolkit — the grouping unit
const helloToolkit = defineToolkit({
id: "hello-world",
name: "Hello World",
description: "A minimal demo extension.",
logo: "./assets/icon.svg",
tools: [sayHelloTool],
block: helloBlock,
triggers: [],
});
// 4. Export the extension — the top-level container
export default defineExtension({
id: "hello-world",
name: "Hello World",
version: "1.0.0",
description: "A minimal example extension.",
color: "#22C55E",
logo: "./assets/icon.svg",
categories: ["extension"],
connectivity: "local",
auth: {
credentials: { none: { type: "none" } },
default: "none",
},
provides: { toolkits: [helloToolkit] },
});Le point d'entrée doit avoir un default export qui est un résultat de defineExtension(). La CLI de build le lit pour extraire le manifeste.
Raccourci d'ID d'outil — Vous pouvez utiliser des IDs d'outil courts comme "say-hello" au lieu du nom complet "hello-world:hello-world:say-hello". Le SDK qualifie automatiquement les IDs courts en ajoutant {extensionId}:{providedId}: lors de defineExtension(). Toutes les intégrations natives utilisent cette forme courte.
Comprendre les couches
Le code ci-dessus comporte quatre couches, chacune avec un rôle précis :
| Couche | Ce qu'elle définit | Champs clés |
|---|---|---|
defineTool() | Logique d'exécution | input (Zod), output (Zod), fonction execute() |
defineBlock() | Interface de l'éditeur de workflow | subBlocks (champs de formulaire), inputs/outputs (flux de données) |
defineToolkit() | Unité de regroupement | tools, block, triggers, activé/désactivé par organisation |
defineExtension() | Conteneur de niveau supérieur | configuration auth, provides.toolkits / provides.models, métadonnées de l'extension |
Flux de données : les subBlocks du bloc définissent les champs du formulaire. Le tableau tools.access du bloc renvoie vers les IDs d'outils. Lors de l'exécution du workflow, la plateforme appelle la fonction execute() de l'outil avec l'entrée validée et un PureContext.
Compiler l'archive
bunx @scrydon/sdk-authoring extension buildSortie :
Archive created: dist/hello-world-1.0.0.archive.tar.gz
Vendor: Hello World (hello-world)
Version: 1.0.0
Products: 1
Tools: 1
Dependencies: 1 (SBOM: meta/sbom.cdx.json)Ce que fait la compilation
- Importe votre point d'entrée et vérifie que l'export par défaut est un résultat de
defineExtension() - Extrait toutes les métadonnées dans
manifest.json— les schémas Zod deviennent des JSON Schemas - Archive votre code avec esbuild dans un seul
dist/index.js(toutes les dépendances intégrées) - Génère un SBOM CycloneDX 1.6 listant tous les packages NPM de l'archive (
meta/sbom.cdx.json) - Empaquète tout dans
{extensionId}-{version}.archive.tar.gz
Inspecter la sortie
# List archive contents
tar -tzf dist/hello-world-1.0.0.archive.tar.gz
# Pretty-print the manifest
tar -xzf dist/hello-world-1.0.0.archive.tar.gz -O manifest.json | python3 -m json.toolValider sans importer
Le SDK inclut une commande de test :
# Static validation — checks manifest, schemas, ID formats
bunx @scrydon/sdk-authoring extension test --level static
# Validation de compatibilité — charge l'archive dans un Worker Thread local (pas une attestation microVM)
bunx @scrydon/sdk-authoring extension test --level sandbox
# Test a specific tool
bunx @scrydon/sdk-authoring extension test --level sandbox --tool hello-world:hello-world:say-helloImporter et utiliser
Les intégrations personnalisées sont livrées à l'intérieur d'un extension — il n'y a pas de téléversement manuel de archive. Consultez Livrer et gérer pour les instructions détaillées. Version rapide :
- Publiez votre
.archive.tar.gzcomme entréeextensiond'une extension (comment) - Enregistrez la source Git/OCI de cette extension dans Paramètres > Plateforme > Extensions > Sources — ou, pour un cas ponctuel, utilisez Téléverser une extension (ponctuel) sur le même onglet
- Ouvrez Ajouter une extension, trouvez « Hello World » et cliquez sur Installer (elle ne déclare aucun identifiant, le verbe est donc Installer et non Connecter)
- Ouvrez l'Éditeur de Workflow et recherchez « Hello World »
Étapes suivantes : ajouter l'authentification
La plupart des intégrations réelles ont besoin de credentials. Voici comment ajouter l'authentification par clé API :
// Change the auth config
export default defineExtension({
// ...
auth: {
credentials: {
apiKey: {
type: "apiKey",
label: "API Key",
description: "Your service API key",
headerName: "Authorization",
headerPrefix: "Bearer",
},
},
default: "apiKey",
},
// ...
});Mettez à jour le bloc pour afficher un champ de saisie de clé API :
const myBlock = defineBlock({
// ...
authMode: "apiKey",
subBlocks: [
{
id: "apiKey",
title: "API Key",
type: "short-input",
password: true,
placeholder: "Enter your API key",
},
// ... other fields
],
// ...
});La clé est ensuite disponible dans ctx.auth.apiKey à l'intérieur de votre fonction execute() :
async execute(input, ctx) {
const response = await fetch("https://api.example.com/data", {
headers: {
Authorization: `Bearer ${ctx.auth.apiKey}`,
},
});
const data = await response.json();
return { success: true, output: data };
},Étapes suivantes : plusieurs outils
Ajoutez un deuxième outil au même toolkit en définissant un autre defineTool() et en l'incluant dans le tableau tools du toolkit :
const reverseTool = defineTool({
id: "reverse",
name: "Reverse Message",
version: "1.0.0",
description: "Reverses the input message.",
input: z.object({ message: z.string() }),
output: z.object({ message: z.string() }),
params: {
message: {
type: "string",
required: true,
visibility: "user-or-llm",
description: "Message to reverse",
},
},
async execute(input, ctx) {
const reversed = input.message.split("").reverse().join("");
ctx.logger.info(`Reversed: "${reversed}"`);
return { success: true, output: { message: reversed } };
},
});
// Add to the toolkit's tools and its block
const helloToolkit = defineToolkit({
// ...
tools: [sayHelloTool, reverseTool],
triggers: [],
block: defineBlock({
// ...
subBlocks: [
{
id: "operation",
title: "Operation",
type: "dropdown",
options: [
{ label: "Say Hello", id: "say-hello" },
{ label: "Reverse", id: "reverse" },
],
},
// ... message field
],
tools: {
access: ["say-hello", "reverse"],
toolSelector: {
param: "operation",
map: {
reverse: "reverse",
"say-hello": "say-hello",
},
default: "say-hello",
},
},
}),
});Le tools.toolSelector déclaratif sélectionne dynamiquement l'outil à exécuter selon le choix de l'utilisateur et survit à la sérialisation du manifeste. Les fonctions tools.config.tool et tools.config.params sont omises de manifest.json.