This folder contains a lightweight Node.js reverse proxy server that forwards model-specific prompt payloads to Gemini and GitHub Models.
The server also serves static files from this folder for GET requests.
- Accepts
POSTrequests from browser or test clients - Forwards provider-shaped request bodies upstream
- Normalizes successful responses to
{ "text": "..." } - Normalizes upstream errors to
{ "error": "..." } - Handles CORS for browser clients
The proxy uses these values:
GEMINI_API_KEYGITHUB_TOKENPORToptional, defaults to3000GEMINI_MODELGemini model name used to build the Gemini upstream URLGEMINI_URLrequired Gemini upstream URL, typically defined in terms ofGEMINI_MODELandGEMINI_API_KEYGH_URLrequired GitHub Models upstream URL
start.sh sources .env.modelspecs if it exists, exports the variables above, prompts for missing API keys, and starts server.cjs.
Use .env.modelspecs.example as the tracked template, then create your local .env.modelspecs with real credentials.
Template file:
cp .env.modelspecs.example .env.modelspecsExample .env.modelspecs.example:
GEMINI_API_KEY=your_gemini_key
GITHUB_TOKEN=your_github_token
GEMINI_MODEL="gemini-2.5-flash"
GEMINI_URL="https://generativelanguage.googleapis.com/v1beta/models/${GEMINI_MODEL}:generateContent?key=${GEMINI_API_KEY}"
GH_URL="https://models.inference.ai.azure.com/chat/completions"
PORT=3000With that layout, the Gemini model name appears only once in the file, and GEMINI_URL is derived from it when the file is sourced by bash.
.env.modelspecs should stay untracked and contain your real keys. .env.modelspecs.example is the safe file to commit and document.
There are two ways to start the server locally. Both end up running node server.cjs — they differ only in how environment variables are supplied.
start.sh sources .env.modelspecs if it exists, exports all required variables, and prompts interactively for any that are still missing (e.g. API keys). This is the easiest path when you do not want to export variables manually.
Run it directly:
bash start.shOr via npm:
npm run start:devIf your environment variables are already exported in your shell session (e.g. via your shell profile or a separate env tool), you can start the server directly without the shell script:
npm startThe server will fail to reach upstream providers if the required variables are not already set — there is no interactive prompt in this path.
http://localhost:3000
POST /api/gempromptPOST /api/ghprompt
The proxy forwards the request body you send as-is to the upstream provider. It does not accept a generic { prompt, systemPrompt } body.
Clients should therefore send provider-shaped JSON:
- Gemini clients send a Gemini
generateContentstyle payload - GitHub Models clients send a chat-completions style payload
Current examples live in these files:
Successful responses are normalized to:
{ "text": "...model output..." }Errors are normalized to:
{ "error": "...message..." }The test suite is split into deterministic unit tests and opt-in live integration tests.
Run local logic only:
npm testThese tests do not call the network. They cover retry behavior, empty-response handling, rollback of failed user turns, and conversation-history updates. This is the CI-friendly layer because failures here usually mean local code regressed.
Run the real end-to-end path through the local proxy and upstream providers:
npm run test:liveYou can also target a deployed environment by overriding the test base URL:
TEST_BASE_URL=https://${AIPROXY_DEPLOYED_URL} npm run test:liveRequirements:
- the local proxy server is already running (via
bash start.sh,npm run start:dev, ornpm startwith env vars pre-set) - valid provider credentials are available
- upstream providers are reachable
These tests can skip when a provider returns a transient overload response such as Gemini high demand.
Run both layers:
npm run test:allThe original prompt test mixed two separate concerns:
- local request and conversation-state logic
- real external provider availability
That made failures ambiguous. A red test could mean broken local code, a stopped proxy, missing credentials, or upstream overload.
The split makes failures easier to interpret:
- unit tests answer: did local code break?
- live tests answer: does the full external system work right now?