import { smoothStream } from "ai";
import { safeJsonStringify } from "../../util/log-sanitizer.js";
import { getErrorMeta, getLogger } from "../../util/logger.js";
import { processLLMConfig } from "../util/llm-config-utils.js";
import { parseJwt } from "../util/parse-jwt.js";
import { TracedFSM, } from "./traced-fsm.js";
const $ = {
    Idle: "Idle",
    AgentPlanning: "AgentPlanning",
    LLMGenerating: "LLMGenerating",
    AwaitingUser: "AwaitingUser",
    PostProcessing: "PostProcessing",
    RuntimeReviewing: "RuntimeReviewing",
    Dead: "Dead",
};
export const ClarkStateNames = $;
export const USER_SENT_PROMPT = "userSentPrompt";
export const USER_ACCEPTED_DRAFT = "userAcceptedDraft";
export const USER_REJECTED_DRAFT = "userRejectedDraft";
export const USER_CANCELED = "userCanceled";
export const AGENT_PLANNED = "agentPlanned";
export const AGENT_REPORTED_ERROR_TO_USER = "agentReportedErrorToUser";
export const AGENT_NEEDS_USER_INPUT = "agentNeedsUserInput";
export const AGENT_GAVE_UP = "agentGaveUp";
export const AGENT_CANCELED = "agentCanceled";
export const LLM_REQUESTED_CONTEXT = "llmRequestedContext";
export const LLM_FINISHED_UNEXPECTEDLY = "llmFinishedUnexpectedly";
export const LLM_ERRORED = "llmErrored";
export const LLM_FINISHED = "llmFinished";
export const POST_PROCESSING_ERRORED = "postProcessingErrored";
export const POST_PROCESSING_FINISHED = "postProcessingFinished";
export const RUNTIME_REVIEW_FINISHED = "runtimeReviewFinished";
export const APP_RUNTIME_UPDATED_WITHOUT_EDITS = "appRuntimeUpdatedWithoutEdits";
export const RUNTIME_REVIEW_ERRORED = "runtimeReviewErrored";
export const BUILD_SYSTEM_ERRORED = "buildSystemErrored";
export const SERVICE_STARTED_WITH_DRAFT = "serviceStartedWithDraft";
export const V3_AGENT_FINISHED = "v3AgentFinished";
// Maps current state to the next state by event
export const transitions = {
    [$.Idle]: {
        [USER_SENT_PROMPT]: $.AgentPlanning,
        [SERVICE_STARTED_WITH_DRAFT]: $.AwaitingUser,
        [APP_RUNTIME_UPDATED_WITHOUT_EDITS]: $.Idle,
    },
    [$.AgentPlanning]: {
        [AGENT_PLANNED]: $.LLMGenerating,
        [AGENT_NEEDS_USER_INPUT]: $.AwaitingUser,
        [V3_AGENT_FINISHED]: $.AwaitingUser,
        [AGENT_GAVE_UP]: $.Dead,
        [AGENT_CANCELED]: $.Idle,
        [USER_CANCELED]: $.Idle,
    },
    [$.LLMGenerating]: {
        [LLM_FINISHED]: $.PostProcessing,
        [LLM_ERRORED]: $.AgentPlanning,
        [LLM_FINISHED_UNEXPECTEDLY]: $.AgentPlanning,
        [LLM_REQUESTED_CONTEXT]: $.AgentPlanning,
        [APP_RUNTIME_UPDATED_WITHOUT_EDITS]: $.Idle,
        [USER_CANCELED]: $.Idle,
        [AGENT_CANCELED]: $.Idle,
        [POST_PROCESSING_ERRORED]: $.AgentPlanning,
        [V3_AGENT_FINISHED]: $.AwaitingUser,
        [AGENT_NEEDS_USER_INPUT]: $.AwaitingUser,
    },
    [$.PostProcessing]: {
        [POST_PROCESSING_FINISHED]: $.RuntimeReviewing,
        [POST_PROCESSING_ERRORED]: $.AgentPlanning,
        [USER_CANCELED]: $.Idle,
        [AGENT_CANCELED]: $.Idle,
        [APP_RUNTIME_UPDATED_WITHOUT_EDITS]: $.Idle,
    },
    [$.RuntimeReviewing]: {
        [RUNTIME_REVIEW_ERRORED]: $.AgentPlanning,
        [BUILD_SYSTEM_ERRORED]: $.AgentPlanning,
        [RUNTIME_REVIEW_FINISHED]: $.AwaitingUser,
        [APP_RUNTIME_UPDATED_WITHOUT_EDITS]: $.Idle,
        [USER_CANCELED]: $.Idle,
        [AGENT_CANCELED]: $.Idle,
    },
    [$.AwaitingUser]: {
        [USER_ACCEPTED_DRAFT]: $.Idle,
        [USER_REJECTED_DRAFT]: $.RuntimeReviewing,
        [USER_SENT_PROMPT]: $.AgentPlanning,
        [USER_CANCELED]: $.Idle,
        [AGENT_CANCELED]: $.Idle,
    },
    [$.Dead]: {},
};
export class Clark extends TracedFSM {
    constructor(config) {
        super({
            initialState: $.Idle,
            transitions,
            ...config,
        });
    }
    getAccessControl(flags) {
        const { userId } = parseJwt(this.context.jwt);
        const { currentMode: mode, planContext, availableIntegrations, taggedEntities, } = this.context;
        const { customerDataAccessEnabled } = this.context.llmConfig || {};
        const sessionId = this.sessionId;
        getLogger().info(`[ai-service] enableClarkAiReadAccessPermission: ${JSON.stringify(flags.enableClarkAiReadAccessPermission)}`);
        return {
            userId,
            sessionId,
            mode,
            planContext,
            availableIntegrations,
            taggedEntities,
            customerDataAccessEnabled,
            enableClarkAiReadAccessPermission: flags.enableClarkAiReadAccessPermission ?? false,
        };
    }
    resetContext() {
        this.logger.info(`[ai-service] resetContext: clearing peer`);
        this.updateContext((ctx) => {
            return {
                // maintain logs and errors across resets
                consoleLogs: ctx.consoleLogs,
                runtimeErrors: ctx.runtimeErrors,
                pendingToolPermissionRequest: ctx.pendingToolPermissionRequest,
                // can't reuse abort controllers
                abortController: new AbortController(),
                // preserve JWT so context ID remains stable for truncation/retry operations
                jwt: ctx.jwt,
                // clear debug-related state
                debugToolCalled: undefined,
                lastDebugTimestamp: undefined,
                lastDebugHadTypeErrors: undefined,
                debugAttemptCount: undefined,
            };
        });
    }
    getRootSpanOptions(_initialEvent) {
        return {
            name: "Clark",
            kind: "agent",
            rootPolicy: "allow",
        };
    }
    getStateSpanOptions(state) {
        let kind = "task";
        switch (state) {
            case $.Idle:
            case $.Dead:
            case $.AwaitingUser:
                return "terminal";
            case $.LLMGenerating:
                kind = "workflow";
                break;
        }
        return {
            name: `Clark.${state}`,
            kind,
        };
    }
    sanitizeEvent(event) {
        switch (event.type) {
            case USER_SENT_PROMPT:
                return {
                    type: event.type,
                    request: {
                        ...event.request,
                        jwt: "<redacted>",
                    },
                };
            default:
                return event;
        }
    }
    /**
     * High-level streamText interface with middleware support.
     *
     * Automatically composes:
     * - Context management (acquires lock, manages conversation history)
     * - Logging middleware (logs full conversation to artifact)
     * - LLMObs middleware (tracing and observability)
     *
     * Only `user` message is required - all other options have smart defaults.
     *
     * @param services - Service dependencies (contextManager, profiler, etc.)
     * @param options - Stream options including model, user message, tools, etc.
     * @returns Promise resolving to stream result
     *
     * @example
     * ```typescript
     * // Minimal usage - only user message required
     * const result = await clark.streamText(services, {
     *   user: { role: "user", content: "Hello!" }
     * });
     *
     * // With overrides
     * const result = await clark.streamText(services, {
     *   user: { role: "user", content: userPrompt },
     *   model: myCustomModel,
     *   system: systemPrompt,
     *   tools: actualTools,
     *   context: { contextOptions: { maxTokens: 150000 } },
     *   logging: { conversationId: "custom-id" }
     * });
     * ```
     */
    async streamText(services, options) {
        const { model: providedModel, context: contextConfig, logging: loggingConfig, enableFallback: enableFallbackOverride, ...streamOptions } = options;
        const model = providedModel ?? services.llmProvider.modelForTask("generateBroadEdit");
        const { thinkingEnabled, thinkingBudgetTokens, maxOutputTokens, headers, providerOptions, } = processLLMConfig(this.context.llmConfig);
        // Prepare logging config with Clark-specific defaults
        const logging = loggingConfig !== false
            ? {
                conversationId: loggingConfig?.conversationId ?? String(Date.now()),
                runTimestamp: this.context.runTimestamp || new Date().toISOString(),
                metadata: loggingConfig?.metadata ?? {
                    mode: this.context.currentMode,
                    provider: this.context.llmConfig?.provider,
                    thinking: thinkingEnabled,
                    thinkingBudgetTokens: thinkingBudgetTokens,
                    disabledTools: this.context.llmConfig?.disabledTools,
                    headers: headers,
                },
            }
            : false;
        // Build context configuration
        const context = {
            contextOptionsV2: this.context.llmConfig?.contextOptionsV2,
            ...contextConfig,
        };
        let tokenUsage = {
            inputTokens: 0,
            outputTokens: 0,
            cachedInputTokens: 0,
            totalTokens: 0,
            model: typeof model === "string" ? model : model.modelId,
            startTime: new Date().toISOString(),
            endTime: "",
        };
        if (!this.context.llmConfig?.provider) {
            throw new Error("llmConfig.provider is required. This should be set from the ui.clark-ai.llm-provider feature flag.");
        }
        if (!this.context.llmConfig?.coreModel) {
            throw new Error("llmConfig.coreModel is required. This should be set from the superblocks.ai.core-model.name feature flag.");
        }
        const providerConfig = {
            provider: this.context.llmConfig.provider,
            model: this.context.llmConfig.coreModel,
        };
        // normally we want to just use the config that is already set, but we should allow
        // for overrides (e.g. for tools that need to disable fallback, because retrying is
        // handled at the tool level)
        let enableFallback = this.context.llmConfig.unifiedProviderEnabled ?? false;
        if (enableFallbackOverride !== undefined) {
            enableFallback = enableFallbackOverride;
        }
        const fallbackStrategy = this.context.llmConfig.fallbackStrategy;
        this.logger.info(`[ai-service] fallbackStrategy from llmConfig: ${fallbackStrategy ?? "undefined"}`);
        return await services.llmClient.streamText({
            ...streamOptions,
            model,
            maxOutputTokens,
            providerOptions,
            headers,
            abortSignal: this.context.abortController?.signal,
            experimental_transform: [smoothStream({ chunking: "line" })],
            onStepFinish: ({ usage }) => this.pushContextUsed(usage),
            onFinish: ({ totalUsage }) => {
                tokenUsage = {
                    ...tokenUsage,
                    ...Object.entries(totalUsage).filter(([, v]) => v != undefined),
                };
            },
            onFinally: () => {
                tokenUsage.endTime = new Date().toISOString();
                const request = {
                    requestId: Date.now().toString(),
                    ...tokenUsage,
                };
                void this.context.peer?.call
                    .aiPushTokenUsage(request)
                    .catch((err) => {
                    this.logger.warn("Failed to push token usage", safeJsonStringify(getErrorMeta(err)));
                });
            },
            context,
            logging,
            retry: streamOptions.retry,
            providerConfig,
            enableFallback,
            fallbackStrategy,
            peer: this.context.peer,
            chatSessionStore: services.chatSessionStore,
            summarizationModel: services.llmProvider.modelForTask("summarizeMessages"),
            safety: streamOptions.safety
                ? {
                    scope: streamOptions.safety.scope, // Pass through unified scope
                    classification: streamOptions.safety.classification, // Pass through
                    secretRedaction: streamOptions.safety.secretRedaction, // Pass through
                }
                : undefined,
        }, this.logger, this.tracer);
    }
    pushContextUsed(usage) {
        const contextUsed = (usage.inputTokens ?? 0) +
            (usage.cachedInputTokens ?? 0) +
            (usage.reasoningTokens ?? 0) +
            (usage.outputTokens ?? 0);
        const formatTokens = (n) => n >= 1000 ? `${(n / 1000).toFixed(1)}k` : `${n}`;
        this.logger.info(`Context used: ${formatTokens(contextUsed)} tokens`);
        void this.context.peer?.call
            .aiPushContextUsed({ contextUsed })
            .catch((err) => {
            this.logger.warn("Failed to push context used", safeJsonStringify(getErrorMeta(err)));
        });
    }
}
//# sourceMappingURL=clark-fsm.js.map