前言

近年来涌现出了不少优秀的 AI 编辑器,如 Cursor、Trae、Windsurf 等,但我自己已经深度习惯了 VS Code 的工作方式——在上面投入了大量时间去打磨开发环境。时至今日,VS Code 仍是我最顺手的编辑器,配合最新版本的 Copilot,AI 辅助编程的体验也相当不错。不过,在接入第三方 AI 供应商时,我遇到了一些让人头疼的问题。

VS Code Copilot 接入第三方 AI 供应商共有两种方式:

方式一:安装插件市场的 BYOK 插件

这种方式看似简单,但实际开发一个插件需要查阅大量文档,过程相当繁琐。我自己曾尝试开发过一款——不仅要生成配置,还要处理请求兼容、异常捕获、token 统计等问题。明明只是想接入一个第三方 AI 供应商,结果却耗费了大量时间和精力,性价比极低。

方式二:官方提供的 Custom Endpoint

这种方式无需编写代码,兼容性由官方负责,我们只需维护一份 JSON 配置文件即可。但对于新手来说,如何正确配置并不直观;随着接入的模型越来越多,配置也会越来越庞大。更令人头疼的是,无论哪种方式,配置都不会同步到云端——每换一台设备、每切换一个 VS Code 配置文件,都得从头配置一遍。

为了彻底解决这个问题,我开发了 VS Code BYOK 配置生成器:只需填写几项基础信息,即可一键生成完整的 JSON 配置,粘贴到 VS Code 后更新 API Key 即可使用。

教程

考虑到插件方式开发成本过高,这里我们直接采用官方提供的 Custom Endpoint 方式来接入第三方 AI 供应商。

VS Code BYOK 配置生成器的作用就是生成对应的 JSON 配置,我们只需将其粘贴到 VS Code 的配置文件中,更新 API Key 后即可使用。

打开工具网站

工具已完整开源到 GitHub,并通过 GitHub Pages 部署了在线版本,无需安装,打开即用。

填写基础信息

进入页面后,首先填写以下三项基础信息:

  1. Group Name(分组名称):由于可能同时接入多个第三方 AI 供应商,建议为每个供应商单独命名分组,便于管理。
  2. BaseUrl(基础 URL):第三方 AI 供应商的接口地址,通常是一个域名。
  3. ApiKey(密钥):仅在本地使用,不会上传到任何云端,主要用于自动拉取模型列表。

填写数据

无法获取模型列表时:使用 Cloudflare Worker 代理

部分 sub2api 站点没有正确配置跨域。工具在获取模型时会请求供应商的 /v1/models 接口,并携带 API Key 认证头;浏览器会因此先发送 OPTIONS 预检请求。若站点没有在预检响应中允许对应的来源、请求头或方法,浏览器就会拦截后续请求,表现为点击“获取模型”后出现 CORS 跨域错误。

这个问题无法由纯前端绕过。可以使用免费的 Cloudflare Worker 作为中转:浏览器只请求 Worker,Worker 再从服务端请求目标 API,因此不会受目标站点浏览器跨域策略的影响。

  1. 登录 Cloudflare 控制台,创建一个 Worker。
  2. 将下面的代码粘贴到 Worker 编辑器并部署。
  3. ALLOWED_TARGET_HOSTS 中填写你的 API 网关域名,例如 "sub2.example.com",然后取消白名单校验代码的注释。不要将 Worker 配置为可代理任意域名的开放代理。
  4. 将部署后得到的 Worker 地址填入生成器的 BaseUrl。例如目标 API 是 https://sub2.example.com,Worker 地址为 https://byok-proxy.example.workers.dev,则填写:https://byok-proxy.example.workers.dev?url=https%3A%2F%2Fsub2.example.com
// 允许代理的目标域名白名单(防止你的 Worker 被当作开放代理滥用,消耗你的配额)
// 如果你确实希望允许代理到【任何】域名,可以将此数组留空 [],并在下方跳过校验(不推荐)
const ALLOWED_TARGET_HOSTS = [
    // "localhost",
    // "sub2.xxxx.com",
];

function corsHeaders(request) {
    const origin = request.headers.get("Origin");
    return {
        // 允许任何前端域名访问,若无 Origin 则降级为 *
        "Access-Control-Allow-Origin": origin || "*",
        "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS, PATCH",
        "Access-Control-Allow-Headers":
            request.headers.get("Access-Control-Request-Headers") ||
            "Authorization, Content-Type, X-Requested-With",
        "Access-Control-Max-Age": "86400",
        // 必须添加 Vary: Origin,防止 CDN 或浏览器缓存错乱
        Vary: "Origin",
    };
}

export default {
    async fetch(request) {
        const headers = corsHeaders(request);

        // 处理预检请求
        if (request.method === "OPTIONS") {
            return new Response(null, { status: 204, headers });
        }

        const requestUrl = new URL(request.url);
        // 通过查询参数 ?url= 获取目标地址,语义更清晰,避免 Path 拼接的怪异感
        const target = requestUrl.searchParams.get("url");

        if (!target) {
            return new Response("Missing 'url' query parameter. Usage: ?url=https://example.com/api", {
                status: 400,
                headers,
            });
        }

        let targetUrl;
        try {
            targetUrl = new URL(target);
        } catch {
            return new Response("Invalid target URL format", {
                status: 400,
                headers,
            });
        }

        if (!["http:", "https:"].includes(targetUrl.protocol)) {
            return new Response("Unsupported target protocol (only http/https allowed)", {
                status: 400,
                headers,
            });
        }

        // 安全检查:验证目标域名是否在白名单中
        // if (ALLOWED_TARGET_HOSTS.length > 0 && !ALLOWED_TARGET_HOSTS.includes(targetUrl.hostname)) {
        //   return new Response(`Target host '${targetUrl.hostname}' is not allowed`, {
        //     status: 403,
        //     headers,
        //   });
        // }

        // 清理请求头,避免将前端的 Host、Origin、Cookie 等敏感或冲突信息带给目标服务器
        const proxyHeaders = new Headers(request.headers);
        const headersToRemove = [
            "host",
            "origin",
            "referer",
            "cookie",
            "cf-connecting-ip",
            "cf-ray",
            "x-forwarded-for",
            "x-forwarded-proto",
        ];
        headersToRemove.forEach((header) => proxyHeaders.delete(header));

        // 可选:添加一个标识,告诉目标服务器请求经过了代理
        proxyHeaders.set("X-Forwarded-Host", requestUrl.hostname);

        try {
            const response = await fetch(targetUrl, {
                method: request.method,
                headers: proxyHeaders,
                body: ["GET", "HEAD"].includes(request.method) ? undefined : request.body,
                redirect: "follow", // 让 Worker 自动处理目标服务器的 301/302 重定向
            });

            const newResponse = new Response(response.body, response);

            // 合并 CORS 头
            for (const [key, value] of Object.entries(headers)) {
                newResponse.headers.set(key, value);
            }

            // 移除可能阻碍前端正常渲染的目标服务器安全策略头
            newResponse.headers.delete("Content-Security-Policy");
            newResponse.headers.delete("X-Frame-Options");

            return newResponse;
        } catch (error) {
            return new Response(
                JSON.stringify({
                    error: error instanceof Error ? error.message : "Proxy request failed",
                }),
                {
                    status: 502,
                    headers: {
                        ...headers,
                        "Content-Type": "application/json",
                    },
                },
            );
        }
    },
};

我目前使用我自己的 Cloudflare Worker 在项目中直接使用了,如果你害怕泄露自己key,可以自己建个自己的worker,拿到链接后替换掉项目中环境变量即可,自己构建一个私有的。

勾选模型

基础信息填写完毕后,选择需要的模型。模型来源有两种:

  1. 自动获取:点击"获取模型"按钮,工具会调用供应商接口自动拉取可用模型列表。目前主流的 AI 网关(如 New API、sub2api 等)均支持该接口;若出现 CORS 错误,请按上文部署代理后再试。

    自动获取

  2. 预设模型:考虑到部分网关未提供标准的 OpenAI 获取模型接口,工具内置了市面上常见的 50 余种模型供手动选择。

    预设模型

工具栏提供了搜索、全选、反选等快捷操作,日常使用完全够用。

协议选择

勾选好模型后,选择对应的请求协议。目前主流的协议共三种:

  1. Chat Completions:最通用的 OpenAI 标准协议,绝大多数供应商均支持。
  2. Responses:较新的 OpenAI 标准协议。
  3. Messages:Anthropic 专属协议。

一般选择 Chat Completions 即可,除非你的供应商仅支持 Responses 或 Messages 协议。

协议选择

模型设置(可选)

VS Code 支持为每个模型配置额外参数,如 temperaturetop_p 等。这些参数最常见的用途是声明模型的思考强度,例如:

{
    "kimi-k3": {
        "reasoningEffort": "max"
    }
}

工具已根据各模型特性预设了合理的默认值,通常无需手动调整,有特殊需求时再修改即可。

模型设置(可选)

生成配置

点击"生成配置"按钮,即可在输出区域查看生成的 JSON 配置。

生成结果分为两种模式:

  1. 全新添加:VS Code 配置文件为空时使用,直接粘贴整段 JSON 即可。
  2. 追加:VS Code 配置文件已有内容时使用,将生成的对象追加到数组末尾。

输出结果支持直接编辑,不满意可在输出框中直接修改,完成后点击复制或下载保存。

生成配置

在 VS Code 中配置

复制好配置后,打开 VS Code,在 Copilot 对话界面右下角点击模型名称,弹出模型选择界面后,点击 Other Models 右侧的齿轮图标,进入设置界面。

进入设置界面

在设置界面右上角点击 Open Settings (JSON),打开 JSON 配置文件。

点击 JSON 配置

  • 若文件为空,直接粘贴全新添加模式生成的 JSON。
  • 若文件已有内容,将追加模式生成的对象插入到数组末尾。

粘贴配置

最终配置示例如下:

[
    {
        "name": "测试分组",
        "vendor": "customendpoint",
        "apiKey": "${input:chat.lm.secret.-7b6c5242}",
        "apiType": "chat-completions",
        "models": [
            {
                "id": "kimi-k3",
                "name": "kimi-k3",
                "url": "xxxx",
                "toolCalling": true,
                "vision": true,
                "maxInputTokens": 1000000,
                "maxOutputTokens": 131072,
                "contextWindow": 1000000,
                "thinking": true,
                "supportsReasoningEffort": ["low", "high", "max"],
                "reasoningEffortFormat": "chat-completions"
            }
        ],
        "settings": {
            "kimi-k3-test": {
                "reasoningEffort": "max"
            }
        }
    }
]

最后一步:更新密钥

生成的配置中,apiKey 是一个随机占位符,VS Code 并不知道真实的 Key 值。回到设置界面,在刚刚添加的分组上右键,选择更新 API 密钥,在弹出的输入框中填入真实的 API Key,按回车确认即可。

更新密钥

使用

密钥更新完成后,在 Copilot 对话界面的模型选择器中即可找到刚刚添加的分组及其模型,选择后直接使用。至此,接入第三方 AI 供应商的全部流程就完成了。

使用

分类: VSCode 标签: vscodeBYOK第三方模型供应

评论

暂无评论数据

暂无评论数据

目录