smgc commited on
Commit
6431757
1 Parent(s): 39f54d9

Update app.js

Browse files
Files changed (1) hide show
  1. app.js +275 -218
app.js CHANGED
@@ -1,236 +1,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
  });
 
 
 
 
 
 
 
 
 
 
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
+ }