技术速递|使用 Copilot SDK 构建 AI 驱动的 GitHub Issue 分类系统
作者:Andrea Griffiths排版:Alan Wang学习如何将 Copilot SDK 集成到 React Native 应用中,以生成由 AI 驱动的 issue 摘要,并采用适用于生产环境的优雅降级和缓存模式。Copilot SDK 让你可以将驱动的同款 AI 添加到你自己的应用中。我想看看它在实际中的效果如何,于是我构建了一个名为 IssueCrush 的 issue 分类应用。下
作者:Andrea Griffiths
排版:Alan Wang
学习如何将 Copilot SDK 集成到 React Native 应用中,以生成由 AI 驱动的 issue 摘要,并采用适用于生产环境的优雅降级和缓存模式。

Copilot SDK 让你可以将驱动 Copilot Chat 的同款 AI 添加到你自己的应用中。我想看看它在实际中的效果如何,于是我构建了一个名为 IssueCrush 的 issue 分类应用。下面是我的一些经验,以及你可以如何开始。
如果你曾维护过开源项目,或者在一个拥有活跃仓库的团队中工作过,你一定熟悉这种感觉。你打开 GitHub,看到通知角标显示:47 个 issues。其中一些是 bug,一些是功能请求,一些其实应该放到讨论区,还有一些是三年前就已出现的重复 issue。
对 issue 进行分类所带来的精力消耗是真实存在的。每一个 issue 都需要上下文切换:阅读标题、浏览描述、检查标签、思考优先级、决定如何处理。当多个仓库中有数十个 issue 需要逐一处理时,你的大脑很快就会不堪重负。
我想让这个过程变得更高效。借助 GitHub Copilot SDK,我找到了实现的方法。
IssueCrush 登场:右滑即处理
IssueCrush 将你的 GitHub issues 以可滑动卡片的形式展示。向左滑表示关闭,向右滑表示保留。当你点击 “Get AI Summary” 时,Copilot 会读取该 issue,并告诉你它的内容以及应该如何处理。这样一来,维护者无需逐条阅读冗长的描述,就能获得即时、可操作的上下文,从而更快地做出分类决策。下面是我如何集成 GitHub Copilot SDK 来实现这一点的。

架构挑战
第一个技术决策是弄清楚在哪里运行 Copilot SDK。React Native 应用无法直接使用 Node.js 包,而 Copilot SDK 需要 Node.js 运行时。在内部,SDK 会管理一个本地的 Copilot CLI 进程,并通过 JSON-RPC 与其通信。由于依赖 CLI 可执行文件和 Node 环境,这种集成必须在服务端运行,而不能直接在 React Native 应用中运行。这意味着服务器必须安装 Copilot CLI,并且该命令需要在系统的 PATH 中可用。
我最终选择了一种服务端集成模式:
以下是这种架构之所以有效的原因:
-
在所有客户端之间共享单个 SDK 实例,这样就不需要为每个移动客户端都创建一个新的连接。服务器为所有请求管理一个实例。开销更低、认证握手更少、清理也更简单。
-
在服务器端存储 Copilot 认证密钥,以确保凭证安全。你的 API token 永远不会接触客户端。它们保存在服务器上——这是它们应在的位置,而不是放在一个可以被反编译的 React Native 包中。
-
当 AI 不可用时具备优雅降级能力,这样即使 Copilot 服务宕机或超时,你仍然可以对 issues 进行分类。应用会回退到一个基础摘要。AI 可以让分类更快,但不应该成为单点故障。
-
对请求进行日志记录以便调试和监控,因为每一次提示和响应都会经过你的服务器。你可以跟踪延迟、捕获失败,并调试提示问题,而无需在移动客户端中额外添加监控代码。
在构建类似系统之前,你需要:
-
在你的服务器上安装 Copilot CLI。
-
GitHub Copilot 订阅服务,或使用自有 API 密钥的 BYOK 配置。
-
完成 Copilot CLI 的认证。在服务器上运行
copilot auth,或者设置COPILOT_GITHUB_TOKEN环境变量。
如何实现 Copilot SDK 集成
Copilot SDK 使用基于会话的模型。你需要先启动一个客户端(这会拉起 CLI 进程),创建一个会话,发送消息,然后进行清理。
const { CopilotClient, approveAll } = await import('@github/copilot-sdk');
let client = null;
let session = null;
try {
// 1. Initialize the client (spawns Copilot CLI in server mode)
client = new CopilotClient();
await client.start();
// 2. Create a session with your preferred model
session = await client.createSession({
model: 'gpt-4.1',
onPermissionRequest: approveAll,
});
// 3. Send your prompt and wait for response
const response = await session.sendAndWait({ prompt });
// 4. Extract the content
if (response && response.data && response.data.content) {
const summary = response.data.content;
// Use the summary...
}
} finally {
// 5. Always clean up
if (session) await session.disconnect().catch(() => {});
if (client) await client.stop().catch(() => {});
}
关键 SDK 模式
生命周期管理
SDK 遵循严格的生命周期:start() → createSession() → sendAndWait() → disconnect() → stop()
这里有一个我吃过亏才学到的教训:如果没有正确清理会话,会导致资源泄漏。我花了两个小时排查内存问题,最后才发现自己忘了调用disconnect()。因此务必将所有会话交互都包裹在 try/finally 语句中。在清理操作上使用 .catch(() => {}) 可以避免清理过程中的错误覆盖原始错误。
用于分类的提示工程
提示词结构为模型提供了充足的上下文信息,使其能够完成任务。我会提供与 issue 相关的结构化信息,而非直接堆砌原始文本:
const prompt = `You are analyzing a GitHub issue to help a developer quickly understand it and decide how to handle it.
Issue Details:
- Title: ${issue.title}
- Number: #${issue.number}
- Repository: ${issue.repository?.full_name || 'Unknown'}
- State: ${issue.state}
- Labels: ${issue.labels?.length ? issue.labels.map(l => l.name).join(', ') : 'None'}
- Created: ${issue.created_at}
- Author: ${issue.user?.login || 'Unknown'}
Issue Body:
${issue.body || 'No description provided.'}
Provide a concise 2-3 sentence summary that:
1. Explains what the issue is about
2. Identifies the key problem or request
3. Suggests a recommended action (e.g., "needs investigation", "ready to implement", "assign to backend team", "close as duplicate")
Keep it clear, actionable, and helpful for quick triage. No markdown formatting.`;
标签和作者相关信息的重要性远超你的想象。首次贡献者提出的 issue,其处理方式与核心维护者提出的 issue 截然不同,而 AI 会利用这些信息来调整其生成的摘要内容。
响应处理
sendAndWait() 方法会在会话进入空闲状态后返回助手的响应。在访问嵌套属性之前,务必验证响应链是否存在。
const response = await session.sendAndWait({ prompt }, 30000); // 30 second timeout
let summary;
if (response && response.data && response.data.content) {
summary = response.data.content;
} else {
throw new Error('No content received from Copilot');
}
sendAndWait() 的第二个参数是超时时间(毫秒)。我们需要将其设置得足够长以处理复杂 issue,但又不能太长,以免用户一直盯着加载状态。我见过太多 “undefined is not an object” 的错误,所以我可以肯定:永远不要省略对响应链的空值校验。
客户端服务层
在 React Native 端,我将 API 调用封装在一个服务类中,用于处理初始化和错误状态:
// src/lib/copilotService.ts
import type { GitHubIssue } from '../api/github';
import { getToken } from './tokenStorage';
export interface SummaryResult {
summary: string;
fallback?: boolean;
requiresCopilot?: boolean;
}
export class CopilotService {
private backendUrl = process.env.EXPO_PUBLIC_API_URL || 'http://localhost:3000';
async initialize(): Promise<{ copilotMode: string }> {
try {
const response = await fetch(`${this.backendUrl}/health`);
const data = await response.json();
console.log('Backend health check:', data);
return { copilotMode: data.copilotMode || 'unknown' };
} catch (error) {
console.error('Failed to connect to backend:', error);
throw new Error('Backend server not available');
}
}
async summarizeIssue(issue: GitHubIssue): Promise<SummaryResult> {
try {
const token = await getToken();
if (!token) {
throw new Error('No GitHub token available');
}
const response = await fetch(`${this.backendUrl}/api/ai-summary`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ issue, token }),
});
const data = await response.json();
if (!response.ok) {
if (response.status === 403 && data.requiresCopilot) {
return {
summary: data.message || 'AI summaries require a GitHub Copilot subscription.',
requiresCopilot: true,
};
}
throw new Error(data.error || 'Failed to generate summary');
}
return {
summary: data.summary || 'Unable to generate summary',
fallback: data.fallback || false,
};
} catch (error) {
console.error('Copilot summarization error:', error);
throw error;
}
}
}
export const copilotService = new CopilotService();
React Native 集成
UI 使用的是非常直接的 React 状态管理。点击按钮,调用服务,并缓存结果:
const [loadingAiSummary, setLoadingAiSummary] = useState(false);
const handleGetAiSummary = async () => {
const issue = issues[currentIndex];
if (!issue || issue.aiSummary) return;
setLoadingAiSummary(true);
try {
const result = await copilotService.summarizeIssue(issue);
setIssues(prevIssues =>
prevIssues.map((item, index) =>
index === currentIndex ? { ...item, aiSummary: result.summary } : item
)
);
} catch (error) {
console.error('AI Summary error:', error);
} finally {
setLoadingAiSummary(false);
}
};
一旦 issue 对象上存在摘要,卡片就会将按钮替换为摘要文本。如果用户滑走后再回来,缓存的版本会立即渲染出来,无需再次发起 API 请求。
优雅降级
AI 服务可能会失败。网络问题、速率限制以及服务中断都会发生。服务器处理两种失败模式:订阅错误会返回 403 状态码,从而让客户端展示清晰的提示信息;而其他所有情况都会回退到基于 issue 元数据生成的摘要。
} catch (error) {
// Clean up on error
try {
if (session) await session.disconnect().catch(() => {});
if (client) await client.stop().catch(() => {});
} catch (cleanupError) {
// Ignore cleanup errors
}
const errorMessage = error.message.toLowerCase();
// Copilot subscription errors get a clear 403
if (errorMessage.includes('unauthorized') ||
errorMessage.includes('forbidden') ||
errorMessage.includes('copilot') ||
errorMessage.includes('subscription')) {
return res.status(403).json({
error: 'Copilot access required',
message: 'AI summaries require a GitHub Copilot subscription.',
requiresCopilot: true
});
}
// Everything else falls back to a metadata-based summary
const fallbackSummary = generateFallbackSummary(issue);
res.json({ summary: fallbackSummary, fallback: true });
}
回退机制会基于我们已有的信息生成一个有用的摘要:
function generateFallbackSummary(issue) {
const parts = [issue.title];
if (issue.labels?.length) {
parts.push(`\nLabels: ${issue.labels.map(l => l.name).join(', ')}`);
}
if (issue.body) {
const firstSentence = issue.body.split(/[.!?]\s/)[0];
if (firstSentence && firstSentence.length < 200) {
parts.push(`\n\n${firstSentence}.`);
}
}
parts.push('\n\nReview the full issue details to determine next steps.');
return parts.join('');
}
其他值得注意的模式
服务器提供了一个 /health 端点,用于指示 AI 是否可用。客户端在启动时会检查该端点,如果后端无法支持 AI 功能,就会直接隐藏摘要按钮,避免出现不可用的按钮。
摘要是按需生成的,而不是预先生成的。这样可以降低 API 成本,并避免用户只是快速滑过 issue 时产生不必要的调用浪费。
SDK 使用 await import('@github/copilot-sdk') 动态加载,而不是在顶层使用 require。这样即使 SDK 本身存在问题,服务器也能正常启动,从而让部署和调试更加顺畅。
依赖项
{
"dependencies": {
"@github/copilot-sdk": "^0.1.14",
"express": "^5.2.1"
}
}
SDK 通过 JSON-RPC 与 Copilot CLI 进程进行通信。你需要在系统中安装 Copilot CLI,并确保它可以在 PATH 环境变量中被访问。请查看 SDK 的包依赖要求,以确认所需的最低 Node.js 版本。
我在构建这个项目时学到的
**服务端是正确的选择。**SDK 依赖 Copilot CLI 二进制文件,而你不可能在手机上安装它。把它运行在服务端可以将 AI 逻辑集中管理,简化移动端客户端,同时确保凭证永远不会离开后端。
**提示词结构比提示词长度更重要。**向模型提供结构化的元数据(例如标题、标签、作者)比直接把整个 issue 内容当作原始文本效果更好。给模型合适的输入,它才能返回更有价值的结果。
**永远要有回退机制。**AI 服务可能会宕机,也可能遇到速率限制。从一开始就设计优雅降级机制。即使 AI 部分不可用,用户仍然应该能够继续处理 issue。
**一定要清理会话。**SDK 要求显式清理:先 disconnect(),再 stop()。我曾经漏掉一次 disconnect(),结果花了两个小时排查内存泄漏。每次都要用 try/finally。
**缓存结果。**一旦生成摘要,就把它存到 issue 对象上。如果用户滑走再回来,可以立即显示缓存内容。没有第二次 API 调用,没有额外成本,也没有延迟。
**AI 可以让维护工作变得可持续。**issue 分类是一种“隐形工作”,非常消耗精力。它不会被感谢,但会不断堆积。如果能把处理 50 个 issue 的时间减半,就意味着可以把时间投入到代码审查、指导他人,或者至少不用再害怕通知数量。Copilot SDK 只是一个工具,但更重要的是这个思路:找出那些让你疲惫的维护工作,然后问问 AI 是否可以先做第一步处理。
尝试一下
@github/copilot-sdk 为构建智能开发者工具提供了真正的可能性。结合 React Native 的跨平台能力,你可以以一种原生且流畅的方式,将 AI 工作流带到移动端。
如果你正在构建类似的东西,可以从我这里介绍的服务端模式开始。这是最简单的可行路径,而且可以随着应用规模扩展。源代码已发布在 GitHub:AndreaGriffiths11/IssueCrush。
开始使用 Copilot SDK,看看你还能构建什么。入门指南会带你用大约五行代码完成第一次集成。如果你有反馈或想法,可以加入 SDK 讨论区一起交流。
更多推荐



所有评论(0)