/**
 * Session runner — orchestrates Agent SDK query() calls for plan execution.
 *
 * Takes a parsed plan, builds the executor prompt, configures query() options,
 * processes the message stream, and extracts results into a typed PlanResult.
 */
import { query } from '@anthropic-ai/claude-agent-sdk';
import { GSDEventType, PhaseType } from './types.js';
import { buildExecutorPrompt, parseAgentTools, DEFAULT_ALLOWED_TOOLS } from './prompt-builder.js';
import { getToolsForPhase } from './tool-scoping.js';
import { detectRuntime } from './query/helpers.js';
import { resolveRuntimeTierDefault } from './model-catalog.js';
// ─── Model resolution ────────────────────────────────────────────────────────
/**
 * Resolve model identifier from options or config profile.
 *
 * Priority: explicit model option > config model_profile > default.
 *
 * Runtime-aware (#2832): the profile -> Claude-id map only applies when the
 * project is targeting the Claude runtime. For Codex, Gemini, OpenCode, etc.,
 * forcing a Claude model id (e.g. 'claude-sonnet-4-6') silently routes the
 * autonomous run through the Claude path, which is wrong for those runtimes.
 * In those cases — and whenever `resolve_model_ids: "omit"` is set — leave
 * `model` unset so the runtime falls back to its configured default.
 */
function resolveModel(options, config) {
    if (options?.model)
        return options.model;
    // Honor the explicit "don't resolve model ids" config knob (#2652, #2832).
    // Mirrors `query/config-query.ts` resolve_model_ids === 'omit' branch.
    if (config?.resolve_model_ids === 'omit') {
        return undefined;
    }
    // Profile -> Claude id map. Applies only on the Claude runtime.
    // Use `detectRuntime` so `GSD_RUNTIME` env precedence is honored — a Codex
    // run with a Claude-shaped config must NOT be silently routed to Claude.
    const runtime = detectRuntime({
        runtime: config?.runtime,
    });
    if (runtime !== 'claude') {
        // Non-Claude runtimes: never inject a Claude id from the profile map.
        return undefined;
    }
    if (config?.model_profile) {
        const profile = String(config.model_profile).toLowerCase();
        if (profile === 'inherit')
            return undefined;
        const tier = profile === 'quality' ? 'opus'
            : (profile === 'budget' || profile === 'speed') ? 'haiku'
                : (profile === 'balanced' || profile === 'adaptive') ? 'sonnet'
                    : null;
        if (!tier)
            return config.model_profile;
        return resolveRuntimeTierDefault('claude', tier)?.model;
    }
    return undefined; // Let SDK use its default
}
// ─── Session runner ──────────────────────────────────────────────────────────
/**
 * Run a plan execution session via the Agent SDK query() function.
 *
 * Builds the executor prompt from the parsed plan, configures query() with
 * appropriate permissions, tool restrictions, and budget limits, then iterates
 * the message stream to extract the result.
 *
 * @param plan - Parsed plan structure
 * @param config - GSD project configuration
 * @param options - Session overrides (maxTurns, budget, model, etc.)
 * @param agentDef - Raw agent definition content (optional, for tool/role extraction)
 * @returns Typed PlanResult with cost, duration, success/error status
 */
export async function runPlanSession(plan, config, options, agentDef, eventStream, streamContext, phaseDir) {
    // Build the executor prompt
    const executorPrompt = buildExecutorPrompt(plan, { agentDef, phaseDir });
    // Resolve allowed tools — from agent definition or defaults
    const allowedTools = options?.allowedTools ??
        (agentDef ? parseAgentTools(agentDef) : DEFAULT_ALLOWED_TOOLS);
    // Resolve model
    const model = resolveModel(options, config);
    // Configure query options
    const maxTurns = options?.maxTurns ?? 50;
    const maxBudgetUsd = options?.maxBudgetUsd ?? 5.0;
    const cwd = options?.cwd ?? process.cwd();
    const queryStream = query({
        prompt: `Execute this plan:\n\n${plan.objective || 'Execute the plan tasks below.'}`,
        options: {
            systemPrompt: {
                type: 'preset',
                preset: 'claude_code',
                append: executorPrompt,
            },
            settingSources: ['project'],
            allowedTools,
            permissionMode: 'bypassPermissions',
            allowDangerouslySkipPermissions: true,
            maxTurns,
            maxBudgetUsd,
            cwd,
            ...(model ? { model } : {}),
        },
    });
    return processQueryStream(queryStream, eventStream, streamContext);
}
// ─── Result extraction ───────────────────────────────────────────────────────
function isResultMessage(msg) {
    return msg.type === 'result';
}
function isSuccessResult(msg) {
    return msg.subtype === 'success';
}
function isErrorResult(msg) {
    return msg.subtype !== 'success';
}
function emptyUsage() {
    return {
        inputTokens: 0,
        outputTokens: 0,
        cacheReadInputTokens: 0,
        cacheCreationInputTokens: 0,
    };
}
function extractUsage(msg) {
    const u = msg.usage;
    return {
        inputTokens: u.input_tokens ?? 0,
        outputTokens: u.output_tokens ?? 0,
        cacheReadInputTokens: u.cache_read_input_tokens ?? 0,
        cacheCreationInputTokens: u.cache_creation_input_tokens ?? 0,
    };
}
function extractResult(msg) {
    const base = {
        sessionId: msg.session_id,
        totalCostUsd: msg.total_cost_usd,
        durationMs: msg.duration_ms,
        usage: extractUsage(msg),
        numTurns: msg.num_turns,
    };
    if (isSuccessResult(msg)) {
        return {
            ...base,
            success: true,
        };
    }
    // Error result
    const errorMsg = msg;
    return {
        ...base,
        success: false,
        error: {
            subtype: errorMsg.subtype,
            messages: errorMsg.errors ?? [],
        },
    };
}
// ─── Shared stream processing ────────────────────────────────────────────────
/**
 * Process a query() message stream, emit events, and extract the result.
 * Shared between runPlanSession and runPhaseStepSession to avoid duplication.
 */
async function processQueryStream(queryStream, eventStream, streamContext) {
    let resultMessage;
    try {
        for await (const message of queryStream) {
            if (eventStream) {
                eventStream.mapAndEmit(message, streamContext ?? {});
            }
            if (isResultMessage(message)) {
                resultMessage = message;
            }
        }
    }
    catch (err) {
        return {
            success: false,
            sessionId: '',
            totalCostUsd: 0,
            durationMs: 0,
            usage: emptyUsage(),
            numTurns: 0,
            error: {
                subtype: 'error_during_execution',
                messages: [err instanceof Error ? err.message : String(err)],
            },
        };
    }
    if (!resultMessage) {
        return {
            success: false,
            sessionId: '',
            totalCostUsd: 0,
            durationMs: 0,
            usage: emptyUsage(),
            numTurns: 0,
            error: {
                subtype: 'error_during_execution',
                messages: ['No result message received from query stream'],
            },
        };
    }
    const result = extractResult(resultMessage);
    if (eventStream) {
        const cost = eventStream.getCost();
        eventStream.emitEvent({
            type: GSDEventType.CostUpdate,
            timestamp: new Date().toISOString(),
            sessionId: resultMessage.session_id,
            phase: streamContext?.phase,
            planName: streamContext?.planName,
            sessionCostUsd: result.totalCostUsd,
            cumulativeCostUsd: cost.cumulative,
        });
    }
    return result;
}
// ─── Phase step session runner ───────────────────────────────────────────────
/**
 * Map PhaseStepType to PhaseType for tool scoping.
 * PhaseStepType includes 'advance' which has no session-level equivalent.
 */
function stepTypeToPhaseType(step) {
    const mapping = {
        discuss: PhaseType.Discuss,
        research: PhaseType.Research,
        plan: PhaseType.Plan,
        plan_check: PhaseType.Verify,
        execute: PhaseType.Execute,
        verify: PhaseType.Verify,
    };
    return mapping[step] ?? PhaseType.Execute;
}
/**
 * Run a phase step session via the Agent SDK query() function.
 *
 * Unlike runPlanSession which takes a ParsedPlan, this accepts a raw prompt
 * string and a phase step type. The prompt becomes the system prompt append,
 * and tools are scoped by phase type.
 *
 * @param prompt - Raw prompt string to append to the system prompt
 * @param phaseStep - Phase step type (determines tool scoping)
 * @param config - GSD project configuration
 * @param options - Session overrides (maxTurns, budget, model, etc.)
 * @param eventStream - Optional event stream for observability
 * @param streamContext - Optional context for event tagging
 * @returns Typed PlanResult with cost, duration, success/error status
 */
export async function runPhaseStepSession(prompt, phaseStep, config, options, eventStream, streamContext) {
    const phaseType = stepTypeToPhaseType(phaseStep);
    const allowedTools = options?.allowedTools ?? getToolsForPhase(phaseType);
    const model = resolveModel(options, config);
    const maxTurns = options?.maxTurns ?? 50;
    const maxBudgetUsd = options?.maxBudgetUsd ?? 5.0;
    const cwd = options?.cwd ?? process.cwd();
    const queryStream = query({
        prompt: `Execute this phase step: ${phaseStep}`,
        options: {
            systemPrompt: {
                type: 'preset',
                preset: 'claude_code',
                append: prompt,
            },
            settingSources: ['project'],
            allowedTools,
            permissionMode: 'bypassPermissions',
            allowDangerouslySkipPermissions: true,
            maxTurns,
            maxBudgetUsd,
            cwd,
            ...(model ? { model } : {}),
        },
    });
    return processQueryStream(queryStream, eventStream, streamContext);
}
//# sourceMappingURL=session-runner.js.map