Skip to main content

a_agent/provider/
mod.rs

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}