mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-07-03 12:27:41 +08:00
Merge branch 'main' of github.com:CherryHQ/cherry-studio into v2
This commit is contained in:
@@ -1,4 +0,0 @@
|
||||
---
|
||||
---
|
||||
|
||||
Bump AI SDK dependencies and fix provider API host formatting
|
||||
@@ -1,10 +0,0 @@
|
||||
---
|
||||
'@cherrystudio/ai-core': patch
|
||||
---
|
||||
|
||||
fix(providers): azure-anthropic variant uses correct Anthropic toolFactories for web search
|
||||
|
||||
- Add `TOutput` generic to `ProviderVariant` so `transform` output type flows to `toolFactories` and `resolveModel`
|
||||
- Add Anthropic-specific `toolFactories` to `azure-anthropic` variant (fixes `provider.tools.webSearchPreview is not a function`)
|
||||
- Fix `urlContext` factory incorrectly mapping to `webSearch` tool key instead of `urlContext`
|
||||
- Fix `BedrockExtension` `satisfies` type to use `AmazonBedrockProvider` instead of `ProviderV3`
|
||||
@@ -144,7 +144,7 @@ artifactBuildCompleted: scripts/artifact-build-completed.js
|
||||
releaseInfo:
|
||||
releaseNotes: |
|
||||
<!--LANG:en-->
|
||||
Cherry Studio 1.9.0 - CherryClaw Agent & Skills System
|
||||
Cherry Studio 1.9.1 - CherryClaw Agent & Skills System
|
||||
|
||||
⚠️ Breaking Changes
|
||||
- [Agents] "Plugins" system renamed to "Skills". Plugin marketplace has been replaced by a unified Skills management interface
|
||||
@@ -190,7 +190,7 @@ releaseInfo:
|
||||
- [Agent] Improve session switch experience
|
||||
|
||||
<!--LANG:zh-CN-->
|
||||
Cherry Studio 1.9.0 - CherryClaw 智能体与技能系统
|
||||
Cherry Studio 1.9.1 - CherryClaw 智能体与技能系统
|
||||
|
||||
⚠️ 破坏性变更
|
||||
- [智能体] "插件"系统更名为"技能(Skills)",插件市场已替换为统一的技能管理界面
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
# @cherrystudio/ai-core
|
||||
|
||||
## 2.0.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#14087](https://github.com/CherryHQ/cherry-studio/pull/14087) [`1f72f98`](https://github.com/CherryHQ/cherry-studio/commit/1f72f9890508c6fc0bc95793e286cf61b991c51c) Thanks [@DeJeune](https://github.com/DeJeune)! - fix(providers): azure-anthropic variant uses correct Anthropic toolFactories for web search
|
||||
|
||||
- Add `TOutput` generic to `ProviderVariant` so `transform` output type flows to `toolFactories` and `resolveModel`
|
||||
- Add Anthropic-specific `toolFactories` to `azure-anthropic` variant (fixes `provider.tools.webSearchPreview is not a function`)
|
||||
- Fix `urlContext` factory incorrectly mapping to `webSearch` tool key instead of `urlContext`
|
||||
- Fix `BedrockExtension` `satisfies` type to use `AmazonBedrockProvider` instead of `ProviderV3`
|
||||
|
||||
## 2.0.0
|
||||
|
||||
### Major Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@cherrystudio/ai-core",
|
||||
"version": "2.0.0",
|
||||
"version": "2.0.1",
|
||||
"description": "Cherry Studio AI Core - Unified AI Provider Interface Based on Vercel AI SDK",
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.mjs",
|
||||
|
||||
@@ -14,7 +14,7 @@ import { crossPlatformSpawn, findExecutableInEnv, getBinaryPath, runInstallScrip
|
||||
import getShellEnv, { refreshShellEnv } from '@main/utils/shell-env'
|
||||
import type { OperationResult } from '@shared/config/types'
|
||||
import { IpcChannel } from '@shared/IpcChannel'
|
||||
import { hasAPIVersion, withoutTrailingSlash } from '@shared/utils'
|
||||
import { formatApiHost, hasAPIVersion, withoutTrailingSlash } from '@shared/utils'
|
||||
import type { Model, Provider, ProviderType, VertexProvider } from '@types'
|
||||
|
||||
import { parseCurrentVersion, parseUpdateStatus } from './utils/openClawParsers'
|
||||
@@ -715,7 +715,9 @@ export class OpenClawService extends BaseService {
|
||||
}
|
||||
let url = `http://127.0.0.1:${this.gatewayPort}`
|
||||
if (this.gatewayAuthToken) {
|
||||
url += `#token=${encodeURIComponent(this.gatewayAuthToken)}`
|
||||
// Use query string (not URL fragment) so dashboard app state can persist correctly.
|
||||
// Fragment (#...) is often used by SPAs for transient client-side state.
|
||||
url += `?token=${encodeURIComponent(this.gatewayAuthToken)}`
|
||||
}
|
||||
return url
|
||||
}
|
||||
@@ -1055,6 +1057,14 @@ export class OpenClawService extends BaseService {
|
||||
* - Others: {host}/v1
|
||||
*/
|
||||
private formatOpenAIUrl(provider: Provider): string {
|
||||
// Special-case built-in GitHub / Copilot providers: these hosts should
|
||||
// not have a `/v1` suffix appended by default (renderer applies
|
||||
// `formatApiHost(..., false)` for these). Mirror that behavior here
|
||||
// to avoid constructing incorrect endpoints that return 404.
|
||||
if (provider.id === 'copilot' || provider.id === 'github') {
|
||||
return formatApiHost(provider.apiHost, false)
|
||||
}
|
||||
|
||||
const url = withoutTrailingSlash(provider.apiHost)
|
||||
const providerType = provider.type
|
||||
|
||||
|
||||
@@ -99,6 +99,16 @@ describe('OpenClawService gateway status state machine', () => {
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
describe('getDashboardUrl', () => {
|
||||
it('uses query string token to preserve dashboard UI state', () => {
|
||||
// @ts-expect-error -- accessing private field for testing
|
||||
service.gatewayAuthToken = 'a b+c'
|
||||
|
||||
const url = service.getDashboardUrl()
|
||||
expect(url).toBe(`http://127.0.0.1:18790?token=${encodeURIComponent('a b+c')}`)
|
||||
})
|
||||
})
|
||||
|
||||
// ─── getStatus ───────────────────────────────────────────────
|
||||
|
||||
describe('getStatus', () => {
|
||||
|
||||
@@ -5608,7 +5608,7 @@
|
||||
"noInstalled": "No skills installed",
|
||||
"noResults": "No skills found",
|
||||
"noSkillFile": "No SKILL.md found",
|
||||
"searchPlaceholder": "Search skills across registries...",
|
||||
"searchPlaceholder": "Discover more skills...",
|
||||
"searchRegistryTitle": "Search skill registries online",
|
||||
"searchTitle": "Search Skills",
|
||||
"selectFile": "Select a file to view",
|
||||
|
||||
@@ -5608,7 +5608,7 @@
|
||||
"noInstalled": "暂无已安装的技能",
|
||||
"noResults": "未找到技能",
|
||||
"noSkillFile": "未找到 SKILL.md 文件",
|
||||
"searchPlaceholder": "跨注册表搜索技能...",
|
||||
"searchPlaceholder": "发现更多技能...",
|
||||
"searchRegistryTitle": "在线搜索技能注册表",
|
||||
"searchTitle": "搜索技能",
|
||||
"selectFile": "选择一个文件查看",
|
||||
|
||||
@@ -5608,7 +5608,7 @@
|
||||
"noInstalled": "暫無已安裝的技能",
|
||||
"noResults": "未找到技能",
|
||||
"noSkillFile": "未找到 SKILL.md 檔案",
|
||||
"searchPlaceholder": "跨登錄檔搜尋技能...",
|
||||
"searchPlaceholder": "發現更多技能...",
|
||||
"searchRegistryTitle": "在線搜尋技能登錄檔",
|
||||
"searchTitle": "搜尋技能",
|
||||
"selectFile": "選擇一個檔案查看",
|
||||
|
||||
@@ -5608,7 +5608,7 @@
|
||||
"noInstalled": "Keine Fähigkeiten installiert",
|
||||
"noResults": "Keine Fähigkeiten gefunden",
|
||||
"noSkillFile": "Keine SKILL.md gefunden",
|
||||
"searchPlaceholder": "Suchfähigkeiten über Registrierungsstellen hinweg...",
|
||||
"searchPlaceholder": "Weitere Fähigkeiten entdecken...",
|
||||
"searchRegistryTitle": "Durchsuche Fähigkeitsregister online",
|
||||
"searchTitle": "Suchfähigkeiten",
|
||||
"selectFile": "Wählen Sie eine Datei zum Anzeigen",
|
||||
|
||||
@@ -5608,7 +5608,7 @@
|
||||
"noInstalled": "Δεν έχουν εγκατασταθεί δεξιότητες",
|
||||
"noResults": "Δεν βρέθηκαν δεξιότητες",
|
||||
"noSkillFile": "Δεν βρέθηκε SKILL.md",
|
||||
"searchPlaceholder": "Αναζήτηση δεξιοτήτων στα μητρώα...",
|
||||
"searchPlaceholder": "Ανακαλύψτε περισσότερες δεξιότητες...",
|
||||
"searchRegistryTitle": "Αναζητήστε μητρώα δεξιοτήτων στο διαδίκτυο",
|
||||
"searchTitle": "Δεξιότητες Αναζήτησης",
|
||||
"selectFile": "Επιλέξτε ένα αρχείο για προβολή",
|
||||
|
||||
@@ -5608,7 +5608,7 @@
|
||||
"noInstalled": "Sin habilidades instaladas",
|
||||
"noResults": "Sin habilidades encontradas",
|
||||
"noSkillFile": "No se encontró SKILL.md",
|
||||
"searchPlaceholder": "Buscar habilidades en los registros...",
|
||||
"searchPlaceholder": "Descubrir más habilidades...",
|
||||
"searchRegistryTitle": "Buscar registros de habilidades en línea",
|
||||
"searchTitle": "Habilidades de búsqueda",
|
||||
"selectFile": "Selecciona un archivo para ver",
|
||||
|
||||
@@ -5608,7 +5608,7 @@
|
||||
"noInstalled": "Aucune compétence installée",
|
||||
"noResults": "Aucune compétence trouvée",
|
||||
"noSkillFile": "Aucun SKILL.md trouvé",
|
||||
"searchPlaceholder": "Rechercher des compétences dans les registres...",
|
||||
"searchPlaceholder": "Découvrir plus de compétences...",
|
||||
"searchRegistryTitle": "Rechercher des registres de compétences en ligne",
|
||||
"searchTitle": "Compétences de recherche",
|
||||
"selectFile": "Sélectionner un fichier à afficher",
|
||||
|
||||
@@ -5608,7 +5608,7 @@
|
||||
"noInstalled": "スキルがインストールされていません",
|
||||
"noResults": "スキルが見つかりません",
|
||||
"noSkillFile": "SKILL.mdが見つかりません",
|
||||
"searchPlaceholder": "レジストリ全体でスキルを検索...",
|
||||
"searchPlaceholder": "さらにスキルを見つける...",
|
||||
"searchRegistryTitle": "オンラインでスキルレジストリを検索してください",
|
||||
"searchTitle": "検索スキル",
|
||||
"selectFile": "ファイルを選択して表示",
|
||||
|
||||
@@ -5608,7 +5608,7 @@
|
||||
"noInstalled": "Nenhuma habilidade instalada",
|
||||
"noResults": "Nenhuma habilidade encontrada",
|
||||
"noSkillFile": "Nenhum SKILL.md encontrado",
|
||||
"searchPlaceholder": "Pesquisar competências nos registros...",
|
||||
"searchPlaceholder": "Descobrir mais competências...",
|
||||
"searchRegistryTitle": "Procure registros de habilidades online",
|
||||
"searchTitle": "Habilidades de Pesquisa",
|
||||
"selectFile": "Selecione um arquivo para visualizar",
|
||||
|
||||
@@ -5608,7 +5608,7 @@
|
||||
"noInstalled": "Nicio competență instalată",
|
||||
"noResults": "Nicio competență găsită",
|
||||
"noSkillFile": "Nu s-a găsit SKILL.md",
|
||||
"searchPlaceholder": "Caută competențe în registre...",
|
||||
"searchPlaceholder": "Descoperă mai multe competențe...",
|
||||
"searchRegistryTitle": "Caută registre de competențe online",
|
||||
"searchTitle": "Abilități de căutare",
|
||||
"selectFile": "Selectați un fișier pentru vizualizare",
|
||||
|
||||
@@ -5608,7 +5608,7 @@
|
||||
"noInstalled": "Навыков не установлено",
|
||||
"noResults": "Навыки не найдены",
|
||||
"noSkillFile": "Файл SKILL.md не найден",
|
||||
"searchPlaceholder": "Поиск навыков в реестрах...",
|
||||
"searchPlaceholder": "Откройте больше навыков...",
|
||||
"searchRegistryTitle": "Ищите реестры навыков онлайн",
|
||||
"searchTitle": "Навыки поиска",
|
||||
"selectFile": "Выберите файл для просмотра",
|
||||
|
||||
@@ -227,7 +227,6 @@ const SkillsSettings: FC = () => {
|
||||
const [expandedDirs, setExpandedDirs] = useState<Set<string>>(new Set())
|
||||
|
||||
// Search state (online registry)
|
||||
const [searchOpen, setSearchOpen] = useState(false)
|
||||
const [searchQuery, setSearchQuery] = useState('')
|
||||
const searchInputRef = useRef<HTMLInputElement>(null)
|
||||
const searchContainerRef = useRef<HTMLDivElement>(null)
|
||||
@@ -293,27 +292,18 @@ const SkillsSettings: FC = () => {
|
||||
|
||||
// Close search dropdown on outside click (but not when clicking inside a modal)
|
||||
useEffect(() => {
|
||||
if (!searchOpen) return
|
||||
const handler = (e: MouseEvent) => {
|
||||
const target = e.target as Node
|
||||
if (searchContainerRef.current && !searchContainerRef.current.contains(target)) {
|
||||
const modal = (target as Element).closest?.('.ant-modal-root, .ant-modal-wrap, .ant-modal')
|
||||
if (modal) return
|
||||
setSearchOpen(false)
|
||||
setSearchQuery('')
|
||||
clear()
|
||||
}
|
||||
}
|
||||
document.addEventListener('mousedown', handler)
|
||||
return () => document.removeEventListener('mousedown', handler)
|
||||
}, [searchOpen, clear])
|
||||
|
||||
// Focus input when search opens
|
||||
useEffect(() => {
|
||||
if (searchOpen) {
|
||||
setTimeout(() => searchInputRef.current?.focus(), 50)
|
||||
}
|
||||
}, [searchOpen])
|
||||
}, [clear])
|
||||
|
||||
// Filtered skills list
|
||||
const filteredSkills = useMemo(() => {
|
||||
@@ -466,9 +456,9 @@ const SkillsSettings: FC = () => {
|
||||
}, [selectedFile])
|
||||
|
||||
const handleCloseSearch = useCallback(() => {
|
||||
setSearchOpen(false)
|
||||
setSearchQuery('')
|
||||
clear()
|
||||
searchInputRef.current?.blur()
|
||||
}, [clear])
|
||||
|
||||
const handleZipInstall = useCallback(
|
||||
@@ -671,60 +661,51 @@ const SkillsSettings: FC = () => {
|
||||
) : null}
|
||||
</DetailMeta>
|
||||
) : null}
|
||||
{searchOpen ? (
|
||||
<SearchInputWrapper>
|
||||
<Input
|
||||
ref={searchInputRef as React.Ref<any>}
|
||||
size="small"
|
||||
placeholder={t('settings.skills.searchPlaceholder')}
|
||||
value={searchQuery}
|
||||
onChange={(e) => handleSearch(e.target.value)}
|
||||
suffix={<X size={12} style={CLOSE_ICON_STYLE} onClick={handleCloseSearch} />}
|
||||
prefix={<Search size={12} />}
|
||||
/>
|
||||
{searching || results.length > 0 || (searchQuery && !searching) ? (
|
||||
<SearchDropdown>
|
||||
<SearchTabs>
|
||||
{SEARCH_SOURCES.map((source) => {
|
||||
const count = tabCounts.get(source) ?? 0
|
||||
return (
|
||||
<SearchTab key={source} $active={searchTab === source} onClick={() => setSearchTab(source)}>
|
||||
{source.replace('.dev', '').replace('.ai', '')}
|
||||
{count > 0 ? <TabCount>{count}</TabCount> : null}
|
||||
</SearchTab>
|
||||
)
|
||||
})}
|
||||
</SearchTabs>
|
||||
<SearchResultsScroll>
|
||||
{searching ? (
|
||||
<DropdownLoading>
|
||||
<Spin size="small" />
|
||||
</DropdownLoading>
|
||||
) : null}
|
||||
{!searching && searchQuery && filteredResults.length === 0 ? (
|
||||
<DropdownEmpty>{t('settings.skills.noResults')}</DropdownEmpty>
|
||||
) : null}
|
||||
{filteredResults.map((result) => (
|
||||
<SearchResultRow
|
||||
key={`${result.sourceRegistry}:${result.slug}`}
|
||||
result={result}
|
||||
isInstalling={isInstalling}
|
||||
onInstall={handleInstall}
|
||||
onPreview={setPreviewResult}
|
||||
installLabel={t('settings.skills.install')}
|
||||
/>
|
||||
))}
|
||||
</SearchResultsScroll>
|
||||
</SearchDropdown>
|
||||
) : null}
|
||||
</SearchInputWrapper>
|
||||
) : (
|
||||
<Tooltip title={t('settings.skills.searchRegistryTitle')}>
|
||||
<SearchIconButton onClick={() => setSearchOpen(true)}>
|
||||
<Search size={16} />
|
||||
</SearchIconButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
<SearchInputWrapper>
|
||||
<Input
|
||||
ref={searchInputRef as React.Ref<any>}
|
||||
placeholder={t('settings.skills.searchPlaceholder')}
|
||||
value={searchQuery}
|
||||
onChange={(e) => handleSearch(e.target.value)}
|
||||
suffix={searchQuery ? <X size={12} style={CLOSE_ICON_STYLE} onClick={handleCloseSearch} /> : <span />}
|
||||
prefix={<Search size={12} />}
|
||||
/>
|
||||
{searching || results.length > 0 || (searchQuery && !searching) ? (
|
||||
<SearchDropdown>
|
||||
<SearchTabs>
|
||||
{SEARCH_SOURCES.map((source) => {
|
||||
const count = tabCounts.get(source) ?? 0
|
||||
return (
|
||||
<SearchTab key={source} $active={searchTab === source} onClick={() => setSearchTab(source)}>
|
||||
{source.replace('.dev', '').replace('.ai', '')}
|
||||
{count > 0 ? <TabCount>{count}</TabCount> : null}
|
||||
</SearchTab>
|
||||
)
|
||||
})}
|
||||
</SearchTabs>
|
||||
<SearchResultsScroll>
|
||||
{searching ? (
|
||||
<DropdownLoading>
|
||||
<Spin size="small" />
|
||||
</DropdownLoading>
|
||||
) : null}
|
||||
{!searching && searchQuery && filteredResults.length === 0 ? (
|
||||
<DropdownEmpty>{t('settings.skills.noResults')}</DropdownEmpty>
|
||||
) : null}
|
||||
{filteredResults.map((result) => (
|
||||
<SearchResultRow
|
||||
key={`${result.sourceRegistry}:${result.slug}`}
|
||||
result={result}
|
||||
isInstalling={isInstalling}
|
||||
onInstall={handleInstall}
|
||||
onPreview={setPreviewResult}
|
||||
installLabel={t('settings.skills.install')}
|
||||
/>
|
||||
))}
|
||||
</SearchResultsScroll>
|
||||
</SearchDropdown>
|
||||
) : null}
|
||||
</SearchInputWrapper>
|
||||
</TopBarRight>
|
||||
</TopBar>
|
||||
|
||||
@@ -934,18 +915,6 @@ const DetailMeta = styled.div`
|
||||
gap: 6px;
|
||||
`
|
||||
|
||||
const SearchIconButton = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
padding: 4px;
|
||||
border-radius: 4px;
|
||||
color: var(--color-text-2);
|
||||
&:hover {
|
||||
background: var(--color-background-soft);
|
||||
}
|
||||
`
|
||||
|
||||
const SearchInputWrapper = styled.div`
|
||||
position: relative;
|
||||
width: 280px;
|
||||
|
||||
Reference in New Issue
Block a user