import { getPrefixedLogger } from "../../../../util/logger.js";
import { backfillReasoningSignatures, normalizeToolMessage, stripUndefinedFromToolOutputs, } from "../../context-v2/types.js";
const logger = getPrefixedLogger("[context-observer]");
/**
 * Observer that manages conversation context during LLM interactions.
 *
 * Handles:
 * - Starting and ending steps in the context
 * - Ending the turn on completion
 * - Releasing the context handle on completion/error/abort
 *
 * @example
 * ```typescript
 * const observer = new ContextObserver({
 *   contextHandle: await contextManager.acquireContext(contextId, ownerId),
 * });
 * ```
 */
export class ContextObserver {
    contextHandle;
    released = false;
    constructor(options) {
        this.contextHandle = options.contextHandle;
    }
    attach(events, _config) {
        events.on("step:finish", async (step, session) => {
            await this.onStepComplete(step, session);
        });
        events.on("stream:finish", (result, session) => {
            this.onStreamFinish(result, session);
        });
        events.on("session:complete", () => {
            this.release();
        });
        events.on("session:error", () => {
            this.release();
        });
        events.on("session:abort", () => {
            this.release();
        });
    }
    async onStepComplete(step, _session) {
        // Don't persist partial state when step ended due to an error.
        // Saving partial assistant messages from interrupted streams can cause issues on retry,
        // especially with extended thinking where the final assistant message must start with
        // a thinking block. If we save a partial message (text only, no thinking block),
        // all following requests will fail.
        if (step.finishReason === "error" || step.finishReason === "other") {
            return;
        }
        if (step.response?.messages) {
            const normalizedMessages = step.response.messages.map((msg) => normalizeToolMessage(stripUndefinedFromToolOutputs(msg)));
            const hasReasoning = normalizedMessages.some((msg) => msg.role === "assistant" &&
                typeof msg.content !== "string" &&
                msg.content.some((p) => p.type === "reasoning"));
            if (hasReasoning && !step.content?.length) {
                logger.warn("Step has reasoning in response.messages but no step.content for signature backfill");
            }
            backfillReasoningSignatures(normalizedMessages, step.content ?? []);
            await this.contextHandle.context.endStep(normalizedMessages, step.usage);
        }
        this.contextHandle.context.startStep();
    }
    onStreamFinish(result, _session) {
        this.contextHandle.context.endTurn(result.totalUsage);
    }
    release() {
        if (!this.released) {
            this.released = true;
            this.contextHandle.release();
        }
    }
}
//# sourceMappingURL=context.js.map