smgc commited on
Commit
b46aa5b
1 Parent(s): 6431757

Update app.js

Browse files
Files changed (1) hide show
  1. app.js +218 -275
app.js CHANGED
@@ -1,293 +1,236 @@
1
- const express = require("express");
2
- const { io } = require("socket.io-client");
3
- const { v4: uuidv4 } = require("uuid");
4
- const { ProxyAgent } = require("proxy-agent");
5
- const agent = new ProxyAgent();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
 
7
- const app = express();
8
- const port = process.env.PORT || 8081;
 
 
 
 
 
9
 
10
- // 从环境变量中获取 accessToken
11
- const accessToken = process.env.PPLX_KEY;
12
 
13
- console.log(`Server starting. Access token: ${accessToken ? "Set" : "Not set"}`);
 
 
14
 
15
- // 添加一个中间件来解析 JSON 请求体
16
- app.use(express.json());
 
 
17
 
18
- // 添加一个中间件来记录所有请求
19
- app.use((req, res, next) => {
20
- console.log(`[${new Date().toISOString()}] ${req.method} request received for ${req.path}`);
21
- console.log(`Headers: ${JSON.stringify(req.headers)}`);
22
- next();
23
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
 
25
- var opts = {
26
- agent: agent,
27
- auth: {
28
- jwt: "anonymous-ask-user",
29
- },
30
- reconnection: false,
31
- transports: ["websocket"],
32
- path: "/socket.io",
33
- hostname: "www.perplexity.ai",
34
- secure: true,
35
- port: "443",
36
- extraHeaders: {
37
- Cookie: process.env.PPLX_COOKIE,
38
- "User-Agent": process.env.USER_AGENT,
39
- Accept: "*/*",
40
- priority: "u=1, i",
41
- Referer: "https://www.perplexity.ai/",
42
- },
43
- };
44
 
45
- app.post("/v1/messages", (req, res) => {
46
- console.log("[POST /v1/messages] Processing request");
47
-
48
- // 严格验证客户端的 Authorization 头
49
- const clientAuth = req.headers['authorization'];
50
- console.log(`[POST /v1/messages] Received Authorization header: ${clientAuth}`);
51
- console.log(`[POST /v1/messages] Expected Authorization: Bearer ${accessToken}`);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
 
53
- if (!clientAuth || clientAuth !== `Bearer ${accessToken}`) {
54
- console.log("[POST /v1/messages] Authorization failed. Sending 401 Unauthorized response.");
55
- res.status(401).json({ error: "Unauthorized" });
56
- return;
57
  }
58
-
59
- console.log("[POST /v1/messages] Authorization successful. Proceeding with request processing.");
60
 
61
- // 使用解析后的 JSON 体
62
- const jsonBody = req.body;
63
- console.log(`[POST /v1/messages] Received request body: ${JSON.stringify(jsonBody)}`);
64
 
65
- res.setHeader("Content-Type", "text/event-stream;charset=utf-8");
66
- try {
67
- if (jsonBody.stream == false) {
68
- console.log("[POST /v1/messages] Stream is false. Sending non-streaming response.");
69
- res.send(
70
- JSON.stringify({
71
- id: uuidv4(),
72
- content: [
73
- {
74
- text: "Please turn on streaming.",
75
- },
76
- {
77
- id: "string",
78
- name: "string",
79
- input: {},
80
- },
81
- ],
82
- model: "string",
83
- stop_reason: "end_turn",
84
- stop_sequence: "string",
85
- usage: {
86
- input_tokens: 0,
87
- output_tokens: 0,
88
- },
89
- })
90
- );
91
- } else if (jsonBody.stream == true) {
92
- console.log("[POST /v1/messages] Stream is true. Processing streaming request.");
93
- // 计算用户消息长度
94
- let userMessage = [{ question: "", answer: "" }];
95
- let lastUpdate = true;
96
- if (jsonBody.system) {
97
- // 把系统消息加入messages的首条
98
- jsonBody.messages.unshift({ role: "system", content: jsonBody.system });
99
- }
100
- console.log(`[POST /v1/messages] Processed messages: ${JSON.stringify(jsonBody.messages)}`);
101
- jsonBody.messages.forEach((msg) => {
102
- if (msg.role == "system" || msg.role == "user") {
103
- if (lastUpdate) {
104
- userMessage[userMessage.length - 1].question += msg.content + "\n";
105
- } else if (userMessage[userMessage.length - 1].question == "") {
106
- userMessage[userMessage.length - 1].question += msg.content + "\n";
107
- } else {
108
- userMessage.push({ question: msg.content + "\n", answer: "" });
109
- }
110
- lastUpdate = true;
111
- } else if (msg.role == "assistant") {
112
- if (!lastUpdate) {
113
- userMessage[userMessage.length - 1].answer += msg.content + "\n";
114
- } else if (userMessage[userMessage.length - 1].answer == "") {
115
- userMessage[userMessage.length - 1].answer += msg.content + "\n";
116
- } else {
117
- userMessage.push({ question: "", answer: msg.content + "\n" });
118
- }
119
- lastUpdate = false;
120
- }
121
- });
122
- // user message to plaintext
123
- let previousMessages = jsonBody.messages
124
- .map((msg) => {
125
- return msg.content
126
- })
127
- .join("\n\n");
128
-
129
- console.log(`[POST /v1/messages] Previous messages: ${previousMessages}`);
130
-
131
- let msgid = uuidv4();
132
- // send message start
133
- res.write(
134
- createEvent("message_start", {
135
- type: "message_start",
136
- message: {
137
- id: msgid,
138
- type: "message",
139
- role: "assistant",
140
- content: [],
141
- model: "claude-3-opus-20240229",
142
- stop_reason: null,
143
- stop_sequence: null,
144
- usage: { input_tokens: 8, output_tokens: 1 },
145
- },
146
- })
147
- );
148
- res.write(createEvent("content_block_start", { type: "content_block_start", index: 0, content_block: { type: "text", text: "" } }));
149
- res.write(createEvent("ping", { type: "ping" }));
150
-
151
- console.log("[POST /v1/messages] Initiating WebSocket connection to Perplexity.ai");
152
- // proxy response
153
- var socket = io("wss://www.perplexity.ai/", opts);
154
-
155
- socket.on("connect", function () {
156
- console.log("[POST /v1/messages] WebSocket connected to Perplexity.ai");
157
- socket
158
- .emitWithAck("perplexity_ask", previousMessages, {
159
- "version": "2.9",
160
- "source": "default",
161
- "attachments": [],
162
- "language": "en-GB",
163
- "timezone": "Europe/London",
164
- "search_focus": "writing",
165
- "frontend_uuid": uuidv4(),
166
- "mode": "concise",
167
- "is_related_query": false,
168
- "is_default_related_query": false,
169
- "visitor_id": uuidv4(),
170
- "frontend_context_uuid": uuidv4(),
171
- "prompt_source": "user",
172
- "query_source": "home"
173
- })
174
- .then((response) => {
175
- console.log(`[POST /v1/messages] Received response from Perplexity.ai: ${JSON.stringify(response)}`);
176
- res.write(createEvent("content_block_stop", { type: "content_block_stop", index: 0 }));
177
- res.write(
178
- createEvent("message_delta", {
179
- type: "message_delta",
180
- delta: { stop_reason: "end_turn", stop_sequence: null },
181
- usage: { output_tokens: 12 },
182
- })
183
- );
184
- res.write(createEvent("message_stop", { type: "message_stop" }));
185
-
186
- res.end();
187
- }).catch((error) => {
188
- if(error.message != "socket has been disconnected"){
189
- console.log(`[POST /v1/messages] Error in WebSocket communication: ${error}`);
190
- }
191
- });
192
- });
193
- socket.onAny((event, ...args) => {
194
- console.log(`[POST /v1/messages] Received WebSocket event: ${event}`);
195
- });
196
- socket.on("query_progress", (data) => {
197
- if(data.text){
198
- var text = JSON.parse(data.text)
199
- var chunk = text.chunks[text.chunks.length - 1];
200
- if(chunk){
201
- console.log(`[POST /v1/messages] Received chunk: ${chunk}`);
202
- chunkJSON = JSON.stringify({
203
- type: "content_block_delta",
204
- index: 0,
205
- delta: { type: "text_delta", text: chunk },
206
- });
207
- res.write(createEvent("content_block_delta", chunkJSON));
208
- }
209
- }
210
- });
211
- socket.on("disconnect", function () {
212
- console.log("[POST /v1/messages] WebSocket disconnected from Perplexity.ai");
213
- });
214
- socket.on("error", (error) => {
215
- console.log(`[POST /v1/messages] WebSocket error: ${error}`);
216
- chunkJSON = JSON.stringify({
217
- type: "content_block_delta",
218
- index: 0,
219
- delta: { type: "text_delta", text: "Error occured while fetching output 输出时出现错误\nPlease refer to the log for more information 请查看日志以获取更多信息" },
220
- });
221
- res.write(createEvent("content_block_delta", chunkJSON));
222
- res.write(createEvent("content_block_stop", { type: "content_block_stop", index: 0 }));
223
- res.write(
224
- createEvent("message_delta", {
225
- type: "message_delta",
226
- delta: { stop_reason: "end_turn", stop_sequence: null },
227
- usage: { output_tokens: 12 },
228
- })
229
- );
230
- res.write(createEvent("message_stop", { type: "message_stop" }));
231
-
232
- res.end();
233
- });
234
- socket.on("connect_error", function (error) {
235
- console.log(`[POST /v1/messages] WebSocket connection error: ${error}`);
236
- chunkJSON = JSON.stringify({
237
- type: "content_block_delta",
238
- index: 0,
239
- delta: { type: "text_delta", text: "Failed to connect to the Perplexity.ai 连接到Perplexity失败\nPlease refer to the log for more information 请查看日志以获取更多信息" },
240
- });
241
- res.write(createEvent("content_block_delta", chunkJSON));
242
- res.write(createEvent("content_block_stop", { type: "content_block_stop", index: 0 }));
243
- res.write(
244
- createEvent("message_delta", {
245
- type: "message_delta",
246
- delta: { stop_reason: "end_turn", stop_sequence: null },
247
- usage: { output_tokens: 12 },
248
- })
249
- );
250
- res.write(createEvent("message_stop", { type: "message_stop" }));
251
-
252
- res.end();
253
- });
254
- res.on("close", function () {
255
- console.log("[POST /v1/messages] Client closed connection");
256
- socket.disconnect();
257
- });
258
- } else {
259
- console.log("[POST /v1/messages] Invalid request: stream is neither true nor false");
260
- throw new Error("Invalid request");
261
  }
262
- } catch (e) {
263
- console.log(`[POST /v1/messages] Error in request processing: ${e}`);
264
- res.write(JSON.stringify({ error: e.message }));
265
- res.end();
266
- return;
267
  }
268
- });
269
 
270
- // 处理 /ai/v1/messages 路由
271
- app.post("/ai/v1/messages", (req, res) => {
272
- console.log("[POST /ai/v1/messages] Received request, forwarding to /v1/messages");
273
- app.handle(req, res, req.next);
274
- });
 
 
 
 
 
 
 
 
275
 
276
- // handle other
277
- app.use((req, res, next) => {
278
- console.log(`[${new Date().toISOString()}] Received request for ${req.path} - returning 404`);
279
- res.status(404).send("Not Found");
280
- });
 
 
 
 
281
 
282
- app.listen(port, () => {
283
- console.log(`[${new Date().toISOString()}] Perplexity proxy listening on port ${port}`);
 
 
 
 
 
284
  });
285
 
286
- // eventStream util
287
- function createEvent(event, data) {
288
- // if data is object, stringify it
289
- if (typeof data === "object") {
290
- data = JSON.stringify(data);
291
- }
292
- return `event: ${event}\ndata: ${data}\n\n`;
293
- }
 
1
+ const express = require('express');
2
+ const fetch = require('node-fetch');
3
+ const zlib = require('zlib');
4
+ const { promisify } = require('util');
5
+ const stream = require('stream');
6
+ const bodyParser = require('body-parser');
7
+
8
+ const gunzip = promisify(zlib.gunzip);
9
+ const pipeline = promisify(stream.pipeline);
10
+
11
+ const PROJECT_ID = process.env.PROJECT_ID;
12
+ const CLIENT_ID = process.env.CLIENT_ID;
13
+ const CLIENT_SECRET = process.env.CLIENT_SECRET;
14
+ const REFRESH_TOKEN = process.env.REFRESH_TOKEN;
15
+ const API_KEY = process.env.API_KEY;
16
+
17
+ const TOKEN_URL = 'https://www.googleapis.com/oauth2/v4/token';
18
+
19
+ let tokenCache = {
20
+ accessToken: '',
21
+ expiry: 0,
22
+ refreshPromise: null
23
+ };
24
 
25
+ function logRequest(req, status, message) {
26
+ const timestamp = new Date().toISOString();
27
+ const method = req.method;
28
+ const url = req.originalUrl;
29
+ const ip = req.ip;
30
+ console.log(`[${timestamp}] ${method} ${url} - Status: ${status}, IP: ${ip}, Message: ${message}`);
31
+ }
32
 
33
+ async function getAccessToken() {
34
+ const now = Date.now() / 1000;
35
 
36
+ if (tokenCache.accessToken && now < tokenCache.expiry - 120) {
37
+ return tokenCache.accessToken;
38
+ }
39
 
40
+ if (tokenCache.refreshPromise) {
41
+ await tokenCache.refreshPromise;
42
+ return tokenCache.accessToken;
43
+ }
44
 
45
+ tokenCache.refreshPromise = (async () => {
46
+ try {
47
+ const response = await fetch(TOKEN_URL, {
48
+ method: 'POST',
49
+ headers: {
50
+ 'Content-Type': 'application/json'
51
+ },
52
+ body: JSON.stringify({
53
+ client_id: CLIENT_ID,
54
+ client_secret: CLIENT_SECRET,
55
+ refresh_token: REFRESH_TOKEN,
56
+ grant_type: 'refresh_token'
57
+ })
58
+ });
59
+
60
+ const data = await response.json();
61
+
62
+ tokenCache.accessToken = data.access_token;
63
+ tokenCache.expiry = now + data.expires_in;
64
+ } finally {
65
+ tokenCache.refreshPromise = null;
66
+ }
67
+ })();
68
 
69
+ await tokenCache.refreshPromise;
70
+ return tokenCache.accessToken;
71
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
 
73
+ function getLocation() {
74
+ const currentSeconds = new Date().getSeconds();
75
+ return currentSeconds < 30 ? 'europe-west1' : 'us-east5';
76
+ }
77
+
78
+ function constructApiUrl(location, model) {
79
+ return `https://${location}-aiplatform.googleapis.com/v1/projects/${PROJECT_ID}/locations/${location}/publishers/anthropic/models/${model}:streamRawPredict`;
80
+ }
81
+
82
+ function formatModelName(model) {
83
+ if (model === 'claude-3-5-sonnet-20240620') {
84
+ return 'claude-3-5-sonnet@20240620';
85
+ }
86
+ return model;
87
+ }
88
+
89
+ async function handleRequest(req, res) {
90
+ if (req.method === 'OPTIONS') {
91
+ handleOptions(res);
92
+ logRequest(req, 204, 'CORS preflight request');
93
+ return;
94
+ }
95
+
96
+ const apiKey = req.headers['x-api-key'];
97
+ if (apiKey !== API_KEY) {
98
+ res.status(403).json({
99
+ type: "error",
100
+ error: {
101
+ type: "permission_error",
102
+ message: "Your API key does not have permission to use the specified resource."
103
+ }
104
+ });
105
+ logRequest(req, 403, 'Invalid API key');
106
+ return;
107
+ }
108
+
109
+ const accessToken = await getAccessToken();
110
+ const location = getLocation();
111
+
112
+ let requestBody = req.body;
113
+
114
+ let model = requestBody.model || 'claude-3-5-sonnet@20240620';
115
+ model = formatModelName(model);
116
+
117
+ const apiUrl = constructApiUrl(location, model);
118
+
119
+ if (requestBody.anthropic_version) {
120
+ delete requestBody.anthropic_version;
121
+ }
122
+
123
+ if (requestBody.model) {
124
+ delete requestBody.model;
125
+ }
126
+
127
+ requestBody.anthropic_version = "vertex-2023-10-16";
128
+
129
+ try {
130
+ const response = await fetch(apiUrl, {
131
+ method: 'POST',
132
+ headers: {
133
+ 'Authorization': `Bearer ${accessToken}`,
134
+ 'Content-Type': 'application/json; charset=utf-8',
135
+ 'Accept-Encoding': 'gzip, deflate'
136
+ },
137
+ body: JSON.stringify(requestBody),
138
+ compress: false
139
+ });
140
+
141
+ res.status(response.status);
142
 
143
+ for (const [key, value] of response.headers.entries()) {
144
+ if (key.toLowerCase() !== 'content-encoding' && key.toLowerCase() !== 'content-length') {
145
+ res.setHeader(key, value);
146
+ }
147
  }
 
 
148
 
149
+ res.setHeader('Access-Control-Allow-Origin', '*');
150
+ res.setHeader('Access-Control-Allow-Methods', 'POST, GET, OPTIONS');
151
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, x-api-key, anthropic-version, model');
152
 
153
+ const contentType = response.headers.get('content-type');
154
+ const contentEncoding = response.headers.get('content-encoding');
155
+
156
+ if (contentType && contentType.includes('text/event-stream')) {
157
+ // 处理 SSE
158
+ res.setHeader('Content-Type', 'text/event-stream');
159
+ res.setHeader('Cache-Control', 'no-cache');
160
+ res.setHeader('Connection', 'keep-alive');
161
+
162
+ const gunzipStream = zlib.createGunzip();
163
+ const transformStream = new stream.Transform({
164
+ transform(chunk, encoding, callback) {
165
+ this.push(chunk);
166
+ callback();
167
+ }
168
+ });
169
+
170
+ pipeline(response.body, gunzipStream, transformStream)
171
+ .then(() => {
172
+ console.log('Stream processing completed');
173
+ })
174
+ .catch((err) => {
175
+ console.error('Stream processing error:', err);
176
+ });
177
+
178
+ transformStream.pipe(res);
179
+ } else {
180
+ // 非流式响应的处理
181
+ const buffer = await response.buffer();
182
+
183
+ let data;
184
+ if (contentEncoding === 'gzip') {
185
+ try {
186
+ data = await gunzip(buffer);
187
+ } catch (error) {
188
+ console.error('Gunzip error:', error);
189
+ throw new Error('Failed to decompress the response');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190
  }
191
+ } else {
192
+ data = buffer;
193
+ }
194
+
195
+ res.send(data);
196
  }
 
197
 
198
+ logRequest(req, response.status, `Request forwarded successfully for model: ${model}`);
199
+ } catch (error) {
200
+ console.error('Request error:', error);
201
+ res.status(500).json({
202
+ type: "error",
203
+ error: {
204
+ type: "internal_server_error",
205
+ message: "An unexpected error occurred while processing your request."
206
+ }
207
+ });
208
+ logRequest(req, 500, `Error: ${error.message}`);
209
+ }
210
+ }
211
 
212
+ function handleOptions(res) {
213
+ res.status(204);
214
+ res.setHeader('Access-Control-Allow-Origin', '*');
215
+ res.setHeader('Access-Control-Allow-Methods', 'POST, GET, OPTIONS');
216
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, x-api-key, anthropic-version, model');
217
+ res.end();
218
+ }
219
+
220
+ const app = express();
221
 
222
+ // 增加 body-parser 的限制
223
+ app.use(bodyParser.json({ limit: '50mb' }));
224
+ app.use(bodyParser.urlencoded({ limit: '50mb', extended: true }));
225
+
226
+ // 根路由处理
227
+ app.get('/', (req, res) => {
228
+ res.status(200).send('GCP VertexAI For Claude Proxy');
229
  });
230
 
231
+ app.all('/ai/v1/messages', handleRequest);
232
+
233
+ const PORT = 8080;
234
+ app.listen(PORT, () => {
235
+ console.log(`Server is running on port ${PORT}`);
236
+ });