/**
 * Per-tool contract for `execute_tool` — second hop of the lazy-load pair
 * with `get_tool_info`. This wrapper gates executable tools until their
 * schema was fetched for the current session.
 */
import { Type } from '@sinclair/typebox';
import { buildRendererArtifact, errorToMessage } from '../../../contracts/host-tool.js';
import { EXECUTABLE_TOOL_NAMES, isExecutableToolName } from '../get-tool-info/contract.js';
import { executableToolNameLiteralUnion } from '../get-tool-info/shared.js';
import { EXECUTE_TOOL } from '../tool-names.js';
import { webSearchContract } from '../web-search/contract.js';
export const EXECUTE_TOOL_KIND = 'executeTool';
// ---------------------------------------------------------------------------
// LLM-facing description + schema
// ---------------------------------------------------------------------------
export const EXECUTE_TOOL_TOOL_DESCRIPTION = `\
Execute a tool by name with the specified parameters.
You MUST call get_tool_info first to understand parameters before using execute_tool. Include all required parameters.

IMPORTANT: This tool dynamically executes other tools. Make sure you have the correct tool name and parameters before calling this.
All target tool parameters MUST be nested inside the 'parameters' dict — do NOT pass them as top-level keys. Correct: {"tool_name": "web_search", "parameters": {"query": "foo"}}. Wrong: {"tool_name": "web_search", "query": "foo"}.`;
export const EXECUTE_TOOL_TOOL_NAME_DESCRIPTION = "The name of the tool to execute (e.g., 'web_search', 'web_crawl')";
export const EXECUTE_TOOL_PARAMETERS_DESCRIPTION = 'Dictionary of parameters to pass to the tool. The keys should match the parameter names from get_tool_info.';
function buildExecuteToolLlmSchema(toolNames) {
    return Type.Object({
        tool_name: executableToolNameLiteralUnion(toolNames, {
            description: EXECUTE_TOOL_TOOL_NAME_DESCRIPTION,
            errorPrefix: 'execute_tool'
        }),
        parameters: Type.Record(Type.String(), Type.Unknown(), {
            description: EXECUTE_TOOL_PARAMETERS_DESCRIPTION
        })
    });
}
// ---------------------------------------------------------------------------
// Auto-nest pre-validator
// ---------------------------------------------------------------------------
/**
 * Lift stray top-level keys into a synthesized `parameters` object when
 * the LLM forgets to nest them. Exported for direct unit testing.
 */
export function autoNestFlatParameters(value) {
    if ('parameters' in value)
        return value;
    if (!('tool_name' in value))
        return value;
    const knownFields = new Set(['tool_name', 'parameters']);
    const extra = {};
    let hasExtra = false;
    const cleaned = {};
    for (const [key, v] of Object.entries(value)) {
        if (knownFields.has(key)) {
            cleaned[key] = v;
        }
        else {
            extra[key] = v;
            hasExtra = true;
        }
    }
    if (!hasExtra)
        return value;
    cleaned['parameters'] = extra;
    return cleaned;
}
// ---------------------------------------------------------------------------
// Validation + normalization
// ---------------------------------------------------------------------------
function validateParams(value) {
    if (!value || typeof value !== 'object')
        return false;
    const candidate = value;
    return (isExecutableToolName(candidate.tool_name) &&
        !!candidate.parameters &&
        typeof candidate.parameters === 'object' &&
        !Array.isArray(candidate.parameters));
}
function validateResultDetails(value) {
    if (value === undefined)
        return true;
    if (!value || typeof value !== 'object')
        return false;
    const candidate = value;
    const hasInner = Object.prototype.hasOwnProperty.call(candidate, 'inner');
    if (hasInner && candidate.inner === undefined)
        return false;
    const wrappedToolIsValid = candidate.wrapped_tool === undefined || isExecutableToolName(candidate.wrapped_tool);
    const innerIsValid = candidate.wrapped_tool === 'web_search' && hasInner
        ? webSearchContract.validateResultDetails(candidate.inner)
        : true;
    return ((candidate.hidden === undefined || typeof candidate.hidden === 'boolean') &&
        wrappedToolIsValid &&
        innerIsValid);
}
function normalizeResultDetails(value) {
    return value === undefined ? undefined : { ...value };
}
function getResultObservabilityFields(details) {
    if (!validateResultDetails(details) || details === undefined)
        return undefined;
    const metadata = {
        ...(details.hidden !== undefined ? { hidden: details.hidden } : {}),
        ...(details.wrapped_tool !== undefined ? { wrapped_tool: details.wrapped_tool } : {})
    };
    return Object.keys(metadata).length > 0 ? metadata : undefined;
}
function evaluateApproval() {
    return { required: false };
}
/** Composite content used in both the hidden-error path and the inner-error path. */
function textContent(text) {
    return [{ type: 'text', text }];
}
function hiddenErrorResult(message) {
    // In-process short-circuit (no host round-trip), so there's no
    // `HostToolResult.userMessage` to bridge from — pass `message` directly
    // through the renderer-artifact helper, which stamps `details.user_message`.
    return {
        content: textContent(message),
        details: buildRendererArtifact({ hidden: true }, message),
        isError: false
    };
}
function createLlmTool(options) {
    const availableToolNames = options.contractsByName === undefined
        ? [...EXECUTABLE_TOOL_NAMES]
        : EXECUTABLE_TOOL_NAMES.filter((name) => options.contractsByName?.[name] !== undefined);
    return {
        name: EXECUTE_TOOL,
        label: 'Execute tool',
        isReadOnly: false,
        description: EXECUTE_TOOL_TOOL_DESCRIPTION,
        parameters: buildExecuteToolLlmSchema(availableToolNames),
        getResultObservabilityFields,
        async execute(toolCallId, llmArgs, signal) {
            const normalizedArgs = autoNestFlatParameters((llmArgs ?? {}));
            const toolName = normalizedArgs.tool_name;
            const parameters = normalizedArgs.parameters;
            const targetContract = options.contractsByName?.[toolName];
            if (!targetContract) {
                return hiddenErrorResult(toolName === 'web_search'
                    ? "Tool 'web_search' is unavailable in this session. Use bash_command for web access."
                    : `Tool '${toolName}' is unavailable in this session. Available: ${availableToolNames.join(', ')}`);
            }
            if (!options.sessionState.hasFetched(toolName)) {
                return hiddenErrorResult(`You must call get_tool_info('${toolName}') first before executing this tool. ` +
                    `This ensures you understand the tool's parameters.`);
            }
            const def = targetContract.createLlmTool({
                pendingHostToolRequests: options.pendingHostToolRequests,
                sessionPolicy: options.sessionPolicy
            });
            // Distinct id so the pending-request duplicate guard doesn't
            // confuse inner and outer round-trips.
            const childToolCallId = `${toolCallId}-inner`;
            try {
                const result = await def.execute(childToolCallId, parameters, signal, undefined, undefined);
                // `wrapped_tool` lets the renderer layer dispatch to the inner renderer.
                return {
                    content: result.content,
                    details: {
                        wrapped_tool: toolName,
                        ...(result.details !== undefined ? { inner: result.details } : {})
                    },
                    isError: result.isError === true
                };
            }
            catch (error) {
                return hiddenErrorResult(`Error executing tool '${toolName}': ${errorToMessage(error)}`);
            }
        }
    };
}
export const executeToolContract = {
    kind: EXECUTE_TOOL_KIND,
    createLlmTool,
    evaluateApproval,
    getResultObservabilityFields,
    validateParams,
    validateResultDetails,
    normalizeResultDetails
};
//# sourceMappingURL=contract.js.map