nemo_flow_adaptive/acg/anthropic_plugin.rs
1// SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Anthropic cache plugin for the Adaptive Cache Governor (ACG) system.
5//!
6//! Translates ACG stability classifications into Anthropic-specific
7//! `cache_control` breakpoints on content blocks. Implements the
8//! [`ProviderPlugin`] trait with:
9//!
10//! - **Breakpoint budget allocation**: Up to 4 breakpoints per request.
11//! - **Token minimum enforcement**: Per-model from [`CapabilityRegistry`].
12//! - **TTL mapping**: `RetentionTier` to Anthropic TTL (5m default or 1h extended).
13//! - **Tool schema canonicalization**: RFC 8785 via [`canonicalize_value`].
14//! - **System string-to-array conversion**: Handles both string and array-of-blocks format.
15//!
16//! # Threat mitigations
17//!
18//! - T-08-01: Only injects `cache_control` annotations with fixed structure `{"type": "ephemeral"}`.
19//! - T-08-02: `scope_label` is logged in detail but does NOT change cache visibility.
20//! - T-08-03: All JSON access uses Option-returning methods; errors propagate via `Result`.
21//! - T-08-05: TTL is hardcoded to exactly 2 behaviors (omit or `"1h"`).
22
23use crate::acg::capability::{BackendCapabilities, CapabilityRegistry};
24use crate::acg::plugin::{
25 HintPlanApplier, PluginInput, PluginOutput, ProviderPlugin, translate_with_hint_plan,
26};
27use crate::acg::prompt_ir::PromptIR;
28use crate::acg::translation::anthropic::AnthropicHintTranslator;
29use crate::acg::translation::{HintPlan, HintTranslation, HintTranslator};
30
31// ===================================================================
32// AnthropicCachePlugin
33// ===================================================================
34
35/// Anthropic-specific provider plugin for cache_control breakpoint injection.
36///
37/// Translates `CacheStability` and `Retention` intents into Anthropic's
38/// explicit `cache_control` annotations. Other intent types are marked
39/// `Ignored` / `NotRelevant`.
40///
41/// # Construction
42///
43/// ```rust,ignore
44/// let registry = CapabilityRegistry::with_defaults();
45/// let plugin = AnthropicCachePlugin::new(®istry);
46/// ```
47pub struct AnthropicCachePlugin {
48 translator: AnthropicHintTranslator,
49 capabilities: BackendCapabilities,
50}
51
52impl AnthropicCachePlugin {
53 /// Create a new Anthropic cache plugin backed by the given capability registry.
54 ///
55 /// The registry is cloned into an `Arc` for shared ownership.
56 pub fn new(registry: &CapabilityRegistry) -> Self {
57 Self {
58 translator: AnthropicHintTranslator::new(registry),
59 capabilities: registry
60 .get_backend("anthropic")
61 .cloned()
62 .unwrap_or_else(|| BackendCapabilities::none("anthropic")),
63 }
64 }
65
66 #[cfg_attr(not(test), allow(dead_code))]
67 pub(crate) fn build_hint_translation(
68 &self,
69 input: &PluginInput<'_>,
70 ) -> crate::acg::error::Result<HintTranslation> {
71 self.translator.translate(input)
72 }
73}
74
75impl ProviderPlugin for AnthropicCachePlugin {
76 fn plugin_id(&self) -> &str {
77 "anthropic"
78 }
79
80 fn plugin_name(&self) -> &str {
81 "Anthropic Cache Plugin"
82 }
83
84 fn translate(&self, input: &PluginInput<'_>) -> crate::acg::error::Result<PluginOutput> {
85 translate_with_hint_plan(&self.translator, self, input)
86 }
87
88 fn capabilities(&self) -> BackendCapabilities {
89 self.capabilities.clone()
90 }
91}
92
93impl HintPlanApplier for AnthropicCachePlugin {
94 fn apply_hint_plan(
95 &self,
96 request: &nemo_flow::api::llm::LlmRequest,
97 prompt_ir: &PromptIR,
98 hint_plan: &HintPlan,
99 ) -> crate::acg::error::Result<nemo_flow::api::llm::LlmRequest> {
100 crate::acg::request_surfaces::apply_request_surface(
101 self.plugin_id(),
102 request,
103 prompt_ir,
104 hint_plan,
105 )
106 }
107}
108
109#[cfg(test)]
110#[path = "../../tests/unit/acg/anthropic_plugin_tests.rs"]
111mod tests;