import { BestAgentError } from "@best-agent/foundation";
import { isProviderContextOverflowError, mergeSystemPrompts, } from "../provider/model-provider.js";
import { formatToolInputForPrompt } from "../shared/utils/text.js";
export function normalizeTokenCount(value) {
    const numeric = Number(value);
    if (!Number.isFinite(numeric) || numeric < 0) {
        return undefined;
    }
    const normalized = Math.floor(numeric);
    return normalized > 0 ? normalized : 0;
}
export function estimateProviderRequestInputTokens(input) {
    const serializedTools = (input.tools ?? [])
        .map((tool) => JSON.stringify({
        description: tool.description,
        inputSchema: tool.inputSchema,
        name: tool.name,
    }))
        .join("\n");
    const inputText = [
        input.systemPrompt ?? "",
        ...input.messages.map((message) => renderConversationMessageForEstimation(message)),
        serializedTools,
    ].join("\n");
    return estimateTextTokens(inputText);
}
export function estimateConversationInputTokens(messages, systemPrompt) {
    const inputText = [
        systemPrompt ?? "",
        ...messages.map((message) => renderConversationMessageForEstimation(message)),
    ].join("\n");
    return estimateTextTokens(inputText);
}
export function providerRequestPromptSignature(input) {
    return JSON.stringify({
        messages: input.messages,
        systemPrompt: input.systemPrompt,
        tools: input.tools,
    });
}
export function calculateConversationMemoryBudgetTokens(input) {
    const fixedOverheadTokens = Math.max(0, input.estimatedRequestTokens - input.estimatedConversationTokens);
    return Math.max(0, input.inputBudgetTokens - fixedOverheadTokens);
}
export function createRequestBudgetSnapshot(providerConfig, estimatedInputTokens, input) {
    const updatedAt = new Date().toISOString();
    const updatedAtMs = Date.parse(updatedAt);
    const contextWindowTokens = normalizeTokenCount(providerConfig.maxInputTokens);
    const maxOutputTokens = normalizeTokenCount(providerConfig.maxOutputTokens);
    const reservedOutputTokens = maxOutputTokens ?? 0;
    const protocolReserveTokens = 0;
    const compatibilityBudget = contextWindowTokens != null
        ? Math.max(0, contextWindowTokens - reservedOutputTokens - protocolReserveTokens)
        : undefined;
    if (contextWindowTokens == null) {
        return {
            autoBudgetOutcome: "budget-unknown",
            autoBudgetReasonCode: "missing-metadata",
            estimatedInputTokens,
            maxOutputTokens,
            protocolReserveTokens,
            reservedOutputTokens,
            updatedAt,
            updatedAtMs,
            usedAutoCompaction: false,
        };
    }
    const inputBudgetTokens = contextWindowTokens - reservedOutputTokens - protocolReserveTokens;
    if (inputBudgetTokens <= 0) {
        return {
            autoBudgetOutcome: "budget-unknown",
            autoBudgetReasonCode: "invalid-budget",
            budgetSource: "explicit-config",
            contextWindowTokens,
            estimatedInputTokens,
            inputBudgetTokens,
            maxInputTokens: compatibilityBudget,
            maxOutputTokens,
            protocolReserveTokens,
            reservedOutputTokens,
            thresholdTokens: compatibilityBudget,
            triggerRatio: 1,
            updatedAt,
            updatedAtMs,
            usedAutoCompaction: false,
        };
    }
    return {
        autoBudgetOutcome: input.autoBudgetOutcome,
        ...(input.autoBudgetReasonCode ? { autoBudgetReasonCode: input.autoBudgetReasonCode } : {}),
        budgetSource: "explicit-config",
        contextWindowTokens,
        estimatedInputTokens,
        inputBudgetTokens,
        maxInputTokens: inputBudgetTokens,
        maxOutputTokens,
        protocolReserveTokens,
        reservedOutputTokens,
        thresholdTokens: inputBudgetTokens,
        triggerRatio: 1,
        updatedAt,
        updatedAtMs,
        usedAutoCompaction: input.autoBudgetOutcome === "compacted",
    };
}
export function createRequestBudgetExceededError(snapshot) {
    const estimated = Math.max(0, Math.floor(Number(snapshot.estimatedInputTokens ?? 0)));
    const budget = Math.max(0, Math.floor(Number(snapshot.inputBudgetTokens ?? 0)));
    return new BestAgentError("REQUEST_BUDGET_EXCEEDED", budget > 0
        ? `The next model request needs ${estimated} input tokens, but the real input budget is ${budget}. Compact manually or reduce the pending context.`
        : "The next model request exceeds the available input budget. Compact manually or reduce the pending context.");
}
export function canRetryProviderContextOverflow(error, snapshot) {
    return (isProviderContextOverflowError(error) &&
        snapshot.autoBudgetOutcome !== "compacted" &&
        snapshot.autoBudgetOutcome !== "failed-to-fit" &&
        snapshot.inputBudgetTokens !== undefined &&
        snapshot.inputBudgetTokens > 0);
}
export function getCompactionAdvisoryFromSnapshot(snapshot, plannedCostEstimate = 0) {
    const normalizedPlannedCost = Math.max(0, Math.floor(plannedCostEstimate));
    if (!snapshot) {
        return {
            budgetKnown: false,
            plannedCostEstimate: normalizedPlannedCost,
            reason: "No request-budget snapshot is available yet.",
            shouldCompact: false,
            timing: "none",
        };
    }
    if (snapshot.autoBudgetOutcome === "failed-to-fit") {
        return {
            autoBudgetOutcome: snapshot.autoBudgetOutcome,
            budgetKnown: snapshot.inputBudgetTokens !== undefined,
            estimatedInputTokens: snapshot.estimatedInputTokens,
            inputBudgetTokens: snapshot.inputBudgetTokens,
            plannedCostEstimate: normalizedPlannedCost,
            reason: "The latest request did not fit the input budget.",
            remainingInputTokens: snapshot.inputBudgetTokens === undefined
                ? undefined
                : snapshot.inputBudgetTokens - snapshot.estimatedInputTokens,
            shouldCompact: true,
            timing: "before-expensive-operation",
        };
    }
    if (snapshot.inputBudgetTokens == null || snapshot.inputBudgetTokens <= 0) {
        return {
            autoBudgetOutcome: snapshot.autoBudgetOutcome,
            budgetKnown: false,
            estimatedInputTokens: snapshot.estimatedInputTokens,
            plannedCostEstimate: normalizedPlannedCost,
            reason: "Provider input budget is unknown.",
            shouldCompact: false,
            timing: "none",
        };
    }
    const projectedInputTokens = snapshot.estimatedInputTokens + normalizedPlannedCost;
    const currentTokenUsage = (snapshot.estimatedInputTokens / snapshot.inputBudgetTokens) * 100;
    const projectedTokenUsage = (projectedInputTokens / snapshot.inputBudgetTokens) * 100;
    const remainingInputTokens = snapshot.inputBudgetTokens - projectedInputTokens;
    const cautionWindowTokens = Math.min(2_048, Math.max(256, Math.floor(snapshot.inputBudgetTokens * 0.05)));
    const shouldCompact = normalizedPlannedCost > 0
        ? projectedTokenUsage >= 70
        : remainingInputTokens <= 0 ||
            snapshot.inputBudgetTokens - snapshot.estimatedInputTokens <= cautionWindowTokens;
    return {
        autoBudgetOutcome: snapshot.autoBudgetOutcome,
        budgetKnown: true,
        currentTokenUsage,
        estimatedInputTokens: snapshot.estimatedInputTokens,
        inputBudgetTokens: snapshot.inputBudgetTokens,
        plannedCostEstimate: normalizedPlannedCost,
        projectedTokenUsage,
        reason: shouldCompact
            ? normalizedPlannedCost > 0
                ? `Projected input usage is ${projectedTokenUsage.toFixed(1)}%, above the 70% predictive compaction threshold.`
                : "The latest request is inside the compaction caution window."
            : normalizedPlannedCost > 0
                ? `Projected input usage is ${projectedTokenUsage.toFixed(1)}%, below the 70% predictive compaction threshold.`
                : "The latest request is outside the compaction caution window.",
        remainingInputTokens,
        shouldCompact,
        timing: shouldCompact ? "before-expensive-operation" : "none",
    };
}
export function buildCompactionAdvisorySystemPrompt(snapshot) {
    const advisory = getCompactionAdvisoryFromSnapshot(snapshot);
    if (!advisory.shouldCompact) {
        return undefined;
    }
    const details = advisory.inputBudgetTokens && advisory.estimatedInputTokens !== undefined
        ? ` Estimated input: ${Math.round(advisory.estimatedInputTokens)}/${Math.round(advisory.inputBudgetTokens)} tokens.`
        : "";
    return [
        "Context compaction advisory:",
        `${advisory.reason}${details}`,
        "Before large or exploratory tool sequences, keep outputs bounded and avoid unnecessary broad exploration.",
    ].join("\n");
}
export function appendCompactionAdvisoryToProviderRequest(request, snapshot) {
    const advisoryPrompt = buildCompactionAdvisorySystemPrompt(snapshot);
    if (!advisoryPrompt || request.systemPrompt?.includes("Context compaction advisory:")) {
        return request;
    }
    const nextRequest = {
        ...request,
        promptLayerOrder: [...request.promptLayerOrder, "compaction-advisory"],
        systemPrompt: mergeSystemPrompts(request.systemPrompt, advisoryPrompt),
    };
    if (snapshot.inputBudgetTokens !== undefined &&
        estimateProviderRequestInputTokens(nextRequest) > snapshot.inputBudgetTokens) {
        return request;
    }
    return nextRequest;
}
export function createEmptyRuntimeSessionUsage() {
    return {
        cacheWriteTokens: 0,
        cachedInputTokens: 0,
        estimatedTokens: 0,
        inputTokens: 0,
        outputTokens: 0,
        reasoningTokens: 0,
        reportedTokens: 0,
        totalTokens: 0,
    };
}
export function estimateProviderUsage(input) {
    const inputText = [
        input.systemPrompt ?? "",
        ...input.messages.map((message) => renderConversationMessageForEstimation(message)),
    ].join("\n");
    const inputTokens = estimateTextTokens(inputText);
    const outputTokens = estimateTextTokens(input.responseText);
    return {
        cacheReadTokens: 0,
        cacheWriteTokens: 0,
        inputTokens,
        outputTokens,
        reasoningTokens: 0,
        totalTokens: inputTokens + outputTokens,
    };
}
function renderConversationMessageForEstimation(message) {
    if (message.role === "assistant" && message.toolCalls && message.toolCalls.length > 0) {
        const serializedToolCalls = message.toolCalls
            .map((toolCall) => `${toolCall.toolName}:${formatToolInputForPrompt(toolCall.input).trim() || "(empty input)"}`)
            .join(" | ");
        return `assistant-tool-calls:${serializedToolCalls}`;
    }
    if (message.role === "tool") {
        return `${message.role}:${message.toolName}:${message.outcome}${message.failureKind ? `:${message.failureKind}` : ""}:${message.content}`;
    }
    return `${message.role}:${message.content}`;
}
export function estimateTextTokens(text) {
    if (!text || text.trim().length === 0) {
        return 0;
    }
    return Math.ceil(text.length / 4);
}
//# sourceMappingURL=provider-request-budget.js.map