Skip to content

refactor(renderer): ProviderSettings v2 migration + ClassifiableModel compat layer#14125

Open
jidan745le wants to merge 92 commits intoCherryHQ:v2from
jidan745le:feat/v2/provider-registry-renderer
Open

refactor(renderer): ProviderSettings v2 migration + ClassifiableModel compat layer#14125
jidan745le wants to merge 92 commits intoCherryHQ:v2from
jidan745le:feat/v2/provider-registry-renderer

Conversation

@jidan745le
Copy link
Copy Markdown
Collaborator

What this PR does

Before this PR:

  • ProviderSettings UI reads all data from Redux store using v1 types (@renderer/types Model/Provider)
  • config/models/ classification functions only accept v1 Model, requiring as unknown as V1Model casts from v2 callers
  • vision.ts and websearch.ts synchronously read Redux store via getProviderByModel to determine provider protocol type

After this PR:

  • ProviderSettings full UI chain (22 files) consumes v2 DataApi (SQLite + SWR) via useProvider(), useModels(), useProviderApiKeys()
  • All config/models/ classification functions accept ClassifiableModel (v1/v2 compatible interface) — zero casts needed from either side
  • vision.ts/websearch.ts use model's own endpointTypes field instead of Redux lookup — config/models/ is now pure functions with zero store dependency
  • Shared components (ModelIdWithTags, FreeTrialModelTag, getModelLogo, ProviderAvatar) accept both v1 and v2 Model

Fixes #N/A

Why we need it and why it was done in this way

The following tradeoffs were made:

  • Introduced ClassifiableModel as a transitional compatibility interface rather than changing all 30+ v1 callers at once. This allows incremental page-by-page migration while keeping everything working.
  • normalizeEndpointType bridges v1 endpoint names ("openai-response") to v2 ("openai-responses") so callers only need to use v2 constants.
  • getLowerBaseModelName auto-strips :: prefix from v2 UniqueModelIds to maintain regex matching correctness.

The following alternatives were considered:

  • Changing all callers to v2 at once — rejected as too large a blast radius for one PR
  • Keeping getProviderByModel Redux dependency — rejected because it makes config/models/ impure and fails silently for v2 Models (provider-specific checks return wrong results)

Breaking changes

None. All changes are backward-compatible:

  • v1 callers continue to pass v1 Model/Provider without any changes
  • v2 callers pass v2 types directly without casts
  • ClassifiableModel is a structural supertype that both satisfy

Special notes for your reviewer

Commit structure (2 commits):

  1. refactor(config/models): introduce ClassifiableModel v1/v2 compat layer — infrastructure: ClassifiableModel interface, helpers, all config/models functions widened, shared components widened
  2. refactor(ProviderSettings): migrate full UI chain to v2 DataApi — consumer: ProviderSettings pages, popups, health check, API key management all switched to v2 DataApi

4 remaining as unknown as V1Model casts are all at the AiProvider (aiCore) boundary — checkApi/checkModel/fetchModels. These will be removed when aiCore supports v2 types.

ClassifiableModel is a transitional layer, not a permanent type. Exit plan documented in docs/provider-registry-renderer-pr.md. When all pages migrate to v2 and aiCore supports v2 types, ClassifiableModel and all its helpers will be deleted in favor of direct v2 Model usage.

Checklist

  • PR: The PR description is expressive enough and will help future contributors
  • Code: Write code that humans can understand and Keep it simple
  • Refactor: You have left the code cleaner than you found it (Boy Scout Rule)
  • Upgrade: Impact of this change on upgrade flows was considered and addressed if required
  • Documentation: A user-guide update was considered and is present (link) or not required.
  • Self-review: I have reviewed my own code before requesting review from others

Release note

NONE

jidan745le and others added 30 commits April 8, 2026 13:43
Signed-off-by: jidan745le <420511176@qq.com>
Add v2 provider-model migration mappings and registration, then align provider handlers/services with lifecycle DI and API contracts so migration generation and node typecheck both pass.

Signed-off-by: jidan745le <420511176@qq.com>
Made-with: Cursor
Add an empty changeset file so CI changeset-check passes for this non-release provider/model runtime integration work.

Signed-off-by: jidan745le <420511176@qq.com>
Made-with: Cursor
…path

Drop redundant assertions flagged by oxlint and keep only required runtime-shape casts so CI test:lint passes.

Signed-off-by: jidan745le <420511176@qq.com>
Made-with: Cursor
Remove duplicate 0007 migration and regenerate migration artifacts as 0009 to keep drizzle journal order consistent with existing 0007/0008 chain.

Signed-off-by: jidan745le <420511176@qq.com>
Made-with: Cursor
The catalog service initialization was missing from the startup
sequence, causing preset provider/model data (capabilities,
context windows, pricing, baseUrls) to never sync from protobuf
catalog files into SQLite on each launch.

Signed-off-by: jidan745le <420511176@qq.com>
Signed-off-by: jidan745le <420511176@qq.com>
…dpointConfigs

Merge three separate provider endpoint fields into a single
`endpointConfigs` map keyed by EndpointType. Each entry holds
baseUrl, modelsApiUrls, and reasoningFormatType in one place.
Also removes unused `createdAt` from ApiKeyEntry.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: suyao <sy20010504@gmail.com>
Rename the entire provider-catalog package, service, and all related
references to provider-registry for clearer naming semantics.

- Package: @cherrystudio/provider-catalog → @cherrystudio/provider-registry
- Directory: packages/provider-catalog → packages/provider-registry
- Proto namespace: catalog.v1 → registry.v1
- Service: ProviderCatalogService → ProviderRegistryService
- All type/function/variable names updated accordingly

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: suyao <sy20010504@gmail.com>
rateLimit depends on the user's API plan, not the model itself —
not appropriate for preset registry data. Also remove inherited
reserved fields since registry.v1 has no legacy data to maintain
compatibility with.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: suyao <sy20010504@gmail.com>
…I design

- Convert RegistryService to lifecycle-managed ProviderRegistryService
  (@Injectable, @ServicePhase, @dependsOn, BaseService)
- Register in serviceRegistry.ts; remove manual init from index.ts
- Replace singleton imports with application.get('ProviderRegistryService')
- Move /models/resolve to POST /providers/:providerId/registry-models
  (RESTful: same resource path, different HTTP methods)
- Rename ResolveModelsDto to EnrichModelsDto (providerId from URL params)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: suyao <sy20010504@gmail.com>
…ings

- Remove protobuf toolchain (proto files, buf config, generated code,
  @bufbuild/* dependencies)
- Convert registry data from .pb binary to .json files
- Replace proto-generated types with Zod-inferred types
- Rewrite registry-reader to use JSON.parse + Zod validation
- Simplify ProviderRegistryService and modelMerger by removing
  proto conversion layers (buildEndpointConfigsFromProto, CASE_TO_TYPE,
  extractReasoningFormatType)
- Convert all enums from numeric TypeScript enum to string-valued
  as-const objects (EndpointType, ModelCapability, Modality, Currency,
  ReasoningEffort) for debuggability
- Regenerate Drizzle migration to match current schema

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: suyao <sy20010504@gmail.com>
The registry data uses "2026.03.09" format (from proto), but the
VersionSchema regex only allowed "YYYY-MM-DD". This caused Zod
validation to fail silently when loading registry JSON, resulting
in empty model data and no enrichment of capabilities/modalities.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: suyao <sy20010504@gmail.com>
registry-reader.ts uses node:fs which crashes in renderer. Split into a separate @cherrystudio/provider-registry/node sub-path export so the main entry stays browser-compatible.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Signed-off-by: suyao <sy20010504@gmail.com>
Made-with: Cursor
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Signed-off-by: suyao <sy20010504@gmail.com>
Made-with: Cursor
Signed-off-by: jidan745le <420511176@qq.com>
Made-with: Cursor
Signed-off-by: jidan745le <420511176@qq.com>
Signed-off-by: jidan745le <420511176@qq.com>
… DataApi

Signed-off-by: jidan745le <420511176@qq.com>
Signed-off-by: jidan745le <420511176@qq.com>
Signed-off-by: jidan745le <420511176@qq.com>
Signed-off-by: jidan745le <420511176@qq.com>
Signed-off-by: jidan745le <420511176@qq.com>
…odel

Migrate renderer consumer code to match the new consolidated
endpointConfigs schema (replaces separate baseUrls, modelsApiUrls,
reasoningFormatTypes fields) and catalog→registry rename.

- Replace provider.baseUrls?.[ep] with endpointConfigs?.[ep]?.baseUrl
  in ProviderSetting, CherryINSettings, DMXAPISettings, ManageModelsPopup
- Rename replaceBaseUrlDomain → replaceEndpointConfigDomain (preserves
  other EndpointConfig fields like reasoningFormatType)
- Update v1ProviderShim to read baseUrl from endpointConfigs
- Rename useProviderCatalogModels → useProviderRegistryModels, update
  endpoint from catalog-models to registry-models
- Replace removed /models/resolve with /providers/:id/registry-models POST
- Update tests to reflect all of the above

Signed-off-by: jidan745le <420511176@qq.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Signed-off-by: suyao <sy20010504@gmail.com>
Made-with: Cursor
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Signed-off-by: suyao <sy20010504@gmail.com>
Made-with: Cursor
- Add void to all floating promises in ProviderSettings components
- Remove closeDelay prop (not in TooltipProps)
- Fix unused destructured variable with delete pattern

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: suyao <sy20010504@gmail.com>
- ManageModelsPopup: replace Math.floor(Number(defaultChatEndpoint))
  with direct string enum access (was producing NaN after string enum
  migration, causing model list to fail loading)
- provider.v2.ts: fix replaceEndpointConfigDomain type signature from
  Record<number, ...> to Record<EndpointType, ...>, remove Number(key)
  conversions
- Update test file to use string EndpointType keys

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: suyao <sy20010504@gmail.com>
jidan745le and others added 18 commits April 10, 2026 11:22
- A1.1: Add missing inputModalities, outputModalities, parameterSupport
  to ModelService.update — DTO advertised but silently dropped
- A1.2: Use lookupRegistryModel in resolveModels for normalized fallback
  (gpt-4o:free, aihubmix-gpt-4o, claude-3.5-sonnet now resolve correctly)
- A1.3: Only protect canonical preset providers (providerId === presetProviderId)
  from deletion — user-created providers inheriting from presets can be deleted

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: suyao <sy20010504@gmail.com>
Backend renamed const objects (EndpointType → ENDPOINT_TYPE,
ModelCapability → MODEL_CAPABILITY). Update all renderer imports and
usages to match, and fix string→EndpointType cast in add-model popups.

Signed-off-by: jidan745le <420511176@qq.com>
- Add sortOrder and notes to Model schema and rowToRuntimeModel
  (previously writable via PATCH but not readable via GET)
- Add regression tests for 3 behavioral boundaries:
  - ProviderService.delete: canonical preset protection vs user copies
  - resolveModels: normalize fallback for variant/aggregator model IDs
  - PresetProviderSeed: insert-only behavior, no overwrite on restart
- Simplify ModelService tests to meaningful edge cases only
- Fix lint: replace import() type annotation with vi.importActual
- Migrate ProviderRegistryService tests to unified mock system

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: suyao <sy20010504@gmail.com>
- Build model and override indexes (4 Maps) on first load for O(1) lookup
- Add idle TTL (30s default): auto-invalidate after no access, freeing ~6MB
- ProviderRegistryService uses loader.findModel/findOverride instead of
  Array.find — eliminates O(N) scans on every query
- Remove lookupRegistryModel import from ProviderRegistryService (now unused)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: suyao <sy20010504@gmail.com>
- Add getOverridesForProvider(providerId) to RegistryLoader (O(1) via
  overridesByProvider Map, replaces Array.filter O(N))
- getRegistryModelsByProvider now uses loader.getOverridesForProvider +
  loader.findModel instead of manual Map construction and filter

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: suyao <sy20010504@gmail.com>
- Remove enrichExistingModels and initializeProvider flow descriptions
- Add RegistryLoader section (indexes, idle TTL, query API)
- Add Merge Functions table (mergePresetModel/mergeModelWithUser/createCustomModel)
- Update resolveModels flow (modelIds only, no SDK data overwrite)
- Update preset deletion protection (canonical vs inherited)
- Add parameters, sortOrder, notes to user_model table docs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: suyao <sy20010504@gmail.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: suyao <sy20010504@gmail.com>
…stry-renderer

# Conflicts:
#	docs/references/data/provider-registry.md
#	electron.vite.config.ts
#	packages/provider-registry/src/registry-loader.ts
#	packages/shared/data/utils/modelMerger.ts
#	src/main/data/db/schemas/userModel.ts
#	src/main/data/db/seeding/presetProviderSeeding.ts
#	src/main/data/services/ModelService.ts
#	src/main/data/services/ProviderRegistryService.ts
#	src/main/data/services/ProviderService.ts
#	tsconfig.node.json
… path

Signed-off-by: jidan745le <420511176@qq.com>
Signed-off-by: jidan745le <420511176@qq.com>
@jidan745le jidan745le marked this pull request as ready for review April 11, 2026 04:32
@jidan745le jidan745le requested a review from a team April 11, 2026 04:32
@jidan745le jidan745le requested a review from 0xfullex as a code owner April 11, 2026 04:32
Signed-off-by: jidan745le <420511176@qq.com>
@DeJeune DeJeune self-assigned this Apr 11, 2026
- Use v2 ENDPOINT_TYPE enums in endpointTypeOptions instead of legacy
  strings, fixing Zod validation errors on batch/single model creation
- Reverse enrichment logic in ManageModelsPopup: fetched models from the
  provider API are now the primary source (ID, name, group, endpointTypes),
  with registry catalog data supplementing metadata fields only
  (capabilities, pricing, contextWindow, etc.)
- Fix HealthCheckService passing empty models array to v1 provider shim,
  which caused checkApiProvider to reject the check before sending request
- Use ENDPOINT_TYPE constants for form default values in add model popups

Signed-off-by: jidan745le <420511176@qq.com>
- Add /models/batch POST endpoint with Zod validation for bulk model creation
- Extract buildCreateValues in ModelService for shared create/batchCreate logic
- Add createModelsBatch hook with cache invalidation in useModels
- Encode providerId/modelId in model API paths to handle slashes
- Use normalizeModelGroupName and getModelGroupLabel for consistent grouping
- Add toV1ModelShim/toV1ModelForCheckApi helpers in v1ProviderShim
- Export Zod schemas (ListModelsQuery, CreateModelDto, UpdateModelDto, EnrichModelsDto)
- Add tests for batchCreate and slash-encoded model deletion

Signed-off-by: jidan745le <420511176@qq.com>
- Add grouping.ts with normalizeModelGroupName and getModelGroupLabel
- Update ProviderSetting and ApiKeyListPopup hook

Signed-off-by: jidan745le <420511176@qq.com>
- ManageModelsPopup: handle optional apiModelId with fallback to parsed modelId
- NewApiAddModelPopup: cast endpointType to EndpointType instead of string
- VertexAISettings: derive gcpConfig inside useEffect to satisfy exhaustive-deps
- AwsBedrockSettings: derive awsConfig inside useEffect to satisfy exhaustive-deps

Signed-off-by: jidan745le <420511176@qq.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants