/**
 * Claude Headless Provider
 *
 * Implements the HeadlessProvider interface using the @anthropic-ai/claude-agent-sdk.
 * Extracted from the original spawner.ts to enable provider abstraction.
 *
 * @module
 */
import { query as sdkQuery } from '@anthropic-ai/claude-agent-sdk';
// ============================================================================
// SDK Input Queue
// ============================================================================
/**
 * Message queue for SDK streaming input mode.
 * Allows pushing messages that will be sent to the SDK query.
 */
class SDKInputQueue {
    queue = [];
    waitingResolve = null;
    closed = false;
    sessionId = '';
    setSessionId(id) {
        this.sessionId = id;
    }
    push(content) {
        if (this.closed)
            return;
        const message = {
            type: 'user',
            message: {
                role: 'user',
                content: content,
            },
            parent_tool_use_id: null,
            session_id: this.sessionId,
        };
        if (this.waitingResolve) {
            this.waitingResolve({ value: message, done: false });
            this.waitingResolve = null;
        }
        else {
            this.queue.push(message);
        }
    }
    close() {
        this.closed = true;
        if (this.waitingResolve) {
            this.waitingResolve({ value: undefined, done: true });
            this.waitingResolve = null;
        }
    }
    [Symbol.asyncIterator]() {
        return {
            next: () => {
                if (this.queue.length > 0) {
                    return Promise.resolve({ value: this.queue.shift(), done: false });
                }
                if (this.closed) {
                    return Promise.resolve({ value: undefined, done: true });
                }
                return new Promise((resolve) => {
                    this.waitingResolve = resolve;
                });
            },
        };
    }
}
// ============================================================================
// Claude Headless Session
// ============================================================================
/**
 * A running Claude SDK headless session.
 */
class ClaudeHeadlessSession {
    inputQueue;
    sdkQuery;
    messageIterator;
    sessionId;
    closed = false;
    constructor(inputQueue, sdkQuery) {
        this.inputQueue = inputQueue;
        this.sdkQuery = sdkQuery;
        this.messageIterator = this.createMessageIterator();
    }
    sendMessage(content) {
        this.inputQueue.push(content);
    }
    [Symbol.asyncIterator]() {
        return this.messageIterator;
    }
    async interrupt() {
        await this.sdkQuery.interrupt();
    }
    close() {
        if (this.closed)
            return;
        this.closed = true;
        this.inputQueue.close();
    }
    getSessionId() {
        return this.sessionId;
    }
    async *createMessageIterator() {
        let resumeErrorDetected = false;
        try {
            for await (const message of this.sdkQuery) {
                if (this.closed)
                    break;
                const subtype = 'subtype' in message ? message.subtype : undefined;
                // Check for resume failure (session not found)
                if (message.type === 'result' && subtype === 'error_during_execution') {
                    const resultMsg = message;
                    const errors = resultMsg.errors || [];
                    const sessionNotFoundError = errors.find(e => e.includes('No conversation found with session ID'));
                    if (sessionNotFoundError) {
                        resumeErrorDetected = true;
                    }
                }
                // Extract session ID from system init message
                if (message.type === 'system' && subtype === 'init') {
                    this.sessionId = message.session_id;
                    if (this.sessionId) {
                        this.inputQueue.setSessionId(this.sessionId);
                    }
                }
                const agentMessages = this.convertSDKMessage(message);
                for (const agentMessage of agentMessages) {
                    yield agentMessage;
                }
            }
        }
        catch (error) {
            if (error instanceof Error && error.name === 'AbortError') {
                // Interrupt - just stop iterating
                return;
            }
            if (!resumeErrorDetected) {
                yield {
                    type: 'error',
                    content: error instanceof Error ? error.message : String(error),
                    raw: error,
                };
            }
        }
    }
    /**
     * Converts an SDK message into one or more AgentMessages.
     *
     * A single SDK assistant/user message may contain multiple content blocks
     * (text, tool_use, tool_result). We decompose these into separate
     * AgentMessages so downstream consumers see the same event types they
     * saw before the provider abstraction (text as assistant/user, tool blocks
     * as tool_use/tool_result). Messages with no meaningful content are dropped.
     */
    convertSDKMessage(message) {
        switch (message.type) {
            case 'system':
                return [{
                        type: 'system',
                        subtype: 'subtype' in message ? message.subtype : undefined,
                        sessionId: 'session_id' in message ? message.session_id : undefined,
                        raw: message,
                    }];
            case 'assistant':
                return this.decomposeContentBlocks('assistant', message);
            case 'user':
                return this.decomposeContentBlocks('user', message);
            case 'result':
                return [{
                        type: 'result',
                        subtype: 'subtype' in message ? message.subtype : undefined,
                        content: 'result' in message ? message.result : undefined,
                        raw: message,
                    }];
            default:
                return [];
        }
    }
    /**
     * Decomposes an SDK assistant or user message into individual AgentMessages.
     *
     * Content blocks are mapped as follows:
     * - text blocks → message of the original type (assistant/user) with text content
     * - tool_use blocks → tool_use message
     * - tool_result blocks → tool_result message
     *
     * If no content blocks produce output, the message is dropped entirely
     * (no empty assistant/user events are emitted).
     */
    decomposeContentBlocks(messageType, message) {
        const apiMessage = message.message;
        if (!apiMessage || typeof apiMessage !== 'object')
            return [];
        const msg = apiMessage;
        const results = [];
        // Handle simple string content
        if (typeof msg.content === 'string') {
            if (msg.content.length > 0) {
                results.push({ type: messageType, content: msg.content, raw: message });
            }
            return results;
        }
        // Handle array of content blocks
        if (!Array.isArray(msg.content))
            return [];
        const textParts = [];
        for (const block of msg.content) {
            if (typeof block !== 'object' || block === null || !('type' in block))
                continue;
            const typed = block;
            switch (typed.type) {
                case 'text':
                    if (typeof typed.text === 'string' && typed.text.length > 0) {
                        textParts.push(typed.text);
                    }
                    break;
                case 'tool_use':
                    results.push({
                        type: 'tool_use',
                        tool: {
                            name: typed.name,
                            id: typed.id,
                            input: typed.input,
                        },
                        raw: message,
                    });
                    break;
                case 'tool_result':
                    results.push({
                        type: 'tool_result',
                        content: typeof typed.content === 'string' ? typed.content : undefined,
                        tool: { id: typed.tool_use_id },
                        raw: message,
                    });
                    break;
            }
        }
        // Emit text as the original message type (assistant/user)
        if (textParts.length > 0) {
            // Insert text message first, before tool messages
            results.unshift({
                type: messageType,
                content: textParts.join(''),
                raw: message,
            });
        }
        return results;
    }
}
// ============================================================================
// Claude Headless Provider
// ============================================================================
/**
 * Claude headless provider using @anthropic-ai/claude-agent-sdk.
 */
export class ClaudeHeadlessProvider {
    name = 'claude-headless';
    async spawn(options) {
        const initialPrompt = options.initialPrompt ?? 'You are an AI agent. Await further instructions.';
        // Build environment
        const env = {
            ...process.env,
            ...options.environmentVariables,
        };
        if (options.stoneforgeRoot) {
            env.STONEFORGE_ROOT = options.stoneforgeRoot;
        }
        // Create input queue for streaming input mode
        const inputQueue = new SDKInputQueue();
        // Build SDK options
        const sdkOptions = {
            cwd: options.workingDirectory,
            env,
            permissionMode: 'bypassPermissions',
            allowDangerouslySkipPermissions: true,
        };
        // Pass model if specified
        if (options.model) {
            sdkOptions.model = options.model;
        }
        // Resume if we have a session ID
        if (options.resumeSessionId) {
            sdkOptions.resume = options.resumeSessionId;
            inputQueue.setSessionId(options.resumeSessionId);
        }
        // Create the SDK query
        const queryResult = sdkQuery({
            prompt: inputQueue,
            options: sdkOptions,
        });
        const session = new ClaudeHeadlessSession(inputQueue, queryResult);
        // Push the initial prompt to start the conversation
        inputQueue.push(initialPrompt);
        return session;
    }
    async isAvailable() {
        try {
            // Check if the SDK can be imported
            await import('@anthropic-ai/claude-agent-sdk');
            return true;
        }
        catch {
            return false;
        }
    }
}
//# sourceMappingURL=headless.js.map