var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
import { logger } from "adminforth";
import { randomUUID } from "crypto";
import { Command } from "@langchain/langgraph";
import { createSequenceDebugCollector } from "./agent/middleware/sequenceDebug.js";
import { getErrorMessage, isAbortError } from "./errors.js";
function getApprovalDecision(input) {
    return "approvalDecision" in input
        && (input.approvalDecision === "approve" || input.approvalDecision === "reject")
        ? input.approvalDecision
        : undefined;
}
function getInterruptItems(interrupt) {
    return Array.isArray(interrupt) ? interrupt : [interrupt];
}
function getHitlInterrupts(interrupt) {
    return getInterruptItems(interrupt).flatMap((item) => {
        const value = item && typeof item === "object" && "value" in item
            ? item.value
            : item;
        const actionRequests = value && typeof value === "object"
            ? value.actionRequests
            : undefined;
        const interruptId = item && typeof item === "object"
            ? item.id
            : undefined;
        return typeof interruptId === "string" && Array.isArray(actionRequests)
            ? [{ id: interruptId, count: actionRequests.length }]
            : [];
    });
}
function buildHitlDecision(decision, prompt) {
    if (decision === "approve") {
        return { type: "approve" };
    }
    return {
        type: "reject",
        message: prompt
            ? `User rejected the pending tool execution and sent a new instruction instead: ${prompt}`
            : "User rejected executing this tool",
    };
}
function buildHitlResumeValue(input) {
    return {
        decisions: Array.from({ length: input.count }, () => (buildHitlDecision(input.decision, input.prompt))),
    };
}
function buildLangGraphResume(input) {
    var _a;
    const interrupts = (_a = input.interrupts) !== null && _a !== void 0 ? _a : [];
    if (interrupts.length === 0) {
        throw new Error("No pending approval interrupt found for resume.");
    }
    if (interrupts.length === 1) {
        return buildHitlResumeValue({
            decision: input.decision,
            count: interrupts[0].count,
            prompt: input.prompt,
        });
    }
    return Object.fromEntries(interrupts.map((interrupt) => [
        interrupt.id,
        buildHitlResumeValue({
            decision: input.decision,
            count: interrupt.count,
            prompt: input.prompt,
        }),
    ]));
}
export class AgentTurnService {
    constructor(lifecycle, contextBuilder, modeResolver, modelFactory, promptBuilder, runtime, streamConsumer) {
        this.lifecycle = lifecycle;
        this.contextBuilder = contextBuilder;
        this.modeResolver = modeResolver;
        this.modelFactory = modelFactory;
        this.promptBuilder = promptBuilder;
        this.runtime = runtime;
        this.streamConsumer = streamConsumer;
        this.pendingInterrupts = new Map();
    }
    prepareTurn(input) {
        return __awaiter(this, void 0, void 0, function* () {
            const sequenceDebugCollector = createSequenceDebugCollector();
            const approvalDecision = getApprovalDecision(input);
            const shouldResume = Boolean(approvalDecision);
            const pendingInterrupts = this.pendingInterrupts.get(input.sessionId);
            if (shouldResume && (!pendingInterrupts || pendingInterrupts.length === 0)) {
                throw new Error(`No pending approval interrupt found for session "${input.sessionId}".`);
            }
            const lifecycleTurn = shouldResume
                ? yield this.lifecycle.resume(input)
                : yield this.lifecycle.start(input);
            const context = yield this.contextBuilder.build({
                base: input,
                turnId: lifecycleTurn.turnId,
            });
            return {
                prompt: input.prompt,
                sessionId: input.sessionId,
                turnId: lifecycleTurn.turnId,
                previousUserMessages: lifecycleTurn.previousUserMessages,
                modeName: input.modeName,
                context,
                observability: {
                    emit: undefined,
                    sequenceDebugSink: sequenceDebugCollector,
                },
                resume: shouldResume
                    ? {
                        decision: approvalDecision,
                        interrupts: pendingInterrupts,
                    }
                    : undefined,
                initialResponse: shouldResume && "initialResponse" in lifecycleTurn
                    ? lifecycleTurn.initialResponse
                    : undefined,
            };
        });
    }
    runAgentTurn(input) {
        return __awaiter(this, void 0, void 0, function* () {
            const selectedMode = this.modeResolver.resolve(input.modeName);
            const [models, messages] = yield Promise.all([
                this.modelFactory.create(selectedMode.completionAdapter),
                this.promptBuilder.build({
                    prompt: input.prompt,
                    previousUserMessages: input.previousUserMessages,
                    adminUser: input.context.adminUser,
                    completionAdapter: selectedMode.completionAdapter,
                    chatSurface: input.context.chatSurface,
                    abortSignal: input.context.abortSignal,
                }),
            ]);
            const stream = yield this.runtime.stream({
                models,
                input: input.resume
                    ? new Command({
                        resume: buildLangGraphResume({
                            decision: input.resume.decision,
                            interrupts: input.resume.interrupts,
                            prompt: input.prompt,
                        }),
                    })
                    : { messages },
                context: input.context,
                observability: input.observability,
            });
            let interrupted = false;
            try {
                return yield this.streamConsumer.consume({
                    stream: stream,
                    abortSignal: input.context.abortSignal,
                    emit: input.observability.emit,
                    onInterrupt: (interrupt) => __awaiter(this, void 0, void 0, function* () {
                        var _a, _b, _c;
                        interrupted = true;
                        const interrupts = getHitlInterrupts(interrupt);
                        const pendingInterrupts = (_a = this.pendingInterrupts.get(input.sessionId)) !== null && _a !== void 0 ? _a : [];
                        const mergedInterrupts = new Map(pendingInterrupts.map((pendingInterrupt) => [
                            pendingInterrupt.id,
                            pendingInterrupt.count,
                        ]));
                        for (const pendingInterrupt of interrupts) {
                            mergedInterrupts.set(pendingInterrupt.id, pendingInterrupt.count);
                        }
                        this.pendingInterrupts.set(input.sessionId, [...mergedInterrupts.entries()].map(([id, count]) => ({ id, count })));
                        yield ((_c = (_b = input.observability).emit) === null || _c === void 0 ? void 0 : _c.call(_b, {
                            type: "interrupt",
                            sessionId: input.sessionId,
                            interrupt,
                        }));
                    }),
                });
            }
            finally {
                if (!interrupted) {
                    this.pendingInterrupts.delete(input.sessionId);
                }
            }
        });
    }
    runAndPersistAgentResponse(input) {
        return __awaiter(this, void 0, void 0, function* () {
            var _a, _b;
            const preparedTurn = yield this.prepareTurn(input);
            preparedTurn.observability.emit = input.emit;
            let fullResponse = (_a = preparedTurn.initialResponse) !== null && _a !== void 0 ? _a : "";
            let aborted = false;
            let failed = false;
            try {
                const agentResponse = yield this.runAgentTurn(preparedTurn);
                fullResponse += agentResponse.text;
            }
            catch (error) {
                if (((_b = input.abortSignal) === null || _b === void 0 ? void 0 : _b.aborted) || isAbortError(error)) {
                    aborted = true;
                    logger.info(input.abortLogMessage);
                }
                else {
                    failed = true;
                    fullResponse = getErrorMessage(error);
                    logger.error(`${input.failureLogMessage}:\n${fullResponse}`);
                }
            }
            preparedTurn.observability.sequenceDebugSink.flush();
            yield this.lifecycle.finish({
                turnId: preparedTurn.turnId,
                responseText: fullResponse,
                debugHistory: preparedTurn.observability.sequenceDebugSink.getHistory(),
            });
            return {
                text: fullResponse,
                turnId: preparedTurn.turnId,
                aborted,
                failed,
            };
        });
    }
    handleTurn(input) {
        return __awaiter(this, void 0, void 0, function* () {
            var _a, _b;
            yield input.emit({
                type: "turn-started",
                messageId: randomUUID(),
            });
            const agentResponse = yield this.runAndPersistAgentResponse({
                prompt: input.prompt,
                sessionId: input.sessionId,
                modeName: input.modeName,
                userTimeZone: input.userTimeZone,
                currentPage: input.currentPage,
                chatSurface: input.chatSurface,
                adminPublicOrigin: input.adminPublicOrigin,
                approvalDecision: input.approvalDecision,
                abortSignal: input.abortSignal,
                adminUser: input.adminUser,
                emit: input.emit,
                failureLogMessage: (_a = input.failureLogMessage) !== null && _a !== void 0 ? _a : "Agent response failed",
                abortLogMessage: (_b = input.abortLogMessage) !== null && _b !== void 0 ? _b : "Agent response aborted",
            });
            if (agentResponse.failed) {
                yield input.emit({
                    type: "error",
                    error: agentResponse.text,
                });
            }
            else if (!agentResponse.aborted) {
                yield input.emit({
                    type: "response",
                    text: agentResponse.text,
                    sessionId: input.sessionId,
                    turnId: agentResponse.turnId,
                });
            }
            yield input.emit({
                type: "finish",
            });
            return agentResponse;
        });
    }
}
