tasq/node_modules/agentic-flow/dist/proxy/quic-proxy.js

228 lines
7.8 KiB
JavaScript

// QUIC-enabled Proxy for Anthropic API
// Optional QUIC transport with automatic HTTP/2 fallback
import { QuicClient, QuicConnectionPool } from '../transport/quic.js';
import { logger } from '../utils/logger.js';
import { AnthropicToOpenRouterProxy } from './anthropic-to-openrouter.js';
export class QuicEnabledProxy extends AnthropicToOpenRouterProxy {
quicClient;
quicPool;
transport;
quicEnabled;
fallbackToHttp2;
constructor(config) {
super({
openrouterApiKey: config.openrouterApiKey,
openrouterBaseUrl: config.openrouterBaseUrl,
defaultModel: config.defaultModel
});
this.transport = config.transport || 'auto';
this.quicEnabled = config.enableQuic ?? this.checkQuicFeatureFlag();
this.fallbackToHttp2 = config.fallbackToHttp2 ?? true;
if (this.quicEnabled) {
this.initializeQuic(config.quic || {});
}
}
/**
* Check if QUIC is enabled via environment variable
*/
checkQuicFeatureFlag() {
const flag = process.env.AGENTIC_FLOW_ENABLE_QUIC;
return flag === 'true' || flag === '1';
}
/**
* Initialize QUIC client and connection pool
*/
async initializeQuic(quicConfig) {
try {
logger.info('Initializing QUIC transport...', { config: quicConfig });
this.quicClient = new QuicClient(quicConfig);
await this.quicClient.initialize();
this.quicPool = new QuicConnectionPool(this.quicClient, 20);
logger.info('QUIC transport initialized successfully');
}
catch (error) {
logger.error('Failed to initialize QUIC transport', { error });
if (this.fallbackToHttp2) {
logger.warn('Falling back to HTTP/2 transport');
this.quicEnabled = false;
}
else {
throw error;
}
}
}
/**
* Select transport protocol based on configuration and availability
*/
selectTransport() {
if (this.transport === 'quic' && this.quicEnabled) {
return 'quic';
}
if (this.transport === 'http2') {
return 'http2';
}
// Auto mode: prefer QUIC if available, fallback to HTTP/2
if (this.transport === 'auto') {
return this.quicEnabled ? 'quic' : 'http2';
}
return 'http2';
}
/**
* Send request using selected transport
*/
async sendRequest(url, options) {
const selectedTransport = this.selectTransport();
logger.debug('Sending request', {
transport: selectedTransport,
url,
method: options.method
});
if (selectedTransport === 'quic') {
return this.sendQuicRequest(url, options);
}
else {
return this.sendHttp2Request(url, options);
}
}
/**
* Send request over QUIC
*/
async sendQuicRequest(url, options) {
if (!this.quicClient || !this.quicPool) {
throw new Error('QUIC client not initialized');
}
try {
const urlObj = new URL(url);
const connection = await this.quicPool.getConnection(urlObj.hostname, parseInt(urlObj.port) || 443);
logger.debug('Using QUIC connection', {
connectionId: connection.id,
url
});
// Prepare headers
const headers = {};
if (options.headers) {
const headerEntries = options.headers instanceof Headers
? Array.from(options.headers.entries())
: Object.entries(options.headers);
for (const [key, value] of headerEntries) {
headers[key] = value;
}
}
// Convert body to Uint8Array
let body;
if (options.body) {
if (typeof options.body === 'string') {
body = new TextEncoder().encode(options.body);
}
else if (options.body instanceof Uint8Array) {
body = options.body;
}
else {
body = new TextEncoder().encode(JSON.stringify(options.body));
}
}
// Send HTTP/3 request over QUIC
const response = await this.quicClient.sendRequest(connection.id, options.method || 'GET', urlObj.pathname + urlObj.search, headers, body);
logger.info('QUIC request completed', {
status: response.status,
bytes: response.body.length
});
// Convert to fetch Response
const responseText = new TextDecoder().decode(response.body);
return new Response(responseText, {
status: response.status,
headers: new Headers(response.headers)
});
}
catch (error) {
logger.error('QUIC request failed', { error, url });
if (this.fallbackToHttp2) {
logger.warn('Falling back to HTTP/2 for this request');
return this.sendHttp2Request(url, options);
}
throw error;
}
}
/**
* Send request over HTTP/2 (standard fetch)
*/
async sendHttp2Request(url, options) {
logger.debug('Using HTTP/2 transport', { url });
return fetch(url, options);
}
/**
* Get transport statistics
*/
getTransportStats() {
if (this.quicClient) {
return {
transport: this.selectTransport(),
quicEnabled: this.quicEnabled,
quicStats: this.quicClient.getStats()
};
}
return {
transport: 'http2',
quicEnabled: false
};
}
/**
* Shutdown and cleanup
*/
async shutdown() {
if (this.quicPool) {
await this.quicPool.clear();
}
if (this.quicClient) {
await this.quicClient.shutdown();
}
logger.info('QUIC proxy shutdown complete');
}
}
/**
* Create QUIC-enabled proxy with configuration
*/
export function createQuicProxy(config) {
const proxy = new QuicEnabledProxy(config);
logger.info('QUIC proxy created', {
transport: config.transport || 'auto',
quicEnabled: config.enableQuic ?? (process.env.AGENTIC_FLOW_ENABLE_QUIC === 'true'),
fallbackEnabled: config.fallbackToHttp2 ?? true
});
return proxy;
}
// CLI entry point for QUIC proxy
if (import.meta.url === `file://${process.argv[1]}`) {
const port = parseInt(process.env.PORT || '3000');
const openrouterApiKey = process.env.OPENROUTER_API_KEY;
if (!openrouterApiKey) {
console.error('❌ Error: OPENROUTER_API_KEY environment variable required');
process.exit(1);
}
const proxy = createQuicProxy({
openrouterApiKey,
openrouterBaseUrl: process.env.ANTHROPIC_PROXY_BASE_URL,
defaultModel: process.env.COMPLETION_MODEL || process.env.REASONING_MODEL,
transport: process.env.TRANSPORT || 'auto',
enableQuic: process.env.AGENTIC_FLOW_ENABLE_QUIC === 'true',
quic: {
port: parseInt(process.env.QUIC_PORT || '4433'),
serverHost: process.env.QUIC_HOST || 'localhost',
certPath: process.env.QUIC_CERT_PATH,
keyPath: process.env.QUIC_KEY_PATH
}
});
proxy.start(port);
// Graceful shutdown
process.on('SIGTERM', async () => {
logger.info('Received SIGTERM, shutting down gracefully...');
await proxy.shutdown();
process.exit(0);
});
process.on('SIGINT', async () => {
logger.info('Received SIGINT, shutting down gracefully...');
await proxy.shutdown();
process.exit(0);
});
}
//# sourceMappingURL=quic-proxy.js.map