GitHub Copilot SDK 插件目录(Plugin Directories)完全指南:打包 Skills、Hooks、MCP 与自定义 Agent 的单一加载单元
【免费下载链接】copilot-sdkMulti-platform SDK for integrating GitHub Copilot Agent into apps and services项目地址: https://gitcode.com/GitHub_Trending/co/copilot-sdk
插件目录(Plugin Directories)是 GitHub Copilot SDK 提供的一种扩展打包机制:一个plugin本质上是一个目录,通过单一清单把 SDK 扩展(skills、hooks、MCP servers、自定义 agents 以及 LSP 配置)捆绑在一起。只需把这个目录指给 SDK,插件贡献的所有扩展就会被一次性加载,从而让你能在宿主应用中打包并分发可复用的能力包,而无需在每一个宿主应用里编写逐项扩展的接线代码。
本文以 docs/features/plugin-directories.md 为主线,覆盖插件的目录布局、从 SDK 加载插件的三种途径(CLI 启动参数、会话级配置、受信任的内置目录)、插件与 marketplace 插件的区别、如何让插件集合具备确定性,并结合仓库源码(如 nodejs/src/client.ts、go/client.go、python/copilot/client.py)剖析底层 RPC 调用链路。读完本文,你将掌握在 Node.js/TypeScript、Python、Go、.NET、Java、Rust 六种语言中正确加载、验证与排障插件目录的完整实战方案。
何时使用插件目录
当你希望做到以下几点时,应当优先考虑插件目录:
- 把一组能力打包成单个单元分发:例如一个 "TypeScript reviewer" 能力包,内含一个 skill、一个强制 lint 的
preToolUsehook,以及一个运行评审流程的自定义 agent; - 把能力包 vendoring 进仓库:让宿主应用的每一次克隆都按确定性加载同一组扩展;
- 在本地开发插件,之后再将插件发布到 marketplace;
- 覆盖或扩展 marketplace 已安装的插件:用本地 checkout 进行测试。
反之,如果只是需要添加单个 MCP server、单个 hook 或单个自定义 agent,可以直接通过 SDK 配置(mcpServers、hooks、customAgents)内联注册。插件目录通常在三个及以上相关扩展需要一起发布时才最具价值。
插件文件夹布局
Copilot CLI 会扫描每个插件目录,寻找plugin.json清单或根目录级的SKILL.md。一个最小化的插件长这样:
my-plugin/ ├── plugin.json # 清单(除非只使用 SKILL.md,否则必填) ├── SKILL.md # 可选:顶层 skill ├── hooks.json # 可选:hooks 配置 ├── .mcp.json # 可选:MCP server 配置 ├── agents/ # 可选:自定义 agents(每个 agent 一个 .md 文件) │ └── code-reviewer.md └── skills/ # 可选:额外 skills └── lint-fix/ └── SKILL.md清单还可以放在.github/plugin.json或.github/plugin/plugin.json,这样插件可以嵌在既有仓库中而无需改动仓库根布局。每个子系统(hooks、MCP、LSP、skills、agents)都有各自的加载器,并且都是可选的——插件只需包含它实际贡献的部分即可。完整的清单 schema 可参见 CLI 的/plugin斜杠命令所引用的运行时文档。
从 SDK 加载插件目录
插件目录通过给 Copilot CLI 传入--plugin-dir <path>加载(由 SDK 负责拉起 CLI)。各语言通过运行时连接的 extra-args 选项暴露该参数,且该参数可以重复传入以加载多个插件。
Node.js / TypeScript
import { CopilotClient, RuntimeConnection } from "@github/copilot-sdk"; async function main() { const client = new CopilotClient({ connection: RuntimeConnection.forStdio({ args: [ "--plugin-dir", "./plugins/code-reviewer", "--plugin-dir", "./plugins/lint-fix", ], }), }); await client.start(); } main();Python
from copilot import CopilotClient, StdioRuntimeConnection client = CopilotClient( connection=StdioRuntimeConnection( args=( "--plugin-dir", "./plugins/code-reviewer", "--plugin-dir", "./plugins/lint-fix", ), ), ) await client.start()Go
client := copilot.NewClient(&copilot.ClientOptions{ Connection: copilot.StdioConnection{ Args: []string{ "--plugin-dir", "./plugins/code-reviewer", "--plugin-dir", "./plugins/lint-fix", }, }, }) if err := client.Start(ctx); err != nil { return err }.NET
using GitHub.Copilot; await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForStdio(args: new[] { "--plugin-dir", "./plugins/code-reviewer", "--plugin-dir", "./plugins/lint-fix", }), }); await client.StartAsync();Java
var options = new CopilotClientOptions() .setCliArgs(new String[] { "--plugin-dir", "./plugins/code-reviewer", "--plugin-dir", "./plugins/lint-fix", }); var client = new CopilotClient(options); client.start().get();Rust
use github_copilot_sdk::{Client, ClientOptions}; let client = Client::start( ClientOptions::new().with_extra_args([ "--plugin-dir", "./plugins/code-reviewer", "--plugin-dir", "./plugins/lint-fix", ]), ) .await?;上面的示例使用 stdio 运行时连接——这是 SDK 捆绑 CLI 时的默认方式。如果你通过 URL 连接到外部运行时(
forUri/ForUri),则需要在启动该长驻 CLI server 时自行传入--plugin-dir;SDK 不会把--plugin-dir转发给它没有拉起的运行时。这一点在 nodejs/src/client.ts 的启动逻辑中体现得很清楚:只有当连接类型不是外部 server 时,SDK 才会自己拉起 CLI server 进程并注入 extra args。
每会话级插件目录(Per-session plugin directories)
--plugin-dir是启动参数,因此它一次性固定了整个 CLI 进程及其上创建的所有会话的插件集合。当不同会话需要不同的插件集合,或者 SDK 连接的是一个并非自己拉起的运行时,应当把目录放到会话配置中传递。这些目录会随session.create与session.resume的 payload 通过 JSON-RPC 传输,而不是作为进程参数,因此对于外部运行时也能以与启动选项相同的方式生效。
以 Node.js / TypeScript 为例:
import { CopilotClient } from "@github/copilot-sdk"; const client = new CopilotClient(); await client.start(); const session = await client.createSession({ pluginDirectories: ["./plugins/code-reviewer"], });各 SDK 中对应的会话级选项如下:
| SDK | Session 选项 |
|---|---|
| Node.js / TypeScript | pluginDirectories: string[] |
| Python | plugin_directories=[...] |
| Go | PluginDirectories: []string{...} |
| .NET | PluginDirectories = [...] |
| Java | .setPluginDirectories(List.of(...)) |
| Rust | .with_plugin_directories([...]) |
路径解析规则:相对路径会相对于workingDirectory解析;若未设置工作目录,则相对于运行时的工作目录,因此推荐使用绝对路径。无法解析的条目只会被记录日志并跳过,而不会导致会话创建失败。该选项是显式 opt-in 的,意味着即使enableConfigDiscovery为false,插件 agents 和规则也会被加载。以这种方式加载的资源,在会话级优先级顺序中位于项目源与个人/主目录源之间。
从源码可以看到实现细节:nodejs/src/client.ts 在组装session.create请求时把pluginDirectories: config.pluginDirectories直接放入 payload;go/client.go 同样在会话请求中设置req.PluginDirectories = config.PluginDirectories;python/copilot/client.py 则执行payload["pluginDirectories"] = plugin_directories。三条实现路径殊途同归:插件目录随会话请求体通过 JSON-RPC 到达运行时。
受信任的宿主内置插件目录(Trusted host-bundled plugin directories)
对于自带可信插件的应用,可以把这些目录注册为客户端启动选项。SDK 会在连接并校验协议之后、start返回或任何会话创建之前,把完整的、保序的目录集合发送给运行时。路径必须是绝对路径;若该选项未设置或为空数组,则完全不会发起 RPC 调用。
以 Node.js / TypeScript 为例:
import { CopilotClient } from "@github/copilot-sdk"; async function main() { const client = new CopilotClient({ builtinPluginDirectories: [ "/opt/my-app/copilot-plugins/core", "/opt/my-app/copilot-plugins/github", ], }); await client.start(); } main();各 SDK 中对应的启动选项如下:
| SDK | 启动选项 |
|---|---|
| Node.js / TypeScript | builtinPluginDirectories: string[] |
| Python | builtin_plugin_directories=[...] |
| Go | BuiltinPluginDirectories: []string{...} |
| .NET | BuiltinPluginDirectories = [...] |
| Java | .setBuiltinPluginDirectories(List.of(Path.of(...))) |
| Rust | .with_builtin_plugin_directories([...]) |
这是一个信任边界:它只用于宿主应用自己捆绑并控制的插件。它和--plugin-dir有本质区别——后者是 CLI 进程的启动参数,用于显式加载普通插件目录。由于该启动选项通过 JSON-RPC 传输(而非转发进程参数),因此连接既有运行时也同样有效。
源码证据非常直接:
- nodejs/src/client.ts:构造函数会遍历
builtinPluginDirectories,用isAbsolute校验每个路径,任何非绝对路径都会抛出"builtinPluginDirectories must contain only absolute paths: ..."错误; - nodejs/src/client.ts:
doStart()在连接 server 并完成协议版本校验之后,调用plugins.builtin.setRPC,发送{ paths: this.builtinPluginDirectories },失败则forceStop并抛出异常; - go/client.go 与 python/copilot/client.py:Go 与 Python 实现同样强制执行绝对路径校验;
- go/client.go、python/copilot/client.py:Go 与 Python 也在客户端启动流程中发送
plugins.builtin.set请求。
插件可以贡献什么
加载一个插件目录后,其扩展对该客户端创建的每一个会话都可见。运行时会把插件提供的扩展与你在 SDK 内联注册的内容合并在一起:
| 插件贡献 | 会话中可见为 |
|---|---|
Skills(SKILL.md、skills/*/SKILL.md) | session.skills.list()中的条目;可按名称注入 |
自定义 agents(agents/*.md) | 可通过task(agent_type=...)工具调度 |
Hooks(hooks.json) | 与通过 SDK 注册的 hooks 一起触发 |
MCP servers(.mcp.json) | 通过session.mcp.*可访问的工具与资源 |
LSP servers(.lsp.json) | 通过session.lsp.initialize(...)初始化 |
插件 agents 是 Fleet Mode 中的一等公民子 agent:父 agent 可以按agent_type调度它们,运行时也会像对待其他子 agent 一样为它们触发subagentStart/subagentStophooks。
插件目录 vs Marketplace 插件
运行时存在两种安装插件的方式,二者对会话最终呈现的效果相同:
- Marketplace / 直接仓库插件:通过 CLI 的
/plugin斜杠命令或底层的installedPlugins用户设置持久安装。它们是ambient(环境性)的——任何针对同一用户配置运行的会话都会看到它们,并参与插件发现规则; --plugin-dir插件:是explicit(显式)且 ephemeral(临时)的——只对用该标志启动的那个 CLI 进程生效。它们优先于 ambient 发现,并会与具有相同缓存路径的 marketplace 条目去重,因此当两种途径都引用同一个插件时不会加载两次。
对于 SDK 驱动的应用,--plugin-dir通常是更合适的选择:它让插件集合始终处于应用的掌控之下,而不依赖于每台机器的用户状态。
让插件集合具备确定性
当宿主机器上可能安装了其他插件(marketplace 或个人插件)时,在运行时环境中设置COPILOT_PLUGIN_DIR_ONLY=true可以抑制自动插件发现——只有通过--plugin-dir传入的目录会被加载。
以 Node.js / TypeScript 为例:
process.env.COPILOT_PLUGIN_DIR_ONLY = "true"; const client = new CopilotClient({ connection: RuntimeConnection.forStdio({ args: ["--plugin-dir", "./plugins/code-reviewer"], }), }); await client.start();这一做法非常适合 CI、无头服务器部署,以及任何希望插件集合可复现、不依赖宿主用户配置的场景。
检查哪些插件已加载
会话创建后,可以通过列出活动插件来确认目录是否被正确拾取:
import { CopilotClient } from "@github/copilot-sdk"; const client = new CopilotClient(); await client.start(); const session = await client.createSession({ onPermissionRequest: async () => ({ kind: "approve-once" }), }); const plugins = await session.rpc.plugins.list(); for (const plugin of plugins.plugins) { console.log(`${plugin.name} (${plugin.enabled ? "enabled" : "disabled"})`); }通过--plugin-dir加载的插件会出现在该列表中,其缓存路径(cache path)被设置为你提供的目录;marketplace 安装项则会标记其 registry 来源。
故障排查
- "no plugin.json or SKILL.md found in <dir>"—— 目录存在但不满足插件资格。请在根目录(或
.github/下)添加plugin.json清单,或放入一个顶层SKILL.md; - 插件已加载但 agents/skills 不可见—— 确保插件清单声明了其贡献的 agents/skills,或使用隐式布局(
agents/*.md、skills/*/SKILL.md)。随后调用session.rpc.skills.reload()即可在不重启的情况下拾取变更; - 重复 hooks 触发—— 运行时按
cache_path去重,但仅当同一个目录同时被引用为 marketplace 安装项和--plugin-dir时才会去重。如果两个不同目录包含同一个插件,两者都会加载。请移除其中一个,或使用COPILOT_PLUGIN_DIR_ONLY=true; - 连接外部运行时后
--plugin-dir被忽略—— SDK 只在自行拉起 CLI 时才转发 extra args。对于外部运行时(forUri/ForUri),请把--plugin-dir传给启动该运行时 server 的命令行。
延伸阅读
- 自定义 Agents:编写随插件
agents/目录分发的 agents; - Skills:
SKILL.md文件的加载方式与 skill 层级排序规则; - Hooks:插件定义的 hooks 与 SDK 注册的 hooks 一起触发;
- MCP Servers:插件提供的 MCP servers 与内联注册的集成方式相同;
- Fleet Mode:插件提供的 agents 可作为子 agent 调度。
【免费下载链接】copilot-sdkMulti-platform SDK for integrating GitHub Copilot Agent into apps and services项目地址: https://gitcode.com/GitHub_Trending/co/copilot-sdk
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考