Skip to content

feat(llm): add custom providers and dynamic API routing - #36

Merged
jamiepine merged 9 commits into
spacedriveapp:mainfrom
yaukwan:setup-provider-detection
Feb 19, 2026
Merged

feat(llm): add custom providers and dynamic API routing#36
jamiepine merged 9 commits into
spacedriveapp:mainfrom
yaukwan:setup-provider-detection

Conversation

@yaukwan

@yaukwan yaukwan commented Feb 18, 2026

Copy link
Copy Markdown

Why

The current setup flow and provider routing still assume a fixed set of legacy keys (anthropic_key, openai_key, etc.). This makes setup-mode detection brittle and limits support for custom providers, custom base URLs, and multiple API protocols.

What Changed

1) Provider model and config loading

  • Added structured provider config to LlmConfig via providers: HashMap<String, ProviderConfig>.
  • Added ApiType (anthropic, openai_completions, openai_responses) and ProviderConfig (api_type, base_url, api_key, name).
  • Added TOML parsing support for custom providers under [llm.provider.<id>].
  • Added validation for provider IDs (length/characters) and provider base_url.
  • Normalized provider IDs to lowercase for stable lookup.

2) Backward compatibility + setup behavior

  • Legacy keys are still supported and auto-mapped into internal provider entries (anthropic, openai, openrouter).
  • Explicit custom provider definitions take precedence over auto-migrated legacy entries with the same provider ID.
  • load_from_env() now builds provider entries from environment keys (same behavior as TOML loading path).
  • needs_onboarding() now checks a broader set of legacy provider env keys and provider-related env signals so setup mode is more accurate.

3) Runtime routing and API integration

  • Refactored LlmManager to resolve provider data through get_provider() and dynamic get_api_key().
  • Refactored SpacebotModel to route by model prefix (<provider_id>/<model>) and select call path from ApiType.
  • Added OpenAI Responses API path support and parser/conversion flow.
  • Updated provider calls to use configured base_url instead of hardcoded endpoints.

4) API and docs updates

  • Updated provider status API to return a dynamic provider map (instead of fixed provider fields), with backward-compatible legacy keys included as defaults when absent.
  • Extended config docs with:
    • custom provider schema and examples
    • migration notes from legacy keys
    • supported API types and base URL guidance
    • routing examples using custom provider IDs
  • Updated .gitignore (test_config.rs, .idea).

Compatibility Notes

  • Existing configs using legacy keys continue to work.
  • Model names without provider prefix still default to anthropic.
  • New custom-provider model naming follows <provider_id>/<model>.

Validation

Added tests

  • Added/expanded src/config.rs tests for:
    • ApiType and provider config deserialization
    • provider table parsing + lowercase normalization
    • legacy key -> provider auto-migration
    • precedence of explicit provider config over legacy migration
    • onboarding detection behavior
    • env-only loading path populating both legacy fields and providers

Recommended checks for reviewers

cargo test config::tests
cargo test
cargo clippy --all-targets --all-features

@yaukwan
yaukwan marked this pull request as draft February 19, 2026 01:56
@yaukwan yaukwan changed the title Update setup mode provider detection feat(config,llm): add custom providers and dynamic API routing Feb 19, 2026
@yaukwan
yaukwan marked this pull request as ready for review February 19, 2026 02:24
@yaukwan yaukwan changed the title feat(config,llm): add custom providers and dynamic API routing feat(llm): add custom providers and dynamic API routing Feb 19, 2026
@yaukwan
yaukwan marked this pull request as draft February 19, 2026 03:03
@yaukwan
yaukwan marked this pull request as ready for review February 19, 2026 11:39
YuKuang added 8 commits February 19, 2026 20:22
- Add .idea directory to .gitignore for JetBrains IDE support
- Document custom provider configuration in config.mdx
- Add migration guide from legacy keys to custom providers
- Document all supported API types (anthropic, openai_completions, openai_responses)
- Add provider ID validation (length, invalid characters)
- Add base URL validation in config loading
- Extend needs_onboarding() to check all legacy env keys
- Add provider-specific env var detection
- Add tests for onboarding logic
- Add get_provider() method to retrieve provider config by ID
- Make config field public for API access
- Refactor get_api_key() to use provider config instead of hardcoded keys
- Remove hardcoded provider key matches in favor of dynamic lookup
- Extract provider ID from model name (e.g., 'my_openai/gpt-4o' -> 'my_openai')
- Route API calls based on provider config api_type field
- Add call_openai_responses() for OpenAI Responses API support
- Add convert_messages_to_openai_responses() message converter
- Add parse_openai_responses_response() response parser
- Update all provider calls to use dynamic base_url from config
- Add convert_image_openai_responses() for image handling in Responses API
@yaukwan
yaukwan force-pushed the setup-provider-detection branch from 35e6027 to 921c0b7 Compare February 19, 2026 13:02
@jamiepine
jamiepine merged commit e1c83cc into spacedriveapp:main Feb 19, 2026
rktmeister pushed a commit to rktmeister/spacebot that referenced this pull request Mar 11, 2026
feat(llm): add custom providers and dynamic API routing
pasogott pushed a commit to pasogott/spacebot that referenced this pull request Aug 15, 2026
Task spacedriveapp#36. Spawning with worktree_mode "create" provisioned or reused a
task-<number> worktree and passed its id to the worker link, but the
update that bound the worker to the task wrote only worker_id and
status. The task never learned its worktree: on this instance 1 of 33
tasks carried a worktree_id while five task worktrees sat on disk
unreferenced.

The bind now records worktree_id, and resolution consults the task's
binding before falling back to the task-<number> name. That makes the
persisted binding authoritative and the naming convention the
compatibility key for tasks that predate it, rather than the only
mechanism.

backfill_worktree_bindings reconnects existing tasks at startup from
worktrees the caller supplies, so the task store does not reach into the
project tables. The revision it writes states the binding was inferred
from the name rather than observed at provision time, which is what
keeps an inferred binding distinguishable from a real one. It skips
tasks that already have a binding, so a rename cannot steal one and a
second pass appends nothing.
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