"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.McpStdioClient = void 0;
const buffer_1 = require("buffer");
const DEFAULT_TIMEOUT_MS = 15_000;
class McpStdioClient {
    child;
    timeoutMs;
    buffer = '';
    nextId = 1;
    initialized;
    pending = new Map();
    constructor(child, timeoutMs = DEFAULT_TIMEOUT_MS) {
        this.child = child;
        this.timeoutMs = timeoutMs;
        child.stdout.on('data', (chunk) => this.handleData(String(chunk)));
        child.on('exit', () => this.rejectAll(new Error('MCP server process exited.')));
        child.on('error', (error) => this.rejectAll(error));
    }
    async initialize() {
        if (!this.initialized) {
            this.initialized = this.request('initialize', {
                protocolVersion: '2024-11-05',
                capabilities: {},
                clientInfo: { name: 'ollama-agent-harness', version: '0.3.25' },
            }).then(async () => {
                await this.notify('notifications/initialized', {});
            });
        }
        await this.initialized;
    }
    async listTools() {
        await this.initialize();
        const result = await this.request('tools/list', {});
        const tools = result && typeof result === 'object' && Array.isArray(result.tools)
            ? result.tools
            : [];
        return tools.map(sanitizeProtocolTool).filter((tool) => Boolean(tool));
    }
    async callTool(name, args) {
        await this.initialize();
        const result = await this.request('tools/call', { name, arguments: args });
        return result && typeof result === 'object' ? result : { content: [{ type: 'text', text: String(result ?? '') }] };
    }
    request(method, params) {
        const id = this.nextId++;
        const message = { jsonrpc: '2.0', id, method, params };
        const payload = JSON.stringify(message);
        const timer = setTimeout(() => {
            const pending = this.pending.get(id);
            if (!pending)
                return;
            this.pending.delete(id);
            pending.reject(new Error(`MCP request timed out: ${method}`));
        }, this.timeoutMs);
        const promise = new Promise((resolve, reject) => {
            this.pending.set(id, { resolve, reject, timer });
        });
        this.child.stdin.write(formatJsonRpcPayload(payload));
        return promise;
    }
    async notify(method, params) {
        const payload = JSON.stringify({ jsonrpc: '2.0', method, params });
        this.child.stdin.write(formatJsonRpcPayload(payload));
    }
    handleData(chunk) {
        this.buffer += chunk;
        for (;;) {
            const framed = readFramedMessage(this.buffer);
            if (framed) {
                this.buffer = framed.rest;
                this.handleMessageText(framed.message);
                continue;
            }
            const newlineIndex = this.buffer.indexOf('\n');
            if (newlineIndex < 0)
                return;
            const line = this.buffer.slice(0, newlineIndex).trim();
            if (!line.startsWith('{'))
                return;
            this.buffer = this.buffer.slice(newlineIndex + 1);
            this.handleMessageText(line);
        }
    }
    handleMessageText(text) {
        let message;
        try {
            message = JSON.parse(text);
        }
        catch {
            return;
        }
        if (message.id === undefined)
            return;
        const pending = this.pending.get(message.id);
        if (!pending)
            return;
        this.pending.delete(message.id);
        clearTimeout(pending.timer);
        if (message.error) {
            pending.reject(new Error(message.error.message || `MCP error ${message.error.code ?? ''}`.trim()));
            return;
        }
        pending.resolve(message.result);
    }
    rejectAll(error) {
        for (const [id, pending] of this.pending) {
            clearTimeout(pending.timer);
            pending.reject(error);
            this.pending.delete(id);
        }
    }
}
exports.McpStdioClient = McpStdioClient;
function formatJsonRpcPayload(payload) {
    return `Content-Length: ${buffer_1.Buffer.byteLength(payload, 'utf-8')}\r\n\r\n${payload}`;
}
function readFramedMessage(buffer) {
    const separator = buffer.indexOf('\r\n\r\n');
    const separatorLength = separator >= 0 ? 4 : 0;
    const headerEnd = separator >= 0 ? separator : buffer.indexOf('\n\n');
    const headerSeparatorLength = separator >= 0 ? separatorLength : 2;
    if (headerEnd < 0)
        return null;
    const header = buffer.slice(0, headerEnd);
    const match = header.match(/Content-Length:\s*(\d+)/i);
    if (!match)
        return null;
    const length = Number(match[1]);
    const bodyStart = headerEnd + headerSeparatorLength;
    if (buffer.length < bodyStart + length)
        return null;
    return { message: buffer.slice(bodyStart, bodyStart + length), rest: buffer.slice(bodyStart + length) };
}
function sanitizeProtocolTool(value) {
    if (!value || typeof value !== 'object')
        return null;
    const raw = value;
    const name = typeof raw.name === 'string' ? raw.name.trim() : '';
    if (!name)
        return null;
    return {
        name,
        description: typeof raw.description === 'string' ? raw.description.trim() || undefined : undefined,
        inputSchema: raw.inputSchema && typeof raw.inputSchema === 'object' ? raw.inputSchema : undefined,
    };
}
//# sourceMappingURL=mcpClient.js.map