Skip to content

feat(providers): future-proof DeepSeek deepseek-chat/-reasoner retirement#4

Merged
Broccolito merged 4 commits into
mainfrom
feat/deepseek-alias-futureproofing
Jun 19, 2026
Merged

feat(providers): future-proof DeepSeek deepseek-chat/-reasoner retirement#4
Broccolito merged 4 commits into
mainfrom
feat/deepseek-alias-futureproofing

Conversation

@Broccolito

Copy link
Copy Markdown
Collaborator

Summary

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 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) 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, not 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 now 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. 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 DeclarativeProviderConfig field) 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 a deepseek.com host.

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:

  • alias table contents
  • case-insensitivity / subdomain matching
  • non-DeepSeek hosts yield no aliases
  • request-time rewrite of retired ids only
  • no-op when no alias table is present
test result: ok. 5 passed; 0 failed; 0 ignored

🤖 Generated with Claude Code

… 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.
@Broccolito Broccolito merged commit 2e7c784 into main Jun 19, 2026
2 checks passed
@Broccolito Broccolito deleted the feat/deepseek-alias-futureproofing branch June 20, 2026 08:42
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