1pub mod anthropic;
2pub mod chat_completion;
3pub mod responses;
4
5use std::sync::Arc;
6
7use async_trait::async_trait;
8use tokio_util::sync::CancellationToken;
9
10use crate::config::{ProviderConfig, ProviderKind};
11use crate::model::{ModelRequest, ModelTurn, StreamEvent};
12
13#[derive(Clone)]
14pub struct EventSink(Arc<dyn Fn(StreamEvent) + Send + Sync>);
15
16impl EventSink {
17 pub fn new(callback: impl Fn(StreamEvent) + Send + Sync + 'static) -> Self {
18 Self(Arc::new(callback))
19 }
20
21 pub fn emit(&self, event: StreamEvent) {
22 (self.0)(event);
23 }
24}
25
26impl Default for EventSink {
27 fn default() -> Self {
28 Self::new(|_| {})
29 }
30}
31
32#[async_trait]
33pub trait Provider: Send + Sync {
34 async fn stream_turn(
35 &self,
36 request: ModelRequest,
37 events: EventSink,
38 cancel: CancellationToken,
39 ) -> anyhow::Result<ModelTurn>;
40}
41
42pub fn create_provider(config: ProviderConfig) -> anyhow::Result<Arc<dyn Provider>> {
43 let api_key = config.resolve_api_key()?;
44 match config.kind {
45 ProviderKind::Responses => Ok(Arc::new(responses::ResponsesProvider::new(
46 config, api_key,
47 )?)),
48 ProviderKind::Anthropic => Ok(Arc::new(anthropic::AnthropicProvider::new(
49 config, api_key,
50 )?)),
51 ProviderKind::Chatcompletion => Ok(Arc::new(chat_completion::ChatCompletionProvider::new(
52 config, api_key,
53 )?)),
54 }
55}
56
57pub(crate) fn tool_definitions() -> Vec<serde_json::Value> {
58 vec![
59 serde_json::json!({
60 "name": "read",
61 "description": "Read a UTF-8 text file with line numbers. Use offset and limit for targeted ranges.",
62 "parameters": {
63 "type": "object",
64 "properties": {
65 "path": {"type": "string"},
66 "offset": {"type": "integer", "minimum": 0},
67 "limit": {"type": "integer", "minimum": 1}
68 },
69 "required": ["path"],
70 "additionalProperties": false
71 }
72 }),
73 serde_json::json!({
74 "name": "apply_patch",
75 "description": "Apply a deterministic V4A patch. Supports Add File, Delete File, and Update File operations.",
76 "parameters": {
77 "type": "object",
78 "properties": {"patch": {"type": "string"}},
79 "required": ["patch"],
80 "additionalProperties": false
81 }
82 }),
83 serde_json::json!({
84 "name": "bash",
85 "description": "Run a shell command in the current workspace and return bounded stdout, stderr, and exit status.",
86 "parameters": {
87 "type": "object",
88 "properties": {"command": {"type": "string"}},
89 "required": ["command"],
90 "additionalProperties": false
91 }
92 }),
93 ]
94}
95
96pub(crate) fn merge_request_fields(
97 target: &mut serde_json::Map<String, serde_json::Value>,
98 config: &ProviderConfig,
99) {
100 for (key, value) in &config.request {
101 target.insert(key.clone(), value.clone());
102 }
103}