2026-05-04 23:27:20 +02:00
/ * *
2026-05-10 23:18:02 +02:00
* Native Anthropic web search extension hooks .
2026-05-04 23:27:20 +02:00
*
2026-05-10 23:18:02 +02:00
* The injection logic ( before _provider _request ) lives in the native provider middleware :
2026-05-10 22:37:42 +02:00
* packages / coding - agent / src / core / providers / web - search - middleware . ts
*
2026-05-10 23:18:02 +02:00
* This file owns only the extension - layer concerns : model _select diagnostics ,
* active - tool management , session reset , and PREFERENCES . md - aware provider resolution .
2026-05-04 23:27:20 +02:00
* /
2026-05-10 22:37:42 +02:00
import {
CUSTOM _SEARCH _TOOL _NAMES ,
setPreferBraveResolver ,
webSearchMiddleware ,
} from "@singularity-forge/coding-agent" ;
2026-05-04 23:27:20 +02:00
import { resolveSearchProviderFromPreferences } from "../sf/preferences.js" ;
/** Tool names for the Brave-backed custom search tools */
export const BRAVE _TOOL _NAMES = [ "search-the-web" , "search_and_read" ] ;
/** When true, skip native web search injection and keep Brave/custom tools active on Anthropic. */
export function preferBraveSearch ( ) {
2026-05-05 14:31:16 +02:00
// PREFERENCES.md takes priority over env var
const prefsPref = resolveSearchProviderFromPreferences ( ) ;
if (
prefsPref === "brave" ||
prefsPref === "tavily" ||
prefsPref === "minimax" ||
prefsPref === "serper" ||
prefsPref === "exa" ||
prefsPref === "ollama" ||
prefsPref === "combosearch"
)
return true ;
if ( prefsPref === "native" ) return false ;
// Fall back to env var
return (
process . env . PREFER _BRAVE _SEARCH === "1" ||
process . env . PREFER _BRAVE _SEARCH === "true"
) ;
2026-05-04 23:27:20 +02:00
}
/ * *
fix: consolidate extensions into sf, migrate kernel.ts, fix test suite
- Fold sf-usage-bar, sf-notify, sf-inturn-guard, sf-permissions,
slash-commands into sf extension (ui/, notifications/, guards/,
permissions/, commands/legacy/)
- Delete vectordrive extension
- Migrate uok/kernel.js to TypeScript (kernel.ts) with full interfaces
- Add allowJs/checkJs:false to tsconfig.resources.json for incremental TS migration
- Add symlink dedup to extension-discovery.ts (seenRealPaths Set)
- Add before_provider_request delegate back to native-search.js so
session budget tests exercise the middleware end-to-end
- Fix parseSfNativeTools() to return all SF manifest tools (drop sf_ filter)
- Fix test assertions: plan_milestone/complete_task/validate_milestone
- Remove subagent from app-smoke.test.ts (folded into sf/subagent/)
- Remove sf-permissions/sf-inturn-guard/subagent from features-inventory test
- Fix resolveSearchProvider autonomous mode test to pass 'auto' explicitly
- Remove legacy /clear slash command (conflicts with built-in clear_terminal)
- Update web-command-parity-contract.test.ts for clear removal
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-11 02:40:52 +02:00
* Register model _select , before _provider _request , and session _start hooks for native Anthropic web search .
2026-05-04 23:27:20 +02:00
*
fix: consolidate extensions into sf, migrate kernel.ts, fix test suite
- Fold sf-usage-bar, sf-notify, sf-inturn-guard, sf-permissions,
slash-commands into sf extension (ui/, notifications/, guards/,
permissions/, commands/legacy/)
- Delete vectordrive extension
- Migrate uok/kernel.js to TypeScript (kernel.ts) with full interfaces
- Add allowJs/checkJs:false to tsconfig.resources.json for incremental TS migration
- Add symlink dedup to extension-discovery.ts (seenRealPaths Set)
- Add before_provider_request delegate back to native-search.js so
session budget tests exercise the middleware end-to-end
- Fix parseSfNativeTools() to return all SF manifest tools (drop sf_ filter)
- Fix test assertions: plan_milestone/complete_task/validate_milestone
- Remove subagent from app-smoke.test.ts (folded into sf/subagent/)
- Remove sf-permissions/sf-inturn-guard/subagent from features-inventory test
- Fix resolveSearchProvider autonomous mode test to pass 'auto' explicitly
- Remove legacy /clear slash command (conflicts with built-in clear_terminal)
- Update web-command-parity-contract.test.ts for clear removal
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-11 02:40:52 +02:00
* before _provider _request delegates to the webSearchMiddleware singleton so that tests
* exercise the same code path as production ( sdk . ts calls it natively first ; the extension
* delegate is a no - op in production due to the double - injection guard in the middleware ) .
2026-05-04 23:27:20 +02:00
* /
export function registerNativeSearchHooks ( pi ) {
fix: consolidate extensions into sf, migrate kernel.ts, fix test suite
- Fold sf-usage-bar, sf-notify, sf-inturn-guard, sf-permissions,
slash-commands into sf extension (ui/, notifications/, guards/,
permissions/, commands/legacy/)
- Delete vectordrive extension
- Migrate uok/kernel.js to TypeScript (kernel.ts) with full interfaces
- Add allowJs/checkJs:false to tsconfig.resources.json for incremental TS migration
- Add symlink dedup to extension-discovery.ts (seenRealPaths Set)
- Add before_provider_request delegate back to native-search.js so
session budget tests exercise the middleware end-to-end
- Fix parseSfNativeTools() to return all SF manifest tools (drop sf_ filter)
- Fix test assertions: plan_milestone/complete_task/validate_milestone
- Remove subagent from app-smoke.test.ts (folded into sf/subagent/)
- Remove sf-permissions/sf-inturn-guard/subagent from features-inventory test
- Fix resolveSearchProvider autonomous mode test to pass 'auto' explicitly
- Remove legacy /clear slash command (conflicts with built-in clear_terminal)
- Update web-command-parity-contract.test.ts for clear removal
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-11 02:40:52 +02:00
// null = unknown (model_select not yet fired); true/false = provider is/isn't Anthropic.
let isAnthropicProvider = null ;
2026-05-10 22:37:42 +02:00
// Register the PREFERENCES.md-aware resolver so the native middleware (shared
// singleton in web-search-middleware.ts) respects search_provider overrides.
// Called here so each test invocation resets the resolver to the current context.
setPreferBraveResolver ( preferBraveSearch ) ;
// Reset the shared middleware session counter for this registration.
// In tests, each registerNativeSearchHooks() call starts a fresh counter.
// In production, the session_start handler below resets it on each new session.
webSearchMiddleware . resetSession ( ) ;
2026-05-05 14:31:16 +02:00
// Track provider changes via model selection — also handles diagnostics
// since model_select fires AFTER session_start and knows the provider.
pi . on ( "model_select" , async ( event , ctx ) => {
const wasAnthropic = isAnthropicProvider ;
isAnthropicProvider = event . model . provider === "anthropic" ;
const hasSearchKey = ! ! (
process . env . BRAVE _API _KEY ||
process . env . TAVILY _API _KEY ||
process . env . MINIMAX _CODE _PLAN _KEY ||
process . env . MINIMAX _CODING _API _KEY ||
process . env . MINIMAX _API _KEY ||
process . env . SERPER _API _KEY ||
process . env . EXA _API _KEY ||
process . env . OLLAMA _API _KEY
) ;
// When Anthropic (and not preferring Brave): disable custom search tools —
// native web_search is server-side and more reliable.
if ( isAnthropicProvider && ! preferBraveSearch ( ) ) {
const active = pi . getActiveTools ( ) ;
pi . setActiveTools (
active . filter ( ( t ) => ! CUSTOM _SEARCH _TOOL _NAMES . includes ( t ) ) ,
) ;
} else if ( ! isAnthropicProvider && wasAnthropic ) {
// Switching away from Anthropic — re-enable custom search tools (they
// were disabled while native search was active). If keys are missing,
// user sees the error rather than tools silently vanishing.
const active = pi . getActiveTools ( ) ;
const toAdd = CUSTOM _SEARCH _TOOL _NAMES . filter ( ( t ) => ! active . includes ( t ) ) ;
if ( toAdd . length > 0 ) {
pi . setActiveTools ( [ ... active , ... toAdd ] ) ;
}
}
// Show provider-aware diagnostics on first selection or provider change
if (
isAnthropicProvider &&
! preferBraveSearch ( ) &&
! wasAnthropic &&
event . source !== "restore"
) {
ctx . ui . notify ( "Native Anthropic web search active" , "info" ) ;
} else if (
isAnthropicProvider &&
preferBraveSearch ( ) &&
! wasAnthropic &&
event . source !== "restore"
) {
ctx . ui . notify ( "Brave search active (PREFER_BRAVE_SEARCH)" , "info" ) ;
} else if ( ! isAnthropicProvider && ! hasSearchKey ) {
ctx . ui . notify (
"Web search: Set BRAVE_API_KEY, TAVILY_API_KEY, MINIMAX_CODE_PLAN_KEY, SERPER_API_KEY, EXA_API_KEY, or OLLAMA_API_KEY, or use an Anthropic model for built-in search" ,
"warning" ,
) ;
}
} ) ;
fix: consolidate extensions into sf, migrate kernel.ts, fix test suite
- Fold sf-usage-bar, sf-notify, sf-inturn-guard, sf-permissions,
slash-commands into sf extension (ui/, notifications/, guards/,
permissions/, commands/legacy/)
- Delete vectordrive extension
- Migrate uok/kernel.js to TypeScript (kernel.ts) with full interfaces
- Add allowJs/checkJs:false to tsconfig.resources.json for incremental TS migration
- Add symlink dedup to extension-discovery.ts (seenRealPaths Set)
- Add before_provider_request delegate back to native-search.js so
session budget tests exercise the middleware end-to-end
- Fix parseSfNativeTools() to return all SF manifest tools (drop sf_ filter)
- Fix test assertions: plan_milestone/complete_task/validate_milestone
- Remove subagent from app-smoke.test.ts (folded into sf/subagent/)
- Remove sf-permissions/sf-inturn-guard/subagent from features-inventory test
- Fix resolveSearchProvider autonomous mode test to pass 'auto' explicitly
- Remove legacy /clear slash command (conflicts with built-in clear_terminal)
- Update web-command-parity-contract.test.ts for clear removal
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-11 02:40:52 +02:00
// Delegate before_provider_request to the native middleware singleton.
// In production, sdk.ts already ran applyToPayload before extension hooks fire,
// so the double-injection guard makes this a no-op. In tests (mock PI without
// sdk.ts), this is the only path that exercises the injection logic.
pi . on ( "before_provider_request" , async ( event , _ctx ) => {
let modelHint = event . model ;
if ( ! modelHint && isAnthropicProvider !== null ) {
modelHint = { provider : isAnthropicProvider ? "anthropic" : "not-anthropic" } ;
}
return webSearchMiddleware . applyToPayload ( event . payload , modelHint ) ;
} ) ;
2026-05-05 14:31:16 +02:00
pi . on ( "session_start" , async ( _event , _ctx ) => {
2026-05-10 22:37:42 +02:00
// Reset the shared middleware session budget (#1309).
webSearchMiddleware . resetSession ( ) ;
2026-05-05 14:31:16 +02:00
} ) ;
2026-05-04 23:27:20 +02:00
}