mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-12 17:09:37 +08:00
🐛 fix: propagate hub tool errors
This commit is contained in:
parent
479f180b1e
commit
1554f082ec
@ -176,6 +176,7 @@ describe('HubServer Integration', () => {
|
|||||||
|
|
||||||
const execOutput = JSON.parse(execResult.content[0].text)
|
const execOutput = JSON.parse(execResult.content[0].text)
|
||||||
expect(execOutput.error).toBe('test error')
|
expect(execOutput.error).toBe('test error')
|
||||||
|
expect(execOutput.isError).toBe(true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -213,6 +214,7 @@ describe('HubServer Integration', () => {
|
|||||||
|
|
||||||
expect(execOutput.error).toBe('Execution timed out after 60000ms')
|
expect(execOutput.error).toBe('Execution timed out after 60000ms')
|
||||||
expect(execOutput.result).toBeUndefined()
|
expect(execOutput.result).toBeUndefined()
|
||||||
|
expect(execOutput.isError).toBe(true)
|
||||||
expect(execOutput.logs).toContain('[log] starting')
|
expect(execOutput.logs).toContain('[log] starting')
|
||||||
expect(vi.mocked(mcpService.abortTool)).toHaveBeenCalled()
|
expect(vi.mocked(mcpService.abortTool)).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|||||||
@ -85,6 +85,7 @@ describe('Runtime', () => {
|
|||||||
|
|
||||||
expect(result.result).toBeUndefined()
|
expect(result.result).toBeUndefined()
|
||||||
expect(result.error).toBe('test error')
|
expect(result.error).toBe('test error')
|
||||||
|
expect(result.isError).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('supports parallel helper', async () => {
|
it('supports parallel helper', async () => {
|
||||||
@ -139,5 +140,20 @@ describe('Runtime', () => {
|
|||||||
|
|
||||||
expect(result.result).toBe(30)
|
expect(result.result).toBe(30)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('stops execution when a tool throws', async () => {
|
||||||
|
const runtime = new Runtime()
|
||||||
|
const tools = [
|
||||||
|
createMockTool({
|
||||||
|
functionName: 'server__failing_tool'
|
||||||
|
})
|
||||||
|
]
|
||||||
|
|
||||||
|
const result = await runtime.execute('return await server__failing_tool({})', tools)
|
||||||
|
|
||||||
|
expect(result.result).toBeUndefined()
|
||||||
|
expect(result.error).toBe('Tool failed')
|
||||||
|
expect(result.isError).toBe(true)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -175,7 +175,8 @@ export class HubServer {
|
|||||||
type: 'text',
|
type: 'text',
|
||||||
text: JSON.stringify(result, null, 2)
|
text: JSON.stringify(result, null, 2)
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
isError: result.isError
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
import mcpService from '@main/services/MCPService'
|
import mcpService from '@main/services/MCPService'
|
||||||
import { generateMcpToolFunctionName } from '@shared/mcp'
|
import { generateMcpToolFunctionName } from '@shared/mcp'
|
||||||
import type { MCPTool } from '@types'
|
import type { MCPCallToolResponse, MCPTool, MCPToolResultContent } from '@types'
|
||||||
|
|
||||||
import type { GeneratedTool } from './types'
|
import type { GeneratedTool } from './types'
|
||||||
|
|
||||||
@ -47,10 +47,12 @@ export const callMcpTool = async (functionName: string, params: unknown, callId?
|
|||||||
}
|
}
|
||||||
const toolId = `${retryToolInfo.serverId}__${retryToolInfo.toolName}`
|
const toolId = `${retryToolInfo.serverId}__${retryToolInfo.toolName}`
|
||||||
const result = await mcpService.callToolById(toolId, params, callId)
|
const result = await mcpService.callToolById(toolId, params, callId)
|
||||||
|
throwIfToolError(result)
|
||||||
return extractToolResult(result)
|
return extractToolResult(result)
|
||||||
}
|
}
|
||||||
const toolId = `${toolInfo.serverId}__${toolInfo.toolName}`
|
const toolId = `${toolInfo.serverId}__${toolInfo.toolName}`
|
||||||
const result = await mcpService.callToolById(toolId, params, callId)
|
const result = await mcpService.callToolById(toolId, params, callId)
|
||||||
|
throwIfToolError(result)
|
||||||
return extractToolResult(result)
|
return extractToolResult(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,7 +60,7 @@ export const abortMcpTool = async (callId: string): Promise<boolean> => {
|
|||||||
return mcpService.abortTool(null as unknown as Electron.IpcMainInvokeEvent, callId)
|
return mcpService.abortTool(null as unknown as Electron.IpcMainInvokeEvent, callId)
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractToolResult(result: { content: Array<{ type: string; text?: string }> }): unknown {
|
function extractToolResult(result: MCPCallToolResponse): unknown {
|
||||||
if (!result.content || result.content.length === 0) {
|
if (!result.content || result.content.length === 0) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@ -74,3 +76,21 @@ function extractToolResult(result: { content: Array<{ type: string; text?: strin
|
|||||||
|
|
||||||
return result.content
|
return result.content
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function throwIfToolError(result: MCPCallToolResponse): void {
|
||||||
|
if (!result.isError) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const textContent = extractTextContent(result.content)
|
||||||
|
throw new Error(textContent ?? 'Tool execution failed')
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractTextContent(content: MCPToolResultContent[] | undefined): string | undefined {
|
||||||
|
if (!content || content.length === 0) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const textBlock = content.find((item) => item.type === 'text' && item.text)
|
||||||
|
return textBlock?.text
|
||||||
|
}
|
||||||
|
|||||||
@ -103,7 +103,8 @@ export class Runtime {
|
|||||||
{
|
{
|
||||||
result: undefined,
|
result: undefined,
|
||||||
logs: resolvedLogs.length > 0 ? resolvedLogs : undefined,
|
logs: resolvedLogs.length > 0 ? resolvedLogs : undefined,
|
||||||
error: errorMessage
|
error: errorMessage,
|
||||||
|
isError: true
|
||||||
},
|
},
|
||||||
terminateWorker
|
terminateWorker
|
||||||
)
|
)
|
||||||
|
|||||||
@ -26,10 +26,11 @@ export interface ExecInput {
|
|||||||
code: string
|
code: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExecOutput {
|
export type ExecOutput = {
|
||||||
result: unknown
|
result: unknown
|
||||||
logs?: string[]
|
logs?: string[]
|
||||||
error?: string
|
error?: string
|
||||||
|
isError?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ToolRegistryOptions {
|
export interface ToolRegistryOptions {
|
||||||
|
|||||||
@ -195,11 +195,7 @@ class McpService {
|
|||||||
* Call a tool by its full ID (serverId__toolName format).
|
* Call a tool by its full ID (serverId__toolName format).
|
||||||
* Used by Hub server's runtime.
|
* Used by Hub server's runtime.
|
||||||
*/
|
*/
|
||||||
public async callToolById(
|
public async callToolById(toolId: string, params: unknown, callId?: string): Promise<MCPCallToolResponse> {
|
||||||
toolId: string,
|
|
||||||
params: unknown,
|
|
||||||
callId?: string
|
|
||||||
): Promise<{ content: Array<{ type: string; text?: string }> }> {
|
|
||||||
const parts = toolId.split('__')
|
const parts = toolId.split('__')
|
||||||
if (parts.length < 2) {
|
if (parts.length < 2) {
|
||||||
throw new Error(`Invalid tool ID format: ${toolId}`)
|
throw new Error(`Invalid tool ID format: ${toolId}`)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user