"use strict";
/**
 * Tool Sanitization Proxy
 *
 * HTTP proxy that intercepts Claude CLI → CLIProxy requests to:
 * 1. Sanitize MCP tool names exceeding Gemini's 64-char limit
 * 2. Sanitize MCP tool input_schema to remove non-standard JSON Schema properties
 * 3. Forward sanitized requests to upstream
 * 4. Restore original names in responses
 *
 * Follows CodexReasoningProxy pattern for consistency.
 */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ToolSanitizationProxy = void 0;
const http = __importStar(require("http"));
const https = __importStar(require("https"));
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const os = __importStar(require("os"));
const url_1 = require("url");
const tool_name_mapper_1 = require("../ai-providers/tool-name-mapper");
const schema_sanitizer_1 = require("../ai-providers/schema-sanitizer");
const model_id_normalizer_1 = require("../ai-providers/model-id-normalizer");
const model_catalog_1 = require("../model-catalog");
const logging_1 = require("../../services/logging");
const config_loader_facade_1 = require("../../config/config-loader-facade");
/**
 * Type guard to check if a value is a plain object (Record).
 * Used for safely accessing properties on unknown JSON values.
 */
function isRecord(value) {
    return typeof value === 'object' && value !== null && !Array.isArray(value);
}
const GEMINI_UNSUPPORTED_TOOL_FIELDS = new Set([
    'strict',
    'input_examples',
    'type',
    'cache_control',
    'defer_loading',
]);
const CODEX_UNSUPPORTED_TOOL_FIELDS = new Set(['cache_control']);
const EXTENDED_CONTEXT_SUFFIX_REGEX = /\[1m\]$/i;
const LEGACY_CODEX_MODEL_ID_REGEX = /^gpt-5(?:\.\d+)?-codex(?:-(?:mini|max))?$/i;
function canonicalizeCodexModelId(model) {
    const normalizedModel = model?.trim().toLowerCase();
    if (!normalizedModel) {
        return null;
    }
    const withoutExtendedContext = normalizedModel.replace(EXTENDED_CONTEXT_SUFFIX_REGEX, '').trim();
    return (0, model_id_normalizer_1.stripCodexEffortSuffix)(withoutExtendedContext);
}
function isKnownCodexModelId(model) {
    const normalizedModel = canonicalizeCodexModelId(model);
    if (!normalizedModel) {
        return false;
    }
    // Root-routed requests can carry Codex model IDs that CCS uses outside the
    // small interactive catalog (for example image analysis and Cursor defaults).
    return (LEGACY_CODEX_MODEL_ID_REGEX.test(normalizedModel) ||
        (0, model_catalog_1.getModelMaxLevel)('codex', normalizedModel) !== undefined);
}
function getUnsupportedToolFields(providerFromPath, model) {
    const normalizedProvider = providerFromPath?.trim().toLowerCase() ?? null;
    const normalizedModel = model?.trim().toLowerCase();
    if (normalizedProvider === 'gemini' ||
        normalizedProvider === 'gemini-cli' ||
        (normalizedProvider === null && normalizedModel?.startsWith('gemini-'))) {
        return GEMINI_UNSUPPORTED_TOOL_FIELDS;
    }
    if (normalizedProvider === 'codex' ||
        (normalizedProvider === null && isKnownCodexModelId(model))) {
        return CODEX_UNSUPPORTED_TOOL_FIELDS;
    }
    return null;
}
function stripUnsupportedToolFields(tools, unsupportedFields) {
    const removedByTool = [];
    let totalRemoved = 0;
    const sanitizedTools = tools.map((tool) => {
        const sanitizedTool = { ...tool };
        const removed = [];
        for (const field of unsupportedFields) {
            if (field in sanitizedTool) {
                delete sanitizedTool[field];
                removed.push(field);
            }
        }
        if (removed.length > 0) {
            removedByTool.push({
                name: tool.name,
                removed,
            });
            totalRemoved += removed.length;
        }
        return sanitizedTool;
    });
    return {
        tools: sanitizedTools,
        removedByTool,
        totalRemoved,
    };
}
class ToolSanitizationProxy {
    constructor(config) {
        this.server = null;
        this.port = null;
        this.logger = (0, logging_1.createLogger)('cliproxy:tool-sanitization-proxy');
        this.config = {
            upstreamBaseUrl: config.upstreamBaseUrl,
            verbose: config.verbose ?? false,
            warnOnSanitize: config.warnOnSanitize ?? true,
            timeoutMs: config.timeoutMs ?? 120000,
            allowSelfSigned: config.allowSelfSigned ?? false,
        };
        this.debugMode = process.env.CCS_DEBUG === '1';
        this.logFilePath = this.initLogFile();
    }
    /**
     * Initialize log file path and ensure directory exists.
     */
    initLogFile() {
        const logsDir = path.join((0, config_loader_facade_1.getCcsDir)(), 'logs');
        try {
            if (!fs.existsSync(logsDir)) {
                fs.mkdirSync(logsDir, { recursive: true });
            }
        }
        catch (err) {
            // Fallback to temp directory if logs dir creation fails
            if (this.debugMode) {
                console.error(`[tool-sanitization-proxy] Failed to create logs dir: ${err.message}`);
            }
            return path.join(os.tmpdir(), 'tool-sanitization-proxy.log');
        }
        return path.join(logsDir, 'tool-sanitization-proxy.log');
    }
    /**
     * Write log entry to file (always) and console (if CCS_DEBUG=1).
     */
    writeLog(level, message) {
        const timestamp = new Date().toISOString();
        const prefix = level === 'info' ? '[i]' : level === 'warn' ? '[!]' : '[X]';
        const logLine = `${timestamp} ${prefix} ${message}\n`;
        // Always write to file
        try {
            fs.appendFileSync(this.logFilePath, logLine);
        }
        catch {
            // Silently ignore file write errors
        }
        // Console output only in debug mode
        if (this.debugMode) {
            console.error(`${prefix} ${message}`);
        }
        this.logger[level](level, message, {
            debugMode: this.debugMode,
            logFilePath: this.logFilePath,
        });
    }
    log(message) {
        if (this.config.verbose) {
            this.writeLog('info', `[tool-sanitization-proxy] ${message}`);
        }
    }
    warn(message) {
        if (this.config.warnOnSanitize) {
            this.writeLog('warn', `Tool name sanitized: ${message}`);
        }
    }
    /**
     * Start the proxy server on an ephemeral port.
     * @returns The assigned port number
     */
    async start() {
        if (this.server)
            return this.port ?? 0;
        return new Promise((resolve, reject) => {
            this.server = http.createServer((req, res) => {
                void this.handleRequest(req, res);
            });
            this.server.listen(0, '127.0.0.1', () => {
                const address = this.server?.address();
                this.port = typeof address === 'object' && address ? address.port : 0;
                this.writeLog('info', `Tool sanitization proxy active (port ${this.port})`);
                resolve(this.port);
            });
            this.server.on('error', (err) => reject(err));
        });
    }
    /**
     * Stop the proxy server.
     */
    stop() {
        if (!this.server)
            return;
        this.server.close();
        this.server = null;
        this.port = null;
    }
    /**
     * Get the port the proxy is listening on.
     */
    getPort() {
        return this.port;
    }
    readBody(req) {
        return new Promise((resolve, reject) => {
            const chunks = [];
            const maxSize = 10 * 1024 * 1024; // 10MB
            let total = 0;
            req.on('data', (chunk) => {
                total += chunk.length;
                if (total > maxSize) {
                    req.destroy();
                    reject(new Error('Request body too large (max 10MB)'));
                    return;
                }
                chunks.push(chunk);
            });
            req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
            req.on('error', reject);
        });
    }
    async handleRequest(req, res) {
        const method = req.method || 'GET';
        const requestPath = req.url || '/';
        const upstreamBase = new url_1.URL(this.config.upstreamBaseUrl);
        const fullUpstreamUrl = new url_1.URL(requestPath, upstreamBase);
        const providerFromPath = (0, model_id_normalizer_1.extractProviderFromPathname)(fullUpstreamUrl.pathname);
        this.log(`${method} ${requestPath} → ${fullUpstreamUrl.href}`);
        // Only buffer+rewrite JSON POST requests
        const contentType = String(req.headers['content-type'] || '');
        const isJson = contentType.includes('application/json');
        const shouldRewrite = isJson && ['POST', 'PUT', 'PATCH'].includes(method.toUpperCase());
        try {
            if (!shouldRewrite) {
                await this.forwardRaw(req, res, fullUpstreamUrl);
                return;
            }
            const rawBody = await this.readBody(req);
            let parsed;
            try {
                parsed = rawBody.length ? JSON.parse(rawBody) : {};
            }
            catch {
                res.writeHead(400, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify({ error: 'Invalid JSON in request body' }));
                return;
            }
            // Create mapper for this request
            const mapper = new tool_name_mapper_1.ToolNameMapper();
            // Normalize dotted Claude model IDs for provider-compatible routing.
            let modifiedBody = parsed;
            if (isRecord(modifiedBody) && typeof modifiedBody.model === 'string') {
                const deniedReason = (0, model_id_normalizer_1.getDeniedModelIdReasonForProvider)(modifiedBody.model, providerFromPath);
                if (deniedReason) {
                    res.writeHead(400, { 'Content-Type': 'application/json' });
                    res.end(JSON.stringify({ error: deniedReason }));
                    return;
                }
                const normalizedModel = (0, model_id_normalizer_1.normalizeModelIdForRouting)(modifiedBody.model, providerFromPath);
                if (normalizedModel !== modifiedBody.model) {
                    this.writeLog('warn', `[tool-sanitization-proxy] Model normalized for provider routing (${providerFromPath ?? 'root'}): "${modifiedBody.model}" → "${normalizedModel}"`);
                    modifiedBody = { ...modifiedBody, model: normalizedModel };
                }
            }
            // Sanitize tools if present
            if (isRecord(modifiedBody) && Array.isArray(modifiedBody.tools)) {
                // Step 1: Sanitize input_schema properties (remove non-standard JSON Schema properties)
                const schemaResult = (0, schema_sanitizer_1.sanitizeToolSchemas)(modifiedBody.tools);
                if (schemaResult.totalRemoved > 0) {
                    for (const entry of schemaResult.removedByTool) {
                        this.writeLog('warn', `[tool-sanitization-proxy] Schema sanitized for "${entry.name}": removed ${entry.removed.length} Gemini-unsupported properties`);
                    }
                    this.log(`Sanitized ${schemaResult.totalRemoved} schema properties across ${schemaResult.removedByTool.length} tool(s)`);
                }
                let rewrittenTools = schemaResult.tools;
                const unsupportedToolFields = isRecord(modifiedBody) && typeof modifiedBody.model === 'string'
                    ? getUnsupportedToolFields(providerFromPath, modifiedBody.model)
                    : getUnsupportedToolFields(providerFromPath, undefined);
                if (unsupportedToolFields) {
                    const fieldResult = stripUnsupportedToolFields(rewrittenTools, unsupportedToolFields);
                    if (fieldResult.totalRemoved > 0) {
                        for (const entry of fieldResult.removedByTool) {
                            this.writeLog('warn', `[tool-sanitization-proxy] Tool fields stripped for "${entry.name}" (${providerFromPath ?? 'model-routed'}): ${entry.removed.join(', ')}`);
                        }
                        this.log(`Stripped ${fieldResult.totalRemoved} unsupported top-level tool field(s) across ${fieldResult.removedByTool.length} tool(s)`);
                    }
                    rewrittenTools = fieldResult.tools;
                }
                // Step 2: Sanitize tool names (truncate to 64 chars for Gemini)
                const sanitizedTools = mapper.registerTools(rewrittenTools);
                modifiedBody = { ...modifiedBody, tools: sanitizedTools };
                // Log sanitization warnings
                if (mapper.hasChanges()) {
                    const changes = mapper.getChanges();
                    for (const change of changes) {
                        this.warn(`"${change.original}" → "${change.sanitized}"`);
                    }
                    this.log(`Sanitized ${changes.length} tool name(s)`);
                }
                // Warn about hash collisions (multiple originals → same sanitized)
                if (mapper.hasCollisions()) {
                    const collisions = mapper.getCollisions();
                    for (const collision of collisions) {
                        this.writeLog('warn', `[tool-sanitization-proxy] Hash collision detected: ${collision.originals.join(', ')} → "${collision.sanitized}"`);
                    }
                }
            }
            // Check if streaming is requested
            const isStreaming = isRecord(modifiedBody) && modifiedBody.stream === true;
            if (isStreaming) {
                await this.forwardJsonStreaming(req, res, fullUpstreamUrl, modifiedBody, mapper);
            }
            else {
                await this.forwardJsonBuffered(req, res, fullUpstreamUrl, modifiedBody, mapper);
            }
        }
        catch (error) {
            const err = error;
            this.log(`Error: ${err.message}`);
            if (!res.headersSent) {
                res.writeHead(502, { 'Content-Type': 'application/json' });
            }
            res.end(JSON.stringify({ error: err.message }));
        }
    }
    buildForwardHeaders(originalHeaders, bodyString) {
        const headers = {};
        // RFC 7230 hop-by-hop headers that should not be forwarded
        const hopByHop = new Set([
            'host',
            'content-length',
            'connection',
            'transfer-encoding',
            'keep-alive',
            'proxy-authenticate',
            'proxy-authorization',
            'te',
            'trailer',
            'upgrade',
        ]);
        for (const [key, value] of Object.entries(originalHeaders)) {
            if (!value)
                continue;
            const lower = key.toLowerCase();
            if (hopByHop.has(lower))
                continue;
            headers[key] = value;
        }
        if (bodyString !== undefined) {
            headers['Content-Type'] = headers['Content-Type'] || 'application/json';
            headers['Content-Length'] = Buffer.byteLength(bodyString);
        }
        return headers;
    }
    getRequestFn(url) {
        return url.protocol === 'https:' ? https.request : http.request;
    }
    startResponseTimeout(upstreamReq) {
        const deadline = setTimeout(() => {
            upstreamReq.destroy(new Error('Upstream request timeout'));
        }, this.config.timeoutMs);
        upstreamReq.setTimeout(this.config.timeoutMs, () => {
            upstreamReq.destroy(new Error('Upstream request timeout'));
        });
        return () => clearTimeout(deadline);
    }
    buildRequestOptions(upstreamUrl, method, headers) {
        const options = {
            protocol: upstreamUrl.protocol,
            hostname: upstreamUrl.hostname,
            port: upstreamUrl.port,
            path: upstreamUrl.pathname + upstreamUrl.search,
            method,
            timeout: this.config.timeoutMs,
            headers,
        };
        if (upstreamUrl.protocol === 'https:' && this.config.allowSelfSigned) {
            options.rejectUnauthorized = false;
        }
        return options;
    }
    forwardRaw(originalReq, clientRes, upstreamUrl) {
        return new Promise((resolve, reject) => {
            const requestFn = this.getRequestFn(upstreamUrl);
            let clearResponseTimeout = () => undefined;
            const upstreamReq = requestFn(this.buildRequestOptions(upstreamUrl, originalReq.method, this.buildForwardHeaders(originalReq.headers)), (upstreamRes) => {
                clearResponseTimeout();
                clientRes.writeHead(upstreamRes.statusCode || 200, upstreamRes.headers);
                upstreamRes.pipe(clientRes);
                upstreamRes.on('end', () => resolve());
                upstreamRes.on('error', reject);
            });
            clearResponseTimeout = this.startResponseTimeout(upstreamReq);
            upstreamReq.on('timeout', () => upstreamReq.destroy(new Error('Upstream request timeout')));
            upstreamReq.on('error', (err) => {
                clearResponseTimeout();
                reject(err);
            });
            originalReq.pipe(upstreamReq);
        });
    }
    /**
     * Forward JSON request and buffer response for tool name restoration.
     */
    forwardJsonBuffered(originalReq, clientRes, upstreamUrl, body, mapper) {
        return new Promise((resolve, reject) => {
            const bodyString = JSON.stringify(body);
            const requestFn = this.getRequestFn(upstreamUrl);
            let clearResponseTimeout = () => undefined;
            const upstreamReq = requestFn(this.buildRequestOptions(upstreamUrl, originalReq.method, this.buildForwardHeaders(originalReq.headers, bodyString)), (upstreamRes) => {
                clearResponseTimeout();
                const chunks = [];
                upstreamRes.on('data', (chunk) => chunks.push(chunk));
                upstreamRes.on('end', () => {
                    try {
                        const responseBody = Buffer.concat(chunks).toString('utf8');
                        const contentType = upstreamRes.headers['content-type'] || '';
                        // Only process JSON responses with tool_use blocks
                        if (contentType.includes('application/json') && mapper.hasChanges()) {
                            try {
                                const parsed = JSON.parse(responseBody);
                                if (isRecord(parsed) && Array.isArray(parsed.content)) {
                                    parsed.content = mapper.restoreToolUse(parsed.content);
                                    const modifiedResponse = JSON.stringify(parsed);
                                    // Update content-length header
                                    const headers = { ...upstreamRes.headers };
                                    headers['content-length'] = String(Buffer.byteLength(modifiedResponse));
                                    clientRes.writeHead(upstreamRes.statusCode || 200, headers);
                                    clientRes.end(modifiedResponse);
                                    resolve();
                                    return;
                                }
                            }
                            catch {
                                // JSON parse failed, pass through unchanged
                            }
                        }
                        // Pass through unchanged
                        clientRes.writeHead(upstreamRes.statusCode || 200, upstreamRes.headers);
                        clientRes.end(responseBody);
                        resolve();
                    }
                    catch (err) {
                        reject(err);
                    }
                });
                upstreamRes.on('error', reject);
            });
            clearResponseTimeout = this.startResponseTimeout(upstreamReq);
            upstreamReq.on('timeout', () => upstreamReq.destroy(new Error('Upstream request timeout')));
            upstreamReq.on('error', (err) => {
                clearResponseTimeout();
                reject(err);
            });
            upstreamReq.write(bodyString);
            upstreamReq.end();
        });
    }
    /**
     * Forward JSON request and stream response with tool name restoration.
     * Handles SSE (Server-Sent Events) format.
     */
    forwardJsonStreaming(originalReq, clientRes, upstreamUrl, body, mapper) {
        return new Promise((resolve, reject) => {
            const bodyString = JSON.stringify(body);
            const requestFn = this.getRequestFn(upstreamUrl);
            let clearResponseTimeout = () => undefined;
            const upstreamReq = requestFn(this.buildRequestOptions(upstreamUrl, originalReq.method, this.buildForwardHeaders(originalReq.headers, bodyString)), (upstreamRes) => {
                clearResponseTimeout();
                clientRes.writeHead(upstreamRes.statusCode || 200, upstreamRes.headers);
                // Track upstream SSE lifecycle events (guards against empty proxy responses)
                const lifecycle = {
                    hasContent: false,
                    hasData: false,
                    hasMessageStart: false,
                    hasMessageDelta: false,
                    hasMessageStop: false,
                    /** Scan text for SSE lifecycle events and update tracking flags */
                    update(text) {
                        if (text.includes('"content_block_start"'))
                            this.hasContent = true;
                        if (text.includes('"message_start"'))
                            this.hasMessageStart = true;
                        if (text.includes('"message_delta"'))
                            this.hasMessageDelta = true;
                        if (text.includes('"message_stop"'))
                            this.hasMessageStop = true;
                    },
                };
                const isSuccessResponse = (upstreamRes.statusCode || 200) >= 200 && (upstreamRes.statusCode || 200) < 300;
                // If no changes were made, intercept to detect empty responses.
                // In the real failure case (issue #350), upstream sends message_start but
                // NOT message_delta/message_stop, so the synthetic response completes the
                // stream. If upstream DID send message_stop, Claude Code treats the second
                // synthetic block as additional content in the same conversation turn.
                if (!mapper.hasChanges()) {
                    upstreamRes.on('data', (chunk) => {
                        lifecycle.hasData = true;
                        lifecycle.update(chunk.toString('utf8'));
                        // Respect backpressure: pause upstream if client can't keep up
                        const canContinue = clientRes.write(chunk);
                        if (!canContinue) {
                            upstreamRes.pause();
                            clientRes.once('drain', () => upstreamRes.resume());
                        }
                    });
                    upstreamRes.on('end', () => {
                        try {
                            if (!lifecycle.hasContent && isSuccessResponse && lifecycle.hasData) {
                                this.writeLog('warn', '[tool-sanitization-proxy] Empty response detected from upstream (no content blocks). Injecting synthetic response to prevent client crash.');
                                clientRes.write(this.buildSyntheticErrorResponse(lifecycle.hasMessageStart, lifecycle.hasMessageDelta, lifecycle.hasMessageStop));
                            }
                            clientRes.end();
                        }
                        catch {
                            // Client may have disconnected — safe to ignore
                        }
                        resolve();
                    });
                    upstreamRes.on('error', reject);
                    return;
                }
                // Process SSE events for tool name restoration
                let buffer = '';
                upstreamRes.on('data', (chunk) => {
                    lifecycle.hasData = true;
                    buffer += chunk.toString('utf8');
                    // Process complete SSE events
                    const events = buffer.split('\n\n');
                    buffer = events.pop() || ''; // Keep incomplete event in buffer
                    for (const event of events) {
                        if (!event.trim())
                            continue;
                        lifecycle.update(event);
                        const processedEvent = this.processSSEEvent(event, mapper);
                        clientRes.write(processedEvent + '\n\n');
                    }
                });
                upstreamRes.on('end', () => {
                    try {
                        // Process any remaining buffer
                        if (buffer.trim()) {
                            lifecycle.update(buffer);
                            const processedEvent = this.processSSEEvent(buffer, mapper);
                            clientRes.write(processedEvent + '\n\n');
                        }
                        // Safety net: if upstream sent data but no content blocks, inject synthetic response
                        if (!lifecycle.hasContent && isSuccessResponse && lifecycle.hasData) {
                            this.writeLog('warn', '[tool-sanitization-proxy] Empty response detected from upstream (no content blocks). Injecting synthetic response to prevent client crash.');
                            clientRes.write(this.buildSyntheticErrorResponse(lifecycle.hasMessageStart, lifecycle.hasMessageDelta, lifecycle.hasMessageStop));
                        }
                        clientRes.end();
                    }
                    catch {
                        // Client may have disconnected — safe to ignore
                    }
                    resolve();
                });
                upstreamRes.on('error', reject);
            });
            clearResponseTimeout = this.startResponseTimeout(upstreamReq);
            upstreamReq.on('timeout', () => upstreamReq.destroy(new Error('Upstream request timeout')));
            upstreamReq.on('error', (err) => {
                clearResponseTimeout();
                reject(err);
            });
            upstreamReq.write(bodyString);
            upstreamReq.end();
        });
    }
    /**
     * Process a single SSE event, restoring tool names if present.
     */
    processSSEEvent(event, mapper) {
        // Parse SSE format: data: {...}
        const lines = event.split('\n');
        const processedLines = [];
        for (const line of lines) {
            if (line.startsWith('data: ')) {
                const jsonStr = line.slice(6); // Remove 'data: ' prefix
                // Skip [DONE] marker
                if (jsonStr.trim() === '[DONE]') {
                    processedLines.push(line);
                    continue;
                }
                try {
                    const data = JSON.parse(jsonStr);
                    // Handle content_block_start with tool_use
                    if (isRecord(data) &&
                        data.type === 'content_block_start' &&
                        isRecord(data.content_block) &&
                        data.content_block.type === 'tool_use' &&
                        typeof data.content_block.name === 'string') {
                        const originalName = mapper.restoreName(data.content_block.name);
                        data.content_block.name = originalName;
                        processedLines.push('data: ' + JSON.stringify(data));
                        continue;
                    }
                    // Handle message with content array (final message)
                    if (isRecord(data) && Array.isArray(data.content)) {
                        data.content = mapper.restoreToolUse(data.content);
                        processedLines.push('data: ' + JSON.stringify(data));
                        continue;
                    }
                    // Pass through unchanged
                    processedLines.push(line);
                }
                catch {
                    // Not valid JSON, pass through unchanged
                    processedLines.push(line);
                }
            }
            else {
                // Non-data lines (event:, id:, etc.) pass through
                processedLines.push(line);
            }
        }
        return processedLines.join('\n');
    }
    /**
     * Build a synthetic minimal SSE response when upstream returns empty content.
     * Prevents Claude Code from crashing with "No assistant message found".
     * Omits lifecycle events that upstream already sent to avoid protocol violations.
     */
    buildSyntheticErrorResponse(upstreamSentMessageStart = false, upstreamSentMessageDelta = false, upstreamSentMessageStop = false) {
        const events = [];
        // Only include message_start if upstream didn't already send one
        if (!upstreamSentMessageStart) {
            const msgId = `msg_synthetic_${Date.now()}`;
            events.push(`event: message_start\ndata: {"type":"message_start","message":{"id":"${msgId}","type":"message","role":"assistant","content":[],"model":"unknown","stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0}}}`);
        }
        events.push(`event: content_block_start\ndata: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}`, `event: content_block_delta\ndata: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"[Proxy Error] The upstream API returned an empty response. This typically occurs when the proxy drops unsigned thinking blocks during sub-agent execution. Please retry the request."}}`, `event: content_block_stop\ndata: {"type":"content_block_stop","index":0}`);
        if (!upstreamSentMessageDelta) {
            events.push(`event: message_delta\ndata: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"input_tokens":0,"output_tokens":0}}`);
        }
        if (!upstreamSentMessageStop) {
            events.push(`event: message_stop\ndata: {"type":"message_stop"}`);
        }
        return events.join('\n\n') + '\n\n';
    }
}
exports.ToolSanitizationProxy = ToolSanitizationProxy;
//# sourceMappingURL=tool-sanitization-proxy.js.map