From 3560d108dbdceb4d481b83700a0a8ae817265a6b Mon Sep 17 00:00:00 2001 From: roboomp Date: Mon, 29 Jun 2026 08:55:53 +0000 Subject: [PATCH] fix(cli): applied search provider settings Initialized standalone search commands with configured web-search provider globals before resolving the implicit provider chain. Fixes #3793 --- packages/coding-agent/CHANGELOG.md | 1 + .../coding-agent/src/cli/web-search-cli.ts | 7 +- .../web/search/cli-provider-settings.test.ts | 128 ++++++++++++++++++ 3 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 packages/coding-agent/test/web/search/cli-provider-settings.test.ts diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index d231182bd..15ad59069 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -4,6 +4,7 @@ ### Fixed +- Fixed `omp search` applying `providers.webSearch` and `providers.webSearchExclude` before resolving its implicit provider chain. ([#3793](https://github.com/can1357/oh-my-pi/issues/3793)) - Fixed the bash interceptor blocking `echo` / `printf` redirects to `/dev/null`, `/dev/tty`, `/dev/stdout`, and `/dev/stderr` device sinks while still directing real file writes to the write tool. ([#3763](https://github.com/can1357/oh-my-pi/issues/3763)) ## [16.2.5] - 2026-06-28 diff --git a/packages/coding-agent/src/cli/web-search-cli.ts b/packages/coding-agent/src/cli/web-search-cli.ts index a4c6f8cc2..b642cda67 100644 --- a/packages/coding-agent/src/cli/web-search-cli.ts +++ b/packages/coding-agent/src/cli/web-search-cli.ts @@ -4,8 +4,10 @@ * Handles `omp q`/`omp web-search` subcommands for testing web search providers. */ -import { APP_NAME } from "@oh-my-pi/pi-utils"; +import { APP_NAME, getProjectDir } from "@oh-my-pi/pi-utils"; import chalk from "chalk"; +import { applyProviderGlobalsFromSettings } from "../config/provider-globals"; +import { Settings } from "../config/settings"; import { initTheme, theme } from "../modes/theme/theme"; import { runSearchQuery, type SearchQueryParams } from "../web/search/index"; import { SEARCH_PROVIDER_ORDER } from "../web/search/provider"; @@ -85,6 +87,9 @@ export async function runSearchCommand(cmd: SearchCommandArgs): Promise { process.exit(1); } + const settings = await Settings.init({ cwd: getProjectDir() }); + applyProviderGlobalsFromSettings(settings); + await initTheme(); const params: SearchQueryParams = { diff --git a/packages/coding-agent/test/web/search/cli-provider-settings.test.ts b/packages/coding-agent/test/web/search/cli-provider-settings.test.ts new file mode 100644 index 000000000..a66b6b7ab --- /dev/null +++ b/packages/coding-agent/test/web/search/cli-provider-settings.test.ts @@ -0,0 +1,128 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "bun:test"; +import * as path from "node:path"; +import { stripVTControlCharacters } from "node:util"; +import { resetSettingsForTest, Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; +import { setExcludedSearchProviders, setPreferredSearchProvider } from "@oh-my-pi/pi-coding-agent/web/search/provider"; +import { getConfigRootDir, setAgentDir, TempDir } from "@oh-my-pi/pi-utils"; +import { runSearchCommand } from "../../../src/cli/web-search-cli"; + +const WEB_SEARCH_ENV_KEYS = [ + "ANTHROPIC_API_KEY", + "BRAVE_API_KEY", + "EXA_API_KEY", + "FIRECRAWL_API_KEY", + "JINA_API_KEY", + "KAGI_API_KEY", + "MOONSHOT_API_KEY", + "MOONSHOT_SEARCH_API_KEY", + "PARALLEL_API_KEY", + "PERPLEXITY_API_KEY", + "SEARXNG_ENDPOINT", + "SYNTHETIC_API_KEY", + "TAVILY_API_KEY", + "TINYFISH_API_KEY", + "XAI_API_KEY", +] as const; + +const originalAgentDir = process.env.PI_CODING_AGENT_DIR; +const fallbackAgentDir = path.join(getConfigRootDir(), "agent"); + +let tempAgentDir: TempDir | undefined; +let originalEnv: Partial> = {}; +let originalExitCode: typeof process.exitCode; + +function responseUrl(input: string | Request | URL): string { + if (typeof input === "string") return input; + if (input instanceof URL) return input.toString(); + return input.url; +} + +function makeFetchMock(): typeof fetch { + return Object.assign( + async (input: string | Request | URL, _init?: RequestInit): Promise => { + const url = responseUrl(input); + if (url.startsWith("https://s.jina.ai/")) { + return new Response( + JSON.stringify({ data: [{ title: "Jina result", url: "https://jina.example", content: "jina" }] }), + { status: 200, headers: { "Content-Type": "application/json" } }, + ); + } + if (url === "https://api.tavily.com/search") { + return new Response( + JSON.stringify({ + answer: "Tavily answer", + results: [{ title: "Tavily result", url: "https://tavily.example", content: "tavily" }], + request_id: "req-test", + }), + { status: 200, headers: { "Content-Type": "application/json" } }, + ); + } + return new Response(`unexpected URL: ${url}`, { status: 500 }); + }, + { preconnect: fetch.preconnect }, + ); +} + +beforeEach(async () => { + originalEnv = Object.fromEntries(WEB_SEARCH_ENV_KEYS.map(key => [key, process.env[key]])); + for (const key of WEB_SEARCH_ENV_KEYS) delete process.env[key]; + process.env.JINA_API_KEY = "test-jina-key"; + process.env.TAVILY_API_KEY = "test-tavily-key"; + originalExitCode = process.exitCode; + process.exitCode = undefined; + + resetSettingsForTest(); + setPreferredSearchProvider("auto"); + setExcludedSearchProviders([]); + tempAgentDir = TempDir.createSync("@omp-search-cli-"); + setAgentDir(tempAgentDir.path()); + await Settings.init({ + inMemory: true, + cwd: tempAgentDir.path(), + overrides: { + "providers.webSearch": "tavily", + "providers.webSearchExclude": ["jina"], + }, + }); +}); + +afterEach(async () => { + vi.restoreAllMocks(); + resetSettingsForTest(); + setPreferredSearchProvider("auto"); + setExcludedSearchProviders([]); + process.exitCode = originalExitCode; + for (const key of WEB_SEARCH_ENV_KEYS) { + const value = originalEnv[key]; + if (value === undefined) delete process.env[key]; + else process.env[key] = value; + } + if (originalAgentDir) { + setAgentDir(originalAgentDir); + } else { + setAgentDir(fallbackAgentDir); + delete process.env.PI_CODING_AGENT_DIR; + } + if (tempAgentDir) { + await tempAgentDir.remove(); + tempAgentDir = undefined; + } +}); + +describe("runSearchCommand provider settings", () => { + it("applies configured web-search preference and exclusions before resolving the implicit chain", async () => { + vi.spyOn(globalThis, "fetch").mockImplementation(makeFetchMock()); + + let stdout = ""; + vi.spyOn(process.stdout, "write").mockImplementation(chunk => { + stdout += typeof chunk === "string" ? chunk : new TextDecoder().decode(chunk); + return true; + }); + + await runSearchCommand({ query: "provider selection smoke test", limit: 1, expanded: false }); + + const plain = stripVTControlCharacters(stdout); + expect(plain).toContain("Provider: Tavily (API)"); + expect(plain).not.toContain("Provider: Jina"); + }); +});