feat(search): web_search onto the injected port — Google CSE retired, research honestly disabled
Chunk A of the search port wave (2026-09-20), per master's assignment: - web.ts: the Google Custom Search backend is gone — web_search now runs on the core search port (SearchProvider), injected as a dependency (import law: the tool never imports server code). createWebTools(search) resolves the LIVE provider fresh per call; null = the honest unavailability string (no silent fallback, owner decision). web_fetch unchanged (needs no engine). - runner: JobRunnerDeps.searchProvider — the server composition threaded into the researcher; jobs already in flight when a key disappears still fail honestly through the same seam, never crash. - jobTools: the research dispatch tool DROPS from the teacher's registry when ctx.searchAvailable() is false (owner decision: registry-level drop — the model never sees the tool, no doomed dispatch, no error card). Absent seam = available (tests). chat-handler injects server/searchAvailable + searchProvider. - tests: stubFetch (the Google wire stub) retired; jobs.test.ts threads a recording stub provider through all four research runners and asserts the scripted query hit the port; new web-search.test.ts covers the factory contract (null → honest string, hits, error strings, fresh resolution, web_fetch independence) + the registry drop matrix. Gates: typecheck, 793/793 tests, lint, build — all green.
This commit is contained in:
+19
-5
@@ -22,6 +22,12 @@ export interface JobToolContext {
|
||||
* input rides along because the spawn happens MID-STREAM, before
|
||||
* onFinish persists the tool part — a transcript scan would race it. */
|
||||
spawn: (job: JobRecord, input: ResearchInput | DemoInput | VisualInput | PdfInput) => void;
|
||||
/** Search availability (search port wave, 2026-09-20): the honest
|
||||
* disable signal for the research dispatch tool — no live engine
|
||||
* means NO research tool in the registry (owner decision: registry-
|
||||
* level drop, no doomed dispatch, no silent fallback). Absent =
|
||||
* available (tests + any seam without search wiring). */
|
||||
searchAvailable?: () => boolean;
|
||||
}
|
||||
|
||||
export const researchInputSchema = z.object({
|
||||
@@ -121,14 +127,22 @@ const PDF_DESCRIPTION =
|
||||
* Per-request job tool registry: each tool closes over the composition root's
|
||||
* context (chatId + jobStore + the spawn seam), so concurrent chats never
|
||||
* share state. chat-handler wires it each turn; tests wire their own.
|
||||
*
|
||||
* The research tool drops from the registry when ctx.searchAvailable reports
|
||||
* no live engine (2026-09-20) — the teacher never sees it, so a keyless
|
||||
* server can't dispatch doomed research jobs. Jobs ALREADY in flight when
|
||||
* a key disappears still run to their honest failure (runner backstop).
|
||||
*/
|
||||
export function jobTools(ctx: JobToolContext): Record<string, LhTool<any, any>> {
|
||||
const searchOn = ctx.searchAvailable?.() ?? true;
|
||||
return {
|
||||
research: lhTool({
|
||||
name: "research",
|
||||
description: RESEARCH_DESCRIPTION,
|
||||
schema: researchInputSchema,
|
||||
execute: async (input, { toolCallId }) => dispatch(ctx, "research", toolCallId, RESEARCH_ACK, input),
|
||||
...(searchOn && {
|
||||
research: lhTool({
|
||||
name: "research",
|
||||
description: RESEARCH_DESCRIPTION,
|
||||
schema: researchInputSchema,
|
||||
execute: async (input, { toolCallId }) => dispatch(ctx, "research", toolCallId, RESEARCH_ACK, input),
|
||||
}),
|
||||
}),
|
||||
demo: lhTool({
|
||||
name: "demo",
|
||||
|
||||
Reference in New Issue
Block a user