llm-message-hash 0.1.0

Stable canonical hash of LLM request/message structures. Recursive key-sorting JSON canonicalization + sha256, with per-provider ignore-lists so semantically-equal Anthropic/OpenAI/Bedrock requests produce the same hash. Useful for cache keys and idempotency.
Documentation
//! Hash options.

/// Tunables for [`hash_canonical_with`](crate::hash_canonical_with).
#[derive(Debug, Clone, Default)]
pub struct HashOpts {
    /// Field NAMES to drop from any object at any depth. Match is exact
    /// on the key string. Examples: `"cache_control"`, `"id"`.
    pub ignore_field_names: Vec<&'static str>,
}

impl HashOpts {
    /// New empty options.
    pub fn new() -> Self {
        Self::default()
    }

    /// Add a field name to the ignore list (chainable).
    pub fn ignore(mut self, field: &'static str) -> Self {
        self.ignore_field_names.push(field);
        self
    }

    /// Pre-configured for Anthropic Messages API. Drops:
    /// - `cache_control` (set by callers for prompt caching; doesn't change semantics)
    /// - `id` (assistant message IDs differ per call)
    /// - `usage` (token counts from the response side)
    /// - `stop_reason` / `stop_sequence` (response-side)
    pub fn anthropic() -> Self {
        Self {
            ignore_field_names: vec![
                "cache_control",
                "id",
                "usage",
                "stop_reason",
                "stop_sequence",
            ],
        }
    }

    /// Pre-configured for OpenAI Chat Completions. Drops:
    /// - `created`, `id`, `object`, `system_fingerprint` (response-side metadata)
    /// - `usage`
    /// - `finish_reason`
    pub fn openai() -> Self {
        Self {
            ignore_field_names: vec![
                "created",
                "id",
                "object",
                "system_fingerprint",
                "usage",
                "finish_reason",
            ],
        }
    }

    /// Pre-configured for AWS Bedrock Converse API. Drops:
    /// - `cache_control` (Anthropic on Bedrock supports caching)
    /// - `usage`
    /// - `stopReason` (camelCase variant)
    /// - `metrics`
    pub fn bedrock() -> Self {
        Self {
            ignore_field_names: vec!["cache_control", "usage", "stopReason", "metrics"],
        }
    }

    /// Pre-configured for Google Gemini generateContent. Drops:
    /// - `usageMetadata`
    /// - `safetyRatings` (response-side, varies)
    /// - `finishReason`
    pub fn gemini() -> Self {
        Self {
            ignore_field_names: vec!["usageMetadata", "safetyRatings", "finishReason"],
        }
    }
}