fix(coding-agent): honor lang: directive in TinyFish search
The shared query pipeline parses a lang:/language: directive into StructuredQuery.lang, which sibling providers (DuckDuckGo, Perplexity, SearXNG) map onto their native locale params. The TinyFish provider dropped parsed.lang entirely, so every request fell back to the API's US/English default and non-US locales were silently lost. Map parsed.lang onto TinyFish location (ISO 3166-1 alpha-2) and language (ISO 639-1): lang:it-it yields location=IT&language=it, lang:it yields language=it only. Behaviour is unchanged when no locale directive is present. Fixes #8913
This commit is contained in:
@@ -13,6 +13,7 @@
|
||||
### Fixed
|
||||
|
||||
- Fixed Claude Code marketplace plugins ignoring the `enabledPlugins` switch in `~/.claude/settings.json` and `.claude/settings(.local).json`: a plugin turned off for a project no longer loads there, and a local-scope install enabled for a project loads even when its recorded `projectPath` is a different directory
|
||||
- Fixed the TinyFish web search provider ignoring the `lang:`/`language:` query directive, so every request fell back to the API's US/English geolocation. `parsed.lang` now maps onto TinyFish's `location`/`language` parameters (e.g. `lang:it-it` → `location=IT&language=it`), matching the DuckDuckGo, Perplexity, and SearXNG providers ([#8913](https://github.com/can1357/oh-my-pi/issues/8913)).
|
||||
- Fixed task and eval subagents discovering newly added agent definitions while resolving their role aliases from stale startup settings. Subagent preflight now atomically reloads persisted settings before agent discovery while preserving live runtime overrides.
|
||||
- Fixed images returned by tools mounted under `xd://` rendering only as file links instead of inline terminal graphics.
|
||||
- Resume Cursor idle-stall turns after completed MCP/todo tool results. The watchdog already closes the Connect stream, so unmarked blocks no longer need the `exec-resolved` marker to continue.
|
||||
|
||||
@@ -35,6 +35,10 @@ export interface TinyFishSearchParams {
|
||||
page?: number;
|
||||
include_domains?: string[];
|
||||
exclude_domains?: string[];
|
||||
/** ISO 3166-1 alpha-2 region, e.g. `IT`. Geolocates results. */
|
||||
location?: string;
|
||||
/** ISO 639-1 language, e.g. `it`. */
|
||||
language?: string;
|
||||
signal?: AbortSignal;
|
||||
timeoutMs?: number;
|
||||
fetch?: FetchImpl;
|
||||
@@ -74,6 +78,12 @@ async function callTinyFishSearch(apiKey: string, params: TinyFishSearchParams):
|
||||
if (params.exclude_domains?.length) {
|
||||
url.searchParams.set("exclude_domains", params.exclude_domains.join(","));
|
||||
}
|
||||
if (params.location) {
|
||||
url.searchParams.set("location", params.location);
|
||||
}
|
||||
if (params.language) {
|
||||
url.searchParams.set("language", params.language);
|
||||
}
|
||||
if (params.num_results !== undefined) {
|
||||
url.searchParams.set("num_results", String(params.num_results));
|
||||
}
|
||||
@@ -133,6 +143,18 @@ function siteHosts(sites: readonly string[]): string[] {
|
||||
return [...hosts];
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive TinyFish `location` (ISO 3166-1 alpha-2, uppercase) and `language`
|
||||
* (ISO 639-1, lowercase) from a parsed `lang:` directive. The region subtag is
|
||||
* optional: `lang:it` yields language only, `lang:it-it` yields both.
|
||||
*/
|
||||
function tinyFishLocale(lang: string | undefined): { location?: string; language?: string } {
|
||||
if (!lang) return {};
|
||||
const match = /^([a-z]{2})(?:[-_]([a-z]{2}))?/.exec(lang.toLowerCase());
|
||||
if (!match) return {};
|
||||
return { language: match[1], location: match[2]?.toUpperCase() };
|
||||
}
|
||||
|
||||
/** Execute TinyFish web search. */
|
||||
export async function searchTinyFish(params: SearchParams): Promise<SearchResponse> {
|
||||
const numResults = clampNumResults(params.numSearchResults ?? params.limit, DEFAULT_NUM_RESULTS, MAX_NUM_RESULTS);
|
||||
@@ -152,6 +174,9 @@ export async function searchTinyFish(params: SearchParams): Promise<SearchRespon
|
||||
if (includeDomains.length > 0) tinyFishParams.include_domains = includeDomains;
|
||||
if (excludeDomains.length > 0) tinyFishParams.exclude_domains = excludeDomains;
|
||||
}
|
||||
const { location, language } = tinyFishLocale(parsed.lang);
|
||||
if (location) tinyFishParams.location = location;
|
||||
if (language) tinyFishParams.language = language;
|
||||
const keyOrResolver: ApiKey = params.authStorage.resolver("tinyfish", {
|
||||
sessionId: params.sessionId,
|
||||
});
|
||||
|
||||
@@ -107,6 +107,45 @@ describe("TinyFish web search provider", () => {
|
||||
expect(captured[0].searchParams.get("query")).toBe("plain query with ordinary words");
|
||||
});
|
||||
|
||||
it("maps a lang: locale directive onto location and language", async () => {
|
||||
const captured: URL[] = [];
|
||||
const fetchMock: FetchImpl = async input => {
|
||||
const url = input instanceof URL ? input : new URL(typeof input === "string" ? input : input.url);
|
||||
captured.push(url);
|
||||
return new Response(JSON.stringify(tinyFishPage(tinyFishResults("tinyfish", 3))), {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
};
|
||||
|
||||
await searchTinyFish({ ...makeParams("Best Cheap Android Tablet lang:it-it"), fetch: fetchMock });
|
||||
|
||||
expect(captured).toHaveLength(1);
|
||||
expect(captured[0].searchParams.get("query")).toBe("Best Cheap Android Tablet");
|
||||
expect(captured[0].searchParams.get("location")).toBe("IT");
|
||||
expect(captured[0].searchParams.get("language")).toBe("it");
|
||||
expectTinyFishParams(captured[0], ["query", "num_results", "page", "location", "language"]);
|
||||
});
|
||||
|
||||
it("sends language only when the lang: directive omits a region", async () => {
|
||||
const captured: URL[] = [];
|
||||
const fetchMock: FetchImpl = async input => {
|
||||
const url = input instanceof URL ? input : new URL(typeof input === "string" ? input : input.url);
|
||||
captured.push(url);
|
||||
return new Response(JSON.stringify(tinyFishPage(tinyFishResults("tinyfish", 3))), {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
};
|
||||
|
||||
await searchTinyFish({ ...makeParams("cheap tablets lang:it"), fetch: fetchMock });
|
||||
|
||||
expect(captured).toHaveLength(1);
|
||||
expect(captured[0].searchParams.get("language")).toBe("it");
|
||||
expect(captured[0].searchParams.has("location")).toBe(false);
|
||||
expectTinyFishParams(captured[0], ["query", "num_results", "page", "language"]);
|
||||
});
|
||||
|
||||
it("passes TinyFish num_results and applies numSearchResults across pages", async () => {
|
||||
const captured: { url: URL; init?: RequestInit }[] = [];
|
||||
const pages = new Map([
|
||||
|
||||
Reference in New Issue
Block a user