/*
 * Copyright OpenSearch Contributors
 * SPDX-License-Identifier: Apache-2.0
 */
import { ToolCallStatus } from '../../../types/index.js';
import { SubprocessConnector } from '../../../services/connectors/subprocess/SubprocessConnector.js';
/**
 * Default Claude Code configuration
 *
 * Telemetry: Set OTEL_EXPORTER_OTLP_ENDPOINT in your environment to enable
 * OpenTelemetry traces. Claude Code respects standard OTEL env vars.
 */
const CLAUDE_CODE_DEFAULT_CONFIG = {
    command: 'claude',
    args: ['--print', '--verbose', '--output-format', 'stream-json'], // Structured JSON output (--verbose required with stream-json)
    env: {
        // These can be overridden by agent config or environment
        DISABLE_PROMPT_CACHING: '1',
        DISABLE_ERROR_REPORTING: '1',
        // Note: DISABLE_TELEMETRY removed - telemetry enabled by default
        // Configure OTEL_EXPORTER_OTLP_ENDPOINT in .env to send traces
    },
    inputMode: 'stdin',
    outputParser: 'streaming',
    timeout: 600000, // 10 minutes for Claude Code
};
/**
 * Claude Code CLI Connector
 * Invokes Claude Code as a subprocess for agent evaluation
 */
export class ClaudeCodeConnector extends SubprocessConnector {
    constructor(config) {
        super({ ...CLAUDE_CODE_DEFAULT_CONFIG, ...config });
        this.type = 'claude-code';
        this.name = 'Claude Code CLI';
        this.traceContext = { propagateEnv: true, serviceName: 'claude-code-agent' };
        this.outputBuffer = '';
        this.thinkingBuffer = '';
        this.textBuffer = '';
        this.isInThinking = false;
    }
    /**
     * Build prompt for Claude Code
     * Structures the input to get the best RCA results
     */
    buildPayload(request) {
        const parts = [];
        // Add system context if available
        if (request.testCase.context && request.testCase.context.length > 0) {
            parts.push('## Context');
            for (const ctx of request.testCase.context) {
                parts.push(`**${ctx.description}:**`);
                parts.push(ctx.value);
                parts.push('');
            }
        }
        // Add the main prompt
        parts.push('## Task');
        parts.push(request.testCase.initialPrompt);
        return parts.join('\n');
    }
    /**
     * Parse Claude Code streaming output (stream-json format)
     * Each line is a JSON object with type and content
     */
    parseStreamingOutput(chunk, trajectory, onProgress) {
        this.outputBuffer += chunk;
        // Parse complete JSON lines (NDJSON format)
        const lines = this.outputBuffer.split('\n');
        this.outputBuffer = lines.pop() || ''; // Keep incomplete line
        for (const line of lines) {
            const trimmed = line.trim();
            if (!trimmed)
                continue;
            try {
                const event = JSON.parse(trimmed);
                const steps = this.parseJsonEvent(event);
                for (const step of steps) {
                    trajectory.push(step);
                    onProgress?.(step);
                }
            }
            catch {
                // Not JSON, treat as plain text
                if (trimmed) {
                    const step = this.createStep('assistant', trimmed);
                    trajectory.push(step);
                    onProgress?.(step);
                }
            }
        }
    }
    /**
     * Parse a single JSON event from stream-json output
     */
    parseJsonEvent(event) {
        const steps = [];
        // Handle different event types from Claude Code stream-json
        if (event.type === 'assistant' && event.message?.content) {
            for (const block of event.message.content) {
                if (block.type === 'thinking' && block.thinking) {
                    steps.push(this.createStep('thinking', block.thinking));
                }
                else if (block.type === 'text' && block.text) {
                    steps.push(this.createStep('assistant', block.text));
                }
                else if (block.type === 'tool_use') {
                    steps.push(this.createStep('action', JSON.stringify(block.input || {}), {
                        toolName: block.name,
                        toolArgs: block.input,
                    }));
                }
            }
        }
        else if (event.type === 'user' && event.message?.content) {
            // Claude Code emits tool results as user-role messages with tool_result
            // content blocks (referenced back to the assistant's tool_use_id).
            // Without this branch, tool outputs are silently dropped — the trajectory
            // shows the tool calls but not their results.
            for (const block of event.message.content) {
                if (block.type === 'tool_result') {
                    const content = typeof block.content === 'string'
                        ? block.content
                        : JSON.stringify(block.content);
                    steps.push(this.createStep('tool_result', content, {
                        status: block.is_error ? ToolCallStatus.FAILURE : ToolCallStatus.SUCCESS,
                    }));
                }
                else if (block.type === 'text' && block.text) {
                    // Rare: plain text in a user message (e.g., tool-orchestrator follow-ups)
                    steps.push(this.createStep('assistant', block.text));
                }
            }
        }
        else if (event.type === 'content_block_delta') {
            // Streaming delta updates — accumulate into buffers
            if (event.delta?.type === 'thinking_delta' && event.delta.thinking) {
                this.thinkingBuffer += event.delta.thinking;
            }
            else if (event.delta?.type === 'text_delta' && event.delta.text) {
                // Accumulate text deltas; the 'result' event will emit the final response
                this.textBuffer += event.delta.text;
            }
        }
        else if (event.type === 'content_block_stop') {
            // Flush thinking buffer when block ends
            if (this.thinkingBuffer) {
                steps.push(this.createStep('thinking', this.thinkingBuffer));
                this.thinkingBuffer = '';
            }
            // Flush text buffer when block ends (consolidated assistant step)
            if (this.textBuffer) {
                steps.push(this.createStep('assistant', this.textBuffer));
                this.textBuffer = '';
            }
        }
        else if (event.type === 'result' && event.result) {
            // Final result message
            steps.push(this.createStep('response', typeof event.result === 'string' ? event.result : JSON.stringify(event.result)));
        }
        return steps;
    }
    /**
     * Parse final output for Claude Code
     */
    parseResponse(data) {
        const steps = [];
        let content = data.stdout;
        // Extract thinking blocks
        const thinkingMatches = content.matchAll(/<thinking>([\s\S]*?)<\/thinking>/g);
        for (const match of thinkingMatches) {
            const thinking = match[1].trim();
            if (thinking) {
                steps.push(this.createStep('thinking', thinking));
            }
            content = content.replace(match[0], '');
        }
        // The remaining content is the response
        const response = content.trim();
        if (response) {
            steps.push(this.createStep('response', response));
        }
        // Add error if there was stderr
        if (data.exitCode !== 0 && data.stderr.trim()) {
            steps.push(this.createStep('tool_result', `Error: ${data.stderr.trim()}`, {
                status: ToolCallStatus.FAILURE,
            }));
        }
        return steps;
    }
    /**
     * Reset state for new execution
     */
    resetState() {
        this.outputBuffer = '';
        this.thinkingBuffer = '';
        this.textBuffer = '';
        this.isInThinking = false;
    }
    /**
     * Flush remaining buffers when the subprocess stream ends.
     */
    onBeforeStreamEnd(trajectory, onProgress) {
        // Flush outputBuffer (incomplete NDJSON line)
        if (this.outputBuffer.trim()) {
            try {
                const event = JSON.parse(this.outputBuffer.trim());
                const steps = this.parseJsonEvent(event);
                for (const step of steps) {
                    trajectory.push(step);
                    onProgress?.(step);
                }
            }
            catch {
                const step = this.createStep('assistant', this.outputBuffer.trim());
                trajectory.push(step);
                onProgress?.(step);
            }
            this.outputBuffer = '';
        }
        // Flush thinkingBuffer
        if (this.thinkingBuffer) {
            const step = this.createStep('thinking', this.thinkingBuffer);
            trajectory.push(step);
            onProgress?.(step);
            this.thinkingBuffer = '';
        }
        // Flush textBuffer (text deltas received without a result event)
        if (this.textBuffer) {
            const step = this.createStep('response', this.textBuffer);
            trajectory.push(step);
            onProgress?.(step);
            this.textBuffer = '';
        }
    }
    /**
     * Build CLI args from ClaudeCodeConnectorConfig
     */
    buildConfigArgs(config) {
        const args = [];
        if (config.dangerouslySkipPermissions) {
            args.push('--dangerously-skip-permissions');
        }
        if (config.systemPrompt) {
            args.push('--system-prompt', config.systemPrompt);
        }
        else if (config.appendSystemPrompt) {
            args.push('--append-system-prompt', config.appendSystemPrompt);
        }
        if (config.allowedTools?.length) {
            args.push('--allowed-tools', ...config.allowedTools);
        }
        if (config.disallowedTools?.length) {
            args.push('--disallowed-tools', ...config.disallowedTools);
        }
        if (config.mcpConfigPath) {
            args.push('--mcp-config', config.mcpConfigPath);
        }
        else if (config.mcpServers && Object.keys(config.mcpServers).length > 0) {
            args.push('--mcp-config', JSON.stringify({ mcpServers: config.mcpServers }));
        }
        if (config.strictMcpConfig) {
            args.push('--strict-mcp-config');
        }
        if (config.additionalArgs) {
            args.push(...config.additionalArgs);
        }
        return args;
    }
    /**
     * Override execute to reset state and apply connectorConfig
     */
    async execute(endpoint, request, auth, onProgress, onRawEvent) {
        this.debug('========== execute() STARTED ==========');
        this.debug('Endpoint:', endpoint);
        this.debug('Test case:', request.testCase.name);
        this.debug('Config:', this['config']);
        this.resetState();
        // Save original config for restoration after execution.
        // Uses structured clone for env to prevent leaking nested mutations
        // between consecutive executions in a benchmark run.
        const originalEnv = this.config.env ? structuredClone(this.config.env) : {};
        const originalArgs = this.config.args ? [...this.config.args] : [];
        const originalInputMode = this.config.inputMode;
        const originalTimeout = this.config.timeout;
        const originalWorkingDir = this.config.workingDir;
        // Apply connectorConfig if provided
        const ccConfig = request.connectorConfig;
        if (ccConfig) {
            this.debug('Applying connectorConfig:', Object.keys(ccConfig));
            // Merge environment variables
            if (ccConfig.env) {
                this.config.env = { ...this.config.env, ...ccConfig.env };
            }
            // Switch input mode if requested
            if (ccConfig.usePromptArg) {
                this.config.inputMode = 'arg';
            }
            // Override timeout if specified
            if (ccConfig.timeout !== undefined) {
                this.config.timeout = ccConfig.timeout;
            }
            // Override working directory if specified
            if (ccConfig.workingDir) {
                this.config.workingDir = ccConfig.workingDir;
            }
        }
        // When using Bedrock, clear any Anthropic API key to prevent
        // login-managed key from taking precedence and triggering a credit
        // balance check instead of routing through Bedrock.
        if (this.config.env?.CLAUDE_CODE_USE_BEDROCK === '1') {
            this.config.env = { ...this.config.env, ANTHROPIC_API_KEY: '' };
            this.debug('Bedrock mode: cleared ANTHROPIC_API_KEY to bypass credit check');
        }
        // Pass --model flag so Claude Code uses the requested model
        if (request.modelId) {
            this.config.args = [...this.config.args || [], '--model', request.modelId];
            this.debug('Model flag added:', request.modelId);
        }
        // Append config-driven args
        if (ccConfig) {
            const configArgs = this.buildConfigArgs(ccConfig);
            if (configArgs.length > 0) {
                this.config.args = [...(this.config.args || []), ...configArgs];
                this.debug('Config args added:', configArgs);
            }
        }
        try {
            this.debug('State reset, calling super.execute()...');
            const result = await super.execute(endpoint, request, auth, onProgress, onRawEvent);
            this.debug('super.execute() returned with', result.trajectory.length, 'steps');
            this.debug('========== execute() COMPLETED ==========');
            return result;
        }
        finally {
            // Restore config to pre-execution state. Uses deep copies to prevent
            // config pollution between consecutive executions in a benchmark run.
            this.config.env = originalEnv;
            this.config.args = originalArgs;
            this.config.inputMode = originalInputMode;
            this.config.timeout = originalTimeout;
            this.config.workingDir = originalWorkingDir;
        }
    }
    /**
     * Health check - verify claude command exists
     */
    async healthCheck(endpoint, auth) {
        return super.healthCheck(endpoint || 'claude', auth);
    }
}
/**
 * Create a Claude Code connector with specific Bedrock configuration
 */
export function createBedrockClaudeCodeConnector() {
    const env = {
        AWS_PROFILE: process.env.AWS_PROFILE || 'Bedrock',
        CLAUDE_CODE_USE_BEDROCK: '1',
        ANTHROPIC_API_KEY: '', // Prevent login-managed key from overriding Bedrock
        AWS_REGION: process.env.AWS_REGION || 'us-west-2',
        DISABLE_PROMPT_CACHING: '1',
        DISABLE_ERROR_REPORTING: '1',
    };
    const telemetryEnabled = process.env.CLAUDE_CODE_TELEMETRY_ENABLED === 'true';
    const otelEndpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
    if (telemetryEnabled && otelEndpoint) {
        env.CLAUDE_CODE_ENABLE_TELEMETRY = '1';
        env.OTEL_EXPORTER_OTLP_ENDPOINT = otelEndpoint;
        env.OTEL_TRACES_EXPORTER = 'otlp';
        env.OTEL_METRICS_EXPORTER = 'otlp';
        env.OTEL_LOGS_EXPORTER = 'otlp';
        if (process.env.OTEL_SERVICE_NAME)
            env.OTEL_SERVICE_NAME = process.env.OTEL_SERVICE_NAME;
        if (process.env.OTEL_EXPORTER_OTLP_PROTOCOL)
            env.OTEL_EXPORTER_OTLP_PROTOCOL = process.env.OTEL_EXPORTER_OTLP_PROTOCOL;
        if (process.env.OTEL_EXPORTER_OTLP_HEADERS)
            env.OTEL_EXPORTER_OTLP_HEADERS = process.env.OTEL_EXPORTER_OTLP_HEADERS;
    }
    else {
        env.DISABLE_TELEMETRY = '1';
    }
    return new ClaudeCodeConnector({ env });
}
/**
 * Default instance for convenience
 */
export const claudeCodeConnector = new ClaudeCodeConnector();
//# sourceMappingURL=ClaudeCodeConnector.js.map