feat: add Astraflow (ModelVerse) provider support - #1481
Conversation
- Add Astraflow (Global) and Astraflow CN provider configs with correct API endpoints and website URLs - Register astraflow modelSource strategy in providerRegistry - Add astraflow case in fetchProviderModelsByStrategy to filter out non-chat models (embedding, reranker, speech, tts, etc.)
📝 WalkthroughWalkthroughThis PR adds support for two new Astraflow LLM providers (Global and CN variants) by registering them in the configuration, extending the provider registry with type definitions, implementing model fetching with filtering logic, and adding visual icon mappings. Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (3)
src/main/presenter/llmProviderPresenter/providers/aiSdkProvider.ts (2)
916-931: Consider deduplicating withfetchDefaultOpenAIModels.The mapping step here is identical to
fetchDefaultOpenAIModels(Lines 572–586). You could collapse theastraflowbranch to filter-then-delegate, e.g.:case 'astraflow': { const models = await this.fetchDefaultOpenAIModels({ timeout: this.getModelFetchTimeout() }) return models.filter( (m) => !ASTRAFLOW_NON_CHAT_PATTERNS.some((p) => m.id.toLowerCase().includes(p)) ) }Keeps mapping logic in one place and avoids drift if defaults change later.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/presenter/llmProviderPresenter/providers/aiSdkProvider.ts` around lines 916 - 931, The astraflow branch duplicates the mapping logic from fetchDefaultOpenAIModels; replace the inline filter+map with a call to this.fetchDefaultOpenAIModels({ timeout: this.getModelFetchTimeout() }) and then filter the returned models by the appropriate non-chat pattern constant (e.g., NON_CHAT_PATTERNS or ASTRAFLOW_NON_CHAT_PATTERNS) using m.id.toLowerCase().includes(p) so all mapping remains in fetchDefaultOpenAIModels and only filtering is applied in the astraflow case.
902-931: HoistNON_CHAT_PATTERNSto a module-level constant and tighten the blacklist.A few observations on the new
astraflowbranch:
NON_CHAT_PATTERNSis allocated on every call tofetchProviderModelsByStrategy. Lift it to a module-levelconst(SCREAMING_SNAKE_CASE per coding guidelines) so it's instantiated once and easier to evolve.- The
-codexsubstring is broad — it will also drop any futurecodex-named chat/code variant that happens to contain-codex. Since Astraflow's/modelsreturns notypefield, a blacklist is pragmatic, but consider documenting the rationale or anchoring patterns (e.g.,codex-prefix //codexboundary) to minimize collateral filtering.uploadsandsuno-are unusual entries for a chat-filtering list; a brief inline comment with an example of the upstream id would help future maintainers.♻️ Proposed refactor
+const ASTRAFLOW_NON_CHAT_PATTERNS = [ + 'embedding', + 'reranker', + 'speech', + 'suno-', + 'whisper', + '-codex', + 'tts-', + 'uploads' +] + // ... case 'astraflow': { const response = await this.fetchOpenAIModelRecords({ timeout: this.getModelFetchTimeout() }) - const NON_CHAT_PATTERNS = [ - 'embedding', - 'reranker', - 'speech', - 'suno-', - 'whisper', - '-codex', - 'tts-', - 'uploads' - ] return response .filter((model) => { if (typeof model.id !== 'string') return false const lower = model.id.toLowerCase() - return !NON_CHAT_PATTERNS.some((p) => lower.includes(p)) + return !ASTRAFLOW_NON_CHAT_PATTERNS.some((p) => lower.includes(p)) })As per coding guidelines: "Constants must use SCREAMING_SNAKE_CASE naming".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/presenter/llmProviderPresenter/providers/aiSdkProvider.ts` around lines 902 - 931, Hoist the NON_CHAT_PATTERNS array out of the astraflow case into a module-level constant named NON_CHAT_MODEL_PATTERNS (SCREAMING_SNAKE_CASE) so it’s allocated once and reusable by fetchProviderModelsByStrategy; then tighten the blacklist entries (replace the broad '-codex' with a more specific pattern such as 'codex-' or a word-boundary style pattern, and prefer prefix/suffix anchoring instead of substring matches) and add a short inline comment documenting why 'uploads' and 'suno-' are filtered and showing an example upstream id; update the astraflow branch to reference the new constant (NON_CHAT_MODEL_PATTERNS) when filtering models.src/renderer/src/components/icons/ModelIcon.vue (1)
167-168: Redundantastraflow-cnentry iniconsmap.The icon lookup at Line 197-199 uses
modelIdLower.includes(key)and iteratesObject.keys(icons)in insertion order. Sinceastraflowis registered beforeastraflow-cnand is a substring of it,astraflow-cnwill never be matched — it's effectively dead. Both map to the same icon, so this is harmless, but consider dropping the duplicate entry for clarity.♻️ Suggested cleanup
astraflow: astraflowIcon, - 'astraflow-cn': astraflowIcon, default: defaultIcon🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/renderer/src/components/icons/ModelIcon.vue` around lines 167 - 168, Remove the redundant 'astraflow-cn' entry from the icons map in ModelIcon.vue: the icons object currently contains both astraflow and 'astraflow-cn', but the lookup logic (modelIdLower.includes(key) iterating Object.keys(icons)) will always match 'astraflow' first and never reach 'astraflow-cn'; delete the 'astraflow-cn' key (leaving astraflow: astraflowIcon) to eliminate the dead entry and keep the icons map unambiguous.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/main/presenter/llmProviderPresenter/providers/aiSdkProvider.ts`:
- Around line 916-931: The astraflow branch duplicates the mapping logic from
fetchDefaultOpenAIModels; replace the inline filter+map with a call to
this.fetchDefaultOpenAIModels({ timeout: this.getModelFetchTimeout() }) and then
filter the returned models by the appropriate non-chat pattern constant (e.g.,
NON_CHAT_PATTERNS or ASTRAFLOW_NON_CHAT_PATTERNS) using
m.id.toLowerCase().includes(p) so all mapping remains in
fetchDefaultOpenAIModels and only filtering is applied in the astraflow case.
- Around line 902-931: Hoist the NON_CHAT_PATTERNS array out of the astraflow
case into a module-level constant named NON_CHAT_MODEL_PATTERNS
(SCREAMING_SNAKE_CASE) so it’s allocated once and reusable by
fetchProviderModelsByStrategy; then tighten the blacklist entries (replace the
broad '-codex' with a more specific pattern such as 'codex-' or a word-boundary
style pattern, and prefer prefix/suffix anchoring instead of substring matches)
and add a short inline comment documenting why 'uploads' and 'suno-' are
filtered and showing an example upstream id; update the astraflow branch to
reference the new constant (NON_CHAT_MODEL_PATTERNS) when filtering models.
In `@src/renderer/src/components/icons/ModelIcon.vue`:
- Around line 167-168: Remove the redundant 'astraflow-cn' entry from the icons
map in ModelIcon.vue: the icons object currently contains both astraflow and
'astraflow-cn', but the lookup logic (modelIdLower.includes(key) iterating
Object.keys(icons)) will always match 'astraflow' first and never reach
'astraflow-cn'; delete the 'astraflow-cn' key (leaving astraflow: astraflowIcon)
to eliminate the dead entry and keep the icons map unambiguous.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 42774207-ec91-41a4-ba17-60d3c2c369f3
⛔ Files ignored due to path filters (2)
src/renderer/src/assets/llm-icons/astraflow.pngis excluded by!**/*.pngsrc/renderer/src/assets/llm-icons/astraflow.svgis excluded by!**/*.svg
📒 Files selected for processing (4)
src/main/presenter/configPresenter/providers.tssrc/main/presenter/llmProviderPresenter/providerRegistry.tssrc/main/presenter/llmProviderPresenter/providers/aiSdkProvider.tssrc/renderer/src/components/icons/ModelIcon.vue
|
LGTM |
Summary
astraflowas a newmodelSourcestrategy inproviderRegistry.tscase 'astraflow'infetchProviderModelsByStrategyto filter out non-chat models (embedding, reranker, speech, tts, codex, whisper) from the model listBackground
Astraflow (formerly ModelVerse) is UCloud's AI model service that provides access to models including Claude, DeepSeek, GPT-4o, etc. The service offers two regional endpoints:
https://api-us-ca.umodelverse.ai/v1(US/Canada node)https://api.modelverse.cn/v1(China node)Why filter models?
The Astraflow
/modelsendpoint returns all model types (chat, embedding, reranker, speech, image generation) without atypefield to distinguish them. DeepChat's model list should only show chat-compatible models, so we apply keyword-based filtering for theastraflowprovider.Test plan
claude-sonnet-4-6,deepseek-ai/DeepSeek-V3)🤖 Generated with Claude Code
Summary by CodeRabbit