| import { |
| buildWebSearchTool, |
| createProxiedFetchTransport, |
| queryTavily, |
| type MakaTool, |
| type ProxiedFetchProxy, |
| type ProxiedFetchTransport, |
| } from '@maka/runtime'; |
| import type { WebSearchResponse } from '@maka/core'; |
| import type { |
| ResolveWebSearchExecutionInput, |
| RuntimePolicyOperationCoordinator, |
| } from '@maka/storage/runtime-policy-stores'; |
| import { toRuntimePolicyProxy } from './runtime-policy-proxy.js'; |
| |
| interface HostWebSearchServiceInput { |
| readonly policy: Pick<RuntimePolicyOperationCoordinator, 'resolveWebSearchExecution'>; |
| readonly createFetchTransport?: (proxy: ProxiedFetchProxy | null) => ProxiedFetchTransport; |
| } |
| |
| export interface HostWebSearchService { |
| search(input: { |
| readonly query: string; |
| readonly limit: number; |
| readonly abortSignal?: AbortSignal; |
| readonly policy?: ResolveWebSearchExecutionInput; |
| }): Promise<WebSearchResponse>; |
| } |
| |
| export function createHostWebSearchService(input: HostWebSearchServiceInput): HostWebSearchService { |
| const createFetchTransport = input.createFetchTransport ?? createProxiedFetchTransport; |
| return { |
| search: async ({ query, limit, abortSignal, policy }) => { |
| const resolved = await input.policy.resolveWebSearchExecution(policy); |
| switch (resolved.kind) { |
| case 'privacy_mode': |
| return { |
| ok: false, |
| reason: 'incognito_active', |
| message: 'Web search is disabled while privacy mode is active.', |
| }; |
| case 'disabled': |
| return { |
| ok: false, |
| reason: 'not_configured', |
| message: 'Enable web search before using this tool.', |
| }; |
| case 'model_native_only': |
| return { |
| ok: false, |
| reason: 'unsupported_provider', |
| message: 'Provider-native web search executes inside the primary model request.', |
| }; |
| case 'credential_not_configured': |
| return { |
| ok: false, |
| reason: 'not_configured', |
| message: |
| resolved.status.locator.scope === 'network_proxy' |
| ? 'Configure the network proxy credential before using web search.' |
| : 'Configure a Tavily API key before using web search.', |
| }; |
| case 'ready': { |
| if (resolved.provider !== 'tavily') { |
| return { |
| ok: false, |
| reason: 'unsupported_provider', |
| message: 'The configured web search provider is not supported by this Host.', |
| }; |
| } |
| const apiKey = resolved.secretMaterial.webSearch.secret; |
| const transport = createFetchTransport( |
| toRuntimePolicyProxy( |
| resolved.networkProxy, |
| resolved.secretMaterial.networkProxy?.secret, |
| ), |
| ); |
| try { |
| return await queryTavily({ |
| apiKey, |
| query, |
| limit, |
| fetch: transport.fetch, |
| ...(abortSignal ? { abortSignal } : {}), |
| }); |
| } finally { |
| await transport.close(); |
| } |
| } |
| } |
| }, |
| }; |
| } |
| |
| export function createHostWebSearchTool(input: HostWebSearchServiceInput): MakaTool { |
| return createHostWebSearchToolFromService(createHostWebSearchService(input)); |
| } |
| |
| export function createHostWebSearchToolFromService(service: HostWebSearchService): MakaTool { |
| return buildWebSearchTool({ |
| search: ({ query, limit, abortSignal }) => |
| service.search({ |
| query, |
| limit, |
| ...(abortSignal ? { abortSignal } : {}), |
| }), |
| }); |
| } |