Skip to main content

vtcode_core/
components.rs

1//! Context-Generic Programming (CGP) substrate for VT Code
2//!
3//! Applies the provider-trait pattern from CGP to decouple tool runtime
4//! composition from coherence restrictions. Instead of relying on blanket
5//! impls or adapter newtypes, each capability (approval, sandboxing,
6//! execution, metadata, etc.) is expressed as a **provider trait** with an
7//! explicit `Context` parameter. A lightweight wiring step maps named
8//! **components** to concrete **providers** per context type.
9//!
10//! # Key ideas (from the RustLab 2025 CGP talk)
11//!
12//! 1. **Provider traits** move `Self` to an explicit generic parameter,
13//!    bypassing Rust's coherence/orphan restrictions.
14//! 2. **Component names** are zero-sized marker types that act as keys in a
15//!    type-level lookup table.
16//! 3. **`delegate_components!`** wires component names to provider types for
17//!    a given context, producing `HasComponent` implementations.
18//! 4. Consumer code depends only on the component name, not the concrete
19//!    provider, enabling the same tool/request to run under different
20//!    policies by simply switching the context.
21//!
22//! ## Dictionary-passing interpretation
23//!
24//! This module intentionally mirrors dictionary-passing style for Rust traits.
25//! `HasComponent<Name>::Provider` is the elaborated "dictionary" selected for a
26//! capability, while the blanket consumer impls (`CanApproveTool`,
27//! `CanExecuteTool`, etc.) are the point where the compiler proves that the
28//! selected provider implements the required provider trait for the current
29//! context. That keeps the capability wiring explicit and avoids depending on
30//! deeper associated-type reasoning at call sites.
31//!
32//! # Example
33//!
34//! ```rust,ignore
35//! use vtcode_core::components::*;
36//!
37//! // Define a context for interactive sessions
38//! struct InteractiveCtx { /* ... */ }
39//!
40//! // Wire components to providers
41//! delegate_components!(InteractiveCtx {
42//!     ApprovalComponent  => PromptApproval,
43//!     SandboxComponent   => WorkspaceSandbox,
44//!     ExecuteComponent   => DefaultExecutor,
45//! });
46//!
47//! // Define a CI context with different providers
48//! struct CiCtx { /* ... */ }
49//!
50//! delegate_components!(CiCtx {
51//!     ApprovalComponent  => AutoApproval,
52//!     SandboxComponent   => StrictSandbox,
53//!     ExecuteComponent   => DefaultExecutor,
54//! });
55//! ```
56
57use std::borrow::Cow;
58use std::fmt::Write as _;
59use std::hash::Hasher;
60use std::marker::PhantomData;
61use std::path::PathBuf;
62use std::sync::Arc;
63use std::time::{Duration, Instant};
64
65use anyhow::{Error, Result};
66use async_trait::async_trait;
67use serde_json::Value;
68use vtcode_commons::serde_helpers::json_to_string_pretty;
69
70use crate::cache::{CacheKey, UnifiedCache, estimate_json_size};
71use crate::tool_policy::ToolPolicy;
72use crate::tools::handlers::tool_handler::{
73    ToolCallError, ToolHandler, ToolInvocation, ToolKind, ToolOutput, ToolPayload,
74};
75use crate::tools::result::ToolResult as SplitToolResult;
76use crate::tools::traits::Tool;
77
78const MAX_RETRY_ATTEMPTS: u32 = 16;
79const MAX_RETRY_BACKOFF: Duration = Duration::from_secs(30);
80
81// ============================================================================
82// Core wiring trait
83// ============================================================================
84
85/// Type-level lookup: maps a component **Name** to a concrete **Provider**
86/// type for a given implementor (the "context").
87///
88/// This is the single foundational trait of the CGP substrate. All
89/// composition flows through it.
90pub trait HasComponent<Name> {
91    /// The concrete provider type wired to `Name` for this context.
92    type Provider;
93}
94
95/// The elaborated provider/dictionary selected by `Ctx` for component `Name`.
96pub type ComponentProvider<Ctx, Name> = <Ctx as HasComponent<Name>>::Provider;
97
98/// Wire multiple component names to provider types for a context.
99///
100/// Generates one `HasComponent<Name>` implementation per entry.
101///
102/// ```rust,ignore
103/// delegate_components!(MyCtx {
104///     ApprovalComponent => PromptApproval,
105///     SandboxComponent  => WorkspaceSandbox,
106/// });
107/// ```
108#[macro_export]
109macro_rules! delegate_components {
110    ($ctx:ty { $($name:ty => $provider:ty),* $(,)? }) => {
111        $(
112            impl $crate::components::HasComponent<$name> for $ctx {
113                type Provider = $provider;
114            }
115        )*
116    };
117}
118
119// ============================================================================
120// Component name markers
121// ============================================================================
122
123/// Component for approval/permission checks before tool execution.
124pub enum ApprovalComponent {}
125
126/// Component for sandbox policy selection and enforcement.
127pub enum SandboxComponent {}
128
129/// Component for the core tool execution logic.
130pub enum ExecuteComponent {}
131
132/// Component for tool metadata (name, description, schemas).
133pub enum MetadataComponent {}
134
135/// Component for session/turn context creation.
136pub enum SessionComponent {}
137
138/// Component for mapping between output formats (JSON ↔ dual-channel, etc.).
139pub enum OutputMapComponent {}
140
141/// Component for execution logging and telemetry.
142pub enum LoggingComponent {}
143
144/// Component for cached tool results.
145pub enum CacheComponent {}
146
147/// Component for retry policy around tool execution.
148pub enum RetryComponent {}
149
150// ============================================================================
151// Provider traits (explicit Context parameter — the CGP "provider" pattern)
152// ============================================================================
153
154/// Provider trait for approval/permission checks.
155///
156/// Moves the traditional `Self` type to an explicit `Ctx` parameter so that
157/// multiple overlapping implementations can coexist (e.g., prompt-based,
158/// auto-approve, session-cached).
159#[async_trait]
160pub trait ApprovalProvider<Ctx: Send + Sync>: Send + Sync {
161    /// Check whether the operation described by `description` is approved
162    /// in the given context.
163    async fn check_approval(ctx: &Ctx, tool_name: &str, description: &str) -> Result<()>;
164}
165
166/// Provider trait for sandbox policy resolution.
167pub trait SandboxProvider<Ctx: Send + Sync>: Send + Sync {
168    /// Resolve the sandbox policy for the given context.
169    fn sandbox_enabled(ctx: &Ctx) -> bool;
170
171    /// Get the workspace root enforced by the sandbox.
172    fn workspace_root(ctx: &Ctx) -> Option<&PathBuf>;
173}
174
175/// Provider trait for tool metadata.
176pub trait MetadataProvider<Ctx>: Send + Sync {
177    fn tool_name(_ctx: &Ctx) -> &str {
178        "unknown"
179    }
180
181    fn tool_description(_ctx: &Ctx) -> &str {
182        ""
183    }
184
185    fn parameter_schema(_ctx: &Ctx) -> Option<Value> {
186        None
187    }
188
189    fn config_schema(_ctx: &Ctx) -> Option<Value> {
190        None
191    }
192
193    fn state_schema(_ctx: &Ctx) -> Option<Value> {
194        None
195    }
196
197    fn prompt_path(_ctx: &Ctx) -> Option<Cow<'static, str>> {
198        None
199    }
200
201    fn default_permission(_ctx: &Ctx) -> ToolPolicy {
202        ToolPolicy::Prompt
203    }
204
205    fn allow_patterns(_ctx: &Ctx) -> Option<&'static [&'static str]> {
206        None
207    }
208
209    fn deny_patterns(_ctx: &Ctx) -> Option<&'static [&'static str]> {
210        None
211    }
212
213    fn is_mutating(_ctx: &Ctx) -> bool {
214        true
215    }
216
217    fn is_parallel_safe(ctx: &Ctx) -> bool {
218        !Self::is_mutating(ctx)
219    }
220
221    fn tool_kind(_ctx: &Ctx) -> &'static str {
222        "unknown"
223    }
224
225    fn resource_hints(_ctx: &Ctx, _args: &Value) -> Vec<String> {
226        Vec::new()
227    }
228
229    fn execution_cost(_ctx: &Ctx) -> u8 {
230        5
231    }
232}
233
234/// Provider trait for output format mapping.
235pub trait OutputMapProvider<Ctx>: Send + Sync {
236    type Input;
237    type Output;
238
239    fn map_output(ctx: &Ctx, input: Self::Input) -> Self::Output;
240}
241
242/// Provider trait for tool execution (the core "handle" operation).
243///
244/// Separates execution logic from metadata, approval, and output mapping
245/// so the same executor can be projected through both `Tool` and `ToolHandler`
246/// facades without bidirectional adapters.
247#[async_trait]
248pub trait ExecuteProvider<Ctx: Send + Sync>: Send + Sync {
249    /// Execute a tool with JSON arguments and return JSON output.
250    async fn execute(ctx: &Ctx, args: Value) -> Result<Value>;
251
252    /// Execute with dual-channel output (LLM summary + UI content).
253    ///
254    /// Default delegates to `execute()` for backward compatibility.
255    async fn execute_dual(ctx: &Ctx, args: Value) -> Result<SplitToolResult> {
256        let result = Self::execute(ctx, args).await?;
257        let name = if let Some(n) = result.get("tool_name").and_then(|v| v.as_str()) {
258            n.to_string()
259        } else {
260            "unknown".to_string()
261        };
262        let content = value_to_text(&result);
263        Ok(SplitToolResult::simple(&name, content))
264    }
265}
266
267/// Provider trait for execution logging and telemetry.
268pub trait LoggingProvider<Ctx>: Send + Sync {
269    fn on_start(_ctx: &Ctx, _tool_name: &str, _args: &Value) {}
270
271    fn on_cache_hit(_ctx: &Ctx, _tool_name: &str, _args: &Value) {}
272
273    fn on_success(
274        _ctx: &Ctx,
275        _tool_name: &str,
276        _duration: Duration,
277        _attempt: u32,
278        _from_cache: bool,
279    ) {
280    }
281
282    fn on_retry(
283        _ctx: &Ctx,
284        _tool_name: &str,
285        _next_attempt: u32,
286        _backoff: Duration,
287        _error: &Error,
288    ) {
289    }
290
291    fn on_failure(
292        _ctx: &Ctx,
293        _tool_name: &str,
294        _duration: Duration,
295        _attempt: u32,
296        _error: &Error,
297    ) {
298    }
299}
300
301/// Provider trait for cached tool results.
302pub trait CacheProvider<Ctx>: Send + Sync {
303    fn get_json(_ctx: &Ctx, _tool_name: &str, _args: &Value) -> Option<Value> {
304        None
305    }
306
307    fn put_json(_ctx: &Ctx, _tool_name: &str, _args: &Value, _result: &Value) {}
308
309    fn get_dual(_ctx: &Ctx, _tool_name: &str, _args: &Value) -> Option<SplitToolResult> {
310        None
311    }
312
313    fn put_dual(_ctx: &Ctx, _tool_name: &str, _args: &Value, _result: &SplitToolResult) {}
314}
315
316/// Provider trait for retry behavior around tool execution.
317pub trait RetryProvider<Ctx>: Send + Sync {
318    fn max_attempts(_ctx: &Ctx, _tool_name: &str, _args: &Value) -> u32 {
319        1
320    }
321
322    fn should_retry(_ctx: &Ctx, _tool_name: &str, _attempt: u32, _error: &Error) -> bool {
323        false
324    }
325
326    fn backoff_duration(_ctx: &Ctx, _tool_name: &str, _attempt: u32) -> Duration {
327        Duration::ZERO
328    }
329}
330
331// ============================================================================
332// Named provider implementations (CGP "named providers")
333// ============================================================================
334
335/// Always-approve provider for CI/test contexts.
336pub struct AutoApproval;
337
338#[async_trait]
339impl<Ctx: Send + Sync> ApprovalProvider<Ctx> for AutoApproval {
340    async fn check_approval(_ctx: &Ctx, _tool_name: &str, _description: &str) -> Result<()> {
341        Ok(())
342    }
343}
344
345/// Provider that denies all operations (useful for read-only/audit contexts).
346pub struct DenyAllApproval;
347
348#[async_trait]
349impl<Ctx: Send + Sync> ApprovalProvider<Ctx> for DenyAllApproval {
350    async fn check_approval(_ctx: &Ctx, tool_name: &str, _description: &str) -> Result<()> {
351        anyhow::bail!("operation denied: {tool_name} is not permitted in this context")
352    }
353}
354
355/// No-sandbox provider for trusted/test contexts.
356pub struct NoSandbox;
357
358#[async_trait]
359impl<Ctx: Send + Sync> SandboxProvider<Ctx> for NoSandbox {
360    fn sandbox_enabled(_ctx: &Ctx) -> bool {
361        false
362    }
363
364    fn workspace_root(_ctx: &Ctx) -> Option<&PathBuf> {
365        None
366    }
367}
368
369/// Default metadata provider for contexts that do not customize tool metadata.
370pub struct DefaultMetadata;
371
372impl<Ctx> MetadataProvider<Ctx> for DefaultMetadata {}
373
374/// No-op logging provider for contexts that don't need telemetry.
375pub struct NoLogging;
376
377impl<Ctx> LoggingProvider<Ctx> for NoLogging {}
378
379/// No-op cache provider for contexts that should always execute directly.
380pub struct NoCache;
381
382impl<Ctx> CacheProvider<Ctx> for NoCache {}
383
384/// No-op retry provider for contexts that should fail fast.
385pub struct NoRetry;
386
387impl<Ctx> RetryProvider<Ctx> for NoRetry {}
388
389/// Logging provider that traces tool execution lifecycle.
390pub struct TracingLogging;
391
392impl<Ctx> LoggingProvider<Ctx> for TracingLogging {
393    fn on_start(_ctx: &Ctx, tool_name: &str, _args: &Value) {
394        tracing::trace!(tool = %tool_name, "CGP tool execution started");
395    }
396
397    fn on_cache_hit(_ctx: &Ctx, tool_name: &str, _args: &Value) {
398        tracing::debug!(tool = %tool_name, "CGP tool result served from cache");
399    }
400
401    fn on_success(_ctx: &Ctx, tool_name: &str, duration: Duration, attempt: u32, from_cache: bool) {
402        tracing::trace!(
403            tool = %tool_name,
404            duration_ms = duration.as_millis() as u64,
405            attempt,
406            from_cache,
407            "CGP tool execution succeeded"
408        );
409    }
410
411    fn on_retry(_ctx: &Ctx, tool_name: &str, next_attempt: u32, backoff: Duration, error: &Error) {
412        tracing::debug!(
413            tool = %tool_name,
414            next_attempt,
415            backoff_ms = backoff.as_millis() as u64,
416            error = %error,
417            "CGP tool execution retry scheduled"
418        );
419    }
420
421    fn on_failure(_ctx: &Ctx, tool_name: &str, duration: Duration, attempt: u32, error: &Error) {
422        tracing::warn!(
423            tool = %tool_name,
424            duration_ms = duration.as_millis() as u64,
425            attempt,
426            error = %error,
427            "CGP tool execution failed"
428        );
429    }
430}
431
432/// Retry policy shared by CGP retry providers.
433#[derive(Debug, Clone, Copy)]
434pub struct RetryPolicy {
435    pub max_attempts: u32,
436    pub initial_backoff: Duration,
437    pub max_backoff: Duration,
438}
439
440impl Default for RetryPolicy {
441    fn default() -> Self {
442        Self {
443            max_attempts: 3,
444            initial_backoff: Duration::from_millis(100),
445            max_backoff: Duration::from_secs(1),
446        }
447    }
448}
449
450/// Trait for contexts that expose retry configuration.
451pub trait HasRetryPolicy: Send + Sync {
452    fn retry_policy(&self) -> RetryPolicy;
453}
454
455/// Exponential-backoff retry provider.
456///
457/// This preserves the legacy async middleware behavior of retrying failed
458/// executions according to the context's retry policy.
459pub struct ExponentialBackoffRetry;
460
461impl<Ctx: HasRetryPolicy> RetryProvider<Ctx> for ExponentialBackoffRetry {
462    fn max_attempts(ctx: &Ctx, _tool_name: &str, _args: &Value) -> u32 {
463        ctx.retry_policy().max_attempts.max(1)
464    }
465
466    fn should_retry(_ctx: &Ctx, _tool_name: &str, _attempt: u32, _error: &Error) -> bool {
467        true
468    }
469
470    fn backoff_duration(ctx: &Ctx, _tool_name: &str, attempt: u32) -> Duration {
471        let policy = ctx.retry_policy();
472        let exponent = attempt.saturating_sub(1).min(31);
473        let factor = 2_u64.saturating_pow(exponent);
474        let millis = policy.initial_backoff.as_millis() as u64;
475        Duration::from_millis(millis.saturating_mul(factor)).min(policy.max_backoff)
476    }
477}
478
479/// Cache key for tool execution results.
480#[derive(Debug, Clone, Hash, PartialEq, Eq)]
481pub struct ToolExecutionCacheKey(String);
482
483impl CacheKey for ToolExecutionCacheKey {
484    fn to_cache_key(&self) -> String {
485        self.0.clone()
486    }
487}
488
489/// Trait for contexts that expose JSON and dual-result caches.
490pub trait HasExecutionCaches: Send + Sync {
491    fn json_cache(&self) -> &UnifiedCache<ToolExecutionCacheKey, Value>;
492    fn dual_cache(&self) -> &UnifiedCache<ToolExecutionCacheKey, SplitToolResult>;
493}
494
495/// Recursively hash a `serde_json::Value` tree without allocating a temporary
496/// String.  Type-discriminant bytes prevent cross-type collisions (e.g. the
497/// string `"true"` vs the boolean `true`).
498fn hash_json_value<H: Hasher>(hasher: &mut H, value: &Value) {
499    match value {
500        Value::Null => hasher.write_u8(0x00),
501        Value::Bool(b) => {
502            hasher.write_u8(0x01);
503            hasher.write_u8(u8::from(*b));
504        }
505        Value::Number(n) => {
506            hasher.write_u8(0x02);
507            let s = n.to_string();
508            hasher.write(s.as_bytes());
509        }
510        Value::String(s) => {
511            hasher.write_u8(0x03);
512            hasher.write(s.as_bytes());
513        }
514        Value::Array(arr) => {
515            hasher.write_u8(0x04);
516            hasher.write_usize(arr.len());
517            for item in arr {
518                hash_json_value(hasher, item);
519            }
520        }
521        Value::Object(map) => {
522            hasher.write_u8(0x05);
523            hasher.write_usize(map.len());
524            // Sort keys for deterministic hashing regardless of insertion order.
525            let mut keys: Vec<&String> = map.keys().collect();
526            keys.sort();
527            for k in keys {
528                hasher.write(k.as_bytes());
529                hash_json_value(hasher, map.get(k).unwrap());
530            }
531        }
532    }
533}
534
535fn build_execution_cache_key(tool_name: &str, args: &Value) -> ToolExecutionCacheKey {
536    let mut hasher = std::collections::hash_map::DefaultHasher::new();
537    hasher.write(tool_name.as_bytes());
538    hash_json_value(&mut hasher, args);
539
540    let mut cache_key = String::with_capacity(tool_name.len() + 22);
541    cache_key.push_str(tool_name);
542    cache_key.push_str("::");
543    let _ = write!(&mut cache_key, "{}", hasher.finish());
544
545    ToolExecutionCacheKey(cache_key)
546}
547
548/// Cache provider backed by `UnifiedCache`.
549pub struct CachedResults;
550
551impl<Ctx: HasExecutionCaches> CacheProvider<Ctx> for CachedResults {
552    fn get_json(ctx: &Ctx, tool_name: &str, args: &Value) -> Option<Value> {
553        ctx.json_cache()
554            .get_owned(&build_execution_cache_key(tool_name, args))
555    }
556
557    fn put_json(ctx: &Ctx, tool_name: &str, args: &Value, result: &Value) {
558        let key = build_execution_cache_key(tool_name, args);
559        let size = estimate_json_size(result);
560        ctx.json_cache().insert(key, result.clone(), size);
561    }
562
563    fn get_dual(ctx: &Ctx, tool_name: &str, args: &Value) -> Option<SplitToolResult> {
564        ctx.dual_cache()
565            .get_owned(&build_execution_cache_key(tool_name, args))
566    }
567
568    fn put_dual(ctx: &Ctx, tool_name: &str, args: &Value, result: &SplitToolResult) {
569        let key = build_execution_cache_key(tool_name, args);
570        let size = (result.llm_content.len() + result.ui_content.len()) as u64;
571        ctx.dual_cache().insert(key, result.clone(), size);
572    }
573}
574
575/// Metadata provider that delegates to an inner `Tool`.
576pub struct PassthroughMetadata;
577
578impl<Ctx: HasToolRef> MetadataProvider<Ctx> for PassthroughMetadata {
579    fn tool_name(ctx: &Ctx) -> &str {
580        ctx.tool().name()
581    }
582
583    fn tool_description(ctx: &Ctx) -> &str {
584        ctx.tool().description()
585    }
586
587    fn parameter_schema(ctx: &Ctx) -> Option<Value> {
588        ctx.tool().parameter_schema()
589    }
590
591    fn config_schema(ctx: &Ctx) -> Option<Value> {
592        ctx.tool().config_schema()
593    }
594
595    fn state_schema(ctx: &Ctx) -> Option<Value> {
596        ctx.tool().state_schema()
597    }
598
599    fn prompt_path(ctx: &Ctx) -> Option<Cow<'static, str>> {
600        ctx.tool().prompt_path()
601    }
602
603    fn default_permission(ctx: &Ctx) -> ToolPolicy {
604        ctx.tool().default_permission()
605    }
606
607    fn allow_patterns(ctx: &Ctx) -> Option<&'static [&'static str]> {
608        ctx.tool().allow_patterns()
609    }
610
611    fn deny_patterns(ctx: &Ctx) -> Option<&'static [&'static str]> {
612        ctx.tool().deny_patterns()
613    }
614
615    fn is_mutating(ctx: &Ctx) -> bool {
616        ctx.tool().is_mutating()
617    }
618
619    fn is_parallel_safe(ctx: &Ctx) -> bool {
620        ctx.tool().is_parallel_safe()
621    }
622
623    fn tool_kind(ctx: &Ctx) -> &'static str {
624        ctx.tool().kind()
625    }
626
627    fn resource_hints(ctx: &Ctx, args: &Value) -> Vec<String> {
628        ctx.tool().resource_hints(args)
629    }
630
631    fn execution_cost(ctx: &Ctx) -> u8 {
632        ctx.tool().execution_cost()
633    }
634}
635
636// ============================================================================
637// Consumer traits (blanket impls over provider wiring)
638// ============================================================================
639
640fn value_to_text(value: &Value) -> String {
641    if value.is_string() {
642        value.as_str().unwrap_or("").to_string()
643    } else {
644        json_to_string_pretty(value)
645    }
646}
647
648#[async_trait]
649trait ExecutionMode<Ctx>
650where
651    Ctx: HasComponent<ExecuteComponent> + HasComponent<CacheComponent> + Send + Sync,
652    ComponentProvider<Ctx, ExecuteComponent>: ExecuteProvider<Ctx>,
653    ComponentProvider<Ctx, CacheComponent>: CacheProvider<Ctx>,
654{
655    type Output: Send;
656
657    fn get_cached(ctx: &Ctx, tool_name: &str, args: &Value) -> Option<Self::Output>;
658
659    fn put_cached(ctx: &Ctx, tool_name: &str, args: &Value, result: &Self::Output);
660
661    async fn execute(ctx: &Ctx, args: Value) -> Result<Self::Output>;
662}
663
664struct JsonExecution;
665
666#[async_trait]
667impl<Ctx> ExecutionMode<Ctx> for JsonExecution
668where
669    Ctx: HasComponent<ExecuteComponent> + HasComponent<CacheComponent> + Send + Sync,
670    ComponentProvider<Ctx, ExecuteComponent>: ExecuteProvider<Ctx>,
671    ComponentProvider<Ctx, CacheComponent>: CacheProvider<Ctx>,
672{
673    type Output = Value;
674
675    fn get_cached(ctx: &Ctx, tool_name: &str, args: &Value) -> Option<Self::Output> {
676        <ComponentProvider<Ctx, CacheComponent> as CacheProvider<Ctx>>::get_json(
677            ctx, tool_name, args,
678        )
679    }
680
681    fn put_cached(ctx: &Ctx, tool_name: &str, args: &Value, result: &Self::Output) {
682        <ComponentProvider<Ctx, CacheComponent> as CacheProvider<Ctx>>::put_json(
683            ctx, tool_name, args, result,
684        );
685    }
686
687    async fn execute(ctx: &Ctx, args: Value) -> Result<Self::Output> {
688        <ComponentProvider<Ctx, ExecuteComponent> as ExecuteProvider<Ctx>>::execute(ctx, args).await
689    }
690}
691
692struct DualExecution;
693
694#[async_trait]
695impl<Ctx> ExecutionMode<Ctx> for DualExecution
696where
697    Ctx: HasComponent<ExecuteComponent> + HasComponent<CacheComponent> + Send + Sync,
698    ComponentProvider<Ctx, ExecuteComponent>: ExecuteProvider<Ctx>,
699    ComponentProvider<Ctx, CacheComponent>: CacheProvider<Ctx>,
700{
701    type Output = SplitToolResult;
702
703    fn get_cached(ctx: &Ctx, tool_name: &str, args: &Value) -> Option<Self::Output> {
704        <ComponentProvider<Ctx, CacheComponent> as CacheProvider<Ctx>>::get_dual(
705            ctx, tool_name, args,
706        )
707    }
708
709    fn put_cached(ctx: &Ctx, tool_name: &str, args: &Value, result: &Self::Output) {
710        <ComponentProvider<Ctx, CacheComponent> as CacheProvider<Ctx>>::put_dual(
711            ctx, tool_name, args, result,
712        );
713    }
714
715    async fn execute(ctx: &Ctx, args: Value) -> Result<Self::Output> {
716        <ComponentProvider<Ctx, ExecuteComponent> as ExecuteProvider<Ctx>>::execute_dual(ctx, args)
717            .await
718    }
719}
720
721fn retry_backoff<Ctx>(ctx: &Ctx, tool_name: &str, attempt: u32) -> Duration
722where
723    Ctx: HasComponent<RetryComponent> + Send + Sync,
724    ComponentProvider<Ctx, RetryComponent>: RetryProvider<Ctx>,
725{
726    <ComponentProvider<Ctx, RetryComponent> as RetryProvider<Ctx>>::backoff_duration(
727        ctx, tool_name, attempt,
728    )
729    .min(MAX_RETRY_BACKOFF)
730}
731
732async fn execute_tool_with_mode<Ctx, Mode>(
733    ctx: &Ctx,
734    tool_name: &str,
735    args: Value,
736) -> Result<Mode::Output>
737where
738    Ctx: HasComponent<ExecuteComponent>
739        + HasComponent<LoggingComponent>
740        + HasComponent<CacheComponent>
741        + HasComponent<RetryComponent>
742        + Send
743        + Sync,
744    ComponentProvider<Ctx, ExecuteComponent>: ExecuteProvider<Ctx>,
745    ComponentProvider<Ctx, LoggingComponent>: LoggingProvider<Ctx>,
746    ComponentProvider<Ctx, CacheComponent>: CacheProvider<Ctx>,
747    ComponentProvider<Ctx, RetryComponent>: RetryProvider<Ctx>,
748    Mode: ExecutionMode<Ctx>,
749{
750    <ComponentProvider<Ctx, LoggingComponent> as LoggingProvider<Ctx>>::on_start(
751        ctx, tool_name, &args,
752    );
753
754    if let Some(result) = Mode::get_cached(ctx, tool_name, &args) {
755        <ComponentProvider<Ctx, LoggingComponent> as LoggingProvider<Ctx>>::on_cache_hit(
756            ctx, tool_name, &args,
757        );
758        <ComponentProvider<Ctx, LoggingComponent> as LoggingProvider<Ctx>>::on_success(
759            ctx,
760            tool_name,
761            Duration::ZERO,
762            1,
763            true,
764        );
765        return Ok(result);
766    }
767
768    let started = Instant::now();
769    let max_attempts =
770        <ComponentProvider<Ctx, RetryComponent> as RetryProvider<Ctx>>::max_attempts(
771            ctx, tool_name, &args,
772        )
773        .clamp(1, MAX_RETRY_ATTEMPTS);
774
775    let mut attempt = 1;
776    loop {
777        match Mode::execute(ctx, args.clone()).await {
778            Ok(result) => {
779                Mode::put_cached(ctx, tool_name, &args, &result);
780                <ComponentProvider<Ctx, LoggingComponent> as LoggingProvider<Ctx>>::on_success(
781                    ctx,
782                    tool_name,
783                    started.elapsed(),
784                    attempt,
785                    false,
786                );
787                return Ok(result);
788            }
789            Err(error) => {
790                let should_retry = attempt < max_attempts
791                    && <ComponentProvider<Ctx, RetryComponent> as RetryProvider<Ctx>>::should_retry(
792                        ctx, tool_name, attempt, &error,
793                    );
794
795                if !should_retry {
796                    <ComponentProvider<Ctx, LoggingComponent> as LoggingProvider<Ctx>>::on_failure(
797                        ctx,
798                        tool_name,
799                        started.elapsed(),
800                        attempt,
801                        &error,
802                    );
803                    return Err(error);
804                }
805
806                let backoff = retry_backoff(ctx, tool_name, attempt);
807                <ComponentProvider<Ctx, LoggingComponent> as LoggingProvider<Ctx>>::on_retry(
808                    ctx,
809                    tool_name,
810                    attempt + 1,
811                    backoff,
812                    &error,
813                );
814                if !backoff.is_zero() {
815                    tokio::time::sleep(backoff).await;
816                }
817                attempt += 1;
818            }
819        }
820    }
821}
822
823#[async_trait]
824pub trait CanApproveTool: Send + Sync {
825    async fn approve_tool(&self, tool_name: &str, description: &str) -> Result<()>;
826}
827
828#[async_trait]
829impl<Ctx> CanApproveTool for Ctx
830where
831    Ctx: HasComponent<ApprovalComponent> + Send + Sync,
832    ComponentProvider<Ctx, ApprovalComponent>: ApprovalProvider<Ctx>,
833{
834    async fn approve_tool(&self, tool_name: &str, description: &str) -> Result<()> {
835        <ComponentProvider<Ctx, ApprovalComponent> as ApprovalProvider<Ctx>>::check_approval(
836            self,
837            tool_name,
838            description,
839        )
840        .await
841    }
842}
843
844pub trait CanResolveSandbox: Send + Sync {
845    fn sandbox_enabled(&self) -> bool;
846
847    fn workspace_root(&self) -> Option<&PathBuf>;
848}
849
850impl<Ctx> CanResolveSandbox for Ctx
851where
852    Ctx: HasComponent<SandboxComponent> + Send + Sync,
853    ComponentProvider<Ctx, SandboxComponent>: SandboxProvider<Ctx>,
854{
855    fn sandbox_enabled(&self) -> bool {
856        <ComponentProvider<Ctx, SandboxComponent> as SandboxProvider<Ctx>>::sandbox_enabled(self)
857    }
858
859    fn workspace_root(&self) -> Option<&PathBuf> {
860        <ComponentProvider<Ctx, SandboxComponent> as SandboxProvider<Ctx>>::workspace_root(self)
861    }
862}
863
864pub trait CanProvideToolMetadata: Send + Sync {
865    fn tool_name(&self) -> &str;
866
867    fn tool_description(&self) -> &str;
868
869    fn parameter_schema(&self) -> Option<Value>;
870
871    fn config_schema(&self) -> Option<Value>;
872
873    fn state_schema(&self) -> Option<Value>;
874
875    fn prompt_path(&self) -> Option<Cow<'static, str>>;
876
877    fn default_permission(&self) -> ToolPolicy;
878
879    fn allow_patterns(&self) -> Option<&'static [&'static str]>;
880
881    fn deny_patterns(&self) -> Option<&'static [&'static str]>;
882
883    fn is_mutating(&self) -> bool;
884
885    fn is_parallel_safe(&self) -> bool;
886
887    fn tool_kind(&self) -> &'static str;
888
889    fn resource_hints(&self, args: &Value) -> Vec<String>;
890
891    fn execution_cost(&self) -> u8;
892}
893
894impl<Ctx> CanProvideToolMetadata for Ctx
895where
896    Ctx: HasComponent<MetadataComponent> + Send + Sync,
897    ComponentProvider<Ctx, MetadataComponent>: MetadataProvider<Ctx>,
898{
899    fn tool_name(&self) -> &str {
900        <ComponentProvider<Ctx, MetadataComponent> as MetadataProvider<Ctx>>::tool_name(self)
901    }
902
903    fn tool_description(&self) -> &str {
904        <ComponentProvider<Ctx, MetadataComponent> as MetadataProvider<Ctx>>::tool_description(self)
905    }
906
907    fn parameter_schema(&self) -> Option<Value> {
908        <ComponentProvider<Ctx, MetadataComponent> as MetadataProvider<Ctx>>::parameter_schema(self)
909    }
910
911    fn config_schema(&self) -> Option<Value> {
912        <ComponentProvider<Ctx, MetadataComponent> as MetadataProvider<Ctx>>::config_schema(self)
913    }
914
915    fn state_schema(&self) -> Option<Value> {
916        <ComponentProvider<Ctx, MetadataComponent> as MetadataProvider<Ctx>>::state_schema(self)
917    }
918
919    fn prompt_path(&self) -> Option<Cow<'static, str>> {
920        <ComponentProvider<Ctx, MetadataComponent> as MetadataProvider<Ctx>>::prompt_path(self)
921    }
922
923    fn default_permission(&self) -> ToolPolicy {
924        <ComponentProvider<Ctx, MetadataComponent> as MetadataProvider<Ctx>>::default_permission(
925            self,
926        )
927    }
928
929    fn allow_patterns(&self) -> Option<&'static [&'static str]> {
930        <ComponentProvider<Ctx, MetadataComponent> as MetadataProvider<Ctx>>::allow_patterns(self)
931    }
932
933    fn deny_patterns(&self) -> Option<&'static [&'static str]> {
934        <ComponentProvider<Ctx, MetadataComponent> as MetadataProvider<Ctx>>::deny_patterns(self)
935    }
936
937    fn is_mutating(&self) -> bool {
938        <ComponentProvider<Ctx, MetadataComponent> as MetadataProvider<Ctx>>::is_mutating(self)
939    }
940
941    fn is_parallel_safe(&self) -> bool {
942        <ComponentProvider<Ctx, MetadataComponent> as MetadataProvider<Ctx>>::is_parallel_safe(self)
943    }
944
945    fn tool_kind(&self) -> &'static str {
946        <ComponentProvider<Ctx, MetadataComponent> as MetadataProvider<Ctx>>::tool_kind(self)
947    }
948
949    fn resource_hints(&self, args: &Value) -> Vec<String> {
950        <ComponentProvider<Ctx, MetadataComponent> as MetadataProvider<Ctx>>::resource_hints(
951            self, args,
952        )
953    }
954
955    fn execution_cost(&self) -> u8 {
956        <ComponentProvider<Ctx, MetadataComponent> as MetadataProvider<Ctx>>::execution_cost(self)
957    }
958}
959
960#[async_trait]
961pub trait CanExecuteTool: Send + Sync {
962    async fn execute_tool_json(&self, tool_name: &str, args: Value) -> Result<Value>;
963
964    async fn execute_tool_dual(&self, tool_name: &str, args: Value) -> Result<SplitToolResult>;
965}
966
967#[async_trait]
968impl<Ctx> CanExecuteTool for Ctx
969where
970    Ctx: HasComponent<ExecuteComponent>
971        + HasComponent<LoggingComponent>
972        + HasComponent<CacheComponent>
973        + HasComponent<RetryComponent>
974        + Send
975        + Sync,
976    ComponentProvider<Ctx, ExecuteComponent>: ExecuteProvider<Ctx>,
977    ComponentProvider<Ctx, LoggingComponent>: LoggingProvider<Ctx>,
978    ComponentProvider<Ctx, CacheComponent>: CacheProvider<Ctx>,
979    ComponentProvider<Ctx, RetryComponent>: RetryProvider<Ctx>,
980{
981    async fn execute_tool_json(&self, tool_name: &str, args: Value) -> Result<Value> {
982        execute_tool_with_mode::<Ctx, JsonExecution>(self, tool_name, args).await
983    }
984
985    async fn execute_tool_dual(&self, tool_name: &str, args: Value) -> Result<SplitToolResult> {
986        execute_tool_with_mode::<Ctx, DualExecution>(self, tool_name, args).await
987    }
988}
989
990// ============================================================================
991// CGP-backed Tool facade
992// ============================================================================
993
994/// A facade that projects a CGP-wired context as a `Tool` trait object.
995///
996/// This replaces `HandlerToToolAdapter` — instead of wrapping a `ToolHandler`
997/// in a `Tool` newtype, the context itself carries all the component wiring.
998/// The facade simply delegates to the wired providers.
999pub struct ToolFacade<Ctx> {
1000    ctx: Ctx,
1001}
1002
1003impl<Ctx> ToolFacade<Ctx> {
1004    pub fn new(ctx: Ctx) -> Self {
1005        Self { ctx }
1006    }
1007}
1008
1009#[async_trait]
1010impl<Ctx> Tool for ToolFacade<Ctx>
1011where
1012    Ctx: CanApproveTool + CanExecuteTool + CanProvideToolMetadata + Send + Sync + 'static,
1013{
1014    async fn execute(&self, args: Value) -> Result<Value> {
1015        self.ctx.approve_tool(self.name(), "execute").await?;
1016
1017        self.ctx.execute_tool_json(self.name(), args).await
1018    }
1019
1020    async fn execute_dual(&self, args: Value) -> Result<SplitToolResult> {
1021        self.ctx.approve_tool(self.name(), "execute_dual").await?;
1022
1023        self.ctx.execute_tool_dual(self.name(), args).await
1024    }
1025
1026    fn name(&self) -> &str {
1027        CanProvideToolMetadata::tool_name(&self.ctx)
1028    }
1029
1030    fn description(&self) -> &str {
1031        CanProvideToolMetadata::tool_description(&self.ctx)
1032    }
1033
1034    fn parameter_schema(&self) -> Option<Value> {
1035        CanProvideToolMetadata::parameter_schema(&self.ctx)
1036    }
1037
1038    fn config_schema(&self) -> Option<Value> {
1039        CanProvideToolMetadata::config_schema(&self.ctx)
1040    }
1041
1042    fn state_schema(&self) -> Option<Value> {
1043        CanProvideToolMetadata::state_schema(&self.ctx)
1044    }
1045
1046    fn prompt_path(&self) -> Option<Cow<'static, str>> {
1047        CanProvideToolMetadata::prompt_path(&self.ctx)
1048    }
1049
1050    fn default_permission(&self) -> ToolPolicy {
1051        CanProvideToolMetadata::default_permission(&self.ctx)
1052    }
1053
1054    fn allow_patterns(&self) -> Option<&'static [&'static str]> {
1055        CanProvideToolMetadata::allow_patterns(&self.ctx)
1056    }
1057
1058    fn deny_patterns(&self) -> Option<&'static [&'static str]> {
1059        CanProvideToolMetadata::deny_patterns(&self.ctx)
1060    }
1061
1062    fn is_mutating(&self) -> bool {
1063        CanProvideToolMetadata::is_mutating(&self.ctx)
1064    }
1065
1066    fn is_parallel_safe(&self) -> bool {
1067        CanProvideToolMetadata::is_parallel_safe(&self.ctx)
1068    }
1069
1070    fn kind(&self) -> &'static str {
1071        CanProvideToolMetadata::tool_kind(&self.ctx)
1072    }
1073
1074    fn resource_hints(&self, args: &Value) -> Vec<String> {
1075        CanProvideToolMetadata::resource_hints(&self.ctx, args)
1076    }
1077
1078    fn execution_cost(&self) -> u8 {
1079        CanProvideToolMetadata::execution_cost(&self.ctx)
1080    }
1081}
1082
1083// ============================================================================
1084// CGP-backed ToolHandler facade
1085// ============================================================================
1086
1087/// A facade that projects a CGP-wired context as a `ToolHandler` trait object.
1088///
1089/// This replaces `ToolToHandlerAdapter` — instead of wrapping a `Tool` in a
1090/// `ToolHandler` newtype, the same context used by `ToolFacade` can also be
1091/// projected as a `ToolHandler` with zero additional adaptation code.
1092pub struct HandlerFacade<Ctx> {
1093    ctx: Ctx,
1094}
1095
1096impl<Ctx> HandlerFacade<Ctx> {
1097    pub fn new(ctx: Ctx) -> Self {
1098        Self { ctx }
1099    }
1100}
1101
1102#[async_trait]
1103impl<Ctx> ToolHandler for HandlerFacade<Ctx>
1104where
1105    Ctx: CanApproveTool + CanExecuteTool + Send + Sync + 'static,
1106{
1107    fn kind(&self) -> ToolKind {
1108        ToolKind::Function
1109    }
1110
1111    async fn handle(&self, invocation: ToolInvocation) -> Result<ToolOutput, ToolCallError> {
1112        // Extract arguments from payload
1113        let args: Value = match &invocation.payload {
1114            ToolPayload::Function { arguments } => serde_json::from_str(arguments)
1115                .map_err(|e| ToolCallError::respond(format!("Invalid arguments: {e}")))?,
1116            _ => return Err(ToolCallError::respond("Unsupported payload type")),
1117        };
1118
1119        self.ctx
1120            .approve_tool(&invocation.tool_name, "handle")
1121            .await
1122            .map_err(|e| ToolCallError::respond(e.to_string()))?;
1123
1124        match self
1125            .ctx
1126            .execute_tool_json(&invocation.tool_name, args)
1127            .await
1128        {
1129            Ok(result) => {
1130                let text = value_to_text(&result);
1131                Ok(ToolOutput::simple(text))
1132            }
1133            Err(e) => Err(ToolCallError::Internal(e)),
1134        }
1135    }
1136}
1137
1138// ============================================================================
1139// Composite runtime via CGP delegation
1140// ============================================================================
1141
1142/// A generic tool runtime that composes approval + sandbox + execution
1143/// through CGP component wiring.
1144///
1145/// Instead of hard-coding policy logic, `ComposableRuntime` delegates to
1146/// whatever providers the context has wired for each component.
1147pub struct ComposableRuntime;
1148
1149impl ComposableRuntime {
1150    /// Execute a tool operation through the full approval → sandbox → execute
1151    /// pipeline, with all behavior determined by the context's component
1152    /// wiring.
1153    pub async fn run<Ctx>(ctx: &Ctx, tool_name: &str, description: &str) -> Result<()>
1154    where
1155        Ctx: CanApproveTool + Send + Sync,
1156    {
1157        ctx.approve_tool(tool_name, description).await?;
1158        Ok(())
1159    }
1160
1161    /// Full pipeline: approval → sandbox check → delegate to caller for
1162    /// execution.
1163    pub async fn run_with_sandbox<Ctx>(
1164        ctx: &Ctx,
1165        tool_name: &str,
1166        description: &str,
1167    ) -> Result<bool>
1168    where
1169        Ctx: CanApproveTool + CanResolveSandbox + Send + Sync,
1170    {
1171        ctx.approve_tool(tool_name, description).await?;
1172        Ok(ctx.sandbox_enabled())
1173    }
1174}
1175
1176// ============================================================================
1177// Concrete runtime contexts
1178// ============================================================================
1179
1180/// Interactive runtime context — used during normal TUI sessions.
1181///
1182/// Wires prompt-based approval, workspace-scoped sandbox, and tracing-only
1183/// static middleware. Carries workspace root for path validation.
1184pub struct InteractiveCtx {
1185    pub workspace_root: PathBuf,
1186}
1187
1188impl InteractiveCtx {
1189    pub fn new(workspace_root: PathBuf) -> Self {
1190        Self { workspace_root }
1191    }
1192}
1193
1194/// Prompt-based approval provider for interactive sessions.
1195///
1196/// In the current implementation this auto-approves (the existing
1197/// `ToolPolicyGateway` handles actual prompting at a higher layer).
1198/// This provider exists so the CGP pipeline is structurally complete
1199/// and ready for deeper integration.
1200pub struct PromptApproval;
1201
1202#[async_trait]
1203impl<Ctx: Send + Sync> ApprovalProvider<Ctx> for PromptApproval {
1204    async fn check_approval(_ctx: &Ctx, _tool_name: &str, _description: &str) -> Result<()> {
1205        // Approval is handled by ToolPolicyGateway at registry level;
1206        // this provider completes the CGP pipeline without double-gating.
1207        Ok(())
1208    }
1209}
1210
1211/// Trait for contexts that expose a workspace root path.
1212pub trait HasWorkspaceRoot: Send + Sync {
1213    fn workspace_root(&self) -> &PathBuf;
1214}
1215
1216impl HasWorkspaceRoot for InteractiveCtx {
1217    fn workspace_root(&self) -> &PathBuf {
1218        &self.workspace_root
1219    }
1220}
1221
1222/// Workspace-scoped sandbox provider.
1223///
1224/// Reports sandbox as enabled and exposes the workspace root for path
1225/// validation. Generic over any context that implements `HasWorkspaceRoot`.
1226pub struct WorkspaceSandbox;
1227
1228#[async_trait]
1229impl<Ctx: HasWorkspaceRoot> SandboxProvider<Ctx> for WorkspaceSandbox {
1230    fn sandbox_enabled(_ctx: &Ctx) -> bool {
1231        true
1232    }
1233
1234    fn workspace_root(ctx: &Ctx) -> Option<&PathBuf> {
1235        Some(HasWorkspaceRoot::workspace_root(ctx))
1236    }
1237}
1238
1239delegate_components!(InteractiveCtx {
1240    ApprovalComponent => PromptApproval,
1241    SandboxComponent  => WorkspaceSandbox,
1242    ExecuteComponent  => PassthroughExecutor,
1243    MetadataComponent => PassthroughMetadata,
1244    LoggingComponent  => TracingLogging,
1245    CacheComponent    => NoCache,
1246    RetryComponent    => NoRetry,
1247});
1248
1249/// CI/automation runtime context — auto-approves, strict sandbox.
1250pub struct CiCtx {
1251    pub workspace_root: PathBuf,
1252}
1253
1254impl CiCtx {
1255    pub fn new(workspace_root: PathBuf) -> Self {
1256        Self { workspace_root }
1257    }
1258}
1259
1260impl HasWorkspaceRoot for CiCtx {
1261    fn workspace_root(&self) -> &PathBuf {
1262        &self.workspace_root
1263    }
1264}
1265
1266/// Strict sandbox that enforces workspace boundaries in CI.
1267pub type StrictWorkspaceSandbox = WorkspaceSandbox;
1268
1269delegate_components!(CiCtx {
1270    ApprovalComponent => AutoApproval,
1271    SandboxComponent  => StrictWorkspaceSandbox,
1272    ExecuteComponent  => PassthroughExecutor,
1273    MetadataComponent => PassthroughMetadata,
1274    LoggingComponent  => NoLogging,
1275    CacheComponent    => NoCache,
1276    RetryComponent    => NoRetry,
1277});
1278
1279/// Benchmark runtime context — auto-approves, no sandbox, no static middleware.
1280pub struct BenchCtx;
1281
1282delegate_components!(BenchCtx {
1283    ApprovalComponent => AutoApproval,
1284    SandboxComponent  => NoSandbox,
1285    ExecuteComponent  => PassthroughExecutor,
1286    MetadataComponent => PassthroughMetadata,
1287    LoggingComponent  => NoLogging,
1288    CacheComponent    => NoCache,
1289    RetryComponent    => NoRetry,
1290});
1291
1292/// Passthrough executor — delegates to a stored `Arc<dyn Tool>`.
1293///
1294/// This bridges existing `Tool` implementations into the CGP pipeline,
1295/// so concrete tools (grep, file_ops, exec, etc.) can be composed with
1296/// CGP approval/sandbox/logging/cache/retry without rewriting them.
1297pub struct PassthroughExecutor;
1298
1299/// Trait for contexts that can expose a tool reference for delegated metadata,
1300/// validation, and execution.
1301pub trait HasToolRef: Send + Sync {
1302    fn tool(&self) -> &dyn Tool;
1303}
1304
1305/// Trait for contexts that can borrow an inner `Tool` for passthrough.
1306///
1307/// Keep shared ownership inside the bridge context; callers only need a borrowed
1308/// `dyn Tool` to delegate metadata, validation, and execution.
1309pub trait HasInnerTool: Send + Sync {
1310    fn inner_tool(&self) -> &dyn Tool;
1311}
1312
1313impl<Ctx> HasToolRef for Ctx
1314where
1315    Ctx: HasInnerTool + Send + Sync,
1316{
1317    fn tool(&self) -> &dyn Tool {
1318        self.inner_tool()
1319    }
1320}
1321
1322#[async_trait]
1323impl<Ctx: HasToolRef> ExecuteProvider<Ctx> for PassthroughExecutor {
1324    async fn execute(ctx: &Ctx, args: Value) -> Result<Value> {
1325        let tool = ctx.tool();
1326        tool.validate_args(&args)?;
1327        tool.execute(args).await
1328    }
1329
1330    async fn execute_dual(ctx: &Ctx, args: Value) -> Result<SplitToolResult> {
1331        let tool = ctx.tool();
1332        tool.validate_args(&args)?;
1333        tool.execute_dual(args).await
1334    }
1335}
1336
1337// ============================================================================
1338// Registry integration bridge
1339// ============================================================================
1340
1341/// Create a `ToolFacade` that wraps an existing `Arc<dyn Tool>` with
1342/// CGP-wired approval, sandbox, and middleware for interactive sessions.
1343///
1344/// Prefer `wrap_native_tool_interactive()` when you still own the concrete
1345/// tool instance. Use this bridge when the tool is already shared elsewhere.
1346///
1347/// The returned facade implements `Tool` and can be registered directly
1348/// with `ToolRegistration::from_tool()`.
1349pub fn wrap_tool_interactive(
1350    tool: Arc<dyn Tool>,
1351    workspace_root: PathBuf,
1352) -> ToolFacade<ToolBridgeCtx<InteractiveCtx>> {
1353    let ctx = ToolBridgeCtx {
1354        inner: tool,
1355        runtime: InteractiveCtx::new(workspace_root),
1356    };
1357    ToolFacade::new(ctx)
1358}
1359
1360/// Create a `ToolFacade` that wraps an existing `Arc<dyn Tool>` with
1361/// CGP-wired auto-approval and no middleware for CI contexts.
1362///
1363/// Prefer `wrap_native_tool_ci()` when you still own the concrete tool
1364/// instance. Use this bridge when the tool is already shared elsewhere.
1365pub fn wrap_tool_ci(
1366    tool: Arc<dyn Tool>,
1367    workspace_root: PathBuf,
1368) -> ToolFacade<ToolBridgeCtx<CiCtx>> {
1369    let ctx = ToolBridgeCtx {
1370        inner: tool,
1371        runtime: CiCtx::new(workspace_root),
1372    };
1373    ToolFacade::new(ctx)
1374}
1375
1376/// Bridge context: combines a runtime context with shared tool ownership.
1377///
1378/// This keeps the `Arc<dyn Tool>` inside the bridge while
1379/// `PassthroughExecutor` only borrows `&dyn Tool`. The rest of the CGP
1380/// pipeline (approval, sandbox, middleware) is provided by the runtime
1381/// context's component wiring.
1382pub struct ToolBridgeCtx<Runtime> {
1383    inner: Arc<dyn Tool>,
1384    /// The runtime context whose component wiring is inherited via
1385    /// the blanket `HasComponent` impl below. Accessed at the type
1386    /// level, not at runtime — the compiler doesn't see field reads.
1387    runtime: Runtime,
1388}
1389
1390impl<Runtime: HasWorkspaceRoot> HasWorkspaceRoot for ToolBridgeCtx<Runtime> {
1391    fn workspace_root(&self) -> &PathBuf {
1392        self.runtime.workspace_root()
1393    }
1394}
1395
1396impl<Runtime: Send + Sync> HasInnerTool for ToolBridgeCtx<Runtime> {
1397    fn inner_tool(&self) -> &dyn Tool {
1398        self.inner.as_ref()
1399    }
1400}
1401
1402// Component wiring for bridge contexts delegates to the runtime's wiring.
1403impl<Name, Runtime> HasComponent<Name> for ToolBridgeCtx<Runtime>
1404where
1405    Runtime: HasComponent<Name>,
1406{
1407    type Provider = ComponentProvider<Runtime, Name>;
1408}
1409
1410/// Trait for contexts that carry a concrete tool instance.
1411pub trait HasToolInstance<T>: Send + Sync {
1412    fn tool_instance(&self) -> &T;
1413}
1414
1415/// Execute provider that dispatches directly to a typed tool instance.
1416pub struct TypedToolExecutor<T>(PhantomData<T>);
1417
1418#[async_trait]
1419impl<Ctx, T> ExecuteProvider<Ctx> for TypedToolExecutor<T>
1420where
1421    Ctx: HasToolRef + Send + Sync,
1422    T: Tool + Send + Sync,
1423{
1424    async fn execute(ctx: &Ctx, args: Value) -> Result<Value> {
1425        <PassthroughExecutor as ExecuteProvider<Ctx>>::execute(ctx, args).await
1426    }
1427
1428    async fn execute_dual(ctx: &Ctx, args: Value) -> Result<SplitToolResult> {
1429        <PassthroughExecutor as ExecuteProvider<Ctx>>::execute_dual(ctx, args).await
1430    }
1431}
1432
1433/// Metadata provider that dispatches directly to a typed tool instance.
1434pub struct TypedToolMetadata<T>(PhantomData<T>);
1435
1436impl<Ctx, T> MetadataProvider<Ctx> for TypedToolMetadata<T>
1437where
1438    Ctx: HasToolRef,
1439    T: Tool + Send + Sync,
1440{
1441    fn tool_name(ctx: &Ctx) -> &str {
1442        <PassthroughMetadata as MetadataProvider<Ctx>>::tool_name(ctx)
1443    }
1444
1445    fn tool_description(ctx: &Ctx) -> &str {
1446        <PassthroughMetadata as MetadataProvider<Ctx>>::tool_description(ctx)
1447    }
1448
1449    fn parameter_schema(ctx: &Ctx) -> Option<Value> {
1450        <PassthroughMetadata as MetadataProvider<Ctx>>::parameter_schema(ctx)
1451    }
1452
1453    fn config_schema(ctx: &Ctx) -> Option<Value> {
1454        <PassthroughMetadata as MetadataProvider<Ctx>>::config_schema(ctx)
1455    }
1456
1457    fn state_schema(ctx: &Ctx) -> Option<Value> {
1458        <PassthroughMetadata as MetadataProvider<Ctx>>::state_schema(ctx)
1459    }
1460
1461    fn prompt_path(ctx: &Ctx) -> Option<Cow<'static, str>> {
1462        <PassthroughMetadata as MetadataProvider<Ctx>>::prompt_path(ctx)
1463    }
1464
1465    fn default_permission(ctx: &Ctx) -> ToolPolicy {
1466        <PassthroughMetadata as MetadataProvider<Ctx>>::default_permission(ctx)
1467    }
1468
1469    fn allow_patterns(ctx: &Ctx) -> Option<&'static [&'static str]> {
1470        <PassthroughMetadata as MetadataProvider<Ctx>>::allow_patterns(ctx)
1471    }
1472
1473    fn deny_patterns(ctx: &Ctx) -> Option<&'static [&'static str]> {
1474        <PassthroughMetadata as MetadataProvider<Ctx>>::deny_patterns(ctx)
1475    }
1476
1477    fn is_mutating(ctx: &Ctx) -> bool {
1478        <PassthroughMetadata as MetadataProvider<Ctx>>::is_mutating(ctx)
1479    }
1480
1481    fn is_parallel_safe(ctx: &Ctx) -> bool {
1482        <PassthroughMetadata as MetadataProvider<Ctx>>::is_parallel_safe(ctx)
1483    }
1484
1485    fn tool_kind(ctx: &Ctx) -> &'static str {
1486        <PassthroughMetadata as MetadataProvider<Ctx>>::tool_kind(ctx)
1487    }
1488
1489    fn resource_hints(ctx: &Ctx, args: &Value) -> Vec<String> {
1490        <PassthroughMetadata as MetadataProvider<Ctx>>::resource_hints(ctx, args)
1491    }
1492
1493    fn execution_cost(ctx: &Ctx) -> u8 {
1494        <PassthroughMetadata as MetadataProvider<Ctx>>::execution_cost(ctx)
1495    }
1496}
1497
1498/// Typed CGP context that carries a concrete tool instance plus runtime policy.
1499pub struct TypedToolCtx<Runtime, T> {
1500    tool: T,
1501    runtime: Runtime,
1502}
1503
1504impl<Runtime, T> TypedToolCtx<Runtime, T> {
1505    pub fn new(tool: T, runtime: Runtime) -> Self {
1506        Self { tool, runtime }
1507    }
1508}
1509
1510impl<Runtime: HasWorkspaceRoot + Send + Sync, T: Send + Sync> HasWorkspaceRoot
1511    for TypedToolCtx<Runtime, T>
1512{
1513    fn workspace_root(&self) -> &PathBuf {
1514        self.runtime.workspace_root()
1515    }
1516}
1517
1518impl<Runtime: Send + Sync, T: Send + Sync> HasToolInstance<T> for TypedToolCtx<Runtime, T> {
1519    fn tool_instance(&self) -> &T {
1520        &self.tool
1521    }
1522}
1523
1524impl<Runtime: Send + Sync, T> HasToolRef for TypedToolCtx<Runtime, T>
1525where
1526    T: Tool + Send + Sync,
1527{
1528    fn tool(&self) -> &dyn Tool {
1529        &self.tool
1530    }
1531}
1532
1533macro_rules! delegate_runtime_components_for_typed_ctx {
1534    ($($component:ty),+ $(,)?) => {
1535        $(
1536            impl<Runtime, T> HasComponent<$component> for TypedToolCtx<Runtime, T>
1537            where
1538                Runtime: HasComponent<$component>,
1539            {
1540                type Provider = ComponentProvider<Runtime, $component>;
1541            }
1542        )+
1543    };
1544}
1545
1546delegate_runtime_components_for_typed_ctx!(
1547    ApprovalComponent,
1548    SandboxComponent,
1549    SessionComponent,
1550    OutputMapComponent,
1551    LoggingComponent,
1552    CacheComponent,
1553    RetryComponent,
1554);
1555
1556impl<Runtime, T> HasComponent<ExecuteComponent> for TypedToolCtx<Runtime, T>
1557where
1558    Runtime: Send + Sync,
1559    T: Tool + Send + Sync,
1560{
1561    type Provider = TypedToolExecutor<T>;
1562}
1563
1564impl<Runtime, T> HasComponent<MetadataComponent> for TypedToolCtx<Runtime, T>
1565where
1566    Runtime: Send + Sync,
1567    T: Tool + Send + Sync,
1568{
1569    type Provider = TypedToolMetadata<T>;
1570}
1571
1572/// Wrap a concrete tool instance in an interactive CGP facade without
1573/// erasing it to `Arc<dyn Tool>` first.
1574///
1575/// Prefer this path when the caller still owns the tool and does not need
1576/// shared ownership.
1577pub fn wrap_native_tool_interactive<T>(
1578    tool: T,
1579    workspace_root: PathBuf,
1580) -> ToolFacade<TypedToolCtx<InteractiveCtx, T>>
1581where
1582    T: Tool + Send + Sync + 'static,
1583{
1584    ToolFacade::new(TypedToolCtx::new(tool, InteractiveCtx::new(workspace_root)))
1585}
1586
1587/// Wrap a concrete tool instance in a CI CGP facade without
1588/// erasing it to `Arc<dyn Tool>` first.
1589///
1590/// Prefer this path when the caller still owns the tool and does not need
1591/// shared ownership.
1592pub fn wrap_native_tool_ci<T>(
1593    tool: T,
1594    workspace_root: PathBuf,
1595) -> ToolFacade<TypedToolCtx<CiCtx, T>>
1596where
1597    T: Tool + Send + Sync + 'static,
1598{
1599    ToolFacade::new(TypedToolCtx::new(tool, CiCtx::new(workspace_root)))
1600}
1601
1602#[cfg(test)]
1603mod tests;