/**
 * LLM calls for workflow steps via Claude Code subprocess.
 *
 * Spawns `claude --print` as a child process with the resolved prompt.
 * Claude Code handles its own OAuth authentication internally — no
 * credential reading, token refresh, or API endpoint logic needed here.
 * This is the same mechanism the admin chat uses (claude-agent.ts).
 *
 *   VALIDATE PROMPT
 *     │
 *     ├─ empty → throw (no silent fallback)
 *     │
 *     v
 *   SPAWN claude --print --model <model> --max-turns 1
 *     │
 *     ├─ ENOENT (not in PATH) → throw
 *     ├─ non-zero exit → throw with stderr
 *     ├─ timeout → SIGTERM → throw
 *     ├─ empty stdout → throw
 *     └─ stdout text → return
 *
 * probeLlmHealth() shares the same spawn path with a trivial "pong"
 * prompt and a shorter timeout. Used as the pre-flight check before
 * executing workflow LLM steps (Task 74 — graceful degradation).
 */
import { spawn as realSpawn } from "node:child_process";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
// ─────────────────────────────────────────────────────────────
// Account config reading
// ─────────────────────────────────────────────────────────────
/**
 * Read the account's adminModel from account.json.
 * Returns the model ID string, or null if the config can't be read.
 */
export function readAdminModel(platformRoot, accountId) {
    // Validate accountId — block path traversal
    if (accountId.includes("..") || accountId.includes("/") || accountId.includes("\\")) {
        throw new Error(`Invalid accountId: "${accountId}" — path traversal blocked`);
    }
    const configPath = resolve(platformRoot, "..", "data", "accounts", accountId, "account.json");
    try {
        const raw = readFileSync(configPath, "utf-8");
        const config = JSON.parse(raw);
        const adminModel = config.adminModel;
        if (typeof adminModel === "string" && adminModel) {
            return adminModel;
        }
        return null;
    }
    catch {
        return null;
    }
}
// ─────────────────────────────────────────────────────────────
// LLM call via Claude Code subprocess
// ─────────────────────────────────────────────────────────────
/** Max stderr to capture — enough for diagnostics, bounded to prevent OOM. */
const MAX_STDERR_BYTES = 4096;
/**
 * Execute a one-shot LLM call by spawning Claude Code.
 *
 * Returns the model's text response. The caller is responsible for
 * parsing the response (JSON or raw text) via parseStepOutput().
 */
export async function executeLlmCall(params) {
    const { prompt, model, timeoutMs } = params;
    const spawn = params._spawn ?? realSpawn;
    if (!prompt.trim()) {
        throw new Error("LLM step has an empty prompt — nothing to send to the model.");
    }
    console.error(`[llm-call] spawning Claude Code: model=${model}`);
    const args = [
        "--print",
        "--model", model,
        "--max-turns", "1",
        "--permission-mode", "dontAsk",
        prompt,
    ];
    return new Promise((promiseResolve, reject) => {
        const proc = spawn("claude", args, {
            stdio: ["ignore", "pipe", "pipe"],
        });
        let stdout = "";
        let stderr = "";
        proc.stdout.on("data", (chunk) => {
            stdout += chunk.toString("utf-8");
        });
        proc.stderr.on("data", (chunk) => {
            if (stderr.length < MAX_STDERR_BYTES) {
                stderr += chunk.toString("utf-8").slice(0, MAX_STDERR_BYTES - stderr.length);
            }
        });
        const timer = setTimeout(() => {
            proc.kill("SIGTERM");
            reject(new Error(`LLM call timed out after ${timeoutMs}ms`));
        }, timeoutMs);
        proc.on("close", (code) => {
            clearTimeout(timer);
            if (code !== 0) {
                reject(new Error(`Claude Code exited with code ${code}${stderr ? `\nstderr: ${stderr.trim()}` : ""}`));
                return;
            }
            if (!stdout.trim()) {
                reject(new Error("Claude Code returned an empty response."));
                return;
            }
            console.error(`[llm-call] response: ${stdout.length} chars`);
            promiseResolve(stdout);
        });
        proc.on("error", (err) => {
            clearTimeout(timer);
            reject(new Error(`Failed to spawn Claude Code: ${err.message}`));
        });
    });
}
// ─────────────────────────────────────────────────────────────
// Health probe — pre-flight check before running LLM workflow steps
// ─────────────────────────────────────────────────────────────
const DEFAULT_PROBE_TIMEOUT_MS = 10_000;
/**
 * Lightweight pre-flight probe for LLM availability.
 *
 * Spawns `claude --print` with a trivial prompt ("pong") and a short
 * timeout. Any failure mode that would cause a real LLM step to fail
 * (missing binary, dead OAuth token, Anthropic outage, network partition)
 * also fails this probe — it is the strongest available signal short
 * of running the real workflow.
 *
 * Called at most once per workflow run, only when the workflow contains
 * at least one LLM step. Per-step spawn failures mid-run are handled
 * separately by the workflow-execute convergence arm.
 *
 *   SUCCESS  →  { ok: true, latencyMs }
 *   FAILURE  →  { ok: false, latencyMs, error: one-line summary }
 */
export async function probeLlmHealth(params) {
    const { model, timeoutMs = DEFAULT_PROBE_TIMEOUT_MS, _spawn } = params;
    const start = Date.now();
    console.error(`[llm-probe] probing: model=${model}`);
    try {
        await executeLlmCall({
            prompt: "pong",
            model,
            maxTokens: 8,
            timeoutMs,
            _spawn,
        });
        const latencyMs = Date.now() - start;
        console.error(`[llm-probe] ok: latencyMs=${latencyMs}`);
        return { ok: true, latencyMs };
    }
    catch (err) {
        const latencyMs = Date.now() - start;
        const error = err instanceof Error ? err.message : String(err);
        // Collapse multi-line errors to a one-liner for log + template substitution
        const oneLine = error.replace(/\s*\n\s*/g, " — ");
        console.error(`[llm-probe] FAILED: ${oneLine} latencyMs=${latencyMs}`);
        return { ok: false, latencyMs, error: oneLine };
    }
}
//# sourceMappingURL=llm-call.js.map