/**
 * Container Execution Protocol — standard contract for running automations
 * inside containers or as local binaries.
 *
 * Contract:
 *
 *   Input (via env vars):
 *     VAUBAN_EXECUTION_ID    — unique execution identifier (UUID)
 *     VAUBAN_AUTOMATION_NAME — automation name (kebab-case)
 *     VAUBAN_INPUT           — JSON-encoded input parameters
 *     VAUBAN_TIMEOUT         — timeout in seconds
 *     VAUBAN_MODE            — execution mode (default: "execute")
 *
 *   Output (via stdout, last non-empty line, JSON):
 *     { "status": "completed", "output": <any> }
 *     { "status": "failed", "error": { "code": "...", "message": "...", "details"?: <any> } }
 *
 *   Diagnostics (via stderr, optional, one JSON object per line):
 *     { "level": "info", "message": "...", "timestamp": "..." }
 *     { "progress": 0.5, "message": "..." }
 *
 * @public @since 1.2.0
 */
/**
 * Reserved environment variable names. All caller env merged on top must NOT
 * shadow these keys, or the automation's view of its own context becomes
 * undefined.
 *
 * @public
 */
export const PROTOCOL_ENV = {
    EXECUTION_ID: "VAUBAN_EXECUTION_ID",
    AUTOMATION_NAME: "VAUBAN_AUTOMATION_NAME",
    INPUT: "VAUBAN_INPUT",
    TIMEOUT: "VAUBAN_TIMEOUT",
    MODE: "VAUBAN_MODE",
};
/**
 * Default protocol mode set when caller does not specify one.
 * @public
 */
export const DEFAULT_PROTOCOL_MODE = "execute";
/**
 * Thrown when stdout does not contain a parseable protocol result on its last
 * non-empty line.
 *
 * @public
 */
export class ProtocolParseError extends Error {
    rawTail;
    constructor(reason, rawTail) {
        super(`container-protocol: ${reason}`);
        this.name = "ProtocolParseError";
        this.rawTail = rawTail;
    }
}
/**
 * Build the protocol's environment variables. Caller `extraEnv` is merged
 * AFTER protocol env, so it cannot accidentally override reserved keys.
 *
 * @public
 */
export function buildProtocolEnv(args) {
    const env = {};
    if (args.extraEnv) {
        for (const [k, v] of Object.entries(args.extraEnv)) {
            env[k] = v;
        }
    }
    env[PROTOCOL_ENV.EXECUTION_ID] = args.executionId;
    env[PROTOCOL_ENV.AUTOMATION_NAME] = args.automationName;
    env[PROTOCOL_ENV.INPUT] = JSON.stringify(args.input);
    env[PROTOCOL_ENV.TIMEOUT] = String(args.timeoutSeconds);
    env[PROTOCOL_ENV.MODE] = args.mode ?? DEFAULT_PROTOCOL_MODE;
    return env;
}
/**
 * Parse the last non-empty line of stdout as a {@link ProtocolResult}.
 *
 * @throws {@link ProtocolParseError} when stdout is empty, has no JSON tail,
 *   or the JSON does not match the protocol shape.
 *
 * @public
 */
export function parseProtocolOutput(stdout) {
    const trimmed = stdout.trim();
    if (trimmed.length === 0) {
        throw new ProtocolParseError("stdout is empty", "");
    }
    const lines = trimmed.split("\n");
    const lastLine = lines[lines.length - 1].trim();
    if (lastLine.length === 0) {
        throw new ProtocolParseError("last stdout line is empty", trimmed.slice(-200));
    }
    let parsed;
    try {
        parsed = JSON.parse(lastLine);
    }
    catch {
        throw new ProtocolParseError("last stdout line is not valid JSON", lastLine.slice(0, 500));
    }
    if (typeof parsed !== "object" || parsed === null || !("status" in parsed)) {
        throw new ProtocolParseError("protocol JSON missing 'status' field", lastLine.slice(0, 500));
    }
    const obj = parsed;
    const status = obj["status"];
    if (status === "completed") {
        return { status: "completed", output: (obj["output"] ?? null) };
    }
    if (status === "failed") {
        const err = obj["error"] ?? {};
        const code = typeof err["code"] === "string" ? err["code"] : "UNKNOWN";
        const message = typeof err["message"] === "string"
            ? err["message"]
            : "(no message)";
        const details = err["details"];
        return { status: "failed", error: { code, message, details } };
    }
    throw new ProtocolParseError(`unknown protocol status: ${String(status)}`, lastLine.slice(0, 500));
}
/**
 * Parse stderr lines as JSON log entries when possible; fall back to plain
 * text wrapped at level "info". Used by {@link ContainerRuntime} to populate
 * `ExecutionResult.logs`.
 *
 * @public
 */
export function parseStderrLogs(stderr) {
    const logs = [];
    const lines = stderr.split("\n");
    for (const raw of lines) {
        const line = raw.trim();
        if (line.length === 0)
            continue;
        try {
            const obj = JSON.parse(line);
            logs.push({
                level: typeof obj["level"] === "string" ? obj["level"] : "info",
                message: typeof obj["message"] === "string"
                    ? obj["message"]
                    : line.slice(0, 500),
                timestamp: typeof obj["timestamp"] === "string"
                    ? obj["timestamp"]
                    : new Date().toISOString(),
            });
        }
        catch {
            logs.push({
                level: "info",
                message: line.slice(0, 500),
                timestamp: new Date().toISOString(),
            });
        }
    }
    return logs;
}
//# sourceMappingURL=protocol.js.map