Skip to content

fix: Format provider API hosts in API server & refactor shared utilities#13198

Merged
EurFelux merged 18 commits intomainfrom
fix/api-server-chat
Mar 18, 2026
Merged

fix: Format provider API hosts in API server & refactor shared utilities#13198
EurFelux merged 18 commits intomainfrom
fix/api-server-chat

Conversation

@EurFelux
Copy link
Collaborator

@EurFelux EurFelux commented Mar 4, 2026

What this PR does

Before this PR:

The API server used raw provider apiHost values from Redux without formatting. This meant provider-specific URL transformations (e.g., appending /v1, Ollama /api suffix, Azure /openai suffix) were skipped, causing upstream API calls to hit incorrect URLs (often resulting in 404 errors). Additionally, shared utility functions (API host formatting, provider type checks, Claude provider constants) were tightly coupled to the renderer process and unavailable to the main process.

After this PR:

  • API utility functions (formatApiHost, withoutTrailingSlash, hasAPIVersion, etc.) are moved to packages/shared/utils/api/
  • Provider type check functions (isAnthropicProvider, isOllamaProvider, etc.) are moved to packages/shared/aiCore/provider/utils/
  • Provider-specific API host formatters (formatOllamaApiHost, formatAzureOpenAIApiHost) are moved to packages/shared/aiCore/provider/utils/api.ts
  • CLAUDE_SUPPORTED_PROVIDERS / CLAUDE_OFFICIAL_SUPPORTED_PROVIDERS constants are moved to packages/shared/config/providers.ts
  • A main-process version of formatProviderApiHost is created at src/main/aiCore/provider/providerConfig.ts
  • The API server now formats provider API hosts before caching/using them, fixing 404 errors caused by unformatted URLs

Fixes #13192

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

The following tradeoffs were made:

  • A separate formatProviderApiHost implementation exists in the main process (src/main/aiCore/provider/providerConfig.ts) alongside the renderer version. This is because the Vertex API host formatter in the renderer depends on the Redux store directly, while the main process version uses reduxService. A // WARNING comment is added to remind developers to keep them in sync.
  • Renderer-side modules re-export from shared for backward compatibility, avoiding a large-scale import path migration across the codebase.

The following alternatives were considered:

Breaking changes

None. All existing renderer imports continue to work via re-exports.

Special notes for your reviewer

  • The core bug fix is in src/main/apiServer/utils/index.ts — providers are now formatted via formatProviderApiHost() before being cached.
  • Most of the diff is moving code from renderer to shared packages with no logic changes.
  • The Revert "refactor: Add const assertions to provider arrays" commit reverts an earlier attempt that caused issues.

Checklist

Release note

NONE

EurFelux added 9 commits March 4, 2026 13:51
- Move `formatApiHost` and related utilities from renderer to shared
  package
- Create new `@shared/utils/api` module with proper exports
- Update renderer to re-export from shared package for backward
  compatibility
- Improve code organization and reuse across codebase
- Move `formatProviderApiHost` from renderer to main process
- Apply formatting to providers returned by `/api/providers` endpoint
- Ensure consistent API host URLs across all provider types
@DeJeune DeJeune self-assigned this Mar 4, 2026
Move tests for functions that were refactored into the shared package
to their corresponding locations. Add vitest alias config for @shared
in the shared test project.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@EurFelux EurFelux requested a review from 0xfullex as a code owner March 4, 2026 08:54
EurFelux and others added 3 commits March 4, 2026 17:01
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
These tests were not part of the original codebase and should not
have been added during the migration.

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

- Log rejected promises in getAvailableProviders instead of silently dropping providers
- Use public entry path for shared utils imports instead of internal module paths
- Unify re-export paths to use @shared/aiCore/provider/utils consistently

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@EurFelux
Copy link
Collaborator Author

EurFelux commented Mar 4, 2026

Note

This issue/comment/review was translated by Claude.

Self Review Fixes (b9f8d920c)

Performed a self review of this branch and identified and fixed 3 issues:

1. Promise.allSettled Silently Losing Providers (Medium Severity)

File: src/main/apiServer/utils/index.ts

Previously, getAvailableProviders() used Promise.allSettled + .filter(fulfilled). If formatProviderApiHost threw an exception for a provider, that provider would silently disappear without any error logs.

Fix: Changed to iterate through results and use logger.warn to output the providerId and failure reason for rejected results.

2. Direct Internal Module Path Imports (Code Style)

File: src/renderer/src/utils/api.ts

import { withoutTrailingSlash } from '@shared/utils/api/utils' directly referenced an internal implementation file, exposing the module's internal structure.

Fix: Changed to import from the public entry point @shared/utils.

3. Inconsistent Re-export Paths (Code Cleanliness)

File: src/renderer/src/utils/provider.ts

Re-exports used @shared/aiCore/provider/utils/types, while imports in the same file used @shared/aiCore/provider/utils, creating inconsistent paths.

Fix: Unified to use @shared/aiCore/provider/utils.


Original Content

Self Review 修复 (b9f8d920c)

对本分支进行了 self review,发现并修复了 3 个问题:

1. Promise.allSettled 静默丢失 provider(中等严重)

文件: src/main/apiServer/utils/index.ts

之前 getAvailableProviders() 中使用 Promise.allSettled + .filter(fulfilled) 的写法,如果 formatProviderApiHost 对某个 provider 抛出异常,该 provider 会静默消失,没有任何错误日志。

修复: 改为遍历 results,对 rejected 的结果用 logger.warn 输出 providerId 和失败原因。

2. 内部模块路径直接引用(代码规范)

文件: src/renderer/src/utils/api.ts

import { withoutTrailingSlash } from '@shared/utils/api/utils' 直接引用了内部实现文件,暴露了模块内部结构。

修复: 改为从公共入口 @shared/utils 导入。

3. re-export 路径不一致(代码洁净度)

文件: src/renderer/src/utils/provider.ts

re-export 使用 @shared/aiCore/provider/utils/types,而同文件 import 使用 @shared/aiCore/provider/utils,路径不一致。

修复: 统一使用 @shared/aiCore/provider/utils

Copy link
Collaborator

@DeJeune DeJeune left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall Assessment

Good PR that addresses a real bug — the API server was using raw provider apiHost values without formatting, causing 404s. The fix in src/main/apiServer/utils/index.ts is clean and well-structured with proper error handling via Promise.allSettled.

The bulk of the diff is a mechanical move of shared utilities from renderer to packages/shared/, which is a sound architectural decision. Tests are comprehensive and CI is green.

Findings

Significant

  • Dual formatProviderApiHost sync risk: The main-process and renderer versions have subtle differences (async vs sync, different formatVertexApiHost signatures). The WARNING comment on the renderer side points to the wrong file path (shared/ instead of src/main/). Consider adding a bidirectional WARNING so the sync obligation is visible from both files.

Minor

  • Variable shadowing (host) in formatVertexApiHost
  • Loose equality (==) instead of strict (===) for string comparison
  • Unrelated uuid import change in settings.ts
  • Surprising test expectations in formatOllamaApiHost could use explanatory comments

Positives

  • Excellent test coverage for the moved utilities
  • Promise.allSettled usage in the core fix gracefully handles per-provider formatting failures
  • Re-exports for backward compatibility is a pragmatic approach
  • PR description is thorough with clear explanation of tradeoffs

Comment on lines +9 to +10
projectId: string
location: string
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Variable host on line 10 shadows the function parameter host on line 3. Consider renaming it to baseHost or vertexHost to avoid confusion.

Suggested change
projectId: string
location: string
const trimmedHost = withoutTrailingSlash(trim(host))
if (\!trimmedHost || trimmedHost.endsWith('aiplatform.googleapis.com')) {
const baseHost =
location == 'global' ? 'https://aiplatform.googleapis.com' : `https://${location}-aiplatform.googleapis.com`
return `${formatApiHost(baseHost)}/projects/${project}/locations/${location}`

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

renamed to fallbackHost

OpenAIVerbosity
} from '@renderer/types/aiCoreTypes'
import { uuid } from '@renderer/utils'
import { API_SERVER_DEFAULTS, UpgradeChannel } from '@shared/config/constant'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: This import change from @renderer/utils to uuid directly appears unrelated to the PR's scope (API host formatting / shared utility refactor). Was this intentional or a leftover from another change? If intentional, it might be worth mentioning in the PR description.

Copy link
Collaborator Author

@EurFelux EurFelux Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

This comment was translated by Claude.

Because if we don't make this change, test errors will occur. The uuid imported from renderer/utils is undefined at runtime, likely caused by circular dependencies in the complex dependency chain. Since it's too difficult to troubleshoot, here we directly changed to use the uuid package, and the runtime behavior remains unchanged.


Original Content 因为若不更改就会出现测试错误,从renderer/utils中导入的uuid在运行时为undefined,估计是在复杂的依赖链中出现循环依赖导致的。由于过于难以排查,这里直接改成使用uuid包,运行时行为不变。

Comment on lines +16 to +27
import { formatVertexApiHost } from './utils/api'

/**
* Format and normalize the API host URL for a provider.
* Handles provider-specific URL formatting rules (e.g., appending version paths, Azure formatting).
*
* @param provider - The provider whose API host is to be formatted.
* @returns A new provider instance with the formatted API host.
*/
export async function formatProviderApiHost(provider: Provider): Promise<Provider> {
// NOTE: It's async to support Vertex API host formatting
// Original implementation: src/renderer/src/aiCore/provider/providerConfig.ts
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Significant: This main-process formatProviderApiHost is a near-duplicate of the renderer version at src/renderer/src/aiCore/provider/providerConfig.ts:formatProviderApiHost. Having two copies that must be manually kept in sync is fragile. A few differences stand out:

  1. This version is async (returns Promise<Provider>) because of Vertex, while the renderer version is sync.
  2. The renderer version uses formatVertexApiHost(formatted) (passing the full provider), while this version uses formatVertexApiHost(formatted.apiHost) (passing just the string). These are actually different functions with different signatures.

Consider adding a corresponding WARNING comment here pointing back to the renderer version, so the sync obligation is visible from both sides.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment added

Co-authored-by: SuYao <sy20010504@gmail.com>
@EurFelux EurFelux requested a review from DeJeune March 9, 2026 08:01
@DeJeune
Copy link
Collaborator

DeJeune commented Mar 10, 2026

Note

This issue/comment/review was translated by Claude.

Looks like there's some overlap with #11495. Once that merges, I can reduce the code size in this PR.


Original Content

看起来和 #11495 有一部分是重合的,这样合并之后,我这个pr就可以少点代码量了

@EurFelux
Copy link
Collaborator Author

Will resolve conflicts later

Copy link
Collaborator

@GeorgeDong32 GeorgeDong32 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

代码审查完成。核心 bug 修复(API server 格式化 provider API hosts)实现正确,测试覆盖完善。存在两处formatProviderApiHost 实现需要手动同步(已有 WARNING 注释),以及小的代码一致性问题(可选改进)。整体质量良好,可以合并。

@EurFelux EurFelux merged commit d081b05 into main Mar 18, 2026
7 checks passed
@EurFelux EurFelux deleted the fix/api-server-chat branch March 18, 2026 16:41
@kangfenmao kangfenmao mentioned this pull request Mar 19, 2026
10 tasks
kangfenmao added a commit that referenced this pull request Mar 19, 2026
<!-- Template from
https://github.com/kubevirt/kubevirt/blob/main/.github/PULL_REQUEST_TEMPLATE.md?-->
<!--  Thanks for sending a pull request!  Here are some tips for you:
1. Consider creating this PR as draft:
https://github.com/CherryHQ/cherry-studio/blob/main/CONTRIBUTING.md
-->

<!--

⚠️ Important: Redux/IndexedDB Data-Changing Feature PRs Temporarily On
Hold ⚠️

Please note: For our current development cycle, we are not accepting
feature Pull Requests that introduce changes to Redux data models or
IndexedDB schemas.

While we value your contributions, PRs of this nature will be blocked
without merge. We welcome all other contributions (bug fixes, perf
enhancements, docs, etc.). Thank you!

Once version 2.0.0 is released, we will resume reviewing feature PRs.

-->

### What this PR does

Release Cherry Studio v1.8.2

Before this PR:
- Version 1.8.1 is the latest release

After this PR:
- Version 1.8.2 with bug fixes and model updates

<!-- (optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)`
format, will close the issue(s) when PR gets merged)*: -->

Fixes #

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

This is a patch release that includes:
- Bug fixes for knowledge base, message search, and backup restoration
- New model support for Xiaomi MiMo-V2-Pro and MiMo-V2-Omni
- MiniMax default model upgrade to M2.7

The following tradeoffs were made:
- N/A — this is a scheduled patch release

The following alternatives were considered:
- N/A — standard release workflow

Links to places where the discussion took place: <!-- optional: slack,
other GH issue, mailinglist, ... -->

### Breaking changes

<!-- optional -->

None. This is a patch release with no breaking changes.

### Special notes for your reviewer

<!-- optional -->

**Release Review Checklist:**

- [ ] Review generated release notes in `electron-builder.yml`
- [ ] Verify version bump in `package.json` (1.8.1 → 1.8.2)
- [ ] CI passes
- [ ] Merge to trigger release build

**Included Commits:**

- 8124236 fix(config): update app upgrade segments and include new
gateway version
- 62c1eb2 fix: correct parameter order in knowledgeSearchTool call
(#13635)
- bb3dec9 fix(MessageHeader): crash when clicking topic in message
search (#13627)
- 24645d3 fix(tests): resolve Windows test failures and upgrade prek
(#13619)
- 0b08fd9 refactor: remove manual install update logic and related API
calls
- f517a07 fix(BackupManager): update data destination path for backup
restoration
- f658484 refactor(ConfigManager): remove legacy config migration logic
- c1c1b34 chore: Add CI check scripts to package.json (#13564)
- 78decc4 feat(model): add support for MiMo-V2-Pro and MiMo-V2-Omni
(#13613)
- d081b05 fix: Format provider API hosts in API server (#13198)
- e4112ba feat: upgrade MiniMax default model to M2.7 (#13593)

### Checklist

This checklist is not enforcing, but it's a reminder of items that could
be relevant to every PR.
Approvers are expected to review this list.

- [x] PR: The PR description is expressive enough and will help future
contributors
- [ ] Code: [Write code that humans can
understand](https://en.wikiquote.org/wiki/Martin_Fowler#code-for-humans)
and [Keep it simple](https://en.wikipedia.org/wiki/KISS_principle)
- [ ] Refactor: You have [left the code cleaner than you found it (Boy
Scout
Rule)](https://learning.oreilly.com/library/view/97-things-every/9780596809515/ch08.html)
- [ ] Upgrade: Impact of this change on upgrade flows was considered and
addressed if required
- [ ] Documentation: A [user-guide update](https://docs.cherry-ai.com)
was considered and is present (link) or not required. Check this only
when the PR introduces or changes a user-facing feature or behavior.
- [ ] Self-review: I have reviewed my own code (e.g., via
[`/gh-pr-review`](/.claude/skills/gh-pr-review/SKILL.md), `gh pr diff`,
or GitHub UI) before requesting review from others

### Release note

<!--  Write your release note:
1. Enter your extended release note in the below block. If the PR
requires additional action from users switching to the new release,
include the string "action required".
2. If no release note is required, just write "NONE".
3. Only include user-facing changes (new features, bug fixes visible to
users, UI changes, behavior changes). For CI, maintenance, internal
refactoring, build tooling, or other non-user-facing work, write "NONE".
-->

```release-note
Cherry Studio 1.8.2 - Bug Fixes and Model Updates

🐛 Bug Fixes
- [Knowledge Base] Fix knowledge base content not being delivered to the model when selected in conversation
- [Search] Fix crash when clicking topic title in message search results
- [Backup] Fix backup restoration path issue

✨ New Features
- [Models] Add support for Xiaomi MiMo-V2-Pro and MiMo-V2-Omni models with reasoning control and tool use capabilities

💄 Improvements
- [Models] Upgrade MiniMax default model to M2.7 with enhanced reasoning and coding capabilities
```

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
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.

[Bug]: API Server does not format provider apiHost, causing 404 on chat completions

4 participants