Skip to content

feat: configurable base_url for all LLM providers#24

Closed
ricorna wants to merge 1 commit intospacedriveapp:mainfrom
ricorna:feat/base-url-infrastructure
Closed

feat: configurable base_url for all LLM providers#24
ricorna wants to merge 1 commit intospacedriveapp:mainfrom
ricorna:feat/base-url-infrastructure

Conversation

@ricorna
Copy link
Contributor

@ricorna ricorna commented Feb 18, 2026

Motivation

LLM providers increasingly offer multiple API surfaces — different endpoints, different formats, sometimes different credentials — for the same underlying models:

  • Z.ai (Zhipu): General API (api.z.ai/api/paas/v4) vs Coding Plan (api.z.ai/api/coding/paas/v4) — same key, different billing path
  • Moonshot/Kimi: Standard Moonshot API (api.moonshot.ai/v1) vs Kimi Coding Plan (api.kimi.com/coding/v1) — same key, entirely different domain
  • MiniMax: International (api.minimax.io) vs China mainland (api.minimaxi.com), OpenAI-compatible (/v1) vs Anthropic-compatible (/anthropic), standard API key vs Coding Plan key (sk-cp- prefix) — different keys, different domains, different API formats
  • OpenAI: Direct API vs Azure OpenAI — different keys, different domains
  • Any provider: Corporate proxies, observability wrappers (Helicone), self-hosted deployments, regional mirrors

Currently every provider hardcodes its URL. Users on alternate plans, behind proxies, or in different regions must fork the code to change an endpoint.

Huge thanks to @ZerGo0 for the research in #13 identifying the Z.ai, Kimi, and MiniMax coding plan endpoints and the need for this feature. This PR takes a complementary approach — making the base URL a configurable property of each existing provider rather than creating separate provider entries per endpoint variant. One provider = one API key field = one identity. The URL is a deployment choice, not a provider identity.

This also lays groundwork for future extensibility: custom provider instances, proxy routing, and arbitrary OpenAI/Anthropic-compatible endpoints can all be configured without code changes.

Changes

Backend

  • Consolidated 11 near-identical call_* methods into 2 generic helpers (call_openai_compatible, call_anthropic_compatible), removing ~260 lines of duplication
  • Added base_url (Option<String>) for every provider in LlmConfig
  • Env var support: {PROVIDER}_BASE_URL (e.g., ZHIPU_BASE_URL, OPENAI_BASE_URL)
  • TOML support: zhipu_base_url = "..." in [llm] section
  • env:VAR_NAME indirection works for base URLs, consistent with API keys
  • URL validation on update (rejects non-http(s) strings)
  • delete_provider cleans up base_url alongside the key
  • GET /providers returns base_urls map so the frontend can display current state

Frontend

  • New EndpointSelector component with declarative presets per provider + custom URL fallback
  • Presets for Z.ai (General / Coding Plan)
  • Providers without alternate endpoints show no selector (unchanged UX)
  • Adding a new preset for any provider is one line in ENDPOINT_PRESETS

Bug fixes discovered during refactor

  • opencode-zen vs opencode_zen key mismatch in frontend isConfigured — was always returning false
  • is_needs_setup only checked 3 of 12 providers — users with only Groq/Together/etc. configured were stuck in setup mode

Breaking Changes

None. All existing config, env vars, and defaults are preserved. Providers without a base_url configured behave identically to before.

How to Test

# Build
cargo build

# Set a custom base URL via env
ZHIPU_BASE_URL="https://api.z.ai/api/coding/paas/v4/chat/completions" cargo run

# Or via TOML
# [llm]
# zhipu_base_url = "https://api.z.ai/api/coding/paas/v4/chat/completions"

# Or via the web UI → Settings → Providers → Z.ai → Endpoint dropdown

Verified

Tested end-to-end with a Z.ai Coding Plan subscription. Set ZHIPU_BASE_URL to the Coding Plan endpoint — chat completions return successfully. Without the override, the same key against the general endpoint returns 429 "Insufficient balance" (no pay-per-token credits loaded). With the Coding Plan endpoint, the subscription billing kicks in and requests succeed. This is exactly the user experience the base_url feature is designed to solve: same key, different endpoint, works without touching code.

Diff stats

7 files changed, 367 insertions(+), 390 deletions(-)

Net reduction of 23 lines while adding the feature.

Deduplicate per-provider API call methods (call_openai, call_zhipu,
call_groq, etc.) into two generic methods — call_openai_compatible and
call_anthropic_compatible — parameterized by provider ID, display name,
and endpoint URL.

Add configurable base_url support for all providers: each provider can
now have its endpoint overridden via TOML config (e.g. zhipu_base_url),
env vars (e.g. ZHIPU_BASE_URL), or the env: reference syntax. The
frontend gains an EndpointSelector component with preset and custom URL
options, and the GET /providers response now includes any configured
base_url overrides.

Also: expand is_needs_setup to check all provider env vars, fix
isConfigured for hyphenated provider IDs, clean up delete_provider to
remove orphaned base_url entries, fix empty env vars (VAR="") being
treated as configured providers.
@ricorna
Copy link
Contributor Author

ricorna commented Feb 18, 2026

built this out when i realized that adding coding providers would mean and even increasing number of permutations that requires forking to add or change. This give ultimate flexibility out of the box (in theory if a provider made a new subscription product and endpoint tomorrow, this would allow a user to add that right away, custom), and lets maintainers add known defaults as they go.

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.

1 participant

Comments