Compare commits
2 Commits
refactor/t
...
feat/claud
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd6d6bd56e | ||
|
|
7a23386de4 |
11
.github/CODEOWNERS
vendored
11
.github/CODEOWNERS
vendored
@@ -1,11 +0,0 @@
|
||||
/src/renderer/src/store/ @0xfullex
|
||||
/src/main/services/ConfigManager.ts @0xfullex
|
||||
/packages/shared/IpcChannel.ts @0xfullex
|
||||
/src/main/ipc.ts @0xfullex
|
||||
|
||||
/migrations/ @0xfullex
|
||||
/packages/shared/data/ @0xfullex
|
||||
/src/main/data/ @0xfullex
|
||||
/src/renderer/src/data/ @0xfullex
|
||||
|
||||
/packages/ui/ @MyPrototypeWhat
|
||||
94
.github/ISSUE_TEMPLATE/#0_bug_report.yml
vendored
Normal file
94
.github/ISSUE_TEMPLATE/#0_bug_report.yml
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
name: 🐛 错误报告 (中文)
|
||||
description: 创建一个报告以帮助我们改进
|
||||
title: '[错误]: '
|
||||
labels: ['BUG']
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
感谢您花时间填写此错误报告!
|
||||
在提交此问题之前,请确保您已经了解了[常见问题](https://docs.cherry-ai.com/question-contact/questions)和[知识科普](https://docs.cherry-ai.com/question-contact/knowledge)
|
||||
|
||||
- type: checkboxes
|
||||
id: checklist
|
||||
attributes:
|
||||
label: 提交前检查
|
||||
description: |
|
||||
在提交 Issue 前请确保您已经完成了以下所有步骤
|
||||
options:
|
||||
- label: 我理解 Issue 是用于反馈和解决问题的,而非吐槽评论区,将尽可能提供更多信息帮助问题解决。
|
||||
required: true
|
||||
- label: 我的问题不是 [常见问题](https://github.com/CherryHQ/cherry-studio/issues/3881) 中的内容。
|
||||
required: true
|
||||
- label: 我已经查看了 **置顶 Issue** 并搜索了现有的 [开放Issue](https://github.com/CherryHQ/cherry-studio/issues)和[已关闭Issue](https://github.com/CherryHQ/cherry-studio/issues?q=is%3Aissue%20state%3Aclosed%20),没有找到类似的问题。
|
||||
required: true
|
||||
- label: 我填写了简短且清晰明确的标题,以便开发者在翻阅 Issue 列表时能快速确定大致问题。而不是“一个建议”、“卡住了”等。
|
||||
required: true
|
||||
- label: 我确认我正在使用最新版本的 Cherry Studio。
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: platform
|
||||
attributes:
|
||||
label: 平台
|
||||
description: 您正在使用哪个平台?
|
||||
options:
|
||||
- Windows
|
||||
- macOS
|
||||
- Linux
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: 版本
|
||||
description: 您正在运行的 Cherry Studio 版本是什么?
|
||||
placeholder: 例如 v1.0.0
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: 错误描述
|
||||
description: 描述问题时请尽可能详细。请尽可能提供截图或屏幕录制,以帮助我们更好地理解问题。
|
||||
placeholder: 告诉我们发生了什么...(记得附上截图/录屏,如果适用)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: reproduction
|
||||
attributes:
|
||||
label: 重现步骤
|
||||
description: 提供详细的重现步骤,以便于我们的开发人员可以准确地重现问题。请尽可能为每个步骤提供截图或屏幕录制。
|
||||
placeholder: |
|
||||
1. 转到 '...'
|
||||
2. 点击 '....'
|
||||
3. 向下滚动到 '....'
|
||||
4. 看到错误
|
||||
|
||||
记得尽可能为每个步骤附上截图/录屏!
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: 预期行为
|
||||
description: 清晰简洁地描述您期望发生的事情
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: 相关日志输出
|
||||
description: 请复制并粘贴任何相关的日志输出
|
||||
render: shell
|
||||
|
||||
- type: textarea
|
||||
id: additional
|
||||
attributes:
|
||||
label: 附加信息
|
||||
description: 任何能让我们对你所遇到的问题有更多了解的东西
|
||||
76
.github/ISSUE_TEMPLATE/#1_feature_request.yml
vendored
Normal file
76
.github/ISSUE_TEMPLATE/#1_feature_request.yml
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
name: 💡 功能建议 (中文)
|
||||
description: 为项目提出新的想法
|
||||
title: '[功能]: '
|
||||
labels: ['feature']
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
感谢您花时间提出新的功能建议!
|
||||
在提交此问题之前,请确保您已经了解了[项目规划](https://docs.cherry-ai.com/cherrystudio/planning)和[功能介绍](https://docs.cherry-ai.com/cherrystudio/preview)
|
||||
|
||||
- type: checkboxes
|
||||
id: checklist
|
||||
attributes:
|
||||
label: 提交前检查
|
||||
description: |
|
||||
在提交 Issue 前请确保您已经完成了以下所有步骤
|
||||
options:
|
||||
- label: 我理解 Issue 是用于反馈和解决问题的,而非吐槽评论区,将尽可能提供更多信息帮助问题解决。
|
||||
required: true
|
||||
- label: 我已经查看了置顶 Issue 并搜索了现有的 [开放Issue](https://github.com/CherryHQ/cherry-studio/issues)和[已关闭Issue](https://github.com/CherryHQ/cherry-studio/issues?q=is%3Aissue%20state%3Aclosed%20),没有找到类似的建议。
|
||||
required: true
|
||||
- label: 我填写了简短且清晰明确的标题,以便开发者在翻阅 Issue 列表时能快速确定大致问题。而不是“一个建议”、“卡住了”等。
|
||||
required: true
|
||||
- label: 最新的 Cherry Studio 版本没有实现我所提出的功能。
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: platform
|
||||
attributes:
|
||||
label: 平台
|
||||
description: 您正在使用哪个平台?
|
||||
options:
|
||||
- Windows
|
||||
- macOS
|
||||
- Linux
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: 版本
|
||||
description: 您正在运行的 Cherry Studio 版本是什么?
|
||||
placeholder: 例如 v1.0.0
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: problem
|
||||
attributes:
|
||||
label: 您的功能建议是否与某个问题/issue相关?
|
||||
description: 请简明扼要地描述您遇到的问题
|
||||
placeholder: 我总是感到沮丧,因为...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: solution
|
||||
attributes:
|
||||
label: 请描述您希望实现的解决方案
|
||||
description: 请简明扼要地描述您希望发生的情况
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: alternatives
|
||||
attributes:
|
||||
label: 请描述您考虑过的其他方案
|
||||
description: 请简明扼要地描述您考虑过的任何其他解决方案或功能
|
||||
|
||||
- type: textarea
|
||||
id: additional
|
||||
attributes:
|
||||
label: 其他补充信息
|
||||
description: 在此添加任何其他与功能建议相关的上下文或截图
|
||||
77
.github/ISSUE_TEMPLATE/#2_question.yml
vendored
Normal file
77
.github/ISSUE_TEMPLATE/#2_question.yml
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
name: ❓ 提问 & 讨论 (中文)
|
||||
description: 寻求帮助、讨论问题、提出疑问等...
|
||||
title: '[讨论]: '
|
||||
labels: ['discussion', 'help wanted']
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
感谢您的提问!请尽可能详细地描述您的问题,这样我们才能更好地帮助您。
|
||||
|
||||
- type: checkboxes
|
||||
id: checklist
|
||||
attributes:
|
||||
label: Issue 检查清单
|
||||
description: |
|
||||
在提交 Issue 前请确保您已经完成了以下所有步骤
|
||||
options:
|
||||
- label: 我理解 Issue 是用于反馈和解决问题的,而非吐槽评论区,将尽可能提供更多信息帮助问题解决。
|
||||
required: true
|
||||
- label: 我确认自己需要的是提出问题并且讨论问题,而不是 Bug 反馈或需求建议。
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: platform
|
||||
attributes:
|
||||
label: 平台
|
||||
description: 您正在使用哪个平台?
|
||||
options:
|
||||
- Windows
|
||||
- macOS
|
||||
- Linux
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: 版本
|
||||
description: 您正在运行的 Cherry Studio 版本是什么?
|
||||
placeholder: 例如 v1.0.0
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: question
|
||||
attributes:
|
||||
label: 您的问题
|
||||
description: 请详细描述您的问题
|
||||
placeholder: 请尽可能清楚地说明您的问题...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: context
|
||||
attributes:
|
||||
label: 相关背景
|
||||
description: 请提供一些背景信息,帮助我们更好地理解您的问题
|
||||
placeholder: 例如:使用场景、已尝试的解决方案等
|
||||
|
||||
- type: textarea
|
||||
id: additional
|
||||
attributes:
|
||||
label: 补充信息
|
||||
description: 任何其他相关的信息、截图或代码示例
|
||||
render: shell
|
||||
|
||||
- type: dropdown
|
||||
id: priority
|
||||
attributes:
|
||||
label: 优先级
|
||||
description: 这个问题对您来说有多紧急?
|
||||
options:
|
||||
- 低 (有空再看)
|
||||
- 中 (希望尽快得到答复)
|
||||
- 高 (阻碍工作进行)
|
||||
validations:
|
||||
required: true
|
||||
76
.github/ISSUE_TEMPLATE/#3_others.yml
vendored
Normal file
76
.github/ISSUE_TEMPLATE/#3_others.yml
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
name: 🤔 其他问题 (中文)
|
||||
description: 提交不属于错误报告或功能需求的问题
|
||||
title: '[其他]: '
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
感谢您花时间提出问题!
|
||||
在提交此问题之前,请确保您已经了解了[常见问题](https://docs.cherry-ai.com/question-contact/questions)和[知识科普](https://docs.cherry-ai.com/question-contact/knowledge)
|
||||
|
||||
- type: checkboxes
|
||||
id: checklist
|
||||
attributes:
|
||||
label: 提交前检查
|
||||
description: |
|
||||
在提交 Issue 前请确保您已经完成了以下所有步骤
|
||||
options:
|
||||
- label: 我理解 Issue 是用于反馈和解决问题的,而非吐槽评论区,将尽可能提供更多信息帮助问题解决。
|
||||
required: true
|
||||
- label: 我已经查看了置顶 Issue 并搜索了现有的 [开放Issue](https://github.com/CherryHQ/cherry-studio/issues)和[已关闭Issue](https://github.com/CherryHQ/cherry-studio/issues?q=is%3Aissue%20state%3Aclosed%20),没有找到类似的问题。
|
||||
required: true
|
||||
- label: 我填写了简短且清晰明确的标题,以便开发者在翻阅 Issue 列表时能快速确定大致问题。而不是"一个问题"、"求助"等。
|
||||
required: true
|
||||
- label: 我的问题不属于错误报告或功能需求类别。
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: platform
|
||||
attributes:
|
||||
label: 平台
|
||||
description: 您正在使用哪个平台?
|
||||
options:
|
||||
- Windows
|
||||
- macOS
|
||||
- Linux
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: 版本
|
||||
description: 您正在运行的 Cherry Studio 版本是什么?
|
||||
placeholder: 例如 v1.0.0
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: question
|
||||
attributes:
|
||||
label: 问题描述
|
||||
description: 请详细描述您的问题或疑问
|
||||
placeholder: 我想了解有关...的更多信息
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: context
|
||||
attributes:
|
||||
label: 相关背景
|
||||
description: 请提供与您的问题相关的任何背景信息或上下文
|
||||
placeholder: 我尝试实现...时遇到了疑问
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: attempts
|
||||
attributes:
|
||||
label: 您已尝试的方法
|
||||
description: 请描述您为解决问题已经尝试过的方法(如果有)
|
||||
|
||||
- type: textarea
|
||||
id: additional
|
||||
attributes:
|
||||
label: 附加信息
|
||||
description: 任何能让我们对您的问题有更多了解的信息,包括截图或相关链接
|
||||
66
.github/workflows/auto-i18n.yml
vendored
66
.github/workflows/auto-i18n.yml
vendored
@@ -1,66 +0,0 @@
|
||||
name: Auto I18N
|
||||
|
||||
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'}}
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
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
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- name: 🐈⬛ Checkout
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.ref }}
|
||||
|
||||
- name: 📦 Setting Node.js
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: 📦 Install dependencies in isolated directory
|
||||
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
|
||||
|
||||
- name: 🏃♀️ Translate
|
||||
run: npx tsx scripts/auto-translate-i18n.ts
|
||||
|
||||
- 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/
|
||||
|
||||
- name: 🔄 Commit changes
|
||||
run: |
|
||||
git config --local user.email "action@github.com"
|
||||
git config --local user.name "GitHub Action"
|
||||
git add .
|
||||
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
|
||||
|
||||
- name: 🚀 Push changes
|
||||
uses: ad-m/github-push-action@master
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
branch: ${{ github.event.pull_request.head.ref }}
|
||||
56
.github/workflows/claude-code-review.yml
vendored
56
.github/workflows/claude-code-review.yml
vendored
@@ -1,56 +0,0 @@
|
||||
name: Claude Code Review
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened]
|
||||
# Optional: Only run on specific file changes
|
||||
# paths:
|
||||
# - "src/**/*.ts"
|
||||
# - "src/**/*.tsx"
|
||||
# - "src/**/*.js"
|
||||
# - "src/**/*.jsx"
|
||||
|
||||
jobs:
|
||||
claude-review:
|
||||
# Only trigger code review for PRs from the main repository due to upstream OIDC issues
|
||||
# https://github.com/anthropics/claude-code-action/issues/542
|
||||
if: |
|
||||
(github.event.pull_request.head.repo.full_name == github.repository) &&
|
||||
(github.event.pull_request.draft == false)
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
issues: read
|
||||
id-token: write
|
||||
actions: read
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Run Claude Code Review
|
||||
id: claude-review
|
||||
uses: anthropics/claude-code-action@v1
|
||||
with:
|
||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
prompt: |
|
||||
Please review this pull request and provide feedback on:
|
||||
- Code quality and best practices
|
||||
- Potential bugs or issues
|
||||
- Performance considerations
|
||||
- Security concerns
|
||||
- Test coverage
|
||||
|
||||
PR number: ${{ github.event.number }}
|
||||
Repo: ${{ github.repository }}
|
||||
|
||||
Use the repository's CLAUDE.md for guidance on style and conventions. Be constructive and helpful in your feedback.
|
||||
|
||||
Use `gh pr comment` with your Bash tool to leave your review as a comment on the PR.
|
||||
|
||||
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
|
||||
# or https://docs.anthropic.com/en/docs/claude-code/sdk#command-line for available options
|
||||
claude_args: '--allowed-tools "Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*)"'
|
||||
110
.github/workflows/claude-translator.yml
vendored
110
.github/workflows/claude-translator.yml
vendored
@@ -1,110 +0,0 @@
|
||||
name: Claude Translator
|
||||
concurrency:
|
||||
group: translator-${{ github.event.comment.id || github.event.issue.number || github.event.review.id }}
|
||||
cancel-in-progress: false
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
issue_comment:
|
||||
types: [created, edited]
|
||||
pull_request_review:
|
||||
types: [submitted, edited]
|
||||
pull_request_review_comment:
|
||||
types: [created, edited]
|
||||
|
||||
jobs:
|
||||
translate:
|
||||
if: |
|
||||
(github.event_name == 'issues')
|
||||
|| (github.event_name == 'issue_comment' && github.event.sender.type != 'Bot')
|
||||
|| (
|
||||
(github.event_name == 'pull_request_review' || github.event_name == 'pull_request_review_comment')
|
||||
&& github.event.sender.type != 'Bot'
|
||||
&& github.event.pull_request.head.repo.fork == false
|
||||
)
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write # 编辑issues/comments
|
||||
pull-requests: write
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Run Claude for translation
|
||||
uses: anthropics/claude-code-action@main
|
||||
id: claude
|
||||
with:
|
||||
# Warning: Permissions should have been controlled by workflow permission.
|
||||
# Now `contents: read` is safe for files, but we could make a fine-grained token to control it.
|
||||
# See: https://github.com/anthropics/claude-code-action/blob/main/docs/security.md
|
||||
github_token: ${{ secrets.TOKEN_GITHUB_WRITE }}
|
||||
allowed_non_write_users: "*"
|
||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
claude_args: "--allowed-tools Bash(gh issue:*),Bash(gh api:repos/*/issues:*),Bash(gh api:repos/*/pulls/*/reviews/*),Bash(gh api:repos/*/pulls/comments/*)"
|
||||
prompt: |
|
||||
你是一个多语言翻译助手。你需要响应 GitHub Webhooks 中的以下四种事件:
|
||||
|
||||
- issues
|
||||
- issue_comment
|
||||
- pull_request_review
|
||||
- pull_request_review_comment
|
||||
|
||||
请完成以下任务:
|
||||
|
||||
1. 获取当前事件的完整信息。
|
||||
|
||||
- 如果当前事件是 issues,就获取该 issues 的信息。
|
||||
- 如果当前事件是 issue_comment,就获取该 comment 的信息。
|
||||
- 如果当前事件是 pull_request_review,就获取该 review 的信息。
|
||||
- 如果当前事件是 pull_request_review_comment,就获取该 comment 的信息。
|
||||
|
||||
2. 智能检测内容。
|
||||
|
||||
- 如果获取到的信息是已经遵循格式要求翻译过的内容,则检查翻译内容和原始内容是否匹配。若不匹配,则重新翻译一次令其匹配,并遵循格式要求;
|
||||
- 如果获取到的信息是未翻译过的内容,检查其内容语言。若不是英文,则翻译成英文;
|
||||
- 如果获取到的信息是部分翻译为英文的内容,则将其翻译为英文;
|
||||
- 如果获取到的信息包含了对已翻译内容的引用,则将引用内容清理为仅含英文的内容。引用的内容不能够包含"This xxx was translated by Claude"和"Original Content`等内容。
|
||||
- 如果获取到的信息包含了其他类型的引用,即对非 Claude 翻译的内容的引用,则直接照原样引用,不进行翻译。
|
||||
- 如果获取到的信息是通过邮件回复的内容,则在翻译时应当将邮件内容的引用放到最后。在原始内容和翻译内容中只需要回复的内容本身,不要包含对邮件内容的引用。
|
||||
- 如果获取到的信息本身不需要任何处理,则跳过任务。
|
||||
|
||||
3. 格式要求:
|
||||
|
||||
- 标题:英文翻译(如果非英文)
|
||||
- 内容格式:
|
||||
> [!NOTE]
|
||||
> This issue/comment/review was translated by Claude.
|
||||
|
||||
[翻译内容]
|
||||
|
||||
---
|
||||
<details>
|
||||
<summary>Original Content</summary>
|
||||
[原始内容]
|
||||
</details>
|
||||
|
||||
4. 使用gh工具更新:
|
||||
|
||||
- 根据环境信息中的Event类型选择正确的命令:
|
||||
- 如果 Event 是 'issues': gh issue edit [ISSUE_NUMBER] --title "[英文标题]" --body "[翻译内容 + 原始内容]"
|
||||
- 如果 Event 是 'issue_comment': gh api -X PATCH /repos/${{ github.repository }}/issues/comments/${{ github.event.comment.id }} -f body="[翻译内容 + 原始内容]"
|
||||
- 如果 Event 是 'pull_request_review': gh api -X PUT /repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/reviews/${{ github.event.review.id }} -f body="[翻译内容]"
|
||||
- 如果 Event 是 'pull_request_review_comment': gh api -X PATCH /repos/${{ github.repository }}/pulls/comments/${{ github.event.comment.id }} -f body="[翻译内容 + 原始内容]"
|
||||
|
||||
环境信息:
|
||||
- Event: ${{ github.event_name }}
|
||||
- Issue Number: ${{ github.event.issue.number }}
|
||||
- Repository: ${{ github.repository }}
|
||||
- (Review) Comment ID: ${{ github.event.comment.id || 'N/A' }}
|
||||
- Pull Request Number: ${{ github.event.pull_request.number || 'N/A' }}
|
||||
- Review ID: ${{ github.event.review.id || 'N/A' }}
|
||||
|
||||
|
||||
使用以下命令获取完整信息:
|
||||
gh issue view ${{ github.event.issue.number }} --json title,body,comments
|
||||
60
.github/workflows/claude.yml
vendored
60
.github/workflows/claude.yml
vendored
@@ -1,60 +0,0 @@
|
||||
name: Claude Code
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
pull_request_review_comment:
|
||||
types: [created]
|
||||
issues:
|
||||
types: [opened]
|
||||
pull_request_review:
|
||||
types: [submitted]
|
||||
|
||||
jobs:
|
||||
claude:
|
||||
if: |
|
||||
(github.event_name == 'issue_comment'
|
||||
&& contains(github.event.comment.body, '@claude')
|
||||
&& contains(fromJSON('["COLLABORATOR","MEMBER","OWNER"]'), github.event.comment.author_association))
|
||||
||
|
||||
(github.event_name == 'pull_request_review_comment'
|
||||
&& contains(github.event.comment.body, '@claude')
|
||||
&& contains(fromJSON('["COLLABORATOR","MEMBER","OWNER"]'), github.event.comment.author_association))
|
||||
||
|
||||
(github.event_name == 'pull_request_review'
|
||||
&& contains(github.event.review.body, '@claude')
|
||||
&& contains(fromJSON('["COLLABORATOR","MEMBER","OWNER"]'), github.event.review.author_association))
|
||||
||
|
||||
(github.event_name == 'issues'
|
||||
&& (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))
|
||||
&& contains(fromJSON('["COLLABORATOR","MEMBER","OWNER"]'), github.event.issue.author_association))
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
issues: read
|
||||
id-token: write
|
||||
actions: read # Required for Claude to read CI results on PRs
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Run Claude Code
|
||||
id: claude
|
||||
uses: anthropics/claude-code-action@v1
|
||||
with:
|
||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
|
||||
# This is an optional setting that allows Claude to read CI results on PRs
|
||||
additional_permissions: |
|
||||
actions: read
|
||||
|
||||
# Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it.
|
||||
# prompt: 'Update the pull request description to include a summary of changes.'
|
||||
|
||||
# Optional: Add claude_args to customize behavior and configuration
|
||||
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
|
||||
# or https://docs.anthropic.com/en/docs/claude-code/sdk#command-line for available options
|
||||
# claude_args: '--model claude-opus-4-1-20250805 --allowed-tools Bash(gh pr:*)'
|
||||
22
.github/workflows/delete-branch.yml
vendored
22
.github/workflows/delete-branch.yml
vendored
@@ -1,22 +0,0 @@
|
||||
name: Delete merged branch
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- closed
|
||||
|
||||
jobs:
|
||||
delete-branch:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
if: github.event.pull_request.merged == true && github.event.pull_request.head.repo.full_name == github.repository
|
||||
steps:
|
||||
- name: Delete merged branch
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
github.rest.git.deleteRef({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
ref: `heads/${context.payload.pull_request.head.ref}`,
|
||||
})
|
||||
32
.github/workflows/nightly-build.yml
vendored
32
.github/workflows/nightly-build.yml
vendored
@@ -56,7 +56,7 @@ jobs:
|
||||
ref: main
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v5
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
@@ -98,10 +98,10 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
NODE_OPTIONS: --max-old-space-size=8192
|
||||
MAIN_VITE_CHERRYAI_CLIENT_SECRET: ${{ secrets.MAIN_VITE_CHERRYAI_CLIENT_SECRET }}
|
||||
MAIN_VITE_MINERU_API_KEY: ${{ secrets.MAIN_VITE_MINERU_API_KEY }}
|
||||
RENDERER_VITE_AIHUBMIX_SECRET: ${{ secrets.RENDERER_VITE_AIHUBMIX_SECRET }}
|
||||
RENDERER_VITE_PPIO_APP_SECRET: ${{ secrets.RENDERER_VITE_PPIO_APP_SECRET }}
|
||||
MAIN_VITE_CHERRYIN_CLIENT_SECRET: ${{ secrets.MAIN_VITE_CHERRYIN_CLIENT_SECRET }}
|
||||
MAIN_VITE_MINERU_API_KEY: ${{ vars.MAIN_VITE_MINERU_API_KEY }}
|
||||
RENDERER_VITE_AIHUBMIX_SECRET: ${{ vars.RENDERER_VITE_AIHUBMIX_SECRET }}
|
||||
RENDERER_VITE_PPIO_APP_SECRET: ${{ vars.RENDERER_VITE_PPIO_APP_SECRET }}
|
||||
|
||||
- name: Build Mac
|
||||
if: matrix.os == 'macos-latest'
|
||||
@@ -110,15 +110,15 @@ jobs:
|
||||
env:
|
||||
CSC_LINK: ${{ secrets.CSC_LINK }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
APPLE_ID: ${{ vars.APPLE_ID }}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ vars.APPLE_APP_SPECIFIC_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ vars.APPLE_TEAM_ID }}
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
NODE_OPTIONS: --max-old-space-size=8192
|
||||
MAIN_VITE_CHERRYAI_CLIENT_SECRET: ${{ secrets.MAIN_VITE_CHERRYAI_CLIENT_SECRET }}
|
||||
MAIN_VITE_MINERU_API_KEY: ${{ secrets.MAIN_VITE_MINERU_API_KEY }}
|
||||
RENDERER_VITE_AIHUBMIX_SECRET: ${{ secrets.RENDERER_VITE_AIHUBMIX_SECRET }}
|
||||
RENDERER_VITE_PPIO_APP_SECRET: ${{ secrets.RENDERER_VITE_PPIO_APP_SECRET }}
|
||||
MAIN_VITE_CHERRYIN_CLIENT_SECRET: ${{ secrets.MAIN_VITE_CHERRYIN_CLIENT_SECRET }}
|
||||
MAIN_VITE_MINERU_API_KEY: ${{ vars.MAIN_VITE_MINERU_API_KEY }}
|
||||
RENDERER_VITE_AIHUBMIX_SECRET: ${{ vars.RENDERER_VITE_AIHUBMIX_SECRET }}
|
||||
RENDERER_VITE_PPIO_APP_SECRET: ${{ vars.RENDERER_VITE_PPIO_APP_SECRET }}
|
||||
|
||||
- name: Build Windows
|
||||
if: matrix.os == 'windows-latest'
|
||||
@@ -127,10 +127,10 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
NODE_OPTIONS: --max-old-space-size=8192
|
||||
MAIN_VITE_CHERRYAI_CLIENT_SECRET: ${{ secrets.MAIN_VITE_CHERRYAI_CLIENT_SECRET }}
|
||||
MAIN_VITE_MINERU_API_KEY: ${{ secrets.MAIN_VITE_MINERU_API_KEY }}
|
||||
RENDERER_VITE_AIHUBMIX_SECRET: ${{ secrets.RENDERER_VITE_AIHUBMIX_SECRET }}
|
||||
RENDERER_VITE_PPIO_APP_SECRET: ${{ secrets.RENDERER_VITE_PPIO_APP_SECRET }}
|
||||
MAIN_VITE_CHERRYIN_CLIENT_SECRET: ${{ secrets.MAIN_VITE_CHERRYIN_CLIENT_SECRET }}
|
||||
MAIN_VITE_MINERU_API_KEY: ${{ vars.MAIN_VITE_MINERU_API_KEY }}
|
||||
RENDERER_VITE_AIHUBMIX_SECRET: ${{ vars.RENDERER_VITE_AIHUBMIX_SECRET }}
|
||||
RENDERER_VITE_PPIO_APP_SECRET: ${{ vars.RENDERER_VITE_PPIO_APP_SECRET }}
|
||||
|
||||
- name: Rename artifacts with nightly format
|
||||
shell: bash
|
||||
|
||||
8
.github/workflows/pr-ci.yml
vendored
8
.github/workflows/pr-ci.yml
vendored
@@ -9,22 +9,19 @@ on:
|
||||
branches:
|
||||
- main
|
||||
- develop
|
||||
- v2
|
||||
types: [ready_for_review, synchronize, opened]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
PRCI: true
|
||||
if: github.event.pull_request.draft == false || github.head_ref == 'v2'
|
||||
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v5
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
@@ -51,9 +48,6 @@ jobs:
|
||||
- name: Lint Check
|
||||
run: yarn test:lint
|
||||
|
||||
- name: Format Check
|
||||
run: yarn format:check
|
||||
|
||||
- name: Type Check
|
||||
run: yarn typecheck
|
||||
|
||||
|
||||
32
.github/workflows/release.yml
vendored
32
.github/workflows/release.yml
vendored
@@ -47,7 +47,7 @@ jobs:
|
||||
npm version "$VERSION" --no-git-tag-version --allow-same-version
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v5
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
@@ -85,10 +85,10 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
NODE_OPTIONS: --max-old-space-size=8192
|
||||
MAIN_VITE_CHERRYAI_CLIENT_SECRET: ${{ secrets.MAIN_VITE_CHERRYAI_CLIENT_SECRET }}
|
||||
MAIN_VITE_MINERU_API_KEY: ${{ secrets.MAIN_VITE_MINERU_API_KEY }}
|
||||
RENDERER_VITE_AIHUBMIX_SECRET: ${{ secrets.RENDERER_VITE_AIHUBMIX_SECRET }}
|
||||
RENDERER_VITE_PPIO_APP_SECRET: ${{ secrets.RENDERER_VITE_PPIO_APP_SECRET }}
|
||||
MAIN_VITE_CHERRYIN_CLIENT_SECRET: ${{ secrets.MAIN_VITE_CHERRYIN_CLIENT_SECRET }}
|
||||
MAIN_VITE_MINERU_API_KEY: ${{ vars.MAIN_VITE_MINERU_API_KEY }}
|
||||
RENDERER_VITE_AIHUBMIX_SECRET: ${{ vars.RENDERER_VITE_AIHUBMIX_SECRET }}
|
||||
RENDERER_VITE_PPIO_APP_SECRET: ${{ vars.RENDERER_VITE_PPIO_APP_SECRET }}
|
||||
|
||||
- name: Build Mac
|
||||
if: matrix.os == 'macos-latest'
|
||||
@@ -98,15 +98,15 @@ jobs:
|
||||
env:
|
||||
CSC_LINK: ${{ secrets.CSC_LINK }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
APPLE_ID: ${{ vars.APPLE_ID }}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ vars.APPLE_APP_SPECIFIC_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ vars.APPLE_TEAM_ID }}
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
NODE_OPTIONS: --max-old-space-size=8192
|
||||
MAIN_VITE_CHERRYAI_CLIENT_SECRET: ${{ secrets.MAIN_VITE_CHERRYAI_CLIENT_SECRET }}
|
||||
MAIN_VITE_MINERU_API_KEY: ${{ secrets.MAIN_VITE_MINERU_API_KEY }}
|
||||
RENDERER_VITE_AIHUBMIX_SECRET: ${{ secrets.RENDERER_VITE_AIHUBMIX_SECRET }}
|
||||
RENDERER_VITE_PPIO_APP_SECRET: ${{ secrets.RENDERER_VITE_PPIO_APP_SECRET }}
|
||||
MAIN_VITE_CHERRYIN_CLIENT_SECRET: ${{ secrets.MAIN_VITE_CHERRYIN_CLIENT_SECRET }}
|
||||
MAIN_VITE_MINERU_API_KEY: ${{ vars.MAIN_VITE_MINERU_API_KEY }}
|
||||
RENDERER_VITE_AIHUBMIX_SECRET: ${{ vars.RENDERER_VITE_AIHUBMIX_SECRET }}
|
||||
RENDERER_VITE_PPIO_APP_SECRET: ${{ vars.RENDERER_VITE_PPIO_APP_SECRET }}
|
||||
|
||||
- name: Build Windows
|
||||
if: matrix.os == 'windows-latest'
|
||||
@@ -115,10 +115,10 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
NODE_OPTIONS: --max-old-space-size=8192
|
||||
MAIN_VITE_CHERRYAI_CLIENT_SECRET: ${{ secrets.MAIN_VITE_CHERRYAI_CLIENT_SECRET }}
|
||||
MAIN_VITE_MINERU_API_KEY: ${{ secrets.MAIN_VITE_MINERU_API_KEY }}
|
||||
RENDERER_VITE_AIHUBMIX_SECRET: ${{ secrets.RENDERER_VITE_AIHUBMIX_SECRET }}
|
||||
RENDERER_VITE_PPIO_APP_SECRET: ${{ secrets.RENDERER_VITE_PPIO_APP_SECRET }}
|
||||
MAIN_VITE_CHERRYIN_CLIENT_SECRET: ${{ secrets.MAIN_VITE_CHERRYIN_CLIENT_SECRET }}
|
||||
MAIN_VITE_MINERU_API_KEY: ${{ vars.MAIN_VITE_MINERU_API_KEY }}
|
||||
RENDERER_VITE_AIHUBMIX_SECRET: ${{ vars.RENDERER_VITE_AIHUBMIX_SECRET }}
|
||||
RENDERER_VITE_PPIO_APP_SECRET: ${{ vars.RENDERER_VITE_PPIO_APP_SECRET }}
|
||||
|
||||
- name: Release
|
||||
uses: ncipollo/release-action@v1
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -37,7 +37,6 @@ dist
|
||||
out
|
||||
mcp_server
|
||||
stats.html
|
||||
.eslintcache
|
||||
|
||||
# ENV
|
||||
.env
|
||||
@@ -54,8 +53,6 @@ local
|
||||
.qwen/*
|
||||
.trae/*
|
||||
.claude-code-router/*
|
||||
.codebuddy/*
|
||||
.zed/*
|
||||
CLAUDE.local.md
|
||||
|
||||
# vitest
|
||||
@@ -71,5 +68,3 @@ playwright-report
|
||||
test-results
|
||||
|
||||
YOUR_MEMORY_FILE_PATH
|
||||
|
||||
.sessions/
|
||||
|
||||
216
.oxlintrc.json
216
.oxlintrc.json
@@ -1,216 +0,0 @@
|
||||
{
|
||||
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
||||
"categories": {},
|
||||
"env": {
|
||||
"es2022": true
|
||||
},
|
||||
"globals": {},
|
||||
"ignorePatterns": [
|
||||
"node_modules/**",
|
||||
"build/**",
|
||||
"dist/**",
|
||||
"out/**",
|
||||
"local/**",
|
||||
".yarn/**",
|
||||
".gitignore",
|
||||
"scripts/cloudflare-worker.js",
|
||||
"src/main/integration/nutstore/sso/lib/**",
|
||||
"src/main/integration/cherryai/index.js",
|
||||
"src/main/integration/nutstore/sso/lib/**",
|
||||
"src/renderer/src/ui/**",
|
||||
"packages/**/dist",
|
||||
"eslint.config.mjs"
|
||||
],
|
||||
"overrides": [
|
||||
// set different env
|
||||
{
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"files": ["src/main/**", "resources/scripts/**", "scripts/**", "playwright.config.ts", "electron.vite.config.ts", "packages/ui/scripts/**"]
|
||||
},
|
||||
{
|
||||
"env": {
|
||||
"browser": true
|
||||
},
|
||||
"files": [
|
||||
"src/renderer/**/*.{ts,tsx}",
|
||||
"packages/aiCore/**",
|
||||
"packages/extension-table-plus/**",
|
||||
"packages/ui/**",
|
||||
"resources/js/**"
|
||||
]
|
||||
},
|
||||
{
|
||||
"env": {
|
||||
"node": true,
|
||||
"vitest": true
|
||||
},
|
||||
"files": ["**/__tests__/*.test.{ts,tsx}", "tests/**"]
|
||||
},
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true
|
||||
},
|
||||
"files": ["src/preload/**"]
|
||||
}
|
||||
],
|
||||
// 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-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",
|
||||
"oxc/bad-min-max-func": "warn",
|
||||
"oxc/bad-object-literal-comparison": "warn",
|
||||
"oxc/bad-replace-all-arg": "warn",
|
||||
"oxc/const-comparisons": "warn",
|
||||
"oxc/double-comparisons": "warn",
|
||||
"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/uninvoked-array-callback": "warn",
|
||||
"require-yield": "error",
|
||||
"typescript/await-thenable": "warn",
|
||||
// "typescript/ban-ts-comment": "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-extra-non-null-assertion": "error",
|
||||
"typescript/no-floating-promises": "warn",
|
||||
"typescript/no-for-in-array": "warn",
|
||||
"typescript/no-implied-eval": "warn",
|
||||
"typescript/no-meaningless-void-operator": "warn",
|
||||
"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-redundant-type-constituents": "warn",
|
||||
"typescript/no-require-imports": "off",
|
||||
"typescript/no-this-alias": "error",
|
||||
"typescript/no-unnecessary-parameter-property-assignment": "warn",
|
||||
"typescript/no-unnecessary-type-constraint": "error",
|
||||
"typescript/no-unsafe-declaration-merging": "error",
|
||||
"typescript/no-unsafe-function-type": "error",
|
||||
"typescript/no-unsafe-unary-minus": "warn",
|
||||
"typescript/no-useless-empty-export": "warn",
|
||||
"typescript/no-wrapper-object-types": "error",
|
||||
"typescript/prefer-as-const": "error",
|
||||
"typescript/prefer-namespace-keyword": "error",
|
||||
"typescript/require-array-sort-compare": "warn",
|
||||
"typescript/restrict-template-expressions": "warn",
|
||||
"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-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-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-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/prefer-set-size": "warn",
|
||||
"unicorn/prefer-string-starts-ends-with": "warn",
|
||||
"use-isnan": "error",
|
||||
"valid-typeof": "error"
|
||||
},
|
||||
"settings": {
|
||||
"jsdoc": {
|
||||
"augmentsExtendsReplacesDocs": false,
|
||||
"exemptDestructuredRootsFromChecks": false,
|
||||
"ignoreInternal": false,
|
||||
"ignorePrivate": false,
|
||||
"ignoreReplacesDocs": true,
|
||||
"implementsReplacesDocs": false,
|
||||
"overrideReplacesDocs": true,
|
||||
"tagNamePreference": {}
|
||||
},
|
||||
"jsx-a11y": {
|
||||
"attributes": {},
|
||||
"components": {},
|
||||
"polymorphicPropName": null
|
||||
},
|
||||
"next": {
|
||||
"rootDir": []
|
||||
},
|
||||
"react": {
|
||||
"formComponents": [],
|
||||
"linkComponents": []
|
||||
}
|
||||
}
|
||||
}
|
||||
11
.prettierignore
Normal file
11
.prettierignore
Normal file
@@ -0,0 +1,11 @@
|
||||
out
|
||||
dist
|
||||
pnpm-lock.yaml
|
||||
LICENSE.md
|
||||
tsconfig.json
|
||||
tsconfig.*.json
|
||||
CHANGELOG*.md
|
||||
agents.json
|
||||
src/renderer/src/integration/nutstore/sso/lib
|
||||
AGENT.md
|
||||
src/main/integration/cherryin/index.js
|
||||
11
.prettierrc
Normal file
11
.prettierrc
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"bracketSameLine": true,
|
||||
"endOfLine": "lf",
|
||||
"jsonRecursiveSort": true,
|
||||
"jsonSortOrder": "{\"*\": \"lexical\"}",
|
||||
"plugins": ["prettier-plugin-sort-json"],
|
||||
"printWidth": 120,
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none"
|
||||
}
|
||||
7
.vscode/extensions.json
vendored
7
.vscode/extensions.json
vendored
@@ -1,11 +1,8 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode",
|
||||
"editorconfig.editorconfig",
|
||||
"lokalise.i18n-ally",
|
||||
"bradlc.vscode-tailwindcss",
|
||||
"vitest.explorer",
|
||||
"oxc.oxc-vscode",
|
||||
"biomejs.biome"
|
||||
"lokalise.i18n-ally"
|
||||
]
|
||||
}
|
||||
|
||||
30
.vscode/settings.json
vendored
30
.vscode/settings.json
vendored
@@ -1,44 +1,38 @@
|
||||
{
|
||||
"[css]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[json]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[jsonc]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[markdown]": {
|
||||
"files.trimTrailingWhitespace": false
|
||||
},
|
||||
"[scss]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[typescriptreact]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.biome": "explicit",
|
||||
"source.fixAll.eslint": "explicit",
|
||||
"source.fixAll.oxc": "explicit",
|
||||
"source.organizeImports": "never"
|
||||
},
|
||||
"editor.formatOnSave": true,
|
||||
"files.associations": {
|
||||
"*.css": "tailwindcss",
|
||||
".oxlintrc.json": "jsonc"
|
||||
},
|
||||
"files.eol": "\n",
|
||||
// "i18n-ally.displayLanguage": "zh-cn", // 界面显示语言
|
||||
"i18n-ally.displayLanguage": "zh-cn",
|
||||
"i18n-ally.enabledFrameworks": ["react-i18next", "i18next"],
|
||||
"i18n-ally.enabledParsers": ["ts", "js", "json"], // 解析语言
|
||||
"i18n-ally.fullReloadOnChanged": true,
|
||||
"i18n-ally.fullReloadOnChanged": true, // 界面显示语言
|
||||
"i18n-ally.keystyle": "nested", // 翻译路径格式
|
||||
"i18n-ally.localesPaths": ["src/renderer/src/i18n/locales"],
|
||||
// "i18n-ally.namespace": true, // 开启命名空间
|
||||
@@ -48,9 +42,5 @@
|
||||
"search.exclude": {
|
||||
"**/dist/**": true,
|
||||
".yarn/releases/**": true
|
||||
},
|
||||
"tailwindCSS.classAttributes": [
|
||||
"className",
|
||||
"classNames",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -1,31 +0,0 @@
|
||||
diff --git a/sdk.mjs b/sdk.mjs
|
||||
index 461e9a2ba246778261108a682762ffcf26f7224e..44bd667d9f591969d36a105ba5eb8b478c738dd8 100644
|
||||
--- a/sdk.mjs
|
||||
+++ b/sdk.mjs
|
||||
@@ -6215,7 +6215,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 {
|
||||
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";
|
||||
- this.child = spawn(spawnCommand, spawnArgs, {
|
||||
+ this.child = fork(pathToClaudeCodeExecutable, args, {
|
||||
cwd,
|
||||
- stdio: ["pipe", "pipe", stderrMode],
|
||||
+ stdio: stderrMode === "pipe" ? ["pipe", "pipe", "pipe", "ipc"] : ["pipe", "pipe", "ignore", "ipc"],
|
||||
signal: this.abortController.signal,
|
||||
env
|
||||
});
|
||||
@@ -5,5 +5,3 @@ httpTimeout: 300000
|
||||
nodeLinker: node-modules
|
||||
|
||||
yarnPath: .yarn/releases/yarn-4.9.1.cjs
|
||||
npmRegistryServer: https://registry.npmjs.org
|
||||
npmPublishRegistry: https://registry.npmjs.org
|
||||
|
||||
140
CLAUDE.md
140
CLAUDE.md
@@ -1,39 +1,43 @@
|
||||
# AI Assistant Guide
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to AI coding assistants when working with code in this repository. Adherence to these guidelines is crucial for maintaining code quality and consistency.
|
||||
|
||||
## Guiding Principles (MUST FOLLOW)
|
||||
|
||||
- **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.
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Development Commands
|
||||
|
||||
- **Install**: `yarn install` - Install all project dependencies
|
||||
- **Development**: `yarn dev` - Runs Electron app in development mode with hot reload
|
||||
- **Debug**: `yarn debug` - Starts with debugging enabled, use `chrome://inspect` to attach debugger
|
||||
- **Build Check**: `yarn build:check` - **REQUIRED** before commits (lint + test + typecheck)
|
||||
- If having i18n sort issues, run `yarn sync:i18n` first to sync template
|
||||
- If having formatting issues, run `yarn format` first
|
||||
- **Test**: `yarn test` - Run all tests (Vitest) across main and renderer processes
|
||||
- **Single Test**:
|
||||
- `yarn test:main` - Run tests for main process only
|
||||
- `yarn test:renderer` - Run tests for renderer process only
|
||||
- **Lint**: `yarn lint` - Fix linting issues and run TypeScript type checking
|
||||
- **Format**: `yarn format` - Auto-format code using Biome
|
||||
### Environment Setup
|
||||
|
||||
## Project Architecture
|
||||
- **Prerequisites**: Node.js v22.x.x or higher, Yarn 4.9.1
|
||||
- **Setup Yarn**: `corepack enable && corepack prepare yarn@4.9.1 --activate`
|
||||
- **Install Dependencies**: `yarn install`
|
||||
|
||||
### Electron Structure
|
||||
- **Main Process** (`src/main/`): Node.js backend with services (MCP, Knowledge, Storage, etc.)
|
||||
- **Renderer Process** (`src/renderer/`): React UI with Redux state management
|
||||
- **Preload Scripts** (`src/preload/`): Secure IPC bridge
|
||||
### Development
|
||||
|
||||
- **Start Development**: `yarn dev` - Runs Electron app in development mode
|
||||
- **Debug Mode**: `yarn debug` - Starts with debugging enabled, use chrome://inspect
|
||||
|
||||
### Testing & Quality
|
||||
|
||||
- **Run Tests**: `yarn test` - Runs all tests (Vitest)
|
||||
- **Run E2E Tests**: `yarn test:e2e` - Playwright end-to-end tests
|
||||
- **Type Check**: `yarn typecheck` - Checks TypeScript for both node and web
|
||||
- **Lint**: `yarn lint` - ESLint with auto-fix
|
||||
- **Format**: `yarn format` - Prettier formatting
|
||||
|
||||
### Build & Release
|
||||
|
||||
- **Build**: `yarn build` - Builds for production (includes typecheck)
|
||||
- **Platform-specific builds**:
|
||||
- Windows: `yarn build:win`
|
||||
- macOS: `yarn build:mac`
|
||||
- Linux: `yarn build:linux`
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
### Electron Multi-Process Architecture
|
||||
|
||||
- **Main Process** (`src/main/`): Node.js backend handling system integration, file operations, and services
|
||||
- **Renderer Process** (`src/renderer/`): React-based UI running in Chromium
|
||||
- **Preload Scripts** (`src/preload/`): Secure bridge between main and renderer processes
|
||||
|
||||
### Key Architectural Components
|
||||
|
||||
@@ -52,11 +56,11 @@ This file provides guidance to AI coding assistants when working with code in th
|
||||
- **Client Factory**: Supports multiple AI providers (OpenAI, Anthropic, Gemini, etc.)
|
||||
- **Stream Processing**: Real-time response handling
|
||||
|
||||
#### Data Management
|
||||
#### State Management (`src/renderer/src/store/`)
|
||||
|
||||
- **Cache System**: Three-layer caching (memory/shared/persist) with React hooks integration
|
||||
- **Preferences**: Type-safe configuration management with multi-window synchronization
|
||||
- **User Data**: SQLite-based storage with Drizzle ORM for business data
|
||||
- **Redux Toolkit**: Centralized state management
|
||||
- **Persistent Storage**: Redux-persist for data persistence
|
||||
- **Thunks**: Async actions for complex operations
|
||||
|
||||
#### Knowledge Management
|
||||
|
||||
@@ -88,63 +92,29 @@ This file provides guidance to AI coding assistants when working with code in th
|
||||
- **Multi-language Support**: i18n with dynamic loading
|
||||
- **Theme System**: Light/dark themes with custom CSS variables
|
||||
|
||||
### UI Design
|
||||
|
||||
The project is in the process of migrating from antd & styled-components to HeroUI. Please use HeroUI to build UI components. The use of antd and styled-components is prohibited.
|
||||
|
||||
HeroUI Docs: https://www.heroui.com/docs/guide/introduction
|
||||
|
||||
### Database Architecture
|
||||
|
||||
- **Database**: SQLite (`cherrystudio.sqlite`) + libsql driver
|
||||
- **ORM**: Drizzle ORM with comprehensive migration system
|
||||
- **Schemas**: Located in `src/main/data/db/schemas/` directory
|
||||
|
||||
#### Database Standards
|
||||
|
||||
- **Table Naming**: Use singular form with snake_case (e.g., `topic`, `message`, `app_state`)
|
||||
- **Schema Exports**: Export using `xxxTable` pattern (e.g., `topicTable`, `appStateTable`)
|
||||
- **Field Definition**: Drizzle auto-infers field names, no need to add default field names
|
||||
- **JSON Fields**: For JSON support, add `{ mode: 'json' }`, refer to `preference.ts` table definition
|
||||
- **JSON Serialization**: For JSON fields, no need to manually serialize/deserialize when reading/writing to database, Drizzle handles this automatically
|
||||
- **Timestamps**: Use existing `crudTimestamps` utility
|
||||
- **Migrations**: Generate via `yarn run migrations:generate`
|
||||
|
||||
## Data Access Patterns
|
||||
|
||||
The application uses three distinct data management systems. Choose the appropriate system based on data characteristics:
|
||||
|
||||
### Cache System
|
||||
- **Purpose**: Temporary data that can be regenerated
|
||||
- **Lifecycle**: Component-level (memory), window-level (shared), or persistent (survives restart)
|
||||
- **Use Cases**: API response caching, computed results, temporary UI state
|
||||
- **APIs**: `useCache`, `useSharedCache`, `usePersistCache` hooks, or `cacheService`
|
||||
|
||||
### Preference System
|
||||
- **Purpose**: User configuration and application settings
|
||||
- **Lifecycle**: Permanent until user changes
|
||||
- **Use Cases**: Theme, language, editor settings, user preferences
|
||||
- **APIs**: `usePreference`, `usePreferences` hooks, or `preferenceService`
|
||||
|
||||
### User Data API
|
||||
- **Purpose**: Core business data (conversations, files, notes, etc.)
|
||||
- **Lifecycle**: Permanent business records
|
||||
- **Use Cases**: Topics, messages, files, knowledge base, user-generated content
|
||||
- **APIs**: `useDataApi` hook or `dataApiService` for direct calls
|
||||
|
||||
### Selection Guidelines
|
||||
|
||||
- **Use Cache** for data that can be lost without impact (computed values, API responses)
|
||||
- **Use Preferences** for user settings that affect app behavior (UI configuration, feature flags)
|
||||
- **Use User Data API** for irreplaceable business data (conversations, documents, user content)
|
||||
|
||||
## Logging Standards
|
||||
|
||||
### Usage
|
||||
|
||||
```typescript
|
||||
// Main process
|
||||
import { loggerService } from '@logger'
|
||||
const logger = loggerService.withContext('moduleName')
|
||||
// Renderer: loggerService.initWindowSource('windowName') first
|
||||
|
||||
// Renderer process (set window source first)
|
||||
loggerService.initWindowSource('windowName')
|
||||
const logger = loggerService.withContext('moduleName')
|
||||
|
||||
// Logging
|
||||
logger.info('message', CONTEXT)
|
||||
logger.error('message', new Error('error'), CONTEXT)
|
||||
```
|
||||
|
||||
### Log Levels (highest to lowest)
|
||||
|
||||
- `error` - Critical errors causing crash/unusable functionality
|
||||
- `warn` - Potential issues that don't affect core functionality
|
||||
- `info` - Application lifecycle and key user actions
|
||||
- `verbose` - Detailed flow information for feature tracing
|
||||
- `debug` - Development diagnostic info (not for production)
|
||||
- `silly` - Extreme debugging, low-level information
|
||||
|
||||
45
LICENSE
45
LICENSE
@@ -1,3 +1,48 @@
|
||||
**许可协议 (Licensing)**
|
||||
|
||||
本项目采用**区分用户的双重许可 (User-Segmented Dual Licensing)** 模式。
|
||||
|
||||
**核心原则:**
|
||||
|
||||
* **个人用户 和 10人及以下企业/组织:** 默认适用 **GNU Affero 通用公共许可证 v3.0 (AGPLv3)**。
|
||||
* **超过10人的企业/组织:** **必须** 获取 **商业许可证 (Commercial License)**。
|
||||
|
||||
定义:“10人及以下”
|
||||
指在您的组织(包括公司、非营利组织、政府机构、教育机构等任何实体)中,能够访问、使用或以任何方式直接或间接受益于本软件(Cherry Studio)功能的个人总数不超过10人。这包括但不限于开发者、测试人员、运营人员、最终用户、通过集成系统间接使用者等。
|
||||
|
||||
---
|
||||
|
||||
**1. 开源许可证 (Open Source License): AGPLv3 - 适用于个人及10人及以下组织**
|
||||
|
||||
* 如果您是个人用户,或者您的组织满足上述“10人及以下”的定义,您可以在 **AGPLv3** 的条款下自由使用、修改和分发 Cherry Studio。AGPLv3 的完整文本可以访问 [https://www.gnu.org/licenses/agpl-3.0.html](https://www.gnu.org/licenses/agpl-3.0.html) 获取。
|
||||
* **核心义务:** AGPLv3 的一个关键要求是,如果您修改了 Cherry Studio 并通过网络提供服务,或者分发了修改后的版本,您必须以 AGPLv3 许可证向接收者提供相应的**完整源代码**。即使您符合“10人及以下”的标准,如果您希望避免此源代码公开义务,您也需要考虑获取商业许可证(见下文)。
|
||||
* 使用前请务必仔细阅读并理解 AGPLv3 的所有条款。
|
||||
|
||||
**2. 商业许可证 (Commercial License) - 适用于超过10人的组织,或希望规避 AGPLv3 义务的用户**
|
||||
|
||||
* **强制要求:** 如果您的组织**不**满足上述“10人及以下”的定义(即有11人或更多人可以访问、使用或受益于本软件),您**必须**联系我们获取并签署一份商业许可证才能使用 Cherry Studio。
|
||||
* **自愿选择:** 即使您的组织满足“10人及以下”的条件,但如果您的使用场景**无法满足 AGPLv3 的条款要求**(特别是关于**源代码公开**的义务),或者您需要 AGPLv3 **未提供**的特定商业条款(如保证、赔偿、无 Copyleft 限制等),您也**必须**联系我们获取并签署一份商业许可证。
|
||||
* **需要商业许可证的常见情况包括(但不限于):**
|
||||
* 您的组织规模超过10人。
|
||||
* (无论组织规模)您希望分发修改过的 Cherry Studio 版本,但**不希望**根据 AGPLv3 公开您修改部分的源代码。
|
||||
* (无论组织规模)您希望基于修改过的 Cherry Studio 提供网络服务(SaaS),但**不希望**根据 AGPLv3 向服务使用者提供修改后的源代码。
|
||||
* (无论组织规模)您的公司政策、客户合同或项目要求不允许使用 AGPLv3 许可的软件,或要求闭源分发及保密。
|
||||
* 商业许可证将为您提供豁免 AGPLv3 义务(如源代码公开)的权利,并可能包含额外的商业保障条款。
|
||||
* **获取商业许可:** 请通过邮箱 **bd@cherry-ai.com** 联系 Cherry Studio 开发团队洽谈商业授权事宜。
|
||||
|
||||
**3. 贡献 (Contributions)**
|
||||
|
||||
* 我们欢迎社区对 Cherry Studio 的贡献。所有向本项目提交的贡献都将被视为在 **AGPLv3** 许可证下提供。
|
||||
* 通过向本项目提交贡献(例如通过 Pull Request),即表示您同意您的代码以 AGPLv3 许可证授权给本项目及所有后续使用者(无论这些使用者最终遵循 AGPLv3 还是商业许可)。
|
||||
* 您也理解并同意,您的贡献可能会被包含在根据商业许可证分发的 Cherry Studio 版本中。
|
||||
|
||||
**4. 其他条款 (Other Terms)**
|
||||
|
||||
* 关于商业许可证的具体条款和条件,以双方签署的正式商业许可协议为准。
|
||||
* 项目维护者保留根据需要更新本许可政策(包括用户规模定义和阈值)的权利。相关更新将通过项目官方渠道(如代码仓库、官方网站)进行通知。
|
||||
|
||||
---
|
||||
|
||||
**Licensing**
|
||||
|
||||
This project employs a **User-Segmented Dual Licensing** model.
|
||||
|
||||
@@ -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, Peplexity, Poe, and others
|
||||
- 💻 Local Model Support with Ollama, LM Studio
|
||||
|
||||
2. **AI Assistants & Conversations**:
|
||||
|
||||
98
biome.jsonc
98
biome.jsonc
@@ -1,98 +0,0 @@
|
||||
{
|
||||
"$schema": "https://biomejs.dev/schemas/2.2.4/schema.json",
|
||||
"assist": {
|
||||
// to sort json
|
||||
"actions": {
|
||||
"source": {
|
||||
"organizeImports": "on",
|
||||
"useSortedKeys": {
|
||||
"level": "on",
|
||||
"options": {
|
||||
"sortOrder": "lexicographic"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"enabled": true,
|
||||
"includes": ["**/*.json", "!*.json", "!**/package.json"]
|
||||
},
|
||||
"css": {
|
||||
"formatter": {
|
||||
"quoteStyle": "single"
|
||||
}
|
||||
},
|
||||
"files": { "ignoreUnknown": false },
|
||||
"formatter": {
|
||||
"attributePosition": "auto",
|
||||
"bracketSameLine": false,
|
||||
"bracketSpacing": true,
|
||||
"enabled": true,
|
||||
"expand": "auto",
|
||||
"formatWithErrors": true,
|
||||
"includes": [
|
||||
"**",
|
||||
"!out/**",
|
||||
"!**/dist/**",
|
||||
"!build/**",
|
||||
"!.yarn/**",
|
||||
"!.github/**",
|
||||
"!.husky/**",
|
||||
"!.vscode/**",
|
||||
"!.claude/**",
|
||||
"!*.yaml",
|
||||
"!*.yml",
|
||||
"!*.mjs",
|
||||
"!*.cjs",
|
||||
"!*.md",
|
||||
"!*.json",
|
||||
"!src/main/integration/**",
|
||||
"!**/tailwind.css",
|
||||
"!**/package.json"
|
||||
],
|
||||
"indentStyle": "space",
|
||||
"indentWidth": 2,
|
||||
"lineEnding": "lf",
|
||||
"lineWidth": 120,
|
||||
"useEditorconfig": true
|
||||
},
|
||||
"html": { "formatter": { "selfCloseVoidElements": "always" } },
|
||||
"javascript": {
|
||||
"formatter": {
|
||||
"arrowParentheses": "always",
|
||||
"attributePosition": "auto",
|
||||
// To minimize changes in this PR as much as possible, it's set to true. However, setting it to false would make it more convenient to add attributes at the end.
|
||||
"bracketSameLine": true,
|
||||
"bracketSpacing": true,
|
||||
"jsxQuoteStyle": "double",
|
||||
"quoteProperties": "asNeeded",
|
||||
"quoteStyle": "single",
|
||||
"semicolons": "asNeeded",
|
||||
"trailingCommas": "none"
|
||||
}
|
||||
},
|
||||
"json": {
|
||||
"parser": {
|
||||
"allowComments": true
|
||||
}
|
||||
},
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"includes": ["!**/tailwind.css", "src/renderer/**/*.{tsx,ts}"],
|
||||
// only enable sorted tailwind css rule. used as formatter instead of linter
|
||||
"rules": {
|
||||
"nursery": {
|
||||
// to sort tailwind css classes
|
||||
"useSortedClasses": {
|
||||
"fix": "safe",
|
||||
"level": "warn",
|
||||
"options": {
|
||||
"functions": ["cn"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"recommended": false,
|
||||
"suspicious": "off"
|
||||
}
|
||||
},
|
||||
"vcs": { "clientKind": "git", "enabled": false, "useIgnoreFile": false }
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -13,7 +13,7 @@
|
||||
<p><a href="https://openaitx.github.io/view.html?user=CherryHQ&project=cherry-studio&lang=fr">Français</a></p>
|
||||
<p><a href="https://openaitx.github.io/view.html?user=CherryHQ&project=cherry-studio&lang=de">Deutsch</a></p>
|
||||
<p><a href="https://openaitx.github.io/view.html?user=CherryHQ&project=cherry-studio&lang=es">Español</a></p>
|
||||
<p><a href="https://openaitx.github.io/view.html?user=CherryHQ&project=cherry-studio&lang=it">Italiano</a></p>
|
||||
<p><a href="https://openaitx.github.io/view.html?user=CherryHQ&project=cherry-studio&lang=it">Itapano</a></p>
|
||||
<p><a href="https://openaitx.github.io/view.html?user=CherryHQ&project=cherry-studio&lang=ru">Русский</a></p>
|
||||
<p><a href="https://openaitx.github.io/view.html?user=CherryHQ&project=cherry-studio&lang=pt">Português</a></p>
|
||||
<p><a href="https://openaitx.github.io/view.html?user=CherryHQ&project=cherry-studio&lang=nl">Nederlands</a></p>
|
||||
@@ -89,7 +89,7 @@ https://docs.cherry-ai.com
|
||||
1. **多样化 LLM 服务支持**:
|
||||
|
||||
- ☁️ 支持主流 LLM 云服务:OpenAI、Gemini、Anthropic、硅基流动等
|
||||
- 🔗 集成流行 AI Web 服务:Claude、Perplexity、Poe、腾讯元宝、知乎直答等
|
||||
- 🔗 集成流行 AI Web 服务:Claude、Peplexity、Poe、腾讯元宝、知乎直答等
|
||||
- 💻 支持 Ollama、LM Studio 本地模型部署
|
||||
|
||||
2. **智能助手与对话**:
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
|
||||
## 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)
|
||||
[Cursor](https://www.cursor.com/) + [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) + [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)
|
||||
|
||||
## Project Setup
|
||||
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
|
||||
| 字段名 | 类型 | 是否主键 | 索引 | 说明 |
|
||||
| ---------- | ------ | -------- | ---- | ------------------------------------------------------------------------ |
|
||||
| `id` | string | ✅ 是 | ✅ | 唯一标识符,主键 |
|
||||
| `langCode` | string | ❌ 否 | ✅ | 语言代码(如:`zh-cn`, `en-us`, `ja-jp` 等,均为小写),支持普通索引查询 |
|
||||
| `value` | string | ❌ 否 | ❌ | 语言的名称,用户输入 |
|
||||
| `emoji` | string | ❌ 否 | ❌ | 语言的emoji,用户输入 |
|
||||
| `id` | string | ✅ 是 | ✅ | 唯一标识符,主键 |
|
||||
| `langCode` | string | ❌ 否 | ✅ | 语言代码(如:`zh-cn`, `en-us`, `ja-jp` 等,均为小写),支持普通索引查询 |
|
||||
| `value` | string | ❌ 否 | ❌ | 语言的名称,用户输入 |
|
||||
| `emoji` | string | ❌ 否 | ❌ | 语言的emoji,用户输入 |
|
||||
|
||||
> `langCode` 虽非主键,但在业务层应当避免重复插入相同语言代码。
|
||||
|
||||
@@ -17,55 +17,52 @@ protocols:
|
||||
schemes:
|
||||
- cherrystudio
|
||||
files:
|
||||
- "**/*"
|
||||
- "!**/{.vscode,.yarn,.yarn-lock,.github,.cursorrules,.prettierrc}"
|
||||
- "!electron.vite.config.{js,ts,mjs,cjs}}"
|
||||
- "!**/{.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}"
|
||||
- "!**/{.editorconfig,.jekyll-metadata}"
|
||||
- "!src"
|
||||
- "!scripts"
|
||||
- "!local"
|
||||
- "!docs"
|
||||
- "!packages"
|
||||
- "!.swc"
|
||||
- "!.bin"
|
||||
- "!._*"
|
||||
- "!*.log"
|
||||
- "!stats.html"
|
||||
- "!*.md"
|
||||
- "!**/*.{iml,o,hprof,orig,pyc,pyo,rbc,swp,csproj,sln,xproj}"
|
||||
- "!**/*.{map,ts,tsx,jsx,less,scss,sass,css.d.ts,d.cts,d.mts,md,markdown,yaml,yml}"
|
||||
- "!**/{test,tests,__tests__,powered-test,coverage}/**"
|
||||
- "!**/{example,examples}/**"
|
||||
- "!**/*.{spec,test}.{js,jsx,ts,tsx}"
|
||||
- "!**/*.min.*.map"
|
||||
- "!**/*.d.ts"
|
||||
- "!**/dist/es6/**"
|
||||
- "!**/dist/demo/**"
|
||||
- "!**/amd/**"
|
||||
- "!**/{.DS_Store,Thumbs.db,thumbs.db,__pycache__}"
|
||||
- "!**/{LICENSE,license,LICENSE.*,*.LICENSE.txt,NOTICE.txt,README.md,readme.md,CHANGELOG.md}"
|
||||
- "!node_modules/rollup-plugin-visualizer"
|
||||
- "!node_modules/js-tiktoken"
|
||||
- "!node_modules/@tavily/core/node_modules/js-tiktoken"
|
||||
- "!node_modules/pdf-parse/lib/pdf.js/{v1.9.426,v1.10.88,v2.0.550}"
|
||||
- "!node_modules/mammoth/{mammoth.browser.js,mammoth.browser.min.js}"
|
||||
- "!node_modules/selection-hook/prebuilds/**/*" # we rebuild .node, don't use prebuilds
|
||||
- "!node_modules/selection-hook/node_modules" # we don't need what in the node_modules dir
|
||||
- "!node_modules/selection-hook/src" # we don't need source files
|
||||
- "!node_modules/tesseract.js-core/{tesseract-core.js,tesseract-core.wasm,tesseract-core.wasm.js}" # we don't need source files
|
||||
- "!node_modules/tesseract.js-core/{tesseract-core-lstm.js,tesseract-core-lstm.wasm,tesseract-core-lstm.wasm.js}" # we don't need source files
|
||||
- "!node_modules/tesseract.js-core/{tesseract-core-simd-lstm.js,tesseract-core-simd-lstm.wasm,tesseract-core-simd-lstm.wasm.js}" # we don't need source files
|
||||
- "!**/*.{h,iobj,ipdb,tlog,recipe,vcxproj,vcxproj.filters,Makefile,*.Makefile}" # filter .node build files
|
||||
- '**/*'
|
||||
- '!**/{.vscode,.yarn,.yarn-lock,.github,.cursorrules,.prettierrc}'
|
||||
- '!electron.vite.config.{js,ts,mjs,cjs}}'
|
||||
- '!**/{.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}'
|
||||
- '!**/{.env,.env.*,.npmrc,pnpm-lock.yaml}'
|
||||
- '!**/{tsconfig.json,tsconfig.tsbuildinfo,tsconfig.node.json,tsconfig.web.json}'
|
||||
- '!**/{.editorconfig,.jekyll-metadata}'
|
||||
- '!src'
|
||||
- '!scripts'
|
||||
- '!local'
|
||||
- '!docs'
|
||||
- '!packages'
|
||||
- '!.swc'
|
||||
- '!.bin'
|
||||
- '!._*'
|
||||
- '!*.log'
|
||||
- '!stats.html'
|
||||
- '!*.md'
|
||||
- '!**/*.{iml,o,hprof,orig,pyc,pyo,rbc,swp,csproj,sln,xproj}'
|
||||
- '!**/*.{map,ts,tsx,jsx,less,scss,sass,css.d.ts,d.cts,d.mts,md,markdown,yaml,yml}'
|
||||
- '!**/{test,tests,__tests__,powered-test,coverage}/**'
|
||||
- '!**/{example,examples}/**'
|
||||
- '!**/*.{spec,test}.{js,jsx,ts,tsx}'
|
||||
- '!**/*.min.*.map'
|
||||
- '!**/*.d.ts'
|
||||
- '!**/dist/es6/**'
|
||||
- '!**/dist/demo/**'
|
||||
- '!**/amd/**'
|
||||
- '!**/{.DS_Store,Thumbs.db,thumbs.db,__pycache__}'
|
||||
- '!**/{LICENSE,license,LICENSE.*,*.LICENSE.txt,NOTICE.txt,README.md,readme.md,CHANGELOG.md}'
|
||||
- '!node_modules/rollup-plugin-visualizer'
|
||||
- '!node_modules/js-tiktoken'
|
||||
- '!node_modules/@tavily/core/node_modules/js-tiktoken'
|
||||
- '!node_modules/pdf-parse/lib/pdf.js/{v1.9.426,v1.10.88,v2.0.550}'
|
||||
- '!node_modules/mammoth/{mammoth.browser.js,mammoth.browser.min.js}'
|
||||
- '!node_modules/selection-hook/prebuilds/**/*' # we rebuild .node, don't use prebuilds
|
||||
- '!node_modules/selection-hook/node_modules' # we don't need what in the node_modules dir
|
||||
- '!node_modules/selection-hook/src' # we don't need source files
|
||||
- '!node_modules/tesseract.js-core/{tesseract-core.js,tesseract-core.wasm,tesseract-core.wasm.js}' # we don't need source files
|
||||
- '!node_modules/tesseract.js-core/{tesseract-core-lstm.js,tesseract-core-lstm.wasm,tesseract-core-lstm.wasm.js}' # we don't need source files
|
||||
- '!node_modules/tesseract.js-core/{tesseract-core-simd-lstm.js,tesseract-core-simd-lstm.wasm,tesseract-core-simd-lstm.wasm.js}' # we don't need source files
|
||||
- '!**/*.{h,iobj,ipdb,tlog,recipe,vcxproj,vcxproj.filters,Makefile,*.Makefile}' # filter .node build files
|
||||
asarUnpack:
|
||||
- resources/**
|
||||
- "**/*.{metal,exp,lib}"
|
||||
- "node_modules/@img/sharp-libvips-*/**"
|
||||
extraResources:
|
||||
- from: "migrations/sqlite-drizzle"
|
||||
to: "migrations/sqlite-drizzle"
|
||||
- '**/*.{metal,exp,lib}'
|
||||
- 'node_modules/@img/sharp-libvips-*/**'
|
||||
win:
|
||||
executableName: Cherry Studio
|
||||
artifactName: ${productName}-${version}-${arch}-setup.${ext}
|
||||
@@ -91,7 +88,7 @@ mac:
|
||||
entitlementsInherit: build/entitlements.mac.plist
|
||||
notarize: false
|
||||
artifactName: ${productName}-${version}-${arch}.${ext}
|
||||
minimumSystemVersion: "20.1.0" # 最低支持 macOS 11.0
|
||||
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.
|
||||
@@ -113,10 +110,6 @@ linux:
|
||||
StartupWMClass: CherryStudio
|
||||
mimeTypes:
|
||||
- x-scheme-handler/cherrystudio
|
||||
rpm:
|
||||
# Workaround for electron build issue on rpm package:
|
||||
# https://github.com/electron/forge/issues/3594
|
||||
fpm: ["--rpm-rpmbuild-define=_build_id_links none"]
|
||||
publish:
|
||||
provider: generic
|
||||
url: https://releases.cherry-ai.com
|
||||
@@ -128,21 +121,12 @@ afterSign: scripts/notarize.js
|
||||
artifactBuildCompleted: scripts/artifact-build-completed.js
|
||||
releaseInfo:
|
||||
releaseNotes: |
|
||||
What's New in v1.6.3
|
||||
🔧 性能优化:
|
||||
- 优化AI服务连接方式,提升响应速度和稳定性
|
||||
- 改进模型列表获取功能,减少不必要的网络请求
|
||||
- 增强各AI服务商的兼容性和连接可靠性
|
||||
|
||||
Features:
|
||||
- Notes: Add spell-check control, automatic table line wrapping, export functionality, and LLM-based renaming
|
||||
- UI: Expand topic rename clickable area, add middle-click tab closing, remove redundant scrollbars, fix message menubar overflow
|
||||
- Editor: Add read-only extension support, make TextFilePreview read-only but copyable
|
||||
- Models: Update support for DeepSeek v3.2, Claude 4.5, GLM 4.6, Gemini regex, and vision models
|
||||
- Code Tools: Add GitHub Copilot CLI integration
|
||||
|
||||
Bug Fixes:
|
||||
- Fix migration for missing providers
|
||||
- Fix forked topic retaining old name after rename
|
||||
- Restore first token latency reporting in metrics
|
||||
- Fix UI scrollbar and overflow issues
|
||||
|
||||
Technical Updates:
|
||||
- Upgrade to Electron 37.6.0
|
||||
- Update dependencies across packages
|
||||
🐛 问题修复:
|
||||
- 修复部分AI服务商连接失败的问题
|
||||
- 修复模型配置加载时的潜在错误
|
||||
- 提升应用整体稳定性和容错能力
|
||||
|
||||
@@ -4,9 +4,7 @@ import { defineConfig, externalizeDepsPlugin } from 'electron-vite'
|
||||
import { resolve } from 'path'
|
||||
import { visualizer } from 'rollup-plugin-visualizer'
|
||||
|
||||
// assert not supported by biome
|
||||
// import pkg from './package.json' assert { type: 'json' }
|
||||
import pkg from './package.json'
|
||||
import pkg from './package.json' assert { type: 'json' }
|
||||
|
||||
const visualizerPlugin = (type: 'renderer' | 'main') => {
|
||||
return process.env[`VISUALIZER_${type.toUpperCase()}`] ? [visualizer({ open: true })] : []
|
||||
@@ -22,11 +20,13 @@ export default defineConfig({
|
||||
alias: {
|
||||
'@main': resolve('src/main'),
|
||||
'@types': resolve('src/renderer/src/types'),
|
||||
'@data': resolve('src/main/data'),
|
||||
'@shared': resolve('packages/shared'),
|
||||
'@logger': resolve('src/main/services/LoggerService'),
|
||||
'@mcp-trace/trace-core': resolve('packages/mcp-trace/trace-core'),
|
||||
'@mcp-trace/trace-node': resolve('packages/mcp-trace/trace-node')
|
||||
'@mcp-trace/trace-node': resolve('packages/mcp-trace/trace-node'),
|
||||
'@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')
|
||||
}
|
||||
},
|
||||
build: {
|
||||
@@ -35,10 +35,6 @@ export default defineConfig({
|
||||
output: {
|
||||
manualChunks: undefined, // 彻底禁用代码分割 - 返回 null 强制单文件打包
|
||||
inlineDynamicImports: true // 内联所有动态导入,这是关键配置
|
||||
},
|
||||
onwarn(warning, warn) {
|
||||
if (warning.code === 'COMMONJS_VARIABLE_IN_ESM') return
|
||||
warn(warning)
|
||||
}
|
||||
},
|
||||
sourcemap: isDev
|
||||
@@ -62,25 +58,11 @@ export default defineConfig({
|
||||
}
|
||||
},
|
||||
build: {
|
||||
sourcemap: isDev,
|
||||
rollupOptions: {
|
||||
// Unlike renderer which auto-discovers entries from HTML files,
|
||||
// preload requires explicit entry point configuration for multiple scripts
|
||||
input: {
|
||||
index: resolve(__dirname, 'src/preload/index.ts'),
|
||||
simplest: resolve(__dirname, 'src/preload/simplest.ts') // Minimal preload
|
||||
},
|
||||
external: ['electron'],
|
||||
output: {
|
||||
entryFileNames: '[name].js',
|
||||
format: 'cjs'
|
||||
}
|
||||
}
|
||||
sourcemap: isDev
|
||||
}
|
||||
},
|
||||
renderer: {
|
||||
plugins: [
|
||||
(async () => (await import('@tailwindcss/vite')).default())(),
|
||||
react({
|
||||
tsDecorators: true,
|
||||
plugins: [
|
||||
@@ -102,16 +84,13 @@ export default defineConfig({
|
||||
alias: {
|
||||
'@renderer': resolve('src/renderer/src'),
|
||||
'@shared': resolve('packages/shared'),
|
||||
'@types': resolve('src/renderer/src/types'),
|
||||
'@logger': resolve('src/renderer/src/services/LoggerService'),
|
||||
'@data': resolve('src/renderer/src/data'),
|
||||
'@mcp-trace/trace-core': resolve('packages/mcp-trace/trace-core'),
|
||||
'@mcp-trace/trace-web': resolve('packages/mcp-trace/trace-web'),
|
||||
'@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/ui': resolve('packages/ui/src')
|
||||
'@cherrystudio/extension-table-plus': resolve('packages/extension-table-plus/src')
|
||||
}
|
||||
},
|
||||
optimizeDeps: {
|
||||
@@ -131,12 +110,7 @@ export default defineConfig({
|
||||
miniWindow: resolve(__dirname, 'src/renderer/miniWindow.html'),
|
||||
selectionToolbar: resolve(__dirname, 'src/renderer/selectionToolbar.html'),
|
||||
selectionAction: resolve(__dirname, 'src/renderer/selectionAction.html'),
|
||||
traceWindow: resolve(__dirname, 'src/renderer/traceWindow.html'),
|
||||
dataRefactorMigrate: resolve(__dirname, 'src/renderer/dataRefactorMigrate.html')
|
||||
},
|
||||
onwarn(warning, warn) {
|
||||
if (warning.code === 'COMMONJS_VARIABLE_IN_ESM') return
|
||||
warn(warning)
|
||||
traceWindow: resolve(__dirname, 'src/renderer/traceWindow.html')
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import electronConfigPrettier from '@electron-toolkit/eslint-config-prettier'
|
||||
import tseslint from '@electron-toolkit/eslint-config-ts'
|
||||
import eslint from '@eslint/js'
|
||||
import eslintReact from '@eslint-react/eslint-plugin'
|
||||
import { defineConfig } from 'eslint/config'
|
||||
import importZod from 'eslint-plugin-import-zod'
|
||||
import oxlint from 'eslint-plugin-oxlint'
|
||||
import reactHooks from 'eslint-plugin-react-hooks'
|
||||
import simpleImportSort from 'eslint-plugin-simple-import-sort'
|
||||
import unusedImports from 'eslint-plugin-unused-imports'
|
||||
@@ -11,13 +10,13 @@ import unusedImports from 'eslint-plugin-unused-imports'
|
||||
export default defineConfig([
|
||||
eslint.configs.recommended,
|
||||
tseslint.configs.recommended,
|
||||
electronConfigPrettier,
|
||||
eslintReact.configs['recommended-typescript'],
|
||||
reactHooks.configs['recommended-latest'],
|
||||
{
|
||||
plugins: {
|
||||
'simple-import-sort': simpleImportSort,
|
||||
'unused-imports': unusedImports,
|
||||
'import-zod': importZod
|
||||
'unused-imports': unusedImports
|
||||
},
|
||||
rules: {
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
@@ -27,7 +26,7 @@ export default defineConfig([
|
||||
'simple-import-sort/exports': 'error',
|
||||
'unused-imports/no-unused-imports': 'error',
|
||||
'@eslint-react/no-prop-types': 'error',
|
||||
'import-zod/prefer-zod-namespace': 'error'
|
||||
'prettier/prettier': ['error']
|
||||
}
|
||||
},
|
||||
// Configuration for ensuring compatibility with the original ESLint(8.x) rules
|
||||
@@ -52,31 +51,9 @@ export default defineConfig([
|
||||
}
|
||||
},
|
||||
{
|
||||
ignores: [
|
||||
'node_modules/**',
|
||||
'build/**',
|
||||
'dist/**',
|
||||
'out/**',
|
||||
'local/**',
|
||||
'.yarn/**',
|
||||
'.gitignore',
|
||||
'scripts/cloudflare-worker.js',
|
||||
'src/main/integration/nutstore/sso/lib/**',
|
||||
'src/main/integration/cherryai/index.js',
|
||||
'src/main/integration/nutstore/sso/lib/**',
|
||||
'src/renderer/src/ui/**',
|
||||
'packages/**/dist'
|
||||
]
|
||||
},
|
||||
// turn off oxlint supported rules.
|
||||
...oxlint.configs['flat/eslint'],
|
||||
...oxlint.configs['flat/typescript'],
|
||||
...oxlint.configs['flat/unicorn'],
|
||||
// Custom rules should be after oxlint to overwrite
|
||||
// LoggerService Custom Rules - only apply to src directory
|
||||
{
|
||||
// LoggerService Custom Rules - only apply to src directory
|
||||
files: ['src/**/*.{ts,tsx,js,jsx}'],
|
||||
ignores: ['src/**/__tests__/**', 'src/**/__mocks__/**', 'src/**/*.test.*', 'src/preload/**'],
|
||||
ignores: ['src/**/__tests__/**', 'src/**/__mocks__/**', 'src/**/*.test.*'],
|
||||
rules: {
|
||||
'no-restricted-syntax': [
|
||||
process.env.PRCI ? 'error' : 'warn',
|
||||
@@ -88,7 +65,6 @@ export default defineConfig([
|
||||
]
|
||||
}
|
||||
},
|
||||
// i18n
|
||||
{
|
||||
files: ['**/*.{ts,tsx,js,jsx}'],
|
||||
languageOptions: {
|
||||
@@ -136,30 +112,18 @@ export default defineConfig([
|
||||
'i18n/no-template-in-t': 'warn'
|
||||
}
|
||||
},
|
||||
// ui migration
|
||||
{
|
||||
// Component Rules - prevent importing antd components when migration completed
|
||||
files: ['**/*.{ts,tsx,js,jsx}'],
|
||||
ignores: ['src/renderer/src/windows/dataRefactorTest/**/*.{ts,tsx}'],
|
||||
rules: {
|
||||
'no-restricted-imports': [
|
||||
'error',
|
||||
{
|
||||
paths: [
|
||||
{
|
||||
name: 'antd',
|
||||
importNames: ['Flex', 'Switch', 'message', 'Button', 'Tooltip'],
|
||||
message:
|
||||
'❌ Do not import this component from antd. Use our custom components instead: import { ... } from "@cherrystudio/ui"'
|
||||
},
|
||||
// {
|
||||
// name: '@heroui/react',
|
||||
// message:
|
||||
// '❌ Do not import components from heroui directly. Use our wrapped components instead: import { ... } from "@cherrystudio/ui"'
|
||||
// }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
ignores: [
|
||||
'node_modules/**',
|
||||
'build/**',
|
||||
'dist/**',
|
||||
'out/**',
|
||||
'local/**',
|
||||
'.yarn/**',
|
||||
'.gitignore',
|
||||
'scripts/cloudflare-worker.js',
|
||||
'src/main/integration/nutstore/sso/lib/**',
|
||||
'src/main/integration/cherryin/index.js'
|
||||
]
|
||||
}
|
||||
])
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
**THIS DIRECTORY IS NOT FOR RUNTIME USE**
|
||||
|
||||
- Using `libsql` as the `sqlite3` driver, and `drizzle` as the ORM and database migration tool
|
||||
- `migrations/sqlite-drizzle` contains auto-generated migration data. Please **DO NOT** modify it.
|
||||
- If table structure changes, we should run migrations.
|
||||
- To generate migrations, use the command `yarn run migrations:generate`
|
||||
@@ -1,7 +0,0 @@
|
||||
import { defineConfig } from 'drizzle-kit'
|
||||
export default defineConfig({
|
||||
out: './migrations/sqlite-drizzle',
|
||||
schema: './src/main/data/db/schemas/*',
|
||||
dialect: 'sqlite',
|
||||
casing: 'snake_case'
|
||||
})
|
||||
@@ -1,17 +0,0 @@
|
||||
CREATE TABLE `app_state` (
|
||||
`key` text PRIMARY KEY NOT NULL,
|
||||
`value` text NOT NULL,
|
||||
`description` text,
|
||||
`created_at` integer,
|
||||
`updated_at` integer
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `preference` (
|
||||
`scope` text NOT NULL,
|
||||
`key` text NOT NULL,
|
||||
`value` text,
|
||||
`created_at` integer,
|
||||
`updated_at` integer
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE INDEX `scope_name_idx` ON `preference` (`scope`,`key`);
|
||||
@@ -1,114 +0,0 @@
|
||||
{
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
},
|
||||
"dialect": "sqlite",
|
||||
"enums": {},
|
||||
"id": "de8009d7-95b9-4f99-99fa-4b8795708f21",
|
||||
"internal": {
|
||||
"indexes": {}
|
||||
},
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"tables": {
|
||||
"app_state": {
|
||||
"checkConstraints": {},
|
||||
"columns": {
|
||||
"created_at": {
|
||||
"autoincrement": false,
|
||||
"name": "created_at",
|
||||
"notNull": false,
|
||||
"primaryKey": false,
|
||||
"type": "integer"
|
||||
},
|
||||
"description": {
|
||||
"autoincrement": false,
|
||||
"name": "description",
|
||||
"notNull": false,
|
||||
"primaryKey": false,
|
||||
"type": "text"
|
||||
},
|
||||
"key": {
|
||||
"autoincrement": false,
|
||||
"name": "key",
|
||||
"notNull": true,
|
||||
"primaryKey": true,
|
||||
"type": "text"
|
||||
},
|
||||
"updated_at": {
|
||||
"autoincrement": false,
|
||||
"name": "updated_at",
|
||||
"notNull": false,
|
||||
"primaryKey": false,
|
||||
"type": "integer"
|
||||
},
|
||||
"value": {
|
||||
"autoincrement": false,
|
||||
"name": "value",
|
||||
"notNull": true,
|
||||
"primaryKey": false,
|
||||
"type": "text"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"foreignKeys": {},
|
||||
"indexes": {},
|
||||
"name": "app_state",
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"preference": {
|
||||
"checkConstraints": {},
|
||||
"columns": {
|
||||
"created_at": {
|
||||
"autoincrement": false,
|
||||
"name": "created_at",
|
||||
"notNull": false,
|
||||
"primaryKey": false,
|
||||
"type": "integer"
|
||||
},
|
||||
"key": {
|
||||
"autoincrement": false,
|
||||
"name": "key",
|
||||
"notNull": true,
|
||||
"primaryKey": false,
|
||||
"type": "text"
|
||||
},
|
||||
"scope": {
|
||||
"autoincrement": false,
|
||||
"name": "scope",
|
||||
"notNull": true,
|
||||
"primaryKey": false,
|
||||
"type": "text"
|
||||
},
|
||||
"updated_at": {
|
||||
"autoincrement": false,
|
||||
"name": "updated_at",
|
||||
"notNull": false,
|
||||
"primaryKey": false,
|
||||
"type": "integer"
|
||||
},
|
||||
"value": {
|
||||
"autoincrement": false,
|
||||
"name": "value",
|
||||
"notNull": false,
|
||||
"primaryKey": false,
|
||||
"type": "text"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"foreignKeys": {},
|
||||
"indexes": {
|
||||
"scope_name_idx": {
|
||||
"columns": ["scope", "key"],
|
||||
"isUnique": false,
|
||||
"name": "scope_name_idx"
|
||||
}
|
||||
},
|
||||
"name": "preference",
|
||||
"uniqueConstraints": {}
|
||||
}
|
||||
},
|
||||
"version": "6",
|
||||
"views": {}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"dialect": "sqlite",
|
||||
"entries": [
|
||||
{
|
||||
"breakpoints": true,
|
||||
"idx": 0,
|
||||
"tag": "0000_solid_lord_hawal",
|
||||
"version": "6",
|
||||
"when": 1754745234572
|
||||
}
|
||||
],
|
||||
"version": "7"
|
||||
}
|
||||
125
package.json
125
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "CherryStudio",
|
||||
"version": "2.0.0-alpha",
|
||||
"version": "1.6.0-beta.6",
|
||||
"private": true,
|
||||
"description": "A powerful AI assistant for producer.",
|
||||
"main": "./out/main/index.js",
|
||||
@@ -43,19 +43,15 @@
|
||||
"release": "node scripts/version.js",
|
||||
"publish": "yarn build:check && yarn release patch push",
|
||||
"pulish:artifacts": "cd packages/artifacts && npm publish && cd -",
|
||||
"agents:generate": "NODE_ENV='development' drizzle-kit generate --config src/main/services/agents/drizzle.config.ts",
|
||||
"agents:push": "NODE_ENV='development' drizzle-kit push --config src/main/services/agents/drizzle.config.ts",
|
||||
"agents:studio": "NODE_ENV='development' drizzle-kit studio --config src/main/services/agents/drizzle.config.ts",
|
||||
"agents:drop": "NODE_ENV='development' drizzle-kit drop --config src/main/services/agents/drizzle.config.ts",
|
||||
"generate:agents": "yarn workspace @cherry-studio/database agents",
|
||||
"generate:icons": "electron-icon-builder --input=./build/logo.png --output=build",
|
||||
"analyze:renderer": "VISUALIZER_RENDERER=true yarn build",
|
||||
"analyze:main": "VISUALIZER_MAIN=true yarn build",
|
||||
"typecheck": "concurrently -n \"node,web,ui\" -c \"cyan,magenta,green\" \"npm run typecheck:node\" \"npm run typecheck:web\" \"npm run typecheck:ui\"",
|
||||
"typecheck:node": "tsgo --noEmit -p tsconfig.node.json --composite false",
|
||||
"typecheck:web": "tsgo --noEmit -p tsconfig.web.json --composite false",
|
||||
"typecheck:ui": "cd packages/ui && npm run type-check",
|
||||
"check:i18n": "dotenv -e .env -- tsx scripts/check-i18n.ts",
|
||||
"sync:i18n": "dotenv -e .env -- tsx scripts/sync-i18n.ts",
|
||||
"typecheck": "concurrently -n \"node,web\" -c \"cyan,magenta\" \"npm run typecheck:node\" \"npm run typecheck:web\"",
|
||||
"typecheck:node": "tsc --noEmit -p tsconfig.node.json --composite false",
|
||||
"typecheck:web": "tsc --noEmit -p tsconfig.web.json --composite false",
|
||||
"check:i18n": "tsx scripts/check-i18n.ts",
|
||||
"sync:i18n": "tsx scripts/sync-i18n.ts",
|
||||
"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",
|
||||
@@ -67,34 +63,26 @@
|
||||
"test:ui": "vitest --ui",
|
||||
"test:watch": "vitest",
|
||||
"test:e2e": "yarn playwright test",
|
||||
"test:lint": "oxlint --deny-warnings && eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --cache",
|
||||
"test:lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts",
|
||||
"test:scripts": "vitest scripts",
|
||||
"lint": "oxlint --fix && eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --cache && biome lint --write && biome format --write && yarn typecheck && yarn check:i18n && yarn format:check",
|
||||
"lint:ox": "oxlint --fix && biome lint --write && biome format --write",
|
||||
"format": "biome format --write && biome lint --write",
|
||||
"format:check": "biome format && biome lint",
|
||||
"prepare": "git config blame.ignoreRevsFile .git-blame-ignore-revs && husky",
|
||||
"claude": "dotenv -e .env -- claude",
|
||||
"migrations:generate": "drizzle-kit generate --config ./migrations/sqlite-drizzle.config.ts",
|
||||
"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"
|
||||
"format": "prettier --write .",
|
||||
"lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix && yarn typecheck && yarn check:i18n",
|
||||
"prepare": "git config blame.ignoreRevsFile .git-blame-ignore-revs && husky"
|
||||
},
|
||||
"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",
|
||||
"@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",
|
||||
"@strongtz/win32-arm64-msvc": "^0.4.7",
|
||||
"font-list": "^2.0.0",
|
||||
"ai-sdk-provider-claude-code": "^1.1.3",
|
||||
"express": "^5.1.0",
|
||||
"graceful-fs": "^4.2.11",
|
||||
"jsdom": "26.1.0",
|
||||
"node-stream-zip": "^1.15.0",
|
||||
"officeparser": "^4.2.0",
|
||||
"os-proxy-config": "^1.1.2",
|
||||
"selection-hook": "^1.0.12",
|
||||
"selection-hook": "^1.0.11",
|
||||
"sharp": "^0.34.3",
|
||||
"swagger-jsdoc": "^6.2.8",
|
||||
"tesseract.js": "patch:tesseract.js@npm%3A6.0.1#~/.yarn/patches/tesseract.js-npm-6.0.1-2562a7e46d.patch",
|
||||
"turndown": "7.2.0"
|
||||
},
|
||||
@@ -102,18 +90,16 @@
|
||||
"@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.0",
|
||||
"@ai-sdk/google-vertex": "^3.0.0",
|
||||
"@ai-sdk/mistral": "^2.0.0",
|
||||
"@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",
|
||||
"@biomejs/biome": "2.2.4",
|
||||
"@cherrystudio/ai-core": "workspace:^1.0.0-alpha.18",
|
||||
"@cherrystudio/ai-core": "workspace:*",
|
||||
"@cherrystudio/embedjs": "^0.1.31",
|
||||
"@cherrystudio/embedjs-libsql": "^0.1.31",
|
||||
"@cherrystudio/embedjs-loader-csv": "^0.1.31",
|
||||
@@ -127,11 +113,11 @@
|
||||
"@cherrystudio/embedjs-ollama": "^0.1.31",
|
||||
"@cherrystudio/embedjs-openai": "^0.1.31",
|
||||
"@cherrystudio/extension-table-plus": "workspace:^",
|
||||
"@cherrystudio/ui": "workspace:*",
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/modifiers": "^9.0.0",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@electron-toolkit/eslint-config-prettier": "^3.0.0",
|
||||
"@electron-toolkit/eslint-config-ts": "^3.0.0",
|
||||
"@electron-toolkit/preload": "^3.0.0",
|
||||
"@electron-toolkit/tsconfig": "^1.0.1",
|
||||
@@ -142,10 +128,11 @@
|
||||
"@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",
|
||||
"@langchain/community": "^0.3.50",
|
||||
"@kangfenmao/keyv-storage": "^0.1.0",
|
||||
"@langchain/community": "^0.3.36",
|
||||
"@langchain/ollama": "^0.2.1",
|
||||
"@mistralai/mistralai": "^1.7.5",
|
||||
"@modelcontextprotocol/sdk": "^1.17.5",
|
||||
"@modelcontextprotocol/sdk": "^1.17.0",
|
||||
"@mozilla/readability": "^0.6.0",
|
||||
"@notionhq/client": "^2.2.15",
|
||||
"@openrouter/ai-sdk-provider": "^1.1.2",
|
||||
@@ -155,13 +142,10 @@
|
||||
"@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": "0.1.18",
|
||||
"@playwright/test": "^1.52.0",
|
||||
"@radix-ui/react-context-menu": "^2.2.16",
|
||||
"@reduxjs/toolkit": "^2.2.5",
|
||||
"@shikijs/markdown-it": "^3.12.0",
|
||||
"@swc/plugin-styled-components": "^8.0.4",
|
||||
"@tailwindcss/vite": "^4.1.13",
|
||||
"@tanstack/react-query": "^5.85.5",
|
||||
"@tanstack/react-virtual": "^3.13.12",
|
||||
"@testing-library/dom": "^10.4.0",
|
||||
@@ -187,31 +171,21 @@
|
||||
"@truto/turndown-plugin-gfm": "^1.0.2",
|
||||
"@tryfabric/martian": "^1.2.4",
|
||||
"@types/cli-progress": "^3",
|
||||
"@types/content-type": "^1.1.9",
|
||||
"@types/cors": "^2.8.19",
|
||||
"@types/diff": "^7",
|
||||
"@types/express": "^5",
|
||||
"@types/express": "^5.0.3",
|
||||
"@types/fs-extra": "^11",
|
||||
"@types/he": "^1",
|
||||
"@types/html-to-text": "^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-infinite-scroll-component": "^5.0.0",
|
||||
"@types/react-transition-group": "^4.4.12",
|
||||
"@types/react-window": "^1",
|
||||
"@types/swagger-jsdoc": "^6",
|
||||
"@types/swagger-ui-express": "^4.1.8",
|
||||
"@types/tinycolor2": "^1",
|
||||
"@types/turndown": "^5.0.5",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@types/word-extractor": "^1",
|
||||
"@typescript/native-preview": "latest",
|
||||
"@uiw/codemirror-extensions-langs": "^4.25.1",
|
||||
"@uiw/codemirror-themes-all": "^4.25.1",
|
||||
"@uiw/react-codemirror": "^4.25.1",
|
||||
@@ -223,18 +197,15 @@
|
||||
"@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.29",
|
||||
"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",
|
||||
"axios": "^1.7.3",
|
||||
"browser-image-compression": "^2.0.2",
|
||||
"chardet": "^2.1.0",
|
||||
"check-disk-space": "3.4.0",
|
||||
"cheerio": "^1.1.2",
|
||||
"chokidar": "^4.0.3",
|
||||
"cli-progress": "^3.12.0",
|
||||
"clsx": "^2.1.1",
|
||||
"code-inspector-plugin": "^0.20.14",
|
||||
"color": "^5.0.0",
|
||||
"concurrently": "^9.2.1",
|
||||
@@ -246,12 +217,9 @@
|
||||
"docx": "^9.0.2",
|
||||
"dompurify": "^3.2.6",
|
||||
"dotenv-cli": "^7.4.2",
|
||||
"drizzle-kit": "^0.31.4",
|
||||
"drizzle-orm": "^0.44.5",
|
||||
"electron": "37.6.0",
|
||||
"electron": "37.4.0",
|
||||
"electron-builder": "26.0.15",
|
||||
"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",
|
||||
@@ -260,24 +228,18 @@
|
||||
"emoji-picker-element": "^1.22.1",
|
||||
"epub": "patch:epub@npm%3A1.3.0#~/.yarn/patches/epub-npm-1.3.0-8325494ffe.patch",
|
||||
"eslint": "^9.22.0",
|
||||
"eslint-plugin-import-zod": "^1.2.0",
|
||||
"eslint-plugin-oxlint": "^1.15.0",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"eslint-plugin-simple-import-sort": "^12.1.1",
|
||||
"eslint-plugin-unused-imports": "^4.1.4",
|
||||
"express": "^5.1.0",
|
||||
"express-validator": "^7.2.1",
|
||||
"fast-diff": "^1.3.0",
|
||||
"fast-xml-parser": "^5.2.0",
|
||||
"fetch-socks": "1.3.2",
|
||||
"framer-motion": "^12.23.12",
|
||||
"franc-min": "^6.2.0",
|
||||
"fs-extra": "^11.2.0",
|
||||
"google-auth-library": "^9.15.1",
|
||||
"he": "^1.2.0",
|
||||
"html-tags": "^5.1.0",
|
||||
"html-to-image": "^1.11.13",
|
||||
"html-to-text": "^9.0.5",
|
||||
"htmlparser2": "^10.0.0",
|
||||
"husky": "^9.1.7",
|
||||
"i18next": "^23.11.5",
|
||||
@@ -294,17 +256,15 @@
|
||||
"markdown-it": "^14.1.0",
|
||||
"mermaid": "^11.10.1",
|
||||
"mime": "^4.0.4",
|
||||
"mime-types": "^3.0.1",
|
||||
"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",
|
||||
"prettier": "^3.5.3",
|
||||
"prettier-plugin-sort-json": "^4.1.1",
|
||||
"proxy-agent": "^6.5.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
@@ -314,7 +274,6 @@
|
||||
"react-infinite-scroll-component": "^6.1.0",
|
||||
"react-json-view": "^1.21.3",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-player": "^3.3.1",
|
||||
"react-redux": "^9.1.2",
|
||||
"react-router": "6",
|
||||
"react-router-dom": "6",
|
||||
@@ -334,24 +293,21 @@
|
||||
"remark-math": "^6.0.0",
|
||||
"remove-markdown": "^0.6.2",
|
||||
"rollup-plugin-visualizer": "^5.12.0",
|
||||
"sass": "^1.88.0",
|
||||
"shiki": "^3.12.0",
|
||||
"strict-url-sanitise": "^0.0.1",
|
||||
"string-width": "^7.2.0",
|
||||
"striptags": "^3.2.0",
|
||||
"styled-components": "^6.1.11",
|
||||
"swagger-ui-express": "^5.0.1",
|
||||
"swr": "^2.3.6",
|
||||
"tailwindcss": "^4.1.13",
|
||||
"tar": "^7.4.3",
|
||||
"tiny-pinyin": "^1.3.2",
|
||||
"tokenx": "^1.1.0",
|
||||
"tsx": "^4.20.3",
|
||||
"turndown-plugin-gfm": "^1.0.2",
|
||||
"tw-animate-css": "^1.3.8",
|
||||
"typescript": "~5.8.2",
|
||||
"typescript": "^5.6.2",
|
||||
"undici": "6.21.2",
|
||||
"unified": "^11.0.5",
|
||||
"uuid": "^13.0.0",
|
||||
"uuid": "^10.0.0",
|
||||
"vite": "npm:rolldown-vite@latest",
|
||||
"vitest": "^3.2.4",
|
||||
"webdav": "^5.8.0",
|
||||
@@ -359,11 +315,9 @@
|
||||
"winston-daily-rotate-file": "^5.0.0",
|
||||
"word-extractor": "^1.0.4",
|
||||
"y-protocols": "^1.0.6",
|
||||
"yaml": "^2.8.1",
|
||||
"yjs": "^13.6.27",
|
||||
"youtubei.js": "^15.0.1",
|
||||
"zipread": "^1.3.3",
|
||||
"zod": "^4.1.5"
|
||||
"zod": "^3.25.74"
|
||||
},
|
||||
"resolutions": {
|
||||
"@codemirror/language": "6.11.3",
|
||||
@@ -375,7 +329,6 @@
|
||||
"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",
|
||||
"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",
|
||||
@@ -383,20 +336,24 @@
|
||||
"openai@npm:^4.87.3": "patch:openai@npm%3A5.12.2#~/.yarn/patches/openai-npm-5.12.2-30b075401c.patch",
|
||||
"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",
|
||||
"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"
|
||||
"@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"
|
||||
},
|
||||
"packageManager": "yarn@4.9.1",
|
||||
"lint-staged": {
|
||||
"*.{js,jsx,ts,tsx,cjs,mjs,cts,mts}": [
|
||||
"biome format --write --no-errors-on-unmatched",
|
||||
"prettier --write",
|
||||
"eslint --fix"
|
||||
],
|
||||
"*.{json,yml,yaml,css,html}": [
|
||||
"biome format --write --no-errors-on-unmatched"
|
||||
"*.{json,yml,yaml,css,scss,html}": [
|
||||
"prettier --write"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
103
packages/aiCore/examples/hub-provider-usage.ts
Normal file
103
packages/aiCore/examples/hub-provider-usage.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* Hub Provider 使用示例
|
||||
*
|
||||
* 演示如何使用简化后的Hub Provider功能来路由到多个底层provider
|
||||
*/
|
||||
|
||||
import { createHubProvider, initializeProvider, providerRegistry } from '../src/index'
|
||||
|
||||
async function demonstrateHubProvider() {
|
||||
try {
|
||||
// 1. 初始化底层providers
|
||||
console.log('📦 初始化底层providers...')
|
||||
|
||||
initializeProvider('openai', {
|
||||
apiKey: process.env.OPENAI_API_KEY || 'sk-test-key'
|
||||
})
|
||||
|
||||
initializeProvider('anthropic', {
|
||||
apiKey: process.env.ANTHROPIC_API_KEY || 'sk-ant-test-key'
|
||||
})
|
||||
|
||||
// 2. 创建Hub Provider(自动包含所有已初始化的providers)
|
||||
console.log('🌐 创建Hub Provider...')
|
||||
|
||||
const aihubmixProvider = createHubProvider({
|
||||
hubId: 'aihubmix',
|
||||
debug: true
|
||||
})
|
||||
|
||||
// 3. 注册Hub Provider
|
||||
providerRegistry.registerProvider('aihubmix', aihubmixProvider)
|
||||
|
||||
console.log('✅ Hub Provider "aihubmix" 注册成功')
|
||||
|
||||
// 4. 使用Hub Provider访问不同的模型
|
||||
console.log('\n🚀 使用Hub模型...')
|
||||
|
||||
// 通过Hub路由到OpenAI
|
||||
const openaiModel = providerRegistry.languageModel('aihubmix:openai:gpt-4')
|
||||
console.log('✓ OpenAI模型已获取:', openaiModel.modelId)
|
||||
|
||||
// 通过Hub路由到Anthropic
|
||||
const anthropicModel = providerRegistry.languageModel('aihubmix:anthropic:claude-3.5-sonnet')
|
||||
console.log('✓ Anthropic模型已获取:', anthropicModel.modelId)
|
||||
|
||||
// 5. 演示错误处理
|
||||
console.log('\n❌ 演示错误处理...')
|
||||
|
||||
try {
|
||||
// 尝试访问未初始化的provider
|
||||
providerRegistry.languageModel('aihubmix:google:gemini-pro')
|
||||
} catch (error) {
|
||||
console.log('预期错误:', error.message)
|
||||
}
|
||||
|
||||
try {
|
||||
// 尝试使用错误的模型ID格式
|
||||
providerRegistry.languageModel('aihubmix:invalid-format')
|
||||
} catch (error) {
|
||||
console.log('预期错误:', error.message)
|
||||
}
|
||||
|
||||
// 6. 多个Hub Provider示例
|
||||
console.log('\n🔄 创建多个Hub Provider...')
|
||||
|
||||
const localHubProvider = createHubProvider({
|
||||
hubId: 'local-ai'
|
||||
})
|
||||
|
||||
providerRegistry.registerProvider('local-ai', localHubProvider)
|
||||
console.log('✅ Hub Provider "local-ai" 注册成功')
|
||||
|
||||
console.log('\n🎉 Hub Provider演示完成!')
|
||||
} catch (error) {
|
||||
console.error('💥 演示过程中发生错误:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 演示简化的使用方式
|
||||
function simplifiedUsageExample() {
|
||||
console.log('\n📝 简化使用示例:')
|
||||
console.log(`
|
||||
// 1. 初始化providers
|
||||
initializeProvider('openai', { apiKey: 'sk-xxx' })
|
||||
initializeProvider('anthropic', { apiKey: 'sk-ant-xxx' })
|
||||
|
||||
// 2. 创建并注册Hub Provider
|
||||
const hubProvider = createHubProvider({ hubId: 'aihubmix' })
|
||||
providerRegistry.registerProvider('aihubmix', hubProvider)
|
||||
|
||||
// 3. 直接使用
|
||||
const model1 = providerRegistry.languageModel('aihubmix:openai:gpt-4')
|
||||
const model2 = providerRegistry.languageModel('aihubmix:anthropic:claude-3.5-sonnet')
|
||||
`)
|
||||
}
|
||||
|
||||
// 运行演示
|
||||
if (require.main === module) {
|
||||
demonstrateHubProvider()
|
||||
simplifiedUsageExample()
|
||||
}
|
||||
|
||||
export { demonstrateHubProvider, simplifiedUsageExample }
|
||||
167
packages/aiCore/examples/image-generation.ts
Normal file
167
packages/aiCore/examples/image-generation.ts
Normal file
@@ -0,0 +1,167 @@
|
||||
/**
|
||||
* Image Generation Example
|
||||
* 演示如何使用 aiCore 的文生图功能
|
||||
*/
|
||||
|
||||
import { createExecutor, generateImage } from '../src/index'
|
||||
|
||||
async function main() {
|
||||
// 方式1: 使用执行器实例
|
||||
console.log('📸 创建 OpenAI 图像生成执行器...')
|
||||
const executor = createExecutor('openai', {
|
||||
apiKey: process.env.OPENAI_API_KEY!
|
||||
})
|
||||
|
||||
try {
|
||||
console.log('🎨 使用执行器生成图像...')
|
||||
const result1 = await executor.generateImage('dall-e-3', {
|
||||
prompt: 'A futuristic cityscape at sunset with flying cars',
|
||||
size: '1024x1024',
|
||||
n: 1
|
||||
})
|
||||
|
||||
console.log('✅ 图像生成成功!')
|
||||
console.log('📊 结果:', {
|
||||
imagesCount: result1.images.length,
|
||||
mediaType: result1.image.mediaType,
|
||||
hasBase64: !!result1.image.base64,
|
||||
providerMetadata: result1.providerMetadata
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('❌ 执行器生成失败:', error)
|
||||
}
|
||||
|
||||
// 方式2: 使用直接调用 API
|
||||
try {
|
||||
console.log('🎨 使用直接 API 生成图像...')
|
||||
const result2 = await generateImage('openai', { apiKey: process.env.OPENAI_API_KEY! }, 'dall-e-3', {
|
||||
prompt: 'A magical forest with glowing mushrooms and fairy lights',
|
||||
aspectRatio: '16:9',
|
||||
providerOptions: {
|
||||
openai: {
|
||||
quality: 'hd',
|
||||
style: 'vivid'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
console.log('✅ 直接 API 生成成功!')
|
||||
console.log('📊 结果:', {
|
||||
imagesCount: result2.images.length,
|
||||
mediaType: result2.image.mediaType,
|
||||
hasBase64: !!result2.image.base64
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('❌ 直接 API 生成失败:', error)
|
||||
}
|
||||
|
||||
// 方式3: 支持其他提供商 (Google Imagen)
|
||||
if (process.env.GOOGLE_API_KEY) {
|
||||
try {
|
||||
console.log('🎨 使用 Google Imagen 生成图像...')
|
||||
const googleExecutor = createExecutor('google', {
|
||||
apiKey: process.env.GOOGLE_API_KEY!
|
||||
})
|
||||
|
||||
const result3 = await googleExecutor.generateImage('imagen-3.0-generate-002', {
|
||||
prompt: 'A serene mountain lake at dawn with mist rising from the water',
|
||||
aspectRatio: '1:1'
|
||||
})
|
||||
|
||||
console.log('✅ Google Imagen 生成成功!')
|
||||
console.log('📊 结果:', {
|
||||
imagesCount: result3.images.length,
|
||||
mediaType: result3.image.mediaType,
|
||||
hasBase64: !!result3.image.base64
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('❌ Google Imagen 生成失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 方式4: 支持插件系统
|
||||
const pluginExample = async () => {
|
||||
console.log('🔌 演示插件系统...')
|
||||
|
||||
// 创建一个示例插件,用于修改提示词
|
||||
const promptEnhancerPlugin = {
|
||||
name: 'prompt-enhancer',
|
||||
transformParams: async (params: any) => {
|
||||
console.log('🔧 插件: 增强提示词...')
|
||||
return {
|
||||
...params,
|
||||
prompt: `${params.prompt}, highly detailed, cinematic lighting, 4K resolution`
|
||||
}
|
||||
},
|
||||
transformResult: async (result: any) => {
|
||||
console.log('🔧 插件: 处理结果...')
|
||||
return {
|
||||
...result,
|
||||
enhanced: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const executorWithPlugin = createExecutor(
|
||||
'openai',
|
||||
{
|
||||
apiKey: process.env.OPENAI_API_KEY!
|
||||
},
|
||||
[promptEnhancerPlugin]
|
||||
)
|
||||
|
||||
try {
|
||||
const result4 = await executorWithPlugin.generateImage('dall-e-3', {
|
||||
prompt: 'A cute robot playing in a garden'
|
||||
})
|
||||
|
||||
console.log('✅ 插件系统生成成功!')
|
||||
console.log('📊 结果:', {
|
||||
imagesCount: result4.images.length,
|
||||
enhanced: (result4 as any).enhanced,
|
||||
mediaType: result4.image.mediaType
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('❌ 插件系统生成失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
await pluginExample()
|
||||
}
|
||||
|
||||
// 错误处理演示
|
||||
async function errorHandlingExample() {
|
||||
console.log('⚠️ 演示错误处理...')
|
||||
|
||||
try {
|
||||
const executor = createExecutor('openai', {
|
||||
apiKey: 'invalid-key'
|
||||
})
|
||||
|
||||
await executor.generateImage('dall-e-3', {
|
||||
prompt: 'Test image'
|
||||
})
|
||||
} catch (error: any) {
|
||||
console.log('✅ 成功捕获错误:', error.constructor.name)
|
||||
console.log('📋 错误信息:', error.message)
|
||||
console.log('🏷️ 提供商ID:', error.providerId)
|
||||
console.log('🏷️ 模型ID:', error.modelId)
|
||||
}
|
||||
}
|
||||
|
||||
// 运行示例
|
||||
if (require.main === module) {
|
||||
main()
|
||||
.then(() => {
|
||||
console.log('🎉 所有示例完成!')
|
||||
return errorHandlingExample()
|
||||
})
|
||||
.then(() => {
|
||||
console.log('🎯 示例程序结束')
|
||||
process.exit(0)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('💥 程序执行出错:', error)
|
||||
process.exit(1)
|
||||
})
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@cherrystudio/ai-core",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.0-alpha.11",
|
||||
"description": "Cherry Studio AI Core - Unified AI Provider Interface Based on Vercel AI SDK",
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.mjs",
|
||||
@@ -36,15 +36,16 @@
|
||||
"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.5",
|
||||
"@ai-sdk/azure": "^2.0.16",
|
||||
"@ai-sdk/deepseek": "^1.0.9",
|
||||
"@ai-sdk/google": "^2.0.7",
|
||||
"@ai-sdk/openai": "^2.0.19",
|
||||
"@ai-sdk/openai-compatible": "^1.0.9",
|
||||
"@ai-sdk/provider": "^2.0.0",
|
||||
"@ai-sdk/provider-utils": "^3.0.12",
|
||||
"@ai-sdk/xai": "^2.0.26",
|
||||
"zod": "^4.1.5"
|
||||
"@ai-sdk/provider-utils": "^3.0.4",
|
||||
"@ai-sdk/xai": "^2.0.9",
|
||||
"zod": "^3.25.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tsdown": "^0.12.9",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* 中间件管理器
|
||||
* 专注于 AI SDK 中间件的管理,与插件系统分离
|
||||
*/
|
||||
import type { LanguageModelV2Middleware } from '@ai-sdk/provider'
|
||||
import { LanguageModelV2Middleware } from '@ai-sdk/provider'
|
||||
|
||||
/**
|
||||
* 创建中间件列表
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* 中间件系统类型定义
|
||||
*/
|
||||
import type { LanguageModelV2Middleware } from '@ai-sdk/provider'
|
||||
import { LanguageModelV2Middleware } from '@ai-sdk/provider'
|
||||
|
||||
/**
|
||||
* 具名中间件接口
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* 模型包装工具函数
|
||||
* 用于将中间件应用到LanguageModel上
|
||||
*/
|
||||
import type { LanguageModelV2, LanguageModelV2Middleware } from '@ai-sdk/provider'
|
||||
import { LanguageModelV2, LanguageModelV2Middleware } from '@ai-sdk/provider'
|
||||
import { wrapLanguageModel } from 'ai'
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* 集成了来自 ModelCreator 的特殊处理逻辑
|
||||
*/
|
||||
|
||||
import type { EmbeddingModelV2, ImageModelV2, LanguageModelV2, LanguageModelV2Middleware } from '@ai-sdk/provider'
|
||||
import { EmbeddingModelV2, ImageModelV2, LanguageModelV2, LanguageModelV2Middleware } from '@ai-sdk/provider'
|
||||
|
||||
import { wrapModelWithMiddlewares } from '../middleware/wrapper'
|
||||
import { DEFAULT_SEPARATOR, globalRegistryManagement } from '../providers/RegistryManagement'
|
||||
@@ -84,6 +84,7 @@ export class ModelResolver {
|
||||
*/
|
||||
private resolveTraditionalModel(providerId: string, modelId: string): LanguageModelV2 {
|
||||
const fullModelId = `${providerId}${DEFAULT_SEPARATOR}${modelId}`
|
||||
console.log('fullModelId', fullModelId)
|
||||
return globalRegistryManagement.languageModel(fullModelId as any)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Creation 模块类型定义
|
||||
*/
|
||||
import type { LanguageModelV2Middleware } from '@ai-sdk/provider'
|
||||
import { LanguageModelV2Middleware } from '@ai-sdk/provider'
|
||||
|
||||
import type { ProviderId, ProviderSettingsMap } from '../providers/types'
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ExtractProviderOptions, ProviderOptionsMap, TypedProviderOptions } from './types'
|
||||
import { ExtractProviderOptions, ProviderOptionsMap, TypedProviderOptions } from './types'
|
||||
|
||||
/**
|
||||
* 创建特定供应商的选项
|
||||
@@ -59,7 +59,7 @@ export function createGoogleOptions(options: ExtractProviderOptions<'google'>) {
|
||||
/**
|
||||
* 创建OpenRouter供应商选项的便捷函数
|
||||
*/
|
||||
export function createOpenRouterOptions(options: ExtractProviderOptions<'openrouter'> | Record<string, any>) {
|
||||
export function createOpenRouterOptions(options: ExtractProviderOptions<'openrouter'>) {
|
||||
return createProviderOptions('openrouter', options)
|
||||
}
|
||||
|
||||
|
||||
38
packages/aiCore/src/core/options/openrouter.ts
Normal file
38
packages/aiCore/src/core/options/openrouter.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
export type OpenRouterProviderOptions = {
|
||||
models?: string[]
|
||||
|
||||
/**
|
||||
* https://openrouter.ai/docs/use-cases/reasoning-tokens
|
||||
* One of `max_tokens` or `effort` is required.
|
||||
* If `exclude` is true, reasoning will be removed from the response. Default is false.
|
||||
*/
|
||||
reasoning?: {
|
||||
exclude?: boolean
|
||||
} & (
|
||||
| {
|
||||
max_tokens: number
|
||||
}
|
||||
| {
|
||||
effort: 'high' | 'medium' | 'low'
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* A unique identifier representing your end-user, which can
|
||||
* help OpenRouter to monitor and detect abuse.
|
||||
*/
|
||||
user?: string
|
||||
|
||||
extraBody?: Record<string, unknown>
|
||||
|
||||
/**
|
||||
* Enable usage accounting to get detailed token usage information.
|
||||
* https://openrouter.ai/docs/use-cases/usage-accounting
|
||||
*/
|
||||
usage?: {
|
||||
/**
|
||||
* When true, includes token usage information in the response.
|
||||
*/
|
||||
include: boolean
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,9 @@ import { type AnthropicProviderOptions } from '@ai-sdk/anthropic'
|
||||
import { type GoogleGenerativeAIProviderOptions } from '@ai-sdk/google'
|
||||
import { type OpenAIResponsesProviderOptions } from '@ai-sdk/openai'
|
||||
import { type SharedV2ProviderMetadata } from '@ai-sdk/provider'
|
||||
import { type XaiProviderOptions } from '@ai-sdk/xai'
|
||||
import { type OpenRouterProviderOptions } from '@openrouter/ai-sdk-provider'
|
||||
|
||||
import { type OpenRouterProviderOptions } from './openrouter'
|
||||
import { type XaiProviderOptions } from './xai'
|
||||
|
||||
export type ProviderOptions<T extends keyof SharedV2ProviderMetadata> = SharedV2ProviderMetadata[T]
|
||||
|
||||
|
||||
86
packages/aiCore/src/core/options/xai.ts
Normal file
86
packages/aiCore/src/core/options/xai.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
// copy from @ai-sdk/xai/xai-chat-options.ts
|
||||
// 如果@ai-sdk/xai暴露出了xaiProviderOptions就删除这个文件
|
||||
|
||||
import * as z from 'zod/v4'
|
||||
|
||||
const webSourceSchema = z.object({
|
||||
type: z.literal('web'),
|
||||
country: z.string().length(2).optional(),
|
||||
excludedWebsites: z.array(z.string()).max(5).optional(),
|
||||
allowedWebsites: z.array(z.string()).max(5).optional(),
|
||||
safeSearch: z.boolean().optional()
|
||||
})
|
||||
|
||||
const xSourceSchema = z.object({
|
||||
type: z.literal('x'),
|
||||
xHandles: z.array(z.string()).optional()
|
||||
})
|
||||
|
||||
const newsSourceSchema = z.object({
|
||||
type: z.literal('news'),
|
||||
country: z.string().length(2).optional(),
|
||||
excludedWebsites: z.array(z.string()).max(5).optional(),
|
||||
safeSearch: z.boolean().optional()
|
||||
})
|
||||
|
||||
const rssSourceSchema = z.object({
|
||||
type: z.literal('rss'),
|
||||
links: z.array(z.url()).max(1) // currently only supports one RSS link
|
||||
})
|
||||
|
||||
const searchSourceSchema = z.discriminatedUnion('type', [
|
||||
webSourceSchema,
|
||||
xSourceSchema,
|
||||
newsSourceSchema,
|
||||
rssSourceSchema
|
||||
])
|
||||
|
||||
export const xaiProviderOptions = z.object({
|
||||
/**
|
||||
* reasoning effort for reasoning models
|
||||
* only supported by grok-3-mini and grok-3-mini-fast models
|
||||
*/
|
||||
reasoningEffort: z.enum(['low', 'high']).optional(),
|
||||
|
||||
searchParameters: z
|
||||
.object({
|
||||
/**
|
||||
* search mode preference
|
||||
* - "off": disables search completely
|
||||
* - "auto": model decides whether to search (default)
|
||||
* - "on": always enables search
|
||||
*/
|
||||
mode: z.enum(['off', 'auto', 'on']),
|
||||
|
||||
/**
|
||||
* whether to return citations in the response
|
||||
* defaults to true
|
||||
*/
|
||||
returnCitations: z.boolean().optional(),
|
||||
|
||||
/**
|
||||
* start date for search data (ISO8601 format: YYYY-MM-DD)
|
||||
*/
|
||||
fromDate: z.string().optional(),
|
||||
|
||||
/**
|
||||
* end date for search data (ISO8601 format: YYYY-MM-DD)
|
||||
*/
|
||||
toDate: z.string().optional(),
|
||||
|
||||
/**
|
||||
* maximum number of search results to consider
|
||||
* defaults to 20
|
||||
*/
|
||||
maxSearchResults: z.number().min(1).max(50).optional(),
|
||||
|
||||
/**
|
||||
* data sources to search from
|
||||
* defaults to ["web", "x"] if not specified
|
||||
*/
|
||||
sources: z.array(searchSourceSchema).optional()
|
||||
})
|
||||
.optional()
|
||||
})
|
||||
|
||||
export type XaiProviderOptions = z.infer<typeof xaiProviderOptions>
|
||||
@@ -1,38 +0,0 @@
|
||||
import { google } from '@ai-sdk/google'
|
||||
|
||||
import { definePlugin } from '../../'
|
||||
import type { AiRequestContext } from '../../types'
|
||||
|
||||
const toolNameMap = {
|
||||
googleSearch: 'google_search',
|
||||
urlContext: 'url_context',
|
||||
codeExecution: 'code_execution'
|
||||
} as const
|
||||
|
||||
type ToolConfigKey = keyof typeof toolNameMap
|
||||
type ToolConfig = { googleSearch?: boolean; urlContext?: boolean; codeExecution?: boolean }
|
||||
|
||||
export const googleToolsPlugin = (config?: ToolConfig) =>
|
||||
definePlugin({
|
||||
name: 'googleToolsPlugin',
|
||||
transformParams: <T>(params: T, context: AiRequestContext): T => {
|
||||
const { providerId } = context
|
||||
if (providerId === 'google' && config) {
|
||||
if (typeof params === 'object' && params !== null) {
|
||||
const typedParams = params as T & { tools?: Record<string, unknown> }
|
||||
|
||||
if (!typedParams.tools) {
|
||||
typedParams.tools = {}
|
||||
}
|
||||
// 使用类型安全的方式遍历配置
|
||||
;(Object.keys(config) as ToolConfigKey[]).forEach((key) => {
|
||||
if (config[key] && key in toolNameMap && key in google.tools) {
|
||||
const toolName = toolNameMap[key]
|
||||
typedParams.tools![toolName] = google.tools[key]({})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
return params
|
||||
}
|
||||
})
|
||||
@@ -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 type { PromptToolUseConfig, ToolUseRequestContext, ToolUseResult } from './toolUsePlugin/type'
|
||||
export { webSearchPlugin } from './webSearchPlugin'
|
||||
|
||||
@@ -27,20 +27,10 @@ export class StreamEventManager {
|
||||
/**
|
||||
* 发送步骤完成事件
|
||||
*/
|
||||
sendStepFinishEvent(
|
||||
controller: StreamController,
|
||||
chunk: any,
|
||||
context: AiRequestContext,
|
||||
finishReason: string = 'stop'
|
||||
): void {
|
||||
// 累加当前步骤的 usage
|
||||
if (chunk.usage && context.accumulatedUsage) {
|
||||
this.accumulateUsage(context.accumulatedUsage, chunk.usage)
|
||||
}
|
||||
|
||||
sendStepFinishEvent(controller: StreamController, chunk: any): void {
|
||||
controller.enqueue({
|
||||
type: 'finish-step',
|
||||
finishReason,
|
||||
finishReason: 'stop',
|
||||
response: chunk.response,
|
||||
usage: chunk.usage,
|
||||
providerMetadata: chunk.providerMetadata
|
||||
@@ -53,32 +43,28 @@ export class StreamEventManager {
|
||||
async handleRecursiveCall(
|
||||
controller: StreamController,
|
||||
recursiveParams: any,
|
||||
context: AiRequestContext
|
||||
context: AiRequestContext,
|
||||
stepId: string
|
||||
): Promise<void> {
|
||||
// try {
|
||||
// 重置工具执行状态,准备处理新的步骤
|
||||
context.hasExecutedToolsInCurrentStep = false
|
||||
try {
|
||||
console.log('[MCP Prompt] Starting recursive call after tool execution...')
|
||||
|
||||
const recursiveResult = await context.recursiveCall(recursiveParams)
|
||||
const recursiveResult = await context.recursiveCall(recursiveParams)
|
||||
|
||||
if (recursiveResult && recursiveResult.fullStream) {
|
||||
await this.pipeRecursiveStream(controller, recursiveResult.fullStream, context)
|
||||
} else {
|
||||
console.warn('[MCP Prompt] No fullstream found in recursive result:', recursiveResult)
|
||||
if (recursiveResult && recursiveResult.fullStream) {
|
||||
await this.pipeRecursiveStream(controller, recursiveResult.fullStream)
|
||||
} else {
|
||||
console.warn('[MCP Prompt] No fullstream found in recursive result:', recursiveResult)
|
||||
}
|
||||
} catch (error) {
|
||||
this.handleRecursiveCallError(controller, error, stepId)
|
||||
}
|
||||
// } catch (error) {
|
||||
// this.handleRecursiveCallError(controller, error, stepId)
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* 将递归流的数据传递到当前流
|
||||
*/
|
||||
private async pipeRecursiveStream(
|
||||
controller: StreamController,
|
||||
recursiveStream: ReadableStream,
|
||||
context?: AiRequestContext
|
||||
): Promise<void> {
|
||||
private async pipeRecursiveStream(controller: StreamController, recursiveStream: ReadableStream): Promise<void> {
|
||||
const reader = recursiveStream.getReader()
|
||||
try {
|
||||
while (true) {
|
||||
@@ -87,16 +73,9 @@ export class StreamEventManager {
|
||||
break
|
||||
}
|
||||
if (value.type === 'finish') {
|
||||
// 迭代的流不发finish,但需要累加其 usage
|
||||
if (value.usage && context?.accumulatedUsage) {
|
||||
this.accumulateUsage(context.accumulatedUsage, value.usage)
|
||||
}
|
||||
// 迭代的流不发finish
|
||||
break
|
||||
}
|
||||
// 对于 finish-step 类型,累加其 usage
|
||||
if (value.type === 'finish-step' && value.usage && context?.accumulatedUsage) {
|
||||
this.accumulateUsage(context.accumulatedUsage, value.usage)
|
||||
}
|
||||
// 将递归流的数据传递到当前流
|
||||
controller.enqueue(value)
|
||||
}
|
||||
@@ -108,25 +87,25 @@ export class StreamEventManager {
|
||||
/**
|
||||
* 处理递归调用错误
|
||||
*/
|
||||
// private handleRecursiveCallError(controller: StreamController, error: unknown): void {
|
||||
// console.error('[MCP Prompt] Recursive call failed:', error)
|
||||
private handleRecursiveCallError(controller: StreamController, error: unknown, stepId: string): void {
|
||||
console.error('[MCP Prompt] Recursive call failed:', error)
|
||||
|
||||
// // 使用 AI SDK 标准错误格式,但不中断流
|
||||
// controller.enqueue({
|
||||
// type: 'error',
|
||||
// error: {
|
||||
// message: error instanceof Error ? error.message : String(error),
|
||||
// name: error instanceof Error ? error.name : 'RecursiveCallError'
|
||||
// }
|
||||
// })
|
||||
// 使用 AI SDK 标准错误格式,但不中断流
|
||||
controller.enqueue({
|
||||
type: 'error',
|
||||
error: {
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
name: error instanceof Error ? error.name : 'RecursiveCallError'
|
||||
}
|
||||
})
|
||||
|
||||
// // // 继续发送文本增量,保持流的连续性
|
||||
// // controller.enqueue({
|
||||
// // type: 'text-delta',
|
||||
// // id: stepId,
|
||||
// // text: '\n\n[工具执行后递归调用失败,继续对话...]'
|
||||
// // })
|
||||
// }
|
||||
// 继续发送文本增量,保持流的连续性
|
||||
controller.enqueue({
|
||||
type: 'text-delta',
|
||||
id: stepId,
|
||||
text: '\n\n[工具执行后递归调用失败,继续对话...]'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建递归调用的参数
|
||||
@@ -157,18 +136,4 @@ export class StreamEventManager {
|
||||
|
||||
return recursiveParams
|
||||
}
|
||||
|
||||
/**
|
||||
* 累加 usage 数据
|
||||
*/
|
||||
private accumulateUsage(target: any, source: any): void {
|
||||
if (!target || !source) return
|
||||
|
||||
// 累加各种 token 类型
|
||||
target.inputTokens = (target.inputTokens || 0) + (source.inputTokens || 0)
|
||||
target.outputTokens = (target.outputTokens || 0) + (source.outputTokens || 0)
|
||||
target.totalTokens = (target.totalTokens || 0) + (source.totalTokens || 0)
|
||||
target.reasoningTokens = (target.reasoningTokens || 0) + (source.reasoningTokens || 0)
|
||||
target.cachedInputTokens = (target.cachedInputTokens || 0) + (source.cachedInputTokens || 0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* 负责工具的执行、结果格式化和相关事件发送
|
||||
* 从 promptToolUsePlugin.ts 中提取出来以降低复杂度
|
||||
*/
|
||||
import type { ToolSet, TypedToolError } from 'ai'
|
||||
import type { ToolSet } from 'ai'
|
||||
|
||||
import type { ToolUseResult } from './type'
|
||||
|
||||
@@ -38,6 +38,7 @@ export class ToolExecutor {
|
||||
controller: StreamController
|
||||
): Promise<ExecutedResult[]> {
|
||||
const executedResults: ExecutedResult[] = []
|
||||
|
||||
for (const toolUse of toolUses) {
|
||||
try {
|
||||
const tool = tools[toolUse.toolName]
|
||||
@@ -45,12 +46,17 @@ export class ToolExecutor {
|
||||
throw new Error(`Tool "${toolUse.toolName}" has no execute method`)
|
||||
}
|
||||
|
||||
// 发送工具调用开始事件
|
||||
this.sendToolStartEvents(controller, toolUse)
|
||||
|
||||
console.log(`[MCP Prompt Stream] Executing tool: ${toolUse.toolName}`, toolUse.arguments)
|
||||
|
||||
// 发送 tool-call 事件
|
||||
controller.enqueue({
|
||||
type: 'tool-call',
|
||||
toolCallId: toolUse.id,
|
||||
toolName: toolUse.toolName,
|
||||
input: toolUse.arguments
|
||||
input: tool.inputSchema
|
||||
})
|
||||
|
||||
const result = await tool.execute(toolUse.arguments, {
|
||||
@@ -105,46 +111,45 @@ export class ToolExecutor {
|
||||
/**
|
||||
* 发送工具调用开始相关事件
|
||||
*/
|
||||
// private sendToolStartEvents(controller: StreamController, toolUse: ToolUseResult): void {
|
||||
// // 发送 tool-input-start 事件
|
||||
// controller.enqueue({
|
||||
// type: 'tool-input-start',
|
||||
// id: toolUse.id,
|
||||
// toolName: toolUse.toolName
|
||||
// })
|
||||
// }
|
||||
private sendToolStartEvents(controller: StreamController, toolUse: ToolUseResult): void {
|
||||
// 发送 tool-input-start 事件
|
||||
controller.enqueue({
|
||||
type: 'tool-input-start',
|
||||
id: toolUse.id,
|
||||
toolName: toolUse.toolName
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理工具执行错误
|
||||
*/
|
||||
private handleToolError<T extends ToolSet>(
|
||||
private handleToolError(
|
||||
toolUse: ToolUseResult,
|
||||
error: unknown,
|
||||
controller: StreamController
|
||||
// _tools: ToolSet
|
||||
): ExecutedResult {
|
||||
// 使用 AI SDK 标准错误格式
|
||||
const toolError: TypedToolError<T> = {
|
||||
type: 'tool-error',
|
||||
toolCallId: toolUse.id,
|
||||
toolName: toolUse.toolName,
|
||||
input: toolUse.arguments,
|
||||
error
|
||||
}
|
||||
|
||||
controller.enqueue(toolError)
|
||||
|
||||
// 发送标准错误事件
|
||||
// controller.enqueue({
|
||||
// const toolError: TypedToolError<typeof _tools> = {
|
||||
// type: 'tool-error',
|
||||
// toolCallId: toolUse.id,
|
||||
// error: error instanceof Error ? error.message : String(error),
|
||||
// input: toolUse.arguments
|
||||
// })
|
||||
// toolName: toolUse.toolName,
|
||||
// input: toolUse.arguments,
|
||||
// error: error instanceof Error ? error.message : String(error)
|
||||
// }
|
||||
|
||||
// controller.enqueue(toolError)
|
||||
|
||||
// 发送标准错误事件
|
||||
controller.enqueue({
|
||||
type: 'error',
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
|
||||
return {
|
||||
toolCallId: toolUse.id,
|
||||
toolName: toolUse.toolName,
|
||||
result: error,
|
||||
result: error instanceof Error ? error.message : String(error),
|
||||
isError: true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,18 +8,8 @@ import type { TextStreamPart, ToolSet } from 'ai'
|
||||
import { definePlugin } from '../../index'
|
||||
import type { AiRequestContext } from '../../types'
|
||||
import { StreamEventManager } from './StreamEventManager'
|
||||
import { type TagConfig, TagExtractor } from './tagExtraction'
|
||||
import { ToolExecutor } from './ToolExecutor'
|
||||
import type { PromptToolUseConfig, ToolUseResult } from './type'
|
||||
|
||||
/**
|
||||
* 工具使用标签配置
|
||||
*/
|
||||
const TOOL_USE_TAG_CONFIG: TagConfig = {
|
||||
openingTag: '<tool_use>',
|
||||
closingTag: '</tool_use>',
|
||||
separator: '\n'
|
||||
}
|
||||
import { PromptToolUseConfig, ToolUseResult } from './type'
|
||||
|
||||
/**
|
||||
* 默认系统提示符模板(提取自 Cherry Studio)
|
||||
@@ -156,10 +146,8 @@ Assistant: The population of Shanghai is 26 million, while Guangzhou has a popul
|
||||
/**
|
||||
* 构建可用工具部分(提取自 Cherry Studio)
|
||||
*/
|
||||
function buildAvailableTools(tools: ToolSet): string | null {
|
||||
function buildAvailableTools(tools: ToolSet): string {
|
||||
const availableTools = Object.keys(tools)
|
||||
if (availableTools.length === 0) return null
|
||||
const result = availableTools
|
||||
.map((toolName: string) => {
|
||||
const tool = tools[toolName]
|
||||
return `
|
||||
@@ -174,7 +162,7 @@ function buildAvailableTools(tools: ToolSet): string | null {
|
||||
})
|
||||
.join('\n')
|
||||
return `<tools>
|
||||
${result}
|
||||
${availableTools}
|
||||
</tools>`
|
||||
}
|
||||
|
||||
@@ -183,7 +171,6 @@ ${result}
|
||||
*/
|
||||
function defaultBuildSystemPrompt(userSystemPrompt: string, tools: ToolSet): string {
|
||||
const availableTools = buildAvailableTools(tools)
|
||||
if (availableTools === null) return userSystemPrompt
|
||||
|
||||
const fullPrompt = DEFAULT_SYSTEM_PROMPT.replace('{{ TOOL_USE_EXAMPLES }}', DEFAULT_TOOL_USE_EXAMPLES)
|
||||
.replace('{{ AVAILABLE_TOOLS }}', availableTools)
|
||||
@@ -261,76 +248,40 @@ export const createPromptToolUsePlugin = (config: PromptToolUseConfig = {}) => {
|
||||
return params
|
||||
}
|
||||
|
||||
// 分离 provider-defined 和其他类型的工具
|
||||
const providerDefinedTools: ToolSet = {}
|
||||
const promptTools: ToolSet = {}
|
||||
context.mcpTools = params.tools
|
||||
console.log('tools stored in context', params.tools)
|
||||
|
||||
for (const [toolName, tool] of Object.entries(params.tools as ToolSet)) {
|
||||
if (tool.type === 'provider-defined') {
|
||||
// provider-defined 类型的工具保留在 tools 参数中
|
||||
providerDefinedTools[toolName] = tool
|
||||
} else {
|
||||
// 其他工具转换为 prompt 模式
|
||||
promptTools[toolName] = tool
|
||||
}
|
||||
}
|
||||
|
||||
// 只有当有非 provider-defined 工具时才保存到 context
|
||||
if (Object.keys(promptTools).length > 0) {
|
||||
context.mcpTools = promptTools
|
||||
}
|
||||
|
||||
// 构建系统提示符(只包含非 provider-defined 工具)
|
||||
// 构建系统提示符
|
||||
const userSystemPrompt = typeof params.system === 'string' ? params.system : ''
|
||||
const systemPrompt = buildSystemPrompt(userSystemPrompt, promptTools)
|
||||
const systemPrompt = buildSystemPrompt(userSystemPrompt, params.tools)
|
||||
let systemMessage: string | null = systemPrompt
|
||||
console.log('config.context', context)
|
||||
if (config.createSystemMessage) {
|
||||
// 🎯 如果用户提供了自定义处理函数,使用它
|
||||
systemMessage = config.createSystemMessage(systemPrompt, params, context)
|
||||
}
|
||||
|
||||
// 保留 provider-defined tools,移除其他 tools
|
||||
// 移除 tools,改为 prompt 模式
|
||||
const transformedParams = {
|
||||
...params,
|
||||
...(systemMessage ? { system: systemMessage } : {}),
|
||||
tools: Object.keys(providerDefinedTools).length > 0 ? providerDefinedTools : undefined
|
||||
tools: undefined
|
||||
}
|
||||
context.originalParams = transformedParams
|
||||
console.log('transformedParams', transformedParams)
|
||||
return transformedParams
|
||||
},
|
||||
transformStream: (_: any, context: AiRequestContext) => () => {
|
||||
let textBuffer = ''
|
||||
// let stepId = ''
|
||||
let stepId = ''
|
||||
|
||||
// 如果没有需要 prompt 模式处理的工具,直接返回原始流
|
||||
if (!context.mcpTools) {
|
||||
return new TransformStream()
|
||||
throw new Error('No tools available')
|
||||
}
|
||||
|
||||
// 从 context 中获取或初始化 usage 累加器
|
||||
if (!context.accumulatedUsage) {
|
||||
context.accumulatedUsage = {
|
||||
inputTokens: 0,
|
||||
outputTokens: 0,
|
||||
totalTokens: 0,
|
||||
reasoningTokens: 0,
|
||||
cachedInputTokens: 0
|
||||
}
|
||||
}
|
||||
|
||||
// 创建工具执行器、流事件管理器和标签提取器
|
||||
// 创建工具执行器和流事件管理器
|
||||
const toolExecutor = new ToolExecutor()
|
||||
const streamEventManager = new StreamEventManager()
|
||||
const tagExtractor = new TagExtractor(TOOL_USE_TAG_CONFIG)
|
||||
|
||||
// 在context中初始化工具执行状态,避免递归调用时状态丢失
|
||||
if (!context.hasExecutedToolsInCurrentStep) {
|
||||
context.hasExecutedToolsInCurrentStep = false
|
||||
}
|
||||
|
||||
// 用于hold text-start事件,直到确认有非工具标签内容
|
||||
let pendingTextStart: TextStreamPart<TOOLS> | null = null
|
||||
let hasStartedText = false
|
||||
|
||||
type TOOLS = NonNullable<typeof context.mcpTools>
|
||||
return new TransformStream<TextStreamPart<TOOLS>, TextStreamPart<TOOLS>>({
|
||||
@@ -338,106 +289,83 @@ export const createPromptToolUsePlugin = (config: PromptToolUseConfig = {}) => {
|
||||
chunk: TextStreamPart<TOOLS>,
|
||||
controller: TransformStreamDefaultController<TextStreamPart<TOOLS>>
|
||||
) {
|
||||
// Hold住text-start事件,直到确认有非工具标签内容
|
||||
if ((chunk as any).type === 'text-start') {
|
||||
pendingTextStart = chunk
|
||||
return
|
||||
}
|
||||
|
||||
// text-delta阶段:收集文本内容并过滤工具标签
|
||||
// 收集文本内容
|
||||
if (chunk.type === 'text-delta') {
|
||||
textBuffer += chunk.text || ''
|
||||
// stepId = chunk.id || ''
|
||||
|
||||
// 使用TagExtractor过滤工具标签,只传递非标签内容到UI层
|
||||
const extractionResults = tagExtractor.processText(chunk.text || '')
|
||||
|
||||
for (const result of extractionResults) {
|
||||
// 只传递非标签内容到UI层
|
||||
if (!result.isTagContent && result.content) {
|
||||
// 如果还没有发送text-start且有pending的text-start,先发送它
|
||||
if (!hasStartedText && pendingTextStart) {
|
||||
controller.enqueue(pendingTextStart)
|
||||
hasStartedText = true
|
||||
pendingTextStart = null
|
||||
}
|
||||
|
||||
const filteredChunk = {
|
||||
...chunk,
|
||||
text: result.content
|
||||
}
|
||||
controller.enqueue(filteredChunk)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (chunk.type === 'text-end') {
|
||||
// 只有当已经发送了text-start时才发送text-end
|
||||
if (hasStartedText) {
|
||||
controller.enqueue(chunk)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (chunk.type === 'finish-step') {
|
||||
// 统一在finish-step阶段检查并执行工具调用
|
||||
const tools = context.mcpTools
|
||||
if (tools && Object.keys(tools).length > 0 && !context.hasExecutedToolsInCurrentStep) {
|
||||
// 解析完整的textBuffer来检测工具调用
|
||||
const { results: parsedTools } = parseToolUse(textBuffer, tools)
|
||||
const validToolUses = parsedTools.filter((t) => t.status === 'pending')
|
||||
|
||||
if (validToolUses.length > 0) {
|
||||
context.hasExecutedToolsInCurrentStep = true
|
||||
|
||||
// 执行工具调用(不需要手动发送 start-step,外部流已经处理)
|
||||
const executedResults = await toolExecutor.executeTools(validToolUses, tools, controller)
|
||||
|
||||
// 发送步骤完成事件,使用 tool-calls 作为 finishReason
|
||||
streamEventManager.sendStepFinishEvent(controller, chunk, context, 'tool-calls')
|
||||
|
||||
// 处理递归调用
|
||||
const toolResultsText = toolExecutor.formatToolResults(executedResults)
|
||||
const recursiveParams = streamEventManager.buildRecursiveParams(
|
||||
context,
|
||||
textBuffer,
|
||||
toolResultsText,
|
||||
tools
|
||||
)
|
||||
|
||||
await streamEventManager.handleRecursiveCall(controller, recursiveParams, context)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有执行工具调用,直接传递原始finish-step事件
|
||||
stepId = chunk.id || ''
|
||||
controller.enqueue(chunk)
|
||||
return
|
||||
}
|
||||
|
||||
if (chunk.type === 'text-end' || chunk.type === 'finish-step') {
|
||||
const tools = context.mcpTools
|
||||
if (!tools || Object.keys(tools).length === 0) {
|
||||
controller.enqueue(chunk)
|
||||
return
|
||||
}
|
||||
|
||||
// 解析工具调用
|
||||
const { results: parsedTools, content: parsedContent } = parseToolUse(textBuffer, tools)
|
||||
const validToolUses = parsedTools.filter((t) => t.status === 'pending')
|
||||
|
||||
// 如果没有有效的工具调用,直接传递原始事件
|
||||
if (validToolUses.length === 0) {
|
||||
controller.enqueue(chunk)
|
||||
return
|
||||
}
|
||||
|
||||
if (chunk.type === 'text-end') {
|
||||
controller.enqueue({
|
||||
type: 'text-end',
|
||||
id: stepId,
|
||||
providerMetadata: {
|
||||
text: {
|
||||
value: parsedContent
|
||||
}
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
controller.enqueue({
|
||||
...chunk,
|
||||
finishReason: 'tool-calls'
|
||||
})
|
||||
|
||||
// 发送步骤开始事件
|
||||
streamEventManager.sendStepStartEvent(controller)
|
||||
|
||||
// 执行工具调用
|
||||
const executedResults = await toolExecutor.executeTools(validToolUses, tools, controller)
|
||||
|
||||
// 发送步骤完成事件
|
||||
streamEventManager.sendStepFinishEvent(controller, chunk)
|
||||
|
||||
// 处理递归调用
|
||||
if (validToolUses.length > 0) {
|
||||
const toolResultsText = toolExecutor.formatToolResults(executedResults)
|
||||
const recursiveParams = streamEventManager.buildRecursiveParams(
|
||||
context,
|
||||
textBuffer,
|
||||
toolResultsText,
|
||||
tools
|
||||
)
|
||||
|
||||
await streamEventManager.handleRecursiveCall(controller, recursiveParams, context, stepId)
|
||||
}
|
||||
|
||||
// 清理状态
|
||||
textBuffer = ''
|
||||
return
|
||||
}
|
||||
|
||||
// 处理 finish 类型,使用累加后的 totalUsage
|
||||
if (chunk.type === 'finish') {
|
||||
controller.enqueue({
|
||||
...chunk,
|
||||
totalUsage: context.accumulatedUsage
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 对于其他类型的事件,直接传递(不包括text-start,已在上面处理)
|
||||
if ((chunk as any).type !== 'text-start') {
|
||||
controller.enqueue(chunk)
|
||||
}
|
||||
// 对于其他类型的事件,直接传递
|
||||
controller.enqueue(chunk)
|
||||
},
|
||||
|
||||
flush() {
|
||||
// 清理pending状态
|
||||
pendingTextStart = null
|
||||
hasStartedText = false
|
||||
// 流结束时的清理工作
|
||||
console.log('[MCP Prompt] Stream ended, cleaning up...')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { ToolSet } from 'ai'
|
||||
import { ToolSet } from 'ai'
|
||||
|
||||
import type { AiRequestContext } from '../..'
|
||||
import { AiRequestContext } from '../..'
|
||||
|
||||
/**
|
||||
* 解析结果类型
|
||||
|
||||
@@ -1,26 +1,15 @@
|
||||
import type { anthropic } from '@ai-sdk/anthropic'
|
||||
import type { google } from '@ai-sdk/google'
|
||||
import type { openai } from '@ai-sdk/openai'
|
||||
import type { InferToolInput, InferToolOutput, Tool } from 'ai'
|
||||
import { anthropic } from '@ai-sdk/anthropic'
|
||||
import { google } from '@ai-sdk/google'
|
||||
import { openai } from '@ai-sdk/openai'
|
||||
|
||||
import type { ProviderOptionsMap } from '../../../options/types'
|
||||
import type { OpenRouterSearchConfig } from './openrouter'
|
||||
import { ProviderOptionsMap } from '../../../options/types'
|
||||
|
||||
/**
|
||||
* 从 AI SDK 的工具函数中提取参数类型,以确保类型安全。
|
||||
*/
|
||||
export type OpenAISearchConfig = NonNullable<Parameters<typeof openai.tools.webSearch>[0]>
|
||||
export type OpenAISearchPreviewConfig = NonNullable<Parameters<typeof openai.tools.webSearchPreview>[0]>
|
||||
export type AnthropicSearchConfig = NonNullable<Parameters<typeof anthropic.tools.webSearch_20250305>[0]>
|
||||
export type GoogleSearchConfig = NonNullable<Parameters<typeof google.tools.googleSearch>[0]>
|
||||
export type XAISearchConfig = NonNullable<ProviderOptionsMap['xai']['searchParameters']>
|
||||
|
||||
type NormalizeTool<T> = T extends Tool<infer INPUT, infer OUTPUT> ? Tool<INPUT, OUTPUT> : Tool<any, any>
|
||||
|
||||
type AnthropicWebSearchTool = NormalizeTool<ReturnType<typeof anthropic.tools.webSearch_20250305>>
|
||||
type OpenAIWebSearchTool = NormalizeTool<ReturnType<typeof openai.tools.webSearch>>
|
||||
type OpenAIChatWebSearchTool = NormalizeTool<ReturnType<typeof openai.tools.webSearchPreview>>
|
||||
type GoogleWebSearchTool = NormalizeTool<ReturnType<typeof google.tools.googleSearch>>
|
||||
type OpenAISearchConfig = Parameters<typeof openai.tools.webSearchPreview>[0]
|
||||
type AnthropicSearchConfig = Parameters<typeof anthropic.tools.webSearch_20250305>[0]
|
||||
type GoogleSearchConfig = Parameters<typeof google.tools.googleSearch>[0]
|
||||
|
||||
/**
|
||||
* 插件初始化时接收的完整配置对象
|
||||
@@ -29,12 +18,10 @@ type GoogleWebSearchTool = NormalizeTool<ReturnType<typeof google.tools.googleSe
|
||||
*/
|
||||
export interface WebSearchPluginConfig {
|
||||
openai?: OpenAISearchConfig
|
||||
'openai-chat'?: OpenAISearchPreviewConfig
|
||||
anthropic?: AnthropicSearchConfig
|
||||
xai?: ProviderOptionsMap['xai']['searchParameters']
|
||||
google?: GoogleSearchConfig
|
||||
'google-vertex'?: GoogleSearchConfig
|
||||
openrouter?: OpenRouterSearchConfig
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -44,7 +31,6 @@ export const DEFAULT_WEB_SEARCH_CONFIG: WebSearchPluginConfig = {
|
||||
google: {},
|
||||
'google-vertex': {},
|
||||
openai: {},
|
||||
'openai-chat': {},
|
||||
xai: {
|
||||
mode: 'on',
|
||||
returnCitations: true,
|
||||
@@ -53,44 +39,29 @@ export const DEFAULT_WEB_SEARCH_CONFIG: WebSearchPluginConfig = {
|
||||
},
|
||||
anthropic: {
|
||||
maxUses: 5
|
||||
},
|
||||
openrouter: {
|
||||
plugins: [
|
||||
{
|
||||
id: 'web',
|
||||
max_results: 5
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
export type WebSearchToolOutputSchema = {
|
||||
// Anthropic 工具 - 手动定义
|
||||
anthropic: InferToolOutput<AnthropicWebSearchTool>
|
||||
anthropicWebSearch: Array<{
|
||||
url: string
|
||||
title: string
|
||||
pageAge: string | null
|
||||
encryptedContent: string
|
||||
type: string
|
||||
}>
|
||||
|
||||
// OpenAI 工具 - 基于实际输出
|
||||
// TODO: 上游定义不规范,是unknown
|
||||
// openai: InferToolOutput<ReturnType<typeof openai.tools.webSearch>>
|
||||
openai: {
|
||||
status: 'completed' | 'failed'
|
||||
}
|
||||
'openai-chat': {
|
||||
openaiWebSearch: {
|
||||
status: 'completed' | 'failed'
|
||||
}
|
||||
|
||||
// Google 工具
|
||||
// TODO: 上游定义不规范,是unknown
|
||||
// google: InferToolOutput<ReturnType<typeof google.tools.googleSearch>>
|
||||
google: {
|
||||
googleSearch: {
|
||||
webSearchQueries?: string[]
|
||||
groundingChunks?: Array<{
|
||||
web?: { uri: string; title: string }
|
||||
}>
|
||||
}
|
||||
}
|
||||
|
||||
export type WebSearchToolInputSchema = {
|
||||
anthropic: InferToolInput<AnthropicWebSearchTool>
|
||||
openai: InferToolInput<OpenAIWebSearchTool>
|
||||
google: InferToolInput<GoogleWebSearchTool>
|
||||
'openai-chat': InferToolInput<OpenAIChatWebSearchTool>
|
||||
}
|
||||
|
||||
@@ -6,11 +6,10 @@ import { anthropic } from '@ai-sdk/anthropic'
|
||||
import { google } from '@ai-sdk/google'
|
||||
import { openai } from '@ai-sdk/openai'
|
||||
|
||||
import { createOpenRouterOptions, createXaiOptions, mergeProviderOptions } from '../../../options'
|
||||
import { createXaiOptions, mergeProviderOptions } from '../../../options'
|
||||
import { definePlugin } from '../../'
|
||||
import type { AiRequestContext } from '../../types'
|
||||
import type { WebSearchPluginConfig } from './helper'
|
||||
import { DEFAULT_WEB_SEARCH_CONFIG } from './helper'
|
||||
import { DEFAULT_WEB_SEARCH_CONFIG, WebSearchPluginConfig } from './helper'
|
||||
|
||||
/**
|
||||
* 网络搜索插件
|
||||
@@ -28,14 +27,7 @@ export const webSearchPlugin = (config: WebSearchPluginConfig = DEFAULT_WEB_SEAR
|
||||
case 'openai': {
|
||||
if (config.openai) {
|
||||
if (!params.tools) params.tools = {}
|
||||
params.tools.web_search = openai.tools.webSearch(config.openai)
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'openai-chat': {
|
||||
if (config['openai-chat']) {
|
||||
if (!params.tools) params.tools = {}
|
||||
params.tools.web_search_preview = openai.tools.webSearchPreview(config['openai-chat'])
|
||||
params.tools.web_search_preview = openai.tools.webSearchPreview(config.openai)
|
||||
}
|
||||
break
|
||||
}
|
||||
@@ -64,14 +56,6 @@ export const webSearchPlugin = (config: WebSearchPluginConfig = DEFAULT_WEB_SEAR
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'openrouter': {
|
||||
if (config.openrouter) {
|
||||
const searchOptions = createOpenRouterOptions(config.openrouter)
|
||||
params.providerOptions = mergeProviderOptions(params.providerOptions, searchOptions)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return params
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
export type OpenRouterSearchConfig = {
|
||||
plugins?: Array<{
|
||||
id: 'web'
|
||||
/**
|
||||
* Maximum number of search results to include (default: 5)
|
||||
*/
|
||||
max_results?: number
|
||||
/**
|
||||
* Custom search prompt to guide the search query
|
||||
*/
|
||||
search_prompt?: string
|
||||
}>
|
||||
/**
|
||||
* Built-in web search options for models that support native web search
|
||||
*/
|
||||
web_search_options?: {
|
||||
/**
|
||||
* Maximum number of search results to include
|
||||
*/
|
||||
max_results?: number
|
||||
/**
|
||||
* Custom search prompt to guide the search query
|
||||
*/
|
||||
search_prompt?: string
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,5 @@
|
||||
// 核心类型和接口
|
||||
export type { AiPlugin, AiRequestContext, HookResult, PluginManagerConfig } from './types'
|
||||
import type { ImageModelV2 } from '@ai-sdk/provider'
|
||||
import type { LanguageModel } from 'ai'
|
||||
|
||||
import type { ProviderId } from '../providers'
|
||||
import type { AiPlugin, AiRequestContext } from './types'
|
||||
|
||||
@@ -12,16 +9,16 @@ export { PluginManager } from './manager'
|
||||
// 工具函数
|
||||
export function createContext<T extends ProviderId>(
|
||||
providerId: T,
|
||||
model: LanguageModel | ImageModelV2,
|
||||
modelId: string,
|
||||
originalParams: any
|
||||
): AiRequestContext {
|
||||
return {
|
||||
providerId,
|
||||
model,
|
||||
modelId,
|
||||
originalParams,
|
||||
metadata: {},
|
||||
startTime: Date.now(),
|
||||
requestId: `${providerId}-${typeof model === 'string' ? model : model?.modelId}-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
||||
requestId: `${providerId}-${modelId}-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
||||
// 占位
|
||||
recursiveCall: () => Promise.resolve(null)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { AiPlugin, AiRequestContext } from './types'
|
||||
import { AiPlugin, AiRequestContext } from './types'
|
||||
|
||||
/**
|
||||
* 插件管理器
|
||||
|
||||
@@ -14,7 +14,7 @@ export type RecursiveCallFn = (newParams: any) => Promise<any>
|
||||
*/
|
||||
export interface AiRequestContext {
|
||||
providerId: ProviderId
|
||||
model: LanguageModel | ImageModelV2
|
||||
modelId: string
|
||||
originalParams: any
|
||||
metadata: Record<string, any>
|
||||
startTime: number
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* 例如: aihubmix:anthropic:claude-3.5-sonnet
|
||||
*/
|
||||
|
||||
import type { ProviderV2 } from '@ai-sdk/provider'
|
||||
import { ProviderV2 } from '@ai-sdk/provider'
|
||||
import { customProvider } from 'ai'
|
||||
|
||||
import { globalRegistryManagement } from './RegistryManagement'
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* 基于 AI SDK 原生的 createProviderRegistry
|
||||
*/
|
||||
|
||||
import type { EmbeddingModelV2, ImageModelV2, LanguageModelV2, ProviderV2 } from '@ai-sdk/provider'
|
||||
import { EmbeddingModelV2, ImageModelV2, LanguageModelV2, ProviderV2 } from '@ai-sdk/provider'
|
||||
import { createProviderRegistry, type ProviderRegistryProvider } from 'ai'
|
||||
|
||||
type PROVIDERS = Record<string, ProviderV2>
|
||||
|
||||
@@ -9,11 +9,8 @@ import { createDeepSeek } from '@ai-sdk/deepseek'
|
||||
import { createGoogleGenerativeAI } from '@ai-sdk/google'
|
||||
import { createOpenAI, type OpenAIProviderSettings } from '@ai-sdk/openai'
|
||||
import { createOpenAICompatible } from '@ai-sdk/openai-compatible'
|
||||
import type { LanguageModelV2 } from '@ai-sdk/provider'
|
||||
import { createXai } from '@ai-sdk/xai'
|
||||
import { createOpenRouter } from '@openrouter/ai-sdk-provider'
|
||||
import type { Provider } from 'ai'
|
||||
import { customProvider } from 'ai'
|
||||
import { customProvider, type Provider } from 'ai'
|
||||
import * as z from 'zod'
|
||||
|
||||
/**
|
||||
@@ -28,8 +25,7 @@ export const baseProviderIds = [
|
||||
'xai',
|
||||
'azure',
|
||||
'azure-responses',
|
||||
'deepseek',
|
||||
'openrouter'
|
||||
'deepseek'
|
||||
] as const
|
||||
|
||||
/**
|
||||
@@ -42,16 +38,14 @@ export const baseProviderIdSchema = z.enum(baseProviderIds)
|
||||
*/
|
||||
export type BaseProviderId = z.infer<typeof baseProviderIdSchema>
|
||||
|
||||
export const isBaseProvider = (id: ProviderId): id is BaseProviderId => {
|
||||
return baseProviderIdSchema.safeParse(id).success
|
||||
}
|
||||
export const baseProviderSchema = z.object({
|
||||
id: baseProviderIdSchema,
|
||||
name: z.string(),
|
||||
creator: z.function().args(z.any()).returns(z.any()) as z.ZodType<(options: any) => Provider>,
|
||||
supportsImageGeneration: z.boolean()
|
||||
})
|
||||
|
||||
type BaseProvider = {
|
||||
id: BaseProviderId
|
||||
name: string
|
||||
creator: (options: any) => Provider | LanguageModelV2
|
||||
supportsImageGeneration: boolean
|
||||
}
|
||||
export type BaseProvider = z.infer<typeof baseProviderSchema>
|
||||
|
||||
/**
|
||||
* 基础 Providers 定义
|
||||
@@ -127,12 +121,6 @@ export const baseProviders = [
|
||||
name: 'DeepSeek',
|
||||
creator: createDeepSeek,
|
||||
supportsImageGeneration: false
|
||||
},
|
||||
{
|
||||
id: 'openrouter',
|
||||
name: 'OpenRouter',
|
||||
creator: createOpenRouter,
|
||||
supportsImageGeneration: true
|
||||
}
|
||||
] as const satisfies BaseProvider[]
|
||||
|
||||
@@ -160,12 +148,7 @@ export const providerConfigSchema = z
|
||||
.object({
|
||||
id: customProviderIdSchema, // 只允许自定义ID
|
||||
name: z.string().min(1),
|
||||
creator: z
|
||||
.function({
|
||||
input: z.any(),
|
||||
output: z.any()
|
||||
})
|
||||
.optional(),
|
||||
creator: z.function().optional(),
|
||||
import: z.function().optional(),
|
||||
creatorFunctionName: z.string().optional(),
|
||||
supportsImageGeneration: z.boolean().default(false),
|
||||
|
||||
@@ -4,7 +4,7 @@ import { type DeepSeekProviderSettings } from '@ai-sdk/deepseek'
|
||||
import { type GoogleGenerativeAIProviderSettings } from '@ai-sdk/google'
|
||||
import { type OpenAIProviderSettings } from '@ai-sdk/openai'
|
||||
import { type OpenAICompatibleProviderSettings } from '@ai-sdk/openai-compatible'
|
||||
import type {
|
||||
import {
|
||||
EmbeddingModelV2 as EmbeddingModel,
|
||||
ImageModelV2 as ImageModel,
|
||||
LanguageModelV2 as LanguageModel,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ImageModelV2 } from '@ai-sdk/provider'
|
||||
import { ImageModelV2 } from '@ai-sdk/provider'
|
||||
import { experimental_generateImage as aiGenerateImage, NoImageGeneratedError } from 'ai'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
* 运行时执行器
|
||||
* 专注于插件化的AI调用处理
|
||||
*/
|
||||
import type { ImageModelV2, LanguageModelV2, LanguageModelV2Middleware } from '@ai-sdk/provider'
|
||||
import type { LanguageModel } from 'ai'
|
||||
import { ImageModelV2, LanguageModelV2, LanguageModelV2Middleware } from '@ai-sdk/provider'
|
||||
import {
|
||||
experimental_generateImage as _generateImage,
|
||||
generateObject as _generateObject,
|
||||
generateText as _generateText,
|
||||
streamObject as _streamObject,
|
||||
streamText as _streamText
|
||||
experimental_generateImage as generateImage,
|
||||
generateObject,
|
||||
generateText,
|
||||
LanguageModel,
|
||||
streamObject,
|
||||
streamText
|
||||
} from 'ai'
|
||||
|
||||
import { globalModelResolver } from '../models'
|
||||
@@ -18,14 +18,7 @@ import { type AiPlugin, type AiRequestContext, definePlugin } from '../plugins'
|
||||
import { type ProviderId } from '../providers'
|
||||
import { ImageGenerationError, ImageModelResolutionError } from './errors'
|
||||
import { PluginEngine } from './pluginEngine'
|
||||
import type {
|
||||
generateImageParams,
|
||||
generateObjectParams,
|
||||
generateTextParams,
|
||||
RuntimeConfig,
|
||||
streamObjectParams,
|
||||
streamTextParams
|
||||
} from './types'
|
||||
import { type RuntimeConfig } from './types'
|
||||
|
||||
export class RuntimeExecutor<T extends ProviderId = ProviderId> {
|
||||
public pluginEngine: PluginEngine<T>
|
||||
@@ -82,12 +75,12 @@ export class RuntimeExecutor<T extends ProviderId = ProviderId> {
|
||||
* 流式文本生成
|
||||
*/
|
||||
async streamText(
|
||||
params: streamTextParams,
|
||||
params: Parameters<typeof streamText>[0],
|
||||
options?: {
|
||||
middlewares?: LanguageModelV2Middleware[]
|
||||
}
|
||||
): Promise<ReturnType<typeof _streamText>> {
|
||||
const { model } = params
|
||||
): Promise<ReturnType<typeof streamText>> {
|
||||
const { model, ...restParams } = params
|
||||
|
||||
// 根据 model 类型决定插件配置
|
||||
if (typeof model === 'string') {
|
||||
@@ -101,16 +94,19 @@ export class RuntimeExecutor<T extends ProviderId = ProviderId> {
|
||||
|
||||
return this.pluginEngine.executeStreamWithPlugins(
|
||||
'streamText',
|
||||
params,
|
||||
(resolvedModel, transformedParams, streamTransforms) => {
|
||||
model,
|
||||
restParams,
|
||||
async (resolvedModel, transformedParams, streamTransforms) => {
|
||||
const experimental_transform =
|
||||
params?.experimental_transform ?? (streamTransforms.length > 0 ? streamTransforms : undefined)
|
||||
|
||||
return _streamText({
|
||||
...transformedParams,
|
||||
const finalParams = {
|
||||
model: resolvedModel,
|
||||
...transformedParams,
|
||||
experimental_transform
|
||||
})
|
||||
} as Parameters<typeof streamText>[0]
|
||||
|
||||
return await streamText(finalParams)
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -121,12 +117,12 @@ export class RuntimeExecutor<T extends ProviderId = ProviderId> {
|
||||
* 生成文本
|
||||
*/
|
||||
async generateText(
|
||||
params: generateTextParams,
|
||||
params: Parameters<typeof generateText>[0],
|
||||
options?: {
|
||||
middlewares?: LanguageModelV2Middleware[]
|
||||
}
|
||||
): Promise<ReturnType<typeof _generateText>> {
|
||||
const { model } = params
|
||||
): Promise<ReturnType<typeof generateText>> {
|
||||
const { model, ...restParams } = params
|
||||
|
||||
// 根据 model 类型决定插件配置
|
||||
if (typeof model === 'string') {
|
||||
@@ -138,10 +134,12 @@ export class RuntimeExecutor<T extends ProviderId = ProviderId> {
|
||||
this.pluginEngine.usePlugins([this.createConfigureContextPlugin()])
|
||||
}
|
||||
|
||||
return this.pluginEngine.executeWithPlugins<Parameters<typeof _generateText>[0], ReturnType<typeof _generateText>>(
|
||||
return this.pluginEngine.executeWithPlugins(
|
||||
'generateText',
|
||||
params,
|
||||
(resolvedModel, transformedParams) => _generateText({ ...transformedParams, model: resolvedModel })
|
||||
model,
|
||||
restParams,
|
||||
async (resolvedModel, transformedParams) =>
|
||||
generateText({ model: resolvedModel, ...transformedParams } as Parameters<typeof generateText>[0])
|
||||
)
|
||||
}
|
||||
|
||||
@@ -149,12 +147,12 @@ export class RuntimeExecutor<T extends ProviderId = ProviderId> {
|
||||
* 生成结构化对象
|
||||
*/
|
||||
async generateObject(
|
||||
params: generateObjectParams,
|
||||
params: Parameters<typeof generateObject>[0],
|
||||
options?: {
|
||||
middlewares?: LanguageModelV2Middleware[]
|
||||
}
|
||||
): Promise<ReturnType<typeof _generateObject>> {
|
||||
const { model } = params
|
||||
): Promise<ReturnType<typeof generateObject>> {
|
||||
const { model, ...restParams } = params
|
||||
|
||||
// 根据 model 类型决定插件配置
|
||||
if (typeof model === 'string') {
|
||||
@@ -166,23 +164,25 @@ export class RuntimeExecutor<T extends ProviderId = ProviderId> {
|
||||
this.pluginEngine.usePlugins([this.createConfigureContextPlugin()])
|
||||
}
|
||||
|
||||
return this.pluginEngine.executeWithPlugins<generateObjectParams, ReturnType<typeof _generateObject>>(
|
||||
return this.pluginEngine.executeWithPlugins(
|
||||
'generateObject',
|
||||
params,
|
||||
async (resolvedModel, transformedParams) => _generateObject({ ...transformedParams, model: resolvedModel })
|
||||
model,
|
||||
restParams,
|
||||
async (resolvedModel, transformedParams) =>
|
||||
generateObject({ model: resolvedModel, ...transformedParams } as Parameters<typeof generateObject>[0])
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 流式生成结构化对象
|
||||
*/
|
||||
streamObject(
|
||||
params: streamObjectParams,
|
||||
async streamObject(
|
||||
params: Parameters<typeof streamObject>[0],
|
||||
options?: {
|
||||
middlewares?: LanguageModelV2Middleware[]
|
||||
}
|
||||
): Promise<ReturnType<typeof _streamObject>> {
|
||||
const { model } = params
|
||||
): Promise<ReturnType<typeof streamObject>> {
|
||||
const { model, ...restParams } = params
|
||||
|
||||
// 根据 model 类型决定插件配置
|
||||
if (typeof model === 'string') {
|
||||
@@ -194,17 +194,23 @@ export class RuntimeExecutor<T extends ProviderId = ProviderId> {
|
||||
this.pluginEngine.usePlugins([this.createConfigureContextPlugin()])
|
||||
}
|
||||
|
||||
return this.pluginEngine.executeStreamWithPlugins('streamObject', params, (resolvedModel, transformedParams) =>
|
||||
_streamObject({ ...transformedParams, model: resolvedModel })
|
||||
return this.pluginEngine.executeWithPlugins(
|
||||
'streamObject',
|
||||
model,
|
||||
restParams,
|
||||
async (resolvedModel, transformedParams) =>
|
||||
streamObject({ model: resolvedModel, ...transformedParams } as Parameters<typeof streamObject>[0])
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成图像
|
||||
*/
|
||||
generateImage(params: generateImageParams): Promise<ReturnType<typeof _generateImage>> {
|
||||
async generateImage(
|
||||
params: Omit<Parameters<typeof generateImage>[0], 'model'> & { model: string | ImageModelV2 }
|
||||
): Promise<ReturnType<typeof generateImage>> {
|
||||
try {
|
||||
const { model } = params
|
||||
const { model, ...restParams } = params
|
||||
|
||||
// 根据 model 类型决定插件配置
|
||||
if (typeof model === 'string') {
|
||||
@@ -213,8 +219,13 @@ export class RuntimeExecutor<T extends ProviderId = ProviderId> {
|
||||
this.pluginEngine.usePlugins([this.createConfigureContextPlugin()])
|
||||
}
|
||||
|
||||
return this.pluginEngine.executeImageWithPlugins('generateImage', params, (resolvedModel, transformedParams) =>
|
||||
_generateImage({ ...transformedParams, model: resolvedModel })
|
||||
return await this.pluginEngine.executeImageWithPlugins(
|
||||
'generateImage',
|
||||
model,
|
||||
restParams,
|
||||
async (resolvedModel, transformedParams) => {
|
||||
return await generateImage({ model: resolvedModel, ...transformedParams })
|
||||
}
|
||||
)
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
|
||||
@@ -11,7 +11,7 @@ export type { RuntimeConfig } from './types'
|
||||
|
||||
// === 便捷工厂函数 ===
|
||||
|
||||
import type { LanguageModelV2Middleware } from '@ai-sdk/provider'
|
||||
import { LanguageModelV2Middleware } from '@ai-sdk/provider'
|
||||
|
||||
import { type AiPlugin } from '../plugins'
|
||||
import { type ProviderId, type ProviderSettingsMap } from '../providers/types'
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
/* eslint-disable @eslint-react/naming-convention/context-name */
|
||||
import type { ImageModelV2 } from '@ai-sdk/provider'
|
||||
import type {
|
||||
experimental_generateImage,
|
||||
generateObject,
|
||||
generateText,
|
||||
LanguageModel,
|
||||
streamObject,
|
||||
streamText
|
||||
} from 'ai'
|
||||
import { ImageModelV2 } from '@ai-sdk/provider'
|
||||
import { LanguageModel } from 'ai'
|
||||
|
||||
import { type AiPlugin, createContext, PluginManager } from '../plugins'
|
||||
import { type ProviderId } from '../providers/types'
|
||||
@@ -69,19 +62,17 @@ export class PluginEngine<T extends ProviderId = ProviderId> {
|
||||
* 执行带插件的操作(非流式)
|
||||
* 提供给AiExecutor使用
|
||||
*/
|
||||
async executeWithPlugins<
|
||||
TParams extends Parameters<typeof generateText | typeof generateObject>[0],
|
||||
TResult extends ReturnType<typeof generateText | typeof generateObject>
|
||||
>(
|
||||
async executeWithPlugins<TParams, TResult>(
|
||||
methodName: string,
|
||||
model: LanguageModel,
|
||||
params: TParams,
|
||||
executor: (model: LanguageModel, transformedParams: TParams) => TResult,
|
||||
executor: (model: LanguageModel, transformedParams: TParams) => Promise<TResult>,
|
||||
_context?: ReturnType<typeof createContext>
|
||||
): Promise<TResult> {
|
||||
// 统一处理模型解析
|
||||
let resolvedModel: LanguageModel | undefined
|
||||
let modelId: string
|
||||
const { model } = params
|
||||
|
||||
if (typeof model === 'string') {
|
||||
// 字符串:需要通过插件解析
|
||||
modelId = model
|
||||
@@ -92,13 +83,13 @@ export class PluginEngine<T extends ProviderId = ProviderId> {
|
||||
}
|
||||
|
||||
// 使用正确的createContext创建请求上下文
|
||||
const context = _context ? _context : createContext(this.providerId, model, params)
|
||||
const context = _context ? _context : createContext(this.providerId, modelId, params)
|
||||
|
||||
// 🔥 为上下文添加递归调用能力
|
||||
context.recursiveCall = async (newParams: any): Promise<TResult> => {
|
||||
// 递归调用自身,重新走完整的插件流程
|
||||
context.isRecursiveCall = true
|
||||
const result = await this.executeWithPlugins(methodName, newParams, executor, context)
|
||||
const result = await this.executeWithPlugins(methodName, model, newParams, executor, context)
|
||||
context.isRecursiveCall = false
|
||||
return result
|
||||
}
|
||||
@@ -147,19 +138,17 @@ export class PluginEngine<T extends ProviderId = ProviderId> {
|
||||
* 执行带插件的图像生成操作
|
||||
* 提供给AiExecutor使用
|
||||
*/
|
||||
async executeImageWithPlugins<
|
||||
TParams extends Omit<Parameters<typeof experimental_generateImage>[0], 'model'> & { model: string | ImageModelV2 },
|
||||
TResult extends ReturnType<typeof experimental_generateImage>
|
||||
>(
|
||||
async executeImageWithPlugins<TParams, TResult>(
|
||||
methodName: string,
|
||||
model: ImageModelV2 | string,
|
||||
params: TParams,
|
||||
executor: (model: ImageModelV2, transformedParams: TParams) => TResult,
|
||||
executor: (model: ImageModelV2, transformedParams: TParams) => Promise<TResult>,
|
||||
_context?: ReturnType<typeof createContext>
|
||||
): Promise<TResult> {
|
||||
// 统一处理模型解析
|
||||
let resolvedModel: ImageModelV2 | undefined
|
||||
let modelId: string
|
||||
const { model } = params
|
||||
|
||||
if (typeof model === 'string') {
|
||||
// 字符串:需要通过插件解析
|
||||
modelId = model
|
||||
@@ -170,13 +159,13 @@ export class PluginEngine<T extends ProviderId = ProviderId> {
|
||||
}
|
||||
|
||||
// 使用正确的createContext创建请求上下文
|
||||
const context = _context ? _context : createContext(this.providerId, model, params)
|
||||
const context = _context ? _context : createContext(this.providerId, modelId, params)
|
||||
|
||||
// 🔥 为上下文添加递归调用能力
|
||||
context.recursiveCall = async (newParams: any): Promise<TResult> => {
|
||||
// 递归调用自身,重新走完整的插件流程
|
||||
context.isRecursiveCall = true
|
||||
const result = await this.executeImageWithPlugins(methodName, newParams, executor, context)
|
||||
const result = await this.executeImageWithPlugins(methodName, model, newParams, executor, context)
|
||||
context.isRecursiveCall = false
|
||||
return result
|
||||
}
|
||||
@@ -225,19 +214,17 @@ export class PluginEngine<T extends ProviderId = ProviderId> {
|
||||
* 执行流式调用的通用逻辑(支持流转换器)
|
||||
* 提供给AiExecutor使用
|
||||
*/
|
||||
async executeStreamWithPlugins<
|
||||
TParams extends Parameters<typeof streamText | typeof streamObject>[0],
|
||||
TResult extends ReturnType<typeof streamText | typeof streamObject>
|
||||
>(
|
||||
async executeStreamWithPlugins<TParams, TResult>(
|
||||
methodName: string,
|
||||
model: LanguageModel,
|
||||
params: TParams,
|
||||
executor: (model: LanguageModel, transformedParams: TParams, streamTransforms: any[]) => TResult,
|
||||
executor: (model: LanguageModel, transformedParams: TParams, streamTransforms: any[]) => Promise<TResult>,
|
||||
_context?: ReturnType<typeof createContext>
|
||||
): Promise<TResult> {
|
||||
// 统一处理模型解析
|
||||
let resolvedModel: LanguageModel | undefined
|
||||
let modelId: string
|
||||
const { model } = params
|
||||
|
||||
if (typeof model === 'string') {
|
||||
// 字符串:需要通过插件解析
|
||||
modelId = model
|
||||
@@ -248,13 +235,13 @@ export class PluginEngine<T extends ProviderId = ProviderId> {
|
||||
}
|
||||
|
||||
// 创建请求上下文
|
||||
const context = _context ? _context : createContext(this.providerId, model, params)
|
||||
const context = _context ? _context : createContext(this.providerId, modelId, params)
|
||||
|
||||
// 🔥 为上下文添加递归调用能力
|
||||
context.recursiveCall = async (newParams: any): Promise<TResult> => {
|
||||
// 递归调用自身,重新走完整的插件流程
|
||||
context.isRecursiveCall = true
|
||||
const result = await this.executeStreamWithPlugins(methodName, newParams, executor, context)
|
||||
const result = await this.executeStreamWithPlugins(methodName, model, newParams, executor, context)
|
||||
context.isRecursiveCall = false
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
/**
|
||||
* Runtime 层类型定义
|
||||
*/
|
||||
import type { ImageModelV2 } from '@ai-sdk/provider'
|
||||
import type { experimental_generateImage, generateObject, generateText, streamObject, streamText } from 'ai'
|
||||
|
||||
import { type ModelConfig } from '../models/types'
|
||||
import { type AiPlugin } from '../plugins'
|
||||
import { type ProviderId } from '../providers/types'
|
||||
@@ -16,11 +13,3 @@ export interface RuntimeConfig<T extends ProviderId = ProviderId> {
|
||||
providerSettings: ModelConfig<T>['providerSettings'] & { mode?: 'chat' | 'responses' }
|
||||
plugins?: AiPlugin[]
|
||||
}
|
||||
|
||||
export type generateImageParams = Omit<Parameters<typeof experimental_generateImage>[0], 'model'> & {
|
||||
model: string | ImageModelV2
|
||||
}
|
||||
export type generateObjectParams = Parameters<typeof generateObject>[0]
|
||||
export type generateTextParams = Parameters<typeof generateText>[0]
|
||||
export type streamObjectParams = Parameters<typeof streamObject>[0]
|
||||
export type streamTextParams = Parameters<typeof streamText>[0]
|
||||
|
||||
@@ -1,21 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"declaration": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"esModuleInterop": true,
|
||||
"experimentalDecorators": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"target": "ES2020",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"noEmitOnError": false,
|
||||
"declaration": true,
|
||||
"outDir": "./dist",
|
||||
"resolveJsonModule": true,
|
||||
"rootDir": "./src",
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"target": "ES2020"
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"noEmitOnError": false,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true
|
||||
},
|
||||
"exclude": ["node_modules", "dist"],
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
"include": [
|
||||
"src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
@@ -67,13 +67,13 @@
|
||||
"dist"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "2.2.4",
|
||||
"@tiptap/core": "^3.2.0",
|
||||
"@tiptap/pm": "^3.2.0",
|
||||
"eslint": "^9.22.0",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"eslint-plugin-simple-import-sort": "^12.1.1",
|
||||
"eslint-plugin-unused-imports": "^4.1.4",
|
||||
"prettier": "^3.5.3",
|
||||
"tsdown": "^0.13.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
@@ -87,7 +87,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsdown",
|
||||
"lint": "biome format ./src/ --write && eslint --fix ./src/"
|
||||
"lint": "prettier ./src/ --write && eslint --fix ./src/"
|
||||
},
|
||||
"packageManager": "yarn@4.9.1"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { Node } from '@tiptap/core'
|
||||
import { Extension } from '@tiptap/core'
|
||||
import { Extension, Node } from '@tiptap/core'
|
||||
|
||||
import type { TableCellOptions } from '../cell/index.js'
|
||||
import { TableCell } from '../cell/index.js'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { SpanKind, SpanStatusCode } from '@opentelemetry/api'
|
||||
import type { ReadableSpan } from '@opentelemetry/sdk-trace-base'
|
||||
import { ReadableSpan } from '@opentelemetry/sdk-trace-base'
|
||||
|
||||
import type { SpanEntity } from '../types/config'
|
||||
import { SpanEntity } from '../types/config'
|
||||
|
||||
/**
|
||||
* convert ReadableSpan to SpanEntity
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ReadableSpan } from '@opentelemetry/sdk-trace-base'
|
||||
import { ReadableSpan } from '@opentelemetry/sdk-trace-base'
|
||||
|
||||
export interface TraceCache {
|
||||
createSpan: (span: ReadableSpan) => void
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { ExportResult } from '@opentelemetry/core'
|
||||
import { ExportResultCode } from '@opentelemetry/core'
|
||||
import type { ReadableSpan, SpanExporter } from '@opentelemetry/sdk-trace-base'
|
||||
import { ExportResult, ExportResultCode } from '@opentelemetry/core'
|
||||
import { ReadableSpan, SpanExporter } from '@opentelemetry/sdk-trace-base'
|
||||
|
||||
export type SaveFunction = (spans: ReadableSpan[]) => Promise<void>
|
||||
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import type { Context } from '@opentelemetry/api'
|
||||
import { trace } from '@opentelemetry/api'
|
||||
import type { BufferConfig, ReadableSpan, Span, SpanExporter } from '@opentelemetry/sdk-trace-base'
|
||||
import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base'
|
||||
import { Context, trace } from '@opentelemetry/api'
|
||||
import { BatchSpanProcessor, BufferConfig, ReadableSpan, Span, SpanExporter } from '@opentelemetry/sdk-trace-base'
|
||||
|
||||
import type { TraceCache } from '../core/traceCache'
|
||||
import { TraceCache } from '../core/traceCache'
|
||||
|
||||
export class CacheBatchSpanProcessor extends BatchSpanProcessor {
|
||||
private cache: TraceCache
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { Context } from '@opentelemetry/api'
|
||||
import type { BufferConfig, ReadableSpan, Span, SpanExporter } from '@opentelemetry/sdk-trace-base'
|
||||
import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base'
|
||||
import type { EventEmitter } from 'stream'
|
||||
import { Context } from '@opentelemetry/api'
|
||||
import { BatchSpanProcessor, BufferConfig, ReadableSpan, Span, SpanExporter } from '@opentelemetry/sdk-trace-base'
|
||||
import { EventEmitter } from 'stream'
|
||||
|
||||
import { convertSpanToSpanEntity } from '../core/spanConvert'
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import type { Context } from '@opentelemetry/api'
|
||||
import { trace } from '@opentelemetry/api'
|
||||
import type { BufferConfig, ReadableSpan, Span, SpanExporter } from '@opentelemetry/sdk-trace-base'
|
||||
import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base'
|
||||
import { Context, trace } from '@opentelemetry/api'
|
||||
import { BatchSpanProcessor, BufferConfig, ReadableSpan, Span, SpanExporter } from '@opentelemetry/sdk-trace-base'
|
||||
|
||||
export type SpanFunction = (span: ReadableSpan) => void
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Link } from '@opentelemetry/api'
|
||||
import type { TimedEvent } from '@opentelemetry/sdk-trace-base'
|
||||
import { Link } from '@opentelemetry/api'
|
||||
import { TimedEvent } from '@opentelemetry/sdk-trace-base'
|
||||
|
||||
export type AttributeValue =
|
||||
| string
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
import type { Tracer } from '@opentelemetry/api'
|
||||
import { trace } from '@opentelemetry/api'
|
||||
import { trace, Tracer } from '@opentelemetry/api'
|
||||
import { AsyncLocalStorageContextManager } from '@opentelemetry/context-async-hooks'
|
||||
import { W3CTraceContextPropagator } from '@opentelemetry/core'
|
||||
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
|
||||
import type { SpanProcessor } from '@opentelemetry/sdk-trace-base'
|
||||
import { BatchSpanProcessor, ConsoleSpanExporter } from '@opentelemetry/sdk-trace-base'
|
||||
import { BatchSpanProcessor, ConsoleSpanExporter, SpanProcessor } from '@opentelemetry/sdk-trace-base'
|
||||
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node'
|
||||
|
||||
import type { TraceConfig } from '../trace-core/types/config'
|
||||
import { defaultConfig } from '../trace-core/types/config'
|
||||
import { defaultConfig, TraceConfig } from '../trace-core/types/config'
|
||||
|
||||
export class NodeTracer {
|
||||
private static provider: NodeTracerProvider
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { Context, ContextManager } from '@opentelemetry/api'
|
||||
import { ROOT_CONTEXT } from '@opentelemetry/api'
|
||||
import { Context, ContextManager, ROOT_CONTEXT } from '@opentelemetry/api'
|
||||
|
||||
export class TopicContextManager implements ContextManager {
|
||||
private topicContextStack: Map<string, Context[]>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { Context } from '@opentelemetry/api'
|
||||
import { context } from '@opentelemetry/api'
|
||||
import { Context, context } from '@opentelemetry/api'
|
||||
|
||||
const originalPromise = globalThis.Promise
|
||||
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { W3CTraceContextPropagator } from '@opentelemetry/core'
|
||||
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
|
||||
import type { SpanProcessor } from '@opentelemetry/sdk-trace-base'
|
||||
import { BatchSpanProcessor, ConsoleSpanExporter } from '@opentelemetry/sdk-trace-base'
|
||||
import { BatchSpanProcessor, ConsoleSpanExporter, SpanProcessor } from '@opentelemetry/sdk-trace-base'
|
||||
import { WebTracerProvider } from '@opentelemetry/sdk-trace-web'
|
||||
|
||||
import type { TraceConfig } from '../trace-core/types/config'
|
||||
import { defaultConfig } from '../trace-core/types/config'
|
||||
import { defaultConfig, TraceConfig } from '../trace-core/types/config'
|
||||
import { TopicContextManager } from './TopicContextManager'
|
||||
|
||||
export const contextManager = new TopicContextManager()
|
||||
|
||||
@@ -2,19 +2,18 @@ export enum IpcChannel {
|
||||
App_GetCacheSize = 'app:get-cache-size',
|
||||
App_ClearCache = 'app:clear-cache',
|
||||
App_SetLaunchOnBoot = 'app:set-launch-on-boot',
|
||||
// App_SetLanguage = 'app:set-language',
|
||||
App_SetLanguage = 'app:set-language',
|
||||
App_SetEnableSpellCheck = 'app:set-enable-spell-check',
|
||||
App_SetSpellCheckLanguages = 'app:set-spell-check-languages',
|
||||
App_ShowUpdateDialog = 'app:show-update-dialog',
|
||||
App_CheckForUpdate = 'app:check-for-update',
|
||||
App_QuitAndInstall = 'app:quit-and-install',
|
||||
App_Reload = 'app:reload',
|
||||
App_Quit = 'app:quit',
|
||||
App_Info = 'app:info',
|
||||
App_Proxy = 'app:proxy',
|
||||
App_SetLaunchToTray = 'app:set-launch-to-tray',
|
||||
App_SetTray = 'app:set-tray',
|
||||
App_SetTrayOnClose = 'app:set-tray-on-close',
|
||||
// App_SetTheme = 'app:set-theme',
|
||||
App_SetTheme = 'app:set-theme',
|
||||
App_SetAutoUpdate = 'app:set-auto-update',
|
||||
App_SetTestPlan = 'app:set-test-plan',
|
||||
App_SetTestChannel = 'app:set-test-channel',
|
||||
@@ -34,26 +33,22 @@ export enum IpcChannel {
|
||||
App_GetBinaryPath = 'app:get-binary-path',
|
||||
App_InstallUvBinary = 'app:install-uv-binary',
|
||||
App_InstallBunBinary = 'app:install-bun-binary',
|
||||
App_InstallOvmsBinary = 'app:install-ovms-binary',
|
||||
App_LogToMain = 'app:log-to-main',
|
||||
App_SaveData = 'app:save-data',
|
||||
App_GetDiskInfo = 'app:get-disk-info',
|
||||
App_SetFullScreen = 'app:set-full-screen',
|
||||
App_IsFullScreen = 'app:is-full-screen',
|
||||
App_GetSystemFonts = 'app:get-system-fonts',
|
||||
|
||||
App_MacIsProcessTrusted = 'app:mac-is-process-trusted',
|
||||
App_MacRequestProcessTrust = 'app:mac-request-process-trust',
|
||||
|
||||
App_QuoteToMain = 'app:quote-to-main',
|
||||
// App_SetDisableHardwareAcceleration = 'app:set-disable-hardware-acceleration',
|
||||
App_SetDisableHardwareAcceleration = 'app:set-disable-hardware-acceleration',
|
||||
|
||||
Notification_Send = 'notification:send',
|
||||
Notification_OnClick = 'notification:on-click',
|
||||
|
||||
Webview_SetOpenLinkExternal = 'webview:set-open-link-external',
|
||||
Webview_SetSpellCheckEnabled = 'webview:set-spell-check-enabled',
|
||||
Webview_SearchHotkey = 'webview:search-hotkey',
|
||||
|
||||
// Open
|
||||
Open_Path = 'open:path',
|
||||
@@ -88,14 +83,10 @@ export enum IpcChannel {
|
||||
Mcp_UploadDxt = 'mcp:upload-dxt',
|
||||
Mcp_AbortTool = 'mcp:abort-tool',
|
||||
Mcp_GetServerVersion = 'mcp:get-server-version',
|
||||
Mcp_Progress = 'mcp:progress',
|
||||
|
||||
// Python
|
||||
Python_Execute = 'python:execute',
|
||||
|
||||
// agent messages
|
||||
AgentMessage_PersistExchange = 'agent-message:persist-exchange',
|
||||
AgentMessage_GetHistory = 'agent-message:get-history',
|
||||
|
||||
//copilot
|
||||
Copilot_GetAuthMessage = 'copilot:get-auth-message',
|
||||
Copilot_GetCopilotToken = 'copilot:get-copilot-token',
|
||||
@@ -132,12 +123,6 @@ export enum IpcChannel {
|
||||
Windows_SetMinimumSize = 'window:set-minimum-size',
|
||||
Windows_Resize = 'window:resize',
|
||||
Windows_GetSize = 'window:get-size',
|
||||
Windows_Minimize = 'window:minimize',
|
||||
Windows_Maximize = 'window:maximize',
|
||||
Windows_Unmaximize = 'window:unmaximize',
|
||||
Windows_Close = 'window:close',
|
||||
Windows_IsMaximized = 'window:is-maximized',
|
||||
Windows_MaximizedChanged = 'window:maximized-changed',
|
||||
|
||||
KnowledgeBase_Create = 'knowledge-base:create',
|
||||
KnowledgeBase_Reset = 'knowledge-base:reset',
|
||||
@@ -189,7 +174,6 @@ export enum IpcChannel {
|
||||
File_ValidateNotesDirectory = 'file:validateNotesDirectory',
|
||||
File_StartWatcher = 'file:startWatcher',
|
||||
File_StopWatcher = 'file:stopWatcher',
|
||||
File_ShowInFolder = 'file:showInFolder',
|
||||
|
||||
// file service
|
||||
FileService_Upload = 'file-service:upload',
|
||||
@@ -220,22 +204,6 @@ export enum IpcChannel {
|
||||
Backup_DeleteS3File = 'backup:deleteS3File',
|
||||
Backup_CheckS3Connection = 'backup:checkS3Connection',
|
||||
|
||||
// data migration
|
||||
DataMigrate_CheckNeeded = 'data-migrate:check-needed',
|
||||
DataMigrate_GetProgress = 'data-migrate:get-progress',
|
||||
DataMigrate_Cancel = 'data-migrate:cancel',
|
||||
DataMigrate_RequireBackup = 'data-migrate:require-backup',
|
||||
DataMigrate_BackupCompleted = 'data-migrate:backup-completed',
|
||||
DataMigrate_ShowBackupDialog = 'data-migrate:show-backup-dialog',
|
||||
DataMigrate_StartFlow = 'data-migrate:start-flow',
|
||||
DataMigrate_ProceedToBackup = 'data-migrate:proceed-to-backup',
|
||||
DataMigrate_StartMigration = 'data-migrate:start-migration',
|
||||
DataMigrate_RetryMigration = 'data-migrate:retry-migration',
|
||||
DataMigrate_RestartApp = 'data-migrate:restart-app',
|
||||
DataMigrate_CloseWindow = 'data-migrate:close-window',
|
||||
DataMigrate_SendReduxData = 'data-migrate:send-redux-data',
|
||||
DataMigrate_GetReduxData = 'data-migrate:get-redux-data',
|
||||
|
||||
// zip
|
||||
Zip_Compress = 'zip:compress',
|
||||
Zip_Decompress = 'zip:decompress',
|
||||
@@ -243,15 +211,14 @@ export enum IpcChannel {
|
||||
// system
|
||||
System_GetDeviceType = 'system:getDeviceType',
|
||||
System_GetHostname = 'system:getHostname',
|
||||
System_GetCpuName = 'system:getCpuName',
|
||||
|
||||
// DevTools
|
||||
System_ToggleDevTools = 'system:toggleDevTools',
|
||||
|
||||
// events
|
||||
BackupProgress = 'backup-progress',
|
||||
DataMigrateProgress = 'data-migrate-progress',
|
||||
NativeThemeUpdated = 'native-theme:updated',
|
||||
ThemeUpdated = 'theme:updated',
|
||||
UpdateDownloadedCancelled = 'update-downloaded-cancelled',
|
||||
RestoreProgress = 'restore-progress',
|
||||
UpdateError = 'update-error',
|
||||
UpdateAvailable = 'update-available',
|
||||
@@ -283,6 +250,7 @@ export enum IpcChannel {
|
||||
|
||||
// Provider
|
||||
Provider_AddKey = 'provider:add-key',
|
||||
Provider_GetClaudeCodePort = 'provider:get-claude-code-port',
|
||||
|
||||
//Selection Assistant
|
||||
Selection_TextSelected = 'selection:text-selected',
|
||||
@@ -290,6 +258,12 @@ export enum IpcChannel {
|
||||
Selection_ToolbarVisibilityChange = 'selection:toolbar-visibility-change',
|
||||
Selection_ToolbarDetermineSize = 'selection:toolbar-determine-size',
|
||||
Selection_WriteToClipboard = 'selection:write-to-clipboard',
|
||||
Selection_SetEnabled = 'selection:set-enabled',
|
||||
Selection_SetTriggerMode = 'selection:set-trigger-mode',
|
||||
Selection_SetFilterMode = 'selection:set-filter-mode',
|
||||
Selection_SetFilterList = 'selection:set-filter-list',
|
||||
Selection_SetFollowToolbar = 'selection:set-follow-toolbar',
|
||||
Selection_SetRemeberWinSize = 'selection:set-remeber-win-size',
|
||||
Selection_ActionWindowClose = 'selection:action-window-close',
|
||||
Selection_ActionWindowMinimize = 'selection:action-window-minimize',
|
||||
Selection_ActionWindowPin = 'selection:action-window-pin',
|
||||
@@ -308,27 +282,6 @@ export enum IpcChannel {
|
||||
Memory_DeleteAllMemoriesForUser = 'memory:delete-all-memories-for-user',
|
||||
Memory_GetUsersList = 'memory:get-users-list',
|
||||
|
||||
// Data: Preference
|
||||
Preference_Get = 'preference:get',
|
||||
Preference_Set = 'preference:set',
|
||||
Preference_GetMultiple = 'preference:get-multiple',
|
||||
Preference_SetMultiple = 'preference:set-multiple',
|
||||
Preference_GetAll = 'preference:get-all',
|
||||
Preference_Subscribe = 'preference:subscribe',
|
||||
Preference_Changed = 'preference:changed',
|
||||
|
||||
// Data: Cache
|
||||
Cache_Sync = 'cache:sync',
|
||||
Cache_SyncBatch = 'cache:sync-batch',
|
||||
|
||||
// Data: API Channels
|
||||
DataApi_Request = 'data-api:request',
|
||||
DataApi_Batch = 'data-api:batch',
|
||||
DataApi_Transaction = 'data-api:transaction',
|
||||
DataApi_Subscribe = 'data-api:subscribe',
|
||||
DataApi_Unsubscribe = 'data-api:unsubscribe',
|
||||
DataApi_Stream = 'data-api:stream',
|
||||
|
||||
// TRACE
|
||||
TRACE_SAVE_DATA = 'trace:saveData',
|
||||
TRACE_GET_DATA = 'trace:getData',
|
||||
@@ -344,40 +297,12 @@ export enum IpcChannel {
|
||||
TRACE_CLEAN_LOCAL_DATA = 'trace:cleanLocalData',
|
||||
TRACE_ADD_STREAM_MESSAGE = 'trace:addStreamMessage',
|
||||
|
||||
// API Server
|
||||
ApiServer_Start = 'api-server:start',
|
||||
ApiServer_Stop = 'api-server:stop',
|
||||
ApiServer_Restart = 'api-server:restart',
|
||||
ApiServer_GetStatus = 'api-server:get-status',
|
||||
ApiServer_GetConfig = 'api-server:get-config',
|
||||
|
||||
// Anthropic OAuth
|
||||
Anthropic_StartOAuthFlow = 'anthropic:start-oauth-flow',
|
||||
Anthropic_CompleteOAuthWithCode = 'anthropic:complete-oauth-with-code',
|
||||
Anthropic_CancelOAuthFlow = 'anthropic:cancel-oauth-flow',
|
||||
Anthropic_GetAccessToken = 'anthropic:get-access-token',
|
||||
Anthropic_HasCredentials = 'anthropic:has-credentials',
|
||||
Anthropic_ClearCredentials = 'anthropic:clear-credentials',
|
||||
|
||||
// CodeTools
|
||||
CodeTools_Run = 'code-tools:run',
|
||||
CodeTools_GetAvailableTerminals = 'code-tools:get-available-terminals',
|
||||
CodeTools_SetCustomTerminalPath = 'code-tools:set-custom-terminal-path',
|
||||
CodeTools_GetCustomTerminalPath = 'code-tools:get-custom-terminal-path',
|
||||
CodeTools_RemoveCustomTerminalPath = 'code-tools:remove-custom-terminal-path',
|
||||
|
||||
// OCR
|
||||
OCR_ocr = 'ocr:ocr',
|
||||
|
||||
// OVMS
|
||||
Ovms_AddModel = 'ovms:add-model',
|
||||
Ovms_StopAddModel = 'ovms:stop-addmodel',
|
||||
Ovms_GetModels = 'ovms:get-models',
|
||||
Ovms_IsRunning = 'ovms:is-running',
|
||||
Ovms_GetStatus = 'ovms:get-status',
|
||||
Ovms_RunOVMS = 'ovms:run-ovms',
|
||||
Ovms_StopOVMS = 'ovms:stop-ovms',
|
||||
|
||||
// CherryAI
|
||||
Cherryai_GetSignature = 'cherryai:get-signature'
|
||||
// Cherryin
|
||||
Cherryin_GetSignature = 'cherryin:get-signature'
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
import type { SDKMessage } from '@anthropic-ai/claude-agent-sdk'
|
||||
import type { ContentBlockParam } from '@anthropic-ai/sdk/resources/messages'
|
||||
|
||||
export type ClaudeCodeRawValue =
|
||||
| {
|
||||
type: string
|
||||
session_id: string
|
||||
slash_commands: string[]
|
||||
tools: string[]
|
||||
raw: Extract<SDKMessage, { type: 'system' }>
|
||||
}
|
||||
| ContentBlockParam
|
||||
@@ -1,170 +0,0 @@
|
||||
/**
|
||||
* @fileoverview Shared Anthropic AI client utilities for Cherry Studio
|
||||
*
|
||||
* This module provides functions for creating Anthropic SDK clients with different
|
||||
* authentication methods (OAuth, API key) and building Claude Code system messages.
|
||||
* It supports both standard Anthropic API and Anthropic Vertex AI endpoints.
|
||||
*
|
||||
* This shared module can be used by both main and renderer processes.
|
||||
*/
|
||||
|
||||
import Anthropic from '@anthropic-ai/sdk'
|
||||
import type { TextBlockParam } from '@anthropic-ai/sdk/resources'
|
||||
import { loggerService } from '@logger'
|
||||
import type { Provider } from '@types'
|
||||
import type { ModelMessage } from 'ai'
|
||||
|
||||
const logger = loggerService.withContext('anthropic-sdk')
|
||||
|
||||
const defaultClaudeCodeSystemPrompt = `You are Claude Code, Anthropic's official CLI for Claude.`
|
||||
|
||||
const defaultClaudeCodeSystem: Array<TextBlockParam> = [
|
||||
{
|
||||
type: 'text',
|
||||
text: defaultClaudeCodeSystemPrompt
|
||||
}
|
||||
]
|
||||
|
||||
/**
|
||||
* Creates and configures an Anthropic SDK client based on the provider configuration.
|
||||
*
|
||||
* This function supports two authentication methods:
|
||||
* 1. OAuth: Uses OAuth tokens passed as parameter
|
||||
* 2. API Key: Uses traditional API key authentication
|
||||
*
|
||||
* For OAuth authentication, it includes Claude Code specific headers and beta features.
|
||||
* For API key authentication, it uses the provider's configuration with custom headers.
|
||||
*
|
||||
* @param provider - The provider configuration containing authentication details
|
||||
* @param oauthToken - Optional OAuth token for OAuth authentication
|
||||
* @returns An initialized Anthropic or AnthropicVertex client
|
||||
* @throws Error when OAuth token is not available for OAuth authentication
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // OAuth authentication
|
||||
* const oauthProvider = { authType: 'oauth' };
|
||||
* const oauthClient = getSdkClient(oauthProvider, 'oauth-token-here');
|
||||
*
|
||||
* // API key authentication
|
||||
* const apiKeyProvider = {
|
||||
* authType: 'apikey',
|
||||
* apiKey: 'your-api-key',
|
||||
* apiHost: 'https://api.anthropic.com'
|
||||
* };
|
||||
* const apiKeyClient = getSdkClient(apiKeyProvider);
|
||||
* ```
|
||||
*/
|
||||
export function getSdkClient(
|
||||
provider: Provider,
|
||||
oauthToken?: string | null,
|
||||
extraHeaders?: Record<string, string | string[]>
|
||||
): Anthropic {
|
||||
if (provider.authType === 'oauth') {
|
||||
if (!oauthToken) {
|
||||
throw new Error('OAuth token is not available')
|
||||
}
|
||||
return new Anthropic({
|
||||
authToken: oauthToken,
|
||||
baseURL: 'https://api.anthropic.com',
|
||||
dangerouslyAllowBrowser: true,
|
||||
defaultHeaders: {
|
||||
'Content-Type': 'application/json',
|
||||
'anthropic-version': '2023-06-01',
|
||||
'anthropic-beta':
|
||||
'oauth-2025-04-20,claude-code-20250219,interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14',
|
||||
'anthropic-dangerous-direct-browser-access': 'true',
|
||||
'user-agent': 'claude-cli/1.0.118 (external, sdk-ts)',
|
||||
'x-app': 'cli',
|
||||
'x-stainless-retry-count': '0',
|
||||
'x-stainless-timeout': '600',
|
||||
'x-stainless-lang': 'js',
|
||||
'x-stainless-package-version': '0.60.0',
|
||||
'x-stainless-os': 'MacOS',
|
||||
'x-stainless-arch': 'arm64',
|
||||
'x-stainless-runtime': 'node',
|
||||
'x-stainless-runtime-version': 'v22.18.0',
|
||||
...extraHeaders
|
||||
}
|
||||
})
|
||||
}
|
||||
const baseURL =
|
||||
provider.type === 'anthropic'
|
||||
? provider.apiHost
|
||||
: (provider.anthropicApiHost && provider.anthropicApiHost.trim()) || provider.apiHost
|
||||
|
||||
logger.debug('Anthropic API baseURL', { baseURL, providerId: provider.id })
|
||||
|
||||
if (provider.id === 'aihubmix') {
|
||||
return new Anthropic({
|
||||
apiKey: provider.apiKey,
|
||||
baseURL,
|
||||
dangerouslyAllowBrowser: true,
|
||||
defaultHeaders: {
|
||||
'anthropic-beta': 'output-128k-2025-02-19',
|
||||
'APP-Code': 'MLTG2087',
|
||||
...provider.extra_headers,
|
||||
...extraHeaders
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return new Anthropic({
|
||||
apiKey: provider.apiKey,
|
||||
authToken: provider.apiKey,
|
||||
baseURL,
|
||||
dangerouslyAllowBrowser: true,
|
||||
defaultHeaders: {
|
||||
'anthropic-beta': 'output-128k-2025-02-19',
|
||||
...provider.extra_headers
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and prepends the Claude Code system message to user-provided system messages.
|
||||
*
|
||||
* This function ensures that all interactions with Claude include the official Claude Code
|
||||
* system prompt, which identifies the assistant as "Claude Code, Anthropic's official CLI for Claude."
|
||||
*
|
||||
* The function handles three cases:
|
||||
* 1. No system message provided: Returns only the default Claude Code system message
|
||||
* 2. String system message: Converts to array format and prepends Claude Code message
|
||||
* 3. Array system message: Checks if Claude Code message exists and prepends if missing
|
||||
*
|
||||
* @param system - Optional user-provided system message (string or TextBlockParam array)
|
||||
* @returns Combined system message with Claude Code prompt prepended
|
||||
*
|
||||
* ```
|
||||
*/
|
||||
export function buildClaudeCodeSystemMessage(system?: string | Array<TextBlockParam>): Array<TextBlockParam> {
|
||||
if (!system) {
|
||||
return defaultClaudeCodeSystem
|
||||
}
|
||||
|
||||
if (typeof system === 'string') {
|
||||
if (system.trim() === defaultClaudeCodeSystemPrompt || system.trim() === '') {
|
||||
return defaultClaudeCodeSystem
|
||||
} else {
|
||||
return [...defaultClaudeCodeSystem, { type: 'text', text: system }]
|
||||
}
|
||||
}
|
||||
if (Array.isArray(system)) {
|
||||
const firstSystem = system[0]
|
||||
if (firstSystem.type === 'text' && firstSystem.text.trim() === defaultClaudeCodeSystemPrompt) {
|
||||
return system
|
||||
} else {
|
||||
return [...defaultClaudeCodeSystem, ...system]
|
||||
}
|
||||
}
|
||||
|
||||
return defaultClaudeCodeSystem
|
||||
}
|
||||
|
||||
export function buildClaudeCodeSystemModelMessage(system?: string | Array<TextBlockParam>): Array<ModelMessage> {
|
||||
const textBlocks = buildClaudeCodeSystemMessage(system)
|
||||
return textBlocks.map((block) => ({
|
||||
role: 'system',
|
||||
content: block.text
|
||||
}))
|
||||
}
|
||||
@@ -197,11 +197,11 @@ export enum FeedUrl {
|
||||
GITHUB_LATEST = 'https://github.com/CherryHQ/cherry-studio/releases/latest/download'
|
||||
}
|
||||
|
||||
// export enum UpgradeChannel {
|
||||
// LATEST = 'latest', // 最新稳定版本
|
||||
// RC = 'rc', // 公测版本
|
||||
// BETA = 'beta' // 预览版本
|
||||
// }
|
||||
export enum UpgradeChannel {
|
||||
LATEST = 'latest', // 最新稳定版本
|
||||
RC = 'rc', // 公测版本
|
||||
BETA = 'beta' // 预览版本
|
||||
}
|
||||
|
||||
export const defaultTimeout = 10 * 1000 * 60
|
||||
|
||||
@@ -216,257 +216,5 @@ export enum codeTools {
|
||||
qwenCode = 'qwen-code',
|
||||
claudeCode = 'claude-code',
|
||||
geminiCli = 'gemini-cli',
|
||||
openaiCodex = 'openai-codex',
|
||||
iFlowCli = 'iflow-cli',
|
||||
githubCopilotCli = 'github-copilot-cli'
|
||||
openaiCodex = 'openai-codex'
|
||||
}
|
||||
|
||||
export enum terminalApps {
|
||||
systemDefault = 'Terminal',
|
||||
iterm2 = 'iTerm2',
|
||||
kitty = 'kitty',
|
||||
alacritty = 'Alacritty',
|
||||
wezterm = 'WezTerm',
|
||||
ghostty = 'Ghostty',
|
||||
tabby = 'Tabby',
|
||||
// Windows terminals
|
||||
windowsTerminal = 'WindowsTerminal',
|
||||
powershell = 'PowerShell',
|
||||
cmd = 'CMD',
|
||||
wsl = 'WSL'
|
||||
}
|
||||
|
||||
export interface TerminalConfig {
|
||||
id: string
|
||||
name: string
|
||||
bundleId?: string
|
||||
customPath?: string // For user-configured terminal paths on Windows
|
||||
}
|
||||
|
||||
export interface TerminalConfigWithCommand extends TerminalConfig {
|
||||
command: (directory: string, fullCommand: string) => { command: string; args: string[] }
|
||||
}
|
||||
|
||||
export const MACOS_TERMINALS: TerminalConfig[] = [
|
||||
{
|
||||
id: terminalApps.systemDefault,
|
||||
name: 'Terminal',
|
||||
bundleId: 'com.apple.Terminal'
|
||||
},
|
||||
{
|
||||
id: terminalApps.iterm2,
|
||||
name: 'iTerm2',
|
||||
bundleId: 'com.googlecode.iterm2'
|
||||
},
|
||||
{
|
||||
id: terminalApps.kitty,
|
||||
name: 'kitty',
|
||||
bundleId: 'net.kovidgoyal.kitty'
|
||||
},
|
||||
{
|
||||
id: terminalApps.alacritty,
|
||||
name: 'Alacritty',
|
||||
bundleId: 'org.alacritty'
|
||||
},
|
||||
{
|
||||
id: terminalApps.wezterm,
|
||||
name: 'WezTerm',
|
||||
bundleId: 'com.github.wez.wezterm'
|
||||
},
|
||||
{
|
||||
id: terminalApps.ghostty,
|
||||
name: 'Ghostty',
|
||||
bundleId: 'com.mitchellh.ghostty'
|
||||
},
|
||||
{
|
||||
id: terminalApps.tabby,
|
||||
name: 'Tabby',
|
||||
bundleId: 'org.tabby'
|
||||
}
|
||||
]
|
||||
|
||||
export const WINDOWS_TERMINALS: TerminalConfig[] = [
|
||||
{
|
||||
id: terminalApps.cmd,
|
||||
name: 'Command Prompt'
|
||||
},
|
||||
{
|
||||
id: terminalApps.powershell,
|
||||
name: 'PowerShell'
|
||||
},
|
||||
{
|
||||
id: terminalApps.windowsTerminal,
|
||||
name: 'Windows Terminal'
|
||||
},
|
||||
{
|
||||
id: terminalApps.wsl,
|
||||
name: 'WSL (Ubuntu/Debian)'
|
||||
},
|
||||
{
|
||||
id: terminalApps.alacritty,
|
||||
name: 'Alacritty'
|
||||
},
|
||||
{
|
||||
id: terminalApps.wezterm,
|
||||
name: 'WezTerm'
|
||||
}
|
||||
]
|
||||
|
||||
export const WINDOWS_TERMINALS_WITH_COMMANDS: TerminalConfigWithCommand[] = [
|
||||
{
|
||||
id: terminalApps.cmd,
|
||||
name: 'Command Prompt',
|
||||
command: (_: string, fullCommand: string) => ({
|
||||
command: 'cmd',
|
||||
args: ['/c', 'start', 'cmd', '/k', fullCommand]
|
||||
})
|
||||
},
|
||||
{
|
||||
id: terminalApps.powershell,
|
||||
name: 'PowerShell',
|
||||
command: (_: string, fullCommand: string) => ({
|
||||
command: 'cmd',
|
||||
args: ['/c', 'start', 'powershell', '-NoExit', '-Command', `& '${fullCommand}'`]
|
||||
})
|
||||
},
|
||||
{
|
||||
id: terminalApps.windowsTerminal,
|
||||
name: 'Windows Terminal',
|
||||
command: (_: string, fullCommand: string) => ({
|
||||
command: 'wt',
|
||||
args: ['cmd', '/k', fullCommand]
|
||||
})
|
||||
},
|
||||
{
|
||||
id: terminalApps.wsl,
|
||||
name: 'WSL (Ubuntu/Debian)',
|
||||
command: (_: string, fullCommand: string) => {
|
||||
// Start WSL in a new window and execute the batch file from within WSL using cmd.exe
|
||||
// The batch file will run in Windows context but output will be in WSL terminal
|
||||
return {
|
||||
command: 'cmd',
|
||||
args: ['/c', 'start', 'wsl', '-e', 'bash', '-c', `cmd.exe /c '${fullCommand}' ; exec bash`]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
id: terminalApps.alacritty,
|
||||
name: 'Alacritty',
|
||||
customPath: '', // Will be set by user in settings
|
||||
command: (_: string, fullCommand: string) => ({
|
||||
command: 'alacritty', // Will be replaced with customPath if set
|
||||
args: ['-e', 'cmd', '/k', fullCommand]
|
||||
})
|
||||
},
|
||||
{
|
||||
id: terminalApps.wezterm,
|
||||
name: 'WezTerm',
|
||||
customPath: '', // Will be set by user in settings
|
||||
command: (_: string, fullCommand: string) => ({
|
||||
command: 'wezterm', // Will be replaced with customPath if set
|
||||
args: ['start', 'cmd', '/k', fullCommand]
|
||||
})
|
||||
}
|
||||
]
|
||||
|
||||
// Helper function to escape strings for AppleScript
|
||||
const escapeForAppleScript = (str: string): string => {
|
||||
// In AppleScript strings, backslashes and double quotes need to be escaped
|
||||
// When passed through osascript -e with single quotes, we need:
|
||||
// 1. Backslash: \ -> \\
|
||||
// 2. Double quote: " -> \"
|
||||
return str
|
||||
.replace(/\\/g, '\\\\') // Escape backslashes first
|
||||
.replace(/"/g, '\\"') // Then escape double quotes
|
||||
}
|
||||
|
||||
export const MACOS_TERMINALS_WITH_COMMANDS: TerminalConfigWithCommand[] = [
|
||||
{
|
||||
id: terminalApps.systemDefault,
|
||||
name: 'Terminal',
|
||||
bundleId: 'com.apple.Terminal',
|
||||
command: (_directory: string, fullCommand: string) => ({
|
||||
command: 'sh',
|
||||
args: [
|
||||
'-c',
|
||||
`open -na Terminal && sleep 0.5 && osascript -e 'tell application "Terminal" to activate' -e 'tell application "Terminal" to do script "${escapeForAppleScript(fullCommand)}" in front window'`
|
||||
]
|
||||
})
|
||||
},
|
||||
{
|
||||
id: terminalApps.iterm2,
|
||||
name: 'iTerm2',
|
||||
bundleId: 'com.googlecode.iterm2',
|
||||
command: (_directory: string, fullCommand: string) => ({
|
||||
command: 'sh',
|
||||
args: [
|
||||
'-c',
|
||||
`open -na iTerm && sleep 0.8 && osascript -e 'on waitUntilRunning()\n repeat 50 times\n tell application "System Events"\n if (exists process "iTerm2") then exit repeat\n end tell\n delay 0.1\n end repeat\nend waitUntilRunning\n\nwaitUntilRunning()\n\ntell application "iTerm2"\n if (count of windows) = 0 then\n create window with default profile\n delay 0.3\n else\n tell current window\n create tab with default profile\n end tell\n delay 0.3\n end if\n tell current session of current window to write text "${escapeForAppleScript(fullCommand)}"\n activate\nend tell'`
|
||||
]
|
||||
})
|
||||
},
|
||||
{
|
||||
id: terminalApps.kitty,
|
||||
name: 'kitty',
|
||||
bundleId: 'net.kovidgoyal.kitty',
|
||||
command: (_directory: string, fullCommand: string) => ({
|
||||
command: 'sh',
|
||||
args: [
|
||||
'-c',
|
||||
`cd "${_directory}" && open -na kitty --args --directory="${_directory}" sh -c "${fullCommand.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}; exec \\$SHELL" && sleep 0.5 && osascript -e 'tell application "kitty" to activate'`
|
||||
]
|
||||
})
|
||||
},
|
||||
{
|
||||
id: terminalApps.alacritty,
|
||||
name: 'Alacritty',
|
||||
bundleId: 'org.alacritty',
|
||||
command: (_directory: string, fullCommand: string) => ({
|
||||
command: 'sh',
|
||||
args: [
|
||||
'-c',
|
||||
`open -na Alacritty --args --working-directory "${_directory}" -e sh -c "${fullCommand.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}; exec \\$SHELL" && sleep 0.5 && osascript -e 'tell application "Alacritty" to activate'`
|
||||
]
|
||||
})
|
||||
},
|
||||
{
|
||||
id: terminalApps.wezterm,
|
||||
name: 'WezTerm',
|
||||
bundleId: 'com.github.wez.wezterm',
|
||||
command: (_directory: string, fullCommand: string) => ({
|
||||
command: 'sh',
|
||||
args: [
|
||||
'-c',
|
||||
`open -na WezTerm --args start --new-tab --cwd "${_directory}" -- sh -c "${fullCommand.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}; exec \\$SHELL" && sleep 0.5 && osascript -e 'tell application "WezTerm" to activate'`
|
||||
]
|
||||
})
|
||||
},
|
||||
{
|
||||
id: terminalApps.ghostty,
|
||||
name: 'Ghostty',
|
||||
bundleId: 'com.mitchellh.ghostty',
|
||||
command: (_directory: string, fullCommand: string) => ({
|
||||
command: 'sh',
|
||||
args: [
|
||||
'-c',
|
||||
`cd "${_directory}" && open -na Ghostty --args --working-directory="${_directory}" -e sh -c "${fullCommand.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}; exec \\$SHELL" && sleep 0.5 && osascript -e 'tell application "Ghostty" to activate'`
|
||||
]
|
||||
})
|
||||
},
|
||||
{
|
||||
id: terminalApps.tabby,
|
||||
name: 'Tabby',
|
||||
bundleId: 'org.tabby',
|
||||
command: (_directory: string, fullCommand: string) => ({
|
||||
command: 'sh',
|
||||
args: [
|
||||
'-c',
|
||||
`if pgrep -x "Tabby" > /dev/null; then
|
||||
open -na Tabby --args open && sleep 0.3
|
||||
else
|
||||
open -na Tabby --args open && sleep 2
|
||||
fi && osascript -e 'tell application "Tabby" to activate' -e 'set the clipboard to "${escapeForAppleScript(fullCommand)}"' -e 'tell application "System Events" to tell process "Tabby" to keystroke "v" using {command down}' -e 'tell application "System Events" to key code 36'`
|
||||
]
|
||||
})
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ProcessingStatus } from '@types'
|
||||
import { ProcessingStatus } from '@types'
|
||||
|
||||
export type LoaderReturn = {
|
||||
entriesAdded: number
|
||||
@@ -7,7 +7,7 @@ export type LoaderReturn = {
|
||||
loaderType: string
|
||||
status?: ProcessingStatus
|
||||
message?: string
|
||||
messageSource?: 'preprocess' | 'embedding' | 'validation'
|
||||
messageSource?: 'preprocess' | 'embedding'
|
||||
}
|
||||
|
||||
export type FileChangeEventType = 'add' | 'change' | 'unlink' | 'addDir' | 'unlinkDir'
|
||||
@@ -17,17 +17,3 @@ export type FileChangeEvent = {
|
||||
filePath: string
|
||||
watchPath: string
|
||||
}
|
||||
|
||||
export type MCPProgressEvent = {
|
||||
callId: string
|
||||
progress: number // 0-1 range
|
||||
}
|
||||
|
||||
export type WebviewKeyEvent = {
|
||||
webviewId: number
|
||||
key: string
|
||||
control: boolean
|
||||
meta: boolean
|
||||
shift: boolean
|
||||
alt: boolean
|
||||
}
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
# Cherry Studio Shared Data
|
||||
|
||||
This directory contains shared type definitions and schemas for the Cherry Studio data management systems. These files provide type safety and consistency across the entire application.
|
||||
|
||||
## 📁 Directory Structure
|
||||
|
||||
```
|
||||
packages/shared/data/
|
||||
├── api/ # Data API type system
|
||||
│ ├── index.ts # Barrel exports for clean imports
|
||||
│ ├── apiSchemas.ts # API endpoint definitions and mappings
|
||||
│ ├── apiTypes.ts # Core request/response infrastructure types
|
||||
│ ├── apiModels.ts # Business entity types and DTOs
|
||||
│ ├── apiPaths.ts # API path definitions and utilities
|
||||
│ └── errorCodes.ts # Standardized error handling
|
||||
├── cache/ # Cache system type definitions
|
||||
│ ├── cacheTypes.ts # Core cache infrastructure types
|
||||
│ ├── cacheSchemas.ts # Cache key schemas and type mappings
|
||||
│ └── cacheValueTypes.ts # Cache value type definitions
|
||||
├── preference/ # Preference system type definitions
|
||||
│ ├── preferenceTypes.ts # Core preference system types
|
||||
│ └── preferenceSchemas.ts # Preference schemas and default values
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
## 🏗️ System Overview
|
||||
|
||||
This directory provides type definitions for three main data management systems:
|
||||
|
||||
### API System (`api/`)
|
||||
- **Purpose**: Type-safe IPC communication between Main and Renderer processes
|
||||
- **Features**: RESTful patterns, error handling, business entity definitions
|
||||
- **Usage**: Ensures type safety for all data API operations
|
||||
|
||||
### Cache System (`cache/`)
|
||||
- **Purpose**: Type definitions for three-layer caching architecture
|
||||
- **Features**: Memory/shared/persist cache schemas, TTL support, hook integration
|
||||
- **Usage**: Type-safe caching operations across the application
|
||||
|
||||
### Preference System (`preference/`)
|
||||
- **Purpose**: User configuration and settings management
|
||||
- **Features**: 158 configuration items, default values, nested key support
|
||||
- **Usage**: Type-safe preference access and synchronization
|
||||
|
||||
## 📋 File Categories
|
||||
|
||||
**Framework Infrastructure** - These are TypeScript type definitions that:
|
||||
- ✅ Exist only at compile time
|
||||
- ✅ Provide type safety and IntelliSense support
|
||||
- ✅ Define contracts between application layers
|
||||
- ✅ Enable static analysis and error detection
|
||||
|
||||
## 📖 Usage Examples
|
||||
|
||||
### API Types
|
||||
```typescript
|
||||
// Import API types
|
||||
import type { DataRequest, DataResponse, ApiSchemas } from '@shared/data/api'
|
||||
```
|
||||
|
||||
### Cache Types
|
||||
```typescript
|
||||
// Import cache types
|
||||
import type { UseCacheKey, UseSharedCacheKey } from '@shared/data/cache'
|
||||
```
|
||||
|
||||
### Preference Types
|
||||
```typescript
|
||||
// Import preference types
|
||||
import type { PreferenceKeyType, PreferenceDefaultScopeType } from '@shared/data/preference'
|
||||
```
|
||||
|
||||
## 🔧 Development Guidelines
|
||||
|
||||
### Adding Cache Types
|
||||
1. Add cache key to `cache/cacheSchemas.ts`
|
||||
2. Define value type in `cache/cacheValueTypes.ts`
|
||||
3. Update type mappings for type safety
|
||||
|
||||
### Adding Preference Types
|
||||
1. Add preference key to `preference/preferenceSchemas.ts`
|
||||
2. Define default value and type
|
||||
3. Preference system automatically picks up new keys
|
||||
|
||||
### Adding API Types
|
||||
1. Define business entities in `api/apiModels.ts`
|
||||
2. Add endpoint definitions to `api/apiSchemas.ts`
|
||||
3. Export types from `api/index.ts`
|
||||
|
||||
### Best Practices
|
||||
- Use `import type` for type-only imports
|
||||
- Follow existing naming conventions
|
||||
- Document complex types with JSDoc
|
||||
- Maintain type safety across all imports
|
||||
|
||||
## 🔗 Related Implementation
|
||||
|
||||
### Main Process Services
|
||||
- `src/main/data/CacheService.ts` - Main process cache management
|
||||
- `src/main/data/PreferenceService.ts` - Preference management service
|
||||
- `src/main/data/DataApiService.ts` - Data API coordination service
|
||||
|
||||
### Renderer Process Services
|
||||
- `src/renderer/src/data/CacheService.ts` - Renderer cache service
|
||||
- `src/renderer/src/data/PreferenceService.ts` - Renderer preference service
|
||||
- `src/renderer/src/data/DataApiService.ts` - Renderer API client
|
||||
@@ -1,107 +0,0 @@
|
||||
/**
|
||||
* Generic test model definitions
|
||||
* Contains flexible types for comprehensive API testing
|
||||
*/
|
||||
|
||||
/**
|
||||
* Generic test item entity - flexible structure for testing various scenarios
|
||||
*/
|
||||
export interface TestItem {
|
||||
/** Unique identifier */
|
||||
id: string
|
||||
/** Item title */
|
||||
title: string
|
||||
/** Optional description */
|
||||
description?: string
|
||||
/** Type category */
|
||||
type: string
|
||||
/** Current status */
|
||||
status: string
|
||||
/** Priority level */
|
||||
priority: string
|
||||
/** Associated tags */
|
||||
tags: string[]
|
||||
/** Creation timestamp */
|
||||
createdAt: string
|
||||
/** Last update timestamp */
|
||||
updatedAt: string
|
||||
/** Additional metadata */
|
||||
metadata: Record<string, any>
|
||||
}
|
||||
|
||||
/**
|
||||
* Data Transfer Objects (DTOs) for test operations
|
||||
*/
|
||||
|
||||
/**
|
||||
* DTO for creating a new test item
|
||||
*/
|
||||
export interface CreateTestItemDto {
|
||||
/** Item title */
|
||||
title: string
|
||||
/** Optional description */
|
||||
description?: string
|
||||
/** Type category */
|
||||
type?: string
|
||||
/** Current status */
|
||||
status?: string
|
||||
/** Priority level */
|
||||
priority?: string
|
||||
/** Associated tags */
|
||||
tags?: string[]
|
||||
/** Additional metadata */
|
||||
metadata?: Record<string, any>
|
||||
}
|
||||
|
||||
/**
|
||||
* DTO for updating an existing test item
|
||||
*/
|
||||
export interface UpdateTestItemDto {
|
||||
/** Updated title */
|
||||
title?: string
|
||||
/** Updated description */
|
||||
description?: string
|
||||
/** Updated type */
|
||||
type?: string
|
||||
/** Updated status */
|
||||
status?: string
|
||||
/** Updated priority */
|
||||
priority?: string
|
||||
/** Updated tags */
|
||||
tags?: string[]
|
||||
/** Updated metadata */
|
||||
metadata?: Record<string, any>
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk operation types for batch processing
|
||||
*/
|
||||
|
||||
/**
|
||||
* Request for bulk operations on multiple items
|
||||
*/
|
||||
export interface BulkOperationRequest<TData = any> {
|
||||
/** Type of bulk operation to perform */
|
||||
operation: 'create' | 'update' | 'delete' | 'archive' | 'restore'
|
||||
/** Array of data items to process */
|
||||
data: TData[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Response from a bulk operation
|
||||
*/
|
||||
export interface BulkOperationResponse {
|
||||
/** Number of successfully processed items */
|
||||
successful: number
|
||||
/** Number of items that failed processing */
|
||||
failed: number
|
||||
/** Array of errors that occurred during processing */
|
||||
errors: Array<{
|
||||
/** Index of the item that failed */
|
||||
index: number
|
||||
/** Error message */
|
||||
error: string
|
||||
/** Optional additional error data */
|
||||
data?: any
|
||||
}>
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
import type { ApiSchemas } from './apiSchemas'
|
||||
|
||||
/**
|
||||
* Template literal type utilities for converting parameterized paths to concrete paths
|
||||
* This enables type-safe API calls with actual paths like '/test/items/123' instead of '/test/items/:id'
|
||||
*/
|
||||
|
||||
/**
|
||||
* Convert parameterized path templates to concrete path types
|
||||
* @example '/test/items/:id' -> '/test/items/${string}'
|
||||
* @example '/topics/:id/messages' -> '/topics/${string}/messages'
|
||||
*/
|
||||
export type ResolvedPath<T extends string> = T extends `${infer Prefix}/:${string}/${infer Suffix}`
|
||||
? `${Prefix}/${string}/${ResolvedPath<Suffix>}`
|
||||
: T extends `${infer Prefix}/:${string}`
|
||||
? `${Prefix}/${string}`
|
||||
: T
|
||||
|
||||
/**
|
||||
* Generate all possible concrete paths from ApiSchemas
|
||||
* This creates a union type of all valid API paths
|
||||
*/
|
||||
export type ConcreteApiPaths = {
|
||||
[K in keyof ApiSchemas]: ResolvedPath<K & string>
|
||||
}[keyof ApiSchemas]
|
||||
|
||||
/**
|
||||
* Reverse lookup: from concrete path back to original template path
|
||||
* Used to determine which ApiSchema entry matches a concrete path
|
||||
*/
|
||||
export type MatchApiPath<Path extends string> = {
|
||||
[K in keyof ApiSchemas]: Path extends ResolvedPath<K & string> ? K : never
|
||||
}[keyof ApiSchemas]
|
||||
|
||||
/**
|
||||
* Extract query parameters type for a given concrete path
|
||||
*/
|
||||
export type QueryParamsForPath<Path extends string> = MatchApiPath<Path> extends keyof ApiSchemas
|
||||
? ApiSchemas[MatchApiPath<Path>] extends { GET: { query?: infer Q } }
|
||||
? Q
|
||||
: Record<string, any>
|
||||
: Record<string, any>
|
||||
|
||||
/**
|
||||
* Extract request body type for a given concrete path and HTTP method
|
||||
*/
|
||||
export type BodyForPath<Path extends string, Method extends string> = MatchApiPath<Path> extends keyof ApiSchemas
|
||||
? ApiSchemas[MatchApiPath<Path>] extends { [M in Method]: { body: infer B } }
|
||||
? B
|
||||
: any
|
||||
: any
|
||||
|
||||
/**
|
||||
* Extract response type for a given concrete path and HTTP method
|
||||
*/
|
||||
export type ResponseForPath<Path extends string, Method extends string> = MatchApiPath<Path> extends keyof ApiSchemas
|
||||
? ApiSchemas[MatchApiPath<Path>] extends { [M in Method]: { response: infer R } }
|
||||
? R
|
||||
: any
|
||||
: any
|
||||
@@ -1,487 +0,0 @@
|
||||
// NOTE: Types are defined inline in the schema for simplicity
|
||||
// If needed, specific types can be imported from './apiModels'
|
||||
import type { BodyForPath, ConcreteApiPaths, QueryParamsForPath, ResponseForPath } from './apiPaths'
|
||||
import type { HttpMethod, PaginatedResponse, PaginationParams } from './apiTypes'
|
||||
|
||||
// Re-export for external use
|
||||
export type { ConcreteApiPaths } from './apiPaths'
|
||||
|
||||
/**
|
||||
* Complete API Schema definitions for Test API
|
||||
*
|
||||
* Each path defines the supported HTTP methods with their:
|
||||
* - Request parameters (params, query, body)
|
||||
* - Response types
|
||||
* - Type safety guarantees
|
||||
*
|
||||
* This schema serves as the contract between renderer and main processes,
|
||||
* enabling full TypeScript type checking across IPC boundaries.
|
||||
*/
|
||||
export interface ApiSchemas {
|
||||
/**
|
||||
* Test items collection endpoint
|
||||
* @example GET /test/items?page=1&limit=10&search=hello
|
||||
* @example POST /test/items { "title": "New Test Item" }
|
||||
*/
|
||||
'/test/items': {
|
||||
/** List all test items with optional filtering and pagination */
|
||||
GET: {
|
||||
query?: PaginationParams & {
|
||||
/** Search items by title or description */
|
||||
search?: string
|
||||
/** Filter by item type */
|
||||
type?: string
|
||||
/** Filter by status */
|
||||
status?: string
|
||||
}
|
||||
response: PaginatedResponse<any>
|
||||
}
|
||||
/** Create a new test item */
|
||||
POST: {
|
||||
body: {
|
||||
title: string
|
||||
description?: string
|
||||
type?: string
|
||||
status?: string
|
||||
priority?: string
|
||||
tags?: string[]
|
||||
metadata?: Record<string, any>
|
||||
}
|
||||
response: any
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Individual test item endpoint
|
||||
* @example GET /test/items/123
|
||||
* @example PUT /test/items/123 { "title": "Updated Title" }
|
||||
* @example DELETE /test/items/123
|
||||
*/
|
||||
'/test/items/:id': {
|
||||
/** Get a specific test item by ID */
|
||||
GET: {
|
||||
params: { id: string }
|
||||
response: any
|
||||
}
|
||||
/** Update a specific test item */
|
||||
PUT: {
|
||||
params: { id: string }
|
||||
body: {
|
||||
title?: string
|
||||
description?: string
|
||||
type?: string
|
||||
status?: string
|
||||
priority?: string
|
||||
tags?: string[]
|
||||
metadata?: Record<string, any>
|
||||
}
|
||||
response: any
|
||||
}
|
||||
/** Delete a specific test item */
|
||||
DELETE: {
|
||||
params: { id: string }
|
||||
response: void
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test search endpoint
|
||||
* @example GET /test/search?query=hello&page=1&limit=20
|
||||
*/
|
||||
'/test/search': {
|
||||
/** Search test items */
|
||||
GET: {
|
||||
query: {
|
||||
/** Search query string */
|
||||
query: string
|
||||
/** Page number for pagination */
|
||||
page?: number
|
||||
/** Number of results per page */
|
||||
limit?: number
|
||||
/** Additional filters */
|
||||
type?: string
|
||||
status?: string
|
||||
}
|
||||
response: PaginatedResponse<any>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test statistics endpoint
|
||||
* @example GET /test/stats
|
||||
*/
|
||||
'/test/stats': {
|
||||
/** Get comprehensive test statistics */
|
||||
GET: {
|
||||
response: {
|
||||
/** Total number of items */
|
||||
total: number
|
||||
/** Item count grouped by type */
|
||||
byType: Record<string, number>
|
||||
/** Item count grouped by status */
|
||||
byStatus: Record<string, number>
|
||||
/** Item count grouped by priority */
|
||||
byPriority: Record<string, number>
|
||||
/** Recent activity timeline */
|
||||
recentActivity: Array<{
|
||||
/** Date of activity */
|
||||
date: string
|
||||
/** Number of items on that date */
|
||||
count: number
|
||||
}>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test bulk operations endpoint
|
||||
* @example POST /test/bulk { "operation": "create", "data": [...] }
|
||||
*/
|
||||
'/test/bulk': {
|
||||
/** Perform bulk operations on test items */
|
||||
POST: {
|
||||
body: {
|
||||
/** Operation type */
|
||||
operation: 'create' | 'update' | 'delete'
|
||||
/** Array of data items to process */
|
||||
data: any[]
|
||||
}
|
||||
response: {
|
||||
successful: number
|
||||
failed: number
|
||||
errors: string[]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test error simulation endpoint
|
||||
* @example POST /test/error { "errorType": "timeout" }
|
||||
*/
|
||||
'/test/error': {
|
||||
/** Simulate various error scenarios for testing */
|
||||
POST: {
|
||||
body: {
|
||||
/** Type of error to simulate */
|
||||
errorType:
|
||||
| 'timeout'
|
||||
| 'network'
|
||||
| 'server'
|
||||
| 'notfound'
|
||||
| 'validation'
|
||||
| 'unauthorized'
|
||||
| 'ratelimit'
|
||||
| 'generic'
|
||||
}
|
||||
response: never
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test slow response endpoint
|
||||
* @example POST /test/slow { "delay": 2000 }
|
||||
*/
|
||||
'/test/slow': {
|
||||
/** Test slow response for performance testing */
|
||||
POST: {
|
||||
body: {
|
||||
/** Delay in milliseconds */
|
||||
delay: number
|
||||
}
|
||||
response: {
|
||||
message: string
|
||||
delay: number
|
||||
timestamp: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test data reset endpoint
|
||||
* @example POST /test/reset
|
||||
*/
|
||||
'/test/reset': {
|
||||
/** Reset all test data to initial state */
|
||||
POST: {
|
||||
response: {
|
||||
message: string
|
||||
timestamp: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test config endpoint
|
||||
* @example GET /test/config
|
||||
* @example PUT /test/config { "setting": "value" }
|
||||
*/
|
||||
'/test/config': {
|
||||
/** Get test configuration */
|
||||
GET: {
|
||||
response: Record<string, any>
|
||||
}
|
||||
/** Update test configuration */
|
||||
PUT: {
|
||||
body: Record<string, any>
|
||||
response: Record<string, any>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test status endpoint
|
||||
* @example GET /test/status
|
||||
*/
|
||||
'/test/status': {
|
||||
/** Get system test status */
|
||||
GET: {
|
||||
response: {
|
||||
status: string
|
||||
timestamp: string
|
||||
version: string
|
||||
uptime: number
|
||||
environment: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test performance endpoint
|
||||
* @example GET /test/performance
|
||||
*/
|
||||
'/test/performance': {
|
||||
/** Get performance metrics */
|
||||
GET: {
|
||||
response: {
|
||||
requestsPerSecond: number
|
||||
averageLatency: number
|
||||
memoryUsage: number
|
||||
cpuUsage: number
|
||||
uptime: number
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch execution of multiple requests
|
||||
* @example POST /batch { "requests": [...], "parallel": true }
|
||||
*/
|
||||
'/batch': {
|
||||
/** Execute multiple API requests in a single call */
|
||||
POST: {
|
||||
body: {
|
||||
/** Array of requests to execute */
|
||||
requests: Array<{
|
||||
/** HTTP method for the request */
|
||||
method: HttpMethod
|
||||
/** API path for the request */
|
||||
path: string
|
||||
/** URL parameters */
|
||||
params?: any
|
||||
/** Request body */
|
||||
body?: any
|
||||
}>
|
||||
/** Execute requests in parallel vs sequential */
|
||||
parallel?: boolean
|
||||
}
|
||||
response: {
|
||||
/** Results array matching input order */
|
||||
results: Array<{
|
||||
/** HTTP status code */
|
||||
status: number
|
||||
/** Response data if successful */
|
||||
data?: any
|
||||
/** Error information if failed */
|
||||
error?: any
|
||||
}>
|
||||
/** Batch execution metadata */
|
||||
metadata: {
|
||||
/** Total execution duration in ms */
|
||||
duration: number
|
||||
/** Number of successful requests */
|
||||
successCount: number
|
||||
/** Number of failed requests */
|
||||
errorCount: number
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Atomic transaction of multiple operations
|
||||
* @example POST /transaction { "operations": [...], "options": { "rollbackOnError": true } }
|
||||
*/
|
||||
'/transaction': {
|
||||
/** Execute multiple operations in a database transaction */
|
||||
POST: {
|
||||
body: {
|
||||
/** Array of operations to execute atomically */
|
||||
operations: Array<{
|
||||
/** HTTP method for the operation */
|
||||
method: HttpMethod
|
||||
/** API path for the operation */
|
||||
path: string
|
||||
/** URL parameters */
|
||||
params?: any
|
||||
/** Request body */
|
||||
body?: any
|
||||
}>
|
||||
/** Transaction configuration options */
|
||||
options?: {
|
||||
/** Database isolation level */
|
||||
isolation?: 'read-uncommitted' | 'read-committed' | 'repeatable-read' | 'serializable'
|
||||
/** Rollback all operations on any error */
|
||||
rollbackOnError?: boolean
|
||||
/** Transaction timeout in milliseconds */
|
||||
timeout?: number
|
||||
}
|
||||
}
|
||||
response: Array<{
|
||||
/** HTTP status code */
|
||||
status: number
|
||||
/** Response data if successful */
|
||||
data?: any
|
||||
/** Error information if failed */
|
||||
error?: any
|
||||
}>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simplified type extraction helpers
|
||||
*/
|
||||
export type ApiPaths = keyof ApiSchemas
|
||||
export type ApiMethods<TPath extends ApiPaths> = keyof ApiSchemas[TPath] & HttpMethod
|
||||
export type ApiResponse<TPath extends ApiPaths, TMethod extends string> = TPath extends keyof ApiSchemas
|
||||
? TMethod extends keyof ApiSchemas[TPath]
|
||||
? ApiSchemas[TPath][TMethod] extends { response: infer R }
|
||||
? R
|
||||
: never
|
||||
: never
|
||||
: never
|
||||
|
||||
export type ApiParams<TPath extends ApiPaths, TMethod extends string> = TPath extends keyof ApiSchemas
|
||||
? TMethod extends keyof ApiSchemas[TPath]
|
||||
? ApiSchemas[TPath][TMethod] extends { params: infer P }
|
||||
? P
|
||||
: never
|
||||
: never
|
||||
: never
|
||||
|
||||
export type ApiQuery<TPath extends ApiPaths, TMethod extends string> = TPath extends keyof ApiSchemas
|
||||
? TMethod extends keyof ApiSchemas[TPath]
|
||||
? ApiSchemas[TPath][TMethod] extends { query: infer Q }
|
||||
? Q
|
||||
: never
|
||||
: never
|
||||
: never
|
||||
|
||||
export type ApiBody<TPath extends ApiPaths, TMethod extends string> = TPath extends keyof ApiSchemas
|
||||
? TMethod extends keyof ApiSchemas[TPath]
|
||||
? ApiSchemas[TPath][TMethod] extends { body: infer B }
|
||||
? B
|
||||
: never
|
||||
: never
|
||||
: never
|
||||
|
||||
/**
|
||||
* Type-safe API client interface using concrete paths
|
||||
* Accepts actual paths like '/test/items/123' instead of '/test/items/:id'
|
||||
* Automatically infers query, body, and response types from ApiSchemas
|
||||
*/
|
||||
export interface ApiClient {
|
||||
get<TPath extends ConcreteApiPaths>(
|
||||
path: TPath,
|
||||
options?: {
|
||||
query?: QueryParamsForPath<TPath>
|
||||
headers?: Record<string, string>
|
||||
}
|
||||
): Promise<ResponseForPath<TPath, 'GET'>>
|
||||
|
||||
post<TPath extends ConcreteApiPaths>(
|
||||
path: TPath,
|
||||
options: {
|
||||
body?: BodyForPath<TPath, 'POST'>
|
||||
query?: Record<string, any>
|
||||
headers?: Record<string, string>
|
||||
}
|
||||
): Promise<ResponseForPath<TPath, 'POST'>>
|
||||
|
||||
put<TPath extends ConcreteApiPaths>(
|
||||
path: TPath,
|
||||
options: {
|
||||
body: BodyForPath<TPath, 'PUT'>
|
||||
query?: Record<string, any>
|
||||
headers?: Record<string, string>
|
||||
}
|
||||
): Promise<ResponseForPath<TPath, 'PUT'>>
|
||||
|
||||
delete<TPath extends ConcreteApiPaths>(
|
||||
path: TPath,
|
||||
options?: {
|
||||
query?: Record<string, any>
|
||||
headers?: Record<string, string>
|
||||
}
|
||||
): Promise<ResponseForPath<TPath, 'DELETE'>>
|
||||
|
||||
patch<TPath extends ConcreteApiPaths>(
|
||||
path: TPath,
|
||||
options: {
|
||||
body?: BodyForPath<TPath, 'PATCH'>
|
||||
query?: Record<string, any>
|
||||
headers?: Record<string, string>
|
||||
}
|
||||
): Promise<ResponseForPath<TPath, 'PATCH'>>
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper types to determine if parameters are required based on schema
|
||||
*/
|
||||
type HasRequiredQuery<Path extends ApiPaths, Method extends ApiMethods<Path>> = Path extends keyof ApiSchemas
|
||||
? Method extends keyof ApiSchemas[Path]
|
||||
? ApiSchemas[Path][Method] extends { query: any }
|
||||
? true
|
||||
: false
|
||||
: false
|
||||
: false
|
||||
|
||||
type HasRequiredBody<Path extends ApiPaths, Method extends ApiMethods<Path>> = Path extends keyof ApiSchemas
|
||||
? Method extends keyof ApiSchemas[Path]
|
||||
? ApiSchemas[Path][Method] extends { body: any }
|
||||
? true
|
||||
: false
|
||||
: false
|
||||
: false
|
||||
|
||||
type HasRequiredParams<Path extends ApiPaths, Method extends ApiMethods<Path>> = Path extends keyof ApiSchemas
|
||||
? Method extends keyof ApiSchemas[Path]
|
||||
? ApiSchemas[Path][Method] extends { params: any }
|
||||
? true
|
||||
: false
|
||||
: false
|
||||
: false
|
||||
|
||||
/**
|
||||
* Handler function for a specific API endpoint
|
||||
* Provides type-safe parameter extraction based on ApiSchemas
|
||||
* Parameters are required or optional based on the schema definition
|
||||
*/
|
||||
export type ApiHandler<Path extends ApiPaths, Method extends ApiMethods<Path>> = (
|
||||
params: (HasRequiredParams<Path, Method> extends true
|
||||
? { params: ApiParams<Path, Method> }
|
||||
: { params?: ApiParams<Path, Method> }) &
|
||||
(HasRequiredQuery<Path, Method> extends true
|
||||
? { query: ApiQuery<Path, Method> }
|
||||
: { query?: ApiQuery<Path, Method> }) &
|
||||
(HasRequiredBody<Path, Method> extends true ? { body: ApiBody<Path, Method> } : { body?: ApiBody<Path, Method> })
|
||||
) => Promise<ApiResponse<Path, Method>>
|
||||
|
||||
/**
|
||||
* Complete API implementation that must match ApiSchemas structure
|
||||
* TypeScript will error if any endpoint is missing - this ensures exhaustive coverage
|
||||
*/
|
||||
export type ApiImplementation = {
|
||||
[Path in ApiPaths]: {
|
||||
[Method in ApiMethods<Path>]: ApiHandler<Path, Method>
|
||||
}
|
||||
}
|
||||
@@ -1,289 +0,0 @@
|
||||
/**
|
||||
* Core types for the Data API system
|
||||
* Provides type definitions for request/response handling across renderer-main IPC communication
|
||||
*/
|
||||
|
||||
/**
|
||||
* Standard HTTP methods supported by the Data API
|
||||
*/
|
||||
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'
|
||||
|
||||
/**
|
||||
* Request object structure for Data API calls
|
||||
*/
|
||||
export interface DataRequest<T = any> {
|
||||
/** Unique request identifier for tracking and correlation */
|
||||
id: string
|
||||
/** HTTP method for the request */
|
||||
method: HttpMethod
|
||||
/** API path (e.g., '/topics', '/topics/123') */
|
||||
path: string
|
||||
/** URL parameters for the request */
|
||||
params?: Record<string, any>
|
||||
/** Request body data */
|
||||
body?: T
|
||||
/** Request headers */
|
||||
headers?: Record<string, string>
|
||||
/** Additional metadata for request processing */
|
||||
metadata?: {
|
||||
/** Request timestamp */
|
||||
timestamp: number
|
||||
/** OpenTelemetry span context for tracing */
|
||||
spanContext?: any
|
||||
/** Cache options for this specific request */
|
||||
cache?: CacheOptions
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Response object structure for Data API calls
|
||||
*/
|
||||
export interface DataResponse<T = any> {
|
||||
/** Request ID that this response corresponds to */
|
||||
id: string
|
||||
/** HTTP status code */
|
||||
status: number
|
||||
/** Response data if successful */
|
||||
data?: T
|
||||
/** Error information if request failed */
|
||||
error?: DataApiError
|
||||
/** Response metadata */
|
||||
metadata?: {
|
||||
/** Request processing duration in milliseconds */
|
||||
duration: number
|
||||
/** Whether response was served from cache */
|
||||
cached?: boolean
|
||||
/** Cache TTL if applicable */
|
||||
cacheTtl?: number
|
||||
/** Response timestamp */
|
||||
timestamp: number
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Standardized error structure for Data API
|
||||
*/
|
||||
export interface DataApiError {
|
||||
/** Error code for programmatic handling */
|
||||
code: string
|
||||
/** Human-readable error message */
|
||||
message: string
|
||||
/** HTTP status code */
|
||||
status: number
|
||||
/** Additional error details */
|
||||
details?: any
|
||||
/** Error stack trace (development mode only) */
|
||||
stack?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard error codes for Data API
|
||||
*/
|
||||
export enum ErrorCode {
|
||||
// Client errors (4xx)
|
||||
BAD_REQUEST = 'BAD_REQUEST',
|
||||
UNAUTHORIZED = 'UNAUTHORIZED',
|
||||
FORBIDDEN = 'FORBIDDEN',
|
||||
NOT_FOUND = 'NOT_FOUND',
|
||||
METHOD_NOT_ALLOWED = 'METHOD_NOT_ALLOWED',
|
||||
VALIDATION_ERROR = 'VALIDATION_ERROR',
|
||||
RATE_LIMIT_EXCEEDED = 'RATE_LIMIT_EXCEEDED',
|
||||
|
||||
// Server errors (5xx)
|
||||
INTERNAL_SERVER_ERROR = 'INTERNAL_SERVER_ERROR',
|
||||
DATABASE_ERROR = 'DATABASE_ERROR',
|
||||
SERVICE_UNAVAILABLE = 'SERVICE_UNAVAILABLE',
|
||||
|
||||
// Custom application errors
|
||||
MIGRATION_ERROR = 'MIGRATION_ERROR',
|
||||
PERMISSION_DENIED = 'PERMISSION_DENIED',
|
||||
RESOURCE_LOCKED = 'RESOURCE_LOCKED',
|
||||
CONCURRENT_MODIFICATION = 'CONCURRENT_MODIFICATION'
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache configuration options
|
||||
*/
|
||||
export interface CacheOptions {
|
||||
/** Cache TTL in seconds */
|
||||
ttl?: number
|
||||
/** Return stale data while revalidating in background */
|
||||
staleWhileRevalidate?: boolean
|
||||
/** Custom cache key override */
|
||||
cacheKey?: string
|
||||
/** Operations that should invalidate this cache entry */
|
||||
invalidateOn?: string[]
|
||||
/** Whether to bypass cache entirely */
|
||||
noCache?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Transaction request wrapper for atomic operations
|
||||
*/
|
||||
export interface TransactionRequest {
|
||||
/** List of operations to execute in transaction */
|
||||
operations: DataRequest[]
|
||||
/** Transaction options */
|
||||
options?: {
|
||||
/** Database isolation level */
|
||||
isolation?: 'read-uncommitted' | 'read-committed' | 'repeatable-read' | 'serializable'
|
||||
/** Whether to rollback entire transaction on any error */
|
||||
rollbackOnError?: boolean
|
||||
/** Transaction timeout in milliseconds */
|
||||
timeout?: number
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch request for multiple operations
|
||||
*/
|
||||
export interface BatchRequest {
|
||||
/** List of requests to execute */
|
||||
requests: DataRequest[]
|
||||
/** Whether to execute requests in parallel */
|
||||
parallel?: boolean
|
||||
/** Stop on first error */
|
||||
stopOnError?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch response containing results for all requests
|
||||
*/
|
||||
export interface BatchResponse {
|
||||
/** Individual response for each request */
|
||||
results: DataResponse[]
|
||||
/** Overall batch execution metadata */
|
||||
metadata: {
|
||||
/** Total execution time */
|
||||
duration: number
|
||||
/** Number of successful operations */
|
||||
successCount: number
|
||||
/** Number of failed operations */
|
||||
errorCount: number
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pagination parameters for list operations
|
||||
*/
|
||||
export interface PaginationParams {
|
||||
/** Page number (1-based) */
|
||||
page?: number
|
||||
/** Items per page */
|
||||
limit?: number
|
||||
/** Cursor for cursor-based pagination */
|
||||
cursor?: string
|
||||
/** Sort field and direction */
|
||||
sort?: {
|
||||
field: string
|
||||
order: 'asc' | 'desc'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Paginated response wrapper
|
||||
*/
|
||||
export interface PaginatedResponse<T> {
|
||||
/** Items for current page */
|
||||
items: T[]
|
||||
/** Total number of items */
|
||||
total: number
|
||||
/** Current page number */
|
||||
page: number
|
||||
/** Total number of pages */
|
||||
pageCount: number
|
||||
/** Whether there are more pages */
|
||||
hasNext: boolean
|
||||
/** Whether there are previous pages */
|
||||
hasPrev: boolean
|
||||
/** Next cursor for cursor-based pagination */
|
||||
nextCursor?: string
|
||||
/** Previous cursor for cursor-based pagination */
|
||||
prevCursor?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscription options for real-time data updates
|
||||
*/
|
||||
export interface SubscriptionOptions {
|
||||
/** Path pattern to subscribe to */
|
||||
path: string
|
||||
/** Filters to apply to subscription */
|
||||
filters?: Record<string, any>
|
||||
/** Whether to receive initial data */
|
||||
includeInitial?: boolean
|
||||
/** Custom subscription metadata */
|
||||
metadata?: Record<string, any>
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscription callback function
|
||||
*/
|
||||
export type SubscriptionCallback<T = any> = (data: T, event: SubscriptionEvent) => void
|
||||
|
||||
/**
|
||||
* Subscription event types
|
||||
*/
|
||||
export enum SubscriptionEvent {
|
||||
CREATED = 'created',
|
||||
UPDATED = 'updated',
|
||||
DELETED = 'deleted',
|
||||
INITIAL = 'initial',
|
||||
ERROR = 'error'
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware interface
|
||||
*/
|
||||
export interface Middleware {
|
||||
/** Middleware name */
|
||||
name: string
|
||||
/** Execution priority (lower = earlier) */
|
||||
priority?: number
|
||||
/** Middleware execution function */
|
||||
execute(req: DataRequest, res: DataResponse, next: () => Promise<void>): Promise<void>
|
||||
}
|
||||
|
||||
/**
|
||||
* Request context passed through middleware chain
|
||||
*/
|
||||
export interface RequestContext {
|
||||
/** Original request */
|
||||
request: DataRequest
|
||||
/** Response being built */
|
||||
response: DataResponse
|
||||
/** Path that matched this request */
|
||||
path?: string
|
||||
/** HTTP method */
|
||||
method?: HttpMethod
|
||||
/** Authenticated user (if any) */
|
||||
user?: any
|
||||
/** Additional context data */
|
||||
data: Map<string, any>
|
||||
}
|
||||
|
||||
/**
|
||||
* Base options for service operations
|
||||
*/
|
||||
export interface ServiceOptions {
|
||||
/** Database transaction to use */
|
||||
transaction?: any
|
||||
/** User context for authorization */
|
||||
user?: any
|
||||
/** Additional service-specific options */
|
||||
metadata?: Record<string, any>
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard service response wrapper
|
||||
*/
|
||||
export interface ServiceResult<T = any> {
|
||||
/** Whether operation was successful */
|
||||
success: boolean
|
||||
/** Result data if successful */
|
||||
data?: T
|
||||
/** Error information if failed */
|
||||
error?: DataApiError
|
||||
/** Additional metadata */
|
||||
metadata?: Record<string, any>
|
||||
}
|
||||
@@ -1,194 +0,0 @@
|
||||
/**
|
||||
* Centralized error code definitions for the Data API system
|
||||
* Provides consistent error handling across renderer and main processes
|
||||
*/
|
||||
|
||||
import type { DataApiError } from './apiTypes'
|
||||
import { ErrorCode } from './apiTypes'
|
||||
|
||||
// Re-export ErrorCode for convenience
|
||||
export { ErrorCode } from './apiTypes'
|
||||
|
||||
/**
|
||||
* Error code to HTTP status mapping
|
||||
*/
|
||||
export const ERROR_STATUS_MAP: Record<ErrorCode, number> = {
|
||||
// Client errors (4xx)
|
||||
[ErrorCode.BAD_REQUEST]: 400,
|
||||
[ErrorCode.UNAUTHORIZED]: 401,
|
||||
[ErrorCode.FORBIDDEN]: 403,
|
||||
[ErrorCode.NOT_FOUND]: 404,
|
||||
[ErrorCode.METHOD_NOT_ALLOWED]: 405,
|
||||
[ErrorCode.VALIDATION_ERROR]: 422,
|
||||
[ErrorCode.RATE_LIMIT_EXCEEDED]: 429,
|
||||
|
||||
// Server errors (5xx)
|
||||
[ErrorCode.INTERNAL_SERVER_ERROR]: 500,
|
||||
[ErrorCode.DATABASE_ERROR]: 500,
|
||||
[ErrorCode.SERVICE_UNAVAILABLE]: 503,
|
||||
|
||||
// Custom application errors (5xx)
|
||||
[ErrorCode.MIGRATION_ERROR]: 500,
|
||||
[ErrorCode.PERMISSION_DENIED]: 403,
|
||||
[ErrorCode.RESOURCE_LOCKED]: 423,
|
||||
[ErrorCode.CONCURRENT_MODIFICATION]: 409
|
||||
}
|
||||
|
||||
/**
|
||||
* Default error messages for each error code
|
||||
*/
|
||||
export const ERROR_MESSAGES: Record<ErrorCode, string> = {
|
||||
[ErrorCode.BAD_REQUEST]: 'Bad request: Invalid request format or parameters',
|
||||
[ErrorCode.UNAUTHORIZED]: 'Unauthorized: Authentication required',
|
||||
[ErrorCode.FORBIDDEN]: 'Forbidden: Insufficient permissions',
|
||||
[ErrorCode.NOT_FOUND]: 'Not found: Requested resource does not exist',
|
||||
[ErrorCode.METHOD_NOT_ALLOWED]: 'Method not allowed: HTTP method not supported for this endpoint',
|
||||
[ErrorCode.VALIDATION_ERROR]: 'Validation error: Request data does not meet requirements',
|
||||
[ErrorCode.RATE_LIMIT_EXCEEDED]: 'Rate limit exceeded: Too many requests',
|
||||
|
||||
[ErrorCode.INTERNAL_SERVER_ERROR]: 'Internal server error: An unexpected error occurred',
|
||||
[ErrorCode.DATABASE_ERROR]: 'Database error: Failed to access or modify data',
|
||||
[ErrorCode.SERVICE_UNAVAILABLE]: 'Service unavailable: The service is temporarily unavailable',
|
||||
|
||||
[ErrorCode.MIGRATION_ERROR]: 'Migration error: Failed to migrate data',
|
||||
[ErrorCode.PERMISSION_DENIED]: 'Permission denied: Operation not allowed for current user',
|
||||
[ErrorCode.RESOURCE_LOCKED]: 'Resource locked: Resource is currently locked by another operation',
|
||||
[ErrorCode.CONCURRENT_MODIFICATION]: 'Concurrent modification: Resource was modified by another user'
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility class for creating standardized Data API errors
|
||||
*/
|
||||
export class DataApiErrorFactory {
|
||||
/**
|
||||
* Create a DataApiError with standard properties
|
||||
*/
|
||||
static create(code: ErrorCode, customMessage?: string, details?: any, stack?: string): DataApiError {
|
||||
return {
|
||||
code,
|
||||
message: customMessage || ERROR_MESSAGES[code],
|
||||
status: ERROR_STATUS_MAP[code],
|
||||
details,
|
||||
stack: stack || undefined
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a validation error with field-specific details
|
||||
*/
|
||||
static validation(fieldErrors: Record<string, string[]>, message?: string): DataApiError {
|
||||
return this.create(ErrorCode.VALIDATION_ERROR, message || 'Request validation failed', { fieldErrors })
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a not found error for specific resource
|
||||
*/
|
||||
static notFound(resource: string, id?: string): DataApiError {
|
||||
const message = id ? `${resource} with id '${id}' not found` : `${resource} not found`
|
||||
|
||||
return this.create(ErrorCode.NOT_FOUND, message, { resource, id })
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a database error with query details
|
||||
*/
|
||||
static database(originalError: Error, operation?: string): DataApiError {
|
||||
return this.create(
|
||||
ErrorCode.DATABASE_ERROR,
|
||||
`Database operation failed${operation ? `: ${operation}` : ''}`,
|
||||
{
|
||||
originalError: originalError.message,
|
||||
operation
|
||||
},
|
||||
originalError.stack
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a permission denied error
|
||||
*/
|
||||
static permissionDenied(action: string, resource?: string): DataApiError {
|
||||
const message = resource ? `Permission denied: Cannot ${action} ${resource}` : `Permission denied: Cannot ${action}`
|
||||
|
||||
return this.create(ErrorCode.PERMISSION_DENIED, message, { action, resource })
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an internal server error from an unexpected error
|
||||
*/
|
||||
static internal(originalError: Error, context?: string): DataApiError {
|
||||
const message = context
|
||||
? `Internal error in ${context}: ${originalError.message}`
|
||||
: `Internal error: ${originalError.message}`
|
||||
|
||||
return this.create(
|
||||
ErrorCode.INTERNAL_SERVER_ERROR,
|
||||
message,
|
||||
{ originalError: originalError.message, context },
|
||||
originalError.stack
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a rate limit exceeded error
|
||||
*/
|
||||
static rateLimit(limit: number, windowMs: number): DataApiError {
|
||||
return this.create(ErrorCode.RATE_LIMIT_EXCEEDED, `Rate limit exceeded: ${limit} requests per ${windowMs}ms`, {
|
||||
limit,
|
||||
windowMs
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a resource locked error
|
||||
*/
|
||||
static resourceLocked(resource: string, id: string, lockedBy?: string): DataApiError {
|
||||
const message = lockedBy
|
||||
? `${resource} '${id}' is locked by ${lockedBy}`
|
||||
: `${resource} '${id}' is currently locked`
|
||||
|
||||
return this.create(ErrorCode.RESOURCE_LOCKED, message, { resource, id, lockedBy })
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a concurrent modification error
|
||||
*/
|
||||
static concurrentModification(resource: string, id: string): DataApiError {
|
||||
return this.create(ErrorCode.CONCURRENT_MODIFICATION, `${resource} '${id}' was modified by another user`, {
|
||||
resource,
|
||||
id
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an error is a Data API error
|
||||
*/
|
||||
export function isDataApiError(error: any): error is DataApiError {
|
||||
return (
|
||||
error &&
|
||||
typeof error === 'object' &&
|
||||
typeof error.code === 'string' &&
|
||||
typeof error.message === 'string' &&
|
||||
typeof error.status === 'number'
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a generic error to a DataApiError
|
||||
*/
|
||||
export function toDataApiError(error: unknown, context?: string): DataApiError {
|
||||
if (isDataApiError(error)) {
|
||||
return error
|
||||
}
|
||||
|
||||
if (error instanceof Error) {
|
||||
return DataApiErrorFactory.internal(error, context)
|
||||
}
|
||||
|
||||
return DataApiErrorFactory.create(
|
||||
ErrorCode.INTERNAL_SERVER_ERROR,
|
||||
`Unknown error${context ? ` in ${context}` : ''}: ${String(error)}`,
|
||||
{ originalError: error, context }
|
||||
)
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
/**
|
||||
* Cherry Studio Data API - Barrel Exports
|
||||
*
|
||||
* This file provides a centralized entry point for all data API types,
|
||||
* schemas, and utilities. Import everything you need from this single location.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { Topic, CreateTopicDto, ApiSchemas, DataRequest, ErrorCode } from '@/shared/data'
|
||||
* ```
|
||||
*/
|
||||
|
||||
// Core data API types and infrastructure
|
||||
export type {
|
||||
BatchRequest,
|
||||
BatchResponse,
|
||||
CacheOptions,
|
||||
DataApiError,
|
||||
DataRequest,
|
||||
DataResponse,
|
||||
HttpMethod,
|
||||
Middleware,
|
||||
PaginatedResponse,
|
||||
PaginationParams,
|
||||
RequestContext,
|
||||
ServiceOptions,
|
||||
ServiceResult,
|
||||
SubscriptionCallback,
|
||||
SubscriptionOptions,
|
||||
TransactionRequest
|
||||
} from './apiTypes'
|
||||
export { ErrorCode, SubscriptionEvent } from './apiTypes'
|
||||
|
||||
// Domain models and DTOs
|
||||
export type {
|
||||
BulkOperationRequest,
|
||||
BulkOperationResponse,
|
||||
CreateTestItemDto,
|
||||
TestItem,
|
||||
UpdateTestItemDto
|
||||
} from './apiModels'
|
||||
|
||||
// API schema definitions and type helpers
|
||||
export type {
|
||||
ApiBody,
|
||||
ApiClient,
|
||||
ApiMethods,
|
||||
ApiParams,
|
||||
ApiPaths,
|
||||
ApiQuery,
|
||||
ApiResponse,
|
||||
ApiSchemas
|
||||
} from './apiSchemas'
|
||||
|
||||
// Path type utilities for template literal types
|
||||
export type {
|
||||
BodyForPath,
|
||||
ConcreteApiPaths,
|
||||
MatchApiPath,
|
||||
QueryParamsForPath,
|
||||
ResolvedPath,
|
||||
ResponseForPath
|
||||
} from './apiPaths'
|
||||
|
||||
// Error handling utilities
|
||||
export {
|
||||
ErrorCode as DataApiErrorCode,
|
||||
DataApiErrorFactory,
|
||||
ERROR_MESSAGES,
|
||||
ERROR_STATUS_MAP,
|
||||
isDataApiError,
|
||||
toDataApiError
|
||||
} from './errorCodes'
|
||||
|
||||
/**
|
||||
* Re-export commonly used type combinations for convenience
|
||||
*/
|
||||
|
||||
// Import types for re-export convenience types
|
||||
import type { CreateTestItemDto, TestItem, UpdateTestItemDto } from './apiModels'
|
||||
import type {
|
||||
BatchRequest,
|
||||
BatchResponse,
|
||||
DataApiError,
|
||||
DataRequest,
|
||||
DataResponse,
|
||||
ErrorCode,
|
||||
PaginatedResponse,
|
||||
PaginationParams,
|
||||
TransactionRequest
|
||||
} from './apiTypes'
|
||||
import type { DataApiErrorFactory } from './errorCodes'
|
||||
|
||||
/** All test item-related types */
|
||||
export type TestItemTypes = {
|
||||
TestItem: TestItem
|
||||
CreateTestItemDto: CreateTestItemDto
|
||||
UpdateTestItemDto: UpdateTestItemDto
|
||||
}
|
||||
|
||||
/** All error-related types and utilities */
|
||||
export type ErrorTypes = {
|
||||
DataApiError: DataApiError
|
||||
ErrorCode: ErrorCode
|
||||
ErrorFactory: typeof DataApiErrorFactory
|
||||
}
|
||||
|
||||
/** All request/response types */
|
||||
export type RequestTypes = {
|
||||
DataRequest: DataRequest
|
||||
DataResponse: DataResponse
|
||||
BatchRequest: BatchRequest
|
||||
BatchResponse: BatchResponse
|
||||
TransactionRequest: TransactionRequest
|
||||
}
|
||||
|
||||
/** All pagination-related types */
|
||||
export type PaginationTypes = {
|
||||
PaginationParams: PaginationParams
|
||||
PaginatedResponse: PaginatedResponse<any>
|
||||
}
|
||||
168
packages/shared/data/cache/cacheSchemas.ts
vendored
168
packages/shared/data/cache/cacheSchemas.ts
vendored
@@ -1,168 +0,0 @@
|
||||
import type { TranslateLanguageCode } from '@types'
|
||||
|
||||
import type * as CacheValueTypes from './cacheValueTypes'
|
||||
|
||||
/**
|
||||
* Use cache schema for renderer hook
|
||||
*/
|
||||
|
||||
export type UseCacheSchema = {
|
||||
// App state
|
||||
'app.dist.update_state': CacheValueTypes.CacheAppUpdateState
|
||||
'app.user.avatar': string
|
||||
|
||||
// Chat context
|
||||
'chat.multi_select_mode': boolean
|
||||
'chat.selected_message_ids': string[]
|
||||
'chat.generating': boolean
|
||||
'chat.websearch.searching': boolean
|
||||
'chat.websearch.active_searches': CacheValueTypes.CacheActiveSearches
|
||||
|
||||
// Minapp management
|
||||
'minapp.opened_keep_alive': CacheValueTypes.CacheMinAppType[]
|
||||
'minapp.current_id': string
|
||||
'minapp.show': boolean
|
||||
'minapp.opened_oneoff': CacheValueTypes.CacheMinAppType | null
|
||||
|
||||
// Topic management
|
||||
'topic.active': CacheValueTypes.CacheTopic | null
|
||||
'topic.renaming': string[]
|
||||
'topic.newly_renamed': string[]
|
||||
|
||||
// Translate state
|
||||
'translate.lang.source': TranslateLanguageCode | 'auto'
|
||||
'translate.lang.target': TranslateLanguageCode
|
||||
'translate.input': string
|
||||
'translate.output': string
|
||||
'translate.detecting': boolean
|
||||
'translate.translating': CacheValueTypes.CacheTranslating
|
||||
'translate.bidirectional': CacheValueTypes.CacheTranslateBidirectional
|
||||
|
||||
// Test keys (for dataRefactorTest window)
|
||||
// TODO: remove after testing
|
||||
'test-hook-memory-1': string
|
||||
'test-ttl-cache': string
|
||||
'test-protected-cache': string
|
||||
'test-deep-equal': { nested: { count: number }; tags: string[] }
|
||||
'test-performance': number
|
||||
'test-multi-hook': string
|
||||
'concurrent-test-1': number
|
||||
'concurrent-test-2': number
|
||||
'large-data-test': Record<string, any>
|
||||
'test-number-cache': number
|
||||
'test-object-cache': { name: string; count: number; active: boolean }
|
||||
}
|
||||
|
||||
export const DefaultUseCache: UseCacheSchema = {
|
||||
// App state
|
||||
'app.dist.update_state': {
|
||||
info: null,
|
||||
checking: false,
|
||||
downloading: false,
|
||||
downloaded: false,
|
||||
downloadProgress: 0,
|
||||
available: false
|
||||
},
|
||||
'app.user.avatar': '',
|
||||
|
||||
// Chat context
|
||||
'chat.multi_select_mode': false,
|
||||
'chat.selected_message_ids': [],
|
||||
'chat.generating': false,
|
||||
'chat.websearch.searching': false,
|
||||
'chat.websearch.active_searches': {},
|
||||
|
||||
// Minapp management
|
||||
'minapp.opened_keep_alive': [],
|
||||
'minapp.current_id': '',
|
||||
'minapp.show': false,
|
||||
'minapp.opened_oneoff': null,
|
||||
|
||||
// Topic management
|
||||
'topic.active': null,
|
||||
'topic.renaming': [],
|
||||
'topic.newly_renamed': [],
|
||||
|
||||
// Translate state
|
||||
'translate.lang.source': 'auto',
|
||||
'translate.lang.target': 'zh-cn',
|
||||
'translate.input': '',
|
||||
'translate.output': '',
|
||||
'translate.detecting': false,
|
||||
'translate.translating': { isTranslating: false, abortKey: null },
|
||||
'translate.bidirectional': {
|
||||
enabled: false,
|
||||
origin: 'en-us',
|
||||
target: 'zh-cn'
|
||||
},
|
||||
|
||||
// Test keys (for dataRefactorTest window)
|
||||
// TODO: remove after testing
|
||||
'test-hook-memory-1': 'default-memory-value',
|
||||
'test-ttl-cache': 'test-ttl-cache',
|
||||
'test-protected-cache': 'protected-value',
|
||||
'test-deep-equal': { nested: { count: 0 }, tags: ['initial'] },
|
||||
'test-performance': 0,
|
||||
'test-multi-hook': 'hook-1-default',
|
||||
'concurrent-test-1': 0,
|
||||
'concurrent-test-2': 0,
|
||||
'large-data-test': {},
|
||||
'test-number-cache': 42,
|
||||
'test-object-cache': { name: 'test', count: 0, active: true }
|
||||
}
|
||||
|
||||
/**
|
||||
* Use shared cache schema for renderer hook
|
||||
*/
|
||||
export type UseSharedCacheSchema = {
|
||||
'example-key': string
|
||||
|
||||
// Test keys (for dataRefactorTest window)
|
||||
// TODO: remove after testing
|
||||
'test-hook-shared-1': string
|
||||
'test-multi-hook': string
|
||||
'concurrent-shared': number
|
||||
}
|
||||
|
||||
export const DefaultUseSharedCache: UseSharedCacheSchema = {
|
||||
'example-key': 'example default value',
|
||||
|
||||
// Test keys (for dataRefactorTest window)
|
||||
// TODO: remove after testing
|
||||
'concurrent-shared': 0,
|
||||
'test-hook-shared-1': 'default-shared-value',
|
||||
'test-multi-hook': 'hook-3-shared'
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist cache schema defining allowed keys and their value types
|
||||
* This ensures type safety and prevents key conflicts
|
||||
*/
|
||||
export type RendererPersistCacheSchema = {
|
||||
'example-key': string
|
||||
|
||||
// Test keys (for dataRefactorTest window)
|
||||
// TODO: remove after testing
|
||||
'example-1': string
|
||||
'example-2': string
|
||||
'example-3': string
|
||||
'example-4': string
|
||||
}
|
||||
|
||||
export const DefaultRendererPersistCache: RendererPersistCacheSchema = {
|
||||
'example-key': 'example default value',
|
||||
|
||||
// Test keys (for dataRefactorTest window)
|
||||
// TODO: remove after testing
|
||||
'example-1': 'example default value',
|
||||
'example-2': 'example default value',
|
||||
'example-3': 'example default value',
|
||||
'example-4': 'example default value'
|
||||
}
|
||||
|
||||
/**
|
||||
* Type-safe cache key
|
||||
*/
|
||||
export type RendererPersistCacheKey = keyof RendererPersistCacheSchema
|
||||
export type UseCacheKey = keyof UseCacheSchema
|
||||
export type UseSharedCacheKey = keyof UseSharedCacheSchema
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user