diff --git a/README.md b/README.md index 280036b..51ed25e 100644 --- a/README.md +++ b/README.md @@ -7,11 +7,11 @@ A lightweight, production-ready OpenAI-compatible proxy server that seamlessly f | Environment Variable | Description | Default Value | |----------------------|-------------|-----------------| | `PORT` | Server port | `3007` | -| `UPSTREAM_URL` | Your LLM endpoint URL | `""` | -| `DATABASE_URL` | PostgreSQL connection string for logging | `""` | +| `UPSTREAM_URL` | Your LLM endpoint URL | **Required** | +| `DATABASE_URL` | PostgreSQL connection string for logging | **Required** | | `DATABASE_TABLE` | Name of the table to store the logs | `"llm_proxy"` | -### Cost Calculation +## 💰 Cost Calculation The cost is calculated based on the model and token usage with configurable pricing per model. @@ -27,7 +27,9 @@ export const MODEL_COSTS: Record = { You can add more models to the `MODEL_COSTS` object to support your specific LLM providers. -## 📊 PostgreSQL Table Schema +## 📊 Database Table Schema + +Before running the proxy, you need to create the table in the database. ```sql CREATE TABLE IF NOT EXISTS ( diff --git a/proxy.ts b/proxy.ts index 6d7598f..39ab90d 100644 --- a/proxy.ts +++ b/proxy.ts @@ -12,6 +12,12 @@ import { calculateCost } from "./cost"; const PORT = Number(process.env.PORT || 3007); const UPSTREAM_URL = (process.env.UPSTREAM_URL || "").replace(/\/+$/, ""); +// Validate UPSTREAM_URL is configured +if (!UPSTREAM_URL) { + console.error("❌ UPSTREAM_URL environment variable is required"); + process.exit(1); +} + // --- PostgreSQL connection --- const pool = new Pool({ connectionString: process.env.DATABASE_URL, @@ -80,16 +86,26 @@ const server = http.createServer(async (req, res) => { const bodyBuf = method === "POST" ? await readBody(req) : Buffer.from(""); const requestJson = bodyBuf.length ? JSON.parse(bodyBuf.toString()) : null; - const upstreamRes = await fetch(UPSTREAM_URL + path + url.search, { - method, - headers: { - "Content-Type": (req.headers["content-type"] as string) || "application/json", - Authorization: auth.toString(), - }, - // @ts-ignore - duplex: "half", - body: method === "POST" ? bodyBuf.toString() : undefined, - }); + const targetUrl = UPSTREAM_URL + path + url.search; + + let upstreamRes; + try { + upstreamRes = await fetch(targetUrl, { + method, + headers: { + "Content-Type": (req.headers["content-type"] as string) || "application/json", + Authorization: auth.toString(), + }, + // @ts-ignore + duplex: "half", + body: method === "POST" ? bodyBuf.toString() : undefined, + }); + } catch (fetchError: any) { + console.error("Fetch error:", fetchError.message, "URL:", targetUrl); + res.statusCode = 502; + res.end(JSON.stringify({ error: "Failed to connect to upstream", message: fetchError.message })); + return; + } const contentType = upstreamRes.headers.get("content-type") || "application/json"; res.statusCode = upstreamRes.status;