auto_derive/lib.rs
1// Copyright 2026 Mahmoud Harmouch.
2//
3// Licensed under the MIT license
4// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
5// option. This file may not be copied, modified, or distributed
6// except according to those terms.
7
8extern crate proc_macro;
9
10use quote::quote;
11use syn::{DeriveInput, parse_macro_input};
12
13#[proc_macro_derive(Auto)]
14pub fn derive_agent(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
15 let input = parse_macro_input!(input as DeriveInput);
16 let name = &input.ident;
17
18 let expanded = quote! {
19 impl Agent for #name {
20 fn new(persona: Cow<'static, str>, behavior: Cow<'static, str>) -> Self {
21 let mut agent = Self::default();
22 agent.agent.persona = persona;
23 agent.agent.behavior = behavior;
24 agent
25 }
26
27 fn update(&mut self, status: Status) {
28 self.agent.update(status);
29 }
30
31 fn behavior(&self) -> &std::borrow::Cow<'static, str> {
32 &self.agent.behavior
33 }
34
35 fn persona(&self) -> &std::borrow::Cow<'static, str> {
36 &self.agent.persona
37 }
38
39 fn status(&self) -> &Status {
40 &self.agent.status
41 }
42
43 fn memory(&self) -> &Vec<Message> {
44 &self.agent.memory
45 }
46
47 fn tools(&self) -> &Vec<Tool> {
48 &self.agent.tools
49 }
50
51 fn knowledge(&self) -> &Knowledge {
52 &self.agent.knowledge
53 }
54
55 fn planner(&self) -> Option<&Planner> {
56 self.agent.planner.as_ref()
57 }
58
59 fn profile(&self) -> &Persona {
60 &self.agent.profile
61 }
62
63 #[cfg(feature = "net")]
64 fn collaborators(&self) -> Vec<Collaborator> {
65 let mut all = Vec::new();
66 all.extend(self.agent.local_collaborators.values().cloned());
67 all.extend(self.agent.remote_collaborators.values().cloned());
68 all
69 }
70
71 fn reflection(&self) -> Option<&Reflection> {
72 self.agent.reflection.as_ref()
73 }
74
75 fn scheduler(&self) -> Option<&TaskScheduler> {
76 self.agent.scheduler.as_ref()
77 }
78
79 fn capabilities(&self) -> &std::collections::HashSet<Capability> {
80 &self.agent.capabilities
81 }
82
83 fn context(&self) -> &ContextManager {
84 &self.agent.context
85 }
86
87 fn tasks(&self) -> &Vec<Task> {
88 &self.agent.tasks
89 }
90
91 fn memory_mut(&mut self) -> &mut Vec<Message> {
92 &mut self.agent.memory
93 }
94
95 fn planner_mut(&mut self) -> Option<&mut Planner> {
96 self.agent.planner.as_mut()
97 }
98
99 fn context_mut(&mut self) -> &mut ContextManager {
100 &mut self.agent.context
101 }
102 }
103
104 impl Functions for #name {
105 fn get_agent(&self) -> &AgentGPT {
106 &self.agent
107 }
108 }
109
110 #[async_trait]
111 impl AsyncFunctions for #name {
112 async fn execute<'a>(
113 &'a mut self,
114 task: &'a mut Task,
115 execute: bool,
116 browse: bool,
117 max_tries: u64,
118 ) -> Result<()> {
119 <#name as Executor>::execute(self, task, execute, browse, max_tries).await
120 }
121
122 /// Saves a communication to long-term memory for the agent.
123 ///
124 /// # Arguments
125 ///
126 /// * `communication` - The communication to save, which contains the role and content.
127 ///
128 /// # Returns
129 ///
130 /// (`Result<()>`): Result indicating the success or failure of saving the communication.
131 ///
132 /// # Business Logic
133 ///
134 /// - This method uses the `save_long_term_memory` util function to save the communication into the agent's long-term memory.
135 /// - The communication is embedded and stored using the agent's unique ID as the namespace.
136 /// - It handles the embedding and metadata for the communication, ensuring it's stored correctly.
137 #[cfg(feature = "mem")]
138 async fn save_ltm(&mut self, message: Message) -> Result<()> {
139 save_long_term_memory(&mut self.client, self.agent.id.clone(), message).await
140 }
141
142 /// Retrieves all communications stored in the agent's long-term memory.
143 ///
144 /// # Returns
145 ///
146 /// (`Result<Vec<Message>>`): A result containing a vector of communications retrieved from the agent's long-term memory.
147 ///
148 /// # Business Logic
149 ///
150 /// - This method fetches the stored communications for the agent by interacting with the `load_long_term_memory` function.
151 /// - The function will return a list of communications that are indexed by the agent's unique ID.
152 /// - It handles the retrieval of the stored metadata and content for each communication.
153 #[cfg(feature = "mem")]
154 async fn get_ltm(&self) -> Result<Vec<Message>> {
155 load_long_term_memory(self.agent.id.clone()).await
156 }
157
158 /// Retrieves the concatenated context of all communications in the agent's long-term memory.
159 ///
160 /// # Returns
161 ///
162 /// (`String`): A string containing the concatenated role and content of all communications stored in the agent's long-term memory.
163 ///
164 /// # Business Logic
165 ///
166 /// - This method calls the `long_term_memory_context` function to generate a string representation of the agent's entire long-term memory.
167 /// - The context string is composed of each communication's role and content, joined by new lines.
168 /// - It provides a quick overview of the agent's memory in a human-readable format.
169 #[cfg(feature = "mem")]
170 async fn ltm_context(&self) -> String {
171 long_term_memory_context(self.agent.id.clone()).await
172 }
173
174 async fn generate(&mut self, request: &str) -> Result<String> {
175 #[cfg(feature = "gem")]
176 use gems::{chat::ChatBuilder, messages::Content, traits::CTrait};
177
178 #[cfg(feature = "oai")]
179 use openai_dive::v1::{models::Gpt4Model, resources::chat::{ChatMessage, ChatMessageContent, ChatCompletionResponseFormat}};
180
181 #[cfg(feature = "cld")]
182 use anthropic_ai_sdk::types::message::{
183 ContentBlock, CreateMessageParams, Message as AnthMessage,
184 MessageClient, RequiredMessageParams, Role,
185 };
186
187 #[cfg(feature = "xai")]
188 use x_ai::chat_compl::{ChatCompletionsRequestBuilder, Message as XaiMessage};
189
190 #[cfg(feature = "co")]
191 use cohere_rust::api::chat::ChatRequest;
192
193 #[cfg(feature = "hf")]
194 use serde_json::{Value as JsonValue, json};
195
196 match &mut self.client {
197 #[cfg(feature = "gem")]
198 ClientType::Gemini(gem_client) => {
199 let parameters = ChatBuilder::default()
200 .messages(vec![gems::messages::Message::User {
201 content: Content::Text(request.to_string()),
202 name: None,
203 }])
204 .build()?;
205
206 let result = gem_client.chat().generate(parameters).await;
207 Ok(result.unwrap_or_default())
208 }
209
210 #[cfg(feature = "oai")]
211 ClientType::OpenAI(oai_client) => {
212 use openai_dive::v1::resources::chat::ChatCompletionParametersBuilder;
213
214 let parameters = ChatCompletionParametersBuilder::default()
215 .model(Gpt4Model::Gpt4O.to_string())
216 .messages(vec![ChatMessage::User {
217 content: ChatMessageContent::Text(request.to_string()),
218 name: None,
219 }])
220 .response_format(ChatCompletionResponseFormat::Text)
221 .build()?;
222
223 let result = oai_client.chat().create(parameters).await?;
224 let message = &result.choices[0].message;
225
226 Ok(match message {
227 ChatMessage::Assistant {
228 content: Some(chat_content),
229 ..
230 } => chat_content.to_string(),
231 ChatMessage::User { content, .. } => content.to_string(),
232 ChatMessage::System { content, .. } => content.to_string(),
233 ChatMessage::Developer { content, .. } => content.to_string(),
234 ChatMessage::Tool { content, .. } => content.to_string(),
235 _ => String::new(),
236 })
237 }
238
239 #[cfg(feature = "cld")]
240 ClientType::Anthropic(client) => {
241 let body = CreateMessageParams::new(RequiredMessageParams {
242 model: "claude-opus-4-6".to_string(),
243 messages: vec![AnthMessage::new_text(Role::User, request.to_string())],
244 max_tokens: 1024,
245 });
246
247 let chat_response = client.create_message(Some(&body)).await?;
248 Ok(chat_response
249 .content
250 .iter()
251 .filter_map(|block| match block {
252 ContentBlock::Text { text, .. } => Some(text.as_str()),
253 _ => None,
254 })
255 .collect::<Vec<_>>()
256 .join("\n"))
257 }
258
259 #[cfg(feature = "xai")]
260 ClientType::Xai(xai_client) => {
261 use x_ai::traits::ChatCompletionsFetcher;
262
263 let messages = vec![XaiMessage::text("user", request)];
264
265 let rb = ChatCompletionsRequestBuilder::new(
266 xai_client.clone(),
267 "grok-4".into(),
268 messages,
269 )
270 .temperature(0.0)
271 .stream(false);
272
273 let req = rb.clone().build()?;
274 let chat = rb.create_chat_completion(req).await?;
275 Ok(chat.choices[0].message.content.to_string())
276 }
277
278 #[cfg(feature = "co")]
279 ClientType::Cohere(co_client) => {
280 let chat_request = ChatRequest {
281 message: request,
282 ..Default::default()
283 };
284
285 let mut receiver = match co_client.chat(&chat_request).await {
286 Ok(rx) => rx,
287 Err(e) => return Err(anyhow::anyhow!("Cohere API initialization failed: {}", e)),
288 };
289 let mut full_text = String::new();
290 while let Some(res) = receiver.recv().await {
291 match res {
292 Ok(cohere_rust::api::chat::ChatStreamResponse::ChatTextGeneration { text, .. }) => {
293 full_text.push_str(&text);
294 }
295 Ok(_) => {}
296 // Err(e) => return Err(anyhow!("Cohere chat error: {:?}", e)),
297 Err(_) => {},
298 }
299 }
300 Ok(full_text)
301 }
302
303 #[cfg(feature = "hf")]
304 ClientType::HuggingFace(hf_client) => {
305 let model_id = hf_model_from_str(&hf_client.model);
306 let result = hf_client
307 .client
308 .inference()
309 .create(request, model_id)
310 .await
311 .map_err(|e| anyhow::anyhow!("HuggingFace inference failed: {}", e))?;
312 let text = match result {
313 api_huggingface::components::inference_shared::InferenceResponse::Single(output) => output.generated_text,
314 api_huggingface::components::inference_shared::InferenceResponse::Batch(mut batch) => {
315 batch.pop().map(|o| o.generated_text).unwrap_or_default()
316 }
317 _ => String::new(),
318 };
319 Ok(text)
320 }
321
322 #[allow(unreachable_patterns)]
323 _ => {
324 return Err(anyhow!(
325 "No valid AI client configured. Enable `hf`, `co`, `gem`, `oai`, `cld`, or `xai` feature."
326 ));
327 }
328 }
329 }
330
331 async fn imagen(&mut self, request: &str) -> Result<Vec<u8>> {
332 #[cfg(feature = "gem")]
333 use gems::{imagen::ImageGenBuilder, messages::Content, models::Model, traits::CTrait};
334
335 #[cfg(feature = "hf")]
336 use serde_json::json;
337
338 match &mut self.client {
339 #[cfg(feature = "gem")]
340 ClientType::Gemini(gem_client) => {
341 gem_client.set_model(Model::Imagen4);
342
343 let input = gems::messages::Message::User {
344 content: Content::Text(request.into()),
345 name: None,
346 };
347
348 let params = ImageGenBuilder::default()
349 .model(Model::Imagen4)
350 .input(input)
351 .build()?;
352
353 let image_bytes = gem_client.images().generate(params).await;
354 Ok(image_bytes.unwrap_or_default())
355 }
356
357 #[cfg(feature = "oai")]
358 ClientType::OpenAI(oai_client) => {
359 // TODO: Implement this
360 Ok(Default::default())
361 }
362
363 #[cfg(feature = "cld")]
364 ClientType::Anthropic(client) => {
365 // TODO: Implement this
366 Ok(Default::default())
367 }
368
369 #[cfg(feature = "xai")]
370 ClientType::Xai(xai_client) => {
371 // TODO: Implement this
372 Ok(Default::default())
373 }
374
375 #[cfg(feature = "co")]
376 ClientType::Cohere(_co_client) => {
377 // Cohere does not support image generation
378 Ok(Default::default())
379 }
380
381 #[cfg(feature = "hf")]
382 ClientType::HuggingFace(hf_client) => {
383 let model_id = hf_model_from_str(&hf_client.model);
384 let result = hf_client
385 .client
386 .inference()
387 .create(request, model_id)
388 .await
389 .map_err(|e| anyhow::anyhow!("HuggingFace imagen failed: {}", e))?;
390 let text = match result {
391 api_huggingface::components::inference_shared::InferenceResponse::Single(output) => output.generated_text,
392 api_huggingface::components::inference_shared::InferenceResponse::Batch(mut batch) => {
393 batch.pop().map(|o| o.generated_text).unwrap_or_default()
394 }
395 _ => String::new(),
396 };
397 Ok(text.into_bytes())
398 }
399
400 #[allow(unreachable_patterns)]
401 _ => {
402 return Err(anyhow!(
403 "No valid AI client configured. Enable `hf`, `co`, `gem`, `oai`, `cld`, or `xai` feature."
404 ));
405 }
406 }
407 }
408
409 async fn stream(&mut self, request: &str) -> Result<ReqResponse> {
410 #[cfg(feature = "gem")]
411 use gems::{messages::Content, models::Model, stream::StreamBuilder, traits::CTrait};
412
413 #[cfg(any(feature = "oai", feature = "cld", feature = "hf"))]
414 use futures::StreamExt;
415
416 #[cfg(feature = "oai")]
417 use {
418 openai_dive::v1::resources::chat::{
419 ChatCompletionParametersBuilder, ChatMessage, ChatMessageContent,
420 },
421 };
422
423 #[cfg(feature = "cld")]
424 use {
425 anthropic_ai_sdk::types::message::{
426 ContentBlockDelta, CreateMessageParams, Message as AnthMessage,
427 MessageClient, RequiredMessageParams, Role, StreamEvent,
428 },
429 };
430
431 #[cfg(feature = "xai")]
432 use {
433 x_ai::chat_compl::{ChatCompletionsRequestBuilder, Message as XaiMessage},
434 x_ai::traits::ClientConfig,
435 };
436
437 #[cfg(feature = "hf")]
438 use {
439 serde_json::{Value as JsonValue, json},
440 };
441
442 let request_owned = request.to_string();
443 match &mut self.client {
444 #[cfg(feature = "gem")]
445 ClientType::Gemini(gem_client) => {
446 let parameters = StreamBuilder::default()
447 .model(Model::Flash3Preview)
448 .input(gems::messages::Message::User {
449 content: Content::Text(request_owned.clone()),
450 name: None,
451 })
452 .build()?;
453
454 let resp = gem_client.stream().generate(parameters).await?;
455 let (tx, rx) = tokio::sync::mpsc::channel::<String>(100);
456
457 tokio::spawn(async move {
458 let mut resp = resp;
459 let mut buffer = String::new();
460
461 while let Ok(Some(chunk)) = resp.chunk().await {
462 if let Ok(text) = std::str::from_utf8(&chunk) {
463 buffer.push_str(text);
464 let mut parts: Vec<&str> =
465 buffer.split("\n\n").collect();
466 let new_buffer = if !buffer.ends_with("\n\n") {
467 parts.pop().unwrap_or("").to_string()
468 } else {
469 String::new()
470 };
471
472 for part in parts {
473 for line in part.lines() {
474 if let Some(data) =
475 line.strip_prefix("data: ")
476 {
477 let data = data.trim();
478 if data == "[DONE]" {
479 continue;
480 }
481 if let Ok(json) =
482 serde_json::from_str::<serde_json::Value>(data)
483 {
484 if let Some(text) = json
485 .get("candidates")
486 .and_then(|c| c.get(0))
487 .and_then(|c| c.get("content"))
488 .and_then(|c| c.get("parts"))
489 .and_then(|p| p.get(0))
490 .and_then(|p| p.get("text"))
491 .and_then(|t| t.as_str())
492 {
493 let _ = tx
494 .send(text.to_string())
495 .await;
496 }
497 }
498 }
499 }
500 }
501
502 buffer = new_buffer;
503 }
504 }
505 });
506
507 Ok(ReqResponse(Some(rx)))
508 }
509
510 #[cfg(feature = "oai")]
511 ClientType::OpenAI(oai_client) => {
512 let oai_client = oai_client.clone();
513 let request_owned = request_owned.clone();
514 let (tx, rx) = tokio::sync::mpsc::channel::<String>(100);
515
516 tokio::spawn(async move {
517 let parameters =
518 ChatCompletionParametersBuilder::default()
519 .model("gpt-5")
520 .messages(vec![ChatMessage::User {
521 content: ChatMessageContent::Text(
522 request_owned,
523 ),
524 name: None,
525 }])
526 .build()
527 .unwrap();
528
529 if let Ok(mut stream) =
530 oai_client.chat().create_stream(parameters).await
531 {
532 while let Some(response) = stream.next().await {
533 match response {
534 Ok(chat_response) => {
535 for choice in chat_response.choices {
536 let text_opt = match &choice.delta {
537 openai_dive::v1::resources::chat::DeltaChatMessage::Assistant {
538 content: Some(
539 openai_dive::v1::resources::chat::ChatMessageContent::Text(text),
540 ),
541 ..
542 } => Some(text.clone()),
543 openai_dive::v1::resources::chat::DeltaChatMessage::Untagged {
544 content: Some(
545 openai_dive::v1::resources::chat::ChatMessageContent::Text(text),
546 ),
547 ..
548 } => Some(text.clone()),
549 _ => None,
550 };
551 if let Some(t) = text_opt {
552 let _ = tx.send(t).await;
553 }
554 }
555 }
556 Err(_) => break,
557 }
558 }
559 }
560 });
561
562 Ok(ReqResponse(Some(rx)))
563 }
564
565 #[cfg(feature = "cld")]
566 ClientType::Anthropic(client) => {
567 let client = client.clone();
568 let request_owned = request_owned.clone();
569 let (tx, rx) = tokio::sync::mpsc::channel::<String>(100);
570
571 tokio::spawn(async move {
572 let body = CreateMessageParams::new(
573 RequiredMessageParams {
574 model: "claude-opus-4-6".to_string(),
575 messages: vec![AnthMessage::new_text(
576 Role::User,
577 request_owned,
578 )],
579 max_tokens: 1024,
580 },
581 )
582 .with_stream(true);
583
584 if let Ok(mut stream) =
585 client.create_message_streaming(&body).await
586 {
587 while let Some(event_result) = stream.next().await {
588 if let Ok(StreamEvent::ContentBlockDelta { delta, .. }) = event_result {
589 if let ContentBlockDelta::TextDelta { text } = delta {
590 let _ = tx.send(text).await;
591 }
592 }
593 }
594 }
595 });
596
597 Ok(ReqResponse(Some(rx)))
598 }
599
600 #[cfg(feature = "xai")]
601 ClientType::Xai(xai_client) => {
602 let messages = vec![XaiMessage::text("user", request_owned)];
603 let req = ChatCompletionsRequestBuilder::new(
604 xai_client.clone(),
605 "grok-4".into(),
606 messages,
607 )
608 .stream(true)
609 .build()?;
610
611 let resp = ClientConfig::request(
612 &*xai_client,
613 reqwest::Method::POST,
614 "chat/completions",
615 )
616 .map_err(|e| {
617 anyhow::anyhow!("Failed to build xAI request: {}", e)
618 })?
619 .json(&req)
620 .send()
621 .await?;
622
623 let (tx, rx) = tokio::sync::mpsc::channel::<String>(100);
624
625 tokio::spawn(async move {
626 let mut resp = resp;
627 let mut buffer = String::new();
628
629 while let Ok(Some(chunk)) = resp.chunk().await {
630 if let Ok(text) = std::str::from_utf8(&chunk) {
631 buffer.push_str(text);
632 let mut parts: Vec<&str> =
633 buffer.split("\n\n").collect();
634 let new_buffer = if !buffer.ends_with("\n\n") {
635 parts.pop().unwrap_or("").to_string()
636 } else {
637 String::new()
638 };
639
640 for part in parts {
641 for line in part.lines() {
642 if let Some(data) =
643 line.strip_prefix("data: ")
644 {
645 let data = data.trim();
646 if data == "[DONE]" {
647 continue;
648 }
649 if let Ok(json) =
650 serde_json::from_str::<serde_json::Value>(data)
651 {
652 if let Some(content) = json
653 .get("choices")
654 .and_then(|c| c.get(0))
655 .and_then(|c| c.get("delta"))
656 .and_then(|d| d.get("content"))
657 .and_then(|c| c.as_str())
658 {
659 let _ = tx
660 .send(content.to_string())
661 .await;
662 }
663 }
664 }
665 }
666 }
667
668 buffer = new_buffer;
669 }
670 }
671 });
672
673 Ok(ReqResponse(Some(rx)))
674 }
675
676 #[cfg(feature = "co")]
677 ClientType::Cohere(co_client) => {
678 let chat_request = cohere_rust::api::chat::ChatRequest {
679 message: request,
680 ..Default::default()
681 };
682 let co_result = co_client.chat(&chat_request).await;
683 let (tx, rx) = tokio::sync::mpsc::channel::<String>(100);
684
685 if let Ok(mut receiver) = co_result {
686 tokio::spawn(async move {
687 while let Some(res) = receiver.recv().await {
688 if let Ok(resp) = res {
689 if let cohere_rust::api::chat::ChatStreamResponse::ChatTextGeneration {
690 text, ..
691 } = resp
692 {
693 let _ = tx.send(text).await;
694 }
695 }
696 }
697 });
698 }
699
700 Ok(ReqResponse(Some(rx)))
701 }
702
703 #[cfg(feature = "hf")]
704 ClientType::HuggingFace(hf_client) => {
705 let model_id = hf_model_from_str(&hf_client.model);
706 let (tx, rx) = tokio::sync::mpsc::channel::<String>(256);
707 let client = hf_client.client.clone();
708 let model_id_owned = model_id.to_string();
709 let request_owned_clone = request_owned.clone();
710
711 tokio::spawn(async move {
712 match client
713 .inference()
714 .create(&request_owned_clone, &model_id_owned)
715 .await
716 {
717 Ok(result) => {
718 use api_huggingface::components::inference_shared::InferenceResponse;
719 let text = match result {
720 InferenceResponse::Single(o) => o.generated_text,
721 InferenceResponse::Batch(mut v) => {
722 v.pop().map(|o| o.generated_text).unwrap_or_default()
723 }
724 _ => String::new(),
725 };
726 for word in text.split_whitespace() {
727 let _ = tx.send(format!("{word} ")).await;
728 }
729 }
730 Err(e) => {
731 let _ = tx
732 .send(format!("[HuggingFace error: {}]", e))
733 .await;
734 }
735 }
736 });
737
738 Ok(ReqResponse(Some(rx)))
739 }
740
741
742
743
744 #[allow(unreachable_patterns)]
745 _ => {
746 return Err(anyhow!(
747 "No valid AI client configured. \
748 Enable `hf`, `co`, `gem`, `oai`, `cld`, or `xai` feature."
749 ));
750 }
751 }
752 }
753 }
754 };
755
756 proc_macro::TokenStream::from(expanded)
757}
758
759// Copyright 2026 Mahmoud Harmouch.
760//
761// Licensed under the MIT license
762// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
763// option. This file may not be copied, modified, or distributed
764// except according to those terms.