Проксирует запросы к нескольким провайдерам по префиксам путей, принудительно добавляет/заменяет провайдерские заголовки и прозрачно поддерживает стриминг (SSE). Конфигурация — через config.json в рабочей директории.
🚀 Быстрый старт: см. QUICKSTART.md
mini-proxy/
├── server.mjs # Основной прокси-сервер
├── config.json # Конфигурация маршрутов и заголовков
├── test.env # Переменные окружения для тестов (пример)
├── test-auto.sh # Автоматический тест (bash)
├── test-simple.sh # Простой curl-тест (bash)
├── test-client.mjs # Node.js тестовый клиент
├── test.ps1 # PowerShell тестовый скрипт
├── README.md # Основная документация
├── QUICKSTART.md # Быстрый старт
└── README-TESTING.md # Детали тестирования
- Маршрутизация по префиксам пути (правило «самый длинный префикс»)
- Прозрачный стриминг (SSE) без буферизации (опционально)
- Впрыск и переопределение заголовков на таргет
- CORS и обработка
OPTIONS /healthzдля проверки состояния- Детальное логирование запросов и ответов
- Node.js 18+ (используется встроенный
fetchи Web Streams)
- Склонируйте/скопируйте проект с файлами
server.mjsиconfig.json. - При необходимости укажите переменные окружения, используемые в
config.json(см. ниже). - Запустите сервер:
# Windows PowerShell
$env:OPENROUTER_API_KEY="..."
$env:PERPLEXITY_API_KEY="..."
node server.mjs# bash/zsh
export OPENROUTER_API_KEY="..."
export PERPLEXITY_API_KEY="..."
node server.mjsПо умолчанию порт берётся из config.json (пример — 8787).
Пример:
{
"port": 8787,
"routes": {
"/openrouter": "openrouter",
"/perplexity": "perplexity"
},
"targets": {
"openrouter": {
"baseUrl": "https://openrouter.ai/api",
"headers": {
"Authorization": "Bearer ${env:OPENROUTER_API_KEY}",
"HTTP-Referer": "https://your-app.example",
"X-Title": "Your Tool"
}
},
"perplexity": {
"baseUrl": "https://api.perplexity.ai",
"headers": {
"Authorization": "Bearer ${env:PERPLEXITY_API_KEY}",
"X-Custom-Required": "must-have"
}
}
},
"cors": {
"origin": "*",
"methods": "*",
"headers": "*",
"exposeHeaders": ""
},
"stripRequestHeaders": ["authorization", "host", "content-length"]
}Пояснения:
routes: сопоставление префикса пути →targetId. Выбирается маршрут с наибольшим совпавшим префиксом. Сам префикс при форвардинге обрезается.targets[*].baseUrl: базовый URL провайдера.targets[*].headers: заголовки, которые принудительно добавляются/переопределяются для каждого запроса на этотtarget(в т.ч. обязательные провайдерские заголовки).targets[*].pathPrefix(опц.): дополнительный префикс пути для провайдера.${env:NAME}внутри значений подставляется из переменных окружения.cors.*: настройка CORS. По умолчанию — «разрешено всё».stripRequestHeaders: входящие заголовки, которые всегда удаляются перед форвардингом.
- Укажите базовый URL вашего инструмента на соответствующий префикс прокси.
- Пример для OpenRouter:
http://localhost:8787/openrouter - Пример для Perplexity:
http://localhost:8787/perplexity
- Пример для OpenRouter:
- Далее используйте обычные пути API, например
/v1/chat/completions.
Проверка здоровья:
curl http://localhost:8787/healthzОбычный (нестриминговый) запрос:
curl -X POST \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-4o-mini",
"messages": [{"role":"user","content":"Hello"}],
"stream": false
}' \
http://localhost:8787/openrouter/v1/chat/completionsСтриминговый запрос (SSE):
curl -N -X POST \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-4o-mini",
"messages": [{"role":"user","content":"Stream please"}],
"stream": true
}' \
http://localhost:8787/openrouter/v1/chat/completionsimport OpenAI from "openai";
const client = new OpenAI({
apiKey: "sk-ignored-by-proxy",
baseURL: "http://localhost:8787/openrouter/v1", // префикс + стандартный путь
});
const stream = await client.chat.completions.create({
model: "gpt-4o-mini",
messages: [{ role: "user", content: "Hello" }],
stream: true,
});
for await (const chunk of stream) {
process.stdout.write(chunk.choices?.[0]?.delta?.content || "");
}Примечание: ключ указывается фиктивный — реальные заголовки вставляются прокси из config.json.
- Прокси выбирает целевой
targetпо наибольшему совпавшему префиксу пути изroutes. - Исходные заголовки клиента фильтруются (удаляются hop-by-hop и
stripRequestHeaders), затем поверх накладываютсяtargets[*].headers. - Ответ провайдера возвращается как есть; hop-by-hop заголовки и
content-lengthпри стриме вычищаются. - При закрытии клиентского соединения исходящий запрос к провайдеру отменяется (
AbortController).
Windows (PowerShell):
$env:AGENT_ROUTER_TOKEN="your-key"
$env:OPENAI_MODEL="gpt-5"
node test-client.mjsWSL/Linux:
# Автоматический выбор метода (рекомендуется)
export AGENT_ROUTER_TOKEN="your-key"
export OPENAI_MODEL="gpt-5"
bash test-auto.sh
# Или напрямую Node.js клиент
node test-client.mjsСм. README-TESTING.md для подробностей.
- 404
route_not_found: для пути нет подходящего префикса; проверьтеroutes. - 502
bad_gatewayилиtarget_not_configured: ошибка сети, неверныйbaseUrl/ключ, либоtargetIdотсутствует вtargets. HPE_LF_EXPECTED: Windows line endings (CRLF) в переменных окружения. См. README-TESTING.md для решения.- Логи сервера печатаются в stdout при запуске (
[proxy] ...). - Для детальной отладки:
DEBUG_HEADERS=true node server.mjs.