Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save rodrigorodriguez/5a104410708ba20485ddf37f0dd5198e to your computer and use it in GitHub Desktop.

Select an option

Save rodrigorodriguez/5a104410708ba20485ddf37f0dd5198e to your computer and use it in GitHub Desktop.
Azure Claude
const http = require("http");
const https = require("https");
const endpoint = "https://xxxxxxxx-eastus2.openai.azure.com/anthropic";
const deploymentName = "claude-sonnet-4-5";
const apiKey = process.env.AZURE_API_KEY;
// Retry configuration
const RETRY_CONFIG = {
maxRetries: 5,
initialDelay: 1000, // 1 second
maxDelay: 30000, // 30 seconds
backoffMultiplier: 2,
timeoutMs: 60000, // 60 seconds per attempt
};
// HTTP agent with connection pooling and keep-alive
const httpsAgent = new https.Agent({
keepAlive: true,
keepAliveMsecs: 30000,
maxSockets: 50,
maxFreeSockets: 10,
timeout: RETRY_CONFIG.timeoutMs,
});
/**
* Sleep utility for retry delays
*/
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
/**
* Calculate exponential backoff delay with jitter
*/
function getRetryDelay(attemptNumber) {
const delay = Math.min(
RETRY_CONFIG.initialDelay *
Math.pow(RETRY_CONFIG.backoffMultiplier, attemptNumber),
RETRY_CONFIG.maxDelay,
);
// Add jitter (Β±25% randomness)
const jitter = delay * 0.25 * (Math.random() * 2 - 1);
return Math.floor(delay + jitter);
}
/**
* Check if error is retryable
*/
function isRetryableError(error, statusCode) {
// Network errors that should be retried
const retryableErrors = [
"ECONNRESET",
"ETIMEDOUT",
"ENOTFOUND",
"ECONNREFUSED",
"EPIPE",
"EHOSTUNREACH",
"EAI_AGAIN",
];
if (error.code && retryableErrors.includes(error.code)) {
return true;
}
// HTTP status codes that should be retried
const retryableStatuses = [408, 429, 500, 502, 503, 504];
if (statusCode && retryableStatuses.includes(statusCode)) {
return true;
}
// Timeout errors
if (error.message && error.message.toLowerCase().includes("timeout")) {
return true;
}
return false;
}
const server = http.createServer(async (req, res) => {
// Set CORS headers
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader(
"Access-Control-Allow-Methods",
"GET, POST, OPTIONS, PUT, DELETE",
);
res.setHeader(
"Access-Control-Allow-Headers",
"Content-Type, Authorization, x-requested-with",
);
// Handle preflight requests
if (req.method === "OPTIONS") {
res.writeHead(200);
res.end();
return;
}
console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
// Only handle POST requests to /v1/messages
if (req.method === "POST" && req.url === "/v1/messages") {
let body = "";
let requestHandled = false;
try {
// Collect the request body
req.on("data", (chunk) => {
body += chunk.toString();
});
req.on("end", async () => {
if (requestHandled) return;
requestHandled = true;
try {
const claudeRequest = JSON.parse(body);
console.log(
`${new Date().toISOString()} - Received request with ${claudeRequest.messages?.length || 0} messages`,
);
// Check if streaming is requested
const stream = claudeRequest.stream === true;
if (stream) {
// Handle streaming response
await handleStreamingResponse(claudeRequest, res);
} else {
// Handle non-streaming response
const claudeResponse =
await forwardToClaudeWithRetry(claudeRequest);
if (!res.headersSent) {
res.writeHead(200, {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
});
res.end(JSON.stringify(claudeResponse));
}
}
} catch (error) {
console.error(
`${new Date().toISOString()} - Error processing request:`,
error.message,
);
if (!res.headersSent) {
res.writeHead(error.statusCode || 400, {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
});
res.end(
JSON.stringify({
error: {
message: error.message,
type: error.type || "invalid_request_error",
},
}),
);
}
}
});
req.on("error", (error) => {
console.error(
`${new Date().toISOString()} - Request error:`,
error.message,
);
if (!requestHandled && !res.headersSent) {
requestHandled = true;
res.writeHead(400, {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
});
res.end(
JSON.stringify({
error: {
message: "Bad request",
type: "invalid_request_error",
},
}),
);
}
});
} catch (error) {
console.error(
`${new Date().toISOString()} - Server error:`,
error.message,
);
if (!res.headersSent) {
res.writeHead(500, {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
});
res.end(
JSON.stringify({
error: {
message: "Internal server error",
type: "server_error",
},
}),
);
}
}
} else {
res.writeHead(404, {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
});
res.end(
JSON.stringify({
error: {
message: "Endpoint not found. Use POST /v1/messages",
type: "not_found",
},
}),
);
}
});
/**
* Wrapper function that adds retry logic to forwardToClaude
*/
async function forwardToClaudeWithRetry(claudeRequest) {
let lastError;
for (let attempt = 0; attempt <= RETRY_CONFIG.maxRetries; attempt++) {
try {
console.log(
`${new Date().toISOString()} - Attempt ${attempt + 1}/${RETRY_CONFIG.maxRetries + 1}`,
);
const response = await forwardToClaude(claudeRequest);
if (attempt > 0) {
console.log(
`${new Date().toISOString()} - βœ… Success after ${attempt + 1} attempts`,
);
}
return response;
} catch (error) {
lastError = error;
const statusCode = error.statusCode;
console.error(
`${new Date().toISOString()} - Attempt ${attempt + 1} failed:`,
error.message,
statusCode ? `(Status: ${statusCode})` : "",
);
// Check if we should retry
if (
attempt < RETRY_CONFIG.maxRetries &&
isRetryableError(error, statusCode)
) {
const delay = getRetryDelay(attempt);
console.log(`${new Date().toISOString()} - Retrying in ${delay}ms...`);
await sleep(delay);
} else if (attempt >= RETRY_CONFIG.maxRetries) {
console.error(
`${new Date().toISOString()} - ❌ Max retries (${RETRY_CONFIG.maxRetries}) exceeded`,
);
break;
} else {
console.error(
`${new Date().toISOString()} - ❌ Non-retryable error, not retrying`,
);
break;
}
}
}
// If we get here, all retries failed
throw lastError;
}
/**
* Forward request to Claude API (single attempt)
*/
async function forwardToClaude(claudeRequest) {
return new Promise((resolve, reject) => {
let resolved = false;
let timeoutHandle;
const cleanup = () => {
if (timeoutHandle) {
clearTimeout(timeoutHandle);
timeoutHandle = null;
}
};
const safeResolve = (value) => {
if (!resolved) {
resolved = true;
cleanup();
resolve(value);
}
};
const safeReject = (error) => {
if (!resolved) {
resolved = true;
cleanup();
reject(error);
}
};
try {
// Prepare the request data
const requestData = {
...claudeRequest,
model: deploymentName,
};
// Remove stream flag for non-streaming requests
if (!requestData.stream) {
delete requestData.stream;
}
const postData = JSON.stringify(requestData);
// Parse the endpoint URL
const url = new URL(endpoint + "/v1/messages");
const options = {
hostname: url.hostname,
port: url.port || 443,
path: url.pathname,
method: "POST",
agent: httpsAgent,
headers: {
"Content-Type": "application/json",
"x-api-key": apiKey,
"anthropic-version": "2023-06-01",
"Content-Length": Buffer.byteLength(postData),
Connection: "keep-alive",
},
};
const req = https.request(options, (res) => {
let responseBody = "";
res.on("data", (chunk) => {
responseBody += chunk;
});
res.on("end", () => {
if (res.statusCode >= 200 && res.statusCode < 300) {
try {
const parsedResponse = JSON.parse(responseBody);
safeResolve(parsedResponse);
} catch (parseError) {
const error = new Error(
`Failed to parse Claude response: ${parseError.message}`,
);
error.statusCode = 502;
safeReject(error);
}
} else {
let errorMessage = `Claude API returned status ${res.statusCode}`;
try {
const errorResponse = JSON.parse(responseBody);
errorMessage = errorResponse.error?.message || errorMessage;
} catch (e) {
errorMessage = responseBody || errorMessage;
}
const error = new Error(errorMessage);
error.statusCode = res.statusCode;
safeReject(error);
}
});
res.on("error", (error) => {
safeReject(error);
});
});
req.on("error", (error) => {
safeReject(error);
});
req.on("timeout", () => {
req.destroy();
const error = new Error("Request timeout");
error.code = "ETIMEDOUT";
safeReject(error);
});
// Set request timeout
req.setTimeout(RETRY_CONFIG.timeoutMs);
// Additional safety timeout
timeoutHandle = setTimeout(() => {
if (!resolved) {
req.destroy();
const error = new Error("Request timeout (safety)");
error.code = "ETIMEDOUT";
safeReject(error);
}
}, RETRY_CONFIG.timeoutMs + 5000);
// Write the request body
req.write(postData);
req.end();
} catch (error) {
safeReject(error);
}
});
}
/**
* Handle streaming response with retry logic
*/
async function handleStreamingResponse(claudeRequest, clientRes) {
let lastError;
for (let attempt = 0; attempt <= RETRY_CONFIG.maxRetries; attempt++) {
try {
console.log(
`${new Date().toISOString()} - Streaming attempt ${attempt + 1}/${RETRY_CONFIG.maxRetries + 1}`,
);
await handleStreamingSingleAttempt(claudeRequest, clientRes);
if (attempt > 0) {
console.log(
`${new Date().toISOString()} - βœ… Streaming success after ${attempt + 1} attempts`,
);
}
return;
} catch (error) {
lastError = error;
const statusCode = error.statusCode;
console.error(
`${new Date().toISOString()} - Streaming attempt ${attempt + 1} failed:`,
error.message,
statusCode ? `(Status: ${statusCode})` : "",
);
// If headers were already sent, we can't retry
if (clientRes.headersSent) {
console.error(
`${new Date().toISOString()} - Cannot retry: headers already sent`,
);
throw error;
}
// Check if we should retry
if (
attempt < RETRY_CONFIG.maxRetries &&
isRetryableError(error, statusCode)
) {
const delay = getRetryDelay(attempt);
console.log(
`${new Date().toISOString()} - Retrying streaming in ${delay}ms...`,
);
await sleep(delay);
} else if (attempt >= RETRY_CONFIG.maxRetries) {
console.error(
`${new Date().toISOString()} - ❌ Max streaming retries (${RETRY_CONFIG.maxRetries}) exceeded`,
);
break;
} else {
console.error(
`${new Date().toISOString()} - ❌ Non-retryable streaming error`,
);
break;
}
}
}
// If we get here, all retries failed
if (!clientRes.headersSent) {
clientRes.writeHead(lastError.statusCode || 500, {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
});
clientRes.end(
JSON.stringify({
error: {
message: lastError.message,
type: "streaming_error",
},
}),
);
}
throw lastError;
}
/**
* Single attempt at streaming response
*/
async function handleStreamingSingleAttempt(claudeRequest, clientRes) {
return new Promise((resolve, reject) => {
let headersSent = false;
let resolved = false;
let timeoutHandle;
const cleanup = () => {
if (timeoutHandle) {
clearTimeout(timeoutHandle);
timeoutHandle = null;
}
};
const safeResolve = () => {
if (!resolved) {
resolved = true;
cleanup();
resolve();
}
};
const safeReject = (error) => {
if (!resolved) {
resolved = true;
cleanup();
reject(error);
}
};
try {
// Prepare the request data
const requestData = {
...claudeRequest,
model: deploymentName,
stream: true,
};
const postData = JSON.stringify(requestData);
const url = new URL(endpoint + "/v1/messages");
const options = {
hostname: url.hostname,
port: url.port || 443,
path: url.pathname,
method: "POST",
agent: httpsAgent,
headers: {
"Content-Type": "application/json",
"x-api-key": apiKey,
"anthropic-version": "2023-06-01",
"Content-Length": Buffer.byteLength(postData),
Connection: "keep-alive",
},
};
const req = https.request(options, (claudeRes) => {
if (claudeRes.statusCode >= 200 && claudeRes.statusCode < 300) {
// Set up streaming response headers
if (!headersSent && !clientRes.headersSent) {
headersSent = true;
clientRes.writeHead(200, {
"Content-Type": "text/event-stream",
"Transfer-Encoding": "chunked",
"Access-Control-Allow-Origin": "*",
"Cache-Control": "no-cache",
Connection: "keep-alive",
});
}
// Pipe the streaming response
claudeRes.pipe(clientRes, { end: true });
claudeRes.on("end", () => {
console.log(`${new Date().toISOString()} - Streaming completed`);
safeResolve();
});
claudeRes.on("error", (error) => {
console.error(
`${new Date().toISOString()} - Stream error:`,
error.message,
);
safeReject(error);
});
} else {
let errorBody = "";
claudeRes.on("data", (chunk) => {
errorBody += chunk;
});
claudeRes.on("end", () => {
let errorMessage = `Claude API returned status ${claudeRes.statusCode}`;
try {
const errorResponse = JSON.parse(errorBody);
errorMessage = errorResponse.error?.message || errorMessage;
} catch (e) {
errorMessage = errorBody || errorMessage;
}
const error = new Error(errorMessage);
error.statusCode = claudeRes.statusCode;
safeReject(error);
});
}
});
req.on("error", (error) => {
safeReject(error);
});
req.on("timeout", () => {
req.destroy();
const error = new Error("Streaming request timeout");
error.code = "ETIMEDOUT";
safeReject(error);
});
// Set a longer timeout for streaming
req.setTimeout(120000);
// Safety timeout
timeoutHandle = setTimeout(() => {
if (!resolved) {
req.destroy();
const error = new Error("Streaming timeout (safety)");
error.code = "ETIMEDOUT";
safeReject(error);
}
}, 125000);
// Write the request body
req.write(postData);
req.end();
} catch (error) {
safeReject(error);
}
});
}
/**
* Test Claude connection with retry
*/
async function testClaudeConnection() {
try {
console.log(`${new Date().toISOString()} - Testing Claude connection...`);
const testResponse = await forwardToClaudeWithRetry({
messages: [
{
role: "user",
content: "Hello, respond with just 'OK' if you can hear me.",
},
],
max_tokens: 10,
temperature: 0,
});
console.log(
`${new Date().toISOString()} - βœ… Claude connection test successful:`,
testResponse.content[0]?.text,
);
return true;
} catch (error) {
console.error(
`${new Date().toISOString()} - ❌ Claude connection test failed:`,
error.message,
);
return false;
}
}
const PORT = process.env.PORT || 3000;
server.listen(PORT, async () => {
console.log(`\n${"=".repeat(60)}`);
console.log(`πŸš€ Claude API Proxy Server`);
console.log(`${"=".repeat(60)}`);
console.log(`πŸ“ Server: http://localhost:${PORT}`);
console.log(`πŸ“‘ Upstream: ${endpoint}`);
console.log(`πŸ€– Model: ${deploymentName}`);
console.log(`πŸ”Œ Endpoint: POST http://localhost:${PORT}/v1/messages`);
console.log(`\nβš™οΈ Configuration:`);
console.log(` β€’ Max Retries: ${RETRY_CONFIG.maxRetries}`);
console.log(` β€’ Initial Delay: ${RETRY_CONFIG.initialDelay}ms`);
console.log(` β€’ Max Delay: ${RETRY_CONFIG.maxDelay}ms`);
console.log(` β€’ Timeout: ${RETRY_CONFIG.timeoutMs}ms`);
console.log(` β€’ Streaming: ENABLED`);
console.log(` β€’ Connection Pooling: ENABLED`);
console.log(`${"=".repeat(60)}\n`);
// Test the connection on startup
const testPassed = await testClaudeConnection();
console.log(`\n${testPassed ? "βœ…" : "⚠️"} Server ready to accept requests!`);
console.log(`${"=".repeat(60)}\n`);
});
// Graceful shutdown
process.on("SIGINT", () => {
console.log(`\n${new Date().toISOString()} - πŸ›‘ Shutting down server...`);
// Destroy the agent to close all connections
httpsAgent.destroy();
server.close(() => {
console.log(`${new Date().toISOString()} - βœ… Server closed`);
process.exit(0);
});
// Force exit after 10 seconds
setTimeout(() => {
console.log(`${new Date().toISOString()} - ⚠️ Forcing exit`);
process.exit(1);
}, 10000);
});
process.on("unhandledRejection", (err) => {
console.error(
`${new Date().toISOString()} - Unhandled promise rejection:`,
err.message,
);
});
process.on("uncaughtException", (err) => {
console.error(
`${new Date().toISOString()} - Uncaught exception:`,
err.message,
);
// Don't exit immediately, let other handlers run
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment