feat(providers): future-proof DeepSeek deepseek-chat/-reasoner retirement#4
Merged
Merged
Conversation
… MATLAB
Modernize the `analyze` tool's tree-sitter stack and extend language coverage
from 8 to 13 languages, adding the four requested by users (C++, R, Julia,
MATLAB) plus C as a near-free addition alongside C++.
## Why
The analyzer was pinned to tree-sitter 0.21, whose `language()` binding API has
been dropped by every modern grammar release. That pin blocked adding new
languages: R/Julia/MATLAB only publish grammars for tree-sitter 0.22+, and the
required exact-version pins were fragile (a loose semver bump would silently
break the `language()` call). Bumping the runtime is the durable fix.
## Dependency bump
- tree-sitter 0.21 -> 0.26.
- All existing grammars moved to current releases. Two grammars are swapped for
actively-maintained projects that track modern tree-sitter:
- tree-sitter-kotlin -> tree-sitter-kotlin-ng (1.1)
- devgen-tree-sitter-swift -> tree-sitter-swift (0.7)
- New grammars: tree-sitter-cpp, tree-sitter-c, tree-sitter-r,
tree-sitter-julia, tree-sitter-matlab.
- Added streaming-iterator (the new QueryCursor::matches iteration model).
The seemingly-incompatible version requirements across grammars (MATLAB needs
ts 0.25, the Swift grammar caps at 0.23) are not a real conflict: every grammar
links the shared `tree-sitter-language 0.1` crate and only dev-depends on
tree-sitter, so a single tree-sitter 0.26 runtime drives all 13 grammars. No
vendoring required.
## API migration (parser.rs)
- `tree_sitter_X::language()` -> `tree_sitter_X::LANGUAGE.into()`.
- The three element/call/reference query loops migrated from a plain iterator to
the streaming-iterator API (`while let Some(m) = matches.next()`).
- `Node::child(i)` -> `Node::child(i as u32)` across existing language handlers
(go/ruby/rust) for the 0.26 signature.
## New language definitions
Each query was authored against real parse trees dumped from the grammars
(not guessed), with name-extraction handlers where the function name is not a
direct child of the function node:
- cpp.rs / c.rs: functions (incl. out-of-line `T::m()`), classes/structs,
includes; recursive `function_declarator` descent for caller attribution.
- r.rs: `<-` and `=` assignment-bound functions, `$` member calls; name read
from the enclosing assignment's lhs.
- julia.rs: long- and short-form functions, macros, structs/abstract/module,
using/import; signature descent for names.
- matlab.rs: functions and classdef (name is a direct child, no handler needed).
kotlin.rs was rewritten because tree-sitter-kotlin-ng changed node kinds
(import_header->import, type_identifier/simple_identifier->identifier).
lang.rs maps the new extensions (.jl, .R, .hh/.hxx); swift.rs queries were
unchanged (the new grammar shares devgen's node kinds).
## Testing
82/82 analyze tests pass. Adds 8 test files (22 tests):
- Per-language suites for C++, C, R, Julia, MATLAB covering element extraction,
call extraction, and call-graph attribution.
- Regression suites for the Kotlin and Swift grammar swaps.
- A cross-cutting language_support_test that locks in all 14 language ids
(parser construction + non-empty queries) and the file-extension mappings.
Generated with Claude Code
…viders
Add two new OpenAI-compatible LLM providers so users can configure and
select them anywhere a provider/model is chosen in BioRouter.
z.ai (id: zai)
- GLM family from Zhipu AI via the OpenAI-compatible endpoint
https://api.z.ai/api/paas/v4 (Bearer ZAI_API_KEY; override host with
ZAI_HOST). Verified live: the key authenticates and GET /models returns
200 (a chat call returns 429 "insufficient balance" only because the test
account is unfunded, not an auth error).
- Default model glm-4.6; known models span the GLM-4 and GLM-5 families.
Xiaomi MiMo (id: xiaomi_mimo)
- MiMo family via an OpenAI-compatible endpoint. Default host is the
Singapore Token-Plan endpoint; override with XIAOMI_MIMO_HOST for other
regions/tiers (Bearer XIAOMI_MIMO_API_KEY).
- Default model mimo-v2.5.
Propagation across every interface and selection surface
Every provider/model picker is registry-driven off
biorouter::providers::providers() (the factory), so registering the two
providers there makes them appear automatically in:
- daemon (biorouterd) via the /config/providers route
- CLI / TUI via configure_provider_dialog()
- GUI: Settings -> Providers grid, Switch Model modal, lead/worker
settings, knowledge-base ingestion model picker, onboarding
No surface needed a hardcoded-list edit; only display polish was wired.
Backend
- New providers/zai.rs and providers/xiaomi_mimo.rs (OpenAI-compatible,
streaming, modeled on xai.rs) with metadata + factory-registration unit
tests.
- Registered both in providers/mod.rs and providers/factory.rs; added to the
config-keys test there.
- auto_detect.rs: paste-an-API-key detection for both.
- model.rs: context-window entries for glm-* and mimo-*.
- Integration-test stubs in tests/providers.rs; scenario configs in
biorouter-cli.
Frontend
- providerOrdering.ts: priority entries so both sort sensibly under
Commercial Models (auto-classified; no special casing).
- configUtils.ts: config-key labels and provider env-var prefixes.
- Onboarding CommercialSetupCard lists z.ai and Xiaomi MiMo.
- No OpenAPI/generated-client changes: provider metadata is served at runtime
via the existing /config/providers route (generic ProviderDetails), so no
new schema types are generated.
Docs
- docs/zai-integration-checklist.md and
docs/xiaomi-mimo-integration-checklist.md: per-surface verification
checklists.
Tests: cargo test -p biorouter --lib providers:: green (187 passed, incl. the
new metadata/registration/config-keys tests); workspace builds.
… and pass The integration test for the xiaomi_mimo provider was silently skipping and, once unskipped, asserting behaviour MiMo does not exhibit. Two fixes: 1. Provider lookup name: test_provider() resolves the provider via create_with_named_model(&name.to_lowercase(), ..), so the first argument must lowercase to the registered id. It was "XiaomiMimo" -> "xiaomimimo", which does not match the registered "xiaomi_mimo", so every run skipped with "Unknown provider: xiaomimimo". Pass "xiaomi_mimo" so the test creates the real provider and exercises the live stack. 2. Context-length expectation: the shared suite expects an oversized prompt to return ProviderError::ContextLengthExceeded. MiMo (mimo-v2.5, ~1M-token window) instead silently truncates to its window (verified live: a ~1.3M-token prompt was accepted with input_tokens capped at 1,048,570) and returns Ok — the same behaviour Ollama already special-cases. Add xiaomi_mimo to that success-because-of-truncation branch. Verified live against the Singapore Token-Plan endpoint with a real key: the full suite (basic completion, tool/function calling, truncation) passes, plus the native-module unit tests (test_metadata_structure, test_registered_in_factory) and test_openai_compatible_providers_config_keys. Completes the cross-surface xiaomi_mimo integration from f8d0fd1.
…rement DeepSeek is retiring the legacy `deepseek-chat` and `deepseek-reasoner` model ids on 2026-07-24. Both have been thin aliases of DeepSeek-V4-Flash since the V4 launch, and after that date the API rejects them outright, which would break any saved config (or custom provider) still selecting those ids. Add a host-scoped model-id alias layer to the OpenAI-compatible provider so the transition is seamless and needs no user action: - `builtin_model_aliases(host)` maps `deepseek-chat -> deepseek-v4-flash` and `deepseek-reasoner -> deepseek-v4-flash` for any `*.deepseek.com` host (case-insensitive, covers subdomains and custom providers re-pointed at DeepSeek). Both map to Flash rather than Pro: Flash is what the aliases already resolve to today and has thinking enabled by default, so `deepseek-reasoner` behaviour is preserved with no surprise cost increase. - `OpenAiProvider` carries an optional alias map, populated from the request host in `from_custom_config`. `resolve_model()` rewrites the model id on the wire just before `complete_with_model` / `stream` build the request, and is a zero-allocation no-op on the common path (live ids pass through). Keying on the API host (rather than a new DeclarativeProviderConfig field) keeps the change self-contained: no OpenAPI/TS-client regeneration, and it also protects bespoke OpenAI-compatible providers pointed at DeepSeek. Covered by 5 unit tests: the alias table, case-insensitivity/subdomain matching, non-DeepSeek hosts yielding no aliases, the request-time rewrite of retired ids only, and the no-op when no alias table is present.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
DeepSeek is retiring the legacy
deepseek-chatanddeepseek-reasonermodel ids on 2026-07-24. Both have been thin aliases of DeepSeek-V4-Flash since the V4 launch, and after that date the DeepSeek API rejects them outright — which would break any saved BioRouter config (or custom OpenAI-compatible provider) still selecting those ids.This adds a host-scoped model-id alias layer to the OpenAI-compatible provider so the transition is seamless and requires no action from users.
What changed
builtin_model_aliases(host)mapsdeepseek-chat -> deepseek-v4-flashanddeepseek-reasoner -> deepseek-v4-flashfor any*.deepseek.comhost (case-insensitive; covers subdomains and custom providers re-pointed at DeepSeek).deepseek-reasonerbehaviour is preserved with no surprise cost increase.OpenAiProvidernow carries an optional alias map, populated from the request host infrom_custom_config.resolve_model()rewrites the model id on the wire just beforecomplete_with_model/streambuild the request. It's a zero-allocation no-op on the common path (live ids pass straight through).Why host-scoped instead of a config field
Keying on the API host (rather than a new
DeclarativeProviderConfigfield) keeps the change self-contained — no OpenAPI/TS-client regeneration — and it's semantically correct: the retirement is a fact about the DeepSeek API, so this also protects bespoke OpenAI-compatible providers pointed at adeepseek.comhost.Context
BioRouter's DeepSeek setup was already current with the V4 era (base URL,
deepseek-v4-pro/deepseek-v4-flash, 1M context, pricing). This PR only adds forward-compatibility for the alias cutoff; no model/pricing data changes were needed.Tests
5 new unit tests in
providers::openai::alias_tests, all passing:🤖 Generated with Claude Code