/**
 * Per-tool contract for `sandbox_llm` — multimodal file analysis dispatched
 * through host-tool RPC. Pure contract; the adapter performs the IO.
 */
import { Type } from '@sinclair/typebox';
import { HostToolResultStatus, buildRendererArtifact } from '../../../contracts/host-tool.js';
import { withAbort } from '../../../utils/abort.js';
import { SANDBOX_LLM } from '../tool-names.js';
export const SANDBOX_LLM_KIND = 'sandboxLlm';
export const SANDBOX_LLM_GEMINI_MODEL = 'gemini-3-flash-preview';
export const SANDBOX_LLM_HAIKU_MODEL = 'claude-haiku-4-5-20251001';
export const SANDBOX_LLM_MAX_FILE_PATHS = 20;
export const SANDBOX_LLM_OUTPUT_TOKENS = 32_768;
export const SANDBOX_LLM_TIMEOUT_SECONDS = 300;
export const SANDBOX_LLM_PDF_PAGE_LIMIT = 100;
export const SANDBOX_LLM_PDF_SIZE_LIMIT_MB = 10;
export const SANDBOX_UPLOADS_PATH = '/workspace';
export const SANDBOX_LLM_IMAGE_EXTENSIONS = {
    '.png': 'image/png',
    '.jpg': 'image/jpeg',
    '.jpeg': 'image/jpeg',
    '.gif': 'image/gif',
    '.webp': 'image/webp'
};
export const SANDBOX_LLM_TEXT_EXTENSIONS = new Set([
    '.txt',
    '.csv',
    '.tsv',
    '.json',
    '.jsonl',
    '.md',
    '.xml',
    '.yaml',
    '.yml',
    '.toml',
    '.html',
    '.htm',
    '.css',
    '.js',
    '.ts',
    '.py',
    '.sql',
    '.log',
    '.ini',
    '.cfg',
    '.env',
    '.sh',
    '.bash',
    '.zsh'
]);
export const SANDBOX_LLM_SYSTEM_PROMPT = `You are a document extraction assistant.

Extraction rules:
- Preserve data exactly: extracted numbers, formulas, formatting, and text must match the source precisely
- For structured data extractions such as tables
    - The delimiter used must be unambiguous and not included in the data itself. Use pipes \`|\` by default.
    - Make sure to handle empty values appropriately (e.g., "| |"). Skipping empty values will cause errors when programmatic extraction is done

Referencing:
- Ensure that extracted data is cited. For example, [Page X, Table "Name"] or [Page X, Section "Name"] before a dump of data blocks.
- IMPORTANT: Always cite using page numbers from the source document (on the bottom right of the page)`;
const supportedTextTypesString = [...SANDBOX_LLM_TEXT_EXTENSIONS].sort().join(', ');
const supportedImageTypesString = Object.keys(SANDBOX_LLM_IMAGE_EXTENSIONS).join(', ');
/** Listing one-liner used by `get_tool_info`. */
export const SANDBOX_LLM_TOOL_SUMMARY = 'Analyze sandbox files (images, PDFs, text) using a vision LLM.';
export const SANDBOX_LLM_TOOL_DESCRIPTION = `Analyzes sandbox files using a vision LLM.

Supported text types: ${supportedTextTypesString}
Supported image types: ${supportedImageTypesString}
Also supports: .pdf

MODEL SELECTION:
- "${SANDBOX_LLM_GEMINI_MODEL}" (default): Best for careful extraction, tables, and especially images
- "${SANDBOX_LLM_HAIKU_MODEL}": Quick scanning, structure discovery, fallback to ${SANDBOX_LLM_GEMINI_MODEL} if it doesn't work.

FOR EXTRACTION TASKS:
- Pay close attention to edge cases, such as commas, spaces, signs and null values.
- For financial data, parentheses are used to indicate negative values.

Output filename is always saved to the file's stem folder:
- For example, blah/report.pdf -> blah/report/analysis.md
- uploads/report/analysis.pdf -> uploads/report/analysis/final_analysis.md

⚠️ PDF SIZE LIMIT: Files cannot exceed ${SANDBOX_LLM_PDF_SIZE_LIMIT_MB} MB (exceeding causes LLM failures). Slice the PDF into smaller chunks or downsample to 150 dpi (use ghostscript for PDFs).
⚠️ PDF PAGE LIMIT: Claude Haiku has a ${SANDBOX_LLM_PDF_PAGE_LIMIT} page HARD LIMIT (exceeding causes instant failure).`;
export const SANDBOX_LLM_DESCRIPTION_DESCRIPTION = "Brief human-readable description of what this analysis will do (e.g., 'Extracting sales data from Q4 report')";
export const SANDBOX_LLM_FILE_PATHS_DESCRIPTION = `List of file paths in sandbox (e.g., ['${SANDBOX_UPLOADS_PATH}/chart.png']). Omit if the query already contains all needed information.`;
export const SANDBOX_LLM_QUERY_DESCRIPTION = "The question/instruction for the LLM about the content. Can also be a file path ending in .txt (e.g., '/uploads/report/query.txt') — the file contents will be used as the query. Useful for batch/parallel calls to programmatically write .txt query files with variables.";
export const SANDBOX_LLM_MODEL_DESCRIPTION = `Model to use: '${SANDBOX_LLM_GEMINI_MODEL}' (best for images/tables) or '${SANDBOX_LLM_HAIKU_MODEL}' (fallback/verifier)`;
export const SANDBOX_LLM_OUTPUT_FILENAME_DESCRIPTION = "Optional output filename (e.g., 'analysis.md'). If empty, auto-generates a unique filename.";
const sandboxLlmLlmSchema = Type.Object({
    description: Type.String({
        description: SANDBOX_LLM_DESCRIPTION_DESCRIPTION,
        minLength: 1
    }),
    query: Type.String({
        description: SANDBOX_LLM_QUERY_DESCRIPTION,
        minLength: 1
    }),
    file_paths: Type.Optional(Type.Array(Type.String(), {
        description: SANDBOX_LLM_FILE_PATHS_DESCRIPTION,
        maxItems: SANDBOX_LLM_MAX_FILE_PATHS
    })),
    model: Type.Optional(Type.String({
        description: SANDBOX_LLM_MODEL_DESCRIPTION
    })),
    output_filename: Type.Optional(Type.String({
        description: SANDBOX_LLM_OUTPUT_FILENAME_DESCRIPTION
    }))
});
function validateParams(value) {
    if (!value || typeof value !== 'object')
        return false;
    const candidate = value;
    if (typeof candidate.description !== 'string')
        return false;
    if (typeof candidate.query !== 'string')
        return false;
    if (candidate.file_paths !== undefined) {
        if (!Array.isArray(candidate.file_paths))
            return false;
        if (candidate.file_paths.length > SANDBOX_LLM_MAX_FILE_PATHS)
            return false;
        for (const fp of candidate.file_paths) {
            if (typeof fp !== 'string')
                return false;
        }
    }
    if (candidate.model !== undefined && typeof candidate.model !== 'string')
        return false;
    if (candidate.output_filename !== undefined && typeof candidate.output_filename !== 'string')
        return false;
    return true;
}
function validateResultDetails(value) {
    if (value === undefined)
        return true;
    if (!value || typeof value !== 'object')
        return false;
    const candidate = value;
    return ((candidate.body === undefined || typeof candidate.body === 'string') &&
        (candidate.error === undefined || typeof candidate.error === 'boolean') &&
        (candidate.save_path === undefined || typeof candidate.save_path === 'string') &&
        (candidate.model === undefined || typeof candidate.model === 'string'));
}
function normalizeResultDetails(value) {
    return value === undefined ? undefined : { ...value };
}
function getResultObservabilityFields(details) {
    if (!validateResultDetails(details) || details === undefined)
        return undefined;
    const metadata = {
        ...(details.model !== undefined ? { model: details.model } : {}),
        ...(details.error !== undefined ? { error: details.error } : {})
    };
    return Object.keys(metadata).length > 0 ? metadata : undefined;
}
function evaluateApproval() {
    return { required: false };
}
function formatResult(details, isError, userMessage) {
    const fallback = isError
        ? 'Sandbox LLM call failed.'
        : details?.save_path
            ? `Analysis complete. Saved to ${details.save_path}.`
            : 'Sandbox LLM call returned no result.';
    const text = isError ? userMessage || fallback : details?.body || userMessage || fallback;
    return {
        content: [{ type: 'text', text }],
        details: buildRendererArtifact(details, userMessage),
        isError
    };
}
function createLlmTool(options) {
    return {
        name: SANDBOX_LLM,
        label: 'Sandbox LLM',
        isReadOnly: false,
        description: SANDBOX_LLM_TOOL_DESCRIPTION,
        parameters: sandboxLlmLlmSchema,
        async execute(toolCallId, llmArgs, signal) {
            const args = llmArgs;
            const params = {
                description: args.description,
                query: args.query,
                ...(args.file_paths !== undefined ? { file_paths: args.file_paths } : {}),
                ...(args.model !== undefined ? { model: args.model } : {}),
                ...(args.output_filename !== undefined ? { output_filename: args.output_filename } : {})
            };
            void options.sessionPolicy;
            const request = {
                approval: evaluateApproval(),
                heartbeat: {
                    label: 'Analyzing sandbox files...',
                    toolName: SANDBOX_LLM
                },
                kind: SANDBOX_LLM_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 ||
                details?.error === true;
            return formatResult(details, isError, result.userMessage);
        }
    };
}
export const sandboxLlmContract = {
    kind: SANDBOX_LLM_KIND,
    createLlmTool,
    evaluateApproval,
    getResultObservabilityFields,
    validateParams,
    validateResultDetails,
    normalizeResultDetails
};
//# sourceMappingURL=contract.js.map