跳转到正文

嵌入 Copilot SDK

本导读说明 API 如何嵌入 GitHub Copilot SDK,并将 Copilot 会话封装为应用程序服务。你将了解应用如何启动 SDK 客户端、创建流式会话、监听会话事件,以及避免使用过时的模型目录。

SDK 的使用位置

主要集成点是 CopilotChatService。它通过以下语句导入 SDK:

using GitHub.Copilot;

该命名空间是迁移时常见的易错点。在 SDK 1.0.0 发布之前,命名空间为 GitHub.Copilot.SDK;在此处使用的 v1.x 代码中,命名空间为 GitHub.Copilot ,尽管包引用仍名为 GitHub.Copilot.SDK

单个长生命周期客户端

Program.csCopilotChatService 注册为单例:

builder.Services.AddSingleton<CopilotChatService>();

在 API 进程的整个生命周期内,该服务持有一个 CopilotClient 字段。这样可避免为每个 HTTP 请求反复启动和停止 Copilot 连接,并将模型列表获取和聊天流式传输统一置于一个托管服务之后。

CopilotChatService 实现了 IAsyncDisposable。当主机释放该单例时, CopilotChatService.DisposeAsync 调用 CopilotClient.StopAsync() ,从而正常关闭 SDK 连接。

启动和恢复客户端

CopilotChatService.EnsureStartedAsync 是列出模型或开始聊天前的入口检查:

if (_isStarted && _client != null) return;

_isStarted = false;
if (_client != null)
{
    try { await _client.StopAsync(); } catch { }
}

_client = new CopilotClient();
await _client.StartAsync();
_isStarted = true;

该方法将 _isStarted_client 作为真实状态来源。如果客户端尚未启动,或之前的故障已将其标记为不健康,服务会停止旧客户端,创建新的 CopilotClient,启动它并记录新状态。

恢复逻辑在 CopilotChatService.ChatStreamAsync中完成。后台会话捕获 IOException,记录 Copilot 连接已丢失,将 _isStarted = false,并以该异常结束输出通道。下一个请求会再次调用 EnsureStartedAsync 并重新创建客户端。

创建流式会话

CopilotChatService.ChatStreamAsync 会为每个提示构建一个 SessionConfig

SessionConfig config = new()
{
    Model = model,
    Streaming = true,
    SystemMessage = systemMessage != null ? new SystemMessageConfig
    {
        Mode = SystemMessageMode.Append,
        Content = systemMessage
    } : null
};

所选模型来自请求,服务默认使用 claude-haiku-4.5Streaming = true 会指示 SDK 发出响应增量。提供系统消息时,该消息会作为 SystemMessageConfigAppend 模式发送,因此应用上下文会追加到会话中,而不是替换会话的基础系统行为。

会话使用 await using创建,因此该提示处理完毕后会异步释放会话:

await using var session = await _client.CreateSessionAsync(config);

会话事件

该服务使用显式泛型类型参数订阅 SDK 事件:

session.On<SessionEvent>(evt => { /* switch on event type */ });

在 v1.x 中,必须显式指定 <SessionEvent> 。旧的非泛型形式已无法可靠推断事件类型,因此升级时遗漏该类型参数是常见故障。

CopilotChatService 处理四种事件:

  • AssistantMessageDeltaEventDeltaContent 写入输出流。
  • AssistantMessageEvent 记录助手响应已完成。
  • SessionIdleEvent 完成用于等待本轮结束的 TaskCompletionSource
  • SessionErrorEvent 记录 SDK 错误,并以异常结束等待任务。

完成订阅后,通过以下调用发送提示:

await session.SendAsync(new MessageOptions { Prompt = prompt });

将 SDK 事件桥接到 IAsyncEnumerable

SDK 会话在后台 Task中运行。增量会写入 Channel<string>,外层异步迭代器则读取该通道:

var outputChannel = Channel.CreateUnbounded<string>();

_ = Task.Run(async () =>
{
    // create session, handle events, write chunks
}, cancellationToken);

await foreach (var chunk in outputChannel.Reader.ReadAllAsync(cancellationToken))
{
    yield return chunk;
}

这种结构是有意设计的。C# 异步迭代器无法在拥有 SDK 会话的 try/catch 代码块内部执行 yield return。通道使会话能够在内部处理异常和完成状态,而外层方法则向控制器公开简洁的 IAsyncEnumerable<string>

列出实时可用模型

CopilotChatService.ListModelsAsync 调用 CopilotClient.ListModelsAsync() ,并返回已连接 Copilot CLI 实际提供的模型 ID 和显示名称:

var models = await _client.ListModelsAsync(cancellationToken);

硬编码模型 ID 很容易引发问题。模型可用性会因账户、发布批次和提供商而变化;此演示中过时的硬编码列表曾导致六个模型中只有一个可用。API 在 ChatController.AvailableModels中仍保留静态目录,但它仅用于元数据和回退。正常流程会从 SDK 获取实时模型列表。