1use std::sync::Arc;
4use tokio::sync::broadcast;
5use tracing::{info, instrument, error};
6use anyhow;
7
8use crate::error::{Error, Result};
9use crate::agent::context::ContextInjector;
10use crate::agent::message::{Message, Role, Content};
11use crate::agent::provider::Provider;
12use crate::skills::tool::{Tool, ToolSet};
13use crate::agent::streaming::StreamingResponse;
14use crate::skills::tool::memory::{SearchHistoryTool, RememberThisTool, TieredSearchTool, FetchDocumentTool}; use crate::agent::memory::MemoryManager;
16use crate::agent::context::{ContextManager, ContextConfig}; use crate::agent::multi_agent::{Coordinator, AgentRole, MultiAgent, AgentMessage};
18use crate::agent::personality::{Persona, PersonalityManager};
19use crate::agent::scheduler::Scheduler;
20use crate::skills::tool::{DelegateTool, CronTool};
21use crate::infra::notification::{Notifier, NotifyChannel};
22
23#[derive(Debug, Clone)]
25pub struct AgentConfig {
26 pub name: String,
28 pub model: String,
30 pub preamble: String,
32 pub temperature: Option<f64>,
34 pub max_tokens: Option<u64>,
36 pub extra_params: Option<serde_json::Value>,
38 pub tool_policy: RiskyToolPolicy,
40 pub max_history_messages: usize,
42 pub max_tool_output_chars: usize,
44 pub json_mode: bool,
46 pub persona: Option<Persona>,
48 pub role: AgentRole,
50}
51
52impl Default for AgentConfig {
53 fn default() -> Self {
54 Self {
55 name: "agent".to_string(),
56 model: "gpt-4o".to_string(),
57 preamble: "You are a helpful AI assistant.".to_string(),
58 temperature: Some(0.7),
59 max_tokens: Some(4096),
60 extra_params: None,
61 tool_policy: RiskyToolPolicy::default(),
62 max_history_messages: 20,
63 max_tool_output_chars: 4096,
64 json_mode: false,
65 persona: None,
66 role: AgentRole::Assistant,
67 }
68 }
69}
70
71#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
73#[serde(rename_all = "snake_case")]
74pub enum ToolPolicy {
75 Auto,
77 RequiresApproval,
79 Disabled,
81}
82
83#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
85pub struct RiskyToolPolicy {
86 pub default_policy: ToolPolicy,
88 pub overrides: std::collections::HashMap<String, ToolPolicy>,
90}
91
92impl Default for RiskyToolPolicy {
93 fn default() -> Self {
94 Self {
95 default_policy: ToolPolicy::Auto,
96 overrides: std::collections::HashMap::new(),
97 }
98 }
99}
100
101#[derive(Debug, Clone, serde::Serialize)]
103#[serde(tag = "type", content = "data", rename_all = "snake_case")]
104pub enum AgentEvent {
105 Thinking { prompt: String },
107 ToolCall { tool: String, input: String },
109 ApprovalPending { tool: String, input: String },
111 ToolResult { tool: String, output: String },
113 Response { content: String },
115 Error { message: String },
117}
118
119#[async_trait::async_trait]
121pub trait ApprovalHandler: Send + Sync {
122 async fn approve(&self, tool_name: &str, arguments: &str) -> anyhow::Result<bool>;
124}
125
126pub struct RejectAllApprovalHandler;
128
129#[async_trait::async_trait]
130impl ApprovalHandler for RejectAllApprovalHandler {
131 async fn approve(&self, _tool: &str, _args: &str) -> anyhow::Result<bool> {
132 Ok(false)
133 }
134}
135
136#[derive(Debug)]
138pub struct ApprovalRequest {
139 pub id: String,
141 pub tool_name: String,
143 pub arguments: String,
145 pub responder: tokio::sync::oneshot::Sender<bool>,
147}
148
149pub struct ChannelApprovalHandler {
151 sender: tokio::sync::mpsc::Sender<ApprovalRequest>,
152}
153
154impl ChannelApprovalHandler {
155 pub fn new(sender: tokio::sync::mpsc::Sender<ApprovalRequest>) -> Self {
157 Self { sender }
158 }
159}
160
161#[async_trait::async_trait]
162impl ApprovalHandler for ChannelApprovalHandler {
163 async fn approve(&self, tool_name: &str, arguments: &str) -> anyhow::Result<bool> {
164 let (tx, rx) = tokio::sync::oneshot::channel();
165
166 let request = ApprovalRequest {
167 id: uuid::Uuid::new_v4().to_string(),
168 tool_name: tool_name.to_string(),
169 arguments: arguments.to_string(),
170 responder: tx,
171 };
172
173 self.sender.send(request).await
174 .map_err(|_| Error::Internal("Approval channel closed".to_string()))?;
175
176 let approved = rx.await
178 .map_err(|_| Error::Internal("Approval responder dropped".to_string()))?;
179
180 Ok(approved)
181 }
182}
183
184pub struct Agent<P: Provider> {
188 provider: Arc<P>,
189 tools: ToolSet,
190 config: AgentConfig,
191 context_manager: ContextManager,
192 events: broadcast::Sender<AgentEvent>,
193 approval_handler: Arc<dyn ApprovalHandler>,
194 notifier: Option<Arc<dyn Notifier>>,
195}
196
197impl<P: Provider> Agent<P> {
198 pub fn builder(provider: P) -> AgentBuilder<P> {
200 AgentBuilder::new(provider)
201 }
202
203 pub fn subscribe(&self) -> broadcast::Receiver<AgentEvent> {
205 self.events.subscribe()
206 }
207
208 fn emit(&self, event: AgentEvent) {
210 if let Err(e) = self.events.send(event) {
211 tracing::debug!("Failed to emit event (no receivers): {}", e);
212 }
213 }
214
215 pub async fn notify(&self, channel: NotifyChannel, message: &str) -> Result<()> {
217 if let Some(notifier) = &self.notifier {
218 notifier.notify(channel, message).await
219 } else {
220 tracing::warn!("Agent tried to notify but no notifier is configured: {}", message);
222 Ok(())
223 }
224 }
225
226 #[instrument(skip(self, prompt), fields(model = %self.config.model))]
228 pub async fn prompt(&self, prompt: impl Into<String>) -> Result<String> {
229 let prompt_str = prompt.into();
230 self.emit(AgentEvent::Thinking { prompt: prompt_str.clone() });
231
232 let messages = vec![
233 Message::user(prompt_str)
234 ];
235
236 self.chat(messages).await
237 }
238
239 #[instrument(skip(self, messages), fields(model = %self.config.model, message_count = messages.len()))]
241 pub async fn chat(&self, mut messages: Vec<Message>) -> Result<String> {
242 let mut steps = 0;
243 const MAX_STEPS: usize = 15;
244
245 loop {
246 if steps >= MAX_STEPS {
247 return Err(Error::agent_config("Max agent steps exceeded"));
248 }
249 steps += 1;
250
251 if let Some(last) = messages.last() {
252 if last.role == Role::User {
253 self.emit(AgentEvent::Thinking { prompt: last.content.as_text() });
254 }
255 }
256
257 info!("Agent starting chat completion (step {})", steps);
258
259 let context_messages = self.context_manager.build_context(&messages)
261 .map_err(|e| Error::agent_config(format!("Failed to build context: {}", e)))?;
262
263 let stream = self.stream_chat(context_messages).await?;
264
265 let mut full_text = String::new();
266 let mut tool_calls = Vec::new(); let mut stream_inner = stream.into_inner();
269
270 use futures::StreamExt;
272 while let Some(chunk) = stream_inner.next().await {
273 match chunk? {
274 crate::agent::streaming::StreamingChoice::Message(text) => {
275 full_text.push_str(&text);
276 }
277 crate::agent::streaming::StreamingChoice::ToolCall { id, name, arguments } => {
278 tool_calls.push((id, name, arguments));
279 }
280 crate::agent::streaming::StreamingChoice::ParallelToolCalls(map) => {
281 let mut sorted: Vec<_> = map.into_iter().collect();
282 sorted.sort_by_key(|(k,_)| *k);
283 for (_, tc) in sorted {
284 tool_calls.push((tc.id, tc.name, tc.arguments));
285 }
286 }
287 _ => {}
288 }
289 }
290
291 if tool_calls.is_empty() {
293 self.emit(AgentEvent::Response { content: full_text.clone() });
294 return Ok(full_text);
295 }
296
297 let mut parts = Vec::new();
300 if !full_text.is_empty() {
301 parts.push(crate::agent::message::ContentPart::Text { text: full_text.clone() });
302 }
303 for (id, name, args) in &tool_calls {
304 parts.push(crate::agent::message::ContentPart::ToolCall {
305 id: id.clone(),
306 name: name.clone(),
307 arguments: args.clone(),
308 });
309 }
310 messages.push(Message {
311 role: Role::Assistant,
312 name: None,
313 content: Content::Parts(parts),
314 });
315
316 let tools = &self.tools;
318 let policy = &self.config.tool_policy;
319 let events = &self.events;
320 let approval_handler = &self.approval_handler;
321
322 let mut futures = Vec::new();
323
324 for (id, name, args) in tool_calls {
325 let name_clone = name.clone();
326 let id_clone = id.clone();
327 let args_str = args.to_string();
328
329 futures.push(async move {
330 let effective_policy = policy.overrides.get(&name_clone)
332 .unwrap_or(&policy.default_policy);
333
334 let result = match effective_policy {
335 ToolPolicy::Disabled => {
336 Err(Error::tool_execution(name_clone.clone(), "Tool execution is disabled by policy".to_string()))
337 }
338 ToolPolicy::RequiresApproval => {
339 let _ = events.send(AgentEvent::ApprovalPending {
340 tool: name_clone.clone(),
341 input: args_str.clone()
342 });
343
344 match approval_handler.approve(&name_clone, &args_str).await {
346 Ok(true) => {
347 let _ = events.send(AgentEvent::ToolCall {
349 tool: name_clone.clone(),
350 input: args_str.clone()
351 });
352 tools.call(&name_clone, &args_str).await
353 .map_err(|e| Error::tool_execution(name_clone.clone(), e.to_string()))
354 }
355 Ok(false) => {
356 Err(Error::ToolApprovalRequired { tool_name: name_clone.clone() })
357 }
358 Err(e) => {
359 Err(Error::tool_execution(name_clone.clone(), format!("Approval check failed: {}", e)))
360 }
361 }
362 }
363 ToolPolicy::Auto => {
364 let _ = events.send(AgentEvent::ToolCall {
365 tool: name_clone.clone(),
366 input: args_str.clone()
367 });
368 tools.call(&name_clone, &args_str).await
370 .map_err(|e| Error::tool_execution(name_clone.clone(), e.to_string()))
371 }
372 };
373
374 let output = match result {
375 Ok(s) => {
376 let _ = events.send(AgentEvent::ToolResult {
377 tool: name_clone.clone(),
378 output: s.clone()
379 });
380 s
381 }
382 Err(e) => {
383 let _ = events.send(AgentEvent::Error { message: e.to_string() });
384 format!("Error: {}", e)
385 }
386 };
387
388 (id_clone, output, name_clone)
389 });
390 }
391
392 let results = futures::future::join_all(futures).await;
393
394 for (id, output, name) in results {
396 messages.push(Message {
397 role: Role::Tool,
398 name: None,
399 content: Content::Parts(vec![crate::agent::message::ContentPart::ToolResult {
400 tool_call_id: id,
401 content: output,
402 name: Some(name),
403 }]),
404 });
405 }
406 }
407 }
408
409 pub async fn stream(&self, prompt: impl Into<String>) -> Result<StreamingResponse> {
411 let messages = vec![Message::user(prompt.into())];
412 self.stream_chat(messages).await
413 }
414
415 pub async fn stream_chat(&self, messages: Vec<Message>) -> Result<StreamingResponse> {
417 let mut extra = self.config.extra_params.clone().unwrap_or(serde_json::Value::Object(serde_json::Map::new()));
418
419 if self.config.json_mode {
421 if let serde_json::Value::Object(ref mut map) = extra {
422 if !map.contains_key("response_format") {
423 map.insert("response_format".to_string(), serde_json::json!({ "type": "json_object" }));
424 }
425 }
426 }
427
428 self.provider
429 .stream_completion(
430 &self.config.model,
431 Some(&self.config.preamble), messages,
433 self.tools.definitions().await,
434 self.config.temperature,
435 self.config.max_tokens,
436 Some(extra),
437 )
438 .await
439 }
440
441 #[instrument(skip(self, arguments), fields(tool_name = %name))]
443 pub async fn call_tool(&self, name: &str, arguments: &str) -> Result<String> {
444 let policy = self.config.tool_policy.overrides.get(name)
446 .unwrap_or(&self.config.tool_policy.default_policy);
447
448 match policy {
449 ToolPolicy::Disabled => {
450 return Err(Error::tool_execution(name.to_string(), "Tool execution is disabled by policy".to_string()));
451 }
452 ToolPolicy::RequiresApproval => {
453 self.emit(AgentEvent::ApprovalPending { tool: name.to_string(), input: arguments.to_string() });
454
455 match self.approval_handler.approve(name, arguments).await {
456 Ok(true) => {}, Ok(false) => return Err(Error::ToolApprovalRequired { tool_name: name.to_string() }),
458 Err(e) => return Err(Error::tool_execution(name.to_string(), format!("Approval check failed: {}", e)))
459 }
460 }
461 ToolPolicy::Auto => {} }
463
464 self.emit(AgentEvent::ToolCall { tool: name.to_string(), input: arguments.to_string() });
465
466 let result = self.tools.call(name, arguments).await;
467
468 match result {
469 Ok(mut output) => {
470 if output.len() > self.config.max_tool_output_chars {
472 let original_len = output.len();
473 output.truncate(self.config.max_tool_output_chars);
474 output.push_str(&format!("\n\n(Note: Output truncated from {} to {} chars to save tokens)",
475 original_len, self.config.max_tool_output_chars));
476 }
477
478 self.emit(AgentEvent::ToolResult { tool: name.to_string(), output: output.clone() });
479 Ok(output)
480 },
481 Err(e) => {
482 self.emit(AgentEvent::Error { message: e.to_string() });
483 Err(Error::tool_execution(name.to_string(), e.to_string()))
485 }
486 }
487 }
488
489 pub fn has_tool(&self, name: &str) -> bool {
491 self.tools.contains(name)
492 }
493
494 pub async fn tool_definitions(&self) -> Vec<crate::skills::tool::ToolDefinition> {
496 self.tools.definitions().await
497 }
498
499 pub fn config(&self) -> &AgentConfig {
501 &self.config
502 }
503
504 pub fn model(&self) -> &str {
506 &self.config.model
507 }
508
509 pub async fn listen(
511 &self,
512 mut user_input: tokio::sync::mpsc::Receiver<String>,
513 mut external_events: tokio::sync::mpsc::Receiver<AgentMessage>
514 ) -> Result<()> {
515 info!("Agent {} starting proactive loop", self.config.name);
516
517 loop {
518 tokio::select! {
519 input = user_input.recv() => {
521 match input {
522 Some(text) => {
523 if let Err(e) = self.process(&text).await {
524 error!("Error in proactive user task: {}", e);
525 }
526 }
527 None => {
528 info!("User input channel closed, exiting proactive loop");
529 break;
530 }
531 }
532 }
533
534 msg = external_events.recv() => {
536 match msg {
537 Some(message) => {
538 if let Err(e) = self.handle_message(message).await {
539 error!("Error in proactive external task: {}", e);
540 }
541 }
542 None => {
543 info!("External events channel closed, exiting proactive loop");
544 break;
545 }
546 }
547 }
548 }
549 }
550
551 Ok(())
552 }
553}
554
555pub struct AgentBuilder<P: Provider> {
557 provider: P,
558 tools: ToolSet,
559 config: AgentConfig,
560 injectors: Vec<Box<dyn ContextInjector>>,
561 approval_handler: Option<Arc<dyn ApprovalHandler>>,
562 notifier: Option<Arc<dyn Notifier>>,
563 has_sidecar: bool,
565 has_dynamic_skill: bool,
567}
568
569impl<P: Provider> AgentBuilder<P> {
570 pub fn new(provider: P) -> Self {
572 Self {
573 provider,
574 tools: ToolSet::new(),
575 config: AgentConfig::default(),
576 injectors: Vec::new(),
577 approval_handler: None,
578 notifier: None,
579 has_sidecar: false,
580 has_dynamic_skill: false,
581 }
582 }
583
584 pub fn model(mut self, model: impl Into<String>) -> Self {
586 self.config.model = model.into();
587 self
588 }
589
590 pub fn system_prompt(mut self, prompt: impl Into<String>) -> Self {
592 self.config.preamble = prompt.into();
593 self
594 }
595
596 pub fn preamble(self, prompt: impl Into<String>) -> Self {
598 self.system_prompt(prompt)
599 }
600
601 pub fn temperature(mut self, temp: f64) -> Self {
603 self.config.temperature = Some(temp);
604 self
605 }
606
607 pub fn max_tokens(mut self, tokens: u64) -> Self {
609 self.config.max_tokens = Some(tokens);
610 self
611 }
612
613 pub fn extra_params(mut self, params: serde_json::Value) -> Self {
615 self.config.extra_params = Some(params);
616 self
617 }
618
619 pub fn tool_policy(mut self, policy: RiskyToolPolicy) -> Self {
621 self.config.tool_policy = policy;
622 self
623 }
624
625 pub fn approval_handler(mut self, handler: impl ApprovalHandler + 'static) -> Self {
627 self.approval_handler = Some(Arc::new(handler));
628 self
629 }
630
631 pub fn max_history_messages(mut self, count: usize) -> Self {
633 self.config.max_history_messages = count;
634 self
635 }
636
637 pub fn max_tool_output_chars(mut self, count: usize) -> Self {
639 self.config.max_tool_output_chars = count;
640 self
641 }
642
643 pub fn json_mode(mut self, enable: bool) -> Self {
645 self.config.json_mode = enable;
646 self
647 }
648
649 pub fn persona(mut self, persona: Persona) -> Self {
651 self.config.persona = Some(persona);
652 self
653 }
654
655 pub fn notifier(mut self, notifier: impl Notifier + 'static) -> Self {
657 self.notifier = Some(Arc::new(notifier));
658 self
659 }
660
661 pub fn role(mut self, role: AgentRole) -> Self {
663 self.config.role = role;
664 self
665 }
666
667 pub fn context_injector(mut self, injector: impl ContextInjector + 'static) -> Self {
669 self.injectors.push(Box::new(injector));
670 self
671 }
672
673 pub fn tool<T: Tool + 'static>(mut self, tool: T) -> Self {
675 self.tools.add(tool);
676 self
677 }
678
679 pub fn shared_tool(mut self, tool: Arc<dyn Tool>) -> Self {
681 self.tools.add_shared(tool);
682 self
683 }
684
685 pub fn tools(mut self, tools: ToolSet) -> Self {
687 for (_, tool) in tools.iter() {
688 self.tools.add_shared(Arc::clone(tool));
689 }
690 self
691 }
692
693 pub fn with_memory(mut self, memory: Arc<MemoryManager>) -> Self {
695 self.tools.add(SearchHistoryTool::new(memory.clone()));
698 self.tools.add(RememberThisTool::new(memory.clone()));
699 self.tools.add(TieredSearchTool::new(memory.clone()));
700 self.tools.add(FetchDocumentTool::new(memory));
701
702 self
703 }
704
705 pub fn with_dynamic_skills(mut self, skill_loader: Arc<crate::skills::SkillLoader>) -> Result<Self> {
718 if self.has_sidecar {
720 return Err(Error::agent_config(
721 "Security Error: Cannot enable DynamicSkill when Python Sidecar is configured. \
722 These are mutually exclusive due to context pollution risks. \
723 See SECURITY.md for details."
724 ));
725 }
726
727 for skill_ref in skill_loader.skills.iter() {
729 self.tools.add_shared(Arc::clone(skill_ref.value()) as Arc<dyn crate::skills::tool::Tool>);
730 }
731
732 self.tools.add(crate::skills::ClawHubTool::new(Arc::clone(&skill_loader)));
734 self.tools.add(crate::skills::ReadSkillDoc::new(skill_loader));
735
736 self.has_dynamic_skill = true;
737
738 Ok(self)
739 }
740
741 pub async fn with_code_interpreter(mut self, address: impl Into<String>) -> Result<Self> {
754 if self.has_dynamic_skill {
756 return Err(Error::agent_config(
757 "Security Error: Cannot enable Python Sidecar when DynamicSkill is configured. \
758 These are mutually exclusive due to context pollution risks. \
759 See SECURITY.md for details."
760 ));
761 }
762
763 let sidecar = crate::skills::capabilities::Sidecar::connect(address.into()).await?;
764 let shared_sidecar = Arc::new(tokio::sync::Mutex::new(sidecar));
765
766 self.tools.add(crate::skills::tool::code_interpreter::CodeInterpreter::new(shared_sidecar));
767 self.has_sidecar = true;
768
769 Ok(self)
770 }
771
772 pub fn build(mut self) -> Result<Agent<P>> {
783 if self.config.model.is_empty() {
785 return Err(Error::agent_config("model name cannot be empty"));
786 }
787 if self.config.max_history_messages == 0 {
788 return Err(Error::agent_config("max_history_messages must be at least 1"));
789 }
790
791 if !self.has_sidecar && !self.has_dynamic_skill {
793 info!("No execution model configured. Auto-enabling DynamicSkill (default)...");
794
795 let skill_loader = Arc::new(crate::skills::SkillLoader::new("./skills"));
797
798 match tokio::task::block_in_place(|| {
800 tokio::runtime::Handle::current().block_on(skill_loader.load_all())
801 }) {
802 Ok(_) => {
803 info!("Loaded DynamicSkills from ./skills");
804
805 for skill_ref in skill_loader.skills.iter() {
807 self.tools.add_shared(Arc::clone(skill_ref.value()) as Arc<dyn crate::skills::tool::Tool>);
808 }
809
810 self.tools.add(crate::skills::ClawHubTool::new(Arc::clone(&skill_loader)));
812 self.tools.add(crate::skills::ReadSkillDoc::new(skill_loader));
813
814 self.has_dynamic_skill = true;
815 },
816 Err(e) => {
817 info!("DynamicSkill auto-enable skipped (no skills found): {}", e);
819 }
821 }
822 }
823
824 let (tx, _) = broadcast::channel(1000);
825
826 let mut context_config = ContextConfig::default();
827 context_config.max_history_messages = self.config.max_history_messages;
828 if let Some(tokens) = self.config.max_tokens {
829 context_config.response_reserve = tokens as usize;
833 }
834
835 let mut context_manager = ContextManager::new(context_config);
836 context_manager.set_system_prompt(self.config.preamble.clone());
837
838 context_manager.add_injector(Box::new(self.tools.clone()));
841
842 for injector in self.injectors {
843 context_manager.add_injector(injector);
844 }
845
846 if let Some(persona) = &self.config.persona {
848 context_manager.add_injector(Box::new(PersonalityManager::new(persona.clone())));
849 }
850
851 Ok(Agent {
852 provider: Arc::new(self.provider),
853 tools: self.tools,
854 config: self.config,
855 context_manager,
856 events: tx,
857 approval_handler: self.approval_handler.unwrap_or_else(|| Arc::new(RejectAllApprovalHandler)),
858 notifier: self.notifier,
859 })
860 }
861
862 pub fn with_delegation(mut self, coordinator: Arc<Coordinator>) -> Self {
864 self.tools.add(DelegateTool::new(Arc::downgrade(&coordinator)));
865 self
866 }
867
868 pub fn with_scheduler(mut self, scheduler: Arc<Scheduler>) -> Self {
870 self.tools.add(CronTool::new(Arc::downgrade(&scheduler)));
871 self
872 }
873}
874
875#[async_trait::async_trait]
876impl<P: Provider> MultiAgent for Agent<P> {
877 fn role(&self) -> AgentRole {
878 self.config.role.clone()
879 }
880
881 async fn handle_message(&self, message: AgentMessage) -> Result<Option<AgentMessage>> {
882 info!("Agent {:?} handling message from {:?}", self.role(), message.from);
883 let response = self.prompt(message.content).await?;
884
885 Ok(Some(AgentMessage {
886 from: self.role(),
887 to: Some(message.from),
888 content: response,
889 msg_type: crate::agent::multi_agent::MessageType::Response,
890 }))
891 }
892
893 async fn process(&self, input: &str) -> Result<String> {
894 self.prompt(input).await
895 }
896}
897
898#[cfg(test)]
899mod tests {
900 use super::*;
901
902 #[test]
903 fn test_agent_config_default() {
904 let config = AgentConfig::default();
905 assert_eq!(config.model, "gpt-4o");
906 assert_eq!(config.max_tokens, Some(4096));
907 }
908}