Spaces:
Running
Running
const http = require('http'); | |
const url = require('url'); | |
const fetch = require('node-fetch'); | |
const MODEL = 'claude-3-5-sonnet@20240620'; | |
const PROJECT_ID = process.env.PROJECT_ID; | |
const CLIENT_ID = process.env.CLIENT_ID; | |
const CLIENT_SECRET = process.env.CLIENT_SECRET; | |
const REFRESH_TOKEN = process.env.REFRESH_TOKEN; | |
const API_KEY = process.env.API_KEY; | |
const TOKEN_URL = 'https://www.googleapis.com/oauth2/v4/token'; | |
let tokenCache = { | |
accessToken: '', | |
expiry: 0, | |
refreshPromise: null | |
}; | |
async function getAccessToken() { | |
const now = Date.now() / 1000; | |
if (tokenCache.accessToken && now < tokenCache.expiry - 120) { | |
return tokenCache.accessToken; | |
} | |
if (tokenCache.refreshPromise) { | |
await tokenCache.refreshPromise; | |
return tokenCache.accessToken; | |
} | |
tokenCache.refreshPromise = (async () => { | |
try { | |
const response = await fetch(TOKEN_URL, { | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/json' | |
}, | |
body: JSON.stringify({ | |
client_id: CLIENT_ID, | |
client_secret: CLIENT_SECRET, | |
refresh_token: REFRESH_TOKEN, | |
grant_type: 'refresh_token' | |
}) | |
}); | |
const data = await response.json(); | |
tokenCache.accessToken = data.access_token; | |
tokenCache.expiry = now + data.expires_in; | |
} finally { | |
tokenCache.refreshPromise = null; | |
} | |
})(); | |
await tokenCache.refreshPromise; | |
return tokenCache.accessToken; | |
} | |
function getLocation() { | |
const currentSeconds = new Date().getSeconds(); | |
return currentSeconds < 30 ? 'europe-west1' : 'us-east5'; | |
} | |
function constructApiUrl(location) { | |
return `https://${location}-aiplatform.googleapis.com/v1/projects/${PROJECT_ID}/locations/${location}/publishers/anthropic/models/${MODEL}:streamRawPredict`; | |
} | |
async function handleRequest(request) { | |
if (request.method === 'OPTIONS') { | |
return handleOptions(); | |
} | |
const apiKey = request.headers['x-api-key']; | |
if (apiKey !== API_KEY) { | |
const errorResponse = { | |
status: 403, | |
headers: { | |
'Content-Type': 'application/json', | |
'Access-Control-Allow-Origin': '*', | |
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS, DELETE, HEAD', | |
'Access-Control-Allow-Headers': 'Content-Type, Authorization, x-api-key, anthropic-version, model' | |
}, | |
body: JSON.stringify({ | |
type: "error", | |
error: { | |
type: "permission_error", | |
message: "Your API key does not have permission to use the specified resource." | |
} | |
}) | |
}; | |
return errorResponse; | |
} | |
const accessToken = await getAccessToken(); | |
const location = getLocation(); | |
const apiUrl = constructApiUrl(location); | |
let requestBody = JSON.parse(request.body); | |
if (requestBody.anthropic_version) { | |
delete requestBody.anthropic_version; | |
} | |
if (requestBody.model) { | |
delete requestBody.model; | |
} | |
requestBody.anthropic_version = "vertex-2023-10-16"; | |
const modifiedHeaders = { | |
...request.headers, | |
'Authorization': `Bearer ${accessToken}`, | |
'Content-Type': 'application/json; charset=utf-8' | |
}; | |
delete modifiedHeaders['anthropic-version']; | |
const modifiedRequest = { | |
headers: modifiedHeaders, | |
method: request.method, | |
body: JSON.stringify(requestBody), | |
redirect: 'follow' | |
}; | |
const response = await fetch(apiUrl, modifiedRequest); | |
const responseBody = await response.text(); | |
const modifiedResponse = { | |
status: response.status, | |
statusText: response.statusText, | |
headers: { | |
...response.headers, | |
'Access-Control-Allow-Origin': '*', | |
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS', | |
'Access-Control-Allow-Headers': 'Content-Type, Authorization, x-api-key, anthropic-version, model' | |
}, | |
body: responseBody | |
}; | |
return modifiedResponse; | |
} | |
function handleOptions() { | |
const headers = { | |
'Access-Control-Allow-Origin': '*', | |
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS', | |
'Access-Control-Allow-Headers': 'Content-Type, Authorization, x-api-key, anthropic-version, model' | |
}; | |
return { | |
status: 204, | |
headers: headers | |
}; | |
} | |
const server = http.createServer(async (req, res) => { | |
const parsedUrl = url.parse(req.url, true); | |
if (parsedUrl.pathname === '/ai/v1/messages') { | |
const request = { | |
method: req.method, | |
headers: req.headers, | |
body: await getRequestBody(req) | |
}; | |
const response = await handleRequest(request); | |
res.writeHead(response.status, response.statusText, response.headers); | |
res.end(response.body); | |
} else { | |
res.writeHead(404, { 'Content-Type': 'application/json' }); | |
res.end(JSON.stringify({ error: 'Not Found' })); | |
} | |
}); | |
function getRequestBody(req) { | |
return new Promise((resolve, reject) => { | |
let body = ''; | |
req.on('data', chunk => { | |
body += chunk.toString(); | |
}); | |
req.on('end', () => { | |
resolve(body); | |
}); | |
req.on('error', err => { | |
reject(err); | |
}); | |
}); | |
} | |
server.listen(8080, () => { | |
console.log('Server is running on port 8080'); | |
}); | |