mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-07-08 08:27:39 +08:00
Clear all `barrel/index-no-impl` violations in src/main (14 barrels) by turning each index.ts into a pure re-export or dissolving it: - Aggregation buckets → drop the barrel, move the assembled object/array/fn to a named file, deep-import at the sole consumer: data/api/handlers → apiHandlers.ts, ipc/handlers → ipcHandlers.ts, migrators → migratorRegistry.ts, params/features → internalFeatures.ts, browser/tools → registry.ts, builtin → registerBuiltinTools.ts, db/seeding → seederRegistry.ts. - Single-impl dirs flattened: provider/extensions.ts, provider/cherryai.ts. - Barrel kept, impl extracted to a named file: i18n/resolver.ts, ai/types/providerConfig.ts. - Route subdir flattened to match its flat siblings: routes/knowledge.ts + routes/knowledgeSchemas.ts. - runtime: replace the import-time `claudeCode/register` side effect with an explicit registerRuntimeDrivers() invoked from AgentSessionRuntimeService.onInit, so runtime/index.ts stays a pure re-export. - loop: dissolve the non-enforced barrel into loop/types.ts + loop/hookRunner.ts. Also route DataApiService's apiHandlers import through the data/api barrel, and expose ClaudeCodeRuntimeDriver via the claudeCode barrel, clearing the two deep-import warnings this batch touched. Docs and comments updated to the new paths.
138 lines
5.7 KiB
Markdown
138 lines
5.7 KiB
Markdown
# Provider Resolution
|
|
|
|
## The problem this solves
|
|
|
|
A request needs to know which `@ai-sdk/*` package to import, with which
|
|
settings, hitting which URL. Three pieces of state determine that:
|
|
|
|
| Field | Lives on | Example |
|
|
|---|---|---|
|
|
| `provider.id` | `Provider` row | `minimax`, `silicon`, `my-relay` |
|
|
| `endpointType` | `model.endpointTypes[0]` or `provider.defaultChatEndpoint` | `openai-chat-completions`, `anthropic-messages` |
|
|
| `adapterFamily` | `provider.endpointConfigs[endpointType].adapterFamily` | `openai-compatible`, `anthropic`, `azure-responses` |
|
|
|
|
`adapterFamily` is the actual SDK selector. `provider.id` is the user-facing
|
|
identity. `endpointType` is the protocol family. The mapping is written
|
|
once at provider-creation time; runtime resolution is read-only.
|
|
|
|
See [Adapter Family](./adapter-family.md) for the full design.
|
|
|
|
## Resolver
|
|
|
|
`src/main/ai/provider/endpoint.ts` exposes three pure helpers:
|
|
|
|
```ts
|
|
resolveEffectiveEndpoint(provider, model): { endpointType, baseUrl }
|
|
resolveProviderVariant(baseProviderId, endpointType): AppProviderId
|
|
resolveAiSdkProviderId(provider, endpointType): AppProviderId
|
|
```
|
|
|
|
`resolveAiSdkProviderId` is the runtime hot-path entry. It reads
|
|
`provider.endpointConfigs[endpointType].adapterFamily`, applies the
|
|
variant suffix if the endpoint type has one, falls back to
|
|
`openai-compatible` when no family is set.
|
|
|
|
```ts
|
|
// Full resolver — 6 lines
|
|
export function resolveAiSdkProviderId(provider, endpointType) {
|
|
const adapterFamily = endpointType
|
|
? provider.endpointConfigs?.[endpointType]?.adapterFamily
|
|
: undefined
|
|
if (adapterFamily && adapterFamily in appProviderIds) {
|
|
return resolveProviderVariant(appProviderIds[adapterFamily], endpointType)
|
|
}
|
|
return appProviderIds['openai-compatible']
|
|
}
|
|
```
|
|
|
|
## Variants
|
|
|
|
Some bases expose variant ids (a different endpoint on the same base).
|
|
`resolveProviderVariant` knows two suffix rules and applies one only when
|
|
the resulting `<base>-<suffix>` id is actually registered — otherwise it
|
|
returns the base unchanged:
|
|
|
|
| Endpoint type | Suffix tried |
|
|
|---|---|
|
|
| `openai-chat-completions`, `ollama-chat` | `-chat` |
|
|
| `openai-responses` | `-responses` |
|
|
|
|
Variants registered today (declared in each provider extension's
|
|
`variants` array, `packages/aiCore/src/core/providers/core/initialization.ts`):
|
|
|
|
| Base | Variant id(s) |
|
|
|---|---|
|
|
| `openai` | `openai-chat` (the base `openai` is itself the Responses API) |
|
|
| `azure` | `azure-responses`, `azure-anthropic` |
|
|
| `xai` | `xai-responses` |
|
|
| `cherryin` | `cherryin-chat` |
|
|
|
|
`ollama` has no registered variant, so an `ollama-chat` endpoint resolves
|
|
to the base `ollama`. Likewise there is **no `openai-responses` variant**
|
|
(the base already is). `azure-anthropic` is not reached through the suffix
|
|
rule — it is selected inside `buildAzureConfig` when the model is a Claude
|
|
model (see below). `resolveProviderVariant(baseId, endpointType)` is
|
|
idempotent when the base id is already a variant.
|
|
|
|
## Provider config
|
|
|
|
`providerToAiSdkConfig(provider, model)`
|
|
(`src/main/ai/provider/config.ts`) returns
|
|
`{ providerId: AppProviderId, providerSettings: AppProviderSettingsMap[id] }`.
|
|
It calls `resolveAiSdkProviderId` internally, then dispatches through an
|
|
ordered `{ match, build }` table to build the provider-specific settings
|
|
object (apiKey, baseURL, organization, headers, ...). There is **no
|
|
"gateway" branch**.
|
|
|
|
The builder table (`config.ts`, first match wins):
|
|
|
|
| Match | Builder | Notes |
|
|
|---|---|---|
|
|
| `id === copilot` | `buildCopilotConfig` | async — fetches a Copilot token |
|
|
| `id === 'cherryai'` | `buildCherryAIConfig` | |
|
|
| `isOllamaProvider` | `buildOllamaConfig` | |
|
|
| `isAzureOpenAIProvider` | `buildAzureConfig` | returns `azure` / `azure-responses` / `azure-anthropic` (Claude on Azure) |
|
|
| `id === 'bedrock'` | `buildBedrockConfig` | |
|
|
| `id === 'google-vertex'` | `buildVertexConfig` | returns `google-vertex` or `google-vertex-anthropic` for Claude; leaves `baseURL` undefined when no host is configured so the SDK derives the aiplatform host |
|
|
| `provider.id === 'cherryin'` | `buildCherryinConfig` | matches the **provider id**, not the resolved variant — the default chat endpoint resolves to `cherryin-chat`, so an `id === 'cherryin'` check never fires; async — resolves relay base URLs |
|
|
| `id === 'newapi'` | `buildNewApiConfig` | |
|
|
| `id === 'aihubmix'` | `buildAiHubMixConfig` | |
|
|
| _(no match)_ | `buildGenericProviderConfig` / `buildOpenAICompatibleConfig` | generic fallback |
|
|
|
|
Several builders are `async` (Copilot token, CherryIN relay URLs), which is
|
|
why `providerToAiSdkConfig` returns a promise.
|
|
|
|
## Custom providers
|
|
|
|
`src/main/ai/provider/custom/`:
|
|
|
|
- **aihubmix** — multi-vendor relay. `provider.id='aihubmix'` but each
|
|
model carries `model.provider='aihubmix.<vendor>'`; the registry's
|
|
aggregator fallback uses the suffix to pick the right `toolFactory`.
|
|
- **newapi** — same shape, different relay.
|
|
|
|
Both register through `ProviderExtension.create(...)` with their own
|
|
`AppProviderSettings` shape.
|
|
|
|
## Provider extensions
|
|
|
|
`src/main/ai/provider/extensions.ts` registers every
|
|
`@ai-sdk/*` package Cherry uses with `ProviderExtension.create`. Each
|
|
extension declares:
|
|
|
|
- `name` (the `AppProviderId` for the base)
|
|
- `aliases` (alternate ids that normalize to `name`)
|
|
- `variants` (suffix entries — see above)
|
|
- `create` (the SDK's factory)
|
|
- `toolFactories` (per-capability factory functions for `webSearch` /
|
|
`urlContext` etc.)
|
|
- `supportsImageGeneration` (boolean flag)
|
|
|
|
## Where to read more
|
|
|
|
- Code: `src/main/ai/provider/`
|
|
- Tests: `provider/__tests__/endpoint.test.ts` (54 cases)
|
|
- Migration of legacy provider rows: `src/main/data/migration/v2/migrators/mappings/ProviderModelMappings.ts`
|
|
- Catalog (new installs): `packages/provider-registry/data/providers.json`
|
|
- Design: [Adapter Family](./adapter-family.md)
|