GitHub Copilot SDK Go 配方示例全解析:从运行命令到源码级实战
GitHub Copilot SDK Go 配方示例全解析:从运行命令到源码级实战
本文以仓库 cookbook/copilot-sdk/go/recipe 目录下的可运行示例为核心,完整讲解 GitHub Copilot SDK for Go 的 5 个实战配方(错误处理、多会话、本地文件管理、PR 可视化、会话持久化)的运行方式、参数用法与源码实现。读完本文,你将能独立 go run 这些示例、理解其内部调用链,并把相同模式移植到自己的 Go 项目中。
配方目录是什么
cookbook/copilot-sdk/go/recipe/ 目录存放了 Go 语言下每个 cookbook 配方的独立可执行示例。每个 .go 文件都是一个完整程序,可以直接用 go run 运行,无需任何额外配置。它与 cookbook/copilot-sdk/go/README.md 中的同名 Markdown 文档一一对应——Markdown 文档讲解模式与思路,recipe/ 目录提供可直接运行、可复制改造的完整代码。
从目录结构看,recipe/ 中共有 7 个 .go 文件:5 个被官方配方文档收录(error-handling.go、multiple-sessions.go、managing-local-files.go、pr-visualization.go、persisting-sessions.go),另有 ralph-loop.go(自主 AI 任务循环)与 accessibility-report.go(无障碍报告生成)两个进阶示例,可作为延伸阅读。
运行前置条件
在运行任何示例前,需要满足:
- Go 1.21 或更高版本;
- GitHub Copilot SDK for Go 已安装(SDK 依赖 Copilot CLI 作为底层运行时)。
安装 SDK 依赖:
go get github.com/github/copilot-sdk/go
SDK 启动客户端时会在内部查找并拉起 Copilot CLI 进程(这在 error-handling.md 的 exec.Error 处理中有印证:Copilot CLI not found. Please install it first),因此请确保 copilot 命令已在 PATH 中可用。
运行示例:统一入口
recipe/ 中每个 .go 文件都是完整程序,运行方式统一为:
go run <filename>.go
可用配方一览
| 配方 | 运行命令 | 说明 |
|---|---|---|
| 错误处理 | go run error-handling.go |
演示连接失败、超时、清理等错误处理模式 |
| 多会话 | go run multiple-sessions.go |
管理多条相互独立的会话 |
| 本地文件管理 | go run managing-local-files.go |
利用 AI 分组策略整理本地文件 |
| PR 可视化 | go run pr-visualization.go |
生成 PR 年龄分布图表 |
| 会话持久化 | go run persisting-sessions.go |
跨重启保存与恢复会话 |
带参数运行的示例
PR 可视化指定目标仓库:
go run pr-visualization.go -repo github/copilot-sdk
本地文件管理(先编辑代码修改目标目录):
# 先修改 managing-local-files.go 中的 targetFolder 变量
go run managing-local-files.go
pr-visualization.go 的 -repo 参数来自标准库 flag 包(flag.String("repo", "", ...)),不传该参数时程序会依次尝试:检测当前目录是否为 git 仓库 → 解析 origin 远程地址 → 交互式提示输入,三级回退策略保证了它在任何目录下都能顺利运行。
配方一:错误处理(error-handling.go)
错误处理是 SDK 应用稳定性的基础。示例代码覆盖了连接失败、创建会话失败、消息发送失败等关键节点:
package main
import (
"context"
"fmt"
"log"
copilot "github.com/github/copilot-sdk/go"
)
func main() {
ctx := context.Background()
client := copilot.NewClient(nil)
if err := client.Start(ctx); err != nil {
log.Fatalf("Failed to start client: %v", err)
}
defer client.Stop()
session, err := client.CreateSession(ctx, &copilot.SessionConfig{
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
Model: "gpt-5.4",
})
if err != nil {
log.Fatalf("Failed to create session: %v", err)
}
defer session.Disconnect()
result, err := session.SendAndWait(ctx, copilot.MessageOptions{Prompt: "Hello!"})
if err != nil {
log.Printf("Failed to send message: %v", err)
return
}
if result != nil {
if d, ok := result.Data.(*copilot.AssistantMessageData); ok {
fmt.Println(d.Content)
}
}
}
关键调用链
这段代码揭示了 SDK 的三个核心阶段:
copilot.NewClient(nil)创建客户端(nil表示使用默认传输层配置);client.Start(ctx)启动底层 Copilot CLI 服务进程,必须与defer client.Stop()成对使用;client.CreateSession(ctx, &copilot.SessionConfig{...})创建会话——SessionConfig中的OnPermissionRequest声明工具调用的权限策略(PermissionHandler.ApproveAll表示自动批准),Model指定对话模型;session.SendAndWait(ctx, copilot.MessageOptions{Prompt: ...})同步发送消息并等待完整回复,返回结果以类型断言result.Data.(*copilot.AssistantMessageData)的方式取出助手消息内容。
具体错误类型的分流处理
error-handling.md 还给出了更精细的错误分类方式,核心是 Go 的 errors.As / errors.Is 链式判断:
func startClient(ctx context.Context) error {
client := copilot.NewClient(nil)
if err := client.Start(ctx); err != nil {
var execErr *exec.Error
if errors.As(err, &execErr) {
return fmt.Errorf("Copilot CLI not found. Please install it first: %w", err)
}
if errors.Is(err, context.DeadlineExceeded) {
return fmt.Errorf("Could not connect to Copilot CLI server: %w", err)
}
return fmt.Errorf("Unexpected error: %w", err)
}
return nil
}
exec.Error 代表 Copilot CLI 可执行文件缺失,context.DeadlineExceeded 代表连接超时——两者被转换为带有明确指引的中文可读错误,方便用户对症处理。
超时控制
对长时间请求使用 context.WithTimeout 设置硬性超时:
func sendWithTimeout(session *copilot.Session) error {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
result, err := session.SendAndWait(ctx, copilot.MessageOptions{Prompt: "Complex question..."})
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
return fmt.Errorf("request timed out")
}
return err
}
if result != nil && result.Data.Content != nil {
fmt.Println(*result.Data.Content)
}
return nil
}
中止请求与优雅关闭
当需要取消一个正在执行的请求时,可调用 session.Abort(ctx):
func abortAfterDelay(ctx context.Context, session *copilot.Session) {
// 发起非阻塞请求
session.Send(ctx, copilot.MessageOptions{Prompt: "Write a very long story..."})
// 5 秒后强制中止
time.AfterFunc(5*time.Second, func() {
if err := session.Abort(ctx); err != nil {
log.Printf("Failed to abort: %v", err)
}
fmt.Println("Request aborted")
})
}
对于长时间运行的服务,应监听系统信号实现优雅关闭(配合 client.Stop() 释放资源):
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
go func() {
<-sigChan
fmt.Println("\nShutting down...")
client.Stop()
os.Exit(0)
}()
错误处理最佳实践
- 始终清理资源:用
defer保证Stop()与Disconnect()必然执行; - 处理连接错误:CLI 可能未安装或未运行;
- 设置合理超时:长请求用
context.WithTimeout兜底; - 记录错误:捕获细节便于排障;
- 包装错误链:用
fmt.Errorf配合%w保留原始错误信息。
配方二:多会话(multiple-sessions.go)
多会话配方解决"并行运行多条独立对话、各自维护上下文"的需求。完整示例:
package main
import (
"context"
"fmt"
"log"
copilot "github.com/github/copilot-sdk/go"
)
func main() {
ctx := context.Background()
client := copilot.NewClient(nil)
if err := client.Start(ctx); err != nil {
log.Fatal(err)
}
defer client.Stop()
// 创建多条独立会话
session1, err := client.CreateSession(ctx, &copilot.SessionConfig{
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
Model: "gpt-5.4",
})
if err != nil {
log.Fatal(err)
}
defer session1.Disconnect()
session2, err := client.CreateSession(ctx, &copilot.SessionConfig{
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
Model: "gpt-5.4",
})
if err != nil {
log.Fatal(err)
}
defer session2.Disconnect()
session3, err := client.CreateSession(ctx, &copilot.SessionConfig{
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
Model: "claude-sonnet-4.6",
})
if err != nil {
log.Fatal(err)
}
defer session3.Disconnect()
fmt.Println("Created 3 independent sessions")
// 每条会话维护自己的对话历史
session1.Send(ctx, copilot.MessageOptions{Prompt: "You are helping with a Python project"})
session2.Send(ctx, copilot.MessageOptions{Prompt: "You are helping with a TypeScript project"})
session3.Send(ctx, copilot.MessageOptions{Prompt: "You are helping with a Go project"})
// 追问仍停留在各自上下文
session1.Send(ctx, copilot.MessageOptions{Prompt: "How do I create a virtual environment?"})
session2.Send(ctx, copilot.MessageOptions{Prompt: "How do I set up tsconfig?"})
session3.Send(ctx, copilot.MessageOptions{Prompt: "How do I initialize a module?"})
}
从源码可以看出,多会话的两个核心要点:
- 独立上下文:每条会话拥有独立的对话历史,向
session1发送的"Python 虚拟环境"追问不会污染session2的 TypeScript 话题; - 不同模型并行:
session3使用claude-sonnet-4.6,其余使用gpt-5.4——同一客户端可同时驱动不同模型,天然适合 A/B 对比。
自定义会话 ID
默认会话 ID 由 SDK 生成,如需便于跟踪可显式指定:
session, err := client.CreateSession(ctx, &copilot.SessionConfig{
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
SessionID: "user-123-chat",
Model: "gpt-5.4",
})
if err != nil {
log.Fatal(err)
}
fmt.Println(session.SessionID) // "user-123-chat"
列出与删除会话
// 列出全部会话
sessions, err := client.ListSessions(ctx, nil)
if err != nil {
log.Fatal(err)
}
for _, sessionInfo := range sessions {
fmt.Printf("Session: %s\n", sessionInfo.SessionID)
}
// 删除指定会话
if err := client.DeleteSession(ctx, "user-123-chat"); err != nil {
log.Printf("Failed to delete session: %v", err)
}
典型使用场景
- 多用户应用:每个用户对应一条会话;
- 多任务工作流:不同任务使用独立会话互不干扰;
- A/B 测试:用不同模型处理同一问题,对比回复质量。
配方三:本地文件管理(managing-local-files.go)
该配方让 Copilot 依据文件元数据(类型、创建日期、大小等)智能整理文件夹。完整示例:
package main
import (
"context"
"fmt"
"log"
"os"
"path/filepath"
copilot "github.com/github/copilot-sdk/go"
)
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{
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
Model: "gpt-5.4",
})
if err != nil {
log.Fatal(err)
}
defer session.Disconnect()
// 事件处理器
session.On(func(event copilot.SessionEvent) {
switch d := event.Data.(type) {
case *copilot.AssistantMessageData:
fmt.Printf("\nCopilot: %s\n", d.Content)
case *copilot.ToolExecutionStartData:
fmt.Printf(" → Running: %s\n", d.ToolName)
case *copilot.ToolExecutionCompleteData:
fmt.Printf(" ✓ Completed (success=%v)\n", d.Success)
}
})
// 让 Copilot 整理文件
// 修改此变量即可切换目标目录
homeDir, _ := os.UserHomeDir()
targetFolder := filepath.Join(homeDir, "Downloads")
prompt := fmt.Sprintf(`
Analyze the files in "%s" and organize them into subfolders.
1. First, list all files and their metadata
2. Preview grouping by file extension
3. Create appropriate subfolders (e.g., "images", "documents", "videos")
4. Move each file to its appropriate subfolder
Please confirm before moving any files.
`, targetFolder)
_, err = session.SendAndWait(ctx, copilot.MessageOptions{Prompt: prompt})
if err != nil {
log.Fatal(err)
}
}
事件驱动的实时反馈
这段代码首次引入了 session.On(func(event copilot.SessionEvent)) 事件订阅模式,三种事件类型构成完整的工具执行可视化:
AssistantMessageData:助手最终回复内容;ToolExecutionStartData:某个工具开始执行(ToolName字段);ToolExecutionCompleteData:工具执行完成(Success字段表示成败)。
SDK 把"Copilot 调用本地文件工具"这一过程拆成了可观测的事件流——这正是指令式 UI 或日志系统对接 SDK 的标准接口。
分组策略
通过改写提示词即可切换分组维度:
按扩展名分组:
// images/ -> .jpg, .png, .gif
// documents/ -> .pdf, .docx, .txt
// videos/ -> .mp4, .avi, .mov
按创建日期分组:
// 2024-01/ -> 2024 年 1 月创建的文件
// 2024-02/ -> 2024 年 2 月创建的文件
按文件大小分组:
// tiny-under-1kb/
// small-under-1mb/
// medium-under-100mb/
// large-over-100mb/
干跑模式(Dry-run)
为安全起见,可要求 Copilot 只预览方案、不执行移动:
prompt := fmt.Sprintf(`
Analyze files in "%s" and show me how you would organize them
by file type. DO NOT move any files - just show me the plan.
`, targetFolder)
session.SendAndWait(ctx, copilot.MessageOptions{Prompt: prompt})
基于内容的自定义分组
让 Copilot 根据文件名、类型与日期规律自行判断逻辑归属:
prompt := fmt.Sprintf(`
Look at the files in "%s" and suggest a logical organization.
Consider:
- File names and what they might contain
- File types and their typical uses
- Date patterns that might indicate projects or events
Propose folder names that are descriptive and useful.
`, targetFolder)
session.SendAndWait(ctx, copilot.MessageOptions{Prompt: prompt})
安全注意事项
- 移动前确认:提示词中要求 Copilot 移动文件前先征求确认;
- 处理重名:明确同名文件冲突时的行为;
- 保留原件:重要文件建议"复制"而非"移动"。
配方四:PR 可视化(pr-visualization.go)
该配方构建一个交互式 CLI 工具,利用 Copilot 内置能力(GitHub MCP Server + 文件工具 + 代码执行)自动生成仓库 PR 年龄分布图表。完整源码较长,其核心结构如下:
仓库检测(Git & GitHub Detection)
func isGitRepo() bool {
cmd := exec.Command("git", "rev-parse", "--git-dir")
return cmd.Run() == nil
}
func getGitHubRemote() string {
cmd := exec.Command("git", "remote", "get-url", "origin")
output, err := cmd.Output()
if err != nil {
return ""
}
remoteURL := strings.TrimSpace(string(output))
// 处理 SSH 格式: git@github.com:owner/repo.git
sshRe := regexp.MustCompile(`git@github\.com:(.+/.+?)(?:\.git)?$`)
if matches := sshRe.FindStringSubmatch(remoteURL); matches != nil {
return matches[1]
}
// 处理 HTTPS 格式: https://github.com/owner/repo.git
httpsRe := regexp.MustCompile(`https://github\.com/(.+/.+?)(?:\.git)?$`)
if matches := httpsRe.FindStringSubmatch(remoteURL); matches != nil {
return matches[1]
}
return ""
}
仓库定位按三级优先级回退:-repo 命令行参数 → 当前 git 仓库的 origin 远程地址(SSH/HTTPS 两种格式均用正则解析)→ 交互式用户输入。
会话与系统提示词
session, err := client.CreateSession(ctx, &copilot.SessionConfig{
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
Model: "gpt-5.4",
SystemMessage: &copilot.SystemMessageConfig{
Content: fmt.Sprintf(`
<context>
You are analyzing pull requests for the GitHub repository: %s/%s
The current working directory is: %s
</context>
<instructions>
- Use the GitHub MCP Server tools to fetch PR data
- Use your file and code execution tools to generate charts
- Save any generated images to the current working directory
- Be concise in your responses
</instructions>
`, owner, repoName, cwd),
},
})
SystemMessageConfig 通过 <context> 与 <instructions> 两个 XML 标签块,把仓库身份、工作目录和工具使用规范注入系统消息——这是 SDK 场景下约束 Agent 行为的标准姿势。
主提示词与交互循环
prompt := fmt.Sprintf(`
Fetch the open pull requests for %s/%s from the last week.
Calculate the age of each PR in days.
Then generate a bar chart image showing the distribution of PR ages
(group them into sensible buckets like <1 day, 1-3 days, etc.).
Save the chart as "pr-age-chart.png" in the current directory.
Finally, summarize the PR health - average age, oldest PR, and how many might be considered stale.
`, owner, repoName)
if _, err := session.SendAndWait(ctx, copilot.MessageOptions{Prompt: prompt}); err != nil {
log.Fatal(err)
}
主分析完成后进入 bufio.NewReader(os.Stdin) 驱动的交互循环,持续读取用户输入并调用 session.SendAndWait,输入 exit 或 quit 时退出:
for {
fmt.Print("You: ")
input, _ := reader.ReadString('\n')
input = strings.TrimSpace(input)
if input == "" {
continue
}
if strings.ToLower(input) == "exit" || strings.ToLower(input) == "quit" {
fmt.Println("👋 Goodbye!")
break
}
if _, err := session.SendAndWait(ctx, copilot.MessageOptions{Prompt: input}); err != nil {
log.Printf("Error: %v", err)
}
}
工作原理
- 仓库检测:
--repo标志 → git 远程地址 → 用户输入; - 零自定义工具:完全依赖 Copilot CLI 内置能力——GitHub MCP Server 拉取 PR 数据、文件工具保存图表、代码执行工具(如 Python/matplotlib)生成图表;
- 交互式会话:首次分析后可持续追问调整(如"扩展到最近一个月""显示最老的 5 个 PR""改为饼图""按作者分组")。
为什么用内置能力而非自定义工具
| 维度 | 自定义工具 | Copilot 内置能力 |
|---|---|---|
| 代码复杂度 | 高 | 极低 |
| 维护成本 | 自行维护 | 由 Copilot 维护 |
| 灵活性 | 逻辑固定 | AI 自主决策最佳方案 |
| 图表类型 | 只支持你写死的 | Copilot 能生成的任意类型 |
| 数据分组 | 硬编码分桶 | 智能分组 |
配方五:会话持久化(persisting-sessions.go)
该配方演示如何跨应用重启保存、恢复会话。核心 API 是 SessionID 指定 + ResumeSession 恢复:
package main
import (
"context"
"fmt"
"log"
copilot "github.com/github/copilot-sdk/go"
)
func main() {
ctx := context.Background()
client := copilot.NewClient(nil)
if err := client.Start(ctx); err != nil {
log.Fatal(err)
}
defer client.Stop()
// 使用有意义的 ID 创建会话
session, err := client.CreateSession(ctx, &copilot.SessionConfig{
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
SessionID: "user-123-conversation",
Model: "gpt-5.4",
})
if err != nil {
log.Fatal(err)
}
_, err = session.SendAndWait(ctx, copilot.MessageOptions{Prompt: "Let's discuss TypeScript generics"})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Session created: %s\n", session.SessionID)
// 断开会话,但数据保留在磁盘
session.Disconnect()
fmt.Println("Session disconnected (state persisted)")
// 恢复之前的会话
resumed, err := client.ResumeSession(ctx, "user-123-conversation", &copilot.ResumeSessionConfig{OnPermissionRequest: copilot.PermissionHandler.ApproveAll})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Resumed: %s\n", resumed.SessionID)
_, err = resumed.SendAndWait(ctx, copilot.MessageOptions{Prompt: "What were we discussing?"})
if err != nil {
log.Fatal(err)
}
// 列出会话
sessions, err := client.ListSessions(ctx, nil)
if err != nil {
log.Fatal(err)
}
ids := make([]string, 0, len(sessions))
for _, s := range sessions {
ids = append(ids, s.SessionID)
}
fmt.Printf("Sessions: %v\n", ids)
// 永久删除会话
if err := client.DeleteSession(ctx, "user-123-conversation"); err != nil {
log.Fatal(err)
}
fmt.Println("Session deleted")
resumed.Disconnect()
}
会话生命周期管理
该配方完整演示了会话生命周期的四个阶段:
- 创建:
CreateSession传入SessionID作为持久化主键; - 断开:
Disconnect()仅断开连接,状态落盘保留(注释明确说明state persisted); - 恢复:
ResumeSession(ctx, sessionID, &copilot.ResumeSessionConfig{...})按 ID 重建会话,历史上下文自动还原——示例中"我们刚才在讨论什么?"能获得 TypeScript 泛型话题的答复; - 删除:
DeleteSession从磁盘永久清除会话及全部数据。
读取会话历史
对于需要审计或导出的场景,GetMessages 可逐条读取历史消息:
messages, _ := session.GetMessages(ctx)
for _, msg := range messages {
if d, ok := msg.Data.(*copilot.AssistantMessageData); ok {
fmt.Printf("[assistant.message] %s\n", d.Content)
}
}
会话持久化最佳实践
- 使用有意义的会话 ID:把用户 ID 或业务上下文嵌入
SessionID; - 处理缺失会话:恢复前先检查会话是否存在(
ResumeSession失败时优雅降级); - 定期清理:周期性删除不再需要的旧会话,避免磁盘膨胀。
配方目录中的其他示例
除上述 5 个官方配方外,recipe/ 目录还包含两个值得研读的进阶示例:
- ralph-loop.go:自主 AI 任务循环,每轮迭代创建全新会话(
gpt-5.3-codex模型 +WorkingDirectory固定工作目录),状态通过磁盘上的IMPLEMENTATION_PLAN.md在迭代间共享,支持plan(规划)与build(构建)两种模式。它体现了"状态在磁盘、上下文保持新鲜"的 Ralph Loop 设计哲学,详见 ralph-loop.md; - accessibility-report.go:通过
MCPServers配置把 Playwright MCP Server 注入会话("type": "local", "command": "npx", "args": ["@playwright/mcp@latest"]),让 Copilot 直接驱动浏览器生成 WCAG 无障碍报告,是"SDK + 自定义 MCP 工具"组合的极佳范本。
Go 最佳实践
recipe/README.md 明确指出这些示例遵循 Go 社区惯例,这也是你移植代码时应保持的风格基线:
- 显式错误检查:每个可能失败的调用都显式处理错误,绝不静默忽略;
defer清理资源:client.Stop()、session.Disconnect()一律通过defer保证执行;- 惯用命名:局部变量使用 camelCase;
- 优先标准库:如
flag(命令行参数)、bufio(交互输入)、os/exec(git 检测)、regexp(远程地址解析); - 清晰的关注点分离:以
pr-visualization.go为例,Git 检测、远程解析、主流程各成函数,代码分段注释明确。
学习资源(仓库内)
- Go 配方索引:5 个配对的 Markdown 文档入口,含模式讲解与精简代码;
- Cookbook 总览:.NET、Node.js/TypeScript、Python、Go、Java 五种语言的同主题配方对照(每个配方在 5 种语言下均有实现,可交叉参考学习 API 映射);
- 仓库贡献指南:若想为 cookbook 新增配方,可参考其中对文档与可运行示例的要求。
总结
cookbook/copilot-sdk/go/recipe/ 目录是学习 GitHub Copilot SDK for Go 的最佳起点:从 go run 一键运行,到逐行拆解 Start → CreateSession → SendAndWait → Disconnect 的标准调用链,再到错误分类、多会话隔离、事件驱动、系统提示词约束、会话持久化等进阶模式,五个配方覆盖了 SDK 应用开发的高频场景。将 recipe/ 中的代码复制到自己的项目中稍作修改(模型名、目标目录、仓库地址),即可快速搭建基于 Copilot 的自动化工具。