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", "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", "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",

View File

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

View File

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

View File

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