Skip to main content

defect_agent/llm/
capability.rs

1//! Provider and model capability matrix.
2
3use serde::{Deserialize, Serialize};
4
5/// Provider-level capability matrix.
6///
7/// Model-level differences are expressed via [`ModelCapabilityOverrides`]; the main loop
8/// merges them as needed:
9/// a model-level `Some(_)` overrides the provider level, while `None` falls back to the
10/// provider level.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
12pub struct Capabilities {
13    /// Tool calls (content_block contains `tool_use` / `tool_calls` fields).
14    pub tool_calls: FeatureSupport,
15    /// Multiple concurrent tool_use calls within a single turn.
16    pub parallel_tool_calls: FeatureSupport,
17    /// Chain of thought.
18    pub thinking: FeatureSupport,
19    /// Multimodal input (images).
20    pub vision: FeatureSupport,
21    /// Prompt cache.
22    pub prompt_cache: FeatureSupport,
23    /// Thinking content replay strategy. See [`ThinkingEcho`].
24    pub thinking_echo: ThinkingEcho,
25}
26
27/// Model-level overrides. `None` means fall back to the provider-level [`Capabilities`]
28/// field.
29///
30/// The field set is limited to properties that actually vary per model in practice, and
31/// does not mechanically mirror [`Capabilities`]. Additional fields may be added later as
32/// new differences emerge.
33#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
34pub struct ModelCapabilityOverrides {
35    pub thinking: Option<FeatureSupport>,
36    pub vision: Option<FeatureSupport>,
37    pub prompt_cache: Option<FeatureSupport>,
38    pub parallel_tool_calls: Option<FeatureSupport>,
39    pub thinking_echo: Option<ThinkingEcho>,
40}
41
42/// Policy for replaying thinking content.
43///
44/// `Required` — the previous assistant turn's thinking must be included in the next
45/// request (Anthropic extended thinking, DeepSeek-v4-pro). `Forbidden` — replay is
46/// rejected by the server (DeepSeek-R1, official OpenAI o1/o3). `Optional` — the server
47/// accepts either behavior.
48#[non_exhaustive]
49#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
50#[serde(rename_all = "snake_case")]
51pub enum ThinkingEcho {
52    #[default]
53    Forbidden,
54    Required,
55    Optional,
56}
57
58/// Tri-state feature support declaration.
59///
60/// Using a tri-state instead of `bool` allows expressing
61/// [`FeatureSupport::PassthroughAsTool`] — pseudo-support via adaptation. Even though
62/// nothing currently produces this value, defining a tri-state from the start is simpler
63/// than upgrading from `bool` to an enum later.
64#[non_exhaustive]
65#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
66#[serde(rename_all = "snake_case")]
67pub enum FeatureSupport {
68    Supported,
69    Unsupported,
70    /// Passthrough support via adapter.
71    ///
72    /// For example, a provider may not natively support `web_search`, but the agent wraps
73    /// it as a tool exposed to the LLM, thereby "pretending" to support it.
74    PassthroughAsTool,
75}
76
77/// The set of hosted capabilities that the provider advertises.
78///
79/// Distinguished from [`Capabilities`]:
80/// - [`Capabilities`] describes model-level abilities (thinking, vision, tool_calls,
81///   etc.)
82/// - [`HostedCapabilities`] describes the provider adapter's own implementation state:
83///   whether the current adapter can declare hosted `web_search`, `fetch`, or
84///   `code_execution` on the wire.
85///
86/// At session startup, this struct is obtained via
87/// [`super::LlmProvider::hosted_capabilities`] and, together with
88/// `capabilities.web_search.mode`, determines the source of web search capability for the
89/// session. Note that local grep/glob tools (the `search` tool) are not part of the
90/// capability layer and are managed separately by `[tools.search]`.
91///
92/// Native metadata returned by the model after a completions call.
93#[non_exhaustive]
94#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
95pub struct HostedCapabilities {
96    /// Whether the provider adapter supports hosted web search.
97    ///
98    /// The hosted tool version is hardcoded internally by the adapter to always use the
99    /// latest (Anthropic `web_search_20260209`, OpenAI Responses API `web_search`); the
100    /// agent is unaware of the specific version field.
101    pub web_search: bool,
102}
103
104impl HostedCapabilities {
105    /// Constructs from a single field. Cross-crate tests or adapter implementations need
106    /// this entry point because the struct is `#[non_exhaustive]` and cannot be built
107    /// with a struct literal directly.
108    #[must_use]
109    pub const fn with_web_search(web_search: bool) -> Self {
110        Self { web_search }
111    }
112}