diff --git a/docs/cli/configuration-v1.md b/docs/cli/configuration-v1.md index 0a16eb2e..0926d49e 100644 --- a/docs/cli/configuration-v1.md +++ b/docs/cli/configuration-v1.md @@ -309,7 +309,8 @@ If you are experiencing performance issues with file searching (e.g., with `@` c ``` - **`tavilyApiKey`** (string): - - **Description:** API key for Tavily web search service. Required to enable the `web_search` tool functionality. If not configured, the web search tool will be disabled and skipped. + - **Description:** API key for Tavily web search service. Used to enable the `web_search` tool functionality. + - **Note:** This is a legacy configuration format. For Qwen OAuth users, DashScope provider is automatically available without any configuration. For other authentication types, configure Tavily or Google providers using the new `webSearch` configuration format. - **Default:** `undefined` (web search disabled) - **Example:** `"tavilyApiKey": "tvly-your-api-key-here"` - **`chatCompression`** (object): @@ -465,8 +466,8 @@ The CLI automatically loads environment variables from an `.env` file. The loadi - This is useful for development and testing. - **`TAVILY_API_KEY`**: - Your API key for the Tavily web search service. - - Required to enable the `web_search` tool functionality. - - If not configured, the web search tool will be disabled and skipped. + - Used to enable the `web_search` tool functionality. + - **Note:** For Qwen OAuth users, DashScope provider is automatically available without any configuration. For other authentication types, configure Tavily or Google providers to enable web search. - Example: `export TAVILY_API_KEY="tvly-your-api-key-here"` ## Command-Line Arguments diff --git a/docs/cli/configuration.md b/docs/cli/configuration.md index 4d593096..b152a701 100644 --- a/docs/cli/configuration.md +++ b/docs/cli/configuration.md @@ -305,7 +305,8 @@ Settings are organized into categories. All settings should be placed within the - **Default:** `undefined` - **`advanced.tavilyApiKey`** (string): - - **Description:** API key for Tavily web search service. Required to enable the `web_search` tool functionality. If not configured, the web search tool will be disabled and skipped. + - **Description:** API key for Tavily web search service. Used to enable the `web_search` tool functionality. + - **Note:** This is a legacy configuration format. For Qwen OAuth users, DashScope provider is automatically available without any configuration. For other authentication types, configure Tavily or Google providers using the new `webSearch` configuration format. - **Default:** `undefined` #### `mcpServers` @@ -474,8 +475,8 @@ The CLI automatically loads environment variables from an `.env` file. The loadi - Set to a string to customize the title of the CLI. - **`TAVILY_API_KEY`**: - Your API key for the Tavily web search service. - - Required to enable the `web_search` tool functionality. - - If not configured, the web search tool will be disabled and skipped. + - Used to enable the `web_search` tool functionality. + - **Note:** For Qwen OAuth users, DashScope provider is automatically available without any configuration. For other authentication types, configure Tavily or Google providers to enable web search. - Example: `export TAVILY_API_KEY="tvly-your-api-key-here"` ## Command-Line Arguments diff --git a/docs/tools/web-search.md b/docs/tools/web-search.md index a2c7f6fa..94845339 100644 --- a/docs/tools/web-search.md +++ b/docs/tools/web-search.md @@ -8,7 +8,7 @@ Use `web_search` to perform a web search and get information from the internet. ### Supported Providers -1. **DashScope** (Official, Free) - Default provider, always available when using Qwen OAuth authentication (200 requests/minute, 2000 requests/day) +1. **DashScope** (Official, Free) - Automatically available for Qwen OAuth users (200 requests/minute, 2000 requests/day) 2. **Tavily** - High-quality search API with built-in answer generation 3. **Google Custom Search** - Google's Custom Search JSON API @@ -46,8 +46,9 @@ Add to your `settings.json`: **Notes:** - DashScope doesn't require an API key (official, free service) -- Only configure the providers you want to use -- Set `default` to specify which provider to use by default +- **Qwen OAuth users:** DashScope is automatically added to your provider list, even if not explicitly configured +- Configure additional providers (Tavily, Google) if you want to use them alongside DashScope +- Set `default` to specify which provider to use by default (if not set, priority order: Tavily > Google > DashScope) ### Method 2: Environment Variables @@ -132,10 +133,11 @@ web_search(query="best practices for React 19", provider="dashscope") ### DashScope (Official) - **Cost:** Free -- **Authentication:** Automatically available with Qwen OAuth -- **Configuration:** No API key required +- **Authentication:** Automatically available when using Qwen OAuth authentication +- **Configuration:** No API key required, automatically added to provider list for Qwen OAuth users - **Quota:** 200 requests/minute, 2000 requests/day -- **Best for:** General queries, always available +- **Best for:** General queries, always available as fallback for Qwen OAuth users +- **Auto-registration:** If you're using Qwen OAuth, DashScope is automatically added to your provider list even if you don't configure it explicitly ### Tavily @@ -158,14 +160,18 @@ web_search(query="best practices for React 19", provider="dashscope") - **Response format:** Returns a concise answer with numbered source citations - **Citations:** Source links are appended as a numbered list: [1], [2], etc. - **Multiple providers:** If one provider fails, manually specify another using the `provider` parameter -- **DashScope availability:** Always available when authenticated with Qwen OAuth, no configuration needed +- **DashScope availability:** Automatically available for Qwen OAuth users, no configuration needed +- **Default provider selection:** The system automatically selects a default provider based on availability: + 1. Your explicit `default` configuration (highest priority) + 2. CLI argument `--web-search-default` + 3. First available provider by priority: Tavily > Google > DashScope ## Troubleshooting **Tool not available?** -- Check if at least one provider is configured -- For DashScope: Ensure you're authenticated with Qwen OAuth +- **For Qwen OAuth users:** The tool is automatically registered with DashScope provider, no configuration needed +- **For other authentication types:** Ensure at least one provider (Tavily or Google) is configured - For Tavily/Google: Verify your API keys are correct **Provider-specific errors?** diff --git a/packages/cli/src/config/config.ts b/packages/cli/src/config/config.ts index 7c20ee83..bd016d76 100755 --- a/packages/cli/src/config/config.ts +++ b/packages/cli/src/config/config.ts @@ -766,7 +766,11 @@ export async function loadCliConfig( : argv.openaiLogging) ?? false, }, cliVersion: await getCliVersion(), - webSearch: buildWebSearchConfig(argv, settings), + webSearch: buildWebSearchConfig( + argv, + settings, + settings.security?.auth?.selectedType, + ), summarizeToolOutput: settings.model?.summarizeToolOutput, ideMode, chatCompression: settings.model?.chatCompression, diff --git a/packages/cli/src/config/webSearch.ts b/packages/cli/src/config/webSearch.ts index a558de17..260220ac 100644 --- a/packages/cli/src/config/webSearch.ts +++ b/packages/cli/src/config/webSearch.ts @@ -4,6 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { AuthType } from '@qwen-code/qwen-code-core'; import type { WebSearchProviderConfig } from '@qwen-code/qwen-code-core'; import type { Settings } from './settings.js'; @@ -33,56 +34,85 @@ export interface WebSearchConfig { * * @param argv - Command line arguments * @param settings - User settings from settings.json + * @param authType - Authentication type (e.g., 'qwen-oauth') * @returns WebSearch configuration or undefined if no providers available */ export function buildWebSearchConfig( argv: WebSearchCliArgs, settings: Settings, + authType?: string, ): WebSearchConfig | undefined { - // Priority 1: Use settings.json webSearch config if present + const isQwenOAuth = authType === AuthType.QWEN_OAUTH; + + // Step 1: Collect providers from settings or command line/env + let providers: WebSearchProviderConfig[] = []; + let userDefault: string | undefined; + if (settings.webSearch) { - return settings.webSearch; + // Use providers from settings.json + providers = [...settings.webSearch.provider]; + userDefault = settings.webSearch.default; + } else { + // Build providers from command line args and environment variables + const tavilyKey = + argv.tavilyApiKey || + settings.advanced?.tavilyApiKey || + process.env['TAVILY_API_KEY']; + if (tavilyKey) { + providers.push({ + type: 'tavily', + apiKey: tavilyKey, + } as WebSearchProviderConfig); + } + + const googleKey = argv.googleApiKey || process.env['GOOGLE_API_KEY']; + const googleEngineId = + argv.googleSearchEngineId || process.env['GOOGLE_SEARCH_ENGINE_ID']; + if (googleKey && googleEngineId) { + providers.push({ + type: 'google', + apiKey: googleKey, + searchEngineId: googleEngineId, + } as WebSearchProviderConfig); + } } - // Priority 2: Build from command line args and environment variables - const providers: WebSearchProviderConfig[] = []; - - // DashScope is always available (official, free) - providers.push({ type: 'dashscope' } as WebSearchProviderConfig); - - // Tavily from args/env/legacy settings - const tavilyKey = - argv.tavilyApiKey || - settings.advanced?.tavilyApiKey || - process.env['TAVILY_API_KEY']; - if (tavilyKey) { - providers.push({ - type: 'tavily', - apiKey: tavilyKey, - } as WebSearchProviderConfig); + // Step 2: Ensure dashscope is available for qwen-oauth users + if (isQwenOAuth) { + const hasDashscope = providers.some((p) => p.type === 'dashscope'); + if (!hasDashscope) { + providers.push({ type: 'dashscope' } as WebSearchProviderConfig); + } } - // Google from args/env - const googleKey = argv.googleApiKey || process.env['GOOGLE_API_KEY']; - const googleEngineId = - argv.googleSearchEngineId || process.env['GOOGLE_SEARCH_ENGINE_ID']; - if (googleKey && googleEngineId) { - providers.push({ - type: 'google', - apiKey: googleKey, - searchEngineId: googleEngineId, - } as WebSearchProviderConfig); - } - - // If no providers configured, return undefined + // Step 3: If no providers available, return undefined if (providers.length === 0) { return undefined; } - // Determine default provider - // Priority: CLI arg > has Tavily key > DashScope (fallback) - const defaultProvider = - argv.webSearchDefault || (tavilyKey ? 'tavily' : 'dashscope'); + // Step 4: Determine default provider + // Priority: user explicit config > CLI arg > first available provider (tavily > google > dashscope) + const providerPriority: Array<'tavily' | 'google' | 'dashscope'> = [ + 'tavily', + 'google', + 'dashscope', + ]; + + // Determine default provider based on availability + let defaultProvider = userDefault || argv.webSearchDefault; + if (!defaultProvider) { + // Find first available provider by priority order + for (const providerType of providerPriority) { + if (providers.some((p) => p.type === providerType)) { + defaultProvider = providerType; + break; + } + } + // Fallback to first available provider if none found in priority list + if (!defaultProvider) { + defaultProvider = providers[0]?.type || 'dashscope'; + } + } return { provider: providers, diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index 6955c698..754551b4 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -1166,12 +1166,10 @@ export class Config { registerCoreTool(TodoWriteTool, this); registerCoreTool(ExitPlanModeTool, this); registerCoreTool(WebFetchTool, this); - // Conditionally register web search tool if any web search provider is configured - // or if using qwen-oauth authentication - if ( - this.getWebSearchConfig() || - this.getAuthType() === AuthType.QWEN_OAUTH - ) { + // Conditionally register web search tool if web search provider is configured + // buildWebSearchConfig ensures qwen-oauth users get dashscope provider, so + // if tool is registered, config must exist + if (this.getWebSearchConfig()) { registerCoreTool(WebSearchTool, this); } diff --git a/packages/core/src/tools/web-search/index.ts b/packages/core/src/tools/web-search/index.ts index 5a8e3b3b..92855116 100644 --- a/packages/core/src/tools/web-search/index.ts +++ b/packages/core/src/tools/web-search/index.ts @@ -17,7 +17,6 @@ import { ToolErrorType } from '../tool-error.js'; import type { Config } from '../../config/config.js'; import { ApprovalMode } from '../../config/config.js'; -import { AuthType } from '../../core/contentGenerator.js'; import { getErrorMessage } from '../../utils/errors.js'; import { buildContentWithSources } from './utils.js'; import { TavilyProvider } from './providers/tavily-provider.js'; @@ -44,16 +43,9 @@ class WebSearchToolInvocation extends BaseToolInvocation< } override getDescription(): string { - const webSearchConfig = this.config.getWebSearchConfig(); - const authType = this.config.getAuthType(); - let defaultProvider = webSearchConfig?.default; - - // If auth type is QWEN_OAUTH, prefer dashscope as default - if (authType === AuthType.QWEN_OAUTH && !defaultProvider) { - defaultProvider = 'dashscope'; - } - - const provider = this.params.provider || defaultProvider; + // If tool is registered, config must exist with a default provider + const webSearchConfig = this.config.getWebSearchConfig()!; + const provider = this.params.provider || webSearchConfig.default; return ` (Searching the web via ${provider})`; } @@ -217,21 +209,8 @@ class WebSearchToolInvocation extends BaseToolInvocation< } async execute(signal: AbortSignal): Promise { - // Guard: Check configuration exists - const webSearchConfig = this.config.getWebSearchConfig(); - if (!webSearchConfig) { - const message = - 'Web search is disabled. Please configure web search providers in settings.json.'; - return { - llmContent: message, - returnDisplay: - 'Web search disabled. Configure providers to enable search.', - error: { - message, - type: ToolErrorType.EXECUTION_FAILED, - }, - }; - } + // If tool is registered, config must exist with providers and default + const webSearchConfig = this.config.getWebSearchConfig()!; try { // Create and select provider