Assistants d'exécution des plugins
Référence de l’objet api.runtime injecté dans chaque plugin lors de l’enregistrement. Utilisez ces assistants au lieu d’importer directement les éléments internes de l’hôte.
Guide pas à pas qui utilise ces assistants dans le contexte des plugins de channel.
Guide pas à pas qui utilise ces assistants dans le contexte des plugins de provider.
register(api) { const runtime = api.runtime;}Chargement et écriture de la configuration
Section intitulée « Chargement et écriture de la configuration »Privilégiez la configuration qui a déjà été transmise au chemin d’appel actif, par exemple api.config lors de l’enregistrement ou un argument cfg sur les rappels channel/provider. Cela permet à un instantané du processus de circuler dans le travail au lieu de réanalyser la configuration sur les chemins critiques.
Utilisez api.runtime.config.current() uniquement lorsqu’un gestionnaire de longue durée a besoin de l’instantané actuel du processus et qu’aucune configuration n’a été transmise à cette fonction. La valeur retournée est en lecture seule ; clonez-la ou utilisez un assistant de mutation avant de la modifier.
Les usines d’outils reçoivent ctx.runtimeConfig ainsi que ctx.getRuntimeConfig(). Utilisez le getter dans le rappel execute d’un outil de longue durée lorsque la configuration peut changer après la création de la définition de l’outil.
Persistez les modifications avec api.runtime.config.mutateConfigFile(...) ou api.runtime.config.replaceConfigFile(...). Chaque écriture doit choisir une politique afterWrite explicite :
afterWrite: { mode: "auto" }laisse le planificateur de rechargement de la passerelle décider.afterWrite: { mode: "restart", reason: "..." }force un redémarrage propre lorsque l’enregistreur sait que le rechargement à chaud n’est pas sûr.afterWrite: { mode: "none", reason: "..." }supprime le rechargement/redémarrage automatique uniquement lorsque l’appelant gère la suite.
Les assistants de mutation renvoient afterWrite ainsi qu’un résumé typé followUp afin que les appelants puissent enregistrer ou tester s’ils ont demandé un redémarrage. La passerelle (gateway) conserve toujours la maîtrise du moment où ce redémarrage a réellement lieu.
api.runtime.config.loadConfig() et api.runtime.config.writeConfigFile(...) sont des assistants de compatibilité obsolètes sous runtime-config-load-write. Ils avertissent une fois lors de l’exécution et restent disponibles pour les anciens plugins externes pendant la fenêtre de migration. Les plugins groupés ne doivent pas les utiliser ; les gardes de la limite de configuration échouent si le code du plugin les appelle ou importe ces assistants depuis les sous-chemins du SDK de plugin.
Pour les importations directes du SDK, utilisez les sous-chemins de configuration ciblés au lieu du tonneau (barrel) de compatabilité large openclaw/plugin-sdk/config-runtime : config-contracts pour les types, plugin-config-runtime pour les assertions de configuration déjà chargées et la recherche des entrées de plugin, runtime-config-snapshot pour les instantanés du processus actuel, et config-mutation pour les écritures. Les tests de plugins groupés doivent simuler (mock) directement ces sous-chemins ciblés au lieu de simuler le tonneau de compatibilité large.
Le code d’exécution interne OpenClaw suit la même direction : charger la configuration une fois à la frontière du CLI, de la passerelle ou du processus, puis transmettre cette valeur. Les écritures de mutation réussies rafraîchissent l’instantané d’exécution du processus et avancent sa révision interne ; les caches longue durée doivent se baser sur la clé de cache détenue par l’exécution au lieu de sérialiser la configuration localement. Les modules d’exécution longue durée disposent d’un scanner de tolérance zéro pour les appels ambiants loadConfig() ; utilisez un cfg transmis, une context.getRuntimeConfig() de requête, ou getRuntimeConfig() à une frontière de processus explicite.
Les chemins d’exécution des providers et des canaux doivent utiliser l’instantané de configuration d’exécution active, et non un instantané de fichier renvoyé pour la lecture ou l’édition de la configuration. Les instantanés de fichiers préservent les valeurs sources telles que les marqueurs SecretRef pour l’interface utilisateur et les écritures ; les rappels de provider ont besoin de la vue d’exécution résolue. Lorsqu’un assistant peut être appelé avec soit l’instantané source actif soit l’instantané d’exécution actif, passez par selectApplicableRuntimeConfig() avant de lire les identifiants.
Utilitaires d’exécution réutilisables
Section intitulée « Utilitaires d’exécution réutilisables »Utilisez les faits entrants botLoopProtection pour les messages entrants rédigés par le bot. Le cœur applique le garde-fenêtre glissant partagé en mémoire avant l’enregistrement de la session et l’envoi, sans lier la stratégie à un channel unique. Le garde suit les clés (scopeId, conversationId, participant pair), compte ensemble les deux directions d’une paire, applique un temps de recharge une fois le budget de fenêtre dépassé, et nettoie les entrées inactives de manière opportuniste.
Les plugins de canal qui exposent ce comportement aux opérateurs devraient privilégier la forme partagée channels.defaults.botLoopProtection pour les budgets de base, puis ajouter par-dessus des redéfinitions spécifiques au canal/fournisseur. La configuration partagée utilise des secondes car elle est destinée à l’utilisateur :
type ChannelBotLoopProtectionConfig = { enabled?: boolean; maxEventsPerWindow?: number; windowSeconds?: number; cooldownSeconds?: number;};Passez les faits normalisés de paire de bots avec le tour résolu. Le Core résout les valeurs par défaut, la conversion des unités et la sémantique enabled :
return { channel: "example", routeSessionKey, storePath, ctxPayload, recordInboundSession, runDispatch, botLoopProtection: { scopeId: "account-1", conversationId: "channel-1", senderId: "bot-a", receiverId: "bot-b", config: channelConfig.botLoopProtection, defaultsConfig: runtimeConfig.channels?.defaults?.botLoopProtection, defaultEnabled: allowBotsMode !== "off", },};Utilisez openclaw/plugin-sdk/pair-loop-guard-runtime directement uniquement pour les boucles d’événements personnalisées à deux parties qui ne passent pas par le lanceur de réponse entrant partagé.
Espaces de noms d’exécution
Section intitulée « Espaces de noms d’exécution »api.runtime.agent
Identité de l’agent, répertoires et gestion de session.
// Resolve the agent's working directoryconst agentDir = api.runtime.agent.resolveAgentDir(cfg);
// Resolve agent workspaceconst workspaceDir = api.runtime.agent.resolveAgentWorkspaceDir(cfg);
// Get agent identityconst identity = api.runtime.agent.resolveAgentIdentity(cfg);
// Get default thinking levelconst thinking = api.runtime.agent.resolveThinkingDefault({ cfg, provider, model,});
// Validate a user-provided thinking level against the active provider profileconst policy = api.runtime.agent.resolveThinkingPolicy({ provider, model });const level = api.runtime.agent.normalizeThinkingLevel("extra high");if (level && policy.levels.some((entry) => entry.id === level)) { // pass level to an embedded run}
// Get agent timeoutconst timeoutMs = api.runtime.agent.resolveAgentTimeoutMs(cfg);
// Ensure workspace existsawait api.runtime.agent.ensureAgentWorkspace(cfg);
// Run an embedded agent turnconst agentDir = api.runtime.agent.resolveAgentDir(cfg);const result = await api.runtime.agent.runEmbeddedAgent({ sessionId: "my-plugin:task-1", runId: crypto.randomUUID(), sessionFile: path.join(agentDir, "sessions", "my-plugin-task-1.jsonl"), workspaceDir: api.runtime.agent.resolveAgentWorkspaceDir(cfg), prompt: "Summarize the latest changes", timeoutMs: api.runtime.agent.resolveAgentTimeoutMs(cfg),});runEmbeddedAgent(...) est la fonction d’aide neutre pour démarrer un tour d’agent OpenClaw normal depuis le code du plugin. Elle utilise la même résolution de fournisseur/modèle et la même sélection de harnais d’agent que les réponses déclenchées par le channel.
runEmbeddedPiAgent(...) reste un alias de compatibilité déconseillé pour les plugins existants. Le nouveau code devrait utiliser runEmbeddedAgent(...).
resolveThinkingPolicy(...) renvoie les niveaux de réflexion pris en charge par le fournisseur/modèle et l’option par défaut facultative. Les plugins fournisseur possèdent le profil spécifique au modèle via leurs hooks de réflexion, donc les plugins outil doivent appeler cette fonction d’aide d’exécution au lieu d’importer ou de dupliquer les listes de fournisseurs.
normalizeThinkingLevel(...) convertit le texte utilisateur tel que on, x-high ou extra high au niveau stocké canonique avant de le vérifier par rapport à la stratégie résolue.
Les aides de stockage de session se trouvent sous api.runtime.agent.session :
const entry = api.runtime.agent.session.getSessionEntry({ agentId, sessionKey });for (const { sessionKey, entry } of api.runtime.agent.session.listSessionEntries({ agentId })) { // Iterate session rows without depending on the legacy sessions.json shape.}await api.runtime.agent.session.patchSessionEntry({ agentId, sessionKey, update: (entry) => ({ thinkingLevel: "high" }),});Privilégiez getSessionEntry(...), listSessionEntries(...), patchSessionEntry(...) ou upsertSessionEntry(...) pour les flux de travail de session. Ces aides adressent les sessions par identité agent/session, de sorte que les plugins ne dépendent pas de la forme de stockage héritée sessions.json. Utilisez preserveActivity: true pour les correctifs métadonnées uniquement qui ne doivent pas actualiser l’activité de session, et replaceEntry: true uniquement lorsque le rappel renvoie une entrée complète et que les champs supprimés doivent rester supprimés. loadSessionStore(...) reste une échappatoire de compatibilité déconseillée pour les appelants qui ont intentionnellement besoin d’un clone mutable de l’intégralité du magasin.
api.runtime.agent.defaults
Constantes de modèle et de provider par défaut :
const model = api.runtime.agent.defaults.model; // e.g. "anthropic/claude-sonnet-4-6"const provider = api.runtime.agent.defaults.provider; // e.g. "anthropic"api.runtime.llm
Exécutez une complétion de texte appartenant à l’hôte sans importer les éléments internes du fournisseur ou dupliquer la préparation du modèle/de l’auth/de l’URL de base OpenClaw.
const result = await api.runtime.llm.complete({ messages: [{ role: "user", content: "Summarize this transcript." }], purpose: "my-plugin.summary", maxTokens: 512, temperature: 0.2,});L’assistant utilise le même chemin de préparation de complétion simple que le runtime
intégré OpenClaw et l’instantané de la configuration du runtime appartenant à l’hôte. Les moteurs de contexte
reçoivent une capacité llm.complete liée à la session, de sorte que les appels du modèle utilisent l’agent
de la session active et ne retombent pas silencieusement sur l’agent par défaut. Le
résultat inclut l’attribution fournisseur/modèle/agent ainsi que les jetons normalisés,
le cache et l’utilisation estimée des coûts lorsque disponibles.
api.runtime.subagent
Lancez et gérez les exécutions de sous-agents en arrière-plan.
// Start a subagent runconst { runId } = await api.runtime.subagent.run({ sessionKey: "agent:main:subagent:search-helper", message: "Expand this query into focused follow-up searches.", provider: "openai", // optional override model: "gpt-4.1-mini", // optional override deliver: false,});
// Wait for completionconst result = await api.runtime.subagent.waitForRun({ runId, timeoutMs: 30000 });
// Read session messagesconst { messages } = await api.runtime.subagent.getSessionMessages({ sessionKey: "agent:main:subagent:search-helper", limit: 10,});
// Delete a sessionawait api.runtime.subagent.deleteSession({ sessionKey: "agent:main:subagent:search-helper",});deleteSession(...) peut supprimer les sessions créées par le même plugin via api.runtime.subagent.run(...). La suppression de sessions utilisateur ou opérateur arbitraires nécessite toujours une requête Gateway avec portage d’administrateur.
api.runtime.nodes
Lister les nœuds connectés et invoquer une commande node-host à partir du code de plugin chargé par le Gateway ou à partir des commandes CLI du plugin. Utilisez ceci lorsqu’un plugin gère un travail local sur un appareil couplé, par exemple un navigateur ou un pont audio sur un autre Mac.
const { nodes } = await api.runtime.nodes.list({ connected: true });
const result = await api.runtime.nodes.invoke({ nodeId: "mac-studio", command: "my-plugin.command", params: { action: "start" }, timeoutMs: 30000,});À l’intérieur du Gateway , ce runtime est en cours de processus (in-process). Dans les commandes CLI du plugin, il appelle le Gateway configuré via RPC , de sorte que des commandes telles que openclaw googlemeet recover-tab peuvent inspecter les nœuds couplés depuis le terminal. Les commandes de nœud passent toujours par le couplage normal des nœuds du Gateway , les listes de permissions de commandes (allowlists), les stratégies d’invocation de nœuds de plugin, et le traitement local des commandes de nœud.
Les plugins qui exposent des commandes node-host dangereuses doivent enregistrer une stratégie d’invocation de nœud avec api.registerNodeInvokePolicy(...). La stratégie s’exécute dans le Gateway après les vérifications de la liste de permissions des commandes et avant que la commande ne soit transmise au nœud, de sorte que les appels directs node.invoke et les outils de plugin de niveau supérieur partagent le même chemin d’application.
api.runtime.tasks.managedFlows
Lier un runtime Task Flow à une clé de session OpenClaw existante ou à un contexte d’outil de confiance, puis créer et gérer des Task Flows sans passer de propriétaire à chaque appel.
Task Flow suit l’état de workflow durable en plusieurs étapes. Ce n’est pas un planificateur :
utilisez Cron ou api.session.workflow.scheduleSessionTurn(...) pour les réveils
futurs, puis utilisez managedFlows depuis le tour planifié lorsque ce travail
nécessite l’état du flux, des tâches enfants, des attentes ou une annulation.
const taskFlow = api.runtime.tasks.managedFlows.fromToolContext(ctx);
const created = taskFlow.createManaged({ controllerId: "my-plugin/review-batch", goal: "Review new pull requests",});
const child = taskFlow.runTask({ flowId: created.flowId, runtime: "acp", childSessionKey: "agent:main:subagent:reviewer", task: "Review PR #123", status: "running", startedAt: Date.now(),});
const waiting = taskFlow.setWaiting({ flowId: created.flowId, expectedRevision: created.revision, currentStep: "await-human-reply", waitJson: { kind: "reply", channel: "telegram" },});Utilisez bindSession({ sessionKey, requesterOrigin }) lorsque vous disposez déjà d’une clé de session OpenClaw de confiance issue de votre propre couche de liaison (binding layer). Ne vous liez pas à partir d’une entrée utilisateur brute.
api.runtime.tts
Synthèse vocale.
// Standard TTSconst clip = await api.runtime.tts.textToSpeech({ text: "Hello from OpenClaw", cfg: api.config,});
// Telephony-optimized TTSconst telephonyClip = await api.runtime.tts.textToSpeechTelephony({ text: "Hello from OpenClaw", cfg: api.config,});
// List available voicesconst voices = await api.runtime.tts.listVoices({ provider: "elevenlabs", cfg: api.config,});Utilise la configuration messages.tts principale et la sélection du fournisseur. Renvoie un tampon audio PCM + la fréquence d’échantillonnage.
api.runtime.mediaUnderstanding
Analyse d’image, d’audio et de vidéo.
// Describe an imageconst image = await api.runtime.mediaUnderstanding.describeImageFile({ filePath: "/tmp/inbound-photo.jpg", cfg: api.config, agentDir: "/tmp/agent",});
// Transcribe audioconst { text } = await api.runtime.mediaUnderstanding.transcribeAudioFile({ filePath: "/tmp/inbound-audio.ogg", cfg: api.config, mime: "audio/ogg", // optional, for when MIME cannot be inferred});
// Describe a videoconst video = await api.runtime.mediaUnderstanding.describeVideoFile({ filePath: "/tmp/inbound-video.mp4", cfg: api.config,});
// Generic file analysisconst result = await api.runtime.mediaUnderstanding.runFile({ filePath: "/tmp/inbound-file.pdf", cfg: api.config,});
// Structured image extraction through a specific provider/model.// Include at least one image; text inputs are supplemental context.const evidence = await api.runtime.mediaUnderstanding.extractStructuredWithModel({ provider: "codex", model: "gpt-5.5", input: [ { type: "image", buffer: receiptImageBuffer, fileName: "receipt.png", mime: "image/png", }, { type: "text", text: "Prefer the printed total over handwritten notes." }, ], instructions: "Extract vendor, total, and searchable tags.", schemaName: "receipt.evidence", jsonSchema: { type: "object", properties: { vendor: { type: "string" }, total: { type: "number" }, tags: { type: "array", items: { type: "string" } }, }, required: ["vendor", "total"], }, cfg: api.config,});Renvoie { text: undefined } lorsque aucune sortie n’est produite (par exemple, entrée ignorée).
api.runtime.imageGeneration
Génération d’images.
const result = await api.runtime.imageGeneration.generate({ prompt: "A robot painting a sunset", cfg: api.config,});
const providers = api.runtime.imageGeneration.listProviders({ cfg: api.config });api.runtime.webSearch
Recherche Web.
const providers = api.runtime.webSearch.listProviders({ config: api.config });
const result = await api.runtime.webSearch.search({ config: api.config, args: { query: "OpenClaw plugin SDK", count: 5 },});api.runtime.media
Utilitaires médias de bas niveau.
const webMedia = await api.runtime.media.loadWebMedia(url);const mime = await api.runtime.media.detectMime(buffer);const kind = api.runtime.media.mediaKindFromMime("image/jpeg"); // "image"const isVoice = api.runtime.media.isVoiceCompatibleAudio(filePath);const metadata = await api.runtime.media.getImageMetadata(filePath);const resized = await api.runtime.media.resizeToJpeg(buffer, { maxWidth: 800 });const terminalQr = await api.runtime.media.renderQrTerminal("https://openclaw.ai");const pngQr = await api.runtime.media.renderQrPngBase64("https://openclaw.ai", { scale: 6, // 1-12 marginModules: 4, // 0-16});const pngQrDataUrl = await api.runtime.media.renderQrPngDataUrl("https://openclaw.ai");const tmpRoot = resolvePreferredOpenClawTmpDir();const pngQrFile = await api.runtime.media.writeQrPngTempFile("https://openclaw.ai", { tmpRoot, dirPrefix: "my-plugin-qr-", fileName: "qr.png",});api.runtime.config
Instantané de la configuration d’exécution actuelle et écritures de configuration transactionnelles. Privilégiez
la configuration qui a déjà été transmise au chemin d’appel actuel ; utilisez
current() uniquement lorsque le gestionnaire a besoin de l’instantané du processus directement.
const cfg = api.runtime.config.current();await api.runtime.config.mutateConfigFile({ afterWrite: { mode: "auto" }, mutate(draft) { draft.plugins ??= {}; },});mutateConfigFile(...) et replaceConfigFile(...) renvoient une valeur followUp,
par exemple { mode: "restart", requiresRestart: true, reason },
qui enregistre l’intention de l’auteur sans retirer le contrôle de redémarrage à la passerelle.
api.runtime.system
Utilitaires au niveau système.
await api.runtime.system.enqueueSystemEvent(event);api.runtime.system.requestHeartbeat({ source: "other", intent: "event", reason: "plugin-event",});api.runtime.system.requestHeartbeatNow({ reason: "plugin-event" }); // Deprecated compatibility alias.const output = await api.runtime.system.runCommandWithTimeout(cmd, args, opts);const hint = api.runtime.system.formatNativeDependencyHint(pkg);api.runtime.events
Abonnements aux événements.
api.runtime.events.onAgentEvent((event) => { /* ... */});api.runtime.events.onSessionTranscriptUpdate((update) => { /* ... */});api.runtime.logging
Journalisation.
const verbose = api.runtime.logging.shouldLogVerbose();const childLogger = api.runtime.logging.getChildLogger({ plugin: "my-plugin" }, { level: "debug" });api.runtime.modelAuth
Résolution de l’authentification du modèle et du provider.
const auth = await api.runtime.modelAuth.getApiKeyForModel({ model, cfg });const providerAuth = await api.runtime.modelAuth.resolveApiKeyForProvider({ provider: "openai", cfg,});api.runtime.state
Résolution du répertoire d’état et stockage à clé basé sur SQLite.
const stateDir = api.runtime.state.resolveStateDir(process.env);const store = api.runtime.state.openKeyedStore({ namespace: “my-feature”, maxEntries: 200, defaultTtlMs: 15 * 60_000, });
await store.register("key-1", { value: "hello" });const claimed = await store.registerIfAbsent("dedupe-key", { value: "first" });const value = await store.lookup("key-1");await store.consume("key-1");await store.clear();```
Les magasins à clé survivent aux redémarrages et sont isolés par l'identifiant du plugin lié au runtime. Utilisez `registerIfAbsent(...)` pour les revendications de déduplication atomique : elle renvoie `true` lorsque la clé était manquante ou expirée et a été enregistrée, ou `false` lorsqu'une valeur active existe déjà sans écraser sa valeur, son heure de création ou son TTL. Limites : `maxEntries` par espace de noms, 6 000 lignes actives par plugin, valeurs JSON inférieures à 64 Ko et expiration TTL facultative. Lorsqu'une écriture dépasserait la limite de lignes du plugin, le runtime peut évincer les lignes actives les plus anciennes de l'espace de noms en cours d'écriture ; les espaces de noms frères ne sont pas évincés pour cette écriture, et l'écriture échoue toujours si l'espace de noms ne peut pas libérer suffisamment de lignes.api.runtime.tools
Usines d’outils de mémoire et CLI.
const getTool = api.runtime.tools.createMemoryGetTool(/* ... */);const searchTool = api.runtime.tools.createMemorySearchTool(/* ... */);api.runtime.tools.registerMemoryCli(/* ... */);api.runtime.channel
Assistants d’exécution spécifiques au channel (disponibles lorsqu’un plugin de channel est chargé).
api.runtime.channel.media est l’interface recommandée pour les téléchargements et le stockage des médias de channel :
const saved = await api.runtime.channel.media.saveRemoteMedia({ url, subdir: "inbound", maxBytes, filePathHint: fileName,});Utilisez saveRemoteMedia(...) lorsqu’une URL distante doit devenir un média OpenClaw. Utilisez saveResponseMedia(...) lorsque le plugin a déjà récupéré un Response avec une gestion de l’authentification, de la redirection ou de la liste d’autorisation propre au plugin. Utilisez readRemoteMediaBuffer(...) uniquement lorsque le plugin a besoin des octets bruts pour inspection, transformation, déchiffrement ou réupload. fetchRemoteMedia(...) reste un alias de compatibilité obsolète pour readRemoteMediaBuffer(...).
api.runtime.channel.mentions est l’interface partagée pour la stratégie de mention entrante des plugins de channel groupés qui utilisent l’injection d’exécution :
const mentionMatch = api.runtime.channel.mentions.matchesMentionWithExplicit(text, { mentionRegexes, mentionPatterns,});
const decision = api.runtime.channel.mentions.resolveInboundMentionDecision({ facts: { canDetectMention: true, wasMentioned: mentionMatch.matched, implicitMentionKinds: api.runtime.channel.mentions.implicitMentionKindWhen( "reply_to_bot", isReplyToBot, ), }, policy: { isGroup, requireMention, allowTextCommands, hasControlCommand, commandAuthorized, },});Assistants de mention disponibles :
buildMentionRegexesmatchesMentionPatternsmatchesMentionWithExplicitimplicitMentionKindWhenresolveInboundMentionDecision
api.runtime.channel.mentions n’expose pas intentionnellement les anciens assistants de compatibilité resolveMentionGating*. Privilégiez le chemin normalisé { facts, policy }.
Stockage des références d’exécution
Section intitulée « Stockage des références d’exécution »Utilisez createPluginRuntimeStore pour stocker la référence d’exécution en vue d’une utilisation en dehors du rappel register :
Créer le magasin
import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";import type { PluginRuntime } from "openclaw/plugin-sdk/runtime-store";const store = createPluginRuntimeStore({ pluginId: “my-plugin”, errorMessage: “my-plugin runtime not initialized”, }); ```
Wire into the entry point
export default defineChannelPluginEntry({id: "my-plugin",name: "My Plugin",description: "Example",plugin: myPlugin,setRuntime: store.setRuntime,});Access from other files
export function getRuntime() {return store.getRuntime(); // throws if not initialized}export function tryGetRuntime() {return store.tryGetRuntime(); // returns null if not initialized}
Autres champs de niveau supérieur api
Section intitulée « Autres champs de niveau supérieur api »Au-delà de api.runtime, l’objet API fournit également :
Connexes
Section intitulée « Connexes »- Internes du plugin — model de capacité et registre
- Points d’entrée du SDK — options
definePluginEntry - Aperçu du SDK — référence de sous-chemin