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}