Skip to content

Support OpenRouter-compatible relay base URLs across UI, API, and CLI#124

Open
xieli123-hub wants to merge 1 commit intoOpenLAIR:mainfrom
xieli123-hub:codex/openrouter-relay-base-url
Open

Support OpenRouter-compatible relay base URLs across UI, API, and CLI#124
xieli123-hub wants to merge 1 commit intoOpenLAIR:mainfrom
xieli123-hub:codex/openrouter-relay-base-url

Conversation

@xieli123-hub
Copy link
Copy Markdown

@xieli123-hub xieli123-hub commented Apr 4, 2026

Summary

This PR adds first-class support for OpenRouter-compatible relay / proxy endpoints so Dr. Claw can work with relay-based APIs without assuming the official OpenRouter base URL everywhere.

What Changed

  • centralize OpenRouter base URL normalization and official-host header handling
  • let Settings > OpenRouter verify and persist both the API key and a custom relay/base URL
  • pass the configured base URL through UI chat sessions, the external /api/agent flow, and dr-claw chat --base-url
  • fetch and cache model lists by base URL instead of assuming the official endpoint
  • add regression coverage for base URL parsing and CLI argument handling
  • document relay setup in .env, CLI usage, and the external API docs

Verification

pm.cmd run typecheck

pm.cmd run test -- server/tests/openrouter-config.test.mjs server/tests/cli.test.mjs

????

  • ?? PR ? OpenRouter ??????? / ????,???????????? OpenRouter ???
  • UI ??????????? /api/agent ??? CLI ?????? base URL?
  • ???????????? base URL ??,???? OpenRouter ? OpenRouter-compatible ?????
  • ?????????????,??????? OpenRouter ??????? API?

@xieli123-hub xieli123-hub changed the title Add OpenRouter relay base URL support Support OpenRouter-compatible relay base URLs across UI, API, and CLI Apr 4, 2026
Copy link
Copy Markdown
Collaborator

@Zhang-Henry Zhang-Henry left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

Clean central architecture — openrouterConfig.js normalizes base URLs and conditions provider-specific headers across all three callsites. The utility design is solid and well-tested. However, there are real issues to address before merging.


Blocking

1. SSRF via user-controlled baseUrl in the agent API route
server/routes/agent.js: The baseUrl from req.body is passed directly to queryOpenRouter, which uses it as the target of a fetch. Unlike /openrouter/verify-api-key (which validates via normalizeOpenRouterBaseUrl), the agent route performs no validation. A caller with a valid API key can direct the server to fetch any internal URL (http://169.254.169.254/, http://localhost:3000/admin, etc.) and have the response streamed back via WebSocket.

Fix: validate that the hostname is not a private/loopback/link-local address, or restrict baseUrl to a server-side env setting only.


Should Fix

2. Default base URL always written to .env on key save
server/routes/cli-auth.js (~L603): normalizeOpenRouterBaseUrl returns the official URL when req.body.baseUrl is undefined/empty. The code then unconditionally writes OPENROUTER_BASE_URL=https://openrouter.ai/api/v1 to .env and sets process.env.OPENROUTER_BASE_URL. Users who save an API key without filling in a relay URL get a surprise .env entry. Fix: only write/set OPENROUTER_BASE_URL when the caller explicitly supplied a non-empty value.

3. agentLoop uses baseUrl via closure, not parameter
server/cli-chat.js (~L412): agentLoop references baseUrl as a closure variable from startChat. If the function is ever called from elsewhere or moved, baseUrl will be undefined. The signature should explicitly receive baseUrl as a parameter.

4. .env parser is fragile
server/routes/cli-auth.js, upsertEnvValues: .split('=')[0] works for simple cases but doesn't handle edge cases like whitespace-prefixed keys ( OPENROUTER_API_KEY=...). Consider using a proper .env line-parse regex or a library like dotenv-edit.


Minor

  • Raw fetch error messages are echoed to the API caller in the catch block (~L631) — could leak internal URL info if a relay returns verbose errors. Consider sanitizing.
  • The filter logic for trailing blank lines in upsertEnvValues doesn't de-duplicate consecutive blank lines in the middle of the file.
  • Removing the module-level _modelsCache in ProviderSelectionEmptyState.tsx means every remount triggers an API call to /api/settings/openrouter-models. The server-side 30-minute cache absorbs this, but worth noting.

Positive Notes

  • The openrouterConfig.js separation of concerns (normalizeOpenRouterBaseUrl throws on bad input, getOpenRouterBaseUrl is safe/fallback, isOfficialOpenRouterBaseUrl, getOpenRouterProviderHeaders) is clean and testable.
  • isDirectExecution guard in server/cli.js is the right approach for ESM test imports.
  • closeDatabase addition to db.js and its use in tests is proper cleanup.
  • Model-list API fallback for flat JSON arrays vs { data: [...] } is good defensive coding.
  • Test coverage for openrouterConfig.js and cliArgs.js is thorough.

@Zhang-Henry
Copy link
Copy Markdown
Collaborator

@xieli123-hub

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants