"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var RunCommandHandler_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.RunCommandHandler = void 0;
const common_1 = require("@nestjs/common");
const run_command_args_dto_1 = require("./dto/run-command.args.dto");
const class_transformer_1 = require("class-transformer");
const class_validator_1 = require("class-validator");
const utils_1 = require("../../utils");
const spawn_with_kill_1 = require("../../utils/spawn-with-kill");
const fs = require("fs/promises");
const path = require("path");
const application_state_service_1 = require("../../application-state/application-state.service");
const gpt_tokenizer_1 = require("gpt-tokenizer");
let RunCommandHandler = RunCommandHandler_1 = class RunCommandHandler {
    constructor(applicationStateService) {
        this.applicationStateService = applicationStateService;
        this.toolName = 'run_command';
        this.logger = new common_1.Logger(RunCommandHandler_1.name);
        this.projectRoot = process.env.REPOBURG_PROJECT_PATH || process.cwd();
        this.tempDir = path.join(this.projectRoot, '.repoburg', 'temp');
    }
    getMetadata() {
        return {
            name: this.toolName,
            description: this.getDefinition(),
            arguments: [
                {
                    name: 'command_string',
                    type: 'string',
                    description: 'The shell command to execute.',
                    required: true,
                },
                {
                    name: 'timeout',
                    type: 'number',
                    description: 'Optional timeout in milliseconds (default: 60000ms, min: 1000ms).',
                    required: false,
                },
            ],
        };
    }
    generateExample(toolCall) {
        return (0, utils_1.generateToolCallJson)(toolCall);
    }
    getDefinition() {
        const treeExample = this.generateExample({
            tool_name: this.toolName,
            command_string: 'tree -L 2 src',
        });
        const rgExample = this.generateExample({
            tool_name: this.toolName,
            command_string: 'rg "TODO|FIXME" .',
        });
        const gitExample = this.generateExample({
            tool_name: this.toolName,
            command_string: 'git status',
        });
        const installExample = this.generateExample({
            tool_name: this.toolName,
            command_string: 'npm install lodash',
        });
        const timeoutExample = this.generateExample({
            tool_name: this.toolName,
            command_string: 'npm run build',
            timeout: 120000,
        });
        const definition = `
-------------
### ${this.toolName}
  Executes a shell command in the root of the project.
  Used for system modifications, running scripts, package management, or git operations.

  #### Parameters
  - "command_string": (string) The shell command to execute.
  - "timeout": (number, optional) Timeout in milliseconds. Default is 60000ms (1 minute), minimum is 1000ms.

  #### Example: Tree
:${treeExample}

  #### Example: RipGrep
:${rgExample}

  #### Example: Git
:${gitExample}

  #### Example: Install
:${installExample}

  #### Example: With Timeout
:${timeoutExample}
-------------
`;
        return `\n${definition.trim()}\n`;
    }
    normalizeArgs(args) {
        const normalized = { ...args };
        if ('command' in normalized && !('command_string' in normalized)) {
            normalized.command_string = normalized.command;
            delete normalized.command;
        }
        return normalized;
    }
    async execute(args, _context) {
        const normalizedArgs = this.normalizeArgs(args);
        const validatedArgs = (0, class_transformer_1.plainToClass)(run_command_args_dto_1.RunCommandArgsDto, normalizedArgs);
        const errors = await (0, class_validator_1.validate)(validatedArgs);
        if (errors.length > 0) {
            const errorMessages = errors
                .map((err) => Object.values(err.constraints || {}).join(', '))
                .join('; ');
            return {
                status: 'FAILURE',
                summary: `Invalid arguments for ${this.toolName}.`,
                error_message: errorMessages,
                persisted_args: args,
                execution_log: { output: '', error_message: errorMessages },
            };
        }
        const { command_string, timeout } = validatedArgs;
        const commandTimeout = timeout ?? 60000;
        let unifiedOutput;
        let summary;
        let status = 'SUCCESS';
        let errorMessage;
        await fs.mkdir(this.tempDir, { recursive: true });
        const scriptPath = path.join(this.tempDir, `run-${Date.now()}-${Math.random().toString(36).substring(7)}.sh`);
        try {
            const scriptContent = `
cd "${this.projectRoot}"
unset NODE_ENV
${command_string}
`;
            await fs.writeFile(scriptPath, scriptContent, { mode: 0o755 });
            const shell = process.env.SHELL || '/bin/bash';
            const command = `cd "${this.projectRoot}" && ${shell} -l "${scriptPath}"`;
            const result = await (0, spawn_with_kill_1.execWithProcessGroupKill)(command, {
                cwd: this.projectRoot,
                timeout: commandTimeout,
            });
            if (result.killed) {
                unifiedOutput = `COMMAND TIMED OUT after ${commandTimeout}ms\n\nSTDOUT:\n${result.stdout || 'N/A'}\n\nSTDERR:\n${result.stderr || 'N/A'}`;
                summary = `Command "${command_string}" timed out after ${commandTimeout}ms.`;
                status = 'FAILURE';
                errorMessage = `Command timed out after ${commandTimeout}ms.`;
                this.logger.warn(`Command timed out: ${command_string}`);
            }
            else if (result.exitCode !== 0) {
                unifiedOutput = `COMMAND FAILED with exit code ${result.exitCode}\n\nSTDOUT:\n${result.stdout || 'N/A'}\n\nSTDERR:\n${result.stderr || 'N/A'}`;
                summary = `Command "${command_string}" failed.`;
                status = 'FAILURE';
                errorMessage = `Command failed with exit code ${result.exitCode}.`;
                this.logger.error(`Command failed: ${unifiedOutput}`);
            }
            else {
                unifiedOutput =
                    result.stdout || result.stderr || '(Command produced no output)';
                summary = `Command "${command_string}" executed.`;
            }
        }
        catch (error) {
            const execError = error;
            unifiedOutput = `COMMAND FAILED: ${execError.message}\n\nSTDOUT:\n${execError.stdout || 'N/A'}\n\nSTDERR:\n${execError.stderr || 'N/A'}`;
            summary = `Command "${command_string}" failed.`;
            status = 'FAILURE';
            errorMessage = execError.message;
            this.logger.error(`Error executing command: ${unifiedOutput}`);
        }
        finally {
            await fs
                .unlink(scriptPath)
                .catch((err) => this.logger.warn(`Failed to cleanup temp file ${scriptPath}: ${err.message}`));
        }
        const tokenCount = (0, gpt_tokenizer_1.countTokens)(unifiedOutput);
        const tokenLimit = await this.applicationStateService.getEffectiveFollowupTokenLimit(_context.system_prompt_id);
        if (tokenCount > tokenLimit) {
            const ratio = tokenLimit / tokenCount;
            const truncatedLength = Math.floor(unifiedOutput.length * ratio);
            const truncatedOutput = unifiedOutput.slice(0, truncatedLength);
            const truncationWarning = `\n\n[OUTPUT TRUNCATED: ${tokenCount} tokens exceeds limit of ${tokenLimit}. Consider redirecting large outputs to a file and using request_context to read it.]`;
            unifiedOutput = truncatedOutput + truncationWarning;
            errorMessage =
                (errorMessage || '') +
                    `Output truncated due to size limit (${tokenCount} > ${tokenLimit} tokens).`;
            this.logger.warn(`Command output truncated: ${tokenCount} tokens exceeds limit of ${tokenLimit}`);
        }
        return {
            status,
            summary: `${summary} Output captured.`,
            persisted_args: { command_string, timeout: commandTimeout },
            error_message: errorMessage,
            execution_log: {
                output: unifiedOutput,
                error_message: errorMessage || '',
            },
        };
    }
};
exports.RunCommandHandler = RunCommandHandler;
exports.RunCommandHandler = RunCommandHandler = RunCommandHandler_1 = __decorate([
    (0, common_1.Injectable)(),
    __metadata("design:paramtypes", [application_state_service_1.ApplicationStateService])
], RunCommandHandler);
//# sourceMappingURL=run-command.handler.js.map