Spaces:
Running
Running
Rename app.py to app.js
Browse files
app.js
ADDED
@@ -0,0 +1,191 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
const http = require('http');
|
2 |
+
const url = require('url');
|
3 |
+
const fetch = require('node-fetch');
|
4 |
+
|
5 |
+
const MODEL = 'claude-3-5-sonnet@20240620';
|
6 |
+
|
7 |
+
const PROJECT_ID = process.env.PROJECT_ID;
|
8 |
+
const CLIENT_ID = process.env.CLIENT_ID;
|
9 |
+
const CLIENT_SECRET = process.env.CLIENT_SECRET;
|
10 |
+
const REFRESH_TOKEN = process.env.REFRESH_TOKEN;
|
11 |
+
const API_KEY = process.env.API_KEY;
|
12 |
+
|
13 |
+
const TOKEN_URL = 'https://www.googleapis.com/oauth2/v4/token';
|
14 |
+
|
15 |
+
let tokenCache = {
|
16 |
+
accessToken: '',
|
17 |
+
expiry: 0,
|
18 |
+
refreshPromise: null
|
19 |
+
};
|
20 |
+
|
21 |
+
async function getAccessToken() {
|
22 |
+
const now = Date.now() / 1000;
|
23 |
+
|
24 |
+
if (tokenCache.accessToken && now < tokenCache.expiry - 120) {
|
25 |
+
return tokenCache.accessToken;
|
26 |
+
}
|
27 |
+
|
28 |
+
if (tokenCache.refreshPromise) {
|
29 |
+
await tokenCache.refreshPromise;
|
30 |
+
return tokenCache.accessToken;
|
31 |
+
}
|
32 |
+
|
33 |
+
tokenCache.refreshPromise = (async () => {
|
34 |
+
try {
|
35 |
+
const response = await fetch(TOKEN_URL, {
|
36 |
+
method: 'POST',
|
37 |
+
headers: {
|
38 |
+
'Content-Type': 'application/json'
|
39 |
+
},
|
40 |
+
body: JSON.stringify({
|
41 |
+
client_id: CLIENT_ID,
|
42 |
+
client_secret: CLIENT_SECRET,
|
43 |
+
refresh_token: REFRESH_TOKEN,
|
44 |
+
grant_type: 'refresh_token'
|
45 |
+
})
|
46 |
+
});
|
47 |
+
|
48 |
+
const data = await response.json();
|
49 |
+
tokenCache.accessToken = data.access_token;
|
50 |
+
tokenCache.expiry = now + data.expires_in;
|
51 |
+
} finally {
|
52 |
+
tokenCache.refreshPromise = null;
|
53 |
+
}
|
54 |
+
})();
|
55 |
+
|
56 |
+
await tokenCache.refreshPromise;
|
57 |
+
return tokenCache.accessToken;
|
58 |
+
}
|
59 |
+
|
60 |
+
function getLocation() {
|
61 |
+
const currentSeconds = new Date().getSeconds();
|
62 |
+
return currentSeconds < 30 ? 'europe-west1' : 'us-east5';
|
63 |
+
}
|
64 |
+
|
65 |
+
function constructApiUrl(location) {
|
66 |
+
return `https://${location}-aiplatform.googleapis.com/v1/projects/${PROJECT_ID}/locations/${location}/publishers/anthropic/models/${MODEL}:streamRawPredict`;
|
67 |
+
}
|
68 |
+
|
69 |
+
async function handleRequest(request) {
|
70 |
+
if (request.method === 'OPTIONS') {
|
71 |
+
return handleOptions();
|
72 |
+
}
|
73 |
+
|
74 |
+
const apiKey = request.headers['x-api-key'];
|
75 |
+
if (apiKey !== API_KEY) {
|
76 |
+
const errorResponse = {
|
77 |
+
status: 403,
|
78 |
+
headers: {
|
79 |
+
'Content-Type': 'application/json',
|
80 |
+
'Access-Control-Allow-Origin': '*',
|
81 |
+
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS, DELETE, HEAD',
|
82 |
+
'Access-Control-Allow-Headers': 'Content-Type, Authorization, x-api-key, anthropic-version, model'
|
83 |
+
},
|
84 |
+
body: JSON.stringify({
|
85 |
+
type: "error",
|
86 |
+
error: {
|
87 |
+
type: "permission_error",
|
88 |
+
message: "Your API key does not have permission to use the specified resource."
|
89 |
+
}
|
90 |
+
})
|
91 |
+
};
|
92 |
+
|
93 |
+
return errorResponse;
|
94 |
+
}
|
95 |
+
|
96 |
+
const accessToken = await getAccessToken();
|
97 |
+
const location = getLocation();
|
98 |
+
const apiUrl = constructApiUrl(location);
|
99 |
+
|
100 |
+
let requestBody = JSON.parse(request.body);
|
101 |
+
|
102 |
+
if (requestBody.anthropic_version) {
|
103 |
+
delete requestBody.anthropic_version;
|
104 |
+
}
|
105 |
+
|
106 |
+
if (requestBody.model) {
|
107 |
+
delete requestBody.model;
|
108 |
+
}
|
109 |
+
|
110 |
+
requestBody.anthropic_version = "vertex-2023-10-16";
|
111 |
+
|
112 |
+
const modifiedHeaders = {
|
113 |
+
...request.headers,
|
114 |
+
'Authorization': `Bearer ${accessToken}`,
|
115 |
+
'Content-Type': 'application/json; charset=utf-8'
|
116 |
+
};
|
117 |
+
delete modifiedHeaders['anthropic-version'];
|
118 |
+
|
119 |
+
const modifiedRequest = {
|
120 |
+
headers: modifiedHeaders,
|
121 |
+
method: request.method,
|
122 |
+
body: JSON.stringify(requestBody),
|
123 |
+
redirect: 'follow'
|
124 |
+
};
|
125 |
+
|
126 |
+
const response = await fetch(apiUrl, modifiedRequest);
|
127 |
+
const responseBody = await response.text();
|
128 |
+
const modifiedResponse = {
|
129 |
+
status: response.status,
|
130 |
+
statusText: response.statusText,
|
131 |
+
headers: {
|
132 |
+
...response.headers,
|
133 |
+
'Access-Control-Allow-Origin': '*',
|
134 |
+
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS',
|
135 |
+
'Access-Control-Allow-Headers': 'Content-Type, Authorization, x-api-key, anthropic-version, model'
|
136 |
+
},
|
137 |
+
body: responseBody
|
138 |
+
};
|
139 |
+
|
140 |
+
return modifiedResponse;
|
141 |
+
}
|
142 |
+
|
143 |
+
function handleOptions() {
|
144 |
+
const headers = {
|
145 |
+
'Access-Control-Allow-Origin': '*',
|
146 |
+
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS',
|
147 |
+
'Access-Control-Allow-Headers': 'Content-Type, Authorization, x-api-key, anthropic-version, model'
|
148 |
+
};
|
149 |
+
|
150 |
+
return {
|
151 |
+
status: 204,
|
152 |
+
headers: headers
|
153 |
+
};
|
154 |
+
}
|
155 |
+
|
156 |
+
const server = http.createServer(async (req, res) => {
|
157 |
+
const parsedUrl = url.parse(req.url, true);
|
158 |
+
if (parsedUrl.pathname === '/ai/v1/messages') {
|
159 |
+
const request = {
|
160 |
+
method: req.method,
|
161 |
+
headers: req.headers,
|
162 |
+
body: await getRequestBody(req)
|
163 |
+
};
|
164 |
+
|
165 |
+
const response = await handleRequest(request);
|
166 |
+
res.writeHead(response.status, response.statusText, response.headers);
|
167 |
+
res.end(response.body);
|
168 |
+
} else {
|
169 |
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
170 |
+
res.end(JSON.stringify({ error: 'Not Found' }));
|
171 |
+
}
|
172 |
+
});
|
173 |
+
|
174 |
+
function getRequestBody(req) {
|
175 |
+
return new Promise((resolve, reject) => {
|
176 |
+
let body = '';
|
177 |
+
req.on('data', chunk => {
|
178 |
+
body += chunk.toString();
|
179 |
+
});
|
180 |
+
req.on('end', () => {
|
181 |
+
resolve(body);
|
182 |
+
});
|
183 |
+
req.on('error', err => {
|
184 |
+
reject(err);
|
185 |
+
});
|
186 |
+
});
|
187 |
+
}
|
188 |
+
|
189 |
+
server.listen(8080, () => {
|
190 |
+
console.log('Server is running on port 8080');
|
191 |
+
});
|
app.py
DELETED
@@ -1,113 +0,0 @@
|
|
1 |
-
from flask import Flask, request, jsonify, Response, make_response
|
2 |
-
import requests
|
3 |
-
import threading
|
4 |
-
import time
|
5 |
-
import os
|
6 |
-
|
7 |
-
app = Flask(__name__)
|
8 |
-
|
9 |
-
MODEL = 'claude-3-5-sonnet@20240620'
|
10 |
-
PROJECT_ID = os.getenv('PROJECT_ID')
|
11 |
-
CLIENT_ID = os.getenv('CLIENT_ID')
|
12 |
-
CLIENT_SECRET = os.getenv('CLIENT_SECRET')
|
13 |
-
REFRESH_TOKEN = os.getenv('REFRESH_TOKEN')
|
14 |
-
API_KEY = os.getenv('API_KEY')
|
15 |
-
TOKEN_URL = 'https://www.googleapis.com/oauth2/v4/token'
|
16 |
-
|
17 |
-
token_cache = {
|
18 |
-
'access_token': '',
|
19 |
-
'expiry': 0,
|
20 |
-
'refresh_promise': None
|
21 |
-
}
|
22 |
-
|
23 |
-
def get_access_token():
|
24 |
-
now = time.time()
|
25 |
-
|
26 |
-
if token_cache['access_token'] and now < token_cache['expiry'] - 120:
|
27 |
-
return token_cache['access_token']
|
28 |
-
|
29 |
-
if token_cache['refresh_promise']:
|
30 |
-
token_cache['refresh_promise'].join()
|
31 |
-
return token_cache['access_token']
|
32 |
-
|
33 |
-
def refresh_token():
|
34 |
-
try:
|
35 |
-
response = requests.post(TOKEN_URL, json={
|
36 |
-
'client_id': CLIENT_ID,
|
37 |
-
'client_secret': CLIENT_SECRET,
|
38 |
-
'refresh_token': REFRESH_TOKEN,
|
39 |
-
'grant_type': 'refresh_token'
|
40 |
-
})
|
41 |
-
data = response.json()
|
42 |
-
token_cache['access_token'] = data['access_token']
|
43 |
-
token_cache['expiry'] = now + data['expires_in']
|
44 |
-
finally:
|
45 |
-
token_cache['refresh_promise'] = None
|
46 |
-
|
47 |
-
token_cache['refresh_promise'] = threading.Thread(target=refresh_token)
|
48 |
-
token_cache['refresh_promise'].start()
|
49 |
-
token_cache['refresh_promise'].join()
|
50 |
-
return token_cache['access_token']
|
51 |
-
|
52 |
-
def get_location():
|
53 |
-
current_seconds = time.localtime().tm_sec
|
54 |
-
return 'europe-west1' if current_seconds < 30 else 'us-east5'
|
55 |
-
|
56 |
-
def construct_api_url(location):
|
57 |
-
return f'https://{location}-aiplatform.googleapis.com/v1/projects/{PROJECT_ID}/locations/{location}/publishers/anthropic/models/{MODEL}:streamRawPredict'
|
58 |
-
|
59 |
-
@app.route('/ai/v1/messages', methods=['POST', 'OPTIONS'])
|
60 |
-
def handle_request():
|
61 |
-
if request.method == 'OPTIONS':
|
62 |
-
return handle_options()
|
63 |
-
|
64 |
-
api_key = request.headers.get('x-api-key')
|
65 |
-
if api_key != API_KEY:
|
66 |
-
error_response = make_response(jsonify({
|
67 |
-
'type': 'error',
|
68 |
-
'error': {
|
69 |
-
'type': 'permission_error',
|
70 |
-
'message': 'Your API key does not have permission to use the specified resource.'
|
71 |
-
}
|
72 |
-
}), 403)
|
73 |
-
error_response.headers['Access-Control-Allow-Origin'] = '*'
|
74 |
-
error_response.headers['Access-Control-Allow-Methods'] = 'POST, GET, OPTIONS, DELETE, HEAD'
|
75 |
-
error_response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization, x-api-key, anthropic-version, model'
|
76 |
-
return error_response
|
77 |
-
|
78 |
-
access_token = get_access_token()
|
79 |
-
location = get_location()
|
80 |
-
api_url = construct_api_url(location)
|
81 |
-
|
82 |
-
request_body = request.json
|
83 |
-
|
84 |
-
if 'anthropic_version' in request_body:
|
85 |
-
del request_body['anthropic_version']
|
86 |
-
if 'model' in request_body:
|
87 |
-
del request_body['model']
|
88 |
-
|
89 |
-
request_body['anthropic_version'] = 'vertex-2023-10-16'
|
90 |
-
|
91 |
-
headers = {
|
92 |
-
'Authorization': f'Bearer {access_token}',
|
93 |
-
'Content-Type': 'application/json; charset=utf-8'
|
94 |
-
}
|
95 |
-
|
96 |
-
response = requests.post(api_url, headers=headers, json=request_body)
|
97 |
-
modified_response = make_response(response.content, response.status_code)
|
98 |
-
modified_response.headers['Access-Control-Allow-Origin'] = '*'
|
99 |
-
modified_response.headers['Access-Control-Allow-Methods'] = 'POST, GET, OPTIONS'
|
100 |
-
modified_response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization, x-api-key, anthropic-version, model'
|
101 |
-
|
102 |
-
return modified_response
|
103 |
-
|
104 |
-
def handle_options():
|
105 |
-
headers = {
|
106 |
-
'Access-Control-Allow-Origin': '*',
|
107 |
-
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS',
|
108 |
-
'Access-Control-Allow-Headers': 'Content-Type, Authorization, x-api-key, anthropic-version, model'
|
109 |
-
}
|
110 |
-
return '', 204, headers
|
111 |
-
|
112 |
-
if __name__ == '__main__':
|
113 |
-
app.run(port=8080)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|