上下文提供者:30+ 插件注入代码上下文
上下文提供者:30+ 插件注入代码上下文
学习目标
本章要解决的核心问题:AI 编程助手如何让用户灵活地选择哪些代码上下文发送给 LLM,同时保持可扩展性和按需加载的性能?
你将学到:
- Continue 的 ContextProvider 双方法接口设计
- 36 个内置提供者的分类与职责
- 子菜单按需加载机制
- Token 预算和
isItemTooBig检查
前置知识
本章涉及上下文提供者插件的通用模式,建议先阅读:
下文假设你已理解上下文提供者的通用概念,直接聚焦 Continue 的具体实现。
项目实践
双方法接口
Continue 的 ContextProvider 接口定义了两个核心方法:
interface ContextProvider { // 1. 加载子菜单项(用于 @mention 下拉列表) loadSubmenuItems(config: Config, extras): Promise<SubmenuItem[]>;
// 2. 根据查询返回上下文片段 getContextItems(query: string, extras: ContextProviderExtras): Promise<ContextItem[]>;}关键设计细节:
loadSubmenuItems是轻量级操作,只返回元数据(文件名、标题等),不读取实际内容getContextItems是重量级操作,可能涉及文件读取、网络请求、向量检索等- 两者共享
extras对象,包含 config、llm、ide、fetch 等运行时信息
36 个内置提供者分类
Continue 在 core/context/providers/ 中实现了 36 个内置提供者:
代码类(核心):
codebase— 整个代码库的语义搜索code— 选中代码或指定文件currentFile— 当前打开的文件open— 所有打开的文件fileTree— 项目目录结构folder— 指定文件夹内容file— 指定文件diff— Git 工作区变更problems— IDE 诊断(错误/警告)terminal— 终端输出
文档类:
docs— 外部文档索引(向量检索)url— 单个 URL 内容web— 网络搜索google— Google 搜索greptile— Greptile 代码搜索服务
外部集成类:
github— GitHub Issues/PRsgitlab— GitLab MRjira— Jira Issuesdiscord— Discord 频道消息http— 自定义 HTTP 端点
系统类:
os— 操作系统信息database— PostgreSQL 查询结果debug— 调试本地变量clipboard— 剪贴板内容
配置类:
rules— 项目规则文件repo-map— 项目结构地图
子菜单按需加载
当用户输入 @ 时,GUI 请求 context/loadSubmenuItems:
// core/core.ts 消息处理on("context/loadSubmenuItems", async (msg) => { const { config } = await this.configHandler.loadConfig(); const items = await config.contextProviders ?.find(p => p.description.title === msg.data.title) ?.loadSubmenuItems({ config, ide: this.ide, fetch: ... }); return items || [];});这确保了:
- 只在用户输入
@时才计算子菜单项 - 每个提供者独立计算,不互相阻塞
- 如果某个提供者加载失败,只影响它自己,不影响其他提供者
Token 预算控制
isItemTooBig 检查确保上下文片段不会超过 LLM 的上下文窗口:
private async isItemTooBig(item: ContextItemWithId) { const llm = config.selectedModelByRole.chat; const tokens = countTokens(item.content, llm.model); if (tokens > llm.contextLength - llm.completionOptions.maxTokens) { return true; // 片段太大,不应发送 } return false;}问题与规避
陷阱 1:@codebase 检索返回过多内容
现象:查询返回大量代码片段,超出上下文窗口。
规避:检索管线内部有 nFinal 参数控制最终返回数量(默认 25 个片段)。每个片段还要通过 isItemTooBig 检查。
陷阱 2:远程提供者(GitHub/Jira)加载慢
现象:输入 @github 后子菜单加载需要几秒。
原因:loadSubmenuItems 需要调用外部 API 获取 Issues 列表。
规避:子菜单加载是异步的,GUI 显示 loading 状态。用户可以设置超时或缓存结果。
陷阱 3:自定义提供者注册失败
现象:通过 CustomContextProviderClass 注册的自定义提供者不生效。
规避:自定义提供者必须正确实现 description 元数据(title 和 displayName)。title 重复会覆盖之前的提供者。
设计取舍
为什么用统一接口而非每种数据源独立接口
36 种数据源如果各有接口,核心代码需要 36 种不同的处理路径。统一 ContextProvider 接口使得:
- 核心代码简单:
getContextItems的调用者不关心具体类型 - 用户可扩展:新提供者只需实现一个接口
- GUI 统一:所有提供者通过
@mention触发,交互一致
代价:接口需要足够通用,某些数据源可能需要适配才能符合接口约束。
内置提供者 vs MCP 工具
Continue 有 36 个内置提供者,同时支持 MCP 扩展。两者的边界:
- 内置提供者:与代码/IDE 紧密相关,需要访问 IDE API、文件系统、Git 等
- MCP 工具:外部服务通过 MCP 协议暴露,不依赖 IDE 内部能力
这不是技术上的必须区分,而是职责边界的设计决策。