From 40a64a7c9228251cab662c508df5674ffc1d63ba Mon Sep 17 00:00:00 2001 From: MyPrototypeWhat Date: Wed, 19 Nov 2025 16:25:29 +0800 Subject: [PATCH 1/7] =?UTF-8?q?feat(options):=20enhance=20provider=20key?= =?UTF-8?q?=20handling=20for=20cherryin=20in=20buildPro=E2=80=A6=20(#11361?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit feat(options): enhance provider key handling for cherryin in buildProviderOptions function --- src/renderer/src/aiCore/utils/options.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/renderer/src/aiCore/utils/options.ts b/src/renderer/src/aiCore/utils/options.ts index 128a0f5269..eacb1b9e05 100644 --- a/src/renderer/src/aiCore/utils/options.ts +++ b/src/renderer/src/aiCore/utils/options.ts @@ -162,13 +162,17 @@ export function buildProviderOptions( ...getCustomParameters(assistant) } - const rawProviderKey = + let rawProviderKey = { 'google-vertex': 'google', 'google-vertex-anthropic': 'anthropic', 'ai-gateway': 'gateway' }[rawProviderId] || rawProviderId + if (rawProviderKey === 'cherryin') { + rawProviderKey = { gemini: 'google' }[actualProvider.type] || actualProvider.type + } + // 返回 AI Core SDK 要求的格式:{ 'providerId': providerOptions } return { [rawProviderKey]: providerSpecificOptions From 0e011ff35f173dc9350971f0448f27949a788221 Mon Sep 17 00:00:00 2001 From: scientia Date: Wed, 19 Nov 2025 17:11:17 +0800 Subject: [PATCH 2/7] fix: fix api-host for vercel ai-gateway provider (#11321) Co-authored-by: scientia --- src/renderer/src/config/providers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/src/config/providers.ts b/src/renderer/src/config/providers.ts index c4c280e666..b21721e719 100644 --- a/src/renderer/src/config/providers.ts +++ b/src/renderer/src/config/providers.ts @@ -686,7 +686,7 @@ export const SYSTEM_PROVIDERS_CONFIG: Record = name: 'AI Gateway', type: 'ai-gateway', apiKey: '', - apiHost: 'https://ai-gateway.vercel.sh/v1', + apiHost: 'https://ai-gateway.vercel.sh/v1/ai', models: [], isSystem: true, enabled: false From c8e9a101907bd5e82f9eac28280b704dbeeecf59 Mon Sep 17 00:00:00 2001 From: SuYao Date: Wed, 19 Nov 2025 18:13:33 +0800 Subject: [PATCH 3/7] bump ai core version (#11363) * bump ai core version * chore * chore: add patch for @ai-sdk/openai and update peer dependencies in aiCore * chore: update installation instructions in README to include @ai-sdk/google and @ai-sdk/openai * chore: bump @cherrystudio/ai-core version to 1.0.6 in package.json and yarn.lock --------- Co-authored-by: MyPrototypeWhat --- package.json | 6 ++++-- packages/ai-sdk-provider/package.json | 2 +- packages/aiCore/README.md | 2 +- packages/aiCore/package.json | 8 ++++---- packages/aiCore/src/core/providers/schemas.ts | 10 +--------- src/renderer/src/aiCore/utils/options.ts | 6 +++--- yarn.lock | 13 +++++++------ 7 files changed, 21 insertions(+), 26 deletions(-) diff --git a/package.json b/package.json index 04ce325558..284843dc3a 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,8 @@ "claude": "dotenv -e .env -- claude", "release:aicore:alpha": "yarn workspace @cherrystudio/ai-core version prerelease --immediate && yarn workspace @cherrystudio/ai-core npm publish --tag alpha --access public", "release:aicore:beta": "yarn workspace @cherrystudio/ai-core version prerelease --immediate && yarn workspace @cherrystudio/ai-core npm publish --tag beta --access public", - "release:aicore": "yarn workspace @cherrystudio/ai-core version patch --immediate && yarn workspace @cherrystudio/ai-core npm publish --access public" + "release:aicore": "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 npm publish --access public" }, "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", @@ -115,6 +116,7 @@ "@ai-sdk/google-vertex": "^3.0.68", "@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/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", "@ant-design/v5-patch-for-react-19": "^1.0.3", "@anthropic-ai/sdk": "^0.41.0", @@ -123,7 +125,7 @@ "@aws-sdk/client-bedrock-runtime": "^3.910.0", "@aws-sdk/client-s3": "^3.910.0", "@biomejs/biome": "2.2.4", - "@cherrystudio/ai-core": "workspace:^1.0.0-alpha.18", + "@cherrystudio/ai-core": "workspace:^1.0.6", "@cherrystudio/embedjs": "^0.1.31", "@cherrystudio/embedjs-libsql": "^0.1.31", "@cherrystudio/embedjs-loader-csv": "^0.1.31", diff --git a/packages/ai-sdk-provider/package.json b/packages/ai-sdk-provider/package.json index fd0aac2643..2f948d6bb7 100644 --- a/packages/ai-sdk-provider/package.json +++ b/packages/ai-sdk-provider/package.json @@ -1,6 +1,6 @@ { "name": "@cherrystudio/ai-sdk-provider", - "version": "0.1.0", + "version": "0.1.1", "description": "Cherry Studio AI SDK provider bundle with CherryIN routing.", "keywords": [ "ai-sdk", diff --git a/packages/aiCore/README.md b/packages/aiCore/README.md index 4ca5ea6640..1380019094 100644 --- a/packages/aiCore/README.md +++ b/packages/aiCore/README.md @@ -71,7 +71,7 @@ Cherry Studio AI Core 是一个基于 Vercel AI SDK 的统一 AI Provider 接口 ## 安装 ```bash -npm install @cherrystudio/ai-core ai +npm install @cherrystudio/ai-core ai @ai-sdk/google @ai-sdk/openai ``` ### React Native diff --git a/packages/aiCore/package.json b/packages/aiCore/package.json index 12249cfb7b..1fc66feddf 100644 --- a/packages/aiCore/package.json +++ b/packages/aiCore/package.json @@ -1,6 +1,6 @@ { "name": "@cherrystudio/ai-core", - "version": "1.0.1", + "version": "1.0.6", "description": "Cherry Studio AI Core - Unified AI Provider Interface Based on Vercel AI SDK", "main": "dist/index.js", "module": "dist/index.mjs", @@ -33,19 +33,19 @@ }, "homepage": "https://github.com/CherryHQ/cherry-studio#readme", "peerDependencies": { + "@ai-sdk/google": "^2.0.36", + "@ai-sdk/openai": "^2.0.64", "ai": "^5.0.26" }, "dependencies": { "@ai-sdk/anthropic": "^2.0.43", "@ai-sdk/azure": "^2.0.66", "@ai-sdk/deepseek": "^1.0.27", - "@ai-sdk/google": "patch:@ai-sdk/google@npm%3A2.0.36#~/.yarn/patches/@ai-sdk-google-npm-2.0.36-6f3cc06026.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/provider": "^2.0.0", "@ai-sdk/provider-utils": "^3.0.16", "@ai-sdk/xai": "^2.0.31", - "@cherrystudio/ai-sdk-provider": "workspace:*", + "@cherrystudio/ai-sdk-provider": "^0.1.1", "zod": "^4.1.5" }, "devDependencies": { diff --git a/packages/aiCore/src/core/providers/schemas.ts b/packages/aiCore/src/core/providers/schemas.ts index 778b1b705a..43a370af9b 100644 --- a/packages/aiCore/src/core/providers/schemas.ts +++ b/packages/aiCore/src/core/providers/schemas.ts @@ -7,7 +7,6 @@ import { createAzure } from '@ai-sdk/azure' import { type AzureOpenAIProviderSettings } from '@ai-sdk/azure' import { createDeepSeek } from '@ai-sdk/deepseek' import { createGoogleGenerativeAI } from '@ai-sdk/google' -import { createHuggingFace } from '@ai-sdk/huggingface' import { createOpenAI, type OpenAIProviderSettings } from '@ai-sdk/openai' import { createOpenAICompatible } from '@ai-sdk/openai-compatible' import type { LanguageModelV2 } from '@ai-sdk/provider' @@ -33,8 +32,7 @@ export const baseProviderIds = [ 'deepseek', 'openrouter', 'cherryin', - 'cherryin-chat', - 'huggingface' + 'cherryin-chat' ] as const /** @@ -158,12 +156,6 @@ export const baseProviders = [ }) }, supportsImageGeneration: true - }, - { - id: 'huggingface', - name: 'HuggingFace', - creator: createHuggingFace, - supportsImageGeneration: true } ] as const satisfies BaseProvider[] diff --git a/src/renderer/src/aiCore/utils/options.ts b/src/renderer/src/aiCore/utils/options.ts index eacb1b9e05..7f4cd33608 100644 --- a/src/renderer/src/aiCore/utils/options.ts +++ b/src/renderer/src/aiCore/utils/options.ts @@ -99,9 +99,6 @@ export function buildProviderOptions( serviceTier: serviceTierSetting } break - case 'huggingface': - providerSpecificOptions = buildOpenAIProviderOptions(assistant, model, capabilities) - break case 'anthropic': providerSpecificOptions = buildAnthropicProviderOptions(assistant, model, capabilities) break @@ -144,6 +141,9 @@ export function buildProviderOptions( case 'bedrock': providerSpecificOptions = buildBedrockProviderOptions(assistant, model, capabilities) break + case 'huggingface': + providerSpecificOptions = buildOpenAIProviderOptions(assistant, model, capabilities) + break default: // 对于其他 provider,使用通用的构建逻辑 providerSpecificOptions = { diff --git a/yarn.lock b/yarn.lock index 0ee7c9401f..507486508b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1891,30 +1891,30 @@ __metadata: languageName: node linkType: hard -"@cherrystudio/ai-core@workspace:^1.0.0-alpha.18, @cherrystudio/ai-core@workspace:packages/aiCore": +"@cherrystudio/ai-core@workspace:^1.0.6, @cherrystudio/ai-core@workspace:packages/aiCore": version: 0.0.0-use.local resolution: "@cherrystudio/ai-core@workspace:packages/aiCore" dependencies: "@ai-sdk/anthropic": "npm:^2.0.43" "@ai-sdk/azure": "npm:^2.0.66" "@ai-sdk/deepseek": "npm:^1.0.27" - "@ai-sdk/google": "patch:@ai-sdk/google@npm%3A2.0.36#~/.yarn/patches/@ai-sdk-google-npm-2.0.36-6f3cc06026.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/provider": "npm:^2.0.0" "@ai-sdk/provider-utils": "npm:^3.0.16" "@ai-sdk/xai": "npm:^2.0.31" - "@cherrystudio/ai-sdk-provider": "workspace:*" + "@cherrystudio/ai-sdk-provider": "npm:^0.1.1" tsdown: "npm:^0.12.9" typescript: "npm:^5.0.0" vitest: "npm:^3.2.4" zod: "npm:^4.1.5" peerDependencies: + "@ai-sdk/google": ^2.0.36 + "@ai-sdk/openai": ^2.0.64 ai: ^5.0.26 languageName: unknown linkType: soft -"@cherrystudio/ai-sdk-provider@workspace:*, @cherrystudio/ai-sdk-provider@workspace:packages/ai-sdk-provider": +"@cherrystudio/ai-sdk-provider@npm:^0.1.1, @cherrystudio/ai-sdk-provider@workspace:packages/ai-sdk-provider": version: 0.0.0-use.local resolution: "@cherrystudio/ai-sdk-provider@workspace:packages/ai-sdk-provider" dependencies: @@ -9910,6 +9910,7 @@ __metadata: "@ai-sdk/google-vertex": "npm:^3.0.68" "@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/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" "@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" @@ -9919,7 +9920,7 @@ __metadata: "@aws-sdk/client-bedrock-runtime": "npm:^3.910.0" "@aws-sdk/client-s3": "npm:^3.910.0" "@biomejs/biome": "npm:2.2.4" - "@cherrystudio/ai-core": "workspace:^1.0.0-alpha.18" + "@cherrystudio/ai-core": "workspace:^1.0.6" "@cherrystudio/embedjs": "npm:^0.1.31" "@cherrystudio/embedjs-libsql": "npm:^0.1.31" "@cherrystudio/embedjs-loader-csv": "npm:^0.1.31" From 77529b3cd3b4773c4858b51726bf89618ed31382 Mon Sep 17 00:00:00 2001 From: MyPrototypeWhat Date: Wed, 19 Nov 2025 20:44:22 +0800 Subject: [PATCH 4/7] chore: update ai-core release scripts and bump version to 1.0.7 (#11370) * chore: update ai-core release scripts and bump version to 1.0.7 * chore: update ai-sdk-provider release script to include build step and enhance type exports in webSearchPlugin and providers * chore: bump @cherrystudio/ai-core version to 1.0.8 and update dependencies in package.json and yarn.lock * chore: bump @cherrystudio/ai-core version to 1.0.9 and @cherrystudio/ai-sdk-provider version to 0.1.2 in package.json and yarn.lock --------- Co-authored-by: suyao --- package.json | 10 +++++----- packages/ai-sdk-provider/package.json | 2 +- packages/aiCore/package.json | 4 ++-- packages/aiCore/src/core/plugins/built-in/index.ts | 13 ++++--------- .../core/plugins/built-in/webSearchPlugin/index.ts | 2 +- packages/aiCore/src/core/providers/index.ts | 2 +- yarn.lock | 8 ++++---- 7 files changed, 18 insertions(+), 23 deletions(-) diff --git a/package.json b/package.json index 284843dc3a..36fe62337c 100644 --- a/package.json +++ b/package.json @@ -74,10 +74,10 @@ "format:check": "biome format && biome lint", "prepare": "git config blame.ignoreRevsFile .git-blame-ignore-revs && husky", "claude": "dotenv -e .env -- claude", - "release:aicore:alpha": "yarn workspace @cherrystudio/ai-core version prerelease --immediate && yarn workspace @cherrystudio/ai-core npm publish --tag alpha --access public", - "release:aicore:beta": "yarn workspace @cherrystudio/ai-core version prerelease --immediate && yarn workspace @cherrystudio/ai-core npm publish --tag beta --access public", - "release:aicore": "yarn workspace @cherrystudio/ai-core version patch --immediate && yarn workspace @cherrystudio/ai-core npm publish --access public", - "release:ai-sdk-provider": "yarn workspace @cherrystudio/ai-sdk-provider version patch --immediate && yarn workspace @cherrystudio/ai-sdk-provider npm publish --access public" + "release:aicore:alpha": "yarn workspace @cherrystudio/ai-core version prerelease --preid alpha --immediate && yarn workspace @cherrystudio/ai-core build && yarn workspace @cherrystudio/ai-core npm publish --tag alpha --access public", + "release:aicore:beta": "yarn workspace @cherrystudio/ai-core version prerelease --preid beta --immediate && yarn workspace @cherrystudio/ai-core build && yarn workspace @cherrystudio/ai-core npm publish --tag beta --access public", + "release:aicore": "yarn workspace @cherrystudio/ai-core version patch --immediate && yarn workspace @cherrystudio/ai-core build && yarn workspace @cherrystudio/ai-core npm publish --access public", + "release:ai-sdk-provider": "yarn workspace @cherrystudio/ai-sdk-provider version patch --immediate && yarn workspace @cherrystudio/ai-sdk-provider build && yarn workspace @cherrystudio/ai-sdk-provider npm publish --access public" }, "dependencies": { "@anthropic-ai/claude-agent-sdk": "patch:@anthropic-ai/claude-agent-sdk@npm%3A0.1.30#~/.yarn/patches/@anthropic-ai-claude-agent-sdk-npm-0.1.30-b50a299674.patch", @@ -125,7 +125,7 @@ "@aws-sdk/client-bedrock-runtime": "^3.910.0", "@aws-sdk/client-s3": "^3.910.0", "@biomejs/biome": "2.2.4", - "@cherrystudio/ai-core": "workspace:^1.0.6", + "@cherrystudio/ai-core": "workspace:^1.0.9", "@cherrystudio/embedjs": "^0.1.31", "@cherrystudio/embedjs-libsql": "^0.1.31", "@cherrystudio/embedjs-loader-csv": "^0.1.31", diff --git a/packages/ai-sdk-provider/package.json b/packages/ai-sdk-provider/package.json index 2f948d6bb7..bf509ee963 100644 --- a/packages/ai-sdk-provider/package.json +++ b/packages/ai-sdk-provider/package.json @@ -1,6 +1,6 @@ { "name": "@cherrystudio/ai-sdk-provider", - "version": "0.1.1", + "version": "0.1.2", "description": "Cherry Studio AI SDK provider bundle with CherryIN routing.", "keywords": [ "ai-sdk", diff --git a/packages/aiCore/package.json b/packages/aiCore/package.json index 1fc66feddf..fbbea52d40 100644 --- a/packages/aiCore/package.json +++ b/packages/aiCore/package.json @@ -1,6 +1,6 @@ { "name": "@cherrystudio/ai-core", - "version": "1.0.6", + "version": "1.0.9", "description": "Cherry Studio AI Core - Unified AI Provider Interface Based on Vercel AI SDK", "main": "dist/index.js", "module": "dist/index.mjs", @@ -35,6 +35,7 @@ "peerDependencies": { "@ai-sdk/google": "^2.0.36", "@ai-sdk/openai": "^2.0.64", + "@cherrystudio/ai-sdk-provider": "^0.1.2", "ai": "^5.0.26" }, "dependencies": { @@ -45,7 +46,6 @@ "@ai-sdk/provider": "^2.0.0", "@ai-sdk/provider-utils": "^3.0.16", "@ai-sdk/xai": "^2.0.31", - "@cherrystudio/ai-sdk-provider": "^0.1.1", "zod": "^4.1.5" }, "devDependencies": { diff --git a/packages/aiCore/src/core/plugins/built-in/index.ts b/packages/aiCore/src/core/plugins/built-in/index.ts index 1f8916b09a..d7f35d0cd1 100644 --- a/packages/aiCore/src/core/plugins/built-in/index.ts +++ b/packages/aiCore/src/core/plugins/built-in/index.ts @@ -4,12 +4,7 @@ */ export const BUILT_IN_PLUGIN_PREFIX = 'built-in:' -export { googleToolsPlugin } from './googleToolsPlugin' -export { createLoggingPlugin } from './logging' -export { createPromptToolUsePlugin } from './toolUsePlugin/promptToolUsePlugin' -export type { - PromptToolUseConfig, - ToolUseRequestContext, - ToolUseResult -} from './toolUsePlugin/type' -export { webSearchPlugin, type WebSearchPluginConfig } from './webSearchPlugin' +export * from './googleToolsPlugin' +export * from './toolUsePlugin/promptToolUsePlugin' +export * from './toolUsePlugin/type' +export * from './webSearchPlugin' diff --git a/packages/aiCore/src/core/plugins/built-in/webSearchPlugin/index.ts b/packages/aiCore/src/core/plugins/built-in/webSearchPlugin/index.ts index 23ea952323..75692cdf36 100644 --- a/packages/aiCore/src/core/plugins/built-in/webSearchPlugin/index.ts +++ b/packages/aiCore/src/core/plugins/built-in/webSearchPlugin/index.ts @@ -32,7 +32,7 @@ export const webSearchPlugin = (config: WebSearchPluginConfig = DEFAULT_WEB_SEAR }) // 导出类型定义供开发者使用 -export type { WebSearchPluginConfig, WebSearchToolOutputSchema } from './helper' +export * from './helper' // 默认导出 export default webSearchPlugin diff --git a/packages/aiCore/src/core/providers/index.ts b/packages/aiCore/src/core/providers/index.ts index 3ac445cb22..b9ebd6f682 100644 --- a/packages/aiCore/src/core/providers/index.ts +++ b/packages/aiCore/src/core/providers/index.ts @@ -44,7 +44,7 @@ export { // ==================== 基础数据和类型 ==================== // 基础Provider数据源 -export { baseProviderIds, baseProviders } from './schemas' +export { baseProviderIds, baseProviders, isBaseProvider } from './schemas' // 类型定义和Schema export type { diff --git a/yarn.lock b/yarn.lock index 507486508b..18bde2062e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1891,7 +1891,7 @@ __metadata: languageName: node linkType: hard -"@cherrystudio/ai-core@workspace:^1.0.6, @cherrystudio/ai-core@workspace:packages/aiCore": +"@cherrystudio/ai-core@workspace:^1.0.9, @cherrystudio/ai-core@workspace:packages/aiCore": version: 0.0.0-use.local resolution: "@cherrystudio/ai-core@workspace:packages/aiCore" dependencies: @@ -1902,7 +1902,6 @@ __metadata: "@ai-sdk/provider": "npm:^2.0.0" "@ai-sdk/provider-utils": "npm:^3.0.16" "@ai-sdk/xai": "npm:^2.0.31" - "@cherrystudio/ai-sdk-provider": "npm:^0.1.1" tsdown: "npm:^0.12.9" typescript: "npm:^5.0.0" vitest: "npm:^3.2.4" @@ -1910,11 +1909,12 @@ __metadata: peerDependencies: "@ai-sdk/google": ^2.0.36 "@ai-sdk/openai": ^2.0.64 + "@cherrystudio/ai-sdk-provider": ^0.1.2 ai: ^5.0.26 languageName: unknown linkType: soft -"@cherrystudio/ai-sdk-provider@npm:^0.1.1, @cherrystudio/ai-sdk-provider@workspace:packages/ai-sdk-provider": +"@cherrystudio/ai-sdk-provider@workspace:packages/ai-sdk-provider": version: 0.0.0-use.local resolution: "@cherrystudio/ai-sdk-provider@workspace:packages/ai-sdk-provider" dependencies: @@ -9920,7 +9920,7 @@ __metadata: "@aws-sdk/client-bedrock-runtime": "npm:^3.910.0" "@aws-sdk/client-s3": "npm:^3.910.0" "@biomejs/biome": "npm:2.2.4" - "@cherrystudio/ai-core": "workspace:^1.0.6" + "@cherrystudio/ai-core": "workspace:^1.0.9" "@cherrystudio/embedjs": "npm:^0.1.31" "@cherrystudio/embedjs-libsql": "npm:^0.1.31" "@cherrystudio/embedjs-loader-csv": "npm:^0.1.31" From 62976f6fe0b2b144b910aeabb46e4d2f34245aaf Mon Sep 17 00:00:00 2001 From: defi-failure <159208748+defi-failure@users.noreply.github.com> Date: Thu, 20 Nov 2025 10:35:11 +0800 Subject: [PATCH 5/7] refactor: namespace tool call ids with session id to prevent conflicts (#11319) --- .../claudecode/__tests__/transform.test.ts | 6 +- .../claudecode/claude-stream-state.ts | 69 +++++++++++++++---- .../agents/services/claudecode/index.ts | 8 ++- .../services/claudecode/tool-permissions.ts | 27 +++++++- .../agents/services/claudecode/transform.ts | 10 +-- .../Tools/ToolPermissionRequestCard.tsx | 6 +- src/renderer/src/store/toolPermissions.ts | 7 +- 7 files changed, 99 insertions(+), 34 deletions(-) diff --git a/src/main/services/agents/services/claudecode/__tests__/transform.test.ts b/src/main/services/agents/services/claudecode/__tests__/transform.test.ts index 8f8c1df038..e38f897f2a 100644 --- a/src/main/services/agents/services/claudecode/__tests__/transform.test.ts +++ b/src/main/services/agents/services/claudecode/__tests__/transform.test.ts @@ -25,7 +25,7 @@ describe('stripLocalCommandTags', () => { describe('Claude → AiSDK transform', () => { it('handles tool call streaming lifecycle', () => { - const state = new ClaudeStreamState() + const state = new ClaudeStreamState({ agentSessionId: baseStreamMetadata.session_id }) const parts: ReturnType[number][] = [] const messages: SDKMessage[] = [ @@ -182,14 +182,14 @@ describe('Claude → AiSDK transform', () => { (typeof parts)[number], { type: 'tool-result' } > - expect(toolResult.toolCallId).toBe('tool-1') + expect(toolResult.toolCallId).toBe('session-123:tool-1') expect(toolResult.toolName).toBe('Bash') expect(toolResult.input).toEqual({ command: 'ls' }) expect(toolResult.output).toBe('ok') }) it('handles streaming text completion', () => { - const state = new ClaudeStreamState() + const state = new ClaudeStreamState({ agentSessionId: baseStreamMetadata.session_id }) const parts: ReturnType[number][] = [] const messages: SDKMessage[] = [ diff --git a/src/main/services/agents/services/claudecode/claude-stream-state.ts b/src/main/services/agents/services/claudecode/claude-stream-state.ts index 078f048ce8..19a664a8d1 100644 --- a/src/main/services/agents/services/claudecode/claude-stream-state.ts +++ b/src/main/services/agents/services/claudecode/claude-stream-state.ts @@ -10,8 +10,21 @@ * 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. */ +import { loggerService } from '@logger' 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). */ @@ -34,6 +47,7 @@ type ReasoningBlockState = BaseBlockState & { type ToolBlockState = BaseBlockState & { kind: 'tool' toolCallId: string + rawToolCallId: string toolName: string inputBuffer: string providerMetadata?: ProviderMetadata @@ -48,12 +62,17 @@ type PendingUsageState = { } type PendingToolCall = { + rawToolCallId: string toolCallId: string toolName: string input: unknown providerMetadata?: ProviderMetadata } +type ClaudeStreamStateOptions = { + agentSessionId: string +} + /** * Tracks the lifecycle of Claude streaming blocks (text, thinking, tool calls) * across individual websocket events. The transformer relies on this class to @@ -61,12 +80,20 @@ type PendingToolCall = { * usage/finish metadata once Anthropic closes a message. */ export class ClaudeStreamState { + private logger + private readonly agentSessionId: string private blocksByIndex = new Map() - private toolIndexById = new Map() + private toolIndexByNamespacedId = new Map() private pendingUsage: PendingUsageState = {} private pendingToolCalls = new Map() 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. */ beginStep(): void { this.stepActive = true @@ -104,19 +131,21 @@ export class ClaudeStreamState { /** Caches tool metadata so subsequent input deltas and results can find it. */ openToolBlock( index: number, - params: { toolCallId: string; toolName: string; providerMetadata?: ProviderMetadata } + params: { rawToolCallId: string; toolName: string; providerMetadata?: ProviderMetadata } ): ToolBlockState { + const toolCallId = buildNamespacedToolCallId(this.agentSessionId, params.rawToolCallId) const block: ToolBlockState = { kind: 'tool', - id: params.toolCallId, + id: toolCallId, index, - toolCallId: params.toolCallId, + toolCallId, + rawToolCallId: params.rawToolCallId, toolName: params.toolName, inputBuffer: '', providerMetadata: params.providerMetadata } this.blocksByIndex.set(index, block) - this.toolIndexById.set(params.toolCallId, index) + this.toolIndexByNamespacedId.set(toolCallId, index) return block } @@ -125,13 +154,17 @@ export class ClaudeStreamState { } getToolBlockById(toolCallId: string): ToolBlockState | undefined { - const index = this.toolIndexById.get(toolCallId) + const index = this.toolIndexByNamespacedId.get(toolCallId) if (index === undefined) return undefined const block = this.blocksByIndex.get(index) if (!block || block.kind !== 'tool') return undefined 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. */ appendTextDelta(index: number, text: string): TextBlockState | undefined { const block = this.blocksByIndex.get(index) @@ -158,10 +191,12 @@ export class ClaudeStreamState { /** Records a tool call to be consumed once its result arrives from the user. */ registerToolCall( - toolCallId: string, + rawToolCallId: string, payload: { toolName: string; input: unknown; providerMetadata?: ProviderMetadata } ): void { - this.pendingToolCalls.set(toolCallId, { + const toolCallId = buildNamespacedToolCallId(this.agentSessionId, rawToolCallId) + this.pendingToolCalls.set(rawToolCallId, { + rawToolCallId, toolCallId, toolName: payload.toolName, input: payload.input, @@ -170,10 +205,10 @@ export class ClaudeStreamState { } /** Retrieves and clears the buffered tool call metadata for the given id. */ - consumePendingToolCall(toolCallId: string): PendingToolCall | undefined { - const entry = this.pendingToolCalls.get(toolCallId) + consumePendingToolCall(rawToolCallId: string): PendingToolCall | undefined { + const entry = this.pendingToolCalls.get(rawToolCallId) if (entry) { - this.pendingToolCalls.delete(toolCallId) + this.pendingToolCalls.delete(rawToolCallId) } return entry } @@ -183,12 +218,12 @@ export class ClaudeStreamState { * completion so that downstream tool results can reference the original call. */ completeToolBlock(toolCallId: string, input: unknown, providerMetadata?: ProviderMetadata): void { + const block = this.getToolBlockByRawId(toolCallId) this.registerToolCall(toolCallId, { - toolName: this.getToolBlockById(toolCallId)?.toolName ?? 'unknown', + toolName: block?.toolName ?? 'unknown', input, providerMetadata }) - const block = this.getToolBlockById(toolCallId) if (block) { block.resolvedInput = input } @@ -200,7 +235,7 @@ export class ClaudeStreamState { if (!block) return undefined this.blocksByIndex.delete(index) if (block.kind === 'tool') { - this.toolIndexById.delete(block.toolCallId) + this.toolIndexByNamespacedId.delete(block.toolCallId) } return block } @@ -227,7 +262,7 @@ export class ClaudeStreamState { /** Drops cached block metadata for the currently active message. */ resetBlocks(): void { this.blocksByIndex.clear() - this.toolIndexById.clear() + this.toolIndexByNamespacedId.clear() } /** Resets the entire step lifecycle after emitting a terminal frame. */ @@ -236,6 +271,10 @@ export class ClaudeStreamState { this.resetPendingUsage() this.stepActive = false } + + getNamespacedToolCallId(rawToolCallId: string): string { + return buildNamespacedToolCallId(this.agentSessionId, rawToolCallId) + } } export type { PendingToolCall } diff --git a/src/main/services/agents/services/claudecode/index.ts b/src/main/services/agents/services/claudecode/index.ts index a8f3f54fa8..327031d2f3 100644 --- a/src/main/services/agents/services/claudecode/index.ts +++ b/src/main/services/agents/services/claudecode/index.ts @@ -13,6 +13,7 @@ import { app } from 'electron' import type { GetAgentSessionResponse } from '../..' import type { AgentServiceInterface, AgentStream, AgentStreamEvent } from '../../interfaces/AgentStreamInterface' import { sessionService } from '../SessionService' +import { buildNamespacedToolCallId } from './claude-stream-state' import { promptForToolApproval } from './tool-permissions' import { ClaudeStreamState, transformSDKMessageToStreamParts } from './transform' @@ -150,7 +151,10 @@ class ClaudeCodeService implements AgentServiceInterface { return { behavior: 'allow', updatedInput: input } } - return promptForToolApproval(toolName, input, options) + return promptForToolApproval(toolName, input, { + ...options, + toolCallId: buildNamespacedToolCallId(session.id, options.toolUseID) + }) } // Build SDK options from parameters @@ -346,7 +350,7 @@ class ClaudeCodeService implements AgentServiceInterface { const jsonOutput: SDKMessage[] = [] let hasCompleted = false const startTime = Date.now() - const streamState = new ClaudeStreamState() + const streamState = new ClaudeStreamState({ agentSessionId: sessionId }) try { for await (const message of query({ prompt: promptStream, options })) { diff --git a/src/main/services/agents/services/claudecode/tool-permissions.ts b/src/main/services/agents/services/claudecode/tool-permissions.ts index c95f4c679e..5b50f4567e 100644 --- a/src/main/services/agents/services/claudecode/tool-permissions.ts +++ b/src/main/services/agents/services/claudecode/tool-permissions.ts @@ -37,6 +37,7 @@ type RendererPermissionRequestPayload = { requestId: string toolName: string toolId: string + toolCallId: string description?: string requiresPermissions: boolean input: Record @@ -206,10 +207,19 @@ 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( toolName: string, input: Record, - options?: { signal: AbortSignal; suggestions?: PermissionUpdate[] } + options: PromptForToolApprovalOptions ): Promise { if (shouldAutoApproveTools) { logger.debug('promptForToolApproval auto-approving tool for test', { @@ -245,6 +255,7 @@ export async function promptForToolApproval( logger.info('Requesting user approval for tool usage', { requestId, toolName, + toolCallId: options.toolCallId, description: toolMetadata?.description }) @@ -252,6 +263,7 @@ export async function promptForToolApproval( requestId, toolName, toolId: toolMetadata?.id ?? toolName, + toolCallId: options.toolCallId, description: toolMetadata?.description, requiresPermissions: toolMetadata?.requirePermissions ?? false, input: sanitizedInput, @@ -266,6 +278,7 @@ export async function promptForToolApproval( logger.debug('Registering tool permission request', { requestId, toolName, + toolCallId: options.toolCallId, requiresPermissions: requestPayload.requiresPermissions, timeoutMs: TOOL_APPROVAL_TIMEOUT_MS, suggestionCount: sanitizedSuggestions.length @@ -273,7 +286,11 @@ export async function promptForToolApproval( return new Promise((resolve) => { const timeout = setTimeout(() => { - logger.info('User tool permission request timed out', { requestId, toolName }) + logger.info('User tool permission request timed out', { + requestId, + toolName, + toolCallId: options.toolCallId + }) finalizeRequest(requestId, { behavior: 'deny', message: 'Timed out waiting for approval' }, 'timeout') }, TOOL_APPROVAL_TIMEOUT_MS) @@ -287,7 +304,11 @@ export async function promptForToolApproval( if (options?.signal) { const abortListener = () => { - logger.info('Tool permission request aborted before user responded', { requestId, toolName }) + logger.info('Tool permission request aborted before user responded', { + requestId, + toolName, + toolCallId: options.toolCallId + }) finalizeRequest(requestId, defaultDenyUpdate, 'aborted') } diff --git a/src/main/services/agents/services/claudecode/transform.ts b/src/main/services/agents/services/claudecode/transform.ts index 41285175b4..cbd2f735d6 100644 --- a/src/main/services/agents/services/claudecode/transform.ts +++ b/src/main/services/agents/services/claudecode/transform.ts @@ -243,9 +243,10 @@ function handleAssistantToolUse( state: ClaudeStreamState, chunks: AgentStreamPart[] ): void { + const toolCallId = state.getNamespacedToolCallId(block.id) chunks.push({ type: 'tool-call', - toolCallId: block.id, + toolCallId, toolName: block.name, input: block.input, providerExecuted: true, @@ -331,10 +332,11 @@ function handleUserMessage( if (block.type === 'tool_result') { const toolResult = block as ToolResultContent const pendingCall = state.consumePendingToolCall(toolResult.tool_use_id) + const toolCallId = pendingCall?.toolCallId ?? state.getNamespacedToolCallId(toolResult.tool_use_id) if (toolResult.is_error) { chunks.push({ type: 'tool-error', - toolCallId: toolResult.tool_use_id, + toolCallId, toolName: pendingCall?.toolName ?? 'unknown', input: pendingCall?.input, error: toolResult.content, @@ -343,7 +345,7 @@ function handleUserMessage( } else { chunks.push({ type: 'tool-result', - toolCallId: toolResult.tool_use_id, + toolCallId, toolName: pendingCall?.toolName ?? 'unknown', input: pendingCall?.input, output: toolResult.content, @@ -514,7 +516,7 @@ function handleContentBlockStart( } case 'tool_use': { const block = state.openToolBlock(index, { - toolCallId: contentBlock.id, + rawToolCallId: contentBlock.id, toolName: contentBlock.name, providerMetadata }) diff --git a/src/renderer/src/pages/home/Messages/Tools/ToolPermissionRequestCard.tsx b/src/renderer/src/pages/home/Messages/Tools/ToolPermissionRequestCard.tsx index 7df8544a6f..1fd2023b38 100644 --- a/src/renderer/src/pages/home/Messages/Tools/ToolPermissionRequestCard.tsx +++ b/src/renderer/src/pages/home/Messages/Tools/ToolPermissionRequestCard.tsx @@ -1,7 +1,7 @@ import type { PermissionUpdate } from '@anthropic-ai/claude-agent-sdk' import { loggerService } from '@logger' import { useAppDispatch, useAppSelector } from '@renderer/store' -import { selectPendingPermissionByToolName, toolPermissionsActions } from '@renderer/store/toolPermissions' +import { selectPendingPermission, toolPermissionsActions } from '@renderer/store/toolPermissions' import type { NormalToolResponse } from '@renderer/types' import { Button } from 'antd' import { ChevronDown, CirclePlay, CircleX } from 'lucide-react' @@ -17,9 +17,7 @@ interface Props { export function ToolPermissionRequestCard({ toolResponse }: Props) { const { t } = useTranslation() const dispatch = useAppDispatch() - const request = useAppSelector((state) => - selectPendingPermissionByToolName(state.toolPermissions, toolResponse.tool.name) - ) + const request = useAppSelector((state) => selectPendingPermission(state.toolPermissions, toolResponse.toolCallId)) const [now, setNow] = useState(() => Date.now()) const [showDetails, setShowDetails] = useState(false) diff --git a/src/renderer/src/store/toolPermissions.ts b/src/renderer/src/store/toolPermissions.ts index a7ac87482e..59ff971329 100644 --- a/src/renderer/src/store/toolPermissions.ts +++ b/src/renderer/src/store/toolPermissions.ts @@ -6,6 +6,7 @@ export type ToolPermissionRequestPayload = { requestId: string toolName: string toolId: string + toolCallId: string description?: string requiresPermissions: boolean input: Record @@ -82,12 +83,12 @@ export const selectActiveToolPermission = (state: ToolPermissionsState): ToolPer return activeEntries[0] } -export const selectPendingPermissionByToolName = ( +export const selectPendingPermission = ( state: ToolPermissionsState, - toolName: string + toolCallId: string ): ToolPermissionEntry | undefined => { const activeEntries = Object.values(state.requests) - .filter((entry) => entry.toolName === toolName) + .filter((entry) => entry.toolCallId === toolCallId) .filter( (entry) => entry.status === 'pending' || entry.status === 'submitting-allow' || entry.status === 'submitting-deny' ) From 2df8bb58df8aea6bd1ac834b1af4cb052cf5e64e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=A2=E5=A5=8B=E7=8C=AB?= Date: Thu, 20 Nov 2025 10:41:41 +0800 Subject: [PATCH 6/7] fix: remove light background from MCP NpxUv install alerts (#11372) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove 'banner' prop from Alert components in InstallNpxUv - Set SettingContainer background to 'inherit' in MCP settings - Fixes the light background color issue in NpxUv interface 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude --- src/renderer/src/pages/settings/MCPSettings/InstallNpxUv.tsx | 2 -- src/renderer/src/pages/settings/MCPSettings/index.tsx | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/renderer/src/pages/settings/MCPSettings/InstallNpxUv.tsx b/src/renderer/src/pages/settings/MCPSettings/InstallNpxUv.tsx index b30ba5e7d7..cbaa57bf0a 100644 --- a/src/renderer/src/pages/settings/MCPSettings/InstallNpxUv.tsx +++ b/src/renderer/src/pages/settings/MCPSettings/InstallNpxUv.tsx @@ -109,7 +109,6 @@ const InstallNpxUv: FC = ({ mini = false }) => { @@ -140,7 +139,6 @@ const InstallNpxUv: FC = ({ mini = false }) => { /> diff --git a/src/renderer/src/pages/settings/MCPSettings/index.tsx b/src/renderer/src/pages/settings/MCPSettings/index.tsx index 987b6cd0d6..4637cf2e89 100644 --- a/src/renderer/src/pages/settings/MCPSettings/index.tsx +++ b/src/renderer/src/pages/settings/MCPSettings/index.tsx @@ -140,7 +140,7 @@ const MCPSettings: FC = () => { + } From 0f1a487bb04d7b3b7bfc752d81821a684c8dab63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=A2=E5=A5=8B=E7=8C=AB?= Date: Thu, 20 Nov 2025 10:42:49 +0800 Subject: [PATCH 7/7] refactor: simplify agent creation form (#11369) * refactor(AgentModal): simplify agent type handling and update default values - Removed unused agent type options and related logic. - Updated default agent name from 'Claude Code' to 'Agent'. - Adjusted padding in button styles and textarea rows for better UI consistency. - Cleaned up unnecessary imports and code comments for improved readability. * refactor(AgentSettings): clean up and enhance name setting component - Removed unused imports and commented-out code in AgentModal and EssentialSettings. - Updated NameSetting to include an emoji avatar picker for enhanced user experience. - Simplified the logic for updating the agent's name and avatar. - Improved overall readability and maintainability of the code. --- .../components/Popups/agent/AgentModal.tsx | 102 +++--------------- .../AgentSettings/EssentialSettings.tsx | 26 +---- .../settings/AgentSettings/NameSetting.tsx | 61 ++++++++--- 3 files changed, 63 insertions(+), 126 deletions(-) diff --git a/src/renderer/src/components/Popups/agent/AgentModal.tsx b/src/renderer/src/components/Popups/agent/AgentModal.tsx index d504699399..2574cbe669 100644 --- a/src/renderer/src/components/Popups/agent/AgentModal.tsx +++ b/src/renderer/src/components/Popups/agent/AgentModal.tsx @@ -1,5 +1,4 @@ import { loggerService } from '@logger' -import ClaudeIcon from '@renderer/assets/images/models/claude.png' import { ErrorBoundary } from '@renderer/components/ErrorBoundary' import { TopView } from '@renderer/components/TopView' import { permissionModeCards } from '@renderer/config/agent' @@ -9,7 +8,6 @@ import SelectAgentBaseModelButton from '@renderer/pages/home/components/SelectAg import type { AddAgentForm, AgentEntity, - AgentType, ApiModel, BaseAgentForm, PermissionMode, @@ -17,30 +15,22 @@ import type { UpdateAgentForm } from '@renderer/types' import { AgentConfigurationSchema, isAgentType } from '@renderer/types' -import { Avatar, Button, Input, Modal, Select } from 'antd' +import { Button, Input, Modal, Select } from 'antd' import { AlertTriangleIcon } from 'lucide-react' import type { ChangeEvent, FormEvent } from 'react' import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' -import type { BaseOption } from './shared' - const { TextArea } = Input const logger = loggerService.withContext('AddAgentPopup') -interface AgentTypeOption extends BaseOption { - type: 'type' - key: AgentEntity['type'] - name: AgentEntity['name'] -} - type AgentWithTools = AgentEntity & { tools?: Tool[] } const buildAgentForm = (existing?: AgentWithTools): BaseAgentForm => ({ type: existing?.type ?? 'claude-code', - name: existing?.name ?? 'Claude Code', + name: existing?.name ?? 'Agent', description: existing?.description, instructions: existing?.instructions, model: existing?.model ?? '', @@ -100,54 +90,6 @@ const PopupContainer: React.FC = ({ 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: ( - - - {option.label} - - ) - })), - [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) => { setForm((prev) => ({ ...prev, @@ -155,12 +97,12 @@ const PopupContainer: React.FC = ({ agent, afterSubmit, resolve }) => { })) }, []) - const onDescChange = useCallback((e: ChangeEvent) => { - setForm((prev) => ({ - ...prev, - description: e.target.value - })) - }, []) + // const onDescChange = useCallback((e: ChangeEvent) => { + // setForm((prev) => ({ + // ...prev, + // description: e.target.value + // })) + // }, []) const onInstChange = useCallback((e: ChangeEvent) => { setForm((prev) => ({ @@ -334,16 +276,6 @@ const PopupContainer: React.FC = ({ agent, afterSubmit, resolve }) => { - - -