Merge branch 'main' into betterSqlite

This commit is contained in:
beyondkmp 2025-11-20 15:17:57 +08:00
commit 67e032344b
23 changed files with 198 additions and 204 deletions

View File

@ -74,9 +74,10 @@
"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 --immediate && yarn workspace @cherrystudio/ai-core npm publish --tag alpha --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 --immediate && yarn workspace @cherrystudio/ai-core npm publish --tag beta --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 npm publish --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",
"rebuild": "electron-rebuild -f -w better-sqlite3", "rebuild": "electron-rebuild -f -w better-sqlite3",
"postinstall": "electron-builder install-app-deps" "postinstall": "electron-builder install-app-deps"
}, },
@ -118,6 +119,7 @@
"@ai-sdk/google-vertex": "^3.0.68", "@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/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",
@ -126,7 +128,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.0-alpha.18", "@cherrystudio/ai-core": "workspace:^1.0.9",
"@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",

View File

@ -1,6 +1,6 @@
{ {
"name": "@cherrystudio/ai-sdk-provider", "name": "@cherrystudio/ai-sdk-provider",
"version": "0.1.0", "version": "0.1.2",
"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 npm install @cherrystudio/ai-core ai @ai-sdk/google @ai-sdk/openai
``` ```
### React Native ### React Native

View File

@ -1,6 +1,6 @@
{ {
"name": "@cherrystudio/ai-core", "name": "@cherrystudio/ai-core",
"version": "1.0.1", "version": "1.0.9",
"description": "Cherry Studio AI Core - Unified AI Provider Interface Based on Vercel AI SDK", "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.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/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,12 +4,7 @@
*/ */
export const BUILT_IN_PLUGIN_PREFIX = 'built-in:' export const BUILT_IN_PLUGIN_PREFIX = 'built-in:'
export { googleToolsPlugin } from './googleToolsPlugin' export * from './googleToolsPlugin'
export { createLoggingPlugin } from './logging' export * from './toolUsePlugin/promptToolUsePlugin'
export { createPromptToolUsePlugin } from './toolUsePlugin/promptToolUsePlugin' export * from './toolUsePlugin/type'
export type { export * from './webSearchPlugin'
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 type { WebSearchPluginConfig, WebSearchToolOutputSchema } from './helper' export * from './helper'
// 默认导出 // 默认导出
export default webSearchPlugin export default webSearchPlugin

View File

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

View File

@ -7,7 +7,6 @@ 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'
@ -33,8 +32,7 @@ export const baseProviderIds = [
'deepseek', 'deepseek',
'openrouter', 'openrouter',
'cherryin', 'cherryin',
'cherryin-chat', 'cherryin-chat'
'huggingface'
] as const ] as const
/** /**
@ -158,12 +156,6 @@ 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

@ -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() const state = new ClaudeStreamState({ agentSessionId: baseStreamMetadata.session_id })
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('tool-1') expect(toolResult.toolCallId).toBe('session-123: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() const state = new ClaudeStreamState({ agentSessionId: baseStreamMetadata.session_id })
const parts: ReturnType<typeof transformSDKMessageToStreamParts>[number][] = [] const parts: ReturnType<typeof transformSDKMessageToStreamParts>[number][] = []
const messages: SDKMessage[] = [ const messages: SDKMessage[] = [

View File

@ -10,8 +10,21 @@
* 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).
*/ */
@ -34,6 +47,7 @@ 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
@ -48,12 +62,17 @@ 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
@ -61,12 +80,20 @@ type PendingToolCall = {
* 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 toolIndexById = new Map<string, number>() private toolIndexByNamespacedId = 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
@ -104,19 +131,21 @@ 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: { toolCallId: string; toolName: string; providerMetadata?: ProviderMetadata } params: { rawToolCallId: 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: params.toolCallId, id: toolCallId,
index, index,
toolCallId: params.toolCallId, 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.toolIndexById.set(params.toolCallId, index) this.toolIndexByNamespacedId.set(toolCallId, index)
return block return block
} }
@ -125,13 +154,17 @@ export class ClaudeStreamState {
} }
getToolBlockById(toolCallId: string): ToolBlockState | undefined { getToolBlockById(toolCallId: string): ToolBlockState | undefined {
const index = this.toolIndexById.get(toolCallId) const index = this.toolIndexByNamespacedId.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)
@ -158,10 +191,12 @@ 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(
toolCallId: string, rawToolCallId: string,
payload: { toolName: string; input: unknown; providerMetadata?: ProviderMetadata } payload: { toolName: string; input: unknown; providerMetadata?: ProviderMetadata }
): void { ): void {
this.pendingToolCalls.set(toolCallId, { const toolCallId = buildNamespacedToolCallId(this.agentSessionId, rawToolCallId)
this.pendingToolCalls.set(rawToolCallId, {
rawToolCallId,
toolCallId, toolCallId,
toolName: payload.toolName, toolName: payload.toolName,
input: payload.input, input: payload.input,
@ -170,10 +205,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(toolCallId: string): PendingToolCall | undefined { consumePendingToolCall(rawToolCallId: string): PendingToolCall | undefined {
const entry = this.pendingToolCalls.get(toolCallId) const entry = this.pendingToolCalls.get(rawToolCallId)
if (entry) { if (entry) {
this.pendingToolCalls.delete(toolCallId) this.pendingToolCalls.delete(rawToolCallId)
} }
return entry return entry
} }
@ -183,12 +218,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: this.getToolBlockById(toolCallId)?.toolName ?? 'unknown', toolName: block?.toolName ?? 'unknown',
input, input,
providerMetadata providerMetadata
}) })
const block = this.getToolBlockById(toolCallId)
if (block) { if (block) {
block.resolvedInput = input block.resolvedInput = input
} }
@ -200,7 +235,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.toolIndexById.delete(block.toolCallId) this.toolIndexByNamespacedId.delete(block.toolCallId)
} }
return block return block
} }
@ -227,7 +262,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.toolIndexById.clear() this.toolIndexByNamespacedId.clear()
} }
/** Resets the entire step lifecycle after emitting a terminal frame. */ /** Resets the entire step lifecycle after emitting a terminal frame. */
@ -236,6 +271,10 @@ 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,6 +13,7 @@ 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'
@ -150,7 +151,10 @@ class ClaudeCodeService implements AgentServiceInterface {
return { behavior: 'allow', updatedInput: input } 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 // Build SDK options from parameters
@ -346,7 +350,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() const streamState = new ClaudeStreamState({ agentSessionId: sessionId })
try { try {
for await (const message of query({ prompt: promptStream, options })) { for await (const message of query({ prompt: promptStream, options })) {

View File

@ -37,6 +37,7 @@ 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>
@ -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( export async function promptForToolApproval(
toolName: string, toolName: string,
input: Record<string, unknown>, input: Record<string, unknown>,
options?: { signal: AbortSignal; suggestions?: PermissionUpdate[] } options: PromptForToolApprovalOptions
): Promise<PermissionResult> { ): Promise<PermissionResult> {
if (shouldAutoApproveTools) { if (shouldAutoApproveTools) {
logger.debug('promptForToolApproval auto-approving tool for test', { logger.debug('promptForToolApproval auto-approving tool for test', {
@ -245,6 +255,7 @@ 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
}) })
@ -252,6 +263,7 @@ 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,
@ -266,6 +278,7 @@ 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
@ -273,7 +286,11 @@ 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', { 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') finalizeRequest(requestId, { behavior: 'deny', message: 'Timed out waiting for approval' }, 'timeout')
}, TOOL_APPROVAL_TIMEOUT_MS) }, TOOL_APPROVAL_TIMEOUT_MS)
@ -287,7 +304,11 @@ export async function promptForToolApproval(
if (options?.signal) { if (options?.signal) {
const abortListener = () => { 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') finalizeRequest(requestId, defaultDenyUpdate, 'aborted')
} }

View File

@ -243,9 +243,10 @@ 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: block.id, toolCallId,
toolName: block.name, toolName: block.name,
input: block.input, input: block.input,
providerExecuted: true, providerExecuted: true,
@ -331,10 +332,11 @@ 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: toolResult.tool_use_id, toolCallId,
toolName: pendingCall?.toolName ?? 'unknown', toolName: pendingCall?.toolName ?? 'unknown',
input: pendingCall?.input, input: pendingCall?.input,
error: toolResult.content, error: toolResult.content,
@ -343,7 +345,7 @@ function handleUserMessage(
} else { } else {
chunks.push({ chunks.push({
type: 'tool-result', type: 'tool-result',
toolCallId: toolResult.tool_use_id, toolCallId,
toolName: pendingCall?.toolName ?? 'unknown', toolName: pendingCall?.toolName ?? 'unknown',
input: pendingCall?.input, input: pendingCall?.input,
output: toolResult.content, output: toolResult.content,
@ -514,7 +516,7 @@ function handleContentBlockStart(
} }
case 'tool_use': { case 'tool_use': {
const block = state.openToolBlock(index, { const block = state.openToolBlock(index, {
toolCallId: contentBlock.id, rawToolCallId: contentBlock.id,
toolName: contentBlock.name, toolName: contentBlock.name,
providerMetadata providerMetadata
}) })

View File

@ -99,9 +99,6 @@ 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
@ -144,6 +141,9 @@ 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,13 +162,17 @@ export function buildProviderOptions(
...getCustomParameters(assistant) ...getCustomParameters(assistant)
} }
const rawProviderKey = let 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,5 +1,4 @@
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'
@ -9,7 +8,6 @@ import SelectAgentBaseModelButton from '@renderer/pages/home/components/SelectAg
import type { import type {
AddAgentForm, AddAgentForm,
AgentEntity, AgentEntity,
AgentType,
ApiModel, ApiModel,
BaseAgentForm, BaseAgentForm,
PermissionMode, PermissionMode,
@ -17,30 +15,22 @@ import type {
UpdateAgentForm UpdateAgentForm
} from '@renderer/types' } from '@renderer/types'
import { AgentConfigurationSchema, isAgentType } 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 { 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 ?? 'Claude Code', name: existing?.name ?? 'Agent',
description: existing?.description, description: existing?.description,
instructions: existing?.instructions, instructions: existing?.instructions,
model: existing?.model ?? '', model: existing?.model ?? '',
@ -100,54 +90,6 @@ 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,
@ -155,12 +97,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) => ({
@ -334,16 +276,6 @@ 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>
@ -363,7 +295,7 @@ const PopupContainer: React.FC<Props> = ({ agent, afterSubmit, resolve }) => {
avatarSize={24} avatarSize={24}
iconSize={16} iconSize={16}
buttonStyle={{ buttonStyle={{
padding: '8px 12px', padding: '3px 8px',
width: '100%', width: '100%',
border: '1px solid var(--color-border)', border: '1px solid var(--color-border)',
borderRadius: 6, borderRadius: 6,
@ -382,7 +314,6 @@ 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)}>
@ -438,10 +369,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={2} value={form.description ?? ''} onChange={onDescChange} /> <TextArea rows={1} value={form.description ?? ''} onChange={onDescChange} />
</FormItem> </FormItem> */}
</FormContent> </FormContent>
<FormFooter> <FormFooter>
@ -575,14 +506,7 @@ const FormFooter = styled.div`
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
gap: 8px; gap: 8px;
padding-top: 16px; padding: 10px;
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

@ -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', apiHost: 'https://ai-gateway.vercel.sh/v1/ai',
models: [], models: [],
isSystem: true, isSystem: true,
enabled: false enabled: false

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 { selectPendingPermissionByToolName, toolPermissionsActions } from '@renderer/store/toolPermissions' import { selectPendingPermission, 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,9 +17,7 @@ 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) => const request = useAppSelector((state) => selectPendingPermission(state.toolPermissions, toolResponse.toolCallId))
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,21 +1,13 @@
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, SettingsItem, SettingsTitle } from './shared' import { SettingsContainer } from './shared'
// const logger = loggerService.withContext('AgentEssentialSettings')
type EssentialSettingsProps = type EssentialSettingsProps =
| { | {
@ -30,26 +22,10 @@ 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,6 +1,8 @@
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 { useState } from 'react' import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { SettingsItem, SettingsTitle } from './shared' import { SettingsItem, SettingsTitle } from './shared'
@ -13,26 +15,61 @@ 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>
<Input <div className="flex max-w-70 flex-1 items-center gap-1">
placeholder={t('common.agent_one') + t('common.name')} {isAgent && (
value={name} <EmojiAvatarWithPicker
onChange={(e) => setName(e.target.value)} emoji={emoji}
onBlur={() => { onPick={(emoji: string) => {
if (name !== base.name) { setEmoji(emoji)
updateName(name) if (isAgent && emoji === base?.configuration?.avatar) return
} 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,7 +109,6 @@ 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>
@ -140,7 +139,6 @@ 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 theme={theme}> <SettingContainer style={{ backgroundColor: 'inherit' }}>
<InstallNpxUv /> <InstallNpxUv />
</SettingContainer> </SettingContainer>
} }

View File

@ -6,6 +6,7 @@ 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>
@ -82,12 +83,12 @@ export const selectActiveToolPermission = (state: ToolPermissionsState): ToolPer
return activeEntries[0] return activeEntries[0]
} }
export const selectPendingPermissionByToolName = ( export const selectPendingPermission = (
state: ToolPermissionsState, state: ToolPermissionsState,
toolName: string toolCallId: string
): ToolPermissionEntry | undefined => { ): ToolPermissionEntry | undefined => {
const activeEntries = Object.values(state.requests) const activeEntries = Object.values(state.requests)
.filter((entry) => entry.toolName === toolName) .filter((entry) => entry.toolCallId === toolCallId)
.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'
) )

View File

@ -1891,30 +1891,30 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@cherrystudio/ai-core@workspace:^1.0.0-alpha.18, @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 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.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/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:*, @cherrystudio/ai-sdk-provider@workspace:packages/ai-sdk-provider": "@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:
@ -9919,6 +9919,7 @@ __metadata:
"@ai-sdk/google-vertex": "npm:^3.0.68" "@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/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"
@ -9928,7 +9929,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.0-alpha.18" "@cherrystudio/ai-core": "workspace:^1.0.9"
"@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"