feat(gateway): tinyfish web search as fallback when wikipedia misses

- new tinyFishSearch module: GET api.search.tinyfish.ai with X-API-Key,
  maps top-3 to SearchResult shape, never throws (all failure modes -> [])
- wikipediaSearch: on wiki miss, one tinyfish attempt; hits cached 6h
  under the same key so fallback latency is paid once
- termGlossary: on summary miss, top tinyfish hit becomes the definition
  (persisted permanently like wiki defs); miss keeps 1h sentinel
- config: TINYFISH_API_KEY (empty = fallback disabled), ENABLED,
  BASE_URL, TIMEOUT_MS, LOCATION, LANGUAGE knobs
- tests: 6 coverage for disabled/mapping/non-OK/network/bad-json

API key NOT committed — set TINYFISH_API_KEY in BWS gmw secrets.

Verified: typecheck + lint clean, 216/216 tests pass, live probe
'gubernur jawa barat' returned 3 mapped results
This commit is contained in:
asepharyana committed 2026-09-14 00:44:38 +07:00
1 parent 9685fa02be
commit 5c133f7302
5 files changed
+303 -1

No files matched your search

@@ -132,6 +132,22 @@ export const configSchema = z
WIKIPEDIA_LANG: z.string().min(1).default("id"),
// Per-request timeout (ms) for Wikipedia API calls.
WIKIPEDIA_TIMEOUT_MS: z.coerce.number().positive().default(8000),
// ── TinyFish web search (fallback when Wikipedia misses) ─────────────
// GET {base}?query=..&location=..&language=.. with X-API-Key header.
// Empty key = fallback disabled (Wikipedia-only, tests stay offline).
TINYFISH_API_KEY: z.string().optional().default(""),
TINYFISH_SEARCH_ENABLED: z
.string()
.optional()
.transform((v) => v === "true")
.default(true),
TINYFISH_SEARCH_BASE_URL: z
.string()
.url()
.default("https://api.search.tinyfish.ai"),
TINYFISH_SEARCH_TIMEOUT_MS: z.coerce.number().positive().default(10000),
TINYFISH_SEARCH_LOCATION: z.string().min(1).default("US"),
TINYFISH_SEARCH_LANGUAGE: z.string().min(1).default("en"),
// ── Voice PCM WebSocket (direct gateway→backend, bypasses Redis) ────
VOICE_PCM_WS_ENABLED: z
.string()