前言

本教程将指导你如何通过 Cloudflare Worker 在 Cursor 中以第三方api的方式访问 Claude 和 Deepseek 模型。通过覆盖 OpenAI 的 URL 地址,我们可以使用 第三方api 来访问这些模型。

功能特点

  1. 支持 OpenAI 的 GPT 模型
  2. 支持将请求中的 ‘my-cld’ 改写为 ‘claude’(通过 第三方api 代理地址)
  3. 支持将请求中的 ‘my-deepseek’ 改写为 ‘deepseek’(直接通过 Deepseek 官方 API)

详细步骤

1. 创建 Cloudflare Worker

  1. 登录到 Cloudflare Dashboard
  2. 在左侧菜单中选择 “Workers & Pages”
  3. 点击 “Create Application”
  4. 选择 “Create Worker”
  5. 为你的 Worker 命名,然后点击 “Deploy”
  6. 进入 Worker 编辑界面

2. 配置环境变量

  1. 在 Cloudflare Dashboard 中:

    • 进入你的 Worker 的设置页面
    • 点击 “Settings” 选项卡
    • 找到 “Variables” 部分
    • 点击 “Add variable” 按钮
  2. 添加以下环境变量:

    • ONEAPI_UPSTREAM: 你的 第三方api 代理地址
    • DEEPSEEK_UPSTREAM: deepseek 代理地址, 官方为 api.deepseek.com
    • VALID_ORIGINAL_API_KEY: 你的 第三方api key
    • DEEPSEEK_API_KEY: 你的 Deepseek API key
  3. 对于包含敏感信息的变量(API Keys),确保:

    • 点击 “Encrypt” 选项
    • 设置类型为 “Secret text”

3. 配置 Worker 代码

将以下代码复制到 Worker 中:

export default {
    async fetch(request, env, ctx) {
        return await handleRequest(request, env);
    }
};

// 主要处理函数
async function handleRequest(request, env) {
    // 从环境变量获取配置
    const default_upstream = env.ONEAPI_UPSTREAM
    const deepseek_upstream = env.DEEPSEEK_UPSTREAM
    const upstream_path = '/'
    const upstream_mobile = default_upstream
    const blocked_region = []
    const blocked_ip_address = ['0.0.0.0', '127.0.0.1']
    const https = true
    const disable_cache = false

    // 从环境变量获取 API Keys
    const VALID_ORIGINAL_API_KEY = env.VALID_ORIGINAL_API_KEY
    const DEEPSEEK_API_KEY = env.DEEPSEEK_API_KEY

    const region = request.headers.get('cf-ipcountry');
    const upper_region = region ? region.toUpperCase() : '';
    const ip_address = request.headers.get('cf-connecting-ip');
    const user_agent = request.headers.get('user-agent');

    let response = null;
    let url = new URL(request.url);
    let url_hostname = url.hostname;
    let isDeepseekRequest = false;
    let originalModel = '';

    if (request.method === 'POST') {
        const contentType = request.headers.get('content-type');
        if (contentType && contentType.includes('application/json')) {
            try {
                const clonedRequest = request.clone();
                const body = await clonedRequest.json();
                const originalApiKey = request.headers.get('authorization')?.replace('Bearer ', '').trim() || '';
                
                if (originalApiKey === VALID_ORIGINAL_API_KEY && 
                    body.model && typeof body.model === 'string' && 
                    (body.model.startsWith('deepseek') || body.model.startsWith('my-deepseek'))) {
                    isDeepseekRequest = true;
                    originalModel = body.model;
                }
            } catch (error) { }
        }
    }

    if (https == true) {
        url.protocol = 'https:';
    } else {
        url.protocol = 'http:';
    }

    let upstream_domain;
    if (isDeepseekRequest) {
        upstream_domain = deepseek_upstream;
    } else if (await device_status(user_agent)) {
        upstream_domain = default_upstream;
    } else {
        upstream_domain = upstream_mobile;
    }

    url.host = upstream_domain;
    if (url.pathname == '//' || url.pathname == '/') {
        url.pathname = upstream_path;
    } else {
        if (isDeepseekRequest) {
            url.pathname = '/v1/chat/completions';
        } else {
            url.pathname = upstream_path + url.pathname;
        }
    }

    if (blocked_region.includes(upper_region)) {
        response = new Response('Access denied: WorkersProxy is not available in your region yet.', {
            status: 403
        });
    } else if (blocked_ip_address.includes(ip_address)) {
        response = new Response('Access denied: Your IP address is blocked by WorkersProxy.', {
            status: 403
        });
    } else {
        let method = request.method;

        if (method === 'POST') {
            const contentType = request.headers.get('content-type');
            if (contentType && contentType.includes('application/json')) {
                let body = await request.json();
                if (body.model && typeof body.model === 'string') {
                    if (isDeepseekRequest) {
                        if (originalModel.startsWith('my-deepseek')) {
                            body.model = originalModel.replace('my-deepseek', 'deepseek');
                        }
                    } else {
                        body.model = body.model.replace('my-cld', 'claude');
                    }
                }
                
                const headers = new Headers();
                for (const [key, value] of request.headers.entries()) {
                    headers.set(key.toLowerCase(), value);
                }

                if (isDeepseekRequest) {
                    headers.set('authorization', `Bearer ${DEEPSEEK_API_KEY}`);
                }
                headers.set('content-type', 'application/json');

                let original_response = await fetch(url.href, {
                    method: method,
                    headers: headers,
                    body: JSON.stringify(body)
                });
                
                let original_response_clone = original_response.clone();
                
                let original_text = null;
                let response_headers = original_response.headers;
                let new_response_headers = new Headers(response_headers);
                let status = original_response.status;
                
                if (disable_cache) {
                    new_response_headers.set('Cache-Control', 'no-store');
                }

                new_response_headers.set('access-control-allow-origin', '*');
                new_response_headers.set('access-control-allow-credentials', 'true');
                new_response_headers.delete('content-security-policy');
                new_response_headers.delete('content-security-policy-report-only');
                new_response_headers.delete('clear-site-data');
                
                if (new_response_headers.get("x-pjax-url")) {
                    new_response_headers.set("x-pjax-url", response_headers.get("x-pjax-url").replace("//" + upstream_domain, "//" + url_hostname));
                }
                
                const content_type = new_response_headers.get('content-type');
                if (content_type != null && content_type.includes('text/html') && content_type.includes('UTF-8')) {
                    original_text = await replace_response_text(original_response_clone, upstream_domain, url_hostname);
                } else {
                    original_text = original_response_clone.body;
                }
                
                response = new Response(original_text, {
                    status,
                    headers: new_response_headers
                });
            } else {
                response = await fetch(request);
            }
        } else {
            response = await fetch(request);
        }
    }
    return response;
}

async function replace_response_text(response, upstream_domain, host_name) {
    let text = await response.text()

    var i, j;
    for (i in replace_dict) {
        j = replace_dict[i]
        if (i == '$upstream') {
            i = upstream_domain
        } else if (i == '$custom_domain') {
            i = host_name
        }

        if (j == '$upstream') {
            j = upstream_domain
        } else if (j == '$custom_domain') {
            j = host_name
        }

        let re = new RegExp(i, 'g')
        text = text.replace(re, j);
    }
    return text;
}

async function device_status(user_agent_info) {
    var agents = ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod"];
    var flag = true;
    for (var v = 0; v < agents.length; v++) {
        if (user_agent_info.indexOf(agents[v]) > 0) {
            flag = false;
            break;
        }
    }
    return flag;
}

const replace_dict = {
    '$upstream': '$custom_domain',
};

配置说明

在 Cloudflare Workers 中配置的环境变量:

  1. ONEAPI_UPSTREAM: 你的第三方api代理地址,如:api.yourdomain.com

    • 类型:普通文本
    • 示例:api.yourdomain.com
  2. DEEPSEEK_UPSTREAM: 你的deepseek代理地址,如:api.deepseek.com

    • 类型:普通文本
    • 示例:api.deepseek.com
  3. VALID_ORIGINAL_API_KEY: 你的第三方api的 API key

    • 类型:加密文本(Secret)
    • 示例:sk-xxxxxxxxxxxxxxxxxxxxxxxx
  4. DEEPSEEK_API_KEY: 你的 Deepseek API key

    • 类型:加密文本(Secret)
    • 示例:sk-xxxxxxxxxxxxxxxxxxxxxxxx

安全提示:将 API Keys 存储为加密的环境变量可以:

  • 防止密钥意外泄露到代码库中
  • 方便进行密钥轮换
  • 在日志中自动隐藏敏感信息
  • 符合安全最佳实践

4. 验证配置

部署完成后,你可以使用以下 curl 命令验证配置是否正常:

# 测试 Claude 模型
curl https://your-worker.workers.dev/v1/chat/completions -H "Content-Type: application/json" -H "Authorization: Bearer sk-oriapikey" -d '{
  "messages": [
    {
      "role": "system",
      "content": "You are a test assistant."
    },
    {
      "role": "user",
      "content": "1+1=?"
    }
  ],
  "model": "my-cld-3-5-sonnet-latest"
}'
# 测试 Deepseek 模型
curl https://your-worker.workers.dev/v1/chat/completions -H "Content-Type: application/json" -H "Authorization: Bearer sk-oriapikey" -d '{
  "messages": [
    {
      "role": "system",
      "content": "You are a test assistant."
    },
    {
      "role": "user",
      "content": "1+1=?"
    }
  ],
  "model": "my-deepseek-coder"
}'

5. 在 Cursor 中配置

  1. 打开 Cursor 设置
  2. 找到 AI 设置部分
  3. 在 OpenAI API Key 中填入你配置的 VALID_ORIGINAL_API_KEY
  4. 在 API URL 中填入 https://your-worker.workers.dev/v1

重要说明:在 Cursor 中点击验证按钮可能会显示 “Invalid openai api key” 的错误提示,这是正常现象,不会影响实际使用。只要你在上一步(步骤4)中使用 curl 命令验证通过,就可以正常在 Cursor 的聊天界面中使用各个模型。这是因为 Cursor 的验证机制直接检查 OpenAI 的 API,而我们使用的是代理服务。

模型映射说明

本配置支持以下模型映射:

  • my-cld-*claude-*:所有以 my-cld 开头的模型名称会被自动映射到对应的 Claude 模型
  • my-deepseek-*deepseek-*:所有以 my-deepseek 开头的模型名称会被自动映射到对应的 Deepseek 模型

例如:

  • my-cld-3-5-sonnet-latestclaude-3-5-sonnet-latest
  • my-deepseek-coderdeepseek-coder

常见问题排查

  1. 如果遇到 403 错误:

    • 检查 API key 是否正确配置
    • 确认 IP 和地区是否在屏蔽列表中
  2. 如果遇到模型不可用:

    • 确认模型名称是否正确映射
    • 检查 第三方api 是否支持该模型
    • 验证 Deepseek API key 是否有效
  3. 如果遇到连接问题:

    • 确认 Worker 是否正常部署
    • 检查 upstream 地址是否正确配置
    • 验证网络连接是否正常

注意事项

  1. 请确保你的 API Key 安全,不要泄露给他人
  2. 建议定期更换 API Key
  3. 如果使用频率较高,请注意 Cloudflare Worker 的使用配额
  4. 建议在正式使用前进行充分的测试验证

如果你需要任何帮助或遇到问题,欢迎提出反馈。