sean-a-ward adds per-user OpenRouter/LiteLLM routing to the OpenAI provider

Three commits from sean-a-ward let Mike's OpenAI provider route to any OpenAI-compatible Chat Completions endpoint - OpenRouter, LiteLLM, LM Studio - both at the instance level via env vars and per user through the settings UI. Model IDs are remapped through a JSON map so Mike's internal names point at whatever the target provider expects.

infrastructureintegration

The core change is in backend/src/lib/llm/openai.ts. Previously, the file had a single hardcoded URL constant pointing at the Responses API. Now there's a base-URL resolution layer: openAIBaseUrl() reads OPENAI_BASE_URL from env or falls back to https://api.openai.com/v1, and useChatCompletions() returns true automatically when the base URL isn't the OpenAI default. Two streaming paths coexist - the original Responses API path and a new streamOpenAIChatCompletions that handles the chat/completions format, including multi-chunk tool-call argument assembly indexed by tool_calls[].index.

OPENAI_MODEL_MAP is a JSON object that remaps Mike's internal model names to provider-specific IDs. Setting it to {"gpt-5.5":"openai/gpt-4o","gpt-5.4-mini":"anthropic/claude-sonnet-4.5","gpt-5.4-nano":"google/gemini-2.5-flash-lite"} is enough to route all three choices through OpenRouter.

The second commit extends this to per-user configuration. Four new columns on public.user_api_keys (openai_base_url, openai_model_map, openai_http_referer, openai_app_title) store per-user overrides, written via a new PUT /user/openai-config route. Per-user values take precedence over env. The key-status check was also tightened: a provider now only shows as configured when the decrypted key value is non-empty - a row with an empty key no longer counts. A quiet behavior change that could affect users with empty-key rows.

A third commit adds a try-then-fallback query in userApiKeys.ts for deployments running the new code before the migration. If the SELECT on the new columns fails with an error mentioning "openai_", it falls back to the old column list. This buys a zero-downtime rollout window but the fallback depends on Postgres including the column name in the error message, which isn't guaranteed everywhere.

Three things to check before pulling this patch. First, the new columns need a migration on user_api_keys - the fallback in commit 9573a579 is a temporary bridge, not a permanent solution. Second, saveOpenAIProviderConfig inserts an empty-key placeholder row when no OpenAI entry exists yet; combined with the tightened status check, a user who configures only a base URL but no API key will still show OpenAI as unconfigured. Third, the Chat Completions path drops Responses-API-only features: previous_response_id threading and reasoning summaries silently stop working when a non-OpenAI base URL is active.

So what Worth a look if you want to run Mike against OpenRouter, LiteLLM, or a local model server. The layering is clean - env-only and per-user paths compose correctly, and the model ID remapping is flexible. Apply the `user_api_keys` migration properly rather than relying on the string-match fallback longer than necessary, and confirm whether the tightened key-status check will affect any existing users.

View this fork on GitHub →

Spotted something wrong? Or know the PR text has fresher detail than the writeup above?

Commits in this thread

3 commits from sean-a-ward/mike, oldest first. Source extracted verbatim from the harvested git log.

SHA Subject Author Date
800b5b39 feat(llm): support OpenAI-compatible endpoints Sean Ward 2026-05-20 ↗ GitHub
ae68cee5 feat(settings): configure OpenAI-compatible endpoints Sean Ward 2026-05-20 ↗ GitHub
9573a579 fix(settings): tolerate pending OpenAI config migration Sean Ward 2026-05-20 ↗ GitHub

Capture this thread into my fork

Download a single Markdown prompt that tells Claude how to port every commit above into your working tree — adapting paths and structure to match your repo. Run it via claude -p < capture-thread-513.md from inside the repo you want the changes in.

⬇ Download capture-thread-513.md