概述
技能是一个具有名称的目录,其中包含一个 SKILL.md 文件——这是一份向 Copilot 提供指令的 Markdown 文档。 加载后,技能的内容将注入到会话上下文中。
技能允许您:
- 将域专业知识打包到可重用模块中
- 跨项目共享特定行为
- 整理复杂的代理配置
- 每个会话启用/禁用功能
加载技能
创建会话时,指定包含技能的目录:
代码语言 navigation
import { CopilotClient } from "@github/copilot-sdk";
const client = new CopilotClient();
const session = await client.createSession({
model: "gpt-5.4",
skillDirectories: [
"./skills/code-review",
"./skills/documentation",
],
onPermissionRequest: async () => ({ kind: "approve-once" }),
});
// Copilot now has access to skills in those directories
await session.sendAndWait({ prompt: "Review this code for security issues" });
from copilot import CopilotClient, PermissionDecisionApproveOnce
async def main():
client = CopilotClient()
await client.start()
session = await client.create_session(
on_permission_request=lambda req, inv: PermissionDecisionApproveOnce(),
model="gpt-5.4",
skill_directories=[
"./skills/code-review",
"./skills/documentation",
],
)
# Copilot now has access to skills in those directories
await session.send_and_wait("Review this code for security issues")
await client.stop()
package main
import (
"context"
"log"
copilot "github.com/github/copilot-sdk/go"
"github.com/github/copilot-sdk/go/rpc"
)
func main() {
ctx := context.Background()
client := copilot.NewClient(nil)
if err := client.Start(ctx); err != nil {
log.Fatal(err)
}
defer client.Stop()
session, err := client.CreateSession(ctx, &copilot.SessionConfig{
Model: "gpt-5.4",
SkillDirectories: []string{
"./skills/code-review",
"./skills/documentation",
},
OnPermissionRequest: func(req copilot.PermissionRequest, inv copilot.PermissionInvocation) (rpc.PermissionDecision, error) {
return &rpc.PermissionDecisionApproveOnce{}, nil
},
})
if err != nil {
log.Fatal(err)
}
// Copilot now has access to skills in those directories
_, err = session.SendAndWait(ctx, copilot.MessageOptions{
Prompt: "Review this code for security issues",
})
if err != nil {
log.Fatal(err)
}
}
using GitHub.Copilot;
using GitHub.Copilot.Rpc;
await using var client = new CopilotClient();
await using var session = await client.CreateSessionAsync(new SessionConfig
{
Model = "gpt-5.4",
SkillDirectories = new List<string>
{
"./skills/code-review",
"./skills/documentation",
},
OnPermissionRequest = (req, inv) =>
Task.FromResult(PermissionDecision.ApproveOnce()),
});
// Copilot now has access to skills in those directories
await session.SendAndWaitAsync(new MessageOptions
{
Prompt = "Review this code for security issues"
});
import com.github.copilot.CopilotClient;
import com.github.copilot.rpc.*;
import java.util.List;
try (var client = new CopilotClient()) {
client.start().get();
var session = client.createSession(
new SessionConfig()
.setModel("gpt-5.4")
.setSkillDirectories(List.of(
"./skills/code-review",
"./skills/documentation"
))
.setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
).get();
// Copilot now has access to skills in those directories
session.sendAndWait(new MessageOptions()
.setPrompt("Review this code for security issues")
).get();
}
禁用技能
禁用特定技能,同时使其他人保持活动状态:
代码语言 navigation
const session = await client.createSession({
skillDirectories: ["./skills"],
disabledSkills: ["experimental-feature", "deprecated-tool"],
});
from copilot.session import PermissionHandler
session = await client.create_session(
on_permission_request=PermissionHandler.approve_all,
skill_directories=["./skills"],
disabled_skills=["experimental-feature", "deprecated-tool"],
)
session, _ := client.CreateSession(context.Background(), &copilot.SessionConfig{
SkillDirectories: []string{"./skills"},
DisabledSkills: []string{"experimental-feature", "deprecated-tool"},
})
var session = await client.CreateSessionAsync(new SessionConfig
{
SkillDirectories = new List<string> { "./skills" },
DisabledSkills = new List<string> { "experimental-feature", "deprecated-tool" },
});
import com.github.copilot.rpc.*;
import java.util.List;
var session = client.createSession(
new SessionConfig()
.setSkillDirectories(List.of("./skills"))
.setDisabledSkills(List.of("experimental-feature", "deprecated-tool"))
.setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
).get();
技能目录结构
每个技能都是一个包含 SKILL.md 文件的命名子目录:
skills/
├── code-review/
│ └── SKILL.md
└── documentation/
└── SKILL.md
选项 skillDirectories 指向父目录(例如 ./skills)。 CLI 可发现即时子目录中的所有 SKILL.md 文件。
SKILL.md 格式
SKILL.md文件是一个具有可选 YAML 头部信息的 Markdown 文件:
---
name: code-review
description: Specialized code review capabilities
---
# Code Review Guidelines
When reviewing code, always check for:
1. **Security vulnerabilities** - SQL injection, XSS, etc.
2. **Performance issues** - N+1 queries, memory leaks
3. **Code style** - Consistent formatting, naming conventions
4. **Test coverage** - Are critical paths tested?
Provide specific line-number references and suggested fixes.
"前置信息字段:"
name:技能的标识符(与disabledSkills一起使用,用于选择性地禁用该技能)。 如果省略,则使用目录名称。description:简要说明技能的作用。
Markdown 正文包含加载技能时注入到会话上下文的说明。
技能提供程序(实验版)
注意
技能提供方是实验性的。 API 可以在将来的 SDK 版本中更改。
技能提供程序提供来自应用程序自身存储中的技能,例如多租户服务中的数据库,而不是磁盘上的 SKILL.md 文件。 提供方技能与基于文件的技能一起加入会话的技能目录。 模型通过 skill 工具按需加载它们,用户可以像任何其他技能一样调用它们。
提供程序实现两个操作:
- 列出技能:返回每个技能的目录元数据:
name、description,以及可选的userInvocable、disableModelInvocation和argumentHint。 运行时在加载会话的技能时调用它。 - 读取技能:返回一个技能的 markdown;如果技能不再存在,则返回未找到的结果。 运行时仅在加载技能时调用它。
创建或恢复会话时传递提供程序:
import { approveAll, CopilotClient, type SkillProvider } from "@github/copilot-sdk";
const releaseSkills = new Map([
[
"release-notes",
{
description: "Writes release notes in the team's format.",
markdown: "Group changes by feature area and link each pull request.",
},
],
]);
const skillProvider: SkillProvider = {
listSkills: () =>
[...releaseSkills].map(([name, skill]) => ({ name, description: skill.description })),
readSkill: (name) => releaseSkills.get(name)?.markdown ?? null,
};
const client = new CopilotClient();
const session = await client.createSession({
onPermissionRequest: approveAll,
skillProvider,
});
from copilot import CopilotClient, SkillProviderDescriptor
from copilot.session import PermissionHandler
RELEASE_SKILLS = {
"release-notes": (
"Writes release notes in the team's format.",
"Group changes by feature area and link each pull request.",
),
}
class ReleaseSkills:
async def list_skills(self) -> list[SkillProviderDescriptor]:
return [
SkillProviderDescriptor(name=name, description=description)
for name, (description, _) in RELEASE_SKILLS.items()
]
async def read_skill(self, name: str) -> str | None:
skill = RELEASE_SKILLS.get(name)
return skill[1] if skill else None
async def main():
client = CopilotClient()
await client.start()
session = await client.create_session(
on_permission_request=PermissionHandler.approve_all,
skill_provider=ReleaseSkills(),
)
package main
import (
"context"
"fmt"
"log"
copilot "github.com/github/copilot-sdk/go"
"github.com/github/copilot-sdk/go/rpc"
)
type releaseSkill struct {
description string
markdown string
}
type releaseSkills map[string]releaseSkill
func (s releaseSkills) ListSkills(ctx context.Context) ([]rpc.SkillProviderDescriptor, error) {
descriptors := make([]rpc.SkillProviderDescriptor, 0, len(s))
for name, skill := range s {
descriptors = append(descriptors, rpc.SkillProviderDescriptor{Name: name, Description: skill.description})
}
return descriptors, nil
}
func (s releaseSkills) ReadSkill(ctx context.Context, name string) (string, error) {
skill, ok := s[name]
if !ok {
return "", fmt.Errorf("%w: %s", copilot.ErrSkillNotFound, name)
}
return skill.markdown, nil
}
func main() {
ctx := context.Background()
client := copilot.NewClient(nil)
session, err := client.CreateSession(ctx, &copilot.SessionConfig{
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
SkillProvider: releaseSkills{
"release-notes": {
description: "Writes release notes in the team's format.",
markdown: "Group changes by feature area and link each pull request.",
},
},
})
if err != nil {
log.Fatal(err)
}
_ = session
}
#pragma warning disable GHCP001 // Skill providers are experimental.
using GitHub.Copilot;
using GitHub.Copilot.Rpc;
public sealed class ReleaseSkills : ISkillProvider
{
private readonly Dictionary<string, (string Description, string Markdown)> _skills = new()
{
["release-notes"] = (
"Writes release notes in the team's format.",
"Group changes by feature area and link each pull request."),
};
public Task<IReadOnlyList<SkillProviderDescriptor>> ListSkillsAsync(CancellationToken cancellationToken) =>
Task.FromResult<IReadOnlyList<SkillProviderDescriptor>>(
_skills.Select(skill => new SkillProviderDescriptor
{
Name = skill.Key,
Description = skill.Value.Description,
}).ToList());
public Task<string?> ReadSkillAsync(string name, CancellationToken cancellationToken) =>
Task.FromResult(_skills.TryGetValue(name, out var skill) ? skill.Markdown : null);
}
public static class SkillProviderExample
{
public static async Task RunAsync(CopilotClient client)
{
await using var session = await client.CreateSessionAsync(new SessionConfig
{
OnPermissionRequest = PermissionHandler.ApproveAll,
SkillProvider = new ReleaseSkills(),
});
}
}
.NET技能提供程序类型引发GHCP001实验性诊断消息。 使用<NoWarn>GHCP001</NoWarn> 或项目级 #pragma warning disable GHCP001 禁止显示它。
import com.github.copilot.AllowCopilotExperimental;
import com.github.copilot.CopilotClient;
import com.github.copilot.CopilotSession;
import com.github.copilot.SkillProvider;
import com.github.copilot.SkillProviderDescriptor;
import com.github.copilot.rpc.PermissionHandler;
import com.github.copilot.rpc.SessionConfig;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
@AllowCopilotExperimental
class ReleaseSkills implements SkillProvider {
private final Map<String, String> markdown = Map.of(
"release-notes", "Group changes by feature area and link each pull request.");
@Override
public CompletableFuture<List<SkillProviderDescriptor>> listSkills() {
return CompletableFuture.completedFuture(List.of(new SkillProviderDescriptor(
"release-notes", "Writes release notes in the team's format.", null, null, null)));
}
@Override
public CompletableFuture<String> readSkill(String name) {
return CompletableFuture.completedFuture(markdown.get(name));
}
}
@AllowCopilotExperimental
class SkillProviderExample {
static CopilotSession createSession(CopilotClient client) throws Exception {
return client.createSession(new SessionConfig()
.setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
.setSkillProvider(new ReleaseSkills()))
.get();
}
}
Java 技能提供程序 API 是实验性的@CopilotExperimental,因此使用类或方法必须选择启用 @AllowCopilotExperimental(或使用 -Acopilot.experimental.allowed=true 编译)。 请参阅 使用实验 API。
use std::sync::Arc;
use async_trait::async_trait;
use github_copilot_sdk::{Error, SessionConfig, SkillProvider, SkillProviderDescriptor};
struct ReleaseSkills;
#[async_trait]
impl SkillProvider for ReleaseSkills {
async fn list_skills(&self) -> Result<Vec<SkillProviderDescriptor>, Error> {
Ok(vec![SkillProviderDescriptor {
name: "release-notes".into(),
description: "Writes release notes in the team's format.".into(),
..Default::default()
}])
}
async fn read_skill(&self, name: &str) -> Result<Option<String>, Error> {
Ok((name == "release-notes")
.then(|| "Group changes by feature area and link each pull request.".to_string()))
}
}
let session = client
.create_session(
SessionConfig::default()
.approve_all_permissions()
.with_skill_provider(Arc::new(ReleaseSkills)),
)
.await?;
提供方技能内容
“Read skill”技能返回该技能的 SKILL.md 文本。 YAML frontmatter 是可选的:
- 如果没有 frontmatter,则整个文本都是技能正文,所有元数据都来自列出的技能。
- 使用 frontmatter 时,省略的字段将继承列出的值。 包含的字段必须与列出的值匹配,否则技能将无法加载。
allowed-tools仅可从前置元数据中读取。- 如果文本的第一行是
---,运行时会将其分析为 frontmatter,因此不要使用 Markdown 水平分隔线启动仅正文技能。
默认情况下,提供程序技能是纯文本的:它们没有基本目录,因此无法引用捆绑的脚本、模板或其他文件。 若要为提供程序技能提供此类文件,请参阅 支持文件。
skills.list 使用源 sdk 和空的 path 报告提供程序功能,有或没有支持文件均可。
支持文件(实验性)
提供程序技能可以附带支持文件,如脚本、引用、模板和资产,就像基于文件的技能文件夹可以附带这些文件一样。 应用程序将每个技能的文件保存在它控制的文件夹中,并将列出的技能上的 directory 设置为该文件夹的绝对路径。
当模型激活技能时,运行时会以列出基于文件的技能文件夹的方式列出文件夹。 该模型会获得与基于文件的技能相同的“此技能的基本目录”行和相关文件列表,用以取代仅含文本的提示,并使用其常规文件工具读取这些文件。 运行时本身绝不会读取、复制或写入这些文件。
import { approveAll, CopilotClient, type SkillProvider } from "@github/copilot-sdk";
const skillProvider: SkillProvider = {
listSkills: () => [
{
name: "release-notes",
description: "Writes release notes in the team's format.",
directory: "/srv/skills/release-notes",
},
],
readSkill: (name) =>
name === "release-notes"
? "Run `python3 scripts/changelog.py` and fill in `templates/notes.md`."
: null,
};
const client = new CopilotClient();
const session = await client.createSession({
onPermissionRequest: approveAll,
skillProvider,
});
每个 SDK 在其所列技能类型对应的文件夹上进行设置:
| SDK | 领域 |
|---|---|
| Node.js | directory: "/ |
| Python | directory="/ |
| Go | Directory: copilot.String("/ |
| .NET | Directory = "/ |
| Java | 第六个 Skill 构造函数参数 |
| Rust | directory: Some("/ |
文件夹所在的位置
将技能的所有文件放在其 directory下,每个技能对应一个文件夹。 运行时所在的文件夹必须可访问:
- 本地磁盘。 在没有会话文件系统时,
directory则为运行运行时的计算机或容器上的路径。 模型可以使用任何文件工具读取文件,并且可以运行脚本。 - 会话文件系统(多租户与服务器部署)。 使用
sessionFs处理程序时,directory使用处理程序的路径约定,并且处理程序提供文件;运行时会通过该处理程序列出文件夹。view、create、edit和apply_patch可访问这些文件。grep、glob和 shell 命令都不支持运行,因此脚本无法运行。 Java SDK 没有sessionFs,因此Java提供程序使用本地磁盘。
运行时在读取技能返回后列出文件夹,因此你可以在读取技能中延迟创建文件夹。 如果文件夹缺失或为空,或者在运行时调用读取技能后的 15 秒内无法列出,则模型仍会获取基本目录行,而没有相关文件列表,与基于文件的技能一样。 如果运行时列出技能时 sessionFs 处理程序断开连接,则这些技能将仅显示为文本,直到下一次列出时。
Access
- 本地磁盘。 从技能被列出时起,运行时会将每个已启用的提供程序技能
directory视为像已配置的skillDirectories文件夹一样:对其下内容的读取操作会跳过外部工作区路径提示,沙盒进程获得对它的只读访问权限。 读取操作仍会转到你的权限处理程序,与基于文件的技能一样。 不要使用宽泛目录,例如主文件夹或保存每个租户文件的文件夹。 - 会话文件系统。 运行时不会授予对该文件夹的任何访问权限。 让权限处理程序批准对其下内容的读取,当沙盒启用时,请将该文件夹添加到沙盒的
readonlyPaths中。
应用程序拥有该文件夹:它所在的位置、存在的时间、清理该文件夹以及防止模型更改它。 只读访问权限仅涵盖沙盒进程。 在沙盒之外,或者当文件夹位于工作区内时,模型可以写入它,除非你的权限处理程序或你的 sessionFs 处理程序拒绝。
编写该技能
- 请通过相对于该技能文件夹的路径来引用支持文件,例如
references/guide.md。 模型被告知文件夹的绝对路径。 - 如技能中使用脚本所述,通过解释器(如
python3 scripts/changelog.py或bash scripts/build.sh)运行脚本。 脚本仅在文件夹位于本地磁盘上时才运行。
规则和限制
directory在会话的路径约定中必须是绝对的;在Windows样式的会话文件系统上,它需要驱动器或 UNC 前缀。 它会像文件工具解析路径那样进行解析,因此会移除.和..段以及重复或尾随分隔符,并且无法解析为文件系统根目录。 它计入目录的 1 MiB 元数据限制,并且不会显示在技能目录中。 无效的directory会导致整个目录被拒绝,就像任何其他列出的无效技能一样。- 相关文件列表遵循基于文件的规则:生成和依赖项文件夹(如
node_modules、bin、obj和dist)被隐藏;汇总了包含 50 个以上的条目的文件夹;未列出嵌套 10 多个文件夹的文件;从不列出SKILL.md;最多显示 100 个文件。 - 压缩对话后,模型会保留技能的说明,但不保留其文件夹路径。 再次调用技能会显示它。 基于文件的能力保留其路径。
- 支持文件至少需要与添加它们的 SDK 版本一样新的Copilot CLI。 当任何列出的技能包含
directory时,较旧的 CLI 将拒绝整个技能目录。 未设置directory的提供程序与较旧的 CLI 兼容。
处理现有的 SKILL.md 文件
若要为已存储的 SKILL.md 文件(如存储在数据库中的文件)提供服务,请分析每个文件的 YAML frontmatter 一次以生成其目录条目,然后从 read skill 原样返回文件:
- 将
name和description映射到所列技能的name和description。 目录清单中需要两者;运行时不会回退到文件夹名称或技能正文。 - 当前言设置了它们时,将
disable-model-invocation、user-invocable和argument-hint映射到匹配的可选字段。 - 从 read skill 返回原始文本(包括 frontmatter)。 由于列出的值来自同一个 frontmatter,因此它们匹配,运行时仍从中读取
allowed-tools。
受信任的提供程序内容
处理提供程序技能(如技能目录):其内容是受信任的输入。 模型遵循技能的说明,frontmatter 控制技能的调用方式。
allowed-tools 不会在 SDK 会话中授予权限。 运行时在 skill.invoked 事件的 allowedTools 字段中报告它,你的权限处理程序仍决定每个工具请求。
请勿呈现最终用户或其他租户可以编辑的内容,除非你允许他们创作技能文件。 如果你从用户输入中构建技能,请自行生成 frontmatter,而不是传递用户提供的 frontmatter。
提供程序限制和错误
运行时验证提供程序的目录和内容:
-
名称必须匹配
^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$且唯一,忽略大小写。 -
目录最多可以包含 1,024 个技能和 1 MiB 元数据。 每个说明和参数提示最多可以有 1,024 个字符。
-
每个技能的 Markdown 最多可以是 UTF-8 的 1 MiB。
-
每个调用必须在 30 秒内完成。 当运行时停止等待调用时,由于超时、会话已结束,或恢复操作替换了提供程序,它会取消请求并忽略任何后续结果。 每个 SDK 以通常的方式将取消传递给提供程序:
SDK 取消信号 Node.js 传递给 readSkill和AbortSignal的选项中的signal(listSkills)已中止。Python 提供程序的 asyncio任务已通过asyncio.Cancelled取消。 同步提供程序运行直至完成。Error Go 呼叫的 context.Context已取消。.NET 呼叫的 CancellationToken已取消。Java 返回的 CompletableFuture项通过cancel(true)取消,不会中断正在运行的工作。Rust 提供程序 Future 被删除。 这些信号适用于被运行时取消的调用。 连接关闭或客户端强制停止时,.NET、Java和 Rust SDK 也会取消仍在运行的调用。 Node.js、Python 和 Go SDK 则不会这样做:正在运行的调用会持续到返回为止,而 SDK 会丢弃其结果。
如果列出操作失败或返回无效的目录,运行时将在 errors 返回的 skills.reload 列表中报告该问题。 重新加载失败后,将保留上次成功列表中的提供程序技能。 如果读取技能失败,该工具会 skill 向模型报告通用加载失败。 SDK 永远不会转发提供程序引发或返回的错误文本,因此这些文本不会泄露到对话中。
提供程序生命周期
一个提供程序只能绑定到一个会话:
- 提供程序会启用技能,除非将
enableSkills设置为false,这将保持提供程序处于绑定状态,但该提供程序永远不会被调用。 在mode: "empty",技能保持禁用状态,直到你将true设置为enableSkills。 - 提供方永远不会被持久化。 恢复会话时再次传递它。 在没有提供方的情况下恢复会将其从会话中删除。 加入会话的扩展不会更改它。
- 与基于文件的技能同名的提供程序技能将替换基于文件的技能,并且
skills.reload会发出警告。 - 子代理使用其父会话的提供程序。
- 运行时可以同时调用提供程序,例如,从
skill工具、用户调用和预加载技能的自定义代理,因此这两个操作都必须安全供并发使用。 - 云会话不支持技能提供程序。 SDK 在创建会话之前拒绝配置。
配置选项
SessionConfig 技能字段
| 语言 | 领域 | 类型 | Description |
|---|---|---|---|
| Node.js | skillDirectories | string[] | 要从中加载技能的目录 |
| Node.js | disabledSkills | string[] | 禁用技能 |
| Node.js | skillProvider | SkillProvider | SDK 提供的技能的实验性提供程序 |
| Python | skill_directories | list[str] | 要从中加载技能的目录 |
| Python | disabled_skills | list[str] | 禁用技能 |
| Python | skill_provider | SkillProvider | SDK 提供的技能实验性提供程序 |
| Go | SkillDirectories | []string | 要从中加载技能的目录 |
| Go | DisabledSkills | []string | 禁用技能 |
| Go | SkillProvider | SkillProvider | SDK 提供的技能实验性提供程序 |
| .NET | SkillDirectories | List<string> | 要从中加载技能的目录 |
| .NET | DisabledSkills | List<string> | 禁用技能 |
| .NET | SkillProvider | ISkillProvider | 用于 SDK 提供技能的实验性提供程序 |
| Java | setSkillProvider | SkillProvider | 用于 SDK 提供技能的实验性提供程序 |
| Rust | with_skill_provider | Arc<dyn SkillProvider> | SDK 提供技能的实验性提供程序 |
内置技能和 mode: "empty"
运行时附带一组默认可用的随附 内置 技能。 当您在 中运行客户端时(这是 mode: "empty" 的推荐基线),SDK 会排除所有在运行时捆绑的内置技能:它会在 post-create 和 post-resume 选项补丁中发送一个空的 includedBuiltinSkills 列表,以及一个空的 installedPlugins 列表。
此排除项是默认的,而不是永久性限制。 若要允许选定的运行时捆绑技能,请将 includedBuiltinSkills(或特定于语言的大小写形式)设置为这些技能的名称。 你还可以在 mode: "empty" 下选择启用自己的自定义技能——启用技能并传入你自己的 skillDirectories——而且这些技能仍然完全可用,包括与内置技能同名的自定义技能。
在 mode: "copilot-cli" 下,除非设置此选项,否则该字段将被省略。
首次加载技能时会扫描自定义技能目录,包括此时缺少或为空的目录。 这些目录中的文件系统更改不会在后续轮次中自动刷新目录。 添加、编辑或删除技能后,调用会话的 skills.reload RPC:
| SDK | 重新发起调用 |
|---|---|
| Node.js | await session.rpc.skills.reload() |
| Python | await session.rpc.skills.reload() |
| Go | session.RPC.Skills.Reload(ctx) |
| .NET | await session.Rpc.Skills.Reload |
| Java | session.get |
| Rust | session.rpc().skills().reload().await? |
更改配置的技能目录也会刷新目录清单。
最佳做法
-
按域组织 - 将相关技能组合在一起(例如,
skills/security/``skills/testing/) -
使用前置元数据 - 在 YAML 前置元数据中包含
name和description,以提高清晰度 -
文档依赖项 - 记下技能所需的任何工具或 MCP 服务器
-
单独测试技能 - 在将技能组合起来之前,先确认各项技能可正常运行
-
使用相对路径 - 使技能在环境中可移植
与其他功能结合使用
技能 + 自定义代理
代理 skills 字段中列出的技能 是预先加载的—其完整内容在启动时注入到代理的上下文中,因此代理可以立即访问技能说明,而无需调用技能工具。 技能名称从会话级 skillDirectories 中解析。
const session = await client.createSession({
skillDirectories: ["./skills/security"],
customAgents: [{
name: "security-auditor",
description: "Security-focused code reviewer",
prompt: "Focus on OWASP Top 10 vulnerabilities",
skills: ["security-scan", "dependency-check"],
}],
onPermissionRequest: async () => ({ kind: "approve-once" }),
});
注意
技能为可选启用项——省略 skills 时,不会注入任何技能内容。 子代理不从父级继承技能;必须为每个代理显式列出它们。
技能 + MCP 服务器
技能可以补充 MCP 服务器功能:
const session = await client.createSession({
skillDirectories: ["./skills/database"],
mcpServers: {
postgres: {
type: "local",
command: "npx",
args: ["-y", "@modelcontextprotocol/server-postgres"],
tools: ["*"],
},
},
onPermissionRequest: async () => ({ kind: "approve-once" }),
});
故障排除
技能未加载
- 检查路径是否存在 - 验证技能目录路径是否正确,并且包含包含
SKILL.md文件的子目录 - 检查权限 - 确保 SDK 可以读取目录
- 检查 SKILL.md 格式 - 验证 markdown 格式正确,并且任何 YAML frontmatter 都使用有效的语法
- 启用调试日志 - 将
logLevel: "debug"设为开启以查看技能加载日志
技能冲突
如果多个技能提供冲突的说明:
- 使用
disabledSkills来排除冲突技能 - 重新组织技能目录以避免重叠
另见
- 构建你的第一个由 Copilot 提供支持的应用 - 定义专用 AI 角色
- 构建你的第一个由 Copilot 提供支持的应用 - 生成自己的工具
- 将 MCP 服务器与 GitHub Copilot SDK 配合使用 - 连接外部工具提供方