import { getPrefixedLogger } from "../../../util/logger.js";
/**
 * Threshold for compacting tool call inputs.
 * Inputs larger than this (in characters) will be summarized or truncated.
 */
export const INPUT_COMPACTION_THRESHOLD_CHARS = 100;
// --- Caching constants and types ---
export const PROVIDER_CACHE_MAX_BREAKPOINTS = 4;
export const PROVIDER_CACHE_MIN_CACHEABLE_TOKENS = 1024;
export const CACHE_BREAKPOINT_PROVIDER_OPTIONS = {
    bedrock: { cachePoint: { type: "default" } },
    anthropic: { cacheControl: { type: "ephemeral" } },
};
const logger = getPrefixedLogger("[context-v2:types]");
function toJsonValue(value) {
    if (value === undefined) {
        return null;
    }
    try {
        return JSON.parse(JSON.stringify(value));
    }
    catch {
        return String(value);
    }
}
function toJsonOutput(value) {
    return { type: "json", value: toJsonValue(value) };
}
function isToolResultOutput(output) {
    if (typeof output !== "object" || output === null) {
        return false;
    }
    if (!("type" in output) || typeof output.type !== "string") {
        return false;
    }
    const outputObj = output;
    switch (outputObj.type) {
        case "text":
        case "error-text":
            return typeof outputObj.value === "string";
        case "json":
        case "error-json":
            return "value" in outputObj;
        case "content":
            return Array.isArray(outputObj.value);
        case "execution-denied":
            return (outputObj.reason === undefined || typeof outputObj.reason === "string");
        default:
            return false;
    }
}
/**
 * Normalizes a tool result output to ensure it conforms to LanguageModelV3ToolResultOutput.
 * This prevents validation errors when passing messages to the AI SDK.
 */
export function normalizeToolResultOutput(output) {
    if (output === null || output === undefined) {
        return toJsonOutput(null);
    }
    if (typeof output !== "object") {
        return toJsonOutput(output);
    }
    const outputObj = output;
    if (isToolResultOutput(output)) {
        return output;
    }
    const validTypes = [
        "text",
        "json",
        "error-text",
        "error-json",
        "content",
        "execution-denied",
    ];
    const type = outputObj.type;
    if (!validTypes.includes(type)) {
        logger.warn(`Invalid tool result output type: ${type}. Normalizing to json type.`);
        return toJsonOutput(output);
    }
    const valueRequiredTypes = new Set([
        "text",
        "json",
        "error-text",
        "error-json",
        "content",
    ]);
    if (valueRequiredTypes.has(type) && !("value" in outputObj)) {
        logger.warn(`Tool result output missing required value for type=${outputObj.type}. Normalizing to json type.`);
        return toJsonOutput(output);
    }
    return toJsonOutput(output);
}
/**
 * Normalizes tool messages to ensure all tool results have valid output formats.
 */
export function normalizeToolMessage(msg) {
    if (msg.role !== "tool" || !Array.isArray(msg.content)) {
        return msg;
    }
    const normalizedContent = msg.content.map((part) => {
        if (typeof part === "object" &&
            part !== null &&
            "type" in part &&
            part.type === "tool-result" &&
            "output" in part) {
            const toolResult = part;
            const normalizedOutput = normalizeToolResultOutput(toolResult.output);
            if (normalizedOutput !== toolResult.output) {
                logger.debug(`Normalized tool result output for ${toolResult.toolName} (callId: ${toolResult.toolCallId})`);
            }
            return { ...toolResult, output: normalizedOutput };
        }
        return part;
    });
    return { ...msg, content: normalizedContent };
}
/**
 * Normalizes provider options on reasoning blocks to work across both
 * Anthropic (Vertex) and Bedrock providers.
 */
export function normalizeReasoningProviderOptions(msg) {
    if (typeof msg.content === "string") {
        return;
    }
    for (const part of msg.content) {
        if (part.type !== "reasoning") {
            continue;
        }
        const providerOptions = part.providerOptions;
        if (!providerOptions)
            return;
        const anthropicSignature = providerOptions.anthropic?.signature;
        const bedrockSignature = providerOptions.bedrock?.signature;
        const signature = anthropicSignature || bedrockSignature;
        if (signature) {
            providerOptions.anthropic = { ...providerOptions.anthropic, signature };
            providerOptions.bedrock = { ...providerOptions.bedrock, signature };
        }
    }
}
/**
 * Backfills missing `providerOptions` on reasoning parts in response messages
 * using `providerMetadata` from the raw step content.
 *
 * Works around an AI SDK bug (vercel/ai#13972) where the Bedrock provider
 * trimmed reasoning text when a signature was present, invalidating the
 * cryptographic signature. Without the signature, replaying conversation
 * history causes Anthropic to reject with `thinking.signature: Field required`.
 *
 * The raw `stepContent` (accumulated from stream chunks) retains the correct
 * `providerMetadata`, so we use it as the source of truth.
 *
 * Mutates `messages` in place.
 */
export function backfillReasoningSignatures(messages, stepContent) {
    const signaturesByIndex = [];
    for (const part of stepContent) {
        if (part.type === "reasoning" && part.providerMetadata) {
            signaturesByIndex.push(part.providerMetadata);
        }
    }
    if (signaturesByIndex.length === 0) {
        return;
    }
    let reasoningIndex = 0;
    for (const msg of messages) {
        if (msg.role !== "assistant" || typeof msg.content === "string") {
            continue;
        }
        for (const part of msg.content) {
            if (part.type !== "reasoning") {
                continue;
            }
            const reasoningPart = part;
            if (reasoningPart.providerOptions) {
                // Already signed (e.g. from a previous step's backfill). Skip without
                // advancing the counter so the positional index stays aligned with
                // the current step's stepContent signatures.
                continue;
            }
            if (reasoningIndex < signaturesByIndex.length) {
                reasoningPart.providerOptions = signaturesByIndex[reasoningIndex];
            }
            reasoningIndex++;
        }
    }
}
/**
 * Strips reasoning content parts that lack `providerOptions` (and thus lack
 * a cryptographic `signature`).
 *
 * When the AI SDK bug (vercel/ai#13972) caused reasoning parts to be persisted
 * without valid signatures, those conversations become permanently broken:
 * every subsequent API call replays the unsigned reasoning and Anthropic
 * rejects with `thinking.signature: Field required`.
 *
 * Since the signatures are cryptographic and cannot be reconstructed after the
 * fact, the only way to unblock these conversations is to drop the unsigned
 * reasoning parts before sending messages to the model.
 *
 * Returns a new array; does not mutate the input.
 */
export function stripUnsignedReasoning(messages) {
    return messages
        .map((msg) => {
        if (msg.role !== "assistant" || typeof msg.content === "string") {
            return msg;
        }
        const filtered = msg.content.filter((part) => {
            if (part.type !== "reasoning") {
                return true;
            }
            const reasoningPart = part;
            const opts = reasoningPart.providerOptions;
            return !!(opts?.anthropic?.signature && opts?.bedrock?.signature);
        });
        if (filtered.length === msg.content.length) {
            return msg;
        }
        return { ...msg, content: filtered };
    })
        .filter((msg) => {
        if (msg.role === "assistant" &&
            Array.isArray(msg.content) &&
            msg.content.length === 0) {
            return false;
        }
        return true;
    });
}
/**
 * Repairs broken tool calls in assistant messages.
 *
 * - Ensures tool-call inputs are valid objects (not null, undefined, or arrays)
 * - Removes tool-call parts without corresponding tool-result in the next message
 *
 * This prevents errors from LLM providers like:
 * - Bedrock: "messages.X.content.Y.tool_use.input: Input should be a valid dictionary"
 * - Vertex: "tool_use ids were found without tool_result blocks immediately after"
 */
export function repairBrokenToolCalls(msg, index, messages) {
    if (msg.role !== "assistant" || typeof msg.content === "string") {
        return msg;
    }
    let content = [...msg.content];
    // Fix tool-call inputs that are not objects
    let inputsFixed = 0;
    content = content.map((part) => {
        if (part.type === "tool-call") {
            const toolCall = part;
            if (typeof toolCall.input !== "object" ||
                toolCall.input === null ||
                Array.isArray(toolCall.input)) {
                inputsFixed++;
                return { ...part, input: {} };
            }
        }
        return part;
    });
    if (inputsFixed > 0) {
        logger.debug(`Fixed ${inputsFixed} tool call(s) with non-object input at index ${index}`);
    }
    // Collect tool-result IDs from the next message
    const toolResultIds = new Set();
    const nextMsg = messages[index + 1];
    if (nextMsg &&
        (nextMsg.role === "assistant" || nextMsg.role === "tool") &&
        typeof nextMsg.content !== "string") {
        for (const part of nextMsg.content) {
            if (typeof part === "object" &&
                part !== null &&
                "type" in part &&
                part.type === "tool-result" &&
                "toolCallId" in part) {
                toolResultIds.add(part.toolCallId);
            }
        }
    }
    // Get tool-call IDs from this message
    const hasToolCalls = content.some((part) => typeof part === "object" &&
        part !== null &&
        "type" in part &&
        part.type === "tool-call");
    if (!hasToolCalls) {
        return { ...msg, content };
    }
    // Also check for tool-result in the same message (parallel tool calls)
    for (const part of content) {
        if (typeof part === "object" &&
            part !== null &&
            "type" in part &&
            part.type === "tool-result" &&
            "toolCallId" in part) {
            toolResultIds.add(part.toolCallId);
        }
    }
    // Remove orphaned tool-call parts
    const before = content.length;
    content = content.filter((part) => {
        if (typeof part === "object" &&
            part !== null &&
            "type" in part &&
            part.type === "tool-call" &&
            "toolCallId" in part) {
            return toolResultIds.has(part.toolCallId);
        }
        return true;
    });
    const removed = before - content.length;
    if (removed > 0) {
        logger.debug(`Removed ${removed} orphaned tool call(s) from assistant message at index ${index}`);
    }
    return { ...msg, content };
}
/**
 * Repairs orphaned tool results in tool messages.
 *
 * Removes tool-result parts that have no corresponding tool-call in the
 * preceding assistant message. This prevents errors from LLM providers like:
 * - Anthropic: "toolResult count exceeds toolUse count in context"
 *
 * This is the inverse of {@link repairBrokenToolCalls}, which removes orphaned
 * tool-call parts from assistant messages.
 */
export function repairOrphanedToolResults(msg, index, messages) {
    if (msg.role !== "tool" || !Array.isArray(msg.content)) {
        return msg;
    }
    // Collect tool-call IDs from the preceding assistant message
    const toolCallIds = new Set();
    const prevMsg = messages[index - 1];
    if (prevMsg &&
        prevMsg.role === "assistant" &&
        typeof prevMsg.content !== "string") {
        for (const part of prevMsg.content) {
            if (typeof part === "object" &&
                part !== null &&
                "type" in part &&
                part.type === "tool-call" &&
                "toolCallId" in part) {
                toolCallIds.add(part.toolCallId);
            }
        }
    }
    // Filter out tool-result parts without a matching tool-call
    const before = msg.content.length;
    const content = msg.content.filter((part) => {
        if (typeof part === "object" &&
            part !== null &&
            "type" in part &&
            part.type === "tool-result" &&
            "toolCallId" in part) {
            return toolCallIds.has(part.toolCallId);
        }
        return true;
    });
    const removed = before - content.length;
    if (removed > 0) {
        logger.debug(`Removed ${removed} orphaned tool result(s) from tool message at index ${index}`);
    }
    return { ...msg, content };
}
function stripUndefined(obj) {
    if (obj === null || typeof obj !== "object") {
        return obj;
    }
    if (Array.isArray(obj)) {
        return obj.map(stripUndefined);
    }
    const result = {};
    for (const [key, value] of Object.entries(obj)) {
        if (value !== undefined) {
            result[key] = stripUndefined(value);
        }
    }
    return result;
}
export function stripUndefinedFromToolOutputs(msg) {
    if (msg.role !== "tool" || !Array.isArray(msg.content)) {
        return msg;
    }
    const cleanedContent = msg.content.map((part) => {
        if (typeof part === "object" &&
            part !== null &&
            "type" in part &&
            part.type === "tool-result" &&
            "output" in part) {
            const toolResult = part;
            // Only strip undefined from the output value, not the entire tool result
            const output = toolResult.output;
            if (output && typeof output === "object" && "value" in output) {
                const typedOutput = output;
                return {
                    ...toolResult,
                    output: {
                        ...typedOutput,
                        value: stripUndefined(typedOutput.value),
                    },
                };
            }
        }
        return part;
    });
    return { ...msg, content: cleanedContent };
}
export function toSdkMessages(messages) {
    return messages.map((msg) => {
        const { _superblocks, ...sdkMessage } = msg;
        return sdkMessage;
    });
}
export function toStoredMessage(msg, metadata = {}) {
    return {
        ...msg,
        _superblocks: { timestamp: Date.now(), ...metadata },
    };
}
export function extractTokenUsage(usage) {
    return {
        input: usage.inputTokens ?? 0,
        output: usage.outputTokens ?? 0,
        cacheRead: usage.inputTokenDetails.cacheReadTokens ?? 0,
    };
}
//# sourceMappingURL=types.js.map