Skip to main content

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.