defect_core/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#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
49#[serde(rename_all = "snake_case")]
50pub enum ThinkingEcho {
51 #[default]
52 Forbidden,
53 Required,
54 Optional,
55}
56
57/// Tri-state feature support declaration.
58///
59/// Using a tri-state instead of `bool` allows expressing
60/// [`FeatureSupport::PassthroughAsTool`] — pseudo-support via adaptation. Even though
61/// nothing currently produces this value, defining a tri-state from the start is simpler
62/// than upgrading from `bool` to an enum later.
63#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
64#[serde(rename_all = "snake_case")]
65pub enum FeatureSupport {
66 Supported,
67 Unsupported,
68 /// Passthrough support via adapter.
69 ///
70 /// For example, a provider may not natively support `web_search`, but the agent wraps
71 /// it as a tool exposed to the LLM, thereby "pretending" to support it.
72 PassthroughAsTool,
73}
74
75/// The set of hosted capabilities that the provider advertises.
76///
77/// Distinguished from [`Capabilities`]:
78/// - [`Capabilities`] describes model-level abilities (thinking, vision, tool_calls,
79/// etc.)
80/// - [`HostedCapabilities`] describes the provider adapter's own implementation state:
81/// whether the current adapter can declare hosted `web_search`, `fetch`, or
82/// `code_execution` on the wire.
83///
84/// At session startup, this struct is obtained via
85/// [`super::LlmProvider::hosted_capabilities`] and, together with
86/// `capabilities.web_search.mode`, determines the source of web search capability for the
87/// session. Note that local grep/glob tools (the `search` tool) are not part of the
88/// capability layer and are managed separately by `[tools.search]`.
89///
90/// Native metadata returned by the model after a completions call.
91#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
92pub struct HostedCapabilities {
93 /// Whether the provider adapter supports hosted web search.
94 ///
95 /// The hosted tool version is hardcoded internally by the adapter to always use the
96 /// latest (Anthropic `web_search_20260209`, OpenAI Responses API `web_search`); the
97 /// agent is unaware of the specific version field.
98 pub web_search: bool,
99}
100
101impl HostedCapabilities {
102 /// Constructs from a single field. A convenience entry point for cross-crate tests and
103 /// adapter implementations.
104 #[must_use]
105 pub const fn with_web_search(web_search: bool) -> Self {
106 Self { web_search }
107 }
108}