feat(search): Tavily + Brave behind a search-provider port; Google CSE retires
Search port + model slots wave, chunk A (owner design, 2026-09-20):
- core/search.ts: SearchHit + SearchProvider port + SearchStatus —
pure, browser-safe; adapters arrive as injected dependencies.
- server/search.ts: Tavily + Brave adapters (wire shapes VERIFIED
against docs.tavily.com's OpenAPI + Brave's dashboard docs — Tavily
auth is the Bearer header, not the legacy body api_key; Brave is
X-Subscription-Token) + engine resolution per owner decisions: a key
ALONE activates its engine (no engine choice needed), explicit
agent.search.engine pins the preference (both keys: Tavily first),
a pinned engine with a missing key is honestly unavailable — no
silent fallback, no default engine.
- Curated keys catalog: GOOGLE_SEARCH_API_KEY/GOOGLE_CSE_ID retire
(CSE closed to new customers, dies Jan 2027 — stored values become
preserved unknown vars); TAVILY_API_KEY (tvly- shape) + BRAVE_API_KEY
(length guard) join. UI keys-section TOOL_IDS now derives from the
catalog — cannot drift.
- GET /api/search: {available, engine, reason} — the honest status for
the settings gray-out; researcher-disable semantics ride the same
signal (Teacher stream rewires the tool onto the port).
- agent.search settings field (schema + sanitizer + strict parser);
web.ts's WebSearchResult is now the core SearchHit (re-exported).
This commit is contained in:
@@ -24,6 +24,8 @@ import { after, describe, it } from "node:test";
|
||||
for (const varName of [
|
||||
"OPENROUTER_API_KEY",
|
||||
"DEEPGRAM_API_KEY",
|
||||
"TAVILY_API_KEY",
|
||||
"BRAVE_API_KEY",
|
||||
"GOOGLE_SEARCH_API_KEY",
|
||||
"GOOGLE_CSE_ID",
|
||||
"OPENAI_API_KEY",
|
||||
@@ -196,10 +198,10 @@ describe("saveKey / deleteKey (immediate effect, no restart)", () => {
|
||||
describe("resolveKeyStatuses (pure projection)", () => {
|
||||
it("same value in env and file is NOT shadowed (redundant, not misleading)", () => {
|
||||
const row = resolveKeyStatuses(
|
||||
{ GOOGLE_SEARCH_API_KEY: "AIzaSame1234" },
|
||||
{ GOOGLE_SEARCH_API_KEY: "AIzaSame1234" },
|
||||
new Set(["GOOGLE_SEARCH_API_KEY"]),
|
||||
).find((s) => s.id === "GOOGLE_SEARCH_API_KEY")!;
|
||||
{ BRAVE_API_KEY: "BSA-same-value-throwaway-01234" },
|
||||
{ BRAVE_API_KEY: "BSA-same-value-throwaway-01234" },
|
||||
new Set(["BRAVE_API_KEY"]),
|
||||
).find((s) => s.id === "BRAVE_API_KEY")!;
|
||||
assert.ok(row, "the curated id is always in the projection");
|
||||
assert.equal(row.source, "env");
|
||||
assert.ok(!row.shadowedByEnv, "same value both places: redundant, not shadowed");
|
||||
|
||||
Reference in New Issue
Block a user