From 7ff07fd88c655060be037d9c02daa3f551195712 Mon Sep 17 00:00:00 2001 From: pomelo-nwu Date: Wed, 5 Nov 2025 11:37:56 +0800 Subject: [PATCH] fix(web-search): handle unconfigured state and improve tests --- integration-tests/web_search.test.ts | 47 +++++++++++++++++-- .../core/src/tools/web-search/index.test.ts | 36 ++++++++++++++ packages/core/src/tools/web-search/index.ts | 21 +++++++-- 3 files changed, 96 insertions(+), 8 deletions(-) diff --git a/integration-tests/web_search.test.ts b/integration-tests/web_search.test.ts index 11cd78b6..680a1ffd 100644 --- a/integration-tests/web_search.test.ts +++ b/integration-tests/web_search.test.ts @@ -9,14 +9,53 @@ import { TestRig, printDebugInfo, validateModelOutput } from './test-helper.js'; describe('web_search', () => { it('should be able to search the web', async () => { - // Skip if Tavily key is not configured - if (!process.env['TAVILY_API_KEY']) { - console.warn('Skipping web search test: TAVILY_API_KEY not set'); + // Check if any web search provider is available + const hasTavilyKey = !!process.env['TAVILY_API_KEY']; + const hasGoogleKey = + !!process.env['GOOGLE_API_KEY'] && + !!process.env['GOOGLE_SEARCH_ENGINE_ID']; + + // Skip if no provider is configured + // Note: DashScope provider is automatically available for Qwen OAuth users, + // but we can't easily detect that in tests without actual OAuth credentials + if (!hasTavilyKey && !hasGoogleKey) { + console.warn( + 'Skipping web search test: No web search provider configured. ' + + 'Set TAVILY_API_KEY or GOOGLE_API_KEY+GOOGLE_SEARCH_ENGINE_ID environment variables.', + ); return; } const rig = new TestRig(); - await rig.setup('should be able to search the web'); + // Configure web search in settings if provider keys are available + const webSearchSettings: Record = {}; + const providers: Array<{ + type: string; + apiKey?: string; + searchEngineId?: string; + }> = []; + + if (hasTavilyKey) { + providers.push({ type: 'tavily', apiKey: process.env['TAVILY_API_KEY'] }); + } + if (hasGoogleKey) { + providers.push({ + type: 'google', + apiKey: process.env['GOOGLE_API_KEY'], + searchEngineId: process.env['GOOGLE_SEARCH_ENGINE_ID'], + }); + } + + if (providers.length > 0) { + webSearchSettings.webSearch = { + provider: providers, + default: providers[0]?.type, + }; + } + + await rig.setup('should be able to search the web', { + settings: webSearchSettings, + }); let result; try { diff --git a/packages/core/src/tools/web-search/index.test.ts b/packages/core/src/tools/web-search/index.test.ts index 288aa5ea..d851ceae 100644 --- a/packages/core/src/tools/web-search/index.test.ts +++ b/packages/core/src/tools/web-search/index.test.ts @@ -272,5 +272,41 @@ describe('WebSearchTool', () => { expect(result.error?.message).toContain('Web search is disabled'); expect(result.llmContent).toContain('Web search is disabled'); }); + + it('should return descriptive message in getDescription when web search is not configured', () => { + ( + mockConfig.getWebSearchConfig as ReturnType + ).mockReturnValue(null); + + const tool = new WebSearchTool(mockConfig); + const invocation = tool.build({ query: 'test query' }); + const description = invocation.getDescription(); + + expect(description).toBe( + ' (Web search is disabled - configure a provider in settings.json)', + ); + }); + + it('should return provider name in getDescription when web search is configured', () => { + const webSearchConfig: WebSearchConfig = { + provider: [ + { + type: 'tavily', + apiKey: 'test-key', + }, + ], + default: 'tavily', + }; + + ( + mockConfig.getWebSearchConfig as ReturnType + ).mockReturnValue(webSearchConfig); + + const tool = new WebSearchTool(mockConfig); + const invocation = tool.build({ query: 'test query' }); + const description = invocation.getDescription(); + + expect(description).toBe(' (Searching the web via tavily)'); + }); }); }); diff --git a/packages/core/src/tools/web-search/index.ts b/packages/core/src/tools/web-search/index.ts index 92855116..f9962b52 100644 --- a/packages/core/src/tools/web-search/index.ts +++ b/packages/core/src/tools/web-search/index.ts @@ -43,8 +43,10 @@ class WebSearchToolInvocation extends BaseToolInvocation< } override getDescription(): string { - // If tool is registered, config must exist with a default provider - const webSearchConfig = this.config.getWebSearchConfig()!; + const webSearchConfig = this.config.getWebSearchConfig(); + if (!webSearchConfig) { + return ' (Web search is disabled - configure a provider in settings.json)'; + } const provider = this.params.provider || webSearchConfig.default; return ` (Searching the web via ${provider})`; } @@ -209,8 +211,19 @@ class WebSearchToolInvocation extends BaseToolInvocation< } async execute(signal: AbortSignal): Promise { - // If tool is registered, config must exist with providers and default - const webSearchConfig = this.config.getWebSearchConfig()!; + // Check if web search is configured + const webSearchConfig = this.config.getWebSearchConfig(); + if (!webSearchConfig) { + return { + llmContent: + 'Web search is disabled. Please configure a web search provider in your settings.', + returnDisplay: 'Web search is disabled.', + error: { + message: 'Web search is disabled', + type: ToolErrorType.EXECUTION_FAILED, + }, + }; + } try { // Create and select provider