Référence SDK
Référence API complète pour le SDK @scrydon/sdk-authoring/extensions/authoring
Installation
bun add -d @scrydon/sdk-authoring zodnpm install --save-dev @scrydon/sdk-authoring zodChemins d'importation
Le SDK utilise des exports de sous-chemins explicites — il n'existe pas d'import barrel racine.
// Definition helpers (all define* functions)
import {
defineExtension, defineToolkit, defineTool, defineBlock, defineTrigger,
defineModels, defineWebSearch, defineWebhooks, defineDiscovery,
} from "@scrydon/sdk-authoring/extensions/define";
// Runtime types (used in execute() signatures)
import type {
PureContext, PureContextAuth, ToolResponse, ExecutorResult,
RealtimeSession, RealtimeMessage,
} from "@scrydon/sdk-authoring/extensions/context";
// Credential narrowing helpers (throw CredentialKindError on kind mismatch)
import {
requireOAuthToken, requireOAuth,
requireApiKey, requireBasicAuth, requireBotToken,
} from "@scrydon/sdk-authoring/extensions/context";
// Manifest types (for advanced use cases)
import { CodeManifestSchema, type CodeManifest } from "@scrydon/sdk-authoring/extensions/manifest";defineExtension(config)
Conteneur de niveau supérieur pour une extension. L'export par défaut du point d'entrée doit être le résultat d'un appel à defineExtension().
interface ExtensionInput {
/** Unique vendor ID — lowercase alphanumeric + hyphens, starts with letter */
id: string;
/** Display name */
name: string;
/** Semantic version (e.g. "1.0.0") */
version: string;
/** Short description */
description?: string;
/** Hex brand color (e.g. "#FF6B35") */
color?: string;
/** SVG logo as a string */
logo: string;
/** Links shown on the extension's page */
links?: {
website?: string;
docs?: string;
releaseNotes?: string;
support?: string;
};
/** Categories (e.g. ["tools"], ["llm"], ["database"]) */
categories?: string[];
/** Whether this vendor is enabled by default for new orgs */
defaultEnabled?: boolean;
/** Whether this vendor supports system-mode (client_credentials) */
supportsSystemMode?: boolean;
/** Whether this vendor requires external network connectivity */
connectivity?: "cloud" | "local" | "hybrid";
/** Minimum requested isolation. Platform and organization floors may only strengthen it. */
isolation?: "memory" | "microvm";
/** Public HTTPS hosts that tool code may contact — exact, or `*.` wildcards. */
egress?: {
httpsHosts: string[];
};
/** Platform operations your tools need beyond their own code (see "Platform permissions") */
platformPermissions?: readonly ExtensionPlatformPermission[];
/** Authentication configuration */
auth?: AuthConfig;
/** Admin-configurable fields (e.g. base URL for self-hosted services) */
configFields?: ConfigFieldInput[];
/** Extension-level secrets (resolved per-invocation via Dapr Secret Store) */
secrets?: SecretInput[];
/** What this extension provides — at least one toolkit, model family or content kind */
provides: {
toolkits?: ToolkitDefinition[];
models?: ModelFamilyDefinition[];
};
/** Wire protocol IDs this extension makes available to orgs that enable it */
wireProtocols?: string[];
}Isolation et egress des outils
isolation est une demande minimale, pas un sélecteur de backend. Les outils Scrydon exacts et
révisés peuvent utiliser le backend memory approuvé. Tout autre outil livré ou installé par une
organisation a un minimum microvm imposé par la plateforme, même si son manifeste demande
memory ou omet le champ. Une politique d'organisation peut renforcer un outil Scrydon révisé vers
microvm ; aucun réglage ne peut affaiblir le minimum de la plateforme. Si le backend Runtime
Plane attesté est indisponible, l'outil refuse de s'exécuter.
Déclarez chaque nom d'hôte HTTPS public qu'un outil peut contacter :
export default defineExtension({
// ...
egress: {
httpsHosts: ["api.acme.example", "uploads.acme.example"],
},
});Les hôtes doivent être des noms DNS en minuscules, sans schéma, chemin ni port.
Lorsque votre hôte d'API dépend du locataire, préfixez une entrée par *. :
export default defineExtension({
// ...
egress: {
// Covers acme.my.acme-crm.example and acme--dev.sandbox.my.acme-crm.example
httpsHosts: ["*.my.acme-crm.example"],
},
});Une entrée *. correspond aux sous-domaines à n'importe quelle profondeur mais pas à son
propre apex — déclarez l'apex séparément si vos outils le contactent. Le wildcard doit couvrir au
moins deux labels : *.example est donc refusé, aucun manifeste ne pouvant revendiquer un TLD
entier. Le préfixe *. est la seule forme de wildcard ; un segment variable à l'intérieur d'un
label (*-api.acme.example) n'est pas exprimable — déclarez alors le domaine parent. Préférez des
hôtes exacts partout où l'ensemble des destinations est fixe : un wildcard sert aux hôtes que vous
ne pouvez réellement pas connaître avant l'exécution.
À l'exécution, la plateforme intersecte cette liste avec la politique d'egress de l'organisation ; un wildcard n'élargit jamais ce que l'organisation autorise. Les sockets Bun/Node directs, ports alternatifs, adresses privées, rebinding DNS et sorties par redirection sont refusés à la frontière de la microVM vendeur. La DLP sortante analyse la requête matérialisée qui quitterait la VM, pas seulement les arguments de l'outil.
Autorisations de la plateforme
Certaines opérations de la plateforme agissent pour l'organisation au-delà du code de votre extension. Votre extension déclare chacune de celles dont elle a besoin. Une organisation y consent en installant ou en activant l'extension : l'administrateur voit ce qu'elle déclare sur l'écran Connect, dans l'onglet Version & security de l'extension, et sur sa ligne du catalogue, à côté de Enable. Il n'y a pas d'étape d'approbation distincte.
import { EXTENSION_PLATFORM_PERMISSIONS } from "@scrydon/sdk-authoring/extensions/types";
export default defineExtension({
id: "acme",
// …
platformPermissions: [EXTENSION_PLATFORM_PERMISSIONS.CORTEX_MESSAGES_SEND],
});| Autorisation | Ce qu'elle permet à vos outils | Sans elle |
|---|---|---|
cortex.messages.send | Appeler ctx.action.collaboration.post pour publier dans un message direct ou un canal Cortex en tant que Cortex, au nom du Run As de l'exécution. Proposer les sélecteurs Person et Channel (selectorKey: "cortex.people" ou "cortex.channels"). | L'appel échoue avec une Error dont le message commence par extension_permission_not_granted:. Reconnaissez ce préfixe. |
Le SDK de la plateforme (ctx.action.*, ctx.data.*, ctx.ai.*) n'est disponible aujourd'hui que pour les outils Scrydon révisés. Un outil d'une extension que votre organisation installe s'exécute isolé, sans ce SDK, et tout appel au SDK y échoue avec Platform SDK unavailable in vendor tool execution. Un outil révisé garde le SDK quand l'organisation ou son manifeste le fait passer à l'isolation microVM, pour les opérations qu'il liste dans platformOperations (voir platformOperations). Chaque appel est contrôlé de la même façon que dans le processus de la plateforme elle-même. Déclarer une autorisation reste utile pour toute extension : le manifeste refuse un sélecteur de destinataire Cortex sans cortex.messages.send, et la plateforme applique la déclaration partout où le SDK de la plateforme est disponible.
- La plateforme refuse l'opération quand votre manifeste ne déclare pas l'autorisation, ou quand le produit sous lequel votre outil s'exécute n'est pas activé pour l'organisation. Une extension activée par défaut compte comme activée tant qu'un administrateur ne l'a pas désactivée.
- Désactiver le produit retire le consentement. L'appel suivant est refusé, y compris un appel fait plus tard dans une exécution déjà en cours.
- Si la plateforme ne peut pas lire si le produit est activé, l'appel échoue avec une erreur de la plateforme et rien n'est publié. Ce n'est pas un refus
extension_permission_not_granted. - La déclaration est lue dans la version qui s'exécute. Une version qui ne déclare plus une autorisation la perd dès qu'elle s'exécute.
- Une version qui déclare une nouvelle autorisation prend effet selon la politique de mise à jour de l'organisation. Le consentement est l'installation ou l'activation, et une mise à jour n'est ni l'une ni l'autre : la revue de mise à jour ne la signale donc pas.
- Un champ de bloc ou de déclencheur qui utilise une clé de sélecteur Cortex exige
cortex.messages.send. Un manifeste qui en utilise une sans la déclarer est refusé à la construction ou au téléversement. - Déclarer deux fois la même autorisation est une erreur de manifeste.
Configuration de l'authentification
Le champ auth définit les types de credentials pris en charge par votre fournisseur :
auth: {
credentials: {
none: { type: "none" },
},
default: "none",
}auth: {
credentials: {
apiKey: {
type: "apiKey",
label: "API Key",
description: "Your service API key",
headerName: "Authorization", // HTTP header name
headerPrefix: "Bearer", // Prefix before the key value
identityScope: "application", // "application" or "principal" — required for apiKey/basicAuth/oauthApp
},
},
default: "apiKey",
}auth: {
credentials: {
oauth: {
type: "oauth",
label: "Connect Account",
authorizationUrl: "https://example.com/oauth/authorize",
tokenUrl: "https://example.com/oauth/token",
scopes: ["read", "write"],
pkce: true,
useBasicAuth: false,
supportsRefreshTokenRotation: true,
},
},
default: "oauth",
}auth: {
credentials: {
botToken: {
type: "botToken",
label: "Bot Token",
description: "Your bot authentication token",
headerName: "Authorization",
headerPrefix: "Bot",
},
},
default: "botToken",
}Vous pouvez définir plusieurs types de credentials et laisser l'utilisateur choisir :
auth: {
credentials: {
oauth: { type: "oauth", /* ... */ },
apiKey: { type: "apiKey", /* ... */ },
},
default: "oauth", // Which one to show first
}Champs de configuration
Pour les fournisseurs nécessitant des paramètres configurés par l'administrateur (ex. une URL de base auto-hébergée) :
configFields: [
{
key: "baseUrl",
label: "Base URL",
placeholder: "https://your-instance.example.com",
description: "The base URL of your self-hosted instance",
required: true,
type: "url",
},
],Secrets
Pour les secrets au niveau du fournisseur stockés dans le Dapr Secret Store :
secrets: [
{
key: "WEBHOOK_SIGNING_SECRET",
label: "Webhook Signing Secret",
description: "Used to verify incoming webhook payloads",
required: true,
},
],Les secrets sont référencés à l'exécution via ctx.secrets["WEBHOOK_SIGNING_SECRET"]. Dans le
chemin gouverné d'outil en microVM vendeur, les chaînes de credential et de secret sont des
placeholders limités à l'invocation. Transmettez-les sans modification au fetch global, que le sandbox fait passer par le broker
d'egress de confiance. Le broker remplace la valeur réelle uniquement dans un emplacement de requête autorisé.
Ne les inspectez, transformez, persistez, journalisez ou retournez jamais.
Extensions de connexion uniquement
auth et configFields décrivent ce que contient une connexion à votre extension : le
formulaire qu'un administrateur remplit. Une extension dont les seuls membres sont des sources de
données les déclare de la même façon, sur defineExtension. Elle n'a besoin d'aucun toolkit :
export default defineExtension({
id: "home-assistant",
name: "Home Assistant",
version: "1.0.0",
logo: "./assets/icon.svg",
configFields: [
{ key: "baseUrl", label: "Home Assistant URL", type: "url", purpose: "endpoint", required: true },
],
auth: {
credentials: {
token: { type: "apiKey", label: "Long-lived access token", identityScope: "application" },
},
default: "token",
},
provides: { dataSources: [homeAssistantStates] }, // lit endpointRef "baseUrl", authRef "token"
});Une source qui n'a besoin d'aucun credential mais prend son URL dans la connexion déclare le champ
d'endpoint et un credential none :
export default defineExtension({
// ...
configFields: [
{ key: "baseUrl", label: "Server URL", type: "url", purpose: "endpoint", required: true },
],
auth: { credentials: { none: { type: "none" } }, default: "none" },
provides: { dataSources: [publicFeed] }, // lit endpointRef "baseUrl", sans authRef
});extension build écrit la déclaration dans code/manifest.json avec toolkits: [] et
models: []. La plateforme n'exécute jamais ce bundle : rien n'y est appelable. Une extension
déclare une connexion lorsqu'elle a au moins un champ de configuration, ou un credential d'un type
autre que none. Sans l'un ni l'autre, la construction n'émet aucun code/, et une source qui
nomme un champ d'endpoint ou un credential est refusée.
extension build comme extension test --level static refusent une source dont endpointRef,
authRef ou credentialRef nomme quelque chose que l'extension ne déclare pas. Le message indique
la clé à ajouter. La façon dont une source lit ces champs est décrite dans
Endpoints depuis une connexion.
Une version de la plateforme antérieure aux extensions de connexion uniquement refuse une telle
archive à l'envoi avec code_validation_failed.
defineToolkit(config)
Regroupe des outils, des déclencheurs et un bloc sous un toolkit pouvant être activé ou désactivé par organisation. Les runtimes de modèles ne se déclarent PAS ici : ils appartiennent à une famille de modèles, voir defineModels() ci-dessous.
interface ToolkitInput {
/** Unique provided-item ID within the extension */
id: string;
/** Display name */
name: string;
/** Short description */
description?: string;
/** Inline SVG string (falls back to the extension's logo if omitted) */
logo?: string;
/** Which credential from auth.credentials this toolkit uses */
credentialRef?: string;
/** OAuth scopes required by this toolkit */
credentialScopes?: string[];
/** Executable tools */
tools?: ToolDefinition[];
/** Triggers — rendered in the editor's "Triggers" section */
triggers?: TriggerDefinition[];
/** Main toolkit block on the canvas (omit for trigger-only toolkits) */
block?: BlockDefinition;
webhooks?: WebhookCapability;
discovery?: DiscoveryCapability;
webSearch?: WebSearchCapability;
/** Resource catalog; actions opt into exact resources separately */
resources?: Array<{
resourceType: string;
displayName: string;
description?: string;
allowMultiple?: boolean;
}>;
/** NPM package dependencies (for risk assessment at upload time) */
dependencies?: Array<{
type: "npm";
package: string;
version: string;
reason?: string;
}>;Le tableau dependencies sert de contexte lisible par un humain — chaque entrée inclut un reason expliquant pourquoi la dépendance est nécessaire. La CLI génère automatiquement un SBOM lisible par machine (meta/sbom.cdx.json) qui capture l'ensemble complet des paquets archivés avec leurs versions et licences. Les deux sont complémentaires : les dépendances déclarées expliquent l'intention, le SBOM fournit l'inventaire auditable.
/** System-mode (client_credentials) configuration */
systemMode?: {
supported: boolean;
permissions?: string[];
};
}Extensions de consentement système
Les credentials OAuth peuvent déclarer des points d'extension sûrs pour le manifeste afin de gérer des flux de configuration spécifiques au fournisseur, comme le consentement administrateur Microsoft. Cette logique doit rester dans l'extension : le manifeste doit décrire la forme de l'URL du fournisseur, les champs de configuration de l'organisation requis, et les paramètres de rappel (callback).
Pour la plupart des fournisseurs, implémentez le consentement système via un modèle de redirection déclaratif. C'est la voie prise en charge lorsque la plateforme n'a besoin que de construire une URL à partir de valeurs de runtime :
auth: {
credentials: {
oauth: {
type: "oauth",
label: "Microsoft Account",
authorizationUrl: "https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/authorize",
tokenUrl: "https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token",
requiredConfig: ["tenantId"],
supportsClientCredentials: true,
clientCredentialScopes: ["https://graph.microsoft.com/.default"],
extensions: {
systemConsent: {
kind: "redirect-template",
urlTemplate: "https://login.microsoftonline.com/{tenantId}/adminconsent",
params: {
client_id: "{clientId}",
redirect_uri: "{redirectUri}",
scope: "{scope}",
state: "{state}",
},
requiredConfig: ["tenantId"],
callback: {
successParam: "admin_consent",
tenantParam: "tenant",
},
},
},
},
},
}La plateforme substitue ces espaces réservés :
| Espace réservé | Valeur |
|---|---|
{clientId} | ID client OAuth résolu pour l'organisation |
{redirectUri} | URL de rappel de consentement administrateur de la plateforme |
{scope} | clientCredentialScopes joints par un espace |
{state} | État CSRF généré par la plateforme |
Toute clé requiredConfig, comme {tenantId} | Valeur de configuration du fournisseur pour l'organisation |
Si un fournisseur ne peut pas être représenté sous forme de modèle d'URL, déclarez une référence de gestionnaire dans le manifeste et implémentez la méthode référencée dans le runtime du fournisseur. La méthode doit être pure : accepter des valeurs de runtime résolues, valider toute configuration spécifique au fournisseur requise, et retourner l'URL de redirection plutôt que de coder en dur la logique du fournisseur dans la plateforme.
extensions: {
systemConsent: {
kind: "handler",
handlerRef: "microsoft.systemConsent.buildUrl",
callback: {
handlerRef: "microsoft.systemConsent.callback",
},
},
}type SystemConsentBuildUrlInput = {
clientId: string;
redirectUri: string;
state?: string;
scopes: string[];
providerConfig: Record<string, string | undefined>;
};
export async function buildUrl(input: SystemConsentBuildUrlInput): Promise<string> {
const tenantId = input.providerConfig.tenantId;
if (!tenantId) {
throw new Error("Microsoft admin consent requires tenantId");
}
const url = new URL(`https://login.microsoftonline.com/${tenantId}/adminconsent`);
url.searchParams.set("client_id", input.clientId);
url.searchParams.set("redirect_uri", input.redirectUri);
url.searchParams.set("scope", input.scopes.join(" "));
if (input.state) {
url.searchParams.set("state", input.state);
}
return url.toString();
}
type SystemConsentCallbackInput = {
query: Record<string, string | undefined>;
providerConfig: Record<string, string | undefined>;
};
export async function callback(input: SystemConsentCallbackInput) {
return {
approved: input.query.admin_consent === "True",
tenantId: input.query.tenant,
};
}Les références de gestionnaire sont des métadonnées du manifeste. Les archives téléversées doivent aujourd'hui utiliser kind: "redirect-template", sauf si l'application hôte a explicitement câblé l'exécution du runtime pour le gestionnaire référencé.
Extensions multi-toolkits
Les extensions comme Google ou Microsoft fournissent plusieurs toolkits. Chaque toolkit dispose de son propre bloc dans l'éditeur de workflow et peut être activé indépendamment :
export default defineExtension({
id: "acme",
// ...
provides: {
toolkits: [
defineToolkit({
id: "acme-crm",
name: "Acme CRM",
tools: [listContacts, createContact],
block: crmBlock,
triggers: [],
}),
defineToolkit({
id: "acme-email",
name: "Acme Email",
tools: [sendEmail, listInbox],
block: emailBlock,
triggers: [],
}),
],
},
});defineTool(config)
Définit la logique de runtime d'un outil. C'est ici que se font vos appels API réels, transformations de données ou calculs.
interface ToolInput<TInput, TOutput> {
/** Unique tool ID — convention: {vendor}:{product}:{action} */
id: string;
/** Display name */
name: string;
/** Semantic version */
version: string;
/** Description shown to users and LLM agents */
description?: string;
/** Zod schema for input validation */
input: z.ZodType<TInput>;
/** Zod schema for output validation */
output: z.ZodType<TOutput>;
/** Parameter metadata — controls visibility in the UI and to LLMs */
params?: Record<string, ParamDef>;
/** Exact product resources authorized before this action dispatches */
resourceAuthorization?: Array<{
required?: boolean;
oneOf: Array<{
resourceType: string;
source: { kind: "input"; key: string } | { kind: "binding" };
}>;
}>;
/** Platform SDK operations this tool calls, e.g. "action.sms.send" */
platformOperations?: PlatformDescriptorKey[];
/** The function that runs when this tool is invoked */
execute: (input: TInput, ctx: PureContext) => Promise<ToolResponse<TOutput>>;
}
interface ParamDef {
type: string;
required?: boolean;
description?: string;
default?: unknown;
/** Controls where this parameter appears */
visibility?: "user-only" | "user-or-llm" | "hidden";
}product.resources est un catalogue, pas une exigence d'exécution globale. Déclarez la cible
réelle de chaque action avec resourceAuthorization. Les règles provenant d'une entrée sont
obligatoires ; seule une règle provenant du binding peut utiliser required: false pour conserver
le comportement délégué à la racine du compte, par exemple /me. Les cibles déléguées sont
vérifiées en direct auprès du fournisseur ; les cibles app/système exigent une attribution exacte
du même produit, type et identifiant de ressource dans l'espace de travail.
L'extraction du manifeste conserve les descriptions Zod et les contraintes
primitives prises en charge, comme la longueur des chaînes et les bornes
minimale/maximale des entiers. Pour z.preprocess(...), le JSON Schema généré
décrit le schéma de sortie validé plutôt que la transformation de prétraitement,
qui ne peut pas être représentée en JSON Schema. Gardez ce schéma de sortie
fidèle aux valeurs que l'outil accepte : son caractère optionnel et ses
contraintes sont ce que consomment les éditeurs de workflow et les descripteurs
d'outils des Agents.
platformOperations
Listez chaque opération du SDK de la plateforme que l'outil appelle, par sa clé : ctx.action.sms.send(...) correspond à "action.sms.send". Seul un outil Scrydon révisé reçoit le SDK de la plateforme.
export const sendSms = defineTool({
id: "send",
name: "Send SMS",
version: "1.0.0",
platformOperations: ["action.sms.send"],
// …
});- Quand l'outil s'exécute isolé dans une microVM, il ne peut appeler que les opérations qu'il liste. Tout autre appel échoue avec une
Errordont le message indique que l'opérationis not declared by this tool (platformOperations). - Une clé inconnue ou répétée est une erreur de manifeste.
- Chaque appel est soumis à la politique de DLP et de modération de votre organisation et à ses plafonds de dépenses. La politique s'applique à l'entrée de l'appel et à son résultat, et au-delà d'un plafond de dépenses appliqué, l'appel est refusé avant de s'exécuter, avec une
Errordont le message estPlatform operation quota was exceeded. - Un outil qui ne liste aucune opération s'exécute isolé sans le SDK de la plateforme.
- Si le déploiement ne peut pas donner son SDK à un outil isolé, l'outil échoue avec
TOOL_VENDOR_PLATFORM_SDK_UNAVAILABLEavant de s'exécuter, et non en cours de route. - Certaines opérations n'admettent qu'un seul produit Scrydon, quoi qu'un manifeste liste.
ctx.action.notification.notify(« Notify me », voir Notifications) est refusé avecexecution_refusedpour tout outil qui n'est pas le Notify me de Scrydon : il notifie le Run As de l'exécution, donc aucun autre outil ne peut envoyer en son nom.
description
Rédigez une description pour chaque outil. Cortex et le bloc Agent ne listent que quelques outils d'emblée et trouvent les autres par une recherche d'outils. Cette recherche classe les outils selon leur nom, leur description et le nom du produit, et renvoie les huit meilleurs. Un outil sans description n'est classé que sur son nom : la recherche peut ne pas le trouver, et le modèle n'a rien pour choisir entre des outils semblables.
- Commencez par un verbe, et dites ce que fait l'outil et ce qu'il renvoie.
- Nommez les entrées principales, leurs valeurs par défaut et leurs limites.
- Employez les mots que les gens recherchent. Un outil qui lit le courrier doit dire « mail », « e-mails » et « boîte de réception », pas seulement « messages ».
- Pour un outil d'écriture ou destructif, énoncez clairement l'effet de bord : « Envoie … », « Supprime définitivement … ».
Quand deux outils correspondent aussi bien à une recherche, celui dont
l'effect vaut read est listé en premier.
Visibilité des paramètres
| Visibilité | Qui peut fournir la valeur | Cas d'usage |
|---|---|---|
user-or-llm | L'utilisateur la définit dans l'interface, ou un agent IA la génère | La plupart des paramètres |
user-only | Seul l'utilisateur peut la configurer (masquée du LLM) | Paramètres de connexion, credentials |
hidden | Non affichée dans l'interface ni au LLM — définie par programmation | Identifiants internes, valeurs calculées |
effect
Indiquez ce que fait l'exécution de l'outil. Les assistants s'en servent pour décider s'ils peuvent exécuter l'outil sans demander d'abord à la personne.
| Valeur | Signification | Exemple |
|---|---|---|
read | Ne fait que récupérer des données qui existent déjà. | Lister des messages, obtenir un ticket |
write | Crée, modifie ou envoie quelque chose, ou dépense au nom de la personne. | Envoyer un e-mail, créer un événement, générer une image |
destructive | Supprime quelque chose, ou ne peut pas être annulé. | Supprimer un fichier, purger une file |
import { defineTool, TOOL_EFFECTS } from "@scrydon/sdk-authoring/extensions/define";
export const listMessages = defineTool({
id: "list-messages",
name: "List messages",
version: "1.0.0",
effect: TOOL_EFFECTS.READ,
// …
});effect est facultatif. Un outil qui n'en déclare pas est traité comme write : un assistant demande donc avant de l'exécuter. En cas de doute, indiquez write.
Fonction d'exécution
La fonction execute reçoit l'entrée validée et un PureContext :
import { requireOAuthToken } from "@scrydon/sdk-authoring/extensions/context";
async execute(input, ctx) {
// Use the logger (visible in workflow execution logs)
ctx.logger.info(`Processing request for org ${ctx.execution.orgId}`);
// Narrow on ctx.auth.kind before reading credential fields.
// This example assumes the vendor declares an OAuth credential —
// requireOAuthToken throws a CredentialKindError if the kind doesn't match.
const accessToken = requireOAuthToken(ctx.auth);
// Make authenticated API calls
const response = await fetch("https://api.example.com/data", {
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify(input),
});
if (!response.ok) {
return {
success: false,
output: { error: `API returned ${response.status}` },
error: `HTTP ${response.status}: ${response.statusText}`,
};
}
const data = await response.json();
return {
success: true,
output: data,
metadata: { requestId: response.headers.get("x-request-id") },
};
},PureContext
L'interface PureContext est la seule interface entre la plateforme et le code de l'archive. Elle est injectée dans chaque appel à execute() au moment de l'exécution :
/**
* Discriminated union of all credential shapes the platform can inject.
* Always narrow on `kind` before reading credential fields.
* Import narrowing helpers from @scrydon/sdk-authoring/extensions/context:
* requireOAuthToken(auth) → string (accessToken, throws on mismatch)
* requireOAuth(auth) → { accessToken, refreshToken?, tokenType?, expiresAt? }
* requireApiKey(auth) → string (apiKey)
* requireBasicAuth(auth) → { username, password }
* requireBotToken(auth) → string (botToken)
* Each helper throws a CredentialKindError with an actionable message on mismatch.
*/
type PureContextAuth =
| { kind: "oauth"; accessToken: string; refreshToken?: string; tokenType?: string; expiresAt?: number }
| { kind: "apiKey"; apiKey: string }
| { kind: "basicAuth"; username: string; password: string }
| { kind: "botToken"; botToken: string }
| { kind: "none" };
interface PureContext {
/** Credential resolved by the platform before invocation */
auth: PureContextAuth;
/** Execution metadata for logging and correlation */
execution: {
executionId: string; // Unique execution run ID
workflowId?: string; // Workflow ID (if running in a workflow)
nodeId?: string; // Block node ID in the workflow
orgId: string; // Organization ID
workspaceId?: string; // Workspace ID
userId?: string; // User who triggered the execution
};
/** Structured logger — console calls are intercepted in the sandbox */
logger: {
info(message: string, ...args: unknown[]): void;
warn(message: string, ...args: unknown[]): void;
error(message: string, ...args: unknown[]): void;
debug(message: string, ...args: unknown[]): void;
};
/** Vendor secrets configured by org admin (resolved per-invocation via Dapr Secret Store) */
secrets: Record<string, string>;
/** Present when executing via an Extension Profile — contains profile-specific overrides */
profileConfig?: Record<string, unknown>;
}PureContext est importé depuis @scrydon/sdk-authoring/extensions/context. La plateforme le construit avant chaque invocation d'outil — votre code n'a jamais besoin d'en créer un manuellement. Tous les champs sauf auth et execution peuvent être des objets vides si non applicables. auth est une union discriminée (PureContextAuth) ; filtrez toujours sur auth.kind ou utilisez un helper require* avant d'accéder aux champs spécifiques au credential. Consultez Modes d'authentification pour la référence complète de la forme par mode.
ToolResponse
Chaque execute() doit retourner un ToolResponse :
interface ToolResponse<T = unknown> {
/** Whether the tool execution succeeded */
success: boolean;
/** The typed output data */
output: T;
/** Stable machine-readable error code */
error?: string;
/** Human-readable failure explanation; preferred over error for display */
message?: string;
/** Optional metadata (logged but not passed to downstream blocks) */
metadata?: Record<string, unknown>;
}defineBlock(config)
Définit l'interface utilisateur de l'éditeur de workflow — comment le bloc apparaît, quels champs de formulaire il contient et comment les données le traversent.
interface BlockInput {
/** Unique block type — lowercase with underscores (e.g. "acme_crm") */
type: string;
/** Display name in the block palette */
name: string;
/** Short description */
description?: string;
/** Sidebar section: "integration" for an extension's toolkit block ("trigger" is set by defineTrigger, "core" is built-in) */
category: string;
/** Background color in hex */
bgColor?: string;
/** Auth mode: "none", "apiKey", "oauth", "botToken" */
authMode?: string;
/** Form fields displayed in the block panel */
subBlocks?: SubBlockInput[];
/** Which tools this block can invoke */
tools?: {
access: string[];
config?: {
/** Dynamic tool selection based on user input */
tool: (params: Record<string, any>) => string;
/** Parameter transformation before passing to tool */
params?: (params: Record<string, any>) => any;
};
};
/** Input connections from upstream blocks */
inputs?: Record<string, { type: string; description?: string }>;
/** Output connections to downstream blocks */
outputs?: Record<string, { type: string; description?: string }>;
/** Trigger configuration (for trigger blocks) */
triggers?: { enabled: boolean; available: string[] };
}Ce que déclare outputs
À l'exécution, la sortie d'un bloc est la charge utile output renvoyée par son outil, à laquelle
s'ajoute error lorsque l'outil a échoué. Ce n'est pas l'objet { success, output } que
renvoie execute() : un bloc en aval lit <my_block.records>, jamais
<my_block.output.records> ni <my_block.success>. Une charge utile qui n'est pas un objet (un
tableau, une chaîne) arrive sous value.
Déclarez dans outputs les clés de premier niveau des schémas output de vos outils, plus
error. L'éditeur propose exactement ces clés comme références <my_block.key> ; une clé
qu'aucun outil ne renvoie est donc une référence qui ne se résout jamais. Les extensions natives
sont tenues à cette règle par un test qui compare les outputs de chaque bloc aux schémas de
sortie de ses outils.
const listRecords = defineTool({
// ...
output: z.object({ records: z.array(z.record(z.string(), z.unknown())), nextLink: z.string().optional() }),
});
defineBlock({
// ...
outputs: {
records: { type: "json", description: "Rows returned by List Records" },
nextLink: { type: "string", description: "Link to the next page, when there is one" },
error: { type: "string", description: "Error message when the operation failed" },
},
});Types de SubBlock
Les SubBlocks sont les champs de formulaire qui apparaissent lorsqu'un utilisateur clique sur un bloc :
| Type | Description | Propriétés clés |
|---|---|---|
short-input | Champ de texte sur une ligne | placeholder, password, defaultValue, generatedDefault, readOnly, showCopyButton |
long-input | Zone de texte multiligne | placeholder |
code | Éditeur de code avec coloration syntaxique | placeholder, mode, generationType |
dropdown | Menu de sélection | options: [{ label, id }] |
combobox | Liste déroulante avec recherche | options, fetchOptions |
slider | Curseur de plage | min, max, step |
switch | Interrupteur à bascule | defaultValue |
oauth-input | Sélecteur de credential OAuth | serviceId, requiredScopes |
Définition de SubBlock
interface SubBlockInput {
/** Unique field ID within the block */
id: string;
/** Field label */
title?: string;
/** SubBlock type */
type: string;
/** Layout: "full" (default) or "half" */
layout?: "full" | "half";
/** Whether this field is required */
required?: boolean;
/** Placeholder text */
placeholder?: string;
/** Mask input as password dots */
password?: boolean;
/** Default value */
defaultValue?: unknown;
/** Generate a fresh value once when each new block is created */
generatedDefault?: "uuid";
/**
* For `type: "code"` fields only: what kind of content this field holds
* (e.g. `"json-object"`, `"javascript-function-body"`, `"system-prompt"`).
* Drives both the editor's default placeholder and the AI wand's prompt.
*/
generationType?:
| "javascript-function-body"
| "typescript-function-body"
| "json-schema"
| "json-object"
| "system-prompt"
| "custom-tool-schema"
| "sql-query"
| "postgrest"
| "mongodb-filter"
| "mongodb-pipeline"
| "mongodb-sort"
| "mongodb-documents"
| "mongodb-update"
| "neo4j-cypher"
| "neo4j-parameters";
/** Merge this field's JSON object into the tool input (see below) */
spreadIntoToolInput?: boolean;
/** Send this field as a tool input of another name (see below) */
canonicalParamId?: string;
/** Options for dropdown/combobox */
options?: Array<{ label: string; id: string }>;
/** Help text shown below the field */
description?: string;
/** Conditional visibility based on another field's value */
condition?: { field: string; value: string };
/** Whether the field is read-only */
readOnly?: boolean;
/** Show a copy-to-clipboard button */
showCopyButton?: boolean;
/** Hide this field from the UI */
hidden?: boolean;
}Utilisez generatedDefault: "uuid" lorsque chaque nouveau bloc doit recevoir
sa propre valeur initiale, par exemple un secret de webhook. Ce marqueur
déclaratif traverse la frontière JSON du manifeste de l'extension ; l'UUID
généré est enregistré comme valeur ordinaire du champ et n'est pas régénéré au
chargement du workflow.
generationType sur les champs code
Définissez generationType sur un champ type: "code" pour déclarer le type de
contenu qu'il contient. C'est ce qu'utilise l'éditeur de workflow pour choisir un
texte d'exemple par défaut et pour formuler correctement l'invite de la baguette
IA — sans lui, un champ code dépourvu de valeur sœur language: "json" affiche
par défaut un modèle de corps de fonction JavaScript, même pour un champ qui est
en réalité un objet JSON :
{
id: "params",
title: "Operation Parameters (JSON)",
type: "code",
generationType: "json-object",
placeholder: '{ "key": "value" }',
}Si vous définissez votre propre placeholder, il est toujours affiché —
generationType ne fournit un texte d'exemple que si vous n'en avez pas défini.
Les valeurs prises en charge sont "javascript-function-body",
"typescript-function-body", "json-object", "json-schema",
"system-prompt", "sql-query", "custom-tool-schema" et quelques formes de
requêtes propres à certaines bases de données ("postgrest",
"mongodb-filter", "mongodb-pipeline", "mongodb-sort",
"mongodb-documents", "mongodb-update", "neo4j-cypher",
"neo4j-parameters"). Ce champ est optionnel et additif : les extensions
construites avant son existence continuent de fonctionner sans modification.
Sélection dynamique d'outil
Un bloc dont tools.access liste plus d'un outil doit déclarer un
tools.toolSelector, qui associe chaque option d'un sous-bloc liste déroulante
à l'outil qu'elle exécute. extension build et extension test --level static
refusent un bloc multi-outils qui n'en a pas (contrôle statique S19) : sinon la
plateforme appellerait le premier outil de access pour chaque opération
choisie par l'utilisateur.
defineBlock({
// ...
subBlocks: [
{
id: "operation",
title: "Operation",
type: "dropdown",
options: [
{ label: "Create", id: "create" },
{ label: "Read", id: "read" },
{ label: "Update", id: "update" },
{ label: "Delete", id: "delete" },
],
},
// ... other fields
],
tools: {
access: ["acme_create", "acme_read", "acme_update", "acme_delete"],
toolSelector: {
param: "operation",
map: {
create: "acme_create",
read: "acme_read",
update: "acme_update",
delete: "acme_delete",
},
default: "acme_read",
},
},
});Paramètres JSON génériques
Un bloc qui propose de nombreuses opérations ne peut généralement pas donner
un champ à chaque paramètre d'outil, car les paramètres diffèrent d'une
opération à l'autre. Donnez un champ aux paramètres communs et regroupez les
autres dans un seul champ JSON marqué spreadIntoToolInput :
defineBlock({
// ...
subBlocks: [
{ id: "operation", title: "Operation", type: "dropdown", options: [/* ... */] },
{ id: "owner", title: "Repository Owner", type: "short-input" },
{
id: "params",
title: "Operation Parameters (JSON)",
type: "code",
placeholder: '{ "title": "...", "body": "..." }',
spreadIntoToolInput: true,
},
],
});À l'exécution du bloc, la plateforme analyse ce champ et fusionne ses clés dans
l'entrée de l'outil : { "title": "Bug" } parvient à l'outil sous la clé
title, et non comme une chaîne sous params. La valeur est lue après la
résolution des références du workflow ; elle peut donc être une référence
unique vers un objet produit par un autre bloc — <compose.result.params> —
ce qui est la manière fiable de transmettre un texte contenant des guillemets
ou des sauts de ligne.
| Règle | Comportement |
|---|---|
| Un champ rempli et une clé JSON portent le même nom | Le champ l'emporte. Un champ vide, non. |
| Le JSON est invalide, ou n'est pas un objet | Le bloc échoue avec TOOL_INPUT_SPREAD_INVALID ; l'outil n'est pas appelé. |
Une clé désigne un identifiant ou une connexion (accessToken, apiKey, credentialId, …) ou commence par _ | Le bloc échoue. Les identifiants proviennent de la connexion, jamais des paramètres du bloc. |
| Le champ est vide | Rien n'est fusionné. |
Ceci remplace une fonction tools.config.params. Un manifeste ne peut pas
contenir de fonction ; un tel mappeur ne s'exécute donc jamais dans un
workflow, et le champ JSON parviendrait à votre outil comme une seule chaîne
non analysée.
Des champs aux entrées de l'outil
Un champ parvient à l'outil comme l'entrée du même nom. Tout ce dont un bloc a besoin pour transmettre la bonne entrée à son outil est déclaré, pas calculé :
| Besoin | Déclaration |
|---|---|
Un champ envoyé sous un autre nom d'entrée (fileIdManual → fileId) | canonicalParamId: "fileId" sur le champ. Plusieurs champs peuvent partager un même canonicalParamId ; celui que l'opération choisie affiche, et que l'utilisateur a rempli, est envoyé. |
| Une même valeur envoyée sous des noms d'entrée différents selon l'opération | Un champ par entrée, chacun avec une condition sur les opérations qui l'utilisent. |
| Un nombre, un booléen, une liste ou un objet | Rien. Déclarez le type dans le schéma input de l'outil : la plateforme y convertit le texte du champ avant l'exécution de l'outil (ci-dessous). |
Une autre forme ("2026-10-21T09:00:00Z" ou { dateTime }) | Acceptez les deux dans le schéma input de l'outil et normalisez dans execute(). |
Un champ du canevas stocke du texte. Lorsqu'un bloc s'exécute, la plateforme
convertit chaque valeur texte vers le type que le schéma input de l'outil
déclare pour elle :
| Le schéma déclare | Le champ contient | L'outil reçoit |
|---|---|---|
number / integer | 10, -1.5, 2e3 | 10, -1.5, 2000 (un integer doit être entier) |
boolean | true / false, sans tenir compte de la casse | true / false |
array / object | Du texte JSON | Le tableau ou l'objet analysé |
| Un tableau de chaînes | PROJ-1, PROJ-2 | ["PROJ-1", "PROJ-2"] (utilisez JSON pour un élément contenant une virgule) |
| Tout sauf une chaîne | Rien, ou seulement des espaces | Aucune entrée, donc la valeur par défaut de l'outil s'applique — jamais 0, false ou [] |
| Une chaîne, ou aucun type | N'importe quoi | Le texte inchangé |
Une valeur qui ne se convertit pas proprement (dix pour un nombre) est
transmise telle quelle, pour que la validation de l'outil la signale. Seul
l'appel d'outil propre au bloc est converti : les arguments d'outil d'un Agent
sont déjà typés.
Les fonctions tools.config.tool et tools.config.params sont refusées par
extension build. Elles ne peuvent pas être écrites dans manifest.json ;
la plateforme ne les a donc jamais exécutées : le routage retombait sur le
premier outil, et chaque renommage ou conversion qu'elles faisaient était
ignoré sans bruit.
Visibilité conditionnelle
Afficher ou masquer des champs en fonction des valeurs d'autres champs :
subBlocks: [
{
id: "authType",
title: "Auth Type",
type: "dropdown",
options: [
{ label: "API Key", id: "apiKey" },
{ label: "Username/Password", id: "basic" },
],
},
{
id: "apiKey",
title: "API Key",
type: "short-input",
password: true,
condition: { field: "authType", value: "apiKey" },
},
{
id: "username",
title: "Username",
type: "short-input",
condition: { field: "authType", value: "basic" },
},
{
id: "password",
title: "Password",
type: "short-input",
password: true,
condition: { field: "authType", value: "basic" },
},
],defineTrigger(config)
Définit un bloc déclencheur — un membre de workflow à part entière qui s'affiche dans la section latérale « Triggers » et porte des métadonnées de runtime décrivant comment il se déclenche (webhook, polling, ou les deux). La valeur de category est automatiquement définie à "trigger".
interface TriggerInput {
/** Unique block type */
type: string;
/** Display name */
name: string;
/** Description */
description?: string;
/** Background color in hex */
bgColor?: string;
/** Auth mode: "none", "apiKey", "oauth", "botToken" */
authMode?: string;
/** Form fields for trigger configuration */
subBlocks?: SubBlockInput[];
/** Which tools this trigger block can invoke */
tools?: {
access: string[];
config?: {
tool?: (params: Record<string, any>) => string;
params?: (params: Record<string, any>) => any;
};
};
/** Input connections */
inputs?: Record<string, { type: string; description?: string }>;
/** Output schema — what data the trigger emits */
outputs?: Record<string, { type: string; description?: string }>;
/** Provider identifier */
provider?: string;
/** Semantic version */
version?: string;
/** Runtime configuration — how events reach the workflow engine */
runtime?: {
webhook?: {
method?: "POST" | "GET" | "PUT" | "DELETE";
headers?: Record<string, string>;
};
polling?: PollingProvider;
};
}Gestion des secrets
Les fournisseurs peuvent déclarer des secrets chiffrés au repos et résolus par invocation. Les secrets sont limités à l'organisation et au fournisseur, de sorte que chaque organisation dispose de son propre jeu de credentials isolé.
Déclaration des secrets
Définissez les exigences en matière de secrets dans defineExtension() via le tableau secrets :
export default defineExtension({
id: "acme",
name: "Acme",
version: "1.0.0",
logo: acmeIcon,
auth: { credentials: { apiKey: { type: "apiKey", label: "API Key", identityScope: "application" } }, default: "apiKey" },
secrets: [
{
key: "WEBHOOK_SIGNING_SECRET",
label: "Webhook Signing Secret",
description: "Used to verify incoming webhook payloads",
required: true,
},
{
key: "ENCRYPTION_KEY",
label: "Encryption Key",
description: "AES key for encrypting sensitive payloads",
required: false,
},
],
provides: { toolkits: [/* ... */] },
});Chaque SecretInput a la forme suivante :
interface SecretInput {
/** Unique key within the vendor — must match ^[a-zA-Z0-9_-]+$ */
key: string;
/** Human-readable label shown in the admin UI */
label: string;
/** Help text describing what this secret is used for */
description?: string;
/** Whether the extension requires this secret to function */
required: boolean;
}Les clés de secret ne peuvent contenir que des caractères alphanumériques, des tirets et des underscores (^[a-zA-Z0-9_-]+$). Les clés ne correspondant pas à ce format sont rejetées lors de l'enregistrement.
Format des clés de secret
Les secrets sont stockés avec une clé structurée qui les rattache à l'organisation et au fournisseur :
extension:{orgId}:{extensionId}:{key}Par exemple, un secret avec la clé WEBHOOK_SIGNING_SECRET pour le fournisseur acme dans l'organisation org_abc123 est stocké sous :
extension:org_abc123:acme:WEBHOOK_SIGNING_SECRETCela garantit une isolation complète entre les organisations et entre les fournisseurs au sein d'une même organisation.
Chiffrement au repos
Toutes les valeurs de secret sont chiffrées avec AES-256-GCM avant d'être persistées. La stratégie de clé de chiffrement est configurée par organisation — LOCAL, BYOK ou HYOK. Les valeurs de secret ne sont jamais stockées en clair. Consultez Sécurité → Gestion des secrets pour le modèle complet.
Accès aux secrets au moment de l'exécution
ctx.secrets est un Record<string, string> plat contenant les clés configurées. Dans le chemin
gouverné en microVM vendeur, chaque valeur est un placeholder limité à l'invocation et adossé à un
grant scellé du broker :
import { defineTool } from "@scrydon/sdk-authoring/extensions/define";
import { z } from "zod";
export const verifyWebhook = defineTool({
id: "acme:webhooks:verify",
name: "Verify Webhook",
version: "1.0.0",
input: z.object({ payload: z.string(), signature: z.string() }),
output: z.object({ valid: z.boolean() }),
async execute(input, ctx) {
const signingSecret = ctx.secrets["WEBHOOK_SIGNING_SECRET"];
if (!signingSecret) {
return { success: false, output: { valid: false }, error: "Webhook signing secret not configured" };
}
const response = await fetch("https://api.acme.example/webhooks/verify", {
method: "POST",
headers: {
"content-type": "application/json",
"x-acme-signing-secret": signingSecret,
},
body: JSON.stringify(input),
});
return { success: true, output: { valid: response.ok } };
},
});Transmettez les placeholders sans modification au fetch global. Il n'existe pas de ctx.fetch : dans le sandbox, le fetch global passe par le broker d'egress. Le broker de confiance remplace la
valeur réutilisable après l'autorisation de destination et avant que la DLP sortante analyse la
requête réelle. Le guest, le système de fichiers, le modèle, le plan, les logs, les traces et la
réponse de l'outil ne reçoivent jamais la valeur réutilisable.
L'objet ne contient que les clés définies par l'administrateur de l'organisation ; une valeur absente reste absente. Les opérations locales qui doivent inspecter ou transformer un secret réutilisable, comme une signature HMAC dans le code d'un outil Agent non-Scrydon, sont intentionnellement indisponibles sur cette frontière.
Interface d'administration
Les administrateurs d'organisation configurent les secrets sous Paramètres > Plateforme > Extensions, dans la fiche du fournisseur. L'interface affiche les champs de secret déclarés dans le tableau secrets du fournisseur.
Les valeurs de secret sont en écriture seule — une fois une valeur enregistrée, elle n'est plus jamais affichée. L'interface indique uniquement quelles clés ont été configurées (avec un indicateur masqué) et permet aux administrateurs de les mettre à jour ou de les supprimer.
Points de terminaison API
La plateforme expose trois points de terminaison internes pour la gestion des secrets :
| Point de terminaison | Objectif |
|---|---|
/extensions/authoring/admin/archive-secret-set | Créer ou mettre à jour une valeur de secret |
/extensions/authoring/admin/archive-secret-delete | Supprimer un secret |
/extensions/authoring/admin/archive-secret-list | Lister les clés de secret configurées (les valeurs ne sont jamais retournées) |
Ces points de terminaison sont internes (service à service) et nécessitent que l'appelant soit un service interne autorisé. Ils ne sont pas directement accessibles depuis les applications clientes.
CLI de build
# Build an archive
sdk-authoring extension build [--entry src/index.ts] [--outDir dist]
# Test an archive
sdk-authoring extension test [--level static|sandbox|live] [--tool <id>]Résultat du build
my-vendor-1.0.0.archive.tar.gz
├── manifest.json # Auto-generated metadata (JSON Schemas, UI defs)
├── dist/
│ └── index.js # Compiled, minified ESM (all deps inlined via esbuild)
├── meta/
│ ├── sbom.cdx.json # CycloneDX 1.6 SBOM (auto-generated)
│ └── metafile.json # esbuild dependency graph
└── assets/ # Optional icons
└── icon.svgLa CLI de build intègre toutes les dépendances npm dans le fichier dist/index.js unique et génère automatiquement un SBOM CycloneDX 1.6 dans meta/sbom.cdx.json. Il n'y a pas de npm install au moment de l'exécution — l'archive est entièrement autonome et déterministe.
Règles des identifiants de fournisseur
- Caractères alphanumériques minuscules et tirets uniquement
- Doit commencer par une lettre
- Format :
/^[a-z][a-z0-9-]*$/ - Exemples :
acme,my-vendor,hello-world
Convention des identifiants d'outil
Les identifiants d'outil utilisent le format complet {extensionId}:{providedId}:{action} au moment de l'exécution :
hello-world:hello-world:say-hellogoogle:sheets:readslack:messaging:send-message
Forme abrégée : Dans le code d'authoring, vous pouvez utiliser uniquement la partie action (ex. "say-hello", "read"). Le SDK qualifie automatiquement les identifiants courts en ajoutant {extensionId}:{providedId}: lors de l'appel à defineExtension(). Toutes les extensions natives utilisent la forme courte. Si vous utilisez la forme complète, le préfixe vendor/product doit correspondre — la CLI de build valide cela.
Helpers de capacités
Les capacités de modèle se déclarent une FAMILLE DE MODÈLES à la fois avec defineModels() ; le runtime de la famille est un objet simple typé par sa configuration de capacité. Les trois capacités qui ne sont pas des familles de modèles appartiennent à un toolkit et conservent chacune un petit wrapper de balise pour que l'extracteur de manifeste puisse les identifier.
| Helper | Déclare | Type de runtime (@scrydon/sdk-authoring/extensions/capabilities/<nom>) |
|---|---|---|
defineModels({ family: "llm", runtime, models, defaultModel }) | Fournisseur LLM complet (chat, complétion, streaming) | LLMCapabilityConfig (llm) |
defineModels({ family: "stt", runtime }) | Reconnaissance vocale (batch et temps réel) | STTCapabilityConfig (stt) |
defineModels({ family: "tts", runtime }) | Synthèse vocale (batch et temps réel) | TTSCapabilityConfig (tts) |
defineModels({ family: "embedding", runtime }) | Génération d'embeddings vectoriels | EmbeddingCapabilityConfig (embedding) |
defineModels({ family: "image", runtime }) | Génération d'images | ImageCapabilityConfig (image) |
defineModels({ family: "video", runtime }) | Génération vidéo | VideoCapabilityConfig (video) |
defineModels({ family: "ocr", runtime }) | Analyse de documents / reconnaissance optique de caractères | OCRCapabilityConfig (ocr) |
defineModels({ family: "moderation", runtime }) | Classification de sécurité du contenu | ModerationCapabilityConfig (moderation) |
defineWebSearch(config) — toolkit webSearch | Recherche internet propre au fournisseur | WebSearchCapabilityConfig (web-search) |
defineWebhooks(config) — toolkit webhooks | Gestion des webhooks spécifiques au fournisseur | WebhookCapabilityConfig (webhooks) |
defineDiscovery(config) — toolkit discovery | Navigation dans les ressources, sélecteurs, portées d'accès | DiscoveryCapabilityConfig (discovery) |
Les quatre helpers s'importent depuis @scrydon/sdk-authoring/extensions/define :
import {
defineModels,
defineWebSearch,
defineWebhooks,
defineDiscovery,
} from "@scrydon/sdk-authoring/extensions/define";Chaque résultat de defineModels() est une entrée de provides.models ; webSearch, webhooks et discovery sont en revanche des membres du toolkit. L'extracteur de manifeste convertit les fonctions de runtime en indicateurs booléens et extrait les métadonnées des modèles (dimensions, benchmarks, tarification) dans le champ capabilityMeta du manifeste. Consultez Capacités pour des exemples d'utilisation complets de chaque famille.
Schéma du manifeste
La CLI de build génère automatiquement manifest.json à partir de votre appel à defineExtension(). Le manifeste est un instantané sérialisable en JSON des métadonnées de votre fournisseur, sans les fonctions de runtime. Il suit l'unique forme prise en charge manifestVersion: 1. Toute autre version est rejetée.
Structure de niveau supérieur
{
"$schema": "...", // Optional JSON Schema URL
"manifestVersion": 1, // Always 1
"version": "1.0.0", // Vendor version (from defineExtension)
"vendor": { ... }, // Vendor metadata
"auth": { ... }, // Authentication config
"products": [ ... ], // Product definitions
"protocols": [ ... ], // Protocol definitions (optional)
"llm": { ... } // LLM provider config (optional)
}Section fournisseur
Contient les métadonnées d'affichage. Le code de runtime est exclu.
| Champ | Type | Obligatoire | Description |
|---|---|---|---|
id | string | Oui | Identifiant du fournisseur (/^[a-z][a-z0-9-]*$/) |
name | string | Oui | Nom d'affichage |
description | string | Non | Description courte |
color | string | Non | Couleur de marque en hexadécimal |
icon | string | Oui | Icône SVG sous forme de chaîne |
website | string | Non | URL du site web du fournisseur |
docsUrl | string | Non | URL de la documentation |
categories | string[] | Non | Étiquettes de catégorie |
defaultEnabled | boolean | Non | Activation automatique pour les nouvelles organisations (par défaut : false) |
supportsSystemMode | boolean | Non | Prend en charge client_credentials (par défaut : false) |
connectivity | "cloud" | "local" | "hybrid" | Non | Exigences réseau |
configFields | ConfigField[] | Non | Champs configurables par l'administrateur |
secrets | Secret[] | Non | Secrets au niveau du fournisseur |
Section produit
Chaque produit dans le tableau products contient :
| Champ | Type | Obligatoire | Description |
|---|---|---|---|
id | string | Oui | Identifiant du produit |
name | string | Oui | Nom d'affichage |
capabilities.tools | Tool[] | Non | Définitions d'outils avec JSON Schema entrée/sortie |
capabilities.triggers | Trigger[] | Non | Définitions de déclencheurs |
capabilities.runtimes | object | Non | Indicateurs booléens pour chaque runtime sélectionnable : llm, embedding, stt, tts, image, video, ocr, moderation, webSearch |
capabilities.capabilityMeta | object | Non | Métadonnées de modèles extraites (dimensions d'embedding, modèles STT/TTS, benchmarks) |
capabilities.webhooks | boolean | Non | Indique si le produit gère les webhooks |
capabilities.discovery | boolean | Non | Indique si le produit prend en charge la découverte de ressources |
workflow.block | Block | Non | Définition du bloc de l'éditeur de workflow |
workflow.triggers | TriggerBlock[] | Non | Blocs déclencheurs du workflow |
resources | Resource[] | Non | Catalogue de ressources, jamais une règle globale pour toutes les actions |
dependencies | Dependency[] | Non | Dépendances NPM déclarées avec leurs raisons |
systemMode.permissions | string[] | Non | Permissions de consentement app/système |
Comment le code de runtime devient des données de manifeste
La CLI de build transforme les définitions de runtime en JSON sûr pour le manifeste :
| Source (defineToolkit) | Sortie du manifeste |
|---|---|
runtimes.llm (avec fonction executeRequest) | runtimes.llm: true |
runtimes.embedding (avec fonction embed, modèles avec dimension) | runtimes.embedding: true + capabilityMeta.embeddingModels |
runtimes.stt (avec fonction transcribe, modèles) | runtimes.stt: true + capabilityMeta.sttModels |
runtimes.tts (avec fonction synthesize, modèles) | runtimes.tts: true + capabilityMeta.ttsModels |
runtimes.image (avec fonction de génération et modèles) | runtimes.image: true + métadonnées des modèles d'image |
runtimes.video (avec fonction de génération et modèles) | runtimes.video: true + métadonnées des modèles vidéo |
runtimes.ocr (avec fonction d'extraction et modèles) | runtimes.ocr: true + métadonnées des modèles OCR |
runtimes.moderation (avec fonction de classification et modèles) | runtimes.moderation: true + métadonnées des modèles de modération |
runtimes.webSearch (avec fonction de recherche) | runtimes.webSearch: true ; propre au fournisseur, sans politique de modèle |
input/output d'un outil (schémas Zod) | Objets JSON Schema |
Fonction execute d'un outil | Omise (non sérialisable) |
tools.toolSelector d'un bloc | Conservé comme métadonnées de routage compatibles JSON |
spreadIntoToolInput d'un sous-bloc | Conservé ; la plateforme fusionne l'objet JSON de ce champ dans l'entrée de l'outil à l'exécution |
Fonctions tools.config.tool / tools.config.params d'un bloc | Refusées : le build échoue (contrôle statique S19) |
Le manifeste est ce que la plateforme lit au démarrage pour alimenter le catalogue d'extensions. Il ne contient aucun code exécutable, uniquement des métadonnées, des JSON Schemas et des définitions UI. L'archive exécutable est chargée à la demande par le backend canonique. Les capacités et outils approuvés peuvent utiliser le backend de compatibilité Worker Thread ; un outil statique non-Scrydon sélectionné par un Agent isolé exige la microVM fournisseur épinglée à la source et validée par preuve.
Les manifestes d'extension ne peuvent pas contenir de closures de fonction. Utilisez tools.toolSelector pour le routage, canonicalParamId pour renommer l'entrée d'un champ, un champ spreadIntoToolInput pour les paramètres propres à chaque opération, et des schémas input d'outil typés — la plateforme convertit le texte des champs vers ces types. Les commandes de build et de test statique échouent sur une fonction tools.config.tool ou tools.config.params, et sur un bloc multi-outils sans tools.toolSelector.
Sessions continues de transcription
Une extension STT qui déclare des modèles en streaming doit implémenter
createStreamingSession(request) et openStreamingSession(request, context).
La compilation vérifie cette association. streaming.protocol est un identifiant
propre à l’extension : ajouter un fournisseur ne nécessite aucun adaptateur dans
l’application d’enregistrement.
Importez les types de session depuis
@scrydon/sdk-authoring/extensions/capabilities/stt-streaming :
import type {
SttSession,
SttSessionContext,
SttStreamingSessionRequest,
} from "@scrydon/sdk-authoring/extensions/capabilities/stt-streaming";
// Signature of runtime.openStreamingSession:
type OpenSpeechSession = (
request: SttStreamingSessionRequest,
context: SttSessionContext,
) => Promise<SttSession>;SttSession expose sendAudio(Uint8Array), finalize(), close(),
onTranscript(handler), onError(handler) et onClose(handler).
L’audio reçu est du PCM16 mono continu à 16 kHz. L’extension gère l’authentification,
le rééchantillonnage, le protocole, le renouvellement des identifiants et le SDK du
fournisseur. Utilisez les interfaces fetch et WebSocket contrôlées par le
sandbox ; les modules réseau natifs ne peuvent pas contourner ces contrôles. Le
sandbox expose les globaux web standard (fetch, WebSocket, crypto.subtle,
CompressionStream, DecompressionStream, TextEncoder, …) ainsi que les
modules Node de la liste blanche du bundler (node:crypto, node:buffer,
node:stream, node:util, node:url, node:path, node:events,
node:string_decoder, node:querystring). node:zlib n'est pas disponible —
son initialisation appelle process.binding, que le sandbox refuse — utilisez
DecompressionStream("gzip") ou une bibliothèque JS pure comme fflate.
Les champs context.auth, context.secrets et context.profileConfig reprennent
le contexte standard des extensions ; l’authentification n’est pas limitée aux
clés API. Respectez context.signal, libérez les ressources à la fermeture et traitez tout
l’audio accepté avant de terminer finalize().
Les résultats ont la forme { text, isFinal, startMs?, endMs?, confidence?, speakerId? }.
Les temps sont exprimés en millisecondes depuis le début audio de la session.
N’inventez pas de temps ni d’attribution manquants. Les identifiants de locuteur ne
sont valables que dans la session continue concernée ; ils n’identifient pas un
membre de l’organisation ni une personne dans une autre requête STT.
La plateforme fournit aux clients média une autorisation scrydon-stt-v1.
Les endpoints et identifiants du fournisseur restent dans l’extension, y compris
les valeurs direct et proxied de createStreamingSession.
Les sessions continues nécessitent actuellement du code d’extension revu, admis sur le backend worker. Si la politique exige un processus géré ou une microVM, le streaming est refusé avant l’émission du ticket : ces backends prennent en charge les appels requête/réponse. La transcription par lots conserve ses niveaux d’exécution habituels. Ne réduisez pas les exigences d’isolation pour contourner cette limite.
Lors de la mise à niveau, recompilez et réinstallez les extensions de streaming
avec le SDK d’authoring mis à jour. Les anciens bundles qui émettent uniquement
des tickets ne possèdent pas le nouveau hook de session. Déployez ensemble la
plateforme et le client média : celui-ci exige scrydon-stt-v1 et ne rétablit pas
les adaptateurs fournisseur dans l’application.
Modèles vocaux OpenAI de première partie
L’extension OpenAI utilise le même contrat de session que les autres extensions. Configurez sa connexion par clé API OpenAI, puis choisissez un modèle pour la capacité STT :
| Modèle | Entrée | Horodatage et locuteurs |
|---|---|---|
gpt-live-transcribe | Session en direct | Limites des tours via VAD serveur ; aucun horodatage par mot ni locuteur fourni |
gpt-transcribe | Fichier / énoncé | Texte et langues détectées ; aucun horodatage inventé |
gpt-4o-transcribe, gpt-4o-mini-transcribe | Fichier / énoncé | Texte sans horodatage par mot |
gpt-4o-transcribe-diarize | Fichier / énoncé | Segments horodatés avec locuteurs propres à la requête |
whisper-1 | Fichier / énoncé | Horodatage par mot ou segment ; traduction vers l’anglais |
Le modèle par défaut reste whisper-1. Les modèles en direct et sur fichier sont
séparés : un modèle réservé au direct ne peut pas traiter la transcription finale
d’un fichier. L’extension utilise le socket contrôlé par la plateforme, convertit
le PCM de 16 kHz à 24 kHz dans chaque session et attend les transcriptions finales
avant de terminer finalize(). Elle déclare la limite fournisseur de 60 minutes
pour le mécanisme de rotation existant. Le suivi des locuteurs reste indépendant
du STT ; les labels d’une requête ne constituent pas des identités entre requêtes.
Les fichiers conservent leur format audio, y compris les fenêtres FLAC de Recording
Studio, et sont limités à 25 Mo. La diarisation utilise diarized_json avec
chunking_strategy=auto, sans prompt ni option d’horodatage par mot non pris en charge.
L’accès aux modèles dépend du compte OpenAI configuré. Reconstruisez et réinstallez
l’extension OpenAI pour disposer des nouveaux modèles et du contrat de session.
Références fournisseur : transcription de fichiers, transcription en direct et cycle de vie des sessions.