deepstrike_sdk/providers/
mod.rs1use async_trait::async_trait;
2use compact_str::CompactString;
3use deepstrike_core::context::renderer::RenderedContext;
4use deepstrike_core::runtime::session::ProviderReplay;
5use deepstrike_core::types::message::{Content, Message, Role, ToolCall, ToolSchema};
6use futures::{Stream, StreamExt};
7
8pub mod anthropic;
9pub mod openai;
10pub mod provider_error;
11pub mod request_plan;
12
13pub use provider_error::{ProviderError, ProviderErrorKind};
14
15pub use request_plan::{
16 CostObservation, NormalizedProviderUsage, PricingRates, PricingSnapshot,
17 ProviderRequestEndpoint, ProviderRequestPlan, ProviderUsage, RecordedPromptMeasurement,
18 RequestPlanError, UnpricedReason, measurement_for_plan, normalize_provider_usage,
19 price_provider_usage, record_prompt_measurement,
20};
21
22pub type ProviderRunState = serde_json::Value;
24
25#[derive(Debug, Clone, Default)]
28pub struct RuntimePolicy {
29 pub max_turns: Option<u32>,
30 pub timeout_ms: Option<u64>,
31}
32
33#[derive(Debug, Clone)]
35pub enum StreamEvent {
36 TextDelta {
37 delta: String,
38 },
39 ThinkingDelta {
40 delta: String,
41 },
42 ToolCall {
43 id: String,
44 name: String,
45 arguments: serde_json::Value,
46 },
47 Usage {
49 total_tokens: u32,
50 input_tokens: u32,
52 output_tokens: u32,
53 cache_read_input_tokens: u32,
55 cache_creation_input_tokens: u32,
57 cache_read_input_tokens_by_slot: Option<CacheReadBySlot>,
61 stop_reason: Option<String>,
64 },
65 Done,
66}
67
68#[derive(Debug, Clone, Default)]
71pub struct CacheReadBySlot {
72 pub system: Option<u32>,
73 pub tools: Option<u32>,
74 pub messages: Option<u32>,
75}
76
77#[async_trait]
78pub trait LLMProvider: Send + Sync {
79 fn create_run_state(&self) -> Option<ProviderRunState> {
81 None
82 }
83
84 fn runtime_policy(&self) -> RuntimePolicy {
86 RuntimePolicy::default()
87 }
88
89 fn peek_provider_replay(
90 &self,
91 _content: &str,
92 _tool_calls: &[ToolCall],
93 ) -> Option<ProviderReplay> {
94 None
95 }
96
97 fn seed_provider_replay(
98 &self,
99 _content: &str,
100 _tool_calls: &[ToolCall],
101 _replay: &ProviderReplay,
102 ) {
103 }
104
105 fn commit_stream_replay(&self, _content: &str, _tool_calls: &[ToolCall]) {}
106
107 async fn complete(
109 &self,
110 context: &RenderedContext,
111 tools: &[ToolSchema],
112 extensions: Option<&serde_json::Value>,
113 ) -> crate::Result<Message> {
114 let mut stream = self.stream(context, tools, extensions, None).await?;
115 collect_message_from_stream(&mut stream).await
116 }
117
118 async fn stream(
119 &self,
120 context: &RenderedContext,
121 tools: &[ToolSchema],
122 extensions: Option<&serde_json::Value>,
123 state: Option<&ProviderRunState>,
124 ) -> crate::Result<Box<dyn Stream<Item = crate::Result<StreamEvent>> + Send + Unpin>>;
125}
126
127pub async fn collect_message_from_stream(
128 stream: &mut (dyn Stream<Item = crate::Result<StreamEvent>> + Send + Unpin),
129) -> crate::Result<Message> {
130 let mut content = String::new();
131 let mut tool_calls = Vec::new();
132 while let Some(evt) = stream.next().await {
133 match evt? {
134 StreamEvent::TextDelta { delta } => content.push_str(&delta),
135 StreamEvent::ThinkingDelta { .. } => {}
136 StreamEvent::ToolCall {
137 id,
138 name,
139 arguments,
140 } => {
141 tool_calls.push(ToolCall {
142 id: CompactString::new(&id),
143 name: CompactString::new(&name),
144 arguments,
145 });
146 }
147 StreamEvent::Usage { .. } | StreamEvent::Done => {}
148 }
149 }
150 Ok(Message {
151 role: Role::Assistant,
152 content: Content::Text(content),
153 tool_calls,
154 token_count: None,
155 })
156}
157
158#[derive(Debug, Clone, Default)]
160pub struct TokenUsage {
161 pub input_tokens: u32,
163 pub output_tokens: u32,
164 pub cache_read_input_tokens: u32,
166 pub cache_creation_input_tokens: u32,
168}
169
170impl TokenUsage {
171 pub fn total_tokens(&self) -> u32 {
172 self.input_tokens + self.output_tokens
173 }
174}
175
176#[derive(Debug, Clone)]
178pub struct ProviderToolSpec {
179 pub name: String,
180 pub description: String,
181 pub parameters: serde_json::Value,
182}