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