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