Double简介

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

主要功能

①聊天

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

②自动补全

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

③快捷键操作

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

对标Github的Copilot,Double目前只支持 VScode,点击了解更多内容。

部署教程

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

限制:目前免费计划 50条/月,订阅用户不受限制利用Work部署Double白嫖GPT4和Claude-3-Opus - 图1

第一步,安装VScode 和 插件

VScode 点击下载 Double插件 点击下载 ### 第二步,注册账号,获取api
①安装好插件后重启,登录插件,登录成功后先发送一条聊天消息试试。

利用Work部署Double白嫖GPT4和Claude-3-Opus - 图2

②关闭VScode,点击打开,在页面中点击取消,再点 **Not working?** 利用Work部署Double白嫖GPT4和Claude-3-Opus - 图3 复制 Auth Token 利用Work部署Double白嫖GPT4和Claude-3-Opus - 图4

第三步,创建worker

  1. /*
  2. 源代码来自 [Linx.do] 的帖子 :https://linux.do/t/topic/28184
  3. */
  4. addEventListener("fetch", event => {
  5. event.respondWith(handleRequest(event.request));
  6. });
  7. async function generateResponseStream(apiResponse, model, timestamp) {
  8. const fixedStart = {
  9. id: "chatcmpl-smnet-2311676378-double",
  10. object: "chat.completion.chunk",
  11. created: timestamp,
  12. model: model,
  13. choices: [
  14. {
  15. delta: {
  16. role: "assistant",
  17. content: ""
  18. },
  19. index: 0,
  20. finish_reason: null
  21. },
  22. ],
  23. };
  24. const fixedEnd = {
  25. id: "chatcmpl-smnet-2311676378-double",
  26. object: "chat.completion.chunk",
  27. created: timestamp,
  28. model: "gpt-4",
  29. choices: [
  30. {
  31. delta: {},
  32. index: 0,
  33. finish_reason: "stop"
  34. },
  35. ],
  36. };
  37. const reader = apiResponse.body.getReader();
  38. return new Response(new ReadableStream({
  39. async start(controller) {
  40. controller.enqueue(new TextEncoder().encode('data: ' + JSON.stringify(fixedStart) + '\n\n'));
  41. while (true) {
  42. const { done, value } = await reader.read();
  43. if (done) break;
  44. const chunk = value;
  45. const formattedChunk = {
  46. id: "chatcmpl-smnet-2311676378-double",
  47. object: "chat.completion.chunk",
  48. created: timestamp,
  49. model: "gpt-4",
  50. choices: [
  51. {
  52. delta: {
  53. content: new TextDecoder().decode(chunk),
  54. role: "assistant"
  55. },
  56. index: 0,
  57. finish_reason: null
  58. },
  59. ],
  60. };
  61. controller.enqueue(new TextEncoder().encode('data: ' + JSON.stringify(formattedChunk) + '\n\n'));
  62. }
  63. controller.enqueue(new TextEncoder().encode('data: ' + JSON.stringify(fixedEnd) + '\n\n'));
  64. controller.enqueue(new TextEncoder().encode('data: [DONE]\n\n'));
  65. controller.close();
  66. }
  67. }), {
  68. headers: {
  69. "Access-Control-Allow-Origin": "*",
  70. 'Content-Type': 'text/event-stream'
  71. }
  72. });
  73. }
  74. async function generateNormalResponse(apiResponse, model, timestamp) {
  75. const responseText = await apiResponse.text();
  76. const jsonResponse = JSON.stringify({
  77. id: "chatcmpl-smnet-2311676378-double",
  78. object: "chat.completion",
  79. created: timestamp,
  80. model: model,
  81. choices: [{
  82. index: 0,
  83. message: {
  84. role: "assistant",
  85. content: responseText
  86. },
  87. finish_reason: "stop"
  88. }]
  89. });
  90. return new Response(jsonResponse, {
  91. headers: {
  92. "Access-Control-Allow-Origin": "*",
  93. 'Content-Type': 'application/json'
  94. }
  95. });
  96. };
  97. async function handleRequest(request) {
  98. try {
  99. const url = new URL(request.url);
  100. if (request.method === 'OPTIONS') {
  101. return new Response(null, {
  102. status: 204,
  103. headers: {
  104. "Access-Control-Allow-Origin": "*",
  105. "Access-Control-Allow-Methods": "POST, OPTIONS",
  106. "Access-Control-Allow-Headers": "Content-Type, Authorization"
  107. }
  108. });
  109. }
  110. if (request.method !== "POST" || url.pathname !== "/v1/chat/completions") {
  111. return new Response("Invalid request", {status: 400});
  112. }
  113. const bearerToken = request.headers.get("Authorization")?.split("Bearer ")[1];
  114. if (!bearerToken) {
  115. throw new Error("Missing API Key");
  116. }
  117. let {messages, model, stream=true} = await request.json();
  118. if (!messages) {
  119. throw new Error("Messages object is missing");
  120. }
  121. const systemMessage = messages.find(message => message.role === 'system');
  122. if (systemMessage) {
  123. let userMessage = messages.find(message => message.role === 'user');
  124. if (userMessage) {
  125. userMessage.content = systemMessage.content + "\n\n" + userMessage.content;
  126. }
  127. messages = messages.filter(message => message.role !== 'system');
  128. }
  129. messages = messages.map(({role, content}) => ({
  130. role: role === 'system' ? 'assistant' : role,
  131. message: content,
  132. codeContexts: []
  133. }));
  134. let chat_model;
  135. switch (model?.toLowerCase()) {
  136. case "claude-3-opus":
  137. chat_model = "Claude 3 (Opus)";
  138. break;
  139. default:
  140. chat_model = "GPT4 Turbo";
  141. }
  142. const apiUrl = "https://api.double.bot/api/v1/chat";
  143. const headers = {
  144. "Content-Type": "application/json",
  145. "Authorization": `Bearer ${bearerToken}`
  146. };
  147. const body = JSON.stringify({
  148. "api_key": bearerToken,
  149. "messages": messages,
  150. "chat_model": chat_model,
  151. });
  152. const apiResponse = await fetch(apiUrl, {method: "POST", headers, body});
  153. if (!apiResponse.ok) {
  154. const errorText = await apiResponse.text();
  155. throw new Error("API request failed: " + errorText);
  156. }
  157. const timestamp = Math.floor(Date.now() / 1000);
  158. if (stream) {
  159. return await generateResponseStream(apiResponse, model?.toLowerCase(), timestamp);
  160. } else {
  161. return await generateNormalResponse(apiResponse, model?.toLowerCase(), timestamp);
  162. }
  163. } catch (err) {
  164. return new Response(err.message, {status: 500});
  165. }
  166. }

第四步,绑定域名

触发器中找到添加自定义域,绑定自己的域名,等待证书变成有效

利用Work部署Double白嫖GPT4和Claude-3-Opus - 图5

第五步,配置客户端

OneAPI

在oneapi自定义渠道,填入你worker的domain以及auth token 模型填写gpt4claude-3-opus 利用Work部署Double白嫖GPT4和Claude-3-Opus - 图6 #### NextChat 在chatnext或者其他客户端,填入你的oneapi密钥和oneapi域名

利用Work部署Double白嫖GPT4和Claude-3-Opus - 图7