@neurameter/proxy 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +15 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/package.json +49 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';var http=require('http'),https=require('https'),url=require('url'),core=require('@neurameter/core');function E(e,o){let t=e.indexOf(o);if(!(t===-1||t+1>=e.length))return e[t+1]}function w(e,o){return e.includes(o)}function S(e,o){let t=JSON.stringify({events:[o]}),s=new url.URL("/v1/events",e.endpoint);fetch(s.toString(),{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e.apiKey}`},body:t}).catch(r=>{process.stderr.write(`[neurameter-proxy] Failed to send event: ${r instanceof Error?r.message:String(r)}
|
|
3
|
+
`);});}function N(e,o,t,s){let r=core.getModelPricing("openai",o),u=r?core.calculateCostMicrodollars(t,r):0;return {eventId:crypto.randomUUID(),timestamp:new Date().toISOString(),traceId:crypto.randomUUID(),spanId:crypto.randomUUID(),agentName:e.agentName,provider:"openai",model:o,inputTokens:t.inputTokens,outputTokens:t.outputTokens,reasoningTokens:t.reasoningTokens??0,cachedTokens:t.cachedTokens??0,costMicrodollars:u,latencyMs:s,orgId:"",projectId:e.projectId}}function I(e){return new Promise((o,t)=>{let s=[];e.on("data",r=>s.push(r)),e.on("end",()=>o(Buffer.concat(s))),e.on("error",t);})}function R(e,o,t,s,r){return new Promise((u,g)=>{let n=new url.URL(t,e.target),f=n.protocol==="https:",h=f?https.request:http.request,d={...s};d.host=n.host,delete d.connection;let i=h({hostname:n.hostname,port:n.port||(f?443:80),path:n.pathname+n.search,method:o,headers:d},a=>{let p=[];a.on("data",m=>p.push(m)),a.on("end",()=>{let m={};for(let[y,c]of Object.entries(a.headers))c&&(m[y]=Array.isArray(c)?c.join(", "):c);u({statusCode:a.statusCode??500,headers:m,body:Buffer.concat(p)});}),a.on("error",g);});i.on("error",g),r&&i.write(r),i.end();})}function U(e,o,t,s,r,u,g){let n=new url.URL(o,e.target),f=n.protocol==="https:",h=f?https.request:http.request,d={...t};d.host=n.host,delete d.connection;let i=h({hostname:n.hostname,port:n.port||(f?443:80),path:n.pathname+n.search,method:"POST",headers:d},a=>{r.writeHead(a.statusCode??500,a.headers);let p=null;a.on("data",m=>{r.write(m);let y=m.toString("utf-8");for(let c of y.split(`
|
|
4
|
+
`)){if(!c.startsWith("data: "))continue;let T=c.slice(6).trim();if(T!=="[DONE]")try{let l=JSON.parse(T);l.usage&&(p={inputTokens:l.usage.prompt_tokens??0,outputTokens:l.usage.completion_tokens??0,reasoningTokens:l.usage.completion_tokens_details?.reasoning_tokens??0,cachedTokens:l.usage.prompt_tokens_details?.cached_tokens??0});}catch{}}}),a.on("end",()=>{if(r.end(),p){let m=Date.now()-g,y=N(e,u,p,m);S(e,y);}}),a.on("error",m=>{process.stderr.write(`[neurameter-proxy] Upstream stream error: ${m.message}
|
|
5
|
+
`),r.end();});});i.on("error",a=>{process.stderr.write(`[neurameter-proxy] Upstream request error: ${a.message}
|
|
6
|
+
`),r.writeHead(502,{"Content-Type":"application/json"}),r.end(JSON.stringify({error:{message:"Bad gateway"}}));}),i.write(s),i.end();}async function A(e,o,t){let s=o.url??"/",r=o.method??"GET";if(s==="/health"&&r==="GET"){t.writeHead(200,{"Content-Type":"application/json"}),t.end(JSON.stringify({status:"ok",target:e.target}));return}if(s==="/v1/chat/completions"&&r==="POST"){let u=Date.now(),g=await I(o),n;try{n=JSON.parse(g.toString("utf-8"));}catch{t.writeHead(400,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:{message:"Invalid JSON body"}}));return}let f=String(n.model??"unknown"),h=n.stream===true;h&&!n.stream_options&&(n.stream_options={include_usage:true});let d={"content-type":"application/json"},i=o.headers.authorization;i&&(d.authorization=Array.isArray(i)?i[0]:i);let a=Buffer.from(JSON.stringify(n),"utf-8");if(d["content-length"]=String(a.length),h)U(e,s,d,a,t,f,u);else try{let p=await R(e,"POST",s,d,a),m=Date.now()-u;try{let c=JSON.parse(p.body.toString("utf-8"));if(c.usage){let T={inputTokens:c.usage.prompt_tokens??0,outputTokens:c.usage.completion_tokens??0,reasoningTokens:c.usage.completion_tokens_details?.reasoning_tokens??0,cachedTokens:c.usage.prompt_tokens_details?.cached_tokens??0},l=N(e,f,T,m);S(e,l);}}catch{}let y={};for(let[c,T]of Object.entries(p.headers))c!=="transfer-encoding"&&(y[c]=T);y["content-length"]=String(p.body.length),t.writeHead(p.statusCode,y),t.end(p.body);}catch(p){process.stderr.write(`[neurameter-proxy] Upstream error: ${p instanceof Error?p.message:String(p)}
|
|
7
|
+
`),t.writeHead(502,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:{message:"Bad gateway"}}));}return}if(s==="/v1/models"&&r==="GET"){let u={},g=o.headers.authorization;g&&(u.authorization=Array.isArray(g)?g[0]:g);try{let n=await R(e,"GET",s,u,null);t.writeHead(n.statusCode,n.headers),t.end(n.body);}catch{t.writeHead(502,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:{message:"Bad gateway"}}));}return}t.writeHead(404,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:{message:"Not found"}}));}function C(){let e=process.argv.slice(2);(w(e,"--help")||w(e,"-h"))&&(process.stderr.write(["neurameter-proxy \u2014 OpenAI-compatible proxy for NeuraMeter cost tracking","","Usage:"," neurameter-proxy --api-key <key> --project <id> [options]","","Options:"," --api-key <key> NeuraMeter API key (or NEURAMETER_API_KEY env)"," --project <id> NeuraMeter project ID (or NEURAMETER_PROJECT_ID env)"," --port <num> Listen port (default: 3100, or NEURAMETER_PROXY_PORT env)"," --target <url> Upstream API URL (default: https://api.openai.com, or NEURAMETER_PROXY_TARGET env)"," --agent-name <name> Agent name for events (default: proxy-agent)"," --endpoint <url> Ingestion API URL (default: https://neurameter-ingestion.neurameter.workers.dev)"," --help, -h Show this help message","","Quick start:"," # Start proxy on port 3100"," neurameter-proxy --api-key nm_xxx --project proj_xxx",""," # Point your app at the proxy"," OPENAI_BASE_URL=http://localhost:3100/v1 node app.js",""].join(`
|
|
8
|
+
`)),process.exit(0));let o=E(e,"--api-key")??process.env.NEURAMETER_API_KEY,t=E(e,"--project")??process.env.NEURAMETER_PROJECT_ID,s=Number(E(e,"--port")??process.env.NEURAMETER_PROXY_PORT??"3100"),r=E(e,"--target")??process.env.NEURAMETER_PROXY_TARGET??"https://api.openai.com",u=E(e,"--agent-name")??"proxy-agent",g=E(e,"--endpoint")??process.env.NEURAMETER_ENDPOINT??"https://neurameter-ingestion.neurameter.workers.dev";o||(process.stderr.write(`Error: --api-key flag or NEURAMETER_API_KEY environment variable is required.
|
|
9
|
+
`),process.exit(1)),t||(process.stderr.write(`Error: --project flag or NEURAMETER_PROJECT_ID environment variable is required.
|
|
10
|
+
`),process.exit(1));let n={apiKey:o,projectId:t,port:s,target:r,agentName:u,endpoint:g},f=http.createServer((d,i)=>{A(n,d,i).catch(a=>{process.stderr.write(`[neurameter-proxy] Unhandled error: ${a instanceof Error?a.message:String(a)}
|
|
11
|
+
`),i.headersSent||i.writeHead(500,{"Content-Type":"application/json"}),i.end(JSON.stringify({error:{message:"Internal server error"}}));});}),h=()=>{process.stderr.write(`[neurameter-proxy] Shutting down...
|
|
12
|
+
`),f.close(),process.exit(0);};process.on("SIGINT",h),process.on("SIGTERM",h),f.listen(s,()=>{process.stderr.write(`[neurameter-proxy] Listening on http://localhost:${s}
|
|
13
|
+
[neurameter-proxy] Forwarding to ${r}
|
|
14
|
+
`);});}C();//# sourceMappingURL=index.cjs.map
|
|
15
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":["getFlag","args","flag","idx","hasFlag","sendEvent","config","event","body","url","URL","err","buildEvent","model","usage","latencyMs","pricing","getModelPricing","costMicrodollars","calculateCostMicrodollars","readBody","req","resolve","reject","chunks","chunk","forwardRequest","method","path","headers","isHttps","reqFn","httpsRequest","httpRequest","outHeaders","res","resHeaders","k","v","forwardStreaming","clientRes","startTime","usageFound","text","line","payload","parsed","handleRequest","rawBody","isStream","auth","modifiedBody","upstream","respBody","main","apiKey","projectId","port","target","agentName","endpoint","server","createServer","shutdown"],"mappings":"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","file":"index.cjs","sourcesContent":["#!/usr/bin/env node\n// ---------------------------------------------------------------------------\n// @neurameter/proxy – OpenAI-compatible proxy server\n// ---------------------------------------------------------------------------\n// Usage:\n// neurameter-proxy --api-key nm_xxx --project proj_xxx\n// npx @neurameter/proxy --api-key nm_xxx --project proj_xxx\n//\n// Env vars (fallbacks):\n// NEURAMETER_API_KEY, NEURAMETER_PROJECT_ID, NEURAMETER_PROXY_PORT,\n// NEURAMETER_PROXY_TARGET, NEURAMETER_ENDPOINT\n// ---------------------------------------------------------------------------\n\nimport { createServer, type IncomingMessage, type ServerResponse } from 'node:http';\nimport { request as httpsRequest } from 'node:https';\nimport { request as httpRequest } from 'node:http';\nimport { URL } from 'node:url';\nimport { getModelPricing, calculateCostMicrodollars } from '@neurameter/core';\nimport type { TokenUsage } from '@neurameter/core';\n\n// ---------------------------------------------------------------------------\n// Argument parsing (lightweight — no external deps)\n// ---------------------------------------------------------------------------\n\nfunction getFlag(args: string[], flag: string): string | undefined {\n const idx = args.indexOf(flag);\n if (idx === -1 || idx + 1 >= args.length) return undefined;\n return args[idx + 1];\n}\n\nfunction hasFlag(args: string[], flag: string): boolean {\n return args.includes(flag);\n}\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ninterface ProxyConfig {\n apiKey: string;\n projectId: string;\n port: number;\n target: string;\n agentName: string;\n endpoint: string;\n}\n\n// ---------------------------------------------------------------------------\n// Event sending (async, never blocks response)\n// ---------------------------------------------------------------------------\n\nfunction sendEvent(config: ProxyConfig, event: Record<string, unknown>): void {\n const body = JSON.stringify({ events: [event] });\n const url = new URL('/v1/events', config.endpoint);\n\n fetch(url.toString(), {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${config.apiKey}`,\n },\n body,\n }).catch((err: unknown) => {\n process.stderr.write(\n `[neurameter-proxy] Failed to send event: ${err instanceof Error ? err.message : String(err)}\\n`,\n );\n });\n}\n\nfunction buildEvent(\n config: ProxyConfig,\n model: string,\n usage: TokenUsage,\n latencyMs: number,\n): Record<string, unknown> {\n const pricing = getModelPricing('openai', model);\n const costMicrodollars = pricing\n ? calculateCostMicrodollars(usage, pricing)\n : 0;\n\n return {\n eventId: crypto.randomUUID(),\n timestamp: new Date().toISOString(),\n traceId: crypto.randomUUID(),\n spanId: crypto.randomUUID(),\n agentName: config.agentName,\n provider: 'openai',\n model,\n inputTokens: usage.inputTokens,\n outputTokens: usage.outputTokens,\n reasoningTokens: usage.reasoningTokens ?? 0,\n cachedTokens: usage.cachedTokens ?? 0,\n costMicrodollars,\n latencyMs,\n orgId: '',\n projectId: config.projectId,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Body reading helper\n// ---------------------------------------------------------------------------\n\nfunction readBody(req: IncomingMessage): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on('data', (chunk: Buffer) => chunks.push(chunk));\n req.on('end', () => resolve(Buffer.concat(chunks)));\n req.on('error', reject);\n });\n}\n\n// ---------------------------------------------------------------------------\n// Forward request to upstream (OpenAI)\n// ---------------------------------------------------------------------------\n\nfunction forwardRequest(\n config: ProxyConfig,\n method: string,\n path: string,\n headers: Record<string, string>,\n body: Buffer | null,\n): Promise<{ statusCode: number; headers: Record<string, string>; body: Buffer }> {\n return new Promise((resolve, reject) => {\n const url = new URL(path, config.target);\n const isHttps = url.protocol === 'https:';\n const reqFn = isHttps ? httpsRequest : httpRequest;\n\n const outHeaders: Record<string, string> = { ...headers };\n outHeaders['host'] = url.host;\n delete outHeaders['connection'];\n\n const req = reqFn(\n {\n hostname: url.hostname,\n port: url.port || (isHttps ? 443 : 80),\n path: url.pathname + url.search,\n method,\n headers: outHeaders,\n },\n (res) => {\n const chunks: Buffer[] = [];\n res.on('data', (chunk: Buffer) => chunks.push(chunk));\n res.on('end', () => {\n const resHeaders: Record<string, string> = {};\n for (const [k, v] of Object.entries(res.headers)) {\n if (v) resHeaders[k] = Array.isArray(v) ? v.join(', ') : v;\n }\n resolve({\n statusCode: res.statusCode ?? 500,\n headers: resHeaders,\n body: Buffer.concat(chunks),\n });\n });\n res.on('error', reject);\n },\n );\n\n req.on('error', reject);\n if (body) req.write(body);\n req.end();\n });\n}\n\n// ---------------------------------------------------------------------------\n// Streaming forward (SSE passthrough with usage extraction)\n// ---------------------------------------------------------------------------\n\nfunction forwardStreaming(\n config: ProxyConfig,\n path: string,\n headers: Record<string, string>,\n body: Buffer,\n clientRes: ServerResponse,\n model: string,\n startTime: number,\n): void {\n const url = new URL(path, config.target);\n const isHttps = url.protocol === 'https:';\n const reqFn = isHttps ? httpsRequest : httpRequest;\n\n const outHeaders: Record<string, string> = { ...headers };\n outHeaders['host'] = url.host;\n delete outHeaders['connection'];\n\n const req = reqFn(\n {\n hostname: url.hostname,\n port: url.port || (isHttps ? 443 : 80),\n path: url.pathname + url.search,\n method: 'POST',\n headers: outHeaders,\n },\n (res) => {\n clientRes.writeHead(res.statusCode ?? 500, res.headers);\n\n let usageFound: TokenUsage | null = null;\n\n res.on('data', (chunk: Buffer) => {\n clientRes.write(chunk);\n\n // Parse SSE lines for usage\n const text = chunk.toString('utf-8');\n for (const line of text.split('\\n')) {\n if (!line.startsWith('data: ')) continue;\n const payload = line.slice(6).trim();\n if (payload === '[DONE]') continue;\n try {\n const parsed = JSON.parse(payload);\n if (parsed.usage) {\n usageFound = {\n inputTokens: parsed.usage.prompt_tokens ?? 0,\n outputTokens: parsed.usage.completion_tokens ?? 0,\n reasoningTokens: parsed.usage.completion_tokens_details?.reasoning_tokens ?? 0,\n cachedTokens: parsed.usage.prompt_tokens_details?.cached_tokens ?? 0,\n };\n }\n } catch {\n // not valid JSON, skip\n }\n }\n });\n\n res.on('end', () => {\n clientRes.end();\n if (usageFound) {\n const latencyMs = Date.now() - startTime;\n const event = buildEvent(config, model, usageFound, latencyMs);\n sendEvent(config, event);\n }\n });\n\n res.on('error', (err) => {\n process.stderr.write(\n `[neurameter-proxy] Upstream stream error: ${err.message}\\n`,\n );\n clientRes.end();\n });\n },\n );\n\n req.on('error', (err) => {\n process.stderr.write(\n `[neurameter-proxy] Upstream request error: ${err.message}\\n`,\n );\n clientRes.writeHead(502, { 'Content-Type': 'application/json' });\n clientRes.end(JSON.stringify({ error: { message: 'Bad gateway' } }));\n });\n\n req.write(body);\n req.end();\n}\n\n// ---------------------------------------------------------------------------\n// Request handler\n// ---------------------------------------------------------------------------\n\nasync function handleRequest(\n config: ProxyConfig,\n req: IncomingMessage,\n res: ServerResponse,\n): Promise<void> {\n const path = req.url ?? '/';\n const method = req.method ?? 'GET';\n\n // Health check\n if (path === '/health' && method === 'GET') {\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ status: 'ok', target: config.target }));\n return;\n }\n\n // POST /v1/chat/completions — the main proxy path\n if (path === '/v1/chat/completions' && method === 'POST') {\n const startTime = Date.now();\n const rawBody = await readBody(req);\n let parsed: Record<string, unknown>;\n\n try {\n parsed = JSON.parse(rawBody.toString('utf-8'));\n } catch {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: { message: 'Invalid JSON body' } }));\n return;\n }\n\n const model = String(parsed['model'] ?? 'unknown');\n const isStream = parsed['stream'] === true;\n\n // Auto-inject stream_options for usage reporting\n if (isStream && !parsed['stream_options']) {\n parsed['stream_options'] = { include_usage: true };\n }\n\n const headers: Record<string, string> = {\n 'content-type': 'application/json',\n };\n // Forward authorization header\n const auth = req.headers['authorization'];\n if (auth) headers['authorization'] = Array.isArray(auth) ? auth[0] : auth;\n\n const modifiedBody = Buffer.from(JSON.stringify(parsed), 'utf-8');\n headers['content-length'] = String(modifiedBody.length);\n\n if (isStream) {\n forwardStreaming(config, path, headers, modifiedBody, res, model, startTime);\n } else {\n try {\n const upstream = await forwardRequest(config, 'POST', path, headers, modifiedBody);\n const latencyMs = Date.now() - startTime;\n\n // Extract usage from response\n try {\n const respBody = JSON.parse(upstream.body.toString('utf-8'));\n if (respBody.usage) {\n const usage: TokenUsage = {\n inputTokens: respBody.usage.prompt_tokens ?? 0,\n outputTokens: respBody.usage.completion_tokens ?? 0,\n reasoningTokens: respBody.usage.completion_tokens_details?.reasoning_tokens ?? 0,\n cachedTokens: respBody.usage.prompt_tokens_details?.cached_tokens ?? 0,\n };\n const event = buildEvent(config, model, usage, latencyMs);\n sendEvent(config, event);\n }\n } catch {\n // response wasn't JSON, skip tracking\n }\n\n // Return upstream response as-is\n const outHeaders: Record<string, string> = {};\n for (const [k, v] of Object.entries(upstream.headers)) {\n if (k !== 'transfer-encoding') outHeaders[k] = v;\n }\n outHeaders['content-length'] = String(upstream.body.length);\n res.writeHead(upstream.statusCode, outHeaders);\n res.end(upstream.body);\n } catch (err) {\n process.stderr.write(\n `[neurameter-proxy] Upstream error: ${err instanceof Error ? err.message : String(err)}\\n`,\n );\n res.writeHead(502, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: { message: 'Bad gateway' } }));\n }\n }\n return;\n }\n\n // GET /v1/models — passthrough\n if (path === '/v1/models' && method === 'GET') {\n const headers: Record<string, string> = {};\n const auth = req.headers['authorization'];\n if (auth) headers['authorization'] = Array.isArray(auth) ? auth[0] : auth;\n\n try {\n const upstream = await forwardRequest(config, 'GET', path, headers, null);\n res.writeHead(upstream.statusCode, upstream.headers);\n res.end(upstream.body);\n } catch {\n res.writeHead(502, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: { message: 'Bad gateway' } }));\n }\n return;\n }\n\n // 404 for everything else\n res.writeHead(404, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: { message: 'Not found' } }));\n}\n\n// ---------------------------------------------------------------------------\n// Main\n// ---------------------------------------------------------------------------\n\nfunction main(): void {\n const args = process.argv.slice(2);\n\n if (hasFlag(args, '--help') || hasFlag(args, '-h')) {\n process.stderr.write(\n [\n 'neurameter-proxy — OpenAI-compatible proxy for NeuraMeter cost tracking',\n '',\n 'Usage:',\n ' neurameter-proxy --api-key <key> --project <id> [options]',\n '',\n 'Options:',\n ' --api-key <key> NeuraMeter API key (or NEURAMETER_API_KEY env)',\n ' --project <id> NeuraMeter project ID (or NEURAMETER_PROJECT_ID env)',\n ' --port <num> Listen port (default: 3100, or NEURAMETER_PROXY_PORT env)',\n ' --target <url> Upstream API URL (default: https://api.openai.com, or NEURAMETER_PROXY_TARGET env)',\n ' --agent-name <name> Agent name for events (default: proxy-agent)',\n ' --endpoint <url> Ingestion API URL (default: https://neurameter-ingestion.neurameter.workers.dev)',\n ' --help, -h Show this help message',\n '',\n 'Quick start:',\n ' # Start proxy on port 3100',\n ' neurameter-proxy --api-key nm_xxx --project proj_xxx',\n '',\n ' # Point your app at the proxy',\n ' OPENAI_BASE_URL=http://localhost:3100/v1 node app.js',\n '',\n ].join('\\n'),\n );\n process.exit(0);\n }\n\n const apiKey =\n getFlag(args, '--api-key') ?? process.env['NEURAMETER_API_KEY'];\n const projectId =\n getFlag(args, '--project') ?? process.env['NEURAMETER_PROJECT_ID'];\n const port = Number(\n getFlag(args, '--port') ?? process.env['NEURAMETER_PROXY_PORT'] ?? '3100',\n );\n const target =\n getFlag(args, '--target') ??\n process.env['NEURAMETER_PROXY_TARGET'] ??\n 'https://api.openai.com';\n const agentName =\n getFlag(args, '--agent-name') ?? 'proxy-agent';\n const endpoint =\n getFlag(args, '--endpoint') ??\n process.env['NEURAMETER_ENDPOINT'] ??\n 'https://neurameter-ingestion.neurameter.workers.dev';\n\n if (!apiKey) {\n process.stderr.write(\n 'Error: --api-key flag or NEURAMETER_API_KEY environment variable is required.\\n',\n );\n process.exit(1);\n }\n\n if (!projectId) {\n process.stderr.write(\n 'Error: --project flag or NEURAMETER_PROJECT_ID environment variable is required.\\n',\n );\n process.exit(1);\n }\n\n const config: ProxyConfig = {\n apiKey,\n projectId,\n port,\n target,\n agentName,\n endpoint,\n };\n\n const server = createServer((req, res) => {\n handleRequest(config, req, res).catch((err: unknown) => {\n process.stderr.write(\n `[neurameter-proxy] Unhandled error: ${err instanceof Error ? err.message : String(err)}\\n`,\n );\n if (!res.headersSent) {\n res.writeHead(500, { 'Content-Type': 'application/json' });\n }\n res.end(JSON.stringify({ error: { message: 'Internal server error' } }));\n });\n });\n\n const shutdown = () => {\n process.stderr.write('[neurameter-proxy] Shutting down...\\n');\n server.close();\n process.exit(0);\n };\n\n process.on('SIGINT', shutdown);\n process.on('SIGTERM', shutdown);\n\n server.listen(port, () => {\n process.stderr.write(\n `[neurameter-proxy] Listening on http://localhost:${port}\\n` +\n `[neurameter-proxy] Forwarding to ${target}\\n`,\n );\n });\n}\n\nmain();\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {createServer,request as request$1}from'http';import {request}from'https';import {URL}from'url';import {getModelPricing,calculateCostMicrodollars}from'@neurameter/core';function E(e,o){let t=e.indexOf(o);if(!(t===-1||t+1>=e.length))return e[t+1]}function w(e,o){return e.includes(o)}function S(e,o){let t=JSON.stringify({events:[o]}),s=new URL("/v1/events",e.endpoint);fetch(s.toString(),{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e.apiKey}`},body:t}).catch(r=>{process.stderr.write(`[neurameter-proxy] Failed to send event: ${r instanceof Error?r.message:String(r)}
|
|
3
|
+
`);});}function N(e,o,t,s){let r=getModelPricing("openai",o),u=r?calculateCostMicrodollars(t,r):0;return {eventId:crypto.randomUUID(),timestamp:new Date().toISOString(),traceId:crypto.randomUUID(),spanId:crypto.randomUUID(),agentName:e.agentName,provider:"openai",model:o,inputTokens:t.inputTokens,outputTokens:t.outputTokens,reasoningTokens:t.reasoningTokens??0,cachedTokens:t.cachedTokens??0,costMicrodollars:u,latencyMs:s,orgId:"",projectId:e.projectId}}function I(e){return new Promise((o,t)=>{let s=[];e.on("data",r=>s.push(r)),e.on("end",()=>o(Buffer.concat(s))),e.on("error",t);})}function R(e,o,t,s,r){return new Promise((u,g)=>{let n=new URL(t,e.target),f=n.protocol==="https:",h=f?request:request$1,d={...s};d.host=n.host,delete d.connection;let i=h({hostname:n.hostname,port:n.port||(f?443:80),path:n.pathname+n.search,method:o,headers:d},a=>{let p=[];a.on("data",m=>p.push(m)),a.on("end",()=>{let m={};for(let[y,c]of Object.entries(a.headers))c&&(m[y]=Array.isArray(c)?c.join(", "):c);u({statusCode:a.statusCode??500,headers:m,body:Buffer.concat(p)});}),a.on("error",g);});i.on("error",g),r&&i.write(r),i.end();})}function U(e,o,t,s,r,u,g){let n=new URL(o,e.target),f=n.protocol==="https:",h=f?request:request$1,d={...t};d.host=n.host,delete d.connection;let i=h({hostname:n.hostname,port:n.port||(f?443:80),path:n.pathname+n.search,method:"POST",headers:d},a=>{r.writeHead(a.statusCode??500,a.headers);let p=null;a.on("data",m=>{r.write(m);let y=m.toString("utf-8");for(let c of y.split(`
|
|
4
|
+
`)){if(!c.startsWith("data: "))continue;let T=c.slice(6).trim();if(T!=="[DONE]")try{let l=JSON.parse(T);l.usage&&(p={inputTokens:l.usage.prompt_tokens??0,outputTokens:l.usage.completion_tokens??0,reasoningTokens:l.usage.completion_tokens_details?.reasoning_tokens??0,cachedTokens:l.usage.prompt_tokens_details?.cached_tokens??0});}catch{}}}),a.on("end",()=>{if(r.end(),p){let m=Date.now()-g,y=N(e,u,p,m);S(e,y);}}),a.on("error",m=>{process.stderr.write(`[neurameter-proxy] Upstream stream error: ${m.message}
|
|
5
|
+
`),r.end();});});i.on("error",a=>{process.stderr.write(`[neurameter-proxy] Upstream request error: ${a.message}
|
|
6
|
+
`),r.writeHead(502,{"Content-Type":"application/json"}),r.end(JSON.stringify({error:{message:"Bad gateway"}}));}),i.write(s),i.end();}async function A(e,o,t){let s=o.url??"/",r=o.method??"GET";if(s==="/health"&&r==="GET"){t.writeHead(200,{"Content-Type":"application/json"}),t.end(JSON.stringify({status:"ok",target:e.target}));return}if(s==="/v1/chat/completions"&&r==="POST"){let u=Date.now(),g=await I(o),n;try{n=JSON.parse(g.toString("utf-8"));}catch{t.writeHead(400,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:{message:"Invalid JSON body"}}));return}let f=String(n.model??"unknown"),h=n.stream===true;h&&!n.stream_options&&(n.stream_options={include_usage:true});let d={"content-type":"application/json"},i=o.headers.authorization;i&&(d.authorization=Array.isArray(i)?i[0]:i);let a=Buffer.from(JSON.stringify(n),"utf-8");if(d["content-length"]=String(a.length),h)U(e,s,d,a,t,f,u);else try{let p=await R(e,"POST",s,d,a),m=Date.now()-u;try{let c=JSON.parse(p.body.toString("utf-8"));if(c.usage){let T={inputTokens:c.usage.prompt_tokens??0,outputTokens:c.usage.completion_tokens??0,reasoningTokens:c.usage.completion_tokens_details?.reasoning_tokens??0,cachedTokens:c.usage.prompt_tokens_details?.cached_tokens??0},l=N(e,f,T,m);S(e,l);}}catch{}let y={};for(let[c,T]of Object.entries(p.headers))c!=="transfer-encoding"&&(y[c]=T);y["content-length"]=String(p.body.length),t.writeHead(p.statusCode,y),t.end(p.body);}catch(p){process.stderr.write(`[neurameter-proxy] Upstream error: ${p instanceof Error?p.message:String(p)}
|
|
7
|
+
`),t.writeHead(502,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:{message:"Bad gateway"}}));}return}if(s==="/v1/models"&&r==="GET"){let u={},g=o.headers.authorization;g&&(u.authorization=Array.isArray(g)?g[0]:g);try{let n=await R(e,"GET",s,u,null);t.writeHead(n.statusCode,n.headers),t.end(n.body);}catch{t.writeHead(502,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:{message:"Bad gateway"}}));}return}t.writeHead(404,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:{message:"Not found"}}));}function C(){let e=process.argv.slice(2);(w(e,"--help")||w(e,"-h"))&&(process.stderr.write(["neurameter-proxy \u2014 OpenAI-compatible proxy for NeuraMeter cost tracking","","Usage:"," neurameter-proxy --api-key <key> --project <id> [options]","","Options:"," --api-key <key> NeuraMeter API key (or NEURAMETER_API_KEY env)"," --project <id> NeuraMeter project ID (or NEURAMETER_PROJECT_ID env)"," --port <num> Listen port (default: 3100, or NEURAMETER_PROXY_PORT env)"," --target <url> Upstream API URL (default: https://api.openai.com, or NEURAMETER_PROXY_TARGET env)"," --agent-name <name> Agent name for events (default: proxy-agent)"," --endpoint <url> Ingestion API URL (default: https://neurameter-ingestion.neurameter.workers.dev)"," --help, -h Show this help message","","Quick start:"," # Start proxy on port 3100"," neurameter-proxy --api-key nm_xxx --project proj_xxx",""," # Point your app at the proxy"," OPENAI_BASE_URL=http://localhost:3100/v1 node app.js",""].join(`
|
|
8
|
+
`)),process.exit(0));let o=E(e,"--api-key")??process.env.NEURAMETER_API_KEY,t=E(e,"--project")??process.env.NEURAMETER_PROJECT_ID,s=Number(E(e,"--port")??process.env.NEURAMETER_PROXY_PORT??"3100"),r=E(e,"--target")??process.env.NEURAMETER_PROXY_TARGET??"https://api.openai.com",u=E(e,"--agent-name")??"proxy-agent",g=E(e,"--endpoint")??process.env.NEURAMETER_ENDPOINT??"https://neurameter-ingestion.neurameter.workers.dev";o||(process.stderr.write(`Error: --api-key flag or NEURAMETER_API_KEY environment variable is required.
|
|
9
|
+
`),process.exit(1)),t||(process.stderr.write(`Error: --project flag or NEURAMETER_PROJECT_ID environment variable is required.
|
|
10
|
+
`),process.exit(1));let n={apiKey:o,projectId:t,port:s,target:r,agentName:u,endpoint:g},f=createServer((d,i)=>{A(n,d,i).catch(a=>{process.stderr.write(`[neurameter-proxy] Unhandled error: ${a instanceof Error?a.message:String(a)}
|
|
11
|
+
`),i.headersSent||i.writeHead(500,{"Content-Type":"application/json"}),i.end(JSON.stringify({error:{message:"Internal server error"}}));});}),h=()=>{process.stderr.write(`[neurameter-proxy] Shutting down...
|
|
12
|
+
`),f.close(),process.exit(0);};process.on("SIGINT",h),process.on("SIGTERM",h),f.listen(s,()=>{process.stderr.write(`[neurameter-proxy] Listening on http://localhost:${s}
|
|
13
|
+
[neurameter-proxy] Forwarding to ${r}
|
|
14
|
+
`);});}C();//# sourceMappingURL=index.js.map
|
|
15
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":["getFlag","args","flag","idx","hasFlag","sendEvent","config","event","body","url","URL","err","buildEvent","model","usage","latencyMs","pricing","getModelPricing","costMicrodollars","calculateCostMicrodollars","readBody","req","resolve","reject","chunks","chunk","forwardRequest","method","path","headers","isHttps","reqFn","httpsRequest","httpRequest","outHeaders","res","resHeaders","k","v","forwardStreaming","clientRes","startTime","usageFound","text","line","payload","parsed","handleRequest","rawBody","isStream","auth","modifiedBody","upstream","respBody","main","apiKey","projectId","port","target","agentName","endpoint","server","createServer","shutdown"],"mappings":";gLAwBA,SAASA,CAAAA,CAAQC,CAAAA,CAAgBC,CAAAA,CAAkC,CACjE,IAAMC,EAAMF,CAAAA,CAAK,OAAA,CAAQC,CAAI,CAAA,CAC7B,GAAI,EAAAC,CAAAA,GAAQ,EAAA,EAAMA,CAAAA,CAAM,CAAA,EAAKF,CAAAA,CAAK,MAAA,CAAA,CAClC,OAAOA,CAAAA,CAAKE,EAAM,CAAC,CACrB,CAEA,SAASC,CAAAA,CAAQH,CAAAA,CAAgBC,CAAAA,CAAuB,CACtD,OAAOD,CAAAA,CAAK,QAAA,CAASC,CAAI,CAC3B,CAmBA,SAASG,CAAAA,CAAUC,CAAAA,CAAqBC,CAAAA,CAAsC,CAC5E,IAAMC,CAAAA,CAAO,KAAK,SAAA,CAAU,CAAE,MAAA,CAAQ,CAACD,CAAK,CAAE,CAAC,CAAA,CACzCE,CAAAA,CAAM,IAAIC,GAAAA,CAAI,YAAA,CAAcJ,CAAAA,CAAO,QAAQ,CAAA,CAEjD,KAAA,CAAMG,CAAAA,CAAI,QAAA,EAAS,CAAG,CACpB,MAAA,CAAQ,OACR,OAAA,CAAS,CACP,cAAA,CAAgB,kBAAA,CAChB,aAAA,CAAe,CAAA,OAAA,EAAUH,CAAAA,CAAO,MAAM,CAAA,CACxC,CAAA,CACA,IAAA,CAAAE,CACF,CAAC,CAAA,CAAE,MAAOG,CAAAA,EAAiB,CACzB,OAAA,CAAQ,MAAA,CAAO,KAAA,CACb,CAAA,yCAAA,EAA4CA,CAAAA,YAAe,KAAA,CAAQA,CAAAA,CAAI,OAAA,CAAU,MAAA,CAAOA,CAAG,CAAC;AAAA,CAC9F,EACF,CAAC,EACH,CAEA,SAASC,CAAAA,CACPN,EACAO,CAAAA,CACAC,CAAAA,CACAC,EACyB,CACzB,IAAMC,EAAUC,eAAAA,CAAgB,QAAA,CAAUJ,CAAK,CAAA,CACzCK,CAAAA,CAAmBF,EACrBG,yBAAAA,CAA0BL,CAAAA,CAAOE,CAAO,CAAA,CACxC,EAEJ,OAAO,CACL,QAAS,MAAA,CAAO,UAAA,GAChB,SAAA,CAAW,IAAI,MAAK,CAAE,WAAA,GACtB,OAAA,CAAS,MAAA,CAAO,YAAW,CAC3B,MAAA,CAAQ,OAAO,UAAA,EAAW,CAC1B,SAAA,CAAWV,CAAAA,CAAO,UAClB,QAAA,CAAU,QAAA,CACV,MAAAO,CAAAA,CACA,WAAA,CAAaC,EAAM,WAAA,CACnB,YAAA,CAAcA,EAAM,YAAA,CACpB,eAAA,CAAiBA,EAAM,eAAA,EAAmB,CAAA,CAC1C,aAAcA,CAAAA,CAAM,YAAA,EAAgB,EACpC,gBAAA,CAAAI,CAAAA,CACA,SAAA,CAAAH,CAAAA,CACA,MAAO,EAAA,CACP,SAAA,CAAWT,EAAO,SACpB,CACF,CAMA,SAASc,CAAAA,CAASC,EAAuC,CACvD,OAAO,IAAI,OAAA,CAAQ,CAACC,EAASC,CAAAA,GAAW,CACtC,IAAMC,CAAAA,CAAmB,EAAC,CAC1BH,CAAAA,CAAI,GAAG,MAAA,CAASI,CAAAA,EAAkBD,EAAO,IAAA,CAAKC,CAAK,CAAC,CAAA,CACpDJ,CAAAA,CAAI,GAAG,KAAA,CAAO,IAAMC,EAAQ,MAAA,CAAO,MAAA,CAAOE,CAAM,CAAC,CAAC,EAClDH,CAAAA,CAAI,EAAA,CAAG,OAAA,CAASE,CAAM,EACxB,CAAC,CACH,CAMA,SAASG,CAAAA,CACPpB,EACAqB,CAAAA,CACAC,CAAAA,CACAC,EACArB,CAAAA,CACgF,CAChF,OAAO,IAAI,OAAA,CAAQ,CAACc,CAAAA,CAASC,CAAAA,GAAW,CACtC,IAAMd,CAAAA,CAAM,IAAIC,GAAAA,CAAIkB,EAAMtB,CAAAA,CAAO,MAAM,EACjCwB,CAAAA,CAAUrB,CAAAA,CAAI,WAAa,QAAA,CAC3BsB,CAAAA,CAAQD,EAAUE,OAAAA,CAAeC,SAAAA,CAEjCC,EAAqC,CAAE,GAAGL,CAAQ,CAAA,CACxDK,CAAAA,CAAW,KAAUzB,CAAAA,CAAI,IAAA,CACzB,OAAOyB,CAAAA,CAAW,WAElB,IAAMb,CAAAA,CAAMU,EACV,CACE,QAAA,CAAUtB,EAAI,QAAA,CACd,IAAA,CAAMA,EAAI,IAAA,GAASqB,CAAAA,CAAU,IAAM,EAAA,CAAA,CACnC,IAAA,CAAMrB,EAAI,QAAA,CAAWA,CAAAA,CAAI,OACzB,MAAA,CAAAkB,CAAAA,CACA,OAAA,CAASO,CACX,EACCC,CAAAA,EAAQ,CACP,IAAMX,CAAAA,CAAmB,GACzBW,CAAAA,CAAI,EAAA,CAAG,OAASV,CAAAA,EAAkBD,CAAAA,CAAO,KAAKC,CAAK,CAAC,EACpDU,CAAAA,CAAI,EAAA,CAAG,MAAO,IAAM,CAClB,IAAMC,CAAAA,CAAqC,EAAC,CAC5C,IAAA,GAAW,CAACC,CAAAA,CAAGC,CAAC,IAAK,MAAA,CAAO,OAAA,CAAQH,EAAI,OAAO,CAAA,CACzCG,IAAGF,CAAAA,CAAWC,CAAC,EAAI,KAAA,CAAM,OAAA,CAAQC,CAAC,CAAA,CAAIA,CAAAA,CAAE,IAAA,CAAK,IAAI,EAAIA,CAAAA,CAAAA,CAE3DhB,CAAAA,CAAQ,CACN,UAAA,CAAYa,CAAAA,CAAI,YAAc,GAAA,CAC9B,OAAA,CAASC,EACT,IAAA,CAAM,MAAA,CAAO,OAAOZ,CAAM,CAC5B,CAAC,EACH,CAAC,EACDW,CAAAA,CAAI,EAAA,CAAG,OAAA,CAASZ,CAAM,EACxB,CACF,CAAA,CAEAF,EAAI,EAAA,CAAG,OAAA,CAASE,CAAM,CAAA,CAClBf,CAAAA,EAAMa,EAAI,KAAA,CAAMb,CAAI,EACxBa,CAAAA,CAAI,GAAA,GACN,CAAC,CACH,CAMA,SAASkB,CAAAA,CACPjC,CAAAA,CACAsB,CAAAA,CACAC,EACArB,CAAAA,CACAgC,CAAAA,CACA3B,EACA4B,CAAAA,CACM,CACN,IAAMhC,CAAAA,CAAM,IAAIC,IAAIkB,CAAAA,CAAMtB,CAAAA,CAAO,MAAM,CAAA,CACjCwB,CAAAA,CAAUrB,EAAI,QAAA,GAAa,QAAA,CAC3BsB,EAAQD,CAAAA,CAAUE,OAAAA,CAAeC,SAAAA,CAEjCC,CAAAA,CAAqC,CAAE,GAAGL,CAAQ,EACxDK,CAAAA,CAAW,IAAA,CAAUzB,EAAI,IAAA,CACzB,OAAOyB,EAAW,UAAA,CAElB,IAAMb,EAAMU,CAAAA,CACV,CACE,SAAUtB,CAAAA,CAAI,QAAA,CACd,KAAMA,CAAAA,CAAI,IAAA,GAASqB,CAAAA,CAAU,GAAA,CAAM,IACnC,IAAA,CAAMrB,CAAAA,CAAI,SAAWA,CAAAA,CAAI,MAAA,CACzB,OAAQ,MAAA,CACR,OAAA,CAASyB,CACX,CAAA,CACCC,CAAAA,EAAQ,CACPK,CAAAA,CAAU,SAAA,CAAUL,EAAI,UAAA,EAAc,GAAA,CAAKA,EAAI,OAAO,CAAA,CAEtD,IAAIO,CAAAA,CAAgC,KAEpCP,CAAAA,CAAI,EAAA,CAAG,OAASV,CAAAA,EAAkB,CAChCe,EAAU,KAAA,CAAMf,CAAK,EAGrB,IAAMkB,CAAAA,CAAOlB,EAAM,QAAA,CAAS,OAAO,EACnC,IAAA,IAAWmB,CAAAA,IAAQD,EAAK,KAAA,CAAM;AAAA,CAAI,CAAA,CAAG,CACnC,GAAI,CAACC,EAAK,UAAA,CAAW,QAAQ,CAAA,CAAG,SAChC,IAAMC,CAAAA,CAAUD,EAAK,KAAA,CAAM,CAAC,EAAE,IAAA,EAAK,CACnC,GAAIC,CAAAA,GAAY,QAAA,CAChB,GAAI,CACF,IAAMC,CAAAA,CAAS,KAAK,KAAA,CAAMD,CAAO,EAC7BC,CAAAA,CAAO,KAAA,GACTJ,EAAa,CACX,WAAA,CAAaI,CAAAA,CAAO,KAAA,CAAM,aAAA,EAAiB,CAAA,CAC3C,aAAcA,CAAAA,CAAO,KAAA,CAAM,iBAAA,EAAqB,CAAA,CAChD,eAAA,CAAiBA,CAAAA,CAAO,MAAM,yBAAA,EAA2B,gBAAA,EAAoB,CAAA,CAC7E,YAAA,CAAcA,CAAAA,CAAO,KAAA,CAAM,uBAAuB,aAAA,EAAiB,CACrE,GAEJ,CAAA,KAAQ,CAER,CACF,CACF,CAAC,CAAA,CAEDX,CAAAA,CAAI,EAAA,CAAG,KAAA,CAAO,IAAM,CAElB,GADAK,EAAU,GAAA,EAAI,CACVE,EAAY,CACd,IAAM3B,CAAAA,CAAY,IAAA,CAAK,GAAA,EAAI,CAAI0B,EACzBlC,CAAAA,CAAQK,CAAAA,CAAWN,EAAQO,CAAAA,CAAO6B,CAAAA,CAAY3B,CAAS,CAAA,CAC7DV,CAAAA,CAAUC,CAAAA,CAAQC,CAAK,EACzB,CACF,CAAC,CAAA,CAED4B,CAAAA,CAAI,EAAA,CAAG,OAAA,CAAUxB,CAAAA,EAAQ,CACvB,QAAQ,MAAA,CAAO,KAAA,CACb,CAAA,0CAAA,EAA6CA,CAAAA,CAAI,OAAO;AAAA,CAC1D,EACA6B,CAAAA,CAAU,GAAA,GACZ,CAAC,EACH,CACF,CAAA,CAEAnB,CAAAA,CAAI,EAAA,CAAG,OAAA,CAAUV,GAAQ,CACvB,OAAA,CAAQ,OAAO,KAAA,CACb,CAAA,2CAAA,EAA8CA,EAAI,OAAO;AAAA,CAC3D,EACA6B,CAAAA,CAAU,SAAA,CAAU,GAAA,CAAK,CAAE,eAAgB,kBAAmB,CAAC,CAAA,CAC/DA,CAAAA,CAAU,IAAI,IAAA,CAAK,SAAA,CAAU,CAAE,KAAA,CAAO,CAAE,OAAA,CAAS,aAAc,CAAE,CAAC,CAAC,EACrE,CAAC,CAAA,CAEDnB,CAAAA,CAAI,MAAMb,CAAI,CAAA,CACda,CAAAA,CAAI,GAAA,GACN,CAMA,eAAe0B,EACbzC,CAAAA,CACAe,CAAAA,CACAc,EACe,CACf,IAAMP,CAAAA,CAAOP,CAAAA,CAAI,KAAO,GAAA,CAClBM,CAAAA,CAASN,CAAAA,CAAI,MAAA,EAAU,MAG7B,GAAIO,CAAAA,GAAS,SAAA,EAAaD,CAAAA,GAAW,MAAO,CAC1CQ,CAAAA,CAAI,SAAA,CAAU,GAAA,CAAK,CAAE,cAAA,CAAgB,kBAAmB,CAAC,CAAA,CACzDA,EAAI,GAAA,CAAI,IAAA,CAAK,SAAA,CAAU,CAAE,OAAQ,IAAA,CAAM,MAAA,CAAQ7B,CAAAA,CAAO,MAAO,CAAC,CAAC,CAAA,CAC/D,MACF,CAGA,GAAIsB,IAAS,sBAAA,EAA0BD,CAAAA,GAAW,MAAA,CAAQ,CACxD,IAAMc,CAAAA,CAAY,IAAA,CAAK,GAAA,EAAI,CACrBO,EAAU,MAAM5B,CAAAA,CAASC,CAAG,CAAA,CAC9ByB,EAEJ,GAAI,CACFA,CAAAA,CAAS,IAAA,CAAK,MAAME,CAAAA,CAAQ,QAAA,CAAS,OAAO,CAAC,EAC/C,CAAA,KAAQ,CACNb,CAAAA,CAAI,SAAA,CAAU,IAAK,CAAE,cAAA,CAAgB,kBAAmB,CAAC,EACzDA,CAAAA,CAAI,GAAA,CAAI,KAAK,SAAA,CAAU,CAAE,MAAO,CAAE,OAAA,CAAS,mBAAoB,CAAE,CAAC,CAAC,CAAA,CACnE,MACF,CAEA,IAAMtB,CAAAA,CAAQ,MAAA,CAAOiC,CAAAA,CAAO,KAAA,EAAY,SAAS,CAAA,CAC3CG,CAAAA,CAAWH,CAAAA,CAAO,MAAA,GAAc,KAGlCG,CAAAA,EAAY,CAACH,CAAAA,CAAO,cAAA,GACtBA,EAAO,cAAA,CAAoB,CAAE,aAAA,CAAe,IAAK,GAGnD,IAAMjB,CAAAA,CAAkC,CACtC,cAAA,CAAgB,kBAClB,CAAA,CAEMqB,CAAAA,CAAO7B,EAAI,OAAA,CAAQ,aAAA,CACrB6B,IAAMrB,CAAAA,CAAQ,aAAA,CAAmB,KAAA,CAAM,OAAA,CAAQqB,CAAI,CAAA,CAAIA,CAAAA,CAAK,CAAC,CAAA,CAAIA,GAErE,IAAMC,CAAAA,CAAe,MAAA,CAAO,IAAA,CAAK,KAAK,SAAA,CAAUL,CAAM,CAAA,CAAG,OAAO,EAGhE,GAFAjB,CAAAA,CAAQ,gBAAgB,CAAA,CAAI,OAAOsB,CAAAA,CAAa,MAAM,CAAA,CAElDF,CAAAA,CACFV,EAAiBjC,CAAAA,CAAQsB,CAAAA,CAAMC,CAAAA,CAASsB,CAAAA,CAAchB,EAAKtB,CAAAA,CAAO4B,CAAS,OAE3E,GAAI,CACF,IAAMW,CAAAA,CAAW,MAAM1B,CAAAA,CAAepB,CAAAA,CAAQ,OAAQsB,CAAAA,CAAMC,CAAAA,CAASsB,CAAY,CAAA,CAC3EpC,EAAY,IAAA,CAAK,GAAA,EAAI,CAAI0B,CAAAA,CAG/B,GAAI,CACF,IAAMY,CAAAA,CAAW,IAAA,CAAK,MAAMD,CAAAA,CAAS,IAAA,CAAK,QAAA,CAAS,OAAO,CAAC,CAAA,CAC3D,GAAIC,CAAAA,CAAS,KAAA,CAAO,CAClB,IAAMvC,CAAAA,CAAoB,CACxB,WAAA,CAAauC,EAAS,KAAA,CAAM,aAAA,EAAiB,EAC7C,YAAA,CAAcA,CAAAA,CAAS,MAAM,iBAAA,EAAqB,CAAA,CAClD,eAAA,CAAiBA,CAAAA,CAAS,MAAM,yBAAA,EAA2B,gBAAA,EAAoB,CAAA,CAC/E,YAAA,CAAcA,EAAS,KAAA,CAAM,qBAAA,EAAuB,aAAA,EAAiB,CACvE,EACM9C,CAAAA,CAAQK,CAAAA,CAAWN,CAAAA,CAAQO,CAAAA,CAAOC,EAAOC,CAAS,CAAA,CACxDV,CAAAA,CAAUC,CAAAA,CAAQC,CAAK,EACzB,CACF,CAAA,KAAQ,CAER,CAGA,IAAM2B,CAAAA,CAAqC,EAAC,CAC5C,OAAW,CAACG,CAAAA,CAAGC,CAAC,CAAA,GAAK,MAAA,CAAO,QAAQc,CAAAA,CAAS,OAAO,CAAA,CAC9Cf,CAAAA,GAAM,sBAAqBH,CAAAA,CAAWG,CAAC,CAAA,CAAIC,CAAAA,CAAAA,CAEjDJ,EAAW,gBAAgB,CAAA,CAAI,MAAA,CAAOkB,CAAAA,CAAS,KAAK,MAAM,CAAA,CAC1DjB,CAAAA,CAAI,SAAA,CAAUiB,EAAS,UAAA,CAAYlB,CAAU,CAAA,CAC7CC,CAAAA,CAAI,IAAIiB,CAAAA,CAAS,IAAI,EACvB,CAAA,MAASzC,EAAK,CACZ,OAAA,CAAQ,MAAA,CAAO,KAAA,CACb,sCAAsCA,CAAAA,YAAe,KAAA,CAAQA,EAAI,OAAA,CAAU,MAAA,CAAOA,CAAG,CAAC;AAAA,CACxF,EACAwB,CAAAA,CAAI,SAAA,CAAU,GAAA,CAAK,CAAE,eAAgB,kBAAmB,CAAC,CAAA,CACzDA,CAAAA,CAAI,IAAI,IAAA,CAAK,SAAA,CAAU,CAAE,KAAA,CAAO,CAAE,QAAS,aAAc,CAAE,CAAC,CAAC,EAC/D,CAEF,MACF,CAGA,GAAIP,IAAS,YAAA,EAAgBD,CAAAA,GAAW,KAAA,CAAO,CAC7C,IAAME,CAAAA,CAAkC,GAClCqB,CAAAA,CAAO7B,CAAAA,CAAI,QAAQ,aAAA,CACrB6B,CAAAA,GAAMrB,CAAAA,CAAQ,aAAA,CAAmB,MAAM,OAAA,CAAQqB,CAAI,CAAA,CAAIA,CAAAA,CAAK,CAAC,CAAA,CAAIA,CAAAA,CAAAA,CAErE,GAAI,CACF,IAAME,CAAAA,CAAW,MAAM1B,EAAepB,CAAAA,CAAQ,KAAA,CAAOsB,EAAMC,CAAAA,CAAS,IAAI,CAAA,CACxEM,CAAAA,CAAI,UAAUiB,CAAAA,CAAS,UAAA,CAAYA,EAAS,OAAO,CAAA,CACnDjB,EAAI,GAAA,CAAIiB,CAAAA,CAAS,IAAI,EACvB,MAAQ,CACNjB,CAAAA,CAAI,UAAU,GAAA,CAAK,CAAE,eAAgB,kBAAmB,CAAC,CAAA,CACzDA,CAAAA,CAAI,IAAI,IAAA,CAAK,SAAA,CAAU,CAAE,KAAA,CAAO,CAAE,OAAA,CAAS,aAAc,CAAE,CAAC,CAAC,EAC/D,CACA,MACF,CAGAA,CAAAA,CAAI,UAAU,GAAA,CAAK,CAAE,cAAA,CAAgB,kBAAmB,CAAC,CAAA,CACzDA,CAAAA,CAAI,GAAA,CAAI,IAAA,CAAK,UAAU,CAAE,KAAA,CAAO,CAAE,OAAA,CAAS,WAAY,CAAE,CAAC,CAAC,EAC7D,CAMA,SAASmB,CAAAA,EAAa,CACpB,IAAMrD,CAAAA,CAAO,QAAQ,IAAA,CAAK,KAAA,CAAM,CAAC,CAAA,CAAA,CAE7BG,EAAQH,CAAAA,CAAM,QAAQ,CAAA,EAAKG,CAAAA,CAAQH,EAAM,IAAI,CAAA,IAC/C,QAAQ,MAAA,CAAO,KAAA,CACb,CACE,8EAAA,CACA,EAAA,CACA,QAAA,CACA,6DAAA,CACA,GACA,UAAA,CACA,wEAAA,CACA,+EACA,mFAAA,CACA,4GAAA,CACA,uEACA,0GAAA,CACA,gDAAA,CACA,EAAA,CACA,cAAA,CACA,+BACA,wDAAA,CACA,EAAA,CACA,kCACA,wDAAA,CACA,EACF,EAAE,IAAA,CAAK;AAAA,CAAI,CACb,CAAA,CACA,OAAA,CAAQ,IAAA,CAAK,CAAC,CAAA,CAAA,CAGhB,IAAMsD,CAAAA,CACJvD,CAAAA,CAAQC,CAAAA,CAAM,WAAW,CAAA,EAAK,OAAA,CAAQ,GAAA,CAAI,kBAAA,CACtCuD,CAAAA,CACJxD,CAAAA,CAAQC,CAAAA,CAAM,WAAW,CAAA,EAAK,OAAA,CAAQ,GAAA,CAAI,qBAAA,CACtCwD,CAAAA,CAAO,MAAA,CACXzD,CAAAA,CAAQC,CAAAA,CAAM,QAAQ,CAAA,EAAK,OAAA,CAAQ,GAAA,CAAI,qBAAA,EAA4B,MACrE,CAAA,CACMyD,CAAAA,CACJ1D,CAAAA,CAAQC,CAAAA,CAAM,UAAU,CAAA,EACxB,OAAA,CAAQ,GAAA,CAAI,uBAAA,EACZ,wBAAA,CACI0D,CAAAA,CACJ3D,CAAAA,CAAQC,CAAAA,CAAM,cAAc,CAAA,EAAK,aAAA,CAC7B2D,CAAAA,CACJ5D,CAAAA,CAAQC,CAAAA,CAAM,YAAY,CAAA,EAC1B,OAAA,CAAQ,GAAA,CAAI,mBAAA,EACZ,qDAAA,CAEGsD,CAAAA,GACH,OAAA,CAAQ,OAAO,KAAA,CACb,CAAA;AAAA,CACF,CAAA,CACA,QAAQ,IAAA,CAAK,CAAC,GAGXC,CAAAA,GACH,OAAA,CAAQ,OAAO,KAAA,CACb,CAAA;AAAA,CACF,CAAA,CACA,QAAQ,IAAA,CAAK,CAAC,GAGhB,IAAMlD,CAAAA,CAAsB,CAC1B,MAAA,CAAAiD,CAAAA,CACA,UAAAC,CAAAA,CACA,IAAA,CAAAC,EACA,MAAA,CAAAC,CAAAA,CACA,UAAAC,CAAAA,CACA,QAAA,CAAAC,CACF,CAAA,CAEMC,CAAAA,CAASC,aAAa,CAACzC,CAAAA,CAAKc,IAAQ,CACxCY,CAAAA,CAAczC,EAAQe,CAAAA,CAAKc,CAAG,EAAE,KAAA,CAAOxB,CAAAA,EAAiB,CACtD,OAAA,CAAQ,MAAA,CAAO,MACb,CAAA,oCAAA,EAAuCA,CAAAA,YAAe,MAAQA,CAAAA,CAAI,OAAA,CAAU,MAAA,CAAOA,CAAG,CAAC;AAAA,CACzF,CAAA,CACKwB,CAAAA,CAAI,WAAA,EACPA,CAAAA,CAAI,UAAU,GAAA,CAAK,CAAE,cAAA,CAAgB,kBAAmB,CAAC,CAAA,CAE3DA,CAAAA,CAAI,GAAA,CAAI,KAAK,SAAA,CAAU,CAAE,KAAA,CAAO,CAAE,OAAA,CAAS,uBAAwB,CAAE,CAAC,CAAC,EACzE,CAAC,EACH,CAAC,EAEK4B,CAAAA,CAAW,IAAM,CACrB,OAAA,CAAQ,OAAO,KAAA,CAAM,CAAA;AAAA,CAAuC,CAAA,CAC5DF,CAAAA,CAAO,KAAA,EAAM,CACb,OAAA,CAAQ,IAAA,CAAK,CAAC,EAChB,CAAA,CAEA,OAAA,CAAQ,EAAA,CAAG,QAAA,CAAUE,CAAQ,CAAA,CAC7B,OAAA,CAAQ,EAAA,CAAG,SAAA,CAAWA,CAAQ,CAAA,CAE9BF,CAAAA,CAAO,MAAA,CAAOJ,CAAAA,CAAM,IAAM,CACxB,OAAA,CAAQ,MAAA,CAAO,KAAA,CACb,CAAA,iDAAA,EAAoDA,CAAI;AAAA,iCAAA,EAClBC,CAAM;AAAA,CAC9C,EACF,CAAC,EACH,CAEAJ,CAAAA,EAAK","file":"index.js","sourcesContent":["#!/usr/bin/env node\n// ---------------------------------------------------------------------------\n// @neurameter/proxy – OpenAI-compatible proxy server\n// ---------------------------------------------------------------------------\n// Usage:\n// neurameter-proxy --api-key nm_xxx --project proj_xxx\n// npx @neurameter/proxy --api-key nm_xxx --project proj_xxx\n//\n// Env vars (fallbacks):\n// NEURAMETER_API_KEY, NEURAMETER_PROJECT_ID, NEURAMETER_PROXY_PORT,\n// NEURAMETER_PROXY_TARGET, NEURAMETER_ENDPOINT\n// ---------------------------------------------------------------------------\n\nimport { createServer, type IncomingMessage, type ServerResponse } from 'node:http';\nimport { request as httpsRequest } from 'node:https';\nimport { request as httpRequest } from 'node:http';\nimport { URL } from 'node:url';\nimport { getModelPricing, calculateCostMicrodollars } from '@neurameter/core';\nimport type { TokenUsage } from '@neurameter/core';\n\n// ---------------------------------------------------------------------------\n// Argument parsing (lightweight — no external deps)\n// ---------------------------------------------------------------------------\n\nfunction getFlag(args: string[], flag: string): string | undefined {\n const idx = args.indexOf(flag);\n if (idx === -1 || idx + 1 >= args.length) return undefined;\n return args[idx + 1];\n}\n\nfunction hasFlag(args: string[], flag: string): boolean {\n return args.includes(flag);\n}\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ninterface ProxyConfig {\n apiKey: string;\n projectId: string;\n port: number;\n target: string;\n agentName: string;\n endpoint: string;\n}\n\n// ---------------------------------------------------------------------------\n// Event sending (async, never blocks response)\n// ---------------------------------------------------------------------------\n\nfunction sendEvent(config: ProxyConfig, event: Record<string, unknown>): void {\n const body = JSON.stringify({ events: [event] });\n const url = new URL('/v1/events', config.endpoint);\n\n fetch(url.toString(), {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${config.apiKey}`,\n },\n body,\n }).catch((err: unknown) => {\n process.stderr.write(\n `[neurameter-proxy] Failed to send event: ${err instanceof Error ? err.message : String(err)}\\n`,\n );\n });\n}\n\nfunction buildEvent(\n config: ProxyConfig,\n model: string,\n usage: TokenUsage,\n latencyMs: number,\n): Record<string, unknown> {\n const pricing = getModelPricing('openai', model);\n const costMicrodollars = pricing\n ? calculateCostMicrodollars(usage, pricing)\n : 0;\n\n return {\n eventId: crypto.randomUUID(),\n timestamp: new Date().toISOString(),\n traceId: crypto.randomUUID(),\n spanId: crypto.randomUUID(),\n agentName: config.agentName,\n provider: 'openai',\n model,\n inputTokens: usage.inputTokens,\n outputTokens: usage.outputTokens,\n reasoningTokens: usage.reasoningTokens ?? 0,\n cachedTokens: usage.cachedTokens ?? 0,\n costMicrodollars,\n latencyMs,\n orgId: '',\n projectId: config.projectId,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Body reading helper\n// ---------------------------------------------------------------------------\n\nfunction readBody(req: IncomingMessage): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on('data', (chunk: Buffer) => chunks.push(chunk));\n req.on('end', () => resolve(Buffer.concat(chunks)));\n req.on('error', reject);\n });\n}\n\n// ---------------------------------------------------------------------------\n// Forward request to upstream (OpenAI)\n// ---------------------------------------------------------------------------\n\nfunction forwardRequest(\n config: ProxyConfig,\n method: string,\n path: string,\n headers: Record<string, string>,\n body: Buffer | null,\n): Promise<{ statusCode: number; headers: Record<string, string>; body: Buffer }> {\n return new Promise((resolve, reject) => {\n const url = new URL(path, config.target);\n const isHttps = url.protocol === 'https:';\n const reqFn = isHttps ? httpsRequest : httpRequest;\n\n const outHeaders: Record<string, string> = { ...headers };\n outHeaders['host'] = url.host;\n delete outHeaders['connection'];\n\n const req = reqFn(\n {\n hostname: url.hostname,\n port: url.port || (isHttps ? 443 : 80),\n path: url.pathname + url.search,\n method,\n headers: outHeaders,\n },\n (res) => {\n const chunks: Buffer[] = [];\n res.on('data', (chunk: Buffer) => chunks.push(chunk));\n res.on('end', () => {\n const resHeaders: Record<string, string> = {};\n for (const [k, v] of Object.entries(res.headers)) {\n if (v) resHeaders[k] = Array.isArray(v) ? v.join(', ') : v;\n }\n resolve({\n statusCode: res.statusCode ?? 500,\n headers: resHeaders,\n body: Buffer.concat(chunks),\n });\n });\n res.on('error', reject);\n },\n );\n\n req.on('error', reject);\n if (body) req.write(body);\n req.end();\n });\n}\n\n// ---------------------------------------------------------------------------\n// Streaming forward (SSE passthrough with usage extraction)\n// ---------------------------------------------------------------------------\n\nfunction forwardStreaming(\n config: ProxyConfig,\n path: string,\n headers: Record<string, string>,\n body: Buffer,\n clientRes: ServerResponse,\n model: string,\n startTime: number,\n): void {\n const url = new URL(path, config.target);\n const isHttps = url.protocol === 'https:';\n const reqFn = isHttps ? httpsRequest : httpRequest;\n\n const outHeaders: Record<string, string> = { ...headers };\n outHeaders['host'] = url.host;\n delete outHeaders['connection'];\n\n const req = reqFn(\n {\n hostname: url.hostname,\n port: url.port || (isHttps ? 443 : 80),\n path: url.pathname + url.search,\n method: 'POST',\n headers: outHeaders,\n },\n (res) => {\n clientRes.writeHead(res.statusCode ?? 500, res.headers);\n\n let usageFound: TokenUsage | null = null;\n\n res.on('data', (chunk: Buffer) => {\n clientRes.write(chunk);\n\n // Parse SSE lines for usage\n const text = chunk.toString('utf-8');\n for (const line of text.split('\\n')) {\n if (!line.startsWith('data: ')) continue;\n const payload = line.slice(6).trim();\n if (payload === '[DONE]') continue;\n try {\n const parsed = JSON.parse(payload);\n if (parsed.usage) {\n usageFound = {\n inputTokens: parsed.usage.prompt_tokens ?? 0,\n outputTokens: parsed.usage.completion_tokens ?? 0,\n reasoningTokens: parsed.usage.completion_tokens_details?.reasoning_tokens ?? 0,\n cachedTokens: parsed.usage.prompt_tokens_details?.cached_tokens ?? 0,\n };\n }\n } catch {\n // not valid JSON, skip\n }\n }\n });\n\n res.on('end', () => {\n clientRes.end();\n if (usageFound) {\n const latencyMs = Date.now() - startTime;\n const event = buildEvent(config, model, usageFound, latencyMs);\n sendEvent(config, event);\n }\n });\n\n res.on('error', (err) => {\n process.stderr.write(\n `[neurameter-proxy] Upstream stream error: ${err.message}\\n`,\n );\n clientRes.end();\n });\n },\n );\n\n req.on('error', (err) => {\n process.stderr.write(\n `[neurameter-proxy] Upstream request error: ${err.message}\\n`,\n );\n clientRes.writeHead(502, { 'Content-Type': 'application/json' });\n clientRes.end(JSON.stringify({ error: { message: 'Bad gateway' } }));\n });\n\n req.write(body);\n req.end();\n}\n\n// ---------------------------------------------------------------------------\n// Request handler\n// ---------------------------------------------------------------------------\n\nasync function handleRequest(\n config: ProxyConfig,\n req: IncomingMessage,\n res: ServerResponse,\n): Promise<void> {\n const path = req.url ?? '/';\n const method = req.method ?? 'GET';\n\n // Health check\n if (path === '/health' && method === 'GET') {\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ status: 'ok', target: config.target }));\n return;\n }\n\n // POST /v1/chat/completions — the main proxy path\n if (path === '/v1/chat/completions' && method === 'POST') {\n const startTime = Date.now();\n const rawBody = await readBody(req);\n let parsed: Record<string, unknown>;\n\n try {\n parsed = JSON.parse(rawBody.toString('utf-8'));\n } catch {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: { message: 'Invalid JSON body' } }));\n return;\n }\n\n const model = String(parsed['model'] ?? 'unknown');\n const isStream = parsed['stream'] === true;\n\n // Auto-inject stream_options for usage reporting\n if (isStream && !parsed['stream_options']) {\n parsed['stream_options'] = { include_usage: true };\n }\n\n const headers: Record<string, string> = {\n 'content-type': 'application/json',\n };\n // Forward authorization header\n const auth = req.headers['authorization'];\n if (auth) headers['authorization'] = Array.isArray(auth) ? auth[0] : auth;\n\n const modifiedBody = Buffer.from(JSON.stringify(parsed), 'utf-8');\n headers['content-length'] = String(modifiedBody.length);\n\n if (isStream) {\n forwardStreaming(config, path, headers, modifiedBody, res, model, startTime);\n } else {\n try {\n const upstream = await forwardRequest(config, 'POST', path, headers, modifiedBody);\n const latencyMs = Date.now() - startTime;\n\n // Extract usage from response\n try {\n const respBody = JSON.parse(upstream.body.toString('utf-8'));\n if (respBody.usage) {\n const usage: TokenUsage = {\n inputTokens: respBody.usage.prompt_tokens ?? 0,\n outputTokens: respBody.usage.completion_tokens ?? 0,\n reasoningTokens: respBody.usage.completion_tokens_details?.reasoning_tokens ?? 0,\n cachedTokens: respBody.usage.prompt_tokens_details?.cached_tokens ?? 0,\n };\n const event = buildEvent(config, model, usage, latencyMs);\n sendEvent(config, event);\n }\n } catch {\n // response wasn't JSON, skip tracking\n }\n\n // Return upstream response as-is\n const outHeaders: Record<string, string> = {};\n for (const [k, v] of Object.entries(upstream.headers)) {\n if (k !== 'transfer-encoding') outHeaders[k] = v;\n }\n outHeaders['content-length'] = String(upstream.body.length);\n res.writeHead(upstream.statusCode, outHeaders);\n res.end(upstream.body);\n } catch (err) {\n process.stderr.write(\n `[neurameter-proxy] Upstream error: ${err instanceof Error ? err.message : String(err)}\\n`,\n );\n res.writeHead(502, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: { message: 'Bad gateway' } }));\n }\n }\n return;\n }\n\n // GET /v1/models — passthrough\n if (path === '/v1/models' && method === 'GET') {\n const headers: Record<string, string> = {};\n const auth = req.headers['authorization'];\n if (auth) headers['authorization'] = Array.isArray(auth) ? auth[0] : auth;\n\n try {\n const upstream = await forwardRequest(config, 'GET', path, headers, null);\n res.writeHead(upstream.statusCode, upstream.headers);\n res.end(upstream.body);\n } catch {\n res.writeHead(502, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: { message: 'Bad gateway' } }));\n }\n return;\n }\n\n // 404 for everything else\n res.writeHead(404, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: { message: 'Not found' } }));\n}\n\n// ---------------------------------------------------------------------------\n// Main\n// ---------------------------------------------------------------------------\n\nfunction main(): void {\n const args = process.argv.slice(2);\n\n if (hasFlag(args, '--help') || hasFlag(args, '-h')) {\n process.stderr.write(\n [\n 'neurameter-proxy — OpenAI-compatible proxy for NeuraMeter cost tracking',\n '',\n 'Usage:',\n ' neurameter-proxy --api-key <key> --project <id> [options]',\n '',\n 'Options:',\n ' --api-key <key> NeuraMeter API key (or NEURAMETER_API_KEY env)',\n ' --project <id> NeuraMeter project ID (or NEURAMETER_PROJECT_ID env)',\n ' --port <num> Listen port (default: 3100, or NEURAMETER_PROXY_PORT env)',\n ' --target <url> Upstream API URL (default: https://api.openai.com, or NEURAMETER_PROXY_TARGET env)',\n ' --agent-name <name> Agent name for events (default: proxy-agent)',\n ' --endpoint <url> Ingestion API URL (default: https://neurameter-ingestion.neurameter.workers.dev)',\n ' --help, -h Show this help message',\n '',\n 'Quick start:',\n ' # Start proxy on port 3100',\n ' neurameter-proxy --api-key nm_xxx --project proj_xxx',\n '',\n ' # Point your app at the proxy',\n ' OPENAI_BASE_URL=http://localhost:3100/v1 node app.js',\n '',\n ].join('\\n'),\n );\n process.exit(0);\n }\n\n const apiKey =\n getFlag(args, '--api-key') ?? process.env['NEURAMETER_API_KEY'];\n const projectId =\n getFlag(args, '--project') ?? process.env['NEURAMETER_PROJECT_ID'];\n const port = Number(\n getFlag(args, '--port') ?? process.env['NEURAMETER_PROXY_PORT'] ?? '3100',\n );\n const target =\n getFlag(args, '--target') ??\n process.env['NEURAMETER_PROXY_TARGET'] ??\n 'https://api.openai.com';\n const agentName =\n getFlag(args, '--agent-name') ?? 'proxy-agent';\n const endpoint =\n getFlag(args, '--endpoint') ??\n process.env['NEURAMETER_ENDPOINT'] ??\n 'https://neurameter-ingestion.neurameter.workers.dev';\n\n if (!apiKey) {\n process.stderr.write(\n 'Error: --api-key flag or NEURAMETER_API_KEY environment variable is required.\\n',\n );\n process.exit(1);\n }\n\n if (!projectId) {\n process.stderr.write(\n 'Error: --project flag or NEURAMETER_PROJECT_ID environment variable is required.\\n',\n );\n process.exit(1);\n }\n\n const config: ProxyConfig = {\n apiKey,\n projectId,\n port,\n target,\n agentName,\n endpoint,\n };\n\n const server = createServer((req, res) => {\n handleRequest(config, req, res).catch((err: unknown) => {\n process.stderr.write(\n `[neurameter-proxy] Unhandled error: ${err instanceof Error ? err.message : String(err)}\\n`,\n );\n if (!res.headersSent) {\n res.writeHead(500, { 'Content-Type': 'application/json' });\n }\n res.end(JSON.stringify({ error: { message: 'Internal server error' } }));\n });\n });\n\n const shutdown = () => {\n process.stderr.write('[neurameter-proxy] Shutting down...\\n');\n server.close();\n process.exit(0);\n };\n\n process.on('SIGINT', shutdown);\n process.on('SIGTERM', shutdown);\n\n server.listen(port, () => {\n process.stderr.write(\n `[neurameter-proxy] Listening on http://localhost:${port}\\n` +\n `[neurameter-proxy] Forwarding to ${target}\\n`,\n );\n });\n}\n\nmain();\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@neurameter/proxy",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "OpenAI-compatible proxy server for zero-code NeuraMeter cost tracking",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"bin": {
|
|
10
|
+
"neurameter-proxy": "dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"import": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"default": "./dist/index.js"
|
|
17
|
+
},
|
|
18
|
+
"require": {
|
|
19
|
+
"types": "./dist/index.d.cts",
|
|
20
|
+
"default": "./dist/index.cjs"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"files": ["dist"],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsup",
|
|
27
|
+
"dev": "tsup --watch",
|
|
28
|
+
"typecheck": "tsc --noEmit",
|
|
29
|
+
"test": "vitest run --passWithNoTests"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@neurameter/core": "^0.1.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^22.0.0",
|
|
36
|
+
"tsup": "^8.4.0",
|
|
37
|
+
"vitest": "^3.0.0"
|
|
38
|
+
},
|
|
39
|
+
"sideEffects": false,
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "https://github.com/neuria-dev/neurameter.git",
|
|
44
|
+
"directory": "packages/proxy"
|
|
45
|
+
},
|
|
46
|
+
"publishConfig": {
|
|
47
|
+
"access": "public"
|
|
48
|
+
}
|
|
49
|
+
}
|