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