fix(cli): applied search provider settings

Initialized standalone search commands with configured web-search provider globals before resolving the implicit provider chain.

Fixes #3793
This commit is contained in:
roboomp
2026-06-29 08:55:53 +00:00
parent ca9f2847e6
commit 3560d108db
3 changed files with 135 additions and 1 deletions
+1
View File
@@ -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
@@ -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<void> {
process.exit(1);
}
const settings = await Settings.init({ cwd: getProjectDir() });
applyProviderGlobalsFromSettings(settings);
await initTheme();
const params: SearchQueryParams = {
@@ -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<Record<(typeof WEB_SEARCH_ENV_KEYS)[number], string | undefined>> = {};
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<Response> => {
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");
});
});