Compare commits

..

1 Commits

Author SHA1 Message Date
kangfenmao
5d0841899b fix(conversation): set context length to 1 for gemini-2.5-flash-image model
Set context count to 1 for gemini-2.5-flash-image (Nano Banana) model to fix continuous conversation failures. This ensures the model only receives the most recent message, preventing context-related errors.

Changes:
- Add isGeminiGenerateImageModel utility to identify Nano Banana models
- Override contextCount to 1 when using gemini-2.5-flash-image
- Remove the previous +2 offset from contextCount calculation
2025-11-18 13:52:57 +08:00
37 changed files with 379 additions and 1018 deletions

View File

@@ -0,0 +1,26 @@
diff --git a/dist/index.js b/dist/index.js
index ff305b112779b718f21a636a27b1196125a332d9..cf32ff5086d4d9e56f8fe90c98724559083bafc3 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -471,7 +471,7 @@ function convertToGoogleGenerativeAIMessages(prompt, options) {
// src/get-model-path.ts
function getModelPath(modelId) {
- return modelId.includes("/") ? modelId : `models/${modelId}`;
+ return modelId.includes("models/") ? modelId : `models/${modelId}`;
}
// src/google-generative-ai-options.ts
diff --git a/dist/index.mjs b/dist/index.mjs
index 57659290f1cec74878a385626ad75b2a4d5cd3fc..d04e5927ec3725b6ffdb80868bfa1b5a48849537 100644
--- a/dist/index.mjs
+++ b/dist/index.mjs
@@ -477,7 +477,7 @@ function convertToGoogleGenerativeAIMessages(prompt, options) {
// src/get-model-path.ts
function getModelPath(modelId) {
- return modelId.includes("/") ? modelId : `models/${modelId}`;
+ return modelId.includes("models/") ? modelId : `models/${modelId}`;
}
// src/google-generative-ai-options.ts

View File

@@ -1,152 +0,0 @@
diff --git a/dist/index.js b/dist/index.js
index c2ef089c42e13a8ee4a833899a415564130e5d79..75efa7baafb0f019fb44dd50dec1641eee8879e7 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -471,7 +471,7 @@ function convertToGoogleGenerativeAIMessages(prompt, options) {
// src/get-model-path.ts
function getModelPath(modelId) {
- return modelId.includes("/") ? modelId : `models/${modelId}`;
+ return modelId.includes("models/") ? modelId : `models/${modelId}`;
}
// src/google-generative-ai-options.ts
diff --git a/dist/index.mjs b/dist/index.mjs
index d75c0cc13c41192408c1f3f2d29d76a7bffa6268..ada730b8cb97d9b7d4cb32883a1d1ff416404d9b 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
diff --git a/dist/internal/index.js b/dist/internal/index.js
index 277cac8dc734bea2fb4f3e9a225986b402b24f48..bb704cd79e602eb8b0cee1889e42497d59ccdb7a 100644
--- a/dist/internal/index.js
+++ b/dist/internal/index.js
@@ -432,7 +432,15 @@ function prepareTools({
var _a;
tools = (tools == null ? void 0 : tools.length) ? tools : void 0;
const toolWarnings = [];
- const isGemini2 = modelId.includes("gemini-2");
+ // These changes could be safely removed when @ai-sdk/google v3 released.
+ const isLatest = (
+ [
+ 'gemini-flash-latest',
+ 'gemini-flash-lite-latest',
+ 'gemini-pro-latest',
+ ]
+ ).some(id => id === modelId);
+ const isGemini2OrNewer = modelId.includes("gemini-2") || modelId.includes("gemini-3") || isLatest;
const supportsDynamicRetrieval = modelId.includes("gemini-1.5-flash") && !modelId.includes("-8b");
const supportsFileSearch = modelId.includes("gemini-2.5");
if (tools == null) {
@@ -458,7 +466,7 @@ function prepareTools({
providerDefinedTools.forEach((tool) => {
switch (tool.id) {
case "google.google_search":
- if (isGemini2) {
+ if (isGemini2OrNewer) {
googleTools2.push({ googleSearch: {} });
} else if (supportsDynamicRetrieval) {
googleTools2.push({
@@ -474,7 +482,7 @@ function prepareTools({
}
break;
case "google.url_context":
- if (isGemini2) {
+ if (isGemini2OrNewer) {
googleTools2.push({ urlContext: {} });
} else {
toolWarnings.push({
@@ -485,7 +493,7 @@ function prepareTools({
}
break;
case "google.code_execution":
- if (isGemini2) {
+ if (isGemini2OrNewer) {
googleTools2.push({ codeExecution: {} });
} else {
toolWarnings.push({
@@ -507,7 +515,7 @@ function prepareTools({
}
break;
case "google.vertex_rag_store":
- if (isGemini2) {
+ if (isGemini2OrNewer) {
googleTools2.push({
retrieval: {
vertex_rag_store: {
diff --git a/dist/internal/index.mjs b/dist/internal/index.mjs
index 03b7cc591be9b58bcc2e775a96740d9f98862a10..347d2c12e1cee79f0f8bb258f3844fb0522a6485 100644
--- a/dist/internal/index.mjs
+++ b/dist/internal/index.mjs
@@ -424,7 +424,15 @@ function prepareTools({
var _a;
tools = (tools == null ? void 0 : tools.length) ? tools : void 0;
const toolWarnings = [];
- const isGemini2 = modelId.includes("gemini-2");
+ // These changes could be safely removed when @ai-sdk/google v3 released.
+ const isLatest = (
+ [
+ 'gemini-flash-latest',
+ 'gemini-flash-lite-latest',
+ 'gemini-pro-latest',
+ ]
+ ).some(id => id === modelId);
+ const isGemini2OrNewer = modelId.includes("gemini-2") || modelId.includes("gemini-3") || isLatest;
const supportsDynamicRetrieval = modelId.includes("gemini-1.5-flash") && !modelId.includes("-8b");
const supportsFileSearch = modelId.includes("gemini-2.5");
if (tools == null) {
@@ -450,7 +458,7 @@ function prepareTools({
providerDefinedTools.forEach((tool) => {
switch (tool.id) {
case "google.google_search":
- if (isGemini2) {
+ if (isGemini2OrNewer) {
googleTools2.push({ googleSearch: {} });
} else if (supportsDynamicRetrieval) {
googleTools2.push({
@@ -466,7 +474,7 @@ function prepareTools({
}
break;
case "google.url_context":
- if (isGemini2) {
+ if (isGemini2OrNewer) {
googleTools2.push({ urlContext: {} });
} else {
toolWarnings.push({
@@ -477,7 +485,7 @@ function prepareTools({
}
break;
case "google.code_execution":
- if (isGemini2) {
+ if (isGemini2OrNewer) {
googleTools2.push({ codeExecution: {} });
} else {
toolWarnings.push({
@@ -499,7 +507,7 @@ function prepareTools({
}
break;
case "google.vertex_rag_store":
- if (isGemini2) {
+ if (isGemini2OrNewer) {
googleTools2.push({
retrieval: {
vertex_rag_store: {
@@ -1434,9 +1442,7 @@ var googleTools = {
vertexRagStore
};
export {
- GoogleGenerativeAILanguageModel,
getGroundingMetadataSchema,
- getUrlContextMetadataSchema,
- googleTools
+ getUrlContextMetadataSchema, GoogleGenerativeAILanguageModel, googleTools
};
//# sourceMappingURL=index.mjs.map
\ No newline at end of file

View File

@@ -74,15 +74,14 @@
"format:check": "biome format && biome lint", "format:check": "biome format && biome lint",
"prepare": "git config blame.ignoreRevsFile .git-blame-ignore-revs && husky", "prepare": "git config blame.ignoreRevsFile .git-blame-ignore-revs && husky",
"claude": "dotenv -e .env -- claude", "claude": "dotenv -e .env -- claude",
"release:aicore:alpha": "yarn workspace @cherrystudio/ai-core version prerelease --preid alpha --immediate && yarn workspace @cherrystudio/ai-core build && yarn workspace @cherrystudio/ai-core npm publish --tag alpha --access public", "release:aicore: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 --preid beta --immediate && yarn workspace @cherrystudio/ai-core build && yarn workspace @cherrystudio/ai-core npm publish --tag beta --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 build && yarn workspace @cherrystudio/ai-core npm publish --access public", "release:aicore": "yarn workspace @cherrystudio/ai-core version patch --immediate && yarn workspace @cherrystudio/ai-core npm publish --access public"
"release:ai-sdk-provider": "yarn workspace @cherrystudio/ai-sdk-provider version patch --immediate && yarn workspace @cherrystudio/ai-sdk-provider build && yarn workspace @cherrystudio/ai-sdk-provider npm publish --access public"
}, },
"dependencies": { "dependencies": {
"@anthropic-ai/claude-agent-sdk": "patch:@anthropic-ai/claude-agent-sdk@npm%3A0.1.30#~/.yarn/patches/@anthropic-ai-claude-agent-sdk-npm-0.1.30-b50a299674.patch", "@anthropic-ai/claude-agent-sdk": "patch:@anthropic-ai/claude-agent-sdk@npm%3A0.1.30#~/.yarn/patches/@anthropic-ai-claude-agent-sdk-npm-0.1.30-b50a299674.patch",
"@libsql/client": "0.15.15", "@libsql/client": "0.14.0",
"@libsql/win32-x64-msvc": "^0.5.22", "@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", "@napi-rs/system-ocr": "patch:@napi-rs/system-ocr@npm%3A1.0.2#~/.yarn/patches/@napi-rs-system-ocr-npm-1.0.2-59e7a78e8b.patch",
"@paymoapp/electron-shutdown-handler": "^1.1.2", "@paymoapp/electron-shutdown-handler": "^1.1.2",
"@strongtz/win32-arm64-msvc": "^0.4.7", "@strongtz/win32-arm64-msvc": "^0.4.7",
@@ -112,11 +111,10 @@
"@ai-sdk/anthropic": "^2.0.44", "@ai-sdk/anthropic": "^2.0.44",
"@ai-sdk/cerebras": "^1.0.31", "@ai-sdk/cerebras": "^1.0.31",
"@ai-sdk/gateway": "^2.0.9", "@ai-sdk/gateway": "^2.0.9",
"@ai-sdk/google": "patch:@ai-sdk/google@npm%3A2.0.36#~/.yarn/patches/@ai-sdk-google-npm-2.0.36-6f3cc06026.patch", "@ai-sdk/google": "^2.0.32",
"@ai-sdk/google-vertex": "^3.0.68", "@ai-sdk/google-vertex": "^3.0.62",
"@ai-sdk/huggingface": "patch:@ai-sdk/huggingface@npm%3A0.0.8#~/.yarn/patches/@ai-sdk-huggingface-npm-0.0.8-d4d0aaac93.patch", "@ai-sdk/huggingface": "patch:@ai-sdk/huggingface@npm%3A0.0.8#~/.yarn/patches/@ai-sdk-huggingface-npm-0.0.8-d4d0aaac93.patch",
"@ai-sdk/mistral": "^2.0.23", "@ai-sdk/mistral": "^2.0.23",
"@ai-sdk/openai": "patch:@ai-sdk/openai@npm%3A2.0.64#~/.yarn/patches/@ai-sdk-openai-npm-2.0.64-48f99f5bf3.patch",
"@ai-sdk/perplexity": "^2.0.17", "@ai-sdk/perplexity": "^2.0.17",
"@ant-design/v5-patch-for-react-19": "^1.0.3", "@ant-design/v5-patch-for-react-19": "^1.0.3",
"@anthropic-ai/sdk": "^0.41.0", "@anthropic-ai/sdk": "^0.41.0",
@@ -125,7 +123,7 @@
"@aws-sdk/client-bedrock-runtime": "^3.910.0", "@aws-sdk/client-bedrock-runtime": "^3.910.0",
"@aws-sdk/client-s3": "^3.910.0", "@aws-sdk/client-s3": "^3.910.0",
"@biomejs/biome": "2.2.4", "@biomejs/biome": "2.2.4",
"@cherrystudio/ai-core": "workspace:^1.0.9", "@cherrystudio/ai-core": "workspace:^1.0.0-alpha.18",
"@cherrystudio/embedjs": "^0.1.31", "@cherrystudio/embedjs": "^0.1.31",
"@cherrystudio/embedjs-libsql": "^0.1.31", "@cherrystudio/embedjs-libsql": "^0.1.31",
"@cherrystudio/embedjs-loader-csv": "^0.1.31", "@cherrystudio/embedjs-loader-csv": "^0.1.31",
@@ -386,10 +384,10 @@
"@codemirror/lint": "6.8.5", "@codemirror/lint": "6.8.5",
"@codemirror/view": "6.38.1", "@codemirror/view": "6.38.1",
"@langchain/core@npm:^0.3.26": "patch:@langchain/core@npm%3A1.0.2#~/.yarn/patches/@langchain-core-npm-1.0.2-183ef83fe4.patch", "@langchain/core@npm:^0.3.26": "patch:@langchain/core@npm%3A1.0.2#~/.yarn/patches/@langchain-core-npm-1.0.2-183ef83fe4.patch",
"@libsql/client": "0.15.15",
"atomically@npm:^1.7.0": "patch:atomically@npm%3A1.7.0#~/.yarn/patches/atomically-npm-1.7.0-e742e5293b.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", "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", "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.24.0", "node-abi": "4.24.0",
"openai@npm:^4.77.0": "npm:@cherrystudio/openai@6.5.0", "openai@npm:^4.77.0": "npm:@cherrystudio/openai@6.5.0",
"openai@npm:^4.87.3": "npm:@cherrystudio/openai@6.5.0", "openai@npm:^4.87.3": "npm:@cherrystudio/openai@6.5.0",
@@ -412,7 +410,7 @@
"@langchain/openai@npm:>=0.2.0 <0.7.0": "patch:@langchain/openai@npm%3A1.0.0#~/.yarn/patches/@langchain-openai-npm-1.0.0-474d0ad9d4.patch", "@langchain/openai@npm:>=0.2.0 <0.7.0": "patch:@langchain/openai@npm%3A1.0.0#~/.yarn/patches/@langchain-openai-npm-1.0.0-474d0ad9d4.patch",
"@ai-sdk/openai@npm:2.0.64": "patch:@ai-sdk/openai@npm%3A2.0.64#~/.yarn/patches/@ai-sdk-openai-npm-2.0.64-48f99f5bf3.patch", "@ai-sdk/openai@npm:2.0.64": "patch:@ai-sdk/openai@npm%3A2.0.64#~/.yarn/patches/@ai-sdk-openai-npm-2.0.64-48f99f5bf3.patch",
"@ai-sdk/openai@npm:^2.0.42": "patch:@ai-sdk/openai@npm%3A2.0.64#~/.yarn/patches/@ai-sdk-openai-npm-2.0.64-48f99f5bf3.patch", "@ai-sdk/openai@npm:^2.0.42": "patch:@ai-sdk/openai@npm%3A2.0.64#~/.yarn/patches/@ai-sdk-openai-npm-2.0.64-48f99f5bf3.patch",
"@ai-sdk/google@npm:2.0.36": "patch:@ai-sdk/google@npm%3A2.0.36#~/.yarn/patches/@ai-sdk-google-npm-2.0.36-6f3cc06026.patch" "@ai-sdk/google@npm:2.0.31": "patch:@ai-sdk/google@npm%3A2.0.31#~/.yarn/patches/@ai-sdk-google-npm-2.0.31-b0de047210.patch"
}, },
"packageManager": "yarn@4.9.1", "packageManager": "yarn@4.9.1",
"lint-staged": { "lint-staged": {

View File

@@ -1,6 +1,6 @@
{ {
"name": "@cherrystudio/ai-sdk-provider", "name": "@cherrystudio/ai-sdk-provider",
"version": "0.1.2", "version": "0.1.0",
"description": "Cherry Studio AI SDK provider bundle with CherryIN routing.", "description": "Cherry Studio AI SDK provider bundle with CherryIN routing.",
"keywords": [ "keywords": [
"ai-sdk", "ai-sdk",

View File

@@ -71,7 +71,7 @@ Cherry Studio AI Core 是一个基于 Vercel AI SDK 的统一 AI Provider 接口
## 安装 ## 安装
```bash ```bash
npm install @cherrystudio/ai-core ai @ai-sdk/google @ai-sdk/openai npm install @cherrystudio/ai-core ai
``` ```
### React Native ### React Native

View File

@@ -1,6 +1,6 @@
{ {
"name": "@cherrystudio/ai-core", "name": "@cherrystudio/ai-core",
"version": "1.0.9", "version": "1.0.1",
"description": "Cherry Studio AI Core - Unified AI Provider Interface Based on Vercel AI SDK", "description": "Cherry Studio AI Core - Unified AI Provider Interface Based on Vercel AI SDK",
"main": "dist/index.js", "main": "dist/index.js",
"module": "dist/index.mjs", "module": "dist/index.mjs",
@@ -33,19 +33,19 @@
}, },
"homepage": "https://github.com/CherryHQ/cherry-studio#readme", "homepage": "https://github.com/CherryHQ/cherry-studio#readme",
"peerDependencies": { "peerDependencies": {
"@ai-sdk/google": "^2.0.36",
"@ai-sdk/openai": "^2.0.64",
"@cherrystudio/ai-sdk-provider": "^0.1.2",
"ai": "^5.0.26" "ai": "^5.0.26"
}, },
"dependencies": { "dependencies": {
"@ai-sdk/anthropic": "^2.0.43", "@ai-sdk/anthropic": "^2.0.43",
"@ai-sdk/azure": "^2.0.66", "@ai-sdk/azure": "^2.0.66",
"@ai-sdk/deepseek": "^1.0.27", "@ai-sdk/deepseek": "^1.0.27",
"@ai-sdk/google": "patch:@ai-sdk/google@npm%3A2.0.31#~/.yarn/patches/@ai-sdk-google-npm-2.0.31-b0de047210.patch",
"@ai-sdk/openai": "patch:@ai-sdk/openai@npm%3A2.0.64#~/.yarn/patches/@ai-sdk-openai-npm-2.0.64-48f99f5bf3.patch",
"@ai-sdk/openai-compatible": "^1.0.26", "@ai-sdk/openai-compatible": "^1.0.26",
"@ai-sdk/provider": "^2.0.0", "@ai-sdk/provider": "^2.0.0",
"@ai-sdk/provider-utils": "^3.0.16", "@ai-sdk/provider-utils": "^3.0.16",
"@ai-sdk/xai": "^2.0.31", "@ai-sdk/xai": "^2.0.31",
"@cherrystudio/ai-sdk-provider": "workspace:*",
"zod": "^4.1.5" "zod": "^4.1.5"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -4,7 +4,12 @@
*/ */
export const BUILT_IN_PLUGIN_PREFIX = 'built-in:' export const BUILT_IN_PLUGIN_PREFIX = 'built-in:'
export * from './googleToolsPlugin' export { googleToolsPlugin } from './googleToolsPlugin'
export * from './toolUsePlugin/promptToolUsePlugin' export { createLoggingPlugin } from './logging'
export * from './toolUsePlugin/type' export { createPromptToolUsePlugin } from './toolUsePlugin/promptToolUsePlugin'
export * from './webSearchPlugin' export type {
PromptToolUseConfig,
ToolUseRequestContext,
ToolUseResult
} from './toolUsePlugin/type'
export { webSearchPlugin, type WebSearchPluginConfig } from './webSearchPlugin'

View File

@@ -32,7 +32,7 @@ export const webSearchPlugin = (config: WebSearchPluginConfig = DEFAULT_WEB_SEAR
}) })
// 导出类型定义供开发者使用 // 导出类型定义供开发者使用
export * from './helper' export type { WebSearchPluginConfig, WebSearchToolOutputSchema } from './helper'
// 默认导出 // 默认导出
export default webSearchPlugin export default webSearchPlugin

View File

@@ -44,7 +44,7 @@ export {
// ==================== 基础数据和类型 ==================== // ==================== 基础数据和类型 ====================
// 基础Provider数据源 // 基础Provider数据源
export { baseProviderIds, baseProviders, isBaseProvider } from './schemas' export { baseProviderIds, baseProviders } from './schemas'
// 类型定义和Schema // 类型定义和Schema
export type { export type {

View File

@@ -7,6 +7,7 @@ import { createAzure } from '@ai-sdk/azure'
import { type AzureOpenAIProviderSettings } from '@ai-sdk/azure' import { type AzureOpenAIProviderSettings } from '@ai-sdk/azure'
import { createDeepSeek } from '@ai-sdk/deepseek' import { createDeepSeek } from '@ai-sdk/deepseek'
import { createGoogleGenerativeAI } from '@ai-sdk/google' import { createGoogleGenerativeAI } from '@ai-sdk/google'
import { createHuggingFace } from '@ai-sdk/huggingface'
import { createOpenAI, type OpenAIProviderSettings } from '@ai-sdk/openai' import { createOpenAI, type OpenAIProviderSettings } from '@ai-sdk/openai'
import { createOpenAICompatible } from '@ai-sdk/openai-compatible' import { createOpenAICompatible } from '@ai-sdk/openai-compatible'
import type { LanguageModelV2 } from '@ai-sdk/provider' import type { LanguageModelV2 } from '@ai-sdk/provider'
@@ -32,7 +33,8 @@ export const baseProviderIds = [
'deepseek', 'deepseek',
'openrouter', 'openrouter',
'cherryin', 'cherryin',
'cherryin-chat' 'cherryin-chat',
'huggingface'
] as const ] as const
/** /**
@@ -156,6 +158,12 @@ export const baseProviders = [
}) })
}, },
supportsImageGeneration: true supportsImageGeneration: true
},
{
id: 'huggingface',
name: 'HuggingFace',
creator: createHuggingFace,
supportsImageGeneration: true
} }
] as const satisfies BaseProvider[] ] as const satisfies BaseProvider[]

View File

@@ -41,7 +41,6 @@ export enum IpcChannel {
App_SetFullScreen = 'app:set-full-screen', App_SetFullScreen = 'app:set-full-screen',
App_IsFullScreen = 'app:is-full-screen', App_IsFullScreen = 'app:is-full-screen',
App_GetSystemFonts = 'app:get-system-fonts', App_GetSystemFonts = 'app:get-system-fonts',
APP_CrashRenderProcess = 'app:crash-render-process',
App_MacIsProcessTrusted = 'app:mac-is-process-trusted', App_MacIsProcessTrusted = 'app:mac-is-process-trusted',
App_MacRequestProcessTrust = 'app:mac-request-process-trust', App_MacRequestProcessTrust = 'app:mac-request-process-trust',

View File

@@ -8,7 +8,7 @@ import '@main/config'
import { loggerService } from '@logger' import { loggerService } from '@logger'
import { electronApp, optimizer } from '@electron-toolkit/utils' import { electronApp, optimizer } from '@electron-toolkit/utils'
import { replaceDevtoolsFont } from '@main/utils/windowUtil' import { replaceDevtoolsFont } from '@main/utils/windowUtil'
import { app, crashReporter } from 'electron' import { app } from 'electron'
import installExtension, { REACT_DEVELOPER_TOOLS, REDUX_DEVTOOLS } from 'electron-devtools-installer' import installExtension, { REACT_DEVELOPER_TOOLS, REDUX_DEVTOOLS } from 'electron-devtools-installer'
import { isDev, isLinux, isWin } from './constant' import { isDev, isLinux, isWin } from './constant'
@@ -37,14 +37,6 @@ import { initWebviewHotkeys } from './services/WebviewService'
const logger = loggerService.withContext('MainEntry') const logger = loggerService.withContext('MainEntry')
// enable local crash reports
crashReporter.start({
companyName: 'CherryHQ',
productName: 'CherryStudio',
submitURL: '',
uploadToServer: false
})
/** /**
* Disable hardware acceleration if setting is enabled * Disable hardware acceleration if setting is enabled
*/ */

View File

@@ -1038,8 +1038,4 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
ipcMain.handle(IpcChannel.WebSocket_Status, WebSocketService.getStatus) ipcMain.handle(IpcChannel.WebSocket_Status, WebSocketService.getStatus)
ipcMain.handle(IpcChannel.WebSocket_SendFile, WebSocketService.sendFile) ipcMain.handle(IpcChannel.WebSocket_SendFile, WebSocketService.sendFile)
ipcMain.handle(IpcChannel.WebSocket_GetAllCandidates, WebSocketService.getAllCandidates) ipcMain.handle(IpcChannel.WebSocket_GetAllCandidates, WebSocketService.getAllCandidates)
ipcMain.handle(IpcChannel.APP_CrashRenderProcess, () => {
mainWindow.webContents.forcefullyCrashRenderer()
})
} }

View File

@@ -25,7 +25,7 @@ describe('stripLocalCommandTags', () => {
describe('Claude → AiSDK transform', () => { describe('Claude → AiSDK transform', () => {
it('handles tool call streaming lifecycle', () => { it('handles tool call streaming lifecycle', () => {
const state = new ClaudeStreamState({ agentSessionId: baseStreamMetadata.session_id }) const state = new ClaudeStreamState()
const parts: ReturnType<typeof transformSDKMessageToStreamParts>[number][] = [] const parts: ReturnType<typeof transformSDKMessageToStreamParts>[number][] = []
const messages: SDKMessage[] = [ const messages: SDKMessage[] = [
@@ -182,14 +182,14 @@ describe('Claude → AiSDK transform', () => {
(typeof parts)[number], (typeof parts)[number],
{ type: 'tool-result' } { type: 'tool-result' }
> >
expect(toolResult.toolCallId).toBe('session-123:tool-1') expect(toolResult.toolCallId).toBe('tool-1')
expect(toolResult.toolName).toBe('Bash') expect(toolResult.toolName).toBe('Bash')
expect(toolResult.input).toEqual({ command: 'ls' }) expect(toolResult.input).toEqual({ command: 'ls' })
expect(toolResult.output).toBe('ok') expect(toolResult.output).toBe('ok')
}) })
it('handles streaming text completion', () => { it('handles streaming text completion', () => {
const state = new ClaudeStreamState({ agentSessionId: baseStreamMetadata.session_id }) const state = new ClaudeStreamState()
const parts: ReturnType<typeof transformSDKMessageToStreamParts>[number][] = [] const parts: ReturnType<typeof transformSDKMessageToStreamParts>[number][] = []
const messages: SDKMessage[] = [ const messages: SDKMessage[] = [

View File

@@ -10,21 +10,8 @@
* Every Claude turn gets its own instance. `resetStep` should be invoked once the finish event has * Every Claude turn gets its own instance. `resetStep` should be invoked once the finish event has
* been emitted to avoid leaking state into the next turn. * been emitted to avoid leaking state into the next turn.
*/ */
import { loggerService } from '@logger'
import type { FinishReason, LanguageModelUsage, ProviderMetadata } from 'ai' import type { FinishReason, LanguageModelUsage, ProviderMetadata } from 'ai'
/**
* Builds a namespaced tool call ID by combining session ID with raw tool call ID.
* This ensures tool calls from different sessions don't conflict even if they have
* the same raw ID from the SDK.
*
* @param sessionId - The agent session ID
* @param rawToolCallId - The raw tool call ID from SDK (e.g., "WebFetch_0")
*/
export function buildNamespacedToolCallId(sessionId: string, rawToolCallId: string): string {
return `${sessionId}:${rawToolCallId}`
}
/** /**
* Shared fields for every block that Claude can stream (text, reasoning, tool). * Shared fields for every block that Claude can stream (text, reasoning, tool).
*/ */
@@ -47,7 +34,6 @@ type ReasoningBlockState = BaseBlockState & {
type ToolBlockState = BaseBlockState & { type ToolBlockState = BaseBlockState & {
kind: 'tool' kind: 'tool'
toolCallId: string toolCallId: string
rawToolCallId: string
toolName: string toolName: string
inputBuffer: string inputBuffer: string
providerMetadata?: ProviderMetadata providerMetadata?: ProviderMetadata
@@ -62,17 +48,12 @@ type PendingUsageState = {
} }
type PendingToolCall = { type PendingToolCall = {
rawToolCallId: string
toolCallId: string toolCallId: string
toolName: string toolName: string
input: unknown input: unknown
providerMetadata?: ProviderMetadata providerMetadata?: ProviderMetadata
} }
type ClaudeStreamStateOptions = {
agentSessionId: string
}
/** /**
* Tracks the lifecycle of Claude streaming blocks (text, thinking, tool calls) * Tracks the lifecycle of Claude streaming blocks (text, thinking, tool calls)
* across individual websocket events. The transformer relies on this class to * across individual websocket events. The transformer relies on this class to
@@ -80,20 +61,12 @@ type ClaudeStreamStateOptions = {
* usage/finish metadata once Anthropic closes a message. * usage/finish metadata once Anthropic closes a message.
*/ */
export class ClaudeStreamState { export class ClaudeStreamState {
private logger
private readonly agentSessionId: string
private blocksByIndex = new Map<number, BlockState>() private blocksByIndex = new Map<number, BlockState>()
private toolIndexByNamespacedId = new Map<string, number>() private toolIndexById = new Map<string, number>()
private pendingUsage: PendingUsageState = {} private pendingUsage: PendingUsageState = {}
private pendingToolCalls = new Map<string, PendingToolCall>() private pendingToolCalls = new Map<string, PendingToolCall>()
private stepActive = false private stepActive = false
constructor(options: ClaudeStreamStateOptions) {
this.logger = loggerService.withContext('ClaudeStreamState')
this.agentSessionId = options.agentSessionId
this.logger.silly('ClaudeStreamState', options)
}
/** Marks the beginning of a new AiSDK step. */ /** Marks the beginning of a new AiSDK step. */
beginStep(): void { beginStep(): void {
this.stepActive = true this.stepActive = true
@@ -131,21 +104,19 @@ export class ClaudeStreamState {
/** Caches tool metadata so subsequent input deltas and results can find it. */ /** Caches tool metadata so subsequent input deltas and results can find it. */
openToolBlock( openToolBlock(
index: number, index: number,
params: { rawToolCallId: string; toolName: string; providerMetadata?: ProviderMetadata } params: { toolCallId: string; toolName: string; providerMetadata?: ProviderMetadata }
): ToolBlockState { ): ToolBlockState {
const toolCallId = buildNamespacedToolCallId(this.agentSessionId, params.rawToolCallId)
const block: ToolBlockState = { const block: ToolBlockState = {
kind: 'tool', kind: 'tool',
id: toolCallId, id: params.toolCallId,
index, index,
toolCallId, toolCallId: params.toolCallId,
rawToolCallId: params.rawToolCallId,
toolName: params.toolName, toolName: params.toolName,
inputBuffer: '', inputBuffer: '',
providerMetadata: params.providerMetadata providerMetadata: params.providerMetadata
} }
this.blocksByIndex.set(index, block) this.blocksByIndex.set(index, block)
this.toolIndexByNamespacedId.set(toolCallId, index) this.toolIndexById.set(params.toolCallId, index)
return block return block
} }
@@ -154,17 +125,13 @@ export class ClaudeStreamState {
} }
getToolBlockById(toolCallId: string): ToolBlockState | undefined { getToolBlockById(toolCallId: string): ToolBlockState | undefined {
const index = this.toolIndexByNamespacedId.get(toolCallId) const index = this.toolIndexById.get(toolCallId)
if (index === undefined) return undefined if (index === undefined) return undefined
const block = this.blocksByIndex.get(index) const block = this.blocksByIndex.get(index)
if (!block || block.kind !== 'tool') return undefined if (!block || block.kind !== 'tool') return undefined
return block return block
} }
getToolBlockByRawId(rawToolCallId: string): ToolBlockState | undefined {
return this.getToolBlockById(buildNamespacedToolCallId(this.agentSessionId, rawToolCallId))
}
/** Appends streamed text to a text block, returning the updated state when present. */ /** Appends streamed text to a text block, returning the updated state when present. */
appendTextDelta(index: number, text: string): TextBlockState | undefined { appendTextDelta(index: number, text: string): TextBlockState | undefined {
const block = this.blocksByIndex.get(index) const block = this.blocksByIndex.get(index)
@@ -191,12 +158,10 @@ export class ClaudeStreamState {
/** Records a tool call to be consumed once its result arrives from the user. */ /** Records a tool call to be consumed once its result arrives from the user. */
registerToolCall( registerToolCall(
rawToolCallId: string, toolCallId: string,
payload: { toolName: string; input: unknown; providerMetadata?: ProviderMetadata } payload: { toolName: string; input: unknown; providerMetadata?: ProviderMetadata }
): void { ): void {
const toolCallId = buildNamespacedToolCallId(this.agentSessionId, rawToolCallId) this.pendingToolCalls.set(toolCallId, {
this.pendingToolCalls.set(rawToolCallId, {
rawToolCallId,
toolCallId, toolCallId,
toolName: payload.toolName, toolName: payload.toolName,
input: payload.input, input: payload.input,
@@ -205,10 +170,10 @@ export class ClaudeStreamState {
} }
/** Retrieves and clears the buffered tool call metadata for the given id. */ /** Retrieves and clears the buffered tool call metadata for the given id. */
consumePendingToolCall(rawToolCallId: string): PendingToolCall | undefined { consumePendingToolCall(toolCallId: string): PendingToolCall | undefined {
const entry = this.pendingToolCalls.get(rawToolCallId) const entry = this.pendingToolCalls.get(toolCallId)
if (entry) { if (entry) {
this.pendingToolCalls.delete(rawToolCallId) this.pendingToolCalls.delete(toolCallId)
} }
return entry return entry
} }
@@ -218,12 +183,12 @@ export class ClaudeStreamState {
* completion so that downstream tool results can reference the original call. * completion so that downstream tool results can reference the original call.
*/ */
completeToolBlock(toolCallId: string, input: unknown, providerMetadata?: ProviderMetadata): void { completeToolBlock(toolCallId: string, input: unknown, providerMetadata?: ProviderMetadata): void {
const block = this.getToolBlockByRawId(toolCallId)
this.registerToolCall(toolCallId, { this.registerToolCall(toolCallId, {
toolName: block?.toolName ?? 'unknown', toolName: this.getToolBlockById(toolCallId)?.toolName ?? 'unknown',
input, input,
providerMetadata providerMetadata
}) })
const block = this.getToolBlockById(toolCallId)
if (block) { if (block) {
block.resolvedInput = input block.resolvedInput = input
} }
@@ -235,7 +200,7 @@ export class ClaudeStreamState {
if (!block) return undefined if (!block) return undefined
this.blocksByIndex.delete(index) this.blocksByIndex.delete(index)
if (block.kind === 'tool') { if (block.kind === 'tool') {
this.toolIndexByNamespacedId.delete(block.toolCallId) this.toolIndexById.delete(block.toolCallId)
} }
return block return block
} }
@@ -262,7 +227,7 @@ export class ClaudeStreamState {
/** Drops cached block metadata for the currently active message. */ /** Drops cached block metadata for the currently active message. */
resetBlocks(): void { resetBlocks(): void {
this.blocksByIndex.clear() this.blocksByIndex.clear()
this.toolIndexByNamespacedId.clear() this.toolIndexById.clear()
} }
/** Resets the entire step lifecycle after emitting a terminal frame. */ /** Resets the entire step lifecycle after emitting a terminal frame. */
@@ -271,10 +236,6 @@ export class ClaudeStreamState {
this.resetPendingUsage() this.resetPendingUsage()
this.stepActive = false this.stepActive = false
} }
getNamespacedToolCallId(rawToolCallId: string): string {
return buildNamespacedToolCallId(this.agentSessionId, rawToolCallId)
}
} }
export type { PendingToolCall } export type { PendingToolCall }

View File

@@ -13,7 +13,6 @@ import { app } from 'electron'
import type { GetAgentSessionResponse } from '../..' import type { GetAgentSessionResponse } from '../..'
import type { AgentServiceInterface, AgentStream, AgentStreamEvent } from '../../interfaces/AgentStreamInterface' import type { AgentServiceInterface, AgentStream, AgentStreamEvent } from '../../interfaces/AgentStreamInterface'
import { sessionService } from '../SessionService' import { sessionService } from '../SessionService'
import { buildNamespacedToolCallId } from './claude-stream-state'
import { promptForToolApproval } from './tool-permissions' import { promptForToolApproval } from './tool-permissions'
import { ClaudeStreamState, transformSDKMessageToStreamParts } from './transform' import { ClaudeStreamState, transformSDKMessageToStreamParts } from './transform'
@@ -151,10 +150,7 @@ class ClaudeCodeService implements AgentServiceInterface {
return { behavior: 'allow', updatedInput: input } return { behavior: 'allow', updatedInput: input }
} }
return promptForToolApproval(toolName, input, { return promptForToolApproval(toolName, input, options)
...options,
toolCallId: buildNamespacedToolCallId(session.id, options.toolUseID)
})
} }
// Build SDK options from parameters // Build SDK options from parameters
@@ -350,7 +346,7 @@ class ClaudeCodeService implements AgentServiceInterface {
const jsonOutput: SDKMessage[] = [] const jsonOutput: SDKMessage[] = []
let hasCompleted = false let hasCompleted = false
const startTime = Date.now() const startTime = Date.now()
const streamState = new ClaudeStreamState({ agentSessionId: sessionId }) const streamState = new ClaudeStreamState()
try { try {
for await (const message of query({ prompt: promptStream, options })) { for await (const message of query({ prompt: promptStream, options })) {

View File

@@ -37,7 +37,6 @@ type RendererPermissionRequestPayload = {
requestId: string requestId: string
toolName: string toolName: string
toolId: string toolId: string
toolCallId: string
description?: string description?: string
requiresPermissions: boolean requiresPermissions: boolean
input: Record<string, unknown> input: Record<string, unknown>
@@ -207,19 +206,10 @@ const ensureIpcHandlersRegistered = () => {
}) })
} }
type PromptForToolApprovalOptions = {
signal: AbortSignal
suggestions?: PermissionUpdate[]
// NOTICE: This ID is namespaced with session ID, not the raw SDK tool call ID.
// Format: `${sessionId}:${rawToolCallId}`, e.g., `session_123:WebFetch_0`
toolCallId: string
}
export async function promptForToolApproval( export async function promptForToolApproval(
toolName: string, toolName: string,
input: Record<string, unknown>, input: Record<string, unknown>,
options: PromptForToolApprovalOptions options?: { signal: AbortSignal; suggestions?: PermissionUpdate[] }
): Promise<PermissionResult> { ): Promise<PermissionResult> {
if (shouldAutoApproveTools) { if (shouldAutoApproveTools) {
logger.debug('promptForToolApproval auto-approving tool for test', { logger.debug('promptForToolApproval auto-approving tool for test', {
@@ -255,7 +245,6 @@ export async function promptForToolApproval(
logger.info('Requesting user approval for tool usage', { logger.info('Requesting user approval for tool usage', {
requestId, requestId,
toolName, toolName,
toolCallId: options.toolCallId,
description: toolMetadata?.description description: toolMetadata?.description
}) })
@@ -263,7 +252,6 @@ export async function promptForToolApproval(
requestId, requestId,
toolName, toolName,
toolId: toolMetadata?.id ?? toolName, toolId: toolMetadata?.id ?? toolName,
toolCallId: options.toolCallId,
description: toolMetadata?.description, description: toolMetadata?.description,
requiresPermissions: toolMetadata?.requirePermissions ?? false, requiresPermissions: toolMetadata?.requirePermissions ?? false,
input: sanitizedInput, input: sanitizedInput,
@@ -278,7 +266,6 @@ export async function promptForToolApproval(
logger.debug('Registering tool permission request', { logger.debug('Registering tool permission request', {
requestId, requestId,
toolName, toolName,
toolCallId: options.toolCallId,
requiresPermissions: requestPayload.requiresPermissions, requiresPermissions: requestPayload.requiresPermissions,
timeoutMs: TOOL_APPROVAL_TIMEOUT_MS, timeoutMs: TOOL_APPROVAL_TIMEOUT_MS,
suggestionCount: sanitizedSuggestions.length suggestionCount: sanitizedSuggestions.length
@@ -286,11 +273,7 @@ export async function promptForToolApproval(
return new Promise<PermissionResult>((resolve) => { return new Promise<PermissionResult>((resolve) => {
const timeout = setTimeout(() => { const timeout = setTimeout(() => {
logger.info('User tool permission request timed out', { logger.info('User tool permission request timed out', { requestId, toolName })
requestId,
toolName,
toolCallId: options.toolCallId
})
finalizeRequest(requestId, { behavior: 'deny', message: 'Timed out waiting for approval' }, 'timeout') finalizeRequest(requestId, { behavior: 'deny', message: 'Timed out waiting for approval' }, 'timeout')
}, TOOL_APPROVAL_TIMEOUT_MS) }, TOOL_APPROVAL_TIMEOUT_MS)
@@ -304,11 +287,7 @@ export async function promptForToolApproval(
if (options?.signal) { if (options?.signal) {
const abortListener = () => { const abortListener = () => {
logger.info('Tool permission request aborted before user responded', { logger.info('Tool permission request aborted before user responded', { requestId, toolName })
requestId,
toolName,
toolCallId: options.toolCallId
})
finalizeRequest(requestId, defaultDenyUpdate, 'aborted') finalizeRequest(requestId, defaultDenyUpdate, 'aborted')
} }

View File

@@ -243,10 +243,9 @@ function handleAssistantToolUse(
state: ClaudeStreamState, state: ClaudeStreamState,
chunks: AgentStreamPart[] chunks: AgentStreamPart[]
): void { ): void {
const toolCallId = state.getNamespacedToolCallId(block.id)
chunks.push({ chunks.push({
type: 'tool-call', type: 'tool-call',
toolCallId, toolCallId: block.id,
toolName: block.name, toolName: block.name,
input: block.input, input: block.input,
providerExecuted: true, providerExecuted: true,
@@ -332,11 +331,10 @@ function handleUserMessage(
if (block.type === 'tool_result') { if (block.type === 'tool_result') {
const toolResult = block as ToolResultContent const toolResult = block as ToolResultContent
const pendingCall = state.consumePendingToolCall(toolResult.tool_use_id) const pendingCall = state.consumePendingToolCall(toolResult.tool_use_id)
const toolCallId = pendingCall?.toolCallId ?? state.getNamespacedToolCallId(toolResult.tool_use_id)
if (toolResult.is_error) { if (toolResult.is_error) {
chunks.push({ chunks.push({
type: 'tool-error', type: 'tool-error',
toolCallId, toolCallId: toolResult.tool_use_id,
toolName: pendingCall?.toolName ?? 'unknown', toolName: pendingCall?.toolName ?? 'unknown',
input: pendingCall?.input, input: pendingCall?.input,
error: toolResult.content, error: toolResult.content,
@@ -345,7 +343,7 @@ function handleUserMessage(
} else { } else {
chunks.push({ chunks.push({
type: 'tool-result', type: 'tool-result',
toolCallId, toolCallId: toolResult.tool_use_id,
toolName: pendingCall?.toolName ?? 'unknown', toolName: pendingCall?.toolName ?? 'unknown',
input: pendingCall?.input, input: pendingCall?.input,
output: toolResult.content, output: toolResult.content,
@@ -516,7 +514,7 @@ function handleContentBlockStart(
} }
case 'tool_use': { case 'tool_use': {
const block = state.openToolBlock(index, { const block = state.openToolBlock(index, {
rawToolCallId: contentBlock.id, toolCallId: contentBlock.id,
toolName: contentBlock.name, toolName: contentBlock.name,
providerMetadata providerMetadata
}) })

View File

@@ -111,7 +111,6 @@ const api = {
setFullScreen: (value: boolean): Promise<void> => ipcRenderer.invoke(IpcChannel.App_SetFullScreen, value), setFullScreen: (value: boolean): Promise<void> => ipcRenderer.invoke(IpcChannel.App_SetFullScreen, value),
isFullScreen: (): Promise<boolean> => ipcRenderer.invoke(IpcChannel.App_IsFullScreen), isFullScreen: (): Promise<boolean> => ipcRenderer.invoke(IpcChannel.App_IsFullScreen),
getSystemFonts: (): Promise<string[]> => ipcRenderer.invoke(IpcChannel.App_GetSystemFonts), getSystemFonts: (): Promise<string[]> => ipcRenderer.invoke(IpcChannel.App_GetSystemFonts),
mockCrashRenderProcess: () => ipcRenderer.invoke(IpcChannel.APP_CrashRenderProcess),
mac: { mac: {
isProcessTrusted: (): Promise<boolean> => ipcRenderer.invoke(IpcChannel.App_MacIsProcessTrusted), isProcessTrusted: (): Promise<boolean> => ipcRenderer.invoke(IpcChannel.App_MacIsProcessTrusted),
requestProcessTrust: (): Promise<boolean> => ipcRenderer.invoke(IpcChannel.App_MacRequestProcessTrust) requestProcessTrust: (): Promise<boolean> => ipcRenderer.invoke(IpcChannel.App_MacRequestProcessTrust)

View File

@@ -14,3 +14,7 @@ export function isOpenRouterGeminiGenerateImageModel(model: Model, provider: Pro
provider.id === SystemProviderIds.openrouter provider.id === SystemProviderIds.openrouter
) )
} }
export function isGeminiGenerateImageModel(model: Model): boolean {
return model.id.includes('gemini-2.5-flash-image')
}

View File

@@ -99,6 +99,9 @@ export function buildProviderOptions(
serviceTier: serviceTierSetting serviceTier: serviceTierSetting
} }
break break
case 'huggingface':
providerSpecificOptions = buildOpenAIProviderOptions(assistant, model, capabilities)
break
case 'anthropic': case 'anthropic':
providerSpecificOptions = buildAnthropicProviderOptions(assistant, model, capabilities) providerSpecificOptions = buildAnthropicProviderOptions(assistant, model, capabilities)
break break
@@ -141,9 +144,6 @@ export function buildProviderOptions(
case 'bedrock': case 'bedrock':
providerSpecificOptions = buildBedrockProviderOptions(assistant, model, capabilities) providerSpecificOptions = buildBedrockProviderOptions(assistant, model, capabilities)
break break
case 'huggingface':
providerSpecificOptions = buildOpenAIProviderOptions(assistant, model, capabilities)
break
default: default:
// 对于其他 provider使用通用的构建逻辑 // 对于其他 provider使用通用的构建逻辑
providerSpecificOptions = { providerSpecificOptions = {
@@ -162,17 +162,13 @@ export function buildProviderOptions(
...getCustomParameters(assistant) ...getCustomParameters(assistant)
} }
let rawProviderKey = const rawProviderKey =
{ {
'google-vertex': 'google', 'google-vertex': 'google',
'google-vertex-anthropic': 'anthropic', 'google-vertex-anthropic': 'anthropic',
'ai-gateway': 'gateway' 'ai-gateway': 'gateway'
}[rawProviderId] || rawProviderId }[rawProviderId] || rawProviderId
if (rawProviderKey === 'cherryin') {
rawProviderKey = { gemini: 'google' }[actualProvider.type] || actualProvider.type
}
// 返回 AI Core SDK 要求的格式:{ 'providerId': providerOptions } // 返回 AI Core SDK 要求的格式:{ 'providerId': providerOptions }
return { return {
[rawProviderKey]: providerSpecificOptions [rawProviderKey]: providerSpecificOptions

View File

@@ -1,4 +1,5 @@
import { loggerService } from '@logger' import { loggerService } from '@logger'
import ClaudeIcon from '@renderer/assets/images/models/claude.png'
import { ErrorBoundary } from '@renderer/components/ErrorBoundary' import { ErrorBoundary } from '@renderer/components/ErrorBoundary'
import { TopView } from '@renderer/components/TopView' import { TopView } from '@renderer/components/TopView'
import { permissionModeCards } from '@renderer/config/agent' import { permissionModeCards } from '@renderer/config/agent'
@@ -8,6 +9,7 @@ import SelectAgentBaseModelButton from '@renderer/pages/home/components/SelectAg
import type { import type {
AddAgentForm, AddAgentForm,
AgentEntity, AgentEntity,
AgentType,
ApiModel, ApiModel,
BaseAgentForm, BaseAgentForm,
PermissionMode, PermissionMode,
@@ -15,22 +17,30 @@ import type {
UpdateAgentForm UpdateAgentForm
} from '@renderer/types' } from '@renderer/types'
import { AgentConfigurationSchema, isAgentType } from '@renderer/types' import { AgentConfigurationSchema, isAgentType } from '@renderer/types'
import { Button, Input, Modal, Select } from 'antd' import { Avatar, Button, Input, Modal, Select } from 'antd'
import { AlertTriangleIcon } from 'lucide-react' import { AlertTriangleIcon } from 'lucide-react'
import type { ChangeEvent, FormEvent } from 'react' import type { ChangeEvent, FormEvent } from 'react'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import styled from 'styled-components' import styled from 'styled-components'
import type { BaseOption } from './shared'
const { TextArea } = Input const { TextArea } = Input
const logger = loggerService.withContext('AddAgentPopup') const logger = loggerService.withContext('AddAgentPopup')
interface AgentTypeOption extends BaseOption {
type: 'type'
key: AgentEntity['type']
name: AgentEntity['name']
}
type AgentWithTools = AgentEntity & { tools?: Tool[] } type AgentWithTools = AgentEntity & { tools?: Tool[] }
const buildAgentForm = (existing?: AgentWithTools): BaseAgentForm => ({ const buildAgentForm = (existing?: AgentWithTools): BaseAgentForm => ({
type: existing?.type ?? 'claude-code', type: existing?.type ?? 'claude-code',
name: existing?.name ?? 'Agent', name: existing?.name ?? 'Claude Code',
description: existing?.description, description: existing?.description,
instructions: existing?.instructions, instructions: existing?.instructions,
model: existing?.model ?? '', model: existing?.model ?? '',
@@ -90,6 +100,54 @@ const PopupContainer: React.FC<Props> = ({ agent, afterSubmit, resolve }) => {
}) })
}, []) }, [])
// add supported agents type here.
const agentConfig = useMemo(
() =>
[
{
type: 'type',
key: 'claude-code',
label: 'Claude Code',
name: 'Claude Code',
avatar: ClaudeIcon
}
] as const satisfies AgentTypeOption[],
[]
)
const agentOptions = useMemo(
() =>
agentConfig.map((option) => ({
value: option.key,
label: (
<OptionWrapper>
<Avatar src={option.avatar} size={24} />
<span>{option.label}</span>
</OptionWrapper>
)
})),
[agentConfig]
)
const onAgentTypeChange = useCallback(
(value: AgentType) => {
const prevConfig = agentConfig.find((config) => config.key === form.type)
let newName: string | undefined = form.name
if (prevConfig && prevConfig.name === form.name) {
const newConfig = agentConfig.find((config) => config.key === value)
if (newConfig) {
newName = newConfig.name
}
}
setForm((prev) => ({
...prev,
type: value,
name: newName
}))
},
[agentConfig, form.name, form.type]
)
const onNameChange = useCallback((e: ChangeEvent<HTMLInputElement>) => { const onNameChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
setForm((prev) => ({ setForm((prev) => ({
...prev, ...prev,
@@ -97,12 +155,12 @@ const PopupContainer: React.FC<Props> = ({ agent, afterSubmit, resolve }) => {
})) }))
}, []) }, [])
// const onDescChange = useCallback((e: ChangeEvent<HTMLTextAreaElement>) => { const onDescChange = useCallback((e: ChangeEvent<HTMLTextAreaElement>) => {
// setForm((prev) => ({ setForm((prev) => ({
// ...prev, ...prev,
// description: e.target.value description: e.target.value
// })) }))
// }, []) }, [])
const onInstChange = useCallback((e: ChangeEvent<HTMLTextAreaElement>) => { const onInstChange = useCallback((e: ChangeEvent<HTMLTextAreaElement>) => {
setForm((prev) => ({ setForm((prev) => ({
@@ -276,6 +334,16 @@ const PopupContainer: React.FC<Props> = ({ agent, afterSubmit, resolve }) => {
<StyledForm onSubmit={onSubmit}> <StyledForm onSubmit={onSubmit}>
<FormContent> <FormContent>
<FormRow> <FormRow>
<FormItem style={{ flex: 1 }}>
<Label>{t('agent.type.label')}</Label>
<Select
value={form.type}
onChange={onAgentTypeChange}
options={agentOptions}
disabled={isEditing(agent)}
style={{ width: '100%' }}
/>
</FormItem>
<FormItem style={{ flex: 1 }}> <FormItem style={{ flex: 1 }}>
<Label> <Label>
{t('common.name')} <RequiredMark>*</RequiredMark> {t('common.name')} <RequiredMark>*</RequiredMark>
@@ -295,7 +363,7 @@ const PopupContainer: React.FC<Props> = ({ agent, afterSubmit, resolve }) => {
avatarSize={24} avatarSize={24}
iconSize={16} iconSize={16}
buttonStyle={{ buttonStyle={{
padding: '3px 8px', padding: '8px 12px',
width: '100%', width: '100%',
border: '1px solid var(--color-border)', border: '1px solid var(--color-border)',
borderRadius: 6, borderRadius: 6,
@@ -314,6 +382,7 @@ const PopupContainer: React.FC<Props> = ({ agent, afterSubmit, resolve }) => {
onChange={onPermissionModeChange} onChange={onPermissionModeChange}
style={{ width: '100%' }} style={{ width: '100%' }}
placeholder={t('agent.settings.tooling.permissionMode.placeholder', 'Select permission mode')} placeholder={t('agent.settings.tooling.permissionMode.placeholder', 'Select permission mode')}
dropdownStyle={{ minWidth: '500px' }}
optionLabelProp="label"> optionLabelProp="label">
{permissionModeCards.map((item) => ( {permissionModeCards.map((item) => (
<Select.Option key={item.mode} value={item.mode} label={t(item.titleKey, item.titleFallback)}> <Select.Option key={item.mode} value={item.mode} label={t(item.titleKey, item.titleFallback)}>
@@ -369,10 +438,10 @@ const PopupContainer: React.FC<Props> = ({ agent, afterSubmit, resolve }) => {
<TextArea rows={3} value={form.instructions ?? ''} onChange={onInstChange} /> <TextArea rows={3} value={form.instructions ?? ''} onChange={onInstChange} />
</FormItem> </FormItem>
{/* <FormItem> <FormItem>
<Label>{t('common.description')}</Label> <Label>{t('common.description')}</Label>
<TextArea rows={1} value={form.description ?? ''} onChange={onDescChange} /> <TextArea rows={2} value={form.description ?? ''} onChange={onDescChange} />
</FormItem> */} </FormItem>
</FormContent> </FormContent>
<FormFooter> <FormFooter>
@@ -506,7 +575,14 @@ const FormFooter = styled.div`
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
gap: 8px; gap: 8px;
padding: 10px; padding-top: 16px;
border-top: 1px solid var(--color-border);
`
const OptionWrapper = styled.div`
display: flex;
align-items: center;
gap: 8px;
` `
const PermissionOptionWrapper = styled.div` const PermissionOptionWrapper = styled.div`

View File

@@ -1,12 +1,6 @@
import { describe, expect, it, vi } from 'vitest' import { describe, expect, it, vi } from 'vitest'
import { import { isDoubaoSeedAfter251015, isDoubaoThinkingAutoModel, isLingReasoningModel } from '../models/reasoning'
isDoubaoSeedAfter251015,
isDoubaoThinkingAutoModel,
isGeminiReasoningModel,
isLingReasoningModel,
isSupportedThinkingTokenGeminiModel
} from '../models/reasoning'
vi.mock('@renderer/store', () => ({ vi.mock('@renderer/store', () => ({
default: { default: {
@@ -237,284 +231,3 @@ describe('Ling Models', () => {
}) })
}) })
}) })
describe('Gemini Models', () => {
describe('isSupportedThinkingTokenGeminiModel', () => {
it('should return true for gemini 2.5 models', () => {
expect(
isSupportedThinkingTokenGeminiModel({
id: 'gemini-2.5-flash',
name: '',
provider: '',
group: ''
})
).toBe(true)
expect(
isSupportedThinkingTokenGeminiModel({
id: 'gemini-2.5-pro',
name: '',
provider: '',
group: ''
})
).toBe(true)
expect(
isSupportedThinkingTokenGeminiModel({
id: 'gemini-2.5-flash-latest',
name: '',
provider: '',
group: ''
})
).toBe(true)
expect(
isSupportedThinkingTokenGeminiModel({
id: 'gemini-2.5-pro-latest',
name: '',
provider: '',
group: ''
})
).toBe(true)
})
it('should return true for gemini latest models', () => {
expect(
isSupportedThinkingTokenGeminiModel({
id: 'gemini-flash-latest',
name: '',
provider: '',
group: ''
})
).toBe(true)
expect(
isSupportedThinkingTokenGeminiModel({
id: 'gemini-pro-latest',
name: '',
provider: '',
group: ''
})
).toBe(true)
expect(
isSupportedThinkingTokenGeminiModel({
id: 'gemini-flash-lite-latest',
name: '',
provider: '',
group: ''
})
).toBe(true)
})
it('should return true for gemini 3 models', () => {
// Preview versions
expect(
isSupportedThinkingTokenGeminiModel({
id: 'gemini-3-pro-preview',
name: '',
provider: '',
group: ''
})
).toBe(true)
expect(
isSupportedThinkingTokenGeminiModel({
id: 'google/gemini-3-pro-preview',
name: '',
provider: '',
group: ''
})
).toBe(true)
// Future stable versions
expect(
isSupportedThinkingTokenGeminiModel({
id: 'gemini-3-flash',
name: '',
provider: '',
group: ''
})
).toBe(true)
expect(
isSupportedThinkingTokenGeminiModel({
id: 'gemini-3-pro',
name: '',
provider: '',
group: ''
})
).toBe(true)
expect(
isSupportedThinkingTokenGeminiModel({
id: 'google/gemini-3-flash',
name: '',
provider: '',
group: ''
})
).toBe(true)
expect(
isSupportedThinkingTokenGeminiModel({
id: 'google/gemini-3-pro',
name: '',
provider: '',
group: ''
})
).toBe(true)
})
it('should return false for image and tts models', () => {
expect(
isSupportedThinkingTokenGeminiModel({
id: 'gemini-2.5-flash-image',
name: '',
provider: '',
group: ''
})
).toBe(false)
expect(
isSupportedThinkingTokenGeminiModel({
id: 'gemini-2.5-flash-preview-tts',
name: '',
provider: '',
group: ''
})
).toBe(false)
})
it('should return false for older gemini models', () => {
expect(
isSupportedThinkingTokenGeminiModel({
id: 'gemini-1.5-flash',
name: '',
provider: '',
group: ''
})
).toBe(false)
expect(
isSupportedThinkingTokenGeminiModel({
id: 'gemini-1.5-pro',
name: '',
provider: '',
group: ''
})
).toBe(false)
expect(
isSupportedThinkingTokenGeminiModel({
id: 'gemini-1.0-pro',
name: '',
provider: '',
group: ''
})
).toBe(false)
})
})
describe('isGeminiReasoningModel', () => {
it('should return true for gemini thinking models', () => {
expect(
isGeminiReasoningModel({
id: 'gemini-2.0-flash-thinking',
name: '',
provider: '',
group: ''
})
).toBe(true)
expect(
isGeminiReasoningModel({
id: 'gemini-thinking-exp',
name: '',
provider: '',
group: ''
})
).toBe(true)
})
it('should return true for supported thinking token gemini models', () => {
expect(
isGeminiReasoningModel({
id: 'gemini-2.5-flash',
name: '',
provider: '',
group: ''
})
).toBe(true)
expect(
isGeminiReasoningModel({
id: 'gemini-2.5-pro',
name: '',
provider: '',
group: ''
})
).toBe(true)
})
it('should return true for gemini-3 models', () => {
// Preview versions
expect(
isGeminiReasoningModel({
id: 'gemini-3-pro-preview',
name: '',
provider: '',
group: ''
})
).toBe(true)
expect(
isGeminiReasoningModel({
id: 'google/gemini-3-pro-preview',
name: '',
provider: '',
group: ''
})
).toBe(true)
// Future stable versions
expect(
isGeminiReasoningModel({
id: 'gemini-3-flash',
name: '',
provider: '',
group: ''
})
).toBe(true)
expect(
isGeminiReasoningModel({
id: 'gemini-3-pro',
name: '',
provider: '',
group: ''
})
).toBe(true)
expect(
isGeminiReasoningModel({
id: 'google/gemini-3-flash',
name: '',
provider: '',
group: ''
})
).toBe(true)
expect(
isGeminiReasoningModel({
id: 'google/gemini-3-pro',
name: '',
provider: '',
group: ''
})
).toBe(true)
})
it('should return false for older gemini models without thinking', () => {
expect(
isGeminiReasoningModel({
id: 'gemini-1.5-flash',
name: '',
provider: '',
group: ''
})
).toBe(false)
expect(
isGeminiReasoningModel({
id: 'gemini-1.5-pro',
name: '',
provider: '',
group: ''
})
).toBe(false)
})
it('should return false for undefined model', () => {
expect(isGeminiReasoningModel(undefined)).toBe(false)
})
})
})

View File

@@ -1,167 +0,0 @@
import { describe, expect, it, vi } from 'vitest'
import { isVisionModel } from '../models/vision'
vi.mock('@renderer/store', () => ({
default: {
getState: () => ({
llm: {
settings: {}
}
})
}
}))
// FIXME: Idk why it's imported. Maybe circular dependency somewhere
vi.mock('@renderer/services/AssistantService.ts', () => ({
getDefaultAssistant: () => {
return {
id: 'default',
name: 'default',
emoji: '😀',
prompt: '',
topics: [],
messages: [],
type: 'assistant',
regularPhrases: [],
settings: {}
}
},
getProviderByModel: () => null
}))
describe('isVisionModel', () => {
describe('Gemini Models', () => {
it('should return true for gemini 1.5 models', () => {
expect(
isVisionModel({
id: 'gemini-1.5-flash',
name: '',
provider: '',
group: ''
})
).toBe(true)
expect(
isVisionModel({
id: 'gemini-1.5-pro',
name: '',
provider: '',
group: ''
})
).toBe(true)
})
it('should return true for gemini 2.x models', () => {
expect(
isVisionModel({
id: 'gemini-2.0-flash',
name: '',
provider: '',
group: ''
})
).toBe(true)
expect(
isVisionModel({
id: 'gemini-2.0-pro',
name: '',
provider: '',
group: ''
})
).toBe(true)
expect(
isVisionModel({
id: 'gemini-2.5-flash',
name: '',
provider: '',
group: ''
})
).toBe(true)
expect(
isVisionModel({
id: 'gemini-2.5-pro',
name: '',
provider: '',
group: ''
})
).toBe(true)
})
it('should return true for gemini latest models', () => {
expect(
isVisionModel({
id: 'gemini-flash-latest',
name: '',
provider: '',
group: ''
})
).toBe(true)
expect(
isVisionModel({
id: 'gemini-pro-latest',
name: '',
provider: '',
group: ''
})
).toBe(true)
expect(
isVisionModel({
id: 'gemini-flash-lite-latest',
name: '',
provider: '',
group: ''
})
).toBe(true)
})
it('should return true for gemini 3 models', () => {
// Preview versions
expect(
isVisionModel({
id: 'gemini-3-pro-preview',
name: '',
provider: '',
group: ''
})
).toBe(true)
// Future stable versions
expect(
isVisionModel({
id: 'gemini-3-flash',
name: '',
provider: '',
group: ''
})
).toBe(true)
expect(
isVisionModel({
id: 'gemini-3-pro',
name: '',
provider: '',
group: ''
})
).toBe(true)
})
it('should return true for gemini exp models', () => {
expect(
isVisionModel({
id: 'gemini-exp-1206',
name: '',
provider: '',
group: ''
})
).toBe(true)
})
it('should return false for gemini 1.0 models', () => {
expect(
isVisionModel({
id: 'gemini-1.0-pro',
name: '',
provider: '',
group: ''
})
).toBe(false)
})
})
})

View File

@@ -1,64 +0,0 @@
import { describe, expect, it, vi } from 'vitest'
import { GEMINI_SEARCH_REGEX } from '../models/websearch'
vi.mock('@renderer/store', () => ({
default: {
getState: () => ({
llm: {
settings: {}
}
})
}
}))
// FIXME: Idk why it's imported. Maybe circular dependency somewhere
vi.mock('@renderer/services/AssistantService.ts', () => ({
getDefaultAssistant: () => {
return {
id: 'default',
name: 'default',
emoji: '😀',
prompt: '',
topics: [],
messages: [],
type: 'assistant',
regularPhrases: [],
settings: {}
}
},
getProviderByModel: () => null
}))
describe('Gemini Search Models', () => {
describe('GEMINI_SEARCH_REGEX', () => {
it('should match gemini 2.x models', () => {
expect(GEMINI_SEARCH_REGEX.test('gemini-2.0-flash')).toBe(true)
expect(GEMINI_SEARCH_REGEX.test('gemini-2.0-pro')).toBe(true)
expect(GEMINI_SEARCH_REGEX.test('gemini-2.5-flash')).toBe(true)
expect(GEMINI_SEARCH_REGEX.test('gemini-2.5-pro')).toBe(true)
expect(GEMINI_SEARCH_REGEX.test('gemini-2.5-flash-latest')).toBe(true)
expect(GEMINI_SEARCH_REGEX.test('gemini-2.5-pro-latest')).toBe(true)
})
it('should match gemini latest models', () => {
expect(GEMINI_SEARCH_REGEX.test('gemini-flash-latest')).toBe(true)
expect(GEMINI_SEARCH_REGEX.test('gemini-pro-latest')).toBe(true)
expect(GEMINI_SEARCH_REGEX.test('gemini-flash-lite-latest')).toBe(true)
})
it('should match gemini 3 models', () => {
// Preview versions
expect(GEMINI_SEARCH_REGEX.test('gemini-3-pro-preview')).toBe(true)
// Future stable versions
expect(GEMINI_SEARCH_REGEX.test('gemini-3-flash')).toBe(true)
expect(GEMINI_SEARCH_REGEX.test('gemini-3-pro')).toBe(true)
})
it('should not match older gemini models', () => {
expect(GEMINI_SEARCH_REGEX.test('gemini-1.5-flash')).toBe(false)
expect(GEMINI_SEARCH_REGEX.test('gemini-1.5-pro')).toBe(false)
expect(GEMINI_SEARCH_REGEX.test('gemini-1.0-pro')).toBe(false)
})
})
})

View File

@@ -254,7 +254,7 @@ export function isGeminiReasoningModel(model?: Model): boolean {
// Gemini 支持思考模式的模型正则 // Gemini 支持思考模式的模型正则
export const GEMINI_THINKING_MODEL_REGEX = export const GEMINI_THINKING_MODEL_REGEX =
/gemini-(?:2\.5.*(?:-latest)?|3-(?:flash|pro)(?:-preview)?|flash-latest|pro-latest|flash-lite-latest)(?:-[\w-]+)*$/i /gemini-(?:2\.5.*(?:-latest)?|flash-latest|pro-latest|flash-lite-latest)(?:-[\w-]+)*$/i
export const isSupportedThinkingTokenGeminiModel = (model: Model): boolean => { export const isSupportedThinkingTokenGeminiModel = (model: Model): boolean => {
const modelId = getLowerBaseModelName(model.id, '/') const modelId = getLowerBaseModelName(model.id, '/')

View File

@@ -12,7 +12,6 @@ const visionAllowedModels = [
'gemini-1\\.5', 'gemini-1\\.5',
'gemini-2\\.0', 'gemini-2\\.0',
'gemini-2\\.5', 'gemini-2\\.5',
'gemini-3-(?:flash|pro)(?:-preview)?',
'gemini-(flash|pro|flash-lite)-latest', 'gemini-(flash|pro|flash-lite)-latest',
'gemini-exp', 'gemini-exp',
'claude-3', 'claude-3',
@@ -65,13 +64,13 @@ const visionExcludedModels = [
'o1-preview', 'o1-preview',
'AIDC-AI/Marco-o1' 'AIDC-AI/Marco-o1'
] ]
const VISION_REGEX = new RegExp( export const VISION_REGEX = new RegExp(
`\\b(?!(?:${visionExcludedModels.join('|')})\\b)(${visionAllowedModels.join('|')})\\b`, `\\b(?!(?:${visionExcludedModels.join('|')})\\b)(${visionAllowedModels.join('|')})\\b`,
'i' 'i'
) )
// For middleware to identify models that must use the dedicated Image API // For middleware to identify models that must use the dedicated Image API
const DEDICATED_IMAGE_MODELS = [ export const DEDICATED_IMAGE_MODELS = [
'grok-2-image', 'grok-2-image',
'grok-2-image-1212', 'grok-2-image-1212',
'grok-2-image-latest', 'grok-2-image-latest',
@@ -80,7 +79,7 @@ const DEDICATED_IMAGE_MODELS = [
'gpt-image-1' 'gpt-image-1'
] ]
const IMAGE_ENHANCEMENT_MODELS = [ export const IMAGE_ENHANCEMENT_MODELS = [
'grok-2-image(?:-[\\w-]+)?', 'grok-2-image(?:-[\\w-]+)?',
'qwen-image-edit', 'qwen-image-edit',
'gpt-image-1', 'gpt-image-1',
@@ -91,9 +90,9 @@ const IMAGE_ENHANCEMENT_MODELS = [
const IMAGE_ENHANCEMENT_MODELS_REGEX = new RegExp(IMAGE_ENHANCEMENT_MODELS.join('|'), 'i') const IMAGE_ENHANCEMENT_MODELS_REGEX = new RegExp(IMAGE_ENHANCEMENT_MODELS.join('|'), 'i')
// Models that should auto-enable image generation button when selected // Models that should auto-enable image generation button when selected
const AUTO_ENABLE_IMAGE_MODELS = ['gemini-2.5-flash-image', ...DEDICATED_IMAGE_MODELS] export const AUTO_ENABLE_IMAGE_MODELS = ['gemini-2.5-flash-image', ...DEDICATED_IMAGE_MODELS]
const OPENAI_TOOL_USE_IMAGE_GENERATION_MODELS = [ export const OPENAI_TOOL_USE_IMAGE_GENERATION_MODELS = [
'o3', 'o3',
'gpt-4o', 'gpt-4o',
'gpt-4o-mini', 'gpt-4o-mini',
@@ -103,9 +102,9 @@ const OPENAI_TOOL_USE_IMAGE_GENERATION_MODELS = [
'gpt-5' 'gpt-5'
] ]
const OPENAI_IMAGE_GENERATION_MODELS = [...OPENAI_TOOL_USE_IMAGE_GENERATION_MODELS, 'gpt-image-1'] export const OPENAI_IMAGE_GENERATION_MODELS = [...OPENAI_TOOL_USE_IMAGE_GENERATION_MODELS, 'gpt-image-1']
const GENERATE_IMAGE_MODELS = [ export const GENERATE_IMAGE_MODELS = [
'gemini-2.0-flash-exp', 'gemini-2.0-flash-exp',
'gemini-2.0-flash-exp-image-generation', 'gemini-2.0-flash-exp-image-generation',
'gemini-2.0-flash-preview-image-generation', 'gemini-2.0-flash-preview-image-generation',
@@ -170,23 +169,22 @@ export function isPureGenerateImageModel(model: Model): boolean {
} }
// Text to image models // Text to image models
const TEXT_TO_IMAGE_REGEX = /flux|diffusion|stabilityai|sd-|dall|cogview|janus|midjourney|mj-|image|gpt-image/i export const TEXT_TO_IMAGE_REGEX = /flux|diffusion|stabilityai|sd-|dall|cogview|janus|midjourney|mj-|image|gpt-image/i
export function isTextToImageModel(model: Model): boolean { export function isTextToImageModel(model: Model): boolean {
const modelId = getLowerBaseModelName(model.id) const modelId = getLowerBaseModelName(model.id)
return TEXT_TO_IMAGE_REGEX.test(modelId) return TEXT_TO_IMAGE_REGEX.test(modelId)
} }
// It's not used now export function isNotSupportedImageSizeModel(model?: Model): boolean {
// export function isNotSupportedImageSizeModel(model?: Model): boolean { if (!model) {
// if (!model) { return false
// return false }
// }
// const baseName = getLowerBaseModelName(model.id, '/') const baseName = getLowerBaseModelName(model.id, '/')
// return baseName.includes('grok-2-image') return baseName.includes('grok-2-image')
// } }
/** /**
* 判断模型是否支持图片增强(包括编辑、增强、修复等) * 判断模型是否支持图片增强(包括编辑、增强、修复等)

View File

@@ -3,13 +3,7 @@ import type { Model } from '@renderer/types'
import { SystemProviderIds } from '@renderer/types' import { SystemProviderIds } from '@renderer/types'
import { getLowerBaseModelName, isUserSelectedModelType } from '@renderer/utils' import { getLowerBaseModelName, isUserSelectedModelType } from '@renderer/utils'
import { import { isGeminiProvider, isNewApiProvider, isOpenAICompatibleProvider, isOpenAIProvider } from '../providers'
isGeminiProvider,
isNewApiProvider,
isOpenAICompatibleProvider,
isOpenAIProvider,
isVertexAiProvider
} from '../providers'
import { isEmbeddingModel, isRerankModel } from './embedding' import { isEmbeddingModel, isRerankModel } from './embedding'
import { isAnthropicModel } from './utils' import { isAnthropicModel } from './utils'
import { isPureGenerateImageModel, isTextToImageModel } from './vision' import { isPureGenerateImageModel, isTextToImageModel } from './vision'
@@ -22,7 +16,7 @@ export const CLAUDE_SUPPORTED_WEBSEARCH_REGEX = new RegExp(
export const GEMINI_FLASH_MODEL_REGEX = new RegExp('gemini.*-flash.*$') export const GEMINI_FLASH_MODEL_REGEX = new RegExp('gemini.*-flash.*$')
export const GEMINI_SEARCH_REGEX = new RegExp( export const GEMINI_SEARCH_REGEX = new RegExp(
'gemini-(?:2.*(?:-latest)?|3-(?:flash|pro)(?:-preview)?|flash-latest|pro-latest|flash-lite-latest)(?:-[\\w-]+)*$', 'gemini-(?:2.*(?:-latest)?|flash-latest|pro-latest|flash-lite-latest)(?:-[\\w-]+)*$',
'i' 'i'
) )
@@ -113,7 +107,7 @@ export function isWebSearchModel(model: Model): boolean {
} }
} }
if (isGeminiProvider(provider) || isVertexAiProvider(provider)) { if (isGeminiProvider(provider) || provider.id === SystemProviderIds.vertexai) {
return GEMINI_SEARCH_REGEX.test(modelId) return GEMINI_SEARCH_REGEX.test(modelId)
} }

View File

@@ -686,7 +686,7 @@ export const SYSTEM_PROVIDERS_CONFIG: Record<SystemProviderId, SystemProvider> =
name: 'AI Gateway', name: 'AI Gateway',
type: 'ai-gateway', type: 'ai-gateway',
apiKey: '', apiKey: '',
apiHost: 'https://ai-gateway.vercel.sh/v1/ai', apiHost: 'https://ai-gateway.vercel.sh/v1',
models: [], models: [],
isSystem: true, isSystem: true,
enabled: false enabled: false
@@ -1571,10 +1571,6 @@ export function isGeminiProvider(provider: Provider): boolean {
return provider.type === 'gemini' return provider.type === 'gemini'
} }
export function isVertexAiProvider(provider: Provider): boolean {
return provider.type === 'vertexai'
}
export function isAIGatewayProvider(provider: Provider): boolean { export function isAIGatewayProvider(provider: Provider): boolean {
return provider.type === 'ai-gateway' return provider.type === 'ai-gateway'
} }

View File

@@ -1,7 +1,7 @@
import type { PermissionUpdate } from '@anthropic-ai/claude-agent-sdk' import type { PermissionUpdate } from '@anthropic-ai/claude-agent-sdk'
import { loggerService } from '@logger' import { loggerService } from '@logger'
import { useAppDispatch, useAppSelector } from '@renderer/store' import { useAppDispatch, useAppSelector } from '@renderer/store'
import { selectPendingPermission, toolPermissionsActions } from '@renderer/store/toolPermissions' import { selectPendingPermissionByToolName, toolPermissionsActions } from '@renderer/store/toolPermissions'
import type { NormalToolResponse } from '@renderer/types' import type { NormalToolResponse } from '@renderer/types'
import { Button } from 'antd' import { Button } from 'antd'
import { ChevronDown, CirclePlay, CircleX } from 'lucide-react' import { ChevronDown, CirclePlay, CircleX } from 'lucide-react'
@@ -17,7 +17,9 @@ interface Props {
export function ToolPermissionRequestCard({ toolResponse }: Props) { export function ToolPermissionRequestCard({ toolResponse }: Props) {
const { t } = useTranslation() const { t } = useTranslation()
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const request = useAppSelector((state) => selectPendingPermission(state.toolPermissions, toolResponse.toolCallId)) const request = useAppSelector((state) =>
selectPendingPermissionByToolName(state.toolPermissions, toolResponse.tool.name)
)
const [now, setNow] = useState(() => Date.now()) const [now, setNow] = useState(() => Date.now())
const [showDetails, setShowDetails] = useState(false) const [showDetails, setShowDetails] = useState(false)

View File

@@ -1,13 +1,21 @@
import { getAgentTypeAvatar } from '@renderer/config/agent'
import type { useUpdateAgent } from '@renderer/hooks/agents/useUpdateAgent' import type { useUpdateAgent } from '@renderer/hooks/agents/useUpdateAgent'
import type { useUpdateSession } from '@renderer/hooks/agents/useUpdateSession' import type { useUpdateSession } from '@renderer/hooks/agents/useUpdateSession'
import { getAgentTypeLabel } from '@renderer/i18n/label'
import type { GetAgentResponse, GetAgentSessionResponse } from '@renderer/types' import type { GetAgentResponse, GetAgentSessionResponse } from '@renderer/types'
import { isAgentEntity } from '@renderer/types'
import { Avatar } from 'antd'
import type { FC } from 'react' import type { FC } from 'react'
import { useTranslation } from 'react-i18next'
import { AccessibleDirsSetting } from './AccessibleDirsSetting' import { AccessibleDirsSetting } from './AccessibleDirsSetting'
import { AvatarSetting } from './AvatarSetting'
import { DescriptionSetting } from './DescriptionSetting' import { DescriptionSetting } from './DescriptionSetting'
import { ModelSetting } from './ModelSetting' import { ModelSetting } from './ModelSetting'
import { NameSetting } from './NameSetting' import { NameSetting } from './NameSetting'
import { SettingsContainer } from './shared' import { SettingsContainer, SettingsItem, SettingsTitle } from './shared'
// const logger = loggerService.withContext('AgentEssentialSettings')
type EssentialSettingsProps = type EssentialSettingsProps =
| { | {
@@ -22,10 +30,26 @@ type EssentialSettingsProps =
} }
const EssentialSettings: FC<EssentialSettingsProps> = ({ agentBase, update, showModelSetting = true }) => { const EssentialSettings: FC<EssentialSettingsProps> = ({ agentBase, update, showModelSetting = true }) => {
const { t } = useTranslation()
if (!agentBase) return null if (!agentBase) return null
const isAgent = isAgentEntity(agentBase)
return ( return (
<SettingsContainer> <SettingsContainer>
{isAgent && (
<SettingsItem inline>
<SettingsTitle>{t('agent.type.label')}</SettingsTitle>
<div className="flex items-center gap-2">
<Avatar size={24} src={getAgentTypeAvatar(agentBase.type)} className="h-6 w-6 text-lg" />
<span>{(agentBase?.name ?? agentBase?.type) ? getAgentTypeLabel(agentBase.type) : ''}</span>
</div>
</SettingsItem>
)}
{isAgent && (
<AvatarSetting agent={agentBase} update={update as ReturnType<typeof useUpdateAgent>['updateAgent']} />
)}
<NameSetting base={agentBase} update={update} /> <NameSetting base={agentBase} update={update} />
{showModelSetting && <ModelSetting base={agentBase} update={update} />} {showModelSetting && <ModelSetting base={agentBase} update={update} />}
<AccessibleDirsSetting base={agentBase} update={update} /> <AccessibleDirsSetting base={agentBase} update={update} />

View File

@@ -1,8 +1,6 @@
import { EmojiAvatarWithPicker } from '@renderer/components/Avatar/EmojiAvatarWithPicker'
import type { AgentBaseWithId, UpdateAgentBaseForm, UpdateAgentFunctionUnion } from '@renderer/types' import type { AgentBaseWithId, UpdateAgentBaseForm, UpdateAgentFunctionUnion } from '@renderer/types'
import { AgentConfigurationSchema, isAgentEntity, isAgentType } from '@renderer/types'
import { Input } from 'antd' import { Input } from 'antd'
import { useCallback, useState } from 'react' import { useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { SettingsItem, SettingsTitle } from './shared' import { SettingsItem, SettingsTitle } from './shared'
@@ -15,61 +13,26 @@ export interface NameSettingsProps {
export const NameSetting = ({ base, update }: NameSettingsProps) => { export const NameSetting = ({ base, update }: NameSettingsProps) => {
const { t } = useTranslation() const { t } = useTranslation()
const [name, setName] = useState<string | undefined>(base?.name?.trim()) const [name, setName] = useState<string | undefined>(base?.name?.trim())
const updateName = async (name: UpdateAgentBaseForm['name']) => { const updateName = async (name: UpdateAgentBaseForm['name']) => {
if (!base) return if (!base) return
return update({ id: base.id, name: name?.trim() }) return update({ id: base.id, name: name?.trim() })
} }
// Avatar logic
const isAgent = isAgentEntity(base)
const isDefault = isAgent ? isAgentType(base.configuration?.avatar) : false
const [emoji, setEmoji] = useState(isAgent && !isDefault ? (base.configuration?.avatar ?? '⭐️') : '⭐️')
const updateAvatar = useCallback(
(avatar: string) => {
if (!isAgent || !base) return
const parsedConfiguration = AgentConfigurationSchema.parse(base.configuration ?? {})
const payload = {
id: base.id,
configuration: {
...parsedConfiguration,
avatar
}
}
update(payload)
},
[base, update, isAgent]
)
if (!base) return null if (!base) return null
return ( return (
<SettingsItem inline> <SettingsItem inline>
<SettingsTitle>{t('common.name')}</SettingsTitle> <SettingsTitle>{t('common.name')}</SettingsTitle>
<div className="flex max-w-70 flex-1 items-center gap-1"> <Input
{isAgent && ( placeholder={t('common.agent_one') + t('common.name')}
<EmojiAvatarWithPicker value={name}
emoji={emoji} onChange={(e) => setName(e.target.value)}
onPick={(emoji: string) => { onBlur={() => {
setEmoji(emoji) if (name !== base.name) {
if (isAgent && emoji === base?.configuration?.avatar) return updateName(name)
updateAvatar(emoji) }
}} }}
/> className="max-w-70 flex-1"
)} />
<Input
placeholder={t('common.agent_one') + t('common.name')}
value={name}
onChange={(e) => setName(e.target.value)}
onBlur={() => {
if (name !== base.name) {
updateName(name)
}
}}
className="flex-1"
/>
</div>
</SettingsItem> </SettingsItem>
) )
} }

View File

@@ -109,6 +109,7 @@ const InstallNpxUv: FC<Props> = ({ mini = false }) => {
<Container> <Container>
<Alert <Alert
type={isUvInstalled ? 'success' : 'warning'} type={isUvInstalled ? 'success' : 'warning'}
banner
style={{ borderRadius: 'var(--list-item-border-radius)' }} style={{ borderRadius: 'var(--list-item-border-radius)' }}
description={ description={
<VStack> <VStack>
@@ -139,6 +140,7 @@ const InstallNpxUv: FC<Props> = ({ mini = false }) => {
/> />
<Alert <Alert
type={isBunInstalled ? 'success' : 'warning'} type={isBunInstalled ? 'success' : 'warning'}
banner
style={{ borderRadius: 'var(--list-item-border-radius)' }} style={{ borderRadius: 'var(--list-item-border-radius)' }}
description={ description={
<VStack> <VStack>

View File

@@ -140,7 +140,7 @@ const MCPSettings: FC = () => {
<Route <Route
path="mcp-install" path="mcp-install"
element={ element={
<SettingContainer style={{ backgroundColor: 'inherit' }}> <SettingContainer theme={theme}>
<InstallNpxUv /> <InstallNpxUv />
</SettingContainer> </SettingContainer>
} }

View File

@@ -1,4 +1,5 @@
import { convertMessagesToSdkMessages } from '@renderer/aiCore/prepareParams' import { convertMessagesToSdkMessages } from '@renderer/aiCore/prepareParams'
import { isGeminiGenerateImageModel } from '@renderer/aiCore/utils/image'
import type { Assistant, Message } from '@renderer/types' import type { Assistant, Message } from '@renderer/types'
import { filterAdjacentUserMessaegs, filterLastAssistantMessage } from '@renderer/utils/messageUtils/filters' import { filterAdjacentUserMessaegs, filterLastAssistantMessage } from '@renderer/utils/messageUtils/filters'
import type { ModelMessage } from 'ai' import type { ModelMessage } from 'ai'
@@ -17,7 +18,14 @@ export class ConversationService {
messages: Message[], messages: Message[],
assistant: Assistant assistant: Assistant
): Promise<{ modelMessages: ModelMessage[]; uiMessages: Message[] }> { ): Promise<{ modelMessages: ModelMessage[]; uiMessages: Message[] }> {
const { contextCount } = getAssistantSettings(assistant) let { contextCount } = getAssistantSettings(assistant)
contextCount = contextCount + 2
if (assistant.model && isGeminiGenerateImageModel(assistant.model)) {
contextCount = 1
}
// This logic is extracted from the original ApiService.fetchChatCompletion // This logic is extracted from the original ApiService.fetchChatCompletion
// const contextMessages = filterContextMessages(messages) // const contextMessages = filterContextMessages(messages)
const lastUserMessage = findLast(messages, (m) => m.role === 'user') const lastUserMessage = findLast(messages, (m) => m.role === 'user')
@@ -37,7 +45,7 @@ export class ConversationService {
const filteredMessages4 = filterAdjacentUserMessaegs(filteredMessages3) const filteredMessages4 = filterAdjacentUserMessaegs(filteredMessages3)
let uiMessages = filterUserRoleStartMessages( let uiMessages = filterUserRoleStartMessages(
filterEmptyMessages(filterAfterContextClearMessages(takeRight(filteredMessages4, contextCount + 2))) // 取原来几个provider的最大值 filterEmptyMessages(filterAfterContextClearMessages(takeRight(filteredMessages4, contextCount))) // 取原来几个provider的最大值
) )
// Fallback: ensure at least the last user message is present to avoid empty payloads // Fallback: ensure at least the last user message is present to avoid empty payloads

View File

@@ -6,7 +6,6 @@ export type ToolPermissionRequestPayload = {
requestId: string requestId: string
toolName: string toolName: string
toolId: string toolId: string
toolCallId: string
description?: string description?: string
requiresPermissions: boolean requiresPermissions: boolean
input: Record<string, unknown> input: Record<string, unknown>
@@ -83,12 +82,12 @@ export const selectActiveToolPermission = (state: ToolPermissionsState): ToolPer
return activeEntries[0] return activeEntries[0]
} }
export const selectPendingPermission = ( export const selectPendingPermissionByToolName = (
state: ToolPermissionsState, state: ToolPermissionsState,
toolCallId: string toolName: string
): ToolPermissionEntry | undefined => { ): ToolPermissionEntry | undefined => {
const activeEntries = Object.values(state.requests) const activeEntries = Object.values(state.requests)
.filter((entry) => entry.toolCallId === toolCallId) .filter((entry) => entry.toolName === toolName)
.filter( .filter(
(entry) => entry.status === 'pending' || entry.status === 'submitting-allow' || entry.status === 'submitting-deny' (entry) => entry.status === 'pending' || entry.status === 'submitting-allow' || entry.status === 'submitting-deny'
) )

216
yarn.lock
View File

@@ -102,19 +102,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@ai-sdk/anthropic@npm:2.0.45": "@ai-sdk/anthropic@npm:2.0.44, @ai-sdk/anthropic@npm:^2.0.44":
version: 2.0.45
resolution: "@ai-sdk/anthropic@npm:2.0.45"
dependencies:
"@ai-sdk/provider": "npm:2.0.0"
"@ai-sdk/provider-utils": "npm:3.0.17"
peerDependencies:
zod: ^3.25.76 || ^4.1.8
checksum: 10c0/ef0e54f032e3b8324c278f3b25d9b388308204d753404c49fd880709a796c2343aee36d335c99f50e683edd39d5b8b6f42b2e9034e1725d8e0db514e2233d104
languageName: node
linkType: hard
"@ai-sdk/anthropic@npm:^2.0.44":
version: 2.0.44 version: 2.0.44
resolution: "@ai-sdk/anthropic@npm:2.0.44" resolution: "@ai-sdk/anthropic@npm:2.0.44"
dependencies: dependencies:
@@ -191,42 +179,54 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@ai-sdk/google-vertex@npm:^3.0.68": "@ai-sdk/google-vertex@npm:^3.0.62":
version: 3.0.68 version: 3.0.62
resolution: "@ai-sdk/google-vertex@npm:3.0.68" resolution: "@ai-sdk/google-vertex@npm:3.0.62"
dependencies: dependencies:
"@ai-sdk/anthropic": "npm:2.0.45" "@ai-sdk/anthropic": "npm:2.0.44"
"@ai-sdk/google": "npm:2.0.36" "@ai-sdk/google": "npm:2.0.31"
"@ai-sdk/provider": "npm:2.0.0" "@ai-sdk/provider": "npm:2.0.0"
"@ai-sdk/provider-utils": "npm:3.0.17" "@ai-sdk/provider-utils": "npm:3.0.17"
google-auth-library: "npm:^9.15.0" google-auth-library: "npm:^9.15.0"
peerDependencies: peerDependencies:
zod: ^3.25.76 || ^4.1.8 zod: ^3.25.76 || ^4.1.8
checksum: 10c0/6a3f4cb1e649313b46a0c349c717757071f8b012b0a28e59ab7a55fd35d9600f0043f0a4f57417c4cc49e0d3734e89a1e4fb248fc88795b5286c83395d3f617a checksum: 10c0/673bb51e3e0cbe5235ad5e65379b1cb8f099dbc690ab8552e208553a9f1cc6026d2588e956e73468bc6d267066be276e7a9aba98e32e905809dfbeab4ac0e352
languageName: node languageName: node
linkType: hard linkType: hard
"@ai-sdk/google@npm:2.0.36": "@ai-sdk/google@npm:2.0.31":
version: 2.0.36 version: 2.0.31
resolution: "@ai-sdk/google@npm:2.0.36" resolution: "@ai-sdk/google@npm:2.0.31"
dependencies: dependencies:
"@ai-sdk/provider": "npm:2.0.0" "@ai-sdk/provider": "npm:2.0.0"
"@ai-sdk/provider-utils": "npm:3.0.17" "@ai-sdk/provider-utils": "npm:3.0.17"
peerDependencies: peerDependencies:
zod: ^3.25.76 || ^4.1.8 zod: ^3.25.76 || ^4.1.8
checksum: 10c0/2c6de5e1cf0703b6b932a3f313bf4bc9439897af39c805169ab04bba397185d99b2b1306f3b817f991ca41fdced0365b072ee39e76382c045930256bce47e0e4 checksum: 10c0/d8f143f058fb62e6e67e30564ec92530d7389c22ad91b1e4bbe781c8570bf718cd417e44dcd4855e347e85c4174538a9a884eac666109e17f20d21467ab3e749
languageName: node languageName: node
linkType: hard linkType: hard
"@ai-sdk/google@patch:@ai-sdk/google@npm%3A2.0.36#~/.yarn/patches/@ai-sdk-google-npm-2.0.36-6f3cc06026.patch": "@ai-sdk/google@npm:^2.0.32":
version: 2.0.36 version: 2.0.32
resolution: "@ai-sdk/google@patch:@ai-sdk/google@npm%3A2.0.36#~/.yarn/patches/@ai-sdk-google-npm-2.0.36-6f3cc06026.patch::version=2.0.36&hash=2da8c3" resolution: "@ai-sdk/google@npm:2.0.32"
dependencies: dependencies:
"@ai-sdk/provider": "npm:2.0.0" "@ai-sdk/provider": "npm:2.0.0"
"@ai-sdk/provider-utils": "npm:3.0.17" "@ai-sdk/provider-utils": "npm:3.0.17"
peerDependencies: peerDependencies:
zod: ^3.25.76 || ^4.1.8 zod: ^3.25.76 || ^4.1.8
checksum: 10c0/ce99a497360377d2917cf3a48278eb6f4337623ce3738ba743cf048c8c2a7731ec4fc27605a50e461e716ed49b3690206ca8e4078f27cb7be162b684bfc2fc22 checksum: 10c0/052de16f1f66188e126168c8a9cc903448104528c7e44d6867bbf555c9067b9d6d44a4c4e0e014838156ba39095cb417f1b76363eb65212ca4d005f3651e58d2
languageName: node
linkType: hard
"@ai-sdk/google@patch:@ai-sdk/google@npm%3A2.0.31#~/.yarn/patches/@ai-sdk-google-npm-2.0.31-b0de047210.patch":
version: 2.0.31
resolution: "@ai-sdk/google@patch:@ai-sdk/google@npm%3A2.0.31#~/.yarn/patches/@ai-sdk-google-npm-2.0.31-b0de047210.patch::version=2.0.31&hash=9f3835"
dependencies:
"@ai-sdk/provider": "npm:2.0.0"
"@ai-sdk/provider-utils": "npm:3.0.17"
peerDependencies:
zod: ^3.25.76 || ^4.1.8
checksum: 10c0/dd37dfb7abf402caaae3edb2f1a8dab018fddad6ba3190376723e03a2a0c352329c8e41e60df3fb8436b717d9c2ee4b82dff091848f50d026f62565cbdb158f8
languageName: node languageName: node
linkType: hard linkType: hard
@@ -1891,30 +1891,30 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@cherrystudio/ai-core@workspace:^1.0.9, @cherrystudio/ai-core@workspace:packages/aiCore": "@cherrystudio/ai-core@workspace:^1.0.0-alpha.18, @cherrystudio/ai-core@workspace:packages/aiCore":
version: 0.0.0-use.local version: 0.0.0-use.local
resolution: "@cherrystudio/ai-core@workspace:packages/aiCore" resolution: "@cherrystudio/ai-core@workspace:packages/aiCore"
dependencies: dependencies:
"@ai-sdk/anthropic": "npm:^2.0.43" "@ai-sdk/anthropic": "npm:^2.0.43"
"@ai-sdk/azure": "npm:^2.0.66" "@ai-sdk/azure": "npm:^2.0.66"
"@ai-sdk/deepseek": "npm:^1.0.27" "@ai-sdk/deepseek": "npm:^1.0.27"
"@ai-sdk/google": "patch:@ai-sdk/google@npm%3A2.0.31#~/.yarn/patches/@ai-sdk-google-npm-2.0.31-b0de047210.patch"
"@ai-sdk/openai": "patch:@ai-sdk/openai@npm%3A2.0.64#~/.yarn/patches/@ai-sdk-openai-npm-2.0.64-48f99f5bf3.patch"
"@ai-sdk/openai-compatible": "npm:^1.0.26" "@ai-sdk/openai-compatible": "npm:^1.0.26"
"@ai-sdk/provider": "npm:^2.0.0" "@ai-sdk/provider": "npm:^2.0.0"
"@ai-sdk/provider-utils": "npm:^3.0.16" "@ai-sdk/provider-utils": "npm:^3.0.16"
"@ai-sdk/xai": "npm:^2.0.31" "@ai-sdk/xai": "npm:^2.0.31"
"@cherrystudio/ai-sdk-provider": "workspace:*"
tsdown: "npm:^0.12.9" tsdown: "npm:^0.12.9"
typescript: "npm:^5.0.0" typescript: "npm:^5.0.0"
vitest: "npm:^3.2.4" vitest: "npm:^3.2.4"
zod: "npm:^4.1.5" zod: "npm:^4.1.5"
peerDependencies: peerDependencies:
"@ai-sdk/google": ^2.0.36
"@ai-sdk/openai": ^2.0.64
"@cherrystudio/ai-sdk-provider": ^0.1.2
ai: ^5.0.26 ai: ^5.0.26
languageName: unknown languageName: unknown
linkType: soft linkType: soft
"@cherrystudio/ai-sdk-provider@workspace:packages/ai-sdk-provider": "@cherrystudio/ai-sdk-provider@workspace:*, @cherrystudio/ai-sdk-provider@workspace:packages/ai-sdk-provider":
version: 0.0.0-use.local version: 0.0.0-use.local
resolution: "@cherrystudio/ai-sdk-provider@workspace:packages/ai-sdk-provider" resolution: "@cherrystudio/ai-sdk-provider@workspace:packages/ai-sdk-provider"
dependencies: dependencies:
@@ -4558,38 +4558,38 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@libsql/client@npm:0.15.15": "@libsql/client@npm:0.14.0, @libsql/client@npm:^0.14.0":
version: 0.15.15 version: 0.14.0
resolution: "@libsql/client@npm:0.15.15" resolution: "@libsql/client@npm:0.14.0"
dependencies: dependencies:
"@libsql/core": "npm:^0.15.14" "@libsql/core": "npm:^0.14.0"
"@libsql/hrana-client": "npm:^0.7.0" "@libsql/hrana-client": "npm:^0.7.0"
js-base64: "npm:^3.7.5" js-base64: "npm:^3.7.5"
libsql: "npm:^0.5.22" libsql: "npm:^0.4.4"
promise-limit: "npm:^2.7.0" promise-limit: "npm:^2.7.0"
checksum: 10c0/1ae67280ebe27903ff142b07e2a256c22ef5ada65185286a72823e8eae8d9d2602e0d72e423d3bd64ae57494791bfffff946aa0fc7c2378b55a227ff63f8df69 checksum: 10c0/9c6bab468453df765f647422c772af3578f1e108b663a80b99063f47ed3542db26ae0fcdba2e153d72e6d5089c5caeba947a167a6c065b0191a0832621539335
languageName: node languageName: node
linkType: hard linkType: hard
"@libsql/core@npm:^0.15.14": "@libsql/core@npm:^0.14.0":
version: 0.15.15 version: 0.14.0
resolution: "@libsql/core@npm:0.15.15" resolution: "@libsql/core@npm:0.14.0"
dependencies: dependencies:
js-base64: "npm:^3.7.5" js-base64: "npm:^3.7.5"
checksum: 10c0/0a619689c9504f4239d9745882a128b81e2f6c0547352bbb0d36932261c053bbcbea4435a17f91abe61556bb791f2f1203b36c36b2d4b4f369953d7949bdc40e checksum: 10c0/327bb991cf191d5a9a9fc0cc1a17123f7ca88f222187a3bde845fbad8ceaeaa1f139882080e4b2969da57b83e576c52702572e2838d1743c6bff75f95e6f774a
languageName: node languageName: node
linkType: hard linkType: hard
"@libsql/darwin-arm64@npm:0.5.22": "@libsql/darwin-arm64@npm:0.4.7":
version: 0.5.22 version: 0.4.7
resolution: "@libsql/darwin-arm64@npm:0.5.22" resolution: "@libsql/darwin-arm64@npm:0.4.7"
conditions: os=darwin & cpu=arm64 conditions: os=darwin & cpu=arm64
languageName: node languageName: node
linkType: hard linkType: hard
"@libsql/darwin-x64@npm:0.5.22": "@libsql/darwin-x64@npm:0.4.7":
version: 0.5.22 version: 0.4.7
resolution: "@libsql/darwin-x64@npm:0.5.22" resolution: "@libsql/darwin-x64@npm:0.4.7"
conditions: os=darwin & cpu=x64 conditions: os=darwin & cpu=x64
languageName: node languageName: node
linkType: hard linkType: hard
@@ -4623,52 +4623,38 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@libsql/linux-arm-gnueabihf@npm:0.5.22": "@libsql/linux-arm64-gnu@npm:0.4.7":
version: 0.5.22 version: 0.4.7
resolution: "@libsql/linux-arm-gnueabihf@npm:0.5.22" resolution: "@libsql/linux-arm64-gnu@npm:0.4.7"
conditions: os=linux & cpu=arm
languageName: node
linkType: hard
"@libsql/linux-arm-musleabihf@npm:0.5.22":
version: 0.5.22
resolution: "@libsql/linux-arm-musleabihf@npm:0.5.22"
conditions: os=linux & cpu=arm
languageName: node
linkType: hard
"@libsql/linux-arm64-gnu@npm:0.5.22":
version: 0.5.22
resolution: "@libsql/linux-arm64-gnu@npm:0.5.22"
conditions: os=linux & cpu=arm64 conditions: os=linux & cpu=arm64
languageName: node languageName: node
linkType: hard linkType: hard
"@libsql/linux-arm64-musl@npm:0.5.22": "@libsql/linux-arm64-musl@npm:0.4.7":
version: 0.5.22 version: 0.4.7
resolution: "@libsql/linux-arm64-musl@npm:0.5.22" resolution: "@libsql/linux-arm64-musl@npm:0.4.7"
conditions: os=linux & cpu=arm64 conditions: os=linux & cpu=arm64
languageName: node languageName: node
linkType: hard linkType: hard
"@libsql/linux-x64-gnu@npm:0.5.22": "@libsql/linux-x64-gnu@npm:0.4.7":
version: 0.5.22 version: 0.4.7
resolution: "@libsql/linux-x64-gnu@npm:0.5.22" resolution: "@libsql/linux-x64-gnu@npm:0.4.7"
conditions: os=linux & cpu=x64 conditions: os=linux & cpu=x64
languageName: node languageName: node
linkType: hard linkType: hard
"@libsql/linux-x64-musl@npm:0.5.22": "@libsql/linux-x64-musl@npm:0.4.7":
version: 0.5.22 version: 0.4.7
resolution: "@libsql/linux-x64-musl@npm:0.5.22" resolution: "@libsql/linux-x64-musl@npm:0.4.7"
conditions: os=linux & cpu=x64 conditions: os=linux & cpu=x64
languageName: node languageName: node
linkType: hard linkType: hard
"@libsql/win32-x64-msvc@npm:0.5.22, @libsql/win32-x64-msvc@npm:^0.5.22": "@libsql/win32-x64-msvc@npm:0.4.7, @libsql/win32-x64-msvc@npm:^0.4.7":
version: 0.5.22 version: 0.4.7
resolution: "@libsql/win32-x64-msvc@npm:0.5.22" resolution: "@libsql/win32-x64-msvc@npm:0.4.7"
checksum: 10c0/1bb2730563c603c03a229faa352897685648659d85ba0872dda60cc02abc469fbd55539ffd8b86c81d00230d76292e5a4d2a763fe44c05694612ce6db6e929aa checksum: 10c0/2fcb8715b6f0571dec145eaaf3fd53c7c5aa5bf408fe1be9d84b10adc8a909bb6ee60b45e0d7052b0c1722c30ac212356a3f1adcdf7f57d5a59b48f36ca5bdf5
conditions: os=win32 & cpu=x64 conditions: os=win32 & cpu=x64
languageName: node languageName: node
linkType: hard linkType: hard
@@ -9920,11 +9906,10 @@ __metadata:
"@ai-sdk/anthropic": "npm:^2.0.44" "@ai-sdk/anthropic": "npm:^2.0.44"
"@ai-sdk/cerebras": "npm:^1.0.31" "@ai-sdk/cerebras": "npm:^1.0.31"
"@ai-sdk/gateway": "npm:^2.0.9" "@ai-sdk/gateway": "npm:^2.0.9"
"@ai-sdk/google": "patch:@ai-sdk/google@npm%3A2.0.36#~/.yarn/patches/@ai-sdk-google-npm-2.0.36-6f3cc06026.patch" "@ai-sdk/google": "npm:^2.0.32"
"@ai-sdk/google-vertex": "npm:^3.0.68" "@ai-sdk/google-vertex": "npm:^3.0.62"
"@ai-sdk/huggingface": "patch:@ai-sdk/huggingface@npm%3A0.0.8#~/.yarn/patches/@ai-sdk-huggingface-npm-0.0.8-d4d0aaac93.patch" "@ai-sdk/huggingface": "patch:@ai-sdk/huggingface@npm%3A0.0.8#~/.yarn/patches/@ai-sdk-huggingface-npm-0.0.8-d4d0aaac93.patch"
"@ai-sdk/mistral": "npm:^2.0.23" "@ai-sdk/mistral": "npm:^2.0.23"
"@ai-sdk/openai": "patch:@ai-sdk/openai@npm%3A2.0.64#~/.yarn/patches/@ai-sdk-openai-npm-2.0.64-48f99f5bf3.patch"
"@ai-sdk/perplexity": "npm:^2.0.17" "@ai-sdk/perplexity": "npm:^2.0.17"
"@ant-design/v5-patch-for-react-19": "npm:^1.0.3" "@ant-design/v5-patch-for-react-19": "npm:^1.0.3"
"@anthropic-ai/claude-agent-sdk": "patch:@anthropic-ai/claude-agent-sdk@npm%3A0.1.30#~/.yarn/patches/@anthropic-ai-claude-agent-sdk-npm-0.1.30-b50a299674.patch" "@anthropic-ai/claude-agent-sdk": "patch:@anthropic-ai/claude-agent-sdk@npm%3A0.1.30#~/.yarn/patches/@anthropic-ai-claude-agent-sdk-npm-0.1.30-b50a299674.patch"
@@ -9934,7 +9919,7 @@ __metadata:
"@aws-sdk/client-bedrock-runtime": "npm:^3.910.0" "@aws-sdk/client-bedrock-runtime": "npm:^3.910.0"
"@aws-sdk/client-s3": "npm:^3.910.0" "@aws-sdk/client-s3": "npm:^3.910.0"
"@biomejs/biome": "npm:2.2.4" "@biomejs/biome": "npm:2.2.4"
"@cherrystudio/ai-core": "workspace:^1.0.9" "@cherrystudio/ai-core": "workspace:^1.0.0-alpha.18"
"@cherrystudio/embedjs": "npm:^0.1.31" "@cherrystudio/embedjs": "npm:^0.1.31"
"@cherrystudio/embedjs-libsql": "npm:^0.1.31" "@cherrystudio/embedjs-libsql": "npm:^0.1.31"
"@cherrystudio/embedjs-loader-csv": "npm:^0.1.31" "@cherrystudio/embedjs-loader-csv": "npm:^0.1.31"
@@ -9967,8 +9952,8 @@ __metadata:
"@langchain/community": "npm:^1.0.0" "@langchain/community": "npm:^1.0.0"
"@langchain/core": "patch:@langchain/core@npm%3A1.0.2#~/.yarn/patches/@langchain-core-npm-1.0.2-183ef83fe4.patch" "@langchain/core": "patch:@langchain/core@npm%3A1.0.2#~/.yarn/patches/@langchain-core-npm-1.0.2-183ef83fe4.patch"
"@langchain/openai": "patch:@langchain/openai@npm%3A1.0.0#~/.yarn/patches/@langchain-openai-npm-1.0.0-474d0ad9d4.patch" "@langchain/openai": "patch:@langchain/openai@npm%3A1.0.0#~/.yarn/patches/@langchain-openai-npm-1.0.0-474d0ad9d4.patch"
"@libsql/client": "npm:0.15.15" "@libsql/client": "npm:0.14.0"
"@libsql/win32-x64-msvc": "npm:^0.5.22" "@libsql/win32-x64-msvc": "npm:^0.4.7"
"@mistralai/mistralai": "npm:^1.7.5" "@mistralai/mistralai": "npm:^1.7.5"
"@modelcontextprotocol/sdk": "npm:^1.17.5" "@modelcontextprotocol/sdk": "npm:^1.17.5"
"@mozilla/readability": "npm:^0.6.0" "@mozilla/readability": "npm:^0.6.0"
@@ -17281,19 +17266,17 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"libsql@npm:^0.5.22": "libsql@npm:0.4.7":
version: 0.5.22 version: 0.4.7
resolution: "libsql@npm:0.5.22" resolution: "libsql@npm:0.4.7"
dependencies: dependencies:
"@libsql/darwin-arm64": "npm:0.5.22" "@libsql/darwin-arm64": "npm:0.4.7"
"@libsql/darwin-x64": "npm:0.5.22" "@libsql/darwin-x64": "npm:0.4.7"
"@libsql/linux-arm-gnueabihf": "npm:0.5.22" "@libsql/linux-arm64-gnu": "npm:0.4.7"
"@libsql/linux-arm-musleabihf": "npm:0.5.22" "@libsql/linux-arm64-musl": "npm:0.4.7"
"@libsql/linux-arm64-gnu": "npm:0.5.22" "@libsql/linux-x64-gnu": "npm:0.4.7"
"@libsql/linux-arm64-musl": "npm:0.5.22" "@libsql/linux-x64-musl": "npm:0.4.7"
"@libsql/linux-x64-gnu": "npm:0.5.22" "@libsql/win32-x64-msvc": "npm:0.4.7"
"@libsql/linux-x64-musl": "npm:0.5.22"
"@libsql/win32-x64-msvc": "npm:0.5.22"
"@neon-rs/load": "npm:^0.0.4" "@neon-rs/load": "npm:^0.0.4"
detect-libc: "npm:2.0.2" detect-libc: "npm:2.0.2"
dependenciesMeta: dependenciesMeta:
@@ -17301,10 +17284,6 @@ __metadata:
optional: true optional: true
"@libsql/darwin-x64": "@libsql/darwin-x64":
optional: true optional: true
"@libsql/linux-arm-gnueabihf":
optional: true
"@libsql/linux-arm-musleabihf":
optional: true
"@libsql/linux-arm64-gnu": "@libsql/linux-arm64-gnu":
optional: true optional: true
"@libsql/linux-arm64-musl": "@libsql/linux-arm64-musl":
@@ -17315,8 +17294,41 @@ __metadata:
optional: true optional: true
"@libsql/win32-x64-msvc": "@libsql/win32-x64-msvc":
optional: true optional: true
checksum: 10c0/6c34f08fc7408ebee16708ba12e5def9d1b2a4fa166070c956a120133ba9be68ec532e2d0b76bdc7005ef9ef69bf70d2ba7208ed824c4288c2a3d881edd5eaf6 checksum: 10c0/351952440e6bad3477e5f1bb1b9d6570d16e403b894f4a13c5c7e183a1307b2fb04a2fa902728cb8594a259e1726c51c61b822d545bbc88319b126ad15468a87
conditions: (os=darwin | os=linux | os=win32) & (cpu=x64 | cpu=arm64 | cpu=wasm32 | cpu=arm) conditions: (os=darwin | os=linux | os=win32) & (cpu=x64 | cpu=arm64 | cpu=wasm32)
languageName: node
linkType: hard
"libsql@patch:libsql@npm%3A0.4.7#~/.yarn/patches/libsql-npm-0.4.7-444e260fb1.patch":
version: 0.4.7
resolution: "libsql@patch:libsql@npm%3A0.4.7#~/.yarn/patches/libsql-npm-0.4.7-444e260fb1.patch::version=0.4.7&hash=972e11"
dependencies:
"@libsql/darwin-arm64": "npm:0.4.7"
"@libsql/darwin-x64": "npm:0.4.7"
"@libsql/linux-arm64-gnu": "npm:0.4.7"
"@libsql/linux-arm64-musl": "npm:0.4.7"
"@libsql/linux-x64-gnu": "npm:0.4.7"
"@libsql/linux-x64-musl": "npm:0.4.7"
"@libsql/win32-x64-msvc": "npm:0.4.7"
"@neon-rs/load": "npm:^0.0.4"
detect-libc: "npm:2.0.2"
dependenciesMeta:
"@libsql/darwin-arm64":
optional: true
"@libsql/darwin-x64":
optional: true
"@libsql/linux-arm64-gnu":
optional: true
"@libsql/linux-arm64-musl":
optional: true
"@libsql/linux-x64-gnu":
optional: true
"@libsql/linux-x64-musl":
optional: true
"@libsql/win32-x64-msvc":
optional: true
checksum: 10c0/6098770dc6c31ae0dbfe0821719d184d9bb353ac92553923096f6e3420d3786f240f0b3858f519af0aeada93beb4aa83cb9a9a1a6aa18d625511b484dcb53d07
conditions: (os=darwin | os=linux | os=win32) & (cpu=x64 | cpu=arm64 | cpu=wasm32)
languageName: node languageName: node
linkType: hard linkType: hard