Compare commits

...

253 Commits

Author SHA1 Message Date
icarus
afd44780d6 feat: improve ImageViewer context menu UX
- Reorder menu items to prioritize "Copy as Image" as the first action
- Rename "Copy" to "Copy Image Source" for better clarity
- Remove unused ImageIcon import
- Add i18n support for "preview.copy.src" across all locales

This change improves the user experience by making the most common
action (copy image) the first option in the context menu, while also
clarifying what each copy action does.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-29 15:36:25 +08:00
xerxesliu
876f59d650 fix: resolve copy image failure for JPEG format pictures (#11529)
- Convert all image formats to PNG before writing to clipboard to ensure compatibility
- Refactor handleCopyImage to unify image source handling (Base64, File, URL)
- Add convertImageToPng utility function using canvas API for robust conversion
- Remove fallback logic that attempted to write unsupported JPEG format
2025-11-29 14:40:49 +08:00
Phantom
c23e88ecd1 fix: handle Gemini API version correctly for Cloudflare Gateway URLs (#11543)
* refactor(api): extract version regex into constant for reuse

Move the version matching regex pattern into a module-level constant to improve code reuse and maintainability. The functionality remains unchanged.

* refactor(api): rename regex constant and use dynamic regex construction

Use a string pattern for version regex to allow dynamic construction and improve maintainability. Rename constant to better reflect its purpose.

* feat(api): add getLastApiVersion utility function

Implement a utility function to extract the last API version segment from URLs. This is useful for handling cases where multiple version segments exist in the path and we need to determine the most specific version being used.

Add comprehensive test cases covering various URL patterns and edge cases.

* feat(api): add utility to remove trailing API version from URLs

Add withoutTrailingApiVersion function to clean up URLs by removing version segments
at the end of paths. This helps standardize API endpoint URLs when version is not needed.

* refactor(api): rename isSupportedAPIVerion to supportApiVersion for clarity

* fix(gemini): handle api version dynamically for non-vertex providers

Use getLastApiVersion utility to determine the latest API version for non-vertex providers instead of hardcoding to v1beta

* feat(api): add function to extract trailing API version from URL

Add getTrailingApiVersion utility function to specifically extract API version segments
that appear at the end of URLs. This complements existing version-related utilities
and helps handle cases where we only care about the final version in the path.

* refactor(gemini): use getTrailingApiVersion instead of getLastApiVersion

The function name was changed to better reflect its purpose of extracting the trailing API version from the URL. The logic was also simplified and made more explicit.

* refactor(api): remove unused getLastApiVersion function

The function was removed as it was no longer needed, simplifying the API version handling to only use trailing version detection. The trailing version regex was extracted to a constant for reuse.
2025-11-29 14:37:26 +08:00
Phantom
284d0f99e1 fix(anthropic): comment out CONTEXT_100M_HEADER to handle via user preferences (#11545)
See #11540 and #11397 for context on moving this to assistant settings
2025-11-29 14:33:10 +08:00
defi-failure
13ac5d564a fix: match tool-call chunk with tool id (#11533) 2025-11-28 20:46:52 +08:00
kangfenmao
4620b71aee chore: update release notes for v1.7.0 2025-11-28 15:55:12 +08:00
kangfenmao
1b926178f1 chore: update @openrouter/ai-sdk-provider to version 1.2.8 in package.json and yarn.lock 2025-11-28 14:44:45 +08:00
Phantom
5167c927be fix: preserve openrouter reasoning with web search (#11505)
* feat(options): implement deep merging for provider options

Add deep merge functionality to preserve nested properties when combining provider options. The new implementation handles object merging recursively while maintaining type safety.

* refactor(tsconfig): reorganize include paths in tsconfig files

Clean up and reorder include paths for better maintainability and consistency between tsconfig.node.json and tsconfig.web.json

* test: add aiCore test configuration and script

Add new test configuration for aiCore package and corresponding test script in package.json to enable running tests specifically for the aiCore module.

* fix: format

* fix(aiCore): resolve test failures and update test infrastructure

- Add vitest setup file with global mocks for @cherrystudio/ai-sdk-provider
- Fix context assertions: use 'model' instead of 'modelId' in plugin tests
- Fix error handling tests: update expected error messages to match actual behavior
- Fix streamText tests: use 'maxOutputTokens' instead of 'maxTokens'
- Fix schemas test: update expected provider list to match actual implementation
- Fix mock-responses: use AI SDK v5 format (inputTokens/outputTokens)
- Update vi.mock to use importOriginal for preserving jsonSchema export

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* fix(aiCore): add alias mock for @cherrystudio/ai-sdk-provider in tests

The vi.mock in setup file doesn't work for source code imports.
Use vitest resolve.alias to mock the external package properly.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* fix(aiCore): disable unused-vars warnings in mock file

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* fix(aiCore): use import.meta.url for ESM compatibility in vitest config

__dirname is not available in ESM modules, use fileURLToPath instead.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* fix(aiCore): use absolute paths in vitest config for workspace compatibility

- Use path.resolve for setupFiles and all alias paths
- Extend aiCore vitest.config.ts from root workspace config
- Change aiCore test environment to 'node' instead of 'jsdom'

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* docs(factory): improve mergeProviderOptions documentation

Add detailed explanation of merge behavior with examples

* test(factory): add tests for mergeProviderOptions behavior

Add test cases to verify mergeProviderOptions correctly handles primitive values, arrays, and nested objects during merging

* refactor(tests): clean up mock responses test fixtures

Remove unused mock streaming chunks and error responses to simplify test fixtures
Update warning details structure in mock complete responses

* docs(test): clarify comment in generateImage test

Update comment to use consistent 'model id' terminology instead of 'modelId'

* test(factory): verify array replacement in mergeProviderOptions

---------

Co-authored-by: suyao <sy20010504@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
2025-11-28 13:56:46 +08:00
SuYao
b18c64b725 feat: enhance support for AWS Bedrock and Azure OpenAI providers (#11510)
* feat: enhance support for AWS Bedrock and Azure OpenAI providers

* fix: resolve PR review issues for AWS Bedrock support

- Fix header.ts logic bug: change && to || for Vertex/Bedrock provider check
- Fix regex in reasoning.ts to match AWS Bedrock model format (anthropic.claude-*)
- Add test coverage for AWS Bedrock format in isClaude4SeriesModel
- Add Bedrock provider tests including anthropicBeta parameter

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-28 11:00:02 +08:00
Copilot
7ce1590eaf fix: add null checks and type guards to all MessageAgentTools to prevent rendering errors (#11512)
* Initial plan

* fix: add null checks to BashTool to prevent rendering errors

Co-authored-by: DeJeune <67425183+DeJeune@users.noreply.github.com>

* fix: add null checks to all MessageAgentTools to prevent rendering errors

Co-authored-by: DeJeune <67425183+DeJeune@users.noreply.github.com>

* fix: add Array.isArray checks to prevent map errors on non-array values

Co-authored-by: DeJeune <67425183+DeJeune@users.noreply.github.com>

* fix: add typeof checks for string operations to prevent type errors

Co-authored-by: DeJeune <67425183+DeJeune@users.noreply.github.com>

* refactor: remove redundant typeof string checks for typed outputs

Co-authored-by: DeJeune <67425183+DeJeune@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: DeJeune <67425183+DeJeune@users.noreply.github.com>
2025-11-28 10:12:21 +08:00
SuYao
77a9504f74 Fix/condition OpenAI settings (#11509)
* fix(provider): update service tier support logic for OpenAI and Azure providers

* fix(settings): enhance OpenAI settings visibility logic with verbosity support
2025-11-27 22:45:43 +08:00
SuYao
bf35902696 fix(mcp): ensure tool uniqueness by using tool IDs for multiple server instances (#11508) 2025-11-27 22:35:24 +08:00
Phantom
0d12b5fbc2 fix(SelectModelPopup): memoize adapted models to avoid unnecessary updates (#11506)
fix(SelectModelPopup): memoize adapted models to avoid unnecessary update
2025-11-27 22:22:04 +08:00
Copilot
1746e8b21f Fix MCP server confusion when multiple instances of the same server are configured (#10897)
* Initial plan

* Fix MCP server confusion by making tool IDs unique with serverId

Co-authored-by: DeJeune <67425183+DeJeune@users.noreply.github.com>

* Run yarn format to fix code formatting

Co-authored-by: DeJeune <67425183+DeJeune@users.noreply.github.com>

* Fix unit test: allow dash separator in tool names

Co-authored-by: DeJeune <67425183+DeJeune@users.noreply.github.com>

* Fix edge cases: preserve suffix on truncation, handle non-alphanumeric serverId

Co-authored-by: DeJeune <67425183+DeJeune@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: DeJeune <67425183+DeJeune@users.noreply.github.com>
2025-11-27 21:50:51 +08:00
xerxesliu
0836eef1a6 fix: store JSON custom parameters as strings instead of objects (#11501) (#11503)
Previously, JSON-type custom parameters were incorrectly parsed and stored
as objects in the UI layer, causing API requests to fail when getCustomParameters()
attempted to JSON.parse() an already-parsed object.

Changes:
- AssistantModelSettings.tsx: Remove JSON.parse() in onChange handler, store as string
- reasoning.ts: Add comments explaining JSON parsing flow
- BaseApiClient.ts: Add comments for legacy API clients
2025-11-27 20:22:27 +08:00
fullex
d0bd10190d feat(test): e2e framework (#11494)
* feat(test): e2e framework

Add Playwright-based e2e testing framework for Electron app with:
- Custom fixtures for electronApp and mainWindow
- Page Object Model (POM) pattern implementation
- 15 example test cases covering app launch, navigation, settings, and chat
- Comprehensive README for humans and AI assistants

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* refactor(tests): update imports and improve code readability

- Changed imports from 'import { Page, Locator }' to 'import type { Locator, Page }' for better type clarity across multiple page files.
- Reformatted waitFor calls in ChatPage and HomePage for improved readability.
- Updated index.ts to correct the export order of ChatPage and SidebarPage.
- Minor adjustments in electron.fixture.ts and electron-app.ts for consistency in import statements.

These changes enhance the maintainability and clarity of the test codebase.

* chore: update linting configuration to include tests directory

- Added 'tests/**' to the ignore patterns in .oxlintrc.json and eslint.config.mjs to ensure test files are not linted.
- Minor adjustment in electron.fixture.ts to improve the fixture definition.

These changes streamline the linting process and enhance code organization.

* fix(test): select main window by title to fix flaky e2e tests on Mac

On Mac, the app may create miniWindow for QuickAssistant alongside mainWindow.
Using firstWindow() could randomly select the wrong window, causing test failures.
Now we wait for the window with title "Cherry Studio" to ensure we get the main window.

Also removed unused electron-app.ts utility file.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-27 19:52:31 +08:00
Phantom
d8191bd4fb refactor: improve verbosity configuration with type-safe validators (#11463)
* refactor(models): improve verbosity level handling for GPT-5 models

Replace hardcoded verbosity configuration with validator functions
Add support for GPT-5.1 series models

* test(models): restructure model utility tests into logical groups

Improve test organization by grouping related test cases under descriptive describe blocks for better maintainability and readability. Each model utility function now has its own dedicated test section with clear subcategories for different behaviors.

* fix: add null check for model in getModelSupportedVerbosity

Handle null model case defensively by returning default verbosity

* refactor(config): remove redundant as const from MODEL_SUPPORTED_VERBOSITY array

* refactor(models): simplify validator function in MODEL_SUPPORTED_VERBOSITY

* test(model utils): add tests for undefined/null input handling

* fix(models): handle undefined/null input in getModelSupportedVerbosity

Remove ts-expect-error comments and update type signature to explicitly handle undefined/null inputs. Also add support for GPT-5.1 series models.

* test(models): add test case for gpt-5-pro variant model
2025-11-27 17:22:33 +08:00
fullex
d15571c727 fix(code-tools): support Chinese paths and validate directory existence (#11489)
- Add `chcp 65001` to Windows batch file to switch CMD.exe to UTF-8 code page,
  fixing CLI tool launch failure when working directory contains Chinese or
  other non-ASCII characters
- Add directory existence validation before launching terminal to provide
  immediate error feedback instead of delayed failure

Closes #11483

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-27 14:05:14 +08:00
MyPrototypeWhat
a2f67dddb6 fix: resolve readonly property error in assistant preset settings (#11491)
When updating assistant preset settings, if agent.settings was undefined,
it was assigned the DEFAULT_ASSISTANT_SETTINGS object directly. Since this
object is defined with `as const`, it is readonly and subsequent property
assignments would fail with "Cannot assign to read only property".

Fixed by creating a shallow copy of DEFAULT_ASSISTANT_SETTINGS instead of
referencing it directly.

Closes #11490

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-27 13:41:33 +08:00
fullex
8f00321a60 fix: inconsistent text color in release notes last line (#11480)
Move color and font-size styles from p selector to container level
in UpdateNotesWrapper. This ensures all content (including li elements
not wrapped in p tags) uses consistent color.

The issue occurred because .replace(/\n/g, '\n\n') creates a "loose list"
in Markdown where most list items get wrapped in <p> tags, but the last
item (without trailing newline) may not, causing it to inherit a different
color from the parent .markdown class.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-27 10:31:34 +08:00
Apine
eb4670c22c docs: correct the links on the readme (#11477) 2025-11-26 21:17:25 +08:00
fullex
c0beab0f8a chore: update release notes for v1.7.0-rc.3
- Updated version to 1.7.0-rc.3 in package.json
- Added new features including support for Silicon provider and AIHubMix
- Consolidated bug fixes related to providers, models, UI, and settings
- Improved SDK integration with upgraded dependencies
2025-11-26 21:09:27 +08:00
chenxue
97519d96d7 feat(aihubmix): support nano banana (#11476)
support nano banana
2025-11-26 20:51:52 +08:00
Phantom
cbf1d461f0 fix(i18n): clean up translation tags and untranslated strings (#11471)
fix(i18n): update translation strings in ja-jp and ru-ru files

Remove unnecessary translate_input tags and fix incorrect translations
2025-11-26 20:08:04 +08:00
SuYao
bed55c418d fix: silicon provider code list (#11474) 2025-11-26 19:59:57 +08:00
Copilot
82ef4a32eb Fix Poe API reasoning parameters for GPT-5 and reasoning models (#11379)
* Initial plan

* feat: Add proper Poe API reasoning parameters support for GPT-5 and other models

Co-authored-by: DeJeune <67425183+DeJeune@users.noreply.github.com>

* test: Add comprehensive tests for Poe API reasoning support

Co-authored-by: DeJeune <67425183+DeJeune@users.noreply.github.com>

* fix: Add missing isGPT5SeriesModel import in reasoning.ts

Co-authored-by: DeJeune <67425183+DeJeune@users.noreply.github.com>

* fix: Use correct extra_body format for Poe API reasoning parameters

Per Poe API documentation, custom bot parameters like reasoning_effort
and thinking_budget should be passed directly in extra_body, not as
nested structures.

Changed from:
- reasoning_effort: 'low' -> extra_body: { reasoning_effort: 'low' }
- thinking: { type: 'enabled', budget_tokens: X } -> extra_body: { thinking_budget: X }
- extra_body: { google: { thinking_config: {...} } } -> extra_body: { thinking_budget: X }

Updated tests to match the corrected implementation.

Co-authored-by: DeJeune <67425183+DeJeune@users.noreply.github.com>

* fix: Update reasoning parameters and improve type definitions for GPT-5 support

* fix lint

* docs

* fix(reasoning): handle edge cases for models without token limit configuration

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: DeJeune <67425183+DeJeune@users.noreply.github.com>
Co-authored-by: suyao <sy20010504@gmail.com>
2025-11-26 19:56:31 +08:00
槑囿脑袋
79f75843a7 fix: get quota and quota tips (#11472)
Co-authored-by: Claude <noreply@anthropic.com>
2025-11-26 19:53:59 +08:00
Phantom
91f0c47b33 fix(anthropic): prevent duplicate /v1 in API endpoints (#11467)
* fix(anthropic): prevent duplicate /v1 in API endpoints

Anthropic SDK automatically appends /v1 to endpoints, so we should not add it in our formatting. This change ensures URLs are correctly formatted without duplicate path segments.

* fix(anthropic): strip /v1 suffix in getSdkClient to prevent duplicate in models endpoint

The issue was:
- AI SDK (for chat) needs baseURL with /v1 suffix
- Anthropic SDK (for listModels) automatically appends /v1 to all endpoints

Solution:
- Keep /v1 in formatProviderApiHost for AI SDK compatibility
- Strip /v1 in getSdkClient before passing to Anthropic SDK
- This ensures chat works correctly while preventing /v1/v1/models duplication

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* fix(anthropic): correct preview URL to match actual request behavior

The preview now correctly shows:
- Input: https://api.siliconflow.cn/v2
- Preview: https://api.siliconflow.cn/v2/messages (was incorrectly showing /v2/v1/messages)
- Actual: https://api.siliconflow.cn/v2/messages

This matches the actual behavior where getSdkClient strips /v1 suffix before
passing to Anthropic SDK, which then appends /v1/messages.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* fix(anthropic): strip all API version suffixes, not just /v1

The Anthropic SDK always appends /v1 to endpoints, regardless of the baseURL.
Previously we only stripped /v1 suffix, causing issues with custom versions like /v2.

Now we strip all version suffixes (/v1, /v2, /v1beta, etc.) before passing to Anthropic SDK.

Examples:
- Input: https://api.siliconflow.cn/v2/
- After strip: https://api.siliconflow.cn
- Actual request: https://api.siliconflow.cn/v1/messages 

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* fix(anthropic): correct preview to show AI SDK behavior, not Anthropic SDK

The preview was showing the wrong URL because it was reflecting Anthropic SDK behavior
(which strips versions and uses /v1), but checkApi and chat use AI SDK which preserves
the user's version path.

Now preview correctly shows:
- Input: https://api.siliconflow.cn/v2/
- AI SDK (checkApi/chat): https://api.siliconflow.cn/v2/messages 
- Preview: https://api.siliconflow.cn/v2/messages 

Note: Anthropic SDK (for listModels) still strips versions to use /v1/models,
but this is not shown in preview since it's a different code path.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* refactor(checkApi): remove unnecessary legacy fallback

The legacy fallback logic in checkApi was:
1. Complex and hard to maintain
2. Never actually triggered in practice for Modern SDK supported providers
3. Could cause duplicate API requests

Since Modern AI SDK now handles all major providers correctly,
we can simplify by directly throwing errors instead of falling back.

This also removes unused imports: AiProvider and CompletionsParams.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* fix(anthropic): restore version stripping in getSdkClient for Anthropic SDK

The Anthropic SDK (used for listModels) always appends /v1 to endpoints,
so we need to strip version suffixes from baseURL to avoid duplication.

This only affects Anthropic SDK operations (like listModels).
AI SDK operations (chat/checkApi) use provider.apiHost directly via
providerToAiSdkConfig, which preserves the user's version path.

Examples:
- AI SDK (chat): https://api.siliconflow.cn/v1 -> /v1/messages 
- Anthropic SDK (models): https://api.siliconflow.cn/v1 -> strip v1 -> /v1/models 

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* fix(anthropic): ensure AI SDK gets /v1 in baseURL, strip for Anthropic SDK

The correct behavior is:
1. formatProviderApiHost: Add /v1 to apiHost (for AI SDK compatibility)
2. AI SDK (chat/checkApi): Use apiHost with /v1 -> /v1/messages 
3. Anthropic SDK (listModels): Strip /v1 from baseURL -> SDK adds /v1/models 
4. Preview: Show AI SDK behavior (main use case) -> /v1/messages 

Examples:
- Input: https://api.siliconflow.cn
- Formatted: https://api.siliconflow.cn/v1 (added by formatApiHost)
- AI SDK: https://api.siliconflow.cn/v1/messages 
- Anthropic SDK: https://api.siliconflow.cn (stripped) + /v1/models 
- Preview: https://api.siliconflow.cn/v1/messages 

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* refactor(ai): simplify AiProviderNew initialization and improve docs

Update AiProviderNew constructor to automatically format URLs by default
Add comprehensive documentation explaining constructor behavior and usage

* chore: remove unused play.ts file

* fix(anthropic): strip api version from baseURL to avoid endpoint duplication

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-26 19:26:39 +08:00
SuYao
28dff9dfe3 feat: add silicon provider support for Anthropic API compatibility (#11468)
* feat: add silicon provider support for Anthropic API compatibility

* fix: update handling of ANTHROPIC_BASE_URL for silicon provider compatibility

* fix: update anthropicApiHost for silicon provider to use the correct endpoint

* fix: remove silicon from CLAUDE_OFFICIAL_SUPPORTED_PROVIDERS

* chore: add comment to clarify silicon model fallback logic in CLAUDE_OFFICIAL_SUPPORTED_PROVIDERS
2025-11-26 19:19:34 +08:00
fullex
155930ecf4 chore: update @types/react and @types/react-dom to latest versions
- Bumped @types/react from ^19.0.12 to ^19.2.7
- Bumped @types/react-dom from ^19.0.4 to ^19.2.3
- Updated csstype dependency from ^3.0.2 to ^3.2.2 in yarn.lock

These updates ensure compatibility with the latest React types and improve type definitions.
2025-11-26 16:05:40 +08:00
Shuchen Luo
b6b999b635 fix: add claude-opus-4-5 pattern to THINKING_TOKEN_MAP (#11457)
* fix: add claude-opus-4-5 pattern to THINKING_TOKEN_MAP

Adds missing regex pattern for claude-opus-4-5 models (e.g., claude-opus-4-5-20251101)
to the THINKING_TOKEN_MAP configuration. Without this pattern, the model was not
recognized, causing findTokenLimit() to return undefined and leading to an
AI_InvalidArgumentError when using Google Vertex AI Anthropic provider.

The fix adds the pattern 'claude-opus-4-5.*$': { min: 1024, max: 64_000 } to
match the existing claude-4 thinking token configuration.

Fixes AI_InvalidArgumentError: invalid anthropic provider options caused by
budgetTokens receiving NaN instead of a number.

Signed-off-by: Shuchen Luo (personal linux) <nemo0806@gmail.com>

* refactor: make THINKING_TOKEN_MAP constant private

* fix(reasoning): update claude model token limit regex patterns

- Consolidate claude model regex patterns to be more consistent
- Add comprehensive test cases for various claude model variants
- Ensure case insensitivity and proper handling of edge cases

* fix: format

* feat(models): extend claude model regex patterns to support AWS and GCP formats

Update regex patterns in THINKING_TOKEN_MAP to support additional Claude model ID formats used in AWS Bedrock and GCP Vertex AI
Add comprehensive test cases for new model ID formats and reorganize test suite

* fix: format

---------

Signed-off-by: Shuchen Luo (personal linux) <nemo0806@gmail.com>
Co-authored-by: icarus <eurfelux@gmail.com>
2025-11-26 15:47:14 +08:00
SuYao
0d69eeaccf fix: improve Gemini reasoning and message handling (#11439)
* fix: some bug

* fix/test

* fix: lint

* fix: 添加跳过 Gemini3 思考签名的中间件并更新消息转换逻辑

* fix: comment

* fix: js docs

* fix:id bug

* fix: condition

* fix: Update the user's verbosity setting logic to ensure that supported options are prioritized for use.

* fix: Add support for the 'openai-response' provider type.

* fix: lint
2025-11-26 15:46:52 +08:00
Phantom
ff48ce0a58 docs: enhance CLAUDE.md with quality guidelines (#11464)
* docs: add linting and testing step to completion guidelines

* docs: update CLAUDE.md with PR template guideline
2025-11-26 15:45:43 +08:00
SuYao
a2de7d48be fix: update Azure provider handling in AI SDK integration (#11465) 2025-11-26 15:43:32 +08:00
fullex
d4396b4890 docs: update links in Chinese contributing guide
- Corrected the paths in the Chinese version of the contributing guide to point to the appropriate documentation locations.
2025-11-26 13:21:10 +08:00
fullex
283519f1fd Merge branch 'main' of github.com:CherryHQ/cherry-studio 2025-11-26 13:17:09 +08:00
fullex
bb41709ce8 docs: update docs directory structure
- Updated links in CONTRIBUTING.md and README.md to point to the correct Chinese documentation paths.
- Removed outdated files including the English and Chinese versions of the branching strategy, contributing guide, and test plan documents.
- Cleaned up references to non-existent documentation in the project structure to streamline the contributor experience.
2025-11-26 13:17:01 +08:00
Copilot
c1f4b5b9b9 Fix: custom parameters for Gemini models (#11456)
* Initial plan

* fix(aiCore): extract AI SDK standard params from custom params for Gemini

Custom parameters like topK, frequencyPenalty, presencePenalty,
stopSequences, and seed should be passed as top-level streamText()
parameters, not in providerOptions. This fixes the issue where these
parameters were being ignored by the AI SDK's @ai-sdk/google module.

Changes:
- Add extractAiSdkStandardParams function to separate standard params
- Update buildProviderOptions to return both providerOptions and standardParams
- Update buildStreamTextParams to spread standardParams into params object
- Update tests to reflect new return structure

Co-authored-by: DeJeune <67425183+DeJeune@users.noreply.github.com>

* refactor(aiCore): remove extractAiSdkStandardParams function and its tests, streamline parameter extraction logic

* chore: type

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: DeJeune <67425183+DeJeune@users.noreply.github.com>
Co-authored-by: suyao <sy20010504@gmail.com>
2025-11-26 13:16:58 +08:00
SuYao
5fb59d21ec fix: header merging logic via chore ai-sdk (#11443)
* fix: update provider-utils and add patch for header merging logic

* fix: enhance header merging logic to deduplicate values

* fix: handle null values in header merging logic

* chore: update ai-sdk dependencies and remove obsolete patches

- Updated @ai-sdk/amazon-bedrock from 3.0.56 to 3.0.61
- Updated @ai-sdk/anthropic from 2.0.45 to 2.0.49
- Updated @ai-sdk/gateway from 2.0.13 to 2.0.15
- Updated @ai-sdk/google from 2.0.40 to 2.0.43
- Updated @ai-sdk/google-vertex from 3.0.72 to 3.0.79
- Updated @ai-sdk/openai from 2.0.71 to 2.0.72
- Updated @ai-sdk/provider-utils from patch version to 3.0.17
- Removed obsolete patches for @ai-sdk/openai and @ai-sdk/provider-utils
- Added reasoning_content field to OpenAIChat response and chunk schemas
- Enhanced OpenAIChatLanguageModel to handle reasoning content in responses

* chore
2025-11-26 12:31:55 +08:00
Phantom
e8de31ca64 fix: Groq verbosity setting (#11452)
* feat(settings): show OpenAI settings for supported service tier providers

Add support for displaying OpenAI settings when provider supports service tiers.
This includes refactoring the condition check and fixing variable naming consistency.

* fix(settings): set openAI verbosity to undefined by default

* fix(store): bump version to 178 and disable verbosity for groq provider

Add migration to remove verbosity from groq provider and implement provider utility to check verbosity support
Update provider types to include verbosity support flag

* feat(provider): add verbosity option support for providers

Add verbosity parameter support in provider API options settings

* fix(aiCore): check provider support for verbosity before applying

Add provider validation and check for verbosity support to prevent errors when unsupported providers are used with verbosity settings

* feat(settings): add Groq settings group component and translations

add new GroqSettingsGroup component for managing Groq provider settings
update translations for Groq settings in both zh-cn and en-us locales
refactor OpenAISettingsGroup to separate Groq-specific logic

* feat(i18n): add groq settings and verbosity support translations

add translations for groq settings title and verbosity parameter support in multiple languages

* refactor(settings): simplify service tier mode fallback logic

Remove conditional service tier mode fallback and use provider-specific defaults directly

* fix(provider): remove redundant system provider check in verbosity support

* test(provider): add tests for verbosity support detection

* fix(OpenAISettingsGroup): add endpoint_type check for showSummarySetting condition

Add model.endpoint_type check to properly determine when to show summary setting for OpenAI models

* refactor(selector): simplify selector option types and add utility functions

remove undefined and null from selector option types
add utility functions to convert between option values and real values
update groq and openai settings groups to use new utilities
add new translation for "ignore" option

* fix(ApiOptionsSettings): correct checked state for verbosity toggle

* feat(i18n): add "ignore" translation for multiple languages

* refactor(groq): remove unused model prop and related checks

Clean up GroqSettingsGroup component by removing unused model prop and unnecessary service tier checks
2025-11-25 23:29:03 +08:00
Phantom
69d31a1e2b fix(models): qwen-mt-flash supports text delta (#11448)
refactor(models): improve text delta support check for qwen-mt models

Replace direct qwen-mt model check with regex pattern matching
Add comprehensive test cases for isNotSupportTextDeltaModel
Update all references to use new function name
2025-11-25 22:22:18 +08:00
fullex
fd3b7f717d fix: correct updateAssistantPreset reducer to properly update preset (#11453)
The previous implementation used `a = preset` inside forEach, which only
reassigns the local variable and doesn't actually update the array element.

Changed to use findIndex + direct array assignment to properly update
the preset in the state.

Fixes #11451

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-25 20:59:56 +08:00
LiuVaayne
bcd7bc9f2d ⬆️ chore: upgrade @anthropic-ai/claude-agent-sdk to 0.1.53 (#11444)
- Upgrade from 0.1.30 to 0.1.53
- Re-apply fork() patch for Electron IPC compatibility
2025-11-25 18:46:11 +08:00
kangfenmao
4dd92c3ce1 fix: handle optional provider in isSupportedReasoningEffortGrokModel function 2025-11-25 17:22:54 +08:00
SuYao
dc8df98929 fix: websearch button condition (#11440)
fix: button
2025-11-25 13:24:37 +08:00
fullex
0004a8cafe fix: respect enableMaxTokens setting when maxTokens is not configured (#11438)
* fix: respect enableMaxTokens setting when maxTokens is not configured

When enableMaxTokens is disabled, getMaxTokens() should return undefined
to let the API use its own default value, instead of forcing 4096 tokens.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* fix(modelParameters): handle max tokens when feature is disabled

Check if max tokens feature is enabled before returning undefined to ensure proper API behavior

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: icarus <eurfelux@gmail.com>
2025-11-25 11:12:50 +08:00
defi-failure
1992363580 chore: bump version to 1.7.0-rc.2 (#11429) 2025-11-24 14:46:10 +08:00
defi-failure
c901771480 chore: update release notes for v1.7.0-rc.2 (#11426) 2025-11-24 11:30:40 +08:00
SuYao
475f718efb fix: improve error handling and display in AiSdkToChunkAdapter (#11423)
* fix: improve error handling and display in AiSdkToChunkAdapter

* fix: test
2025-11-24 10:57:51 +08:00
SuYao
2c3338939e feat: update Google and OpenAI SDKs with new features and fixes (#11395)
* feat: update Google and OpenAI SDKs with new features and fixes

- Updated Google SDK to ensure model paths are correctly formatted.
- Enhanced OpenAI SDK to include support for image URLs in chat responses.
- Added reasoning content handling in OpenAI chat responses and chunks.
- Introduced Azure Anthropic provider configuration for Claude integration.

* fix: azure error

* fix: lint

* fix: test

* fix: test

* fix type

* fix comment

* fix: redundant

* chore resolution

* fix: test

* fix: comment

* fix: comment

* fix

* feat: 添加 OpenRouter 推理中间件以支持内容过滤
2025-11-23 23:18:57 +08:00
槑囿脑袋
64ca3802a4 feat: support gemini 3 pro image preview (#11416)
feat: support gemini 3 pro preview
2025-11-23 21:40:22 +08:00
Phantom
fa361126b8 refactor: aisdk config (#11402)
* refactor: improve model filtering with todo for robust conversion

* refactor(aiCore): add AiSdkConfig type and update provider config handling

- Introduce new AiSdkConfig type in aiCoreTypes for better type safety
- Update provider factory and config to use AiSdkConfig consistently
- Simplify getAiSdkProviderId return type to string
- Add config validation in ModernAiProvider

* refactor(aiCore): move ai core types to dedicated module

Consolidate AI core type definitions into a dedicated module under aiCore/types. This improves code organization by keeping related types together and removes circular dependencies between modules. The change includes:
- Moving AiSdkConfig to aiCore/types
- Updating all imports to reference the new location
- Removing duplicate type definitions

* refactor(provider): add return type to createAiSdkProvider function
2025-11-23 21:12:57 +08:00
SuYao
49903a1567 Test/ai-core (#11307)
* test: 1

* test: 2

* test: 3

* format

* chore: move provider from config to utils

* fix: 4

* test: 5

* chore: redundant logic

* test: add reasoning model tests and improve provider options typings

* chore: format

* test 6

* chore: format

* test: 7

* test: 8

* fix: test

* fix: format and typecheck

* fix error

* test: isClaude4SeriesModel

* fix: test

* fix: test

---------

Co-authored-by: defi-failure <159208748+defi-failure@users.noreply.github.com>
2025-11-23 17:33:27 +08:00
Phantom
086b16a59c ci: update PR title in auto-i18n workflow to be more specific (#11406) 2025-11-23 11:48:44 +08:00
github-actions[bot]
e2562d8224 🤖 Weekly Automated Update: Nov 23, 2025 (#11412)
feat(bot): Weekly automated script run

Co-authored-by: EurFelux <59059173+EurFelux@users.noreply.github.com>
2025-11-23 11:47:54 +08:00
Phantom
c9be949853 fix: adjacent user messages appear when assistant message contains error only (#11390)
* feat(messages): add filter for error-only messages and their related pairs

Add new filter function to remove assistant messages containing only error blocks along with their associated user messages, identified by askId. This improves conversation quality by cleaning up error-only responses.

* refactor(ConversationService): improve message filtering pipeline readability

Break down complex message filtering chain into clearly labeled steps
Add comments explaining each filtering step's purpose
Maintain same functionality while improving code maintainability

* test(messageUtils): add test cases for message filter utilities

* docs(messageUtils): correct jsdoc for filterUsefulMessages

* refactor(ConversationService): extract message filtering logic into pipeline method

Move message filtering steps into a dedicated static method to improve testability and maintainability. Add comprehensive tests to verify pipeline behavior.

* refactor(ConversationService): add logging and improve message filtering readability

Add logger service to track message pipeline output
Split filterUserRoleStartMessages into separate variable for better debugging
2025-11-22 23:00:13 +08:00
defi-failure
ebfb1c5abf fix: add missing execution state for approved tool permissions (#11394) 2025-11-22 21:45:42 +08:00
SuYao
c1f1d7996d test: add thinking budget token test (#11305)
* refactor: add thinking budget token test

* fix comment
2025-11-22 21:43:57 +08:00
Phantom
0a72c613af fix(openai): apply verbosity setting with type safety improvements (#10964)
* refactor(types): consolidate OpenAI types and improve type safety

- Move OpenAI-related types to aiCoreTypes.ts
- Rename FetchChatCompletionOptions to FetchChatCompletionRequestOptions
- Add proper type definitions for service tiers and verbosity
- Improve type guards for service tier checks

* refactor(api): rename options parameter to requestOptions for consistency

Update parameter name across multiple files to use requestOptions instead of options for better clarity and consistency in API calls

* refactor(aiCore): simplify OpenAI summary text handling and improve type safety

- Remove 'off' option from OpenAISummaryText type and use null instead
- Add migration to convert 'off' values to null
- Add utility function to convert undefined to null
- Update Selector component to handle null/undefined values
- Improve type safety in provider options and reasoning params

* fix(i18n): Auto update translations for PR #10964

* feat(utils): add notNull function to convert null to undefined

* refactor(utils): move defined and notNull functions to shared package

Consolidate utility functions into shared package to improve code organization and reuse

* Revert "fix(i18n): Auto update translations for PR #10964"

This reverts commit 68bd7eaac5.

* feat(i18n): add "off" translation and remove "performance" tier

Add "off" translation for multiple languages and remove "performance" service tier option from translations

* Apply suggestion from @EurFelux

* docs(types): clarify handling of undefined and null values

Add comments to explain that undefined is treated as default and null as explicitly off in OpenAIVerbosity and OpenAIServiceTier types. Also update type safety for OpenAIServiceTiers record.

* fix(migration): update migration version from 167 to 171 for removed type

* chore: update store version to 172

* fix(migrate): update migration version number from 171 to 172

* fix(i18n): Auto update translations for PR #10964

* refactor(types): improve type safety for verbosity handling

add NotUndefined and NotNull utility types to better handle null/undefined cases
clarify verbosity types in aiCoreTypes and update related utility functions

* refactor(types): replace null with undefined for verbosity values

Standardize on undefined instead of null for verbosity values to align with OpenAI API docs and improve type consistency

* refactor(aiCore): update OpenAI provider options type import and usage

* fix(openai): change summaryText default from null to 'auto'

Update OpenAI settings to use 'auto' as default summaryText value instead of null for consistency with API behavior. Remove 'off' option and add 'concise' option while maintaining type safety.

* refactor(OpenAISettingsGroup): extract service tier options type for better maintainability

* refactor(types): make SystemProviderIdTypeMap internal type

* docs(provider): clarify OpenAIServiceTier behavior for undefined vs null

Explain that undefined and null values for serviceTier should be treated differently since they affect whether the field appears in the response

* refactor(utils): rename utility functions for clarity

Rename `defined` to `toNullIfUndefined` and `notNull` to `toUndefinedIfNull` to better reflect their functionality

* refactor(aiCore): extract service tier logic and improve type safety

Extract service tier validation logic into separate functions for better reusability
Add proper type annotations for provider options
Pass service tier parameter through provider option builders

* refactor(utils): comment out unused utility functions

Keep commented utility functions for potential future use while cleaning up current codebase

* fix(migration): update migration version number from 172 to 177

* docs(aiCoreTypes): clarify parameter passing behavior in OpenAI API

Update comments to consistently use 'undefined' instead of 'null' when describing parameter passing behavior in OpenAI API requests, as they share the same meaning in this context

---------

Co-authored-by: GitHub Action <action@github.com>
2025-11-22 21:41:12 +08:00
SuYao
a1ac3207f1 fix/anthropic-vertex (#11397)
* 100m

* feat: add web search header for Claude 4 series models

* fix: typo

* fix: identify model

---------

Co-authored-by: defi-failure <159208748+defi-failure@users.noreply.github.com>
2025-11-22 20:56:05 +08:00
Caelan
f98a063a8f Fix the issue where base64 images cannot be saved (#11398) 2025-11-22 20:20:02 +08:00
亢奋猫
1cb2af57ae refactor: optimize DatabaseManager and fix libsql crash issues (#11392)
* refactor: optimize DatabaseManager and fix libsql crash issues

Major improvements:
- Created DatabaseManager singleton to centralize database connection management
- Auto-initialize database in constructor (no manual initialization needed)
- Removed all manual initialize() and ensureInitialized() calls (47 occurrences)
- Simplified initialization logic (removed retry loops that could cause crashes)
- Removed unused close() and reinitialize() methods
- Reduced code from ~270 lines to 172 lines (-36%)

Key changes:
1. DatabaseManager.ts (new file):
   - Singleton pattern with auto-initialization
   - State management (INITIALIZING, INITIALIZED, FAILED)
   - Windows compatibility fixes (empty file detection, intMode: 'number')
   - Simplified waitForInitialization() logic

2. BaseService.ts:
   - Removed static initialize() and ensureInitialized() methods
   - Simplified database/rawClient getters to use DatabaseManager

3. Service classes (AgentService, SessionService, SessionMessageService):
   - Removed all initialize() methods
   - Removed all ensureInitialized() calls
   - Services now work out of the box

4. Main entry points (index.ts, server.ts):
   - Removed explicit database initialization calls
   - Database initializes automatically on first access

Benefits:
- Fixes Windows libsql crashes by removing dangerous retry logic
- Simpler API - no need to remember to call initialize()
- Better separation of concerns
- Cleaner codebase with 36% less code

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* fix: wait for database initialization on app startup

Issue: "Database is still initializing" error on startup
Root cause: Synchronous database getter was called before async initialization completed

Solution:
- Explicitly wait for database initialization in main index.ts
- Import DatabaseManager and call getDatabase() to ensure initialization is complete
- This guarantees database is ready before any service methods are called

Changes:
- src/main/index.ts: Added explicit database initialization wait before API server check

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* refactor: use static import for getDatabaseManager

- Move import to top of file for better code organization
- Remove unnecessary dynamic import

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* refactor: streamline database access in service classes

- Replaced direct database access with asynchronous calls to getDatabase() in various service classes (AgentService, SessionService, SessionMessageService).
- Updated the main index.ts to utilize runAsyncFunction for API server initialization, ensuring proper handling of asynchronous database access.
- Improved code organization and readability by consolidating database access logic.

This change enhances the reliability of database interactions across the application and ensures that services are correctly initialized before use.

* refactor: remove redundant logging in ApiServer initialization

- Removed the logging statement for 'AgentService ready' during server initialization.
- This change streamlines the startup process by eliminating unnecessary log entries.

This update contributes to cleaner logs and improved readability during server startup.

* refactor: change getDatabase method to synchronous return type

- Updated the getDatabase method in DatabaseManager to return a synchronous LibSQLDatabase instance instead of a Promise.
- This change simplifies the database access pattern, aligning with the current initialization logic.

This refactor enhances code clarity and reduces unnecessary asynchronous handling in the database access layer.

* refactor: simplify sessionMessageRepository by removing transaction handling

- Removed transaction handling parameters from message persistence methods in sessionMessageRepository.
- Updated database access to use a direct call to getDatabase() instead of passing a transaction client.
- Streamlined the upsertMessage and persistExchange methods for improved clarity and reduced complexity.

This refactor enhances code readability and simplifies the database interaction logic.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-22 09:12:11 +08:00
fullex
62309ae1bf fix: prevent EventEmitter memory leak in useApiServer hook (#11385)
Implement single instance IPC subscription pattern to resolve MaxListenersExceededWarning. Previously, each component using useApiServer would register a separate 'api-server:ready' listener, and React strict mode double rendering would quickly exceed the 10 listener limit.

Changes:
- Add module-level subscription manager with onReadyCallbacks Set
- Ensure only one IPC listener is registered regardless of component count
- Use useRef to maintain stable callback references
- Properly cleanup subscriptions when all components unmount

This maintains existing behavior while keeping listener count constant at 1.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-21 21:42:34 +08:00
defi-failure
c48f222cdb feat: add endpoint type support for cherryin provider (#11367)
* feat: add endpoint type support for cherryin provider

* chore: bump @cherrystudio/ai-sdk-provider version to 0.1.1

* chore: bump ai-sdk-provider version to 0.1.3
2025-11-21 21:42:08 +08:00
亢奋猫
cea0058f87 refactor: simplify knowledge base creation modal (#11371)
* test(knowledge): fix tests for knowledge base form modal refactoring

Update all test files to match the new vertical layout structure with button-based advanced settings toggle. Remove obsolete tests for deleted features.

Changes:
- Rewrite KnowledgeBaseFormModal.test.tsx for new button-toggle structure
- Remove tests for preprocess and rerank features from GeneralSettingsPanel
- Update AdvancedSettingsPanel tests with required props
- Update all snapshots to reflect new component structure
- Format test files according to biome rules

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* test(knowledge): simplify KnowledgeBaseFormModal button tests

Simplify button interaction tests to avoid text matching issues. Focus on testing behavior rather than implementation details.

Changes:
- Simplify advanced settings toggle test
- Simplify footer buttons test to check button count instead of text content
- Remove fragile text-based button selection

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-21 21:34:34 +08:00
beyondkmp
852192dce6 feat: add Git Bash detection and requirement check for Windows agents (#11388)
* feat: add Git Bash detection and requirement check for Windows agents

- Add System_CheckGitBash IPC channel for detecting Git Bash installation
- Implement detection logic checking common installation paths and PATH environment
- Display non-closable error alert in AgentModal when Git Bash is not found
- Disable agent creation/edit button until Git Bash is installed
- Add recheck functionality to verify installation without restarting app

Git Bash is required for agents to function properly on Windows systems.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* i18n: add Git Bash requirement translations for agent modal

- Add English translations for Git Bash detection warnings
- Add Simplified Chinese (zh-cn) translations
- Add Traditional Chinese (zh-tw) translations

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* format code

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-21 21:32:53 +08:00
Pleasure1234
eee49d1580 feat: add ChatGPT conversation import feature (#11272)
* feat: add ChatGPT conversation import feature

Introduces a new import workflow for ChatGPT conversations, including UI components, service logic, and i18n support for English, Simplified Chinese, and Traditional Chinese. Adds an import menu to data settings, a popup for file selection and progress, and a service to parse and store imported conversations as topics and messages.

* fix: ci failure

* refactor: import service and add modular importers

Refactored the import service to support a modular importer architecture. Moved ChatGPT import logic to a dedicated importer class and directory. Updated UI components and i18n descriptions for clarity. Removed unused Redux selector in ImportMenuSettings. This change enables easier addition of new importers in the future.

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fix: improve ChatGPT import UX and set model for assistant

Added a loading state and spinner for file selection in the ChatGPT import popup, with new translations for the 'selecting' state in en-us, zh-cn, and zh-tw locales. Also, set the model property for imported assistant messages to display the GPT-5 logo.

---------

Co-authored-by: SuYao <sy20010504@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-21 14:58:47 +08:00
SuYao
dcdd1bf852 refactor: replace renderToolContent function with ToolContent component for improved readability (#11300)
* refactor: replace renderToolContent function with ToolContent component for improved readability

* fix

* fix test
2025-11-21 09:55:46 +08:00
beyondkmp
a12b6bfeca feat: enable native language emoji search with CLDR data format (#11381)
* feat: add i18n support and local data to emoji picker

- Add emoji-picker-element-data package for offline-first emoji data
- Implement i18n translations for emoji picker UI (de, en, es, fr, ja, pt, ru, zh)
- Switch from CDN to local emoji data to improve performance and reliability
- Add locale mapping to match app language with emoji picker data
- Move emoji-picker-element import to EmojiPicker component for better encapsulation
- Use proper TypeScript types instead of 'any' for type safety

This improves user experience by providing localized emoji picker interface
and eliminating dependency on external CDN, ensuring the picker works offline.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* feat: enable native language emoji search with CLDR data format

Switch from emojibase to CLDR format for emoji-picker-element data to support full multi-language search functionality. Users can now search for emojis in their native language (e.g., German users can search "Herz" for ❤️, Spanish users can search "corazón"). Also improves type safety by using the LanguageVarious type for locale mappings.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-20 19:23:27 +08:00
亢奋猫
0f1a487bb0 refactor: simplify agent creation form (#11369)
* refactor(AgentModal): simplify agent type handling and update default values

- Removed unused agent type options and related logic.
- Updated default agent name from 'Claude Code' to 'Agent'.
- Adjusted padding in button styles and textarea rows for better UI consistency.
- Cleaned up unnecessary imports and code comments for improved readability.

* refactor(AgentSettings): clean up and enhance name setting component

- Removed unused imports and commented-out code in AgentModal and EssentialSettings.
- Updated NameSetting to include an emoji avatar picker for enhanced user experience.
- Simplified the logic for updating the agent's name and avatar.
- Improved overall readability and maintainability of the code.
2025-11-20 10:42:49 +08:00
亢奋猫
2df8bb58df fix: remove light background from MCP NpxUv install alerts (#11372)
- Remove 'banner' prop from Alert components in InstallNpxUv
- Set SettingContainer background to 'inherit' in MCP settings
- Fixes the light background color issue in NpxUv interface

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-20 10:41:41 +08:00
defi-failure
62976f6fe0 refactor: namespace tool call ids with session id to prevent conflicts (#11319) 2025-11-20 10:35:11 +08:00
MyPrototypeWhat
77529b3cd3 chore: update ai-core release scripts and bump version to 1.0.7 (#11370)
* chore: update ai-core release scripts and bump version to 1.0.7

* chore: update ai-sdk-provider release script to include build step and enhance type exports in webSearchPlugin and providers

* chore: bump @cherrystudio/ai-core version to 1.0.8 and update dependencies in package.json and yarn.lock

* chore: bump @cherrystudio/ai-core version to 1.0.9 and @cherrystudio/ai-sdk-provider version to 0.1.2 in package.json and yarn.lock

---------

Co-authored-by: suyao <sy20010504@gmail.com>
2025-11-19 20:44:22 +08:00
SuYao
c8e9a10190 bump ai core version (#11363)
* bump ai core version

* chore

* chore: add patch for @ai-sdk/openai and update peer dependencies in aiCore

* chore: update installation instructions in README to include @ai-sdk/google and @ai-sdk/openai

* chore: bump @cherrystudio/ai-core version to 1.0.6 in package.json and yarn.lock

---------

Co-authored-by: MyPrototypeWhat <daoquqiexing@gmail.com>
2025-11-19 18:13:33 +08:00
scientia
0e011ff35f fix: fix api-host for vercel ai-gateway provider (#11321)
Co-authored-by: scientia <wangdenghui@xiaomi.com>
2025-11-19 17:11:17 +08:00
MyPrototypeWhat
40a64a7c92 feat(options): enhance provider key handling for cherryin in buildPro… (#11361)
feat(options): enhance provider key handling for cherryin in buildProviderOptions function
2025-11-19 16:25:29 +08:00
Phantom
dc9503ef8b feat: support gemini 3 (#11356)
* feat(reasoning): add support for gemini-3-pro-preview model

Update regex pattern to include gemini-3-pro-preview as a supported thinking model
Add tests for new gemini-3 model support and edge cases

* fix(reasoning): update gemini model regex to include stable versions

Add support for stable versions of gemini-3-flash and gemini-3-pro in the model regex pattern. Update tests to verify both preview and stable versions are correctly identified.

* feat(providers): add vertexai provider check function

Add isVertexAiProvider function to consistently check for vertexai provider type and use it in websearch model detection

* feat(websearch): update gemini search regex to include v3 models

Add support for gemini 3.x models in the search regex pattern, including preview versions

* feat(vision): add support for gemini-3 models and add tests

Add regex pattern for gemini-3 models in visionAllowedModels
Create comprehensive test suite for isVisionModel function

* refactor(vision): make vision-related model constants private

Remove unused isNotSupportedImageSizeModel function and change exports to const declarations for internal use only

* chore(deps): update @ai-sdk/google to v2.0.36 and related dependencies

update @ai-sdk/google dependency from v2.0.31 to v2.0.36 to include fixes for model path handling and tool support for newer Gemini models

* chore: remove outdated @ai-sdk-google patch file

* chore: remove outdated @ai-sdk/google patch dependency
2025-11-19 14:05:14 +08:00
beyondkmp
f2c8484c48 feat: enable local crash mini dump file (#11348)
* feat: enabel loca crash mini file dump

* update version
2025-11-18 18:27:57 +08:00
kangfenmao
a9c9224835 fix(migrate): update anthropicApiHost for qiniu and longcat providers in migration to version 176
- Added anthropicApiHost configuration for qiniu and longcat providers during state migration.
- Incremented version number in persistedReducer to 176.
- Ensured proper handling of reasoning_effort settings during migration.
2025-11-18 11:05:46 +08:00
caoli5288
43223fd1f5 feat(config): add anthropicApiHost for qiniu and longcat providers (#11335) 2025-11-18 10:10:59 +08:00
Phantom
4bac843b37 fix(InputbarCore): prevent message send when cannotSend is true (#11337)
Add cannotSend check to prevent message sending when conditions aren't met
2025-11-18 10:08:54 +08:00
Phantom
34723934f4 fix: use function as default tool use mode (#11338)
* refactor(assistant): change default tool use mode to function and use default settings

Simplify reset logic by using DEFAULT_ASSISTANT_SETTINGS object instead of hardcoded values

* fix(ApiService): safely fallback to prompt tool use for unsupported models

Add check for function calling model support before using tool use mode to prevent errors with unsupported models.
2025-11-17 23:28:43 +08:00
defi-failure
096c36caf8 fix: improve todo tool status icon visibility and colors (#11323) 2025-11-17 14:01:27 +08:00
beyondkmp
139950e193 fix(i18n): add input placeholder translations for multiple languages (#11320)
feat(i18n): add input placeholder translations for multiple languages

- Introduced a new placeholder for the input field in various language files, providing guidance on message entry and command selection.
- Updated English, Chinese (Simplified and Traditional), German, Greek, Spanish, French, Japanese, Portuguese, and Russian translations to include the new input placeholder text.
- Adjusted the reference in the AgentSessionInputbar component to use the new translation key for consistency.
2025-11-17 11:51:04 +08:00
SuYao
31eec403f7 fix: url context and web search capability (#11306)
* fix: enhance support for interleaved thinking and model compatibility

* fix: type
2025-11-17 10:53:47 +08:00
槑囿脑袋
7fd4837a47 fix: mineru validate pdf error and 403 error (#11312)
* fix: validate pdf error

* fix: net fetch error

* fix: mineru 403 error

* chore: change comment to english

* fix: format
2025-11-16 16:02:15 +00:00
Carlton
90b0c8b4a6 fix: resolve "no such file" error when processing non-English filenames in open-mineru (#11315) 2025-11-16 22:10:43 +08:00
github-actions[bot]
556353e910 docs: Weekly Automated Update: Nov 16, 2025 (#11308)
feat(bot): Weekly automated script run

Co-authored-by: EurFelux <59059173+EurFelux@users.noreply.github.com>
2025-11-16 10:57:32 +08:00
Copilot
11fb730b4d fix: add verbosity parameter support for GPT-5 models across legacy and modern AI SDK (#11281)
* Initial plan

* feat: add verbosity parameter support for GPT-5 models in OpenAIAPIClient

Co-authored-by: DeJeune <67425183+DeJeune@users.noreply.github.com>

* fix: ensure gpt-5-pro always uses 'high' verbosity

Co-authored-by: DeJeune <67425183+DeJeune@users.noreply.github.com>

* refactor: move verbosity configuration to config/models as suggested

Co-authored-by: DeJeune <67425183+DeJeune@users.noreply.github.com>

* refactor: encapsulate verbosity logic in getVerbosity method

Co-authored-by: DeJeune <67425183+DeJeune@users.noreply.github.com>

* feat: add support for verbosity and reasoning options for GPT-5 Pro and GPT-5.1 models

* fix comment

* build: add @ai-sdk/google dependency

Add the @ai-sdk/google package to support Google AI SDK integration

* build: add @ai-sdk/anthropic dependency

* refactor(aiCore): update reasoning params handling for AI providers

- Add type imports for provider options
- Handle 'none' reasoning effort consistently across providers
- Improve type safety by using Pick with provider options
- Standardize disabled reasoning config for all providers

* fix: adjust none effort ratio from 0 to 0.01

Prevent potential division by zero errors by ensuring none effort ratio has a small positive value

* feat(reasoning): add support for GPT-5.1 series models

Handle 'none' reasoning effort for GPT-5.1 models and add model type check

* Update src/renderer/src/aiCore/utils/reasoning.ts

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: DeJeune <67425183+DeJeune@users.noreply.github.com>
Co-authored-by: suyao <sy20010504@gmail.com>
Co-authored-by: icarus <eurfelux@gmail.com>
2025-11-16 10:22:14 +08:00
Phantom
2511113b62 feat: support gpt-5.1 (#11294)
* build: update @cherrystudio/openai dependency from v6.5.0 to v6.9.0

* refactor(reasoning): replace 'off' with 'none' for reasoning effort option

Update reasoning effort option from 'off' to 'none' across multiple files for consistency
Add support for gpt5_1 model with reasoning effort options

* fix(openai): handle apply_patch_call and apply_patch_call_output in response conversion

Filter and properly handle apply_patch_call and apply_patch_call_output types in OpenAI response conversion. Ensure undefined/null values are handled appropriately and log warnings for missing required fields.

* feat(models): add gpt-5.1 model logo and configuration

* fix(providers): include cherryin in url context provider check

Add SystemProviderIds.cherryin to the list of providers that support URL context to ensure proper functionality

* feat(models): add logo images for gpt-5.1 model variants

* feat(model): add support for GPT-5.1 series models

- Add new model type check for GPT-5.1 series
- Update reasoning effort and verbosity checks to include GPT-5.1
- Add logging to provider options builder

* feat(models): add gpt5_1_codex model support

Add new model type 'gpt5_1_codex' to ThinkModelTypes and configure its reasoning effort levels
Update model type detection logic to handle gpt5_1_codex variant
2025-11-15 19:09:43 +08:00
beyondkmp
a29b2bb3d6 chore: update @opeoginni/github-copilot-openai-compatible to support gpt5.1 (#11299)
* chore: update @opeoginni/github-copilot-openai-compatible to version 0.1.21

- Updated package version in package.json and yarn.lock.
- Refactored OpenAIBaseClient to enhance getBaseURL method and improve header management for SDK instances.

* format
2025-11-15 19:07:16 +08:00
beyondkmp
d2be450906 fix: update gitcode update config url (#11298)
* fix: update gitcode update config url

* update version

---------

Co-authored-by: Payne Fu <payne@Paynes-MacBook-Pro.local>
2025-11-15 10:01:33 +08:00
kangfenmao
9c020f0d56 docs: update release notes for v1.7.0-rc.1
Add comprehensive release notes highlighting:
- AI Agent system as the major new feature
- New AI providers support (Hugging Face, Mistral, Perplexity, SophNet)
- Knowledge base enhancements (OpenMinerU, full-text search)
- Image & OCR improvements (Intel OVMS, OpenVINO NPU)
- MCP management interface redesign with dual-column layout
- German language support
- Electron 38.7.0 upgrade and system improvements
- Important bug fixes
2025-11-14 20:04:16 +08:00
fullex
e033eb5b5c Add CODEOWNER for app-upgrade-config.json 2025-11-14 19:02:03 +08:00
beyondkmp
073d43c7cb chore: rename cs-releases to x-files/app-upgrade-config (#11290)
rename cs-releases to x-files/app-upgrade-config
2025-11-14 18:53:11 +08:00
kangfenmao
fa7646e18f feat: enhance DynamicVirtualList with header and className props
- Added `header` prop to display content above the list.
- Introduced `className` prop for additional styling on the container.
- Updated `Sessions` component to utilize `StyledVirtualList` with new props for improved layout and functionality.
2025-11-14 18:29:33 +08:00
beyondkmp
038d30831c ♻️ refactor: implement config-based update system with version compatibility control (#11147)
* ♻️ refactor: implement config-based update system with version compatibility control

Replace GitHub API-based update discovery with JSON config file system. Support
version gating (users below v1.7 must upgrade to v1.7.0 before v2.0). Auto-select
GitHub/GitCode config source based on IP location. Simplify fallback logic.

Changes:
- Add update-config.json with version compatibility rules
- Implement _fetchUpdateConfig() and _findCompatibleChannel()
- Remove legacy _getReleaseVersionFromGithub() and GitHub API dependency
- Refactor _setFeedUrl() with simplified fallback to default feed URLs
- Add design documentation in docs/UPDATE_CONFIG_DESIGN.md

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* fix(i18n): Auto update translations for PR #11147

* format code

* 🔧 chore: update config for v1.7.5 → v2.0.0 → v2.1.6 upgrade path

Update version configuration to support multi-step upgrade path:
- v1.6.x users → v1.7.5 (last v1.x release)
- v1.7.x users → v2.0.0 (v2.x intermediate version)
- v2.0.0+ users → v2.1.6 (current latest)

Changes:
- Update 1.7.0 → 1.7.5 with fixed feedUrl
- Set 2.0.0 as intermediate version with fixed feedUrl
- Add 2.1.6 as current latest pointing to releases/latest

This ensures users upgrade through required intermediate versions
before jumping to major releases.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* 🔧 chore: refactor update config with constants and adjust versions

Refactor update configuration system and adjust to actual versions:

- Add UpdateConfigUrl enum in constant.ts for centralized config URLs
- Point to test server (birdcat.top) for development testing
- Update AppUpdater.ts to use UpdateConfigUrl constants
- Adjust update-config.json to actual v1.6.7 with rc/beta channels
- Remove v2.1.6 entry (not yet released)
- Set package version to 1.6.5 for testing upgrade path
- Add update-config.example.json for reference

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* update version

*  test: add comprehensive unit tests for AppUpdater config system

Add extensive test coverage for new config-based update system including:
- Config fetching with IP-based source selection (GitHub/GitCode)
- Channel compatibility matching with version constraints
- Smart fallback from rc/beta to latest when appropriate
- Multi-step upgrade path validation (1.6.3 → 1.6.7 → 2.0.0)
- Error handling for network and HTTP failures

Test Coverage:
- _fetchUpdateConfig: 4 tests (GitHub/GitCode selection, error handling)
- _findCompatibleChannel: 9 tests (channel matching, version comparison)
- Upgrade Path: 3 tests (version gating scenarios)
- Total: 30 tests, 100% passing

Also optimize _findCompatibleChannel logic with better variable naming
and log messages.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

*  test: add complete multi-step upgrade path tests (1.6.3 → 1.7.5 → 2.0.0 → 2.1.6)

Add comprehensive test suite for complete upgrade journey including:
- Individual step validation (1.6.3→1.7.5, 1.7.5→2.0.0, 2.0.0→2.1.6)
- Full multi-step upgrade simulation with version progression
- Version gating enforcement (block skipping intermediate versions)
- Verification that 1.6.3 cannot directly upgrade to 2.0.0 or 2.1.6
- Verification that 1.7.5 cannot skip 2.0.0 to reach 2.1.6

Test Coverage:
- 6 new tests for complete upgrade path scenarios
- Total: 36 tests, 100% passing

This ensures the version compatibility system correctly enforces
intermediate version upgrades for major releases.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* 📝 docs: reorganize update config documentation with English translation

Move update configuration design document to docs/technical/ directory
and add English translation for international contributors.

Changes:
- Move docs/UPDATE_CONFIG_DESIGN.md → docs/technical/app-update-config-zh.md
- Add docs/technical/app-update-config-en.md (English translation)
- Organize technical documentation in dedicated directory

Documentation covers:
- Config-based update system design and rationale
- JSON schema with version compatibility control
- Multi-step upgrade path examples (1.6.3 → 1.7.5 → 2.0.0 → 2.1.6)
- TypeScript type definitions and matching algorithms
- GitHub/GitCode source selection for different regions

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* format code

*  test: add tests for latest channel self-comparison prevention

Add tests to verify the optimization that prevents comparing latest
channel with itself when latest is requested, and ensures rc/beta
channels are returned when they are newer than latest.

New tests:
- should not compare latest with itself when requesting latest channel
- should return rc when rc version > latest version
- should return beta when beta version > latest version

These tests ensure the requestedChannel !== UpgradeChannel.LATEST
check works correctly and users get the right channel based on
version comparisons.

Test Coverage: 39 tests, 100% passing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* update github/gitcode

* format code

* update rc version

* ♻️ refactor: merge update configs into single multi-mirror file

- Merge app-upgrade-config-github.json and app-upgrade-config-gitcode.json into single app-upgrade-config.json
- Add UpdateMirror enum for type-safe mirror selection
- Optimize _fetchUpdateConfig to receive mirror parameter, eliminating duplicate IP country checks
- Update ChannelConfig interface to use Record<UpdateMirror, string> for feedUrls
- Rename documentation files from app-update-config-* to app-upgrade-config-*
- Update docs with new multi-mirror configuration structure

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

*  test: update AppUpdater tests for multi-mirror configuration

- Add UpdateMirror enum import
- Update _fetchUpdateConfig tests to accept mirror parameter
- Convert all feedUrl to feedUrls structure in test mocks
- Update test expectations to match new ChannelConfig interface
- All 39 tests passing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* format code

* delete files

* 📝 docs: add UpdateMirror enum to type definitions

- Add UpdateMirror enum definition in both EN and ZH docs
- Update ChannelConfig to use Record<UpdateMirror, string>
- Add comments showing equivalent structure for clarity

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* 🐛 fix: return actual channel from _findCompatibleChannel

Fix channel mismatch issue where requesting rc/beta but getting latest:
- Change _findCompatibleChannel return type to include actual channel
- Return { config, channel } instead of just config
- Update _setFeedUrl to use actualChannel instead of requestedChannel
- Update all test expectations to match new return structure
- Add channel assertions to key tests

This ensures autoUpdater.channel matches the actual feed URL being used.

Fixes issue where:
- User requests 'rc' channel
- latest >= rc, so latest config is returned
- But channel was set to 'rc' with latest URL 
- Now channel is correctly set to 'latest' 

All 39 tests passing 

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* update version

* udpate version

* update config

* add no cache header

* update files

* 🤖 chore: automate app upgrade config updates

* format code

* update workflow

* update get method

* docs: document upgrade workflow automation

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: GitHub Action <action@github.com>
2025-11-14 17:49:40 +08:00
defi-failure
68ee5164f0 fix: session list can't scroll (#11285) 2025-11-14 17:10:13 +08:00
beyondkmp
a1a3b9bd96 fix: can hide when close the app to tray (#11282)
* fix: can hide when close the app to tray

* format code

* udpate version
2025-11-14 16:52:09 +08:00
SuYao
4e699c48bc fix: update Azure OpenAI API version references to v1 in configuration and translations (#10799)
* fix: update Azure OpenAI API version references to v1 in configuration and translations

* fix: support Azure OpenAI API v1 in client compatibility check

* fix: lint/ format
2025-11-14 13:10:13 +08:00
Zhaokun
75fcf8fbb5 fix: notes content search next scroll (#10908)
* fix: topic branch incomplete copy - split ID mapping into two passes

Fix the bug where topic branching would not copy all message relationships completely.The issue was that askId mapping lookup happened in the same loop as ID generation, causing later messages' askIds to fail mapping when they referenced messages that hadn't been processed yet.

Solution: Split into two passes:
 1. First pass: Generate new IDs for all messages and build complete mapping
 2. Second pass: Clone messages and blocks using the complete ID mapping

This ensures all message relationships (especially assistant message askId references)are properly maintained in the new topic.

* fix(notes): 保持 Ctrl+F ‘下一个’在编辑器容器内滚动,避免索引提前回到第一条

- 使用传入的滚动容器计算相对偏移并 target.scrollTo 居中
- 容器不可滚动时回退到 scrollIntoView,兼容其他页面
- 将 target 纳入依赖,确保引用最新容器

受影响文件:
- src/renderer/src/components/ContentSearch.tsx:165

* fix(search): improve notes content search next-scroll behavior

* Update dom.ts

---------

Co-authored-by: Pleasurecruise <3196812536@qq.com>
2025-11-14 11:51:18 +08:00
Phantom
35aa9d7355 fix: Incorrect navigation when creating new message with @ (#10930)
* fix(message): Incorrect navigation when creating new message with @

Update variable name from newAssistantStub to newAssistantMessageStub for clarity
Add dispatch calls to update message folding state
Remove unused message length tracking effect in MessageGroup

Fixes #10928

* refactor(MessageGroup): remove unused prevMessageLengthRef variable
2025-11-14 11:45:10 +08:00
Pleasure1234
b08aecb22b fix: enable numeric sorting for note names (#11261)
Updated the sorting logic in getSorter to use the 'numeric' option in localeCompare for all name-based sorts. This ensures that note names containing numbers are sorted in a more natural, human-friendly order.
2025-11-14 11:37:19 +08:00
Phantom
45fc6c2afd fix: minimax new api host & anthropic api support (#11269)
* feat(models): add MiniMax M2 models to default configuration

* fix(config): update minimax api host and add anthropic host

Update the API endpoint for MiniMax provider and add a new endpoint for Anthropic integration

* feat: add minimax to ANTHROPIC_COMPATIBLE_PROVIDER_IDS

* docs(ProviderSetting): add todo comment for reset button

* fix(store): update minimax provider config in migration 174

Add anthropicApiHost to minimax provider configuration during state migration

* fix(store): revert version and remove unused migration

Remove migration for version 175 and revert persisted reducer version to 174
2025-11-14 10:55:41 +08:00
defi-failure
d6e7ce330e feat: move error response to top and enlarge window for easier debugging (#11169) 2025-11-13 18:22:00 +08:00
枫亚
4f7d8731ea fix: correct typo in zh-cn locale (#11270) 2025-11-13 17:04:39 +08:00
SuYao
2b5ac5ab51 feat: 添加 AI Gateway Provider (#11064)
* feat: 添加 AI Gateway 提供者支持,包括配置、类型定义和本地化文本

* fix(i18n): Auto update translations for PR #11064

* fix/typecheck

* fix(i18n): Auto update translations for PR #11064

* fix(i18n): Auto update translations for PR #11064

* feat: cerebras

* fix: glm

* fix: minimax api host

---------

Co-authored-by: GitHub Action <action@github.com>
2025-11-13 16:09:49 +08:00
kangfenmao
060fcd2ce6 chore: update release notes for v1.7.0-beta.6
- Update releaseNotes in electron-builder.yml with comprehensive changelog
- Document inputbar system refactor with scope-based architecture
- Include AI SDK provider integration details
- Add bug fixes and improvements documentation
- Provide bilingual release notes (English/Chinese)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

chore: simplify release notes for v1.7.0-beta.6

- Rewrite release notes to focus on user-facing improvements
- Remove technical jargon and developer-specific details
- Use clear, user-friendly language for features and fixes
- Maintain bilingual support (English/Chinese)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-13 10:01:18 +08:00
SuYao
a6182eaf85 Refactor/inputbar (#10332)
* Refactor inputbar system with configurable scope-based architecture

- **Implement scope-based configuration** for chat, agent sessions, and mini-window with feature toggles
- **Add tool registry system** with dependency injection for modular inputbar tools
- **Create shared state management** via InputbarToolsProvider for consistent state handling
- **Migrate existing tools** to registry-based definitions with proper scope filtering

The changes introduce a flexible inputbar architecture that supports different use cases through scope-based configuration while maintaining feature parity and improving code organization.

* Remove unused import and refactor tool rendering

- Delete obsolete '@renderer/pages/home/Inputbar/tools' import from Inputbar.tsx
- Extract ToolButton component to render tools outside useMemo dependency cycle
- Store tool definitions in config for deferred rendering with current context
- Fix potential stale closure issues in tool rendering by rebuilding context on each render

* Wrap ToolButton in React.memo and optimize quick panel menu updates

- Memoize ToolButton component to prevent unnecessary re-renders when tool key remains unchanged
- Replace direct menu state updates with version-based triggering to batch registry changes
- Add useEffect to consolidate menu updates and reduce redundant flat operations

* chore style

* refactor(InputbarToolsProvider): simplify quick panel menu update logic

* Improve QuickPanel behavior and input handling

- Default select first item when panel symbol changes to enhance user experience
- Add Tab key support for selecting template variables in input field
- Refactor QuickPanel trigger logic with better symbol tracking and boundary checks
- Fix typo in translation key for model selection menu item

* Refactor import statements to use type-only imports

- Convert inline type imports to explicit type imports in Inputbar.tsx and types.ts
- Replace combined type/value imports with separate type imports in InputbarToolsProvider and tools
- Remove unnecessary menu version state and effect in InputbarToolsProvider

* Refactor InputbarTools context to separate state and dispatch concerns

- Split single context into separate state and dispatch contexts to optimize re-renders
- Introduce derived state for `couldMentionNotVisionModel` based on file types
- Encapsulate Quick Panel API in stable object with memoized functions
- Add internal dispatch context for Inputbar-specific state setters

* Refactor Inputbar to use split context hooks and optimize QuickPanel

- Replace monolithic `useInputbarTools` with separate state, dispatch, and internal dispatch hooks
- Move text state from context to local component state in InputbarInner
- Optimize QuickPanel trigger registration to use ref pattern, avoiding frequent re-registrations

* Refactor QuickPanel API to separate concerns between tools and inputbar

- Split QuickPanel API into `toolsRegistry` for tool registration and `triggers` for inputbar triggering
- Remove unused QuickPanel state variables and clean up dependencies
- Update tool context to use new API structure with proper type safety

* Optimize the state management of QuickPanel and Inputbar, add text update functionality, and improve the tool registration logic.

* chore

* Add reusable React hooks and InputbarCore component for chat input

- Create `useInputText`, `useKeyboardHandler`, and `useTextareaResize` hooks for text management, keyboard shortcuts, and auto-resizing
- Implement `InputbarCore` component with modular toolbar sections, drag-drop support, and textarea customization
- Add `useFileDragDrop` and `usePasteHandler` hooks for file uploads and paste handling with type filtering

* Refactor Inputbar to use custom hooks for text and textarea management

- Replace manual text state with useInputText hook for text management and empty state
- Replace textarea resize logic with useTextareaResize hook for automatic height adjustment
- Add comprehensive refactoring documentation with usage examples and guidelines

* Refactor inputbar drag-drop and paste handling into custom hooks

- Extract paste handling logic into usePasteHandler hook
- Extract drag-drop file handling into useFileDragDrop hook
- Remove inline drag-drop state and handlers, use hook interfaces
- Clean up dependencies and callback optimizations

* Refactor Inputbar component to use InputbarCore composition

- Extract complex UI logic into InputbarCore component for better separation of concerns
- Remove intermediate wrapper component and action ref forwarding pattern
- Consolidate focus/blur handlers and simplify component structure

* Refactor Inputbar to expose actions via ref for external control

- Extract action handlers into ProviderActionHandlers interface and expose via ref
- Split component into Inputbar wrapper and InputbarInner implementation
- Update useEffect to sync inner component actions with ref for external access

* feat: inputbar core

* refactor: Update QuickPanel integration across various tools

* refactor: migrate to antd

* chore: format

* fix: clean code

* clean code

* fix i18n

* fix: i18n

* relative path

* model type

* 🤖 Weekly Automated Update: Nov 09, 2025 (#11209)

feat(bot): Weekly automated script run

Co-authored-by: DeJeune <67425183+DeJeune@users.noreply.github.com>
Co-authored-by: SuYao <sy20010504@gmail.com>

* format

* fix

* fix: format

* use ripgrep

* update with input

* add common filters

* fix build issue

* format

* fix error

* smooth change

* adjust

* support listing dir

* keep list files when focus and blur

* support draft save

* Optimize the rendering logic of session messages and input bars, and simplify conditional judgments.

* Upgrade to agentId

* format

* 🐛 fix: force quick triggers for agent sessions

* revert

* fix migrate

* fix: filter

* fix: trigger

* chore packages

* feat: 添加过滤和排序功能,支持自定义函数

* fix cursor bug

* fix format

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: beyondkmp <beyondkmp@gmail.com>
Co-authored-by: kangfenmao <kangfenmao@qq.com>
2025-11-12 20:04:58 +08:00
MyPrototypeWhat
649f9420a4 feat: add @cherrystudio/ai-sdk-provider package and integrate (#10715)
* feat: add @cherrystudio/ai-sdk-provider package and integrate with CherryIN

- Introduced the @cherrystudio/ai-sdk-provider package, providing a CherryIN routing solution for AI SDKs.
- Updated configuration files to include the new provider.
- Enhanced provider initialization to support CherryIN as a new AI provider.
- Added README and documentation for usage instructions.

* chore: remove deprecated @ai-sdk/google dependency and clean up package files

- Removed the @ai-sdk/google dependency from package.json and yarn.lock as it is no longer needed.
- Simplified the createGeminiModel function in index.ts for better readability and maintainability.

* feat: update CherryIN provider integration and dependencies

- Updated @ai-sdk/anthropic and @ai-sdk/google dependencies to their latest versions in package.json and yarn.lock.
- Introduced a new CherryInProvider implementation in cherryin-provider.ts, enhancing support for CherryIN API.
- Refactored provider initialization to include CherryIN as a supported provider in schemas.ts and options.ts.
- Updated web search plugin to utilize the new CherryIN provider capabilities.
- Cleaned up and organized imports across various files for better maintainability.

* chore: clean up tsconfig and remove unnecessary nullish coalescing in CherryIn provider

- Simplified tsconfig.json by consolidating exclude and include arrays.
- Removed nullish coalescing in cherryin-provider.ts for cleaner header handling in model initialization.

* fix: remove console.log from webSearchPlugin to clean up code

- Eliminated unnecessary console.log statement in the webSearchPlugin to enhance code clarity and maintainability.

* fix(i18n): Auto update translations for PR #10715

* chore: update yarn.lock with new package versions and dependencies

- Added new versions for @ai-sdk/anthropic, @ai-sdk/google, @ai-sdk/provider-utils, and eventsource-parser.
- Updated dependencies and peerDependencies for the newly added packages.

* feat: enhance CherryIn provider with chat model support and custom fetch logic

- Introduced CherryInOpenAIChatLanguageModel to handle chat-specific configurations.
- Updated createChatModel to support CherryIn chat models.
- Modified webSearchPlugin to accommodate both 'cherryin' and 'cherryin-chat' provider IDs.
- Added 'cherryin-chat' provider ID to schemas and provider configurations.
- Adjusted provider factory to correctly set provider ID for chat mode.
- Enhanced web search utility to handle CherryIn chat models.

* 🐛 fix: resolve cherryin provider lint errors and web search config

- Add fetch global variable declaration for ai-sdk-provider in oxlintrc
- Fix endpoint_type mapping fallback logic in cherryin provider
- Add error handling comment for better code readability

* chore(dependencies): update AI SDK packages and patches

- Added new versions for @ai-sdk/anthropic, @ai-sdk/google, and @ai-sdk/provider-utils in yarn.lock.
- Updated @ai-sdk/openai dependency to use a patch version in package.json.
- Included @cherrystudio/ai-sdk-provider as a new dependency in the workspace.

* chore(dependencies): update peer dependencies and installation instructions

- Removed specific versions of @ai-sdk/anthropic and @ai-sdk/google from package.json and yarn.lock.
- Updated peer dependencies in package.json to include @ai-sdk/anthropic, @ai-sdk/google, and @ai-sdk/openai.
- Revised installation instructions in README.md to reflect the new dependencies.

---------

Co-authored-by: GitHub Action <action@github.com>
2025-11-12 18:16:27 +08:00
Xiang, Haihao
2552d97ea7 fix: ensure the user can select any image in NewApiPage (#11238)
NewApiPage always show the first image in filteredPaintings. As a
result, the user is unable to select other images. This issue was
introduced in commit 0502ff4.
2025-11-12 13:30:23 +08:00
Phantom
803f4b5a64 fix(migrate): use provider apiHost for new-api (#11244)
fix(migrate): use provider apiHost for new-api case instead of hardcoded value
2025-11-12 10:05:21 +08:00
beyondkmp
31f8fff6e2 chore: update claude code plugins (#11237)
* chore: update claude code plugins

* update version

---------

Co-authored-by: Payne Fu <payne@Paynes-MacBook-Pro.local>
2025-11-11 19:19:30 +08:00
SuYao
2663cb19ce Chore/aisdk (#11232)
* chore(dependencies): update AI SDK dependencies to latest versions

* chore(patches): update AI SDK patches for Hugging Face, OpenAI, and Google
2025-11-11 00:09:26 +08:00
Pleasure1234
ce5d46bfc7 fix: remove explicit Content-Type header in file upload (#11231)
The Content-Type header was removed from the fetch request when uploading files. This change may allow the server to infer the correct content type or handle uploads more flexibly.
2025-11-10 23:28:38 +08:00
kangfenmao
c1fa24522d chore(release): update release notes for v1.7.0-beta.5
- Add MCPRouter provider and MCP marketplace features
- Improve UI optimization and MCP OAuth callback
- Fix various bugs including Agent allowed_tools inheritance
2025-11-10 20:19:40 +08:00
亢奋猫
2f66f5b511 refactor(AssistantPresetsPage): added assistants subscribe settings to AssistantPresetsPage (#11184)
refactor(DataSettings, MCPSettings, AssistantPresetsPage): clean up imports and enhance UI components

- Removed unused imports and components from DataSettings for better clarity.
- Updated MCPSettings layout by introducing a new Container styled with Scrollbar for improved scrolling.
- Added AssistantsSubscribeUrlSettings component to manage subscription URLs in AssistantPresetsPage, enhancing user interaction.
- Adjusted button styles and layout in AssistantPresetsPage for a more cohesive design.
2025-11-10 20:10:38 +08:00
亢奋猫
2d8555c326 fix(agents): inherit allowed_tools from Agent when creating Session (#11201)
When creating a Session under an Agent, the Session should inherit the Agent's allowed_tools configuration. Previously, the allowed_tools parameter was missing from the Session creation API call, causing inconsistency between Agent and Session configurations.

Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-10 18:44:33 +08:00
Konjac-XZ
e2c8edab61 fix: incorrect spelling caused Gemini endpoint’s thinking budget to fail (#11217) 2025-11-10 16:42:34 +08:00
kangfenmao
5e0a66fa1f docs(README): update AI Web Service Integration section and remove public beta notice
- Added a hyperlink to Poe in the AI Web Service Integration list for better accessibility.
- Removed the public beta notice for the Enterprise Edition to streamline the documentation.
- Updated the cost section to include a link to the AGPL-3.0 License for clarity.
2025-11-10 15:44:29 +08:00
亢奋猫
bc8b0a8d53 feat(agent): add permission mode display component for empty session state (#11204)
Replace empty state text with a visual permission mode display card that shows:
- Permission mode icon with unique colors for each mode (default, plan, acceptEdits, bypassPermissions)
- Permission mode title and description
- Clickable to navigate directly to tooling settings tab

Replace loading text with Ant Design Spin component for better UX.
2025-11-10 11:26:36 +08:00
fullex
e43562423e refactor: remove unused files and configurations (#11176)
* ♻️ refactor: remove unused resources/js directory and references

Remove legacy resources/js directory (bridge.js and utils.js) that was left over after minapp.html removal in commit 461458e5e. Also update .oxlintrc.json to remove the unused resources/js/** file pattern.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* ♻️ refactor: remove additional unused files

- Remove duplicate ipService.js (superseded by TypeScript version in src/main/utils/)
- Remove unused components.json (shadcn config with non-existent target directory)
- Remove unused context-menu.tsx component (no imports found)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-10 11:14:32 +08:00
亢奋猫
120ac122eb fix(ui): resolve sidebar tooltip overlap with window controls on macOS (#11216)
Fixes #11125

Add placement="right" to sidebar toggle tooltips in ChatNavbar, Navbar,
and Notes HeaderNavbar to prevent tooltips from overlapping with macOS
window control buttons (minimize, maximize, close) in the top-left corner.

This ensures tooltips appear to the right of the toggle buttons rather
than above them, avoiding overlap with native window controls.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-09 23:24:35 +08:00
Phantom
9013fcba14 fix(useMessageOperations): skip timestamp update for UI-only changes (#10927)
Prevent unnecessary message updates when only UI-related states change by checking the update keys and skipping timestamp updates in those cases
2025-11-09 18:17:34 +08:00
Phantom
c32f4badbd fix(ErrorBlock): reorder field (#11057)
feat(ErrorBlock): add responseBody display above requestBodyValues

Move responseBody display to appear before requestBodyValues for better error flow readability
2025-11-09 17:53:05 +08:00
亢奋猫
66f66fe08e fix: prevent MCP card description text from overflowing dialog width (#11203)
* fix: prevent MCP card description text from overflowing dialog width

Add whitespace-pre-wrap and break-all classes to the MCP server description
text in Agent Settings to ensure long descriptions wrap properly within the
dialog bounds instead of causing layout overflow issues.

Generated with [Claude Code](https://claude.com/claude-code)

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

* feat: display MCP server logo in Agent Settings tooling section

Add logo display support for MCP servers in the Agent Settings tooling
section. When a server has a logoUrl defined, it will now be shown next
to the server name as a 20x20px rounded image, matching the design
pattern used in MCPSettings.

Generated with [Claude Code](https://claude.com/claude-code)

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-09 17:50:41 +08:00
亢奋猫
d5826c2dc7 fix(ui): truncate long Bash command in tag with popover (#11200)
* 🐛 fix(ui): truncate long Bash command in tag with popover

Add automatic truncation for Bash commands exceeding 200 characters in the tag display. When truncated, users can hover over the tag to view the full command in a popover.

- Add MAX_TAG_LENGTH constant (200 chars)
- Implement command truncation logic
- Add Popover component for full command display on hover
- Prevent UI overflow issues with long commands

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* ♻️ refactor(ui): reduce MAX_TAG_LENGTH to 100 for smaller screens

Reduce the command truncation threshold from 200 to 100 characters to better support smaller screen sizes and improve readability.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* docs: remove emoji requirement from conventional commits

Update commit message guidelines to use standard Conventional Commit format without emoji prefixes for better compatibility and consistency.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-09 12:27:15 +08:00
亢奋猫
85a628f8dd style(ui): center plugin browser tabs (#11205)
💄 style(ui): center plugin browser tabs

Center the tab items in the plugin browser component for better visual alignment and improved user experience.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-09 12:06:50 +08:00
cheng chao
ed453750fe fix(mcp): resolve OAuth callback page hanging and add i18n support (#11195)
- Fix OAuth callback server not sending HTTP response, causing browser to hang
- Add internationalization support for OAuth callback page (10 languages)
- Simplify callback page design with clean white background
- Improve user experience with localized success messages

Changes:
- src/main/services/mcp/oauth/callback.ts: Add HTTP response to OAuth callback
- src/renderer/src/i18n/: Add callback page translations for all supported languages

Signed-off-by: charles <kidccc@gmail.com>
2025-11-09 01:45:25 +08:00
亢奋猫
57d9a31c0f refactor(migrate): consolidate migrations into version 172 (#11194)
* refactor(migrate): consolidate migrations into version 172

Consolidates migrations 162-166 into a single migration 172 to fix data
inconsistencies between release/v1.6.x and v1.7.0-x versions. This
ensures a single, consistent migration path and corrects data deviations
that occurred during version upgrades.

Changes:
- Remove separate migrations 162-166
- Add consolidated migration 172 that includes:
  - Mini app additions (ling, huggingchat)
  - OCR provider updates (ovocr)
  - Agent to preset migration
  - Sidebar icon updates (agents -> store)
  - LLM provider Anthropic API host configurations
  - Assistant preset settings initialization

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

* refactor(store): update persist version to 172

Update the redux-persist version number from 171 to 172 to match the
consolidated migration version.

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

* fix(migrate): add missing break statement in switch case

Add missing break statement after 'grok' case to prevent fall-through
to 'cherryin' case. Also add break statement for 'longcat' case.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-09 00:31:35 +08:00
亢奋猫
58afbe8a79 refactor(config): optimize oxlint configuration by removing redundant default rules (#11192)
Remove ~60 redundant rule declarations that match oxlint's default behavior.
This reduces the config file by 28% (211 -> 152 lines) while maintaining
identical linting behavior.

Changes:
- Remove default error-level rules (constructor-super, no-debugger, etc.)
- Retain only custom configurations that differ from defaults
- Keep all environment overrides and plugin settings unchanged
- Preserve all modified severity levels (warn) and disabled rules (off)

Benefits:
- Improved readability: clearly shows project-specific lint strategy
- Reduced maintenance: no need to sync with oxlint default updates
- Smaller config: 46% fewer rule declarations (130 -> 70 rules)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-09 00:31:20 +08:00
亢奋猫
9a10516b52 chore: update bun and uv versions (#11193)
* chore: update bun and uv versions

- Update bun from 1.2.17 to 1.3.1
- Update uv from 0.7.13 to 0.9.5

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* refactor: update UV installer to support tar.gz format

- Update UV package mappings from .zip to .tar.gz for macOS and Linux
- Add RISCV64 Linux platform support
- Implement dual extraction logic:
  - tar.gz extraction for macOS/Linux using tar command
  - zip extraction for Windows using StreamZip
- Flatten directory structure during extraction
- Maintain executable permissions on Unix-like systems

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* 🐛 fix: correct error handling in UV installer

Remove ineffective error code 102 return from nested function.
Chmod errors now properly propagate to outer try-catch block.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-09 00:31:00 +08:00
Phantom
e268e69597 refactor(config): centralize home directory constant to shared config (#11158)
Replace hardcoded '.cherrystudio' directory references with HOME_CHERRY_DIR constant
2025-11-07 22:24:05 +08:00
kangfenmao
10e78ac60e refactor(MCPSettings): update styled components and enhance server synchronization
- Changed RightContainer from Scrollbar to a standard div for layout adjustments.
- Updated DetailContainer to use Scrollbar for improved scrolling behavior.
- Modified server synchronization logic across multiple providers to include allServers in the results, enhancing server management capabilities.
- Refactored provider configurations to ensure consistency and support for new server data structure.
2025-11-07 19:22:58 +08:00
kangfenmao
44b2b859da feat(MCPRouter): add MCPRouter provider support and integration
- Introduced MCPRouter provider with token management and server synchronization functionalities.
- Added MCPRouter logo to settings page for visual representation.
- Updated provider configuration to include MCPRouter details and API interactions.
- Implemented functions for saving, retrieving, and clearing MCPRouter tokens, along with server synchronization logic.
2025-11-07 18:41:15 +08:00
kangfenmao
bfef0c5580 feat(MCPSettings): enhance MCP server management and localization support
- Updated auto-translation script to allow configurable max concurrent translations and delay via environment variables.
- Added new translations for "discover", "fetch", "marketplaces", "providers", and "servers" across multiple locales (en-us, zh-cn, zh-tw, de-de, el-gr, es-es, fr-fr, ja-jp, pt-pt, ru-ru).
- Improved MCPSettings UI by adjusting layout and adding a new provider settings component for better server management.
- Refactored MCP server list and market list components for improved usability and styling consistency.
2025-11-07 18:01:55 +08:00
fullex
1e8055031a Merge branch 'main' of github.com:CherryHQ/cherry-studio 2025-11-07 12:02:35 +08:00
fullex
8e33ff8d90 docs: update test plan documentation to clarify upgrade behavior for RC and Beta channels 2025-11-07 12:02:28 +08:00
kangfenmao
a619000340 chore: update v1.7.0-beta.4 release notes
Update electron-builder.yml with release notes covering:
- UI framework upgrade with improved performance and UX
- New features: AWS Bedrock API key support, SophNet provider, auto session rename, TopP parameter, and reasoning effort control
- Improvements to UI components, quick panel, painting models, system shutdown handling, and package size optimization
- Bug fixes for provider support, i18n translations, and API issues
2025-11-06 20:51:03 +08:00
kangfenmao
78278ce96d refactor: remove heroui
commit 7c8bf8b591
Author: defi-failure <159208748+defi-failure@users.noreply.github.com>
Date:   Thu Nov 6 17:59:38 2025 +0800

    fix: add token usage to agent session message

commit ff8e5ddd27
Author: defi-failure <159208748+defi-failure@users.noreply.github.com>
Date:   Thu Nov 6 17:25:54 2025 +0800

    fix: close prompt stream when finish or error chunk received

commit 530e6516fd
Author: defi-failure <159208748+defi-failure@users.noreply.github.com>
Date:   Thu Nov 6 17:19:53 2025 +0800

    chore: code cleanup

commit ab21c0d56c
Author: kangfenmao <kangfenmao@qq.com>
Date:   Thu Nov 6 16:13:36 2025 +0800

    feat(SessionItem): implement auto-rename feature for sessions and improve context menu handling

    - Added a new context menu option to automatically rename sessions based on topics.
    - Introduced useDeferredValue for managing target session state.
    - Updated imports to include necessary thunk actions and components.
    - Enhanced API service to handle optional assistant model in message summary fetching.
    - Exported renameAgentSessionIfNeeded function for better accessibility in the store.

commit 21ea8ccf37
Merge: ab7b207d2 816a92c60
Author: kangfenmao <kangfenmao@qq.com>
Date:   Thu Nov 6 15:29:09 2025 +0800

    Merge branch 'main' of github.com:CherryHQ/cherry-studio into refactor/heroui-antd

    # Conflicts:
    #	src/renderer/src/pages/home/Tabs/components/AddButton.tsx
    #	src/renderer/src/pages/home/Tabs/components/SessionItem.tsx
    #	src/renderer/src/pages/home/Tabs/components/Sessions.tsx
    #	src/renderer/src/pages/home/Tabs/components/Topics.tsx
    #	src/renderer/src/pages/paintings/NewApiPage.tsx

commit ab7b207d29
Author: kangfenmao <kangfenmao@qq.com>
Date:   Thu Nov 6 14:50:05 2025 +0800

    refactor: streamline event listener management in useAppInit and update ToolPermissionRequestCard styling

commit 3834c5d402
Author: kangfenmao <kangfenmao@qq.com>
Date:   Thu Nov 6 14:21:25 2025 +0800

    refactor: enhance API server state management and remove unused initialization in useAppInit

commit a64b94a41f
Author: kangfenmao <kangfenmao@qq.com>
Date:   Thu Nov 6 13:21:58 2025 +0800

    refactor: update OpenAPI documentation paths to include subdirectories for better route coverage

commit 2e0ff28505
Author: kangfenmao <kangfenmao@qq.com>
Date:   Thu Nov 6 12:26:09 2025 +0800

    refactor: center align columns in InstalledPluginsList and set AntTable size to small

commit 84bf94e2ff
Author: defi-failure <159208748+defi-failure@users.noreply.github.com>
Date:   Thu Nov 6 12:06:09 2025 +0800

    refactor: align create agent model selection with edit agent

commit 84f2281506
Author: kangfenmao <kangfenmao@qq.com>
Date:   Thu Nov 6 11:29:32 2025 +0800

    refactor: integrate API server functionality into various components and enhance user notifications

commit 4e01210df4
Author: kangfenmao <kangfenmao@qq.com>
Date:   Thu Nov 6 10:56:38 2025 +0800

    refactor: replace ContextMenu with Dropdown in AgentItem and SessionItem components for improved context menu handling

commit 9df38c7e83
Author: kangfenmao <kangfenmao@qq.com>
Date:   Thu Nov 6 10:27:30 2025 +0800

    refactor: update AddButton styling to use CSS variable for border radius and remove unused settings hook

commit 251c269ab3
Author: kangfenmao <kangfenmao@qq.com>
Date:   Thu Nov 6 10:11:21 2025 +0800

    refactor: remove unused error handling alerts from AssistantsTab component

commit 9b9640d8d1
Author: kangfenmao <kangfenmao@qq.com>
Date:   Thu Nov 6 10:07:26 2025 +0800

    refactor: adjust margin styling for UnifiedAddButton component

commit edd6b11aa7
Author: kangfenmao <kangfenmao@qq.com>
Date:   Thu Nov 6 10:04:01 2025 +0800

    refactor: update AddButton styling based on topic position and clean up CSS for root element

commit 1c0de625d8
Author: kangfenmao <kangfenmao@qq.com>
Date:   Thu Nov 6 09:56:42 2025 +0800

    fix: update assistant addition messages for multiple languages

commit 0ea4dd4e3a
Author: dev <verc20.dev@proton.me>
Date:   Wed Nov 5 21:01:24 2025 +0800

    fix: init message api err

commit f3bbd4ed44
Author: dev <verc20.dev@proton.me>
Date:   Wed Nov 5 20:42:49 2025 +0800

    refactor: remove heroui

commit d01609fc36
Author: dev <verc20.dev@proton.me>
Date:   Wed Nov 5 19:08:41 2025 +0800

    refactor: migrate heroui/toast to antd message

commit f4b14dfc10
Author: kangfenmao <kangfenmao@qq.com>
Date:   Wed Nov 5 18:51:29 2025 +0800

    refactor: enhance Sessions component layout with styled Scrollbar and adjust UnifiedAddButton margins

commit 6ae5f69163
Author: kangfenmao <kangfenmao@qq.com>
Date:   Wed Nov 5 18:44:13 2025 +0800

    refactor: update PluginSettings and ToolingSettings for improved layout and functionality

commit fcb0020787
Author: kangfenmao <kangfenmao@qq.com>
Date:   Wed Nov 5 18:29:52 2025 +0800

    wip

commit 02265f369e
Author: dev <verc20.dev@proton.me>
Date:   Wed Nov 5 17:26:39 2025 +0800

    fix: error block related

commit 5e22d9d36f
Author: dev <verc20.dev@proton.me>
Date:   Wed Nov 5 17:14:25 2025 +0800

    fix: note head nav related

commit 3f52b7766a
Author: dev <verc20.dev@proton.me>
Date:   Wed Nov 5 16:45:49 2025 +0800

    chore: remove dead code

commit 484622f12b
Author: dev <verc20.dev@proton.me>
Date:   Wed Nov 5 16:43:12 2025 +0800

    chore: remove dead code

commit 2bceb302e0
Author: dev <verc20.dev@proton.me>
Date:   Wed Nov 5 15:33:25 2025 +0800

    fix: tool setting related

commit 5c455f25eb
Author: dev <verc20.dev@proton.me>
Date:   Wed Nov 5 13:59:33 2025 +0800

    chore: remove dead code

commit d1d1dbc046
Author: dev <verc20.dev@proton.me>
Date:   Wed Nov 5 13:51:41 2025 +0800

    fix: tool permission card related

commit bf4ec23ef7
Author: dev <verc20.dev@proton.me>
Date:   Wed Nov 5 12:22:53 2025 +0800

    fix: remove button and modal renaming

commit 47db5baeb1
Author: dev <verc20.dev@proton.me>
Date:   Wed Nov 5 12:20:36 2025 +0800

    fix: plugin setting related

commit 81fecce552
Author: kangfenmao <kangfenmao@qq.com>
Date:   Wed Nov 5 12:16:42 2025 +0800

    refactor: enhance ChatNavbarContent structure by replacing Breadcrumbs with custom layout and adding separators

commit fc64b6c611
Author: kangfenmao <kangfenmao@qq.com>
Date:   Wed Nov 5 12:10:48 2025 +0800

    refactor: simplify MessageAgentTools component structure by removing unnecessary wrapper div

commit e0f383a050
Author: kangfenmao <kangfenmao@qq.com>
Date:   Wed Nov 5 12:08:32 2025 +0800

    fix: update button classes in AddAssistantOrAgentPopup for improved cursor behavior

commit 720284262f
Author: kangfenmao <kangfenmao@qq.com>
Date:   Wed Nov 5 12:06:58 2025 +0800

    refactor: update AgentModal to use TopView for improved modal management and enhance form structure

commit b334a2c5be
Author: kangfenmao <kangfenmao@qq.com>
Date:   Wed Nov 5 11:40:47 2025 +0800

    refactor: replace UpdateDialog with UpdateDialogPopup for better modal handling

commit 468aebd632
Author: dev <verc20.dev@proton.me>
Date:   Wed Nov 5 10:56:40 2025 +0800

    fix: plugins related wip

commit bd4a979f62
Author: dev <verc20.dev@proton.me>
Date:   Tue Nov 4 17:46:14 2025 +0800

    fix: add button related

commit b3316a4dc8
Author: dev <verc20.dev@proton.me>
Date:   Tue Nov 4 17:18:31 2025 +0800

    fix: agent tool result related components

commit 6ca7597a98
Author: dev <verc20.dev@proton.me>
Date:   Tue Nov 4 11:12:01 2025 +0800

    fix: lint

commit 7d0f0b38a6
Author: kangfenmao <kangfenmao@qq.com>
Date:   Tue Nov 4 09:56:32 2025 +0800

    wip

commit 96a607a410
Author: kangfenmao <kangfenmao@qq.com>
Date:   Mon Nov 3 20:23:25 2025 +0800

    wip

commit 235ad16252
Author: kangfenmao <kangfenmao@qq.com>
Date:   Mon Nov 3 20:08:45 2025 +0800

    wip

commit f23fe1b9e9
Author: kangfenmao <kangfenmao@qq.com>
Date:   Mon Nov 3 19:15:01 2025 +0800

    wip

commit 28fac543fc
Author: kangfenmao <kangfenmao@qq.com>
Date:   Mon Nov 3 18:39:39 2025 +0800

    wip

commit 3cc7ee01e2
Author: kangfenmao <kangfenmao@qq.com>
Date:   Mon Nov 3 17:33:13 2025 +0800

    wip

commit 37bdf9e508
Author: kangfenmao <kangfenmao@qq.com>
Date:   Sat Nov 1 19:16:58 2025 +0800

    wip

commit 1bf5104f97
Author: kangfenmao <kangfenmao@qq.com>
Date:   Sat Nov 1 12:12:01 2025 +0800

    wip
2025-11-06 18:27:43 +08:00
Phantom
76483d828e ci(i18n): change auto i18n workflow to run weekly (#11152)
* ci(i18n): change auto i18n workflow to run weekly

Update the workflow to run on a weekly schedule instead of on pull request events.
Also simplify the workflow by using yarn for dependency management and create a PR
for changes instead of committing directly to the branch.

* ci(github-actions): improve workflow step names with emojis

Use emojis in step names to enhance readability and visual scanning of workflow logs

* ci(workflow): prevent committing package.json and yarn.lock changes in i18n workflow
2025-11-06 18:25:04 +08:00
Jake Jia
816a92c609 feat(app-menu): add full i18n support and sync lanuage with app language settings (#11131)
Previously, the macOS menu bar was always displayed in English regardless of
system language or in-app language settings. This change enables the menu bar
to dynamically follow the application's language preference.

Key changes:
- Add language change listener to automatically update menu when user switches language
- Refactor AppMenuService with proper subscription management and cleanup
- Add appMenu translations for en-us, zh-cn, and zh-tw locales
- Implement destroy method to prevent memory leaks from config subscriptions
- Convert all menu items (File, Edit, View, Window, Help) to use localized labels

The menu bar now respects the in-app language setting and updates in real-time
when users change their preferences, providing a consistent multilingual experience.
2025-11-06 14:46:42 +08:00
beyondkmp
83e4d4363f fix: add Perplexity provider support and update API host formatting (#11162)
* feat: add Perplexity provider support and update API host formatting

- Introduced `isPerplexityProvider` function to identify Perplexity providers.
- Updated `formatProviderApiHost` to handle Perplexity provider API host formatting.
- Added unit tests for Perplexity provider configuration to ensure correct API host formatting behavior.

* fix: add 'perplexity' to unsupported API version providers list
2025-11-06 10:43:33 +08:00
Phantom
1103449a4f fix: wrong migration in #10727 (#11151) 2025-11-05 14:33:07 +08:00
beyondkmp
56c7a7f066 🐛 fix: resolve TypeScript type conflicts and React hooks warnings (#11148)
* 🐛 fix: resolve TypeScript type conflicts and React hooks warnings

- Add @smithy/types@4.7.1 to resolutions to unify AWS SDK dependencies
- Wrap updatePaintingState in useCallback to fix exhaustive-deps warning
- Fix AWS Bedrock client type incompatibility issues

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* fix(i18n): Auto update translations for PR #11148

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: GitHub Action <action@github.com>
2025-11-05 14:19:14 +08:00
Phantom
caa59c4c50 refactor(Topics & Sessions): Style and code structure adjustments (#10868)
* refactor(Tabs): extract shared styled components into separate file

Move common styled components (ListItem, ListItemNameContainer, ListItemName, ListItemEditInput) from SessionItem.tsx and Topics.tsx into shared.tsx to improve code reuse and maintainability

* refactor(components): extract ListContainer component for shared tab layouts

Create reusable ListContainer component to standardize layout styling across tabs
Replace manual div containers in Sessions and Topics components with new ListContainer

* refactor(ListItem): convert styled component to Tailwind CSS function component

- Convert ListItem from styled-components to Tailwind CSS function component
- Maintain all original styling and hover/active states
- Use HTMLDivElement props interface for proper TypeScript typing
- Preserve CSS custom properties for theme variables

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* refactor(ListItemNameContainer): convert styled component to Tailwind CSS function component

- Convert ListItemNameContainer from styled-components to Tailwind CSS function component
- Simplify layout styles using Tailwind's utility classes
- Use HTMLDivElement props interface for proper TypeScript typing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* refactor(ListItemName): convert styled component to Tailwind CSS function component

- Convert ListItemName from styled-components to Tailwind CSS function component
- Use inline styles for webkit-specific line clamping properties
- Remove complex animations from component definition (can be added via CSS classes)
- Use HTMLDivElement props interface for proper TypeScript typing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* refactor(ListItemEditInput): convert styled component to Tailwind CSS function component

- Convert ListItemEditInput from styled-components to Tailwind CSS function component
- Use proper InputHTMLAttributes type for input elements
- Remove styled-components import as no longer needed
- Maintain all original styling using Tailwind utility classes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* refactor(components): improve type safety and class ordering in shared components

- Replace HTMLAttributes with more specific ComponentProps types
- Reorder class names for better readability and consistency

* refactor(components): update styling and class handling in list items

- Replace deprecated classNames utility with cn from @heroui/react
- Consolidate style properties into className using cn
- Improve CSS selector syntax for better specificity
- Standardize padding and border radius values

* Revert "refactor(ListItemName): convert styled component to Tailwind CSS function component"

This reverts commit 196136068d.

* style(shared): increase font size and remove redundant padding

The font size was increased from 13px to 14px for better readability. Redundant padding in ListItemEditInput was removed to maintain consistent styling.

* refactor(AddButton): simplify component by removing FC type and inline props

Remove unnecessary FC type declaration and inline the Props interface with ButtonProps. Also clean up prop spreading by moving it to the end of the component.

* style(Topics): remove redundant className and add overflow styles

* refactor(components): extract MenuButton to shared components

Move MenuButton implementation from individual components to shared module to reduce code duplication and improve maintainability

* refactor(PendingIndicator): convert styled component to Tailwind CSS function component

- Convert PendingIndicator from styled-components to Tailwind CSS function component
- Use ComponentPropsWithoutRef<'div'> for consistent TypeScript typing
- Replace styled-components attrs with Tailwind animate-pulse class
- Use CSS custom properties for pulse-size variable
- Remove styled-components import as no longer needed

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* refactor(components): replace styled indicators with shared StatusIndicator

Consolidate PendingIndicator and FulfilledIndicator into a single StatusIndicator component with variant support

* style(shared.tsx): adjust border styles for singlealone active state

* refactor: use type-only imports for react props

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-05 14:14:40 +08:00
fullex
2546dfbe5d chore: update Node.js version to 22 and Yarn version to 4.9.1 across workflows and documentation 2025-11-05 12:54:30 +08:00
beyondkmp
5fea202a7d fix: add PowerMonitorService for system shutdown handling (#11115)
* feat: add PowerMonitorService for system shutdown handling

- Add PowerMonitorService to monitor system shutdown events
- Use @paymoapp/electron-shutdown-handler for Windows platform
- Use Electron's powerMonitor for macOS and Linux platforms
- Support registering multiple shutdown handlers via dependency injection
- Register shutdown handlers in ipc.ts to disable auto-update and save data

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* format code

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-04 18:56:09 +08:00
fullex
7dce1d776b feat: app's version history log (#11097)
* feat: integrate version tracking in app initialization

- Added versionService to record the current version during app startup.
- This change prepares for upcoming data refactoring in version 2.

* fix: lint from other PRs & format

* feat: enhance version tracking with meaningful change detection

- Updated VersionService to check for changes in version, OS, environment, packaged status, and install mode before recording a new entry.
- Improved logging to reflect whether version information has changed or remained the same.
2025-11-04 14:13:07 +08:00
beyondkmp
346af4d338 fix: add CherryAI provider support and update API host formatting (#11135)
* fix: add CherryAI provider support and update API host formatting

* format code

* add ut

* format code
2025-11-04 12:59:14 +08:00
Zephyr
abd5d3b96f feat: amazon bedrock request use bedrock api key (#10727)
* feat: amazon bedrock request use bedrock api key

* feat: ai-core/provider support bedrock api key

* refactor: extract AWS Bedrock auth type and remove redundant state

* feat: add bedrock reasoning support

Add AWS Bedrock-specific reasoning parameter handling to support Extended Thinking feature for Claude models via Bedrock API.

Changes:
- Add `buildBedrockProviderOptions` function in options.ts to handle Bedrock-specific provider options
- Add `getBedrockReasoningParams` function in reasoning.ts to generate reasoning config with budget tokens
- Register 'bedrock' case in provider options switch to route to Bedrock-specific builder
- Reuse `getAnthropicThinkingBudget` helper for consistent token budget calculation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* feat: add migration for Bedrock auth type and API key fields

* refactor: replace any type with BedrockRuntimeClientConfig in AWS Bedrock client

* fix: bug fix

* fix: lint error

* fix: bedrock reasoning

* chore: bump persisted reducer version to 171

* Update src/renderer/src/store/migrate.ts

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: icarus <eurfelux@gmail.com>
2025-11-03 21:05:10 +08:00
Phantom
49bd298d37 feat(InputbarTools): add reasoning effort button to quick panel (#10959)
Add new menu item with lightbulb icon that opens the reasoning effort quick panel when clicked
2025-11-03 20:36:52 +08:00
Phantom
714a28ac29 fix(QuickPanel): Hide the options that should be hidden in the quick panel. (#10931)
* feat(QuickPanel): add hidden property to list items

Add support for hiding QuickPanel items by introducing a hidden property. This allows conditional visibility of items like the knowledge base button based on application state.

* docs(types): clarify settings field comment in Assistant type
2025-11-03 20:34:24 +08:00
beyondkmp
0cf81c04c8 chore: update electron-builder.yml to exclude additional configuration files from build (#11129)
* chore: update electron-builder.yml to exclude additional configuration files from build

* delete all hide files
2025-11-03 17:54:29 +08:00
kangfenmao
4186e9c990 feat: add support for TopP in model capabilities and update parameter builder to utilize it 2025-11-03 16:37:12 +08:00
kangfenmao
d8f68a6056 feat: initialize painting model with first available option and update default provider to 'cherryin' 2025-11-03 15:12:58 +08:00
kangfenmao
11bf50e722 fix(i18n): improve label retrieval for paintings image size options 2025-11-03 14:45:21 +08:00
kangfenmao
32a84311aa feat: add SophNet LLM provider 2025-11-03 13:28:40 +08:00
beyondkmp
6eaa2b2461 refactor: remove main window dependency from PythonService and utilize WindowService for window management (#11116)
* refactor: remove main window dependency from PythonService and utilize WindowService for window management

* format code
2025-11-03 13:09:40 +08:00
defi-failure
9f00f00546 chore: update v1.7.0-beta.3 release notes (#11105)
* chore: update v1.7.0-beta.3 release notes

* fix(i18n): Auto update translations for PR #11105

* fix: code lint error

---------

Co-authored-by: GitHub Action <action@github.com>
2025-11-02 22:28:36 +08:00
SuYao
bd94d23343 refactor:Unify the naming of configuration fields in thinking, change to using underscore style. (#11106)
* refactor:Unify the naming of configuration fields in thinking, change to using underscore style.

* fix(i18n): Auto update translations for PR #11106

* chore: lint

* fix: typecheck

---------

Co-authored-by: GitHub Action <action@github.com>
2025-11-02 19:24:23 +08:00
chenxue
5f1c14e2c0 fix(aihubmix): fix default rules missing app code (#11100)
* add imagen

* Update aihubmix.ts

* fix type

---------

Co-authored-by: zhaochenxue <zhaochenxue@bixin.cn>
2025-11-02 17:03:05 +08:00
dependabot[bot]
cdc12d5092 ci(deps): bump actions/stale from 9 to 10 (#11088)
Bumps [actions/stale](https://github.com/actions/stale) from 9 to 10.
- [Release notes](https://github.com/actions/stale/releases)
- [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/stale/compare/v9...v10)

---
updated-dependencies:
- dependency-name: actions/stale
  dependency-version: '10'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-02 08:44:53 +08:00
dependabot[bot]
e5967fd874 ci(deps): bump actions/upload-artifact from 4 to 5 (#11089)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-02 08:44:41 +08:00
dependabot[bot]
e2f1d80697 ci(deps): bump actions/setup-node from 4 to 6 (#11090)
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4 to 6.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v4...v6)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-02 08:44:28 +08:00
SuYao
28bc89ac7c perf: optimize QR code generation and connection info for phone LAN export (#11086)
* Increase QR code margin for better scanning reliability

- Change QRCodeSVG marginSize from 2 to 4 pixels
- Maintains same QR code size (160px) and error correction level (Q)
- Improves readability and scanning success rate on mobile devices

* Optimize QR code generation and connection info for phone LAN export

- Increase QR code size to 180px and reduce error correction to 'L' for better mobile scanning
- Replace hardcoded logo path with AppLogo config and increase logo size to 60px
- Simplify connection info by removing candidates array and using only essential IP/port data

* Optimize QR code data structure for LAN connection

- Compress IP addresses to numeric format to reduce QR code complexity
- Use compact array format instead of verbose JSON object structure
- Remove debug logging to streamline connection flow

* feat: 更新 WebSocket 状态和候选者响应类型,优化连接信息处理

* Increase QR code size and error correction for better scanning

- Increase QR code size from 180px to 300px for improved readability
- Change error correction level from L (low) to H (high) for better reliability
- Reduce logo size from 60px to 40px to accommodate larger QR data
- Increase margin size from 1 to 2 for better border clearance

* 调整二维码大小和图标尺寸以优化扫描体验

* fix(i18n): Auto update translations for PR #11086

* fix(i18n): Auto update translations for PR #11086

* fix(i18n): Auto update translations for PR #11086

---------

Co-authored-by: GitHub Action <action@github.com>
2025-11-01 12:13:11 +08:00
fullex
dc06c103e0 chore[lint]: add import type lint (#11091)
chore: add import type lint
2025-11-01 10:40:02 +08:00
SuYao
1f0381aebe Fix/azure embedding (#11044)
* fix: update EmbeddingsFactory to use net.fetch and refactor KnowledgeService to use ModernAiProvider

* fix: remove deprecated @langchain/community dependency from package.json

* fix: add @langchain/community dependency to package.json and update yarn.lock
2025-11-01 01:52:16 +08:00
kangfenmao
fb02a61a48 feat: add AddAssistantOrAgentPopup component and update i18n translations
- Introduced a new AddAssistantOrAgentPopup component for selecting between assistant and agent options.
- Updated English, Simplified Chinese, and Traditional Chinese translations to include descriptions and titles for assistant and agent options.
- Refactored UnifiedAddButton to utilize the new popup for adding assistants or agents.
2025-10-31 23:46:51 +08:00
defi-failure
562fbb3ff7 fix: minor ui tweak of plugin installation interface (#11085)
* fix: use dropdown instead of chip filter

* fix: add padding to avoid scroll bar overlap

* fix: set max card grid col to 2

* fix: minor ui tweak for plugin card

* fix: remove redundant args

* fix(i18n): Auto update translations for PR #11085

* fix: cleanup comments

---------

Co-authored-by: GitHub Action <action@github.com>
2025-10-31 22:28:25 +08:00
Pleasure1234
1018ad87b8 fix: cancel debounced save on file path update (#11069)
Adds cancellation of the debounced save when the active file path is updated after moving a file or folder. This prevents saving to the old path and ensures lastFilePathRef is updated accordingly.
2025-10-31 14:17:06 +00:00
kangfenmao
82ca35fc29 chore: update issue template names by removing language specification
- Modified the names of the issue templates for bug reports, feature requests, and other questions to remove the "(English)" suffix, simplifying the titles for better clarity.
2025-10-31 21:29:29 +08:00
kangfenmao
fe53b0914a feat: add enterprise section and remove license from AboutSettings
- Introduced an "Enterprise" section in the i18n files for English, Simplified Chinese, and Traditional Chinese.
- Removed the "License" section from the AboutSettings component, replacing it with a link to the enterprise website.
- Updated icons in the AboutSettings component to reflect the new structure.
2025-10-31 21:28:30 +08:00
defi-failure
67a379641f fix(agent): resolve edit modal loading race condition (#11084)
* fix(agent): resolve edit modal loading race condition

* fix(i18n): Auto update translations for PR #11084

---------

Co-authored-by: GitHub Action <action@github.com>
2025-10-31 21:00:35 +08:00
kangfenmao
9dbc6fbf67 Revert "feat: 添加路由懒加载组件以优化页面加载性能 (#11042)"
This reverts commit dd8690b592.
2025-10-31 18:54:48 +08:00
kangfenmao
8da43ab794 chore: update release notes for v1.7.0-beta.3
- Added new features including an enhanced tool permission system, plugin management, and support for various AI models.
- Improved UI elements and agent creation processes.
- Fixed multiple bugs related to session models, assistant activation, and various API integrations.
- Updated version in package.json to v1.7.0-beta.3.
2025-10-31 17:20:41 +08:00
槑囿脑袋
2a06c606e1 feat: restore data to mobile App (#10108)
* feat: restore data to App

* fix: i18n check

* fix: lint

* Change WebSocket service port to 11451

- Update default port from 3000 to 11451 for WebSocket connections
- Maintain existing service structure and client connection handling

* Add local IP address to WebSocket server configuration

- Set server path using local IP address for improved network accessibility
- Maintain existing CORS policy with wildcard origin
- Keep backward compatibility with current connection handling

* Remove local IP path and enforce WebSocket transport

- Replace dynamic local IP path with static WebSocket transport configuration
- Maintain CORS policy with wildcard origin for cross-origin connections
- Ensure reliable WebSocket-only communication by disabling fallback transports

* Add detailed logging to WebSocket connection flow

- Enhance WebSocketService with verbose connection logging including transport type and client count
- Add comprehensive logging in ExportToPhoneLanPopup for WebSocket initialization and status tracking
- Improve error handling with null checks for main window before sending events

* Add engine-level WebSocket connection monitoring

- Add initial_headers event listener to log connection attempts with URL and headers
- Add engine connection event to log established connections with remote addresses
- Add startup logs for server binding and allowed transports

* chore: change to use 7017 port

* Improve local IP address selection with interface priority system

- Implement network interface priority ranking to prefer Ethernet/Wi-Fi over virtual/VPN interfaces
- Add detailed logging for interface discovery and selection process
- Remove websocket-only transport restriction for broader client compatibility
- Clean up unused parameter in initial_headers event handler

* Add VPN interface patterns for Tailscale and WireGuard

- Include Tailscale VPN interfaces in network interface filtering
- Add WireGuard VPN interfaces to low-priority network candidates
- Maintain existing VPN tunnel interface patterns for compatibility

* Add network interface prioritization for QR code generation

- Implement `getAllCandidates()` method to scan and prioritize network interfaces by type (Ethernet/Wi-Fi over VPN/virtual interfaces)
- Update QR code payload to include all candidate IPs with priority rankings instead of single host
- Add comprehensive interface pattern matching for macOS, Windows, and Linux systems

* Add WebSocket getAllCandidates IPC channel

- Add new WebSocket_GetAllCandidates enum value to IpcChannel
- Register getAllCandidates handler in main process IPC
- Expose getAllCandidates method in preload script API

* Add WebSocket connection logging and temporary test button

- Add URL and method logging to WebSocket engine connection events
- Implement Socket.IO connect and connect_error event handlers with logging
- Add temporary test button to force connection status for debugging

* Clean up WebSocket logging and remove debug code

- Remove verbose debug logs from WebSocket service and connection handling
- Consolidate connection logging into single informative messages
- Remove temporary test button and force connection functionality from UI
- Add missing "sending" translation key for export button loading state

* Enhance file transfer with progress tracking and improved UI

- Add transfer speed monitoring and formatted file size display in WebSocket service
- Implement detailed connection and transfer state management in UI component
- Improve visual feedback with status indicators, progress bars, and error handling

* Enhance WebSocket service and LAN export UI with improved logging and user experience

- Add detailed WebSocket server configuration with transports, CORS, and timeout settings
- Implement comprehensive connection logging at both Socket.IO and Engine.IO levels
- Refactor export popup with modular components, status indicators, and i18n support

* 移除 WebSocket 连接时的冗余日志记录

* Remove dot indicator from connection status component

- Simplify status style map by removing unused dot color properties
- Delete dot indicator element from connection status display
- Maintain existing border and background color styling for status states

* Refactor ExportToPhoneLanPopup with dedicated UI components and improved UX

- Extract QR code display states into separate components (LoadingQRCode, ScanQRCode, ConnectingAnimation, ConnectedDisplay, ErrorQRCode)
- Add confirmation dialog when attempting to close during active file transfer
- Improve WebSocket cleanup and modal dismissal behavior with proper connection handling

* Remove close button hiding during QR code generation

- Eliminate `hideCloseButton={isSending}` prop to keep close button visible
- Maintain consistent modal behavior throughout export process
- Prevent user confusion by ensuring close option remains available

* auto close

* Extract auto-close countdown into separate component

- Move auto-close countdown logic from TransferProgress to dedicated AutoCloseCountdown component
- Update styling to use paddingTop instead of marginTop for better spacing
- Clean up TransferProgress dependencies by removing autoCloseCountdown

* 添加局域网传输相关的翻译文本,包括自动关闭提示和确认关闭消息

---------

Co-authored-by: suyao <sy20010504@gmail.com>
2025-10-31 16:48:09 +08:00
MyPrototypeWhat
b6dcf2f5fa Feat/add skill tool (#11051)
* feat: add SkillTool component and integrate into agent tools

- Introduced SkillTool component for rendering skill-related functionality.
- Updated MessageAgentTools to include SkillTool in the tool renderers.
- Enhanced MessageTool to recognize 'Skill' as a valid agent tool type.
- Modified handleUserMessage to conditionally handle text blocks based on skill inclusion.
- Added SkillToolInput and SkillToolOutput types for better type safety.

* feat: implement command tag filtering in message handling

- Added filterCommandTags function to remove command-* tags from text content, ensuring internal command messages do not appear in the user-facing UI.
- Updated handleUserMessage to utilize the new filtering logic, enhancing the handling of text blocks and improving user experience by preventing unwanted command messages from being displayed.

* refactor: rename tool prefix constants for clarity

- Updated variable names for tool prefixes in MessageTool and SkillTool components to enhance code readability.
- Changed `prefix` to `builtinToolsPrefix` and `agentPrefix` to `agentMcpToolsPrefix` for better understanding of their purpose.
2025-10-31 16:31:50 +08:00
defi-failure
68e0d8b0f1 feat: add confirmation modal for activating protocol-installed MCP (#11070)
* feat: add confirmation modal for activating protocol-installed MCP

* fix: sync i18n

* fix(i18n): Auto update translations for PR #11070

* chore: verify ci is working

* Revert "chore: verify ci is working"

This reverts commit a2434a397d.

---------

Co-authored-by: GitHub Action <action@github.com>
2025-10-31 16:05:02 +08:00
LiuVaayne
7f1c234ac1 fix(ClaudeCodeService): update environment variable names for models (#11073) 2025-10-31 14:46:24 +08:00
Phantom
c1fd23742f fix: activate assistant/agent when creating new (#11009)
* refactor: remove unused SWITCH_ASSISTANT event and related code

Clean up unused event and associated listener in HomePage component

* feat(agents): improve agent handling and state management

- Return result from useUpdateAgent hook
- Update useActiveTopic to handle null assistantId
- Add state management for active agent and topic in Tabs
- Implement afterSubmit callback in AgentModal
- Refactor agent press handling in AssistantsTab
- Clean up HomePage state management logic
- Add afterCreate callback in UnifiedAddButton

* refactor(agent): update agent and session update functions to return entities

Modify update functions in useUpdateAgent and useUpdateSession hooks to return updated entities.
Update related components to handle the new return types and adjust type definitions accordingly.

* refactor(hooks): simplify active topic hook by using useAssistant

* refactor(agent): consolidate agent update types and functions

Move UpdateAgentBaseOptions and related function types from hooks/agents/types.ts to types/agent.ts
Update components to use new UpdateAgentFunctionUnion type
Simplify component props by removing redundant type definitions

* refactor(agent): update type for plugin settings update function

* refactor(AgentSettings): simplify tooling settings type definitions

Remove unused hooks and use direct type imports instead of ReturnType
2025-10-31 14:41:07 +08:00
LiuVaayne
d792bf7fe0 🐛 fix: resolve tool approval UI and shared workspace plugin inconsistency (#11043)
* fix(ToolPermissionRequestCard): simplify button rendering by removing suggestion handling

*  feat: add CachedPluginsDataSchema for plugin cache file

- Add Zod schema for .claude/plugins.json cache file format
- Schema includes version, lastUpdated timestamp, and plugins array
- Reuses existing InstalledPluginSchema for type safety
- Cache will store metadata for all installed plugins

*  feat: add cache management methods to PluginService

- Add readCacheFile() to read .claude/plugins.json
- Add writeCacheFile() for atomic cache writes (temp + rename)
- Add rebuildCache() to scan filesystem and rebuild cache
- Add listInstalledFromCache() to load plugins from cache with fallback
- Add updateCache() helper for transactional cache updates
- All methods handle missing/corrupt cache gracefully
- Cache auto-regenerates from filesystem if needed

*  feat: integrate cache loading in AgentService.getAgent()

- Add installed_plugins field to GetAgentResponseSchema
- Load plugins from cache via PluginService.listInstalledFromCache()
- Gracefully handle errors by returning empty array
- Use loggerService for error logging

* 🐛 fix: break circular dependency causing infinite loop in cache methods

- Change cache method signatures from agentId to workdir parameter
- Update listInstalledFromCache(workdir) to accept workdir directly
- Update rebuildCache(workdir) to accept workdir directly
- Update updateCache(workdir, updater) to accept workdir directly
- AgentService.getAgent() now passes accessible_paths[0] to cache methods
- Removes AgentService.getAgent() calls from PluginService methods
- Fixes infinite recursion bug where methods called each other endlessly

Breaking the circular dependency:
BEFORE: AgentService.getAgent() → PluginService.listInstalledFromCache(id)
        → AgentService.getAgent(id) [INFINITE LOOP]
AFTER:  AgentService.getAgent() → PluginService.listInstalledFromCache(workdir)
        [NO MORE RECURSION]

* 🐛 fix: update listInstalled() to use agent.installed_plugins

- Change from agent.configuration.installed_plugins (old DB location)
- To agent.installed_plugins (new top-level field from cache)
- Simplify validation logic to use existing plugin structure
- Fixes UI not showing installed plugins correctly

This was causing the UI to show empty plugin lists even though plugins
were correctly loaded in the cache by AgentService.getAgent().

* ♻️ refactor: remove unused updateCache helper

* ♻️ refactor: centralize plugin directory helpers

* feat: Implement Plugin Management System

- Added PluginCacheStore for managing plugin metadata and caching.
- Introduced PluginInstaller for handling installation and uninstallation of plugins.
- Created PluginService to manage plugin lifecycle, including installation, uninstallation, and listing of available plugins.
- Enhanced AgentService to integrate with PluginService for loading installed plugins.
- Implemented validation and sanitization for plugin file names and paths to prevent security issues.
- Added support for skills as a new plugin type, including installation and management.
- Introduced caching mechanism for available plugins to improve performance.

* ♻️ refactor: simplify PluginInstaller and PluginService by removing agent dependency and updating plugin handling
2025-10-31 14:30:50 +08:00
亢奋猫
f8a599322f feat(useAppInit): implement automatic update checks with interval sup… (#11063)
feat(useAppInit): implement automatic update checks with interval support

- Added a function to check for updates, which is called initially and set to run every 6 hours if the app is packaged and auto-update is enabled.
- Refactored the initial update check to utilize the new function for better code organization and clarity.
2025-10-31 13:35:27 +08:00
defi-failure
aa810a7ead fix: notify renderer when api server ready (#11049)
* fix: notify renderer when api server ready

* chore: minor comment update

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fix: minor ui change to reflect server loading state

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-31 12:13:59 +08:00
Pleasure1234
b586e1796e fix: sort grouped items by saved tags order from Redux (#11065)
Updated useUnifiedGrouping to sort grouped items by the tags order saved in the Redux store, falling back to untagged first. This improves consistency with user-defined tag ordering.
2025-10-31 11:21:10 +08:00
George·Dong
fa2ec69fa9 fix(SettingsTab): Context slider inconsistent (#10943)
* fix(i18n): standardize "max" translation to indicate unlimited

* feat(SettingsTab): add current context

* feat(settings): show proper "max" label for context count

* fix(settings): simplify contextCount value expression

* feat(settings): make context count editable with number input
2025-10-30 20:15:35 +08:00
SuYao
dd8690b592 feat: 添加路由懒加载组件以优化页面加载性能 (#11042) 2025-10-30 16:41:07 +08:00
MyPrototypeWhat
09e6b9741e fix: update GlobTool to count lines instead of files in output (#11036) 2025-10-30 16:14:04 +08:00
ABucket
0767952a6f docs: fix invalid link in the contributing guide (#11038)
docs: fix invalid link
2025-10-29 22:28:38 +08:00
MyPrototypeWhat
72299f833a feat(ReadTool): add function to remove <system-reminder> tags (#11034)
feat(ReadTool): add function to remove <system-reminder> tags from output text

- Introduced `removeSystemReminderTags` function to clean output by removing <system-reminder> tags and their content.
- Updated output processing logic to apply this function for both array and string output types, ensuring consistent formatting.
2025-10-29 22:24:44 +08:00
MyPrototypeWhat
7badaf02b9 fix(TodoWriteTool): remove output rendering from TodoWriteTool component (#11035)
* fix: remove output rendering from TodoWriteTool component

* refactor: remove output parameter from TodoWriteTool component
2025-10-29 20:37:54 +08:00
defi-failure
dfbfc2869c fix: use session model when sending messages (#11028) 2025-10-29 16:32:04 +08:00
Phantom
1575e97168 fix: approved tools (#11025)
* refactor(agent): move permission mode types and constants to config

Move PermissionModeCard type definition to types/agent.ts and relocate permissionModeCards constant from constants/permissionModes.ts to config/agent.ts for better organization and maintainability

* refactor(AgentSettings): simplify state management in ToolingSettings

remove redundant state for selectedMode and derive it from configuration
consolidate permission mode constants import path

* docs(AgentSettings): add jsdoc for computeModeDefaults function

* refactor(AgentSettings): simplify tooling state management with useMemo

remove redundant state for autoToolIds and compute it directly using useMemo

* refactor(AgentSettings): simplify tool approval state management

- Replace useState with useMemo for approvedToolIds to prevent unnecessary state updates
- Remove redundant state transitions and simplify toggle logic
- Ensure consistent tool filtering and merging with defaults

* refactor(AgentSettings): replace useState with useMemo for configuration state

Optimize performance by memoizing agent configuration state to prevent unnecessary re-renders

* perf(AgentSettings): optimize permission_mode computation with useMemo

Prevent unnecessary recalculations of permission_mode by memoizing the value

* refactor(AgentSettings): simplify MCP selection logic and remove unused imports

Remove useEffect for MCP state synchronization and directly use memoized value
Clean up unused imports and simplify toggle handler logic

* refactor: remove unused useAgentClient hook from ToolingSettings
2025-10-29 16:21:29 +08:00
SuYao
e0a2ed0481 Provider Config & anthropic-web-fetch (#10808)
* fix: update AI SDK dependencies to latest versions

* feat: Update provider configurations and API handling

- Refactor provider configuration to support new API types and enhance API host formatting.
- Introduce new utility functions for handling API versions and formatting Azure OpenAI hosts.
- Update system models to include new capabilities and adjust provider types for CherryIN and VertexAI.
- Enhance provider settings UI to accommodate new API types and improve user experience.
- Implement migration logic for provider type updates and default API host settings.
- Update translations for API host configuration tips across multiple languages.
- Fix various type checks and utility functions to ensure compatibility with new provider types.

* fix: update unsupported API version providers and add longcat to compatible provider IDs

* fix: 移除不再使用的 Azure OpenAI API 版本参数,优化 API 主机格式化逻辑
feat: 在选择器组件中添加样式属性,增强可定制性
feat: 更新提供者设置,支持动态选择 API 主机字段

* refactor: 优化测试用例

* 修复: 更新工具调用处理器以支持新的工具调用类型

* feat: 添加TODO注释以改进基于AI SDK的供应商内置工具展示和类型安全处理

* feat: 添加对Google SDK的支持,更新流式参数构建逻辑以包含Google工具的上下文

* feat: 更新web搜索模型判断逻辑,使用SystemProviderIds常量替代硬编码字符串

* feat: 添加对@renderer/store的mock以支持测试环境

* feat: 添加API主机地址验证功能,更新相关逻辑以支持端点提取

* fix: i18n

* fix(i18n): Auto update translations for PR #10808

* Apply suggestion from @EurFelux

Co-authored-by: Phantom <eurfelux@gmail.com>

* Apply suggestion from @EurFelux

Co-authored-by: Phantom <eurfelux@gmail.com>

* Apply suggestion from @EurFelux

Co-authored-by: Phantom <eurfelux@gmail.com>

* refactor: Simplify provider type migration logic and enhance API version validation

* fix: Correct variable name from configedApiHost to configuredApiHost for consistency

* fix: Update package.json to remove deprecated @ai-sdk/google version and streamline @ai-sdk/openai versioning

* fix: 更新 hasAPIVersion 函数中的正则表达式以更准确地匹配 API 版本路径

* fix(api): 简化 validateApiHost 函数逻辑以始终返回 true
fix(yarn): 更新 @ai-sdk/openai 版本至 2.0.53 并添加依赖项

* fix(api): 修正 validateApiHost 函数在使用哈希后缀时的验证逻辑

---------

Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: Phantom <eurfelux@gmail.com>
2025-10-29 14:47:21 +08:00
LiuVaayne
5790c12011 Feat: Enhanced Tool Permission System with Real-time Approvals #10738 (#10743)
* ⬆️ chore: upgrade claude agent sdk to 0.1.15

*  feat: add initial tool permission approval system

- Add promptForToolApproval function for real-time tool approval UI
- Integrate canUseTool callback into ClaudeCodeService
- Create tool-permissions.ts module for permission handling
- Set foundation for enhanced tool permission system #10738

This provides the basic infrastructure for displaying tool approval
prompts and getting user consent before agents execute potentially
dangerous operations.

* restore to main for

restore to main for

*  feat: implement agent tool permission request system

Add comprehensive tool permission management for Claude Code agents with:
- IPC channels for bidirectional permission requests/responses between main and renderer
- Permission request queue with timeout (30s), abort signal handling, and auto-cleanup
- Auto-approval system via CHERRY_AUTO_ALLOW_TOOLS env var and default allow-list (Read, Glob, Grep)
- New ToolPermissionRequestCard UI component for user approval with input preview
- Redux store (toolPermissions) for tracking pending/resolved permission requests
- User input stream architecture allowing dynamic user messages during agent execution

Key changes:
- packages/shared/IpcChannel.ts: Add AgentToolPermission_* channels
- src/main/services/agents/services/claudecode/: Refactor canUseTool with permission prompts
- src/renderer/src/store/toolPermissions.ts: New Redux slice for permission state
- src/renderer/src/pages/home/Messages/Tools/ToolPermissionRequestCard.tsx: Interactive approval UI

* refactor: simplify ToolPermissionRequestCard by removing unused imports and suggestion handling logic

* feat: add i18n

* fix(i18n): Auto update translations for PR #10743

---------

Co-authored-by: dev <verc20.dev@proton.me>
Co-authored-by: GitHub Action <action@github.com>
2025-10-29 13:37:32 +08:00
LiuVaayne
352ecbc506 feat: add plugin management system for Claude Agent (agents, commands, skills) (#10854)
*  feat: add claude-code-templates via git submodule with build-time copy

- Add git submodule for davila7/claude-code-templates
- Create scripts/copy-templates.js to copy components at build time
- Update package.json build script to include template copying
- Add resources/data/components/ to .gitignore (generated files)

Templates are now automatically synced from external repo to resources/data/components/
during build process, avoiding manual file copying.

To update templates: git submodule update --remote --merge

* fix: update target directory for copying Claude Code templates

*  feat: merge Anthropics skills into template sync

* 📝 docs: add agent plugins management implementation spec

Add comprehensive implementation plan for plugin management feature:
- Security validation and transactional operations
- Plugin browsing, installation, and management UI
- IPC handlers and PluginService architecture
- Metadata caching and database integration

*  feat: add plugin management backend infrastructure

Backend implementation for Claude Code plugin management:

- Add PluginService with security validation and caching
- Create IPC handlers for plugin operations (list, install, uninstall)
- Add markdown parser with safe YAML frontmatter parsing
- Extend AgentConfiguration schema with installed_plugins field
- Update preload bridge to expose plugin API to renderer
- Add plugin types (PluginMetadata, PluginError, PluginResult)

Features:
- Transactional install/uninstall with rollback
- Path traversal prevention and file validation
- 5-minute plugin list caching for performance
- SHA-256 content hashing for integrity checks
- Duplicate plugin handling (auto-replace)

Dependencies added:
- gray-matter: Markdown frontmatter parsing
- js-yaml: Safe YAML parsing with FAILSAFE_SCHEMA

*  feat: add plugin management UI and integration

Complete frontend implementation for Claude Code plugin management:

**React Hooks:**
- useAvailablePlugins: Fetch and cache available plugins
- useInstalledPlugins: List installed plugins with refresh
- usePluginActions: Install/uninstall with loading states

**UI Components (HeroUI):**
- PluginCard: Display plugin with install/uninstall actions
- CategoryFilter: Multi-select chip-based category filter
- InstalledPluginsList: Table view with uninstall confirmation
- PluginBrowser: Search, filter, pagination, responsive grid
- PluginSettings: Main container with Available/Installed tabs

**Integration:**
- Added "Plugins" tab to AgentSettingsPopup
- Full i18n support (English, Simplified Chinese, Traditional Chinese)
- Toast notifications for success/error states
- Loading skeletons and empty states

**Features:**
- Search plugins by name/description
- Filter by category and type (agents/commands)
- Pagination (12 items per page)
- Install/uninstall with confirmation dialogs
- Real-time plugin list updates
- Responsive grid layout (1-3 columns)

All code formatted with Biome and follows existing patterns.

* 🐛 fix: add missing plugin i18n keys at root level

Add plugin translation keys at root 'plugins.*' level to match component usage:
- Search and filter UI strings
- Pluralization support for result counts
- Empty state messages
- Action button labels
- Confirmation dialog text

Translations added for all three locales (en-US, zh-CN, zh-TW).

* 🐛 fix: use getResourcePath() utility for plugin directory resolution

Replace manual path calculation with getResourcePath() utility which correctly
handles both development and production environments. This fixes the issue where
plugins were not loading because __dirname was resolving to the wrong location.

Fixes:
- Plugins now load correctly in development mode
- Path resolution consistent with other resource loading in the app
- Removed unused 'app' import from electron

* 🎨 fix: improve plugin UI scrolling and category filter layout

Fixes two UI issues:

1. Enable scrolling for plugin list:
   - Changed overflow-hidden to overflow-y-auto on tab containers
   - Plugin grid now scrollable when content exceeds viewport

2. Make category filter more compact:
   - Added max-h-24 (96px) height limit to category chip container
   - Enabled vertical scrolling for category chips
   - Prevents category filter from taking too much vertical space

UI improvements enhance usability when browsing large plugin collections.

* 🎨 fix: ensure both agent and command badges have visible backgrounds

Changed Chip variant from 'flat' to 'solid' for plugin type badges.
This ensures both agent (primary) and command (secondary) badges display
with consistent, visible background colors instead of command badges
appearing as text-only.

*  feat: add plugin detail modal for viewing full plugin information

Add modal to display complete plugin details when clicking on a card:

Features:
- Click any plugin card to view full details in a modal
- Shows complete description (not truncated)
- Displays all metadata: version, author, tools, allowed_tools, tags
- Shows file info: filename, size, source path, install date
- Install/uninstall actions available in modal
- Hover effect on cards to indicate clickability
- Button clicks don't trigger card click (event.stopPropagation)

Components:
- New PluginDetailModal component with scrollable content
- Updated PluginCard to be clickable (isPressable)
- Updated PluginBrowser to manage modal state

UI improvements provide better plugin exploration and decision-making.

* 🐛 fix: render plugin detail modal above agent settings modal

Use React portal to render PluginDetailModal directly to document.body,
ensuring it appears above the agent settings modal instead of being
blocked by it.

Changes:
- Import createPortal from react-dom
- Wrap modal content in createPortal(modalContent, document.body)
- Add z-[9999] to modal wrapper for proper layering

Fixes modal visibility issue where plugin details were hidden behind
the parent agent settings modal.

*  feat: add plugin content viewing and editing in detail modal

- Added IPC channels for reading and writing plugin content
- Implemented readContent() and writeContent() methods in PluginService
- Added IPC handlers for content operations with proper error handling
- Exposed plugin content API through preload bridge
- Updated PluginDetailModal to fetch and display markdown content
- Added edit mode with textarea for modifying plugin content
- Implemented save/cancel functionality with optimistic UI updates
- Added agentId prop to component chain for write operations
- Updated AgentConfigurationSchema to include all plugin metadata fields
- Moved plugin types to shared @types for cross-process access
- Added validation and security checks for content read/write
- Updated content hash in DB after successful edits

* 🐛 fix: change event handler from onPress to onClick for uninstall and install buttons

* 📝 docs: update AI Assistant Guide to clarify proposal and commit guidelines

* 📝 docs: add skills support extension spec for agent plugins management

*  feat: add secure file operation utilities for skills plugin system

- Implement copyDirectoryRecursive() with security protections
- Implement deleteDirectoryRecursive() with path validation
- Implement getDirectorySize() for folder size calculation
- Add path traversal protection using isPathInside()
- Handle symlinks securely to prevent attacks
- Add recursion depth limits to prevent stack overflow
- Preserve file permissions during copy
- Handle race conditions and missing files gracefully
- Skip special files (pipes, sockets, devices)

Security features:
- Path validation against allowedBasePath boundary
- Symlink detection and skip to prevent circular loops
- Input validation for null/empty/relative paths
- Comprehensive error handling and logging

Updated spec status to "In Progress" and added implementation progress checklist.

*  feat: add skill type support and skill metadata parsing

Type System Updates (plugin.ts):
- Add PluginType export for 'agent' | 'command' | 'skill'
- Update PluginMetadataSchema to include 'skill' in type enum
- Update InstalledPluginSchema to support skill type
- Update all option interfaces to support skill type
- Add skills array to ListAvailablePluginsResult
- Document filename semantics differences between types

Markdown Parser Updates (markdownParser.ts):
- Implement parseSkillMetadata() function for SKILL.md parsing
- Add comprehensive input validation (absolute path check)
- Add robust error handling with specific PluginErrors
- Add try-catch around file operations and YAML parsing
- Add type validation for frontmatter data fields
- Add proper logging using loggerService
- Handle getDirectorySize() failures gracefully
- Document hash scope decision (SKILL.md only vs entire folder)
- Use FAILSAFE_SCHEMA for safe YAML parsing

Security improvements:
- Path validation to ensure absolute paths
- Differentiate ENOENT from permission errors
- Type validation for all frontmatter fields
- Safe YAML parsing to prevent deserialization attacks

Updated spec progress tracking.

*  feat: implement complete skill support in PluginService

Core Infrastructure:
- Add imports for parseSkillMetadata and file operation utilities
- Add PluginType to imports for type-safe handling

Skill-Specific Methods:
- sanitizeFolderName() - validates folder names (no dots allowed)
- scanSkillDirectory() - scans skills/ for skill folders
- installSkill() - copies folders with transaction/rollback
- uninstallSkill() - removes folders with transaction/rollback

Updated Methods for Skills Support:
- listAvailable() - now scans and returns skills array
- install() - branches on type to handle skills vs files
- uninstall() - branches on type for skill/file handling
- ensureClaudeDirectory() - handles 'skills' subdirectory
- listInstalled() - validates skill folders on filesystem
- writeContent() - updated signature to accept PluginType

Key Implementation Details:
- Skills use folder names WITHOUT extensions
- Agents/commands use filenames WITH .md extension
- Different sanitization rules for folders vs files
- Transaction pattern with rollback for all operations
- Comprehensive logging and error handling
- Maintains backward compatibility with existing code

Updated spec progress tracking.

*  feat: add skill support to frontend hooks and UI components

Frontend Hooks (usePlugins.ts):
- Add skills state to useAvailablePlugins hook
- Return skills array in hook result
- Update install() to accept 'skill' type
- Update uninstall() to accept 'skill' type

UI Components:
- PluginCard: Add 'skill' type badge with success color
- PluginBrowser: Add skills prop and include in plugin list
- PluginBrowser: Update type definitions to include 'skill'
- PluginBrowser: Include skills in tab filtering

Complete frontend integration for skills plugin type.
Updated spec progress tracking.

* ♻️ refactor: remove unused variable in installSkill method

* 📝 docs: mark implementation as complete with summary

Implementation Status: COMPLETE (11/12 tasks)

Completed:
-  File operation utilities with security protections
-  Skill metadata parsing with validation
-  Plugin type system updated to include 'skill'
-  PluginService skill methods (scan, install, uninstall)
-  PluginService updated for skill support
-  IPC handlers (no changes needed - already generic)
-  Frontend hooks updated for skills
-  UI components updated (PluginCard, PluginBrowser)
-  Build check passed with lint fixes

Deferred (non-blocking):
- ⏸️ Session integration - requires further investigation
  into session handler location and implementation

The core skills plugin system is fully implemented and functional.
Skills can be browsed, installed, and uninstalled through the UI.
All security requirements met with path validation and transaction
rollback. Code passes lint checks and follows project patterns.

* 🐛 fix: pass skills prop to PluginBrowser component

Fixed "skills is not iterable" error by:
- Destructuring skills from useAvailablePlugins hook
- Updating type annotations to include 'skill' type
- Passing skills prop to PluginBrowser component

This completes the missing UI wiring for skills support.

*  feat: add Skills tab to plugin browser

Added missing Skills tab to PluginBrowser component:
- Added Skills tab to type tabs
- Added translations for skills in all locales (en-us, zh-cn, zh-tw)
  - English: "Skills"
  - Simplified Chinese: "技能"
  - Traditional Chinese: "技能"

This completes the UI integration for the skills plugin type.

*  feat: add 'skill' type to AgentConfiguration and GetAgentSessionResponse schemas

* ⬆️ chore: upgrade @anthropic-ai/claude-agent-sdk to v0.1.25 with patch

- Updated from v0.1.1 to v0.1.25
- Applied fork/IPC patch to new version
- Removed old patch file
- All tests passing

* 🐛 fix: resolve linting and TypeScript type errors in build check

- Add external/** and resources/data/claude-code-plugins/** to lint ignore patterns
  to exclude git submodules and plugin templates from linting
- Fix TypeScript error handling in IPC handlers by properly typing caught errors
- Fix AgentConfiguration type mismatches by providing default values for
  permission_mode and max_turns when spreading configuration
- Replace control character regex with String.fromCharCode() to avoid ESLint
  no-control-regex rule in sanitization functions
- Fix markdownParser yaml.load return type by adding type assertion
- Add getPluginErrorMessage helper to properly extract error messages from
  PluginError discriminated union types

Main process TypeScript errors: Fixed (0 errors)
Linting errors: Fixed (0 errors from 4397)
Remaining: 4 renderer TypeScript errors in settings components

* ♻️ refactor: improve plugin error handling and reorganize i18n structure

* ⬆️ chore: update @anthropic-ai/claude-agent-sdk to include patch and additional dependencies

* 🗑️ chore: remove unused Claude code plugins and related configurations

- Deleted `.gitmodules` and associated submodules for `claude-code-templates` and `anthropics-skills`.
- Updated `.gitignore`, `.oxlintrc.json`, and `eslint.config.mjs` to exclude `claude-code-plugins`.
- Modified `package.json` to remove the build script dependency on copying templates.
- Adjusted `PluginService.ts` to handle plugin paths without relying on removed resources.

* format code

* delete

* delete

* fix(i18n): Auto update translations for PR #10854

*  feat: enhance PluginService and markdownParser with recursive skill directory search

- Added `findAllSkillDirectories` function to recursively locate directories containing `SKILL.md`.
- Updated `scanSkillDirectory` method in `PluginService` to utilize the new recursive search.
- Modified `PluginDetailModal` to append `/SKILL.md` to the source path for skill plugins.

* fix(i18n): Auto update translations for PR #10854

* remove specs

* update claude code plugins files

---------

Co-authored-by: suyao <sy20010504@gmail.com>
Co-authored-by: beyondkmp <beyondkmp@gmail.com>
Co-authored-by: GitHub Action <action@github.com>
2025-10-29 13:33:11 +08:00
George·Dong
fc4f30feab fix: update Dashscope Anthropic API host and migrate old configs (#10973)
* fix: update Dashscope Anthropic API host and migrate old configs

* fix(migration): remove obsolete dashscope rewrite

* fix(migrate): overwrite Anthropic API host for dashscope provider
2025-10-29 09:20:19 +08:00
Carlton
888a183328 feat(knowledge, preprocess): Add OpenMinerU preprocessor, knowledge base supports using open-source version of MinerU to process documents (#10992)
* feat(knowledge): 为文档处理增加open-mineru的支持

* 移除OpenMineruPreprocessProvider多余的apiKey处理

* Add preprocessProviders migrator for open-mineru

* Remove invalid code from OpenMineruPreprocessProvider

* fix property '"open-mineru"' is missing in PREPROCESS_PROVIDER_CONFIG

* refactor(preprocess): improve OpenMinerU error handling and file cleanup
2025-10-29 09:19:18 +08:00
scientia
9a01e092f6 feat: allow new-topic bindkey to create new session for agent as well (#10862)
* fix: allow new-topic shortcut to create agent sessions

* revert: restore ProxyManager.ts

* fix: make agent session shortcut sidebar-independent

* refactor: centralize default session creation

* feat: add new session button to agent inputbar

* fix: encapsulate agent session creation state

* refactor: remove redundant useMemo in useCreateDefaultSession

The useMemo wrapper around the return object was unnecessary because:
- createDefaultSession is already memoized via useCallback
- creatingSession is a primitive boolean that doesn't need memoization
- The object gets recreated on every creatingSession change anyway

This simplifies the code and removes unnecessary overhead.

---------

Co-authored-by: wangdenghui <wangdenghui@xiaomi.com>
2025-10-28 22:59:44 +08:00
George·Dong
5986800c9d chore(ci): exempt all milestones and assignee from staling (#11008) 2025-10-28 19:20:05 +08:00
槑囿脑袋
56d68276e1 fix(knowledge): force choose knowledge aisdk error (#11006)
fix: aisdk error
2025-10-28 16:37:58 +08:00
Copilot
29c1173365 Fix Qwen3 thinking mode control for Ollama using aiCore middleware (#10947)
* Initial plan

* Fix Qwen3 thinking mode control for Ollama by using reasoning_effort instead of qwenThinkMode

Co-authored-by: DeJeune <67425183+DeJeune@users.noreply.github.com>

* Refactor: Move Qwen thinking mode control from legacy to aiCore middleware

- Revert changes to legacy OpenAIApiClient.ts
- Create new qwenThinkingMiddleware for handling /think and /no_think suffixes
- Update AiSdkMiddlewareBuilder to add assistant field and apply Qwen middleware
- Middleware automatically handles Qwen models on Ollama, LM Studio, and NVIDIA providers
- This follows the modern aiCore architecture instead of deprecated legacy approach

Co-authored-by: DeJeune <67425183+DeJeune@users.noreply.github.com>

* Fix: Explicitly pass assistant field to buildAiSdkMiddlewares

Ensure that the assistant field from ModernAiProviderConfig is explicitly passed
to buildAiSdkMiddlewares so that Qwen thinking middleware can access
assistant.settings.reasoning_effort correctly.

Co-authored-by: DeJeune <67425183+DeJeune@users.noreply.github.com>

* Fix: Remove unnecessary whitespace in processPostsuffixQwen3Model tests

* Refactor: Simplify user message suffix handling in qwenThinkingMiddleware

* Refactor: Remove processPostsuffixQwen3Model tests to streamline ModelMessageService tests

* refactor: remove logger and debug statement from qwenThinkingMiddleware

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: DeJeune <67425183+DeJeune@users.noreply.github.com>
Co-authored-by: suyao <sy20010504@gmail.com>
2025-10-28 14:26:54 +08:00
Jake Jia
c7ceb3035d fix: align and unify LocalBackupManager footer layout (#10985)
* fix: align and unify LocalBackupManager footer layout

- Use Space component to wrap footer buttons, consistent with S3BackupManager
- Optimize delete button i18n text by using count parameter instead of hardcoded concatenation

* fix: fix the i18n issue in the  delete button text
2025-10-28 13:26:53 +08:00
Phantom
7bcae6fba2 fix(Navbar): adjust min-height calculation for fullscreen mode on Mac (#10990)
Ensure the navbar height is correctly calculated when toggling fullscreen mode on macOS by considering the $isFullScreen prop
2025-10-28 10:29:28 +08:00
Phantom
9776b4e46c fix(sidebar): replace 'agents' with 'store' in sidebar (#10989)
refactor(sidebar): replace 'agents' with 'store' in sidebar icons and labels

Update sidebar icon mapping and translation keys to use 'store' instead of 'agents' for consistency with the application's terminology. The change includes both the label definitions and the icon component mapping.
2025-10-27 20:35:12 +08:00
SuYao
250f59234b feat: add isClaude45ReasoningModel function and update getTopP logic (#10988)
* feat: add isClaude45ReasoningModel function and update getTopP logic

* fix: update getTopP logic to correctly handle Claude45 model support

* fix: update getTemperature and getTopP logic to handle Claude45 model conditions

* fix: update getTemperature logic to correctly handle Claude45 model conditions
fix: refine isClaude45ReasoningModel regex pattern for better matching
2025-10-27 20:34:11 +08:00
SuYao
82132d479a feat: add huggingface provider (#10966)
* Refactor code structure for improved readability and maintainability

* fix(i18n): Auto update translations for PR #10966

* fix: add empty array for huggingface models in SYSTEM_MODELS

* feat: integrate HuggingFace provider and enhance reasoning options

* fix: remove debug console logs from provider options functions

---------

Co-authored-by: GitHub Action <action@github.com>
2025-10-27 13:30:23 +08:00
fullex
44e01e5ad4 chore: add CODEOWNERS for databases directories 2025-10-27 09:35:08 +08:00
Xin Rui
c5ce0b763b fix: up-down button does not hide properly in some cases (#10693)
* fix: simplify navigation button auto-hide logic

Remove complex state management (isNearButtons, resetHideTimer) and rely directly
on isInTriggerArea to control button visibility. This fixes the issue where buttons
don't properly auto-hide by using mouse position detection instead of fragile state tracking.

- Simplify showNavigation to just show and clear timers
- Remove resetHideTimer function and use showNavigation directly
- Simplify handleNavigationMouseLeave to always schedule hide after 500ms
- Update all button handlers to call showNavigation() instead of resetHideTimer()
- Rely on mouse enter/leave events to control visibility state

* refactor(ChatNavigation): replace native setTimeout with custom useTimer hook

Use custom useTimer hook for better timer management and cleanup

---------

Co-authored-by: icarus <eurfelux@gmail.com>
2025-10-26 23:09:46 +08:00
Phantom
f5a1d3f8d0 fix(hooks): prevent save on composing enter key in useInPlaceEdit (#10972) 2025-10-26 17:45:31 +08:00
Phantom
d8f1a68e87 ci(i18n): update translation config to use TRANSLATION_BASE_LOCALE (#10965)
Change BASE_LOCALE to TRANSLATION_BASE_LOCALE across scripts and workflows for consistency
Add console log for base locale in auto-translate script
2025-10-26 15:58:54 +08:00
Phantom
8054ed7ad8 fix: disappeared MCP button (#10956)
* refactor(types): add InputBarToolType and update related types

- Define InputBarToolType union type in chat types
- Update ToolOrder and InputToolsState to use InputBarToolType
- Modify InputbarTools component to use new type for tool keys

* refactor(assistants): use DEFAULT_ASSISTANT_SETTINGS constant for default settings

* fix(assistants): ensure default settings for presets in migration

Add default settings for assistant presets during migration if they are missing, including toolUseMode
2025-10-26 11:58:38 +08:00
Phantom
487b5c4d8a fix(aiCore): support minimax-m2 (#10962)
* fix(aiCore): add minimax-m2 to reasoning model check and correct comment

* feat(models): add minimax-m2 to function calling models list

* feat(models): add isMiniMaxReasoningModel helper function

Add helper function to check for MiniMax reasoning models and update isReasoningModel to use it
2025-10-26 02:29:52 +08:00
Phantom
dedfc79406 ci(auto-i18n): disable package manager cache for node setup (#10957)
* ci(github): disable package manager cache for node setup

* refactor(i18n): translate sync script comments to english

Update all Chinese comments and log messages in sync-i18n.ts to English for better international collaboration

* style(scripts): format error message string in sync-i18n.ts
2025-10-26 00:15:25 +08:00
Phantom
1f0fd8215a docs: update PR template and README with feature PR restrictions (#10955)
* docs: update PR template and README with feature PR restrictions

Add temporary hold notice for Redux/IndexedDB feature PRs in both pull request template and README
Fix whitespace and formatting inconsistencies in README

* docs: update contributing guidelines with temporary PR restrictions

Add important notice about temporary restrictions on data-changing feature PRs
Clarify acceptable contribution types during v2.0.0 development phase

* docs: remove warning about feature PR restrictions

The warning about temporary restrictions on feature PRs involving Redux or IndexedDB changes has been removed as it is no longer relevant

* docs: remove core developer membership section from contributing guides
2025-10-26 00:14:27 +08:00
defi-failure
e69fd7f22b fix: create assistant causing blank screen (#10853)
* fix: create or update assistant causing blank screen

* fix: remove redundant type annotation

* fix: improve logging

* fix: remove redundant check

* fix(migration): move presets initialization to migration 166

The initialization of assistants.presets was incorrectly placed in migration 164. Move it to a new migration 166 to ensure proper state initialization after versions 1.6.5 and 1.7.0-beta.2.

---------

Co-authored-by: icarus <eurfelux@gmail.com>
2025-10-25 21:10:37 +08:00
SuYao
ac4aa33e79 fix: azure gpt-image-1 and openrouter gemini-image (#10797)
* fix: azure gpt-image-1 and openroute gemini-image

* feat: update encoding format handling for embeddings based on model type

* fix: normalize model ID check for Azure OpenAI GPT-Image-1-Mini

* feat: enhance regex for gemini-2.5-flash-image in image enhancement models

* feat: 支持处理 base64 格式的图片 URL 在消息转换中

* feat: 更新消息转换函数以支持图像增强模型的特殊处理

* fix: update model handling in AzureOpenAI and Embeddings classes

* fix: update OpenAI package version to 6.5.0

* fix: remove outdated OpenAI package patch for version 6.4.0

* fix: remove outdated OpenAI package entry from yarn.lock
2025-10-25 16:18:18 +08:00
Jake Jia
6795a044fa feat(miniapp): add HuggingChat mini app (#10923)
* feat(miniapp): add HuggingChat mini app

- Add HuggingChat to default mini apps list
- Update HuggingChat SVG icon with official design

* chore(migration): add migration for HuggingChat mini app

- Add migration version 165 to add HuggingChat to existing users
- Update store version from 163 to 165

* fix(migrate): log error during mini app migration
2025-10-25 10:24:37 +08:00
SuYao
13093bb821 fix: optimize excluded websites handling in xai provider configuration (#10894) 2025-10-24 15:10:59 +08:00
Phantom
c7c9e1ee44 fix: use system prompt variables in quick assistant (#10925)
* feat: replace prompt variables in assistant before chat completion

* refactor(home-window): reorder prompt variable replacement for clarity

Move prompt variable replacement before message preparation to improve logical flow
2025-10-24 13:58:37 +08:00
beyondkmp
369b367562 feat(AppMenuService): enhance application menu with help section and others (#10934)
* feat(AppMenuService): enhance application menu with help section and others

* format

* fix german
2025-10-24 13:57:52 +08:00
Phantom
0081a0740f fix(InputbarTools): allow url context for gemini endpoint type model (#10926)
fix(InputbarTools): allow url context for gemini endpoint type

Add condition to check for gemini endpoint type when determining URL context support
2025-10-24 13:55:10 +08:00
Phantom
4dfb73c982 fix: silicon reasoning (#10932)
* refactor(aiCore): reorganize reasoning effort logic for different providers

Restructure the reasoning effort calculation logic to handle different model providers more clearly. Move OpenRouter and SiliconFlow specific logic to dedicated sections and remove duplicate checks. Improve maintainability by grouping related provider logic together.

* refactor(sdk): update thinking config type and property names

- Replace inline thinking config type with imported ThinkingConfig type
- Update property names from snake_case to camelCase for consistency
- Add null checks for token limit calculations
- Clarify hard-coded maximum for silicon provider in comments

* refactor(openai): standardize property names to camelCase in thinking_config

Update property names in thinking_config object from snake_case to camelCase for consistency with codebase conventions
2025-10-24 13:01:00 +08:00
Phantom
691656a397 feat(i18n): enhance translation script with concurrency and validation (#10916)
* feat(i18n): enhance translation script with concurrency and validation

- Add concurrent translation support with configurable limits
- Implement input validation for script configuration
- Improve error handling and progress tracking
- Add detailed usage instructions and performance recommendations

* fix(i18n): update translations for multiple languages

- Translate previously untranslated strings in zh-tw, ja-jp, pt-pt, es-es, ru-ru, el-gr, fr-fr
- Fix array to object structure in zh-cn accessibility description
- Add missing translations and fix structure in de-de locale

* chore: update i18n auto-translation script command

Update the yarn command from 'i18n:auto' to 'auto:i18n' for consistency with other script naming conventions

* ci: rename i18n workflow env vars for clarity

Use more descriptive names for translation-related environment variables to improve readability and maintainability

* Revert "fix(i18n): update translations for multiple languages"

This reverts commit 01dac1552e.

* fix(i18n): Auto update translations for PR #10916

* ci: run sync-i18n script before auto-translate in workflow

* fix(i18n): Auto update translations for PR #10916

---------

Co-authored-by: GitHub Action <action@github.com>
2025-10-24 02:12:10 +08:00
Jake Jia
d184f7a24b fix: align S3 backup manager action buttons horizontally (#10922) 2025-10-23 23:58:03 +08:00
Pleasure1234
1ac746a40e fix: use nullish coalescing for advanced property updates (#10921)
Replaces logical OR with nullish coalescing when updating advanced server properties to allow empty string values, enabling users to clear fields instead of preserving previous values.
2025-10-23 23:49:25 +08:00
beyondkmp
d187adb0d3 feat: redirect macOS About menu to settings About page (#10902)
* ci: add GitHub issue tracker workflow with Feishu notifications  (#10895)

* feat: add GitHub issue tracker workflow with Feishu notifications

* fix: add missing environment variable for Claude translator in GitHub issue tracker workflow

* fix: update environment variable for Claude translator in GitHub issue tracker workflow

* Add quiet hours handling and scheduled processing for GitHub issue notifications

- Implement quiet hours detection (00:00-08:30 Beijing Time) with delayed notifications
- Add scheduled workflow to process pending issues daily at 08:30 Beijing Time
- Create new script to batch process and summarize multiple pending issues with Claude

* Replace custom Node.js script with Claude Code Action for issue processing

- Migrate from custom JavaScript implementation to Claude Code Action for AI-powered issue summarization and processing
- Simplify workflow by leveraging Claude's built-in GitHub API integration and tool usage capabilities
- Maintain same functionality: fetch pending issues, generate Chinese summaries, send Feishu notifications, and clean up labels
- Update Claude action reference from version pin to main branch for latest features

* Remove GitHub issue comment functionality

- Delete automated AI summary comments on issues after processing
- Remove documentation for manual issue commenting workflow
- Keep Feishu notification system intact while streamlining issue interactions

* feat: redirect macOS About menu to settings About page

Add functionality to navigate to the About page in settings when clicking the About menu item in macOS menu bar.

Changes:
- Add Windows_NavigateToAbout IPC channel for communication between main and renderer processes
- Create AppMenuService to setup macOS application menu with custom About handler
- Add IPC handler in main process to show main window and trigger navigation
- Add IPC listener in renderer NavigationHandler to navigate to /settings/about
- Initialize AppMenuService on app startup for macOS platform

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* * feat: add GitHub issue tracker workflow with Feishu notifications

* feat: add GitHub issue tracker workflow with Feishu notifications

* fix: add missing environment variable for Claude translator in GitHub issue tracker workflow

* fix: update environment variable for Claude translator in GitHub issue tracker workflow

* Add quiet hours handling and scheduled processing for GitHub issue notifications

- Implement quiet hours detection (00:00-08:30 Beijing Time) with delayed notifications
- Add scheduled workflow to process pending issues daily at 08:30 Beijing Time
- Create new script to batch process and summarize multiple pending issues with Claude

* Replace custom Node.js script with Claude Code Action for issue processing

- Migrate from custom JavaScript implementation to Claude Code Action for AI-powered issue summarization and processing
- Simplify workflow by leveraging Claude's built-in GitHub API integration and tool usage capabilities
- Maintain same functionality: fetch pending issues, generate Chinese summaries, send Feishu notifications, and clean up labels
- Update Claude action reference from version pin to main branch for latest features

* Remove GitHub issue comment functionality

- Delete automated AI summary comments on issues after processing
- Remove documentation for manual issue commenting workflow
- Keep Feishu notification system intact while streamlining issue interactions

* Add OIDC token permissions and GitHub token to Claude workflow

- Add `id-token: write` permission for OIDC authentication in both jobs
- Pass `github_token` to Claude action for proper GitHub API access
- Maintain existing issue write and contents read permissions

* fix: add GitHub issue tracker workflow with Feishu notifications

* feat: add GitHub issue tracker workflow with Feishu notifications

* fix: add missing environment variable for Claude translator in GitHub issue tracker workflow

* fix: update environment variable for Claude translator in GitHub issue tracker workflow

* Add quiet hours handling and scheduled processing for GitHub issue notifications

- Implement quiet hours detection (00:00-08:30 Beijing Time) with delayed notifications
- Add scheduled workflow to process pending issues daily at 08:30 Beijing Time
- Create new script to batch process and summarize multiple pending issues with Claude

* Replace custom Node.js script with Claude Code Action for issue processing

- Migrate from custom JavaScript implementation to Claude Code Action for AI-powered issue summarization and processing
- Simplify workflow by leveraging Claude's built-in GitHub API integration and tool usage capabilities
- Maintain same functionality: fetch pending issues, generate Chinese summaries, send Feishu notifications, and clean up labels
- Update Claude action reference from version pin to main branch for latest features

* Remove GitHub issue comment functionality

- Delete automated AI summary comments on issues after processing
- Remove documentation for manual issue commenting workflow
- Keep Feishu notification system intact while streamlining issue interactions

* Add OIDC token permissions and GitHub token to Claude workflow

- Add `id-token: write` permission for OIDC authentication in both jobs
- Pass `github_token` to Claude action for proper GitHub API access
- Maintain existing issue write and contents read permissions

* Enhance GitHub issue automation workflow with Claude integration

- Refactor Claude action to handle issue analysis, Feishu notification, and comment creation in single step
- Add tool permissions for Bash commands and custom notification script execution
- Update prompt with detailed task instructions including summary generation and automated actions
- Remove separate notification step by integrating all operations into Claude action workflow

* fix

* 删除AI总结评论的添加步骤和注意事项

* fix comments

* refactor(AppMenuService): streamline WindowService usage

Updated the AppMenuService to directly import and use the windowService for retrieving the main window and showing it, enhancing code clarity and maintainability.

* add i18n

* fix(AppMenuService): handle macOS application menu setup conditionally

Updated the AppMenuService to only instantiate when running on macOS, preventing potential null reference errors. Additionally, added optional chaining in the main index file for safer menu setup.

* fix(i18n): Auto update translations for PR #10902

---------

Co-authored-by: SuYao <sy20010504@gmail.com>
Co-authored-by: Payne Fu <payne@Paynes-MacBook-Pro.local>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: GitHub Action <action@github.com>
2025-10-23 18:35:10 +08:00
Phantom
53881c5824 ci: update OpenAI dependency in i18n workflow (#10914)
* ci: update OpenAI dependency in i18n workflow

Use @cherrystudio/openai instead of openai package for translation dependencies

* ci(workflows): allow workflow dispatch for auto-i18n job
2025-10-23 18:21:00 +08:00
Zhaokun
35c15cd02c fix: topic branch incomplete copy - split ID mapping into two passes (#10900)
Fix the bug where topic branching would not copy all message relationships completely.The issue was that askId mapping lookup happened in the same loop as ID generation, causing later messages' askIds to fail mapping when they referenced messages that hadn't been processed yet.

Solution: Split into two passes:
 1. First pass: Generate new IDs for all messages and build complete mapping
 2. Second pass: Clone messages and blocks using the complete ID mapping

This ensures all message relationships (especially assistant message askId references)are properly maintained in the new topic.
2025-10-23 16:18:23 +08:00
SuYao
3c8b61e268 ci: add GitHub issue tracker workflow with Feishu notifications (#10895)
* feat: add GitHub issue tracker workflow with Feishu notifications

* fix: add missing environment variable for Claude translator in GitHub issue tracker workflow

* fix: update environment variable for Claude translator in GitHub issue tracker workflow

* Add quiet hours handling and scheduled processing for GitHub issue notifications

- Implement quiet hours detection (00:00-08:30 Beijing Time) with delayed notifications
- Add scheduled workflow to process pending issues daily at 08:30 Beijing Time
- Create new script to batch process and summarize multiple pending issues with Claude

* Replace custom Node.js script with Claude Code Action for issue processing

- Migrate from custom JavaScript implementation to Claude Code Action for AI-powered issue summarization and processing
- Simplify workflow by leveraging Claude's built-in GitHub API integration and tool usage capabilities
- Maintain same functionality: fetch pending issues, generate Chinese summaries, send Feishu notifications, and clean up labels
- Update Claude action reference from version pin to main branch for latest features

* Remove GitHub issue comment functionality

- Delete automated AI summary comments on issues after processing
- Remove documentation for manual issue commenting workflow
- Keep Feishu notification system intact while streamlining issue interactions

* feat: add GitHub issue tracker workflow with Feishu notifications

* feat: add GitHub issue tracker workflow with Feishu notifications

* fix: add missing environment variable for Claude translator in GitHub issue tracker workflow

* fix: update environment variable for Claude translator in GitHub issue tracker workflow

* Add quiet hours handling and scheduled processing for GitHub issue notifications

- Implement quiet hours detection (00:00-08:30 Beijing Time) with delayed notifications
- Add scheduled workflow to process pending issues daily at 08:30 Beijing Time
- Create new script to batch process and summarize multiple pending issues with Claude

* Replace custom Node.js script with Claude Code Action for issue processing

- Migrate from custom JavaScript implementation to Claude Code Action for AI-powered issue summarization and processing
- Simplify workflow by leveraging Claude's built-in GitHub API integration and tool usage capabilities
- Maintain same functionality: fetch pending issues, generate Chinese summaries, send Feishu notifications, and clean up labels
- Update Claude action reference from version pin to main branch for latest features

* Remove GitHub issue comment functionality

- Delete automated AI summary comments on issues after processing
- Remove documentation for manual issue commenting workflow
- Keep Feishu notification system intact while streamlining issue interactions

* Add OIDC token permissions and GitHub token to Claude workflow

- Add `id-token: write` permission for OIDC authentication in both jobs
- Pass `github_token` to Claude action for proper GitHub API access
- Maintain existing issue write and contents read permissions

fix: add GitHub issue tracker workflow with Feishu notifications

* feat: add GitHub issue tracker workflow with Feishu notifications

* fix: add missing environment variable for Claude translator in GitHub issue tracker workflow

* fix: update environment variable for Claude translator in GitHub issue tracker workflow

* Add quiet hours handling and scheduled processing for GitHub issue notifications

- Implement quiet hours detection (00:00-08:30 Beijing Time) with delayed notifications
- Add scheduled workflow to process pending issues daily at 08:30 Beijing Time
- Create new script to batch process and summarize multiple pending issues with Claude

* Replace custom Node.js script with Claude Code Action for issue processing

- Migrate from custom JavaScript implementation to Claude Code Action for AI-powered issue summarization and processing
- Simplify workflow by leveraging Claude's built-in GitHub API integration and tool usage capabilities
- Maintain same functionality: fetch pending issues, generate Chinese summaries, send Feishu notifications, and clean up labels
- Update Claude action reference from version pin to main branch for latest features

* Remove GitHub issue comment functionality

- Delete automated AI summary comments on issues after processing
- Remove documentation for manual issue commenting workflow
- Keep Feishu notification system intact while streamlining issue interactions

* Add OIDC token permissions and GitHub token to Claude workflow

- Add `id-token: write` permission for OIDC authentication in both jobs
- Pass `github_token` to Claude action for proper GitHub API access
- Maintain existing issue write and contents read permissions

* Enhance GitHub issue automation workflow with Claude integration

- Refactor Claude action to handle issue analysis, Feishu notification, and comment creation in single step
- Add tool permissions for Bash commands and custom notification script execution
- Update prompt with detailed task instructions including summary generation and automated actions
- Remove separate notification step by integrating all operations into Claude action workflow

* fix

* 删除AI总结评论的添加步骤和注意事项
2025-10-23 15:09:19 +08:00
ABucket
6f63eefa86 fix: deep research model only support medium search context and reasoning effort (#10676)
Co-authored-by: ABucket <abucket@github.com>
2025-10-22 21:59:00 +08:00
defi-failure
4a38f2e8b1 feat: add cherryin in provider type options (#10891) 2025-10-22 21:32:33 +08:00
Sipan
f088069fb3 Feature/add ling series support (#10863)
* feat(Miniapp): add Ling app and according migration support.

* feat(models): add Ling model support and related reasoning checks

Signed-off-by: cafe3310 <4354898+cafe3310@users.noreply.github.com>

* fix: resolved lint findings; simplifying model reasoning check.

---------

Signed-off-by: cafe3310 <4354898+cafe3310@users.noreply.github.com>
2025-10-22 18:25:11 +08:00
Phantom
7f83f0700b chore: migrate from openai to @cherrystudio/openai package (#10802)
* build: replace openai package with @cherrystudio/openai

Update all imports from 'openai' to '@cherrystudio/openai' and remove the yarn patch

* refactor(OpenAIResponseAPIClient): simplify token estimation logic for function call output

Consolidate token estimation by first concatenating all output parts into a single string before counting tokens. This improves maintainability and handles additional output types consistently.
2025-10-22 17:34:23 +08:00
defi-failure
296f71ed8a feat: position add button and new items at the top (#10881)
* feat: add sorting support to list agent api

* feat: move add button to top

* feat: display newly added assistant or agent on top
2025-10-22 17:20:17 +08:00
beyondkmp
f4d7c90126 chore: update electron to 38 and vite to 4.0.1 (#10884)
update electron to 38.4.0
2025-10-22 17:15:27 +08:00
beyondkmp
4063c20505 feat: support germen (#10879)
* feat: support germen

* format code

* translate

* update trans

* format

* add de

---------

Co-authored-by: Payne Fu <payne@Paynes-MBP.rcoffice.ringcentral.com>
2025-10-22 15:38:17 +08:00
chenxue
50798280db fix(aihubmix): fix model route rules (#10878)
Update aihubmix.ts
2025-10-22 11:08:38 +08:00
Phantom
39fa080263 fix: check model capability with model name (#10860)
* fix(ModelListItem): fallback to model name when logo not found by id

Use model name as fallback when fetching model logo if lookup by id fails

* refactor(model-logo): simplify model logo handling with unified function

Replace direct calls to getModelLogo with model.id with new getModelLogo function that handles both id and name fallback
Rename original getModelLogo to getModelLogoById for clarity
Update all components to use the new unified function

* refactor(model-utils): improve model type detection with fallback logic

Add helper function to check both model ID and name as ID for type detection
Refactor getThinkModelType and isSupportedThinkingTokenModel to use new fallback logic

* refactor(agent-popups): make avatar optional in BaseOption interface

update getModelLogo functions to return undefined instead of null for consistency

* refactor(models): remove outdated comment in reasoning.ts
2025-10-22 04:39:38 +08:00
dependabot[bot]
5c7b81569e build(deps-dev): bump playwright from 1.52.0 to 1.55.1 (#10850)
Bumps [playwright](https://github.com/microsoft/playwright) from 1.52.0 to 1.55.1.
- [Release notes](https://github.com/microsoft/playwright/releases)
- [Commits](https://github.com/microsoft/playwright/compare/v1.52.0...v1.55.1)

---
updated-dependencies:
- dependency-name: playwright
  dependency-version: 1.55.1
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-21 18:18:14 +01:00
Phantom
c021947d52 fix(home-tabs): correct tab switching logic for left position (#10858)
Ensure proper tab switching when position is left and topicPosition is right by including 'settings' tab in the condition
2025-10-22 01:15:25 +08:00
Phantom
f58d2e2e52 build: pin vite to specific version 7.1.5 (#10873)
* build: pin vite to specific version 7.1.5

Update package.json and yarn.lock to use exact version of vite instead of latest tag for better dependency stability

* build: pin vite version to 7.1.5

* build: pin rolldown-vite version to 7.1.5
2025-10-22 01:08:43 +08:00
kangfenmao
81ac77e988 chore: update LICENSE file to include full text of GNU AGPL-3.0
- Replaced previous licensing information with the complete text of the GNU Affero General Public License v3.0 (AGPL-3.0).
- Clarified terms regarding commercial use and compliance with AGPL-3.0.
- Added detailed definitions and conditions for users and organizations regarding licensing options.

This update ensures that users have clear access to the licensing terms governing the Cherry Studio Community Edition.
2025-10-21 22:56:27 +08:00
beyondkmp
a5049d8872 feat: enhance proxy bypass rules with comprehensive matching (#10817)
* feat: enhance proxy bypass rules with comprehensive matching

- Add support for wildcard domains (*.example.com, .example.com)
- Add CIDR notation support for IPv4 and IPv6 (192.168.0.0/16, 2001:db8::/32)
- Add wildcard IP matching (192.168.1.*)
- Add <local> keyword for local network hostnames
- Support both semicolon and comma separators in bypass rules
- Add comprehensive unit tests with 22 test cases
- Export matchWildcardDomain and matchIpRule for testability

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* move to devDeps

* delete logs

* feat: enhance ProxyManager with advanced proxy bypass rule handling

- Introduced comprehensive parsing and matching for proxy bypass rules, including support for wildcard domains, CIDR notation, and local network addresses.
- Refactored existing functions and added new utility methods for improved clarity and maintainability.
- Updated unit tests to cover new functionality and ensure robust validation of bypass rules.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

* update proxy rules

* fix lint

* add tips

* delete hostname rule

* add logs

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-10-21 10:39:16 +08:00
Zhaokun
bf35228b49 fix: capture detailed error response body for reranker API failures (#10839)
* fix: capture detailed error response body for reranker API failures

Previously, when reranker API returned 400 or other error status codes,
only the HTTP status and status text were captured, without reading the
actual error response body that contains detailed error information.

This commit fixes the issue by:
- Reading the error response body (as JSON or text) before throwing error
- Attaching the response details to the error object
- Including responseBody in formatErrorMessage output

This will help diagnose issues like "qwen3-reranker not available" by
showing the actual error message from the API provider.

* fix: enhance error handling in GeneralReranker for API failures

This update improves the error handling in the GeneralReranker class by ensuring that the response body is properly cloned and read when an API call fails. The detailed error information, including the status, status text, and body, is now attached to the error object. This change aids in diagnosing issues by providing more context in error messages.
2025-10-21 10:36:53 +08:00
Chen Tao
5df8a55f1e fix: support toolchoice for knowledge (#10763)
* fix: support toolchoice for knowledge

* fix: ci
2025-10-20 17:41:20 +08:00
Kejiang Ma
749a4f4679 feat: new painting provider: intel ovms (#10570)
* new painting provider: intel ovms

Signed-off-by: Ma, Kejiang <kj.ma@intel.com>
Signed-off-by: Kejiang Ma <kj.ma@intel.com>

* cherryin -> cherryai

Signed-off-by: Kejiang Ma <kj.ma@intel.com>

* ovms painting only valid when ovms is running

Signed-off-by: Kejiang Ma <kj.ma@intel.com>

* fix: painting(ovms) still appear while ovms is not running after rebase

Signed-off-by: Kejiang Ma <kj.ma@intel.com>

* fix warning in PaintingRoute

Signed-off-by: Kejiang Ma <kj.ma@intel.com>

* add ovms_paintings in migrate config 163

---------

Signed-off-by: Ma, Kejiang <kj.ma@intel.com>
Signed-off-by: Kejiang Ma <kj.ma@intel.com>
2025-10-20 15:41:34 +08:00
beyondkmp
528524b075 fix: Support right-click to paste file content into inputbar (#10730)
* feat: add right-click to paste text file content into input

Implemented context menu functionality for text file attachments that allows users to right-click on a text file attachment to paste its content directly into the input field.

Changes:
- Added onContextMenu prop to CustomTag component for handling right-click events
- Extended AttachmentPreview with onAttachmentContextMenu callback
- Implemented appendTxtContentToInput function to read and paste text file content
- Added clipboard support for copying file content
- Integrated context menu handler in Inputbar component

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* use real path

* 🐛 fix: clear txt attachment after paste

*  fix: improve attachment confirm flow

* update i18n

* 🎨 refactor: restyle confirm dialog

* format code

* refactor(ConfirmDialog): replace text buttons with icon buttons and remove i18n

- Replace text-based cancel/confirm buttons with icon buttons for better visual clarity
- Remove unused i18n translation hook as it's no longer needed
- Adjust styling to accommodate new button layout

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: icarus <eurfelux@gmail.com>
2025-10-20 13:16:17 +08:00
beyondkmp
4cca5210b9 chore: update @opeoginni/github-copilot-openai-compatible to version 0.1.19 and remove obsolete patch file (#10836)
* chore: update @opeoginni/github-copilot-openai-compatible to version 0.1.19 and remove obsolete patch file

- Updated the dependency version for @opeoginni/github-copilot-openai-compatible from 0.1.18 to 0.1.19 in package.json and yarn.lock.
- Removed the obsolete patch file for the previous version to clean up the project.

* recover
2025-10-20 10:51:53 +08:00
Pleasure1234
b26df0e614 fix: add continue-on-error & remove unused issue checker (#10821) 2025-10-20 00:06:34 +08:00
Phantom
8e482a97e5 style(AgentItem): improve agent item style (#10824)
refactor(AgentItem): simplify BotIcon component and adjust styling

- Replace absolute positioning with flex layout in BotIcon
- Add tooltip for better user experience
- Consolidate styling classes for better maintainability
2025-10-20 00:04:29 +08:00
Phantom
036f61bf12 fix: use consistent sharp dependencies (#10832)
build: update sharp dependencies to version 0.34.3

Update sharp image processing library dependencies to latest version 0.34.3 across all platforms (darwin, linux, win32) to ensure consistent behavior and security fixes
2025-10-20 00:01:39 +08:00
Phantom
06b1ae0cb8 feat(models): add doubao_after_251015 reasoning model type and support (#10826)
* feat(models): add doubao_after_251015 model type and support

Add new model type 'doubao_after_251015' with reasoning effort levels and update regex patterns to handle version 251015 and later

* fix(sdk): add warning for reasoning_effort field and update reasoning logic

Add warning comment about reasoning_effort field being overwritten for openai-compatible providers
Update reasoning effort logic to handle Doubao seed models after 251015 and standardize field naming

* fix(reasoning): update Doubao model regex patterns and tests

Update regex patterns for Doubao model validation to correctly handle version constraints
Add comprehensive test cases for model validation functions
2025-10-19 19:02:16 +08:00
1209 changed files with 55955 additions and 18120 deletions

4
.github/CODEOWNERS vendored
View File

@@ -1,4 +1,6 @@
/src/renderer/src/store/ @0xfullex
/src/renderer/src/databases/ @0xfullex
/src/main/services/ConfigManager.ts @0xfullex
/packages/shared/IpcChannel.ts @0xfullex
/src/main/ipc.ts @0xfullex
/src/main/ipc.ts @0xfullex
/app-upgrade-config.json @kangfenmao

View File

@@ -1,4 +1,4 @@
name: 🐛 Bug Report (English)
name: 🐛 Bug Report
description: Create a report to help us improve
title: '[Bug]: '
labels: ['BUG']

View File

@@ -1,4 +1,4 @@
name: 💡 Feature Request (English)
name: 💡 Feature Request
description: Suggest an idea for this project
title: '[Feature]: '
labels: ['feature']

View File

@@ -1,4 +1,4 @@
name: 🤔 Other Questions (English)
name: 🤔 Other Questions
description: Submit questions that don't fit into bug reports or feature requests
title: '[Other]: '
body:

View File

@@ -1,252 +0,0 @@
default-mode:
add:
remove: [pull_request_target, issues]
labels:
# <!-- [Ss]kip `LABEL` --> 跳过一个 label
# <!-- [Rr]emove `LABEL` --> 去掉一个 label
# skips and removes
- name: skip all
content:
regexes: '[Ss]kip (?:[Aa]ll |)[Ll]abels?'
- name: remove all
content:
regexes: '[Rr]emove (?:[Aa]ll |)[Ll]abels?'
- name: skip kind/bug
content:
regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)kind/bug(?:`|)'
- name: remove kind/bug
content:
regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)kind/bug(?:`|)'
- name: skip kind/enhancement
content:
regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)kind/enhancement(?:`|)'
- name: remove kind/enhancement
content:
regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)kind/enhancement(?:`|)'
- name: skip kind/question
content:
regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)kind/question(?:`|)'
- name: remove kind/question
content:
regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)kind/question(?:`|)'
- name: skip area/Connectivity
content:
regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)area/Connectivity(?:`|)'
- name: remove area/Connectivity
content:
regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)area/Connectivity(?:`|)'
- name: skip area/UI/UX
content:
regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)area/UI/UX(?:`|)'
- name: remove area/UI/UX
content:
regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)area/UI/UX(?:`|)'
- name: skip kind/documentation
content:
regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)kind/documentation(?:`|)'
- name: remove kind/documentation
content:
regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)kind/documentation(?:`|)'
- name: skip client:linux
content:
regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)client:linux(?:`|)'
- name: remove client:linux
content:
regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)client:linux(?:`|)'
- name: skip client:mac
content:
regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)client:mac(?:`|)'
- name: remove client:mac
content:
regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)client:mac(?:`|)'
- name: skip client:win
content:
regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)client:win(?:`|)'
- name: remove client:win
content:
regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)client:win(?:`|)'
- name: skip sig/Assistant
content:
regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)sig/Assistant(?:`|)'
- name: remove sig/Assistant
content:
regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)sig/Assistant(?:`|)'
- name: skip sig/Data
content:
regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)sig/Data(?:`|)'
- name: remove sig/Data
content:
regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)sig/Data(?:`|)'
- name: skip sig/MCP
content:
regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)sig/MCP(?:`|)'
- name: remove sig/MCP
content:
regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)sig/MCP(?:`|)'
- name: skip sig/RAG
content:
regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)sig/RAG(?:`|)'
- name: remove sig/RAG
content:
regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)sig/RAG(?:`|)'
- name: skip lgtm
content:
regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)lgtm(?:`|)'
- name: remove lgtm
content:
regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)lgtm(?:`|)'
- name: skip License
content:
regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)License(?:`|)'
- name: remove License
content:
regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)License(?:`|)'
# `Dev Team`
- name: Dev Team
mode:
add: [pull_request_target, issues]
author_association:
- COLLABORATOR
# Area labels
- name: area/Connectivity
content: area/Connectivity
regexes: '代理|[Pp]roxy'
skip-if:
- skip all
- skip area/Connectivity
remove-if:
- remove all
- remove area/Connectivity
- name: area/UI/UX
content: area/UI/UX
regexes: '界面|[Uu][Ii]|重叠|按钮|图标|组件|渲染|菜单|栏目|头像|主题|样式|[Cc][Ss][Ss]'
skip-if:
- skip all
- skip area/UI/UX
remove-if:
- remove all
- remove area/UI/UX
# Kind labels
- name: kind/documentation
content: kind/documentation
regexes: '文档|教程|[Dd]oc(s|umentation)|[Rr]eadme'
skip-if:
- skip all
- skip kind/documentation
remove-if:
- remove all
- remove kind/documentation
# Client labels
- name: client:linux
content: client:linux
regexes: '(?:[Ll]inux|[Uu]buntu|[Dd]ebian)'
skip-if:
- skip all
- skip client:linux
remove-if:
- remove all
- remove client:linux
- name: client:mac
content: client:mac
regexes: '(?:[Mm]ac|[Mm]acOS|[Oo]SX)'
skip-if:
- skip all
- skip client:mac
remove-if:
- remove all
- remove client:mac
- name: client:win
content: client:win
regexes: '(?:[Ww]in|[Ww]indows)'
skip-if:
- skip all
- skip client:win
remove-if:
- remove all
- remove client:win
# SIG labels
- name: sig/Assistant
content: sig/Assistant
regexes: '快捷助手|[Aa]ssistant'
skip-if:
- skip all
- skip sig/Assistant
remove-if:
- remove all
- remove sig/Assistant
- name: sig/Data
content: sig/Data
regexes: '[Ww]ebdav|坚果云|备份|同步|数据|Obsidian|Notion|Joplin|思源'
skip-if:
- skip all
- skip sig/Data
remove-if:
- remove all
- remove sig/Data
- name: sig/MCP
content: sig/MCP
regexes: '[Mm][Cc][Pp]'
skip-if:
- skip all
- skip sig/MCP
remove-if:
- remove all
- remove sig/MCP
- name: sig/RAG
content: sig/RAG
regexes: '知识库|[Rr][Aa][Gg]'
skip-if:
- skip all
- skip sig/RAG
remove-if:
- remove all
- remove sig/RAG
# Other labels
- name: lgtm
content: lgtm
regexes: '(?:[Ll][Gg][Tt][Mm]|[Ll]ooks [Gg]ood [Tt]o [Mm]e)'
skip-if:
- skip all
- skip lgtm
remove-if:
- remove all
- remove lgtm
- name: License
content: License
regexes: '(?:[Ll]icense|[Cc]opyright|[Mm][Ii][Tt]|[Aa]pache)'
skip-if:
- skip all
- skip License
remove-if:
- remove all
- remove License

View File

@@ -3,6 +3,18 @@
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
Before this PR:

View File

@@ -1,19 +1,21 @@
name: Auto I18N
name: Auto I18N Weekly
env:
API_KEY: ${{ secrets.TRANSLATE_API_KEY }}
MODEL: ${{ vars.AUTO_I18N_MODEL || 'deepseek/deepseek-v3.1'}}
BASE_URL: ${{ vars.AUTO_I18N_BASE_URL || 'https://api.ppinfra.com/openai'}}
TRANSLATION_API_KEY: ${{ secrets.TRANSLATE_API_KEY }}
TRANSLATION_MODEL: ${{ vars.AUTO_I18N_MODEL || 'deepseek/deepseek-v3.1'}}
TRANSLATION_BASE_URL: ${{ vars.AUTO_I18N_BASE_URL || 'https://api.ppinfra.com/openai'}}
TRANSLATION_BASE_LOCALE: ${{ vars.AUTO_I18N_BASE_LOCALE || 'en-us'}}
on:
pull_request:
types: [opened, synchronize, reopened]
schedule:
# Runs at 00:00 UTC every Sunday.
# This corresponds to 08:00 AM UTC+8 (Beijing time) every Sunday.
- cron: "0 0 * * 0"
workflow_dispatch:
jobs:
auto-i18n:
runs-on: ubuntu-latest
if: github.event.pull_request.head.repo.full_name == 'CherryHQ/cherry-studio'
name: Auto I18N
permissions:
contents: write
@@ -23,44 +25,69 @@ jobs:
- name: 🐈‍⬛ Checkout
uses: actions/checkout@v5
with:
ref: ${{ github.event.pull_request.head.ref }}
fetch-depth: 0
- name: 📦 Setting Node.js
uses: actions/setup-node@v5
uses: actions/setup-node@v6
with:
node-version: 20
node-version: 22
- name: 📦 Install dependencies in isolated directory
- name: 📦 Install corepack
run: corepack enable && corepack prepare yarn@4.9.1 --activate
- name: 📂 Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
- name: 💾 Cache yarn dependencies
uses: actions/cache@v4
with:
path: |
${{ steps.yarn-cache-dir-path.outputs.dir }}
node_modules
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: 📦 Install dependencies
run: |
# 在临时目录安装依赖
mkdir -p /tmp/translation-deps
cd /tmp/translation-deps
echo '{"dependencies": {"openai": "^5.12.2", "cli-progress": "^3.12.0", "tsx": "^4.20.3", "@biomejs/biome": "2.2.4"}}' > package.json
npm install --no-package-lock
# 设置 NODE_PATH 让项目能找到这些依赖
echo "NODE_PATH=/tmp/translation-deps/node_modules" >> $GITHUB_ENV
yarn install
- name: 🏃‍♀️ Translate
run: npx tsx scripts/auto-translate-i18n.ts
run: yarn sync:i18n && yarn auto:i18n
- name: 🔍 Format
run: cd /tmp/translation-deps && npx biome format --config-path /home/runner/work/cherry-studio/cherry-studio/biome.jsonc --write /home/runner/work/cherry-studio/cherry-studio/src/renderer/src/i18n/
run: yarn format
- name: 🔄 Commit changes
- name: 🔍 Check for changes
id: git_status
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
git add .
# Check if there are any uncommitted changes
git reset -- package.json yarn.lock # 不提交 package.json 和 yarn.lock 的更改
if git diff --cached --quiet; then
echo "No changes to commit"
else
git commit -m "fix(i18n): Auto update translations for PR #${{ github.event.pull_request.number }}"
fi
git diff --exit-code --quiet || echo "::set-output name=has_changes::true"
git status --porcelain
- name: 🚀 Push changes
uses: ad-m/github-push-action@master
- name: 📅 Set current date for PR title
id: set_date
run: echo "CURRENT_DATE=$(date +'%b %d, %Y')" >> $GITHUB_ENV # e.g., "Jun 06, 2024"
- name: 🚀 Create Pull Request if changes exist
if: steps.git_status.outputs.has_changes == 'true'
uses: peter-evans/create-pull-request@v6
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
branch: ${{ github.event.pull_request.head.ref }}
token: ${{ secrets.GITHUB_TOKEN }} # Use the built-in GITHUB_TOKEN for bot actions
commit-message: "feat(bot): Weekly automated script run"
title: "🤖 Weekly Auto I18N Sync: ${{ env.CURRENT_DATE }}"
body: |
This PR includes changes generated by the weekly auto i18n.
Review the changes before merging.
---
_Generated by the automated weekly workflow_
branch: "auto-i18n-weekly-${{ github.run_id }}" # Unique branch name
base: "main" # Or 'develop', set your base branch
delete-branch: true # Delete the branch after merging or closing the PR
- name: 📢 Notify if no changes
if: steps.git_status.outputs.has_changes != 'true'
run: echo "Bot script ran, but no changes were detected. No PR created."

View File

@@ -13,6 +13,7 @@ jobs:
steps:
- name: Delete merged branch
uses: actions/github-script@v8
continue-on-error: true
with:
script: |
github.rest.git.deleteRef({

View File

@@ -0,0 +1,187 @@
name: GitHub Issue Tracker with Feishu Notification
on:
issues:
types: [opened]
schedule:
# Run every day at 8:30 Beijing Time (00:30 UTC)
- cron: "30 0 * * *"
workflow_dispatch:
jobs:
process-new-issue:
if: github.event_name == 'issues'
runs-on: ubuntu-latest
permissions:
issues: write
contents: read
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Check Beijing Time
id: check_time
run: |
# Get current time in Beijing timezone (UTC+8)
BEIJING_HOUR=$(TZ='Asia/Shanghai' date +%H)
BEIJING_MINUTE=$(TZ='Asia/Shanghai' date +%M)
echo "Beijing Time: ${BEIJING_HOUR}:${BEIJING_MINUTE}"
# Check if time is between 00:00 and 08:30
if [ $BEIJING_HOUR -lt 8 ] || ([ $BEIJING_HOUR -eq 8 ] && [ $BEIJING_MINUTE -le 30 ]); then
echo "should_delay=true" >> $GITHUB_OUTPUT
echo "⏰ Issue created during quiet hours (00:00-08:30 Beijing Time)"
echo "Will schedule notification for 08:30"
else
echo "should_delay=false" >> $GITHUB_OUTPUT
echo "✅ Issue created during active hours, will notify immediately"
fi
- name: Add pending label if in quiet hours
if: steps.check_time.outputs.should_delay == 'true'
uses: actions/github-script@v7
with:
script: |
github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
labels: ['pending-feishu-notification']
});
- name: Setup Node.js
if: steps.check_time.outputs.should_delay == 'false'
uses: actions/setup-node@v6
with:
node-version: 22
- name: Process issue with Claude
if: steps.check_time.outputs.should_delay == 'false'
uses: anthropics/claude-code-action@main
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
allowed_non_write_users: "*"
anthropic_api_key: ${{ secrets.CLAUDE_TRANSLATOR_APIKEY }}
claude_args: "--allowed-tools Bash(gh issue:*),Bash(node scripts/feishu-notify.js)"
prompt: |
你是一个GitHub Issue自动化处理助手。请完成以下任务
## 当前Issue信息
- Issue编号#${{ github.event.issue.number }}
- 标题:${{ github.event.issue.title }}
- 作者:${{ github.event.issue.user.login }}
- URL${{ github.event.issue.html_url }}
- 内容:${{ github.event.issue.body }}
- 标签:${{ join(github.event.issue.labels.*.name, ', ') }}
## 任务步骤
1. **分析并总结issue**
用中文简体提供简洁的总结2-3句话包括
- 问题的主要内容
- 核心诉求
- 重要的技术细节
2. **发送飞书通知**
使用以下命令发送飞书通知注意ISSUE_SUMMARY需要用引号包裹
```bash
ISSUE_URL="${{ github.event.issue.html_url }}" \
ISSUE_NUMBER="${{ github.event.issue.number }}" \
ISSUE_TITLE="${{ github.event.issue.title }}" \
ISSUE_AUTHOR="${{ github.event.issue.user.login }}" \
ISSUE_LABELS="${{ join(github.event.issue.labels.*.name, ',') }}" \
ISSUE_SUMMARY="<你生成的中文总结>" \
node scripts/feishu-notify.js
```
## 注意事项
- 总结必须使用简体中文
- ISSUE_SUMMARY 在传递给 node 命令时需要正确转义特殊字符
- 如果issue内容为空也要提供一个简短的说明
请开始执行任务!
env:
ANTHROPIC_BASE_URL: ${{ secrets.CLAUDE_TRANSLATOR_BASEURL }}
FEISHU_WEBHOOK_URL: ${{ secrets.FEISHU_WEBHOOK_URL }}
FEISHU_WEBHOOK_SECRET: ${{ secrets.FEISHU_WEBHOOK_SECRET }}
process-pending-issues:
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
permissions:
issues: write
contents: read
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 22
- name: Process pending issues with Claude
uses: anthropics/claude-code-action@main
with:
anthropic_api_key: ${{ secrets.CLAUDE_TRANSLATOR_APIKEY }}
allowed_non_write_users: "*"
github_token: ${{ secrets.GITHUB_TOKEN }}
claude_args: "--allowed-tools Bash(gh issue:*),Bash(gh api:*),Bash(node scripts/feishu-notify.js)"
prompt: |
你是一个GitHub Issue自动化处理助手。请完成以下任务
## 任务说明
处理所有待发送飞书通知的GitHub Issues标记为 `pending-feishu-notification` 的issues
## 步骤
1. **获取待处理的issues**
使用以下命令获取所有带 `pending-feishu-notification` 标签的issues
```bash
gh api repos/${{ github.repository }}/issues?labels=pending-feishu-notification&state=open
```
2. **总结每个issue**
对于每个找到的issue用中文提供简洁的总结2-3句话包括
- 问题的主要内容
- 核心诉求
- 重要的技术细节
3. **发送飞书通知**
对于每个issue使用以下命令发送飞书通知
```bash
ISSUE_URL="<issue的html_url>" \
ISSUE_NUMBER="<issue编号>" \
ISSUE_TITLE="<issue标题>" \
ISSUE_AUTHOR="<issue作者>" \
ISSUE_LABELS="<逗号分隔的标签列表排除pending-feishu-notification>" \
ISSUE_SUMMARY="<你生成的中文总结>" \
node scripts/feishu-notify.js
```
4. **移除标签**
成功发送后,使用以下命令移除 `pending-feishu-notification` 标签:
```bash
gh api -X DELETE repos/${{ github.repository }}/issues/<issue编号>/labels/pending-feishu-notification
```
## 环境变量
- Repository: ${{ github.repository }}
- Feishu webhook URL和密钥已在环境变量中配置好
## 注意事项
- 如果没有待处理的issues输出提示信息后直接结束
- 处理多个issues时每个issue之间等待2-3秒避免API限流
- 如果某个issue处理失败继续处理下一个不要中断整个流程
- 所有总结必须使用中文(简体中文)
请开始执行任务!
env:
ANTHROPIC_BASE_URL: ${{ secrets.CLAUDE_TRANSLATOR_BASEURL }}
FEISHU_WEBHOOK_URL: ${{ secrets.FEISHU_WEBHOOK_URL }}
FEISHU_WEBHOOK_SECRET: ${{ secrets.FEISHU_WEBHOOK_SECRET }}

View File

@@ -1,25 +0,0 @@
name: 'Issue Checker'
on:
issues:
types: [opened, edited]
pull_request_target:
types: [opened, edited]
issue_comment:
types: [created, edited]
permissions:
contents: read
issues: write
pull-requests: write
jobs:
triage:
runs-on: ubuntu-latest
steps:
- uses: MaaAssistantArknights/issue-checker@v1.14
with:
repo-token: '${{ secrets.GITHUB_TOKEN }}'
configuration-path: .github/issue-checker.yml
not-before: 2022-08-05T00:00:00Z
include-title: 1

View File

@@ -21,7 +21,7 @@ jobs:
contents: none
steps:
- name: Close needs-more-info issues
uses: actions/stale@v9
uses: actions/stale@v10
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
only-labels: 'needs-more-info'
@@ -29,8 +29,10 @@ jobs:
days-before-close: 0 # Close immediately after stale
stale-issue-label: 'inactive'
close-issue-label: 'closed:no-response'
exempt-all-milestones: true
exempt-all-assignees: true
stale-issue-message: |
This issue has been labeled as needing more information and has been inactive for ${{ env.daysBeforeStale }} days.
This issue has been labeled as needing more information and has been inactive for ${{ env.daysBeforeStale }} days.
It will be closed now due to lack of additional information.
该问题被标记为"需要更多信息"且已经 ${{ env.daysBeforeStale }} 天没有任何活动,将立即关闭。
@@ -40,12 +42,14 @@ jobs:
days-before-pr-close: -1
- name: Close inactive issues
uses: actions/stale@v9
uses: actions/stale@v10
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: ${{ env.daysBeforeStale }}
days-before-close: ${{ env.daysBeforeClose }}
stale-issue-label: 'inactive'
exempt-all-milestones: true
exempt-all-assignees: true
stale-issue-message: |
This issue has been inactive for a prolonged period and will be closed automatically in ${{ env.daysBeforeClose }} days.
该问题已长时间处于闲置状态,${{ env.daysBeforeClose }} 天后将自动关闭。

View File

@@ -3,7 +3,7 @@ name: Nightly Build
on:
workflow_dispatch:
schedule:
- cron: '0 17 * * *' # 1:00 BJ Time
- cron: "0 17 * * *" # 1:00 BJ Time
permissions:
contents: write
@@ -56,9 +56,9 @@ jobs:
ref: main
- name: Install Node.js
uses: actions/setup-node@v5
uses: actions/setup-node@v6
with:
node-version: 20
node-version: 22
- name: macos-latest dependencies fix
if: matrix.os == 'macos-latest'
@@ -66,7 +66,7 @@ jobs:
brew install python-setuptools
- name: Install corepack
run: corepack enable && corepack prepare yarn@4.6.0 --activate
run: corepack enable && corepack prepare yarn@4.9.1 --activate
- name: Get yarn cache directory path
id: yarn-cache-dir-path
@@ -208,7 +208,7 @@ jobs:
echo "总计: $(find renamed-artifacts -type f | wc -l) 个文件"
- name: Upload artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: cherry-studio-nightly-${{ steps.date.outputs.date }}-${{ matrix.os }}
path: renamed-artifacts/*

View File

@@ -24,12 +24,12 @@ jobs:
uses: actions/checkout@v5
- name: Install Node.js
uses: actions/setup-node@v5
uses: actions/setup-node@v6
with:
node-version: 20
node-version: 22
- name: Install corepack
run: corepack enable && corepack prepare yarn@4.6.0 --activate
run: corepack enable && corepack prepare yarn@4.9.1 --activate
- name: Get yarn cache directory path
id: yarn-cache-dir-path

View File

@@ -4,9 +4,9 @@ on:
workflow_dispatch:
inputs:
tag:
description: 'Release tag (e.g. v1.0.0)'
description: "Release tag (e.g. v1.0.0)"
required: true
default: 'v1.0.0'
default: "v1.0.0"
push:
tags:
- v*.*.*
@@ -47,9 +47,9 @@ jobs:
npm version "$VERSION" --no-git-tag-version --allow-same-version
- name: Install Node.js
uses: actions/setup-node@v5
uses: actions/setup-node@v6
with:
node-version: 20
node-version: 22
- name: macos-latest dependencies fix
if: matrix.os == 'macos-latest'
@@ -57,7 +57,7 @@ jobs:
brew install python-setuptools
- name: Install corepack
run: corepack enable && corepack prepare yarn@4.6.0 --activate
run: corepack enable && corepack prepare yarn@4.9.1 --activate
- name: Get yarn cache directory path
id: yarn-cache-dir-path
@@ -127,5 +127,5 @@ jobs:
allowUpdates: true
makeLatest: false
tag: ${{ steps.get-tag.outputs.tag }}
artifacts: 'dist/*.exe,dist/*.zip,dist/*.dmg,dist/*.AppImage,dist/*.snap,dist/*.deb,dist/*.rpm,dist/*.tar.gz,dist/latest*.yml,dist/rc*.yml,dist/beta*.yml,dist/*.blockmap'
artifacts: "dist/*.exe,dist/*.zip,dist/*.dmg,dist/*.AppImage,dist/*.snap,dist/*.deb,dist/*.rpm,dist/*.tar.gz,dist/latest*.yml,dist/rc*.yml,dist/beta*.yml,dist/*.blockmap"
token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -0,0 +1,212 @@
name: Update App Upgrade Config
on:
release:
types:
- released
- prereleased
workflow_dispatch:
inputs:
tag:
description: "Release tag (e.g., v1.2.3)"
required: true
type: string
is_prerelease:
description: "Mark the tag as a prerelease when running manually"
required: false
default: false
type: boolean
permissions:
contents: write
pull-requests: write
jobs:
propose-update:
runs-on: ubuntu-latest
if: github.event_name == 'workflow_dispatch' || (github.event_name == 'release' && github.event.release.draft == false)
steps:
- name: Check if should proceed
id: check
run: |
EVENT="${{ github.event_name }}"
if [ "$EVENT" = "workflow_dispatch" ]; then
TAG="${{ github.event.inputs.tag }}"
else
TAG="${{ github.event.release.tag_name }}"
fi
latest_tag=$(
curl -L \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${{ github.token }}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/repos/${{ github.repository }}/releases/latest \
| jq -r '.tag_name'
)
if [ "$EVENT" = "workflow_dispatch" ]; then
MANUAL_IS_PRERELEASE="${{ github.event.inputs.is_prerelease }}"
if [ -z "$MANUAL_IS_PRERELEASE" ]; then
MANUAL_IS_PRERELEASE="false"
fi
if [ "$MANUAL_IS_PRERELEASE" = "true" ]; then
if ! echo "$TAG" | grep -E '(-beta([.-][0-9]+)?|-rc([.-][0-9]+)?)' >/dev/null; then
echo "Manual prerelease flag set but tag $TAG lacks beta/rc suffix. Skipping." >&2
echo "should_run=false" >> "$GITHUB_OUTPUT"
echo "is_prerelease=false" >> "$GITHUB_OUTPUT"
echo "latest_tag=$latest_tag" >> "$GITHUB_OUTPUT"
exit 0
fi
fi
echo "should_run=true" >> "$GITHUB_OUTPUT"
echo "is_prerelease=$MANUAL_IS_PRERELEASE" >> "$GITHUB_OUTPUT"
echo "latest_tag=$latest_tag" >> "$GITHUB_OUTPUT"
exit 0
fi
IS_PRERELEASE="${{ github.event.release.prerelease }}"
if [ "$IS_PRERELEASE" = "true" ]; then
if ! echo "$TAG" | grep -E '(-beta([.-][0-9]+)?|-rc([.-][0-9]+)?)' >/dev/null; then
echo "Release marked as prerelease but tag $TAG lacks beta/rc suffix. Skipping." >&2
echo "should_run=false" >> "$GITHUB_OUTPUT"
echo "is_prerelease=false" >> "$GITHUB_OUTPUT"
echo "latest_tag=$latest_tag" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "should_run=true" >> "$GITHUB_OUTPUT"
echo "is_prerelease=true" >> "$GITHUB_OUTPUT"
echo "latest_tag=$latest_tag" >> "$GITHUB_OUTPUT"
echo "Release is prerelease, proceeding"
exit 0
fi
if [[ "${latest_tag}" == "$TAG" ]]; then
echo "should_run=true" >> "$GITHUB_OUTPUT"
echo "is_prerelease=false" >> "$GITHUB_OUTPUT"
echo "latest_tag=$latest_tag" >> "$GITHUB_OUTPUT"
echo "Release is latest, proceeding"
else
echo "should_run=false" >> "$GITHUB_OUTPUT"
echo "is_prerelease=false" >> "$GITHUB_OUTPUT"
echo "latest_tag=$latest_tag" >> "$GITHUB_OUTPUT"
echo "Release is neither prerelease nor latest, skipping"
fi
- name: Prepare metadata
id: meta
if: steps.check.outputs.should_run == 'true'
run: |
EVENT="${{ github.event_name }}"
LATEST_TAG="${{ steps.check.outputs.latest_tag }}"
if [ "$EVENT" = "release" ]; then
TAG="${{ github.event.release.tag_name }}"
PRE="${{ github.event.release.prerelease }}"
if [ -n "$LATEST_TAG" ] && [ "$LATEST_TAG" = "$TAG" ]; then
LATEST="true"
else
LATEST="false"
fi
TRIGGER="release"
else
TAG="${{ github.event.inputs.tag }}"
PRE="${{ github.event.inputs.is_prerelease }}"
if [ -z "$PRE" ]; then
PRE="false"
fi
if [ -n "$LATEST_TAG" ] && [ "$LATEST_TAG" = "$TAG" ] && [ "$PRE" != "true" ]; then
LATEST="true"
else
LATEST="false"
fi
TRIGGER="manual"
fi
SAFE_TAG=$(echo "$TAG" | sed 's/[^A-Za-z0-9._-]/-/g')
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "safe_tag=$SAFE_TAG" >> "$GITHUB_OUTPUT"
echo "prerelease=$PRE" >> "$GITHUB_OUTPUT"
echo "latest=$LATEST" >> "$GITHUB_OUTPUT"
echo "trigger=$TRIGGER" >> "$GITHUB_OUTPUT"
- name: Checkout default branch
if: steps.check.outputs.should_run == 'true'
uses: actions/checkout@v5
with:
ref: ${{ github.event.repository.default_branch }}
path: main
fetch-depth: 0
- name: Checkout x-files/app-upgrade-config branch
if: steps.check.outputs.should_run == 'true'
uses: actions/checkout@v5
with:
ref: x-files/app-upgrade-config
path: cs
fetch-depth: 0
- name: Setup Node.js
if: steps.check.outputs.should_run == 'true'
uses: actions/setup-node@v4
with:
node-version: 22
- name: Enable Corepack
if: steps.check.outputs.should_run == 'true'
run: corepack enable && corepack prepare yarn@4.9.1 --activate
- name: Install dependencies
if: steps.check.outputs.should_run == 'true'
working-directory: main
run: yarn install --immutable
- name: Update upgrade config
if: steps.check.outputs.should_run == 'true'
working-directory: main
env:
RELEASE_TAG: ${{ steps.meta.outputs.tag }}
IS_PRERELEASE: ${{ steps.check.outputs.is_prerelease }}
run: |
yarn tsx scripts/update-app-upgrade-config.ts \
--tag "$RELEASE_TAG" \
--config ../cs/app-upgrade-config.json \
--is-prerelease "$IS_PRERELEASE"
- name: Detect changes
if: steps.check.outputs.should_run == 'true'
id: diff
working-directory: cs
run: |
if git diff --quiet -- app-upgrade-config.json; then
echo "changed=false" >> "$GITHUB_OUTPUT"
else
echo "changed=true" >> "$GITHUB_OUTPUT"
fi
- name: Create pull request
if: steps.check.outputs.should_run == 'true' && steps.diff.outputs.changed == 'true'
uses: peter-evans/create-pull-request@v7
with:
path: cs
base: x-files/app-upgrade-config
branch: chore/update-app-upgrade-config/${{ steps.meta.outputs.safe_tag }}
commit-message: "🤖 chore: sync app-upgrade-config for ${{ steps.meta.outputs.tag }}"
title: "chore: update app-upgrade-config for ${{ steps.meta.outputs.tag }}"
body: |
Automated update triggered by `${{ steps.meta.outputs.trigger }}`.
- Source tag: `${{ steps.meta.outputs.tag }}`
- Pre-release: `${{ steps.meta.outputs.prerelease }}`
- Latest: `${{ steps.meta.outputs.latest }}`
- Workflow run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
labels: |
automation
app-upgrade
- name: No changes detected
if: steps.check.outputs.should_run == 'true' && steps.diff.outputs.changed != 'true'
run: echo "No updates required for x-files/app-upgrade-config/app-upgrade-config.json"

View File

@@ -11,6 +11,7 @@
"dist/**",
"out/**",
"local/**",
"tests/**",
".yarn/**",
".gitignore",
"scripts/cloudflare-worker.js",
@@ -22,7 +23,6 @@
"eslint.config.mjs"
],
"overrides": [
// set different env
{
"env": {
"node": true
@@ -36,8 +36,7 @@
"files": [
"src/renderer/**/*.{ts,tsx}",
"packages/aiCore/**",
"packages/extension-table-plus/**",
"resources/js/**"
"packages/extension-table-plus/**"
]
},
{
@@ -53,76 +52,24 @@
"node": true
},
"files": ["src/preload/**"]
},
{
"files": ["packages/ai-sdk-provider/**"],
"globals": {
"fetch": "readonly"
}
}
],
// We don't use the React plugin here because its behavior differs slightly from that of ESLint's React plugin.
"plugins": ["unicorn", "typescript", "oxc", "import"],
"rules": {
"constructor-super": "error",
"for-direction": "error",
"getter-return": "error",
"no-array-constructor": "off",
// "import/no-cycle": "error", // tons of error, bro
"no-async-promise-executor": "error",
"no-caller": "warn",
"no-case-declarations": "error",
"no-class-assign": "error",
"no-compare-neg-zero": "error",
"no-cond-assign": "error",
"no-const-assign": "error",
"no-constant-binary-expression": "error",
"no-constant-condition": "error",
"no-control-regex": "error",
"no-debugger": "error",
"no-delete-var": "error",
"no-dupe-args": "error",
"no-dupe-class-members": "error",
"no-dupe-else-if": "error",
"no-dupe-keys": "error",
"no-duplicate-case": "error",
"no-empty": "error",
"no-empty-character-class": "error",
"no-empty-pattern": "error",
"no-empty-static-block": "error",
"no-eval": "warn",
"no-ex-assign": "error",
"no-extra-boolean-cast": "error",
"no-fallthrough": "warn",
"no-func-assign": "error",
"no-global-assign": "error",
"no-import-assign": "error",
"no-invalid-regexp": "error",
"no-irregular-whitespace": "error",
"no-loss-of-precision": "error",
"no-misleading-character-class": "error",
"no-new-native-nonconstructor": "error",
"no-nonoctal-decimal-escape": "error",
"no-obj-calls": "error",
"no-octal": "error",
"no-prototype-builtins": "error",
"no-redeclare": "error",
"no-regex-spaces": "error",
"no-self-assign": "error",
"no-setter-return": "error",
"no-shadow-restricted-names": "error",
"no-sparse-arrays": "error",
"no-this-before-super": "error",
"no-unassigned-vars": "warn",
"no-undef": "error",
"no-unexpected-multiline": "error",
"no-unreachable": "error",
"no-unsafe-finally": "error",
"no-unsafe-negation": "error",
"no-unsafe-optional-chaining": "error",
"no-unused-expressions": "off", // this rule disallow us to use expression to call function, like `condition && fn()`
"no-unused-labels": "error",
"no-unused-private-class-members": "error",
"no-unused-expressions": "off",
"no-unused-vars": ["warn", { "caughtErrors": "none" }],
"no-useless-backreference": "error",
"no-useless-catch": "error",
"no-useless-escape": "error",
"no-useless-rename": "warn",
"no-with": "error",
"oxc/bad-array-method-on-arguments": "warn",
"oxc/bad-char-at-comparison": "warn",
"oxc/bad-comparison-sequence": "warn",
@@ -134,19 +81,17 @@
"oxc/erasing-op": "warn",
"oxc/missing-throw": "warn",
"oxc/number-arg-out-of-range": "warn",
"oxc/only-used-in-recursion": "off", // manually off bacause of existing warning. may turn it on in the future
"oxc/only-used-in-recursion": "off",
"oxc/uninvoked-array-callback": "warn",
"require-yield": "error",
"typescript/await-thenable": "warn",
// "typescript/ban-ts-comment": "error",
"typescript/consistent-type-imports": "error",
"typescript/no-array-constructor": "error",
// "typescript/consistent-type-imports": "error",
"typescript/no-array-delete": "warn",
"typescript/no-base-to-string": "warn",
"typescript/no-duplicate-enum-values": "error",
"typescript/no-duplicate-type-constituents": "warn",
"typescript/no-empty-object-type": "off",
"typescript/no-explicit-any": "off", // not safe but too many errors
"typescript/no-explicit-any": "off",
"typescript/no-extra-non-null-assertion": "error",
"typescript/no-floating-promises": "warn",
"typescript/no-for-in-array": "warn",
@@ -155,7 +100,7 @@
"typescript/no-misused-new": "error",
"typescript/no-misused-spread": "warn",
"typescript/no-namespace": "error",
"typescript/no-non-null-asserted-optional-chain": "off", // it's off now. but may turn it on.
"typescript/no-non-null-asserted-optional-chain": "off",
"typescript/no-redundant-type-constituents": "warn",
"typescript/no-require-imports": "off",
"typescript/no-this-alias": "error",
@@ -173,20 +118,18 @@
"typescript/triple-slash-reference": "error",
"typescript/unbound-method": "warn",
"unicorn/no-await-in-promise-methods": "warn",
"unicorn/no-empty-file": "off", // manually off bacause of existing warning. may turn it on in the future
"unicorn/no-empty-file": "off",
"unicorn/no-invalid-fetch-options": "warn",
"unicorn/no-invalid-remove-event-listener": "warn",
"unicorn/no-new-array": "off", // manually off bacause of existing warning. may turn it on in the future
"unicorn/no-new-array": "off",
"unicorn/no-single-promise-in-promise-methods": "warn",
"unicorn/no-thenable": "off", // manually off bacause of existing warning. may turn it on in the future
"unicorn/no-thenable": "off",
"unicorn/no-unnecessary-await": "warn",
"unicorn/no-useless-fallback-in-spread": "warn",
"unicorn/no-useless-length-check": "warn",
"unicorn/no-useless-spread": "off", // manually off bacause of existing warning. may turn it on in the future
"unicorn/no-useless-spread": "off",
"unicorn/prefer-set-size": "warn",
"unicorn/prefer-string-starts-ends-with": "warn",
"use-isnan": "error",
"valid-typeof": "error"
"unicorn/prefer-string-starts-ends-with": "warn"
},
"settings": {
"jsdoc": {

View File

@@ -1,13 +0,0 @@
diff --git a/dist/index.mjs b/dist/index.mjs
index 69ab1599c76801dc1167551b6fa283dded123466..f0af43bba7ad1196fe05338817e65b4ebda40955 100644
--- a/dist/index.mjs
+++ b/dist/index.mjs
@@ -477,7 +477,7 @@ function convertToGoogleGenerativeAIMessages(prompt, options) {
// src/get-model-path.ts
function getModelPath(modelId) {
- return modelId.includes("/") ? modelId : `models/${modelId}`;
+ return modelId?.includes("models/") ? modelId : `models/${modelId}`;
}
// src/google-generative-ai-options.ts

View File

@@ -0,0 +1,26 @@
diff --git a/dist/index.js b/dist/index.js
index 51ce7e423934fb717cb90245cdfcdb3dae6780e6..0f7f7009e2f41a79a8669d38c8a44867bbff5e1f 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -474,7 +474,7 @@ function convertToGoogleGenerativeAIMessages(prompt, options) {
// src/get-model-path.ts
function getModelPath(modelId) {
- return modelId.includes("/") ? modelId : `models/${modelId}`;
+ return modelId.includes("models/") ? modelId : `models/${modelId}`;
}
// src/google-generative-ai-options.ts
diff --git a/dist/index.mjs b/dist/index.mjs
index f4b77e35c0cbfece85a3ef0d4f4e67aa6dde6271..8d2fecf8155a226006a0bde72b00b6036d4014b6 100644
--- a/dist/index.mjs
+++ b/dist/index.mjs
@@ -480,7 +480,7 @@ function convertToGoogleGenerativeAIMessages(prompt, options) {
// src/get-model-path.ts
function getModelPath(modelId) {
- return modelId.includes("/") ? modelId : `models/${modelId}`;
+ return modelId.includes("models/") ? modelId : `models/${modelId}`;
}
// src/google-generative-ai-options.ts

View File

@@ -0,0 +1,140 @@
diff --git a/dist/index.js b/dist/index.js
index 73045a7d38faafdc7f7d2cd79d7ff0e2b031056b..8d948c9ac4ea4b474db9ef3c5491961e7fcf9a07 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -421,6 +421,17 @@ var OpenAICompatibleChatLanguageModel = class {
text: reasoning
});
}
+ if (choice.message.images) {
+ for (const image of choice.message.images) {
+ const match1 = image.image_url.url.match(/^data:([^;]+)/)
+ const match2 = image.image_url.url.match(/^data:[^;]*;base64,(.+)$/);
+ content.push({
+ type: 'file',
+ mediaType: match1 ? (match1[1] ?? 'image/jpeg') : 'image/jpeg',
+ data: match2 ? match2[1] : image.image_url.url,
+ });
+ }
+ }
if (choice.message.tool_calls != null) {
for (const toolCall of choice.message.tool_calls) {
content.push({
@@ -598,6 +609,17 @@ var OpenAICompatibleChatLanguageModel = class {
delta: delta.content
});
}
+ if (delta.images) {
+ for (const image of delta.images) {
+ const match1 = image.image_url.url.match(/^data:([^;]+)/)
+ const match2 = image.image_url.url.match(/^data:[^;]*;base64,(.+)$/);
+ controller.enqueue({
+ type: 'file',
+ mediaType: match1 ? (match1[1] ?? 'image/jpeg') : 'image/jpeg',
+ data: match2 ? match2[1] : image.image_url.url,
+ });
+ }
+ }
if (delta.tool_calls != null) {
for (const toolCallDelta of delta.tool_calls) {
const index = toolCallDelta.index;
@@ -765,6 +787,14 @@ var OpenAICompatibleChatResponseSchema = import_v43.z.object({
arguments: import_v43.z.string()
})
})
+ ).nullish(),
+ images: import_v43.z.array(
+ import_v43.z.object({
+ type: import_v43.z.literal('image_url'),
+ image_url: import_v43.z.object({
+ url: import_v43.z.string(),
+ })
+ })
).nullish()
}),
finish_reason: import_v43.z.string().nullish()
@@ -795,6 +825,14 @@ var createOpenAICompatibleChatChunkSchema = (errorSchema) => import_v43.z.union(
arguments: import_v43.z.string().nullish()
})
})
+ ).nullish(),
+ images: import_v43.z.array(
+ import_v43.z.object({
+ type: import_v43.z.literal('image_url'),
+ image_url: import_v43.z.object({
+ url: import_v43.z.string(),
+ })
+ })
).nullish()
}).nullish(),
finish_reason: import_v43.z.string().nullish()
diff --git a/dist/index.mjs b/dist/index.mjs
index 1c2b9560bbfbfe10cb01af080aeeed4ff59db29c..2c8ddc4fc9bfc5e7e06cfca105d197a08864c427 100644
--- a/dist/index.mjs
+++ b/dist/index.mjs
@@ -405,6 +405,17 @@ var OpenAICompatibleChatLanguageModel = class {
text: reasoning
});
}
+ if (choice.message.images) {
+ for (const image of choice.message.images) {
+ const match1 = image.image_url.url.match(/^data:([^;]+)/)
+ const match2 = image.image_url.url.match(/^data:[^;]*;base64,(.+)$/);
+ content.push({
+ type: 'file',
+ mediaType: match1 ? (match1[1] ?? 'image/jpeg') : 'image/jpeg',
+ data: match2 ? match2[1] : image.image_url.url,
+ });
+ }
+ }
if (choice.message.tool_calls != null) {
for (const toolCall of choice.message.tool_calls) {
content.push({
@@ -582,6 +593,17 @@ var OpenAICompatibleChatLanguageModel = class {
delta: delta.content
});
}
+ if (delta.images) {
+ for (const image of delta.images) {
+ const match1 = image.image_url.url.match(/^data:([^;]+)/)
+ const match2 = image.image_url.url.match(/^data:[^;]*;base64,(.+)$/);
+ controller.enqueue({
+ type: 'file',
+ mediaType: match1 ? (match1[1] ?? 'image/jpeg') : 'image/jpeg',
+ data: match2 ? match2[1] : image.image_url.url,
+ });
+ }
+ }
if (delta.tool_calls != null) {
for (const toolCallDelta of delta.tool_calls) {
const index = toolCallDelta.index;
@@ -749,6 +771,14 @@ var OpenAICompatibleChatResponseSchema = z3.object({
arguments: z3.string()
})
})
+ ).nullish(),
+ images: z3.array(
+ z3.object({
+ type: z3.literal('image_url'),
+ image_url: z3.object({
+ url: z3.string(),
+ })
+ })
).nullish()
}),
finish_reason: z3.string().nullish()
@@ -779,6 +809,14 @@ var createOpenAICompatibleChatChunkSchema = (errorSchema) => z3.union([
arguments: z3.string().nullish()
})
})
+ ).nullish(),
+ images: z3.array(
+ z3.object({
+ type: z3.literal('image_url'),
+ image_url: z3.object({
+ url: z3.string(),
+ })
+ })
).nullish()
}).nullish(),
finish_reason: z3.string().nullish()

View File

@@ -0,0 +1,74 @@
diff --git a/dist/index.js b/dist/index.js
index bf900591bf2847a3253fe441aad24c06da19c6c1..c1d9bb6fefa2df1383339324073db0a70ea2b5a2 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -274,6 +274,7 @@ var openaiChatResponseSchema = (0, import_provider_utils3.lazyValidator)(
message: import_v42.z.object({
role: import_v42.z.literal("assistant").nullish(),
content: import_v42.z.string().nullish(),
+ reasoning_content: import_v42.z.string().nullish(),
tool_calls: import_v42.z.array(
import_v42.z.object({
id: import_v42.z.string().nullish(),
@@ -340,6 +341,7 @@ var openaiChatChunkSchema = (0, import_provider_utils3.lazyValidator)(
delta: import_v42.z.object({
role: import_v42.z.enum(["assistant"]).nullish(),
content: import_v42.z.string().nullish(),
+ reasoning_content: import_v42.z.string().nullish(),
tool_calls: import_v42.z.array(
import_v42.z.object({
index: import_v42.z.number(),
@@ -795,6 +797,13 @@ var OpenAIChatLanguageModel = class {
if (text != null && text.length > 0) {
content.push({ type: "text", text });
}
+ const reasoning = choice.message.reasoning_content;
+ if (reasoning != null && reasoning.length > 0) {
+ content.push({
+ type: 'reasoning',
+ text: reasoning
+ });
+ }
for (const toolCall of (_a = choice.message.tool_calls) != null ? _a : []) {
content.push({
type: "tool-call",
@@ -876,6 +885,7 @@ var OpenAIChatLanguageModel = class {
};
let metadataExtracted = false;
let isActiveText = false;
+ let isActiveReasoning = false;
const providerMetadata = { openai: {} };
return {
stream: response.pipeThrough(
@@ -933,6 +943,21 @@ var OpenAIChatLanguageModel = class {
return;
}
const delta = choice.delta;
+ const reasoningContent = delta.reasoning_content;
+ if (reasoningContent) {
+ if (!isActiveReasoning) {
+ controller.enqueue({
+ type: 'reasoning-start',
+ id: 'reasoning-0',
+ });
+ isActiveReasoning = true;
+ }
+ controller.enqueue({
+ type: 'reasoning-delta',
+ id: 'reasoning-0',
+ delta: reasoningContent,
+ });
+ }
if (delta.content != null) {
if (!isActiveText) {
controller.enqueue({ type: "text-start", id: "0" });
@@ -1045,6 +1070,9 @@ var OpenAIChatLanguageModel = class {
}
},
flush(controller) {
+ if (isActiveReasoning) {
+ controller.enqueue({ type: 'reasoning-end', id: 'reasoning-0' });
+ }
if (isActiveText) {
controller.enqueue({ type: "text-end", id: "0" });
}

View File

@@ -1,26 +1,30 @@
diff --git a/sdk.mjs b/sdk.mjs
index 461e9a2ba246778261108a682762ffcf26f7224e..44bd667d9f591969d36a105ba5eb8b478c738dd8 100644
index bf429a344b7d59f70aead16b639f949b07688a81..f77d50cc5d3fb04292cb3ac7fa7085d02dcc628f 100755
--- a/sdk.mjs
+++ b/sdk.mjs
@@ -6215,7 +6215,7 @@ function createAbortController(maxListeners = DEFAULT_MAX_LISTENERS) {
@@ -6250,7 +6250,7 @@ function createAbortController(maxListeners = DEFAULT_MAX_LISTENERS) {
}
// ../src/transport/ProcessTransport.ts
-import { spawn } from "child_process";
+import { fork } from "child_process";
import { createInterface } from "readline";
// ../src/utils/fsOperations.ts
@@ -6473,14 +6473,11 @@ class ProcessTransport {
@@ -6619,18 +6619,11 @@ class ProcessTransport {
const errorMessage = isNativeBinary(pathToClaudeCodeExecutable) ? `Claude Code native binary not found at ${pathToClaudeCodeExecutable}. Please ensure Claude Code is installed via native installer or specify a valid path with options.pathToClaudeCodeExecutable.` : `Claude Code executable not found at ${pathToClaudeCodeExecutable}. Is options.pathToClaudeCodeExecutable set?`;
throw new ReferenceError(errorMessage);
}
- const isNative = isNativeBinary(pathToClaudeCodeExecutable);
- const spawnCommand = isNative ? pathToClaudeCodeExecutable : executable;
- const spawnArgs = isNative ? args : [...executableArgs, pathToClaudeCodeExecutable, ...args];
- this.logForDebugging(isNative ? `Spawning Claude Code native binary: ${pathToClaudeCodeExecutable} ${args.join(" ")}` : `Spawning Claude Code process: ${executable} ${[...executableArgs, pathToClaudeCodeExecutable, ...args].join(" ")}`);
+ this.logForDebugging(`Forking Claude Code Node.js process: ${pathToClaudeCodeExecutable} ${args.join(" ")}`);
const stderrMode = env.DEBUG || stderr ? "pipe" : "ignore";
- const spawnArgs = isNative ? [...executableArgs, ...args] : [...executableArgs, pathToClaudeCodeExecutable, ...args];
- const spawnMessage = isNative ? `Spawning Claude Code native binary: ${spawnCommand} ${spawnArgs.join(" ")}` : `Spawning Claude Code process: ${spawnCommand} ${spawnArgs.join(" ")}`;
- logForSdkDebugging(spawnMessage);
- if (stderr) {
- stderr(spawnMessage);
- }
+ logForSdkDebugging(`Forking Claude Code Node.js process: ${pathToClaudeCodeExecutable} ${args.join(" ")}`);
const stderrMode = env.DEBUG_CLAUDE_AGENT_SDK || stderr ? "pipe" : "ignore";
- this.child = spawn(spawnCommand, spawnArgs, {
+ this.child = fork(pathToClaudeCodeExecutable, args, {
cwd,

View File

@@ -1,71 +0,0 @@
diff --git a/dist/utils/tiktoken.cjs b/dist/utils/tiktoken.cjs
index 973b0d0e75aeaf8de579419af31b879b32975413..f23c7caa8b9dc8bd404132725346a4786f6b278b 100644
--- a/dist/utils/tiktoken.cjs
+++ b/dist/utils/tiktoken.cjs
@@ -1,25 +1,14 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.encodingForModel = exports.getEncoding = void 0;
-const lite_1 = require("js-tiktoken/lite");
const async_caller_js_1 = require("./async_caller.cjs");
const cache = {};
const caller = /* #__PURE__ */ new async_caller_js_1.AsyncCaller({});
async function getEncoding(encoding) {
- if (!(encoding in cache)) {
- cache[encoding] = caller
- .fetch(`https://tiktoken.pages.dev/js/${encoding}.json`)
- .then((res) => res.json())
- .then((data) => new lite_1.Tiktoken(data))
- .catch((e) => {
- delete cache[encoding];
- throw e;
- });
- }
- return await cache[encoding];
+ throw new Error("TikToken Not implemented");
}
exports.getEncoding = getEncoding;
async function encodingForModel(model) {
- return getEncoding((0, lite_1.getEncodingNameForModel)(model));
+ throw new Error("TikToken Not implemented");
}
exports.encodingForModel = encodingForModel;
diff --git a/dist/utils/tiktoken.js b/dist/utils/tiktoken.js
index 8e41ee6f00f2f9c7fa2c59fa2b2f4297634b97aa..aa5f314a6349ad0d1c5aea8631a56aad099176e0 100644
--- a/dist/utils/tiktoken.js
+++ b/dist/utils/tiktoken.js
@@ -1,20 +1,9 @@
-import { Tiktoken, getEncodingNameForModel, } from "js-tiktoken/lite";
import { AsyncCaller } from "./async_caller.js";
const cache = {};
const caller = /* #__PURE__ */ new AsyncCaller({});
export async function getEncoding(encoding) {
- if (!(encoding in cache)) {
- cache[encoding] = caller
- .fetch(`https://tiktoken.pages.dev/js/${encoding}.json`)
- .then((res) => res.json())
- .then((data) => new Tiktoken(data))
- .catch((e) => {
- delete cache[encoding];
- throw e;
- });
- }
- return await cache[encoding];
+ throw new Error("TikToken Not implemented");
}
export async function encodingForModel(model) {
- return getEncoding(getEncodingNameForModel(model));
+ throw new Error("TikToken Not implemented");
}
diff --git a/package.json b/package.json
index 36072aecf700fca1bc49832a19be832eca726103..90b8922fba1c3d1b26f78477c891b07816d6238a 100644
--- a/package.json
+++ b/package.json
@@ -37,7 +37,6 @@
"ansi-styles": "^5.0.0",
"camelcase": "6",
"decamelize": "1.2.0",
- "js-tiktoken": "^1.0.12",
"langsmith": ">=0.2.8 <0.4.0",
"mustache": "^4.2.0",
"p-queue": "^6.6.2",

View File

@@ -0,0 +1,68 @@
diff --git a/dist/utils/tiktoken.cjs b/dist/utils/tiktoken.cjs
index c5b41f121d2e3d24c3a4969e31fa1acffdcad3b9..ec724489dcae79ee6c61acf2d4d84bd19daef036 100644
--- a/dist/utils/tiktoken.cjs
+++ b/dist/utils/tiktoken.cjs
@@ -1,6 +1,5 @@
const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
const require_utils_async_caller = require('./async_caller.cjs');
-const js_tiktoken_lite = require_rolldown_runtime.__toESM(require("js-tiktoken/lite"));
//#region src/utils/tiktoken.ts
var tiktoken_exports = {};
@@ -11,14 +10,10 @@ require_rolldown_runtime.__export(tiktoken_exports, {
const cache = {};
const caller = /* @__PURE__ */ new require_utils_async_caller.AsyncCaller({});
async function getEncoding(encoding) {
- if (!(encoding in cache)) cache[encoding] = caller.fetch(`https://tiktoken.pages.dev/js/${encoding}.json`).then((res) => res.json()).then((data) => new js_tiktoken_lite.Tiktoken(data)).catch((e) => {
- delete cache[encoding];
- throw e;
- });
- return await cache[encoding];
+ throw new Error("TikToken Not implemented");
}
async function encodingForModel(model) {
- return getEncoding((0, js_tiktoken_lite.getEncodingNameForModel)(model));
+ throw new Error("TikToken Not implemented");
}
//#endregion
diff --git a/dist/utils/tiktoken.js b/dist/utils/tiktoken.js
index 641acca03cb92f04a6fa5c9c31f1880ce635572e..707389970ad957aa0ff20ef37fa8dd2875be737c 100644
--- a/dist/utils/tiktoken.js
+++ b/dist/utils/tiktoken.js
@@ -1,6 +1,5 @@
import { __export } from "../_virtual/rolldown_runtime.js";
import { AsyncCaller } from "./async_caller.js";
-import { Tiktoken, getEncodingNameForModel } from "js-tiktoken/lite";
//#region src/utils/tiktoken.ts
var tiktoken_exports = {};
@@ -11,14 +10,10 @@ __export(tiktoken_exports, {
const cache = {};
const caller = /* @__PURE__ */ new AsyncCaller({});
async function getEncoding(encoding) {
- if (!(encoding in cache)) cache[encoding] = caller.fetch(`https://tiktoken.pages.dev/js/${encoding}.json`).then((res) => res.json()).then((data) => new Tiktoken(data)).catch((e) => {
- delete cache[encoding];
- throw e;
- });
- return await cache[encoding];
+ throw new Error("TikToken Not implemented");
}
async function encodingForModel(model) {
- return getEncoding(getEncodingNameForModel(model));
+ throw new Error("TikToken Not implemented");
}
//#endregion
diff --git a/package.json b/package.json
index a24f8fc61de58526051999260f2ebee5f136354b..e885359e8966e7730c51772533ce37e01edb3046 100644
--- a/package.json
+++ b/package.json
@@ -20,7 +20,6 @@
"ansi-styles": "^5.0.0",
"camelcase": "6",
"decamelize": "1.2.0",
- "js-tiktoken": "^1.0.12",
"langsmith": "^0.3.64",
"mustache": "^4.2.0",
"p-queue": "^6.6.2",

View File

@@ -1,19 +0,0 @@
diff --git a/dist/embeddings.js b/dist/embeddings.js
index 1f8154be3e9c22442a915eb4b85fa6d2a21b0d0c..dc13ef4a30e6c282824a5357bcee9bd0ae222aab 100644
--- a/dist/embeddings.js
+++ b/dist/embeddings.js
@@ -214,10 +214,12 @@ export class OpenAIEmbeddings extends Embeddings {
* @returns Promise that resolves to an embedding for the document.
*/
async embedQuery(text) {
+ const isBaiduCloud = this.clientConfig.baseURL.includes('baidubce.com')
+ const input = this.stripNewLines ? text.replace(/\n/g, ' ') : text
const params = {
model: this.model,
- input: this.stripNewLines ? text.replace(/\n/g, " ") : text,
- };
+ input: isBaiduCloud ? [input] : input
+ }
if (this.dimensions) {
params.dimensions = this.dimensions;
}

View File

@@ -0,0 +1,17 @@
diff --git a/dist/embeddings.js b/dist/embeddings.js
index 6f4b928d3e4717309382e1b5c2e31ab5bc6c5af0..bc79429c88a6d27d4997a2740c4d8ae0707f5991 100644
--- a/dist/embeddings.js
+++ b/dist/embeddings.js
@@ -94,9 +94,11 @@ var OpenAIEmbeddings = class extends Embeddings {
* @returns Promise that resolves to an embedding for the document.
*/
async embedQuery(text) {
+ const isBaiduCloud = this.clientConfig.baseURL.includes('baidubce.com');
+ const input = this.stripNewLines ? text.replace(/\n/g, " ") : text
const params = {
model: this.model,
- input: this.stripNewLines ? text.replace(/\n/g, " ") : text
+ input: isBaiduCloud ? [input] : input
};
if (this.dimensions) params.dimensions = this.dimensions;
if (this.encodingFormat) params.encoding_format = this.encodingFormat;

View File

@@ -1,44 +0,0 @@
diff --git a/dist/index.js b/dist/index.js
index 53f411e55a4c9a06fd29bb4ab8161c4ad15980cd..71b91f196c8b886ed90dd237dec5625d79d5677e 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -12676,10 +12676,13 @@ var OpenAIResponsesLanguageModel = class {
}
});
} else if (value.item.type === "message") {
- controller.enqueue({
- type: "text-end",
- id: value.item.id
- });
+ // Fix for gpt-5-codex: use currentTextId to ensure text-end matches text-start
+ if (currentTextId) {
+ controller.enqueue({
+ type: "text-end",
+ id: currentTextId
+ });
+ }
currentTextId = null;
} else if (isResponseOutputItemDoneReasoningChunk(value)) {
const activeReasoningPart = activeReasoning[value.item.id];
diff --git a/dist/index.mjs b/dist/index.mjs
index 7719264da3c49a66c2626082f6ccaae6e3ef5e89..090fd8cf142674192a826148428ed6a0c4a54e35 100644
--- a/dist/index.mjs
+++ b/dist/index.mjs
@@ -12670,10 +12670,13 @@ var OpenAIResponsesLanguageModel = class {
}
});
} else if (value.item.type === "message") {
- controller.enqueue({
- type: "text-end",
- id: value.item.id
- });
+ // Fix for gpt-5-codex: use currentTextId to ensure text-end matches text-start
+ if (currentTextId) {
+ controller.enqueue({
+ type: "text-end",
+ id: currentTextId
+ });
+ }
currentTextId = null;
} else if (isResponseOutputItemDoneReasoningChunk(value)) {
const activeReasoningPart = activeReasoning[value.item.id];

View File

@@ -1,276 +0,0 @@
diff --git a/out/macPackager.js b/out/macPackager.js
index 852f6c4d16f86a7bb8a78bf1ed5a14647a279aa1..60e7f5f16a844541eb1909b215fcda1811e924b8 100644
--- a/out/macPackager.js
+++ b/out/macPackager.js
@@ -423,7 +423,7 @@ class MacPackager extends platformPackager_1.PlatformPackager {
}
appPlist.CFBundleName = appInfo.productName;
appPlist.CFBundleDisplayName = appInfo.productName;
- const minimumSystemVersion = this.platformSpecificBuildOptions.minimumSystemVersion;
+ const minimumSystemVersion = this.platformSpecificBuildOptions.LSMinimumSystemVersion;
if (minimumSystemVersion != null) {
appPlist.LSMinimumSystemVersion = minimumSystemVersion;
}
diff --git a/out/publish/updateInfoBuilder.js b/out/publish/updateInfoBuilder.js
index 7924c5b47d01f8dfccccb8f46658015fa66da1f7..1a1588923c3939ae1297b87931ba83f0ebc052d8 100644
--- a/out/publish/updateInfoBuilder.js
+++ b/out/publish/updateInfoBuilder.js
@@ -133,6 +133,7 @@ async function createUpdateInfo(version, event, releaseInfo) {
const customUpdateInfo = event.updateInfo;
const url = path.basename(event.file);
const sha512 = (customUpdateInfo == null ? null : customUpdateInfo.sha512) || (await (0, hash_1.hashFile)(event.file));
+ const minimumSystemVersion = customUpdateInfo == null ? null : customUpdateInfo.minimumSystemVersion;
const files = [{ url, sha512 }];
const result = {
// @ts-ignore
@@ -143,9 +144,13 @@ async function createUpdateInfo(version, event, releaseInfo) {
path: url /* backward compatibility, electron-updater 1.x - electron-updater 2.15.0 */,
// @ts-ignore
sha512 /* backward compatibility, electron-updater 1.x - electron-updater 2.15.0 */,
+ minimumSystemVersion,
...releaseInfo,
};
if (customUpdateInfo != null) {
+ if (customUpdateInfo.minimumSystemVersion) {
+ delete customUpdateInfo.minimumSystemVersion;
+ }
// file info or nsis web installer packages info
Object.assign("sha512" in customUpdateInfo ? files[0] : result, customUpdateInfo);
}
diff --git a/out/targets/ArchiveTarget.js b/out/targets/ArchiveTarget.js
index e1f52a5fa86fff6643b2e57eaf2af318d541f865..47cc347f154a24b365e70ae5e1f6d309f3582ed0 100644
--- a/out/targets/ArchiveTarget.js
+++ b/out/targets/ArchiveTarget.js
@@ -69,6 +69,9 @@ class ArchiveTarget extends core_1.Target {
}
}
}
+ if (updateInfo != null && this.packager.platformSpecificBuildOptions.minimumSystemVersion) {
+ updateInfo.minimumSystemVersion = this.packager.platformSpecificBuildOptions.minimumSystemVersion;
+ }
await packager.info.emitArtifactBuildCompleted({
updateInfo,
file: artifactPath,
diff --git a/out/targets/nsis/NsisTarget.js b/out/targets/nsis/NsisTarget.js
index e8bd7bb46c8a54b3f55cf3a853ef924195271e01..f956e9f3fe9eb903c78aef3502553b01de4b89b1 100644
--- a/out/targets/nsis/NsisTarget.js
+++ b/out/targets/nsis/NsisTarget.js
@@ -305,6 +305,9 @@ class NsisTarget extends core_1.Target {
if (updateInfo != null && isPerMachine && (oneClick || options.packElevateHelper)) {
updateInfo.isAdminRightsRequired = true;
}
+ if (updateInfo != null && this.packager.platformSpecificBuildOptions.minimumSystemVersion) {
+ updateInfo.minimumSystemVersion = this.packager.platformSpecificBuildOptions.minimumSystemVersion;
+ }
await packager.info.emitArtifactBuildCompleted({
file: installerPath,
updateInfo,
diff --git a/out/util/yarn.js b/out/util/yarn.js
index 1ee20f8b252a8f28d0c7b103789cf0a9a427aec1..c2878ec54d57da50bf14225e0c70c9c88664eb8a 100644
--- a/out/util/yarn.js
+++ b/out/util/yarn.js
@@ -140,6 +140,7 @@ async function rebuild(config, { appDir, projectDir }, options) {
arch,
platform,
buildFromSource,
+ ignoreModules: config.excludeReBuildModules || undefined,
projectRootPath: projectDir,
mode: config.nativeRebuilder || "sequential",
disablePreGypCopy: true,
diff --git a/scheme.json b/scheme.json
index 433e2efc9cef156ff5444f0c4520362ed2ef9ea7..0167441bf928a92f59b5dbe70b2317a74dda74c9 100644
--- a/scheme.json
+++ b/scheme.json
@@ -1825,6 +1825,20 @@
"string"
]
},
+ "excludeReBuildModules": {
+ "anyOf": [
+ {
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "The modules to exclude from the rebuild."
+ },
"executableArgs": {
"anyOf": [
{
@@ -1975,6 +1989,13 @@
],
"description": "The mime types in addition to specified in the file associations. Use it if you don't want to register a new mime type, but reuse existing."
},
+ "minimumSystemVersion": {
+ "description": "The minimum os kernel version required to install the application.",
+ "type": [
+ "null",
+ "string"
+ ]
+ },
"packageCategory": {
"description": "backward compatibility + to allow specify fpm-only category for all possible fpm targets in one place",
"type": [
@@ -2327,6 +2348,13 @@
"MacConfiguration": {
"additionalProperties": false,
"properties": {
+ "LSMinimumSystemVersion": {
+ "description": "The minimum version of macOS required for the app to run. Corresponds to `LSMinimumSystemVersion`.",
+ "type": [
+ "null",
+ "string"
+ ]
+ },
"additionalArguments": {
"anyOf": [
{
@@ -2527,6 +2555,20 @@
"string"
]
},
+ "excludeReBuildModules": {
+ "anyOf": [
+ {
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "The modules to exclude from the rebuild."
+ },
"executableName": {
"description": "The executable name. Defaults to `productName`.",
"type": [
@@ -2737,7 +2779,7 @@
"type": "boolean"
},
"minimumSystemVersion": {
- "description": "The minimum version of macOS required for the app to run. Corresponds to `LSMinimumSystemVersion`.",
+ "description": "The minimum os kernel version required to install the application.",
"type": [
"null",
"string"
@@ -2959,6 +3001,13 @@
"MasConfiguration": {
"additionalProperties": false,
"properties": {
+ "LSMinimumSystemVersion": {
+ "description": "The minimum version of macOS required for the app to run. Corresponds to `LSMinimumSystemVersion`.",
+ "type": [
+ "null",
+ "string"
+ ]
+ },
"additionalArguments": {
"anyOf": [
{
@@ -3159,6 +3208,20 @@
"string"
]
},
+ "excludeReBuildModules": {
+ "anyOf": [
+ {
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "The modules to exclude from the rebuild."
+ },
"executableName": {
"description": "The executable name. Defaults to `productName`.",
"type": [
@@ -3369,7 +3432,7 @@
"type": "boolean"
},
"minimumSystemVersion": {
- "description": "The minimum version of macOS required for the app to run. Corresponds to `LSMinimumSystemVersion`.",
+ "description": "The minimum os kernel version required to install the application.",
"type": [
"null",
"string"
@@ -6381,6 +6444,20 @@
"string"
]
},
+ "excludeReBuildModules": {
+ "anyOf": [
+ {
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "The modules to exclude from the rebuild."
+ },
"executableName": {
"description": "The executable name. Defaults to `productName`.",
"type": [
@@ -6507,6 +6584,13 @@
"string"
]
},
+ "minimumSystemVersion": {
+ "description": "The minimum os kernel version required to install the application.",
+ "type": [
+ "null",
+ "string"
+ ]
+ },
"protocols": {
"anyOf": [
{
@@ -7153,6 +7237,20 @@
"string"
]
},
+ "excludeReBuildModules": {
+ "anyOf": [
+ {
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "The modules to exclude from the rebuild."
+ },
"executableName": {
"description": "The executable name. Defaults to `productName`.",
"type": [
@@ -7376,6 +7474,13 @@
],
"description": "MAS (Mac Application Store) development options (`mas-dev` target)."
},
+ "minimumSystemVersion": {
+ "description": "The minimum os kernel version required to install the application.",
+ "type": [
+ "null",
+ "string"
+ ]
+ },
"msi": {
"anyOf": [
{

View File

@@ -0,0 +1,14 @@
diff --git a/out/util.js b/out/util.js
index 9294ffd6ca8f02c2e0f90c663e7e9cdc02c1ac37..f52107493e2995320ee4efd0eb2a8c9bf03291a2 100644
--- a/out/util.js
+++ b/out/util.js
@@ -23,7 +23,8 @@ function newUrlFromBase(pathname, baseUrl, addRandomQueryToAvoidCaching = false)
result.search = search;
}
else if (addRandomQueryToAvoidCaching) {
- result.search = `noCache=${Date.now().toString(32)}`;
+ // use no cache header instead
+ // result.search = `noCache=${Date.now().toString(32)}`;
}
return result;
}

Binary file not shown.

View File

@@ -7,11 +7,12 @@ This file provides guidance to AI coding assistants when working with code in th
- **Keep it clear**: Write code that is easy to read, maintain, and explain.
- **Match the house style**: Reuse existing patterns, naming, and conventions.
- **Search smart**: Prefer `ast-grep` for semantic queries; fall back to `rg`/`grep` when needed.
- **Build with HeroUI**: Use HeroUI for every new UI component; never add `antd` or `styled-components`.
- **Log centrally**: Route all logging through `loggerService` with the right context—no `console.log`.
- **Research via subagent**: Lean on `subagent` for external docs, APIs, news, and references.
- **Seek review**: Ask a human developer to review substantial changes before merging.
- **Commit in rhythm**: Keep commits small, conventional, and emoji-tagged.
- **Always propose before executing**: Before making any changes, clearly explain your planned approach and wait for explicit user approval to ensure alignment and prevent unwanted modifications.
- **Lint, test, and format before completion**: Coding tasks are only complete after running `yarn lint`, `yarn test`, and `yarn format` successfully.
- **Write conventional commits**: Commit small, focused changes using Conventional Commit messages (e.g., `feat:`, `fix:`, `refactor:`, `docs:`).
- **Follow PR template**: When submitting pull requests, follow the template in `.github/pull_request_template.md` to ensure complete context and documentation.
## Development Commands
@@ -40,7 +41,6 @@ This file provides guidance to AI coding assistants when working with code in th
- **Services** (`src/main/services/`): MCPService, KnowledgeService, WindowService, etc.
- **Build System**: Electron-Vite with experimental rolldown-vite, yarn workspaces.
- **State Management**: Redux Toolkit (`src/renderer/src/store/`) for predictable state.
- **UI Components**: HeroUI (`@heroui/*`) for all new UI elements.
### Logging
```typescript

View File

@@ -1,4 +1,4 @@
[中文](docs/CONTRIBUTING.zh.md) | [English](CONTRIBUTING.md)
[中文](docs/zh/guides/contributing.md) | [English](CONTRIBUTING.md)
# Cherry Studio Contributor Guide
@@ -32,7 +32,7 @@ To help you get familiar with the codebase, we recommend tackling issues tagged
### Testing
Features without tests are considered non-existent. To ensure code is truly effective, relevant processes should be covered by unit tests and functional tests. Therefore, when considering contributions, please also consider testability. All tests can be run locally without dependency on CI. Please refer to the "Testing" section in the [Developer Guide](docs/dev.md).
Features without tests are considered non-existent. To ensure code is truly effective, relevant processes should be covered by unit tests and functional tests. Therefore, when considering contributions, please also consider testability. All tests can be run locally without dependency on CI. Please refer to the "Testing" section in the [Developer Guide](docs/zh/guides/development.md).
### Automated Testing for Pull Requests
@@ -60,12 +60,33 @@ Maintainers are here to help you implement your use case within a reasonable tim
### Participating in the Test Plan
The Test Plan aims to provide users with a more stable application experience and faster iteration speed. For details, please refer to the [Test Plan](docs/testplan-en.md).
The Test Plan aims to provide users with a more stable application experience and faster iteration speed. For details, please refer to the [Test Plan](docs/en/guides/test-plan.md).
### Other Suggestions
- **Contact Developers**: Before submitting a PR, you can contact the developers first to discuss or get help.
- **Become a Core Developer**: If you contribute to the project consistently, congratulations, you can become a core developer and gain project membership status. Please check our [Membership Guide](https://github.com/CherryHQ/community/blob/main/docs/membership.en.md).
## Important Contribution Guidelines & Focus Areas
Please review the following critical information before submitting your Pull Request:
### Temporary Restriction on Data-Changing Feature PRs 🚫
**Currently, we are NOT accepting feature Pull Requests that introduce changes to our Redux data models or IndexedDB schemas.**
Our core team is currently focused on significant architectural updates that involve these data structures. To ensure stability and focus during this period, contributions of this nature will be temporarily managed internally.
* **PRs that require changes to Redux state shape or IndexedDB schemas will be closed.**
* **This restriction is temporary and will be lifted with the release of `v2.0.0`.** You can track the progress of `v2.0.0` and its related discussions on issue [#10162](https://github.com/CherryHQ/cherry-studio/pull/10162).
We highly encourage contributions for:
* Bug fixes 🐞
* Performance improvements 🚀
* Documentation updates 📚
* Features that **do not** alter Redux data models or IndexedDB schemas (e.g., UI enhancements, new components, minor refactors). ✨
We appreciate your understanding and continued support during this important development phase. Thank you!
## Contact Us

677
LICENSE
View File

@@ -1,42 +1,661 @@
**Licensing**
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
This project employs a **User-Segmented Dual Licensing** model.
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
**Core Principle:**
Preamble
* **Individual Users and Organizations with 10 or Fewer Individuals:** Governed by default under the **GNU Affero General Public License v3.0 (AGPLv3)**.
* **Organizations with More Than 10 Individuals:** **Must** obtain a **Commercial License**.
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
Definition: "10 or Fewer Individuals"
Refers to any organization (including companies, non-profits, government agencies, educational institutions, etc.) where the total number of individuals who can access, use, or in any way directly or indirectly benefit from the functionality of this software (Cherry Studio) does not exceed 10. This includes, but is not limited to, developers, testers, operations staff, end-users, and indirect users via integrated systems.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.
---
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
**1. Open Source License: AGPLv3 - For Individuals and Organizations of 10 or Fewer**
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
* If you are an individual user, or if your organization meets the "10 or Fewer Individuals" definition above, you are free to use, modify, and distribute Cherry Studio under the terms of the **AGPLv3**. The full text of the AGPLv3 can be found in the LICENSE file at [https://www.gnu.org/licenses/agpl-3.0.html](https://www.gnu.org/licenses/agpl-3.0.html).
* **Core Obligation:** A key requirement of the AGPLv3 is that if you modify Cherry Studio and make it available over a network, or distribute the modified version, you must provide the **complete corresponding source code** under the AGPLv3 license to the recipients. Even if you qualify under the "10 or Fewer Individuals" rule, if you wish to avoid this source code disclosure obligation, you will need to obtain a Commercial License (see below).
* Please read and understand the full terms of the AGPLv3 carefully before use.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
**2. Commercial License - For Organizations with More Than 10 Individuals, or Users Needing to Avoid AGPLv3 Obligations**
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
* **Mandatory Requirement:** If your organization does **not** meet the "10 or Fewer Individuals" definition above (i.e., 11 or more individuals can access, use, or benefit from the software), you **must** contact us to obtain and execute a Commercial License to use Cherry Studio.
* **Voluntary Option:** Even if your organization meets the "10 or Fewer Individuals" condition, if your intended use case **cannot comply with the terms of the AGPLv3** (particularly the obligations regarding **source code disclosure**), or if you require specific commercial terms **not offered** by the AGPLv3 (such as warranties, indemnities, or freedom from copyleft restrictions), you also **must** contact us to obtain and execute a Commercial License.
* **Common scenarios requiring a Commercial License include (but are not limited to):**
* Your organization has more than 10 individuals who can access, use, or benefit from the software.
* (Regardless of organization size) You wish to distribute a modified version of Cherry Studio but **do not want** to disclose the source code of your modifications under AGPLv3.
* (Regardless of organization size) You wish to provide a network service (SaaS) based on a modified version of Cherry Studio but **do not want** to provide the modified source code to users of the service under AGPLv3.
* (Regardless of organization size) Your corporate policies, client contracts, or project requirements prohibit the use of AGPLv3-licensed software or mandate closed-source distribution and confidentiality.
* The Commercial License grants you rights exempting you from AGPLv3 obligations (like source code disclosure) and may include additional commercial assurances.
* **Obtaining a Commercial License:** Please contact the Cherry Studio development team via email at **bd@cherry-ai.com** to discuss commercial licensing options.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
**3. Contributions**
The precise terms and conditions for copying, distribution and
modification follow.
* We welcome community contributions to Cherry Studio. All contributions submitted to this project are considered to be offered under the **AGPLv3** license.
* By submitting a contribution to this project (e.g., via a Pull Request), you agree to license your code under the AGPLv3 to the project and all its downstream users (regardless of whether those users ultimately operate under AGPLv3 or a Commercial License).
* You also understand and agree that your contribution may be included in distributions of Cherry Studio offered under our commercial license.
TERMS AND CONDITIONS
**4. Other Terms**
0. Definitions.
* The specific terms and conditions of the Commercial License are governed by the formal commercial license agreement signed by both parties.
* The project maintainers reserve the right to update this licensing policy (including the definition and threshold for user count) as needed. Updates will be communicated through official project channels (e.g., code repository, official website).
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<https://www.gnu.org/licenses/>.

View File

@@ -34,10 +34,10 @@
</a>
</h1>
<p align="center">English | <a href="./docs/README.zh.md">中文</a> | <a href="https://cherry-ai.com">Official Site</a> | <a href="https://docs.cherry-ai.com/cherry-studio-wen-dang/en-us">Documents</a> | <a href="./docs/dev.md">Development</a> | <a href="https://github.com/CherryHQ/cherry-studio/issues">Feedback</a><br></p>
<p align="center">English | <a href="./docs/zh/README.md">中文</a> | <a href="https://cherry-ai.com">Official Site</a> | <a href="https://docs.cherry-ai.com/cherry-studio-wen-dang/en-us">Documents</a> | <a href="./docs/en/guides/development.md">Development</a> | <a href="https://github.com/CherryHQ/cherry-studio/issues">Feedback</a><br></p>
<div align="center">
[![][deepwiki-shield]][deepwiki-link]
[![][twitter-shield]][twitter-link]
[![][discord-shield]][discord-link]
@@ -45,7 +45,7 @@
</div>
<div align="center">
[![][github-release-shield]][github-release-link]
[![][github-nightly-shield]][github-nightly-link]
[![][github-contributors-shield]][github-contributors-link]
@@ -67,7 +67,7 @@ Cherry Studio is a desktop client that supports multiple LLM providers, availabl
👏 Join [Telegram Group](https://t.me/CherryStudioAI)[Discord](https://discord.gg/wez8HtpxqQ) | [QQ Group(575014769)](https://qm.qq.com/q/lo0D4qVZKi)
❤️ Like Cherry Studio? Give it a star 🌟 or [Sponsor](docs/sponsor.md) to support the development!
❤️ Like Cherry Studio? Give it a star 🌟 or [Sponsor](docs/zh/guides/sponsor.md) to support the development!
# 🌠 Screenshot
@@ -82,7 +82,7 @@ Cherry Studio is a desktop client that supports multiple LLM providers, availabl
1. **Diverse LLM Provider Support**:
- ☁️ Major LLM Cloud Services: OpenAI, Gemini, Anthropic, and more
- 🔗 AI Web Service Integration: Claude, Perplexity, Poe, and others
- 🔗 AI Web Service Integration: Claude, Perplexity, [Poe](https://poe.com/), and others
- 💻 Local Model Support with Ollama, LM Studio
2. **AI Assistants & Conversations**:
@@ -141,6 +141,7 @@ We're actively working on the following features and improvements:
- iOS App (Phase 1)
- Multi-Window support
- Window Pinning functionality
- Intel AI PC (Core Ultra) Support
4. 🔌 **Advanced Features**
@@ -174,7 +175,7 @@ We welcome contributions to Cherry Studio! Here are some ways you can contribute
6. **Community Engagement**: Join discussions and help users.
7. **Promote Usage**: Spread the word about Cherry Studio.
Refer to the [Branching Strategy](docs/branching-strategy-en.md) for contribution guidelines
Refer to the [Branching Strategy](docs/en/guides/branching-strategy.md) for contribution guidelines
## Getting Started
@@ -237,20 +238,16 @@ The Enterprise Edition addresses core challenges in team collaboration by centra
## ✨ Online Demo
> 🚧 **Public Beta Notice**
>
> The Enterprise Edition is currently in its early public beta stage, and we are actively iterating and optimizing its features. We are aware that it may not be perfectly stable yet. If you encounter any issues or have valuable suggestions during your trial, we would be very grateful if you could contact us via email to provide feedback.
**🔗 [Cherry Studio Enterprise](https://www.cherry-ai.com/enterprise)**
## Version Comparison
| Feature | Community Edition | Enterprise Edition |
| :---------------- | :----------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------- |
| **Open Source** | ✅ Yes | ⭕️ Partially released to customers |
| **Cost** | Free for Personal Use / Commercial License | Buyout / Subscription Fee |
| **Open Source** | ✅ Yes | ⭕️ Partially released to customers |
| **Cost** | [AGPL-3.0 License](https://github.com/CherryHQ/cherry-studio?tab=AGPL-3.0-1-ov-file) | Buyout / Subscription Fee |
| **Admin Backend** | — | ● Centralized **Model** Access<br>**Employee** Management<br>● Shared **Knowledge Base**<br>**Access** Control<br>**Data** Backup |
| **Server** | — | ✅ Dedicated Private Deployment |
| **Server** | — | ✅ Dedicated Private Deployment |
## Get the Enterprise Edition
@@ -261,8 +258,12 @@ We believe the Enterprise Edition will become your team's AI productivity engine
# 🔗 Related Projects
- [new-api](https://github.com/QuantumNous/new-api): The next-generation LLM gateway and AI asset management system supports multiple languages.
- [one-api](https://github.com/songquanpeng/one-api): LLM API management and distribution system supporting mainstream models like OpenAI, Azure, and Anthropic. Features a unified API interface, suitable for key management and secondary distribution.
- [Poe](https://poe.com/): Poe gives you access to the best AI, all in one place. Explore GPT-5, Claude Opus 4.1, DeepSeek-R1, Veo 3, ElevenLabs, and millions of others.
- [ublacklist](https://github.com/iorate/ublacklist): Blocks specific sites from appearing in Google search results
# 🚀 Contributors
@@ -286,6 +287,14 @@ We believe the Enterprise Edition will become your team's AI productivity engine
</picture>
</a>
# 📜 License
The Cherry Studio Community Edition is governed by the standard GNU Affero General Public License v3.0 (AGPL-3.0), available at https://www.gnu.org/licenses/agpl-3.0.html.
Use of the Cherry Studio Community Edition for commercial purposes is permitted, subject to full compliance with the terms and conditions of the AGPL-3.0 license.
Should you require a commercial license that provides an exemption from the AGPL-3.0 requirements, please contact us at bd@cherry-ai.com.
<!-- Links & Images -->
[deepwiki-shield]: https://img.shields.io/badge/Deepwiki-CherryHQ-0088CC?logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNy45MyAzMiI+PHBhdGggZD0iTTE5LjMzIDE0LjEyYy42Ny0uMzkgMS41LS4zOSAyLjE4IDBsMS43NCAxYy4wNi4wMy4xMS4wNi4xOC4wN2guMDRjLjA2LjAzLjEyLjAzLjE4LjAzaC4wMmMuMDYgMCAuMTEgMCAuMTctLjAyaC4wM2MuMDYtLjAyLjEyLS4wNS4xNy0uMDhoLjAybDMuNDgtMi4wMWMuMjUtLjE0LjQtLjQxLjQtLjdWOC40YS44MS44MSAwIDAgMC0uNC0uN2wtMy40OC0yLjAxYS44My44MyAwIDAgMC0uODEgMEwxOS43NyA3LjdoLS4wMWwtLjE1LjEyLS4wMi4wMnMtLjA3LjA5LS4xLjE0VjhhLjQuNCAwIDAgMC0uMDguMTd2LjA0Yy0uMDMuMDYtLjAzLjEyLS4wMy4xOXYyLjAxYzAgLjc4LS40MSAxLjQ5LTEuMDkgMS44OC0uNjcuMzktMS41LjM5LTIuMTggMGwtMS43NC0xYS42LjYgMCAwIDAtLjIxLS4wOGMtLjA2LS4wMS0uMTItLjAyLS4xOC0uMDJoLS4wM2MtLjA2IDAtLjExLjAxLS4xNy4wMmgtLjAzYy0uMDYuMDItLjEyLjA0LS4xNy4wN2gtLjAybC0zLjQ3IDIuMDFjLS4yNS4xNC0uNC40MS0uNC43VjE4YzAgLjI5LjE1LjU1LjQuN2wzLjQ4IDIuMDFoLjAyYy4wNi4wNC4xMS4wNi4xNy4wOGguMDNjLjA1LjAyLjExLjAzLjE3LjAzaC4wMmMuMDYgMCAuMTIgMCAuMTgtLjAyaC4wNGMuMDYtLjAzLjEyLS4wNS4xOC0uMDhsMS43NC0xYy42Ny0uMzkgMS41LS4zOSAyLjE3IDBzMS4wOSAxLjExIDEuMDkgMS44OHYyLjAxYzAgLjA3IDAgLjEzLjAyLjE5di4wNGMuMDMuMDYuMDUuMTIuMDguMTd2LjAycy4wOC4wOS4xMi4xM2wuMDIuMDJzLjA5LjA4LjE1LjExYzAgMCAuMDEgMCAuMDEuMDFsMy40OCAyLjAxYy4yNS4xNC41Ni4xNC44MSAwbDMuNDgtMi4wMWMuMjUtLjE0LjQtLjQxLjQtLjd2LTQuMDFhLjgxLjgxIDAgMCAwLS40LS43bC0zLjQ4LTIuMDFoLS4wMmMtLjA1LS4wNC0uMTEtLjA2LS4xNy0uMDhoLS4wM2EuNS41IDAgMCAwLS4xNy0uMDNoLS4wM2MtLjA2IDAtLjEyIDAtLjE4LjAyLS4wNy4wMi0uMTUuMDUtLjIxLjA4bC0xLjc0IDFjLS42Ny4zOS0xLjUuMzktMi4xNyAwYTIuMTkgMi4xOSAwIDAgMS0xLjA5LTEuODhjMC0uNzguNDItMS40OSAxLjA5LTEuODhaIiBzdHlsZT0iZmlsbDojNWRiZjlkIi8+PHBhdGggZD0ibS40IDEzLjExIDMuNDcgMi4wMWMuMjUuMTQuNTYuMTQuOCAwbDMuNDctMi4wMWguMDFsLjE1LS4xMi4wMi0uMDJzLjA3LS4wOS4xLS4xNGwuMDItLjAyYy4wMy0uMDUuMDUtLjExLjA3LS4xN3YtLjA0Yy4wMy0uMDYuMDMtLjEyLjAzLS4xOVYxMC40YzAtLjc4LjQyLTEuNDkgMS4wOS0xLjg4czEuNS0uMzkgMi4xOCAwbDEuNzQgMWMuMDcuMDQuMTQuMDcuMjEuMDguMDYuMDEuMTIuMDIuMTguMDJoLjAzYy4wNiAwIC4xMS0uMDEuMTctLjAyaC4wM2MuMDYtLjAyLjEyLS4wNC4xNy0uMDdoLjAybDMuNDctMi4wMmMuMjUtLjE0LjQtLjQxLjQtLjd2LTRhLjgxLjgxIDAgMCAwLS40LS43bC0zLjQ2LTJhLjgzLjgzIDAgMCAwLS44MSAwbC0zLjQ4IDIuMDFoLS4wMWwtLjE1LjEyLS4wMi4wMi0uMS4xMy0uMDIuMDJjLS4wMy4wNS0uMDUuMTEtLjA3LjE3di4wNGMtLjAzLjA2LS4wMy4xMi0uMDMuMTl2Mi4wMWMwIC43OC0uNDIgMS40OS0xLjA5IDEuODhzLTEuNS4zOS0yLjE4IDBsLTEuNzQtMWEuNi42IDAgMCAwLS4yMS0uMDhjLS4wNi0uMDEtLjEyLS4wMi0uMTgtLjAyaC0uMDNjLS4wNiAwLS4xMS4wMS0uMTcuMDJoLS4wM2MtLjA2LjAyLS4xMi4wNS0uMTcuMDhoLS4wMkwuNCA3LjcxYy0uMjUuMTQtLjQuNDEtLjQuNjl2NC4wMWMwIC4yOS4xNS41Ni40LjciIHN0eWxlPSJmaWxsOiM0NDY4YzQiLz48cGF0aCBkPSJtMTcuODQgMjQuNDgtMy40OC0yLjAxaC0uMDJjLS4wNS0uMDQtLjExLS4wNi0uMTctLjA4aC0uMDNhLjUuNSAwIDAgMC0uMTctLjAzaC0uMDNjLS4wNiAwLS4xMiAwLS4xOC4wMmgtLjA0Yy0uMDYuMDMtLjEyLjA1LS4xOC4wOGwtMS43NCAxYy0uNjcuMzktMS41LjM5LTIuMTggMGEyLjE5IDIuMTkgMCAwIDEtMS4wOS0xLjg4di0yLjAxYzAtLjA2IDAtLjEzLS4wMi0uMTl2LS4wNGMtLjAzLS4wNi0uMDUtLjExLS4wOC0uMTdsLS4wMi0uMDJzLS4wNi0uMDktLjEtLjEzTDguMjkgMTlzLS4wOS0uMDgtLjE1LS4xMWgtLjAxbC0zLjQ3LTIuMDJhLjgzLjgzIDAgMCAwLS44MSAwTC4zNyAxOC44OGEuODcuODcgMCAwIDAtLjM3LjcxdjQuMDFjMCAuMjkuMTUuNTUuNC43bDMuNDcgMi4wMWguMDJjLjA1LjA0LjExLjA2LjE3LjA4aC4wM2MuMDUuMDIuMTEuMDMuMTYuMDNoLjAzYy4wNiAwIC4xMiAwIC4xOC0uMDJoLjA0Yy4wNi0uMDMuMTItLjA1LjE4LS4wOGwxLjc0LTFjLjY3LS4zOSAxLjUtLjM5IDIuMTcgMHMxLjA5IDEuMTEgMS4wOSAxLjg4djIuMDFjMCAuMDcgMCAuMTMuMDIuMTl2LjA0Yy4wMy4wNi4wNS4xMS4wOC4xN2wuMDIuMDJzLjA2LjA5LjEuMTRsLjAyLjAycy4wOS4wOC4xNS4xMWguMDFsMy40OCAyLjAyYy4yNS4xNC41Ni4xNC44MSAwbDMuNDgtMi4wMWMuMjUtLjE0LjQtLjQxLjQtLjdWMjUuMmEuODEuODEgMCAwIDAtLjQtLjdaIiBzdHlsZT0iZmlsbDojNDI5M2Q5Ii8+PC9zdmc+

49
app-upgrade-config.json Normal file
View File

@@ -0,0 +1,49 @@
{
"lastUpdated": "2025-11-10T08:14:28Z",
"versions": {
"1.6.7": {
"metadata": {
"segmentId": "legacy-v1",
"segmentType": "legacy"
},
"minCompatibleVersion": "1.0.0",
"description": "Last stable v1.7.x release - required intermediate version for users below v1.7",
"channels": {
"latest": {
"version": "1.6.7",
"feedUrls": {
"github": "https://github.com/CherryHQ/cherry-studio/releases/download/v1.6.7",
"gitcode": "https://releases.cherry-ai.com"
}
},
"rc": {
"version": "1.6.0-rc.5",
"feedUrls": {
"github": "https://github.com/CherryHQ/cherry-studio/releases/download/v1.6.0-rc.5",
"gitcode": "https://github.com/CherryHQ/cherry-studio/releases/download/v1.6.0-rc.5"
}
},
"beta": {
"version": "1.7.0-beta.3",
"feedUrls": {
"github": "https://github.com/CherryHQ/cherry-studio/releases/download/v1.7.0-beta.3",
"gitcode": "https://github.com/CherryHQ/cherry-studio/releases/download/v1.7.0-beta.3"
}
}
}
},
"2.0.0": {
"metadata": {
"segmentId": "gateway-v2",
"segmentType": "breaking"
},
"minCompatibleVersion": "1.7.0",
"description": "Major release v2.0 - required intermediate version for v2.x upgrades",
"channels": {
"latest": null,
"rc": null,
"beta": null
}
}
}
}

View File

@@ -14,14 +14,18 @@
}
},
"enabled": true,
"includes": ["**/*.json", "!*.json", "!**/package.json"]
"includes": ["**/*.json", "!*.json", "!**/package.json", "!coverage/**"]
},
"css": {
"formatter": {
"quoteStyle": "single"
}
},
"files": { "ignoreUnknown": false },
"files": {
"ignoreUnknown": false,
"includes": ["**", "!**/.claude/**", "!**/.vscode/**"],
"maxSize": 2097152
},
"formatter": {
"attributePosition": "auto",
"bracketSameLine": false,

View File

@@ -1,21 +0,0 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"aliases": {
"components": "@renderer/ui/third-party",
"hooks": "@renderer/hooks",
"lib": "@renderer/lib",
"ui": "@renderer/ui",
"utils": "@renderer/utils"
},
"iconLibrary": "lucide",
"rsc": false,
"style": "new-york",
"tailwind": {
"baseColor": "zinc",
"config": "",
"css": "src/renderer/src/assets/styles/tailwind.css",
"cssVariables": true,
"prefix": ""
},
"tsx": true
}

View File

@@ -0,0 +1,81 @@
{
"segments": [
{
"id": "legacy-v1",
"type": "legacy",
"match": {
"range": ">=1.0.0 <2.0.0"
},
"minCompatibleVersion": "1.0.0",
"description": "Last stable v1.7.x release - required intermediate version for users below v1.7",
"channelTemplates": {
"latest": {
"feedTemplates": {
"github": "https://github.com/CherryHQ/cherry-studio/releases/download/{{tag}}",
"gitcode": "https://releases.cherry-ai.com"
}
},
"rc": {
"feedTemplates": {
"github": "https://github.com/CherryHQ/cherry-studio/releases/download/{{tag}}",
"gitcode": "https://github.com/CherryHQ/cherry-studio/releases/download/{{tag}}"
}
},
"beta": {
"feedTemplates": {
"github": "https://github.com/CherryHQ/cherry-studio/releases/download/{{tag}}",
"gitcode": "https://github.com/CherryHQ/cherry-studio/releases/download/{{tag}}"
}
}
}
},
{
"id": "gateway-v2",
"type": "breaking",
"match": {
"exact": ["2.0.0"]
},
"lockedVersion": "2.0.0",
"minCompatibleVersion": "1.7.0",
"description": "Major release v2.0 - required intermediate version for v2.x upgrades",
"channelTemplates": {
"latest": {
"feedTemplates": {
"github": "https://github.com/CherryHQ/cherry-studio/releases/download/{{tag}}",
"gitcode": "https://gitcode.com/CherryHQ/cherry-studio/releases/download/{{tag}}"
}
}
}
},
{
"id": "current-v2",
"type": "latest",
"match": {
"range": ">=2.0.0 <3.0.0",
"excludeExact": ["2.0.0"]
},
"minCompatibleVersion": "2.0.0",
"description": "Current latest v2.x release",
"channelTemplates": {
"latest": {
"feedTemplates": {
"github": "https://github.com/CherryHQ/cherry-studio/releases/download/{{tag}}",
"gitcode": "https://gitcode.com/CherryHQ/cherry-studio/releases/download/{{tag}}"
}
},
"rc": {
"feedTemplates": {
"github": "https://github.com/CherryHQ/cherry-studio/releases/download/{{tag}}",
"gitcode": "https://gitcode.com/CherryHQ/cherry-studio/releases/download/{{tag}}"
}
},
"beta": {
"feedTemplates": {
"github": "https://github.com/CherryHQ/cherry-studio/releases/download/{{tag}}",
"gitcode": "https://gitcode.com/CherryHQ/cherry-studio/releases/download/{{tag}}"
}
}
}
}
]
}

81
docs/README.md Normal file
View File

@@ -0,0 +1,81 @@
# Cherry Studio Documentation / 文档
This directory contains the project documentation in multiple languages.
本目录包含多语言项目文档。
---
## Languages / 语言
- **[中文文档](./zh/README.md)** - Chinese Documentation
- **English Documentation** - See sections below
---
## English Documentation
### Guides
| Document | Description |
|----------|-------------|
| [Development Setup](./en/guides/development.md) | Development environment setup |
| [Branching Strategy](./en/guides/branching-strategy.md) | Git branching workflow |
| [i18n Guide](./en/guides/i18n.md) | Internationalization guide |
| [Logging Guide](./en/guides/logging.md) | How to use the logger service |
| [Test Plan](./en/guides/test-plan.md) | Test plan and release channels |
### References
| Document | Description |
|----------|-------------|
| [App Upgrade Config](./en/references/app-upgrade.md) | Application upgrade configuration |
| [CodeBlockView Component](./en/references/components/code-block-view.md) | Code block view component |
| [Image Preview Components](./en/references/components/image-preview.md) | Image preview components |
---
## 中文文档
### 指南 (Guides)
| 文档 | 说明 |
|------|------|
| [开发环境设置](./zh/guides/development.md) | 开发环境配置 |
| [贡献指南](./zh/guides/contributing.md) | 如何贡献代码 |
| [分支策略](./zh/guides/branching-strategy.md) | Git 分支工作流 |
| [测试计划](./zh/guides/test-plan.md) | 测试计划和发布通道 |
| [国际化指南](./zh/guides/i18n.md) | 国际化开发指南 |
| [日志使用指南](./zh/guides/logging.md) | 如何使用日志服务 |
| [中间件开发](./zh/guides/middleware.md) | 如何编写中间件 |
| [记忆功能](./zh/guides/memory.md) | 记忆功能使用指南 |
| [赞助信息](./zh/guides/sponsor.md) | 赞助相关信息 |
### 参考 (References)
| 文档 | 说明 |
|------|------|
| [消息系统](./zh/references/message-system.md) | 消息系统架构和 API |
| [数据库结构](./zh/references/database.md) | 数据库表结构 |
| [服务](./zh/references/services.md) | 服务层文档 (KnowledgeService) |
| [代码执行](./zh/references/code-execution.md) | 代码执行功能 |
| [应用升级配置](./zh/references/app-upgrade.md) | 应用升级配置 |
| [CodeBlockView 组件](./zh/references/components/code-block-view.md) | 代码块视图组件 |
| [图像预览组件](./zh/references/components/image-preview.md) | 图像预览组件 |
---
## Missing Translations / 缺少翻译
The following documents are only available in Chinese and need English translations:
以下文档仅有中文版本,需要英文翻译:
- `guides/contributing.md`
- `guides/memory.md`
- `guides/middleware.md`
- `guides/sponsor.md`
- `references/message-system.md`
- `references/database.md`
- `references/services.md`
- `references/code-execution.md`

View File

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 150 KiB

View File

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

View File

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

View File

Before

Width:  |  Height:  |  Size: 563 KiB

After

Width:  |  Height:  |  Size: 563 KiB

View File

@@ -16,7 +16,7 @@ Cherry Studio implements a structured branching strategy to maintain code qualit
- Only accepts documentation updates and bug fixes
- Thoroughly tested before production deployment
For details about the `testplan` branch used in the Test Plan, please refer to the [Test Plan](testplan-en.md).
For details about the `testplan` branch used in the Test Plan, please refer to the [Test Plan](./test-plan.md).
## Contributing Branches

View File

@@ -18,13 +18,13 @@ yarn
### Setup Node.js
Download and install [Node.js v20.x.x](https://nodejs.org/en/download)
Download and install [Node.js v22.x.x](https://nodejs.org/en/download)
### Setup Yarn
```bash
corepack enable
corepack prepare yarn@4.6.0 --activate
corepack prepare yarn@4.9.1 --activate
```
### Install Dependencies

View File

@@ -18,11 +18,11 @@ The plugin has already been configured in the project — simply install it to g
### Demo
![demo-1](./.assets.how-to-i18n/demo-1.png)
![demo-1](../../assets/images/i18n/demo-1.png)
![demo-2](./.assets.how-to-i18n/demo-2.png)
![demo-2](../../assets/images/i18n/demo-2.png)
![demo-3](./.assets.how-to-i18n/demo-3.png)
![demo-3](../../assets/images/i18n/demo-3.png)
## i18n Conventions

View File

@@ -11,13 +11,15 @@ The Test Plan is divided into the RC channel and the Beta channel, with the foll
Users can enable the "Test Plan" and select the version channel in the software's `Settings` > `About`. Please note that the versions in the "Test Plan" cannot guarantee data consistency, so be sure to back up your data before using them.
After enabling the RC channel or Beta channel, if a stable version is released, users will still be upgraded to the stable version.
Users are welcome to submit issues or provide feedback through other channels for any bugs encountered during testing. Your feedback is very important to us.
## Developer Guide
### Participating in the Test Plan
Developers should submit `PRs` according to the [Contributor Guide](../CONTRIBUTING.md) (and ensure the target branch is `main`). The repository maintainers will evaluate whether the `PR` should be included in the Test Plan based on factors such as the impact of the feature on the application, its importance, and whether broader testing is needed.
Developers should submit `PRs` according to the [Contributor Guide](../../CONTRIBUTING.md) (and ensure the target branch is `main`). The repository maintainers will evaluate whether the `PR` should be included in the Test Plan based on factors such as the impact of the feature on the application, its importance, and whether broader testing is needed.
If the `PR` is added to the Test Plan, the repository maintainers will:

View File

@@ -0,0 +1,430 @@
# Update Configuration System Design Document
## Background
Currently, AppUpdater directly queries the GitHub API to retrieve beta and rc update information. To support users in China, we need to fetch a static JSON configuration file from GitHub/GitCode based on IP geolocation, which contains update URLs for all channels.
## Design Goals
1. Support different configuration sources based on IP geolocation (GitHub/GitCode)
2. Support version compatibility control (e.g., users below v1.x must upgrade to v1.7.0 before upgrading to v2.0)
3. Easy to extend, supporting future multi-major-version upgrade paths (v1.6 → v1.7 → v2.0 → v2.8 → v3.0)
4. Maintain compatibility with existing electron-updater mechanism
## Current Version Strategy
- **v1.7.x** is the last version of the 1.x series
- Users **below v1.7.0** must first upgrade to v1.7.0 (or higher 1.7.x version)
- Users **v1.7.0 and above** can directly upgrade to v2.x.x
## Automation Workflow
The `x-files/app-upgrade-config/app-upgrade-config.json` file is synchronized by the [`Update App Upgrade Config`](../../.github/workflows/update-app-upgrade-config.yml) workflow. The workflow runs the [`scripts/update-app-upgrade-config.ts`](../../scripts/update-app-upgrade-config.ts) helper so that every release tag automatically updates the JSON in `x-files/app-upgrade-config`.
### Trigger Conditions
- **Release events (`release: released/prereleased`)**
- Draft releases are ignored.
- When GitHub marks the release as _prerelease_, the tag must include `-beta`/`-rc` (with optional numeric suffix). Otherwise the workflow exits early.
- When GitHub marks the release as stable, the tag must match the latest release returned by the GitHub API. This prevents out-of-order updates when publishing historical tags.
- If the guard clauses pass, the version is tagged as `latest` or `beta/rc` based on its semantic suffix and propagated to the script through the `IS_PRERELEASE` flag.
- **Manual dispatch (`workflow_dispatch`)**
- Required input: `tag` (e.g., `v2.0.1`). Optional input: `is_prerelease` (defaults to `false`).
- When `is_prerelease=true`, the tag must carry a beta/rc suffix, mirroring the automatic validation.
- Manual runs still download the latest release metadata so that the workflow knows whether the tag represents the newest stable version (for documentation inside the PR body).
### Workflow Steps
1. **Guard + metadata preparation** the `Check if should proceed` and `Prepare metadata` steps compute the target tag, prerelease flag, whether the tag is the newest release, and a `safe_tag` slug used for branch names. When any rule fails, the workflow stops without touching the config.
2. **Checkout source branches** the default branch is checked out into `main/`, while the long-lived `x-files/app-upgrade-config` branch lives in `cs/`. All modifications happen in the latter directory.
3. **Install toolchain** Node.js 22, Corepack, and frozen Yarn dependencies are installed inside `main/`.
4. **Run the update script** `yarn tsx scripts/update-app-upgrade-config.ts --tag <tag> --config ../cs/app-upgrade-config.json --is-prerelease <flag>` updates the JSON in-place.
- The script normalizes the tag (e.g., strips `v` prefix), detects the release channel (`latest`, `rc`, `beta`), and loads segment rules from `config/app-upgrade-segments.json`.
- It validates that prerelease flags and semantic suffixes agree, enforces locked segments, builds mirror feed URLs, and performs release-availability checks (GitHub HEAD request for every channel; GitCode GET for latest channels, falling back to `https://releases.cherry-ai.com` when gitcode is delayed).
- After updating the relevant channel entry, the script rewrites the config with semver-sort order and a new `lastUpdated` timestamp.
5. **Detect changes + create PR** if `cs/app-upgrade-config.json` changed, the workflow opens a PR `chore/update-app-upgrade-config/<safe_tag>` against `x-files/app-upgrade-config` with a commit message `🤖 chore: sync app-upgrade-config for <tag>`. Otherwise it logs that no update is required.
### Manual Trigger Guide
1. Open the Cherry Studio repository on GitHub → **Actions** tab → select **Update App Upgrade Config**.
2. Click **Run workflow**, choose the default branch (usually `main`), and fill in the `tag` input (e.g., `v2.1.0`).
3. Toggle `is_prerelease` only when the tag carries a prerelease suffix (`-beta`, `-rc`). Leave it unchecked for stable releases.
4. Start the run and wait for it to finish. Check the generated PR in the `x-files/app-upgrade-config` branch, verify the diff in `app-upgrade-config.json`, and merge once validated.
## JSON Configuration File Format
### File Location
- **GitHub**: `https://raw.githubusercontent.com/CherryHQ/cherry-studio/refs/heads/x-files/app-upgrade-config/app-upgrade-config.json`
- **GitCode**: `https://gitcode.com/CherryHQ/cherry-studio/raw/x-files/app-upgrade-config/app-upgrade-config.json`
**Note**: Both mirrors provide the same configuration file hosted on the `x-files/app-upgrade-config` branch. The client automatically selects the optimal mirror based on IP geolocation.
### Configuration Structure (Current Implementation)
```json
{
"lastUpdated": "2025-01-05T00:00:00Z",
"versions": {
"1.6.7": {
"minCompatibleVersion": "1.0.0",
"description": "Last stable v1.7.x release - required intermediate version for users below v1.7",
"channels": {
"latest": {
"version": "1.6.7",
"feedUrls": {
"github": "https://github.com/CherryHQ/cherry-studio/releases/download/v1.6.7",
"gitcode": "https://gitcode.com/CherryHQ/cherry-studio/releases/download/v1.6.7"
}
},
"rc": {
"version": "1.6.0-rc.5",
"feedUrls": {
"github": "https://github.com/CherryHQ/cherry-studio/releases/download/v1.6.0-rc.5",
"gitcode": "https://github.com/CherryHQ/cherry-studio/releases/download/v1.6.0-rc.5"
}
},
"beta": {
"version": "1.6.7-beta.3",
"feedUrls": {
"github": "https://github.com/CherryHQ/cherry-studio/releases/download/v1.7.0-beta.3",
"gitcode": "https://github.com/CherryHQ/cherry-studio/releases/download/v1.7.0-beta.3"
}
}
}
},
"2.0.0": {
"minCompatibleVersion": "1.7.0",
"description": "Major release v2.0 - required intermediate version for v2.x upgrades",
"channels": {
"latest": null,
"rc": null,
"beta": null
}
}
}
}
```
### Future Extension Example
When releasing v3.0, if users need to first upgrade to v2.8, you can add:
```json
{
"2.8.0": {
"minCompatibleVersion": "2.0.0",
"description": "Stable v2.8 - required for v3 upgrade",
"channels": {
"latest": {
"version": "2.8.0",
"feedUrls": {
"github": "https://github.com/CherryHQ/cherry-studio/releases/download/v2.8.0",
"gitcode": "https://gitcode.com/CherryHQ/cherry-studio/releases/download/v2.8.0"
}
},
"rc": null,
"beta": null
}
},
"3.0.0": {
"minCompatibleVersion": "2.8.0",
"description": "Major release v3.0",
"channels": {
"latest": {
"version": "3.0.0",
"feedUrls": {
"github": "https://github.com/CherryHQ/cherry-studio/releases/latest",
"gitcode": "https://gitcode.com/CherryHQ/cherry-studio/releases/latest"
}
},
"rc": {
"version": "3.0.0-rc.1",
"feedUrls": {
"github": "https://github.com/CherryHQ/cherry-studio/releases/download/v3.0.0-rc.1",
"gitcode": "https://gitcode.com/CherryHQ/cherry-studio/releases/download/v3.0.0-rc.1"
}
},
"beta": null
}
}
}
```
### Field Descriptions
- `lastUpdated`: Last update time of the configuration file (ISO 8601 format)
- `versions`: Version configuration object, key is the version number, sorted by semantic versioning
- `minCompatibleVersion`: Minimum compatible version that can upgrade to this version
- `description`: Version description
- `channels`: Update channel configuration
- `latest`: Stable release channel
- `rc`: Release Candidate channel
- `beta`: Beta testing channel
- Each channel contains:
- `version`: Version number for this channel
- `feedUrls`: Multi-mirror URL configuration
- `github`: electron-updater feed URL for GitHub mirror
- `gitcode`: electron-updater feed URL for GitCode mirror
- `metadata`: Stable mapping info for automation
- `segmentId`: ID from `config/app-upgrade-segments.json`
- `segmentType`: Optional flag (`legacy` | `breaking` | `latest`) for documentation/debugging
## TypeScript Type Definitions
```typescript
// Mirror enum
enum UpdateMirror {
GITHUB = 'github',
GITCODE = 'gitcode'
}
interface UpdateConfig {
lastUpdated: string
versions: {
[versionKey: string]: VersionConfig
}
}
interface VersionConfig {
minCompatibleVersion: string
description: string
channels: {
latest: ChannelConfig | null
rc: ChannelConfig | null
beta: ChannelConfig | null
}
metadata?: {
segmentId: string
segmentType?: 'legacy' | 'breaking' | 'latest'
}
}
interface ChannelConfig {
version: string
feedUrls: Record<UpdateMirror, string>
// Equivalent to:
// feedUrls: {
// github: string
// gitcode: string
// }
}
```
## Segment Metadata & Breaking Markers
- **Segment definitions** now live in `config/app-upgrade-segments.json`. Each segment describes a semantic-version range (or exact matches) plus metadata such as `segmentId`, `segmentType`, `minCompatibleVersion`, and per-channel feed URL templates.
- Each entry under `versions` carries a `metadata.segmentId`. This acts as the stable key that scripts use to decide which slot to update, even if the actual semantic version string changes.
- Mark major upgrade gateways (e.g., `2.0.0`) by giving the related segment a `segmentType: "breaking"` and (optionally) `lockedVersion`. This prevents automation from accidentally moving that entry when other 2.x builds ship.
- Adding another breaking hop (e.g., `3.0.0`) only requires defining a new segment in the JSON file; the automation will pick it up on the next run.
## Automation Workflow
Starting from this change, `.github/workflows/update-app-upgrade-config.yml` listens to GitHub release events (published + prerelease). The workflow:
1. Checks out the default branch (for scripts) and the `x-files/app-upgrade-config` branch (where the config is hosted).
2. Runs `yarn tsx scripts/update-app-upgrade-config.ts --tag <tag> --config ../cs/app-upgrade-config.json` to regenerate the config directly inside the `x-files/app-upgrade-config` working tree.
3. If the file changed, it opens a PR against `x-files/app-upgrade-config` via `peter-evans/create-pull-request`, with the generated diff limited to `app-upgrade-config.json`.
You can run the same script locally via `yarn update:upgrade-config --tag v2.1.6 --config ../cs/app-upgrade-config.json` (add `--dry-run` to preview) to reproduce or debug whatever the workflow does. Passing `--skip-release-checks` along with `--dry-run` lets you bypass the release-page existence check (useful when the GitHub/GitCode pages arent published yet). Running without `--config` continues to update the copy in your current working directory (main branch) for documentation purposes.
## Version Matching Logic
### Algorithm Flow
1. Get user's current version (`currentVersion`) and requested channel (`requestedChannel`)
2. Get all version numbers from configuration file, sort in descending order by semantic versioning
3. Iterate through the sorted version list:
- Check if `currentVersion >= minCompatibleVersion`
- Check if the requested `channel` exists and is not `null`
- If conditions are met, return the channel configuration
4. If no matching version is found, return `null`
### Pseudocode Implementation
```typescript
function findCompatibleVersion(
currentVersion: string,
requestedChannel: UpgradeChannel,
config: UpdateConfig
): ChannelConfig | null {
// Get all version numbers and sort in descending order
const versions = Object.keys(config.versions).sort(semver.rcompare)
for (const versionKey of versions) {
const versionConfig = config.versions[versionKey]
const channelConfig = versionConfig.channels[requestedChannel]
// Check version compatibility and channel availability
if (
semver.gte(currentVersion, versionConfig.minCompatibleVersion) &&
channelConfig !== null
) {
return channelConfig
}
}
return null // No compatible version found
}
```
## Upgrade Path Examples
### Scenario 1: v1.6.5 User Upgrade (Below 1.7)
- **Current Version**: 1.6.5
- **Requested Channel**: latest
- **Match Result**: 1.7.0
- **Reason**: 1.6.5 >= 0.0.0 (satisfies 1.7.0's minCompatibleVersion), but doesn't satisfy 2.0.0's minCompatibleVersion (1.7.0)
- **Action**: Prompt user to upgrade to 1.7.0, which is the required intermediate version for v2.x upgrade
### Scenario 2: v1.6.5 User Requests rc/beta
- **Current Version**: 1.6.5
- **Requested Channel**: rc or beta
- **Match Result**: 1.7.0 (latest)
- **Reason**: 1.7.0 version doesn't provide rc/beta channels (values are null)
- **Action**: Upgrade to 1.7.0 stable version
### Scenario 3: v1.7.0 User Upgrades to Latest
- **Current Version**: 1.7.0
- **Requested Channel**: latest
- **Match Result**: 2.0.0
- **Reason**: 1.7.0 >= 1.7.0 (satisfies 2.0.0's minCompatibleVersion)
- **Action**: Directly upgrade to 2.0.0 (current latest stable version)
### Scenario 4: v1.7.2 User Upgrades to RC Version
- **Current Version**: 1.7.2
- **Requested Channel**: rc
- **Match Result**: 2.0.0-rc.1
- **Reason**: 1.7.2 >= 1.7.0 (satisfies 2.0.0's minCompatibleVersion), and rc channel exists
- **Action**: Upgrade to 2.0.0-rc.1
### Scenario 5: v1.7.0 User Upgrades to Beta Version
- **Current Version**: 1.7.0
- **Requested Channel**: beta
- **Match Result**: 2.0.0-beta.1
- **Reason**: 1.7.0 >= 1.7.0, and beta channel exists
- **Action**: Upgrade to 2.0.0-beta.1
### Scenario 6: v2.5.0 User Upgrade (Future)
Assuming v2.8.0 and v3.0.0 configurations have been added:
- **Current Version**: 2.5.0
- **Requested Channel**: latest
- **Match Result**: 2.8.0
- **Reason**: 2.5.0 >= 2.0.0 (satisfies 2.8.0's minCompatibleVersion), but doesn't satisfy 3.0.0's requirement
- **Action**: Prompt user to upgrade to 2.8.0, which is the required intermediate version for v3.x upgrade
## Code Changes
### Main Modifications
1. **New Methods**
- `_fetchUpdateConfig(ipCountry: string): Promise<UpdateConfig | null>` - Fetch configuration file based on IP
- `_findCompatibleChannel(currentVersion: string, channel: UpgradeChannel, config: UpdateConfig): ChannelConfig | null` - Find compatible channel configuration
2. **Modified Methods**
- `_getReleaseVersionFromGithub()` → Remove or refactor to `_getChannelFeedUrl()`
- `_setFeedUrl()` - Use new configuration system to replace existing logic
3. **New Type Definitions**
- `UpdateConfig`
- `VersionConfig`
- `ChannelConfig`
### Mirror Selection Logic
The client automatically selects the optimal mirror based on IP geolocation:
```typescript
private async _setFeedUrl() {
const currentVersion = app.getVersion()
const testPlan = configManager.getTestPlan()
const requestedChannel = testPlan ? this._getTestChannel() : UpgradeChannel.LATEST
// Determine mirror based on IP country
const ipCountry = await getIpCountry()
const mirror = ipCountry.toLowerCase() === 'cn' ? 'gitcode' : 'github'
// Fetch update config
const config = await this._fetchUpdateConfig(mirror)
if (config) {
const channelConfig = this._findCompatibleChannel(currentVersion, requestedChannel, config)
if (channelConfig) {
// Select feed URL from the corresponding mirror
const feedUrl = channelConfig.feedUrls[mirror]
this._setChannel(requestedChannel, feedUrl)
return
}
}
// Fallback logic
const defaultFeedUrl = mirror === 'gitcode'
? FeedUrl.PRODUCTION
: FeedUrl.GITHUB_LATEST
this._setChannel(UpgradeChannel.LATEST, defaultFeedUrl)
}
private async _fetchUpdateConfig(mirror: 'github' | 'gitcode'): Promise<UpdateConfig | null> {
const configUrl = mirror === 'gitcode'
? UpdateConfigUrl.GITCODE
: UpdateConfigUrl.GITHUB
try {
const response = await net.fetch(configUrl, {
headers: {
'User-Agent': generateUserAgent(),
'Accept': 'application/json',
'X-Client-Id': configManager.getClientId()
}
})
return await response.json() as UpdateConfig
} catch (error) {
logger.error('Failed to fetch update config:', error)
return null
}
}
```
## Fallback and Error Handling Strategy
1. **Configuration file fetch failure**: Log error, return current version, don't offer updates
2. **No matching version**: Notify user that current version doesn't support automatic upgrade
3. **Network exception**: Cache last successfully fetched configuration (optional)
## GitHub Release Requirements
To support intermediate version upgrades, the following files need to be retained:
- **v1.7.0 release** and its latest*.yml files (as upgrade target for users below v1.7)
- Future intermediate versions (e.g., v2.8.0) need to retain corresponding release and latest*.yml files
- Complete installation packages for each version
### Currently Required Releases
| Version | Purpose | Must Retain |
|---------|---------|-------------|
| v1.7.0 | Upgrade target for users below 1.7 | ✅ Yes |
| v2.0.0-rc.1 | RC testing channel | ❌ Optional |
| v2.0.0-beta.1 | Beta testing channel | ❌ Optional |
| latest | Latest stable version (automatic) | ✅ Yes |
## Advantages
1. **Flexibility**: Supports arbitrarily complex upgrade paths
2. **Extensibility**: Adding new versions only requires adding new entries to the configuration file
3. **Maintainability**: Configuration is separated from code, allowing upgrade strategy adjustments without releasing new versions
4. **Multi-source support**: Automatically selects optimal configuration source based on geolocation
5. **Version control**: Enforces intermediate version upgrades, ensuring data migration and compatibility
## Future Extensions
- Support more granular version range control (e.g., `>=1.5.0 <1.8.0`)
- Support multi-step upgrade path hints (e.g., notify user needs 1.5 → 1.8 → 2.0)
- Support A/B testing and gradual rollout
- Support local caching and expiration strategy for configuration files

View File

@@ -85,7 +85,7 @@ Main responsibilities:
- **SvgPreview**: SVG image preview
- **GraphvizPreview**: Graphviz diagram preview
All special view components share a common architecture for consistent user experience and functionality. For detailed information about these components and their implementation, see [Image Preview Components Documentation](./ImagePreview-en.md).
All special view components share a common architecture for consistent user experience and functionality. For detailed information about these components and their implementation, see [Image Preview Components Documentation](./image-preview.md).
#### StatusBar

View File

@@ -192,4 +192,4 @@ Image Preview Components integrate seamlessly with CodeBlockView:
- Shared state management
- Responsive layout adaptation
For more information about the overall CodeBlockView architecture, see [CodeBlockView Documentation](./CodeBlockView-en.md).
For more information about the overall CodeBlockView architecture, see [CodeBlockView Documentation](./code-block-view.md).

View File

@@ -1,3 +0,0 @@
# 消息的生命周期
![image](./message-lifecycle.png)

View File

@@ -1,11 +0,0 @@
# 数据库设置字段
此文档包含部分字段的数据类型说明。
## 字段
| 字段名 | 类型 | 说明 |
| ------------------------------ | ------------------------------ | ------------ |
| `translate:target:language` | `LanguageCode` | 翻译目标语言 |
| `translate:source:language` | `LanguageCode` | 翻译源语言 |
| `translate:bidirectional:pair` | `[LanguageCode, LanguageCode]` | 双向翻译对 |

View File

@@ -1,127 +0,0 @@
# messageBlock.ts 使用指南
该文件定义了用于管理应用程序中所有 `MessageBlock` 实体的 Redux Slice。它使用 Redux Toolkit 的 `createSlice``createEntityAdapter` 来高效地处理规范化的状态,并提供了一系列 actions 和 selectors 用于与消息块数据交互。
## 核心目标
- **状态管理**: 集中管理所有 `MessageBlock` 的状态。`MessageBlock` 代表消息中的不同内容单元(如文本、代码、图片、引用等)。
- **规范化**: 使用 `createEntityAdapter``MessageBlock` 数据存储在规范化的结构中(`{ ids: [], entities: {} }`),这有助于提高性能和简化更新逻辑。
- **可预测性**: 提供明确的 actions 来修改状态,并通过 selectors 安全地访问状态。
## 关键概念
- **Slice (`createSlice`)**: Redux Toolkit 的核心 API用于创建包含 reducer 逻辑、action creators 和初始状态的 Redux 模块。
- **Entity Adapter (`createEntityAdapter`)**: Redux Toolkit 提供的工具,用于简化对规范化数据的 CRUD创建、读取、更新、删除操作。它会自动生成 reducer 函数和 selectors。
- **Selectors**: 用于从 Redux store 中派生和计算数据的函数。Selectors 可以被记忆化memoized以提高性能。
## State 结构
`messageBlocks` slice 的状态结构由 `createEntityAdapter` 定义,大致如下:
```typescript
{
ids: string[]; // 存储所有 MessageBlock ID 的有序列表
entities: { [id: string]: MessageBlock }; // 按 ID 存储 MessageBlock 对象的字典
loadingState: 'idle' | 'loading' | 'succeeded' | 'failed'; // (可选) 其他状态,如加载状态
error: string | null; // (可选) 错误信息
}
```
## Actions
该 slice 导出以下 actions (由 `createSlice``createEntityAdapter` 自动生成或自定义)
- **`upsertOneBlock(payload: MessageBlock)`**:
- 添加一个新的 `MessageBlock` 或更新一个已存在的 `MessageBlock`。如果 payload 中的 `id` 已存在,则执行更新;否则执行插入。
- **`upsertManyBlocks(payload: MessageBlock[])`**:
- 添加或更新多个 `MessageBlock`。常用于批量加载数据(例如,加载一个 Topic 的所有消息块)。
- **`removeOneBlock(payload: string)`**:
- 根据提供的 `id` (payload) 移除单个 `MessageBlock`
- **`removeManyBlocks(payload: string[])`**:
- 根据提供的 `id` 数组 (payload) 移除多个 `MessageBlock`。常用于删除消息或清空 Topic 时清理相关的块。
- **`removeAllBlocks()`**:
- 移除 state 中的所有 `MessageBlock` 实体。
- **`updateOneBlock(payload: { id: string; changes: Partial<MessageBlock> })`**:
- 更新一个已存在的 `MessageBlock``payload` 需要包含块的 `id` 和一个包含要更改的字段的 `changes` 对象。
- **`setMessageBlocksLoading(payload: 'idle' | 'loading')`**:
- (自定义) 设置 `loadingState` 属性。
- **`setMessageBlocksError(payload: string)`**:
- (自定义) 设置 `loadingState``'failed'` 并记录错误信息。
**使用示例 (在 Thunk 或其他 Dispatch 的地方):**
```typescript
import { upsertOneBlock, removeManyBlocks, updateOneBlock } from './messageBlock'
import store from './store' // 假设这是你的 Redux store 实例
// 添加或更新一个块
const newBlock: MessageBlock = {
/* ... block data ... */
}
store.dispatch(upsertOneBlock(newBlock))
// 更新一个块的内容
store.dispatch(updateOneBlock({ id: blockId, changes: { content: 'New content' } }))
// 删除多个块
const blockIdsToRemove = ['id1', 'id2']
store.dispatch(removeManyBlocks(blockIdsToRemove))
```
## Selectors
该 slice 导出由 `createEntityAdapter` 生成的基础 selectors并通过 `messageBlocksSelectors` 对象访问:
- **`messageBlocksSelectors.selectIds(state: RootState): string[]`**: 返回包含所有块 ID 的数组。
- **`messageBlocksSelectors.selectEntities(state: RootState): { [id: string]: MessageBlock }`**: 返回块 ID 到块对象的映射字典。
- **`messageBlocksSelectors.selectAll(state: RootState): MessageBlock[]`**: 返回包含所有块对象的数组。
- **`messageBlocksSelectors.selectTotal(state: RootState): number`**: 返回块的总数。
- **`messageBlocksSelectors.selectById(state: RootState, id: string): MessageBlock | undefined`**: 根据 ID 返回单个块对象,如果找不到则返回 `undefined`
**此外,还提供了一个自定义的、记忆化的 selector**
- **`selectFormattedCitationsByBlockId(state: RootState, blockId: string | undefined): Citation[]`**:
- 接收一个 `blockId`
- 如果该 ID 对应的块是 `CITATION` 类型,则提取并格式化其包含的引用信息(来自网页搜索、知识库等),进行去重和重新编号,最后返回一个 `Citation[]` 数组,用于在 UI 中显示。
- 如果块不存在或类型不匹配,返回空数组 `[]`
- 这个 selector 封装了处理不同引用来源Gemini, OpenAI, OpenRouter, Zhipu 等)的复杂逻辑。
**使用示例 (在 React 组件或 `useSelector` 中):**
```typescript
import { useSelector } from 'react-redux'
import { messageBlocksSelectors, selectFormattedCitationsByBlockId } from './messageBlock'
import type { RootState } from './store'
// 获取所有块
const allBlocks = useSelector(messageBlocksSelectors.selectAll)
// 获取特定 ID 的块
const specificBlock = useSelector((state: RootState) => messageBlocksSelectors.selectById(state, someBlockId))
// 获取特定引用块格式化后的引用列表
const formattedCitations = useSelector((state: RootState) => selectFormattedCitationsByBlockId(state, citationBlockId))
// 在组件中使用引用数据
// {formattedCitations.map(citation => ...)}
```
## 集成
`messageBlock.ts` slice 通常与 `messageThunk.ts` 中的 Thunks 紧密协作。Thunks 负责处理异步逻辑(如 API 调用、数据库操作),并在需要时 dispatch `messageBlock` slice 的 actions 来更新状态。例如,当 `messageThunk` 接收到流式响应时,它会 dispatch `upsertOneBlock``updateOneBlock` 来实时更新对应的 `MessageBlock`。同样,删除消息的 Thunk 会 dispatch `removeManyBlocks`
理解 `messageBlock.ts` 的职责是管理**状态本身**,而 `messageThunk.ts` 负责**触发状态变更**的异步流程,这对于维护清晰的应用架构至关重要。

View File

@@ -1,105 +0,0 @@
# messageThunk.ts 使用指南
该文件包含用于管理应用程序中消息流、处理助手交互以及同步 Redux 状态与 IndexedDB 数据库的核心 Thunk Action Creators。主要围绕 `Message``MessageBlock` 对象进行操作。
## 核心功能
1. **发送/接收消息**: 处理用户消息的发送,触发助手响应,并流式处理返回的数据,将其解析为不同的 `MessageBlock`
2. **状态管理**: 确保 Redux store 中的消息和消息块状态与 IndexedDB 中的持久化数据保持一致。
3. **消息操作**: 提供删除、重发、重新生成、编辑后重发、追加响应、克隆等消息生命周期管理功能。
4. **Block 处理**: 动态创建、更新和保存各种类型的 `MessageBlock`(文本、思考过程、工具调用、引用、图片、错误、翻译等)。
## 主要 Thunks
以下是一些关键的 Thunk 函数及其用途:
1. **`sendMessage(userMessage, userMessageBlocks, assistant, topicId)`**
- **用途**: 发送一条新的用户消息。
- **流程**:
- 保存用户消息 (`userMessage`) 及其块 (`userMessageBlocks`) 到 Redux 和 DB。
- 检查 `@mentions` 以确定是单模型响应还是多模型响应。
- 创建助手消息(们)的存根 (Stub)。
- 将存根添加到 Redux 和 DB。
- 将核心处理逻辑 `fetchAndProcessAssistantResponseImpl` 添加到该 `topicId` 的队列中以获取实际响应。
- **Block 相关**: 主要处理用户消息的初始 `MessageBlock` 保存。
2. **`fetchAndProcessAssistantResponseImpl(dispatch, getState, topicId, assistant, assistantMessage)`**
- **用途**: (内部函数) 获取并处理单个助手响应的核心逻辑,被 `sendMessage`, `resend...`, `regenerate...`, `append...` 等调用。
- **流程**:
- 设置 Topic 加载状态。
- 准备上下文消息。
- 调用 `fetchChatCompletion` API 服务。
- 使用 `createStreamProcessor` 处理流式响应。
- 通过各种回调 (`onTextChunk`, `onThinkingChunk`, `onToolCallComplete`, `onImageGenerated`, `onError`, `onComplete` 等) 处理不同类型的事件。
- **Block 相关**:
- 根据流事件创建初始 `UNKNOWN` 块。
- 实时创建和更新 `MAIN_TEXT``THINKING` 块,使用 `throttledBlockUpdate``throttledBlockDbUpdate` 进行节流更新。
- 创建 `TOOL`, `CITATION`, `IMAGE`, `ERROR` 等类型的块。
- 在事件完成时(如 `onTextComplete`, `onToolCallComplete`)将块状态标记为 `SUCCESS``ERROR`,并使用 `saveUpdatedBlockToDB` 保存最终状态。
- 使用 `handleBlockTransition` 管理非流式块(如 `TOOL`, `CITATION`)的添加和状态更新。
3. **`loadTopicMessagesThunk(topicId, forceReload)`**
- **用途**: 从数据库加载指定主题的所有消息及其关联的 `MessageBlock`
- **流程**:
- 从 DB 获取 `Topic` 及其 `messages` 列表。
- 根据消息 ID 列表从 DB 获取所有相关的 `MessageBlock`
- 使用 `upsertManyBlocks` 将块更新到 Redux。
- 将消息更新到 Redux。
- **Block 相关**: 负责将持久化的 `MessageBlock` 加载到 Redux 状态。
4. **删除 Thunks**
- `deleteSingleMessageThunk(topicId, messageId)`: 删除单个消息及其所有 `MessageBlock`
- `deleteMessageGroupThunk(topicId, askId)`: 删除一个用户消息及其所有相关的助手响应消息和它们的所有 `MessageBlock`
- `clearTopicMessagesThunk(topicId)`: 清空主题下的所有消息及其所有 `MessageBlock`
- **Block 相关**: 从 Redux 和 DB 中移除指定的 `MessageBlock`
5. **重发/重新生成 Thunks**
- `resendMessageThunk(topicId, userMessageToResend, assistant)`: 重发用户消息。会重置(清空 Block 并标记为 PENDING所有与该用户消息关联的助手响应然后重新请求生成。
- `resendUserMessageWithEditThunk(topicId, originalMessage, mainTextBlockId, editedContent, assistant)`: 用户编辑消息内容后重发。先更新用户消息的 `MAIN_TEXT` 块内容,然后调用 `resendMessageThunk`
- `regenerateAssistantResponseThunk(topicId, assistantMessageToRegenerate, assistant)`: 重新生成单个助手响应。重置该助手消息(清空 Block 并标记为 PENDING然后重新请求生成。
- **Block 相关**: 删除旧的 `MessageBlock`,并在重新生成过程中创建新的 `MessageBlock`
6. **`appendAssistantResponseThunk(topicId, existingAssistantMessageId, newModel, assistant)`**
- **用途**: 在已有的对话上下文中,针对同一个用户问题,使用新选择的模型追加一个新的助手响应。
- **流程**:
- 找到现有助手消息以获取原始 `askId`
- 创建使用 `newModel` 的新助手消息存根(使用相同的 `askId`)。
- 添加新存根到 Redux 和 DB。
-`fetchAndProcessAssistantResponseImpl` 添加到队列以生成新响应。
- **Block 相关**: 为新的助手响应创建全新的 `MessageBlock`
7. **`cloneMessagesToNewTopicThunk(sourceTopicId, branchPointIndex, newTopic)`**
- **用途**: 将源主题的部分消息(及其 Block克隆到一个**已存在**的新主题中。
- **流程**:
- 复制指定索引前的消息。
- 为所有克隆的消息和 Block 生成新的 UUID。
- 正确映射克隆消息之间的 `askId` 关系。
- 复制 `MessageBlock` 内容,更新其 `messageId` 指向新的消息 ID。
- 更新文件引用计数(如果 Block 是文件或图片)。
- 将克隆的消息和 Block 保存到新主题的 Redux 状态和 DB 中。
- **Block 相关**: 创建 `MessageBlock` 的副本,并更新其 ID 和 `messageId`
8. **`initiateTranslationThunk(messageId, topicId, targetLanguage, sourceBlockId?, sourceLanguage?)`**
- **用途**: 为指定消息启动翻译流程,创建一个初始的 `TRANSLATION` 类型的 `MessageBlock`
- **流程**:
- 创建一个状态为 `STREAMING``TranslationMessageBlock`
- 将其添加到 Redux 和 DB。
- 更新原消息的 `blocks` 列表以包含新的翻译块 ID。
- **Block 相关**: 创建并保存一个占位的 `TranslationMessageBlock`。实际翻译内容的获取和填充需要后续步骤。
## 内部机制和注意事项
- **数据库交互**: 通过 `saveMessageAndBlocksToDB`, `updateExistingMessageAndBlocksInDB`, `saveUpdatesToDB`, `saveUpdatedBlockToDB`, `throttledBlockDbUpdate` 等辅助函数与 IndexedDB (`db`) 交互,确保数据持久化。
- **状态同步**: Thunks 负责协调 Redux Store 和 IndexedDB 之间的数据一致性。
- **队列 (`getTopicQueue`)**: 使用 `AsyncQueue` 确保对同一主题的操作(尤其是 API 请求)按顺序执行,避免竞态条件。
- **节流 (`throttle`)**: 对流式响应中频繁的 Block 更新(文本、思考)使用 `lodash.throttle` 优化性能,减少 Redux dispatch 和 DB 写入次数。
- **错误处理**: `fetchAndProcessAssistantResponseImpl` 内的回调函数(特别是 `onError`)处理流处理和 API 调用中可能出现的错误,并创建 `ERROR` 类型的 `MessageBlock`
开发者在使用这些 Thunks 时,通常需要提供 `dispatch`, `getState` (由 Redux Thunk 中间件注入),以及如 `topicId`, `assistant` 配置对象, 相关的 `Message``MessageBlock` 对象/ID 等参数。理解每个 Thunk 的职责和它如何影响消息及块的状态至关重要。

View File

@@ -1,156 +0,0 @@
# useMessageOperations.ts 使用指南
该文件定义了一个名为 `useMessageOperations` 的自定义 React Hook。这个 Hook 的主要目的是为 React 组件提供一个便捷的接口用于执行与特定主题Topic相关的各种消息操作。它封装了调用 Redux Thunks (`messageThunk.ts`) 和 Actions (`newMessage.ts`, `messageBlock.ts`) 的逻辑,简化了组件与消息数据交互的代码。
## 核心目标
- **封装**: 将复杂的消息操作逻辑(如删除、重发、重新生成、编辑、翻译等)封装在易于使用的函数中。
- **简化**: 让组件可以直接调用这些操作函数,而无需直接与 Redux `dispatch` 或 Thunks 交互。
- **上下文关联**: 所有操作都与传入的 `topic` 对象相关联,确保操作作用于正确的主题。
## 如何使用
在你的 React 函数组件中,导入并调用 `useMessageOperations` Hook并传入当前活动的 `Topic` 对象。
```typescript
import React from 'react';
import { useMessageOperations } from '@renderer/hooks/useMessageOperations';
import type { Topic, Message, Assistant, Model } from '@renderer/types';
interface MyComponentProps {
currentTopic: Topic;
currentAssistant: Assistant;
}
function MyComponent({ currentTopic, currentAssistant }: MyComponentProps) {
const {
deleteMessage,
resendMessage,
regenerateAssistantMessage,
appendAssistantResponse,
getTranslationUpdater,
createTopicBranch,
// ... 其他操作函数
} = useMessageOperations(currentTopic);
const handleDelete = (messageId: string) => {
deleteMessage(messageId);
};
const handleResend = (message: Message) => {
resendMessage(message, currentAssistant);
};
const handleAppend = (existingMsg: Message, newModel: Model) => {
appendAssistantResponse(existingMsg, newModel, currentAssistant);
}
// ... 在组件中使用其他操作函数
return (
<div>
{/* Component UI */}
<button onClick={() => handleDelete('some-message-id')}>Delete Message</button>
{/* ... */}
</div>
);
}
```
## 返回值
`useMessageOperations(topic)` Hook 返回一个包含以下函数和值的对象:
- **`deleteMessage(id: string)`**:
- 删除指定 `id` 的单个消息。
- 内部调用 `deleteSingleMessageThunk`
- **`deleteGroupMessages(askId: string)`**:
- 删除与指定 `askId` 相关联的一组消息(通常是用户提问及其所有助手回答)。
- 内部调用 `deleteMessageGroupThunk`
- **`editMessage(messageId: string, updates: Partial<Message>)`**:
- 更新指定 `messageId` 的消息的部分属性。
- **注意**: 目前主要用于更新 Redux 状态
- 内部调用 `newMessagesActions.updateMessage`
- **`resendMessage(message: Message, assistant: Assistant)`**:
- 重新发送指定的用户消息 (`message`),这将触发其所有关联助手响应的重新生成。
- 内部调用 `resendMessageThunk`
- **`resendUserMessageWithEdit(message: Message, editedContent: string, assistant: Assistant)`**:
- 在用户消息的主要文本块被编辑后,重新发送该消息。
- 会先查找消息的 `MAIN_TEXT` 块 ID然后调用 `resendUserMessageWithEditThunk`
- **`clearTopicMessages(_topicId?: string)`**:
- 清除当前主题(或可选的指定 `_topicId`)下的所有消息。
- 内部调用 `clearTopicMessagesThunk`
- **`createNewContext()`**:
- 发出一个全局事件 (`EVENT_NAMES.NEW_CONTEXT`),通常用于通知 UI 清空显示,准备新的上下文。不直接修改 Redux 状态。
- **`displayCount`**:
- (非操作函数) 从 Redux store 中获取当前的 `displayCount` 值。
- **`pauseMessages()`**:
- 尝试中止当前主题中正在进行的消息生成(状态为 `processing``pending`)。
- 通过查找相关的 `askId` 并调用 `abortCompletion` 来实现。
- 同时会 dispatch `setTopicLoading` action 将加载状态设为 `false`
- **`resumeMessage(message: Message, assistant: Assistant)`**:
- 恢复/重新发送一个用户消息。目前实现为直接调用 `resendMessage`
- **`regenerateAssistantMessage(message: Message, assistant: Assistant)`**:
- 重新生成指定的**助手**消息 (`message`) 的响应。
- 内部调用 `regenerateAssistantResponseThunk`
- **`appendAssistantResponse(existingAssistantMessage: Message, newModel: Model, assistant: Assistant)`**:
- 针对 `existingAssistantMessage` 所回复的**同一用户提问**,使用 `newModel` 追加一个新的助手响应。
- 内部调用 `appendAssistantResponseThunk`
- **`getTranslationUpdater(messageId: string, targetLanguage: string, sourceBlockId?: string, sourceLanguage?: string)`**:
- **用途**: 获取一个用于逐步更新翻译块内容的函数。
- **流程**:
1. 内部调用 `initiateTranslationThunk` 来创建或获取一个 `TRANSLATION` 类型的 `MessageBlock`,并获取其 `blockId`
2. 返回一个**异步更新函数**。
- **返回的更新函数 `(accumulatedText: string, isComplete?: boolean) => void`**:
- 接收累积的翻译文本和完成状态。
- 调用 `updateOneBlock` 更新 Redux 中的翻译块内容和状态 (`STREAMING``SUCCESS`)。
- 调用 `throttledBlockDbUpdate` 将更新(节流地)保存到数据库。
- 如果初始化失败Thunk 返回 `undefined`),则此函数返回 `null`
- **`createTopicBranch(sourceTopicId: string, branchPointIndex: number, newTopic: Topic)`**:
- 创建一个主题分支,将 `sourceTopicId` 主题中 `branchPointIndex` 索引之前的消息克隆到 `newTopic` 中。
- **注意**: `newTopic` 对象必须是调用此函数**之前**已经创建并添加到 Redux 和数据库中的。
- 内部调用 `cloneMessagesToNewTopicThunk`
## 依赖
- **`topic: Topic`**: 必须传入当前操作上下文的主题对象。Hook 返回的操作函数将始终作用于这个主题的 `topic.id`
- **Redux `dispatch`**: Hook 内部使用 `useAppDispatch` 获取 `dispatch` 函数来调用 actions 和 thunks。
## 相关 Hooks
在同一文件中还定义了两个辅助 Hook
- **`useTopicMessages(topic: Topic)`**:
- 使用 `selectMessagesForTopic` selector 来获取并返回指定主题的消息列表。
- **`useTopicLoading(topic: Topic)`**:
- 使用 `selectNewTopicLoading` selector 来获取并返回指定主题的加载状态。
这些 Hook 可以与 `useMessageOperations` 结合使用,方便地在组件中获取消息数据、加载状态,并执行相关操作。

View File

@@ -34,7 +34,7 @@
</a>
</h1>
<p align="center">
<a href="https://github.com/CherryHQ/cherry-studio">English</a> | 中文 | <a href="https://cherry-ai.com">官方网站</a> | <a href="https://docs.cherry-ai.com/cherry-studio-wen-dang/zh-cn">文档</a> | <a href="./dev.md">开发</a> | <a href="https://github.com/CherryHQ/cherry-studio/issues">反馈</a><br>
<a href="https://github.com/CherryHQ/cherry-studio">English</a> | 中文 | <a href="https://cherry-ai.com">官方网站</a> | <a href="https://docs.cherry-ai.com/cherry-studio-wen-dang/zh-cn">文档</a> | <a href="./guides/development.md">开发</a> | <a href="https://github.com/CherryHQ/cherry-studio/issues">反馈</a><br>
</p>
<!-- 题头徽章组合 -->
@@ -70,7 +70,7 @@ Cherry Studio 是一款支持多个大语言模型LLM服务商的桌面客
👏 欢迎加入 [Telegram 群组](https://t.me/CherryStudioAI)[Discord](https://discord.gg/wez8HtpxqQ) | [QQ群(575014769)](https://qm.qq.com/q/lo0D4qVZKi)
❤️ 喜欢 Cherry Studio? 点亮小星星 🌟 或 [赞助开发者](sponsor.md)! ❤️
❤️ 喜欢 Cherry Studio? 点亮小星星 🌟 或 [赞助开发者](./guides/sponsor.md)! ❤️
# 📖 使用教程
@@ -181,7 +181,7 @@ https://docs.cherry-ai.com
6. **社区参与**:加入讨论并帮助用户
7. **推广使用**:宣传 Cherry Studio
参考[分支策略](branching-strategy-zh.md)了解贡献指南
参考[分支策略](./guides/branching-strategy.md)了解贡献指南
## 入门
@@ -190,7 +190,7 @@ https://docs.cherry-ai.com
3. **提交更改**:提交并推送您的更改
4. **打开 Pull Request**:描述您的更改和原因
有关更详细的指南,请参阅我们的 [贡献指南](CONTRIBUTING.zh.md)
有关更详细的指南,请参阅我们的 [贡献指南](./guides/contributing.md)
感谢您的支持和贡献!

View File

@@ -16,7 +16,7 @@ Cherry Studio 采用结构化的分支策略来维护代码质量并简化开发
- 只接受文档更新和 bug 修复
- 经过完整测试后可以发布到生产环境
关于测试计划所使用的`testplan`分支,请查阅[测试计划](testplan-zh.md)。
关于测试计划所使用的`testplan`分支,请查阅[测试计划](./test-plan.md)。
## 贡献分支

View File

@@ -1,6 +1,6 @@
# Cherry Studio 贡献者指南
[**English**](../CONTRIBUTING.md) | [**中文**](CONTRIBUTING.zh.md)
[**English**](../../../CONTRIBUTING.md) | **中文**
欢迎来到 Cherry Studio 的贡献者社区!我们致力于将 Cherry Studio 打造成一个长期提供价值的项目,并希望邀请更多的开发者加入我们的行列。无论您是经验丰富的开发者还是刚刚起步的初学者,您的贡献都将帮助我们更好地服务用户,提升软件质量。
@@ -24,7 +24,7 @@
## 开始之前
请确保阅读了[行为准则](../CODE_OF_CONDUCT.md)和[LICENSE](../LICENSE)。
请确保阅读了[行为准则](../../../CODE_OF_CONDUCT.md)和[LICENSE](../../../LICENSE)。
## 开始贡献
@@ -32,7 +32,7 @@
### 测试
未经测试的功能等同于不存在。为确保代码真正有效,应通过单元测试和功能测试覆盖相关流程。因此,在考虑贡献时,也请考虑可测试性。所有测试均可本地运行,无需依赖 CI。请参阅[开发者指南](dev.md#test)中的Test部分。
未经测试的功能等同于不存在。为确保代码真正有效,应通过单元测试和功能测试覆盖相关流程。因此,在考虑贡献时,也请考虑可测试性。所有测试均可本地运行,无需依赖 CI。请参阅[开发者指南](./development.md#test)中的"Test"部分。
### 拉取请求的自动化测试
@@ -60,16 +60,37 @@ git commit --signoff -m "Your commit message"
### 获取代码审查/合并
维护者在此帮助您在合理时间内实现您的用例。他们会尽力在合理时间内审查您的代码并提供建设性反馈。但如果您在审查过程中受阻,或认为您的 Pull Request 未得到应有的关注,请通过 Issue 中的评论或者[社群](README.zh.md#-community)联系我们
维护者在此帮助您在合理时间内实现您的用例。他们会尽力在合理时间内审查您的代码并提供建设性反馈。但如果您在审查过程中受阻,或认为您的 Pull Request 未得到应有的关注,请通过 Issue 中的评论或者[社群](../README.md#-community)联系我们
### 参与测试计划
测试计划旨在为用户提供更稳定的应用体验和更快的迭代速度,详细情况请参阅[测试计划](testplan-zh.md)。
测试计划旨在为用户提供更稳定的应用体验和更快的迭代速度,详细情况请参阅[测试计划](./test-plan.md)。
### 其他建议
- **联系开发者**:在提交 PR 之前,您可以先和开发者进行联系,共同探讨或者获取帮助。
- **成为核心开发者**:如果您能够稳定为项目贡献,恭喜您可以成为项目核心开发者,获取到项目成员身份。请查看我们的[成员指南](https://github.com/CherryHQ/community/blob/main/membership.md)
## 重要贡献指南与关注点
在提交 Pull Request 之前,请务必阅读以下关键信息:
### 🚫 暂时限制涉及数据更改的功能性 PR
**目前,我们不接受涉及 Redux 数据模型或 IndexedDB schema 变更的功能性 Pull Request。**
我们的核心团队目前正专注于涉及这些数据结构的关键架构更新和基础工作。为确保在此期间的稳定性与专注,此类贡献将暂时由内部进行管理。
* **需要更改 Redux 状态结构或 IndexedDB schema 的 PR 将会被关闭。**
* **此限制是临时性的,并将在 `v2.0.0` 版本发布后解除。** 您可以通过 Issue [#10162](https://github.com/CherryHQ/cherry-studio/pull/10162) 跟踪 `v2.0.0` 的进展及相关讨论。
我们非常鼓励以下类型的贡献:
* 错误修复 🐞
* 性能改进 🚀
* 文档更新 📚
* 不改变 Redux 数据模型或 IndexedDB schema 的功能例如UI 增强、新组件、小型重构)。✨
感谢您在此重要开发阶段的理解与持续支持。谢谢!
## 联系我们

View File

@@ -0,0 +1,73 @@
# 🖥️ Develop
## IDE Setup
- Editor: [Cursor](https://www.cursor.com/), etc. Any VS Code compatible editor.
- Linter: [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)
- Formatter: [Biome](https://marketplace.visualstudio.com/items?itemName=biomejs.biome)
## Project Setup
### Install
```bash
yarn
```
### Development
### Setup Node.js
Download and install [Node.js v22.x.x](https://nodejs.org/en/download)
### Setup Yarn
```bash
corepack enable
corepack prepare yarn@4.9.1 --activate
```
### Install Dependencies
```bash
yarn install
```
### ENV
```bash
copy .env.example .env
```
### Start
```bash
yarn dev
```
### Debug
```bash
yarn debug
```
Then input chrome://inspect in browser
### Test
```bash
yarn test
```
### Build
```bash
# For windows
$ yarn build:win
# For macOS
$ yarn build:mac
# For Linux
$ yarn build:linux
```

View File

@@ -15,11 +15,11 @@ i18n ally是一个强大的VSCode插件它能在开发阶段提供实时反
### 效果展示
![demo-1](./.assets.how-to-i18n/demo-1.png)
![demo-1](../../assets/images/i18n/demo-1.png)
![demo-2](./.assets.how-to-i18n/demo-2.png)
![demo-2](../../assets/images/i18n/demo-2.png)
![demo-3](./.assets.how-to-i18n/demo-3.png)
![demo-3](../../assets/images/i18n/demo-3.png)
## i18n 约定

View File

@@ -11,13 +11,15 @@
用户可以在软件的`设置`-`关于`中,开启“测试计划”并选择版本通道。请注意“测试计划”的版本无法保证数据的一致性,请使用前一定要备份数据。
用户选择RC版通道或Beta版通道后若发布了正式版仍旧会升级到正式版。
用户在测试过程中发现的BUG欢迎提交issue或通过其他渠道反馈。用户的反馈对我们非常重要。
## 开发者指南
### 参与测试计划
开发者按照[贡献者指南](CONTRIBUTING.zh.md)要求正常提交`PR`并注意提交target为`main`)。仓库维护者会综合考虑(例如该功能对应用的影响程度,功能的重要性,是否需要更广泛的测试等),决定该`PR`是否应加入测试计划。
开发者按照[贡献者指南](./contributing.md)要求正常提交`PR`并注意提交target为`main`)。仓库维护者会综合考虑(例如该功能对应用的影响程度,功能的重要性,是否需要更广泛的测试等),决定该`PR`是否应加入测试计划。
若该`PR`加入测试计划,仓库维护者会做如下操作:

View File

@@ -0,0 +1,430 @@
# 更新配置系统设计文档
## 背景
当前 AppUpdater 直接请求 GitHub API 获取 beta 和 rc 的更新信息。为了支持国内用户,需要根据 IP 地理位置,分别从 GitHub/GitCode 获取一个固定的 JSON 配置文件,该文件包含所有渠道的更新地址。
## 设计目标
1. 支持根据 IP 地理位置选择不同的配置源GitHub/GitCode
2. 支持版本兼容性控制(如 v1.x 以下必须先升级到 v1.7.0 才能升级到 v2.0
3. 易于扩展支持未来多个主版本的升级路径v1.6 → v1.7 → v2.0 → v2.8 → v3.0
4. 保持与现有 electron-updater 机制的兼容性
## 当前版本策略
- **v1.7.x** 是 1.x 系列的最后版本
- **v1.7.0 以下**的用户必须先升级到 v1.7.0(或更高的 1.7.x 版本)
- **v1.7.0 及以上**的用户可以直接升级到 v2.x.x
## 自动化工作流
`x-files/app-upgrade-config/app-upgrade-config.json` 由 [`Update App Upgrade Config`](../../.github/workflows/update-app-upgrade-config.yml) workflow 自动同步。工作流会调用 [`scripts/update-app-upgrade-config.ts`](../../scripts/update-app-upgrade-config.ts) 脚本,根据指定 tag 更新 `x-files/app-upgrade-config` 分支上的配置文件。
### 触发条件
- **Release 事件(`release: released/prereleased`**
- Draft release 会被忽略。
- 当 GitHub 将 release 标记为 *prerelease*tag 必须包含 `-beta`/`-rc`(可带序号),否则直接跳过。
- 当 release 标记为稳定版时tag 必须与 GitHub API 返回的最新稳定版本一致,防止发布历史 tag 时意外挂起工作流。
- 满足上述条件后,工作流会根据语义化版本判断渠道(`latest`/`beta`/`rc`),并通过 `IS_PRERELEASE` 传递给脚本。
- **手动触发(`workflow_dispatch`**
- 必填:`tag`(例:`v2.0.1`);选填:`is_prerelease`(默认 `false`)。
-`is_prerelease=true` 时,同样要求 tag 带有 beta/rc 后缀。
- 手动运行仍会请求 GitHub 最新 release 信息,用于在 PR 说明中标注该 tag 是否是最新稳定版。
### 工作流步骤
1. **检查与元数据准备**`Check if should proceed``Prepare metadata` 步骤会计算 tag、prerelease 标志、是否最新版本以及用于分支名的 `safe_tag`。若任意校验失败,工作流立即退出。
2. **检出分支**:默认分支被检出到 `main/`,长期维护的 `x-files/app-upgrade-config` 分支则在 `cs/` 中,所有改动都发生在 `cs/`
3. **安装工具链**:安装 Node.js 22、启用 Corepack并在 `main/` 目录执行 `yarn install --immutable`
4. **运行更新脚本**:执行 `yarn tsx scripts/update-app-upgrade-config.ts --tag <tag> --config ../cs/app-upgrade-config.json --is-prerelease <flag>`
- 脚本会标准化 tag去掉 `v` 前缀等)、识别渠道、加载 `config/app-upgrade-segments.json` 中的分段规则。
- 校验 prerelease 标志与语义后缀是否匹配、强制锁定的 segment 是否满足、生成镜像的下载地址,并检查 release 是否已经在 GitHub/GitCode 可用latest 渠道在 GitCode 不可用时会回退到 `https://releases.cherry-ai.com`)。
- 更新对应的渠道配置后,脚本会按 semver 排序写回 JSON并刷新 `lastUpdated`
5. **检测变更并创建 PR**:若 `cs/app-upgrade-config.json` 有变更,则创建 `chore/update-app-upgrade-config/<safe_tag>` 分支,提交信息为 `🤖 chore: sync app-upgrade-config for <tag>`,并向 `x-files/app-upgrade-config` 提 PR无变更则输出提示。
### 手动触发指南
1. 进入 Cherry Studio 仓库的 GitHub **Actions** 页面,选择 **Update App Upgrade Config** 工作流。
2. 点击 **Run workflow**,保持默认分支(通常为 `main`),填写 `tag`(如 `v2.1.0`)。
3. 只有在 tag 带 `-beta`/`-rc` 后缀时才勾选 `is_prerelease`,稳定版保持默认。
4. 启动运行并等待完成,随后到 `x-files/app-upgrade-config` 分支的 PR 查看 `app-upgrade-config.json` 的变更并在验证后合并。
## JSON 配置文件格式
### 文件位置
- **GitHub**: `https://raw.githubusercontent.com/CherryHQ/cherry-studio/refs/heads/x-files/app-upgrade-config/app-upgrade-config.json`
- **GitCode**: `https://gitcode.com/CherryHQ/cherry-studio/raw/x-files/app-upgrade-config/app-upgrade-config.json`
**说明**:两个镜像源提供相同的配置文件,统一托管在 `x-files/app-upgrade-config` 分支上。客户端根据 IP 地理位置自动选择最优镜像源。
### 配置结构(当前实际配置)
```json
{
"lastUpdated": "2025-01-05T00:00:00Z",
"versions": {
"1.6.7": {
"minCompatibleVersion": "1.0.0",
"description": "Last stable v1.7.x release - required intermediate version for users below v1.7",
"channels": {
"latest": {
"version": "1.6.7",
"feedUrls": {
"github": "https://github.com/CherryHQ/cherry-studio/releases/download/v1.6.7",
"gitcode": "https://gitcode.com/CherryHQ/cherry-studio/releases/download/v1.6.7"
}
},
"rc": {
"version": "1.6.0-rc.5",
"feedUrls": {
"github": "https://github.com/CherryHQ/cherry-studio/releases/download/v1.6.0-rc.5",
"gitcode": "https://github.com/CherryHQ/cherry-studio/releases/download/v1.6.0-rc.5"
}
},
"beta": {
"version": "1.6.7-beta.3",
"feedUrls": {
"github": "https://github.com/CherryHQ/cherry-studio/releases/download/v1.7.0-beta.3",
"gitcode": "https://github.com/CherryHQ/cherry-studio/releases/download/v1.7.0-beta.3"
}
}
}
},
"2.0.0": {
"minCompatibleVersion": "1.7.0",
"description": "Major release v2.0 - required intermediate version for v2.x upgrades",
"channels": {
"latest": null,
"rc": null,
"beta": null
}
}
}
}
```
### 未来扩展示例
当需要发布 v3.0 时,如果需要强制用户先升级到 v2.8,可以添加:
```json
{
"2.8.0": {
"minCompatibleVersion": "2.0.0",
"description": "Stable v2.8 - required for v3 upgrade",
"channels": {
"latest": {
"version": "2.8.0",
"feedUrls": {
"github": "https://github.com/CherryHQ/cherry-studio/releases/download/v2.8.0",
"gitcode": "https://gitcode.com/CherryHQ/cherry-studio/releases/download/v2.8.0"
}
},
"rc": null,
"beta": null
}
},
"3.0.0": {
"minCompatibleVersion": "2.8.0",
"description": "Major release v3.0",
"channels": {
"latest": {
"version": "3.0.0",
"feedUrls": {
"github": "https://github.com/CherryHQ/cherry-studio/releases/latest",
"gitcode": "https://gitcode.com/CherryHQ/cherry-studio/releases/latest"
}
},
"rc": {
"version": "3.0.0-rc.1",
"feedUrls": {
"github": "https://github.com/CherryHQ/cherry-studio/releases/download/v3.0.0-rc.1",
"gitcode": "https://gitcode.com/CherryHQ/cherry-studio/releases/download/v3.0.0-rc.1"
}
},
"beta": null
}
}
}
```
### 字段说明
- `lastUpdated`: 配置文件最后更新时间ISO 8601 格式)
- `versions`: 版本配置对象key 为版本号,按语义化版本排序
- `minCompatibleVersion`: 可以升级到此版本的最低兼容版本
- `description`: 版本描述
- `channels`: 更新渠道配置
- `latest`: 稳定版渠道
- `rc`: Release Candidate 渠道
- `beta`: Beta 测试渠道
- 每个渠道包含:
- `version`: 该渠道的版本号
- `feedUrls`: 多镜像源 URL 配置
- `github`: GitHub 镜像源的 electron-updater feed URL
- `gitcode`: GitCode 镜像源的 electron-updater feed URL
- `metadata`: 自动化匹配所需的稳定标识
- `segmentId`: 来自 `config/app-upgrade-segments.json` 的段位 ID
- `segmentType`: 可选字段(`legacy` | `breaking` | `latest`),便于文档/调试
## TypeScript 类型定义
```typescript
// 镜像源枚举
enum UpdateMirror {
GITHUB = 'github',
GITCODE = 'gitcode'
}
interface UpdateConfig {
lastUpdated: string
versions: {
[versionKey: string]: VersionConfig
}
}
interface VersionConfig {
minCompatibleVersion: string
description: string
channels: {
latest: ChannelConfig | null
rc: ChannelConfig | null
beta: ChannelConfig | null
}
metadata?: {
segmentId: string
segmentType?: 'legacy' | 'breaking' | 'latest'
}
}
interface ChannelConfig {
version: string
feedUrls: Record<UpdateMirror, string>
// 等同于:
// feedUrls: {
// github: string
// gitcode: string
// }
}
```
## 段位元数据Break Change 标记)
- 所有段位定义(如 `legacy-v1``gateway-v2` 等)集中在 `config/app-upgrade-segments.json`,用于描述匹配范围、`segmentId``segmentType`、默认 `minCompatibleVersion/description` 以及各渠道的 URL 模板。
- `versions` 下的每个节点都会带上 `metadata.segmentId`。自动脚本始终依据该 ID 来定位并更新条目,即便 key 从 `2.1.5` 切换到 `2.1.6` 也不会错位。
- 如果某段需要锁死在特定版本(例如 `2.0.0` 的 break change可在段定义中设置 `segmentType: "breaking"` 并提供 `lockedVersion`,脚本在遇到不匹配的 tag 时会短路报错,保证升级路径安全。
- 面对未来新的断层(例如 `3.0.0`),只需要在段定义里新增一段,自动化即可识别并更新。
## 自动化工作流
`.github/workflows/update-app-upgrade-config.yml` 会在 GitHub Release包含正常发布与 Pre Release触发
1. 同时 Checkout 仓库默认分支(用于脚本)和 `x-files/app-upgrade-config` 分支(真实托管配置的分支)。
2. 在默认分支目录执行 `yarn tsx scripts/update-app-upgrade-config.ts --tag <tag> --config ../cs/app-upgrade-config.json`,直接重写 `x-files/app-upgrade-config` 分支里的配置文件。
3. 如果 `app-upgrade-config.json` 有变化,则通过 `peter-evans/create-pull-request` 自动创建一个指向 `x-files/app-upgrade-config` 的 PRDiff 仅包含该文件。
如需本地调试,可执行 `yarn update:upgrade-config --tag v2.1.6 --config ../cs/app-upgrade-config.json`(加 `--dry-run` 仅打印结果)来复现 CI 行为。若需要暂时跳过 GitHub/GitCode Release 页面是否就绪的校验,可在 `--dry-run` 的同时附加 `--skip-release-checks`。不加 `--config` 时默认更新当前工作目录(通常是 main 分支)下的副本,方便文档/审查。
## 版本匹配逻辑
### 算法流程
1. 获取用户当前版本(`currentVersion`)和请求的渠道(`requestedChannel`
2. 获取配置文件中所有版本号,按语义化版本从大到小排序
3. 遍历排序后的版本列表:
- 检查 `currentVersion >= minCompatibleVersion`
- 检查请求的 `channel` 是否存在且不为 `null`
- 如果满足条件,返回该渠道配置
4. 如果没有找到匹配版本,返回 `null`
### 伪代码实现
```typescript
function findCompatibleVersion(
currentVersion: string,
requestedChannel: UpgradeChannel,
config: UpdateConfig
): ChannelConfig | null {
// 获取所有版本号并从大到小排序
const versions = Object.keys(config.versions).sort(semver.rcompare)
for (const versionKey of versions) {
const versionConfig = config.versions[versionKey]
const channelConfig = versionConfig.channels[requestedChannel]
// 检查版本兼容性和渠道可用性
if (
semver.gte(currentVersion, versionConfig.minCompatibleVersion) &&
channelConfig !== null
) {
return channelConfig
}
}
return null // 没有找到兼容版本
}
```
## 升级路径示例
### 场景 1: v1.6.5 用户升级(低于 1.7
- **当前版本**: 1.6.5
- **请求渠道**: latest
- **匹配结果**: 1.7.0
- **原因**: 1.6.5 >= 0.0.0(满足 1.7.0 的 minCompatibleVersion但不满足 2.0.0 的 minCompatibleVersion (1.7.0)
- **操作**: 提示用户升级到 1.7.0,这是升级到 v2.x 的必要中间版本
### 场景 2: v1.6.5 用户请求 rc/beta
- **当前版本**: 1.6.5
- **请求渠道**: rc 或 beta
- **匹配结果**: 1.7.0 (latest)
- **原因**: 1.7.0 版本不提供 rc/beta 渠道(值为 null
- **操作**: 升级到 1.7.0 稳定版
### 场景 3: v1.7.0 用户升级到最新版
- **当前版本**: 1.7.0
- **请求渠道**: latest
- **匹配结果**: 2.0.0
- **原因**: 1.7.0 >= 1.7.0(满足 2.0.0 的 minCompatibleVersion
- **操作**: 直接升级到 2.0.0(当前最新稳定版)
### 场景 4: v1.7.2 用户升级到 RC 版本
- **当前版本**: 1.7.2
- **请求渠道**: rc
- **匹配结果**: 2.0.0-rc.1
- **原因**: 1.7.2 >= 1.7.0(满足 2.0.0 的 minCompatibleVersion且 rc 渠道存在
- **操作**: 升级到 2.0.0-rc.1
### 场景 5: v1.7.0 用户升级到 Beta 版本
- **当前版本**: 1.7.0
- **请求渠道**: beta
- **匹配结果**: 2.0.0-beta.1
- **原因**: 1.7.0 >= 1.7.0,且 beta 渠道存在
- **操作**: 升级到 2.0.0-beta.1
### 场景 6: v2.5.0 用户升级(未来)
假设已添加 v2.8.0 和 v3.0.0 配置:
- **当前版本**: 2.5.0
- **请求渠道**: latest
- **匹配结果**: 2.8.0
- **原因**: 2.5.0 >= 2.0.0(满足 2.8.0 的 minCompatibleVersion但不满足 3.0.0 的要求
- **操作**: 提示用户升级到 2.8.0,这是升级到 v3.x 的必要中间版本
## 代码改动计划
### 主要修改
1. **新增方法**
- `_fetchUpdateConfig(ipCountry: string): Promise<UpdateConfig | null>` - 根据 IP 获取配置文件
- `_findCompatibleChannel(currentVersion: string, channel: UpgradeChannel, config: UpdateConfig): ChannelConfig | null` - 查找兼容的渠道配置
2. **修改方法**
- `_getReleaseVersionFromGithub()` → 移除或重构为 `_getChannelFeedUrl()`
- `_setFeedUrl()` - 使用新的配置系统替代现有逻辑
3. **新增类型定义**
- `UpdateConfig`
- `VersionConfig`
- `ChannelConfig`
### 镜像源选择逻辑
客户端根据 IP 地理位置自动选择最优镜像源:
```typescript
private async _setFeedUrl() {
const currentVersion = app.getVersion()
const testPlan = configManager.getTestPlan()
const requestedChannel = testPlan ? this._getTestChannel() : UpgradeChannel.LATEST
// 根据 IP 国家确定镜像源
const ipCountry = await getIpCountry()
const mirror = ipCountry.toLowerCase() === 'cn' ? 'gitcode' : 'github'
// 获取更新配置
const config = await this._fetchUpdateConfig(mirror)
if (config) {
const channelConfig = this._findCompatibleChannel(currentVersion, requestedChannel, config)
if (channelConfig) {
// 从配置中选择对应镜像源的 URL
const feedUrl = channelConfig.feedUrls[mirror]
this._setChannel(requestedChannel, feedUrl)
return
}
}
// Fallback 逻辑
const defaultFeedUrl = mirror === 'gitcode'
? FeedUrl.PRODUCTION
: FeedUrl.GITHUB_LATEST
this._setChannel(UpgradeChannel.LATEST, defaultFeedUrl)
}
private async _fetchUpdateConfig(mirror: 'github' | 'gitcode'): Promise<UpdateConfig | null> {
const configUrl = mirror === 'gitcode'
? UpdateConfigUrl.GITCODE
: UpdateConfigUrl.GITHUB
try {
const response = await net.fetch(configUrl, {
headers: {
'User-Agent': generateUserAgent(),
'Accept': 'application/json',
'X-Client-Id': configManager.getClientId()
}
})
return await response.json() as UpdateConfig
} catch (error) {
logger.error('Failed to fetch update config:', error)
return null
}
}
```
## 降级和容错策略
1. **配置文件获取失败**: 记录错误日志,返回当前版本,不提供更新
2. **没有匹配的版本**: 提示用户当前版本不支持自动升级
3. **网络异常**: 缓存上次成功获取的配置(可选)
## GitHub Release 要求
为支持中间版本升级,需要保留以下文件:
- **v1.7.0 release** 及其 latest*.yml 文件(作为 v1.7 以下用户的升级目标)
- 未来如需强制中间版本(如 v2.8.0),需要保留对应的 release 和 latest*.yml 文件
- 各版本的完整安装包
### 当前需要的 Release
| 版本 | 用途 | 必须保留 |
|------|------|---------|
| v1.7.0 | 1.7 以下用户的升级目标 | ✅ 是 |
| v2.0.0-rc.1 | RC 测试渠道 | ❌ 可选 |
| v2.0.0-beta.1 | Beta 测试渠道 | ❌ 可选 |
| latest | 最新稳定版(自动) | ✅ 是 |
## 优势
1. **灵活性**: 支持任意复杂的升级路径
2. **可扩展性**: 新增版本只需在配置文件中添加新条目
3. **可维护性**: 配置与代码分离,无需发版即可调整升级策略
4. **多源支持**: 自动根据地理位置选择最优配置源
5. **版本控制**: 强制中间版本升级,确保数据迁移和兼容性
## 未来扩展
- 支持更细粒度的版本范围控制(如 `>=1.5.0 <1.8.0`
- 支持多步升级路径提示(如提示用户需要 1.5 → 1.8 → 2.0
- 支持 A/B 测试和灰度发布
- 支持配置文件的本地缓存和过期策略

View File

@@ -85,7 +85,7 @@ graph TD
- **SvgPreview**: SVG 图像预览
- **GraphvizPreview**: Graphviz 图表预览
所有特殊视图组件共享通用架构,以确保一致的用户体验和功能。有关这些组件及其实现的详细信息,请参阅 [图像预览组件文档](./ImagePreview-zh.md)。
所有特殊视图组件共享通用架构,以确保一致的用户体验和功能。有关这些组件及其实现的详细信息,请参阅[图像预览组件文档](./image-preview.md)。
#### StatusBar 状态栏

View File

@@ -192,4 +192,4 @@ const { containerRef, error, isLoading, triggerRender, cancelRender, clearError,
- 共享状态管理
- 响应式布局适应
有关整体 CodeBlockView 架构的更多信息,请参阅 [CodeBlockView 文档](./CodeBlockView-zh.md)。
有关整体 CodeBlockView 架构的更多信息,请参阅 [CodeBlockView 文档](./code-block-view.md)。

View File

@@ -1,6 +1,24 @@
# `translate_languages` 表技术文档
# 数据库参考文档
## 📄 概述
本文档介绍 Cherry Studio 的数据库结构,包括设置字段和翻译语言表。
---
## 设置字段 (settings)
此部分包含设置相关字段的数据类型说明。
### 翻译相关字段
| 字段名 | 类型 | 说明 |
| ------------------------------ | ------------------------------ | ------------ |
| `translate:target:language` | `LanguageCode` | 翻译目标语言 |
| `translate:source:language` | `LanguageCode` | 翻译源语言 |
| `translate:bidirectional:pair` | `[LanguageCode, LanguageCode]` | 双向翻译对 |
---
## 翻译语言表 (translate_languages)
`translate_languages` 记录用户自定义的的语言类型(`Language`)。

View File

@@ -0,0 +1,404 @@
# 消息系统
本文档介绍 Cherry Studio 的消息系统架构,包括消息生命周期、状态管理和操作接口。
## 消息的生命周期
![消息生命周期](../../assets/images/message-lifecycle.png)
---
# messageBlock.ts 使用指南
该文件定义了用于管理应用程序中所有 `MessageBlock` 实体的 Redux Slice。它使用 Redux Toolkit 的 `createSlice``createEntityAdapter` 来高效地处理规范化的状态,并提供了一系列 actions 和 selectors 用于与消息块数据交互。
## 核心目标
- **状态管理**: 集中管理所有 `MessageBlock` 的状态。`MessageBlock` 代表消息中的不同内容单元(如文本、代码、图片、引用等)。
- **规范化**: 使用 `createEntityAdapter``MessageBlock` 数据存储在规范化的结构中(`{ ids: [], entities: {} }`),这有助于提高性能和简化更新逻辑。
- **可预测性**: 提供明确的 actions 来修改状态,并通过 selectors 安全地访问状态。
## 关键概念
- **Slice (`createSlice`)**: Redux Toolkit 的核心 API用于创建包含 reducer 逻辑、action creators 和初始状态的 Redux 模块。
- **Entity Adapter (`createEntityAdapter`)**: Redux Toolkit 提供的工具,用于简化对规范化数据的 CRUD创建、读取、更新、删除操作。它会自动生成 reducer 函数和 selectors。
- **Selectors**: 用于从 Redux store 中派生和计算数据的函数。Selectors 可以被记忆化memoized以提高性能。
## State 结构
`messageBlocks` slice 的状态结构由 `createEntityAdapter` 定义,大致如下:
```typescript
{
ids: string[]; // 存储所有 MessageBlock ID 的有序列表
entities: { [id: string]: MessageBlock }; // 按 ID 存储 MessageBlock 对象的字典
loadingState: 'idle' | 'loading' | 'succeeded' | 'failed'; // (可选) 其他状态,如加载状态
error: string | null; // (可选) 错误信息
}
```
## Actions
该 slice 导出以下 actions (由 `createSlice``createEntityAdapter` 自动生成或自定义)
- **`upsertOneBlock(payload: MessageBlock)`**:
- 添加一个新的 `MessageBlock` 或更新一个已存在的 `MessageBlock`。如果 payload 中的 `id` 已存在,则执行更新;否则执行插入。
- **`upsertManyBlocks(payload: MessageBlock[])`**:
- 添加或更新多个 `MessageBlock`。常用于批量加载数据(例如,加载一个 Topic 的所有消息块)。
- **`removeOneBlock(payload: string)`**:
- 根据提供的 `id` (payload) 移除单个 `MessageBlock`
- **`removeManyBlocks(payload: string[])`**:
- 根据提供的 `id` 数组 (payload) 移除多个 `MessageBlock`。常用于删除消息或清空 Topic 时清理相关的块。
- **`removeAllBlocks()`**:
- 移除 state 中的所有 `MessageBlock` 实体。
- **`updateOneBlock(payload: { id: string; changes: Partial<MessageBlock> })`**:
- 更新一个已存在的 `MessageBlock``payload` 需要包含块的 `id` 和一个包含要更改的字段的 `changes` 对象。
- **`setMessageBlocksLoading(payload: 'idle' | 'loading')`**:
- (自定义) 设置 `loadingState` 属性。
- **`setMessageBlocksError(payload: string)`**:
- (自定义) 设置 `loadingState``'failed'` 并记录错误信息。
**使用示例 (在 Thunk 或其他 Dispatch 的地方):**
```typescript
import { upsertOneBlock, removeManyBlocks, updateOneBlock } from './messageBlock'
import store from './store' // 假设这是你的 Redux store 实例
// 添加或更新一个块
const newBlock: MessageBlock = {
/* ... block data ... */
}
store.dispatch(upsertOneBlock(newBlock))
// 更新一个块的内容
store.dispatch(updateOneBlock({ id: blockId, changes: { content: 'New content' } }))
// 删除多个块
const blockIdsToRemove = ['id1', 'id2']
store.dispatch(removeManyBlocks(blockIdsToRemove))
```
## Selectors
该 slice 导出由 `createEntityAdapter` 生成的基础 selectors并通过 `messageBlocksSelectors` 对象访问:
- **`messageBlocksSelectors.selectIds(state: RootState): string[]`**: 返回包含所有块 ID 的数组。
- **`messageBlocksSelectors.selectEntities(state: RootState): { [id: string]: MessageBlock }`**: 返回块 ID 到块对象的映射字典。
- **`messageBlocksSelectors.selectAll(state: RootState): MessageBlock[]`**: 返回包含所有块对象的数组。
- **`messageBlocksSelectors.selectTotal(state: RootState): number`**: 返回块的总数。
- **`messageBlocksSelectors.selectById(state: RootState, id: string): MessageBlock | undefined`**: 根据 ID 返回单个块对象,如果找不到则返回 `undefined`
**此外,还提供了一个自定义的、记忆化的 selector**
- **`selectFormattedCitationsByBlockId(state: RootState, blockId: string | undefined): Citation[]`**:
- 接收一个 `blockId`
- 如果该 ID 对应的块是 `CITATION` 类型,则提取并格式化其包含的引用信息(来自网页搜索、知识库等),进行去重和重新编号,最后返回一个 `Citation[]` 数组,用于在 UI 中显示。
- 如果块不存在或类型不匹配,返回空数组 `[]`
- 这个 selector 封装了处理不同引用来源Gemini, OpenAI, OpenRouter, Zhipu 等)的复杂逻辑。
**使用示例 (在 React 组件或 `useSelector` 中):**
```typescript
import { useSelector } from 'react-redux'
import { messageBlocksSelectors, selectFormattedCitationsByBlockId } from './messageBlock'
import type { RootState } from './store'
// 获取所有块
const allBlocks = useSelector(messageBlocksSelectors.selectAll)
// 获取特定 ID 的块
const specificBlock = useSelector((state: RootState) => messageBlocksSelectors.selectById(state, someBlockId))
// 获取特定引用块格式化后的引用列表
const formattedCitations = useSelector((state: RootState) => selectFormattedCitationsByBlockId(state, citationBlockId))
// 在组件中使用引用数据
// {formattedCitations.map(citation => ...)}
```
## 集成
`messageBlock.ts` slice 通常与 `messageThunk.ts` 中的 Thunks 紧密协作。Thunks 负责处理异步逻辑(如 API 调用、数据库操作),并在需要时 dispatch `messageBlock` slice 的 actions 来更新状态。例如,当 `messageThunk` 接收到流式响应时,它会 dispatch `upsertOneBlock``updateOneBlock` 来实时更新对应的 `MessageBlock`。同样,删除消息的 Thunk 会 dispatch `removeManyBlocks`
理解 `messageBlock.ts` 的职责是管理**状态本身**,而 `messageThunk.ts` 负责**触发状态变更**的异步流程,这对于维护清晰的应用架构至关重要。
---
# messageThunk.ts 使用指南
该文件包含用于管理应用程序中消息流、处理助手交互以及同步 Redux 状态与 IndexedDB 数据库的核心 Thunk Action Creators。主要围绕 `Message``MessageBlock` 对象进行操作。
## 核心功能
1. **发送/接收消息**: 处理用户消息的发送,触发助手响应,并流式处理返回的数据,将其解析为不同的 `MessageBlock`
2. **状态管理**: 确保 Redux store 中的消息和消息块状态与 IndexedDB 中的持久化数据保持一致。
3. **消息操作**: 提供删除、重发、重新生成、编辑后重发、追加响应、克隆等消息生命周期管理功能。
4. **Block 处理**: 动态创建、更新和保存各种类型的 `MessageBlock`(文本、思考过程、工具调用、引用、图片、错误、翻译等)。
## 主要 Thunks
以下是一些关键的 Thunk 函数及其用途:
1. **`sendMessage(userMessage, userMessageBlocks, assistant, topicId)`**
- **用途**: 发送一条新的用户消息。
- **流程**:
- 保存用户消息 (`userMessage`) 及其块 (`userMessageBlocks`) 到 Redux 和 DB。
- 检查 `@mentions` 以确定是单模型响应还是多模型响应。
- 创建助手消息(们)的存根 (Stub)。
- 将存根添加到 Redux 和 DB。
- 将核心处理逻辑 `fetchAndProcessAssistantResponseImpl` 添加到该 `topicId` 的队列中以获取实际响应。
- **Block 相关**: 主要处理用户消息的初始 `MessageBlock` 保存。
2. **`fetchAndProcessAssistantResponseImpl(dispatch, getState, topicId, assistant, assistantMessage)`**
- **用途**: (内部函数) 获取并处理单个助手响应的核心逻辑,被 `sendMessage`, `resend...`, `regenerate...`, `append...` 等调用。
- **流程**:
- 设置 Topic 加载状态。
- 准备上下文消息。
- 调用 `fetchChatCompletion` API 服务。
- 使用 `createStreamProcessor` 处理流式响应。
- 通过各种回调 (`onTextChunk`, `onThinkingChunk`, `onToolCallComplete`, `onImageGenerated`, `onError`, `onComplete` 等) 处理不同类型的事件。
- **Block 相关**:
- 根据流事件创建初始 `UNKNOWN` 块。
- 实时创建和更新 `MAIN_TEXT``THINKING` 块,使用 `throttledBlockUpdate``throttledBlockDbUpdate` 进行节流更新。
- 创建 `TOOL`, `CITATION`, `IMAGE`, `ERROR` 等类型的块。
- 在事件完成时(如 `onTextComplete`, `onToolCallComplete`)将块状态标记为 `SUCCESS``ERROR`,并使用 `saveUpdatedBlockToDB` 保存最终状态。
- 使用 `handleBlockTransition` 管理非流式块(如 `TOOL`, `CITATION`)的添加和状态更新。
3. **`loadTopicMessagesThunk(topicId, forceReload)`**
- **用途**: 从数据库加载指定主题的所有消息及其关联的 `MessageBlock`
- **流程**:
- 从 DB 获取 `Topic` 及其 `messages` 列表。
- 根据消息 ID 列表从 DB 获取所有相关的 `MessageBlock`
- 使用 `upsertManyBlocks` 将块更新到 Redux。
- 将消息更新到 Redux。
- **Block 相关**: 负责将持久化的 `MessageBlock` 加载到 Redux 状态。
4. **删除 Thunks**
- `deleteSingleMessageThunk(topicId, messageId)`: 删除单个消息及其所有 `MessageBlock`
- `deleteMessageGroupThunk(topicId, askId)`: 删除一个用户消息及其所有相关的助手响应消息和它们的所有 `MessageBlock`
- `clearTopicMessagesThunk(topicId)`: 清空主题下的所有消息及其所有 `MessageBlock`
- **Block 相关**: 从 Redux 和 DB 中移除指定的 `MessageBlock`
5. **重发/重新生成 Thunks**
- `resendMessageThunk(topicId, userMessageToResend, assistant)`: 重发用户消息。会重置(清空 Block 并标记为 PENDING所有与该用户消息关联的助手响应然后重新请求生成。
- `resendUserMessageWithEditThunk(topicId, originalMessage, mainTextBlockId, editedContent, assistant)`: 用户编辑消息内容后重发。先更新用户消息的 `MAIN_TEXT` 块内容,然后调用 `resendMessageThunk`
- `regenerateAssistantResponseThunk(topicId, assistantMessageToRegenerate, assistant)`: 重新生成单个助手响应。重置该助手消息(清空 Block 并标记为 PENDING然后重新请求生成。
- **Block 相关**: 删除旧的 `MessageBlock`,并在重新生成过程中创建新的 `MessageBlock`
6. **`appendAssistantResponseThunk(topicId, existingAssistantMessageId, newModel, assistant)`**
- **用途**: 在已有的对话上下文中,针对同一个用户问题,使用新选择的模型追加一个新的助手响应。
- **流程**:
- 找到现有助手消息以获取原始 `askId`
- 创建使用 `newModel` 的新助手消息存根(使用相同的 `askId`)。
- 添加新存根到 Redux 和 DB。
-`fetchAndProcessAssistantResponseImpl` 添加到队列以生成新响应。
- **Block 相关**: 为新的助手响应创建全新的 `MessageBlock`
7. **`cloneMessagesToNewTopicThunk(sourceTopicId, branchPointIndex, newTopic)`**
- **用途**: 将源主题的部分消息(及其 Block克隆到一个**已存在**的新主题中。
- **流程**:
- 复制指定索引前的消息。
- 为所有克隆的消息和 Block 生成新的 UUID。
- 正确映射克隆消息之间的 `askId` 关系。
- 复制 `MessageBlock` 内容,更新其 `messageId` 指向新的消息 ID。
- 更新文件引用计数(如果 Block 是文件或图片)。
- 将克隆的消息和 Block 保存到新主题的 Redux 状态和 DB 中。
- **Block 相关**: 创建 `MessageBlock` 的副本,并更新其 ID 和 `messageId`
8. **`initiateTranslationThunk(messageId, topicId, targetLanguage, sourceBlockId?, sourceLanguage?)`**
- **用途**: 为指定消息启动翻译流程,创建一个初始的 `TRANSLATION` 类型的 `MessageBlock`
- **流程**:
- 创建一个状态为 `STREAMING``TranslationMessageBlock`
- 将其添加到 Redux 和 DB。
- 更新原消息的 `blocks` 列表以包含新的翻译块 ID。
- **Block 相关**: 创建并保存一个占位的 `TranslationMessageBlock`。实际翻译内容的获取和填充需要后续步骤。
## 内部机制和注意事项
- **数据库交互**: 通过 `saveMessageAndBlocksToDB`, `updateExistingMessageAndBlocksInDB`, `saveUpdatesToDB`, `saveUpdatedBlockToDB`, `throttledBlockDbUpdate` 等辅助函数与 IndexedDB (`db`) 交互,确保数据持久化。
- **状态同步**: Thunks 负责协调 Redux Store 和 IndexedDB 之间的数据一致性。
- **队列 (`getTopicQueue`)**: 使用 `AsyncQueue` 确保对同一主题的操作(尤其是 API 请求)按顺序执行,避免竞态条件。
- **节流 (`throttle`)**: 对流式响应中频繁的 Block 更新(文本、思考)使用 `lodash.throttle` 优化性能,减少 Redux dispatch 和 DB 写入次数。
- **错误处理**: `fetchAndProcessAssistantResponseImpl` 内的回调函数(特别是 `onError`)处理流处理和 API 调用中可能出现的错误,并创建 `ERROR` 类型的 `MessageBlock`
开发者在使用这些 Thunks 时,通常需要提供 `dispatch`, `getState` (由 Redux Thunk 中间件注入),以及如 `topicId`, `assistant` 配置对象, 相关的 `Message``MessageBlock` 对象/ID 等参数。理解每个 Thunk 的职责和它如何影响消息及块的状态至关重要。
---
# useMessageOperations.ts 使用指南
该文件定义了一个名为 `useMessageOperations` 的自定义 React Hook。这个 Hook 的主要目的是为 React 组件提供一个便捷的接口用于执行与特定主题Topic相关的各种消息操作。它封装了调用 Redux Thunks (`messageThunk.ts`) 和 Actions (`newMessage.ts`, `messageBlock.ts`) 的逻辑,简化了组件与消息数据交互的代码。
## 核心目标
- **封装**: 将复杂的消息操作逻辑(如删除、重发、重新生成、编辑、翻译等)封装在易于使用的函数中。
- **简化**: 让组件可以直接调用这些操作函数,而无需直接与 Redux `dispatch` 或 Thunks 交互。
- **上下文关联**: 所有操作都与传入的 `topic` 对象相关联,确保操作作用于正确的主题。
## 如何使用
在你的 React 函数组件中,导入并调用 `useMessageOperations` Hook并传入当前活动的 `Topic` 对象。
```typescript
import React from 'react';
import { useMessageOperations } from '@renderer/hooks/useMessageOperations';
import type { Topic, Message, Assistant, Model } from '@renderer/types';
interface MyComponentProps {
currentTopic: Topic;
currentAssistant: Assistant;
}
function MyComponent({ currentTopic, currentAssistant }: MyComponentProps) {
const {
deleteMessage,
resendMessage,
regenerateAssistantMessage,
appendAssistantResponse,
getTranslationUpdater,
createTopicBranch,
// ... 其他操作函数
} = useMessageOperations(currentTopic);
const handleDelete = (messageId: string) => {
deleteMessage(messageId);
};
const handleResend = (message: Message) => {
resendMessage(message, currentAssistant);
};
const handleAppend = (existingMsg: Message, newModel: Model) => {
appendAssistantResponse(existingMsg, newModel, currentAssistant);
}
// ... 在组件中使用其他操作函数
return (
<div>
{/* Component UI */}
<button onClick={() => handleDelete('some-message-id')}>Delete Message</button>
{/* ... */}
</div>
);
}
```
## 返回值
`useMessageOperations(topic)` Hook 返回一个包含以下函数和值的对象:
- **`deleteMessage(id: string)`**:
- 删除指定 `id` 的单个消息。
- 内部调用 `deleteSingleMessageThunk`
- **`deleteGroupMessages(askId: string)`**:
- 删除与指定 `askId` 相关联的一组消息(通常是用户提问及其所有助手回答)。
- 内部调用 `deleteMessageGroupThunk`
- **`editMessage(messageId: string, updates: Partial<Message>)`**:
- 更新指定 `messageId` 的消息的部分属性。
- **注意**: 目前主要用于更新 Redux 状态
- 内部调用 `newMessagesActions.updateMessage`
- **`resendMessage(message: Message, assistant: Assistant)`**:
- 重新发送指定的用户消息 (`message`),这将触发其所有关联助手响应的重新生成。
- 内部调用 `resendMessageThunk`
- **`resendUserMessageWithEdit(message: Message, editedContent: string, assistant: Assistant)`**:
- 在用户消息的主要文本块被编辑后,重新发送该消息。
- 会先查找消息的 `MAIN_TEXT` 块 ID然后调用 `resendUserMessageWithEditThunk`
- **`clearTopicMessages(_topicId?: string)`**:
- 清除当前主题(或可选的指定 `_topicId`)下的所有消息。
- 内部调用 `clearTopicMessagesThunk`
- **`createNewContext()`**:
- 发出一个全局事件 (`EVENT_NAMES.NEW_CONTEXT`),通常用于通知 UI 清空显示,准备新的上下文。不直接修改 Redux 状态。
- **`displayCount`**:
- (非操作函数) 从 Redux store 中获取当前的 `displayCount` 值。
- **`pauseMessages()`**:
- 尝试中止当前主题中正在进行的消息生成(状态为 `processing``pending`)。
- 通过查找相关的 `askId` 并调用 `abortCompletion` 来实现。
- 同时会 dispatch `setTopicLoading` action 将加载状态设为 `false`
- **`resumeMessage(message: Message, assistant: Assistant)`**:
- 恢复/重新发送一个用户消息。目前实现为直接调用 `resendMessage`
- **`regenerateAssistantMessage(message: Message, assistant: Assistant)`**:
- 重新生成指定的**助手**消息 (`message`) 的响应。
- 内部调用 `regenerateAssistantResponseThunk`
- **`appendAssistantResponse(existingAssistantMessage: Message, newModel: Model, assistant: Assistant)`**:
- 针对 `existingAssistantMessage` 所回复的**同一用户提问**,使用 `newModel` 追加一个新的助手响应。
- 内部调用 `appendAssistantResponseThunk`
- **`getTranslationUpdater(messageId: string, targetLanguage: string, sourceBlockId?: string, sourceLanguage?: string)`**:
- **用途**: 获取一个用于逐步更新翻译块内容的函数。
- **流程**:
1. 内部调用 `initiateTranslationThunk` 来创建或获取一个 `TRANSLATION` 类型的 `MessageBlock`,并获取其 `blockId`
2. 返回一个**异步更新函数**。
- **返回的更新函数 `(accumulatedText: string, isComplete?: boolean) => void`**:
- 接收累积的翻译文本和完成状态。
- 调用 `updateOneBlock` 更新 Redux 中的翻译块内容和状态 (`STREAMING``SUCCESS`)。
- 调用 `throttledBlockDbUpdate` 将更新(节流地)保存到数据库。
- 如果初始化失败Thunk 返回 `undefined`),则此函数返回 `null`
- **`createTopicBranch(sourceTopicId: string, branchPointIndex: number, newTopic: Topic)`**:
- 创建一个主题分支,将 `sourceTopicId` 主题中 `branchPointIndex` 索引之前的消息克隆到 `newTopic` 中。
- **注意**: `newTopic` 对象必须是调用此函数**之前**已经创建并添加到 Redux 和数据库中的。
- 内部调用 `cloneMessagesToNewTopicThunk`
## 依赖
- **`topic: Topic`**: 必须传入当前操作上下文的主题对象。Hook 返回的操作函数将始终作用于这个主题的 `topic.id`
- **Redux `dispatch`**: Hook 内部使用 `useAppDispatch` 获取 `dispatch` 函数来调用 actions 和 thunks。
## 相关 Hooks
在同一文件中还定义了两个辅助 Hook
- **`useTopicMessages(topic: Topic)`**:
- 使用 `selectMessagesForTopic` selector 来获取并返回指定主题的消息列表。
- **`useTopicLoading(topic: Topic)`**:
- 使用 `selectNewTopicLoading` selector 来获取并返回指定主题的加载状态。
这些 Hook 可以与 `useMessageOperations` 结合使用,方便地在组件中获取消息数据、加载状态,并执行相关操作。

View File

@@ -9,6 +9,7 @@ electronLanguages:
- zh_CN # for macOS
- zh_TW # for macOS
- en # for macOS
- de
directories:
buildResources: build
@@ -20,6 +21,8 @@ files:
- "**/*"
- "!**/{.vscode,.yarn,.yarn-lock,.github,.cursorrules,.prettierrc}"
- "!electron.vite.config.{js,ts,mjs,cjs}}"
- "!.*"
- "!components.json"
- "!**/{.eslintignore,.eslintrc.js,.eslintrc.json,.eslintcache,root.eslint.config.js,eslint.config.js,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,eslint.config.mjs,dev-app-update.yml,CHANGELOG.md,README.md,biome.jsonc}"
- "!**/{.env,.env.*,.npmrc,pnpm-lock.yaml}"
- "!**/{tsconfig.json,tsconfig.tsbuildinfo,tsconfig.node.json,tsconfig.web.json}"
@@ -63,6 +66,12 @@ asarUnpack:
- resources/**
- "**/*.{metal,exp,lib}"
- "node_modules/@img/sharp-libvips-*/**"
# copy from node_modules/claude-code-plugins/plugins to resources/data/claude-code-pluginso
extraResources:
- from: "./node_modules/claude-code-plugins/plugins/"
to: "claude-code-plugins"
win:
executableName: Cherry Studio
artifactName: ${productName}-${version}-${arch}-setup.${ext}
@@ -88,7 +97,6 @@ mac:
entitlementsInherit: build/entitlements.mac.plist
notarize: false
artifactName: ${productName}-${version}-${arch}.${ext}
minimumSystemVersion: "20.1.0" # 最低支持 macOS 11.0
extendInfo:
- NSCameraUsageDescription: Application requests access to the device's camera.
- NSMicrophoneUsageDescription: Application requests access to the device's microphone.
@@ -126,60 +134,108 @@ artifactBuildCompleted: scripts/artifact-build-completed.js
releaseInfo:
releaseNotes: |
<!--LANG:en-->
What's New in v1.7.0-beta.2
A New Era of Intelligence with Cherry Studio 1.7.0
New Features:
- Session Settings: Manage session-specific settings and model configurations independently
- Notes Full-Text Search: Search across all notes with match highlighting
- Built-in DiDi MCP Server: Integration with DiDi ride-hailing services (China only)
- Intel OV OCR: Hardware-accelerated OCR using Intel NPU
- Auto-start API Server: Automatically starts when agents exist
Today we're releasing Cherry Studio 1.7.0 — our most ambitious update yet, introducing Agent: autonomous AI that thinks, plans, and acts.
Improvements:
- Agent model selection now requires explicit user choice
- Added Mistral AI provider support
- Added NewAPI generic provider support
- Improved navbar layout consistency across different modes
- Enhanced chat component responsiveness
- Better code block display on small screens
- Updated OVMS to 2025.3 official release
- Added Greek language support
For years, AI assistants have been reactive — waiting for your commands, responding to your questions. With Agent, we're changing that. Now, AI can truly work alongside you: understanding complex goals, breaking them into steps, and executing them independently.
Bug Fixes:
- Fixed GitHub Copilot gpt-5-codex streaming issues
- Fixed assistant creation failures
- Fixed translate auto-copy functionality
- Fixed miniapps external link opening
- Fixed message layout and overflow issues
- Fixed API key parsing to preserve spaces
- Fixed agent display in different navbar layouts
This is what we've been building toward. And it's just the beginning.
🤖 Meet Agent
Imagine having a brilliant colleague who never sleeps. Give Agent a goal — write a report, analyze data, refactor code — and watch it work. It reasons through problems, breaks them into steps, calls the right tools, and adapts when things change.
- **Think → Plan → Act**: From goal to execution, fully autonomous
- **Deep Reasoning**: Multi-turn thinking that solves real problems
- **Tool Mastery**: File operations, web search, code execution, and more
- **Skill Plugins**: Extend with custom commands and capabilities
- **You Stay in Control**: Real-time approval for sensitive actions
- **Full Visibility**: Every thought, every decision, fully transparent
🌐 Expanding Ecosystem
- **New Providers**: HuggingFace, Mistral, CherryIN, AI Gateway, Intel OVMS, Didi MCP
- **New Models**: Claude 4.5 Haiku, DeepSeek v3.2, GLM-4.6, Doubao, Ling series
- **MCP Integration**: Alibaba Cloud, ModelScope, Higress, MCP.so, TokenFlux and more
📚 Smarter Knowledge Base
- **OpenMinerU**: Self-hosted document processing
- **Full-Text Search**: Find anything instantly across your notes
- **Enhanced Tool Selection**: Smarter configuration for better AI assistance
📝 Notes, Reimagined
- Full-text search with highlighted results
- AI-powered smart rename
- Export as image
- Auto-wrap for tables
🖼️ Image & OCR
- Intel OVMS painting capabilities
- Intel OpenVINO NPU-accelerated OCR
🌍 Now in 10+ Languages
- Added German support
- Enhanced internationalization
⚡ Faster & More Polished
- Electron 38 upgrade
- New MCP management interface
- Dozens of UI refinements
❤️ Fully Open Source
Commercial restrictions removed. Cherry Studio now follows standard AGPL v3 — free for teams of any size.
The Agent Era is here. We can't wait to see what you'll create.
<!--LANG:zh-CN-->
v1.7.0-beta.2 新特性
Cherry Studio 1.7.0:开启智能新纪元
新功能:
- 会话设置:独立管理会话特定的设置和模型配置
- 笔记全文搜索:跨所有笔记搜索并高亮匹配内容
- 内置滴滴 MCP 服务器:集成滴滴打车服务(仅限中国地区)
- Intel OV OCR使用 Intel NPU 的硬件加速 OCR
- 自动启动 API 服务器:当存在 Agent 时自动启动
今天,我们正式发布 Cherry Studio 1.7.0 —— 迄今最具雄心的版本,带来全新的 Agent能够自主思考、规划和行动的 AI。
改进:
- Agent 模型选择现在需要用户显式选择
- 添加 Mistral AI 提供商支持
- 添加 NewAPI 通用提供商支持
- 改进不同模式下的导航栏布局一致性
- 增强聊天组件响应式设计
- 优化小屏幕代码块显示
- 更新 OVMS 至 2025.3 正式版
- 添加希腊语支持
多年来AI 助手一直是被动的——等待你的指令回应你的问题。Agent 改变了这一切。现在AI 能够真正与你并肩工作:理解复杂目标,将其拆解为步骤,并独立执行。
问题修复:
- 修复 GitHub Copilot gpt-5-codex 流式传输问题
- 修复助手创建失败
- 修复翻译自动复制功能
- 修复小程序外部链接打开
- 修复消息布局和溢出问题
- 修复 API 密钥解析以保留空格
- 修复不同导航栏布局中的 Agent 显示
这是我们一直在构建的未来。而这,仅仅是开始。
🤖 认识 Agent
想象一位永不疲倦的得力伙伴。给 Agent 一个目标——撰写报告、分析数据、重构代码——然后看它工作。它会推理问题、拆解步骤、调用工具,并在情况变化时灵活应对。
- **思考 → 规划 → 行动**:从目标到执行,全程自主
- **深度推理**:多轮思考,解决真实问题
- **工具大师**:文件操作、网络搜索、代码执行,样样精通
- **技能插件**:自定义命令,无限扩展
- **你掌控全局**:敏感操作,实时审批
- **完全透明**:每一步思考,每一个决策,清晰可见
🌐 生态持续壮大
- **新增服务商**Hugging Face、Mistral、Perplexity、SophNet、AI Gateway、Cerebras AI
- **新增模型**Gemini 3、Gemini 3 Pro支持图像预览、GPT-5.1、Claude Opus 4.5
- **MCP 集成**百炼、魔搭、Higress、MCP.so、TokenFlux 等平台
📚 更智能的知识库
- **OpenMinerU**:本地自部署文档处理
- **全文搜索**:笔记内容一搜即达
- **增强工具选择**:更智能的配置,更好的 AI 协助
📝 笔记,焕然一新
- 全文搜索,结果高亮
- AI 智能重命名
- 导出为图片
- 表格自动换行
🖼️ 图像与 OCR
- Intel OVMS 绘图能力
- Intel OpenVINO NPU 加速 OCR
🌍 支持 10+ 种语言
- 新增德语支持
- 全面增强国际化
⚡ 更快、更精致
- 升级 Electron 38
- 新的 MCP 管理界面
- 数十处 UI 细节打磨
❤️ 完全开源
商用限制已移除。Cherry Studio 现遵循标准 AGPL v3 协议——任意规模团队均可自由使用。
Agent 纪元已至。期待你的创造。
<!--LANG:END-->

View File

@@ -95,7 +95,8 @@ export default defineConfig({
'@cherrystudio/ai-core/provider': resolve('packages/aiCore/src/core/providers'),
'@cherrystudio/ai-core/built-in/plugins': resolve('packages/aiCore/src/core/plugins/built-in'),
'@cherrystudio/ai-core': resolve('packages/aiCore/src'),
'@cherrystudio/extension-table-plus': resolve('packages/extension-table-plus/src')
'@cherrystudio/extension-table-plus': resolve('packages/extension-table-plus/src'),
'@cherrystudio/ai-sdk-provider': resolve('packages/ai-sdk-provider/src')
}
},
optimizeDeps: {

View File

@@ -58,6 +58,7 @@ export default defineConfig([
'dist/**',
'out/**',
'local/**',
'tests/**',
'.yarn/**',
'.gitignore',
'scripts/cloudflare-worker.js',

View File

@@ -1,6 +1,6 @@
{
"name": "CherryStudio",
"version": "1.7.0-beta.2",
"version": "1.7.0",
"private": true,
"description": "A powerful AI assistant for producer.",
"main": "./out/main/index.js",
@@ -58,9 +58,11 @@
"update:i18n": "dotenv -e .env -- tsx scripts/update-i18n.ts",
"auto:i18n": "dotenv -e .env -- tsx scripts/auto-translate-i18n.ts",
"update:languages": "tsx scripts/update-languages.ts",
"update:upgrade-config": "tsx scripts/update-app-upgrade-config.ts",
"test": "vitest run --silent",
"test:main": "vitest run --project main",
"test:renderer": "vitest run --project renderer",
"test:aicore": "vitest run --project aiCore",
"test:update": "yarn test:renderer --update",
"test:coverage": "vitest run --coverage --silent",
"test:ui": "vitest --ui",
@@ -73,25 +75,32 @@
"format:check": "biome format && biome lint",
"prepare": "git config blame.ignoreRevsFile .git-blame-ignore-revs && husky",
"claude": "dotenv -e .env -- claude",
"release:aicore:alpha": "yarn workspace @cherrystudio/ai-core version prerelease --immediate && yarn workspace @cherrystudio/ai-core npm publish --tag alpha --access public",
"release:aicore:beta": "yarn workspace @cherrystudio/ai-core version prerelease --immediate && yarn workspace @cherrystudio/ai-core npm publish --tag beta --access public",
"release:aicore": "yarn workspace @cherrystudio/ai-core version patch --immediate && yarn workspace @cherrystudio/ai-core npm publish --access public"
"release:aicore:alpha": "yarn workspace @cherrystudio/ai-core version prerelease --preid alpha --immediate && yarn workspace @cherrystudio/ai-core build && yarn workspace @cherrystudio/ai-core npm publish --tag alpha --access public",
"release:aicore:beta": "yarn workspace @cherrystudio/ai-core version prerelease --preid beta --immediate && yarn workspace @cherrystudio/ai-core build && yarn workspace @cherrystudio/ai-core npm publish --tag beta --access public",
"release:aicore": "yarn workspace @cherrystudio/ai-core version patch --immediate && yarn workspace @cherrystudio/ai-core build && yarn workspace @cherrystudio/ai-core npm publish --access public",
"release:ai-sdk-provider": "yarn workspace @cherrystudio/ai-sdk-provider version patch --immediate && yarn workspace @cherrystudio/ai-sdk-provider build && yarn workspace @cherrystudio/ai-sdk-provider npm publish --access public"
},
"dependencies": {
"@anthropic-ai/claude-agent-sdk": "patch:@anthropic-ai/claude-agent-sdk@npm%3A0.1.1#~/.yarn/patches/@anthropic-ai-claude-agent-sdk-npm-0.1.1-d937b73fed.patch",
"@anthropic-ai/claude-agent-sdk": "patch:@anthropic-ai/claude-agent-sdk@npm%3A0.1.53#~/.yarn/patches/@anthropic-ai-claude-agent-sdk-npm-0.1.53-4b77f4cf29.patch",
"@libsql/client": "0.14.0",
"@libsql/win32-x64-msvc": "^0.4.7",
"@napi-rs/system-ocr": "patch:@napi-rs/system-ocr@npm%3A1.0.2#~/.yarn/patches/@napi-rs-system-ocr-npm-1.0.2-59e7a78e8b.patch",
"@paymoapp/electron-shutdown-handler": "^1.1.2",
"@strongtz/win32-arm64-msvc": "^0.4.7",
"emoji-picker-element-data": "^1",
"express": "^5.1.0",
"font-list": "^2.0.0",
"graceful-fs": "^4.2.11",
"gray-matter": "^4.0.3",
"js-yaml": "^4.1.0",
"jsdom": "26.1.0",
"node-stream-zip": "^1.15.0",
"officeparser": "^4.2.0",
"os-proxy-config": "^1.1.2",
"qrcode.react": "^4.2.0",
"selection-hook": "^1.0.12",
"sharp": "^0.34.3",
"socket.io": "^4.8.1",
"swagger-jsdoc": "^6.2.8",
"swagger-ui-express": "^5.0.1",
"tesseract.js": "patch:tesseract.js@npm%3A6.0.1#~/.yarn/patches/tesseract.js-npm-6.0.1-2562a7e46d.patch",
@@ -101,18 +110,25 @@
"@agentic/exa": "^7.3.3",
"@agentic/searxng": "^7.3.3",
"@agentic/tavily": "^7.3.3",
"@ai-sdk/amazon-bedrock": "^3.0.35",
"@ai-sdk/google-vertex": "^3.0.40",
"@ai-sdk/mistral": "^2.0.19",
"@ai-sdk/perplexity": "^2.0.13",
"@ai-sdk/amazon-bedrock": "^3.0.61",
"@ai-sdk/anthropic": "^2.0.49",
"@ai-sdk/cerebras": "^1.0.31",
"@ai-sdk/gateway": "^2.0.15",
"@ai-sdk/google": "patch:@ai-sdk/google@npm%3A2.0.43#~/.yarn/patches/@ai-sdk-google-npm-2.0.43-689ed559b3.patch",
"@ai-sdk/google-vertex": "^3.0.79",
"@ai-sdk/huggingface": "^0.0.10",
"@ai-sdk/mistral": "^2.0.24",
"@ai-sdk/openai": "patch:@ai-sdk/openai@npm%3A2.0.72#~/.yarn/patches/@ai-sdk-openai-npm-2.0.72-234e68da87.patch",
"@ai-sdk/perplexity": "^2.0.20",
"@ai-sdk/test-server": "^0.0.1",
"@ant-design/v5-patch-for-react-19": "^1.0.3",
"@anthropic-ai/sdk": "^0.41.0",
"@anthropic-ai/vertex-sdk": "patch:@anthropic-ai/vertex-sdk@npm%3A0.11.4#~/.yarn/patches/@anthropic-ai-vertex-sdk-npm-0.11.4-c19cb41edb.patch",
"@aws-sdk/client-bedrock": "^3.840.0",
"@aws-sdk/client-bedrock-runtime": "^3.840.0",
"@aws-sdk/client-s3": "^3.840.0",
"@aws-sdk/client-bedrock": "^3.910.0",
"@aws-sdk/client-bedrock-runtime": "^3.910.0",
"@aws-sdk/client-s3": "^3.910.0",
"@biomejs/biome": "2.2.4",
"@cherrystudio/ai-core": "workspace:^1.0.0-alpha.18",
"@cherrystudio/ai-core": "workspace:^1.0.9",
"@cherrystudio/embedjs": "^0.1.31",
"@cherrystudio/embedjs-libsql": "^0.1.31",
"@cherrystudio/embedjs-loader-csv": "^0.1.31",
@@ -126,6 +142,7 @@
"@cherrystudio/embedjs-ollama": "^0.1.31",
"@cherrystudio/embedjs-openai": "^0.1.31",
"@cherrystudio/extension-table-plus": "workspace:^",
"@cherrystudio/openai": "^6.9.0",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/modifiers": "^9.0.0",
"@dnd-kit/sortable": "^10.0.0",
@@ -140,22 +157,23 @@
"@eslint/js": "^9.22.0",
"@google/genai": "patch:@google/genai@npm%3A1.0.1#~/.yarn/patches/@google-genai-npm-1.0.1-e26f0f9af7.patch",
"@hello-pangea/dnd": "^18.0.1",
"@heroui/react": "^2.8.3",
"@kangfenmao/keyv-storage": "^0.1.0",
"@langchain/community": "^0.3.50",
"@langchain/community": "^1.0.0",
"@langchain/core": "patch:@langchain/core@npm%3A1.0.2#~/.yarn/patches/@langchain-core-npm-1.0.2-183ef83fe4.patch",
"@langchain/openai": "patch:@langchain/openai@npm%3A1.0.0#~/.yarn/patches/@langchain-openai-npm-1.0.0-474d0ad9d4.patch",
"@mistralai/mistralai": "^1.7.5",
"@modelcontextprotocol/sdk": "^1.17.5",
"@mozilla/readability": "^0.6.0",
"@notionhq/client": "^2.2.15",
"@openrouter/ai-sdk-provider": "^1.1.2",
"@openrouter/ai-sdk-provider": "^1.2.8",
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/core": "2.0.0",
"@opentelemetry/exporter-trace-otlp-http": "^0.200.0",
"@opentelemetry/sdk-trace-base": "^2.0.0",
"@opentelemetry/sdk-trace-node": "^2.0.0",
"@opentelemetry/sdk-trace-web": "^2.0.0",
"@opeoginni/github-copilot-openai-compatible": "patch:@opeoginni/github-copilot-openai-compatible@npm%3A0.1.18#~/.yarn/patches/@opeoginni-github-copilot-openai-compatible-npm-0.1.18-3f65760532.patch",
"@playwright/test": "^1.52.0",
"@opeoginni/github-copilot-openai-compatible": "^0.1.21",
"@playwright/test": "^1.55.1",
"@radix-ui/react-context-menu": "^2.2.16",
"@reduxjs/toolkit": "^2.2.5",
"@shikijs/markdown-it": "^3.12.0",
@@ -193,14 +211,15 @@
"@types/fs-extra": "^11",
"@types/he": "^1",
"@types/html-to-text": "^9",
"@types/js-yaml": "^4.0.9",
"@types/lodash": "^4.17.5",
"@types/markdown-it": "^14",
"@types/md5": "^2.3.5",
"@types/mime-types": "^3",
"@types/node": "^22.17.1",
"@types/pako": "^1.0.2",
"@types/react": "^19.0.12",
"@types/react-dom": "^19.0.4",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
"@types/react-infinite-scroll-component": "^5.0.0",
"@types/react-transition-group": "^4.4.12",
"@types/react-window": "^1",
@@ -222,7 +241,7 @@
"@viz-js/lang-dot": "^1.0.5",
"@viz-js/viz": "^3.14.0",
"@xyflow/react": "^12.4.4",
"ai": "^5.0.68",
"ai": "^5.0.98",
"antd": "patch:antd@npm%3A5.27.0#~/.yarn/patches/antd-npm-5.27.0-aa91c36546.patch",
"archiver": "^7.0.1",
"async-mutex": "^0.5.0",
@@ -232,6 +251,7 @@
"check-disk-space": "3.4.0",
"cheerio": "^1.1.2",
"chokidar": "^4.0.3",
"claude-code-plugins": "1.0.3",
"cli-progress": "^3.12.0",
"clsx": "^2.1.1",
"code-inspector-plugin": "^0.20.14",
@@ -247,13 +267,13 @@
"dotenv-cli": "^7.4.2",
"drizzle-kit": "^0.31.4",
"drizzle-orm": "^0.44.5",
"electron": "37.6.0",
"electron-builder": "26.0.15",
"electron": "38.7.0",
"electron-builder": "26.1.0",
"electron-devtools-installer": "^3.2.0",
"electron-reload": "^2.0.0-alpha.1",
"electron-store": "^8.2.0",
"electron-updater": "6.6.4",
"electron-vite": "4.0.0",
"electron-updater": "patch:electron-updater@npm%3A6.7.0#~/.yarn/patches/electron-updater-npm-6.7.0-47b11bb0d4.patch",
"electron-vite": "4.0.1",
"electron-window-state": "^5.0.3",
"emittery": "^1.0.3",
"emoji-picker-element": "^1.22.1",
@@ -280,6 +300,7 @@
"husky": "^9.1.7",
"i18next": "^23.11.5",
"iconv-lite": "^0.6.3",
"ipaddr.js": "^2.2.0",
"isbinaryfile": "5.0.4",
"jaison": "^2.0.2",
"jest-styled-components": "^7.2.0",
@@ -296,13 +317,11 @@
"motion": "^12.10.5",
"notion-helper": "^1.3.22",
"npx-scope-finder": "^1.2.0",
"openai": "patch:openai@npm%3A5.12.2#~/.yarn/patches/openai-npm-5.12.2-30b075401c.patch",
"oxlint": "^1.22.0",
"oxlint-tsgolint": "^0.2.0",
"p-queue": "^8.1.0",
"pdf-lib": "^1.17.1",
"pdf-parse": "^1.1.1",
"playwright": "^1.52.0",
"proxy-agent": "^6.5.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
@@ -338,6 +357,7 @@
"striptags": "^3.2.0",
"styled-components": "^6.1.11",
"swr": "^2.3.6",
"tailwind-merge": "^3.3.1",
"tailwindcss": "^4.1.13",
"tar": "^7.4.3",
"tiny-pinyin": "^1.3.2",
@@ -349,7 +369,7 @@
"undici": "6.21.2",
"unified": "^11.0.5",
"uuid": "^13.0.0",
"vite": "npm:rolldown-vite@latest",
"vite": "npm:rolldown-vite@7.1.5",
"vitest": "^3.2.4",
"webdav": "^5.8.0",
"winston": "^3.17.0",
@@ -363,28 +383,38 @@
"zod": "^4.1.5"
},
"resolutions": {
"@smithy/types": "4.7.1",
"@codemirror/language": "6.11.3",
"@codemirror/lint": "6.8.5",
"@codemirror/view": "6.38.1",
"@langchain/core@npm:^0.3.26": "patch:@langchain/core@npm%3A0.3.44#~/.yarn/patches/@langchain-core-npm-0.3.44-41d5c3cb0a.patch",
"@langchain/openai@npm:^0.3.16": "patch:@langchain/openai@npm%3A0.3.16#~/.yarn/patches/@langchain-openai-npm-0.3.16-e525b59526.patch",
"@langchain/openai@npm:>=0.1.0 <0.4.0": "patch:@langchain/openai@npm%3A0.3.16#~/.yarn/patches/@langchain-openai-npm-0.3.16-e525b59526.patch",
"app-builder-lib@npm:26.0.13": "patch:app-builder-lib@npm%3A26.0.13#~/.yarn/patches/app-builder-lib-npm-26.0.13-a064c9e1d0.patch",
"app-builder-lib@npm:26.0.15": "patch:app-builder-lib@npm%3A26.0.15#~/.yarn/patches/app-builder-lib-npm-26.0.15-360e5b0476.patch",
"@langchain/core@npm:^0.3.26": "patch:@langchain/core@npm%3A1.0.2#~/.yarn/patches/@langchain-core-npm-1.0.2-183ef83fe4.patch",
"atomically@npm:^1.7.0": "patch:atomically@npm%3A1.7.0#~/.yarn/patches/atomically-npm-1.7.0-e742e5293b.patch",
"esbuild": "^0.25.0",
"file-stream-rotator@npm:^0.6.1": "patch:file-stream-rotator@npm%3A0.6.1#~/.yarn/patches/file-stream-rotator-npm-0.6.1-eab45fb13d.patch",
"libsql@npm:^0.4.4": "patch:libsql@npm%3A0.4.7#~/.yarn/patches/libsql-npm-0.4.7-444e260fb1.patch",
"node-abi": "4.12.0",
"openai@npm:^4.77.0": "patch:openai@npm%3A5.12.2#~/.yarn/patches/openai-npm-5.12.2-30b075401c.patch",
"openai@npm:^4.87.3": "patch:openai@npm%3A5.12.2#~/.yarn/patches/openai-npm-5.12.2-30b075401c.patch",
"node-abi": "4.24.0",
"openai@npm:^4.77.0": "npm:@cherrystudio/openai@6.5.0",
"openai@npm:^4.87.3": "npm:@cherrystudio/openai@6.5.0",
"pdf-parse@npm:1.1.1": "patch:pdf-parse@npm%3A1.1.1#~/.yarn/patches/pdf-parse-npm-1.1.1-04a6109b2a.patch",
"pkce-challenge@npm:^4.1.0": "patch:pkce-challenge@npm%3A4.1.0#~/.yarn/patches/pkce-challenge-npm-4.1.0-fbc51695a3.patch",
"tar-fs": "^2.1.4",
"undici": "6.21.2",
"vite": "npm:rolldown-vite@latest",
"vite": "npm:rolldown-vite@7.1.5",
"tesseract.js@npm:*": "patch:tesseract.js@npm%3A6.0.1#~/.yarn/patches/tesseract.js-npm-6.0.1-2562a7e46d.patch",
"@ai-sdk/google@npm:2.0.20": "patch:@ai-sdk/google@npm%3A2.0.20#~/.yarn/patches/@ai-sdk-google-npm-2.0.20-b9102f9d54.patch"
"@ai-sdk/openai@npm:^2.0.52": "patch:@ai-sdk/openai@npm%3A2.0.52#~/.yarn/patches/@ai-sdk-openai-npm-2.0.52-b36d949c76.patch",
"@img/sharp-darwin-arm64": "0.34.3",
"@img/sharp-darwin-x64": "0.34.3",
"@img/sharp-linux-arm": "0.34.3",
"@img/sharp-linux-arm64": "0.34.3",
"@img/sharp-linux-x64": "0.34.3",
"@img/sharp-win32-x64": "0.34.3",
"openai@npm:5.12.2": "npm:@cherrystudio/openai@6.5.0",
"@langchain/openai@npm:>=0.1.0 <0.6.0": "patch:@langchain/openai@npm%3A1.0.0#~/.yarn/patches/@langchain-openai-npm-1.0.0-474d0ad9d4.patch",
"@langchain/openai@npm:^0.3.16": "patch:@langchain/openai@npm%3A1.0.0#~/.yarn/patches/@langchain-openai-npm-1.0.0-474d0ad9d4.patch",
"@langchain/openai@npm:>=0.2.0 <0.7.0": "patch:@langchain/openai@npm%3A1.0.0#~/.yarn/patches/@langchain-openai-npm-1.0.0-474d0ad9d4.patch",
"@ai-sdk/openai@npm:^2.0.42": "patch:@ai-sdk/openai@npm%3A2.0.72#~/.yarn/patches/@ai-sdk-openai-npm-2.0.72-234e68da87.patch",
"@ai-sdk/google@npm:^2.0.40": "patch:@ai-sdk/google@npm%3A2.0.40#~/.yarn/patches/@ai-sdk-google-npm-2.0.40-47e0eeee83.patch",
"@ai-sdk/openai-compatible@npm:^1.0.27": "patch:@ai-sdk/openai-compatible@npm%3A1.0.27#~/.yarn/patches/@ai-sdk-openai-compatible-npm-1.0.27-06f74278cf.patch"
},
"packageManager": "yarn@4.9.1",
"lint-staged": {

View File

@@ -0,0 +1,39 @@
# @cherrystudio/ai-sdk-provider
CherryIN provider bundle for the [Vercel AI SDK](https://ai-sdk.dev/).
It exposes the CherryIN OpenAI-compatible entrypoints and dynamically routes Anthropic and Gemini model ids to their CherryIN upstream equivalents.
## Installation
```bash
npm install ai @cherrystudio/ai-sdk-provider @ai-sdk/anthropic @ai-sdk/google @ai-sdk/openai
# or
yarn add ai @cherrystudio/ai-sdk-provider @ai-sdk/anthropic @ai-sdk/google @ai-sdk/openai
```
> **Note**: This package requires peer dependencies `ai`, `@ai-sdk/anthropic`, `@ai-sdk/google`, and `@ai-sdk/openai` to be installed.
## Usage
```ts
import { createCherryIn, cherryIn } from '@cherrystudio/ai-sdk-provider'
const cherryInProvider = createCherryIn({
apiKey: process.env.CHERRYIN_API_KEY,
// optional overrides:
// baseURL: 'https://open.cherryin.net/v1',
// anthropicBaseURL: 'https://open.cherryin.net/anthropic',
// geminiBaseURL: 'https://open.cherryin.net/gemini/v1beta',
})
// Chat models will auto-route based on the model id prefix:
const openaiModel = cherryInProvider.chat('gpt-4o-mini')
const anthropicModel = cherryInProvider.chat('claude-3-5-sonnet-latest')
const geminiModel = cherryInProvider.chat('gemini-2.0-pro-exp')
const { text } = await openaiModel.invoke('Hello CherryIN!')
```
The provider also exposes `completion`, `responses`, `embedding`, `image`, `transcription`, and `speech` helpers aligned with the upstream APIs.
See [AI SDK docs](https://ai-sdk.dev/providers/community-providers/custom-providers) for configuring custom providers.

View File

@@ -0,0 +1,64 @@
{
"name": "@cherrystudio/ai-sdk-provider",
"version": "0.1.3",
"description": "Cherry Studio AI SDK provider bundle with CherryIN routing.",
"keywords": [
"ai-sdk",
"provider",
"cherryin",
"vercel-ai-sdk",
"cherry-studio"
],
"author": "Cherry Studio",
"license": "MIT",
"homepage": "https://github.com/CherryHQ/cherry-studio",
"repository": {
"type": "git",
"url": "git+https://github.com/CherryHQ/cherry-studio.git",
"directory": "packages/ai-sdk-provider"
},
"bugs": {
"url": "https://github.com/CherryHQ/cherry-studio/issues"
},
"type": "module",
"main": "dist/index.cjs",
"module": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"dist"
],
"scripts": {
"build": "tsdown",
"dev": "tsc -w",
"clean": "rm -rf dist",
"test": "vitest run",
"test:watch": "vitest"
},
"peerDependencies": {
"@ai-sdk/anthropic": "^2.0.29",
"@ai-sdk/google": "^2.0.23",
"@ai-sdk/openai": "^2.0.64",
"ai": "^5.0.26"
},
"dependencies": {
"@ai-sdk/provider": "^2.0.0",
"@ai-sdk/provider-utils": "^3.0.17"
},
"devDependencies": {
"tsdown": "^0.13.3",
"typescript": "^5.8.2",
"vitest": "^3.2.4"
},
"sideEffects": false,
"engines": {
"node": ">=18.0.0"
},
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.cjs",
"default": "./dist/index.js"
}
}
}

View File

@@ -0,0 +1,347 @@
import { AnthropicMessagesLanguageModel } from '@ai-sdk/anthropic/internal'
import { GoogleGenerativeAILanguageModel } from '@ai-sdk/google/internal'
import type { OpenAIProviderSettings } from '@ai-sdk/openai'
import {
OpenAIChatLanguageModel,
OpenAICompletionLanguageModel,
OpenAIEmbeddingModel,
OpenAIImageModel,
OpenAIResponsesLanguageModel,
OpenAISpeechModel,
OpenAITranscriptionModel
} from '@ai-sdk/openai/internal'
import {
type EmbeddingModelV2,
type ImageModelV2,
type LanguageModelV2,
type ProviderV2,
type SpeechModelV2,
type TranscriptionModelV2
} from '@ai-sdk/provider'
import type { FetchFunction } from '@ai-sdk/provider-utils'
import { loadApiKey, withoutTrailingSlash } from '@ai-sdk/provider-utils'
export const CHERRYIN_PROVIDER_NAME = 'cherryin' as const
export const DEFAULT_CHERRYIN_BASE_URL = 'https://open.cherryin.net/v1'
export const DEFAULT_CHERRYIN_ANTHROPIC_BASE_URL = 'https://open.cherryin.net/v1'
export const DEFAULT_CHERRYIN_GEMINI_BASE_URL = 'https://open.cherryin.net/v1beta/models'
const ANTHROPIC_PREFIX = /^anthropic\//i
const GEMINI_PREFIX = /^google\//i
// const GEMINI_EXCLUDED_SUFFIXES = ['-nothink', '-search']
type HeaderValue = string | undefined
type HeadersInput = Record<string, HeaderValue> | (() => Record<string, HeaderValue>)
export interface CherryInProviderSettings {
/**
* CherryIN API key.
*
* If omitted, the provider will read the `CHERRYIN_API_KEY` environment variable.
*/
apiKey?: string
/**
* Optional custom fetch implementation.
*/
fetch?: FetchFunction
/**
* Base URL for OpenAI-compatible CherryIN endpoints.
*
* Defaults to `https://open.cherryin.net/v1`.
*/
baseURL?: string
/**
* Base URL for Anthropic-compatible endpoints.
*
* Defaults to `https://open.cherryin.net/anthropic`.
*/
anthropicBaseURL?: string
/**
* Base URL for Gemini-compatible endpoints.
*
* Defaults to `https://open.cherryin.net/gemini/v1beta`.
*/
geminiBaseURL?: string
/**
* Optional static headers applied to every request.
*/
headers?: HeadersInput
/**
* Optional endpoint type to distinguish different endpoint behaviors.
*/
endpointType?: 'openai' | 'openai-response' | 'anthropic' | 'gemini' | 'image-generation' | 'jina-rerank'
}
export interface CherryInProvider extends ProviderV2 {
(modelId: string, settings?: OpenAIProviderSettings): LanguageModelV2
languageModel(modelId: string, settings?: OpenAIProviderSettings): LanguageModelV2
chat(modelId: string, settings?: OpenAIProviderSettings): LanguageModelV2
responses(modelId: string): LanguageModelV2
completion(modelId: string, settings?: OpenAIProviderSettings): LanguageModelV2
embedding(modelId: string, settings?: OpenAIProviderSettings): EmbeddingModelV2<string>
textEmbedding(modelId: string, settings?: OpenAIProviderSettings): EmbeddingModelV2<string>
textEmbeddingModel(modelId: string, settings?: OpenAIProviderSettings): EmbeddingModelV2<string>
image(modelId: string, settings?: OpenAIProviderSettings): ImageModelV2
imageModel(modelId: string, settings?: OpenAIProviderSettings): ImageModelV2
transcription(modelId: string): TranscriptionModelV2
transcriptionModel(modelId: string): TranscriptionModelV2
speech(modelId: string): SpeechModelV2
speechModel(modelId: string): SpeechModelV2
}
const resolveApiKey = (options: CherryInProviderSettings): string =>
loadApiKey({
apiKey: options.apiKey,
environmentVariableName: 'CHERRYIN_API_KEY',
description: 'CherryIN'
})
const isAnthropicModel = (modelId: string) => ANTHROPIC_PREFIX.test(modelId)
const isGeminiModel = (modelId: string) => GEMINI_PREFIX.test(modelId)
const createCustomFetch = (originalFetch?: any) => {
return async (url: string, options: any) => {
if (options?.body) {
try {
const body = JSON.parse(options.body)
if (body.tools && Array.isArray(body.tools) && body.tools.length === 0 && body.tool_choice) {
delete body.tool_choice
options.body = JSON.stringify(body)
}
} catch (error) {
// ignore error
}
}
return originalFetch ? originalFetch(url, options) : fetch(url, options)
}
}
class CherryInOpenAIChatLanguageModel extends OpenAIChatLanguageModel {
constructor(modelId: string, settings: any) {
super(modelId, {
...settings,
fetch: createCustomFetch(settings.fetch)
})
}
}
const resolveConfiguredHeaders = (headers?: HeadersInput): Record<string, HeaderValue> => {
if (typeof headers === 'function') {
return { ...headers() }
}
return headers ? { ...headers } : {}
}
const toBearerToken = (authorization?: string) => (authorization ? authorization.replace(/^Bearer\s+/i, '') : undefined)
const createJsonHeadersGetter = (options: CherryInProviderSettings): (() => Record<string, HeaderValue>) => {
return () => ({
Authorization: `Bearer ${resolveApiKey(options)}`,
'Content-Type': 'application/json',
...resolveConfiguredHeaders(options.headers)
})
}
const createAuthHeadersGetter = (options: CherryInProviderSettings): (() => Record<string, HeaderValue>) => {
return () => ({
Authorization: `Bearer ${resolveApiKey(options)}`,
...resolveConfiguredHeaders(options.headers)
})
}
export const createCherryIn = (options: CherryInProviderSettings = {}): CherryInProvider => {
const {
baseURL = DEFAULT_CHERRYIN_BASE_URL,
anthropicBaseURL = DEFAULT_CHERRYIN_ANTHROPIC_BASE_URL,
geminiBaseURL = DEFAULT_CHERRYIN_GEMINI_BASE_URL,
fetch,
endpointType
} = options
const getJsonHeaders = createJsonHeadersGetter(options)
const getAuthHeaders = createAuthHeadersGetter(options)
const url = ({ path }: { path: string; modelId: string }) => `${withoutTrailingSlash(baseURL)}${path}`
const createAnthropicModel = (modelId: string) =>
new AnthropicMessagesLanguageModel(modelId, {
provider: `${CHERRYIN_PROVIDER_NAME}.anthropic`,
baseURL: anthropicBaseURL,
headers: () => {
const headers = getJsonHeaders()
const apiKey = toBearerToken(headers.Authorization)
return {
...headers,
'x-api-key': apiKey
}
},
fetch,
supportedUrls: () => ({
'image/*': [/^https?:\/\/.*$/]
})
})
const createGeminiModel = (modelId: string) =>
new GoogleGenerativeAILanguageModel(modelId, {
provider: `${CHERRYIN_PROVIDER_NAME}.google`,
baseURL: geminiBaseURL,
headers: () => {
const headers = getJsonHeaders()
const apiKey = toBearerToken(headers.Authorization)
return {
...headers,
'x-goog-api-key': apiKey
}
},
fetch,
generateId: () => `${CHERRYIN_PROVIDER_NAME}-${Date.now()}`,
supportedUrls: () => ({})
})
const createOpenAIChatModel = (modelId: string, settings: OpenAIProviderSettings = {}) =>
new CherryInOpenAIChatLanguageModel(modelId, {
provider: `${CHERRYIN_PROVIDER_NAME}.openai-chat`,
url,
headers: () => ({
...getJsonHeaders(),
...settings.headers
}),
fetch
})
const createChatModelByModelId = (modelId: string, settings: OpenAIProviderSettings = {}) => {
if (isAnthropicModel(modelId)) {
return createAnthropicModel(modelId)
}
if (isGeminiModel(modelId)) {
return createGeminiModel(modelId)
}
return new OpenAIResponsesLanguageModel(modelId, {
provider: `${CHERRYIN_PROVIDER_NAME}.openai`,
url,
headers: () => ({
...getJsonHeaders(),
...settings.headers
}),
fetch
})
}
const createChatModel = (modelId: string, settings: OpenAIProviderSettings = {}) => {
if (!endpointType) return createChatModelByModelId(modelId, settings)
switch (endpointType) {
case 'anthropic':
return createAnthropicModel(modelId)
case 'gemini':
return createGeminiModel(modelId)
case 'openai':
return createOpenAIChatModel(modelId)
case 'openai-response':
default:
return new OpenAIResponsesLanguageModel(modelId, {
provider: `${CHERRYIN_PROVIDER_NAME}.openai`,
url,
headers: () => ({
...getJsonHeaders(),
...settings.headers
}),
fetch
})
}
}
const createCompletionModel = (modelId: string, settings: OpenAIProviderSettings = {}) =>
new OpenAICompletionLanguageModel(modelId, {
provider: `${CHERRYIN_PROVIDER_NAME}.completion`,
url,
headers: () => ({
...getJsonHeaders(),
...settings.headers
}),
fetch
})
const createEmbeddingModel = (modelId: string, settings: OpenAIProviderSettings = {}) =>
new OpenAIEmbeddingModel(modelId, {
provider: `${CHERRYIN_PROVIDER_NAME}.embeddings`,
url,
headers: () => ({
...getJsonHeaders(),
...settings.headers
}),
fetch
})
const createResponsesModel = (modelId: string) =>
new OpenAIResponsesLanguageModel(modelId, {
provider: `${CHERRYIN_PROVIDER_NAME}.responses`,
url,
headers: () => ({
...getJsonHeaders()
}),
fetch
})
const createImageModel = (modelId: string, settings: OpenAIProviderSettings = {}) =>
new OpenAIImageModel(modelId, {
provider: `${CHERRYIN_PROVIDER_NAME}.image`,
url,
headers: () => ({
...getJsonHeaders(),
...settings.headers
}),
fetch
})
const createTranscriptionModel = (modelId: string) =>
new OpenAITranscriptionModel(modelId, {
provider: `${CHERRYIN_PROVIDER_NAME}.transcription`,
url,
headers: () => ({
...getAuthHeaders()
}),
fetch
})
const createSpeechModel = (modelId: string) =>
new OpenAISpeechModel(modelId, {
provider: `${CHERRYIN_PROVIDER_NAME}.speech`,
url,
headers: () => ({
...getJsonHeaders()
}),
fetch
})
const provider: CherryInProvider = function (modelId: string, settings?: OpenAIProviderSettings) {
if (new.target) {
throw new Error('CherryIN provider function cannot be called with the new keyword.')
}
return createChatModel(modelId, settings)
}
provider.languageModel = createChatModel
provider.chat = createOpenAIChatModel
provider.responses = createResponsesModel
provider.completion = createCompletionModel
provider.embedding = createEmbeddingModel
provider.textEmbedding = createEmbeddingModel
provider.textEmbeddingModel = createEmbeddingModel
provider.image = createImageModel
provider.imageModel = createImageModel
provider.transcription = createTranscriptionModel
provider.transcriptionModel = createTranscriptionModel
provider.speech = createSpeechModel
provider.speechModel = createSpeechModel
return provider
}
export const cherryIn = createCherryIn()

View File

@@ -0,0 +1 @@
export * from './cherryin-provider'

View File

@@ -0,0 +1,19 @@
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"declaration": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "bundler",
"noEmitOnError": false,
"outDir": "./dist",
"resolveJsonModule": true,
"rootDir": "./src",
"skipLibCheck": true,
"strict": true,
"target": "ES2020"
},
"exclude": ["node_modules", "dist"],
"include": ["src/**/*"]
}

View File

@@ -0,0 +1,12 @@
import { defineConfig } from 'tsdown'
export default defineConfig({
entry: {
index: 'src/index.ts'
},
outDir: 'dist',
format: ['esm', 'cjs'],
clean: true,
dts: true,
tsconfig: 'tsconfig.json'
})

View File

@@ -71,7 +71,7 @@ Cherry Studio AI Core 是一个基于 Vercel AI SDK 的统一 AI Provider 接口
## 安装
```bash
npm install @cherrystudio/ai-core ai
npm install @cherrystudio/ai-core ai @ai-sdk/google @ai-sdk/openai
```
### React Native

View File

@@ -1,6 +1,6 @@
{
"name": "@cherrystudio/ai-core",
"version": "1.0.1",
"version": "1.0.9",
"description": "Cherry Studio AI Core - Unified AI Provider Interface Based on Vercel AI SDK",
"main": "dist/index.js",
"module": "dist/index.mjs",
@@ -33,17 +33,19 @@
},
"homepage": "https://github.com/CherryHQ/cherry-studio#readme",
"peerDependencies": {
"@ai-sdk/google": "^2.0.36",
"@ai-sdk/openai": "^2.0.64",
"@cherrystudio/ai-sdk-provider": "^0.1.3",
"ai": "^5.0.26"
},
"dependencies": {
"@ai-sdk/anthropic": "^2.0.27",
"@ai-sdk/azure": "^2.0.49",
"@ai-sdk/deepseek": "^1.0.23",
"@ai-sdk/openai": "^2.0.48",
"@ai-sdk/openai-compatible": "^1.0.22",
"@ai-sdk/anthropic": "^2.0.49",
"@ai-sdk/azure": "^2.0.74",
"@ai-sdk/deepseek": "^1.0.29",
"@ai-sdk/openai-compatible": "patch:@ai-sdk/openai-compatible@npm%3A1.0.27#~/.yarn/patches/@ai-sdk-openai-compatible-npm-1.0.27-06f74278cf.patch",
"@ai-sdk/provider": "^2.0.0",
"@ai-sdk/provider-utils": "^3.0.12",
"@ai-sdk/xai": "^2.0.26",
"@ai-sdk/provider-utils": "^3.0.17",
"@ai-sdk/xai": "^2.0.36",
"zod": "^4.1.5"
},
"devDependencies": {

View File

@@ -0,0 +1,180 @@
/**
* Mock Provider Instances
* Provides mock implementations for all supported AI providers
*/
import type { ImageModelV2, LanguageModelV2 } from '@ai-sdk/provider'
import { vi } from 'vitest'
/**
* Creates a mock language model with customizable behavior
*/
export function createMockLanguageModel(overrides?: Partial<LanguageModelV2>): LanguageModelV2 {
return {
specificationVersion: 'v1',
provider: 'mock-provider',
modelId: 'mock-model',
defaultObjectGenerationMode: 'tool',
doGenerate: vi.fn().mockResolvedValue({
text: 'Mock response text',
finishReason: 'stop',
usage: {
promptTokens: 10,
completionTokens: 20,
totalTokens: 30
},
rawCall: { rawPrompt: null, rawSettings: {} },
rawResponse: { headers: {} },
warnings: []
}),
doStream: vi.fn().mockReturnValue({
stream: (async function* () {
yield {
type: 'text-delta',
textDelta: 'Mock '
}
yield {
type: 'text-delta',
textDelta: 'streaming '
}
yield {
type: 'text-delta',
textDelta: 'response'
}
yield {
type: 'finish',
finishReason: 'stop',
usage: {
promptTokens: 10,
completionTokens: 15,
totalTokens: 25
}
}
})(),
rawCall: { rawPrompt: null, rawSettings: {} },
rawResponse: { headers: {} },
warnings: []
}),
...overrides
} as LanguageModelV2
}
/**
* Creates a mock image model with customizable behavior
*/
export function createMockImageModel(overrides?: Partial<ImageModelV2>): ImageModelV2 {
return {
specificationVersion: 'v2',
provider: 'mock-provider',
modelId: 'mock-image-model',
doGenerate: vi.fn().mockResolvedValue({
images: [
{
base64: 'mock-base64-image-data',
uint8Array: new Uint8Array([1, 2, 3, 4, 5]),
mimeType: 'image/png'
}
],
warnings: []
}),
...overrides
} as ImageModelV2
}
/**
* Mock provider configurations for testing
*/
export const mockProviderConfigs = {
openai: {
apiKey: 'sk-test-openai-key-123456789',
baseURL: 'https://api.openai.com/v1',
organization: 'test-org'
},
anthropic: {
apiKey: 'sk-ant-test-key-123456789',
baseURL: 'https://api.anthropic.com'
},
google: {
apiKey: 'test-google-api-key-123456789',
baseURL: 'https://generativelanguage.googleapis.com/v1'
},
xai: {
apiKey: 'xai-test-key-123456789',
baseURL: 'https://api.x.ai/v1'
},
azure: {
apiKey: 'test-azure-key-123456789',
resourceName: 'test-resource',
deployment: 'test-deployment'
},
deepseek: {
apiKey: 'sk-test-deepseek-key-123456789',
baseURL: 'https://api.deepseek.com/v1'
},
openrouter: {
apiKey: 'sk-or-test-key-123456789',
baseURL: 'https://openrouter.ai/api/v1'
},
huggingface: {
apiKey: 'hf_test_key_123456789',
baseURL: 'https://api-inference.huggingface.co'
},
'openai-compatible': {
apiKey: 'test-compatible-key-123456789',
baseURL: 'https://api.example.com/v1',
name: 'test-provider'
},
'openai-chat': {
apiKey: 'sk-test-chat-key-123456789',
baseURL: 'https://api.openai.com/v1'
}
} as const
/**
* Mock provider instances for testing
*/
export const mockProviderInstances = {
openai: {
name: 'openai-mock',
languageModel: createMockLanguageModel({ provider: 'openai', modelId: 'gpt-4' }),
imageModel: createMockImageModel({ provider: 'openai', modelId: 'dall-e-3' })
},
anthropic: {
name: 'anthropic-mock',
languageModel: createMockLanguageModel({ provider: 'anthropic', modelId: 'claude-3-5-sonnet-20241022' })
},
google: {
name: 'google-mock',
languageModel: createMockLanguageModel({ provider: 'google', modelId: 'gemini-2.0-flash-exp' }),
imageModel: createMockImageModel({ provider: 'google', modelId: 'imagen-3.0-generate-001' })
},
xai: {
name: 'xai-mock',
languageModel: createMockLanguageModel({ provider: 'xai', modelId: 'grok-2-latest' }),
imageModel: createMockImageModel({ provider: 'xai', modelId: 'grok-2-image-latest' })
},
deepseek: {
name: 'deepseek-mock',
languageModel: createMockLanguageModel({ provider: 'deepseek', modelId: 'deepseek-chat' })
}
}
export type ProviderId = keyof typeof mockProviderConfigs

View File

@@ -0,0 +1,238 @@
/**
* Mock Responses
* Provides realistic mock responses for all provider types
*/
import type { ModelMessage, Tool } from 'ai'
import { jsonSchema } from 'ai'
/**
* Standard test messages for all scenarios
*/
export const testMessages: Record<string, ModelMessage[]> = {
simple: [{ role: 'user' as const, content: 'Hello, how are you?' }],
conversation: [
{ role: 'user' as const, content: 'What is the capital of France?' },
{ role: 'assistant' as const, content: 'The capital of France is Paris.' },
{ role: 'user' as const, content: 'What is its population?' }
],
withSystem: [
{ role: 'system' as const, content: 'You are a helpful assistant that provides concise answers.' },
{ role: 'user' as const, content: 'Explain quantum computing in one sentence.' }
],
withImages: [
{
role: 'user' as const,
content: [
{ type: 'text' as const, text: 'What is in this image?' },
{
type: 'image' as const,
image:
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=='
}
]
}
],
toolUse: [{ role: 'user' as const, content: 'What is the weather in San Francisco?' }],
multiTurn: [
{ role: 'user' as const, content: 'Can you help me with a math problem?' },
{ role: 'assistant' as const, content: 'Of course! What math problem would you like help with?' },
{ role: 'user' as const, content: 'What is 15 * 23?' },
{ role: 'assistant' as const, content: '15 * 23 = 345' },
{ role: 'user' as const, content: 'Now divide that by 5' }
]
}
/**
* Standard test tools for tool calling scenarios
*/
export const testTools: Record<string, Tool> = {
getWeather: {
description: 'Get the current weather in a given location',
inputSchema: jsonSchema({
type: 'object',
properties: {
location: {
type: 'string',
description: 'The city and state, e.g. San Francisco, CA'
},
unit: {
type: 'string',
enum: ['celsius', 'fahrenheit'],
description: 'The temperature unit to use'
}
},
required: ['location']
}),
execute: async ({ location, unit = 'fahrenheit' }) => {
return {
location,
temperature: unit === 'celsius' ? 22 : 72,
unit,
condition: 'sunny'
}
}
},
calculate: {
description: 'Perform a mathematical calculation',
inputSchema: jsonSchema({
type: 'object',
properties: {
operation: {
type: 'string',
enum: ['add', 'subtract', 'multiply', 'divide'],
description: 'The operation to perform'
},
a: {
type: 'number',
description: 'The first number'
},
b: {
type: 'number',
description: 'The second number'
}
},
required: ['operation', 'a', 'b']
}),
execute: async ({ operation, a, b }) => {
const operations = {
add: (x: number, y: number) => x + y,
subtract: (x: number, y: number) => x - y,
multiply: (x: number, y: number) => x * y,
divide: (x: number, y: number) => x / y
}
return { result: operations[operation as keyof typeof operations](a, b) }
}
},
searchDatabase: {
description: 'Search for information in a database',
inputSchema: jsonSchema({
type: 'object',
properties: {
query: {
type: 'string',
description: 'The search query'
},
limit: {
type: 'number',
description: 'Maximum number of results to return',
default: 10
}
},
required: ['query']
}),
execute: async ({ query, limit = 10 }) => {
return {
results: [
{ id: 1, title: `Result 1 for ${query}`, relevance: 0.95 },
{ id: 2, title: `Result 2 for ${query}`, relevance: 0.87 }
].slice(0, limit)
}
}
}
}
/**
* Mock complete responses for non-streaming scenarios
* Note: AI SDK v5 uses inputTokens/outputTokens instead of promptTokens/completionTokens
*/
export const mockCompleteResponses = {
simple: {
text: 'This is a simple response.',
finishReason: 'stop' as const,
usage: {
inputTokens: 15,
outputTokens: 8,
totalTokens: 23
}
},
withToolCalls: {
text: 'I will check the weather for you.',
toolCalls: [
{
toolCallId: 'call_456',
toolName: 'getWeather',
args: { location: 'New York, NY', unit: 'celsius' }
}
],
finishReason: 'tool-calls' as const,
usage: {
inputTokens: 25,
outputTokens: 12,
totalTokens: 37
}
},
withWarnings: {
text: 'Response with warnings.',
finishReason: 'stop' as const,
usage: {
inputTokens: 10,
outputTokens: 5,
totalTokens: 15
},
warnings: [
{
type: 'unsupported-setting' as const,
setting: 'temperature',
details: 'Temperature parameter not supported for this model'
}
]
}
}
/**
* Mock image generation responses
*/
export const mockImageResponses = {
single: {
image: {
base64: 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==',
uint8Array: new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82]),
mimeType: 'image/png' as const
},
warnings: []
},
multiple: {
images: [
{
base64: 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==',
uint8Array: new Uint8Array([137, 80, 78, 71]),
mimeType: 'image/png' as const
},
{
base64: 'iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAEklEQVR42mNk+M9QzwAEjDAGACCKAgdZ9zImAAAAAElFTkSuQmCC',
uint8Array: new Uint8Array([137, 80, 78, 71]),
mimeType: 'image/png' as const
}
],
warnings: []
},
withProviderMetadata: {
image: {
base64: 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==',
uint8Array: new Uint8Array([137, 80, 78, 71]),
mimeType: 'image/png' as const
},
providerMetadata: {
openai: {
images: [
{
revisedPrompt: 'A detailed and enhanced version of the original prompt'
}
]
}
},
warnings: []
}
}

View File

@@ -0,0 +1,329 @@
/**
* Provider-Specific Test Utilities
* Helper functions for testing individual providers with all their parameters
*/
import type { Tool } from 'ai'
import { expect } from 'vitest'
/**
* Provider parameter configurations for comprehensive testing
*/
export const providerParameterMatrix = {
openai: {
models: ['gpt-4', 'gpt-4-turbo', 'gpt-3.5-turbo', 'gpt-4o'],
parameters: {
temperature: [0, 0.5, 0.7, 1.0, 1.5, 2.0],
maxTokens: [100, 500, 1000, 2000, 4000],
topP: [0.1, 0.5, 0.9, 1.0],
frequencyPenalty: [-2.0, -1.0, 0, 1.0, 2.0],
presencePenalty: [-2.0, -1.0, 0, 1.0, 2.0],
stop: [undefined, ['stop'], ['STOP', 'END']],
seed: [undefined, 12345, 67890],
responseFormat: [undefined, { type: 'json_object' as const }],
user: [undefined, 'test-user-123']
},
toolChoice: ['auto', 'required', 'none', { type: 'function' as const, name: 'getWeather' }],
parallelToolCalls: [true, false]
},
anthropic: {
models: ['claude-3-5-sonnet-20241022', 'claude-3-opus-20240229', 'claude-3-haiku-20240307'],
parameters: {
temperature: [0, 0.5, 1.0],
maxTokens: [100, 1000, 4000, 8000],
topP: [0.1, 0.5, 0.9, 1.0],
topK: [undefined, 1, 5, 10, 40],
stop: [undefined, ['Human:', 'Assistant:']],
metadata: [undefined, { userId: 'test-123' }]
},
toolChoice: ['auto', 'any', { type: 'tool' as const, name: 'getWeather' }]
},
google: {
models: ['gemini-2.0-flash-exp', 'gemini-1.5-pro', 'gemini-1.5-flash'],
parameters: {
temperature: [0, 0.5, 0.9, 1.0],
maxTokens: [100, 1000, 2000, 8000],
topP: [0.1, 0.5, 0.95, 1.0],
topK: [undefined, 1, 16, 40],
stopSequences: [undefined, ['END'], ['STOP', 'TERMINATE']]
},
safetySettings: [
undefined,
[
{ category: 'HARM_CATEGORY_HARASSMENT', threshold: 'BLOCK_MEDIUM_AND_ABOVE' },
{ category: 'HARM_CATEGORY_HATE_SPEECH', threshold: 'BLOCK_ONLY_HIGH' }
]
]
},
xai: {
models: ['grok-2-latest', 'grok-2-1212'],
parameters: {
temperature: [0, 0.5, 1.0, 1.5],
maxTokens: [100, 500, 2000, 4000],
topP: [0.1, 0.5, 0.9, 1.0],
stop: [undefined, ['STOP'], ['END', 'TERMINATE']],
seed: [undefined, 12345]
}
},
deepseek: {
models: ['deepseek-chat', 'deepseek-coder'],
parameters: {
temperature: [0, 0.5, 1.0],
maxTokens: [100, 1000, 4000],
topP: [0.1, 0.5, 0.95],
frequencyPenalty: [0, 0.5, 1.0],
presencePenalty: [0, 0.5, 1.0],
stop: [undefined, ['```'], ['END']]
}
},
azure: {
deployments: ['gpt-4-deployment', 'gpt-35-turbo-deployment'],
parameters: {
temperature: [0, 0.7, 1.0],
maxTokens: [100, 1000, 2000],
topP: [0.1, 0.5, 0.95],
frequencyPenalty: [0, 1.0],
presencePenalty: [0, 1.0],
stop: [undefined, ['STOP']]
}
}
} as const
/**
* Creates test cases for all parameter combinations
*/
export function generateParameterTestCases<T extends Record<string, any[]>>(
params: T,
maxCombinations = 50
): Array<Partial<{ [K in keyof T]: T[K][number] }>> {
const keys = Object.keys(params) as Array<keyof T>
const testCases: Array<Partial<{ [K in keyof T]: T[K][number] }>> = []
// Generate combinations using sampling strategy for large parameter spaces
const totalCombinations = keys.reduce((acc, key) => acc * params[key].length, 1)
if (totalCombinations <= maxCombinations) {
// Generate all combinations if total is small
generateAllCombinations(params, keys, 0, {}, testCases)
} else {
// Sample diverse combinations if total is large
generateSampledCombinations(params, keys, maxCombinations, testCases)
}
return testCases
}
function generateAllCombinations<T extends Record<string, any[]>>(
params: T,
keys: Array<keyof T>,
index: number,
current: Partial<{ [K in keyof T]: T[K][number] }>,
results: Array<Partial<{ [K in keyof T]: T[K][number] }>>
) {
if (index === keys.length) {
results.push({ ...current })
return
}
const key = keys[index]
for (const value of params[key]) {
generateAllCombinations(params, keys, index + 1, { ...current, [key]: value }, results)
}
}
function generateSampledCombinations<T extends Record<string, any[]>>(
params: T,
keys: Array<keyof T>,
count: number,
results: Array<Partial<{ [K in keyof T]: T[K][number] }>>
) {
// Generate edge cases first (min/max values)
const edgeCase1: any = {}
const edgeCase2: any = {}
for (const key of keys) {
edgeCase1[key] = params[key][0]
edgeCase2[key] = params[key][params[key].length - 1]
}
results.push(edgeCase1, edgeCase2)
// Generate random combinations for the rest
for (let i = results.length; i < count; i++) {
const combination: any = {}
for (const key of keys) {
const values = params[key]
combination[key] = values[Math.floor(Math.random() * values.length)]
}
results.push(combination)
}
}
/**
* Validates that all provider-specific parameters are correctly passed through
*/
export function validateProviderParams(providerId: string, actualParams: any, expectedParams: any): void {
const requiredFields: Record<string, string[]> = {
openai: ['model', 'messages'],
anthropic: ['model', 'messages'],
google: ['model', 'contents'],
xai: ['model', 'messages'],
deepseek: ['model', 'messages'],
azure: ['messages']
}
const fields = requiredFields[providerId] || ['model', 'messages']
for (const field of fields) {
expect(actualParams).toHaveProperty(field)
}
// Validate optional parameters if they were provided
const optionalParams = ['temperature', 'max_tokens', 'top_p', 'stop', 'tools']
for (const param of optionalParams) {
if (expectedParams[param] !== undefined) {
expect(actualParams[param]).toEqual(expectedParams[param])
}
}
}
/**
* Creates a comprehensive test suite for a provider
*/
// oxlint-disable-next-line no-unused-vars
export function createProviderTestSuite(_providerId: string) {
return {
testBasicCompletion: async (executor: any, model: string) => {
const result = await executor.generateText({
model,
messages: [{ role: 'user' as const, content: 'Hello' }]
})
expect(result).toBeDefined()
expect(result.text).toBeDefined()
expect(typeof result.text).toBe('string')
},
testStreaming: async (executor: any, model: string) => {
const chunks: any[] = []
const result = await executor.streamText({
model,
messages: [{ role: 'user' as const, content: 'Hello' }]
})
for await (const chunk of result.textStream) {
chunks.push(chunk)
}
expect(chunks.length).toBeGreaterThan(0)
},
testTemperature: async (executor: any, model: string, temperatures: number[]) => {
for (const temperature of temperatures) {
const result = await executor.generateText({
model,
messages: [{ role: 'user' as const, content: 'Hello' }],
temperature
})
expect(result).toBeDefined()
}
},
testMaxTokens: async (executor: any, model: string, maxTokensValues: number[]) => {
for (const maxTokens of maxTokensValues) {
const result = await executor.generateText({
model,
messages: [{ role: 'user' as const, content: 'Hello' }],
maxTokens
})
expect(result).toBeDefined()
if (result.usage?.completionTokens) {
expect(result.usage.completionTokens).toBeLessThanOrEqual(maxTokens)
}
}
},
testToolCalling: async (executor: any, model: string, tools: Record<string, Tool>) => {
const result = await executor.generateText({
model,
messages: [{ role: 'user' as const, content: 'What is the weather in SF?' }],
tools
})
expect(result).toBeDefined()
},
testStopSequences: async (executor: any, model: string, stopSequences: string[][]) => {
for (const stop of stopSequences) {
const result = await executor.generateText({
model,
messages: [{ role: 'user' as const, content: 'Count to 10' }],
stop
})
expect(result).toBeDefined()
}
}
}
}
/**
* Generates test data for vision/multimodal testing
*/
export function createVisionTestData() {
return {
imageUrl: 'https://example.com/test-image.jpg',
base64Image:
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==',
messages: [
{
role: 'user' as const,
content: [
{ type: 'text' as const, text: 'What is in this image?' },
{
type: 'image' as const,
image:
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=='
}
]
}
]
}
}
/**
* Creates mock responses for different finish reasons
*/
export function createFinishReasonMocks() {
return {
stop: {
text: 'Complete response.',
finishReason: 'stop' as const,
usage: { promptTokens: 10, completionTokens: 5, totalTokens: 15 }
},
length: {
text: 'Incomplete response due to',
finishReason: 'length' as const,
usage: { promptTokens: 10, completionTokens: 100, totalTokens: 110 }
},
'tool-calls': {
text: 'Calling tools',
finishReason: 'tool-calls' as const,
toolCalls: [{ toolCallId: 'call_1', toolName: 'getWeather', args: { location: 'SF' } }],
usage: { promptTokens: 10, completionTokens: 8, totalTokens: 18 }
},
'content-filter': {
text: '',
finishReason: 'content-filter' as const,
usage: { promptTokens: 10, completionTokens: 0, totalTokens: 10 }
}
}
}

View File

@@ -0,0 +1,291 @@
/**
* Test Utilities
* Helper functions for testing AI Core functionality
*/
import { expect, vi } from 'vitest'
import type { ProviderId } from '../fixtures/mock-providers'
import { createMockImageModel, createMockLanguageModel, mockProviderConfigs } from '../fixtures/mock-providers'
/**
* Creates a test provider with streaming support
*/
export function createTestStreamingProvider(chunks: any[]) {
return createMockLanguageModel({
doStream: vi.fn().mockReturnValue({
stream: (async function* () {
for (const chunk of chunks) {
yield chunk
}
})(),
rawCall: { rawPrompt: null, rawSettings: {} },
rawResponse: { headers: {} },
warnings: []
})
})
}
/**
* Creates a test provider that throws errors
*/
export function createErrorProvider(error: Error) {
return createMockLanguageModel({
doGenerate: vi.fn().mockRejectedValue(error),
doStream: vi.fn().mockImplementation(() => {
throw error
})
})
}
/**
* Collects all chunks from a stream
*/
export async function collectStreamChunks<T>(stream: AsyncIterable<T>): Promise<T[]> {
const chunks: T[] = []
for await (const chunk of stream) {
chunks.push(chunk)
}
return chunks
}
/**
* Waits for a specific number of milliseconds
*/
export function wait(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms))
}
/**
* Creates a mock abort controller that aborts after a delay
*/
export function createDelayedAbortController(delayMs: number): AbortController {
const controller = new AbortController()
setTimeout(() => controller.abort(), delayMs)
return controller
}
/**
* Asserts that a function throws an error with a specific message
*/
export async function expectError(fn: () => Promise<any>, expectedMessage?: string | RegExp): Promise<Error> {
try {
await fn()
throw new Error('Expected function to throw an error, but it did not')
} catch (error) {
if (expectedMessage) {
const message = (error as Error).message
if (typeof expectedMessage === 'string') {
if (!message.includes(expectedMessage)) {
throw new Error(`Expected error message to include "${expectedMessage}", but got "${message}"`)
}
} else {
if (!expectedMessage.test(message)) {
throw new Error(`Expected error message to match ${expectedMessage}, but got "${message}"`)
}
}
}
return error as Error
}
}
/**
* Creates a spy function that tracks calls and arguments
*/
export function createSpy<T extends (...args: any[]) => any>() {
const calls: Array<{ args: Parameters<T>; result?: ReturnType<T>; error?: Error }> = []
const spy = vi.fn((...args: Parameters<T>) => {
try {
const result = undefined as ReturnType<T>
calls.push({ args, result })
return result
} catch (error) {
calls.push({ args, error: error as Error })
throw error
}
})
return {
fn: spy,
calls,
getCalls: () => calls,
getCallCount: () => calls.length,
getLastCall: () => calls[calls.length - 1],
reset: () => {
calls.length = 0
spy.mockClear()
}
}
}
/**
* Validates provider configuration
*/
export function validateProviderConfig(providerId: ProviderId) {
const config = mockProviderConfigs[providerId]
if (!config) {
throw new Error(`No mock configuration found for provider: ${providerId}`)
}
if (!config.apiKey) {
throw new Error(`Provider ${providerId} is missing apiKey in mock config`)
}
return config
}
/**
* Creates a test context with common setup
*/
export function createTestContext() {
const mocks = {
languageModel: createMockLanguageModel(),
imageModel: createMockImageModel(),
providers: new Map<string, any>()
}
const cleanup = () => {
mocks.providers.clear()
vi.clearAllMocks()
}
return {
mocks,
cleanup
}
}
/**
* Measures execution time of an async function
*/
export async function measureTime<T>(fn: () => Promise<T>): Promise<{ result: T; duration: number }> {
const start = Date.now()
const result = await fn()
const duration = Date.now() - start
return { result, duration }
}
/**
* Retries a function until it succeeds or max attempts reached
*/
export async function retryUntilSuccess<T>(fn: () => Promise<T>, maxAttempts = 3, delayMs = 100): Promise<T> {
let lastError: Error | undefined
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await fn()
} catch (error) {
lastError = error as Error
if (attempt < maxAttempts) {
await wait(delayMs)
}
}
}
throw lastError || new Error('All retry attempts failed')
}
/**
* Creates a mock streaming response that emits chunks at intervals
*/
export function createTimedStream<T>(chunks: T[], intervalMs = 10) {
return {
async *[Symbol.asyncIterator]() {
for (const chunk of chunks) {
await wait(intervalMs)
yield chunk
}
}
}
}
/**
* Asserts that two objects are deeply equal, ignoring specified keys
*/
export function assertDeepEqualIgnoring<T extends Record<string, any>>(
actual: T,
expected: T,
ignoreKeys: string[] = []
): void {
const filterKeys = (obj: T): Partial<T> => {
const filtered = { ...obj }
for (const key of ignoreKeys) {
delete filtered[key]
}
return filtered
}
const filteredActual = filterKeys(actual)
const filteredExpected = filterKeys(expected)
expect(filteredActual).toEqual(filteredExpected)
}
/**
* Creates a provider mock that simulates rate limiting
*/
export function createRateLimitedProvider(limitPerSecond: number) {
const calls: number[] = []
return createMockLanguageModel({
doGenerate: vi.fn().mockImplementation(async () => {
const now = Date.now()
calls.push(now)
// Remove calls older than 1 second
const recentCalls = calls.filter((time) => now - time < 1000)
if (recentCalls.length > limitPerSecond) {
throw new Error('Rate limit exceeded')
}
return {
text: 'Rate limited response',
finishReason: 'stop' as const,
usage: { promptTokens: 10, completionTokens: 5, totalTokens: 15 },
rawCall: { rawPrompt: null, rawSettings: {} },
rawResponse: { headers: {} },
warnings: []
}
})
})
}
/**
* Validates streaming response structure
*/
export function validateStreamChunk(chunk: any): void {
expect(chunk).toBeDefined()
expect(chunk).toHaveProperty('type')
if (chunk.type === 'text-delta') {
expect(chunk).toHaveProperty('textDelta')
expect(typeof chunk.textDelta).toBe('string')
} else if (chunk.type === 'finish') {
expect(chunk).toHaveProperty('finishReason')
expect(chunk).toHaveProperty('usage')
} else if (chunk.type === 'tool-call') {
expect(chunk).toHaveProperty('toolCallId')
expect(chunk).toHaveProperty('toolName')
expect(chunk).toHaveProperty('args')
}
}
/**
* Creates a test logger that captures log messages
*/
export function createTestLogger() {
const logs: Array<{ level: string; message: string; meta?: any }> = []
return {
info: (message: string, meta?: any) => logs.push({ level: 'info', message, meta }),
warn: (message: string, meta?: any) => logs.push({ level: 'warn', message, meta }),
error: (message: string, meta?: any) => logs.push({ level: 'error', message, meta }),
debug: (message: string, meta?: any) => logs.push({ level: 'debug', message, meta }),
getLogs: () => logs,
clear: () => {
logs.length = 0
}
}
}

View File

@@ -0,0 +1,12 @@
/**
* Test Infrastructure Exports
* Central export point for all test utilities, fixtures, and helpers
*/
// Fixtures
export * from './fixtures/mock-providers'
export * from './fixtures/mock-responses'
// Helpers
export * from './helpers/provider-test-utils'
export * from './helpers/test-utils'

View File

@@ -0,0 +1,35 @@
/**
* Mock for @cherrystudio/ai-sdk-provider
* This mock is used in tests to avoid importing the actual package
*/
export type CherryInProviderSettings = {
apiKey?: string
baseURL?: string
}
// oxlint-disable-next-line no-unused-vars
export const createCherryIn = (_options?: CherryInProviderSettings) => ({
// oxlint-disable-next-line no-unused-vars
languageModel: (_modelId: string) => ({
specificationVersion: 'v1',
provider: 'cherryin',
modelId: 'mock-model',
doGenerate: async () => ({ text: 'mock response' }),
doStream: async () => ({ stream: (async function* () {})() })
}),
// oxlint-disable-next-line no-unused-vars
chat: (_modelId: string) => ({
specificationVersion: 'v1',
provider: 'cherryin-chat',
modelId: 'mock-model',
doGenerate: async () => ({ text: 'mock response' }),
doStream: async () => ({ stream: (async function* () {})() })
}),
// oxlint-disable-next-line no-unused-vars
textEmbeddingModel: (_modelId: string) => ({
specificationVersion: 'v1',
provider: 'cherryin',
modelId: 'mock-embedding-model'
})
})

View File

@@ -0,0 +1,9 @@
/**
* Vitest Setup File
* Global test configuration and mocks for @cherrystudio/ai-core package
*/
// Mock Vite SSR helper to avoid Node environment errors
;(globalThis as any).__vite_ssr_exportName__ = (_name: string, value: any) => value
// Note: @cherrystudio/ai-sdk-provider is mocked via alias in vitest.config.ts

View File

@@ -2,7 +2,7 @@
* 中间件管理器
* 专注于 AI SDK 中间件的管理,与插件系统分离
*/
import { LanguageModelV2Middleware } from '@ai-sdk/provider'
import type { LanguageModelV2Middleware } from '@ai-sdk/provider'
/**
* 创建中间件列表

View File

@@ -1,7 +1,7 @@
/**
* 中间件系统类型定义
*/
import { LanguageModelV2Middleware } from '@ai-sdk/provider'
import type { LanguageModelV2Middleware } from '@ai-sdk/provider'
/**
* 具名中间件接口

View File

@@ -2,7 +2,7 @@
* 模型包装工具函数
* 用于将中间件应用到LanguageModel上
*/
import { LanguageModelV2, LanguageModelV2Middleware } from '@ai-sdk/provider'
import type { LanguageModelV2, LanguageModelV2Middleware } from '@ai-sdk/provider'
import { wrapLanguageModel } from 'ai'
/**

View File

@@ -5,7 +5,7 @@
* 集成了来自 ModelCreator 的特殊处理逻辑
*/
import { EmbeddingModelV2, ImageModelV2, LanguageModelV2, LanguageModelV2Middleware } from '@ai-sdk/provider'
import type { EmbeddingModelV2, ImageModelV2, LanguageModelV2, LanguageModelV2Middleware } from '@ai-sdk/provider'
import { wrapModelWithMiddlewares } from '../middleware/wrapper'
import { DEFAULT_SEPARATOR, globalRegistryManagement } from '../providers/RegistryManagement'

View File

@@ -1,7 +1,7 @@
/**
* Creation 模块类型定义
*/
import { LanguageModelV2Middleware } from '@ai-sdk/provider'
import type { LanguageModelV2Middleware } from '@ai-sdk/provider'
import type { ProviderId, ProviderSettingsMap } from '../providers/types'

View File

@@ -0,0 +1,109 @@
import { describe, expect, it } from 'vitest'
import { createOpenAIOptions, createOpenRouterOptions, mergeProviderOptions } from '../factory'
describe('mergeProviderOptions', () => {
it('deep merges provider options for the same provider', () => {
const reasoningOptions = createOpenRouterOptions({
reasoning: {
enabled: true,
effort: 'medium'
}
})
const webSearchOptions = createOpenRouterOptions({
plugins: [{ id: 'web', max_results: 5 }]
})
const merged = mergeProviderOptions(reasoningOptions, webSearchOptions)
expect(merged.openrouter).toEqual({
reasoning: {
enabled: true,
effort: 'medium'
},
plugins: [{ id: 'web', max_results: 5 }]
})
})
it('preserves options from other providers while merging', () => {
const openRouter = createOpenRouterOptions({
reasoning: { enabled: true }
})
const openAI = createOpenAIOptions({
reasoningEffort: 'low'
})
const merged = mergeProviderOptions(openRouter, openAI)
expect(merged.openrouter).toEqual({ reasoning: { enabled: true } })
expect(merged.openai).toEqual({ reasoningEffort: 'low' })
})
it('overwrites primitive values with later values', () => {
const first = createOpenAIOptions({
reasoningEffort: 'low',
user: 'user-123'
})
const second = createOpenAIOptions({
reasoningEffort: 'high',
maxToolCalls: 5
})
const merged = mergeProviderOptions(first, second)
expect(merged.openai).toEqual({
reasoningEffort: 'high', // overwritten by second
user: 'user-123', // preserved from first
maxToolCalls: 5 // added from second
})
})
it('overwrites arrays with later values instead of merging', () => {
const first = createOpenRouterOptions({
models: ['gpt-4', 'gpt-3.5-turbo']
})
const second = createOpenRouterOptions({
models: ['claude-3-opus', 'claude-3-sonnet']
})
const merged = mergeProviderOptions(first, second)
// Array is completely replaced, not merged
expect(merged.openrouter?.models).toEqual(['claude-3-opus', 'claude-3-sonnet'])
})
it('deeply merges nested objects while overwriting primitives', () => {
const first = createOpenRouterOptions({
reasoning: {
enabled: true,
effort: 'low'
},
user: 'user-123'
})
const second = createOpenRouterOptions({
reasoning: {
effort: 'high',
max_tokens: 500
},
user: 'user-456'
})
const merged = mergeProviderOptions(first, second)
expect(merged.openrouter).toEqual({
reasoning: {
enabled: true, // preserved from first
effort: 'high', // overwritten by second
max_tokens: 500 // added from second
},
user: 'user-456' // overwritten by second
})
})
it('replaces arrays instead of merging them', () => {
const first = createOpenRouterOptions({ plugins: [{ id: 'old' }] })
const second = createOpenRouterOptions({ plugins: [{ id: 'new' }] })
const merged = mergeProviderOptions(first, second)
// @ts-expect-error type-check for openrouter options is skipped. see function signature of createOpenRouterOptions
expect(merged.openrouter?.plugins).toEqual([{ id: 'new' }])
})
})

View File

@@ -1,4 +1,4 @@
import { ExtractProviderOptions, ProviderOptionsMap, TypedProviderOptions } from './types'
import type { ExtractProviderOptions, ProviderOptionsMap, TypedProviderOptions } from './types'
/**
* 创建特定供应商的选项
@@ -26,13 +26,65 @@ export function createGenericProviderOptions<T extends string>(
return { [provider]: options } as Record<T, Record<string, any>>
}
type PlainObject = Record<string, any>
const isPlainObject = (value: unknown): value is PlainObject => {
return typeof value === 'object' && value !== null && !Array.isArray(value)
}
function deepMergeObjects<T extends PlainObject>(target: T, source: PlainObject): T {
const result: PlainObject = { ...target }
Object.entries(source).forEach(([key, value]) => {
if (isPlainObject(value) && isPlainObject(result[key])) {
result[key] = deepMergeObjects(result[key], value)
} else {
result[key] = value
}
})
return result as T
}
/**
* 合并多个供应商的options
* @param optionsMap 包含多个供应商选项的对象
* @returns 合并后的TypedProviderOptions
* Deep-merge multiple provider-specific options.
* Nested objects are recursively merged; primitive values are overwritten.
*
* When the same key appears in multiple options:
* - If both values are plain objects: they are deeply merged (recursive merge)
* - If values are primitives/arrays: the later value overwrites the earlier one
*
* @example
* mergeProviderOptions(
* { openrouter: { reasoning: { enabled: true, effort: 'low' }, user: 'user-123' } },
* { openrouter: { reasoning: { effort: 'high', max_tokens: 500 }, models: ['gpt-4'] } }
* )
* // Result: {
* // openrouter: {
* // reasoning: { enabled: true, effort: 'high', max_tokens: 500 },
* // user: 'user-123',
* // models: ['gpt-4']
* // }
* // }
*
* @param optionsMap Objects containing options for multiple providers
* @returns Fully merged TypedProviderOptions
*/
export function mergeProviderOptions(...optionsMap: Partial<TypedProviderOptions>[]): TypedProviderOptions {
return Object.assign({}, ...optionsMap)
return optionsMap.reduce<TypedProviderOptions>((acc, options) => {
if (!options) {
return acc
}
Object.entries(options).forEach(([providerId, providerOptions]) => {
if (!providerOptions) {
return
}
if (acc[providerId]) {
acc[providerId] = deepMergeObjects(acc[providerId] as PlainObject, providerOptions as PlainObject)
} else {
acc[providerId] = providerOptions as any
}
})
return acc
}, {} as TypedProviderOptions)
}
/**

View File

@@ -4,12 +4,7 @@
*/
export const BUILT_IN_PLUGIN_PREFIX = 'built-in:'
export { googleToolsPlugin } from './googleToolsPlugin'
export { createLoggingPlugin } from './logging'
export { createPromptToolUsePlugin } from './toolUsePlugin/promptToolUsePlugin'
export type {
PromptToolUseConfig,
ToolUseRequestContext,
ToolUseResult
} from './toolUsePlugin/type'
export { webSearchPlugin, type WebSearchPluginConfig } from './webSearchPlugin'
export * from './googleToolsPlugin'
export * from './toolUsePlugin/promptToolUsePlugin'
export * from './toolUsePlugin/type'
export * from './webSearchPlugin'

View File

@@ -10,7 +10,7 @@ import type { AiRequestContext } from '../../types'
import { StreamEventManager } from './StreamEventManager'
import { type TagConfig, TagExtractor } from './tagExtraction'
import { ToolExecutor } from './ToolExecutor'
import { PromptToolUseConfig, ToolUseResult } from './type'
import type { PromptToolUseConfig, ToolUseResult } from './type'
/**
* 工具使用标签配置

Some files were not shown because too many files have changed in this diff Show More