nemo_flow_adaptive/acg/plugin.rs
1// SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Provider plugin trait and input/output types for the Adaptive Cache Governor
5//! (ACG) system.
6//!
7//! The [`ProviderPlugin`] trait defines the contract between ACG's
8//! provider-agnostic optimization pipeline and backend-specific
9//! translation logic. Plugins receive a [`PluginInput`] containing
10//! the original request, Prompt IR, intent bundle, and agent identity,
11//! and produce a [`PluginOutput`] with the translated request and a
12//! [`TranslationReport`].
13//!
14//! # Design
15//!
16//! - **Synchronous**: `translate` is a pure data transform (JSON
17//! restructuring), not an I/O operation. This matches the `LlmCodec`
18//! pattern in `crates/core/src/codec/traits.rs`.
19//! - **Compatibility facade**: provider plugins keep their existing
20//! synchronous trait surface, but can internally split translation into
21//! semantic hint planning plus request-surface application.
22//! - **`Send + Sync`**: Required for storage as `Arc<dyn ProviderPlugin>`
23//! in concurrent contexts.
24//! - **Object-safe**: The trait is designed to be used as a trait object.
25
26use nemo_flow::api::llm::LlmRequest;
27
28use crate::acg::capability::BackendCapabilities;
29use crate::acg::prompt_ir::PromptIR;
30use crate::acg::request_surfaces::RequestSurfaceApplier;
31use crate::acg::translation::{HintPlan, HintTranslation, HintTranslator};
32use crate::acg::types::{AgentIdentity, OptimizationIntentBundle, TranslationReport};
33
34// ===================================================================
35// Plugin input / output types
36// ===================================================================
37
38/// Input data provided to a plugin for translation.
39///
40/// All fields are borrowed references to avoid unnecessary cloning.
41/// The lifetime `'a` ties the input to the caller's data.
42#[derive(Debug)]
43pub struct PluginInput<'a> {
44 /// The original (pre-rewrite) request.
45 pub original_request: &'a LlmRequest,
46 /// The rewritten request (may be identical to original in early phases).
47 pub rewritten_request: &'a LlmRequest,
48 /// The Prompt IR decomposition of the request.
49 pub prompt_ir: &'a PromptIR,
50 /// The optimization intent bundle from the policy engine.
51 pub intent_bundle: &'a OptimizationIntentBundle,
52 /// The agent identity for context.
53 pub agent_identity: &'a AgentIdentity,
54}
55
56/// Output produced by a plugin after translation.
57///
58/// Contains the translated request in the backend's native API format
59/// and a [`TranslationReport`] describing what happened to each intent.
60#[derive(Debug)]
61pub struct PluginOutput {
62 /// The final request in the backend's native API format.
63 pub translated_request: LlmRequest,
64 /// Report describing what happened to each intent.
65 pub translation_report: TranslationReport,
66}
67
68/// Internal adapter boundary used while the provider-plugin facade remains stable.
69///
70/// Translators produce surface-agnostic semantic plans. Appliers remain
71/// responsible for delegating raw request mutation to dedicated request-surface
72/// modules while the public provider-plugin facade remains stable.
73pub(crate) trait HintPlanApplier {
74 fn apply_hint_plan(
75 &self,
76 request: &LlmRequest,
77 prompt_ir: &PromptIR,
78 hint_plan: &HintPlan,
79 ) -> crate::acg::error::Result<LlmRequest>;
80}
81
82impl<T> RequestSurfaceApplier for T
83where
84 T: HintPlanApplier + Send + Sync + ?Sized,
85{
86 fn apply(
87 &self,
88 request: &LlmRequest,
89 prompt_ir: &PromptIR,
90 plan: &HintPlan,
91 ) -> crate::acg::Result<LlmRequest> {
92 self.apply_hint_plan(request, prompt_ir, plan)
93 }
94}
95
96/// Run the internal translator -> applier split behind the stable plugin facade.
97pub(crate) fn translate_with_hint_plan<T, A>(
98 translator: &T,
99 applier: &A,
100 input: &PluginInput<'_>,
101) -> crate::acg::error::Result<PluginOutput>
102where
103 T: HintTranslator,
104 A: HintPlanApplier + Send + Sync,
105{
106 let HintTranslation {
107 hint_plan,
108 translation_report,
109 } = translator.translate(input)?;
110
111 let translated_request = RequestSurfaceApplier::apply(
112 applier,
113 input.rewritten_request,
114 input.prompt_ir,
115 &hint_plan,
116 )?;
117
118 Ok(PluginOutput {
119 translated_request,
120 translation_report,
121 })
122}
123
124// ===================================================================
125// ProviderPlugin trait
126// ===================================================================
127
128/// Provider plugin trait for backend-specific translation.
129///
130/// Plugins are stateless on the forward path. They translate
131/// provider-agnostic intents into backend-native API parameters.
132///
133/// # Object Safety
134///
135/// This trait is object-safe and can be stored as `Arc<dyn ProviderPlugin>`.
136///
137/// # Thread Safety
138///
139/// The `Send + Sync` bounds allow plugins to be shared across async
140/// tasks and threads.
141pub trait ProviderPlugin: Send + Sync {
142 /// Unique identifier for this plugin (e.g., "anthropic", "openai", "passthrough").
143 fn plugin_id(&self) -> &str;
144
145 /// Human-readable name for this plugin.
146 fn plugin_name(&self) -> &str;
147
148 /// Translate intents into backend-native API parameters.
149 fn translate(&self, input: &PluginInput<'_>) -> crate::acg::error::Result<PluginOutput>;
150
151 /// Report the capabilities of the backend this plugin targets.
152 fn capabilities(&self) -> BackendCapabilities;
153}
154
155#[cfg(test)]
156#[path = "../../tests/unit/acg/plugin_tests.rs"]
157mod tests;