VS Code Copilot Chat Sessions Provider 深度解析:Agent 会话如何统一到 Sessions 门面架构
【免费下载链接】vscodeVisual Studio Code项目地址: https://gitcode.com/GitHub_Trending/vscode6/vscode
Copilot Chat Sessions Provider(default-copilot)是 VS Code "Sessions" 会话体系把既有 Copilot Agent 会话基建适配进统一ISessionsProvider契约的桥接层。本文以其规范文档 COPILOT_CHAT_SESSIONS_PROVIDER.md 为主体骨架,结合同目录源码、注册入口与测试,讲清注册身份、草稿与既有会话两类实现、请求生命周期、选择器贡献、删除归档以及该规范的变更门禁。读完你能掌握一条独立会话 provider 在该架构下需要遵守的身份、缓存、生命周期与契约边界。
文档定位:一份带"规范变更门禁"的设计规约
文件开头的声明决定了本文档的写法和使用方式:
Specification change gate:Do not update this document for provider bug fixes, option details, picker behavior, or timing. Update it only when provider ownership, identity, cache semantics, or request lifecycle changes.
即这不是操作手册,而是一份架构契约:它只应随 provider 的属主权(ownership)、身份(identity)、缓存语义(cache semantics)或请求生命周期(request lifecycle)的变化而更新;具体选项细节、picker 行为、超时值与 bug 修复叙述都属于代码与针对性测试的范畴,不应回流进文档。这与文档结尾 Change policy 一节前后呼应,写作与审阅此组件时应把这条门禁当作最低门槛。
Scope:把 Copilot Agent 会话基建"适配"进 Sessions 契约
CopilotChatSessionsProvider的目标不是另起炉灶实现一套会话,而是适配(adapts):将既有的 Copilot agent-session 基础设施(位于 src/vs/workbench/contrib/chat/browser/agentSessions 的IAgentSessionsService、IAgentSession、AgentSessionProviders等)包装成 Sessions 层消费的ISessionsProvider接口。
它支持两类会话来源:
- Copilot Cloud:云端 agent 会话,即
CopilotCloudSessionType(id 为copilot-cloud-agent,见 copilotChatSessionsProvider.ts); - 本地 Copilot CLI:仅当 Agent Host 运行时不可用时才启用本地路径(代码里对应
_isCopilotCliAvailable()检查agentHostEnablementService.enabled的反向值,见同文件 L1507-L1509)。
ISessionsProvider本身被定义为"封装一个计算环境(compute environment)",负责工作区发现、会话创建、会话列举与 picker 贡献;一个 provider 可服务多种 session type,多个 provider 实例也可服务同一种 session type(例如每个远程 Agent Host 一个)。该接口的完整契约定义在 sessionsProvider.ts。
注册与身份(Registration and identity)
规范明确:工作台恢复之后(after workbench restoration),DefaultSessionsProviderContribution只注册这一个 provider。落地代码在 copilotChatSessions.contribution.ts:以WorkbenchPhase.AfterRestored阶段创建CopilotChatSessionsProvider实例并调用sessionsProvidersService.registerProvider(provider)。
该 provider 的身份契约如下表:
| Property | Contract |
|---|---|
| Provider ID | default-copilot(源码常量COPILOT_PROVIDER_ID,L168) |
| Label | Copilot Chat |
| Cloud session type | 可用时始终对外广告 |
| Local CLI session type | 仅当 Agent Host 不拥有它时才广告 |
源码侧的证据:
sessionTypesgetter 总是把 Cloud 类型追加进列表,而 CLI 类型(CopilotCLISessionType,idcopilotcli、label "Copilot"、支持 worktree 配置,见 baseAgentHostSessionsProvider.ts)只在 Agent Host enablement 被关闭时出现(L1433-L1440)。- 按工作区 URI 精确选择:
getSessionTypes(workspaceUri)对github-remote-file://(GITHUB_REMOTE_FILE_SCHEME)工作区只返回 Cloud 类型,本地file://工作区则返回(可用的)CLI 类型(L1581-L1590)。工作区 URI scheme 直接决定采用哪一份草稿实现。 - provider 可暴露本地文件夹(local-folder)与远程仓库(remote-repository)浏览动作:构造函数里注册了
Repository...、Issue...、Pull Request...三个 browse action,均落在 GitHub 工作区组,并声明supportsLocalWorkspaces = true(L1543-L1568)。底层命令是github.copilot.chat.cloudSessions.openRepository/openIssue/openPullRequest。
关于"身份"还有一个贯穿全局的细节:会话唯一 ID 使用providerId:resourceUri格式,由toSessionId()统一生成(见 session.ts);provider 的多个缓存(会话适配器缓存、分组缓存)都以 resource 身份为键,这为"元数据变化但身份不变"提供了前提。
草稿(Drafts):本地与云端实现同一份 ISession 契约
规范指出:本地草稿与云端草稿实现同一份ISession契约(契约定义在 session.ts),只是适配不同的后端选项:
- 本地草稿
CopilotCLISession(L230 起)负责解析仓库与本地执行配置。构造函数里异步打开 Git 仓库、加载分支(过滤掉copilot-worktree-前缀的 agent 内部分支)、解析默认分支,并处理isolation 模式的选择与记忆(worktree/workspace两种,把选择存入sessions.isolationPicker.selectedMode存储键)。当仓库没有 HEAD commit(空仓库)或无法打开时自动回退到workspace模式。发送时getAgentHostSessionConfig()把isolation、branch及用户的git.branchPrefix、git.worktreeIncludeFiles配置转译为 Agent Host 的 session config。 - 云端草稿
RemoteNewSession(L598 起)暴露 provider 声明的 option group(models、repositories等)与远端 workspace 元数据。其可见性还受when上下文约束(_isOptionGroupVisible用ContextKeyExpr.deserialize(group.when)计算),并会把"是否使用 GitHub 托管 sandbox"选择持久化到sessions.cloudSandboxPicker.useSandbox。
两者都对外暴露可观察(IObservable)的 loading、workspace、model、mode、capabilities 等状态,供共享的新会话 UI 消费;共享 UI 只依赖这些契约字段,不按草稿类分支写逻辑。例如buildChatFromSession()从草稿组装出IChat快照并种入mainChat可观察量(L189-L207)。
既有会话(Existing sessions):AgentSessionAdapter 门面
对已经提交(committed)的 agent 会话,AgentSessionAdapter(L925 起)把它投影成一个稳定的ISession门面。要点:
- 初始化时从
IAgentSession抽取 title、status、file changes、checkpoints、description、GitHub 信息等,并以独立可观察量承载;update()方法在一个**事务(transaction)**里批量刷新这些值,配合setIfChanged与各类相等比较器(sessionWorkspaceEqual、sessionFileChangesEqual、gitHubInfoEqual、structuralEquals、dateEquals等),只有当真实变化时才触发通知。 - 资源身份在元数据变化期间保持不变——门面被缓存于以 resource URI 字符串为键的
_sessionCache(L1451-L1452)。后台 agent 会话列表刷新时(_refreshSessionCache,L3016),既有 adapter 被update()复用;新增/消失/变化则相应发出 added / removed / changed 目录通知;当多聊天分组开启时,还有专门的 replacement 语义与"组内某聊天被删除但组仍存在"的降级处理(_refreshSessionCacheMultiChat,L3105)。 - Provider 相关的元数据翻译留在 adapter 内部:包括仓库/工作树解析(
_buildWorkspace)、GitHub owner/repo 提取(支持metadata.owner/name、repositoryNwo、github-remote-fileURI 三种来源)、Pull Request 编号探测与图标状态展示。adapter 同时把 workspace 划分到本地组(SESSION_WORKSPACE_GROUP_LOCAL)或 GitHub 组(SESSION_WORKSPACE_GROUP_GITHUB)。共享 Sessions 代码只消费 provider 中立的 workspace、changes、status 与 GitHub 信息。
关于"多聊天"的开关是配置项sessions.github.copilot.multiChatSessions(常量COPILOT_MULTI_CHAT_SETTING),默认为true、标记为 preview,注册于同一 contribution 文件(L16-L26)。开启后,provider 依据sessionParentId元数据(草稿阶段则是parentSessionIdoption)把多个 chat 归并为会话组,_chatToSession再从组内主 chat 构建对外ISession,并通过 capabilitysupportsMultipleChats通告能力。
请求生命周期:创建与发送严格分离
规范给出了核心的流程分层:
createNewChat -> return the provider chat resource -> Sessions presents the chat sendRequest -> send through the backing chat service -> commit or update the session -> publish replacement when draft identity changes源码中的关键落点:
- 创建:
createNewSession(workspaceUri, sessionTypeId)先用resolveWorkspace解析工作区,再按 scheme 创建CopilotCLISession或RemoteNewSession,resource 采用untitled-<uuid>形式的临时 URI(L1654-L1678)。createQuickChat/forkChat/createSideChat明确抛错——本 provider 是工作区绑定的,不支持这些形态。 - 发送:
sendRequest区分"新会话首条请求"与"既有 chat 请求"。首条请求走_sendFirstChat:立即把临时会话放入缓存并广播 added,随后构建IChatSendRequestOptions(携带用户选择的模型、mode、permission level、agentHostSessionConfig等),经chatService.sendRequest投递给底层 chat 服务。已提交会话则走_sendExistingChat,对着自己既有的 chat resource 发送,不会再创建新资源。 - 提交与替换:临时(untitled)resource 在首条请求后提交为正式 resource。
_waitForCommittedSession通过监听IChatSessionsService.onDidCommitSession事件等待提交结果——默认与"响应完成"竞争,5 秒兜底;云端会话因需确认往返与网络委派而标记deferred,改用 5 分钟的超时(L2622-L2682)。提交后用_waitForSessionInCache(30 秒上限)等 adapter 进入缓存,最后以_onDidReplaceSession.fire({ from: 临时会话, to: 提交会话 })发布草稿身份变化时的替换事件。进行中的提交还受_inFlightCommits保护,避免并发刷新误删(L1454-L1461)。 - 取消与失败:若
responseCreatedPromise报告取消,则抛CancellationError,会话回到 Completed 并保留供用户查看;若请求被拒或出现意外错误,则清理临时会话并广播 removed。 - 云端新会话还可选择走 GitHub 托管 sandbox:
_sendFirstChatToSandbox先provisionSession,把用户在 composer 中选择的模型带到沙箱(_carryModelToSandbox,等待模型目录最长 5 秒),并在跨 provider 切换时也通过onDidReplaceSession发布替换(L2171-L2214)。
规范强调两条边界:provider 从不直接打开 chat UI——展示与聚焦归ISessionsService所有;多聊天创建受 capability 门控,并遵循共享管理生命周期(createNewChat在非多聊天模式下会拒绝额外创建)。
选择器贡献(Picker contributions)
provider 特有的新会话控件通过共享 Sessions 菜单与作用域化的 picker 服务注入。一个 picker contribution 由三部分构成:
- 带 provider 中立 enablement 的菜单 action;
- 一个 action view item;
- 一个作用域化的 widget / controller。
实例集中在 copilotChatSessionsActions.ts:sessions.defaultCopilot.branchPicker、sandboxPicker、modePicker、permissionPicker等 action 分别挂到Menus.NewSessionRepositoryConfig、Menus.NewSessionConfig、Menus.NewSessionControl菜单,并用when上下文约束到正确的会话类型与 provider。这些上下文由SessionTypeContext、SessionProviderIdContext、SessionHasGitRepositoryContext、IsNewChatSessionContext、ChatContextKeys.enabled组合而成(例如 Cloud 会话才显示 Sandbox picker,CLI 会话才显示 Branch/Mode/Permissions)。PickerActionViewItem把独立 picker widget 包装成BaseActionViewItem供菜单工具栏渲染。
作用域 widget/controller 本身位于同目录的 modePicker.ts(ModePicker/ModePickerModel)、permissionPicker.ts、branchPicker.ts、sandboxPicker.ts,另有面向 Web 端的 mobilePermissionPicker.contribution.ts 复用同一包装器。
关于模型选择,规范强调了两条设计纪律:
- 模型选择策略与 Workbench Chat 共享。本 provider 只负责供给模型快照(
getModelsSnapshot)、呈现选项(getModelPickerOptions)与写入选择(setModel),云端模型的元数据由扩展宿主下发的modelsoption group 合成(_toSyntheticModel),不实现第二套优先级策略。 - 上下文键从作用域化的会话与 provider capabilities 派生;在其它会话 surface 里被调用的 action 不得读取窗口全局的 active session。
contextkey文件位于 src/vs/sessions/common/contextkeys.ts。
删除与归档(Deletion and archive)
删除、归档、重命名与已读状态操作都委托给底层的 agent 会话基础设施,provider 只做编排:
- 归档/取消归档:对未提交(NEW/untitled)会话直接调用草稿的
setArchived并广播 changed(因为其 agent-host 条目是Local类型,会被缓存刷新过滤,直接走agentSession.setArchived无法回流到 UI);对已提交会话则委托_findAgentSession找到的 agent session(L1896-L1930)。 - 已读状态:分组会话的已读状态要跨组内所有 chat 聚合,因此
setSessionReadState先取整组 chat 逐个更新(L1932-L1944)。 - 删除:
deleteSession/deleteSessions会先收集主会话及其组内成员再统一删除;单个 chat 的删除(deleteChat)在仅剩一个 chat 时退化为删除整组,多 chat 时先删底层 agent session 并在确认对话框(deleteChat.confirm)通过后执行,skipConfirmation选项供丢弃临时草稿这类场景使用(L1946-L2075)。底层删除按类型分派:CLI 会话执行agents.github.copilot.cli.deleteSessions,Cloud 会话调用chatService.removeHistoryEntry。 - 重命名:仅 Copilot CLI 后端暴露
github.copilot.cli.sessions.setTitle命令,其余类型直接抛"不支持",因此ISessionCapabilities.supportsRename/supportsDelete也只对 CLI 会话为真(L3421-L3431)。
provider 特有的确认元数据由操作负载(operation payload)携带,共享服务不会下探到扩展宿主的内部实现,保持层间解耦。
测试与共享生命周期覆盖的划分
规范给出一条清晰的测试职责边界:provider 自身的测试只负责本地/云端具体 option 行为、提交时序、元数据翻译与回归;共享的 provider 生命周期行为则由 Sessions 管理测试覆盖。
对应地,该目录维护了一批聚焦的测试:
- copilotChatSessionsProvider.test.ts——provider 主体行为;
- branchPicker.test.ts、modePicker.test.ts、permissionPicker.test.ts、sandboxPicker.test.ts——各作用域 picker。
provider 代码里也为此保留了显式测试缝隙(test seam),例如_getCloudSandboxContribution()与_sandboxModelWaitMs(L2151-L2159),让测试不必真的等待 5 秒沙箱模型超时或依赖全局注册表。
Change policy:这份文档什么时候该动
作为收尾,规范要求维护者仅当以下维度之一发生变化时才更新本文档:
- provider 属主权(ownership);
- 会话/草稿身份(identity)语义;
- 草稿类(draft classes)结构;
- 缓存(cache)语义;
- 请求生命周期(request lifecycle)。
而 picker 细节、选项列表、超时值与 bug 叙事应留在代码与聚焦测试中,正如上文在请求生命周期、模型快照、沙箱等待超时等处看到的那样——它们都以源码注释与测试的形式沉淀,而不是写在 spec 里。
【免费下载链接】vscodeVisual Studio Code项目地址: https://gitcode.com/GitHub_Trending/vscode6/vscode
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考