【Cloudflare系列教程】利用Worker部署DTO


Double简介


Double 是一款对细节有着极致追求,专为高效工作而打造的人工智能编码助手,目前已经可以在 VScode 中免费体验。


主要功能


  • 聊天

    侧边栏支持 GPT-4 聊天,支持在提问时引用代码。

  • 自动补全

    编辑代码时支持 Tab 一键自动补全。

  • 快捷键操作

    解放鼠标,支持快捷键,方便键盘操作。

与百度的代码助手Comate一样,Double目前只支持 VScode点击了解更多内容。


简单上手


DTODouble to OpenAI,接下来本帖将介绍如何利用Worker逆向Double的请求并将其模拟为OpenAI格式,实现统一化调用。

限制:目前免费计划 50条/月,订阅用户不受限制

第一步,安装VScode 和 插件

第二步,注册账号,获取api

  • 登录
    安装好插件后重启,登录插件,登录成功后先发送一条聊天消息试试。
  • 获取auth token
    关闭VScode,点击 打开,在页面中点击取消,再点 Not working? ,复制 Auth Token

第三步,创建worker

感谢热佬用户2311676378、用户superares,这是原帖地址

addEventListener("fetch", event => {
  event.respondWith(handleRequest(event.request));
});

async function generateResponseStream(apiResponse, model, timestamp) {
    const fixedStart = {
        id: "chatcmpl-smnet-2311676378-double",
        object: "chat.completion.chunk",
        created: timestamp,
        model: model,
        choices: [
            {
                delta: {
                    role: "assistant",
                    content: ""
                },
                index: 0,
                finish_reason: null
            },
        ],
    };

    const fixedEnd = {
        id: "chatcmpl-smnet-2311676378-double",
        object: "chat.completion.chunk",
        created: timestamp,
        model: "gpt-4",
        choices: [
            {
                delta: {},
                index: 0,
                finish_reason: "stop"
            },
        ],
    };

    const reader = apiResponse.body.getReader();
    return new Response(new ReadableStream({
        async start(controller) {
            controller.enqueue(new TextEncoder().encode('data: ' + JSON.stringify(fixedStart) + '\n\n'));
            while (true) {
                const { done, value } = await reader.read();
                if (done) break;
                const chunk = value;
                const formattedChunk = {
                    id: "chatcmpl-smnet-2311676378-double",
                    object: "chat.completion.chunk",
                    created: timestamp,
                    model: "gpt-4",
                    choices: [
                        {
                            delta: {
                                content:  new TextDecoder().decode(chunk),
                                role: "assistant"
                            },
                            index: 0,
                            finish_reason: null
                        },
                    ],
                };
                controller.enqueue(new TextEncoder().encode('data: ' + JSON.stringify(formattedChunk) + '\n\n'));
              }
              controller.enqueue(new TextEncoder().encode('data: ' + JSON.stringify(fixedEnd) + '\n\n'));
              controller.enqueue(new TextEncoder().encode('data: [DONE]\n\n'));
              controller.close();
        }
    }), {
        headers: {
        "Access-Control-Allow-Origin": "*",
        'Content-Type': 'text/event-stream'
        }
    });
}

async function generateNormalResponse(apiResponse, model, timestamp) {
    const responseText = await apiResponse.text();
    const jsonResponse = JSON.stringify({
        id: "chatcmpl-smnet-2311676378-double",
        object: "chat.completion",
        created: timestamp,
        model: model,
        choices: [{
            index: 0,
            message: {
                role: "assistant",
                content: responseText
            },
            finish_reason: "stop"
        }]
    });
      return new Response(jsonResponse, {
        headers: {
          "Access-Control-Allow-Origin": "*",
          'Content-Type': 'application/json'
        }
      });
};

async function handleRequest(request) {
  try {
      const url = new URL(request.url);
      if (request.method === 'OPTIONS') {
          return new Response(null, {
              status: 204,
              headers: {
                  "Access-Control-Allow-Origin": "*",
                  "Access-Control-Allow-Methods": "POST, OPTIONS",
                  "Access-Control-Allow-Headers": "Content-Type, Authorization"
              }
          });
      }
      if (request.method !== "POST" || url.pathname !== "/v1/chat/completions") {
          return new Response("Invalid request", {status: 400});
      }

      const bearerToken = request.headers.get("Authorization")?.split("Bearer ")[1];

      if (!bearerToken) {
          throw new Error("Missing API Key");
      }

      let {messages, model, stream=false} = await request.json();
      if (!messages) {
          throw new Error("Messages object is missing");
      }

      const systemMessage = messages.find(message => message.role === 'system');
      if (systemMessage) {
        let userMessage = messages.find(message => message.role === 'user');
        if (userMessage) {
          userMessage.content = systemMessage.content + "\n\n" + userMessage.content;
        }
        messages = messages.filter(message => message.role !== 'system');
      }
      messages = messages.map(({role, content}) => ({
        role: role === 'system' ? 'assistant' : role,
        message: content,
        codeContexts: []
      }));

      let chat_model;
      switch (model?.toLowerCase()) {
          case "claude-3-opus":
              chat_model = "Claude 3 (Opus)";
              break;
          default:
              chat_model = "GPT4 Turbo";
      }
      const JWT = await getJWT(bearerToken);

      const apiUrl = "https://api.double.bot/api/v1/chat";
      const headers = {
          "Content-Type": "application/json",
          "double-version": "2024-03-04",
          "Authorization": `Bearer ${JWT}`
      };
      const body = JSON.stringify({
          "api_key": bearerToken,
          "messages": messages,
          "chat_model": chat_model,
      });

      const apiResponse = await fetch(apiUrl, {method: "POST", headers, body});

      if (!apiResponse.ok) {
          const errorText = await apiResponse.text();
          throw new Error("API request failed: " + errorText);
      }
      const timestamp = Math.floor(Date.now() / 1000);
      if (stream) {
        return await generateResponseStream(apiResponse, model?.toLowerCase(), timestamp);
      } else {
        return await generateNormalResponse(apiResponse, model?.toLowerCase(), timestamp);
      }
  } catch (err) {
      return new Response(err.message, {status: 500});
  }
}

async function getJWT(api_key) {
  const jwtUrl = "https://api.double.bot/api/auth/refresh";
  const headers = {
    "Content-Type": "application/json",
    "Authorization": `Bearer ${api_key}`
  };
  const apiResponse = await fetch(jwtUrl, {method: "POST", headers});
  const jwt = await apiResponse.json();
  return jwt.access_token;
}

第四步,绑定域名

第五步,配置客户端

  • 在oneapi自定义渠道,填入你worker的domain以及auth token

  • 在chatnext或者其他客户端,填入你的oneapi密钥和oneapi域名

参考链接



45 Likes

消灭零回复

1 Like

good

1 Like

开码了叫我一声

nice

1 Like

完工 速来

1 Like

来了,给你顶一发

牛的

1 Like

支持

1 Like

开搞开搞

1 Like

https://linux.do/t/topic/28215?u=lueluelue
这个代码也可以嘛

1 Like

最开始我引用的就是这个帖子,目前无法连续对话,需要帖主更新

1 Like

感谢

1 Like

https://worker名.用户名.workers.dev
这个域名可以吗

1 Like

cf的这个域名有几率被墙,建议绑自己的,可以优选ip提高访问速度

1 Like

谢谢!

1 Like

围观学习

1 Like

大佬,我按照你的配置
接口地址:https://production.double.xxxxx.workers.dev/
api_key: api_i8bbxxxxxxxxx
自定义模型名:claude-3-opus,gpt-4

然后报错
{
“error”: true,
“message”: “URL is not valid or contains user credentials.”
}

请问我是哪里配置不正确吗?

1 Like

DNS解析没同步吧,你等会儿试试

1 Like

请教下,我在chat-next-web上面报错

{
“error”: true,
“message”: “Failed to fetch”
}

选了gtp4或者gpt3模型都不行 :rofl:

1 Like