/**
 * Per-tool contract for `web_search` — host-mediated server call.
 *
 * `details.text` is model-facing markdown with numbered references.
 * `details.user_message` is UI-facing markdown with `{{citation-<id>}}` markers.
 * `details.citations` resolves those UI markers into source metadata.
 */
import { Type } from '@sinclair/typebox';
import { HostToolResultStatus, buildRendererArtifact } from '../../../contracts/host-tool.js';
import { withAbort } from '../../../utils/abort.js';
import { WEB_SEARCH } from '../tool-names.js';
export const WEB_SEARCH_KIND = 'webSearch';
const currentDate = () => new Date().toISOString().slice(0, 10);
/** Listing one-liner used by `get_tool_info`. */
export const WEB_SEARCH_TOOL_SUMMARY = 'Search the web for current information (use liberally for up-to-date data)';
export const WEB_SEARCH_TOOL_DESCRIPTION = `\
Search the web for real-time information with verifiable citations.

**CURRENT DATE: ${currentDate()}** - Use this date for all web inquiries.

⚠️ **IMPORTANT COMMON MISTAKE**: You often forget to backtrack from the current date.
For example, if asked to find the most recent quarterly data, you should not assume that the most recent data is from 2024.
Instead, you should check what today's date is (${currentDate()}) and backtrack from that.

WHEN TO USE:
- Use LIBERALLY for current data, market info, company details, product specs, trends, or ANY external data that could enhance accuracy
- Essential for ensuring quantitative data is up to date
- Should be used frequently as it provides access to the most current information

IMPORTANT USAGE NOTES:
- No conversation context is provided to this tool - your query must be completely self-contained
  and include all necessary context, instructions, and specific details about what information to find.`;
export const WEB_SEARCH_DESCRIPTION_DESCRIPTION = "A short one-sentence summary of the search query starting with 'Searching for...' or 'Looking for...'";
export const WEB_SEARCH_QUERY_DESCRIPTION = 'The search query to execute. Must be self-contained with all necessary context.';
const webSearchLlmSchema = Type.Object({
    description: Type.String({
        description: WEB_SEARCH_DESCRIPTION_DESCRIPTION,
        minLength: 1
    }),
    query: Type.String({
        description: WEB_SEARCH_QUERY_DESCRIPTION,
        minLength: 1
    })
});
const CITATION_MARKER_PATTERN = String.raw `\{\{citation-([a-zA-Z0-9]+)\}\}`;
function validateParams(value) {
    if (!value || typeof value !== 'object')
        return false;
    const candidate = value;
    return typeof candidate.description === 'string' && typeof candidate.query === 'string';
}
function isCitationEntry(value) {
    if (!value || typeof value !== 'object')
        return false;
    const candidate = value;
    return (typeof candidate.id === 'string' &&
        candidate.type === 'web' &&
        typeof candidate.title === 'string' &&
        typeof candidate.url === 'string' &&
        typeof candidate.cited_text === 'string');
}
function isCitationsRecord(value) {
    if (!value || typeof value !== 'object')
        return false;
    for (const entry of Object.values(value)) {
        if (!isCitationEntry(entry))
            return false;
    }
    return true;
}
function allUserMessageMarkersResolve(userMessage, citations) {
    if (!userMessage)
        return true;
    for (const match of userMessage.matchAll(new RegExp(CITATION_MARKER_PATTERN, 'g'))) {
        const id = match[1];
        if (!citations?.[id])
            return false;
    }
    return true;
}
function validateResultDetails(value) {
    if (value === undefined)
        return true;
    if (!value || typeof value !== 'object')
        return false;
    const candidate = value;
    const citationsAreValid = candidate.citations === undefined || isCitationsRecord(candidate.citations);
    return ((candidate.provider === undefined || candidate.provider === 'anthropic') &&
        (candidate.query === undefined || typeof candidate.query === 'string') &&
        (candidate.text === undefined || typeof candidate.text === 'string') &&
        (candidate.user_message === undefined || typeof candidate.user_message === 'string') &&
        citationsAreValid &&
        (typeof candidate.text !== 'string' ||
            !new RegExp(CITATION_MARKER_PATTERN).test(candidate.text)) &&
        allUserMessageMarkersResolve(candidate.user_message, citationsAreValid ? candidate.citations : undefined));
}
function normalizeResultDetails(value) {
    if (value === undefined)
        return undefined;
    const normalized = { ...value };
    if (value.citations) {
        const cloned = {};
        for (const [id, entry] of Object.entries(value.citations)) {
            cloned[id] = { ...entry };
        }
        return { ...normalized, citations: cloned };
    }
    return normalized;
}
function getResultObservabilityFields(details) {
    if (!validateResultDetails(details) || details === undefined)
        return undefined;
    const citationCount = details.citations !== undefined ? Object.keys(details.citations).length : undefined;
    const metadata = {
        ...(details.provider !== undefined ? { provider: details.provider } : {}),
        ...(citationCount !== undefined ? { citation_count: citationCount } : {})
    };
    return Object.keys(metadata).length > 0 ? metadata : undefined;
}
function evaluateApproval() {
    return { required: false };
}
function formatResult(details, isError, userMessage) {
    const body = details?.text?.trim();
    const cleanError = userMessage?.trim();
    const citationBody = details?.user_message?.trim();
    const text = body && body.length > 0
        ? body
        : citationBody && citationBody.length > 0
            ? citationBody
            : cleanError && cleanError.length > 0
                ? cleanError
                : isError
                    ? 'Web search failed.'
                    : 'No search results found.';
    // On failure, stamp the canonical user message into the renderer artifact;
    // on success the citation-marked body in `details.user_message` is the real
    // payload — pass `undefined` so the helper doesn't overwrite it.
    return {
        content: [{ type: 'text', text }],
        details: buildRendererArtifact(details, isError ? userMessage : undefined),
        isError
    };
}
function createLlmTool(options) {
    return {
        name: WEB_SEARCH,
        label: 'Web Search',
        isReadOnly: true,
        description: WEB_SEARCH_TOOL_DESCRIPTION,
        parameters: webSearchLlmSchema,
        async execute(toolCallId, llmArgs, signal) {
            const args = llmArgs;
            const params = {
                description: args.description,
                query: args.query
            };
            void options.sessionPolicy;
            const request = {
                approval: evaluateApproval(),
                heartbeat: {
                    label: 'Searching the web...',
                    toolName: WEB_SEARCH
                },
                kind: WEB_SEARCH_KIND,
                params,
                requestId: toolCallId
            };
            const result = await withAbort(signal, async () => options.pendingHostToolRequests.request(request));
            const details = result.details;
            const isError = result.status === HostToolResultStatus.Failed ||
                result.status === HostToolResultStatus.Cancelled;
            return formatResult(details, isError, result.userMessage);
        }
    };
}
export const webSearchContract = {
    kind: WEB_SEARCH_KIND,
    createLlmTool,
    evaluateApproval,
    getResultObservabilityFields,
    validateParams,
    validateResultDetails,
    normalizeResultDetails
};
//# sourceMappingURL=contract.js.map