import { Route, Router, RouterType, error, json, cors, text } from 'itty-router'; // import { libx } from "libx.js/build/bundles/essentials"; import { RouterWrapper } from 'edge.libx.js/build/main.js'; import _cors from "edge.libx.js/src/modules/cors.js"; import { AskModule } from '../ask.js'; import { helpers } from "../../src/helpers"; import { nodeHelpers } from "../../src/helpers/node"; import { libx } from "libx.js/build/bundles/essentials"; import { FeedoxAIModule } from '../providers/FeedoxAI.js'; import { WorkerClient } from '../providers/WorkerClient.js'; import { LlmManager } from '../worker/modules/LlmManager.js'; import { supportedModels } from 'ai.libx.js'; // Load .env file BEFORE reading any environment variables nodeHelpers.loadEnv(); export const prefix = '/v1'; const apiRouter = RouterWrapper.getNew(prefix); // Choose between Worker and FeedoxAI based on environment const useWorker = process.env.USE_WORKER_DIRECT === 'true'; if (useWorker) { // Use WorkerClient for remote worker helpers.di.register('ai', new WorkerClient({ baseUrl: process.env.WORKER_API_URL, defaultProvider: process.env.DEFAULT_PROVIDER, defaultModel: process.env.DEFAULT_MODEL, // Pass all API keys from environment openaiApiKey: process.env.OPENAI_API_KEY, claudeApiKey: process.env.CLAUDE_API_KEY, groqApiKey: process.env.GROQ_API_KEY, googleApiKey: process.env.GOOGLE_AI_API_KEY, mistralApiKey: process.env.MISTRAL_API_KEY, openrouterApiKey: process.env.OPENROUTER_API_KEY, cohereApiKey: process.env.COHERE_API_KEY, xaiApiKey: process.env.XAI_API_KEY, deepseekApiKey: process.env.DEEPSEEK_API_KEY, ai21ApiKey: process.env.AI21_API_KEY, cloudflareApiKey: process.env.CLOUDFLARE_API_KEY, cloudflareAccountId: process.env.CLOUDFLARE_ACCOUNT_ID, })); } else { // Use FeedoxAI (legacy) helpers.di.register('ai', new FeedoxAIModule({ baseUrl: process.env.FEEDOX_AI_URL, })); } // baseRouterWrapper.registerRoute('/v1', v1Routes); // Health check endpoint apiRouter.router.get('/health', async (req) => await _cors( req, new Response(JSON.stringify({ status: 'ok', version: '1.0.0', timestamp: new Date().toISOString(), providers: ['openai', 'anthropic', 'groq'], mode: useWorker ? 'direct' : 'feedox' }), { headers: { 'Content-Type': 'application/json' } }) ) ); // Models list endpoint apiRouter.router.get('/models', async (req) => await _cors( req, json({ models: supportedModels, count: Object.keys(supportedModels || {}).length }) ) ); // Completion endpoint (direct LLM access) apiRouter.router.post('/completion', async (req, env, ctx) => { try { const body = await req.json(); const { messages, systemPrompt, config } = body; if (!config || !config.provider || !config.model) { return json({ error: 'Missing required fields: config.provider and config.model are required' }, { status: 400 }); } // Get API keys from request headers with env fallback const header = (name: string) => req.headers.get(name) || req.headers.get(name.toLowerCase()); const apiKeys = { openai: header('x-openai-api-key') || process.env.OPENAI_API_KEY, anthropic: header('x-claude-api-key') || process.env.CLAUDE_API_KEY, google: header('x-google-ai-api-key') || process.env.GOOGLE_AI_API_KEY, groq: header('x-groq-api-key') || process.env.GROQ_API_KEY, mistral: header('x-mistral-api-key') || process.env.MISTRAL_API_KEY, openrouter: header('x-openrouter-api-key') || process.env.OPENROUTER_API_KEY, cohere: header('x-cohere-api-key') || process.env.COHERE_API_KEY, ai21: header('x-ai21-api-key') || process.env.AI21_API_KEY, xai: header('x-xai-api-key') || process.env.XAI_API_KEY, deepseek: header('x-deepseek-api-key') || process.env.DEEPSEEK_API_KEY, cloudflare: header('x-cloudflare-api-key') || process.env.CLOUDFLARE_API_KEY, stability: header('x-stability-ai-api-key') || process.env.STABILITY_AI_API_KEY, falai: header('x-falai-api-key') || process.env.FALAI_API_KEY, }; // Get Cloudflare Account ID const cloudflareAccountId = header('x-cloudflare-account-id') || process.env.CLOUDFLARE_ACCOUNT_ID; // Initialize LlmManager with direct LLM access const llmManager = new LlmManager({ apiKeys, cloudflareAccountId }); // Execute prompt const readable = await llmManager.executePrompt({ messages, systemPrompt, config, }); // Return stream return await _cors(req, new Response(readable, { headers: { 'Content-Type': config?.stream ? 'text/event-stream' : 'application/json', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive', } })); } catch (error: any) { libx.log.e('Completion error:', error); return json({ error: error.message || 'Internal server error', timestamp: new Date().toISOString() }, { status: 500 }); } }); apiRouter.router.get('/ping', async (req) => await _cors( req, new Response(JSON.stringify({ message: 'pong' }), { headers: { 'Content-Type': 'application/json' } }) ) ); apiRouter.router.all('/ask', async (req, env, ctx) => { const askModule = new AskModule(helpers.di.modules.ai, { }); const answerStream = askModule.ask( 'long poem about the earth', 'you always write in capital letters', { stream: true, maxTokens: 10, model: 'grok-2-1212' }, ); let { readable, writable } = new TransformStream(); let writer = writable.getWriter(); const textEncoder = new TextEncoder(); (async () => { for await (const chunk of answerStream) { await writer.write(textEncoder.encode(chunk)); // await libx.delay(50); process.stdout.write(chunk ?? ''); // Print to console without new line }; writer.close() })(); libx.log.v('returning readable'); return new Response(readable, { headers: { 'Content-Type': 'text/plain; charset=utf-8', 'Transfer-Encoding': 'chunked' } }); }); apiRouter.router.post('/ask2', async (req, env, ctx) => { const OPENROUTER_API_KEY = process.env.OPENROUTER_API_KEY || 'sk-...'; // TODO: set your OpenRouter API key const prompt = 'what is in this pic'; const model = 'meta-llama/llama-4-maverick:free'; // Free model on OpenRouter // const model = 'deepseek/deepseek-r1-0528-qwen3-8b:free'; // Free model on OpenRouter // Allow showReasoning via query param or env const url = new URL(req.url, 'http://localhost'); const showReasoning = url.searchParams.get('showReasoning') === '1' || !!process.env.SHOW_REASONING; const imageAsUrl = url.searchParams.get('imageAsUrl') === '1' || !!process.env.IMAGE_AS_URL; let messages; let contentType = req.headers.get('content-type') || ''; if (contentType.includes('multipart/form-data')) { const formData = await req.formData(); if (imageAsUrl) { const imageUrl = formData.get('imageUrl'); if (typeof imageUrl === 'string' && imageUrl.length > 0) { messages = [ { role: 'user', content: [ { type: 'text', text: prompt }, { type: 'image_url', image_url: { url: imageUrl } } ] } ]; } else { messages = [ { role: 'user', content: prompt } ]; } } else { const imageFile = formData.get('image'); if (imageFile && typeof imageFile === 'object' && imageFile.arrayBuffer) { const arrayBuffer = await imageFile.arrayBuffer(); const base64 = Buffer.from(arrayBuffer).toString('base64'); const mime = imageFile.type || 'image/png'; const dataUrl = `data:${mime};base64,${base64}`; messages = [ { role: 'user', content: [ { type: 'text', text: prompt }, { type: 'image_url', image_url: { url: dataUrl } } ] } ]; } else { messages = [ { role: 'user', content: prompt } ]; } } } else { messages = [ { role: 'user', content: prompt } ]; } const response = await fetch('https://openrouter.ai/api/v1/chat/completions', { method: 'POST', headers: { 'Authorization': `Bearer ${OPENROUTER_API_KEY}`, 'Content-Type': 'application/json', 'HTTP-Referer': 'https://your-app-url.com', // required by OpenRouter 'X-Title': 'Ask2Test', }, body: JSON.stringify({ model, messages, stream: true }) }); if (!response.body) { return new Response('No stream from OpenRouter', { status: 500 }); } const encoder = new TextEncoder(); const stream = new ReadableStream({ async start(controller) { const reader = response.body.getReader(); let buffer = ''; let reasoningBuffer = ''; let inReasoning = showReasoning; let reasoningEmitted = false; while (true) { const { value, done } = await reader.read(); if (done) break; buffer += new TextDecoder().decode(value); let lines = buffer.split('\n'); buffer = lines.pop() ?? ''; for (const line of lines) { const trimmed = line.trim(); if (!trimmed.startsWith('data:')) continue; const jsonStr = trimmed.slice(5).trim(); if (!jsonStr) continue; try { const data = JSON.parse(jsonStr); const content = data.choices?.[0]?.delta?.content; const reasoning = data.choices?.[0]?.delta?.reasoning; if (inReasoning && typeof reasoning === 'string') { reasoningBuffer += reasoning; } if (typeof content === 'string' && content.length > 0) { if (inReasoning && showReasoning && !reasoningEmitted && reasoningBuffer.length > 0) { controller.enqueue(encoder.encode('<|' + reasoningBuffer + '|>' + '\n
\n')); reasoningEmitted = true; } inReasoning = false; controller.enqueue(encoder.encode(content)); } } catch (e) { // ignore parse errors } } } // If stream ends and we never emitted reasoning but have it, emit it now if (inReasoning && showReasoning && !reasoningEmitted && reasoningBuffer.length > 0) { controller.enqueue(encoder.encode('<|' + reasoningBuffer + '|>')); } controller.close(); } }); return new Response(stream, { headers: { 'Content-Type': 'text/plain; charset=utf-8', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive', } }); }); apiRouter.router.all('/ask3', async (req, env, ctx) => { const OPENROUTER_API_KEY = process.env.OPENROUTER_API_KEY || 'sk-...'; // TODO: set your OpenRouter API key const prompt = 'Write a short poem about the earth.'; const model = 'deepseek/deepseek-r1-0528-qwen3-8b:free'; // Free model on OpenRouter const response = await fetch('https://openrouter.ai/api/v1/chat/completions', { method: 'POST', headers: { 'Authorization': `Bearer ${OPENROUTER_API_KEY}`, 'Content-Type': 'application/json', 'HTTP-Referer': 'https://your-app-url.com', // required by OpenRouter 'X-Title': 'Ask2Test', }, body: JSON.stringify({ model, messages: [ { role: 'user', content: prompt } ], stream: true }) }); if (!response.body) { return new Response('No stream from OpenRouter', { status: 500 }); } return new Response(response.body, { headers: { 'Content-Type': 'text/event-stream; charset=utf-8', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive', } }); }); // Handle preflight OPTIONS requests apiRouter.router.options('/*', async (req) => await _cors( req, new Response(null, { status: 204, headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET,POST,OPTIONS', 'Access-Control-Allow-Headers': [ 'Content-Type', 'Authorization', 'x-openai-api-key', 'x-claude-api-key', 'x-google-ai-api-key', 'x-groq-api-key', 'x-openrouter-api-key', 'x-cohere-api-key', 'x-ai21-api-key', 'x-xai-api-key', 'x-deepseek-api-key', 'x-stability-ai-api-key', 'x-falai-api-key', ].join(','), }, }) ) ); apiRouter.router.all('/ping', async (req) => { return json({ message: 'pong' }); }); apiRouter.router.all('/*', async (req) => { return json({ message: 'pong2' }); }); apiRouter.catchNotFound(); const ittyServer = apiRouter.createServerAdapter(); export default ittyServer.handle; export const router = apiRouter.router; export const server = ittyServer; export const handler = ittyServer.handle;