chore: bump @cherrystudio/ai-core version to 1.0.0-alpha.6 and refactor web search tool

- Updated version in package.json to 1.0.0-alpha.6.
- Simplified response structure in ToolCallChunkHandler by removing unnecessary nesting.
- Refactored input schema for web search tool to enhance type safety and clarity.
- Cleaned up commented-out code in MessageTool for improved readability.
This commit is contained in:
lizhixuan 2025-07-22 21:58:22 +08:00
parent eca9442907
commit e690da840c
6 changed files with 121 additions and 84 deletions

View File

@ -1,6 +1,6 @@
{
"name": "@cherrystudio/ai-core",
"version": "1.0.0-alpha.5",
"version": "1.0.0-alpha.6",
"description": "Cherry Studio AI Core - Unified AI Provider Interface Based on Vercel AI SDK",
"main": "dist/index.js",
"module": "dist/index.mjs",

View File

@ -229,10 +229,7 @@ export class ToolCallChunkHandler {
tool: toolCallInfo.tool,
arguments: input,
status: 'done',
response: {
data: output,
success: true
},
response: output,
toolCallId: toolCallId
}
// 从活跃调用中移除(交互结束后整个实例会被丢弃)

View File

@ -3,7 +3,7 @@ import WebSearchService from '@renderer/services/WebSearchService'
import { Assistant, Message, WebSearchProvider } from '@renderer/types'
import { UserMessageStatus } from '@renderer/types/newMessage'
import { ExtractResults } from '@renderer/utils/extract'
import { type InferToolOutput, tool } from 'ai'
import { type InferToolInput, type InferToolOutput, tool } from 'ai'
import { z } from 'zod'
// import { AiSdkTool, ToolCallResult } from './types'
@ -18,15 +18,16 @@ const WebSearchProviderResult = z.object({
})
)
})
const webSearchToolInputSchema = z.object({
query: z.string().describe('The query to search for')
})
export const webSearchTool = (webSearchProviderId: WebSearchProvider['id']) => {
const webSearchService = WebSearchService.getInstance(webSearchProviderId)
return tool({
name: 'builtin_web_search',
description: 'Search the web for information',
inputSchema: z.object({
query: z.string().describe('The query to search for')
}),
inputSchema: webSearchToolInputSchema,
outputSchema: WebSearchProviderResult,
execute: async ({ query }) => {
console.log('webSearchTool', query)
@ -36,6 +37,7 @@ export const webSearchTool = (webSearchProviderId: WebSearchProvider['id']) => {
}
})
}
export type WebSearchToolInput = InferToolInput<ReturnType<typeof webSearchTool>>
export type WebSearchToolOutput = InferToolOutput<ReturnType<typeof webSearchTool>>
export const webSearchToolWithExtraction = (

View File

@ -33,28 +33,6 @@ export default function Spinner({ text }: Props) {
</Searching>
)
}
// const baseContainer = css`
// display: flex;
// flex-direction: row;
// align-items: center;
// `
// const Container = styled.div`
// ${baseContainer}
// background-color: var(--color-background-mute);
// padding: 10px;
// border-radius: 10px;
// margin-bottom: 10px;
// gap: 10px;
// `
// const StatusText = styled.div`
// font-size: 14px;
// line-height: 1.6;
// text-decoration: none;
// color: var(--color-text-1);
// `
const SearchWrapper = styled.div`
display: flex;
align-items: center;

View File

@ -1,48 +1,45 @@
import Spinner from '@renderer/components/Spinner'
import i18n from '@renderer/i18n'
import type { MCPToolResponse } from '@renderer/types'
import type { ToolMessageBlock } from '@renderer/types/newMessage'
import { Collapse } from 'antd'
import { useMemo } from 'react'
import styled from 'styled-components'
import { MessageWebSearchToolBody, MessageWebSearchToolTitle } from './MessageWebSearchTool'
interface Props {
block: ToolMessageBlock
}
const toolNameMapText = {
web_search: i18n.t('message.searching')
}
const toolDoneNameMapText = (args: Record<string, any>) => {
const count = args.count ?? 0
return i18n.t('message.websearch.fetch_complete', { count })
}
// const toolNameMapText = {
// web_search: i18n.t('message.searching')
// }
// const toolDoneNameMapText = (args: Record<string, any>) => {
// const count = args.count ?? 0
// return i18n.t('message.websearch.fetch_complete', { count })
// }
const PrepareTool = ({ toolResponse }: { toolResponse: MCPToolResponse }) => {
const toolNameText = useMemo(
() => toolNameMapText[toolResponse.tool.name] || toolResponse.tool.name,
[toolResponse.tool]
)
// const PrepareTool = ({ toolResponse }: { toolResponse: MCPToolResponse }) => {
// const toolNameText = useMemo(
// () => toolNameMapText[toolResponse.tool.name] || toolResponse.tool.name,
// [toolResponse.tool]
// )
return (
<Spinner
text={
<PrepareToolWrapper>
{toolNameText}
<span>{JSON.stringify(toolResponse.arguments)}</span>
</PrepareToolWrapper>
}
/>
)
}
// return (
// <Spinner
// text={
// <PrepareToolWrapper>
// {toolNameText}
// <span>{JSON.stringify(toolResponse.arguments)}</span>
// </PrepareToolWrapper>
// }
// />
// )
// }
const DoneTool = ({ toolResponse }: { toolResponse: MCPToolResponse }) => {
const toolDoneNameText = useMemo(
() => toolDoneNameMapText({ count: toolResponse.response?.data?.length ?? 0 }),
[toolResponse.response]
)
return <p>{toolDoneNameText}</p>
}
// const DoneTool = ({ toolResponse }: { toolResponse: MCPToolResponse }) => {
// const toolDoneNameText = useMemo(
// () => toolDoneNameMapText({ count: toolResponse.response?.data?.length ?? 0 }),
// [toolResponse.response]
// )
// return <p>{toolDoneNameText}</p>
// }
export default function MessageTool({ block }: Props) {
const toolResponse = block.metadata?.rawMcpToolResponse
@ -54,27 +51,25 @@ export default function MessageTool({ block }: Props) {
items={[
{
key: '1',
label:
toolResponse.status !== 'done' ? (
<PrepareTool toolResponse={toolResponse} />
) : (
<DoneTool toolResponse={toolResponse} />
),
children: (
<p>{JSON.stringify(toolResponse.status !== 'done' ? toolResponse.arguments : toolResponse.response)}</p>
),
showArrow: false
label: <MessageWebSearchToolTitle toolResponse={toolResponse} />,
children: <MessageWebSearchToolBody toolResponse={toolResponse} />,
showArrow: false,
styles: {
header: {
paddingLeft: '0'
}
}
}
]}
size="small"
ghost
/>
)
}
const PrepareToolWrapper = styled.span`
display: flex;
align-items: center;
gap: 4px;
font-size: 14px;
padding: 10px;
padding-left: 0;
`
// const PrepareToolWrapper = styled.span`
// display: flex;
// align-items: center;
// gap: 4px;
// font-size: 14px;
// padding-left: 0;
// `

View File

@ -0,0 +1,65 @@
import { WebSearchToolInput, WebSearchToolOutput } from '@renderer/aiCore/tools/WebSearchTool'
import Spinner from '@renderer/components/Spinner'
import i18n from '@renderer/i18n'
import { MCPToolResponse } from '@renderer/types'
import { Typography } from 'antd'
import { Search } from 'lucide-react'
import styled from 'styled-components'
const { Text, Link } = Typography
export const MessageWebSearchToolTitle = ({ toolResponse }: { toolResponse: MCPToolResponse }) => {
const toolInput = toolResponse.arguments as WebSearchToolInput
const toolOutput = toolResponse.response as WebSearchToolOutput
return toolResponse.status !== 'done' ? (
<Spinner
text={
<PrepareToolWrapper>
{i18n.t('message.searching')}
<span>{toolInput?.query ?? ''}</span>
</PrepareToolWrapper>
}
/>
) : (
<MessageWebSearchToolTitleTextWrapper type="secondary">
<Search size={16} style={{ color: 'unset' }} />
{i18n.t('message.websearch.fetch_complete', { count: toolOutput.results.length ?? 0 })}
</MessageWebSearchToolTitleTextWrapper>
)
}
export const MessageWebSearchToolBody = ({ toolResponse }: { toolResponse: MCPToolResponse }) => {
const toolOutput = toolResponse.response as WebSearchToolOutput
return toolResponse.status === 'done' ? (
<MessageWebSearchToolBodyUlWrapper>
{toolOutput.results.map((result) => (
<li key={result.url}>
<Link href={result.url}>{result.title}</Link>
</li>
))}
</MessageWebSearchToolBodyUlWrapper>
) : null
}
const PrepareToolWrapper = styled.span`
display: flex;
align-items: center;
gap: 4px;
font-size: 14px;
padding-left: 0;
`
const MessageWebSearchToolTitleTextWrapper = styled(Text)`
display: flex;
align-items: center;
gap: 4px;
`
const MessageWebSearchToolBodyUlWrapper = styled.ul`
display: flex;
flex-direction: column;
gap: 4px;
padding: 0;
`