songdaooi commited on
Commit
44b730a
·
1 Parent(s): 2139b46
Files changed (5) hide show
  1. Dockerfile +13 -0
  2. docker-compose.yml +9 -0
  3. index.js +396 -0
  4. package.json +18 -0
  5. worker.js +171 -0
Dockerfile ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM node:lts-alpine
2
+
3
+ WORKDIR /app
4
+
5
+ COPY package*.json ./
6
+
7
+ RUN npm install
8
+
9
+ COPY . .
10
+ ENV PORT 7860
11
+ EXPOSE 7860
12
+
13
+ CMD ["npm", "start"]
docker-compose.yml ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ services:
2
+ app:
3
+ image: my-chat-api
4
+ build: .
5
+ ports:
6
+ - "7860:7860"
7
+ environment:
8
+ NODE_ENV: production
9
+ restart: unless-stopped # 自动重启
index.js ADDED
@@ -0,0 +1,396 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import express from 'express';
2
+ import WebSocket from 'ws';
3
+ import { v4 as uuidv4 } from 'uuid';
4
+ import { randomBytes } from 'crypto';
5
+ import cors from 'cors';
6
+ import dotenv from 'dotenv';
7
+
8
+ // 配置加载
9
+ dotenv.config();
10
+
11
+ // 配置常量
12
+ const 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 客户端类
41
+ class 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" ? "USER" : current.role.toUpperCase();
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
+ // 响应处理类
117
+ class 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工具类
174
+ class WebSocketUtils {
175
+ // 生成WebSocket密钥
176
+ static generateWebSocketKey() {
177
+ return randomBytes(16).toString('base64');
178
+ }
179
+
180
+ // 创建WebSocket客户端
181
+ static createWebSocketClient(requestPayload, onChunk) {
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 lastContent = '';
192
+ let responseContent = '';
193
+ let isComplete = false;
194
+
195
+ ws.on('open', () => {
196
+ console.time('query');
197
+ console.log("WebSocket连接已打开");
198
+ const connectionInitMessage = {
199
+ type: 'connection_init',
200
+ payload: {
201
+ headers: {
202
+ Authorization: 'Bearer ee5b7c15ed3553cd6abc407340aad09ac7cb3b9f76d8613a'
203
+ }
204
+ }
205
+ };
206
+ ws.send(JSON.stringify(connectionInitMessage));
207
+ });
208
+
209
+ ws.on('message', async (data) => {
210
+ const message = data.toString();
211
+ const parsedMessage = JSON.parse(message);
212
+ console.log("收到消息:", message);
213
+
214
+ switch (parsedMessage.type) {
215
+ case 'connection_ack':
216
+ this.sendChatSubscription(ws, requestPayload);
217
+ break;
218
+ case 'next':
219
+ try {
220
+ const chatResult = parsedMessage.payload.data.newSessionChatResult;
221
+ if (chatResult && chatResult.message) {
222
+ const currentContent = chatResult.message.content || '';
223
+
224
+ // 计算增量内容
225
+ const delta = currentContent.slice(lastContent.length);
226
+ lastContent = currentContent;
227
+ responseContent = currentContent;
228
+
229
+ if (delta && onChunk) {
230
+ const payload = {
231
+ id: uuidv4(),
232
+ object: 'chat.completion.chunk',
233
+ created: Math.floor(Date.now() / 1000),
234
+ model: requestPayload.modelId,
235
+ choices: [{
236
+ index: 0,
237
+ delta: { content: delta },
238
+ finish_reason: null
239
+ }]
240
+ };
241
+ onChunk(payload);
242
+ }
243
+ }
244
+ } catch (e) {
245
+ console.error('处理消息时出错:', e);
246
+ }
247
+ break;
248
+ case 'complete':
249
+ isComplete = true;
250
+ ws.close();
251
+ resolve(responseContent);
252
+ break;
253
+ }
254
+ });
255
+ ws.on('error', (err) => {
256
+ console.error('WebSocket错误:', err);
257
+ reject(err);
258
+ });
259
+
260
+ ws.on('close', (code, reason) => {
261
+ console.log('请求完毕,关闭连接');
262
+ console.timeEnd('query');
263
+ if (!isComplete) {
264
+ reject(new Error('WebSocket closed unexpectedly'));
265
+ }
266
+ });
267
+ });
268
+ }
269
+
270
+ // 发送聊天订阅
271
+ static sendChatSubscription(ws, requestPayload) {
272
+ const subscribeMessage = {
273
+ id: uuidv4(),
274
+ type: 'subscribe',
275
+ payload: {
276
+ variables: {
277
+ messageInput: requestPayload.contextMessages,
278
+ messageContext: null,
279
+ organizationId: 'org_JfjtEvzbwOikUEUn',
280
+ integrationId: 'clwtqz9sq001izszu8ms5g4om',
281
+ chatMode: 'AUTO',
282
+ context: requestPayload.systemMessage,
283
+ messageAttributes: {},
284
+ includeAIAnnotations: false,
285
+ environment: 'production'
286
+ },
287
+ extensions: {},
288
+ operationName: 'OnNewSessionChatResult',
289
+ 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) {
290
+ newSessionChatResult(
291
+ 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}
292
+ ) {
293
+ isEnd
294
+ sessionId
295
+ message {
296
+ id
297
+ content
298
+ }
299
+ __typename
300
+ }
301
+ }`
302
+ }
303
+ };
304
+ console.log(JSON.stringify(subscribeMessage))
305
+
306
+ ws.send(JSON.stringify(subscribeMessage));
307
+ }
308
+
309
+ // 处理聊天响应
310
+ static async handleChatResponse(message) {
311
+ if (message.payload && message.payload.data) {
312
+ const chatResult = message.payload.data.newSessionChatResult;
313
+ if (chatResult && chatResult.isEnd == true && chatResult.message) {
314
+ return chatResult.message.content;
315
+ }
316
+ }
317
+ return null;
318
+ }
319
+ }
320
+
321
+ // 创建Express应用
322
+ const app = express();
323
+
324
+ // 中间件配置
325
+ app.use(express.json({ limit: CONFIG.SERVER.BODY_LIMIT }));
326
+ app.use(express.urlencoded({ extended: true, limit: CONFIG.SERVER.BODY_LIMIT }));
327
+ app.use(cors({
328
+ origin: '*',
329
+ methods: ['GET', 'POST', 'OPTIONS'],
330
+ allowedHeaders: ['Content-Type', 'Authorization']
331
+ }));
332
+
333
+ // 获取模型列表路由
334
+ app.get('/v1/models', (req, res) => {
335
+ res.json({
336
+ object: "list",
337
+ data: [{
338
+ id: "claude-3-5-sonnet-20241022",
339
+ object: "model",
340
+ created: Math.floor(Date.now() / 1000),
341
+ owned_by: "claude",
342
+ }]
343
+ });
344
+ });
345
+
346
+ // 聊天完成路由
347
+ app.post('/v1/chat/completions', async (req, res) => {
348
+ try {
349
+ const { messages, model, stream } = req.body;
350
+ const authToken = req.headers.authorization?.replace('Bearer ', '');
351
+
352
+ if (authToken !== CONFIG.API.API_KEY) {
353
+ return res.status(401).json({ error: "Unauthorized" });
354
+ }
355
+
356
+ const apiClient = new AiApiClient(req.body.model);
357
+ const requestPayload = await apiClient.transformMessages(req.body);
358
+
359
+ const userMessage = messages.reverse().find(message => message.role === 'user')?.content;
360
+ if (!userMessage) {
361
+ return res.status(400).json({ error: "缺失用户消息" });
362
+ }
363
+
364
+ if (stream) {
365
+ res.setHeader('Content-Type', 'text/event-stream');
366
+ res.setHeader('Cache-Control', 'no-cache');
367
+ res.setHeader('Connection', 'keep-alive');
368
+
369
+ await WebSocketUtils.createWebSocketClient(
370
+ { ...requestPayload, modelId: model },
371
+ (chunk) => {
372
+ res.write(`data: ${JSON.stringify(chunk)}\n\n`);
373
+ }
374
+ );
375
+
376
+ res.write('data: [DONE]\n\n');
377
+ res.end();
378
+ } else {
379
+ const responseContent = await WebSocketUtils.createWebSocketClient(requestPayload);
380
+ await ResponseHandler.handleNormalResponse(userMessage, responseContent, model, res);
381
+ }
382
+ } catch (error) {
383
+ console.error('处理请求时发生错误:', error);
384
+ res.status(500).json({ error: "内部服务器错误", details: error.message });
385
+ }
386
+ });
387
+
388
+ // 404处理
389
+ app.use((req, res) => {
390
+ res.status(404).json({ message: "请使用正确请求路径" });
391
+ });
392
+
393
+ // 启动服务器
394
+ app.listen(CONFIG.SERVER.PORT, () => {
395
+ console.log(`服务器运行在 http://localhost:${CONFIG.SERVER.PORT}`);
396
+ });
package.json ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "claudeServicee",
3
+ "version": "1.0.0",
4
+ "main": "index.js",
5
+ "type": "module",
6
+ "scripts": {
7
+ "start": "node index.js"
8
+ },
9
+ "author": "yxmiler",
10
+ "dependencies": {
11
+ "cors": "^2.8.5",
12
+ "crypto": "^1.0.1",
13
+ "dotenv": "^16.3.1",
14
+ "express": "^4.18.2",
15
+ "uuid": "^9.0.0",
16
+ "ws": "^8.18.0"
17
+ }
18
+ }
worker.js ADDED
@@ -0,0 +1,171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Rate Limiting Proxy Worker with Sliding Window
3
+ */
4
+ const LIMIT = 2; // 120秒内允许2个请求
5
+ const WINDOW = 120; // 滑动窗口大小(秒)
6
+ const TARGET_HOST = "https://example.com"; // 目标服务器
7
+
8
+ async function updateRequestStats(ip, path, isSuccess, env) {
9
+ // 获取统计数据
10
+ const statsKey = `${ip}`;
11
+ let stats = await env.ad2api_ip_list.get(statsKey);
12
+ stats = stats
13
+ ? JSON.parse(stats)
14
+ : {
15
+ modelsCount: 0, // /v1/models 的请求次数
16
+ successCount: 0, // 成功请求的次数
17
+ hitMaxLimitCount: 0, // 到达最大限制的次数
18
+ };
19
+
20
+ // 更新统计
21
+ if (path === "/v1/models") {
22
+ stats.modelsCount++;
23
+ } else if (!isSuccess) {
24
+ stats.hitMaxLimitCount++;
25
+ } else {
26
+ stats.successCount++;
27
+ }
28
+
29
+ // 保存统计数据到 ad2api_ip_list
30
+ await env.ad2api_ip_list.put(statsKey, JSON.stringify(stats));
31
+
32
+ return stats;
33
+ }
34
+
35
+ async function checkRateLimit(ip, path, env) {
36
+ // 如果是 /v1/models 路径,不进行限流
37
+ if (path === "/v1/models") {
38
+ await updateRequestStats(ip, path, true, env);
39
+ return { allowed: true };
40
+ }
41
+
42
+ const now = Math.floor(Date.now() / 1000);
43
+ const key = `${ip}`;
44
+
45
+ // 获取请求历史记录
46
+ let history = await env.ad2api_rate_limits.get(key);
47
+ let timestamps = [];
48
+
49
+ if (history) {
50
+ timestamps = JSON.parse(history);
51
+ // 过滤掉超过窗口期的时间戳
52
+ timestamps = timestamps.filter((ts) => now - ts < WINDOW);
53
+ }
54
+
55
+ // 检查是否超过限制
56
+ if (timestamps.length >= LIMIT) {
57
+ // 计算最早的请求何时可以过期
58
+ const oldestTimestamp = timestamps[0];
59
+ const waitTime = WINDOW - (now - oldestTimestamp);
60
+ if (waitTime > 0) {
61
+ return { allowed: false, waitTime };
62
+ }
63
+ // 移除最早的请求
64
+ timestamps.shift();
65
+ }
66
+
67
+ // 添加新的请求时间戳
68
+ timestamps.push(now);
69
+
70
+ // 更新存储
71
+ await env.ad2api_rate_limits.put(key, JSON.stringify(timestamps), {
72
+ expirationTtl: WINDOW,
73
+ });
74
+
75
+ return { allowed: true };
76
+ }
77
+
78
+ async function handleRequest(request, env) {
79
+ const ip = request.headers.get("cf-connecting-ip");
80
+ const url = new URL(request.url);
81
+
82
+ // 检查速率限制
83
+ const result = await checkRateLimit(ip, url.pathname, env);
84
+ if (!result.allowed) {
85
+ return new Response("Too Many Requests", {
86
+ status: 429,
87
+ headers: {
88
+ "Content-Type": "text/plain",
89
+ "Retry-After": String(result.waitTime),
90
+ "Access-Control-Allow-Origin": "*",
91
+ },
92
+ });
93
+ }
94
+
95
+ // 构建目标 URL
96
+ const targetUrl = new URL(TARGET_HOST + url.pathname + url.search);
97
+
98
+ // 复制并修改请求头
99
+ const headers = new Headers(request.headers);
100
+ headers.set("Host", new URL(TARGET_HOST).host);
101
+
102
+ // 创建新的请求
103
+ const newRequest = new Request(targetUrl.toString(), {
104
+ method: request.method,
105
+ headers: headers,
106
+ body: request.body,
107
+ redirect: "follow",
108
+ });
109
+
110
+ try {
111
+ // 发送请求到目标服务器
112
+ const response = await fetch(newRequest);
113
+ const isSuccess = response.status !== 500;
114
+
115
+ // 更新统计
116
+ await updateRequestStats(ip, url.pathname, isSuccess, env);
117
+
118
+ // 如果是500错误,从速率限制历史中移除此次请求
119
+ if (!isSuccess) {
120
+ const key = `${ip}`;
121
+ let history = await env.ad2api_rate_limits.get(key);
122
+ if (history) {
123
+ let timestamps = JSON.parse(history);
124
+ timestamps.pop(); // 移除最后添加的时间戳
125
+ await env.ad2api_rate_limits.put(
126
+ key,
127
+ JSON.stringify(timestamps),
128
+ {
129
+ expirationTtl: WINDOW,
130
+ }
131
+ );
132
+ }
133
+ }
134
+
135
+ // 创建响应头
136
+ const responseHeaders = new Headers(response.headers);
137
+ responseHeaders.set("Access-Control-Allow-Origin", "*");
138
+
139
+ // 返回流式响应
140
+ return new Response(response.body, {
141
+ status: response.status,
142
+ headers: responseHeaders,
143
+ });
144
+ } catch (error) {
145
+ // 处理异常为500错误
146
+ await updateRequestStats(ip, url.pathname, false, env);
147
+ // 从速率限制历史中移除此次请求
148
+ const key = `${ip}`;
149
+ let history = await env.ad2api_rate_limits.get(key);
150
+ if (history) {
151
+ let timestamps = JSON.parse(history);
152
+ timestamps.pop(); // 移除最后添加的时间戳
153
+ await env.ad2api_rate_limits.put(key, JSON.stringify(timestamps), {
154
+ expirationTtl: WINDOW,
155
+ });
156
+ }
157
+
158
+ return new Response("Internal Server Error", {
159
+ status: 500,
160
+ headers: {
161
+ "Access-Control-Allow-Origin": "*",
162
+ },
163
+ });
164
+ }
165
+ }
166
+
167
+ export default {
168
+ async fetch(request, env, ctx) {
169
+ return handleRequest(request, env);
170
+ },
171
+ };