import { convertAnthropicUsageToInputTokenDetails } from "./usage.js";
import { isRecord } from "./types.js";
function extractTraceableServiceTier(providerMetadata) {
    if (providerMetadata?.openai != null &&
        typeof providerMetadata.openai === "object") {
        const openai = providerMetadata.openai;
        if (openai.serviceTier != null &&
            typeof openai.serviceTier === "string" &&
            ["priority", "flex"].includes(openai.serviceTier)) {
            return openai.serviceTier;
        }
    }
    return undefined;
}
function isLanguageModelV3Usage(usage) {
    return usage.inputTokens != null && typeof usage.inputTokens === "object";
}
function isTelemetryLanguageModelUsage(usage) {
    if (!isRecord(usage))
        return false;
    return ((usage.inputTokens == null || typeof usage.inputTokens === "number") &&
        isRecord(usage.inputTokenDetails) &&
        (usage.outputTokens == null || typeof usage.outputTokens === "number") &&
        isRecord(usage.outputTokenDetails));
}
function extractAISDK6OutputTokenDetails(usage, providerMetadata) {
    const openAIServiceTier = extractTraceableServiceTier(providerMetadata ?? {});
    const outputTokenDetailsKeyPrefix = openAIServiceTier
        ? `${openAIServiceTier}_`
        : "";
    const outputTokens = usage.outputTokens;
    const outputTokenDetails = {};
    // Extract reasoning tokens from AI SDK 6
    if (typeof outputTokens?.reasoning === "number" &&
        outputTokens?.reasoning > 0) {
        outputTokenDetails[`${outputTokenDetailsKeyPrefix}reasoning`] =
            outputTokens.reasoning;
    }
    // Apply service tier logic for AI SDK 6
    if (openAIServiceTier && typeof outputTokens?.total === "number") {
        // Avoid counting reasoning tokens towards the output token count
        // since service tier tokens are already priced differently
        outputTokenDetails[openAIServiceTier] =
            outputTokens.total -
                (outputTokenDetails[`${outputTokenDetailsKeyPrefix}reasoning`] ?? 0);
    }
    return outputTokenDetails;
}
// TODO: verify service tier token counting
function extractTelemetryOutputTokenDetails(usage, providerMetadata) {
    const result = {};
    const serviceTier = extractTraceableServiceTier(providerMetadata ?? {});
    const prefix = serviceTier ? `${serviceTier}_` : "";
    const reasoning = usage.outputTokenDetails?.reasoningTokens ?? undefined;
    if (typeof reasoning === "number" && reasoning > 0) {
        const key = `${prefix}reasoning`;
        result[key] = reasoning;
    }
    // Apply service tier logic
    if (serviceTier != null) {
        // Avoid counting reasoning tokens towards the output token count
        // since service tier tokens are already priced differently
        result[serviceTier] = (usage.outputTokens ?? 0) - (reasoning ?? 0);
    }
    return result;
}
export function extractOutputTokenDetails(usage, providerMetadata) {
    if (usage == null) {
        return {};
    }
    // AI SDK 7: Use Telemetry Language Model Usage
    if (isTelemetryLanguageModelUsage(usage)) {
        return extractTelemetryOutputTokenDetails(usage, providerMetadata);
    }
    // AI SDK 6: Check for built-in outputTokens breakdown first
    if (isLanguageModelV3Usage(usage)) {
        // Return AI SDK 6 results (even if empty, to prevent falling through to SDK 5 logic)
        return extractAISDK6OutputTokenDetails(usage, providerMetadata);
    }
    // AI SDK <= 5
    const openAIServiceTier = extractTraceableServiceTier(providerMetadata ?? {});
    const prefix = openAIServiceTier ? `${openAIServiceTier}_` : "";
    const result = {};
    if (typeof usage?.reasoningTokens === "number") {
        result[`${prefix}reasoning`] = usage.reasoningTokens;
    }
    if (openAIServiceTier && typeof usage?.outputTokens === "number") {
        // Avoid counting reasoning tokens towards the output token count
        // since service tier tokens are already priced differently
        result[openAIServiceTier] =
            usage.outputTokens - (result[`${prefix}reasoning`] ?? 0);
    }
    return result;
}
function extractAISDK6InputTokenDetails(usage, providerMetadata) {
    let inputTokenDetails = {};
    const inputTokens = usage.inputTokens;
    // Extract standard AI SDK 6 input token breakdowns
    // Map AI SDK 6 fields to LangSmith token detail fields:
    // - cacheRead -> cache_read
    // - cacheWrite -> cache_creation
    if (providerMetadata?.anthropic != null &&
        typeof providerMetadata?.anthropic === "object") {
        const anthropic = providerMetadata.anthropic;
        if (anthropic.usage != null && typeof anthropic.usage === "object") {
            // Raw usage from Anthropic returned in AI SDK 5
            const usage = anthropic.usage;
            inputTokenDetails = convertAnthropicUsageToInputTokenDetails(usage);
        }
    }
    else {
        if (typeof inputTokens?.cacheRead === "number" &&
            inputTokens.cacheRead > 0) {
            inputTokenDetails.cache_read = inputTokens.cacheRead;
        }
        if (typeof inputTokens?.cacheWrite === "number" &&
            inputTokens?.cacheWrite > 0) {
            inputTokenDetails.cache_creation = inputTokens?.cacheWrite;
        }
    }
    // Handle OpenAI service tier for AI SDK 6
    const openAIServiceTier = extractTraceableServiceTier(providerMetadata ?? {});
    if (openAIServiceTier) {
        const serviceTierPrefix = `${openAIServiceTier}_`;
        // Add cache_read with service tier prefix if we have cached tokens
        if (typeof inputTokens?.cacheRead === "number" &&
            inputTokens?.cacheRead > 0) {
            inputTokenDetails[`${serviceTierPrefix}cache_read`] =
                inputTokens.cacheRead;
            // Remove the non-prefixed version since we're using service tier
            delete inputTokenDetails.cache_read;
        }
        // Calculate service tier tokens (total minus cached)
        if (typeof inputTokens?.total === "number") {
            inputTokenDetails[openAIServiceTier] =
                inputTokens.total -
                    (inputTokenDetails[`${serviceTierPrefix}cache_read`] ?? 0);
        }
    }
    return inputTokenDetails;
}
function extractTelemetryInputTokenDetails(usage, providerMetadata) {
    const result = {};
    const cacheRead = usage.inputTokenDetails?.cacheReadTokens ?? undefined;
    const cacheWrite = usage.inputTokenDetails?.cacheWriteTokens ?? undefined;
    if (typeof cacheRead === "number" && cacheRead > 0) {
        result.cache_read = cacheRead;
    }
    if (typeof cacheWrite === "number" && cacheWrite > 0) {
        result.cache_creation = cacheWrite;
    }
    const openAIServiceTier = extractTraceableServiceTier(providerMetadata ?? {});
    const prefix = openAIServiceTier
        ? `${openAIServiceTier}_`
        : "";
    if (openAIServiceTier && typeof usage.inputTokens === "number") {
        if (result.cache_read != null) {
            result[`${prefix}cache_read`] = result.cache_read;
            delete result.cache_read;
        }
        if (typeof usage?.inputTokens === "number") {
            result[openAIServiceTier] =
                usage.inputTokens - (result[`${prefix}cache_read`] ?? 0);
        }
    }
    return result;
}
export function extractInputTokenDetails(usage, providerMetadata) {
    if (usage == null) {
        return {};
    }
    // AI SDK 7: Use Telemetry Language Model Usage
    if (isTelemetryLanguageModelUsage(usage)) {
        return extractTelemetryInputTokenDetails(usage, providerMetadata);
    }
    // AI SDK 6: Check for built-in inputTokens breakdown first
    if (isLanguageModelV3Usage(usage)) {
        // Return AI SDK 6 results (even if empty, to prevent falling through to SDK 5 logic)
        return extractAISDK6InputTokenDetails(usage, providerMetadata);
    }
    let inputTokenDetails = {};
    if (providerMetadata?.anthropic != null &&
        typeof providerMetadata?.anthropic === "object") {
        const anthropic = providerMetadata.anthropic;
        if (anthropic.usage != null && typeof anthropic.usage === "object") {
            // Raw usage from Anthropic returned in AI SDK 5
            const usage = anthropic.usage;
            inputTokenDetails = convertAnthropicUsageToInputTokenDetails(usage);
        }
        else {
            // AI SDK 4 fields
            if (anthropic.cacheReadInputTokens != null &&
                typeof anthropic.cacheReadInputTokens === "number") {
                inputTokenDetails.cache_read = anthropic.cacheReadInputTokens;
            }
            if (anthropic.cacheCreationInputTokens != null &&
                typeof anthropic.cacheCreationInputTokens === "number") {
                inputTokenDetails.ephemeral_5m_input_tokens =
                    anthropic.cacheCreationInputTokens;
            }
        }
        return inputTokenDetails;
    }
    else if (providerMetadata?.openai != null &&
        typeof providerMetadata?.openai === "object") {
        const openAIServiceTier = extractTraceableServiceTier(providerMetadata ?? {});
        const outputTokenDetailsKeyPrefix = openAIServiceTier
            ? `${openAIServiceTier}_`
            : "";
        if (typeof usage?.cachedInputTokens === "number") {
            inputTokenDetails[`${outputTokenDetailsKeyPrefix}cache_read`] =
                usage.cachedInputTokens;
        }
        else if ("cachedPromptTokens" in providerMetadata.openai &&
            providerMetadata.openai.cachedPromptTokens != null &&
            typeof providerMetadata.openai.cachedPromptTokens === "number") {
            inputTokenDetails[`${outputTokenDetailsKeyPrefix}cache_read`] =
                providerMetadata.openai.cachedPromptTokens;
        }
        if (openAIServiceTier && typeof usage?.inputTokens === "number") {
            // Avoid counting cached input tokens towards the input token count
            // since service tier tokens are already priced differently
            inputTokenDetails[openAIServiceTier] =
                usage.inputTokens -
                    (inputTokenDetails[`${outputTokenDetailsKeyPrefix}cache_read`] ?? 0);
        }
    }
    return inputTokenDetails;
}
export function extractUsageMetadata(span) {
    const isError = span?.status?.code === 2;
    if (isError || !span || !span.attributes) {
        return {
            input_tokens: 0,
            output_tokens: 0,
            total_tokens: 0,
        };
    }
    const usageMetadata = {
        input_tokens: 0,
        output_tokens: 0,
        total_tokens: 0,
    };
    if (typeof span.attributes["ai.usage.promptTokens"] === "number" ||
        typeof span.attributes["ai.usage.inputTokens"] === "number") {
        usageMetadata.input_tokens =
            span.attributes["ai.usage.promptTokens"] ??
                span.attributes["ai.usage.inputTokens"];
    }
    if (typeof span.attributes["ai.usage.completionTokens"] === "number" ||
        typeof span.attributes["ai.usage.outputTokens"] === "number") {
        usageMetadata.output_tokens =
            span.attributes["ai.usage.completionTokens"] ??
                span.attributes["ai.usage.outputTokens"];
    }
    if (typeof span.attributes["ai.response.providerMetadata"] === "string") {
        try {
            const providerMetadata = JSON.parse(span.attributes["ai.response.providerMetadata"]);
            usageMetadata.input_token_details = extractInputTokenDetails(typeof span.attributes["ai.usage.cachedInputTokens"] === "number"
                ? { cachedInputTokens: span.attributes["ai.usage.cachedInputTokens"] }
                : undefined, providerMetadata);
            if (providerMetadata.anthropic != null &&
                typeof providerMetadata.anthropic === "object") {
                // AI SDK does not include Anthropic cache tokens in their stated input token
                // numbers, so we need to add them manually
                for (const key in usageMetadata.input_token_details) {
                    usageMetadata.input_tokens += usageMetadata.input_token_details[key];
                }
            }
        }
        catch {
            // pass
        }
    }
    usageMetadata.total_tokens =
        usageMetadata.input_tokens + usageMetadata.output_tokens;
    return usageMetadata;
}
