LinASTC/DocsAnthropic2API
0
1import express from 'express';
2import WebSocket from 'ws';
3import { v4 as uuidv4 } from 'uuid';
4import { randomBytes } from 'crypto';
5import cors from 'cors';
6import dotenv from 'dotenv';
7
8// 配置加载
9dotenv.config();
10
11// 配置常量
12const CONFIG = {
13 API: {
14 BASE_URL: "wss://api.inkeep.com/graphql",
15 API_KEY: process.env.API_KEY || "sk-123456",
16 },
17 MODELS: {
18 'claude-3-5-sonnet-20241022': 'claude-3-5-sonnet-20241022',
19 },
20 SERVER: {
21 PORT: process.env.PORT || 3000,
22 BODY_LIMIT: '5mb'
23 },
24 DEFAULT_HEADERS: {
25 'Host': 'api.inkeep.com',
26 'Connection': 'Upgrade',
27 'Pragma': 'no-cache',
28 'Cache-Control': 'no-cache',
29 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36',
30 'Upgrade': 'websocket',
31 'Origin': 'https://docs.anthropic.com',
32 'Sec-WebSocket-Version': '13',
33 'Accept-Encoding': 'gzip, deflate, br, zstd',
34 'Accept-Language': 'zh-CN,zh;q=0.9',
35 'Sec-WebSocket-Extensions': 'permessage-deflate; client_max_window_bits',
36 'Sec-WebSocket-Protocol': 'graphql-transport-ws'
37 }
38};
39
40// AI API 客户端类
41class AiApiClient {
42 constructor(modelId) {
43 this.modelId = CONFIG.MODELS[modelId];
44 if (!this.modelId) {
45 throw new Error(`不支持的模型: ${modelId}`);
46 }
47 }
48
49 // 处理消息内容
50 processMessageContent(content) {
51 if (typeof content === 'string') return content;
52 if (Array.isArray(content)) {
53 return content
54 .filter(item => item.type === 'text')
55 .map(item => item.text)
56 .join('\n');
57 }
58 return typeof content === 'object' ? content.text || null : null;
59 }
60
61 // 转换消息格式
62 async transformMessages(request) {
63 let systemMessageList = [];
64 let systemMergeMode = false;
65 let closedSystemMergeMode = false;
66
67 const contextMessages = await request.messages.reduce(async (accPromise, current) => {
68 const acc = await accPromise;
69 const currentContent = this.processMessageContent(current.content);
70
71 if (currentContent === null) return acc;
72
73 const currentMessageRole = current.role === "system" || current.role === "user" ? "HUMAN" : "ASSISTANT";
74
75 // 系统消息处理逻辑
76 if (current.role === "system") {
77 if (!closedSystemMergeMode) {
78 systemMergeMode = true;
79 const lastSystemMessage = systemMessageList[systemMessageList.length - 1];
80
81 if (!lastSystemMessage) {
82 systemMessageList.push(currentContent);
83 } else {
84 systemMessageList[systemMessageList.length - 1] = `${lastSystemMessage}\n${currentContent}`;
85 }
86 return acc;
87 }
88 }
89
90 // 关闭系统消息合并模式
91 if (current.role !== "system" && systemMergeMode) {
92 systemMergeMode = false;
93 closedSystemMergeMode = true;
94 }
95
96 // 消息合并逻辑
97 const previousMessage = acc[acc.length - 1];
98 const newMessage = `${currentMessageRole}: ${currentContent}`;
99
100 if (!previousMessage || previousMessage.startsWith(currentMessageRole)) {
101 return previousMessage
102 ? [...acc.slice(0, -1), `${previousMessage}\n${currentContent}`]
103 : [...acc, newMessage];
104 }
105
106 return [...acc, newMessage];
107 }, Promise.resolve([]));
108
109 return {
110 contextMessages: contextMessages.join('\n'),
111 systemMessage: systemMessageList.join('\n')
112 };
113 }
114}
115
116// 响应处理类
117class ResponseHandler {
118 // 流式响应处理
119 static async handleStreamResponse(responseContent, model, res) {
120 res.setHeader('Content-Type', 'text/event-stream');
121 res.setHeader('Cache-Control', 'no-cache');
122 res.setHeader('Connection', 'keep-alive');
123
124 let index = 0;
125 while (index < responseContent.length) {
126 const chunkSize = Math.floor(Math.random() * (30 - 16)) + 15;
127 const chunk = responseContent.slice(index, index + chunkSize);
128
129 res.write(`data: ${JSON.stringify({
130 id: uuidv4(),
131 object: 'chat.completion.chunk',
132 created: Math.floor(Date.now() / 1000),
133 model: model,
134 choices: [{
135 index: 0,
136 delta: { content: chunk },
137 finish_reason: null
138 }]
139 })}\n\n`);
140
141 index += chunkSize;
142 await new Promise(resolve => setTimeout(resolve, 50));
143 }
144
145 res.write('data: [DONE]\n\n');
146 res.end();
147 }
148
149 // 普通响应处理
150 static async handleNormalResponse(userMessage, responseContent, model, res) {
151 res.json({
152 id: uuidv4(),
153 object: "chat.completion",
154 created: Math.floor(Date.now() / 1000),
155 model: model,
156 choices: [{
157 index: 0,
158 message: {
159 role: "assistant",
160 content: responseContent
161 },
162 finish_reason: "stop"
163 }],
164 usage: {
165 prompt_tokens: userMessage.length,
166 completion_tokens: responseContent.length,
167 total_tokens: userMessage.length + responseContent.length
168 }
169 });
170 }
171}
172
173// WebSocket工具类
174class WebSocketUtils {
175 // 生成WebSocket密钥
176 static generateWebSocketKey() {
177 return randomBytes(16).toString('base64');
178 }
179
180 // 创建WebSocket客户端
181 static createWebSocketClient(requestPayload) {
182 return new Promise((resolve, reject) => {
183 const websocketKey = this.generateWebSocketKey();
184 const ws = new WebSocket(CONFIG.API.BASE_URL, 'graphql-transport-ws', {
185 headers: {
186 ...CONFIG.DEFAULT_HEADERS,
187 'Sec-WebSocket-Key': websocketKey,
188 }
189 });
190
191 let responseContent = '';
192 let isComplete = false;
193
194 ws.on('open', () => {
195 console.log('WebSocket连接已建立');
196 const connectionInitMessage = {
197 type: 'connection_init',
198 payload: {
199 headers: {
200 Authorization: 'Bearer ee5b7c15ed3553cd6abc407340aad09ac7cb3b9f76d8613a'
201 }
202 }
203 };
204 ws.send(JSON.stringify(connectionInitMessage));
205 });
206
207 ws.on('message', async (data) => {
208 const message = data.toString();
209 const parsedMessage = JSON.parse(message);
210
211 switch (parsedMessage.type) {
212 case 'connection_ack':
213 console.log('WebSocket连接请求中');
214 this.sendChatSubscription(ws, requestPayload);
215 break;
216 case 'next':
217 const chatResponse = await this.handleChatResponse(parsedMessage);
218 if (chatResponse) {
219 responseContent = chatResponse;
220 // 获取到响应后立即关闭连接
221 isComplete = true;
222 ws.close();
223 resolve(responseContent);
224 }
225 break;
226 case 'complete':
227 isComplete = true;
228 ws.close();
229 resolve(responseContent);
230 break;
231 }
232 });
233
234 ws.on('error', (err) => {
235 console.error('WebSocket错误:', err);
236 reject(err);
237 });
238
239 ws.on('close', (code, reason) => {
240 console.log('请求完毕,关闭连接');
241 if (!isComplete) {
242 reject(new Error('WebSocket closed unexpectedly'));
243 }
244 });
245 });
246 }
247
248 // 发送聊天订阅
249 static sendChatSubscription(ws, requestPayload) {
250 const subscribeMessage = {
251 id: uuidv4(),
252 type: 'subscribe',
253 payload: {
254 variables: {
255 messageInput: requestPayload.contextMessages,
256 messageContext: null,
257 organizationId: 'org_JfjtEvzbwOikUEUn',
258 integrationId: 'clwtqz9sq001izszu8ms5g4om',
259 chatMode: 'AUTO',
260 context: requestPayload.systemMessage,
261 messageAttributes: {},
262 includeAIAnnotations: false,
263 environment: 'production'
264 },
265 extensions: {},
266 operationName: 'OnNewSessionChatResult',
267 query: `subscription OnNewSessionChatResult($messageInput: String!, $messageContext: String, $organizationId: ID!, $integrationId: ID, $chatMode: ChatMode, $filters: ChatFiltersInput, $messageAttributes: JSON, $tags: [String!], $workflowId: String, $context: String, $guidance: String, $includeAIAnnotations: Boolean!, $environment: String) {
268 newSessionChatResult(
269 input: {messageInput: $messageInput, messageContext: $messageContext, organizationId: $organizationId, integrationId: $integrationId, chatMode: $chatMode, filters: $filters, messageAttributes: $messageAttributes, tags: $tags, workflowId: $workflowId, context: $context, guidance: $guidance, environment: $environment}
270 ) {
271 isEnd
272 sessionId
273 message {
274 id
275 content
276 }
277 __typename
278 }
279 }`
280 }
281 };
282
283 ws.send(JSON.stringify(subscribeMessage));
284 }
285
286 // 处理聊天响应
287 static async handleChatResponse(message) {
288 if (message.payload && message.payload.data) {
289 const chatResult = message.payload.data.newSessionChatResult;
290 if (chatResult && chatResult.isEnd == true && chatResult.message) {
291 return chatResult.message.content;
292 }
293 }
294 return null;
295 }
296}
297
298// 创建Express应用
299const app = express();
300
301// 中间件配置
302app.use(express.json({ limit: CONFIG.SERVER.BODY_LIMIT }));
303app.use(express.urlencoded({ extended: true, limit: CONFIG.SERVER.BODY_LIMIT }));
304app.use(cors({
305 origin: '*',
306 methods: ['GET', 'POST', 'OPTIONS'],
307 allowedHeaders: ['Content-Type', 'Authorization']
308}));
309
310// 获取模型列表路由
311app.get('/hf/v1/models', (req, res) => {
312 res.json({
313 object: "list",
314 data: [{
315 id: "claude-3-5-sonnet-20241022",
316 object: "model",
317 created: Math.floor(Date.now() / 1000),
318 owned_by: "claude",
319 }]
320 });
321});
322
323// 聊天完成路由
324app.post('/hf/v1/chat/completions', async (req, res) => {
325 try {
326 const { messages, model, stream } = req.body;
327 const authToken = req.headers.authorization?.replace('Bearer ', '');
328
329 if (authToken !== CONFIG.API.API_KEY) {
330 return res.status(401).json({ error: "Unauthorized" });
331 }
332
333 const apiClient = new AiApiClient(req.body.model);
334 const requestPayload = await apiClient.transformMessages(req.body);
335
336 const userMessage = messages.reverse().find(message => message.role === 'user')?.content;
337 if (!userMessage) {
338 return res.status(400).json({ error: "缺失用户消息" });
339 }
340
341 const responseContent = await WebSocketUtils.createWebSocketClient(requestPayload);
342
343 if (stream) {
344 await ResponseHandler.handleStreamResponse(responseContent, model, res);
345 } else {
346 await ResponseHandler.handleNormalResponse(userMessage, responseContent, model, res);
347 }
348 } catch (error) {
349 console.error('处理请求时发生错误:', error);
350 res.status(500).json({ error: "内部服务器错误", details: error.message });
351 }
352});
353
354// 404处理
355app.use((req, res) => {
356 res.status(404).json({ message: "服务创建成功运行中,请根据规则使用正确请求路径" });
357});
358
359// 启动服务器
360app.listen(CONFIG.SERVER.PORT, () => {
361 console.log(`服务器运行在 http://localhost:${CONFIG.SERVER.PORT}`);
362});
363 