/**
 * Anthropic prompt-cache breakpoint placement for the native Vertex+Claude
 * request path.
 *
 * Vertex does NOT support automatic prompt caching — the only way the
 * conversation prefix gets cached across turns is explicit `cache_control`
 * markers in the request. Anthropic renders a request as `tools → system →
 * messages` and caches the prefix up to each marker, with a hard ceiling of
 * four markers.
 *
 * Without a marker on the message history, the entire (growing, often
 * tool-result-heavy) conversation falls *after* the last breakpoint and is
 * re-billed at full input price on every turn. That is the regression this
 * fixes: it gives the history a rolling breakpoint so the stable prefix is
 * cached at ~0.1x and only the newest turn is fresh.
 */
const EPHEMERAL = { type: "ephemeral" };
/** Anthropic allows at most four `cache_control` breakpoints per request. */
const MAX_BREAKPOINTS = 4;
/**
 * Annotate a native Vertex+Claude request with prompt-cache breakpoints.
 *
 * Budget allocation (max 4 markers):
 *   1. The stable prefix — the last system block when a system prompt is
 *      present (this single marker caches `tools + system`, since system
 *      renders after tools); otherwise the last tool definition.
 *   2-4. A rolling breakpoint on the last few messages, so the
 *      growing-but-now-stable conversation history is cached and only the
 *      newest turn is billed as fresh input.
 *
 * Pure: the inputs are cloned, never mutated.
 */
export function applyVertexAnthropicCacheBreakpoints(input) {
    let budget = MAX_BREAKPOINTS;
    // 1. Stable prefix. A bare string `system` cannot carry cache_control, so
    // convert it to block form. Marking the last system block caches the whole
    // tools + system prefix; only fall back to marking the last tool when there
    // is no system prompt to mark.
    let system = input.system;
    let tools = input.tools;
    const hasSystem = !!input.system && input.system.trim().length > 0;
    if (hasSystem) {
        system = [
            { type: "text", text: input.system, cache_control: EPHEMERAL },
        ];
        budget--;
    }
    else if (input.tools && input.tools.length > 0) {
        const lastIndex = input.tools.length - 1;
        tools = input.tools.map((tool, i) => i === lastIndex ? { ...tool, cache_control: EPHEMERAL } : tool);
        budget--;
    }
    // 2. Rolling history breakpoints over the tail of the conversation.
    const messages = input.messages.map((m) => ({ ...m }));
    let remaining = Math.min(budget, input.maxHistoryBreakpoints ?? MAX_BREAKPOINTS);
    for (let i = messages.length - 1; i >= 0 && remaining > 0; i--) {
        if (markLastContentBlock(messages, i)) {
            remaining--;
        }
    }
    return { system, tools, messages };
}
/**
 * Place a cache breakpoint on the last content block of `messages[i]`.
 * Anthropic attaches `cache_control` to a content block, not the message
 * envelope, so a string content body is first converted to block form.
 * Returns false when the message has no markable block (caller then walks to
 * an earlier message), so an empty turn never silently consumes a breakpoint.
 */
function markLastContentBlock(messages, i) {
    const message = messages[i];
    if (typeof message.content === "string") {
        if (message.content.length === 0) {
            return false;
        }
        messages[i] = {
            ...message,
            content: [
                { type: "text", text: message.content, cache_control: EPHEMERAL },
            ],
        };
        return true;
    }
    if (!Array.isArray(message.content) || message.content.length === 0) {
        return false;
    }
    // Shallow clone is sufficient: we only ever add a top-level `cache_control`
    // field to the last block and never mutate nested members (e.g. an image's
    // `source`). Those nested objects stay shared with the input by reference,
    // which is safe because they are never written to. If a future block shape
    // requires mutating nested members, deep-clone that block instead.
    const content = message.content.map((block) => ({ ...block }));
    const lastIndex = content.length - 1;
    content[lastIndex] = { ...content[lastIndex], cache_control: EPHEMERAL };
    messages[i] = { ...message, content };
    return true;
}
