/**
 * Per-tool contract for `execute_code` — spreadsheet read/write surface.
 * Owns LLM argument validation, approval classification, and structured
 * result normalization; host adapters own the actual spreadsheet execution.
 */
import { Type } from '@sinclair/typebox';
import { AgentExecuteCodeMode, ExecuteCodeSurface } from '../../../contracts/agent-execute-code.js';
import { HostToolResultStatus, buildRendererArtifact } from '../../../contracts/host-tool.js';
import { withAbort } from '../../../utils/abort.js';
import { toStructuredCloneSafeValue } from '../../../utils/structured-clone-safe.js';
import { truncateOutput } from '../../../utils/truncate.js';
import { EXECUTE_CODE } from '../tool-names.js';
import { ExecuteCodeAccess, classifyExecuteCode, evaluateAccessApproval } from './classify.js';
export { ExecuteCodeAccess };
export const EXECUTE_CODE_KIND = 'executeCode';
/** Status values surfaced on `ExecuteCodeDetails.status`; consumed by the renderer. */
export const ExecuteCodeDetailStatus = {
    Success: 'success',
    Error: 'error',
    Cancelled: 'cancelled',
    /** Static analysis blocked the code (e.g. write op in read-only mode). */
    Blocked: 'blocked'
};
export const EXECUTE_CODE_TOOL_DESCRIPTION = 'Execute Typescript code';
export const EXECUTE_CODE_HUMAN_DESCRIPTION = "A short one-sentence description of what the code does, starting with a gerund (e.g., 'Adding...', 'Fetching...', 'Updating...', 'Calculating...').";
export const EXECUTE_CODE_CODE_DESCRIPTION = `
The code to execute written as an async Typescript function. Return values are not saved nor logged.

**IMPORTANT**: Your code should be simple and efficient. Minimize code duplication.
- Keep each code block under 100 lines of code -- errors accumulate quickly if you write too much code.
- Minimize comments. Use console log statements instead and only do so when necessary as they are not revealed to the user. Console statements should be extremely concise and to the point.
- For read operations, prefer reading large ranges to grab richer context instead of iteratively reading over small ranges. You can read a maximum of 2000 cells at a time.

Working with addresses:
- Use Excel-style addresses (e.g., "A1", "B5") instead of row and column indices
- Use sheet name and the cell address, e.g. "Sheet1!A1" or "Sheet2!B2:C4" to refer to cells and ranges across sheets
- To convert between letters and indices, use addressToIndex(address) / indexToAddress(row, col)
- Methods that takes an Excel-style range or address can only take a single range or address, not multiples (e.g., avoid "A1:B2,C3:D4").

Structure this as an individual function: async function execute() { ... }.
Just define the function, never call it. The call will be automatically made.
`.trim();
const executeCodeLlmSchema = Type.Object({
    description: Type.String({
        description: EXECUTE_CODE_HUMAN_DESCRIPTION
    }),
    code: Type.String({
        description: EXECUTE_CODE_CODE_DESCRIPTION
    })
});
function isDirtyCell(value) {
    if (!value || typeof value !== 'object')
        return false;
    const candidate = value;
    return (typeof candidate.address === 'string' &&
        typeof candidate.sheet === 'string' &&
        (candidate.changeType === undefined ||
            candidate.changeType === 'direct' ||
            candidate.changeType === 'indirect') &&
        (candidate.formula === undefined || typeof candidate.formula === 'string') &&
        (candidate.numberFormat === undefined || typeof candidate.numberFormat === 'string'));
}
function validateParams(value) {
    if (!value || typeof value !== 'object')
        return false;
    const candidate = value;
    return ((candidate.access === ExecuteCodeAccess.ReadOnly ||
        candidate.access === ExecuteCodeAccess.ReadWrite) &&
        typeof candidate.code === 'string' &&
        typeof candidate.description === 'string' &&
        (candidate.surface === ExecuteCodeSurface.Plugin ||
            candidate.surface === ExecuteCodeSurface.Sheets ||
            candidate.surface === ExecuteCodeSurface.Web));
}
function validateResultDetails(value) {
    if (value === undefined)
        return true;
    if (!value || typeof value !== 'object')
        return false;
    const candidate = value;
    return ((candidate.blocked === undefined || typeof candidate.blocked === 'boolean') &&
        (candidate.change_count === undefined || typeof candidate.change_count === 'number') &&
        (candidate.direct_count === undefined || typeof candidate.direct_count === 'number') &&
        (candidate.detail === undefined || typeof candidate.detail === 'string') &&
        (candidate.disallowedFunctions === undefined ||
            (Array.isArray(candidate.disallowedFunctions) &&
                candidate.disallowedFunctions.every((f) => typeof f === 'string'))) &&
        (candidate.edit_ranges === undefined ||
            (Array.isArray(candidate.edit_ranges) &&
                candidate.edit_ranges.every((editRange) => typeof editRange === 'string'))) &&
        (candidate.status === undefined ||
            candidate.status === ExecuteCodeDetailStatus.Success ||
            candidate.status === ExecuteCodeDetailStatus.Error ||
            candidate.status === ExecuteCodeDetailStatus.Cancelled ||
            candidate.status === ExecuteCodeDetailStatus.Blocked) &&
        (candidate.indirect_count === undefined || typeof candidate.indirect_count === 'number') &&
        (candidate.logs === undefined ||
            (Array.isArray(candidate.logs) && candidate.logs.every((log) => typeof log === 'string'))) &&
        (candidate.dirtyCells === undefined ||
            (Array.isArray(candidate.dirtyCells) && candidate.dirtyCells.every(isDirtyCell))));
}
function normalizeResultDetails(value) {
    if (value === undefined)
        return undefined;
    return {
        ...value,
        dirtyCells: value.dirtyCells?.map((cell) => ({
            ...cell,
            oldValue: toStructuredCloneSafeValue(cell.oldValue),
            value: toStructuredCloneSafeValue(cell.value)
        }))
    };
}
function getResultObservabilityFields(details) {
    if (!validateResultDetails(details) || details === undefined)
        return undefined;
    const failed = details.status === ExecuteCodeDetailStatus.Error ||
        details.status === ExecuteCodeDetailStatus.Blocked ||
        details.status === ExecuteCodeDetailStatus.Cancelled;
    const metadata = {
        ...(details.status !== undefined ? { status: details.status } : {}),
        ...(details.blocked !== undefined ? { blocked: details.blocked } : {}),
        ...(failed ? { failure_reason: details.status } : {}),
        ...(details.change_count !== undefined ? { change_count: details.change_count } : {}),
        ...(details.direct_count !== undefined ? { direct_count: details.direct_count } : {}),
        ...(details.indirect_count !== undefined ? { indirect_count: details.indirect_count } : {}),
        ...(details.disallowedFunctions !== undefined
            ? { disallowed_functions_count: details.disallowedFunctions.length }
            : {}),
        ...(details.edit_ranges !== undefined ? { edit_ranges_count: details.edit_ranges.length } : {})
    };
    return Object.keys(metadata).length > 0 ? metadata : undefined;
}
function evaluateApproval(params, context) {
    return evaluateAccessApproval(params.access, context.autoApprove);
}
function formatResult(status, details, userMessage) {
    const logs = details?.logs ?? [];
    const logOutput = logs.join('\n');
    const cleanDetail = details?.detail?.trim() ?? '';
    const cleanError = userMessage?.trim() ?? '';
    if (status === HostToolResultStatus.Cancelled || status === HostToolResultStatus.Failed) {
        const prefix = status === HostToolResultStatus.Cancelled ? 'Cancelled' : 'Error';
        return {
            content: [
                {
                    type: 'text',
                    text: truncateOutput([`${prefix}: ${cleanError || cleanDetail}`, logOutput]
                        .filter((p) => p.length > 0)
                        .join('\n\n'))
                }
            ],
            details: buildRendererArtifact(details, userMessage),
            isError: true
        };
    }
    return {
        content: [{ type: 'text', text: truncateOutput(logOutput || cleanDetail) }],
        details: details ?? {}
    };
}
function createLlmTool(options) {
    const mode = options.mode ?? AgentExecuteCodeMode.Action;
    const surface = options.surface;
    // `isReadOnly` is exposed on the tool definition for inspection; the
    // authoritative decision is made per-call by classifyExecuteCode.
    const isReadOnly = mode === AgentExecuteCodeMode.Ask || mode === AgentExecuteCodeMode.Plan;
    return {
        name: EXECUTE_CODE,
        label: EXECUTE_CODE,
        isReadOnly,
        description: EXECUTE_CODE_TOOL_DESCRIPTION,
        parameters: executeCodeLlmSchema,
        async execute(toolCallId, llmArgs, signal) {
            const args = llmArgs;
            const outcome = classifyExecuteCode({
                autoApprove: options.sessionPolicy.getApprovalContext().autoApprove,
                btw: options.btw ?? false,
                code: args.code,
                mode,
                surface
            });
            if (outcome.kind === 'blocked') {
                const details = {
                    blocked: true,
                    disallowedFunctions: [...outcome.disallowed],
                    status: ExecuteCodeDetailStatus.Blocked
                };
                return {
                    content: [
                        {
                            type: 'text',
                            text: `Error: Code blocked: ${outcome.disallowed.join(', ')} not allowed in read-only mode. These functions modify the spreadsheet.`
                        }
                    ],
                    details,
                    isError: true
                };
            }
            const params = {
                access: outcome.access,
                code: args.code,
                description: args.description,
                surface
            };
            const request = {
                approval: outcome.approval,
                heartbeat: {
                    label: 'Running code...',
                    toolName: EXECUTE_CODE
                },
                kind: EXECUTE_CODE_KIND,
                params,
                requestId: toolCallId
            };
            const result = await withAbort(signal, async () => options.pendingHostToolRequests.request(request));
            const details = result.details;
            return formatResult(result.status, details, result.userMessage);
        }
    };
}
export const executeCodeContract = {
    kind: EXECUTE_CODE_KIND,
    createLlmTool,
    evaluateApproval,
    getResultObservabilityFields,
    validateParams,
    validateResultDetails,
    normalizeResultDetails
};
//# sourceMappingURL=contract.js.map