/**
 * @license
 * Copyright 2025 Vybestack LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import { randomUUID } from 'node:crypto';
import { SubAgentScope, } from './subagent.js';
import fs from 'node:fs';
import path from 'node:path';
import { homedir } from 'node:os';
import { createAgentRuntimeState, } from '../runtime/AgentRuntimeState.js';
import { createProviderRuntimeContext, } from '../runtime/providerRuntimeContext.js';
import { loadAgentRuntime, } from '../runtime/AgentRuntimeLoader.js';
import { SettingsService } from '../settings/SettingsService.js';
import { AuthType } from './contentGenerator.js';
import { getEnvironmentContext } from '../utils/environmentContext.js';
const createAbortError = (message) => {
    const error = new Error(message);
    error.name = 'AbortError';
    return error;
};
/**
 * Light-weight orchestrator responsible for resolving subagent configuration,
 * building isolated runtime bundles, and launching {@link SubAgentScope} instances.
 *
 * @plan PLAN-20251029-SUBAGENTORCHESTRATION
 * @requirement REQ-SUBAGENT-ORCH-001, REQ-SUBAGENT-ORCH-002
 */
export class SubagentOrchestrator {
    options;
    runtimeLoader;
    scopeFactory;
    idFactory;
    constructor(options) {
        this.options = options;
        this.runtimeLoader = options.runtimeLoader ?? loadAgentRuntime;
        this.scopeFactory =
            options.scopeFactory ??
                SubAgentScope.create.bind(SubAgentScope);
        this.idFactory = options.idFactory ?? randomUUID;
    }
    /**
     * Launches a subagent by name, returning the created {@link SubAgentScope}
     * and associated agent metadata.
     */
    async launch(request, signal) {
        this.throwIfAborted(signal, 'Subagent launch aborted before start.');
        const subagent = await this.loadSubagentConfig(request.name);
        this.throwIfAborted(signal, 'Subagent launch aborted while loading config.');
        const profile = await this.options.profileManager.loadProfile(subagent.profile);
        this.throwIfAborted(signal, 'Subagent launch aborted while loading profile.');
        const promptConfig = this.buildPromptConfig(subagent.systemPrompt, request.behaviourPrompts);
        const modelConfig = this.buildModelConfig(profile);
        const runConfig = this.buildRunConfig(profile, request.runConfig);
        this.throwIfAborted(signal, 'Subagent launch aborted before runtime assembly.');
        const agentRuntimeId = this.createRuntimeId(subagent.name);
        const runtimeResult = await this.createRuntimeBundle({
            subagent,
            profile,
            modelConfig,
            agentRuntimeId,
        }, signal);
        this.throwIfAborted(signal, 'Subagent launch aborted after runtime assembly completed.');
        const scope = await this.scopeFactory(subagent.name, this.options.foregroundConfig, promptConfig, modelConfig, runConfig, request.toolConfig, request.outputConfig, {
            runtimeBundle: runtimeResult,
            environmentContextLoader: async (_runtime) => getEnvironmentContext(this.options.foregroundConfig),
        }, signal);
        this.throwIfAborted(signal, 'Subagent launch aborted before completion.');
        const agentId = typeof scope.getAgentId === 'function'
            ? scope.getAgentId()
            : `${subagent.name}-${agentRuntimeId}`;
        return {
            agentId,
            scope,
            prompt: promptConfig,
            profile,
            config: subagent,
            runtime: runtimeResult,
            dispose: async () => {
                // 1. Dispose the scope first to clean up abort controllers and event listeners
                if (typeof scope.dispose === 'function') {
                    scope.dispose();
                }
                // 2. Then clean up the history service
                const history = runtimeResult.history ?? scope.runtimeContext.history;
                if (history) {
                    const disposable = history.dispose;
                    if (typeof disposable === 'function') {
                        disposable();
                    }
                    else if (typeof history.clear === 'function') {
                        history.clear();
                        history.removeAllListeners?.();
                    }
                }
            },
        };
    }
    throwIfAborted(signal, message) {
        if (signal?.aborted) {
            throw createAbortError(message);
        }
    }
    async loadSubagentConfig(name) {
        if (!name?.trim()) {
            throw new Error('Subagent name is required.');
        }
        try {
            return await this.options.subagentManager.loadSubagent(name);
        }
        catch (error) {
            if (error instanceof Error) {
                // Check if this is a "subagent not found" error
                if (error.message.includes(`'${name}' not found`)) {
                    throw new Error(`Unable to load subagent '${name}': Subagent not found. Use the list_subagents tool to discover available subagents before calling the task tool.`);
                }
                throw new Error(`Unable to load subagent '${name}': ${error.message}`);
            }
            throw error;
        }
    }
    buildPromptConfig(basePrompt, additions) {
        const trimmedBase = basePrompt?.trim();
        const trimmedAdditions = (additions ?? [])
            .map((part) => part?.trim())
            .filter((part) => part && part.length > 0);
        const promptSections = [];
        if (trimmedBase) {
            promptSections.push(trimmedBase);
        }
        if (trimmedAdditions.length > 0) {
            const numberedInstructions = trimmedAdditions
                .map((instruction, index) => `(${index + 1}) ${instruction}`)
                .join('\n');
            promptSections.push([
                '--- CURRENT TASK DIRECTIVES ---',
                'Follow these instructions precisely for this run. They take precedence over any default behaviours.',
                numberedInstructions,
            ].join('\n'));
        }
        const merged = promptSections.join('\n\n');
        return {
            systemPrompt: merged,
        };
    }
    buildModelConfig(profile) {
        return {
            model: profile.model,
            temp: profile.modelParams.temperature ?? 0.7,
            top_p: profile.modelParams.top_p ?? 1,
        };
    }
    buildRunConfig(profile, custom) {
        const profileMaxTime = this.getNumberSetting(profile.ephemeralSettings, [
            'subagent.max_time_minutes',
            'max_time_minutes',
        ]);
        const runConfig = {
            max_time_minutes: custom?.max_time_minutes ??
                (profileMaxTime !== undefined
                    ? profileMaxTime
                    : Number.POSITIVE_INFINITY),
        };
        const profileMaxTurns = this.getNumberSetting(profile.ephemeralSettings, [
            'maxTurnsPerPrompt',
        ]);
        const maxTurns = custom?.max_turns !== undefined
            ? custom.max_turns
            : (profileMaxTurns ?? 200);
        if (maxTurns !== undefined && maxTurns > 0) {
            runConfig.max_turns = Math.floor(maxTurns);
        }
        return runConfig;
    }
    baseSessionId() {
        const { foregroundConfig } = this.options;
        if (typeof foregroundConfig.getSessionId === 'function') {
            const session = foregroundConfig.getSessionId();
            if (session) {
                return String(session);
            }
        }
        return 'llxprt-session';
    }
    createRuntimeId(subagentName) {
        const suffix = this.idFactory().slice(0, 8);
        return `${this.baseSessionId()}#${subagentName}#${suffix}`;
    }
    buildContentGeneratorConfig(profile, modelConfig) {
        const authKey = this.getStringSetting(profile.ephemeralSettings, [
            'auth-key',
        ]);
        const proxy = this.getStringSetting(profile.ephemeralSettings, [
            'proxy',
            'proxy-url',
        ]);
        let authType;
        if (authKey) {
            authType = profile.provider.includes('gemini')
                ? AuthType.USE_GEMINI
                : AuthType.API_KEY;
        }
        else {
            authType = AuthType.USE_PROVIDER;
        }
        return {
            model: modelConfig.model,
            authType,
            apiKey: authKey,
            proxy,
        };
    }
    createSettingsSnapshot(profile) {
        const allowed = this.getStringArraySetting(profile.ephemeralSettings, [
            'tools.allowed',
            'tools_allowed',
        ]);
        const disabled = this.getStringArraySetting(profile.ephemeralSettings, [
            'tools.disabled',
            'disabled-tools',
        ]);
        return {
            compressionThreshold: this.getNumberSetting(profile.ephemeralSettings, [
                'compression-threshold',
            ]),
            contextLimit: this.getNumberSetting(profile.ephemeralSettings, [
                'context-limit',
            ]),
            preserveThreshold: this.getNumberSetting(profile.ephemeralSettings, [
                'compression-preserve-threshold',
            ]),
            toolFormatOverride: this.getStringSetting(profile.ephemeralSettings, [
                'tool-format',
            ]),
            tools: {
                allowed,
                disabled,
            },
        };
    }
    populateSettingsService(service, profile) {
        const provider = profile.provider;
        service.set('activeProvider', provider);
        service.set(`providers.${provider}.model`, profile.model);
        const temperature = profile.modelParams.temperature;
        if (typeof temperature === 'number') {
            service.set(`providers.${provider}.temperature`, temperature);
        }
        const maxTokens = profile.modelParams.max_tokens;
        if (typeof maxTokens === 'number') {
            service.set(`providers.${provider}.maxTokens`, maxTokens);
        }
        const baseUrl = this.getStringSetting(profile.ephemeralSettings, [
            'base-url',
        ]);
        if (baseUrl) {
            service.set(`providers.${provider}.baseUrl`, baseUrl);
            service.set(`providers.${provider}.baseURL`, baseUrl);
        }
        else {
            service.set(`providers.${provider}.baseUrl`, undefined);
            service.set(`providers.${provider}.baseURL`, undefined);
        }
        const authKey = this.getStringSetting(profile.ephemeralSettings, [
            'auth-key',
        ]);
        if (authKey) {
            service.set('auth-key', authKey);
            service.set(`providers.${provider}.apiKey`, authKey);
        }
        const authKeyfile = this.getStringSetting(profile.ephemeralSettings, [
            'auth-keyfile',
        ]);
        if (authKeyfile) {
            const expandedKeyfile = authKeyfile.replace(/^~(?=$|[\\/])/, homedir());
            service.set('auth-keyfile', expandedKeyfile);
            service.set(`providers.${provider}.apiKeyfile`, expandedKeyfile);
            if (!service.get(`providers.${provider}.apiKey`)) {
                try {
                    const resolvedPath = path.resolve(expandedKeyfile);
                    if (fs.existsSync(resolvedPath)) {
                        const content = fs.readFileSync(resolvedPath, 'utf8').trim();
                        if (content) {
                            service.set(`providers.${provider}.apiKey`, content);
                        }
                    }
                }
                catch (error) {
                    console.warn(`SubagentOrchestrator: unable to read auth key file '${authKeyfile}': ${error instanceof Error ? error.message : String(error)}`);
                }
            }
        }
        const contextLimit = this.getNumberSetting(profile.ephemeralSettings, [
            'context-limit',
        ]);
        if (contextLimit !== undefined) {
            service.set('context-limit', contextLimit);
        }
        const compressionThreshold = this.getNumberSetting(profile.ephemeralSettings, ['compression-threshold']);
        if (compressionThreshold !== undefined) {
            service.set('compression-threshold', compressionThreshold);
        }
        const preserveThreshold = this.getNumberSetting(profile.ephemeralSettings, [
            'compression-preserve-threshold',
        ]);
        if (preserveThreshold !== undefined) {
            service.set('compression-preserve-threshold', preserveThreshold);
        }
        const toolFormat = this.getStringSetting(profile.ephemeralSettings, [
            'tool-format',
        ]);
        if (toolFormat) {
            service.set('tool-format-override', toolFormat);
        }
        const allowed = this.getStringArraySetting(profile.ephemeralSettings, [
            'tools.allowed',
            'tools_allowed',
        ]);
        if (allowed) {
            service.set('tools.allowed', allowed);
        }
        const disabled = this.getStringArraySetting(profile.ephemeralSettings, [
            'tools.disabled',
            'disabled-tools',
        ]);
        if (disabled) {
            service.set('tools.disabled', disabled);
        }
    }
    getNumberSetting(settings, keys) {
        for (const key of keys) {
            const value = this.getSetting(settings, key);
            if (typeof value === 'number' && Number.isFinite(value)) {
                return value;
            }
            if (typeof value === 'string' && value.trim().length > 0) {
                const parsed = Number(value);
                if (Number.isFinite(parsed)) {
                    return parsed;
                }
            }
        }
        return undefined;
    }
    getStringSetting(settings, keys) {
        for (const key of keys) {
            const value = this.getSetting(settings, key);
            if (typeof value === 'string' && value.trim().length > 0) {
                return value;
            }
        }
        return undefined;
    }
    getStringArraySetting(settings, keys) {
        for (const key of keys) {
            const value = this.getSetting(settings, key);
            if (Array.isArray(value)) {
                return value.map(String);
            }
        }
        return undefined;
    }
    getSetting(settings, key) {
        return settings[key];
    }
    createRuntimeState(profile, modelConfig, agentRuntimeId) {
        const authKey = this.getStringSetting(profile.ephemeralSettings, [
            'auth-key',
        ]);
        const authType = authKey
            ? profile.provider.includes('gemini')
                ? AuthType.USE_GEMINI
                : AuthType.API_KEY
            : AuthType.USE_PROVIDER;
        const sessionId = `${this.baseSessionId()}::${agentRuntimeId}`;
        const baseUrl = this.getStringSetting(profile.ephemeralSettings, [
            'base-url',
        ]);
        return createAgentRuntimeState({
            runtimeId: agentRuntimeId,
            provider: profile.provider,
            model: modelConfig.model,
            authType,
            authPayload: authKey ? { apiKey: authKey } : undefined,
            baseUrl,
            proxyUrl: this.getStringSetting(profile.ephemeralSettings, [
                'proxy',
                'proxy-url',
            ]),
            modelParams: {
                temperature: modelConfig.temp,
                topP: modelConfig.top_p,
                maxTokens: profile.modelParams.max_tokens ?? undefined,
            },
            sessionId,
        });
    }
    async createRuntimeBundle(params, signal) {
        const { profile, modelConfig, agentRuntimeId, subagent } = params;
        this.throwIfAborted(signal, 'Subagent launch aborted before runtime state.');
        const runtimeState = this.createRuntimeState(profile, modelConfig, agentRuntimeId);
        const settingsService = new SettingsService();
        this.populateSettingsService(settingsService, profile);
        const providerRuntime = createProviderRuntimeContext({
            settingsService,
            config: this.options.foregroundConfig,
            runtimeId: agentRuntimeId,
            metadata: {
                source: 'SubagentOrchestrator',
                subagent: subagent.name,
            },
        });
        const settingsSnapshot = this.createSettingsSnapshot(profile);
        const contentGeneratorConfig = this.buildContentGeneratorConfig(profile, modelConfig);
        const providerManager = typeof this.options.foregroundConfig.getProviderManager === 'function'
            ? this.options.foregroundConfig.getProviderManager()
            : undefined;
        if (providerManager) {
            contentGeneratorConfig.providerManager = providerManager;
        }
        const toolRegistry = typeof this.options.foregroundConfig.getToolRegistry === 'function'
            ? this.options.foregroundConfig.getToolRegistry()
            : undefined;
        const loaderOptions = {
            profile: {
                config: this.options.foregroundConfig,
                state: runtimeState,
                settings: settingsSnapshot,
                providerRuntime,
                contentGeneratorConfig,
                toolRegistry,
                providerManager,
            },
            signal,
        };
        return this.runtimeLoader(loaderOptions);
    }
}
//# sourceMappingURL=subagentOrchestrator.js.map